{ "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." ] }