Skip to navigation

The Sentinel F source

Name: SmoothTileCorners (Part 1 of 4) [Show more] Type: Subroutine Category: Landscape Summary: Smooth a row or column of tile corners (a "strip of tiles")
Context: See this subroutine on its own page References: This subroutine is called as follows: * SmoothTileData calls SmoothTileCorners

This part copies the row or column of tile corners to a temporary workspace.
Arguments: A Controls which tile corners we smooth in the tile data: * Bit 7 clear = smooth the row of tile corners at z-coordinate zTile * Bit 7 set = smooth the column of tile corners at x-coordinate xTile
.SmoothTileCorners ORA smoothingAction \ We configured the smoothing action in bit 6 of STA processAction \ smoothingAction in the SmoothTileData routine before \ calling this routine, and bit 7 of A tells us whether \ to smooth a row or a column of tile corners, so this \ combines both configurations from bit 6 and bit 7 into \ one byte that we store in processAction LDX #34 \ We start by copying the tile data for the row/column \ that we want to smooth into the stripData workspace, \ so we can process it before copying it back into the \ tileData table \ \ In the following commentary I will refer to this \ copied row or column of tile corners as a "strip of \ tiles", as saying "row or column of tile corners" \ every time is a bit of a mouthful \ \ We actually create a strip of tile data containing 35 \ tile corners, with offsets 0 to 31 being the tile data \ for the strip we are smoothing and offsets 32 to 34 \ being repeats of the data for tile corners 0 to 2 \ \ So we effectively duplicate the first three tile \ corners onto the end of the strip so we can wrap the \ smoothing calculations around past the end of the \ strip .stri1 TXA \ Set A = X mod 32 AND #31 \ \ This ensures that for X = 32 to 34, we copy the tile \ data for tiles 0 to 2 (as A = 0 to 2 for X = 32 to 34) BIT processAction \ If bit 7 of processAction is clear then we are BPL stri2 \ smoothing a row of tiles at z-coordinate zTile, so \ jump to stri2 to iterate across xTile STA zTile \ If we get here then we are smoothing a column of tiles \ so set zTile to the counter in X so we iterate along \ the z-coordinate (i.e. coming out of the screen) JMP stri3 \ Jump to stri3 to skip the following .stri2 STA xTile \ If we get here then we are smoothing a row of tiles \ so set xTile to the counter in X so we iterate along \ the x-coordinate (i.e. from right to left) .stri3 JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile) STA stripData,X \ Store the tile data for (xTile, zTile) into the X-th \ byte in our temporary workspace DEX \ Decrement the tile counter in X BPL stri1 \ Loop back until we have copied tile data for all 32 \ tiles in the strip, plus three more tiles on the end BIT processAction \ If bit 6 of processAction is clear then we need to BVC stri11 \ smooth the strip by averaging tile altitudes, so jump \ to part 3 to implement this \ Otherwise fall through into part 2 to smooth the strip \ by moving outlier tiles
Name: SmoothTileCorners (Part 2 of 4) [Show more] Type: Subroutine Category: Landscape Summary: Smooth a strip by moving each outlier tile corner to the altitude of its closest immediate neighbour (in terms of altitude)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part smoothes the strip by working along the strip and applying the following algorithm: * If this tile corner is higher than both its neighbours, move it down * If this tile corner is lower than both its neighbours, move it up In each case, we move the tile corner until it is level with the closest one to its original altitude. This has the effect of smoothing out single-point spikes or troughs in the strip.
\ If we get here then bit 6 of processAction is set, so \ we smooth the tile strip by moving each outlier tile \ to the altitude of its closest immediate neighbour (in \ terms of altitude) \ \ This smoothes out single-point spikes or troughs LDX #31 \ We now work our way along the strip, smoothing the \ altitudes of tiles 1 to 32, so set a tile counter in X \ \ Note that we smooth tiles 1 to 32 rather than tiles \ 0 to 31 (tile 0 remains unchanged by the smoothing \ process) .stri4 \ In the following, we are processing the tile at \ position X + 1, i.e. stripData+1,X \ \ We smooth a tile by looking at the altitudes of the \ two tiles either side of that tile, i.e. stripData,X \ and stripData+2,X \ \ We are smoothing from high values of X to low values, \ so by this point the tile at stripData+2,X has already \ been smoothed \ \ Let's name the tiles as follows: \ \ * stripData+2,X = "previous tile" (as it has already \ been smoothed) \ \ * stripData+1,X = "this tile" (as this is the tile \ we are smoothing) \ \ * stripData,X = "next tile" (as this is the tile \ we will be smoothing next) \ \ The smoothing algorithm is implemented as follows, \ where we are comparing the altitude of each tile (as \ at this stage tileData only contains tile altitudes): \ \ * If this = previous, do nothing \ \ * If this < previous and this >= next, do nothing \ \ * If this < previous and this < next, set \ this = min(previous, next) \ \ * If this > previous and this <= next, do nothing \ \ * If this > previous and this > next, set \ this = max(previous, next) \ \ Or to simplify: \ \ * If this tile is higher than both its neighbours, \ move it down until it isn't \ \ * If this tile is lower than both its neighbours, \ move it up until it isn't \ \ So this algorithm smoothes the landscape by squeezing \ the landscape into a flatter shape, moving outlier \ tiles closer to the landscape's overall line LDA stripData+1,X \ If this tile is at the same altitude as the previous CMP stripData+2,X \ tile, jump to stri9 to move on to smoothing the next BEQ stri9 \ tile, as the transition from the previous tile to this \ tile is already flat BCS stri5 \ If this tile is higher than the previous tile, jump to \ stri5 \ If we get here then this tile is lower than the \ previous tile CMP stripData,X \ If this tile is at the same altitude or higher than BEQ stri9 \ the next tile, jump to stri9 to move on to smoothing BCS stri9 \ the next tile \ If we get here then this tile is lower than the \ previous tile and lower than the next tile LDA stripData+2,X \ Set the flags from comparing the previous and next CMP stripData,X \ tiles (so in stri6, tile A is the previous tile and \ tile B is the next tile) JMP stri6 \ Jump to stri6, so that: \ \ * If previous < next, set this = previous \ \ * If previous >= next, set this = next \ \ In other words, set: \ \ this = min(previous, next) \ \ before moving on to the next tile .stri5 \ If we get here then this tile is higher than the \ previous tile CMP stripData,X \ If this tile is at the same altitude or lower than the BEQ stri9 \ next tile jump to stri9 to move on to smoothing the BCC stri9 \ next tile \ If we get here then this tile is higher than the \ previous tile and this tile is higher than the next \ tile LDA stripData,X \ Set the flags from comparing the next and previous CMP stripData+2,X \ tiles (so in stri6, tile A is the next tile and \ tile B is the previous tile) \ Fall through into stri6, so that: \ \ * If next < previous, set this = previous \ \ * If next >= previous, set this = next \ \ In other words, set: \ \ this = max(previous, next) \ \ before moving on to the next tile .stri6 \ We get here after comparing two tiles; let's call them \ tile A and tile B BCC stri7 \ If tile A is lower than tile B, jump to stri7 to set \ the altitude of this tile to the altitude of the \ previous tile LDA stripData,X \ Set A to the altitude of the next tile and jump to JMP stri8 \ stri8 to set the altitude of this tile to the altitude \ of the next tile .stri7 LDA stripData+2,X \ Set A to the altitude of the previous tile .stri8 STA stripData+1,X \ Set the altitude of this tile to the value in A .stri9 DEX \ Decrement the strip tile counter in X BPL stri4 \ Loop back until we have worked our way through the \ whole strip BIT doNotPlayLandscape \ If bit 7 of doNotPlayLandscape is set then we do not BMI stri10 \ want to play the landscape after generating it, so \ jump to stri10 to skip the following, leaving the \ stack unmodified \ \ This means that the JSR GenerateLandscape instruction \ that got us here will return normally, so the RTS at \ the end of the GenerateLandscape routine will behave \ as expected, like this: \ \ * If we called GenerateLandscape from the \ MainTitleLoop routine, then we return there to \ fall through into SecretCodeError, which displays \ the "WRONG SECRET CODE" error message for when the \ player enters an incorrect secret code \ \ * If we called GenerateLandscape from the \ FinishLandscape routine, then we return there to \ display the landscape's secret entry code \ on-screen, for when the player completes a level \ \ If bit 7 of doNotPlayLandscape is clear then we keep \ going to modify the return address on the stack, so \ that the RTS at the end of the GenerateLandscape takes \ us to the PreviewLandscape routine \ The above loop ended with X set to &FF, so &0100 + X \ in the following points to the top of the stack at \ &01FF LDA #HI(JumpToPreview) \ Set the return address on the bottom of the stack at STA &0100,X \ (&01FE &01FF) to JumpToPreview DEX \ LDA #LO(JumpToPreview) \ Note that when an RTS instruction is executed, it pops STA &0100,X \ the address off the top of the stack and then jumps to \ that address + 1, so putting the JumpToPreview address \ on the stack means that the RTS at the end of the \ GenerateLandscape routine will actually send us to \ address JumpToPreview+1 \ \ This is intentional and is intended to confuse any \ crackers who might have reached this point, because \ the JumpToPreview routine not only contains a JMP \ instruction at JumpToPreview, but it also contains a \ BMI instruction at JumpToPreview+1, if our crackers \ forgot about this subtlety of the RTS instruction, \ they might end up going down a rabbit hole .stri10 JMP stri16 \ Jump to stri16 to copy the tile data for the smoothed \ strip back into the tileData table
Name: SmoothTileCorners (Part 3 of 4) [Show more] Type: Subroutine Category: Landscape Summary: Smooth a strip by setting the tile corner altitudes to the average of the current tile corner altitude and three following corners
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part smoothes the strip by working along the strip and replacing the altitude of each tile corner with the average of that corner's altitude plus the next three corners, working along rows from left to right and columns from near to far.
.stri11 \ If we get here then bit 6 of processAction is clear, \ so we smooth the tile strip by working our way along \ the strip and setting each tile's altitude to the \ average of its altitude with the three following tiles LDX #0 \ We now work our way along the strip, smoothing the \ altitudes of tiles 0 to 31, so set a tile counter in X \ \ We work along rows from left to right and columns from \ near to far .stri12 LDA #0 \ Set U = 0 to use as the high byte of (U A), which we STA U \ will use to calculate the sum of the neighbouring tile \ altitudes LDA stripData,X \ Set A to the altitude of the tile we are smoothing \ (the one at index X) CLC \ Add the altitude of the next tile along ADC stripData+1,X BCC stri13 \ If the addition overflowed then increment the high CLC \ byte in U, so we have the correct result of the sum INC U \ in (U A) .stri13 ADC stripData+2,X \ Add the altitude of the next tile along BCC stri14 \ If the addition overflowed then increment the high CLC \ byte in U, so we have the correct result of the sum INC U \ in (U A) .stri14 ADC stripData+3,X \ Add the altitude of the next tile along BCC stri15 \ If the addition overflowed then increment the high CLC \ byte in U, so we have the correct result of the sum INC U \ in (U A) .stri15 \ So by this point (U A) contains the sum of the \ altitudes of the tile we are smoothing, and the next \ three tiles along the strip LSR U \ Set (U A) = (U A) / 4 ROR A \ LSR U \ So (U A) contains the average of the four altitudes, ROR A \ and we know that the high byte in U will be zero as \ each of the four elements of the sum fits into one \ byte STA stripData,X \ Set the altitude of the tile we are smoothing to the \ average that we just calculated INX \ Increment the tile counter to move along the strip CPX #32 \ Loop back until we have worked our way through the BCC stri12 \ strip and have smoothed tiles 0 to 31 \ \ This means that when we exit the loop, X = 32 \ We now have a very short interlude to set up some of \ the anti-cracker code before continuing the smoothing \ process in part 4
Name: SetCrackerSeed [Show more] Type: Subroutine Category: Cracker protection Summary: Set up anti-cracker tile-related data that can be checked in the CheckCrackerSeed routine
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.SetCrackerSeed LDA stripData+78-32,X \ Copy the contents of stripData+78 into the operand of STA CrackerSeed+1-32,X \ the unused LDA #0 instruction at CrackerSeed (this \ location is presumably disguised as an instruction to \ obfuscate this process) \ \ At this point stripData+78 contains the third seed \ number to be generated at the start of the \ GenerateLandscape routine, where 81 seed numbers are \ generated and stored at stripData \ \ This third seed contains the high byte of the BCD \ landscape number \ \ This is because the seed generator is initialised \ using the landscape number, and the third number out \ of the shift register is unchanged and still contains \ the initial value of that byte - see the \ InitialiseSeeds routine for more details) \ \ This value is altered in the AlterCrackerSeed routine \ that runs as part of the gameplay routines, and is \ checked in the CheckCrackerSeed routine that runs as \ part of the SpawnCharacter3D routine when drawing the \ landscape's secret code \ Fall through into part 4 of SmoothTileCorners to \ continue with the smoothing process
Name: SmoothTileCorners (Part 4 of 4) [Show more] Type: Subroutine Category: Landscape Summary: Copy the smoothed strip data back into the tileData table
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part copies the smoothed strip back into the tileData table.
.stri16 \ By this point we have smoothed the whole strip, so we \ can now copy the smoothed tile data back into the \ tileData table LDX #31 \ Set A counter in X to work through the tile data for \ the 32 tiles in the strip we just smoothed .stri17 TXA \ Set A to the tile index BIT processAction \ If bit 7 of processAction is clear then we are BPL stri18 \ smoothing a row of tiles at z-coordinate zTile, so \ jump to stri18 to iterate across xTile STA zTile \ If we get here then we are smoothing a column of tiles \ so set zTile to the counter in X so we iterate along \ the z-coordinate (i.e. coming out of the screen) JMP stri19 \ Jump to stri19 to skip the following .stri18 STA xTile \ If we get here then we are smoothing a row of tiles \ so set xTile to the counter in X so we iterate along \ the x-coordinate (i.e. from right to left) .stri19 JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile), which we ignore, but this also sets \ the tile page in tileDataPage and the index in Y, so \ tileDataPage+Y now points to the tile data entry in \ the tileData table LDA stripData,X \ Copy the X-th byte of tile data from the smoothed STA (tileDataPage),Y \ strip into the corresponding entry for (xTile, zTile) \ in the tileData table DEX \ Decrement the tile counter in X so we work our way \ along to the next tile in the strip we are smoothing BPL stri17 \ Loop back to smooth the next tile until we have \ smoothed the whole strip RTS \ Return from the subroutine
Name: GetTileShape [Show more] Type: Subroutine Category: Landscape Summary: Calculate the shape of the tile anchored at (xTile, zTile)
Context: See this subroutine on its own page References: This subroutine is called as follows: * GenerateLandscape calls GetTileShape

