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 and RISC OS 2 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%