REM ****************************************************************************** REM REM LANDER GAME SOURCE REM REM Lander was written by David Braben and is copyright D.J.Braben 1987 REM REM The code on this site has been reconstructed from a disassembly of the game on REM the Arthur, RISC OS 2 and RISC OS 3.00 application discs REM REM The commentary is copyright Mark Moxon, and any misunderstandings or mistakes REM in the documentation are entirely my fault REM REM The terminology and notations used in this commentary are explained at REM https;//lander.bbcelite.com/terminology REM REM The deep dive articles referred to in this commentary can be found at REM https;//lander.bbcelite.com/deep_dives REM REM ------------------------------------------------------------------------------ REM REM This source file produces the following binary file; REM REM * GameCode REM REM ****************************************************************************** CODE = &00008000 : REM The build address for the game code REM ****************************************************************************** REM REM Configuration variables REM REM ****************************************************************************** TILE_SIZE = &01000000 : REM The length of one side of a square : REM landscape tile in 3D coordinates TILES_X = 13 : REM The number of tile corners in a landscape : REM row from left to right [i.e. 12 tiles] TILES_Z = 11 : REM The number of tile corners in a landscape : REM row from front to back [i.e. 10 tiles] MAX_PARTICLES = 484 : REM The maximum number of particles at any one : REM time LAUNCHPAD_OBJECT = 9 : REM The type of object along the right edge of : REM the launchpad [i.e. the three rockets] LAUNCHPAD_ALTITUDE = &03500000 : REM The altitude of the launchpad SEA_LEVEL = &05500000 : REM The altitude of sea level LANDING_SPEED = &00200000 : REM The maximum safe landing speed SMOKE_RISING_SPEED = &00080000 : REM The speed at which smoke particles rise up : REM from a destroyed object UNDERCARRIAGE_Y = &00640000 : REM The height of the ship's undercarriage : REM [0.390625 tiles], which is the distance : REM from the centre of the ship to the bottom : REM of the ship BUFFER_SIZE = 4308 : REM The size of each of the graphics buffers : REM in bytes REM ****************************************************************************** REM REM Calculated constants REM REM ****************************************************************************** STORE = 256 * INT(TILES_X / 32) : REM We need 256 bytes of cornerStore for every : REM 32 tiles across the landscape, so STORE : REM contains the number of extra bytes of data : REM we need for each extra set of 32 tiles on : REM top of the default 256 byte buffers [so : REM STORE is zero for the default landscape] LAUNCHPAD_Y = LAUNCHPAD_ALTITUDE - UNDERCARRIAGE_Y : REM The y-coordinate of the : REM ship as it sits on the : REM launchpad, and on top of : REM its undercarriage LAUNCHPAD_SIZE = TILE_SIZE * 8 : REM Size of the launchpad [8 tile sizes] HIGHEST_ALTITUDE = TILE_SIZE * 52 : REM Highest altitude for the engines to work : REM [52 tile sizes, but as a positive number; : REM the altitude is actually -TILE_SIZE * 52] SPLASH_HEIGHT = TILE_SIZE / 16 : REM The height above the sea at which splash : REM particles are added when a particle : REM splashes into the sea [1/16 of a tile] CRASH_CLOUD_Y = TILE_SIZE * 5 / 16 : REM The vertical distance above the : REM player's ship where we add the : REM explosion cloud when they crash : REM [5/16-th of a tile] SMOKE_HEIGHT = TILE_SIZE * 3 / 4 : REM The height at which smoke particles are : REM added above the bottom of a destroyed : REM object [3/4 of a tile] SAFE_HEIGHT = TILE_SIZE * 3 / 2 : REM The minimum safe height for avoiding : REM objects on the ground [1.5 tile sizes] CAMERA_PLAYER_Z = (TILES_Z - 6) * TILE_SIZE : REM The distance along the z-axis : REM between the player and the : REM camera position [which is at : REM the back of the landscape, in : REM the middle] LAND_MID_HEIGHT = TILE_SIZE * 5 : REM The altitude of the mid-point of the : REM generated landscape, to which the height : REM of +/- 5 tiles is added by the Fourier : REM synthesis in GetLandscapeAltitude PLAYER_FRONT_Z = (TILES_Z - 5) * TILE_SIZE : REM The distance along the z-axis : REM between the tile in front of the : REM player and the camera, which is : REM where we spawn explosions and : REM falling rocks [set to six tiles : REM forwards from the camera] ROCK_HEIGHT = TILE_SIZE * 32 : REM The height from which we drop rocks from : REM the sky [32 tile sizes] LANDSCAPE_X_WIDTH = TILE_SIZE * (TILES_X - 2) : REM The width of the visible : REM landscape in x-coordinates, : REM counting whole tiles only, : REM and ignoring the centre tile LANDSCAPE_Z_DEPTH = TILE_SIZE * (TILES_Z - 1) : REM The depth of the visible : REM landscape in z-coordinates, : REM counting whole tiles only LANDSCAPE_X = LANDSCAPE_X_WIDTH / 2 : REM The x-coordinate of the landscape : REM offset, which is set to the whole : REM number of tiles that fit into the half : REM the width of the landscape, so we end : REM up looking at the middle point of the : REM landscape LANDSCAPE_Y = 0 : REM The y-coordinate of the landscape offset, : REM which is zero so the landscape remains at : REM the same height LANDSCAPE_Z = LANDSCAPE_Z_DEPTH + (10 * TILE_SIZE) : REM The z-coordinate of the : REM landscape offset, which is : REM set to push the landscape : REM away from the viewer by : REM ten tile sizes HALF_TILES_X = INT(TILES_X / 2) : REM Half the number of tiles from left to : REM right in the landscape LANDSCAPE_X_HALF = TILE_SIZE * HALF_TILES_X : REM The width of half the number : REM of tiles from left to right in : REM the landscape in x-coordinates LANDSCAPE_Z_BEYOND = LANDSCAPE_Z_DEPTH + TILE_SIZE : REM The depth of the visible : REM landscape in z-coordinates : REM plus one more tile LANDSCAPE_Z_FRONT = LANDSCAPE_Z - LANDSCAPE_Z_DEPTH : REM The z-coordinate of : REM the front of the : REM visible landscape LANDSCAPE_Z_MID = LANDSCAPE_Z - CAMERA_PLAYER_Z : REM The z-coordinate of the : REM mid-point of the landscape : REM depth [i.e. the player] REM ****************************************************************************** REM REM Operating system SWI numbers REM REM ****************************************************************************** OS_WriteC = &00 : REM The operating system call to write a : REM character to all the output streams OS_WriteS = &01 : REM The operating system call to write a : REM string to all the output streams OS_ReadC = &04 : REM The operating system call to read a key : REM press OS_Byte = &06 : REM General-purpose operating system calls OS_Word = &07 : REM General-purpose operating system calls OS_Mouse = &1C : REM The operating system call to read the : REM mouse status OS_BinaryToDecimal = &28 : REM The operating system call to convert a : REM number into a string REM ****************************************************************************** REM REM Workspace variables REM REM ****************************************************************************** stack = &000121FC : REM The stack is after the end of the main : REM game code and descends from this address : REM down to &00012000 [to give a stack size of : REM 512 32-bit entries] workspace = stack + 4 : REM The variable workspace starts just after : REM the top of the stack : REM R11 typically contains the address of the : REM workspace, so each of the variables in the : REM workspace can be referred to as; : REM : REM [R11, #offset] : REM : REM where #offset is one of the following : REM variables xObject = &00 : REM The x-coordinate of the object currently : REM being drawn yObject = &04 : REM The y-coordinate of the object currently : REM being drawn zObject = &08 : REM The z-coordinate of the object currently : REM being drawn : REM Offsets &0C to &2C appear to be unused rotationMatrix = &30 : REM The rotation matrix of the object : REM currently being drawn [i.e. the matrix : REM formed from the object's three orientation : REM vectors] xNoseV = &30 : REM The x-coordinate of the nose vector xRoofV = &34 : REM The x-coordinate of the roof vector xSideV = &38 : REM The x-coordinate of the side vector yNoseV = &3C : REM The y-coordinate of the nose vector yRoofV = &40 : REM The y-coordinate of the roof vector ySideV = &44 : REM The y-coordinate of the side vector zNoseV = &48 : REM The z-coordinate of the nose vector zRoofV = &4C : REM The z-coordinate of the roof vector zSideV = &50 : REM The z-coordinate of the side vector xVertex = &54 : REM The x-coordinate of the vertex being : REM processed, relative to the object's origin yVertex = &58 : REM The y-coordinate of the vertex being : REM processed, relative to the object's origin zVertex = &5C : REM The z-coordinate of the vertex being : REM processed, relative to the object's origin : REM Offset &60 appears to be unused xVertexRotated = &64 : REM The x-coordinate of the vertex after being : REM rotated by the object's rotation matrix yVertexRotated = &68 : REM The y-coordinate of the vertex after being : REM rotated by the object's rotation matrix zVertexRotated = &6C : REM The z-coordinate of the vertex after being : REM rotated by the object's rotation matrix : REM Offset &70 appears to be unused xCoord = &74 : REM The x-coordinate of the vertex being : REM processed, in the game's 3D coordinate : REM system yCoord = &78 : REM The y-coordinate of the vertex being : REM processed, in the game's 3D coordinate : REM system zCoord = &7C : REM The z-coordinate of the vertex being : REM processed, in the game's 3D coordinate : REM system : REM Offset &80 appears to be unused xObjectScaled = &84 : REM The x-coordinate of the vertex being : REM processed, scaled as high as possible yObjectScaled = &88 : REM The y-coordinate of the vertex being : REM processed, scaled as high as possible zObjectScaled = &8C : REM The z-coordinate of the vertex being : REM processed, scaled as high as possible : REM Offset &90 appears to be unused xPlayer = &94 : REM The x-coordinate of the player's ship yPlayer = &98 : REM The y-coordinate of the player's ship zPlayer = &9C : REM The z-coordinate of the player's ship xVelocity = &A0 : REM The player's velocity in the x-axis yVelocity = &A4 : REM The player's velocity in the y-axis zVelocity = &A8 : REM The player's velocity in the z-axis xExhaust = &AC : REM The x-coordinate of the ship's exhaust : REM vector, which points along the exhaust : REM plume yExhaust = &B0 : REM The y-coordinate of the ship's exhaust : REM vector, which points along the exhaust : REM plume zExhaust = &B4 : REM The z-coordinate of the ship's exhaust : REM vector, which points along the exhaust : REM plume : REM Offsets &B8 to &C0 appear to be unused previousColumn = &C4 : REM The corner coordinates of the previous : REM column while working along a row from left : REM to right tileCornerRow = &E4 : REM The number of the landscape tile corner : REM row that is currently being processed : REM Offset &E8 appears to be unused unusedConfig = &F0 : REM Storage for the first configuration value : REM in the landscapeConfig table, which is : REM stored but not used objectType = &F4 : REM The type of the object currently being : REM drawn tileRowOddEven = &F8 : REM A counter that flips every time we process : REM a tile corner row when drawing the : REM landscape, so we can trigger actions : REM accordingly : REM Offset &FC appears to be unused altitude = &100 : REM The altitude of the landscape at the : REM most recently calculated coordinate prevAltitude = &104 : REM The altitude of the landscape at the : REM previously calculated coordinate particleEnd = &108 : REM The address of the end of the particle : REM data in the particle data buffer particleCount = &10C : REM The number of particles currently : REM on-screen objectData = &110 : REM The address of the blueprint for the : REM object currently being drawn objectFlags = &114 : REM The flags of the object currently being : REM drawn mainLoopCount = &118 : REM The main loop counter crashLoopCount = &11C : REM The loop counter for the crash animation crashedFlag = &120 : REM If this is non-zero then the object being : REM processed has crashed into the ground currentScore = &124 : REM Our current score, which is displayed at : REM the left end of the score bar : REM : REM It is initialised to initialScore at the : REM start of each new game : REM : REM It goes down by one each time we fire a : REM bullet, and goes up by 20 for each object : REM we destroy fuelLevel = &128 : REM The player's fuel level gravity = &12C : REM The current setting of gravity [which : REM changes on higher levels] playingGame = &130 : REM A flag to determine whether the game is : REM being player, or whether this is the crash : REM animation; : REM : REM * 0 = this is the crash animation : REM : REM * -1 = game is being played remainingLives = &134 : REM The number of remaining lives, which is : REM displayed towards the right end of the : REM score bar, just before the high score : REM : REM It is initialised to 3 at the start of : REM each new game highScore = &138 : REM The high score, which is displayed at the : REM right end of the score bar : REM : REM It is initialised to initialHighScore when : REM the game is loaded xCamera = &13C : REM The 3D x-coordinate of the camera position : REM [though note that the camera position is : REM actually at the back of the on-screen : REM landscape, not the front] yCamera = &140 : REM The 3D y-coordinate of the camera position : REM [though note that the camera position is : REM actually at the back of the on-screen : REM landscape, not the front] zCamera = &144 : REM The 3D z-coordinate of the camera position : REM [though note that the camera position is : REM actually at the back of the on-screen : REM landscape, not the front, so the camera's : REM z-coordinate is larger than it would be : REM for a more traditional camera position; : REM it is more like the camera's focal point : REM than position, in a sense] xCameraTile = &148 : REM The 3D x-coordinate of the camera, clipped : REM to the nearest tile yCameraTile = &14C : REM The 3D y-coordinate of the camera, clipped : REM to the nearest tile zCameraTile = &150 : REM The 3D z-coordinate of the camera, clipped : REM to the nearest tile shipDirection = &154 : REM The direction in which the player's ship : REM faces, which is angle b in the rotation : REM matrix, and which is determined by the : REM angle of the mouse from the centre point shipPitch = &158 : REM The pitch of the player's ship, which is : REM angle a in the rotation matrix, and which : REM is determined by the distance of the mouse : REM from the centre point fuelBurnRate = &15C : REM The current fuel burn rate [bit 0 is : REM ignored] xLandscapeRow = &160 : REM The x-coordinate of the far-left corner : REM of the landscape row that we are drawing, : REM where rows run from left to right across : REM the screen [this is a relative coordinate] yLandscapeRow = &164 : REM The y-coordinate of the far-left corner : REM of the landscape row that we are drawing, : REM where rows run from left to right across : REM the screen [this is a relative coordinate] zLandscapeRow = &168 : REM The z-coordinate of the far-left corner : REM of the landscape row that we are drawing, : REM where rows run from left to right across : REM the screen [this is a relative coordinate] : REM Offset &16C appears to be unused xLandscapeCol = &170 : REM The x-coordinate of the far-left corner : REM of the column [i.e. tile] that we are : REM drawing in the current landscape row, : REM where columns run in and out of the screen : REM [this is a relative coordinate] yLandscapeCol = &174 : REM The y-coordinate of the far-left corner : REM of the column [i.e. tile] that we are : REM drawing in the current landscape row, : REM where columns run in and out of the screen : REM [this is a relative coordinate] zLandscapeCol = &178 : REM The z-coordinate of the far-left corner : REM of the column [i.e. tile] that we are : REM drawing in the current landscape row, : REM where columns run in and out of the screen : REM [this is a relative coordinate] : REM Offsets &17C to &1FC appear to be unused stringBuffer = &200 : REM A string buffer that's used when printing : REM the scores cornerStore1 = &400 : REM Storage for coordinates of tile corners as : REM we work our way through the landscape cornerStore2 = &500 + STORE : REM Storage for coordinates of tile corners as : REM we work our way through the landscape vertexProjected = &600 + STORE * 2 : REM Storage for projected vertices particleData = &700 + STORE * 2 : REM The particle data buffer, which stores : REM eight data bytes for each on-screen : REM particle objectMap = &4400 + STORE * 2 : REM The object map determines which objects : REM appear on the landscape, where objects are : REM trees, buildings, rockets and so on [size : REM of object map is 256 * 256 bytes = &10000] buffers = &14400 + STORE * 2 : REM The graphics buffers, one for each tile : REM corner row [so there are TILE_Z of them] REM ****************************************************************************** REM REM LANDER MAIN GAME CODE REM REM Produces the binary file GameCode. REM REM ****************************************************************************** DIM CODE% &A000 + STORE * 2 : REM Reserve a block in memory for the : REM assembled code FOR pass% = 4 TO 6 STEP 2 : REM Perform a two-pass assembly, using both : REM P% and O%, with errors enabled on the : REM second pass only O% = CODE% : REM Assemble the code for deployment to : REM address O% P% = CODE : REM Assemble the code into the block at P% [ \ Switch from BASIC into assembly language OPT pass% \ Set the assembly option for this pass \ ****************************************************************************** \ \ Name; landscapeOffset \ Type; Variable \ Category; Landscape \ Summary; The offset we apply to the on-screen landscape to push it away \ from us and to the left, so the visible tiles fit nicely on-screen \ \ ****************************************************************************** .landscapeOffset EQUD -LANDSCAPE_X EQUD LANDSCAPE_Y EQUD LANDSCAPE_Z \ ****************************************************************************** \ \ Name; landscapeOffsetAddr \ Type; Variable \ Category; Landscape \ Summary; The address of the landscape offset \ \ ****************************************************************************** .landscapeOffsetAddr EQUD landscapeOffset \ ****************************************************************************** \ \ Name; landscapeConfig \ Type; Variable \ Category; Landscape \ Summary; The configuration data for each tile row in the landscape \ \ ------------------------------------------------------------------------------ \ \ This table contains configuration data for the tile rows that make up the \ landscape. \ \ The first byte is read but is never used. \ \ The second byte is the number of points [i.e. corners] in each tile row. This \ is the same for all tile rows, so while this table would allow us to tailor \ the number of tiles plotted on each row, perhaps to make them taper off into \ the distance, this isn't actually done. \ \ ****************************************************************************** .landscapeConfig \ We need a configuration for each tile \ corner row EQUB &3A : EQUB TILES_X \ Tile row #0 ] FOR I% = 1 TO (TILES_Z - 1) / 2 [ OPT pass% EQUB &E3 : EQUB TILES_X \ Tile row data [even] EQUB &E4 : EQUB TILES_X \ Tile row data [odd] ] NEXT [ OPT pass% EQUB &E3 : EQUB TILES_X \ Tile row #TILES_Z OPT FN_AlignWithZeroes \ ****************************************************************************** \ \ Name; landscapeConfigAddr \ Type; Variable \ Category; Landscape \ Summary; The address of the landscapeConfig table \ \ ****************************************************************************** .landscapeConfigAddr EQUD landscapeConfig \ ****************************************************************************** \ \ Name; graphicsBuffers \ Type; Variable \ Category; Graphics buffers \ Summary; The addresses of each of the graphics buffers [these values do not \ change] \ Deep dive; Depth-sorting with the graphics buffers \ \ ****************************************************************************** .graphicsBuffers \ We need a graphics buffer for each tile \ corner row, numbered 0 to 10 [and the game \ also includes an extra buffer that is \ unused] ] buffer = workspace + buffers : REM The graphics buffers live at the address : REM given in the buffersAddr variable FOR I% = 1 TO TILES_Z + 1 : REM Add a buffer for each corner row [plus 1] [ OPT pass% EQUD buffer \ Insert the address of the buffer ] buffer = buffer + BUFFER_SIZE : REM Move on to the next buffer NEXT : REM Repeat until we have inserted addresses of : REM all the graphics buffers [ OPT pass% \ ****************************************************************************** \ \ Name; graphicsBuffersEnd \ Type; Variable \ Category; Graphics buffers \ Summary; The end addresses of each of the graphics buffers [these values \ get updated as objects are drawn into the buffers] \ Deep dive; Depth-sorting with the graphics buffers \ \ ****************************************************************************** .graphicsBuffersEnd \ We need a graphics buffer for each tile \ corner row, numbered 0 to 10 [and the game \ also includes an extra buffer that is \ unused] ] buffer = workspace + buffers : REM The graphics buffers live at the address : REM given in the buffersAddr variable FOR I% = 1 TO TILES_Z + 1 : REM Add a buffer for each corner row [plus 1] [ OPT pass% EQUD buffer \ Insert the address of the buffer ] buffer = buffer + BUFFER_SIZE : REM Move on to the next buffer NEXT : REM Repeat until we have inserted addresses of : REM all the graphics buffers [ OPT pass% \ ****************************************************************************** \ \ Name; DrawLandscapeAndBuffers [Part 1 of 4] \ Type; Subroutine \ Category; Landscape \ Summary; Draw the landscape and the contents of the graphics buffers, from \ the back of the screen to the front \ Deep dive; Drawing the landscape \ Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ Both the landscape and objects are drawn one tile row at a time, working from \ the back to the front. In each iteration, we process a horizontal row of tile \ corners, working from left to right and back to front. For each corner we draw \ a landscape tile where possible [i.e. when we have already processed the other \ three corners in that tile, so we don't start drawing until we reach the \ second corner on the second row]. \ \ Objects are drawn after the tiles on which they sit. This is achieved by \ staggering the drawing of objects so they are drawn two rows later than the \ landscape, so we draw an object two iterations after we have finished drawing \ all four of its surrounding tiles. This ensures that the landscape always \ appears behind the objects that sit on it. \ \ More specifically, we do the following; \ \ * Part 1; Start by setting up all the variables \ \ * We now step through each row of tile corners, working through the tile \ corners from left to right, one row at a time, keeping track of the row \ number in tileCornerRow = 0 to 10 \ \ For each row of tile corners, we do the following; \ \ * Part 2; Work along the current row of tile corners, from left to right, \ one corner coordinate at a time, and draw each tile as a \ quadrilateral once we have four valid corner coordinates from \ the previous row and previous column \ \ * Part 3; If tileCornerRow >= 2, also draw the contents of the graphics \ buffer with number tileCornerRow - 2 \ \ * Part 4; Finish by drawing the objects in graphics buffer 9 and graphics \ buffer 10 \ \ So we draw the objects in graphics buffer 0 just after we process tile corner \ row 2, so that's just after we draw the tiles between corner rows 1 and 2. \ \ Note that the game allocates memory to an extra graphics buffer, but only \ buffers numbers 0 to TILE_Z are used. \ \ ****************************************************************************** .DrawLandscapeAndBuffers STMFD R13!, {R5-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R3, [R11, #zCamera] \ Set R3 to the z-coordinate of the camera, \ which is the 3D coordinate in the game \ world at the middle-back of the on-screen \ view AND R9, R3, #&FF000000 \ Set R9 to the z-coordinate of the tile \ containing the camera, as this rounds the \ coordinate down to the nearest tile SUB R3, R3, R9 \ Set R3 = R3 - R9 \ = zCamera - tile containing zCamera \ \ So R3 contains the fractional element of \ zCamera in terms of whole tiles STR R9, [R11, #zCameraTile] \ Set zCameraTile to the z-coordinate of the \ tile containing the camera [though this \ isn't actually used - the value of R9 is \ used below, though] LDR R4, [R11, #xCamera] \ Set R4 to the x-coordinate of the camera, \ which is the 3D coordinate in the game \ world at the middle-back of the on-screen \ view AND R8, R4, #&FF000000 \ Set R8 to the x-coordinate of the tile \ containing the camera, as this rounds the \ coordinate down to the nearest tile SUB R4, R4, R8 \ Set R4 = R4 - R8 \ = xCamera - tile containing xCamera \ \ So R4 contains the fractional element of \ xCamera in terms of whole tiles STR R8, [R11, #xCameraTile] \ Set xCameraTile to the x-coordinate of the \ tile containing the camera ADD R5, R11, #xLandscapeRow \ Set R5 to the address of xLandscapeRow LDR R0, landscapeOffsetAddr \ Fetch the landscape offset vector into LDMFD R0, {R0-R2} \ [R0, R1, R2], which defines the offset \ that we apply to the on-screen landscape \ to move it away from the viewer so it \ fits nicely on-screen SUB R0, R0, R4 \ Subtract the fractional element of xCamera \ from the landscape offset x-coordinate in \ R0 SUB R2, R2, R3 \ Subtract the fractional element of zCamera \ from the landscape offset z-coordinate in \ R2 STMIA R5, {R0-R2} \ [R0, R1, R2] now contains the coordinate \ of the far-left corner of the back row of \ the landscape, which is the first corner \ row that we will process, so store it in \ [xLandscapeRow, yLandscapeRow, \ zLandscapeRow] MOV R0, #0 \ Set tileCornerRow = 0 to use as the STRB R0, [R11, #tileCornerRow] \ number of the tile corner row we are \ currently processing, working through the \ corner rows from row 0 [at the back] to \ row 10 [at the front] MOV R6, #0 \ Set R6 = 0 to use as the address of the \ previous row's coordinates, which we set \ to zero as we don't have a previous row \ yet ADD R7, R11, #cornerStore1 \ Set R7 to the address of cornerStore1 STRB R6, [R11, #tileRowOddEven] \ Set tileRowOddEven = 0, which we will flip \ between 0 and 1 for each tile corner row \ that we process \ So we now have the following variables set \ up, ready for the iteration through each \ row of tile corners, stepping from left to \ right, column by column; \ \ * R6 = 0 \ \ R6 always points to the set of corner \ pixel coordinates from the previous \ tile corner row, so it starts out as \ zero as there is no previous row at \ this point \ \ * R7 = address of cornerStore1 \ \ R7 always points to the place where we \ store the pixel coordinates for the \ current row corner as we work our way \ along the row it [we store these \ coordinates so we can draw the tiles \ when we're on the next corner row in \ the next iteration] \ \ * R9 = the z-coordinate of the tile \ containing the camera \ \ So [x, R9] is the coordinate of each \ corner on the corner row we're working \ along, starting from the far-left \ corner [as the camera position is \ actually at the back of the on-screen \ landscape] \ \ * zLandscapeRow = the z-coordinate of \ the back corner row of the landscape \ \ So zLandscapeRow keeps track of the \ z-coordinate of the tile corner row \ that we are processing, decreasing as \ we move towards the front of the tile \ landscape \ \ * tileRowOddEven = 0 \ \ tileRowOddEven flips between 0 and 1 \ for each tile corner row, so we can \ set the correct values of R6 and R7 at \ the end of each iteration \ \ * tileCornerRow = 0 \ \ tileCornerRow contains the number of \ the tile corner row that we are \ currently processing, which increments \ on each iteration as we move forwards \ \ * xCameraTile is the x-coordinate of the \ tile containing the camera \ \ This is the same as the x-coordinate of \ the middle of each tile corner row, \ rounded down to the nearest tile \ \ We now fall through into part 2 \ ****************************************************************************** \ \ Name; DrawLandscapeAndBuffers [Part 2 of 4] \ Type; Subroutine \ Category; Landscape \ Summary; Draw a row of landscape tiles \ Deep dive; Drawing the landscape \ Depth-sorting with the graphics buffers \ \ ****************************************************************************** \ We're now ready to iterate through the \ landscape, from left to right and back to \ front, looping back to land1 after \ processing each corner row .land1 LDRB R1, [R11, #tileCornerRow] \ Set R1 to the number of the tile corner \ row we are currently processing LDR R0, landscapeConfigAddr \ Set R0 to the address of the pair of ADD R0, R0, R1, LSL #1 \ bytes in the landscapeConfig table for the \ tile row number in R1 LDRB R10, [R0, #1] \ Set R10 to the second byte from the pair \ in landscapeConfig, which is the number \ of tile corners in the row \ \ We now use R10 as a loop counter when \ working our way along the row, counting \ tile corners as we progress LDRB R0, [R0] \ Set unusedConfig to the first byte from STRB R0, [R11, #unusedConfig] \ the pair in landscapeConfig, though this \ value is not used, so this has no effect ADD R0, R11, #xLandscapeRow \ Fetch the coordinate from [xLandscapeRow, LDMIA R0, {R0-R2} \ yLandscapeRow, zLandscapeRow] into \ [R0, R1, R2], so they contain the \ coordinates of the left end of the corner \ row that we need to process ADD R4, R11, #xLandscapeCol \ Store the coordinates in [xLandscapeCol, STMIA R4, {R0-R2} \ yLandscapeCol, zLandscapeCol] so we can \ start processing from the left end of this \ tile corner row, using these coordinates \ as we work through the columns, updating \ yLandscapeCol with the landscape height as \ we go LDR R8, [R11, #xCameraTile] \ Set R8 = xCameraTile - LANDSCAPE_X, so SUB R8, R8, #LANDSCAPE_X \ R8 is now the coordinate of the tile \ corner at the left end of the corner row, \ as subtracting the offset of the landscape \ effectively moves the camera to the left \ by that amount MOV R0, #&80000000 \ Set previousColumn = &80000000 to denote STR R0, [R11, #previousColumn] \ that we don't have corner coordinates from \ the previous column yet [as we are \ starting a new row] \ We now do the following loop R10 times, \ once for each of the tile corners in the \ row, looping back to land2 as we move \ right through the columns .land2 BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates [x, z] = [R8, R9], which is \ the current corner on this row LDR R14, [R11, #yCamera] \ Set yLandscapeCol = R0 - yCamera SUB R0, R0, R14 \ = altitude - yCamera STR R0, [R11, #yLandscapeCol] \ \ So this stores the y-coordinate of the \ landscape at this point, as we're seeing \ it from the point of view of the camera \ [so this will move it down the screen if \ the player is flying high, for example] ADD R0, R11, #xLandscapeCol \ Set R0 to the address of xLandscapeCol BL ProjectVertexOntoScreen \ Project [xLandscapeCol, yLandscapeCol, \ zLandscapeCol] onto the screen, returning \ the results in [R0, R1], so this projects \ the tile corner as it sits on the \ landscape \ \ This also sets the C flag if the vertex is \ too far away to draw BCS land4 \ If the vertex is too far away to draw then \ jump to land4 to skip the following STMIA R7!, {R0-R1} \ Store the corner coordinates in [R0, R1] \ in R7, updating R7 as we go, as R7 is \ where we store the tile corner pixel \ coordinates as we go [so this stores the \ corner coordinates in either cornerStore1 \ or cornerStore2, so we can fetch them when \ we process the next tile corner row] CMP R6, #0 \ If R6 = 0 then this is either the very BEQ land4 \ first tile corner row, or we have already \ fetched all the data for the previous row \ from the storage at R6 \ \ In either case, jump to land4 to skip \ drawing this tile as we can't draw tiles \ without the corresponding corner \ coordinates from the previous row and \ previous column LDMIA R6!, {R2-R3} \ Load the corner pixel coordinates from R6 \ into [R2, R3], updating R6 as we go, so \ this fetches the corresponding corner \ pixel coordinates from the previous tile \ corner row [i.e. from the opposite corner \ store to the one where we just stored this \ row's coordinate] CMP R2, #&80000000 \ If R2 = &80000000 then we have reached the MOVEQ R6, #0 \ end of the storage in R6, so set R6 = 0 BEQ land4 \ to prevent any more access attempts from \ the storage in R6, and jump to land4 to \ skip drawing this tile \ If we get here then we have the \ following pixel corner coordinates \ calculated; \ \ [R0, R1] = new corner from this row \ \ [R2, R3] = corresponding corner from \ previous row [i.e. the corner \ that's back by one row] STMFD R13!, {R6-R8} \ Store the registers that we want to use on \ the stack so they can be preserved ADD R14, R11, #previousColumn \ We now need to fetch the previous corners, LDMIA R14, {R4-R7} \ looking left along the tile row to the \ previous column of corners, so we can draw \ a tile from there to the new corner \ \ We will have stored these previous corners \ in the previousColumn table in the \ previous iteration, so fetch them into \ R4 to R7 like this; \ \ [R4, R5] = corner from the column to the \ left on this row \ \ [R6, R7] = corresponding corner from the \ previous row STMIA R14, {R0-R3} \ Store the new corners from this corner row \ in [R0, R1] and [R2, R3] in previousColumn \ so we can fetch them in the next iteration \ in the same way CMP R4, #&80000000 \ If R4 = &80000000 then this is actually BEQ land3 \ the first column of corner coordinates in \ this row, so we can't yet draw the tile, \ so jump to land3 to skip the drawing part \ If we get here then we actually have a \ tile to draw BL GetLandscapeTileColour \ Calculate the tile colour, depending on \ the slope, and return it in R8 BL DrawQuadrilateral \ Draw the tile by drawing a quadrilateral \ in colour R8 with corners at; \ \ [R0, R1] = the corner we're processing \ [R2, R3] = same corner in previous row \ [R4, R5] = previous column in this row \ [R6, R7] = previous row, previous column \ \ So this draws the tile we want on-screen .land3 LDMFD R13!, {R6-R8} \ Retrieve the registers that we stored on \ the stack .land4 SUBS R10, R10, #1 \ Decrement the loop counter in R10, which \ counts the number of tile corners in this \ row LDRNE R0, [R11, #xLandscapeCol] \ If we haven't yet processed all the tile ADDNE R0, R0, #TILE_SIZE \ corners in this row, add a tile's width to STRNE R0, [R11, #xLandscapeCol] \ xLandscapeCol and R8 to move them along ADDNE R8, R8, #TILE_SIZE \ the row to the next tile corner, and jump BNE land2 \ back to land2 to process the next corner \ By this point have drawn all the tiles in \ this row, so we now move on to the objects \ in the graphics buffers \ ****************************************************************************** \ \ Name; DrawLandscapeAndBuffers [Part 3 of 4] \ Type; Subroutine \ Category; Landscape \ Summary; Draw the objects in the graphics buffers for two rows behind the \ current corner row \ Deep dive; Drawing the landscape \ Depth-sorting with the graphics buffers \ \ ****************************************************************************** LDRB R0, [R11, #tileCornerRow] \ Set R0 to the number of the tile corner \ row we are currently processing SUBS R0, R0, #2 \ Subtract 2 from the corner row to get the \ number of the row that's two tile rows \ behind the current row BLPL DrawGraphicsBuffer \ If this results in a valid graphics buffer \ number, i.e. one that is positive, draw \ the contents of the buffer LDRB R0, [R11, #tileCornerRow] \ Set R0 to the number of the tile corner \ row we are currently processing ADDS R0, R0, #1 \ Increment the tile row number in R0 to \ move forwards by one tile row CMP R0, #TILES_Z \ If R0 = TILES_Z then we just drew the last BEQ land5 \ tile row, so jump to land5 to finish off \ by drawing graphics buffers 9 and 10 STRB R0, [R11, #tileCornerRow] \ Otherwise store the updated tile corner \ row number in tileCornerRow, ready for the \ next corner row LDR R0, [R11, #zLandscapeRow] \ Subtract a tile's width from zLandscapeRow SUB R0, R0, #TILE_SIZE \ to move the coordinates of the current row STR R0, [R11, #zLandscapeRow] \ forward by one whole tile SUB R9, R9, #TILE_SIZE \ Subtract a tile's width from R9 to step \ along the z-axis by one whole tile, going \ from back to front MOV R0, #&80000000 \ Store &80000000 in the address in R7 to STR R0, [R7] \ reset the store, so it's ready to be used \ to store the new row's pixel corner \ coordinates LDRB R0, [R11, #tileRowOddEven] \ Flip the value of tileRowOddEven between EORS R0, R0, #1 \ 0 and 1, setting the flags to feed into STRB R0, [R11, #tileRowOddEven] \ the following logic ADDNE R6, R11, #cornerStore1 \ Do the following after drawing the first ADDEQ R6, R11, #cornerStore2 \ tile row and then every other row; ADDEQ R7, R11, #cornerStore1 \ ADDNE R7, R11, #cornerStore2 \ * R6 = address of cornerStore1 \ * R7 = address of cornerStore2 \ \ Or do the following after drawing the \ second tile row and then every other row; \ \ * R6 = address of cornerStore2 \ * R7 = address of cornerStore1 \ \ So R6 and R7 swap over after each row, so \ R6 always points to the set of pixel \ corner coordinates from the previous row, \ and R7 points to the place where we store \ this row's pixel corner coordinates B land1 \ Loop back to land1 to process the next row \ of tile corners \ ****************************************************************************** \ \ Name; DrawLandscapeAndBuffers [Part 4 of 4] \ Type; Subroutine \ Category; Landscape \ Summary; Draw the remaining graphics buffers \ Deep dive; Drawing the landscape \ Depth-sorting with the graphics buffers \ \ ****************************************************************************** .land5 MOV R0, #TILES_Z - 2 \ Draw the contents of the penultimate BL DrawGraphicsBuffer \ graphics buffer MOV R0, #TILES_Z - 1 \ Draw the contents of the last graphics BL DrawGraphicsBuffer \ buffer LDMFD R13!, {R5-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; GetLandscapeAltitude \ Type; Subroutine \ Category; Landscape \ Summary; Calculate the altitude of the landscape for a given coordinate \ Deep dive; Generating the landscape \ Drawing the landscape \ \ ------------------------------------------------------------------------------ \ \ This routine calculates the altitude of the landscape at a given coordinate. \ The altitude is inverse, with lower values indicating higher altitudes. The \ launchpad is at LAUNCHPAD_ALTITUDE, while the sea is at SEA_LEVEL. \ \ The altitude at landscape coordinate [x, z] is calculated as follows; \ \ LAND_MID_HEIGHT - [ 2*sin[x - 2z] + 2*sin[4x + 3z] + 2*sin[3z - 5x] \ + 2*sin[3x + 3z] + sin[5x + 11z] + sin[10x + 7z] \ ] / 256 \ \ Note that the object map is flat, like a paper map, so the x- and z-axes on \ the map correspond to the x- and z-axes in the three-dimensional space when \ the map is laid out on the landscape [as the 3D z-axis goes into the screen]. \ When talking about the map, we are talking about [x, z] coordinates that are \ a bit like longitude and latitude, and this routine returns the y-coordinate \ of the point on the landscape [as the y-axis goes down the screen and \ determines the altitude]. \ \ Note that more negative values denote lower altitudes, as the y-axis goes down \ the screen, working down from zero at the very highest altitude in space. This \ is the opposite to conventional altitudes in the real world. \ \ The altitude is capped to a maximum value of SEA_LEVEL, and the altitude on \ the launchpad is set to LAUNCHPAD_ALTITUDE. The launchpad is defined as [x, z] \ where 0 <= x < LAUNCHPAD_SIZE and 0 <= z < LAUNCHPAD_SIZE, so that's tiles 0 \ to 7 along each axis [with the origin being at the front-left corner of the \ launchpad]. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R8 The x-coordinate of the landscape coordinate \ \ R9 The z-coordinate of the landscape coordinate \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R0 The altitude [y-coordinate] of the landscape \ at these coordinates, with more negative \ values denoting lower altitudes, as the \ y-axis points downwards \ \ ****************************************************************************** .GetLandscapeAltitude LDR R0, [R11, #altitude] \ Set prevAltitude = altitude, so we store STR R0, [R11, #prevAltitude] \ the altitude from the previous calculation \ In the following commentary, we will refer \ to the coordinates in R8 and R9 as x and z SUB R0, R8, R9, LSL #1 \ Set R0 = R8 - R9 << 1 \ = x - 2z LDR R2, sinTableAddr \ Set R2 to the address of the sine lookup \ table BIC R0, R0, #&00300000 \ Clear bits 21 and 22 of R0 so when we \ shift R0 to the right by 20 places in the \ following lookup, the address is aligned \ to the words in the lookup table LDR R0, [R2, R0, LSR #20] \ Set \ \ R0 = [2^31 - 1] \ * SIN[2 * PI * [[R0 >> 20] / 1024]] \ \ So R0 = sin[R0] \ = sin[x - 2z] MOV R0, R0, ASR #7 \ Set R0 = R0 >> 7 \ = sin[x - 2z] / 128 ADD R1, R9, R8, LSL #1 \ R1 = R9 + R8 << 1 \ = 2x + z ADD R1, R9, R1, LSL #1 \ R1 = R9 + R1 << 1 \ = z + [2x + z] << 1 \ = z + 4x + 2z \ = 4x + 3z ADD R3, R1, R8 \ R3 = R1 + R8 \ = 4x + 3z + x \ = 5x + 3z \ So the above gives us; \ \ R0 = sin[x - 2z] / 128 \ R1 = 4x + 3z \ R3 = 5x + 3z LDR R2, sinTableAddr \ Using the same approach as above, set; BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin[R1] \ = sin[4x + 3z] ADD R0, R0, R1, ASR #7 \ R0 = R0 + R1 >> 7 \ = [sin[x - 2z] + sin[4x + 3z]] / 128 SUB R1, R9, R8, LSL #1 \ R1 = R9 - R8 << 1 \ = z - 2x RSB R1, R8, R1, LSL #1 \ R1 = [R1 << 1] - R8 \ = [z - 2x] << 1 - x \ = 2z - 4x - x \ = 2z - 5x ADD R1, R1, R9 \ R1 = R1 + R9 \ = 2z - 5x + z \ = 3z - 5x \ So the above gives us; \ \ R0 = [sin[x - 2z] + sin[4x + 3z]] / 128 \ R1 = 3z - 5x LDR R2, sinTableAddr \ Using the same approach as above, set; BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin[R1] \ = sin[3z - 5x] ADD R0, R0, R1, ASR #7 \ R0 = R0 + R1 >> 7 \ = [sin[x - 2z] + sin[4x + 3z] \ + sin[3z - 5x]] / 128 ADD R1, R9, R8, LSL #1 \ R1 = R9 + R8 << 1 \ = z + 2x \ = 2x + z ADD R1, R9, R1, LSL #2 \ R1 = R9 + R1 << 2 \ = z + [2x + z] << 2 \ = z + 4x + 2z \ = 4x + 3z SUB R1, R1, R8 \ R1 = R1 - R8 \ = 4x + 3z - x \ = 3x + 3z \ So the above gives us; \ \ R0 = [sin[x - 2z] + sin[4x + 3z] \ + sin[3z - 5x]] / 128 \ R1 = 3x + 3z LDR R2, sinTableAddr \ Using the same approach as above, set; BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin[R1] \ = sin[3x + 3z] ADD R0, R0, R1, ASR #7 \ R0 = R0 + R1 >> 7 \ = [sin[x - 2z] + sin[4x + 3z] \ + sin[3z - 5x] \ + sin[3x + 3z]] / 128 ADD R1, R3, R9, LSL #3 \ R1 = R3 + R9 << 3 \ = 5x + 3z + 8z \ = 5x + 11z \ So the above gives us; \ \ R0 = [sin[x - 2z] + sin[4x + 3z] \ + sin[3z - 5x] \ + sin[3x + 3z]] / 128 \ R1 = 5x + 11z LDR R2, sinTableAddr \ Using the same approach as above, set; BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin[R1] \ = sin[5x + 11z] ADD R0, R0, R1, ASR #8 \ R0 = R0 + R1 >> 8 \ = [sin[x - 2z] + sin[4x + 3z] \ + sin[3z - 5x] \ + sin[3x + 3z]] / 128 \ + sin[5x + 11z] / 256 ADD R1, R9, R3, LSL #1 \ R1 = R9 + R3 << 1 \ = z + [5x + 3z] << 1 \ = z + 10x + 6z \ = 10x + 7z \ So the above gives us; \ \ R0 = [sin[x - 2z] + sin[4x + 3z] \ + sin[3z - 5x] \ + sin[3x + 3z]] / 128 \ + sin[5x + 11z] / 256 \ R1 = 10x + 7z LDR R2, sinTableAddr \ Using the same approach as above, set; BIC R1, R1, #&00300000 \ LDR R1, [R2, R1, LSR #20] \ R1 = sin[R1] \ = sin[10x + 7z] ADD R0, R0, R1, ASR #8 \ R0 = R0 + R1 >> 8 \ = [sin[x - 2z] + sin[4x + 3z] \ + sin[3z - 5x] \ + sin[3x + 3z]] / 128 \ + \ [sin[5x + 11z] + sin[10x + 7z]] / 256 RSB R0, R0, #LAND_MID_HEIGHT \ R0 = LAND_MID_HEIGHT - R0 \ \ = LAND_MID_HEIGHT - \ [2*sin[x - 2z] + 2*sin[4x + 3z] \ + 2*sin[3z - 5x] \ + 2*sin[3x + 3z] \ + sin[5x + 11z] \ + sin[10x + 7z]] / 256 \ \ which is the result that we want CMP R0, #SEA_LEVEL \ If R0 > SEA_LEVEL, set R0 = SEA_LEVEL so MOVGT R0, #SEA_LEVEL \ we don't create landscapes lower then sea \ level CMP R8, #LAUNCHPAD_SIZE \ If R8 and R9 are both < LAUNCHPAD_SIZE, CMPLO R9, #LAUNCHPAD_SIZE \ then this coordinate is on the launchpad, MOVLO R0, #LAUNCHPAD_ALTITUDE \ so set R0 = LAUNCHPAD_ALTITUDE STR R0, [R11, #altitude] \ Set altitude = R0, so we return the result \ in R0 and set the altitude variable MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; GetLandscapeBelowVertex \ Type; Subroutine \ Category; Landscape \ Summary; Calculate the landscape altitude directly below an object's vertex \ Deep dive; Generating the landscape \ Drawing 3D objects \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The address containing the object's vertex \ [x, y, z], relative to the camera position \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R0 The altitude [y-coordinate] of the landscape \ directly below the coordinate \ \ ****************************************************************************** .GetLandscapeBelowVertex STMFD R13!, {R8-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R8, [R0] \ Add xCamera to the x-coordinate in R8 to LDR R1, [R11, #xCamera] \ get the vertex position in the game's ADD R8, R8, R1 \ world coordinate system LDR R9, [R0, #8] \ Add zCamera to the z-coordinate in R8 to LDR R1, [R11, #zCamera] \ get the vertex position in the game's ADD R9, R9, R1 \ world coordinate system SUB R9, R9, #LANDSCAPE_Z \ Move the z-coordinate forward by the \ landscape offset, as the altitude \ calculation needs the coordinate to be \ relative to the front-centre point of the \ landscape \ The [x, z] coordinate in [R8, R9] is now \ relative to the game's coordinate system, \ rather than the camera or the landscape \ offset, which is what we need in order \ to calculate the altitude of the landscape \ at this point BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates [x, z] = [R8, R9], which is \ the point directly below the vertex LDR R14, [R11, #yCamera] \ Subtract yCamera from the altitude so the SUB R0, R0, R14 \ result is relative to the camera position LDMFD R13!, {R8-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; GetLandscapeTileColour \ Type; Subroutine \ Category; Landscape \ Summary; Calculate the colour of the landscape tile currently being drawn \ Deep dive; Drawing the landscape \ Screen memory in the Archimedes \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R8 The colour of the landscape tile \ \ ****************************************************************************** .GetLandscapeTileColour STMFD R13!, {R0-R4, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R3, [R11, #prevAltitude] \ Set R3 to the altitude of the previous \ point on the landscape LDR R4, [R11, #altitude] \ Set R4 to the altitude of the current \ point on the landscape SUBS R14, R3, R4 \ Set R14 to the slope of the tile, which we MOVMI R14, #0 \ get from the change of altitude from the \ previous point to this one, making sure \ the slope value is greater than zero; \ \ R14 = min[0, prevAltitude - altitude] \ \ The y-axis that measures altitude goes \ down the screen [counterintuitively], so \ if prevAltitude has a bigger value than \ altitude, this means the previous point \ is lower down than the current point \ \ As we draw the landscape from left to \ right, this means the slope value will be \ non-zero for tiles that face left, with a \ greater value for steeper slopes, while \ tiles that face right will have a slope \ value of zero \ We now calculate the colour, with the red, \ green and blue channels in R0, R1 and R2 \ respectively AND R2, R4, #&10 \ This instruction appears to have no \ effect, as we overwrite the result in the \ next instruction, but it looks like it's \ all that remains of an experiment to add \ blue to the landscape MOV R2, #0 \ Set the blue channel in R2 to zero, as we \ only use blue for the sea \ We now set the green and red channels \ depending on bits 2 and 3 of the altitude, \ for red and green respectively \ \ This makes the green channel change more \ slowly between neighbouring tiles, with \ the red channel changing more quickly, \ giving the overall effect of a gentle \ green landscape pockmarked with small \ groups of red-brown dirt AND R1, R4, #%00001000 \ Set the green channel in R1 to bit 3 of MOV R1, R1, LSR #1 \ the current point's altitude, as follows; ADD R1, R1, #4 \ \ R1 = [bit 3] * 4 + 4 \ \ So it's 4 if bit 3 is clear, or 8 if bit 3 \ is set AND R0, R4, #%00000100 \ Set the red channel in R0 to bit 2 of the \ current point's altitude CMP R4, #LAUNCHPAD_ALTITUDE \ If the current point is on the launchpad, MOVEQ R0, #4 \ set the colour to grey, i.e. red, green MOVEQ R1, #4 \ and blue all have the same value of 4 MOVEQ R2, #4 CMP R4, #SEA_LEVEL \ If both the previous and current points CMPEQ R3, #SEA_LEVEL \ are at sea level, set the colour to blue, MOVEQ R1, #0 \ i.e. the blue channel has value 4 while MOVEQ R2, #4 \ red and green are zero MOVEQ R0, #0 LDRB R8, [R11, #tileCornerRow] \ Set R8 to the number of the tile corner \ row that we're processing, so it goes from \ 1 at the back to TILES_Z - 1 at the front, \ or 1 to 10 [it doesn't start at zero as we \ don't draw any tiles for the very first \ row of tile corners, so we don't call this \ routine] ADD R3, R8, R14, LSR #22 \ Set the tile's brightness in R3 to; \ \ tileCornerRow + slope >> 22 \ \ where tileCornerRow is 1 at the back and \ TILES_Z - 1 at the front \ \ As we draw the landscape from back to \ front and left to right, this means that; \ \ * Tiles nearer the front will have a \ higher brightness level than those at \ the back [as tileCornerRow will be \ higher for closer tiles] \ \ * Tiles that face to the left will have \ a higher brightness level than those \ that face to the right, with steeper \ sloping tiles being brighter than \ shallow ones [as slope will be higher \ for steeper sloping tiles] \ \ * Tiles that face to the right will have \ fixed brightness levels that only vary \ with distance and not with slope [as \ slope is zero for tiles that face \ right] \ \ This implements a light source that is \ directly above and slightly to the left ADD R0, R0, R3 \ Add the brightness in R3 to all three ADD R1, R1, R3 \ channels ADD R2, R2, R3 CMP R0, #16 \ Ensure that the red channel in R0 fits MOVHS R0, #15 \ into four bits CMP R1, #16 \ Ensure that the green channel in R1 fits MOVHS R1, #15 \ into four bits CMP R2, #16 \ Ensure that the blue channel in R2 fits MOVHS R2, #15 \ into four bits \ We now build a VIDC colour number in R8 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form; \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R8 from \ the red, green and blue values in R0, R1 \ and R2 ORR R8, R1, R2 \ Set R8 to the bottom three bits of; AND R8, R8, #%00000011 \ ORR R8, R8, R0 \ [the bottom two bits of R1 OR R2] OR R0 AND R8, R8, #%00000111 \ \ So this sets bits 0, 1 and 2 of R8 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R8, R8, #%00010000 \ set bit 4 of R8 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R8, R8, R1, LSL #3 \ And stick them into bits 5-6 of R8 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R8, R8, #%00001000 \ set bit 3 of R8 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R8, R8, #%10000000 \ set bit 7 of R8 ORR R8, R8, R8, LSL #8 \ Duplicate the lower byte of R8 into the ORR R8, R8, R8, LSL #16 \ other three bytes in the word to produce ORR R8, R8, R8, LSL #24 \ a four-pixel colour word containing four \ pixels of this colour LDMFD R13!, {R0-R4, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; MoveAndDrawPlayer [Part 1 of 5] \ Type; Subroutine \ Category; Player \ Summary; Process player movement and draw the player's ship into the \ graphics buffers, starting with reading the mouse position \ Deep dive; Flying by mouse \ \ ****************************************************************************** .MoveAndDrawPlayer STMFD R13!, {R14} \ Store the return address on the stack \ We start by reading the position of the \ mouse and generating a rotation matrix for \ the player's ship, based on its new \ orientation SWI OS_Mouse \ Read the mouse coordinates, returning; \ \ R0 = x-coordinate \ \ R1 = y-coordinate \ \ R2 = mouse buttons [%lmr] STRB R2, [R11, #fuelBurnRate] \ Set the fuel burn rate to the mouse button \ result, so it's set to; \ \ * %000 [0] if no buttons are being \ pressed \ \ * %001 [1] if the right button is being \ pressed [i.e. fire bullets] \ \ * %010 [2] if the middle button is being \ pressed [i.e. hover] \ \ * %100 [4] if the left button is being \ pressed [i.e. full thrust] \ \ Bit 0 of the fuel rate is ignored in the \ fuel calculations, so firing bullets does \ not burn fuel, even though fuelBurnRate is \ set to a non-zero value LDR R14, [R11, #fuelLevel] \ If the fuel level is zero, also zero the CMP R14, #0 \ fuel burn rate, as we can't burn fuel that STREQB R14, [R11, #fuelBurnRate] \ we don't have \ At the start of each new life, the mouse \ is initialised to coordinates [511, 511] CMP R0, #1024 \ Cap the mouse x-coordinate in R0 so it's MOVHS R0, #1024 \ in the range 0 to 1023 SUBHS R0, R0, #1 SUB R0, R0, #512 \ Convert R0 into the range -512 to +511 RSB R1, R1, #1024 \ Set R1 = 1024 - R1 - 512 SUB R1, R1, #512 \ = 512 - R1 \ \ So the mouse y-coordinate in R1 is now in \ the range -512 to +512 MOV R0, R0, LSL #22 \ Scale both mouse coordinates up as far as MOV R1, R1, LSL #22 \ possible without losing data [512 << 22 is \ &80000000, so this is as high as we can \ go] BL GetMouseInPolarCoordinates \ Convert the mouse coordinates into polar \ coordinates, returning the polar angle in \ R1 and the polar distance in R0 CMP R0, #&40000000 \ Cap the polar distance in R0 so it's in MOVHS R0, #&40000000 \ the range 0 to &3FFFFFFF SUBHS R0, R0, #1 MOV R0, R0, LSL #1 \ Scale R0 to the range 0 to &7FFFFFFE LDR R2, [R11, #shipPitch] \ Set R2 = shipPitch LDR R3, [R11, #shipDirection] \ Set R3 = shipDirection SUBS R4, R3, R1 \ Set R4 = R3 - R1 \ = shipDirection - polar angle BMI ship1 \ If the result is negative, jump to ship1 \ so we cap R4 against negative values CMP R4, #&30000000 \ Cap the value in R4 to a maximum magnitude MOVHS R4, #&30000000 \ of &30000001 B ship2 \ Jump to ship2 to skip the following and \ keep going .ship1 CMN R4, #&30000000 \ Cap the value in R4 to a maximum magnitude MVNLO R4, #&30000000 \ of -&30000001 .ship2 SUBS R5, R2, R0 \ Set R5 = R2 - R0 \ = shipPitch - polar distance BLE ship3 \ If the result is zero or negative, jump to \ ship3 so we cap R5 against negative values CMP R5, #&30000000 \ Cap the value in R5 to a maximum magnitude MOVHS R5, #&30000000 \ of &30000001 B ship4 \ Jump to ship4 to skip the following and \ keep going .ship3 CMN R5, #&30000000 \ Cap the value in R5 to a maximum magnitude MVNLO R5, #&30000000 \ of -&30000001 .ship4 \ We now update the rotation angles with the \ latest mouse values [in the form of the \ distance and angle] \ \ We do this by adding half of the new value \ and half of the old value, which seems to \ apply some kind of damping to the controls SUB R0, R2, R5, ASR #1 \ Set R0 = R2 - R5 / 2 \ = shipPitch \ - [shipPitch - distance] / 2 SUB R1, R3, R4, ASR #1 \ Set R1 = R3 - R4 / 2 \ = shipDirection \ - [shipDirection - angle] / 2 STR R0, [R11, #shipPitch] \ Store the updated value in shipPitch STR R1, [R11, #shipDirection] \ Store the updated value in shipDirection BL CalculateRotationMatrix \ Calculate the rotation matrix from the \ updated angles given in R0 and R1, so \ this sets the rotation matrix for the \ player's ship \ Now that we have the player's rotation \ matrix, we can move on to the ship's \ movement in space \ ****************************************************************************** \ \ Name; MoveAndDrawPlayer [Part 2 of 5] \ Type; Subroutine \ Category; Player \ Summary; Update the player's velocity and coordinates \ Deep dive; Flying by mouse \ \ ****************************************************************************** ADD R14, R11, #xPlayer \ Set R0 to R5 as follows; LDMIA R14, {R0-R5} \ \ R0 = xPlayer \ R1 = yPlayer \ R2 = zPlayer \ \ R3 = xVelocity \ R4 = yVelocity \ R5 = zVelocity \ \ So [R0, R1, R2] is the player's coordinate \ and [R3, R4, R5] is the player's velocity \ vector \ \ We now update the velocity by adding in \ various factors such as friction, thrust \ and gravity CMN R1, #HIGHEST_ALTITUDE \ If R1 is higher than the altitude where LDRLTB R9, [R11, #fuelBurnRate] \ the engines cut out, clear bits 2 and 3 of BICLT R9, R9, #%00000110 \ the fuel burn rate to cut the engines STRLTB R9, [R11, #fuelBurnRate] LDR R6, [R11, #xRoofV] \ Set [R6, R7, R8] to the roof vector from LDR R7, [R11, #yRoofV] \ the rotation matrix, which is the vector LDR R8, [R11, #zRoofV] \ that points directly down through the \ ship's floor as the y-axis is inverted \ [so it's in the direction of thrust, as \ the thrusters are on the bottom of the \ ship] \ \ Let's refer to this thrust vector as \ follows; \ \ R6 = xExhaust \ R7 = yExhaust \ R8 = zExhaust LDRB R9, [R11, #fuelBurnRate] \ Set R9 to the fuel burn rate TST R9, #%00000100 \ Set the flags according to bit 2 of the \ fuel burn rate, which is set if the left \ button is being pressed [i.e. full thrust] \ We start by updating xVelocity in R3 SUB R3, R3, R3, ASR #6 \ Set R3 = R3 - R3 >> 6 \ = xVelocity - [xVelocity / 64] \ \ This reduces the velocity along the x-axis \ by 1/64, so this applies a deceleration in \ this direction [due to friction] SUBNE R3, R3, R6, ASR #11 \ Increase R3 by a further xExhaust / 2048 \ if full thrust is being pressed, so this \ applies the engine thrust \ \ We subtract the exhaust vector to apply \ thrust because the direction of the thrust \ applied by the plume is in the opposite \ direction to the plume [in other words, a \ rocket's plume pushes downwards so the \ rocket can rise up] ADD R0, R0, R3 \ Set R0 = R0 + R3 \ = xPlayer + R3 \ \ This applies the newly calculated velocity \ to the player's x-coordinate, which moves \ the player in 3D space \ Next we update yVelocity in R4 SUB R4, R4, R4, ASR #6 \ Set R4 = R4 - R4 >> 6 \ = yVelocity - [yVelocity / 64] \ \ This reduces the velocity along the y-axis \ by 1/64, so this applies a deceleration in \ this direction [due to friction] SUBNE R4, R4, R7, ASR #11 \ Increase R3 by a further yExhaust / 2048 \ if full thrust is being pressed, so this \ applies the engine thrust \ \ We subtract the exhaust vector to apply \ thrust because the direction of the thrust \ applied by the plume is in the opposite \ direction to the plume [in other words, a \ rocket's plume pushes downwards so the \ rocket can rise up] ADD R1, R1, R4 \ Set R1 = R1 + R4 \ = yPlayer + R4 \ \ This applies the newly calculated velocity \ to the player's y-coordinate, which moves \ the player in 3D space \ And finally we update zVelocity in R4 SUB R5, R5, R5, ASR #6 \ Set R5 = R5 - R5 >> 6 \ = zVelocity - [zVelocity / 64] \ \ This reduces the velocity along the z-axis \ by 1/64, so this applies a deceleration in \ this direction [due to friction] SUBNE R5, R5, R8, ASR #11 \ Increase R3 by a further zExhaust / 2048 \ if full thrust is being pressed, so this \ applies the engine thrust \ \ We subtract the exhaust vector to apply \ thrust because the direction of the thrust \ applied by the plume is in the opposite \ direction to the plume [in other words, a \ rocket's plume pushes downwards so the \ rocket can rise up] ADD R2, R2, R5 \ Set R2 = R2 + R5 \ = zPlayer + R5 \ \ This applies the newly calculated velocity \ to the player's z-coordinate, which moves \ the player in 3D space \ By this point we have updated the player's \ coordinates in [R0, R1, R2] by the new \ velocity in [R3, R4, R5] TST R9, #%00000010 \ Set the flags according to bit 1 of the \ fuel burn rate, which is set if the middle \ button is being pressed [i.e. hover] SUBNE R3, R3, R6, ASR #13 \ If the middle button is being pressed, for SUBNE R4, R4, R7, ASR #13 \ hovering mode, then apply a quarter of the SUBNE R5, R5, R8, ASR #13 \ full thrust vector to the velocity, rather \ than the full thrust vector we apply for \ the left button \ \ Note that the hover thrust is applied \ after we applied the velocity vector to \ the player's coordinates, so hovering has \ a slightly delayed impact on the ship, to \ simulate the effects of inertia LDR R9, [R11, #gravity] \ Add the effect of gravity to yVelocity in ADD R4, R4, R9 \ R4, adding it to R4 as it is a downwards \ force along the z-axis STMIA R14, {R0-R8} \ Store the updated player coordinates, ship \ velocity and thrust vector as follows; \ \ xPlayer = R0 \ yPlayer = R1 \ zPlayer = R2 \ \ xVelocity = R3 \ yVelocity = R4 \ zVelocity = R5 \ \ xExhaust = R6 \ yExhaust = R7 \ zExhaust = R8 \ \ We use these values in part 4 \ ****************************************************************************** \ \ Name; MoveAndDrawPlayer [Part 3 of 5] \ Type; Subroutine \ Category; Player \ Summary; Check for collisions and draw the ship \ Deep dive; Drawing 3D objects \ Collisions and bullets \ Flying by mouse \ \ ****************************************************************************** CMP R1, #0 \ If R1 [i.e. the y-coordinate of the ship] MOVPL R1, #0 \ is positive, then set it to zero \ \ As we store this value as the camera's \ y-coordinate below, this ensures that the \ camera doesn't drop down all the way with \ the ship in last few moments before it \ lands, and similarly it doesn't start \ rising up with the ship as it takes off \ until the ship is at a reasonable height ADD R2, R2, #CAMERA_PLAYER_Z \ Set R2 to the z-coordinate we want to use \ for the camera, which is at the back of \ the landscape ADD R14, R11, #xCamera \ Set [xCamera, yCamera, zCamera] to the STMIA R14, {R0-R2} \ coordinates in [R0, R1, R2], which is at \ the back of the landscape and in the \ middle \ \ This sets the camera so that it follows \ the player's ship as it flies around, \ and is positioned at the back of the \ visible screen MOV R8, R0 \ Set [R8, R9] to the point on the landscape MOV R9, R2 \ that's directly below the player, by SUB R9, R9, #CAMERA_PLAYER_Z \ setting [R8, R9] to the ship's [x, z] \ coordinates in [R0, R2] and subtracting \ the five tiles we added above BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates [x, z] = [R8, R9] SUB R0, R0, #UNDERCARRIAGE_Y \ Set R0 to the altitude of a point that's \ the height of the ship's undercarriage \ above the point on the landscape beneath \ the player, which is the lowest altitude \ the player can be without being in danger \ of hitting the ground with the bottom of \ the ship LDR R1, [R11, #yPlayer] \ Set R14 to the y-coordinate of the player \ in yPlayer SUB R14, R0, R1 \ If R0 - yPlayer >= SAFE_HEIGHT [1.5 tile CMP R14, #SAFE_HEIGHT \ sizes] then this means the bottom of the BHS ship6 \ player's ship is safely clear of objects \ on the ground, as SAFE_HEIGHT is the \ minimum safe height for avoiding objects \ on the ground, and the undercarriage of \ the player's ship is above this height \ \ So if this is the case, jump to ship6 to \ skip the following set of collision \ checks, as we know we are safe STMFD R13!, {R0} \ Store R0 on the stack so we can retrieve \ it below ADD R14, R11, #objectMap \ Set R14 to the address of the object map AND R9, R9, #&FF000000 \ Clip R9 down to the nearest tile ADD R14, R14, R8, LSR #24 \ Set R14 = R6 + [R8 >> 24] + [R9 >> 16] ADD R14, R14, R9, LSR #16 \ \ R8 is shifted into the bottom byte of R14, \ so that's the x-coordinate, and R9 is \ shifted into the second byte of R14, so \ that's the z-coordinate, so R14 points to \ the object map entry for coordinate \ [R8, R9] LDRB R0, [R14] \ Set R0 to the byte in the object map at \ the coordinate [R8, R9] CMP R0, #0 \ If the object map entry is zero, jump to BEQ ship5 \ ship5 to skip the following [though this \ shouldn't ever happen, as an object type \ of zero is never added to the object map] CMP R0, #12 \ If R0 < 12 then the object map entry is a ADDLO R13, R13, #4 \ valid object number in the range 1 to 11, BLO LoseLife \ so the player's ship just hit the object \ below it on the ground, so jump to \ LoseLife to show the explosion animation \ and lose a life .ship5 LDMFD R13!, {R0} \ Retrieve R0 from the stack, so it is once \ again the lowest altitude the player can \ be without being in danger of hitting the \ ground .ship6 \ If we get here then R0 is the lowest \ altitude the player can be without being \ in danger of hitting the ground, and R1 is \ the y-coordinate of the player, i.e. the \ player's altitude CMP R1, R0 \ If R1 > R0 then the player is lower down BLGT LandOnLaunchpad \ then the safe altitude, so call \ LandOnLaunchpad to check whether we have \ landed on the launchpad [as we need to be \ within the danger zone in order to land] CMP R1, #0 \ If R1 [i.e. the y-coordinate of the ship] MOVMI R1, #0 \ is negative, then set it to zero \ \ This is the converse of the test we did at \ the start of this part, as it sets R1 to \ zero only when the altitude of the player \ is higher than zero, so this only keeps \ R1 set to the player's y-coordinate when \ they are really close to the ground \ \ As we pass this value to DrawObject below, \ this means we draw the player's ship in \ the middle of the screen most of the time \ [by passing an R1 of zero to DrawObject], \ but when the ship is close to the ground, \ we draw the ship lower down the screen ADD R3, R11, #rotationMatrix \ Set R3 to the address of the ship's \ rotation matrix to pass to DrawObject LDR R14, objectPlayerAddr \ Set objectData to the object blueprint STR R14, [R11, #objectData] \ for the player's ship MOV R0, #0 \ Set the coordinates in [R0, R1, R2] so MOV R2, #LANDSCAPE_Z_MID \ the ship gets drawn in the middle of the \ screen or slightly below if the ship is \ near the ground [as R0 = 0 and R1 is set \ as described above], at a position into \ the screen of LANDSCAPE_Z_MID, which \ places it above the middle of the \ landscape \ \ This works because the landscape offset \ pushes the far edge of the landscape 20 \ tiles into the screen, and the landscape \ is ten tiles deep, so the centre is 15 \ tiles into the screen BL DrawObject \ Draw the player's ship with the correct \ orientation and position LDRB R10, [R11, #crashedFlag] \ DrawObject sets crashedFlag to -1 if the CMP R10, #0 \ ship is lower down than its shadow, to BLNE LoseLife \ indicate that it has crashed, so jump to \ LoseLife is this is the case \ ****************************************************************************** \ \ Name; MoveAndDrawPlayer [Part 4 of 5] \ Type; Subroutine \ Category; Player \ Summary; Spawn the particles in the exhaust plume if the engine is engaged \ Deep dive; Flying by mouse \ \ ****************************************************************************** LDRB R10, [R11, #fuelBurnRate] \ If both bits 1 and 2 of the fuel burn rate TST R10, #%00000110 \ are clear then the engine is not running, BEQ ship7 \ so jump to ship7 to skip generating an \ exhaust plume ADD R14, R11, #xVelocity \ Set R0 to R2 and R6 to R8 as follows by LDMIA R14, {R0-R2, R6-R8} \ fetching the values we stored in part 2; \ \ R0 = xVelocity \ R1 = yVelocity \ R2 = zVelocity \ \ R6 = xExhaust \ R7 = yExhaust \ R8 = zExhaust \ \ So [R0, R1, R2] is the player's velocity \ and [R6, R7, R8] is the player's thrust \ vector ADD R3, R0, R6, ASR #7 \ Set [R3, R4, R5] as follows; ADD R4, R1, R7, ASR #7 \ ADD R5, R2, R8, ASR #7 \ [ [xVelocity + xExhaust / 128] / 2 ] MOV R3, R3, ASR #1 \ [ [yVelocity + yExhaust / 128] / 2 ] MOV R4, R4, ASR #1 \ [ [zVelocity + zExhaust / 128] / 2 ] MOV R5, R5, ASR #1 \ \ So this sets [R3, R4, R5] to a vector in \ the direction of the player's velocity \ [so the particles move along with the \ ship] and in the direction of the exhaust \ plume [so that's heading away from the \ engine, in the direction that it's \ pointing], and we halve the result so they \ shoot out of the engine but soon get left \ behind as we blast away ADD R0, R11, #xPlayer \ Set R0 to R2 as follows; LDMIA R0, {R0-R2} \ \ R0 = xPlayer \ R1 = yPlayer \ R2 = zPlayer \ \ So [R0, R1, R2] is the player's coordinate SUB R0, R0, R3 \ Set [R0, R1, R2] as follows; SUB R1, R1, R4 \ SUB R2, R2, R5 \ [ xPlayer - R3 + [xExhaust / 128] ] ADD R0, R0, R6, ASR #7 \ [ yPlayer - R4 + [yExhaust / 128] ] ADD R1, R1, R7, ASR #7 \ [ zPlayer - R5 + [zExhaust / 128] ] ADD R2, R2, R8, ASR #7 \ \ So this sets [R0, R1, R2] to the position \ of the player's ship, but a little way in \ the direction of the exhaust plume [so \ that's below the engine in the direction \ that it's pointing], and we also subtract \ the velocity in [R3, R4, R5] because the \ first thing that happens when we process \ the particle in MoveAndDrawParticles is \ to add the velocity, so this cancels that \ out to ensure the particle starts out \ along the line of the exhaust plume \ By this stage we have; \ \ [R0, R1, R2] = particle coordinate \ \ [R3, R4, R5] = particle velocity \ \ We now set the values of R6 to R9 to pass \ to the AddExhaustParticleToBuffer routine MOV R7, #&001D0000 \ Set bits 16, 18, 19 and 20 of the particle \ flags, so that's; \ \ * Bit 16 set = colour fades white to red \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle MOV R6, #8 \ Set the particle's lifespan counter to 8 \ iterations of the main loop MOV R8, #10 \ Set the random element of the particle's \ velocity to the range +/- 2^[32 - 10], \ i.e. -&400000 to +&400000 MOV R9, #29 \ Set the random element of the particle's \ lifespan to the range 0 to 2^[32 - 29], \ i.e. 0 to 8 STMFD R13!, {R0-R9} \ Store R0 to R9 on the stack so we can \ fetch this set of values before each call \ to AddExhaustParticleToBuffer \ We now call the AddExhaustParticleToBuffer \ routine eight times if full thrust is \ engaged, or twice if hover mode is being \ used [so the exhaust plume is four times \ denser when full thrust is engaged] BL AddExhaustParticleToBuffer \ Call AddExhaustParticleToBuffer with the \ set of parameters in R0 to R9 to add the \ first exhaust plume particle to the \ particle data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the second particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the third particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the fourth particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the fifth particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the sixth particle to the particle \ data buffer TST R10, #%00000100 \ If bit 2 of R10 is set then full thrust is LDMNEIA R13, {R0-R9} \ engaged, so fetch the same parameters and BLNE AddExhaustParticleToBuffer \ add the seventh particle to the particle \ data buffer LDMFD R13!, {R0-R9} \ Fetch the same parameters and add the BL AddExhaustParticleToBuffer \ final particle to the particle data buffer \ ****************************************************************************** \ \ Name; MoveAndDrawPlayer [Part 5 of 5] \ Type; Subroutine \ Category; Player \ Summary; Spawn a bullet particle if the fire button is being pressed \ Deep dive; Collisions and bullets \ \ ****************************************************************************** .ship7 TST R10, #%00000001 \ If bit 0 of the fuel burn rate is clear BEQ ship8 \ then the fire button is not being pressed, \ so jump to ship8 to skip the bullet-firing \ process and return from the subroutine LDR R8, [R11, #currentScore] \ Decrement the current score by one, as we SUB R8, R8, #1 \ are about to fire a bullet STR R8, [R11, #currentScore] ADD R14, R11, #xPlayer \ Set R0 to R5 as follows; LDMIA R14, {R0-R5} \ \ R0 = xPlayer \ R1 = yPlayer \ R2 = zPlayer \ \ R3 = xVelocity \ R4 = yVelocity \ R5 = zVelocity \ \ So [R0, R1, R2] is the player's coordinate \ and [R3, R4, R5] is the player's velocity LDR R6, [R11, #xNoseV] \ Set [R6, R7, R8] to the nose vector from LDR R7, [R11, #yNoseV] \ the rotation matrix, which is the vector LDR R8, [R11, #zNoseV] \ that points out through the ship's nose, \ just like the ship's gun \ \ Let's refer to this gun vector as follows; \ \ R6 = xGun \ R7 = yGun \ R8 = zGun ADD R3, R3, R6, ASR #8 \ Set [R3, R4, R5] as follows; ADD R4, R4, R7, ASR #8 \ ADD R5, R5, R8, ASR #8 \ [ xVelocity + xGun / 256 ] \ [ yVelocity + yGun / 256 ] \ [ zVelocity + zGun / 256 ] \ \ So this sets [R3, R4, R5] to a vector in \ the direction of the player's velocity \ [so the bullet particles move along with \ the ship from which they are fired] and \ then in the direction that the gun is \ pointing [so they leave the barrel in the \ correct direction] SUB R0, R0, R3 \ Set [R0, R1, R2] as follows; SUB R1, R1, R4 \ SUB R2, R2, R5 \ [ xPlayer - R3 + [xGun / 128] ] ADD R0, R0, R6, ASR #7 \ [ yPlayer - R4 + [yGun / 128] ] ADD R1, R1, R7, ASR #7 \ [ zPlayer - R5 + [zGun / 128] ] ADD R2, R2, R8, ASR #7 \ \ So this sets [R0, R1, R2] to the position \ of the player's ship, but a little way in \ the direction of the gun [so the bullet \ fires out of the end of the gun], and we \ also subtract the velocity in [R3, R4, R5] \ because the first thing that happens when \ we process the particle in \ MoveAndDrawParticles is to add the \ velocity, so this cancels that out to \ ensure the particle starts out at the end \ of the gun MOV R6, #20 \ Set the bullet particle's lifespan \ counter to 20 iterations of the main loop MOV R7, #&01BC0000 \ Set bits 18, 19, 20, 21, 23 and 24 of the \ particle flags, so that's; \ \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size is big \ * Bit 24 set = explode on hitting ground ORR R7, R7, #&FF \ Set the particle colour to white in bits 0 \ to 7 of the particle flag BL AddBulletParticleToBuffer \ Add a bullet particle to the particle data \ buffer .ship8 LDMFD R13!, {PC} \ Return from the subroutine \ ****************************************************************************** \ \ Name; LandOnLaunchpad \ Type; Subroutine \ Category; Player \ Summary; Check to see if the player has landed on the launchpad \ Deep dive; Collisions and bullets \ \ ------------------------------------------------------------------------------ \ \ We call this routine from MoveAndDrawPlayer when the player's altitude matches \ that of the launchpad, so this performs additional checks to see if the player \ just landed. \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R1 The y-coordinate of the player's ship \ \ ****************************************************************************** .LandOnLaunchpad ADD R3, R11, #xPlayer \ Fetch the six words at xPlayer as follows; LDMIA R3, {R0-R5} \ \ [R0, R1, R2] = the player's coordinates \ from [xPlayer, yPlayer, zPlayer] \ \ [R3, R4, R5] = the player's velocity \ from [xVelocity, yVelocity, zVelocity] CMP R0, #LAUNCHPAD_SIZE \ If either of xPlayer or zPlayer is >= CMPLO R2, #LAUNCHPAD_SIZE \ LAUNCHPAD_SIZE then we are not over the BHS LoseLife \ launchpad, as the launchpad is defined as \ this part of the map; \ \ 0 <= x = LANDING_SPEED, then; ADD R3, R3, R5 \ CMP R3, #LANDING_SPEED \ |xVelocity| + |yVelocity| + |zVelocity| MOVHS PC, R14 \ \ is greater than the safe landing speed, so \ we can't be performing a safe landing, so \ return from the subroutine \ If we get here then we have landed, as the \ altitude checks were made before this \ routine was called, we know we are above \ the launchpad, and our speed is slow \ enough to land \ \ So now we start refuelling MOV R1, #LAUNCHPAD_Y \ Set R1 to the y-coordinate of the player's \ ship as it sits on the launchpad [which is \ set to the launchpad altitude plus the \ height of the ship's undercarriage] LDR R3, [R11, #fuelLevel] \ Bump up the fuel level by 1/160 of a full ADD R3, R3, #&20 \ tank CMP R3, #&1400 STRLO R3, [R11, #fuelLevel] MOV R3, #0 \ Zero the player's velocity MOV R4, #0 MOV R5, #0 STR R3, [R11, #xVelocity] STR R4, [R11, #yVelocity] STR R5, [R11, #zVelocity] STR R1, [R11, #yPlayer] \ Set the y-coordinate of the player to the \ y-coordinate of the launchpad, so the \ player's ship is resting correctly on the \ pad MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; LoseLifeFromParticleLoop \ Type; Subroutine \ Category; Main loop \ Summary; Lose a life when a crash is detected in the particle processing \ loop \ Deep dive; Collisions and bullets \ \ ****************************************************************************** .LoseLifeFromParticleLoop LDMFD R13!, {R10, R12} \ Remove the R10 and R12 registers that the \ MoveAndDrawParticles put on the stack, \ leaving just the return address \ Fall through into LoseLife to lose a life \ ****************************************************************************** \ \ Name; LoseLife \ Type; Subroutine \ Category; Main loop \ Summary; Display a crash animation when we lose a life and end the game if \ this is our last life \ Deep dive; The main game loop \ Collisions and bullets \ \ ****************************************************************************** .LoseLife MOV R0, #0 \ Set playingGame = 0 to flag that the game STR R0, [R11, #playingGame] \ is no longer being played and that this is \ the crash animation MOV R0, #30 \ Set crashLoopCount = 30 to act as a loop STR R0, [R11, #crashLoopCount] \ the crash animation below MOV R8, #81 \ Set R8 = 81 to use as the size of the \ explosion in AddExplosionToBuffer ADD R0, R11, #xPlayer \ Set [R0, R1, R2] to the coordinate in LDMIA R0, {R0-R2} \ xPlayer SUB R1, R1, #CRASH_CLOUD_Y \ Subtract CRASH_CLOUD_Y from the ship's \ y-coordinate so the explosion occurs just \ above the player's ship [5/16 tile sizes \ above the ship, to be precise] BL AddExplosionToBuffer \ Draw a large explosion in place of the \ player's ship .lose1 \ We now run a cut-down version of the main \ loop to display the crash animation [this \ is like the main loop but without the \ calls to drop rocks from the sky, draw the \ player's ship or update the fuel level] \ We now set up the rotation matrix for the \ rocks, using the main loop counter to \ generate rotation angles that change along \ with the main loop [so the rocks spin at a \ nice steady speed] LDR R0, [R11, #mainLoopCount] \ Set R0 = mainLoopCount << 24 MOV R0, R0, LSL #24 MOV R1, R0, LSL #1 \ Set R1 = mainLoopCount << 25 BL CalculateRotationMatrix \ Calculate the rotation matrix from the \ 'angles' given in R0 and R1, which we can \ apply to any rocks we draw in the \ MoveAndDrawParticles routine [as rocks are \ only rotating 3D objects apart from the \ player, and the player calculates its own \ rotation matrix] BL MoveAndDrawParticles \ Move and draw all the particles, such as \ smoke clouds and bullets, into the \ graphics buffers BL DrawObjects \ Draw all the objects, such as trees and \ buildings, into the graphics buffers BL AddTerminatorsToBuffers \ Add terminators to the ends of the \ graphics buffers so we know when to stop \ drawing BL DrawLandscapeAndBuffers \ Draw the landscape and the contents of the \ graphics buffers BL PrintCurrentScore \ Print the number of remaining bullets at \ the left end of the score bar BL SwitchScreenBank \ Switch screen banks and clear the newly \ hidden screen bank to black LDR R0, [R11, #mainLoopCount] \ Increment the main loop counter ADD R0, R0, #1 STR R0, [R11, #mainLoopCount] LDR R0, [R11, #crashLoopCount] \ Decrement the loop counter for the crash SUBS R0, R0, #1 \ animation above STR R0, [R11, #crashLoopCount] BPL lose1 \ Loop back to keep running the crash \ animation until the loop counter runs down LDR R0, [R11, #remainingLives] \ Decrement the number of remaining lives SUBS R0, R0, #1 \ and set the flags accordingly STR R0, [R11, #remainingLives] \ ADD R13, R13, #4 \ Increment the stack pointer by one word so \ we discard the return address from the top \ of the stack, so we rejoin the main loop \ without keeping the return address of the \ subroutine we were in before we jumped \ here [i.e. MoveAndDrawPlayer or \ LandOnLaunchpad] BNE PlacePlayerOnLaunchpad \ If we still have one or more lives left, \ jump to PlacePlayerOnLaunchpad to play the \ next life \ ****************************************************************************** \ \ Name; GameOver \ Type; Subroutine \ Category; Main loop \ Summary; Print a Game Over message and start a new game \ \ ****************************************************************************** .GameOver MOV R0, #112 \ Set the VDU driver screen bank to bank 1 MOV R1, #1 SWI OS_Byte MOV R0, #31 \ Print the following VDU command; SWI OS_WriteC \ MOV R0, #1 \ VDU 31, 1, 16 SWI OS_WriteC \ MOV R0, #16 \ which moves the text cursor to column 1 on SWI OS_WriteC \ row 16, halfway down the screen SWI OS_WriteS \ Print the Game Over message EQUS "GAME OVER - press a " EQUS "key to start again" EQUB 0 OPT FN_AlignWithZeroes MOV R0, #112 \ Set the VDU driver screen bank to bank 2 MOV R1, #2 SWI OS_Byte MOV R0, #31 \ Print the following VDU command; SWI OS_WriteC \ MOV R0, #1 \ VDU 31, 1, 16 SWI OS_WriteC \ MOV R0, #16 \ which moves the text cursor to column 1 on SWI OS_WriteC \ row 16, i.e. the same text coordinates as \ the text we printed in screen bank 1 above SWI OS_WriteS \ Print the Game Over message EQUS "GAME OVER - press a " EQUS "key to start again" EQUB 0 OPT FN_AlignWithZeroes SWI OS_ReadC \ Wait for a key press B StartNewGame \ Jump to StartNewGame to start a brand new \ game \ ****************************************************************************** \ \ Name; graphicsBuffEndAddr2 \ Type; Variable \ Category; Graphics buffers \ Summary; The addresses of the tables containing the graphics buffer \ addresses [same as graphicsBufferEndAddr and graphicsBufferAddr] \ Deep dive; Depth-sorting with the graphics buffers \ \ ****************************************************************************** .graphicsBuffEndAddr2 EQUD graphicsBuffersEnd EQUD graphicsBuffers \ ****************************************************************************** \ \ Name; MoveAndDrawParticles [Part 1 of 4] \ Type; Subroutine \ Category; Particles \ Summary; Process particle movement and draw the particles into the graphics \ buffers, starting with the movement of particles \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Other entry points; \ \ dpar1 The start of the main particle-processing \ loop \ \ ****************************************************************************** .MoveAndDrawParticles STMFD R13!, {R10, R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved ADD R10, R11, #particleData \ Set R10 to the address of the particle \ data buffer, which we update as we work \ through the particle data buffer, so this \ points to the data of the particle being \ processed LDR R12, graphicsBuffEndAddr2 \ Set R1 to the address of the table that \ contains the end addresses of the graphics \ buffers \ We now work our way through the particle \ data buffer, processing each particle in \ turn .dpar1 LDMIA R10, {R0-R7} \ Fetch the eight words of particle data \ from the particle data buffer at R10, so \ this sets the following; \ \ [R0, R1, R2] = particle coordinate \ \ [R3, R4, R5] = particle velocity \ \ R6 = particle lifespan counter \ \ R7 = particle flags CMP R7, #0 \ If the last word of the data is zero, then LDMEQFD R13!, {R10, R12, PC} \ this is a null terminator and we have \ reached the end of the buffer, so retrieve \ the registers that we stored on the stack \ and return from the subroutine .dpar2 SUBS R6, R6, #1 \ Decrement the particle's lifespan counter \ in R6 BEQ DeleteParticleData \ If R6 is now zero then the particle just \ expired, so jump to DeleteParticleData to \ delete this particle from the particle \ data buffer \ \ DeleteParticleData then jumps back to \ dpar1 to move on to the next particle ADD R0, R0, R3 \ Move the particle by its current velocity ADD R1, R1, R4 \ by adding the particle's velocity vector ADD R2, R2, R5 \ in [R3, R4, R5] to the particle's \ coordinates in [R0, R1, R2] TST R7, #&00100000 \ If bit 20 of the particle flags is set, LDRNE R14, [R11, #gravity] \ add gravity to the velocity's y-coordinate ADDNE R4, R4, R14 \ in R4, so the particle accelerates towards \ the ground TST R7, #&00010000 \ If bit 16 of the particle flags is set, BLNE SetParticleColourToFade \ call SetParticleColourToFade to fade the \ particle colour from white to red, \ according to the particle's age, and store \ the colour in the bottom byte of the \ particle flags [bits 0 to 7] MOV R8, R0 \ Set [R8, R9] = [R0, R2] so we can fetch MOV R9, R2 \ the landscape altitude directly below the \ particle STMFD R13!, {R0-R3} \ Store R0 to R3 on the stack so they don't \ get corrupted by the following call to \ GetLandscapeAltitude BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates [x, z] = [R8, R9] MOV R9, R0 \ Copy the altitude into R9 LDMFD R13!, {R0-R3} \ Retrieve the values of R0 to R3 that we \ stored above TST R7, #&00200000 \ If bit 21 of the particle flags is set BLNE ProcessObjectDestruction \ then the particle can destroy objects that \ it hits, so call ProcessObjectDestruction \ to process this CMP R1, R9 \ If R1 > R9 then the particle is below the BLGT BounceParticle \ level of the landscape, so bounce the \ particle off the ground STMIA R10!, {R0-R7} \ Store the updated particle data in the \ particle data buffer, updating R10 so it \ points to the next particle, ready for the \ next iteration around the loop \ ****************************************************************************** \ \ Name; MoveAndDrawParticles [Part 2 of 4] \ Type; Subroutine \ Category; Particles \ Summary; Draw particles [including rocks] into the graphics buffers \ Deep dive; Particles and particle clouds \ \ ****************************************************************************** LDR R8, [R11, #xCamera] \ Set R0 = R0 - xCamera SUB R0, R0, R8 \ = x - xCamera \ \ So R0 contains the x-coordinate of the \ particle relative to the camera LDR R8, [R11, #zCamera] \ Set R2 = R2 - zCamera SUB R2, R2, R8 \ \ So R2 contains the z-coordinate of the \ particle relative to the camera ADD R2, R2, #LANDSCAPE_Z \ Move the coordinate back by the landscape \ offset, so [R0, R2] contains the \ coordinate of the particle relative to the \ back-centre point of the landscape LDR R8, [R11, #yCamera] \ Set R1 = R1 - yCamera SUB R1, R1, R8 \ = y - yCamera \ \ So R1 contains the y-coordinate of the \ particle relative to the camera CMP R2, #LANDSCAPE_Z \ If the z-coordinate of the particle in R2 BHS dpar1 \ is further into the screen than the \ landscape offset in LANDSCAPE_Z, then it \ is beyond the back of the visible \ landscape, so jump to dpar1 to move on to \ the next particle in the buffer CMP R2, #LANDSCAPE_Z_FRONT \ If the z-coordinate of the particle in R2 BLO dpar1 \ is closer to us than LANDSCAPE_Z_FRONT, \ then it is closer than the front of the \ visible landscape, so jump to dpar1 to \ move on to the next particle in the buffer MOVS R14, R0 \ Set R14 = |R0| RSBMI R14, R0, #0 \ = |particle x-coordinate| CMP R14, #LANDSCAPE_X_HALF \ If the x-coordinate of the particle in R14 BHS dpar1 \ is more than half the x-axis width of the \ landscape to the left or right, then it is \ past the edge of the landscape and is too \ far to be drawn, so jump to dpar1 to move \ on to the next particle in the buffer TST R7, #&00020000 \ If bit 17 of the particle flags is clear BEQ dpar4 \ then this is not a rock, so jump to dpar4 \ to draw the particle and its shadow into \ the graphics buffers in part 4 \ Otherwise this is a rock, so fall through \ into part 3 to process collisions \ ****************************************************************************** \ \ Name; MoveAndDrawParticles [Part 3 of 4] \ Type; Subroutine \ Category; Particles \ Summary; Process rocks by checking for collisions and drawing them as 3D \ objects \ Deep dive; Drawing 3D objects \ Particles and particle clouds \ Collisions and bullets \ \ ****************************************************************************** \ If we get here then the particle is a \ rock, so we need to check whether it has \ hit the player's ship STMFD R13!, {R0-R2} \ Store the rock coordinates in [R0, R1, R2] \ on the stack so we can retrieve them below LDR R14, [R11, #playingGame] \ If playingGame = 0 then this is the crash CMP R14, #0 \ animation, so jump to dpar3 to draw the BEQ dpar3 \ rock, skipping the call to lose a life [as \ the EQ condition is true, which does not \ match BLO] CMP R0, #0 \ Set R0 = |R0| RSBMI R0, R0, #0 \ = |rock x-coordinate| SUBS R2, R2, #LANDSCAPE_Z_MID \ Set R2 = |R2 - LANDSCAPE_Z_MID| RSBMI R2, R2, #0 \ = |rock z-coordinate - 15 tiles| \ \ Because the player's ship is always at \ x-coordinate 0 and the z-coordinate at \ the mid-point of the landscape, this works \ out the rock's coordinate relative to the \ ship [for the x- and z-coordinates] \ \ The rock object is one tile in size along \ each axis, so we can do a check against \ the file size to see if we are being hit \ by the rock CMP R0, #TILE_SIZE \ If either of R0 or R2 is bigger than one CMPLO R2, #TILE_SIZE \ tile size, jump to dpar3 as the rock is BHS dpar3 \ missing us, skipping the call to lose a \ life [as the HS condition is true, which \ does not match BLO] \ The rock is overlapping the player in \ either the x- or z-coordinate, so now we \ need to check its altitude LDR R0, [R11, #yCamera] \ Set R1 = |R1 + yCamera - yPlayer| ADD R1, R1, R0 \ LDR R0, [R11, #yPlayer] \ So R1 contains the y-coordinate of the SUBS R1, R1, R0 \ rock relative to the player, which we can RSBMI R1, R1, #0 \ also test against the tile size to check \ for a collision CMP R1, #TILE_SIZE \ If R1 < TILE_SIZE then the LO condition \ will be true, which will send us to \ LoseLifeFromParticleLoop below to lose a \ life, as the rock has hit us .dpar3 LDMFD R13!, {R0-R2} \ Retrieve the rock coordinates that we \ stored above into [R0, R1, R2] BLO LoseLifeFromParticleLoop \ Jump to LoseLifeFromParticleLoop if the LO \ condition is true, which will only be the \ case if we reached the CMP just before \ dpar3 and R1 < TILE_SIZE LDR R14, objectRockAddr \ Store the address of the rock's object STR R14, [R11, #objectData] \ blueprint in objectData to pass to the \ DrawObject routine ADD R3, R11, #rotationMatrix \ Set R3 to the address of the rock's \ rotation matrix, to pass to DrawObject BL DrawObject \ Draw the rock into the graphics buffers B dpar1 \ Loop back to dpar1 to process the next \ particle in the particle data buffer \ ****************************************************************************** \ \ Name; MoveAndDrawParticles [Part 4 of 4] \ Type; Subroutine \ Category; Particles \ Summary; Draw particles into the graphics buffers \ Deep dive; Particles and particle clouds \ \ ****************************************************************************** .dpar4 \ If we get here then we draw the particle \ and its shadow into the graphics buffers STMFD R13!, {R0-R2, R7} \ Store R0, R1, R2 and R7 on the stack so \ they don't get corrupted by the following \ calls LDR R14, [R11, #yCamera] \ Set R1 = R9 - yCamera SUB R1, R9, R14 \ = landscape altitude - yCamera \ \ We set R9 in part 1 to the altitude of the \ landscape below the particle, so this sets \ R1 to the y-coordinate of the particle's \ shadow relative to the camera MOV R8, R2 \ Set R8 to the z-coordinate of the particle \ relative to the camera BL ProjectParticleOntoScreen \ Project the coordinates of the particle's \ shadow in [R0, R1, R2] onto the screen, \ returning the results in [R0, R1] \ \ This also clears the C flag if the \ particle coordinates are on-screen BLCC DrawParticleShadowToBuffer \ If the projected coordinates fit onto the \ screen, draw the particle's projected \ shadow into the graphics buffers LDMFD R13!, {R0-R2, R7} \ Retrieve the values of R0, R1, R2 and R7 \ that we stored above MOV R8, R2 \ Set R8 to the z-coordinate of the particle \ relative to the camera BL ProjectParticleOntoScreen \ Project the coordinates of the particle in \ [R0, R1, R2] onto the screen, returning \ the results in [R0, R1] \ \ This also clears the C flag if the \ particle coordinates are on-screen BLCC DrawParticleToBuffer \ If the projected coordinates fit onto the \ screen, draw the projected particle into \ the graphics buffers LDMIA R10, {R0-R7} \ Fetch the eight words of particle data \ for the next particle from the particle \ data buffer at R10 CMP R7, #0 \ If the last word of the data is not zero BNE dpar2 \ then this is a valid particle rather than \ a null terminator, so loop back to dpar2 \ to process the next particle LDMFD R13!, {R10, R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; SetParticleColourToFade \ Type; Subroutine \ Category; Particles \ Summary; Set the flags for a particle whose colour fades from white to red \ over time, to give a white-hot explosion particle that cools down \ Deep dive; Particles and particle clouds \ Screen memory in the Archimedes \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R6 Particle lifespan counter [i.e. how many \ iterations around the main loop before the \ particle expires] \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R7 Particle flags for a fading colour particle \ \ * Bits 0-7 = particle colour \ * Bit 16 set = colour fades white to red \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle \ \ ****************************************************************************** .SetParticleColourToFade STMFD R13!, {R0-R2, R6} \ Store R0, R1, R2 and R6 on the stack so \ they don't get corrupted by the following \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 MOV R0, #15 \ Set the red channel to 15 CMP R6, #8 \ If R6 >= 8, set; MOVHS R1, #15 \ MOVLO R1, R6, LSL #1 \ Green channel in R1 = 15 SUBHS R2, R6, #8 \ Blue channel in R2 = [R6 - 8] * 2 MOVHS R2, R2, LSL #1 \ MOVLO R2, #0 \ otherwise set; \ \ Green channel in R1 = R6 * 2 \ Blue channel in R2 = 0 \ \ So particles start out white [R6 > 8] \ with a fading level of blue, until the \ blue disappears entirely [R6 = 8] and \ then the green fades away [R6 < 8] to \ leave a pure red particle [R6 = 0] \ \ So this is a fading particle from white \ to red, so this looks like a burning \ particle from an explosion that cools \ over time \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form; \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of; AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ [the bottom two bits of R1 OR R2] OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&001D0000 \ Set bits 16, 18, 19 and 20 of the particle \ flags, so that's; \ \ * Bit 16 set = colour fades white to red \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle LDMFD R13!, {R0-R2, R6} \ Retrieve the registers that we stored on \ the stack above MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; BounceParticle \ Type; Subroutine \ Category; Particles \ Summary; Bounce a particle off the ground \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinates \ \ [R3, R4, R5] Particle velocity \ \ R7 Particle flags \ \ R9 The altitude of the landscape directly below \ the particle \ \ ****************************************************************************** .BounceParticle MOV R1, R9 \ Set R1 to R9, so [R0, R1, R2] now contains \ the coordinates of the point on the \ landscape directly below the particle CMP R9, #SEA_LEVEL \ If the particle is above the sea, jump to BEQ SplashParticleIntoSea \ SplashParticleIntoSea to splash the \ particle into the sea, returning from the \ subroutine using a tail call TST R7, #&00080000 \ If bit 19 of the particle flags is clear, BEQ DeleteParticleData \ jump to DeleteParticleData to delete the \ particle without bouncing or exploding TST R7, #&01000000 \ If bit 24 of the particle flags is set, BNE AddSmallExplosionToBuffer \ jump to AddSmallExplosionToBuffer to \ destroy the particle in a small explosion MOV R3, R3, ASR #1 \ Otherwise we bounce the particle off the MOV R4, R4, ASR #1 \ ground by setting the particle's velocity MOV R5, R5, ASR #1 \ vector to half its previous speed, and in RSB R4, R4, #0 \ the opposite direction in the y-axis MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; ProcessObjectDestruction \ Type; Subroutine \ Category; Particles \ Summary; If this particle has hit an object, destroy the object and the \ particle in an explosion, scoring points if it's a bullet \ Deep dive; Particles and particle clouds \ Collisions and bullets \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] The particle's coordinates \ \ R7 Particle flags \ \ R9 The altitude of the landscape directly below \ the particle \ \ ****************************************************************************** .ProcessObjectDestruction SUB R8, R9, R1 \ Set R8 = altitude - y-coordinate \ \ So R8 contains the vertical distance \ between the particle and the ground CMP R8, #SAFE_HEIGHT \ If R8 is higher than the minimum safe MOVHS PC, R14 \ height for avoiding objects on the ground \ in SAFE_HEIGHT [which is set to 1.5 tile \ sizes] then return from the subroutine as \ the particle is too high off the ground to \ be hitting any objects STMFD R13!, {R2} \ Store R2 on the stack so we can retrieve \ it below STR R14, [R11, #objectType] \ Store the return address in objectType \ [the choice of variable is not important, \ we are just using it as temporary storage \ here] ADD R14, R11, #objectMap \ Set R14 to the address of the object map AND R2, R2, #&FF000000 \ Set the bottom three bytes of R2 to zero, \ leaving just the top byte, so we can use \ it in the following ADD R14, R14, R0, LSR #24 \ Set R14 = R14 + [R0 >> 24] + [R2 >> 16] ADD R14, R14, R2, LSR #16 \ \ R0 is shifted into the bottom byte of R14, \ so that's the x-coordinate, and R2 is \ shifted into the second byte of R14, so \ that's the z-coordinate, so R14 points to \ the object map entry for coordinate \ [R0, R2] LDMFD R13!, {R2} \ Retrieve the value of R2 that we stored \ above, so it contains the particle \ y-coordinate once again LDRB R8, [R14] \ Set R8 to the byte in the object map at \ the coordinate [R0, R2] CMP R8, #&FF \ If the object map entry is &FF, then there LDREQ PC, [R11, #objectType] \ is no object on the map at the particle's \ location, so we haven't hit anything, so \ return from the subroutine by fetching the \ return address that we stored above CMP R8, #12 \ If the object being hit by the particle is BHS AddSmallExplosionToBuffer \ 12 or greater, then the object has already \ been destroyed, so draw a small explosion \ by calling AddSmallExplosionToBuffer, and \ return from the subroutine using a tail \ call ADD R8, R8, #12 \ Otherwise the particle has just hit an STRB R8, [R14] \ undestroyed object, so add 12 to the \ object's type in the object map to denote \ that it has been destroyed TST R7, #&00020000 \ If bit 17 the particle flags is clear then LDREQ R8, [R11, #currentScore] \ the particle doing the hitting is not a ADDEQ R8, R8, #20 \ rock, so it must be a bullet, so increment STREQ R8, [R11, #currentScore] \ the current score by 20 MOV R8, #20 \ Set R8 = 20 and call AddExplosionToBuffer BL AddExplosionToBuffer \ to draw a medium-sized explosion where the \ particle hit B DeleteParticleData \ Jump to DeleteParticleData to delete the \ particle, as it gets destroyed in the \ explosion, and return from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name; AddSmallExplosionToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add a small explosion to the particle data buffer \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Explosion coordinates \ \ R7 Particle flags \ \ ****************************************************************************** .AddSmallExplosionToBuffer MOV R8, #3 \ Set R8 = 3 and call AddExplosionToBuffer BL AddExplosionToBuffer \ to draw a small explosion at the given \ coordinates B DeleteParticleData \ Jump to DeleteParticleData to delete the \ particle, as it gets destroyed in the \ explosion, and return from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name; SplashParticleIntoSea \ Type; Subroutine \ Category; Particles \ Summary; Splash a particle into the sea, creating a spray particle \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinates [on the sea's surface] \ \ R7 Particle flags \ \ ****************************************************************************** .SplashParticleIntoSea TST R7, #&00040000 \ If bit 18 of the particle flags is clear, BEQ DeleteParticleData \ jump to DeleteParticleData to delete the \ particle without splashing TST R7, #&00800000 \ If bit 23 of the particle flags is set, MOVNE R8, #65 \ set the number of spray particles in R8 MOVEQ R8, #4 \ to 65, otherwise set it to 4 SUB R1, R1, #SPLASH_HEIGHT \ Set R1 to a point just above the sea's \ surface [R1 is on the surface, so this \ moves it to SPLASH_HEIGHT above the waves, \ or 1/16 of a tile size] .psea1 BL AddSprayParticleToBuffer \ Add a spray particle to the particle data \ buffer SUBS R8, R8, #1 \ Decrement the particle counter in R8 BNE psea1 \ Loop back until we have added all the \ spray particles \ Fall through into DeleteParticleData to \ delete the particle and return from the \ subroutine, as the particle has now \ crashed into the sea \ ****************************************************************************** \ \ Name; DeleteParticleData \ Type; Subroutine \ Category; Particles \ Summary; Delete a particle from the particle data buffer and move the last \ particle's data down to take its place \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R10 The address in the particle data buffer of \ the particle to delete \ \ ****************************************************************************** .DeleteParticleData LDR R8, [R11, #particleEnd] \ Decrease the address of the end of the SUB R8, R8, #8*4 \ particle data buffer by eight words, so it STR R8, [R11, #particleEnd] \ points to the last particle's data in the \ buffer LDMIA R8, {R0-R7} \ Copy the eight bytes of particle data from STMIA R10, {R0-R7} \ the end of the buffer to the address in \ R10 MOV R7, #0 \ Zero the last word of the last particle's STR R7, [R8, #7*4] \ data, so the last particle in the buffer \ now acts as a null terminator LDR R8, [R11, #particleCount] \ Decrement the particle counter to reflect SUB R8, R8, #1 \ the new number of particles on-screen STR R8, [R11, #particleCount] B dpar1 \ Jump to the start of the main loop in \ MoveAndDrawParticles to process the next \ particle in the particle data buffer \ ****************************************************************************** \ \ Name; AddBulletParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add a bullet particle to the particle data buffer \ Deep dive; Particles and particle clouds \ Collisions and bullets \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinate \ \ [R3, R4, R5] Particle velocity \ \ R6 Particle lifespan counter [i.e. how many \ iterations around the main loop before the \ particle expires] \ \ R7 Particle flags \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^[32 - R8] \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^[32 - R9] \ \ ****************************************************************************** .AddBulletParticleToBuffer STMFD R13!, {R8, R14} \ Store R8 and the return address on the \ stack, so the call to StoreParticleData \ can return properly B StoreParticleData \ Jump to StoreParticleData to store the \ particle in the particle data buffer, \ returning from the subroutine using a tail \ call \ ****************************************************************************** \ \ Name; AddExhaustParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add one of the moving particles in the exhaust plume to the \ particle data buffer \ Deep dive; Particles and particle clouds \ Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinate \ \ [R3, R4, R5] Particle velocity \ \ R6 Particle lifespan counter [i.e. how many \ iterations around the main loop before the \ particle expires] \ \ R7 Particle flags \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^[32 - R8] \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^[32 - R9] \ \ ****************************************************************************** .AddExhaustParticleToBuffer STMFD R13!, {R8, R14} \ Store R8 and the return address on the \ stack, so the following call to the \ particle adding routine below can return \ properly B AddMovingParticleToBuffer \ Add a moving particle to the particle data \ buffer that starts off moving along the \ exhaust vector, and with a random element \ being added to its velocity and lifespan \ counter, returning from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name; AddStaticParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add a particle to the particle data buffer that starts off static, \ adding a random element to its velocity and lifespan counter \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinate \ \ R6 Particle lifespan counter [i.e. how many \ iterations around the main loop before the \ particle expires] \ \ R7 Particle flags; \ \ * Bits 0-7 = particle colour \ * Bit 16 set = colour fades white to red \ * Bit 17 set = particle is a rock \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on hitting ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size [big when set] \ * Bit 24 set = explode on hitting ground \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^[32 - R8] \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^[32 - R9] \ \ Stack Contains a value to restore into R8 at the \ end, and the return address \ \ ****************************************************************************** .AddStaticParticleToBuffer MOV R4, #0 \ Set R4 = 0 \ Fall into AddRisingParticleToBuffer so we \ pass a velocity of [0, 0, 0] to the \ AddMovingParticleToBuffer routine \ ****************************************************************************** \ \ Name; AddRisingParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add a particle to the particle data buffer that initially drifts \ up, with a random element to its velocity and lifespan counter \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinate \ \ R4 Particle velocity in the y-axis [up-down] \ \ R6 Particle lifespan counter [i.e. how many \ iterations around the main loop before the \ particle expires] \ \ R7 Particle flags; \ \ * Bits 0-7 = particle colour \ * Bit 16 set = colour fades white to red \ * Bit 17 set = particle is a rock \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on hitting ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size [big when set] \ * Bit 24 set = explode on hitting ground \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^[32 - R8] \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^[32 - R9] \ \ Stack Contains a value to restore into R8 at the \ end, and the return address \ \ ****************************************************************************** .AddRisingParticleToBuffer MOV R3, #0 \ Set R3 = 0 MOV R5, #0 \ Set R5 = 0 \ Fall into AddMovingParticleToBuffer with \ a velocity of [0, R4, 0] \ ****************************************************************************** \ \ Name; AddMovingParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add a moving particle to the particle data buffer, adding a random \ element to its velocity and lifespan counter \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinate \ \ [R3, R4, R5] Particle velocity \ \ R6 Particle lifespan counter [i.e. how many \ iterations around the main loop before the \ particle expires] \ \ R7 Particle flags; \ \ * Bits 0-7 = particle colour \ * Bit 16 set = colour fades white to red \ * Bit 17 set = particle is a rock \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on hitting ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size [big when set] \ * Bit 24 set = explode on hitting ground \ \ R8 Magnitude of the random element that's added \ to the velocity, with a larger figure giving \ a smaller random element; the actual range \ is +/- 2^[32 - R8] \ \ R9 Magnitude of the random element that's added \ to the particle lifespan, with a larger \ figure giving a smaller random element; the \ actual range is 0 to 2^[32 - R9] \ \ Stack Contains a value to restore into R8 at the \ end, and the return address \ \ ****************************************************************************** .AddMovingParticleToBuffer STMFD R13!, {R0-R1} \ Store R0 and R1 on the stack so we can \ restore them after the following \ We now add a random signed element to the \ particle velocity in [R3, R4, R5], with \ the scale of the random element determined \ by R8 [so a larger R8 adds a smaller \ random element to the velocity] BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R3, R3, R0, ASR R8 \ Set R3 = R3 + R0 >> R8 \ \ We keep the sign in R0, so this can either \ increase or decrease R3 BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R4, R4, R0, ASR R8 \ Set R4 = R4 + R0 >> R8 \ \ We keep the sign in R0, so this can either \ increase or decrease R4 BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R5, R5, R0, ASR R8 \ Set R5 = R5 + R0 >> R8 \ \ We keep the sign in R0, so this can either \ increase or decrease R5 \ We now add a random positive element to \ the particle's lifespan counter in R6, \ with the scale determined by R9 [so a \ larger R9 adds a smaller random element \ to the particle's lifespan] BL GetRandomNumbers \ Set R0 and R1 to random numbers ADD R6, R6, R0, LSR R9 \ Set R6 = R6 + R0 >> R9 \ \ We do not keep the sign in R0, so the \ addition is always positive LDMFD R13!, {R0-R1} \ Retrieve the values of R0 and R1 that we \ stored above \ ****************************************************************************** \ \ Name; StoreParticleData \ Type; Subroutine \ Category; Particles \ Summary; Store the data for a new particle in the particle data buffer \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinate \ \ [R3, R4, R5] Particle velocity \ \ R6 Particle lifespan counter [i.e. how many \ iterations around the main loop before the \ particle expires] \ \ R7 Particle flags \ \ Stack Contains a value to restore into R8 at the \ end, and the return address \ \ ****************************************************************************** .StoreParticleData LDR R8, [R11, #particleCount] \ Set R8 to the number of particles that are \ already on-screen CMP R8, #MAX_PARTICLES \ If R8 >= MAX_PARTICLES then we already LDMHSFD R13!, {R8, PC} \ have the maximum number of particles \ on-screen, so restore the value of R8 from \ the stack and return from the subroutine ADD R8, R8, #1 \ Increment the particle counter to record STR R8, [R11, #particleCount] \ that we have added a new particle LDR R8, [R11, #particleEnd] \ Set R8 to the address of the end of the \ particle data buffer, which is where we \ can store the new batch of data STMIA R8!, {R0-R7} \ Store the eight words of particle data in \ the buffer, updating R8 as we go STR R8, [R11, #particleEnd] \ Update particleEnd with the new address of \ the end of the buffer MOV R9, #0 \ Zero the last word of the next particle's STR R9, [R8, #7*4] \ data after the one we just wrote, so this \ acts as a null terminator LDMFD R13!, {R8, PC} \ Restore the value of R8 from the stack and \ return from the subroutine \ ****************************************************************************** \ \ Name; InitialiseParticleData \ Type; Subroutine \ Category; Start and end \ Summary; Initialise the particle data buffer and associated variables \ Deep dive; Particles and particle clouds \ \ ****************************************************************************** .InitialiseParticleData ADD R0, R11, #particleData \ Set particleEnd to the address of the STR R0, [R11, #particleEnd] \ particle data buffer, so the buffer starts \ off empty MOV R1, #0 \ Zero the last word of the first particle's STR R1, [R0, #7*4] \ data, so it acts as a null terminator STR R1, [R11, #particleCount] \ Set particleCount = 0 to indicate that \ there are no particles on-screen MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; AddSmokeParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add a smoke particle to the particle data buffer \ Deep dive; Particles and particle clouds \ Screen memory in the Archimedes \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinate \ \ ****************************************************************************** .AddSmokeParticleToBuffer STMFD R13!, {R0-R2, R8, R14} \ Store the particle coordinates on the \ stack so we can retrieve them below, and \ also store R8 and the return address so \ we can pass them to the particle adding \ routine below \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 BL GetRandomNumbers \ Set R0 and R1 to random numbers AND R0, R0, #7 \ Reduce R0 to a random number in the range ADD R0, R0, #3 \ 3 to 10 MOV R1, R0 \ Set R1 and R2 to the same number, so if MOV R2, R0 \ R0, R1 and R2 represent the three colour \ channels, we have a grey colour of a \ random intensity \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form; \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of; AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ [the bottom two bits of R1 OR R2] OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&00080000 \ Set bit 19 of the particle flags, so that \ the particle bounces on the ground should \ it ever reach it MVN R4, #SMOKE_RISING_SPEED \ Set R4 to the speed of the smoke rising \ to pass as the y-axis velocity in the \ particle adding routine below, so the \ particle drifts slowly up and away from \ the ground LDMFD R13!, {R0-R2} \ Retrieve the particle coordinates that we \ stored above into [R0, R1, R2] MOV R9, #25 \ Set the random element of the particle's \ lifespan to the range 0 to 2^[32 - 25], \ i.e. 0 to 128 MOV R8, #13 \ Set the random element of the particle's \ velocity to the range +/- 2^[32 - 13], \ i.e. -&80000 to +&80000 MOV R6, #15 \ Set the particle's lifespan counter to 15 \ iterations of the main loop B AddRisingParticleToBuffer \ Add a particle to the particle data buffer \ that initially drifts upwards, and with a \ random element being added to its velocity \ and lifespan counter, returning from the \ subroutine using a tail call \ ****************************************************************************** \ \ Name; AddDebrisParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add a debris particle to the particle data buffer, which is a \ purple-brownish-green particle that bounces out of an explosion \ Deep dive; Particles and particle clouds \ Screen memory in the Archimedes \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinates \ \ ****************************************************************************** .AddDebrisParticleToBuffer STMFD R13!, {R0-R2, R8, R14} \ Store the particle coordinates on the \ stack so we can retrieve them below, and \ also store R8 and the return address so \ we can pass them to the particle adding \ routine below \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 BL GetRandomNumbers \ Set R0 and R1 to random numbers MOV R1, R1, LSR #29 \ Set R1 to a random number in the range 0 \ to 7 MOV R2, R0, LSR #30 \ Set R2 to a random number in the range 0 \ to 3 AND R0, R0, #7 \ Set R0 to a random number in the range 0 \ to 7 ADD R0, R0, #4 \ Set R0 to a random number in the range 4 \ to 11, to use in the red channel ADD R1, R1, #2 \ Set R1 to a random number in the range 2 \ to 2, to use for the green channel ADD R2, R2, #4 \ Set R2 to a random number in the range 4 \ to 7, to use in the blue channel \ This sets the channels to a randomly \ purple-brownish-green colour \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form; \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of; AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ [the bottom two bits of R1 OR R2] OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&001C0000 \ Set bits 18, 19 and 20 of the particle \ flags, so that's; \ \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle LDMFD R13!, {R0-R2} \ Retrieve the particle coordinates that we \ stored above into [R0, R1, R2] MOV R9, #26 \ Set the random element of the particle's \ lifespan to the range 0 to 2^[32 - 26], \ i.e. 0 to 64 MOV R8, #10 \ Set the random element of the particle's \ velocity to the range +/- 2^[32 - 10], \ i.e. -&400000 to +&400000 MOV R6, #15 \ Set the particle's lifespan counter to 15 \ iterations of the main loop B AddStaticParticleToBuffer \ Add a particle to the particle data buffer \ that starts off static, and with a random \ element being added to its velocity and \ lifespan counter, returning from the \ subroutine using a tail call \ ****************************************************************************** \ \ Name; DropARockFromTheSky \ Type; Subroutine \ Category; Particles \ Summary; Drop a rock from the specified coordinates by spawning it as a \ particle, albeit a very big particle with an associated 3D object \ Deep dive; Particles and particle clouds \ Screen memory in the Archimedes \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] The 3D coordinate [x, y, z] where we spawn \ the rock \ \ ****************************************************************************** .DropARockFromTheSky STMFD R13!, {R0-R2, R8, R14} \ Store the particle coordinates on the \ stack so we can retrieve them below, and \ also store R8 and the return address so \ we can pass them to the particle adding \ routine below \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 BL GetRandomNumbers \ Set R0 and R1 to random numbers MOV R1, R1, LSR #29 \ Set R1 to a random number in the range 0 \ to 7 MOV R2, R0, LSR #30 \ Set R2 to a random number in the range 0 \ to 3 AND R0, R0, #7 \ Set R0 to a random number in the range 0 \ to 7 ADD R0, R0, #4 \ Set R0 to a random number in the range 4 \ to 11, to use in the red channel ADD R1, R1, #2 \ Set R1 to a random number in the range 2 \ to 2, to use for the green channel ADD R2, R2, #4 \ Set R2 to a random number in the range 4 \ to 7, to use in the blue channel \ This sets the channels to a randomly \ purple-brownish-green colour \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form; \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of; AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ [the bottom two bits of R1 OR R2] OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&00FE0000 \ Set bits 17 to 23 of the particle flags, \ so that's; \ \ * Bit 17 set = particle is a rock \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle \ * Bit 21 set = can destroy objects \ * Bit 23 set = splash size is big \ * Bit 24 set = explode on hitting ground LDMFD R13!, {R0-R2} \ Retrieve the particle coordinates that we \ stored above into [R0, R1, R2] MOV R9, #27 \ Set the random element of the particle's \ lifespan to the range 0 to 2^[32 - 27], \ i.e. 0 to 32 MOV R8, #10 \ Set the random element of the particle's \ velocity to the range +/- 2^[32 - 10], \ i.e. -&400000 to +&400000 MOV R6, #170 \ Set the particle's lifespan counter to \ 170 iterations of the main loop, so it \ won't disappear before it hits the ground B AddStaticParticleToBuffer \ Add a rock to the particle data buffer \ that starts off static, and with a random \ element being added to its velocity and \ lifespan counter, returning from the \ subroutine using a tail call \ ****************************************************************************** \ \ Name; AddSparkParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add a spark particle to the particle data buffer that fades from \ white-hot to red over time \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinates \ Deep dive; Particles and particle clouds \ \ ****************************************************************************** .AddSparkParticleToBuffer STMFD R13!, {R8, R14} \ Store R8 and the return address on the \ stack, so the call to the particle adding \ routine below can return properly MOV R7, #&001D0000 \ Set bits 16, 18, 19 and 20 of the particle \ flags, so that's; \ \ * Bit 16 set = colour fades white to red \ * Bit 18 set = splash on impact with sea \ * Bit 19 set = bounce on ground \ * Bit 20 set = apply gravity to particle MOV R9, #29 \ Set the random element of the particle's \ lifespan to the range 0 to 2^[32 - 29], \ i.e. 0 to 8 MOV R8, #8 \ Set the random element of the particle's \ velocity to the range +/- 2^[32 - 8], \ i.e. -&1000000 to +&1000000 MOV R6, #8 \ Set the particle's lifespan counter to 8 \ iterations of the main loop B AddStaticParticleToBuffer \ Add a particle to the particle data buffer \ that starts off static, and with a random \ element being added to its velocity and \ lifespan counter, returning from the \ subroutine using a tail call \ ****************************************************************************** \ \ Name; AddSprayParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Add a spray particle to the particle data buffer \ Deep dive; Particles and particle clouds \ Screen memory in the Archimedes \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinates \ \ ****************************************************************************** .AddSprayParticleToBuffer STMFD R13!, {R0-R2, R8, R14} \ Store the particle coordinates on the \ stack so we can retrieve them below, and \ also store R8 and the return address so \ we can pass them to the particle adding \ routine below \ We start by setting up a three-channel \ colour, with the red channel in R0, the \ green channel in R1 and the blue channel \ in R2 BL GetRandomNumbers \ Set R0 and R1 to random numbers AND R2, R0, #3 \ Set R2 to a random number in the range 12 ADD R2, R2, #12 \ to 15, to use as the blue channel AND R0, R0, #4 \ Set R0 to a random number that's either 8 ADD R0, R0, #8 \ or 12 MOV R1, R0 \ Set R1 to the same as R0, so the red and \ green channels are the same, giving an \ overall colour that combines the high blue \ channel level with one of two greyscale \ levels, giving one of four shades of blue \ We now build a VIDC colour number in R7 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form; \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R7 from \ the red, green and blue values in R0, R1 \ and R2 ORR R7, R1, R2 \ Set R7 to the bottom three bits of; AND R7, R7, #%00000011 \ ORR R7, R7, R0 \ [the bottom two bits of R1 OR R2] OR R0 AND R7, R7, #7 \ \ So this sets bits 0, 1 and 2 of R7 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R7, R7, #%00010000 \ set bit 4 of R7 AND R1, R1, #%00001100 \ Clear all bits of the green channel in R1 \ except bits 2-3 ORR R7, R7, R1, LSL #3 \ And stick them into bits 5-6 of R7 TST R2, #%00000100 \ If bit 2 of the blue channel in R2 is set, ORRNE R7, R7, #%00001000 \ set bit 3 of R7 TST R2, #%00001000 \ If bit 3 of the blue channel in R2 is set, ORRNE R7, R7, #%10000000 \ set bit 7 of R7 ORR R7, R7, #&00100000 \ Set bit 20 of the particle flags, so that \ gravity is applied to the particle LDMFD R13!, {R0-R2} \ Retrieve the particle coordinates that we \ stored above into [R0, R1, R2] MOV R9, #26 \ Set the random element of the particle's \ lifespan to the range 0 to 2^[32 - 26], \ i.e. 0 to 64 MOV R8, #10 \ Set the random element of the particle's \ velocity to the range +/- 2^[32 - 10], \ i.e. -&400000 to +&400000 MOV R6, #20 \ Set the particle's lifespan counter to 20 \ iterations of the main loop B AddStaticParticleToBuffer \ Add a particle to the particle data buffer \ that starts off static, and with a random \ element being added to its velocity and \ lifespan counter, returning from the \ subroutine using a tail call \ ****************************************************************************** \ \ Name; AddExplosionToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Create a big explosion of particles and add it to the particle \ data buffer \ Deep dive; Particles and particle clouds \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R8 The number of particle clusters in the \ explosion [there are four particles per \ cluster] \ \ ****************************************************************************** .AddExplosionToBuffer STMFD R13!, {R3-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved .expl1 \ Each cluster is made up of four particles, \ so we now add them to the particle data \ buffer BL AddSparkParticleToBuffer \ Add a spark particle to the particle data \ buffer [one that fades from white to red] BL AddDebrisParticleToBuffer \ Add a spark particle to the particle data \ buffer [a purple-brownish-green particle \ that flies out and bounces on the ground] BL AddSmokeParticleToBuffer \ Add a smoke particle to the particle data \ buffer [a grey particle that slowly rises] BL AddSparkParticleToBuffer \ Add another spark particle to the particle \ data buffer [one that fades from white to \ red] SUBS R8, R8, #1 \ Decrement the particle cluster counter in \ R8 BPL expl1 \ Loop back until we have drawn all R8 \ particle clusters in the explosion LDMFD R13!, {R3-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; AddShipExplosionToBuffer \ Type; Subroutine \ Category; Particles \ Summary; An unused routine that adds a 50-cluster explosion cloud to the \ particle data buffer, just front of the player's ship \ Deep dive; Unused code in Lander \ \ ****************************************************************************** .AddShipExplosionToBuffer STMFD R13!, {R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R8, #50 \ Set R8 = 50 so the explosion contains 50 \ clusters of four particles \ Now we add an explosion to the particle \ buffer at coordinates [R0, R1, R2], which \ are set to PLAYER_FRONT_Z tiles forwards \ from the camera coordinates, so that's \ just in front of the ship [when the ship \ is not close to the ground, in which case \ the explosion would be above the ship as \ the ship moves down the screen] LDR R0, [R11, #xCamera] \ Set R0 = xCamera MOV R1, #0 \ Set R1 = 0 LDR R2, [R11, #zCamera] \ Set R2 = zCamera - PLAYER_FRONT_Z SUB R2, R2, #PLAYER_FRONT_Z BL AddExplosionToBuffer \ Add an explosion into the particle data \ buffers LDMFD R13!, {PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; AddSparkCloudToBuffer \ Type; Subroutine \ Category; Particles \ Summary; An unused routine that adds a cloud of 150 spark particles to the \ particle data buffer, just in front of the player's ship \ Deep dive; Unused code in Lander \ \ ****************************************************************************** .AddSparkCloudToBuffer STMFD R13!, {R6-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R8, #150 \ Set R8 = 150 so the explosion contains 150 \ clusters of four particles \ Now we add an explosion to the particle \ buffer at coordinates [R0, R1, R2], which \ are set to PLAYER_FRONT_Z tiles forwards \ from the camera coordinates, so that's \ just in front of the ship [when the ship \ is not close to the ground, in which case \ the explosion would be above the ship as \ the ship moves down the screen] LDR R0, [R11, #xCamera] \ Set R0 = xCamera MOV R1, #0 \ Set R1 = 0 LDR R2, [R11, #zCamera] \ Set R2 = zCamera - PLAYER_FRONT_Z SUB R2, R2, #PLAYER_FRONT_Z .spcl1 BL AddSparkParticleToBuffer \ Add a spark particle to the particle data \ buffer [one that fades from white to red] SUBS R8, R8, #1 \ Decrement the particle counter in R8 BPL spcl1 \ Loop back until we have drawn all R8 \ particles LDMFD R13!, {R6-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; SpawnRock \ Type; Subroutine \ Category; Particles \ Summary; An unused routine that spawns a rock in the sky, at half the \ altitude of the rocks in the DropRocksFromTheSky routine \ Deep dive; Unused code in Lander \ \ ****************************************************************************** .SpawnRock STMFD R13!, {R6-R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved \ Drop a rock from the sky, spawning the \ rock at coordinates [R0, R1, R2], which \ are set to half the height of a normal \ rock and PLAYER_FRONT_Z tiles forwards \ from the camera coordinates, which is \ fairly high in the sky above the ship's \ current position and one tile in front of \ the ship LDR R0, [R11, #xCamera] \ Set R0 = xCamera MVN R1, #ROCK_HEIGHT / 2 \ Set R1 = ~ROCK_HEIGHT / 2 \ = -[ROCK_HEIGHT + 1] / 2 LDR R2, [R11, #zCamera] \ Set R2 = zCamera - PLAYER_FRONT_Z SUB R2, R2, #PLAYER_FRONT_Z BL DropARockFromTheSky \ Drop a rock from the coordinates in \ [R0, R1, R2] LDMFD R13!, {R6-R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; DropRocksFromTheSky \ Type; Subroutine \ Category; Particles \ Summary; If the score is 800 or more, then randomly drop rocks from the sky \ by spawning them as particles \ \ ****************************************************************************** .DropRocksFromTheSky LDR R4, [R11, #currentScore] \ Set R4 to the current score minus 800 SUBS R4, R4, #800 MOVMI PC, R14 \ If the result is negative then the current \ score is 800 or less, so return from the \ subroutine without dropping any rocks \ If we get here then the current score is \ greater than 800, so we randomly drop \ rocks from the sky STMFD R13!, {R14} \ Store the return address on the stack BL GetRandomNumbers \ Set R0 and R1 to random numbers MOV R0, R0, LSR #18 \ Scale R0 to the range 0 to 16383 CMP R0, R4 \ If R0 >= R4 then return from the LDMHSIA R13!, {PC} \ subroutine without dropping a rock, so the \ chances of a rock dropping from the sky on \ each iteration of the main loop increases \ with higher scores \ If we get here then we drop a rock from \ the sky, spawning the rock at coordinates \ [R0, R1, R2], which are set to ROCK_HEIGHT \ tiles above and PLAYER_FRONT_Z tiles \ forwards from the camera coordinates, \ which is very high in the sky above the \ ship's current position and one tile in \ front of the ship LDR R0, [R11, #xCamera] \ Set R0 = xCamera MVN R1, #ROCK_HEIGHT \ Set R1 = ~ROCK_HEIGHT \ = -[ROCK_HEIGHT + 1] LDR R2, [R11, #zCamera] \ Set R2 = zCamera - PLAYER_FRONT_Z SUB R2, R2, #PLAYER_FRONT_Z BL DropARockFromTheSky \ Drop a rock from the coordinates in \ [R0, R1, R2] by spawning it as a \ particle, albeit a very big particle with \ an associated 3D object LDMFD R13!, {PC} \ Return from the subroutine \ ****************************************************************************** \ \ Name; objectTypes \ Type; Variable \ Category; 3D objects \ Summary; A table that maps object types to object blueprints \ Deep dive; Placing objects on the map \ Object blueprints \ \ ****************************************************************************** .objectTypes EQUD objectPyramid \ 0 = pyramid [unused] EQUD objectSmallLeafyTree \ 1 = small leafy tree EQUD objectTallLeafyTree \ 2 = tall leafy tree EQUD objectSmallLeafyTree \ 3 = small leafy tree EQUD objectSmallLeafyTree \ 4 = small leafy tree EQUD objectGazebo \ 5 = gazebo EQUD objectTallLeafyTree \ 6 = tall leafy tree EQUD objectFirTree \ 7 = fir tree EQUD objectBuilding \ 8 = building EQUD objectRocket \ 9 = rocket EQUD objectRocket \ 10 = rocket EQUD objectRocket \ 11 = rocket EQUD objectRocket \ 12 = smoking but intact rocket [unused] EQUD objectSmokingRemainsRight \ 13 = smoking remains [bends to the right] EQUD objectSmokingRemainsLeft \ 14 = smoking remains [bends to the left] EQUD objectSmokingRemainsLeft \ 15 = smoking remains [bends to the left] EQUD objectSmokingRemainsLeft \ 16 = smoking remains [bends to the left] EQUD objectSmokingGazebo \ 17 = smoking remains of a gazebo EQUD objectSmokingRemainsRight \ 18 = smoking remains [bends to the right] EQUD objectSmokingRemainsRight \ 19 = smoking remains [bends to the right] EQUD objectSmokingBuilding \ 20 = smoking remains of a building EQUD objectSmokingRemainsRight \ 21 = smoking remains [bends to the right] EQUD objectSmokingRemainsLeft \ 22 = smoking remains [bends to the left] EQUD objectSmokingRemainsLeft \ 23 = smoking remains [bends to the left] EQUD objectSmokingRemainsLeft \ 24 = smoking remains [unused] \ ****************************************************************************** \ \ Name; DrawObjects [Part 1 of 3] \ Type; Subroutine \ Category; 3D objects \ Summary; Draw all the objects in the visible portion of the object map, \ starting by working our way through the map looking for objects \ Deep dive; Drawing 3D objects \ \ ****************************************************************************** .DrawObjects STMFD R13!, {R5-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved \ We are going to loop through every tile on \ the screen to check whether there are any \ corresponding objects in the object map, \ so we start by working out which entry in \ the object map corresponds to the far left \ corner at the back of the on-screen \ landscape view \ \ We work from back to front so that objects \ get drawn in that order, to ensure that \ distant objects always appear behind \ closer objects LDR R0, [R11, #xCamera] \ Set R0 to the x-coordinate of the camera, \ which is at the back of the landscape and \ in the middle AND R8, R0, #&FF000000 \ Zero the bottom three bytes of R0 and \ store in R8, so R8 contains the \ x-coordinate of the tile below the camera, \ in the middle of the landscape LDR R1, [R11, #zCamera] \ Set R1 to the z-coordinate of the camera AND R9, R1, #&FF000000 \ Zero the bottom three bytes of R1 and \ store in R9, so R9 contains the \ z-coordinate of the tile below the camera, \ at the back of the landscape SUB R8, R8, #LANDSCAPE_X \ Set xCameraTile = R8 - LANDSCAPE_X STR R8, [R11, #xCameraTile] \ \ We already rounded xCamera down to the \ nearest tile, so this moves us to the \ coordinate of the tile corner at the left \ end of the corner row, as subtracting the \ landscape offset effectively moves the \ camera to the left by that amount \ \ We store the result in xCameraTile, so \ this is set to the x-coordinate of the \ left end of the tile row \ \ As we only take the top byte of the \ x-coordinate when reading the object map, \ this moves us to the front-left corner of \ the on-screen landscape, so we can now \ work along the x-axis in the object map \ from left to right in the following loop \ We are now ready to loop through the \ object map, so we set up the loop counters \ for two loops - an outer loop with R7 as \ the counter to work through the z-axis \ from back to front, and an inner loop with \ R6 as the counter to work through the \ x-axis for each row from left to right MOV R7, #TILES_Z \ Set R7 = TILES_Z to act as a loop counter \ as we work our way along the tiles on the \ z-axis [i.e. from back to front on the \ screen] .dobs1 MOV R6, #TILES_X \ Set R6 = TILES_X to act as a loop counter \ as we work our way along the tiles on the \ x-axis [i.e. from left to right on the \ screen] LDR R8, [R11, #xCameraTile] \ Set R8 = xCameraTile, so R8 contains the \ x-coordinate of the leftmost tile of the \ visible landscape \ We now have the coordinates of the far \ left tile in [R8, R9], so we can start to \ iterate through the object map one row at \ a time, checking each tile to see if there \ is an object there [so we can draw it] .dobs2 ADD R14, R11, #objectMap \ Set R14 to the address of the object map ADD R14, R14, R8, LSR #24 \ Set R14 = R6 + [R8 >> 24] + [R9 >> 16] ADD R14, R14, R9, LSR #16 \ \ R8 is shifted into the bottom byte of R14, \ so that's the x-coordinate, and R9 is \ shifted into the second byte of R14, so \ that's the z-coordinate, so R14 points to \ the object map entry for coordinate \ [R8, R9] LDRB R0, [R14] \ Set R0 to the byte in the object map at \ the coordinate [R8, R9] CMP R0, #&FF \ If the object map entry is not &FF, then BNE dobs4 \ there is an object at this point, so jump \ to dobs4 to consider drawing the object .dobs3 ADD R8, R8, #TILE_SIZE \ Step along the x-axis by one whole tile, \ going from left to right SUBS R6, R6, #1 \ Decrement the x-axis loop counter in R6 BNE dobs2 \ Loop back to check the next tile until we \ have checked along the whole x-axis SUB R9, R9, #TILE_SIZE \ Step along the z-axis by one whole tile, \ going from back to front SUBS R7, R7, #1 \ Decrement the z-axis loop counter in R6 BNE dobs1 \ Loop back to check the next tile until we \ have checked along the whole z-axis LDMFD R13!, {R5-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; DrawObjects [Part 2 of 3] \ Type; Subroutine \ Category; 3D objects \ Summary; Draw the object that we have found on the object map \ Deep dive; Drawing 3D objects \ \ ****************************************************************************** .dobs4 \ If we get here then there is an object at \ [R8, R9] of type R0 STRB R0, [R11, #objectType] \ Store the object type in objectType ] typeOffset = P% + 8 - objectTypes : REM Set typeOffset to the offset back to the : REM objectTypes table from the next : REM instruction [ OPT pass% ADD R0, PC, R0, LSL #2 \ Set R14 to the address in entry R0 in the LDR R14, [R0, #-typeOffset] \ objectTypes table, which is the address of \ the blueprint for this object type STR R14, [R11, #objectData] \ Store the address of the blueprint in \ objectData BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates [R8, R9], which is where we \ are drawing our object MOV R1, R0 \ Make a copy of the altitude in R1, so R1 \ contains the y-coordinate of the object as \ it sits on the landscape CMP R0, #SEA_LEVEL \ If the object is on the sea, jump back to BEQ dobs3 \ dobs3 to move on to the next object, as \ there are no objects on the sea \ \ This check sounds unnecessary as there \ shouldn't be any objects in the sea in the \ object map anyway, as this check is also \ performed when populating the object map \ \ However, because the landscape in Lander \ is infinite but the object map is finite, \ the object map gets repeated every 256 \ tiles in each direction, and objects that \ appear on land in one instance of the \ object may well appear in the sea in \ another instance, so this check prevents \ that from happening LDR R14, [R11, #xCamera] \ Set R0 = R8 - xCamera SUB R0, R8, R14 \ = x - xCamera \ \ So R0 contains the x-coordinate of the \ object relative to the camera LDR R14, [R11, #zCamera] \ Set R2 = R9 - zCamera SUB R2, R9, R14 \ \ So R2 contains the z-coordinate of the \ object relative to the camera ADD R2, R2, #LANDSCAPE_Z \ Move the coordinate back by the landscape \ offset, so [R0, R2] contains the \ coordinate of the particle relative to the \ back-centre point of the landscape LDR R14, [R11, #yCamera] \ Set R1 = R1 - yCamera SUB R1, R1, R14 \ = y - yCamera \ \ So R1 contains the y-coordinate of the \ object relative to the camera LDRB R14, [R11, #objectType] \ If the object type is 12 or more, then it CMP R14, #12 \ represents a destroyed object, so jump to BHS dobs6 \ dobs6 .dobs5 ADD R3, R11, #rotationMatrix \ Set R3 to the address of the object's \ rotation matrix, to pass to DrawObject BL DrawObject \ Draw the object B dobs3 \ Jump back to dobs3 to move on to the next \ object \ ****************************************************************************** \ \ Name; DrawObjects [Part 3 of 3] \ Type; Subroutine \ Category; 3D objects \ Summary; Draw a destroyed object that we have found on the object map \ Deep dive; Drawing 3D objects \ \ ****************************************************************************** .dobs6 \ If we get here then this is a destroyed \ object LDR R14, [R11, #mainLoopCount] \ If either bit 0 or 1 of mainLoopCount are TST R14, #%00000011 \ set, jump to dobs5 to draw the object and BNE dobs5 \ skip the following \ We only get here on one out of every four \ iterations around the main loop STMFD R13!, {R0-R7, R9} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R14, [R11, #yCamera] \ Set R1 = R1 + yCamera ADD R1, R1, R14 \ = y + yCamera \ \ This reverts R1 to the altitude of the \ landscape at the object [as we subtracted \ yCamera from the altitude back in part 3] MOV R0, R8 \ Set [R0, R1, R2] = [x, y-SMOKE_HEIGHT, z] MOV R2, R9 \ SUB R1, R1, #SMOKE_HEIGHT \ So this coordinate is SMOKE_HEIGHT above \ the base of the object, or 3/4 of the tile \ size [as the y-axis points downwards], \ which is where we add our smoke particles, \ one on each iteration around the main loop BL AddSmokeParticleToBuffer \ Call AddSmokeParticleToBuffer to draw a \ smoke particle rising from the destroyed \ object LDMFD R13!, {R0-R7, R9} \ Retrieve the registers that we stored on \ the stack B dobs5 \ Jump to dobs5 to draw the object \ ****************************************************************************** \ \ Name; objectPlayerAddr \ Type; Variable \ Category; 3D objects \ Summary; The address of the object blueprint for the player's ship \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectPlayerAddr EQUD objectPlayer \ ****************************************************************************** \ \ Name; objectRockAddr \ Type; Variable \ Category; 3D objects \ Summary; The address of the object blueprint for a rock \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectRockAddr EQUD objectRock \ ****************************************************************************** \ \ Name; DrawObject [Part 1 of 5] \ Type; Subroutine \ Category; 3D objects \ Summary; Draw a 3D object and its shadow \ Deep dive; Drawing 3D objects \ Object blueprints \ Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] The 3D coordinate [x, y, z] of the object \ relative to the camera \ \ R3 The address of the object's rotation matrix \ \ ****************************************************************************** .DrawObject STMFD R13!, {R6-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R12, graphicsBufferEndAddr \ Set R12 to the address of the table that \ contains the end addresses of the graphics \ buffers, so when we draw the objects, we \ can add them as drawing commands onto the \ ends of the relevant graphics buffers \ \ In other words, R12 = graphicsBuffersEnd \ throughout the following ADD R4, R11, #xObject \ Set R4 to the address of xObject STMIA R4, {R0-R2} \ Store [R0, R1, R2] in the first three \ words of xObject, so we have; \ \ * xObject = x-coordinate of the object \ relative to the camera \ \ * yObject = y-coordinate of the object \ relative to the camera \ \ * zObject = z-coordinate of the object \ relative to the camera \ We now scale up the coordinate [x, y, z] \ in [R0, R1, R2] as high as it can go while \ still staying within 32-bit coordinates MOVS R4, R0 \ Set R4 = |R0| MVNMI R4, R4 \ = |x| \ \ If R0 is negative then R4 is actually set \ ~x, which is -[x+1], but it's close enough MOVS R5, R1 \ Set R5 = |R1| MVNMI R5, R5 \ = |y| \ \ If R1 is negative then R5 is actually set \ ~y, which is -[y+1], but it's close enough ORR R4, R4, R5 \ Set R4 = |R0| OR |R1| OR R2 ORR R4, R4, R2 \ = |x| OR |y| OR z ORRS R4, R4, #1 \ \ And round it up so it is non-zero \ \ So this sets R4 to a number that is \ greater than |x|, |y| and z, so R4 is \ therefore an upper bound on the values of \ all three coordinates MOVPL R4, R4, LSL #1 \ If R4 is positive then we know bit 7 is \ clear, so shift the sign bit off the end \ so we don't include it in the scale factor \ calculation below \ We now work out how many times we can \ scale up R4 so that it is as large as \ possible but still fits within a 32-bit \ word MOV R5, #0 \ Set R5 = 0 to use as the scale factor in \ the following loop STRB R5, [R11, #crashedFlag] \ Set crashedFlag = 0 to indicate that the \ object has not crashed [though we will \ change this later if we find that it has] .dobj1 MOVS R4, R4, LSL #1 \ Shift R4 to the left until the top bit is ADDPL R5, R5, #1 \ set, incrementing R5 for each shift so R5 BPL dobj1 \ contains the scale factor we have applied \ to R4 \ \ So this scales R4 as high as possible \ while still staying within 32 bits, with \ the scale factor given in R5 MOV R0, R0, LSL R5 \ We now scale up the coordinate [x, y, z] MOV R1, R1, LSL R5 \ in [R0, R1, R2] by the scale factor in R5, MOV R2, R2, LSL R5 \ as we know the result will stay within \ 32-bit words ADD R4, R11, #xObjectScaled \ Store [R0, R1, R2] in xObjectScaled, STMIA R4, {R0-R2} \ zObjectScaled and zObjectScaled ADD R4, R11, #rotationMatrix \ If R3 does not already point to the CMP R3, R4 \ rotation matrix at rotationMatrix, copy LDMNEIA R3!, {R0-R2} \ the nine-word matrix from the address in STMNEIA R4!, {R0-R2} \ R3 into rotationMatrix, so rotationMatrix LDMNEIA R3!, {R0-R2} \ now contains the object's rotation matrix, STMNEIA R4!, {R0-R2} \ which is made up of the three orientation LDMNEIA R3!, {R0-R2} \ vectors in; STMNEIA R4!, {R0-R2} \ \ [xNoseV, xRoofV, xSideV] \ [yNoseV, yRoofV, ySideV] \ [zNoseV, zRoofV, zSideV] \ So by this point we have; \ \ * The object's coordinate in; \ \ [xObject, yObject, zObject] \ \ * The scaled-up coordinate in; \ \ [xObjectScaled, yObjectScaled, \ zObjectScaled] \ \ * The object's rotation matrix at \ rotationMatrix, which is made up of \ the orientation vectors as follows; \ \ [xNoseV, xRoofV, xSideV] \ [yNoseV, yRoofV, ySideV] \ [zNoseV, zRoofV, zSideV] \ \ * crashedFlag = 0 \ \ * R12 = graphicsBuffersEnd \ \ We are now ready to move on to processing \ the object's vertices and faces \ ****************************************************************************** \ \ Name; DrawObject [Part 2 of 5] \ Type; Subroutine \ Category; 3D objects \ Summary; Process the object's vertices \ Deep dive; Drawing 3D objects \ Object blueprints \ Collisions and bullets \ \ ****************************************************************************** LDR R0, [R11, #objectData] \ Set R0 to the address of the blueprint \ for the object that is being drawn LDR R8, [R0] \ Set R8 to the first word of the blueprint, \ which contains the number of vertices ADD R9, R0, #16 \ Set R9 to the address of the vertices data \ in the blueprint, which appears from the \ 16th byte onwards ADD R10, R11, #vertexProjected \ Set R10 to the address of vertexProjected \ which is where we will store the screen \ coordinates of the projected vertices LDRB R1, [R0, #12] \ Set objectFlags to the fourth word of the STRB R1, [R11, #objectFlags] \ blueprint, which contains the object's \ flags \ We now iterate through the vertices in the \ blueprint, using R8 as a loop counter [as \ we set it above to the number of vertices] \ \ As we iterate through the vertices we \ rotate each one by the object's rotation \ matrix [to orientate the object properly] \ and project it onto the screen, saving the \ results in the vertexProjected table .dobj2 LDMIA R9!, {R2-R4} \ Load the coordinates of the next vertex \ from R9 into [R2, R3, R4], and update R9 \ to point to the vertex after that, ready \ for the next iteration ADD R0, R11, #xVertex \ Set R0 to the address of xVertex ADD R1, R11, #xVertexRotated \ Set R1 to the address of xVertexRotated STMIA R0, {R2-R4} \ Store the vertex coordinates in xVertex, \ so [xVertex, yVertex, zVertex] contains \ the vertex coordinates BL MultiplyVectorByMatrix \ If this object is a static object, then \ simply copy the vertex into xVertexRotated \ as follows; \ \ [xVertexRotated] [xVertex] \ [yVertexRotated] = [yVertex] \ [zVertexRotated] [zVertex] \ \ If this is a rotating object, then \ multiply the coordinates at R0 [i.e. the \ vertex coordinates at xVertex] by the \ rotation matrix in rotationMatrix, and \ store the results at R1 [i.e. the rotated \ coordinates at xVertexRotated]; \ \ [xVertexRotated] [xVertex] \ [yVertexRotated] = rotMatrix . [yVertex] \ [zVertexRotated] [zVertex] \ \ So this rotates the vertex coordinates by \ the object's rotation matrix ADD R0, R11, #xObject \ Set R0 to the address of xObject BL AddVectorToVertices \ Call AddVectorToVertices to set; \ \ [xCoord] [xObject] [xVertexRotated] \ [yCoord] = [yObject] + [yVertexRotated] \ [zCoord] [zObject] [zVertexRotated] \ \ So [xCoord, yCoord, zCoord] contains the \ coordinates of this vertex in 3D space, \ using the game's coordinate system ADD R0, R11, #xCoord \ Set R0 to the address of xCoord BL ProjectVertexOntoScreen \ Project [xCoord, yCoord, zCoord] onto the \ screen, returning the results in [R0, R1] STMIA R10!, {R0-R1} \ Store [R0, R1] in vertexProjected and \ update R10 to point to the next coordinate \ in vertexProjected, ready for us to add \ the shadow's projected vertex next ADD R0, R11, #xCoord \ Set R0 to the address of xCoord BL GetLandscapeBelowVertex \ Get the landscape altitude below the \ vertex and return it in R0 STR R0, [R11, #yCoord] \ Set yCoord = R0, so [xCoord, yCoord, \ zCoord] now contains the coordinate on the \ landscape directly below the vertex \ \ We use this coordinate for the object's \ shadow, which we store in vertexProjected \ just after the normally projected vertex ADD R0, R11, #xCoord \ Set R0 to the address of xCoord BL ProjectVertexOntoScreen \ Project [xCoord, yCoord, zCoord] onto the \ screen, returning the results in [R0, R1] STMIA R10!, {R0-R1} \ Store [R0, R1] in vertexProjected and \ update R10 to point to the next coordinate \ in vertexProjected, ready for the next \ iteration LDR R14, [R10, #-12] \ Set R14 to the second coordinate from the \ previous projection, i.e. the y-coordinate \ of the projected vertex [so that's the \ object rather than the shadow] CMP R14, R1 \ If R14 >= R1 then the y-coordinate of the MVNHS R14, #0 \ object is bigger than the y-coordinate of STRHSB R14, [R11, #crashedFlag] \ the shadow, which means the object is \ lower down the screen than its shadow \ \ This can only happen if the object has \ 'passed through' the ground, so set \ crashedFlag to &FF to indicate that the \ object has crashed SUBS R8, R8, #1 \ Decrement the loop counter, which keeps \ track of the number of vertices we have \ processed BNE dobj2 \ Loop back to dobj2 to move on to the next \ vertex, until we have processed them all \ We now move on to processing the object's \ face data so we can draw the visible faces \ ****************************************************************************** \ \ Name; DrawObject [Part 3 of 5] \ Type; Subroutine \ Category; 3D objects \ Summary; Calculate the visibility of each of the object's faces \ Deep dive; Drawing 3D objects \ Object blueprints \ \ ****************************************************************************** LDR R1, [R11, #objectData] \ Set R1 to the address of the blueprint \ for the object that is being drawn LDR R0, [R1, #8] \ Set R0 to the third word of the blueprint, \ which contains the offset from the start \ of the blueprint to the face data ADD R9, R1, R0 \ Set R9 to the address of the face data in \ the blueprint LDR R8, [R1, #4] \ Set R8 to the second word of the \ blueprint, which contains the number of \ faces \ We now iterate through the faces in the \ blueprint, using R8 as a loop counter [as \ we just set it to the number of faces] \ \ As we iterate through the faces we check \ their visibility, and draw the visible \ faces and [if applicable] their shadows \ into the graphics buffers .dobj3 MOV R0, R9 \ Copy the value of R9 into R0, so R0 points \ to the start of the current face data, \ which is where the face's normal vector is \ stored ADD R9, R9, #12 \ Add 12 to R9 so it points to the fourth \ word in the current face data, which is \ where the vertex numbers are stored ADD R1, R11, #xVertex \ Set R1 to the address of xVertex, so we \ store the results of the following \ calculation here BL MultiplyVectorByMatrix \ If this object is a static object, then \ simply copy the normal vector into xVertex \ as follows; \ \ [xVertex] [xNormal] \ [yVertex] = [yNormal] \ [zVertex] [zNormal] \ \ If this is a rotating object, then \ calculate the following, multiplying the \ vector in R0 by the rotation matrix in \ rotationMatrix; \ \ [xVertex] [xNormal] \ [yVertex] = rotationMatrix . [yNormal] \ [zVertex] [zNormal] \ \ So this rotates the normal vector in R0 by \ the object's rotation matrix, so the \ normal has now been rotated into the same \ frame of reference as the object in the 3D \ world, and we can check the orientation of \ that rotated normal to see if the face is \ visible LDR R1, [R11, #yVertex] \ Set R1 to yVertex, the y-coordinate of the \ rotated normal LDRB R14, [R11, #objectFlags] \ If bit 0 of objectFlags is zero then this TST R14, #%00000001 \ object is static and does not rotate, so MVNEQ R3, #0 \ set R1 and R3 to -1 so this face is always MVNEQ R1, #0 \ visible [as R3 is negative] and always \ casts a shadow in objects with shadows \ as [R1 is negative] ADDNE R2, R11, #xObjectScaled \ Otherwise bit 0 of objectFlags is set, so ADDNE R0, R11, #xVertex \ calculate the dot product of the vectors BLNE GetDotProduct \ at xObjectScaled and xVertex, storing the \ result in R3 \ \ The sign of the dot product depends on the \ angle between the two vectors, so R3 is; \ \ * Negative if angle < 90 degrees \ \ * Positive if angle >= 90 degrees \ \ The vector at xObjectScaled is the vector \ from the camera to the object, so we can \ see faces with normals that are less than \ 90 degrees off this vector, as they point \ towards the camera, while hidden faces \ point away from the camera at angles of \ more than 90 degrees \ \ So if R3 is positive, the face is facing \ away from us and is not visible, and if \ it's negative, the opposite is true STMFD R13!, {R8-R9, R11-R12} \ Store the registers that we want to use on \ the stack so they can be retrieved at \ dobj5 after the face has been processed \ ****************************************************************************** \ \ Name; DrawObject [Part 4 of 5] \ Type; Subroutine \ Category; 3D objects \ Summary; Draw the shadow for each of the object's faces \ Deep dive; Drawing 3D objects \ Object blueprints \ \ ****************************************************************************** CMP R1, #0 \ If R1 is positive, then the y-coordinate BPL dobj4 \ of the rotated normal vector is positive, \ which means it is pointing down, so jump \ to dobj4 to skip drawing the shadow for \ this face, as we only draw shadows for \ faces that point up, like the roof of the \ gazebo LDRB R14, [R11, #objectFlags] \ If bit 1 of objectFlags is zero then this TST R14, #%00000010 \ object is configured not to have a shadow, BEQ dobj4 \ so jump to dobj4 to skip the following \ We now draw the object's shadow into the \ graphics buffers STMFD R13!, {R3, R9, R11} \ Store the registers that we want to use on \ the stack so they can be preserved LDMIA R9, {R5-R7} \ Set R5, R6, R7 to the fourth, fifth and \ sixth words of the face data, which \ contain the numbers of the vertices that \ make up the face MOV R8, #0 \ Set R8 = 0, which we will pass to the \ DrawTriangleShadowToBuffer routine as the \ face colour, so the shadow is drawn in \ black ADD R10, R11, #vertexProjected \ Set R10 to the address of the third word ADD R10, R10, #8 \ in vertexProjected \ \ For each vertex we wrote two coordinates \ to vertexProjected, with the second being \ the shadow's projected coordinates, so \ this sets R10 to the address of the second \ projected coordinate's pixel x-coordinate, \ i.e. the pixel coordinate for the shadow \ of the first object vertex ADD R5, R10, R5, LSL #4 \ Set [R0, R1] to the two words at offset LDMIA R5, {R0-R1} \ R5 * 16 from the table at R10, which is \ the pixel coordinate of the shadow for the \ vertex number in R5, i.e. the first vertex \ in this face \ \ We multiply the vertex number by 16 as \ there are four words per vertex in \ vertexProjected, which is 16 bytes per \ face ADD R6, R10, R6, LSL #4 \ Set [R2, R3] to the two words at offset LDMIA R6, {R2-R3} \ R6 * 16 from the table at R10, which is \ the pixel coordinate of the shadow for the \ vertex number in R6, i.e. the second \ vertex in this face ADD R7, R10, R7, LSL #4 \ Set [R4, R5] to the two words at offset LDMIA R7, {R4-R5} \ R7 * 16 from the table at R10, which is \ the pixel coordinate of the shadow for the \ vertex number in R7, i.e. the third vertex \ in this face BL DrawTriangleShadowToBuffer \ Draw the triangle that shows the shadow \ for this face, which uses the vertices of \ the face, dropped directly down onto the \ landscape, all drawn in black LDMFD R13!, {R3, R9, R11} \ Retrieve the registers that we stored on \ the stack \ ****************************************************************************** \ \ Name; DrawObject [Part 5 of 5] \ Type; Subroutine \ Category; 3D objects \ Summary; Draw each of the object's faces \ Deep dive; Drawing 3D objects \ Object blueprints \ Screen memory in the Archimedes \ \ ****************************************************************************** .dobj4 CMP R3, #0 \ If R3 is positive then the face is facing BPL dobj5 \ away from us and isn't visible, so jump to \ dobj5 to skip drawing the face \ We now calculate the colour of the face, \ setting the brightness according to the \ direction that the face is pointing [the \ light source is directly above and \ slightly to the left] LDMIA R9, {R4-R7} \ Set R4, R5, R6, R7 to the fourth, fifth, \ sixth and seventh words of face data, \ which contain the numbers of the vertices \ that make up the face [in R4, R5 and R6], \ and the face colour [in R7] ADD R10, R11, #vertexProjected \ Set R10 to the address of vertexProjected \ \ For each vertex we wrote two coordinates \ to vertexProjected, with the first being \ the face's projected coordinates, so this \ sets R10 to the address of the first \ projected coordinate's pixel x-coordinate, \ i.e. the pixel coordinate for the first \ object vertex LDR R1, [R11, #yVertex] \ Set R1 to yVertex, the y-coordinate of the \ rotated normal LDR R0, [R11, #xVertex] \ Set R0 to xVertex, the x-coordinate of the \ rotated normal RSB R1, R1, #&80000000 \ Set R1 = [&80000000 - R1] >> 28 MOV R1, R1, LSR #28 \ = [&80000000 - yVertex] >> 28 \ \ So R1 is in the range 0 to 8 and is \ bigger when the normal is pointing more \ vertically [i.e. when the normal vector \ has a negative y-coordinate with a larger \ magnitude, as the y-axis points down the \ screen] \ \ So a bigger, more negative R1 means \ brighter colours CMP R0, #0 \ If R0 is negative then the normal is ADDMI R1, R1, #1 \ pointing more towards the left then the \ right, so increment R1 to make the face \ slightly brighter [as the light source is \ a little bit to the left] SUBS R1, R1, #5 \ Set R1 = max[0, R1 - 5] MOVMI R1, #0 \ \ So R1 is now between 0 and 3, and can be \ added to the colour numbers below to add \ the correct level of brightness MOV R8, #%00001111 \ Set R0 = bits 8-11 of the colour in R7, so AND R0, R8, R7, LSR #8 \ if the face colour is &rgb, this is &r AND R2, R8, R7, LSR #4 \ Set R2 = bits 4-7 of the colour in R7, so \ if the face colour is &rgb, this is &g AND R7, R7, R8 \ Set R7 = bits 0-3 of the colour in R7, so \ if the face colour is &rgb, this is &b ADD R0, R0, R1 \ Set R0 = R0 + R1 and clip to a maximum CMP R0, #16 \ value of 15, so this adds the face MOVHS R0, #15 \ brightness to the red channel and ensures \ the result fits into four bits ADD R2, R2, R1 \ Set R2 = R2 + R1 and clip to a maximum CMP R2, #16 \ value of %00001111, so this adds the face MOVHS R2, #15 \ brightness to the green channel and \ ensures the result fits into four bits ADD R7, R7, R1 \ Set R7 = R7 + R1 and clip to a maximum CMP R7, #16 \ value of %00001111, so this adds the face MOVHS R7, #15 \ brightness to the blue channel and ensures \ the result fits into four bits \ We now build a VIDC colour number in R8 \ by combining the three channels into one \ byte, which we then replicate four times \ to get a 32-bit colour number \ \ The byte is of the form; \ \ * Bit 7 = blue bit 3 \ * Bit 6 = green bit 3 \ * Bit 5 = green bit 2 \ * Bit 4 = red bit 3 \ * Bit 3 = blue bit 2 \ * Bit 2 = red bit 2 \ * Bit 1 = sum of red/green/blue bit 1 \ * Bit 0 = sum of red/green/blue bit 0 \ \ We now build this colour number in R8 from \ the red, green and blue values in R0, R2 \ and R7 ORR R8, R2, R7 \ Set R8 to the bottom three bits of; AND R8, R8, #%00000011 \ ORR R8, R8, R0 \ [the bottom two bits of R2 OR R7] OR R0 AND R8, R8, #%00000111 \ \ So this sets bits 0, 1 and 2 of R8 as \ required TST R0, #%00001000 \ If bit 3 of the red channel in R0 is set, ORRNE R8, R8, #%00010000 \ set bit 4 of R8 AND R2, R2, #%00001100 \ Clear all bits of the green channel in R2 \ except bits 2-3 ORR R8, R8, R2, LSL #3 \ And stick them into bits 5-6 of R8 TST R7, #%00000100 \ If bit 2 of the blue channel in R7 is set, ORRNE R8, R8, #%00001000 \ set bit 3 of R8 TST R7, #%00001000 \ If bit 3 of the blue channel in R7 is set, ORRNE R8, R8, #%10000000 \ set bit 7 of R8 ORR R8, R8, R8, LSL #8 \ Duplicate the lower byte of R8 into the ORR R8, R8, R8, LSL #16 \ other three bytes in the word to produce ORR R8, R8, R8, LSL #24 \ a four-pixel colour word containing four \ pixels of this colour \ Finally we can draw the face, using the \ colour in R8 ADD R4, R10, R4, LSL #4 \ Set [R0, R1] to the two words at offset LDMIA R4, {R0-R1} \ R4 * 16 from the table at R10, which is \ the pixel coordinate of the vertex number \ in R4, i.e. the first vertex in this face \ \ We multiply the vertex number by 16 as \ there are four words per vertex in \ vertexProjected, which is 16 bytes per \ face ADD R5, R10, R5, LSL #4 \ Set [R2, R3] to the two words at offset LDMIA R5, {R2-R3} \ R5 * 16 from the table at R10, which is \ the pixel coordinate of the vertex number \ in R5, i.e. the second vertex in this face ADD R6, R10, R6, LSL #4 \ Set [R4, R5] to the two words at offset LDMIA R6, {R4-R5} \ R6 * 16 from the table at R10, which is \ the pixel coordinate of the vertex number \ in R6, i.e. the third vertex in this face BL DrawTriangleToBuffer \ Draw the triangle for this face using the \ projected vertices for the face, drawn in \ the colour in R8, and draw it one buffer \ nearer the camera than the shadow, so the \ shadow never overlaps the object .dobj5 LDMFD R13!, {R8-R9, R11-R12} \ Retrieve the registers that we stored on \ the stack before processing the face ADD R9, R9, #16 \ Add 16 to R9 so it points to the next \ batch of face data [R9 already points to \ the fourth word in the current face data, \ so this skips the next four, thereby \ skipping all seven words of face data] SUBS R8, R8, #1 \ Decrement the loop counter, which keeps \ track of the number of faces we have \ processed BNE dobj3 \ Loop back to dobj3 to move on to the next \ face, until we have processed them all LDMFD R13!, {R6-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; PrintCurrentScore \ Type; Subroutine \ Category; Score bar \ Summary; Print the current score at the left end of the score bar \ \ ****************************************************************************** .PrintCurrentScore LDR R0, [R11, #currentScore] \ Set R0 to the current score ADD R1, R11, #stringBuffer \ Set R1 = stringBuffer to use as a string \ buffer for the following call MOV R2, #19 \ Set R2 = 19, to use as the size of the \ string buffer in the following call CMP R0, #1024 \ If R0 >= 1024, set gravity = &50000, so MOVHS R4, #&50000 \ gravity increases from the starting value STRHS R4, [R11, #gravity] \ of &30000 when we reach a score of 1024 CMP R0, #1488 \ If R0 >= 1488, set gravity = &70000, so MOVHS R4, #&70000 \ gravity increases again when we reach a STRHS R4, [R11, #gravity] \ score of 1488 SWI OS_BinaryToDecimal \ Convert the unsigned number in R0 to a \ string and store it in the buffer at R1 \ with a maximum string size of R2 MOV R0, #30 \ Print a VDU 30 command to move the text SWI OS_WriteC \ cursor to the top-left corner of the \ screen MOV R0, #&0A \ Print a line feed [ASCII &0A] to move the SWI OS_WriteC \ cursor down one line, to the start of the \ second line, which is where we print the \ score bar \ We now print the contents of the string \ buffer, which updates the number of \ remaining bullets on-screen .prsc1 LDRB R0, [R1], #1 \ Print the character in the buffer at R1, SWI OS_WriteC \ incrementing R1 as we go SUBS R2, R2, #1 \ Decrement the loop counter in R2 BNE prsc1 \ Loop back to print the next character from \ the buffer until we have printed all R2 of \ them MOV R0, #&20 \ Print two spaces [ASCII &20] to ensure we SWI OS_WriteC \ remove any characters from the previous SWI OS_WriteC \ bullet count MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; PrintScoreInBothBanks \ Type; Subroutine \ Category; Score bar \ Summary; Print a number at a specified text column in the score bar \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The unsigned number to print \ \ R1 Text column [x-coordinate] \ \ R2 Text row [y-coordinate] \ \ ****************************************************************************** .PrintScoreInBothBanks STMFD R13!, {R1-R2} \ Store the arguments in R1 and R2 on the \ stack so can retrieve them below ADD R1, R11, #stringBuffer \ Set R1 = stringBuffer to use as a string \ buffer for the following call MOV R2, #19 \ Set R2 = 19, to use as the size of the \ string buffer in the following call SWI OS_BinaryToDecimal \ Convert the unsigned number in R0 to a \ string and store it in the buffer at R1 \ with a maximum string size of R2 MOV R0, #31 \ Start printing the following VDU command; SWI OS_WriteC \ \ VDU 31, x, y \ \ which moves the text cursor to column x \ on row y LDMFD R13!, {R3-R4} \ Fetch R1 and R2 into R3 and R4 and write MOV R0, R3 \ them to complete the VDU 31 command, so SWI OS_WriteC \ this moves the text cursor to the position MOV R0, R4 \ defined in the subroutine arguments, SWI OS_WriteC \ leaving the text coordinates in [R3, R4] STMFD R13!, {R1-R2} \ Store R1 and R2 on the stack so we can \ preserve them through the following \ OS_Byte call MOV R0, #112 \ Set the VDU driver screen bank to bank 1 MOV R1, #1 SWI OS_Byte LDMFD R13, {R1-R2} \ Retrieve R1 and R2 from the stack, so R1 \ points to the string buffer and R2 is the \ buffer size \ \ Note that we don't update the stack \ pointer, so we can retrieve them again \ below .prsb1 LDRB R0, [R1], #1 \ Print the character in the buffer at R1, SWI OS_WriteC \ incrementing R1 as we go SUBS R2, R2, #1 \ Decrement the loop counter in R2 BNE prsb1 \ Loop back to print the next character from \ the buffer until we have printed all R2 of \ them MOV R0, #112 \ Set the hardware and VDU screen banks to MOV R1, #2 \ screen bank 1 SWI OS_Byte MOV R0, #31 \ Print the following VDU command; SWI OS_WriteC \ MOV R0, R3 \ VDU 31, R3, R4 SWI OS_WriteC \ MOV R0, R4 \ which moves the text cursor to column R3 SWI OS_WriteC \ on row R4, i.e. the same text coordinates \ as the text we printed in screen bank 1 \ above LDMFD R13!, {R1-R2} \ Retrieve R1 and R2 from the stack, so R1 \ points to the string buffer and R2 is the \ buffer size .prsb2 LDRB R0, [R1], #1 \ Print the character in the buffer at R1, SWI OS_WriteC \ incrementing R1 as we go SUBS R2, R2, #1 \ Decrement the loop counter in R2 BNE prsb2 \ Loop back to print the next character from \ the buffer until we have printed all R2 of \ them MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; fuelBarColour \ Type; Variable \ Category; Score bar \ Summary; A four-pixel colour word for the colour of the fuel bar \ \ ****************************************************************************** .fuelBarColour EQUD &37373737 \ ****************************************************************************** \ \ Name; sinTableAddr \ Type; Variable \ Category; Maths [Geometry] \ Summary; The address of the sine/cosine lookup table \ \ ****************************************************************************** .sinTableAddr EQUD sinTable \ ****************************************************************************** \ \ Name; arctanTableAddr \ Type; Variable \ Category; Maths [Geometry] \ Summary; The address of the arctan lookup table \ Deep dive; Flying by mouse \ \ ****************************************************************************** .arctanTableAddr EQUD arctanTable \ ****************************************************************************** \ \ Name; squareRootTableAddr \ Type; Variable \ Category; Maths [Arithmetic] \ Summary; The address of the square root lookup table \ Deep dive; Flying by mouse \ \ ****************************************************************************** .squareRootTableAddr EQUD squareRootTable \ ****************************************************************************** \ \ Name; DrawFuelLevel \ Type; Subroutine \ Category; Score bar \ Summary; Draw the bar at the top of the screen showing the current fuel \ level \ \ ****************************************************************************** .DrawFuelLevel STMFD R13!, {R10-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R1, [R11, #fuelLevel] \ Set R1 to the current fuel level in \ fuelLevel LDRB R2, [R11, #fuelBurnRate] \ Set R2 to the current fuel burn rate in \ fuelBurnRate SUBS R1, R1, R2 \ Subtract the fuel burn rate from the fuel MOVMI R1, #0 \ level, making sure it doesn't fall below STR R1, [R11, #fuelLevel] \ zero, and store the updated fuel level in \ fuelLevel [and leaving the value in R1] LDR R7, screenAddr \ Set R7 to the address of the screen bank \ we are drawing in, pointing just below \ the two lines of text at the top of the \ screen ADD R11, R7, #320 \ Set R11 to the address in R7, plus 320 to \ move it down by one pixel line LDR R8, fuelBarColour \ Set R8 to the four-pixel colour word that \ we need to poke into screen memory to draw \ four pixels in the correct colour for the \ fuel bar MOV R10, R1, LSR #4 \ Set R10 = R1 >> 4 \ = fuel level / 16 BL DrawHorizontalLine \ Draw a horizontal line of length R10, \ starting at screen address R11 and drawing \ to the right in the colour in R8, so \ that's the top pixel row of the bar ADD R11, R7, #2*320 \ Set R11 to the address in R7, plus 640 to \ move it down by two pixel lines LDR R8, fuelBarColour \ Set R8 to the four-pixel colour word that \ we need to poke into screen memory to draw \ four pixels in the correct colour for the \ fuel bar MOV R10, R1, LSR #4 \ Set R10 = R1 >> 4 \ = fuel level / 16 BL DrawHorizontalLine \ Draw a horizontal line of length R10, \ starting at screen address R11 and drawing \ to the right in the colour in R8, so \ that's the middle pixel row of the bar ADD R11, R7, #3*320 \ Set R11 to the address in R7, plus 960 to \ move it down by two pixel lines LDR R8, fuelBarColour \ Set R8 to the four-pixel colour word that \ we need to poke into screen memory to draw \ four pixels in the correct colour for the \ fuel bar MOV R10, R1, LSR #4 \ Set R10 = R1 >> 4 \ = fuel level / 16 BL DrawHorizontalLine \ Draw a horizontal line of length R10, \ starting at screen address R11 and drawing \ to the right in the colour in R8, so \ that's the bottom pixel row of the bar LDMFD R13!, {R10-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; graphicsBufferEndAddr \ Type; Variable \ Category; Graphics buffers \ Summary; The address of the table containing the end addresses of the \ graphics buffers \ Deep dive; Depth-sorting with the graphics buffers \ \ ****************************************************************************** .graphicsBufferEndAddr EQUD graphicsBuffersEnd \ ****************************************************************************** \ \ Name; graphicsBufferAddr \ Type; Variable \ Category; Graphics buffers \ Summary; The address of the table containing the addresses of the graphics \ buffers \ Deep dive; Depth-sorting with the graphics buffers \ \ ****************************************************************************** .graphicsBufferAddr EQUD graphicsBuffers \ ****************************************************************************** \ \ Name; MultiplyVectorByMatrix \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; Multiply a 3D vector by the rotation matrix in rotationMatrix, if \ the object is a rotating object \ \ ------------------------------------------------------------------------------ \ \ If the object currently being processed is static [i.e. bit 0 of objectFlags \ is clear], then this routine simply returns the vector in R0 unchanged. \ \ If the object is a rotating object, then this routine multiplies the vector at \ R0 by the rotation matrix at rotationMatrix and store the result at the \ address in R1; \ \ [ R1 ] [ R0 ] \ [ R1+4 ] = rotationMatrix . [ R0+4 ] \ [ R1+8 ] [ R0+8 ] \ \ [ xNoseV xRoofV xSideV ] [ R0 ] \ = [ yNoseV yRoofV ySideV ] . [ R0+4 ] \ [ zNoseV zRoofV zSideV ] [ R0+8 ] \ \ So this rotates a coordinate by the object's rotation matrix [i.e. by each \ of the object's orientation vectors]. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The address of the vector to multiply \ \ R1 The address to store the result \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ [R1 R1+4 R1+8] The result of the multiplication as a vector \ \ ****************************************************************************** .MultiplyVectorByMatrix LDRB R2, [R11, #objectFlags] \ If bit 0 of objectFlags is clear then this TST R2, #%00000001 \ object is static and does not rotate, so LDMEQIA R0, {R2-R4} \ simply set the following so we do not STMEQIA R1, {R2-R4} \ apply the rotation matrix; MOVEQ PC, R14 \ \ [ R1 ] [ R0 ] \ [ R1+4 ] = [ R0+4 ] \ [ R1+8 ] [ R0+8 ] \ \ and return from the subroutine STMFD R13!, {R6-R7, R14} \ Store the registers that we want to use on \ the stack so they can be preserved ADD R2, R11, #rotationMatrix \ Set R2 to the address of the rotation \ matrix at rotationMatrix, which contains \ the object's rotation matrix BL GetDotProduct \ Set R1 = R2 . R0, so; STR R3, [R1] \ BL GetDotProduct \ [R1 ] [ R2 R2+4 R2+8 ] [R0 ] STR R3, [R1, #4] \ [R1+4] = [ R2+12 R2+16 R2+20 ] . [R0+4] BL GetDotProduct \ [R1+8] [ R2+24 R2+28 R2+32 ] [R0+8] STR R3, [R1, #8] \ \ which is the result we want LDMFD R13!, {R6-R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; GetDotProduct \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; Calculate the dot product of two 3D vectors \ Deep dive; Drawing 3D objects \ \ ------------------------------------------------------------------------------ \ \ Calculate the dot product of the 3D vectors at R0 and R2 as follows; \ \ [ R0 ] [ R2 ] \ R3 = [ R0+4 ] . [ R2+4 ] = [R0 * R2] + [R0+4 * R2+4] + [R0+8 * R2*8] \ [ R0+8 ] [ R2+8 ] \ \ and set the flags according to the result. R2 is updated to the next vector in \ memory, so repeated calls will work through the following multiplication, \ returning one row at a time; \ \ [ R2 R2+4 R2+8 ] [ R0 ] \ [ R2+12 R2+16 R2+20 ] . [ R0+4 ] \ [ R2+24 R2+28 R2+32 ] [ R0+8 ] \ \ See the MultiplyVectorByMatrix routine to see this calculation in use. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The address of the first vector \ \ R2 The address of the second vector \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R2 Updated to the address of the next R2 vector \ \ R3 The dot product \ \ Flags Set according to the result in R3 \ \ ****************************************************************************** .GetDotProduct LDR R4, [R2], #4 \ Set R4 to the x-coordinate of R2 [let's \ call it xR2], and increment R2 to point to \ the y-coordinate of R2 LDR R5, [R0] \ Set R5 to the x-coordinate of R0 [let's \ call it xR0] \ We now calculate R3 = R4 * R5 using the \ shift-and-add multiplication algorithm EOR R7, R4, R5 \ Set the sign of the result in R7 TEQ R4, #0 \ Set R4 = 4 * |R4| RSBMI R4, R4, #0 MOVS R4, R4, LSL #2 TEQ R5, #0 \ Set R5 = 2 * |R5| RSBMI R5, R5, #0 MOV R5, R5, LSL #1 AND R4, R4, #&FE000000 \ Zero all but the top byte of R4, ensuring ORR R4, R4, #&01000000 \ that bit 0 of the top byte is set so the \ value of the fractional part is set to 0.5 MOV R3, #0 \ Set R3 = 0 to use for building the sum in \ our shift-and-add multiplication result .dotp1 MOV R5, R5, LSR #1 \ If bit 0 of R5 is set, add R5 to the ADDCS R3, R3, R5 \ result in R3, shifting R5 to the right MOVS R4, R4, LSL #1 \ Shift R4 left by one place BNE dotp1 \ Loop back if R4 is non-zero MOV R3, R3, LSR #1 \ Set R3 = R3 / 2 TEQ R7, #0 \ Apply the sign from R7 to R3 to get the RSBMI R3, R3, #0 \ final result, so; \ \ R3 = R4 * R5 \ = xR0 * xR2 LDR R4, [R2], #4 \ Set R4 to the y-coordinate at R2+4 [let's \ call it yR2], and increment R2 to point to \ the z-coordinate of R2 LDR R5, [R0, #4] \ Set R5 to the y-coordinate at R0+4 [let's \ call it yR0] EOR R7, R4, R5 \ Set R6 = R4 * R5 using the same algorithm TEQ R4, #0 \ as above RSBMI R4, R4, #0 \ MOVS R4, R4, LSL #2 \ This is the first part of the algorithm TEQ R5, #0 RSBMI R5, R5, #0 MOV R5, R5, LSL #1 AND R4, R4, #&FE000000 ORR R4, R4, #&01000000 MOV R6, #0 .dotp2 MOV R5, R5, LSR #1 \ This is the second part of the algorithm, ADDHS R6, R6, R5 \ so we now have; MOVS R4, R4, LSL #1 \ BNE dotp2 \ R6 = R4 * R5 MOV R6, R6, LSR #1 \ = yR0 * yR2 TEQ R7, #0 RSBMI R6, R6, #0 ADD R3, R3, R6 \ Set R3 = R3 + R6 \ = xR0 * xR2 + yR0 * yR2 LDR R4, [R2], #4 \ Set R4 to the z-coordinate at R2+8 [let's \ call it zR2], and increment R2 to point to \ the next coordinate at R2+12 LDR R5, [R0, #8] \ Set R5 to the z-coordinate at R0+8 [let's \ call it zR0] EOR R7, R4, R5 \ Set R6 = R4 * R5 using the same algorithm TEQ R4, #0 \ as above RSBMI R4, R4, #0 \ MOVS R4, R4, LSL #2 \ This is the first part of the algorithm TEQ R5, #0 RSBMI R5, R5, #0 MOV R5, R5, LSL #1 AND R4, R4, #&FE000000 ORR R4, R4, #&01000000 MOV R6, #0 .dotp3 MOV R5, R5, LSR #1 \ This is the second part of the algorithm, ADDHS R6, R6, R5 \ so we now have; MOVS R4, R4, LSL #1 \ BNE dotp3 \ R6 = R4 * R5 MOV R6, R6, LSR #1 \ = zR0 * zR2 TEQ R7, #0 RSBMI R6, R6, #0 ADDS R3, R3, R6 \ Set R3 = R3 + R6 \ = xR0 * xR2 + yR0 * yR2 + zR0 * zR2 \ \ which is the result we want \ \ We also set the flags depending on the \ result MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; TransposeRotationMatrix \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; An unused routine that transposes the rotation matrix \ Deep dive; Unused code in Lander \ \ ****************************************************************************** .TransposeRotationMatrix ADD R2, R11, #rotationMatrix \ Set R2 to the address of the rotation \ matrix LDR R0, [R2, #4] \ Transpose it by swapping coordinates on LDR R1, [R2, #12] \ either side of the diagonal from the STR R0, [R2, #12] \ top-left to bottom-right STR R1, [R2, #4] \ LDR R0, [R2, #8] \ [ x1 x2 x3 ] [ x1 y1 z1 ] LDR R1, [R2, #24] \ [ y1 y2 y3 ] -> [ x2 y2 z2 ] STR R0, [R2, #24] \ [ z1 z2 z3 ] [ x3 y3 z3 ] STR R1, [R2, #8] \ LDR R0, [R2, #20] \ This converts the rotation matrix into the LDR R1, [R2, #28] \ inverse rotation STR R0, [R2, #28] STR R1, [R2, #20] MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; AddVectorsWithFeedback \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; An unused routine that adds a delta vector to a coordinate and \ updates the delta with feedback from the coordinate value \ Deep dive; Unused code in Lander \ \ ****************************************************************************** .AddVectorsWithFeedback STMFD R13!, {R5-R6, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDMIA R0, {R2-R4} \ Fetch the coordinate into [R2, R3, R4] LDMIA R1, {R5-R6, R14} \ Fetch the delta vector into [R5, R6, R14] ADD R2, R2, R5, ASR #4 \ Set R2 = R2 + R5 / 16 \ \ So this adds a small amount of the x-delta \ to the x-coordinate SUB R5, R5, R2, ASR #4 \ Set R5 = R5 - R2 / 16 \ \ So this updates the x-delta with feedback \ from the x-coordinate ADD R3, R3, R6, ASR #4 \ Set R3 = R3 + R6 / 16 \ \ So this adds a small amount of the y-delta \ to the y-coordinate SUB R6, R6, R3, ASR #4 \ Set R6 = R6 - R3 / 16 \ \ So this updates the y-delta with feedback \ from the y-coordinate ADD R4, R4, R14, ASR #4 \ Set R4 = R4 + R14 / 16 \ \ So this adds a small amount of the z-delta \ to the z-coordinate SUB R14, R14, R4, ASR #4 \ Set R14 = R14 - R4 / 16 \ \ So this updates the z-delta with feedback \ from the z-coordinate STMIA R0, {R2-R4} \ Store the updated coordinate STMIA R1, {R5-R6, R14} \ Store the updated delta vector LDMFD R13!, {R5-R6, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; CalculateRotationMatrix \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; Calculate the rotation matrix \ Deep dive; Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ The rotation matrix is of the form; \ \ [ xNoseV xRoofV xSideV ] \ [ yNoseV yRoofV ySideV ] \ [ zNoseV zRoofV zSideV ] \ \ where the nose, roof and side vectors are the orientation vectors. \ \ This routine sets the rotation matrix to the following; \ \ [ cos[a] * cos[b] -sin[a] * cos[b] sin[b] ] \ [ sin[a] cos[a] 0 ] \ [ -cos[a] * sin[b] sin[a] * sin[b] cos[b] ] \ \ This matrix represents a combination of two rotations, one with angle a and \ the other with angle b. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The first rotation angle, a \ \ R1 The second rotation angle, b \ \ ****************************************************************************** .CalculateRotationMatrix STMFD R13!, {R0-R1, R14} \ Store the arguments and the return address \ on the stack ADD R0, R0, #&40000000 \ Set R0 = R0 + &40000000 \ = a + &40000000 \ \ As we shift R0 to the right by 20 places \ in the sine lookup below, this is the same \ as adding a value of &40000000 >> 20 to \ the index \ \ &40000000 >> 20 = 1024, so when we add \ this to the index, it skips 256 four-byte \ words compared to looking up R0 without \ the addition \ \ The sine table contains 1024 entries that \ cover the whole circle, so adding 256 to \ the index changes the lookup from sine to \ cosine, as it is effectively adding an \ extra 90-degrees to the lookup index LDR R14, sinTableAddr \ Set R14 to the address of the sine lookup \ table BIC R0, R0, #&00300000 \ Clear bits 21 and 22 of R0 so when we \ shift R0 to the right by 20 places in the \ following lookup, the address is aligned \ to the words in the lookup table LDR R2, [R14, R0, LSR #20] \ Set \ \ R2 = [2^31 - 1] \ * SIN[2 * PI * [[R0 >> 20] / 1024]] \ \ So R2 = sin[a + &40000000] \ = cos[a] ADD R1, R1, #&40000000 \ Using the same approach as above, set; LDR R14, sinTableAddr \ BIC R1, R1, #&00300000 \ R3 = sin[b + &40000000] LDR R3, [R14, R1, LSR #20] \ = cos[b] LDMFD R13!, {R4-R5} \ Set R4 and R5 to the original arguments in \ R0 and R1, so R4 = a and R5 = b LDR R14, sinTableAddr \ Using the same approach as above, set; BIC R4, R4, #&00300000 \ LDR R0, [R14, R4, LSR #20] \ R0 = sin[R4] \ = sin[a] LDR R14, sinTableAddr \ Using the same approach as above, set; BIC R5, R5, #&00300000 \ LDR R1, [R14, R5, LSR #20] \ R1 = sin[R4] \ = sin[b] STMFD R13!, {R0-R3} \ Store R0 to R3 on the stack, so the stack \ contains; \ \ R0 = sin[a] \ R1 = sin[b] \ R2 = cos[a] \ R3 = cos[b] \ We now calculate R4 = R2 * R3 using the \ shift-and-add multiplication algorithm EOR R14, R2, R3 \ Set the sign of the result in R14 TEQ R2, #0 \ Set R2 = 4 * |R2| RSBMI R2, R2, #0 MOVS R2, R2, LSL #2 TEQ R3, #0 \ Set R3 = 2 * |R3| RSBMI R3, R3, #0 MOV R3, R3, LSL #1 AND R2, R2, #&FE000000 \ Zero all but the top byte of R2, ensuring ORR R2, R2, #&01000000 \ that bit 0 of the top byte is set so the \ value of R2 is non-zero MOV R4, #0 \ Set R4 = 0 to use for building the sum in \ our shift-and-add multiplication result .rmat1 MOV R3, R3, LSR #1 \ If bit 0 of R3 is set, add R3 to the ADDHS R4, R4, R3 \ result in R4, shifting R3 to the right MOVS R2, R2, LSL #1 \ Shift R2 left by one place BNE rmat1 \ Loop back if R4 is non-zero MOV R4, R4, LSR #1 \ Set R4 = R4 / 2 TEQ R14, #0 \ Apply the sign from R14 to R4 to get the RSBMI R4, R4, #0 \ final result, so; \ \ R4 = R2 * R3 \ = cos[a] * cos[b] STR R4, [R11, #xNoseV] \ Store the result in xNoseV, so; \ \ xNoseV = cos[a] * cos[b] EOR R14, R0, R1 \ Set R4 = R0 * R1 using the same algorithm TEQ R0, #0 \ as above RSBMI R0, R0, #0 \ MOVS R0, R0, LSL #2 \ This is the first part of the algorithm TEQ R1, #0 RSBMI R1, R1, #0 MOV R1, R1, LSL #1 AND R0, R0, #&FE000000 ORR R0, R0, #&01000000 MOV R4, #0 .rmat2 MOV R1, R1, LSR #1 \ This is the second part of the algorithm, ADDHS R4, R4, R1 \ so we now have; MOVS R0, R0, LSL #1 \ BNE rmat2 \ R4 = R0 * R1 MOV R4, R4, LSR #1 \ = sin[a] * sin[b] TEQ R14, #0 RSBMI R4, R4, #0 STR R4, [R11, #zRoofV] \ Store the result in zRoofV, so; \ \ zRoofV = sin[a] * sin[b] LDMFD R13, {R0-R3} \ Retrieve R0 to R3 from the stack, leaving \ the stack pointer alone so we can repeat \ the process later, so R0 to R3 once again \ contain the following; \ \ R0 = sin[a] \ R1 = sin[b] \ R2 = cos[a] \ R3 = cos[b] EOR R14, R1, R2 \ Set R4 = R1 * R2 using the same algorithm TEQ R1, #0 \ as above RSBMI R1, R1, #0 \ MOVS R1, R1, LSL #2 \ This is the first part of the algorithm TEQ R2, #0 RSBMI R2, R2, #0 MOV R2, R2, LSL #1 AND R1, R1, #&FE000000 ORR R1, R1, #&01000000 MOV R4, #0 .rmat3 MOV R2, R2, LSR #1 \ This is the second part of the algorithm, ADDHS R4, R4, R2 \ so we now have; MOVS R1, R1, LSL #1 \ BNE rmat3 \ R4 = R1 * R2 MOV R4, R4, LSR #1 \ = sin[b] * cos[a] TEQ R14, #0 \ = cos[a] * sin[b] RSBMI R4, R4, #0 RSB R4, R4, #0 \ Negate the result and store it in zNoseV, STR R4, [R11, #zNoseV] \ so; \ \ zNoseV = -cos[a] * sin[b] EOR R14, R0, R3 \ Set R4 = R0 * R3 using the same algorithm TEQ R0, #0 \ as above RSBMI R0, R0, #0 \ MOVS R0, R0, LSL #2 \ This is the first part of the algorithm TEQ R3, #0 RSBMI R3, R3, #0 MOV R3, R3, LSL #1 AND R0, R0, #&FE000000 ORR R0, R0, #&01000000 MOV R4, #0 .rmat4 MOV R3, R3, LSR #1 \ This is the second part of the algorithm, ADDHS R4, R4, R3 \ so we now have; MOVS R0, R0, LSL #1 \ BNE rmat4 \ R4 = R0 * R3 MOV R4, R4, LSR #1 \ = sin[a] * cos[b] TEQ R14, #0 RSBMI R4, R4, #0 RSB R4, R4, #0 \ Negate the result and store it in xRoofV, STR R4, [R11, #xRoofV] \ so; \ \ xRoofV = -sin[a] * cos[b] LDMFD R13!, {R0-R3, R14} \ Retrieve R0 to R3 from the stack, so R0 to \ R3 once again contain the following; \ \ R0 = sin[a] \ R1 = sin[b] \ R2 = cos[a] \ R3 = cos[b] \ \ We also retrieve the subroutine return \ address into R14 STR R0, [R11, #yNoseV] \ Set yNoseV = sin[a] STR R1, [R11, #xSideV] \ Set xSideV = sin[b] STR R2, [R11, #yRoofV] \ Set yRoofV = cos[a] STR R3, [R11, #zSideV] \ Set zSideV = cos[b] MOV R0, #0 \ Set ySideV = 0 STR R0, [R11, #ySideV] MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; GetMouseInPolarCoordinates [Part 1 of 2] \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; Convert the mouse x- and y-coordinates into polar coordinates, \ starting by calculating the polar angle \ Deep dive; Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ This routine converts mouse coordinates [x, y] into polar coordinates, as in \ this example; \ \ .| \ R0 .´ | \ .´ | y \ .´R1 | \ +´-------+ \ x \ \ R1 is the angle and R0 is the distance. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The scaled-up mouse x-coordinate \ \ R1 The scaled-up mouse y-coordinate \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R0 The distance of the polar coordinate \ \ R1 The angle of the polar coordinate \ \ ****************************************************************************** .GetMouseInPolarCoordinates \ In the following, let's call the mouse \ x- and y-coordinates xMouse and yMouse STMFD R13!, {R14} \ Store the return address on the stack MOV R3, #0 \ Set R3 = 0 CMP R0, #0 \ If the mouse x-coordinate in R0 is EORMI R3, R3, #%00000011 \ negative, negate R0 to get |xMouse| and RSBMI R0, R0, #0 \ flip bits 0 and 1 of R3 CMP R1, #0 \ If the mouse y-coordinate in R1 is EORMI R3, R3, #%00000111 \ negative, negate R1 to get |yMouse| and RSBMI R1, R1, #0 \ flip bits 0, 1 and 2 of R3 STMFD R13!, {R0-R1} \ Store |xMouse| and |yMouse| on the stack \ We now divide one coordinate by the other, \ with the smaller value as the numerator, \ so the result is between 0 and 1 \ \ We do this so we can take the arctan of \ the result to calculate the angle in our \ polar coordinate CMP R0, R1 \ If |xMouse| < |yMouse|, flip bit 0 of R3 EORLO R3, R3, #%00000001 \ so we can make sure the angle is in the \ correct quadrant later BHS pole2 \ If |xMouse| >= |yMouse|, jump to pole2 to \ calculate the division with |yMouse| as \ the numerator \ If we get here then |xMouse| < |yMouse| \ so we calculate the division with |xMouse| \ as the numerator \ We now calculate R2 = R0 / R1 using the \ shift-and-subtract division algorithm MOV R2, #0 \ Set R2 = 0 to contain the result MOV R14, #%10000000 \ Set bit 7 of R14 so we can shift it to the \ right in each iteration, using it as a \ counter .pole1 MOVS R0, R0, LSL #1 \ Shift R0 left, moving the top bit into the \ C flag CMPCC R0, R1 \ If we shifted a 0 out of the top of R0, \ test for a possible subtraction SUBCS R0, R0, R1 \ If we shifted a 1 out of the top of R0 or ORRCS R2, R2, R14 \ R0 >= R1, then do the subtraction; \ \ R0 = R0 - R1 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R14 to the \ result in R2] MOVS R14, R14, LSR #1 \ Shift R14 to the right, moving bit 0 into \ the C flag BCC pole1 \ Loop back until we shift the 1 out of the \ right end of R14 [after eight shifts] MOVS R2, R2, LSL #24 \ Scale up the result, so now we have; \ \ R2 = 2^24 * [|xMouse| / |yMouse|] B pole4 \ Jump to pole4 to skip the following .pole2 \ If we get here then |xMouse| >= |yMouse| \ so we calculate the division with |yMouse| \ as the numerator MOV R2, #0 \ Set R2 = R1 / R0 using the same algorithm MOV R14, #&80 \ as above \ \ This is the first part of the algorithm .pole3 MOVS R1, R1, LSL #1 \ This is the second part of the algorithm, CMPLO R1, R0 \ so now we have; SUBHS R1, R1, R0 \ ORRHS R2, R2, R14 \ R2 = 2^24 * [|yMouse| / |xMouse|] MOVS R14, R14, LSR #1 BLO pole3 MOVS R2, R2, LSL #24 .pole4 \ At this point we have calculated one of \ the following, with the smaller coordinate \ on the top of the division; \ \ R2 = 2^24 * [|xMouse| / |yMouse|] \ \ or; \ \ R2 = 2^24 * [|yMouse| / |xMouse|] \ \ So R2 is in the range 0 to 2^24 LDR R14, arctanTableAddr \ Set R14 to the address of the arctan \ lookup table BIC R2, R2, #&01800000 \ Clear bits 23 and 24 of R2 so when we \ shift R2 to the right by 23 places in the \ following lookup, the address is aligned \ to the words in the lookup table LDR R1, [R14, R2, LSR #23] \ Look up the arctan to get the following; \ \ R1 = [[2^31 - 1] / PI] * ATAN[R2 / 128] \ \ So R1 contains the smaller of the two \ angles in the triangle formed by xMouse \ and yMouse, like this [in this example, \ xMouse >= yMouse, though the ASCII art \ might not make that obvious]; \ \ .| \ .´ | \ .´ | y \ .´R1 | \ +´-------+ \ x \ \ So we now have; \ \ R1 = arctan[|xMouse| / |yMouse|] \ \ or; \ \ R1 = arctan[|yMouse| / |xMouse|] TST R3, #%00000001 \ If bit 0 of R3 is set then; ADDEQ R1, R1, R3, LSL #29 \ ADDNE R3, R3, #1 \ R1 = R1 + R3 << 29 RSBNE R1, R1, R3, LSL #29 \ \ otherwise bit 0 of R3 is clear, so; \ \ R1 = [R3 + 1] << 29 - R1 \ \ So this moves the arctan angle into the \ correct circle quadrant, depending on the \ relative sizes and signs of xMouse and \ yMouse \ We now have the angle for our polar \ coordinate in R1, so next we need to \ calculate the distance \ ****************************************************************************** \ \ Name; GetMouseInPolarCoordinates [Part 2 of 2] \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; Calculate the polar distance \ Deep dive; Flying by mouse \ \ ****************************************************************************** LDMFD R13!, {R0, R2} \ Retrieve the values we put on the stack \ earlier, so; \ \ R0 = |xMouse| \ \ R2 = |yMouse| \ We now calculate R4 = R0 * R0 using the \ shift-and-add multiplication algorithm, \ which calculates; \ \ R4 = R0 ^ 2 \ = |xMouse| ^ 2 MOV R3, R0 \ Set R3 = 2 * R0 MOVS R3, R3, LSL #1 \ = 2 * |xMouse| \ \ So now we calculate R4 = R3 * R0 to get \ our result AND R3, R3, #&FE000000 \ Zero all but the top byte of R3, ensuring ORR R3, R3, #&01000000 \ that bit 0 of the top byte is set so the \ value of the fractional part is set to 0.5 MOV R4, #0 \ Set R4 = 0 to use for building the sum in \ our shift-and-add multiplication result .pole5 MOV R0, R0, LSR #1 \ If bit 0 of R0 is set, add R0 to the ADDHS R4, R4, R0 \ result in R4, shifting R0 to the right MOVS R3, R3, LSL #1 \ Shift R3 left by one place BNE pole5 \ Loop back if R3 is non-zero \ Next we calculate R14 = R2 * R2 using the \ shift-and-add multiplication algorithm, \ which calculates; \ \ R14 = R2 ^ 2 \ = |yMouse| ^ 2 MOV R3, R2 \ Set R3 = 2 * R2 MOVS R3, R3, LSL #1 \ = 2 * |yMouse| \ \ So now we calculate R14 = R3 * R2 to get \ our result AND R3, R3, #&FE000000 \ Zero all but the top byte of R3, ensuring ORR R3, R3, #&01000000 \ that bit 0 of the top byte is set so the \ value of the top byte is at least 1 MOV R14, #0 \ Set R14 = 0 to use for building the sum in \ our shift-and-add multiplication result .pole6 MOV R2, R2, LSR #1 \ If bit 0 of R2 is set, add R2 to the ADDHS R14, R14, R2 \ result in R14, shifting R2 to the right MOVS R3, R3, LSL #1 \ Shift R3 left by one place BNE pole6 \ Loop back if R3 is non-zero \ So by this point we have the following; \ \ R4 = |xMouse| ^ 2 \ \ R14 = |yMouse| ^ 2 \ \ So now we can apply Pythagoras to find the \ length of the hypotenuse, which is the \ distance in our polar coordinate, as in \ the example where x >= y; \ \ .| \ R0 .´ | \ .´ | y \ .´R1 | \ +´-------+ \ x \ \ We calculate the distance in R0 ADD R2, R14, R4 \ Set R2 = R14 + R4 \ \ = |xMouse| ^ 2 + |yMouse| ^ 2 LDR R14, squareRootTableAddr \ Set R14 to the address of the square root \ lookup table BIC R2, R2, #&00300000 \ Clear bits 20 and 21 of R2 so when we \ shift R2 to the right by 20 places in the \ following lookup, the address is aligned \ to the words in the lookup table LDR R0, [R14, R2, LSR #20] \ Set R0 = SQRT[R2] \ = SQRT[|xMouse| ^ 2 + |yMouse| ^ 2] \ \ which is the length of the hypotenuse and \ the distance in our polar coordinate LDMFD R13!, {PC} \ Return from the subroutine LDMIA R0, {R0-R2} \ This instruction appears to be unused \ ****************************************************************************** \ \ Name; ProjectParticleOntoScreen \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; Project a 3D particle coordinate onto the screen \ Deep dive; Particles and particle clouds \ Projecting onto the screen \ \ ------------------------------------------------------------------------------ \ \ This routine projects a 3D coordinate [x, y, z] into screen coordinates, as \ follows; \ \ screen x-coordinate = 160 + x / z \ \ screen y-coordinate = 64 + y / z \ \ The screen coordinate is deemed on-screen when it is within the following \ valid ranges; \ \ screen x is in the range 0 to 319 \ \ screen y is in the range 0 to 238 \ \ and is at less than a 45-degree viewing angle [i.e. within a viewing cone of \ 45 degrees]. \ \ The particle is deemed visible if its z-coordinate is less than &80000000, and \ is not projected if it is further away than this. \ \ The y-coordinate is the screen height [256 pixels] minus the two text lines \ that are reserved for the score bar at the top of the screen [16 pixels]. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinates in [x, y, z] \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ [R0, R1] Projected screen coordinates [x, y] \ \ C flag C flag is set if the particle is off-screen, \ clear if it is on-screen \ \ ****************************************************************************** .ProjectParticleOntoScreen CMN R2, #&80000000 \ If R2 + &80000000 produces a carry, then MOVCS PC, R14 \ the particle is too far away to be \ visible, so return from the subroutine \ with the C flag set MOVS R3, R0 \ Set R3 = |R0| RSBMI R3, R3, #0 \ = |x| MVNMI R5, R3 \ If R0 is negative, set R5 = ~R3 MOVPL R5, R3 \ If R0 is positive, set R5 = R3 MOVS R4, R1 \ Set R4 = |R1| RSBMI R4, R4, #0 \ = |y| MVNMI R6, R4 \ If R1 is negative, set R6 = ~R4 MOVPL R6, R4 \ If R1 is positive, set R6 = R4 ORR R5, R5, R6 \ Set R5 = R5 OR R6 OR R2 ORR R5, R5, R2 \ = |R0| OR |R1| OR R2 ORR R5, R5, #1 \ = |x| OR |y| OR z \ \ And round it up so it is non-zero \ \ So this sets R5 to a number that is \ greater than |x|, |y| and z, so R5 is \ therefore an upper bound on the values of \ all three coordinates \ We now work out how many times we can \ scale up R5 so that it is as large as \ possible but still fits within a 32-bit \ word MOV R6, #0 \ Set R6 = 0 to use as the scale factor in \ the following loop .ppar1 MOVS R5, R5, LSL #1 \ Shift R5 to the left until the top bit is ADDPL R6, R6, #1 \ set, incrementing R6 for each shift so R6 BPL ppar1 \ contains the scale factor we have applied \ to R5 \ \ So this scales R5 as high as possible \ while still staying within 32 bits, with \ the scale factor given in R6 MOV R2, R2, LSL R6 \ We now scale up the updated coordinate MOV R3, R3, LSL R6 \ [|x|, |y|, z] in [R3, R4, R2] by the scale MOV R4, R4, LSL R6 \ factor in R6, as we know the result will \ stay within 32-bit words CMP R3, R2 \ If R3 >= R2 or R4 >= R2, then at least one CMPCC R4, R2 \ of these is true; MOVCS PC, R14 \ \ |x| >= z or |y| >= z \ \ so return from the subroutine with C flag \ set to indicate the particle is off-screen \ \ This is the case when the particle has a \ greater viewing angle then 45 degrees in \ either direction, in which case we don't \ draw the particle on-screen STMFD R13!, {R7, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R7, R2 \ Set R7 = R2 \ = z \ \ where the value of z is scaled up \ We now calculate R6 = R4 / R7 using the \ shift-and-subtract algorithm MOV R6, #0 \ Set R6 = 0 to use for building the sum in \ our shift-and-subtract division result MOV R14, #&200 \ Set bit 9 in R14 to act as our shifted \ division bit, so we populate ten bits of \ the result .ppar2 MOVS R4, R4, LSL #1 \ Shift R4 left by one place CMPCC R4, R7 \ If we just shifted a zero out of R4, set \ the C flag if R4 >= R7 SUBCS R4, R4, R7 \ The numerator in R4 is bigger than the ORRCS R6, R6, R14 \ denominator in R7, so subtract the \ denominator from the numerator and set the \ corresponding bit in the result MOVS R14, R14, LSR #1 \ Shift R14 right by one place, shifting bit \ 0 into the C flag BCC ppar2 \ Loop back until we shift a 1 out of R14 MOVS R6, R6, LSL #22 \ Shift the result in R6 left as far as \ possible so it still fits in to 32 bits \ [as the result contains ten bits], so now \ we have; \ \ R6 = R4 / R7 \ = |y| / z \ \ where |y| and z are both scaled up by the \ same factor MOV R5, #0 \ Set R5 = R3 / R2 using the same algorithm MOV R14, #&200 \ as above \ \ This is the first part of the algorithm .ppar3 MOVS R3, R3, LSL #1 \ This is the second part of the algorithm, CMPCC R3, R2 \ so we now have; SUBCS R3, R3, R2 \ ORRCS R5, R5, R14 \ R5 = R3 / R2 MOVS R14, R14, LSR #1 \ = |x| / z BCC ppar3 \ MOVS R5, R5, LSL #22 \ where |x| and z are both scaled up by the \ same factor MOV R5, R5, LSR #24 \ Shift both division results down so we MOV R6, R6, LSR #24 \ only keep the top byte of each result TEQ R0, #0 \ If the original R0 argument was positive, ADDPL R0, R5, #160 \ set; RSBMI R0, R5, #160 \ \ R0 = 160 + R5 \ = 160 + |x| / z \ = 160 + x / z \ \ otherwise set; \ \ R0 = 160 - R5 \ = 160 - |x| / z \ = 160 + x / z \ \ So this sets R0 as follows, where x and z \ are the original particle's coordinates; \ \ R0 = 160 + x / z TEQ R1, #0 \ If the original R1 argument was positive, ADDPL R1, R6, #64 \ set; RSBMI R1, R6, #64 \ \ R1 = 64 + R6 \ = 64 + |y| / z \ = 64 + y / z \ \ otherwise set; \ \ R1 = 64 - R6 \ = 64 - |y| / z \ = 64 + y / z \ \ So this sets R1 as follows, where y and z \ are the original particle's coordinates; \ \ R1 = 64 + y / z CMP R0, #320 \ Set the C flag if either of these is true; CMPCC R1, #239 \ \ screen x-coordinate in R0 >= 320 \ \ screen y-coordinate in R1 >= 239 \ \ to indicate that the particle is \ off-screen LDMFD R13!, {R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; ProjectVertexOntoScreen \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; Project a vertex coordinate from a 3D object onto the screen \ Deep dive; Drawing 3D objects \ Drawing the landscape \ Projecting onto the screen \ \ ------------------------------------------------------------------------------ \ \ This routine projects a 3D coordinate [x, y, z] into screen coordinates, as \ follows; \ \ screen x-coordinate = 160 + x / z \ \ screen y-coordinate = 64 + y / z \ \ The vertex is deemed visible if its z-coordinate is less than &80000000, and \ is not projected if it is further away than this. \ \ This routine differs from the particle projection routine in that it projects \ vertices irrespective of their viewing angle. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1, R2] Particle coordinates in [x, y, z] \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ [R0, R1] Projected screen coordinates [x, y] \ \ C flag C flag is set if the vertex is too far away \ to draw, clear if it is close enough \ \ ****************************************************************************** .ProjectVertexOntoScreen LDMIA R0, {R0-R2} \ Fetch the coordinates we want to project \ from [R0, R1, R2], which we'll call \ [x, y, z] in the following CMN R2, #&80000000 \ If R2 + &80000000 produces a carry, then MOVCS PC, R14 \ the vertex is too far away to be visible, \ so return from the subroutine with the \ C flag set STMFD R13!, {R5-R7, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOVS R3, R0 \ Set R3 = |R0| RSBMI R3, R3, #0 \ = |x| MVNMI R5, R3 \ If R0 is negative, set R5 = ~R3 MOVPL R5, R3 \ If R0 is positive, set R5 = R3 MOVS R4, R1 \ Set R4 = |R1| RSBMI R4, R4, #0 \ = |y| MVNMI R6, R4 \ If R1 is negative, set R6 = ~R4 MOVPL R6, R4 \ If R1 is positive, set R6 = R4 ORR R5, R5, R6 \ Set R5 = R5 OR R6 OR R2 ORR R5, R5, R2 \ = |R0| OR |R1| OR R2 ORR R5, R5, #1 \ = |x| OR |y| OR z \ \ And round it up so it is non-zero \ \ So this sets R5 to a number that is \ greater than |x|, |y| and z, so R5 is \ therefore an upper bound on the values of \ all three coordinates \ We now work out how many times we can \ scale up R5 so that it is as large as \ possible but still fits within a 32-bit \ word MOV R6, #0 \ Set R6 = 0 to use as the scale factor in \ the following loop .pver1 MOVS R5, R5, LSL #1 \ Shift R5 to the left until the top bit is ADDPL R6, R6, #1 \ set, incrementing R6 for each shift so R6 BPL pver1 \ contains the scale factor we have applied \ to R5 \ \ So this scales R5 as high as possible \ while still staying within 32 bits, with \ the scale factor given in R6 MOV R2, R2, LSL R6 \ We now scale up the updated coordinate MOV R3, R3, LSL R6 \ [|x|, |y|, z] in [R3, R4, R2] by the scale MOV R4, R4, LSL R6 \ factor in R6, as we know the result will \ stay within 32-bit words CMP R2, R3 \ If R2 < R3 or R2 < R4, then at least one CMPHS R2, R4 \ of these is true; BLO pver4 \ \ |x| > z or |y| > z \ \ so jump to pver4 to deal with this special \ case, when the vertex has a greater \ viewing angle then 45 degrees in either \ direction MOV R7, R2 \ Set R7 = R2 \ = z \ \ where the value of z is scaled up \ We now calculate R6 = R4 / R7 using the \ shift-and-subtract algorithm MOV R6, #0 \ Set R6 = 0 to use for building the sum in \ our shift-and-subtract division result MOV R14, #&100 \ Set bit 8 in R14 to act as our shifted \ division bit, so we populate nine bits of \ the result .pver2 MOVS R4, R4, LSL #1 \ Shift R4 left by one place CMPCC R4, R7 \ If we just shifted a zero out of R4, set \ the C flag if R4 >= R7 SUBCS R4, R4, R7 \ The numerator in R4 is bigger than the ORRCS R6, R6, R14 \ denominator in R7, so subtract the \ denominator from the numerator and set the \ corresponding bit in the result MOVS R14, R14, LSR #1 \ Shift R14 right by one place, shifting bit \ 0 into the C flag BCC pver2 \ Loop back until we shift a 1 out of R14 MOVS R6, R6, LSL #23 \ Shift the result in R6 left as far as \ possible so it still fits in to 32 bits, \ [as the result contains nine bits], so \ now we have; \ \ R6 = R4 / R7 \ = |y| / z \ \ where |y| and z are both scaled up by the \ same factor MOV R5, #0 \ Set R5 = R3 / R2 using the same algorithm MOV R14, #&200 \ as above, but populating ten bits in the \ result \ \ This is the first part of the algorithm .pver3 MOVS R3, R3, LSL #1 \ This is the second part of the algorithm, CMPCC R3, R2 \ so we now have; SUBCS R3, R3, R2 \ ORRCS R5, R5, R14 \ R5 = R3 / R2 MOVS R14, R14, LSR #1 \ = |x| / z BCC pver3 \ MOVS R5, R5, LSL #22 \ where |x| and z are both scaled up by the \ same factor MOV R5, R5, LSR #24 \ Shift both division results down so we MOV R6, R6, LSR #24 \ only keep the top byte of each result TEQ R0, #0 \ If the original R0 argument was positive, ADDPL R0, R5, #160 \ set; RSBMI R0, R5, #160 \ \ R0 = 160 + R5 \ = 160 + |x| / z \ = 160 + x / z \ \ otherwise set; \ \ R0 = 160 - R5 \ = 160 - |x| / z \ = 160 + x / z \ \ So this sets R0 as follows, where x and z \ are the original particle's coordinates; \ \ R0 = 160 + x / z TEQ R1, #0 \ If the original R1 argument was positive, ADDPL R1, R6, #64 \ set; RSBMI R1, R6, #64 \ \ R1 = 64 + R6 \ = 64 + |y| / z \ = 64 + y / z \ \ otherwise set; \ \ R1 = 64 - R6 \ = 64 - |y| / z \ = 64 + y / z \ \ So this sets R1 as follows, where y and z \ are the original particle's coordinates; \ \ R1 = 64 + y / z CMN R0, #0 \ Clear the C flag to indicate that the \ vertex is on-screen [this CMN instruction \ is a hacky way of clearing the C flag] LDMFD R13!, {R5-R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine .pver4 \ If we get here then; \ \ |x| > z or |y| > z \ \ So the vertex has a greater viewing angle \ then 45 degrees in either direction \ \ The following registers are also set; \ \ [R3, R4, R2] = [|x|, |y|, z] MOV R5, #24 \ Set R5 = 24 CMP R3, R4 \ Set R6 = R3 / 2 MOVHS R6, R3, LSR #1 \ = |x| / 2 MOVLO R6, R3, LSR #1 \ \ The comparison seems to be unnecessary as \ both conditions do the same thing [if the \ shifts used the C flag then this would \ make more sense, but LSR overwrites the C \ flag rather than using it] .pver5 CMP R2, R6 \ Shift R6 to the right and increment R5 ADDLO R5, R5, #1 \ until R2 >= R6, so this scales R6 down, MOV R6, R6, LSR #1 \ counting the scale factor in R5 BLO pver5 \ \ Specifically, this scales |x| / 2 down \ until it is smaller than z MOV R2, R2, LSR #23 \ We now scale down the updated coordinate MOV R3, R3, LSR R5 \ [|x|, |y|, z] in [R3, R4, R2] by the scale MOV R4, R4, LSR R5 \ factor in R5 [for x and y] and by 23 \ places [for z] MOV R7, R2 \ Set R7 = R2 STMFD R13!, {R5} \ Store the scale factor in R5 on the stack \ We now calculate R6 = R4 / R7 using the \ shift-and-subtract algorithm, scaling both \ the numerator and denominator up by 24 \ places before doing the division MOV R6, #0 \ Set R6 = 0 to use for building the sum in \ our shift-and-subtract division result MOV R14, #&80 \ Set bit 7 in R14 to act as our shifted \ division bit, so we populate eight bits of \ the result MOV R4, R4, LSL #24 \ Scale the numerator and denominator up by MOV R7, R7, LSL #24 \ 24 places to ensure more accuracy .pver6 MOVS R4, R4, LSL #1 \ Shift R4 left by one place CMPCC R4, R7 \ If we just shifted a zero out of R4, set \ the C flag if R4 >= R7 SUBCS R4, R4, R7 \ The numerator in R4 is bigger than the ORRCS R6, R6, R14 \ denominator in R7, so subtract the \ denominator from the numerator and set the \ corresponding bit in the result MOVS R14, R14, LSR #1 \ Shift R14 right by one place, shifting bit \ 0 into the C flag BCC pver6 \ Loop back until we shift a 1 out of R14 MOVS R6, R6, LSL #24 \ Shift the result in R6 left as far as \ possible so it still fits in to 32 bits, \ [as the result contains eight bits], so \ now we have; \ \ R6 = R4 / R7 \ = |y| / z \ \ where |y| and z are both scaled up by the \ same factor MOV R5, #0 \ Set R5 = R3 / R2 using the same algorithm MOV R14, #&200 \ as above, but populating ten bits in the MOV R3, R3, LSL #22 \ result MOV R2, R2, LSL #22 \ \ This is the first part of the algorithm .pver7 MOVS R3, R3, LSL #1 \ This is the second part of the algorithm, CMPCC R3, R2 \ so we now have; SUBCS R3, R3, R2 \ ORRCS R5, R5, R14 \ R5 = R3 / R2 MOVS R14, R14, LSR #1 \ = |x| / z BCC pver7 \ MOVS R5, R5, LSL #22 \ where |x| and z are both scaled up by the \ same factor LDMFD R13!, {R14} \ Fetch the scale factor that we stored on \ the stack into R14 SUB R14, R14, #23 \ I have no idea what this does, as it RSB R14, R14, #23 \ appears to have no effect at all MOV R5, R5, LSR R14 \ Apply the scale factor in R14 to R5 MOV R6, R6, LSR R14 \ Apply the scale factor in R14+1 to R6 MOV R6, R6, LSR #1 TEQ R0, #0 \ If the original R0 argument was positive, ADDPL R0, R5, #160 \ set; RSBMI R0, R5, #160 \ \ R0 = 160 + R5 \ = 160 + |x| / z \ = 160 + x / z \ \ otherwise set; \ \ R0 = 160 - R5 \ = 160 - |x| / z \ = 160 + x / z \ \ So this sets R0 as follows, where x and z \ are the original particle's coordinates; \ \ R0 = 160 + x / z TEQ R1, #0 \ If the original R1 argument was positive, ADDPL R1, R6, #64 \ set; RSBMI R1, R6, #64 \ \ R1 = 64 + R6 \ = 64 + |y| / z \ = 64 + y / z \ \ otherwise set; \ \ R1 = 64 - R6 \ = 64 - |y| / z \ = 64 + y / z \ \ So this sets R1 as follows, where y and z \ are the original particle's coordinates; \ \ R1 = 64 + y / z CMN R0, #0 \ Clear the C flag to indicate that the \ vertex is on-screen [this CMN instruction \ is a hacky way of clearing the C flag] LDMFD R13!, {R5-R7, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; AddVectors \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; An unused routine that adds two vectors and uses self-modifying \ code to store the results in a specified location \ Deep dive; Unused code in Lander \ \ ****************************************************************************** .vmod1 ADD R4, R11, #xVertexRotated \ Modification code to store the result of \ the vector addition in xVertexRotated .vmod2 ADD R4, R11, #xCoord \ Modification code to restore the result of \ the vector addition to xCoord .vmod3 MOV R4, R12 \ Modification code to store the result of \ the vector addition in R12 .vmod4 ADD R4, R11, #xLandscapeCol \ Modification code to store the result of \ the vector addition in xLandscapeCol .vmod5 ADD R4, R11, #xLandscapeRow \ Modification code to store the result of \ the vector addition in xLandscapeRow .AddVectors STMFD R13!, {R14} \ Store the return address on the stack \ We now modify the following instruction in \ the AddVectorToVertices routine to change \ the way it works; \ \ ADD R3, R11, #xCoord \ \ Interestingly, though, none of the \ modifications above will work properly, as \ they all change R4 rather than R3, so \ presumably the modified subroutine was \ refactored and this unused routine wasn't \ updated \ \ If this routine worked, it would use the \ modifications to change the address where \ we stored the result LDR R1, vmod3 \ Modify the eighth instruction in the STR R1, AddVectorToVertices+28 \ AddVectorToVertices routine to the \ instruction in vmod3 LDMIA R0, {R0-R2} \ Fetch the coordinate from R0 into \ [R0, R1, R2] MOV R3, R12 \ Set R3 = R12 to use as the address of the \ second vector to add BL AddVectorToVertices+8 \ Add the vector [R0, R1, R2] to the vector \ at the address in R3 [by skipping the \ first two instructions in the modified \ routine LDR R1, vmod2 \ Revert the eighth instruction in the STR R1, AddVectorToVertices+28 \ AddVectorToVertices routine to the \ instruction in vmod2 [though note that \ this doesn't actually revert the \ instruction, it corrupts it, for the \ reasons noted above] LDMFD R13!, {PC} \ Return from the subroutine \ ****************************************************************************** \ \ Name; AddVectorToVertices \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; Add a vector to the rotated vertex coordinates to get the vertex \ coordinates in 3D space \ \ ------------------------------------------------------------------------------ \ \ Calculate the following; \ \ [ xCoord ] [ R0 ] + [ xVertexRotated ] \ [ yCoord ] = [ R0+4 ] + [ yVertexRotated ] \ [ zCoord ] [ R0+8 ] + [ zVertexRotated ] \ \ This routine is only ever called with R0 = xObject, so this adds the rotated \ vertex coordinate to the object's coordinate, giving the vertex's coordinate \ in the game's coordinate system. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The address of the 3D vector to add in the \ above calculation \ \ ------------------------------------------------------------------------------ \ \ Other entry points; \ \ AddVectorToVertices+8 Enter the routine with R0 to R3 already set \ \ ****************************************************************************** .AddVectorToVertices LDMIA R0, {R0-R2} \ Fetch the coordinate from R0 into \ [R0, R1, R2] ADD R3, R11, #xVertexRotated \ Set R3 to the address of the coordinates \ in xVertexRotated STMFD R13!, {R5-R6} \ Store the registers that we want to use on \ the stack so they can be preserved LDMIA R3, {R3-R5} \ Fetch the vector at xVertexRotated into \ [R3, R4, R5] ADD R0, R0, R3 \ Set R0 = R0 + R3 \ = R0 + xVertexRotated ADD R1, R1, R4 \ Set R1 = R1 + R4 \ = R0+4 + yVertexRotated ADD R2, R2, R5 \ Set R2 = R2 + R5 \ = R0+8 + zVertexRotated ADD R3, R11, #xCoord \ Store [R0, R1, R2] in [xCoord, yCoord, STMIA R3, {R0-R2} \ zCoord] LDMFD R13!, {R5-R6} \ Retrieve the registers that we stored on \ the stack MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; MultiplyVectorByConstant \ Type; Subroutine \ Category; Maths [Geometry] \ Summary; An unused routine that multiplies a vector by a constant value \ Deep dive; Unused code in Lander \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The constant value to multiply the vector by \ \ R1 The address of the three-word vector, plus a \ counter word \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ xVertexRotated The result of the multiplication, followed \ by the incremented counter \ \ ****************************************************************************** .MultiplyVectorByConstant STMFD R13!, {R5-R8, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDMIA R1, {R1-R3, R14} \ Fetch R1, R2, R3 and R14 from address R1 MOV R8, R0 \ Set R8 = R0 \ We now calculate R5 = R8 * R1 using the \ shift-and-add multiplication algorithm EOR R4, R8, R1 \ Set the sign of the result in R4 TEQ R8, #0 \ Set R8 = 4 * |R8| RSBMI R8, R8, #0 MOVS R8, R8, LSL #2 TEQ R1, #0 \ Set R1 = 2 * |R1| RSBMI R1, R1, #0 MOV R1, R1, LSL #1 AND R8, R8, #&FE000000 \ Zero all but the top byte of R8, ensuring ORR R8, R8, #&01000000 \ that bit 0 of the top byte is set so the \ value of the fractional part is set to 0.5 MOV R5, #0 \ Set R5 = 0 to use for building the sum in \ our shift-and-add multiplication result .vcon1 MOV R1, R1, LSR #1 \ If bit 0 of R1 is set, add R1 to the ADDCS R5, R5, R1 \ result in R5, shifting R1 to the right MOVS R8, R8, LSL #1 \ Shift R8 left by one place BNE vcon1 \ Loop back if R8 is non-zero MOV R5, R5, LSR #1 \ Set R5 = R5 / 2 TEQ R4, #0 \ Apply the sign from R4 to R5 to get the RSBMI R5, R5, #0 \ final result, so; \ \ R5 = R8 * R1 \ = R0 * R1 MOV R8, R0 \ Set R8 = R0 EOR R4, R8, R2 \ Set R6 = R8 * R2 using the same algorithm TEQ R8, #0 \ as above RSBMI R8, R8, #0 \ MOVS R8, R8, LSL #2 \ This is the first part of the algorithm TEQ R2, #0 RSBMI R2, R2, #0 MOV R2, R2, LSL #1 AND R8, R8, #&FE000000 ORR R8, R8, #&01000000 MOV R6, #0 .vcon2 MOV R2, R2, LSR #1 \ This is the second part of the algorithm, ADDCS R6, R6, R2 \ so we now have; MOVS R8, R8, LSL #1 \ BNE vcon2 \ R4 = R8 * R2 MOV R6, R6, LSR #1 \ = R0 * R2 TEQ R4, #0 RSBMI R6, R6, #0 MOV R8, R0 \ Set R8 = R0 EOR R4, R8, R3 \ Set R7 = R8 * R3 using the same algorithm TEQ R8, #0 \ as above RSBMI R8, R8, #0 \ MOVS R8, R8, LSL #2 \ This is the first part of the algorithm TEQ R3, #0 RSBMI R3, R3, #0 MOV R3, R3, LSL #1 AND R8, R8, #&FE000000 ORR R8, R8, #&01000000 MOV R7, #0 .vcon3 MOV R3, R3, LSR #1 \ This is the second part of the algorithm, ADDCS R7, R7, R3 \ so we now have; MOVS R8, R8, LSL #1 \ BNE vcon3 \ R7 = R8 * R3 MOV R7, R7, LSR #1 \ = R0 * R3 TEQ R4, #0 RSBMI R7, R7, #0 ADD R14, R14, #2 \ Set R14 = R14 + 2 ADD R8, R11, #xVertexRotated \ Store [R5, R6, R7] and the updated R14 in STMIA R8, {R5-R7, R14} \ xVertexRotated LDMFD R13!, {R5-R8, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; randomSeed1 \ Type; Variable \ Category; Maths [Arithmetic] \ Summary; The first random seed for the random number generator \ \ ****************************************************************************** .randomSeed1 EQUD &4F9C3490 \ ****************************************************************************** \ \ Name; randomSeed2 \ Type; Variable \ Category; Maths [Arithmetic] \ Summary; The second random seed for the random number generator \ \ ****************************************************************************** .randomSeed2 EQUD &DA0383CF \ ****************************************************************************** \ \ Name; GetRandomNumbers \ Type; Subroutine \ Category; Maths [Arithmetic] \ Summary; Generate pseudo-random numbers from the random number seeds \ Deep dive; Random numbers \ \ ------------------------------------------------------------------------------ \ \ This algorithm appears in the original 1986 ARM Assembler manual that came \ with Acorn's ARM Evaluation System [it's in section 11.2 on page 96]. It also \ appears in later Acorn manuals, such as Acorn Desktop Assembler [release 2], \ where it's on page 186. \ \ Here's the algorithm's description from the manual; \ \ 'It is often necessary to generate [pseudo-]random numbers and the most \ efficient algorithms are based on shift generators with exclusive-or feedback \ rather like a cyclic redundancy check generator. Unfortunately the sequence of \ a 32-bit generator needs more than one feedback tap to be maximal length [that \ is 2^32-1 cycles before repetition]. A 33-bit shift generator with taps at \ bits 20 and 33 is required. The basic algorithm is newbit ;= bit33 eor bit20, \ shift left the 33 bit number and put in newbit at the bottom. Then do this for \ all the newbits needed, that is 32 of them. Luckily this can all be done in \ five S cycles.' \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R0 A random number \ \ R1 A random number \ \ ****************************************************************************** .GetRandomNumbers STMFD R13!, {R14} \ Store the return address on the stack LDR R0, randomSeed1 \ Set R0 = randomSeed1 LDR R1, randomSeed2 \ Set R1 = randomSeed2 TST R1, R1, LSR #1 \ Set flags on R1 AND [R1 >> 1] MOVS R14, R0, RRX \ Rotate R0 right, incorporating the C flag, \ and store the result in R14, replacing the \ C flag with bit 0 from R0 ADC R1, R1, R1 \ R1 = R1 + R1 + C EOR R14, R14, R0, LSL #12 \ R14 = R14 EOR [R0 << 12] EOR R0, R14, R14, LSR #20 \ R0 = R14 EOR [R14 >> 20] STR R1, randomSeed1 \ Set randomSeed1 = R1 STR R0, randomSeed2 \ Set randomSeed2 = R0 LDMFD R13!, {PC} \ Return from the subroutine \ ****************************************************************************** \ \ Name; screenAddr \ Type; Variable \ Category; Drawing the screen \ Summary; The screen address for the start of the 17th pixel line in the \ current bank [i.e. the line just below the two rows of text] \ Deep dive; Screen memory in the Archimedes \ \ ****************************************************************************** .screenAddr EQUD &01FD8000 + 16 * 320 \ ****************************************************************************** \ \ Name; greyColourWords \ Type; Variable \ Category; Drawing the screen \ Summary; An unused table of grey four-pixel colour words \ Deep dive; Unused code in Lander \ \ ****************************************************************************** .greyColourWords EQUD &00000000 EQUD &01010101 EQUD &02020202 EQUD &03030303 EQUD &2C2C2C2C EQUD &2D2D2D2D EQUD &2E2E2E2E EQUD &2F2F2F2F EQUD &D0D0D0D0 EQUD &D1D1D1D1 EQUD &D2D2D2D2 EQUD &D3D3D3D3 EQUD &FCFCFCFC EQUD &FDFDFDFD EQUD &FEFEFEFE EQUD &FFFFFFFF EQUD &FFFFFFFF EQUD &FFFFFFFF EQUD &FFFFFFFF EQUD &FFFFFFFF \ ****************************************************************************** \ \ Name; greyColourWordsAddr \ Type; Variable \ Category; Drawing the screen \ Summary; The unused address of the unused table of grey four-pixel colour \ words \ Deep dive; Unused code in Lander \ \ ****************************************************************************** .greyColourWordsAddr EQUD greyColourWords \ ****************************************************************************** \ \ Name; Draw1x1ParticleFromBuffer \ Type; Subroutine \ Category; Particles \ Summary; Process the 'draw 1x1-pixel specified' command from the graphics \ buffer \ Deep dive; Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows; \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ [0-319] [0-255] [0-255] \ \ This command draws a 1x1-pixel particle at the given coordinates in the \ specified colour. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw1x1ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + [[R1 + R1 << 2] << 6] ADD R3, R3, R1, LSL #6 \ = R3 + [[R1 + 4 * R1] * 64] \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3] \ Draw a 1x1-pixel particle in the colour \ specified in R7 B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name; Draw2x1ParticleFromBuffer \ Type; Subroutine \ Category; Particles \ Summary; Process the 'draw 2x1-pixel particle' command from the graphics \ buffer \ Deep dive; Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows; \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ [0-319] [0-255] [0-255] \ \ This command draws a 2x1-pixel particle at the given coordinates in the \ specified colour, with the coordinates specifying the left end of the \ particle. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw2x1ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + [[R1 + R1 << 2] << 6] ADD R3, R3, R1, LSL #6 \ = R3 + [[R1 + 4 * R1] * 64] \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3] \ Draw a 2x1-pixel particle in the colour STRB R7, [R3, #1] \ specified in R7 B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name; Draw2x2ParticleFromBuffer \ Type; Subroutine \ Category; Particles \ Summary; Process the 'draw 2x2-pixel particle' command from the graphics \ buffer \ Deep dive; Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows; \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ [0-319] [0-255] [0-255] \ \ This command draws a 2x2-pixel particle at the given coordinates in the \ specified colour, with the coordinates specifying the top-left corner of the \ particle. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw2x2ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + [[R1 + R1 << 2] << 6] ADD R3, R3, R1, LSL #6 \ = R3 + [[R1 + 4 * R1] * 64] \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3] \ Draw a 2x2-pixel particle in the colour STRB R7, [R3, #1] \ specified in R7 STRB R7, [R3, #320] STRB R7, [R3, #320+1] B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name; Draw3x2ParticleFromBuffer \ Type; Subroutine \ Category; Particles \ Summary; Process the 'draw 3x2-pixel particle' command from the graphics \ buffer \ Deep dive; Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows; \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ [0-319] [0-255] [0-255] \ \ This command draws a 3x2-pixel particle at the given coordinates in the \ specified colour, with the coordinates specifying the pixel in the \ bottom-middle of the particle. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw3x2ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + [[R1 + R1 << 2] << 6] ADD R3, R3, R1, LSL #6 \ = R3 + [[R1 + 4 * R1] * 64] \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3, #-1] \ Draw a 3x2-pixel particle in the colour STRB R7, [R3] \ specified in R7 STRB R7, [R3, #1] STRB R7, [R3, #320-1] STRB R7, [R3, #320] STRB R7, [R3, #320+1] B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name; Draw3x1ParticleFromBuffer \ Type; Subroutine \ Category; Particles \ Summary; Process the 'draw 3x1-pixel particle' command from the graphics \ buffer \ Deep dive; Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ The 32-bit parameter word for this command is composed as follows; \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ [0-319] [0-255] [0-255] \ \ This command draws a 3x1-pixel particle at the given coordinates in the \ specified colour, with the coordinates specifying the pixel in the middle of \ the particle. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R9 The address of the parameter word in the \ graphics buffer \ \ R12 The address of the screen bank we are \ drawing in, pointing just below the two \ lines of text at the top of the screen, \ as set in screenAddr \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ ****************************************************************************** .Draw3x1ParticleFromBuffer LDR R1, [R9], #4 \ Fetch the parameter word from the graphics \ buffer into R1, incrementing R9 to point \ to the next command in the buffer MOV R0, R1, LSR #20 \ Set R0 to bits #20-31 of the parameter \ word, to extract the pixel x-coordinate MOV R7, R1, LSR #12 \ Set R7 to bits #12-31 of the parameter \ word to extract the colour number, which \ we poke into memory as a byte containing \ bits #12-19 AND R1, R1, #&FF \ Set R1 to bits #0-7 of the parameter word, \ to extract the pixel y-coordinate ADD R3, R12, R0 \ Set R3 = R12 + R0 \ = screenAddr + x-coordinate ADD R1, R1, R1, LSL #2 \ Set R3 = R3 + [[R1 + R1 << 2] << 6] ADD R3, R3, R1, LSL #6 \ = R3 + [[R1 + 4 * R1] * 64] \ = R3 + 320 * R1 \ = screenAddr + x-coordinate \ + 320 * y-coordinate \ \ So R3 contains the screen address of the \ coordinate in screen memory STRB R7, [R3, #-1] \ Draw a 3x1-pixel particle in the colour STRB R7, [R3] \ specified in R7 STRB R7, [R3, #1] B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name; DrawTriangleFromBuffer \ Type; Subroutine \ Category; Drawing triangles \ Summary; Process the 'draw triangle' command from the graphics buffer \ Deep dive; Depth-sorting with the graphics buffers \ Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ The seven parameter words for this command match the arguments for the \ DrawTriangle routine; \ \ * Pixel coordinate of corner 1 [x1, y1] \ * Pixel coordinate of corner 2 [x2, y2] \ * Pixel coordinate of corner 3 [x3, y3] \ * Colour \ \ This command draws a triangle of the specified shape and colour. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R9 The address of the seven parameter words in \ the graphics buffer \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R9 Updated to point to the next word in the \ graphics buffer \ \ R12 R12 is preserved \ \ ****************************************************************************** .DrawTriangleFromBuffer LDMIA R9!, {R0-R5, R8} \ Fetch the seven parameter words from the \ graphics buffer into R0 to R5 and R8, \ incrementing R9 to point to the next \ command in the buffer STMFD R13!, {R9, R12} \ Store R9 and R12 on the stack so they \ don't get corrupted by the following call \ to DrawTriangle BL DrawTriangle \ Draw a triangle at the coordinates \ specified in R0 to R5 and R8 LDMFD R13!, {R9, R12} \ Retrieve the values of R9 and R12 that we \ stored above B DrawNextFromGraphicsBuffer \ Draw the next command from the graphics \ buffer \ ****************************************************************************** \ \ Name; DrawParticleToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Draw a large coloured particle into a slightly nearer graphics, \ buffer according to its distance \ Deep dive; Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ This command draws a large coloured particle into a graphics buffer, with the \ buffer being chosen according to the distance of the particle in the z-axis, \ plus one [so this routine draws the particle into a slightly nearer graphics \ buffer than DrawParticleShadowToBuffer, so shadows never overlap their \ corresponding particles]. \ \ This command is used for drawing particles, while DrawParticleShadowToBuffer \ is used for drawing their shadows. \ \ The command numbers for drawing large particles are 0 to 8, with higher \ numbers giving smaller particles [so particles are smaller when they are \ further away, as the command number is based on the z-coordinate]. This is \ followed by the pixel coordinates, composed into one 32-bit parameter word as \ follows; \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ [0-319] [0-255] [0-255] \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1] The pixel coordinate of the particle \ \ R7 The particle colour \ \ R8 The 3D z-coordinate of the particle \ \ R12 The address of the table of containing the \ end addresses of the graphics buffers \ [i.e. graphicsBuffersEnd] \ \ ****************************************************************************** .DrawParticleToBuffer STMFD R13!, {R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved RSB R14, R8, #LANDSCAPE_Z \ Set R14 = landscape offset - R8 \ \ We use the top byte of this number as the \ number of the graphics buffer to draw into \ \ This ensures that more distant particles \ are drawn into lower-numbered graphics \ buffers, so higher z-coordinates map to \ lower buffer numbers and vice versa [so \ buffer 0 is for objects that are far away \ and buffer 10 is for the closest objects \ to the viewer] ADD R14, R14, #TILE_SIZE \ Add the size of one tile to R14 so we draw \ the particle one tile nearer than we would \ otherwise [i.e. one buffer nearer] BIC R14, R14, #&00C00000 \ We are going to use the top byte of R14 as \ the buffer number, and we're going to look \ up the corresponding buffer address from a \ lookup table with four bytes per entry, so \ we clear bits 22 and 23 to ensure that \ R14 >> 22 contains the value of the top \ byte * 4, which we can then use as an \ offset into the lookup table to fetch the \ address of the end of the buffer, which is \ where we can store our particle-drawing \ command LDR R9, [R12, R14, LSR #22] \ Set R9 to the address of the next free \ byte in the graphics buffer whose number \ matches the top byte of R14, fetching the \ address from the lookup table at R12 MOV R8, R8, LSR #25 \ Scale R8 from a 3D z-coordinate into a CMP R8, #8 \ number between 0 and 8, which we can use MOVHS R8, #8 \ as the drawing command number, as drawing \ commands 0 to 8 draw particles with higher \ numbers giving smaller particles [see \ bufferJump for a full list of drawing \ commands] AND R7, R7, #&FF \ Encode the two pixel coordinates in R0 and ADD R1, R1, R7, LSL #12 \ R1 and the colour in R7 into one parameter ADD R1, R1, R0, LSL #20 \ word in R1, by inserting the value of R0 \ into bits 20-31 of R1 and the value of R7 \ into bits 12-19 STR R8, [R9], #4 \ Store the drawing command number in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer STR R1, [R9], #4 \ Store the parameter word in R1 in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer STR R9, [R12, R14, LSR #22] \ Update the end address in the table at R12 \ to point to the new end address of the \ graphics buffer LDMFD R13!, {R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; DrawParticleShadowToBuffer \ Type; Subroutine \ Category; Particles \ Summary; Draw a small black particle into the correct graphics buffer, \ according to its distance \ Deep dive; Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ This command draws a small black particle into a graphics buffer, with the \ buffer being chosen according to the distance of the particle in the z-axis. \ \ This command is used for drawing particle shadows, while the particles \ themselves are drawn by DrawParticleToBuffer. \ \ The command numbers for drawing small particles are 9 to 17, with higher \ numbers giving smaller particles [so particles are smaller when they are \ further away, as the command number is based on the z-coordinate]. This is \ followed by the pixel coordinates, composed into one 32-bit parameter word as \ follows; \ \ #20-31 #12-19 #8-11 #0-7 \ 019876543210 98765432 1098 76543210 \ ^ ^ ^ ^ \ | | | | \ Pixel x-coord Colour Unused Pixel y-coord \ [0-319] [always 0] [0-255] \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1] The pixel coordinate of the particle \ \ R8 The 3D z-coordinate of the particle \ \ R12 The address of the table of containing the \ end addresses of the graphics buffers \ [i.e. graphicsBuffersEnd] \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R12 R12 is preserved \ \ ****************************************************************************** .DrawParticleShadowToBuffer STMFD R13!, {R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved RSB R14, R8, #LANDSCAPE_Z \ Set R14 = landscape offset - R8 \ \ We use the top byte of this number as the \ number of the graphics buffer to draw into \ \ This ensures that more distant particles \ are drawn into lower-numbered graphics \ buffers, so higher z-coordinates map to \ lower buffer numbers and vice versa [so \ buffer 0 is for objects that are far away \ and buffer 10 is for the closest objects \ to the viewer] BIC R14, R14, #&00C00000 \ We are going to use the top byte of R14 as \ the buffer number, and we're going to look \ up the corresponding buffer address from a \ lookup table with four bytes per entry, so \ we clear bits 22 and 23 to ensure that \ R14 >> 22 contains the value of the top \ byte * 4, which we can then use as an \ offset into the lookup table to fetch the \ address of the end of the buffer, which is \ where we can store our particle-drawing \ command LDR R9, [R12, R14, LSR #22] \ Set R9 to the address of the next free \ byte in the graphics buffer whose number \ matches the top byte of R14, fetching the \ address from the lookup table at R12 MOV R8, R8, LSR #25 \ Scale R8 from a 3D z-coordinate into a CMP R8, #8 \ number between 9 and 17, which we can use MOVHS R8, #8 \ as the drawing command number, as drawing ADD R8, R8, #9 \ commands 9 to 17 draw shadow particles \ with higher numbers giving smaller \ particles [see bufferJump for a full list \ of drawing commands] ADD R1, R1, R0, LSL #20 \ Encode the two pixel coordinates in R0 and \ R1 into one parameter word in R1, by \ inserting the value of R0 into bits 20-31 \ of R1 \ \ The colour in bits 12-19 is left at zero, \ so the particle is always black STR R8, [R9], #4 \ Store the drawing command number in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer STR R1, [R9], #4 \ Store the parameter word in R1 in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer STR R9, [R12, R14, LSR #22] \ Update the end address in the table at R12 \ to point to the new end address of the \ graphics buffer LDMFD R13!, {R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; DrawTriangleShadowToBuffer \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw a triangle shadow into the correct graphics buffer, according \ to its distance \ Deep dive; Drawing 3D objects \ Depth-sorting with the graphics buffers \ Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ This command draws a triangle into a graphics buffer, with the buffer being \ chosen according to the distance of the triangle in the z-axis. \ \ This command is used for drawing triangle shadows, while the triangles \ themselves are drawn by DrawTriangleToBuffer. It is always called with a \ colour value of 0 in R8, so shadows are always black. \ \ The command number for drawing a triangle is 18, followed by seven parameter \ words that match the arguments for the DrawTriangle routine; \ \ * Command number [18] \ \ * Pixel coordinate of corner 1 [x1, y1] \ \ * Pixel coordinate of corner 2 [x2, y2] \ \ * Pixel coordinate of corner 3 [x3, y3] \ \ * Colour \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1] Pixel coordinate of corner 1 \ \ [R2, R2] Pixel coordinate of corner 2 \ \ [R4, R5] Pixel coordinate of corner 4 \ \ R8 Colour [always 0 for black] \ \ R12 The address of the table of containing the \ end addresses of the graphics buffers \ [i.e. graphicsBuffersEnd] \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R12 R12 is preserved \ \ ****************************************************************************** .DrawTriangleShadowToBuffer STMFD R13!, {R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R14, [R11, #zObject] \ Set R14 to the z-coordinate of the \ triangle RSB R14, R14, #LANDSCAPE_Z \ Set R14 = landscape offset - R14 \ \ We use the top byte of this number as the \ number of the graphics buffer to draw into \ \ This ensures that more distant triangles \ are drawn into lower-numbered graphics \ buffers, so higher z-coordinates map to \ lower buffer numbers and vice versa [so \ buffer 0 is for objects that are far away \ and buffer 10 is for the closest objects \ to the viewer] CMP R14, #LANDSCAPE_Z_BEYOND \ If R14 is a z-distance beyond the back of MOVHS R14, #LANDSCAPE_Z_DEPTH \ the visible landscape, cap it to the depth \ of the landscape so it fits into the range \ of graphics buffers [as there is one \ buffer for each tile row, going into the \ screen] BIC R14, R14, #&00C00000 \ We are going to use the top byte of R14 as \ the buffer number, and we're going to look \ up the corresponding buffer address from a \ lookup table with four bytes per entry, so \ we clear bits 22 and 23 to ensure that \ R14 >> 22 contains the value of the top \ byte * 4, which we can then use as an \ offset into the lookup table to fetch the \ address of the end of the buffer, which is \ where we can store our triangle-drawing \ command LDR R9, [R12, R14, LSR #22] \ Set R9 to the address of the next free \ byte in the graphics buffer whose number \ matches the top byte of R14, fetching the \ address from the lookup table at R12 MOV R7, #18 \ The command number for drawing a triangle STR R7, [R9], #4 \ is 18, so we store a value of 18 in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer [see bufferJump for a full list of \ drawing commands] STMIA R9!, {R0-R5, R8} \ Store the three pixel coordinates from R0 \ to R5, and then the colour in R8, in the \ buffer STR R9, [R12, R14, LSR #22] \ Update the end address in the table at R12 \ to point to the new end address of the \ graphics buffer LDMFD R13!, {R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; DrawTriangleToBuffer \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw a coloured triangle into a slightly nearer graphics buffer, \ according to its distance \ Deep dive; Drawing 3D objects \ Depth-sorting with the graphics buffers \ Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ This command draws a triangle into a graphics buffer, with the buffer being \ chosen according to the distance of the triangle in the z-axis, plus one [so \ this routine draws the triangle into a slightly nearer graphics buffer than \ DrawTriangleShadowToBuffer, so shadows never overlap their corresponding \ triangles]. \ \ This command is used for drawing triangles, while DrawTriangleShadowToBuffer \ is used to draw their shadows. \ \ The command number for drawing a triangle is 18, followed by seven parameter \ words that match the arguments for the DrawTriangle routine; \ \ * Command number [18] \ \ * Pixel coordinate of corner 1 [x1, y1] \ \ * Pixel coordinate of corner 2 [x2, y2] \ \ * Pixel coordinate of corner 3 [x3, y3] \ \ * Colour \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1] Pixel coordinate of corner 1 \ \ [R2, R2] Pixel coordinate of corner 2 \ \ [R4, R5] Pixel coordinate of corner 4 \ \ R8 Colour \ \ R12 The address of the table of containing the \ end addresses of the graphics buffers \ [i.e. graphicsBuffersEnd] \ \ ------------------------------------------------------------------------------ \ \ Returns; \ \ R12 R12 is preserved \ \ ****************************************************************************** .DrawTriangleToBuffer STMFD R13!, {R9, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R14, [R11, #zObject] \ Set R14 to the z-coordinate of the \ triangle RSB R14, R14, #LANDSCAPE_Z \ Set R14 = landscape offset - R14 \ \ We use the top byte of this number as the \ number of the graphics buffer to draw into \ \ This ensures that more distant triangles \ are drawn into lower-numbered graphics \ buffers, so higher z-coordinates map to \ lower buffer numbers and vice versa [so \ buffer 0 is for objects that are far away \ and buffer 10 is for the closest objects \ to the viewer] ADD R14, R14, #TILE_SIZE \ Add the size of one tile to R14 so we draw \ the triangle one tile nearer than we would \ otherwise [i.e. one buffer nearer] CMP R14, #LANDSCAPE_Z_BEYOND \ If R14 is a z-distance beyond the back of MOVHS R14, #LANDSCAPE_Z_DEPTH \ the visible landscape, cap it to the depth \ of the landscape so it fits into the range \ of graphics buffers [as there is one \ buffer for each tile row, going into the \ screen] BIC R14, R14, #&00C00000 \ We are going to use the top byte of R14 as \ the buffer number, and we're going to look \ up the corresponding buffer address from a \ lookup table with four bytes per entry, so \ we clear bits 22 and 23 to ensure that \ R14 >> 22 contains the value of the top \ byte * 4, which we can then use as an \ offset into the lookup table to fetch the \ address of the end of the buffer, which is \ where we can store our triangle-drawing \ command LDR R9, [R12, R14, LSR #22] \ Set R9 to the address of the next free \ byte in the graphics buffer whose number \ matches the top byte of R14, fetching the \ address from the lookup table at R12 MOV R7, #18 \ The command number for drawing a triangle STR R7, [R9], #4 \ is 18, so we store a value of 18 in the \ graphics buffer and increment the address \ in R9 to point to the next word in the \ buffer [see bufferJump for a full list of \ drawing commands] STMIA R9!, {R0-R5, R8} \ Store the three pixel coordinates from R0 \ to R5, and then the colour in R8, in the \ buffer STR R9, [R12, R14, LSR #22] \ Update the end address in the table at R12 \ to point to the new end address of the \ graphics buffer LDMFD R13!, {R9, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; bufferJump \ Type; Variable \ Category; Graphics buffers \ Summary; The jump table for drawing commands that we store in the graphics \ buffers \ Deep dive; Depth-sorting with the graphics buffers \ \ ****************************************************************************** .bufferJump EQUD Draw3x2ParticleFromBuffer \ Drawing commands 0 to 8 draw large EQUD Draw3x2ParticleFromBuffer \ particles [for particles] EQUD Draw3x2ParticleFromBuffer EQUD Draw3x2ParticleFromBuffer EQUD Draw3x2ParticleFromBuffer EQUD Draw3x2ParticleFromBuffer EQUD Draw2x2ParticleFromBuffer EQUD Draw2x1ParticleFromBuffer EQUD Draw1x1ParticleFromBuffer EQUD Draw3x1ParticleFromBuffer \ Drawing commands 9 to 17 draw small EQUD Draw3x1ParticleFromBuffer \ particles [for shadows] EQUD Draw3x1ParticleFromBuffer EQUD Draw3x1ParticleFromBuffer EQUD Draw3x1ParticleFromBuffer EQUD Draw3x1ParticleFromBuffer EQUD Draw2x1ParticleFromBuffer EQUD Draw2x1ParticleFromBuffer EQUD Draw1x1ParticleFromBuffer EQUD DrawTriangleFromBuffer \ Drawing command 18 draws a triangle EQUD TerminateGraphicsBuffer \ Drawing command 19 terminates the buffer \ ****************************************************************************** \ \ Name; DrawGraphicsBuffer \ Type; Subroutine \ Category; Graphics buffers \ Summary; Draw the contents of the specified graphics buffer \ Deep dive; Depth-sorting with the graphics buffers \ \ ------------------------------------------------------------------------------ \ \ This routine draws the contents of a specified graphics buffer. There are 11 \ buffers that are used to layer the game's graphics correctly according to an \ object's distance. This routine draws the contents of a specific buffer onto \ the screen. \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The number of the graphics buffer to draw \ [0 to 10] \ \ ------------------------------------------------------------------------------ \ \ Other entry points; \ \ DrawNextFromGraphicsBuffer Process the next command from the graphics \ buffer \ \ TerminateGraphicsBuffer Terminate drawing from the graphics buffer \ by returning from the subroutine \ \ ****************************************************************************** .DrawGraphicsBuffer STMFD R13!, {R6-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R1, graphicsBufferEndAddr \ Set R1 to the address of the table that \ contains the end addresses of the graphics \ buffers LDR R9, [R1, R0, LSL #2] \ Set R9 to the R0-th entry from the \ graphicsBuffersEnd table, which contains \ the end address for graphics buffer R0 LDR R12, screenAddr \ Set R12 to the address of the screen bank \ we are drawing in, pointing just below \ the two lines of text at the top of the \ screen .DrawNextFromGraphicsBuffer LDR R0, [R9], #4 \ Fetch the address of the next free byte in \ the graphics buffer [which is the same as \ the end address of the buffer] from the \ graphicsBuffersEnd table, put it into R0 \ and increment R9 to point to the next \ buffer's end address ] labOffset = P% + 8 - bufferJump : REM Set labOffset to the offset back to the : REM bufferJump table from the next instruction [ OPT pass% ADD R0, PC, R0, LSL #2 \ Jump to the address in entry R0 in the LDR PC, [R0, #-labOffset] \ bufferJump table .TerminateGraphicsBuffer LDMFD R13!, {R6-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; AddTerminatorsToBuffers \ Type; Subroutine \ Category; Graphics buffers \ Summary; Add terminators to the ends of the graphics buffers so we know \ when to stop drawing \ Deep dive; Depth-sorting with the graphics buffers \ \ ****************************************************************************** .AddTerminatorsToBuffers STMFD R13!, {R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved LDR R12, graphicsBufferEndAddr \ Set R12 to the address of the table that \ contains the end addresses of the graphics \ buffers LDR R14, graphicsBufferAddr \ Set R14 to the address of the table that \ contains the start addresses of the \ graphics buffers MOV R0, #TILES_Z \ We are going to loop through all of the \ graphics buffers, so set a counter in R0 \ to the number of tile corners in the \ visible landscape, going back to front [as \ there is one graphics buffer for each \ corner row] MOV R1, #19 \ The command number for terminating a \ graphics buffer is 19, so put this in R1 \ [see bufferJump for a full list of drawing \ commands] .term1 LDR R2, [R12, R0, LSL #2] \ Set R2 to the end address of graphics \ buffer number R0 STR R1, [R2] \ Store the termination command number at \ the end of the buffer LDR R2, [R14, R0, LSL #2] \ Reset the graphicsBufferEnd entry for this STR R2, [R12, R0, LSL #2] \ buffer back to the start address from the \ graphicsBuffer table, so when we draw the \ next frame, we fill up the graphics \ buffers from the start again SUBS R0, R0, #1 \ Decrement the loop counter BPL term1 \ Loop back until we have terminated all 12 \ graphics buffers LDMFD R13!, {R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; screenBank2Addr \ Type; Variable \ Category; Drawing the screen \ Summary; The screen address for the start of the 17th pixel line in screen \ bank 0 [i.e. the line just below the two rows of text] \ Deep dive; Screen memory in the Archimedes \ \ ****************************************************************************** .screenBank2Addr EQUD &01FD8000 + 320 * 16 \ ****************************************************************************** \ \ Name; screenBank1Addr \ Type; Variable \ Category; Drawing the screen \ Summary; The screen address for the start of the 17th pixel line in screen \ bank 1 [i.e. the line just below the two rows of text] \ Deep dive; Screen memory in the Archimedes \ \ ****************************************************************************** .screenBank1Addr EQUD &01FEC000 + 320 * 16 \ ****************************************************************************** \ \ Name; screenBankNumber \ Type; Variable \ Category; Drawing the screen \ Summary; The number of the current screen bank [0 or 1] \ Deep dive; Screen memory in the Archimedes \ \ ****************************************************************************** .screenBankNumber EQUD 0 \ Stores the details of the current bank, as \ follows; \ \ * 0 = draw bank 1, show bank 2 \ \ * 1 = draw bank 2, show bank 1 \ ****************************************************************************** \ \ Name; divisionTableAddr \ Type; Variable \ Category; Maths [Arithmetic] \ Summary; The address of the division lookup table \ \ ****************************************************************************** .divisionTableAddr EQUD divisionTable \ ****************************************************************************** \ \ Name; SwitchScreenBank \ Type; Subroutine \ Category; Drawing the screen \ Summary; Switch screen banks and clear the newly hidden screen bank to \ black \ Deep dive; Screen memory in the Archimedes \ \ ****************************************************************************** .SwitchScreenBank LDRB R0, screenBankNumber \ Flip the current screen bank number in EORS R0, R0, #1 \ screenBankNumber between bank 0 and bank 1 STRB R0, screenBankNumber \ which means; \ \ * 0 = draw bank 1, show bank 2 \ \ * 1 = draw bank 2, show bank 1 LDRNE R1, screenBank2Addr \ Set screenAddr as follows; LDREQ R1, screenBank1Addr \ STR R1, screenAddr \ * If screenBankNumber = 0, set it to the \ address of bank 2 in screenBank2Addr \ \ * If screenBankNumber = 1, set it to the \ address of bank 1 in screenBank1Addr \ \ So screenAddr points to the screen that is \ no longer being shown, i.e. the bank that \ we should now draw into TEQ R0, #0 \ Set R1 = screenBankNumber + 1 MOVEQ R1, #1 \ MOVNE R1, #2 \ So this sets R1 as follows; \ \ * If screenBankNumber = 0, R1 = 1 \ \ * If screenBankNumber = 1, R1 = 2 \ \ So R1 is the number of the opposite bank \ to the one in screenAddr, i.e. the bank \ that we should now show on-screen MOV R0, #113 \ Set the display hardware to display the SWI OS_Byte \ screen bank in R1, so the correct bank is \ shown on-screen \ \ This call returns the old bank number in \ R1, so R1 now contains the number of the \ screen bank that is not being displayed MOV R0, #112 \ Set the VDU driver screen bank to the bank SWI OS_Byte \ number in R1, so this directs VDU calls \ [such as the text routines that update the \ score bar] so they update the hidden \ screen bank MOV R0, #19 \ Wait for the vertical sync SWI OS_Byte \ We now clear the screen bank that isn't \ being shown, i.e. the bank at screenAddr STMFD R13!, {R9, R11-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R0, #0 \ Set R0 to R9 to zero so we can use them to MOV R1, #0 \ clear the screen bank to black [as black MOV R2, #0 \ is represented by zero in screen memory] MOV R3, #0 MOV R4, #0 MOV R5, #0 MOV R6, #0 MOV R7, #0 MOV R8, #0 MOV R9, #0 LDR R11, screenAddr \ Set R11 = screenAddr \ \ So R11 contains the address of the screen \ bank we want to clear, pointing just below \ the two lines of text at the top of the \ screen ADD R12, R11, #&12C00 \ Set R12 = screenAddr + 320 * 240 \ \ So R12 points to the end of screen memory \ for the bank at screenAddr, as the address \ in R11 skips the first 16 pixel rows of \ the 320 * 256 screen \ We now zero screen memory from R12 down to \ R11, which clears the screen bank that we \ are no longer showing on-screen .bank1 STMDB R12!, {R0-R9} \ Zero 40 x 32-bit words of screen memory, STMDB R12!, {R0-R9} \ decreasing the address in R12 as we go STMDB R12!, {R0-R9} STMDB R12!, {R0-R9} CMP R12, R11 \ If R12 hasn't yet reached the start of the BNE bank1 \ bank's screen memory in R11, loop back to \ keep zeroing screen memory until it is all \ cleared to black, leaving just the two \ lines of text at the top LDMFD R13!, {R9, R11-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; DrawQuadrilateral \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw a quadrilateral [i.e. two triangles] \ Deep dive; Drawing the landscape \ Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1] Pixel coordinate of corner 1 \ \ [R2, R3] Pixel coordinate of corner 2 \ \ [R4, R5] Pixel coordinate of corner 3 \ \ [R6, R7] Pixel coordinate of corner 3 \ \ R8 Colour \ \ ****************************************************************************** .DrawQuadrilateral STMFD R13!, {R2-R7, R9-R12, R14} \ Store R2 to R7 on the stack, as well as \ the registers that we want to use on the \ stack so they can be preserved BL DrawTriangle \ Draw a triangle in colour R8 with the \ following corner coordinates; \ \ [R0, R1] \ [R2, R3] \ [R4, R5] LDMFD R13!, {R0-R5} \ Set R0 to R5 to the original values of \ R2 to R7, to [R0, R2] is the pixel \ coordinate of corner 2, and so on BL DrawTriangle \ Draw a triangle in colour R8 with the \ following corner coordinates, in terms \ of the original arguments to the routine; \ \ [R2, R3] \ [R4, R5] \ [R6, R7] \ \ So overall this draws a quadrilateral in \ colour R8 with corners at; \ \ [R0, R1] \ [R2, R3] \ [R4, R5] \ [R6, R7] LDMFD R13!, {R9-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; DrawTriangle [Part 1 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw a triangle, starting by ordering the coordinates and jumping \ to the relevant part of the routine \ Deep dive; Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ [R0, R1] Pixel coordinate of corner 1 in [x1, y1] \ \ [R2, R3] Pixel coordinate of corner 2 in [x2, y2] \ \ [R4, R5] Pixel coordinate of corner 3 in [x3, y3] \ \ R8 Colour \ \ ****************************************************************************** .DrawTriangle CMP R0, #320 \ Check to see whether any of the triangle CMPLO R1, #239 \ corners are off-screen, in which case at CMPLO R2, #320 \ least one of x1 to x3 will be >= 320, or CMPLO R3, #239 \ at least one of y1 to y3 will be >= 239 CMPLO R4, #320 \ [the y-coordinate is checked against 239 CMPLO R5, #239 \ rather than 255 as the top 16 pixel lines \ of the 320x256 screen are taken up by the \ score bar] \ \ This also catches any negative pixel \ coordinates, as they will be treated as \ extremely large positive values in the \ comparisons [as the LO condition is used \ for unsigned comparisons] BHS trin23 \ If any of the corners are off-screen, jump \ to part 6 to draw a clipped triangle STMFD R13!, {R14} \ Store the return address on the stack LDR R12, screenAddr \ Set R12 to the address of the screen bank \ we are drawing in, pointing just below \ the two lines of text at the top of the \ screen \ We start by ordering the triangle corners \ by y-coordinate, so [x1, y1] is the \ furthest down the screen with the highest \ y-coordinate, followed by [x2, y2] then \ [x3, y3], at the same or higher level, and \ in that order CMP R1, R3 \ If R1 >= R3, jump to trin1 to skip the BHS trin1 \ following, as y1 >= y2 already MOV R6, R0 \ Swap [x1, y1] and [x2, y2], so we now have MOV R0, R2 \ y1 >= y2 MOV R2, R6 MOV R6, R1 MOV R1, R3 MOV R3, R6 .trin1 CMP R1, R5 \ If R1 >= R5, jump to trin2 to skip the BHS trin2 \ following, as y1 >= y3 already MOV R6, R0 \ Swap [x1, y1] and [x3, y3], so we now have MOV R0, R4 \ y1 >= y3 MOV R4, R6 MOV R6, R1 MOV R1, R5 MOV R5, R6 .trin2 CMP R3, R5 \ If R3 >= R5, jump to trin3 to skip the BHS trin3 \ following, as y2 >= y3 already MOV R6, R2 \ Swap [x2, y2] and [x3, y3], so we now have MOV R2, R4 \ y2 >= y3 MOV R4, R6 MOV R6, R3 MOV R3, R5 MOV R5, R6 \ So the triangle coordinates are ordered \ like this on-screen; \ \ [x3, y3] \ [x2, y2] \ [x1, y1] .trin3 ADD R11, R1, R1, LSL #2 \ Set R11 = R1 + R1 * 4 \ = 5 * y1 ADD R12, R12, R11, LSL #6 \ Set R12 = R12 + R11 * 64 \ = screenAddr + 5 * y1 * 64 \ = screenAddr + 320 * y1 \ \ So R12 is the screen address of the start \ of the pixel row containing [x1, y1], at \ the bottom of the triangle on-screen \ ****************************************************************************** \ \ Name; DrawTriangle [Part 2 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Calculate the slope of [x1, y1] to [x2, y2] \ Deep dive; Drawing triangles \ \ ****************************************************************************** \ We now calculate the slope of the first \ side of the triangle, from [x1, y1] to \ [x2, y2], as follows; \ \ R6 = [y1 - y2] / [x2 - x1] SUBS R9, R1, R3 \ Set R9 = R1 - R3 \ = y1 - y2 \ \ So this is the vertical distance between \ [x1, y1] and [x2, y2], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y1 >= y2 BEQ trin16 \ If [x1, y1] and [x2, y2] share the same \ y-coordinate then the triangle has a \ horizontal edge between [x1, y1] and \ [x2, y2], so jump to part 5 to process \ this special case STMFD R13!, {R4-R5} \ Store the third corner's coordinates in \ [x3, y3] on the stack to we can retrieve \ them later SUBS R14, R2, R0 \ Set R14 = R2 - R0 \ = x2 - x1 \ \ So this is the vertical distance between \ [x1, y1] and [x2, y2], i.e. the delta \ x-coordinate between the two points \ \ This will be positive if the line from \ [x1, y1] to [x2, y2] slopes up and to the \ right, or negative if the line slopes up \ and to the left RSBMI R14, R2, R0 \ If R14 is negative, set R14 = x1 - x2, so \ we have the following; \ \ R14 = |x2 - x1| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin4 \ \ R14 = |x2 - x1| >= 64 \ \ R9 = [y1 - y2] >= 64 \ \ so jump to trin4 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R6, [R10, R9, LSL #2] \ Set R6 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R6 = R9 / R14 \ \ = [y1 - y2] / |x2 - x1| B trin6 \ Jump to trin6 to skip the following and \ keep going .trin4 \ If we get here then at least one of these \ is true; \ \ R14 = |x2 - x1| >= 64 \ \ R9 = [y1 - y2] >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R6, #0 \ Set R6 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin5 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R6, R6, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R6] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin5 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R6 = R9 / R14 \ \ = [y1 - y2] / |x2 - x1| .trin6 CMP R2, R0 \ If R2 - R0 < 0 then; RSBMI R6, R6, #0 \ \ x2 - x1 < 0 \ \ so negate R6 to give R6 the correct sign \ for the following calculation; \ \ R6 = [y1 - y2] / [x2 - x1] \ \ So R6 contains the slope of the first side \ of the triangle, from [x1, y1] to [x2, y2] \ ****************************************************************************** \ \ Name; DrawTriangle [Part 3 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Calculate the slope of [x1, y1] to [x3, y3] \ Deep dive; Drawing triangles \ \ ****************************************************************************** \ We now calculate the slope for the second \ side of the triangle, from [x1, y1] to \ [x3, y3], as follows; \ \ R7 = [y1 - y3] / [x3 - x1] SUBS R9, R1, R5 \ Set R9 = R1 - R5 \ = y1 - y3 SUBS R14, R4, R0 \ Set R14 = R4 - R0 \ = x3 - x1 RSBMI R14, R4, R0 \ If R14 is negative, set R4 = x1 - x3, so \ we have the following; \ \ R14 = |x3 - x1| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin7 \ \ R14 = |x3 - x1| >= 64 \ \ R9 = [y1 - y3] >= 64 \ \ so jump to trin7 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R7, [R10, R9, LSL #2] \ Set R7 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R7 = R9 / R14 \ \ = [y1 - y3] / |x3 - x1| B trin9 \ Jump to trin9 to skip the following and \ keep going .trin7 \ If we get here then at least one of these \ is true; \ \ R14 = |x3 - x1| >= 64 \ \ R9 = [y1 - y3] >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R7, #0 \ Set R7 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin8 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R7, R7, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R7] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin8 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R7 = R9 / R14 \ \ = [y1 - y3] / |x3 - x1| .trin9 CMP R4, R0 \ If R4 - R0 < 0 then; RSBMI R7, R7, #0 \ \ x3 - x1 < 0 \ \ so negate R7 to give R7 the correct sign \ for the following calculation; \ \ R7 = [y1 - y3] / [x3 - x1] \ \ So R7 contains the slope of the second \ side of the triangle, from [x1, y1] to \ [x3, y3] \ ****************************************************************************** \ \ Name; DrawTriangle [Part 4 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw a triangle that isn't clipped and has a sloping first side \ Deep dive; Drawing triangles \ \ ****************************************************************************** \ By this point we have the following; \ \ [R0, R1] = [x1, y1] \ \ [R2, R3] = [x2, y2] \ \ [x3, y3] is on the stack \ \ R6 = [y1 - y2] / [x2 - x1] \ = slope of [x1, y1] to [x2, y2] \ \ R7 = [y1 - y3] / [x3 - x1] \ = slope of [x1, y1] to [x3, y3] \ \ R12 = screen address of the start of the \ pixel row containing [x1, y1] \ \ [x1, y1] is the point lowest down the \ screen and [x3, y3] is the highest up the \ screen, with [x2, y2] the point in the \ middle [in terms of y-coordinate] \ \ We now draw the triangle in two parts, \ effectively slicing the triangle in half \ with a horizontal line at y-coordinate y2, \ leaving two triangles to draw; \ \ 1. The triangle from [x1, y1] at the \ bottom up to the horizontal line with \ [x2, y2] at one end \ \ 2. The triangle from the horizontal line \ with [x2, y2] at one end, up to \ [x3, y3] at the top \ \ We start at the bottom of the triangle, at \ [x1, y1], and step upwards by one pixel \ row at a time, drawing a horizontal line \ between the two sides, until we reach the \ level of [x2, y2] \ \ As we step up each pixel row, we calculate \ the x-coordinates of each row we draw by \ adding the slopes in R6 and R7 \ \ We store the x-coordinates of the current \ horizontal line in R4 and R5, so these are \ the registers we update with the slope \ values SUBS R9, R1, R3 \ Set R9 = R1 - R3 \ = y1 - y2 \ \ So this is the vertical distance between \ [x1, y1] and [x2, y2], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y1 >= y2, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y1 and y2 MOV R4, R0, LSL #16 \ Set R4 = R0 << 16 \ = x1 << 16 \ \ We scale up R4 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R4 >> 16 gives us the integral part \ \ We use R4 as the x-coordinate of the left \ end of the horizontal line to draw ORR R4, R4, #&8000 \ Set R5 to x1, with the top bit of its MOV R5, R4 \ fractional part set [so that's 0.5] \ \ We use R5 as the x-coordinate of the right \ end of the horizontal line to draw CMP R6, R7 \ If R6 > R7, swap R6 and R7, so we know MOVGT R14, R6 \ that R6 <= R7, i.e. R6 contains the side MOVGT R6, R7 \ with the lesser slope, which will be the MOVGT R7, R14 \ slope along the left edge of the triangle .trin10 ADD R4, R4, R6 \ Set R4 = R4 + R6 \ = R4 + slope of left edge \ \ So this moves the x-coordinate of the left \ edge by the correct slope as we move up by \ one pixel row ADD R5, R5, R7 \ Set R5 = R5 + R7 \ = R5 + slope of right edge \ \ So this moves the x-coordinate of the \ right edge by the correct slope as we move \ up by one pixel row ADD R11, R12, R4, LSR #16 \ Set R11 = R12 + R4 >> 16 \ = screen address of row + x1 \ \ So R11 contains the screen address of the \ left end of the line to draw MOV R10, R5, LSR #16 \ Set R10 = R5 >> 16 - R4 >> 16 SUBS R10, R10, R4, LSR #16 \ = x-coordinate of right edge \ - x-coordinate of left edge \ \ So R10 contains the length of the line \ from the left edge to the right edge BLPL DrawHorizontalLine \ If R10 is positive then we draw a line \ from the left edge to the right edge, \ using the four-pixel colour word that was \ passed to the DrawTriangle routine in R8 SUB R12, R12, #320 \ Subtract 320 from R12 so that R12 now \ points to the start of the pixel row above \ the one we just drew SUBS R9, R9, #1 \ Decrement the pixel row counter in R9, \ which contains the number of pixel rows \ between y1 and y2 BNE trin10 \ Loop back until we have drawn all the \ horizontal lines in the first part of the \ triangle, from [x1, y1] at the bottom, up \ to the level of [x2, y2] \ So now we need to draw the rest of the \ triangle, from [x2, y2] up to [x3, y3] LDMFD R13!, {R0-R1} \ Fetch the coordinates for the third point \ [x3, y3] from the stack and into [R0, R1] SUBS R9, R3, R1 \ Set R9 = R3 - R1 \ = y2 - y3 \ \ So this is the vertical distance between \ [x2, y2] and [x3, y3], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y2 and y3 LDMEQIA R13!, {PC} \ If R9 is zero then [x2, y2] and [x3, y3] \ are at the same y-coordinate, so there is \ nothing to draw in the top part of the \ triangle, so return from the subroutine as \ we are done \ We now calculate the slope for the third \ side of the triangle, from [x2, y2] to \ [x3, y3], as follows; \ \ R14 = [y2 - y3] / [x3 - x2] SUBS R14, R0, R2 \ Set R14 = R0 - R2 \ = x3 - x2 RSBMI R14, R0, R2 \ If R14 is negative, set R4 = x2 - x3, so \ we have the following; \ \ R14 = |x3 - x2| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin11 \ \ R14 = |x3 - x2| >= 64 \ \ R9 = [y2 - y3] >= 64 \ \ so jump to trin11 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R11, [R10, R9, LSL #2] \ Set R11 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R11 = R9 / R14 \ \ = [y2 - y3] / |x3 - x2| B trin13 \ Jump to trin13 to skip the following and \ keep going .trin11 \ If we get here then at least one of these \ is true; \ \ R14 = |x3 - x2| >= 64 \ \ R9 = [y2 - y3] >= 64 \ \ We now calculate R11 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R11, #0 \ Set R11 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin12 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R11, R11, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R11] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin12 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R11 = R9 / R14 \ \ = [y2 - y3] / |x3 - x2| .trin13 CMP R0, R2 \ If R0 - R2 < 0 then; RSBMI R11, R11, #0 \ \ x3 - x2 < 0 \ \ so negate R11 to give R11 the correct sign \ for the following calculation; \ \ R11 = [y2 - y3] / [x3 - x2] \ \ So R11 contains the slope of the third \ side of the triangle, from [x2, y2] to \ [x3, y3] \ By this point we have the following; \ \ [R0, R1] = [x3, y3] \ \ [R2, R3] = [x2, y2] \ \ R4 = x-coordinate of the left edge for \ the last line that we drew \ \ R5 = x-coordinate of the right edge for \ the last line that we drew \ \ R7 = [y1 - y3] / [x3 - x1] \ = slope of [x1, y1] to [x3, y3] \ \ R11 = [y2 - y3] / [x3 - x2] \ = slope of [x2, y2] to [x3, y3] \ \ R12 = screen address of the start of the \ pixel row containing [x2, y2] SUB R9, R3, R1 \ Set R9 = R3 - R1 \ = y2 - y3 \ \ So this is the vertical distance between \ [x2, y2] and [x3, y3], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y2 and y3 SUBS R14, R2, R4, LSR #16 \ Set; \ \ R14 = R2 - R4 >> 16 \ = x2 - left edge of horizontal line \ \ So this will be zero if [x2, y2] is at the \ left edge of the horizontal line SUBNES R10, R2, R5, LSR #16 \ If the above is non-zero, set; \ \ R10 = R2 - R5 >> 16 \ = x2 - right edge of horizontal line \ \ So this will be zero if [x2, y2] is at the \ right edge of the horizontal line BNE trin15 \ If both of the above are non-zero, jump to \ trin15, as [x2, y2] doesn't match either \ edge of the horizontal line we just drew CMP R2, R4, LSR #16 \ Set the flags on R2 - R4 >> 16 MOVEQ R6, R11 \ If R2 = R4 >> 16, then x2 = left edge, so; BICEQ R4, R4, #&FF00 \ so x2 must be at the left edge, so we set BICEQ R4, R4, #&00FF \ the slope of the left edge in R6 to R11, ORREQ R4, R4, #&8000 \ as it's the left edge that is changing \ slope as we move into the top part of the \ triangle; \ \ R6 = R11 \ = slope of [x2, y2] to [x3, y3] \ \ We also reset the fractional part of R4 \ [the left edge x-coordinate] to just the \ top bit set [so that's 0.5] \ \ So this sets the slope of the left edge \ and leaves the slope of the right edge as \ before MOVNE R7, R11 \ If R2 <> R4 >> 16, then x2 <> left edge, BICNE R5, R5, #&FF00 \ so x2 must be at the right edge, so we set BICNE R5, R5, #&00FF \ the slope of the right edge in R7 to R11, ORRNE R5, R5, #&8000 \ as it's the right edge that is changing \ slope as we move into the top part of the \ triangle; \ \ R7 = R11 \ = slope of [x2, y2] to [x3, y3] \ \ We also reset the fractional part of R5 \ [the right edge x-coordinate] to just the \ top bit set [so that's 0.5] \ \ So this sets the slope of the right edge \ and leaves the slope of the left edge as \ before .trin14 ADD R4, R4, R6 \ Set R4 = R4 + R6 \ = R4 + slope of left edge \ \ So this moves the x-coordinate of the left \ edge by the correct slope as we move up by \ one pixel row ADD R5, R5, R7 \ Set R5 = R5 + R7 \ = R5 + slope of right edge \ \ So this moves the x-coordinate of the \ right edge by the correct slope as we move \ up by one pixel row ADD R11, R12, R4, LSR #16 \ Set R11 = R12 + R4 >> 16 \ = screen address of row + x1 \ \ So R11 contains the screen address of the \ left end of the line to draw MOV R10, R5, LSR #16 \ Set R10 = R5 >> 16 - R4 >> 16 SUBS R10, R10, R4, LSR #16 \ = x-coordinate of right edge \ - x-coordinate of left edge \ \ So R10 contains the length of the line \ from the left edge to the right edge BLPL DrawHorizontalLine \ If R10 is positive then we draw a line \ from the left edge to the right edge, \ using the four-pixel colour word that was \ passed to the DrawTriangle routine in R8 SUB R12, R12, #320 \ Subtract 320 from R12 so that R12 now \ points to the start of the pixel row above \ the one we just drew SUBS R9, R9, #1 \ Decrement the pixel row counter in R9, \ which contains the number of pixel rows \ between y1 and y2 BNE trin14 \ Loop back until we have drawn all the \ horizontal lines in the second part of \ the triangle, from [x2, y2] at the bottom, \ up to the level of [x3, y3] LDMFD R13!, {PC} \ Return from the subroutine .trin15 \ We jump here following these two \ calculations, if neither of them are zero; \ \ R14 = R2 - R4 >> 16 \ = x2 - left edge of horizontal line \ \ R10 = R2 - R5 >> 16 \ = x2 - right edge of horizontal line \ \ I am not sure what this signifies! RSBMI R10, R10, #0 \ The last calculation was for R10, so this \ makes R10 positive if the above result is \ negative, so it does this; \ \ R10 = |R10| CMP R14, #0 \ Set the following; RSBMI R14, R14, #0 \ \ R14 = |R14| \ So R10 and R14 are set to the magnitudes \ of the distances between [x2, y2] and each \ edge of the horizontal line [R14 for left, \ R10 for right] CMP R14, R10 \ If |R14| >= |R10| then [x2, y2] is nearer MOVHS R6, R11 \ the right edge, so set; \ \ R6 = R11 \ = slope of [x2, y2] to [x3, y3] \ \ So this sets the slope of the left edge \ and leaves the slope of the right edge as \ before [though I'm not sure why?] MOVLO R7, R11 \ Otherwise |R14| < |R10| and [x2, y2] is \ nearer the left edge, so set; \ \ R7 = R11 \ = slope of [x2, y2] to [x3, y3] \ \ So this sets the slope of the right edge \ and leaves the slope of the left edge as \ before [though I'm not sure why?] B trin14 \ Jump up to trin14 to draw the top part of \ the triangle \ ****************************************************************************** \ \ Name; DrawTriangle [Part 5 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw a triangle with a horizontal edge between [x1, y1] and \ [x2, y2] \ Deep dive; Drawing triangles \ \ ****************************************************************************** .trin16 \ If we get here then y1 = y2, so [x1, y1] \ and [x2, y2] share the same y-coordinate \ and the triangle has a horizontal edge \ between [x1, y1] and [x2, y2] SUBS R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 \ \ So this is the vertical distance between \ [x2, y2] and [x3, y3], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3 \ \ We also know that y1 = y2, so; \ \ R9 = y2 - y3 \ = y1 - y3 LDMEQIA R13!, {PC} \ If R9 = 0 then y2 = y3, which means all \ three corners share the same y-coordinate \ and are at the same height on-screen \ \ This means there is no triangle to draw, \ so return from the subroutine SUBS R14, R0, R4 \ Set R14 = R0 - R4 \ = x1 - x3 \ \ So this is the vertical distance between \ [x1, y1] and [x3, y3], i.e. the delta \ x-coordinate between the two points \ \ This will be negative if the line from \ [x1, y1] to [x3, y3] slopes up and to the \ right, or negative if the line slopes up \ and to the left RSBMI R14, R0, R4 \ If R14 is negative, set R14 = x1 - x3, so \ we have the following; \ \ R14 = |x1 - x3| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin17 \ \ R14 = |x1 - x3| >= 64 \ \ R9 = [y1 - y3] >= 64 \ \ so jump to trin17 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R6, [R10, R9, LSL #2] \ Set R6 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R6 = R9 / R14 \ \ = [y3 - y1] / |x3 - x1| B trin19 \ Jump to trin19 to skip the following and \ keep going .trin17 \ If we get here then at least one of these \ is true; \ \ R14 = |x1 - x3| >= 64 \ \ R9 = [y1 - y3] >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R6, #0 \ Set R6 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin18 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R6, R6, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R6] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin18 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R6 = R9 / R14 \ \ = [y3 - y1] / |x3 - x1| .trin19 CMP R4, R0 \ If R4 - R0 < 0 then; RSBMI R6, R6, #0 \ \ x3 - x1 < 0 \ \ so negate R6 to give R6 the correct sign \ for the following calculation; \ \ R6 = [y3 - y1] / [x3 - x1] \ \ So R6 contains the slope of the first side \ of the triangle, from [x1, y1] to [x3, y3] \ We now calculate the slope for the second \ side of the triangle, from [x2, y2] to \ [x3, y3], as follows; \ \ R7 = [y2 - y3] / [x2 - x3] SUB R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 SUBS R14, R2, R4 \ Set R14 = R4 - R2 \ = x3 - x2 RSBMI R14, R2, R4 \ If R14 is negative, set R4 = x2 - x3, so \ we have the following; \ \ R14 = |x3 - x2| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin20 \ \ R14 = |x3 - x2| >= 64 \ \ R9 = [y2 - y3] >= 64 \ \ so jump to trin20 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R7, [R10, R9, LSL #2] \ Set R7 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R7 = R9 / R14 \ \ = [y2 - y3] / |x3 - x2| B trin22 \ Jump to trin22 to skip the following and \ keep going .trin20 \ If we get here then at least one of these \ is true; \ \ R14 = |x3 - x2| >= 64 \ \ R9 = [y2 - y3] >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R7, #0 \ Set R7 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin21 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R7, R7, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R7] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin21 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R7 = R9 / R14 \ \ = [y2 - y3] / |x3 - x2| .trin22 CMP R4, R2 \ If R4 - R2 < 0 then; RSBMI R7, R7, #0 \ \ x3 - x2 < 0 \ \ so negate R7 to give R7 the correct sign \ for the following calculation; \ \ R7 = [y2 - y3] / [x3 - x2] \ \ So R7 contains the slope of the second \ side of the triangle, from [x2, y2] to \ [x3, y3] SUB R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 MOV R4, R0, LSL #16 \ Set R4 = R0 << 16 \ = x1 << 16 \ \ We scale up R4 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R4 >> 16 gives us the integral part \ \ We use R4 as the x-coordinate of the left \ end of the horizontal line to draw [after \ swapping the ends below if necessary] MOV R5, R2, LSL #16 \ Set R5 = R0 << 16 \ = x2 << 16 \ \ We scale up R5 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R5 >> 16 gives us the integral part \ \ We use R5 as the x-coordinate of the right \ end of the horizontal line to draw [after \ swapping the ends below if necessary] ORR R4, R4, #&8000 \ Set the top bit of the fractional parts of ORR R5, R5, #&8000 \ both R4 and R5 [so that's 0.5] CMP R4, R5 \ If R4 >= R5 then x1 >= x2, so we need to MOVHS R14, R6 \ swap R6 and R7, and swap R4 and R5, to MOVHS R6, R7 \ ensure that R4 is the left end of the line MOVHS R7, R14 \ and R5 is the right end, and that the MOVHS R14, R4 \ slopes are swapped accordingly MOVHS R4, R5 MOVHS R5, R14 B trin14 \ Jump to trin14 in part 4 to draw the \ triangle \ ****************************************************************************** \ \ Name; DrawTriangle [Part 6 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw a clipped triangle that's partly off-screen \ Deep dive; Drawing triangles \ \ ****************************************************************************** .trin23 CMP R1, #239 \ If all of the y-coordinates are off-screen CMPHS R3, #239 \ then return from the subroutine CMPHS R5, #239 MOVHS PC, R14 CMP R0, #320 \ If all of the x-coordinates are off-screen CMPHS R2, #320 \ then return from the subroutine CMPHS R4, #320 MOVHS PC, R14 STMFD R13!, {R14} \ Store the return address on the stack LDR R12, screenAddr \ Set R12 to the address of the screen bank \ we are drawing in, pointing just below \ the two lines of text at the top of the \ screen \ We start by ordering the triangle corners \ by y-coordinate, so [x1, y1] is the \ furthest down the screen with the highest \ y-coordinate, followed by [x2, y2] then \ [x3, y3], at the same or higher level, and \ in that order CMP R1, R3 \ If R1 > R3, jump to trin24 to skip the BGT trin24 \ following, as y1 > y2 already MOV R6, R0 \ Swap [x1, y1] and [x2, y2], so we now have MOV R0, R2 \ y1 >= y2 MOV R2, R6 MOV R6, R1 MOV R1, R3 MOV R3, R6 .trin24 CMP R1, R5 \ If R1 > R5, jump to trin25 to skip the BGT trin25 \ following, as y1 > y3 already MOV R6, R0 \ Swap [x1, y1] and [x3, y3], so we now have MOV R0, R4 \ y1 >= y3 MOV R4, R6 MOV R6, R1 MOV R1, R5 MOV R5, R6 .trin25 CMP R3, R5 \ If R3 > R5, jump to trin26 to skip the BGT trin26 \ following, as y2 > y3 already MOV R6, R2 \ Swap [x2, y2] and [x3, y3], so we now have MOV R2, R4 \ y2 >= y3 MOV R4, R6 MOV R6, R3 MOV R3, R5 MOV R5, R6 \ So the triangle coordinates are ordered \ like this on-screen; \ \ [x3, y3] \ [x2, y2] \ [x1, y1] .trin26 MOV R11, R1 \ Set R11 = R1 \ = y1 \ \ We use the value of R11 in part 11, when \ drawing the clipped triangle \ ****************************************************************************** \ \ Name; DrawTriangle [Part 7 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Calculate the slopes of [x1, y1] to [x2, y2] and [x1, y1] to \ [x3, y3] for a clipped triangle \ Deep dive; Drawing triangles \ \ ****************************************************************************** \ We now calculate the slope of the first \ side of the triangle, from [x1, y1] to \ [x2, y2], as follows; \ \ R6 = [y1 - y2] / [x2 - x1] SUBS R9, R1, R3 \ Set R9 = R1 - R3 \ = y1 - y2 \ \ So this is the vertical distance between \ [x1, y1] and [x2, y2], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y1 >= y2 BEQ trin38 \ If [x1, y1] and [x2, y2] share the same \ y-coordinate then the triangle has a \ horizontal edge between [x1, y1] and \ [x2, y2], so jump to part 10 to process \ this special case STMFD R13!, {R4-R5} \ Store the third corner's coordinates in \ [x3, y3] on the stack to we can retrieve \ them later SUBS R14, R2, R0 \ Set R14 = R2 - R0 \ = x2 - x1 \ \ So this is the vertical distance between \ [x1, y1] and [x2, y2], i.e. the delta \ x-coordinate between the two points \ \ This will be positive if the line from \ [x1, y1] to [x2, y2] slopes up and to the \ right, or negative if the line slopes up \ and to the left RSBMI R14, R2, R0 \ If R14 is negative, set R14 = x1 - x2, so \ we have the following; \ \ R14 = |x2 - x1| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin27 \ \ R14 = |x2 - x1| >= 64 \ \ R9 = [y1 - y2] >= 64 \ \ so jump to trin27 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R6, [R10, R9, LSL #2] \ Set R6 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R6 = R9 / R14 \ \ = [y1 - y2] / |x2 - x1| B trin29 \ Jump to trin29 to skip the following and \ keep going .trin27 \ If we get here then at least one of these \ is true; \ \ R14 = |x2 - x1| >= 64 \ \ R9 = [y1 - y2] >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R6, #0 \ Set R6 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin28 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R6, R6, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R6] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin28 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R6 = R9 / R14 \ \ = [y1 - y2] / |x2 - x1| .trin29 CMP R2, R0 \ If R2 - R0 < 0 then; RSBMI R6, R6, #0 \ \ x2 - x1 < 0 \ \ so negate R6 to give R6 the correct sign \ for the following calculation; \ \ R6 = [y1 - y2] / [x2 - x1] \ \ So R6 contains the slope of the first side \ of the triangle, from [x1, y1] to [x2, y2] \ We now calculate the slope for the second \ side of the triangle, from [x1, y1] to \ [x3, y3], as follows; \ \ R7 = [y1 - y3] / [x3 - x1] SUBS R9, R1, R5 \ Set R9 = R1 - R5 \ = y1 - y3 SUBS R14, R4, R0 \ Set R14 = R4 - R0 \ = x3 - x1 RSBMI R14, R4, R0 \ If R14 is negative, set R4 = x1 - x3, so \ we have the following; \ \ R14 = |x3 - x1| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin30 \ \ R14 = |x3 - x1| >= 64 \ \ R9 = [y1 - y3] >= 64 \ \ so jump to trin30 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R7, [R10, R9, LSL #2] \ Set R7 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R7 = R9 / R14 \ \ = [y1 - y3] / |x3 - x1| B trin32 \ Jump to trin32 to skip the following and \ keep going .trin30 \ If we get here then at least one of these \ is true; \ \ R14 = |x3 - x1| >= 64 \ \ R9 = [y1 - y3] >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R7, #0 \ Set R7 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin31 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R7, R7, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R7] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin31 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R7 = R9 / R14 \ \ = [y1 - y3] / |x3 - x1| .trin32 CMP R4, R0 \ If R4 - R0 < 0 then; RSBMI R7, R7, #0 \ \ x3 - x1 < 0 \ \ so negate R7 to give R7 the correct sign \ for the following calculation; \ \ R7 = [y1 - y3] / [x3 - x1] \ \ So R7 contains the slope of the second \ side of the triangle, from [x1, y1] to \ [x3, y3] \ ****************************************************************************** \ \ Name; DrawTriangle [Part 8 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw the bottom part of a clipped triangle \ Deep dive; Drawing triangles \ \ ****************************************************************************** \ By this point we have the following; \ \ [R0, R1] = [x1, y1] \ \ [R2, R3] = [x2, y2] \ \ [x3, y3] is on the stack \ \ R6 = [y1 - y2] / [x2 - x1] \ = slope of [x1, y1] to [x2, y2] \ \ R7 = [y1 - y3] / [x3 - x1] \ = slope of [x1, y1] to [x3, y3] \ \ R12 = screen address of the start of the \ pixel row containing [x1, y1] \ \ [x1, y1] is the point lowest down the \ screen and [x3, y3] is the highest up the \ screen, with [x2, y2] the point in the \ middle [in terms of y-coordinate] \ \ We now draw the triangle in two parts, \ effectively slicing the triangle in half \ with a horizontal line at y-coordinate y2, \ leaving two triangles to draw; \ \ 1. The triangle from [x1, y1] at the \ bottom up to the horizontal line with \ [x2, y2] at one end \ \ 2. The triangle from the horizontal line \ with [x2, y2] at one end, up to \ [x3, y3] at the top \ \ We start at the bottom of the triangle, at \ [x1, y1], and step upwards by one pixel \ row at a time, drawing a horizontal line \ between the two sides, until we reach the \ level of [x2, y2] \ \ As we step up each pixel row, we calculate \ the x-coordinates of each row we draw by \ adding the slopes in R6 and R7 \ \ We store the x-coordinates of the current \ horizontal line in R4 and R5, so these are \ the registers we update with the slope \ values SUBS R9, R1, R3 \ Set R9 = R1 - R3 \ = y1 - y2 \ \ So this is the vertical distance between \ [x1, y1] and [x2, y2], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y1 >= y2, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y1 and y2 MOV R4, R0, LSL #16 \ Set R4 = R0 << 16 \ = x1 << 16 \ \ We scale up R4 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R4 >> 16 gives us the integral part \ \ We use R4 as the x-coordinate of the left \ end of the horizontal line to draw ORR R4, R4, #&8000 \ Set R5 to x1, with the top bit of its MOV R5, R4 \ fractional part set [so that's 0.5] \ \ We use R5 as the x-coordinate of the right \ end of the horizontal line to draw CMP R6, R7 \ If R6 > R7, swap R6 and R7, so we know MOVGT R14, R6 \ that R6 <= R7, i.e. R6 contains the side MOVGT R6, R7 \ with the lesser slope, which will be the MOVGT R7, R14 \ slope along the left edge of the triangle BL trin45 \ Call the subroutine in part 11 to draw the \ bottom part of the triangle, clipping it \ to the screen as we go LDMFD R13!, {R0-R1} \ Fetch the coordinates for the third point \ [x3, y3] from the stack and into [R0, R1] SUBS R9, R3, R1 \ Set R9 = R3 - R1 \ = y2 - y3 \ \ So this is the vertical distance between \ [x2, y2] and [x3, y3], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y2 and y3 LDMEQIA R13!, {PC} \ If R9 is zero then [x2, y2] and [x3, y3] \ are at the same y-coordinate, so there is \ nothing to draw in the top part of the \ triangle, so return from the subroutine as \ we are done \ We now calculate the slope for the third \ side of the triangle, from [x2, y2] to \ [x3, y3], as follows; \ \ R14 = [y2 - y3] / [x2 - x1] SUBS R14, R0, R2 \ Set R14 = R0 - R2 \ = x3 - x2 RSBMI R14, R0, R2 \ If R14 is negative, set R4 = x2 - x3, so \ we have the following; \ \ R14 = |x3 - x2| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin33 \ \ R14 = |x3 - x2| >= 64 \ \ R9 = [y2 - y3] >= 64 \ \ so jump to trin33 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R12, [R10, R9, LSL #2] \ Set R12 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R12 = R9 / R14 \ \ = [y2 - y3] / |x3 - x2| B trin35 \ Jump to trin35 to skip the following and \ keep going .trin33 \ If we get here then at least one of these \ is true; \ \ R14 = |x3 - x2| >= 64 \ \ R9 = [y2 - y3] >= 64 \ \ We now calculate R12 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R12, #0 \ Set R12 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin34 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R12, R12, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R12] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin34 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R12 = R9 / R14 \ \ = [y2 - y3] / |x3 - x2| .trin35 CMP R0, R2 \ If R0 - R2 < 0 then; RSBMI R12, R12, #0 \ \ x3 - x2 < 0 \ \ so negate R12 to give R12 the correct sign \ for the following calculation; \ \ R12 = [y2 - y3] / [x3 - x2] \ \ So R12 contains the slope of the third \ side of the triangle, from [x2, y2] to \ [x3, y3] \ ****************************************************************************** \ \ Name; DrawTriangle [Part 9 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw the top part of a clipped triangle \ Deep dive; Drawing triangles \ \ ****************************************************************************** \ By this point we have the following; \ \ [R0, R1] = [x3, y3] \ \ [R2, R3] = [x2, y2] \ \ R4 = x-coordinate of the left edge for \ the last line that we drew \ \ R5 = x-coordinate of the right edge for \ the last line that we drew \ \ R7 = [y1 - y3] / [x3 - x1] \ = slope of [x1, y1] to [x3, y3] \ \ R12 = [y2 - y3] / [x3 - x2] \ = slope of [x2, y2] to [x3, y3] SUB R9, R3, R1 \ Set R9 = R3 - R1 \ = y2 - y3 \ \ So this is the vertical distance between \ [x2, y2] and [x3, y3], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3, \ so we can use this as a loop counter for \ drawing horizontal lines in the triangle \ between y-coordinates y2 and y3 \ The following calculations are very \ similar to those in part 4 for the \ unclipped triangle, but the shifts keep \ the sign of the shifted register [they use \ ASR instead of LSR] SUBS R14, R2, R4, ASR #16 \ Set; \ \ R14 = R2 - R4 >> 16 \ = x2 - left edge of horizontal line \ \ So this will be zero if [x2, y2] is at the \ left edge of the horizontal line SUBNES R10, R2, R5, ASR #16 \ If the above is non-zero, set; \ \ R10 = R2 - R5 >> 16 \ = x2 - right edge of horizontal line \ \ So this will be zero if [x2, y2] is at the \ right edge of the horizontal line BNE trin37 \ If both of the above are non-zero, jump to \ trin37, as [x2, y2] doesn't match either \ edge of the horizontal line we just drew CMP R2, R4, ASR #16 \ Set the flags on R2 - R4 >> 16 MOVEQ R6, R12 \ If R2 = R4 >> 16, then x2 = left edge, so; BICEQ R4, R4, #&FF00 \ so x2 must be at the left edge, so we set BICEQ R4, R4, #&00FF \ the slope of the left edge in R6 to R12, ORREQ R4, R4, #&8000 \ as it's the left edge that is changing \ slope as we move into the top part of the \ triangle; \ \ R6 = R12 \ = slope of [x2, y2] to [x3, y3] \ \ We also reset the fractional part of R4 \ [the left edge x-coordinate] to just the \ top bit set [so that's 0.5] \ \ So this sets the slope of the left edge \ and leaves the slope of the right edge as \ before MOVNE R7, R12 \ If R2 <> R4 >> 16, then x2 <> left edge, BICNE R5, R5, #&FF00 \ so x2 must be at the right edge, so we set BICNE R5, R5, #&00FF \ the slope of the right edge in R7 to R12, ORRNE R5, R5, #&8000 \ as it's the right edge that is changing \ slope as we move into the top part of the \ triangle; \ \ R7 = R12 \ = slope of [x2, y2] to [x3, y3] \ \ We also reset the fractional part of R5 \ [the right edge x-coordinate] to just the \ top bit set [so that's 0.5] \ \ So this sets the slope of the right edge \ and leaves the slope of the left edge as \ before .trin36 LDR R12, screenAddr \ Set R12 to the address of the screen bank \ we are drawing in, pointing just below \ the two lines of text at the top of the \ screen BL trin45 \ Call the subroutine in part 11 to draw the \ top part of the triangle, clipping it to \ the screen as we go LDMFD R13!, {PC} \ Return from the subroutine .trin37 \ We jump here following these two \ calculations, if neither of them are zero; \ \ R14 = R2 - R4 >> 16 \ = x2 - left edge of horizontal line \ \ R10 = R2 - R5 >> 16 \ = x2 - right edge of horizontal line \ \ I am not sure what this signifies! RSBMI R10, R10, #0 \ The last calculation was for R10, so this \ makes R10 positive if the above result is \ negative, so it does this; \ \ R10 = |R10| CMP R14, #0 \ Set the following; RSBMI R14, R14, #0 \ \ R14 = |R14| \ So R10 and R14 are set to the magnitudes \ of the distances between [x2, y2] and each \ edge of the horizontal line [R14 for left, \ R10 for right] CMP R14, R10 \ If |R14| >= |R10| then [x2, y2] is nearer MOVHS R6, R12 \ the right edge, so set; \ \ R6 = R12 \ = slope of [x2, y2] to [x3, y3] \ \ So this sets the slope of the left edge \ and leaves the slope of the right edge as \ before [though I'm not sure why?] MOVLO R7, R12 \ Otherwise |R14| < |R10| and [x2, y2] is \ nearer the left edge, so set; \ \ R7 = R12 \ = slope of [x2, y2] to [x3, y3] \ \ So this sets the slope of the right edge \ and leaves the slope of the left edge as \ before [though I'm not sure why?] B trin36 \ Jump up to trin14 to draw the top part of \ the triangle \ ****************************************************************************** \ \ Name; DrawTriangle [Part 10 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw a clipped triangle with a horizontal edge between [x1, y1] \ and [x2, y2] \ Deep dive; Drawing triangles \ \ ****************************************************************************** .trin38 \ If we get here then y1 = y2, so [x1, y1] \ and [x2, y2] share the same y-coordinate \ and the triangle has a horizontal edge \ between [x1, y1] and [x2, y2] SUBS R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 \ \ So this is the vertical distance between \ [x2, y2] and [x3, y3], i.e. the delta \ y-coordinate between the two points \ \ We know this is positive because y2 >= y3 \ \ We also know that y1 = y2, so; \ \ R9 = y2 - y3 \ = y1 - y3 LDMEQIA R13!, {PC} \ If R9 = 0 then y2 = y3, which means all \ three corners share the same y-coordinate \ and are at the same height on-screen \ \ This means there is no triangle to draw, \ so return from the subroutine SUBS R14, R0, R4 \ Set R14 = R0 - R4 \ = x1 - x3 \ \ So this is the vertical distance between \ [x1, y1] and [x3, y3], i.e. the delta \ x-coordinate between the two points \ \ This will be negative if the line from \ [x1, y1] to [x3, y3] slopes up and to the \ right, or negative if the line slopes up \ and to the left RSBMI R14, R0, R4 \ If R14 is negative, set R14 = x1 - x3, so \ we have the following; \ \ R14 = |x1 - x3| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin39 \ \ R14 = |x1 - x3| >= 64 \ \ R9 = [y1 - y3] >= 64 \ \ so jump to trin39 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R6, [R10, R9, LSL #2] \ Set R6 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R6 = R9 / R14 \ \ = [y3 - y1] / |x3 - x1| B trin41 \ Jump to trin41 to skip the following and \ keep going .trin39 \ If we get here then at least one of these \ is true; \ \ R14 = |x1 - x3| >= 64 \ \ R9 = [y1 - y3] >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R6, #0 \ Set R6 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin40 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R6, R6, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R6] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin40 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R6 = R9 / R14 \ \ = [y3 - y1] / |x3 - x1| .trin41 CMP R4, R0 \ If R4 - R0 < 0 then; RSBMI R6, R6, #0 \ \ x3 - x1 < 0 \ \ so negate R6 to give R6 the correct sign \ for the following calculation; \ \ R6 = [y3 - y1] / [x3 - x1] \ \ So R6 contains the slope of the first side \ of the triangle, from [x1, y1] to [x3, y3] \ We now calculate the slope for the second \ side of the triangle, from [x2, y2] to \ [x3, y3], as follows; \ \ R7 = [y2 - y3] / [x2 - x3] SUB R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 SUBS R14, R2, R4 \ Set R14 = R4 - R2 \ = x3 - x2 RSBMI R14, R2, R4 \ If R14 is negative, set R4 = x2 - x3, so \ we have the following; \ \ R14 = |x3 - x2| CMP R14, #64 \ If either R14 >= 64 or R9 >= 64, then at CMPLO R9, #64 \ least one of these is true; BHS trin42 \ \ R14 = |x3 - x2| >= 64 \ \ R9 = [y2 - y3] >= 64 \ \ so jump to trin42 to calculate the slope \ using the shift-and-subtract algorithm LDR R10, divisionTableAddr \ Set R10 to the address of the division \ tables ADD R10, R10, R14, LSL #8 \ Set R10 to the address of the division \ table containing n / R14, for n = 0 to 63 \ \ This works because each division table \ contains 64 words, or 256 bytes, so the \ address of table n / d is; \ \ divisionTable + d * 256 LDR R7, [R10, R9, LSL #2] \ Set R7 to the R9-th word in the division \ table containing n / R14, so this \ calculates the following; \ \ R7 = R9 / R14 \ \ = [y2 - y3] / |x3 - x2| B trin44 \ Jump to trin44 to skip the following and \ keep going .trin42 \ If we get here then at least one of these \ is true; \ \ R14 = |x3 - x2| >= 64 \ \ R9 = [y2 - y3] >= 64 \ \ We now calculate R2 = R9 / R14 using the \ shift-and-subtract division algorithm MOV R9, R9, LSL #16 \ First we scale R9 up as far as we can, to \ make the result as accurate as possible MOV R7, #0 \ Set R7 = 0 to contain the result MOV R10, #&80000000 \ Set bit 31 of R10 so we can shift it to \ the right in each iteration, using it as a \ counter .trin43 MOVS R14, R14, LSL #1 \ Shift R14 left, moving the top bit into \ the C flag CMPCC R14, R9 \ If we shifted a 0 out of the top of R14, \ test for a possible subtraction SUBCS R14, R14, R9 \ If we shifted a 1 out of the top of R14 or ORRCS R7, R7, R10 \ R14 >= R9, then do the subtraction; \ \ R14 = R14 - R9 \ \ and set the relevant bit in the result \ [i.e. apply the set bit in R10 to the \ result in R7] MOVS R10, R10, LSR #1 \ Shift R10 to the right, moving bit 0 into \ the C flag BCC trin43 \ Loop back until we shift the 1 out of the \ right end of R10 [after 32 shifts] \ So we now have the following result; \ \ R7 = R9 / R14 \ \ = [y2 - y3] / |x3 - x2| .trin44 CMP R4, R2 \ If R4 - R2 < 0 then; RSBMI R7, R7, #0 \ \ x3 - x2 < 0 \ \ so negate R7 to give R7 the correct sign \ for the following calculation; \ \ R7 = [y2 - y3] / [x3 - x2] \ \ So R7 contains the slope of the second \ side of the triangle, from [x2, y2] to \ [x3, y3] SUB R9, R3, R5 \ Set R9 = R3 - R5 \ = y2 - y3 MOV R4, R0, LSL #16 \ Set R4 = R0 << 16 \ = x1 << 16 \ \ We scale up R4 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R4 >> 16 gives us the integral part \ \ We use R4 as the x-coordinate of the left \ end of the horizontal line to draw [after \ swapping the ends below if necessary] MOV R5, R2, LSL #16 \ Set R5 = R0 << 16 \ = x2 << 16 \ \ We scale up R5 so that it can contain a \ fractional result - we will treat the \ bottom 16 bits as the fraction, so \ R5 >> 16 gives us the integral part \ \ We use R5 as the x-coordinate of the right \ end of the horizontal line to draw [after \ swapping the ends below if necessary] ORR R4, R4, #&8000 \ Set the top bit of the fractional parts of ORR R5, R5, #&8000 \ both R4 and R5 [so that's 0.5] CMP R4, R5 \ If R4 > R5 then x1 > x2, so we need to MOVGT R14, R6 \ swap R6 and R7, and swap R4 and R5, to MOVGT R6, R7 \ ensure that R4 is the left end of the line MOVGT R7, R14 \ and R5 is the right end, and that the MOVGT R14, R4 \ slopes are swapped accordingly MOVGT R4, R5 MOVGT R5, R14 B trin36 \ Jump to trin36 in part 9 to draw the \ triangle \ ****************************************************************************** \ \ Name; DrawTriangle [Part 11 of 11] \ Type; Subroutine \ Category; Drawing triangles \ Summary; Draw a triangle, clipping it to the screen as we go \ Deep dive; Drawing triangles \ \ ****************************************************************************** .trin45 \ We call this part of the routine as a \ subroutine, so returning from the \ subroutine in this context means returning \ to the main triangle code above \ \ We call it with the following values; \ \ R4 = x-coordinate of the left end of the \ horizontal line to draw \ \ R5 = x-coordinate of the right end of \ the horizontal line to draw \ \ R6 = left edge slope \ \ R7 = right edge slope \ \ R8 = four-pixel colour word \ \ R9 = the delta y-coordinate between \ [x1, y1] and [x2, y2], which \ defines the height of the triangle \ to draw \ \ R11 = y1, so that's the y-coordinate of \ the bottom point of the triangle \ to draw \ \ R12 = screen address of the start of the \ pixel row containing [x1, y1] CMP R9, #256 \ If R9 >= 256 then the triangle to draw is MOVHS PC, R14 \ taller than the screen, which is just too \ big, so return from the subroutine without \ drawing this triangle STMFD R13!, {R14} \ Store the return address on the stack \ We now draw the triangle from bottom to \ top, keeping track of the y-coordinate of \ the current pixel row in R11 and counting \ down the pixel rows in R9 .trin46 ADD R4, R4, R6 \ Set R4 = R4 + R6 \ = R4 + slope of left edge \ \ So this moves the x-coordinate of the left \ edge by the correct slope as we move up by \ one pixel row ADD R5, R5, R7 \ Set R5 = R5 + R7 \ = R5 + slope of right edge \ \ So this moves the x-coordinate of the \ right edge by the correct slope as we move \ up by one pixel row CMP R11, #0 \ If R11 < 0 then we are already off the top LDMMIIA R13!, {PC} \ of the screen, so return from the \ subroutine CMP R11, #239 \ If R11 >= 239 then we are off the bottom BHS trin48 \ of the screen, so jump to trin48 to move \ on to the next row above in the triangle, \ so the triangle is clipped to the bottom \ of the screen STMFD R13!, {R11} \ Store the y-coordinate of the current row \ on the stack so we can retrieve it below ADD R11, R11, R11, LSL #2 \ Set R11 = R11 + R11 * 4 \ = y-coord of row * 5 ADD R11, R12, R11, LSL #6 \ Set R11 = R12 + R11 * 64 \ = screen address + 320 * y-coord \ \ So R11 points to the start of the pixel \ row in screen memory CMP R4, #&01400000 \ If R4 > &01400000, then the left end of BPL trin47 \ the line is greater than &140 [as the \ fractional number &01400000 represents the \ integer &0140], so it is greater than 320 \ and past the right edge of the screen \ \ This means the whole line is off-screen, \ so jump to trin47 to move on to the next \ row above in the triangle CMP R5, #0 \ If R5 < 0, then the right end of the line BMI trin47 \ is off the left edge of the screen, so \ jump to trin47 to move on to the next row \ above in the triangle CMP R4, #0 \ If the left end of the line is positive, MOVPL R0, R4, LSR #16 \ then it's on-screen, so set; \ \ R0 = R4 >> 16 \ = x-coordinate of left end of line \ \ The shift removes the fractional part from \ R4 ADDPL R11, R11, R4, LSR #16 \ If the left end of the line is positive, \ set R11 = R11 + R4 >> 16 \ = screen address of row + x1 \ \ So R11 contains the screen address of the \ left end of the line to draw MOVMI R0, #0 \ If the left end of the line is negative, \ i.e. off the left edge of the screen, then \ set R0 = 0 \ So R0 is set to the x-coordinate of the \ left end of the line we want to draw CMP R5, #&01400000 \ If R5 < &01400000, then the right end of RSBLO R10, R0, R5, LSR #16 \ the line is less than &140 [as the \ fractional number &01400000 represents the \ integer &0140], so it is less than 320 \ and is not past the right edge of the \ screen, so set; \ \ R10 = R5 >> 16 - R0 \ = x-coordinate of right end of line \ - x-coordinate of left end of line RSBHS R10, R0, #320 \ If the right end of the line is off the \ right side of the screen, set; \ \ R10 = 320 - x-coordinate of left end of \ line \ So R10 contains the length of the line \ from the left edge to the right edge CMP R10, #0 \ If R10 is positive then we draw a line BLPL DrawHorizontalLine \ from the left edge to the right edge, \ using the four-pixel colour word that was \ passed to the DrawTriangle routine in R8 .trin47 LDMFD R13!, {R11} \ Retrieve the y-coordinate of the current \ row from the stack, which we stored above, \ and put it into R11 once more .trin48 SUB R11, R11, #1 \ Decrement R11 so we move one pixel line up \ the screen SUBS R9, R9, #1 \ Decrement the triangle height counter in \ R9 BNE trin46 \ Loop back to draw the next line in the \ triangle until we have drawn all R9 \ horizontal lines LDMFD R13!, {PC} \ Return from the subroutine [and rejoin the \ main triangle routine] \ ****************************************************************************** \ \ Name; lineJump \ Type; Variable \ Category; Drawing lines \ Summary; Jump table for drawing a horizontal line of between 0 and 17 \ pixels using the relevant entry point in DrawLineSegment \ Deep dive; Drawing triangles \ \ ****************************************************************************** .lineJump EQUD DrawLineSegment + 17 * 4 \ Draw a horizontal line of 0 pixels EQUD DrawLineSegment + 16 * 4 \ Draw a horizontal line of 1 pixels EQUD DrawLineSegment + 15 * 4 \ Draw a horizontal line of 2 pixels EQUD DrawLineSegment + 14 * 4 \ Draw a horizontal line of 3 pixels EQUD DrawLineSegment + 13 * 4 \ Draw a horizontal line of 4 pixels EQUD DrawLineSegment + 12 * 4 \ Draw a horizontal line of 5 pixels EQUD DrawLineSegment + 11 * 4 \ Draw a horizontal line of 6 pixels EQUD DrawLineSegment + 10 * 4 \ Draw a horizontal line of 7 pixels EQUD DrawLineSegment + 9 * 4 \ Draw a horizontal line of 8 pixels EQUD DrawLineSegment + 8 * 4 \ Draw a horizontal line of 9 pixels EQUD DrawLineSegment + 7 * 4 \ Draw a horizontal line of 10 pixels EQUD DrawLineSegment + 6 * 4 \ Draw a horizontal line of 11 pixels EQUD DrawLineSegment + 5 * 4 \ Draw a horizontal line of 12 pixels EQUD DrawLineSegment + 4 * 4 \ Draw a horizontal line of 13 pixels EQUD DrawLineSegment + 3 * 4 \ Draw a horizontal line of 14 pixels EQUD DrawLineSegment + 2 * 4 \ Draw a horizontal line of 15 pixels EQUD DrawLineSegment + 1 * 4 \ Draw a horizontal line of 16 pixels EQUD DrawLineSegment \ Draw a horizontal line of 17 pixels \ ****************************************************************************** \ \ Name; DrawHorizontalLine \ Type; Subroutine \ Category; Drawing lines \ Summary; Draw a horizontal line \ Deep dive; Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R8 Line colour [in the form of a four-pixel \ colour word] \ \ R10 Line length \ \ R11 Screen address of the left end of the line \ \ ****************************************************************************** .DrawHorizontalLine CMP R10, #18 \ If R10 < 18 then set the flags so the next \ set of instructions are run [so for lines \ of length 17 pixels and fewer, we jump to \ the relevant routine in the lineJump to \ draw the line ] varOffset = P% + 8 - lineJump : REM Set labOffset to the offset back to the : REM lineJump table from the next instruction [ OPT pass% ADDLO R0, PC, R10, LSL #2 \ If R10 < 18, jump to the address in entry LDRLO PC, [R0, #-varOffset] \ R10 in the lineJump table, which will draw \ a horizontal line R10 pixels long and \ return from the subroutine using a tail \ call \ If we get here then we need to draw a line \ of 18 pixels or more ADD R10, R11, R10 \ Set R10 to the screen address of the right \ end of the line [i.e. the address of the \ left end in R11 plus the width in R10] BIC R0, R10, #%00000011 \ Set R0 to the screen address with bits 0 \ and 1 cleared, so R0 points to the word in \ screen memory that contains the right end \ of the line [the 'right cap'] \ So by this point R11 points to the word in \ screen memory that contains the left cap \ of the line [which is the word containing \ the left end of the line], and R0 points \ to the right cap \ \ The caps can contain 1, 2, 3 or 4 pixels, \ as each 32-bit word contains four one-byte \ pixels \ \ If a cap contains four pixels, then bits 0 \ and 1 of the cap address will be zero, so \ in the following we draw the line as \ follows; \ \ * If the left cap contains 1, 2 or 3 \ pixels, draw those pixels \ \ * Draw the portion of the line between \ the left and right caps, drawing it \ one word [four pixels] at a time \ \ * If the right cap contains 1, 2 or 3 \ pixels, draw those pixels \ \ We start with the left cap at screen \ address R11 TST R11, #%00000011 \ If R11 is not a multiple of 4 then one or STRNEB R8, [R11], #1 \ both of bits 0 and 1 will be non-zero, so \ set the pixel at R11 to the colour in R8 \ and increment R11 \ \ So this draws the pixel at the end of the \ line, and it increments R11 so R11 now \ points to the next pixel along TSTNE R11, #%00000011 \ If R11 is still not a multiple of 4 then STRNEB R8, [R11], #1 \ bits 0 and 1 will still be non-zero, so \ set the pixel at R11 to the colour in R8 \ and increment R11 \ \ So this draws the second pixel in the line \ if drawing the first one didn't take us to \ a word boundary, and it increments R11 so \ R11 now points to the next pixel along TSTNE R11, #%00000011 \ If R11 is still not a multiple of 4 then STRNEB R8, [R11], #1 \ bits 0 and 1 will still be non-zero, so \ set the pixel at R11 to the colour in R8 \ and increment R11 \ \ So this draws the third pixel in the line \ if drawing the first two didn't take us to \ a word boundary, and it increments R11 so \ R11 now points to the next pixel along \ By this point R11 will definitely be on a \ word boundary and we have successfully \ drawn the left cap of the line, so now we \ draw the centre portion of the line, all \ the way to the right cap \ \ We do this in a loop that is unrolled once \ to speed things up a little but .hlin1 STR R8, [R11], #4 \ Draw a full word [four pixels] in the \ colour in R8 at screen address R11 and \ increment R11 by 4 to move on to the next \ word CMP R11, R0 \ If R11 < R0 then we have not yet reached STRLO R8, [R11], #4 \ the right cap, so draw another word in \ memory and increment R11 again CMPLO R11, R0 \ If R11 < R0 then we have still not reached BLO hlin1 \ the right cap, so loop back to hlin1 to \ keep drawing the centre portion of the \ line until we do reach the right cap CMP R11, R10 \ If R11 < R10 then we have not yet reached STRLOB R8, [R11], #1 \ the end of the line, so we draw up to CMPLO R11, R10 \ three pixels in the final word of the line STRLOB R8, [R11], #1 \ by simply drawing each pixel, incrementing CMPLO R11, R10 \ R11 and then re-checking whether R11 has STRLOB R8, [R11], #1 \ reached R10 [and drawing the next pixel if \ it hasn't] MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; DrawLineSegment \ Type; Subroutine \ Category; Drawing lines \ Summary; Draw a horizontal line of between 0 and 17 pixels by jumping to \ the relevant entry point \ Deep dive; Drawing triangles \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R8 Line colour [in the form of a four-pixel \ colour word] \ \ R11 Screen address of left end of line \ \ ****************************************************************************** .DrawLineSegment STRB R8, [R11, #16] \ Draw a horizontal line of 17 pixels STRB R8, [R11, #15] \ Draw a horizontal line of 16 pixels STRB R8, [R11, #14] \ Draw a horizontal line of 15 pixels STRB R8, [R11, #13] \ Draw a horizontal line of 14 pixels STRB R8, [R11, #12] \ Draw a horizontal line of 13 pixels STRB R8, [R11, #11] \ Draw a horizontal line of 12 pixels STRB R8, [R11, #10] \ Draw a horizontal line of 11 pixels STRB R8, [R11, #9] \ Draw a horizontal line of 10 pixels STRB R8, [R11, #8] \ Draw a horizontal line of 9 pixels STRB R8, [R11, #7] \ Draw a horizontal line of 8 pixels STRB R8, [R11, #6] \ Draw a horizontal line of 7 pixels STRB R8, [R11, #5] \ Draw a horizontal line of 6 pixels STRB R8, [R11, #4] \ Draw a horizontal line of 5 pixels STRB R8, [R11, #3] \ Draw a horizontal line of 4 pixels STRB R8, [R11, #2] \ Draw a horizontal line of 3 pixels STRB R8, [R11, #1] \ Draw a horizontal line of 2 pixels STRB R8, [R11] \ Draw a horizontal line of 1 pixel MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; workspaceAddr \ Type; Variable \ Category; Start and end \ Summary; The address of the game's variable workspace \ \ ****************************************************************************** .workspaceAddr EQUD workspace \ ****************************************************************************** \ \ Name; stackAddr \ Type; Variable \ Category; Start and end \ Summary; The address of the game's stack \ \ ****************************************************************************** .stackAddr EQUD stack \ ****************************************************************************** \ \ Name; memoryTestAddr \ Type; Variable \ Category; Start and end \ Summary; The memory location to check to ensure we have enough memory for \ the game \ \ ****************************************************************************** .memoryTestAddr EQUD 0 \ ****************************************************************************** \ \ Name; initialScore \ Type; Variable \ Category; Score bar \ Summary; The score at the start of each game \ \ ****************************************************************************** .initialScore EQUD 500 \ ****************************************************************************** \ \ Name; initialHighScore \ Type; Variable \ Category; Score bar \ Summary; The high score when we first load the game \ \ ****************************************************************************** .initialHighScore EQUD 500 \ ****************************************************************************** \ \ Name; initialFuelLevel \ Type; Variable \ Category; Score bar \ Summary; The fuel level at the start of each new game \ \ ****************************************************************************** .initialFuelLevel EQUD 3413 \ ****************************************************************************** \ \ Name; stackPointerOnEntry \ Type; Variable \ Category; Start and end \ Summary; Stores the stack pointer from when the game was run \ \ ****************************************************************************** .stackPointerOnEntry EQUD 0 \ ****************************************************************************** \ \ Name; mouseParameters \ Type; Variable \ Category; Player \ Summary; The parameters for OS_Word 21,3 for resetting the mouse position \ \ ****************************************************************************** .mouseParameters EQUB 3 \ This is OS_Word 21,3, so the reason code \ in the first byte is 3 EQUW 511 \ The X position for the mouse EQUW 511 \ The Y position for the mouse OPT FN_AlignWithZeroes \ ****************************************************************************** \ \ Name; mouseParametersAddr \ Type; Variable \ Category; Player \ Summary; The address of the OS_Word block for resetting the mouse position \ \ ****************************************************************************** .mouseParametersAddr EQUD mouseParameters \ ****************************************************************************** \ \ Name; ResetMousePosition \ Type; Subroutine \ Category; Player \ Summary; Reset the mouse position to [511, 511], ready for the game \ \ ****************************************************************************** .ResetMousePosition LDR R1, mouseParametersAddr \ Call OS_Word 21,3 to reset the mouse MOV R0, #21 \ position to [511, 511] SWI OS_Word MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; AbortWithMemoryError \ Type; Subroutine \ Category; Start and end \ Summary; Show a memory error and abort the game \ \ ****************************************************************************** .AbortWithMemoryError STRB R1, [R0] \ Restore the byte that we have been poking \ in the Entry routine to determine whether \ there is enough memory SWI OS_WriteS \ Print an error explaining that there isn't EQUS "There is not enough " \ enough free memory to run the game EQUS "memory to run this " EQUS "game." EQUW &0D0A EQUS "Please *CONFIGURE your " EQUS "machine to free more " EQUS "memory." EQUB 0 OPT FN_AlignWithZeroes B ReturnToDesktop \ Jump to ReturnToDesktop to quit the game \ and return to the desktop \ ****************************************************************************** \ \ Name; Entry \ Type; Subroutine \ Category; Start and end \ Summary; The main entry point for the game \ \ ****************************************************************************** .Entry MOV R0, #22 \ Change to screen mode 15, which uses 160K SWI OS_WriteC \ of screen memory, the same amount that we MOV R0, #15 \ need for the game [though in the game we SWI OS_WriteC \ actually use two mode 13 screens, each of \ which is 80K] STR R13, stackPointerOnEntry \ Store the stack pointer from when the game \ started in stackPointerOnEntry, so we can \ retore it when we quit the game LDR R13, stackAddr \ Set the stack pointer in R13 to stackAddr, \ so the 512 bytes descending from stackAddr \ can be used as the game's stack LDR R11, workspaceAddr \ Set R11 to the address of the workspace, \ which is stored in workspaceAddr, so we \ can access workspace variables by applying \ an offset to R11 using [R11, #offset] STMFD R13!, {R14} \ R14 contains the address we should return \ to when quitting the game, so store it on \ the stack so we can retrieve it later LDR R0, memoryTestAddr \ Set R0 to the address in memoryTestAddr, \ which points to a byte that will be \ writable if there is enough free memory \ for running the game LDRB R1, [R0] \ Set R1 to the current contents of memory \ at R0, so we can restore it after running \ our memory tests MOV R2, #&AA \ Store the value &AA in our test memory STRB R2, [R0] \ location in R0, read it back, and if the LDRB R3, [R0] \ returned value is different to &AA, jump CMP R2, R3 \ to AbortWithMemoryError to abort the game, BNE AbortWithMemoryError \ as there isn't enough free memory MOV R2, #&55 \ Store the value &55 in our test memory STRB R2, [R0] \ location in R0, read it back, and if the LDRB R3, [R0] \ returned value is different to &55, jump CMP R2, R3 \ to AbortWithMemoryError to abort the game, BNE AbortWithMemoryError \ as there isn't enough free memory STRB R1, [R0] \ Restore the original contents of address \ R0 so it's unchanged by our memory tests MOV R0, #4 \ Call OS_Byte 4 to set the cursor keys to MOV R1, #1 \ return ASCII values, so they don't move MOV R2, #0 \ the cursor during the game SWI OS_Byte BL InitialiseParticleData \ Initialise the particle data buffer and \ associated variables MOV R0, #22 \ Change to screen mode 13 [320×256 pixels SWI OS_WriteC \ with 256 colours], which will display MOV R0, #13 \ screen bank 1 SWI OS_WriteC SWI OS_WriteS \ Write the game's title into the top line EQUS "Lander Demo/Practice " \ of the screen, where it remains for the EQUS "(C) D.J.Braben 1987" \ whole game EQUB 0 OPT FN_AlignWithZeroes BL SwitchScreenBank \ Switch screen bank, so VDU commands go to \ screen bank 2 MOV R0, #22 \ Change to screen mode 13 with shadow SWI OS_WriteC \ memory enabled [i.e. mode 128 + 13], which MOV R0, #128+13 \ creates two mode 13 screen banks SWI OS_WriteC \ MOV R0, #23 \ Start printing the following VDU command; SWI OS_WriteC \ MOV R0, #1 \ VDU 23, 1, 0, 0, 0, 0, 0, 0, 0, 0 SWI OS_WriteC \ \ which disables the cursor MOV R8, #8 \ We now want to print the eight zeroes in \ the above command, so set a loop counter \ in R8 .entr1 MOV R0, #0 \ This loop prints the eight zeroes in the SWI OS_WriteC \ above VDU command SUBS R8, R8, #1 \ Decrement the loop counter BNE entr1 \ Loop back until we have printed all eight \ zeroes SWI OS_WriteS \ Write the game's title into the top line EQUS "Lander Demo/Practice " \ of screen bank 2, so the same title EQUS "(C) D.J.Braben 1987" \ appears at the top of both screen banks EQUB 0 OPT FN_AlignWithZeroes LDR R0, initialScore \ Initialise currentScore to the score that STR R0, [R11, #currentScore] \ we start each game with, which is set in \ initialScore LDR R0, initialHighScore \ Initialise highScore to the high score STR R0, [R11, #highScore] \ that we start the game with, which is set \ in initialHighScore \ ****************************************************************************** \ \ Name; StartNewGame \ Type; Subroutine \ Category; Main loop \ Summary; Start a brand new game with a full set of lives and a newly \ generated set of objects \ \ ****************************************************************************** .StartNewGame \ We start by initialising the scores and \ printing them on the score bar LDR R0, [R11, #highScore] \ Set R0 to the current high score LDR R1, [R11, #currentScore] \ Set R1 to our current score CMP R1, R0 \ If R1 - R0 is positive, i.e. R1 >= R0, MOVPL R0, R1 \ then our latest score is higher than the \ high score, so set R0 to our latest score \ \ So R0 is set to the maximum of highScore \ and currentScore, which is the new high \ score STRHS R0, [R11, #highScore] \ If R1 >= R0 then we just updated the high \ score and the new high score is in R0, so \ store the new high score in highScore MOV R1, #35 \ Set [R1, R2] = [35, 1] so the following MOV R2, #1 \ call to PrintScoreInBothBanks prints the \ high score at column 35 on row 1 BL PrintScoreInBothBanks \ Print the high score in R0 at column 35 on \ row 1, at the right end of the score bar LDR R0, initialScore \ Initialise currentScore to the score that STR R0, [R11, #currentScore] \ we start each game with, which is set in \ initialScore \ We now initialise more game variables LDR R0, initialFuelLevel \ Initialise fuelLevel to the fuel level STR R0, [R11, #fuelLevel] \ that we start each game with, which is set \ in initialFuelLevel MOV R0, #&30000 \ Initialise gravity to &30000 STR R0, [R11, #gravity] MOV R0, #3 \ Initialise the number of lives to 3 STR R0, [R11, #remainingLives] \ ****************************************************************************** \ \ Name; PlaceObjectsOnMap \ Type; Subroutine \ Category; 3D objects \ Summary; Randomly place a number of objects on the map, avoiding the sea \ and the launchpad \ Deep dive; Placing objects on the map \ \ ------------------------------------------------------------------------------ \ \ The object map at objectMap contains one byte for each tile on the landscape. \ This byte determines which object [if any] appears on that tile, where objects \ are trees, buildings, rockets and so on. \ \ ****************************************************************************** .PlaceObjectsOnMap \ We start by initialising the object map \ at objectMap with values of &FF, which \ indicates no objects on the map MVN R0, #0 \ Set R0 to R3 to &FF so we can poke them MVN R1, #0 \ into memory at objectMap [this sets each MVN R2, #0 \ 32-bit register to &FFFFFFFF, which is the MVN R3, #0 \ same as four bytes, each of which is &FF] MOV R4, #256*256 \ Set R4 to use as a byte counter in the \ following loop, which works through each \ of the coordinates in the 256x256 map ADD R6, R11, #objectMap \ Set R6 to the address of the object map .snew1 STMIA R6!, {R0-R3} \ Store four words, or 16 bytes, at R6, \ updating R6 as we go, with each byte \ containing &FF SUBS R4, R4, #16 \ Subtract 16 from the byte counter in R4 \ as we just initialised 16 bytes BNE snew1 \ Loop back until we have set all bytes in \ the object map to &FF \ We now add 2048 randomly chosen 3D objects \ to the object map, each one at a random \ coordinate and of a random type ADD R6, R11, #objectMap \ Set R6 to the address of the object map MOV R5, #2048 \ Set R5 to a loop counter as we work \ through all 2048 objects .snew2 BL GetRandomNumbers \ Set R0 and R1 to random numbers MOV R8, R0 \ Set R8 = R0, so R8 is a random number \ \ We use the top byte of R8 below as the \ x-coordinate of the 3D object MOV R9, R0, LSL #8 \ Set R9 = R0 << 8 \ \ We use the top byte of R9 below as the \ z-coordinate of the 3D object, so the \ shift ensures that the top bytes of R8 \ and R9 are different STMFD R13!, {R0} \ Store R0 on the stack so it doesn't get \ corrupted by the following call to \ GetLandscapeAltitude BL GetLandscapeAltitude \ Set R0 to the altitude of the landscape at \ coordinates [x, z] = [R8, R9] MOV R14, R0 \ Set R14 to the landscape altitude returned \ in R0 LDMFD R13!, {R0} \ Retrieve the value of R0 from the stack \ that we stored above CMP R14, #SEA_LEVEL \ If R14 = LAUNCHPAD_ALTITUDE or SEA_LEVEL, CMPNE R14, #LAUNCHPAD_ALTITUDE \ jump to snew3 to skip the following, so we BEQ snew3 \ do not place any objects on the sea or the \ launchpad AND R0, R0, #7 \ Reduce R0 to the range 1 to 8, so this is ADD R0, R0, #1 \ a random number that we use to determine \ the type of object we're adding [so there \ are lots of trees]; \ \ * 1 = small leafy tree \ * 2 = tall leafy tree \ * 3 = small leafy tree \ * 4 = small leafy tree \ * 5 = gazebo \ * 6 = tall leafy tree \ * 7 = fir tree \ * 8 = building \ \ See the objectTypes table for details of \ object types AND R9, R9, #&FF000000 \ Set the bottom three bytes of R9 to zero, \ leaving just the top byte, so we can use \ it in the following \ The object at coordinate [x, z] is stored \ at offset &zzxx within objectMap, where \ &xx and &zz are the top bytes of the full \ 32-bit coordinates \ \ In the following we set this address in \ R14; \ \ objectMap + [R8 >> 24] + [R9 >> 16] \ \ R8 is shifted into the bottom byte of R14, \ so that's the x-coordinate, and R9 is \ shifted into the second byte of R14, so \ that's the z-coordinate ADD R14, R6, R8, LSR #24 \ Set R14 = R6 + [R8 >> 24] + [R9 >> 16] ADD R14, R14, R9, LSR #16 STRB R0, [R14] \ Store R0 in the address in R14 to add the \ object to the map at the coordinates given \ by the top bytes of R8 and R9, at [R8, R9] .snew3 SUBS R5, R5, #1 \ Decrement the loop counter BPL snew2 \ Loop back until we have done all &800 \ iterations \ We now place three rockets along the right \ edge of the launchpad, with a rocket on \ every other tile, working from front to \ back, into the screen and parallel to the \ z-axis, and with each one having an \ x-coordinate of 7 MOV R0, #LAUNCHPAD_OBJECT \ Set R0 to the type of object along the \ right edge of the launchpad, which is a \ rocket of type 9 STRB R0, [R6, #&0107] \ Add the front rocket to coordinate [7, 1] STRB R0, [R6, #&0307] \ Add the middle rocket to coordinate [7, 3] STRB R0, [R6, #&0507] \ Add the rear rocket to coordinate [7, 5] \ ****************************************************************************** \ \ Name; PlacePlayerOnLaunchpad \ Type; Subroutine \ Category; Player \ Summary; The main entry point for the game \ \ ****************************************************************************** .PlacePlayerOnLaunchpad LDR R0, [R11, #remainingLives] \ Set R0 to the number of remaining lives MOV R1, #30 \ Set [R1, R2] = [30, 1] so the following MOV R2, #1 \ call to PrintScoreInBothBanks prints the \ number of lives at column 30 on row 1 BL PrintScoreInBothBanks \ Print the number of lives in R0 at column \ 30 on row 1, just before the high score \ towards the right end of the score bar MVN R0, #0 \ Set playingGame = -1 to flag that the game STR R0, [R11, #playingGame] \ is being played and that this is not the \ crash animation MOV R0, #0 \ Set xCamera = 0 and zCamera = 0 STR R0, [R11, #xCamera] \ STR R0, [R11, #zCamera] \ This doesn't have any effect as the camera \ position is set at the start of the main \ loop by the call to MoveAndDrawPlayer, \ which overwrites these values STR R0, [R11, #shipDirection] \ Set shipDirection = 0 so the ship faces \ right when the game starts [though this is \ quickly corrected when the game uses the \ mouse coordinates to calculate the \ direction] MOV R0, #1 \ Set shipPitch = 1 so the ship is very STR R0, [R11, #shipPitch] \ slightly pitched up for take-off MOV R0, #LAUNCHPAD_SIZE/2 \ Set the starting coordinates of the MOV R2, R0 \ player's ship as follows; ADD R3, R11, #xPlayer \ MOV R1, #LAUNCHPAD_Y \ xPlayer = LAUNCHPAD_SIZE / 2 STMIA R3!, {R0-R2} \ yPlayer = LAUNCHPAD_Y \ zPlayer = LAUNCHPAD_SIZE / 2 \ \ which is in the middle of the launchpad MOV R0, #0 \ Set the player's velocity to zero as MOV R1, #0 \ follows; MOV R2, #0 \ STMIA R3!, {R0-R2} \ xVelocity = 0 \ yVelocity = 0 \ zVelocity = 0 BL ResetMousePosition \ Reset the mouse position to [511, 511], \ ready for the game \ ****************************************************************************** \ \ Name; MainLoop \ Type; Subroutine \ Category; Main loop \ Summary; The main game loop \ Deep dive; The main game loop \ \ ****************************************************************************** .MainLoop MOV R0, #129 \ Call OS_Byte 129 to read the keyboard with MOV R1, #0 \ the time limit in R1 and R2 [so that's MOV R2, #0 \ with no time limit as R1 and R2 are zero], SWI OS_Byte \ returning the result in R2 TEQ R2, #&1B \ If R2 = &1B then an escape condition BEQ EndGame \ occurred during the keyboard scan [in \ other words, Escape was pressed], so jump \ to EndGame to acknowledge the escape \ condition and quit the game BL MoveAndDrawPlayer \ Move the player's ship and draw it into \ the graphics buffers \ We now set up the rotation matrix for the \ rocks, using the main loop counter to \ generate rotation angles that change along \ with the main loop [so the rocks spin at a \ nice steady speed] LDR R0, [R11, #mainLoopCount] \ Set R0 = mainLoopCount << 24 MOV R0, R0, LSL #24 MOV R1, R0, LSL #1 \ Set R1 = mainLoopCount << 25 BL CalculateRotationMatrix \ Calculate the rotation matrix from the \ 'angles' given in R0 and R1, which we can \ apply to any rocks we draw in the \ MoveAndDrawParticles routine [as rocks are \ only rotating 3D objects apart from the \ player, and the player calculates its own \ rotation matrix] BL DropRocksFromTheSky \ If the score is 800 or more, then randomly \ drop rocks from the sky BL MoveAndDrawParticles \ Move and draw all the particles, such as \ smoke clouds and bullets, into the \ graphics buffers BL DrawObjects \ Draw all the objects, such as trees and \ buildings, into the graphics buffers BL AddTerminatorsToBuffers \ Add terminators to the ends of the \ graphics buffers so we know when to stop \ drawing BL DrawLandscapeAndBuffers \ Draw the landscape and the contents of the \ graphics buffers BL PrintCurrentScore \ Print the number of remaining bullets at \ the left end of the score bar BL DrawFuelLevel \ Draw the fuel bar BL SwitchScreenBank \ Switch screen banks and clear the newly \ hidden screen bank to black LDR R14, [R11, #mainLoopCount] \ Increment the main loop counter ADD R14, R14, #1 STR R14, [R11, #mainLoopCount] B MainLoop \ Loop back to repeat the main loop \ ****************************************************************************** \ \ Name; EndGame \ Type; Subroutine \ Category; Main loop \ Summary; Finish the game \ \ ****************************************************************************** .EndGame MOV R0, #126 \ Call OS_Byte 126 to acknowledge the escape SWI OS_Byte \ condition caused by the player pressing \ Escape in the main loop MOV R0, #22 \ Change to screen mode 0 SWI OS_WriteC MOV R0, #0 SWI OS_WriteC MOV R0, #4 \ Call OS_Byte 4 to set the cursor keys to MOV R1, #0 \ move the cursor, so they work normally MOV R2, #0 \ again SWI OS_Byte \ ****************************************************************************** \ \ Name; ReturnToDesktop \ Type; Subroutine \ Category; Start and end \ Summary; Return to the desktop \ \ ****************************************************************************** .ReturnToDesktop LDMFD R13!, {R14} \ Restore the value from the stack and store \ it in R14, so R14 contains the same value \ that it had when the game was first run, \ and which we stored on the stack in the \ Entry routine \ \ So this sets R14 to the address we should \ return to when quitting the game LDR R13, stackPointerOnEntry \ Set R13 to the value that we stored in the \ Entry routine, so that the stack pointer \ is restored to the value that it had when \ the game was first run, and which we \ stored in stackPointerOnEntry in the Entry \ routine MOV PC, R14 \ Exit from the game by jumping to the \ address in R14, which will return us to \ the Desktop [or wherever the game was run \ from] \ ****************************************************************************** \ \ Name; PrintHexNumber \ Type; Subroutine \ Category; Score bar \ Summary; An unused routine that prints an 8-digit hexadecimal number on the \ second character row of the screen \ Deep dive; Unused code in Lander \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R1 The number to print \ \ ****************************************************************************** .PrintHexNumber MOV R0, #30 \ Print a VDU 30 command to move the text SWI OS_WriteC \ cursor to the top-left corner of the \ screen MOV R0, #&0A \ Print a line feed [ASCII &0A] to move the SWI OS_WriteC \ cursor down one line, to the start of the \ second line, which is where we print the \ score bar STMFD R13!, {R0-R12, R14} \ Store the registers that we want to use on \ the stack so they can be preserved MOV R0, R1, LSR #28 \ Print the top nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #24 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #20 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #16 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #12 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #8 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1, LSR #4 \ Print the next nibble of the value in R1 BL PrintHexDigit MOV R0, R1 \ Print the bottom nibble of the value in R1 BL PrintHexDigit MOV R0, #&A \ Print a line feed [ASCII &0A] and carriage SWI OS_WriteC \ return [ASCII &0D] to move the cursor down MOV R0, #&D \ to the start of the next line, ready to SWI OS_WriteC \ print further numbers if required LDMFD R13!, {R0-R12, PC} \ Retrieve the registers that we stored on \ the stack and return from the subroutine \ ****************************************************************************** \ \ Name; PrintHexDigit \ Type; Subroutine \ Category; Score bar \ Summary; An unused routine that prints a single digit hexadecimal number in \ the score bar \ Deep dive; Unused code in Lander \ \ ------------------------------------------------------------------------------ \ \ Arguments; \ \ R0 The number to print [only the low nibble is \ printed, the rest of the number is ignored] \ \ ****************************************************************************** .PrintHexDigit AND R0, R0, #&F \ Extract the low nibble from the number in \ R0 CMP R0, #&A \ If the low nibble in R0 >= &A then the hex ADDHS R0, R0, #&37 \ digit is A to F, so add &37 to get the \ corresponding hex digit [so this converts \ &A into ASCII &37 + &A = &41, which gives \ us 'A', the hex digit we want] ADDLO R0, R0, #&30 \ Otherwise the low nibble in R0 is 0 to 9, \ so add the ASCII value of '0' [ASCII &30] \ to get the corresponding hex digit SWI OS_WriteC \ Print the character in R0 MOV PC, R14 \ Return from the subroutine \ ****************************************************************************** \ \ Name; objectRock \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for a rock \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectRock EQUD 6 \ Number of vertices EQUD 8 \ Number of faces EQUD objectRockFaces - objectRock EQUD %00000011 \ Flags; Bit 0 = 1 = object rotates \ Bit 1 = 0 = object has a shadow .objectRockVertices \ xObject, yObject, zObject EQUD &00000000 : EQUD &00000000 : EQUD &00A00000 \ Vertex 0 EQUD &00A00000 : EQUD &00A00000 : EQUD &00000000 \ Vertex 1 EQUD &FF600000 : EQUD &00A00000 : EQUD &00000000 \ Vertex 2 EQUD &00A00000 : EQUD &FF600000 : EQUD &00000000 \ Vertex 3 EQUD &FF600000 : EQUD &FF600000 : EQUD &00000000 \ Vertex 4 EQUD &00000000 : EQUD &00000000 : EQUD &FF600000 \ Vertex 5 .objectRockFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &54DA5200 : EQUD &54DA5200 : EQUD 0 : EQUD 1 : EQUD 2 : EQUD &444 \ 0 EQUD &54DA5200 : EQUD &00000000 : EQUD &54DA5200 : EQUD 0 : EQUD 3 : EQUD 1 : EQUD &444 \ 1 EQUD &00000000 : EQUD &AB25AE00 : EQUD &54DA5200 : EQUD 0 : EQUD 4 : EQUD 3 : EQUD &444 \ 2 EQUD &AB25AE00 : EQUD &00000000 : EQUD &54DA5200 : EQUD 0 : EQUD 2 : EQUD 4 : EQUD &444 \ 3 EQUD &00000000 : EQUD &54DA5200 : EQUD &AB25AE00 : EQUD 5 : EQUD 1 : EQUD 2 : EQUD &444 \ 4 EQUD &54DA5200 : EQUD &00000000 : EQUD &AB25AE00 : EQUD 5 : EQUD 3 : EQUD 1 : EQUD &444 \ 5 EQUD &00000000 : EQUD &AB25AE00 : EQUD &AB25AE00 : EQUD 5 : EQUD 4 : EQUD 3 : EQUD &444 \ 6 EQUD &AB25AE00 : EQUD &00000000 : EQUD &AB25AE00 : EQUD 5 : EQUD 2 : EQUD 4 : EQUD &444 \ 7 \ ****************************************************************************** \ \ Name; objectPyramid \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for a pyramid \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectPyramid EQUD 5 \ Number of vertices EQUD 6 \ Number of faces EQUD objectPyramidFaces - objectPyramid EQUD %00000001 \ Flags; Bit 0 = 1 = object rotates \ Bit 1 = 0 = object has no shadow .objectPyramidVertices \ xObject, yObject, zObject EQUD &00000000 : EQUD &01000000 : EQUD &00000000 \ Vertex 0 EQUD &00C00000 : EQUD &FF800000 : EQUD &00C00000 \ Vertex 1 EQUD &FF400000 : EQUD &FF800000 : EQUD &00C00000 \ Vertex 2 EQUD &00C00000 : EQUD &FF800000 : EQUD &FF400000 \ Vertex 3 EQUD &FF400000 : EQUD &FF800000 : EQUD &FF400000 \ Vertex 4 .objectPyramidFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &35AA66D2 : EQUD &6B54CDA5 : EQUD 0 : EQUD 1 : EQUD 2 : EQUD &800 \ 0 EQUD &6B54CDA5 : EQUD &35AA66D2 : EQUD &00000000 : EQUD 0 : EQUD 3 : EQUD 1 : EQUD &088 \ 1 EQUD &00000000 : EQUD &35AA66D2 : EQUD &94AB325B : EQUD 0 : EQUD 4 : EQUD 3 : EQUD &880 \ 2 EQUD &94AB325B : EQUD &35AA66D2 : EQUD &00000000 : EQUD 0 : EQUD 2 : EQUD 4 : EQUD &808 \ 3 EQUD &00000000 : EQUD &88000000 : EQUD &00000000 : EQUD 1 : EQUD 2 : EQUD 3 : EQUD &444 \ 4 EQUD &00000000 : EQUD &88000000 : EQUD &00000000 : EQUD 2 : EQUD 3 : EQUD 4 : EQUD &008 \ 5 \ ****************************************************************************** \ \ Name; objectPlayer \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the player's ship \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectPlayer EQUD 9 \ Number of vertices EQUD 9 \ Number of faces EQUD objectPlayerFaces - objectPlayer EQUD %00000011 \ Flags; Bit 0 = 1 = object rotates \ Bit 1 = 0 = object has a shadow .objectPlayerVertices \ xObject, yObject, zObject EQUD &01000000 : EQUD &00500000 : EQUD &00800000 \ Vertex 0 EQUD &01000000 : EQUD &00500000 : EQUD &FF800000 \ Vertex 1 EQUD &00000000 : EQUD &000A0000 : EQUD &FECCCCCD \ Vertex 2 EQUD &FF19999A : EQUD &00500000 : EQUD &00000000 \ Vertex 3 EQUD &00000000 : EQUD &000A0000 : EQUD &01333333 \ Vertex 4 EQUD &FFE66667 : EQUD &FF880000 : EQUD &00000000 \ Vertex 5 EQUD &00555555 : EQUD &00500000 : EQUD &00400000 \ Vertex 6 EQUD &00555555 : EQUD &00500000 : EQUD &FFC00000 \ Vertex 7 EQUD &FFCCCCCD : EQUD &00500000 : EQUD &00000000 \ Vertex 8 .objectPlayerFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &457C441A : EQUD &9E2A1F4C : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 5 : EQUD &080 \ 0 EQUD &35F5D83B : EQUD &9BC03EC1 : EQUD &DA12D71D : EQUD 1 : EQUD 2 : EQUD 5 : EQUD &040 \ 1 EQUD &35F5D83B : EQUD &9BC03EC1 : EQUD &25ED28E3 : EQUD 0 : EQUD 5 : EQUD 4 : EQUD &040 \ 2 EQUD &B123D51C : EQUD &AF3F50EE : EQUD &D7417278 : EQUD 2 : EQUD 3 : EQUD 5 : EQUD &040 \ 3 EQUD &B123D51D : EQUD &AF3F50EE : EQUD &28BE8D88 : EQUD 3 : EQUD 4 : EQUD 5 : EQUD &040 \ 4 EQUD &F765D8CD : EQUD &73242236 : EQUD &DF4FD176 : EQUD 1 : EQUD 2 : EQUD 3 : EQUD &088 \ 5 EQUD &F765D8CD : EQUD &73242236 : EQUD &20B02E8A : EQUD 0 : EQUD 3 : EQUD 4 : EQUD &088 \ 6 EQUD &00000000 : EQUD &78000000 : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 3 : EQUD &044 \ 7 EQUD &00000000 : EQUD &78000000 : EQUD &00000000 : EQUD 6 : EQUD 7 : EQUD 8 : EQUD &C80 \ 8 \ ****************************************************************************** \ \ Name; objectSmallLeafyTree \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the small leafy tree \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectSmallLeafyTree EQUD 11 \ Number of vertices EQUD 5 \ Number of faces EQUD objectSmallLeafyTreeFaces - objectSmallLeafyTree EQUD %00000010 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectSmallLeafyTreeVertices \ xObject, yObject, zObject EQUD &00300000 : EQUD &FE800000 : EQUD &00300000 \ Vertex 0 EQUD &FFD9999A : EQUD &00000000 : EQUD &00000000 \ Vertex 1 EQUD &00266666 : EQUD &00000000 : EQUD &00000000 \ Vertex 2 EQUD &00000000 : EQUD &FEF33334 : EQUD &FF400000 \ Vertex 3 EQUD &00800000 : EQUD &FF400000 : EQUD &FF800000 \ Vertex 4 EQUD &FF400000 : EQUD &FECCCCCD : EQUD &FFD55556 \ Vertex 5 EQUD &FF800000 : EQUD &FEA66667 : EQUD &00400000 \ Vertex 6 EQUD &00800000 : EQUD &FE59999A : EQUD &002AAAAA \ Vertex 7 EQUD &00C00000 : EQUD &FEA66667 : EQUD &FFC00000 \ Vertex 8 EQUD &FFA00000 : EQUD &FECCCCCD : EQUD &00999999 \ Vertex 9 EQUD &00C00000 : EQUD &FF400000 : EQUD &00C00000 \ Vertex 10 .objectSmallLeafyTreeFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &14A01873 : EQUD &AF8F9F93 : EQUD &56A0681E : EQUD 0 : EQUD 9 : EQUD 10 : EQUD &040 \ 0 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 2 : EQUD &400 \ 1 EQUD &499A254E : EQUD &B123FC2C : EQUD &CB6D5299 : EQUD 0 : EQUD 3 : EQUD 4 : EQUD &080 \ 2 EQUD &E4D2EEBE : EQUD &8DC82837 : EQUD &E72FE5E9 : EQUD 0 : EQUD 5 : EQUD 6 : EQUD &080 \ 3 EQUD &D5710585 : EQUD &B29EF364 : EQUD &AEC07EB3 : EQUD 0 : EQUD 7 : EQUD 8 : EQUD &080 \ 4 \ ****************************************************************************** \ \ Name; objectTallLeafyTree \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the tall leafy tree \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectTallLeafyTree EQUD 14 \ Number of vertices EQUD 6 \ Number of faces EQUD objectTallLeafyTreeFaces - objectTallLeafyTree EQUD %00000010 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectTallLeafyTreeVertices \ xObject, yObject, zObject EQUD &0036DB6D : EQUD &FD733334 : EQUD &00300000 \ Vertex 0 EQUD &FFD00000 : EQUD &00000000 : EQUD &00000000 \ Vertex 1 EQUD &00300000 : EQUD &00000000 : EQUD &00000000 \ Vertex 2 EQUD &00000000 : EQUD &FE0CCCCD : EQUD &FF400000 \ Vertex 3 EQUD &00800000 : EQUD &FE59999A : EQUD &FF800000 \ Vertex 4 EQUD &FF533334 : EQUD &FE333334 : EQUD &FFC92493 \ Vertex 5 EQUD &FF400000 : EQUD &FEA66667 : EQUD &00600000 \ Vertex 6 EQUD &00000000 : EQUD &FF19999A : EQUD &FF666667 \ Vertex 7 EQUD &FF800000 : EQUD &FF400000 : EQUD &FFA00000 \ Vertex 8 EQUD &FFA00000 : EQUD &FE800000 : EQUD &00999999 \ Vertex 9 EQUD &00C00000 : EQUD &FECCCCCD : EQUD &00C00000 \ Vertex 10 EQUD &FFB33334 : EQUD &FF19999A : EQUD &00E66666 \ Vertex 11 EQUD &00800000 : EQUD &FF400000 : EQUD &00C00000 \ Vertex 12 EQUD &00300000 : EQUD &FE59999A : EQUD &00300000 \ Vertex 13 .objectTallLeafyTreeFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &FD3D01DD : EQUD &D2CB371E : EQUD &6F20024E : EQUD 0 : EQUD 9 : EQUD 10 : EQUD &040 \ 0 EQUD &1E6F981A : EQUD &BB105ECE : EQUD &5D638B16 : EQUD 13 : EQUD 11 : EQUD 12 : EQUD &080 \ 1 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 2 : EQUD &400 \ 2 EQUD &49D96509 : EQUD &B8E72762 : EQUD &C19E3A19 : EQUD 0 : EQUD 3 : EQUD 4 : EQUD &080 \ 3 EQUD &AD213B74 : EQUD &B641CA5D : EQUD &2DC40650 : EQUD 0 : EQUD 5 : EQUD 6 : EQUD &040 \ 4 EQUD &C9102051 : EQUD &AC846CAD : EQUD &BD92A8C1 : EQUD 13 : EQUD 7 : EQUD 8 : EQUD &040 \ 5 \ ****************************************************************************** \ \ Name; objectSmokingRemainsLeft \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the smoking remains that bend to the left \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectSmokingRemainsLeft EQUD 5 \ Number of vertices EQUD 2 \ Number of faces EQUD objectSmokingRemainsLeftFaces - objectSmokingRemainsLeft EQUD %00000000 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has no shadow .objectSmokingRemainsLeftVertices \ xObject, yObject, zObject EQUD &FFD9999A : EQUD &00000000 : EQUD &00000000 \ Vertex 0 EQUD &00266666 : EQUD &00000000 : EQUD &00000000 \ Vertex 1 EQUD &002B3333 : EQUD &FFC00000 : EQUD &00000000 \ Vertex 2 EQUD &00300000 : EQUD &FF800000 : EQUD &00000000 \ Vertex 3 EQUD &FFD55556 : EQUD &FECCCCCD : EQUD &00000000 \ Vertex 4 .objectSmokingRemainsLeftFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 3 : EQUD &000 \ 0 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 2 : EQUD 3 : EQUD 4 : EQUD &000 \ 1 \ ****************************************************************************** \ \ Name; objectSmokingRemainsRight \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the smoking remains that bend to the right \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectSmokingRemainsRight EQUD 5 \ Number of vertices EQUD 2 \ Number of faces EQUD objectSmokingRemainsRightFaces - objectSmokingRemainsRight EQUD %00000000 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has no shadow .objectSmokingRemainsRightVertices \ xObject, yObject, zObject EQUD &002AAAAA : EQUD &00000000 : EQUD &00000000 \ Vertex 0 EQUD &FFD55556 : EQUD &00000000 : EQUD &00000000 \ Vertex 1 EQUD &FFD4CCCD : EQUD &FFD00000 : EQUD &00000000 \ Vertex 2 EQUD &FFD00000 : EQUD &FFA00000 : EQUD &00000000 \ Vertex 3 EQUD &002AAAAA : EQUD &FEA66667 : EQUD &00000000 \ Vertex 4 .objectSmokingRemainsRightFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 3 : EQUD &000 \ 0 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 2 : EQUD 3 : EQUD 4 : EQUD &000 \ 1 \ ****************************************************************************** \ \ Name; objectFirTree \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the fir tree \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectFirTree EQUD 5 \ Number of vertices EQUD 2 \ Number of faces EQUD objectFirTreeFaces - objectFirTree EQUD %00000010 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectFirTreeVertices \ xObject, yObject, zObject EQUD &FFA00000 : EQUD &FFC92493 : EQUD &FFC92493 \ Vertex 0 EQUD &00600000 : EQUD &FFC92493 : EQUD &FFC92493 \ Vertex 1 EQUD &00000000 : EQUD &FE333334 : EQUD &0036DB6D \ Vertex 2 EQUD &00266666 : EQUD &00000000 : EQUD &00000000 \ Vertex 3 EQUD &FFD9999A : EQUD &00000000 : EQUD &00000000 \ Vertex 4 .objectFirTreeFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 2 : EQUD 3 : EQUD 4 : EQUD &400 \ 0 EQUD &00000000 : EQUD &E0B0E050 : EQUD &8C280943 : EQUD 0 : EQUD 1 : EQUD 2 : EQUD &040 \ 1 \ ****************************************************************************** \ \ Name; objectGazebo \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the gazebo \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectGazebo EQUD 13 \ Number of vertices EQUD 8 \ Number of faces EQUD objectGazeboFaces - objectGazebo EQUD %00000010 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectGazeboVertices \ xObject, yObject, zObject EQUD &00000000 : EQUD &FF000000 : EQUD &00000000 \ Vertex 0 EQUD &FF800000 : EQUD &FF400000 : EQUD &00800000 \ Vertex 1 EQUD &FF800000 : EQUD &FF400000 : EQUD &FF800000 \ Vertex 2 EQUD &00800000 : EQUD &FF400000 : EQUD &FF800000 \ Vertex 3 EQUD &00800000 : EQUD &FF400000 : EQUD &00800000 \ Vertex 4 EQUD &FF800000 : EQUD &00000000 : EQUD &00800000 \ Vertex 5 EQUD &FF800000 : EQUD &00000000 : EQUD &FF800000 \ Vertex 6 EQUD &00800000 : EQUD &00000000 : EQUD &FF800000 \ Vertex 7 EQUD &00800000 : EQUD &00000000 : EQUD &00800000 \ Vertex 8 EQUD &FF99999A : EQUD &FF400000 : EQUD &00800000 \ Vertex 9 EQUD &FF99999A : EQUD &FF400000 : EQUD &FF800000 \ Vertex 10 EQUD &00666666 : EQUD &FF400000 : EQUD &FF800000 \ Vertex 11 EQUD &00666666 : EQUD &FF400000 : EQUD &00800000 \ Vertex 12 .objectGazeboFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &00000000 : EQUD &78000000 : EQUD 1 : EQUD 5 : EQUD 9 : EQUD &444 \ 0 EQUD &00000000 : EQUD &00000000 : EQUD &88000000 : EQUD 2 : EQUD 6 : EQUD 10 : EQUD &444 \ 1 EQUD &00000000 : EQUD &94AB325B : EQUD &35AA66D2 : EQUD 0 : EQUD 1 : EQUD 4 : EQUD &400 \ 2 EQUD &00000000 : EQUD &00000000 : EQUD &88000000 : EQUD 3 : EQUD 7 : EQUD 11 : EQUD &444 \ 3 EQUD &00000000 : EQUD &00000000 : EQUD &78000000 : EQUD 4 : EQUD 8 : EQUD 12 : EQUD &444 \ 4 EQUD &CA55992E : EQUD &94AB325B : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 2 : EQUD &840 \ 5 EQUD &35AA66D2 : EQUD &94AB325B : EQUD &00000000 : EQUD 0 : EQUD 3 : EQUD 4 : EQUD &840 \ 6 EQUD &00000000 : EQUD &94AB325B : EQUD &CA55992E : EQUD 0 : EQUD 2 : EQUD 3 : EQUD &400 \ 7 \ ****************************************************************************** \ \ Name; objectBuilding \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the building \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectBuilding EQUD 16 \ Number of vertices EQUD 12 \ Number of faces EQUD objectBuildingFaces - objectBuilding EQUD %00000000 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has no shadow .objectBuildingVertices \ xObject, yObject, zObject EQUD &FF19999A : EQUD &FF266667 : EQUD &00000000 \ Vertex 0 EQUD &FF400000 : EQUD &FF266667 : EQUD &00000000 \ Vertex 1 EQUD &00C00000 : EQUD &FF266667 : EQUD &00000000 \ Vertex 2 EQUD &00E66666 : EQUD &FF266667 : EQUD &00000000 \ Vertex 3 EQUD &FF19999A : EQUD &FF8CCCCD : EQUD &00A66666 \ Vertex 4 EQUD &FF19999A : EQUD &FF8CCCCD : EQUD &FF59999A \ Vertex 5 EQUD &00E66666 : EQUD &FF8CCCCD : EQUD &00A66666 \ Vertex 6 EQUD &00E66666 : EQUD &FF8CCCCD : EQUD &FF59999A \ Vertex 7 EQUD &FF400000 : EQUD &FF666667 : EQUD &00800000 \ Vertex 8 EQUD &FF400000 : EQUD &FF666667 : EQUD &FF800000 \ Vertex 9 EQUD &00C00000 : EQUD &FF666667 : EQUD &00800000 \ Vertex 10 EQUD &00C00000 : EQUD &FF666667 : EQUD &FF800000 \ Vertex 11 EQUD &FF400000 : EQUD &00000000 : EQUD &00800000 \ Vertex 12 EQUD &FF400000 : EQUD &00000000 : EQUD &FF800000 \ Vertex 13 EQUD &00C00000 : EQUD &00000000 : EQUD &00800000 \ Vertex 14 EQUD &00C00000 : EQUD &00000000 : EQUD &FF800000 \ Vertex 15 .objectBuildingFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &99CD0E6D : EQUD &3EE445CC : EQUD 0 : EQUD 4 : EQUD 6 : EQUD &400 \ 0 EQUD &00000000 : EQUD &99CD0E6D : EQUD &3EE445CC : EQUD 0 : EQUD 3 : EQUD 6 : EQUD &400 \ 1 EQUD &88000000 : EQUD &00000000 : EQUD &00000000 : EQUD 1 : EQUD 8 : EQUD 9 : EQUD &DDD \ 2 EQUD &78000000 : EQUD &00000000 : EQUD &00000000 : EQUD 2 : EQUD 10 : EQUD 11 : EQUD &555 \ 3 EQUD &88000000 : EQUD &00000000 : EQUD &00000000 : EQUD 8 : EQUD 12 : EQUD 13 : EQUD &FFF \ 4 EQUD &88000000 : EQUD &00000000 : EQUD &00000000 : EQUD 8 : EQUD 9 : EQUD 13 : EQUD &FFF \ 5 EQUD &78000000 : EQUD &00000000 : EQUD &00000000 : EQUD 10 : EQUD 14 : EQUD 15 : EQUD &777 \ 6 EQUD &78000000 : EQUD &00000000 : EQUD &00000000 : EQUD 10 : EQUD 11 : EQUD 15 : EQUD &777 \ 7 EQUD &00000000 : EQUD &00000000 : EQUD &88000000 : EQUD 9 : EQUD 13 : EQUD 15 : EQUD &BBB \ 8 EQUD &00000000 : EQUD &00000000 : EQUD &88000000 : EQUD 9 : EQUD 11 : EQUD 15 : EQUD &BBB \ 9 EQUD &00000000 : EQUD &99CD0E6D : EQUD &C11BBA34 : EQUD 0 : EQUD 5 : EQUD 7 : EQUD &800 \ 10 EQUD &00000000 : EQUD &99CD0E6D : EQUD &C11BBA34 : EQUD 0 : EQUD 3 : EQUD 7 : EQUD &800 \ 11 \ ****************************************************************************** \ \ Name; objectSmokingBuilding \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the smoking remains of a building \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectSmokingBuilding EQUD 6 \ Number of vertices EQUD 6 \ Number of faces EQUD objectSmokingBuildingFaces - objectSmokingBuilding EQUD %00000000 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has no shadow .objectSmokingBuildingVertices \ xObject, yObject, zObject EQUD &FF400000 : EQUD &00000001 : EQUD &00800000 \ Vertex 0 EQUD &FF400000 : EQUD &00000001 : EQUD &FF800000 \ Vertex 1 EQUD &00C00000 : EQUD &00000001 : EQUD &00800000 \ Vertex 2 EQUD &00C00000 : EQUD &00000001 : EQUD &FF800000 \ Vertex 3 EQUD &FF400000 : EQUD &FF99999A : EQUD &00800000 \ Vertex 4 EQUD &00C00000 : EQUD &FFB33334 : EQUD &FF800000 \ Vertex 5 .objectSmokingBuildingFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &78000000 : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 2 : EQUD &000 \ 0 EQUD &00000000 : EQUD &78000000 : EQUD &00000000 : EQUD 1 : EQUD 2 : EQUD 3 : EQUD &000 \ 1 EQUD &00000000 : EQUD &00000000 : EQUD &78000000 : EQUD 0 : EQUD 2 : EQUD 4 : EQUD &333 \ 2 EQUD &88000000 : EQUD &00000000 : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 4 : EQUD &666 \ 3 EQUD &78000000 : EQUD &00000000 : EQUD &00000000 : EQUD 2 : EQUD 3 : EQUD 5 : EQUD &555 \ 4 EQUD &00000000 : EQUD &00000000 : EQUD &88000001 : EQUD 1 : EQUD 3 : EQUD 5 : EQUD &777 \ 5 \ ****************************************************************************** \ \ Name; objectSmokingGazebo \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the smoking remains of a gazebo \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectSmokingGazebo EQUD 6 \ Number of vertices EQUD 4 \ Number of faces EQUD objectSmokingGazeboFaces - objectSmokingGazebo EQUD %00000010 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectSmokingGazeboVertices \ xObject, yObject, zObject EQUD &00000000 : EQUD &FF8CCCCD : EQUD &FFF00000 \ Vertex 0 EQUD &00199999 : EQUD &FF8CCCCD : EQUD &FFF00000 \ Vertex 1 EQUD &00800000 : EQUD &00000000 : EQUD &00800000 \ Vertex 2 EQUD &FF800000 : EQUD &00000000 : EQUD &00800000 \ Vertex 3 EQUD &00800000 : EQUD &00000000 : EQUD &FF800000 \ Vertex 4 EQUD &FF800000 : EQUD &00000000 : EQUD &FF800000 \ Vertex 5 .objectSmokingGazeboFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &A24BB5BE : EQUD &4AF6A1AD : EQUD 0 : EQUD 1 : EQUD 2 : EQUD &000 \ 0 EQUD &00000000 : EQUD &A24BB5BE : EQUD &4AF6A1AD : EQUD 0 : EQUD 1 : EQUD 3 : EQUD &333 \ 1 EQUD &00000000 : EQUD &AC59C060 : EQUD &A9F5EA98 : EQUD 0 : EQUD 1 : EQUD 4 : EQUD &444 \ 2 EQUD &00000000 : EQUD &AC59C060 : EQUD &A9F5EA98 : EQUD 0 : EQUD 1 : EQUD 5 : EQUD &000 \ 3 \ ****************************************************************************** \ \ Name; objectRocket \ Type; Variable \ Category; 3D objects \ Summary; Object blueprint for the rocket \ Deep dive; Object blueprints \ \ ****************************************************************************** .objectRocket EQUD 13 \ Number of vertices EQUD 8 \ Number of faces EQUD objectRocketFaces - objectRocket EQUD %00000010 \ Flags; Bit 0 = 0 = object is static \ Bit 1 = 0 = object has a shadow .objectRocketVertices \ xObject, yObject, zObject EQUD &00000000 : EQUD &FE400000 : EQUD &00000000 \ Vertex 0 EQUD &FFC80000 : EQUD &FFD745D2 : EQUD &00380000 \ Vertex 1 EQUD &FFC80000 : EQUD &FFD745D2 : EQUD &FFC80000 \ Vertex 2 EQUD &00380000 : EQUD &FFD745D2 : EQUD &00380000 \ Vertex 3 EQUD &00380000 : EQUD &FFD745D2 : EQUD &FFC80000 \ Vertex 4 EQUD &FF900000 : EQUD &00000000 : EQUD &00700000 \ Vertex 5 EQUD &FF900000 : EQUD &00000000 : EQUD &FF900000 \ Vertex 6 EQUD &00700000 : EQUD &00000000 : EQUD &00700000 \ Vertex 7 EQUD &00700000 : EQUD &00000000 : EQUD &FF900000 \ Vertex 8 EQUD &FFE40000 : EQUD &FF071C72 : EQUD &001C0000 \ Vertex 9 EQUD &FFE40000 : EQUD &FF071C72 : EQUD &FFE40000 \ Vertex 10 EQUD &001C0000 : EQUD &FF071C72 : EQUD &001C0000 \ Vertex 11 EQUD &001C0000 : EQUD &FF071C72 : EQUD &FFE40000 \ Vertex 12 .objectRocketFaces \ xNormal, yNormal, zNormal, vertex1, vertex2, vertex3, colour EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 9 : EQUD 1 : EQUD 5 : EQUD &CC0 \ 0 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 11 : EQUD 3 : EQUD 7 : EQUD &CC0 \ 1 EQUD &00000000 : EQUD &EFA75F67 : EQUD &76E1A76B : EQUD 0 : EQUD 1 : EQUD 3 : EQUD &C00 \ 2 EQUD &891E5895 : EQUD &EFA75F67 : EQUD &00000000 : EQUD 0 : EQUD 1 : EQUD 2 : EQUD &800 \ 3 EQUD &76E1A76B : EQUD &EFA75F67 : EQUD &00000000 : EQUD 3 : EQUD 0 : EQUD 4 : EQUD &800 \ 4 EQUD &00000000 : EQUD &EFA75F67 : EQUD &891E5895 : EQUD 0 : EQUD 2 : EQUD 4 : EQUD &C00 \ 5 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 10 : EQUD 2 : EQUD 6 : EQUD &CC0 \ 6 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD 12 : EQUD 4 : EQUD 8 : EQUD &CC0 \ 7 \ ****************************************************************************** \ \ Name; sinTable \ Type; Variable \ Category; Maths [Geometry] \ Summary; Sine/cosine lookup table \ \ ------------------------------------------------------------------------------ \ \ At byte n, the table contains; \ \ [2^31 - 1] * SIN[2 * PI * [n / 1024]] \ \ For n = 0 to 1023 \ \ In the original BBC BASIC source, this table would have been populated using \ something along these lines; \ \ FOR I% = 0 TO 1023 \ [ \ OPT pass% \ EQUD [2^31 - 1] * SIN[2 * PI * [I% / 1024]] \ ] \ NEXT \ \ I have used EQUDs here because different computers have different algorithms \ and accuracies in their maths routines, so the only way to ensure a complete \ match with the original binaries is to hard-code the values. \ \ The above loop produces the correct values when run on an Archimedes. \ \ ****************************************************************************** .sinTable EQUD &00000000 : EQUD &00C90F87 : EQUD &01921D1F : EQUD &025B26D7 EQUD &03242ABE : EQUD &03ED26E6 : EQUD &04B6195D : EQUD &057F0034 EQUD &0647D97C : EQUD &0710A344 : EQUD &07D95B9E : EQUD &08A2009A EQUD &096A9049 : EQUD &0A3308BC : EQUD &0AFB6805 : EQUD &0BC3AC35 EQUD &0C8BD35D : EQUD &0D53DB92 : EQUD &0E1BC2E3 : EQUD &0EE38765 EQUD &0FAB272B : EQUD &1072A047 : EQUD &1139F0CE : EQUD &120116D4 EQUD &12C8106E : EQUD &138EDBB0 : EQUD &145576B1 : EQUD &151BDF85 EQUD &15E21444 : EQUD &16A81304 : EQUD &176DD9DE : EQUD &183366E8 EQUD &18F8B83C : EQUD &19BDCBF2 : EQUD &1A82A025 : EQUD &1B4732EE EQUD &1C0B826A : EQUD &1CCF8CB2 : EQUD &1D934FE4 : EQUD &1E56CA1D EQUD &1F19F97A : EQUD &1FDCDC1A : EQUD &209F701C : EQUD &2161B39F EQUD &2223A4C5 : EQUD &22E541AE : EQUD &23A6887E : EQUD &24677757 EQUD &25280C5D : EQUD &25E845B5 : EQUD &26A82185 : EQUD &27679DF3 EQUD &2826B928 : EQUD &28E5714A : EQUD &29A3C484 : EQUD &2A61B101 EQUD &2B1F34EB : EQUD &2BDC4E6E : EQUD &2C98FBBA : EQUD &2D553AFB EQUD &2E110A61 : EQUD &2ECC681D : EQUD &2F875262 : EQUD &3041C760 EQUD &30FBC54C : EQUD &31B54A5D : EQUD &326E54C6 : EQUD &3326E2C2 EQUD &33DEF286 : EQUD &3496824F : EQUD &354D9056 : EQUD &36041AD8 EQUD &36BA2013 : EQUD &376F9E45 : EQUD &382493AF : EQUD &38D8FE92 EQUD &398CDD31 : EQUD &3A402DD1 : EQUD &3AF2EEB6 : EQUD &3BA51E28 EQUD &3C56BA6F : EQUD &3D07C1D5 : EQUD &3DB832A5 : EQUD &3E680B2C EQUD &3F1749B7 : EQUD &3FC5EC97 : EQUD &4073F21C : EQUD &4121589A EQUD &41CE1E63 : EQUD &427A41D0 : EQUD &4325C135 : EQUD &43D09AEC EQUD &447ACD4F : EQUD &452456BC : EQUD &45CD358E : EQUD &46756827 EQUD &471CECE6 : EQUD &47C3C22E : EQUD &4869E664 : EQUD &490F57ED EQUD &49B41533 : EQUD &4A581C9D : EQUD &4AFB6C97 : EQUD &4B9E038E EQUD &4C3FDFF3 : EQUD &4CE10033 : EQUD &4D8162C3 : EQUD &4E210616 EQUD &4EBFE8A4 : EQUD &4F5E08E2 : EQUD &4FFB654C : EQUD &5097FC5D EQUD &5133CC93 : EQUD &51CED46D : EQUD &5269126D : EQUD &53028517 EQUD &539B2AEF : EQUD &5433027C : EQUD &54CA0A49 : EQUD &556040E2 EQUD &55F5A4D1 : EQUD &568A34A8 : EQUD &571DEEF8 : EQUD &57B0D255 EQUD &5842DD53 : EQUD &58D40E8B : EQUD &59646497 : EQUD &59F3DE11 EQUD &5A827999 : EQUD &5B1035CE : EQUD &5B9D1153 : EQUD &5C290ACB EQUD &5CB420DF : EQUD &5D3E5236 : EQUD &5DC79D7B : EQUD &5E50015C EQUD &5ED77C89 : EQUD &5F5E0DB2 : EQUD &5FE3B38C : EQUD &60686CCD EQUD &60EC382E : EQUD &616F146A : EQUD &61F1003E : EQUD &6271FA68 EQUD &62F201AB : EQUD &637114CB : EQUD &63EF328E : EQUD &646C59BE EQUD &64E88925 : EQUD &6563BF91 : EQUD &65DDFBD2 : EQUD &66573CBA EQUD &66CF811E : EQUD &6746C7D6 : EQUD &67BD0FBB : EQUD &683257A9 EQUD &68A69E80 : EQUD &6919E31F : EQUD &698C246B : EQUD &69FD6149 EQUD &6A6D98A3 : EQUD &6ADCC963 : EQUD &6B4AF277 : EQUD &6BB812CF EQUD &6C24295F : EQUD &6C8F351A : EQUD &6CF934FB : EQUD &6D6227F9 EQUD &6DCA0D13 : EQUD &6E30E349 : EQUD &6E96A99C : EQUD &6EFB5F11 EQUD &6F5F02B1 : EQUD &6FC19384 : EQUD &70231098 : EQUD &708378FE EQUD &70E2CBC5 : EQUD &71410803 : EQUD &719E2CD1 : EQUD &71FA3947 EQUD &72552C84 : EQUD &72AF05A6 : EQUD &7307C3CF : EQUD &735F6625 EQUD &73B5EBD0 : EQUD &740B53F9 : EQUD &745F9DD0 : EQUD &74B2C882 EQUD &7504D344 : EQUD &7555BD4A : EQUD &75A585CE : EQUD &75F42C09 EQUD &7641AF3B : EQUD &768E0EA4 : EQUD &76D94988 : EQUD &77235F2C EQUD &776C4EDA : EQUD &77B417DE : EQUD &77FAB987 : EQUD &78403327 EQUD &78848413 : EQUD &78C7ABA0 : EQUD &7909A92B : EQUD &794A7C10 EQUD &798A23B0 : EQUD &79C89F6C : EQUD &7A05EEAC : EQUD &7A4210D7 EQUD &7A7D055A : EQUD &7AB6CBA2 : EQUD &7AEF6322 : EQUD &7B26CB4E EQUD &7B5D039C : EQUD &7B920B88 : EQUD &7BC5E28E : EQUD &7BF8882F EQUD &7C29FBED : EQUD &7C5A3D4E : EQUD &7C894BDC : EQUD &7CB72723 EQUD &7CE3CEB0 : EQUD &7D0F4217 : EQUD &7D3980EB : EQUD &7D628AC5 EQUD &7D8A5F3F : EQUD &7DB0FDF6 : EQUD &7DD6668D : EQUD &7DFA98A7 EQUD &7E1D93E8 : EQUD &7E3F57FD : EQUD &7E5FE492 : EQUD &7E7F3955 EQUD &7E9D55FB : EQUD &7EBA3A38 : EQUD &7ED5E5C5 : EQUD &7EF0585E EQUD &7F0991C2 : EQUD &7F2191B3 : EQUD &7F3857F5 : EQUD &7F4DE44F EQUD &7F62368E : EQUD &7F754E7E : EQUD &7F872BF2 : EQUD &7F97CEBC EQUD &7FA736B3 : EQUD &7FB563B2 : EQUD &7FC25595 : EQUD &7FCE0C3D EQUD &7FD8878D : EQUD &7FE1C76A : EQUD &7FE9CBBF : EQUD &7FF09477 EQUD &7FF62181 : EQUD &7FFA72D0 : EQUD &7FFD8859 : EQUD &7FFF6216 EQUD &7FFFFFFE : EQUD &7FFF6216 : EQUD &7FFD8859 : EQUD &7FFA72D0 EQUD &7FF62181 : EQUD &7FF09477 : EQUD &7FE9CBBF : EQUD &7FE1C76A EQUD &7FD8878D : EQUD &7FCE0C3D : EQUD &7FC25595 : EQUD &7FB563B2 EQUD &7FA736B3 : EQUD &7F97CEBC : EQUD &7F872BF2 : EQUD &7F754E7E EQUD &7F62368E : EQUD &7F4DE44F : EQUD &7F3857F5 : EQUD &7F2191B3 EQUD &7F0991C2 : EQUD &7EF0585E : EQUD &7ED5E5C5 : EQUD &7EBA3A38 EQUD &7E9D55FB : EQUD &7E7F3955 : EQUD &7E5FE492 : EQUD &7E3F57FD EQUD &7E1D93E8 : EQUD &7DFA98A7 : EQUD &7DD6668D : EQUD &7DB0FDF6 EQUD &7D8A5F3F : EQUD &7D628AC5 : EQUD &7D3980EB : EQUD &7D0F4217 EQUD &7CE3CEB0 : EQUD &7CB72723 : EQUD &7C894BDC : EQUD &7C5A3D4E EQUD &7C29FBED : EQUD &7BF8882F : EQUD &7BC5E28E : EQUD &7B920B88 EQUD &7B5D039C : EQUD &7B26CB4E : EQUD &7AEF6322 : EQUD &7AB6CBA2 EQUD &7A7D055A : EQUD &7A4210D7 : EQUD &7A05EEAC : EQUD &79C89F6C EQUD &798A23B0 : EQUD &794A7C10 : EQUD &7909A92B : EQUD &78C7ABA0 EQUD &78848413 : EQUD &78403327 : EQUD &77FAB987 : EQUD &77B417DE EQUD &776C4EDA : EQUD &77235F2C : EQUD &76D94988 : EQUD &768E0EA4 EQUD &7641AF3B : EQUD &75F42C09 : EQUD &75A585CE : EQUD &7555BD4A EQUD &7504D344 : EQUD &74B2C882 : EQUD &745F9DD0 : EQUD &740B53FA EQUD &73B5EBD0 : EQUD &735F6625 : EQUD &7307C3CF : EQUD &72AF05A5 EQUD &72552C84 : EQUD &71FA3948 : EQUD &719E2CD1 : EQUD &71410803 EQUD &70E2CBC5 : EQUD &708378FD : EQUD &70231098 : EQUD &6FC19384 EQUD &6F5F02B0 : EQUD &6EFB5F11 : EQUD &6E96A99C : EQUD &6E30E348 EQUD &6DCA0D13 : EQUD &6D6227F9 : EQUD &6CF934FB : EQUD &6C8F351A EQUD &6C24295F : EQUD &6BB812D0 : EQUD &6B4AF277 : EQUD &6ADCC963 EQUD &6A6D98A3 : EQUD &69FD6149 : EQUD &698C246B : EQUD &6919E320 EQUD &68A69E80 : EQUD &683257AA : EQUD &67BD0FBC : EQUD &6746C7D6 EQUD &66CF811E : EQUD &66573CBA : EQUD &65DDFBD1 : EQUD &6563BF91 EQUD &64E88925 : EQUD &646C59BF : EQUD &63EF328E : EQUD &637114CB EQUD &62F201AC : EQUD &6271FA68 : EQUD &61F1003E : EQUD &616F146B EQUD &60EC382E : EQUD &60686CCE : EQUD &5FE3B38D : EQUD &5F5E0DB2 EQUD &5ED77C89 : EQUD &5E50015D : EQUD &5DC79D7B : EQUD &5D3E5236 EQUD &5CB420DF : EQUD &5C290ACB : EQUD &5B9D1153 : EQUD &5B1035CF EQUD &5A82799A : EQUD &59F3DE11 : EQUD &59646497 : EQUD &58D40E8C EQUD &5842DD53 : EQUD &57B0D256 : EQUD &571DEEFA : EQUD &568A34A8 EQUD &55F5A4D2 : EQUD &556040E2 : EQUD &54CA0A49 : EQUD &5433027D EQUD &539B2AEF : EQUD &53028516 : EQUD &5269126E : EQUD &51CED46E EQUD &5133CC93 : EQUD &5097FC5D : EQUD &4FFB654D : EQUD &4F5E08E2 EQUD &4EBFE8A4 : EQUD &4E210617 : EQUD &4D8162C4 : EQUD &4CE10033 EQUD &4C3FDFF3 : EQUD &4B9E0390 : EQUD &4AFB6C97 : EQUD &4A581C9D EQUD &49B41533 : EQUD &490F57ED : EQUD &4869E664 : EQUD &47C3C22F EQUD &471CECE6 : EQUD &46756827 : EQUD &45CD358F : EQUD &452456BC EQUD &447ACD50 : EQUD &43D09AEC : EQUD &4325C134 : EQUD &427A41D0 EQUD &41CE1E64 : EQUD &4121589B : EQUD &4073F21C : EQUD &3FC5EC98 EQUD &3F1749B8 : EQUD &3E680B2C : EQUD &3DB832A6 : EQUD &3D07C1D6 EQUD &3C56BA6F : EQUD &3BA51E28 : EQUD &3AF2EEB7 : EQUD &3A402DD0 EQUD &398CDD32 : EQUD &38D8FE93 : EQUD &382493AF : EQUD &376F9E45 EQUD &36BA2013 : EQUD &36041AD7 : EQUD &354D9056 : EQUD &3496824F EQUD &33DEF287 : EQUD &3326E2C1 : EQUD &326E54C7 : EQUD &31B54A5E EQUD &30FBC54C : EQUD &3041C760 : EQUD &2F875262 : EQUD &2ECC681D EQUD &2E110A61 : EQUD &2D553AFC : EQUD &2C98FBB9 : EQUD &2BDC4E6F EQUD &2B1F34EB : EQUD &2A61B100 : EQUD &29A3C484 : EQUD &28E5714B EQUD &2826B927 : EQUD &27679DF3 : EQUD &26A82186 : EQUD &25E845B6 EQUD &25280C5D : EQUD &24677757 : EQUD &23A6887F : EQUD &22E541AE EQUD &2223A4C5 : EQUD &2161B3A0 : EQUD &209F701B : EQUD &1FDCDC1A EQUD &1F19F97B : EQUD &1E56CA1D : EQUD &1D934FE5 : EQUD &1CCF8CB3 EQUD &1C0B8269 : EQUD &1B4732EF : EQUD &1A82A026 : EQUD &19BDCBF1 EQUD &18F8B83C : EQUD &183366E8 : EQUD &176DD9DD : EQUD &16A81304 EQUD &15E21444 : EQUD &151BDF86 : EQUD &145576B0 : EQUD &138EDBB1 EQUD &12C8106F : EQUD &120116D4 : EQUD &1139F0CF : EQUD &1072A048 EQUD &0FAB272A : EQUD &0EE38765 : EQUD &0E1BC2E4 : EQUD &0D53DB91 EQUD &0C8BD35E : EQUD &0BC3AC35 : EQUD &0AFB6804 : EQUD &0A3308BC EQUD &096A9049 : EQUD &08A20099 : EQUD &07D95B9E : EQUD &0710A345 EQUD &0647D97D : EQUD &057F0034 : EQUD &04B6195D : EQUD &03ED26E7 EQUD &03242ABE : EQUD &025B26D7 : EQUD &01921D20 : EQUD &00C90F87 EQUD &00000000 : EQUD &FF36F079 : EQUD &FE6DE2E0 : EQUD &FDA4D929 EQUD &FCDBD542 : EQUD &FC12D91A : EQUD &FB49E6A3 : EQUD &FA80FFCD EQUD &F9B82684 : EQUD &F8EF5CBC : EQUD &F826A463 : EQUD &F75DFF67 EQUD &F6956FB7 : EQUD &F5CCF745 : EQUD &F50497FC : EQUD &F43C53CB EQUD &F3742CA3 : EQUD &F2AC2470 : EQUD &F1E43D1D : EQUD &F11C789B EQUD &F054D8D6 : EQUD &EF8D5FB9 : EQUD &EEC60F32 : EQUD &EDFEE92D EQUD &ED37EF92 : EQUD &EC712450 : EQUD &EBAA8950 : EQUD &EAE4207B EQUD &EA1DEBBC : EQUD &E957ECFC : EQUD &E8922624 : EQUD &E7CC9918 EQUD &E70747C5 : EQUD &E642340F : EQUD &E57D5FDB : EQUD &E4B8CD12 EQUD &E3F47D97 : EQUD &E330734D : EQUD &E26CB01C : EQUD &E1A935E4 EQUD &E0E60685 : EQUD &E02323E6 : EQUD &DF608FE5 : EQUD &DE9E4C61 EQUD &DDDC5B3C : EQUD &DD1ABE53 : EQUD &DC597782 : EQUD &DB9888A9 EQUD &DAD7F3A4 : EQUD &DA17BA4A : EQUD &D957DE7B : EQUD &D898620D EQUD &D7D946DA : EQUD &D71A8EB6 : EQUD &D65C3B7C : EQUD &D59E4F01 EQUD &D4E0CB15 : EQUD &D423B192 : EQUD &D3670447 : EQUD &D2AAC505 EQUD &D1EEF59F : EQUD &D13397E3 : EQUD &D078AD9E : EQUD &CFBE38A1 EQUD &CF043AB5 : EQUD &CE4AB5A3 : EQUD &CD91AB3A : EQUD &CCD91D3F EQUD &CC210D79 : EQUD &CB697DB1 : EQUD &CAB26FAB : EQUD &C9FBE529 EQUD &C945DFED : EQUD &C89061BC : EQUD &C7DB6C52 : EQUD &C727016E EQUD &C67322CF : EQUD &C5BFD230 : EQUD &C50D114A : EQUD &C45AE1D8 EQUD &C3A94592 : EQUD &C2F83E2B : EQUD &C247CD5B : EQUD &C197F4D5 EQUD &C0E8B649 : EQUD &C03A1369 : EQUD &BF8C0DE5 : EQUD &BEDEA766 EQUD &BE31E19D : EQUD &BD85BE31 : EQUD &BCDA3ECD : EQUD &BC2F6514 EQUD &BB8532B1 : EQUD &BADBA945 : EQUD &BA32CA72 : EQUD &B98A97DA EQUD &B8E3131B : EQUD &B83C3DD2 : EQUD &B796199D : EQUD &B6F0A814 EQUD &B64BEACD : EQUD &B5A7E364 : EQUD &B504936A : EQUD &B461FC71 EQUD &B3C0200E : EQUD &B31EFFCE : EQUD &B27E9D3D : EQUD &B1DEF9EA EQUD &B140175D : EQUD &B0A1F71F : EQUD &B0049AB4 : EQUD &AF6803A3 EQUD &AECC336E : EQUD &AE312B93 : EQUD &AD96ED93 : EQUD &ACFD7AEA EQUD &AC64D511 : EQUD &ABCCFD84 : EQUD &AB35F5B7 : EQUD &AA9FBF1E EQUD &AA0A5B2F : EQUD &A975CB58 : EQUD &A8E21107 : EQUD &A84F2DAB EQUD &A7BD22AD : EQUD &A72BF174 : EQUD &A69B9B69 : EQUD &A60C21EF EQUD &A57D8667 : EQUD &A4EFCA32 : EQUD &A462EEAE : EQUD &A3D6F536 EQUD &A34BDF21 : EQUD &A2C1ADCB : EQUD &A2386286 : EQUD &A1AFFEA4 EQUD &A1288378 : EQUD &A0A1F24F : EQUD &A01C4C74 : EQUD &9F979333 EQUD &9F13C7D2 : EQUD &9E90EB96 : EQUD &9E0EFFC4 : EQUD &9D8E0599 EQUD &9D0DFE55 : EQUD &9C8EEB37 : EQUD &9C10CD72 : EQUD &9B93A642 EQUD &9B1776DD : EQUD &9A9C4070 : EQUD &9A22042E : EQUD &99A8C347 EQUD &99307EE2 : EQUD &98B93829 : EQUD &9842F046 : EQUD &97CDA857 EQUD &97596180 : EQUD &96E61CE2 : EQUD &9673DB96 : EQUD &96029EB6 EQUD &9592675E : EQUD &9523369D : EQUD &94B50D88 : EQUD &9447ED31 EQUD &93DBD6A1 : EQUD &9370CAE4 : EQUD &9306CB06 : EQUD &929DD807 EQUD &9235F2EC : EQUD &91CF1CB8 : EQUD &91695664 : EQUD &9104A0EE EQUD &90A0FD50 : EQUD &903E6C7C : EQUD &8FDCEF67 : EQUD &8F7C8703 EQUD &8F1D343B : EQUD &8EBEF7FC : EQUD &8E61D32F : EQUD &8E05C6B8 EQUD &8DAAD37D : EQUD &8D50FA5B : EQUD &8CF83C31 : EQUD &8CA099DC EQUD &8C4A1430 : EQUD &8BF4AC06 : EQUD &8BA06231 : EQUD &8B4D377E EQUD &8AFB2CBC : EQUD &8AAA42B6 : EQUD &8A5A7A32 : EQUD &8A0BD3F6 EQUD &89BE50C5 : EQUD &8971F15C : EQUD &8926B678 : EQUD &88DCA0D5 EQUD &8893B126 : EQUD &884BE821 : EQUD &88054679 : EQUD &87BFCCD9 EQUD &877B7BED : EQUD &87385460 : EQUD &86F656D5 : EQUD &86B583EF EQUD &8675DC51 : EQUD &86376094 : EQUD &85FA1154 : EQUD &85BDEF29 EQUD &8582FAA6 : EQUD &8549345D : EQUD &85109CDE : EQUD &84D934B2 EQUD &84A2FC63 : EQUD &846DF478 : EQUD &843A1D72 : EQUD &840777D1 EQUD &83D60413 : EQUD &83A5C2B1 : EQUD &8376B424 : EQUD &8348D8DD EQUD &831C3150 : EQUD &82F0BDEA : EQUD &82C67F15 : EQUD &829D753B EQUD &8275A0C2 : EQUD &824F020A : EQUD &82299973 : EQUD &8205675A EQUD &81E26C18 : EQUD &81C0A803 : EQUD &81A01B6F : EQUD &8180C6AB EQUD &8162AA05 : EQUD &8145C5C9 : EQUD &812A1A3B : EQUD &810FA7A2 EQUD &80F66E3E : EQUD &80DE6E4D : EQUD &80C7A80B : EQUD &80B21BB1 EQUD &809DC972 : EQUD &808AB181 : EQUD &8078D40E : EQUD &80683144 EQUD &8058C94D : EQUD &804A9C4E : EQUD &803DAA6B : EQUD &8031F3C3 EQUD &80277873 : EQUD &801E3896 : EQUD &80163441 : EQUD &800F6B89 EQUD &8009DE7F : EQUD &80058D30 : EQUD &800277A7 : EQUD &80009DEB EQUD &80000001 : EQUD &80009DEB : EQUD &800277A7 : EQUD &80058D30 EQUD &8009DE7F : EQUD &800F6B89 : EQUD &80163441 : EQUD &801E3895 EQUD &80277873 : EQUD &8031F3C3 : EQUD &803DAA6B : EQUD &804A9C4E EQUD &8058C94D : EQUD &80683145 : EQUD &8078D40E : EQUD &808AB182 EQUD &809DC972 : EQUD &80B21BB1 : EQUD &80C7A80B : EQUD &80DE6E4D EQUD &80F66E3E : EQUD &810FA7A1 : EQUD &812A1A3B : EQUD &8145C5C8 EQUD &8162AA05 : EQUD &8180C6AA : EQUD &81A01B6E : EQUD &81C0A803 EQUD &81E26C18 : EQUD &8205675A : EQUD &82299972 : EQUD &824F0209 EQUD &8275A0C2 : EQUD &829D753B : EQUD &82C67F15 : EQUD &82F0BDEA EQUD &831C314F : EQUD &8348D8DD : EQUD &8376B424 : EQUD &83A5C2B1 EQUD &83D60413 : EQUD &840777D0 : EQUD &843A1D71 : EQUD &846DF478 EQUD &84A2FC63 : EQUD &84D934B2 : EQUD &85109CDE : EQUD &8549345D EQUD &8582FAA6 : EQUD &85BDEF29 : EQUD &85FA1153 : EQUD &86376093 EQUD &8675DC50 : EQUD &86B583EF : EQUD &86F656D4 : EQUD &87385460 EQUD &877B7BED : EQUD &87BFCCD8 : EQUD &88054679 : EQUD &884BE821 EQUD &8893B126 : EQUD &88DCA0D4 : EQUD &8926B678 : EQUD &8971F15B EQUD &89BE50C5 : EQUD &8A0BD3F6 : EQUD &8A5A7A32 : EQUD &8AAA42B6 EQUD &8AFB2CBB : EQUD &8B4D377D : EQUD &8BA06231 : EQUD &8BF4AC06 EQUD &8C4A1430 : EQUD &8CA099DB : EQUD &8CF83C30 : EQUD &8D50FA5A EQUD &8DAAD37D : EQUD &8E05C6B8 : EQUD &8E61D32F : EQUD &8EBEF7FB EQUD &8F1D343A : EQUD &8F7C8702 : EQUD &8FDCEF66 : EQUD &903E6C7B EQUD &90A0FD4F : EQUD &9104A0EE : EQUD &91695664 : EQUD &91CF1CB7 EQUD &9235F2EC : EQUD &929DD806 : EQUD &9306CB05 : EQUD &9370CAE4 EQUD &93DBD6A0 : EQUD &9447ED31 : EQUD &94B50D88 : EQUD &9523369D EQUD &9592675E : EQUD &96029EB6 : EQUD &9673DB95 : EQUD &96E61CE2 EQUD &9759617F : EQUD &97CDA856 : EQUD &9842F045 : EQUD &98B93829 EQUD &99307EE2 : EQUD &99A8C347 : EQUD &9A22042D : EQUD &9A9C406F EQUD &9B1776DC : EQUD &9B93A641 : EQUD &9C10CD72 : EQUD &9C8EEB36 EQUD &9D0DFE54 : EQUD &9D8E0598 : EQUD &9E0EFFC3 : EQUD &9E90EB95 EQUD &9F13C7D2 : EQUD &9F979334 : EQUD &A01C4C73 : EQUD &A0A1F24E EQUD &A1288376 : EQUD &A1AFFEA3 : EQUD &A2386285 : EQUD &A2C1ADC9 EQUD &A34BDF21 : EQUD &A3D6F535 : EQUD &A462EEAC : EQUD &A4EFCA31 EQUD &A57D8667 : EQUD &A60C21ED : EQUD &A69B9B69 : EQUD &A72BF175 EQUD &A7BD22AB : EQUD &A84F2DAA : EQUD &A8E21108 : EQUD &A975CB56 EQUD &AA0A5B2E : EQUD &AA9FBF1F : EQUD &AB35F5B5 : EQUD &ABCCFD83 EQUD &AC64D512 : EQUD &ACFD7AE8 : EQUD &AD96ED92 : EQUD &AE312B93 EQUD &AECC336C : EQUD &AF6803A3 : EQUD &B0049AB5 : EQUD &B0A1F71D EQUD &B140175C : EQUD &B1DEF9EA : EQUD &B27E9D3C : EQUD &B31EFFCD EQUD &B3C0200F : EQUD &B461FC70 : EQUD &B5049369 : EQUD &B5A7E365 EQUD &B64BEACD : EQUD &B6F0A813 : EQUD &B796199A : EQUD &B83C3DD1 EQUD &B8E3131A : EQUD &B98A97D7 : EQUD &BA32CA71 : EQUD &BADBA944 EQUD &BB8532AF : EQUD &BC2F6514 : EQUD &BCDA3ECC : EQUD &BD85BE2F EQUD &BE31E19C : EQUD &BEDEA767 : EQUD &BF8C0DE2 : EQUD &C03A1368 EQUD &C0E8B649 : EQUD &C197F4D3 : EQUD &C247CD5A : EQUD &C2F83E2C EQUD &C3A9458F : EQUD &C45AE1D8 : EQUD &C50D114B : EQUD &C5BFD22E EQUD &C67322CE : EQUD &C727016F : EQUD &C7DB6C4F : EQUD &C89061BB EQUD &C945DFEE : EQUD &C9FBE527 : EQUD &CAB26FAA : EQUD &CB697DB2 EQUD &CC210D78 : EQUD &CCD91D3E : EQUD &CD91AB3B : EQUD &CE4AB5A2 EQUD &CF043AB4 : EQUD &CFBE38A2 : EQUD &D078AD9D : EQUD &D13397E3 EQUD &D1EEF5A0 : EQUD &D2AAC504 : EQUD &D3670446 : EQUD &D423B18F EQUD &D4E0CB14 : EQUD &D59E4F00 : EQUD &D65C3B7A : EQUD &D71A8EB5 EQUD &D7D946D9 : EQUD &D898620B : EQUD &D957DE7A : EQUD &DA17BA4B EQUD &DAD7F3A1 : EQUD &DB9888A8 : EQUD &DC597783 : EQUD &DD1ABE50 EQUD &DDDC5B3B : EQUD &DE9E4C62 : EQUD &DF608FE2 : EQUD &E02323E5 EQUD &E0E60686 : EQUD &E1A935E1 : EQUD &E26CB01B : EQUD &E330734F EQUD &E3F47D95 : EQUD &E4B8CD11 : EQUD &E57D5FDC : EQUD &E642340C EQUD &E70747C4 : EQUD &E7CC9919 : EQUD &E8922621 : EQUD &E957ECFC EQUD &EA1DEBBD : EQUD &EAE4207A : EQUD &EBAA894F : EQUD &EC712451 EQUD &ED37EF91 : EQUD &EDFEE92C : EQUD &EEC60F33 : EQUD &EF8D5FB8 EQUD &F054D8D5 : EQUD &F11C7898 : EQUD &F1E43D1C : EQUD &F2AC246F EQUD &F3742CA0 : EQUD &F43C53CA : EQUD &F50497FC : EQUD &F5CCF742 EQUD &F6956FB6 : EQUD &F75DFF67 : EQUD &F826A460 : EQUD &F8EF5CBB EQUD &F9B82685 : EQUD &FA80FFCA : EQUD &FB49E6A2 : EQUD &FC12D91B EQUD &FCDBD540 : EQUD &FDA4D928 : EQUD &FE6DE2E1 : EQUD &FF36F077 \ ****************************************************************************** \ \ Name; arctanTable \ Type; Variable \ Category; Maths [Geometry] \ Summary; Arctan lookup table \ Deep dive; Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ At byte n, the table contains; \ \ [[2^31 - 1] / PI] * arctan[n / 128] \ \ For n = 0 to 127 \ \ In the original BBC BASIC source, this table would have been populated using \ something along these lines; \ \ FOR I% = 0 TO 127 \ [ \ OPT pass% \ EQUD [[2^31 - 1] / PI] * ATN[I% / 128] \ ] \ NEXT \ \ I have used EQUDs here because different computers have different algorithms \ and accuracies in their maths routines, so the only way to ensure a complete \ match with the original binaries is to hard-code the values. \ \ The above loop produces the correct values when run on an Archimedes. \ \ ****************************************************************************** .arctanTable EQUD &00000000 : EQUD &00517C55 : EQUD &00A2F61E : EQUD &00F46AD0 EQUD &0145D7E1 : EQUD &01973AC7 : EQUD &01E890FC : EQUD &0239D7FB EQUD &028B0D43 : EQUD &02DC2E53 : EQUD &032D38B3 : EQUD &037E29EB EQUD &03CEFF89 : EQUD &041FB721 : EQUD &04704E4A : EQUD &04C0C2A4 EQUD &051111D4 : EQUD &05613983 : EQUD &05B13767 : EQUD &06010937 EQUD &0650ACB6 : EQUD &06A01FAE : EQUD &06EF5FF1 : EQUD &073E6B5A EQUD &078D3FCE : EQUD &07DBDB3A : EQUD &082A3B94 : EQUD &08785EDF EQUD &08C64325 : EQUD &0913E67C : EQUD &09614703 : EQUD &09AE62E6 EQUD &09FB385B : EQUD &0A47C5A2 : EQUD &0A940907 : EQUD &0AE000E1 EQUD &0B2BAB95 : EQUD &0B77078F : EQUD &0BC2134B : EQUD &0C0CCD4E EQUD &0C57342A : EQUD &0CA1467C : EQUD &0CEB02EF : EQUD &0D346836 EQUD &0D7D7514 : EQUD &0DC62856 : EQUD &0E0E80D4 : EQUD &0E567D73 EQUD &0E9E1D23 : EQUD &0EE55EE2 : EQUD &0F2C41B6 : EQUD &0F72C4B3 EQUD &0FB8E6F9 : EQUD &0FFEA7B0 : EQUD &1044060F : EQUD &10890156 EQUD &10CD98D1 : EQUD &1111CBD6 : EQUD &115599C6 : EQUD &1199020E EQUD &11DC0423 : EQUD &121E9F86 : EQUD &1260D3C1 : EQUD &12A2A069 EQUD &12E4051D : EQUD &13250183 : EQUD &1365954E : EQUD &13A5C038 EQUD &13E58203 : EQUD &1424DA7D : EQUD &1463C97A : EQUD &14A24ED7 EQUD &14E06A7A : EQUD &151E1C50 : EQUD &155B6450 : EQUD &15984275 EQUD &15D4B6C4 : EQUD &1610C149 : EQUD &164C6216 : EQUD &16879945 EQUD &16C266F6 : EQUD &16FCCB50 : EQUD &1736C67E : EQUD &177058B5 EQUD &17A9822C : EQUD &17E24322 : EQUD &181A9BDA : EQUD &18528C9E EQUD &188A15BB : EQUD &18C13785 : EQUD &18F7F252 : EQUD &192E467F EQUD &1964346D : EQUD &1999BC80 : EQUD &19CEDF21 : EQUD &1A039CBD EQUD &1A37F5C4 : EQUD &1A6BEAA9 : EQUD &1A9F7BE4 : EQUD &1AD2A9EF EQUD &1B057548 : EQUD &1B37DE6E : EQUD &1B69E5E5 : EQUD &1B9B8C33 EQUD &1BCCD1DF : EQUD &1BFDB775 : EQUD &1C2E3D81 : EQUD &1C5E6491 EQUD &1C8E2D38 : EQUD &1CBD9807 : EQUD &1CECA593 : EQUD &1D1B5671 EQUD &1D49AB3A : EQUD &1D77A486 : EQUD &1DA542F0 : EQUD &1DD28713 EQUD &1DFF718C : EQUD &1E2C02F7 : EQUD &1E583BF4 : EQUD &1E841D21 EQUD &1EAFA71E : EQUD &1EDADA8D : EQUD &1F05B80D : EQUD &1F304042 EQUD &1F5A73CC : EQUD &1F84534E : EQUD &1FADDF6B : EQUD &1FD718C5 \ ****************************************************************************** \ \ Name; squareRootTable \ Type; Variable \ Category; Maths [Arithmetic] \ Summary; Square root lookup table \ Deep dive; Flying by mouse \ \ ------------------------------------------------------------------------------ \ \ At byte n, the table contains; \ \ [2^31 - 1] * SQRT[n / 1024] \ \ For n = 0 to 1023 \ \ In the original BBC BASIC source, this table would have been populated using \ something along these lines; \ \ FOR I% = 0 TO 1023 \ [ \ OPT pass% \ EQUD [2^31 - 1] * SQR[I% / 1024] \ ] \ NEXT \ \ I have used EQUDs here because different computers have different algorithms \ and accuracies in their maths routines, so the only way to ensure a complete \ match with the original binaries is to hard-code the values. \ \ The above loop produces the correct values when run on an Archimedes. \ \ ****************************************************************************** .squareRootTable EQUD &00000000 : EQUD &03FFFFFF : EQUD &05A82799 : EQUD &06ED9EBA EQUD &07FFFFFF : EQUD &08F1BBCD : EQUD &09CC4709 : EQUD &0A953FD4 EQUD &0B504F33 : EQUD &0BFFFFFF : EQUD &0CA62C1D : EQUD &0D443949 EQUD &0DDB3D74 : EQUD &0E6C15A2 : EQUD &0EF77508 : EQUD &0F7DEF58 EQUD &0FFFFFFF : EQUD &107E0F66 : EQUD &10F876CC : EQUD &116F8334 EQUD &11E3779B : EQUD &12548EB9 : EQUD &12C2FC59 : EQUD &132EEE75 EQUD &13988E13 : EQUD &13FFFFFF : EQUD &1465655F : EQUD &14C8DC2E EQUD &152A7FA9 : EQUD &158A68A4 : EQUD &15E8ADD2 : EQUD &16456405 EQUD &16A09E66 : EQUD &16FA6EA1 : EQUD &1752E50D : EQUD &17AA10D1 EQUD &17FFFFFF : EQUD &1854BFB3 : EQUD &18A85C24 : EQUD &18FAE0C1 EQUD &194C583A : EQUD &199CCC99 : EQUD &19EC4749 : EQUD &1A3AD129 EQUD &1A887293 : EQUD &1AD53369 : EQUD &1B211B1C : EQUD &1B6C30B8 EQUD &1BB67AE8 : EQUD &1BFFFFFF : EQUD &1C48C5FF : EQUD &1C90D29C EQUD &1CD82B44 : EQUD &1D1ED520 : EQUD &1D64D51D : EQUD &1DAA2FEF EQUD &1DEEEA11 : EQUD &1E3307CC : EQUD &1E768D39 : EQUD &1EB97E45 EQUD &1EFBDEB1 : EQUD &1F3DB217 : EQUD &1F7EFBEB : EQUD &1FBFBF7E EQUD &1FFFFFFF : EQUD &203FC07E : EQUD &207F03EC : EQUD &20BDCD1D EQUD &20FC1ECD : EQUD &2139FB9A : EQUD &2177660F : EQUD &21B4609B EQUD &21F0ED99 : EQUD &222D0F51 : EQUD &2268C7F4 : EQUD &22A419A2 EQUD &22DF0668 : EQUD &23199043 : EQUD &2353B91E : EQUD &238D82D6 EQUD &23C6EF37 : EQUD &23FFFFFF : EQUD &2438B6E1 : EQUD &2471157F EQUD &24A91D72 : EQUD &24E0D043 : EQUD &25182F72 : EQUD &254F3C74 EQUD &2585F8B2 : EQUD &25BC658C : EQUD &25F28458 : EQUD &26285661 EQUD &265DDCEA : EQUD &2693192F : EQUD &26C80C60 : EQUD &26FCB7A7 EQUD &27311C27 : EQUD &27653AFA : EQUD &27991533 : EQUD &27CCABDD EQUD &27FFFFFF : EQUD &28331298 : EQUD &2865E49F : EQUD &28987707 EQUD &28CACABE : EQUD &28FCE0A9 : EQUD &292EB9AA : EQUD &2960569E EQUD &2991B85C : EQUD &29C2DFB5 : EQUD &29F3CD77 : EQUD &2A24826A EQUD &2A54FF53 : EQUD &2A8544F1 : EQUD &2AB55400 : EQUD &2AE52D36 EQUD &2B14D149 : EQUD &2B4440E6 : EQUD &2B737CBA : EQUD &2BA2856D EQUD &2BD15BA4 : EQUD &2BFFFFFF : EQUD &2C2E731E : EQUD &2C5CB599 EQUD &2C8AC80A : EQUD &2CB8AB04 : EQUD &2CE65F19 : EQUD &2D13E4D8 EQUD &2D413CCC : EQUD &2D6E677F : EQUD &2D9B6577 : EQUD &2DC83737 EQUD &2DF4DD42 : EQUD &2E215816 : EQUD &2E4DA830 : EQUD &2E79CE09 EQUD &2EA5CA1B : EQUD &2ED19CDA : EQUD &2EFD46BA : EQUD &2F28C82D EQUD &2F5421A3 : EQUD &2F7F5388 : EQUD &2FAA5E48 : EQUD &2FD5424D EQUD &2FFFFFFF : EQUD &302A97C4 : EQUD &30550A00 : EQUD &307F5716 EQUD &30A97F66 : EQUD &30D38350 : EQUD &30FD6331 : EQUD &31271F66 EQUD &3150B849 : EQUD &317A2E33 : EQUD &31A3817C : EQUD &31CCB27A EQUD &31F5C182 : EQUD &321EAEE7 : EQUD &32477AFB : EQUD &32702610 EQUD &3298B075 : EQUD &32C11A78 : EQUD &32E96466 : EQUD &33118E8B EQUD &33399932 : EQUD &336184A5 : EQUD &3389512C : EQUD &33B0FF0F EQUD &33D88E93 : EQUD &33FFFFFF : EQUD &34275396 : EQUD &344E899C EQUD &3475A253 : EQUD &349C9DFD : EQUD &34C37CD9 : EQUD &34EA3F28 EQUD &3510E527 : EQUD &35376F15 : EQUD &355DDD2E : EQUD &35842FAF EQUD &35AA66D2 : EQUD &35D082D2 : EQUD &35F683E8 : EQUD &361C6A4C EQUD &36423638 : EQUD &3667E7E2 : EQUD &368D7F80 : EQUD &36B2FD48 EQUD &36D86170 : EQUD &36FDAC2A : EQUD &3722DDAC : EQUD &3747F628 EQUD &376CF5D0 : EQUD &3791DCD5 : EQUD &37B6AB6A : EQUD &37DB61BD EQUD &37FFFFFF : EQUD &3824865F : EQUD &3848F50B : EQUD &386D4C31 EQUD &38918BFF : EQUD &38B5B4A2 : EQUD &38D9C644 : EQUD &38FDC113 EQUD &3921A539 : EQUD &394572E2 : EQUD &39692A36 : EQUD &398CCB60 EQUD &39B05688 : EQUD &39D3CBD7 : EQUD &39F72B76 : EQUD &3A1A758C EQUD &3A3DAA40 : EQUD &3A60C9B9 : EQUD &3A83D41C : EQUD &3AA6C991 EQUD &3AC9AA3B : EQUD &3AEC7641 : EQUD &3B0F2DC6 : EQUD &3B31D0EF EQUD &3B545FDE : EQUD &3B76DAB9 : EQUD &3B9941A1 : EQUD &3BBB94B8 EQUD &3BDDD422 : EQUD &3BFFFFFF : EQUD &3C221871 : EQUD &3C441D99 EQUD &3C660F98 : EQUD &3C87EE8D : EQUD &3CA9BA99 : EQUD &3CCB73DB EQUD &3CED1A72 : EQUD &3D0EAE7E : EQUD &3D30301C : EQUD &3D519F6C EQUD &3D72FC8A : EQUD &3D944794 : EQUD &3DB580A9 : EQUD &3DD6A7E4 EQUD &3DF7BD62 : EQUD &3E18C140 : EQUD &3E39B399 : EQUD &3E5A948A EQUD &3E7B642E : EQUD &3E9C22A0 : EQUD &3EBCCFFB : EQUD &3EDD6C59 EQUD &3EFDF7D6 : EQUD &3F1E728B : EQUD &3F3EDC92 : EQUD &3F5F3605 EQUD &3F7F7EFD : EQUD &3F9FB792 : EQUD &3FBFDFDF : EQUD &3FDFF7FB EQUD &3FFFFFFF : EQUD &401FF803 : EQUD &403FE01F : EQUD &405FB86A EQUD &407F80FD : EQUD &409F39ED : EQUD &40BEE353 : EQUD &40DE7D44 EQUD &40FE07D8 : EQUD &411D8325 : EQUD &413CEF40 : EQUD &415C4C40 EQUD &417B9A3B : EQUD &419AD946 : EQUD &41BA0976 : EQUD &41D92AE0 EQUD &41F83D9A : EQUD &421741B8 : EQUD &4236374E : EQUD &42551E71 EQUD &4273F735 : EQUD &4292C1AF : EQUD &42B17DF1 : EQUD &42D02C0F EQUD &42EECC1E : EQUD &430D5E30 : EQUD &432BE257 : EQUD &434A58A9 EQUD &4368C136 : EQUD &43871C11 : EQUD &43A5694E : EQUD &43C3A8FE EQUD &43E1DB33 : EQUD &43FFFFFF : EQUD &441E1775 : EQUD &443C21A5 EQUD &445A1EA2 : EQUD &44780E7C : EQUD &4495F145 : EQUD &44B3C70E EQUD &44D18FE8 : EQUD &44EF4BE3 : EQUD &450CFB11 : EQUD &452A9D81 EQUD &45483344 : EQUD &4565BC6A : EQUD &45833904 : EQUD &45A0A921 EQUD &45BE0CD1 : EQUD &45DB6423 : EQUD &45F8AF28 : EQUD &4615EDEF EQUD &46332087 : EQUD &465046FE : EQUD &466D6165 : EQUD &468A6FCB EQUD &46A7723D : EQUD &46C468CB : EQUD &46E15383 : EQUD &46FE3274 EQUD &471B05AC : EQUD &4737CD39 : EQUD &4754892A : EQUD &4771398C EQUD &478DDE6E : EQUD &47AA77DC : EQUD &47C705E6 : EQUD &47E38898 EQUD &47FFFFFF : EQUD &481C6C2A : EQUD &4838CD26 : EQUD &485522FF EQUD &48716DC3 : EQUD &488DAD7E : EQUD &48A9E23E : EQUD &48C60C10 EQUD &48E22AFF : EQUD &48FE3F19 : EQUD &491A486B : EQUD &493646FF EQUD &49523AE4 : EQUD &496E2424 : EQUD &498A02CC : EQUD &49A5D6E9 EQUD &49C1A086 : EQUD &49DD5FAE : EQUD &49F9146E : EQUD &4A14BED2 EQUD &4A305EE4 : EQUD &4A4BF4B1 : EQUD &4A678044 : EQUD &4A8301A8 EQUD &4A9E78E8 : EQUD &4AB9E610 : EQUD &4AD5492B : EQUD &4AF0A243 EQUD &4B0BF165 : EQUD &4B273699 : EQUD &4B4271ED : EQUD &4B5DA369 EQUD &4B78CB19 : EQUD &4B93E907 : EQUD &4BAEFD3E : EQUD &4BCA07C8 EQUD &4BE508B0 : EQUD &4BFFFFFF : EQUD &4C1AEDC1 : EQUD &4C35D1FE EQUD &4C50ACC2 : EQUD &4C6B7E16 : EQUD &4C864604 : EQUD &4CA10496 EQUD &4CBBB9D5 : EQUD &4CD665CC : EQUD &4CF10884 : EQUD &4D0BA207 EQUD &4D26325E : EQUD &4D40B993 : EQUD &4D5B37AE : EQUD &4D75ACBB EQUD &4D9018C0 : EQUD &4DAA7BC9 : EQUD &4DC4D5DE : EQUD &4DDF2708 EQUD &4DF96F4F : EQUD &4E13AEBF : EQUD &4E2DE55E : EQUD &4E481336 EQUD &4E62384F : EQUD &4E7C54B3 : EQUD &4E96686B : EQUD &4EB0737E EQUD &4ECA75F5 : EQUD &4EE46FD9 : EQUD &4EFE6132 : EQUD &4F184A09 EQUD &4F322A66 : EQUD &4F4C0251 : EQUD &4F65D1D3 : EQUD &4F7F98F4 EQUD &4F9957BB : EQUD &4FB30E32 : EQUD &4FCCBC5F : EQUD &4FE6624C EQUD &4FFFFFFF : EQUD &50199582 : EQUD &503322DB : EQUD &504CA812 EQUD &50662530 : EQUD &507F9A3B : EQUD &5099073D : EQUD &50B26C3B EQUD &50CBC93E : EQUD &50E51E4D : EQUD &50FE6B70 : EQUD &5117B0AE EQUD &5130EE0F : EQUD &514A2399 : EQUD &51635155 : EQUD &517C7748 EQUD &5195957C : EQUD &51AEABF5 : EQUD &51C7BABD : EQUD &51E0C1DA EQUD &51F9C152 : EQUD &5212B92D : EQUD &522BA972 : EQUD &52449228 EQUD &525D7355 : EQUD &52764D01 : EQUD &528F1F31 : EQUD &52A7E9EE EQUD &52C0AD3D : EQUD &52D96926 : EQUD &52F21DAE : EQUD &530ACADD EQUD &532370B8 : EQUD &533C0F47 : EQUD &5354A691 : EQUD &536D369A EQUD &5385BF6A : EQUD &539E4108 : EQUD &53B6BB79 : EQUD &53CF2EC4 EQUD &53E79AEE : EQUD &53FFFFFF : EQUD &54185DFD : EQUD &5430B4EC EQUD &544904D5 : EQUD &54614DBC : EQUD &54798FA8 : EQUD &5491CA9F EQUD &54A9FEA7 : EQUD &54C22BC5 : EQUD &54DA5200 : EQUD &54F2715D EQUD &550A89E3 : EQUD &55229B96 : EQUD &553AA67E : EQUD &5552AA9F EQUD &556AA800 : EQUD &55829EA6 : EQUD &559A8E96 : EQUD &55B277D7 EQUD &55CA5A6D : EQUD &55E23660 : EQUD &55FA0BB3 : EQUD &5611DA6C EQUD &5629A292 : EQUD &56416429 : EQUD &56591F37 : EQUD &5670D3C1 EQUD &568881CC : EQUD &56A0295F : EQUD &56B7CA7E : EQUD &56CF652E EQUD &56E6F975 : EQUD &56FE8757 : EQUD &57160EDB : EQUD &572D9005 EQUD &57450ADB : EQUD &575C7F61 : EQUD &5773ED9C : EQUD &578B5592 EQUD &57A2B748 : EQUD &57BA12C3 : EQUD &57D16807 : EQUD &57E8B719 EQUD &57FFFFFF : EQUD &581742BE : EQUD &582E7F59 : EQUD &5845B5D7 EQUD &585CE63C : EQUD &5874108C : EQUD &588B34CD : EQUD &58A25303 EQUD &58B96B33 : EQUD &58D07D62 : EQUD &58E78994 : EQUD &58FE8FCE EQUD &59159015 : EQUD &592C8A6D : EQUD &59437EDA : EQUD &595A6D62 EQUD &59715609 : EQUD &598838D3 : EQUD &599F15C6 : EQUD &59B5ECE4 EQUD &59CCBE33 : EQUD &59E389B8 : EQUD &59FA4F76 : EQUD &5A110F72 EQUD &5A27C9B1 : EQUD &5A3E7E36 : EQUD &5A552D06 : EQUD &5A6BD626 EQUD &5A827999 : EQUD &5A991764 : EQUD &5AAFAF8B : EQUD &5AC64213 EQUD &5ADCCEFF : EQUD &5AF35653 : EQUD &5B09D814 : EQUD &5B205447 EQUD &5B36CAEE : EQUD &5B4D3C0E : EQUD &5B63A7AC : EQUD &5B7A0DCB EQUD &5B906E6F : EQUD &5BA6C99D : EQUD &5BBD1F57 : EQUD &5BD36FA4 EQUD &5BE9BA85 : EQUD &5BFFFFFF : EQUD &5C164017 : EQUD &5C2C7ACF EQUD &5C42B02D : EQUD &5C58E033 : EQUD &5C6F0AE6 : EQUD &5C853049 EQUD &5C9B5060 : EQUD &5CB16B2F : EQUD &5CC780BB : EQUD &5CDD9105 EQUD &5CF39C13 : EQUD &5D09A1E8 : EQUD &5D1FA288 : EQUD &5D359DF6 EQUD &5D4B9436 : EQUD &5D61854C : EQUD &5D77713B : EQUD &5D8D5807 EQUD &5DA339B4 : EQUD &5DB91645 : EQUD &5DCEEDBE : EQUD &5DE4C022 EQUD &5DFA8D75 : EQUD &5E1055BA : EQUD &5E2618F5 : EQUD &5E3BD72A EQUD &5E51905B : EQUD &5E67448D : EQUD &5E7CF3C2 : EQUD &5E929DFE EQUD &5EA84346 : EQUD &5EBDE39B : EQUD &5ED37F01 : EQUD &5EE9157C EQUD &5EFEA710 : EQUD &5F1433BE : EQUD &5F29BB8C : EQUD &5F3F3E7B EQUD &5F54BC90 : EQUD &5F6A35CE : EQUD &5F7FAA37 : EQUD &5F9519D0 EQUD &5FAA849B : EQUD &5FBFEA9C : EQUD &5FD54BD5 : EQUD &5FEAA84B EQUD &5FFFFFFF : EQUD &601552F6 : EQUD &602AA132 : EQUD &603FEAB8 EQUD &60552F89 : EQUD &606A6FA9 : EQUD &607FAB1A : EQUD &6094E1E1 EQUD &60AA1401 : EQUD &60BF417B : EQUD &60D46A54 : EQUD &60E98E8E EQUD &60FEAE2C : EQUD &6113C932 : EQUD &6128DFA2 : EQUD &613DF17F EQUD &6152FECC : EQUD &6168078D : EQUD &617D0BC4 : EQUD &61920B74 EQUD &61A706A0 : EQUD &61BBFD4B : EQUD &61D0EF78 : EQUD &61E5DD2A EQUD &61FAC663 : EQUD &620FAB27 : EQUD &62248B78 : EQUD &62396759 EQUD &624E3ECD : EQUD &626311D7 : EQUD &6277E079 : EQUD &628CAAB7 EQUD &62A17093 : EQUD &62B6320F : EQUD &62CAEF30 : EQUD &62DFA7F7 EQUD &62F45C67 : EQUD &63090C83 : EQUD &631DB84D : EQUD &63325FC9 EQUD &634702F9 : EQUD &635BA1DF : EQUD &63703C7F : EQUD &6384D2DA EQUD &639964F5 : EQUD &63ADF2D0 : EQUD &63C27C6F : EQUD &63D701D5 EQUD &63EB8304 : EQUD &63FFFFFF : EQUD &641478C8 : EQUD &6428ED61 EQUD &643D5DCE : EQUD &6451CA11 : EQUD &6466322D : EQUD &647A9623 EQUD &648EF5F7 : EQUD &64A351AB : EQUD &64B7A942 : EQUD &64CBFCBE EQUD &64E04C21 : EQUD &64F4976E : EQUD &6508DEA8 : EQUD &651D21D0 EQUD &653160EA : EQUD &65459BF8 : EQUD &6559D2FC : EQUD &656E05F8 EQUD &658234F0 : EQUD &65965FE5 : EQUD &65AA86DA : EQUD &65BEA9D0 EQUD &65D2C8CC : EQUD &65E6E3CE : EQUD &65FAFADA : EQUD &660F0DF1 EQUD &66231D17 : EQUD &6637284C : EQUD &664B2F94 : EQUD &665F32F1 EQUD &66733265 : EQUD &66872DF3 : EQUD &669B259C : EQUD &66AF1963 EQUD &66C3094B : EQUD &66D6F555 : EQUD &66EADD84 : EQUD &66FEC1DA EQUD &6712A259 : EQUD &67267F04 : EQUD &673A57DC : EQUD &674E2CE4 EQUD &6761FE1E : EQUD &6775CB8D : EQUD &67899532 : EQUD &679D5B0F EQUD &67B11D27 : EQUD &67C4DB7C : EQUD &67D89611 : EQUD &67EC4CE6 EQUD &67FFFFFF : EQUD &6813AF5D : EQUD &68275B03 : EQUD &683B02F2 EQUD &684EA72D : EQUD &686247B6 : EQUD &6875E48F : EQUD &68897DBA EQUD &689D1339 : EQUD &68B0A50E : EQUD &68C4333C : EQUD &68D7BDC3 EQUD &68EB44A7 : EQUD &68FEC7E9 : EQUD &6912478C : EQUD &6925C391 EQUD &69393BFA : EQUD &694CB0CA : EQUD &69602202 : EQUD &69738FA4 EQUD &6986F9B3 : EQUD &699A6030 : EQUD &69ADC31D : EQUD &69C1227C EQUD &69D47E50 : EQUD &69E7D69A : EQUD &69FB2B5B : EQUD &6A0E7C97 EQUD &6A21CA4F : EQUD &6A351484 : EQUD &6A485B39 : EQUD &6A5B9E70 EQUD &6A6EDE2B : EQUD &6A821A6A : EQUD &6A955332 : EQUD &6AA88882 EQUD &6ABBBA5D : EQUD &6ACEE8C5 : EQUD &6AE213BD : EQUD &6AF53B44 EQUD &6B085F5E : EQUD &6B1B800D : EQUD &6B2E9D51 : EQUD &6B41B72E EQUD &6B54CDA4 : EQUD &6B67E0B6 : EQUD &6B7AF066 : EQUD &6B8DFCB4 EQUD &6BA105A4 : EQUD &6BB40B36 : EQUD &6BC70D6D : EQUD &6BDA0C4A EQUD &6BED07D0 : EQUD &6BFFFFFF : EQUD &6C12F4DA : EQUD &6C25E662 EQUD &6C38D499 : EQUD &6C4BBF81 : EQUD &6C5EA71C : EQUD &6C718B6B EQUD &6C846C71 : EQUD &6C974A2D : EQUD &6CAA24A4 : EQUD &6CBCFBD5 EQUD &6CCFCFC4 : EQUD &6CE2A071 : EQUD &6CF56DDE : EQUD &6D08380D EQUD &6D1AFF00 : EQUD &6D2DC2B8 : EQUD &6D408337 : EQUD &6D53407F EQUD &6D65FA91 : EQUD &6D78B16F : EQUD &6D8B651A : EQUD &6D9E1594 EQUD &6DB0C2E0 : EQUD &6DC36CFD : EQUD &6DD613EF : EQUD &6DE8B7B7 EQUD &6DFB5855 : EQUD &6E0DF5CD : EQUD &6E20901F : EQUD &6E33274D EQUD &6E45BB59 : EQUD &6E584C44 : EQUD &6E6ADA10 : EQUD &6E7D64BE EQUD &6E8FEC50 : EQUD &6EA270C8 : EQUD &6EB4F227 : EQUD &6EC7706E EQUD &6ED9EBA0 : EQUD &6EEC63BE : EQUD &6EFED8C8 : EQUD &6F114AC2 EQUD &6F23B9AB : EQUD &6F362587 : EQUD &6F488E56 : EQUD &6F5AF41A EQUD &6F6D56D4 : EQUD &6F7FB686 : EQUD &6F921332 : EQUD &6FA46CD8 EQUD &6FB6C37B : EQUD &6FC9171B : EQUD &6FDB67BB : EQUD &6FEDB55C EQUD &6FFFFFFF : EQUD &701247A5 : EQUD &70248C51 : EQUD &7036CE04 EQUD &70490CBE : EQUD &705B4883 : EQUD &706D8152 : EQUD &707FB72D EQUD &7091EA17 : EQUD &70A41A0F : EQUD &70B64719 : EQUD &70C87134 EQUD &70DA9863 : EQUD &70ECBCA7 : EQUD &70FEDE02 : EQUD &7110FC74 EQUD &712317FF : EQUD &713530A5 : EQUD &71474667 : EQUD &71595946 EQUD &716B6944 : EQUD &717D7661 : EQUD &718F80A1 : EQUD &71A18803 EQUD &71B38C89 : EQUD &71C58E35 : EQUD &71D78D08 : EQUD &71E98902 EQUD &71FB8227 : EQUD &720D7876 : EQUD &721F6BF2 : EQUD &72315C9B EQUD &72434A73 : EQUD &7255357C : EQUD &72671DB6 : EQUD &72790323 EQUD &728AE5C4 : EQUD &729CC59A : EQUD &72AEA2A8 : EQUD &72C07CED EQUD &72D2546C : EQUD &72E42926 : EQUD &72F5FB1B : EQUD &7307CA4E EQUD &731996C0 : EQUD &732B6071 : EQUD &733D2763 : EQUD &734EEB98 EQUD &7360AD10 : EQUD &73726BCE : EQUD &738427D1 : EQUD &7395E11C EQUD &73A797AF : EQUD &73B94B8D : EQUD &73CAFCB6 : EQUD &73DCAB2B EQUD &73EE56ED : EQUD &73FFFFFF : EQUD &7411A660 : EQUD &74234A13 EQUD &7434EB19 : EQUD &74468972 : EQUD &74582520 : EQUD &7469BE25 EQUD &747B5481 : EQUD &748CE835 : EQUD &749E7943 : EQUD &74B007AC EQUD &74C19372 : EQUD &74D31C95 : EQUD &74E4A316 : EQUD &74F626F7 EQUD &7507A839 : EQUD &751926DD : EQUD &752AA2E5 : EQUD &753C1C50 EQUD &754D9322 : EQUD &755F075A : EQUD &757078FA : EQUD &7581E804 EQUD &75935477 : EQUD &75A4BE56 : EQUD &75B625A1 : EQUD &75C78A5A EQUD &75D8EC82 : EQUD &75EA4C1A : EQUD &75FBA923 : EQUD &760D039E EQUD &761E5B8C : EQUD &762FB0EF : EQUD &764103C8 : EQUD &76525417 EQUD &7663A1DE : EQUD &7674ED1D : EQUD &768635D7 : EQUD &76977C0C EQUD &76A8BFBD : EQUD &76BA00EC : EQUD &76CB3F99 : EQUD &76DC7BC5 EQUD &76EDB572 : EQUD &76FEECA1 : EQUD &77102152 : EQUD &77215388 EQUD &77328342 : EQUD &7743B082 : EQUD &7754DB49 : EQUD &77660399 EQUD &77772971 : EQUD &77884CD4 : EQUD &77996DC2 : EQUD &77AA8C3D EQUD &77BBA844 : EQUD &77CCC1DB : EQUD &77DDD900 : EQUD &77EEEDB7 EQUD &77FFFFFF : EQUD &78110FD9 : EQUD &78221D48 : EQUD &7833284A EQUD &784430E3 : EQUD &78553712 : EQUD &78663AD9 : EQUD &78773C39 EQUD &78883B33 : EQUD &789937C8 : EQUD &78AA31F8 : EQUD &78BB29C5 EQUD &78CC1F30 : EQUD &78DD123A : EQUD &78EE02E4 : EQUD &78FEF12E EQUD &790FDD1B : EQUD &7920C6AA : EQUD &7931ADDD : EQUD &794292B5 EQUD &79537532 : EQUD &79645557 : EQUD &79753323 : EQUD &79860E98 EQUD &7996E7B6 : EQUD &79A7BE80 : EQUD &79B892F5 : EQUD &79C96516 EQUD &79DA34E5 : EQUD &79EB0263 : EQUD &79FBCD90 : EQUD &7A0C966D EQUD &7A1D5CFC : EQUD &7A2E213E : EQUD &7A3EE332 : EQUD &7A4FA2DB EQUD &7A606039 : EQUD &7A711B4D : EQUD &7A81D419 : EQUD &7A928A9C EQUD &7AA33ED8 : EQUD &7AB3F0CE : EQUD &7AC4A07F : EQUD &7AD54DEB EQUD &7AE5F914 : EQUD &7AF6A1FB : EQUD &7B0748A0 : EQUD &7B17ED05 EQUD &7B288F29 : EQUD &7B392F0F : EQUD &7B49CCB8 : EQUD &7B5A6823 EQUD &7B6B0152 : EQUD &7B7B9846 : EQUD &7B8C2D00 : EQUD &7B9CBF80 EQUD &7BAD4FC8 : EQUD &7BBDDDD8 : EQUD &7BCE69B1 : EQUD &7BDEF355 EQUD &7BEF7AC4 : EQUD &7BFFFFFF : EQUD &7C108306 : EQUD &7C2103DC EQUD &7C318280 : EQUD &7C41FEF3 : EQUD &7C527937 : EQUD &7C62F14C EQUD &7C736732 : EQUD &7C83DAEC : EQUD &7C944C7A : EQUD &7CA4BBDC EQUD &7CB52914 : EQUD &7CC59423 : EQUD &7CD5FD08 : EQUD &7CE663C6 EQUD &7CF6C85C : EQUD &7D072ACC : EQUD &7D178B17 : EQUD &7D27E93D EQUD &7D384540 : EQUD &7D489F20 : EQUD &7D58F6DD : EQUD &7D694C7A EQUD &7D799FF6 : EQUD &7D89F153 : EQUD &7D9A4090 : EQUD &7DAA8DB0 EQUD &7DBAD8B3 : EQUD &7DCB219A : EQUD &7DDB6865 : EQUD &7DEBAD16 EQUD &7DFBEFAD : EQUD &7E0C302B : EQUD &7E1C6E91 : EQUD &7E2CAADF EQUD &7E3CE517 : EQUD &7E4D1D39 : EQUD &7E5D5346 : EQUD &7E6D873F EQUD &7E7DB925 : EQUD &7E8DE8F8 : EQUD &7E9E16BA : EQUD &7EAE426A EQUD &7EBE6C0A : EQUD &7ECE939B : EQUD &7EDEB91E : EQUD &7EEEDC92 EQUD &7EFEFDFA : EQUD &7F0F1D55 : EQUD &7F1F3AA5 : EQUD &7F2F55EA EQUD &7F3F6F25 : EQUD &7F4F8657 : EQUD &7F5F9B81 : EQUD &7F6FAEA3 EQUD &7F7FBFBE : EQUD &7F8FCED4 : EQUD &7F9FDBE4 : EQUD &7FAFE6EF EQUD &7FBFEFF7 : EQUD &7FCFF6FB : EQUD &7FDFFBFE : EQUD &7FEFFEFF \ ****************************************************************************** \ \ Name; divisionTable \ Type; Variable \ Category; Maths [Arithmetic] \ Summary; Division lookup tables \ \ ------------------------------------------------------------------------------ \ \ There are 64 tables, each one for a different denominator d [d = 0 to 63] \ \ In table d, byte n in the table contains; \ \ 65536 * n / d \ \ For n = 0 to 63 [the value is &FFFFFFFF when n = 0] \ \ The address of the table containing the values of n / d is; \ \ divisionTable + d * 256 \ \ In the original BBC BASIC source, this table would have been populated using \ something along these lines; \ \ FOR I% = 0 TO 63 \ [ \ OPT pass% \ EQUD &FFFFFFFF \ ] \ FOR J% = 1 TO 63 \ [ \ OPT pass% \ EQUD 65536 * I% / J% \ ] \ NEXT \ NEXT \ \ I have used EQUDs here because different computers have different algorithms \ and accuracies in their maths routines, so the only way to ensure a complete \ match with the original binaries is to hard-code the values. \ \ The above loop produces the correct values when run on an Archimedes. \ \ ****************************************************************************** .divisionTable EQUD &FFFFFFFF : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 \ n / 0 [n = 0 to 63] EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &00000000 : EQUD &00000000 : EQUD &00000000 : EQUD &00000000 EQUD &FFFFFFFF : EQUD &00010000 : EQUD &00008000 : EQUD &00005555 \ n / 1 [n = 0 to 63] EQUD &00004000 : EQUD &00003333 : EQUD &00002AAA : EQUD &00002492 EQUD &00002000 : EQUD &00001C71 : EQUD &00001999 : EQUD &00001745 EQUD &00001555 : EQUD &000013B1 : EQUD &00001249 : EQUD &00001111 EQUD &00001000 : EQUD &00000F0F : EQUD &00000E38 : EQUD &00000D79 EQUD &00000CCC : EQUD &00000C30 : EQUD &00000BA2 : EQUD &00000B21 EQUD &00000AAA : EQUD &00000A3D : EQUD &000009D8 : EQUD &0000097B EQUD &00000924 : EQUD &000008D3 : EQUD &00000888 : EQUD &00000842 EQUD &00000800 : EQUD &000007C1 : EQUD &00000787 : EQUD &00000750 EQUD &0000071C : EQUD &000006EB : EQUD &000006BC : EQUD &00000690 EQUD &00000666 : EQUD &0000063E : EQUD &00000618 : EQUD &000005F4 EQUD &000005D1 : EQUD &000005B0 : EQUD &00000590 : EQUD &00000572 EQUD &00000555 : EQUD &00000539 : EQUD &0000051E : EQUD &00000505 EQUD &000004EC : EQUD &000004D4 : EQUD &000004BD : EQUD &000004A7 EQUD &00000492 : EQUD &0000047D : EQUD &00000469 : EQUD &00000456 EQUD &00000444 : EQUD &00000432 : EQUD &00000421 : EQUD &00000410 EQUD &FFFFFFFF : EQUD &00020000 : EQUD &00010000 : EQUD &0000AAAA \ n / 2 [n = 0 to 63] EQUD &00008000 : EQUD &00006666 : EQUD &00005555 : EQUD &00004924 EQUD &00004000 : EQUD &000038E3 : EQUD &00003333 : EQUD &00002E8B EQUD &00002AAA : EQUD &00002762 : EQUD &00002492 : EQUD &00002222 EQUD &00002000 : EQUD &00001E1E : EQUD &00001C71 : EQUD &00001AF2 EQUD &00001999 : EQUD &00001861 : EQUD &00001745 : EQUD &00001642 EQUD &00001555 : EQUD &0000147A : EQUD &000013B1 : EQUD &000012F6 EQUD &00001249 : EQUD &000011A7 : EQUD &00001111 : EQUD &00001084 EQUD &00001000 : EQUD &00000F83 : EQUD &00000F0F : EQUD &00000EA0 EQUD &00000E38 : EQUD &00000DD6 : EQUD &00000D79 : EQUD &00000D20 EQUD &00000CCC : EQUD &00000C7C : EQUD &00000C30 : EQUD &00000BE8 EQUD &00000BA2 : EQUD &00000B60 : EQUD &00000B21 : EQUD &00000AE4 EQUD &00000AAA : EQUD &00000A72 : EQUD &00000A3D : EQUD &00000A0A EQUD &000009D8 : EQUD &000009A9 : EQUD &0000097B : EQUD &0000094F EQUD &00000924 : EQUD &000008FB : EQUD &000008D3 : EQUD &000008AD EQUD &00000888 : EQUD &00000864 : EQUD &00000842 : EQUD &00000820 EQUD &FFFFFFFF : EQUD &00030000 : EQUD &00018000 : EQUD &00010000 \ n / 3 [n = 0 to 63] EQUD &0000C000 : EQUD &00009999 : EQUD &00008000 : EQUD &00006DB6 EQUD &00006000 : EQUD &00005555 : EQUD &00004CCC : EQUD &000045D1 EQUD &00004000 : EQUD &00003B13 : EQUD &000036DB : EQUD &00003333 EQUD &00003000 : EQUD &00002D2D : EQUD &00002AAA : EQUD &0000286B EQUD &00002666 : EQUD &00002492 : EQUD &000022E8 : EQUD &00002164 EQUD &00002000 : EQUD &00001EB8 : EQUD &00001D89 : EQUD &00001C71 EQUD &00001B6D : EQUD &00001A7B : EQUD &00001999 : EQUD &000018C6 EQUD &00001800 : EQUD &00001745 : EQUD &00001696 : EQUD &000015F1 EQUD &00001555 : EQUD &000014C1 : EQUD &00001435 : EQUD &000013B1 EQUD &00001333 : EQUD &000012BB : EQUD &00001249 : EQUD &000011DC EQUD &00001174 : EQUD &00001111 : EQUD &000010B2 : EQUD &00001057 EQUD &00001000 : EQUD &00000FAC : EQUD &00000F5C : EQUD &00000F0F EQUD &00000EC4 : EQUD &00000E7D : EQUD &00000E38 : EQUD &00000DF6 EQUD &00000DB6 : EQUD &00000D79 : EQUD &00000D3D : EQUD &00000D04 EQUD &00000CCC : EQUD &00000C97 : EQUD &00000C63 : EQUD &00000C30 EQUD &FFFFFFFF : EQUD &00040000 : EQUD &00020000 : EQUD &00015555 \ n / 4 [n = 0 to 63] EQUD &00010000 : EQUD &0000CCCC : EQUD &0000AAAA : EQUD &00009249 EQUD &00008000 : EQUD &000071C7 : EQUD &00006666 : EQUD &00005D17 EQUD &00005555 : EQUD &00004EC4 : EQUD &00004924 : EQUD &00004444 EQUD &00004000 : EQUD &00003C3C : EQUD &000038E3 : EQUD &000035E5 EQUD &00003333 : EQUD &000030C3 : EQUD &00002E8B : EQUD &00002C85 EQUD &00002AAA : EQUD &000028F5 : EQUD &00002762 : EQUD &000025ED EQUD &00002492 : EQUD &0000234F : EQUD &00002222 : EQUD &00002108 EQUD &00002000 : EQUD &00001F07 : EQUD &00001E1E : EQUD &00001D41 EQUD &00001C71 : EQUD &00001BAC : EQUD &00001AF2 : EQUD &00001A41 EQUD &00001999 : EQUD &000018F9 : EQUD &00001861 : EQUD &000017D0 EQUD &00001745 : EQUD &000016C1 : EQUD &00001642 : EQUD &000015C9 EQUD &00001555 : EQUD &000014E5 : EQUD &0000147A : EQUD &00001414 EQUD &000013B1 : EQUD &00001352 : EQUD &000012F6 : EQUD &0000129E EQUD &00001249 : EQUD &000011F7 : EQUD &000011A7 : EQUD &0000115B EQUD &00001111 : EQUD &000010C9 : EQUD &00001084 : EQUD &00001041 EQUD &FFFFFFFF : EQUD &00050000 : EQUD &00028000 : EQUD &0001AAAA \ n / 5 [n = 0 to 63] EQUD &00014000 : EQUD &00010000 : EQUD &0000D555 : EQUD &0000B6DB EQUD &0000A000 : EQUD &00008E38 : EQUD &00008000 : EQUD &0000745D EQUD &00006AAA : EQUD &00006276 : EQUD &00005B6D : EQUD &00005555 EQUD &00005000 : EQUD &00004B4B : EQUD &0000471C : EQUD &0000435E EQUD &00004000 : EQUD &00003CF3 : EQUD &00003A2E : EQUD &000037A6 EQUD &00003555 : EQUD &00003333 : EQUD &0000313B : EQUD &00002F68 EQUD &00002DB6 : EQUD &00002C23 : EQUD &00002AAA : EQUD &0000294A EQUD &00002800 : EQUD &000026C9 : EQUD &000025A5 : EQUD &00002492 EQUD &0000238E : EQUD &00002298 : EQUD &000021AF : EQUD &000020D2 EQUD &00002000 : EQUD &00001F38 : EQUD &00001E79 : EQUD &00001DC4 EQUD &00001D17 : EQUD &00001C71 : EQUD &00001BD3 : EQUD &00001B3B EQUD &00001AAA : EQUD &00001A1F : EQUD &00001999 : EQUD &00001919 EQUD &0000189D : EQUD &00001826 : EQUD &000017B4 : EQUD &00001745 EQUD &000016DB : EQUD &00001674 : EQUD &00001611 : EQUD &000015B1 EQUD &00001555 : EQUD &000014FB : EQUD &000014A5 : EQUD &00001451 EQUD &FFFFFFFF : EQUD &00060000 : EQUD &00030000 : EQUD &00020000 \ n / 6 [n = 0 to 63] EQUD &00018000 : EQUD &00013333 : EQUD &00010000 : EQUD &0000DB6D EQUD &0000C000 : EQUD &0000AAAA : EQUD &00009999 : EQUD &00008BA2 EQUD &00008000 : EQUD &00007627 : EQUD &00006DB6 : EQUD &00006666 EQUD &00006000 : EQUD &00005A5A : EQUD &00005555 : EQUD &000050D7 EQUD &00004CCC : EQUD &00004924 : EQUD &000045D1 : EQUD &000042C8 EQUD &00004000 : EQUD &00003D70 : EQUD &00003B13 : EQUD &000038E3 EQUD &000036DB : EQUD &000034F7 : EQUD &00003333 : EQUD &0000318C EQUD &00003000 : EQUD &00002E8B : EQUD &00002D2D : EQUD &00002BE2 EQUD &00002AAA : EQUD &00002983 : EQUD &0000286B : EQUD &00002762 EQUD &00002666 : EQUD &00002576 : EQUD &00002492 : EQUD &000023B8 EQUD &000022E8 : EQUD &00002222 : EQUD &00002164 : EQUD &000020AE EQUD &00002000 : EQUD &00001F58 : EQUD &00001EB8 : EQUD &00001E1E EQUD &00001D89 : EQUD &00001CFB : EQUD &00001C71 : EQUD &00001BED EQUD &00001B6D : EQUD &00001AF2 : EQUD &00001A7B : EQUD &00001A08 EQUD &00001999 : EQUD &0000192E : EQUD &000018C6 : EQUD &00001861 EQUD &FFFFFFFF : EQUD &00070000 : EQUD &00038000 : EQUD &00025555 \ n / 7 [n = 0 to 63] EQUD &0001C000 : EQUD &00016666 : EQUD &00012AAA : EQUD &00010000 EQUD &0000E000 : EQUD &0000C71C : EQUD &0000B333 : EQUD &0000A2E8 EQUD &00009555 : EQUD &000089D8 : EQUD &00008000 : EQUD &00007777 EQUD &00007000 : EQUD &00006969 : EQUD &0000638E : EQUD &00005E50 EQUD &00005999 : EQUD &00005555 : EQUD &00005174 : EQUD &00004DE9 EQUD &00004AAA : EQUD &000047AE : EQUD &000044EC : EQUD &0000425E EQUD &00004000 : EQUD &00003DCB : EQUD &00003BBB : EQUD &000039CE EQUD &00003800 : EQUD &0000364D : EQUD &000034B4 : EQUD &00003333 EQUD &000031C7 : EQUD &0000306E : EQUD &00002F28 : EQUD &00002DF2 EQUD &00002CCC : EQUD &00002BB5 : EQUD &00002AAA : EQUD &000029AC EQUD &000028BA : EQUD &000027D2 : EQUD &000026F4 : EQUD &00002620 EQUD &00002555 : EQUD &00002492 : EQUD &000023D7 : EQUD &00002323 EQUD &00002276 : EQUD &000021CF : EQUD &0000212F : EQUD &00002094 EQUD &00002000 : EQUD &00001F70 : EQUD &00001EE5 : EQUD &00001E5F EQUD &00001DDD : EQUD &00001D60 : EQUD &00001CE7 : EQUD &00001C71 EQUD &FFFFFFFF : EQUD &00080000 : EQUD &00040000 : EQUD &0002AAAA \ n / 8 [n = 0 to 63] EQUD &00020000 : EQUD &00019999 : EQUD &00015555 : EQUD &00012492 EQUD &00010000 : EQUD &0000E38E : EQUD &0000CCCC : EQUD &0000BA2E EQUD &0000AAAA : EQUD &00009D89 : EQUD &00009249 : EQUD &00008888 EQUD &00008000 : EQUD &00007878 : EQUD &000071C7 : EQUD &00006BCA EQUD &00006666 : EQUD &00006186 : EQUD &00005D17 : EQUD &0000590B EQUD &00005555 : EQUD &000051EB : EQUD &00004EC4 : EQUD &00004BDA EQUD &00004924 : EQUD &0000469E : EQUD &00004444 : EQUD &00004210 EQUD &00004000 : EQUD &00003E0F : EQUD &00003C3C : EQUD &00003A83 EQUD &000038E3 : EQUD &00003759 : EQUD &000035E5 : EQUD &00003483 EQUD &00003333 : EQUD &000031F3 : EQUD &000030C3 : EQUD &00002FA0 EQUD &00002E8B : EQUD &00002D82 : EQUD &00002C85 : EQUD &00002B93 EQUD &00002AAA : EQUD &000029CB : EQUD &000028F5 : EQUD &00002828 EQUD &00002762 : EQUD &000026A4 : EQUD &000025ED : EQUD &0000253C EQUD &00002492 : EQUD &000023EE : EQUD &0000234F : EQUD &000022B6 EQUD &00002222 : EQUD &00002192 : EQUD &00002108 : EQUD &00002082 EQUD &FFFFFFFF : EQUD &00090000 : EQUD &00048000 : EQUD &00030000 \ n / 9 [n = 0 to 63] EQUD &00024000 : EQUD &0001CCCC : EQUD &00018000 : EQUD &00014924 EQUD &00012000 : EQUD &00010000 : EQUD &0000E666 : EQUD &0000D174 EQUD &0000C000 : EQUD &0000B13B : EQUD &0000A492 : EQUD &00009999 EQUD &00009000 : EQUD &00008787 : EQUD &00008000 : EQUD &00007943 EQUD &00007333 : EQUD &00006DB6 : EQUD &000068BA : EQUD &0000642C EQUD &00006000 : EQUD &00005C28 : EQUD &0000589D : EQUD &00005555 EQUD &00005249 : EQUD &00004F72 : EQUD &00004CCC : EQUD &00004A52 EQUD &00004800 : EQUD &000045D1 : EQUD &000043C3 : EQUD &000041D4 EQUD &00004000 : EQUD &00003E45 : EQUD &00003CA1 : EQUD &00003B13 EQUD &00003999 : EQUD &00003831 : EQUD &000036DB : EQUD &00003594 EQUD &0000345D : EQUD &00003333 : EQUD &00003216 : EQUD &00003105 EQUD &00003000 : EQUD &00002F05 : EQUD &00002E14 : EQUD &00002D2D EQUD &00002C4E : EQUD &00002B78 : EQUD &00002AAA : EQUD &000029E4 EQUD &00002924 : EQUD &0000286B : EQUD &000027B9 : EQUD &0000270D EQUD &00002666 : EQUD &000025C5 : EQUD &00002529 : EQUD &00002492 EQUD &FFFFFFFF : EQUD &000A0000 : EQUD &00050000 : EQUD &00035555 \ n / 10 [n = 0 to 63] EQUD &00028000 : EQUD &00020000 : EQUD &0001AAAA : EQUD &00016DB6 EQUD &00014000 : EQUD &00011C71 : EQUD &00010000 : EQUD &0000E8BA EQUD &0000D555 : EQUD &0000C4EC : EQUD &0000B6DB : EQUD &0000AAAA EQUD &0000A000 : EQUD &00009696 : EQUD &00008E38 : EQUD &000086BC EQUD &00008000 : EQUD &000079E7 : EQUD &0000745D : EQUD &00006F4D EQUD &00006AAA : EQUD &00006666 : EQUD &00006276 : EQUD &00005ED0 EQUD &00005B6D : EQUD &00005846 : EQUD &00005555 : EQUD &00005294 EQUD &00005000 : EQUD &00004D93 : EQUD &00004B4B : EQUD &00004924 EQUD &0000471C : EQUD &00004530 : EQUD &0000435E : EQUD &000041A4 EQUD &00004000 : EQUD &00003E70 : EQUD &00003CF3 : EQUD &00003B88 EQUD &00003A2E : EQUD &000038E3 : EQUD &000037A6 : EQUD &00003677 EQUD &00003555 : EQUD &0000343E : EQUD &00003333 : EQUD &00003232 EQUD &0000313B : EQUD &0000304D : EQUD &00002F68 : EQUD &00002E8B EQUD &00002DB6 : EQUD &00002CE9 : EQUD &00002C23 : EQUD &00002B63 EQUD &00002AAA : EQUD &000029F7 : EQUD &0000294A : EQUD &000028A2 EQUD &FFFFFFFF : EQUD &000B0000 : EQUD &00058000 : EQUD &0003AAAA \ n / 11 [n = 0 to 63] EQUD &0002C000 : EQUD &00023333 : EQUD &0001D555 : EQUD &00019249 EQUD &00016000 : EQUD &000138E3 : EQUD &00011999 : EQUD &00010000 EQUD &0000EAAA : EQUD &0000D89D : EQUD &0000C924 : EQUD &0000BBBB EQUD &0000B000 : EQUD &0000A5A5 : EQUD &00009C71 : EQUD &00009435 EQUD &00008CCC : EQUD &00008618 : EQUD &00008000 : EQUD &00007A6F EQUD &00007555 : EQUD &000070A3 : EQUD &00006C4E : EQUD &0000684B EQUD &00006492 : EQUD &0000611A : EQUD &00005DDD : EQUD &00005AD6 EQUD &00005800 : EQUD &00005555 : EQUD &000052D2 : EQUD &00005075 EQUD &00004E38 : EQUD &00004C1B : EQUD &00004A1A : EQUD &00004834 EQUD &00004666 : EQUD &000044AE : EQUD &0000430C : EQUD &0000417D EQUD &00004000 : EQUD &00003E93 : EQUD &00003D37 : EQUD &00003BEA EQUD &00003AAA : EQUD &00003978 : EQUD &00003851 : EQUD &00003737 EQUD &00003627 : EQUD &00003521 : EQUD &00003425 : EQUD &00003333 EQUD &00003249 : EQUD &00003167 : EQUD &0000308D : EQUD &00002FBA EQUD &00002EEE : EQUD &00002E29 : EQUD &00002D6B : EQUD &00002CB2 EQUD &FFFFFFFF : EQUD &000C0000 : EQUD &00060000 : EQUD &00040000 \ n / 12 [n = 0 to 63] EQUD &00030000 : EQUD &00026666 : EQUD &00020000 : EQUD &0001B6DB EQUD &00018000 : EQUD &00015555 : EQUD &00013333 : EQUD &00011745 EQUD &00010000 : EQUD &0000EC4E : EQUD &0000DB6D : EQUD &0000CCCC EQUD &0000C000 : EQUD &0000B4B4 : EQUD &0000AAAA : EQUD &0000A1AF EQUD &00009999 : EQUD &00009249 : EQUD &00008BA2 : EQUD &00008590 EQUD &00008000 : EQUD &00007AE1 : EQUD &00007627 : EQUD &000071C7 EQUD &00006DB6 : EQUD &000069EE : EQUD &00006666 : EQUD &00006318 EQUD &00006000 : EQUD &00005D17 : EQUD &00005A5A : EQUD &000057C5 EQUD &00005555 : EQUD &00005306 : EQUD &000050D7 : EQUD &00004EC4 EQUD &00004CCC : EQUD &00004AED : EQUD &00004924 : EQUD &00004771 EQUD &000045D1 : EQUD &00004444 : EQUD &000042C8 : EQUD &0000415C EQUD &00004000 : EQUD &00003EB1 : EQUD &00003D70 : EQUD &00003C3C EQUD &00003B13 : EQUD &000039F6 : EQUD &000038E3 : EQUD &000037DA EQUD &000036DB : EQUD &000035E5 : EQUD &000034F7 : EQUD &00003411 EQUD &00003333 : EQUD &0000325C : EQUD &0000318C : EQUD &000030C3 EQUD &FFFFFFFF : EQUD &000D0000 : EQUD &00068000 : EQUD &00045555 \ n / 13 [n = 0 to 63] EQUD &00034000 : EQUD &00029999 : EQUD &00022AAA : EQUD &0001DB6D EQUD &0001A000 : EQUD &000171C7 : EQUD &00014CCC : EQUD &00012E8B EQUD &00011555 : EQUD &00010000 : EQUD &0000EDB6 : EQUD &0000DDDD EQUD &0000D000 : EQUD &0000C3C3 : EQUD &0000B8E3 : EQUD &0000AF28 EQUD &0000A666 : EQUD &00009E79 : EQUD &00009745 : EQUD &000090B2 EQUD &00008AAA : EQUD &0000851E : EQUD &00008000 : EQUD &00007B42 EQUD &000076DB : EQUD &000072C2 : EQUD &00006EEE : EQUD &00006B5A EQUD &00006800 : EQUD &000064D9 : EQUD &000061E1 : EQUD &00005F15 EQUD &00005C71 : EQUD &000059F2 : EQUD &00005794 : EQUD &00005555 EQUD &00005333 : EQUD &0000512B : EQUD &00004F3C : EQUD &00004D65 EQUD &00004BA2 : EQUD &000049F4 : EQUD &00004859 : EQUD &000046CE EQUD &00004555 : EQUD &000043EB : EQUD &0000428F : EQUD &00004141 EQUD &00004000 : EQUD &00003ECA : EQUD &00003DA1 : EQUD &00003C82 EQUD &00003B6D : EQUD &00003A62 : EQUD &00003961 : EQUD &00003868 EQUD &00003777 : EQUD &0000368E : EQUD &000035AD : EQUD &000034D3 EQUD &FFFFFFFF : EQUD &000E0000 : EQUD &00070000 : EQUD &0004AAAA \ n / 14 [n = 0 to 63] EQUD &00038000 : EQUD &0002CCCC : EQUD &00025555 : EQUD &00020000 EQUD &0001C000 : EQUD &00018E38 : EQUD &00016666 : EQUD &000145D1 EQUD &00012AAA : EQUD &000113B1 : EQUD &00010000 : EQUD &0000EEEE EQUD &0000E000 : EQUD &0000D2D2 : EQUD &0000C71C : EQUD &0000BCA1 EQUD &0000B333 : EQUD &0000AAAA : EQUD &0000A2E8 : EQUD &00009BD3 EQUD &00009555 : EQUD &00008F5C : EQUD &000089D8 : EQUD &000084BD EQUD &00008000 : EQUD &00007B96 : EQUD &00007777 : EQUD &0000739C EQUD &00007000 : EQUD &00006C9B : EQUD &00006969 : EQUD &00006666 EQUD &0000638E : EQUD &000060DD : EQUD &00005E50 : EQUD &00005BE5 EQUD &00005999 : EQUD &0000576A : EQUD &00005555 : EQUD &00005359 EQUD &00005174 : EQUD &00004FA4 : EQUD &00004DE9 : EQUD &00004C41 EQUD &00004AAA : EQUD &00004924 : EQUD &000047AE : EQUD &00004646 EQUD &000044EC : EQUD &0000439F : EQUD &0000425E : EQUD &00004129 EQUD &00004000 : EQUD &00003EE0 : EQUD &00003DCB : EQUD &00003CBE EQUD &00003BBB : EQUD &00003AC1 : EQUD &000039CE : EQUD &000038E3 EQUD &FFFFFFFF : EQUD &000F0000 : EQUD &00078000 : EQUD &00050000 \ n / 15 [n = 0 to 63] EQUD &0003C000 : EQUD &00030000 : EQUD &00028000 : EQUD &00022492 EQUD &0001E000 : EQUD &0001AAAA : EQUD &00018000 : EQUD &00015D17 EQUD &00014000 : EQUD &00012762 : EQUD &00011249 : EQUD &00010000 EQUD &0000F000 : EQUD &0000E1E1 : EQUD &0000D555 : EQUD &0000CA1A EQUD &0000C000 : EQUD &0000B6DB : EQUD &0000AE8B : EQUD &0000A6F4 EQUD &0000A000 : EQUD &00009999 : EQUD &000093B1 : EQUD &00008E38 EQUD &00008924 : EQUD &00008469 : EQUD &00008000 : EQUD &00007BDE EQUD &00007800 : EQUD &0000745D : EQUD &000070F0 : EQUD &00006DB6 EQUD &00006AAA : EQUD &000067C8 : EQUD &0000650D : EQUD &00006276 EQUD &00006000 : EQUD &00005DA8 : EQUD &00005B6D : EQUD &0000594D EQUD &00005745 : EQUD &00005555 : EQUD &0000537A : EQUD &000051B3 EQUD &00005000 : EQUD &00004E5E : EQUD &00004CCC : EQUD &00004B4B EQUD &000049D8 : EQUD &00004873 : EQUD &0000471C : EQUD &000045D1 EQUD &00004492 : EQUD &0000435E : EQUD &00004234 : EQUD &00004115 EQUD &00004000 : EQUD &00003EF3 : EQUD &00003DEF : EQUD &00003CF3 EQUD &FFFFFFFF : EQUD &00100000 : EQUD &00080000 : EQUD &00055555 \ n / 16 [n = 0 to 63] EQUD &00040000 : EQUD &00033333 : EQUD &0002AAAA : EQUD &00024924 EQUD &00020000 : EQUD &0001C71C : EQUD &00019999 : EQUD &0001745D EQUD &00015555 : EQUD &00013B13 : EQUD &00012492 : EQUD &00011111 EQUD &00010000 : EQUD &0000F0F0 : EQUD &0000E38E : EQUD &0000D794 EQUD &0000CCCC : EQUD &0000C30C : EQUD &0000BA2E : EQUD &0000B216 EQUD &0000AAAA : EQUD &0000A3D7 : EQUD &00009D89 : EQUD &000097B4 EQUD &00009249 : EQUD &00008D3D : EQUD &00008888 : EQUD &00008421 EQUD &00008000 : EQUD &00007C1F : EQUD &00007878 : EQUD &00007507 EQUD &000071C7 : EQUD &00006EB3 : EQUD &00006BCA : EQUD &00006906 EQUD &00006666 : EQUD &000063E7 : EQUD &00006186 : EQUD &00005F41 EQUD &00005D17 : EQUD &00005B05 : EQUD &0000590B : EQUD &00005726 EQUD &00005555 : EQUD &00005397 : EQUD &000051EB : EQUD &00005050 EQUD &00004EC4 : EQUD &00004D48 : EQUD &00004BDA : EQUD &00004A79 EQUD &00004924 : EQUD &000047DC : EQUD &0000469E : EQUD &0000456C EQUD &00004444 : EQUD &00004325 : EQUD &00004210 : EQUD &00004104 EQUD &FFFFFFFF : EQUD &00110000 : EQUD &00088000 : EQUD &0005AAAA \ n / 17 [n = 0 to 63] EQUD &00044000 : EQUD &00036666 : EQUD &0002D555 : EQUD &00026DB6 EQUD &00022000 : EQUD &0001E38E : EQUD &0001B333 : EQUD &00018BA2 EQUD &00016AAA : EQUD &00014EC4 : EQUD &000136DB : EQUD &00012222 EQUD &00011000 : EQUD &00010000 : EQUD &0000F1C7 : EQUD &0000E50D EQUD &0000D999 : EQUD &0000CF3C : EQUD &0000C5D1 : EQUD &0000BD37 EQUD &0000B555 : EQUD &0000AE14 : EQUD &0000A762 : EQUD &0000A12F EQUD &00009B6D : EQUD &00009611 : EQUD &00009111 : EQUD &00008C63 EQUD &00008800 : EQUD &000083E0 : EQUD &00008000 : EQUD &00007C57 EQUD &000078E3 : EQUD &0000759F : EQUD &00007286 : EQUD &00006F96 EQUD &00006CCC : EQUD &00006A25 : EQUD &0000679E : EQUD &00006535 EQUD &000062E8 : EQUD &000060B6 : EQUD &00005E9B : EQUD &00005C98 EQUD &00005AAA : EQUD &000058D0 : EQUD &0000570A : EQUD &00005555 EQUD &000053B1 : EQUD &0000521C : EQUD &00005097 : EQUD &00004F20 EQUD &00004DB6 : EQUD &00004C59 : EQUD &00004B08 : EQUD &000049C3 EQUD &00004888 : EQUD &00004758 : EQUD &00004631 : EQUD &00004514 EQUD &FFFFFFFF : EQUD &00120000 : EQUD &00090000 : EQUD &00060000 \ n / 18 [n = 0 to 63] EQUD &00048000 : EQUD &00039999 : EQUD &00030000 : EQUD &00029249 EQUD &00024000 : EQUD &00020000 : EQUD &0001CCCC : EQUD &0001A2E8 EQUD &00018000 : EQUD &00016276 : EQUD &00014924 : EQUD &00013333 EQUD &00012000 : EQUD &00010F0F : EQUD &00010000 : EQUD &0000F286 EQUD &0000E666 : EQUD &0000DB6D : EQUD &0000D174 : EQUD &0000C859 EQUD &0000C000 : EQUD &0000B851 : EQUD &0000B13B : EQUD &0000AAAA EQUD &0000A492 : EQUD &00009EE5 : EQUD &00009999 : EQUD &000094A5 EQUD &00009000 : EQUD &00008BA2 : EQUD &00008787 : EQUD &000083A8 EQUD &00008000 : EQUD &00007C8A : EQUD &00007943 : EQUD &00007627 EQUD &00007333 : EQUD &00007063 : EQUD &00006DB6 : EQUD &00006B29 EQUD &000068BA : EQUD &00006666 : EQUD &0000642C : EQUD &0000620A EQUD &00006000 : EQUD &00005E0A : EQUD &00005C28 : EQUD &00005A5A EQUD &0000589D : EQUD &000056F1 : EQUD &00005555 : EQUD &000053C8 EQUD &00005249 : EQUD &000050D7 : EQUD &00004F72 : EQUD &00004E1A EQUD &00004CCC : EQUD &00004B8A : EQUD &00004A52 : EQUD &00004924 EQUD &FFFFFFFF : EQUD &00130000 : EQUD &00098000 : EQUD &00065555 \ n / 19 [n = 0 to 63] EQUD &0004C000 : EQUD &0003CCCC : EQUD &00032AAA : EQUD &0002B6DB EQUD &00026000 : EQUD &00021C71 : EQUD &0001E666 : EQUD &0001BA2E EQUD &00019555 : EQUD &00017627 : EQUD &00015B6D : EQUD &00014444 EQUD &00013000 : EQUD &00011E1E : EQUD &00010E38 : EQUD &00010000 EQUD &0000F333 : EQUD &0000E79E : EQUD &0000DD17 : EQUD &0000D37A EQUD &0000CAAA : EQUD &0000C28F : EQUD &0000BB13 : EQUD &0000B425 EQUD &0000ADB6 : EQUD &0000A7B9 : EQUD &0000A222 : EQUD &00009CE7 EQUD &00009800 : EQUD &00009364 : EQUD &00008F0F : EQUD &00008AF8 EQUD &0000871C : EQUD &00008375 : EQUD &00008000 : EQUD &00007CB7 EQUD &00007999 : EQUD &000076A2 : EQUD &000073CF : EQUD &0000711D EQUD &00006E8B : EQUD &00006C16 : EQUD &000069BD : EQUD &0000677D EQUD &00006555 : EQUD &00006343 : EQUD &00006147 : EQUD &00005F5F EQUD &00005D89 : EQUD &00005BC6 : EQUD &00005A12 : EQUD &0000586F EQUD &000056DB : EQUD &00005555 : EQUD &000053DC : EQUD &00005270 EQUD &00005111 : EQUD &00004FBC : EQUD &00004E73 : EQUD &00004D34 EQUD &FFFFFFFF : EQUD &00140000 : EQUD &000A0000 : EQUD &0006AAAA \ n / 20 [n = 0 to 63] EQUD &00050000 : EQUD &00040000 : EQUD &00035555 : EQUD &0002DB6D EQUD &00028000 : EQUD &000238E3 : EQUD &00020000 : EQUD &0001D174 EQUD &0001AAAA : EQUD &000189D8 : EQUD &00016DB6 : EQUD &00015555 EQUD &00014000 : EQUD &00012D2D : EQUD &00011C71 : EQUD &00010D79 EQUD &00010000 : EQUD &0000F3CF : EQUD &0000E8BA : EQUD &0000DE9B EQUD &0000D555 : EQUD &0000CCCC : EQUD &0000C4EC : EQUD &0000BDA1 EQUD &0000B6DB : EQUD &0000B08D : EQUD &0000AAAA : EQUD &0000A529 EQUD &0000A000 : EQUD &00009B26 : EQUD &00009696 : EQUD &00009249 EQUD &00008E38 : EQUD &00008A60 : EQUD &000086BC : EQUD &00008348 EQUD &00008000 : EQUD &00007CE0 : EQUD &000079E7 : EQUD &00007711 EQUD &0000745D : EQUD &000071C7 : EQUD &00006F4D : EQUD &00006CEF EQUD &00006AAA : EQUD &0000687D : EQUD &00006666 : EQUD &00006464 EQUD &00006276 : EQUD &0000609A : EQUD &00005ED0 : EQUD &00005D17 EQUD &00005B6D : EQUD &000059D3 : EQUD &00005846 : EQUD &000056C7 EQUD &00005555 : EQUD &000053EF : EQUD &00005294 : EQUD &00005145 EQUD &FFFFFFFF : EQUD &00150000 : EQUD &000A8000 : EQUD &00070000 \ n / 21 [n = 0 to 63] EQUD &00054000 : EQUD &00043333 : EQUD &00038000 : EQUD &00030000 EQUD &0002A000 : EQUD &00025555 : EQUD &00021999 : EQUD &0001E8BA EQUD &0001C000 : EQUD &00019D89 : EQUD &00018000 : EQUD &00016666 EQUD &00015000 : EQUD &00013C3C : EQUD &00012AAA : EQUD &00011AF2 EQUD &00010CCC : EQUD &00010000 : EQUD &0000F45D : EQUD &0000E9BD EQUD &0000E000 : EQUD &0000D70A : EQUD &0000CEC4 : EQUD &0000C71C EQUD &0000C000 : EQUD &0000B961 : EQUD &0000B333 : EQUD &0000AD6B EQUD &0000A800 : EQUD &0000A2E8 : EQUD &00009E1E : EQUD &00009999 EQUD &00009555 : EQUD &0000914C : EQUD &00008D79 : EQUD &000089D8 EQUD &00008666 : EQUD &0000831F : EQUD &00008000 : EQUD &00007D05 EQUD &00007A2E : EQUD &00007777 : EQUD &000074DE : EQUD &00007262 EQUD &00007000 : EQUD &00006DB6 : EQUD &00006B85 : EQUD &00006969 EQUD &00006762 : EQUD &0000656F : EQUD &0000638E : EQUD &000061BE EQUD &00006000 : EQUD &00005E50 : EQUD &00005CB0 : EQUD &00005B1E EQUD &00005999 : EQUD &00005821 : EQUD &000056B5 : EQUD &00005555 EQUD &FFFFFFFF : EQUD &00160000 : EQUD &000B0000 : EQUD &00075555 \ n / 22 [n = 0 to 63] EQUD &00058000 : EQUD &00046666 : EQUD &0003AAAA : EQUD &00032492 EQUD &0002C000 : EQUD &000271C7 : EQUD &00023333 : EQUD &00020000 EQUD &0001D555 : EQUD &0001B13B : EQUD &00019249 : EQUD &00017777 EQUD &00016000 : EQUD &00014B4B : EQUD &000138E3 : EQUD &0001286B EQUD &00011999 : EQUD &00010C30 : EQUD &00010000 : EQUD &0000F4DE EQUD &0000EAAA : EQUD &0000E147 : EQUD &0000D89D : EQUD &0000D097 EQUD &0000C924 : EQUD &0000C234 : EQUD &0000BBBB : EQUD &0000B5AD EQUD &0000B000 : EQUD &0000AAAA : EQUD &0000A5A5 : EQUD &0000A0EA EQUD &00009C71 : EQUD &00009837 : EQUD &00009435 : EQUD &00009069 EQUD &00008CCC : EQUD &0000895D : EQUD &00008618 : EQUD &000082FA EQUD &00008000 : EQUD &00007D27 : EQUD &00007A6F : EQUD &000077D4 EQUD &00007555 : EQUD &000072F0 : EQUD &000070A3 : EQUD &00006E6E EQUD &00006C4E : EQUD &00006A43 : EQUD &0000684B : EQUD &00006666 EQUD &00006492 : EQUD &000062CE : EQUD &0000611A : EQUD &00005F75 EQUD &00005DDD : EQUD &00005C53 : EQUD &00005AD6 : EQUD &00005965 EQUD &FFFFFFFF : EQUD &00170000 : EQUD &000B8000 : EQUD &0007AAAA \ n / 23 [n = 0 to 63] EQUD &0005C000 : EQUD &00049999 : EQUD &0003D555 : EQUD &00034924 EQUD &0002E000 : EQUD &00028E38 : EQUD &00024CCC : EQUD &00021745 EQUD &0001EAAA : EQUD &0001C4EC : EQUD &0001A492 : EQUD &00018888 EQUD &00017000 : EQUD &00015A5A : EQUD &0001471C : EQUD &000135E5 EQUD &00012666 : EQUD &00011861 : EQUD &00010BA2 : EQUD &00010000 EQUD &0000F555 : EQUD &0000EB85 : EQUD &0000E276 : EQUD &0000DA12 EQUD &0000D249 : EQUD &0000CB08 : EQUD &0000C444 : EQUD &0000BDEF EQUD &0000B800 : EQUD &0000B26C : EQUD &0000AD2D : EQUD &0000A83A EQUD &0000A38E : EQUD &00009F22 : EQUD &00009AF2 : EQUD &000096F9 EQUD &00009333 : EQUD &00008F9C : EQUD &00008C30 : EQUD &000088EE EQUD &000085D1 : EQUD &000082D8 : EQUD &00008000 : EQUD &00007D46 EQUD &00007AAA : EQUD &00007829 : EQUD &000075C2 : EQUD &00007373 EQUD &0000713B : EQUD &00006F18 : EQUD &00006D09 : EQUD &00006B0D EQUD &00006924 : EQUD &0000674C : EQUD &00006584 : EQUD &000063CB EQUD &00006222 : EQUD &00006086 : EQUD &00005EF7 : EQUD &00005D75 EQUD &FFFFFFFF : EQUD &00180000 : EQUD &000C0000 : EQUD &00080000 \ n / 24 [n = 0 to 63] EQUD &00060000 : EQUD &0004CCCC : EQUD &00040000 : EQUD &00036DB6 EQUD &00030000 : EQUD &0002AAAA : EQUD &00026666 : EQUD &00022E8B EQUD &00020000 : EQUD &0001D89D : EQUD &0001B6DB : EQUD &00019999 EQUD &00018000 : EQUD &00016969 : EQUD &00015555 : EQUD &0001435E EQUD &00013333 : EQUD &00012492 : EQUD &00011745 : EQUD &00010B21 EQUD &00010000 : EQUD &0000F5C2 : EQUD &0000EC4E : EQUD &0000E38E EQUD &0000DB6D : EQUD &0000D3DC : EQUD &0000CCCC : EQUD &0000C631 EQUD &0000C000 : EQUD &0000BA2E : EQUD &0000B4B4 : EQUD &0000AF8A EQUD &0000AAAA : EQUD &0000A60D : EQUD &0000A1AF : EQUD &00009D89 EQUD &00009999 : EQUD &000095DA : EQUD &00009249 : EQUD &00008EE2 EQUD &00008BA2 : EQUD &00008888 : EQUD &00008590 : EQUD &000082B9 EQUD &00008000 : EQUD &00007D63 : EQUD &00007AE1 : EQUD &00007878 EQUD &00007627 : EQUD &000073EC : EQUD &000071C7 : EQUD &00006FB5 EQUD &00006DB6 : EQUD &00006BCA : EQUD &000069EE : EQUD &00006822 EQUD &00006666 : EQUD &000064B8 : EQUD &00006318 : EQUD &00006186 EQUD &FFFFFFFF : EQUD &00190000 : EQUD &000C8000 : EQUD &00085555 \ n / 25 [n = 0 to 63] EQUD &00064000 : EQUD &00050000 : EQUD &00042AAA : EQUD &00039249 EQUD &00032000 : EQUD &0002C71C : EQUD &00028000 : EQUD &000245D1 EQUD &00021555 : EQUD &0001EC4E : EQUD &0001C924 : EQUD &0001AAAA EQUD &00019000 : EQUD &00017878 : EQUD &0001638E : EQUD &000150D7 EQUD &00014000 : EQUD &000130C3 : EQUD &000122E8 : EQUD &00011642 EQUD &00010AAA : EQUD &00010000 : EQUD &0000F627 : EQUD &0000ED09 EQUD &0000E492 : EQUD &0000DCB0 : EQUD &0000D555 : EQUD &0000CE73 EQUD &0000C800 : EQUD &0000C1F0 : EQUD &0000BC3C : EQUD &0000B6DB EQUD &0000B1C7 : EQUD &0000ACF9 : EQUD &0000A86B : EQUD &0000A41A EQUD &0000A000 : EQUD &00009C18 : EQUD &00009861 : EQUD &000094D6 EQUD &00009174 : EQUD &00008E38 : EQUD &00008B21 : EQUD &0000882B EQUD &00008555 : EQUD &0000829C : EQUD &00008000 : EQUD &00007D7D EQUD &00007B13 : EQUD &000078C1 : EQUD &00007684 : EQUD &0000745D EQUD &00007249 : EQUD &00007047 : EQUD &00006E58 : EQUD &00006C79 EQUD &00006AAA : EQUD &000068EB : EQUD &00006739 : EQUD &00006596 EQUD &FFFFFFFF : EQUD &001A0000 : EQUD &000D0000 : EQUD &0008AAAA \ n / 26 [n = 0 to 63] EQUD &00068000 : EQUD &00053333 : EQUD &00045555 : EQUD &0003B6DB EQUD &00034000 : EQUD &0002E38E : EQUD &00029999 : EQUD &00025D17 EQUD &00022AAA : EQUD &00020000 : EQUD &0001DB6D : EQUD &0001BBBB EQUD &0001A000 : EQUD &00018787 : EQUD &000171C7 : EQUD &00015E50 EQUD &00014CCC : EQUD &00013CF3 : EQUD &00012E8B : EQUD &00012164 EQUD &00011555 : EQUD &00010A3D : EQUD &00010000 : EQUD &0000F684 EQUD &0000EDB6 : EQUD &0000E584 : EQUD &0000DDDD : EQUD &0000D6B5 EQUD &0000D000 : EQUD &0000C9B2 : EQUD &0000C3C3 : EQUD &0000BE2B EQUD &0000B8E3 : EQUD &0000B3E4 : EQUD &0000AF28 : EQUD &0000AAAA EQUD &0000A666 : EQUD &0000A257 : EQUD &00009E79 : EQUD &00009ACA EQUD &00009745 : EQUD &000093E9 : EQUD &000090B2 : EQUD &00008D9D EQUD &00008AAA : EQUD &000087D6 : EQUD &0000851E : EQUD &00008282 EQUD &00008000 : EQUD &00007D95 : EQUD &00007B42 : EQUD &00007904 EQUD &000076DB : EQUD &000074C5 : EQUD &000072C2 : EQUD &000070D0 EQUD &00006EEE : EQUD &00006D1D : EQUD &00006B5A : EQUD &000069A6 EQUD &FFFFFFFF : EQUD &001B0000 : EQUD &000D8000 : EQUD &00090000 \ n / 27 [n = 0 to 63] EQUD &0006C000 : EQUD &00056666 : EQUD &00048000 : EQUD &0003DB6D EQUD &00036000 : EQUD &00030000 : EQUD &0002B333 : EQUD &0002745D EQUD &00024000 : EQUD &000213B1 : EQUD &0001EDB6 : EQUD &0001CCCC EQUD &0001B000 : EQUD &00019696 : EQUD &00018000 : EQUD &00016BCA EQUD &00015999 : EQUD &00014924 : EQUD &00013A2E : EQUD &00012C85 EQUD &00012000 : EQUD &0001147A : EQUD &000109D8 : EQUD &00010000 EQUD &0000F6DB : EQUD &0000EE58 : EQUD &0000E666 : EQUD &0000DEF7 EQUD &0000D800 : EQUD &0000D174 : EQUD &0000CB4B : EQUD &0000C57C EQUD &0000C000 : EQUD &0000BACF : EQUD &0000B5E5 : EQUD &0000B13B EQUD &0000ACCC : EQUD &0000A895 : EQUD &0000A492 : EQUD &0000A0BE EQUD &00009D17 : EQUD &00009999 : EQUD &00009642 : EQUD &00009310 EQUD &00009000 : EQUD &00008D0F : EQUD &00008A3D : EQUD &00008787 EQUD &000084EC : EQUD &0000826A : EQUD &00008000 : EQUD &00007DAC EQUD &00007B6D : EQUD &00007943 : EQUD &0000772C : EQUD &00007527 EQUD &00007333 : EQUD &0000714F : EQUD &00006F7B : EQUD &00006DB6 EQUD &FFFFFFFF : EQUD &001C0000 : EQUD &000E0000 : EQUD &00095555 \ n / 28 [n = 0 to 63] EQUD &00070000 : EQUD &00059999 : EQUD &0004AAAA : EQUD &00040000 EQUD &00038000 : EQUD &00031C71 : EQUD &0002CCCC : EQUD &00028BA2 EQUD &00025555 : EQUD &00022762 : EQUD &00020000 : EQUD &0001DDDD EQUD &0001C000 : EQUD &0001A5A5 : EQUD &00018E38 : EQUD &00017943 EQUD &00016666 : EQUD &00015555 : EQUD &000145D1 : EQUD &000137A6 EQUD &00012AAA : EQUD &00011EB8 : EQUD &000113B1 : EQUD &0001097B EQUD &00010000 : EQUD &0000F72C : EQUD &0000EEEE : EQUD &0000E739 EQUD &0000E000 : EQUD &0000D936 : EQUD &0000D2D2 : EQUD &0000CCCC EQUD &0000C71C : EQUD &0000C1BA : EQUD &0000BCA1 : EQUD &0000B7CB EQUD &0000B333 : EQUD &0000AED4 : EQUD &0000AAAA : EQUD &0000A6B2 EQUD &0000A2E8 : EQUD &00009F49 : EQUD &00009BD3 : EQUD &00009882 EQUD &00009555 : EQUD &00009249 : EQUD &00008F5C : EQUD &00008C8C EQUD &000089D8 : EQUD &0000873E : EQUD &000084BD : EQUD &00008253 EQUD &00008000 : EQUD &00007DC1 : EQUD &00007B96 : EQUD &0000797D EQUD &00007777 : EQUD &00007582 : EQUD &0000739C : EQUD &000071C7 EQUD &FFFFFFFF : EQUD &001D0000 : EQUD &000E8000 : EQUD &0009AAAA \ n / 29 [n = 0 to 63] EQUD &00074000 : EQUD &0005CCCC : EQUD &0004D555 : EQUD &00042492 EQUD &0003A000 : EQUD &000338E3 : EQUD &0002E666 : EQUD &0002A2E8 EQUD &00026AAA : EQUD &00023B13 : EQUD &00021249 : EQUD &0001EEEE EQUD &0001D000 : EQUD &0001B4B4 : EQUD &00019C71 : EQUD &000186BC EQUD &00017333 : EQUD &00016186 : EQUD &00015174 : EQUD &000142C8 EQUD &00013555 : EQUD &000128F5 : EQUD &00011D89 : EQUD &000112F6 EQUD &00010924 : EQUD &00010000 : EQUD &0000F777 : EQUD &0000EF7B EQUD &0000E800 : EQUD &0000E0F8 : EQUD &0000DA5A : EQUD &0000D41D EQUD &0000CE38 : EQUD &0000C8A6 : EQUD &0000C35E : EQUD &0000BE5B EQUD &0000B999 : EQUD &0000B512 : EQUD &0000B0C3 : EQUD &0000ACA6 EQUD &0000A8BA : EQUD &0000A4FA : EQUD &0000A164 : EQUD &00009DF5 EQUD &00009AAA : EQUD &00009782 : EQUD &0000947A : EQUD &00009191 EQUD &00008EC4 : EQUD &00008C13 : EQUD &0000897B : EQUD &000086FB EQUD &00008492 : EQUD &0000823E : EQUD &00008000 : EQUD &00007DD4 EQUD &00007BBB : EQUD &000079B4 : EQUD &000077BD : EQUD &000075D7 EQUD &FFFFFFFF : EQUD &001E0000 : EQUD &000F0000 : EQUD &000A0000 \ n / 30 [n = 0 to 63] EQUD &00078000 : EQUD &00060000 : EQUD &00050000 : EQUD &00044924 EQUD &0003C000 : EQUD &00035555 : EQUD &00030000 : EQUD &0002BA2E EQUD &00028000 : EQUD &00024EC4 : EQUD &00022492 : EQUD &00020000 EQUD &0001E000 : EQUD &0001C3C3 : EQUD &0001AAAA : EQUD &00019435 EQUD &00018000 : EQUD &00016DB6 : EQUD &00015D17 : EQUD &00014DE9 EQUD &00014000 : EQUD &00013333 : EQUD &00012762 : EQUD &00011C71 EQUD &00011249 : EQUD &000108D3 : EQUD &00010000 : EQUD &0000F7BD EQUD &0000F000 : EQUD &0000E8BA : EQUD &0000E1E1 : EQUD &0000DB6D EQUD &0000D555 : EQUD &0000CF91 : EQUD &0000CA1A : EQUD &0000C4EC EQUD &0000C000 : EQUD &0000BB51 : EQUD &0000B6DB : EQUD &0000B29A EQUD &0000AE8B : EQUD &0000AAAA : EQUD &0000A6F4 : EQUD &0000A367 EQUD &0000A000 : EQUD &00009CBC : EQUD &00009999 : EQUD &00009696 EQUD &000093B1 : EQUD &000090E7 : EQUD &00008E38 : EQUD &00008BA2 EQUD &00008924 : EQUD &000086BC : EQUD &00008469 : EQUD &0000822B EQUD &00008000 : EQUD &00007DE6 : EQUD &00007BDE : EQUD &000079E7 EQUD &FFFFFFFF : EQUD &001F0000 : EQUD &000F8000 : EQUD &000A5555 \ n / 31 [n = 0 to 63] EQUD &0007C000 : EQUD &00063333 : EQUD &00052AAA : EQUD &00046DB6 EQUD &0003E000 : EQUD &000371C7 : EQUD &00031999 : EQUD &0002D174 EQUD &00029555 : EQUD &00026276 : EQUD &000236DB : EQUD &00021111 EQUD &0001F000 : EQUD &0001D2D2 : EQUD &0001B8E3 : EQUD &0001A1AF EQUD &00018CCC : EQUD &000179E7 : EQUD &000168BA : EQUD &0001590B EQUD &00014AAA : EQUD &00013D70 : EQUD &0001313B : EQUD &000125ED EQUD &00011B6D : EQUD &000111A7 : EQUD &00010888 : EQUD &00010000 EQUD &0000F800 : EQUD &0000F07C : EQUD &0000E969 : EQUD &0000E2BE EQUD &0000DC71 : EQUD &0000D67C : EQUD &0000D0D7 : EQUD &0000CB7C EQUD &0000C666 : EQUD &0000C18F : EQUD &0000BCF3 : EQUD &0000B88E EQUD &0000B45D : EQUD &0000B05B : EQUD &0000AC85 : EQUD &0000A8D9 EQUD &0000A555 : EQUD &0000A1F5 : EQUD &00009EB8 : EQUD &00009B9B EQUD &0000989D : EQUD &000095BC : EQUD &000092F6 : EQUD &0000904A EQUD &00008DB6 : EQUD &00008B3A : EQUD &000088D3 : EQUD &00008682 EQUD &00008444 : EQUD &00008219 : EQUD &00008000 : EQUD &00007DF7 EQUD &FFFFFFFF : EQUD &00200000 : EQUD &00100000 : EQUD &000AAAAA \ n / 32 [n = 0 to 63] EQUD &00080000 : EQUD &00066666 : EQUD &00055555 : EQUD &00049249 EQUD &00040000 : EQUD &00038E38 : EQUD &00033333 : EQUD &0002E8BA EQUD &0002AAAA : EQUD &00027627 : EQUD &00024924 : EQUD &00022222 EQUD &00020000 : EQUD &0001E1E1 : EQUD &0001C71C : EQUD &0001AF28 EQUD &00019999 : EQUD &00018618 : EQUD &0001745D : EQUD &0001642C EQUD &00015555 : EQUD &000147AE : EQUD &00013B13 : EQUD &00012F68 EQUD &00012492 : EQUD &00011A7B : EQUD &00011111 : EQUD &00010842 EQUD &00010000 : EQUD &0000F83E : EQUD &0000F0F0 : EQUD &0000EA0E EQUD &0000E38E : EQUD &0000DD67 : EQUD &0000D794 : EQUD &0000D20D EQUD &0000CCCC : EQUD &0000C7CE : EQUD &0000C30C : EQUD &0000BE82 EQUD &0000BA2E : EQUD &0000B60B : EQUD &0000B216 : EQUD &0000AE4C EQUD &0000AAAA : EQUD &0000A72F : EQUD &0000A3D7 : EQUD &0000A0A0 EQUD &00009D89 : EQUD &00009A90 : EQUD &000097B4 : EQUD &000094F2 EQUD &00009249 : EQUD &00008FB8 : EQUD &00008D3D : EQUD &00008AD8 EQUD &00008888 : EQUD &0000864B : EQUD &00008421 : EQUD &00008208 EQUD &FFFFFFFF : EQUD &00210000 : EQUD &00108000 : EQUD &000B0000 \ n / 33 [n = 0 to 63] EQUD &00084000 : EQUD &00069999 : EQUD &00058000 : EQUD &0004B6DB EQUD &00042000 : EQUD &0003AAAA : EQUD &00034CCC : EQUD &00030000 EQUD &0002C000 : EQUD &000289D8 : EQUD &00025B6D : EQUD &00023333 EQUD &00021000 : EQUD &0001F0F0 : EQUD &0001D555 : EQUD &0001BCA1 EQUD &0001A666 : EQUD &00019249 : EQUD &00018000 : EQUD &00016F4D EQUD &00016000 : EQUD &000151EB : EQUD &000144EC : EQUD &000138E3 EQUD &00012DB6 : EQUD &0001234F : EQUD &00011999 : EQUD &00011084 EQUD &00010800 : EQUD &00010000 : EQUD &0000F878 : EQUD &0000F15F EQUD &0000EAAA : EQUD &0000E453 : EQUD &0000DE50 : EQUD &0000D89D EQUD &0000D333 : EQUD &0000CE0C : EQUD &0000C924 : EQUD &0000C477 EQUD &0000C000 : EQUD &0000BBBB : EQUD &0000B7A6 : EQUD &0000B3BE EQUD &0000B000 : EQUD &0000AC68 : EQUD &0000A8F5 : EQUD &0000A5A5 EQUD &0000A276 : EQUD &00009F65 : EQUD &00009C71 : EQUD &00009999 EQUD &000096DB : EQUD &00009435 : EQUD &000091A7 : EQUD &00008F2F EQUD &00008CCC : EQUD &00008A7D : EQUD &00008842 : EQUD &00008618 EQUD &FFFFFFFF : EQUD &00220000 : EQUD &00110000 : EQUD &000B5555 \ n / 34 [n = 0 to 63] EQUD &00088000 : EQUD &0006CCCC : EQUD &0005AAAA : EQUD &0004DB6D EQUD &00044000 : EQUD &0003C71C : EQUD &00036666 : EQUD &00031745 EQUD &0002D555 : EQUD &00029D89 : EQUD &00026DB6 : EQUD &00024444 EQUD &00022000 : EQUD &00020000 : EQUD &0001E38E : EQUD &0001CA1A EQUD &0001B333 : EQUD &00019E79 : EQUD &00018BA2 : EQUD &00017A6F EQUD &00016AAA : EQUD &00015C28 : EQUD &00014EC4 : EQUD &0001425E EQUD &000136DB : EQUD &00012C23 : EQUD &00012222 : EQUD &000118C6 EQUD &00011000 : EQUD &000107C1 : EQUD &00010000 : EQUD &0000F8AF EQUD &0000F1C7 : EQUD &0000EB3E : EQUD &0000E50D : EQUD &0000DF2D EQUD &0000D999 : EQUD &0000D44A : EQUD &0000CF3C : EQUD &0000CA6B EQUD &0000C5D1 : EQUD &0000C16C : EQUD &0000BD37 : EQUD &0000B931 EQUD &0000B555 : EQUD &0000B1A1 : EQUD &0000AE14 : EQUD &0000AAAA EQUD &0000A762 : EQUD &0000A439 : EQUD &0000A12F : EQUD &00009E41 EQUD &00009B6D : EQUD &000098B3 : EQUD &00009611 : EQUD &00009386 EQUD &00009111 : EQUD &00008EB0 : EQUD &00008C63 : EQUD &00008A28 EQUD &FFFFFFFF : EQUD &00230000 : EQUD &00118000 : EQUD &000BAAAA \ n / 35 [n = 0 to 63] EQUD &0008C000 : EQUD &00070000 : EQUD &0005D555 : EQUD &00050000 EQUD &00046000 : EQUD &0003E38E : EQUD &00038000 : EQUD &00032E8B EQUD &0002EAAA : EQUD &0002B13B : EQUD &00028000 : EQUD &00025555 EQUD &00023000 : EQUD &00020F0F : EQUD &0001F1C7 : EQUD &0001D794 EQUD &0001C000 : EQUD &0001AAAA : EQUD &00019745 : EQUD &00018590 EQUD &00017555 : EQUD &00016666 : EQUD &0001589D : EQUD &00014BDA EQUD &00014000 : EQUD &000134F7 : EQUD &00012AAA : EQUD &00012108 EQUD &00011800 : EQUD &00010F83 : EQUD &00010787 : EQUD &00010000 EQUD &0000F8E3 : EQUD &0000F229 : EQUD &0000EBCA : EQUD &0000E5BE EQUD &0000E000 : EQUD &0000DA89 : EQUD &0000D555 : EQUD &0000D05F EQUD &0000CBA2 : EQUD &0000C71C : EQUD &0000C2C8 : EQUD &0000BEA3 EQUD &0000BAAA : EQUD &0000B6DB : EQUD &0000B333 : EQUD &0000AFAF EQUD &0000AC4E : EQUD &0000A90E : EQUD &0000A5ED : EQUD &0000A2E8 EQUD &0000A000 : EQUD &00009D31 : EQUD &00009A7B : EQUD &000097DD EQUD &00009555 : EQUD &000092E2 : EQUD &00009084 : EQUD &00008E38 EQUD &FFFFFFFF : EQUD &00240000 : EQUD &00120000 : EQUD &000C0000 \ n / 36 [n = 0 to 63] EQUD &00090000 : EQUD &00073333 : EQUD &00060000 : EQUD &00052492 EQUD &00048000 : EQUD &00040000 : EQUD &00039999 : EQUD &000345D1 EQUD &00030000 : EQUD &0002C4EC : EQUD &00029249 : EQUD &00026666 EQUD &00024000 : EQUD &00021E1E : EQUD &00020000 : EQUD &0001E50D EQUD &0001CCCC : EQUD &0001B6DB : EQUD &0001A2E8 : EQUD &000190B2 EQUD &00018000 : EQUD &000170A3 : EQUD &00016276 : EQUD &00015555 EQUD &00014924 : EQUD &00013DCB : EQUD &00013333 : EQUD &0001294A EQUD &00012000 : EQUD &00011745 : EQUD &00010F0F : EQUD &00010750 EQUD &00010000 : EQUD &0000F914 : EQUD &0000F286 : EQUD &0000EC4E EQUD &0000E666 : EQUD &0000E0C7 : EQUD &0000DB6D : EQUD &0000D653 EQUD &0000D174 : EQUD &0000CCCC : EQUD &0000C859 : EQUD &0000C415 EQUD &0000C000 : EQUD &0000BC14 : EQUD &0000B851 : EQUD &0000B4B4 EQUD &0000B13B : EQUD &0000ADE3 : EQUD &0000AAAA : EQUD &0000A790 EQUD &0000A492 : EQUD &0000A1AF : EQUD &00009EE5 : EQUD &00009C34 EQUD &00009999 : EQUD &00009714 : EQUD &000094A5 : EQUD &00009249 EQUD &FFFFFFFF : EQUD &00250000 : EQUD &00128000 : EQUD &000C5555 \ n / 37 [n = 0 to 63] EQUD &00094000 : EQUD &00076666 : EQUD &00062AAA : EQUD &00054924 EQUD &0004A000 : EQUD &00041C71 : EQUD &0003B333 : EQUD &00035D17 EQUD &00031555 : EQUD &0002D89D : EQUD &0002A492 : EQUD &00027777 EQUD &00025000 : EQUD &00022D2D : EQUD &00020E38 : EQUD &0001F286 EQUD &0001D999 : EQUD &0001C30C : EQUD &0001AE8B : EQUD &00019BD3 EQUD &00018AAA : EQUD &00017AE1 : EQUD &00016C4E : EQUD &00015ED0 EQUD &00015249 : EQUD &0001469E : EQUD &00013BBB : EQUD &0001318C EQUD &00012800 : EQUD &00011F07 : EQUD &00011696 : EQUD &00010EA0 EQUD &0001071C : EQUD &00010000 : EQUD &0000F943 : EQUD &0000F2DF EQUD &0000ECCC : EQUD &0000E706 : EQUD &0000E186 : EQUD &0000DC47 EQUD &0000D745 : EQUD &0000D27D : EQUD &0000CDE9 : EQUD &0000C988 EQUD &0000C555 : EQUD &0000C14E : EQUD &0000BD70 : EQUD &0000B9B9 EQUD &0000B627 : EQUD &0000B2B7 : EQUD &0000AF68 : EQUD &0000AC37 EQUD &0000A924 : EQUD &0000A62C : EQUD &0000A34F : EQUD &0000A08A EQUD &00009DDD : EQUD &00009B47 : EQUD &000098C6 : EQUD &00009659 EQUD &FFFFFFFF : EQUD &00260000 : EQUD &00130000 : EQUD &000CAAAA \ n / 38 [n = 0 to 63] EQUD &00098000 : EQUD &00079999 : EQUD &00065555 : EQUD &00056DB6 EQUD &0004C000 : EQUD &000438E3 : EQUD &0003CCCC : EQUD &0003745D EQUD &00032AAA : EQUD &0002EC4E : EQUD &0002B6DB : EQUD &00028888 EQUD &00026000 : EQUD &00023C3C : EQUD &00021C71 : EQUD &00020000 EQUD &0001E666 : EQUD &0001CF3C : EQUD &0001BA2E : EQUD &0001A6F4 EQUD &00019555 : EQUD &0001851E : EQUD &00017627 : EQUD &0001684B EQUD &00015B6D : EQUD &00014F72 : EQUD &00014444 : EQUD &000139CE EQUD &00013000 : EQUD &000126C9 : EQUD &00011E1E : EQUD &000115F1 EQUD &00010E38 : EQUD &000106EB : EQUD &00010000 : EQUD &0000F96F EQUD &0000F333 : EQUD &0000ED44 : EQUD &0000E79E : EQUD &0000E23B EQUD &0000DD17 : EQUD &0000D82D : EQUD &0000D37A : EQUD &0000CEFA EQUD &0000CAAA : EQUD &0000C687 : EQUD &0000C28F : EQUD &0000BEBE EQUD &0000BB13 : EQUD &0000B78C : EQUD &0000B425 : EQUD &0000B0DF EQUD &0000ADB6 : EQUD &0000AAAA : EQUD &0000A7B9 : EQUD &0000A4E1 EQUD &0000A222 : EQUD &00009F79 : EQUD &00009CE7 : EQUD &00009A69 EQUD &FFFFFFFF : EQUD &00270000 : EQUD &00138000 : EQUD &000D0000 \ n / 39 [n = 0 to 63] EQUD &0009C000 : EQUD &0007CCCC : EQUD &00068000 : EQUD &00059249 EQUD &0004E000 : EQUD &00045555 : EQUD &0003E666 : EQUD &00038BA2 EQUD &00034000 : EQUD &00030000 : EQUD &0002C924 : EQUD &00029999 EQUD &00027000 : EQUD &00024B4B : EQUD &00022AAA : EQUD &00020D79 EQUD &0001F333 : EQUD &0001DB6D : EQUD &0001C5D1 : EQUD &0001B216 EQUD &0001A000 : EQUD &00018F5C : EQUD &00018000 : EQUD &000171C7 EQUD &00016492 : EQUD &00015846 : EQUD &00014CCC : EQUD &00014210 EQUD &00013800 : EQUD &00012E8B : EQUD &000125A5 : EQUD &00011D41 EQUD &00011555 : EQUD &00010DD6 : EQUD &000106BC : EQUD &00010000 EQUD &0000F999 : EQUD &0000F383 : EQUD &0000EDB6 : EQUD &0000E82F EQUD &0000E2E8 : EQUD &0000DDDD : EQUD &0000D90B : EQUD &0000D46C EQUD &0000D000 : EQUD &0000CBC1 : EQUD &0000C7AE : EQUD &0000C3C3 EQUD &0000C000 : EQUD &0000BC60 : EQUD &0000B8E3 : EQUD &0000B586 EQUD &0000B249 : EQUD &0000AF28 : EQUD &0000AC23 : EQUD &0000A938 EQUD &0000A666 : EQUD &0000A3AC : EQUD &0000A108 : EQUD &00009E79 EQUD &FFFFFFFF : EQUD &00280000 : EQUD &00140000 : EQUD &000D5555 \ n / 40 [n = 0 to 63] EQUD &000A0000 : EQUD &00080000 : EQUD &0006AAAA : EQUD &0005B6DB EQUD &00050000 : EQUD &000471C7 : EQUD &00040000 : EQUD &0003A2E8 EQUD &00035555 : EQUD &000313B1 : EQUD &0002DB6D : EQUD &0002AAAA EQUD &00028000 : EQUD &00025A5A : EQUD &000238E3 : EQUD &00021AF2 EQUD &00020000 : EQUD &0001E79E : EQUD &0001D174 : EQUD &0001BD37 EQUD &0001AAAA : EQUD &00019999 : EQUD &000189D8 : EQUD &00017B42 EQUD &00016DB6 : EQUD &0001611A : EQUD &00015555 : EQUD &00014A52 EQUD &00014000 : EQUD &0001364D : EQUD &00012D2D : EQUD &00012492 EQUD &00011C71 : EQUD &000114C1 : EQUD &00010D79 : EQUD &00010690 EQUD &00010000 : EQUD &0000F9C1 : EQUD &0000F3CF : EQUD &0000EE23 EQUD &0000E8BA : EQUD &0000E38E : EQUD &0000DE9B : EQUD &0000D9DF EQUD &0000D555 : EQUD &0000D0FA : EQUD &0000CCCC : EQUD &0000C8C8 EQUD &0000C4EC : EQUD &0000C135 : EQUD &0000BDA1 : EQUD &0000BA2E EQUD &0000B6DB : EQUD &0000B3A6 : EQUD &0000B08D : EQUD &0000AD8F EQUD &0000AAAA : EQUD &0000A7DE : EQUD &0000A529 : EQUD &0000A28A EQUD &FFFFFFFF : EQUD &00290000 : EQUD &00148000 : EQUD &000DAAAA \ n / 41 [n = 0 to 63] EQUD &000A4000 : EQUD &00083333 : EQUD &0006D555 : EQUD &0005DB6D EQUD &00052000 : EQUD &00048E38 : EQUD &00041999 : EQUD &0003BA2E EQUD &00036AAA : EQUD &00032762 : EQUD &0002EDB6 : EQUD &0002BBBB EQUD &00029000 : EQUD &00026969 : EQUD &0002471C : EQUD &0002286B EQUD &00020CCC : EQUD &0001F3CF : EQUD &0001DD17 : EQUD &0001C859 EQUD &0001B555 : EQUD &0001A3D7 : EQUD &000193B1 : EQUD &000184BD EQUD &000176DB : EQUD &000169EE : EQUD &00015DDD : EQUD &00015294 EQUD &00014800 : EQUD &00013E0F : EQUD &000134B4 : EQUD &00012BE2 EQUD &0001238E : EQUD &00011BAC : EQUD &00011435 : EQUD &00010D20 EQUD &00010666 : EQUD &00010000 : EQUD &0000F9E7 : EQUD &0000F417 EQUD &0000EE8B : EQUD &0000E93E : EQUD &0000E42C : EQUD &0000DF51 EQUD &0000DAAA : EQUD &0000D634 : EQUD &0000D1EB : EQUD &0000CDCD EQUD &0000C9D8 : EQUD &0000C609 : EQUD &0000C25E : EQUD &0000BED6 EQUD &0000BB6D : EQUD &0000B823 : EQUD &0000B4F7 : EQUD &0000B1E5 EQUD &0000AEEE : EQUD &0000AC10 : EQUD &0000A94A : EQUD &0000A69A EQUD &FFFFFFFF : EQUD &002A0000 : EQUD &00150000 : EQUD &000E0000 \ n / 42 [n = 0 to 63] EQUD &000A8000 : EQUD &00086666 : EQUD &00070000 : EQUD &00060000 EQUD &00054000 : EQUD &0004AAAA : EQUD &00043333 : EQUD &0003D174 EQUD &00038000 : EQUD &00033B13 : EQUD &00030000 : EQUD &0002CCCC EQUD &0002A000 : EQUD &00027878 : EQUD &00025555 : EQUD &000235E5 EQUD &00021999 : EQUD &00020000 : EQUD &0001E8BA : EQUD &0001D37A EQUD &0001C000 : EQUD &0001AE14 : EQUD &00019D89 : EQUD &00018E38 EQUD &00018000 : EQUD &000172C2 : EQUD &00016666 : EQUD &00015AD6 EQUD &00015000 : EQUD &000145D1 : EQUD &00013C3C : EQUD &00013333 EQUD &00012AAA : EQUD &00012298 : EQUD &00011AF2 : EQUD &000113B1 EQUD &00010CCC : EQUD &0001063E : EQUD &00010000 : EQUD &0000FA0B EQUD &0000F45D : EQUD &0000EEEE : EQUD &0000E9BD : EQUD &0000E4C4 EQUD &0000E000 : EQUD &0000DB6D : EQUD &0000D70A : EQUD &0000D2D2 EQUD &0000CEC4 : EQUD &0000CADE : EQUD &0000C71C : EQUD &0000C37D EQUD &0000C000 : EQUD &0000BCA1 : EQUD &0000B961 : EQUD &0000B63C EQUD &0000B333 : EQUD &0000B043 : EQUD &0000AD6B : EQUD &0000AAAA EQUD &FFFFFFFF : EQUD &002B0000 : EQUD &00158000 : EQUD &000E5555 \ n / 43 [n = 0 to 63] EQUD &000AC000 : EQUD &00089999 : EQUD &00072AAA : EQUD &00062492 EQUD &00056000 : EQUD &0004C71C : EQUD &00044CCC : EQUD &0003E8BA EQUD &00039555 : EQUD &00034EC4 : EQUD &00031249 : EQUD &0002DDDD EQUD &0002B000 : EQUD &00028787 : EQUD &0002638E : EQUD &0002435E EQUD &00022666 : EQUD &00020C30 : EQUD &0001F45D : EQUD &0001DE9B EQUD &0001CAAA : EQUD &0001B851 : EQUD &0001A762 : EQUD &000197B4 EQUD &00018924 : EQUD &00017B96 : EQUD &00016EEE : EQUD &00016318 EQUD &00015800 : EQUD &00014D93 : EQUD &000143C3 : EQUD &00013A83 EQUD &000131C7 : EQUD &00012983 : EQUD &000121AF : EQUD &00011A41 EQUD &00011333 : EQUD &00010C7C : EQUD &00010618 : EQUD &00010000 EQUD &0000FA2E : EQUD &0000F49F : EQUD &0000EF4D : EQUD &0000EA36 EQUD &0000E555 : EQUD &0000E0A7 : EQUD &0000DC28 : EQUD &0000D7D7 EQUD &0000D3B1 : EQUD &0000CFB2 : EQUD &0000CBDA : EQUD &0000C825 EQUD &0000C492 : EQUD &0000C11F : EQUD &0000BDCB : EQUD &0000BA93 EQUD &0000B777 : EQUD &0000B475 : EQUD &0000B18C : EQUD &0000AEBA EQUD &FFFFFFFF : EQUD &002C0000 : EQUD &00160000 : EQUD &000EAAAA \ n / 44 [n = 0 to 63] EQUD &000B0000 : EQUD &0008CCCC : EQUD &00075555 : EQUD &00064924 EQUD &00058000 : EQUD &0004E38E : EQUD &00046666 : EQUD &00040000 EQUD &0003AAAA : EQUD &00036276 : EQUD &00032492 : EQUD &0002EEEE EQUD &0002C000 : EQUD &00029696 : EQUD &000271C7 : EQUD &000250D7 EQUD &00023333 : EQUD &00021861 : EQUD &00020000 : EQUD &0001E9BD EQUD &0001D555 : EQUD &0001C28F : EQUD &0001B13B : EQUD &0001A12F EQUD &00019249 : EQUD &00018469 : EQUD &00017777 : EQUD &00016B5A EQUD &00016000 : EQUD &00015555 : EQUD &00014B4B : EQUD &000141D4 EQUD &000138E3 : EQUD &0001306E : EQUD &0001286B : EQUD &000120D2 EQUD &00011999 : EQUD &000112BB : EQUD &00010C30 : EQUD &000105F4 EQUD &00010000 : EQUD &0000FA4F : EQUD &0000F4DE : EQUD &0000EFA8 EQUD &0000EAAA : EQUD &0000E5E0 : EQUD &0000E147 : EQUD &0000DCDC EQUD &0000D89D : EQUD &0000D487 : EQUD &0000D097 : EQUD &0000CCCC EQUD &0000C924 : EQUD &0000C59D : EQUD &0000C234 : EQUD &0000BEEA EQUD &0000BBBB : EQUD &0000B8A7 : EQUD &0000B5AD : EQUD &0000B2CB EQUD &FFFFFFFF : EQUD &002D0000 : EQUD &00168000 : EQUD &000F0000 \ n / 45 [n = 0 to 63] EQUD &000B4000 : EQUD &00090000 : EQUD &00078000 : EQUD &00066DB6 EQUD &0005A000 : EQUD &00050000 : EQUD &00048000 : EQUD &00041745 EQUD &0003C000 : EQUD &00037627 : EQUD &000336DB : EQUD &00030000 EQUD &0002D000 : EQUD &0002A5A5 : EQUD &00028000 : EQUD &00025E50 EQUD &00024000 : EQUD &00022492 : EQUD &00020BA2 : EQUD &0001F4DE EQUD &0001E000 : EQUD &0001CCCC : EQUD &0001BB13 : EQUD &0001AAAA EQUD &00019B6D : EQUD &00018D3D : EQUD &00018000 : EQUD &0001739C EQUD &00016800 : EQUD &00015D17 : EQUD &000152D2 : EQUD &00014924 EQUD &00014000 : EQUD &00013759 : EQUD &00012F28 : EQUD &00012762 EQUD &00012000 : EQUD &000118F9 : EQUD &00011249 : EQUD &00010BE8 EQUD &000105D1 : EQUD &00010000 : EQUD &0000FA6F : EQUD &0000F51B EQUD &0000F000 : EQUD &0000EB1A : EQUD &0000E666 : EQUD &0000E1E1 EQUD &0000DD89 : EQUD &0000D95B : EQUD &0000D555 : EQUD &0000D174 EQUD &0000CDB6 : EQUD &0000CA1A : EQUD &0000C69E : EQUD &0000C341 EQUD &0000C000 : EQUD &0000BCDA : EQUD &0000B9CE : EQUD &0000B6DB EQUD &FFFFFFFF : EQUD &002E0000 : EQUD &00170000 : EQUD &000F5555 \ n / 46 [n = 0 to 63] EQUD &000B8000 : EQUD &00093333 : EQUD &0007AAAA : EQUD &00069249 EQUD &0005C000 : EQUD &00051C71 : EQUD &00049999 : EQUD &00042E8B EQUD &0003D555 : EQUD &000389D8 : EQUD &00034924 : EQUD &00031111 EQUD &0002E000 : EQUD &0002B4B4 : EQUD &00028E38 : EQUD &00026BCA EQUD &00024CCC : EQUD &000230C3 : EQUD &00021745 : EQUD &00020000 EQUD &0001EAAA : EQUD &0001D70A : EQUD &0001C4EC : EQUD &0001B425 EQUD &0001A492 : EQUD &00019611 : EQUD &00018888 : EQUD &00017BDE EQUD &00017000 : EQUD &000164D9 : EQUD &00015A5A : EQUD &00015075 EQUD &0001471C : EQUD &00013E45 : EQUD &000135E5 : EQUD &00012DF2 EQUD &00012666 : EQUD &00011F38 : EQUD &00011861 : EQUD &000111DC EQUD &00010BA2 : EQUD &000105B0 : EQUD &00010000 : EQUD &0000FA8D EQUD &0000F555 : EQUD &0000F053 : EQUD &0000EB85 : EQUD &0000E6E6 EQUD &0000E276 : EQUD &0000DE30 : EQUD &0000DA12 : EQUD &0000D61B EQUD &0000D249 : EQUD &0000CE98 : EQUD &0000CB08 : EQUD &0000C797 EQUD &0000C444 : EQUD &0000C10C : EQUD &0000BDEF : EQUD &0000BAEB EQUD &FFFFFFFF : EQUD &002F0000 : EQUD &00178000 : EQUD &000FAAAA \ n / 47 [n = 0 to 63] EQUD &000BC000 : EQUD &00096666 : EQUD &0007D555 : EQUD &0006B6DB EQUD &0005E000 : EQUD &000538E3 : EQUD &0004B333 : EQUD &000445D1 EQUD &0003EAAA : EQUD &00039D89 : EQUD &00035B6D : EQUD &00032222 EQUD &0002F000 : EQUD &0002C3C3 : EQUD &00029C71 : EQUD &00027943 EQUD &00025999 : EQUD &00023CF3 : EQUD &000222E8 : EQUD &00020B21 EQUD &0001F555 : EQUD &0001E147 : EQUD &0001CEC4 : EQUD &0001BDA1 EQUD &0001ADB6 : EQUD &00019EE5 : EQUD &00019111 : EQUD &00018421 EQUD &00017800 : EQUD &00016C9B : EQUD &000161E1 : EQUD &000157C5 EQUD &00014E38 : EQUD &00014530 : EQUD &00013CA1 : EQUD &00013483 EQUD &00012CCC : EQUD &00012576 : EQUD &00011E79 : EQUD &000117D0 EQUD &00011174 : EQUD &00010B60 : EQUD &00010590 : EQUD &00010000 EQUD &0000FAAA : EQUD &0000F58D : EQUD &0000F0A3 : EQUD &0000EBEB EQUD &0000E762 : EQUD &0000E304 : EQUD &0000DED0 : EQUD &0000DAC3 EQUD &0000D6DB : EQUD &0000D316 : EQUD &0000CF72 : EQUD &0000CBEE EQUD &0000C888 : EQUD &0000C53E : EQUD &0000C210 : EQUD &0000BEFB EQUD &FFFFFFFF : EQUD &00300000 : EQUD &00180000 : EQUD &00100000 \ n / 48 [n = 0 to 63] EQUD &000C0000 : EQUD &00099999 : EQUD &00080000 : EQUD &0006DB6D EQUD &00060000 : EQUD &00055555 : EQUD &0004CCCC : EQUD &00045D17 EQUD &00040000 : EQUD &0003B13B : EQUD &00036DB6 : EQUD &00033333 EQUD &00030000 : EQUD &0002D2D2 : EQUD &0002AAAA : EQUD &000286BC EQUD &00026666 : EQUD &00024924 : EQUD &00022E8B : EQUD &00021642 EQUD &00020000 : EQUD &0001EB85 : EQUD &0001D89D : EQUD &0001C71C EQUD &0001B6DB : EQUD &0001A7B9 : EQUD &00019999 : EQUD &00018C63 EQUD &00018000 : EQUD &0001745D : EQUD &00016969 : EQUD &00015F15 EQUD &00015555 : EQUD &00014C1B : EQUD &0001435E : EQUD &00013B13 EQUD &00013333 : EQUD &00012BB5 : EQUD &00012492 : EQUD &00011DC4 EQUD &00011745 : EQUD &00011111 : EQUD &00010B21 : EQUD &00010572 EQUD &00010000 : EQUD &0000FAC6 : EQUD &0000F5C2 : EQUD &0000F0F0 EQUD &0000EC4E : EQUD &0000E7D9 : EQUD &0000E38E : EQUD &0000DF6B EQUD &0000DB6D : EQUD &0000D794 : EQUD &0000D3DC : EQUD &0000D045 EQUD &0000CCCC : EQUD &0000C971 : EQUD &0000C631 : EQUD &0000C30C EQUD &FFFFFFFF : EQUD &00310000 : EQUD &00188000 : EQUD &00105555 \ n / 49 [n = 0 to 63] EQUD &000C4000 : EQUD &0009CCCC : EQUD &00082AAA : EQUD &00070000 EQUD &00062000 : EQUD &000571C7 : EQUD &0004E666 : EQUD &0004745D EQUD &00041555 : EQUD &0003C4EC : EQUD &00038000 : EQUD &00034444 EQUD &00031000 : EQUD &0002E1E1 : EQUD &0002B8E3 : EQUD &00029435 EQUD &00027333 : EQUD &00025555 : EQUD &00023A2E : EQUD &00022164 EQUD &00020AAA : EQUD &0001F5C2 : EQUD &0001E276 : EQUD &0001D097 EQUD &0001C000 : EQUD &0001B08D : EQUD &0001A222 : EQUD &000194A5 EQUD &00018800 : EQUD &00017C1F : EQUD &000170F0 : EQUD &00016666 EQUD &00015C71 : EQUD &00015306 : EQUD &00014A1A : EQUD &000141A4 EQUD &00013999 : EQUD &000131F3 : EQUD &00012AAA : EQUD &000123B8 EQUD &00011D17 : EQUD &000116C1 : EQUD &000110B2 : EQUD &00010AE4 EQUD &00010555 : EQUD &00010000 : EQUD &0000FAE1 : EQUD &0000F5F5 EQUD &0000F13B : EQUD &0000ECAD : EQUD &0000E84B : EQUD &0000E412 EQUD &0000E000 : EQUD &0000DC11 : EQUD &0000D846 : EQUD &0000D49C EQUD &0000D111 : EQUD &0000CDA3 : EQUD &0000CA52 : EQUD &0000C71C EQUD &FFFFFFFF : EQUD &00320000 : EQUD &00190000 : EQUD &0010AAAA \ n / 50 [n = 0 to 63] EQUD &000C8000 : EQUD &000A0000 : EQUD &00085555 : EQUD &00072492 EQUD &00064000 : EQUD &00058E38 : EQUD &00050000 : EQUD &00048BA2 EQUD &00042AAA : EQUD &0003D89D : EQUD &00039249 : EQUD &00035555 EQUD &00032000 : EQUD &0002F0F0 : EQUD &0002C71C : EQUD &0002A1AF EQUD &00028000 : EQUD &00026186 : EQUD &000245D1 : EQUD &00022C85 EQUD &00021555 : EQUD &00020000 : EQUD &0001EC4E : EQUD &0001DA12 EQUD &0001C924 : EQUD &0001B961 : EQUD &0001AAAA : EQUD &00019CE7 EQUD &00019000 : EQUD &000183E0 : EQUD &00017878 : EQUD &00016DB6 EQUD &0001638E : EQUD &000159F2 : EQUD &000150D7 : EQUD &00014834 EQUD &00014000 : EQUD &00013831 : EQUD &000130C3 : EQUD &000129AC EQUD &000122E8 : EQUD &00011C71 : EQUD &00011642 : EQUD &00011057 EQUD &00010AAA : EQUD &00010539 : EQUD &00010000 : EQUD &0000FAFA EQUD &0000F627 : EQUD &0000F182 : EQUD &0000ED09 : EQUD &0000E8BA EQUD &0000E492 : EQUD &0000E08F : EQUD &0000DCB0 : EQUD &0000D8F2 EQUD &0000D555 : EQUD &0000D1D6 : EQUD &0000CE73 : EQUD &0000CB2C EQUD &FFFFFFFF : EQUD &00330000 : EQUD &00198000 : EQUD &00110000 \ n / 51 [n = 0 to 63] EQUD &000CC000 : EQUD &000A3333 : EQUD &00088000 : EQUD &00074924 EQUD &00066000 : EQUD &0005AAAA : EQUD &00051999 : EQUD &0004A2E8 EQUD &00044000 : EQUD &0003EC4E : EQUD &0003A492 : EQUD &00036666 EQUD &00033000 : EQUD &00030000 : EQUD &0002D555 : EQUD &0002AF28 EQUD &00028CCC : EQUD &00026DB6 : EQUD &00025174 : EQUD &000237A6 EQUD &00022000 : EQUD &00020A3D : EQUD &0001F627 : EQUD &0001E38E EQUD &0001D249 : EQUD &0001C234 : EQUD &0001B333 : EQUD &0001A529 EQUD &00019800 : EQUD &00018BA2 : EQUD &00018000 : EQUD &00017507 EQUD &00016AAA : EQUD &000160DD : EQUD &00015794 : EQUD &00014EC4 EQUD &00014666 : EQUD &00013E70 : EQUD &000136DB : EQUD &00012FA0 EQUD &000128BA : EQUD &00012222 : EQUD &00011BD3 : EQUD &000115C9 EQUD &00011000 : EQUD &00010A72 : EQUD &0001051E : EQUD &00010000 EQUD &0000FB13 : EQUD &0000F656 : EQUD &0000F1C7 : EQUD &0000ED61 EQUD &0000E924 : EQUD &0000E50D : EQUD &0000E11A : EQUD &0000DD49 EQUD &0000D999 : EQUD &0000D608 : EQUD &0000D294 : EQUD &0000CF3C EQUD &FFFFFFFF : EQUD &00340000 : EQUD &001A0000 : EQUD &00115555 \ n / 52 [n = 0 to 63] EQUD &000D0000 : EQUD &000A6666 : EQUD &0008AAAA : EQUD &00076DB6 EQUD &00068000 : EQUD &0005C71C : EQUD &00053333 : EQUD &0004BA2E EQUD &00045555 : EQUD &00040000 : EQUD &0003B6DB : EQUD &00037777 EQUD &00034000 : EQUD &00030F0F : EQUD &0002E38E : EQUD &0002BCA1 EQUD &00029999 : EQUD &000279E7 : EQUD &00025D17 : EQUD &000242C8 EQUD &00022AAA : EQUD &0002147A : EQUD &00020000 : EQUD &0001ED09 EQUD &0001DB6D : EQUD &0001CB08 : EQUD &0001BBBB : EQUD &0001AD6B EQUD &0001A000 : EQUD &00019364 : EQUD &00018787 : EQUD &00017C57 EQUD &000171C7 : EQUD &000167C8 : EQUD &00015E50 : EQUD &00015555 EQUD &00014CCC : EQUD &000144AE : EQUD &00013CF3 : EQUD &00013594 EQUD &00012E8B : EQUD &000127D2 : EQUD &00012164 : EQUD &00011B3B EQUD &00011555 : EQUD &00010FAC : EQUD &00010A3D : EQUD &00010505 EQUD &00010000 : EQUD &0000FB2B : EQUD &0000F684 : EQUD &0000F209 EQUD &0000EDB6 : EQUD &0000E98B : EQUD &0000E584 : EQUD &0000E1A0 EQUD &0000DDDD : EQUD &0000DA3A : EQUD &0000D6B5 : EQUD &0000D34D EQUD &FFFFFFFF : EQUD &00350000 : EQUD &001A8000 : EQUD &0011AAAA \ n / 53 [n = 0 to 63] EQUD &000D4000 : EQUD &000A9999 : EQUD &0008D555 : EQUD &00079249 EQUD &0006A000 : EQUD &0005E38E : EQUD &00054CCC : EQUD &0004D174 EQUD &00046AAA : EQUD &000413B1 : EQUD &0003C924 : EQUD &00038888 EQUD &00035000 : EQUD &00031E1E : EQUD &0002F1C7 : EQUD &0002CA1A EQUD &0002A666 : EQUD &00028618 : EQUD &000268BA : EQUD &00024DE9 EQUD &00023555 : EQUD &00021EB8 : EQUD &000209D8 : EQUD &0001F684 EQUD &0001E492 : EQUD &0001D3DC : EQUD &0001C444 : EQUD &0001B5AD EQUD &0001A800 : EQUD &00019B26 : EQUD &00018F0F : EQUD &000183A8 EQUD &000178E3 : EQUD &00016EB3 : EQUD &0001650D : EQUD &00015BE5 EQUD &00015333 : EQUD &00014AED : EQUD &0001430C : EQUD &00013B88 EQUD &0001345D : EQUD &00012D82 : EQUD &000126F4 : EQUD &000120AE EQUD &00011AAA : EQUD &000114E5 : EQUD &00010F5C : EQUD &00010A0A EQUD &000104EC : EQUD &00010000 : EQUD &0000FB42 : EQUD &0000F6B0 EQUD &0000F249 : EQUD &0000EE08 : EQUD &0000E9EE : EQUD &0000E5F7 EQUD &0000E222 : EQUD &0000DE6D : EQUD &0000DAD6 : EQUD &0000D75D EQUD &FFFFFFFF : EQUD &00360000 : EQUD &001B0000 : EQUD &00120000 \ n / 54 [n = 0 to 63] EQUD &000D8000 : EQUD &000ACCCC : EQUD &00090000 : EQUD &0007B6DB EQUD &0006C000 : EQUD &00060000 : EQUD &00056666 : EQUD &0004E8BA EQUD &00048000 : EQUD &00042762 : EQUD &0003DB6D : EQUD &00039999 EQUD &00036000 : EQUD &00032D2D : EQUD &00030000 : EQUD &0002D794 EQUD &0002B333 : EQUD &00029249 : EQUD &0002745D : EQUD &0002590B EQUD &00024000 : EQUD &000228F5 : EQUD &000213B1 : EQUD &00020000 EQUD &0001EDB6 : EQUD &0001DCB0 : EQUD &0001CCCC : EQUD &0001BDEF EQUD &0001B000 : EQUD &0001A2E8 : EQUD &00019696 : EQUD &00018AF8 EQUD &00018000 : EQUD &0001759F : EQUD &00016BCA : EQUD &00016276 EQUD &00015999 : EQUD &0001512B : EQUD &00014924 : EQUD &0001417D EQUD &00013A2E : EQUD &00013333 : EQUD &00012C85 : EQUD &00012620 EQUD &00012000 : EQUD &00011A1F : EQUD &0001147A : EQUD &00010F0F EQUD &000109D8 : EQUD &000104D4 : EQUD &00010000 : EQUD &0000FB58 EQUD &0000F6DB : EQUD &0000F286 : EQUD &0000EE58 : EQUD &0000EA4E EQUD &0000E666 : EQUD &0000E29F : EQUD &0000DEF7 : EQUD &0000DB6D EQUD &FFFFFFFF : EQUD &00370000 : EQUD &001B8000 : EQUD &00125555 \ n / 55 [n = 0 to 63] EQUD &000DC000 : EQUD &000B0000 : EQUD &00092AAA : EQUD &0007DB6D EQUD &0006E000 : EQUD &00061C71 : EQUD &00058000 : EQUD &00050000 EQUD &00049555 : EQUD &00043B13 : EQUD &0003EDB6 : EQUD &0003AAAA EQUD &00037000 : EQUD &00033C3C : EQUD &00030E38 : EQUD &0002E50D EQUD &0002C000 : EQUD &00029E79 : EQUD &00028000 : EQUD &0002642C EQUD &00024AAA : EQUD &00023333 : EQUD &00021D89 : EQUD &0002097B EQUD &0001F6DB : EQUD &0001E584 : EQUD &0001D555 : EQUD &0001C631 EQUD &0001B800 : EQUD &0001AAAA : EQUD &00019E1E : EQUD &00019249 EQUD &0001871C : EQUD &00017C8A : EQUD &00017286 : EQUD &00016906 EQUD &00016000 : EQUD &0001576A : EQUD &00014F3C : EQUD &00014771 EQUD &00014000 : EQUD &000138E3 : EQUD &00013216 : EQUD &00012B93 EQUD &00012555 : EQUD &00011F58 : EQUD &00011999 : EQUD &00011414 EQUD &00010EC4 : EQUD &000109A9 : EQUD &000104BD : EQUD &00010000 EQUD &0000FB6D : EQUD &0000F704 : EQUD &0000F2C2 : EQUD &0000EEA4 EQUD &0000EAAA : EQUD &0000E6D1 : EQUD &0000E318 : EQUD &0000DF7D EQUD &FFFFFFFF : EQUD &00380000 : EQUD &001C0000 : EQUD &0012AAAA \ n / 56 [n = 0 to 63] EQUD &000E0000 : EQUD &000B3333 : EQUD &00095555 : EQUD &00080000 EQUD &00070000 : EQUD &000638E3 : EQUD &00059999 : EQUD &00051745 EQUD &0004AAAA : EQUD &00044EC4 : EQUD &00040000 : EQUD &0003BBBB EQUD &00038000 : EQUD &00034B4B : EQUD &00031C71 : EQUD &0002F286 EQUD &0002CCCC : EQUD &0002AAAA : EQUD &00028BA2 : EQUD &00026F4D EQUD &00025555 : EQUD &00023D70 : EQUD &00022762 : EQUD &000212F6 EQUD &00020000 : EQUD &0001EE58 : EQUD &0001DDDD : EQUD &0001CE73 EQUD &0001C000 : EQUD &0001B26C : EQUD &0001A5A5 : EQUD &00019999 EQUD &00018E38 : EQUD &00018375 : EQUD &00017943 : EQUD &00016F96 EQUD &00016666 : EQUD &00015DA8 : EQUD &00015555 : EQUD &00014D65 EQUD &000145D1 : EQUD &00013E93 : EQUD &000137A6 : EQUD &00013105 EQUD &00012AAA : EQUD &00012492 : EQUD &00011EB8 : EQUD &00011919 EQUD &000113B1 : EQUD &00010E7D : EQUD &0001097B : EQUD &000104A7 EQUD &00010000 : EQUD &0000FB82 : EQUD &0000F72C : EQUD &0000F2FB EQUD &0000EEEE : EQUD &0000EB04 : EQUD &0000E739 : EQUD &0000E38E EQUD &FFFFFFFF : EQUD &00390000 : EQUD &001C8000 : EQUD &00130000 \ n / 57 [n = 0 to 63] EQUD &000E4000 : EQUD &000B6666 : EQUD &00098000 : EQUD &00082492 EQUD &00072000 : EQUD &00065555 : EQUD &0005B333 : EQUD &00052E8B EQUD &0004C000 : EQUD &00046276 : EQUD &00041249 : EQUD &0003CCCC EQUD &00039000 : EQUD &00035A5A : EQUD &00032AAA : EQUD &00030000 EQUD &0002D999 : EQUD &0002B6DB : EQUD &00029745 : EQUD &00027A6F EQUD &00026000 : EQUD &000247AE : EQUD &0002313B : EQUD &00021C71 EQUD &00020924 : EQUD &0001F72C : EQUD &0001E666 : EQUD &0001D6B5 EQUD &0001C800 : EQUD &0001BA2E : EQUD &0001AD2D : EQUD &0001A0EA EQUD &00019555 : EQUD &00018A60 : EQUD &00018000 : EQUD &00017627 EQUD &00016CCC : EQUD &000163E7 : EQUD &00015B6D : EQUD &00015359 EQUD &00014BA2 : EQUD &00014444 : EQUD &00013D37 : EQUD &00013677 EQUD &00013000 : EQUD &000129CB : EQUD &000123D7 : EQUD &00011E1E EQUD &0001189D : EQUD &00011352 : EQUD &00010E38 : EQUD &0001094F EQUD &00010492 : EQUD &00010000 : EQUD &0000FB96 : EQUD &0000F752 EQUD &0000F333 : EQUD &0000EF36 : EQUD &0000EB5A : EQUD &0000E79E EQUD &FFFFFFFF : EQUD &003A0000 : EQUD &001D0000 : EQUD &00135555 \ n / 58 [n = 0 to 63] EQUD &000E8000 : EQUD &000B9999 : EQUD &0009AAAA : EQUD &00084924 EQUD &00074000 : EQUD &000671C7 : EQUD &0005CCCC : EQUD &000545D1 EQUD &0004D555 : EQUD &00047627 : EQUD &00042492 : EQUD &0003DDDD EQUD &0003A000 : EQUD &00036969 : EQUD &000338E3 : EQUD &00030D79 EQUD &0002E666 : EQUD &0002C30C : EQUD &0002A2E8 : EQUD &00028590 EQUD &00026AAA : EQUD &000251EB : EQUD &00023B13 : EQUD &000225ED EQUD &00021249 : EQUD &00020000 : EQUD &0001EEEE : EQUD &0001DEF7 EQUD &0001D000 : EQUD &0001C1F0 : EQUD &0001B4B4 : EQUD &0001A83A EQUD &00019C71 : EQUD &0001914C : EQUD &000186BC : EQUD &00017CB7 EQUD &00017333 : EQUD &00016A25 : EQUD &00016186 : EQUD &0001594D EQUD &00015174 : EQUD &000149F4 : EQUD &000142C8 : EQUD &00013BEA EQUD &00013555 : EQUD &00012F05 : EQUD &000128F5 : EQUD &00012323 EQUD &00011D89 : EQUD &00011826 : EQUD &000112F6 : EQUD &00010DF6 EQUD &00010924 : EQUD &0001047D : EQUD &00010000 : EQUD &0000FBA9 EQUD &0000F777 : EQUD &0000F368 : EQUD &0000EF7B : EQUD &0000EBAE EQUD &FFFFFFFF : EQUD &003B0000 : EQUD &001D8000 : EQUD &0013AAAA \ n / 59 [n = 0 to 63] EQUD &000EC000 : EQUD &000BCCCC : EQUD &0009D555 : EQUD &00086DB6 EQUD &00076000 : EQUD &00068E38 : EQUD &0005E666 : EQUD &00055D17 EQUD &0004EAAA : EQUD &000489D8 : EQUD &000436DB : EQUD &0003EEEE EQUD &0003B000 : EQUD &00037878 : EQUD &0003471C : EQUD &00031AF2 EQUD &0002F333 : EQUD &0002CF3C : EQUD &0002AE8B : EQUD &000290B2 EQUD &00027555 : EQUD &00025C28 : EQUD &000244EC : EQUD &00022F68 EQUD &00021B6D : EQUD &000208D3 : EQUD &0001F777 : EQUD &0001E739 EQUD &0001D800 : EQUD &0001C9B2 : EQUD &0001BC3C : EQUD &0001AF8A EQUD &0001A38E : EQUD &00019837 : EQUD &00018D79 : EQUD &00018348 EQUD &00017999 : EQUD &00017063 : EQUD &0001679E : EQUD &00015F41 EQUD &00015745 : EQUD &00014FA4 : EQUD &00014859 : EQUD &0001415C EQUD &00013AAA : EQUD &0001343E : EQUD &00012E14 : EQUD &00012828 EQUD &00012276 : EQUD &00011CFB : EQUD &000117B4 : EQUD &0001129E EQUD &00010DB6 : EQUD &000108FB : EQUD &00010469 : EQUD &00010000 EQUD &0000FBBB : EQUD &0000F79B : EQUD &0000F39C : EQUD &0000EFBE EQUD &FFFFFFFF : EQUD &003C0000 : EQUD &001E0000 : EQUD &00140000 \ n / 60 [n = 0 to 63] EQUD &000F0000 : EQUD &000C0000 : EQUD &000A0000 : EQUD &00089249 EQUD &00078000 : EQUD &0006AAAA : EQUD &00060000 : EQUD &0005745D EQUD &00050000 : EQUD &00049D89 : EQUD &00044924 : EQUD &00040000 EQUD &0003C000 : EQUD &00038787 : EQUD &00035555 : EQUD &0003286B EQUD &00030000 : EQUD &0002DB6D : EQUD &0002BA2E : EQUD &00029BD3 EQUD &00028000 : EQUD &00026666 : EQUD &00024EC4 : EQUD &000238E3 EQUD &00022492 : EQUD &000211A7 : EQUD &00020000 : EQUD &0001EF7B EQUD &0001E000 : EQUD &0001D174 : EQUD &0001C3C3 : EQUD &0001B6DB EQUD &0001AAAA : EQUD &00019F22 : EQUD &00019435 : EQUD &000189D8 EQUD &00018000 : EQUD &000176A2 : EQUD &00016DB6 : EQUD &00016535 EQUD &00015D17 : EQUD &00015555 : EQUD &00014DE9 : EQUD &000146CE EQUD &00014000 : EQUD &00013978 : EQUD &00013333 : EQUD &00012D2D EQUD &00012762 : EQUD &000121CF : EQUD &00011C71 : EQUD &00011745 EQUD &00011249 : EQUD &00010D79 : EQUD &000108D3 : EQUD &00010456 EQUD &00010000 : EQUD &0000FBCD : EQUD &0000F7BD : EQUD &0000F3CF EQUD &FFFFFFFF : EQUD &003D0000 : EQUD &001E8000 : EQUD &00145555 \ n / 61 [n = 0 to 63] EQUD &000F4000 : EQUD &000C3333 : EQUD &000A2AAA : EQUD &0008B6DB EQUD &0007A000 : EQUD &0006C71C : EQUD &00061999 : EQUD &00058BA2 EQUD &00051555 : EQUD &0004B13B : EQUD &00045B6D : EQUD &00041111 EQUD &0003D000 : EQUD &00039696 : EQUD &0003638E : EQUD &000335E5 EQUD &00030CCC : EQUD &0002E79E : EQUD &0002C5D1 : EQUD &0002A6F4 EQUD &00028AAA : EQUD &000270A3 : EQUD &0002589D : EQUD &0002425E EQUD &00022DB6 : EQUD &00021A7B : EQUD &00020888 : EQUD &0001F7BD EQUD &0001E800 : EQUD &0001D936 : EQUD &0001CB4B : EQUD &0001BE2B EQUD &0001B1C7 : EQUD &0001A60D : EQUD &00019AF2 : EQUD &00019069 EQUD &00018666 : EQUD &00017CE0 : EQUD &000173CF : EQUD &00016B29 EQUD &000162E8 : EQUD &00015B05 : EQUD &0001537A : EQUD &00014C41 EQUD &00014555 : EQUD &00013EB1 : EQUD &00013851 : EQUD &00013232 EQUD &00012C4E : EQUD &000126A4 : EQUD &0001212F : EQUD &00011BED EQUD &000116DB : EQUD &000111F7 : EQUD &00010D3D : EQUD &000108AD EQUD &00010444 : EQUD &00010000 : EQUD &0000FBDE : EQUD &0000F7DF EQUD &FFFFFFFF : EQUD &003E0000 : EQUD &001F0000 : EQUD &0014AAAA \ n / 62 [n = 0 to 63] EQUD &000F8000 : EQUD &000C6666 : EQUD &000A5555 : EQUD &0008DB6D EQUD &0007C000 : EQUD &0006E38E : EQUD &00063333 : EQUD &0005A2E8 EQUD &00052AAA : EQUD &0004C4EC : EQUD &00046DB6 : EQUD &00042222 EQUD &0003E000 : EQUD &0003A5A5 : EQUD &000371C7 : EQUD &0003435E EQUD &00031999 : EQUD &0002F3CF : EQUD &0002D174 : EQUD &0002B216 EQUD &00029555 : EQUD &00027AE1 : EQUD &00026276 : EQUD &00024BDA EQUD &000236DB : EQUD &0002234F : EQUD &00021111 : EQUD &00020000 EQUD &0001F000 : EQUD &0001E0F8 : EQUD &0001D2D2 : EQUD &0001C57C EQUD &0001B8E3 : EQUD &0001ACF9 : EQUD &0001A1AF : EQUD &000196F9 EQUD &00018CCC : EQUD &0001831F : EQUD &000179E7 : EQUD &0001711D EQUD &000168BA : EQUD &000160B6 : EQUD &0001590B : EQUD &000151B3 EQUD &00014AAA : EQUD &000143EB : EQUD &00013D70 : EQUD &00013737 EQUD &0001313B : EQUD &00012B78 : EQUD &000125ED : EQUD &00012094 EQUD &00011B6D : EQUD &00011674 : EQUD &000111A7 : EQUD &00010D04 EQUD &00010888 : EQUD &00010432 : EQUD &00010000 : EQUD &0000FBEF EQUD &FFFFFFFF : EQUD &003F0000 : EQUD &001F8000 : EQUD &00150000 \ n / 63 [n = 0 to 63] EQUD &000FC000 : EQUD &000C9999 : EQUD &000A8000 : EQUD &00090000 EQUD &0007E000 : EQUD &00070000 : EQUD &00064CCC : EQUD &0005BA2E EQUD &00054000 : EQUD &0004D89D : EQUD &00048000 : EQUD &00043333 EQUD &0003F000 : EQUD &0003B4B4 : EQUD &00038000 : EQUD &000350D7 EQUD &00032666 : EQUD &00030000 : EQUD &0002DD17 : EQUD &0002BD37 EQUD &0002A000 : EQUD &0002851E : EQUD &00026C4E : EQUD &00025555 EQUD &00024000 : EQUD &00022C23 : EQUD &00021999 : EQUD &00020842 EQUD &0001F800 : EQUD &0001E8BA : EQUD &0001DA5A : EQUD &0001CCCC EQUD &0001C000 : EQUD &0001B3E4 : EQUD &0001A86B : EQUD &00019D89 EQUD &00019333 : EQUD &0001895D : EQUD &00018000 : EQUD &00017711 EQUD &00016E8B : EQUD &00016666 : EQUD &00015E9B : EQUD &00015726 EQUD &00015000 : EQUD &00014924 : EQUD &0001428F : EQUD &00013C3C EQUD &00013627 : EQUD &0001304D : EQUD &00012AAA : EQUD &0001253C EQUD &00012000 : EQUD &00011AF2 : EQUD &00011611 : EQUD &0001115B EQUD &00010CCC : EQUD &00010864 : EQUD &00010421 : EQUD &00010000 \ ****************************************************************************** \ \ Two-pass assembly loop \ \ ****************************************************************************** .endCode ] NEXT pass% : REM Loop back for the second pass REM ****************************************************************************** REM REM Save GameCode REM REM ****************************************************************************** OSCLI "SAVE GameCode "+STR$~CODE%+" "+STR$~O%+" "+STR$~Entry+" "+STR$~CODE END DEF FN_AlignWithZeroes a = P% AND 3 IF a > 0 THEN FOR I% = 1 TO 4 - a [ OPT pass% EQUB 0 ] NEXT I% ENDIF =pass%