Given a tile at altitude S, with neighbouring altitudes T, U and V: ^ [T] [U] | | [S] [V] z-axis into screen x-axis from left to right ---> The shape is calculated as follows, where: * 0 and 1 represent arbitrary altitudes that are in that order, with 1 being higher than 0 * a, b represent arbitrary altitudes where a <> b <> 1 These are all the different types of shape (note there is no shape 8, and shapes 4 and 12 can have multiple layouts): Shape S vs V S vs T S vs U U vs V U vs T Layout ----- ------ ------ ------ ------ ------ ------ 0 S == V S == T S == U 1 1 1 1 1 S == V S <> T U < V U == T 0 0 1 1 2 S <> V S <> T S <= U U == V U == T 1 1 0 1 3 S == V S == T S > U 1 0 1 1 4a S <> V S <> T U == V U <> T a 1 b 1 4b S <> V S == T U <> V U <> T 1 a 1 b 5 S <> V S == T U == V U < T 1 0 1 0 6 S == V S <> T U == V U < T 1 0 0 0 7 S <> V S == T U >= V U == T 1 1 1 0 9 S == V S <> T U >= V U == T 1 1 0 0 10 S == V S == T S < U 0 1 0 0 11 S <> V S <> T S > U U == V U == T 0 0 1 0 12a S <> V S <> T U <> V 1 1 a b 12b S == V S <> T U <> V U <> T a b 1 1 13 S <> V S == T U == V U >= T 0 1 0 1 14 S <> V S == T U < V U == T 0 0 0 1 15 S == V S <> T U == V U >= T 0 1 1 1 Note that for shape 12a, the top-right corner could in theory be a different altitude to a, b and 1, and the comparisons would still fit. However, the way the landscape gets smoothed ensures that every tile has at least one horizontal edge, so this means the top-right corner must be at altitude 1. It is worth noting that: * Shape 0 has four horizontal edges * Shapes 4 and 12 have one horizontal edge and three sloping edges * Shape 8 is unused * All other shapes (1, 2, 3, 5, 6, 7, 9, 10, 11, 14 and 15) have two horizontal edges and two sloping edges
Returns: X The shape of the tile anchored at (xTile, zTile)
.GetTileShape JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile) AND #%00001111 \ Extract the tile altitude from the low nibble and STA S \ store it in S INC xTile \ Move along the x-axis to fetch the next tile to the \ right JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile) AND #%00001111 \ Extract the tile altitude from the low nibble and STA V \ store it in V INC zTile \ Move along the x-axis to fetch the next tile into the \ screen JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile) AND #%00001111 \ Extract the tile altitude from the low nibble and STA U \ store it in U DEC xTile \ Move back along the x-axis to fetch the next tile to \ the left JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile) AND #%00001111 \ Extract the tile altitude from the low nibble and STA T \ store it in T DEC zTile \ Move out of the screen, back along the z-axis to take \ us back to the tile we are processing \ So at this point we have the altitudes of four tile \ corners, as follows, with the view from above: \ \ ^ [T] [U] \ | \ | [S] [V] \ z-axis \ into \ screen x-axis from left to right ---> \ \ S is the altitude of the tile corner that anchors the \ tile for which we are calculating the shape, and T, U \ and V are the altitudes of the tile's other three \ corners, so now we can analyse the shape of the tile LDA S \ If S = V then jump to shap10 CMP V BEQ shap10 CMP T \ If S = T then jump to shap4 BEQ shap4 LDA U \ If U = V then jump to shap2 CMP V BEQ shap2 .shap1 \ If we get here then we either fell through from above: \ \ * S <> V \ * S <> T \ * U <> V \ \ or we jumped here from shap10 and: \ \ * S == V \ * S <> T \ * U <> T \ * U <> V LDX #12 \ Return a shape value of 12 in X RTS \ Return from the subroutine .shap2 \ If we get here then then: \ \ * S <> V \ * S <> T \ * U == V \ \ and A is set to U CMP T \ If U <> T then jump to shap5 BNE shap5 \ If we get here then: \ \ * S <> V \ * S <> T \ * U == V \ * U == T \ \ and A is set to U LDX #2 \ Set X = 2 to return as the shape if U >= S CMP S \ If U >= S then jump to shap3 to return a shape value BCS shap3 \ of 2 LDX #11 \ U < S so return a shape value of 11 in X .shap3 RTS \ Return from the subroutine .shap4 \ If we get here then: \ \ * S <> V \ * S == T LDA U \ If U = V then jump to shap8 CMP V BEQ shap8 \ If we get here then: \ \ * S <> V \ * S == T \ * U <> V \ \ and A is set to U CMP T \ If U = T then jump to shap6 BEQ shap6 .shap5 \ If we get here then either we jumped from shap2: \ \ * S <> V \ * S <> T \ * U == V \ * U <> T \ \ or we fell through from above: \ \ * S <> V \ * S == T \ * U <> V \ * U <> T LDX #4 \ Return a shape value of 4 in X RTS \ Return from the subroutine .shap6 \ If we get here then: \ \ * S <> V \ * S == T \ * U <> V \ * U == T \ \ and A is set to U LDX #14 \ Set X = 14 to return as the shape if U < V CMP V \ If U < V then jump to shap7 to return a shape value BCC shap7 \ of 14 LDX #7 \ U >= V so return a shape value of 7 in X .shap7 RTS \ Return from the subroutine .shap8 \ If we get here then: \ \ * S <> V \ * S == T \ * U == V \ \ and A is set to U LDX #5 \ Set X = 5 to return as the shape if U < T CMP T \ If U < T then jump to shap7 to return a shape value BCC shap9 \ of 5 LDX #13 \ U >= T so return a shape value of 13 in X .shap9 RTS \ Return from the subroutine .shap10 \ If we get here then: \ \ * S == V \ \ and A is set to S CMP T \ If S = T then jump to shap14 BEQ shap14 LDA U \ If U = T then jump to shap12 CMP T BEQ shap12 \ If we get here then: \ \ * S == V \ * S <> T \ * U <> T \ \ and A is set to U CMP V \ If U <> V then jump to shap1 BNE shap1 \ If we get here then: \ \ * S == V \ * S <> T \ * U <> T \ * U == V \ \ and A is set to U LDX #6 \ Set X = 6 to return as the shape if U < T CMP T \ If U < T then jump to shap11 to return a shape value BCC shap11 \ of 6 LDX #15 \ U >= T so return a shape value of 15 in X .shap11 RTS \ Return from the subroutine .shap12 \ If we get here then: \ \ * S == V \ * S <> T \ * U == T \ \ and A is set to U LDX #1 \ Set X = 1 to return as the shape if U < V CMP V \ If U < V then jump to shap11 to return a shape value BCC shap13 \ of 1 LDX #9 \ U >= V so return a shape value of 9 in X .shap13 RTS \ Return from the subroutine .shap14 \ If we get here then: \ \ * S == V \ * S == T \ \ and A is set to S CMP U \ If S = U then jump to shap16 BEQ shap16 LDX #10 \ Set X = 10 to return as the shape if S < U BCC shap15 \ If S < U then jump to shap15 to return a shape value \ of 10 LDX #3 \ S > U so return a shape value of 93 in X .shap15 RTS \ Return from the subroutine .shap16 \ If we get here then: \ \ * S == V \ * S == T \ * S == U LDX #0 \ Return a shape value of 0 in X RTS \ Return from the subroutine
Name: trianglePointAdd [Show more] Type: Variable Category: Drawing polygons Summary: The value to add to the second point number to get the third point number when drawing a tile face as two triangles
Context: See this variable on its own page References: This variable is used as follows: * GetPolygonLines (Part 1 of 6) uses trianglePointAdd
.trianglePointAdd EQUB 1 \ Add when triangleStartPoint = 0 and we are drawing the \ first of the two triangles EQUB 32 + 1 \ Add when triangleStartPoint = 1 and we are drawing the \ first of the two triangles EQUB -1 \ Add when triangleStartPoint = 0 and we are drawing the \ second of the two triangles EQUB 32 - 1 \ Add when triangleStartPoint = 1 and we are drawing the \ second of the two triangles
Name: tileShapeColour [Show more] Type: Variable Category: Drawing the landscape Summary: Tile colours by shape and the orientation of the viewer
Context: See this variable on its own page References: This variable is used as follows: * DrawOneFaceTile uses tileShapeColour * DrawTwoFaceTile uses tileShapeColour

The colours shown in the comments below are for landscape 0000, which has the following palette: * Colour 0 = blue * Colour 1 = black * Colour 2 = white * Colour 3 = green Colours 2 and 3 can be altered depending on the context (for gameplay, the palette changes depending on the number of enemies, for example). The range of colours is as follows: * Colour 2 can be white, yellow, cyan or red * Colour 3 can be green, red, yellow or cyan Note that all the tiles have an edge colour of 0 (blue) apart from the first one, which has an edge colour of colour 3 (green, red, yellow or cyan) to match the fill colour.
.tileShapeColour \ Colours for the first face in a two-face tile, or the \ only face in a one-face tile EQUB 3 << 2 + 3 << 4 \ Shape 0: fill colour 3 (green), edge colour 3 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 1: fill colour 1 (black), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 2: fill colour 1 (black), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 3: fill colour 2 (white), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 4: fill colour 2 (white), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 5: fill colour 2 (white), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 6: fill colour 1 (black), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 7: fill colour 2 (white), edge colour 0 (blue) EQUB 0 << 2 + 0 << 4 \ Shape 8 = unused EQUB 1 << 2 + 0 << 4 \ Shape 9: fill colour 1 (black), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 10: fill colour 2 (white), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 11: fill colour 1 (black), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 12: fill colour 1 (black), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 13: fill colour 2 (white), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 14: fill colour 2 (white), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 15: fill colour 1 (black), edge colour 0 (blue) \ Colours for the second face in a two-face tile EQUB 0 << 2 + 0 << 4 \ Shape 0: fill colour 3 (green), edge colour 0 (blue) EQUB 0 << 2 + 0 << 4 \ Shape 1: fill colour 1 (black), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 2: fill colour 1 (black), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 3: fill colour 2 (white), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 4: fill colour 2 (white), edge colour 0 (blue) EQUB 0 << 2 + 0 << 4 \ Shape 5: fill colour 2 (white), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 6: fill colour 1 (black), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 7: fill colour 2 (white), edge colour 0 (blue) EQUB 0 << 2 + 0 << 4 \ Shape 8 = unused EQUB 0 << 2 + 0 << 4 \ Shape 9: fill colour 1 (black), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 10: fill colour 2 (white), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 11: fill colour 1 (black), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 12: fill colour 1 (black), edge colour 0 (blue) EQUB 0 << 2 + 0 << 4 \ Shape 13: fill colour 2 (white), edge colour 0 (blue) EQUB 1 << 2 + 0 << 4 \ Shape 14: fill colour 2 (white), edge colour 0 (blue) EQUB 2 << 2 + 0 << 4 \ Shape 15: fill colour 1 (black), edge colour 0 (blue)
Name: triangleStart [Show more] Type: Variable Category: Drawing polygons Summary: The number of the first point in each two-face shape that is drawn as a pair of triangles
Context: See this variable on its own page References: This variable is used as follows: * DrawSlopingTile uses triangleStart
.triangleStart EQUB 0 \ Shape 0 starts with point 0 EQUB 0 \ Shape 1 starts with point 0 EQUB 0 \ Shape 2 starts with point 0 EQUB 0 \ Shape 3 starts with point 0 EQUB 0 \ Shape 4 starts with point 0 EQUB 0 \ Shape 5 starts with point 0 EQUB 1 \ Shape 6 starts with point 1 EQUB 1 \ Shape 7 starts with point 1 EQUB 0 \ Shape 8 is unused EQUB 0 \ Shape 9 starts with point 0 EQUB 0 \ Shape 10 starts with point 0 EQUB 0 \ Shape 11 starts with point 0 EQUB 0 \ Shape 12 starts with point 0 EQUB 0 \ Shape 13 starts with point 0 EQUB 1 \ Shape 14 starts with point 1 EQUB 1 \ Shape 15 starts with point 1
Name: GetPolygonLines (Part 1 of 6) [Show more] Type: Subroutine Category: Drawing polygons Summary: Calculate the points in a two-face tile polygon when it consists of a pair of triangles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gpol1 \ If we get here then we are drawing a two-face tile as \ a pair of triangles and A contains the offset of the \ rear left tile corner in the drawing tables \ For a triangle, we number the points like this: \ \ 1. Rear left \ 3. Point 2 + trianglePointAdd,Y \ 2. Front left \ \ We also set point 4 to be the same as point 1, so that \ the third edge from point 3 to point 4 is effectively \ from point 3 to point 1 LDY triangleStartPoint \ Set Y to the number of the starting point for this \ tile shape BVC gpol2 \ If bit 6 of polygonType is clear then we are drawing \ the first triangle in a two-face tile, so jump to \ gpol2 to do this \ If we get here then bit 6 and 7 of polygonType are \ both set, so we are drawing the second triangle in a \ two-face file CLC \ Set A = (A + 32 + 1) mod 64 ADC #33 \ AND #63 \ So this moves diagonally from top-left to bottom-right \ or bottom-left to top-right: \ \ offset x offset x + 1 \ \ offset 32 + x offset 32 + x + 1 INY \ Set Y = Y + 2 INY \ \ to give 2 or 3 .gpol2 STA polygonPoint \ Set the first triangle point to A STA polygonPoint+3 \ Set the fourth triangle point to A, so the third edge \ in the triangle joins with the start of the first \ edge EOR #32 \ Set the second triangle point to the equivalent to A STA polygonPoint+1 \ but in the other drawing table offset CLC \ Set the third triangle point to A + the Y-th entry ADC trianglePointAdd,Y \ from trianglePointAdd, mod 64 AND #63 STA polygonPoint+2 LDX #3 \ Set X = 3 to set as the value of polygonEdgeCount as \ there are three edges in a triangle polygon BNE gpol3 \ Jump to gpol3 in part 1 to set polygonEdgeCount = 3 \ and move on to part 4
Name: GetPolygonLines (Part 2 of 6) [Show more] Type: Subroutine Category: Drawing polygons Summary: The main entry point for the routine to calculate the horizontal lines in filled polygon and prepare them for drawing on-screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawPolygon calls GetPolygonLines

