; ============================================================================ ; $FBB8-$FFD1 - resident high memory: line walker, random numbers, arithmetic helpers ; ============================================================================ ; Track 29 sectors 11-15, loaded at $FA00 and then moved up by $1B8 so that it ends just below the CPU ; vectors. $FFDE-$FFE7 holds the 10-byte disk serial the two sides compare. .setcpu "6502" .include "c64.inc" .include "zeropage.inc" ; ---- references to code/data outside this file ---- lastStepRowDelta := $90F4 lastStepColDelta := $90F5 bitmapRowLoTable := $9300 bitmapRowHiTable := $9319 bitmapColLoTable := $9332 bitmapColHiTable := $935A ; Contents ; -------- ; $FBB8 initLineStepper Sets up a DDA/Bresenham walk over the 40x40 battlefield map from the cell ; ($93,$92) = (column,row) to the cell ($95,$94). ; $FBFA 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. ; $FC58 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. ; $FC74 plotBitmapPixelRow Second half of plotFatPixel: writes one multicolour pixel pair into the bitmap. ; $FCCD 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). ; $FCEC divideSigned16by8 Divides the signed 16-bit value Y:A (Y = high byte, A = low byte) by the ; unsigned 8-bit divisor X. ; $FD0A 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. ; $FD1E 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. ; $FD4A 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. ; $FD5D 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. .org $FBB8 ; ---------------------------------------------------------------------- ; 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) ; ---------------------------------------------------------------------- initLineStepper: ldx #$01 ; FBB8 assume the row steps downwards (+1) lda lineEndRow ; FBBA target row of the line sec ; FBBC no borrow into the subtraction sbc lineCurRow ; FBBD dRow = target row - current row bcs L_FBC7 ; FBBF branch if the target is at or below the start (dRow >= 0) dex ; FBC1 target is above the start, so ... dex ; FBC2 ... X = $FF: the row steps upwards (-1) eor #$FF ; FBC3 negate dRow to get its magnitude ... adc #$01 ; FBC5 ... (C is clear after the borrow, so EOR $FF + 1 is a two's complement negate) L_FBC7: sta lineDeltaRow ; FBC7 $8F = |dRow|, the length of the row axis stx lineStepRow ; FBC9 $96 = row increment applied per major step (+1 or -1) ldx #$01 ; FBCB assume the column steps rightwards (+1) lda lineEndCol ; FBCD target column of the line sec ; FBCF no borrow into the subtraction sbc lineCurCol ; FBD0 dCol = target column - current column bcs L_FBDA ; FBD2 branch if the target is at or right of the start (dCol >= 0) dex ; FBD4 target is left of the start, so ... dex ; FBD5 ... X = $FF: the column steps leftwards (-1) eor #$FF ; FBD6 negate dCol ... adc #$01 ; FBD8 ... to get its magnitude L_FBDA: sta lineDeltaCol ; FBDA $90 = |dCol|, the length of the column axis stx lineStepCol ; FBDC $97 = column increment applied per major step (+1 or -1) ldx #$00 ; FBDE start both Bresenham error accumulators at zero stx lineErrRow ; FBE0 $8C = row accumulator stx lineErrCol ; FBE2 $8D = column accumulator; X = 0 also means 'the column is the major axis' lda lineDeltaCol ; FBE4 |dCol| ... cmp lineDeltaRow ; FBE6 ... against |dRow|: the longer axis is the major one beq L_FBEC ; FBE8 on a tie call the row the major axis (perfect diagonal) bcs L_FBF0 ; FBEA branch if |dCol| > |dRow|: the column is major and A already holds the major length L_FBEC: ldx #$01 ; FBEC the row is major, so the accumulator to preload is $8C+1 = $8D (the column one) lda lineDeltaRow ; FBEE major length = |dRow| L_FBF0: sta lineStepsLeft ; FBF0 $91 = steps still to walk = the major-axis length sta lineMajorLen ; FBF2 $8E = major length; stepLine wraps an accumulator whenever it reaches this lsr a ; FBF4 half the major length ... sta lineErrRow,x ; FBF5 ... preloaded into the MINOR accumulator ($8C when the column is major, $8D when the row is) to centre the stair steps lda lineStepsLeft ; FBF7 return the number of steps rts ; FBF9 Z is set when the two cells are identical (nothing to walk) ; ---------------------------------------------------------------------- ; 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 ; ---------------------------------------------------------------------- stepLine: lda #$00 ; FBFA nothing has moved yet in this step sta lastStepColDelta ; FBFC $90F5 = column delta applied by this step (read at $1D89 and $E819) sta lastStepRowDelta ; FBFF $90F4 = row delta applied by this step (read at $1D8C and $E821) lda lineStepsLeft ; FC02 steps still to walk sta isDiagonalStep ; FC04 park the count in $A9 for a moment; it stays 0 if the line is finished beq L_FC4A ; FC06 line already at its target: report no movement lda #$FF ; FC08 $A9 = -1; every axis that fires below increments it ... sta isDiagonalStep ; FC0A ... so it ends at 0 for an orthogonal and 1 for a diagonal step lda lineErrRow ; FC0C row error accumulator clc ; FC0E clear the carry before the add adc lineDeltaRow ; FC0F advance it by the row-axis length |dRow| sta lineErrRow ; FC11 keep the updated row error cmp lineMajorLen ; FC13 has it reached the major-axis length? bcc L_FC2A ; FC15 no: the row does not move this step inc isDiagonalStep ; FC17 count the row axis as having fired lda lineStepRow ; FC19 row step (+1 down / -1 up) sta lastStepRowDelta ; FC1B report it to the caller in $90F4 clc ; FC1E clear the carry before the add adc lineCurRow ; FC1F current map row ... sta lineCurRow ; FC21 ... moves one cell along the line lda lineErrRow ; FC23 wrap the row accumulator ... sec ; FC25 no borrow into the subtraction sbc lineMajorLen ; FC26 ... by subtracting the major length sta lineErrRow ; FC28 store the wrapped row accumulator L_FC2A: lda lineErrCol ; FC2A column error accumulator clc ; FC2C clear the carry before the add adc lineDeltaCol ; FC2D advance it by the column-axis length |dCol| sta lineErrCol ; FC2F keep the updated column error cmp lineMajorLen ; FC31 has it reached the major-axis length? bcc L_FC48 ; FC33 no: the column does not move this step inc isDiagonalStep ; FC35 count the column axis too: $A9 becomes 1 = diagonal step lda lineStepCol ; FC37 column step (+1 right / -1 left) sta lastStepColDelta ; FC39 report it to the caller in $90F5 clc ; FC3C clear the carry before the add adc lineCurCol ; FC3D current map column ... sta lineCurCol ; FC3F ... moves one cell along the line lda lineErrCol ; FC41 wrap the column accumulator ... sec ; FC43 no borrow into the subtraction sbc lineMajorLen ; FC44 ... by subtracting the major length sta lineErrCol ; FC46 store the wrapped column accumulator L_FC48: dec lineStepsLeft ; FC48 one step of the line used up L_FC4A: lda lineStepsLeft ; FC4A return the steps still to walk rts ; FC4C Z is set once the line has reached its target cell ; 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. plotColorIndex: .byte $00 ; FC4D plotColorIndex: colour 0-3 handed from plotFatPixel to plotBitmapPixelRow plotColorBits: .byte $00 ; FC4E plotColorBits: the colour pattern already masked down to the pixel pair being written plotKeepMask: .byte $00 ; FC4F plotKeepMask: first the mask of that pixel pair, then (inverted) the bits of the byte to keep ; 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). multicolorFillPatterns: .byte $00,$55,$AA,$FF ; FC50 .U.. colour 0/1/2/3 in every pixel pair ; 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). multicolorPixelMasks: .byte $C0,$30,$0C,$03 ; FC54 .0.. bit mask of pixel pair 0/1/2/3 inside a bitmap byte ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- plotFatPixel: cpx #$A0 ; FC58 fat-pixel column past the right edge? (the bitmap is 160 multicolour pixels wide) bcc L_FC61 ; FC5A column in range: plot - note this also skips the row test cpy #$60 ; FC5C fat-pixel row past the bottom? (192 raster lines / 2 lines per fat pixel) bcc L_FC61 ; FC5E row in range: plot, even though the column was already rejected rts ; FC60 only reached when both coordinates are out of range L_FC61: stx L_FCC8+1 ; FC61 stash the caller's column in the LDX # operand at L_FCC8+1 (restored on the way out) sty L_FCCA+1 ; FC64 stash the caller's row in the LDY # operand at L_FCCA+1 sta plotColorIndex ; FC67 hand the colour index to plotBitmapPixelRow tya ; FC6A fat-pixel row ... asl a ; FC6B ... x2 = the upper of the two bitmap rows it covers pha ; FC6C keep it for the second half tay ; FC6D Y = bitmap row 2Y jsr plotBitmapPixelRow ; FC6E draw the upper raster line pla ; FC71 recover the upper bitmap row ... tay ; FC72 Y = the upper bitmap row again iny ; FC73 ... and step to the lower one (2Y+1), then fall straight into the plotter again ; ---------------------------------------------------------------------- ; 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 ; ---------------------------------------------------------------------- plotBitmapPixelRow: tya ; FC74 bitmap row and #$07 ; FC75 raster line inside its 8-line character row sta L_FC83+1 ; FC77 patch it into the ADC # operand at L_FC83+1 tya ; FC7A bitmap row again lsr a ; FC7B divide by 8 ... lsr a ; FC7C ... lsr a ; FC7D ... = character row 0-24 tay ; FC7E index the row tables with it clc ; FC7F clear the carry before the address add lda bitmapRowLoTable,y ; FC80 low byte of $A000 + 320*charRow (25-entry table in the $9300 block) L_FC83: adc #$FF ; FC83 + the raster line within the row (operand written at $FC77) sta bitmapPtr ; FC85 $B4 = low byte of the target address lda bitmapRowHiTable,y ; FC87 matching high byte ($A0..$BE) adc #$00 ; FC8A carry out of the low half sta bitmapPtrHi ; FC8C $B5 = high byte of the target address txa ; FC8E pixel column and #$03 ; FC8F which of the four pixel pairs of the byte (0 = leftmost) tay ; FC91 index the pixel-pair mask table with it lda multicolorPixelMasks,y ; FC92 $C0/$30/$0C/$03 = the two bits of that pair sta plotKeepMask ; FC95 park the pair mask ldy plotColorIndex ; FC98 colour index stored by plotFatPixel and multicolorFillPatterns,y; FC9B $00/$55/$AA/$FF; the AND leaves the colour only in this pixel pair sta plotColorBits ; FC9E the bits to OR into the byte lda plotKeepMask ; FCA1 the pair mask again ... eor #$FF ; FCA4 ... inverted ... sta plotKeepMask ; FCA6 ... = every bit of the byte that must survive txa ; FCA9 pixel column once more lsr a ; FCAA divide by 4 (4 multicolour pixels per byte) ... lsr a ; FCAB ... = character column 0-39 tay ; FCAC index the column tables with it clc ; FCAD clear the carry before the 16-bit add lda bitmapColLoTable,y ; FCAE low byte of 8*column (40-entry table at $9332) adc bitmapPtr ; FCB1 add it to the row address ... sta bitmapPtr ; FCB3 low byte of the bitmap byte address lda bitmapColHiTable,y ; FCB5 high byte of 8*column (0 or 1 - column 32 onwards crosses $100) adc bitmapPtrHi ; FCB8 carry on into the high byte sta bitmapPtrHi ; FCBA $B4/$B5 now point at the exact bitmap byte ldy #$00 ; FCBC read/modify/write through the pointer with no offset lda (bitmapPtr),y ; FCBE read the byte and plotKeepMask ; FCC0 clear the two bits of the addressed pixel pair ora plotColorBits ; FCC3 drop the new colour into them sta (bitmapPtr),y ; FCC6 write the byte back to the bitmap L_FCC8: ldx #$FF ; FCC8 restore the caller's fat-pixel column (operand written at $FC61) L_FCCA: ldy #$FF ; FCCA restore the caller's fat-pixel row (operand written at $FC64) rts ; FCCC back to plotFatPixel, or to its caller after the second raster line ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- multiply8x8: sta scratch18 ; FCCD $18 = multiplicand, added in on every set multiplier bit sty scratch19 ; FCCF $19 = multiplier, consumed bit by bit lda #$00 ; FCD1 A = high half of the running product sta scratch1A ; FCD3 $1A = low half of the running product ldx #$08 ; FCD5 8 multiplier bits to go L_FCD7: lsr scratch19 ; FCD7 shift the next multiplier bit into carry bcc L_FCDE ; FCD9 bit clear: nothing to add this round clc ; FCDB clear the carry before the add adc scratch18 ; FCDC bit set: add the multiplicand into the high half L_FCDE: ror a ; FCDE shift the 16-bit product right one place ... ror scratch1A ; FCDF ... carrying the bit that falls out into the low half dex ; FCE1 one round of the multiply done bne L_FCD7 ; FCE2 next multiplier bit tay ; FCE4 Y = product high byte sta scratch19 ; FCE5 $19 = product high byte lda scratch1A ; FCE7 product low byte ... sta scratch18 ; FCE9 ... into $18 as well rts ; FCEB returns A = low byte, Y = high byte, X = 0 ; ---------------------------------------------------------------------- ; 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) ; ---------------------------------------------------------------------- divideSigned16by8: stx scratch18 ; FCEC $18 = divisor sta scratch19 ; FCEE $19 = dividend low byte tya ; FCF0 dividend high byte carries the sign bpl divideUnsigned16by8 ; FCF1 non-negative: divide it as it stands (A is already the high byte) lda scratch19 ; FCF3 negative dividend: take its low byte ... eor #$FF ; FCF5 one's complement of the low byte ... clc ; FCF7 clear the carry for the +1 adc #$01 ; FCF8 ... plus one = two's complement sta scratch19 ; FCFA store the negated low byte tya ; FCFC then the high byte ... eor #$FF ; FCFD one's complement of the high byte ... adc #$00 ; FCFF ... taking the carry out of the low half (16-bit two's complement) jsr divideUnsigned16by8 ; FD01 divide the magnitude eor #$FF ; FD04 negate the quotient again ... clc ; FD06 clear the carry for the +1 adc #$01 ; FD07 ... so the sign of the result matches the dividend rts ; FD09 Y still holds the remainder of the absolute value ; ---------------------------------------------------------------------- ; 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). ; ---------------------------------------------------------------------- divideUnsigned16by8: ldx #$08 ; FD0A 8 quotient bits L_FD0C: asl scratch19 ; FD0C shift the dividend left, its top bit into carry ... rol a ; FD0E ... and on into the running remainder cmp scratch18 ; FD0F does the divisor fit into the remainder? bcc L_FD17 ; FD11 no: this quotient bit stays 0 sbc scratch18 ; FD13 yes: subtract it (C is set, so no borrow) inc scratch19 ; FD15 and set the quotient bit that was just shifted in at bit 0 L_FD17: dex ; FD17 one quotient bit done bne L_FD0C ; FD18 next quotient bit D_FD1A: tay ; FD1A Y = remainder (the label D_FD1A is an artifact of the boot loader's block move at $C200, not a data item) lda scratch19 ; FD1B A = quotient rts ; FD1D back to divideSigned16by8 or straight to its caller ; ---------------------------------------------------------------------- ; 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 ; ---------------------------------------------------------------------- addRandomJitter: sta L_FD43+1 ; FD1E patch the base value into the ADC # operand at L_FD43+1 sty scratch19 ; FD21 $19 = how many 1 bits may still be counted tya ; FD23 the limit once more beq L_FD42 ; FD24 a limit of 0 means 'no jitter at all' lda #$00 ; FD26 start the count of 1 bits at zero sta scratch18 ; FD28 $18 = number of 1 bits counted so far jsr nextScenarioRandom ; FD2A one byte from the scenario/map RNG L_FD2D: lsr a ; FD2D shift the next random bit out bcc L_FD36 ; FD2E stop at the first 0 bit - hence the geometric distribution inc scratch18 ; FD30 count this 1 bit dec scratch19 ; FD32 and use up one of the allowed counts bne L_FD2D ; FD34 keep counting until a 0 bit or the limit L_FD36: lda scratch18 ; FD36 number of consecutive 1 bits asl a ; FD38 offset = 2 * that count (so 0, 2, 4, ... cells) bit scenarioSeed1 ; FD39 bit 7 of $5B, just stirred by the RNG, picks the direction bpl L_FD42 ; FD3B clear: add the offset eor #$FF ; FD3D set: negate the offset ... clc ; FD3F clear the carry for the +1 of the negate adc #$01 ; FD40 ... so it is subtracted instead L_FD42: clc ; FD42 drop the carry the negate may have left L_FD43: adc #$FF ; FD43 add the base value (operand written at $FD1E) bpl L_FD49 ; FD45 keep it while the result is still positive lda #$00 ; FD47 a coordinate that fell below 0 is clamped to the map edge L_FD49: rts ; FD49 the callers reject anything >= 40 and try again ; ---------------------------------------------------------------------- ; 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) ; ---------------------------------------------------------------------- nextScenarioRandom: lda scenarioSeed0 ; FD4A byte 0 of the scenario seed lsr a ; FD4C shift it right ... lsr a ; FD4D ... twice sbc scenarioSeed0 ; FD4E minus the original byte, using whatever carry the shifts left (the non-linear part) lsr a ; FD50 the bit that will be fed back is now in carry rol scenarioSeed2 ; FD51 roll it into byte 2, whose top bit goes to carry rol scenarioSeed1 ; FD53 roll that into byte 1, whose top bit goes to carry ror scenarioSeed0 ; FD55 and that one into the top of byte 0 - the 24-bit register has advanced clc ; FD57 callers rely on C = 0 on return lda scenarioSeed0 ; FD58 result = byte 0 ... eor scenarioSeed1 ; FD5A ... EOR byte 1 rts ; FD5C return the random byte with C = 0 ; ---------------------------------------------------------------------- ; 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. ; ---------------------------------------------------------------------- nextGameRandom: lda gameRngState0 ; FD5D byte 0 of the game RNG state (seeded $49) lsr a ; FD5F shift it right ... lsr a ; FD60 ... twice sbc gameRngState0 ; FD61 minus the original byte, carry included lsr a ; FD63 the feedback bit lands in carry rol gameRngState2 ; FD64 roll it into byte 2 (seeded $02) rol gameRngState1 ; FD66 roll on into byte 1 (seeded $19) ror gameRngState0 ; FD68 and into the top of byte 0 clc ; FD6A callers rely on C = 0 on return lda gameRngState0 ; FD6B result = byte 0 ... eor gameRngState1 ; FD6D ... EOR byte 1 rts ; FD6F return the random byte with C = 0 ; radarDroneShapesRle - 180 bytes, $FD70-$FE23: an RLE-compressed set of eight 24x21 sprite shapes ; whose first byte ($01) is the escape marker. runMissileScreen ($7587, comcen screens overlay) ; points A/Y at it with LDA #$70 / LDY #$FD ($7592-$7594) and calls unpackRleTo0200 ($67CB) at $7596; ; the unpacker always writes exactly 512 bytes to $0200-$03FF, so the block becomes eight shapes on a ; 64-byte stride (63 sprite bytes plus one spare byte each). loadDirectionShapeSprite ($3340) copies ; $0200 + 64*shape into sprite 0 ($8A00) or sprite 5 ($8B40); on this screen the caller is ; cmdDroneMove at $5322, which passes (heading + 4) AND 7 - the heading turned 180 degrees, because a ; received command's coordinates are mirrored - to draw the opponent's drone blip. Decoded, the eight ; shapes are a small top-down aircraft glyph in the middle byte of sprite rows 7-13 (north = ; $08,$1C,$7F,$08,$08,$08,$1C), in compass order N, NE, E, SE, S, SW, W, NW. Correction: the first ; survey pass labelled this arrowGlyphTable, read the escape/count/value triples as '2-byte record ; headers' and stated that 'no static reference to this table exists anywhere in the disassembly ... ; it may be unused leftover data'. That is wrong: the block is live data, decompressed every time the ; missile/radar screen is entered. No reference appears in XREF.txt only because the address reaches ; the unpacker as two immediate operands in A/Y, never as an absolute operand. radarDroneShapesRle: .byte $01,$01,$16,$00,$08,$00,$00,$1C; FD70 ........ escape marker $01, then the compressed shape stream .byte $00,$00,$7F,$00,$00,$08,$00,$00; FD78 ........ .byte $08,$00,$00,$08,$00,$00,$1C,$01; FD80 ........ .byte $2D,$00,$11,$00,$00,$0E,$00,$00; FD88 -....... .byte $06,$00,$00,$0A,$00,$00,$51,$00; FD90 ......Q. .byte $00,$20,$00,$00,$10,$01,$2D,$00; FD98 . ....-. .byte $04,$00,$00,$04,$00,$00,$46,$00; FDA0 ......F. .byte $00,$7F,$00,$00,$46,$00,$00,$04; FDA8 ....F... .byte $00,$00,$04,$01,$2D,$00,$10,$00; FDB0 ....-... .byte $00,$20,$00,$00,$51,$00,$00,$0A; FDB8 . ..Q... .byte $00,$00,$06,$00,$00,$0E,$00,$00; FDC0 ........ .byte $11,$01,$2D,$00,$1C,$00,$00,$08; FDC8 ..-..... .byte $00,$00,$08,$00,$00,$08,$00,$00; FDD0 ........ .byte $7F,$00,$00,$1C,$00,$00,$08,$01; FDD8 ........ .byte $2D,$00,$04,$00,$00,$02,$00,$00; FDE0 -....... .byte $45,$00,$00,$28,$00,$00,$30,$00; FDE8 E..(..0. .byte $00,$38,$00,$00,$44,$01,$2D,$00; FDF0 .8..D.-. .byte $10,$00,$00,$10,$00,$00,$31,$00; FDF8 ......1. .byte $00,$7F,$00,$00,$31,$00,$00,$10; FE00 ....1... .byte $00,$00,$10,$01,$2D,$00,$44,$00; FE08 ....-.D. .byte $00,$38,$00,$00,$30,$00,$00,$28; FE10 .8..0..( .byte $00,$00,$45,$00,$00,$02,$00,$00; FE18 ..E..... .byte $04 ; FE20 . ; 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. radarDroneShapesRleEnd: .byte $01,$17,$00 ; FE21 ... $01,$17,$00 = run of 23 zeros ending the radar drone shapes ; droneScreenShapesRle - 367 bytes, $FE24-$FF92: the second RLE stream, escape marker $05. ; enterDroneScreen ($7FE6, comcen screens overlay) loads A/Y with LDA #$24 / LDY #$FE ($7FEC-$7FEE) ; and calls unpackRleTo0200 ($67CB) at $7FF0, which again expands it to exactly 512 bytes at ; $0200-$03FF = eight shapes on a 64-byte stride. The drone screen copies one of them into sprite 5 ; through loadDirectionShapeSprite ($802A when the screen is reset, $810B at launch, ; $829E on every camera step) as the heading indicator; the shapes are the big top-down drone ; silhouettes in the same compass order N, NE, E, SE, S, SW, W, NW. Correction: the survey pass called ; this aircraftShapeTable, started it three bytes early at ; $FE21 (those three bytes are the last run of the $FD70 stream, not a header), described the ; $05,$xx run tokens as record headers and judged it 'probably stale data' that 'nothing ; references'. All of that is wrong - see the note at $FD70 for why the reference does not show up in ; XREF.txt. droneScreenShapesRle: .byte $05,$05,$0A,$00,$10,$00,$00,$10; FE24 ........ escape marker $05, then the compressed silhouette stream .byte $00,$02,$38,$80,$0F,$FF,$E0,$0F; FE2C ..8..... .byte $FF,$E0,$0F,$FF,$E0,$08,$7C,$20; FE34 ......| .byte $00,$38,$00,$00,$10,$00,$00,$10; FE3C .8...... .byte $00,$00,$10,$00,$00,$38,$00,$00; FE44 .....8.. .byte $7C,$00,$00,$EE,$05,$15,$00,$40; FE4C |......@ .byte $00,$00,$F0,$00,$01,$F0,$00,$00; FE54 ........ .byte $78,$80,$00,$3F,$00,$00,$1F,$00; FE5C x..?.... .byte $00,$1F,$00,$00,$1F,$80,$00,$1F; FE64 ........ .byte $E0,$00,$21,$E0,$07,$C0,$F0,$03; FE6C ..!..... .byte $C0,$60,$00,$C0,$40,$00,$C0,$00; FE74 .`..@... .byte $00,$40,$05,$18,$00,$0F,$00,$00; FE7C .@...... .byte $07,$00,$00,$07,$80,$00,$07,$00; FE84 ........ .byte $04,$07,$00,$06,$0F,$00,$07,$1F; FE8C ........ .byte $80,$03,$FF,$E0,$07,$1F,$80,$06; FE94 ........ .byte $0F,$00,$04,$07,$00,$00,$07,$00; FE9C ........ .byte $00,$07,$80,$00,$07,$00,$00,$0F; FEA4 ........ .byte $05,$18,$00,$40,$00,$00,$C0,$00; FEAC ...@.... .byte $00,$C0,$40,$03,$C0,$60,$07,$C0; FEB4 ..@..`.. .byte $F0,$00,$21,$E0,$00,$1F,$E0,$00; FEBC ..!..... .byte $1F,$80,$00,$1F,$00,$00,$1F,$00; FEC4 ........ .byte $00,$3F,$00,$00,$78,$80 ; FECC .?..x. D_FED2: .byte $01,$F0,$00,$00,$F0,$00,$00,$40; FED2 .......@ 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 .byte $05,$15,$00,$EE,$00,$00,$7C,$00; FEDA ......|. .byte $00,$38,$00,$00,$10,$00,$00,$10; FEE2 .8...... .byte $00,$00,$10,$00,$00,$38,$00,$08; FEEA .....8.. .byte $7C,$20,$0F,$FF,$E0,$0F,$FF,$E0; FEF2 | ...... .byte $0F,$FF,$E0,$02,$38,$80,$00,$10; FEFA ....8... .byte $00,$00,$10,$05,$18,$00,$04,$00; FF02 ........ .byte $00,$06,$00,$04,$06,$00,$0C,$07; FF0A ........ .byte $80,$1E,$07,$C0,$0F,$08,$00,$0F; FF12 ........ .byte $F0,$00,$03,$F0,$00,$01,$F0,$00; FF1A ........ .byte $01,$F0,$00,$01,$F8,$00,$02,$3C; FF22 .......< .byte $00,$00,$1F,$00,$00,$1E,$00,$00; FF2A ........ .byte $04,$05,$11,$00,$01,$E0,$00,$01; FF32 ........ .byte $C0,$00,$03,$C0,$00,$01,$C0,$00; FF3A ........ .byte $01,$C0,$40,$01,$E0,$C0,$03,$F1; FF42 ..@..... .byte $C0,$0F,$FF,$80,$03,$F1,$C0,$01; FF4A ........ .byte $E0,$C0,$01,$C0,$40,$01,$C0,$00; FF52 ....@... .byte $03,$C0,$00,$01,$C0,$00,$01,$E0; FF5A ........ .byte $05,$12,$00,$04,$00,$00,$1E,$00; FF62 ........ .byte $00,$1F,$00,$02,$3C,$00,$01,$F8; FF6A ....<... .byte $00,$01,$F0,$00,$01,$F0,$00,$03; FF72 ........ .byte $F0,$00,$0F,$F0,$00,$0F,$08,$00; FF7A ........ .byte $1E,$07,$C0,$0C,$07,$80,$04,$06; FF82 ........ .byte $00,$00,$06,$00,$00,$04 ; FF8A ...... ; $FF90-$FF92 is not a block of its own. It is the last token of the compressed stream that starts ; at $FE24: escape marker $05, count $0E, value $00 - the 14 zero bytes that pad the eighth shape out ; to its 64-byte slot and bring the unpacker's output to exactly 512 bytes. Decoding the stream byte ; by byte confirms it: the run that begins here is consumed at $FF92, and the very next byte, $FF93, ; is the first byte of droneViewfinderSprite, the 63-byte uncompressed reticle that launchDrone copies ; into sprite 2 at $80EB. So nothing at the top of this block is spare: ; $FD70-$FFD1 is two compressed sprite sets and one raw sprite, back to back, ending exactly at ; $FFD1 just below the CPU vectors. ; Correction: the survey pass labelled $FF90 unknownShapeTail, gave it 66 bytes and called it 'most ; likely unused leftover memory ... stale bytes that resemble the end of the $8800 message block'. It ; is neither unused nor related to $8800 - that block's tail ($897F-$89FF) is an unrelated ; $00/$FF dither pattern and does not match these bytes. droneScreenShapesRleEnd: .byte $05,$0E,$00 ; FF90 ... $05,$0E,$00 = run of 14 zeros ending the drone silhouettes ; 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. droneViewfinderSprite: ; drone camera viewfinder, 21 rows of 24 pixels .byte $00,$08,$00 ; FF93 ............#........... .byte $00,$00,$00 ; FF96 ........................ .byte $0F,$08,$78 ; FF99 ....####....#....####... .byte $08,$00,$08 ; FF9C ....#...............#... .byte $08,$08,$08 ; FF9F ....#.......#.......#... .byte $08,$00,$08 ; FFA2 ....#...............#... .byte $00,$00,$00 ; FFA5 ........................ .byte $00,$00,$00 ; FFA8 ........................ .byte $00,$00,$00 ; FFAB ........................ .byte $00,$00,$00 ; FFAE ........................ .byte $49,$00,$49 ; FFB1 .#..#..#.........#..#..# .byte $00,$00,$00 ; FFB4 ........................ .byte $00,$00,$00 ; FFB7 ........................ .byte $00,$00,$00 ; FFBA ........................ .byte $00,$00,$00 ; FFBD ........................ .byte $08,$00,$08 ; FFC0 ....#...............#... .byte $08,$08,$08 ; FFC3 ....#.......#.......#... .byte $08,$00,$08 ; FFC6 ....#...............#... .byte $0F,$08,$78 ; FFC9 ....####....#....####... .byte $00,$00,$00 ; FFCC ........................ .byte $00,$08,$00 ; FFCF ............#...........