{
 "chunk": "game_main_0800_3C50",
 "unit": "game/main_0800",
 "routines": [
  {
   "addr": "3C50",
   "name": "setBoomerTargetCmd",
   "summary": "Receive handler of command $87 (SET TARGET for a boomer): X = cmdParam0 (boomer unit index), countBoomersBelow gives its ordinal in Y, then boomerTargetTable[Y] = cmdParam2 | $80 (target unit index with the lock bit). The disassembly shows these 13 bytes as data; they are reached by the JMP $3C50 at $5182 inside the command dispatcher (also mis-listed as data at $515D-$523D). The sending side is sub_3C20 (cursor on an enemy unit while a unit is selected).",
   "inputs": "zp_73 = boomer unit index, zp_75 = target unit index (bits 0-6)",
   "outputs": "D_91DC[boomer ordinal] = target | $80; Y = ordinal; X = unit index",
   "confidence": "high"
  },
  {
   "addr": "3C5D",
   "name": "countBoomersBelow",
   "summary": "Counts the units with type 2 (boomers) whose index is lower than X, i.e. returns the ordinal of boomer X among all boomers. The limit X is patched into the CPX operand at $3C70. Used to index boomerTargetTable.",
   "inputs": "X = unit index (exclusive upper limit)",
   "outputs": "Y = number of boomers with index < X; X = input X; A = last type byte; C = 1",
   "confidence": "high"
  },
  {
   "addr": "3C74",
   "name": "getBoomerTarget",
   "summary": "If unit X is a boomer (unitTypeTable & 7 == 2) returns its entry of boomerTargetTable (bit 7 set = target locked, bits 0-6 = target unit index) with C = 1; for any other unit type returns with C = 0 and A = type ^ 2 (nonzero, positive). Callers use C/N: the unit option menu shows CLEAR TARGET/SET TARGET, and the unit update loop clears the lock when a boomer can no longer fire.",
   "inputs": "X = unit index",
   "outputs": "C = 1 and A = boomerTargetTable[Y], Y = boomer ordinal (boomer); C = 0 otherwise",
   "confidence": "high"
  },
  {
   "addr": "3C85",
   "name": "selectBoomerTarget",
   "summary": "Chooses the firing target for boomer Y. Runs the spotting scan (sub_35C3 with X=$80 = weapon-range mode) which fills candidateUnitList/candidateDistList/candidateDirList with visible enemies, then walks the list: if the previously locked target (boomerTargetTable entry ^ $80) is still listed it is kept and the routine returns at once; otherwise the enemy comcen (unit 49 or 99) is preferred, else the nearest candidate, and the lock in boomerTargetTable is cleared. Called from the per-unit update loop at $4425 for units of type 2.",
   "inputs": "Y = boomer unit index; D_0400/D_0432/D_0464 lists produced by sub_35C3; zp_2F = list count-1",
   "outputs": "D_91F0 = chosen target unit ($FF none), D_91F1 = its distance, zp_6B = direction to it (0-3); D_91DC[ordinal] cleared when the old lock is lost; zp_18/19/1A scratch",
   "confidence": "high"
  },
  {
   "addr": "3CF2",
   "name": "animateRingBurst",
   "summary": "Plays an expanding-then-contracting ring animation on hardware sprite 1 (colour shadow $9009 = $12, i.e. flashing white/red): clears the sprite shape, enables sprite bit 1, then for radius index 1..A-1 draws a circle frame (sub_3239 with spritePlotMode $40 = set pixels), waiting one tick each (sub_4094); at the largest radius plays sound $17, waits 5 ticks, then erases the frames in reverse order (spritePlotMode 0) and disables the sprite. Overlay B calls it with A=3 (unit placement / kick-off, $767B) and A=$0B (comcen knock-out explosion, $7A0E).",
   "inputs": "A = number of radius steps (patched into the CPX at $3D25); sprite 1 position already set in $9011/$9019",
   "outputs": "sprite 1 shape and enable bit modified, D_3CF1 loop counter, D_91F3 plot mode (left 0), zp_7D = 0 (never read), sound $17",
   "confidence": "medium"
  },
  {
   "addr": "3D57",
   "name": "rollComcenSystemDamage",
   "summary": "When the player's comcen is hit (called from the damage routine at $3B63 only for unit == comcenUnit) and the DAMAGE option (gameTypeOptions bit 4) is on, in a linked game (soloModeFlag bit 7 clear) with a game in progress, rolls for each of the three comcen systems 3..1 that is not yet damaged: if (random & $3F) < damageAmount the system status is set to $80 (damaged).",
   "inputs": "D_9240 = damage amount of the hit; D_0B9B, D_0BA3, D_0BA8",
   "outputs": "D_9241[1..3] may become $80; D_3D56 loop index; A/Y clobbered",
   "confidence": "high"
  },
  {
   "addr": "3D8A",
   "name": "initAllUnits",
   "summary": "Game start: runs initUnitRecord for every unit 99..0 (zp_18 = index), which resets each unit from its start coordinates and places it on the map, then clears the 16 groupStatusTable bytes. Called from overlay A at $7AEF.",
   "inputs": "unit start positions in unitCol/unitRow, unitTypeTable",
   "outputs": "all per-unit arrays reset, map cells marked with $80|unit, D_91F4-D_9203 = 0",
   "confidence": "high"
  },
  {
   "addr": "3DA0",
   "name": "initUnitRecord",
   "summary": "Resets the order/destination state of unit zp_18 (X): waypoint = destination = current position (unitDestCol gets bit 7 = idle), clears unitTypeTable bits 3-4, unitStunTable = 0, keeps only bits 2-4 and 6 of unitFlagsTable (group and membership), sets the facing bits from initialFacingForUnit and bit 5 (side) for units >= 50, then falls into placeUnitOnMap.",
   "inputs": "zp_18 = X = unit index",
   "outputs": "F898/F8FC/F7D0/F834/F76C/F960/F708 of the unit; continues in placeUnitOnMap",
   "confidence": "high"
  },
  {
   "addr": "3DDA",
   "name": "placeUnitOnMap",
   "summary": "Second half of the unit reset, also called on its own by overlay A ($7D83, all units) to re-place units: unitStunTable bit 7 = dug-in bit (unitTypeTable bit 6), pinnedTimer = 0, movePoints = 10, energy = 50 (100%), unitDisplayFlags bit 7 = idle bit, copies group/side bits into unitDisplayFlags (sub_4C6B), computes the map pointer of the unit's cell, saves the cell contents in unitUnderCell and writes $80|index into the map; a dead unit (type byte bit 7) instead becomes an empty slot ($FF in unitTypeTable and unitUnderCell).",
   "inputs": "zp_18 = X = unit index; unitCol/unitRow",
   "outputs": "F960/FA8C/FA28/F9C4/FB54/FAF0 of the unit, map cell at (col,row), zp_48/49 = map pointer",
   "confidence": "high"
  },
  {
   "addr": "3E23",
   "name": "recycleDeadUnits",
   "summary": "Per-tick recycler logic (called at $465A). For each side 1..0 that has a recycler (recyclerCol bit 7 clear) whose cell is not occupied by a unit: scans the side's 50 units from the comcen downwards for dead units (type byte bit 7 set but not $FF). If recyclerMode[side] is 0 the first dead unit is respawned immediately; otherwise two dead units of the same type are needed - the first is freed (slot $FF) and the second is respawned. The respawned unit gets the recycler coordinates, its type, flags 0 and goes through initUnitRecord. For the local side it stores the unit in recycledUnit and queues message 5 (UNIT RECYCLED).",
   "inputs": "D_0BA8 (no game -> return), D_92A8/D_92AA recycler coordinates, D_92A6 recycler mode, D_0C8A last unit index per side, unit arrays",
   "outputs": "unit arrays and map updated, D_923B, message 5; zp_18/19/1A/1B/1C/1D scratch",
   "confidence": "high"
  },
  {
   "addr": "3EBD",
   "name": "removeUnitFromMap",
   "summary": "Takes unit X off the map if it is on it (unitUnderCell bit 7 clear): restores the saved terrain byte into the unit's map cell, marks unitUnderCell = $FF and clears pinnedTimer. Falls into removeUnitLeavingCell with A = saved cell.",
   "inputs": "X = unit index, unitUnderCell[X], unitCol/unitRow",
   "outputs": "map cell restored, FAF0[X] = $FF, FA8C[X] = 0, zp_48/49",
   "confidence": "high"
  },
  {
   "addr": "3EC2",
   "name": "removeUnitLeavingCell",
   "summary": "Like removeUnitFromMap but the caller supplies the byte to leave in the unit's map cell in A (used at $4894 to leave a specific marker behind). Stores A at the unit's map position, then unitUnderCell = $FF and pinnedTimer = 0.",
   "inputs": "A = new map cell value, X = unit index",
   "outputs": "map cell, FAF0[X] = $FF, FA8C[X] = 0; Y = 0",
   "confidence": "high"
  },
  {
   "addr": "3EE2",
   "name": "applyBlitzStepCost",
   "summary": "Called after a successful movement step of unit Y when the blitz bit (unitWaypointRow bit 7) is set: if the unit is moving (unitDestCol bit 7 clear) and still has energy, sets unitFlagsTable bit 7 (charge bonus for the next attack), takes 1 point of energy and adds 25 movement points (saturating at $7F). Plays sound $16 when the unit is the player's own comcen in a live game and no other sound ($14/$16) is busy.",
   "inputs": "Y = unit index; zp_B1, D_0B9B, D_9236, D_67CA",
   "outputs": "F708[Y] bit 7, F9C4[Y] energy-1 (via sub_3BDF, D_9240 = 1), FA28[Y] += 25",
   "confidence": "medium"
  },
  {
   "addr": "3F27",
   "name": "drawRecyclerOnMap",
   "summary": "Writes the recycler glyph ($67) into the map at side Y's recycler position (recyclerCol/recyclerRow). If a unit occupies that cell the glyph goes into that unit's unitUnderCell entry instead (via the $FA70,Y = $FAF0 + (Y & $7F) trick) so it reappears when the unit leaves. Returns the cell coordinates and the byte to draw so the caller can JSR drawMapCell. Returns immediately if the side has no recycler.",
   "inputs": "Y = side index (0/1); D_92A8/D_92AA",
   "outputs": "X = column, Y = row, A = cell value to draw ($67 or the $80|unit byte); map or unitUnderCell updated; operands at $3F51/$3F53/$3F55 patched",
   "confidence": "high"
  },
  {
   "addr": "3F57",
   "name": "checkEnemyRecyclerSpotted",
   "summary": "Once per tick until it succeeds: collects the local player's units within radius 3 of the opponent's recycler (sub_378B/sub_37AA with the opponent's comcen index as the 'enemy' reference) and, if any of them is not cloaked (unitWaypointCol bit 7 clear), draws the enemy recycler on the map (drawRecyclerOnMap + drawMapCell), queues message 8 (SPOTTED RECYCLER!) and patches its own first byte to RTS so it never runs again. The byte is restored to NOP at game init ($0BBD) and set to RTS by the comm module when the opponent has no recycler ($EE3E).",
   "inputs": "D_0B9F playerSide, D_0C8A, D_92A8/D_92AA of the opponent",
   "outputs": "map cell, message 8, self-patch at $3F57; candidate lists at $0400/$0432",
   "confidence": "high"
  },
  {
   "addr": "3F97",
   "name": "initialFacingForUnit",
   "summary": "Returns the initial facing code for unit X: 0 (up) for units of the local player's side, 1 (down) for the other side. The branch opcode at $3F9E is patched by overlay A ($7BD5) to BCC for side 0 and BCS for side 1 so that 'own' means index < 50 or >= 50 respectively. The leading LDA D_0B9F is dead (overwritten by LDA #0).",
   "inputs": "X = unit index; opcode at $3F9E",
   "outputs": "A = 0 or 1",
   "confidence": "high"
  },
  {
   "addr": "3FA3",
   "name": "mirrorMapCoordinate",
   "summary": "Returns 39 - A: mirrors a map column/row across the 40-cell battlefield. Used by the comm module and overlays to convert coordinates between the two players' viewpoints (each player sees his own side at the bottom).",
   "inputs": "A = coordinate 0..39",
   "outputs": "A = 39 - A",
   "confidence": "high"
  },
  {
   "addr": "3FAC",
   "name": "haltStunnedUnit",
   "summary": "Called from the unit update loop while unit Y is stunned (stun counter nonzero). Unless gameTypeOptions bit 3 is set it only acts on the player's comcen; and when linkTypeAtStart bit 7 is set it ignores units of side 1 (index >= 50). It stops the unit: waypoint and destination = current position, unitDestCol bit 7 (idle) set, unitFlagsTable bit 6 (group membership) cleared, unitDisplayFlags bit 7 set and bit 6 cleared.",
   "inputs": "Y = unit index; D_0BA3, D_9236, D_92FE",
   "outputs": "F898/F7D0/F8FC/F834/F708/FB54 of the unit",
   "confidence": "medium"
  },
  {
   "addr": "3FE8",
   "name": "haltAllUnitsAndReveal",
   "summary": "Game-over helper: clears D_9242/D_9243/D_9244, then for all 100 units clears the fired/hit bits and sets the idle bit of unitDisplayFlags and sets the waypoint to the current position (keeping the cloak/blitz bits), marks every unit as spotted (sub_3568 sets energy bits 6-7) and, if the battlefield screen is active, redraws the overview (sub_5AD7) and the view (sub_26C1).",
   "inputs": "D_90FB currentScreen",
   "outputs": "FB54/F898/F8FC/F9C4 of all units, D_9242-D_9244 = 0, screen redraw",
   "confidence": "high"
  },
  {
   "addr": "4027",
   "name": "endGame",
   "summary": "Ends the battle after the caller has stored the end reason in D_0BA8 ($08 = clock ran out at $4608, $A0/$C0 = a comcen was knocked out at $3BD3): clears D_920D and D_92F9, halts and reveals all units, shows the result/score message (sub_28F1) and starts the 120-tick game-end countdown D_92C9.",
   "inputs": "D_0BA8 end reason, scores D_92A2-D_92A5 (via sub_28F1)",
   "outputs": "D_920D = 0, D_92F9 = 0, D_92C9 = $78, all units halted",
   "confidence": "high"
  }
 ],
 "variables": [
  {
   "addr": "18",
   "scope": "zp",
   "name": "tempByte18",
   "meaning": "scratch: unit index passed to initUnitRecord/placeUnitOnMap, list index in selectBoomerTarget, damage cap in the attack routine",
   "confidence": "high"
  },
  {
   "addr": "19",
   "scope": "zp",
   "name": "tempByte19",
   "meaning": "scratch: locked target index in selectBoomerTarget, unit type of the dead unit in recycleDeadUnits, next-cell value in the movement code",
   "confidence": "high"
  },
  {
   "addr": "1A",
   "scope": "zp",
   "name": "tempByte1A",
   "meaning": "scratch: boomer ordinal in selectBoomerTarget, side index loop in recycleDeadUnits",
   "confidence": "high"
  },
  {
   "addr": "1B",
   "scope": "zp",
   "name": "firstDeadUnit",
   "meaning": "recycleDeadUnits: index of the first dead unit of the current type found (freed when a second one is found); elsewhere direction scratch",
   "confidence": "medium"
  },
  {
   "addr": "1C",
   "scope": "zp",
   "name": "scanUnitIndex",
   "meaning": "recycleDeadUnits: unit index cursor running from the side's comcen downwards",
   "confidence": "medium"
  },
  {
   "addr": "1D",
   "scope": "zp",
   "name": "scanUnitsLeft",
   "meaning": "recycleDeadUnits: number of units of the side still to scan (50 down to 1)",
   "confidence": "medium"
  },
  {
   "addr": "2F",
   "scope": "zp",
   "name": "candidateCount",
   "meaning": "index of the last entry of the candidate lists at $0400/$0432/$0464 built by sub_37AA (count-1, $FF = none)",
   "confidence": "high"
  },
  {
   "addr": "48",
   "scope": "zp",
   "name": "mapCellPtr",
   "meaning": "pointer (lo) into the $F000 battlefield map set by sub_25FA (X = column, Y = row); $49 = hi",
   "confidence": "high"
  },
  {
   "addr": "6B",
   "scope": "zp",
   "name": "unitFacing",
   "meaning": "facing/aim direction of the unit being processed: 0 up, 1 down, 2 left, 3 right (stored in unitFlagsTable bits 0-1); set to the direction of the chosen target",
   "confidence": "high"
  },
  {
   "addr": "73",
   "scope": "zp",
   "name": "cmdParam0",
   "meaning": "first parameter of a received command (unit index for setBoomerTargetCmd)",
   "confidence": "high"
  },
  {
   "addr": "75",
   "scope": "zp",
   "name": "cmdParam2",
   "meaning": "third parameter of a received command (target unit index for setBoomerTargetCmd)",
   "confidence": "high"
  },
  {
   "addr": "7D",
   "scope": "zp",
   "name": "unusedByte7D",
   "meaning": "written with 0 by animateRingBurst and never read anywhere (dead store)",
   "confidence": "high"
  },
  {
   "addr": "B1",
   "scope": "zp",
   "name": "gamePhase",
   "meaning": "0 while the battle runs; nonzero in setup/film/menu phases (blitz cost and comcen sound are skipped)",
   "confidence": "high"
  },
  {
   "addr": "0400",
   "scope": "abs",
   "name": "candidateUnitList",
   "meaning": "work list (up to 50 entries) of enemy unit indices found by sub_37AA within a radius; entries set to $FF by sub_35C3 when line of sight fails. Overlays the copy-protection answer table, which is only needed at start-up",
   "confidence": "high"
  },
  {
   "addr": "0432",
   "scope": "abs",
   "name": "candidateDistList",
   "meaning": "parallel to candidateUnitList: dx^2 + dy^2 distance metric (table D_3860, saturating at $FF)",
   "confidence": "high"
  },
  {
   "addr": "0464",
   "scope": "abs",
   "name": "candidateDirList",
   "meaning": "parallel to candidateUnitList: direction 0-3 (up/down/left/right) from the viewing unit to the candidate (sub_375D)",
   "confidence": "high"
  },
  {
   "addr": "0B9B",
   "scope": "abs",
   "name": "soloModeFlag",
   "meaning": "bit 7 set = solo/film mode: no comcen system damage rolls, no blitz sound, messages via sub_C560 suppressed",
   "confidence": "high"
  },
  {
   "addr": "0B9F",
   "scope": "abs",
   "name": "playerSide",
   "meaning": "local player's side 0/1; EOR 1 gives the opponent",
   "confidence": "high"
  },
  {
   "addr": "0BA3",
   "scope": "abs",
   "name": "gameTypeOptions",
   "meaning": "bits 0-2 game type, bit 3 = stunned units of any kind are halted (otherwise only the comcen), bit 4 = DAMAGE option",
   "confidence": "medium"
  },
  {
   "addr": "0BA8",
   "scope": "abs",
   "name": "gameEndReason",
   "meaning": "0 while a game is in progress; nonzero ends it: $08 clock expired, $A0/$C0 = comcen of side 0/1 knocked out (bit 5/6 from unitFlagsTable bit 5), also set by menus when no game runs",
   "confidence": "medium"
  },
  {
   "addr": "0C8A",
   "scope": "abs",
   "name": "lastUnitOfSide",
   "meaning": "2 bytes $31,$63: index of the last unit (the comcen) of side 0 and side 1; units 0-49 belong to side 0, 50-99 to side 1",
   "confidence": "high"
  },
  {
   "addr": "3CF1",
   "scope": "abs",
   "name": "ringAnimStep",
   "meaning": "current radius index of animateRingBurst",
   "confidence": "high"
  },
  {
   "addr": "3D56",
   "scope": "abs",
   "name": "comcenSystemIndex",
   "meaning": "loop index 3..1 of rollComcenSystemDamage",
   "confidence": "high"
  },
  {
   "addr": "67CA",
   "scope": "abs",
   "name": "currentSoundId",
   "meaning": "id of the sound effect currently playing in the $C89A sound driver (0 = none); applyBlitzStepCost only starts sound $16 when this is 0, $14 or $16",
   "confidence": "medium"
  },
  {
   "addr": "9009",
   "scope": "abs",
   "name": "sprite1ColourShadow",
   "meaning": "colour shadow of sprite 1 ($9008+1); $12 = two alternating colours (white/red flashing) for the ring burst",
   "confidence": "high"
  },
  {
   "addr": "90FB",
   "scope": "abs",
   "name": "currentScreen",
   "meaning": "0 = battlefield view; haltAllUnitsAndReveal redraws only then",
   "confidence": "high"
  },
  {
   "addr": "91DC",
   "scope": "abs",
   "name": "boomerTargetTable",
   "meaning": "one byte per boomer (ordinal among type-2 units, up to 20 entries $91DC-$91EF): bit 7 = target locked, bits 0-6 = target unit index; 0 = no target",
   "confidence": "high"
  },
  {
   "addr": "91F0",
   "scope": "abs",
   "name": "chosenTargetUnit",
   "meaning": "unit index selected as firing target by the spotting/targeting code for the unit being updated; $FF = none",
   "confidence": "high"
  },
  {
   "addr": "91F1",
   "scope": "abs",
   "name": "chosenTargetDist",
   "meaning": "distance metric of chosenTargetUnit (also the search radius passed to sub_37AA by sub_35C3)",
   "confidence": "high"
  },
  {
   "addr": "91F3",
   "scope": "abs",
   "name": "spritePlotMode",
   "meaning": "mode for the sprite shape plotter sub_31AF: $40 = set pixels, $80 = toggle (EOR), 0 = clear pixels",
   "confidence": "medium"
  },
  {
   "addr": "91F4",
   "scope": "abs",
   "name": "groupStatusTable",
   "meaning": "16 bytes, one per group (unitFlagsTable bits 2-5): bits 3-5 = terrain class of the slowest member, bits 0-2 = type-derived value; rebuilt each tick by sub_48DD, cleared by initAllUnits",
   "confidence": "low"
  },
  {
   "addr": "920D",
   "scope": "abs",
   "name": "screenStateFlags920D",
   "meaning": "flag byte cleared by endGame and on screen switches (bit 0 set at $52EB); exact meaning unknown",
   "confidence": "low"
  },
  {
   "addr": "9236",
   "scope": "abs",
   "name": "comcenUnit",
   "meaning": "index of the local player's comcen unit (49 or 99)",
   "confidence": "high"
  },
  {
   "addr": "923B",
   "scope": "abs",
   "name": "recycledUnit",
   "meaning": "index of the unit most recently recycled on the local side (set by recycleDeadUnits, read at $417B)",
   "confidence": "medium"
  },
  {
   "addr": "9240",
   "scope": "abs",
   "name": "damageAmount",
   "meaning": "amount of the current hit being applied (parameter of sub_3BDF / the damage routine); compared with random & $3F for system damage",
   "confidence": "high"
  },
  {
   "addr": "9241",
   "scope": "abs",
   "name": "comcenSystemStatus",
   "meaning": "4 bytes: [0] energy status, [1..3] status of the three comcen systems, $80 = damaged",
   "confidence": "high"
  },
  {
   "addr": "9242",
   "scope": "abs",
   "name": "unitDrawOverride",
   "meaning": "bit 7 set changes how units are drawn (sub_5BDA skips the type/side lookup); set by overlay A, cleared by haltAllUnitsAndReveal",
   "confidence": "low"
  },
  {
   "addr": "9243",
   "scope": "abs",
   "name": "radarEventFlag9243",
   "meaning": "nonzero enables a random event in overlay B screen 1 ($7604); cleared at game end",
   "confidence": "low"
  },
  {
   "addr": "9244",
   "scope": "abs",
   "name": "radarEventFlag9244",
   "meaning": "nonzero enables a random event in overlay B screen 2 ($804D); cleared at game end",
   "confidence": "low"
  },
  {
   "addr": "92A6",
   "scope": "abs",
   "name": "recyclerMode",
   "meaning": "2 bytes, one per side: recycler setting from the setup screen (FULL/HALF/NONE): 0 = every dead unit is recycled, 1 = two dead units of a type yield one, 2 = no recycler (coordinates $FF)",
   "confidence": "medium"
  },
  {
   "addr": "92A8",
   "scope": "abs",
   "name": "recyclerCol",
   "meaning": "2 bytes, one per side: map column of the side's recycler, bit 7 set = none",
   "confidence": "high"
  },
  {
   "addr": "92AA",
   "scope": "abs",
   "name": "recyclerRow",
   "meaning": "2 bytes, one per side: map row of the side's recycler",
   "confidence": "high"
  },
  {
   "addr": "92C9",
   "scope": "abs",
   "name": "gameEndCountdown",
   "meaning": "set to $78 by endGame; nonzero = game over sequence running (aborts waits, ticks in sub_4094)",
   "confidence": "high"
  },
  {
   "addr": "92F9",
   "scope": "abs",
   "name": "viewStateFlags92F9",
   "meaning": "flag byte cleared by endGame and when a screen is switched; set by overlay B ($79EB) with bit 5 and a toggled bit 4",
   "confidence": "low"
  },
  {
   "addr": "92FE",
   "scope": "abs",
   "name": "linkTypeAtStart",
   "meaning": "copy of linkTypeFlag taken at game start; bit 7 set makes haltStunnedUnit ignore side-1 units",
   "confidence": "medium"
  },
  {
   "addr": "F640",
   "scope": "abs",
   "name": "unitCol",
   "meaning": "per-unit map column (100 entries; all per-unit arrays are 100 bytes apart)",
   "confidence": "high"
  },
  {
   "addr": "F6A4",
   "scope": "abs",
   "name": "unitRow",
   "meaning": "per-unit map row",
   "confidence": "high"
  },
  {
   "addr": "F708",
   "scope": "abs",
   "name": "unitFlagsTable",
   "meaning": "per-unit flags: bits 0-1 facing (0 up,1 down,2 left,3 right), bits 2-4 group number, bit 5 side (1 = units 50-99), bit 6 group membership active, bit 7 blitz charge ready (attack bonus pending)",
   "confidence": "medium"
  },
  {
   "addr": "F76C",
   "scope": "abs",
   "name": "unitTypeTable",
   "meaning": "per-unit type byte: bits 0-2 type (0 grunt, 1 rider, 2 boomer, 3 spy, 4 comcen; 5-7 drone/missile class), bit 6 dug in, bit 7 dead; $FF = empty slot",
   "confidence": "medium"
  },
  {
   "addr": "F7D0",
   "scope": "abs",
   "name": "unitDestCol",
   "meaning": "per-unit destination column (bits 0-5); bit 7 set = idle (no move order, destination reached)",
   "confidence": "high"
  },
  {
   "addr": "F834",
   "scope": "abs",
   "name": "unitDestRow",
   "meaning": "per-unit destination row (bits 0-5); bit 7 = resting/recovering energy flag",
   "confidence": "medium"
  },
  {
   "addr": "F898",
   "scope": "abs",
   "name": "unitWaypointCol",
   "meaning": "per-unit next waypoint column (bits 0-5); bit 6 = cloaking ordered (CLOAKING ON), bit 7 = cloak active (unit hidden, does not spot or fire)",
   "confidence": "medium"
  },
  {
   "addr": "F8FC",
   "scope": "abs",
   "name": "unitWaypointRow",
   "meaning": "per-unit next waypoint row (bits 0-5); bit 6 = blitz ordered (SET BLITZ), bit 7 = blitz in effect while moving",
   "confidence": "medium"
  },
  {
   "addr": "F960",
   "scope": "abs",
   "name": "unitStunTable",
   "meaning": "per-unit: bits 0-3 stun counter, bits 5-6 dig-in progress, bit 7 dug in",
   "confidence": "medium"
  },
  {
   "addr": "F9C4",
   "scope": "abs",
   "name": "unitEnergyTable",
   "meaning": "per-unit energy bits 0-5 (0..50 = 0..100%), bit 6 = seen, bit 7 = visible/spotted",
   "confidence": "high"
  },
  {
   "addr": "FA28",
   "scope": "abs",
   "name": "unitMovePoints",
   "meaning": "per-unit movement point accumulator (signed, starts at 10): a step costs the terrain value from D_405E (+8 diagonal), being hit subtracts the damage (pinned), blitz adds 25",
   "confidence": "medium"
  },
  {
   "addr": "FA8C",
   "scope": "abs",
   "name": "unitPinnedTimer",
   "meaning": "per-unit: bit 7 set with the hit amount (max $14) when the unit was pinned by fire; 0 = free; $85 for a knocked-out comcen",
   "confidence": "medium"
  },
  {
   "addr": "FAF0",
   "scope": "abs",
   "name": "unitUnderCell",
   "meaning": "per-unit saved map byte of the cell the unit stands on (restored when it leaves); $FF = unit is not on the map",
   "confidence": "high"
  },
  {
   "addr": "FB54",
   "scope": "abs",
   "name": "unitDisplayFlags",
   "meaning": "per-unit: bit 0 fired this tick, bit 1 was hit this tick, bits 2-6 copy of unitFlagsTable bits 2-6 (group/side/membership), bit 7 idle/stationary",
   "confidence": "medium"
  }
 ],
 "dataBlocks": [
  {
   "addr": "3CF1",
   "length": 1,
   "type": "byteTable",
   "name": "ringAnimStep",
   "description": "loop counter of animateRingBurst"
  },
  {
   "addr": "3D56",
   "length": 1,
   "type": "byteTable",
   "name": "comcenSystemIndex",
   "description": "loop counter of rollComcenSystemDamage"
  }
 ],
 "misclassified": [
  {
   "addr": "3C50",
   "length": 13,
   "actual": "code",
   "evidence": "bytes A6 73 20 5D 3C A5 75 09 80 99 DC 91 60 = LDX $73 / JSR $3C5D / LDA $75 / ORA #$80 / STA $91DC,Y / RTS; reached by JMP $3C50 at $5182 (bytes 4C 50 3C inside the .byte run at $5181), the handler of command $87"
  },
  {
   "addr": "515D",
   "length": 40,
   "actual": "code",
   "evidence": "outside this chunk but related: $515D-$5184 disassembles to JSR $4FF7 / LDX $73 / JSR $3C74 / LDA #0 / STA $91DC,Y / RTS (clear boomer target command) and JSR $4FD0 / parameter shuffling / JMP $3C50 (set boomer target command); the whole .byte run $50D9-$523D appears to be command handlers"
  }
 ],
 "insights": [
  "Unit records: 100 units, index 0-49 = side 0, 50-99 = side 1, the last unit of each side (49, 99) is the comcen. Fourteen parallel 100-byte arrays start at $F640 right after the 40x40 map: F640 col, F6A4 row, F708 flags (facing/group/side), F76C type (0 grunt, 1 rider, 2 boomer, 3 spy, 4 comcen; bit 6 dug in, bit 7 dead, $FF empty), F7D0/F834 destination (F7D0 bit 7 = idle), F898/F8FC waypoint (bit 6/7 cloak flags resp. blitz flags), F960 stun/dig-in, F9C4 energy (0-50) + spotted bits, FA28 movement points, FA8C pinned timer, FAF0 saved map byte under the unit, FB54 display flags.",
  "A unit on the battlefield map is stored as $80 | unitIndex in the cell; sub_25FA(X = col, Y = row) returns the cell byte in A (N flag = unit present) and the cell address in zp_48/49. Routines index other per-unit arrays with the raw cell byte by using base-$80 addresses (e.g. $FA70,Y = unitUnderCell, $F688,Y = unitFlagsTable, $F818,Y = unitWaypointCol, $F6EC,Y = unitTypeTable).",
  "The map cell value $67 is the recycler glyph. Each side's recycler position is D_92A8/D_92AA (bit 7 = none, when recyclerMode D_92A6 = 2). The own recycler is drawn at game start by the comm module (JMP sub_3F27); the enemy recycler only appears after one of the player's uncloaked units comes within radius 3 (sub_3F57, message 8 'SPOTTED RECYCLER!', then the routine patches itself to RTS). Dead units respawn at the recycler (sub_3E23, message 5 'UNIT RECYCLED'); with recyclerMode 1 two dead units of one type are needed for one respawn.",
  "Candidate lists at $0400 (unit index), $0432 (distance metric dx^2+dy^2 from table D_3860, capped $FF) and $0464 (direction 0-3) are built by sub_37AA (A = radius, X/Y = centre) for the units of the side opposite to the reference unit passed to sub_378B, which patches the CMP #$32 / BCC-BCS pair at $37FE/$3800. sub_35C3 then removes the ones without line of sight (entry = $FF). zp_2F holds count-1. These lists overwrite the copy-protection answer table at $0401, which is only used at start-up.",
  "Boomers (type 2) keep a persistent target lock in boomerTargetTable ($91DC, indexed by the boomer's ordinal among all boomers, computed by sub_3C5D). SET TARGET / CLEAR TARGET in the unit option menu send commands $87 (handler $3C50, hidden as data) and the clear command (handler at $515D, also hidden as data). sub_3C85 keeps the lock while the target stays visible, otherwise prefers the enemy comcen, then the nearest visible enemy.",
  "Game end: the caller stores the reason in D_0BA8 ($08 time up, $A0/$C0 comcen of side 0/1 destroyed) and calls sub_4027, which halts and reveals all units, shows the result (sub_28F1 compares the 16-bit scores D_92A2/D_92A4 vs D_92A3/D_92A5) and starts the $78-tick countdown D_92C9. The comcen never gets the dead bit; its destruction sets D_0BA8 instead.",
  "Overlay A's sub_7BC6 patches branch opcodes in the resident code per player side ($90 BCC for side 0, $B0 BCS for side 1) at $63E2, $2840, $3F9E, $36D5, $379C, $4783, $5BF7 and $206B (EOR $20 variant): every 'is this one of my units' test in main code is a CPX/CPY #$32 followed by a patched branch. In this chunk that affects sub_3F97 (initial facing: own units face up).",
  "Message ids used here map through the pointer tables at $0500/$0524 (lo/hi) into the $8800 strings: 5 = 'UNIT RECYCLED', 8 = 'SPOTTED RECYCLER!', $0C = 'COMCEN STUNNED!'. sub_C560 drops the message in solo/film mode ($0B9B bit 7); sub_C566 always queues it (priority-sorted 8-entry queue at $9249).",
  "The unit option menu strings live in the $C000 overlay: items 0-$0B at $C9D2 + 16*n (FORMATION, MOVE TO GOAL, CLOAKING ON, NO CLOAKING, CLEAR TARGET, SET TARGET, MEMBERSHIP, JOIN GROUP, DIRECT UNIT, SELF DESTRUCT, SET BLITZ, BLITZ OFF) and items $0C+ at $CAB2 + 8*n ((EXIT), ENERGY, BATTLE, RADAR, DRONE, DIG OUT, DIG IN). The menu code at $5E99 shows which unit bits they toggle: F898 bit 6 cloak, F8FC bit 6 blitz, FB54 bit 7 / F960 bits 5-7 dig in."
 ]
}