{
 "chunk": "game_main_0800_652E",
 "unit": "game/main_0800",
 "routines": [
  {
   "addr": "652E",
   "name": "runRepairWhichMenu",
   "summary": "REPAIR tab menu (the disassembly shows it as data; it is JSR'd from $1860 inside repairTabAction, itself mis-typed). Opens a 7-column text window at column 3, rows 9-19 (colour $61), prints 'REPAIR\\rWHICH:\\r\\r', appends menu items 13-16 (ENERGY, BATTLE, RADAR, DRONE) and 12 ((EXIT)) with addMenuItem, then runs the menu selector with selectedUnit=$FF (repair-menu mode, which makes the highlight blink the comcen system marker). Index 4 = (EXIT) clears the window with colour $66 and returns; otherwise, unless film playback is active or D_923F bit 6 is set (getSoloOrFlag923F), it calls attemptSystemRepair for system D_91D5, removes the highlight and re-runs the menu.",
   "inputs": "D_0B9B/D_923F via sub_0EE5, D_91D5 after the selector, comcen status via attemptSystemRepair",
   "outputs": "zp_39-zp_3D window/colour, zp_43=0, D_90EF=0, zp_3E=0, D_91D4/D_91D5/D_91D6 menu state, D_90F8=$FF, zp_1C loop counter; window cleared on exit",
   "confidence": "high"
  },
  {
   "addr": "65A0",
   "name": "runInGameOptionsMenu",
   "summary": "Runs the MISC-tab options menu while a game is in progress (items printed by printOptionsList: ABORT GAME, optional VOICE PAUSE, (EXIT)); called from miscTabAction ($1A78). Sets selectedUnit=$FE (plain menu) and colour $60, runs the selector and returns (after removing the highlight) for any index other than 1. Index 1 = VOICE PAUSE, valid only when not in film/solo mode and the comm build flag D_0BA5 is clear: if the carrier is up (D_E03C bit 6) it sends command $8C with reason 5 to the opponent, sets D_91D5=$81 and spins in serviceFrame until bit 7 is cleared by voicePauseReconnect (invoked from the $C000 overlay at $C768 when zp_BE flags the pause); otherwise it calls voicePauseReconnect directly. Then it restarts the menu.",
   "inputs": "D_91D4 item count and D_91D6 first row prepared by the caller, D_0B9B, D_0BA5, D_E03C, zp_73",
   "outputs": "Y = D_91D5 selected index (0 = ABORT GAME, last = (EXIT)), D_90F8=$FE, zp_3D=$60, command $8C/5 queued, highlight removed",
   "confidence": "high"
  },
  {
   "addr": "65E7",
   "name": "waitForGroupRepositionsDone",
   "summary": "If the selected group id D_9206 equals lastRepositionedGroup D_929D, loops while pendingRepositionCount D_929E is nonzero, calling serviceFrame and checkUiContinue each pass. When checkUiContinue returns 0 (game over / screen switch) it pops the return address and returns to the caller's caller, abandoning the group operation. Used by the group (GROUP/FORMATION) screens at $621D and $63B7 so they operate on settled unit positions.",
   "inputs": "D_9206, D_929D, D_929E",
   "outputs": "none (may unwind one stack level)",
   "confidence": "medium"
  },
  {
   "addr": "65FF",
   "name": "runFormationOptionsMenu",
   "summary": "Clears the info panel, prints the centred 'FORMATION' title, '   OPTIONS\\r\\r' ($CB13) and the item list 'SWEEP LEFT / SWEEP RIGHT / REVERSE / CUSTOM / (EXIT)' ($CA8C), sets item count 5 and runs the menu selector. For indices 0-2 it sends command $A3 with cmdParam1 = selectedUnit and cmdParam2 = the chosen option (sub_560F). Returns Y = selected index (3 = CUSTOM, 4 = (EXIT)). Called from $62FD.",
   "inputs": "D_90F8 selected unit (or group leader)",
   "outputs": "Y = D_91D5, zp_74/zp_75 command parameters, command $A3 queued, D_91D4=5",
   "confidence": "high"
  },
  {
   "addr": "6637",
   "name": "soundPlayerTick",
   "summary": "Per-frame SID sound driver, called from the raster IRQ at $128C (the IRQ first dequeues a sound with $C8E9 and calls startSound at $1289). Returns immediately while sndInitLock ($67C5) is nonzero. Writes sndVolume ($67C2) to SID_VOLUME and, if nonzero, updates voices 0 and 1 through updateSoundVoice and falls into it for voice 2 (X = 0, 2, 4). The whole $6637-$6781 block is shown as .byte in the disassembly but is code.",
   "inputs": "$67C5, $67C2, per-voice state at $6792-$67C1, script pointers zp_D8-zp_DD",
   "outputs": "SID registers $D400-$D418, voice state, $67C8-$67CA",
   "confidence": "high"
  },
  {
   "addr": "6651",
   "name": "updateSoundVoice",
   "summary": "Advances one sound voice (X = voice*2). Does nothing while the voice duration counter ($6792,x) is 0 (idle). Adds the 16-bit frequency delta ($67B6/$67B7,x) to the frequency ($67A4/$67A5,x) and the pulse delta ($67BC/$67BD,x) to the pulse width ($67AA/$67AB,x), decrements the duration and when it reaches 0 runs the voice script (runSoundScript); if the script ended (duration still 0) the voice's sound id ($67C8 + voice) is cleared. Finally writes frequency, pulse width, AD, SR and the control byte ($6793,x) to the SID voice at offset sidVoiceRegOffsetTable[x].",
   "inputs": "X = 0/2/4, voice state arrays $6792-$67C1",
   "outputs": "SID voice registers, $67C8+voice cleared on end, X preserved",
   "confidence": "high"
  },
  {
   "addr": "66C7",
   "name": "runSoundScript",
   "summary": "Interprets the voice's script bytecode (pointer zp_D8/zp_D9 indexed by X) until a 'set duration' command. Byte formats: $80-$FF = add zp_5A (scenario RNG byte, used as jitter) to the frequency low byte; $40-$7F = loop: the low 6 bits select a counter slot ($6792+slot+x), followed by a 16-bit target address - if the counter is 0 the jump is always taken, otherwise it is decremented and taken while nonzero ($46 = counter A set by command 2, $47 = counter B set by command 3); $00-$0A = store the following byte(s) into the state slot given by sndCommandSlotTable: 0 duration (ends processing), 1 control/waveform, 2 loop counter A, 3 loop counter B, 4/5 spare slots, 6 frequency (2 bytes), 7 pulse width (2), 8 AD/SR (2), 9 frequency delta (2), 10 pulse delta (2). A duration of 0 ends the voice. $66BF is the bit-7 jitter handler that falls back into this loop.",
   "inputs": "X = voice*2, script at (zp_D8,x), $67C3 current byte",
   "outputs": "voice state $6792-$67C1 updated, script pointer advanced, $67C4 scratch",
   "confidence": "high"
  },
  {
   "addr": "671C",
   "name": "fetchSoundScriptByte",
   "summary": "Reads the next byte of the voice script through the zero-page pointer (zp_D8,x), stores it in sndLastByte ($67C3) for the BIT test and post-increments the 16-bit pointer.",
   "inputs": "X = voice*2, zp_D8/zp_D9 + X",
   "outputs": "A = byte, $67C3 = byte, pointer incremented",
   "confidence": "high"
  },
  {
   "addr": "6728",
   "name": "startSound",
   "summary": "Starts sound A (0-28) on up to three voices; called from the IRQ ($1289) with the id dequeued by $C8E9. Sets sndInitLock=3 (loop counter, also blocks soundPlayerTick), sndVolume=$0C, sndCurrentSound=A, indexes soundPointerTable by A*6 and for every voice whose pointer high byte is nonzero: copies the pointer to zp_D8/zp_D9 (+voice*2), zeroes the eight 6-byte state rows for that voice ($6792-$67C1 stepping by 6), records A in sndVoiceSoundId[voice] and sets the duration to 2 so the script starts on the second tick. Voices with a zero pointer are left untouched (still playing their previous sound).",
   "inputs": "A = sound id, soundPointerTable $6831",
   "outputs": "$67C2-$67C7, $67C8-$67CA, zp_D8-zp_DD, voice state; X/Y clobbered",
   "confidence": "high"
  },
  {
   "addr": "67CB",
   "name": "unpackRleTo0200",
   "summary": "Run-length decoder: unpacks the block at A/Y into $0200-$03FF (512 bytes, zp_B2/zp_B3 destination, zp_1A page counter). The first byte of the block is the escape marker (saved in D_9246; D_9247 is cleared); in the stream 'marker, count, value' emits value count times, any other byte is literal. The source pointer is self-modified into fetchRleByte. Callers: overlay B ($7596 with the block at $FD70, $7FF0 with $FE24) and the $C000 overlay sub_CD0A (five blocks at $CD52/$CDD3/$CE3B/$CECD/$CF3A), which then copy the unpacked page elsewhere.",
   "inputs": "A/Y = source block address",
   "outputs": "$0200-$03FF filled, D_9246 = marker, D_9247 = 0, zp_B2/zp_B3/zp_1A/zp_1C clobbered, sub_681A operand patched",
   "confidence": "high"
  },
  {
   "addr": "681A",
   "name": "fetchRleByte",
   "summary": "Returns the next byte of the RLE stream from the self-modified absolute address at $681B/$681C and increments that address. Shared with the overlay-B bitmap unpacker at $8644-$868B (which also compares against D_9246).",
   "inputs": "patched operand $681B/$681C",
   "outputs": "A = X = byte, operand incremented",
   "confidence": "high"
  }
 ],
 "variables": [
  {
   "addr": "1A",
   "scope": "zp",
   "name": "rlePageCounter",
   "meaning": "unpackRleTo0200: pages left to fill (2); general scratch elsewhere",
   "confidence": "high"
  },
  {
   "addr": "1C",
   "scope": "zp",
   "name": "scratchCounter1C",
   "meaning": "runRepairWhichMenu: item-id loop counter 13..16; unpackRleTo0200: run length of the current RLE run",
   "confidence": "high"
  },
  {
   "addr": "39",
   "scope": "zp",
   "name": "textWindowTop",
   "meaning": "text window top row (9 for the repair menu)",
   "confidence": "high"
  },
  {
   "addr": "3A",
   "scope": "zp",
   "name": "textWindowBottom",
   "meaning": "text window bottom row limit (top+10 for the repair menu)",
   "confidence": "high"
  },
  {
   "addr": "3B",
   "scope": "zp",
   "name": "textWindowLeft",
   "meaning": "text window left column (3 for the repair menu)",
   "confidence": "high"
  },
  {
   "addr": "3C",
   "scope": "zp",
   "name": "textWindowWidth",
   "meaning": "text window width in characters (7 for the repair menu); also the width of the menu highlight bar",
   "confidence": "high"
  },
  {
   "addr": "3D",
   "scope": "zp",
   "name": "textColour",
   "meaning": "colour byte for printed text and for the menu highlight EOR ($61 print, $60 highlight, $66 clear in the repair menu)",
   "confidence": "high"
  },
  {
   "addr": "3E",
   "scope": "zp",
   "name": "textFillChar",
   "meaning": "fill character for window clears (0 here)",
   "confidence": "medium"
  },
  {
   "addr": "40",
   "scope": "zp",
   "name": "textCursorRow",
   "meaning": "text cursor row; copied to menuFirstRow after printing the menu title",
   "confidence": "high"
  },
  {
   "addr": "43",
   "scope": "zp",
   "name": "textRawMode",
   "meaning": "text engine flag cleared before printing menus",
   "confidence": "medium"
  },
  {
   "addr": "5A",
   "scope": "zp",
   "name": "scenarioSeed0",
   "meaning": "scenario RNG state byte 0; the sound script command $80-$FF adds it to the voice frequency as pseudo-random jitter",
   "confidence": "medium"
  },
  {
   "addr": "73",
   "scope": "zp",
   "name": "cmdParam0",
   "meaning": "first parameter of an outgoing command (reason 5 for the voice-pause $8C)",
   "confidence": "high"
  },
  {
   "addr": "74",
   "scope": "zp",
   "name": "cmdParam1",
   "meaning": "second command parameter (selected unit for formation command $A3)",
   "confidence": "high"
  },
  {
   "addr": "75",
   "scope": "zp",
   "name": "cmdParam2",
   "meaning": "third command parameter (formation option 0-2 for command $A3)",
   "confidence": "high"
  },
  {
   "addr": "B2",
   "scope": "zp",
   "name": "rleDestPtr",
   "meaning": "unpackRleTo0200 destination pointer low byte ($00)",
   "confidence": "high"
  },
  {
   "addr": "B3",
   "scope": "zp",
   "name": "rleDestPtrHi",
   "meaning": "unpackRleTo0200 destination pointer high byte ($02, $03)",
   "confidence": "high"
  },
  {
   "addr": "D8",
   "scope": "zp",
   "name": "sndVoice0ScriptPtr",
   "meaning": "sound script read pointer of SID voice 0 (lo); zp_D9 hi. Indexed by voice*2 from the sound driver",
   "confidence": "high"
  },
  {
   "addr": "D9",
   "scope": "zp",
   "name": "sndVoice0ScriptPtrHi",
   "meaning": "sound script pointer of voice 0, high byte",
   "confidence": "high"
  },
  {
   "addr": "DA",
   "scope": "zp",
   "name": "sndVoice1ScriptPtr",
   "meaning": "sound script pointer of SID voice 1 (lo/hi at $DA/$DB)",
   "confidence": "high"
  },
  {
   "addr": "DB",
   "scope": "zp",
   "name": "sndVoice1ScriptPtrHi",
   "meaning": "sound script pointer of voice 1, high byte",
   "confidence": "high"
  },
  {
   "addr": "DC",
   "scope": "zp",
   "name": "sndVoice2ScriptPtr",
   "meaning": "sound script pointer of SID voice 2 (lo/hi at $DC/$DD)",
   "confidence": "high"
  },
  {
   "addr": "DD",
   "scope": "zp",
   "name": "sndVoice2ScriptPtrHi",
   "meaning": "sound script pointer of voice 2, high byte",
   "confidence": "high"
  },
  {
   "addr": "0B9B",
   "scope": "abs",
   "name": "playModeFlags",
   "meaning": "bit 7 = solo/film mode: disables VOICE PAUSE and system repairs",
   "confidence": "medium"
  },
  {
   "addr": "0BA5",
   "scope": "abs",
   "name": "commBuildFlag",
   "meaning": "bit 7 = comm build 1 (T35): VOICE PAUSE item absent/ignored",
   "confidence": "medium"
  },
  {
   "addr": "6792",
   "scope": "abs",
   "name": "sndVoiceDuration",
   "meaning": "ticks left before the next script command, per voice (index voice*2; 0 = voice idle). First of eight 6-byte state rows at $6792-$67C1",
   "confidence": "high"
  },
  {
   "addr": "6793",
   "scope": "abs",
   "name": "sndVoiceControl",
   "meaning": "SID control/waveform byte written to $D404+7*voice each tick (index voice*2)",
   "confidence": "high"
  },
  {
   "addr": "6798",
   "scope": "abs",
   "name": "sndLoopCounterA",
   "meaning": "script loop counter A (command 2, tested by loop command $46), index voice*2",
   "confidence": "high"
  },
  {
   "addr": "6799",
   "scope": "abs",
   "name": "sndLoopCounterB",
   "meaning": "script loop counter B (command 3, tested by loop command $47), index voice*2",
   "confidence": "high"
  },
  {
   "addr": "679E",
   "scope": "abs",
   "name": "sndSpareSlot4",
   "meaning": "script slot for command 4 (unused by the shipped scripts), index voice*2",
   "confidence": "medium"
  },
  {
   "addr": "679F",
   "scope": "abs",
   "name": "sndSpareSlot5",
   "meaning": "script slot for command 5 (unused by the shipped scripts), index voice*2",
   "confidence": "medium"
  },
  {
   "addr": "67A4",
   "scope": "abs",
   "name": "sndVoiceFreq",
   "meaning": "16-bit SID frequency per voice ($67A4 lo, $67A5 hi, index voice*2); advanced by sndVoiceFreqDelta every tick",
   "confidence": "high"
  },
  {
   "addr": "67AA",
   "scope": "abs",
   "name": "sndVoicePulse",
   "meaning": "16-bit pulse width per voice ($67AA lo, $67AB hi, index voice*2)",
   "confidence": "high"
  },
  {
   "addr": "67B0",
   "scope": "abs",
   "name": "sndVoiceAdsr",
   "meaning": "attack/decay ($67B0) and sustain/release ($67B1) per voice, index voice*2",
   "confidence": "high"
  },
  {
   "addr": "67B6",
   "scope": "abs",
   "name": "sndVoiceFreqDelta",
   "meaning": "signed 16-bit frequency increment added every tick (slide), index voice*2",
   "confidence": "high"
  },
  {
   "addr": "67BC",
   "scope": "abs",
   "name": "sndVoicePulseDelta",
   "meaning": "signed 16-bit pulse-width increment added every tick, index voice*2",
   "confidence": "high"
  },
  {
   "addr": "67C2",
   "scope": "abs",
   "name": "sndVolume",
   "meaning": "value written to SID_VOLUME every tick; set to $0C by startSound, 0 silences the driver",
   "confidence": "high"
  },
  {
   "addr": "67C3",
   "scope": "abs",
   "name": "sndLastByte",
   "meaning": "last script byte fetched (BIT-tested for bits 7/6)",
   "confidence": "high"
  },
  {
   "addr": "67C4",
   "scope": "abs",
   "name": "sndScratch",
   "meaning": "scratch: loop slot offset in runSoundScript, sound*2 in startSound",
   "confidence": "high"
  },
  {
   "addr": "67C5",
   "scope": "abs",
   "name": "sndInitLock",
   "meaning": "voice loop counter of startSound (3..0); nonzero makes soundPlayerTick return early",
   "confidence": "high"
  },
  {
   "addr": "67C6",
   "scope": "abs",
   "name": "sndCurrentSound",
   "meaning": "id of the sound most recently started",
   "confidence": "high"
  },
  {
   "addr": "67C7",
   "scope": "abs",
   "name": "sndStartVoiceIndex",
   "meaning": "voice number 0-2 being initialised by startSound",
   "confidence": "high"
  },
  {
   "addr": "67C8",
   "scope": "abs",
   "name": "sndVoiceSoundId",
   "meaning": "3 bytes: id of the sound playing on voice 0/1/2, 0 when the voice's script has ended; polled by $1AEC, $4E56, $5A4B and the sound queue ($C8D0); $67CA (voice 2) is checked by sub_C8FA for $14/$16",
   "confidence": "high"
  },
  {
   "addr": "90EF",
   "scope": "abs",
   "name": "textCentreWidth",
   "meaning": "text engine centring width, cleared before printing menu text",
   "confidence": "medium"
  },
  {
   "addr": "90F8",
   "scope": "abs",
   "name": "selectedUnit",
   "meaning": "selected unit index; the menu selector treats $FF as 'repair menu' (blinks the comcen system marker) and $FE as a plain menu with no unit",
   "confidence": "high"
  },
  {
   "addr": "91CE",
   "scope": "abs",
   "name": "menuItemIdList",
   "meaning": "item ids of the current menu in display order (filled by addMenuItem $5FB1, read by the selector to return the id of the chosen row)",
   "confidence": "high"
  },
  {
   "addr": "91D4",
   "scope": "abs",
   "name": "menuItemCount",
   "meaning": "number of items in the current menu",
   "confidence": "high"
  },
  {
   "addr": "91D5",
   "scope": "abs",
   "name": "menuSelectedItem",
   "meaning": "current/selected menu row; $81 while waiting for the voice-pause handshake (bit 7 cleared by voicePauseReconnect)",
   "confidence": "high"
  },
  {
   "addr": "91D6",
   "scope": "abs",
   "name": "menuFirstRow",
   "meaning": "screen row of the first menu item",
   "confidence": "high"
  },
  {
   "addr": "9206",
   "scope": "abs",
   "name": "selectedGroupId",
   "meaning": "group id (unitGroupFlags & $3C) of the selected group",
   "confidence": "medium"
  },
  {
   "addr": "923F",
   "scope": "abs",
   "name": "gameStateFlags923F",
   "meaning": "set to $C0 at game-over/knockout points ($28F9, $524E, $5A03); bit 6 blocks system repairs and fire actions in sub_0ED5",
   "confidence": "low"
  },
  {
   "addr": "9246",
   "scope": "abs",
   "name": "rleMarkerByte",
   "meaning": "escape byte of the RLE stream being unpacked (first byte of the block)",
   "confidence": "high"
  },
  {
   "addr": "9247",
   "scope": "abs",
   "name": "rleFlag9247",
   "meaning": "cleared by unpackRleTo0200 and set by the overlay-B unpacker at $8634; purpose unknown",
   "confidence": "low"
  },
  {
   "addr": "929D",
   "scope": "abs",
   "name": "lastRepositionedGroup",
   "meaning": "group id (unitGroupFlags & $3C) of the unit(s) most recently given a re-position order ($98) at $4C25; $FF at game reset ($78E1)",
   "confidence": "medium"
  },
  {
   "addr": "929E",
   "scope": "abs",
   "name": "pendingRepositionCount",
   "meaning": "number of re-position orders sent for lastRepositionedGroup that the engine has not yet executed (incremented at $4C1F, decremented at $4C65 - a region mis-typed as data)",
   "confidence": "medium"
  },
  {
   "addr": "E03C",
   "scope": "abs",
   "name": "commLineStatus",
   "meaning": "bit 6 = carrier/connected: decides whether VOICE PAUSE sends the $8C request or reconnects directly",
   "confidence": "medium"
  }
 ],
 "dataBlocks": [
  {
   "addr": "6782",
   "length": 5,
   "type": "byteTable",
   "name": "sidVoiceRegOffsetTable",
   "description": "SID register offset per voice, indexed by voice*2: $00, (pad), $07, (pad), $0E"
  },
  {
   "addr": "6787",
   "length": 11,
   "type": "byteTable",
   "name": "sndCommandSlotTable",
   "description": "script command 0-10 -> offset of its state slot from $6792 ($00 duration, $01 control, $06 loop A, $07 loop B, $0C/$0D spare, $12 freq, $18 pulse, $1E ADSR, $24 freq delta, $2A pulse delta)"
  },
  {
   "addr": "6792",
   "length": 48,
   "type": "byteTable",
   "name": "sndVoiceState",
   "description": "work RAM: eight 6-byte rows of per-voice sound state (see variables $6792-$67BD); zeroed per voice by startSound"
  },
  {
   "addr": "67C2",
   "length": 6,
   "type": "byteTable",
   "name": "sndDriverVars",
   "description": "work RAM: sndVolume, sndLastByte, sndScratch, sndInitLock, sndCurrentSound, sndStartVoiceIndex"
  },
  {
   "addr": "67C8",
   "length": 3,
   "type": "byteTable",
   "name": "sndVoiceSoundId",
   "description": "work RAM: sound id currently playing on each of the three voices (0 = free)"
  },
  {
   "addr": "6831",
   "length": 174,
   "type": "wordTable",
   "name": "soundPointerTable",
   "description": "29 sounds x 3 voice script addresses (6 bytes per sound, 0 = voice not used). Sound 0 plays the 60-tick silence script $68DF on all voices (stop all), 26/27/28 silence voice 0/1/2 only; 6, 13, 14, 21 and 25 are multi-voice; 8-11 use voice 1, 15-24 voice 2, the rest voice 0"
  },
  {
   "addr": "68DF",
   "length": 854,
   "type": "byteTable",
   "name": "soundScriptData",
   "description": "bytecode scripts for the sound driver (format described in runSoundScript); $68E3 '01 00 00 00' is the shared end-of-sound target of the '46 E3 68' jumps"
  },
  {
   "addr": "6C35",
   "length": 40,
   "type": "addrTable",
   "name": "unitGlyphPointerTable",
   "description": "20 word pointers to the 32-byte unit glyphs below, indexed by (unitFlags & 3) + D_0799[unitType] - $2C, i.e. unitType*4 + facing"
  },
  {
   "addr": "6C5D",
   "length": 640,
   "type": "charset",
   "name": "unitGlyphShapes",
   "description": "20 multicolour 2x2-character (16x16 pixel) unit glyphs of 32 bytes (TL, TR, BL, BR 8 bytes each), all pixels %11: grunt soldier (0-3), rider (4-7), boomer with barrel (8-11), spy chevron (12-15), comcen dome (16-19); within each type the facings are up, down, left, right"
  },
  {
   "addr": "6EDD",
   "length": 35,
   "type": "unknown",
   "name": "padding6EDD",
   "description": "zero filler to the end of the main program at $6EFF"
  }
 ],
 "misclassified": [
  {
   "addr": "652E",
   "length": 114,
   "actual": "code",
   "evidence": "JSR $652E at $1860 (inside repairTabAction, a region also emitted as .byte); disassembles cleanly as the REPAIR WHICH menu ending in JMP $C06A at $659D, immediately followed by sub_65A0"
  },
  {
   "addr": "6637",
   "length": 331,
   "actual": "code",
   "evidence": "JSR $6728 / JSR $6637 from the raster IRQ at $1289/$128C (also mis-typed); the bytes form a complete SID driver writing $D400-$D418 and referencing the tables at $6782/$6787/$6831; the last instruction is RTS at $6781"
  },
  {
   "addr": "0EE5",
   "length": 5,
   "actual": "code",
   "evidence": "outside this chunk but called from $658B: LDA #$00 / JMP $0EDA"
  },
  {
   "addr": "1283",
   "length": 12,
   "actual": "code",
   "evidence": "outside this chunk: IRQ tail JSR $C8E9 / TXA / BMI / JSR $6728 / JSR $6637 before the RTI at $1298"
  },
  {
   "addr": "4C59",
   "length": 18,
   "actual": "code",
   "evidence": "outside this chunk: LDY $73 / LDA $FB54,Y / AND #$3C / CMP $929D / BNE / DEC $929E / JMP $34D5 - the only decrement of pendingRepositionCount"
  },
  {
   "addr": "C8E9",
   "length": 17,
   "actual": "code",
   "evidence": "outside this chunk: dequeueSound - scans $92E7-$92EA from Y=3 down, returns X = sound id and clears the slot, X negative when empty"
  }
 ],
 "insights": [
  "The chunk spec's line numbers were wrong; $652E-$6EFD is lines 11586-12009 of main_0800.s (the address range was used).",
  "Sound architecture: game code calls sub_C89A (A = sound id 0-28, X/Y preserved) which uses the priority/class table D_C87D and the 4-slot queue D_92E7-D_92EA ($FF = empty; ids >= $1D are rejected, id 0 clears the queue and silences everything). The raster IRQ ($1283) dequeues with $C8E9 into X, starts it with startSound ($6728) and then runs soundPlayerTick ($6637) every frame. sndVoiceSoundId $67C8[voice] tells the game which sound a voice is playing (endGameToMainMenu at $1AEC skips the overlay reload while voice 0 plays 13/14, sub_C8FA checks voice 2 for $14/$16).",
  "Sound script bytecode (one stream per voice, pointers zp_D8-zp_DD): 00 n = wait n ticks (00 00 = end), 01 n = SID control byte, 02 n / 03 n = loop counters A/B, 06 lo hi = frequency, 07 lo hi = pulse width, 08 ad sr = envelope, 09 lo hi = per-tick frequency delta, 0A lo hi = per-tick pulse delta, 46 lo hi / 47 lo hi = jump (unconditional while the counter is 0, else decrement and jump while nonzero, so 02 03 + 46 target loops three times), any byte >= $80 = add zp_5A to the frequency low byte. Per-voice state lives in eight 6-byte rows at $6792-$67C1 indexed by voice*2.",
  "Sound id usage seen at the call sites of sub_C89A: 0 stop all ($0EFE stopAllSound), 1 fire-button click, 2 menu move/typing/connection, 3 error/alert beep (disk messages, aborts), 5 chat RETURN received, 8 own comcen hit, 9 ($3A54/$79F8), $0F/$10/$11 overlay-A screens, $14 and $1C comcen sounds (sub_C8FA), $16 ($3F14 recycler), $17 ($3D2C/$79F3), $18 repair attempt. Sounds 13 and 14 are three-voice rising/falling chords (likely the win/lose fanfares), 25 a three-voice alternating siren.",
  "Menu system used here (selector sub_5FD7/sub_5FDC/sub_5FD1 and addMenuItem sub_5FB1 are in the previous chunk): addMenuItem appends item id X to menuItemIdList $91CE and prints it - ids 0-11 are 16-byte strings at $C9D2 (FORMATION, MOVE TO GOAL, CLOAKING ON, NO CLOAKING, CLEAR TARGET, SET TARGET, MEMBERSHIP, JOIN GROUP, DIRECT UNIT, SELF DESTRCT, SET BLITZ, BLITZ OFF), ids 12-18 are 8-byte strings at $CAB2 ((EXIT), ENERGY, BATTLE, RADAR, DRONE, DIG OUT, DIG IN). The selector returns A = id of the chosen row and Y = D_91D5 = row; when the UI is interrupted (checkUiContinue $C451 = 0, game over, comcen stunned in repair mode) it selects the last row, which every menu here makes (EXIT). selectedUnit $FF selects the repair-menu behaviour (system marker blink), $FE a plain menu. Menus printed by other code (printOptionsList, the formation list) set D_91D4/D_91D6 themselves and skip addMenuItem, so A on return is meaningless and only Y is used.",
  "Voice pause protocol (sub_65A0): with a carrier, command $8C reason 5 is sent, D_91D5 is parked at $81 and the frame service loop runs until the $C000 overlay (sub_C86D -> $C768, triggered by zp_BE bit 7) calls voicePauseReconnect ($1BF7), which hangs up, waits for a new connection and clears bit 7 of D_91D5.",
  "unpackRleTo0200 ($67CB) reveals that the 'unreferenced' high-memory data at $FD70 and $FE24 (survey game_high_FBB8) and five blocks in the $C000 overlay ($CD52, $CDD3, $CE3B, $CECD, $CF3A via sub_CD0A) are RLE-packed 512-byte blocks (first byte = escape marker, marker/count/value runs). Overlay B has a sibling at $8644 that unpacks 10 rows of 160 bytes straight into the bitmap with the same fetchRleByte ($681A).",
  "Unit glyphs: the 2762 survey's range $6C5D-$6E7C is short; the pointer table has 20 entries and the shapes run $6C5D-$6EDC (5 types x 4 facings up/down/left/right, matching unitFlagsTable bits 0-1 and D_0799 = $2C,$30,$34,$38,$3C). The rest of the page to $6EFF is zero."
 ]
}