Arguments: polygonType The polygon type triangleStartPoint When drawing a two-face tile as a pair of triangles, this is the number of the starting point to use (0 or 1)
Returns: C flag Status flag: * Clear if the polygon is visible in the current screen buffer and should be drawn * Set if the polygon is not visible in the current screen buffer and should not be drawn yPolygonTop The y-coordinate of the top of the polygon (where higher y-coordinates are up the screen) yPolygonBottom The y-coordinate of the bottom of the polygon (where higher y-coordinates are up the screen) xPolygonLeft The pixel x-coordinates of the left edges of each pixel line in the polygon xPolygonRight The pixel x-coordinates of the right edges of each pixel line in the polygon
.GetPolygonLines LDA xTileToDraw \ Set A to the drawing table offset plus the column ORA drawingTableOffset \ number of the tile we are currently drawing, so if we \ are drawing a tile, this is the offset of the first \ point of the tile in the drawing tables \ \ So A is the position in the drawViewYaw(Hi Lo) and \ drawViewPitch(Hi Lo) tables of the tile corners \ \ The four points are laid out with the front edge in \ one drawing table offset (0 or 32) and the back edge \ in the other drawing table offset (32 or 0), and the \ left-right points are together in the same offset \ \ Specifically, the four tile corners will be offset in \ the drawing tables in one of two layouts \ \ This is the layout when drawingTableOffset = 0: \ \ offset x offset x + 1 \ \ offset 32 + x offset 32 + x + 1 \ \ where x is xTileToDraw and the top row is the back row \ away from the viewer \ \ And this is the layout when drawingTableOffset = 32: \ \ offset 32 + x offset 32 + x + 1 \ \ offset x offset x + 1 \ \ So the offset in A will point to the drawing data for \ the top-left point above, i.e. the rear left tile \ corner from the perspective of the viewer \ \ Also, consider the above layouts and how we can move \ between the corners: \ \ * We can move from left to right by adding 1 to the \ offset, and from right to left by subtracting 1 \ \ * We can move between the top and bottom rows by \ using EOR #32 on the offset, as this will flip \ between x and x + 32 \ \ We use these calculations to work out which points in \ the drawing table we should map to points 1 to 5 in \ polygonPoint for use in the polygon-drawing process BIT polygonType \ If bit 7 of polygonType is set then we are drawing a BMI gpol1 \ two-face tile as a pair of triangles, so jump to gpol1 \ to do this BVS gpol7 \ If bit 7 of polygonType is clear and bit 6 is set then \ we are drawing an object, so jump to part 4 as the \ point numbers in polygonPoint are already set up \ correctly for the polygon \ If we get here then both bits 6 and 7 of polygonType \ are clear, so we are drawing a tile as a four-sided \ shape (quadrilateral) and A contains the offset of the \ rear left tile corner in the drawing tables \ \ For a quadrilateral, we number the points like \ this: \ \ 1. Rear left 4. Rear right \ \ 2. Front left 3. Front right \ \ We also set point 5 to be the same as point 1, so that \ the fourth edge from point 4 to point 5 is effectively \ from point 4 to point 1 STA polygonPoint \ Set the first quadrilateral point to the rear-left \ tile corner STA polygonPoint+4 \ Set the fifth quadrilateral point to A, so the fourth \ edge in the quadrilateral joins with the start of the \ first edge EOR #32 \ Set the second quadrilateral point to the front-left STA polygonPoint+1 \ tile corner CLC \ Set the third quadrilateral point to the front-right ADC #1 \ tile corner STA polygonPoint+2 EOR #32 \ Set the fourth quadrilateral point to the rear-right STA polygonPoint+3 \ tile corner LDX #4 \ Set X = 4 to set as the value of polygonEdgeCount as \ there are four edges in a quadrilateral .gpol3 STX polygonEdgeCount \ Set polygonEdgeCount to the value in X JMP gpol7 \ Jump to part 4 to continue analysing the polygon
Name: GetPolygonLines (Part 3 of 6) [Show more] Type: Subroutine Category: Drawing polygons Summary: Convert all the polygon point yaw angles into pixel x-coordinates (for larger yaw angles that convert into a 16-bit x-coordinate)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gpol4 LDA #%11000000 \ Set bit 6 of xPolygonPointScale to indicate that the STA xPolygonPointScale \ pixel x-coordinate of this polygon point needs to be \ stored as a two-byte number LDY polygonEdgeCount \ We now loop through all the points in the polygon, so \ set Y to count through the number of edges in the \ polygon that we are drawing (as that's also the number \ of unique points in the polygon) .gpol5 LDA (drawViewAngles),Y \ Set X to the offset within the drawing tables of the TAX \ Y-th point in the polygon LDA drawViewYawLo,X \ Set the following: CLC \ ADC bufferOriginLo \ (A T) = drawViewYaw(Hi Lo) + bufferOrigin(Hi Lo) STA T \ LDA drawViewYawHi,X \ for the Y-th polygon point, so this adds the yaw angle ADC bufferOriginHi \ offset in bufferOrigin(Hi Lo) to the polygon point \ \ The yaw angles in drawViewYaw(Hi Lo) are relative to \ the origin in the centre of the screen (i.e. the \ direction of the viewer's gaze), so this converts the \ yaw angle from having the origin in the centre to \ having the origin on the left edge of the buffer ASL T \ Set (A T) = (A T) * 8 ROL A \ ROL T \ So (A T) contains the yaw angle in pixels, which is ROL A \ the same as a pixel x-coordinate, with the integer ROL T \ part in A and the fractional part in T ROL A \ \ Also, the top three bits of the original A are in the \ bottom two bits of T and the C flag, as all but the \ first instruction are rotations rather than shifts STA xPolygonPointLo,X \ Store the pixel x-coordinate for the Y-th point of the \ polygon in the xPolygonPointLo table, storing the \ result at the offset given in the drawViewAngles table \ (i.e. in the correct place for the polygon point \ number in Y) \ \ This stores the low byte of the two-byte value in \ (A T) * 8, so now we calculate and store the high byte LDA T \ Rotate the C flag into bit 0 of T and put the result ROL A \ in A, so top three bits of the original A are now in \ the bottom three bits of A AND #%00000111 \ Clear all the other bits in A, so A just contains the \ top three bits of the original A \ \ In other words, A now contains the overflow from the \ left-shifted (A T), which spilled out into a third top \ byte when the multiplication was applied CMP #%00000100 \ If bit 2 of A is set then bit 7 of the original A must BCC gpol6 \ have been set so the original yaw angle must have been ORA #%11111000 \ negative, so set bits 3 to 7 of the top byte .gpol6 STA xPolygonPointHi,X \ Store the pixel x-coordinate for the Y-th point of the \ polygon in the xPolygonPointHi table, storing the \ result at the offset given in the drawViewAngles table \ (i.e. in the correct place for the polygon point \ number in Y) \ \ This stores the high byte of the two-byte value in \ (A T) * 8 \ \ So this calculation is for polygon points that have a \ pixel x-coordinate greater than 255 (as recorded by \ the set bit 6 in xPolygonPointScale) DEY \ Decrement the point counter in Y BPL gpol5 \ Loop back until we have converted all the polygon \ point yaw angles into pixel x-coordinates JMP gpol9 \ Jump to part 5 to continue analysing the polygon
Name: GetPolygonLines (Part 4 of 6) [Show more] Type: Subroutine Category: Drawing polygons Summary: Convert all the polygon point yaw angles into pixel x-coordinates (for smaller yaw angles that convert into an 8-bit x-coordinate)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gpol7 \ By this point the point numbers in the polygon have \ been set up as follows: \ \ * polygonPoint to polygonPoint+3 (when drawing a \ triangle) \ \ * polygonPoint to polygonPoint+4 (when drawing a \ quadrilateral) \ \ Each point number is given as an offset within the \ drawing tables \ \ We now calculate the pixel x-coordinates for each of \ these points, storing the results in xPolygonPointLo \ or xPolygonPoint(Hi Lo), depending on whether the \ pixel x-coordinates fit into one byte (when they are \ all in the range 0 to 255) or two bytes (when at least \ one x-coordinate is 256 or more) LDA #0 \ Clear bit 6 of xPolygonPointScale to indicate that the STA xPolygonPointScale \ pixel x-coordinate of this polygon point fits into one \ byte (we will change this if the x-coordinate ends up \ being too large to fit into the range 0 to 255) LDY polygonEdgeCount \ We now loop through all the points in the polygon, so \ set Y to count through the number of edges in the \ polygon that we are drawing (as that's also the number \ of unique points in the polygon) .gpol8 LDA (drawViewAngles),Y \ Set X to the offset within the drawing tables of the TAX \ Y-th point in the polygon LDA drawViewYawLo,X \ Set the following: CLC \ ADC bufferOriginLo \ (A T) = drawViewYaw(Hi Lo) + bufferOrigin(Hi Lo) STA T \ LDA drawViewYawHi,X \ for the Y-th polygon point, so this adds the yaw angle ADC bufferOriginHi \ offset in bufferOrigin(Hi Lo) to the polygon point \ \ The yaw angles in drawViewYaw(Hi Lo) are relative to \ the origin in the centre of the screen (i.e. the \ direction of the viewer's gaze), so this converts the \ yaw angle from having the origin in the centre to \ having the origin on the left edge of the buffer \ We now convert the point's yaw angle in (A T) into a \ pixel x-coordinate by multiplying the yaw angle by 8, \ as there are 20 yaw angles in a screen width, the \ screen is 160 pixels across, and 20 * 8 = 160 CMP #%00100000 \ If the high byte in A is %00100000 or more then BCS gpol4 \ multiplying by 8 will overflow (as three left shifts \ will spill out of the end of the high byte), so jump \ up to part 3 to redo the x-coordinate calculations for \ all the polygon points, but this time storing the \ results in 16-bit numbers \ \ After the calculations in part 3 are done, we will \ then jump straight on to part 5 \ If we get here then the high byte in A is less than \ %00100000, so we can multiply by 8 without risk of \ overflow ASL T \ Set (A T) = (A T) * 8 ROL A \ ASL T \ So (A T) contains the yaw angle in pixels, which is ROL A \ the same as a pixel x-coordinate, with the integer ASL T \ part in A and the fractional part in T ROL A STA xPolygonPointLo,X \ Store the pixel x-coordinate for the Y-th point of the \ polygon in the xPolygonPointLo table, storing the \ result at the offset given in the drawViewAngles table \ (i.e. in the correct place for the polygon point \ number in Y) \ \ So this calculation is for polygon points that have a \ pixel x-coordinate in the range 0 to 255 (as recorded \ by the clear bit 6 in xPolygonPointScale) DEY \ Decrement the point counter in Y BPL gpol8 \ Loop back until we have converted all the polygon \ point yaw angles into pixel x-coordinates
Name: GetPolygonLines (Part 5 of 6) [Show more] Type: Subroutine Category: Drawing polygons Summary: Loop through all the edges in the polygon and call the correct routines to process one-byte, two-byte or horizontal edges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gpol9 LDA #0 \ Set yPolygonTop = 0 to record the y-coordinate of the STA yPolygonTop \ top of the polygon, where higher y-coordinates are up \ the screen (so this sets the top of the polygon to the \ bottom of the screen, so we can bump it up as we work \ through the polygon) STA xMaxHorizontal \ Set xMaxHorizontal = 0 so we can use it to keep track \ of the maximum x-coordinates of any polygon edges that \ are horizontal STA horizontalEdges \ Set horizontalEdges = 0 so we can use it to keep track \ of how many polygon edges are horizontal LDA #255 \ Set yPolygonBottom = 255 to record the y-coordinate of STA yPolygonBottom \ the bottom of the polygon, where higher y-coordinates \ are up the screen (so this sets the bottom of the \ polygon to the top of the screen, so we can move it \ down as we work through the polygon) STA xMinHorizontal \ Set xMinHorizontal = 255 so we can use it to keep \ track of the minimum x-coordinates of any polygon \ edges that are horizontal STA drawPolygon \ Set drawPolygon to a non-zero value, so by default the \ polygon will not be drawn, though we will set this to \ zero later if the polygon should be drawn LDY #0 \ We now loop through all the edges of the polygon, so \ set a loop counter in Y to work through all the edges \ of the polygon (three for a triangle, four for a \ quadrilateral) .gpol10 STY polygonEdge \ Store the polygon edge number that we are about to \ process in polygonEdge, so we can retrieve it at the \ end of the loop LDA (drawViewAngles),Y \ Set X to the offset within the drawing tables of the TAX \ Y-th point in the polygon INY \ Set Y to the offset within the drawing tables of the LDA (drawViewAngles),Y \ Y+1-st point in the polygon TAY \ So Y and X are the offsets within the drawing tables \ of the start and end points for the polygon edge that \ we are processing LDA #HI(xPolygonLeft) \ Set xPolygonAddrHi to the high byte of the address of STA xPolygonAddrHi \ the xPolygonLeft table, so this value can be used to \ modify the instructions for storing the polygon line \ x-coordinates as we process them \ \ The default is therefore to store the x-coordinate of \ the left end of the polygon line at xPolygonLeft LDA drawViewPitchLo,Y \ Set (A yEdgeDeltaLo) to the following: SEC \ SBC drawViewPitchLo,X \ drawViewPitch(Hi Lo) for point #Y STA yEdgeDeltaLo \ - drawViewPitch(Hi Lo) for point #X LDA drawViewPitchHi,Y \ SBC drawViewPitchHi,X \ So (A yEdgeDeltaLo) contains the difference in pitch \ angle between the two points, or the delta along the \ y-axis BPL gpol11 \ If the result in (A yEdgeDeltaLo) is positive then \ point #Y is higher than point #X and (A yEdgeDeltaLo) \ already has the correct sign for the absolute value, \ so jump to gpol11 to skip the following \ If we get here then point #X is higher than point #Y, \ so we swap them around STA yEdgeDeltaHi \ Set yEdgeDelta(Hi Lo) = (A yEdgeDeltaLo) INC xPolygonAddrHi \ Increment xPolygonAddrHi to HI(xPolygonRight), so when \ we modify the instructions for storing the polygon \ line x-coordinates, they are stored in the table for \ the right end of the polygon line at xPolygonRight STX T \ Swap X and Y around, so point #Y is now higher than STY U \ point #X LDX U LDY T LDA #0 \ Set (A yEdgeDeltaLo) = -yEdgeDelta(Hi Lo) SEC \ SBC yEdgeDeltaLo \ So yEdgeDelta(Hi Lo) is now positive STA yEdgeDeltaLo LDA #0 SBC yEdgeDeltaHi .gpol11 STA yEdgeDeltaHi \ Set yEdgeDelta(Hi Lo) = (A yEdgeDeltaLo) \ \ So point #Y is higher than point #X, and we have a \ positive value in yEdgeDelta(Hi Lo) that contains the \ y-axis delta between the two points \ \ Also, xPolygonAddrHi points to the correct table for \ storing the polygon line (left or right) BIT xPolygonPointScale \ If bit 6 of xPolygonPointScale is clear then the pixel BVC gpol13 \ x-coordinates of all the polygon points fit into \ single-byte numbers, so jump to gpol13 to process the \ edge accordingly LDA xPolygonPointHi,Y \ If both of the high bytes for point #X and point #Y ORA xPolygonPointHi,X \ are zero then the x-coordinates for these particular BEQ gpol13 \ points happen to fit into single-byte numbers, so \ jump to gpol13 to process the edge accordingly LDA yEdgeDeltaHi \ If yEdgeDeltaHi is non-zero then the y-axis delta is BNE gpol12 \ non-zero, so jump to part 6 via gpol12 to process the \ edge accordingly LDA yEdgeDeltaLo \ If yEdgeDeltaLo = 0 then we know yEdgeDelta(Hi Lo) BEQ gpol15 \ must be zero as we passed through the BNE above, so \ the polygon edge is horizontal and we jump to gpol15 \ to move on to the next polygon edge .gpol12 JMP gpol22 \ If we get here then: \ \ * The x-coordinates for points #X and #Y are both \ two-byte numbers \ \ * The y-axis delta in yEdgeDelta(Hi Lo) is non-zero \ and positive \ \ So we jump to part 6 to process this edge accordingly .gpol13 \ If we get here then the x-coordinates for points #X \ and #Y fit into single-byte numbers LDA yEdgeDeltaHi \ If yEdgeDeltaHi is zero then the y-axis delta also BEQ gpol14 \ fits into a single-byte number, so jump to gpol14 LDA #0 \ Zero the high bytes for both points' x-coordinates, STA xPolygonPointHi,Y \ because we are about to process the edge using STA xPolygonPointHi,X \ two-byte calculations, and the high bytes don't get \ written when the x-coordinates of all the polygon \ points fit into single-byte numbers \ \ So this just ensures that the high bytes are correctly \ set up so we can use the value of xPolygonPoint(Hi Lo) \ in the calculations and get the correct x-coordinate \ values JMP gpol22 \ If we get here then: \ \ * The x-coordinates for points #X and #Y are both \ single-byte numbers \ \ * The x-coordinates for points #X and #Y have had \ their high bytes zeroed so they can be used as \ two-byte numbers \ \ * The y-axis delta in yEdgeDelta(Hi Lo) is a \ two-byte number that is non-zero and positive \ \ So we jump to part 6 to process this edge accordingly .gpol14 \ If we get here then: \ \ * The x-coordinates for points #X and #Y are both \ single-byte numbers \ \ * The y-axis delta in yEdgeDelta(Hi Lo) is a \ single-byte number and is therefore equal to \ yEdgeDeltaLo LDA yEdgeDeltaLo \ If yEdgeDeltaLo = 0 then yEdgeDelta(Hi Lo) = 0, so BEQ gpol19 \ jump to gpol19 to process this \ We now set up the start and end points so that the \ edge is either horizontal or slopes downwards when \ moving from the start to the end, as that's what the \ TracePolygonEdge routine expects \ \ We know that point #Y is higher than point #X as we \ set that up above, so we trace along the line from \ the start at point #Y to the end at point #X LDA drawViewPitchHi,Y \ Set yEdgeStart(Hi Lo) = drawViewPitch(Hi Lo) STA yEdgeStartHi \ LDA drawViewPitchLo,Y \ So this sets the y-coordinate of the start point in STA yEdgeStartLo \ point #Y (the lower point) LDA drawViewPitchHi,X \ Set yEdgeEnd(Hi Lo) = drawViewPitch(Hi Lo) STA yEdgeEndHi \ LDA drawViewPitchLo,X \ So this sets the y-coordinate of the end point in STA yEdgeEndLo \ point #X (the higher point) LDA xPolygonPointLo,Y \ Set xEdgeStart(Hi Lo) = (0 xPolygonPointLo) STA xEdgeStartLo \ \ So this sets the x-coordinate of the start point in \ point #Y (the lower point), starting with the low \ bytes LDA xPolygonPointLo,X \ Set xEdgeEnd(Hi Lo) = (0 xPolygonPointLo) STA xEdgeEndLo \ \ So this sets the x-coordinate of the start point in \ point #Y (the lower point), starting with the low \ bytes LDA #0 \ Zero the high bytes of xEdgeStart(Hi Lo) and STA xEdgeStartHi \ xEdgeEnd(Hi Lo) STA xEdgeEndHi JSR TracePolygonEdge \ Trace the polygon edge, populating the xPolygonRight \ or xPolygonLeft table with the x-coordinate of the \ edge for each y-coordinate .gpol15 \ We now move on to the next edge in the polygon LDY polygonEdge \ Set Y to the number of the polygon edge that we have \ been processing INY \ Increment Y to move on to the next edge in the polygon CPY polygonEdgeCount \ If we have processed all the edges in the polygon then BEQ gpol16 \ jump to gpol16 to keep going JMP gpol10 \ Otherwise loop back to gpol10 to process the next edge \ in the polygon .gpol16 \ We have processed all the edges in the polygon, so now \ we work out whether the polygon is visible in the \ current screen buffer LDA horizontalEdges \ If horizontalEdges does not match polygonEdgeCount CMP polygonEdgeCount \ then not every edge in the polygon was horizontal, so BNE gpol17 \ jump to gpol17 to skip the following \ If we get here then every edge in the polygon is \ horizontal, so the entire polygon appears within a \ single horizontal line LDA drawViewPitchHi,X \ If the high byte of the view-relative pitch angle for BNE gpol17 \ point #X is non-zero then point #X is definitely not \ on-screen, and so neither is the horizontal line, so \ jump to gpol17 to skip the following LDY drawViewPitchLo,X \ Set Y to the low byte of the view-relative pitch angle \ for point #X (and therefore the pitch angle for the \ horizontal line) CPY minPitchAngle \ If Y < minPitchAngle then the line is not within the BCC gpol17 \ screen buffer as the pitch angle is below the minimum \ value in the buffer, so jump to gpol17 to skip the \ following CPY maxPitchAngle \ If Y >= maxPitchAngle hen the line is not within the BCS gpol17 \ screen buffer as the pitch angle is above the maximum \ value in the buffer, so jump to gpol17 to skip the \ following \ If we get here then the polygon is a single horizontal \ line and it appears within the screen buffer, so we \ configure the result to return a single polygon line \ with the y-coordinate of single horizontal line, and \ with the maximum and minimum x-coordinates that we \ have been calculating in xMinHorizontal and \ xMaxHorizontal STY yPolygonBottom \ Set both the top and bottom polygon y-coordinates to STY yPolygonTop \ the low byte of the view-relative pitch angle for \ point #X LDA xMinHorizontal \ Set the left edge of the polygon line to the STA xPolygonLeft,Y \ x-coordinate in xMinHorizontal LDA xMaxHorizontal \ Set the right edge of the polygon line to the STA xPolygonRight,Y \ x-coordinate in xMaxHorizontal LDA #0 \ Set drawPolygon = 0 so the polygon gets drawn STA drawPolygon .gpol17 LDA drawPolygon \ If drawPolygon is non-zero then the polygon should not BNE gpol18 \ be drawn, so jump to gpol18 to return from the \ subroutine with the C flag set LDA yPolygonTop \ If yPolygonTop < yPolygonBottom then there are no CMP yPolygonBottom \ visible lines to draw in the polygon, so jump to BCC gpol18 \ gpol18 to return from the subroutine with the C flag \ set CLC \ Clear the C flag to indicate that the polygon is \ visible in the current screen buffer and should be \ drawn RTS \ Return from the subroutine .gpol18 SEC \ Set the C flag to indicate that the polygon is not \ visible in the current screen buffer and should not be \ drawn RTS \ Return from the subroutine .gpol19 \ If we get here then yEdgeDelta(Hi Lo) = 0, so this \ polygon edge is horizontal LDA xPolygonPointLo,X \ Set A = xPolygonPointLo for point #X CMP xMaxHorizontal \ Set xMaxHorizontal = max(xMaxHorizontal, A) BCC gpol20 \ STA xMaxHorizontal \ So xMaxHorizontal keeps track of the maximum \ x-coordinate of all the horizontal polygon edges .gpol20 CMP xMinHorizontal \ Set xMinHorizontal = min(xMinHorizontal, A) BCS gpol21 \ STA xMinHorizontal \ So xMinHorizontal keeps track of the minimum \ x-coordinate of all the horizontal polygon edges .gpol21 INC horizontalEdges \ Increment horizontalEdges so we keep a count of all \ the horizontal polygon edges JMP gpol15 \ Jump to gpol15 to move on to the next polygon edge
Name: TracePolygonEdge (Part 1 of 8) [Show more] Type: Subroutine Category: Drawing polygons Summary: Trace a polygon edge, populating xPolygonRight or xPolygonLeft with the x-coordinate of the edge for each y-coordinate
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetPolygonLines (Part 5 of 6) calls TracePolygonEdge * GetPolygonLines (Part 6 of 6) calls TracePolygonEdge

