; ============================================================================
; $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  ............#...........

