{
 "chunk": "game_main_0800_32DA",
 "unit": "game/main_0800",
 "routines": [
  {
   "addr": "32EA",
   "name": "hideSprite1",
   "summary": "Clears bit 1 of the sprite-enable shadow ($9022) and writes the result straight to VIC_SPR_ENA, turning sprite 1 off immediately (sprite 1 is the unit-selection sprite drawn by sub_3CF2). Called from the battlefield cursor / selection code ($229F, $22F4, $2359, $253C, $2540, $3D4E).",
   "inputs": "spriteEnableShadow $9022",
   "outputs": "$9022 and $D015 with bit 1 cleared; A clobbered",
   "confidence": "high"
  },
  {
   "addr": "3302",
   "name": "showCrosshairSprite",
   "summary": "Loads the 63-byte diamond/crosshair shape ($9EE3-$9F21, offset X=$7D into the $9EA4 shape pair) into sprite 2 data ($8A80) and falls into the shared body at $3308 which makes sprite 2 black, double-size, positioned at X=256 (MSB set, $9012=0) Y=$4D - the centre of the 7x5 battlefield view - and enables it. Used when a target/attack cursor is active ($21AB).",
   "inputs": "none",
   "outputs": "sprite 2 data, $900A=0, $9023/$9024 bit 2 set, $9012=0, $9020 bit 2 set, $901A=$4D, sprite 2 enabled via sub_31A3",
   "confidence": "medium"
  },
  {
   "addr": "3306",
   "name": "showCursorBoxSprite",
   "summary": "Same as showCrosshairSprite but copies the square box shape ($9EA4-$9EE2, offset X=$3E) - the normal battlefield centre cursor - into sprite 2 and shows it black and expanded at (256,$4D). Called when the battlefield is (re)entered ($1FEB, $2260) and from $4C56.",
   "inputs": "none",
   "outputs": "sprite 2 data and VIC shadow registers as for showCrosshairSprite",
   "confidence": "medium"
  },
  {
   "addr": "3340",
   "name": "loadDirectionShapeSprite",
   "summary": "Copies the 63-byte sprite shape number A (0-7, a facing/direction) from the decompressed shape buffer at $0200+64*A into sprite 0 data ($8A00) when activeSpriteIndex $91CC is 0, otherwise into sprite 5 data ($8B40), and then enables sprite $91CC. The buffer is filled by the RLE decompressor sub_67CB from the compressed sets at $FD70/$FE24. Callers pass the direction byte $920B ($5322, overlay B $802A/$810B/$829E).",
   "inputs": "A = shape index 0-7, $91CC = target sprite (0 or 5)",
   "outputs": "$8A00 or $8B40 sprite data, sprite enabled; zp_98/zp_99 = source pointer (clobbered), X/Y clobbered",
   "confidence": "medium"
  },
  {
   "addr": "3383",
   "name": "showComcenCrossMarker",
   "summary": "Clears sprite 2 (sub_2FB2 A=2), copies the 21-byte cross/plus shape at $9F22 into rows 7-13 of sprite 2 ($8A95), colours it light red ($900A=$0A), positions it at grid cell (10,8) converted by gridCellToSpriteCoords (the comcen cell of overlay B's 21x17 strategic grid) and enables sprite 2. Called by overlay B at $75B6 when screen 1 is built.",
   "inputs": "none",
   "outputs": "sprite 2 data/colour/position, zp_67=10, zp_68=8, sprite 2 enabled",
   "confidence": "medium"
  },
  {
   "addr": "33B1",
   "name": "gridCellToSpriteCoords",
   "summary": "Converts an 8x8-pixel grid cell (X = column, Y = row) of the overlay B strategic map into sprite coordinates: X = X*8+$27, Y = Y*8+$43.",
   "inputs": "X = grid column, Y = grid row",
   "outputs": "X = sprite X, Y = sprite Y",
   "confidence": "high"
  },
  {
   "addr": "340B",
   "name": "getUnitSpriteCentreOnGrid",
   "summary": "Calls overlay B's sub_7A13 (map cell X/Y -> grid cell relative to the comcen, C=1 when outside the 21x17 grid); if inside, converts the cell with gridCellToSpriteCoords and adds 4 to both so the result is the cell centre. Returns C=0 on success, C=1 when off-grid.",
   "inputs": "X = map column, Y = map row (passed to sub_7A13)",
   "outputs": "C = 0 and X/Y = sprite coordinates of the cell centre, or C = 1",
   "confidence": "medium"
  },
  {
   "addr": "341F",
   "name": "updateMarkerSprite",
   "summary": "If the countdown $923E is nonzero and the marker cell (immediate operands at $3425/$3437, column 0 / row $FF in this build) lies within 14 columns and 12 rows of the view origin, positions sprite 4 at X=col*8+$78, Y=row*8+$5C and enables it; otherwise disables sprite 4. Nothing in this build sets $923E or patches the operands (only the dead code at $33C8), so in practice it only clears sprite 4. Called by overlay B's screen loop at $827E.",
   "inputs": "$923E, zp_A5/zp_A6 view origin, patched operands $3425/$3437",
   "outputs": "$9014/$901C sprite 4 position, sprite 4 enable bit set or cleared",
   "confidence": "medium"
  },
  {
   "addr": "3456",
   "name": "drawComcenRingSprite",
   "summary": "Disables sprite 4, sets spritePlotMode $91F3=$40 (OR pixels), clears sprite 4 data (sub_2FB2 A=4) and draws two concentric circles of radius 2 and 1 into it with sub_3239 (centre (12,10)), then gives sprite 4 the flashing colour pair $12. Overlay B calls it at $80E1 right after centring the view on the comcen, so this is the comcen marker of screen 2.",
   "inputs": "none",
   "outputs": "sprite 4 data, $900C=$12, $91CC=4, $91F3=$40; self-modifies the loop counter at $346E",
   "confidence": "medium"
  },
  {
   "addr": "347F",
   "name": "orderSelectedUnitToCursor",
   "summary": "Builds a move order for the selected unit ($90F8): cmdParam1/2 (zp_74/zp_75) = cursor cell (viewOriginCol+3, viewOriginRow+2), clears bit 6 of the unit's status flags ($FB54), sends command $80 unit,col,row through sub_560A, then calls updateUnitAtDestinationFlag. Returns C=0. Fire-button handler of the battlefield ($2212).",
   "inputs": "$90F8 selected unit, zp_A5/zp_A6 view origin",
   "outputs": "zp_73/74/75 command parameters, $FB54[unit] bits 6/7, 4 bytes queued in the outgoing command ring, C=0",
   "confidence": "medium"
  },
  {
   "addr": "34A4",
   "name": "updateUnitAtDestinationFlag",
   "summary": "Sets bit 7 of unitStatusFlags ($FB54) for unit zp_73 when the unit's map position ($F640/$F6A4) equals the ordered destination in zp_74/zp_75, clears it otherwise. Also called from the command executors at $4A4D and $4C10.",
   "inputs": "zp_73 = unit, zp_74 = destination column, zp_75 = destination row",
   "outputs": "$FB54[unit] bit 7; self-modifies the ORA operand at $34C1",
   "confidence": "medium"
  },
  {
   "addr": "3568",
   "name": "markAllUnitsVisible",
   "summary": "Sets bits 6 and 7 (spotted this cycle + visible) in unitEnergy ($F9C4) for all 100 units, i.e. reveals every unit. Called at the end of the reset routine at $4018 and by ageUnitVisibility when no game is in progress.",
   "inputs": "none",
   "outputs": "$F9C4[0..99] |= $C0; Y = $FF",
   "confidence": "high"
  },
  {
   "addr": "3576",
   "name": "ageUnitVisibility",
   "summary": "If noGameInProgress ($0BA8) is nonzero, jumps to markAllUnitsVisible. Otherwise sets the unit-scan side filter to 'all units' (compare operand $37FF=0, opcode $3800=BCC) and for every unit copies energy bit 6 into bit 7 and clears bit 6: units spotted during the last scan cycle stay visible, everything else fades. Called once per unit-processing cycle at $4660, before the spotting passes.",
   "inputs": "$0BA8, $F9C4[]",
   "outputs": "$F9C4[] visibility bits, $37FF/$3800 scan filter",
   "confidence": "high"
  },
  {
   "addr": "35C3",
   "name": "scanVisibleEnemies",
   "summary": "Builds the list of enemy units that unit Y can see (X bit 7 clear = spotting pass) or shoot at (X bit 7 set = attack-target pass) into scanUnitList $0400 / scanDistSqList $0432 / scanDirectionList $0464. Range = sightRangeByType[type (+5 for attack)] + terrainSightBonus[terrain class under the unit] (doubled for spotting), capped at 10; collectUnitsInRadius gathers opposite-side units in the square, then each candidate is rejected ($0400[n]=$FF) if its squared distance exceeds rangeDistanceSq[range] (+1 penalty when not in the preferred direction zp_6B), if it is destroyed, if (attack pass) it is not yet visible, if (spotting) it is a spy seen by a non-spy or farther than dist2 20, or a 'hidden' unit ($F898 bit 7) farther than dist2 12 (6 for non-spies). Grunts/Riders (and all spotting) then need a clear line of sight (checkLineOfSightStep over each cell, enemy units block, friendly units block only from flat ground, spies are transparent, terrain class 2 blocks, a class-2 target farther than dist2 6 is hidden unless already spotted); Boomers/spies/comcen skip the LOS test when attacking. First sighting of an enemy spy/comcen queues status message $0F/$0D.",
   "inputs": "X = mode (bit 7: 0 spotting, 1 attack targets), Y = scanning unit; unit tables, map, $077E terrain classes",
   "outputs": "$0400/$0432/$0464 list with zp_2F = last index ($FF none), $91F1 = range, $9227/$9228/$9229 = unit terrain cell/type/height, zp_18-zp_29 scratch; self-modifies $35F9, $362B",
   "confidence": "medium"
  },
  {
   "addr": "3746",
   "name": "checkLineOfSightStep",
   "summary": "Feeds one more terrain class X into the running line-of-sight test: compares terrainHeight[X] with the previous height zp_1E; a rise is remembered in zp_1F, a fall after a rise means a ridge hides the target and returns C=1. Equal heights or falls while no rise has happened yet (looking down from the start) return C=0.",
   "inputs": "X = terrain class of the next cell, zp_1E = previous height, zp_1F = $80 until the line went uphill",
   "outputs": "C = 1 blocked / 0 clear, zp_1E updated, zp_1F = rise amount",
   "confidence": "high"
  },
  {
   "addr": "375D",
   "name": "initLineBetweenUnits",
   "summary": "Sets the line-walker target (zp_95/zp_94) to unit Y's position and falls into initLineFromUnit with X as the start unit.",
   "inputs": "X = start unit, Y = end unit",
   "outputs": "see initLineFromUnit",
   "confidence": "high"
  },
  {
   "addr": "3767",
   "name": "initLineFromUnit",
   "summary": "Sets the line-walker start (zp_93/zp_92) to unit X's position, calls initLineStepper ($FBB8) and returns the principal direction of the line as 0 = north, 1 = south (row-major lines, by zp_96) or 2 = east, 3 = west (column-major lines, by zp_97). The value becomes the unit's facing (bits 0-1 of $F708) at $4464. Also called from $428E.",
   "inputs": "X = unit, zp_94/zp_95 = target row/column",
   "outputs": "A = direction code 0-3, line walker state zp_8C-zp_97",
   "confidence": "high"
  },
  {
   "addr": "378B",
   "name": "setScanFilterOppositeSide",
   "summary": "Configures collectUnitsInRadius to keep only units of the side opposite to unit Y: compare operand $37FF = $32 and branch opcode $3800 = BCC ($90) when Y < 50 (side 0 scanner keeps units >= 50) or BCS ($B0) when Y >= 50.",
   "inputs": "Y = unit index",
   "outputs": "$37FF, $3800 patched; A clobbered",
   "confidence": "high"
  },
  {
   "addr": "37AA",
   "name": "collectUnitsInRadius",
   "summary": "Scans the map square of half-width A around cell (X,Y), clipped to 0..39, for cells holding a unit (bit 7 set, low bits = unit index) that passes the side filter at $37FE/$3800, storing the unit index in $0400[n] and its squared distance (distanceSquared) in $0432[n]. When more than one unit was found and playerSide ($0B9F) is nonzero the list is reversed, so both players get mirror-symmetric tie breaking. Returns X = last index ($FF when empty).",
   "inputs": "A = radius, X = centre column, Y = centre row, $0B9F, map via sub_25FA",
   "outputs": "$0400/$0432 lists, X = zp_29 = count-1, zp_22-zp_29 scratch, zp_48/zp_49 map pointer; self-modifies $385A",
   "confidence": "high"
  },
  {
   "addr": "3870",
   "name": "distanceSquared",
   "summary": "Returns |zp_22-zp_27|^2 + |zp_24-zp_28|^2 using squaresTable (each delta capped at 15, sum saturating at $FF).",
   "inputs": "zp_22/zp_24 = cell column/row, zp_27/zp_28 = centre column/row",
   "outputs": "A = squared distance (max $FF), X clobbered",
   "confidence": "high"
  },
  {
   "addr": "38C2",
   "name": "resolveAttack",
   "summary": "Resolves an attack of unit X on unit Y. Only Grunts/Riders/Boomers (types 0-2) attack. Damage = baseDamageTable[attacker type][target type]; a moving attacker ($F8FC bit 7) whose first-strike flag ($F708 bit 7) is set sacrifices up to 15 energy (5 for Boomers) once: the amount is added to the damage and 3/4 of it is taken from the attacker. +2 if the attacker has $F76C bit 6, -25% if the target has it; + height difference of the terrain (terrainHeightTable); +10 if the target stands on terrain class 1, +2 on class 3 or 5; for non-Boomers a facing comparison adds +20 when the target faces the same way (hit from behind) and +10 for a flank hit. A zero/negative result becomes 2; half of the total is applied by applyHitToUnit. On the battlefield screen (and comcen not knocked out) the shot is drawn by overlay B's sub_82BF and drawShotLineSprite. Finally plays sound 9 if the target died, else 11 for a Boomer shot or 10. Called from the unit-processing loop at $44A0.",
   "inputs": "X = attacker unit, Y = target unit; unit tables, $077E terrain classes, $90FB, $922A",
   "outputs": "$38BB-$38C1 scratch, $923D attacker type, target energy/status updated, sprites 3 and zoom-window line, sound; self-modifies $3A4E, $393F, $3971",
   "confidence": "medium"
  },
  {
   "addr": "3A5A",
   "name": "drawShotLineSprite",
   "summary": "Shows the shot between (zp_93,zp_92) and (zp_95,zp_94) on the 4-pixel-per-cell overview map using sprite 3: disables and clears the sprite, converts both cells to sprite pixels (sub_303A), walks the Bresenham line until the remaining segment fits a 23x20 pixel box, places sprite 3 at the segment's top-left corner (+2) in white, draws the segment with sub_306C (dotted when the attacker is a Boomer, $923D = 2) plus the diamond end marker sub_308A, enables sprite 3 and sets shotLineTimer $91C8 = 8 frames.",
   "inputs": "zp_92-zp_95 = attacker row/col and target row/col (map cells), $923D",
   "outputs": "sprite 3 data ($8AC0), $9013/$901B, $900B=1, $9022 bit 3, $91C8=8; zp_48 used as step counter",
   "confidence": "high"
  },
  {
   "addr": "3B22",
   "name": "applyHitToUnit",
   "summary": "Applies A damage points to unit Y. If the unit belongs to the local player (branch at $3B2C patched per side): a full-energy spy being hit queues message $0E, a comcen hit sets the shake counter $92C8 = 8 and plays sound 8. For any comcen: the player's own comcen gets a random system damaged (sub_3D57), the damage is halved and capped at the remaining energy, scoreDirtyCounter $9208 is bumped and 2*damage is added to the 16-bit score of the attacking side ($92A2/$92A4 indexed by target side). Then reduceUnitEnergy and recordUnitHit run; when the energy reaches 0 the unit is marked destroyed ($F76C bit 7, hit effect $85, redraw) - except a comcen, which stays but ends the game: $0BA8 |= $80 | ($20 or $40 by side) and sub_4027 is entered with X = 1 if the opponent's comcen fell, 0 for the local one.",
   "inputs": "A = damage, Y = target unit",
   "outputs": "$9240 = applied damage, $F9C4/$FA28/$FA8C/$FB54/$F76C of the target, $9208, $92A2-$92A5, $92C8, $0BA8, sounds/messages, unit redraw via sub_48A2",
   "confidence": "medium"
  },
  {
   "addr": "3BDF",
   "name": "reduceUnitEnergyBy",
   "summary": "Stores A into hitDamage $9240 and falls into reduceUnitEnergy. Used by resolveAttack for the first-strike energy sacrifice and by $3EF8 (1 point).",
   "inputs": "A = amount, Y = unit",
   "outputs": "see reduceUnitEnergy",
   "confidence": "high"
  },
  {
   "addr": "3BE2",
   "name": "reduceUnitEnergy",
   "summary": "Subtracts hitDamage $9240 from the low 6 bits of unitEnergy[Y] with a floor of 0, preserving the visibility bits 6-7.",
   "inputs": "Y = unit, $9240",
   "outputs": "$F9C4[Y]; self-modifies the ORA operand at $3BF7",
   "confidence": "high"
  },
  {
   "addr": "3BFC",
   "name": "recordUnitHit",
   "summary": "Caps the damage A at 20, stores $80|damage as the unit's hit effect ($FA8C), subtracts it from the signed suppression value $FA28 (clamped at -127 on overflow) and sets bit 1 of unitStatusFlags ($FB54) to flag the unit for a status refresh.",
   "inputs": "A = damage, Y = unit",
   "outputs": "$FA8C[Y], $FA28[Y], $FB54[Y] bit 1; self-modifies $3C0F",
   "confidence": "medium"
  },
  {
   "addr": "3C20",
   "name": "orderAttackUnitAtCursor",
   "summary": "Reads the map cell under the battlefield cursor (viewOriginCol+3, viewOriginRow+2). If it holds a unit whose side bit ($F708 bit 5) differs from the selected unit's, sends command $87 with cmdParam1 = selected unit and cmdParam2 = target unit (sub_560F); otherwise plays the error sound 3. Fire handler at $21F2.",
   "inputs": "zp_A5/zp_A6, $90F8 selected unit, map, $F708",
   "outputs": "zp_74/zp_75, 3 command bytes queued, or sound 3",
   "confidence": "medium"
  },
  {
   "addr": "32F6",
   "name": "hideSprite0Dead",
   "summary": "Unreferenced copy of hideSprite1 that clears bit 0 (sprite 0) instead; rendered as .byte in the listing.",
   "inputs": "none",
   "outputs": "would clear $9022/$D015 bit 0",
   "confidence": "high"
  },
  {
   "addr": "33C8",
   "name": "setMarkerSpriteDead",
   "summary": "Unreferenced older routine (listed as .byte $33C2-$340A, with a 6-byte offset/colour table at $33C2): stores A in its own operand, and either patches the marker cell into updateMarkerSprite ($3425/$3437) and starts the 90-frame countdown $923E, or on the battlefield places sprite 4 on the overview map via sub_303A minus table offsets. Its JSR $3458 lands mid-instruction in the current build, proving it is stale.",
   "inputs": "A = index 0/1, X/Y = cell",
   "outputs": "dead code",
   "confidence": "medium"
  },
  {
   "addr": "34C6",
   "name": "applyMoveOrderOld",
   "summary": "Unreferenced older executor of the move order (listed as .byte $34C6-$355C): for unit zp_73 clears $F708 bit 6, calls sub_4C6B, snaps a comcen's destination to itself under some condition ($92B4/$92B5 == 4), stores the clamped destination (bit 7 set when equal to the position) in $F7D0/$F834, updates $FB54 bit 7, copies the position into the low bits of $F898/$F8FC unless it is an opponent unit in a linked game ($92FE bit 7), and clears $F960 bits 5-7 and $F76C bit 6. The live version is around $3F9E-$4016.",
   "inputs": "zp_73/zp_74/zp_75",
   "outputs": "dead code",
   "confidence": "medium"
  },
  {
   "addr": "355D",
   "name": "clampMapCoordOld",
   "summary": "Helper of applyMoveOrderOld: negative values become 0, values >= 40 become 39.",
   "inputs": "A = coordinate",
   "outputs": "A clamped to 0..39",
   "confidence": "high"
  },
  {
   "addr": "379E",
   "name": "collectAllUnitsInRadiusDead",
   "summary": "Unreferenced alternate entry to collectUnitsInRadius (listed as .byte): saves A, sets the side filter to 'all units' ($37FF = 0, $3800 = BCC), restores A and falls into $37AA. The two bytes before it ($379C-$379D, $90 $FA) are a leftover branch that overlay A's side-patch routine still writes to ($7BDB), harmlessly.",
   "inputs": "A = radius, X/Y = centre",
   "outputs": "dead code",
   "confidence": "medium"
  },
  {
   "addr": "3C50",
   "name": "assignBoomerTargetOld",
   "summary": "Unreferenced older handler (listed as .byte): X = zp_73, counts the Boomers before it with sub_3C5D and stores zp_75|$80 into boomerTargetTable $91DC[Y]. The live equivalent writes $91DC at $4454.",
   "inputs": "zp_73 = unit, zp_75 = target",
   "outputs": "dead code",
   "confidence": "medium"
  }
 ],
 "variables": [
  {
   "addr": "18",
   "scope": "zp",
   "name": "tempByte18",
   "meaning": "scratch: list loop index in scanVisibleEnemies, energy temp in resolveAttack",
   "confidence": "high"
  },
  {
   "addr": "1A",
   "scope": "zp",
   "name": "tempByte1A",
   "meaning": "scratch in resolveAttack: damage-table row offset (0/5/10 by attacker type), later the attacker's facing",
   "confidence": "high"
  },
  {
   "addr": "1C",
   "scope": "zp",
   "name": "targetTerrainClass",
   "meaning": "terrain class (0-8) of the cell under the attack target",
   "confidence": "medium"
  },
  {
   "addr": "1D",
   "scope": "zp",
   "name": "attackerTerrainClass",
   "meaning": "terrain class of the cell under the attacker",
   "confidence": "medium"
  },
  {
   "addr": "1E",
   "scope": "zp",
   "name": "losHeight",
   "meaning": "running terrain height along the line of sight (checkLineOfSightStep); in resolveAttack the target's terrain height",
   "confidence": "high"
  },
  {
   "addr": "1F",
   "scope": "zp",
   "name": "losRise",
   "meaning": "$80 until the line of sight went uphill, then the last positive height step; a fall afterwards blocks the view",
   "confidence": "high"
  },
  {
   "addr": "20",
   "scope": "zp",
   "name": "scanMode",
   "meaning": "bit 7 clear = spotting pass, set = attack-target pass (X on entry to scanVisibleEnemies)",
   "confidence": "high"
  },
  {
   "addr": "21",
   "scope": "zp",
   "name": "scanUnit",
   "meaning": "unit whose sight/fire range is being scanned",
   "confidence": "high"
  },
  {
   "addr": "22",
   "scope": "zp",
   "name": "scanCol",
   "meaning": "collectUnitsInRadius: radius on entry, then the current map column of the scan",
   "confidence": "high"
  },
  {
   "addr": "23",
   "scope": "zp",
   "name": "scanStartCol",
   "meaning": "first (clipped) map column of the scan square",
   "confidence": "high"
  },
  {
   "addr": "24",
   "scope": "zp",
   "name": "scanRow",
   "meaning": "current map row of the scan square",
   "confidence": "high"
  },
  {
   "addr": "25",
   "scope": "zp",
   "name": "scanColCount",
   "meaning": "number of columns to scan per row (2r+1, clipped at the left edge)",
   "confidence": "high"
  },
  {
   "addr": "26",
   "scope": "zp",
   "name": "scanRowCount",
   "meaning": "number of rows left to scan",
   "confidence": "high"
  },
  {
   "addr": "27",
   "scope": "zp",
   "name": "scanCentreCol",
   "meaning": "centre column of the scan (distanceSquared reference)",
   "confidence": "high"
  },
  {
   "addr": "28",
   "scope": "zp",
   "name": "scanCentreRow",
   "meaning": "centre row of the scan",
   "confidence": "high"
  },
  {
   "addr": "29",
   "scope": "zp",
   "name": "scanListIndex",
   "meaning": "index of the last entry written to scanUnitList ($FF = empty)",
   "confidence": "high"
  },
  {
   "addr": "2F",
   "scope": "zp",
   "name": "scanListLast",
   "meaning": "copy of the last list index returned by collectUnitsInRadius; read by the callers of scanVisibleEnemies ($43AD, $43DF, $3CA0)",
   "confidence": "high"
  },
  {
   "addr": "48",
   "scope": "zp",
   "name": "mapCellPtr",
   "meaning": "pointer (lo) to the current map cell set by sub_25FA and advanced by 40 per row in collectUnitsInRadius (same zp as fillPtr); drawShotLineSprite reuses it as a step counter",
   "confidence": "high"
  },
  {
   "addr": "49",
   "scope": "zp",
   "name": "mapCellPtrHi",
   "meaning": "map cell pointer (hi)",
   "confidence": "high"
  },
  {
   "addr": "6B",
   "scope": "zp",
   "name": "targetDirection",
   "meaning": "direction code (0 N, 1 S, 2 E, 3 W) of the unit's current target; candidates in another direction get +1 distance in scanVisibleEnemies; written from scanDirectionList by the target pickers ($3CD3, $3CEE, $4420, $4446) and copied into $F708 bits 0-1 (facing) at $4464",
   "confidence": "medium"
  },
  {
   "addr": "73",
   "scope": "zp",
   "name": "cmdParam0",
   "meaning": "first parameter of an outgoing command (unit index)",
   "confidence": "high"
  },
  {
   "addr": "74",
   "scope": "zp",
   "name": "cmdParam1",
   "meaning": "second command parameter (destination column / attacker unit)",
   "confidence": "high"
  },
  {
   "addr": "75",
   "scope": "zp",
   "name": "cmdParam2",
   "meaning": "third command parameter (destination row / target unit)",
   "confidence": "high"
  },
  {
   "addr": "92",
   "scope": "zp",
   "name": "lineCurRow",
   "meaning": "line walker current row (start row on entry); also the cursor row",
   "confidence": "high"
  },
  {
   "addr": "93",
   "scope": "zp",
   "name": "lineCurCol",
   "meaning": "line walker current column",
   "confidence": "high"
  },
  {
   "addr": "94",
   "scope": "zp",
   "name": "lineEndRow",
   "meaning": "line walker target row",
   "confidence": "high"
  },
  {
   "addr": "95",
   "scope": "zp",
   "name": "lineEndCol",
   "meaning": "line walker target column",
   "confidence": "high"
  },
  {
   "addr": "96",
   "scope": "zp",
   "name": "lineStepRow",
   "meaning": "row step of the line (+1/-1); sign gives north/south in initLineFromUnit",
   "confidence": "high"
  },
  {
   "addr": "97",
   "scope": "zp",
   "name": "lineStepCol",
   "meaning": "column step of the line (+1/-1); sign gives east/west",
   "confidence": "high"
  },
  {
   "addr": "98",
   "scope": "zp",
   "name": "spritePlotPtr",
   "meaning": "pointer (lo) into sprite data used by the sprite pixel plotter sub_31AF; loadDirectionShapeSprite uses it as the copy source pointer",
   "confidence": "high"
  },
  {
   "addr": "99",
   "scope": "zp",
   "name": "spritePlotPtrHi",
   "meaning": "sprite data pointer (hi)",
   "confidence": "high"
  },
  {
   "addr": "A5",
   "scope": "zp",
   "name": "viewOriginCol",
   "meaning": "map column of the left edge of the battlefield view; cursor cell = +3",
   "confidence": "high"
  },
  {
   "addr": "A6",
   "scope": "zp",
   "name": "viewOriginRow",
   "meaning": "map row of the top edge of the battlefield view; cursor cell = +2",
   "confidence": "high"
  },
  {
   "addr": "B1",
   "scope": "zp",
   "name": "gamePhase",
   "meaning": "0 during battle; nonzero in setup/menu phases (units do not scan or attack)",
   "confidence": "medium"
  },
  {
   "addr": "0200",
   "scope": "abs",
   "name": "spriteShapeBuffer",
   "meaning": "$0200-$03FF: 8 x 64-byte sprite shapes produced by the RLE decompressor sub_67CB (sources $FD70, $FE24 in overlay B, unit pictures $CD52.. for sub_CD0A); loadDirectionShapeSprite reads shape A from $0200+64*A. Overwrites the saved setup block loaded at boot.",
   "confidence": "medium"
  },
  {
   "addr": "0400",
   "scope": "abs",
   "name": "scanUnitList",
   "meaning": "up to 50 unit indices found by collectUnitsInRadius ($FF = entry rejected); reuses the copy-protection table area",
   "confidence": "high"
  },
  {
   "addr": "0432",
   "scope": "abs",
   "name": "scanDistSqList",
   "meaning": "squared distance of each scanUnitList entry from the scanning unit",
   "confidence": "high"
  },
  {
   "addr": "0464",
   "scope": "abs",
   "name": "scanDirectionList",
   "meaning": "principal direction code (0 N,1 S,2 E,3 W) from the scanning unit to each list entry",
   "confidence": "high"
  },
  {
   "addr": "077E",
   "scope": "abs",
   "name": "terrainClassTable",
   "meaning": "128-entry table indexed by map cell value (0-$7F): terrain class 0-8 (blank $40 -> 0, $41-$54 -> 5, $55-$5C -> 1, $5D-$60 -> 2, $61-$64 -> 4, $65-$66 -> 6, $67/$6C -> 7, $68-$6B/$6F-$72 -> 8, $6D -> 3, $6E -> 5); the class indexes terrainHeightTable and terrainSightBonusTable",
   "confidence": "medium"
  },
  {
   "addr": "0B9F",
   "scope": "abs",
   "name": "playerSide",
   "meaning": "local player's side 0/1; nonzero reverses the scan list order; overlay A uses it to patch the side-test branches in this chunk",
   "confidence": "high"
  },
  {
   "addr": "0BA8",
   "scope": "abs",
   "name": "noGameInProgress",
   "meaning": "nonzero when no game runs; applyHitToUnit ORs in $80 | $20 (side-0 comcen destroyed) or $80 | $40 (side-1 comcen destroyed) when a comcen dies",
   "confidence": "medium"
  },
  {
   "addr": "8A00",
   "scope": "abs",
   "name": "sprite0Data",
   "meaning": "sprite 0 shape (64 bytes); sprite n data lives at $8A00+64*n (pointers $A8+n in the $8C00 screen)",
   "confidence": "high"
  },
  {
   "addr": "8A80",
   "scope": "abs",
   "name": "sprite2Data",
   "meaning": "sprite 2 shape: battlefield centre cursor box / crosshair, or the comcen cross marker in overlay B",
   "confidence": "high"
  },
  {
   "addr": "8AC0",
   "scope": "abs",
   "name": "sprite3Data",
   "meaning": "sprite 3 shape: shot line drawn by drawShotLineSprite on the overview map",
   "confidence": "high"
  },
  {
   "addr": "8B40",
   "scope": "abs",
   "name": "sprite5Data",
   "meaning": "sprite 5 shape (direction shape when $91CC = 5; first of the 3-sprite unit picture copied by sub_CD0A)",
   "confidence": "medium"
  },
  {
   "addr": "900A",
   "scope": "abs",
   "name": "sprite2ColorShadow",
   "meaning": "colour of sprite 2 (0 black cursor, $0A comcen marker)",
   "confidence": "high"
  },
  {
   "addr": "900B",
   "scope": "abs",
   "name": "sprite3ColorShadow",
   "meaning": "colour of sprite 3 (1 white for the shot line)",
   "confidence": "high"
  },
  {
   "addr": "900C",
   "scope": "abs",
   "name": "sprite4ColorShadow",
   "meaning": "colour of sprite 4 ($12 = flashing white/red comcen rings)",
   "confidence": "high"
  },
  {
   "addr": "9012",
   "scope": "abs",
   "name": "sprite2XShadow",
   "meaning": "sprite 2 X position (low byte)",
   "confidence": "high"
  },
  {
   "addr": "9013",
   "scope": "abs",
   "name": "sprite3XShadow",
   "meaning": "sprite 3 X position",
   "confidence": "high"
  },
  {
   "addr": "9014",
   "scope": "abs",
   "name": "sprite4XShadow",
   "meaning": "sprite 4 X position",
   "confidence": "high"
  },
  {
   "addr": "901A",
   "scope": "abs",
   "name": "sprite2YShadow",
   "meaning": "sprite 2 Y position",
   "confidence": "high"
  },
  {
   "addr": "901B",
   "scope": "abs",
   "name": "sprite3YShadow",
   "meaning": "sprite 3 Y position",
   "confidence": "high"
  },
  {
   "addr": "901C",
   "scope": "abs",
   "name": "sprite4YShadow",
   "meaning": "sprite 4 Y position",
   "confidence": "high"
  },
  {
   "addr": "9020",
   "scope": "abs",
   "name": "spriteXMsbShadow",
   "meaning": "shadow of $D010; bit 2 set puts the cursor sprite at X=256",
   "confidence": "high"
  },
  {
   "addr": "9022",
   "scope": "abs",
   "name": "spriteEnableShadow",
   "meaning": "shadow of $D015; sub_31A3 sets bits, the routines here clear bits 1/3/4 directly",
   "confidence": "high"
  },
  {
   "addr": "9023",
   "scope": "abs",
   "name": "spriteXExpandShadow",
   "meaning": "shadow of $D01D (bit 2 set for the cursor sprite)",
   "confidence": "high"
  },
  {
   "addr": "9024",
   "scope": "abs",
   "name": "spriteYExpandShadow",
   "meaning": "shadow of $D017",
   "confidence": "high"
  },
  {
   "addr": "90F8",
   "scope": "abs",
   "name": "selectedUnit",
   "meaning": "index of the currently selected unit",
   "confidence": "high"
  },
  {
   "addr": "90FB",
   "scope": "abs",
   "name": "currentScreenId",
   "meaning": "0 = battlefield (shots are drawn only then)",
   "confidence": "high"
  },
  {
   "addr": "91C8",
   "scope": "abs",
   "name": "shotLineTimer",
   "meaning": "frames the shot-line sprite 3 stays visible (set to 8; $415A disables the sprite when it reaches 0)",
   "confidence": "high"
  },
  {
   "addr": "91CC",
   "scope": "abs",
   "name": "activeSpriteIndex",
   "meaning": "sprite number (0-7) that the sprite plotting/copy routines (sub_31AF, sub_3239, loadDirectionShapeSprite) target; bit 7 set = plot into the $9025 scratch shape",
   "confidence": "medium"
  },
  {
   "addr": "91DC",
   "scope": "abs",
   "name": "boomerTargetTable",
   "meaning": "per-Boomer ordered target unit ($80|unit, 0 = none), indexed by the Boomer's ordinal among type-2 units (sub_3C5D)",
   "confidence": "medium"
  },
  {
   "addr": "91F1",
   "scope": "abs",
   "name": "scanRange",
   "meaning": "sight/fire range (0-10) computed by scanVisibleEnemies; the callers then reuse it as the best candidate distance",
   "confidence": "medium"
  },
  {
   "addr": "91F3",
   "scope": "abs",
   "name": "spritePlotMode",
   "meaning": "pixel operation of sub_31AF: $00 clear (AND), $40 set (ORA), $80 toggle (EOR)",
   "confidence": "high"
  },
  {
   "addr": "9208",
   "scope": "abs",
   "name": "scoreDirtyCounter",
   "meaning": "incremented whenever a side scores; $C519 redraws the score line when it differs from $91CB",
   "confidence": "medium"
  },
  {
   "addr": "9227",
   "scope": "abs",
   "name": "scanUnitTerrain",
   "meaning": "map cell value under the scanning unit",
   "confidence": "high"
  },
  {
   "addr": "9228",
   "scope": "abs",
   "name": "scanUnitType",
   "meaning": "type (0-4) of the scanning unit",
   "confidence": "high"
  },
  {
   "addr": "9229",
   "scope": "abs",
   "name": "scanUnitTerrainHeight",
   "meaning": "terrain height under the scanning unit; nonzero lets the unit see over friendly units",
   "confidence": "high"
  },
  {
   "addr": "922A",
   "scope": "abs",
   "name": "comcenStunStatus",
   "meaning": "bit 6 set while the comcen is knocked out (no sprite enabling, no shot drawing)",
   "confidence": "medium"
  },
  {
   "addr": "9236",
   "scope": "abs",
   "name": "comcenUnit",
   "meaning": "index of the local player's COMCEN (49 or 99)",
   "confidence": "high"
  },
  {
   "addr": "923D",
   "scope": "abs",
   "name": "attackerType",
   "meaning": "type of the attacking unit in resolveAttack (2 = Boomer selects dotted shot line and sound 11)",
   "confidence": "high"
  },
  {
   "addr": "923E",
   "scope": "abs",
   "name": "markerTimer",
   "meaning": "countdown gating updateMarkerSprite; never set nonzero in this build (only by dead code)",
   "confidence": "medium"
  },
  {
   "addr": "9240",
   "scope": "abs",
   "name": "hitDamage",
   "meaning": "damage currently being applied to a unit (reduceUnitEnergy/recordUnitHit/applyHitToUnit)",
   "confidence": "high"
  },
  {
   "addr": "92A2",
   "scope": "abs",
   "name": "sideScoreLo",
   "meaning": "low bytes of two 16-bit scores indexed by the side whose comcen was hit (0/1): each comcen hit adds 2 x damage; read by the score display ($2906, $5845, $C6E6)",
   "confidence": "medium"
  },
  {
   "addr": "92A4",
   "scope": "abs",
   "name": "sideScoreHi",
   "meaning": "high bytes of the two 16-bit scores",
   "confidence": "medium"
  },
  {
   "addr": "92C8",
   "scope": "abs",
   "name": "spriteShakeFrames",
   "meaning": "set to 8 when the local comcen is hit; while nonzero the IRQ adds the fine-scroll offsets to the sprite positions (screen shake)",
   "confidence": "medium"
  },
  {
   "addr": "F640",
   "scope": "abs",
   "name": "unitCol",
   "meaning": "map column of each unit (100 entries; units 0-49 side 0, 50-99 side 1, comcen = 49/99)",
   "confidence": "high"
  },
  {
   "addr": "F6A4",
   "scope": "abs",
   "name": "unitRow",
   "meaning": "map row of each unit",
   "confidence": "high"
  },
  {
   "addr": "F708",
   "scope": "abs",
   "name": "unitFlags",
   "meaning": "per-unit: bits 0-1 facing (0 N,1 S,2 E,3 W), bit 5 side, bit 7 first-strike bonus available (consumed by resolveAttack); bits 2-4/6 group-related",
   "confidence": "medium"
  },
  {
   "addr": "F76C",
   "scope": "abs",
   "name": "unitType",
   "meaning": "per-unit: bits 0-2 type (0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN per the names at $C964), bit 6 combat modifier (+2 dealt, -25% taken), bit 7 destroyed",
   "confidence": "high"
  },
  {
   "addr": "F7D0",
   "scope": "abs",
   "name": "unitDestCol",
   "meaning": "per-unit ordered destination column, bit 7 set while a move is pending",
   "confidence": "medium"
  },
  {
   "addr": "F834",
   "scope": "abs",
   "name": "unitDestRow",
   "meaning": "per-unit ordered destination row (bit 7 flag)",
   "confidence": "medium"
  },
  {
   "addr": "F898",
   "scope": "abs",
   "name": "unitAltCol",
   "meaning": "per-unit secondary column (bits 0-5) with flags: bit 6 toggled by MISC-menu commands $84/$85, bit 7 = unit inactive/hidden (does not scan or attack, harder to spot)",
   "confidence": "low"
  },
  {
   "addr": "F8FC",
   "scope": "abs",
   "name": "unitAltRow",
   "meaning": "per-unit secondary row (bits 0-5); bit 6 toggled by commands $95/$96; bit 7 set = unit is moving (enables the first-strike energy sacrifice)",
   "confidence": "low"
  },
  {
   "addr": "F960",
   "scope": "abs",
   "name": "unitStunTable",
   "meaning": "per-unit stun counter (low nibble)",
   "confidence": "medium"
  },
  {
   "addr": "F9C4",
   "scope": "abs",
   "name": "unitEnergy",
   "meaning": "per-unit: bits 0-5 energy 0-50, bit 6 spotted in the current scan cycle, bit 7 visible to the local player",
   "confidence": "high"
  },
  {
   "addr": "FA28",
   "scope": "abs",
   "name": "unitSuppression",
   "meaning": "per-unit signed value reduced by each hit (clamped at -127); read by the status display and movement cost code",
   "confidence": "low"
  },
  {
   "addr": "FA8C",
   "scope": "abs",
   "name": "unitHitEffect",
   "meaning": "per-unit effect code: $80|damage after a hit, $85 when destroyed, 0 none",
   "confidence": "medium"
  },
  {
   "addr": "FAF0",
   "scope": "abs",
   "name": "unitTerrainCell",
   "meaning": "map cell value that the unit stands on (the map itself holds $80|unit); $FF for dead units",
   "confidence": "high"
  },
  {
   "addr": "FB54",
   "scope": "abs",
   "name": "unitStatusFlags",
   "meaning": "per-unit: bit 0 attacked, bit 1 hit (status refresh), bits 2-6 group/formation, bit 6 cleared by a move order, bit 7 unit is at its ordered destination",
   "confidence": "medium"
  }
 ],
 "dataBlocks": [
  {
   "addr": "32DA",
   "length": 16,
   "type": "byteTable",
   "name": "circlePointBuffer",
   "description": "RAM: 8 X coordinates ($32DA-$32E1) and 8 Y coordinates ($32E2-$32E9) of the current octant point of a circle, filled by sub_3266 and plotted by sub_32BF"
  },
  {
   "addr": "33C2",
   "length": 6,
   "type": "byteTable",
   "name": "markerOffsetTableDead",
   "description": "three 2-entry tables (X offset $0A/$08, Y offset $08/$09, colour $02/$04) used only by the dead routine at $33C8"
  },
  {
   "addr": "35A6",
   "length": 10,
   "type": "byteTable",
   "name": "sightRangeByType",
   "description": "range in cells per unit type: entries 0-4 spotting (Grunt 3, Rider 3, Boomer 3, Spy 5, Comcen 3), entries 5-9 attacking (3, 3, 6, 5, 3)"
  },
  {
   "addr": "35B0",
   "length": 11,
   "type": "byteTable",
   "name": "rangeDistanceSqTable",
   "description": "r*(r+1) for r = 0..10: squared-distance threshold that a target must be below to be within range r"
  },
  {
   "addr": "35BB",
   "length": 8,
   "type": "byteTable",
   "name": "terrainSightBonusTable",
   "description": "extra range by terrain class 0-7 (0,0,0,1,1,2,2,0), doubled for spotting"
  },
  {
   "addr": "3860",
   "length": 16,
   "type": "byteTable",
   "name": "squaresTable",
   "description": "n*n for n = 0..15 (also used by overlay A $7831 and the comm module $E832)"
  },
  {
   "addr": "38A4",
   "length": 15,
   "type": "byteTable",
   "name": "baseDamageTable",
   "description": "base damage [attacker type 0-2][target type 0-4]: Grunt 4,5,25,8,12; Rider 3,4,20,6,8; Boomer 12,16,8,8,20"
  },
  {
   "addr": "38B3",
   "length": 8,
   "type": "byteTable",
   "name": "terrainHeightTable",
   "description": "height/cover level by terrain class 0-7 (0,0,0,1,2,3,4,0) used for line of sight and the combat height bonus"
  },
  {
   "addr": "38BB",
   "length": 7,
   "type": "byteTable",
   "name": "combatScratch",
   "description": "RAM: $38BB target unit, $38BC attacker unit, $38BD damage accumulator, $38BE/$38BF attacker row/col, $38C0/$38C1 target row/col (saved around the overlay shot drawing)"
  }
 ],
 "misclassified": [
  {
   "addr": "32F6",
   "length": 12,
   "actual": "code",
   "evidence": "decodes as LDA $9022 / AND #$FE / STA $9022 / STA $D015 / RTS, an unreferenced twin of sub_32EA for sprite 0"
  },
  {
   "addr": "33C8",
   "length": 67,
   "actual": "code",
   "evidence": "clean 6502 sequence (STA $33EA / CMP #0 / BNE / LDA $920D / BPL / JSR $3405 / STX $3425 / STY $3437 / JMP $341F ...) that patches the operands of sub_341F and reads $90FB; unreferenced dead code. $33C2-$33C7 remain data used by it"
  },
  {
   "addr": "34C6",
   "length": 162,
   "actual": "code",
   "evidence": "decodes as a complete routine ($34C6-$355C, RTS at $355C) plus a clamp helper $355D-$3567 operating on $73-$75 and the unit tables; unreferenced older move-order executor"
  },
  {
   "addr": "379E",
   "length": 12,
   "actual": "code",
   "evidence": "PHA / LDA #0 / STA $37FF / LDA #$90 / STA $3800 / PLA falling into sub_37AA: an alternate 'all units' entry of collectUnitsInRadius; $379C-$379D ($90 $FA) is a leftover branch opcode that overlay A still patches at $7BDB"
  },
  {
   "addr": "3C50",
   "length": 13,
   "actual": "code",
   "evidence": "LDX $73 / JSR $3C5D / LDA $75 / ORA #$80 / STA $91DC,Y / RTS - unreferenced older Boomer-target handler"
  },
  {
   "addr": "36D5",
   "length": 2,
   "actual": "code",
   "evidence": "labelled D_36D5 only because overlay A writes the branch opcode ($90/$B0 by player side) at $7BD8; likewise D_3B2C ($7BEC), D_3BD0 ($7BEF) and D_3800 (patched by sub_378B/sub_3576) are instructions"
  }
 ],
 "insights": [
  "Address/line mismatch: the chunk spec says lines 5252-6187 but those lines hold $3040-$3625; the address range $32DA-$3C4F is at file lines 5604-6835 and that is what this survey covers.",
  "Unit records are 15 parallel 100-byte arrays starting at $F640 (stride $64): $F640 col, $F6A4 row, $F708 flags (facing bits 0-1, side bit 5, first-strike bit 7), $F76C type (0 GRUNT 1 RIDER 2 BOOMER 3 SPY 4 COMCEN, bit 7 destroyed), $F7D0/$F834 destination col/row (bit 7 pending), $F898/$F8FC secondary col/row with mode bits, $F960 stun, $F9C4 energy (0-50) + visibility bits 6/7, $FA28 signed suppression, $FA8C hit effect, $FAF0 terrain cell under the unit, $FB54 status flags. Units 0-49 belong to side 0 with the comcen at 49, units 50-99 to side 1 with the comcen at 99 ($0C8A table, $9236).",
  "The map cell of an occupied square holds $80|unitIndex; the terrain the unit covers is kept in $FAF0. Code often indexes the tables with Y = cell value by using a base 128 lower ($F688,y = $F708+unit, $F6EC,y = $F76C+unit, $FA70,y = $FAF0+unit). Terrain values map to classes 0-8 through $077E, and classes to heights through $38B3 (LOS and combat) and sight bonuses through $35BB.",
  "Side-dependent branches are patched at run time: overlay A's routine at $7BC6-$7BF8 writes BCC/BCS ($90/$B0 chosen by playerSide $0B9F) into the 'cpy #$32' tests at $206B, $2084, $2840, $36D5, $379C (stale), $3B2C, $3BD0, $3F9E, $4783, $5BF7, $63E2 and sets comcenUnit $9236. The unit scanner has its own filter patch ($37FF compare value, $3800 opcode) set by setScanFilterOppositeSide.",
  "Visibility model: each unit-processing cycle calls ageUnitVisibility (energy bit 7 := bit 6, bit 6 := 0), then every own unit runs scanVisibleEnemies in spotting mode (X=0) which sets bit 6 on everything it sees and bit 7 + redraw on newly seen units, followed by an attack-target pass (X=$80) whose best candidate (closest, comcen preferred) is attacked via resolveAttack. Sight ranges come from $35A6 (+terrain bonus, max 10) and distance tests use squared distances against r*(r+1) from $35B0; line of sight uses the Bresenham walker $FBB8/$FBFA with a ridge rule (a descent after an ascent blocks) in checkLineOfSightStep. Boomers fire at range 6 without line of sight but only at already-visible targets; spies see 5 cells and are the only units that can spot spies beyond dist2 20.",
  "Combat (resolveAttack/applyHitToUnit): damage = baseDamageTable[attacker][target] + optional one-time first-strike energy sacrifice (up to 15, Boomers 5) + terrain height difference + 10 on class-1 terrain (+2 on class 3/5) + facing bonus (20 from behind, 10 from the flank, 0 head-on) +/- the $F76C bit 6 modifier; half of that is applied. Comcens take half again, capped at remaining energy, and every comcen hit adds 2x damage to the attacker's 16-bit score ($92A2/$92A4 per side) and bumps $9208. A comcen reaching 0 energy ends the game: $0BA8 |= $80|$20<<(side+0/1) and sub_4027 is entered with X=1 if the opponent's comcen died, 0 if the local one did.",
  "Sprite allocation seen here (data at $8A00+64*n): sprite 1 = unit selection marker (hidden by sub_32EA), sprite 2 = black double-size centre cursor of the battlefield view (box $9EA4 or diamond $9EE3 at X=256,Y=$4D) and the comcen cross marker in overlay B, sprite 3 = white shot line on the 4px/cell overview map (shown 8 frames, $91C8), sprite 4 = comcen rings / vestigial marker, sprites 0/5 = direction shapes from the $0200 buffer, sprites 5-7 = unit picture via sub_CD0A. sub_31A3 is 'enable sprites in A' (suppressed while the comcen is knocked out), sub_2FB2 clears sprite A's data, sub_31AF/sub_31CA plot a pixel (X,Y) into sprite $91CC with mode $91F3, sub_3239 draws a circle of radius X from the octant tables at $9F37-$9F68.",
  "$0200-$03FF is reused at run time as an RLE-decompressed sprite-shape buffer (sub_67CB, escape-byte run-length format; 8 shapes of 64 bytes). Overlay B decompresses $FD70 (screen 1) and $FE24 (screen 2) from the 'unreferenced' high-memory data into it; sub_CD0A decompresses the five unit pictures at $CD52/$CDD3/$CE3B/$CECD/$CF3A and copies the first 192 bytes into sprites 5-7.",
  "Five unreferenced code fragments in this range are rendered as .byte: $32F6 (hide sprite 0), $33C8 (old marker-sprite setter, stale JSR into the middle of sub_3456), $34C6 (old move-order executor with clamp helper $355D), $379E (all-units scan entry), $3C50 (old Boomer target assignment). Together with the never-written $923E/$3425/$3437 they show this build kept an older, partly removed feature (a timed marker sprite).",
  "Self-modifying operands in this chunk: $346E loop counter, $34C1 ORA value, $3373/$3374 copy destination, $359C ORA value, $35F9 range-table index, $362B distance threshold, $37FF/$3800 scan filter, $385A reverse-loop bound, $393F sacrifice amount, $3971 damage reduction, $3A4E sound number, $3BF7 visibility bits, $3C0F hit amount, and the marker cell at $3425/$3437. Annotators should treat these as variables, not constants.",
  "The scan list $0400/$0432/$0464 (50 entries each) overlays the copy-protection table, which is only needed during the manual check at start-up."
 ]
}