{
 "chunk": "game_main_0800_2762",
 "unit": "game/main_0800",
 "routines": [
  {"addr":"2772", "name":"renderMapTile",
   "summary":"Builds one 16x16 pixel multicolour map tile (2x2 characters, 32 bytes) in the tile buffer at $04C8-$04E7 for the map cell byte in A at map coordinates (zp_A5, zp_A6). Off-map coordinates (>= 40 or negative) use border shapes $68-$6B; terrain $40 gives an empty tile; otherwise the 8x8 1-bit terrain shape at $9C6E+shape*8 is doubled in both directions through nybbleToMulticolourTable and the terrain colour (D_07BE/D_07EA) is written to the four screen-RAM cells at (zp_B6). If bit 7 of the cell byte is set the low 7 bits are a unit index: the 32-byte unit glyph (D_6C35 table, index = (D_F708&3) + D_0799[type] - $2C) is ORed into the buffer, inverted when the unit is the selected unit (D_9204) or belongs to the selected group, drawn on every other scanline when D_F898 bit 7 is set, and the unit colour (zp_77 own / zp_78 enemy, 1 = white when flashing) is written to colour RAM. Enemy units are skipped unless spotted (D_F9C4 bit 7), reveal-all (D_92FC bit 7) or the omniscient replay view (D_0B9B >= $82) is active. The caller (sub_26C1 and $5D45) copies the buffer to the bitmap at (zp_B4).",
   "inputs":"A = map cell byte; zp_A5/zp_A6 = map column/row; zp_B6/B7 = screen RAM cell pointer; zp_B1 bit 6 suppresses unit drawing; patched branch opcodes at $2840/$2846 select which unit-index half (0-49 or 50-99) is 'own'",
   "outputs":"$04C8-$04E7 tile buffer; 4 screen RAM bytes at (zp_B6)+0,1,40,41; 4 colour RAM bytes when a unit is drawn (zp_B7 is advanced by $4C to point into $D800); zp_32 = colour; clobbers A,X,Y, zp_B2/B3, self-modified operands at $2808,$27D4,$27F2,$28B8,$28C0",
   "confidence":"high"},
  {"addr":"28E1", "name":"drawMainWindowFrame",
   "summary":"Sets the multicolour text colour zp_3D = $0C and draws framed window number 3, or number 4 when D_90FB is non-zero, through sub_2C7F (window table at D_2BDD/D_2BF8/D_2C13/D_2C2E/D_2C49).",
   "inputs":"D_90FB", "outputs":"zp_3D; window frame on screen; D_2A2D/D_2A2E/D_9221/D_9222 set by sub_2C7F", "confidence":"medium"},
  {"addr":"28F1", "name":"computeGameResult",
   "summary":"Game-over scoring. Clears the message queue (sub_C6EF), sets D_923F = $C0 and, unless D_0BA8 bit 7 (knockout) is set, queues message $16 ('GAME OVER') and compares the two 16-bit player point totals D_92A2/D_92A4 (player 0) and D_92A3/D_92A5 (player 1): equal -> both become 250 and message $13 (draw); otherwise the winner's total becomes 300 + min(|difference|/2, 150), the loser gets 200, and message $11 ('won on points') or $12 is queued depending on whether the winner is the local player (D_0B9F). For a knockout, D_0BA8 bits 5-6 = $60 means a draw (250 each), otherwise bit 6 selects the winner who gets 500 and the loser 150 with message $14/$15. Finally the chosen message is queued with sub_C566 and D_92A1/D_0548 are set to $9E (no message showing) and D_929C cleared. Called from sub_4027.",
   "inputs":"Y (preserved); D_0BA8, D_0B9F, D_92A2-D_92A5", "outputs":"D_92A2-D_92A5 rewritten with result points; D_92A1 = $9E, D_0548 = $9E, D_929C = 0, D_923F = $C0; messages queued; zp_18/19 scratch",
   "confidence":"medium"},
  {"addr":"29B3", "name":"setOtherPlayerPoints",
   "summary":"Helper of computeGameResult: stores Y as the 16-bit point total of the player other than X (X ^ 1), high byte 0.",
   "inputs":"X = player index, Y = value", "outputs":"D_92A2[X^1] = Y, D_92A4[X^1] = 0; X = X^1", "confidence":"high"},
  {"addr":"29C1", "name":"shakeScreenScroll",
   "summary":"Screen shake: takes a pseudo-random byte from sub_FD5D and uses bits 0-2 as the vertical fine scroll and bits 3-5 as the horizontal fine scroll, each only if non-zero and smaller than the shake counter D_92C8, otherwise the normal values ($0B for $D011, $08 for $D016). The results are merged into the shadow registers D_9000 ($D011) and D_9001 ($D016) and remembered in D_92F5/D_92F4. Called once per frame at $41C7 while D_92C8 counts down.",
   "inputs":"D_92C8, D_9000, D_9001, PRNG state zp_57-59", "outputs":"D_9000, D_9001, D_92F4, D_92F5; self-modified operands $29D6,$29E6,$29F3,$2A03", "confidence":"high"},
  {"addr":"2A08", "name":"patchUnitSideBranches",
   "summary":"Copies the BCC/BCS opcode stored at $2840 (set from the local player index by sub_7BC6 in the overlay) to the visibility-test branch at $2846 in renderMapTile and to $5BFE in the other map renderer, inverting it (BCC <-> BCS, EOR $20) when D_0B9B bit 0 is set, i.e. when the replay viewpoint is the other player's side. Called at start-up ($0AD9), after the map redraw setup ($2639) and when the replay viewpoint key cycles D_0B9B ($4134).",
   "inputs":"D_0B9B bit 0, opcode byte at $2840", "outputs":"opcode bytes at $2846 and $5BFE", "confidence":"high"},
  {"addr":"2A1A", "name":"printOptionWord",
   "summary":"Prints the 4-character option word number A from the table at $0652 ('FULL','HALF','NONE', bit 7 terminated) through the text printer sub_C133. Used by the overlays to show the per-player option D_92A6.",
   "inputs":"A = word index (0-2)", "outputs":"text printed at the current text cursor; A/Y = string pointer passed to sub_C133", "confidence":"high"},
  {"addr":"2A38", "name":"setBitmapDrawMode",
   "summary":"Selects how glyph bitmap bytes are combined with the existing bitmap: Y = 0 replace (LDA), 1 OR, 2 EOR, 3 keep existing (CMP). Patches the opcode at $2AF3 in plotGlyph and the identical instruction at $C22B in the $C000 overlay's text plotter. A is preserved.",
   "inputs":"Y = mode 0-3, A preserved", "outputs":"opcode at $2AF3 and $C22B; operand at $2A45", "confidence":"high"},
  {"addr":"2A47", "name":"setColourDrawMode",
   "summary":"Selects how the colour byte D_2A2A is combined with existing screen RAM colour cells: Y = 0 replace, 1 OR, 2 EOR, 3 keep. Patches the opcode at $2B00 (plotGlyph) and $2A85 (fillScreenColourRect). A is preserved.",
   "inputs":"Y = mode 0-3, A preserved", "outputs":"opcode at $2B00 and $2A85; operand at $2A54", "confidence":"high"},
  {"addr":"2A56", "name":"resetDrawModes",
   "summary":"Sets both the bitmap and the colour draw modes back to 0 (replace). Y is preserved.",
   "inputs":"Y preserved", "outputs":"opcodes at $2AF3, $C22B, $2B00, $2A85 = LDA", "confidence":"high"},
  {"addr":"2A64", "name":"fillScreenColourRect",
   "summary":"Fills an X columns by Y rows rectangle of screen RAM (the multicolour bitmap's colour cells, base $8C00) starting at window origin (D_2A2D row, D_2A2E column) with the colour byte A, combined according to the colour draw mode. Stores A in D_2A2A first.",
   "inputs":"A = colour byte, X = width, Y = height, D_2A2D/D_2A2E origin", "outputs":"screen RAM cells; D_2A2A = A; zp_B6/B7, zp_1B, zp_1C clobbered", "confidence":"high"},
  {"addr":"2A9F", "name":"calcCellPointers",
   "summary":"Computes the bitmap address and screen RAM address of the character cell at row zp_18, column zp_19 (falls into calcCellPointersXY).",
   "inputs":"zp_18 = row 0-24, zp_19 = column 0-39", "outputs":"zp_B4/B5 = bitmap cell address ($A000 + row*320 + col*8), zp_B6/B7 = screen cell address ($8C00 + row*40 + col); Y = row, X = col", "confidence":"high"},
  {"addr":"2AA3", "name":"calcCellPointersXY",
   "summary":"Same as calcCellPointers but takes Y = row and X = column in registers, using the row/column address tables at $9300 (bitmap row lo/hi D_9300/D_9319, column*8 lo/hi D_9332/D_935A, screen row lo/hi D_9382/D_939B). Also used by the $C000 overlay text printer and the overlays.",
   "inputs":"Y = row, X = column", "outputs":"zp_B4/B5 bitmap pointer, zp_B6/B7 screen RAM pointer", "confidence":"high"},
  {"addr":"2AC3", "name":"drawCharAt",
   "summary":"Draws character A at row zp_18, column zp_19: computes the cell pointers and falls into plotGlyph.",
   "inputs":"A = character code, zp_18/zp_19", "outputs":"see plotGlyph", "confidence":"high"},
  {"addr":"2AC8", "name":"plotGlyph",
   "summary":"Draws the 8x8 glyph of character A (font at $9426, 8 bytes per character) into the bitmap cell at (zp_B4) using the bitmap draw mode, then writes the cell colour to screen RAM at (zp_B6): the per-character colour from D_93B4 when D_2A29 is zero and the table entry is non-zero, otherwise D_2A2A combined with the colour draw mode. In multicolour mode (D_9001 bit 4) it also writes zp_3D into colour RAM (zp_B7 + $4C).",
   "inputs":"A = character code (0-113); zp_B4/B5, zp_B6/B7 pointers; D_2A29, D_2A2A, zp_3D, D_9001", "outputs":"8 bitmap bytes, 1 screen RAM byte, optionally 1 colour RAM byte; zp_B2/B3 = glyph pointer; zp_B7 advanced to colour RAM in multicolour mode", "confidence":"high"},
  {"addr":"2B18", "name":"drawCharBlock",
   "summary":"Draws a rectangular block of characters at window origin (D_2A2D row, D_2A2E column). A/Y point to the data: byte 0 = width, byte 1 = height, then width*height character codes row by row. If D_2A2B is non-zero the data characters are ignored and every cell is filled with the character D_2A2C instead (the header is still read).",
   "inputs":"A/Y = pointer to block data; D_2A2D, D_2A2E, D_2A2B, D_2A2C", "outputs":"D_9221 = width, D_9222 = height; characters drawn; zp_B8/B9 = data pointer, zp_18/19, zp_1B (row), zp_1C (column), zp_1D (data index) clobbered", "confidence":"high"},
  {"addr":"2B75", "name":"drawWindowInnerCorners",
   "summary":"Draws character $38 at the four cells just inside the corners of the current window (rows D_2A2D+1 and D_2A2D+D_9222-1, columns D_2A2E+1 and D_2A2E+D_9221-1). Used by the overlay menus, probably to mark a selected/framed item.",
   "inputs":"D_2A2D, D_2A2E, D_9221, D_9222", "outputs":"4 characters drawn; zp_18/19/1A/1B clobbered", "confidence":"medium"},
  {"addr":"2BB7", "name":"drawTallGlyph",
   "summary":"Draws the two-character tall symbol number A at row zp_18, column zp_19: the pair of character codes at D_9810[A*2] (top) and D_9810[A*2+1] (bottom, drawn one row lower). zp_18 is restored.",
   "inputs":"A = symbol index, zp_18/zp_19", "outputs":"two characters drawn", "confidence":"high"},
  {"addr":"2BCE", "name":"xorColourBlock2x2",
   "summary":"Sets the colour draw mode to EOR, XORs the colour byte A into the 2x2 screen RAM colour cells at the window origin (one map tile), then resets both draw modes to replace. Used by the overlays to flash/highlight a tile.",
   "inputs":"A = colour byte, D_2A2D/D_2A2E", "outputs":"4 screen RAM cells toggled; draw modes reset; D_2A2A = A", "confidence":"medium"}
 ],
 "variables": [
  {"addr":"18", "scope":"zp", "name":"textRow", "meaning":"character row (0-24) for the text/glyph plotters (calcCellPointers); also general scratch", "confidence":"high"},
  {"addr":"19", "scope":"zp", "name":"textCol", "meaning":"character column (0-39) for the text/glyph plotters; also general scratch", "confidence":"high"},
  {"addr":"1A", "scope":"zp", "name":"textRightCol", "meaning":"right-hand column of the current window (drawWindowInnerCorners, sub_2C7F); scratch elsewhere", "confidence":"medium"},
  {"addr":"1B", "scope":"zp", "name":"textRowCounter", "meaning":"row counter / bottom row during block draws (drawCharBlock, fillScreenColourRect)", "confidence":"medium"},
  {"addr":"1C", "scope":"zp", "name":"textColCounter", "meaning":"column counter during block draws", "confidence":"medium"},
  {"addr":"1D", "scope":"zp", "name":"blockDataIndex", "meaning":"index into the character data while drawCharBlock runs", "confidence":"medium"},
  {"addr":"32", "scope":"zp", "name":"tileColour", "meaning":"screen RAM colour byte for the tile being rendered (terrain colour, then unit colour)", "confidence":"high"},
  {"addr":"3D", "scope":"zp", "name":"multicolourTextColour", "meaning":"value written to colour RAM for each plotted character when the multicolour bit of the $D016 shadow is set", "confidence":"high"},
  {"addr":"48", "scope":"zp", "name":"mapPtr", "meaning":"pointer into the 40x40 battlefield map at $F000 (lo)", "confidence":"high"},
  {"addr":"49", "scope":"zp", "name":"mapPtrHi", "meaning":"pointer into the battlefield map (hi)", "confidence":"high"},
  {"addr":"57", "scope":"zp", "name":"rngState0", "meaning":"pseudo-random generator state (sub_FD5D), byte 0", "confidence":"high"},
  {"addr":"58", "scope":"zp", "name":"rngState1", "meaning":"pseudo-random generator state, byte 1", "confidence":"high"},
  {"addr":"59", "scope":"zp", "name":"rngState2", "meaning":"pseudo-random generator state, byte 2", "confidence":"high"},
  {"addr":"77", "scope":"zp", "name":"ownUnitColour", "meaning":"colour RAM value for the local player's units (6 = blue, set at $7900)", "confidence":"high"},
  {"addr":"78", "scope":"zp", "name":"enemyUnitColour", "meaning":"colour RAM value for the opponent's units (2 = red, set at $78F7)", "confidence":"high"},
  {"addr":"A5", "scope":"zp", "name":"viewMapX", "meaning":"map column of the cell being rendered / left edge of the map viewport (may be negative or >= 40 when the viewport overhangs the map)", "confidence":"high"},
  {"addr":"A6", "scope":"zp", "name":"viewMapY", "meaning":"map row of the cell being rendered / top edge of the map viewport", "confidence":"high"},
  {"addr":"B1", "scope":"zp", "name":"gamePhaseFlags", "meaning":"0 while the battle is being played; $FF/$7F/$F0/$3F in setup, editor and replay phases. Bit 6 set suppresses unit drawing in renderMapTile", "confidence":"medium"},
  {"addr":"B2", "scope":"zp", "name":"glyphSrcPtr", "meaning":"pointer to the glyph / shape data being copied (lo)", "confidence":"high"},
  {"addr":"B3", "scope":"zp", "name":"glyphSrcPtrHi", "meaning":"pointer to the glyph / shape data being copied (hi)", "confidence":"high"},
  {"addr":"B4", "scope":"zp", "name":"bitmapCellPtr", "meaning":"bitmap address of the current character cell ($A000 based) (lo)", "confidence":"high"},
  {"addr":"B5", "scope":"zp", "name":"bitmapCellPtrHi", "meaning":"bitmap address of the current character cell (hi)", "confidence":"high"},
  {"addr":"B6", "scope":"zp", "name":"screenCellPtr", "meaning":"screen RAM ($8C00 based) address of the current character cell (lo); +$4C00 gives colour RAM", "confidence":"high"},
  {"addr":"B7", "scope":"zp", "name":"screenCellPtrHi", "meaning":"screen RAM address of the current character cell (hi)", "confidence":"high"},
  {"addr":"B8", "scope":"zp", "name":"blockDataPtr", "meaning":"pointer to character block data for drawCharBlock / sub_2C7F (lo)", "confidence":"high"},
  {"addr":"B9", "scope":"zp", "name":"blockDataPtrHi", "meaning":"pointer to character block data (hi)", "confidence":"high"},
  {"addr":"04C8", "scope":"abs", "name":"tileBuffer", "meaning":"32-byte buffer holding one 2x2 character map tile: $04C8 top-left glyph, $04D0 top-right, $04D8 bottom-left, $04E0 bottom-right (8 bitmap bytes each)", "confidence":"high"},
  {"addr":"0548", "scope":"abs", "name":"messageDisplayState", "meaning":"message system state byte; $9E = idle/no message (set here and by $C62A)", "confidence":"low"},
  {"addr":"0B9B", "scope":"abs", "name":"replayViewMode", "meaning":"bit 7 = replay/observer mode active; bits 0-1 = viewpoint: 0 = player 0's side, 1 = player 1's side, 2 = see everything (cycled $80->$81->$82 by a key at $4108)", "confidence":"medium"},
  {"addr":"0B9F", "scope":"abs", "name":"localPlayerIndex", "meaning":"0 or 1: which side the local machine plays (units 0-49 belong to player 0, 50-99 to player 1)", "confidence":"high"},
  {"addr":"0BA8", "scope":"abs", "name":"gameOverFlags", "meaning":"bit 7 = game ended by knockout; bits 5-6: $60 = both knocked out (draw), $40 = player 1 won, $00 = player 0 won", "confidence":"medium"},
  {"addr":"2808", "scope":"abs", "name":"tileCellByte", "meaning":"self-modified operand: the map cell byte being rendered (bit 7 = unit present)", "confidence":"high"},
  {"addr":"2840", "scope":"abs", "name":"ownSideBranchOpcode", "meaning":"opcode of 'cpy #50' branch: $90 (BCC) when the local player is 0, $B0 (BCS) when 1; written by sub_7BC6", "confidence":"high"},
  {"addr":"2846", "scope":"abs", "name":"visibilityBranchOpcode", "meaning":"opcode of the branch that skips the enemy-visibility test; copy of $2840, inverted for the other replay viewpoint (sub_2A08)", "confidence":"high"},
  {"addr":"28B8", "scope":"abs", "name":"unitInvertMask", "meaning":"self-modified EOR operand: $FF inverts the unit glyph (selected unit / selected group / D_F76C bit 7), 0 normal", "confidence":"high"},
  {"addr":"28C0", "scope":"abs", "name":"unitHalfScanFlag", "meaning":"self-modified operand: $FF draws only every other scanline of the unit glyph (D_F898 bit 7), 0 draws all", "confidence":"high"},
  {"addr":"2A29", "scope":"abs", "name":"useFixedColourFlag", "meaning":"non-zero: plotGlyph writes the fixed colour D_2A2A; zero: use the per-character colour table D_93B4 (falling back to D_2A2A for entries of 0)", "confidence":"high"},
  {"addr":"2A2A", "scope":"abs", "name":"drawColourByte", "meaning":"screen RAM colour byte (two nybbles) used by plotGlyph and fillScreenColourRect", "confidence":"high"},
  {"addr":"2A2B", "scope":"abs", "name":"blockFillFlag", "meaning":"non-zero: drawCharBlock fills the block with D_2A2C instead of the data characters", "confidence":"high"},
  {"addr":"2A2C", "scope":"abs", "name":"blockFillChar", "meaning":"character used by drawCharBlock when blockFillFlag is set", "confidence":"high"},
  {"addr":"2A2D", "scope":"abs", "name":"windowRow", "meaning":"character row of the current window / block origin", "confidence":"high"},
  {"addr":"2A2E", "scope":"abs", "name":"windowCol", "meaning":"character column of the current window / block origin ($2A2F is an unreferenced filler byte)", "confidence":"high"},
  {"addr":"2AF3", "scope":"abs", "name":"bitmapDrawOpcode", "meaning":"patched instruction: LDA/ORA/EOR/CMP (zp_B2),y selecting the bitmap draw mode", "confidence":"high"},
  {"addr":"2B00", "scope":"abs", "name":"colourDrawOpcode", "meaning":"patched instruction: LDA/ORA/EOR/CMP D_2A2A selecting the colour draw mode in plotGlyph", "confidence":"high"},
  {"addr":"2A85", "scope":"abs", "name":"fillColourOpcode", "meaning":"patched instruction: LDA/ORA/EOR/CMP D_2A2A selecting the colour draw mode in fillScreenColourRect", "confidence":"high"},
  {"addr":"9000", "scope":"abs", "name":"vicControl1Shadow", "meaning":"shadow of $D011 (low 3 bits = vertical fine scroll)", "confidence":"high"},
  {"addr":"9001", "scope":"abs", "name":"vicControl2Shadow", "meaning":"shadow of $D016 (low 3 bits = horizontal fine scroll, bit 4 = multicolour)", "confidence":"high"},
  {"addr":"90F6", "scope":"abs", "name":"savedViewMapX", "meaning":"saved map viewport column while the viewport is being redrawn", "confidence":"medium"},
  {"addr":"90F7", "scope":"abs", "name":"savedViewMapY", "meaning":"saved map viewport row while the viewport is being redrawn", "confidence":"medium"},
  {"addr":"9204", "scope":"abs", "name":"selectedUnit", "meaning":"index of the unit drawn inverted (highlighted) on the map", "confidence":"medium"},
  {"addr":"9205", "scope":"abs", "name":"selectedGroupId", "meaning":"group id (bits 2-5 of D_FB54) whose members are highlighted when D_9209 = 2", "confidence":"medium"},
  {"addr":"9209", "scope":"abs", "name":"selectionMode", "meaning":"2 = a unit group is selected (members drawn inverted)", "confidence":"low"},
  {"addr":"9221", "scope":"abs", "name":"windowWidth", "meaning":"width in characters of the current window / character block", "confidence":"high"},
  {"addr":"9222", "scope":"abs", "name":"windowHeight", "meaning":"height in characters of the current window / character block", "confidence":"high"},
  {"addr":"92A1", "scope":"abs", "name":"currentMessageId", "meaning":"id of the message currently displayed; $9E = none", "confidence":"medium"},
  {"addr":"92A2", "scope":"abs", "name":"playerPointsLo", "meaning":"two bytes: low byte of each player's point total (index = player); rewritten with the result points at game over", "confidence":"medium"},
  {"addr":"92A4", "scope":"abs", "name":"playerPointsHi", "meaning":"two bytes: high byte of each player's point total", "confidence":"medium"},
  {"addr":"92A6", "scope":"abs", "name":"playerOptionLevel", "meaning":"two bytes, one per player: setup option displayed as FULL/HALF/NONE (0/1/2), read from the setup data at $7C60 and used when units are deployed ($3E3F)", "confidence":"low"},
  {"addr":"92C8", "scope":"abs", "name":"screenShakeTimer", "meaning":"frames of screen shake left (set to 8 on an explosion at $3B48); also bounds the random scroll offsets", "confidence":"high"},
  {"addr":"92F4", "scope":"abs", "name":"currentXScroll", "meaning":"horizontal fine scroll value last written by shakeScreenScroll", "confidence":"medium"},
  {"addr":"92F5", "scope":"abs", "name":"currentYScroll", "meaning":"vertical fine scroll value last written by shakeScreenScroll", "confidence":"medium"},
  {"addr":"92FC", "scope":"abs", "name":"revealAllFlag", "meaning":"bit 7 set: draw enemy units even when not spotted", "confidence":"medium"},
  {"addr":"F640", "scope":"abs", "name":"unitX", "meaning":"unit record array (100 entries, one per unit): map column", "confidence":"high"},
  {"addr":"F6A4", "scope":"abs", "name":"unitY", "meaning":"unit array: map row", "confidence":"high"},
  {"addr":"F708", "scope":"abs", "name":"unitAnimFrame", "meaning":"unit array: bits 0-1 select one of 4 glyph variants (facing/animation frame); bit 7 used elsewhere", "confidence":"medium"},
  {"addr":"F76C", "scope":"abs", "name":"unitType", "meaning":"unit array: bits 0-2 = unit type (0-4 have glyphs), bit 7 set = special state drawn white and inverted ($FF = removed)", "confidence":"medium"},
  {"addr":"F898", "scope":"abs", "name":"unitStatusA", "meaning":"unit array: bit 7 set makes the unit glyph draw on alternate scanlines only (damaged/disrupted look)", "confidence":"low"},
  {"addr":"F960", "scope":"abs", "name":"unitStatusB", "meaning":"unit array: bit 7 set adds solid top and bottom lines to the unit tile; low nybble and bits 5-7 tested by the status panel", "confidence":"low"},
  {"addr":"F9C4", "scope":"abs", "name":"unitSpottedCounter", "meaning":"unit array: bit 7 set = unit is visible to the other side; low 6 bits a counter (initialised to 50)", "confidence":"low"},
  {"addr":"FA8C", "scope":"abs", "name":"unitFlashFlag", "meaning":"unit array: bit 7 set draws the unit in white (colour 1)", "confidence":"low"},
  {"addr":"FAF0", "scope":"abs", "name":"unitTerrainUnder", "meaning":"unit array: terrain byte of the map cell the unit stands on (the cell itself holds $80|unit); accessed as D_FA70,y with y = $80|unit in renderMapTile", "confidence":"high"},
  {"addr":"FB54", "scope":"abs", "name":"unitGroupFlags", "meaning":"unit array: bit 6 = member of a group, bits 2-5 = group id, bits 0-1 other flags", "confidence":"medium"}
 ],
 "dataBlocks": [
  {"addr":"2762", "length":16, "type":"byteTable", "name":"nybbleToMulticolourTable", "description":"expands a 4-bit terrain shape nybble to one multicolour bitmap byte: each set bit becomes the pixel pair %10 (bit0->$02, bit1->$08, bit2->$20, bit3->$80)"},
  {"addr":"2A29", "length":7, "type":"byteTable", "name":"textDrawParams", "description":"parameter block of the glyph plotters: useFixedColourFlag, drawColourByte, blockFillFlag, blockFillChar, windowRow, windowCol, one filler byte"},
  {"addr":"2A30", "length":4, "type":"byteTable", "name":"bitmapDrawOpcodes", "description":"opcodes LDA/ORA/EOR/CMP (zp),Y ($B1,$11,$51,$D1) for the bitmap draw modes 0-3"},
  {"addr":"2A34", "length":4, "type":"byteTable", "name":"colourDrawOpcodes", "description":"opcodes LDA/ORA/EOR/CMP abs ($AD,$0D,$4D,$CD) for the colour draw modes 0-3"}
 ],
 "misclassified": [],
 "insights": [
  "Screen layout: the game runs in multicolour bitmap mode with the bitmap at $A000 and screen RAM at $8C00 (VIC bank 2, as left by the title picture). Screen RAM colour = screenCellPtr, colour RAM = screenCellPtr + $4C00. Tables at $9300 give bitmap row base (D_9300 lo / D_9319 hi, 25 rows of 320 bytes), column*8 (D_9332 lo / D_935A hi, 40 columns) and screen row base (D_9382 lo / D_939B hi).",
  "Font: 114 characters of 8 bytes at $9426-$97B5; D_93B4 (114 bytes) is the default screen RAM colour byte for each character. D_9810 holds pairs of character codes forming two-character tall symbols (drawTallGlyph). Text option words FULL/HALF/NONE are at $0652 (4 bytes each, bit 7 terminated).",
  "Map tiles are 2x2 characters (16x16 pixels). Terrain shapes are 8 bytes of 8x1-bit pixels (two nybbles per row) at $9C6E + shape*8, shape = cell byte - $40 (cell $40 = blank, $68-$6B are the off-map border shapes); renderMapTile doubles them 2x and writes them through the tile buffer $04C8-$04E7, whose caller sub_26C1 copies 16 bytes to the bitmap cell and 16 bytes to the cell one character row lower (+320). Terrain colour = D_07EA[D_07BE[shape]] (D_077E is the same table indexed by the raw cell byte).",
  "Map cell encoding: bit 7 set -> low 7 bits = unit index (0-99) and the terrain underneath is kept in unitTerrainUnder ($FAF0+unit). Unit records are parallel 100-byte arrays spaced $64 apart from $F640 ($F640 x, $F6A4 y, $F708, $F76C type, $F7D0, $F834, $F898, $F8FC, $F960, $F9C4, $FA28, $FA8C, $FAF0, $FB54). Units 0-49 belong to player 0, 50-99 to player 1.",
  "Unit glyphs: 20 glyphs of 32 bytes (2x2 chars, multicolour %11 pixels coloured through colour RAM) at $6C5D-$6E7C, addressed by the word table D_6C35; glyph = (D_F708 & 3) + D_0799[unitType] - $2C, i.e. 5 unit types x 4 frames. Own units are blue (zp_77 = 6), enemy red (zp_78 = 2).",
  "Several branches that decide 'own vs enemy' on the unit index are self-modified: sub_7BC6 (overlay A) writes $90/$B0 into $2840, $63E2, $3F9E, $36D5, $379C, $4783, $5BF7 and the inverse into $206B, and sub_2A08 copies $2840 to $2846 and $5BFE with an optional inversion for the replay viewpoint D_0B9B. Annotators of those addresses should treat the opcode byte as a variable.",
  "Drawing primitives in this chunk are shared by the whole game (the $C000 overlay text printer uses calcCellPointersXY and has its own copy of the glyph copy loop at $C227-$C231, which setBitmapDrawMode also patches at $C22B). The seed label 'rtiVector' at $C229 only describes the boot loader; after the runtime overlay is loaded $C229 is inside the glyph plot loop.",
  "sub_FD5D in the resident high code is the pseudo-random number generator (24-bit shift/feedback on zp_57-59, returns A); the overview's 'IRQ handler region $FD5D-$FD6F' is actually this PRNG.",
  "Game-over result strings live at the end of overlay A ($879F..): 'WON ON POINTS!', 'IT WAS A DRAW.', 'WE WERE KNOCKED OUT!', 'WE WON BY A KNOCKOUT!', 'GAME OVER.', 'DRONE ALERT!', matching message ids $11-$16 queued by computeGameResult.",
  "Outside this chunk: the .byte block at $403B-$405D decodes as valid code (lda D_0B9F / cpx #0 / beq / eor #1 / tay / lda #6 / sta $92B2,y / lda $0C8A,y / tay / lda $F9C4,y / ora #$C0 / sta $F9C4,y / cpx #0 / beq / sty $923B / rts) and is probably an orphaned or indirectly reached routine that the disassembler missed."
 ]
}