This routine traces a polygon edge. For this to work the start point must have a bigger y-coordinate than the end point, so the edge is either horizontal or slopes downwards when tracing the edge from start to end.
Arguments: xEdgeStart(Hi Lo) The x-coordinate of the start of the edge to trace yEdgeStart(Hi Lo) The y-coordinate of the start of the edge to trace xEdgeEnd(Hi Lo) The x-coordinate of the end of the edge to trace yEdgeEnd(Hi Lo) The y-coordinate of the start of the edge to trace yEdgeDeltaLo The y-axis delta along the edge yPolygonTop The y-coordinate of the top of the polygon (where higher y-coordinates are up the screen) yPolygonBottom The y-coordinate of the bottom of the polygon (where higher y-coordinates are up the screen) xPolygonAddrHi The high byte of either xPolygonLeft or xPolygonRight to determine which of the resulting tables is written to
Returns: xPolygonLeft The pixel x-coordinates of the left edges of each pixel line in the polygon (when xPolygonAddrHi is the high byte of xPolygonLeft) xPolygonRight The pixel x-coordinates of the right edges of each pixel line in the polygon (when xPolygonAddrHi is the high byte of xPolygonRight)
.tred1 JMP tred35 \ Jump to part 6 to trace an edge with two-byte start or \ end point x-coordinates .tred2 RTS \ Return from the subroutine .TracePolygonEdge \ We start by updating yPolygonBottom so it covers the \ y-coordinate of the end point of the edge, as the end \ point has the lower y-coordinate LDA yEdgeEndHi \ If yEdgeEnd(Hi Lo) is negative then the end point is BMI tred3 \ in the lower half of the screen, so jump to tred3 to \ set yPolygonBottom to the lowest point in the screen \ buffer, i.e. minPitchAngle BNE tred2 \ If yEdgeEndHi > 0 then the end point is off the top of \ the screen, so jump to tred2 to return from the \ subroutine \ If we get here then yEdgeEndHi = 0, so now we analyse \ the low byte of yEdgeEnd(Hi Lo) LDA yEdgeEndLo \ If yEdgeEndLo >= maxPitchAngle then the edge ends CMP maxPitchAngle \ beyond the top of the screen buffer, so jump to tred2 BCS tred2 \ to return from the subroutine CMP yPolygonBottom \ If yEdgeEndLo >= yPolygonBottom then yPolygonBottom BCS tred5 \ already covers the y-coordinate, so jump to tred5 to \ move on to the next calculation \ If we get here then yEdgeEndLo < yPolygonBottom, so we \ need to reduce yPolygonBottom to cater for this new \ y-coordinate CMP minPitchAngle \ If yEdgeEndLo >= minPitchAngle, then the y-coordinate BCS tred4 \ in yEdgeEndLo is inside the screen buffer, so jump \ to tred4 to store this as the new value of \ yPolygonBottom \ Otherwise yEdgeEndLo is below the bottom of the screen \ buffer, so we need to clip yPolygonBottom to the \ y-coordinate of the bottom of the screen buffer .tred3 LDA minPitchAngle \ Set A = minPitchAngle, so A is clipped to the bottom \ of the screen buffer .tred4 STA yPolygonBottom \ Set yPolygonBottom to the clipped value of yEdgeEndLo \ as this is the lowest y-coordinate we have found yet .tred5 \ Next we update yPolygonTop so it covers the \ y-coordinate of the start point of the edge LDA yEdgeStartHi \ If yEdgeStart(Hi Lo) is negative then the start point BMI tred2 \ is off the bottom of the screen, so jump to tred2 to \ return from the subroutine BNE tred6 \ If 0 < yEdgeStartHi < 128 then the start point is \ beyond the top of the screen, so jump to tred6 to set \ yPolygonTop to the highest point in the screen buffer, \ i.e. maxPitchAngle \ If we get here then yEdgeStartHi = 0, so now we \ analyse the low byte of yEdgeStart(Hi Lo) LDA yEdgeStartLo \ If yEdgeStartLo < minPitchAngle then the edge starts CMP minPitchAngle \ below the bottom of the screen buffer, so jump to BCC tred2 \ tred2 to return from the subroutine CMP yPolygonTop \ If yEdgeStartLo < yPolygonTop then yPolygonTop already BCC tred8 \ caters for the y-coordinate, so jump to tred8 to move \ on to the next calculation \ If we get here then yEdgeStartLo >= yPolygonTop, so we \ need to bump up yPolygonTop to cater for this new \ y-coordinate CMP maxPitchAngle \ If yEdgeStartLo < maxPitchAngle, then the y-coordinate BCC tred7 \ in yEdgeStartLo is inside the screen buffer, so jump \ to tred7 to store this as the new value of yPolygonTop \ Otherwise yEdgeStartLo is beyond the top of the screen \ buffer, so we need to clip yPolygonTop to the \ y-coordinate of the top of the screen buffer .tred6 LDA maxPitchAngle \ Set A = maxPitchAngle - 1, so A is clipped to the top SEC \ of the screen buffer SBC #1 .tred7 STA yPolygonTop \ Set yPolygonTop to the clipped value of yEdgeStartLo \ as this is the highest y-coordinate we have found yet .tred8 LDA xEdgeStartHi \ If either of the start or end points have a non-zero ORA xEdgeEndHi \ high byte in their x-coordinates, jump to part 4 via BNE tred1 \ tred1 to trace the polygon edge accordingly \ If we get here then both xEdgeStartHi and xEdgeEndHi \ are zero, so the x-coordinates for the polygon edge \ are all in the range 0 to 255 LDA xEdgeStartLo \ Set A = xEdgeStartLo - xEdgeEndLo SEC \ SBC xEdgeEndLo \ So A contains the x-axis delta along the edge we are \ tracing BCS tred9 \ If the subtraction didn't underflow, jump to tred9 to \ store the result in xEdgeDelta, as it is already the \ correct sign for the absolute value EOR #&FF \ Negate A using two's complement, so that A is now CLC \ positive and contains the absolute value of the ADC #1 \ distance in the x-axis between the two points STA xEdgeDelta \ Store the result in xEdgeDelta, so xEdgeDelta is the \ positive absolute of the x-axis delta, as follows: \ \ xEdgeDelta = |xEdgeStartLo - xEdgeEndLo| LDX #&E8 \ Set A to the opcode for the INX instruction, so we \ trace along the edge by incrementing X BNE tred10 \ Jump to tred10 to keep going (this BNE is effectively \ a JMP as X is never zero) .tred9 STA xEdgeDelta \ Set xEdgeDelta to the positive x-axis delta, so this \ is the case: \ \ xEdgeDelta = |xEdgeStartLo - xEdgeEndLo| LDX #&CA \ Set A to the opcode for the DEX instruction, so we \ trace along the edge by decrementing X .tred10 \ By this point we have the following: \ \ * xEdgeDelta = |xEdgeStartLo - xEdgeEndLo| \ \ * X is the correct opcode for tred12 (DEX or INX) LDY xEdgeDelta \ Set the status flags on the comparison of xEdgeDelta CPY yEdgeDeltaLo \ and yEdgeDeltaLo, so we can decide which axis has the \ longer side along the polygon edge (and therefore \ which axis we should step along when tracing the edge) LDY yEdgeStartLo \ Set (A Y) = (xPolygonAddrHi yEdgeStartLo) to use as LDA xPolygonAddrHi \ the storage address in the modification below BCS tred18 \ If xEdgeDelta >= yEdgeDeltaLo then the x-axis delta is \ the biggest and the edge has a shallow gradient, so \ jump to part 3 to trace the edge by stepping along the \ x-axis \ Otherwise the edge has a steep gradient, so fall \ through into part 2 to trace the edge by stepping \ along the y-axis
Name: TracePolygonEdge (Part 2 of 8) [Show more] Type: Subroutine Category: Drawing polygons Summary: Trace a polygon edge with a steep gradient by stepping along the y-axis
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
\ If we get here then xEdgeDelta < yEdgeDeltaLo, so the \ y-axis delta is the biggest and the polygon edge has a \ steep gradient \ \ We therefore step along the y-axis one pixel at a time \ and cumulatively add the gradient to calculate the \ x-coordinate at each step STA tred13+2 \ Modify the instruction at tred13 to use the address in STY tred13+1 \ (A Y), so it becomes: \ \ STX into address (xPolygonAddrHi yEdgeStartLo) \ \ xPolygonAddrHi is set to the high byte of either \ xPolygonLeft or xPolygonRight, and both of these \ tables start on a page boundary, so this sets the \ address to offset yEdgeStartLo within the table \ specified by xPolygonAddrHi \ \ We will decrement this address for each step along the \ edge, so we step along the y-axis as we trace the edge STX tred12 \ Modify the instruction at tred12 to use the opcode \ specified in X, so we have: \ \ * DEX when xEdgeStartLo - xEdgeEndLo is positive \ \ * INX when xEdgeStartLo - xEdgeEndLo is negative \ \ We store the x-coordinate in X, so this ensures that: \ \ * We step left when required with DEX, for when the \ start point is to the right of the end point \ \ * We step right when required with INX, for when the \ start point is to the left of the end point \ \ We will run this instruction when we need to step \ along the x-axis, according to the cumulative value of \ the slope error LDY yEdgeDeltaLo \ Set Y = yEdgeDeltaLo + 1 INY \ \ We can use this as a pixel counter when stepping along \ the polygon edge's y-axis delta one pixel at a time, \ as there are yEdgeDeltaLo + 1 pixels along the edge \ (the additional one ensures we include the pixels at \ both ends) LDA yEdgeDeltaLo \ Set A = ~(yEdgeDeltaLo / 2) LSR A \ EOR #&FF \ We use A to keep track of the slope error, though I am \ unsure of why we start with this value CLC \ Clear the C flag so the first addition we do in the \ following loop will work correctly \ We already ensured in part 1 that yEdgeStart(Hi Lo) \ is positive, so the following check only matches \ yEdgeStartHi when it's in the range 1 to 127 LDX yEdgeStartHi \ If 0 < yEdgeStartHi < 128 then the start point is BNE tred26 \ beyond the top of the screen, so jump to part 4 to \ trace the edge from the start until it reaches the \ screen, but without storing the results \ \ When the tracing reaches the top of the screen, part 4 \ will jump into the loop below at tred14 to continue \ the normal tracing process LDX xEdgeStartLo \ Set X to the x-coordinate of the starting point, so we \ can start tracing from the start of the edge JMP tred13 \ Jump into the middle of the loop at tred13 .tred11 ADC xEdgeDelta \ Add the x-axis delta to the slope error in A, so we \ keep track of the slope error as we move along the \ y-axis BCC tred13 \ If the addition didn't overflow then we have not moved \ into a new x-coordinate with this step along the \ y-axis, so jump to tred13 so we do not move along the \ x-axis \ If we get here then the addition overflowed and the \ cumulative slope error along the x-axis has added up \ to a whole pixel, so we now need to step along the \ x-axis by a pixel SBC yEdgeDeltaLo \ Set A = A - yEdgeDeltaLo \ \ This updates the slope error in A \ \ This subtraction works as we just passed through a \ BCC, so we know the C flag is set .tred12 INX \ This instruction is modified above to: \ \ * INX (if we are stepping right along the edge) \ \ * DEX (if we are stepping left along the edge) \ \ So in either case this steps us along the x-axis by \ one pixel .tred13 \ This is the entry point for the tracing loop and the \ point where we record the current pixel in the edge \ that we are tracing STX xPolygonRight+&9F \ This instruction is modified above to the following: \ \ STX into address (xPolygonAddrHi yEdgeStartLo) \ \ So this stores the current x-coordinate as we step \ along the edge, storing the coordinate in either the \ xPolygonRight or xPolygonLeft table, and storing the \ coordinate in the offset given by the y-coordinate \ of the current position along the edge we are tracing \ \ The original value of xPolygonRight+&9F is just \ workspace noise and has no meaning, as it is modified \ before we get here DEC tred13+1 \ Decrement the low byte of the address in the \ instruction above so that it points to the table \ entry for the next y-coordinate down BEQ tred17 \ If we just decremented the low byte of the address to \ zero then we have finished stepping along the y-axis, \ so jump to tred17 to finish off .tred14 DEY \ Decrement the pixel counter in Y BNE tred11 \ Loop back to keep tracing the polygon edge until we \ have worked our way through all the pixels along the \ y-axis \ If we get here then we have finished tracing the edge \ and Y = 0, so we can now finish off .tred15 STY drawPolygon \ Set drawPolygon = 0 so the polygon gets drawn .tred16 RTS \ Return from the subroutine .tred17 LDY #0 \ Set Y = 0 to store as the value of drawPolygon so the \ polygon gets drawn BEQ tred15 \ Jump to tred15 to set drawPolygon and return from the \ subroutine (this BEQ is effectively a JMP as Y is \ always zero)
Name: TracePolygonEdge (Part 3 of 8) [Show more] Type: Subroutine Category: Drawing polygons Summary: Trace a polygon edge with a shallow gradient by stepping along the x-axis
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tred18 \ If we get here then xEdgeDelta >= yEdgeDeltaLo, so the \ x-axis delta is the biggest and the polygon edge has a \ shallow gradient \ \ We therefore step along the x-axis one pixel at a time \ and cumulatively add the gradient to calculate the \ y-coordinate at each step STA tred24+2 \ Modify the instruction at tred24 to use the address in STY tred24+1 \ (A Y), so it becomes: \ \ STX into address (xPolygonAddrHi yEdgeStartLo) \ \ xPolygonAddrHi is set to the high byte of either \ xPolygonLeft or xPolygonRight, and both of these \ tables start on a page boundary, so this sets the \ address to offset yEdgeStartLo within the table \ specified by xPolygonAddrHi \ \ We will decrement this address when we need to step \ along the y-axis, according to the cumulative value of \ the slope error STX tred22 \ Modify the instruction at tred12 to use the opcode \ specified in X, so we have: \ \ * DEX when xEdgeStartLo - xEdgeEndLo is positive \ \ * INX when xEdgeStartLo - xEdgeEndLo is negative \ \ We store the x-coordinate in X, so this ensures that: \ \ * We step left when required with DEX, for when the \ start point is to the right of the end point \ \ * We step right when required with INX, for when the \ start point is to the left of the end point \ \ We will run this instruction for each step along the \ edge, so we step along the x-axis as we trace the edge LDY #tred24-tred23-2 \ Set Y to the operand required to modify the BCC \ instruction at tred23 to the following: \ \ BCC tred24 CMP #HI(xPolygonLeft) \ If we are tracing a left edge of the polygon, jump to BEQ tred19 \ tred19 \ If we get here then we are tracing a right edge of the \ polygon CPX #&CA \ If we are going to be stepping along the x-axis with a BEQ tred20 \ DEX instruction (opcode &DA), jump to tred20 so we can \ modify the instruction at tred23 to BCC tred25 BNE tred21 \ Otherwise we are going to be stepping along the x-axis \ with an INX instruction, so jump to tred21 to modify \ the instruction at tred23 to BCC tred24 (this BNE is \ effectively a JMP as we just passed through a BEQ) .tred19 \ If we get here then we are tracing a left edge of the \ polygon CPX #&CA \ If we are going to be stepping along the x-axis with a BEQ tred21 \ DEX instruction (opcode &DA), jump to tred21 so we can \ modify the instruction at tred23 to BCC tred24 .tred20 LDY #tred25-tred23-2 \ Set Y to the operand required to modify the BCC \ instruction at tred23 to the following: \ \ BCC tred25 .tred21 STY tred23+1 \ Modify the instruction at tred23 to use the operand \ specified in Y, so we have: \ \ * BCC tred24 when we are tracing a right edge and \ stepping along the x-axis with INX or a left edge \ and stepping along with DEX \ \ * BCC tred25 when we are tracing a right edge and \ stepping along the x-axis with DEX or a left edge \ and stepping along with INX \ \ This ensures that we store the x-coordinate of the \ outermost pixel along the edge for each horizontal \ polygon line (see tred23 below for details) LDY xEdgeDelta \ Set Y = xEdgeDelta + 1 INY \ \ We can use this as a pixel counter when stepping along \ the polygon edge's x-axis delta one pixel at a time, \ as there are xEdgeDelta + 1 pixels along the edge \ (the additional one ensures we include the pixels at \ both ends) LDA xEdgeDelta \ Set A = ~(xEdgeDelta / 2) LSR A \ EOR #&FF \ We use A to keep track of the slope error, though I am \ unsure of why we start with this value CLC \ Clear the C flag so the first addition we do in the \ following loop will work correctly \ We already ensured in part 1 that yEdgeStart(Hi Lo) \ is positive, so the following check only matches \ yEdgeStartHi when it's in the range 1 to 127 LDX yEdgeStartHi \ If 0 < yEdgeStartHi < 128 then the start point is BNE tred31 \ beyond the top of the screen, so jump to part 5 to \ trace the edge from the start until it reaches the \ screen, but without storing the results \ \ When the tracing reaches the top of the screen, part 4 \ will jump into the loop below at tred14 to continue \ the normal tracing process LDX xEdgeStartLo \ Set X to the x-coordinate of the starting point, so we \ can start tracing from the start of the edge JMP tred24 \ Jump into the middle of the loop at tred24 .tred22 DEX \ This instruction is modified above to: \ \ * INX (if we are stepping right along the x-axis) \ \ * DEX (if we are stepping left along the x-axis) \ \ So in either case this steps us along the x-axis by \ one pixel ADC yEdgeDeltaLo \ Add the y-axis delta to the slope error in A, so we \ keep track of the slope error as we move along the \ x-axis .tred23 BCC tred25 \ This instruction is modified above to: \ \ * BCC tred24 when we are tracing a right edge with \ INX or a left edge with DEX \ \ * BCC tred25 when we are tracing a right edge with \ DEX or a left edge with INX \ \ So if the addition didn't overflow then we have not \ moved into a new y-coordinate with this step along the \ x-axis, so jump to tred24 or tred25 so we do not move \ along the y-axis \ \ The difference in the two branches is as follows: \ \ * BCC tred24 stores the coordinate \ \ * BCC tred25 does not store the coordinate \ \ So when we are tracing a right edge with DEX and we \ are moving left along the x-axis, then we only \ update the coordinate when the slope error overflows, \ so we store the x-coordinate for the rightmost pixel \ there is more than one pixel along the x-axis for this \ step (so we store the rightmost pixels from the edge, \ which is what we want) \ \ The same is true when we are tracing a left edge with \ INX and we are moving right along the x-axis, but in \ this case we store the x-coordinate for the leftmost \ pixel for each step (so we store the leftmost pixels \ from this edge, which is what we want) SBC xEdgeDelta \ Set A = A - xEdgeDelta \ \ This updates the slope error in A \ \ This subtraction works as we just passed through a \ BCC, so we know the C flag is set DEC tred24+1 \ Decrement the low byte of the address in the \ instruction below so that it points to the table \ entry for the next y-coordinate down BEQ tred17 \ If we just decremented the low byte of the address to \ zero then we have finished stepping along the y-axis, \ so jump to tred17 in part 2 to finish off .tred24 STX xPolygonRight+&9E \ This instruction is modified above to the following: \ \ STX into address (xPolygonAddrHi yEdgeStartLo) \ \ So this stores the current x-coordinate as we step \ along the edge, storing the coordinate in either the \ xPolygonRight or xPolygonLeft table, and storing the \ coordinate in the offset given by the y-coordinate \ of the current position along the edge we are tracing \ \ The original value of xPolygonRight+&9E is just \ workspace noise and has no meaning, as it is modified \ before we get here .tred25 DEY \ Decrement the pixel counter in Y BNE tred22 \ Loop back to keep tracing the polygon edge until we \ have worked our way through all the pixels along the \ x-axis \ If we get here then we have finished tracing the edge \ and Y = 0, so we can now finish off by setting the \ value of drawPolygon to zero so the polygon gets drawn JMP tred15 \ Jump to tred15 to set drawPolygon and return from the \ subroutine (this BEQ is effectively a JMP as Y is \ always zero)
Name: TracePolygonEdge (Part 4 of 8) [Show more] Type: Subroutine Category: Drawing polygons Summary: Trace a steep edge that starts off-screen, without storing the coordinates, until we reach the screen and return to part 2
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tred26 INC tred13+1 \ Increment the low byte of the address in the STX \ instruction at tred13 to balance out the additional \ DEC tred13+1 that we do below when we rejoin the \ loop in part 2 LDX tred12 \ Modify the instruction at tred28 so it matches the STX tred28 \ modified instruction at tred12, so that's: \ \ * INX (if we are stepping right along the edge) \ \ * DEX (if we are stepping left along the edge) \ \ So in either case this steps us along the x-axis by \ one pixel LDX xEdgeStartLo \ Set X to the x-coordinate of the starting point, so we \ can start tracing from the start of the edge \ The loop below has the same structure as the loop in \ part 2 between tred11 and tred15, and which we enter \ via tred13, but in this version of the loop we do not \ store the coordinates of the edge, as the portion we \ are currently tracing is off-screen JMP tred29 \ Jump into the middle of the loop at tred29 to trace \ the edge but without storing the results .tred27 ADC xEdgeDelta \ Add the x-axis delta to the slope error in A, so we \ keep track of the slope error as we move along the \ y-axis BCC tred29 \ If the addition didn't overflow then we have not moved \ into a new x-coordinate with this step along the \ y-axis, so jump to tred29 so we do not move along the \ x-axis \ If we get here then the addition overflowed and the \ cumulative slope error along the x-axis has added up \ to a whole pixel, so we now need to step along the \ x-axis by a pixel SBC yEdgeDeltaLo \ Set A = A - yEdgeDeltaLo \ \ This updates the slope error in A \ \ This subtraction works as we just passed through a \ BCC, so we know the C flag is set .tred28 INX \ This instruction is modified above to: \ \ * INX (if we are stepping right along the edge) \ \ * DEX (if we are stepping left along the edge) \ \ So in either case this steps us along the x-axis by \ one pixel .tred29 DEC tred13+1 \ Decrement the low byte of the address in the STX \ instruction at tred13 so that it points to the table \ entry for the next y-coordinate down BEQ tred30 \ If we just decremented the low byte of the address to \ zero then we have finished stepping along the y-axis \ for this value of the high byte, so the next address \ we decrement it to will be on-screen \ \ We therefore jump to tred30 to set the address for the \ top of the screen and rejoin the edge-tracing loop in \ part 2 to keep tracing the edge DEY \ Decrement the pixel counter in Y BNE tred27 \ Loop back to keep tracing the polygon edge until we \ have worked our way through all the pixels along the \ y-axis JMP tred16 \ If we get here then we have finished tracing the edge \ and we didn't reach an on-screen portion, so jump to \ tred16 to return from the subroutine without changing \ drawPolygon (as we didn't draw anything in this \ routine) .tred30 DEC tred13+1 \ Decrement the low byte of the address in the STX \ instruction at tred13 to &FF (as we only get here when \ we have just decremented it to zero), so it now points \ to the address for the top line of the screen at a \ y-coordinate of 255 JMP tred14 \ Jump to tred25 to rejoin the normal edge-tracing loop \ in part 2 so we trace the rest of the edge
Name: TracePolygonEdge (Part 5 of 8) [Show more] Type: Subroutine Category: Drawing polygons Summary: Trace a shallow edge that starts off-screen, without storing the coordinates, until we reach the screen and return to part 3
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tred31 INC tred24+1 \ Increment the low byte of the address in the STX \ instruction at tred24 to balance out the additional \ DEC tred24+1 that we do below when we rejoin the \ loop in part 3 LDX tred22 \ Modify the instruction at tred32 so it matches the STX tred32 \ modified instruction at tred22, so that's: \ \ * INX (if we are stepping right along the x-axis) \ \ * DEX (if we are stepping left along the x-axis) \ \ So in either case this steps us along the x-axis by \ one pixel LDX xEdgeStartLo \ Set X to the x-coordinate of the starting point, so we \ can start tracing from the start of the edge \ The loop below has the same structure as the loop in \ part 3 between tred22 and tred25, and which we enter \ via tred24, but in this version of the loop we do not \ store the coordinates of the edge, as the portion we \ are currently tracing is off-screen JMP tred33 \ Jump into the middle of the loop at tred33 to trace \ the edge but without storing the results .tred32 INX \ This instruction is modified above to: \ \ * INX (if we are stepping right along the x-axis) \ \ * DEX (if we are stepping left along the x-axis) \ \ So in either case this steps us along the x-axis by \ one pixel ADC yEdgeDeltaLo \ Add the y-axis delta to the slope error in A, so we \ keep track of the slope error as we move along the \ x-axis BCC tred33 \ If the addition didn't overflow then we have not \ moved into a new y-coordinate with this step along the \ x-axis, so jump to tred33 so we do not move along the \ y-axis SBC xEdgeDelta \ Set A = A - xEdgeDelta \ \ This updates the slope error in A \ \ This subtraction works as we just passed through a \ BCC, so we know the C flag is set DEC tred24+1 \ Decrement the low byte of the address in the STX \ instruction at tred24 so that it points to the table \ entry for the next y-coordinate down BEQ tred34 \ If we just decremented the low byte of the address to \ zero then we have finished stepping along the y-axis \ for this value of the high byte, so the next address \ we decrement it to will be on-screen \ \ We therefore jump to tred34 to set the address for the \ top of the screen and rejoin the edge-tracing loop in \ part 3 to keep tracing the edge .tred33 DEY \ Decrement the pixel counter in Y BNE tred32 \ Loop back to keep tracing the polygon edge until we \ have worked our way through all the pixels along the \ x-axis JMP tred16 \ If we get here then we have finished tracing the edge \ and we didn't reach an on-screen portion, so jump to \ tred16 to return from the subroutine without changing \ drawPolygon (as we didn't draw anything in this \ routine) .tred34 DEC tred24+1 \ Decrement the low byte of the address in the STX \ instruction at tred24 to &FF (as we only get here when \ we have just decremented it to zero), so it now points \ to the address for the top line of the screen at a \ y-coordinate of 255 JMP tred25 \ Jump to tred25 to rejoin the normal edge-tracing loop \ in part 3 so we trace the rest of the edge
Name: GetPolygonLines (Part 6 of 6) [Show more] Type: Subroutine Category: Drawing polygons Summary: Split polygon edges whose coordinates are stored in two-byte numbers into smaller sections for processing
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gpol22 \ If we get here then: \ \ * The x-coordinates for points #X and #Y are both \ two-byte numbers \ \ * The y-axis delta in yEdgeDelta(Hi Lo) is non-zero \ and a two-byte number, and it is also positive STX pointX \ Store the number of point #X in pointX so we can \ retrieve it below LDA #0 \ Set scaleFactor = 0 so we can use it to record the STA scaleFactor \ scale factor we apply to the x-axis and y-axis deltas \ below when fitting them into single-byte numbers LDA xPolygonPointLo,Y \ Set (A T) to the following: SEC \ SBC xPolygonPointLo,X \ xPolygonPoint(Hi Lo) for point #Y STA T \ - xPolygonPoint(Hi Lo) for point #X LDA xPolygonPointHi,Y \ SBC xPolygonPointHi,X \ So (A T) contains the difference in the x-axis between \ the two polygon points STA xPointDeltaHi \ Store the high byte in xPointDeltaHi so we can test \ the sign below JSR Absolute16Bit \ Set (U T) = |A T| STA U \ \ So by this point we have: \ \ * The absolute x-axis delta in (U T) \ \ * The positive y-axis delta in yEdgeDelta(Hi Lo) ORA yEdgeDeltaHi \ Set A = U OR yEdgeDeltaHi \ \ So the highest set bit in A matches the position of \ the highest set bit across the two high bytes of the \ two deltas BEQ gpol24 \ If both U and yEdgeDeltaHi are zero then both deltas \ fit into one byte (the low byte), so jump to gpol24 \ If we get here then at least one of the deltas does \ not fit into a single byte, so we now scale both of \ the deltas down \ \ We do this by shifting both deltas to the right, and \ at the same time we shift A to the right, repeating \ the process until there are no more set bits in A, by \ which time we will have shifted the deltas by the \ minimum number of shifts required to zero both high \ bytes \ \ We also record the number of shifts in scaleFactor in \ the form of one set bit for each shift .gpol23 LSR yEdgeDeltaHi \ Shift the y-axis delta in yEdgeDelta(Hi Lo) to the ROR yEdgeDeltaLo \ right by one place to scale it down by half LSR U \ Shift the x-axis delta in (U T) to the right by one ROR T \ place to scale it down by half SEC \ Rotate a set bit into bit 0 of scaleFactor, so we ROL scaleFactor \ build up a sequence of set bits in the bottom part \ of scaleFactor that matches the number of shifts, \ effectively giving us the total cumulative scale \ factor of the shifts (in fact scaleFactor will end \ up being the cumulative scale factor minus 1, but \ that's good enough for our needs) LSR A \ Shift A to the right to move the highest set bit \ towards bit 0 BNE gpol23 \ If there is still a set bit in A then at least one of \ the high bytes in the deltas is non-zero, so loop back \ to perform another shift until both high bytes are \ zero .gpol24 \ By this point we have: \ \ * The scaled absolute x-axis delta in T \ \ * The scaled positive y-axis delta in yEdgeDeltaLo \ \ * The scale factor in scaleFactor \ \ We now ensure that bit 7 is clear in both low bytes, \ to make sure the single-byte values are both positive LDX yEdgeDeltaLo \ If yEdgeDeltaLo = 255 then jump back to gpol23 to CPX #255 \ scale both deltas down by one more shift BEQ gpol23 LDX T \ If T = 255 then jump back to gpol23 to scale both CPX #255 \ deltas down by one more shift BEQ gpol23 LDA U \ Set (A T) = (U T) \ \ This effectively sets (A T) to the two-byte value of \ the x-axis delta, though by this point U will always \ be zero BIT xPointDeltaHi \ We set xPointDeltaHi to the high byte of the original \ x-axis delta subtraction, so this sets the N flag \ according to the sign of that calculation JSR Absolute16Bit \ Set the sign of (A T) to that of the N flag argument, \ so (A T) now has the correct sign of the x-axis delta STA xEdgeDeltaHi \ Set xEdgeDelta(Hi Lo) = (A T) LDA T \ STA xEdgeDeltaLo \ So xEdgeDelta(Hi Lo) contains the signed x-axis delta \ By this point we have: \ \ * The scaled signed x-axis delta across the polygon \ edge in xEdgeDelta(Hi Lo) \ \ * The scaled signed y-axis delta across the polygon \ edge in (0 yEdgeDeltaLo), which also happens to be \ positive \ \ * The scale factor in scaleFactor \ We now trace the polygon edge from the starting point \ in point #Y to the end point in point #X \ \ To kick this process off, we set the coordinates in \ xEdgeEnd and yEdgeEnd to those of point #Y, as these \ variables are used to store the "end of the previous \ part of the edge" and are therefore used as the "start \ of the next part of the edge" in the tracing process LDA xPolygonPointLo,Y \ Set xEdgeEnd(Hi Lo) to the x-coordinate of point #Y, STA xEdgeEndLo \ from the x-coordinate tables we populated in parts 3 LDA xPolygonPointHi,Y \ and 4 STA xEdgeEndHi LDA drawViewPitchHi,Y \ Set yEdgeEnd(Hi Lo) to the y-coordinate of point #Y, STA yEdgeEndHi \ taken from the pitch angles of the points in the LDA drawViewPitchLo,Y \ drawing tables STA yEdgeEndLo LDA scaleFactor \ If scaleFactor is zero then we didn't need to do any BEQ gpol26 \ scaling on the deltas, so jump to gpol26 to skip the \ following and process the polygon edge in one step \ If we get here then we have scaled the deltas down by \ a factor of scaleFactor \ \ Because of the way we fed a set bit into scaleFactor \ for each shift, the scale factor is scaleFactor+1, so \ this is the same as splitting the polygon edge into \ scaleFactor+1 parts, each of which is a step along the \ the edge of the size given in the scaled-down deltas \ \ So we now process each of these small steps in a loop \ that we perform scaleFactor times in the first part \ and one more time at gpol26 \ \ For each step we move the start point to the end point \ from the previous step, and we move the end point \ along the edge by the delta \ \ We set up the "end point from the previous step" to \ point #Y above, so this process starts from point #Y \ and steps toward point #X \ \ Note that we set up the start and end points so the \ edge is either horizontal or slopes downwards when \ moving from the start to the end, as that's what the \ TracePolygonEdge routine expects \ \ We know that point #Y is higher than point #X as we \ set that up in part 5, so we trace along the line from \ the start at point #Y to the end at point #X .gpol25 LDA yEdgeEndLo \ Set the following, in sequence: STA yEdgeStartLo \ SEC \ yEdgeStart(Hi Lo) = yEdgeEnd(Hi Lo) SBC yEdgeDeltaLo \ STA yEdgeEndLo \ yEdgeEnd(Hi Lo) -= yEdgeDelta(Hi Lo) LDA yEdgeEndHi \ STA yEdgeStartHi \ So we step along the y-axis by one more delta SBC #0 STA yEdgeEndHi LDA xEdgeEndLo \ Set the following, in sequence: STA xEdgeStartLo \ SEC \ xEdgeStart(Hi Lo) = xEdgeEnd(Hi Lo) SBC xEdgeDeltaLo \ STA xEdgeEndLo \ xEdgeEnd(Hi Lo) -= xEdgeDelta(Hi Lo) LDA xEdgeEndHi \ STA xEdgeStartHi \ So we step along the x-axis by one more delta SBC xEdgeDeltaHi STA xEdgeEndHi JSR TracePolygonEdge \ Trace the small step of polygon edge that we just made \ along the edge, populating the xPolygonRight or \ xPolygonLeft table with the x-coordinate of the edge \ for each y-coordinate DEC scaleFactor \ Decrement the scale factor in scaleFactor BNE gpol25 \ Loop back to repeat the tracing process scaleFactor \ times \ \ As there are actually scaleFactor+1 steps, we now fall \ into the following to do one more step .gpol26 \ If we get here then we are either tracing the entire \ edge in one step, or we are performing the last step \ in a set of scaled steps \ \ If we are tracing the entire edge in one step, then we \ already set up the coordinates of the "end point from \ the previous step" in xEdgeEnd and yEdgeEnd to that of \ point #Y, so this process starts from point #Y and \ traces the edge to point #X \ \ If we are performing the last step in a set of scaled \ steps, then xEdgeEnd and yEdgeEnd are set to the end \ of the last step to be traced, so this process traces \ the last step towards point #X \ \ In either case, we start by setting the starting point \ for this tracing process to the values in xEdgeEnd and \ yEdgeEnd \ \ Note that we set up the start and end points so the \ edge is either horizontal or slopes downwards when \ moving from the start to the end, as that's what the \ TracePolygonEdge routine expects \ \ We know that point #Y is higher than point #X as we \ set that up in part 5, so we trace along the line from \ the start at point #Y to the end at point #X LDA yEdgeEndLo \ Set yEdgeStart(Hi Lo) = yEdgeEnd(Hi Lo) STA yEdgeStartLo \ LDA yEdgeEndHi \ So this is either the y-coordinate of point #Y or the STA yEdgeStartHi \ end of the last step in a multi-step trace LDA xEdgeEndLo \ Set xEdgeStart(Hi Lo) = xEdgeEnd(Hi Lo) STA xEdgeStartLo \ LDA xEdgeEndHi \ So this is either the x-coordinate of point #Y or the STA xEdgeStartHi \ end of the last step in a multi-step trace LDX pointX \ Set X to the number of point #X, which we stored at \ the start of this part LDA drawViewPitchLo,X \ Set yEdgeEnd(Hi Lo) to the y-coordinate of point #X, STA yEdgeEndLo \ from the x-coordinate tables we populated in parts 3 LDA drawViewPitchHi,X \ and 4 STA yEdgeEndHi LDA xPolygonPointLo,X \ Set xEdgeEnd(Hi Lo) to the x-coordinate of point #X STA xEdgeEndLo \ from the x-coordinate tables we populated in parts 3 LDA xPolygonPointHi,X \ and 4 STA xEdgeEndHi LDA yEdgeStartLo \ Set yEdgeDeltaLo = yEdgeStartLo - yEdgeEndLo SEC \ SBC yEdgeEndLo \ This calculates the y-axis delta along the edge, which STA yEdgeDeltaLo \ we know we can do in one byte by this point JSR TracePolygonEdge \ Trace the polygon edge, populating the xPolygonRight \ or xPolygonLeft table with the x-coordinate of the \ edge for each y-coordinate JMP gpol15 \ Jump back to part 5 to move on to the next polygon \ edge
Name: TracePolygonEdge (Part 6 of 8) [Show more] Type: Subroutine Category: Drawing polygons Summary: Trace a polygon edge where the start or end point x-coordinates are two-byte numbers
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tred35 \ If we get here then at least one of xEdgeStartHi and \ xEdgeEndHi is non-zero LDA xEdgeStartLo \ Set xEdgeDelta = xEdgeStart(Hi Lo) - xEdgeEnd(Hi Lo) SEC \ SBC xEdgeEndLo \ So xEdgeDelta contains the x-axis delta along the edge STA xEdgeDelta \ we are tracing (we can discard the high byte as we LDA xEdgeStartHi \ know it will be zero) SBC xEdgeEndHi BPL tred36 \ If the result is positive, jump to tred36 to skip the \ following, as the delta is already the correct sign \ for the absolute value \ If we get here then xEdgeDelta is negative and the \ start point is to the left of the end point LDA #0 \ Negate xEdgeDelta as follows: SEC \ SBC xEdgeDelta \ xEdgeDelta = 0 - xEdgeDelta STA xEdgeDelta \ \ So xEdgeDelta is positive and contains the absolute \ value of the x-axis delta LDX #&E8 \ Set A to the opcode for the INX instruction, so we \ trace along the edge by incrementing X LDA #%00000000 \ Set A to use as the high byte in yEdgeDeltaHi for a \ positive 16-bit number LDY #&E6 \ Set Y to the opcode for the INC zp instruction, so we \ trace along the edge by incrementing Y JMP tred37 \ Jump to tred37 to keep going .tred36 \ If we get here then xEdgeDelta is positive and the \ start point is to the right of the end point LDX #&CA \ Set A to the opcode for the DEX instruction, so we \ trace along the edge by decrementing X LDA #%11111111 \ Set A to use as the high byte in yEdgeDeltaHi for a \ negative 16-bit number LDY #&C6 \ Set Y to the opcode for the DEC zp instruction, so we \ trace along the edge by decrementing Y .tred37 STY T \ Store the INC/DEC opcode in T so we can retrieve it \ in parts 7 and 8 STA yEdgeDeltaHi \ Set the high byte of yEdgeDelta(Hi Lo) to be negative \ when we are moving along the x-axis from right to left \ and positive when we are moving along the x-axis from \ left to right LDY xEdgeDelta \ Set the status flags on the comparison of xEdgeDelta CPY yEdgeDeltaLo \ and yEdgeDeltaLo, so we can decide which axis has the \ longer side along the polygon edge (and therefore \ which axis we should step along when tracing the edge) LDY yEdgeStartLo \ Set (A Y) = (xPolygonAddrHi yEdgeStartLo) to use as LDA xPolygonAddrHi \ the storage address in the modification below BCS tred46 \ If xEdgeDelta >= yEdgeDeltaLo then the x-axis delta is \ the biggest and the edge has a shallow gradient, so \ jump to part 8 to trace the edge by stepping along the \ x-axis \ Otherwise the edge has a steep gradient, so fall \ through into part 7 to trace the edge by stepping \ along the y-axis
Name: TracePolygonEdge (Part 7 of 8) [Show more] Type: Subroutine Category: Drawing polygons Summary: Trace a polygon edge with a steep gradient by stepping along the y-axis (for two-byte x-coordinates)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
\ If we get here then xEdgeDelta < yEdgeDelta, so the \ y-axis delta is the biggest and the polygon edge has a \ steep gradient \ \ We therefore step along the y-axis one pixel at a time \ and cumulatively add the gradient to calculate the \ x-coordinate at each step \ \ We get here with the following: \ \ * (A Y) = (xPolygonAddrHi yEdgeStartLo) \ \ * X = the opcode for DEX or INX \ \ * T = the opcode for DEC zp or INC zp STA tred41+2 \ Modify the instruction at tred41 to use the address in \ (A Y), starting with the high byte STX tred40 \ Modify the instruction at tred40 to use the opcode \ specified in X, so we have: \ \ * DEX when xEdgeStart - xEdgeEnd is positive \ \ * INX when xEdgeStart - xEdgeEnd is negative \ \ We store the x-coordinate in X, so this ensures that: \ \ * We step left when required with DEX, for when the \ start point is to the right of the end point \ \ * We step right when required with INX, for when the \ start point is to the left of the end point \ \ We will run this instruction when we need to step \ along the x-axis, according to the cumulative value of \ the slope error LDA yEdgeStartHi \ If yEdgeStartHi = 0 then the start point is below the BEQ tred38 \ top edge of the screen, so skip the following \ instruction INY \ The start point is above the top edge of the screen, \ so increment the low byte of (A Y) .tred38 STY tred41+1 \ Modify the instruction at tred41 to use the address in \ (A Y), so it becomes: \ \ STX into address (xPolygonAddrHi yEdgeStartLo) \ \ with the low byte incremented by 1 when yEdgeStartHi \ is non-zero \ \ xPolygonAddrHi is set to the high byte of either \ xPolygonLeft or xPolygonRight, and both of these \ tables start on a page boundary, so this sets the \ address to offset yEdgeStartLo within the table \ specified by xPolygonAddrHi \ \ We will decrement this address for each step along the \ edge, so we step along the y-axis as we trace the edge LDA T \ Modify the instruction at tred45 to use the opcode STA tred45 \ specified in T, so we have: \ \ * DEC xEdgeStartHi when xEdgeStart - xEdgeEnd is \ positive \ \ * INC xEdgeStartHi when xEdgeStart - xEdgeEnd is \ negative \ \ So in either case this steps us along the x-axis and \ updates the high byte in xEdgeStartHi accordingly \ \ We will run this instruction when we need to step \ along the x-axis and update the high byte of the \ address in the process LDY yEdgeDeltaLo \ Set A = ~(yEdgeDeltaLo / 2) TYA \ LSR A \ We use A to keep track of the slope error, though I am EOR #&FF \ unsure of why we start with this value CLC \ Clear the C flag so the first addition we do in the \ following loop will work correctly INY \ Set U = yEdgeDeltaLo + 1 STY U \ \ We can use this as a pixel counter when stepping along \ the polygon edge's y-axis delta one pixel at a time, \ as there are yEdgeDeltaLo + 1 pixels along the edge \ (the additional one ensures we include the pixels at \ both ends) LDX xEdgeStartLo \ Set X to the x-coordinate of the starting point, so we \ can start tracing from the start of the edge JSR ModifyStoringCode \ Modify the instruction at tred41 and set drawPolygon \ and Y according to the values of xEdgeStartHi and \ yEdgeStartHi \ \ The instruction at tred41 is modified as follows: \ \ * BIT when yEdgeStartHi <> 0 \ \ * STY when yEdgeStartHi = 0 and xEdgeStartHi <> 0 \ \ * STX when yEdgeStartHi = 0 and xEdgeStartHi = 0 \ \ The drawPolygon flag is set as follows: \ \ * drawPolygon = 0 when yEdgeStartHi = 0 JMP tred41 \ Jump into the middle of the loop at tred41 .tred39 ADC xEdgeDelta \ Add the x-axis delta to the slope error in A, so we \ keep track of the slope error as we move along the \ y-axis BCC tred41 \ If the addition didn't overflow then we have not moved \ into a new x-coordinate with this step along the \ y-axis, so jump to tred41 so we do not move along the \ x-axis \ If we get here then the addition overflowed and the \ cumulative slope error along the x-axis has added up \ to a whole pixel, so we now need to step along the \ x-axis by a pixel SBC yEdgeDeltaLo \ Set A = A - yEdgeDeltaLo \ \ This updates the slope error in A \ \ This subtraction works as we just passed through a \ BCC, so we know the C flag is set .tred40 INX \ This instruction is modified above to: \ \ * INX (if we are stepping right along the edge) \ \ * DEX (if we are stepping left along the edge) \ \ So in either case this steps us along the x-axis by \ one pixel CPX yEdgeDeltaHi \ Set the status flags on the comparison of X and \ yEdgeDeltaHi CLC \ Clear the C flag so the comparison won't affect the \ arithmetic in this loop BEQ tred45 \ If X = yEdgeDeltaHi, jump to tred45 to step along the \ x-axis by updating the high byte in xEdgeStartHi .tred41 \ This is the entry point for the tracing loop and the \ point where we record the current pixel in the edge \ that we are tracing STX xPolygonLeft \ This instruction is modified above to the following \ address: \ \ Store into address (xPolygonAddrHi yEdgeStartLo) \ \ with the low byte incremented by 1 when yEdgeStartHi \ is non-zero \ \ So this stores the current x-coordinate as we step \ along the edge, storing the coordinate in either the \ xPolygonRight or xPolygonLeft table, and storing the \ coordinate in the offset given by the y-coordinate \ of the current position along the edge we are tracing \ \ The ModifyStoringCode routine modifies the instruction \ further, as follows: \ \ * BIT when yEdgeStartHi <> 0 \ \ * STY when yEdgeStartHi = 0 and xEdgeStartHi <> 0 \ \ * STX when yEdgeStartHi = 0 and xEdgeStartHi = 0 DEC tred41+1 \ Decrement the low byte of the address in the \ instruction above so that it points to the table \ entry for the next y-coordinate down BEQ tred43 \ If we just decremented the low byte of the address to \ zero then we have finished stepping along the y-axis, \ so jump to tred43 to finish off .tred42 DEC U \ Decrement the pixel counter in U BNE tred39 \ Loop back to keep tracing the polygon edge until we \ have worked our way through all the pixels along the \ y-axis JMP tred16 \ Jump to tred16 to return from the subroutine and stop \ the trace .tred43 DEC yEdgeStartHi \ Decrement the high byte of the current y-coordinate \ to keep moving along the y-axis BPL tred44 \ If yEdgeStartHi is positive then we haven't gone off \ the left edge of the screen, so jump to tred44 to \ keep going JMP tred16 \ Jump to tred16 to return from the subroutine and stop \ the trace .tred44 BNE tred42 \ If yEdgeStartHi is non-zero, jump to tred42 to keep \ working along the edge with the same y-coordinate DEC tred41+1 \ Decrement the low byte of the address in the \ instruction above so that it points to the table \ entry for the next y-coordinate down JSR ModifyStoringCode \ Modify the instruction at tred41 and set drawPolygon \ and Y according to the values of xEdgeStartHi and \ yEdgeStartHi \ \ The instruction at tred41 is modified as follows: \ \ * BIT when yEdgeStartHi <> 0 \ \ * STY when yEdgeStartHi = 0 and xEdgeStartHi <> 0 \ \ * STX when yEdgeStartHi = 0 and xEdgeStartHi = 0 \ \ The drawPolygon flag is set as follows: \ \ * drawPolygon = 0 when yEdgeStartHi = 0 JMP tred42 \ Jump to tred42 to keep working along the edge .tred45 INC xEdgeStartHi \ This instruction is modified above to: \ \ * DEC xEdgeStartHi when xEdgeStart - xEdgeEnd is \ positive \ \ * INC xEdgeStartHi when xEdgeStart - xEdgeEnd is \ negative \ \ So in either case this steps us along the x-axis and \ updates the high byte in xEdgeStartHi accordingly JSR ModifyStoringCode \ Modify the instruction at tred41 and set drawPolygon \ and Y according to the values of xEdgeStartHi and \ yEdgeStartHi \ \ The instruction at tred41 is modified as follows: \ \ * BIT when yEdgeStartHi <> 0 \ \ * STY when yEdgeStartHi = 0 and xEdgeStartHi <> 0 \ \ * STX when yEdgeStartHi = 0 and xEdgeStartHi = 0 \ \ The drawPolygon flag is set as follows: \ \ * drawPolygon = 0 when yEdgeStartHi = 0 JMP tred41 \ Jump to tred41 to restart the loop
Name: TracePolygonEdge (Part 8 of 8) [Show more] Type: Subroutine Category: Drawing polygons Summary: Trace a polygon edge with a shallow gradient by stepping along the x-axis (for two-byte x-coordinates)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
\ If we get here then xEdgeDelta >= yEdgeDelta, so the \ x-axis delta is the biggest and the polygon edge has a \ shallow gradient \ \ We therefore step along the x-axis one pixel at a time \ and cumulatively add the gradient to calculate the \ y-coordinate at each step \ \ We get here with the following: \ \ * (A Y) = (xPolygonAddrHi yEdgeStartLo) \ \ * X = the opcode for DEX or INX \ \ * T = the opcode for DEC zp or INC zp .tred46 STA tred50+2 \ Modify the instruction at tred50 to use the address in \ (A Y), starting with the high byte STX tred48 \ Modify the instruction at tred48 to use the opcode \ specified in X, so we have: \ \ * DEX when xEdgeStart - xEdgeEnd is positive \ \ * INX when xEdgeStart - xEdgeEnd is negative \ \ We store the x-coordinate in X, so this ensures that: \ \ * We step left when required with DEX, for when the \ start point is to the right of the end point \ \ * We step right when required with INX, for when the \ start point is to the left of the end point \ \ We will run this instruction for each step along the \ edge, so we step along the x-axis as we trace the edge LDA yEdgeStartHi \ If yEdgeStartHi = 0 then the start point is below the BEQ tred47 \ top edge of the screen, so skip the following \ instruction INY \ The start point is above the top edge of the screen, \ so increment the low byte of (A Y) .tred47 STY tred50+1 \ Modify the instruction at tred50 to use the address in \ (A Y), so it becomes: \ \ STX into address (xPolygonAddrHi yEdgeStartLo) \ \ with the low byte incremented by 1 when yEdgeStartHi \ is non-zero \ \ xPolygonAddrHi is set to the high byte of either \ xPolygonLeft or xPolygonRight, and both of these \ tables start on a page boundary, so this sets the \ address to offset yEdgeStartLo within the table \ specified by xPolygonAddrHi \ \ We will decrement this address when we need to step \ along the y-axis, according to the cumulative value of \ the slope error LDA T \ Modify the instruction at tred53 to use the opcode STA tred53 \ specified in T, so we have: \ \ * DEC xEdgeStartHi when xEdgeStart - xEdgeEnd is \ positive \ \ * INC xEdgeStartHi when xEdgeStart - xEdgeEnd is \ negative \ \ So in either case this steps us along the x-axis and \ updates the high byte in xEdgeStartHi accordingly \ \ We will run this instruction when we need to step \ along the x-axis and update the high byte of the \ address in the process LDY xEdgeDelta \ Set A = ~(xEdgeDelta / 2) TYA \ LSR A \ We use A to keep track of the slope error, though I am EOR #&FF \ unsure of why we start with this value CLC \ Clear the C flag so the first addition we do in the \ following loop will work correctly INY \ Set U = xEdgeDelta + 1 STY U \ \ We can use this as a pixel counter when stepping along \ the polygon edge's x-axis delta one pixel at a time, \ as there are xEdgeDelta + 1 pixels along the edge \ (the additional one ensures we include the pixels at \ both ends) LDX xEdgeStartLo \ Set X to the x-coordinate of the starting point, so we \ can start tracing from the start of the edge JSR ModifyStoringCode \ Modify the instruction at tred50 and set drawPolygon \ and Y according to the values of xEdgeStartHi and \ yEdgeStartHi \ \ The instruction at tred50 is modified as follows: \ \ * BIT when yEdgeStartHi <> 0 \ \ * STY when yEdgeStartHi = 0 and xEdgeStartHi <> 0 \ \ * STX when yEdgeStartHi = 0 and xEdgeStartHi = 0 \ \ The drawPolygon flag is set as follows: \ \ * drawPolygon = 0 when yEdgeStartHi = 0 JMP tred50 \ Jump into the middle of the loop at tred50 .tred48 INX \ This instruction is modified above to: \ \ * INX (if we are stepping right along the edge) \ \ * DEX (if we are stepping left along the edge) \ \ So in either case this steps us along the x-axis by \ one pixel CPX yEdgeDeltaHi \ Set the status flags on the comparison of X and \ yEdgeDeltaHi CLC \ Clear the C flag so the comparison won't affect the \ arithmetic in this loop BEQ tred53 \ If X = yEdgeDeltaHi, jump to tred53 to step along the \ x-axis by updating the high byte in xEdgeStartHi .tred49 ADC yEdgeDeltaLo \ Add the y-axis delta to the slope error in A, so we \ keep track of the slope error as we move along the \ x-axis BCC tred50 \ If the addition didn't overflow then we have not moved \ into a new y-coordinate with this step along the \ x-axis, so jump to tred50 so we do not move along the \ y-axis \ If we get here then the addition overflowed and the \ cumulative slope error along the y-axis has added up \ to a whole pixel, so we now need to step along the \ y-axis by a pixel SBC xEdgeDelta \ Set A = A - xEdgeDelta \ \ This updates the slope error in A \ \ This subtraction works as we just passed through a \ BCC, so we know the C flag is set DEC tred50+1 \ Decrement the low byte of the address in the \ instruction below so that it points to the table \ entry for the next y-coordinate down BEQ tred51 \ If we just decremented the low byte of the address to \ zero then we have finished stepping along the y-axis, \ so jump to tred51 to finish off .tred50 \ This is the entry point for the tracing loop and the \ point where we record the current pixel in the edge \ that we are tracing STX xPolygonLeft \ This instruction is modified above to the following \ address: \ \ Store into address (xPolygonAddrHi yEdgeStartLo) \ \ with the low byte incremented by 1 when yEdgeStartHi \ is non-zero \ \ So this stores the current x-coordinate as we step \ along the edge, storing the coordinate in either the \ xPolygonRight or xPolygonLeft table, and storing the \ coordinate in the offset given by the y-coordinate \ of the current position along the edge we are tracing \ \ The ModifyStoringCode routine modifies the instruction \ further, as follows: \ \ * BIT when yEdgeStartHi <> 0 \ \ * STY when yEdgeStartHi = 0 and xEdgeStartHi <> 0 \ \ * STX when yEdgeStartHi = 0 and xEdgeStartHi = 0 DEC U \ Decrement the pixel counter in U BNE tred48 \ Loop back to keep tracing the polygon edge until we \ have worked our way through all the pixels along the \ x-axis JMP tred16 \ Jump to tred16 to return from the subroutine and stop \ the trace .tred51 DEC yEdgeStartHi \ Decrement the high byte of the current y-coordinate \ to keep moving along the y-axis BPL tred52 \ If yEdgeStartHi is positive then we haven't gone off \ the left edge of the screen, so jump to tred52 to \ keep going JMP tred16 \ Jump to tred16 to return from the subroutine and stop \ the trace .tred52 BNE tred50 \ If yEdgeStartHi is non-zero, jump to tred50 to keep \ working along the edge with the same y-coordinate DEC tred50+1 \ Decrement the low byte of the address in the \ instruction above so that it points to the table \ entry for the next y-coordinate down JSR ModifyStoringCode \ Modify the instruction at tred41 and set drawPolygon \ and Y according to the values of xEdgeStartHi and \ yEdgeStartHi \ \ The instruction at tred41 is modified as follows: \ \ * BIT when yEdgeStartHi <> 0 \ \ * STY when yEdgeStartHi = 0 and xEdgeStartHi <> 0 \ \ * STX when yEdgeStartHi = 0 and xEdgeStartHi = 0 \ \ The drawPolygon flag is set as follows: \ \ * drawPolygon = 0 when yEdgeStartHi = 0 JMP tred50 \ Jump to tred50 to keep working along the edge .tred53 INC xEdgeStartHi \ This instruction is modified above to: \ \ * DEC xEdgeStartHi when xEdgeStart - xEdgeEnd is \ positive \ \ * INC xEdgeStartHi when xEdgeStart - xEdgeEnd is \ negative \ \ So in either case this steps us along the x-axis and \ updates the high byte in xEdgeStartHi accordingly JSR ModifyStoringCode \ Modify the instruction at tred41 and set drawPolygon \ and Y according to the values of xEdgeStartHi and \ yEdgeStartHi \ \ The instruction at tred41 is modified as follows: \ \ * BIT when yEdgeStartHi <> 0 \ \ * STY when yEdgeStartHi = 0 and xEdgeStartHi <> 0 \ \ * STX when yEdgeStartHi = 0 and xEdgeStartHi = 0 \ \ The drawPolygon flag is set as follows: \ \ * drawPolygon = 0 when yEdgeStartHi = 0 JMP tred49 \ Jump to tred49 to restart the loop
Name: CorruptSecretCode [Show more] Type: Subroutine Category: Cracker protection Summary: Corrupt the generation process for the landscape's secret code by fetching one more seed number than necessary
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckCrackerSeed calls CorruptSecretCode
.CorruptSecretCode BCC GetNextSeedNumber \ We only jump here with the C flag clear, so this \ generates the next number from the landscape's \ sequence of seed numbers, thus corrupting the \ generation of the landscape's secret code \ \ We then return to the caller using a tail call, so the \ player doesn't know anything has gone wrong
Name: ModifyStoringCode [Show more] Type: Subroutine Category: Drawing polygons Summary: Modify the code in TracePolygonEdge that stores the coordinates of the polygon edge that is being traced
Context: See this subroutine on its own page References: This subroutine is called as follows: * TracePolygonEdge (Part 7 of 8) calls ModifyStoringCode * TracePolygonEdge (Part 8 of 8) calls ModifyStoringCode

