{
 "chunk": "game_ovl_6F00_B_7C6E",
 "unit": "game/ovl_6F00_B",
 "routines": [
  {
   "addr": "7C5C",
   "name": "getComcenCentredViewOrigin",
   "summary": "Returns the map origin that centres the player's comcen in overlay B's 21x17-cell screen-1 map window: X = unitCol[comcenUnit]-10, Y = unitRow[comcenUnit]-8. Called from $7959 and $7BC5 (screen 1 comcen drawing). Starts two bytes before the nominal chunk start ($7C6E) but is on the first line of the line range.",
   "inputs": "$9236 comcenUnit, unitCol $F640, unitRow $F6A4",
   "outputs": "X = origin column, Y = origin row, A clobbered",
   "confidence": "medium"
  },
  {
   "addr": "7FE6",
   "name": "enterDroneScreen",
   "summary": "Entry of game screen 2 (the DRONE screen, F5), jumped to by the screen switcher sub_06BA ($06FC) when currentScreenId becomes 2. Copies dronesLeft[0] ($92AE) to $9233, unpacks the eight aircraft silhouettes at $FE24 into the $0200 shape buffer (unpackRleTo0200) and draws the whole screen with sub_70F5, then falls into resetDroneScreen.",
   "inputs": "$92AE dronesLeft[own]",
   "outputs": "$9233, $0200-$03FF shape buffer, screen 2 drawn",
   "confidence": "high"
  },
  {
   "addr": "7FF6",
   "name": "resetDroneScreen",
   "summary": "(Re)initialises the drone screen's panel state and enters the idle loop. Also the re-entry point used by the knock-out sequence ($076A) and after a drone flight ends ($81FB). Reloads $9233 from dronesLeft[0], positions sprite 0 (X $37,Y $64, colour 1), sprite 5 (X $38,Y $64, colour 2) and sprite 3 (X $38,Y $65) in the left panel, sets droneJoyHeading zp_5E=0 (north), draws the heading indicator (sub_823F), loads aircraft shape 0 into sprite 5, enables sprites 0+3, and sets droneFuel $9220 to 100 when drones are left (else 0) for the fuel read-out.",
   "inputs": "$92AE, sprite tables",
   "outputs": "$9233, $9220, zp_5E, $9010/$9013/$9015, $9018/$901B/$901D, $9008/$900D, $9022=$09, $91CC=5; falls into droneScreenIdleLoop",
   "confidence": "high"
  },
  {
   "addr": "803D",
   "name": "droneScreenIdleLoop",
   "summary": "Idle loop of the drone screen while no drone is in flight: runs updateGameFrame and the screen switcher every frame; every 11 frames (zp_6C) blinks the drones-left box (sub_7061, only when fewer than 2 drones remain) and XORs colour 8 into the 2x2 LAUNCH box at row 12 column 34. A fresh fire press (checkFirePressedNotSolo) launches a drone via launchDrone unless the own comcen is cloaked ($9245 bit 7), no drones are left ($9233 = 0), or the drone system is damaged ($9244 != 0), in which case only one press in four (random & 3 == 0) succeeds.",
   "inputs": "zp_6C, $9233, $9244, $9245, fire button",
   "outputs": "colour cells toggled; jumps to launchDrone",
   "confidence": "high"
  },
  {
   "addr": "808D",
   "name": "launchDrone",
   "summary": "Launch sequence: reads the joystick heading (sub_82A4), droneHeading $920B = joyDirToHeadingTable[zp_5E], queues the 1-byte command $97 (drone launch), paints the LAUNCH box (12,34) colour 5, droneFuel = 100 (printed by sub_6F99), decrements $9233 and reprints it (sub_7036), sets droneStateFlags $920D |= $C0 (active + locally steered), sets the view origin and droneViewOriginCol/Row $92F6/$92F7 to comcen position minus (6,5) so the drone starts over the comcen, draws the comcen ring sprite (sub_3456), loads the targeting reticle at $FF93 into sprite 2 (black, X $AC, Y $88) and enables it, puts the heading silhouette into sprite 5, plays sound $0F, clears the camera window with static (sub_7135), sets colour override $AA, runs the iris-open animation sub_7221 and enters droneFlightLoop.",
   "inputs": "zp_5D joystick, $9236 comcenUnit, unitCol/unitRow, $FF93 reticle shape",
   "outputs": "$920B, $920D, $9220, $9233, $92F6/$92F7, zp_A5/zp_A6, sprites 2/4/5, command $97 queued",
   "confidence": "high"
  },
  {
   "addr": "8124",
   "name": "droneFlightLoop",
   "summary": "Per-frame loop while the drone flies: updateGameFrame; a fire press goes to strikeWithDrone. Every 40 frames (zp_6C) blinks the drones-left box (<2 left) and XORs colour 8 into the STRIKE box at (16,34). When zp_6E has expired re-reads the joystick heading (sub_82A4). When exchangeStepFlag $920E bit 7 is set (a lock-step exchange was applied) advances the camera one step (sub_8279); if droneFuel reaches 0 or the game ended ($0BA8 != 0) the drone is released (sub_8230) and the loss animation sub_7163 + sound $1C run; if $920D bit 7 was cleared by the command handlers (drone arrived $92 / remote detonation) the same ending runs without sending $8F. Then endDroneFlight.",
   "inputs": "zp_6C, zp_6E, $920E, $920D, $9220, $0BA8, fire button",
   "outputs": "drone view redrawn per step; falls into endDroneFlight",
   "confidence": "high"
  },
  {
   "addr": "8182",
   "name": "strikeWithDrone",
   "summary": "Fire pressed during flight: droneStateFlags = ($920D & $BF) | $20 (stop local steering, detonation pending), paints the STRIKE box (16,34) colour 2, computes the drone's map position (sub_81FE) and queues command $8E col,row (detonate). Then waits: while bit 5 stays set it keeps advancing the camera on each exchange with sound $1C; once cmdDroneDetonate has cleared bit 5 (own command executed) and bit 7 is still set it plays sound $11 and runs the strike flash/loss animation sub_7552. Ends in endDroneFlight.",
   "inputs": "$920D, $920E, $92F6/$92F7",
   "outputs": "command $8E queued, $920D, $74/$75",
   "confidence": "high"
  },
  {
   "addr": "81D0",
   "name": "endDroneFlight",
   "summary": "Common tail of a drone flight: sound $1C, droneFuel = 0 (reprinted), droneStateFlags &= $3F, view origin back to comcen minus (6,5), camera window cleared without static (sub_713C), then jumps to resetDroneScreen for the next launch.",
   "inputs": "$9236, unitCol/unitRow",
   "outputs": "$9220=0, $920D, zp_A5/zp_A6",
   "confidence": "high"
  },
  {
   "addr": "81FE",
   "name": "getDroneMapPosition",
   "summary": "cmdParam1/2 ($74/$75) = droneViewOriginCol+6, droneViewOriginRow+5, i.e. the drone's own map cell (the camera window is kept centred on the drone). Used by sendOutgoingPacket ($563C) to build the $8D heading,col,row move command and by strikeWithDrone for the $8E detonate command.",
   "inputs": "$92F6/$92F7",
   "outputs": "zp_74 = drone column, zp_75 = drone row",
   "confidence": "high"
  },
  {
   "addr": "820F",
   "name": "showDroneAlert",
   "summary": "Opponent drone launch notification called by cmdDroneLaunch ($53AF): plays sound 7 and, unless playModeFlags $0B9B bit 7 (solo/film) is set, shows message $20 'DRONE ALERT!' through the $C000 overlay's sub_C566. Assembled as .byte in the listing (misclassified).",
   "inputs": "$0B9B",
   "outputs": "sound, message",
   "confidence": "high"
  },
  {
   "addr": "821F",
   "name": "fillDroneViewWindow",
   "summary": "Fills the drone camera window (14 columns x 12 rows at row 6, column 13) with colour byte A through fillScreenColourRect. Used to clear the window ($7141), to show static ($AA) when the drone system is damaged, and by the main program at $2E07.",
   "inputs": "A = colour byte",
   "outputs": "D_2A2D=6, D_2A2E=13, screen RAM cells",
   "confidence": "high"
  },
  {
   "addr": "8230",
   "name": "releaseDroneControl",
   "summary": "If a drone is active ($920D bit 7) clears bits 6-7 of droneStateFlags and queues the 1-byte command $8F (drone released) so the opponent stops tracking it. Called when fuel runs out or the game ends, and by the comcen knock-out sequence ($0709).",
   "inputs": "$920D",
   "outputs": "$920D &= $3F, command $8F queued",
   "confidence": "high"
  },
  {
   "addr": "823F",
   "name": "drawDroneHeadingIndicator",
   "summary": "Draws the heading pointer of the left panel: clears sprite 3, draws a solid line in it from the centre (col 12,row 10) to headingLineEndCol/RowTable[joyDirToHeadingTable[zp_5E]] with drawLineInPathSprite, sets sprite 3's colour to $0B (or 6 for north/south, zp_5E < 2), copies the 64-byte shape to sprite 0 (which sits one pixel up-left in white, giving a shadowed line) and enables sprites 0 and 3.",
   "inputs": "zp_5E droneJoyHeading, tables $7FCE/$7FD6/$7FDE",
   "outputs": "$8AC0 and $8A00 sprite shapes, $900B, $9022 |= $09, zp_92-zp_95 clobbered",
   "confidence": "high"
  },
  {
   "addr": "8279",
   "name": "advanceDroneCamera",
   "summary": "One drone step on the DRONE screen, run whenever exchangeStepFlag $920E bit 7 is set: clears the flag, updateMarkerSprite, redraws the 14x12 camera window from the map around droneViewOrigin (sub_741C); if the drone system is damaged ($9244) half of the steps (random bit 7 clear) fill the window with static colour $AA; decrements droneFuel, reloads the heading silhouette into sprite 5 and reprints the fuel (sub_6F99). The actual drone movement is done by sendOutgoingPacket via $731E.",
   "inputs": "$920B, $9220, $9244, $92F6/$92F7",
   "outputs": "$920E=0, $9220-1, sprite 5, camera window",
   "confidence": "high"
  },
  {
   "addr": "82A4",
   "name": "readDroneHeadingFromJoystick",
   "summary": "Reads joystickDirectionTable[zp_5D]; if a direction is held (entry >= 0) stores it in droneJoyHeading zp_5E, sets the re-read delay zp_6E = 20 frames and redraws the heading indicator (sub_823F). Called at launch and, when zp_6E has expired, during flight.",
   "inputs": "zp_5D joystick bits, $1FA6 table",
   "outputs": "zp_5E, zp_6E, heading sprites",
   "confidence": "high"
  },
  {
   "addr": "82BF",
   "name": "drawShotLineInViewport",
   "summary": "Draws the shot of resolveAttack ($3A30) in the 7x5-cell battlefield viewport (2x2 characters per cell at row 1, column 25). First forces the upper colour nibble of the viewport's 14x10 screen RAM cells to 1 (white for bit pair 01). Handles a negative view origin (zp_A5/zp_A6 < 0) by shifting all columns/rows, computes the viewport bounds $82B5-$82B8, and records in $82B9 which endpoints lie inside (attacker -> -1, target -> +2); returns if neither. Then rebases the coordinates (sub_840F), scales to fat pixels (x8, +3/+4 for the cell centre, sub_84B2), runs the line stepper from attacker (zp_93,zp_92) to target (zp_95,zp_94) and plots colour-1 fat pixels via plotFatPixel for points inside the window (X = dx+100, Y = dy+4). The first 4 steps from the shooter are skipped, the 5th gets the 4-pixel diamond (sub_84F1), unless only the target is visible ($82B9 = 2) in which case the whole line is solid. Boomers (attackerType $923D = 2) step 3 cells per plot (dotted). Finally viewRedrawTimer $9234 = $8F so the view is redrawn (line erased) 15 frames later.",
   "inputs": "zp_92/zp_93 attacker row/col, zp_94/zp_95 target row/col, zp_A5/zp_A6 view origin, $923D attackerType, $9382/$939B screen row tables",
   "outputs": "bitmap pixels, screen RAM colour nibbles, $9234 = $8F, $82B5-$82BC, zp_92-zp_95 and line stepper state clobbered, zp_B6/zp_B7",
   "confidence": "high"
  },
  {
   "addr": "840F",
   "name": "rebaseLineCoordsToMin",
   "summary": "Subtracts min(viewLeft $82B5, zp_93, zp_95) from all three column values and min(viewTop $82B7, zp_92, zp_94) from the three row values so that the smallest becomes 0. Keeps the following x8 scaling from overflowing a byte.",
   "inputs": "$82B5, $82B7, zp_92-zp_95",
   "outputs": "$82B5, $82B7, zp_92-zp_95 rebased",
   "confidence": "high"
  },
  {
   "addr": "84B2",
   "name": "scaleLineCoordsToFatPixels",
   "summary": "Converts the four line coordinates from map cells to fat-pixel units: zp_94 = row*8+4, zp_95 = col*8+3, zp_92 = row*8+4, zp_93 = col*8+3, saturating to $FF on overflow (the zp_92 case lacks the LDA #$FF and only skips the +4 when the overflowed value is negative - harmless in practice).",
   "inputs": "zp_92-zp_95 in cells",
   "outputs": "zp_92-zp_95 in fat pixels",
   "confidence": "high"
  },
  {
   "addr": "84F1",
   "name": "plotDiamondMarker",
   "summary": "Plots four colour-1 fat pixels at (X-1,Y-1), (X+1,Y-1), (X+1,Y+1), (X-1,Y+1) with plotFatPixel and restores X/Y: the small diamond drawn at the muzzle end of a shot line.",
   "inputs": "X, Y = fat pixel position",
   "outputs": "4 pixels; X, Y preserved",
   "confidence": "high"
  },
  {
   "addr": "8518",
   "name": "drawStatsTab",
   "summary": "Fills in the STATS tab of the in-game menu (called from $1460/$14D3): text window left 10, colour $61, clears exchangeStepFlag $920E (the refresh request). Rows 9-12: number of living GRUNTS/RIDERS/BOOMERS/SPIES per side, counted by scanning the 50 unit slots down from each comcen (own comcen $9236 in column 9 = US, the other comcen $9236 EOR $52 in column 14 = THEM). Row 14 DRONES ($92AE/$92AF), row 15 MISSILES ($92B0/$92B1), row 17 COMSPEED (comSpeedDisplayTable[$92B4/$92B5]), row 18 TER PTS ($92AC[side EOR 1] under US, $92AC[side] under THEM), row 19 RECYCLER words via printOptionWord ($92A6[side], $92A6[side EOR 1]), row 20 the MAP ID string at $0BA9 in column 9 for side 0, column 14 for side 1.",
   "inputs": "$9236, $F76C unitTypeFlags, $92AE-$92B5, $92AC, $92A6, $0B9F playerSide, $0BA9",
   "outputs": "text printed; $920E = 0; zp_18, zp_1A, zp_20, zp_21, zp_3B, zp_3D, zp_3F, zp_40 used",
   "confidence": "high"
  },
  {
   "addr": "8601",
   "name": "printNumberRightAligned",
   "summary": "Prints the 8-bit value A right-aligned in a 3-character field at text column X of the current row: sets zp_3F = X, computes the number of leading fill characters (0 for >= 100, 1 for 10-99, 2 for < 10) into the self-modified operands at $8622/$8627/$8629, positions the cursor (sub_C099), prints the fill characters with the $C000 overlay routine at $C0EF and the number with sub_C17D (Y = 0 high byte). The CPY #0/BNE at $8607 is dead (Y is always 0).",
   "inputs": "A = value, X = column",
   "outputs": "text; zp_3F; operands $8622, $8627, $8629",
   "confidence": "high"
  },
  {
   "addr": "862D",
   "name": "setTextColumn",
   "summary": "Sets the text cursor column zp_3F = A and repositions the cursor through sub_C099 (X and Y preserved).",
   "inputs": "A = column relative to textWindowLeft",
   "outputs": "zp_3F, text cursor pointers",
   "confidence": "high"
  },
  {
   "addr": "8632",
   "name": "drawComcenSchematic",
   "summary": "Unpacks the RLE-compressed comcen picture at $7C6E straight into the bitmap for the REPAIR tab (called from drawRepairTab $176B): marker = first byte ($13) -> rleMarkerByte $9246, $9247 = 0, fetchRleByte's pointer ($681B/$681C) = $7C6F, destination = bitmap cell (row 10, column 13) from calcCellPointersXY. Emits 10 character rows of 160 bytes (20 characters x 8 lines) - 'marker,count,value' runs (count 0 = 256) may cross row boundaries - advancing zp_B4/zp_B5 by 320 per row. The picture is a side view of the tracked comcen vehicle with dish and antenna.",
   "inputs": "$7C6E compressed data",
   "outputs": "bitmap $A000 area rows 10-19 columns 13-32; $9246, $9247, $681B/$681C, zp_1A, zp_1B, zp_B4/zp_B5",
   "confidence": "high"
  }
 ],
 "variables": [
  {
   "addr": "5E",
   "scope": "zp",
   "name": "droneJoyHeading",
   "meaning": "Desired drone direction in joystickDirectionTable order (0 N, 1 S, 2 E, 3 W, 4 NW, 5 NE, 6 SE, 7 SW); converted with joyDirToHeadingTable to the clockwise heading $920B by launchDrone and the steering routine $731E.",
   "confidence": "high"
  },
  {
   "addr": "5D",
   "scope": "zp",
   "name": "joystickDirection",
   "meaning": "Joystick port 2 direction bits (active low, $0F centred); index into joystickDirectionTable $1FA6.",
   "confidence": "high"
  },
  {
   "addr": "6C",
   "scope": "zp",
   "name": "blinkRepeatTimer",
   "meaning": "IRQ frame countdown; on the drone screen it paces the LAUNCH/STRIKE box blink (11 frames idle, 40 in flight).",
   "confidence": "high"
  },
  {
   "addr": "6E",
   "scope": "zp",
   "name": "headingRereadDelay",
   "meaning": "IRQ frame countdown set to 20 after a joystick heading was accepted; the joystick is re-read for the drone only when it is 0.",
   "confidence": "high"
  },
  {
   "addr": "74",
   "scope": "zp",
   "name": "cmdParam1",
   "meaning": "Second byte of an outgoing command; getDroneMapPosition stores the drone column here.",
   "confidence": "high"
  },
  {
   "addr": "75",
   "scope": "zp",
   "name": "cmdParam2",
   "meaning": "Third byte of an outgoing command; getDroneMapPosition stores the drone row here.",
   "confidence": "high"
  },
  {
   "addr": "91",
   "scope": "zp",
   "name": "lineStepsLeft",
   "meaning": "Steps left in the high-memory line stepper; 0 ends the shot line.",
   "confidence": "high"
  },
  {
   "addr": "92",
   "scope": "zp",
   "name": "lineCurRow",
   "meaning": "Line start/current row (attacker row in drawShotLineInViewport, 10 = sprite centre in drawDroneHeadingIndicator).",
   "confidence": "high"
  },
  {
   "addr": "93",
   "scope": "zp",
   "name": "lineCurCol",
   "meaning": "Line start/current column (attacker column; 12 = sprite centre for the heading indicator).",
   "confidence": "high"
  },
  {
   "addr": "94",
   "scope": "zp",
   "name": "lineEndRow",
   "meaning": "Line end row (target row / heading line end).",
   "confidence": "high"
  },
  {
   "addr": "95",
   "scope": "zp",
   "name": "lineEndCol",
   "meaning": "Line end column (target column / heading line end).",
   "confidence": "high"
  },
  {
   "addr": "A5",
   "scope": "zp",
   "name": "viewOriginCol",
   "meaning": "Map column of the left edge of the battlefield/drone view (may be negative); the drone screen sets it to comcen column - 6.",
   "confidence": "high"
  },
  {
   "addr": "A6",
   "scope": "zp",
   "name": "viewOriginRow",
   "meaning": "Map row of the top edge of the view (may be negative); drone screen sets comcen row - 5.",
   "confidence": "high"
  },
  {
   "addr": "B4",
   "scope": "zp",
   "name": "bitmapPtr",
   "meaning": "Bitmap destination pointer (lo) of drawComcenSchematic, from calcCellPointersXY.",
   "confidence": "high"
  },
  {
   "addr": "B5",
   "scope": "zp",
   "name": "bitmapPtrHi",
   "meaning": "Bitmap destination pointer (hi).",
   "confidence": "high"
  },
  {
   "addr": "B6",
   "scope": "zp",
   "name": "screenPtr",
   "meaning": "Screen RAM row pointer (lo) used by drawShotLineInViewport to whiten the viewport colour nibbles.",
   "confidence": "high"
  },
  {
   "addr": "B7",
   "scope": "zp",
   "name": "screenPtrHi",
   "meaning": "Screen RAM row pointer (hi).",
   "confidence": "high"
  },
  {
   "addr": "18",
   "scope": "zp",
   "name": "unitCountScratch",
   "meaning": "drawStatsTab: number of living units of the current type on the side being counted.",
   "confidence": "high"
  },
  {
   "addr": "1A",
   "scope": "zp",
   "name": "loopCounter1A",
   "meaning": "drawStatsTab: 50-slot unit loop counter; drawComcenSchematic: character rows left (10).",
   "confidence": "high"
  },
  {
   "addr": "1B",
   "scope": "zp",
   "name": "rleRunLength",
   "meaning": "drawComcenSchematic: run length of the current RLE run (0 = 256).",
   "confidence": "high"
  },
  {
   "addr": "20",
   "scope": "zp",
   "name": "statsUnitType",
   "meaning": "drawStatsTab: unit type currently counted (0 GRUNT .. 3 SPY).",
   "confidence": "high"
  },
  {
   "addr": "21",
   "scope": "zp",
   "name": "statsSidePass",
   "meaning": "drawStatsTab: 1 = counting own side (column 9), 0 = opponent (column 14).",
   "confidence": "high"
  },
  {
   "addr": "3B",
   "scope": "zp",
   "name": "textWindowLeft",
   "meaning": "Text engine left column (10 for the STATS tab).",
   "confidence": "high"
  },
  {
   "addr": "3D",
   "scope": "zp",
   "name": "textColour",
   "meaning": "Text colour byte ($61 on the STATS tab).",
   "confidence": "high"
  },
  {
   "addr": "3F",
   "scope": "zp",
   "name": "textCursorCol",
   "meaning": "Text cursor column relative to textWindowLeft (9 = US column, 14 = THEM column).",
   "confidence": "high"
  },
  {
   "addr": "40",
   "scope": "zp",
   "name": "textCursorRow",
   "meaning": "Text cursor row (STATS rows 9-20).",
   "confidence": "high"
  },
  {
   "addr": "9220",
   "scope": "abs",
   "name": "droneFuel",
   "meaning": "Flight time left of the current drone: 100 at launch, -1 per lock-step exchange (advanceDroneCamera); shown by sub_6F99 as a three-digit read-out whose colour changes below 21 and below 11 and alternates on even values (blink); 0 forces releaseDroneControl. Before a launch it shows 100 if drones remain, else 0.",
   "confidence": "high"
  },
  {
   "addr": "9233",
   "scope": "abs",
   "name": "dronesLeftDisplay",
   "meaning": "Drone screen's copy of dronesLeft[own] ($92AE): reloaded on every (re)entry, decremented locally at launch, printed by sub_7036; 0 blocks launching, < 2 makes the drone box blink.",
   "confidence": "high"
  },
  {
   "addr": "920B",
   "scope": "abs",
   "name": "droneHeading",
   "meaning": "Drone heading 0-7 clockwise from north; index of the aircraft silhouette (sprite 5) and first byte of command $8D. Set from joyDirToHeadingTable at launch, turned one step per exchange by $731E.",
   "confidence": "high"
  },
  {
   "addr": "920D",
   "scope": "abs",
   "name": "droneStateFlags",
   "meaning": "Bit 7 = own drone in flight, bit 6 = steered locally (sendOutgoingPacket emits $8D moves), bit 5 = strike ($8E) pending, bit 0 set by cmdDroneMove. Set to $C0 at launch, &= $3F when the flight ends.",
   "confidence": "high"
  },
  {
   "addr": "920E",
   "scope": "abs",
   "name": "exchangeStepFlag",
   "meaning": "Bit 7 set (at $41F8) after each merged command exchange is applied; the drone screen consumes it to advance the camera one step, the STATS tab clears it as its refresh request.",
   "confidence": "high"
  },
  {
   "addr": "9236",
   "scope": "abs",
   "name": "comcenUnit",
   "meaning": "Index of the player's comcen unit (49 for side 0, 99 for side 1; EOR $52 gives the other comcen).",
   "confidence": "high"
  },
  {
   "addr": "9244",
   "scope": "abs",
   "name": "droneSystemStatus",
   "meaning": "comcenSystemStatus[3] ($9241+3): $80 when the comcen's drone system was damaged by a hit (sub_3D57). Then only 1 in 4 launch presses succeeds and half of the camera steps show static.",
   "confidence": "high"
  },
  {
   "addr": "9245",
   "scope": "abs",
   "name": "unitInspectLockFlags",
   "meaning": "Bit 7 = own comcen cloaked; blocks drone launches.",
   "confidence": "medium"
  },
  {
   "addr": "92F6",
   "scope": "abs",
   "name": "droneViewOriginCol",
   "meaning": "Map column of the left edge of the 14x12 drone camera window; the drone itself is at +6 (sent in $8D/$8E).",
   "confidence": "high"
  },
  {
   "addr": "92F7",
   "scope": "abs",
   "name": "droneViewOriginRow",
   "meaning": "Map row of the top edge of the drone camera window; the drone is at +5.",
   "confidence": "high"
  },
  {
   "addr": "92AE",
   "scope": "abs",
   "name": "dronesLeft",
   "meaning": "2 bytes: drones remaining, [0] own, [1] opponent (STATS row DRONES).",
   "confidence": "high"
  },
  {
   "addr": "92B0",
   "scope": "abs",
   "name": "missilesLeft",
   "meaning": "2 bytes: missiles remaining, [0] own, [1] opponent (STATS row MISSILES).",
   "confidence": "high"
  },
  {
   "addr": "92B4",
   "scope": "abs",
   "name": "comcenSpeedClass",
   "meaning": "2 bytes [0 own, 1 opponent]: comcen mobility class 0-5, shown on the STATS row COMSPEED through comSpeedDisplayTable.",
   "confidence": "medium"
  },
  {
   "addr": "92AC",
   "scope": "abs",
   "name": "territoryPoints",
   "meaning": "2 bytes indexed by absolute side: STATS row TER PTS shows [playerSide EOR 1] under US and [playerSide] under THEM (the entry written by each player's own $A5 kind-1 setup command).",
   "confidence": "medium"
  },
  {
   "addr": "92A6",
   "scope": "abs",
   "name": "recyclerMode",
   "meaning": "2 bytes indexed by absolute side: 0 FULL, 1 HALF, 2 NONE (STATS row RECYCLER via printOptionWord).",
   "confidence": "high"
  },
  {
   "addr": "9234",
   "scope": "abs",
   "name": "viewRedrawTimer",
   "meaning": "Set to $8F by drawShotLineInViewport: the battlefield view is redrawn (erasing the line) when it counts down to $80.",
   "confidence": "high"
  },
  {
   "addr": "923D",
   "scope": "abs",
   "name": "attackerType",
   "meaning": "Type of the attacking unit; 2 (Boomer) makes the viewport shot line dotted (3 steps per pixel).",
   "confidence": "high"
  },
  {
   "addr": "0BA8",
   "scope": "abs",
   "name": "gameOverFlag",
   "meaning": "Nonzero when the game is over / no game running; ends a drone flight immediately.",
   "confidence": "medium"
  },
  {
   "addr": "0B9F",
   "scope": "abs",
   "name": "playerSide",
   "meaning": "Local player's side 0/1; EOR 1 = opponent; selects STATS columns.",
   "confidence": "high"
  },
  {
   "addr": "0B9B",
   "scope": "abs",
   "name": "playModeFlags",
   "meaning": "Bit 7 (solo / film playback) suppresses the DRONE ALERT message.",
   "confidence": "medium"
  },
  {
   "addr": "0BA9",
   "scope": "abs",
   "name": "mapIdText",
   "meaning": "Map id string (bit 7 terminated) printed on the MAP ID row of the STATS tab.",
   "confidence": "medium"
  },
  {
   "addr": "2A2D",
   "scope": "abs",
   "name": "windowRow",
   "meaning": "Row of the fill/XOR rectangle origin (12 LAUNCH box, 16 STRIKE box, 6 camera window).",
   "confidence": "high"
  },
  {
   "addr": "2A2E",
   "scope": "abs",
   "name": "windowCol",
   "meaning": "Column of the fill/XOR rectangle origin (34 for the buttons, 13 for the camera window).",
   "confidence": "high"
  },
  {
   "addr": "2A29",
   "scope": "abs",
   "name": "colourOverride",
   "meaning": "Nonzero = fixed colour for drawn characters; set to $AA during the drone camera iris animation.",
   "confidence": "medium"
  },
  {
   "addr": "2A2A",
   "scope": "abs",
   "name": "drawColourByte",
   "meaning": "Current colour byte for fills/characters ($AA static colour).",
   "confidence": "medium"
  },
  {
   "addr": "9010",
   "scope": "abs",
   "name": "spriteXShadow",
   "meaning": "Sprite X shadows; $9010 sprite 0 (heading line, $37), $9012 sprite 2 (reticle, $AC), $9013 sprite 3 (line shadow, $38), $9015 sprite 5 (drone silhouette, $38).",
   "confidence": "high"
  },
  {
   "addr": "9018",
   "scope": "abs",
   "name": "spriteYShadow",
   "meaning": "Sprite Y shadows; $9018 sprite 0 ($64), $901A sprite 2 ($88), $901B sprite 3 ($65), $901D sprite 5 ($64).",
   "confidence": "high"
  },
  {
   "addr": "9008",
   "scope": "abs",
   "name": "spriteColorShadow",
   "meaning": "Sprite colour shadows; $9008 sprite 0 = 1 white, $900A sprite 2 = 0 black reticle, $900B sprite 3 = $0B/$06 line shadow, $900D sprite 5 = 2 red silhouette.",
   "confidence": "high"
  },
  {
   "addr": "9022",
   "scope": "abs",
   "name": "spriteEnableShadow",
   "meaning": "Shadow of $D015; the drone screen enables sprites 0+3 ($09), sprite 2 (4) and sprite 5 via loadDirectionShapeSprite.",
   "confidence": "high"
  },
  {
   "addr": "91CC",
   "scope": "abs",
   "name": "activeSpriteIndex",
   "meaning": "Target sprite of loadDirectionShapeSprite; 5 on the drone screen.",
   "confidence": "high"
  },
  {
   "addr": "8A00",
   "scope": "abs",
   "name": "sprite0Data",
   "meaning": "Sprite 0 shape; receives a copy of the heading line from sprite 3.",
   "confidence": "high"
  },
  {
   "addr": "8A80",
   "scope": "abs",
   "name": "sprite2Data",
   "meaning": "Sprite 2 shape; receives the 64-byte targeting reticle from $FF93 at launch.",
   "confidence": "high"
  },
  {
   "addr": "8AC0",
   "scope": "abs",
   "name": "sprite3Data",
   "meaning": "Sprite 3 shape; the heading line is drawn here by drawLineInPathSprite.",
   "confidence": "high"
  },
  {
   "addr": "FF93",
   "scope": "abs",
   "name": "droneReticleSprite",
   "meaning": "64-byte sprite shape of the drone camera reticle (four corner brackets, centre ticks), copied into sprite 2 by launchDrone.",
   "confidence": "high"
  },
  {
   "addr": "F640",
   "scope": "abs",
   "name": "unitCol",
   "meaning": "Per-unit map column (100 entries).",
   "confidence": "high"
  },
  {
   "addr": "F6A4",
   "scope": "abs",
   "name": "unitRow",
   "meaning": "Per-unit map row (100 entries).",
   "confidence": "high"
  },
  {
   "addr": "F76C",
   "scope": "abs",
   "name": "unitTypeFlags",
   "meaning": "Per-unit byte: bits 0-2 type (0 GRUNT 1 RIDER 2 BOOMER 3 SPY 4 COMCEN), bit 7 = dead/unused; counted by drawStatsTab.",
   "confidence": "high"
  },
  {
   "addr": "9246",
   "scope": "abs",
   "name": "rleMarkerByte",
   "meaning": "Escape byte of the RLE stream being unpacked ($13 for the comcen picture).",
   "confidence": "high"
  },
  {
   "addr": "9247",
   "scope": "abs",
   "name": "rleFlag9247",
   "meaning": "Cleared by both RLE unpackers; no reader found.",
   "confidence": "low"
  },
  {
   "addr": "681B",
   "scope": "abs",
   "name": "rleSrcPtr",
   "meaning": "Self-modified operand (lo/hi at $681B/$681C) of fetchRleByte; drawComcenSchematic sets it to $7C6F.",
   "confidence": "high"
  },
  {
   "addr": "82B5",
   "scope": "abs",
   "name": "shotViewLeft",
   "meaning": "drawShotLineInViewport: viewport left column (rebased, later x8 = fat pixels).",
   "confidence": "high"
  },
  {
   "addr": "82B6",
   "scope": "abs",
   "name": "shotViewRight",
   "meaning": "viewport left column + 7 (exclusive right bound, cells).",
   "confidence": "high"
  },
  {
   "addr": "82B7",
   "scope": "abs",
   "name": "shotViewTop",
   "meaning": "viewport top row (rebased, later x8).",
   "confidence": "high"
  },
  {
   "addr": "82B8",
   "scope": "abs",
   "name": "shotViewBottom",
   "meaning": "viewport top row + 5 (exclusive bottom bound, cells).",
   "confidence": "high"
  },
  {
   "addr": "82B9",
   "scope": "abs",
   "name": "shotEndpointVisibility",
   "meaning": "-1 attacker inside the viewport, +2 target inside (1 = both, 0 = neither -> nothing drawn).",
   "confidence": "high"
  },
  {
   "addr": "82BA",
   "scope": "abs",
   "name": "shotColShift",
   "meaning": "Offset added to all columns when the view origin column is negative.",
   "confidence": "high"
  },
  {
   "addr": "82BB",
   "scope": "abs",
   "name": "shotRowShift",
   "meaning": "Offset added to all rows when the view origin row is negative.",
   "confidence": "high"
  },
  {
   "addr": "82BC",
   "scope": "abs",
   "name": "shotDashCountdown",
   "meaning": "Starts at 4: steps with a positive value are not plotted (gap at the shooter), 0 plots the diamond marker, negative plots normally.",
   "confidence": "high"
  }
 ],
 "dataBlocks": [
  {
   "addr": "7C6E",
   "length": 864,
   "type": "unknown",
   "name": "comcenSchematicRle",
   "description": "RLE-compressed 160x80 pixel hires picture (20x10 characters, 1600 bytes unpacked) of the comcen vehicle shown on the REPAIR tab. Byte 0 = escape marker $13; stream of literals and 'marker,count,value' runs (count 0 = 256); ends at $7FCD with the run $13,$00,$00 (256 zero bytes). The labels sub_7CA2/7CAE/7CBF/7D70/7D7D/7D8B/7D98/7DA7/7E4F/7E74/7EA0/7F3F/7FA6/7FCB and the L_/D_ labels inside come from overlay A references and have no meaning in variant B."
  },
  {
   "addr": "7FCE",
   "length": 8,
   "type": "byteTable",
   "name": "joyDirToHeadingTable",
   "description": "00 04 02 06 07 01 03 05: maps the joystickDirectionTable index (N,S,E,W,NW,NE,SE,SW) to the clockwise heading 0-7 (N,NE,E,SE,S,SW,W,NW) used for sprite shapes and command $8D. Also read by $731E."
  },
  {
   "addr": "7FD6",
   "length": 8,
   "type": "byteTable",
   "name": "headingLineEndColTable",
   "description": "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)."
  },
  {
   "addr": "7FDE",
   "length": 8,
   "type": "byteTable",
   "name": "headingLineEndRowTable",
   "description": "00 03 0A 11 14 11 0A 03: end row of the heading pointer line for headings 0-7 (centre row 10)."
  },
  {
   "addr": "82B5",
   "length": 8,
   "type": "byteTable",
   "name": "shotLineWorkVars",
   "description": "Eight zero-initialised work bytes of drawShotLineInViewport (see variables $82B5-$82BC)."
  },
  {
   "addr": "82BD",
   "length": 2,
   "type": "unknown",
   "name": "unusedBytes82BD",
   "description": "$00,$60: no reference in either variant; filler between the shot-line work bytes and drawShotLineInViewport."
  },
  {
   "addr": "8512",
   "length": 6,
   "type": "byteTable",
   "name": "comSpeedDisplayTable",
   "description": "01 02 02 02 00 03: COMSPEED value printed on the STATS tab for comcen speed class 0-5 (class 4 = immobile shows 0)."
  }
 ],
 "misclassified": [
  {
   "addr": "820F",
   "length": 16,
   "actual": "code",
   "evidence": "Bytes A9 07 20 9A C8 AD 9B 0B 30 05 A9 20 20 66 C5 60 = LDA #7 / JSR $C89A / LDA $0B9B / BMI +5 / LDA #$20 / JSR $C566 / RTS; reached by JSR $820F from the (also data-hidden) cmdDroneLaunch handler at $53AF (bytes 20 0F 82). It is the 'DRONE ALERT!' notification."
  }
 ],
 "insights": [
  "Overlay B screen 2 (currentScreenId 2, F5 key) is the DRONE screen: entry L_7FE6 from the screen switcher sub_06BA ($06FC), re-entry L_7FF6 from the knock-out sequence ($076A). Layout: left panel with a red aircraft silhouette (sprite 5, shapes from the RLE set at $FE24 = 8 headings clockwise from north) overlaid by a white heading line with a dark shadow (sprites 0 and 3, line drawn into $8AC0 and copied to $8A00), a three-digit fuel read-out (sub_6F99), the drones-left digit (sub_7036, box at row 5 column 33), a 14x12-character camera window at row 6 column 13 (one character per map cell, redrawn by sub_741C, reticle sprite 2 from $FF93 at X $AC Y $88), and two 2x2 buttons at column 34: LAUNCH (row 12) and STRIKE (row 16). Strings 'DRONE','LAUNCH','STRIKE','FIRE','DRONE ALERT' follow the chunk at $868C.",
  "Drone flight protocol as seen from this screen: fire -> $97 (launch), then sendOutgoingPacket emits $8D heading,col,row every exchange while $920D bit 6 is set (steering toward joyDirToHeadingTable[zp_5E] one 45-degree step per exchange in $731E), fuel $9220 counts 100 exchanges; fire again -> $8E col,row (detonate, bit 5 pending until the own command executes); fuel out / game over -> $8F (released). The drone arriving ($92, cleared bit 7) or a remote detonation ends the flight without $8F. The camera advances only when exchangeStepFlag $920E bit 7 is set, i.e. one step per lock-step exchange, keeping both machines in sync.",
  "The comcen systems table comcenSystemStatus $9241-$9244 maps to: [1] $9242 overview drawing, [2] $9243 screen 1 (scanner) events, [3] $9244 the drone system. With $9244 = $80 only one launch press in four is accepted (random & 3) and half of the camera steps are replaced by static (fillDroneViewWindow $AA).",
  "drawShotLineInViewport ($82BF) is the routine that draws shots inside the 7x5-cell zoomed battlefield viewport (2x2 characters per cell, characters row 1-10, columns 25-38, fat-pixel origin X=100,Y=4, 8 fat pixels per cell). It is the only user of plotFatPixel ($FC58). Line: gap of 4 steps at the shooter, a 4-pixel diamond, then a solid (or for Boomers dotted) white line; the viewport colour nibbles are forced to white first and viewRedrawTimer $9234 = $8F schedules the erase 15 frames later. Clipping excludes relative coordinate 0 (left column / top row of the window are never plotted).",
  "The comcen schematic on the REPAIR tab is an RLE-compressed 20x10 character hires bitmap at $7C6E-$7FCD (same stream format as unpackRleTo0200: first byte = marker, marker,count,value runs, count 0 = 256) unpacked by drawComcenSchematic ($8632) directly into the bitmap at row 10 column 13, 160 bytes per character row with a 320-byte row stride; it shares fetchRleByte ($681A) and rleMarkerByte ($9246) with the main program's unpacker.",
  "The high-memory block labelled unknownShapeTail at $FF90/$FF93 in the FBB8 survey is actually the 64-byte drone camera reticle sprite copied to sprite 2 ($8A80) by launchDrone ($80EB).",
  "Many sub_/L_/D_ labels inside $7C6E-$868B (sub_7CA2, sub_7CAE, sub_7CBF, sub_7D70, sub_7D7D, sub_7D8B, sub_7D98, sub_7DA7, sub_7E4F, sub_7E74, sub_7EA0, sub_7F3F, sub_7FA6, sub_7FCB, sub_7FDD, sub_8143, sub_81B6, sub_81F4, sub_8364, sub_84EC, sub_8511, sub_85DB, sub_860B, D_81C8, D_829E, D_8319, D_844F, D_846A, D_8493, D_85EC, D_8622...) are artefacts of XREF merging both overlay variants: they are referenced only by variant A code and are plain data bytes or mid-instruction addresses in variant B. Likewise the main program's JMP L_7DB8 ($0C9D), JMP L_7DE3 ($1C78) and JSR/JMP sub_8658 ($25C4/$6002) target overlay A code; in variant B those addresses are inside the compressed picture / the bitmap unpacker.",
  "STATS tab layout (drawStatsTab $8518, text window left 10): rows 9-12 GRUNTS/RIDERS/BOOMERS/SPIES counts, 14 DRONES, 15 MISSILES, 17 COMSPEED, 18 TER PTS, 19 RECYCLER, 20 MAP ID; column 9 = US (own side), column 14 = THEM. Unit counting scans the 50 slots downward from each comcen (49 / 99), so the comcen is the highest-numbered unit of each side. Numbers are printed right-aligned in 3 columns by printNumberRightAligned ($8601), which uses the $C000 overlay routines $C0EF (print A fill characters zp_3E) and $C17D (print decimal number)."
 ]
}