{
 "chunk": "game_mainProgram0800_646A",
 "unit": "game/mainProgram0800",
 "unitLabels": {
  "game/mainProgram0800": {
   "6793": "sndVoiceCtrlByte",
   "67A4": "sndVoiceFreqLo",
   "67A5": "sndVoiceFreqHi",
   "67AA": "sndVoicePulseLo",
   "67AB": "sndVoicePulseHi",
   "67B0": "sndVoiceAttackDecay",
   "67B1": "sndVoiceSustainRelease",
   "67B6": "sndVoiceFreqStepLo",
   "67B7": "sndVoiceFreqStepHi",
   "67BC": "sndVoicePulseStepLo",
   "67BD": "sndVoicePulseStepHi",
   "67C2": "sndVolume",
   "67C3": "sndScriptByte",
   "67C4": "sndScratch",
   "67C5": "sndInitLock",
   "67C6": "sndCurrentSoundId",
   "67C7": "sndStartVoiceIndex",
   "67CA": "sndVoice2SoundId",
   "6832": "soundPointerTableHi",
   "68DF": "sndScriptSilence",
   "68E3": "sndScriptEnd",
   "68E7": "sndScriptPhoneTroubleV0",
   "68F0": "sndScriptPhoneTroubleV1",
   "6904": "sndScriptButtonClick",
   "6914": "sndScriptErrorBeep",
   "6924": "sndScriptMenuClick",
   "6931": "sndScriptAlertChime",
   "6941": "sndScriptChatKeyClick",
   "6956": "sndScriptMissileBlast",
   "6969": "sndScriptDroneAlert",
   "6980": "sndScriptPhonePromptV0",
   "69C9": "sndScriptPhonePromptV1",
   "6A18": "sndScriptComcenHit",
   "6A30": "sndScriptUnitDestroyed",
   "6A4C": "sndScriptUnusedShot",
   "6A73": "sndScriptBoomerShot",
   "6A83": "sndScriptDroneStrike",
   "6A9A": "sndScriptDamageSweep",
   "6AA7": "sndScriptWinFanfareV0",
   "6AB5": "sndScriptWinFanfareV1",
   "6AC3": "sndScriptWinFanfareV2",
   "6ADC": "sndScriptLossFanfareV0",
   "6AEC": "sndScriptLossFanfareV1",
   "6AFC": "sndScriptLossFanfareV2",
   "6B17": "sndScriptDroneLaunch",
   "6B3B": "sndScriptDroneTurn",
   "6B67": "sndScriptRepairAttempt",
   "6B89": "sndScriptRepairOk",
   "6BA3": "sndScriptRepairFail",
   "6BC7": "sndScriptOwnComcenMove",
   "6BE8": "sndScriptComcenEngine",
   "6BFB": "sndScriptGameOverV0",
   "6C15": "sndScriptGameOverV1",
   "6C1B": "sndScriptGameOverV2",
   "6C5D": "unitGlyphGruntUp",
   "6C7D": "unitGlyphGruntDown",
   "6C9D": "unitGlyphGruntLeft",
   "6CBD": "unitGlyphGruntRight",
   "6CDD": "unitGlyphRiderUp",
   "6CFD": "unitGlyphRiderDown",
   "6D1D": "unitGlyphRiderLeft",
   "6D3D": "unitGlyphRiderRight",
   "6D5D": "unitGlyphBoomerUp",
   "6D7D": "unitGlyphBoomerDown",
   "6D9D": "unitGlyphBoomerLeft",
   "6DBD": "unitGlyphBoomerRight",
   "6DDD": "unitGlyphSpyUp",
   "6DFD": "unitGlyphSpyDown",
   "6E1D": "unitGlyphSpyLeft",
   "6E3D": "unitGlyphSpyRight",
   "6E5D": "unitGlyphComcenUp",
   "6E7D": "unitGlyphComcenDown",
   "6E9D": "unitGlyphComcenLeft",
   "6EBD": "unitGlyphComcenRight"
  }
 },
 "notes": {
  "646A": {
   "unit": "game/mainProgram0800",
   "block": [
    "Click on one of our own units while the group editor is running.  Y = the unit index",
    "(selectedUnit, already known to be alive, ours and not the comcen).  Four cases:",
    "  MEMBERSHIP mode (cursorTargetMode >= 0): outsider -> ADDED / TOO FAR!, member -> REMOVED",
    "  FORMATION  mode (cursorTargetMode <  0): outsider -> NOT IN GROUP, member -> NEW POSITION"
   ],
   "line": "local flag copy of the clicked unit: facing 0-1, group id 2-4, side 5, member 6"
  },
  "64DA": {
   "unit": "game/mainProgram0800",
   "routine": [
    "sendHaltGroupCmd - Queues the 2-byte command $9B with the key of the currently selected group.",
    "Its executor ($549E) stops every member: destination = current position, idle bit set, cloak and",
    "blitz orders cleared.  The group editor halts the group first so the player edits settled units.",
    "In:  selectedGroupKey ($9206) = group id in bits 2-4 plus the side bit $20",
    "Out: cmdParam0 = the group key, 2 bytes appended to the outgoing command ring by queueCmd2",
    "Called from: runGroupEditor at $63BA (formation mode) and $63C0 (membership mode)"
   ],
   "line": "the group being edited"
  },
  "64E4": {
   "unit": "game/mainProgram0800",
   "routine": [
    "printSettingHeaderForLevel - Clears the info panel in the colour that belongs to the current UI",
    "level, moves to row 15 and falls into printSettingWord, which prints SETTING centred plus a",
    "newline.  The caller then prints the object of the order (TARGET, DESTINATION, NEW POSITION).",
    "In:  uiLevel ($9209) 0-2, uiLevelTextColourTable at $5E14 ($09 browsing, $05 unit, $04 group)",
    "Out: textColour = textPadColour = that colour, info panel cleared, cursor on row 15",
    "Called from: the SET TARGET item at $5F56, the NEW POSITION branch at $64A1, setUnitDestination"
   ],
   "line": "0 = just browsing, 1 = one unit selected, 2 = a whole group"
  },
  "64F6": {
   "unit": "game/mainProgram0800",
   "routine": [
    "printSettingWord - Prints the word SETTING ($CB4F) centred in the current text window and ends",
    "the line, leaving the cursor on the next row for the object word.",
    "In:  the text window and colour set up by the caller",
    "Out: SETTING printed, cursor on the following row",
    "Called from: printSettingHeader at $637C, and by falling out of printSettingHeaderForLevel"
   ],
   "line": "'SETTING' ($CB4F), low byte"
  },
  "6500": {
   "unit": "game/mainProgram0800",
   "routine": [
    "runSetDestinationScreen - The SETTING / DESTINATION screen.  Prints the header, selects the plain",
    "move-order cursor and runs the target cursor loop, which issues the move order for the selected",
    "unit or, at uiLevel 2, for every member of the selected group.  Entered as soon as the player",
    "pushes the stick after picking a unit or a group.",
    "In:  selectedUnit ($90F8), uiLevel ($9209), selectedGroupKey ($9206)",
    "Out: cursorTargetMode = 0, a move order queued by runTargetCursorLoop",
    "Called from: the unit branch at $5E62 and the group branch at $6220 of handleOwnUnitClick"
   ],
   "line": "'SETTING' in the colour of the current UI level"
  },
  "650E": {
   "unit": "game/mainProgram0800",
   "routine": [
    "countGroupMembers - Counts the live units that belong to the group whose key is in A.  Used by",
    "the WHICH GROUP: list to print the member count beside each group name.",
    "In:  A = group key (group number * 4 plus the side bit $20)",
    "Out: A = scratch18 = number of members, scratch19 = key | $40, Y = $FF",
    "Called from: the join-group list builder at $617D"
   ],
   "line": "add bit 6, the 'is in a group' flag, to the pattern we look for"
  },
  "6518": {
   "unit": "game/mainProgram0800",
   "block": [
    "Scan every unit slot; count the live ones whose local flags carry exactly this group key."
   ],
   "line": "unit type, bit 7 set = the unit is dead"
  },
  "652E": {
   "unit": "game/mainProgram0800",
   "routine": [
    "runRepairWhichMenu - Draws and runs the 'REPAIR WHICH:' menu of the REPAIR console tab.  It opens",
    "a 7-column text window at column 3 spanning rows 9-19, prints the header, appends the four comcen",
    "systems (ENERGY, BATTLE, RADAR, DRONE) and (EXIT) as menu items, then loops: run the selector, and",
    "for anything but (EXIT) make one repair attempt on the chosen system.  selectedUnit = $FF is the",
    "sentinel that makes the menu highlight blink the matching system marker on the comcen picture.",
    "In:  the comcen state read by attemptSystemRepair; filmPlaybackMode ($0B9B) and gameOverFlags",
    "     ($923F) through isPlayerInputAllowed; menuSelectedItem ($91D5) after each selector pass",
    "Out: text window $39-$3E, menu state $91D4/$91D5/$91D6/$91CE, selectedUnit = $FF; may queue the",
    "     $9A energy-repair command and clear comcenSystemStatus[system]; window erased on exit",
    "Called from: repairTabAction at $1860, which has just erased the four system markers and set",
    "     showOkMarkers = 1 so undamaged systems are shown too"
   ],
   "line": "zero for the text modes and the menu counters below"
  },
  "6568": {
   "unit": "game/mainProgram0800",
   "block": [
    "Append the four comcen systems as menu items 13,14,15,16 = ENERGY, BATTLE, RADAR, DRONE.",
    "Each addMenuItem prints the name (its string ends in CR) so the rows fill downwards."
   ],
   "line": "record the id in menuItemIdList and print its name"
  },
  "657C": {
   "unit": "game/mainProgram0800",
   "block": [
    "Menu loop: pick a system, repair it, remove the highlight and start over until (EXIT)."
   ],
   "line": "$FF = repair-menu mode..."
  },
  "65A0": {
   "unit": "game/mainProgram0800",
   "routine": [
    "runInGameOptionsMenu - Runs the MISC-tab options menu while a battle is in progress.  The caller",
    "has already printed the items (ABORT GAME, VOICE PAUSE when a modem game is running, (EXIT)) and",
    "set menuItemCount / menuFirstRow.  Every index other than 1 is handed back to the caller.  Index 1",
    "is VOICE PAUSE: with a carrier up it tells the opponent with command $8C reason 5 and then waits",
    "until voicePauseReconnect (called from the $C000 overlay at $C768) clears bit 7 of menuSelectedItem;",
    "without a carrier it just hangs up and redials itself.  Afterwards the menu is restarted.",
    "In:  menuItemCount ($91D4) and menuFirstRow ($91D6) set by the caller, filmPlaybackMode ($0B9B),",
    "     isSoloTrainer ($0BA5), the comm module link flags at $E03C",
    "Out: Y = menuSelectedItem (0 = ABORT GAME, last = (EXIT)), selectedUnit = $FE, textColour = $60,",
    "     command $8C/5 possibly queued, menu highlight removed",
    "Called from: miscTabAction at $1A78; tail-jumps to itself at $65E0 after a voice pause"
   ],
   "line": "$FE = plain menu: the highlight does not track a unit or a comcen marker"
  },
  "65D5": {
   "unit": "game/mainProgram0800",
   "block": [
    "Spin here while the players talk on the phone.  updateGameFrame keeps the battle, the",
    "message system and the link running; voicePauseReconnect clears bit 7 when the modems",
    "have connected again."
   ],
   "line": "one game frame (engine, messages, comm poll)"
  },
  "65E7": {
   "unit": "game/mainProgram0800",
   "routine": [
    "waitForGroupRepositionsDone - Before a group screen edits a group, wait until the re-position",
    "orders already sent for that same group have been executed, so the editor works on settled",
    "positions.  If the player gives up meanwhile (checkUiContinue = 0) the routine pops its own return",
    "address so the RTS lands in the caller's caller and the whole group operation is abandoned.",
    "In:  selectedGroupKey ($9206), lastOrderedGroupKey ($929D), pendingGroupOrderCount ($929E)",
    "Out: nothing, but the return may unwind one stack level",
    "Called from: the GROUP screen at $621D and the group editor at $63B7"
   ],
   "line": "the group we are about to work on"
  },
  "65EF": {
   "unit": "game/mainProgram0800",
   "block": [
    "Wait while orders are still outstanding, keeping the real-time battle running."
   ],
   "line": "re-position orders queued but not yet executed"
  },
  "65FF": {
   "unit": "game/mainProgram0800",
   "routine": [
    "runFormationOptionsMenu - The FORMATION sub-menu of the group options.  Prints the FORMATION",
    "OPTIONS heading and the five items (SWEEP LEFT, SWEEP RIGHT, REVERSE, CUSTOM, (EXIT)) as one",
    "multi-line string, then runs the selector.  Items 0-2 are rotations and are sent straight to both",
    "machines as command $A3 leader,mode; 3 (CUSTOM) and 4 ((EXIT)) are returned to the caller.",
    "In:  selectedUnit ($90F8) = the unit the formation is rotated about, menuFirstRow ($91D6) = 15,",
    "     already set by the group menu at $62A7",
    "Out: Y = menuSelectedItem, cmdParam1/cmdParam2 = leader and mode, command $A3 queued, item count 5",
    "Called from: the group options menu at $62FD"
   ],
   "line": "select and clear the 14-column info panel (columns 25-38, rows 12-21)"
  },
  "6637": {
   "unit": "game/mainProgram0800",
   "routine": [
    "soundPlayerTick - The per-frame SID sound driver.  It is skipped while startSound is still",
    "rebuilding the voice tables, writes the master volume and then advances the three voices.  X holds",
    "voice*2 throughout the driver, because every per-voice value lives at a fixed offset plus voice*2",
    "inside the 48-byte block at sndVoiceState.",
    "In:  sndInitLock ($67C5), sndVolume ($67C2), the voice state at $6792-$67C1, the script pointers",
    "     in zero page $D8-$DD",
    "Out: SID registers $D400-$D418, updated voice state, sndVoiceSoundId cleared for finished voices",
    "Called from: the raster IRQ at $128C (which first dequeues a request with $C8E9 and, if there is",
    "     one, calls startSound at $1289)"
   ],
   "line": "non-zero while startSound is half way through filling the voice tables"
  },
  "6651": {
   "unit": "game/mainProgram0800",
   "routine": [
    "updateSoundVoice - Advances one SID voice by one frame.  An idle voice (duration 0) is left alone.",
    "Otherwise the 16-bit frequency and pulse-width slides are added to the current values, the note",
    "duration is counted down and, when it expires, the voice's script is interpreted up to its next",
    "'set duration' command; a script that ends without setting one releases the voice and frees its",
    "sndVoiceSoundId slot.  Finally the whole voice image is written to the SID registers.",
    "In:  X = voice*2 (0, 2 or 4), the voice state rows at $6792-$67C1",
    "Out: SID voice registers at $D400 + voice*7, updated state, X preserved",
    "Called from: soundPlayerTick at $6647 and $664C, and by falling through at $664F"
   ],
   "line": "duration counter of this voice ($6792 + voice*2)"
  },
  "6691": {
   "unit": "game/mainProgram0800",
   "block": [
    "Write the voice image out to the SID.  Y = 0, 7 or $0E, the register offset of this voice."
   ],
   "line": "$00 / $07 / $0E - distance of this voice's registers from $D400"
  },
  "66BF": {
   "unit": "game/mainProgram0800",
   "block": [
    "Script byte $80-$FF: 'jitter the pitch'.  Only bit 7 matters; the value of the byte is ignored.",
    "The current map/scenario random byte is added to the frequency low byte, which is what gives the",
    "shot and drone scripts their rasp.  Note there is no CLC first, so the carry left by the BIT/",
    "fetch sequence can add one extra - harmless for a noise effect.  Reached only by the BMI at $66CD."
   ],
   "line": "byte 0 of the 24-bit scenario seed, used here as a cheap random value"
  },
  "66C7": {
   "unit": "game/mainProgram0800",
   "routine": [
    "runSoundScript - Interprets one voice's script bytecode until it sets a note duration (or ends).",
    "Byte formats:",
    "  $80-$FF  add the scenario random byte to the frequency low byte (handler at $66BF)",
    "  $40-$7F  loop/jump: bits 0-5 select a counter slot ($06 = loop counter A, $07 = counter B),",
    "           followed by a 16-bit target address, low byte first.  A counter of 0 jumps always;",
    "           otherwise it is decremented and the jump is taken while it is still non-zero.",
    "  $00-$0A  store the following byte (two bytes for slots from $12 up) into the state slot named by",
    "           sndCommandSlotTable: 0 duration (and stop here), 1 waveform/gate, 2 loop counter A,",
    "           3 loop counter B, 4/5 spare, 6 frequency, 7 pulse width, 8 AD/SR, 9 frequency slide,",
    "           10 pulse-width slide.  A duration of 0 ends the sound.",
    "In:  X = voice*2, the script pointer in zero page ($D8 + X), sndScriptByte",
    "Out: the voice state rows updated, the script pointer advanced, sndScratch clobbered",
    "Called from: updateSoundVoice at $6681, and re-entered from its own $66EB / $6710 / $6719"
   ],
   "line": "next script byte, also stored in sndScriptByte for the test below"
  },
  "66EE": {
   "unit": "game/mainProgram0800",
   "block": [
    "Loop/jump command $40-$7F.  The two script bytes after it are the target address, low byte",
    "first; they are pushed and only stored into the pointer if the jump is taken."
   ],
   "line": "bits 0-5 = which counter slot ($06 = loop A, $07 = loop B)"
  },
  "671C": {
   "unit": "game/mainProgram0800",
   "routine": [
    "fetchSoundScriptByte - Reads the next byte of a voice's script through the zero-page pointer at",
    "$D8 + voice*2, keeps a copy in sndScriptByte for the BIT test in runSoundScript, and advances the",
    "pointer.",
    "In:  X = voice*2, the 16-bit pointer at zero page $D8/$D9 + X",
    "Out: A = the byte, sndScriptByte = the byte, pointer incremented",
    "Called from: runSoundScript at $66C7, $66D9, $66E4, $66F3 and $66F7"
   ],
   "line": "(zp,X) with X = voice*2 picks this voice's script pointer"
  },
  "6728": {
   "unit": "game/mainProgram0800",
   "routine": [
    "startSound - Starts sound A (0-28) on the voices its table row names.  Each row of",
    "soundPointerTable holds three script addresses, one per voice; a zero entry means this sound does",
    "not use that voice, which is then left playing whatever it had.  For every voice it does use, the",
    "script pointer is loaded, the voice's eight state slots are zeroed, the sound id is recorded and",
    "the duration is set to 2 so the script is first interpreted on the next-but-one driver tick.",
    "In:  A = sound id 0-28, soundPointerTable at $6831",
    "Out: sndVolume, sndCurrentSoundId, sndScratch, sndInitLock, sndStartVoiceIndex, sndVoiceSoundId,",
    "     the zero-page script pointers $D8-$DD and the voice state; X and Y clobbered",
    "Called from: the raster IRQ at $1289 with the id dequeued by $C8E9"
   ],
   "line": "3 = the number of voices, used as the loop counter..."
  },
  "6744": {
   "unit": "game/mainProgram0800",
   "block": [
    "Once per voice: take the script address from the table row, or skip the voice if it is 0."
   ],
   "line": "high byte of this voice's script address (soundPointerTable+1, indexed by X)"
  },
  "6754": {
   "unit": "game/mainProgram0800",
   "block": [
    "Clear this voice's two bytes in each of the eight 6-byte rows of the state block, i.e.",
    "duration, waveform, both loop counters, the spares, frequency, pulse, AD/SR and the two",
    "slides.  Y starts at voice*2 and steps by 6 until it passes the end of the 48-byte block."
   ],
   "line": "the value written into every slot"
  },
  "6782": {
   "unit": "game/mainProgram0800",
   "block": [
    "sidVoiceRegOffsetTable - 5 bytes, indexed by voice*2 (0, 2, 4); the two odd bytes are padding.",
    "Value = distance of that voice's SID registers from $D400: $00, $07, $0E.  Read at $6691."
   ],
   "line": "voice 0 -> $D400, (pad), voice 1 -> $D407, (pad), voice 2 -> $D40E"
  },
  "6787": {
   "unit": "game/mainProgram0800",
   "block": [
    "sndCommandSlotTable - 11 bytes, indexed by script command 0-10.  Value = the offset of that",
    "command's state slot from sndVoiceState ($6792); the driver adds voice*2 to it.  Slots from $12",
    "up hold 16-bit values and take two operand bytes.  Read at $66D5."
   ],
   "line": "0 duration, 1 waveform/gate, 2 loop A, 3 loop B, 4/5 spare, 6 frequency, 7 pulse width"
  },
  "6792": {
   "unit": "game/mainProgram0800",
   "block": [
    "sndVoiceState - 48 bytes of work RAM: eight 6-byte rows, each row holding one parameter for all",
    "three voices at row + voice*2.  The driver indexes everything from here.",
    "  $6792 row 0: +0 note duration in ticks, +1 waveform/gate byte",
    "  $6798 row 1: +0 loop counter A, +1 loop counter B",
    "  $679E row 2: +0/+1 two spare slots (script commands 4 and 5, never used by the shipped scripts)",
    "  $67A4 row 3: 16-bit oscillator frequency",
    "  $67AA row 4: 16-bit pulse width",
    "  $67B0 row 5: +0 attack/decay, +1 sustain/release",
    "  $67B6 row 6: 16-bit frequency slide added every tick",
    "  $67BC row 7: 16-bit pulse-width sweep added every tick",
    "startSound zeroes one voice's two bytes in every row; the whole block is RAM, the $00s below are",
    "only the state the loader happens to leave behind."
   ],
   "line": "note duration of voice 0 (voices 1 and 2 at $6794 and $6796)"
  },
  "6793": {
   "unit": "game/mainProgram0800",
   "block": [
    "Waveform/gate byte written to $D404 + voice*7: $11 triangle, $21 sawtooth, $41 pulse,",
    "$81 noise, each with the gate in bit 0; clearing bit 0 ($00/$20/$80) starts the release."
   ],
   "line": "waveform/gate byte of voice 0 ($6795 and $6797 for voices 1 and 2)"
  },
  "67A4": {
   "unit": "game/mainProgram0800",
   "block": [
    "16-bit oscillator frequency, voice 0 at $67A4/$67A5, voice 1 at $67A6/$67A7, voice 2 at",
    "$67A8/$67A9.  Written to $D400/$D401 + voice*7 every tick."
   ],
   "line": "oscillator frequency of voice 0, low byte"
  },
  "67AA": {
   "unit": "game/mainProgram0800",
   "block": [
    "16-bit pulse width -> $D402/$D403 + voice*7 (only the low 12 bits are used by the SID)."
   ],
   "line": "pulse width of voice 0, low byte"
  },
  "67B0": {
   "unit": "game/mainProgram0800",
   "block": [
    "Envelope: attack/decay -> $D405 + voice*7, sustain/release -> $D406 + voice*7."
   ],
   "line": "attack/decay of voice 0"
  },
  "67B6": {
   "unit": "game/mainProgram0800",
   "block": [
    "16-bit signed frequency slide added to the frequency once per tick (the pitch bends)."
   ],
   "line": "per-tick frequency slide of voice 0, low byte"
  },
  "67BC": {
   "unit": "game/mainProgram0800",
   "block": [
    "16-bit signed pulse-width sweep added to the pulse width once per tick."
   ],
   "line": "per-tick pulse-width sweep of voice 0, low byte"
  },
  "67C2": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound driver work RAM, six separate bytes:",
    "  $67C2 sndVolume          master volume written to $D418 every tick ($0C playing, 0 silent)",
    "  $67C3 sndScriptByte      the script byte just fetched, kept for the BIT test at $66CA",
    "  $67C4 sndScratch         id*2 in startSound, loop slot number in runSoundScript",
    "  $67C5 sndInitLock        non-zero while startSound rebuilds the tables; also its voice counter",
    "  $67C6 sndCurrentSoundId  the id startSound is installing",
    "  $67C7 sndStartVoiceIndex the plain voice number 0-2 startSound is working on"
   ],
   "line": "master volume written to $D418 every tick"
  },
  "67C8": {
   "unit": "game/mainProgram0800",
   "block": [
    "sndVoiceSoundId - the sound id currently playing on each of the three voices, 0 = free.  The",
    "driver clears an entry when that voice's script ends; requestSound in the $C000 overlay ($C87D",
    "priority classes) reads it to decide whether a new sound may take a busy voice.  $67CA (voice 2)",
    "is read on its own at $3F07 and $C909/$C913 to see whether the comcen engine sound is running."
   ],
   "line": "voice 0 and voice 1"
  },
  "67CA": {
   "unit": "game/mainProgram0800",
   "block": [
    "Voice 2 - the drone/comcen engine voice; tested by name elsewhere."
   ],
   "line": "sound id playing on voice 2 (0 = free)"
  },
  "67CB": {
   "unit": "game/mainProgram0800",
   "routine": [
    "unpackRleTo0200 - Run-length decoder that always unpacks into the two pages $0200-$03FF.  The",
    "first byte of the source block is the escape marker; in the stream 'marker, count, value' emits",
    "value count times and any other byte is a literal.  The source pointer is kept as the",
    "self-modified operand of fetchRleByte.",
    "In:  A/Y = address of the packed block",
    "Out: $0200-$03FF filled, rleMarkerByte ($9246) = the marker, rleUnpackFlag ($9247) = 0,",
    "     glyphPtr/glyphPtrHi, scratch1A and scratch1C clobbered, fetchRleByte's operand left past the",
    "     end of the block",
    "Called from: overlay B at $7596 (block at $FD70) and $7FF0 (block at $FE24), and the $C000 overlay",
    "     at $CD10 for the five unit pictures at $CD52/$CDD3/$CE3B/$CECD/$CF3A; each caller then copies",
    "     the unpacked page somewhere else"
   ],
   "line": "self-modify: low byte of the LDX operand at $67D6, which reads the marker"
  },
  "67D6": {
   "unit": "game/mainProgram0800",
   "block": [
    "The operand of this LDX was patched two instructions ago.  The $FFFF the disassembly shows",
    "is simply what the loader left in the file - the instruction is never executed unpatched."
   ],
   "line": "first byte of the block = the escape marker (operand patched at $67CB/$67CE)"
  },
  "67F7": {
   "unit": "game/mainProgram0800",
   "block": [
    "Emit one byte per pass.  X counts down the bytes still owed by the current run; when it",
    "reaches 0 the next stream byte is fetched, which is either a literal (run of 1) or the",
    "escape marker followed by a count and a value."
   ],
   "line": "one byte of the current run consumed"
  },
  "681A": {
   "unit": "game/mainProgram0800",
   "routine": [
    "fetchRleByte - Returns the next byte of the packed stream and advances the pointer.  The pointer",
    "is the absolute operand of the LDA below ($681B/$681C), patched by whoever starts an unpack.",
    "In:  the self-modified operand at $681B/$681C",
    "Out: A = X = the byte, the operand incremented by one",
    "Called from: unpackRleTo0200 at $67FA/$6804/$6809, and from overlay B's bitmap unpacker at",
    "     $8659/$8663/$8668, which sets $681B itself at $8644 and shares rleMarkerByte"
   ],
   "line": "read through the self-modified pointer; the $FFFF shown is only the loader's filler"
  },
  "6831": {
   "unit": "game/mainProgram0800",
   "block": [
    "soundPointerTable - 29 rows of 3 words, 6 bytes per row, indexed by sound id * 6.  Word n is the",
    "script address for SID voice n, or $0000 when the sound leaves that voice alone.  startSound reads",
    "the high byte through soundPointerTableHi (= this label + 1) and the low byte through this label.",
    "Sound 0 points all three voices at sndScriptSilence, so 'play sound 0' stops everything; ids $1A,",
    "$1B and $1C do the same for one voice each.  The names below come from the call sites of each id:",
    "$01 button click, $02 menu/typing click, $03 error beep, $04 alert chime, $05 incoming chat",
    "character, $06 phone prompt, $07 drone alert, $08 comcen hit, $09 unit destroyed, $0A no known",
    "caller, $0B boomer shot, $0C missile blast, $0D win fanfare, $0E loss fanfare, $0F drone launch,",
    "$10 drone turn, $11 drone strike, $12 repair failed, $13 repair succeeded, $14 comcen engine loop,",
    "$15 phone trouble, $16 own comcen moving, $17 damage/radar sweep, $18 attempting repair, $19 game",
    "over / warning chord."
   ],
   "line": "sound $00 = stop all three voices"
  },
  "68DF": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound script data - the 854 bytes $68DF-$6C34 are sound-driver bytecode interpreted by",
    "runSoundScript.  The scripts are packed back to back; the labels through to $6C1B are exactly the",
    "entry points soundPointerTable names, plus the shared end-of-sound tail at $68E3.  Several scripts",
    "jump into the middle of another one ($6ACE, $6B09, $6B24, $6C1E) to share a tail or a loop body,",
    "which is why the pieces below are not independent.",
    "",
    "sndScriptSilence: waveform 0 (gate off) then a 60-tick rest.  Used by sound $00 on all three",
    "voices to stop everything, as the unused third voice of sounds $06 and $15, and by the",
    "single-voice stop sounds $1A, $1B and $1C."
   ]
  },
  "68E3": {
   "unit": "game/mainProgram0800",
   "block": [
    "Waveform 0, duration 0.  A duration of 0 makes updateSoundVoice free the voice, so this is",
    "the common 'end of sound' tail every '46 E3 68' jump in the scripts points at."
   ]
  },
  "68E7": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $15 (PHONE TROUBLE) voice 0: one tone, then an unconditional jump into the voice 1",
    "script at $68F6 so both voices run the same alternating body."
   ]
  },
  "68F0": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $15 voice 1: two-tone alarm looping for ever at $68F9 (only stopped by sound $00)."
   ]
  },
  "6904": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $01, voice 0: the fire-button click played by checkFirePressed."
   ]
  },
  "6914": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $03, voice 0: the error beep (TOO FAR!, rejected key, disk failure)."
   ]
  },
  "6924": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $02, voice 0: the short click when a menu selection moves or a chat key is taken."
   ]
  },
  "6931": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $04, voice 0: the chime that goes with the alert messages (SPOTTED..., STUNNED...)."
   ]
  },
  "6941": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $05, voice 0: the click for each character received from the opponent's keyboard."
   ]
  },
  "6956": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0C, voice 0: the comcen missile explosion (a falling sawtooth, freq slide $0095)."
   ]
  },
  "6969": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $07, voice 0: two-note warning that the opponent has launched a drone."
   ]
  },
  "6980": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $06 voice 0: the two-voice attention chord behind the 'PICK UP PHONE' / 'HANG UP",
    "PHONE' messages (message ids $17 and $18 in messageSoundTable)."
   ]
  },
  "69C9": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $06 voice 1: the second part of the same chord."
   ]
  },
  "6A18": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $08, voice 1: noise burst played when a comcen is hit."
   ]
  },
  "6A30": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $09, voice 1: the noise-into-tone whoosh when a unit is destroyed."
   ]
  },
  "6A4C": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0A, voice 1.  No call site for id $0A was found anywhere in the game, so this",
    "script looks like a leftover shot effect; it is the only script that uses the bit-7",
    "'jitter' command."
   ]
  },
  "6A73": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0B, voice 1: the boomer's shot (noise with a downward slide)."
   ]
  },
  "6A83": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $11, voice 2: the drone detonation / strike flash."
   ]
  },
  "6A9A": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $17, voice 2: the tone under the expanding damage/radar frames ($3D2E, $79F5)."
   ]
  },
  "6AA7": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0D voice 0: the winning chord (WE WON ON POINTS / BY A KNOCKOUT).  Voices 0 and 1",
    "set their own pitch and then jump into the shared tail at $6ACE inside the voice 2 script."
   ]
  },
  "6AB5": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0D voice 1: same body, different pitch; also jumps to the tail at $6ACE."
   ]
  },
  "6AC3": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0D voice 2: the third note and, from $6ACE on, the shared decay all three voices run."
   ]
  },
  "6ADC": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0E voice 0: the losing chord (WE LOST ON POINTS / WE WERE KNOCKED OUT).  Voices 0",
    "and 1 jump into the shared tail at $6B09 inside the voice 2 script."
   ]
  },
  "6AEC": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0E voice 1: same body one interval down; also jumps to $6B09."
   ]
  },
  "6AFC": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0E voice 2: the third note plus the shared falling tail from $6B09."
   ]
  },
  "6B17": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $0F, voice 2: the drone launch, which then settles into the engine loop at $6B24."
   ]
  },
  "6B3B": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $10, voice 2: the pitch wobble as the drone turns one step, ending by jumping back",
    "into the engine loop of sndScriptDroneLaunch at $6B24."
   ]
  },
  "6B67": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $18, voice 2: the repeating warble under 'ATTEMPTING REPAIR...'."
   ]
  },
  "6B89": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $13, voice 2: the rising sweep that reports a successful repair."
   ]
  },
  "6BA3": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $12, voice 2: the two-counter stutter that reports a failed repair."
   ]
  },
  "6BC7": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $16, voice 2: played while the player's own comcen is moving in a live game."
   ]
  },
  "6BE8": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $14, voice 2: the comcen engine loop, kept running while the comcen is moving and",
    "stopped again with sound $1C."
   ]
  },
  "6BFB": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $19 voice 0: the alarm chord used for GAME OVER, TIMEOUT, RESUMED and the",
    "LAST QTR WARNING."
   ]
  },
  "6C15": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $19 voice 1: sets its own pitch and jumps into the middle of the voice 2 script",
    "at $6C1E, so voices 1 and 2 share one body."
   ]
  },
  "6C1B": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sound $19 voice 2: the body both it and voice 1 run from $6C1E."
   ]
  },
  "6C35": {
   "unit": "game/mainProgram0800",
   "block": [
    "unitGlyphPointerTable - 20 words, one per unit picture, indexed by unitType*4 + facing.  The",
    "viewport tile builder computes the index at $289A-$28A7 as (unitFlagsTable[unit] & 3) +",
    "unitGlyphBaseTable[type] - $2C, where unitGlyphBaseTable ($0799) holds $2C,$30,$34,$38,$3C for",
    "GRUNT, RIDER, BOOMER, SPY and COMCEN, so the subtraction turns it into type*4 + facing.  It then",
    "copies the 32 bytes at the pointer into the cell buffer at $28B5."
   ],
   "line": "GRUNT facing 0"
  },
  "6C5D": {
   "unit": "game/mainProgram0800",
   "block": [
    "Unit glyph shapes - the 640 bytes $6C5D-$6EDC are 20 unit pictures of 32 bytes, one for each unit",
    "type and facing, in the order unitGlyphPointerTable indexes them (type*4 + facing).",
    "A picture is one map cell of the 7x5 tactical view: 2x2 character cells = 16x16 pixels, stored as",
    "top-left, top-right, bottom-left and bottom-right, 8 bytes each, which is why the bit art below",
    "shows each picture as four separate 8x8 blocks rather than one square.",
    "The bitmap is multicolour, so a pixel is a pair of bits and every pixel drawn here is %11, whose",
    "colour comes from colour RAM - that is how one shape serves both sides in their own colour.",
    "Facings 0 and 1 are a vertical mirror pair and 2 and 3 a horizontal pair; the survey reads them as",
    "up, down, left, right.",
    "",
    "GRUNT, facing 0 (up) - glyph index 0.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6C7D": {
   "unit": "game/mainProgram0800",
   "block": [
    "GRUNT, facing 1 (down) - glyph index 1.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6C9D": {
   "unit": "game/mainProgram0800",
   "block": [
    "GRUNT, facing 2 (left) - glyph index 2.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6CBD": {
   "unit": "game/mainProgram0800",
   "block": [
    "GRUNT, facing 3 (right) - glyph index 3.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6CDD": {
   "unit": "game/mainProgram0800",
   "block": [
    "RIDER, facing 0 (up) - glyph index 4.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6CFD": {
   "unit": "game/mainProgram0800",
   "block": [
    "RIDER, facing 1 (down) - glyph index 5.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6D1D": {
   "unit": "game/mainProgram0800",
   "block": [
    "RIDER, facing 2 (left) - glyph index 6.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6D3D": {
   "unit": "game/mainProgram0800",
   "block": [
    "RIDER, facing 3 (right) - glyph index 7.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6D5D": {
   "unit": "game/mainProgram0800",
   "block": [
    "BOOMER, facing 0 (up) - glyph index 8.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6D7D": {
   "unit": "game/mainProgram0800",
   "block": [
    "BOOMER, facing 1 (down) - glyph index 9.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6D9D": {
   "unit": "game/mainProgram0800",
   "block": [
    "BOOMER, facing 2 (left) - glyph index 10.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6DBD": {
   "unit": "game/mainProgram0800",
   "block": [
    "BOOMER, facing 3 (right) - glyph index 11.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6DDD": {
   "unit": "game/mainProgram0800",
   "block": [
    "SPY, facing 0 (up) - glyph index 12.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6DFD": {
   "unit": "game/mainProgram0800",
   "block": [
    "SPY, facing 1 (down) - glyph index 13.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6E1D": {
   "unit": "game/mainProgram0800",
   "block": [
    "SPY, facing 2 (left) - glyph index 14.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6E3D": {
   "unit": "game/mainProgram0800",
   "block": [
    "SPY, facing 3 (right) - glyph index 15.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6E5D": {
   "unit": "game/mainProgram0800",
   "block": [
    "COMCEN, facing 0 (up) - glyph index 16.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6E7D": {
   "unit": "game/mainProgram0800",
   "block": [
    "COMCEN, facing 1 (down) - glyph index 17.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6E9D": {
   "unit": "game/mainProgram0800",
   "block": [
    "COMCEN, facing 2 (left) - glyph index 18.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6EBD": {
   "unit": "game/mainProgram0800",
   "block": [
    "COMCEN, facing 3 (right) - glyph index 19.  32 bytes: TL, TR, BL, BR."
   ]
  },
  "6EDD": {
   "unit": "game/mainProgram0800",
   "block": [
    "padding6EDD - 35 zero bytes filling out the last sector of the main program to $6EFF.  Nothing",
    "reads them; the first byte the game does use above this is the overlay window at $6F00."
   ],
   "line": "unused filler to the end of the $0800-$6EFF image"
  },
  "646D": {
   "unit": "game/mainProgram0800",
   "line": "keep the group key = group id (bits 2-4) plus the side bit ($20)"
  },
  "646F": {
   "unit": "game/mainProgram0800",
   "line": "does it already carry the key of the group being edited?"
  },
  "6472": {
   "unit": "game/mainProgram0800",
   "line": "yes - go on to test the real membership bit"
  },
  "6474": {
   "unit": "game/mainProgram0800",
   "line": "editor mode: >= 0 = MEMBERSHIP, < 0 = custom FORMATION"
  },
  "6476": {
   "unit": "game/mainProgram0800",
   "line": "MEMBERSHIP mode: try to enlist this outsider"
  },
  "6478": {
   "unit": "game/mainProgram0800",
   "line": "sound 3 = the error beep"
  },
  "647A": {
   "unit": "game/mainProgram0800",
   "line": "FORMATION mode may only re-position units that are already members"
  },
  "647D": {
   "unit": "game/mainProgram0800",
   "line": "'NOT IN GROUP' ($CB8D), low byte"
  },
  "647F": {
   "unit": "game/mainProgram0800",
   "line": "...high byte"
  },
  "6481": {
   "unit": "game/mainProgram0800",
   "line": "always taken (A = $8D) - go and print the refusal"
  },
  "6483": {
   "unit": "game/mainProgram0800",
   "line": "membership add: C=1 when the unit is more than 8 cells from the group (TOO FAR! already shown)"
  },
  "6486": {
   "unit": "game/mainProgram0800",
   "line": "accepted -> report ADDED"
  },
  "6488": {
   "unit": "game/mainProgram0800",
   "line": "60 frames"
  },
  "648A": {
   "unit": "game/mainProgram0800",
   "line": "let the real-time battle run on so TOO FAR! stays readable"
  },
  "648D": {
   "unit": "game/mainProgram0800",
   "line": "straight back to the cursor loop; the panel already holds the message"
  },
  "6490": {
   "unit": "game/mainProgram0800",
   "line": "'ADDED' ($CB3B), low byte"
  },
  "6492": {
   "unit": "game/mainProgram0800",
   "line": "...high byte"
  },
  "6494": {
   "unit": "game/mainProgram0800",
   "line": "always taken (A = $3B)"
  },
  "6496": {
   "unit": "game/mainProgram0800",
   "line": "the key matches - now check the 'is in a group' bit itself"
  },
  "6499": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 of the local flags"
  },
  "649B": {
   "unit": "game/mainProgram0800",
   "line": "key matches but the member bit is clear (stale key) - treat it as an outsider"
  },
  "649D": {
   "unit": "game/mainProgram0800",
   "line": "editor mode again"
  },
  "649F": {
   "unit": "game/mainProgram0800",
   "line": "MEMBERSHIP mode: this member is being removed"
  },
  "64A1": {
   "unit": "game/mainProgram0800",
   "line": "FORMATION mode: 'SETTING' header in the info panel, in the uiLevel colour"
  },
  "64A4": {
   "unit": "game/mainProgram0800",
   "line": "'NEW POSITION' ($CBA6), low byte"
  },
  "64A6": {
   "unit": "game/mainProgram0800",
   "line": "...high byte"
  },
  "64A8": {
   "unit": "game/mainProgram0800",
   "line": "centred under SETTING"
  },
  "64AB": {
   "unit": "game/mainProgram0800",
   "line": "run the free cell pick and queue command $98 unit,col,row"
  },
  "64AE": {
   "unit": "game/mainProgram0800",
   "line": "skip the ADDED/REMOVED print - orderGroupNewPosition has drawn its own feedback"
  },
  "64B1": {
   "unit": "game/mainProgram0800",
   "line": "clear the local member bit and queue command $89 unit"
  },
  "64B4": {
   "unit": "game/mainProgram0800",
   "line": "'REMOVED' ($CB40), low byte"
  },
  "64B6": {
   "unit": "game/mainProgram0800",
   "line": "...high byte"
  },
  "64B8": {
   "unit": "game/mainProgram0800",
   "line": "print ADDED / REMOVED / NOT IN GROUP centred in the info panel"
  },
  "64BB": {
   "unit": "game/mainProgram0800",
   "line": "redraw the 7x5 tactical view so the member highlight is current"
  },
  "64BE": {
   "unit": "game/mainProgram0800",
   "line": "may the editor keep running?  0 = game over, screen change or SPACE"
  },
  "64C1": {
   "unit": "game/mainProgram0800",
   "line": "no - tear the editor down"
  },
  "64C3": {
   "unit": "game/mainProgram0800",
   "line": "60 frames"
  },
  "64C5": {
   "unit": "game/mainProgram0800",
   "line": "hold the message, but return early on a fresh stick move or button press"
  },
  "64C8": {
   "unit": "game/mainProgram0800",
   "line": "the group being edited..."
  },
  "64CB": {
   "unit": "game/mainProgram0800",
   "line": "...is highlighted again (NEW POSITION had highlighted a single unit)"
  },
  "64CE": {
   "unit": "game/mainProgram0800",
   "line": "redraw the viewport with the group highlight back"
  },
  "64D1": {
   "unit": "game/mainProgram0800",
   "line": "back to the top of the group-editor cursor loop at $63C8"
  },
  "64D4": {
   "unit": "game/mainProgram0800",
   "line": "highlightedGroupKey = $FF and redraw - no more group highlight"
  },
  "64D7": {
   "unit": "game/mainProgram0800",
   "line": "uiLevel = 0, info-panel unit = $FF, return to the battlefield loop"
  },
  "64DD": {
   "unit": "game/mainProgram0800",
   "line": "becomes the single argument byte of the command"
  },
  "64DF": {
   "unit": "game/mainProgram0800",
   "line": "command $9B = halt every member of a group"
  },
  "64E1": {
   "unit": "game/mainProgram0800",
   "line": "queue code + argument and return through queueCmd2"
  },
  "64E7": {
   "unit": "game/mainProgram0800",
   "line": "$09 / $05 / $04 - the info-panel colour byte for that level"
  },
  "64EA": {
   "unit": "game/mainProgram0800",
   "line": "colour of every character cell printed from here on"
  },
  "64EC": {
   "unit": "game/mainProgram0800",
   "line": "...and of the blanks that pad a centred string"
  },
  "64EE": {
   "unit": "game/mainProgram0800",
   "line": "select and clear the info panel (columns 25-38, rows 12-21)"
  },
  "64F1": {
   "unit": "game/mainProgram0800",
   "line": "row 15, three rows below the top of the panel"
  },
  "64F3": {
   "unit": "game/mainProgram0800",
   "line": "move the cursor there without touching the column"
  },
  "64F8": {
   "unit": "game/mainProgram0800",
   "line": "...high byte"
  },
  "64FA": {
   "unit": "game/mainProgram0800",
   "line": "centred across the 14-column info panel"
  },
  "64FD": {
   "unit": "game/mainProgram0800",
   "line": "end the line and return through printNewline"
  },
  "6503": {
   "unit": "game/mainProgram0800",
   "line": "the string right after SETTING is 'DESTINATION' ($CB56) - print it centred too"
  },
  "6506": {
   "unit": "game/mainProgram0800",
   "line": "0 = plain move cursor: no range limit, the pick becomes a move order"
  },
  "6508": {
   "unit": "game/mainProgram0800",
   "line": "cursorTargetMode selects the behaviour of runTargetCursorLoop"
  },
  "650A": {
   "unit": "game/mainProgram0800",
   "line": "drive the cursor until the player commits or aborts"
  },
  "650D": {
   "unit": "game/mainProgram0800",
   "line": "back to the caller (a JMP would have done)"
  },
  "6510": {
   "unit": "game/mainProgram0800",
   "line": "keep the wanted flag pattern here for the compare inside the loop"
  },
  "6512": {
   "unit": "game/mainProgram0800",
   "line": "walk all 100 unit slots, 99 down to 0"
  },
  "6514": {
   "unit": "game/mainProgram0800",
   "line": "member count starts at zero"
  },
  "651B": {
   "unit": "game/mainProgram0800",
   "line": "dead units are not members"
  },
  "651D": {
   "unit": "game/mainProgram0800",
   "line": "local flag copy"
  },
  "6520": {
   "unit": "game/mainProgram0800",
   "line": "group id (bits 2-4), side (bit 5) and member bit (bit 6)"
  },
  "6522": {
   "unit": "game/mainProgram0800",
   "line": "same group and actually a member?"
  },
  "6524": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "6526": {
   "unit": "game/mainProgram0800",
   "line": "yes - one more member"
  },
  "6528": {
   "unit": "game/mainProgram0800",
   "line": "next unit slot"
  },
  "6529": {
   "unit": "game/mainProgram0800",
   "line": "until index $FF wraps the sign bit"
  },
  "652B": {
   "unit": "game/mainProgram0800",
   "line": "return the count in A as well"
  },
  "6530": {
   "unit": "game/mainProgram0800",
   "line": "$43 = 0: strings are ASCII, control codes (CR) honoured"
  },
  "6532": {
   "unit": "game/mainProgram0800",
   "line": "$90EF = 0: no centring field, text starts at the left edge"
  },
  "6535": {
   "unit": "game/mainProgram0800",
   "line": "$3E = 0: glyph 0 is the blank used by the window clear"
  },
  "6537": {
   "unit": "game/mainProgram0800",
   "line": "the item list starts out empty"
  },
  "653A": {
   "unit": "game/mainProgram0800",
   "line": "highlight will start on the first item"
  },
  "653D": {
   "unit": "game/mainProgram0800",
   "line": "row 9, just under the top of the left map panel"
  },
  "653F": {
   "unit": "game/mainProgram0800",
   "line": "first row of the menu window"
  },
  "6542": {
   "unit": "game/mainProgram0800",
   "line": "the window is 10 rows tall"
  },
  "6544": {
   "unit": "game/mainProgram0800",
   "line": "row 19 = row limit of the window"
  },
  "6546": {
   "unit": "game/mainProgram0800",
   "line": "column 3, inside the 22-column map panel"
  },
  "654A": {
   "unit": "game/mainProgram0800",
   "line": "7 characters wide - enough for 'REPAIR' and ' ENERGY'"
  },
  "654C": {
   "unit": "game/mainProgram0800",
   "line": "wrap point and default centring width"
  },
  "654E": {
   "unit": "game/mainProgram0800",
   "line": "video-matrix colour byte $61: %01 pixel pairs blue (6), %10 pairs white (1)"
  },
  "6552": {
   "unit": "game/mainProgram0800",
   "line": "paint the whole window blank and home the cursor to row 9"
  },
  "6555": {
   "unit": "game/mainProgram0800",
   "line": "leave row 9 empty; the text starts on row 10"
  },
  "6558": {
   "unit": "game/mainProgram0800",
   "line": "'REPAIR' CR 'WHICH:' CR CR ($CBD6), low byte"
  },
  "655A": {
   "unit": "game/mainProgram0800",
   "line": "...high byte"
  },
  "655C": {
   "unit": "game/mainProgram0800",
   "line": "two header rows plus a blank one"
  },
  "655F": {
   "unit": "game/mainProgram0800",
   "line": "the trailing CRs left the cursor on row 13"
  },
  "6561": {
   "unit": "game/mainProgram0800",
   "line": "row 13 is the first row the highlight bar may sit on"
  },
  "6564": {
   "unit": "game/mainProgram0800",
   "line": "menu item id $0D = ' ENERGY' (ids 12-16 index the strings at $CAB2)"
  },
  "6566": {
   "unit": "game/mainProgram0800",
   "line": "id of the item currently being added"
  },
  "656B": {
   "unit": "game/mainProgram0800",
   "line": "next system"
  },
  "656D": {
   "unit": "game/mainProgram0800",
   "line": "addMenuItem takes the id in X"
  },
  "656F": {
   "unit": "game/mainProgram0800",
   "line": "$11 = one past ' DRONE', so ids $0D-$10 are added"
  },
  "6571": {
   "unit": "game/mainProgram0800",
   "line": "keep going while there are systems left"
  },
  "6573": {
   "unit": "game/mainProgram0800",
   "line": "item id $0C = ' (EXIT)', always the last entry"
  },
  "6575": {
   "unit": "game/mainProgram0800",
   "line": "menu now holds 5 items"
  },
  "6578": {
   "unit": "game/mainProgram0800",
   "line": "colour byte $60 for the rows the highlight will run over"
  },
  "657E": {
   "unit": "game/mainProgram0800",
   "line": "...so the blinking highlight also blinks the comcen system marker"
  },
  "6581": {
   "unit": "game/mainProgram0800",
   "line": "wait for the player to choose (returns the index in Y and in menuSelectedItem)"
  },
  "6584": {
   "unit": "game/mainProgram0800",
   "line": "0 ENERGY, 1 BATTLE, 2 RADAR, 3 DRONE, 4 (EXIT)"
  },
  "6587": {
   "unit": "game/mainProgram0800",
   "line": "the (EXIT) row"
  },
  "6589": {
   "unit": "game/mainProgram0800",
   "line": "leave the menu"
  },
  "658B": {
   "unit": "game/mainProgram0800",
   "line": "Z = 1 only when no film is playing and the game is not already over"
  },
  "658E": {
   "unit": "game/mainProgram0800",
   "line": "repairs are locked out - just re-run the menu"
  },
  "6590": {
   "unit": "game/mainProgram0800",
   "line": "one repair roll on the system in menuSelectedItem (1/2 for energy, 1/4 otherwise)"
  },
  "6593": {
   "unit": "game/mainProgram0800",
   "line": "un-highlight the row before the selector redraws it"
  },
  "6596": {
   "unit": "game/mainProgram0800",
   "line": "back into the menu"
  },
  "6599": {
   "unit": "game/mainProgram0800",
   "line": "colour byte $66: both nibbles blue, i.e. wipe the window out"
  },
  "659D": {
   "unit": "game/mainProgram0800",
   "line": "erase the menu window and return to repairTabAction"
  },
  "65A5": {
   "unit": "game/mainProgram0800",
   "line": "colour byte $60 for the menu rows (%01 blue, %10 black)"
  },
  "65A9": {
   "unit": "game/mainProgram0800",
   "line": "run the selector; returns the chosen index in Y"
  },
  "65AC": {
   "unit": "game/mainProgram0800",
   "line": "index 1 = VOICE PAUSE"
  },
  "65AE": {
   "unit": "game/mainProgram0800",
   "line": "index 0 = ABORT GAME - let miscTabAction deal with it"
  },
  "65B0": {
   "unit": "game/mainProgram0800",
   "line": "index 2 = (EXIT) - likewise"
  },
  "65B2": {
   "unit": "game/mainProgram0800",
   "line": "film record/playback flag"
  },
  "65B5": {
   "unit": "game/mainProgram0800",
   "line": "a film has no phone call to pause"
  },
  "65B7": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 set = the solo trainer is the opponent"
  },
  "65BA": {
   "unit": "game/mainProgram0800",
   "line": "the trainer has no phone call either"
  },
  "65BC": {
   "unit": "game/mainProgram0800",
   "line": "comm module link flags; bit 6 = carrier present"
  },
  "65BF": {
   "unit": "game/mainProgram0800",
   "line": "carrier up - warn the opponent before hanging up"
  },
  "65C1": {
   "unit": "game/mainProgram0800",
   "line": "no carrier: hang up and re-open the link straight away"
  },
  "65C7": {
   "unit": "game/mainProgram0800",
   "line": "reason 5 of command $8C = 'hanging up for a voice call'"
  },
  "65C9": {
   "unit": "game/mainProgram0800",
   "line": "the single argument byte"
  },
  "65CB": {
   "unit": "game/mainProgram0800",
   "line": "command $8C = session control"
  },
  "65CD": {
   "unit": "game/mainProgram0800",
   "line": "queue the two bytes for the opponent"
  },
  "65D0": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = 'waiting for the call to come back'"
  },
  "65D2": {
   "unit": "game/mainProgram0800",
   "line": "menuSelectedItem doubles as the wait flag"
  },
  "65DB": {
   "unit": "game/mainProgram0800",
   "line": "still waiting for the reconnect"
  },
  "65DD": {
   "unit": "game/mainProgram0800",
   "line": "take the highlight off the VOICE PAUSE row"
  },
  "65E0": {
   "unit": "game/mainProgram0800",
   "line": "and put the whole options menu up again"
  },
  "65E3": {
   "unit": "game/mainProgram0800",
   "line": "the caller redraws, so drop the highlight first"
  },
  "65E6": {
   "unit": "game/mainProgram0800",
   "line": "Y still holds the chosen index"
  },
  "65EA": {
   "unit": "game/mainProgram0800",
   "line": "the group the last batch of re-position orders was for"
  },
  "65ED": {
   "unit": "game/mainProgram0800",
   "line": "a different group - nothing of ours is in flight"
  },
  "65F2": {
   "unit": "game/mainProgram0800",
   "line": "all settled - carry on"
  },
  "65F4": {
   "unit": "game/mainProgram0800",
   "line": "run one frame of the battle and the link"
  },
  "65F7": {
   "unit": "game/mainProgram0800",
   "line": "0 = game over, screen change or SPACE"
  },
  "65FA": {
   "unit": "game/mainProgram0800",
   "line": "keep waiting"
  },
  "65FC": {
   "unit": "game/mainProgram0800",
   "line": "abandon: throw away the low byte of our return address..."
  },
  "65FD": {
   "unit": "game/mainProgram0800",
   "line": "...and the high byte, so the RTS below returns to the caller's caller"
  },
  "6602": {
   "unit": "game/mainProgram0800",
   "line": "'FORMATION' ($C9D2) centred on row 12"
  },
  "6605": {
   "unit": "game/mainProgram0800",
   "line": "'   OPTIONS' CR CR ($CB13), low byte"
  },
  "6607": {
   "unit": "game/mainProgram0800",
   "line": "...high byte"
  },
  "6609": {
   "unit": "game/mainProgram0800",
   "line": "heading plus a blank row, leaving the cursor on row 15"
  },
  "660C": {
   "unit": "game/mainProgram0800",
   "line": "the five item lines at $CA8C, separated by CR"
  },
  "660E": {
   "unit": "game/mainProgram0800",
   "line": "...high byte"
  },
  "6610": {
   "unit": "game/mainProgram0800",
   "line": "SWEEP LEFT / SWEEP RIGHT / REVERSE / CUSTOM / (EXIT) on rows 15-19"
  },
  "6615": {
   "unit": "game/mainProgram0800",
   "line": "start the highlight on SWEEP LEFT"
  },
  "6618": {
   "unit": "game/mainProgram0800",
   "line": "five rows to choose from"
  },
  "661D": {
   "unit": "game/mainProgram0800",
   "line": "wait for the choice"
  },
  "6620": {
   "unit": "game/mainProgram0800",
   "line": "0 SWEEP LEFT, 1 SWEEP RIGHT, 2 REVERSE, 3 CUSTOM, 4 (EXIT)"
  },
  "6625": {
   "unit": "game/mainProgram0800",
   "line": "CUSTOM and (EXIT) are handled by the caller"
  },
  "6627": {
   "unit": "game/mainProgram0800",
   "line": "third command byte = rotation mode 0-2"
  },
  "6629": {
   "unit": "game/mainProgram0800",
   "line": "the group leader / clicked unit"
  },
  "662C": {
   "unit": "game/mainProgram0800",
   "line": "second command byte"
  },
  "662E": {
   "unit": "game/mainProgram0800",
   "line": "command $A3 = rotate the leader's formation"
  },
  "6630": {
   "unit": "game/mainProgram0800",
   "line": "queue code + two arguments"
  },
  "6633": {
   "unit": "game/mainProgram0800",
   "line": "hand the choice back in Y"
  },
  "663A": {
   "unit": "game/mainProgram0800",
   "line": "safe to run"
  },
  "663C": {
   "unit": "game/mainProgram0800",
   "line": "skip this tick rather than play a half-built voice"
  },
  "663D": {
   "unit": "game/mainProgram0800",
   "line": "$0C while anything is sounding, 0 when the driver has been silenced"
  },
  "6640": {
   "unit": "game/mainProgram0800",
   "line": "$D418 bits 0-3 = master volume (bits 4-6 are the filter mode, left at 0)"
  },
  "6643": {
   "unit": "game/mainProgram0800",
   "line": "volume 0 - nothing can be heard, so do no work"
  },
  "6645": {
   "unit": "game/mainProgram0800",
   "line": "X = voice*2: SID voice 1"
  },
  "664A": {
   "unit": "game/mainProgram0800",
   "line": "SID voice 2"
  },
  "664F": {
   "unit": "game/mainProgram0800",
   "line": "SID voice 3 - fall through into updateSoundVoice, whose RTS returns for us"
  },
  "6654": {
   "unit": "game/mainProgram0800",
   "line": "0 = the voice is idle, nothing to do"
  },
  "6657": {
   "unit": "game/mainProgram0800",
   "line": "per-tick frequency slide, low byte"
  },
  "665D": {
   "unit": "game/mainProgram0800",
   "line": "new oscillator frequency, low byte"
  },
  "6660": {
   "unit": "game/mainProgram0800",
   "line": "slide high byte, with the carry from the low half"
  },
  "6666": {
   "unit": "game/mainProgram0800",
   "line": "new oscillator frequency, high byte"
  },
  "666A": {
   "unit": "game/mainProgram0800",
   "line": "per-tick pulse-width sweep, low byte"
  },
  "6670": {
   "unit": "game/mainProgram0800",
   "line": "new pulse width, low byte"
  },
  "6673": {
   "unit": "game/mainProgram0800",
   "line": "sweep high byte plus carry"
  },
  "6679": {
   "unit": "game/mainProgram0800",
   "line": "new pulse width, high byte"
  },
  "667C": {
   "unit": "game/mainProgram0800",
   "line": "one tick off the remaining note duration"
  },
  "667F": {
   "unit": "game/mainProgram0800",
   "line": "note still running - just refresh the SID registers"
  },
  "6681": {
   "unit": "game/mainProgram0800",
   "line": "duration expired: interpret the script up to its next duration command"
  },
  "6684": {
   "unit": "game/mainProgram0800",
   "line": "did the script set a new duration?"
  },
  "6687": {
   "unit": "game/mainProgram0800",
   "line": "yes - the voice carries on"
  },
  "6689": {
   "unit": "game/mainProgram0800",
   "line": "no, the sound is over: turn voice*2..."
  },
  "668A": {
   "unit": "game/mainProgram0800",
   "line": "...back into a plain voice number 0-2"
  },
  "668E": {
   "unit": "game/mainProgram0800",
   "line": "mark the voice free so requestSound may hand it to another sound"
  },
  "6697": {
   "unit": "game/mainProgram0800",
   "line": "$D400 + voice*7 = frequency low"
  },
  "669D": {
   "unit": "game/mainProgram0800",
   "line": "$D401 + voice*7 = frequency high"
  },
  "66A3": {
   "unit": "game/mainProgram0800",
   "line": "$D402 + voice*7 = pulse width low"
  },
  "66A9": {
   "unit": "game/mainProgram0800",
   "line": "$D403 + voice*7 = pulse width high (only bits 0-3 of it matter)"
  },
  "66AF": {
   "unit": "game/mainProgram0800",
   "line": "$D405 + voice*7 = attack (high nibble) and decay (low nibble)"
  },
  "66B5": {
   "unit": "game/mainProgram0800",
   "line": "$D406 + voice*7 = sustain level and release rate"
  },
  "66B8": {
   "unit": "game/mainProgram0800",
   "line": "waveform + gate byte, e.g. $41 pulse, $21 sawtooth, $81 noise, gate off = bit 0 clear"
  },
  "66BB": {
   "unit": "game/mainProgram0800",
   "line": "$D404 + voice*7 - written last so the gate change lands after the new envelope"
  },
  "66BE": {
   "unit": "game/mainProgram0800",
   "line": "shared exit of soundPlayerTick and updateSoundVoice"
  },
  "66C1": {
   "unit": "game/mainProgram0800",
   "line": "add it to the oscillator frequency low byte"
  },
  "66C4": {
   "unit": "game/mainProgram0800",
   "line": "and fall straight back into the script interpreter"
  },
  "66CA": {
   "unit": "game/mainProgram0800",
   "line": "N = bit 7, V = bit 6, Z = 1 only when the byte is 0 (the 'set duration' command)"
  },
  "66CD": {
   "unit": "game/mainProgram0800",
   "line": "$80-$FF: jitter the frequency"
  },
  "66CF": {
   "unit": "game/mainProgram0800",
   "line": "$40-$7F: loop or jump"
  },
  "66D1": {
   "unit": "game/mainProgram0800",
   "line": "remember Z: command 0 must stop the interpreter after it has run"
  },
  "66D2": {
   "unit": "game/mainProgram0800",
   "line": "Y = command number 0-10"
  },
  "66D3": {
   "unit": "game/mainProgram0800",
   "line": "X = voice*2"
  },
  "66D5": {
   "unit": "game/mainProgram0800",
   "line": "plus the offset of that command's slot inside the 48-byte state block"
  },
  "66D8": {
   "unit": "game/mainProgram0800",
   "line": "Y now indexes the exact byte to write"
  },
  "66D9": {
   "unit": "game/mainProgram0800",
   "line": "operand byte"
  },
  "66DC": {
   "unit": "game/mainProgram0800",
   "line": "store it in the slot"
  },
  "66DF": {
   "unit": "game/mainProgram0800",
   "line": "slots from $12 on (frequency, pulse, AD/SR, the two slides) are 16-bit"
  },
  "66E1": {
   "unit": "game/mainProgram0800",
   "line": "one-byte slot - done"
  },
  "66E3": {
   "unit": "game/mainProgram0800",
   "line": "second byte of the pair, at slot+1"
  },
  "66EA": {
   "unit": "game/mainProgram0800",
   "line": "recover 'was the command 0?'"
  },
  "66EB": {
   "unit": "game/mainProgram0800",
   "line": "any other command - keep interpreting"
  },
  "66ED": {
   "unit": "game/mainProgram0800",
   "line": "command 0 set the note duration; stop until it runs out"
  },
  "66F0": {
   "unit": "game/mainProgram0800",
   "line": "keep the slot offset"
  },
  "66F3": {
   "unit": "game/mainProgram0800",
   "line": "target address low byte"
  },
  "66F6": {
   "unit": "game/mainProgram0800",
   "line": "push it"
  },
  "66F7": {
   "unit": "game/mainProgram0800",
   "line": "target address high byte"
  },
  "66FA": {
   "unit": "game/mainProgram0800",
   "line": "push it as well (so it comes off the stack first)"
  },
  "66FC": {
   "unit": "game/mainProgram0800",
   "line": "voice*2"
  },
  "66FD": {
   "unit": "game/mainProgram0800",
   "line": "plus the counter slot"
  },
  "6700": {
   "unit": "game/mainProgram0800",
   "line": "Y = this voice's copy of that counter"
  },
  "6704": {
   "unit": "game/mainProgram0800",
   "line": "a counter of 0 means 'jump unconditionally'"
  },
  "6707": {
   "unit": "game/mainProgram0800",
   "line": "count one repetition off"
  },
  "670C": {
   "unit": "game/mainProgram0800",
   "line": "repetitions left - take the jump"
  },
  "670E": {
   "unit": "game/mainProgram0800",
   "line": "loop finished: drop the target high byte..."
  },
  "670F": {
   "unit": "game/mainProgram0800",
   "line": "...and the low byte"
  },
  "6710": {
   "unit": "game/mainProgram0800",
   "line": "carry on with the byte after the jump"
  },
  "6713": {
   "unit": "game/mainProgram0800",
   "line": "top of stack is the high byte pushed at $66FA"
  },
  "6714": {
   "unit": "game/mainProgram0800",
   "line": "script pointer high byte for this voice ($D9 + voice*2)"
  },
  "6716": {
   "unit": "game/mainProgram0800",
   "line": "then the low byte"
  },
  "6717": {
   "unit": "game/mainProgram0800",
   "line": "script pointer low byte ($D8 + voice*2)"
  },
  "6719": {
   "unit": "game/mainProgram0800",
   "line": "continue interpreting at the jump target"
  },
  "671E": {
   "unit": "game/mainProgram0800",
   "line": "kept so runSoundScript can BIT it without disturbing A"
  },
  "6721": {
   "unit": "game/mainProgram0800",
   "line": "advance the pointer low byte"
  },
  "6723": {
   "unit": "game/mainProgram0800",
   "line": "no page crossing"
  },
  "6725": {
   "unit": "game/mainProgram0800",
   "line": "carry into the high byte"
  },
  "672A": {
   "unit": "game/mainProgram0800",
   "line": "...and, while it is non-zero, as the 'driver busy' lock read by soundPlayerTick"
  },
  "672D": {
   "unit": "game/mainProgram0800",
   "line": "volume 12 of 15"
  },
  "672F": {
   "unit": "game/mainProgram0800",
   "line": "the driver writes this to $D418 every tick"
  },
  "6732": {
   "unit": "game/mainProgram0800",
   "line": "remember the id; it is copied into every voice slot this sound takes"
  },
  "6735": {
   "unit": "game/mainProgram0800",
   "line": "id*2"
  },
  "6739": {
   "unit": "game/mainProgram0800",
   "line": "id*4"
  },
  "673B": {
   "unit": "game/mainProgram0800",
   "line": "id*4 + id*2 = id*6, the row stride of soundPointerTable"
  },
  "673E": {
   "unit": "game/mainProgram0800",
   "line": "X = byte offset of this sound's row"
  },
  "673F": {
   "unit": "game/mainProgram0800",
   "line": "Y = voice*2, starting at voice 0"
  },
  "6741": {
   "unit": "game/mainProgram0800",
   "line": "plain voice number 0-2, kept in parallel with Y"
  },
  "6747": {
   "unit": "game/mainProgram0800",
   "line": "0 = this sound does not use this voice; leave it playing"
  },
  "6749": {
   "unit": "game/mainProgram0800",
   "line": "absolute,Y because there is no STA zero page,Y - writes $D9 + voice*2"
  },
  "674C": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the script address"
  },
  "674F": {
   "unit": "game/mainProgram0800",
   "line": "writes $D8 + voice*2"
  },
  "6753": {
   "unit": "game/mainProgram0800",
   "line": "save voice*2 across the clear loop, which walks Y through the whole block"
  },
  "6756": {
   "unit": "game/mainProgram0800",
   "line": "first byte of the row for this voice"
  },
  "6759": {
   "unit": "game/mainProgram0800",
   "line": "second byte of the row for this voice"
  },
  "675E": {
   "unit": "game/mainProgram0800",
   "line": "next 6-byte row"
  },
  "6761": {
   "unit": "game/mainProgram0800",
   "line": "$30 = 48 bytes = eight rows: stop once Y has walked past the block"
  },
  "6765": {
   "unit": "game/mainProgram0800",
   "line": "voice number 0-2"
  },
  "6768": {
   "unit": "game/mainProgram0800",
   "line": "the sound now playing"
  },
  "676B": {
   "unit": "game/mainProgram0800",
   "line": "record it so requestSound can weigh priorities against it"
  },
  "676F": {
   "unit": "game/mainProgram0800",
   "line": "voice*2 back in Y"
  },
  "6770": {
   "unit": "game/mainProgram0800",
   "line": "duration 2 ticks..."
  },
  "6772": {
   "unit": "game/mainProgram0800",
   "line": "...so the script is first run on the second driver tick, not this one"
  },
  "6775": {
   "unit": "game/mainProgram0800",
   "line": "next 16-bit entry of the table row"
  },
  "6777": {
   "unit": "game/mainProgram0800",
   "line": "next voice*2"
  },
  "6779": {
   "unit": "game/mainProgram0800",
   "line": "next voice number"
  },
  "677C": {
   "unit": "game/mainProgram0800",
   "line": "3, 2, 1, 0 - and reaching 0 also releases soundPlayerTick again"
  },
  "678F": {
   "unit": "game/mainProgram0800",
   "line": "8 AD/SR, 9 frequency slide, 10 pulse-width slide"
  },
  "67CE": {
   "unit": "game/mainProgram0800",
   "line": "and its high byte"
  },
  "67D3": {
   "unit": "game/mainProgram0800",
   "line": "$9247 = 0 (the overlay-B bitmap unpacker keeps a state flag here)"
  },
  "67D9": {
   "unit": "game/mainProgram0800",
   "line": "keep the marker; the overlay-B unpacker at $865E compares against the same byte"
  },
  "67DC": {
   "unit": "game/mainProgram0800",
   "line": "A still holds the source low byte"
  },
  "67DD": {
   "unit": "game/mainProgram0800",
   "line": "step past the marker byte"
  },
  "67DE": {
   "unit": "game/mainProgram0800",
   "line": "no page crossing"
  },
  "67E0": {
   "unit": "game/mainProgram0800",
   "line": "carry into the source high byte"
  },
  "67E1": {
   "unit": "game/mainProgram0800",
   "line": "point fetchRleByte at the first packed byte (self-modified operand $681B)"
  },
  "67E4": {
   "unit": "game/mainProgram0800",
   "line": "and its high byte ($681C)"
  },
  "67E9": {
   "unit": "game/mainProgram0800",
   "line": "destination pointer = $0200"
  },
  "67F1": {
   "unit": "game/mainProgram0800",
   "line": "two pages to fill: $0200-$03FF"
  },
  "67F3": {
   "unit": "game/mainProgram0800",
   "line": "offset inside the current page"
  },
  "67F5": {
   "unit": "game/mainProgram0800",
   "line": "run counter: one byte still owed before the next stream byte is read"
  },
  "67F8": {
   "unit": "game/mainProgram0800",
   "line": "still repeating - store the same value again"
  },
  "67FA": {
   "unit": "game/mainProgram0800",
   "line": "next byte of the packed stream"
  },
  "67FD": {
   "unit": "game/mainProgram0800",
   "line": "assume a literal: a run of exactly one"
  },
  "67FF": {
   "unit": "game/mainProgram0800",
   "line": "is it the escape marker?"
  },
  "6802": {
   "unit": "game/mainProgram0800",
   "line": "no - store it as it stands"
  },
  "6804": {
   "unit": "game/mainProgram0800",
   "line": "escape: the repeat count comes next"
  },
  "6809": {
   "unit": "game/mainProgram0800",
   "line": "and then the byte to repeat"
  },
  "680C": {
   "unit": "game/mainProgram0800",
   "line": "X = how many copies to emit"
  },
  "680E": {
   "unit": "game/mainProgram0800",
   "line": "store into $0200 + page*256 + Y"
  },
  "6811": {
   "unit": "game/mainProgram0800",
   "line": "until the page is full"
  },
  "6813": {
   "unit": "game/mainProgram0800",
   "line": "next destination page"
  },
  "6817": {
   "unit": "game/mainProgram0800",
   "line": "two pages done?"
  },
  "681D": {
   "unit": "game/mainProgram0800",
   "line": "keep a copy - the routine hands the byte back in X as well"
  },
  "6821": {
   "unit": "game/mainProgram0800",
   "line": "advance the stream pointer low byte"
  },
  "6829": {
   "unit": "game/mainProgram0800",
   "line": "and its high byte"
  },
  "682F": {
   "unit": "game/mainProgram0800",
   "line": "return the byte in A"
  },
  "6516": {
   "unit": "game/mainProgram0800",
   "line": "scratch18 accumulates the member count"
  },
  "652D": {
   "unit": "game/mainProgram0800",
   "line": "the caller wants the count in A"
  },
  "6541": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the add that follows"
  },
  "6548": {
   "unit": "game/mainProgram0800",
   "line": "left edge of the menu window"
  },
  "6550": {
   "unit": "game/mainProgram0800",
   "line": "the clear below and the header are drawn in it"
  },
  "657A": {
   "unit": "game/mainProgram0800",
   "line": "the item rows and the highlight bar use this"
  },
  "659B": {
   "unit": "game/mainProgram0800",
   "line": "the clear below paints the whole window in it"
  },
  "65A2": {
   "unit": "game/mainProgram0800",
   "line": "the menu selector reads this sentinel every frame"
  },
  "65A7": {
   "unit": "game/mainProgram0800",
   "line": "colour of the item rows"
  },
  "65C4": {
   "unit": "game/mainProgram0800",
   "line": "join the common tail: unhighlight and restart the menu"
  },
  "65D8": {
   "unit": "game/mainProgram0800",
   "line": "voicePauseReconnect clears bit 7 when the link is back"
  },
  "65FE": {
   "unit": "game/mainProgram0800",
   "line": "return - normally, or one level higher when the wait was abandoned"
  },
  "6613": {
   "unit": "game/mainProgram0800",
   "line": "start at the top of the list"
  },
  "661A": {
   "unit": "game/mainProgram0800",
   "line": "five selectable rows"
  },
  "6623": {
   "unit": "game/mainProgram0800",
   "line": "indices 3 (CUSTOM) and 4 ((EXIT)) are not rotations"
  },
  "6636": {
   "unit": "game/mainProgram0800",
   "line": "Y = the chosen index"
  },
  "6647": {
   "unit": "game/mainProgram0800",
   "line": "advance SID voice 1"
  },
  "664C": {
   "unit": "game/mainProgram0800",
   "line": "advance SID voice 2"
  },
  "6656": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the 16-bit frequency add"
  },
  "665A": {
   "unit": "game/mainProgram0800",
   "line": "current oscillator frequency, low byte"
  },
  "6663": {
   "unit": "game/mainProgram0800",
   "line": "current frequency high byte, plus the carry from the low half"
  },
  "6669": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the 16-bit pulse-width add"
  },
  "666D": {
   "unit": "game/mainProgram0800",
   "line": "current pulse width, low byte"
  },
  "6676": {
   "unit": "game/mainProgram0800",
   "line": "current pulse width high byte, plus carry"
  },
  "668B": {
   "unit": "game/mainProgram0800",
   "line": "Y = voice number 0-2"
  },
  "668C": {
   "unit": "game/mainProgram0800",
   "line": "0 = no sound playing on this voice"
  },
  "6694": {
   "unit": "game/mainProgram0800",
   "line": "frequency low byte of this voice"
  },
  "669A": {
   "unit": "game/mainProgram0800",
   "line": "frequency high byte"
  },
  "66A0": {
   "unit": "game/mainProgram0800",
   "line": "pulse width low byte"
  },
  "66A6": {
   "unit": "game/mainProgram0800",
   "line": "pulse width high byte"
  },
  "66AC": {
   "unit": "game/mainProgram0800",
   "line": "attack/decay byte"
  },
  "66B2": {
   "unit": "game/mainProgram0800",
   "line": "sustain/release byte"
  },
  "66D4": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry before adding the slot offset"
  },
  "66E4": {
   "unit": "game/mainProgram0800",
   "line": "high byte of a 16-bit parameter"
  },
  "66E7": {
   "unit": "game/mainProgram0800",
   "line": "stored one byte further on, in slot+1"
  },
  "66FB": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry before indexing the counter"
  },
  "6701": {
   "unit": "game/mainProgram0800",
   "line": "this voice's copy of the selected loop counter"
  },
  "6706": {
   "unit": "game/mainProgram0800",
   "line": "set the carry for the subtract"
  },
  "6709": {
   "unit": "game/mainProgram0800",
   "line": "write the decremented repeat count back"
  },
  "6727": {
   "unit": "game/mainProgram0800",
   "line": "A holds the byte, the pointer now points past it"
  },
  "6736": {
   "unit": "game/mainProgram0800",
   "line": "keep id*2 so it can be added to id*4 below"
  },
  "673A": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the id*4 + id*2 sum"
  },
  "6752": {
   "unit": "game/mainProgram0800",
   "line": "voice*2 for this voice"
  },
  "675C": {
   "unit": "game/mainProgram0800",
   "line": "current offset inside the state block"
  },
  "675D": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the +6 row step"
  },
  "6760": {
   "unit": "game/mainProgram0800",
   "line": "Y = the same voice's slot in the next row"
  },
  "6763": {
   "unit": "game/mainProgram0800",
   "line": "keep clearing while Y is still inside the 48-byte block"
  },
  "676E": {
   "unit": "game/mainProgram0800",
   "line": "get voice*2 back"
  },
  "6776": {
   "unit": "game/mainProgram0800",
   "line": "table entries are 16-bit, so step X by two"
  },
  "6778": {
   "unit": "game/mainProgram0800",
   "line": "and Y by two, to the next voice's slot"
  },
  "677F": {
   "unit": "game/mainProgram0800",
   "line": "three voices per sound"
  },
  "6781": {
   "unit": "game/mainProgram0800",
   "line": "every voice this sound uses is now armed"
  },
  "67D1": {
   "unit": "game/mainProgram0800",
   "line": "X = 0 for the flag store below"
  },
  "67E7": {
   "unit": "game/mainProgram0800",
   "line": "destination low byte"
  },
  "67EB": {
   "unit": "game/mainProgram0800",
   "line": "destination page $02"
  },
  "67ED": {
   "unit": "game/mainProgram0800",
   "line": "the unpacked data always lands at $0200"
  },
  "67EF": {
   "unit": "game/mainProgram0800",
   "line": "two pages of output"
  },
  "6807": {
   "unit": "game/mainProgram0800",
   "line": "remember how many copies to emit"
  },
  "6810": {
   "unit": "game/mainProgram0800",
   "line": "next byte of the current page"
  },
  "6815": {
   "unit": "game/mainProgram0800",
   "line": "one page finished"
  },
  "6819": {
   "unit": "game/mainProgram0800",
   "line": "$0200-$03FF now hold the unpacked block"
  },
  "681E": {
   "unit": "game/mainProgram0800",
   "line": "the pointer is advanced by one byte"
  },
  "6820": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the 16-bit increment"
  },
  "6824": {
   "unit": "game/mainProgram0800",
   "line": "the operand of the LDA above is the pointer itself"
  },
  "6827": {
   "unit": "game/mainProgram0800",
   "line": "only the carry has to reach the high byte"
  },
  "682C": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the advanced stream pointer"
  },
  "6830": {
   "unit": "game/mainProgram0800",
   "line": "byte returned in A (and in X)"
  },
  "67A5": {
   "unit": "game/mainProgram0800",
   "line": "oscillator frequency of voice 0, high byte (then voices 1 and 2)"
  },
  "67AB": {
   "unit": "game/mainProgram0800",
   "line": "pulse width of voice 0, high byte (then voices 1 and 2)"
  },
  "67B1": {
   "unit": "game/mainProgram0800",
   "line": "sustain/release of voice 0 (then voices 1 and 2)"
  },
  "67B7": {
   "unit": "game/mainProgram0800",
   "line": "per-tick frequency slide of voice 0, high byte (then voices 1 and 2)"
  },
  "67BD": {
   "unit": "game/mainProgram0800",
   "line": "per-tick pulse-width sweep of voice 0, high byte (then voices 1 and 2)"
  },
  "67C3": {
   "unit": "game/mainProgram0800",
   "line": "the script byte last fetched"
  },
  "67C4": {
   "unit": "game/mainProgram0800",
   "line": "scratch: id*2 in startSound, loop slot in runSoundScript"
  },
  "67C5": {
   "unit": "game/mainProgram0800",
   "line": "non-zero = startSound is busy, soundPlayerTick must not run"
  },
  "67C6": {
   "unit": "game/mainProgram0800",
   "line": "the sound id startSound is installing"
  },
  "67C7": {
   "unit": "game/mainProgram0800",
   "line": "voice number 0-2 startSound is working on"
  }
 },
 "dataTypes": {
  "6831": [
   "addr",
   174
  ],
  "6C5D": [
   "bitmap",
   32
  ],
  "6C7D": [
   "bitmap",
   32
  ],
  "6C9D": [
   "bitmap",
   32
  ],
  "6CBD": [
   "bitmap",
   32
  ],
  "6CDD": [
   "bitmap",
   32
  ],
  "6CFD": [
   "bitmap",
   32
  ],
  "6D1D": [
   "bitmap",
   32
  ],
  "6D3D": [
   "bitmap",
   32
  ],
  "6D5D": [
   "bitmap",
   32
  ],
  "6D7D": [
   "bitmap",
   32
  ],
  "6D9D": [
   "bitmap",
   32
  ],
  "6DBD": [
   "bitmap",
   32
  ],
  "6DDD": [
   "bitmap",
   32
  ],
  "6DFD": [
   "bitmap",
   32
  ],
  "6E1D": [
   "bitmap",
   32
  ],
  "6E3D": [
   "bitmap",
   32
  ],
  "6E5D": [
   "bitmap",
   32
  ],
  "6E7D": [
   "bitmap",
   32
  ],
  "6E9D": [
   "bitmap",
   32
  ],
  "6EBD": [
   "bitmap",
   32
  ],
  "6EDD": [
   "byte",
   35
  ]
 },
 "zpComments": {
  "5A": "byte 0 of the 24-bit scenario/map seed stepped by nextScenarioRandom ($FD4A); also the first answer byte of the manual look-up check, and the value the sound driver adds to a voice frequency for the bit-7 'jitter' script command ($66BF)"
 }
}