; ============================================================================
; $6F00-$87FF - comcen missile and drone screens (track 33 sectors 0-16 + track 32 sectors 8-15)
; ============================================================================
; The variant the boot loader installs; $1086 reloads it. Implements the two command centre screens
; reached with the function keys: the missile screen ($7587) and the drone screen ($7FE6), including the
; radar rendering, plus the end-of-game result messages.

        .setcpu "6502"
        .include "c64.inc"
        .include "zeropage.inc"

; ---- references to code/data outside this file ----
switchToRequestedScreen  := $06BA
unitGlyphBaseTable       := $0799
filmPlaybackMode         := $0B9B
playerSide               := $0B9F
gameTypeOptions          := $0BA3
gameEndReason            := $0BA8
checkFirePressed         := $0EB1
checkFirePressedInteractive := $0ED5
joystickDirectionTable   := $1FA6
diagonalDirectionTable   := $1FB2
directionDeltaTable      := $20F0
getViewportOriginPointer := $25C7
readMapCell              := $25FA
drawMainWindowFrame      := $28E1
printOptionWord          := $2A1A
useDrawColourFlag        := $2A29
drawColourByte           := $2A2A
blockFillFlag            := $2A2B
blockFillChar            := $2A2C
windowOriginRow          := $2A2D
windowOriginCol          := $2A2E
setBitmapDrawMode        := $2A38
setColourDrawMode        := $2A47
resetDrawModes           := $2A56
fillScreenColourRect     := $2A64
calcCellPointersXY       := $2AA3
drawCharAt               := $2AC3
drawCharBlock            := $2B18
drawWindowInnerCorners   := $2B75
drawTallGlyph            := $2BB7
xorColourBlock2x2        := $2BCE
drawWindowFrame          := $2C7F
drawFrameBox             := $2CA1
setTerrainTilePtr        := $2DBD
magnifyWindow2x          := $2E0F
clearSpriteShape         := $2FB2
drawLineInPathSprite     := $306C
plotPathSpritePixel      := $30A1
enableSprites            := $31A3
drawCircleInSprite       := $3239
loadDirectionShapeSprite := $3340
showComcenCrossMarker    := $3383
gridCellToSpriteCoords   := $33B1
getUnitSpriteCentreOnGrid := $340B
updateExplosionMarker    := $341F
drawComcenRingSprite     := $3456
squaresTable             := $3860
applyHitToUnit           := $3B22
animateRingBurst         := $3CF2
haltStunnedUnit          := $3FAC
runGameFrames            := $4094
updateGameFrame          := $40B2
updateComcenCloakLock    := $5151
queueCmdBytePending      := $55EC
queueCmd3                := $560F
drawScreen1Cell          := $5D7F
unpackRleTo0200          := $67CB
fetchRleByte             := $681A
D_681B                   := $681B
D_681C                   := $681C
sprite0Shape             := $8A00
sprite2Shape             := $8A80
sprite3Shape             := $8AC0
vicBorderShadow          := $9003
vicBg0Shadow             := $9004
spriteColourShadow       := $9008
sprite2ColourShadow      := $900A
sprite3ColourShadow      := $900B
sprite5ColourShadow      := $900D
spriteXShadow            := $9010
sprite1XShadow           := $9011
sprite2XShadow           := $9012
sprite3XShadow           := $9013
sprite5XShadow           := $9015
spriteYShadow            := $9018
sprite1YShadow           := $9019
sprite2YShadow           := $901A
sprite3YShadow           := $901B
sprite5YShadow           := $901D
spriteEnableShadow       := $9022
drawCellSaveCol          := $90F6
drawCellSaveRow          := $90F7
currentScreen            := $90FB
joyRepeatMask            := $91C9
joyHoldCounter           := $91CA
activeSpriteIndex        := $91CC
spritePlotMode           := $91F3
droneHeading             := $920B
droneControlFlags        := $920D
exchangeAppliedFlag      := $920E
radarGhostCountdown      := $920F
radarNoiseMasks          := $9210
droneFuel                := $9220
frameWidth               := $9221
frameHeight              := $9222
droneRadarOwnColour      := $9224
droneRadarEnemyColour    := $9225
cellBackgroundColour     := $9226
comcenStunStatus         := $922A
radarHalfToneFlag        := $922D
radarMissilesLeft        := $9232
radarDronesLeft          := $9233
viewRedrawTimer          := $9234
comcenUnit               := $9236
radarOwnUnitColour       := $9237
radarEnemyUnitColour     := $9238
radarTerrainColour       := $9239
attackerType             := $923D
comcenBattleStatus       := $9242
comcenRadarStatus        := $9243
comcenDroneStatus        := $9244
comcenCloakedFlag        := $9245
rleMarkerByte            := $9246
rleUnpackFlag            := $9247
recyclerMode             := $92A6
terrainPointsBySide      := $92AC
dronesLeft               := $92AE
opponentDronesLeft       := $92AF
missilesLeft             := $92B0
opponentMissilesLeft     := $92B1
comcenSpeedClass         := $92B4
opponentComcenSpeedClass := $92B5
savedLineState           := $92CA
droneViewOriginCol       := $92F6
droneViewOriginRow       := $92F7
missileFlightTick        := $92F8
droneArrivalFlags        := $92F9
radarSweepTick           := $92FB
bitmapRowLoTableFrom3    := $9303
bitmapRowLoTableFrom6    := $9306
bitmapRowHiTableFrom3    := $931C
bitmapRowHiTableFrom6    := $931F
bitmapColLoTableFrom3    := $9335
bitmapColLoTableFrom13   := $933F
bitmapColHiTableFrom3    := $935D
bitmapColHiTableFrom13   := $9367
screenRowLoTable         := $9382
screenRowLoTableFrom3    := $9385
screenRowLoTableFrom6    := $9388
screenRowHiTable         := $939B
screenRowHiTableFrom3    := $939E
screenRowHiTableFrom6    := $93A1
updateCursorPointers     := $C099
setFullScreenWindow      := $C0D7
bootReceiveSectorData    := $C0EF
printString              := $C133
printDecimal             := $C17D
splitDecimalDigits       := $C428
decimalHundreds          := $C44E
decimalTens              := $C44F
decimalOnes              := $C450
queueMessage             := $C566
redrawStatusMessage      := $C5D7
playSound                := $C89A
unitColTable             := $F640
unitRowTable             := $F6A4
unitFlagsTable           := $F708
unitTypeTable            := $F76C
unitPathColTable         := $F898
unitStunTable            := $F960
unitEnergyTable          := $F9C4
unitTerrainUnderTable    := $FAF0
initLineStepper          := $FBB8
stepLine                 := $FBFA
plotFatPixel             := $FC58
nextGameRandom           := $FD5D
droneViewfinderSprite    := $FF93

; Contents
; --------
;    $6F00  drawDroneHeadingPanel        Draws the compass / heading dial in the top-left corner of the DRONE screen
;                                        (game screen 2): frame window $0D (columns 1-9, rows 3-11), the 7x7 character picture at $97D1 at row 4 column 2, the
;                                        5x5 picture at $97B6 at row 5 column 3, and a 3x3 screen-RAM colour fill of $6E (blue %01 pixels over light-blue %10
;                                        pixels) at row 6 column 4.
;    $6F36  drawDroneFuelPanel           Draws the fuel gauge panel in the bottom-left corner of the DRONE screen: frame
;                                        window $0E (columns 1-9, rows 12-20), the same 7x7 picture as the compass ($97D1) at row 13 column 2 but forced to
;                                        colour $BC (dark grey over medium grey), then the three caption characters $57,$58,$59 at row 17 columns 4-6 in colour
;                                        $0C (black over medium grey).
;    $6F89  drawDroneRadarFrame          Draws the double frame around the drone camera window and clears the window:
;                                        builds a fresh set of radar noise masks, draws window $0F (left 11, top 4, 18x16) and window $10 (left 12, top 5,
;                                        16x14), then falls through into clearRadarWindowBlack which blanks the 14x12 camera area and re-centres the view on
;                                        the player's own comcen.
;    $6F99  drawDroneFuelDigits          Prints droneFuel ($9220) as three tall (2-row) digits at rows 15-16, columns
;                                        4-6 of the fuel panel.
;    $6FF5  drawDroneCountPanel          Draws the 'DRONE' counter panel at the top right of the DRONE screen: resets
;                                        the text window to the full screen, draws window $11 (left 29, top 3, 11x7) with the four inner corner marks, window
;                                        $12 (left 32, top 4, 5x4), fills the 3x2 colour box for the digit at row 5 column 33 with $30, and prints the caption
;                                        'DRONE' ($868C) at row 7 column 32 in colour $CF with the bitmap draw mode set to OR.
;    $7036  drawDroneCountDigit          Draws the number of drones still available (radarDronesLeft $9233) as a single
;                                        tall digit at rows 5-6, column 34, in the fixed colour $30.
;    $7061  blinkDroneCountBox           Alternates the background of the drones-left box (3x2 colour cells at row 5,
;                                        column 33) between $30 and its nibble-swapped twin $03 according to bit 0 of the frame counter, which swaps the
;                                        foreground and background of the tall digit sitting in it and makes the counter flash.
;    $708D  drawLaunchStrikeButtons      Draws the two-button panel down the right edge of the DRONE screen: frame
;                                        window $13 (left 30, top 10, 10 wide x 11 rows) with inner corner marks, the 2x2 button icon from $9804 (characters
;                                        $45-$48) at row 12 column 34 painted colour $05, the caption 'LAUNCH' ($8691) at row 14 column 32, the same icon again
;                                        at row 16 column 34, and the caption 'STRIKE' ($8697) at row 18 column 32.
;    $70F5  buildDroneScreen             Paints the whole DRONE screen (game screen 2) from scratch: clears the glyph
;                                        plotter's parameter block, sets border and background shadows to $0C (medium grey), sets the radar cell background
;                                        nibble to $0A (light red) and marks the screen as built, then draws every panel in turn and finishes by redrawing the
;                                        status line through the $C000 overlay.
;    $7135  clearRadarWindowGrey         Blanks the 14x12 drone camera window (row 6, column 13) to colour $BB (dark
;                                        grey on dark grey, i.e.
;    $713C  clearRadarWindowBlack        Same as clearRadarWindowGrey but with colour 0: blanks the drone camera window
;                                        to black and re-centres the view on the own comcen.
;    $7163  playDroneLostAnimation       The 'drone is gone' effect, used when the drone is shot down, detonated by its
;                                        own strike or runs out of fuel.
;    $7204  flashRadarAndWipe            Paints the whole 14x12 drone camera window white for two game frames, then
;                                        clears radarWipeColour and falls into drawRadarIrisWipe, so the iris closes over the white flash with no pauses.
;    $7221  drawRadarIrisWipe            Closes (or opens) an iris over the drone camera window: it first draws window
;                                        $16 (the camera area itself, left 13, top 6, 14x12) and then repeatedly draws a rectangular frame out of the all-$03
;                                        solid tile set at $9848, starting 12x12 cells at row 6 column 14 and shrinking two cells and moving in one cell each
;                                        pass until the height drops below 3.
;    $727A  buildRadarNoiseMasks         Fills the 16-byte table radarNoiseMasks ($9210-$921F) with $FF and then knocks
;                                        four randomly chosen bits out of each byte.
;    $72A1  applyDroneBlast              Resolves a drone detonation: damages every unit standing in the 2x2 block of
;                                        map cells whose top-left corner is (cmdParam1, cmdParam2) - (x,y), (x+1,y), (x+1,y+1) and (x,y+1) - by calling
;                                        applyDroneBlastToCell four times.
;    $72B3  applyDroneBlastToCell        Applies the drone blast to the single map cell (cmdParam1, cmdParam2).
;    $731E  steerDrone                   Advances the locally flown drone by one map cell.
;    $7396  drawRadarGlyph               Plots one 8x8 map glyph into the bitmap cell pointed at by bitmapPtr
;                                        (zp_B4/zp_B5) and writes its colour through screenPtr (zp_B6/zp_B7) at offset Y.
;    $741C  drawDroneRadarWindow         Redraws the whole 14x12 drone camera window (character rows 6-17, columns
;                                        13-26, one character per map cell) from the map around the view origin zp_A5/zp_A6.
;    $74C3  getRadarCellGlyph            Turns one map cell byte into the glyph index and colour that drawRadarGlyph
;                                        needs.
;    $7552  playStrikeZoomAnimation      Drone strike effect on the DRONE screen: magnifies the middle of the
;    $7587  runMissileScreen             Entry of game screen 1, the comcen RADAR / MISSILE screen (function key F3).
;    $759C  missileScreenResume          Main loop of the missile screen, and the re-entry point used after the
;    $7688  refreshMissileScreen         Redraw hook of screen 1, run once when the screen is built and again after
;    $76B2  moveRadarCursorByDirection   Turns a joystick direction code 0-7 into a (dCol,dRow) pair and hands
;    $76EC  offsetRadarCursor            Moves the missile target cursor by (X,Y) grid cells, refusing the move if it
;    $7713  animateMissileFlight         Flies the launched missile across the radar window: builds a short slanted
;    $7847  plotMissileTrailSegment      Draws one 4-pixel line segment into the shape of sprite 3, from
;    $785B  stepAndPlotTrailPixel        Advances the line stepper one cell and sets the resulting pixel in sprite
;    $7861  saveLineStepperState         Copies the 12-byte line stepper working set $8C-$97 (both error
;    $786D  restoreLineStepperState      Copies savedLineState ($92CA-$92D5) back over the 12-byte line stepper
;    $7879  rebasePairToMin              Subtracts the smaller of A and Y from both, i.e.
;    $7894  updateRadarSweep             Per-frame driver of the radar sweep on screen 1.
;    $78CB  restartRadarSweep            Starts a new sweep at grid row 0 and immediately: clears the step counter, the
;    $78E7  paintRadarSweepRow           Rewrites the 21 video-matrix bytes of one radar grid row (screen row A+3,
;    $7926  redrawEnemyUnitsOnRadar      Repaints all 50 units of the opposing side on the radar window and gives
;    $7959  scrollRadarOnComcenMove      Entered by JMP from drawScreen1Cell ($5D8A) whenever the cell being
;    $7987  isRadarCursorOnComcen        Tests whether the missile target cursor is on the comcen's own cell, which
;    $7992  radarCursorToMapCoords       Converts the radar grid position of the target cursor into absolute map
;    $79A1  checkStrikeHitsDrone         Run straight after a missile has been launched: if a drone is airborne and
;    $79D8  showDroneKillBurst           Plays the 'drone shot down' effect on the radar screen once our $92 command
;    $7A13  mapCellToRadarGrid           Converts absolute map coordinates into radar grid coordinates relative to the
;    $7A2B  isRadarGridCellInRange       Range check for a radar grid cell: the window is 21 columns by 17 rows,
;    $7A32  initRadarScreen              Builds the whole RADAR / MISSILE screen (game screen 1): resets the glyph
;    $7A6B  redrawRadarMapWindow         Draws the double frame around the radar window and then the map inside it.
;    $7A78  drawMissilePanel             Draws the MISSILES panel in the bottom right corner: an outer frame with inner
;    $7ABA  drawMissileCount             Shows the number of missiles left as two tall (two-character-high) digits in
;    $7AEC  blinkMissileCount            Swaps the two colour nibbles of the missile counter box, which exchanges the
;    $7B05  drawFireButton               Draws the FIRE button panel on the right: a framed box with inner corner marks,
;    $7B44  drawDroneAlertPanel          Draws the three nested frames of the DRONE ALERT panel in the top right
;    $7B59  resetDroneAlertPanel         Prints the two-line 'DRONE' / 'ALERT' label into the panel and paints its
;    $7B86  flashDroneAlertPanel         Toggles the DRONE ALERT panel between its two colour states and repaints the
;    $7B91  fillDroneAlertPanel          Shared tail of resetDroneAlertPanel and flashDroneAlertPanel: fills the 7 x 4
;    $7BA2  drawRadarMap                 Redraws the whole 21 x 17 cell strategic map of screen 1 (the RADAR/MISSILE
;                                        screen)
;    $7C5C  getComcenCentredViewOrigin   Returns the map origin that puts the player's own comcen in the middle
;    $7FE6  enterDroneScreen             Entry point of game screen 2, the DRONE screen (F5).
;    $7FF6  resetDroneScreen             (Re)initialises the drone screen's sprites and read-outs and drops into the
;                                        idle
;    $803D  droneScreenIdleLoop          Main loop of the DRONE screen while nothing is flying.
;    $808D  launchDrone                  Sends a drone on its way: latches the joystick heading, queues command $97
;                                        (drone
;    $8124  droneFlightLoop              Per-frame loop while our drone is in the air.
;    $8182  strikeWithDrone              Fire pressed while the drone is flying: stop steering it, mark a detonation as
;    $81D0  endDroneFlight               Common tail of every drone flight: explosion sound, empty the tank, clear the
;    $81FE  getDroneMapPosition          Converts the camera origin into the drone's own map cell: the 14 x 12 window
;    $820F  showDroneAlert               Notification that the opponent has launched a drone: plays the alert sound and,
;                                        in
;    $821F  fillDroneViewWindow          Fills the drone camera window (14 columns x 12 rows starting at character row
;    $8230  releaseDroneControl          Gives an airborne drone back to the engine: clears the 'airborne' and 'locally
;    $823F  drawDroneHeadingIndicator    Redraws the compass pointer of the drone screen's left panel.
;    $8279  advanceDroneCamera           One drone step on the DRONE screen, run once per lock-step command exchange.
;    $82A4  readDroneHeadingFromJoystick Latches the joystick direction as the drone's new steer direction and
;    $82BF  drawShotLineInViewport       Draws the tracer of one shot inside the 7 x 5 cell zoomed battlefield
;    $840F  rebaseLineCoordsToMin        Subtracts the smallest of the three column values (window left $82B5,
;    $84B2  scaleLineCoordsToFatPixels   Converts the two line endpoints from map cells to fat pixels: row*8+4
;    $84F1  plotDiamondMarker            Plots the four colour-1 fat pixels at (X-1,Y-1), (X+1,Y-1), (X-1,Y+1) and
;    $8518  drawStatsTab                 Fills in the numbers of the console's STATS panel.
;    $8601  printNumberRightAligned      prints the unsigned byte in A as decimal, right-aligned in a three-character
;                                        field that starts at text column X of the current text row.
;    $862D  setTextColumn                moves the text cursor to column A of the current row and recomputes the bitmap
;                                        and screen-RAM pointers for that character cell by tail-calling updateCursorPointers ($C099), which leaves X and Y
;                                        untouched.
;    $8632  drawComcenSchematic          unpacks the RLE-compressed comcen picture at comcenSchematicRle ($7C6E)
;                                        straight into the hires bitmap for the console REPAIR tab: a 20x10 character (160x80 pixel) side view of the tracked
;                                        comcen vehicle with its dish and antenna, drawn at character row 10, column 13.

        .org $6F00


; ----------------------------------------------------------------------
; drawDroneHeadingPanel - Draws the compass / heading dial in the top-left corner of the DRONE screen
; (game screen 2): frame window $0D (columns 1-9, rows 3-11), the 7x7 character picture at $97D1 at
; row 4 column 2, the 5x5 picture at $97B6 at row 5 column 3, and a 3x3 screen-RAM colour fill of $6E
; (blue %01 pixels over light-blue %10 pixels) at row 6 column 4. Sprites 3 and 0 (the heading line
; plus its white shadow) and sprite 5 (the red aircraft silhouette) are parked over this panel by the
; screen-2 entry code, so the panel is only the dial they sit in.
; In:  nothing; expects the glyph-plotter parameter block $2A29-$2A2C to be all zero (buildDroneScreen
;     clears it just before)
; Out: frame, two character pictures and 6 colour cells drawn; D_2A2D = 6 and D_2A2E = 4 left pointing
;     at the colour patch; frameWidth/frameHeight ($9221/$9222) left at 5x5 by the last drawCharBlock
; Called from: buildDroneScreen ($7122) - the only caller in this overlay variant
; ----------------------------------------------------------------------
drawDroneHeadingPanel:
        ldx     #$0D                    ; 6F00  window $0D = left 1, top 3, 9x9 - the compass panel outline
        jsr     drawWindowFrame         ; 6F02  draw the panel frame from that window's 9-tile set
        lda     #$04                    ; 6F05  character-block origin row 4 (one row inside the frame top)
        sta     windowOriginRow         ; 6F07  D_2A2D is the glyph plotters' window row
        lda     #$02                    ; 6F0A  character-block origin column 2 (one column inside the frame left)
        sta     windowOriginCol         ; 6F0C  D_2A2E is the glyph plotters' window column
        lda     #$D1                    ; 6F0F  low byte of the picture block at $97D1 (header 7,7 then 49 character codes)
        ldy     #$97                    ; 6F11  high byte -> $97D1: the 7x7 outer dial face
        jsr     drawCharBlock           ; 6F13  stamp it over rows 4-10, columns 2-8
        lda     #$05                    ; 6F16  next block starts one row lower, row 5
        sta     windowOriginRow         ; 6F18  set the window row for the inner picture
        lda     #$03                    ; 6F1B  and one column further right, column 3
        sta     windowOriginCol         ; 6F1D  set the window column
        lda     #$B6                    ; 6F20  low byte of the picture block at $97B6 (header 5,5 then 25 character codes)
        ldy     #$97                    ; 6F22  high byte -> $97B6: the 5x5 inner dial face
        jsr     drawCharBlock           ; 6F24  stamp it over rows 5-9, columns 3-7
        inc     windowOriginRow         ; 6F27  step the fill origin down to row 6
L_6F2A:
        inc     windowOriginCol         ; 6F2A  and right to column 4 - the 3x3 centre of the dial (label L_6F2A is a variant-A branch target only)
        lda     #$6E                    ; 6F2D  screen-RAM colour byte $6E: %01 pixels blue, %10 pixels light blue
        ldx     #$03                    ; 6F2F  3 columns wide
        ldy     #$03                    ; 6F31  3 rows high
        jmp     fillScreenColourRect    ; 6F33  paint rows 6-8, columns 4-6 and return through fillScreenColourRect


; ----------------------------------------------------------------------
; drawDroneFuelPanel - Draws the fuel gauge panel in the bottom-left corner of the DRONE screen: frame
; window $0E (columns 1-9, rows 12-20), the same 7x7 picture as the compass ($97D1) at row 13 column 2
; but forced to colour $BC (dark grey over medium grey), then the three caption characters $57,$58,$59
; at row 17 columns 4-6 in colour $0C (black over medium grey). drawDroneFuelDigits writes the three
; tall digits into rows 15-16 of the same panel.
; In:  nothing
; Out: frame, picture and caption drawn; the fixed-colour override textDrawParams ($2A29) and D_2A2A
;     are cleared again on exit; D_2A2B = 0 (block-fill off); zp_18 = 17, zp_19 = 6
; Called from: buildDroneScreen ($7125) only
; ----------------------------------------------------------------------
drawDroneFuelPanel:
        ldx     #$0E                    ; 6F36  window $0E = left 1, top 12, 9x9 - the fuel panel outline
        jsr     drawWindowFrame         ; 6F38  draw its frame
        lda     #$FF                    ; 6F3B  non-zero = 'use the fixed colour in D_2A2A' instead of the per-character colour table $93B4
        sta     useDrawColourFlag       ; 6F3D  textDrawParams ($2A29) is the plotter's useFixedColour flag
        lda     #$BC                    ; 6F40  screen-RAM colour $BC: %01 pixels dark grey, %10 pixels medium grey
        sta     drawColourByte          ; 6F42  D_2A2A is the fixed colour byte used by every glyph plotted from now on
        lda     #$0D                    ; 6F45  character-block origin row 13
        sta     windowOriginRow         ; 6F47  set the window row
        lda     #$02                    ; 6F4A  character-block origin column 2
        sta     windowOriginCol         ; 6F4C  set the window column
        lda     #$D1                    ; 6F4F  low byte of the 7x7 picture at $97D1
        ldy     #$97                    ; 6F51  high byte -> $97D1, the same dial art reused as the gauge body
        jsr     drawCharBlock           ; 6F53  stamp it over rows 13-19, columns 2-8
        lda     #$00                    ; 6F56  0 = do not replace the block characters with a fill character
        sta     blockFillFlag           ; 6F58  D_2A2B is drawCharBlock's block-fill flag
        lda     #$FF                    ; 6F5B  keep the fixed-colour override on for the caption too
        sta     useDrawColourFlag       ; 6F5D  textDrawParams = $FF
        lda     #$0C                    ; 6F60  screen-RAM colour $0C: %01 pixels black, %10 pixels medium grey
        sta     drawColourByte          ; 6F62  fixed colour for the caption characters
        lda     #$11                    ; 6F65  caption row 17 (two rows below the digits at rows 15-16)
        sta     scratch18               ; 6F67  zp_18 is drawCharAt's cell row
        lda     #$04                    ; 6F69  caption starts at column 4, under the leftmost digit
        sta     scratch19               ; 6F6B  zp_19 is drawCharAt's cell column
        lda     #$57                    ; 6F6D  caption glyph $57 (first third of the 3-character 'fuel' legend)
        jsr     drawCharAt              ; 6F6F  plot it at row 17, column 4
        inc     scratch19               ; 6F72  advance to column 5
        lda     #$58                    ; 6F74  caption glyph $58
        jsr     drawCharAt              ; 6F76  plot it at row 17, column 5
        inc     scratch19               ; 6F79  advance to column 6
        lda     #$59                    ; 6F7B  caption glyph $59
        jsr     drawCharAt              ; 6F7D  plot it at row 17, column 6
        lda     #$00                    ; 6F80  release the colour override again...
        sta     useDrawColourFlag       ; 6F82  ...so later text uses the per-character colour table
        sta     drawColourByte          ; 6F85  and clear the fixed colour byte
        rts                             ; 6F88  done


; ----------------------------------------------------------------------
; drawDroneRadarFrame - Draws the double frame around the drone camera window and clears the window:
; builds a fresh set of radar noise masks, draws window $0F (left 11, top 4, 18x16) and window $10
; (left 12, top 5, 16x14), then falls through into clearRadarWindowBlack which blanks the 14x12 camera
; area and re-centres the view on the player's own comcen.
; In:  comcenUnit ($9236), unit column/row arrays
; Out: two frames drawn; radarNoiseMasks ($9210-$921F) re-randomised; camera area filled with colour
;     0; radarGhostCountdown ($920F) = 0; viewOriginCol/Row (zp_A5/zp_A6) = comcen - (6,5);
;     radarWipeColour ($7134) = 0
; Called from: buildDroneScreen ($712B) only
; ----------------------------------------------------------------------
drawDroneRadarFrame:
        jsr     buildRadarNoiseMasks    ; 6F89  re-roll the 16 noise masks used for unspotted enemy blips
        ldx     #$0F                    ; 6F8C  window $0F = left 11, top 4, 18 wide x 16 rows - outer camera bezel
L_6F8E:
        jsr     drawWindowFrame         ; 6F8E  draw it (label L_6F8E is a variant-A branch target only)

; ----------------------------------------------------------------------
; The label at $6F91 is a variant-A entry point (serviceLinkWhileGenerating) that XREF merges into
; this file; in overlay variant B it is just the next instruction of drawDroneRadarFrame.
; ----------------------------------------------------------------------
drawRadarInnerBezel:
        ldx     #$10                    ; 6F91  window $10 = left 12, top 5, 16 wide x 14 rows - inner camera bezel
        jsr     drawWindowFrame         ; 6F93  draw it
        jmp     clearRadarWindowBlack   ; 6F96  blank the camera area and centre the view on the own comcen; returns from there


; ----------------------------------------------------------------------
; drawDroneFuelDigits - Prints droneFuel ($9220) as three tall (2-row) digits at rows 15-16, columns
; 4-6 of the fuel panel. splitDecimalDigits splits the value into hundreds ($C44E), tens ($C44F) and
; ones ($C450) and drawTallGlyph draws each digit as a pair of characters from the glyph-pair table at
; $9810. The colour is a fuel warning: $30 (cyan over black) above 20, $B7 (dark grey over yellow) for
; 11-20, $21 (red over white) for 10 or less; below 21 the two nibbles are swapped on even fuel
; values, so the read-out flashes as it counts down towards empty.
; In:  droneFuel ($9220), 0-100
; Out: six characters drawn; $C44E-$C450 = the decimal digits; textDrawParams/D_2A2A cleared on exit;
;     zp_18 = 15, zp_19 = 6, zp_1A used as the nibble-rotate carry buffer
; Called from: buildDroneScreen ($7128), playDroneLostAnimation ($71FC, fuel forced to 0), launchDrone
;     ($80B5, fuel set to 100), endDroneFlight ($81DA) and advanceDroneCamera ($82A1, once per
;     exchange)
; ----------------------------------------------------------------------
drawDroneFuelDigits:
        lda     droneFuel               ; 6F99  current fuel, counted down one per command exchange
        jsr     splitDecimalDigits      ; 6F9C  split it into hundreds/tens/ones at $C44E/$C44F/$C450
        lda     #$0F                    ; 6F9F  digits go on row 15 (drawTallGlyph also uses row 16)
        sta     scratch18               ; 6FA1  zp_18 = cell row
        lda     #$04                    ; 6FA3  leftmost digit column 4
        sta     scratch19               ; 6FA5  zp_19 = cell column
        lda     #$FF                    ; 6FA7  force the fixed draw colour instead of the per-character table
        sta     useDrawColourFlag       ; 6FA9  textDrawParams = $FF
        ldx     #$30                    ; 6FAC  default colour $30: %01 cyan, %10 black - plenty of fuel
        lda     droneFuel               ; 6FAE  reload the fuel level for the range tests
        cmp     #$15                    ; 6FB1  21 or more?
        bcs     L_6FD3                  ; 6FB3  yes - keep $30 and skip the flashing logic entirely
        ldx     #$B7                    ; 6FB5  warning colour $B7: %01 dark grey, %10 yellow
        cmp     #$0B                    ; 6FB7  11 or more?
        bcs     L_6FBD                  ; 6FB9  yes - keep $B7
        ldx     #$21                    ; 6FBB  critical colour $21: %01 red, %10 white
L_6FBD:
        and     #$01                    ; 6FBD  test the fuel level's parity (A still holds the fuel)
        bne     L_6FD3                  ; 6FBF  odd exchange - draw in the plain colour
        sta     scratch1A               ; 6FC1  even exchange: A is 0 here, so this clears the rotate buffer
        txa                             ; 6FC3  take the colour byte into A for the nibble swap
L_6FC4:
        asl     a                       ; 6FC4  shift the colour left one bit...
        rol     scratch1A               ; 6FC5  ...catching the bit that falls out in zp_1A (rotate left through 8 bits)
        asl     a                       ; 6FC7  bit 2 of 4
        rol     scratch1A               ; 6FC8  collect it
        asl     a                       ; 6FCA  bit 3 of 4
L_6FCB:
        rol     scratch1A               ; 6FCB  collect it
        asl     a                       ; 6FCD  bit 4 of 4 - the byte has now rotated left by four
        rol     scratch1A               ; 6FCE  collect the last carried bit
        ora     scratch1A               ; 6FD0  merge the two halves: the nibbles are now swapped
L_6FD2:
        tax                             ; 6FD2  back into X as the colour byte
L_6FD3:
        stx     drawColourByte          ; 6FD3  install the chosen colour as the plotter's fixed colour
        lda     decimalHundreds         ; 6FD6  hundreds digit (1 only when fuel is 100)
        jsr     drawTallGlyph           ; 6FD9  draw it as a 2-row glyph pair at row 15/16, column 4
        inc     scratch19               ; 6FDC  move to column 5
L_6FDE:
        lda     decimalTens             ; 6FDE  tens digit
        jsr     drawTallGlyph           ; 6FE1  draw it at column 5
        inc     scratch19               ; 6FE4  move to column 6
        lda     decimalOnes             ; 6FE6  ones digit
        jsr     drawTallGlyph           ; 6FE9  draw it at column 6
        lda     #$00                    ; 6FEC  restore the normal colour handling...
        sta     useDrawColourFlag       ; 6FEE  ...clear the fixed-colour flag...
        sta     drawColourByte          ; 6FF1  ...and the fixed colour byte
        rts                             ; 6FF4  done


; ----------------------------------------------------------------------
; drawDroneCountPanel - Draws the 'DRONE' counter panel at the top right of the DRONE screen: resets
; the text window to the full screen, draws window $11 (left 29, top 3, 11x7) with the four inner
; corner marks, window $12 (left 32, top 4, 5x4), fills the 3x2 colour box for the digit at row 5
; column 33 with $30, and prints the caption 'DRONE' ($868C) at row 7 column 32 in colour $CF with the
; bitmap draw mode set to OR.
; In:  nothing
; Out: frames, colour box and caption drawn; textColour zp_3D = $CF, textCursorCol zp_3F = 32,
;     textCursorRow zp_40 = 7; both draw modes reset to replace on exit
; Called from: buildDroneScreen ($7119) only
; ----------------------------------------------------------------------
drawDroneCountPanel:
        jsr     setFullScreenWindow     ; 6FF5  text window = whole 40x25 screen so the absolute column below is not shifted
        ldx     #$11                    ; 6FF8  window $11 = left 29, top 3, 11 wide x 7 rows - outer counter panel
        jsr     drawWindowFrame         ; 6FFA  draw its frame
        jsr     drawWindowInnerCorners  ; 6FFD  add character $38 just inside the four corners of that frame
        ldx     #$12                    ; 7000  window $12 = left 32, top 4, 5 wide x 4 rows - the box around the digit
        jsr     drawWindowFrame         ; 7002  draw its frame
        lda     #$05                    ; 7005  colour fill origin row 5
        sta     windowOriginRow         ; 7007  D_2A2D = window row
        lda     #$21                    ; 700A  colour fill origin column 33
        sta     windowOriginCol         ; 700C  D_2A2E = window column
        lda     #$30                    ; 700F  screen-RAM colour $30: %01 cyan, %10 black - the digit's background
        ldx     #$03                    ; 7011  3 columns
        ldy     #$02                    ; 7013  2 rows (the tall digit is 2 cells high)
        jsr     fillScreenColourRect    ; 7015  paint rows 5-6, columns 33-35
        lda     #$CF                    ; 7018  colour RAM value for the caption ($F = light grey for the %11 pixels)
        sta     textColour              ; 701A  zp_3D is the colour the $C000 text plotter writes per cell
printDroneCaption:
        ldy     #$01                    ; 701C  bitmap draw mode 1 = OR the glyph into the existing bitmap (label is a variant-A entry point only)
        jsr     setBitmapDrawMode       ; 701E  patch the plotter's read instruction accordingly
        lda     #$20                    ; 7021  caption column 32
        sta     textCursorCol           ; 7023  zp_3F = cursor column, relative to the window left edge (0 here)
        lda     #$07                    ; 7025  caption row 7 (the bottom frame row of window $12)
L_7027:
        sta     textCursorRow           ; 7027  zp_40 = cursor row
        jsr     updateCursorPointers    ; 7029  recompute the bitmap and colour-RAM pointers for that cell
        lda     #$8C                    ; 702C  low byte of the string 'DRONE' at $868C
        ldy     #$86                    ; 702E  high byte -> $868C
        jsr     printString             ; 7030  print it (strings end on a byte with bit 7 set)
        jmp     resetDrawModes          ; 7033  put both draw modes back to plain replace and return


; ----------------------------------------------------------------------
; drawDroneCountDigit - Draws the number of drones still available (radarDronesLeft $9233) as a single
; tall digit at rows 5-6, column 34, in the fixed colour $30. When no drones are left the colour byte
; is forced to 0, which makes the digit the same colour as its background, i.e. invisible.
; In:  radarDronesLeft ($9233), 0-9
; Out: one glyph pair drawn; textDrawParams/D_2A2A cleared on exit; zp_18 = 5, zp_19 = 34
; Called from: buildDroneScreen ($711C) and launchDrone ($80BB) after the counter is decremented
; ----------------------------------------------------------------------
drawDroneCountDigit:
        lda     #$05                    ; 7036  digit row 5
        sta     scratch18               ; 7038  zp_18 = cell row
        lda     #$22                    ; 703A  digit column 34 (centre of the 33-35 colour box)
        sta     scratch19               ; 703C  zp_19 = cell column
        lda     #$FF                    ; 703E  use the fixed colour in D_2A2A...
        sta     useDrawColourFlag       ; 7040  ...rather than the per-character colour table
        ldx     #$30                    ; 7043  normal digit colour $30 (%01 cyan, %10 black)
        lda     radarDronesLeft         ; 7045  drones left
        cmp     #$01                    ; 7048  at least one left?
        bcs     L_704D                  ; 704A  yes - keep colour $30
        tax                             ; 704C  none left: colour byte 0, so the glyph vanishes into the background
L_704D:
        stx     drawColourByte          ; 704D  install the colour
        lda     radarDronesLeft         ; 7050  reload the count - it is also the glyph-pair index for drawTallGlyph
        bne     L_7055                  ; 7053  no-op branch: both paths continue at $7055 (probably a leftover leading-zero test)
L_7055:
        jsr     drawTallGlyph           ; 7055  draw the digit as a 2-row glyph pair at rows 5-6, column 34
        lda     #$00                    ; 7058  restore normal colour handling...
        sta     useDrawColourFlag       ; 705A  ...clear the fixed-colour flag...
        sta     drawColourByte          ; 705D  ...and the fixed colour
        rts                             ; 7060  done


; ----------------------------------------------------------------------
; blinkDroneCountBox - Alternates the background of the drones-left box (3x2 colour cells at row 5,
; column 33) between $30 and its nibble-swapped twin $03 according to bit 0 of the frame counter,
; which swaps the foreground and background of the tall digit sitting in it and makes the counter
; flash.
; In:  frameCounter (zp_45) bit 0
; Out: 6 screen-RAM colour cells rewritten; D_2A2D = 5, D_2A2E = 33, D_2A2A = the colour used; zp_1A
;     used as the rotate buffer
; Called from: droneScreenIdleLoop ($8067, every 11 frames) and droneFlightLoop ($813E, every 40
;     frames), in both cases only while fewer than two drones remain
; ----------------------------------------------------------------------
blinkDroneCountBox:
        lda     #$05                    ; 7061  colour fill origin row 5
        sta     windowOriginRow         ; 7063  D_2A2D = window row
        lda     #$21                    ; 7066  colour fill origin column 33
        sta     windowOriginCol         ; 7068  D_2A2E = window column
L_706B:
        ldx     #$30                    ; 706B  base colour $30 (%01 cyan, %10 black)
        lda     frameCounter            ; 706D  free-running frame counter maintained by the raster IRQ
        and     #$01                    ; 706F  look at its parity
        bne     L_7085                  ; 7071  odd frame - use $30 unchanged
        sta     scratch1A               ; 7073  even frame: A is 0, so this clears the rotate buffer
        txa                             ; 7075  colour byte into A for the nibble swap
        asl     a                       ; 7076  rotate the byte left by four bits, one shift...
        rol     scratch1A               ; 7077  ...catching the bit that leaves the byte
        asl     a                       ; 7079  shift 2
        rol     scratch1A               ; 707A  catch
        asl     a                       ; 707C  shift 3
        rol     scratch1A               ; 707D  catch
        asl     a                       ; 707F  shift 4
        rol     scratch1A               ; 7080  catch
        ora     scratch1A               ; 7082  merge - $30 has become $03 (%01 black, %10 cyan)
        tax                             ; 7084  keep it in X
L_7085:
        txa                             ; 7085  colour byte back into A for the fill call
        ldx     #$03                    ; 7086  3 columns (33-35)
L_7088:
        ldy     #$02                    ; 7088  2 rows (5-6)
        jmp     fillScreenColourRect    ; 708A  repaint the box and return through fillScreenColourRect


; ----------------------------------------------------------------------
; drawLaunchStrikeButtons - Draws the two-button panel down the right edge of the DRONE screen: frame
; window $13 (left 30, top 10, 10 wide x 11 rows) with inner corner marks, the 2x2 button icon from
; $9804 (characters $45-$48) at row 12 column 34 painted colour $05, the caption 'LAUNCH' ($8691) at
; row 14 column 32, the same icon again at row 16 column 34, and the caption 'STRIKE' ($8697) at row
; 18 column 32. The screen-2 loops afterwards recolour these two 2x2 icon blocks: colour $05 when
; LAUNCH is pressed, colour $02 when STRIKE is pressed and an XOR of $08 for the idle blink.
; In:  nothing
; Out: frame, two icons and two captions drawn; zp_3D/zp_3F/zp_40 left at $C7/32/18; both draw modes
;     reset to replace
; Called from: buildDroneScreen ($711F) only
; Note: the survey's window geometry was wrong - windowLeftTable[$13] = 30 and windowWidthTable[$13] =
; 10, not left 29 / width 14.
; ----------------------------------------------------------------------
drawLaunchStrikeButtons:
        ldx     #$13                    ; 708D  window $13 = left 30, top 10, 10 wide x 11 rows - the button panel
        jsr     drawWindowFrame         ; 708F  draw its frame
        jsr     drawWindowInnerCorners  ; 7092  add the four inner corner marks (character $38)
        lda     #$0C                    ; 7095  LAUNCH icon row 12
setLaunchIconOrigin:
        sta     windowOriginRow         ; 7097  D_2A2D = window row (label is a variant-A entry point only)
        lda     #$22                    ; 709A  LAUNCH icon column 34
        sta     windowOriginCol         ; 709C  D_2A2E = window column
L_709F:
        lda     #$04                    ; 709F  low byte of the 2x2 icon block at $9804 (header 2,2 then characters $45,$46,$47,$48)
        ldy     #$98                    ; 70A1  high byte -> $9804
        jsr     drawCharBlock           ; 70A3  stamp the icon at rows 12-13, columns 34-35
        lda     #$05                    ; 70A6  screen-RAM colour $05: %01 black, %10 green - the unpressed button
        ldx     #$02                    ; 70A8  2 columns
        ldy     #$02                    ; 70AA  2 rows
L_70AC:
        jsr     fillScreenColourRect    ; 70AC  paint the icon's 4 colour cells
        lda     #$CD                    ; 70AF  colour RAM value for the caption ($D = light green for the %11 pixels)
        sta     textColour              ; 70B1  zp_3D = text colour
        jsr     setFullScreenWindow     ; 70B3  text window = whole screen so column 32 is absolute
        lda     #$20                    ; 70B6  caption column 32
        sta     textCursorCol           ; 70B8  zp_3F = cursor column
        lda     #$0E                    ; 70BA  caption row 14, just under the icon
        sta     textCursorRow           ; 70BC  zp_40 = cursor row
        jsr     updateCursorPointers    ; 70BE  recompute the cell pointers
        lda     #$91                    ; 70C1  low byte of 'LAUNCH' at $8691
        ldy     #$86                    ; 70C3  high byte -> $8691
        jsr     printString             ; 70C5  print it
        lda     #$10                    ; 70C8  STRIKE icon row 16
        sta     windowOriginRow         ; 70CA  D_2A2D = window row
        lda     #$22                    ; 70CD  STRIKE icon column 34
        sta     windowOriginCol         ; 70CF  D_2A2E = window column
        lda     #$04                    ; 70D2  the same 2x2 icon block at $9804...
        ldy     #$98                    ; 70D4  ...high byte
        jsr     drawCharBlock           ; 70D6  stamp it at rows 16-17, columns 34-35 (its colour cells are left as the frame drew them until a strike is armed)
printStrikeCaption:
        lda     #$C7                    ; 70D9  colour RAM value for the caption (7 = yellow for the %11 pixels) (label is a variant-A entry point only)
        sta     textColour              ; 70DB  zp_3D = text colour
        jsr     setFullScreenWindow     ; 70DD  text window = whole screen
        lda     #$20                    ; 70E0  caption column 32
        sta     textCursorCol           ; 70E2  zp_3F = cursor column
        lda     #$12                    ; 70E4  caption row 18
        sta     textCursorRow           ; 70E6  zp_40 = cursor row
        jsr     updateCursorPointers    ; 70E8  recompute the cell pointers
        lda     #$97                    ; 70EB  low byte of 'STRIKE' at $8697
        ldy     #$86                    ; 70ED  high byte -> $8697
        jsr     printString             ; 70EF  print it
        jmp     resetDrawModes          ; 70F2  restore both draw modes and return


; ----------------------------------------------------------------------
; buildDroneScreen - Paints the whole DRONE screen (game screen 2) from scratch: clears the glyph
; plotter's parameter block, sets border and background shadows to $0C (medium grey), sets the radar
; cell background nibble to $0A (light red) and marks the screen as built, then draws every panel in
; turn and finishes by redrawing the status line through the $C000 overlay.
; In:  droneFuel ($9220), radarDronesLeft ($9233), comcenUnit ($9236)
; Out: complete screen 2; vicBorderShadow/vicBg0Shadow ($9003/$9004) = $0C, cellBackgroundColour
;     ($9226) = $0A, screenBuiltFlag (zp_41) = 1, $2A29-$2A2C = 0; jumps away into redrawStatusMessage
; Called from: resetDroneScreen ($7FF3), which is entered from the screen-2 entry $7FE6 and from the
;     comcen knock-out sequence at $076A
; ----------------------------------------------------------------------
buildDroneScreen:
        lda     #$00                    ; 70F5  clear the glyph plotter parameter block...
        sta     useDrawColourFlag       ; 70F7  ...useFixedColour flag off
        sta     drawColourByte          ; 70FA  ...fixed colour byte 0
        sta     blockFillFlag           ; 70FD  ...block-fill flag off
        sta     blockFillChar           ; 7100  ...block-fill character 0
        lda     #$0C                    ; 7103  $0C = medium grey
        sta     vicBg0Shadow            ; 7105  background colour 0 shadow (written to $D021 by the raster IRQ)
        sta     vicBorderShadow         ; 7108  border colour shadow ($D020)
        lda     #$0A                    ; 710B  $0A = light red
        sta     cellBackgroundColour    ; 710D  cellBackgroundColour supplies the low nibble (%10 pixels) of every radar cell on this screen
        lda     #$01                    ; 7110  every screen builder stores 1 here
L_7112:
        sta     screenBuiltFlag         ; 7112  zp_41 screenBuiltFlag; nothing ever reads it
        lda     #$00                    ; 7114  redundant - D_2A2B was already cleared at $70FD
        sta     blockFillFlag           ; 7116  block-fill flag off (again)
        jsr     drawDroneCountPanel     ; 7119  top-right 'DRONE' counter panel
        jsr     drawDroneCountDigit     ; 711C  the drones-left digit inside it
        jsr     drawLaunchStrikeButtons ; 711F  the LAUNCH / STRIKE button panel
L_7122:
        jsr     drawDroneHeadingPanel   ; 7122  top-left compass dial
        jsr     drawDroneFuelPanel      ; 7125  bottom-left fuel gauge panel
        jsr     drawDroneFuelDigits     ; 7128  the three fuel digits
        jsr     drawDroneRadarFrame     ; 712B  camera bezels; also blanks the camera and centres the view on the comcen
        jsr     drawMainWindowFrame     ; 712E  the outer main window / status bar frame (window 4 while a screen id is active)
        jmp     redrawStatusMessage     ; 7131  re-show whatever the status line was displaying, and return from there


; Radar/camera wipe state byte, shared by clearRadarWindowGrey, clearRadarWindowBlack,
; flashRadarAndWipe and drawRadarIrisWipe. It doubles as (a) the colour the camera window is blanked
; with and (b) a flag: non-zero makes the iris wipe pause 3 game frames per ring, zero makes it close
; instantly.
radarWipeColour:
        .byte   $00                     ; 7134  .  $BB = animated grey wipe (after a launch), $00 = instant black wipe

; ----------------------------------------------------------------------
; clearRadarWindowGrey - Blanks the 14x12 drone camera window (row 6, column 13) to colour $BB (dark
; grey on dark grey, i.e. static) and re-centres the view origin on the player's own comcen, leaving
; radarWipeColour ($7134) non-zero so the iris wipe that follows animates with 3-frame pauses.
; In:  comcenUnit ($9236), unitColTable/unitRowTable
; Out: radarWipeColour = $BB; camera window filled with $BB; radarGhostCountdown ($920F) = 0;
;     viewOriginCol/Row (zp_A5/zp_A6) = comcen column - 6, comcen row - 5
; Called from: launchDrone ($8116) only
; ----------------------------------------------------------------------
clearRadarWindowGrey:
        lda     #$BB                    ; 7135  $BB = dark grey in both multicolour nibbles - looks like blank static
        sta     radarWipeColour         ; 7137  remember it as both the fill colour and the 'animate the wipe' flag
        bne     L_7141                  ; 713A  always taken ($BB is non-zero); joins the shared tail

; ----------------------------------------------------------------------
; clearRadarWindowBlack - Same as clearRadarWindowGrey but with colour 0: blanks the drone camera
; window to black and re-centres the view on the own comcen. radarWipeColour is left 0, so a following
; iris wipe runs with no pauses.
; In:  comcenUnit ($9236), unitColTable/unitRowTable
; Out: radarWipeColour = 0; camera window black; radarGhostCountdown ($920F) = 0; viewOriginCol/Row =
;     comcen - (6,5)
; Called from: drawDroneRadarFrame ($6F96, screen build) and endDroneFlight ($81F8)
; ----------------------------------------------------------------------
clearRadarWindowBlack:
        lda     #$00                    ; 713C  colour 0 = all pixels black
        sta     radarWipeColour         ; 713E  0 also means 'no pauses in the iris wipe'

; ----------------------------------------------------------------------
; Shared tail of the two window clears: paint the camera area, forget any pending noise ghost and park
; the map view so that the own comcen sits in the middle of the 14x12 window (the drone starts its
; flight over the comcen).
; ----------------------------------------------------------------------
L_7141:
        jsr     fillDroneViewWindow     ; 7141  fill the 14 column x 12 row camera window at row 6, column 13 with the colour in A
        lda     #$00                    ; 7144  clear the countdown that decides when the next unspotted enemy is drawn as a ghost blip
        sta     radarGhostCountdown     ; 7146  radarGhostCountdown ($920F)
        ldy     comcenUnit              ; 7149  the player's own comcen (unit 49 for side 0, 99 for side 1)
L_714C:
        lda     unitColTable,y          ; 714C  its map column (unit arrays are 100-byte arrays $64 apart from $F640)
        sec                             ; 714F  prepare the subtraction
        sbc     #$06                    ; 7150  the window is 14 cells wide, so back off 6 to put the comcen at cell 6
        sta     viewOriginCol           ; 7152  zp_A5 = map column of the window's left edge
        lda     unitRowTable,y          ; 7154  the comcen's map row ($F6A4 = $F640 + $64)
        sec                             ; 7157  prepare the subtraction
        sbc     #$05                    ; 7158  the window is 12 cells high, so back off 5 to put the comcen at cell 5
        sta     viewOriginRow           ; 715A  zp_A6 = map row of the window's top edge
        rts                             ; 715C  done


; Six bytes with no reference anywhere in overlay variant B ($21,$A1,$21,$81,$A1,$11). They are code
; or data of overlay variant A, which shares this address range; the label L_7161 is a branch target
; only in variant A. Dead in this variant.
unusedBytes715D:
        .byte   $21,$A1,$21,$81         ; 715D  !.!.  unreferenced filler in variant B (variant A code/data)
L_7161:
        .byte   $A1                     ; 7161  continuation of the same unreferenced filler
        .byte   $11                     ; 7162  .

; ----------------------------------------------------------------------
; playDroneLostAnimation - The 'drone is gone' effect, used when the drone is shot down, detonated by
; its own strike or runs out of fuel. It switches sprite 4 (the comcen range rings) off, flashes the
; camera window white and irises it shut, then rebuilds sprite 2 (the drone reticle) as a solid disc
; of radius 3 and lets it shrink and fade (white -> light grey -> medium grey -> dark grey) by erasing
; one ring at a time, disables sprite 2, zeroes the fuel read-out and waits 30 game frames.
; In:  nothing meaningful (droneHeading is copied to a byte nobody reads)
; Out: spriteEnableShadow ($9022) bits 4 and 2 cleared; sprite 2 shape and colour changed;
;     spritePlotMode ($91F3) = 0; droneFuel ($9220) = 0 and redrawn; textDrawParams/D_2A2A = 0;
;     roughly 50 game frames elapse (the battle keeps running inside runGameFrames)
; Called from: playStrikeZoomAnimation ($7582, after a strike) and droneFlightLoop ($8177, drone lost)
; Dead code: 'sta zp_30' at $716A (only magnifyWindow2x uses zp_30, and it initialises it itself) and
; the store to savedDroneHeading at $717C (never read back).
; ----------------------------------------------------------------------
playDroneLostAnimation:
        lda     #$FF                    ; 7163  force the fixed draw colour for everything drawn from here
        sta     useDrawColourFlag       ; 7165  textDrawParams = $FF
        lda     #$05                    ; 7168  dead store: 5 into magnifySrcRow...
        sta     magnifySrcRow           ; 716A  ...zp_30, which magnifyWindow2x always initialises itself
        lda     spriteEnableShadow      ; 716C  current sprite enable mask shadow
        and     #$EF                    ; 716F  clear bit 4 = sprite 4, the comcen range rings
        sta     spriteEnableShadow      ; 7171  the raster IRQ copies this to $D015
        lda     #$00                    ; 7174  fixed colour 0 (black) for the wipe frames
L_7176:
        sta     drawColourByte          ; 7176  D_2A2A = fixed colour byte
        lda     droneHeading            ; 7179  current drone heading 0-7
        sta     savedDroneHeading       ; 717C  dead store - savedDroneHeading ($7271) is never read
        jsr     flashRadarAndWipe       ; 717F  flash the camera window white and iris it shut
        lda     #$02                    ; 7182  sprite 2 = the drone reticle
        jsr     clearSpriteShape        ; 7184  zero its 64 shape bytes at $8A80
        lda     #$40                    ; 7187  sprite plot mode $40 = OR pixels in (draw)
        sta     spritePlotMode          ; 7189  spritePlotMode ($91F3)
        lda     #$02                    ; 718C  sprite 2
        ldx     #$03                    ; 718E  radius 3
        jsr     drawCircleInSprite      ; 7190  draw a radius-3 circle centred at (12,10) in the sprite
        lda     #$02                    ; 7193  sprite 2 again
        tax                             ; 7195  X = 2 (A was 2) - radius 2
        jsr     drawCircleInSprite      ; 7196  fill in the radius-2 ring, making the shape a solid disc
        lda     #$02                    ; 7199  sprite 2 again
        ldx     #$01                    ; 719B  radius 1 = the 3x3 centre dot
        jsr     drawCircleInSprite      ; 719D  complete the disc
        lda     #$04                    ; 71A0  bit 2 = sprite 2
        jsr     enableSprites           ; 71A2  switch it on (unless sprites are suppressed by $922A bit 6)
        lda     #$01                    ; 71A5  colour 1 = white
        sta     sprite2ColourShadow     ; 71A7  sprite 2 colour shadow ($900A -> $D029)
L_71AA:
        lda     #$03                    ; 71AA  hold the white disc for 3 frames...
        jsr     runGameFrames           ; 71AC  ...while the battle keeps updating
        lda     #$00                    ; 71AF  sprite plot mode 0 = clear pixels (erase)
        sta     spritePlotMode          ; 71B1  spritePlotMode
        lda     #$02                    ; 71B4  sprite 2
        ldx     #$03                    ; 71B6  radius 3
        jsr     drawCircleInSprite      ; 71B8  erase the outer ring - the disc shrinks
        lda     #$0F                    ; 71BB  colour $0F = light grey
        sta     sprite2ColourShadow     ; 71BD  fade step 1
        lda     #$03                    ; 71C0  3 more frames
        jsr     runGameFrames           ; 71C2  run them
        lda     #$02                    ; 71C5  sprite 2
        ldx     #$02                    ; 71C7  radius 2
        jsr     drawCircleInSprite      ; 71C9  erase the next ring
        lda     #$0C                    ; 71CC  colour $0C = medium grey
        sta     sprite2ColourShadow     ; 71CE  fade step 2
        lda     #$05                    ; 71D1  5 frames
        jsr     runGameFrames           ; 71D3  run them
        lda     #$02                    ; 71D6  sprite 2
        ldx     #$01                    ; 71D8  radius 1
        jsr     drawCircleInSprite      ; 71DA  erase the last dot - the sprite is now empty
        lda     #$0B                    ; 71DD  colour $0B = dark grey
        sta     sprite2ColourShadow     ; 71DF  fade step 3
        lda     #$06                    ; 71E2  6 frames
        jsr     runGameFrames           ; 71E4  run them
        lda     spriteEnableShadow      ; 71E7  sprite enable shadow
        and     #$FB                    ; 71EA  clear bit 2 = sprite 2 off
        sta     spriteEnableShadow      ; 71EC  store it back
        lda     #$00                    ; 71EF  restore normal colour handling...
        sta     useDrawColourFlag       ; 71F1  ...fixed-colour flag off...
        sta     drawColourByte          ; 71F4  ...and fixed colour 0
        lda     #$00                    ; 71F7  the drone is gone, so the tank reads empty
        sta     droneFuel               ; 71F9  droneFuel ($9220) = 0
        jsr     drawDroneFuelDigits     ; 71FC  redraw '000' in the critical colour
        lda     #$1E                    ; 71FF  30 frames = about half a second
        jmp     runGameFrames           ; 7201  wait them out and return from runGameFrames


; ----------------------------------------------------------------------
; flashRadarAndWipe - Paints the whole 14x12 drone camera window white for two game frames, then
; clears radarWipeColour and falls into drawRadarIrisWipe, so the iris closes over the white flash
; with no pauses.
; In:  nothing
; Out: camera window colour 1; radarWipeColour ($7134) = 0; then see drawRadarIrisWipe
; Called from: playDroneLostAnimation ($717F) only
; ----------------------------------------------------------------------
flashRadarAndWipe:
        lda     #$06                    ; 7204  fill origin row 6 = top of the camera window
        sta     windowOriginRow         ; 7206  D_2A2D = window row
        lda     #$0D                    ; 7209  fill origin column 13 = left edge of the camera window
        sta     windowOriginCol         ; 720B  D_2A2E = window column
        ldx     #$0E                    ; 720E  14 columns
        ldy     #$0C                    ; 7210  12 rows
        lda     #$01                    ; 7212  colour byte $01: %01 pixels black, %10 pixels white - a bright flash
        jsr     fillScreenColourRect    ; 7214  paint it
        lda     #$02                    ; 7217  hold for 2 frames
        jsr     runGameFrames           ; 7219  while the battle keeps running
L_721C:
        lda     #$00                    ; 721C  0 = close the iris with no pauses
        sta     radarWipeColour         ; 721E  radarWipeColour; falls straight into drawRadarIrisWipe

; ----------------------------------------------------------------------
; drawRadarIrisWipe - Closes (or opens) an iris over the drone camera window: it first draws window
; $16 (the camera area itself, left 13, top 6, 14x12) and then repeatedly draws a rectangular frame
; out of the all-$03 solid tile set at $9848, starting 12x12 cells at row 6 column 14 and shrinking
; two cells and moving in one cell each pass until the height drops below 3. The centre is finished
; off with a 2x2 block of character $05 at row 11, column 19. Each pass waits for raster line $B0 so
; the frame appears in one go, and when radarWipeColour is non-zero it also runs 3 game frames per
; pass, turning the wipe into a visible animation.
; In:  radarWipeColour ($7134); the current fixed colour in $2A29/$2A2A ($AA light red after a launch,
;     0 during the loss animation)
; Out: the camera area is covered by concentric frames; D_2A2D = 11, D_2A2E = 19;
;     frameWidth/frameHeight ($9221/$9222) clobbered
; Called from: flashRadarAndWipe ($721E, fall-through) and launchDrone ($8121, as the iris-open
;     effect)
; ----------------------------------------------------------------------
drawRadarIrisWipe:
        ldx     #$16                    ; 7221  window $16 = left 13, top 6, 14x12 - the camera area drawn as a plain frame
        jsr     drawWindowFrame         ; 7223  draw it
        lda     #$0C                    ; 7226  the first iris ring is 12x12 cells
        sta     frameWidth              ; 7228  frameWidth ($9221)
        sta     frameHeight             ; 722B  frameHeight ($9222)
        lda     #$06                    ; 722E  first ring's top row 6
        sta     windowOriginRow         ; 7230  D_2A2D = frame row
        lda     #$0E                    ; 7233  first ring's left column 14
        sta     windowOriginCol         ; 7235  D_2A2E = frame column

; ----------------------------------------------------------------------
; Iris loop: draw one concentric frame, then shrink it by two cells and move its origin in by one
; cell, so the ring closes in on the centre of the camera window. drawFrameBox itself decrements width
; and height once and this loop decrements them again, hence two cells per pass.
; ----------------------------------------------------------------------
L_7238:
        lda     frameHeight             ; 7238  current ring height
        cmp     #$03                    ; 723B  smaller than 3 cells?
        bcc     L_7265                  ; 723D  yes - the iris is shut, go and plug the centre
L_723F:
        lda     VIC_RASTER              ; 723F  poll the raster...
        cmp     #$B0                    ; 7242  ...for line $B0, below the picture, so the ring is drawn between frames
        bne     L_723F                  ; 7244  keep polling until it matches
        lda     #$48                    ; 7246  low byte of the 9-byte frame tile set at $9848 (all nine tiles are character $03 = solid)
        ldy     #$98                    ; 7248  high byte -> $9848
        jsr     drawFrameBox            ; 724A  draw the ring; this also decrements frameWidth/frameHeight by one
        dec     frameWidth              ; 724D  second decrement: the ring loses one cell on each side
        dec     frameHeight             ; 7250  same for the height
        lda     radarWipeColour         ; 7253  animated wipe?
        beq     L_725D                  ; 7256  no - draw the next ring immediately
        lda     #$03                    ; 7258  3 game frames of pause...
        jsr     runGameFrames           ; 725A  ...so the iris is visible as an animation
L_725D:
        inc     windowOriginRow         ; 725D  move the origin one row down...
        inc     windowOriginCol         ; 7260  ...and one column right, keeping the ring centred
        bne     L_7238                  ; 7263  always taken - next ring
L_7265:
        lda     #$13                    ; 7265  centre column 19
        sta     windowOriginCol         ; 7267  D_2A2E; D_2A2D is already 11 after the last pass
        lda     #$0A                    ; 726A  low byte of the 2x2 block at $980A (header 2,2 then four copies of character $05)
        ldy     #$98                    ; 726C  high byte -> $980A
        jmp     drawCharBlock           ; 726E  plug the 2x2 hole at rows 11-12, columns 19-20 and return from drawCharBlock


; savedDroneHeading: byteTable, 1 bytes. Dead copy of droneHeading written by playDroneLostAnimation
savedDroneHeading:
        .byte   $00                     ; 7271  .  written by playDroneLostAnimation ($717C) and never read - a dead copy of droneHeading

; Eight AND masks that clear a single bit: index n clears bit n. buildRadarNoiseMasks picks the index
; with a random number 0-7.
clearBitMaskTable:
        .byte   $FE,$FD,$FB,$F7,$EF,$DF,$BF,$7F; 7272  ........  $FE,$FD,$FB,$F7,$EF,$DF,$BF,$7F - clear bit 0..7 respectively

; ----------------------------------------------------------------------
; buildRadarNoiseMasks - Fills the 16-byte table radarNoiseMasks ($9210-$921F) with $FF and then
; knocks four randomly chosen bits out of each byte. drawRadarGlyph ANDs the eight rows of a glyph
; with eight consecutive entries of this table (starting at a random offset 0-7, which is why the
; table is 16 and not 8 bytes long) to turn an unspotted enemy blip into a speckled 'ghost'.
; In:  the game RNG state $57-$59
; Out: radarNoiseMasks ($9210-$921F); zp_1A = 0; Y = $FF; A/X clobbered
; Called from: drawDroneRadarFrame ($6F89), drawDroneRadarWindow ($742D), redrawEnemyUnitsOnRadar
;     ($7926) and drawRadarMap ($7BA2) - i.e. the noise pattern is re-rolled on every radar redraw
; ----------------------------------------------------------------------
buildRadarNoiseMasks:
        ldy     #$0F                    ; 727A  16 masks, index 15 down to 0
        lda     #$FF                    ; 727C  start from 'all bits set' = no noise

; ----------------------------------------------------------------------
; Clear the table to $FF.
; ----------------------------------------------------------------------
L_727E:
        sta     radarNoiseMasks,y       ; 727E  radarNoiseMasks[Y] = $FF
        dey                             ; 7281  next entry down
        bpl     L_727E                  ; 7282  until index 0 has been written
        ldy     #$0F                    ; 7284  second pass, again from index 15

; ----------------------------------------------------------------------
; For every mask byte, punch out four random bits (they may collide, so between one and four bits
; actually end up clear).
; ----------------------------------------------------------------------
L_7286:
        lda     #$04                    ; 7286  four bits to clear per mask byte
        sta     scratch1A               ; 7288  zp_1A = per-byte bit counter
L_728A:
        jsr     nextGameRandom          ; 728A  step the shared 24-bit game RNG (both machines run the same sequence)
        and     #$07                    ; 728D  keep a bit number 0-7
L_728F:
        tax                             ; 728F  use it as the index into clearBitMaskTable
        lda     clearBitMaskTable,x     ; 7290  AND mask that clears just that bit
        and     radarNoiseMasks,y       ; 7293  combine with the mask built so far
        sta     radarNoiseMasks,y       ; 7296  store it back
        dec     scratch1A               ; 7299  one of the four bits done
        bne     L_728A                  ; 729B  repeat until four have been cleared
        dey                             ; 729D  next mask byte
        bpl     L_7286                  ; 729E  until all 16 are done
        rts                             ; 72A0  done


; ----------------------------------------------------------------------
; applyDroneBlast - Resolves a drone detonation: damages every unit standing in the 2x2 block of map
; cells whose top-left corner is (cmdParam1, cmdParam2) - (x,y), (x+1,y), (x+1,y+1) and (x,y+1) - by
; calling applyDroneBlastToCell four times. The blast strength arrives in A and is written into the
; LDA # operand at $7310+1, so each cell's damage call ends with 'LDA #strength / JSR applyHitToUnit'.
; In:  A = blast strength (cmdDroneDetonate at $536C passes $19 = 25), cmdParam1/cmdParam2
;     (zp_74/zp_75) = the top-left map cell of the blast
; Out: up to four units damaged and stunned; the operand at $7311 is patched; cmdParam1 is restored to
;     x but cmdParam2 is left at y+1, which is what the caller then uses to place the explosion marker
;     sprite
; Called from: cmdDroneDetonate in the main program ($536C) only
; ----------------------------------------------------------------------
applyDroneBlast:
        sta     L_7310+1                ; 72A1  self-modifying code: store the blast strength into the operand byte of the 'lda #$FF' at $7310
        jsr     applyDroneBlastToCell   ; 72A4  cell (x, y) - the top-left of the 2x2 blast
        inc     cmdParam1               ; 72A7  step to column x+1
        jsr     applyDroneBlastToCell   ; 72A9  cell (x+1, y)
        inc     cmdParam2               ; 72AC  step to row y+1
        jsr     applyDroneBlastToCell   ; 72AE  cell (x+1, y+1)
L_72B1:
        dec     cmdParam1               ; 72B1  back to column x; falls straight into the fourth call for cell (x, y+1)

; ----------------------------------------------------------------------
; applyDroneBlastToCell - Applies the drone blast to the single map cell (cmdParam1, cmdParam2). It
; returns at once if the cell is outside the 40x40 map or holds no unit. Otherwise, if the blast
; strength is at least 15 and the game type is not THE BOMB (type 2), the unit's stun counter is set
; to 15 and haltStunnedUnit stops it where it stands. If the unit is the player's own comcen, the
; cloak lock is refreshed and - with the DAMAGE option on ($0BA3 bit 4) and not in a solo/film game -
; all three comcen systems are marked damaged and comcenStunStatus is set to $FF, which triggers the
; knock-out cut-scene at $0709. Without DAMAGE the 'COMCEN STUNNED!' message (id $0C) is queued
; instead. Finally the unit takes the blast damage.
; In:  cmdParam1/cmdParam2 = map cell, blast strength in the operand at $7311, gameTypeOptions
;     ($0BA3), filmPlaybackMode ($0B9B), comcenUnit ($9236)
; Out: unit stun/halt, possibly the comcen knock-out flags, and applyHitToUnit with A = strength, Y =
;     the unit index
; Called from: applyDroneBlast ($72A4/$72A9/$72AE plus the fall-through at $72B1)
; Note: the labels L_72B1, L_72D3, L_72D5, L_72E1, L_72E5, L_72F2, L_72F5, sub_72F6 and L_730D are
; XREF artefacts of overlay variant A sharing these addresses; only sub_72F6 ($72F6) is also a real
; branch target here.
; ----------------------------------------------------------------------
applyDroneBlastToCell:
        ldx     cmdParam1               ; 72B3  map column of the cell
        cpx     #$28                    ; 72B5  the map is 40 cells wide
        bcs     L_7315                  ; 72B7  off the map - nothing to damage
        ldy     cmdParam2               ; 72B9  map row of the cell
        cpy     #$28                    ; 72BB  the map is 40 cells high
        bcs     L_7315                  ; 72BD  off the map - nothing to damage
        jsr     readMapCell             ; 72BF  read map[$F000 + row*40 + col]
        bpl     L_7315                  ; 72C2  bit 7 clear = terrain only, no unit standing here
        and     #$7F                    ; 72C4  strip the 'unit here' bit...
        tay                             ; 72C6  ...leaving the unit index 0-99 in Y
        lda     L_7310+1                ; 72C7  the blast strength patched in by applyDroneBlast
        cmp     #$0F                    ; 72CA  weaker than 15?
        bcc     L_7310                  ; 72CC  yes - just apply damage, no stun
        lda     gameTypeOptions         ; 72CE  game options byte
        and     #$07                    ; 72D1  bits 0-2 = game type
L_72D3:
        cmp     #$02                    ; 72D3  type 2 = THE BOMB
L_72D5:
        beq     L_7310                  ; 72D5  in THE BOMB nothing gets stunned - just apply damage
        lda     unitStunTable,y         ; 72D7  the unit's stun / dug-in counter
        ora     #$0F                    ; 72DA  set the low nibble to 15 = fully stunned
        sta     unitStunTable,y         ; 72DC  store it back
        jsr     haltStunnedUnit         ; 72DF  cancel the unit's orders so it stops where it is
        cpy     comcenUnit              ; 72E2  is this the local player's own comcen?
L_72E5:
        bne     L_7310                  ; 72E5  no - just apply damage
        jsr     updateComcenCloakLock   ; 72E7  refresh comcenCloakedFlag ($9245) from the comcen's cloak bit
        lda     gameTypeOptions         ; 72EA  game options byte
        and     #$10                    ; 72ED  bit 4 = the DAMAGE option
        beq     queueComcenStunnedMessage; 72EF  DAMAGE off - only announce the stun
        lda     filmPlaybackMode        ; 72F1  solo trainer / film playback flag
        bpl     L_7300                  ; 72F4  bit 7 clear = real linked game, so knock the comcen out

; ----------------------------------------------------------------------
; Soft outcome: announce the stun on the status line and skip the knock-out.
; ----------------------------------------------------------------------
queueComcenStunnedMessage:
        lda     #$0C                    ; 72F6  message id $0C = 'COMCEN STUNNED!'
        jsr     queueMessage            ; 72F8  queue it in the $C000 overlay's sorted message ring
        ldy     comcenUnit              ; 72FB  reload the unit index for applyHitToUnit
        bne     L_7310                  ; 72FE  always taken: comcenUnit is 49 or 99, never 0 - so the knock-out block below is skipped

; ----------------------------------------------------------------------
; Hard outcome: the comcen is knocked out. All three of its subsystems are marked damaged and
; comcenStunStatus is set, which the main loop turns into the black-out cut-scene at $0709.
; ----------------------------------------------------------------------
L_7300:
        lda     #$80                    ; 7300  $80 = system damaged
        sta     comcenBattleStatus      ; 7302  comcenSystemStatus[1]: the overview map drawing
        sta     comcenRadarStatus       ; 7305  comcenSystemStatus[2]: screen 1, the radar/missile scanner
        sta     comcenDroneStatus       ; 7308  comcenSystemStatus[3]: the drone system
        lda     #$FF                    ; 730B  $FF = comcen stunned
L_730D:
        sta     comcenStunStatus        ; 730D  comcenStunStatus ($922A); the knock-out sequence polls this
L_7310:
        lda     #$FF                    ; 7310  operand $7311 is patched by applyDroneBlast with the blast strength (25 for a drone)
        jsr     applyHitToUnit          ; 7312  apply the damage to unit Y (halved and score-adjusted inside applyHitToUnit)
L_7315:
        rts                             ; 7315  done


; Maps a clockwise compass heading 0-7 (0 N, 1 NE, 2 E, 3 SE, 4 S, 5 SW, 6 W, 7 NW) to the engine's
; direction code 0-7 (0 N, 1 S, 2 E, 3 W, 4 NW, 5 NE, 6 SE, 7 SW), which is what directionDeltaTable
; ($20F0) and diagonalDirectionTable ($1FB2) are indexed with. joyDirToHeadingTable ($7FCE) is the
; inverse mapping.
headingToDirectionTable:
        .byte   $00,$05,$02,$06         ; 7316  ....  headings N,NE,E,SE -> direction codes 0,5,2,6
L_731A:
        .byte   $01                     ; 731A  headings S,SW,W,NW -> direction codes 1,7,3,4
        .byte   $07,$03,$04             ; 731B  ...

; ----------------------------------------------------------------------
; steerDrone - Advances the locally flown drone by one map cell. Called once per command exchange from
; sendOutgoingPacket ($5639) while droneControlFlags ($920D) bit 6 says the drone is under local
; control, just before the $8D heading,col,row report is queued. It turns droneHeading ($920B) at most
; one 45-degree step towards the heading the joystick is asking for, taking the shorter way round and
; playing sound $10 on each turn, then converts the heading to a direction code and adds that
; direction's dx/dy to droneViewOriginCol/Row ($92F6/$92F7).
; In:  droneJoyDirection (zp_5E) = direction code of the last joystick reading, droneHeading ($920B),
;     droneViewOriginCol/Row ($92F6/$92F7)
; Out: droneHeading turned by at most one step and kept in 0-7; the drone's map position advanced by
;     one cell; sound $10 when it turned; A/X/Y clobbered
; Called from: sendOutgoingPacket ($5639) in the main program
; ----------------------------------------------------------------------
steerDrone:
        ldy     droneJoyDirection       ; 731E  the direction code the joystick last produced (0 N, 1 S, 2 E, 3 W, 4 NW, 5 NE, 6 SE, 7 SW)
        lda     joyDirToHeadingTable,y  ; 7320  convert it to a clockwise heading 0-7 through joyDirToHeadingTable ($7FCE)
        sec                             ; 7323  prepare the subtraction
        sbc     droneHeading            ; 7324  how far is that from the heading we are flying?
        beq     L_734C                  ; 7327  already on heading - no turn
        bcc     L_7335                  ; 7329  wanted heading is numerically smaller - handle the negative case
        ldx     #$FF                    ; 732B  assume a turn of -1 (anticlockwise)
        cmp     #$04                    ; 732D  is the difference 4 or more?
        bcs     L_733D                  ; 732F  yes - going anticlockwise is the short way round
        ldx     #$01                    ; 7331  difference 1-3: turn +1, clockwise, is shorter
        bne     L_733D                  ; 7333  always taken
L_7335:
        ldx     #$01                    ; 7335  negative difference: assume +1 (clockwise)
        cmp     #$FC                    ; 7337  difference of -4..-1 (i.e. $FC..$FF)?
        bcc     L_733D                  ; 7339  no - the gap is bigger than 4, so clockwise is shorter
        ldx     #$FF                    ; 733B  -1..-4: turn anticlockwise
L_733D:
        txa                             ; 733D  the turn step (+1 or -1) into A
L_733E:
        clc                             ; 733E  prepare the addition
        adc     droneHeading            ; 733F  apply it to the current heading
        and     #$07                    ; 7342  wrap into the range 0-7
        sta     droneHeading            ; 7344  the drone has swung 45 degrees
        lda     #$10                    ; 7347  sound effect $10 = the turn
        jsr     playSound               ; 7349  play it

; ----------------------------------------------------------------------
; Translate the (possibly just updated) heading into a map step and move the drone.
; ----------------------------------------------------------------------
L_734C:
        ldy     droneHeading            ; 734C  current heading 0-7
        lda     headingToDirectionTable,y; 734F  -> direction code 0-7
        ldy     #$00                    ; 7352  Y will hold the row delta, default 0
        ldx     #$00                    ; 7354  X will hold the column delta, default 0
        cmp     #$04                    ; 7356  codes 4-7 are the diagonals
        bcs     L_736E                  ; 7358  handle a diagonal
        cmp     #$02                    ; 735A  codes 2-3 are east/west
        bcs     L_7366                  ; 735C  handle a pure column move
        tay                             ; 735E  codes 0-1 (N/S): index directionDeltaTable with the code
        lda     directionDeltaTable,y   ; 735F  $20F0[0] = -1 for north, [1] = +1 for south
L_7362:
        tay                             ; 7362  that is the row delta
        jmp     L_7384                  ; 7363  go and apply the step

L_7366:
        tax                             ; 7366  codes 2-3 (E/W): index directionDeltaTable with the code
        lda     directionDeltaTable,x   ; 7367  $20F0[2] = +1 for east, [3] = -1 for west
        tax                             ; 736A  that is the column delta
L_736B:
        jmp     L_7384                  ; 736B  go and apply the step

L_736E:
        tay                             ; 736E  codes 4-7 (NW/NE/SE/SW): use the packed table
        lda     diagonalDirectionTable,y; 736F  $1FB2[4..7] = $30,$20,$21,$31 - high nibble = column index, low nibble = row index
        pha                             ; 7372  keep the packed byte
        and     #$0F                    ; 7373  low nibble = index into directionDeltaTable for the row
        tay                             ; 7375  into Y
        lda     directionDeltaTable,y   ; 7376  fetch the row delta
        tay                             ; 7379  Y = row delta
        pla                             ; 737A  packed byte back
        lsr     a                       ; 737B  shift the high nibble down, 1 of 4
        lsr     a                       ; 737C  2 of 4
        lsr     a                       ; 737D  3 of 4
        lsr     a                       ; 737E  4 of 4 - now the column index
        tax                             ; 737F  into X
        lda     directionDeltaTable,x   ; 7380  fetch the column delta
        tax                             ; 7383  X = column delta
L_7384:
        txa                             ; 7384  column delta into A
        clc                             ; 7385  prepare the addition
        adc     droneViewOriginCol      ; 7386  the drone's current map column ($92F6, the camera window's centre)
        sta     droneViewOriginCol      ; 7389  move it one cell
        tya                             ; 738C  row delta into A
        clc                             ; 738D  prepare the addition
        adc     droneViewOriginRow      ; 738E  the drone's current map row ($92F7)
        sta     droneViewOriginRow      ; 7391  move it one cell
L_7394:
        rts                             ; 7394  done - the caller now queues the $8D heading,col,row report


; radarGlyphSavedY: byteTable, 1 bytes. Saved Y register of drawRadarGlyph
radarGlyphSavedY:
        .byte   $00                     ; 7395  .  scratch byte holding drawRadarGlyph's entry Y (the colour-cell offset), restored on exit

; ----------------------------------------------------------------------
; drawRadarGlyph - Plots one 8x8 map glyph into the bitmap cell pointed at by bitmapPtr (zp_B4/zp_B5)
; and writes its colour through screenPtr (zp_B6/zp_B7) at offset Y. The glyph source is the
; 8-bytes-per-tile set at $9C6E, selected by setTerrainTilePtr from A & $7F. Two effects can be
; applied: if radarHalfToneFlag ($922D) is set (a cloaked unit) the even bitmap rows are blanked and
; only the odd rows copied, so the unit shows up as a half-tone; if bit 7 of A is set (a noise
; 'ghost') every row is ANDed with eight consecutive entries of radarNoiseMasks starting at a random
; offset 0-7. The colour byte in tileColour (zp_32) is written raw on screen 1 - where the caller has
; biased screenPtr into colour RAM at $D800 - and as (tileColour << 4) | cellBackgroundColour on
; screen 2, where screenPtr points into screen RAM at $8C00 and supplies both multicolour nibbles.
; In:  A = glyph index (bit 7 = draw with noise), Y = colour-cell offset, X free, tileColour (zp_32),
;     bitmapPtr, screenPtr, radarHalfToneFlag ($922D), currentScreen ($90FB), cellBackgroundColour
;     ($9226), radarNoiseMasks ($9210)
; Out: 8 bitmap bytes and 1 colour byte written; bitmapPtr advanced 8 bytes to the next cell to the
;     right; radarHalfToneFlag cleared; A = glyph & $7F, X and Y restored via self-modified operands;
;     radarGlyphSavedY = entry Y
; Called from: drawDroneRadarWindow ($7474), drawRadarMap ($7C0E) and the main program's
;     drawScreen1Cell ($5DF5)
; Dead code: the 'lda textDrawParams / beq' test at $73F9 can never take the non-zero path, because
; setTerrainTilePtr at $73A4 always clears textDrawParams first - so the forced colour 8 at $73FE is
; unreachable.
; ----------------------------------------------------------------------
drawRadarGlyph:
        sta     L_73B6+1                ; 7396  self-modifying: save the raw glyph byte (bit 7 = noise flag) into the 'lda #' operand at $73B7
        and     #$7F                    ; 7399  strip the noise flag
        sta     L_7414+1                ; 739B  save the plain glyph index into the 'lda #' operand at $7415 so it can be returned in A
        sty     radarGlyphSavedY        ; 739E  save the caller's Y (the colour-cell offset within the row)
        stx     L_7419+1                ; 73A1  save the caller's X into the 'ldx #' operand at $741A
        jsr     setTerrainTilePtr       ; 73A4  glyphPtr (zp_B2/B3) = $9C6E + glyph*8; also clears textDrawParams
        lda     radarHalfToneFlag       ; 73A7  half-tone flag, set by getRadarCellGlyph for a cloaked unit
        bpl     L_73B6                  ; 73AA  not cloaked - leave all eight rows to the copy loop

; ----------------------------------------------------------------------
; Cloaked unit: blank the four even bitmap rows first; the copy loop below will then only write the
; odd rows, so the unit appears on alternate scanlines.
; ----------------------------------------------------------------------
L_73AC:
        ldy     #$06                    ; 73AC  start at bitmap row 6
        lda     #$00                    ; 73AE  blank byte
L_73B0:
        sta     (bitmapPtr),y           ; 73B0  clear this row of the cell
        dey                             ; 73B2  step two rows...
        dey                             ; 73B3  ...i.e. rows 6, 4, 2, 0
        bpl     L_73B0                  ; 73B4  until row 0 has been cleared
L_73B6:
        lda     #$FF                    ; 73B6  operand patched at $7396 with the glyph byte including its bit 7
        bmi     L_73CB                  ; 73B8  bit 7 set = draw this blip as noise

; ----------------------------------------------------------------------
; Plain copy of the glyph into the bitmap cell, bottom row upwards. When the half-tone flag is set the
; loop steps two rows at a time (odd rows only).
; ----------------------------------------------------------------------
        ldy     #$07                    ; 73BA  bitmap rows 7 down to 0
L_73BC:
        lda     (glyphPtr),y            ; 73BC  glyph row from the $9C6E tile set
        sta     (bitmapPtr),y           ; 73BE  into the bitmap cell
        lda     radarHalfToneFlag       ; 73C0  cloaked?
        bpl     L_73C6                  ; 73C3  no - step one row
        dey                             ; 73C5  cloaked: skip a row so only odd rows are written
L_73C6:
        dey                             ; 73C6  next row up
        bpl     L_73BC                  ; 73C7  until row 0 is done
        bmi     L_73E4                  ; 73C9  always taken - go to the colour write

; ----------------------------------------------------------------------
; Noise variant: the glyph is ANDed row by row with eight consecutive noise masks, starting at a
; random offset 0-7 within the 16-byte table. That is why the table holds 16 masks: the walk of eight
; never runs off the end.
; ----------------------------------------------------------------------
L_73CB:
        jsr     nextGameRandom          ; 73CB  random number from the shared game RNG
        and     #$07                    ; 73CE  start offset 0-7 into radarNoiseMasks
        tax                             ; 73D0  X = mask index
        ldy     #$07                    ; 73D1  bitmap rows 7 down to 0
L_73D3:
        lda     (glyphPtr),y            ; 73D3  glyph row
        and     radarNoiseMasks,x       ; 73D5  knock out four random bits of it
        sta     (bitmapPtr),y           ; 73D8  store the speckled row
        inx                             ; 73DA  next mask for the next row
        lda     radarHalfToneFlag       ; 73DB  cloaked as well?
        bpl     L_73E1                  ; 73DE  no - step one row
        dey                             ; 73E0  cloaked: skip a row
L_73E1:
        dey                             ; 73E1  next row up
        bpl     L_73D3                  ; 73E2  until row 0 is done

; ----------------------------------------------------------------------
; Common tail: clear the one-shot half-tone flag, step the bitmap pointer to the next cell and write
; the cell's colour.
; ----------------------------------------------------------------------
L_73E4:
        ldy     #$00                    ; 73E4  Y = 0 for the flag clear
        sty     radarHalfToneFlag       ; 73E6  radarHalfToneFlag is per cell - clear it for the next one
        clc                             ; 73E9  prepare the 16-bit add
        lda     bitmapPtr               ; 73EA  bitmap pointer low byte
        adc     #$08                    ; 73EC  one character cell = 8 bytes to the right
storeGlyphBitmapPtr:
        sta     bitmapPtr               ; 73EE  store it back (label sub_73EE is a variant-A entry point only)
        lda     bitmapPtrHi             ; 73F0  bitmap pointer high byte
        adc     #$00                    ; 73F2  propagate the carry
        sta     bitmapPtrHi             ; 73F4  bitmapPtr now addresses the next cell of the row
        ldy     radarGlyphSavedY        ; 73F6  restore the colour-cell offset
        lda     useDrawColourFlag       ; 73F9  dead test: setTerrainTilePtr cleared textDrawParams at $73A4, so this is always 0
        beq     L_7402                  ; 73FC  always taken
        lda     #$08                    ; 73FE  unreachable: forced colour 8
        sta     tileColour              ; 7400  unreachable
L_7402:
        lda     tileColour              ; 7402  the colour getRadarCellGlyph chose for this cell
        ldx     currentScreen           ; 7404  which screen are we drawing?
        cpx     #$01                    ; 7407  screen 1 = the RADAR / MISSILE screen
        beq     L_7412                  ; 7409  yes - screenPtr already points into colour RAM, store the raw nibble
        asl     a                       ; 740B  screen 2: move the colour into the high nibble, shift 1...
        asl     a                       ; 740C  ...2...
        asl     a                       ; 740D  ...3...
        asl     a                       ; 740E  ...4 - it is now the %01 pixel colour
        ora     cellBackgroundColour    ; 740F  the %10 pixel colour comes from cellBackgroundColour ($0A on the drone screen)
L_7412:
        sta     (screenPtr),y           ; 7412  write the cell's colour byte
L_7414:
        lda     #$FF                    ; 7414  operand patched at $739B - return the glyph index in A
        ldy     radarGlyphSavedY        ; 7416  restore the caller's Y
L_7419:
        ldx     #$FF                    ; 7419  operand patched at $73A1 - restore the caller's X
        rts                             ; 741B  done


; ----------------------------------------------------------------------
; drawDroneRadarWindow - Redraws the whole 14x12 drone camera window (character rows 6-17, columns
; 13-26, one character per map cell) from the map around the view origin zp_A5/zp_A6. It sets the
; three radar colours, re-rolls the noise masks, walks the map row by row and calls getRadarCellGlyph
; followed by drawRadarGlyph for every cell. The bitmap pointer is built from the $9300 address tables
; biased to row 6 / column 13 (D_9306/D_931F and D_933F/D_9367) and the screen RAM pointer from the
; screen row table biased to row 6 (D_9388/D_93A1) plus column 13. It waits for raster line $90 before
; starting so the redraw is not torn.
; In:  viewOriginCol/Row (zp_A5/zp_A6), the 40x40 map at $F000, the unit arrays, droneRadarOwnColour
;     ($9224) and droneRadarEnemyColour ($9225)
; Out: the camera window redrawn; radarTerrainColour ($9239) = 8, radarOwnUnitColour ($9237) and
;     radarEnemyUnitColour ($9238) set; noise masks re-rolled; drawCellSaveCol/Row ($90F6/$90F7) hold
;     the saved view origin, which is restored on exit; zp_48/49, zp_B4-zp_B7 clobbered
; Called from: advanceDroneCamera ($8281), i.e. once per lock-step command exchange while the drone
;     flies
; ----------------------------------------------------------------------
drawDroneRadarWindow:
        lda     #$08                    ; 741C  terrain colour 8 = orange for the %01 pixels of every terrain cell
        sta     radarTerrainColour      ; 741E  radarTerrainColour ($9239)
        lda     droneRadarOwnColour     ; 7421  own-side unit colour for the drone camera ($0B dark grey, set by overlay A at $7904)
        sta     radarOwnUnitColour      ; 7424  radarOwnUnitColour ($9237)
        lda     droneRadarEnemyColour   ; 7427  enemy unit colour for the drone camera ($09 brown, set by overlay A at $78FB)
L_742A:
        sta     radarEnemyUnitColour    ; 742A  radarEnemyUnitColour ($9238)
        jsr     buildRadarNoiseMasks    ; 742D  re-roll the noise masks so ghost blips shimmer between frames
        jsr     getViewportOriginPointer; 7430  zp_48/49 = map address of the cell at (zp_A5, zp_A6), handling negative origins
        lda     viewOriginCol           ; 7433  the view origin column...
        sta     drawCellSaveCol         ; 7435  ...is saved so the per-cell walk can trample zp_A5
        lda     viewOriginRow           ; 7438  the view origin row...
        sta     drawCellSaveRow         ; 743A  ...likewise
        clc                             ; 743D  prepare the 16-bit add
        lda     bitmapRowLoTableFrom6   ; 743E  bitmap row table entry for row 6 ($9300 + 6)
        adc     bitmapColLoTableFrom13  ; 7441  plus the column offset for column 13 ($9332 + 13 = col*8)
storeRadarBitmapPtr:
        sta     bitmapPtr               ; 7444  bitmapPtr low (label sub_7444 is a variant-A entry point only)
        lda     bitmapRowHiTableFrom6   ; 7446  high byte of the row address
        adc     bitmapColHiTableFrom13  ; 7449  high byte of the column offset
        sta     bitmapPtrHi             ; 744C  bitmapPtr now addresses bitmap cell (6,13)
        clc                             ; 744E  prepare the 16-bit add
        lda     screenRowLoTableFrom6   ; 744F  screen RAM row table entry for row 6 ($9382 + 6, base $8C00)
        adc     #$0D                    ; 7452  plus column 13
        sta     screenPtr               ; 7454  screenPtr low
        lda     screenRowHiTableFrom6   ; 7456  high byte of the screen row address
        adc     #$00                    ; 7459  propagate the carry
        sta     screenPtrHi             ; 745B  screenPtr addresses screen RAM cell (6,13)

; ----------------------------------------------------------------------
; Wait until the raster is below the visible picture (line $90) so the whole window is redrawn between
; two displayed frames.
; ----------------------------------------------------------------------
L_745D:
        lda     VIC_RASTER              ; 745D  read the raster line counter
L_7460:
        cmp     #$90                    ; 7460  line $90
        bne     L_745D                  ; 7462  keep polling
        ldx     #$00                    ; 7464  X = camera row 0

; ----------------------------------------------------------------------
; Outer loop over the 12 camera rows; inner loop over the 14 cells of a row. zp_A5/zp_A6 are walked
; cell by cell so getRadarCellGlyph can tell whether the cell is off the 40x40 map.
; ----------------------------------------------------------------------
L_7466:
        ldy     #$00                    ; 7466  Y = camera column 0
        tya                             ; 7468  column index into A
        clc                             ; 7469  prepare the add
        adc     drawCellSaveCol         ; 746A  plus the saved origin column
        sta     viewOriginCol           ; 746D  zp_A5 = map column of this cell
L_746F:
        lda     (mapCellPtr),y          ; 746F  the map byte for this cell
        jsr     getRadarCellGlyph       ; 7471  classify it into a glyph index (A) and a colour (zp_32)
        jsr     drawRadarGlyph          ; 7474  plot it; this also advances bitmapPtr to the next cell
        iny                             ; 7477  next camera column
        inc     viewOriginCol           ; 7478  and the matching map column
        tya                             ; 747A  column counter into A
L_747B:
        cmp     #$0E                    ; 747B  the window is 14 cells wide
        bcc     L_746F                  ; 747D  more cells on this row
        inc     viewOriginRow           ; 747F  row finished - step the map row
L_7481:
        lda     mapCellPtr              ; 7481  map pointer low
        clc                             ; 7483  prepare the add
        adc     #$28                    ; 7484  one map row = 40 bytes
        sta     mapCellPtr              ; 7486  store it
        lda     mapCellPtrHi            ; 7488  map pointer high
        adc     #$00                    ; 748A  propagate the carry
        sta     mapCellPtrHi            ; 748C  zp_48/49 now addresses the next map row
        clc                             ; 748E  prepare the add
        lda     screenPtr               ; 748F  screen RAM pointer low
        adc     #$28                    ; 7491  one screen row = 40 bytes
        sta     screenPtr               ; 7493  store it
        lda     screenPtrHi             ; 7495  screen RAM pointer high
        adc     #$00                    ; 7497  propagate the carry
        sta     screenPtrHi             ; 7499  zp_B6/B7 now addresses the next screen row
        inx                             ; 749B  next camera row
        ldy     #$00                    ; 749C  back to column 0 for the bitmap address
L_749E:
        clc                             ; 749E  prepare the add
        lda     bitmapRowLoTableFrom6,x ; 749F  bitmap row address for row 6+X
        adc     bitmapColLoTableFrom13,y; 74A2  plus the column offset for column 13+0
        sta     bitmapPtr               ; 74A5  bitmapPtr low
        lda     bitmapRowHiTableFrom6,x ; 74A7  high byte of the row address
        adc     bitmapColHiTableFrom13,y; 74AA  high byte of the column offset
        sta     bitmapPtrHi             ; 74AD  bitmapPtr = first cell of the next camera row
        cpx     #$0C                    ; 74AF  12 rows in the window
        bcc     L_7466                  ; 74B1  more rows to draw
        lda     #$00                    ; 74B3  already 0 - setTerrainTilePtr clears it on every glyph
        sta     useDrawColourFlag       ; 74B5  redundant clear of textDrawParams
        lda     drawCellSaveCol         ; 74B8  restore the view origin column...
        sta     viewOriginCol           ; 74BB  ...into zp_A5
L_74BD:
        lda     drawCellSaveRow         ; 74BD  restore the view origin row...
        sta     viewOriginRow           ; 74C0  ...into zp_A6
        rts                             ; 74C2  done


; ----------------------------------------------------------------------
; getRadarCellGlyph - Turns one map cell byte into the glyph index and colour that drawRadarGlyph
; needs. X and Y are preserved through self-modified operands, so it can be called from inside a
; rendering loop. Cells outside the 40x40 map become one of four hatch glyphs ($28-$2B, chosen by
; (row+column) & 3) in the terrain colour. Terrain cells become glyph (cell - $40) in
; radarTerrainColour ($9239). A cell holding a unit ($80 | index) becomes unitGlyphBaseTable[type] +
; facing: in radarOwnUnitColour ($9237) for the local player's units, and for the opponent's units in
; radarEnemyUnitColour ($9238) only while the unit is spotted (unitEnergyTable bit 7). An unspotted
; enemy normally shows the terrain saved under it, except that roughly every 1-8th one (countdown
; radarGhostCountdown $920F, reseeded from the RNG) is drawn as a speckled 'ghost' blip (glyph | $80)
; in the terrain colour. A dead unit (unitTypeTable bit 7) also shows the terrain hidden under it. A
; cloaked unit (unitPathColTable bit 7) sets radarHalfToneFlag so it is drawn on alternate scanlines.
; In:  A = the map cell byte, zp_A5/zp_A6 = that cell's map coordinates,
;     radarOwnUnitColour/radarEnemyUnitColour/radarTerrainColour, radarGhostCountdown, unit arrays
;     $F708/$F76C/$F898/$F9C4/$FAF0, unitGlyphBaseTable ($0799)
; Out: A = glyph index (bit 7 set = draw with noise), tileColour (zp_32) = colour byte,
;     radarHalfToneFlag ($922D) = $FF for a cloaked unit, radarGhostCountdown updated; X and Y
;     restored
; Called from: drawDroneRadarWindow ($7471), drawRadarMap ($7C0B) and the main program's
;     drawScreen1Cell ($5DF2)
; Self-modifying: $750A is NOT a constant BCS. loadOverlayB in the main program copies the
; side-dependent opcode from $206B into it right after loading this overlay ($10A5): $B0 (BCS) when
; the local player is side 0 so units 50-99 are the enemy, $90 (BCC) when the local player is side 1
; so units 0-49 are. Treat $750A as a variable. Quirk: a dead unit's terrain is emitted with
; tileColour still set to radarOwnUnitColour (set at $74F6) rather than the terrain colour; on screen
; 1 both are $0B so it is invisible, on the drone camera it tints dead units' ground dark grey instead
; of orange. If the saved terrain byte happens to be 0 the 'bne' at $7503 falls through and the dead
; unit is drawn as a live GRUNT - terrain 0 does not occur on a real map ($40 is blank).
; ----------------------------------------------------------------------
getRadarCellGlyph:
        stx     L_754C+1                ; 74C3  self-modifying: save the caller's X into the 'ldx #' operand at $754D
        sty     L_754E+1                ; 74C6  and the caller's Y into the 'ldy #' operand at $754F
L_74C9:
        tay                             ; 74C9  keep the map cell byte in Y while the coordinates are range-checked
        lda     viewOriginCol           ; 74CA  this cell's map column
        cmp     #$28                    ; 74CC  the map is 40 columns wide
        bcs     L_74D6                  ; 74CE  off the map - draw hatching
        lda     viewOriginRow           ; 74D0  this cell's map row
L_74D2:
        cmp     #$28                    ; 74D2  the map is 40 rows high
        bcc     L_74E1                  ; 74D4  on the map - use the real cell byte

; ----------------------------------------------------------------------
; Off-map cell: pick one of the four hatch patterns so the border round the battlefield gets a woven
; look instead of a flat block.
; ----------------------------------------------------------------------
L_74D6:
        lda     viewOriginRow           ; 74D6  row...
        clc                             ; 74D8  prepare the add
        adc     viewOriginCol           ; 74D9  ...plus column
        and     #$03                    ; 74DB  pick one of four patterns
L_74DD:
        clc                             ; 74DD  prepare the add
        adc     #$68                    ; 74DE  map codes $68-$6B are the four hatch tiles
L_74E0:
        tay                             ; 74E0  pretend the map held that code

; ----------------------------------------------------------------------
; Default: the cell is plain terrain, drawn in the terrain colour.
; ----------------------------------------------------------------------
L_74E1:
        ldx     radarTerrainColour      ; 74E1  terrain colour ($9239): 8 on the drone camera, $0B on screen 1
        stx     tileColour              ; 74E4  zp_32 is the colour drawRadarGlyph will write
        tya                             ; 74E6  the cell byte back into A
        bmi     L_74F3                  ; 74E7  bit 7 set = a unit stands on this cell
L_74E9:
        sec                             ; 74E9  terrain codes start at $40...
        sbc     #$40                    ; 74EA  ...so the glyph index in the $9C6E tile set is cell - $40
L_74EC:
        bcs     L_754C                  ; 74EC  normal terrain - return the glyph
glyphFromLowCellCode:
        adc     #$20                    ; 74EE  cell below $40 (should not happen): wrap to cell - $20
        jmp     L_754C                  ; 74F0  return the glyph


; ----------------------------------------------------------------------
; A unit stands here. Work out its glyph from type and facing, and its colour from the side, whether
; it is spotted, dead or cloaked.
; ----------------------------------------------------------------------
L_74F3:
        and     #$7F                    ; 74F3  strip the 'unit here' bit...
        tay                             ; 74F5  ...leaving the unit index 0-99 in Y
unitCellToGlyph:
        lda     radarOwnUnitColour      ; 74F6  assume it is one of ours
        sta     tileColour              ; 74F9  zp_32 = own unit colour
        lda     unitTypeTable,y         ; 74FB  the unit's type byte (bit 7 = destroyed)
testUnitAliveForRadar:
        bpl     L_7505                  ; 74FE  alive - carry on (label sub_74FE is a variant-A entry point only)
        lda     unitTerrainUnderTable,y ; 7500  dead: show the terrain that was hidden under it
        bne     L_74E9                  ; 7503  and render it as terrain (note: the colour is still the own-unit colour)
L_7505:
        and     #$07                    ; 7505  type 0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN
        tax                             ; 7507  X = unit type, the index into unitGlyphBaseTable
        cpy     #$32                    ; 7508  units 0-49 are side 0, 50-99 are side 1
radarSideBranchOpcode:
        bcs     L_7518                  ; 750A  SIDE-DEPENDENT OPCODE: patched by loadOverlayB ($10A5) from $206B - $B0 (BCS) for side 0, $90 (BCC) for side 1; taken when this unit belongs to the opponent
L_750C:
        lda     unitFlagsTable,y        ; 750C  the unit's flags byte
        and     #$03                    ; 750F  bits 0-1 = facing
        clc                             ; 7511  prepare the add
        adc     unitGlyphBaseTable,x    ; 7512  base glyph per type ($2C GRUNT, $30 RIDER, $34 BOOMER, $38 SPY, $3C COMCEN) plus the facing
        jmp     L_7542                  ; 7515  go and check the cloak flag


; ----------------------------------------------------------------------
; The unit belongs to the opponent. It is only drawn if we can currently see it; otherwise the terrain
; under it is shown, with an occasional random 'ghost' blip to keep the radar noisy.
; ----------------------------------------------------------------------
L_7518:
        lda     radarEnemyUnitColour    ; 7518  enemy colour (1 = white on screen 1, so the sweep can find these cells; $09 brown on the camera)
        sta     tileColour              ; 751B  zp_32 = enemy colour
        lda     unitEnergyTable,y       ; 751D  the unit's energy byte; bit 7 = currently visible/spotted
        bmi     L_750C                  ; 7520  spotted - draw it normally in the enemy colour
hiddenEnemyCell:
        lda     unitTerrainUnderTable,y ; 7522  not spotted: fall back to the terrain hidden under it
        dec     radarGhostCountdown     ; 7525  count down towards the next ghost blip
        bpl     L_74E0                  ; 7528  not due yet - just show the terrain
        jsr     nextGameRandom          ; 752A  due: reseed the countdown from the shared RNG...
        and     #$07                    ; 752D  ...with 0-7, so about one unspotted enemy in five is ghosted
        sta     radarGhostCountdown     ; 752F  radarGhostCountdown ($920F)
        lda     unitFlagsTable,y        ; 7532  the unit's flags byte
        and     #$03                    ; 7535  bits 0-1 = facing
        clc                             ; 7537  prepare the add
        adc     unitGlyphBaseTable,x    ; 7538  the unit's real glyph
        ora     #$80                    ; 753B  bit 7 = tell drawRadarGlyph to speckle it with the noise masks
        ldx     radarTerrainColour      ; 753D  and draw the ghost in the terrain colour, not the enemy colour
        stx     tileColour              ; 7540  zp_32 = terrain colour

; ----------------------------------------------------------------------
; Cloak test, reached for every unit glyph: a cloaked unit is drawn on alternate scanlines only.
; ----------------------------------------------------------------------
L_7542:
        ldx     unitPathColTable,y      ; 7542  the unit's waypoint-column byte, whose bit 7 is the CLOAK flag
        bpl     L_754C                  ; 7545  not cloaked - done
        ldx     #$FF                    ; 7547  $FF = half-tone
        stx     radarHalfToneFlag       ; 7549  radarHalfToneFlag ($922D); drawRadarGlyph clears it again after the cell
L_754C:
        ldx     #$FF                    ; 754C  operand patched at $74C3 - restore the caller's X
L_754E:
        ldy     #$FF                    ; 754E  operand patched at $74C6 - restore the caller's Y
        rts                             ; 7550  done; A = glyph index, zp_32 = colour


; Work byte of playStrikeZoomAnimation (see below).  In overlay A the same address holds unrelated
; map-generator data, so the label is unit-scoped.
zoomStepCounter:
        .byte   $01                     ; 7551  .  zoom step counter: loaded with 3 and counted down to $FF (four magnification passes)

; ----------------------------------------------------------------------
; playStrikeZoomAnimation - Drone strike effect on the DRONE screen: magnifies the middle of the 14x12
; drone camera window four times in a row, each pass blowing the centre 7x6 cells up to 2x2 characters
; per cell so the picture appears to dive into the target, then hands over to playDroneLostAnimation
; which flashes and wipes the window and shrinks the drone icon away.
; In:  nothing; the camera window must already show the drone's surroundings.  Uses zoomStepCounter
;      ($7551) as the pass counter.
; Out: the camera window (rows 6-17, columns 13-26) is left magnified; zoomStepCounter = $FF; never
;      returns - it jumps into playDroneLostAnimation ($7163).
; Called from: strikeWithDrone's wait loop ($81CD), right after sound $11 (the detonation).
; Four magnification passes: 12, 10, 8 and 6 game frames are run between them.
; ----------------------------------------------------------------------
playStrikeZoomAnimation:
        lda     #$03                    ; 7552  four zoom passes: the counter runs 3,2,1,0
        sta     zoomStepCounter         ; 7554  pass counter for the loop below

; ----------------------------------------------------------------------
; One zoom pass.  magnifyWindow2x eats windowWidth/windowHeight, so both are reloaded every time.
; Source = the 7x6 cell block at row 9, column 16; the routine centres it, so the result covers rows
; 6-17 and columns 13-26 - exactly the drone camera window.
; ----------------------------------------------------------------------
L_7557:
        lda     #$09                    ; 7557  fillRow = character row 9
        sta     windowOriginRow         ; 7559  row of the source block magnifyWindow2x reads
        lda     #$10                    ; 755C  fillColumn = character column 16
        sta     windowOriginCol         ; 755E  column of the source block
        lda     #$07                    ; 7561  source block is 7 cells wide
        sta     frameWidth              ; 7563  windowWidth for magnifyWindow2x
        lda     #$06                    ; 7566  source block is 6 cells high
        sta     frameHeight             ; 7568  windowHeight for magnifyWindow2x
        jsr     magnifyWindow2x         ; 756B  blow those 7x6 cells up 2x2 in place, filling the whole 14x12 camera window
        lda     zoomStepCounter         ; 756E  pass number 3..0
        clc                             ; 7571  clear carry before the add
        adc     #$03                    ; 7572  pass+3 = 6,5,4,3
        asl     a                       ; 7574  doubled: 12,10,8,6 game frames of pause after this pass
        jsr     runGameFrames           ; 7575  keep the battle running while the picture is held
        dec     zoomStepCounter         ; 7578  one pass done
        bpl     L_7557                  ; 757B  branch while zoom passes remain (counter 3..0; it leaves the loop at $FF)
        lda     #$06                    ; 757D  hold the final, fully magnified picture
        jsr     runGameFrames           ; 757F  for 6 more game frames
        jmp     playDroneLostAnimation  ; 7582  continue with the drone-destroyed animation (white flash, iris wipe, shrinking icon)


; Two state bytes of the missile screen.
missileScreenLocked:
        .byte   $00                     ; 7585  .  bit 7 set while the missile screen is inoperative (SCRIMAGE game type, or our comcen is cloaked)

; cursorMovedFlag: byteTable, 1 bytes. 0 until the missile cursor was moved, then $7F
cursorMovedFlag:
        .byte   $00                     ; 7586  .  0 until the target cursor has been moved once, then $7F; firing is refused while it is 0

; ----------------------------------------------------------------------
; runMissileScreen - Entry of game screen 1, the comcen RADAR / MISSILE screen (function key F3).
; Resets the 'cursor moved' interlock, takes a display copy of the local missile count, unpacks the
; eight direction sprite shapes used for the enemy drone marker and builds the whole screen; then
; falls into missileScreenResume, which never returns.
; In:  missilesLeft ($92B0, own side), comcenUnit ($9236), the $F000 map and the unit arrays.
; Out: cursorMovedFlag = 0, radarMissilesLeft ($9232) = missilesLeft, $0200 = unpacked sprite shapes,
;      screen 1 drawn.  Never returns.
; Called from: the screen dispatcher at $0703 (game/scratchAndMessages0400) when currentScreen = 1.
; ----------------------------------------------------------------------
runMissileScreen:
        lda     #$00                    ; 7587  clear the interlock
        sta     cursorMovedFlag         ; 7589  a fire press does nothing until the cursor has been moved
        lda     missilesLeft            ; 758C  missiles the local player still has
        sta     radarMissilesLeft       ; 758F  display copy shown in the MISSILES box and decremented as they are launched
        lda     #$70                    ; 7592  low byte of the packed sprite shapes at $FD70
        ldy     #$FD                    ; 7594  high byte: $FD70 in the resident high-memory block
        jsr     unpackRleTo0200         ; 7596  unpack the eight direction/aircraft silhouettes to $0200 for loadDirectionShapeSprite
        jsr     initRadarScreen         ; 7599  draw the frames, the MISSILES/FIRE/DRONE ALERT panels and the 21x17 radar map

; ----------------------------------------------------------------------
; missileScreenResume - Main loop of the missile screen, and the re-entry point used after the 'COMCEN
; STUNNED!' blackout cut-scene.  Decides first whether the screen works at all (it is dead in a
; SCRIMAGE game, which has no comcens, and while our own comcen is cloaked); if it works the radar
; picture, the sweep and the target cross are (re)built.  The loop then runs one game frame at a time,
; blinks the FIRE button and - on the last missile - the count box, redraws after every command
; exchange, advances the radar sweep, and turns a fire press into a $90 (missile strike) command or a
; joystick deflection into a cursor move with auto-repeat.
; In:  gameTypeOptions ($0BA3 bits 0-2), comcenCloakedFlag ($9245), exchangeAppliedFlag ($920E),
;      comcenRadarStatus ($9243), radarMissilesLeft ($9232), droneArrivalFlags ($92F9),
;      joyDirection (zp_5D), joyRepeatTimer (zp_6C), markerBlinkTimer (zp_6F), joyRepeatMask/
;      joyHoldCounter ($91C9/$91CA), joystickDirectionTable ($1FA6).
; Out: missileScreenLocked, cursorMovedFlag, radarMissilesLeft, joyRepeatMask = $7F, sprites 1-3,
;      $90 commands queued.  Never returns.
; Called from: fallen into from runMissileScreen; JMP from $0764 (end of runComcenStunnedBlackout).
; Is the screen usable?  Game type 0 (SCRIMAGE) is played without command centres, and a cloaked
; comcen runs its radar silent.  Either sets bit 7 of missileScreenLocked.
; ----------------------------------------------------------------------
missileScreenResume:
        lda     gameTypeOptions         ; 759C  game option byte
        and     #$07                    ; 759F  bits 0-2 = game type (0 SCRIMAGE .. 6 DEFENDER)
        bne     L_75A5                  ; 75A1  branch unless the type is 0 (SCRIMAGE), which is played without comcens
        lda     #$80                    ; 75A3  no comcen: mark the missile screen inoperative
L_75A5:
        ora     comcenCloakedFlag       ; 75A5  bit 7 of $9245 is set while our own comcen is cloaked (radar off the air)
        sta     missileScreenLocked     ; 75A8  bit 7 = screen locked
        bmi     L_75B9                  ; 75AB  branch if locked: keep ticking the game but draw nothing
        jsr     refreshMissileScreen    ; 75AD  enemy blips, missile count and the DRONE ALERT panel
        jsr     drawRadarMap            ; 75B0  the 21x17 cell radar map centred on our comcen
        jsr     restartRadarSweep       ; 75B3  start a fresh sweep from the top row
        jsr     showComcenCrossMarker   ; 75B6  sprite 2 = the target cross, parked on the comcen at grid cell (10,8)

; ----------------------------------------------------------------------
; Main loop.  One pass per game frame.  Everything below the 'locked' test is skipped while the screen
; is inoperative.
; ----------------------------------------------------------------------
L_75B9:
        jsr     updateGameFrame         ; 75B9  one game tick: IRQ counters, unit updates, link traffic
        lda     droneArrivalFlags       ; 75BC  drone status flags
        and     #$01                    ; 75BF  bit 0 = our own $92 'drone destroyed' command is queued but not yet acknowledged
        bne     L_75C6                  ; 75C1  branch if it is still pending - do not leave the screen before it is confirmed
        jsr     switchToRequestedScreen ; 75C3  F1/F3/F5/F7 may switch to another screen (never returns if it does)
L_75C6:
        lda     markerBlinkTimer        ; 75C6  IRQ frame countdown for the button blink
        bne     L_75E7                  ; 75C8  branch until the 9-frame blink interval has expired
        lda     #$09                    ; 75CA  next blink in 9 frames
        sta     markerBlinkTimer        ; 75CC  restart the countdown
        lda     #$0A                    ; 75CE  character row 10
        sta     windowOriginRow         ; 75D0  row of the 2x2 FIRE button icon
        lda     #$20                    ; 75D3  character column 32
        sta     windowOriginCol         ; 75D5  column of the FIRE button icon
        lda     #$08                    ; 75D8  EOR mask 8: flips bit 3 of the %10 colour nibble, so green 5 <-> light green $D
        jsr     xorColourBlock2x2       ; 75DA  toggle the 4 video-matrix cells of the button, making it pulse
        lda     radarMissilesLeft       ; 75DD  missiles left on the display
        cmp     #$02                    ; 75E0  compare with 2
        bcs     L_75E7                  ; 75E2  branch while at least 2 missiles remain
        jsr     blinkMissileCount       ; 75E4  down to the last missile: flash the count box as well
L_75E7:
        lda     missileScreenLocked     ; 75E7  screen lock flag
        bmi     L_75B9                  ; 75EA  branch if locked - nothing but the frame tick and the blink runs

; ----------------------------------------------------------------------
; A command exchange has been applied ($920E = $80 from $41F8): the opponent's units have moved, so
; the enemy blips and the drone panel are redrawn and a pending drone kill is shown.
; ----------------------------------------------------------------------
L_75EC:
        lda     exchangeAppliedFlag     ; 75EC  $80 after processCommandExchange has applied a turn's commands
        beq     L_75F7                  ; 75EF  branch unless a new exchange has arrived
        jsr     refreshMissileScreen    ; 75F1  redraw the enemy blips, the count and the DRONE ALERT panel
        jsr     showDroneKillBurst      ; 75F4  if our missile killed the drone, play the burst now
L_75F7:
        jsr     updateRadarSweep        ; 75F7  advance the sweep band by one step if its 2-frame slot is due

; ----------------------------------------------------------------------
; Fire button.  A press only counts once the cursor has been moved, and a comcen whose fire control is
; damaged loses roughly half of the presses.
; ----------------------------------------------------------------------
        jsr     checkFirePressedInteractive; 75FA  returns A = 0 (Z set) only for a fresh, accepted press
        bne     L_760E                  ; 75FD  no press this frame - go and read the joystick instead
        lda     cursorMovedFlag         ; 75FF  has the cursor ever been moved?
        beq     L_760E                  ; 7602  no - ignore the press (guards against firing at the comcen's own cell)
        lda     comcenRadarStatus       ; 7604  non-zero once the comcen radar/fire control has been knocked out
        beq     L_763F                  ; 7607  undamaged: the shot always goes off
        jsr     nextGameRandom          ; 7609  damaged: draw a random bit
        bmi     L_763F                  ; 760C  about half the presses still get through

; ----------------------------------------------------------------------
; Joystick: move the target cursor over the radar grid, with a 10-frame delay before the first repeat
; and 4 frames between the following ones.
; ----------------------------------------------------------------------
L_760E:
        lda     joyRepeatTimer          ; 760E  IRQ auto-repeat countdown
L_7610:
        bne     L_75B9                  ; 7610  branch back while the repeat delay is still running
        ldy     joyDirection            ; 7612  active-low joystick nibble latched by the IRQ ($0F = centred)
        lda     joystickDirectionTable,y; 7614  -> direction code 0-7 (N,S,E,W,NW,NE,SE,SW), $FF when centred
        bmi     L_75B9                  ; 7617  stick centred - nothing to do this frame
        ldy     #$0A                    ; 7619  10 frames before the first auto-repeat
        pha                             ; 761B  stash the direction code across the timing code

; ----------------------------------------------------------------------
; Auto-repeat pacing.  joyRepeatMask is cleared by the IRQ ($1220) whenever the stick returns to
; centre, so the first deflection sees mask 0 (counter stays 0 -> long delay) and every later frame
; sees mask $7F (counter free-runs -> short delay). (The label itself is only a branch target in the
; overlay A variant of this address.)
; ----------------------------------------------------------------------
radarCursorRepeatStep:
        inc     joyHoldCounter          ; 761C  count the frames the stick has been held
        lda     joyHoldCounter          ; 761F  reload it
        and     joyRepeatMask           ; 7622  mask = 0 on the very first deflection, $7F while the stick stays deflected
        sta     joyHoldCounter          ; 7625  store the masked counter back
        cmp     #$01                    ; 7628  still zero?
        bcc     L_762E                  ; 762A  yes - first step, keep the long 10-frame delay
radarCursorRepeatFast:
        ldy     #$04                    ; 762C  held down: repeat every 4 frames
L_762E:
        sty     joyRepeatTimer          ; 762E  arm the IRQ countdown
        lda     #$7F                    ; 7630  mask $7F
        sta     joyRepeatMask           ; 7632  from now on the hold counter free-runs, so the fast repeat sticks
        sta     cursorMovedFlag         ; 7635  $7F also marks 'the cursor has been moved', which unlocks firing
        pla                             ; 7638  recover the direction code
        jsr     moveRadarCursorByDirection; 7639  step the cursor one grid cell that way
        jmp     L_75B9                  ; 763C  back to the top of the loop


; ----------------------------------------------------------------------
; Fire accepted: spend a missile, light the button, queue command $90 (missile strike at col,row) and
; play the flight and impact animation.
; ----------------------------------------------------------------------
L_763F:
        lda     radarMissilesLeft       ; 763F  missiles left
checkMissilesRemain:
        beq     L_760E                  ; 7642  none left - the press does nothing
        dec     radarMissilesLeft       ; 7644  spend one
        jsr     drawMissileCount        ; 7647  redraw the two-digit count

; ----------------------------------------------------------------------
; Light the FIRE button while the missile is away.
; ----------------------------------------------------------------------
launchMissileEffect:
        lda     #$0A                    ; 764A  character row 10
        sta     windowOriginRow         ; 764C  row of the button icon
        lda     #$20                    ; 764F  character column 32
L_7651:
        sta     windowOriginCol         ; 7651  column of the button icon
        lda     #$05                    ; 7654  $05 = %01 pixels black, %10 pixels green: button lit
        ldx     #$02                    ; 7656  2 cells wide
        ldy     #$02                    ; 7658  2 cells high
        jsr     fillScreenColourRect    ; 765A  write the 4 video-matrix cells
        lda     #$0F                    ; 765D  sound $0F = missile launch
        jsr     playSound               ; 765F  play it
        jsr     radarCursorToMapCoords  ; 7662  cmdParam1/cmdParam2 = the map cell under the target cursor
        lda     #$90                    ; 7665  command $90 = missile strike at col,row
        jsr     queueCmd3               ; 7667  queue it for this turn's packet (and for the game film)
        jsr     animateMissileFlight    ; 766A  fly sprite 3 from the comcen to the target cell

; ----------------------------------------------------------------------
; Impact: put the ring-burst sprite 1 exactly where the target cross (sprite 2) is.
; ----------------------------------------------------------------------
L_766D:
        lda     sprite2XShadow          ; 766D  target cross X
        sta     sprite1XShadow          ; 7670  -> ring burst X
        lda     sprite2YShadow          ; 7673  target cross Y
        sta     sprite1YShadow          ; 7676  -> ring burst Y
        lda     #$03                    ; 7679  3 radius steps
        jsr     animateRingBurst        ; 767B  small expanding ring at the impact point
        jsr     checkStrikeHitsDrone    ; 767E  did the shot hit an airborne drone?
        lda     #$0A                    ; 7681  10 frames
        sta     fireDebounceTimer       ; 7683  swallow further fire presses for that long
        jmp     L_75B9                  ; 7685  back to the top of the loop


; ----------------------------------------------------------------------
; refreshMissileScreen - Redraw hook of screen 1, run once when the screen is built and again after
; every applied command exchange: acknowledges the refresh request, repaints every enemy unit blip
; with a fresh noise pattern and puts the DRONE ALERT panel into its flashing or its dark state
; depending on whether any drone is airborne.  With no drone up it also blanks and disables sprite 0
; (the enemy drone marker) and sprite 1 (the ring burst).
; In:  droneControlFlags ($920D bit 0, set by cmdDroneMove while a drone is flying), gameEndReason
;      ($0BA8), the unit arrays.
; Out: exchangeAppliedFlag = 0, blips redrawn, radarNoiseMasks re-randomised, sprites 0/1 off or
;      left alone, DRONE ALERT panel repainted.  Tail-jumps into the panel routines.
; Called from: missileScreenResume ($75AD screen build, $75F1 after an exchange).
; (survey name sub_7688)
; ----------------------------------------------------------------------
refreshMissileScreen:
        lda     #$00                    ; 7688  clear the request
        sta     exchangeAppliedFlag     ; 768A  the refresh has been served
        jsr     redrawEnemyUnitsOnRadar ; 768D  repaint all 50 enemy units with a new random dropout pattern
        lda     gameEndReason           ; 7690  0 while a game is running; 1 = aborted, $08 = time up, $A0/$C0 = a comcen was knocked out
        beq     L_7698                  ; 7693  branch while a game is running - so the count is only refreshed once the game is already over,

; ----------------------------------------------------------------------
; which looks like an inverted test (the count is redrawn from the fire handler at $7647 anyway, so
; during play this branch simply never fires).
; ----------------------------------------------------------------------
L_7695:
        jsr     drawMissileCount        ; 7695  redraw the two-digit missile count

; ----------------------------------------------------------------------
; DRONE ALERT panel: flashing while any drone is up, dark otherwise.
; ----------------------------------------------------------------------
L_7698:
        lda     droneControlFlags       ; 7698  drone control flags
        and     #$01                    ; 769B  bit 0 is set by cmdDroneMove ($52E9) while a drone - ours or theirs - is in the air
        bne     L_76AF                  ; 769D  a drone is up: flash the panel
        lda     #$01                    ; 769F  sprite 1 = the ring burst
        jsr     clearSpriteShape        ; 76A1  blank its shape
        lda     spriteEnableShadow      ; 76A4  sprite enable shadow
        and     #$FC                    ; 76A7  clear bits 0 and 1: drone marker (sprite 0) and ring burst (sprite 1) off
L_76A9:
        sta     spriteEnableShadow      ; 76A9  store it back
        jmp     resetDroneAlertPanel    ; 76AC  print DRONE / ALERT dark and return through it

L_76AF:
        jmp     flashDroneAlertPanel    ; 76AF  toggle the panel colour and return through it


; ----------------------------------------------------------------------
; moveRadarCursorByDirection - Turns a joystick direction code 0-7 into a (dCol,dRow) pair and hands
; it to offsetRadarCursor.  Codes 0/1 are N/S, 2/3 are E/W and 4-7 are the diagonals, whose deltas are
; packed one per nibble in diagonalDirectionTable; a diagonal step also stretches the auto-repeat
; delay by 1.5 so diagonal travel is not faster than straight travel.
; In:  A = direction code 0-7 (from joystickDirectionTable), joyRepeatTimer (zp_6C).
; Out: X = column delta, Y = row delta, then tail-calls offsetRadarCursor; zp_6C stretched for
;      diagonals.
; Called from: missileScreenResume ($7639).
; (survey name sub_76B2)
; ----------------------------------------------------------------------
moveRadarCursorByDirection:
        ldy     #$00                    ; 76B2  row delta defaults to 0
        ldx     #$00                    ; 76B4  column delta defaults to 0
        cmp     #$04                    ; 76B6  codes 0-3 are orthogonal, 4-7 diagonal
L_76B8:
        bcs     L_76CE                  ; 76B8  branch for the four diagonals
        cmp     #$02                    ; 76BA  codes 0/1 = N/S, 2/3 = E/W
        bcs     L_76C6                  ; 76BC  branch for the two horizontal directions
L_76BE:
        tay                             ; 76BE  N/S: use the code as the table index
        lda     directionDeltaTable,y   ; 76BF  $20F0: index 0 -> $FF (up), 1 -> $01 (down)
        tay                             ; 76C2  that is the row delta
        jmp     offsetRadarCursor       ; 76C3  apply it (column delta stays 0)

L_76C6:
        tax                             ; 76C6  E/W: use the code as the table index
        lda     directionDeltaTable,x   ; 76C7  $20F0: index 2 -> $01 (right), 3 -> $FF (left)
        tax                             ; 76CA  that is the column delta
        jmp     offsetRadarCursor       ; 76CB  apply it (row delta stays 0)

L_76CE:
        tay                             ; 76CE  diagonal: index the packed table with the code
        lda     diagonalDirectionTable,y; 76CF  $1FB2+4..7 = $30,$20,$21,$31 = (column index << 4) | row index
        pha                             ; 76D2  keep the packed byte
        and     #$0F                    ; 76D3  low nibble = row index into directionDeltaTable
        tay                             ; 76D5  as index
        lda     directionDeltaTable,y   ; 76D6  -1 or +1
        tay                             ; 76D9  row delta
        pla                             ; 76DA  packed byte back
        lsr     a                       ; 76DB  shift the
        lsr     a                       ; 76DC  high nibble
        lsr     a                       ; 76DD  down into
        lsr     a                       ; 76DE  bits 0-3
        tax                             ; 76DF  column index into directionDeltaTable
        lda     directionDeltaTable,x   ; 76E0  -1 or +1
        tax                             ; 76E3  column delta
        lda     joyRepeatTimer          ; 76E4  current auto-repeat delay
        lsr     a                       ; 76E6  half of it
        clc                             ; 76E7  clear carry before the add
        adc     joyRepeatTimer          ; 76E8  delay + delay/2
        sta     joyRepeatTimer          ; 76EA  a diagonal step costs 1.5 repeat delays, so diagonal travel is not faster

; ----------------------------------------------------------------------
; offsetRadarCursor - Moves the missile target cursor by (X,Y) grid cells, refusing the move if it
; would leave the 21x17 radar grid, and repositions the cross-hair sprite 2 on the new cell.
; In:  X = column delta, Y = row delta, gridCursorCol/gridCursorRow (zp_67/zp_68).
; Out: zp_67/zp_68 and sprite2XShadow/sprite2YShadow updated, or nothing at all when the target cell
;      is off the grid.
; Called from: moveRadarCursorByDirection ($76C3, $76CB) and scrollRadarOnComcenMove ($797B).
; Self-modifying: the candidate column and row are parked in the LDA # operands at setCursorColImm+1
; ($7702) and setCursorRowImm+1 ($7706) while the range test runs. (survey name sub_76EC)
; ----------------------------------------------------------------------
offsetRadarCursor:
        txa                             ; 76EC  column delta
        clc                             ; 76ED  clear carry before the add
        adc     gridCursorCol           ; 76EE  current cursor column 0-20
        tax                             ; 76F0  candidate column in X for the range test
        stx     setCursorColImm+1       ; 76F1  park it in the LDA # operand at $7701 (self-modifying code)
        tya                             ; 76F4  row delta
        clc                             ; 76F5  clear carry before the add
        adc     gridCursorRow           ; 76F6  current cursor row 0-16
        tay                             ; 76F8  candidate row in Y for the range test
        sty     setCursorRowImm+1       ; 76F9  park it in the LDA # operand at $7705 (self-modifying code)
        jsr     isRadarGridCellInRange  ; 76FC  C = 0 only when the cell lies inside the 21x17 grid
        bcs     L_7712                  ; 76FF  off the grid - leave the cursor where it was
setCursorColImm:
        lda     #$FF                    ; 7701  operand patched at $76F1: the accepted column
        sta     gridCursorCol           ; 7703  new cursor column
setCursorRowImm:
        lda     #$FF                    ; 7705  operand patched at $76F9: the accepted row
        sta     gridCursorRow           ; 7707  new cursor row
        jsr     gridCellToSpriteCoords  ; 7709  grid cell -> sprite coordinates (column*8+$27, row*8+$43)
        stx     sprite2XShadow          ; 770C  move the target cross horizontally
        sty     sprite2YShadow          ; 770F  and vertically
L_7712:
        rts                             ; 7712  done


; ----------------------------------------------------------------------
; animateMissileFlight - Flies the launched missile across the radar window: builds a short slanted
; dash in sprite 3 pointing along the line from the comcen cell (grid 10,8) to the target cursor, then
; walks that line pixel by pixel, moving the sprite every 6 frames and taking more pixel steps per
; move as the flight goes on (X = squaresTable[move/4]) so the missile accelerates.  The battle keeps
; running underneath, so the 12-byte line stepper state is saved and restored around every game frame.
; The animation stops when the line is exhausted or the game ends.
; In:  gridCursorCol/gridCursorRow (zp_67/zp_68) = the target cell, missileFlightTick ($92F8) which
;      the IRQ increments once per frame, gameEndReason ($0BA8).
; Out: sprite 3 shape/position/colour, sprite 3 enabled then disabled again, line stepper state
;      ($8C-$97) consumed, savedLineState ($92CA) used as scratch.
; Called from: missileScreenResume ($766A), right after the $90 command has been queued.
; Self-modifying: the sprite origin bytes at missileSpriteXOrigin+1/$77E0 and missileSpriteYOrigin+1/
;      $77E8, the frame threshold at missileTickThresholdCmp+1/$7808 and the move counter at
;      missileStepIndexLoad+1/$782B are all written by this routine.
; (survey name sub_7713) Rebase both endpoints of the flight to the top-left corner of their bounding
; box, so the line stepper works with small positive numbers; scratch20/scratch21 keep that corner in
; grid cells.
; ----------------------------------------------------------------------
animateMissileFlight:
        lda     #$0A                    ; 7713  the comcen is always drawn at grid column 10
        ldy     gridCursorCol           ; 7715  target column 0-20

; ----------------------------------------------------------------------
; $7717 is only a fall-through point here (in the overlay A variant the same address is the map
; generator's enterMapName, which is why the survey gave it a label).
; ----------------------------------------------------------------------
missileLineFromComcen:
        jsr     rebasePairToMin         ; 7717  X = min of the two, A and Y = both values minus that min
        stx     scratch20               ; 771A  left column of the bounding box, in grid cells
        sta     lineCurCol              ; 771C  line start column, relative to the box
        sta     scratch1C               ; 771E  keep a copy: the line is walked twice
        sty     lineEndCol              ; 7720  line end column, relative to the box
        sty     scratch1E               ; 7722  copy
        lda     #$08                    ; 7724  the comcen is always drawn at grid row 8
        ldy     gridCursorRow           ; 7726  target row 0-16
        jsr     rebasePairToMin         ; 7728  same rebasing for the rows
        stx     scratch21               ; 772B  top row of the bounding box
        sta     lineCurRow              ; 772D  line start row
        sta     scratch1D               ; 772F  copy
        sty     lineEndRow              ; 7731  line end row
        sty     scratch1F               ; 7733  copy
        tya                             ; 7735  end row
        ora     scratch1E               ; 7736  OR in the end column
        ora     scratch1D               ; 7738  OR in the start row
L_773A:
        ora     scratch1C               ; 773A  OR in the start column
        bne     L_7741                  ; 773C  branch if any of the four is non-zero
        jmp     L_7846                  ; 773E  all four zero: the cursor sits on the comcen itself, nothing to fly

L_7741:
        cmp     #$04                    ; 7741  is the longest side at least 4 cells?
        bcs     L_7755                  ; 7743  yes - the coordinates already have enough resolution

; ----------------------------------------------------------------------
; Very short shot: scale the four coordinates by 4 so that the three-step probe below still finds the
; true direction of the line.
; ----------------------------------------------------------------------
        asl     lineCurCol              ; 7745  start column x2
        asl     lineCurCol              ; 7747  x4
        asl     lineCurRow              ; 7749  start row x2
        asl     lineCurRow              ; 774B  x4
        asl     lineEndCol              ; 774D  end column x2
        asl     lineEndCol              ; 774F  x4
L_7751:
        asl     lineEndRow              ; 7751  end row x2
        asl     lineEndRow              ; 7753  x4

; ----------------------------------------------------------------------
; Build the missile shape: walk three units along the line, then draw that little segment twice (one
; pixel apart) into sprite 3, giving a 4-pixel long, 2-pixel thick dash aimed at the target.
; ----------------------------------------------------------------------
L_7755:
        lda     lineCurCol              ; 7755  line start column
        sta     scratch1A               ; 7757  remember it
        lda     lineCurRow              ; 7759  line start row
        sta     scratch1B               ; 775B  remember it
L_775D:
        jsr     initLineStepper         ; 775D  set up the Bresenham walk from (start) to (end)
        jsr     stepLine                ; 7760  step 1
        jsr     stepLine                ; 7763  step 2
        jsr     stepLine                ; 7766  step 3 - far enough to know the direction
        lda     scratch1A               ; 7769  segment start column
        ldy     lineCurCol              ; 776B  segment end column after 3 steps
        jsr     rebasePairToMin         ; 776D  rebase the short segment to its own origin
        sta     scratch1A               ; 7770  segment start column, 0-based
        sty     lineEndCol              ; 7772  segment end column
        lda     scratch1B               ; 7774  segment start row
        ldy     lineCurRow              ; 7776  segment end row after 3 steps
        jsr     rebasePairToMin         ; 7778  rebase the rows the same way
        sta     scratch1B               ; 777B  segment start row, 0-based
        sty     lineEndRow              ; 777D  segment end row
        lda     #$03                    ; 777F  sprite 3 is the missile
        jsr     clearSpriteShape        ; 7781  blank its 64-byte shape
        jsr     plotMissileTrailSegment ; 7784  plot the 4-pixel dash
        inc     scratch1A               ; 7787  shift the start one pixel right
        inc     lineEndCol              ; 7789  and the end with it
        jsr     plotMissileTrailSegment ; 778B  plot it again: the dash is now 2 pixels thick

; ----------------------------------------------------------------------
; Now walk the real flight path.  The four saved cell coordinates are turned into pixels (8 pixels per
; grid cell) and stepped one pixel at a time.
; ----------------------------------------------------------------------
        ldy     #$03                    ; 778E  index of the last of the four saved coordinates ($1C..$1F)
L_7790:
        lda     a:scratch1C,y           ; 7790  one saved coordinate (absolute addressing forced so it is a 3-byte instruction)
        asl     a                       ; 7793  x2
        asl     a                       ; 7794  x4
L_7795:
        asl     a                       ; 7795  x8 - one grid cell is 8 pixels
        sta     a:scratch1C,y           ; 7796  write it back
        dey                             ; 7799  next coordinate
        bpl     L_7790                  ; 779A  loop over start column, start row, end column, end row
        lda     scratch1C               ; 779C  start column in pixels
        sta     lineCurCol              ; 779E  into the line stepper
        lda     scratch1D               ; 77A0  start row in pixels
        sta     lineCurRow              ; 77A2  into the line stepper
        lda     scratch1E               ; 77A4  end column in pixels
        sta     lineEndCol              ; 77A6  into the line stepper
        lda     scratch1F               ; 77A8  end row in pixels
        sta     lineEndRow              ; 77AA  into the line stepper
        jsr     initLineStepper         ; 77AC  set the walk up again, now measured in pixels
        jsr     stepLine                ; 77AF  skip the first pixel
        jsr     stepLine                ; 77B2  and the second, so the missile starts clear of the comcen
        ldx     scratch20               ; 77B5  bounding box origin column (grid cells)
        ldy     scratch21               ; 77B7  bounding box origin row
        jsr     isRadarGridCellInRange  ; 77B9  the returned carry is never tested - a leftover range check
        jsr     gridCellToSpriteCoords  ; 77BC  -> sprite coordinates of that corner cell
        txa                             ; 77BF  sprite X of the corner
        clc                             ; 77C0  clear carry before the add
        adc     #$0B                    ; 77C1  +11 pixels centres the dash inside the cell
        sta     missileSpriteXOrigin+1  ; 77C3  patch the ADC # operand at $77DF (self-modifying code)
        tya                             ; 77C6  sprite Y of the corner
        clc                             ; 77C7  clear carry before the add
        adc     #$09                    ; 77C8  +9 pixels centres the dash vertically
        sta     missileSpriteYOrigin+1  ; 77CA  patch the ADC # operand at $77E7 (self-modifying code)
        lda     #$00                    ; 77CD  restart the frame counter
        sta     missileFlightTick       ; 77CF  $92F8 is incremented once per frame by the IRQ ($129C)
        lda     #$06                    ; 77D2  first move after 6 frames
        sta     missileTickThresholdCmp+1; 77D4  patch the CMP # operand at $7807 (self-modifying code)
        lda     #$00                    ; 77D7  no moves made yet
        sta     missileStepIndexLoad+1  ; 77D9  patch the LDA # operand at $782A (self-modifying code)

; ----------------------------------------------------------------------
; Per-move: place sprite 3 at the current pixel, then wait 6 frames while the game keeps running.
; ----------------------------------------------------------------------
L_77DC:
        lda     lineCurCol              ; 77DC  current pixel column along the line
        clc                             ; 77DE  clear carry before the add
missileSpriteXOrigin:
        adc     #$FF                    ; 77DF  operand patched at $77C3: sprite X of the bounding box corner + 11
        sta     sprite3XShadow          ; 77E1  sprite 3 X position
        lda     lineCurRow              ; 77E4  current pixel row along the line
        clc                             ; 77E6  clear carry before the add
missileSpriteYOrigin:
        adc     #$FF                    ; 77E7  operand patched at $77CA: sprite Y of the bounding box corner + 9
        sta     sprite3YShadow          ; 77E9  sprite 3 Y position
        lda     #$01                    ; 77EC  colour 1 = white
        sta     sprite3ColourShadow     ; 77EE  sprite 3 colour
        lda     #$08                    ; 77F1  sprite 3 enable bit
        jsr     enableSprites           ; 77F3  switch it on
        jsr     saveLineStepperState    ; 77F6  the game frame below reuses the line stepper ($8C-$97), so save it
L_77F9:
        jsr     updateRadarSweep        ; 77F9  keep the radar sweep running during the flight
        jsr     updateGameFrame         ; 77FC  one game tick: units keep moving, the link keeps talking
L_77FF:
        lda     gameEndReason           ; 77FF  0 while a game is running
        bne     L_783E                  ; 7802  the game ended under us - drop the animation
L_7804:
        lda     missileFlightTick       ; 7804  free-running frame counter
missileTickThresholdCmp:
        cmp     #$FF                    ; 7807  operand patched at $77D4/$781F: the frame at which the next move is due
        bcc     L_77F9                  ; 7809  not yet - run another frame
        lda     missileTickThresholdCmp+1; 780B  current threshold

; ----------------------------------------------------------------------
; Both counters are folded back into 0-$7F before the threshold could pass $FF, so the unsigned
; compare above stays meaningful for arbitrarily long flights.
; ----------------------------------------------------------------------
L_780E:
        bpl     L_781C                  ; 780E  still below $80 - no wrap needed
        and     #$7F                    ; 7810  fold the threshold back into 0-$7F
L_7812:
        tax                             ; 7812  keep it
        lda     missileFlightTick       ; 7813  frame counter
        and     #$7F                    ; 7816  fold it back the same way
        sta     missileFlightTick       ; 7818  store it
        txa                             ; 781B  threshold back into A
L_781C:
        clc                             ; 781C  clear carry before the add
        adc     #$06                    ; 781D  the next move is due 6 frames later
        sta     missileTickThresholdCmp+1; 781F  patch the CMP # operand at $7807
        jsr     restoreLineStepperState ; 7822  put the line stepper state back the way the game frame found it
        inc     missileStepIndexLoad+1  ; 7825  count this move (patches the LDA # operand at $782A)
L_7828:
        ldx     #$00                    ; 7828  at least one pixel step per move
missileStepIndexLoad:
        lda     #$FF                    ; 782A  operand patched at $77D9/$7825: the move number
        bmi     L_7834                  ; 782C  never taken in practice - the flight ends long before 128 moves
        lsr     a                       ; 782E  move/2
        lsr     a                       ; 782F  move/4
        tay                             ; 7830  as table index
        ldx     squaresTable,y          ; 7831  n*n extra steps: the missile accelerates as it travels
L_7834:
        jsr     stepLine                ; 7834  advance one pixel along the line
        beq     L_783E                  ; 7837  returns 0 when the line is exhausted - the missile has arrived
        dex                             ; 7839  one of the extra steps used
        bpl     L_7834                  ; 783A  keep stepping while X >= 0
        bmi     L_77DC                  ; 783C  always taken: redraw the sprite at its new position

; ----------------------------------------------------------------------
; Common exit: switch the missile sprite off again.
; ----------------------------------------------------------------------
L_783E:
        lda     spriteEnableShadow      ; 783E  sprite enable shadow

; ----------------------------------------------------------------------
; $7841 is only a fall-through point in this overlay (the same address is printMapName in the
; overlay A variant).
; ----------------------------------------------------------------------
hideMissileSprite:
        and     #$F7                    ; 7841  clear bit 3 = sprite 3 off
        sta     spriteEnableShadow      ; 7843  store it back
L_7846:
        rts                             ; 7846  done (also the early exit for a zero-length shot)


; ----------------------------------------------------------------------
; plotMissileTrailSegment - Draws one 4-pixel line segment into the shape of sprite 3, from
; (scratch1B,scratch1A) to (lineEndRow,lineEndCol): the start pixel plus three stepped pixels.
; Called twice by animateMissileFlight, one pixel apart, to make the missile dash 2 pixels thick.
; In:  scratch1A/scratch1B = segment start column/row, lineEndCol/lineEndRow = segment end.
; Out: 4 bits set in sprite 3's shape at $8AC0; the line stepper state is consumed.
; Called from: animateMissileFlight ($7784, $778B).
; (survey name sub_7847)
; ----------------------------------------------------------------------
plotMissileTrailSegment:
        lda     scratch1A               ; 7847  segment start column
        sta     lineCurCol              ; 7849  into the line stepper
        lda     scratch1B               ; 784B  segment start row
        sta     lineCurRow              ; 784D  into the line stepper
        jsr     initLineStepper         ; 784F  set the Bresenham walk up
        jsr     plotPathSpritePixel     ; 7852  plot pixel 1 (the start)
        jsr     stepAndPlotTrailPixel   ; 7855  step and plot pixel 2
L_7858:
        jsr     stepAndPlotTrailPixel   ; 7858  step and plot pixel 3

; ----------------------------------------------------------------------
; stepAndPlotTrailPixel - Advances the line stepper one cell and sets the resulting pixel in sprite
; 3's shape.  plotMissileTrailSegment both calls it and falls through into it, so the segment ends up
; 4 pixels long.
; In:  the active line stepper state ($8C-$97).
; Out: one bit set in $8AC0-$8AFF; returns through plotPathSpritePixel.
; Called from: plotMissileTrailSegment ($7855, $7858) and by falling through from it.
; (survey name sub_785B)
; ----------------------------------------------------------------------
stepAndPlotTrailPixel:
        jsr     stepLine                ; 785B  advance one pixel
        jmp     plotPathSpritePixel     ; 785E  set that pixel and return to the caller


; ----------------------------------------------------------------------
; saveLineStepperState - Copies the 12-byte line stepper working set $8C-$97 (both error accumulators,
; the deltas, the step signs, the current and the end point) into savedLineState ($92CA-$92D5) so that
; a game frame, which uses the same variables for unit movement, can run in the middle of the missile
; flight.
; In:  $8C-$97.
; Out: $92CA-$92D5.
; Called from: animateMissileFlight ($77F6).
; (survey name sub_7861)
; ----------------------------------------------------------------------
saveLineStepperState:
        ldy     #$0B                    ; 7861  12 bytes: lineErrRow $8C .. lineStepCol $97
L_7863:
        lda     a:lineErrRow,y          ; 7863  read one byte of the live stepper state (absolute addressing so Y indexes past $FF safely)
        sta     savedLineState,y        ; 7866  park it in the $92CA save area
        dey                             ; 7869  next byte
        bpl     L_7863                  ; 786A  loop over all 12
        rts                             ; 786C  done


; ----------------------------------------------------------------------
; restoreLineStepperState - Copies savedLineState ($92CA-$92D5) back over the 12-byte line stepper
; working set $8C-$97, undoing whatever the game frame did to it.
; In:  $92CA-$92D5.
; Out: $8C-$97.
; Called from: animateMissileFlight ($7822), before every stepLine burst.
; (survey name sub_786D)
; ----------------------------------------------------------------------
restoreLineStepperState:
        ldy     #$0B                    ; 786D  12 bytes
L_786F:
        lda     savedLineState,y        ; 786F  read the saved byte

; ----------------------------------------------------------------------
; $7872 is only a fall-through point here (the same address is randomTileVariant in the overlay A
; variant).
; ----------------------------------------------------------------------
restoreLineStateStore:
        sta     a:lineErrRow,y          ; 7872  write it back into the live stepper state
        dey                             ; 7875  next byte
        bpl     L_786F                  ; 7876  loop over all 12
        rts                             ; 7878  done


; ----------------------------------------------------------------------
; rebasePairToMin - Subtracts the smaller of A and Y from both, i.e. shifts a pair of coordinates so
; that the smaller one becomes 0, and returns that offset.  Used by animateMissileFlight to move the
; flight path into a small positive coordinate box before the x4 / x8 scaling can overflow a byte.
; In:  A = first value, Y = second value.
; Out: X = min(A,Y), A = A - X, Y = Y - X (one of A and Y is always 0); scratch18 clobbered.
; Called from: animateMissileFlight ($7717, $7728, $776D, $7778).
; (survey name sub_7879)
; ----------------------------------------------------------------------
rebasePairToMin:
        sty     scratch18               ; 7879  keep the second value
        cmp     scratch18               ; 787B  which of the two is smaller?
L_787D:
        bcs     L_788A                  ; 787D  branch if A >= Y
        sta     scratch18               ; 787F  A is the smaller one - it becomes the offset
        tya                             ; 7881  the larger value
        sec                             ; 7882  set carry for the subtract
        sbc     scratch18               ; 7883  Y - A
        tay                             ; 7885  back into Y
        lda     #$00                    ; 7886  and A itself becomes 0
        beq     L_788E                  ; 7888  always taken
L_788A:
        sbc     scratch18               ; 788A  A - Y (carry is already set by the CMP at $787B)
        ldy     #$00                    ; 788C  and Y becomes 0
L_788E:
        ldx     scratch18               ; 788E  return the offset that was subtracted

; ----------------------------------------------------------------------
; $7890 carries a label only because the overlay A variant writes to this byte; here it is just the
; RTS of rebasePairToMin.
; ----------------------------------------------------------------------
rebasePairToMinRts:
        rts                             ; 7890  done


; Three state bytes of the radar sweep animation, all zero on disk.  (The survey called the block
; radarSweepState; the three bytes are used independently, so they are named separately here.)
radarSweepStep:
        .byte   $00                     ; 7891  .  sweep step 0-30; steps 0-16 light grid row = step, steps 14-30 re-hide row = step-14
radarSweepRow:
        .byte   $00                     ; 7892  .  grid row paintRadarSweepRow is currently working on
radarSweepDarkFlag:
        .byte   $00                     ; 7893  .  $FF = this whole sweep stays dark (a damaged radar drops roughly every other sweep)

; ----------------------------------------------------------------------
; updateRadarSweep - Per-frame driver of the radar sweep on screen 1.  Every two frames it performs
; one sweep step: step s lights grid row s and re-hides grid row s-14, so a 14-row bright band wipes
; down the 21x17 radar window in 31 steps (about 62 frames); after the last step the sweep is
; restarted.  The pacing uses the free-running frame counter $92FB against a threshold kept in the CMP
; operand at radarSweepThresholdCmp+1 ($7898).
; In:  radarSweepTick ($92FB, bumped by the IRQ at $129F), radarSweepStep ($7891), the threshold byte
;      at $7898.
; Out: radarSweepStep incremented or reset, radarSweepRow = the row painted, $7898 += 2, video matrix
;      cells of one or two radar rows rewritten; A/X/Y clobbered.
; Called from: missileScreenResume ($75F7) and animateMissileFlight ($77F9).
; Self-modifying: the immediate operand of the CMP at $7897 is the next-step frame number; it is
;      advanced here and cleared by restartRadarSweep.
; ----------------------------------------------------------------------
updateRadarSweep:
        lda     radarSweepTick          ; 7894  free-running frame counter, incremented once per frame by the IRQ
radarSweepThresholdCmp:
        cmp     #$FF                    ; 7897  operand patched at $78A1/$78D6: the frame number of the next sweep step
        bcc     L_78CA                  ; 7899  not yet time for the next step
        lda     radarSweepThresholdCmp+1; 789B  current threshold
        clc                             ; 789E  clear carry before the add
        adc     #$02                    ; 789F  one sweep step every 2 frames
        sta     radarSweepThresholdCmp+1; 78A1  patch the CMP # operand at $7897
        lda     radarSweepStep          ; 78A4  current step number
        cmp     #$1F                    ; 78A7  31 steps make one full sweep
        bcs     restartRadarSweep       ; 78A9  sweep finished - start the next one (and return through it)
        sta     radarSweepRow           ; 78AB  the row to light is the step number itself
        cmp     #$11                    ; 78AE  the grid has 17 rows
        bcs     L_78B9                  ; 78B0  steps 17-30 are past the bottom, so there is no leading row left to light
        jsr     paintRadarSweepRow      ; 78B2  light grid row = step
        lda     radarSweepStep          ; 78B5  step number again

; ----------------------------------------------------------------------
; Reached with carry already set from the BCS at $78B0; the SEC only matters on the fall-through.
; ----------------------------------------------------------------------
D_78B8:
        sec                             ; 78B8  set carry for the subtract
L_78B9:
        sbc     #$0E                    ; 78B9  trailing edge of the band = step - 14 (a 14-row bright band)
        sta     radarSweepRow           ; 78BB  row to re-hide
        bcc     L_78C7                  ; 78BE  still negative - the band has not fully entered the window yet
        cmp     #$11                    ; 78C0  17 grid rows
        bcs     L_78C7                  ; 78C2  past the bottom row - nothing to re-hide
        jsr     paintRadarSweepRow      ; 78C4  darken that row again
L_78C7:
        inc     radarSweepStep          ; 78C7  next step
L_78CA:
        rts                             ; 78CA  done


; ----------------------------------------------------------------------
; restartRadarSweep - Starts a new sweep at grid row 0 and immediately: clears the step counter, the
; dark flag, the frame counter and the step threshold.  If the comcen's radar has been knocked out, a
; coin toss makes the entire coming sweep dark, so a damaged radar shows nothing on roughly half of
; its sweeps.
; In:  comcenRadarStatus ($9243, non-zero once the radar system is destroyed), the game RNG $57-$59.
; Out: radarSweepStep = 0, radarSweepDarkFlag = 0 or $FF, radarSweepTick = 0, threshold at $7898 = 0;
;      X clobbered.
; Called from: missileScreenResume ($75B3) and updateRadarSweep ($78A9, end of every sweep).
; ----------------------------------------------------------------------
restartRadarSweep:
        lda     #$00                    ; 78CB  zero
        sta     radarSweepStep          ; 78CD  sweep starts at grid row 0
        sta     radarSweepDarkFlag      ; 78D0  assume a normal, visible sweep
        sta     radarSweepTick          ; 78D3  restart the frame counter
        sta     radarSweepThresholdCmp+1; 78D6  threshold 0 = the first step happens on the next call
        ldx     comcenRadarStatus       ; 78D9  non-zero once the comcen radar has been knocked out
        beq     L_78E6                  ; 78DC  radar intact - always sweep normally
        jsr     nextGameRandom          ; 78DE  damaged: draw a random byte
        bmi     L_78E6                  ; 78E1  about half the draws still give a normal sweep
        dec     radarSweepDarkFlag      ; 78E3  0 -> $FF: this whole sweep will show nothing
L_78E6:
        rts                             ; 78E6  done


; ----------------------------------------------------------------------
; paintRadarSweepRow - Rewrites the 21 video-matrix bytes of one radar grid row (screen row A+3,
; columns 3-23).  In the multicolour bitmap the video matrix byte gives the colours of the %01 and %10
; pixel pairs, so writing $F0 (light grey / black) makes the cell's map glyph visible and $00 makes it
; black.  A cell is lit when its colour RAM nibble is 1 - the colour drawRadarMap gives a spotted
; enemy unit - and the radar is intact, or while the bright sweep band is passing over it.
; In:  A = grid row 0-16; radarSweepStep/radarSweepRow/radarSweepDarkFlag ($7891-$7893),
;      comcenRadarStatus ($9243), colour RAM ($D800) of that row.
; Out: 21 bytes at $8C00 + (row+3)*40 + 3 set to $F0 or $00; blockPtr (zp_B8) = video matrix pointer,
;      screenPtr (zp_B6) = colour RAM pointer; A/X/Y clobbered.
; Called from: updateRadarSweep ($78B2 leading edge, $78C4 trailing edge).
; ----------------------------------------------------------------------
paintRadarSweepRow:
        tay                             ; 78E7  grid row 0-16 as table index
        clc                             ; 78E8  clear carry before the add
        lda     screenRowLoTableFrom3,y ; 78E9  $9382+3: low byte of the screen address of row+3
        adc     #$03                    ; 78EC  +3 characters: the radar window starts at column 3
        sta     blockPtr                ; 78EE  low byte of the video matrix pointer
        sta     screenPtr               ; 78F0  and of the colour RAM pointer
        lda     screenRowHiTableFrom3,y ; 78F2  $939B+3: high byte of the screen address of row+3
        adc     #$00                    ; 78F5  propagate the carry from the +3
        sta     blockPtrHi              ; 78F7  blockPtr now points into the video matrix at $8C00
        clc                             ; 78F9  clear carry before the add
        adc     #$4C                    ; 78FA  +$4C pages: $8C00 + $4C00 = colour RAM $D800
        sta     screenPtrHi             ; 78FC  screenPtr now points at the matching colour RAM cell
        ldy     #$00                    ; 78FE  start at the left edge of the row

; ----------------------------------------------------------------------
; One cell per pass; 21 cells make up the width of the radar window.
; ----------------------------------------------------------------------
L_7900:
        lda     (screenPtr),y           ; 7900  colour RAM byte = the colour of this cell's %11 pixels
        and     #$0F                    ; 7902  only the low nibble is real
        cmp     #$01                    ; 7904  colour 1 (white) is what drawRadarMap gives a spotted enemy unit
        bne     L_790D                  ; 7906  not an enemy blip - it only shows while the band passes
        lda     comcenRadarStatus       ; 7908  is the comcen radar still working?
        beq     L_791C                  ; 790B  yes - a spotted enemy stays lit permanently
L_790D:
        lda     #$00                    ; 790D  default: both nibbles black, the cell is hidden
        ldx     radarSweepDarkFlag      ; 790F  is this sweep a dead one?
        bmi     L_791E                  ; 7912  yes - leave everything dark
        ldx     radarSweepRow           ; 7914  the row being painted
        cpx     radarSweepStep          ; 7917  is it the leading (bright) edge of the band?
        bne     L_791E                  ; 791A  no - this call is the trailing edge, so hide the row
L_791C:
        lda     #$F0                    ; 791C  $F0 = %01 pixels light grey, %10 pixels black: the cell shows
L_791E:
        sta     (blockPtr),y            ; 791E  write the video matrix byte
        iny                             ; 7920  next cell
        cpy     #$15                    ; 7921  21 cells across
        bcc     L_7900                  ; 7923  loop
        rts                             ; 7925  done


; ----------------------------------------------------------------------
; redrawEnemyUnitsOnRadar - Repaints all 50 units of the opposing side on the radar window and gives
; them a fresh random dropout pattern, so the blips move and shimmer once per turn.  Walks downwards
; from the enemy comcen index (comcenUnit EOR $52 turns 49 into 99 and 99 into 49) through 50 units,
; handing each one's map cell value and position to the main program's drawScreen1Cell.
; In:  comcenUnit ($9236); unit arrays unitColTable ($F640), unitRowTable ($F6A4), unitTypeTable
;      ($F76C) and unitTerrainUnderTable ($FAF0), all 100 bytes, $64 apart.
; Out: radarNoiseMasks ($9210-$921F) re-randomised, cells redrawn; scratch18 = unit index,
;      scratch19 = countdown, savedRegA/X/Y ($54-$56) = cell value/column/row.
; Called from: refreshMissileScreen ($768D).
; ----------------------------------------------------------------------
redrawEnemyUnitsOnRadar:
        jsr     buildRadarNoiseMasks    ; 7926  new random dropout mask set for the unit glyphs
        lda     comcenUnit              ; 7929  49 for player 0, 99 for player 1
        eor     #$52                    ; 792C  $31 EOR $52 = $63 and $63 EOR $52 = $31: the opposing side's comcen index
        sta     scratch18               ; 792E  unit index, walked downwards from there
        lda     #$32                    ; 7930  50 units per side
        sta     scratch19               ; 7932  loop counter

; ----------------------------------------------------------------------
; One enemy unit per pass, from the enemy comcen down to that side's first unit.
; ----------------------------------------------------------------------
L_7934:
        ldy     scratch18               ; 7934  current unit index
        lda     unitColTable,y          ; 7936  unitColTable[unit] = map column
        sta     savedRegX               ; 7939  drawScreen1Cell takes the column in $55
L_793B:
        lda     unitRowTable,y          ; 793B  unitRowTable[unit] = map row (the next 100-byte array, $64 further on)
        sta     savedRegY               ; 793E  drawScreen1Cell takes the row in $56
        tya                             ; 7940  unit index
        ora     #$80                    ; 7941  map cell code $80|index = 'this unit stands on this cell'
        ldx     unitTypeTable,y         ; 7943  unitTypeTable[unit]; bit 7 set = the unit is dead
        bpl     L_794D                  ; 7946  alive - draw the unit itself
        lda     unitTerrainUnderTable,y ; 7948  dead: draw the terrain the corpse was standing on instead
        bmi     L_7952                  ; 794B  $FF = the unit is not on the map at all, skip it
L_794D:
        sta     savedRegA               ; 794D  drawScreen1Cell takes the cell value in $54
        jsr     drawScreen1Cell         ; 794F  plot that one cell of the radar window
L_7952:
        dec     scratch18               ; 7952  previous unit index
        dec     scratch19               ; 7954  one unit done
        bne     L_7934                  ; 7956  loop over all 50 units of the enemy side
        rts                             ; 7958  done


; ----------------------------------------------------------------------
; scrollRadarOnComcenMove - Entered by JMP from drawScreen1Cell ($5D8A) whenever the cell being
; redrawn is the local player's comcen.  The radar window is always centred on the comcen, so if the
; comcen has moved the whole window has to scroll: the target cursor is shifted by the same amount
; (unless it is parked on the comcen cell itself, where it simply rides along), the map is redrawn and
; the sweep restarts at the top row.
; In:  comcenUnit ($9236) and the unit position arrays, viewOriginCol/Row (zp_A5/zp_A6),
;      gridCursorCol/Row (zp_67/zp_68).
; Out: nothing at all when the comcen has not moved; otherwise zp_A5/zp_A6 = the new origin (set by
;      drawRadarMap), cursor and sprite 2 shifted, window redrawn, radarSweepStep = 0.
; Called from: drawScreen1Cell ($5D8A, JMP).
; Self-modifying: the new origin is written into the SBC # operands at subNewOriginCol+1 ($7970) and
;      subNewOriginRow+1 ($7979) to compute the shift.
; ----------------------------------------------------------------------
scrollRadarOnComcenMove:
        jsr     getComcenCentredViewOrigin; 7959  X = comcenCol - 10, Y = comcenRow - 8: the map cell shown at the top-left of the window
        cpx     viewOriginCol           ; 795C  same column as the window currently shows?
        bne     L_7964                  ; 795E  no - the comcen has moved sideways
        cpy     viewOriginRow           ; 7960  same row?
L_7962:
        beq     L_7986                  ; 7962  origin unchanged - the comcen just repainted itself, nothing to do
L_7964:
        jsr     isRadarCursorOnComcen   ; 7964  is the target cursor sitting on the comcen cell (10,8)?
        beq     L_797E                  ; 7967  yes - leave it there so it rides along with the comcen
        stx     subNewOriginCol+1       ; 7969  park the new origin column in the SBC # operand at $796F
        lda     viewOriginCol           ; 796C  old origin column
        sec                             ; 796E  set carry for the subtract
subNewOriginCol:
        sbc     #$FF                    ; 796F  operand patched at $7969: the new origin column
        tax                             ; 7971  column shift = old - new
        sty     subNewOriginRow+1       ; 7972  park the new origin row in the SBC # operand at $7978
        lda     viewOriginRow           ; 7975  old origin row
        sec                             ; 7977  set carry for the subtract
subNewOriginRow:
        sbc     #$FF                    ; 7978  operand patched at $7972: the new origin row
        tay                             ; 797A  row shift = old - new
        jsr     offsetRadarCursor       ; 797B  shift the cursor so it keeps pointing at the same map cell (refused if it would leave the grid)
L_797E:
        jsr     drawRadarMap            ; 797E  redraw the whole 21x17 window and set zp_A5/zp_A6 to the new origin
        lda     #$00                    ; 7981  zero
        sta     radarSweepStep          ; 7983  restart the sweep at the top row of the new picture
L_7986:
        rts                             ; 7986  done


; ----------------------------------------------------------------------
; isRadarCursorOnComcen - Tests whether the missile target cursor is on the comcen's own cell, which
; is always the centre cell (10,8) of the 21x17 radar grid.
; In:  gridCursorCol (zp_67), gridCursorRow (zp_68).
; Out: Z = 1 when the cursor is on the comcen cell; A clobbered.
; Called from: scrollRadarOnComcenMove ($7964).
; ----------------------------------------------------------------------
isRadarCursorOnComcen:
        lda     gridCursorCol           ; 7987  cursor column 0-20
        cmp     #$0A                    ; 7989  the comcen is always drawn at grid column 10
        bne     L_7991                  ; 798B  different column - return with Z clear
        lda     gridCursorRow           ; 798D  cursor row 0-16
        cmp     #$08                    ; 798F  the comcen is always drawn at grid row 8; Z set = cursor is on it
L_7991:
        rts                             ; 7991  done


; ----------------------------------------------------------------------
; radarCursorToMapCoords - Converts the radar grid position of the target cursor into absolute map
; coordinates for an outgoing command, by adding the map coordinates of the window's top-left cell.
; In:  gridCursorCol/Row (zp_67/zp_68), viewOriginCol/Row (zp_A5/zp_A6).
; Out: cmdParam1 (zp_74) = map column, cmdParam2 (zp_75) = map row; A clobbered.
; Called from: missileScreenResume ($7662, before queueing the $90 strike) and checkStrikeHitsDrone
;      ($79AB).
; ----------------------------------------------------------------------
radarCursorToMapCoords:
        lda     gridCursorCol           ; 7992  cursor column inside the grid
        clc                             ; 7994  clear carry before the add
        adc     viewOriginCol           ; 7995  map column of the window's left edge
        sta     cmdParam1               ; 7997  command argument 1 = map column of the target cell
        lda     gridCursorRow           ; 7999  cursor row inside the grid
        clc                             ; 799B  clear carry before the add
        adc     viewOriginRow           ; 799C  map row of the window's top edge
        sta     cmdParam2               ; 799E  command argument 2 = map row of the target cell
        rts                             ; 79A0  done


; ----------------------------------------------------------------------
; checkStrikeHitsDrone - Run straight after a missile has been launched: if a drone is airborne and
; has not been dealt with yet, works out whether the targeted map cell is one of the four cells the
; drone occupies (the drone covers a 2x2 block whose top-left corner is its own cell), and if so
; queues the 1-byte command $92 that tells both machines the drone is finished.
; In:  droneControlFlags ($920D), droneArrivalFlags ($92F9), droneViewOriginCol/Row ($92F6/$92F7 =
;      the drone's map cell), the cursor and view origin used by radarCursorToMapCoords.
; Out: cmdParam1/2 = the target cell; on a hit, command $92 queued and droneArrivalFlags bit 0 set
;      (which also blocks screen switching until the command has gone out).
; Called from: missileScreenResume ($767E).
; ----------------------------------------------------------------------
checkStrikeHitsDrone:
        lda     droneControlFlags       ; 79A1  drone flags: 0 = no drone in the air
        beq     L_79D7                  ; 79A4  no drone - nothing to hit
        lda     droneArrivalFlags       ; 79A6  non-zero once the drone has been hit, detonated or has landed
        bne     L_79D7                  ; 79A9  the drone is already out of the game
        jsr     radarCursorToMapCoords  ; 79AB  cmdParam1/2 = map cell the missile was aimed at
        lda     droneViewOriginCol      ; 79AE  the drone's own map column
        sec                             ; 79B1  set carry for the subtract
        sbc     cmdParam1               ; 79B2  difference to the targeted column
        cmp     #$FF                    ; 79B4  compare with -1
        bcs     L_79BC                  ; 79B6  difference is -1: the target is the drone's right-hand cell, still a hit
        cmp     #$01                    ; 79B8  compare with +1
        bcs     L_79D7                  ; 79BA  difference is not 0 or -1 - the shot missed
L_79BC:
        lda     droneViewOriginRow      ; 79BC  the drone's own map row
L_79BF:
        sec                             ; 79BF  set carry for the subtract
        sbc     cmdParam2               ; 79C0  difference to the targeted row
L_79C2:
        cmp     #$FF                    ; 79C2  compare with -1
        bcs     L_79CA                  ; 79C4  difference is -1: the drone's lower cell, still a hit
        cmp     #$01                    ; 79C6  compare with +1
        bcs     L_79D7                  ; 79C8  difference is not 0 or -1 - the shot missed
L_79CA:
        lda     #$92                    ; 79CA  command $92 = the drone is destroyed / its flight is over
        jsr     queueCmdBytePending     ; 79CC  queue the single command byte for this turn's packet
        lda     droneArrivalFlags       ; 79CF  drone flags
        ora     #$01                    ; 79D2  bit 0 = our own $92 is queued and not yet acknowledged
        sta     droneArrivalFlags       ; 79D4  store it back
L_79D7:
        rts                             ; 79D7  done


; ----------------------------------------------------------------------
; showDroneKillBurst - Plays the 'drone shot down' effect on the radar screen once our $92 command has
; actually been executed by the engine (cmdDroneArrived sets droneArrivalFlags bit 4 for an own
; command).  Bit 4 is moved to bit 5 so the burst is played only once.  Suppressed during game film
; playback.
; In:  filmPlaybackMode ($0B9B bit 7 = a film is being replayed), droneArrivalFlags ($92F9),
;      droneViewOriginCol/Row ($92F6/$92F7) = the drone's map cell.
; Out: droneArrivalFlags bit 4 -> bit 5, droneControlFlags = 0, sounds $17 and $09, sprite 1
;      positioned and animated as an 11-step expanding ring.
; Called from: missileScreenResume ($75F4), whenever an exchange has just been applied.
; ----------------------------------------------------------------------
showDroneKillBurst:
        lda     filmPlaybackMode        ; 79D8  bit 7 = a recorded game film is being played back
        bmi     L_7A11                  ; 79DB  during playback the effect is skipped
        lda     droneArrivalFlags       ; 79DD  drone flags
        and     #$10                    ; 79E0  bit 4 = cmdDroneArrived executed our own $92
        beq     L_7A11                  ; 79E2  not our kill - nothing to show
        lda     droneArrivalFlags       ; 79E4  drone flags again
        eor     #$10                    ; 79E7  clear bit 4
        ora     #$20                    ; 79E9  set bit 5 instead, so the burst plays exactly once
        sta     droneArrivalFlags       ; 79EB  store it back
        lda     #$00                    ; 79EE  zero
        sta     droneControlFlags       ; 79F0  there is no drone under control any more
        lda     #$17                    ; 79F3  sound $17 = explosion
        jsr     playSound               ; 79F5  play it
        lda     #$09                    ; 79F8  sound $09 = debris
        jsr     playSound               ; 79FA  play it
        ldx     droneViewOriginCol      ; 79FD  the drone's map column
        ldy     droneViewOriginRow      ; 7A00  and map row
        jsr     getUnitSpriteCentreOnGrid; 7A03  -> sprite coordinates of that cell's centre (C = 1 if off the window; not tested here)
        stx     sprite1XShadow          ; 7A06  ring burst sprite 1 X
        sty     sprite1YShadow          ; 7A09  ring burst sprite 1 Y
        lda     #$0B                    ; 7A0C  11 radius steps = a big burst
        jsr     animateRingBurst        ; 7A0E  play the expanding/contracting ring
L_7A11:
        rts                             ; 7A11  done


; Single unreferenced $00 byte between the two routines (alignment filler).
padding7A12:
        .byte   $00                     ; 7A12  .  filler

; ----------------------------------------------------------------------
; mapCellToRadarGrid - Converts absolute map coordinates into radar grid coordinates relative to the
; local comcen, which is always drawn at the centre cell (10,8) of the 21x17 window, and falls into
; the range test.
; In:  X = map column, Y = map row, comcenUnit ($9236), unitColTable/unitRowTable.
; Out: X = grid column, Y = grid row, C = 0 when the cell is inside the window, C = 1 outside.
; Called from: the main program's getUnitSpriteCentreOnGrid ($340B) and the opponent modules
;      (trainer $EE7C, drone AI $EEE1) when they place sprites on screen 1.
; ----------------------------------------------------------------------
mapCellToRadarGrid:
        tya                             ; 7A13  map row
        clc                             ; 7A14  clear carry before the add
        adc     #$08                    ; 7A15  the comcen is drawn at grid row 8
        sec                             ; 7A17  set carry for the subtract
        ldy     comcenUnit              ; 7A18  the local comcen's unit index (49 or 99)
        sbc     unitRowTable,y          ; 7A1B  minus the comcen's map row
L_7A1E:
        tay                             ; 7A1E  Y = grid row
        txa                             ; 7A1F  map column
        clc                             ; 7A20  clear carry before the add
        adc     #$0A                    ; 7A21  the comcen is drawn at grid column 10
        sec                             ; 7A23  set carry for the subtract
L_7A24:
        ldx     comcenUnit              ; 7A24  the local comcen's unit index again
        sbc     unitColTable,x          ; 7A27  minus the comcen's map column
        tax                             ; 7A2A  X = grid column; fall into the range test

; ----------------------------------------------------------------------
; isRadarGridCellInRange - Range check for a radar grid cell: the window is 21 columns by 17 rows, and
; the comparisons are unsigned so a negative coordinate (which wraps to >= $80) counts as outside.
; In:  X = grid column, Y = grid row.
; Out: C = 0 when Y < 17 and X < 21, C = 1 otherwise; registers untouched.
; Called from: mapCellToRadarGrid (fall-through), offsetRadarCursor ($76FC), animateMissileFlight
;      ($77B9, result unused) and the main program at $33A0.
; ----------------------------------------------------------------------
isRadarGridCellInRange:
        cpy     #$11                    ; 7A2B  17 grid rows
        bcs     L_7A31                  ; 7A2D  off the bottom, or negative (which wraps above $80)
        cpx     #$15                    ; 7A2F  21 grid columns; C = 0 only if the column is inside too
L_7A31:
        rts                             ; 7A31  done


; ----------------------------------------------------------------------
; initRadarScreen - Builds the whole RADAR / MISSILE screen (game screen 1): resets the glyph
; plotter's parameter block, sets border and background to grey, then draws the MISSILES panel and its
; count, the FIRE button, the DRONE ALERT panel, the framed 21x17 radar map and the bottom message
; frame, and finally reprints whatever message the status line was showing.
; In:  radarMissilesLeft ($9232), comcen position, the $F000 map.
; Out: whole screen drawn; vicBorderShadow/vicBg0Shadow ($9003/$9004) = $0C, cellBackgroundColour
;      ($9226) = 0, screenBuiltFlag (zp_41) = 1, textDrawParams block ($2A29-$2A2C) cleared.
; Called from: runMissileScreen ($7599).
; ----------------------------------------------------------------------
initRadarScreen:
        lda     #$00                    ; 7A32  zero
        sta     useDrawColourFlag       ; 7A34  useFixedColourFlag = 0: glyphs take their colour from fontColorTable
        sta     drawColourByte          ; 7A37  fixed draw colour byte = 0
        sta     blockFillFlag           ; 7A3A  block fill flag = 0
        sta     blockFillChar           ; 7A3D  block fill character = 0
        lda     #$0C                    ; 7A40  colour $0C = medium grey
        sta     vicBorderShadow         ; 7A42  border colour shadow (the IRQ copies it to $D020)
        sta     vicBg0Shadow            ; 7A45  background colour shadow ($D021)
        lda     #$00                    ; 7A48  zero
        sta     cellBackgroundColour    ; 7A4A  screen 1 writes glyph colours straight to colour RAM, so no background nibble is ORed in
        lda     #$01                    ; 7A4D  1
        sta     screenBuiltFlag         ; 7A4F  every screen builder sets this flag; nothing reads it
        lda     #$00                    ; 7A51  zero
        sta     blockFillFlag           ; 7A53  redundant - D_2A2B was already cleared at $7A3A
L_7A56:
        jsr     drawMissilePanel        ; 7A56  MISSILES panel, bottom right
        jsr     drawMissileCount        ; 7A59  the two-digit count inside it
        jsr     drawFireButton          ; 7A5C  FIRE button panel
        jsr     drawDroneAlertPanel     ; 7A5F  DRONE ALERT panel, top right
        jsr     redrawRadarMapWindow    ; 7A62  the two frames plus the 21x17 radar map itself
        jsr     drawMainWindowFrame     ; 7A65  the bottom status/message frame shared by all screens
        jmp     redrawStatusMessage     ; 7A68  reprint the current status-line message and return through it


; ----------------------------------------------------------------------
; redrawRadarMapWindow - Draws the double frame around the radar window and then the map inside it.
; In:  the comcen position and the $F000 map (used by drawRadarMap).
; Out: window 5 (25x21 at column 1, row 1) and window 6 (23x19 at column 2, row 2) drawn, then the
;      21x17 cell map.
; Called from: initRadarScreen ($7A62) and clearCurrentScreenWindows ($2DFA, JMP) while screen 1 is
;      the active screen.
; ----------------------------------------------------------------------
redrawRadarMapWindow:
        ldx     #$05                    ; 7A6B  window 5 = the outer frame, 25x21 cells at (column 1, row 1)
        jsr     drawWindowFrame         ; 7A6D  draw it
        ldx     #$06                    ; 7A70  window 6 = the inner frame, 23x19 cells at (column 2, row 2)
        jsr     drawWindowFrame         ; 7A72  draw it
        jmp     drawRadarMap            ; 7A75  fill it with the 21x17 cell radar map and return through it


; ----------------------------------------------------------------------
; drawMissilePanel - Draws the MISSILES panel in the bottom right corner: an outer frame with inner
; corner marks, a small inner frame around the two-digit counter, the coloured background of the
; counter box and the caption 'MISSILES'.
; In:  nothing.
; Out: window 7 (12x7 at (27,15)) and window 8 (4x4 at (31,16)) drawn, the 2x2 counter box at row 17
;      column 32 filled with $30, the caption printed at column 29 row 19; textColour (zp_3D), the
;      text cursor and the draw modes are left reset.
; Called from: initRadarScreen ($7A56).
; ----------------------------------------------------------------------
drawMissilePanel:
        jsr     setFullScreenWindow     ; 7A78  text window = the whole screen, so the coordinates below are absolute
        ldx     #$07                    ; 7A7B  window 7 = 12x7 cells at column 27, row 15
        jsr     drawWindowFrame         ; 7A7D  draw the frame
        jsr     drawWindowInnerCorners  ; 7A80  put the four inner corner marks (character $38) inside it
L_7A83:
        ldx     #$08                    ; 7A83  window 8 = 4x4 cells at column 31, row 16
        jsr     drawWindowFrame         ; 7A85  draw the inner frame around the counter
        lda     #$11                    ; 7A88  character row 17
        sta     windowOriginRow         ; 7A8A  row of the counter box
        lda     #$20                    ; 7A8D  character column 32
        sta     windowOriginCol         ; 7A8F  column of the counter box
        lda     #$30                    ; 7A92  $30 = %01 pixels cyan, %10 pixels black
        ldx     #$02                    ; 7A94  2 cells wide
        ldy     #$02                    ; 7A96  2 cells high
        jsr     fillScreenColourRect    ; 7A98  paint the counter box background
        lda     #$CF                    ; 7A9B  colour byte for the caption glyphs
        sta     textColour              ; 7A9D  text colour
        ldy     #$01                    ; 7A9F  bitmap draw mode 1
        jsr     setBitmapDrawMode       ; 7AA1  OR the glyphs into the bitmap so the frame keeps showing through
        lda     #$1D                    ; 7AA4  column 29
        sta     textCursorCol           ; 7AA6  text cursor column
        lda     #$13                    ; 7AA8  row 19
        sta     textCursorRow           ; 7AAA  text cursor row
        jsr     updateCursorPointers    ; 7AAC  recompute the bitmap/screen pointers from the cursor
        lda     #$9E                    ; 7AAF  low byte of the string address
        ldy     #$CC                    ; 7AB1  $CC9E = 'MISSILES' in the $C000 text overlay
        jsr     printString             ; 7AB3  print it
        jmp     resetDrawModes          ; 7AB6  back to replace mode and return through it


; Colour byte the missile counter box is currently painted with.
missileCountColour:
        .byte   $00                     ; 7AB9  .  $30 (cyan digits on black) or $03 (black digits on cyan) while it flashes

; ----------------------------------------------------------------------
; drawMissileCount - Shows the number of missiles left as two tall (two-character-high) digits in the
; counter box at row 17, columns 32 and 33, in the fixed colour $30.
; In:  radarMissilesLeft ($9232).
; Out: the two digits drawn, missileCountColour ($7AB9) = $30, the glyph plotter's fixed-colour mode
;      switched on and off again, scratch18/scratch19 = the row/column drawTallGlyph used.
; Called from: initRadarScreen ($7A59), the fire handler after a launch ($7647) and
;      refreshMissileScreen ($7695).
; ----------------------------------------------------------------------
drawMissileCount:
        lda     radarMissilesLeft       ; 7ABA  missiles left
        jsr     splitDecimalDigits      ; 7ABD  split into $C44E hundreds, $C44F tens, $C450 ones
        lda     #$11                    ; 7AC0  character row 17
        sta     scratch18               ; 7AC2  drawTallGlyph's row
        lda     #$20                    ; 7AC4  character column 32
        sta     scratch19               ; 7AC6  drawTallGlyph's column
        lda     #$FF                    ; 7AC8  non-zero
        sta     useDrawColourFlag       ; 7ACA  useFixedColourFlag: take the colour from $2A2A, not from the font colour table
L_7ACD:
        ldx     #$30                    ; 7ACD  $30 = %01 pixels cyan, %10 pixels black
        stx     drawColourByte          ; 7ACF  the fixed draw colour
        stx     missileCountColour      ; 7AD2  remember it so blinkMissileCount can toggle it
        lda     decimalTens             ; 7AD5  tens digit
        jsr     drawTallGlyph           ; 7AD8  draw it as a two-character-high glyph pair
        inc     scratch19               ; 7ADB  next column (33)
        lda     decimalOnes             ; 7ADD  ones digit
        jsr     drawTallGlyph           ; 7AE0  draw it
        lda     #$00                    ; 7AE3  zero
        sta     useDrawColourFlag       ; 7AE5  back to per-character colours
        sta     drawColourByte          ; 7AE8  and clear the fixed colour byte
        rts                             ; 7AEB  done


; ----------------------------------------------------------------------
; blinkMissileCount - Swaps the two colour nibbles of the missile counter box, which exchanges the
; digit and background colours and makes the counter flash.
; In:  missileCountColour ($7AB9).
; Out: $7AB9 toggled between $30 and $03, the 2x2 video matrix cells at row 17 column 32 rewritten.
; Called from: missileScreenResume ($75E4), every 9 frames while fewer than two missiles are left.
; ----------------------------------------------------------------------
blinkMissileCount:
        lda     missileCountColour      ; 7AEC  current box colour
        eor     #$33                    ; 7AEF  EOR $33 swaps the two nibbles' colours: $30 <-> $03
        sta     missileCountColour      ; 7AF1  keep the new one
        ldx     #$11                    ; 7AF4  character row 17
        stx     windowOriginRow         ; 7AF6  row of the counter box
        ldx     #$20                    ; 7AF9  character column 32
        stx     windowOriginCol         ; 7AFB  column of the counter box
        ldx     #$02                    ; 7AFE  2 cells wide
        ldy     #$02                    ; 7B00  2 cells high
        jmp     fillScreenColourRect    ; 7B02  repaint them (the draw mode is 'replace' here) and return through it


; ----------------------------------------------------------------------
; drawFireButton - Draws the FIRE button panel on the right: a framed box with inner corner marks, the
; 2x2 character button icon from $9804 in green, and the caption 'FIRE' underneath.
; In:  nothing.
; Out: window 9 (8x7 at (29,8)) drawn, the icon at row 10 column 32 painted with colour byte $05,
;      the caption at column 31 row 12; the text window and the draw modes are left reset.
; Called from: initRadarScreen ($7A5C).  The main loop makes the button pulse by EORing 8 into the
;      same four cells every 9 frames ($75DA).
; ----------------------------------------------------------------------
drawFireButton:
        ldx     #$09                    ; 7B05  window 9 = 8x7 cells at column 29, row 8
        jsr     drawWindowFrame         ; 7B07  draw the frame
        jsr     drawWindowInnerCorners  ; 7B0A  add the four inner corner marks
        lda     #$0A                    ; 7B0D  character row 10
        sta     windowOriginRow         ; 7B0F  row of the button icon
        lda     #$20                    ; 7B12  character column 32
        sta     windowOriginCol         ; 7B14  column of the button icon
        lda     #$04                    ; 7B17  low byte of the block address
        ldy     #$98                    ; 7B19  $9804 = a 2x2 character block (width, height, then glyphs $45-$48)
        jsr     drawCharBlock           ; 7B1B  stamp the icon into the bitmap
        lda     #$05                    ; 7B1E  $05 = %01 pixels black, %10 pixels green
        ldx     #$02                    ; 7B20  2 cells wide
        ldy     #$02                    ; 7B22  2 cells high
        jsr     fillScreenColourRect    ; 7B24  paint the icon's colour cells
        lda     #$CD                    ; 7B27  colour byte for the caption glyphs
        sta     textColour              ; 7B29  text colour
        jsr     setFullScreenWindow     ; 7B2B  text window = the whole screen again
        lda     #$1F                    ; 7B2E  column 31
        sta     textCursorCol           ; 7B30  text cursor column
        lda     #$0C                    ; 7B32  row 12
        sta     textCursorRow           ; 7B34  text cursor row
        jsr     updateCursorPointers    ; 7B36  recompute the bitmap/screen pointers
L_7B39:
        lda     #$9D                    ; 7B39  low byte of the string address
        ldy     #$86                    ; 7B3B  $869D = 'FIRE'
        jsr     printString             ; 7B3D  print it
        jmp     resetDrawModes          ; 7B40  back to replace mode and return through it


; Flash colour of the DRONE ALERT panel.
droneAlertColour:
        .byte   $00                     ; 7B43  .  $02 (red text area) or $20 (inverted), toggled by flashDroneAlertPanel

; ----------------------------------------------------------------------
; drawDroneAlertPanel - Draws the three nested frames of the DRONE ALERT panel in the top right corner
; and falls into resetDroneAlertPanel, which prints the text and leaves the panel dark.
; In:  nothing.
; Out: windows $0A (11x8 at (28,0)), $0B (9x6 at (29,1)) and $0C (7x4 at (30,2)) drawn, then the
;      text and the inactive colour.
; Called from: initRadarScreen ($7A5F).
; ----------------------------------------------------------------------
drawDroneAlertPanel:
        jsr     setFullScreenWindow     ; 7B44  text window = the whole screen
        ldx     #$0A                    ; 7B47  window $0A = the outer frame, 11x8 cells at column 28, row 0
        jsr     drawWindowFrame         ; 7B49  draw it
        jsr     drawWindowInnerCorners  ; 7B4C  add the four inner corner marks
        ldx     #$0B                    ; 7B4F  window $0B = the middle frame, 9x6 cells at column 29, row 1
        jsr     drawWindowFrame         ; 7B51  draw it
        ldx     #$0C                    ; 7B54  window $0C = the inner frame, 7x4 cells at column 30, row 2
L_7B56:
        jsr     drawWindowFrame         ; 7B56  draw it and fall into the text/colour part

; ----------------------------------------------------------------------
; resetDroneAlertPanel - Prints the two-line 'DRONE' / 'ALERT' label into the panel and paints its
; interior in the dark, inactive colour, also resetting the flash state so the next alert starts on
; the red phase.
; In:  nothing.
; Out: the label printed at column 31, row 3; droneAlertColour ($7B43) = $02; textWindowLeft (zp_3B)
;      = 31, textColour (zp_3D) = $0B, the 7x4 interior filled with $0B via fillDroneAlertPanel.
; Called from: drawDroneAlertPanel (fall-through) and refreshMissileScreen ($76AC, JMP) whenever no
;      drone is airborne.
; ----------------------------------------------------------------------
resetDroneAlertPanel:
        ldy     #$00                    ; 7B59  colour draw mode 0
        jsr     setColourDrawMode       ; 7B5B  replace the colour cells instead of ORing/EORing into them
        lda     #$0B                    ; 7B5E  colour byte $0B for the label glyphs
        sta     textColour              ; 7B60  text colour
        lda     #$02                    ; 7B62  $02 = the first flash phase
L_7B64:
        sta     droneAlertColour        ; 7B64  reset the flash state so the next alert starts red
        lda     #$1F                    ; 7B67  column 31
        sta     textWindowLeft          ; 7B69  make it the left edge of the text window, so the label wraps inside the panel
        lda     #$00                    ; 7B6B  column 0 relative to that
        sta     textCursorCol           ; 7B6D  text cursor column
        lda     #$03                    ; 7B6F  row 3
        sta     textCursorRow           ; 7B71  text cursor row
        jsr     updateCursorPointers    ; 7B73  recompute the bitmap/screen pointers
        lda     #$A1                    ; 7B76  low byte of the string address
        ldy     #$86                    ; 7B78  $86A1 = 'DRONE' + CR + 'ALERT'
        jsr     printString             ; 7B7A  print both lines
        lda     #$0B                    ; 7B7D  11 frames
        sta     keyRepeatTimer          ; 7B7F  delays the keyboard auto-repeat countdown; nothing here depends on it (leftover)
        lda     #$0B                    ; 7B81  $0B = %01 pixels black, %10 pixels dark grey: the panel is dark/inactive
        jmp     fillDroneAlertPanel     ; 7B83  fill the 7x4 interior with it and return through it


; ----------------------------------------------------------------------
; flashDroneAlertPanel - Toggles the DRONE ALERT panel between its two colour states and repaints the
; 7x4 colour cells behind the two-line 'DRONE / ALERT' text, so the panel blinks red while an opponent
; drone is airborne.  droneAlertColour ($7B43) alternates between $02 (red as the %10 bit pair) and
; $20 (red as the %01 bit pair), which swaps the text and its background.
; In:  droneAlertColour $7B43
; Out: $7B43 toggled, 28 screen RAM colour cells at row 2 column 30 rewritten, draw modes reset,
;      D_2A2A = the new colour
; Called from: JMP at $76AF in the screen-1 refresh routine sub_7688, taken when droneControlFlags
;      ($920D) bit 0 says an enemy drone is in the air (the other path JMPs to resetDroneAlertPanel)
; ----------------------------------------------------------------------
flashDroneAlertPanel:
        jsr     resetDrawModes          ; 7B86  put the bitmap and colour draw modes back to plain replace
        lda     droneAlertColour        ; 7B89  current state of the alert panel ($02 or $20)
        eor     #$22                    ; 7B8C  EOR $22 swaps the two red nibbles: $02 <-> $20
        sta     droneAlertColour        ; 7B8E  remember the new state for the next refresh

; ----------------------------------------------------------------------
; fillDroneAlertPanel - Shared tail of resetDroneAlertPanel and flashDroneAlertPanel: fills the 7 x 4
; character interior of DRONE ALERT window $0C (top-left corner row 2, column 30) with the colour byte
; in A.
; In:  A = screen RAM colour byte
; Out: 28 colour cells written, D_2A2D = 2, D_2A2E = $1E, D_2A2A = A
; Called from: JMP at $7B83 (resetDroneAlertPanel) and by falling through from flashDroneAlertPanel
; ----------------------------------------------------------------------
fillDroneAlertPanel:
        ldx     #$02                    ; 7B91  colour rectangle starts at character row 2
        stx     windowOriginRow         ; 7B93  D_2A2D = fill origin row
        ldx     #$1E                    ; 7B96  character column 30
        stx     windowOriginCol         ; 7B98  D_2A2E = fill origin column
        ldx     #$07                    ; 7B9B  7 character columns wide
        ldy     #$04                    ; 7B9D  4 character rows high
        jmp     fillScreenColourRect    ; 7B9F  paint them and return straight to the panel routine's caller


; ----------------------------------------------------------------------
; drawRadarMap - Redraws the whole 21 x 17 cell strategic map of screen 1 (the RADAR/MISSILE screen)
; at character rows 3-19, columns 3-23, one 8x8 character per map cell, centred so that the player's
; own comcen sits at grid cell (10,8).  Terrain and own units are dark grey, a spotted enemy is white;
; the actual per-cell decision is made by getRadarCellGlyph and the plotting by drawRadarGlyph.  The
; colour bytes go to colour RAM $D800 (the %11 bit pair); paintRadarSweepRow later writes $F0/$00 into
; screen RAM $8C00 to reveal or hide each row as the sweep passes.
; In:  comcenUnit $9236, the 40x40 map at $F000, the unit arrays at $F640+
; Out: 357 bitmap cells and colour cells written; zp_A5/zp_A6 restored; $90F6/$90F7 = the saved view
;      origin; $9237/$9239 = $0B, $9238 = 1; zp_48/49, zp_B4-zp_B7 clobbered; textDrawParams = 0
; Called from: $75B0 (screen-1 build), $797E (comcen moved, via scrollRadarOnComcenMove) and by the
;      JMP at $7A75 in redrawRadarMapWindow
; ----------------------------------------------------------------------
drawRadarMap:
        jsr     buildRadarNoiseMasks    ; 7BA2  new random noise masks so unspotted enemy 'ghost' blips shimmer on every redraw
        lda     #$0B                    ; 7BA5  $0B = dark grey
        sta     radarOwnUnitColour      ; 7BA7  $9237: colour used for our own units
        sta     radarTerrainColour      ; 7BAA  $9239: and for plain terrain
        lda     #$01                    ; 7BAD  1 = white
        sta     radarEnemyUnitColour    ; 7BAF  $9238: a spotted enemy blip - the sweep only lights up colour-1 cells

; ----------------------------------------------------------------------
; Blank the window, then work out where the map has to be scrolled to keep the comcen centred.
; ----------------------------------------------------------------------
drawRadarMapBody:
        lda     #$03                    ; 7BB2  colour fill origin: character row 3 (top edge of the radar window)
        sta     windowOriginRow         ; 7BB4  D_2A2D = fill origin row
        lda     #$03                    ; 7BB7  character column 3 (left edge)
        sta     windowOriginCol         ; 7BB9  D_2A2E = fill origin column
L_7BBC:
        ldx     #$15                    ; 7BBC  21 cells across
        ldy     #$11                    ; 7BBE  17 cells down
        lda     #$00                    ; 7BC0  colour 0 = both multicolour nibbles black
        jsr     fillScreenColourRect    ; 7BC2  clear the screen RAM colour cells of the whole window first
        jsr     getComcenCentredViewOrigin; 7BC5  X/Y = map origin that puts the comcen at grid cell (10,8)
        stx     viewOriginCol           ; 7BC8  zp_A5 = map column of the left edge
        sty     viewOriginRow           ; 7BCA  zp_A6 = map row of the top edge
        jsr     getViewportOriginPointer; 7BCC  zp_48/zp_49 = address in the $F000 map of that top-left cell (handles negative origins)

; ----------------------------------------------------------------------
; Save the origin - the drawing loop walks zp_A5/zp_A6 cell by cell so that getRadarCellGlyph can test
; for off-map coordinates - and build the two output pointers for character cell (row 3, column 3).
; ----------------------------------------------------------------------
L_7BCF:
        lda     viewOriginCol           ; 7BCF  keep the origin column ...
        sta     drawCellSaveCol         ; 7BD1  ... in $90F6
        lda     viewOriginRow           ; 7BD4  keep the origin row ...
        sta     drawCellSaveRow         ; 7BD6  ... in $90F7
        clc                             ; 7BD9  no carry into the address addition
        lda     bitmapRowLoTableFrom3   ; 7BDA  bitmap row table (real base $9300) entry for character row 3, low byte
        adc     bitmapColLoTableFrom3   ; 7BDD  plus 8*column table (real base $9332) entry for character column 3
        sta     bitmapPtr               ; 7BE0  zp_B4 = low byte of the first bitmap cell
        lda     bitmapRowHiTableFrom3   ; 7BE2  same tables, high bytes ($9319+3 and $935A+3)
        adc     bitmapColHiTableFrom3   ; 7BE5  add with the carry from the low half
        sta     bitmapPtrHi             ; 7BE8  zp_B5 = high byte; zp_B4/B5 now point into the $A000 bitmap
        clc                             ; 7BEA  clear carry for the screen RAM address
        lda     screenRowLoTableFrom3   ; 7BEB  screen RAM row table (real base $9382) entry for character row 3, low byte
        adc     #$03                    ; 7BEE  +3 characters for column 3
        sta     screenPtr               ; 7BF0  zp_B6 = low byte
        lda     screenRowHiTableFrom3   ; 7BF2  row 3 high byte from the $939B table (points into screen RAM $8C00)
        adc     #$00                    ; 7BF5  propagate the carry of the column addition
        sta     screenPtrHi             ; 7BF7  dead store: zp_B7 is overwritten three instructions later with the colour RAM value
radarMapColourRamPtr:
        clc                             ; 7BF9  clear carry for the +$4C00
        adc     #$4C                    ; 7BFA  $8C00 + $4C00 = $D800: turn the screen RAM address into the colour RAM address
        sta     screenPtrHi             ; 7BFC  zp_B6/B7 now address colour RAM; on screen 1 drawRadarGlyph stores the raw colour there

; ----------------------------------------------------------------------
; Outer loop over the 17 radar rows (X), inner loop over the 21 columns (Y).  Y is also the offset
; added to zp_B6/B7, while zp_B4/B5 is advanced 8 bytes per cell by drawRadarGlyph itself.
; ----------------------------------------------------------------------
        ldx     #$00                    ; 7BFE  X = radar row 0..16 (character row 3+X)
L_7C00:
        ldy     #$00                    ; 7C00  Y = radar column 0..20 (character column 3+Y)
        tya                             ; 7C02  A = 0
L_7C03:
        clc                             ; 7C03  clear carry for the column reset
        adc     drawCellSaveCol         ; 7C04  restart the running map column at the left edge of the window
        sta     viewOriginCol           ; 7C07  zp_A5 = map column of the cell about to be drawn
L_7C09:
        lda     (mapCellPtr),y          ; 7C09  map byte of this cell: $80|unitIndex, or a terrain tile
        jsr     getRadarCellGlyph       ; 7C0B  classify it: A = glyph index (bit 7 = draw through the noise masks), zp_32 = colour
        jsr     drawRadarGlyph          ; 7C0E  plot the 8x8 glyph, store zp_32 at (zp_B6),Y and step zp_B4/B5 on by one character
        iny                             ; 7C11  next character column of the window
L_7C12:
        inc     viewOriginCol           ; 7C12  and the matching map column, for the off-map test in getRadarCellGlyph
        cpy     #$15                    ; 7C14  21 columns done?
        bcc     L_7C09                  ; 7C16  no - draw the next cell of this row

; ----------------------------------------------------------------------
; End of a row: advance the map pointer, the colour RAM pointer and the bitmap pointer by one row.
; ----------------------------------------------------------------------
        inc     viewOriginRow           ; 7C18  the next window row shows the next map row
        lda     mapCellPtr              ; 7C1A  map pointer low byte
radarMapNextMapRow:
        clc                             ; 7C1C  clear carry
        adc     #$28                    ; 7C1D  one map row = 40 bytes
        sta     mapCellPtr              ; 7C1F  zp_48 updated
        lda     mapCellPtrHi            ; 7C21  map pointer high byte
        adc     #$00                    ; 7C23  add the carry
        sta     mapCellPtrHi            ; 7C25  zp_49 updated
        clc                             ; 7C27  clear carry
        lda     screenPtr               ; 7C28  colour RAM pointer low byte
        adc     #$28                    ; 7C2A  one character row = 40 bytes
        sta     screenPtr               ; 7C2C  zp_B6 updated
radarMapScreenPtrCarry:
        lda     screenPtrHi             ; 7C2E  colour RAM pointer high byte
        adc     #$00                    ; 7C30  add the carry
        sta     screenPtrHi             ; 7C32  zp_B7 updated
        inx                             ; 7C34  next radar row
        ldy     #$00                    ; 7C35  column index 0 = character column 3 again
        clc                             ; 7C37  clear carry
        lda     bitmapRowLoTableFrom3,x ; 7C38  bitmap row table entry for character row 3+X, low byte
        adc     bitmapColLoTableFrom3,y ; 7C3B  plus the column-3 offset
L_7C3E:
        sta     bitmapPtr               ; 7C3E  zp_B4 = start of the new row
        lda     bitmapRowHiTableFrom3,x ; 7C40  row 3+X high byte
        adc     bitmapColHiTableFrom3,y ; 7C43  plus the column-3 high byte
        sta     bitmapPtrHi             ; 7C46  zp_B5 = start of the new row
        cpx     #$11                    ; 7C48  17 rows done?
        bcc     L_7C00                  ; 7C4A  no - draw the next row

; ----------------------------------------------------------------------
; Finished: drop any fixed-colour override and put the view origin back the way the caller left it.
; ----------------------------------------------------------------------
        lda     #$00                    ; 7C4C  0 = use each glyph's own colour again
        sta     useDrawColourFlag       ; 7C4E  clear the fixed-colour flag $2A29
        lda     drawCellSaveCol         ; 7C51  reload the saved origin column
L_7C54:
        sta     viewOriginCol           ; 7C54  zp_A5 restored
        lda     drawCellSaveRow         ; 7C56  reload the saved origin row
        sta     viewOriginRow           ; 7C59  zp_A6 restored
        rts                             ; 7C5B  back to the caller


; ----------------------------------------------------------------------
; getComcenCentredViewOrigin - Returns the map origin that puts the player's own comcen in the middle
; of the 21 x 17 cell strategic window of screen 1: column = unitCol[comcenUnit] - 10, row =
; unitRow[comcenUnit] - 8.  Either value may be negative when the comcen is near an edge of the 40x40
; map; getViewportOriginPointer copes with that.
; In:  comcenUnit $9236 (49 for side 0, 99 for side 1), unitColTable $F640, unitRowTable $F6A4
; Out: X = origin column, Y = origin row; A clobbered
; Called from: $7959 (scrollRadarOnComcenMove) and $7BC5 (drawRadarMap)
; ----------------------------------------------------------------------
getComcenCentredViewOrigin:
        ldy     comcenUnit              ; 7C5C  $9236 = index of this side's comcen (49 or 99)
        lda     unitColTable,y          ; 7C5F  its map column ($F640 + unit)
        sec                             ; 7C62  no borrow
        sbc     #$0A                    ; 7C63  the comcen is drawn at grid column 10 of the 21-wide window
        tax                             ; 7C65  return the origin column in X
        lda     unitRowTable,y          ; 7C66  its map row ($F6A4 + unit; the arrays are $64 apart)
        sec                             ; 7C69  no borrow
        sbc     #$08                    ; 7C6A  and at grid row 8 of the 17-high window
        tay                             ; 7C6C  return the origin row in Y
        rts                             ; 7C6D  done


; comcenSchematicRle - 864 bytes of RLE-compressed hires picture (20 x 10 characters = 160 x 80
; pixels, 1600 bytes when unpacked): the side view of the tracked comcen vehicle shown on the REPAIR
; tab.  Stream format as used by unpackRleTo0200: byte 0 is the escape marker ($13), afterwards any
; byte other than the marker is a literal and 'marker, count, value' emits value count times (count 0
; means 256).  The stream ends at $7FCB with $13,$13,$00 = 256 zero bytes.  drawComcenSchematic
; ($8632) is the only reader; it unpacks straight into the bitmap at character row 10, column 13 with
; a 320-byte row stride.  Every sub_/L_/D_ label inside this block is an artefact of XREF merging the
; two $6F00 overlay variants: those addresses are code only in the map generator overlay.
comcenSchematicRle:
        .byte   $13,$13,$1C,$00,$13,$04,$04,$13; 7C6E  ........
        .byte   $06,$00,$01,$41,$13,$07,$00,$10; 7C76  ...A....
        .byte   $13,$14,$00,$01,$03,$06,$44,$13; 7C7E  ......D.
D_7C86:
        .byte   $04,$00,$80,$13,$6B,$00,$04,$04; 7C86  ....k...
        .byte   $04,$07,$13,$04,$04,$41,$41,$E1; 7C8E  .....AA.
        .byte   $FF,$E1,$41,$41,$41,$14,$14,$14; 7C96  ..AAA...
        .byte   $FF,$14                 ; 7C9E  ..
L_7CA0:
        .byte   $14,$14                 ; 7CA0  ..
sub_7CA2:
        .byte   $10,$00,$00,$40,$C0,$40,$13,$04; 7CA2  ...@.@..
        .byte   $00,$01,$13             ; 7CAA  ...
L_7CAD:
        .byte   $06                     ; 7CAD  .
sub_7CAE:
        .byte   $00,$AC,$FF,$AC,$E4,$A6,$A3,$A1; 7CAE  ........
        .byte   $A0,$00,$80,$13         ; 7CB6  ....
L_7CBA:
        .byte   $04,$00,$80,$13,$58     ; 7CBA  ....X
sub_7CBF:
        .byte   $00                     ; 7CBF  .
D_7CC0:
        .byte   $03,$13,$07,$00,$FF,$13,$07,$00; 7CC0  ........
        .byte   $FF,$04,$04,$04,$00,$00,$00; 7CC8  .......
L_7CCF:
        .byte   $01,$FF                 ; 7CCF  ..
L_7CD1:
        .byte   $41,$13,$04,$40,$E0,$F0,$FF,$13; 7CD1  A..@....
        .byte   $07,$00,$FF,$00,$00,$00,$1F,$20; 7CD9  ....... 
        .byte   $40,$80,$00,$01,$02,$07,$FF,$13; 7CE1  @.......
        .byte   $04,$00,$F0,$08,$FC,$FF,$13,$07; 7CE9  ........
        .byte   $00,$FF,$00             ; 7CF1  ...
L_7CF4:
        .byte   $0F,$04,$04,$00,$00,$00,$80,$80; 7CF4  ........
        .byte   $13,$07,$00,$18,$14,$12,$11,$13; 7CFC  ........
L_7D04:
        .byte   $40,$00,$0C,$10,$20,$40,$40,$40; 7D04  @... @@@
        .byte   $4F,$48,$13,$06,$00,$FF,$13,$07; 7D0C  OH......
        .byte   $00,$F0,$10,$13,$30,$00,$02,$02; 7D14  ....0...
        .byte   $01,$01,$13,$08,$00,$80,$80,$40; 7D1C  .......@
        .byte   $40,$10,$10,$20,$2F,$20,$1F,$00; 7D24  @.. / ..
        .byte   $00,$80,$7F,$00,$00     ; 7D2C  .....
L_7D31:
        .byte   $38,$D7,$44,$44         ; 7D31  8.DD
L_7D35:
        .byte   $00,$FF,$00,$7F,$00,$FF,$00,$00; 7D35  ........
        .byte   $00,$FF,$00,$00,$38,$D7,$44,$44; 7D3D  ....8.DD
        .byte   $00,$F0,$0F,$00,$0F,$F0,$13,$05; 7D45  ........
        .byte   $00                     ; 7D4D  .
L_7D4E:
        .byte   $E0,$13,$1C,$00,$4A,$48,$48,$49; 7D4E  ....JHHI
        .byte   $49,$48,$49,$49,$00,$00,$FF,$00; 7D56  IHII....
        .byte   $00,$81,$00,$00,$50,$10,$10,$90; 7D5E  ....P...
        .byte   $90,$10,$90,$90,$13,$06,$00,$1F; 7D66  ........
        .byte   $60,$13                 ; 7D6E  `.
sub_7D70:
        .byte   $06,$00                 ; 7D70  ..
L_7D72:
        .byte   $FF,$13,$07,$00,$FF,$13,$07,$00; 7D72  ........
        .byte   $FF,$13,$07             ; 7D7A  ...
sub_7D7D:
        .byte   $00,$FF,$13,$07         ; 7D7D  ....
L_7D81:
        .byte   $00,$FF,$13,$07,$00,$FF,$00,$20; 7D81  ....... 
        .byte   $20,$1F                 ; 7D89   .
sub_7D8B:
        .byte   $20,$40,$80,$FF,$00,$00,$01,$FF; 7D8B   @......
        .byte   $00,$00,$00,$FF,$00     ; 7D93  .....
sub_7D98:
        .byte   $88,$10                 ; 7D98  ..
L_7D9A:
        .byte   $FF,$00,$00,$00,$FF,$00,$00,$01; 7D9A  ........
        .byte   $FF,$00,$00,$00,$FF     ; 7DA2  .....
sub_7DA7:
        .byte   $00,$88,$10,$FF,$00,$00,$00,$FF; 7DA7  ........
        .byte   $00,$00,$00,$80,$40,$40,$40,$F0; 7DAF  ....@@@.
L_7DB7:
        .byte   $0C                     ; 7DB7  .
L_7DB8:
        .byte   $13,$07,$00,$01,$13,$05,$00,$60; 7DB8  .......`
        .byte   $C0,$8E,$13,$10,$00,$48,$49,$49; 7DC0  .....HII
L_7DC8:
        .byte   $48,$49,$49,$48,$49,$81,$00,$00; 7DC8  HIIHI...
        .byte   $81,$00,$00,$81,$00,$90,$91,$91; 7DD0  ........
        .byte   $12,$92,$92,$12,$92,$80,$13,$5F; 7DD8  ......._
        .byte   $00,$03,$13             ; 7DE0  ...
L_7DE3:
        .byte   $07,$00,$03,$C6,$3C,$0C,$03,$00; 7DE3  ....<...
        .byte   $00,$00,$16,$23,$45,$88,$11,$E2; 7DEB  ...#E...
        .byte   $34                     ; 7DF3  4
L_7DF4:
        .byte   $0C,$00,$80,$80,$E0,$60,$38,$58; 7DF4  .....`8X
        .byte   $88,$13,$08,$00,$49,$48,$49,$49; 7DFC  ....IHII
        .byte   $48,$48,$4A,$48,$00,$81,$00,$00; 7E04  HHJH....
        .byte   $FF,$00,$00             ; 7E0C  ...
D_7E0F:
        .byte   $00,$92,$12,$92,$92,$12,$12,$52; 7E0F  .......R
        .byte   $12,$13,$70,$00,$03,$13,$07,$00; 7E17  ..p.....
        .byte   $10,$E0,$30,$0C,$03     ; 7E1F  ..0..
L_7E24:
        .byte   $13,$08,$00,$C0,$30,$0C ; 7E24  ....0.
L_7E2A:
        .byte   $4F                     ; 7E2A  O
L_7E2B:
        .byte   $7F,$13,$05,$80,$61,$FF,$FF,$00; 7E2B  ....a...
        .byte   $00,$00,$3F,$C0,$00,$F2 ; 7E33  ..?...
L_7E39:
        .byte   $FF,$00,$00,$00,$FF,$00,$00,$00; 7E39  ........
        .byte   $FF                     ; 7E41  .
L_7E42:
        .byte   $00,$00,$00,$FF,$00,$00,$00,$FF; 7E42  ........
        .byte   $00,$00,$00,$FF,$00     ; 7E4A  .....
sub_7E4F:
        .byte   $00,$00,$FF,$00,$00,$00,$FF,$00; 7E4F  ........
        .byte   $00,$00,$FF,$00,$00,$00,$FF,$00; 7E57  ........
        .byte   $00,$00,$FF,$00         ; 7E5F  ....
L_7E63:
        .byte   $00,$00,$FF,$00,$00,$00,$FF,$00; 7E63  ........
        .byte   $00,$00                 ; 7E6B  ..
L_7E6D:
        .byte   $F8,$07,$00             ; 7E6D  ...
L_7E70:
        .byte   $00                     ; 7E70  .
L_7E71:
        .byte   $FF,$13,$05             ; 7E71  ...
sub_7E74:
        .byte   $00,$C0,$00,$FF,$13,$07,$00,$FF; 7E74  ........
        .byte   $13,$04,$00,$3F,$C0,$00,$FF,$13; 7E7C  ...?....
        .byte   $04,$00,$FF,$00,$00,$FF,$13; 7E84  .......
L_7E8B:
        .byte   $04,$00,$FF,$00,$00,$FF,$13,$04; 7E8B  ........
        .byte   $00,$FF,$00,$00,$FF,$13,$04,$00; 7E93  ........
        .byte   $FF,$00                 ; 7E9B  ..
L_7E9D:
        .byte   $00,$FF,$13             ; 7E9D  ...
sub_7EA0:
        .byte   $04,$00,$FF,$00,$00,$FF,$13,$04; 7EA0  ........
        .byte   $00,$FC,$03,$00,$FF,$13,$06,$00; 7EA8  ........
L_7EB0:
        .byte   $02,$FF,$13,$06,$01,$1D,$12,$0A; 7EB0  ........
        .byte   $0B,$06,$02,$02,$03,$00,$00,$FF; 7EB8  ........
        .byte   $00,$1F,$26,$26,$E0,$00,$00,$FF; 7EC0  ..&&....
        .byte   $00,$FF,$41             ; 7EC8  ..A
L_7ECB:
        .byte   $41,$40,$00,$00,$FF,$00,$FF,$68; 7ECB  A@.....h
        .byte   $08,$F0,$00,$00         ; 7ED3  ....
L_7ED7:
        .byte   $FF,$00,$FF,$5A,$42,$3C,$00,$00; 7ED7  ...ZB<..
        .byte   $FF,$00,$FF,$16,$10,$0F,$00,$00; 7EDF  ........
        .byte   $FF,$00,$FF,$85,$84,$03,$00,$00; 7EE7  ........
        .byte   $FF,$00,$FF,$A0,$20,$C0,$00,$00; 7EEF  .... ...
        .byte   $FF,$00,$FE,$99,$99,$81,$2F,$10; 7EF7  ....../.
        .byte   $D0,$30,$10,$10,$1F,$F0,$FD,$02; 7EFF  .0......
        .byte   $02,$03,$02,$02,$FE,$03,$00,$00; 7F07  ........
        .byte   $FF,$00,$1F,$26,$26,$E0,$00,$00; 7F0F  ...&&...
        .byte   $FF,$00,$FF,$41,$41     ; 7F17  ...AA
L_7F1C:
        .byte   $40,$00,$00,$FF,$00,$FF,$68,$08; 7F1C  @.....h.
        .byte   $F0,$00,$00,$FF,$00,$FF,$5A,$42; 7F24  ......ZB
        .byte   $3C,$00,$00,$FF,$00,$FF,$16,$10; 7F2C  <.......
        .byte   $0F,$00,$00,$FF,$00,$FF,$82,$82; 7F34  ........
        .byte   $02,$00,$00             ; 7F3C  ...
sub_7F3F:
        .byte   $FF,$00,$F8,$64,$64,$07,$BF,$40; 7F3F  ...dd..@
        .byte   $40,$C3,$7C,$40,$40,$C0,$FE,$06; 7F47  @.|@@...
        .byte   $38,$C0,$13,$0C,$00,$10,$0F,$06; 7F4F  8.......
L_7F57:
        .byte   $03,$01,$00,$00,$00,$9E,$21,$40; 7F57  ......!@
        .byte   $CC,$CC,$C0,$61,$1F,$07,$08,$90; 7F5F  ...a....
        .byte   $93,$F3,$90,$08,$FF,$81,$42,$24; 7F67  ......B$
        .byte   $24,$3C,$24,$42,$FF,$E0,$10,$09; 7F6F  $<$B....
L_7F77:
        .byte   $C9,$CF,$09,$10,$FF,$78,$84,$02; 7F77  .....x..
        .byte   $32,$33,$02,$FC,$FF,$1E,$21,$40; 7F7F  23....!@
        .byte   $4C,$CC,$40,$21,$FE,$42,$3C,$98; 7F87  L.@!.B<.
        .byte   $B0,$E0,$C0,$80,$13,$11,$00,$10; 7F8F  ........
        .byte   $0F,$06,$03,$01,$00,$00,$00; 7F97  .......
L_7F9E:
        .byte   $9E,$21,$40             ; 7F9E  .!@
D_7FA1:
        .byte   $4C                     ; 7FA1  L
D_7FA2:
        .byte   $CC                     ; 7FA2  .
D_7FA3:
        .byte   $C0,$61,$1F             ; 7FA3  .a.
sub_7FA6:
        .byte   $07,$08,$90,$93,$F3,$90,$08,$FF; 7FA6  ........
        .byte   $81,$42,$24,$24,$3C,$24,$42,$FF; 7FAE  .B$$<$B.
        .byte   $E0,$10,$09,$C9         ; 7FB6  ....
L_7FBA:
        .byte   $CF,$09,$10,$FF,$79,$84,$02,$32; 7FBA  ....y..2
        .byte   $33,$03,$86,$F8,$08,$F0,$60,$C0; 7FC2  3.....`.
L_7FCA:
        .byte   $80                     ; 7FCA  .
sub_7FCB:
        .byte   $13,$13,$00             ; 7FCB  ...

; Three 8-byte lookup tables for the drone heading indicator, indexed 0-7.
joyDirToHeadingTable:
        .byte   $00,$04,$02,$06,$07,$01,$03,$05; 7FCE  ........  joyDirToHeadingTable: joystick direction code (0 N, 1 S, 2 E, 3 W, 4 NW, 5 NE, 6 SE, 7 SW) -> clockwise heading (0 N, 1 NE, 2 E, 3 SE, 4 S, 5 SW, 6 W, 7 NW); also read by steerDrone at $7320 and by launchDrone at $8092

; headingLineEndColTable: byteTable, 8 bytes. 0C 13 16 13 0C 05 03 05: end column inside the 24x21
; sprite of the heading pointer line for headings 0-7 (centre column 12).
headingLineEndColTable:
        .byte   $0C,$13,$16,$13         ; 7FD6  ....  headingLineEndColTable[0..3]: end column of the pointer line inside the 24x21 sprite for headings N, NE, E, SE (the line starts at column 12)
L_7FDA:
        .byte   $0C                     ; 7FDA  headingLineEndColTable[4..6]: headings S, SW, W
        .byte   $05,$03                 ; 7FDB  ..
sub_7FDD:
        .byte   $05                     ; 7FDD  headingLineEndColTable[7]: heading NW

; headingLineEndRowTable: byteTable, 8 bytes. 00 03 0A 11 14 11 0A 03: end row of the heading pointer
; line for headings 0-7 (centre row 10).
headingLineEndRowTable:
        .byte   $00,$03,$0A,$11,$14,$11,$0A,$03; 7FDE  ........  headingLineEndRowTable[0..7]: end row of the pointer line for headings N..NW (the line starts at row 10)

; ----------------------------------------------------------------------
; enterDroneScreen - Entry point of game screen 2, the DRONE screen (F5).  Copies this side's drone
; stock into the display counter, unpacks the eight aircraft silhouettes into the $0200 sprite shape
; buffer, builds the whole screen and falls into resetDroneScreen.
; In:  dronesLeft $92AE
; Out: radarDronesLeft $9233; $0200-$03FF = 8 sprite shapes of 64 bytes; screen 2 drawn; never returns
;      (the idle loop is entered by falling through)
; Called from: JMP at $06FC in switchToRequestedScreen when the requested screen becomes 2
; ----------------------------------------------------------------------
enterDroneScreen:
        lda     dronesLeft              ; 7FE6  $92AE = drones this side still owns
        sta     radarDronesLeft         ; 7FE9  $9233 = the digit painted in the count box at row 5 column 34
L_7FEC:
        lda     #$24                    ; 7FEC  low byte of the compressed shape set
        ldy     #$FE                    ; 7FEE  $FE24 in the high-memory block = eight aircraft silhouettes, one per heading
        jsr     unpackRleTo0200         ; 7FF0  decompress them into $0200-$03FF (64 bytes per heading)
        jsr     buildDroneScreen        ; 7FF3  draw the frames, panels, buttons, fuel read-out and the empty camera window

; ----------------------------------------------------------------------
; resetDroneScreen - (Re)initialises the drone screen's sprites and read-outs and drops into the idle
; loop that waits for a launch.  Parks sprite 0 (white heading line) at ($37,$64), sprite 3 (the same
; line one pixel down-right, used as its shadow) at ($38,$65) and sprite 5 (aircraft silhouette) at
; ($38,$64), all over the compass panel, and shows a full tank if a drone is still available.
; In:  dronesLeft $92AE
; Out: $9233, droneFuel $9220 (100 or 0), zp_5E = 0, sprite shadows $9008-$901D, spriteEnableShadow
;      $9022 = $09, activeSpriteIndex $91CC = 5; falls into droneScreenIdleLoop
; Called from: fall-through from enterDroneScreen, JMP at $81FB (endDroneFlight) and JMP at $076A
;      (the comcen knock-out sequence returning to this screen)
; ----------------------------------------------------------------------
resetDroneScreen:
        lda     dronesLeft              ; 7FF6  reload the drone stock ...
        sta     radarDronesLeft         ; 7FF9  ... into the on-screen counter

; ----------------------------------------------------------------------
; Park the three compass-panel sprites.  Sprite 0 is white and sits one pixel up and to the left of
; sprite 3, so the dark line of sprite 3 reads as its shadow.
; ----------------------------------------------------------------------
        ldx     #$37                    ; 7FFC  sprite X = $37 (left panel, low byte only - MSB stays clear)
        stx     spriteXShadow           ; 7FFE  $9010 = sprite 0 X shadow
        inx                             ; 8001  $38 = one pixel further right
        stx     sprite5XShadow          ; 8002  $9015 = sprite 5 (aircraft silhouette) X
        stx     sprite3XShadow          ; 8005  $9013 = sprite 3 (heading line) X
        ldy     #$64                    ; 8008  sprite Y = $64
        sty     spriteYShadow           ; 800A  $9018 = sprite 0 Y
        sty     sprite5YShadow          ; 800D  $901D = sprite 5 Y
L_8010:
        iny                             ; 8010  $65 = one pixel further down
        sty     sprite3YShadow          ; 8011  $901B = sprite 3 Y, so sprite 3 trails sprite 0 by (1,1)
        lda     #$01                    ; 8014  colour 1 = white
        sta     spriteColourShadow      ; 8016  $9008 = sprite 0 colour (the highlight of the pointer line)
        lda     #$02                    ; 8019  colour 2 = red
        sta     sprite5ColourShadow     ; 801B  $900D = sprite 5 colour (the aircraft)
        lda     #$00                    ; 801E  direction code 0 = due north
        sta     droneJoyDirection       ; 8020  zp_5E = the steer direction the pointer shows
        jsr     drawDroneHeadingIndicator; 8022  build the pointer line into sprites 3 and 0
        lda     #$05                    ; 8025  5 = sprite 5 is the shape target
        sta     activeSpriteIndex       ; 8027  $91CC selects sprite 5 inside loadDirectionShapeSprite
        jsr     loadDirectionShapeSprite; 802A  probable bug: A is still 5, so silhouette 5 (SW) is loaded although the pointer shows north
        lda     #$09                    ; 802D  bit 0 + bit 3 = sprites 0 and 3
        sta     spriteEnableShadow      ; 802F  assigned, not ORed - this also switches sprite 5 back off, so the idle panel shows only the pointer
        ldx     #$64                    ; 8032  a full tank is 100 exchanges of fuel
L_8034:
        lda     radarDronesLeft         ; 8034  any drone left to launch?
        bne     L_803A                  ; 8037  yes - keep 100
        tax                             ; 8039  no - A is 0, so show an empty tank
L_803A:
        stx     droneFuel               ; 803A  $9220 = fuel value printed by drawDroneFuelDigits

; ----------------------------------------------------------------------
; droneScreenIdleLoop - Main loop of the DRONE screen while nothing is flying.  Each frame it runs the
; game engine and honours a screen change; every 11 frames it flashes the LAUNCH button and, with
; fewer than two drones left, the count box.  A fresh fire press launches, unless the own comcen is
; cloaked (never) or the drone bay is damaged (then only one press in four gets through).
; In:  zp_6C blink countdown, radarDronesLeft $9233, comcenDroneStatus $9244, comcenCloakedFlag $9245,
;      the fire button
; Out: never returns - either jumps to launchDrone or leaves through switchToRequestedScreen /
;      updateGameFrame
; Called from: fall-through from resetDroneScreen; looped back to from $805E, $8071, $8082 and $808A
; ----------------------------------------------------------------------
droneScreenIdleLoop:
        jsr     updateGameFrame         ; 803D  one game frame: engine step, message line, sound, timers
L_8040:
        jsr     switchToRequestedScreen ; 8040  act on an F1/F3/F5/F7 request - does not return if the screen changes
        jsr     checkFirePressedInteractive; 8043  Z=1 only for a fresh press, and only in a live (non-solo, non-film) game
        bne     L_805C                  ; 8046  no press - fall through to the blinking
        lda     comcenCloakedFlag       ; 8048  $9245 bit 7 = our comcen is cloaked
        bmi     L_805C                  ; 804B  a cloaked comcen cannot launch drones
        lda     comcenDroneStatus       ; 804D  $9244 != 0 = the drone bay has been knocked out
        beq     L_8059                  ; 8050  undamaged - accept the launch
        jsr     nextGameRandom          ; 8052  damaged: roll the game RNG
        and     #$03                    ; 8055  keep two bits
        bne     L_805C                  ; 8057  three presses out of four are swallowed
L_8059:
        jmp     L_8085                  ; 8059  go and check that a drone is actually in stock


; ----------------------------------------------------------------------
; Blinking, once every 11 frames.
; ----------------------------------------------------------------------
L_805C:
        lda     joyRepeatTimer          ; 805C  zp_6C is decremented once per frame by the IRQ
L_805E:
        bne     droneScreenIdleLoop     ; 805E  not expired yet - keep polling
        lda     radarDronesLeft         ; 8060  how many drones are left?
        cmp     #$02                    ; 8063  fewer than two?
        bcs     L_806A                  ; 8065  no - leave the count box alone
        jsr     blinkDroneCountBox      ; 8067  yes - pulse the count box as a warning
L_806A:
        lda     #$0B                    ; 806A  11 frames ...
        sta     joyRepeatTimer          ; 806C  ... until the next blink
        lda     radarDronesLeft         ; 806E  drones left?
        beq     droneScreenIdleLoop     ; 8071  none - leave the LAUNCH button dark
        lda     #$0C                    ; 8073  character row 12 ...
        sta     windowOriginRow         ; 8075  ... into the colour-fill origin row
        lda     #$22                    ; 8078  character column 34 = the LAUNCH button ...
        sta     windowOriginCol         ; 807A  ... into the colour-fill origin column
        lda     #$08                    ; 807D  colour 8 (orange)
        jsr     xorColourBlock2x2       ; 807F  XOR it into the 2x2 button cells so the button flashes
        jmp     droneScreenIdleLoop     ; 8082  round again

L_8085:
        lda     radarDronesLeft         ; 8085  drones in stock?
L_8088:
        bne     launchDrone             ; 8088  yes - go
        jmp     droneScreenIdleLoop     ; 808A  no - ignore the press


; ----------------------------------------------------------------------
; launchDrone - Sends a drone on its way: latches the joystick heading, queues command $97 (drone
; launch) for the engine and the opponent, lights the LAUNCH button, fills the tank with 100, drops
; the stock by one and puts the 14 x 12 camera window over our own comcen, where the drone starts.
; Then it loads the targeting reticle into sprite 2, the aircraft silhouette into sprite 5, plays the
; launch sound and opens the camera with a grey iris wipe.
; In:  zp_5D joystick, comcenUnit $9236, unitColTable/unitRowTable, the reticle shape at $FF93
; Out: droneHeading $920B, droneControlFlags $920D |= $C0, droneFuel $9220 = 100, $9233 decremented,
;      droneViewOriginCol/Row $92F6/$92F7 and zp_A5/zp_A6 = comcen - (6,5), sprites 2/4/5, command $97
;      queued; falls into droneFlightLoop
; Called from: branch at $8088 in droneScreenIdleLoop
; ----------------------------------------------------------------------
launchDrone:
        jsr     readDroneHeadingFromJoystick; 808D  sample the stick that was held while fire was pressed
        ldy     droneJoyDirection       ; 8090  zp_5E = direction code 0-7
        lda     joyDirToHeadingTable,y  ; 8092  translate it into a clockwise heading 0-7 starting at north
        sta     droneHeading            ; 8095  $920B: the heading steerDrone turns towards and $8D reports every exchange
L_8098:
        lda     #$97                    ; 8098  command $97 = drone launched (one byte, no arguments)
        jsr     queueCmdBytePending     ; 809A  queue it so the engine and the opponent both see it
        lda     #$0C                    ; 809D  character row 12 ...
        sta     windowOriginRow         ; 809F  ... fill origin row
        lda     #$22                    ; 80A2  character column 34 = the LAUNCH button ...
        sta     windowOriginCol         ; 80A4  ... fill origin column
L_80A7:
        lda     #$05                    ; 80A7  colour 5 (green)
        ldx     #$02                    ; 80A9  2 character columns
        ldy     #$02                    ; 80AB  2 character rows
        jsr     fillScreenColourRect    ; 80AD  light the button up solid
        lda     #$64                    ; 80B0  100 = a full tank (one unit per lock-step exchange)
        sta     droneFuel               ; 80B2  $9220
        jsr     drawDroneFuelDigits     ; 80B5  print it as three tall digits at row 15
        dec     radarDronesLeft         ; 80B8  one drone fewer in stock
        jsr     drawDroneCountDigit     ; 80BB  reprint the count digit
        lda     droneControlFlags       ; 80BE  $920D drone state flags
        ora     #$C0                    ; 80C1  bit 7 = one of our drones is airborne, bit 6 = we are steering it
        sta     droneControlFlags       ; 80C3  store back (bit 0, the enemy-drone alert, is left alone)

; ----------------------------------------------------------------------
; Put the camera window over our comcen: the window is 14 x 12 cells and keeps the drone in the
; middle, so its origin is the drone cell minus (6,5).
; ----------------------------------------------------------------------
        ldy     comcenUnit              ; 80C6  own comcen index
        lda     unitColTable,y          ; 80C9  its map column
        sec                             ; 80CC  no borrow
        sbc     #$06                    ; 80CD  6 columns to the left of the drone
        sta     viewOriginCol           ; 80CF  zp_A5 = view origin column
        sta     droneViewOriginCol      ; 80D1  $92F6 = the drone's own camera origin, stepped by steerDrone
        lda     unitRowTable,y          ; 80D4  comcen map row
        sec                             ; 80D7  no borrow
        sbc     #$05                    ; 80D8  5 rows above the drone
        sta     viewOriginRow           ; 80DA  zp_A6 = view origin row
        sta     droneViewOriginRow      ; 80DC  $92F7
        ldx     #$00                    ; 80DF  leftover: drawComcenRingSprite reads nothing from X
L_80E1:
        jsr     drawComcenRingSprite    ; 80E1  sprite 4 = the two concentric rings marking our comcen under the drone

; ----------------------------------------------------------------------
; Load the targeting reticle into sprite 2 and park it in the centre of the camera window.
; ----------------------------------------------------------------------
L_80E4:
        lda     #$00                    ; 80E4  colour 0 = black
        sta     sprite2ColourShadow     ; 80E6  $900A = sprite 2 colour
        ldy     #$3F                    ; 80E9  64 shape bytes, copied backwards
L_80EB:
        lda     droneViewfinderSprite,y ; 80EB  $FF93 in the high-memory block holds the reticle shape (the last byte read, $FFD2, is one past the loaded block but is the unused 64th sprite byte)
        sta     sprite2Shape,y          ; 80EE  sprite 2 data at $8A80
        dey                             ; 80F1  next byte down
        bpl     L_80EB                  ; 80F2  until byte 0 has been copied
        lda     #$AC                    ; 80F4  X = $AC, the middle of the camera window
        sta     sprite2XShadow          ; 80F6  $9012 = sprite 2 X
        lda     #$88                    ; 80F9  Y = $88
L_80FB:
        sta     sprite2YShadow          ; 80FB  $901A = sprite 2 Y
        lda     #$04                    ; 80FE  bit 2 = sprite 2
        jsr     enableSprites           ; 8100  OR it into the sprite enable shadow
L_8103:
        lda     #$05                    ; 8103  5 = sprite 5
        sta     activeSpriteIndex       ; 8105  $91CC selects sprite 5 for the shape load
        lda     droneHeading            ; 8108  silhouette index = the launch heading
        jsr     loadDirectionShapeSprite; 810B  copy that shape to $8B40 and enable sprite 5
        jsr     resetDrawModes          ; 810E  back to plain replace draw modes

; ----------------------------------------------------------------------
; Launch effect: sound, grey static in the camera window, then the iris opens.
; ----------------------------------------------------------------------
        lda     #$0F                    ; 8111  sound $0F = launch
        jsr     playSound               ; 8113  play it
        jsr     clearRadarWindowGrey    ; 8116  fill the camera window with $BB (dark grey) and set radarWipeColour so the iris animates slowly
        lda     #$AA                    ; 8119  $AA = light red in both multicolour nibbles
        sta     useDrawColourFlag       ; 811B  $2A29 != 0 forces every following draw to use one fixed colour ...
        sta     drawColourByte          ; 811E  ... and D_2A2A holds it
        jsr     drawRadarIrisWipe       ; 8121  shrinking concentric frames over the camera window

; ----------------------------------------------------------------------
; droneFlightLoop - Per-frame loop while our drone is in the air.  A fire press jumps to
; strikeWithDrone; every 40 frames the STRIKE button (and, below two drones, the count box) flashes;
; when the joystick lockout has expired the heading is re-read.  The camera only advances when
; exchangeAppliedFlag ($920E) bit 7 says a lock-step command exchange has been applied, which keeps
; both machines in step.  The flight ends when the tank runs dry, when the game ends, or when the
; command handlers clear droneControlFlags bit 7 (drone arrived / detonated by the other side).
; In:  zp_6C, zp_6E, exchangeAppliedFlag $920E, droneControlFlags $920D, droneFuel $9220,
;      gameEndReason $0BA8, the fire button
; Out: camera redrawn one step per exchange; falls into endDroneFlight
; Called from: fall-through from launchDrone; looped back to from the JMP at $8171
; ----------------------------------------------------------------------
droneFlightLoop:
        jsr     updateGameFrame         ; 8124  run one game frame
        jsr     checkFirePressed        ; 8127  Z=1 on a fresh fire press (this variant also fires during solo/film)
        bne     L_812F                  ; 812A  no press - carry on flying
        jmp     strikeWithDrone         ; 812C  fire during flight = detonate here

L_812F:
        lda     joyRepeatTimer          ; 812F  blink countdown
        bne     L_8150                  ; 8131  not expired - skip the blinking
        lda     #$28                    ; 8133  40 frames ...
        sta     joyRepeatTimer          ; 8135  ... until the next blink (slower than the idle loop's 11)
        lda     radarDronesLeft         ; 8137  drones still in stock?
        cmp     #$02                    ; 813A  fewer than two?
L_813C:
        bcs     L_8141                  ; 813C  no - leave the count box
        jsr     blinkDroneCountBox      ; 813E  pulse the count box
L_8141:
        lda     #$10                    ; 8141  character row 16 ...
flightStrikeBoxOrigin:
        sta     windowOriginRow         ; 8143  ... fill origin row
        lda     #$22                    ; 8146  character column 34 = the STRIKE button ...
        sta     windowOriginCol         ; 8148  ... fill origin column
        lda     #$08                    ; 814B  colour 8 (orange)
L_814D:
        jsr     xorColourBlock2x2       ; 814D  XOR it into the 2x2 button cells
L_8150:
        lda     menuBlinkTimer          ; 8150  zp_6E = the 20-frame lockout between joystick reads
        bne     L_8157                  ; 8152  still locked out - do not re-read the stick
        jsr     readDroneHeadingFromJoystick; 8154  latch a new steer direction and redraw the pointer

; ----------------------------------------------------------------------
; The camera moves exactly one cell per lock-step command exchange, never per frame.
; ----------------------------------------------------------------------
L_8157:
        lda     exchangeAppliedFlag     ; 8157  $920E bit 7 = an exchange was applied this frame
L_815A:
        bmi     L_815F                  ; 815A  yes - step the camera
        jmp     L_8167                  ; 815C  no - a plain BPL $8167 would have done; the JMP is two bytes of slack

L_815F:
        jsr     advanceDroneCamera      ; 815F  redraw the camera window and burn one unit of fuel
        lda     droneFuel               ; 8162  tank empty?
        beq     L_8174                  ; 8165  yes - hand the drone back to the engine

; ----------------------------------------------------------------------
; Decide whether the flight goes on for another frame.
; ----------------------------------------------------------------------
L_8167:
        lda     droneControlFlags       ; 8167  $920D drone state
        bpl     L_8177                  ; 816A  bit 7 cleared by cmdDroneArrived ($92) or a remote detonation - end without sending $8F
        lda     gameEndReason           ; 816C  $0BA8 != 0 = the game has ended
        bne     L_8174                  ; 816F  then release the drone as well
        jmp     droneFlightLoop         ; 8171  otherwise keep flying

L_8174:
        jsr     releaseDroneControl     ; 8174  clear bits 7-6 and queue command $8F so the opponent stops tracking the drone
L_8177:
        jsr     playDroneLostAnimation  ; 8177  white flash, iris close and the shrinking disc
        lda     #$1C                    ; 817A  sound $1C = explosion ...
        jsr     playSound               ; 817C  ... which endDroneFlight immediately plays a second time
        jmp     endDroneFlight          ; 817F  common tail


; ----------------------------------------------------------------------
; strikeWithDrone - Fire pressed while the drone is flying: stop steering it, mark a detonation as
; pending (droneControlFlags bit 5), light the STRIKE button and queue command $8E col,row with the
; drone's current map cell.  Then it waits, still flying the camera one step per exchange, until the
; command handler executes our own $8E and clears bit 5; the strike then gets its sound and the
; zoom-in animation.
; In:  droneControlFlags $920D, exchangeAppliedFlag $920E, droneViewOriginCol/Row $92F6/$92F7
; Out: command $8E queued with cmdParam1/2, $920D bit 6 cleared and bit 5 set, sound and animation;
;      falls into endDroneFlight
; Called from: JMP at $812C in droneFlightLoop
; ----------------------------------------------------------------------
strikeWithDrone:
        lda     droneControlFlags       ; 8182  $920D drone state
        and     #$BF                    ; 8185  clear bit 6 - we stop steering from now on
        ora     #$20                    ; 8187  set bit 5 - our own detonate command is queued but not yet executed
        sta     droneControlFlags       ; 8189  store back
        lda     #$10                    ; 818C  character row 16 ...
        sta     windowOriginRow         ; 818E  ... fill origin row
        lda     #$22                    ; 8191  character column 34 = the STRIKE button ...
        sta     windowOriginCol         ; 8193  ... fill origin column
        lda     #$02                    ; 8196  colour 2 (red)
        ldx     #$02                    ; 8198  2 character columns
        ldy     #$02                    ; 819A  2 character rows
        jsr     fillScreenColourRect    ; 819C  light the STRIKE button solid
        jsr     getDroneMapPosition     ; 819F  zp_74/zp_75 = the drone's own map cell
        lda     #$8E                    ; 81A2  command $8E = detonate a drone at col,row
        jsr     queueCmd3               ; 81A4  queue the 3-byte command

; ----------------------------------------------------------------------
; Wait for our own $8E to come back out of the command stream; keep the camera moving (with a rumble)
; on every exchange until then.
; ----------------------------------------------------------------------
L_81A7:
        jsr     updateGameFrame         ; 81A7  one game frame
        lda     droneControlFlags       ; 81AA  $920D drone state
        bpl     endDroneFlight          ; 81AD  bit 7 gone - the drone was destroyed before the command landed
        and     #$20                    ; 81AF  test the 'detonation pending' bit
        beq     L_81C3                  ; 81B1  cleared = cmdDroneDetonate has run our command
        lda     exchangeAppliedFlag     ; 81B3  $920E bit 7 = an exchange was applied
strikeWaitForExchange:
        bpl     L_81A7                  ; 81B6  not yet - keep waiting
        jsr     advanceDroneCamera      ; 81B8  another camera step
        lda     #$1C                    ; 81BB  sound $1C ...
        jsr     playSound               ; 81BD  ... on every step, so the drone rumbles towards the target
        jmp     L_81A7                  ; 81C0  keep waiting

L_81C3:
        lda     droneControlFlags       ; 81C3  dead test: bit 7 was already proved set at $81AD and nothing here clears it
        bpl     endDroneFlight          ; 81C6  never taken
D_81C8:
        lda     #$11                    ; 81C8  sound $11 = the strike itself
        jsr     playSound               ; 81CA  play it
        jsr     playStrikeZoomAnimation ; 81CD  four 2x zooms into the camera centre, then the drone-lost animation

; ----------------------------------------------------------------------
; endDroneFlight - Common tail of every drone flight: explosion sound, empty the tank, clear the
; 'airborne' and 'we steer it' bits, put the camera window back over our own comcen, blank it without
; static and restart the idle screen.
; In:  comcenUnit $9236, unitColTable/unitRowTable, droneControlFlags $920D
; Out: droneFuel $9220 = 0 and reprinted, $920D &= $3F, zp_A5/zp_A6 = comcen - (6,5), camera window
;      black; jumps to resetDroneScreen
; Called from: JMP at $817F and branches at $81AD / $81C6, plus fall-through from strikeWithDrone
; ----------------------------------------------------------------------
endDroneFlight:
        lda     #$1C                    ; 81D0  sound $1C = explosion
        jsr     playSound               ; 81D2  play it
        lda     #$00                    ; 81D5  no fuel left
        sta     droneFuel               ; 81D7  $9220
        jsr     drawDroneFuelDigits     ; 81DA  read-out back to 000
L_81DD:
        lda     droneControlFlags       ; 81DD  $920D drone state
        and     #$3F                    ; 81E0  clear bit 7 (airborne) and bit 6 (locally steered); bit 5 and bit 0 survive
        sta     droneControlFlags       ; 81E2  store back
        ldy     comcenUnit              ; 81E5  own comcen index
        lda     unitColTable,y          ; 81E8  its map column
        sec                             ; 81EB  no borrow
        sbc     #$06                    ; 81EC  the 14-wide window is centred 6 columns to the right of its origin
        sta     viewOriginCol           ; 81EE  zp_A5
        lda     unitRowTable,y          ; 81F0  comcen map row
        sec                             ; 81F3  no borrow
endFlightRowOffset:
        sbc     #$05                    ; 81F4  the 12-high window is centred 5 rows below its origin
        sta     viewOriginRow           ; 81F6  zp_A6
        jsr     clearRadarWindowBlack   ; 81F8  blank the camera window to colour 0 (no grey static this time)
        jmp     resetDroneScreen        ; 81FB  back to the idle screen, ready for the next launch


; ----------------------------------------------------------------------
; getDroneMapPosition - Converts the camera origin into the drone's own map cell: the 14 x 12 window
; is kept centred on the drone, so the drone stands at origin + (6,5).
; In:  droneViewOriginCol $92F6, droneViewOriginRow $92F7
; Out: cmdParam1 zp_74 = drone map column, cmdParam2 zp_75 = drone map row
; Called from: $819F (strikeWithDrone, for the $8E detonate command) and $563C in sendOutgoingPacket
;      (for the $8D heading,col,row move report sent every exchange)
; ----------------------------------------------------------------------
getDroneMapPosition:
        lda     droneViewOriginCol      ; 81FE  $92F6 = map column of the window's left edge
        clc                             ; 8201  no carry in
L_8202:
        adc     #$06                    ; 8202  the drone sits 6 columns into the 14-wide window
        sta     cmdParam1               ; 8204  zp_74 = command argument 1
        lda     droneViewOriginRow      ; 8206  $92F7 = map row of the window's top edge
        clc                             ; 8209  no carry in
L_820A:
        adc     #$05                    ; 820A  and 5 rows into the 12-high window
        sta     cmdParam2               ; 820C  zp_75 = command argument 2
        rts                             ; 820E  done


; ----------------------------------------------------------------------
; showDroneAlert - Notification that the opponent has launched a drone: plays the alert sound and, in
; a real two-player game, queues the 'DRONE ALERT!' status message.  In a solo or film game the
; message is suppressed because there is nobody to warn.
; In:  filmPlaybackMode $0B9B bit 7 (set = solo trainer or film playback)
; Out: sound 7 played, message $20 queued
; Called from: $53AF, the cmdDroneLaunch handler of command $97 in the main program
; ----------------------------------------------------------------------
showDroneAlert:
        lda     #$07                    ; 820F  sound 7 = alert
        jsr     playSound               ; 8211  play it
        lda     filmPlaybackMode        ; 8214  $0B9B bit 7 = solo trainer or film playback
        bmi     L_821E                  ; 8217  then say nothing
        lda     #$20                    ; 8219  message id $20 = 'DRONE ALERT!'
        jsr     queueMessage            ; 821B  insert it into the status message queue
L_821E:
        rts                             ; 821E  done (also the RTS shared by releaseDroneControl)


; ----------------------------------------------------------------------
; fillDroneViewWindow - Fills the drone camera window (14 columns x 12 rows starting at character row
; 6, column 13) with one screen RAM colour byte.
; In:  A = colour byte
; Out: 168 colour cells written, D_2A2D = 6, D_2A2E = $0D, D_2A2A = A
; Called from: $7141 (the shared radar-clear tail), $8290 (static when the drone bay is damaged) and
;      $2E07 in the main program
; ----------------------------------------------------------------------
fillDroneViewWindow:
        ldy     #$06                    ; 821F  camera window starts at character row 6
        sty     windowOriginRow         ; 8221  D_2A2D = fill origin row
        ldy     #$0D                    ; 8224  ... and character column 13
        sty     windowOriginCol         ; 8226  D_2A2E = fill origin column
        ldx     #$0E                    ; 8229  14 character columns = 14 map cells
        ldy     #$0C                    ; 822B  12 character rows = 12 map cells
        jmp     fillScreenColourRect    ; 822D  fill and return to the caller


; ----------------------------------------------------------------------
; releaseDroneControl - Gives an airborne drone back to the engine: clears the 'airborne' and 'locally
; steered' bits and queues the single-byte command $8F so the opponent stops tracking it.  Does
; nothing when no drone of ours is up.
; In:  droneControlFlags $920D
; Out: $920D &= $3F, command $8F queued
; Called from: $8174 (fuel out or game over) and $0709 (the comcen knock-out sequence)
; ----------------------------------------------------------------------
releaseDroneControl:
        lda     droneControlFlags       ; 8230  $920D drone state
        bpl     L_821E                  ; 8233  bit 7 clear - nothing of ours is airborne, so nothing to release
        and     #$3F                    ; 8235  clear bit 7 (airborne) and bit 6 (steered by us)
        sta     droneControlFlags       ; 8237  store back
        lda     #$8F                    ; 823A  command $8F = drone released
        jmp     queueCmdBytePending     ; 823C  queue it and return through the queue routine


; ----------------------------------------------------------------------
; drawDroneHeadingIndicator - Redraws the compass pointer of the drone screen's left panel.  It clears
; sprite 3, draws a line inside it from the sprite centre (column 12, row 10) out to the end point
; looked up for the current heading, colours it, then copies the identical shape into sprite 0.
; Because sprite 0 is white and sits one pixel up and left of sprite 3, the pointer appears as a white
; line with a dark shadow.
; In:  droneJoyDirection zp_5E, joyDirToHeadingTable $7FCE, headingLineEndColTable $7FD6,
;      headingLineEndRowTable $7FDE
; Out: sprite 3 shape $8AC0 and sprite 0 shape $8A00 redrawn, sprite3ColourShadow $900B, sprites 0 and
;      3 enabled; zp_92-zp_95 and the line stepper state clobbered
; Called from: $8022 (resetDroneScreen) and $82B1 (readDroneHeadingFromJoystick)
; ----------------------------------------------------------------------
drawDroneHeadingIndicator:
        lda     #$03                    ; 823F  sprite 3 = the path/route sprite at $8AC0
        jsr     clearSpriteShape        ; 8241  zero its 64 shape bytes
        lda     #$0A                    ; 8244  sprite row 10 ...
        sta     lineCurRow              ; 8246  ... zp_92 = line start row (centre of the 24x21 sprite)
        lda     #$0C                    ; 8248  sprite column 12 ...
        sta     lineCurCol              ; 824A  ... zp_93 = line start column
        lda     #$0B                    ; 824C  default line colour $0B (dark grey)
L_824E:
        ldy     droneJoyDirection       ; 824E  current steer direction code
        cpy     #$02                    ; 8250  codes 0 and 1 are due north and due south
        bcs     L_8256                  ; 8252  any other direction keeps dark grey
        lda     #$06                    ; 8254  N/S get colour 6 (blue) instead
L_8256:
        sta     sprite3ColourShadow     ; 8256  $900B = sprite 3 colour
        ldx     joyDirToHeadingTable,y  ; 8259  direction code -> clockwise heading 0-7
        lda     headingLineEndRowTable,x; 825C  end row of the pointer for that heading
        sta     lineEndRow              ; 825F  zp_94 = line end row
        lda     headingLineEndColTable,x; 8261  end column of the pointer
L_8264:
        sta     lineEndCol              ; 8264  zp_95 = line end column
        jsr     drawLineInPathSprite    ; 8266  A still holds the end column ($03-$16), so bit 7 is clear and the line is drawn solid

; ----------------------------------------------------------------------
; Sprite 0 carries an identical copy of the line, one pixel up and left and in white.
; ----------------------------------------------------------------------
        ldy     #$3F                    ; 8269  64 shape bytes, copied backwards
L_826B:
        lda     sprite3Shape,y          ; 826B  read sprite 3's shape
        sta     sprite0Shape,y          ; 826E  write sprite 0's shape at $8A00
        dey                             ; 8271  next byte down
        bpl     L_826B                  ; 8272  until byte 0
        lda     #$09                    ; 8274  bit 0 + bit 3 = sprites 0 and 3
L_8276:
        jmp     enableSprites           ; 8276  OR them into the enable shadow and return


; ----------------------------------------------------------------------
; advanceDroneCamera - One drone step on the DRONE screen, run once per lock-step command exchange. It
; consumes the exchange flag, redraws the 14 x 12 camera window from the map around the drone,
; replaces half of the pictures with static when the drone bay is damaged, burns one unit of fuel and
; reloads the silhouette for the drone's current heading.  The drone itself is moved by steerDrone,
; which sendOutgoingPacket calls just before the $8D report goes out.
; In:  exchangeAppliedFlag $920E, droneHeading $920B, droneFuel $9220, comcenDroneStatus $9244,
;      droneViewOriginCol/Row $92F6/$92F7
; Out: $920E = 0, $9220 decremented, camera window redrawn, sprite 5 reloaded, fuel read-out reprinted
; Called from: $815F (droneFlightLoop) and $81B8 (strikeWithDrone waiting for its $8E)
; ----------------------------------------------------------------------
advanceDroneCamera:
        lda     #$00                    ; 8279  clear the flag ...
        sta     exchangeAppliedFlag     ; 827B  ... so only one camera step happens per exchange
        jsr     updateExplosionMarker   ; 827E  in this build this only switches sprite 4 (the comcen ring) off again
        jsr     drawDroneRadarWindow    ; 8281  redraw the 14x12 camera window from the map around zp_A5/zp_A6
        lda     comcenDroneStatus       ; 8284  $9244 != 0 = the drone bay has been knocked out
        beq     L_8293                  ; 8287  undamaged - show a clean picture
        jsr     nextGameRandom          ; 8289  roll the game RNG
        bmi     L_8293                  ; 828C  bit 7 set - this step still shows a clean picture
        lda     #$AA                    ; 828E  $AA = light red in both nibbles
        jsr     fillDroneViewWindow     ; 8290  otherwise bury the window in static
L_8293:
        dec     droneFuel               ; 8293  one unit of fuel per exchange, so 100 exchanges of flight
        lda     #$05                    ; 8296  5 = sprite 5
        sta     activeSpriteIndex       ; 8298  $91CC selects sprite 5
        lda     droneHeading            ; 829B  current heading, which steerDrone turns one step per exchange
D_829E:
        jsr     loadDirectionShapeSprite; 829E  reload the matching silhouette and enable sprite 5
        jmp     drawDroneFuelDigits     ; 82A1  reprint the fuel digits and return through them


; ----------------------------------------------------------------------
; readDroneHeadingFromJoystick - Latches the joystick direction as the drone's new steer direction and
; redraws the compass pointer.  A centred stick ($FF from joystickDirectionTable) leaves the current
; heading alone; after a successful read the stick is ignored for 20 frames so the pointer cannot
; spin.
; In:  joyDirection zp_5D (the IRQ-latched joystick 2 nibble), joystickDirectionTable $1FA6
; Out: droneJoyDirection zp_5E, menuBlinkTimer zp_6E = 20, sprites 0 and 3 redrawn
; Called from: $808D (launchDrone) and $8154 (droneFlightLoop, once the lockout has expired)
; ----------------------------------------------------------------------
readDroneHeadingFromJoystick:
        ldy     joyDirection            ; 82A4  zp_5D = joystick 2 direction bits latched by the IRQ
        lda     joystickDirectionTable,y; 82A6  $1FA6 turns them into a direction code 0-7, or $FF when centred
        bmi     L_82B4                  ; 82A9  stick centred - keep steering the way we were
        ldy     #$14                    ; 82AB  20 frames ...
        sty     menuBlinkTimer          ; 82AD  ... of lockout before the stick is read again
        sta     droneJoyDirection       ; 82AF  zp_5E = the new steer direction
        jsr     drawDroneHeadingIndicator; 82B1  redraw the compass pointer to match
L_82B4:
        rts                             ; 82B4  done


; shotLineWorkVars - the eight work bytes of drawShotLineInViewport, zero in the loaded image.
shotLineWorkVars:
        .byte   $00                     ; 82B5  .  viewLeft: map column of the viewport's left edge; later rebased to 0 and multiplied by 8 (fat pixels)
D_82B6:
        .byte   $00                     ; 82B6  column bound used by the endpoint test = viewLeft + 7 (the window is 7 cells wide)
D_82B7:
        .byte   $00                     ; 82B7  viewTop: map row of the viewport's top edge; later rebased and multiplied by 8
D_82B8:
        .byte   $00                     ; 82B8  row bound used by the endpoint test = viewTop + 5 (the window is 5 cells high)
D_82B9:
        .byte   $00                     ; 82B9  endpoint visibility: $FF = only the shooter is in view, $02 = only the target, $01 = both, 0 = neither
D_82BA:
        .byte   $00                     ; 82BA  shift added to every column when the view origin is negative (= -viewOriginCol)
D_82BB:
        .byte   $00                     ; 82BB  shift added to every row when the view origin is negative (= -viewOriginRow)
D_82BC:
        .byte   $00                     ; 82BC  countdown of the muzzle gap: 4 down to 1 = skip, 0 = draw the diamond, negative = draw the line

; unusedBytes82BD - $00,$60: not referenced from anywhere in this overlay variant, padding in front of
; drawShotLineInViewport.
unusedBytes82BD:
        .byte   $00,$60                 ; 82BD  .`  unreferenced filler

; ----------------------------------------------------------------------
; drawShotLineInViewport - Draws the tracer of one shot inside the 7 x 5 cell zoomed battlefield
; viewport (2 x 2 characters per cell at character row 1, column 25; 8 'fat' multicolour pixels per
; map cell, fat-pixel origin X=100, Y=4).  It first forces the %01 colour of all 14 x 10 viewport
; cells to white, then decides whether the shooter and/or the target cell is inside the window,
; rebases and scales the coordinates, walks the line with the high-memory stepper and plots colour-1
; fat pixels.  The first four steps out of the muzzle are left blank, the fifth carries a small
; diamond, and a BOOMER shot ($923D = 2) advances three cells per plotted pixel, which makes it
; dashed.  Finally viewRedrawTimer is armed so the view is redrawn - erasing the line - 15 frames on.
; In:  zp_92/zp_93 = shooter row/column, zp_94/zp_95 = target row/column, zp_A5/zp_A6 = view origin,
;      attackerType $923D, the screen row tables at $9382/$939B
; Out: bitmap pixels and viewport colour nibbles changed, viewRedrawTimer $9234 = $8F, $82B5-$82BC
;      used, zp_92-zp_95 and the line stepper state clobbered, zp_B6/zp_B7 clobbered
; Called from: $3A30, resolveAttack in the main program.  The only user of plotFatPixel ($FC58).
; Force the %01 bit pair of every character cell of the viewport (rows 1-10, columns 25-38) to white
; so the line about to be plotted is visible whatever the terrain colours were.
; ----------------------------------------------------------------------
drawShotLineInViewport:
        ldy     #$01                    ; 82BF  screen row table index 1 = character row 1, the top of the viewport
        lda     screenRowLoTable,y      ; 82C1  screen RAM ($8C00) row address, low byte
        sta     screenPtr               ; 82C4  zp_B6
        lda     screenRowHiTable,y      ; 82C6  row address high byte
        sta     screenPtrHi             ; 82C9  zp_B7 - the loop writes screen RAM, not colour RAM
        ldx     #$0A                    ; 82CB  10 character rows = 5 map cells
L_82CD:
        ldy     #$26                    ; 82CD  start at the rightmost character column, 38
L_82CF:
        lda     (screenPtr),y           ; 82CF  current colour byte of the cell
        and     #$0F                    ; 82D1  keep the low nibble = the %10 colour
L_82D3:
        ora     #$10                    ; 82D3  force the high nibble to 1 = white for the %01 bit pair
        sta     (screenPtr),y           ; 82D5  store it back
        dey                             ; 82D7  next column to the left
        cpy     #$19                    ; 82D8  leftmost viewport column is 25
        bcs     L_82CF                  ; 82DA  keep going while Y >= 25
        dex                             ; 82DC  one row done
        beq     L_82EC                  ; 82DD  all ten rows done
        clc                             ; 82DF  clear carry
        lda     screenPtr               ; 82E0  screen pointer low byte
        adc     #$28                    ; 82E2  one character row = 40 bytes
        sta     screenPtr               ; 82E4  zp_B6
        bcc     L_82CD                  ; 82E6  no carry - next row
        inc     screenPtrHi             ; 82E8  carry into the high byte
        bne     L_82CD                  ; 82EA  always taken - next row

; ----------------------------------------------------------------------
; The 7x5 window may hang off the left or top edge of the map, in which case zp_A5/zp_A6 are negative.
; Shift the origin and both endpoints right/down by -origin so all the following arithmetic stays
; unsigned, and work out the window bounds.
; ----------------------------------------------------------------------
L_82EC:
        lda     #$00                    ; 82EC  no endpoint seen yet
        sta     D_82B9                  ; 82EE  $82B9 = endpoint visibility flags
        lda     viewOriginCol           ; 82F1  view origin column
        bpl     L_8313                  ; 82F3  non-negative - use it as it is
        eor     #$FF                    ; 82F5  negate it: EOR $FF ...
L_82F7:
        clc                             ; 82F7  ... clear carry ...
        adc     #$01                    ; 82F8  ... +1 gives -viewOriginCol
        sta     D_82BA                  ; 82FA  $82BA = the column shift
        clc                             ; 82FD  clear carry
        lda     lineCurCol              ; 82FE  shooter column
        adc     D_82BA                  ; 8300  shift it
        sta     lineCurCol              ; 8303  zp_93 updated
        clc                             ; 8305  clear carry
        lda     lineEndCol              ; 8306  target column
        adc     D_82BA                  ; 8308  shift it
        sta     lineEndCol              ; 830B  zp_95 updated
        clc                             ; 830D  clear carry
        lda     viewOriginCol           ; 830E  the origin itself
        adc     D_82BA                  ; 8310  shifted origin = 0
L_8313:
        sta     shotLineWorkVars        ; 8313  $82B5 = left edge of the window (now never negative)
        clc                             ; 8316  clear carry
        adc     #$07                    ; 8317  the window is 7 cells wide
D_8319:
        sta     D_82B6                  ; 8319  $82B6 = right bound used below (note it is left+7, while the value tested against it is already relative to left - too permissive for a non-zero origin; the fat-pixel clip in the plot loop catches the difference)
        lda     viewOriginRow           ; 831C  view origin row
        bpl     L_833E                  ; 831E  non-negative - use it as it is
        eor     #$FF                    ; 8320  negate it ...
L_8322:
        clc                             ; 8322  ... clear carry ...
        adc     #$01                    ; 8323  ... +1 gives -viewOriginRow
        sta     D_82BB                  ; 8325  $82BB = the row shift
        clc                             ; 8328  clear carry
L_8329:
        lda     lineCurRow              ; 8329  shooter row
L_832B:
        adc     D_82BB                  ; 832B  shift it
        sta     lineCurRow              ; 832E  zp_92 updated
        clc                             ; 8330  clear carry
        lda     lineEndRow              ; 8331  target row
        adc     D_82BB                  ; 8333  shift it
        sta     lineEndRow              ; 8336  zp_94 updated
L_8338:
        clc                             ; 8338  clear carry
L_8339:
        lda     viewOriginRow           ; 8339  the origin itself
        adc     D_82BB                  ; 833B  shifted origin = 0
L_833E:
        sta     D_82B7                  ; 833E  $82B7 = top edge of the window
        clc                             ; 8341  clear carry
        adc     #$05                    ; 8342  the window is 5 cells high
        sta     D_82B8                  ; 8344  $82B8 = bottom bound (same +top skew as $82B6)

; ----------------------------------------------------------------------
; Is the shooter's cell inside the 7x5 window?
; ----------------------------------------------------------------------
        sec                             ; 8347  no borrow
        lda     lineCurCol              ; 8348  shooter column
        sbc     shotLineWorkVars        ; 834A  make it relative to the left edge
        bcc     testTargetEndpointInView; 834D  left of the window - not visible
        cmp     D_82B6                  ; 834F  compare against the right bound
        bcs     testTargetEndpointInView; 8352  right of the window - not visible
        sec                             ; 8354  no borrow
        lda     lineCurRow              ; 8355  shooter row
        sbc     D_82B7                  ; 8357  make it relative to the top edge
        bcc     testTargetEndpointInView; 835A  above the window - not visible
        cmp     D_82B8                  ; 835C  compare against the bottom bound
        bcs     testTargetEndpointInView; 835F  below the window - not visible
        dec     D_82B9                  ; 8361  flags = $FF: the shooter is in view

; ----------------------------------------------------------------------
; ... and is the target's cell inside it?
; ----------------------------------------------------------------------
testTargetEndpointInView:
        sec                             ; 8364  no borrow
        lda     lineEndCol              ; 8365  target column
        sbc     shotLineWorkVars        ; 8367  relative to the left edge
        bcc     L_8384                  ; 836A  outside
        cmp     D_82B6                  ; 836C  right bound
        bcs     L_8384                  ; 836F  outside
        sec                             ; 8371  no borrow
        lda     lineEndRow              ; 8372  target row
        sbc     D_82B7                  ; 8374  relative to the top edge
        bcc     L_8384                  ; 8377  outside
        cmp     D_82B8                  ; 8379  bottom bound
        bcs     L_8384                  ; 837C  outside
        inc     D_82B9                  ; 837E  flags += 1 ...
        inc     D_82B9                  ; 8381  ... twice, so $02 = only the target, $01 = both endpoints in view
L_8384:
        lda     D_82B9                  ; 8384  any endpoint in view?
        bne     L_838C                  ; 8387  yes - draw it
        jmp     L_840E                  ; 8389  neither - nothing to draw, and no redraw to schedule


; ----------------------------------------------------------------------
; Rebase the window origin and both endpoints so the smallest value is 0, then scale everything from
; map cells to fat pixels (8 per cell).  Rebasing is what stops the x8 from overflowing a byte.
; ----------------------------------------------------------------------
L_838C:
        jsr     rebaseLineCoordsToMin   ; 838C  subtract the minimum column and the minimum row from all three values
        lda     shotLineWorkVars        ; 838F  window left edge, in cells
        asl     a                       ; 8392  x2 ...
        asl     a                       ; 8393  x4 ...
        asl     a                       ; 8394  x8 = fat pixels per map cell
        sta     shotLineWorkVars        ; 8395  $82B5 now in fat pixels
L_8398:
        lda     D_82B7                  ; 8398  window top edge, in cells
        asl     a                       ; 839B  x2 ...
        asl     a                       ; 839C  x4 ...
        asl     a                       ; 839D  x8
        sta     D_82B7                  ; 839E  $82B7 now in fat pixels
        jsr     scaleLineCoordsToFatPixels; 83A1  scale zp_92-zp_95 the same way and add the half-cell offsets (+4 rows, +3 columns)
        jsr     initLineStepper         ; 83A4  set up the Bresenham walk from (zp_93,zp_92) to (zp_95,zp_94)
        lda     #$04                    ; 83A7  4 steps of muzzle gap ...
        sta     D_82BC                  ; 83A9  ... in $82BC

; ----------------------------------------------------------------------
; Walk the line one fat pixel step at a time.  A point is plotted only when it lies strictly inside
; the window - relative coordinate 0 is skipped, so the left column and top row are never drawn.
; ----------------------------------------------------------------------
L_83AC:
        sec                             ; 83AC  no borrow
        lda     lineCurCol              ; 83AD  current column of the walk, in fat pixels
        sbc     shotLineWorkVars        ; 83AF  relative to the window's left edge
        beq     L_83E9                  ; 83B2  exactly on the left edge - skip it
        bcc     L_83E9                  ; 83B4  left of the window - skip
        cmp     #$38                    ; 83B6  7 cells x 8 fat pixels = 56
        bcs     L_83E9                  ; 83B8  right of the window - skip
        clc                             ; 83BA  clear carry
        adc     #$64                    ; 83BB  character column 25 = fat pixel column 100
        tax                             ; 83BD  X = fat pixel column on screen
        sec                             ; 83BE  no borrow
        lda     lineCurRow              ; 83BF  current row of the walk
        sbc     D_82B7                  ; 83C1  relative to the window's top edge
        beq     L_83E9                  ; 83C4  exactly on the top edge - skip
        bcc     L_83E9                  ; 83C6  above the window - skip
        cmp     #$28                    ; 83C8  5 cells x 8 fat pixels = 40
        bcs     L_83E9                  ; 83CA  below the window - skip
        clc                             ; 83CC  clear carry
        adc     #$04                    ; 83CD  character row 1 = fat pixel row 4
        tay                             ; 83CF  Y = fat pixel row on screen
        lda     D_82B9                  ; 83D0  endpoint visibility flags
        cmp     #$02                    ; 83D3  only the target is in view?
        beq     L_83E4                  ; 83D5  then the shooter is off screen and the whole visible line is solid
        lda     D_82BC                  ; 83D7  muzzle gap countdown
        bmi     L_83E4                  ; 83DA  already negative - past the gap, plot the line
        bne     L_83E9                  ; 83DC  still positive - inside the 4-step gap, plot nothing
        jsr     plotDiamondMarker       ; 83DE  exactly 0 - this is the muzzle, draw the little diamond
        jmp     L_83E9                  ; 83E1  and step on

L_83E4:
        lda     #$01                    ; 83E4  colour 1 = white
        jsr     plotFatPixel            ; 83E6  plot one fat multicolour pixel at (X,Y)

; ----------------------------------------------------------------------
; Advance the walk.  A BOOMER's shot ($923D = 2) takes three steps for every plotted pixel, so its
; tracer comes out dashed.
; ----------------------------------------------------------------------
L_83E9:
        dec     D_82BC                  ; 83E9  one step of the gap countdown used up
        lda     lineStepsLeft           ; 83EC  steps left on the line
        beq     L_8409                  ; 83EE  line finished
        jsr     stepLine                ; 83F0  advance zp_92/zp_93 by one cell along the line
        lda     attackerType            ; 83F3  $923D = type of the shooting unit
        cmp     #$02                    ; 83F6  2 = BOOMER
        bne     L_8406                  ; 83F8  anything else plots every step
        dec     D_82BC                  ; 83FA  BOOMER: burn a second gap count ...
        jsr     stepLine                ; 83FD  ... and a second step ...
        dec     D_82BC                  ; 8400  ... and a third gap count ...
        jsr     stepLine                ; 8403  ... and a third step, leaving gaps in the tracer
L_8406:
        jmp     L_83AC                  ; 8406  back for the next point

L_8409:
        lda     #$8F                    ; 8409  $8F = redraw pending, 15 frames of countdown (the IRQ stops at $80)
        sta     viewRedrawTimer         ; 840B  $9234: the view is repainted 15 frames from now, erasing the line
L_840E:
        rts                             ; 840E  done (also the early exit when neither endpoint is visible)


; ----------------------------------------------------------------------
; rebaseLineCoordsToMin - Subtracts the smallest of the three column values (window left $82B5,
; shooter zp_93, target zp_95) from all three, and likewise the smallest of the three row values
; ($82B7, zp_92, zp_94), so the smallest becomes 0.  That is what keeps the following multiply by 8
; inside a byte.
; In:  $82B5, $82B7, zp_92-zp_95
; Out: the same six values, rebased
; Called from: $838C (drawShotLineInViewport)
; ----------------------------------------------------------------------
rebaseLineCoordsToMin:
        lda     shotLineWorkVars        ; 840F  window left edge
        cmp     lineCurCol              ; 8412  compare with the shooter column
        bcs     L_841A                  ; 8414  left >= shooter, so the minimum is one of the two endpoints
        cmp     lineEndCol              ; 8416  left < shooter: compare with the target column
        bcc     L_844C                  ; 8418  left is smaller than both - it is the minimum
L_841A:
        lda     lineCurCol              ; 841A  shooter column
        cmp     lineEndCol              ; 841C  compare with the target column
        bcc     L_8436                  ; 841E  shooter is the minimum
        sec                             ; 8420  no borrow
        lda     shotLineWorkVars        ; 8421  target column is the minimum: rebase the window edge ...
        sbc     lineEndCol              ; 8424  ... by subtracting it ...
        sta     shotLineWorkVars        ; 8426  ... and store it back
        sec                             ; 8429  no borrow
        lda     lineCurCol              ; 842A  rebase the shooter column ...
        sbc     lineEndCol              ; 842C  ... the same way ...
        sta     lineCurCol              ; 842E  ... and store it
        lda     #$00                    ; 8430  the minimum itself becomes 0
L_8432:
        sta     lineEndCol              ; 8432  zp_95 = 0
        beq     L_8461                  ; 8434  always taken - now do the rows
L_8436:
        sec                             ; 8436  no borrow
        lda     shotLineWorkVars        ; 8437  shooter column is the minimum: rebase the window edge
        sbc     lineCurCol              ; 843A  subtract it
        sta     shotLineWorkVars        ; 843C  store it back
        sec                             ; 843F  no borrow
        lda     lineEndCol              ; 8440  rebase the target column
        sbc     lineCurCol              ; 8442  subtract it
        sta     lineEndCol              ; 8444  store it back
        lda     #$00                    ; 8446  the minimum becomes 0
        sta     lineCurCol              ; 8448  zp_93 = 0
        beq     L_8461                  ; 844A  always taken - now do the rows
L_844C:
        sec                             ; 844C  no borrow
        lda     lineCurCol              ; 844D  window edge is the minimum: rebase the shooter column
D_844F:
        sbc     shotLineWorkVars        ; 844F  subtract it
        sta     lineCurCol              ; 8452  store it back
        sec                             ; 8454  no borrow
        lda     lineEndCol              ; 8455  rebase the target column
        sbc     shotLineWorkVars        ; 8457  subtract it
        sta     lineEndCol              ; 845A  store it back
L_845C:
        lda     #$00                    ; 845C  the minimum becomes 0
        sta     shotLineWorkVars        ; 845E  $82B5 = 0

; ----------------------------------------------------------------------
; Exactly the same three-way minimum for the rows.
; ----------------------------------------------------------------------
L_8461:
        lda     D_82B7                  ; 8461  window top edge
        cmp     lineCurRow              ; 8464  compare with the shooter row
        bcs     L_846C                  ; 8466  top >= shooter, so an endpoint holds the minimum
        cmp     lineEndRow              ; 8468  compare with the target row
D_846A:
        bcc     L_849C                  ; 846A  the top edge is smaller than both
L_846C:
        lda     lineCurRow              ; 846C  shooter row
        cmp     lineEndRow              ; 846E  compare with the target row
        bcc     L_8487                  ; 8470  shooter row is the minimum
        sec                             ; 8472  no borrow
        lda     D_82B7                  ; 8473  target row is the minimum: rebase the top edge
        sbc     lineEndRow              ; 8476  subtract it
        sta     D_82B7                  ; 8478  store it back
        sec                             ; 847B  no borrow
        lda     lineCurRow              ; 847C  rebase the shooter row
L_847E:
        sbc     lineEndRow              ; 847E  subtract it
        sta     lineCurRow              ; 8480  store it back
        lda     #$00                    ; 8482  the minimum becomes 0
        sta     lineEndRow              ; 8484  zp_94 = 0
        rts                             ; 8486  done

L_8487:
        sec                             ; 8487  no borrow
        lda     D_82B7                  ; 8488  shooter row is the minimum: rebase the top edge
        sbc     lineCurRow              ; 848B  subtract it
        sta     D_82B7                  ; 848D  store it back
        sec                             ; 8490  no borrow
        lda     lineEndRow              ; 8491  rebase the target row
D_8493:
        sbc     lineCurRow              ; 8493  subtract it
        sta     lineEndRow              ; 8495  store it back
        lda     #$00                    ; 8497  the minimum becomes 0
        sta     lineCurRow              ; 8499  zp_92 = 0
        rts                             ; 849B  done

L_849C:
        sec                             ; 849C  no borrow
        lda     lineCurRow              ; 849D  top edge is the minimum: rebase the shooter row
        sbc     D_82B7                  ; 849F  subtract it
        sta     lineCurRow              ; 84A2  store it back
L_84A4:
        sec                             ; 84A4  no borrow
        lda     lineEndRow              ; 84A5  rebase the target row
        sbc     D_82B7                  ; 84A7  subtract it
L_84AA:
        sta     lineEndRow              ; 84AA  store it back
        lda     #$00                    ; 84AC  the minimum becomes 0
        sta     D_82B7                  ; 84AE  $82B7 = 0
        rts                             ; 84B1  done


; ----------------------------------------------------------------------
; scaleLineCoordsToFatPixels - Converts the two line endpoints from map cells to fat pixels: row*8+4
; and column*8+3, i.e. the middle of the cell in the 8x8-fat-pixel grid the viewport uses.  Three of
; the four cases saturate to $FF on overflow; the zp_92 (shooter row) case is missing its LDA #$FF, so
; it keeps the wrapped value instead - harmless because rebaseLineCoordsToMin has already brought all
; four values below 32 cells in practice.
; In:  zp_92-zp_95 in map cells
; Out: zp_92-zp_95 in fat pixels
; Called from: $83A1 (drawShotLineInViewport)
; ----------------------------------------------------------------------
scaleLineCoordsToFatPixels:
        lda     lineEndRow              ; 84B2  target row, in cells
        asl     a                       ; 84B4  x2 ...
        asl     a                       ; 84B5  x4 ...
        asl     a                       ; 84B6  x8 fat pixels per cell
        bcc     L_84BD                  ; 84B7  no overflow - add the half-cell offset
        lda     #$FF                    ; 84B9  overflow - clamp to the last fat pixel row
        bmi     L_84C0                  ; 84BB  always taken ($FF is negative)
L_84BD:
        clc                             ; 84BD  clear carry
        adc     #$04                    ; 84BE  +4 = the vertical centre of the cell
L_84C0:
        sta     lineEndRow              ; 84C0  zp_94
        lda     lineEndCol              ; 84C2  target column, in cells
        asl     a                       ; 84C4  x2 ...
        asl     a                       ; 84C5  x4 ...
        asl     a                       ; 84C6  x8
L_84C7:
        bcc     L_84CD                  ; 84C7  no overflow
        lda     #$FF                    ; 84C9  clamp
        bmi     L_84D0                  ; 84CB  always taken
L_84CD:
        clc                             ; 84CD  clear carry
        adc     #$03                    ; 84CE  +3 = the horizontal centre of the cell
L_84D0:
        sta     lineEndCol              ; 84D0  zp_95
        lda     lineCurRow              ; 84D2  shooter row, in cells
        asl     a                       ; 84D4  x2 ...
        asl     a                       ; 84D5  x4 ...
        asl     a                       ; 84D6  x8
        bcc     L_84DB                  ; 84D7  no overflow - add the offset
        bmi     L_84DE                  ; 84D9  overflowed: the LDA #$FF of the other three cases is missing here, so the wrapped value is kept
L_84DB:
        clc                             ; 84DB  clear carry
        adc     #$04                    ; 84DC  +4 = the vertical centre of the cell
L_84DE:
        sta     lineCurRow              ; 84DE  zp_92
        lda     lineCurCol              ; 84E0  shooter column, in cells
        asl     a                       ; 84E2  x2 ...
        asl     a                       ; 84E3  x4 ...
        asl     a                       ; 84E4  x8
        bcc     L_84EB                  ; 84E5  no overflow
        lda     #$FF                    ; 84E7  clamp
        bmi     L_84EE                  ; 84E9  always taken
L_84EB:
        clc                             ; 84EB  clear carry
scaleColAddHalfCell:
        adc     #$03                    ; 84EC  +3 = the horizontal centre of the cell
L_84EE:
        sta     lineCurCol              ; 84EE  zp_93
        rts                             ; 84F0  done


; ----------------------------------------------------------------------
; plotDiamondMarker - Plots the four colour-1 fat pixels at (X-1,Y-1), (X+1,Y-1), (X-1,Y+1) and
; (X+1,Y+1): the small diamond drawn at the muzzle end of a shot tracer.  X and Y are left exactly as
; they were on entry.
; In:  X = fat pixel column, Y = fat pixel row
; Out: four bitmap pixels set; X and Y preserved
; Called from: $83DE (drawShotLineInViewport, on the fifth step out of the muzzle)
; ----------------------------------------------------------------------
plotDiamondMarker:
        dey                             ; 84F1  one fat pixel up
        dex                             ; 84F2  one fat pixel left
        lda     #$01                    ; 84F3  colour 1 = white
        jsr     plotFatPixel            ; 84F5  top-left corner of the diamond
        inx                             ; 84F8  two to the right ...
        inx                             ; 84F9  ... = one right of the centre
        lda     #$01                    ; 84FA  colour 1
        jsr     plotFatPixel            ; 84FC  top-right corner
        dex                             ; 84FF  two to the left ...
        dex                             ; 8500  ... back to one left of the centre
        iny                             ; 8501  two down ...
        iny                             ; 8502  ... = one below the centre
        lda     #$01                    ; 8503  colour 1
        jsr     plotFatPixel            ; 8505  bottom-left corner
        inx                             ; 8508  two to the right ...
        inx                             ; 8509  ... = one right of the centre
        lda     #$01                    ; 850A  colour 1
        jsr     plotFatPixel            ; 850C  bottom-right corner
        dex                             ; 850F  back to the entry column ...
        dey                             ; 8510  ... and the entry row
plotDiamondDone:
        rts                             ; 8511  done, X and Y unchanged


; comSpeedDisplayTable - the COMSPEED figure shown on the STATS tab, indexed by the comcen speed class
; 0-5 held in $92B4 (us) and $92B5 (them).  Class 4 is the immobile comcen and prints 0.
comSpeedDisplayTable:
        .byte   $01,$02,$02,$02,$00,$03 ; 8512  ......  class 0 -> 1, class 1-3 -> 2, class 4 -> 0 (immobile), class 5 -> 3

; ----------------------------------------------------------------------
; drawStatsTab - Fills in the numbers of the console's STATS panel.  With the text window left edge at
; column 10 it prints two columns - US at column 9 (relative), THEM at column 14 - of: living GRUNTS,
; RIDERS, BOOMERS and SPIES (rows 9-12, counted by scanning the 50 unit slots downwards from each
; side's comcen), DRONES (row 14), MISSILES (row 15), COMSPEED (row 17), TER PTS (row 18), RECYCLER
; (row 19) and the five-character map name (row 20).  Clearing exchangeAppliedFlag $920E marks the
; panel as refreshed for this turn.
; In:  comcenUnit $9236, unitTypeTable $F76C, $92AE/$92AF drones, $92B0/$92B1 missiles, $92B4/$92B5
;      comcen speed, $92AC terrain points, $92A6 recycler mode, playerSide $0B9F, map name $0BA9
; Out: the STATS text drawn; $920E = 0; zp_18, zp_1A, zp_20, zp_21, zp_3B, zp_3D, zp_3F, zp_40 used
; Called from: $1460 and $14D3 in the main program (console tab drawing and refresh)
; ----------------------------------------------------------------------
drawStatsTab:
        lda     #$0A                    ; 8518  text window starts at screen column 10
        sta     textWindowLeft          ; 851A  zp_3B, added to every text column below
        lda     #$00                    ; 851C  0 = refreshed
        sta     exchangeAppliedFlag     ; 851E  $920E: consume the 'a turn was exchanged, redraw the stats' request
        lda     #$61                    ; 8521  colour byte $61: %01 pixels blue, %10 pixels white
        sta     textColour              ; 8523  zp_3D = colour written with every printed character
        lda     #$09                    ; 8525  first data row
        sta     textCursorRow           ; 8527  zp_40 = text cursor row 9 (GRUNTS)
        lda     #$00                    ; 8529  unit type 0 = GRUNT
        sta     scratch20               ; 852B  zp_20 = the type being counted

; ----------------------------------------------------------------------
; Outer loop over the four fighting unit types (0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY); one text row each.
; Inner pass counts our side first (column 9) and then the opponent's (column 14).
; ----------------------------------------------------------------------
L_852D:
        ldx     #$01                    ; 852D  one more column to print after this one
        stx     scratch21               ; 852F  zp_21 = columns still to do
        lda     #$09                    ; 8531  column 9 = the US column
        ldy     comcenUnit              ; 8533  start the scan at our own comcen (49 or 99)
L_8536:
        jsr     setTextColumn           ; 8536  position the text cursor at that column
        lda     #$00                    ; 8539  count = 0
        sta     scratch18               ; 853B  zp_18 = the running count
        lda     #$32                    ; 853D  50 unit slots per side
        sta     scratch1A               ; 853F  zp_1A = slots still to scan
L_8541:
        lda     unitTypeTable,y         ; 8541  type/flags byte of this unit ($F76C + unit)
        bmi     L_854E                  ; 8544  bit 7 set = the unit is dead, skip it
        and     #$07                    ; 8546  type is in bits 0-2 (0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN)
        cmp     scratch20               ; 8548  is it the type being counted?
        bne     L_854E                  ; 854A  no - next slot
        inc     scratch18               ; 854C  yes - one more of this type alive
L_854E:
        dey                             ; 854E  next unit index downwards (comcen is the highest of its side)
        dec     scratch1A               ; 854F  one slot fewer
        bne     L_8541                  ; 8551  50 slots not done yet
        ldx     textCursorCol           ; 8553  X = the column just set
        lda     scratch18               ; 8555  the count
        jsr     printNumberRightAligned ; 8557  print it right-aligned in a 3-character field
        dec     scratch21               ; 855A  one column done
L_855C:
        bmi     L_8568                  ; 855C  both columns done - next unit type
        lda     comcenUnit              ; 855E  own comcen index
        eor     #$52                    ; 8561  EOR $52 swaps 49 <-> 99, i.e. the other side's comcen
        tay                             ; 8563  scan starts there
        lda     #$0E                    ; 8564  column 14 = the THEM column
        bne     L_8536                  ; 8566  always taken - print the second column
L_8568:
        inc     textCursorRow           ; 8568  next text row
        inc     scratch20               ; 856A  next unit type
        lda     scratch20               ; 856C  which type is next?
        cmp     #$04                    ; 856E  types 0-3 only; type 4 (COMCEN) is not listed
        bcc     L_852D                  ; 8570  more types to do

; ----------------------------------------------------------------------
; Fixed rows below the unit table: DRONES (14), MISSILES (15), COMSPEED (17), TER PTS (18), RECYCLER
; (19) and the map name (20).
; ----------------------------------------------------------------------
        inc     textCursorRow           ; 8572  skip a row - DRONES is on row 14
        ldx     #$09                    ; 8574  US column
        lda     dronesLeft              ; 8576  $92AE = our drones left
        jsr     printNumberRightAligned ; 8579  print it
        ldx     #$0E                    ; 857C  THEM column
        lda     opponentDronesLeft      ; 857E  $92AF = the opponent's drones left
        jsr     printNumberRightAligned ; 8581  print it
        inc     textCursorRow           ; 8584  row 15 = MISSILES
        ldx     #$09                    ; 8586  US column
        lda     missilesLeft            ; 8588  $92B0 = our missiles left
        jsr     printNumberRightAligned ; 858B  print it
        ldx     #$0E                    ; 858E  THEM column
        lda     opponentMissilesLeft    ; 8590  $92B1 = the opponent's missiles left
        jsr     printNumberRightAligned ; 8593  print it
        inc     textCursorRow           ; 8596  row 16 ...
        inc     textCursorRow           ; 8598  ... row 17 = COMSPEED
        ldx     #$09                    ; 859A  US column
        ldy     comcenSpeedClass        ; 859C  $92B4 = our comcen speed class 0-5
        lda     comSpeedDisplayTable,y  ; 859F  translate it into the printed figure
        jsr     printNumberRightAligned ; 85A2  print it
        ldx     #$0E                    ; 85A5  THEM column
        ldy     opponentComcenSpeedClass; 85A7  $92B5 = the opponent's speed class
        lda     comSpeedDisplayTable,y  ; 85AA  translate it
L_85AD:
        jsr     printNumberRightAligned ; 85AD  print it
        inc     textCursorRow           ; 85B0  row 18 = TER PTS
        ldx     #$09                    ; 85B2  US column
        lda     playerSide              ; 85B4  $0B9F = which side we are
        eor     #$01                    ; 85B7  the OTHER side's index - note this is the opposite indexing to the RECYCLER row below, so one of the two rows is probably swapped
L_85B9:
        tay                             ; 85B9  index the per-side table
        lda     terrainPointsBySide,y   ; 85BA  $92AC[side EOR 1]
        jsr     printNumberRightAligned ; 85BD  print it
        ldx     #$0E                    ; 85C0  THEM column
        ldy     playerSide              ; 85C2  our own side index
        lda     terrainPointsBySide,y   ; 85C5  $92AC[side]
        jsr     printNumberRightAligned ; 85C8  print it
        inc     textCursorRow           ; 85CB  row 19 = RECYCLER
        lda     #$09                    ; 85CD  US column
        jsr     setTextColumn           ; 85CF  position the cursor there
        ldy     playerSide              ; 85D2  our side
        lda     recyclerMode,y          ; 85D5  $92A6[side] = FULL / HALF / NONE
        jsr     printOptionWord         ; 85D8  print the 4-character option word
statsPrintThemRecycler:
        lda     #$0E                    ; 85DB  THEM column
        jsr     setTextColumn           ; 85DD  position the cursor there
        lda     playerSide              ; 85E0  our side ...
        eor     #$01                    ; 85E3  ... EOR 1 = the opponent
        tay                             ; 85E5  index the table
        lda     recyclerMode,y          ; 85E6  $92A6[side EOR 1]
        jsr     printOptionWord         ; 85E9  print the option word
D_85EC:
        inc     textCursorRow           ; 85EC  row 20 = the map name
        lda     #$09                    ; 85EE  column 9 for side 0 ...
        ldy     playerSide              ; 85F0  which side are we?
        beq     L_85F7                  ; 85F3  side 0 - use column 9
        lda     #$0E                    ; 85F5  side 1 - the name is printed under the THEM column instead
L_85F7:
        jsr     setTextColumn           ; 85F7  position the cursor
        lda     #$A9                    ; 85FA  $0BA9 = the five-character map name, terminated at $0BAE
        ldy     #$0B                    ; 85FC  high byte of the string address
        jmp     printString             ; 85FE  print it and return through the text engine


; ----------------------------------------------------------------------
; printNumberRightAligned - prints the unsigned byte in A as decimal, right-aligned in a
; three-character field that starts at text column X of the current text row.  It works out how many
; blank glyphs have to go in front (2 for 0-9, 1 for 10-99, none for 100-255), patches that count and
; the value into three immediate operands further down ($8622, $8627, $8629), positions the text
; cursor and then prints padding + number through the resident $C000 text engine. Only the low byte is
; ever printed: the LDY #$00 at the entry wipes whatever the caller left in Y, which makes the CPY
; #$00 at $8607 and the whole $8626/$8627 patch vestiges of an earlier 16-bit version of this routine
; (see the notes on those lines).
; In:  A = value 0-255, X = column relative to textWindowLeft (zp_3B).  Also uses zp_40 (text row),
;     zp_3D (colour), zp_3E (fill glyph, 0 = blank) and needs decimalValueHigh zp_7E to be 0 - it
;     always is, because convertToDecimalDigits clears it at $C1DD after every conversion.
; Out: up to 3 cells printed and the cursor left just past the number; zp_3F = X; the three immediate
;     operands at $8622/$8627/$8629 patched; A, X, Y and zp_18-zp_1F clobbered (printDecimal uses
;     them).
; Called from: drawStatsTab only - $8557 (the GRUNTS/RIDERS/BOOMERS/SPIES counts, both columns),
;     $8579/$8581 (DRONES), $858B/$8593 (MISSILES), $85A2/$85AD (COMSPEED), $85BD/$85C8 (TER PTS).
;     XREF also lists the same call sites under game/mapGenerator6F00; that is a label-merge artifact
;     - variant A has completely different code at $8557 and at $8601 (bytes 'a9 fe 8d f8 90' there).
; Number printer for the console STATS tab.  Right alignment is done the cheap way: count the digits
; first, print that many blanks, then let printDecimal ($C17D) emit the digits with no leading zeros.
; The value and the pad count are carried to the print calls in self-modified immediate operands
; rather than on the stack, because updateCursorPointers ($C099) preserves only X and Y, not A.
; ----------------------------------------------------------------------
printNumberRightAligned:
        ldy     #$00                    ; 8601  high byte of the number to print: always 0 here - the STATS fields are all 8-bit counts
        stx     textCursorCol           ; 8603  X held the target column (relative to textWindowLeft zp_3B); make it the text cursor column zp_3F
        ldx     #$00                    ; 8605  X now counts the blank glyphs to print in front of the number; start by assuming none
        cpy     #$00                    ; 8607  dead test: would leave the pad count at 0 when a 16-bit caller passed a non-zero high byte in Y, but $8601 has just forced Y = 0
        bne     emitPaddedNumber        ; 8609  branch never taken (Y is always 0) - leftover of the 16-bit version of this routine

; ----------------------------------------------------------------------
; Pad count: how many blanks are needed so that the digits end at the right edge of the
; three-character field.  X = 3 - number of decimal digits. In variant B this is just an internal
; label; the JSRs to $860B in XREF come from variant A, where a different routine (its OFF/ON option
; editor) starts on this byte.
; ----------------------------------------------------------------------
countNumberPadChars:
        cmp     #$64                    ; 860B  is the value 100 or more, i.e. does it already fill all three columns?
        bcs     emitPaddedNumber        ; 860D  100-255: three digits, no padding at all
        inx                             ; 860F  under 100: at least one blank in front
        cmp     #$0A                    ; 8610  is the value 10 or more?
        bcs     emitPaddedNumber        ; 8612  10-99: two digits, so the one blank counted above is enough
        inx                             ; 8614  0-9: a single digit, so two blanks in front

; ----------------------------------------------------------------------
; Self-modifying code.  The three immediates below are written here on every call:
;   $8622 (operand of the LDA at $8621) = number of blanks to print
;   $8627 (operand of the LDY at $8626) = middle byte handed to printDecimal - always 0
;   $8629 (operand of the LDA at $8628) = the value to print
; Nothing else in either overlay writes these three bytes.
; ----------------------------------------------------------------------
emitPaddedNumber:
        stx     padCountPatch+1         ; 8615  patch the blank count into the LDA #imm at $8621 that feeds printFillChars
        sty     valueMidPatch+1         ; 8618  patch the (always zero) middle byte into the LDY #imm at $8626 that feeds printDecimal
        sta     valueLowPatch+1         ; 861B  patch the value itself into the LDA #imm at $8628
        jsr     updateCursorPointers    ; 861E  $C099: absolute column = zp_3F + textWindowLeft zp_3B, row = zp_40; sets the bitmap pointer zp_B4/B5 and the screen pointer zp_B6/B7.  It preserves X and Y but destroys A - which is why the value had to be patched in above
padCountPatch:
        lda     #$FF                    ; 8621  operand $8622 patched at $8615: 0, 1 or 2 leading blanks
        jsr     bootReceiveSectorData   ; 8623  printFillChars at $C0EF of the resident $C000 text engine (the listing shows the boot loader's name for the same address): prints the fill glyph zp_3E - normally 0, the blank - A times, advancing the cursor one cell each time  (also textEngineC000:printFillChars)
valueMidPatch:
        ldy     #$FF                    ; 8626  operand $8627 patched at $8618: middle byte of printDecimal's 24-bit value, always 0
valueLowPatch:
        lda     #$FF                    ; 8628  operand $8629 patched at $861B: low byte = the number to print
        jmp     printDecimal            ; 862A  tail-call printDecimal $C17D (A = low, Y = middle, zp_7E = high): converts by repeated division by 10 and prints the digits without leading zeros, then returns to drawStatsTab


; ----------------------------------------------------------------------
; setTextColumn - moves the text cursor to column A of the current row and recomputes the bitmap and
; screen-RAM pointers for that character cell by tail-calling updateCursorPointers ($C099), which
; leaves X and Y untouched.
; In:  A = column relative to textWindowLeft (zp_3B; 10 while the STATS tab is being drawn), zp_40 =
;     text row.
; Out: zp_3F = A, zp_B4/B5 = bitmap address of the cell, zp_B6/B7 = screen RAM address; A clobbered, X
;     and Y preserved.
; Called from: drawStatsTab only - $8536 (the US/THEM column of each unit-count row), $85CF and $85DD
;     (the two RECYCLER words) and $85F7 (the MAP ID column).  The game/mapGenerator6F00 callers in
;     XREF are label-merge artifacts; variant A holds unrelated code at $862D.
; Two-instruction helper used wherever the STATS tab jumps to a fixed column without printing a
; number: column 9 is the US column, column 14 the THEM column (both relative to textWindowLeft 10).
; ----------------------------------------------------------------------
setTextColumn:
        sta     textCursorCol           ; 862D  new cursor column, counted from the left edge of the text window (zp_3B)
        jmp     updateCursorPointers    ; 862F  $C099 recomputes zp_B4/B5 and zp_B6/B7 for cell (zp_3B + zp_3F, zp_40) and returns straight to the caller


; ----------------------------------------------------------------------
; drawComcenSchematic - unpacks the RLE-compressed comcen picture at comcenSchematicRle ($7C6E)
; straight into the hires bitmap for the console REPAIR tab: a 20x10 character (160x80 pixel) side
; view of the tracked comcen vehicle with its dish and antenna, drawn at character row 10, column 13.
; Stream format (the same one unpackRleTo0200 $67CB uses): byte 0 is the escape marker ($13 here);
; afterwards any byte other than the marker is a literal, and 'marker, count, value' emits value count
; times, with count 0 meaning 256.  Runs are allowed to cross the 160-byte row boundary, which is why
; the run value is stacked over the pointer advance at $8678/$8686.
; In:  the compressed stream at $7C6E-$7FCD.  No registers.
; Out: 10 x 160 bytes written into the bitmap from $A000 + 10*320 + 13*8 = $ACE8 with a 320-byte row
;     stride; rleMarkerByte $9246 = $13, rleUnpackFlag $9247 = 0, fetchRleByte's self-modified operand
;     $681B/$681C left just past the end of the stream; zp_1A (row counter), zp_1B (run count),
;     zp_B4/B5 (bitmap pointer) and zp_B6/B7 (set but unused) clobbered.
; Called from: drawRepairTab ($176B) in the main program, its only caller.  Since the routine lives in
;     overlay B, the console REPAIR tab implicitly requires variant B to be resident.
; Set up the decoder: escape marker, source pointer and destination cell.  Note that this routine does
; not use the general unpacker at $67CB - that one always writes 512 bytes to $0200 - but it does
; share its byte fetcher (fetchRleByte $681A) and its marker byte ($9246).
; ----------------------------------------------------------------------
drawComcenSchematic:
        ldy     #$00                    ; 8632  index 0 of the stream, and the zero about to be stored in $9247
        sty     rleUnpackFlag           ; 8634  $9247 = 0, exactly as unpackRleTo0200 does at $67D3 - but no code in any unit ever reads this byte, so it is a dead leftover
        lda     comcenSchematicRle,y    ; 8637  byte 0 of the compressed stream is the escape marker ($13 for this picture)
        sta     rleMarkerByte           ; 863A  keep the marker in $9246 for the compare at $865E (shared with the main program's unpacker)
        ldy     #$7C                    ; 863D  high byte of the source address...
        sty     D_681C                  ; 863F  ...poked into the high half of fetchRleByte's self-modified 'lda $XXXX' operand at $681B/$681C
        lda     #$6F                    ; 8642  low byte: decoding starts at $7C6F, one past the marker byte
        sta     D_681B                  ; 8644  complete the patched source pointer = $7C6F
        ldx     #$0D                    ; 8647  left edge of the picture: character column 13, just right of the 22-column map panel
        ldy     #$0A                    ; 8649  top edge: character row 10, under the REPAIR tab heading
        jsr     calcCellPointersXY      ; 864B  $2AA3: zp_B4/B5 = $A000 + row*320 + col*8 (bitmap cell), zp_B6/B7 = $8C00 + row*40 + col (screen RAM cell, not used here)
        lda     #$0A                    ; 864E  the picture is 10 character rows tall
        sta     scratch1A               ; 8650  zp_1A counts the rows still to emit
        ldy     #$00                    ; 8652  Y = byte offset inside the current character row, 0..159

; ----------------------------------------------------------------------
; Decoder loop.  X is the number of copies of A still to store: it is decremented on entry to each
; pass, and only when it reaches 0 is a new item pulled from the stream.  Y walks the 160 bytes of one
; picture row (20 cells x 8 scanlines, stored cell by cell), zp_1A counts the rows. primeRleRunCounter
; is entered once, by falling through from $8652; the per-row restart at $8689 jumps back to
; rleEmitLoop with X and A intact.  It is a branch target only in variant A.
; ----------------------------------------------------------------------
primeRleRunCounter:
        ldx     #$01                    ; 8654  prime the run counter with 1 so that the very first pass fetches from the stream
rleEmitLoop:
        dex                             ; 8656  one byte of the current run consumed
        bne     rleStoreByte            ; 8657  run not exhausted: store the same value again without touching the stream
        jsr     fetchRleByte            ; 8659  $681A: fetch the next stream byte into A (and X) and bump the source pointer
        ldx     #$01                    ; 865C  assume a literal byte: exactly one store
        cmp     rleMarkerByte           ; 865E  is this byte the escape marker that introduces a run?
        bne     rleStoreByte            ; 8661  not the marker: plain literal, store it once
        jsr     fetchRleByte            ; 8663  marker seen - the next byte is the repeat count (0 counts as 256)
rleSaveRunCount:
        sta     scratch1B               ; 8666  park the repeat count while the value byte is fetched (this label is a branch target only in variant A)
        jsr     fetchRleByte            ; 8668  third byte of the run: the value to repeat
        ldx     scratch1B               ; 866B  X = repeat count; this pass stores the value once and X-1 further passes repeat it
rleStoreByte:
        sta     (bitmapPtr),y           ; 866D  write one bitmap byte; eight consecutive bytes are the eight scanlines of one character cell
        iny                             ; 866F  next byte of the picture row
        cpy     #$A0                    ; 8670  160 bytes = 20 character cells x 8 scanlines = one full picture row
        bcc     rleEmitLoop             ; 8672  row not finished yet, keep decoding

; ----------------------------------------------------------------------
; End of a picture row: step the bitmap pointer down one character row.  A (the current run value) and
; X (the remaining repeat count) are carried across untouched, so a run may span rows.
; ----------------------------------------------------------------------
        dec     scratch1A               ; 8674  one of the 10 picture rows done
        beq     schematicDone           ; 8676  all 10 rows drawn - the picture is complete
        pha                             ; 8678  save the current run value across the pointer arithmetic
        clc                             ; 8679  clear carry for the 16-bit add below
        lda     bitmapPtr               ; 867A  advance the bitmap pointer...
        adc     #$40                    ; 867C  ...by $0140 = 320 bytes = one character row of the 40-column bitmap ($40 into the low byte...
        sta     bitmapPtr               ; 867E  ...store the new low byte
        lda     bitmapPtrHi             ; 8680  ...carry into the high byte...
        adc     #$01                    ; 8682  ...plus 1, completing the +$140
        sta     bitmapPtrHi             ; 8684  zp_B4/B5 now points at the leftmost cell of the next picture row (still column 13)
        pla                             ; 8686  restore the run value so a run that crossed the row boundary keeps storing the same byte
        ldy     #$00                    ; 8687  back to the left edge of the picture row
        beq     rleEmitLoop             ; 8689  always taken (Y was just loaded with 0): continue decoding into the new row
schematicDone:
        rts                             ; 868B  return to drawRepairTab, which then paints the colour cells over the picture


; ================================================================== From here to the end of the
; overlay ($868C-$87FE) variant B contains no code at all: five user- interface captions, the 79-byte
; STATS tab label blob, a 12-byte word table for the film status line and 16 bit-7-terminated message
; strings, then six padding zeroes and the variant flag. Every routine-looking label inside this area
; belongs to variant A, which keeps its custom- scenario dialog code at $868C-$879E - XREF merges both
; variants into one namespace, so names such as setDestroyModeMessage ($86AA),
; showCustomSummaryMessage ($86C3), setPersistentMessage ($8722), appendOnOffWord ($872E),
; showScenarioBannerAndWaitForButton ($8756) and printCentredTableEntry ($8795), as well as the D_/L_
; labels sprinkled through the strings, mean nothing here and only mark where variant A branches or
; stores.  The only two interior addresses that matter with variant B resident are $8726 and $8730,
; both patched by the film replay key handler at $40B2. A byte compare of extracted/runtimeImage_A.bin
; and _B.bin shows $868C-$879E differing,
; $879F-$87FE identical in both variants and $87FF differing ($00 in A, $FF in B).
; ==================================================================
; 
; strDrone - 'DRONE', bit 7 on the final E.  Caption of the drones-left box on the DRONE screen,
; printed by overlay B code at $7018-$7033 with textColour $CF at column 32, row 7.
strDrone:
        .byte   "D"                     ; 868C  'D' of DRONE
D_868D:
        .byte   $52                     ; 868D  'R' - label from variant A, which stores here at $8213/$86AC
L_868E:
        .byte   $4F                     ; 868E  'ONE' with bit 7 on the E as the string terminator
        .byte   $4E,$C5                 ; 868F  N.

; strLaunch - 'LAUNCH', caption under the 2x2 LAUNCH button icon of the DRONE screen; printed at
; $70B6-$70C5 at column 32, row 14.
strLaunch:
        .byte   "LAUNC",'H'|$80         ; 8691  6 bytes, terminated by 'H' | $80

; strStrike - 'STRIKE', caption under the 2x2 STRIKE button icon of the DRONE screen; printed by the
; code at $70D9-$70EF with textColour $C7 at column 32, row 18.
strStrike:
        .byte   "STR"                   ; 8697  'STR'
D_869A:
        .byte   $49                     ; 869A  'I' - variant A writes here ($8221/$86BA); no meaning in variant B
D_869B:
        .byte   $4B                     ; 869B  'KE' with bit 7 on the E
        .byte   $C5                     ; 869C  .

; strFire - 'FIRE', caption of the missile FIRE button on the main game screen; printed at
; $7B2E-$7B3D at column 31, row 12.
strFire:
        .byte   "FIR",'E'|$80           ; 869D  4 bytes, terminated by 'E' | $80

; strDroneAlertTwoLine - 'DRONE' + CR + 'ALERT': the two-line label of the top-right DRONE ALERT
; panel, printed at $7B67-$7B7A with textWindowLeft 31, column 0, row 3.  The $0D is the text engine's
; newline, so the two words land on consecutive rows.  What flashes is the panel colour
; (droneAlertColour $7B43, $02 or $20), not this text.
strDroneAlertTwoLine:
        .byte   "DRONE",$0D,"ALE"       ; 86A1  'DRONE', carriage return, 'ALE'
strDroneAlertEnd:
        .byte   $52                     ; 86AA  'RT' with bit 7 on the T (terminator)
        .byte   $D4                     ; 86AB  .

; statsTabLabels - the entire left-hand label column of the console STATS tab as ONE bit-7- terminated
; string with embedded carriage returns ($0D), printed by a single printString call at
; $161D-$1621 in the main program (textWindowLeft 10, column 3, right after ownerNameString $0B7F).
; Each $0D drops one row, so the leading $0D lands the first label on row 9:
;   row 9 GRUNTS, 10 RIDERS, 11 BOOMERS, 12 SPIES, (13 blank), 14 DRONES, 15 MISSILES,
;   (16 blank), 17 COMSPEED, 18 TER PTS, 19 RECYCLER, 20 MAP ID.
; The matching numbers are printed separately by drawStatsTab ($8518) into column 9 (US) and column 14
; (THEM).  79 bytes, $86AC-$86FA.
statsTabLabels:
        .byte   $0D,"GRUNTS",$0D,"RIDERS",$0D,"BOOMERS",$0D; 86AC  CR, 'GRUNTS', CR, 'RIDERS', CR, 'BOOMERS', CR
statsLabelsSpies:
        .byte   $53                     ; 86C3  'SP' - split here only because variant A has a routine entry on this byte
        .byte   $50                     ; 86C4  P
L_86C5:
        .byte   $49                     ; 86C5  'IES' CR CR 'DRO' - end of the SPIES row, a blank row, then DRONES
        .byte   $45,$53,$0D,$0D,$44,$52,$4F; 86C6  ES..DRO
        .byte   $4E                     ; 86CD  'NES' CR 'MISS'
        .byte   $45,$53,$0D,$4D,$49,$53,$53; 86CE  ES.MISS
        .byte   $49                     ; 86D5  'ILES' CR CR 'CO' - end of MISSILES, a blank row, then COMSPEED
        .byte   $4C,$45,$53,$0D,$0D,$43,$4F; 86D6  LES..CO
        .byte   $4D                     ; 86DD  'MS'
        .byte   $53                     ; 86DE  S
L_86DF:
        .byte   $50                     ; 86DF  'PEED' CR 'TER'
        .byte   $45,$45,$44,$0D,$54,$45,$52; 86E0  EED.TER
        .byte   $20                     ; 86E7  ' PTS' CR 'REC'
        .byte   $50,$54,$53,$0D,$52,$45,$43; 86E8  PTS.REC
        .byte   $59                     ; 86EF  'YCLE'
        .byte   $43,$4C,$45             ; 86F0  CLE
L_86F3:
        .byte   $52                     ; 86F3  'R' CR 'MAP I' + 'D' | $80: last label, bit 7 ends the whole blob
        .byte   $0D,$4D,$41,$50,$20,$49,$C4; 86F4  .MAP I.

; filmViewWordTable - three fixed 4-byte words used to complete the 'SHOW ****' status line during
; game-film playback: 'US' (padded with two zeroes), 'THEM', 'BOTH'.  The film key handler indexes it
; with (playModeFlags $0B9B & 3) * 4 at $4116-$4120 and copies four bytes over $8726-$8729 ($4123)
; before queueing message 6.  Each word carries its own bit-7 terminator, so the two padding zeroes of
; the 'US' entry are copied but never printed.  Only indices 0-2 occur: the CLR/ HOME handler wraps
; playModeFlags from $83 back to $80 at $4110-$4116.
filmViewWordTable:
        .byte   $55,$D3,$00,$00         ; 86FB  U...  entry 0 'US' | $80 plus two pad bytes, then entry 1 'THEM' | $80
        .byte   $54,$48,$45,$CD         ; 86FF  THE.
        .byte   $42                     ; 8703  entry 2 'BOTH' with bit 7 on the H
        .byte   $4F,$54,$C8             ; 8704  OT.

; Message strings.  From here on every block is one entry of the message table at $0500 (low bytes) /
; $0524 (high bytes), with the sound played for it in the parallel table at $0612 ($80 = silent).
; Messages are queued by id through queueMessage ($C566) or queueMessageUnlessFilm ($C560), the latter
; dropping the message during solo-trainer or film playback.
; 
; msgEndOfFilm - 'END OF FILM.', message id 2, sound 3; queued at $59FC after setFilmPtrToEnd when a
; recorded game film has been replayed to its last frame.
msgEndOfFilm:
        .byte   "END OF FILM"           ; 8707  'END OF FILM'
D_8712:
        .byte   $AE                     ; 8712  '.' with bit 7 set = terminator

; msgUnitRecycled - 'UNIT RECYCLED.', message id 5, sound 4; queued with queueMessageUnlessFilm at
; $3EA3 after initUnitRecord, but only when the recycled unit's owner (zp_1A) is the local player.
msgUnitRecycled:
        .byte   "U"                     ; 8713  'U'
L_8714:
        .byte   $4E                     ; 8714  'NIT RECY'
        .byte   $49,$54,$20,$52,$45,$43,$59; 8715  IT RECY
        .byte   $43                     ; 871C  'CLED' + '.' | $80
        .byte   $4C,$45,$44,$AE         ; 871D  LED.

; msgShowViewTemplate - 'SHOW ****', message id 6, sound 3: the film playback status line.  The four
; '*' at $8726-$8729 are overwritten with a filmViewWordTable entry (US / THEM / BOTH) at
; $4123 before the message is queued at $412E, and that entry supplies the bit-7 terminator.
; As stored on disk the template has NO terminator, so printing it unpatched would run straight on
; through 'SPEED *' and only stop at the '!' of 'SPOTTED RECYCLER!' ($8741).
msgShowViewTemplate:
        .byte   "S"                     ; 8721  'S'
msgShowViewBody:
        .byte   $48                     ; 8722  'HOW ' - the space before the patched word
        .byte   $4F,$57,$20             ; 8723  OW 
msgShowViewWord:
        .byte   $2A                     ; 8726  four-byte patch field: receives 'US'+$80+2 pad / 'THEM'|$80 / 'BOTH'|$80 from filmViewWordTable, written by the film key handler at $4123
        .byte   $2A,$2A,$2A             ; 8727  ***

; msgSpeedTemplate - 'SPEED *', message id 7, sound 3: shown when a digit key changes the film replay
; speed.  Pressing '1'-'9' sets stepIntervalFrames $92E3 = 8 - (key - $B1) and stores the raw key code
; $B1-$B9 into $8730 at $40DE; because those codes already have bit 7 set they double as the string
; terminator.  Unterminated as stored on disk, like the SHOW template above.
msgSpeedTemplate:
        .byte   "SPEE"                  ; 872A  'SPEE'
msgSpeedBody:
        .byte   $44                     ; 872E  'D' and a space
        .byte   $20                     ; 872F   
msgSpeedDigit:
        .byte   $2A                     ; 8730  one-byte patch field: the pressed digit's key code $B1-$B9 (screen code with bit 7 = terminator), written at $40DE

; msgSpottedRecycler - 'SPOTTED RECYCLER!', message id 8, sound 4; queued with queueMessageUnlessFilm
; at $3F8C right after drawRecyclerOnMap / drawMapCell, i.e. the moment the recycler cell first
; becomes visible to the local player.
msgSpottedRecycler:
        .byte   "SPO"                   ; 8731  'SPO'
L_8734:
        .byte   $54                     ; 8734  'TTED REC'
        .byte   $54,$45,$44,$20,$52,$45,$43; 8735  TED REC
        .byte   $59                     ; 873C  'YCLE'
        .byte   $43,$4C,$45             ; 873D  CLE
L_8740:
        .byte   $52                     ; 8740  'R' + '!' | $80
        .byte   $A1                     ; 8741  .

; msgComcenStunned - 'COMCEN STUNNED!', message id $0C, sound 4; queued at $0741-$0743 by the
; game-tick code in the $0400 block when $9162 reads $FF.  The same text is also printed straight into
; the console status area at column 5 by $17C4-$17D4 for as long as comcenStunnedFlag ($922A) bit 7 is
; set.
msgComcenStunned:
        .byte   "COMCEN STUNNED",'!'|$80; 8742  15 bytes, terminated by '!' | $80

; msgSpottedEnemyComcen - 'SPOTTED ENEMY COMCEN!', message id $0D, sound 4; queued with
; queueMessageUnlessFilm at $36EF when a newly visible enemy unit's type field ($F76C,y & 7) is 4
; (COMCEN).  Types 0-2 (GRUNT, RIDER, BOOMER) are spotted silently.
msgSpottedEnemyComcen:
        .byte   "SPOT"                  ; 8751  'SPOT'
L_8755:
        .byte   $54                     ; 8755  'T'
msgSpottedComcenBody:
        .byte   $45                     ; 8756  'ED ENEMY'
        .byte   $44,$20,$45,$4E,$45,$4D,$59; 8757  D ENEMY
        .byte   $20                     ; 875E  ' COMCEN'
        .byte   $43,$4F,$4D,$43,$45,$4E ; 875F  COMCEN
L_8765:
        .byte   $A1                     ; 8765  '!' | $80 terminator

; msgSpyUnderAttack - 'SPY UNDER ATTACK!', message id $0E, sound 4; queued with queueMessageUnlessFilm
; at $3B3B when the unit being hit is type 3 (SPY) and its energy field ($F9C4,y & $3F) is still $32 =
; 50, i.e. full - so it fires once, on the first hit only.
msgSpyUnderAttack:
        .byte   "SPY UNDER ATTACK",'!'|$80; 8766  17 bytes, terminated by '!' | $80

; msgSpottedEnemySpy - 'SPOTTED ENEMY SPY!', message id $0F, sound 4; queued with
; queueMessageUnlessFilm at $36E7, the sibling of the COMCEN case above in the visibility code.
msgSpottedEnemySpy:
        .byte   "SPOTTED ENEMY SPY",'!'|$80; 8777  18 bytes, terminated by '!' | $80

; The five end-of-game result messages.  computeGameResult ($28F1) picks one of them into
; persistentMessageId and queues it at $29A0.
; 
; msgWeLostOnPoints - 'WE LOST ON POINTS!', message id $11, sound $0E: the time-out result when the
; LOSING side ($2963-$296B: X, the side whose accumulated damage score is higher) is the local player.
; If it is the other side the code does INY and picks $12 instead. Variant A stores 22 bytes of its
; own code over $8789-$879E, so this string is only intact while variant B is resident - which is
; fine, because results are only shown during the battle.
msgWeLostOnPoints:
        .byte   "W"                     ; 8789  'W'
L_878A:
        .byte   $45                     ; 878A  'E LOST O'
        .byte   $20,$4C,$4F,$53,$54,$20,$4F; 878B   LOST O
        .byte   $4E                     ; 8792  'N P'
        .byte   $20,$50                 ; 8793   P
msgWeLostOnPointsEnd:
        .byte   $4F                     ; 8795  'OINTS' + '!' | $80
        .byte   $49,$4E,$54,$53,$A1     ; 8796  INTS.

; msgWeWonOnPoints - 'WE WON ON POINTS!', message id $12, sound $0D: the time-out win (losing side !=
; local player).  Its first four bytes 'WE W' ($879B-$879E) are the last of the bytes variant A
; overlays; from $879F on the two overlay variants are byte-identical to the end of the block.
msgWeWonOnPoints:
        .byte   "WE W"                  ; 879B  'WE W' - the last four bytes that variant A clobbers
        .byte   "ON ON POINTS",'!'|$80  ; 879F  'ON ON POINTS' + '!' | $80; identical in both overlay variants

; msgItWasADraw - 'IT WAS A DRAW.', message id $13 and the only result message with no sound ($80 in
; the sound table).  Chosen when the 16-bit score difference is exactly zero at time-out ($2917-$2929)
; or when gameOverFlags bits 5-6 are both set, i.e. both comcens were knocked out ($297A); both sides
; are then given 250 points.
msgItWasADraw:
        .byte   "IT WAS A DRAW",'.'|$80 ; 87AC  14 bytes, terminated by '.' | $80

; msgWeWereKnockedOut - 'WE WERE KNOCKED OUT!', message id $14, sound $0E; selected at $298D-$2995
; when the knocked-out side equals the local player.  The loser is given 500 points, the winner 150.
msgWeWereKnockedOut:
        .byte   "WE WERE KNOCKED OUT",'!'|$80; 87BA  20 bytes, terminated by '!' | $80

; msgWeWonByAKnockout - 'WE WON BY A KNOCKOUT!', message id $15, sound $0D: the enemy comcen was
; destroyed (the INY sibling of message $14). The solo trainer's stores to 'msgWeWonByAKnockout,y' at
; $EB29/$EB53 do NOT corrupt this string: there Y is an absolute unit index of the AI side, 50-99, so
; the writes land at $8800-$8831 - the trainer's per-unit order-timestamp table, which it addresses
; through the base $87CE.
msgWeWonByAKnockout:
        .byte   "WE WON BY A KNOCKOUT",'!'|$80; 87CE  21 bytes, terminated by '!' | $80

; msgGameOver - 'GAME OVER.', message id $16, sound $19; queued first at $2901-$2903 on the time-out
; path, ahead of the points result message.
msgGameOver:
        .byte   "GAME OVER",'.'|$80     ; 87E3  10 bytes, terminated by '.' | $80

; msgDroneAlert - 'DRONE ALERT!'.  This one has no fixed id: overlay A installs its address into
; run-time message slot $20 at $7BA6-$7BAC (ldx #$20 / lda #$ED / ldy #$87 / jsr setMessageSlot
; $C924) just before jumping into the opponent module's battle-start entry.  The pointer survives
; the swap to overlay B because this string sits in the tail that both variants hold identically.
; During the battle showDroneAlert ($820F) plays sound 7 and queues id $20 whenever the opponent
; launches a drone, unless playModeFlags $0B9B bit 7 (solo trainer / film playback) is set.
msgDroneAlert:
        .byte   "DRONE ALERT",'!'|$80   ; 87ED  12 bytes, terminated by '!' | $80

; overlayTailPadding - six unused $00 bytes between the last string and the variant flag.  Nothing
; reads them; they are identical in both overlay variants and exist only to push the flag to the very
; last byte of the block.
overlayTailPadding:
        .byte   $00,$00,$00,$00,$00,$00 ; 87F9  ......  filler up to $87FE

; overlayVariantFlag - identity byte of whichever $6F00-$87FF overlay is currently resident.  The
; overlay loader tests bit 7 at $1036 and $1080 to decide whether the variant it wants is already in
; memory or has to be pulled from disk (variant A = track 31 s0-16 + track 32 s0-7, variant B = track
; 33 s0-16 + track 32 s8-15).
overlayVariantFlag:
        .byte   $FF                     ; 87FF  .  $FF = variant B (comcen missile and drone screens) is resident; variant A (map generator and setup) stores $00 here
