{
 "chunk": "game_high_FBB8_FBB8",
 "unit": "game/high_FBB8",
 "routines": [
  {"addr": "FBB8", "name": "initLineStepper",
   "summary": "Bresenham/DDA line set-up between the current cell ($93,$92) = (col,row) and the target cell ($95,$94). Computes |dRow| -> $8F, |dCol| -> $90, the step signs $96/$97 (+1 or -1), the major-axis length -> $8E and $91 (steps left), clears both error accumulators ($8C/$8D) and preloads the minor-axis accumulator with major/2. Used by the unit movement / path code in the main program ($1D57, $2512, $306F, $3771, $3A82, $42EC, $4559), by the comm module ($E80E, $E96F) and by overlay B to draw a shot/route line in the zoom window ($83A4).",
   "inputs": "$92 = start row, $93 = start column, $94 = end row, $95 = end column",
   "outputs": "A = $91 = number of steps (major axis length, Z set if zero); $8C,$8D = 0 / major/2; $8E = major length; $8F,$90 = |dRow|,|dCol|; $96,$97 = row/col step (+1/-1)",
   "confidence": "high"},
  {"addr": "FBFA", "name": "stepLine",
   "summary": "Advances the line started by initLineStepper by one cell: adds |dRow| to $8C and |dCol| to $8D, and whenever an accumulator reaches the major length it wraps it and moves $92 (row) / $93 (col) by the step sign, recording the applied delta in $90F4 / $90F5. $A9 ends up 0 for an orthogonal step and 1 for a diagonal step (the major axis always advances); if no steps are left nothing moves and $A9 = 0. Returns the remaining step count so callers loop with BEQ.",
   "inputs": "line state $8C-$8E,$8F,$90,$91,$92,$93,$96,$97",
   "outputs": "A = $91 (steps remaining, Z = line finished); $92/$93 advanced; $90F4 = row delta applied (-1/0/+1), $90F5 = column delta applied; $A9 = 1 if diagonal step, 0 otherwise",
   "confidence": "high"},
  {"addr": "FC58", "name": "plotFatPixel",
   "summary": "Plots a 'fat' multicolour pixel (one 2x2 hires block) of colour A (0-3) at X = 0..159, Y = 0..95 into the $A000 bitmap by drawing bitmap rows 2Y and 2Y+1 via plotBitmapPixelRow. X and Y are saved in self-modifying code and restored before returning. Only called from overlay B ($83E6 and the 2x2 marker at sub_84F1) to draw a shot/route line in the zoom window. The clip test is wrong: it returns early only when X >= 160 AND Y >= 96 (two BCCs into the plot path), so an out-of-range Y with a valid X is not rejected.",
   "inputs": "A = colour index 0-3, X = fat pixel column 0-159, Y = fat pixel row 0-95",
   "outputs": "bitmap byte(s) modified; X and Y preserved; $FC4D-$FC4F, $B4/$B5 clobbered",
   "confidence": "high"},
  {"addr": "FC74", "name": "plotBitmapPixelRow",
   "summary": "Inner routine of plotFatPixel: for bitmap row Y (0-191) and multicolour column X (0-159) computes the byte address bitmapRowAddr[Y>>3] + (Y&7) + colOffset[X>>2] using the tables at $9300/$9319 (row lo/hi, 25 entries, $A000 + 320*row) and $9332/$935A (8*column lo/hi, 40 entries), then merges the 2-bit colour ($FC50[colour] & $FC54[X&3]) into the byte. Colour index must already be in $FC4D.",
   "inputs": "X = pixel column 0-159, Y = bitmap row 0-191, $FC4D = colour index",
   "outputs": "one bitmap byte updated; $B4/$B5 = byte address; $FC4E = colour bits, $FC4F = keep mask; A,Y clobbered, X preserved",
   "confidence": "high"},
  {"addr": "FCCD", "name": "multiply8x8",
   "summary": "Unsigned 8x8 -> 16 bit multiply by shift-and-add (8 iterations). Used by overlay A ($814A, $846B: value * Y for the option/limit editor) and the $EC00 sub-overlay ($ECD4: A * $96).",
   "inputs": "A = multiplicand, Y = multiplier",
   "outputs": "A = Y = product high byte, $18 = product low byte, $19 = product high byte, $1A clobbered, X = 0",
   "confidence": "high"},
  {"addr": "FCEC", "name": "divideSigned16by8",
   "summary": "Divides the signed 16-bit value Y:A (Y = high byte) by the unsigned 8-bit divisor X. A negative dividend is negated first, the quotient is negated afterwards (the remainder is that of the absolute value). Callers: $41E4 (timer mod 10/50 test), the comm module ($E8EA-$E90D) and the $EE00 sub-overlay ($EE86-$EEB6).",
   "inputs": "X = divisor, A = dividend low, Y = dividend high (sign)",
   "outputs": "A = quotient (low 8 bits, sign restored), Y = remainder (of |dividend|), $18 = divisor, $19 clobbered",
   "confidence": "high"},
  {"addr": "FD0A", "name": "divideUnsigned16by8",
   "summary": "Unsigned 16/8 restoring division, 8 iterations: dividend high byte in A, low byte in $19, divisor in $18. Entered directly from divideSigned16by8 for non-negative dividends. Quotient bits beyond 8 are lost.",
   "inputs": "A = dividend high byte, $19 = dividend low byte, $18 = divisor",
   "outputs": "A = quotient (low byte), Y = remainder, $19 = quotient, X = 0",
   "confidence": "high"},
  {"addr": "FD1E", "name": "addRandomJitter",
   "summary": "Returns base value A plus a random offset +/- 2*k, where k is the number of consecutive 1 bits at the bottom of a scenario random byte (geometric distribution) capped at Y, and the sign comes from bit 7 of $5B. Y = 0 returns A unchanged. A negative (bit 7 set) result is clamped to 0. Overlay A's scenario generator uses it to scatter unit/terrain positions around a centre (callers $7036, $738B, $7398, which retry while the result is >= 40).",
   "inputs": "A = base value, Y = maximum offset count; uses scenario RNG $5A-$5C",
   "outputs": "A = jittered value (0..127); $18, $19 clobbered; RNG state advanced",
   "confidence": "medium"},
  {"addr": "FD4A", "name": "nextScenarioRandom",
   "summary": "24-bit shift-register pseudo random generator on $5A/$5B/$5C, returns $5A EOR $5B. This generator is the scenario/map seed: it is zeroed at $7731, seeded from the game RNG at $77E7 or from the typed 5-character MAP ID code (5 bits per character via the 32-entry alphabet at $890C, $77F7-$7821), and consumed only by overlay A's battlefield/unit generator and $4805. Same algorithm as nextGameRandom.",
   "inputs": "$5A-$5C RNG state",
   "outputs": "A = random byte, $5A-$5C advanced, C = 0",
   "confidence": "high"},
  {"addr": "FD5D", "name": "nextGameRandom",
   "summary": "24-bit shift-register pseudo random generator on $57/$58/$59, returns $57 EOR $58. Seeded with the constants $49,$19,$02 by the start-up code at $0309-$0313, so both machines of a modem game run the same deterministic sequence; used throughout the main program, overlays, the sound code ($C862) and the comm module. NOTE: this is not an IRQ handler (docs/overview.md calls $FD5D-$FD6F the IRQ handler region; the IRQ vector actually points to $1298 and later $10F1).",
   "inputs": "$57-$59 RNG state",
   "outputs": "A = random byte, $57-$59 advanced, C = 0",
   "confidence": "high"}
 ],
 "variables": [
  {"addr": "8C", "scope": "zp", "name": "lineErrRow", "meaning": "line stepper error accumulator for the row axis (starts at 0 or major/2)", "confidence": "high"},
  {"addr": "8D", "scope": "zp", "name": "lineErrCol", "meaning": "line stepper error accumulator for the column axis", "confidence": "high"},
  {"addr": "8E", "scope": "zp", "name": "lineMajorLen", "meaning": "length of the line along its major axis (max(|dRow|,|dCol|)); also set directly by overlay A at $73F5", "confidence": "high"},
  {"addr": "8F", "scope": "zp", "name": "lineDeltaRow", "meaning": "|end row - start row|", "confidence": "high"},
  {"addr": "90", "scope": "zp", "name": "lineDeltaCol", "meaning": "|end col - start col|", "confidence": "high"},
  {"addr": "91", "scope": "zp", "name": "lineStepsLeft", "meaning": "remaining steps of the current line (0 = finished); returned in A by initLineStepper/stepLine", "confidence": "high"},
  {"addr": "92", "scope": "zp", "name": "lineCurRow", "meaning": "current map row of the line walker (start row on entry); pairs with $94. Main code loads it from the view origin $A6+2 or the unit row array", "confidence": "medium"},
  {"addr": "93", "scope": "zp", "name": "lineCurCol", "meaning": "current map column of the line walker (start column on entry); pairs with $95. Overlay B uses it as the horizontal pixel source ($83BF -> X of plotFatPixel)", "confidence": "medium"},
  {"addr": "94", "scope": "zp", "name": "lineEndRow", "meaning": "target row of the line (loaded from unitRow $F6A4,x at $1D9C)", "confidence": "medium"},
  {"addr": "95", "scope": "zp", "name": "lineEndCol", "meaning": "target column of the line (loaded from unitCol $F640,x at $1D97)", "confidence": "medium"},
  {"addr": "96", "scope": "zp", "name": "lineStepRow", "meaning": "row increment per major step, $01 or $FF", "confidence": "high"},
  {"addr": "97", "scope": "zp", "name": "lineStepCol", "meaning": "column increment per major step, $01 or $FF", "confidence": "high"},
  {"addr": "A9", "scope": "zp", "name": "isDiagonalStep", "meaning": "set by stepLine: 1 when the last step moved both row and column, 0 for an orthogonal step or a finished line; $45DF charges 8 extra movement points for a diagonal step", "confidence": "high"},
  {"addr": "B4", "scope": "zp", "name": "bitmapPtr", "meaning": "pointer (lo) to a byte in the $A000 multicolour bitmap; set by plotBitmapPixelRow and by sub_2AA3 (bitmap cell address from the $9300 tables)", "confidence": "medium"},
  {"addr": "B5", "scope": "zp", "name": "bitmapPtrHi", "meaning": "pointer (hi) to a byte in the $A000 multicolour bitmap", "confidence": "medium"},
  {"addr": "18", "scope": "zp", "name": "mathTemp0", "meaning": "general scratch; in the high code: multiply product low / divide divisor / jitter bit counter", "confidence": "medium"},
  {"addr": "19", "scope": "zp", "name": "mathTemp1", "meaning": "general scratch; in the high code: multiplier then product high / dividend low then quotient / jitter count", "confidence": "medium"},
  {"addr": "1A", "scope": "zp", "name": "mathTemp2", "meaning": "general scratch; multiply partial product low byte", "confidence": "medium"},
  {"addr": "57", "scope": "zp", "name": "gameRngState0", "meaning": "game random generator state byte 0 (seeded $49 at $030B); also read directly by $77D6 and $8316", "confidence": "high"},
  {"addr": "58", "scope": "zp", "name": "gameRngState1", "meaning": "game random generator state byte 1 (seeded $19)", "confidence": "high"},
  {"addr": "59", "scope": "zp", "name": "gameRngState2", "meaning": "game random generator state byte 2 (seeded $02)", "confidence": "high"},
  {"addr": "5A", "scope": "zp", "name": "scenarioSeed0", "meaning": "scenario/map random generator state byte 0; the 24-bit $5A-$5C value is the MAP ID seed (5 base-32 characters). Also reused as the 3-byte answer buffer of the disabled manual copy-protection check ($09FC)", "confidence": "high"},
  {"addr": "5B", "scope": "zp", "name": "scenarioSeed1", "meaning": "scenario random generator state byte 1; bit 7 supplies the sign in addRandomJitter", "confidence": "high"},
  {"addr": "5C", "scope": "zp", "name": "scenarioSeed2", "meaning": "scenario random generator state byte 2", "confidence": "high"},
  {"addr": "A5", "scope": "zp", "name": "viewOriginCol", "meaning": "map column of the left edge of the scrolling battlefield view ($A5+3 is passed as the column to the map address routine sub_25FA at $260D)", "confidence": "medium"},
  {"addr": "A6", "scope": "zp", "name": "viewOriginRow", "meaning": "map row of the top edge of the scrolling battlefield view ($A6+2 is the row index into the $BFB0/$BFD8 row table)", "confidence": "medium"},
  {"addr": "90F4", "scope": "abs", "name": "lineMovedRow", "meaning": "row delta (-1/0/+1) applied by the last stepLine call; read at $1D89 and $E821", "confidence": "high"},
  {"addr": "90F5", "scope": "abs", "name": "lineMovedCol", "meaning": "column delta (-1/0/+1) applied by the last stepLine call; read at $1D8C and $E819", "confidence": "high"},
  {"addr": "FC4D", "scope": "abs", "name": "plotColorIndex", "meaning": "colour index 0-3 of the pixel being plotted (saved A of plotFatPixel)", "confidence": "high"},
  {"addr": "FC4E", "scope": "abs", "name": "plotColorBits", "meaning": "colour pattern already masked to the target pixel pair", "confidence": "high"},
  {"addr": "FC4F", "scope": "abs", "name": "plotKeepMask", "meaning": "inverted pixel-pair mask (bits of the bitmap byte to keep)", "confidence": "high"},
  {"addr": "F640", "scope": "abs", "name": "unitCol", "meaning": "per-unit map column array (100 entries, $F640-$F6A3); compared with $93 at $25A2", "confidence": "medium"},
  {"addr": "F6A4", "scope": "abs", "name": "unitRow", "meaning": "per-unit map row array (100 entries, $F6A4-$F707); compared with $92 at $25A9", "confidence": "medium"}
 ],
 "dataBlocks": [
  {"addr": "FC50", "length": 4, "type": "byteTable", "name": "multicolorFillPatterns", "description": "$00,$55,$AA,$FF: colour index 0-3 replicated into all four pixel pairs of a bitmap byte; indexed by plotColorIndex"},
  {"addr": "FC54", "length": 4, "type": "byteTable", "name": "multicolorPixelMasks", "description": "$C0,$30,$0C,$03: bit mask of multicolour pixel pair (X & 3) within a bitmap byte"},
  {"addr": "FD70", "length": 177, "type": "sprite", "name": "arrowGlyphTable", "description": "Leading byte $01 followed by eight 22-byte records: a 2-byte header ($01,$16 for the first, $01,$2D for the rest) and seven 3-byte rows holding an 8x7 arrow glyph in the middle byte (last row only 2 bytes). Glyphs are arrows for the 8 compass directions in clockwise order: N ($08,$1C,$7F,$08,$08,$08,$1C), NE, E, SE, S, SW, W, NW. No static reference to this table exists anywhere in the disassembly (no $FD7x/$FE/$FF pointers); it may be unused leftover data."},
  {"addr": "FE21", "length": 367, "type": "sprite", "name": "aircraftShapeTable", "description": "Eight records, each a 2-3 byte header ($01,$17,$00,$05,$05,$0A / $05,$15 / $05,$18 / $05,$18 / $05,$15 / $05,$18 / $05,$11,$00 / $05,$12) followed by 14-15 rows of 3 bytes (24 px wide) that draw a top-down aeroplane/drone silhouette in 8 orientations (up, diagonal, left, diagonal, down, diagonal, right, diagonal); e.g. rows $02,$38,$80 / $0F,$FF,$E0 x3 / $08,$7C,$20 are the wings. The record framing is not fully resolved (header length and row alignment differ between records) and nothing references the table; probably stale data from the mastering system or an unused earlier unit graphic."},
  {"addr": "FF90", "length": 66, "type": "unknown", "name": "unknownShapeTail", "description": "Starts with the same $05,$xx header pattern ($05,$0E) but the following bytes ($08,$78,$08,$08,..,$49,$00,$49) look like 8-pixel-wide character rows rather than 24-px rows; the sector continues past $FFD1 with stale bytes that resemble the end of the $8800 message block (track 29 s9-10) and were not moved by the loader, so this block is most likely unused leftover memory."}
 ],
 "misclassified": [],
 "insights": [
  "The resident high code ($FBB8-$FD6F, 440 bytes) is a small math/graphics library: Bresenham line stepper ($FBB8/$FBFA), multicolour fat-pixel plotter ($FC58), 8x8 multiply ($FCCD), signed/unsigned 16/8 divide ($FCEC/$FD0A), random jitter ($FD1E) and two identical 24-bit shift-register RNGs ($FD4A on $5A-$5C, $FD5D on $57-$59). Everything from $FD70 to $FFD1 is unreferenced data.",
  "docs/overview.md is wrong about an 'IRQ handler region $FD5D-$FD6F': $FD5D is nextGameRandom. The game's IRQ/NMI vectors are written at $0A72-$0A7D (all four -> $1298) and $0C6B/$0C72 (IRQ -> $10F1).",
  "Two RNGs with different roles: $57-$59 is the lock-step game RNG seeded with fixed constants $49,$19,$02 at $0309 (so both modem players generate identical sequences); $5A-$5C is the scenario seed. Overlay A encodes/decodes the 24-bit scenario seed as a 5-character MAP ID using the 32-character alphabet at $890C (5 bits per character, $77F7-$7821) and can also derive it from the game RNG ($77E7).",
  "Line walker coordinate convention: $93/$95 are columns (X), $92/$94 rows (Y). Evidence: $258F sets $93 = $A5+3 and $92 = $A6+2 and compares them with $F640,x / $F6A4,x; $260D passes $A5+3 in X (column offset) and $A6+2 in Y (row index into the $BFB0/$BFD8 row table built at $0339) to sub_25FA; overlay B feeds $93 into the horizontal pixel coordinate. Overlay A's scenario code ($7371-$739F) swaps the pairs but only uses them symmetrically.",
  "Map addressing: $BFB0 (lo) / $BFD8 (hi) = 40-entry table of map row addresses ($F000 + 40*row), built by the setup code sub_0339; sub_25FA(X = column, Y = row) returns the cell byte. Unit position arrays: $F640 (column) and $F6A4 (row), 100 entries each, live right after the 1600-byte map.",
  "The in-game display is a multicolour bitmap at $A000 in VIC bank 2 (same configuration as the title picture: bitmap $A000, screen $8C00, $D018=$38). data_9300 holds the address tables: $9300/$9319 = bitmap row base lo/hi (25 entries, $A000 + 320*row), $9332/$935A = 8*column lo/hi (40 entries), $9382/$939B = screen row lo/hi; sub_2AA3 builds both a bitmap pointer ($B4/$B5) and a screen pointer ($B6/$B7) from them.",
  "plotFatPixel treats the bitmap as 160x96 fat pixels (each fat pixel = 1 multicolour pixel wide x 2 lines tall). Overlay B ($82BF-$8410) uses it with initLineStepper/stepLine to draw a route/shot line in a 14x10 character zoom window at columns 25-38, rows 1-10 (fat pixel X 100-155, Y 4-43, cells drawn 8x8 fat pixels via sub_84B2 = coord*8+4/+3); sub_84F1 plots a 2x2 marker.",
  "plotFatPixel clip bug: 'cpx #$A0 / bcc plot / cpy #$60 / bcc plot / rts' rejects a point only when both coordinates are out of range; harmless in practice because overlay B range-checks before calling.",
  "stepLine's $A9 (0 orthogonal, 1 diagonal) is used at $45DF to add 8 extra movement cost for diagonal moves; $90F4/$90F5 give the per-step row/column delta to the unit movement code ($1D89) and the comm module ($E819).",
  "The labels D_FD1A and D_FED2 are artifacts of the boot loader's block move loop ($C200/$C203: LDA $FD1A,Y / STA $FED2,Y with decrementing page bytes), not real data references; $FD1A is the TAY inside divideUnsigned16by8."
 ]
}