This routine modifies the code in part 6 of TracePolygonEdge that stores the x-coordinates of the polygon edge and sets the values of drawPolygon and Y as we trace the edge: yEdgeStartHi = 0 yEdgeStartHi <> 0 xEdgeStartHi < 0 STY, drawPolygon = 0, Y = 0 BIT xEdgeStartHi = 0 STX, drawPolygon = 0 BIT xEdgeStartHi > 0 STY, drawPolygon = 0, Y = 255 BIT Note that the value of Y doesn't appear to be used anywhere.
Returns: A A is preserved
.ModifyStoringCode PHA \ Store A on the stack so we can preserve it LDA yEdgeStartHi \ If yEdgeStartHi = 0 then jump to stor1 BEQ stor1 \ If we get here then: \ \ * yEdgeStartHi <> 0 LDA #&2C \ Set A to the opcode for the BIT addr instruction, so \ the TracePolygonEdge routine does not store anything \ in the xPolygonLeft or xPolygonRight table (as the BIT \ instruction has no effect beyond setting the status \ flags) BNE stor5 \ Jump to stor5 to modify the instructions at tred41 and \ tred50 (this BNE is effectively a JMP as A is never \ zero) .stor1 \ If we get here then: \ \ * yEdgeStartHi = 0 \ \ and A = 0 STA drawPolygon \ Set drawPolygon = 0 so the polygon gets drawn LDA xEdgeStartHi \ If xEdgeStartHi <> 0 then jump to stor2 BNE stor2 \ If we get here then: \ \ * xEdgeStartHi = 0 \ \ * yEdgeStartHi = 0 LDA #&8E \ Set A to the opcode for the STX addr instruction, so \ the TracePolygonEdge routine stores the value of X \ into the xPolygonLeft or xPolygonRight table BNE stor5 \ Jump to stor5 to modify the instructions at tred41 and \ tred50 (this BNE is effectively a JMP as A is never \ zero) .stor2 \ If we get here then: \ \ * xEdgeStartHi <> 0 \ \ * yEdgeStartHi = 0 \ \ and the flags are set according to the value of \ xEdgeStartHi BPL stor3 \ If xEdgeStartHi is positive, jump to stor3 \ If we get here then: \ \ * xEdgeStartHi < 0 \ \ * yEdgeStartHi = 0 LDY #0 \ Set Y = 0 JMP stor4 \ Jump to stor4 to modify the code to use STY .stor3 \ If we get here then: \ \ * xEdgeStartHi > 0 \ \ * yEdgeStartHi = 0 LDY #255 \ Set Y = 255 .stor4 LDA #&8C \ Set A to the opcode for the STY addr instruction, so \ the TracePolygonEdge routine stores the value of Y \ into the xPolygonLeft or xPolygonRight table .stor5 STA tred41 \ Modify the instruction at tred41 to use the opcode \ specified in A STA tred50 \ Modify the instruction at tred50 to use the opcode \ specified in A PLA \ Restore the value of A that we stored on the stack \ above RTS \ Return from the subroutine
Name: GetNextSeedNumber [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Set A to a seed number
Seed numbers in The Sentinel are produced using a five-byte (40-bit) linear feedback shift register (LFSR) with EOR feedback. Specifically, to generate a new seed number, we shift the LFSR left by eight places, and on each shift we insert the EOR of bits 19 and 32 into bit 0 of the register. After eight shifts, the top byte is our next seed number.
.GetNextSeedNumber STY yStoreNextSeed \ Store Y in yStoreNextSeed so it can be preserved \ across calls to the routine \ We generate a new seed number by shifting the \ five-byte linear feedback shift register in \ seedNumberLFSR(4 3 2 1 0) by eight places, inserting \ EOR feedback as we do so LDY #8 \ Set a shift counter in Y .rand1 LDA seedNumberLFSR+2 \ Apply EOR feedback to the linear feedback shift LSR A \ register by taking the middle byte seedNumberLFSR+2, LSR A \ shifting it right by three places, EOR'ing it with LSR A \ seedNumberLFSR+4 in the output end of the shift EOR seedNumberLFSR+4 \ register and rotating bit 0 of the result into the C ROR A \ flag \ \ This is the same as taking bit 3 of seedNumberLFSR+2 \ and EOR'ing it with bit 0 of seedNumberLFSR+4 into the \ C flag \ \ We now use the C flag as the next input bit into the \ shift register \ \ So this is the same as EOR'ing bits 19 and 32 of our \ 40-bit register and shifting the result into bit 0 of \ the register ROL seedNumberLFSR \ Shift seedNumberLFSR(4 3 2 1 0) to the left by one ROL seedNumberLFSR+1 \ place, inserting the C flag into bit 0 of the input ROL seedNumberLFSR+2 \ end of the shift register in seedNumberLFSR ROL seedNumberLFSR+3 ROL seedNumberLFSR+4 DEY \ Decrement the shift counter BNE rand1 \ Loop back until we have shifted eight times LDY yStoreNextSeed \ Restore the value of Y from yStoreNextSeed that we \ stored at the start of the routine, so that it's \ preserved LDA seedNumberLFSR+4 \ Set A to the output end of the shift register in \ seedNumberLFSR+4 to give us our next seed number RTS \ Return from the subroutine
Name: yStoreNextSeed [Show more] Type: Variable Category: Maths (Arithmetic) Summary: Temporary storage for Y so it can be preserved through calls to GetNextSeedNumber
Context: See this variable on its own page References: This variable is used as follows: * GetNextSeedNumber uses yStoreNextSeed
.yStoreNextSeed EQUB 0
Name: PrintNumber [Show more] Type: Subroutine Category: Text Summary: Print a number as a single digit, printing zero as a capital "O"
Context: See this subroutine on its own page References: This subroutine is called as follows: * Print2DigitBCD calls PrintNumber

Arguments: A The number to be printed (0 to 9)
.PrintNumber CLC \ Convert the number in A into an ASCII digit by adding ADC #'0' \ ASCII "0" \ Fall into PrintDigit to print the digit in A
Name: PrintDigit [Show more] Type: Subroutine Category: Text Summary: Print a numerical digit, printing zero as a capital "O"
Context: See this subroutine on its own page References: This subroutine is called as follows: * PrintInputBuffer calls PrintDigit

Arguments: A The numerical digit to be printed as an ASCII code
.PrintDigit CMP #'0' \ If the character in A is not a zero, jump to zero1 to BNE zero1 \ skip the following LDA #'O' \ The character in A is a zero, so set A to ASCII "O" so \ we print zero as capital "O" instead .zero1 BIT printTextIn3D \ If bit 7 of printTextIn3D is set then we are printing BMI SpawnCharacter3D \ 3D text, so jump to SpawnCharacter3D to spawn the \ character in 3D text blocks JMP PrintCharacter \ Otherwise jump to PrintCharacter to print the single- \ byte VDU command or character in A, returning from the \ subroutine using a tail call
Name: SpawnCharacter3D (Part 1 of 2) [Show more] Type: Subroutine Category: Title screen Summary: Spawn a character on the landscape in large 3D blocks for drawing on the main title screen or secret code screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTitleScreen calls SpawnCharacter3D * PrintDigit calls SpawnCharacter3D * SpawnSecretCode3D calls SpawnCharacter3D

Arguments: A The character to be spawned or the tile coordinates of the character on the landscape: * %00xxxxxx = bits 0-5 contain the ASCII code of the character to spawn * %10xxxxxx = bits 0-4 contain the tile x-coordinate of the character on the landscape * %11xxxxxx = bits 0-4 contain the tile z-coordinate of the character on the landscape
Returns: X X is preserved Y Y is preserved
.SpawnCharacter3D PHA \ Store the character on the stack so we can retrieve it \ below STA characterDef \ Store the character in the first byte of the OSWORD \ block at characterDef, so we can extract the operating \ system's character definition (if this is a printable \ character) AND #%11000000 \ Clear all bits of the character number except for bits \ 6 and 7 BPL spac2 \ If bit 7 is clear then this is a printable character, \ so jump to spac2 to spawn the character in 3D text \ blocks ASL A \ Bit 7 is set so this is a tile coordinate, so extract ASL A \ bit 6 of the character number into the C flag PLA \ Retrieve the character number from the stack and AND #%00011111 \ extract bits 0-4 to get the coordinate value BCS spac1 \ If bit 6 of the original character number is set then \ this is a tile z-coordinate, so jump to spac1 to set \ zTileCharacter STA xTileCharacter \ Bit 6 of the original character number is clear, so \ store bits 0-4 in xTileCharacter, so we can spawn the \ next printable character that's passed to the routine \ in the right place RTS \ Return from the subroutine .spac1 STA zTileCharacter \ Bit 6 of the original character number is set, so \ store bits 0-4 in zTileCharacter, so we can spawn the \ next printable character that's passed to the routine \ in the right place RTS \ Return from the subroutine .spac2 \ If we get here then we have a printable character in A TXA \ Store X and Y on the stack to we can preserve them PHA TYA PHA LDY #HI(characterDef) \ Call OSWORD with A = 10 to extract the operating LDX #LO(characterDef) \ system's character definition into the block at LDA #10 \ characterDef JSR OSWORD \ \ The first byte of the block contains the ASCII code \ of the character (which we stored above), and the call \ returns the character definition from the second byte \ onwards LDA zTileCharacter \ Set zTile to the z-coordinate where we want to spawn STA zTile \ the character LDX #7 \ We now loop through the top seven rows of the \ character definition, so set a row counter in X \ \ We actually loop through the character definition \ from the bottom to top, ignoring the very bottom row \ of the definition as this is always blank for the \ capital letters and digits that we are displaying \ (the bottom row is only used for descenders, and \ "THE SENTINEL" and landscape digits don't have them) \ \ Instead we insert a blank pixel row at the top of the \ definition, on the iteration when X is zero \ We now have a short interlude to check some of the \ anti-cracker code, so we can corrupt the secret code \ being drawn on-screen if this landscape has not been \ played through properly
Name: CheckCrackerSeed [Show more] Type: Subroutine Category: Cracker protection Summary: Check whether the anti-cracker seed-related data is correctly set up, as part of the anti-cracker code
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.CheckCrackerSeed LDA alteredSeed-7,X \ Set A to the value of alteredSeed, which we set in \ the AlterCrackerSeed routine CMP CrackerSeed+1-7,X \ If A >= the contents of CrackerSeed+1 then the code BCS csee1 \ in AlterCrackerSeed was correctly run (which only \ happens if the gameplay routines are run, i.e. when \ the landscape has been played), so jump to csee1 to \ skip the following \ If we get here then the AlterCrackerSeed routine has \ not been correctly run, which means the landscape was \ not played properly, so we now corrupt the secret code \ that's being drawn on-screen (if that's why we are \ here) JSR CorruptSecretCode \ At this point A < the contents of CrackerSeed+1 and \ the C flag is clear, so CorruptSecretCode will call \ the GetNextSeedNumber routine, which will in turn \ corrupt the generation of the landscape's secret code \ by moving one step too far in the landscape's sequence \ of seed numbers .csee1 \ Fall through into part 2 of SpawnCharacter3D to \ continue with the character-spawning process
Name: SpawnCharacter3D (Part 2 of 2) [Show more] Type: Subroutine Category: Title screen Summary: Spawn large 3D blocks for the extracted character definition
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
\ We now loop through the top seven rows in the \ character definition, using X as the row counter \ \ We draw the character from the bottom up on the \ landscape, so that's drawing the character rows on \ the landscape from front the back as we work through \ the character definition from bottom to top \ \ The row counter in X therefore iterates from 7 down \ to 0, with row 7 spawning the blocks for the \ penultimate row in the character definition, and row 0 \ spawning a blank row (as this moves the blank row in \ the character definition from the bottom to the top, \ as discussed in part 1) .spac3 ASL characterDef,X \ The leftmost column of each character definition is \ blank, so that characters are spaced out, so this \ shifts the character definition row to the left to \ move this blank column to the right side of the \ character definition, so the character's 3D blocks \ start at the correct tile coordinate LDA xTileCharacter \ Set xTile to the x-coordinate where we want to spawn STA xTile \ the character \ Each row in the character definition consists of eight \ bits (where each bit will be represented by a block on \ the landscape) \ \ In order to get tall characters on-screen, rather than \ the rather square characters in the operating system's \ font, we actually draw each character so that it's \ four tiles wide and eight tiles tall, so each tile \ contains a pair of blocks that represents a pair of \ bits in the character definition LDA #4 \ Set a loop counter in loopCounter to work through the STA loopCounter \ four pairs in the eight-block row .spac4 ASL characterDef,X \ Shift the character definition row to the left by two ROL A \ places and extract the top two bits into bits 0 and 1 ASL characterDef,X \ of A, so this is the leftmost block pair from this row ROL A AND #%00000011 \ Clear bits 2 to 7 of A and store the result in Y, so TAY \ Y contains the bit pattern for the two leftmost bits \ in the character row \ \ We can now use this as an index into the table that \ maps bit patterns to object numbers in objBlockNumber LDA objBlockNumber,Y \ Fetch the object number of the 3D text block object \ that matches the bit pair that we just extracted \ \ This actually fetches the object number + 32, though \ the 32 part is removed when the block is drawn in the \ DrawTileAndObjects, so this doesn't seem to have any \ effect PHA \ Store the object number on the stack, as follows: \ \ * 32 + 0 for no blocks in the pair \ \ * 32 + 7 for no block (left), block (right) \ \ * 32 + 8 for block (left), no block (right) \ \ * 32 + 9 for block (left), no block (right) \ \ This maps the pair to one of the three 3D text block \ objects JSR GetTileData \ Set A to the tile data for the tile anchored at \ (xTile, zTile), which we ignore, but this also sets \ the tile page in tileDataPage and the index in Y, so \ tileDataPage+Y now points to the tile data entry in \ the tileData table PLA \ Retrieve the object number for the 3D text block pair STA (tileDataPage),Y \ and store it in the tile data for the tile where we \ want to spawn this part of the character definition \ \ Note that the DrawTileAndObjects routine has separate \ logic when drawing 3D text, so this doesn't follow the \ normal tile data rules, and instead we just need the \ object number in the low nibble, which we fetched from \ the objBlockNumber above INC xTile \ Increment the x-coordinate to move on to the next tile \ to the right, for the next bit pair DEC loopCounter \ Decrement the loop counter to move onto the next bit \ pair in the character definition BNE spac4 \ Loop back to spawn the next bit pair's objects until \ we have done all four pairs in the character row INC zTile \ Increment the z-coordinate to move backwards to the \ tile row behind in the landscape DEX \ Decrement the character row counter in X BMI spac5 \ If we just spawned row 0 then X will now be negative, \ so jump to spac5 to finish off as we have spawned all \ eight character rows BNE spac3 \ Otherwise loop back until we have spawned rows 7 to 1 \ If we get here then we just spawned row 1, so now we \ need to spawn a blank for row 0 LDA #0 \ Zero the character definition row that we will use STA characterDef \ when X is 0, so we don't spawn any blocks for the top \ row of the 3D text block character BEQ spac3 \ Jump to spac3 to spawn row 0 (this BEQ is effectively \ a JMP as A is always zero) .spac5 LDA xTileCharacter \ We just spawned a character in the landscape, so add 4 CLC \ to xTileCharacter so the next character gets spawned ADC #4 \ to the right of the one we just spawned STA xTileCharacter PLA \ Pull A from the stack to reverse the push that we did \ at the start of the routine TAY \ Retrieve X and Y from the stack so they are preserved PLA TAX PLA RTS \ Return from the subroutine
Name: objBlockNumber [Show more] Type: Variable Category: Title screen Summary: A lookup table to convert bit pairs into object numbers for spawning 3D text blocks on the landscape
Context: See this variable on its own page References: This variable is used as follows: * SpawnCharacter3D (Part 2 of 2) uses objBlockNumber
.objBlockNumber EQUB 32 + 0 \ %00 = no block (left), no block (right) \ \ No 3D text blocks are required, so set the object \ number to 0 to indicate no object EQUB 32 + 7 \ %01 = no block (left), block (right) \ \ This is the shape of 3D text block 1, which is object \ type 7 EQUB 32 + 8 \ %10 = block (left), no block (right) \ \ This is the shape of 3D text block 2, which is object \ type 8 EQUB 32 + 9 \ %11 = block (left), no block (right) \ \ This is the shape of 3D text block 3, which is object \ type 9
Name: DrawTitleScreen [Show more] Type: Subroutine Category: Title screen Summary: Draw the title screen or the screen showing the secret code
Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishLandscape calls DrawTitleScreen * MainTitleLoop calls DrawTitleScreen * SecretCodeError calls DrawTitleScreen

