{ "chunk": "game_highMemoryFBB8_FBB8", "unit": "game/highMemoryFBB8", "unitLabels": { "game/highMemoryFBB8": { "FC4D": "plotColorIndex", "FC4E": "plotColorBits", "FC4F": "plotKeepMask", "FD70": "radarDroneShapesRle", "FE21": "radarDroneShapesRleEnd", "FE24": "droneScreenShapesRle", "FF90": "droneScreenShapesRleEnd", "FF93": "droneViewfinderSprite" } }, "notes": { "FBB8": { "unit": "game/highMemoryFBB8", "routine": [ "initLineStepper - Sets up a DDA/Bresenham walk over the 40x40 battlefield map from the cell ($93,$92) = (column,row) to the cell ($95,$94). Computes |dRow| -> $8F and |dCol| -> $90, the per-step signs $96/$97 (+1 or -1), the major-axis length -> $8E (wrap threshold) and $91 (steps still to walk), clears both error accumulators and preloads the MINOR one with majorLen/2 so the stair steps fall in the middle of a cell. The walk itself is done one cell at a time by stepLine.", "In: $92 = start row, $93 = start column, $94 = target row, $95 = target column", "Out: A = $91 = number of steps = max(|dRow|,|dCol|), Z set when start and target are the same cell; $8E = major length; $8F/$90 = |dRow|/|dCol|; $96/$97 = row/column step; $8C/$8D = 0 and majorLen/2", "Called from: the movement/line-of-sight code of the main program ($1D57, $1D83, $2512, $306F, $3771, $3A82, $42EC, $4559), the $6F00 overlay ($775D, $77AC, $784F and $83A4 = the shot/route line in the zoom window) and the opponent module ($E80E getStepDirection, $E96F)" ], "line": "assume the row steps downwards (+1)" }, "FBBA": { "unit": "game/highMemoryFBB8", "line": "target row of the line" }, "FBBC": { "unit": "game/highMemoryFBB8", "line": "no borrow into the subtraction" }, "FBBD": { "unit": "game/highMemoryFBB8", "line": "dRow = target row - current row" }, "FBBF": { "unit": "game/highMemoryFBB8", "line": "branch if the target is at or below the start (dRow >= 0)" }, "FBC1": { "unit": "game/highMemoryFBB8", "line": "target is above the start, so ..." }, "FBC2": { "unit": "game/highMemoryFBB8", "line": "... X = $FF: the row steps upwards (-1)" }, "FBC3": { "unit": "game/highMemoryFBB8", "line": "negate dRow to get its magnitude ..." }, "FBC5": { "unit": "game/highMemoryFBB8", "line": "... (C is clear after the borrow, so EOR $FF + 1 is a two's complement negate)" }, "FBC7": { "unit": "game/highMemoryFBB8", "line": "$8F = |dRow|, the length of the row axis" }, "FBC9": { "unit": "game/highMemoryFBB8", "line": "$96 = row increment applied per major step (+1 or -1)" }, "FBCB": { "unit": "game/highMemoryFBB8", "line": "assume the column steps rightwards (+1)" }, "FBCD": { "unit": "game/highMemoryFBB8", "line": "target column of the line" }, "FBCF": { "unit": "game/highMemoryFBB8", "line": "no borrow into the subtraction" }, "FBD0": { "unit": "game/highMemoryFBB8", "line": "dCol = target column - current column" }, "FBD2": { "unit": "game/highMemoryFBB8", "line": "branch if the target is at or right of the start (dCol >= 0)" }, "FBD4": { "unit": "game/highMemoryFBB8", "line": "target is left of the start, so ..." }, "FBD5": { "unit": "game/highMemoryFBB8", "line": "... X = $FF: the column steps leftwards (-1)" }, "FBD6": { "unit": "game/highMemoryFBB8", "line": "negate dCol ..." }, "FBD8": { "unit": "game/highMemoryFBB8", "line": "... to get its magnitude" }, "FBDA": { "unit": "game/highMemoryFBB8", "line": "$90 = |dCol|, the length of the column axis" }, "FBDC": { "unit": "game/highMemoryFBB8", "line": "$97 = column increment applied per major step (+1 or -1)" }, "FBDE": { "unit": "game/highMemoryFBB8", "line": "start both Bresenham error accumulators at zero" }, "FBE0": { "unit": "game/highMemoryFBB8", "line": "$8C = row accumulator" }, "FBE2": { "unit": "game/highMemoryFBB8", "line": "$8D = column accumulator; X = 0 also means 'the column is the major axis'" }, "FBE4": { "unit": "game/highMemoryFBB8", "line": "|dCol| ..." }, "FBE6": { "unit": "game/highMemoryFBB8", "line": "... against |dRow|: the longer axis is the major one" }, "FBE8": { "unit": "game/highMemoryFBB8", "line": "on a tie call the row the major axis (perfect diagonal)" }, "FBEA": { "unit": "game/highMemoryFBB8", "line": "branch if |dCol| > |dRow|: the column is major and A already holds the major length" }, "FBEC": { "unit": "game/highMemoryFBB8", "line": "the row is major, so the accumulator to preload is $8C+1 = $8D (the column one)" }, "FBEE": { "unit": "game/highMemoryFBB8", "line": "major length = |dRow|" }, "FBF0": { "unit": "game/highMemoryFBB8", "line": "$91 = steps still to walk = the major-axis length" }, "FBF2": { "unit": "game/highMemoryFBB8", "line": "$8E = major length; stepLine wraps an accumulator whenever it reaches this" }, "FBF4": { "unit": "game/highMemoryFBB8", "line": "half the major length ..." }, "FBF5": { "unit": "game/highMemoryFBB8", "line": "... preloaded into the MINOR accumulator ($8C when the column is major, $8D when the row is) to centre the stair steps" }, "FBF7": { "unit": "game/highMemoryFBB8", "line": "return the number of steps" }, "FBF9": { "unit": "game/highMemoryFBB8", "line": "Z is set when the two cells are identical (nothing to walk)" }, "FBFA": { "unit": "game/highMemoryFBB8", "routine": [ "stepLine - Walks the line set up by initLineStepper one cell further: adds |dRow| to the row accumulator $8C and |dCol| to the column accumulator $8D, and each time an accumulator reaches the major length $8E it wraps it, moves $92 (row) / $93 (column) by that axis's step sign and reports the applied delta in $90F4 / $90F5. The major axis always fires, so $A9 ends at 0 for an orthogonal step and 1 for a diagonal one; with no steps left nothing moves, both deltas and $A9 are 0.", "In: the line state left by initLineStepper: $8C-$8E, $8F, $90, $91, $92, $93, $96, $97", "Out: A = $91 = steps still to walk (Z = line finished); $92/$93 advanced by one cell; $90F4 = row delta applied (-1/0/+1), $90F5 = column delta applied; $A9 = 1 for a diagonal step, else 0", "Called from: the main program ($1D86, $2517, $3079, $3080, $3083, $36A5, $3AA7, $42EF, $4562), the $6F00 overlay ($7760, $7763, $7766, $77AF, $77B2, $7834, $785B, $83F0, $83FD, $8403) and the opponent module ($E811, $E972); callers loop until the returned count is zero" ], "line": "nothing has moved yet in this step" }, "FBFC": { "unit": "game/highMemoryFBB8", "line": "$90F5 = column delta applied by this step (read at $1D89 and $E819)" }, "FBFF": { "unit": "game/highMemoryFBB8", "line": "$90F4 = row delta applied by this step (read at $1D8C and $E821)" }, "FC02": { "unit": "game/highMemoryFBB8", "line": "steps still to walk" }, "FC04": { "unit": "game/highMemoryFBB8", "line": "park the count in $A9 for a moment; it stays 0 if the line is finished" }, "FC06": { "unit": "game/highMemoryFBB8", "line": "line already at its target: report no movement" }, "FC08": { "unit": "game/highMemoryFBB8", "line": "$A9 = -1; every axis that fires below increments it ..." }, "FC0A": { "unit": "game/highMemoryFBB8", "line": "... so it ends at 0 for an orthogonal and 1 for a diagonal step" }, "FC0C": { "unit": "game/highMemoryFBB8", "line": "row error accumulator" }, "FC0E": { "unit": "game/highMemoryFBB8", "line": "clear the carry before the add" }, "FC0F": { "unit": "game/highMemoryFBB8", "line": "advance it by the row-axis length |dRow|" }, "FC11": { "unit": "game/highMemoryFBB8", "line": "keep the updated row error" }, "FC13": { "unit": "game/highMemoryFBB8", "line": "has it reached the major-axis length?" }, "FC15": { "unit": "game/highMemoryFBB8", "line": "no: the row does not move this step" }, "FC17": { "unit": "game/highMemoryFBB8", "line": "count the row axis as having fired" }, "FC19": { "unit": "game/highMemoryFBB8", "line": "row step (+1 down / -1 up)" }, "FC1B": { "unit": "game/highMemoryFBB8", "line": "report it to the caller in $90F4" }, "FC1E": { "unit": "game/highMemoryFBB8", "line": "clear the carry before the add" }, "FC1F": { "unit": "game/highMemoryFBB8", "line": "current map row ..." }, "FC21": { "unit": "game/highMemoryFBB8", "line": "... moves one cell along the line" }, "FC23": { "unit": "game/highMemoryFBB8", "line": "wrap the row accumulator ..." }, "FC25": { "unit": "game/highMemoryFBB8", "line": "no borrow into the subtraction" }, "FC26": { "unit": "game/highMemoryFBB8", "line": "... by subtracting the major length" }, "FC28": { "unit": "game/highMemoryFBB8", "line": "store the wrapped row accumulator" }, "FC2A": { "unit": "game/highMemoryFBB8", "line": "column error accumulator" }, "FC2C": { "unit": "game/highMemoryFBB8", "line": "clear the carry before the add" }, "FC2D": { "unit": "game/highMemoryFBB8", "line": "advance it by the column-axis length |dCol|" }, "FC2F": { "unit": "game/highMemoryFBB8", "line": "keep the updated column error" }, "FC31": { "unit": "game/highMemoryFBB8", "line": "has it reached the major-axis length?" }, "FC33": { "unit": "game/highMemoryFBB8", "line": "no: the column does not move this step" }, "FC35": { "unit": "game/highMemoryFBB8", "line": "count the column axis too: $A9 becomes 1 = diagonal step" }, "FC37": { "unit": "game/highMemoryFBB8", "line": "column step (+1 right / -1 left)" }, "FC39": { "unit": "game/highMemoryFBB8", "line": "report it to the caller in $90F5" }, "FC3C": { "unit": "game/highMemoryFBB8", "line": "clear the carry before the add" }, "FC3D": { "unit": "game/highMemoryFBB8", "line": "current map column ..." }, "FC3F": { "unit": "game/highMemoryFBB8", "line": "... moves one cell along the line" }, "FC41": { "unit": "game/highMemoryFBB8", "line": "wrap the column accumulator ..." }, "FC43": { "unit": "game/highMemoryFBB8", "line": "no borrow into the subtraction" }, "FC44": { "unit": "game/highMemoryFBB8", "line": "... by subtracting the major length" }, "FC46": { "unit": "game/highMemoryFBB8", "line": "store the wrapped column accumulator" }, "FC48": { "unit": "game/highMemoryFBB8", "line": "one step of the line used up" }, "FC4A": { "unit": "game/highMemoryFBB8", "line": "return the steps still to walk" }, "FC4C": { "unit": "game/highMemoryFBB8", "line": "Z is set once the line has reached its target cell" }, "FC4D": { "unit": "game/highMemoryFBB8", "block": [ "Three scratch bytes of the multicolour plotter below. They live in code space because the plotter", "is called from the $6F00 overlay and must not depend on any overlay variable." ], "line": "plotColorIndex: colour 0-3 handed from plotFatPixel to plotBitmapPixelRow" }, "FC4E": { "unit": "game/highMemoryFBB8", "line": "plotColorBits: the colour pattern already masked down to the pixel pair being written" }, "FC4F": { "unit": "game/highMemoryFBB8", "line": "plotKeepMask: first the mask of that pixel pair, then (inverted) the bits of the byte to keep" }, "FC50": { "unit": "game/highMemoryFBB8", "block": [ "multicolorFillPatterns - 4 bytes, indexed by plotColorIndex: colour index 0-3 replicated into all", "four multicolour pixel pairs of a bitmap byte ($00 = %00000000, $55 = %01010101, $AA, $FF)." ], "line": "colour 0/1/2/3 in every pixel pair" }, "FC54": { "unit": "game/highMemoryFBB8", "block": [ "multicolorPixelMasks - 4 bytes, indexed by (pixel column AND 3): which pixel pair of the byte is", "addressed, leftmost first ($C0 = bits 7-6, $30 = 5-4, $0C = 3-2, $03 = 1-0)." ], "line": "bit mask of pixel pair 0/1/2/3 inside a bitmap byte" }, "FC58": { "unit": "game/highMemoryFBB8", "routine": [ "plotFatPixel - Plots one 'fat' pixel of colour A (0-3) at fat-pixel column X (0-159) and row Y (0-95) into the $A000 multicolour bitmap: a fat pixel is one multicolour pixel wide and two raster lines tall, so it draws bitmap rows 2Y and 2Y+1 through plotBitmapPixelRow. X and Y are stashed in the LDX/LDY operands at the end of plotBitmapPixelRow and come back unchanged.", "In: A = colour index 0-3, X = fat-pixel column 0-159, Y = fat-pixel row 0-95", "Out: one or two bitmap bytes updated; A/X/Y as described (X and Y preserved); $FC4D-$FC4F and $B4/$B5 clobbered", "Called from: the $6F00 overlay only - $83E6 draws the shot/route line in the zoom window and $84F5/$84FC/$8505/$850C draw the four pixels of plotDiamondMarker", "Bug: the range check rejects a point only when the column AND the row are out of range (both tests branch INTO the plot path); it should be BCS to the RTS. Harmless in practice because the overlay clips the coordinates itself at $83AC-$83CA." ], "line": "fat-pixel column past the right edge? (the bitmap is 160 multicolour pixels wide)" }, "FC5A": { "unit": "game/highMemoryFBB8", "line": "column in range: plot - note this also skips the row test" }, "FC5C": { "unit": "game/highMemoryFBB8", "line": "fat-pixel row past the bottom? (192 raster lines / 2 lines per fat pixel)" }, "FC5E": { "unit": "game/highMemoryFBB8", "line": "row in range: plot, even though the column was already rejected" }, "FC60": { "unit": "game/highMemoryFBB8", "line": "only reached when both coordinates are out of range" }, "FC61": { "unit": "game/highMemoryFBB8", "line": "stash the caller's column in the LDX # operand at L_FCC8+1 (restored on the way out)" }, "FC64": { "unit": "game/highMemoryFBB8", "line": "stash the caller's row in the LDY # operand at L_FCCA+1" }, "FC67": { "unit": "game/highMemoryFBB8", "line": "hand the colour index to plotBitmapPixelRow" }, "FC6A": { "unit": "game/highMemoryFBB8", "line": "fat-pixel row ..." }, "FC6B": { "unit": "game/highMemoryFBB8", "line": "... x2 = the upper of the two bitmap rows it covers" }, "FC6C": { "unit": "game/highMemoryFBB8", "line": "keep it for the second half" }, "FC6D": { "unit": "game/highMemoryFBB8", "line": "Y = bitmap row 2Y" }, "FC6E": { "unit": "game/highMemoryFBB8", "line": "draw the upper raster line" }, "FC71": { "unit": "game/highMemoryFBB8", "line": "recover the upper bitmap row ..." }, "FC72": { "unit": "game/highMemoryFBB8", "line": "Y = the upper bitmap row again" }, "FC73": { "unit": "game/highMemoryFBB8", "line": "... and step to the lower one (2Y+1), then fall straight into the plotter again" }, "FC74": { "unit": "game/highMemoryFBB8", "routine": [ "plotBitmapPixelRow - Second half of plotFatPixel: writes one multicolour pixel pair into the bitmap. For bitmap row Y (0-191) and pixel column X (0-159) it builds the byte address $A000 + 320*(Y/8) + 8*(X/4) + (Y AND 7) from the four tables in the $9300 data block, then merges plotColorIndex's two bits into the pixel pair (X AND 3) of that byte. Entered by JSR for the upper raster line and by falling through for the lower one.", "In: X = pixel column 0-159, Y = bitmap row 0-191, $FC4D = colour index 0-3", "Out: one bitmap byte updated; $B4/$B5 = its address; $FC4E = colour bits, $FC4F = keep mask; A clobbered, X and Y reloaded with the values plotFatPixel stashed at $FC61/$FC64", "Called from: plotFatPixel ($FC6E and the fall-through at $FC73) only" ], "line": "bitmap row" }, "FC75": { "unit": "game/highMemoryFBB8", "line": "raster line inside its 8-line character row" }, "FC77": { "unit": "game/highMemoryFBB8", "line": "patch it into the ADC # operand at L_FC83+1" }, "FC7A": { "unit": "game/highMemoryFBB8", "line": "bitmap row again" }, "FC7B": { "unit": "game/highMemoryFBB8", "line": "divide by 8 ..." }, "FC7C": { "unit": "game/highMemoryFBB8", "line": "..." }, "FC7D": { "unit": "game/highMemoryFBB8", "line": "... = character row 0-24" }, "FC7E": { "unit": "game/highMemoryFBB8", "line": "index the row tables with it" }, "FC7F": { "unit": "game/highMemoryFBB8", "line": "clear the carry before the address add" }, "FC80": { "unit": "game/highMemoryFBB8", "line": "low byte of $A000 + 320*charRow (25-entry table in the $9300 block)" }, "FC83": { "unit": "game/highMemoryFBB8", "line": "+ the raster line within the row (operand written at $FC77)" }, "FC85": { "unit": "game/highMemoryFBB8", "line": "$B4 = low byte of the target address" }, "FC87": { "unit": "game/highMemoryFBB8", "line": "matching high byte ($A0..$BE)" }, "FC8A": { "unit": "game/highMemoryFBB8", "line": "carry out of the low half" }, "FC8C": { "unit": "game/highMemoryFBB8", "line": "$B5 = high byte of the target address" }, "FC8E": { "unit": "game/highMemoryFBB8", "line": "pixel column" }, "FC8F": { "unit": "game/highMemoryFBB8", "line": "which of the four pixel pairs of the byte (0 = leftmost)" }, "FC91": { "unit": "game/highMemoryFBB8", "line": "index the pixel-pair mask table with it" }, "FC92": { "unit": "game/highMemoryFBB8", "line": "$C0/$30/$0C/$03 = the two bits of that pair" }, "FC95": { "unit": "game/highMemoryFBB8", "line": "park the pair mask" }, "FC98": { "unit": "game/highMemoryFBB8", "line": "colour index stored by plotFatPixel" }, "FC9B": { "unit": "game/highMemoryFBB8", "line": "$00/$55/$AA/$FF; the AND leaves the colour only in this pixel pair" }, "FC9E": { "unit": "game/highMemoryFBB8", "line": "the bits to OR into the byte" }, "FCA1": { "unit": "game/highMemoryFBB8", "line": "the pair mask again ..." }, "FCA4": { "unit": "game/highMemoryFBB8", "line": "... inverted ..." }, "FCA6": { "unit": "game/highMemoryFBB8", "line": "... = every bit of the byte that must survive" }, "FCA9": { "unit": "game/highMemoryFBB8", "line": "pixel column once more" }, "FCAA": { "unit": "game/highMemoryFBB8", "line": "divide by 4 (4 multicolour pixels per byte) ..." }, "FCAB": { "unit": "game/highMemoryFBB8", "line": "... = character column 0-39" }, "FCAC": { "unit": "game/highMemoryFBB8", "line": "index the column tables with it" }, "FCAD": { "unit": "game/highMemoryFBB8", "line": "clear the carry before the 16-bit add" }, "FCAE": { "unit": "game/highMemoryFBB8", "line": "low byte of 8*column (40-entry table at $9332)" }, "FCB1": { "unit": "game/highMemoryFBB8", "line": "add it to the row address ..." }, "FCB3": { "unit": "game/highMemoryFBB8", "line": "low byte of the bitmap byte address" }, "FCB5": { "unit": "game/highMemoryFBB8", "line": "high byte of 8*column (0 or 1 - column 32 onwards crosses $100)" }, "FCB8": { "unit": "game/highMemoryFBB8", "line": "carry on into the high byte" }, "FCBA": { "unit": "game/highMemoryFBB8", "line": "$B4/$B5 now point at the exact bitmap byte" }, "FCBC": { "unit": "game/highMemoryFBB8", "line": "read/modify/write through the pointer with no offset" }, "FCBE": { "unit": "game/highMemoryFBB8", "line": "read the byte" }, "FCC0": { "unit": "game/highMemoryFBB8", "line": "clear the two bits of the addressed pixel pair" }, "FCC3": { "unit": "game/highMemoryFBB8", "line": "drop the new colour into them" }, "FCC6": { "unit": "game/highMemoryFBB8", "line": "write the byte back to the bitmap" }, "FCC8": { "unit": "game/highMemoryFBB8", "line": "restore the caller's fat-pixel column (operand written at $FC61)" }, "FCCA": { "unit": "game/highMemoryFBB8", "line": "restore the caller's fat-pixel row (operand written at $FC64)" }, "FCCC": { "unit": "game/highMemoryFBB8", "line": "back to plotFatPixel, or to its caller after the second raster line" }, "FCCD": { "unit": "game/highMemoryFBB8", "routine": [ "multiply8x8 - Unsigned 8 x 8 -> 16 bit multiply, shift-and-add, 8 rounds: the multiplier is shifted out of $19 one bit at a time while the 16-bit product is shifted right through A (high half) and $1A (low half).", "In: A = multiplicand, Y = multiplier", "Out: A = $18 = product low byte, Y = $19 = product high byte, $1A clobbered, X = 0", "Called from: $55BD (kill value x 10 added to the side score), the $6F00 overlay ($814A film page x 10, $846B value x 255 before printDecimal) and the trainer's $EC00 tail ($ECD4)", "Note: the survey header had the outputs the wrong way round - A holds the LOW byte on return, which is what $55C3 and printDecimal (A = low, Y = middle) expect." ], "line": "$18 = multiplicand, added in on every set multiplier bit" }, "FCCF": { "unit": "game/highMemoryFBB8", "line": "$19 = multiplier, consumed bit by bit" }, "FCD1": { "unit": "game/highMemoryFBB8", "line": "A = high half of the running product" }, "FCD3": { "unit": "game/highMemoryFBB8", "line": "$1A = low half of the running product" }, "FCD5": { "unit": "game/highMemoryFBB8", "line": "8 multiplier bits to go" }, "FCD7": { "unit": "game/highMemoryFBB8", "line": "shift the next multiplier bit into carry" }, "FCD9": { "unit": "game/highMemoryFBB8", "line": "bit clear: nothing to add this round" }, "FCDB": { "unit": "game/highMemoryFBB8", "line": "clear the carry before the add" }, "FCDC": { "unit": "game/highMemoryFBB8", "line": "bit set: add the multiplicand into the high half" }, "FCDE": { "unit": "game/highMemoryFBB8", "line": "shift the 16-bit product right one place ..." }, "FCDF": { "unit": "game/highMemoryFBB8", "line": "... carrying the bit that falls out into the low half" }, "FCE1": { "unit": "game/highMemoryFBB8", "line": "one round of the multiply done" }, "FCE2": { "unit": "game/highMemoryFBB8", "line": "next multiplier bit" }, "FCE4": { "unit": "game/highMemoryFBB8", "line": "Y = product high byte" }, "FCE5": { "unit": "game/highMemoryFBB8", "line": "$19 = product high byte" }, "FCE7": { "unit": "game/highMemoryFBB8", "line": "product low byte ..." }, "FCE9": { "unit": "game/highMemoryFBB8", "line": "... into $18 as well" }, "FCEB": { "unit": "game/highMemoryFBB8", "line": "returns A = low byte, Y = high byte, X = 0" }, "FCEC": { "unit": "game/highMemoryFBB8", "routine": [ "divideSigned16by8 - Divides the signed 16-bit value Y:A (Y = high byte, A = low byte) by the unsigned 8-bit divisor X. A negative dividend is negated first and the quotient negated again afterwards, so the remainder left in Y is always that of the absolute value.", "In: X = divisor, A = dividend low byte, Y = dividend high byte (its bit 7 is the sign)", "Out: A = quotient (low 8 bits, sign restored), Y = remainder of |dividend|, $18 = divisor, $19 = |quotient|, X = 0", "Called from: $41E4 (game clock modulo 10 or 50 - the caller tests the remainder with CPY #$00), $49EF/$49F9 (average of a group's column and row), the trainer ($E8EA, $E8F6, $E902, $E90D) and the drone-flight AI ($EE86, $EE96, $EEA6, $EEB6)" ], "line": "$18 = divisor" }, "FCEE": { "unit": "game/highMemoryFBB8", "line": "$19 = dividend low byte" }, "FCF0": { "unit": "game/highMemoryFBB8", "line": "dividend high byte carries the sign" }, "FCF1": { "unit": "game/highMemoryFBB8", "line": "non-negative: divide it as it stands (A is already the high byte)" }, "FCF3": { "unit": "game/highMemoryFBB8", "line": "negative dividend: take its low byte ..." }, "FCF5": { "unit": "game/highMemoryFBB8", "line": "one's complement of the low byte ..." }, "FCF7": { "unit": "game/highMemoryFBB8", "line": "clear the carry for the +1" }, "FCF8": { "unit": "game/highMemoryFBB8", "line": "... plus one = two's complement" }, "FCFA": { "unit": "game/highMemoryFBB8", "line": "store the negated low byte" }, "FCFC": { "unit": "game/highMemoryFBB8", "line": "then the high byte ..." }, "FCFD": { "unit": "game/highMemoryFBB8", "line": "one's complement of the high byte ..." }, "FCFF": { "unit": "game/highMemoryFBB8", "line": "... taking the carry out of the low half (16-bit two's complement)" }, "FD01": { "unit": "game/highMemoryFBB8", "line": "divide the magnitude" }, "FD04": { "unit": "game/highMemoryFBB8", "line": "negate the quotient again ..." }, "FD06": { "unit": "game/highMemoryFBB8", "line": "clear the carry for the +1" }, "FD07": { "unit": "game/highMemoryFBB8", "line": "... so the sign of the result matches the dividend" }, "FD09": { "unit": "game/highMemoryFBB8", "line": "Y still holds the remainder of the absolute value" }, "FD0A": { "unit": "game/highMemoryFBB8", "routine": [ "divideUnsigned16by8 - Unsigned restoring division, 8 rounds: the dividend low byte in $19 is shifted left into the running remainder in A, the divisor $18 is subtracted whenever it fits, and the quotient bits are collected in the low end of $19. Entered from divideSigned16by8, never called directly.", "In: A = dividend high byte (the running remainder to start with), $19 = dividend low byte, $18 = divisor", "Out: A = $19 = quotient low byte, Y = remainder, X = 0", "Called from: divideSigned16by8 ($FCF1 for a non-negative dividend, $FD01 for a negated one)", "Note: only 8 quotient bits are produced, so a quotient above 255 silently wraps, and there is no divide-by-zero guard (X = 0 gives quotient $FF)." ], "line": "8 quotient bits" }, "FD0C": { "unit": "game/highMemoryFBB8", "line": "shift the dividend left, its top bit into carry ..." }, "FD0E": { "unit": "game/highMemoryFBB8", "line": "... and on into the running remainder" }, "FD0F": { "unit": "game/highMemoryFBB8", "line": "does the divisor fit into the remainder?" }, "FD11": { "unit": "game/highMemoryFBB8", "line": "no: this quotient bit stays 0" }, "FD13": { "unit": "game/highMemoryFBB8", "line": "yes: subtract it (C is set, so no borrow)" }, "FD15": { "unit": "game/highMemoryFBB8", "line": "and set the quotient bit that was just shifted in at bit 0" }, "FD17": { "unit": "game/highMemoryFBB8", "line": "one quotient bit done" }, "FD18": { "unit": "game/highMemoryFBB8", "line": "next quotient bit" }, "FD1A": { "unit": "game/highMemoryFBB8", "line": "Y = remainder (the label D_FD1A is an artifact of the boot loader's block move at $C200, not a data item)" }, "FD1B": { "unit": "game/highMemoryFBB8", "line": "A = quotient" }, "FD1D": { "unit": "game/highMemoryFBB8", "line": "back to divideSigned16by8 or straight to its caller" }, "FD1E": { "unit": "game/highMemoryFBB8", "routine": [ "addRandomJitter - Scatters a map coordinate: returns the base value A plus or minus 2*k, where k is the number of consecutive 1 bits at the bottom of a fresh scenario-RNG byte (a geometric distribution, so small offsets are the common case) capped at the limit in Y, and the sign is bit 7 of the RNG's middle byte $5B. Y = 0 returns the base unchanged; a result that went negative is clamped to 0.", "In: A = base value, Y = maximum number of bits to count (0 = no jitter); scenario RNG $5A-$5C", "Out: A = jittered value 0..127; $18 (bit count) and $19 (limit) clobbered; the RNG is advanced once", "Called from: the map generator only - $7036 (hill site column), $738B and $7398 (forest cluster row/column); each caller retries while the result is >= 40, i.e. off the 40x40 map" ], "line": "patch the base value into the ADC # operand at L_FD43+1" }, "FD21": { "unit": "game/highMemoryFBB8", "line": "$19 = how many 1 bits may still be counted" }, "FD23": { "unit": "game/highMemoryFBB8", "line": "the limit once more" }, "FD24": { "unit": "game/highMemoryFBB8", "line": "a limit of 0 means 'no jitter at all'" }, "FD26": { "unit": "game/highMemoryFBB8", "line": "start the count of 1 bits at zero" }, "FD28": { "unit": "game/highMemoryFBB8", "line": "$18 = number of 1 bits counted so far" }, "FD2A": { "unit": "game/highMemoryFBB8", "line": "one byte from the scenario/map RNG" }, "FD2D": { "unit": "game/highMemoryFBB8", "line": "shift the next random bit out" }, "FD2E": { "unit": "game/highMemoryFBB8", "line": "stop at the first 0 bit - hence the geometric distribution" }, "FD30": { "unit": "game/highMemoryFBB8", "line": "count this 1 bit" }, "FD32": { "unit": "game/highMemoryFBB8", "line": "and use up one of the allowed counts" }, "FD34": { "unit": "game/highMemoryFBB8", "line": "keep counting until a 0 bit or the limit" }, "FD36": { "unit": "game/highMemoryFBB8", "line": "number of consecutive 1 bits" }, "FD38": { "unit": "game/highMemoryFBB8", "line": "offset = 2 * that count (so 0, 2, 4, ... cells)" }, "FD39": { "unit": "game/highMemoryFBB8", "line": "bit 7 of $5B, just stirred by the RNG, picks the direction" }, "FD3B": { "unit": "game/highMemoryFBB8", "line": "clear: add the offset" }, "FD3D": { "unit": "game/highMemoryFBB8", "line": "set: negate the offset ..." }, "FD3F": { "unit": "game/highMemoryFBB8", "line": "clear the carry for the +1 of the negate" }, "FD40": { "unit": "game/highMemoryFBB8", "line": "... so it is subtracted instead" }, "FD42": { "unit": "game/highMemoryFBB8", "line": "drop the carry the negate may have left" }, "FD43": { "unit": "game/highMemoryFBB8", "line": "add the base value (operand written at $FD1E)" }, "FD45": { "unit": "game/highMemoryFBB8", "line": "keep it while the result is still positive" }, "FD47": { "unit": "game/highMemoryFBB8", "line": "a coordinate that fell below 0 is clamped to the map edge" }, "FD49": { "unit": "game/highMemoryFBB8", "line": "the callers reject anything >= 40 and try again" }, "FD4A": { "unit": "game/highMemoryFBB8", "routine": [ "nextScenarioRandom - Pseudo random generator of the scenario/map seed: a 24-bit shift register on $5A/$5B/$5C fed by a non-linear function of $5A ((seed0 >> 2) minus seed0, with the carry the shifts left), returning $5A EOR $5B. This seed IS the map: it is zeroed at $7731, taken from the game RNG at $77E7 or decoded from the 5-character MAP ID the players type (5 bits per character through the alphabet at $890C, $77F7-$7821), and only the battlefield/unit generator consumes it. Byte-for-byte the same code as nextGameRandom, on different state.", "In: the 24-bit state $5A-$5C", "Out: A = random byte ($5A EOR $5B), state advanced, C = 0", "Called from: addRandomJitter ($FD2A), randomUnitIndex ($4805) and the map generator throughout ($6FA9-$7889)" ], "line": "byte 0 of the scenario seed" }, "FD4C": { "unit": "game/highMemoryFBB8", "line": "shift it right ..." }, "FD4D": { "unit": "game/highMemoryFBB8", "line": "... twice" }, "FD4E": { "unit": "game/highMemoryFBB8", "line": "minus the original byte, using whatever carry the shifts left (the non-linear part)" }, "FD50": { "unit": "game/highMemoryFBB8", "line": "the bit that will be fed back is now in carry" }, "FD51": { "unit": "game/highMemoryFBB8", "line": "roll it into byte 2, whose top bit goes to carry" }, "FD53": { "unit": "game/highMemoryFBB8", "line": "roll that into byte 1, whose top bit goes to carry" }, "FD55": { "unit": "game/highMemoryFBB8", "line": "and that one into the top of byte 0 - the 24-bit register has advanced" }, "FD57": { "unit": "game/highMemoryFBB8", "line": "callers rely on C = 0 on return" }, "FD58": { "unit": "game/highMemoryFBB8", "line": "result = byte 0 ..." }, "FD5A": { "unit": "game/highMemoryFBB8", "line": "... EOR byte 1" }, "FD5C": { "unit": "game/highMemoryFBB8", "line": "return the random byte with C = 0" }, "FD5D": { "unit": "game/highMemoryFBB8", "routine": [ "nextGameRandom - The lock-step game random generator: identical code to nextScenarioRandom but on the state $57/$58/$59, which the start-up block seeds with the fixed constants $49,$19,$02 at $0309-$0313, so both machines of a modem game walk the same sequence and stay in step.", "In: the 24-bit state $57-$59", "Out: A = random byte ($57 EOR $58), state advanced, C = 0", "Called from: everywhere - the main program ($0CCD, $1316, $180B, $1947, $1BDF, $29C1, $3D75, $5533, $5FF3), both $6F00 overlays, the sound driver ($C862), the trainer AI and the drone AI", "Note: docs/overview.md calls $FD5D-$FD6F 'the IRQ handler region'; that is wrong - the IRQ vector points at $1298 and later $10F1, and this is just the RNG." ], "line": "byte 0 of the game RNG state (seeded $49)" }, "FD5F": { "unit": "game/highMemoryFBB8", "line": "shift it right ..." }, "FD60": { "unit": "game/highMemoryFBB8", "line": "... twice" }, "FD61": { "unit": "game/highMemoryFBB8", "line": "minus the original byte, carry included" }, "FD63": { "unit": "game/highMemoryFBB8", "line": "the feedback bit lands in carry" }, "FD64": { "unit": "game/highMemoryFBB8", "line": "roll it into byte 2 (seeded $02)" }, "FD66": { "unit": "game/highMemoryFBB8", "line": "roll on into byte 1 (seeded $19)" }, "FD68": { "unit": "game/highMemoryFBB8", "line": "and into the top of byte 0" }, "FD6A": { "unit": "game/highMemoryFBB8", "line": "callers rely on C = 0 on return" }, "FD6B": { "unit": "game/highMemoryFBB8", "line": "result = byte 0 ..." }, "FD6D": { "unit": "game/highMemoryFBB8", "line": "... EOR byte 1" }, "FD6F": { "unit": "game/highMemoryFBB8", "line": "return the random byte with C = 0" }, "FD70": { "unit": "game/highMemoryFBB8", "block": [ "radarDroneShapesRle - 177 bytes (the stream runs on to $FE23): the RLE-compressed set of the", "eight small drone icons. runMissileScreen unpacks it with unpackRleTo0200 ($7596, A/Y = $FD70)", "into $0200-$03FF as eight 64-byte sprite shapes, and cmdDroneMove ($5322) copies shape", "(heading+4) AND 7 - the heading turned 180 degrees, because a received command's coordinates are", "mirrored - into sprite 0 to show the opponent's drone on the missile/radar screen.", "Format: the first byte is the escape marker ($01); after it 'marker,count,value' emits value", "count times and every other byte is a literal. Each shape carries an 8x7 top-down aircraft glyph", "in the middle byte of sprite rows 7-13, e.g. north = $08,$1C,$7F,$08,$08,$08,$1C, then NE, E,", "SE, S, SW, W, NW.", "(The survey called this arrowGlyphTable and believed it unreferenced; it is neither arrows nor", "dead data.)" ], "line": "escape marker $01, then the compressed shape stream" }, "FE21": { "unit": "game/highMemoryFBB8", "block": [ "Last token of the stream above: marker $01, count $17, value $00 = the 23 zero bytes that pad out", "the eighth shape. It only carries a label because the survey mistook it for a table start." ], "line": "$01,$17,$00 = run of 23 zeros ending the radar drone shapes" }, "FE24": { "unit": "game/highMemoryFBB8", "block": [ "droneScreenShapesRle - RLE stream (escape marker $05) running from here to $FF92, unpacked into", "$0200-$03FF by enterDroneScreen ($7FF0, A/Y = $FE24) as eight 64-byte sprite shapes. These are", "the big 24x21 top-down drone silhouettes for the eight headings (N, NE, E, SE, S, SW, W, NW);", "the drone screen loads one of them into sprite 5 through loadDirectionShapeSprite ($802A at", "reset, $810B at launch, $829E on every camera step) as the heading indicator.", "(The survey called this aircraftShapeTable, read the $05,$xx run tokens as record headers and", "took it for stale mastering data; the $05 is simply the escape marker of the compressed stream.)" ], "line": "escape marker $05, then the compressed silhouette stream" }, "FED2": { "unit": "game/highMemoryFBB8", "line": "the label here is an artifact of the boot loader's block move (STA $FED2,Y at $C203); this is just more compressed shape data" }, "FF90": { "unit": "game/highMemoryFBB8", "block": [ "Last token of the drone-silhouette stream: marker $05, count $0E, value $00 = the 14 zero bytes", "that fill up the eighth 64-byte shape. (Labelled unknownShapeTail by the survey.)" ], "line": "$05,$0E,$00 = run of 14 zeros ending the drone silhouettes" }, "FF93": { "unit": "game/highMemoryFBB8", "block": [ "droneViewfinderSprite - 63 bytes of raw, uncompressed sprite data (21 rows x 3 bytes): the drone", "camera's viewfinder, a frame of four corner brackets with a centre tick on each side and a scale", "bar across the middle. launchDrone copies it into sprite 2's shape block with the loop at", "$80EB-$80F2 (LDY #$3F, so it also reads the sprite's unused 64th byte from $FFD2, one byte past", "the block the loader moved up here), colours the sprite black ($80E4), parks it at X = $AC /", "Y = $88 over the camera window and enables it.", "This is why the loader moves the $FA00 block up by $1B8: the sprite has to end exactly at $FFD1,", "immediately below the CPU vectors." ], "line": "drone camera viewfinder, 21 rows of 24 pixels" } }, "dataTypes": { "FC50": [ "byte", 4 ], "FC54": [ "byte", 4 ], "FD70": [ "byte", 177 ], "FE21": [ "byte", 3 ], "FE24": [ "byte", 174 ], "FED2": [ "byte", 190 ], "FF90": [ "byte", 3 ], "FF93": [ "sprite", 63 ] }, "zpComments": { "8C": "line stepper: Bresenham error accumulator of the row axis, stepped by |dRow| and wrapped at the major length $8E; initLineStepper preloads it with majorLen/2 when the column is the major axis; the map generator reuses it as the random-bit count", "8D": "line stepper: error accumulator of the column axis, stepped by |dCol|; preloaded with majorLen/2 when the row is the major axis; the map generator reuses it as the random bit buffer", "A9": "set to 1 by stepLine when the step moved along both axes, 0 for an orthogonal step and 0 when the line was already finished; a diagonal step costs 8 extra movement points at $45DF" } }