{
 "chunk": "game_main_0800_1FA6",
 "unit": "game/main_0800",
 "routines": [
  {
   "addr": "1FC3",
   "name": "enterMapScreen",
   "summary": "Entry used by the game-mode dispatcher (sub_06BA, mode 0): restores the viewport origin from savedViewportX/Y ($92C3/$92C4), redraws the whole game screen (sub_2639) and falls into runMapMainLoop.",
   "inputs": "D_92C3/D_92C4 saved viewport origin",
   "outputs": "zp_A5/zp_A6 viewport origin; screen redrawn; does not return (falls into sub_1FD0)",
   "confidence": "high"
  },
  {
   "addr": "1FD0",
   "name": "runMapMainLoop",
   "summary": "Main interactive loop of the battlefield screen. Initialises cursor state (clears last key, info-panel unit, path marker, target mode), loads the cursor sprites, then loops (L_2018): saves the viewport origin, runs the per-frame housekeeping (sub_40B2), shows the unit info panel when the joystick rests on an own unit for 15+ frames, selects a unit (sub_5E17) on a fire click (or, during the setup phase, sends command $91 to pick the unit up), and scrolls the viewport with joystick auto-repeat (zp_6C timer, sub_0D49, sub_20F4). In the setup phase (zp_B1 != 0) it calls the overlay-A hook sub_8658 each pass and returns when zp_66 == 0 or sub_C470 reports a stop; otherwise sub_06BA handles game-mode switches.",
   "inputs": "zp_A5/zp_A6, zp_B1 game phase, zp_5D joystick, zp_66, D_90EE idle counter, D_0B9B, D_92C9, D_92FC, D_9236, D_9245, D_92F3, branch opcodes at $206B/$2084 patched for the local side",
   "outputs": "D_92C3/D_92C4, D_90F8 selected unit, D_9248, D_91D7, D_24E1, zp_BB, zp_41, D_9209, D_91CC, zp_3D, D_9204/D_9205, D_92A1, zp_7B, zp_6C, D_0B7D, zp_73",
   "confidence": "high"
  },
  {
   "addr": "20F4",
   "name": "scrollViewportByDirection",
   "summary": "Converts a direction code 0-7 (from joystickDirectionTable) into dx/dy using directionDeltaTable and diagonalDirectionTable (diagonals also stretch the repeat timer zp_6C by 1.5x) and scrolls the viewport by one cell via sub_1CD3. When zp_BB > 0 (range-limited target mode) the move is only allowed if the new cursor cell stays within the selected unit's range (sub_3870 distance vs. D_35B0/D_35BB tables indexed by the terrain under the unit). Afterwards sets the fire debounce timer zp_6D = 10 and disarms the fire button (zp_79 = 0).",
   "inputs": "A = direction code 0-7, zp_6C, zp_BB, zp_A5/zp_A6, D_90F8, D_FAF0 terrain under unit",
   "outputs": "viewport scrolled (zp_A5/zp_A6 changed by sub_1CD3), zp_6C, zp_6D = 10, zp_79 = 0, zp_22/zp_24/zp_27/zp_28 scratch",
   "confidence": "high"
  },
  {
   "addr": "2185",
   "name": "runTargetCursorLoop",
   "summary": "Lets the player move the cursor to choose a target cell after a command was chosen (called from 4BE1, 5F64, 650A, 83FF). Picks the cursor shapes according to zp_BB (>0 crosshair sprite 0 via sub_2FF3; 0 normal marker, or the group zoom view sub_30C0 when D_9209 >= 2; <0 marker) and loops: housekeeping, path preview toward the cursor after 90 idle frames, joystick scrolling, and on a fire click executes the command: zp_BB == 0 -> sub_347F (move, D_9209 < 2) or sub_4A35; zp_BB > 0 -> sub_3C20 (attack); zp_BB < 0 -> just stores the cursor cell in D_91D8/D_91D9 and returns immediately. Normal exits recenter the viewport on the unit (sub_258F), reset zp_BB/D_920A, clear sprites 0/1 and restore the cursor frame.",
   "inputs": "zp_BB target mode, D_9209, D_90F8, zp_B1, D_90FC, D_90EE, zp_5D, zp_6C",
   "outputs": "D_91D8/D_91D9 (zp_BB < 0), zp_BB = 0, D_920A = 0, D_91D7 = $FF, D_91CC = 0, sprites 0/1 cleared, D_9023/D_9024 bits 0-1 cleared",
   "confidence": "high"
  },
  {
   "addr": "2264",
   "name": "previewPathToCursor",
   "summary": "Idle-time path preview: if the cursor is not on the selected unit, resets the path marker to the unit and repeatedly animates the mini-map marker (sprite 1) from the unit toward the cursor cell (sub_24F3) until sub_2367 reports user input; then hides sprite 1.",
   "inputs": "D_90F8, zp_A5/zp_A6, D_F640/D_F6A4, D_F76C",
   "outputs": "zp_94/zp_95 target, sprite 1 moved/hidden",
   "confidence": "high"
  },
  {
   "addr": "22A2",
   "name": "showUnitInfoPanel",
   "summary": "Displays the info panel (rows 12-20, cols 25-38) for unit D_90F8: unit type name from the 8-byte string table at $C964 (GRUNT/RIDER/BOOMER/SPY/COMCEN), unit number, the labels GROUP/ENERGY/WEAPON/ACTION ($CAE9), the status values (sub_2384) and the unit picture sprites (sub_CD0A) if available. Then waits while the cursor stays on the unit, refreshing the status when the unit is in combat (== D_90FA or D_91F0) or D_920E bit 7 is set, animating the destination marker, until sub_2367 or a cursor move ends it. Clears the panel and sprites 5-7 on exit.",
   "inputs": "D_90F8, D_9209 (text colour via D_5E14), unit arrays, D_90FC, D_90FA, D_91F0, D_920E, D_9205, zp_BB, zp_B1",
   "outputs": "D_91D7 (unit shown, $FF on exit), D_90EF = 0, zp_3D, D_920E cleared, D_9022 bits 5-7 cleared, text window reset",
   "confidence": "high"
  },
  {
   "addr": "2367",
   "name": "checkUserInterrupt",
   "summary": "Returns C = 0 while a passive display may continue: requires sub_C451 != 0 (no comm/key interruption), zp_66 negative (no pending game-mode change), joystick centred and no fresh fire click (sub_0EEA). Otherwise returns C = 1 and sets the joystick lockout timer D_0B7D = 30 frames.",
   "inputs": "zp_66, zp_5D, zp_5F/zp_79",
   "outputs": "C flag, D_0B7D = $1E when interrupted",
   "confidence": "high"
  },
  {
   "addr": "2384",
   "name": "printUnitStatusLines",
   "summary": "Prints the value column of the info panel (col 31, rows 13..) for unit D_90F8: group name (ALPHA..GROVER from FB54 bits 2-4 when bit 6 set, else '-'), energy percent ((F9C4 & $3F)*2), weapon state (READY/RELOAD/FIRED, '-' for SPY/COMCEN, STUN'D when F960 & $0F; blinks via zp_6F), action (-/REPAIR/MOVE/PINNED/STUN'D/DIGGIN from F7D0/F834/FA28/FB54/F960), then BLITZ/CLOAKED/DUG IN flags (F8FC/F898/F960 bit 7) and '*' markers for F76C bits 3-4.",
   "inputs": "D_90F8, D_F76C, D_F7D0, D_F834, D_F898, D_F8FC, D_F960, D_F9C4, D_FA28, D_FB54, D_90FA, D_91CB, zp_6F",
   "outputs": "text printed, zp_3B/zp_3C/zp_3F/zp_40 text window changed, zp_6F = 20 when the fired-blink starts, zp_18/zp_19 scratch",
   "confidence": "high"
  },
  {
   "addr": "24E4",
   "name": "animatePathMarkerToDestination",
   "summary": "If unit D_90F8 has a destination (F7D0 bit 7 clear) sets it as the line target (zp_95/zp_94) and falls into animatePathMarkerStep; otherwise hides sprite 1.",
   "inputs": "D_90F8, D_F7D0, D_F834",
   "outputs": "zp_94/zp_95, sprite 1",
   "confidence": "high"
  },
  {
   "addr": "24F3",
   "name": "animatePathMarkerStep",
   "summary": "One animation step of the mini-map path marker (sprite 1): only when the player has been idle >= 90 frames and the zp_6C timer expired; advances the marker point (D_24E2/D_24E3) one Bresenham step (sub_FBB8/sub_FBFA) toward zp_95/zp_94 every 4 frames and positions sprite 1 there; at the end of the line decrements D_24E1 and blinks the sprite, after 5 passes resets the marker to the unit (sub_2540).",
   "inputs": "D_90EE, zp_6C, D_24E1/D_24E2/D_24E3, zp_94/zp_95 target",
   "outputs": "zp_92/zp_93, D_24E2/D_24E3, zp_6C = 4, sprite 1 position/enable (D_9011/D_9019/D_9022)",
   "confidence": "high"
  },
  {
   "addr": "2540",
   "name": "resetPathMarker",
   "summary": "Hides sprite 1, resets the path marker to the selected unit's position and reloads sprite 1's image: the grey 4x4 block shape (sub_2FFA with D_91CC = 1) in normal mode, or a copy of the sprite 0 cursor box in the zoomed mini-map mode (D_920A bit 7).",
   "inputs": "D_90F8, D_920A",
   "outputs": "D_91CC = 0, sprite 1 image/colour ($8A40, D_9009), D_24E1-D_24E3",
   "confidence": "high"
  },
  {
   "addr": "256F",
   "name": "resetPathMarkerToUnit",
   "summary": "Sets the path marker repeat counter D_24E1 = 5 and the marker point D_24E2/D_24E3 to the selected unit's map position, then falls into positionMarkerSprite.",
   "inputs": "D_90F8, D_F640, D_F6A4",
   "outputs": "D_24E1 = 5, D_24E2/D_24E3, sprite 1 position",
   "confidence": "high"
  },
  {
   "addr": "2585",
   "name": "positionMarkerSprite",
   "summary": "Converts map cell X,Y to mini-map sprite coordinates (sub_3023: x*4+$1F, y*4+$39, or the zoomed mapping) and stores them as sprite 1 position shadows.",
   "inputs": "X = map column, Y = map row",
   "outputs": "D_9011 (sprite 1 X), D_9019 (sprite 1 Y)",
   "confidence": "high"
  },
  {
   "addr": "258F",
   "name": "recenterViewportOnUnit",
   "summary": "If the cursor cell (A5+3, A6+2) is not the selected unit's position, scrolls the viewport step by step (sub_1D4E with A = 1) until the unit is under the cursor.",
   "inputs": "D_90F8, zp_A5/zp_A6",
   "outputs": "zp_92/zp_93 (start point), viewport scrolled",
   "confidence": "high"
  },
  {
   "addr": "25B7",
   "name": "consumeSpaceKey",
   "summary": "If the last key code D_9248 is $A0 (space) clears it to $FF so it is not processed again after the info panel.",
   "inputs": "D_9248",
   "outputs": "D_9248",
   "confidence": "medium"
  },
  {
   "addr": "25C4",
   "name": "callSetupOverlayHook",
   "summary": "Trampoline to sub_8658 in overlay variant A (setup-phase key handler that toggles D_92F3 bit 7). Only valid while overlay A is resident; in overlay B $8658 is the middle of an instruction.",
   "inputs": "zp_B1 must be nonzero (setup phase)",
   "outputs": "C flag from sub_8658",
   "confidence": "high"
  },
  {
   "addr": "25C7",
   "name": "getViewportOriginPointer",
   "summary": "Sets zp_48/zp_49 to the map address of the viewport's top-left cell (zp_A5, zp_A6), correctly handling negative origins (viewport partly outside the 40x40 map) by subtracting 40 per negative row and adding the negative column.",
   "inputs": "zp_A5, zp_A6, D_BFB0/D_BFD8 map row tables",
   "outputs": "zp_48/zp_49, A = map byte (only meaningful when in range), Y = 0",
   "confidence": "high"
  },
  {
   "addr": "25E3",
   "name": "getViewportRowPointer",
   "summary": "Sets zp_48/zp_49 to the map address of cell (zp_A5, Y); if zp_A5 is negative uses column 0 and adds the signed offset afterwards.",
   "inputs": "Y = map row, zp_A5",
   "outputs": "zp_48/zp_49, A = map byte, Y = 0",
   "confidence": "high"
  },
  {
   "addr": "25FA",
   "name": "readMapCell",
   "summary": "Generic map read: zp_48/zp_49 = rowTable[Y] + X and returns the map byte at column X, row Y of the 40x40 map at $F000. Called from ~25 places including the overlays and the comm module.",
   "inputs": "X = map column 0-39, Y = map row 0-39, D_BFB0/D_BFD8 row address tables (built at $0345/$034A)",
   "outputs": "A = map byte (bit 7 set = unit, low 7 bits = unit index; else terrain code, $40 = blank), zp_48/zp_49 pointer, Y = 0",
   "confidence": "high"
  },
  {
   "addr": "260D",
   "name": "readCellUnderCursor",
   "summary": "Reads the map cell at the cursor, which is always the centre of the 7x5 viewport: (zp_A5+3, zp_A6+2).",
   "inputs": "zp_A5, zp_A6",
   "outputs": "A = map byte, X/Y = cursor column/0, zp_48/zp_49",
   "confidence": "high"
  },
  {
   "addr": "261C",
   "name": "clearInfoPanel",
   "summary": "Sets the info-panel text window (sub_2622) and clears it with the text engine's window clear (sub_C06A).",
   "inputs": "zp_3D text colour, zp_3E fill character",
   "outputs": "zp_39-zp_3C window, zp_3F/zp_40 cursor, panel area cleared",
   "confidence": "high"
  },
  {
   "addr": "2622",
   "name": "setInfoPanelWindow",
   "summary": "Resets the text window to full screen (sub_C0D7) then defines the info-panel window: left column 25 (zp_3B), width 14 (zp_3C), top row 12 (zp_39), bottom row 21 (zp_3A).",
   "inputs": "-",
   "outputs": "zp_39 = $0C, zp_3A = $15, zp_3B = $19, zp_3C = $0E",
   "confidence": "high"
  },
  {
   "addr": "2636",
   "name": "clearScreenAndRedrawGameScreen",
   "summary": "Clears the bitmap screen to grey (sub_2D41) and falls into redrawGameScreen. Called at start-up ($0A95) and from overlay A.",
   "inputs": "-",
   "outputs": "whole screen",
   "confidence": "high"
  },
  {
   "addr": "2639",
   "name": "redrawGameScreen",
   "summary": "Rebuilds the battlefield screen: patches the own-side branches for the local player (sub_2A08), sets VIC D016 shadow $D8 (multicolour), clears the full-screen text window, zeroes D_2A29-D_2A2C, sets background colour 7, draws the three panel frames (mini-map, viewport, info panel) and the bottom status bar (sub_28E1); unless zp_B1 bit 7 is set draws the strategic mini-map (sub_5AD7) and, unless zp_B1 bit 6 is set, the viewport (sub_26C1); finally redraws the status-bar message (L_C5D7).",
   "inputs": "zp_B1, D_90FB, map, zp_7B",
   "outputs": "D_9001 = $D8, D_9004 = 7, D_9226 = 0, zp_3D, D_2A29-D_2A2C = 0, screen",
   "confidence": "high"
  },
  {
   "addr": "268F",
   "name": "drawMiniMapFrame",
   "summary": "Draws panel 0: the 22x22 character frame at column 0, row 0 around the strategic mini-map, in colour $0C.",
   "inputs": "-",
   "outputs": "zp_3D = $0C, X = 0 -> sub_2C7F",
   "confidence": "high"
  },
  {
   "addr": "2698",
   "name": "drawViewportFrame",
   "summary": "Draws panel 1: the 16x12 frame at column 24, row 0 around the tactical viewport, then falls into clearViewportTextArea.",
   "inputs": "-",
   "outputs": "zp_3D, frame drawn, viewport interior cleared",
   "confidence": "high"
  },
  {
   "addr": "26A1",
   "name": "clearViewportTextArea",
   "summary": "Defines the viewport interior as a text window (rows 1-10, column 25, width 14, colour 0) and clears it.",
   "inputs": "-",
   "outputs": "zp_39 = 1, zp_3A = $0B, zp_3B = $19, zp_3C = $0E, zp_3D = 0, area cleared",
   "confidence": "high"
  },
  {
   "addr": "26B8",
   "name": "drawInfoPanelFrame",
   "summary": "Draws panel 2: the 16x11 frame at column 24, row 11 around the unit info panel.",
   "inputs": "-",
   "outputs": "zp_3D = $0C, X = 2 -> sub_2C7F",
   "confidence": "high"
  },
  {
   "addr": "26C1",
   "name": "drawViewport",
   "summary": "Draws the 7x5-cell tactical viewport (each map cell = 2x2 characters = 16x16 pixels in the multicolour bitmap) into character rows 1-10, columns 25-38. Walks the map from the viewport origin (zp_A5, zp_A6) row by row, builds each cell's 32-byte tile in $04C8 with sub_2772 (terrain, units, highlight) and copies it to the bitmap address from sub_2A9F (second character row at +$140). Preserves zp_18/zp_19 and the viewport origin (D_90F6/D_90F7).",
   "inputs": "zp_A5/zp_A6 viewport origin, map at $F000, unit arrays",
   "outputs": "bitmap/colour RAM updated, zp_2F/zp_31 counters, zp_48/zp_49, zp_B4-zp_B7, D_2A2D/D_2A2E, D_90F6/D_90F7",
   "confidence": "high"
  }
 ],
 "variables": [
  {
   "addr": "A5",
   "scope": "zp",
   "name": "viewportOriginX",
   "meaning": "Map column of the viewport's top-left cell (may be negative near the left edge). The cursor cell is viewportOriginX + 3.",
   "confidence": "high"
  },
  {
   "addr": "A6",
   "scope": "zp",
   "name": "viewportOriginY",
   "meaning": "Map row of the viewport's top-left cell; cursor row = viewportOriginY + 2. Restricted to rows 20-39 (own half) while zp_B1 != 0 (sub_1CC1).",
   "confidence": "high"
  },
  {
   "addr": "5D",
   "scope": "zp",
   "name": "joystickDirection",
   "meaning": "CIA1 port A bits 0-3 (active low) sampled by the raster IRQ at $1212; $0F = centred. Indexes joystickDirectionTable.",
   "confidence": "high"
  },
  {
   "addr": "5F",
   "scope": "zp",
   "name": "isFireReleased",
   "meaning": "1 = fire button not pressed, 0 = pressed (CIA1 port A bit 4), written by the IRQ at $120E.",
   "confidence": "high"
  },
  {
   "addr": "79",
   "scope": "zp",
   "name": "fireButtonArmed",
   "meaning": "Set when the fire button was seen released; a press only counts as a click when armed (sub_0EB1/sub_0EEA). Cleared after each viewport scroll step.",
   "confidence": "high"
  },
  {
   "addr": "66",
   "scope": "zp",
   "name": "requestedGameMode",
   "meaning": "Last key / game-mode request written by the IRQ key handler at $0E80/$0EA1: function keys F1/F3/F5/F7 give 0-3, which sub_06BA dispatches as game modes (0 = battlefield map, 1/2 = overlay screens, 3 = $141D); any other key (>= $80) or $FF means no mode change. zp_66 == 0 (F1) also ends the setup-phase main loop ('PRESS F 1 WHEN DONE').",
   "confidence": "medium"
  },
  {
   "addr": "6C",
   "scope": "zp",
   "name": "scrollRepeatTimer",
   "meaning": "Frame countdown (decremented by the IRQ at $123D, first of six timers zp_6C-zp_71) gating joystick auto-repeat scrolling and the path-marker step rate.",
   "confidence": "high"
  },
  {
   "addr": "6D",
   "scope": "zp",
   "name": "fireDebounceTimer",
   "meaning": "Frame countdown; clicks are ignored while nonzero (set to 10 after a click or a scroll step).",
   "confidence": "high"
  },
  {
   "addr": "6E",
   "scope": "zp",
   "name": "autoScrollDelayTimer",
   "meaning": "Frame countdown used by sub_1D4E to pace the animated viewport scroll toward a unit.",
   "confidence": "medium"
  },
  {
   "addr": "6F",
   "scope": "zp",
   "name": "weaponLineBlinkTimer",
   "meaning": "Frame countdown (20) during which the WEAPON line of the info panel is left blank, making 'FIRED' blink.",
   "confidence": "medium"
  },
  {
   "addr": "73",
   "scope": "zp",
   "name": "commandUnitArg",
   "meaning": "Unit index argument of a unit command sent with sub_561F/sub_560A (bit 7 = whole group).",
   "confidence": "medium"
  },
  {
   "addr": "7B",
   "scope": "zp",
   "name": "statusBarMessage",
   "meaning": "Index of the message shown in the bottom status bar by L_C5D7 (table $0500/$0524).",
   "confidence": "medium"
  },
  {
   "addr": "B1",
   "scope": "zp",
   "name": "gamePhaseFlags",
   "meaning": "0 = battle running (full map, mode dispatcher active); nonzero = pre-battle unit setup / other phases (viewport limited to the own half, overlay-A hook sub_8658 called, message 10 'SETUP UNITS. PRESS F 1 WHEN DONE.'). Bit 7 suppresses the mini-map redraw, bit 6 suppresses viewport/unit drawing.",
   "confidence": "medium"
  },
  {
   "addr": "BB",
   "scope": "zp",
   "name": "cursorTargetMode",
   "meaning": "0 = normal browsing; > 0 = choosing a target limited to the unit's range (crosshair, sub_3C20 on click); < 0 = free cell pick that returns the cell in D_91D8/D_91D9.",
   "confidence": "medium"
  },
  {
   "addr": "41",
   "scope": "zp",
   "name": "screenClearedFlag",
   "meaning": "Set to 1 by the screen clear (sub_2D41) and 0 by the main-loop init; never read anywhere (write-only).",
   "confidence": "low"
  },
  {
   "addr": "48",
   "scope": "zp",
   "name": "mapCellPtr",
   "meaning": "16-bit pointer (zp_48/zp_49) to a cell of the 40x40 map at $F000, set by readMapCell and friends.",
   "confidence": "high"
  },
  {
   "addr": "49",
   "scope": "zp",
   "name": "mapCellPtrHi",
   "meaning": "High byte of mapCellPtr.",
   "confidence": "high"
  },
  {
   "addr": "91",
   "scope": "zp",
   "name": "lineStepsLeft",
   "meaning": "Remaining steps of the Bresenham line started by sub_FBB8 (max(|dx|,|dy|)).",
   "confidence": "high"
  },
  {
   "addr": "92",
   "scope": "zp",
   "name": "lineCurrentY",
   "meaning": "Current Y of the Bresenham line (sub_FBB8/sub_FBFA).",
   "confidence": "high"
  },
  {
   "addr": "93",
   "scope": "zp",
   "name": "lineCurrentX",
   "meaning": "Current X of the Bresenham line.",
   "confidence": "high"
  },
  {
   "addr": "94",
   "scope": "zp",
   "name": "lineTargetY",
   "meaning": "Target Y of the Bresenham line.",
   "confidence": "high"
  },
  {
   "addr": "95",
   "scope": "zp",
   "name": "lineTargetX",
   "meaning": "Target X of the Bresenham line.",
   "confidence": "high"
  },
  {
   "addr": "18",
   "scope": "zp",
   "name": "scratchRow",
   "meaning": "General scratch; for sub_2A9F/sub_26C1 it is the character row (1..10 in the viewport).",
   "confidence": "medium"
  },
  {
   "addr": "19",
   "scope": "zp",
   "name": "scratchCol",
   "meaning": "General scratch; for sub_2A9F/sub_26C1 it is the character column (25..37 in the viewport).",
   "confidence": "medium"
  },
  {
   "addr": "2F",
   "scope": "zp",
   "name": "viewportCellRow",
   "meaning": "Row counter 0-4 while drawing the viewport.",
   "confidence": "high"
  },
  {
   "addr": "31",
   "scope": "zp",
   "name": "viewportCellCol",
   "meaning": "Column counter 0-6 while drawing the viewport (also the index into the map row).",
   "confidence": "high"
  },
  {
   "addr": "B4",
   "scope": "zp",
   "name": "bitmapDestPtr",
   "meaning": "Bitmap address of the current character cell (zp_B4/zp_B5), from sub_2A9F.",
   "confidence": "high"
  },
  {
   "addr": "B5",
   "scope": "zp",
   "name": "bitmapDestPtrHi",
   "meaning": "High byte of bitmapDestPtr.",
   "confidence": "high"
  },
  {
   "addr": "B6",
   "scope": "zp",
   "name": "screenColorPtr",
   "meaning": "Screen-RAM (bitmap colour) address of the current character cell (zp_B6/zp_B7).",
   "confidence": "high"
  },
  {
   "addr": "B7",
   "scope": "zp",
   "name": "screenColorPtrHi",
   "meaning": "High byte of screenColorPtr.",
   "confidence": "high"
  },
  {
   "addr": "39",
   "scope": "zp",
   "name": "textWindowTop",
   "meaning": "Text engine ($C000 overlay): top row of the text window.",
   "confidence": "high"
  },
  {
   "addr": "3A",
   "scope": "zp",
   "name": "textWindowBottom",
   "meaning": "Text engine: row limit (exclusive) used by window clears.",
   "confidence": "high"
  },
  {
   "addr": "3B",
   "scope": "zp",
   "name": "textWindowLeft",
   "meaning": "Text engine: left column of the text window.",
   "confidence": "high"
  },
  {
   "addr": "3C",
   "scope": "zp",
   "name": "textWindowWidth",
   "meaning": "Text engine: window width in characters (wraps when textCursorCol reaches it).",
   "confidence": "high"
  },
  {
   "addr": "3D",
   "scope": "zp",
   "name": "textColor",
   "meaning": "Text engine: colour written for printed characters.",
   "confidence": "medium"
  },
  {
   "addr": "3E",
   "scope": "zp",
   "name": "textFillChar",
   "meaning": "Text engine: character used to clear lines/windows.",
   "confidence": "medium"
  },
  {
   "addr": "3F",
   "scope": "zp",
   "name": "textCursorCol",
   "meaning": "Text engine: cursor column relative to textWindowLeft.",
   "confidence": "high"
  },
  {
   "addr": "40",
   "scope": "zp",
   "name": "textCursorRow",
   "meaning": "Text engine: absolute cursor row.",
   "confidence": "high"
  },
  {
   "addr": "43",
   "scope": "zp",
   "name": "textRawMode",
   "meaning": "Text engine: bit 7 set = print character codes without the $987E translation.",
   "confidence": "medium"
  },
  {
   "addr": "22",
   "scope": "zp",
   "name": "distPointAX",
   "meaning": "X of point A for the distance routine sub_3870 (here the prospective cursor cell).",
   "confidence": "medium"
  },
  {
   "addr": "24",
   "scope": "zp",
   "name": "distPointAY",
   "meaning": "Y of point A for sub_3870.",
   "confidence": "medium"
  },
  {
   "addr": "27",
   "scope": "zp",
   "name": "distPointBX",
   "meaning": "X of point B for sub_3870 (here the selected unit).",
   "confidence": "medium"
  },
  {
   "addr": "28",
   "scope": "zp",
   "name": "distPointBY",
   "meaning": "Y of point B for sub_3870.",
   "confidence": "medium"
  },
  {
   "addr": "45",
   "scope": "zp",
   "name": "frameCounter",
   "meaning": "24-bit frame counter (zp_45/zp_46/zp_47) incremented by the raster IRQ at $11D1.",
   "confidence": "high"
  },
  {
   "addr": "0B7D",
   "scope": "abs",
   "name": "joystickLockoutTimer",
   "meaning": "Frame countdown; while nonzero the IRQ does not re-read the joystick (keeps old zp_5D/zp_5F). Set to 60 after selections, 30 by checkUserInterrupt.",
   "confidence": "high"
  },
  {
   "addr": "0B9B",
   "scope": "abs",
   "name": "playModeFlags",
   "meaning": "Bit 7 = solo/practice mode (F8 cycles $80-$82; digits 1-9 set a speed in sub_40B2); bit 0 = local player side, used by sub_2A08 and overlay A ($7BE6) to patch the bcc/bcs opcodes that decide whether unit indices < 50 or >= 50 are 'ours'.",
   "confidence": "medium"
  },
  {
   "addr": "1FA6",
   "scope": "abs",
   "name": "joystickDirectionTable",
   "meaning": "16-entry table joystick nibble -> direction code: $FF invalid, 0 = N, 1 = S, 2 = E, 3 = W, 4 = NW, 5 = NE, 6 = SE, 7 = SW.",
   "confidence": "high"
  },
  {
   "addr": "206B",
   "scope": "abs",
   "name": "ownSideBranchA",
   "meaning": "Opcode byte of the 'bcs' after 'cmp #$32' (unit index < 50 = own side); patched to $90/$B0 by overlay A ($7BE6) for player 2/1. Also copied to $750A at $10A2.",
   "confidence": "high"
  },
  {
   "addr": "2084",
   "scope": "abs",
   "name": "ownSideBranchB",
   "meaning": "Second patched own-side branch opcode (unit selection on fire click), patched at $7BE9.",
   "confidence": "high"
  },
  {
   "addr": "24E1",
   "scope": "abs",
   "name": "pathMarkerRepeatCount",
   "meaning": "Number of remaining unit-to-destination marker passes (starts at 5, then the marker blinks).",
   "confidence": "high"
  },
  {
   "addr": "24E2",
   "scope": "abs",
   "name": "pathMarkerX",
   "meaning": "Current map X of the animated mini-map path marker.",
   "confidence": "high"
  },
  {
   "addr": "24E3",
   "scope": "abs",
   "name": "pathMarkerY",
   "meaning": "Current map Y of the animated mini-map path marker.",
   "confidence": "high"
  },
  {
   "addr": "90EE",
   "scope": "abs",
   "name": "idleFrameCounter",
   "meaning": "Frames since the joystick was last moved or fire pressed (IRQ $1227-$1235, saturates at $FF). 15 frames -> info panel, 90 frames -> path preview.",
   "confidence": "high"
  },
  {
   "addr": "90EF",
   "scope": "abs",
   "name": "textHighlightMode",
   "meaning": "Text engine flag: when nonzero strings are printed with the zp_42 colour (sub_C137); cleared before printing the info panel.",
   "confidence": "low"
  },
  {
   "addr": "90F4",
   "scope": "abs",
   "name": "lineStepY",
   "meaning": "Y increment (-1/0/+1) of the last Bresenham step (sub_FBFA).",
   "confidence": "high"
  },
  {
   "addr": "90F5",
   "scope": "abs",
   "name": "lineStepX",
   "meaning": "X increment (-1/0/+1) of the last Bresenham step.",
   "confidence": "high"
  },
  {
   "addr": "90F6",
   "scope": "abs",
   "name": "viewportOriginXSave",
   "meaning": "Viewport origin X saved by drawViewport while it walks zp_A5 through the cells.",
   "confidence": "high"
  },
  {
   "addr": "90F7",
   "scope": "abs",
   "name": "viewportOriginYSave",
   "meaning": "Viewport origin Y saved by drawViewport.",
   "confidence": "high"
  },
  {
   "addr": "90F8",
   "scope": "abs",
   "name": "selectedUnit",
   "meaning": "Index 0-99 of the unit currently under the cursor / selected for commands ($F0 = none in some phases).",
   "confidence": "high"
  },
  {
   "addr": "90FA",
   "scope": "abs",
   "name": "combatUnitA",
   "meaning": "Unit index involved in the current combat event; the info panel refreshes while the shown unit equals it.",
   "confidence": "low"
  },
  {
   "addr": "90FB",
   "scope": "abs",
   "name": "currentGameMode",
   "meaning": "Game mode currently running (compared with zp_66 by sub_06BA); 0 = battlefield map.",
   "confidence": "medium"
  },
  {
   "addr": "90FC",
   "scope": "abs",
   "name": "inputHoldTimer",
   "meaning": "Frame countdown (IRQ $12E5); while nonzero the per-frame housekeeping/input is skipped in the cursor loops.",
   "confidence": "low"
  },
  {
   "addr": "91C9",
   "scope": "abs",
   "name": "scrollRepeatMask",
   "meaning": "$7F while the joystick is held, 0 after it returns to centre (IRQ $1220); makes the first auto-repeat delay long (16) and later ones short (6) in sub_0D49.",
   "confidence": "medium"
  },
  {
   "addr": "91CA",
   "scope": "abs",
   "name": "scrollRepeatCounter",
   "meaning": "Incremented per scroll step by sub_0D49 and masked with scrollRepeatMask.",
   "confidence": "medium"
  },
  {
   "addr": "91CC",
   "scope": "abs",
   "name": "cursorSpriteSlot",
   "meaning": "Sprite number (0 or 1) that sub_3049/sub_2FFA operate on; normally 0 (cursor box), 1 while loading the marker shape.",
   "confidence": "high"
  },
  {
   "addr": "91D7",
   "scope": "abs",
   "name": "infoPanelUnit",
   "meaning": "Unit index currently shown in the info panel, $FF = none.",
   "confidence": "high"
  },
  {
   "addr": "91D8",
   "scope": "abs",
   "name": "pickedCellX",
   "meaning": "Map X returned by runTargetCursorLoop in free-pick mode (zp_BB < 0).",
   "confidence": "high"
  },
  {
   "addr": "91D9",
   "scope": "abs",
   "name": "pickedCellY",
   "meaning": "Map Y returned by runTargetCursorLoop in free-pick mode.",
   "confidence": "high"
  },
  {
   "addr": "91F0",
   "scope": "abs",
   "name": "combatUnitB",
   "meaning": "Second unit index of the current combat event (see combatUnitA).",
   "confidence": "low"
  },
  {
   "addr": "9001",
   "scope": "abs",
   "name": "vicD016Shadow",
   "meaning": "Shadow of VIC $D016 copied by the IRQ ($1121); $D8 = multicolour bitmap, 40 columns.",
   "confidence": "high"
  },
  {
   "addr": "9004",
   "scope": "abs",
   "name": "vicBackgroundShadow",
   "meaning": "Shadow of VIC $D021 (IRQ $1143).",
   "confidence": "high"
  },
  {
   "addr": "9008",
   "scope": "abs",
   "name": "spriteColorShadow",
   "meaning": "8 sprite colour shadows ($9008-$900F) copied to $D027+ by the IRQ; $9009 = sprite 1 (path marker).",
   "confidence": "high"
  },
  {
   "addr": "9010",
   "scope": "abs",
   "name": "spriteXShadow",
   "meaning": "8 sprite X shadows ($9010-$9017); $9011 = sprite 1.",
   "confidence": "high"
  },
  {
   "addr": "9018",
   "scope": "abs",
   "name": "spriteYShadow",
   "meaning": "8 sprite Y shadows ($9018-$901F); $9019 = sprite 1.",
   "confidence": "high"
  },
  {
   "addr": "9020",
   "scope": "abs",
   "name": "spriteXMsbShadow",
   "meaning": "Shadow of VIC $D010.",
   "confidence": "high"
  },
  {
   "addr": "9022",
   "scope": "abs",
   "name": "spriteEnableShadow",
   "meaning": "Shadow of VIC $D015 (sub_31A3 enables, sub_32EA disables sprite 1).",
   "confidence": "high"
  },
  {
   "addr": "9023",
   "scope": "abs",
   "name": "spriteXExpandShadow",
   "meaning": "Shadow of VIC $D01D.",
   "confidence": "high"
  },
  {
   "addr": "9024",
   "scope": "abs",
   "name": "spriteYExpandShadow",
   "meaning": "Shadow of VIC $D017.",
   "confidence": "high"
  },
  {
   "addr": "9204",
   "scope": "abs",
   "name": "highlightedUnit",
   "meaning": "Unit drawn highlighted in the viewport (compared in sub_2772 at $2878); $FF = none.",
   "confidence": "medium"
  },
  {
   "addr": "9205",
   "scope": "abs",
   "name": "secondaryHighlightUnit",
   "meaning": "Second highlighted unit for the viewport; $FF = none (negative also enables the destination-marker animation in the info panel).",
   "confidence": "low"
  },
  {
   "addr": "9209",
   "scope": "abs",
   "name": "uiLevel",
   "meaning": "0 = browsing the map, 1 = unit selected (command menu), 2+ = group mode (zoomed mini-map). Selects the info-panel text colour via D_5E14.",
   "confidence": "medium"
  },
  {
   "addr": "920A",
   "scope": "abs",
   "name": "miniMapZoomFlags",
   "meaning": "Bit 7 = mini-map shows a zoomed sub-rectangle (group view, set near $319A) defined by D_922E-D_9231; affects sprite mapping (sub_3023) and scroll limits.",
   "confidence": "medium"
  },
  {
   "addr": "920E",
   "scope": "abs",
   "name": "infoPanelRefreshRequest",
   "meaning": "Bit 7 set asks the info panel to reprint the status lines; cleared by showUnitInfoPanel.",
   "confidence": "medium"
  },
  {
   "addr": "9226",
   "scope": "abs",
   "name": "screenStateFlag9226",
   "meaning": "Cleared by redrawGameScreen; read only by overlay code ($740F).",
   "confidence": "low"
  },
  {
   "addr": "922E",
   "scope": "abs",
   "name": "zoomWidth",
   "meaning": "Width of the zoomed mini-map rectangle (set in sub_30C0).",
   "confidence": "low"
  },
  {
   "addr": "922F",
   "scope": "abs",
   "name": "zoomOriginX",
   "meaning": "Left map column of the zoomed mini-map rectangle.",
   "confidence": "low"
  },
  {
   "addr": "9230",
   "scope": "abs",
   "name": "zoomHeight",
   "meaning": "Height of the zoomed mini-map rectangle.",
   "confidence": "low"
  },
  {
   "addr": "9231",
   "scope": "abs",
   "name": "zoomOriginY",
   "meaning": "Top map row of the zoomed mini-map rectangle.",
   "confidence": "low"
  },
  {
   "addr": "9236",
   "scope": "abs",
   "name": "revealedEnemyUnit",
   "meaning": "Unit index that may be inspected/drawn even though it is not ours (set by overlay A $7BF5 and the comm module).",
   "confidence": "low"
  },
  {
   "addr": "9245",
   "scope": "abs",
   "name": "unitInspectLockFlags",
   "meaning": "Bit 7 set blocks inspecting/selecting units other than revealedEnemyUnit.",
   "confidence": "low"
  },
  {
   "addr": "9248",
   "scope": "abs",
   "name": "lastKeyCode",
   "meaning": "Last key pressed ($FF = none); PETSCII with bit 7 set ($A0 space, $B1-$B9 digits, $AA '*'), F1/F3/F5/F7 = $00-$03.",
   "confidence": "medium"
  },
  {
   "addr": "92A1",
   "scope": "abs",
   "name": "statusMessageNumber",
   "meaning": "Number of the status-bar message last requested (10 = 'SETUP UNITS. PRESS F 1 WHEN DONE.').",
   "confidence": "medium"
  },
  {
   "addr": "92C3",
   "scope": "abs",
   "name": "savedViewportX",
   "meaning": "Viewport origin X preserved across screens (saved every main-loop pass, also written by the $EC00 sub-overlay).",
   "confidence": "high"
  },
  {
   "addr": "92C4",
   "scope": "abs",
   "name": "savedViewportY",
   "meaning": "Viewport origin Y preserved across screens.",
   "confidence": "high"
  },
  {
   "addr": "92C9",
   "scope": "abs",
   "name": "pauseTimeoutCounter",
   "meaning": "Counts up every 8 frames while 0 < value < $80 (IRQ $12C1); nonzero blocks the hover info and interrupts displays (sub_C451).",
   "confidence": "low"
  },
  {
   "addr": "92F3",
   "scope": "abs",
   "name": "setupPickupMode",
   "meaning": "Bit 7 = setup-phase toggle (flipped by the '*' key in overlay A sub_8658); when set, clicking an own unit sends command $91 (pick up / relocate unit).",
   "confidence": "low"
  },
  {
   "addr": "92FC",
   "scope": "abs",
   "name": "revealAllUnitsFlag",
   "meaning": "Bit 7 set lets the info panel and viewport show enemy units (film replay / debug style mode).",
   "confidence": "low"
  },
  {
   "addr": "F640",
   "scope": "abs",
   "name": "unitX",
   "meaning": "Unit record array (100 entries, one per unit index 0-99; indices < 50 belong to one side): map column.",
   "confidence": "high"
  },
  {
   "addr": "F6A4",
   "scope": "abs",
   "name": "unitY",
   "meaning": "Unit map row (100 entries).",
   "confidence": "high"
  },
  {
   "addr": "F708",
   "scope": "abs",
   "name": "unitOwnerFlags",
   "meaning": "Unit flags; bit 5 = owning side (compared in sub_3C20), bits 2-6 used as a group mask in sub_40B2.",
   "confidence": "low"
  },
  {
   "addr": "F76C",
   "scope": "abs",
   "name": "unitTypeFlags",
   "meaning": "Bits 0-2 = unit type (0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN), bits 3-4 = '*' status markers, bit 7 = unit dead/absent.",
   "confidence": "high"
  },
  {
   "addr": "F7D0",
   "scope": "abs",
   "name": "unitDestX",
   "meaning": "Destination map column; bit 7 set = no destination.",
   "confidence": "high"
  },
  {
   "addr": "F834",
   "scope": "abs",
   "name": "unitDestY",
   "meaning": "Destination map row; bit 7 set = unit is repairing (info panel shows REPAIR).",
   "confidence": "medium"
  },
  {
   "addr": "F898",
   "scope": "abs",
   "name": "unitCloakFlags",
   "meaning": "Bit 7 = CLOAKED.",
   "confidence": "medium"
  },
  {
   "addr": "F8FC",
   "scope": "abs",
   "name": "unitBlitzFlags",
   "meaning": "Bit 7 = BLITZ mode.",
   "confidence": "medium"
  },
  {
   "addr": "F960",
   "scope": "abs",
   "name": "unitStunDigState",
   "meaning": "Bits 0-3 = stun counter (STUN'D, unit not selectable), bits 5-6 = digging in progress (DIGGIN), bit 7 = DUG IN.",
   "confidence": "medium"
  },
  {
   "addr": "F9C4",
   "scope": "abs",
   "name": "unitEnergy",
   "meaning": "Bits 0-5 = energy in 2% units (info panel prints value*2 '%').",
   "confidence": "high"
  },
  {
   "addr": "FA28",
   "scope": "abs",
   "name": "unitMoveState",
   "meaning": "Movement state/counter: bit 7 or value >= 10 with FB54 bit 1 changes the ACTION line (MOVE/PINNED).",
   "confidence": "low"
  },
  {
   "addr": "FA8C",
   "scope": "abs",
   "name": "unitDrawFlags",
   "meaning": "Bit 7 alters the tile colour of the unit in the viewport (sub_2772 $286F).",
   "confidence": "low"
  },
  {
   "addr": "FAF0",
   "scope": "abs",
   "name": "unitTerrainUnder",
   "meaning": "Map byte (terrain code) saved under the unit; used for movement range/cost look-ups (D_077E -> D_35BB -> D_35B0).",
   "confidence": "high"
  },
  {
   "addr": "FB54",
   "scope": "abs",
   "name": "unitGroupFlags",
   "meaning": "Bit 0 = weapon reloading/fired, bit 1 = moving flag, bits 2-4 = group number (ALPHA..GROVER), bit 6 = member of a group.",
   "confidence": "medium"
  },
  {
   "addr": "BFB0",
   "scope": "abs",
   "name": "mapRowAddrLo",
   "meaning": "40-entry low-byte table of map row addresses ($F000 + 40*row), built by the start-up code at $034A.",
   "confidence": "high"
  },
  {
   "addr": "BFD8",
   "scope": "abs",
   "name": "mapRowAddrHi",
   "meaning": "High bytes of the map row address table (built at $0345).",
   "confidence": "high"
  },
  {
   "addr": "8A00",
   "scope": "abs",
   "name": "spriteData",
   "meaning": "Sprite images, 64 bytes each: $8A00 sprite 0 (mini-map cursor box / crosshair), $8A40 sprite 1 (path marker), $8A80 sprite 2 (expanded viewport cursor frame), $8B40+ unit picture sprites 5-7.",
   "confidence": "high"
  },
  {
   "addr": "04C8",
   "scope": "abs",
   "name": "tileBuildBuffer",
   "meaning": "32-byte buffer where sub_2772 assembles one 2x2-character map tile before drawViewport copies it into the bitmap.",
   "confidence": "high"
  },
  {
   "addr": "90EA",
   "scope": "abs",
   "name": "currentKeyCode",
   "meaning": "Key currently held as reported by $E015 ($FF = none); D_90E9 is the previous value and zp_70 the key repeat timer (IRQ key handler $0E56).",
   "confidence": "medium"
  }
 ],
 "dataBlocks": [
  {
   "addr": "1FA6",
   "length": 12,
   "type": "byteTable",
   "name": "joystickDirectionTable",
   "description": "Indexed by the joystick nibble (zp_5D): FF FF FF FF FF 06 05 02 FF 07 04 03 (continues with FF 01 00 FF at $1FB2, which the disassembler split off): $0E up -> 0 (N), $0D down -> 1 (S), $07 right -> 2 (E), $0B left -> 3 (W), $0A -> 4 (NW), $06 -> 5 (NE), $05 -> 6 (SE), $09 -> 7 (SW); $FF = no/invalid direction. Logically the table is 16 bytes ($1FA6-$1FB5)."
  },
  {
   "addr": "1FB2",
   "length": 17,
   "type": "byteTable",
   "name": "diagonalDirectionTable",
   "description": "Bytes 0-3 (FF 01 00 FF) are really the tail of joystickDirectionTable. Bytes 4-7 (30 20 21 31) give, for direction codes 4-7, (dx index << 4) | dy index into directionDeltaTable: NW = (-1,-1), NE = (+1,-1), SE = (+1,+1), SW = (-1,+1). Bytes 8-15 (01 00 03 02 06 07 04 05) look like an opposite-direction table (N<->S, E<->W, NW<->SE, NE<->SW) but no reference to $1FBA was found; byte 16 is a $00 pad."
  },
  {
   "addr": "20F0",
   "length": 4,
   "type": "byteTable",
   "name": "directionDeltaTable",
   "description": "Signed deltas: index 0 = -1 (dy for N), 1 = +1 (dy for S), 2 = +1 (dx for E), 3 = -1 (dx for W)."
  },
  {
   "addr": "24E1",
   "length": 3,
   "type": "byteTable",
   "name": "pathMarkerState",
   "description": "Variables embedded in code: repeat counter (initial 5), marker X, marker Y of the animated mini-map path marker."
  }
 ],
 "misclassified": [
  {
   "addr": "10F1",
   "length": 801,
   "actual": "code",
   "evidence": "Outside this chunk but in the same unit: $10F1-$1411 is the raster IRQ handler installed by sub_0C66 ($FFFE = $10F1): PHA/TXA/PHA/TYA/PHA, BRK-flag test, $D019 ack, copies the VIC shadow block $9000-$9024 to the VIC (sprites from $9010/$9018/$9008), calls $E000/$E00C (comm), increments the 24-bit frame counter zp_45-47, decrements D_0B7D, reads CIA1 $DC00 into zp_5D (direction) / zp_5F (fire) at $11F7-$1216, maintains idleFrameCounter D_90EE, decrements the six timers zp_6C-zp_71 and several absolute timers ($129C), seeds the RNG ($130A), blinks sprite 0/2 colour ($131B), handles comm status ($1331) and received chat characters ($1399, buffer $9253 shown at $0549). Only $1412/$1413 are data."
  },
  {
   "addr": "0DB7",
   "length": 250,
   "actual": "code",
   "evidence": "Outside this chunk: $0DB7-$0EB0 is code, not data. $0DB7-$0E55 scans the keyboard matrix through CIA1 ($DC00-$DC03) with the decode table at $0D67-$0DB6 (PETSCII|$80 codes, F1/F3/F5/F7 = $00-$03); $0E56 (called from the IRQ at $11E7) calls the comm module entry $E015 to fetch a key, stores it in D_90EA/D_90E9 with a repeat timer zp_70, and routes codes < $80 (function keys) into zp_66 as a game-mode request and codes >= $80 into D_9248 (lastKeyCode) and zp_66 (negative = no mode change)."
  },
  {
   "addr": "0EE5",
   "length": 5,
   "actual": "code",
   "evidence": "Outside this chunk: bytes A9 00 4C DA 0E = 'lda #$00 / jmp $0EDA', an alternate entry of sub_0ED5 that skips the click test (possibly dead)."
  },
  {
   "addr": "32F6",
   "length": 12,
   "actual": "code",
   "evidence": "Outside this chunk: AD 22 90 29 FE 8D 22 90 8D 15 D0 60 = 'lda $9022 / and #$FE / sta $9022 / sta $D015 / rts' = disable sprite 0, the sibling of sub_32EA; no references found so probably dead code."
  }
 ],
 "insights": [
  "Screen layout (multicolour bitmap, VIC bank 2, screen RAM $8C00): left 22x22-char frame at column 0 holds the strategic mini-map (40x40 map cells drawn 4x4 pixels each, sprite X = cell*4+$1F, Y = cell*4+$39); right 16x12 frame at column 24 holds the tactical viewport (7x5 map cells, each 2x2 chars, rows 1-10 / columns 25-38); below it a 16x11 frame at column 24 row 11 is the unit info panel (text window rows 12-20, cols 25-38); the bottom status bar is at row 22 (panel 3/4, 38 wide) and shows messages from the table at $0500/$0524 (e.g. 10 = 'SETUP UNITS. PRESS F 1 WHEN DONE.', 16 = 'WAITING FOR CONNECTION...').",
  "The player's cursor is always the centre cell of the viewport: cursor = (zp_A5 + 3, zp_A6 + 2). Moving the joystick scrolls the viewport (sub_1CD3 -> the self-modifying bitmap scroll at $1DE4-$1F4D) rather than moving a cursor; sprite 0 shows the cursor box on the mini-map (sub_3049) and the expanded sprite 2 frames the centre cell of the viewport. sub_1CC1 limits the cursor to map rows 20-39 while zp_B1 != 0 (own half during unit setup).",
  "Unit records are parallel 100-byte arrays following the map: $F640 X, $F6A4 Y, $F708 owner/flags, $F76C type (0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN) with bit 7 = dead, $F7D0/$F834 destination, $F898 cloaked, $F8FC blitz, $F960 stun/dug-in, $F9C4 energy (x2 = percent), $FA28 move state, $FA8C draw flags, $FAF0 terrain under the unit, $FB54 group (bits 2-4 = ALPHA..GROVER, bit 6 = grouped). A map byte with bit 7 set holds the unit index in bits 0-6; terrain codes are $40 (blank) and up, off-map cells are drawn with pattern $68 + ((x+y) & 3).",
  "Which half of the unit array is 'ours' is decided by self-modifying branch opcodes: 'cmp #$32' followed by bcs/bcc at $206B, $2084, $2840, $2846, $36D5, $379C, $3B2C, $3BD0, $3F9E, $4783, $5BF7, $5BFE, $63E2 are patched with $90/$B0 by sub_2A08 and overlay A ($7BCF-$7BEF) from D_0B9B bit 0.",
  "Joystick/keyboard are sampled in the raster IRQ ($10F1, misclassified as data): zp_5D = direction nibble ($0F centred), zp_5F = 1 when fire released, D_90EE counts idle frames, D_0B7D freezes input for N frames, zp_6C-zp_71 are six per-frame countdown timers. sub_0EB1/sub_0EEA turn zp_5F into edge-triggered clicks using the armed flag zp_79 and the debounce timer zp_6D; the main loop uses D_1FA6 to map the nibble to 8 direction codes and D_20F0/D_1FB2 to deltas.",
  "Hover behaviour: after 15 idle frames on an own unit the info panel (sub_22A2/sub_2384) is displayed and stays while the cursor rests there; after 90 idle frames in target mode a path preview animates the mini-map marker (sprite 1) from the unit to the cursor (sub_2264/sub_24F3) using the Bresenham routines sub_FBB8 (init: zp_93/zp_92 -> zp_95/zp_94, returns steps in zp_91) and sub_FBFA (step, leaves dx/dy in D_90F5/D_90F4).",
  "Text engine in the $C000 overlay: window = zp_39 top, zp_3A bottom, zp_3B left, zp_3C width; cursor zp_3F (col) / zp_40 (row); zp_3D colour, zp_3E fill char; sub_C0D7 full screen, sub_C06A clear window, sub_C097 set row, sub_C041 newline, sub_C01A print char, sub_C133 print string (A/Y), sub_C100 print entry X of a table of 8-byte strings at A/Y, sub_C17D print decimal number, sub_C075 clear to window bottom. Strings end with bit 7 set; unit names at $C964, group names at $C92C, weapon/action words at $C98C/$C9A4.",
  "Game modes: zp_66 requests a mode, D_90FB holds the current one, sub_06BA ($0400 area) resets the stack and dispatches (0 = L_1FC3 battlefield map, 1 = $7587 and 2 = $7FE6 in the overlay, 3 = $141D). sub_25C4 jumps into overlay A at $8658 and is only meaningful while zp_B1 != 0 (setup phase with overlay A loaded); in overlay B that address is mid-instruction.",
  "The header line numbers given for this chunk (2669-3522) did not match the stated addresses; the survey follows the address range $1FA6-$2761 (file lines 3022-4055).",
  "Function keys drive the screen flow: the IRQ key handler ($0E56, misclassified as data) stores F1/F3/F5/F7 as 0-3 in zp_66 and sub_06BA switches game modes accordingly; F1 (0) = return to / start the battlefield map, which is why the setup message says 'PRESS F 1 WHEN DONE'."
 ]
}