Arguments: A Determines the type of screen to draw: * If bit 7 = 0 then draw the title screen * If bit 7 = 1 then draw the secret code screen
.DrawTitleScreen STA screenType \ Store the screen type in A in screenType, so we can \ refer to it below LDA #128 \ Set objectYawAngle+63 = 128 STA objectYawAngle+63 \ \ The degree system in the Sentinel looks like this: \ \ 0 \ -32 | +32 Overhead view of object \ \ | / \ \ | / 0 = looking straight ahead \ \|/ +64 = looking sharp right \ -64 -----+----- +64 -64 = looking sharp left \ /|\ \ / | \ \ / | \ \ -96 | +96 \ 128 \ \ So this makes object #63 face directly out of the \ screen LDA #&E0 \ For object #63, set the following: STA yObjectLo+63 \ LDA #&02 \ yObject(Hi Lo) = &2E0 STA yObjectHi+63 \ \ So this sets the altitude of object #63 to just under \ three full block heights (2.875, to be precise) \ \ We use object #63 for drawing the 3D text blocks in \ the game title in the main title screen, or the code \ in the secret code screen, so this ensures that the \ text blocks are drawn at the correct height to get the \ title effect SEC \ Set bit 7 of drawingTitleScreen to indicate that we ROR drawingTitleScreen \ are drawing a title screen LDA #0 \ Call ProcessTileData with A = 0 to zero the tile data JSR ProcessTileData \ for the whole landscape BIT screenType \ If bit 7 of the screen type is clear, jump to titl1 to BPL titl1 \ draw "THE SENTINEL" in large 3D text for the main \ title screen \ If we get here then bit 7 of the argument is set, so \ we now draw the secret code JSR SpawnSecretCode3D \ Spawn the secret code in large 3D text blocks LDX #3 \ Set X = 3 to pass to DrawTitleView so the background \ of the secret code screen is black with stars LDA #0 \ Set A = 0 so the call to DrawTitleView draws a robot \ on the right of the screen BEQ titl3 \ Jump to titl3 to skip the following and draw the robot \ (this BEQ is effectively a JMP as A is always zero) .titl1 LDX #0 \ We now look through all the characters in the title \ text, drawing each one in turn, so set X as a \ character index .titl2 LDA titleText,X \ Set A to the X-th character in the title text JSR SpawnCharacter3D \ Spawn the 3D text blocks for drawing the character in \ A in large 3D text INX \ Increment the character index CPX #15 \ Loop back until we have drawn all 15 characters in the BCC titl2 \ title text LDX #1 \ Set X = 1 to pass to DrawTitleView so the background \ of the landscape preview is solid blue LDA #5 \ Set A = 5 so the call to DrawTitleView draws the \ Sentinel on the right of the screen .titl3 LDY #1 \ Set Y = 1 to pass to DrawTitleView so it draws the \ screen with the correct perspective for the title \ screen JSR DrawTitleView \ Draw the title screen with the object and background \ defined in A and X LSR drawingTitleScreen \ Clear bit 7 of drawingTitleScreen to indicate we are \ no longer drawing a title screen RTS \ Return from the subroutine
Name: screenType [Show more] Type: Variable Category: Title screen Summary: A variable that determines whether we are drawing the title screen or the secret code screen in the DrawTitleScreen routine
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleScreen uses screenType
.screenType EQUB 0
Name: titleText [Show more] Type: Variable Category: Title screen Summary: The text to draw on the title screen
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleScreen uses titleText
.titleText EQUB %10000000 + 4 \ Move to tile coordinate (4, 21), towards the back of EQUB %11000000 + 21 \ the landscape EQUS "THE" \ Draw "THE" in 3D text blocks EQUB %10000000 + 0 \ Move to tile coordinate (0, 7), which is further EQUB %11000000 + 7 \ forward and a bit to the left EQUS "SENTINEL" \ Draw "SENTINEL" in 3D text blocks
Name: ReadNumber [Show more] Type: Subroutine Category: Keyboard Summary: Read a number from the keyboard into the input buffer
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainTitleLoop calls ReadNumber

