{
 "_comment": "generated by tools/mergeSurvey.py from survey/*.json - do not edit by hand",
 "notes": {
  "9300": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "bitmapRowLoTable: byteTable, 25 bytes. Low bytes of the 25 bitmap character-row addresses $A000 + 320*row (values cycle $00,$40,$80,$C0). Read as bitmapRowLoTable,Y (Y = row 0-24) by calcCellPointersXY ($2AA3) and by plotBitmapPixelRow ($FC80) in the high-memory block; overlay B reads it biased by 3 ($9303, rows 3+) at $7BDA/$7C38."
   ]
  },
  "9319": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "bitmapRowHiTable: byteTable, 25 bytes. High bytes of the same 25 row addresses: $A0,$A1,$A2,$A3,$A5,$A6,$A7,$A8,$AA,... (step $140 per row). Read at $2AAC and $FC87; biased alias $931C used by overlay B."
   ]
  },
  "9332": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "bitmapColLoTable: byteTable, 40 bytes. Low bytes of column*8 for the 40 bitmap columns (0,8,...,312 mod 256). Read at $2AA7 and $FCAE; biased alias $9335 used by overlay B at $7BDD/$7C3B."
   ]
  },
  "935A": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "bitmapColHiTable: byteTable, 40 bytes. High bytes of column*8: $00 for columns 0-31, $01 for columns 32-39. Read at $2AAF and $FCB5."
   ]
  },
  "9382": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "screenRowLoTable: byteTable, 25 bytes. Low bytes of the 25 screen-RAM row addresses $8C00 + 40*row (0,$28,$50,...). Read by fillScreenColourRect ($2A71), calcCellPointersXY ($2AB6) and both $6F00 overlays at $82C1."
   ]
  },
  "939B": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "screenRowHiTable: byteTable, 25 bytes. High bytes of the screen-RAM row addresses: $8C x7, $8D x6, $8E x7, $8F x5. Read at $2A6C, $2ABB and $82C6 in both $6F00 overlays. Colour RAM is reached by adding $4C to the high byte."
   ]
  },
  "93B4": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "fontColorTable: byteTable, 114 bytes. Default screen-RAM colour byte for each of the 114 gameFont glyphs, same index as gameFont. Read by plotGlyph at $2AD6 only when textDrawParams ($2A29) is zero; a zero entry makes the plotter fall back to the explicit colour $2A2A. Values are packed nibble pairs, e.g. $BC, $FC, $6E for the diagonal shading glyphs $30-$37 and $BB/$FF for the heavy bevel glyphs $6A-$71."
   ]
  },
  "9426": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "gameFont: charset, 912 bytes. 114 glyphs of 8x8 pixels, 8 bytes each, addressed as $9426 + code*8 by plotGlyph ($2AC8). This is not a text font: $00 blank, $01-$05 solid, $06-$13 single-pixel corner/edge pieces, $14-$2F the chunky diagonal / wedge shading set, $30-$33 the two halves of a tall 'N' and 'S' (stacked vertically) and $34-$37 a goalpost 'H' and a comb 'E' (two cells side by side), $38 a small hollow diamond, $39-$48 thin frame pieces, diagonals and the four double-line corners, $49-$56 the top and bottom halves of the ten tall digits 0-9 (see tallDigitGlyphPairTable), $57-$59 three narrow glyphs that together form a 24-pixel strip of small letters and are not reached from any table found so far, $5A-$5E vertical dither patterns, $5F-$71 the heavy raised-bevel frame set. Text on the battlefield screens is drawn with the separate 59-glyph sets at $98BE/$9A96, not with this one."
   ]
  },
  "97B6": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "unreferencedGlyphCodeBlock: byteTable, 90 bytes. 90 bytes of valid gameFont glyph codes ($00,$02,$04,$05,$07,$14-$2F,$30-$38,$45-$48) sitting between gameFont and tallDigitGlyphPairTable. No traced code reads it: XREF.txt has no label anywhere in $9426-$980F, no instruction in any .s file carries an immediate $97 or $98 high byte, and a scan of the whole 64K runtime image finds no little-endian pointer into $97B6-$980F. Rendering it as 10x9, 9x10, 15x6, 6x15, 5x18 or 18x5 glyph grids, as 45 vertically stacked glyph pairs (the tallDigitGlyphPairTable format) and as ten 3x3 window frame sets (the $9824 format) all produce incoherent art, so the exact structure is unresolved; the value range says it is a screen-code layout of some kind, most likely leftover from an earlier build. (confidence: low on the interpretation, high on the fact that nothing reads it)"
   ]
  },
  "9810": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "tallDigitGlyphPairTable: byteTable, 20 bytes. Ten 2-byte pairs, indexed by drawTallGlyph ($2BB7) as $9810 + symbol*2. The first code is drawn at row zp_18 and the second one row lower, so each pair forms a 8x16 pixel tall digit: 0=($49,$4A), 1=($4B,$4C), 2=($4D,$4E), 3=($4D,$4F), 4=($50,$51), 5=($52,$4F), 6=($52,$53), 7=($54,$55), 8=($56,$53), 9=($56,$51). Used for the big numbers on the console panels."
   ]
  },
  "9824": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "windowFrameTileSets: byteTable, 90 bytes. Ten 9-byte frame tile sets, each nine gameFont codes in row-major order (topLeft, top, topRight, left, centre, right, bottomLeft, bottom, bottomRight), selected through windowFramePtrTable ($2C49) and drawn by drawWindowFrame ($2BDD group) / drawFrameBox. $9824 = thin single box, $982D = thin box built from the $39-$40 pieces, $9836 = octagon with diagonal corners, $983F = solid box with diagonal shadow corners, $9848 = all $03 (solid fill), $9851 = all $00 (blank/erase), $985A = 50 percent dither box, $9863/$986C = heavy raised bevel with a dithered outer edge, $9875 = heavy solid bevel."
   ]
  },
  "987E": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "asciiToGlyphTable: byteTable, 64 bytes. Character code $20-$5F -> gameFont glyph index, 64 entries. printChar ($C01A) reads it as asciiToGlyphTable,Y with Y = char - $20 ($C02A). The chat-line editor at $C80A reads exactly the same table through the biased base $985E with the raw character code in X and rejects the character when the entry is $3B, which is why XREF lists a second label $985E for it. Layout: space -> 0, digits '0'-'9' -> $01-$0A, 'A'-'Z' -> $0B-$24, punctuation to the small symbol glyphs, '[' -> $28, ']' -> $29, '\\', '^', '_' -> $3B (not typeable)."
   ]
  },
  "98BE": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "consoleFontMulticolour: charset, 472 bytes. 59 glyphs of 8x8, 8 bytes each: the multicolour / double-width character set of the $C000 text plotter, selected when bit 4 of the $D016 shadow $9001 is set ($C20B). Every pixel is drawn as the bit pair %11 (byte values $00,$03,$0C,$0F,$30,$33,$3C,$3F). Order: 0 blank, 1-10 digits 0-9, 11-36 letters A-Z, 37-58 punctuation and arrows. Stored positive on disk and inverted in place at boot by invertTextFonts ($031B), which EORs $98BE-$9C6D with $FF."
   ]
  },
  "9A96": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "consoleFontHires: charset, 472 bytes. The same 59 glyphs in single-width hires form ($9A96 = $98BE + $1D8), used when $9001 bit 4 is clear. Also inverted at boot by invertTextFonts."
   ]
  },
  "9C6E": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "terrainTileShapes: charset, 352 bytes. 44 one-bit 8x8 terrain tiles, 8 bytes each, addressed as $9C6E + index*8 by setTerrainTilePtr ($2DBD). The index is mapCell - $40, or mapCell - $20 when the cell is below $40 ($27B2), so the lower contour codes $21-$34 and the upper contour codes $41-$54 share tiles $01-$14. Tiles: $00 blank ground, $01-$14 contour/ridge line segments, $15-$18 the four quadrants of a small round blob and $19-$1C its filled forms (river, cells $55-$5C), $1D-$20 dense noise (forest, $5D-$60), $21-$24 sparse dots (lower hill, $61-$64), $25-$26 very sparse dots (summit, $65-$66), $27 a boxed cross (the recycler, $67), $28-$2B four checkerboard blocks (end-zone/goal cells $68-$6B). The main program doubles each nybble through nybbleToMulticolourTable to draw them 16x16 in the tactical view; overlay B's drawRadarGlyph ($7396) uses the same base for the radar screens."
   ]
  },
  "9DCE": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "unitSymbolShapes: charset, 160 bytes. 20 one-bit 8x8 unit symbols continuing the $9C6E table at indices $2C-$3F: five unit types x four facings, matching unitGlyphBaseTable at $0799 ($2C GRUNT, $30 RIDER, $34 BOOMER, $38 SPY, $3C COMCEN, plus unitFlags AND 3). They are the small single-cell symbols; the big 16x16 multicolour versions live at $6C5D in the main program."
   ]
  },
  "9E6E": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "spareCursorShapes: sprite, 36 bytes. Two 18-byte sprite fragments in the same format as cursorBlockShape: six 24-pixel rows drawing a 6x6 hollow box ($9E6E) and a 6x6 plus/cross ($9E80). Nothing in the disassembly reads them - they look like alternative cursor shapes kept next to the one that is used. (confidence: medium)"
   ]
  },
  "9E92": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "cursorBlockShape: sprite, 18 bytes. 18 bytes = six 3-byte sprite rows drawing a small solid 4x4 block. setCursorShapeBlock ($2FFA) copies exactly $12 bytes from here into the top of sprite $91CC's shape ($8A00 + n*64) for the plain battlefield cursor."
   ]
  },
  "9EA4": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "cursorBoxSprite: sprite, 63 bytes. One complete 24x21 sprite (21 rows of 3 bytes): an 11x9 hollow rectangle centred in the sprite. showCursorBoxSprite ($3306) copies it into sprite 2 with X counting $3E..0; it is the normal battlefield centre cursor."
   ]
  },
  "9EE3": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "crosshairSprite: sprite, 63 bytes. One complete 24x21 sprite: a diamond crosshair with a vertical stem above and below and horizontal ticks. showCrosshairSprite ($3302) copies it into sprite 2 by loading X with $7D (= $9EA4 + $3F)."
   ]
  },
  "9F22": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "comcenCrossMarkerShape: sprite, 21 bytes. 21 bytes = seven 3-byte sprite rows drawing a plus/cross marker. showComcenCrossMarker ($3383) copies it (LDY #$14, 21 bytes) into rows 7-13 of sprite 2 at $8A95 and colours it light red; overlay B calls it at $75B6 to mark the comcen on the strategic grid."
   ]
  },
  "9F37": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "circleDxPtrLoTable: byteTable, 10 bytes. Low bytes of the ten dx point-list pointers used by drawCircleInSprite ($3239), indexed by radius-1: $9F69,$9F6F,$9F73,$9F79,$9F7F,$9F8B,$9F97,$9FA1,$9FAF,$9FBF. Read at $323D into zp_AD."
   ]
  },
  "9F41": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "circleDxPtrHiTable: byteTable, 10 bytes. High bytes of the dx pointers, all $9F. Read at $3242 into zp_AE."
   ]
  },
  "9F4B": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "circleDyPtrLoTable: byteTable, 10 bytes. Low bytes of the ten dy point-list pointers: $9F6C,$9F71,$9F76,$9F7C,$9F85,$9F91,$9F9C,$9FA8,$9FB7,$9FC8. Read at $3247 into zp_AF."
   ]
  },
  "9F55": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "circleDyPtrHiTable: byteTable, 10 bytes. High bytes of the dy pointers, all $9F. Read at $324C into zp_B0."
   ]
  },
  "9F5F": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "circleStepCountTable: byteTable, 10 bytes. Highest point index of each circle radius 1-10: 2,1,2,2,5,5,4,6,7,8. drawCircleInSprite loads it into circleStepsLeft ($91F2) at $3251 and counts down to 0, so radius 1 plots 3 points (a filled 3x3 dot) and radius 10 plots 9."
   ]
  },
  "9F69": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "circlePointDeltaLists: byteTable, 104 bytes. The ten dx lists and ten dy lists themselves, interleaved so that each radius' dx list is immediately followed by its dy list (radius 1: dx $9F69 = 00,01,01 / dy $9F6C = 00,00,01; radius 10: dx $9FBF = 0A,0A,0A,09,09,08,07,07,08 / dy $9FC8 = 00,01,02,03,04,05,06,07,06). computeCircleOctantPoints reflects each (dx,dy) into the eight octants around the sprite centre (12,10)."
   ]
  },
  "9FD1": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "graphicsBlockTail: unknown, 47 bytes. 47 bytes to the end of track 28 sector 12, not referenced by anything. Byte-for-byte a 50 percent dither: alternating $FF/$00 pairs whose phase flips every eight bytes, with a handful of stray values ($3F,$33,$C0,$F3,$30). The identical pattern appears in the tail of the $8800 block ($897F-$89FF, track 29 sector 10), so both are almost certainly the same stale bitmap fragment left in the mastering machine's memory. (confidence: medium)"
   ]
  },
  "8800": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgGetOpponentOnPhoneAnd: text, 28 bytes. Bit-7-terminated message text \"GET OPPONENT ON PHONE AND...\". Message slot $20 while a modem game is being set up: main $0B1D patches messagePtrLoTable/HiTable entry $20 to point here (A=$00, Y=$88) right before waitForAnswerOriginateKey."
   ]
  },
  "881C": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgPressAOrOAndSetModem: text, 27 bytes. Bit-7-terminated message text \"PRESS A OR O AND SET MODEM.\". Message slot $21, patched next to it at $0B26 (A=$1C, Y=$88). Tells the player to press A(nswer) or O(riginate)."
   ]
  },
  "8837": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgPressSpaceWhenReady: text, 23 bytes. Bit-7-terminated message text \"PRESS SPACE WHEN READY.\". Message slot $21 set by showModemSetupPrompt ($0C8C, called from overlay A $79E3), which puts the caller's string in slot $20 and this one in slot $21."
   ]
  },
  "884E": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgTrainerName: text, 10 bytes. Bit-7-terminated message text \"TRAINER   \". Ten characters, read by exchangePlayerNamesAndSettings ($ED4A in the modem-driver build): in solo-trainer mode ($0BA5 bit 7) the whole name handshake is skipped and this string is copied into opponentName $FFE8 instead."
   ]
  },
  "8858": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgOpponentReady: text, 15 bytes. Bit-7-terminated message text \"OPPONENT READY.\". Message id $04 (messagePtrLoTable[$04]=$58, HiTable[$04]=$88)."
   ]
  },
  "8867": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgWaitingForOpponent: text, 23 bytes. Bit-7-terminated message text \"WAITING FOR OPPONENT...\". Message id $0B."
   ]
  },
  "887E": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgWaitingForConnection: text, 25 bytes. Bit-7-terminated message text \"WAITING FOR CONNECTION...\". Message id $10."
   ]
  },
  "8897": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgPickUpPhoneThenPressSpace: text, 31 bytes. Bit-7-terminated message text \"PICK UP PHONE THEN PRESS SPACE.\". Message id $17."
   ]
  },
  "88B6": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgPressSpaceWaitHangupPhone: text, 32 bytes. Bit-7-terminated message text \"PRESS SPACE, WAIT, HANGUP PHONE.\". Message id $18."
   ]
  },
  "88D6": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgOpponentPickingGameMap: text, 26 bytes. Bit-7-terminated message text \"OPPONENT PICKING GAME/MAP.\". Message id $1A - shown to player 1 while it waits for player 0's map seed."
   ]
  },
  "88F0": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgOpponentReadyForNewGame: text, 28 bytes. Bit-7-terminated message text \"OPPONENT READY FOR NEW GAME.\". Message id $1F."
   ]
  },
  "895E": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgOpponentUsingNonStandardGame: text, 33 bytes. Bit-7-terminated message text \"OPPONENT USING NON-STANDARD GAME.\". Shown when the program-checksum byte exchanged during the identity handshake does not match: overlay A stores the expected value $DB at $79CC, compares it after exchangePlayerNamesAndSettings and, on a mismatch, does LDA #$5E / LDY #$89 / JSR showModemSetupPrompt ($79DF-$79E3), which puts this string in message slot $20 and \"PRESS SPACE WHEN READY.\" ($8837) in slot $21. Because the address is built from two immediates the disassembler produced no reference, which is why XREF shows only trainer writes here."
   ]
  },
  "890C": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "mapNameAlphabet: byteTable, 32 bytes. The 32-character alphabet ' ABCDEFGHIJKLMNOPQRSTUVWXYZ+-*/=' used to convert between a five-character map name and the 24-bit map seed. The map-name editor in overlay A searches it backwards from index $1F at $779B to validate a typed character, and packs the name at $7804 by shifting zp_5A/5B/5C left five times per character and ORing in the index, so name = seed in base 32 (5 chars x 5 bits). $785D uses the same table to print a seed back as a name. This is also what makes the copy-protection work: the 32 x 3-byte table at $0401 holds seed triples, and the player types the matching name out of the manual."
   ]
  },
  "892C": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "gameTypeDefaultsSide0: byteTable, 25 bytes. Side 0 half of the game-type parameter defaults: five rows of five bytes, read through selectSideDefaultsTable ($7CAE) and applied by applyGameTypeDefaults ($7C2E). Rows are TERRAIN PTS (-> $92AC), RECYCLER (-> $92A6), COMCEN SPEED (-> $92B4), DRONES (-> $92AE) and MISSILES (-> $92B0); columns are the game type folded to 0-4 (0 SCRIMAGE, 1 QB SNEAK, 2 THE BOMB/FACE-OFF/SLUGGERS, 3 FULL WAR, 4 DEFENDER). Values: 01 00 01 01 01 / 02 02 02 01 02 / 02 03 02 01 00 / 00 00 03 05 02 / 00 08 0C 14 14, i.e. SCRIMAGE has no drones and no missiles while FULL WAR gives 5 drones and 20 missiles."
   ]
  },
  "8945": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "gameTypeDefaultsSide1: byteTable, 25 bytes. Side 1 half of the same 5x5 table: 01 01 01 01 00 / 02 02 02 01 01 / 02 02 02 01 01 / 00 00 03 05 05 / 00 08 0C 14 08. The only asymmetric column is DEFENDER (4), where side 0 gets 2 drones and 20 missiles (the attacker) and side 1 gets 5 drones and 8 missiles (the defender). Note that the trainer AI's buildDirectionFan ($E7E3) later reads its eight compass offsets from $894A, which is the middle of this table and is never rewritten - see the insight below."
   ]
  },
  "897F": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "messageBlockTail: unknown, 129 bytes. 129 unreferenced bytes to the end of track 29 sector 10. They are a 50 percent dither: pairs of $00/$FF whose phase flips every eight bytes, plus a few stray values ($03,$F3,$BF,$0C,$DC,$B3,$3F,$C0,$CC,$01,$DF). The tail of the $9300 graphics block ($9FD1-$9FFF, track 28 sector 12) contains the same pattern, so both look like the same stale bitmap fragment from the mastering machine rather than game data. (confidence: medium)"
   ]
  },
  "F000": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "unitStartTemplate0: byteTable, 400 bytes. Unit start-position template 0 (400 bytes = four parallel 100-entry arrays, one entry per unit 0-99): +0 unitCol (values 3-36), +100 unitRow (side 0 units 0-49 in rows 20-33, side 1 units 50-99 in rows 6-19), +200 unitFlags (bits 0-1 facing - 0 for side 0, 1 for side 1; bit 5 set on every side-1 unit; bit 6 = 'belongs to a group'; bits 2-4 = group id 0-7, so $40+4*g on side 0 and $61+4*g on side 1, and $00/$21 for ungrouped units), +300 unitType ($10+type for side 0 and $18+type for side 1, type 0 GRUNT .. 4 COMCEN, $FF = slot not in play). FULL WAR / DEFENDER roster: 28 GRUNT, 12 RIDER, 6 BOOMER, 3 SPY, 1 COMCEN per side (all 50 slots used). Groups 0-5 are populated (flags $40,$44,$48,$4C,$50,$54 on side 0), 22 units are ungrouped. loadUnitStartTemplate ($ECF3, in the comm-module tail) copies 512 bytes from $F000 + 400*n to $F640 once per session, where n = unitLayoutByGameType[$78B8 + gameType & 7] = 2,2,2,1,3,0,0."
   ]
  },
  "F190": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "unitStartTemplate1: byteTable, 400 bytes. Unit start-position template 1 (400 bytes = four parallel 100-entry arrays, one entry per unit 0-99): +0 unitCol (values 3-36), +100 unitRow (side 0 units 0-49 in rows 20-33, side 1 units 50-99 in rows 6-19), +200 unitFlags (bits 0-1 facing - 0 for side 0, 1 for side 1; bit 5 set on every side-1 unit; bit 6 = 'belongs to a group'; bits 2-4 = group id 0-7, so $40+4*g on side 0 and $61+4*g on side 1, and $00/$21 for ungrouped units), +300 unitType ($10+type for side 0 and $18+type for side 1, type 0 GRUNT .. 4 COMCEN, $FF = slot not in play). FACE-OFF roster: 28 RIDER, 3 SPY, 1 COMCEN per side, 18 slots marked $FF (not in play). Groups 0-7 used. loadUnitStartTemplate ($ECF3, in the comm-module tail) copies 512 bytes from $F000 + 400*n to $F640 once per session, where n = unitLayoutByGameType[$78B8 + gameType & 7] = 2,2,2,1,3,0,0."
   ]
  },
  "F320": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "unitStartTemplate2: byteTable, 400 bytes. Unit start-position template 2 (400 bytes = four parallel 100-entry arrays, one entry per unit 0-99): +0 unitCol (values 3-36), +100 unitRow (side 0 units 0-49 in rows 20-33, side 1 units 50-99 in rows 6-19), +200 unitFlags (bits 0-1 facing - 0 for side 0, 1 for side 1; bit 5 set on every side-1 unit; bit 6 = 'belongs to a group'; bits 2-4 = group id 0-7, so $40+4*g on side 0 and $61+4*g on side 1, and $00/$21 for ungrouped units), +300 unitType ($10+type for side 0 and $18+type for side 1, type 0 GRUNT .. 4 COMCEN, $FF = slot not in play). SCRIMAGE / QB SNEAK / THE BOMB roster: 5 GRUNT, 5 RIDER and 1 COMCEN per side, 39 slots $FF. No unit belongs to a group (flags $00 on side 0, $21 on side 1). applyGameTypeDefaults then removes units 0-6 for QB SNEAK and everything except the two comcens for THE BOMB. loadUnitStartTemplate ($ECF3, in the comm-module tail) copies 512 bytes from $F000 + 400*n to $F640 once per session, where n = unitLayoutByGameType[$78B8 + gameType & 7] = 2,2,2,1,3,0,0."
   ]
  },
  "F4B0": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "unitStartTemplate3: byteTable, 400 bytes. Unit start-position template 3 (400 bytes = four parallel 100-entry arrays, one entry per unit 0-99): +0 unitCol (values 3-36), +100 unitRow (side 0 units 0-49 in rows 20-33, side 1 units 50-99 in rows 6-19), +200 unitFlags (bits 0-1 facing - 0 for side 0, 1 for side 1; bit 5 set on every side-1 unit; bit 6 = 'belongs to a group'; bits 2-4 = group id 0-7, so $40+4*g on side 0 and $61+4*g on side 1, and $00/$21 for ungrouped units), +300 unitType ($10+type for side 0 and $18+type for side 1, type 0 GRUNT .. 4 COMCEN, $FF = slot not in play). SLUGGERS roster: 12 GRUNT, 12 RIDER, 4 BOOMER, 3 SPY, 1 COMCEN per side, 18 slots $FF. Groups 0-7 used. loadUnitStartTemplate ($ECF3, in the comm-module tail) copies 512 bytes from $F000 + 400*n to $F640 once per session, where n = unitLayoutByGameType[$78B8 + gameType & 7] = 2,2,2,1,3,0,0."
   ]
  },
  "F640": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "mapBlockTail: unknown, 192 bytes. The last 192 bytes of track 18 sectors 8-14 are not part of any template - they are a fragment of 6502 code left in the mastering machine's memory. Disassembled it is an EARLIER version of printOptionsList ($19F3 in the shipped build): it sets a text window at column $0C rows 9-$12 with colour $61, prints a string at $CAB2, tests $92CD and $0881, prints $6610, checks the link flag $E03B, prints $6698 and $CA53, then falls into a film-slot routine that writes $91D5/$91D6/$91D7, calls $C097, $5F41, $1205 and jumps to $1648. The shipped build keeps the same flags at $0B94-$0BA8 and the same strings at $CC.. instead of $0876../$66.., so this code is from a different (older) build. At run time the area is unitCol[0..99] ($F640) and unitRow[0..99] ($F6A4), and startGameSession deliberately stores $FF into $F640 before calling loadUnitStartTemplate so the copy always happens. (confidence: high that it is leftover code, medium on which build)"
   ]
  },
  "0200": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "contourRuleTable: byteTable, 256 bytes. Terrain-contour rule table of the random map generator, read as contourRuleTable,Y at $7484 in overlay A. Y is an 8-bit mask of which of the eight neighbours of the current cell are higher terrain, bit order bit0=NW, bit1=N, bit2=NE, bit3=W, bit4=E, bit5=SW, bit6=S, bit7=SE (verified from the edge-clamp masks at $7463-$7481: $D6 at column 0, $6B at column 39, $F8 at row 0, $1F at row 39). Entry semantics: $00 (index 0 only) do nothing; $41-$51 a contour tile code to place, from which the generator subtracts contourTileOffset ($0324, 0 for the summit ring and $20 for the lower-hill ring) - $51 north edge, $4D south, $4B west, $49 east, $45 N+W corner, $46 N+E, $47 W+S, $48 E+S, $44/$43/$42/$41 the isolated NW/NE/SW/SE diagonals; $80+n (105 entries) randomly erase neighbour n instead; $91/$95/$97 (indexes $05, $A0, $84) erase that neighbour or else fill; $FF (93 entries) fill the cell with the interior tile. Loaded from track 18 sector 15 by loadSetupBlock ($10BB) / loadMapAndSetup_10AB and never written back to disk. Only valid between $0B35 and $7AE9, after which the trainer playbook (track 29 s6-8) overwrites the page."
   ]
  },
  "0296": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "trainerPlay1: byteTable, 150 bytes. Solo-trainer opening play 1: 150 bytes = three 50-entry arrays indexed by AI unit ordinal 0-49 (absolute unit 50-99). +0 group id per unit ($FF = no group, entry 49 = 0 = the AI comcen's own group), +50 destination column, +100 destination row (all in rows $06-$13, the AI's half). sub_EC91/$ECD4 in the trainer setup overlay computes the base as $0200 + 150*plan with multiply8x8, then copies the three slices to aiUnitGroup ($8832), unitDestCol+50 ($F802) and unitDestRow+50 ($F866) with Y counting $31..0. This play: same ten single-unit groups but numbered the other way round (10..1); columns evenly spaced $24,$1C,$18,$14,$10,$0C,$04 with two units held back at row $12. Its companion scalars come from mainGroupByPlanTable ($EC00 = FF,FF,04,04,04), groupCountByPlanTable ($EC05 = 0A,0A,04,04,04) and aiPlanParamTable ($EC0A = FF,FF,08,0C,0C)."
   ]
  },
  "032C": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "trainerPlay2: byteTable, 150 bytes. Solo-trainer opening play 2: 150 bytes = three 50-entry arrays indexed by AI unit ordinal 0-49 (absolute unit 50-99). +0 group id per unit ($FF = no group, entry 49 = 0 = the AI comcen's own group), +50 destination column, +100 destination row (all in rows $06-$13, the AI's half). sub_EC91/$ECD4 in the trainer setup overlay computes the base as $0200 + 150*plan with multiply8x8, then copies the three slices to aiUnitGroup ($8832), unitDestCol+50 ($F802) and unitDestRow+50 ($F866) with Y counting $31..0. This play: four real groups (1,2,3 and the assault group 4) covering units 0-27, a wedge of pairs at rows $0F-$11. Its companion scalars come from mainGroupByPlanTable ($EC00 = FF,FF,04,04,04), groupCountByPlanTable ($EC05 = 0A,0A,04,04,04) and aiPlanParamTable ($EC0A = FF,FF,08,0C,0C)."
   ]
  },
  "03C2": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "trainerPlay3: byteTable, 150 bytes. Solo-trainer opening play 3: 150 bytes = three 50-entry arrays indexed by AI unit ordinal 0-49 (absolute unit 50-99). +0 group id per unit ($FF = no group, entry 49 = 0 = the AI comcen's own group), +50 destination column, +100 destination row (all in rows $06-$13, the AI's half). sub_EC91/$ECD4 in the trainer setup overlay computes the base as $0200 + 150*plan with multiply8x8, then copies the three slices to aiUnitGroup ($8832), unitDestCol+50 ($F802) and unitDestRow+50 ($F866) with Y counting $31..0. This play: same four groups with group 4 leading (units 0-11 and 24-27), rows $0C-$10. Its companion scalars come from mainGroupByPlanTable ($EC00 = FF,FF,04,04,04), groupCountByPlanTable ($EC05 = 0A,0A,04,04,04) and aiPlanParamTable ($EC0A = FF,FF,08,0C,0C)."
   ]
  },
  "0458": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "trainerPlay4: byteTable, 150 bytes. Solo-trainer opening play 4: 150 bytes = three 50-entry arrays indexed by AI unit ordinal 0-49 (absolute unit 50-99). +0 group id per unit ($FF = no group, entry 49 = 0 = the AI comcen's own group), +50 destination column, +100 destination row (all in rows $06-$13, the AI's half). sub_EC91/$ECD4 in the trainer setup overlay computes the base as $0200 + 150*plan with multiply8x8, then copies the three slices to aiUnitGroup ($8832), unitDestCol+50 ($F802) and unitDestRow+50 ($F866) with Y counting $31..0. This play: the default plan: group 4 takes units 0-27 and 40-45, groups 1/2/3 the flanks; the widest spread of destinations (rows $07-$10). Its companion scalars come from mainGroupByPlanTable ($EC00 = FF,FF,04,04,04), groupCountByPlanTable ($EC05 = 0A,0A,04,04,04) and aiPlanParamTable ($EC0A = FF,FF,08,0C,0C)."
   ]
  },
  "04EE": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "playbookTail: unknown, 18 bytes. 18 bytes of filler after the fifth play (5 x 150 = 750 bytes end at $04ED). Content: $20,$20, then $06,$01,$13,$14,$0C,$0F,$01,$04, then eight more $20 - that is ' FASTLOAD' followed by spaces in C64 SCREEN codes, so it is a scrap of the mastering machine's screen memory, not game data. (confidence: medium)"
   ]
  },
  "0400": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "checksumXorConstant: byteTable, 1 bytes. $B0. The constant verifyProgramChecksum ($0461) EORs into the final sum before storing it in programChecksum $0B7E. It sits one byte below the start of the checksummed range ($0402), so it can never influence itself."
   ]
  },
  "0401": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "mapNameSeedTable: byteTable, 96 bytes. 32 entries of 3 bytes for the manual look-up protection at $09A6: randomSeed ($09C3) AND $1F picks an entry, the triple is copied to $0B95-$0B97 and used as the 24-bit map seed, the generated map is shown with 'IDENTIFY THIS MAP (SEE MANUAL)' ($0A48) and the five-character name the player types is packed through mapNameAlphabet ($890C) into zp_5A-zp_5C and compared with the same triple. Three failures wipe memory ($0A1E). Note $0401 (entry 0's first byte) is the only table byte outside the checksummed range."
   ]
  },
  "04AE": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "scratchPageTail: unknown, 82 bytes. $00 on disk. At run time this is shared scratch: the 2x2 tile buffer $04C8-$04E7 filled by the terrain drawing code at $279F/$27CD, statistics accumulators, and the map generator's blob stacks. The trainer's playbook load (track 29 s6-8 -> $0200-$04FF) also overwrites it."
   ]
  },
  "0500": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "messagePtrLoTable: byteTable, 36 bytes. Low bytes of the 36 status-message pointers, indexed by message id $00-$23; read by the message system at $C536/$C5E5 and patched at run time by setMessageSlot ($C924). Ids $1E, $20 and $21 are $FFFF placeholders on disk: $20/$21 are filled with $8800/$881C by mainProgram $0B1D/$0B26, with $895E/$8837 by showModemSetupPrompt ($0C8C), and with the modem driver's own $EFxx strings by $EED6/$EEFF."
   ]
  },
  "0524": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "messagePtrHiTable: byteTable, 36 bytes. High bytes of the same 36 pointers. $05/$06 = strings in this block, $87 = strings in the $6F00 overlay variant B, $88 = strings in the loaded $8800 message block, $FF = unset placeholder."
   ]
  },
  "0548": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "chatMessageLine: text, 36 bytes. Message id $00, the chat / typed line. $0548 is a leading marker glyph ($1E = cursor, or $9E written by sub_C62A to terminate at once = blank line) and $0549-$056B hold up to 35 characters mirrored from $9253 with bit 7 set on the last. On disk it is 35 spaces followed by $A0."
   ]
  },
  "056C": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgWorking: text, 10 bytes. Message id $01 \"WORKING...\" (bit-7 terminated), sound $80 = silent."
   ]
  },
  "0576": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgGameAborted: text, 13 bytes. Message id $03 \"GAME ABORTED.\", sound $03."
   ]
  },
  "0583": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgNotAGameFilm: text, 16 bytes. Message id $09 \"NOT A GAME FILM!\", sound $03 - a film load was rejected."
   ]
  },
  "0593": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgSetupUnitsPressF1WhenDone: text, 33 bytes. Message id $0A \"SETUP UNITS. PRESS F 1 WHEN DONE.\", silent - the unit setup phase prompt."
   ]
  },
  "05B4": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgTimeoutRunStopToResume: text, 28 bytes. Message id $19 \"TIMEOUT. RUN/STOP TO RESUME.\", sound $19."
   ]
  },
  "05D0": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgResumed: text, 7 bytes. Message id $1B \"RESUMED\", sound $19."
   ]
  },
  "05D7": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgTheBattleBegins: text, 18 bytes. Message id $1C \"THE BATTLE BEGINS!\", sound $03."
   ]
  },
  "05E9": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgLastQtrWarning: text, 17 bytes. Message id $1D \"LAST QTR WARNING!\", sound $19 - the game is played in quarters."
   ]
  },
  "05FA": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgPhoneTrouble: text, 14 bytes. Message id $22 \"PHONE TROUBLE.\", sound $15 - modem link failure."
   ]
  },
  "0608": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgTryAgain: text, 10 bytes. Message id $23 \"TRY AGAIN.\", sound $03."
   ]
  },
  "0612": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "messageSoundTable: byteTable, 36 bytes. Sound effect number played when message id X is shown (read at $C557, played through sub_C89A); $80 = silent. $0E for the two 'we lost' messages, $0D for the two 'we won' messages, $19 for GAME OVER / TIMEOUT / RESUMED / LAST QTR WARNING, $15 for PHONE TROUBLE, $06 for the two phone prompts."
   ]
  },
  "0636": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "optionSummaryTemplate: text, 23 bytes. \" MENUS XXXX DAMAGE XXX.\" (bit-7 terminated) - a rules summary line whose XXXX and XXX fields are overwritten in place by the $6F00 overlays ($870C, $8739-$8751) with OFF,/ON, and FULL/HALF/NONE before it is printed."
   ]
  },
  "064D": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "optionWordTable: text, 17 bytes. Five bit-7 terminated words at fixed offsets, copied into optionSummaryTemplate: +0 \"OFF\", +3 \"ON\", +5 \"FULL\", +9 \"HALF\", +13 \"NONE\". XREF.txt calls this label eaCopyrightMessage, which is wrong (that name belongs to the boot-time `ea` file that once occupied $02A8-$030B)."
   ]
  },
  "065E": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgClicks: text, 10 bytes. \"   CLICKS:\" - the score header printed by $C6BC (LDA #$5E / LDY #$06 / JSR $C133). \"Clicks\" are Modem Wars' score unit."
   ]
  },
  "0668": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgUs: text, 7 bytes. \"    US:\" - printed straight after msgClicks, followed by the local player's 16-bit score from $92A2/$92A4."
   ]
  },
  "066F": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgThem: text, 8 bytes. \"   THEM:\" - the opponent's score label, printed by the same $C6B6 routine."
   ]
  },
  "0677": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgTypeMapName: text, 40 bytes. Three lines separated by $0D: \"TYPE MAP NAME\" / \" (F7 RANDOM)\" / \" (F5 SYM/RND)\" - the prompt of the map name editor in overlay A, also used by the manual look-up protection at $09EF."
   ]
  },
  "069F": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "msgBtnToAccept: text, 27 bytes. Two lines separated by $0D: \"BTN TO ACCEPT\" / \"(F 1 TO FLIP)\" - shown while the generated map is offered for acceptance."
   ]
  },
  "0770": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "tileVariantMaskTable: byteTable, 40 bytes. Indexed by map cell value minus $40 (cells below $40 use cell-$20), covering cells $40-$67. The value is ANDed with a scenario random number and added to the base tile by randomTileVariant ($7872, overlay A): cells $49, $4B and $65 -> mask 1; cells $4D, $51, $59, $5D, $61 -> mask 3; everything else 0."
   ]
  },
  "0798": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "tileVariantPad: unknown, 1 bytes. $00 pad between tileVariantMaskTable and unitGlyphBaseTable."
   ]
  },
  "0799": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "unitGlyphBaseTable: byteTable, 6 bytes. Base glyph index of the one-character overview symbol per unit type: $2C GRUNT, $30 RIDER, $34 BOOMER, $38 SPY, $3C COMCEN (plus one $00 pad). The facing (unitFlags AND 3) is added; the result indexes the 8x8 shapes at $9C6E + code*8, and, doubled after subtracting $2C, the 16x16 glyph pointer tables ($28A0, $6C35, $7512, $7538)."
   ]
  },
  "079F": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "terrainClassTable: byteTable, 75 bytes. Map cell value -> terrain class 0-8. Addressed as $077E,Y with the raw cell value, so the body runs from $077E+$21 = $079F to $077E+$6B = $07E9: cells $21-$34 -> 3 (lower contour), $35-$3F -> $FF (invalid), $40 -> 0 (blank), $41-$54 -> 5 (upper contour), $55-$5C -> 1 (river), $5D-$60 -> 2 (forest), $61-$64 -> 4 (lower hill), $65-$66 -> 6 (summit), $67 -> 7 (recycler), $68-$6B -> 8 (end zone). The same bytes are read as $07BE,X by $5BC8 with the index already reduced to cell-$40. The trainer also reads it as terrainClassTable $077E from $E773."
   ]
  },
  "07EA": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "terrainColourTable: byteTable, 9 bytes. Colour-RAM nibble per terrain class 0-8: 7,3,5,8,8,8,8,0,0 (yellow plain, cyan river, green forest, orange for every contour/hill/summit class, black for classes 7-8). Read at $27AB and $5BCB immediately after a terrainClassTable lookup."
   ]
  },
  "07F3": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "table0400Tail: unknown, 13 bytes. $00 filler to the end of the page; unreferenced."
   ]
  },
  "0389": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "setupBlockPadding: unknown, 119 bytes. $0389-$03FF, all zero on disk: the tail of track 18 sector 16 after the one-shot start-up code at $0300-$0388. Not referenced as code or data. At run time the page is reused for the film directory, the RLE sprite-shape buffer, overlay A's map-generator variables and the trainer playbook."
   ]
  },
  "FC50": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "multicolorFillPatterns: byteTable, 4 bytes. $00,$55,$AA,$FF - colour index 0-3 replicated into all four multicolour pixel pairs of a bitmap byte. Read by plotBitmapPixelRow as $FC50,X with X = the colour index."
   ]
  },
  "FC54": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "multicolorPixelMasks: byteTable, 4 bytes. $C0,$30,$0C,$03 - the bit mask of multicolour pixel pair (X AND 3) inside a bitmap byte. ANDed with the fill pattern and merged into the byte by plotBitmapPixelRow."
   ]
  },
  "FD70": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "missileCursorShapesRle: sprite, 180 bytes. RLE-compressed set of eight 24x21 sprites, first byte $01 = the escape marker. runMissileScreen ($7587 in the $6F00 overlay variant B) does LDA #$70 / LDY #$FD / JSR unpackRleTo0200 ($67CB), which expands it to exactly 512 bytes at $0200-$03FF = eight shapes on a 64-byte stride; loadDirectionShapeSprite ($3340) then copies shape number ($920B) from $0200 + 64*n into sprite 0 ($8A00) or sprite 5 ($8B40). The eight shapes are a small arrow/missile marker in the eight compass headings in the order N, NE, E, SE, S, SW, W, NW."
   ]
  },
  "FE24": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "droneShapesRle: sprite, 367 bytes. RLE-compressed set of eight 24x21 sprites, first byte $05 = the escape marker. enterDroneScreen ($7FE6, overlay variant B) does LDA #$24 / LDY #$FE / JSR unpackRleTo0200, giving eight 64-byte shapes at $0200-$03FF that loadDirectionShapeSprite copies into the sprite data. The shapes are a top-down aeroplane silhouette (the drone) in the eight compass headings N, NE, E, SE, S, SW, W, NW. The stream ends with the run $05,$0E,$00 at $FF90-$FF92, which pads the buffer out to 512 bytes."
   ]
  },
  "FF93": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "targetingReticleSprite: sprite, 63 bytes. One complete uncompressed 24x21 sprite: four corner brackets with a centre cross - the drone screen's targeting reticle. Copied into sprite 2 by overlay B at $80EB (LDA $FF93,Y) when the drone screen is built, so the drone starts over the comcen with this frame drawn around it. It ends exactly at $FFD1, the last byte of the relocated high-memory block."
   ]
  },
  "1DA2": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "scrollViewParamTables: byteTable, 66 bytes. Twenty-two parallel 3-byte tables (one entry per direction: 0 = -1, 1 = 0, 2 = +1) that parameterise scrollViewBitmap ($1DE4), which shifts the 7x5-cell tactical view one cell without redrawing it. In order: $1DA2 bitmap copy start index ($5F,$00,$00), $1DA5 end index ($FF,$70,$60), $1DA8 the DEY/INY opcode patched into both copy loops ($88,$C8,$C8), $1DAB/$1DAE bitmap source and destination byte offsets (0,0,$10 / $10,0,0), $1DB1/$1DB4 bitmap source address lo/hi ($AAC8,$A208,$A488), $1DB7/$1DBA bitmap destination lo/hi ($AD48,$A208,$A208), $1DBD/$1DC0 bitmap row stride lo/hi ($FEC0 = -320 up, $0140 = +320), $1DC3 rows to copy (8,10,8), $1DC6/$1DC9 screen copy start/end index, $1DCC/$1DCF screen source/destination column offsets, $1DD2/$1DD5 screen source lo/hi ($8D59,$8C41,$8C91), $1DD8/$1DDB screen destination lo/hi ($8DA9,$8C41,$8C41) and $1DDE/$1DE1 screen row stride lo/hi ($FFD8 = -40 up, $0028 = +40). Colour RAM is handled by adding $4C to the screen high byte."
   ]
  },
  "2BDD": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "windowLeftTable: byteTable, 27 bytes. Left screen column of the 27 predefined windows 0-26, read by drawWindowFrame with X = window index. 0 = the overview panel (column 0), 1 = the tactical view (column $18), 2 = the status panel, 5-$16 the $6F00 overlay screens, $18/$19 the main menu frames."
   ]
  },
  "2BF8": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "windowTopTable: byteTable, 27 bytes. Top screen row of windows 0-26."
   ]
  },
  "2C13": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "windowWidthTable: byteTable, 27 bytes. Width in character cells including the frame: window 0 is $16 = 22 wide (the 20x20 overview map plus its frame), window 1 is $10 = 16 (the 7x5-cell tactical view at 2 cells per map cell plus frame), window $18 is the full 40 columns."
   ]
  },
  "2C2E": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "windowHeightTable: byteTable, 27 bytes. Height in character cells of windows 0-26 (window 0 = $16 = 22, window 1 = $0C = 12)."
   ]
  },
  "2C49": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "windowFramePtrTable: addrTable, 54 bytes. 27 little-endian pointers selecting each window's 9-byte frame tile set out of the ten sets at $9824-$987D in the graphics block. Windows 0-3 use the four decorated sets $985A/$9863/$986C/$9875, the rest reuse $9824 (thin box), $982D, $9836 (octagon), $983F (shadowed), $9848 (solid) and $9851 (blank/erase)."
   ]
  },
  "5A59": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "overviewPatternLo: byteTable, 33 bytes. Low bytes of the overview-map pattern pointer for terrain index $00-$20 (33 entries), read by buildOverviewQuadrant at $5C40 with Y = mapCell - $40, or mapCell - $20 when the cell is below $40, clamped below $21 ($5BC3). Index 0 (blank ground) -> $5A9B; indices 1-8 cycle through the four contour-edge patterns $5A9F/$5AA3/$5AA7/$5AAB; 9-$0C -> $5AAF; $0D-$14 -> $5AB3; $15-$18 (river cells $55-$58) -> $5AC7/$5ACB/$5ACF/$5AD3; $19-$1C -> $5AC3; $1D-$20 (forest cells $5D-$60) -> $5ABB."
   ]
  },
  "5A7A": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "overviewPatternHi: byteTable, 33 bytes. High bytes of the same 33 pointers - all $5A, because every pattern lives in this block."
   ]
  },
  "5A9B": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "overviewPatterns: byteTable, 60 bytes. Fifteen patterns of four bitmap scanline bytes each. The overview draws each 2x2 block of map cells as one 8x8 character, so a pattern carries both horizontal halves as nibble pairs ($00/$33/$CC/$FF) and the quadrant mask picks the half that belongs to the cell. Contents: $5A9B 00 00 00 00 (blank), $5A9F 00 00 33 33, $5AA3 00 CC 33 33, $5AA7 33 00 00 00, $5AAB 33 CC 00 00 (the four contour edges), $5AAF 33 33 33 33, $5AB3 00 FF 00 00, $5AB7 FF FF FF FF (a live unit), $5ABB 33 CC 33 CC (forest checker), $5ABF FF 00 FF 00 (a cloaked unit), $5AC3 CC FF FF 33, $5AC7 33 FF FF FF, $5ACB CC FF FF FF, $5ACF FF FF FF 33, $5AD3 FF FF FF CC (the four river blob quadrants). $5AB7 and $5ABF are the only two not reachable through overviewPatternLo/Hi - buildOverviewQuadrant uses them directly for units."
   ]
  },
  "6782": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "sidVoiceRegOffsetTable: byteTable, 5 bytes. SID register offset per voice, indexed by voice*2 so the driver can use the same X for its own state and for the chip: $00, (pad), $07, (pad), $0E."
   ]
  },
  "6787": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "sndCommandSlotTable: byteTable, 11 bytes. Sound-script command 0-10 -> byte offset of its state slot from sndVoiceState ($6792): 0 duration ($00, also ends processing), 1 control/waveform ($01), 2 loop counter A ($06), 3 loop counter B ($07), 4/5 spare ($0C/$0D), 6 frequency ($12, 2 bytes), 7 pulse width ($18, 2), 8 AD/SR ($1E, 2), 9 frequency delta ($24, 2), 10 pulse delta ($2A, 2). runSoundScript adds voice*2 to the entry and treats every slot from $12 up as 16-bit ($66DF CPY #$12)."
   ]
  },
  "6792": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "sndVoiceState: byteTable, 48 bytes. Work RAM, zero on disk: eight 6-byte rows of per-voice sound state (duration, control, loop counters, spares, frequency, pulse width, ADSR, and the two deltas), cleared per voice by startSound."
   ]
  },
  "67C2": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "sndDriverVars: byteTable, 6 bytes. Work RAM, zero on disk: sndVolume, sndLastByte ($67C3, the current script byte), sndScratch, sndInitLock, sndCurrentSound, sndStartVoiceIndex."
   ]
  },
  "67C8": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "sndVoiceSoundId: byteTable, 3 bytes. Work RAM, zero on disk: the sound id currently playing on each of the three voices (0 = free). $67CA is polled by the text engine at $C909/$C913 before it plays a UI beep."
   ]
  },
  "6831": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "soundPointerTable: wordTable, 174 bytes. 29 sound effects x three little-endian voice-script addresses (6 bytes per sound, $0000 = the sound does not use that voice). Sound $00 points all three voices at the 60-tick silence script $68DF (stop everything) and $1A/$1B/$1C silence voice 0/1/2 individually. Multi-voice effects are $06 ($6980/$69C9/$68DF), $0D ($6AA7/$6AB5/$6AC3), $0E ($6ADC/$6AEC/$6AFC), $15 ($68E7/$68F0/$68DF) and $19 ($6BFB/$6C15/$6C1B). Sounds $01-$07 and $0C use voice 0, $08-$0B voice 1 and $0F-$18 voice 2. messageSoundTable ($0612) picks the sound played with each status message."
   ]
  },
  "68DF": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "soundScriptData: byteTable, 854 bytes. The bytecode for all the voice scripts, in the format interpreted by runSoundScript ($66C7): a byte $00-$0A stores the following one or two bytes into the state slot named by sndCommandSlotTable (slot 0 = duration ends the frame, duration 0 ends the voice); $40-$7F is a loop whose low six bits select a counter slot and which is followed by a 16-bit target address; $80-$FF adds the scenario random byte zp_5A to the frequency low byte (noise jitter). $68DF is the shared 60-tick silence script and $68E3 ('01 00 00 00') is the common end-of-sound target that the many '46 E3 68' jumps branch to."
   ]
  },
  "6C35": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "unitGlyphPointerTable: addrTable, 40 bytes. 20 little-endian pointers to the 32-byte unit glyphs that follow, indexed by unitType*4 + facing - the caller computes (unitFlags AND 3) + unitGlyphBaseTable[$0799 + unitType] - $2C and doubles it."
   ]
  },
  "6C5D": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "unitGlyphShapes: charset, 640 bytes. 20 multicolour unit glyphs of 16x16 pixels, 32 bytes each, stored as four 8-byte character cells in the order top-left, top-right, bottom-left, bottom-right. Every lit pixel is the bit pair %11, so the byte values are combinations of $00/$03/$0C/$30/$C0/$33/$3C/$F0/$FC/$FF. The 20 shapes are five unit types x four facings, all clearly recognisable when rendered: 0-3 GRUNT (a stubby figure with a head, up/down/left/right), 4-7 RIDER (a mounted figure with a wide base), 8-11 BOOMER (a heavy block with a protruding barrel), 12-15 SPY (a small chevron/arrow), 16-19 COMCEN (a large domed block). These are the big glyphs used in the 16x16-per-cell tactical view; the single-cell versions are the 8x8 shapes at $9DCE."
   ]
  },
  "6EDD": {
   "unit": "game/pure-data units (index; per-unit detail in data_data_9300.json, data_ovl_8800.json, data_map_F000.json, data_setup_0200_default.json, data_table_0400.json, data_setup_0200.json, data_high_FBB8.json, data_main_0800.json)",
   "block": [
    "mainProgramPadding: unknown, 35 bytes. $6EDD-$6EFF, all zero: filler to the end of the main program image (track 22-27). Unreferenced."
   ]
  }
 }
}