Arguments: A The maximum number of digits to read
.ReadNumber STA T \ Set T to the maximum number of digits to read JSR EnableKeyboard \ Select the keyboard as the input stream and flush the \ keyboard buffer \ We start by clearing the input buffer by filling it \ with spaces LDY #7 \ The input buffer is eight bytes long, so set a byte \ counter in Y LDA #' ' \ Set A to the space character for use when clearing the \ input buffer .rkey1 STA inputBuffer,Y \ Reset the Y-th byte of the input buffer to contain a \ space character DEY \ Decrement the byte counter BPL rkey1 \ Loop back until we have cleared the whole input buffer JSR PrintInputBuffer \ Print the contents of the keyboard input buffer, which \ will erase any existing text on-screen as we just \ filled the input buffer with spaces .rkey2 LDY #0 \ We now read the specified number of key presses, so \ set Y as a counter for the number of valid characters \ in the input buffer, starting from zero (as the buffer \ is empty at the start) .rkey3 JSR ReadCharacter \ Read a character from the keyboard into A, so A is set \ to the ASCII code of the pressed key CMP #13 \ If RETURN was pressed then jump to rkey9 to return BEQ rkey9 \ from the subroutine, as RETURN terminates the input CMP #'0' \ If the key pressed is less than ACSII "0" then it is a BCC rkey3 \ control code, so jump back to rkey3 to keep listening \ for key presses, as control codes are not valid input CMP #127 \ If the key pressed is less than ASCII 127 then it is BCC rkey5 \ a printable ASCII character, so jump to rkey5 to \ process it BNE rkey3 \ If the key pressed is not the DELETE key then jump \ back to rkey3 to keep listening for key presses, as \ this is not a valid input \ If we get here then DELETE has been pressed, so we \ need to delete the most recently entered character \ \ Because the input buffer is stored as an ascending \ stack, this means we need to delete the character at \ inputBuffer, which is the top of the buffer stack, \ and shuffle the rest of the stack to the left to \ close up the gap (so that's shuffling then down in \ memory) DEY \ Decrement Y to reduce the character count by one, as \ we are about to delete a character from the buffer BMI rkey2 \ If we just decremented Y past zero then the buffer is \ empty, so jump to rkey2 to reset Y to zero and keep \ reading characters, as there is nothing to delete LDX #0 \ Otherwise we want to delete the character from the top \ of the buffer stack at inputBuffer and shuffle the \ rest of the stack along to the left, so set an index \ in X to work through the buffer from left to right .rkey4 LDA inputBuffer+1,X \ Shuffle the character at index X + 1 to the left and STA inputBuffer,X \ into index X INX \ Increment the buffer index to point to the next \ character in the buffer as we work from left to right CPX #7 \ Loop back until we have shuffled all seven characters BNE rkey4 \ to the left LDA #' ' \ Set the last character in the input buffer to a space STA inputBuffer+7 \ as the bottom of the stack at inputBuffer+7 is now \ empty BNE rkey8 \ Jump to rkey8 to print the updated contents of the \ input buffer, so we can see the character being \ deleted, and loop back to listen for more key presses \ (this BNE is effectively a JMP as A is never zero) .rkey5 \ If we get here then the key press in A is a printable \ ASCII character CMP #':' \ If the character in A is ASCII ":" or greater then it BCS rkey3 \ is not a number, so jump to rkey3 to keep listening \ for key presses, as we are only interested in numbers CPY T \ If Y <> T then the buffer does not yet contain the BNE rkey6 \ maximum number of digits allowed, so jump to rkey6 to \ process the number key press LDA #7 \ Otherwise the buffer is already full, so perform a JSR OSWRCH \ VDU 7 command to make a system beep JMP rkey3 \ Jump back to rkey3 to listen for more key presses .rkey6 \ If we get here then the key press in A is a number key \ and the input buffer is not full INY \ Increment Y to increase the character count by one, as \ we are about to add a character to the buffer PHA \ Store the key number in A on the stack, so we can \ retrieve it after the following loop LDX #6 \ We now want to insert the new character into the top \ of the buffer stack at inputBuffer and shuffle the \ stack along to the right (so that's shuffling then up \ in memory), so set an index in X to work through the \ buffer from right to left .rkey7 LDA inputBuffer,X \ Shuffle the character at index X to the right and into STA inputBuffer+1,X \ index X + 1 DEX \ Decrement the buffer index to point to the next \ character in the buffer as we work from right to left BPL rkey7 \ Loop back until we have shuffled all seven characters \ to the right PLA \ Restore the key number that we stored on the stack \ above STA inputBuffer \ Store the key press at the top of the stack, in \ inputBuffer .rkey8 JSR PrintInputBuffer \ Print the contents of the keyboard input buffer so we \ we can see the characters being entered or deleted JMP rkey3 \ Jump back to rkey3 to listen for more key presses .rkey9 RTS \ Return from the subroutine
Name: PrintInputBuffer [Show more] Type: Subroutine Category: Text Summary: Print the contents of the keyboard input buffer
Context: See this subroutine on its own page References: This subroutine is called as follows: * ReadNumber calls PrintInputBuffer

Arguments: T The size of the input buffer
.PrintInputBuffer SEC \ Set bit 7 of textDropShadow so the following text is ROR textDropShadow \ printed without a drop shadow \ We now print the contents of the input buffer \ \ Key presses are stored in the input buffer using an \ ascending stack, with new input being pushed into \ inputBuffer, so to print the contents of the buffer, \ we need to print it backwards, from the oldest input \ at index T - 1 down to the most recent input at \ index 0 LDX T \ Set X = T - 1 so we can use X as an index into the DEX \ buffer, starting from the oldest input .pinb1 LDA inputBuffer,X \ Set A to the X-th entry in the input buffer JSR PrintDigit \ Print the numerical digit in A DEX \ Decrement the buffer index BPL pinb1 \ Loop back until we have printed the whole buffer \ We now want to backspace by the number of characters \ we just printed, to leave the cursor at the start of \ the printed number LDX T \ Set X to the size of the input buffer, which we can \ use as a character counter in the following loop to \ ensure we backspace by the correct number of \ characters to reach the start of printed number LDA #8 \ Set A = 8 to perform a series of VDU 8 commands, each \ of which will backspace the cursor by one character .pinb2 JSR PrintDigit \ Print the character in A, which performs a VDU 8 to \ backspace the cursor by one character DEX \ Decrement the character counter BNE pinb2 \ Loop back until we have backspaced to the start of the \ buffer contents that we just printed LSR textDropShadow \ Clear bit 7 of textDropShadow so text tokens are once \ again printed with drop shadows RTS \ Return from the subroutine