{
 "chunk": "game_mainProgram0800_158F",
 "unit": "game/mainProgram0800",
 "unitLabels": {
  "game/mainProgram0800": {}
 },
 "notes": {
  "158F": {
   "unit": "game/mainProgram0800",
   "routine": [
    "clearMenuWindowPlain - Blanks the whole console-menu panel with text colour 0 (black text, no highlight EOR mask). This is the screen-id-3 arm of clearCurrentScreenWindows ($2DDE), which re-clears the content area of whichever screen is active; it is reached only by the JMP at $2E0A, so it returns straight to that routine's caller (typically the $C000 overlay's resume-after-pause path at $C75E).",
    "In:  nothing in registers; textFillGlyph zp_3E supplies the blank glyph",
    "Out: zp_3D = 0, the menu window geometry of setMenuWindowBounds (columns 2..37, rows 7..21) and the whole panel filled with blanks",
    "Called from: JMP at $2E0A (clearCurrentScreenWindows, screen id 3)"
   ],
   "line": "colour byte 0: black text and an all-zero highlight EOR mask"
  },
  "1591": {
   "unit": "game/mainProgram0800",
   "line": "the text engine writes zp_3D into colour RAM for every cell it touches"
  },
  "1593": {
   "unit": "game/mainProgram0800",
   "line": "menu panel window: left column 2, width $24, rows 7..$16"
  },
  "1596": {
   "unit": "game/mainProgram0800",
   "line": "fill the panel with the blank glyph zp_3E and return to clearCurrentScreenWindows' caller"
  },
  "1599": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawGameSetupSummary - Draws the fixed header of the console STATS tab (menu tab 0): the rules word STANDARD/CUSTOM, the game-type name, an optional '. 1ST PLAY'/'. 2ND PLAY' suffix, then the run-time owner-name line and the column of row labels (GRUNTS/RIDERS/.../MAP ID) that drawStatsTab ($8518, overlay B) later fills numbers in beside. For a CUSTOM game it adds the ' MENUS xxxx DAMAGE xxx.' summary line.",
    "In:  gameTypeOptions $0BA3 (bits 0-2 game type, bit 6 CUSTOM rules), playsFirstFlag $0BA1, ownerNameString $0B7F, statsTabLabels $86AC and msgOptionSummary $0636 (both only valid while overlay B / the string block is resident)",
    "Out: text drawn into the bitmap from row 7 down; zp_3B window left ends at 10 (or 8 for CUSTOM), zp_3D = $6E, zp_3F/zp_40 left after the last character; self-modifies the operand at $15DD",
    "Called from: mainMenuLoop at $14D0 when tab 0 (STATS) becomes the selected tab"
   ],
   "block": [
    "Line 1 of the STATS tab: '<STANDARD|CUSTOM> <GAME TYPE>[. 1ST PLAY]'."
   ],
   "line": "colour byte $6F: light-grey text (colour RAM low nibble), highlight EOR mask 6"
  },
  "159B": {
   "unit": "game/mainProgram0800",
   "line": "set the text colour for the heading line"
  },
  "159D": {
   "unit": "game/mainProgram0800",
   "line": "cursor to the left edge of the window"
  },
  "159F": {
   "unit": "game/mainProgram0800",
   "line": "textCursorCol is relative to textWindowLeft"
  },
  "15A1": {
   "unit": "game/mainProgram0800",
   "line": "heading starts at screen column 7"
  },
  "15A3": {
   "unit": "game/mainProgram0800",
   "line": "window left edge = 7"
  },
  "15A5": {
   "unit": "game/mainProgram0800",
   "line": "first line of the panel is screen row 7"
  },
  "15A7": {
   "unit": "game/mainProgram0800",
   "line": "absolute cursor row"
  },
  "15A9": {
   "unit": "game/mainProgram0800",
   "line": "recompute the bitmap and colour-RAM pointers for that cell"
  },
  "15AC": {
   "unit": "game/mainProgram0800",
   "line": "assume rulesNameTable entry 1 = CUSTOM"
  },
  "15AE": {
   "unit": "game/mainProgram0800",
   "line": "game settings byte: bits 0-2 type, bit 3 UNIT MENUS, bit 4 DAMAGE, bit 6 CUSTOM rules"
  },
  "15B1": {
   "unit": "game/mainProgram0800",
   "line": "keep the settings byte on the stack for the game-type lookup below"
  },
  "15B2": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 = CUSTOM rules selected"
  },
  "15B4": {
   "unit": "game/mainProgram0800",
   "line": "branch if CUSTOM - keep table entry 1 and always print the play order"
  },
  "15B6": {
   "unit": "game/mainProgram0800",
   "line": "Z=1 for game type 1 (QB SNEAK) or 6 (DEFENDER), the asymmetric ones"
  },
  "15B9": {
   "unit": "game/mainProgram0800",
   "line": "branch if this type shows 1ST/2ND PLAY - no padding needed"
  },
  "15BB": {
   "unit": "game/mainProgram0800",
   "line": "four blanks..."
  },
  "15BD": {
   "unit": "game/mainProgram0800",
   "line": "...printFillChars ($C0EF): centres 'STANDARD <type>' when no ' 1ST PLAY' follows"
  },
  "15C0": {
   "unit": "game/mainProgram0800",
   "line": "rulesNameTable entry 0 = STANDARD"
  },
  "15C2": {
   "unit": "game/mainProgram0800",
   "line": "self-modifying code: patches the LDA #imm operand at $15DD, so $15DC loads 1 for CUSTOM and 0 for STANDARD"
  },
  "15C5": {
   "unit": "game/mainProgram0800",
   "line": "low byte of rulesNameTable"
  },
  "15C7": {
   "unit": "game/mainProgram0800",
   "line": "$CC11 = rulesNameTable, two 8-byte bit-7-terminated entries"
  },
  "15C9": {
   "unit": "game/mainProgram0800",
   "line": "print entry X ($CC11 + X*8): 'STANDARD' or 'CUSTOM'"
  },
  "15CC": {
   "unit": "game/mainProgram0800",
   "line": "one blank..."
  },
  "15CE": {
   "unit": "game/mainProgram0800",
   "line": "...between the rules word and the game type name"
  },
  "15D1": {
   "unit": "game/mainProgram0800",
   "line": "recover the settings byte"
  },
  "15D2": {
   "unit": "game/mainProgram0800",
   "line": "bits 0-2 = game type 0..6"
  },
  "15D4": {
   "unit": "game/mainProgram0800",
   "line": "use it as the table index"
  },
  "15D5": {
   "unit": "game/mainProgram0800",
   "line": "low byte of scenarioNameTable"
  },
  "15D7": {
   "unit": "game/mainProgram0800",
   "line": "$CCC8 = scenarioNameTable, seven 8-byte names"
  },
  "15D9": {
   "unit": "game/mainProgram0800",
   "line": "print SCRIMAGE / QB SNEAK / THE BOMB / FACE-OFF / SLUGGERS / FULL WAR / DEFENDER"
  },
  "15DC": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $15C2: 1 = CUSTOM rules, 0 = STANDARD"
  },
  "15DE": {
   "unit": "game/mainProgram0800",
   "line": "CUSTOM rules always show whose play it is"
  },
  "15E0": {
   "unit": "game/mainProgram0800",
   "line": "standard rules: only QB SNEAK and DEFENDER have a play order"
  },
  "15E3": {
   "unit": "game/mainProgram0800",
   "line": "symmetric game type under standard rules - skip the play-order suffix"
  },
  "15E5": {
   "unit": "game/mainProgram0800",
   "block": [
    "Suffix '. 1ST PLAY' / '. 2ND PLAY'."
   ],
   "line": "$AE = '.' with bit 7 set; printChar masks bit 7 off"
  },
  "15E7": {
   "unit": "game/mainProgram0800",
   "line": "print the full stop after the game type"
  },
  "15EA": {
   "unit": "game/mainProgram0800",
   "line": "two blanks..."
  },
  "15EC": {
   "unit": "game/mainProgram0800",
   "line": "...before the play-order words"
  },
  "15EF": {
   "unit": "game/mainProgram0800",
   "line": "low byte of '1ST'"
  },
  "15F1": {
   "unit": "game/mainProgram0800",
   "line": "$CBFE = '1ST'"
  },
  "15F3": {
   "unit": "game/mainProgram0800",
   "line": "$0BA1 non-zero = this machine has first play this game"
  },
  "15F6": {
   "unit": "game/mainProgram0800",
   "line": "branch if we play first - keep the '1ST' pointer"
  },
  "15F8": {
   "unit": "game/mainProgram0800",
   "line": "low byte of '2ND'"
  },
  "15FA": {
   "unit": "game/mainProgram0800",
   "line": "$CC01 = '2ND'"
  },
  "15FC": {
   "unit": "game/mainProgram0800",
   "line": "print '1ST' or '2ND'"
  },
  "15FF": {
   "unit": "game/mainProgram0800",
   "line": "low byte of ' PLAY'"
  },
  "1601": {
   "unit": "game/mainProgram0800",
   "line": "$CBF9 = ' PLAY'"
  },
  "1603": {
   "unit": "game/mainProgram0800",
   "line": "print ' PLAY'"
  },
  "1606": {
   "unit": "game/mainProgram0800",
   "block": [
    "Owner-name line plus the column of STATS row labels, then the CUSTOM rules summary."
   ],
   "line": "carriage return: column 0 of the next panel row"
  },
  "1609": {
   "unit": "game/mainProgram0800",
   "line": "label block starts at screen column 10"
  },
  "160B": {
   "unit": "game/mainProgram0800",
   "line": "move the window left edge"
  },
  "160D": {
   "unit": "game/mainProgram0800",
   "line": "indent three more columns (absolute column 13)"
  },
  "160F": {
   "unit": "game/mainProgram0800",
   "line": "cursor column relative to the window"
  },
  "1611": {
   "unit": "game/mainProgram0800",
   "line": "recompute the cell pointers for the new window/cursor"
  },
  "1614": {
   "unit": "game/mainProgram0800",
   "line": "$6F -> $6E: light-blue body text instead of light grey"
  },
  "1616": {
   "unit": "game/mainProgram0800",
   "line": "low byte of ownerNameString"
  },
  "1618": {
   "unit": "game/mainProgram0800",
   "line": "$0B7F = the 21-character owner/player line built at run time by buildOwnerNameString"
  },
  "161A": {
   "unit": "game/mainProgram0800",
   "line": "print it; printString leaves zp_A2/zp_A3 just past the terminator"
  },
  "161D": {
   "unit": "game/mainProgram0800",
   "line": "low byte of statsTabLabels"
  },
  "161F": {
   "unit": "game/mainProgram0800",
   "line": "$86AC lives in the resident $6F00 overlay B (comcenScreens6F00)"
  },
  "1621": {
   "unit": "game/mainProgram0800",
   "line": "one string with embedded CRs draws GRUNTS/RIDERS/BOOMERS/SPIES/DRONES/MISSILES/COMSPEED/TER PTS/RECYCLER/MAP ID"
  },
  "1624": {
   "unit": "game/mainProgram0800",
   "line": "settings byte again"
  },
  "1627": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 = CUSTOM rules"
  },
  "1629": {
   "unit": "game/mainProgram0800",
   "line": "standard rules - nothing more to print"
  },
  "162B": {
   "unit": "game/mainProgram0800",
   "line": "custom summary starts at screen column 8"
  },
  "162D": {
   "unit": "game/mainProgram0800",
   "line": "move the window left edge once more"
  },
  "162F": {
   "unit": "game/mainProgram0800",
   "line": "carriage return onto the summary row"
  },
  "1632": {
   "unit": "game/mainProgram0800",
   "line": "low byte of optionSummaryTemplate"
  },
  "1634": {
   "unit": "game/mainProgram0800",
   "line": "$0636 = ' MENUS xxxx DAMAGE xxx.' - overlay B has already patched the xxxx/xxx fields in place"
  },
  "1636": {
   "unit": "game/mainProgram0800",
   "line": "print the rules summary"
  },
  "1639": {
   "unit": "game/mainProgram0800",
   "line": "done - back to mainMenuLoop"
  },
  "163A": {
   "unit": "game/mainProgram0800",
   "block": [
    "tabColumnTable - screen column of the top-left cell of each console tab box,",
    "indexed by tab number (0 STATS, 1 REPAIR, 2 MISC). Each box is 2x2 character",
    "cells at row 1. Read by setTabNormalColour/setTabHighlightColour."
   ],
   "line": "STATS at column $0B, REPAIR at $13, MISC at $1B"
  },
  "163D": {
   "unit": "game/mainProgram0800",
   "block": [
    "tabColourTable - normal screen-RAM colour byte of each tab box (upper nibble =",
    "multicolour pair 01, lower nibble = pair 10). One entry per tab 0..2."
   ],
   "line": "STATS black/green $05, REPAIR black/blue $06, MISC black/red $02"
  },
  "1640": {
   "unit": "game/mainProgram0800",
   "block": [
    "tabHighlightColourTable - bright variant of tabColourTable used while a tab is",
    "selected and blinking; same three-entry layout. A zero entry would make",
    "setTabHighlightColour fall through to the normal colour, but none is zero."
   ],
   "line": "STATS black/light green $0D, REPAIR black/light blue $0E, MISC black/light red $0A"
  },
  "1643": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawMenuTabBar - Draws the three console tabs of screen 3: prints the labels STATS, REPAIR and MISC on row 3 starting at column 10, each in its own colour, then paints the 2x2 tab box behind each of them in its normal colour, and finally resets the bitmap/colour draw modes to 'replace'.",
    "In:  nothing; uses the character-block at $9804 and the three colour/column tables at $163A-$1642",
    "Out: labels and tab boxes drawn; zp_21 used as the 2..0 tab counter (setTabNormalColour clobbers X); text window left as the full screen, zp_3D = $C2, zp_3F/zp_40 after 'MISC'",
    "Called from: showMainMenu at $155C, when the STATS/REPAIR/MISC screen is built"
   ],
   "block": [
    "Print the three tab labels on row 3, two/three blanks apart."
   ],
   "line": "colour byte $CD: light-green text, highlight EOR mask $C - matches the green STATS tab"
  },
  "1645": {
   "unit": "game/mainProgram0800",
   "line": "set the label colour"
  },
  "1647": {
   "unit": "game/mainProgram0800",
   "line": "print over the whole screen: left 0, top 0, bottom $19, width $28"
  },
  "164A": {
   "unit": "game/mainProgram0800",
   "line": "labels start at column 10"
  },
  "164C": {
   "unit": "game/mainProgram0800",
   "line": "cursor column (window left is 0 here, so this is absolute)"
  },
  "164E": {
   "unit": "game/mainProgram0800",
   "line": "tab labels sit on screen row 3"
  },
  "1650": {
   "unit": "game/mainProgram0800",
   "line": "absolute cursor row"
  },
  "1652": {
   "unit": "game/mainProgram0800",
   "line": "recompute the cell pointers"
  },
  "1655": {
   "unit": "game/mainProgram0800",
   "line": "low byte of 'STATS'"
  },
  "1657": {
   "unit": "game/mainProgram0800",
   "line": "$CB70 = 'STATS'"
  },
  "1659": {
   "unit": "game/mainProgram0800",
   "line": "print it"
  },
  "165C": {
   "unit": "game/mainProgram0800",
   "line": "two blanks..."
  },
  "165E": {
   "unit": "game/mainProgram0800",
   "line": "...between STATS and REPAIR"
  },
  "1661": {
   "unit": "game/mainProgram0800",
   "line": "colour byte $C6: blue text, matching the blue REPAIR tab"
  },
  "1663": {
   "unit": "game/mainProgram0800",
   "line": "switch colour before the next label"
  },
  "1665": {
   "unit": "game/mainProgram0800",
   "line": "low byte of 'REPAIR'"
  },
  "1667": {
   "unit": "game/mainProgram0800",
   "line": "$CB75 = 'REPAIR'"
  },
  "1669": {
   "unit": "game/mainProgram0800",
   "line": "print it"
  },
  "166C": {
   "unit": "game/mainProgram0800",
   "line": "three blanks..."
  },
  "166E": {
   "unit": "game/mainProgram0800",
   "line": "...between REPAIR and MISC"
  },
  "1671": {
   "unit": "game/mainProgram0800",
   "line": "colour byte $C2: red text, matching the red MISC tab"
  },
  "1673": {
   "unit": "game/mainProgram0800",
   "line": "switch colour again"
  },
  "1675": {
   "unit": "game/mainProgram0800",
   "line": "low byte of 'MISC'"
  },
  "1677": {
   "unit": "game/mainProgram0800",
   "line": "$CB6C = 'MISC'"
  },
  "1679": {
   "unit": "game/mainProgram0800",
   "line": "print it"
  },
  "167C": {
   "unit": "game/mainProgram0800",
   "block": [
    "Paint the three tab boxes, from MISC (2) down to STATS (0). The loop counter has",
    "to live in zero page because setTabNormalColour reloads X for fillScreenColourRect."
   ],
   "line": "start with tab 2 = MISC"
  },
  "167E": {
   "unit": "game/mainProgram0800",
   "line": "keep the tab index in zp_21 across the call"
  },
  "1680": {
   "unit": "game/mainProgram0800",
   "line": "draw tab X's box shape and fill it with its normal colour"
  },
  "1683": {
   "unit": "game/mainProgram0800",
   "line": "next tab down"
  },
  "1685": {
   "unit": "game/mainProgram0800",
   "line": "reload the index that setTabNormalColour destroyed"
  },
  "1687": {
   "unit": "game/mainProgram0800",
   "line": "loop while the index is still 0..2"
  },
  "1689": {
   "unit": "game/mainProgram0800",
   "line": "restore bitmap and colour draw mode 0 (replace) and return"
  },
  "168C": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setTabHighlightColour - Redraws console tab X in its bright colour: looks the colour up in tabHighlightColourTable and joins setTabNormalColour's common tail. A zero table entry would fall through to the normal colour, but all three entries are non-zero, so the BNE is always taken.",
    "In:  X = tab index 0..2",
    "Out: the tab's 2x2 box redrawn and recoloured; see setTabNormalColour for the details",
    "Called from: mainMenuLoop at $1478 (fire pressed on the tab) and $14C5 (tab changed), and blinkSelectedTab at $16D0"
   ],
   "line": "bright colour byte of tab X ($0D/$0E/$0A)"
  },
  "168F": {
   "unit": "game/mainProgram0800",
   "line": "always taken; a zero entry would mean 'no bright colour, use the normal one'"
  },
  "1691": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setTabNormalColour - Redraws console tab X: stores the chosen colour and the tab index into the two self-modified operands, sets the character-block origin to row 1 / tabColumnTable[X], draws the 2x2 tab-box shape from $9804 and then fills those 2x2 screen-RAM colour cells with the tab colour.",
    "In:  X = tab index 0..2; tabColumnTable, tabColourTable/tabHighlightColourTable",
    "Out: D_2A2D = 1 (row), D_2A2E = tab column, D_9221/D_9222 = 2x2 (set by drawCharBlock), four bitmap cells and four screen-RAM colour bytes updated; A/X/Y clobbered; patches the operands at $16AD and $16AF",
    "Called from: mainMenuLoop at $14A1, drawMenuTabBar's loop at $1680, blinkSelectedTab at $16D3, and by falling through from setTabHighlightColour"
   ],
   "line": "normal colour byte of tab X ($05/$06/$02)"
  },
  "1694": {
   "unit": "game/mainProgram0800",
   "line": "self-modifying code: the colour becomes the LDA #imm operand at $16AF"
  },
  "1697": {
   "unit": "game/mainProgram0800",
   "line": "self-modifying code: the tab index becomes the LDX #imm operand at $16AD - dead, see $16AC"
  },
  "169A": {
   "unit": "game/mainProgram0800",
   "line": "tab boxes sit on character row 1"
  },
  "169C": {
   "unit": "game/mainProgram0800",
   "line": "block/fill origin row for drawCharBlock and fillScreenColourRect"
  },
  "169F": {
   "unit": "game/mainProgram0800",
   "line": "screen column of this tab ($0B, $13 or $1B)"
  },
  "16A2": {
   "unit": "game/mainProgram0800",
   "line": "block/fill origin column"
  },
  "16A5": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the tab-box shape"
  },
  "16A7": {
   "unit": "game/mainProgram0800",
   "line": "$9804 = character block: width 2, height 2, glyphs $45,$46,$47,$48"
  },
  "16A9": {
   "unit": "game/mainProgram0800",
   "line": "draw the 2x2 box into the bitmap at (row 1, tab column)"
  },
  "16AC": {
   "unit": "game/mainProgram0800",
   "line": "operand written at $1697 = the tab index; dead code - X is reloaded two instructions later"
  },
  "16AE": {
   "unit": "game/mainProgram0800",
   "line": "operand written at $1694 = the tab colour byte"
  },
  "16B0": {
   "unit": "game/mainProgram0800",
   "line": "fill width: 2 character cells"
  },
  "16B2": {
   "unit": "game/mainProgram0800",
   "line": "fill height: 2 character cells"
  },
  "16B4": {
   "unit": "game/mainProgram0800",
   "line": "paint the 2x2 colour cells and return to the caller"
  },
  "16B7": {
   "unit": "game/mainProgram0800",
   "routine": [
    "blinkSelectedTab - Called once per pass of mainMenuLoop. It does nothing until the IRQ frame countdown zp_6E reaches 0; then it reloads it with 10 frames, flips blinkPhase and repaints the currently selected tab in either its bright or its normal colour, so the open tab blinks about three times a second.",
    "In:  zp_6E menuBlinkTimer, menuTabIndex $90F9 (selected tab), blinkPhase $1889",
    "Out: zp_6E = 10, blinkPhase toggled, the selected tab recoloured",
    "Called from: mainMenuLoop at $143C"
   ],
   "line": "IRQ frame countdown shared by the tab blink and the menu-row blink"
  },
  "16B9": {
   "unit": "game/mainProgram0800",
   "line": "branch when the countdown has expired"
  },
  "16BB": {
   "unit": "game/mainProgram0800",
   "line": "still counting down - leave the tab as it is"
  },
  "16BC": {
   "unit": "game/mainProgram0800",
   "line": "10 frames per blink phase"
  },
  "16BE": {
   "unit": "game/mainProgram0800",
   "line": "restart the countdown (the raster IRQ decrements it)"
  },
  "16C0": {
   "unit": "game/mainProgram0800",
   "line": "$90F9 = tab currently open: 0 STATS, 1 REPAIR, 2 MISC"
  },
  "16C3": {
   "unit": "game/mainProgram0800",
   "line": "branch if a tab is selected"
  },
  "16C5": {
   "unit": "game/mainProgram0800",
   "line": "negative index = no tab open, nothing to blink"
  },
  "16C6": {
   "unit": "game/mainProgram0800",
   "line": "current blink phase, 0 or 1"
  },
  "16C9": {
   "unit": "game/mainProgram0800",
   "line": "flip it"
  },
  "16CB": {
   "unit": "game/mainProgram0800",
   "line": "store the new phase (also read by the marker blink)"
  },
  "16CE": {
   "unit": "game/mainProgram0800",
   "line": "phase 0 - draw the tab in its normal colour"
  },
  "16D0": {
   "unit": "game/mainProgram0800",
   "line": "phase 1 - draw it bright"
  },
  "16D3": {
   "unit": "game/mainProgram0800",
   "line": "tail call: normal colour"
  },
  "16D6": {
   "unit": "game/mainProgram0800",
   "routine": [
    "printEnergyLabel - Prints the fixed word 'ENERGY' of the REPAIR tab at window column $1C, row 8 (absolute column 30 inside the menu panel whose left edge is 2), in the current menu text colour.",
    "In:  none; setMenuTextColour picks $61 or $21 depending on the comcen stun flag",
    "Out: zp_3D, zp_3F, zp_40 set; the label drawn",
    "Called from: drawRepairTab at $17BE"
   ],
   "line": "white text, or white on the red panel while the comcen is stunned"
  },
  "16D9": {
   "unit": "game/mainProgram0800",
   "line": "row 8 of the screen"
  },
  "16DB": {
   "unit": "game/mainProgram0800",
   "line": "absolute cursor row"
  },
  "16DD": {
   "unit": "game/mainProgram0800",
   "line": "column $1C relative to the panel's left edge (2) = screen column 30"
  },
  "16DF": {
   "unit": "game/mainProgram0800",
   "line": "cursor column"
  },
  "16E1": {
   "unit": "game/mainProgram0800",
   "line": "recompute the cell pointers"
  },
  "16E4": {
   "unit": "game/mainProgram0800",
   "line": "low byte of 'ENERGY'"
  },
  "16E6": {
   "unit": "game/mainProgram0800",
   "line": "$CBB8 = 'ENERGY'"
  },
  "16E8": {
   "unit": "game/mainProgram0800",
   "line": "print it and return to drawRepairTab"
  },
  "16EB": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setMenuTextColour - Chooses the text colour byte for the console menu screen: normally $61 (white text, highlight EOR mask 6 matching the blue panel), but $21 while the REPAIR tab is open and our own comcen is stunned, because drawRepairTab then paints the panel red instead of blue.",
    "In:  comcenStunStatus $922A (bit 7 = stunned), menuTabIndex $90F9",
    "Out: zp_3D textColour",
    "Called from: setMenuTextWindow $1575, printEnergyLabel $16D6, printEnergyValue $1702, setRepairMessageWindow $19E4"
   ],
   "line": "default: white text on the blue panel, EOR mask 6 for the row highlight"
  },
  "16ED": {
   "unit": "game/mainProgram0800",
   "line": "$922A bit 7 = our comcen is stunned (bits 0-3 hold the stun counter)"
  },
  "16F0": {
   "unit": "game/mainProgram0800",
   "line": "not stunned - keep $61"
  },
  "16F2": {
   "unit": "game/mainProgram0800",
   "line": "which tab is open?"
  },
  "16F5": {
   "unit": "game/mainProgram0800",
   "line": "tab 1 = REPAIR"
  },
  "16F7": {
   "unit": "game/mainProgram0800",
   "line": "the other tabs keep the blue-panel colour"
  },
  "16F9": {
   "unit": "game/mainProgram0800",
   "line": "REPAIR tab while stunned: white text, EOR mask 2 for the red panel"
  },
  "16FB": {
   "unit": "game/mainProgram0800",
   "line": "store the chosen colour"
  },
  "16FD": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "16FE": {
   "unit": "game/mainProgram0800",
   "block": [
    "lastEnergyShown - the raw comcen energy value (0..50) that printEnergyValue last",
    "drew, so the reading is only redrawn when it actually changes. drawRepairTab",
    "presets it to $FF to force the first print."
   ],
   "line": "on disk 0; drawRepairTab writes $FF to force the first print"
  },
  "16FF": {
   "unit": "game/mainProgram0800",
   "routine": [
    "printEnergyValue - Prints the comcen's energy as a percentage at column $22, row 9 of the REPAIR tab, right-aligned in a three-character field, but only when the value has changed since the last call. It then classifies the same value into comcenSystemStatus[0], the ENERGY entry of the four comcen systems: 0 = OK at 100%, $40 = degraded from 21% up, $80 = critical below that.",
    "In:  comcenUnit $9236 (49 or 99), unitEnergyTable $F9C4 (bits 0-5 = 0..50 energy), lastEnergyShown $16FE",
    "Out: lastEnergyShown updated, comcenSystemStatus $9241 updated, up to three characters drawn; the caller's text window is saved to $92B6 and restored",
    "Called from: mainMenuLoop at $1453 (every pass while the REPAIR tab is open), drawRepairTab at $17C1, drawSystemMarker at $18A0"
   ],
   "block": [
    "Print the percentage, padded with leading blanks so it always occupies three cells."
   ],
   "line": "save the caller's 12-byte text-window state to $92B6"
  },
  "1702": {
   "unit": "game/mainProgram0800",
   "line": "menu text colour ($61, or $21 while stunned)"
  },
  "1705": {
   "unit": "game/mainProgram0800",
   "line": "the reading sits on row 9"
  },
  "1707": {
   "unit": "game/mainProgram0800",
   "line": "absolute cursor row"
  },
  "1709": {
   "unit": "game/mainProgram0800",
   "line": "column $22 relative to the panel left edge (2) = screen column 36"
  },
  "170B": {
   "unit": "game/mainProgram0800",
   "line": "cursor column"
  },
  "170D": {
   "unit": "game/mainProgram0800",
   "line": "recompute the cell pointers"
  },
  "1710": {
   "unit": "game/mainProgram0800",
   "line": "our own comcen: unit 49 (side 0) or 99 (side 1)"
  },
  "1713": {
   "unit": "game/mainProgram0800",
   "line": "energy array of the unit records, $64 apart from $F640"
  },
  "1716": {
   "unit": "game/mainProgram0800",
   "line": "bits 0-5 hold 0..50; the upper bits are other unit flags"
  },
  "1718": {
   "unit": "game/mainProgram0800",
   "line": "compare with the value already on screen"
  },
  "171B": {
   "unit": "game/mainProgram0800",
   "line": "unchanged - skip the (slow) redraw"
  },
  "171D": {
   "unit": "game/mainProgram0800",
   "line": "remember the new raw value"
  },
  "1720": {
   "unit": "game/mainProgram0800",
   "line": "x2 turns 0..50 into a 0..100 percentage"
  },
  "1721": {
   "unit": "game/mainProgram0800",
   "line": "100?"
  },
  "1723": {
   "unit": "game/mainProgram0800",
   "line": "three digits - no padding"
  },
  "1725": {
   "unit": "game/mainProgram0800",
   "line": "keep the percentage"
  },
  "1726": {
   "unit": "game/mainProgram0800",
   "line": "one blank..."
  },
  "1728": {
   "unit": "game/mainProgram0800",
   "line": "...so a two-digit value is right-aligned"
  },
  "172B": {
   "unit": "game/mainProgram0800",
   "line": "get the percentage back"
  },
  "172C": {
   "unit": "game/mainProgram0800",
   "line": "10?"
  },
  "172E": {
   "unit": "game/mainProgram0800",
   "line": "two digits - one blank was enough"
  },
  "1730": {
   "unit": "game/mainProgram0800",
   "line": "keep the percentage"
  },
  "1731": {
   "unit": "game/mainProgram0800",
   "line": "a second blank..."
  },
  "1733": {
   "unit": "game/mainProgram0800",
   "line": "...so a one-digit value is right-aligned too"
  },
  "1736": {
   "unit": "game/mainProgram0800",
   "line": "get the percentage back"
  },
  "1737": {
   "unit": "game/mainProgram0800",
   "line": "printDecimal wants a 24-bit value: middle byte 0 (zp_7E is already 0)"
  },
  "1739": {
   "unit": "game/mainProgram0800",
   "line": "print it without leading zeros"
  },
  "173C": {
   "unit": "game/mainProgram0800",
   "block": [
    "Recompute the ENERGY entry of comcenSystemStatus from the same reading. This runs",
    "even when the printing was skipped, so drawSystemMarker always sees a fresh status."
   ],
   "line": "put the caller's text window back"
  },
  "173F": {
   "unit": "game/mainProgram0800",
   "line": "comcen unit index again"
  },
  "1742": {
   "unit": "game/mainProgram0800",
   "line": "its energy byte"
  },
  "1745": {
   "unit": "game/mainProgram0800",
   "line": "0..50"
  },
  "1747": {
   "unit": "game/mainProgram0800",
   "line": "0..100 percent"
  },
  "1748": {
   "unit": "game/mainProgram0800",
   "line": "assume status 0 = system OK"
  },
  "174A": {
   "unit": "game/mainProgram0800",
   "line": "exactly 100%?"
  },
  "174C": {
   "unit": "game/mainProgram0800",
   "line": "yes - the energy system counts as undamaged"
  },
  "174E": {
   "unit": "game/mainProgram0800",
   "line": "$40 = degraded (yellow marker)"
  },
  "1750": {
   "unit": "game/mainProgram0800",
   "line": "21%?"
  },
  "1752": {
   "unit": "game/mainProgram0800",
   "line": "21% or more - degraded"
  },
  "1754": {
   "unit": "game/mainProgram0800",
   "line": "$80 = critical/damaged (red marker)"
  },
  "1756": {
   "unit": "game/mainProgram0800",
   "line": "store into comcenSystemStatus[0], the ENERGY system"
  },
  "1759": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "175A": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawRepairTab - Builds the whole REPAIR tab: clears the panel in blue ($66) or, if our comcen is stunned, red ($22); unpacks the comcen schematic picture into the bitmap; paints a 20x10 colour block behind it; resets the blink and energy bookkeeping; draws the four system markers (only when not stunned); prints the ENERGY label and value; and, when stunned, prints 'COMCEN STUNNED!'. The stun flag is copied to $1413 so mainMenuLoop can notice a change and redraw.",
    "In:  comcenStunStatus $922A, comcenUnit $9236, unitEnergyTable $F9C4",
    "Out: $1413 = stun flag snapshot, zp_6E = 0, showOkMarkers = 0, blinkPhase = 0 then 1, lastEnergyShown = $FF, comcenEnergySnapshot = current energy, menuSelectedItem $91D5 left at 4 (the (EXIT) row of the REPAIR WHICH menu), the panel redrawn",
    "Called from: mainMenuLoop at $1450 (stun state changed) and $14DD (REPAIR tab selected)"
   ],
   "block": [
    "Clear the panel and draw the comcen schematic with its backing colour block."
   ],
   "line": "assume panel colour $66: blue text/EOR mask 6"
  },
  "175C": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = our comcen is stunned"
  },
  "175F": {
   "unit": "game/mainProgram0800",
   "line": "snapshot for mainMenuLoop's 'has the stun state changed?' test at $1449"
  },
  "1762": {
   "unit": "game/mainProgram0800",
   "line": "not stunned - keep the blue panel"
  },
  "1764": {
   "unit": "game/mainProgram0800",
   "line": "stunned: colour $22, a red panel"
  },
  "1766": {
   "unit": "game/mainProgram0800",
   "line": "set the panel text colour"
  },
  "1768": {
   "unit": "game/mainProgram0800",
   "line": "blank the whole menu window in that colour"
  },
  "176B": {
   "unit": "game/mainProgram0800",
   "line": "$8632 in overlay B: RLE-unpacks the comcen side view into the bitmap at row 10, column 13 (the same address holds unrelated code in overlay A)"
  },
  "176E": {
   "unit": "game/mainProgram0800",
   "line": "colour-block origin column 13"
  },
  "1770": {
   "unit": "game/mainProgram0800",
   "line": "fillScreenColourRect origin column"
  },
  "1773": {
   "unit": "game/mainProgram0800",
   "line": "colour-block origin row 10"
  },
  "1775": {
   "unit": "game/mainProgram0800",
   "line": "fillScreenColourRect origin row"
  },
  "1778": {
   "unit": "game/mainProgram0800",
   "line": "20 cells wide - the width of the schematic"
  },
  "177A": {
   "unit": "game/mainProgram0800",
   "line": "10 cells tall"
  },
  "177C": {
   "unit": "game/mainProgram0800",
   "line": "take the panel colour..."
  },
  "177E": {
   "unit": "game/mainProgram0800",
   "line": "...keep its low nibble (6 blue, or 2 red)..."
  },
  "1780": {
   "unit": "game/mainProgram0800",
   "line": "...and put white (1) in the upper nibble: $16 normally, $12 when stunned"
  },
  "1782": {
   "unit": "game/mainProgram0800",
   "line": "paint the schematic's background colour cells"
  },
  "1785": {
   "unit": "game/mainProgram0800",
   "block": [
    "Reset the blink/energy bookkeeping the marker code depends on."
   ],
   "line": "zero for the three flags below"
  },
  "1787": {
   "unit": "game/mainProgram0800",
   "line": "blink countdown expired, so the next blink happens at once"
  },
  "1789": {
   "unit": "game/mainProgram0800",
   "line": "only the REPAIR WHICH menu wants markers on healthy systems"
  },
  "178C": {
   "unit": "game/mainProgram0800",
   "line": "blink phase 0"
  },
  "178F": {
   "unit": "game/mainProgram0800",
   "line": "sentinel value"
  },
  "1791": {
   "unit": "game/mainProgram0800",
   "line": "force printEnergyValue to actually draw the reading"
  },
  "1794": {
   "unit": "game/mainProgram0800",
   "line": "our comcen's unit index"
  },
  "1797": {
   "unit": "game/mainProgram0800",
   "line": "its energy byte"
  },
  "179A": {
   "unit": "game/mainProgram0800",
   "line": "0..50"
  },
  "179C": {
   "unit": "game/mainProgram0800",
   "line": "remember it; attemptSystemRepair works from this snapshot"
  },
  "179F": {
   "unit": "game/mainProgram0800",
   "block": [
    "Draw the four system markers, unless the comcen is stunned (then it has no",
    "readable status at all). $91D5 doubles as the 3..0 loop counter."
   ],
   "line": "stun flag once more"
  },
  "17A2": {
   "unit": "game/mainProgram0800",
   "line": "stunned - no markers, jump straight to the ENERGY readout"
  },
  "17A4": {
   "unit": "game/mainProgram0800",
   "line": "start at system 3 (DRONE)"
  },
  "17A6": {
   "unit": "game/mainProgram0800",
   "line": "menuSelectedItem is reused as the loop counter here"
  },
  "17A9": {
   "unit": "game/mainProgram0800",
   "line": "draw the marker of system Y"
  },
  "17AC": {
   "unit": "game/mainProgram0800",
   "line": "next system down"
  },
  "17AF": {
   "unit": "game/mainProgram0800",
   "line": "reload the counter into Y (drawSystemMarker clobbers it)"
  },
  "17B2": {
   "unit": "game/mainProgram0800",
   "line": "loop over systems 3,2,1,0"
  },
  "17B4": {
   "unit": "game/mainProgram0800",
   "line": "blink phase 1 = 'marker currently drawn'"
  },
  "17B6": {
   "unit": "game/mainProgram0800",
   "line": "so the first toggle erases it"
  },
  "17B9": {
   "unit": "game/mainProgram0800",
   "line": "menu row 4 of the REPAIR WHICH menu = (EXIT)"
  },
  "17BB": {
   "unit": "game/mainProgram0800",
   "line": "leave the selection on (EXIT)"
  },
  "17BE": {
   "unit": "game/mainProgram0800",
   "block": [
    "ENERGY label and reading, plus the stun banner."
   ],
   "line": "print the word ENERGY at column 30, row 8"
  },
  "17C1": {
   "unit": "game/mainProgram0800",
   "line": "print the percentage at column 36, row 9 and refresh comcenSystemStatus[0]"
  },
  "17C4": {
   "unit": "game/mainProgram0800",
   "line": "stunned?"
  },
  "17C7": {
   "unit": "game/mainProgram0800",
   "line": "no - done"
  },
  "17C9": {
   "unit": "game/mainProgram0800",
   "line": "banner starts at column 5 of the panel (screen column 7)"
  },
  "17CB": {
   "unit": "game/mainProgram0800",
   "line": "cursor column"
  },
  "17CD": {
   "unit": "game/mainProgram0800",
   "line": "recompute the cell pointers (the row is wherever printEnergyValue left it)"
  },
  "17D0": {
   "unit": "game/mainProgram0800",
   "line": "low byte of 'COMCEN STUNNED!'"
  },
  "17D2": {
   "unit": "game/mainProgram0800",
   "line": "$8742 in overlay B (msgComcenStunned)"
  },
  "17D4": {
   "unit": "game/mainProgram0800",
   "line": "print the banner"
  },
  "17D7": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "17D8": {
   "unit": "game/mainProgram0800",
   "block": [
    "comcenEnergySnapshot - the comcen's raw energy (0..50) as of the last REPAIR-tab",
    "redraw or repair attempt. attemptSystemRepair sizes the repair from it and adds",
    "the repaired amount back locally, because the real unitEnergyTable entry only",
    "changes once the $9A command has travelled through the lock-step exchange."
   ],
   "line": "0..50, refreshed by drawRepairTab and attemptSystemRepair"
  },
  "17D9": {
   "unit": "game/mainProgram0800",
   "block": [
    "repairAmount - energy points granted by the last successful ENERGY repair,",
    "computed as (50 - comcenEnergySnapshot)/8 + 1 and sent as the parameter of",
    "command $9A."
   ],
   "line": "parameter of the $9A cmdAddEnergy command"
  },
  "17DA": {
   "unit": "game/mainProgram0800",
   "routine": [
    "repairTabAction - Fire-button handler of the REPAIR tab (entry 1 of menuItemHandlerTable at $1535, reached through the self-modified JSR at $148D). It does nothing unless the DAMAGE option is on, the game is not a film playback and a game is actually running. If the comcen is stunned it runs a single stun-recovery attempt: message, sound, 200 frames of real-time battle, then - only while the stun counter is below 8 - a 1-in-8 chance of queueing command $9E, which clears the stun on both machines. If the comcen is not stunned it halts the comcen if it is still moving or under group orders, blanks the four markers and opens the REPAIR WHICH menu.",
    "In:  gameTypeOptions $0BA3 bit 4 (DAMAGE), filmPlaybackMode $0B9B bit 7, gameEndReason $0BA8, comcenStunStatus $922A, comcenUnit $9236, unitDisplayFlagsTable $FB54, unitColTable/unitRowTable, nextGameRandom",
    "Out: command $9E or $80 queued into the outgoing ring; sounds $12/$13/$18; the battle advanced by runGameFrames; blinkPhase, showOkMarkers, $1412 (3..0 loop counter) and the four markers updated; tail-calls snapshotSetupId so the menu's 'anything changed?' test is re-armed",
    "Called from: mainMenuLoop through menuItemHandlerTable[1] at $148D"
   ],
   "block": [
    "Guards: repairs need the DAMAGE option, a live game and player control."
   ],
   "line": "game settings byte"
  },
  "17DD": {
   "unit": "game/mainProgram0800",
   "line": "bit 4 = the DAMAGE option is on"
  },
  "17DF": {
   "unit": "game/mainProgram0800",
   "line": "no damage model - the REPAIR tab is decoration only"
  },
  "17E1": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = a game film is playing back"
  },
  "17E4": {
   "unit": "game/mainProgram0800",
   "line": "during playback the player may not issue commands"
  },
  "17E6": {
   "unit": "game/mainProgram0800",
   "line": "0 while a game is running"
  },
  "17E9": {
   "unit": "game/mainProgram0800",
   "line": "no game in progress - nothing to repair"
  },
  "17EB": {
   "unit": "game/mainProgram0800",
   "block": [
    "Stunned comcen: one attempt at shaking the stun off."
   ],
   "line": "bit 7 = comcen stunned"
  },
  "17EE": {
   "unit": "game/mainProgram0800",
   "line": "not stunned - go and open the REPAIR WHICH menu"
  },
  "17F0": {
   "unit": "game/mainProgram0800",
   "line": "print 'ATTEMPTING REPAIR...' on the panel's message row"
  },
  "17F3": {
   "unit": "game/mainProgram0800",
   "line": "sound $18 = the repair work noise"
  },
  "17F5": {
   "unit": "game/mainProgram0800",
   "line": "start it"
  },
  "17F8": {
   "unit": "game/mainProgram0800",
   "line": "200 frames..."
  },
  "17FA": {
   "unit": "game/mainProgram0800",
   "line": "...of real-time battle while the crew works"
  },
  "17FD": {
   "unit": "game/mainProgram0800",
   "line": "erase the message row again"
  },
  "1800": {
   "unit": "game/mainProgram0800",
   "line": "assume sound $12 = failure"
  },
  "1802": {
   "unit": "game/mainProgram0800",
   "line": "stun status byte"
  },
  "1805": {
   "unit": "game/mainProgram0800",
   "line": "bits 0-3 = how badly stunned (counts down elsewhere)"
  },
  "1807": {
   "unit": "game/mainProgram0800",
   "line": "8 or more?"
  },
  "1809": {
   "unit": "game/mainProgram0800",
   "line": "too badly stunned - the attempt cannot succeed"
  },
  "180B": {
   "unit": "game/mainProgram0800",
   "line": "shared RNG so both machines and a replayed film agree"
  },
  "180E": {
   "unit": "game/mainProgram0800",
   "line": "one chance in eight"
  },
  "1810": {
   "unit": "game/mainProgram0800",
   "line": "not this time - keep the failure sound"
  },
  "1812": {
   "unit": "game/mainProgram0800",
   "line": "$9E = cmdComcenRepairedOrSetupReady"
  },
  "1814": {
   "unit": "game/mainProgram0800",
   "line": "queue the 1-byte command; both machines clear the stun counter"
  },
  "1817": {
   "unit": "game/mainProgram0800",
   "line": "sound $13 = success"
  },
  "1819": {
   "unit": "game/mainProgram0800",
   "line": "move the chosen sound id into A"
  },
  "181A": {
   "unit": "game/mainProgram0800",
   "line": "play it"
  },
  "181D": {
   "unit": "game/mainProgram0800",
   "line": "20 more frames..."
  },
  "181F": {
   "unit": "game/mainProgram0800",
   "line": "...so the sound is heard before the menu redraws"
  },
  "1822": {
   "unit": "game/mainProgram0800",
   "line": "return to mainMenuLoop"
  },
  "1823": {
   "unit": "game/mainProgram0800",
   "block": [
    "Not stunned: halt the comcen if necessary, blank the markers and open the",
    "'REPAIR WHICH:' menu. A comcen that is driving or following a group order has to",
    "stop before it can be worked on, which is done by ordering it to move to the cell",
    "it already occupies - cmdMoveUnit also drops its group membership."
   ],
   "line": "our comcen's unit index"
  },
  "1826": {
   "unit": "game/mainProgram0800",
   "line": "$FB54: bit 7 = idle/arrived, bit 6 = acting on a group order"
  },
  "1829": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 clear = still moving - it must be halted"
  },
  "182B": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 = following a group order"
  },
  "182D": {
   "unit": "game/mainProgram0800",
   "line": "idle and ungrouped - no halt command needed"
  },
  "182F": {
   "unit": "game/mainProgram0800",
   "line": "command argument 0 = the unit to move"
  },
  "1831": {
   "unit": "game/mainProgram0800",
   "line": "current map column of the comcen"
  },
  "1834": {
   "unit": "game/mainProgram0800",
   "line": "command argument 1 = destination column"
  },
  "1836": {
   "unit": "game/mainProgram0800",
   "line": "current map row"
  },
  "1839": {
   "unit": "game/mainProgram0800",
   "line": "command argument 2 = destination row"
  },
  "183B": {
   "unit": "game/mainProgram0800",
   "line": "$80 = cmdMoveUnit"
  },
  "183D": {
   "unit": "game/mainProgram0800",
   "line": "queue the 4-byte command: 'move to where you already are' = halt and leave the group"
  },
  "1840": {
   "unit": "game/mainProgram0800",
   "line": "blink phase 1..."
  },
  "1842": {
   "unit": "game/mainProgram0800",
   "line": "...so the first toggle in the menu erases the marker"
  },
  "1845": {
   "unit": "game/mainProgram0800",
   "line": "Y = 0"
  },
  "1846": {
   "unit": "game/mainProgram0800",
   "line": "healthy systems stay invisible while the markers are wiped"
  },
  "1849": {
   "unit": "game/mainProgram0800",
   "line": "start at system 3"
  },
  "184B": {
   "unit": "game/mainProgram0800",
   "line": "$1412 is the 3..0 loop counter here (drawRepairTab used $91D5 instead)"
  },
  "184E": {
   "unit": "game/mainProgram0800",
   "line": "colour $16 = the schematic background (white/blue)"
  },
  "1850": {
   "unit": "game/mainProgram0800",
   "line": "blank system Y's 3x3 marker area"
  },
  "1853": {
   "unit": "game/mainProgram0800",
   "line": "next system down"
  },
  "1856": {
   "unit": "game/mainProgram0800",
   "line": "reload the counter into Y"
  },
  "1859": {
   "unit": "game/mainProgram0800",
   "line": "loop over systems 3,2,1,0"
  },
  "185B": {
   "unit": "game/mainProgram0800",
   "line": "now show markers for healthy systems too..."
  },
  "185D": {
   "unit": "game/mainProgram0800",
   "line": "...so the player can pick any of the four in the menu"
  },
  "1860": {
   "unit": "game/mainProgram0800",
   "line": "$652E: draw and run the 'REPAIR WHICH:' menu (ENERGY/BATTLE/RADAR/DRONE/(EXIT))"
  },
  "1863": {
   "unit": "game/mainProgram0800",
   "line": "swallow a pending space key so it is not re-processed"
  },
  "1866": {
   "unit": "game/mainProgram0800",
   "line": "put the menu panel's text window and colour back"
  },
  "1869": {
   "unit": "game/mainProgram0800",
   "line": "back to 'only damaged systems get a marker'"
  },
  "186B": {
   "unit": "game/mainProgram0800",
   "line": "clear the flag"
  },
  "186E": {
   "unit": "game/mainProgram0800",
   "line": "start at system 3 again"
  },
  "1870": {
   "unit": "game/mainProgram0800",
   "line": "reuse $1412 as the counter"
  },
  "1873": {
   "unit": "game/mainProgram0800",
   "line": "redraw system Y's marker with its new status"
  },
  "1876": {
   "unit": "game/mainProgram0800",
   "line": "next system down"
  },
  "1879": {
   "unit": "game/mainProgram0800",
   "line": "reload the counter"
  },
  "187C": {
   "unit": "game/mainProgram0800",
   "line": "loop over systems 3,2,1,0"
  },
  "187E": {
   "unit": "game/mainProgram0800",
   "line": "re-arm selectDefaultMenuItem's 'has anything changed?' snapshot and return"
  },
  "1881": {
   "unit": "game/mainProgram0800",
   "block": [
    "systemMarkerColumnTable - screen column of the marker of each comcen system,",
    "indexed by system number: 0 ENERGY, 1 BATTLE, 2 RADAR, 3 DRONE. The marker is a",
    "plus of five colour cells inside a 3x3 block whose top-left corner is this",
    "column and systemMarkerRowTable[Y]."
   ],
   "line": "ENERGY $0D, BATTLE $10, RADAR $14, DRONE $19"
  },
  "1885": {
   "unit": "game/mainProgram0800",
   "block": [
    "systemMarkerRowTable - screen row of each comcen system marker, same index as",
    "systemMarkerColumnTable. All four sit inside the schematic block drawn at row 10."
   ],
   "line": "ENERGY $0E, BATTLE $0A, RADAR $0A, DRONE $0C"
  },
  "1889": {
   "unit": "game/mainProgram0800",
   "block": [
    "blinkPhase - the shared 0/1 blink phase used by both the console tab blink",
    "(blinkSelectedTab) and the comcen system marker blink (toggleSystemMarker).",
    "0 = the item is currently shown in its 'on' state."
   ],
   "line": "0 or 1"
  },
  "188A": {
   "unit": "game/mainProgram0800",
   "block": [
    "showOkMarkers - when non-zero, drawSystemMarker also draws a marker for a system",
    "whose status is OK (green) instead of erasing its cells. Set only while the",
    "REPAIR WHICH menu is open, so all four systems can be selected."
   ],
   "line": "1 only while the REPAIR WHICH menu is up"
  },
  "188B": {
   "unit": "game/mainProgram0800",
   "routine": [
    "toggleSystemMarker - One blink step for the marker of the comcen system currently highlighted in the REPAIR WHICH menu: flips blinkPhase and then either draws the marker (new phase 0) or erases it with the schematic background colour $16 (new phase 1).",
    "In:  menuSelectedItem $91D5 = system 0..3, blinkPhase $1889",
    "Out: blinkPhase toggled; falls through to drawSystemMarker or jumps to eraseSystemMarker with X = $16",
    "Called from: blinkRepairMarker at $19C1, and toggleMenuRowHighlight at $6108 whenever the menu selector is running in repair mode (selectedUnit $90F8 == $FF) on a row below 4, so the marker blinks in step with the highlighted row"
   ],
   "line": "the highlighted comcen system, 0 ENERGY .. 3 DRONE"
  },
  "188E": {
   "unit": "game/mainProgram0800",
   "line": "$16 = white/blue, the schematic background - used if we are erasing"
  },
  "1890": {
   "unit": "game/mainProgram0800",
   "line": "current blink phase"
  },
  "1893": {
   "unit": "game/mainProgram0800",
   "line": "flip it"
  },
  "1895": {
   "unit": "game/mainProgram0800",
   "line": "store the new phase"
  },
  "1898": {
   "unit": "game/mainProgram0800",
   "line": "new phase 0 - draw the marker (falls straight into drawSystemMarker)"
  },
  "189A": {
   "unit": "game/mainProgram0800",
   "line": "new phase 1 - wipe the 3x3 marker block"
  },
  "189D": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawSystemMarker - Draws the status marker of comcen system Y on the REPAIR tab schematic. The marker is a plus made of five 1x1 colour-cell fills around (systemMarkerRowTable[Y], systemMarkerColumnTable[Y]): white/green $15 when the system is OK, white/red $12 when its status byte has bit 7 set (damaged) and white/yellow $17 otherwise ($40 = degraded). A healthy system is wiped instead of drawn unless showOkMarkers is set. Y has to be stashed in a self-modified operand because the energy refresh in the first line destroys it.",
    "In:  Y = system index 0..3, comcenSystemStatus $9241, showOkMarkers $188A",
    "Out: five (or nine, when erasing) screen-RAM colour cells at $8C00 updated; D_2A2A holds the fill colour, D_2A2D/D_2A2E the running row/column; the operand at $18A4 patched; the ENERGY reading refreshed as a side effect",
    "Called from: drawRepairTab's loop at $17A9, repairTabAction's redraw loop at $1873, and by falling through from toggleSystemMarker"
   ],
   "block": [
    "Pick the marker colour from the system's status byte."
   ],
   "line": "self-modifying code: save Y into the LDY #imm operand at $18A4 - printEnergyValue clobbers Y"
  },
  "18A0": {
   "unit": "game/mainProgram0800",
   "line": "refresh the percentage; this also recomputes comcenSystemStatus[0] for the ENERGY system"
  },
  "18A3": {
   "unit": "game/mainProgram0800",
   "line": "operand written at $189D = the system index"
  },
  "18A5": {
   "unit": "game/mainProgram0800",
   "line": "assume $15 = white/green (system OK)"
  },
  "18A7": {
   "unit": "game/mainProgram0800",
   "line": "$9241+Y: 0 OK, $40 degraded, $80 damaged"
  },
  "18AA": {
   "unit": "game/mainProgram0800",
   "line": "status 0 - keep green"
  },
  "18AC": {
   "unit": "game/mainProgram0800",
   "line": "$12 = white/red (damaged)"
  },
  "18AE": {
   "unit": "game/mainProgram0800",
   "line": "look at the status again"
  },
  "18B1": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 set = damaged - keep red"
  },
  "18B3": {
   "unit": "game/mainProgram0800",
   "line": "$17 = white/yellow (degraded, i.e. $40)"
  },
  "18B5": {
   "unit": "game/mainProgram0800",
   "block": [
    "A healthy system normally shows nothing at all; only the REPAIR WHICH menu wants",
    "green markers so that every system can be picked."
   ],
   "line": "is the colour the 'OK' green?"
  },
  "18B7": {
   "unit": "game/mainProgram0800",
   "line": "no - draw the marker"
  },
  "18B9": {
   "unit": "game/mainProgram0800",
   "line": "are healthy systems supposed to show a marker?"
  },
  "18BC": {
   "unit": "game/mainProgram0800",
   "line": "yes (REPAIR WHICH menu open) - draw it"
  },
  "18BE": {
   "unit": "game/mainProgram0800",
   "line": "$16 = schematic background"
  },
  "18C0": {
   "unit": "game/mainProgram0800",
   "line": "wipe the whole 3x3 block instead and return to the caller"
  },
  "18C3": {
   "unit": "game/mainProgram0800",
   "block": [
    "Paint the plus: (r,c+1), (r+2,c+1), (r+1,c), (r+1,c+1), (r+1,c+2). D_2A2D/D_2A2E",
    "are stepped between the five 1x1 fills; fillScreenColourRect reloads D_2A2A from A,",
    "so the colour is fetched again before each fill."
   ],
   "line": "keep the chosen colour byte in the fill-colour slot"
  },
  "18C6": {
   "unit": "game/mainProgram0800",
   "line": "screen column of this system's marker"
  },
  "18C9": {
   "unit": "game/mainProgram0800",
   "line": "fill origin column"
  },
  "18CC": {
   "unit": "game/mainProgram0800",
   "line": "screen row of this system's marker"
  },
  "18CF": {
   "unit": "game/mainProgram0800",
   "line": "fill origin row"
  },
  "18D2": {
   "unit": "game/mainProgram0800",
   "line": "step right one cell: top arm of the plus"
  },
  "18D5": {
   "unit": "game/mainProgram0800",
   "line": "marker colour"
  },
  "18D8": {
   "unit": "game/mainProgram0800",
   "line": "1 cell wide"
  },
  "18DA": {
   "unit": "game/mainProgram0800",
   "line": "1 cell tall"
  },
  "18DC": {
   "unit": "game/mainProgram0800",
   "line": "paint (row, col+1)"
  },
  "18DF": {
   "unit": "game/mainProgram0800",
   "line": "down one row..."
  },
  "18E2": {
   "unit": "game/mainProgram0800",
   "line": "...and one more: bottom arm"
  },
  "18E5": {
   "unit": "game/mainProgram0800",
   "line": "marker colour"
  },
  "18E8": {
   "unit": "game/mainProgram0800",
   "line": "1 cell wide"
  },
  "18EA": {
   "unit": "game/mainProgram0800",
   "line": "1 cell tall"
  },
  "18EC": {
   "unit": "game/mainProgram0800",
   "line": "paint (row+2, col+1)"
  },
  "18EF": {
   "unit": "game/mainProgram0800",
   "line": "back up to the middle row"
  },
  "18F2": {
   "unit": "game/mainProgram0800",
   "line": "back left to the original column"
  },
  "18F5": {
   "unit": "game/mainProgram0800",
   "line": "marker colour"
  },
  "18F8": {
   "unit": "game/mainProgram0800",
   "line": "1 cell wide"
  },
  "18FA": {
   "unit": "game/mainProgram0800",
   "line": "1 cell tall"
  },
  "18FC": {
   "unit": "game/mainProgram0800",
   "line": "paint (row+1, col) - left arm"
  },
  "18FF": {
   "unit": "game/mainProgram0800",
   "line": "step right"
  },
  "1902": {
   "unit": "game/mainProgram0800",
   "line": "marker colour"
  },
  "1905": {
   "unit": "game/mainProgram0800",
   "line": "1 cell wide"
  },
  "1907": {
   "unit": "game/mainProgram0800",
   "line": "1 cell tall"
  },
  "1909": {
   "unit": "game/mainProgram0800",
   "line": "paint (row+1, col+1) - centre"
  },
  "190C": {
   "unit": "game/mainProgram0800",
   "line": "step right again"
  },
  "190F": {
   "unit": "game/mainProgram0800",
   "line": "marker colour"
  },
  "1912": {
   "unit": "game/mainProgram0800",
   "line": "1 cell wide"
  },
  "1914": {
   "unit": "game/mainProgram0800",
   "line": "1 cell tall"
  },
  "1916": {
   "unit": "game/mainProgram0800",
   "line": "paint (row+1, col+2) - right arm, and return to the caller"
  },
  "1919": {
   "unit": "game/mainProgram0800",
   "routine": [
    "eraseSystemMarker - Fills the whole 3x3 colour-cell block of system Y's marker with the colour byte in X. Every caller passes $16, the white/blue background the schematic is drawn on, so this blanks the marker.",
    "In:  Y = system index 0..3, X = colour byte (always $16)",
    "Out: D_2A2D/D_2A2E set to the marker origin, nine screen-RAM colour bytes overwritten",
    "Called from: repairTabAction's wipe loop at $1850, toggleSystemMarker at $189A (blink off), drawSystemMarker at $18C0 (healthy system with showOkMarkers clear)"
   ],
   "line": "screen column of this system's marker"
  },
  "191C": {
   "unit": "game/mainProgram0800",
   "line": "fill origin column"
  },
  "191F": {
   "unit": "game/mainProgram0800",
   "line": "screen row of this system's marker"
  },
  "1922": {
   "unit": "game/mainProgram0800",
   "line": "fill origin row"
  },
  "1925": {
   "unit": "game/mainProgram0800",
   "line": "the colour byte arrives in X, fillScreenColourRect wants it in A"
  },
  "1926": {
   "unit": "game/mainProgram0800",
   "line": "3 cells wide"
  },
  "1928": {
   "unit": "game/mainProgram0800",
   "line": "3 cells tall - covers the whole plus"
  },
  "192A": {
   "unit": "game/mainProgram0800",
   "line": "paint the block and return to the caller"
  },
  "192D": {
   "unit": "game/mainProgram0800",
   "routine": [
    "sendEnergyRepairCommand - Queues the 3-byte command $9A (cmdAddEnergy) with the comcen's unit index and the repaired amount, so that both machines add the same energy to that unit. This is the only part of a comcen repair that travels over the link - the status of systems 1-3 is purely local cosmetics.",
    "In:  comcenUnit $9236, repairAmount $17D9",
    "Out: zp_74 = unit, zp_75 = amount, the command written into the outgoing ring and pendingOwnCmdCount $929F bumped",
    "Called from: attemptSystemRepair at $196C"
   ],
   "line": "our comcen (unit 49 or 99)"
  },
  "1930": {
   "unit": "game/mainProgram0800",
   "line": "command argument 1 = unit index"
  },
  "1932": {
   "unit": "game/mainProgram0800",
   "line": "energy points won by this repair"
  },
  "1935": {
   "unit": "game/mainProgram0800",
   "line": "command argument 2 = amount"
  },
  "1937": {
   "unit": "game/mainProgram0800",
   "line": "$9A = cmdAddEnergy"
  },
  "1939": {
   "unit": "game/mainProgram0800",
   "line": "queue the 3-byte command and return to the caller"
  },
  "193C": {
   "unit": "game/mainProgram0800",
   "routine": [
    "attemptSystemRepair - Resolves one repair attempt on the comcen system highlighted in the REPAIR WHICH menu. It shows the message, plays the work sound and rolls the shared RNG: the attempt succeeds with probability 1/2 for the ENERGY system and 1/4 for the other three, and only if the system is not already OK. For ENERGY it computes (50 - snapshot)/8 + 1 points, sends them with command $9A and updates the snapshot (capped at 50); for the others it just clears the status byte. Success plays sound $13, failure $12, each surrounded by about a second of marker blinking during which the real-time battle keeps running.",
    "In:  menuSelectedItem $91D5 = system 0..3, comcenSystemStatus $9241, comcenEnergySnapshot $17D8, comcenUnit $9236, unitEnergyTable $F9C4, nextGameRandom",
    "Out: repairAmount, comcenEnergySnapshot, comcenSystemStatus[Y], one $9A command, sounds $12/$13/$18, zp_6E = 0, blinkPhase = 0 and the message row cleared",
    "Called from: the REPAIR WHICH menu loop at $6590 (skipped in solo/film mode)"
   ],
   "block": [
    "Announce the attempt and roll for success."
   ],
   "line": "print 'ATTEMPTING REPAIR...'"
  },
  "193F": {
   "unit": "game/mainProgram0800",
   "line": "sound $18 = repair work noise"
  },
  "1941": {
   "unit": "game/mainProgram0800",
   "line": "start it"
  },
  "1944": {
   "unit": "game/mainProgram0800",
   "line": "the system the player picked: 0 ENERGY, 1 BATTLE, 2 RADAR, 3 DRONE"
  },
  "1947": {
   "unit": "game/mainProgram0800",
   "line": "shared RNG - both machines and a replayed film draw the same number"
  },
  "194A": {
   "unit": "game/mainProgram0800",
   "line": "the ENERGY system?"
  },
  "194C": {
   "unit": "game/mainProgram0800",
   "line": "systems 1-3 keep both random bits"
  },
  "194E": {
   "unit": "game/mainProgram0800",
   "line": "ENERGY: one bit only, so a 1-in-2 chance"
  },
  "1950": {
   "unit": "game/mainProgram0800",
   "line": "1-in-4 for systems 1-3 (already narrowed to 1-in-2 above)"
  },
  "1952": {
   "unit": "game/mainProgram0800",
   "line": "non-zero - the attempt failed"
  },
  "1954": {
   "unit": "game/mainProgram0800",
   "line": "status of the chosen system"
  },
  "1957": {
   "unit": "game/mainProgram0800",
   "line": "already OK - treat it as a failure, there is nothing to repair"
  },
  "1959": {
   "unit": "game/mainProgram0800",
   "block": [
    "ENERGY: work out how much energy the crew restores and tell the opponent."
   ],
   "line": "the ENERGY system?"
  },
  "195B": {
   "unit": "game/mainProgram0800",
   "line": "systems 1-3 just get their status cleared"
  },
  "195D": {
   "unit": "game/mainProgram0800",
   "line": "50 = a full comcen energy bar"
  },
  "195F": {
   "unit": "game/mainProgram0800",
   "line": "prepare the subtraction"
  },
  "1960": {
   "unit": "game/mainProgram0800",
   "line": "how much energy is missing"
  },
  "1963": {
   "unit": "game/mainProgram0800",
   "line": "divide by 8..."
  },
  "1964": {
   "unit": "game/mainProgram0800",
   "line": "..."
  },
  "1965": {
   "unit": "game/mainProgram0800",
   "line": "...so a badly hurt comcen recovers faster"
  },
  "1966": {
   "unit": "game/mainProgram0800",
   "line": "prepare the add"
  },
  "1967": {
   "unit": "game/mainProgram0800",
   "line": "always at least one point"
  },
  "1969": {
   "unit": "game/mainProgram0800",
   "line": "remember what we granted"
  },
  "196C": {
   "unit": "game/mainProgram0800",
   "line": "queue command $9A so both machines credit the energy"
  },
  "196F": {
   "unit": "game/mainProgram0800",
   "line": "prepare the add"
  },
  "1970": {
   "unit": "game/mainProgram0800",
   "line": "energy before the repair"
  },
  "1973": {
   "unit": "game/mainProgram0800",
   "line": "plus the repaired amount"
  },
  "1976": {
   "unit": "game/mainProgram0800",
   "line": "51 or more?"
  },
  "1978": {
   "unit": "game/mainProgram0800",
   "line": "still below the maximum"
  },
  "197A": {
   "unit": "game/mainProgram0800",
   "line": "clamp at 50"
  },
  "197C": {
   "unit": "game/mainProgram0800",
   "line": "update the local snapshot (the real table only changes when $9A executes)"
  },
  "197F": {
   "unit": "game/mainProgram0800",
   "block": [
    "Success: blink, clear the status, chime. Failure joins at $1990."
   ],
   "line": "about a second of marker blinking while the battle runs on"
  },
  "1982": {
   "unit": "game/mainProgram0800",
   "line": "the chosen system again"
  },
  "1985": {
   "unit": "game/mainProgram0800",
   "line": "ENERGY - its status is recomputed by printEnergyValue, not stored here"
  },
  "1987": {
   "unit": "game/mainProgram0800",
   "line": "0 = system OK"
  },
  "1989": {
   "unit": "game/mainProgram0800",
   "line": "mark BATTLE/RADAR/DRONE repaired"
  },
  "198C": {
   "unit": "game/mainProgram0800",
   "line": "sound $13 = repair succeeded"
  },
  "198E": {
   "unit": "game/mainProgram0800",
   "line": "always taken"
  },
  "1990": {
   "unit": "game/mainProgram0800",
   "line": "failure: the same blink pause so both outcomes take the same time"
  },
  "1993": {
   "unit": "game/mainProgram0800",
   "line": "sound $12 = repair failed"
  },
  "1995": {
   "unit": "game/mainProgram0800",
   "line": "play the outcome sound"
  },
  "1998": {
   "unit": "game/mainProgram0800",
   "line": "another second of blinking while it sounds"
  },
  "199B": {
   "unit": "game/mainProgram0800",
   "block": [
    "Re-read the real energy (the $9A command may have been applied in the meantime)",
    "and clean up the blink state and the message row."
   ],
   "line": "our comcen"
  },
  "199E": {
   "unit": "game/mainProgram0800",
   "line": "its energy byte"
  },
  "19A1": {
   "unit": "game/mainProgram0800",
   "line": "0..50"
  },
  "19A3": {
   "unit": "game/mainProgram0800",
   "line": "refresh the snapshot from the live table"
  },
  "19A6": {
   "unit": "game/mainProgram0800",
   "line": "zero..."
  },
  "19A8": {
   "unit": "game/mainProgram0800",
   "line": "...so the menu-row blink fires again on the next pass"
  },
  "19AA": {
   "unit": "game/mainProgram0800",
   "line": "...and the marker is left in the 'drawn' phase"
  },
  "19AD": {
   "unit": "game/mainProgram0800",
   "line": "erase 'ATTEMPTING REPAIR...' and return to the menu loop"
  },
  "19B0": {
   "unit": "game/mainProgram0800",
   "routine": [
    "blinkRepairMarker - Blinks the marker of the selected comcen system for about one second while a repair attempt resolves. It arms the IRQ frame countdown zp_6F with 60 and toggles the marker every 20 game frames; runGameFrames is used rather than a plain delay so that the real-time battle (and the lock-step command exchange) keeps running. It bails out at once if the game has ended.",
    "In:  menuSelectedItem $91D5, gameEndCountdown $92C9",
    "Out: zp_6F counted down by the raster IRQ, blinkPhase toggled several times, the marker left in whichever phase it ended on, the battle advanced",
    "Called from: attemptSystemRepair at $197F, $1990 and $1998"
   ],
   "line": "60 frames, about one second"
  },
  "19B2": {
   "unit": "game/mainProgram0800",
   "line": "the raster IRQ counts zp_6F down"
  },
  "19B4": {
   "unit": "game/mainProgram0800",
   "line": "20 frames..."
  },
  "19B6": {
   "unit": "game/mainProgram0800",
   "line": "...of real-time battle between toggles"
  },
  "19B9": {
   "unit": "game/mainProgram0800",
   "line": "non-zero once the game is over"
  },
  "19BC": {
   "unit": "game/mainProgram0800",
   "line": "the game ended while we blinked - stop immediately"
  },
  "19BE": {
   "unit": "game/mainProgram0800",
   "line": "the highlighted comcen system"
  },
  "19C1": {
   "unit": "game/mainProgram0800",
   "line": "flip the marker on or off"
  },
  "19C4": {
   "unit": "game/mainProgram0800",
   "line": "has the one-second countdown expired?"
  },
  "19C6": {
   "unit": "game/mainProgram0800",
   "line": "no - keep blinking"
  },
  "19C8": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "19C9": {
   "unit": "game/mainProgram0800",
   "routine": [
    "printAttemptingRepair - Positions the REPAIR panel's own message row (window column 10, screen column 12, screen row 20) and prints 'ATTEMPTING REPAIR...' there, then restores the text window the caller had.",
    "In:  none",
    "Out: 20 characters drawn on the panel's message row; the text window restored from the single save slot $92B6",
    "Called from: repairTabAction at $17F0 and attemptSystemRepair at $193C"
   ],
   "line": "save the caller's window and move to column 10, row 20"
  },
  "19CC": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the message"
  },
  "19CE": {
   "unit": "game/mainProgram0800",
   "line": "$CBE5 = 'ATTEMPTING REPAIR...'"
  },
  "19D0": {
   "unit": "game/mainProgram0800",
   "line": "print it"
  },
  "19D3": {
   "unit": "game/mainProgram0800",
   "line": "put the caller's text window back and return"
  },
  "19D6": {
   "unit": "game/mainProgram0800",
   "routine": [
    "clearRepairMessage - Erases the REPAIR panel's message row by printing 20 copies of the blank glyph zp_3E over it, then restores the caller's text window. (The routine it calls is the $C000 overlay's printFillChars at $C0EF; the listing's bootReceiveSectorData label is a stale leftover from the boot loader that used to live at $C000.)",
    "In:  zp_3E fill glyph",
    "Out: 20 cells of the panel's message row blanked; the text window restored from $92B6",
    "Called from: repairTabAction at $17FD and attemptSystemRepair at $19AD"
   ],
   "line": "same window and cursor position as printAttemptingRepair"
  },
  "19D9": {
   "unit": "game/mainProgram0800",
   "line": "20 characters - the length of 'ATTEMPTING REPAIR...'"
  },
  "19DB": {
   "unit": "game/mainProgram0800",
   "line": "printFillChars ($C0EF): print the blank glyph that many times"
  },
  "19DE": {
   "unit": "game/mainProgram0800",
   "line": "put the caller's text window back and return"
  },
  "19E1": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setRepairMessageWindow - Shared prologue of printAttemptingRepair and clearRepairMessage: saves the caller's 12-byte text state, picks the menu text colour, applies the menu panel geometry (left column 2, width $24, rows 7..$16) and places the cursor at window column 10 = screen column 12 of screen row 20.",
    "In:  comcenStunStatus and menuTabIndex through setMenuTextColour",
    "Out: $92B6-$92C1 = saved window, zp_39-zp_40 = menu window, zp_3D = $61 or $21, cursor pointers recomputed",
    "Called from: printAttemptingRepair $19C9, clearRepairMessage $19D6"
   ],
   "line": "copy zp_39-zp_44 to $92B6 (there is only one save slot, so this cannot nest)"
  },
  "19E4": {
   "unit": "game/mainProgram0800",
   "line": "$61, or $21 while the stunned-comcen panel is red"
  },
  "19E7": {
   "unit": "game/mainProgram0800",
   "line": "menu panel window: left column 2, width $24, rows 7..$16"
  },
  "19EA": {
   "unit": "game/mainProgram0800",
   "line": "column 10 of the window (screen column 12)"
  },
  "19EC": {
   "unit": "game/mainProgram0800",
   "line": "cursor column"
  },
  "19EE": {
   "unit": "game/mainProgram0800",
   "line": "screen row 20 - the panel's message row"
  },
  "19F0": {
   "unit": "game/mainProgram0800",
   "line": "set the row, recompute the pointers and return to the caller"
  },
  "19F3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "printOptionsList - Prints the body of the MISC tab. While a game is running it lists the in-game options ABORT GAME, VOICE PAUSE (only for a modem game that is not a film playback) and ' (EXIT)'. Otherwise it prints the five main-menu lines COMPETE WITH MODEM OPPONENT / PRACTICE WITH SOLO TRAINER / WATCH GAME FILM / SAVE GAME FILM / LOAD GAME FILM, plus DISCONNECT/SET VOICE when the comm module reports the link is up.",
    "In:  gameEndReason $0BA8, gameEndCountdown $92C9, filmPlaybackMode $0B9B, isSoloTrainer $0BA5, link-active flag $E03B",
    "Out: the item text drawn from row 9 down; zp_39 = 9, zp_3B = 6, zp_3C = $1D, zp_3D = $61, zp_3F = 0, zp_40 left after the last line",
    "Called from: mainMenuLoop at $14EA, miscTabAction at $1A53, $1A83 and $1AD0"
   ],
   "block": [
    "Set up the option-list window and print the '   OPTIONS' heading."
   ],
   "line": "items start at screen column 6"
  },
  "19F5": {
   "unit": "game/mainProgram0800",
   "line": "window left edge"
  },
  "19F7": {
   "unit": "game/mainProgram0800",
   "line": "first row of the list is screen row 9"
  },
  "19F9": {
   "unit": "game/mainProgram0800",
   "line": "window top"
  },
  "19FB": {
   "unit": "game/mainProgram0800",
   "line": "and the cursor row"
  },
  "19FD": {
   "unit": "game/mainProgram0800",
   "line": "29 columns wide - also the width of the menu highlight bar"
  },
  "19FF": {
   "unit": "game/mainProgram0800",
   "line": "window width"
  },
  "1A01": {
   "unit": "game/mainProgram0800",
   "line": "column 0 of the window"
  },
  "1A03": {
   "unit": "game/mainProgram0800",
   "line": "cursor column"
  },
  "1A05": {
   "unit": "game/mainProgram0800",
   "line": "recompute the cell pointers"
  },
  "1A08": {
   "unit": "game/mainProgram0800",
   "line": "white text, EOR mask 6 for the highlight bar"
  },
  "1A0A": {
   "unit": "game/mainProgram0800",
   "line": "set the colour"
  },
  "1A0C": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the heading"
  },
  "1A0E": {
   "unit": "game/mainProgram0800",
   "line": "$CB13 = '   OPTIONS' followed by a CR"
  },
  "1A10": {
   "unit": "game/mainProgram0800",
   "line": "print it"
  },
  "1A13": {
   "unit": "game/mainProgram0800",
   "block": [
    "Decide which list to print."
   ],
   "line": "0 while a game is running"
  },
  "1A16": {
   "unit": "game/mainProgram0800",
   "line": "non-zero while the end-of-game sequence runs (endGame sets it to $78 and the IRQ counts it up to $80)"
  },
  "1A19": {
   "unit": "game/mainProgram0800",
   "line": "no live game - print the main menu instead"
  },
  "1A1B": {
   "unit": "game/mainProgram0800",
   "block": [
    "In-game options."
   ],
   "line": "low byte of 'ABORT GAME'"
  },
  "1A1D": {
   "unit": "game/mainProgram0800",
   "line": "$CCA6 = 'ABORT GAME' + CR"
  },
  "1A1F": {
   "unit": "game/mainProgram0800",
   "line": "print it; the pointer is left at $CCB1 = 'VOICE PAUSE'"
  },
  "1A22": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = film playback"
  },
  "1A25": {
   "unit": "game/mainProgram0800",
   "line": "you cannot ring your opponent during a replay"
  },
  "1A27": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = solo trainer game"
  },
  "1A2A": {
   "unit": "game/mainProgram0800",
   "line": "the trainer has no phone - skip VOICE PAUSE"
  },
  "1A2C": {
   "unit": "game/mainProgram0800",
   "line": "print the next string in the block: 'VOICE PAUSE' + CR"
  },
  "1A2F": {
   "unit": "game/mainProgram0800",
   "line": "low byte of ' (EXIT)'"
  },
  "1A31": {
   "unit": "game/mainProgram0800",
   "line": "$CAB2 = ' (EXIT)', the tail of the formation-menu string block"
  },
  "1A33": {
   "unit": "game/mainProgram0800",
   "line": "print the last item and return"
  },
  "1A36": {
   "unit": "game/mainProgram0800",
   "block": [
    "Main menu (no game running)."
   ],
   "line": "low byte of mainMenuItemText"
  },
  "1A38": {
   "unit": "game/mainProgram0800",
   "line": "$CC1F = four CR-separated lines: COMPETE WITH MODEM OPPONENT / PRACTICE WITH SOLO TRAINER / WATCH GAME FILM / SAVE GAME FILM"
  },
  "1A3A": {
   "unit": "game/mainProgram0800",
   "line": "print all four; the pointer is left at $CC75"
  },
  "1A3D": {
   "unit": "game/mainProgram0800",
   "line": "print the next string: 'LOAD GAME FILM '"
  },
  "1A40": {
   "unit": "game/mainProgram0800",
   "line": "comm module's link-active flag"
  },
  "1A43": {
   "unit": "game/mainProgram0800",
   "line": "no link - the menu has five items"
  },
  "1A45": {
   "unit": "game/mainProgram0800",
   "line": "link up: print 'DISCONNECT/SET VOICE ' as item 5"
  },
  "1A48": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "1A49": {
   "unit": "game/mainProgram0800",
   "routine": [
    "miscTabAction - Fire-button handler of the MISC tab (entry 2 of menuItemHandlerTable at $1535) and, through the JMP at $0AC5, the top of the whole main-menu loop at cold start. With a game running it runs the in-game options menu; picking ABORT GAME tells a connected opponent with command $8C reason 0, waits up to $26 ticks for the exchange to drain, hangs up and restores the status message. It then falls into an endless loop that clears the panel, undoes any film-playback settings, reprints the option list and runs the main menu.",
    "In:  gameEndCountdown $92C9, gameEndReason $0BA8, filmPlaybackMode $0B9B, isSoloTrainer $0BA5, link flags $E03B/$E030, linkSessionState $0B9C, persistentMessageId $92A1",
    "Out: menu state $91D4/$91D5/$91D6, command $8C queued, linkPollDelay $9163 = 0, zp_7F timeout, the operand at $1AC0 patched with the saved message id; may end the session through endGameToMainMenu. Never returns while no game is running - runMainMenu leaves through handleMainMenuChoice instead",
    "Called from: mainMenuLoop through menuItemHandlerTable[2] at $148D, and JMP $0AC5 (cold start into the main menu)"
   ],
   "block": [
    "Run the in-game options menu, if there is a game to have options about."
   ],
   "line": "non-zero during the countdown after a game has just ended"
  },
  "1A4C": {
   "unit": "game/mainProgram0800",
   "line": "the game is over - go straight to the session teardown"
  },
  "1A4E": {
   "unit": "game/mainProgram0800",
   "line": "0 while a game is running"
  },
  "1A51": {
   "unit": "game/mainProgram0800",
   "line": "no game at all - go straight to the main-menu loop"
  },
  "1A53": {
   "unit": "game/mainProgram0800",
   "line": "print ABORT GAME / [VOICE PAUSE] / (EXIT)"
  },
  "1A56": {
   "unit": "game/mainProgram0800",
   "line": "assume two items"
  },
  "1A58": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = film playback"
  },
  "1A5B": {
   "unit": "game/mainProgram0800",
   "line": "no VOICE PAUSE during a replay"
  },
  "1A5D": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = solo trainer"
  },
  "1A60": {
   "unit": "game/mainProgram0800",
   "line": "no VOICE PAUSE against the trainer"
  },
  "1A62": {
   "unit": "game/mainProgram0800",
   "line": "modem game: three items"
  },
  "1A63": {
   "unit": "game/mainProgram0800",
   "line": "tell the menu selector how many rows there are"
  },
  "1A66": {
   "unit": "game/mainProgram0800",
   "line": "index of the last item"
  },
  "1A67": {
   "unit": "game/mainProgram0800",
   "line": "preselect (EXIT)"
  },
  "1A6A": {
   "unit": "game/mainProgram0800",
   "line": "shift the window left edge to 5 so the highlight bar starts one column left of the text"
  },
  "1A6C": {
   "unit": "game/mainProgram0800",
   "line": "the new left edge..."
  },
  "1A6E": {
   "unit": "game/mainProgram0800",
   "line": "...also used as the cursor column; toggleMenuRowHighlight forces column 0 anyway, so this looks vestigial"
  },
  "1A70": {
   "unit": "game/mainProgram0800",
   "line": "the first menu row is screen row 11"
  },
  "1A72": {
   "unit": "game/mainProgram0800",
   "line": "the selector adds the selection index to this"
  },
  "1A75": {
   "unit": "game/mainProgram0800",
   "line": "put the cursor on that row"
  },
  "1A78": {
   "unit": "game/mainProgram0800",
   "line": "run the selector; it handles VOICE PAUSE itself and returns Y = chosen item"
  },
  "1A7B": {
   "unit": "game/mainProgram0800",
   "line": "which item was picked?"
  },
  "1A7E": {
   "unit": "game/mainProgram0800",
   "line": "item 0 = ABORT GAME"
  },
  "1A80": {
   "unit": "game/mainProgram0800",
   "line": "(EXIT): restore the menu window and colour..."
  },
  "1A83": {
   "unit": "game/mainProgram0800",
   "line": "...reprint the list and return to mainMenuLoop"
  },
  "1A86": {
   "unit": "game/mainProgram0800",
   "block": [
    "ABORT GAME (also the cold-start entry from $0AC5). Tell the opponent, drain the",
    "link and hang up, then fall through into the session teardown."
   ],
   "line": "the message currently pinned on the status line"
  },
  "1A89": {
   "unit": "game/mainProgram0800",
   "line": "self-modifying code: it becomes the LDA #imm operand at $1AC0, so it can be restored at $1ABF"
  },
  "1A8C": {
   "unit": "game/mainProgram0800",
   "line": "is the comm link up?"
  },
  "1A8F": {
   "unit": "game/mainProgram0800",
   "line": "no link - nothing to tell anybody"
  },
  "1A91": {
   "unit": "game/mainProgram0800",
   "line": "0 none, 2 game session agreed, $80 link up at menu level, $FF link lost"
  },
  "1A94": {
   "unit": "game/mainProgram0800",
   "line": "$80/$FF: no game session to abort"
  },
  "1A96": {
   "unit": "game/mainProgram0800",
   "line": "0: no session at all - just hang up"
  },
  "1A98": {
   "unit": "game/mainProgram0800",
   "line": "reason 0 = 'I am leaving the game'"
  },
  "1A9A": {
   "unit": "game/mainProgram0800",
   "line": "command argument 0"
  },
  "1A9C": {
   "unit": "game/mainProgram0800",
   "line": "clear the exchange throttle so the packet goes out at full speed"
  },
  "1A9F": {
   "unit": "game/mainProgram0800",
   "line": "$E030 = comm build id: $00 modem driver, $FF solo trainer"
  },
  "1AA2": {
   "unit": "game/mainProgram0800",
   "line": "the trainer discards everything we send - skip the handshake"
  },
  "1AA4": {
   "unit": "game/mainProgram0800",
   "line": "put 'WORKING...' on the status line"
  },
  "1AA7": {
   "unit": "game/mainProgram0800",
   "line": "$8C = cmdSessionControl"
  },
  "1AA9": {
   "unit": "game/mainProgram0800",
   "line": "queue the 2-byte command (code + reason)"
  },
  "1AAC": {
   "unit": "game/mainProgram0800",
   "line": "$26 ticks..."
  },
  "1AAE": {
   "unit": "game/mainProgram0800",
   "line": "...as the timeout (zp_7F is decremented every 8th frame by the IRQ)"
  },
  "1AB0": {
   "unit": "game/mainProgram0800",
   "line": "keep the lock-step exchange running so the packet is actually sent"
  },
  "1AB3": {
   "unit": "game/mainProgram0800",
   "line": "$E01D = the comm module's exchange flag byte"
  },
  "1AB6": {
   "unit": "game/mainProgram0800",
   "line": "all flags clear - nothing is in flight any more"
  },
  "1AB8": {
   "unit": "game/mainProgram0800",
   "line": "has the timeout expired?"
  },
  "1ABA": {
   "unit": "game/mainProgram0800",
   "line": "no - keep servicing the link"
  },
  "1ABC": {
   "unit": "game/mainProgram0800",
   "line": "drop the line (prompting the player to pick up the handset first)"
  },
  "1ABF": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $1A89 = the message id that was on the status line"
  },
  "1AC1": {
   "unit": "game/mainProgram0800",
   "line": "pin it again"
  },
  "1AC4": {
   "unit": "game/mainProgram0800",
   "line": "and redisplay it"
  },
  "1AC7": {
   "unit": "game/mainProgram0800",
   "block": [
    "Session teardown and the main-menu loop. runMainMenu normally never comes back:",
    "it jumps into handleMainMenuChoice in the $6F00 overlay."
   ],
   "line": "reset all the per-game state (also entered directly when the game just ended)"
  },
  "1ACA": {
   "unit": "game/mainProgram0800",
   "line": "clear the menu panel"
  },
  "1ACD": {
   "unit": "game/mainProgram0800",
   "line": "if we were watching a film, put the real game settings back"
  },
  "1AD0": {
   "unit": "game/mainProgram0800",
   "line": "print the five- or six-item main menu"
  },
  "1AD3": {
   "unit": "game/mainProgram0800",
   "line": "run it; only DISCONNECT/SET VOICE returns here"
  },
  "1AD6": {
   "unit": "game/mainProgram0800",
   "line": "clear the panel again"
  },
  "1AD9": {
   "unit": "game/mainProgram0800",
   "line": "and start over"
  },
  "1ADC": {
   "unit": "game/mainProgram0800",
   "routine": [
    "endGameToMainMenu - Tears a finished game session down and prepares the main menu. It clears the end-of-game countdown and the pause state, empties the outgoing command ring, silences the sound unless a win/lose jingle is still playing, marks 'no game in progress', writes the game film out when the battle really ended, chooses a sensible default main-menu item, and leaves the link either flushed and up ($0B9C = $80) or down (0).",
    "In:  sndVoiceSoundId $67C8, comm build id $E030, link-active flag $E03B, gamePhase zp_B1, filmPlaybackMode $0B9B, linkSessionState $0B9C, gameEndReason $0BA8",
    "Out: $92C9 = 0, zp_BE = 0, $908D/$908E = 0, defaultMainMenuItem $92FD, gameEndReason $0BA8 >= 1, film header written, $0B9C = 0 or $80",
    "Called from: miscTabAction at $1AC7 and by JMP from the $C000 overlay at $C879 when $92C9 goes negative"
   ],
   "block": [
    "Clear the per-game state."
   ],
   "line": "zero for the five stores below"
  },
  "1ADE": {
   "unit": "game/mainProgram0800",
   "line": "cancel the end-of-game countdown"
  },
  "1AE1": {
   "unit": "game/mainProgram0800",
   "line": "clear own/opponent pause and voice-pause bits"
  },
  "1AE3": {
   "unit": "game/mainProgram0800",
   "line": "empty the outgoing command ring (write index)"
  },
  "1AE6": {
   "unit": "game/mainProgram0800",
   "line": "and its read index"
  },
  "1AE9": {
   "unit": "game/mainProgram0800",
   "line": "default main-menu item 0 = COMPETE WITH MODEM OPPONENT"
  },
  "1AEC": {
   "unit": "game/mainProgram0800",
   "line": "sound id currently playing on the 'voice' channel"
  },
  "1AEF": {
   "unit": "game/mainProgram0800",
   "line": "$0D = the 'we won' fanfare"
  },
  "1AF1": {
   "unit": "game/mainProgram0800",
   "line": "let it finish"
  },
  "1AF3": {
   "unit": "game/mainProgram0800",
   "line": "$0E = the 'we lost' theme"
  },
  "1AF5": {
   "unit": "game/mainProgram0800",
   "line": "let that finish too"
  },
  "1AF7": {
   "unit": "game/mainProgram0800",
   "line": "otherwise silence all three SID voices"
  },
  "1AFA": {
   "unit": "game/mainProgram0800",
   "block": [
    "Pick the default main-menu item and finalise the film."
   ],
   "line": "$E030: $FF = the solo trainer module is behind the $E000 jump table"
  },
  "1AFD": {
   "unit": "game/mainProgram0800",
   "line": "modem build - the link flag means something"
  },
  "1AFF": {
   "unit": "game/mainProgram0800",
   "line": "a trainer game never has a real link..."
  },
  "1B01": {
   "unit": "game/mainProgram0800",
   "line": "...so force the link-active flag down"
  },
  "1B04": {
   "unit": "game/mainProgram0800",
   "line": "default item 1 = PRACTICE WITH SOLO TRAINER"
  },
  "1B07": {
   "unit": "game/mainProgram0800",
   "line": "0 while a game was running"
  },
  "1B0A": {
   "unit": "game/mainProgram0800",
   "line": "already non-zero - leave the real end reason alone"
  },
  "1B0C": {
   "unit": "game/mainProgram0800",
   "line": "0 -> 1 = 'no game in progress'"
  },
  "1B0F": {
   "unit": "game/mainProgram0800",
   "line": "zp_B1 is 0 only during the actual battle"
  },
  "1B11": {
   "unit": "game/mainProgram0800",
   "line": "we were in setup, film or menu phase - no film to close"
  },
  "1B13": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = we were watching a film, not recording one"
  },
  "1B16": {
   "unit": "game/mainProgram0800",
   "line": "nothing to write in that case"
  },
  "1B18": {
   "unit": "game/mainProgram0800",
   "line": "write the end marker, length, clock, scores and checksum into the film header"
  },
  "1B1B": {
   "unit": "game/mainProgram0800",
   "line": "default item 2 = WATCH GAME FILM"
  },
  "1B1D": {
   "unit": "game/mainProgram0800",
   "line": "so the player can review what just happened"
  },
  "1B20": {
   "unit": "game/mainProgram0800",
   "block": [
    "Leave the link in a defined state."
   ],
   "line": "is the comm link up?"
  },
  "1B23": {
   "unit": "game/mainProgram0800",
   "line": "no - session state 0"
  },
  "1B25": {
   "unit": "game/mainProgram0800",
   "line": "current session state"
  },
  "1B28": {
   "unit": "game/mainProgram0800",
   "line": "$80/$FF - already at menu level, nothing in flight"
  },
  "1B2A": {
   "unit": "game/mainProgram0800",
   "line": "flush the packet exchange that is still in progress"
  },
  "1B2D": {
   "unit": "game/mainProgram0800",
   "line": "$80 = link still up, but at menu level"
  },
  "1B2F": {
   "unit": "game/mainProgram0800",
   "line": "store the new session state"
  },
  "1B32": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "1B33": {
   "unit": "game/mainProgram0800",
   "routine": [
    "hangUpModem - Convenience entry: hang up and leave linkSessionState at 0 (no session).",
    "In:  none",
    "Out: see hangUpModemSetState",
    "Called from: miscTabAction $1ABC, disconnectFromOpponent $1C28, and by JMP from $56DB, $5722, $5771"
   ],
   "line": "the new linkSessionState is 0 = no session"
  },
  "1B35": {
   "unit": "game/mainProgram0800",
   "routine": [
    "hangUpModemSetState - Drops the modem link and stores A as the new linkSessionState. On the modem build it first tells the player 'PICK UP PHONE THEN PRESS SPACE.' (message $17) and waits for the space bar, so the voice call survives the modem going on-hook; then it waits up to $26 ticks for bit 7 of the debounced carrier state $E03C to clear before calling the comm module's link-control entry with X = 3 (hang up, then shut the UART down).",
    "In:  A = new value for linkSessionState $0B9C; link-active flag $E03B, comm build id $E030, carrier state $E03C",
    "Out: $0B9C = A, the comm module's link variables $E039-$E047 cleared, zp_7F used as the timeout; the operand at $1B5A is patched with A",
    "Called from: hangUpModem (fall-through) and hangUpAndPause at $1C0F.  NOTE: the survey text had messages $17 and $18 swapped between this routine and openCommLink; the pointer tables at $0500/$0524 give id $17 = $8897 'PICK UP PHONE THEN PRESS SPACE.' and id $18 = $88B6 'PRESS SPACE, WAIT, HANGUP PHONE.'"
   ],
   "line": "self-modifying code: A becomes the LDA #imm operand at $1B5A, restored at $1B59"
  },
  "1B38": {
   "unit": "game/mainProgram0800",
   "line": "is the link up at all?"
  },
  "1B3B": {
   "unit": "game/mainProgram0800",
   "line": "no - just record the new session state"
  },
  "1B3D": {
   "unit": "game/mainProgram0800",
   "line": "$E030: $FF = solo trainer module, $00 = real modem driver"
  },
  "1B40": {
   "unit": "game/mainProgram0800",
   "line": "the trainer has no phone line - skip the prompt"
  },
  "1B42": {
   "unit": "game/mainProgram0800",
   "line": "message $17 = 'PICK UP PHONE THEN PRESS SPACE.'"
  },
  "1B44": {
   "unit": "game/mainProgram0800",
   "line": "show it and block until the player presses space"
  },
  "1B47": {
   "unit": "game/mainProgram0800",
   "line": "$26 ticks..."
  },
  "1B49": {
   "unit": "game/mainProgram0800",
   "line": "...as a timeout (zp_7F is decremented every 8th frame by the IRQ)"
  },
  "1B4B": {
   "unit": "game/mainProgram0800",
   "line": "debounced carrier state; bit 7 mirrors the 'sample has settled' bit the driver puts in $E03D"
  },
  "1B4E": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 clear = the line state is no longer settled, i.e. the modem has reacted - stop waiting (probably)"
  },
  "1B50": {
   "unit": "game/mainProgram0800",
   "line": "timeout expired?"
  },
  "1B52": {
   "unit": "game/mainProgram0800",
   "line": "no - keep watching the carrier"
  },
  "1B54": {
   "unit": "game/mainProgram0800",
   "line": "X = 3: hang up, wait for the UART to idle, then shut it down and clear the link variables"
  },
  "1B56": {
   "unit": "game/mainProgram0800",
   "line": "call the $E003 entry of whichever comm module is loaded"
  },
  "1B59": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $1B35 = the requested session state"
  },
  "1B5B": {
   "unit": "game/mainProgram0800",
   "line": "store it"
  },
  "1B5E": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "1B5F": {
   "unit": "game/mainProgram0800",
   "routine": [
    "openCommLink - Brings the link up if it is not already. On the modem build it first shows message $18 'PRESS SPACE, WAIT, HANGUP PHONE.' and waits for the space bar - the player presses space so the modem goes off-hook, waits, and then puts the handset down - and then calls the comm module's link-control entry with X = 0, which sets the link flags $E03B/$E03C/$E03D.",
    "In:  link-active flag $E03B, isSoloTrainer $0BA5",
    "Out: the comm module's link flags set",
    "Called from: openLinkAndWaitForConnection $1B74 and the $6F00 overlay A at $7962.  NOTE: the survey text had this message swapped with the one used by hangUpModemSetState"
   ],
   "line": "is the link already up?"
  },
  "1B62": {
   "unit": "game/mainProgram0800",
   "line": "yes - nothing to do"
  },
  "1B64": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = solo trainer game"
  },
  "1B67": {
   "unit": "game/mainProgram0800",
   "line": "the trainer needs no prompt"
  },
  "1B69": {
   "unit": "game/mainProgram0800",
   "line": "message $18 = 'PRESS SPACE, WAIT, HANGUP PHONE.'"
  },
  "1B6B": {
   "unit": "game/mainProgram0800",
   "line": "show it and block until the player presses space"
  },
  "1B6E": {
   "unit": "game/mainProgram0800",
   "line": "X = 0: open the link"
  },
  "1B70": {
   "unit": "game/mainProgram0800",
   "line": "call the $E003 entry of whichever comm module is loaded"
  },
  "1B73": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "1B74": {
   "unit": "game/mainProgram0800",
   "routine": [
    "openLinkAndWaitForConnection - openCommLink followed by waitForConnection (fall-through).",
    "In:  none",
    "Out: see the two callees",
    "Called from: voicePauseReconnect at $1BFA"
   ],
   "line": "bring the line up (with the player prompt on a modem build)"
  },
  "1B77": {
   "unit": "game/mainProgram0800",
   "routine": [
    "waitForConnection - On the modem build, pins the status message 'WAITING FOR CONNECTION...', spins in waitForCarrier until the modem reports carrier, then clears the voice-pause bit of pauseState and blanks the message line. On the solo trainer build it returns immediately.",
    "In:  isSoloTrainer $0BA5",
    "Out: persistentMessageId $92A1, zp_18, zp_BE bit 7 cleared, message line cleared",
    "Called from: openLinkAndWaitForConnection (fall-through) and the $6F00 overlay A at $7965"
   ],
   "line": "bit 7 = solo trainer game"
  },
  "1B7A": {
   "unit": "game/mainProgram0800",
   "line": "no line to wait for - return at once"
  },
  "1B7C": {
   "unit": "game/mainProgram0800",
   "line": "message $10 = 'WAITING FOR CONNECTION...'"
  },
  "1B7E": {
   "unit": "game/mainProgram0800",
   "line": "pin it so it is not replaced by queued messages"
  },
  "1B81": {
   "unit": "game/mainProgram0800",
   "line": "show it immediately"
  },
  "1B84": {
   "unit": "game/mainProgram0800",
   "line": "preset the (never tested) wait counter"
  },
  "1B86": {
   "unit": "game/mainProgram0800",
   "line": "zp_18 - see the note in waitForCarrier"
  },
  "1B88": {
   "unit": "game/mainProgram0800",
   "line": "spin until the modem reports carrier"
  },
  "1B8B": {
   "unit": "game/mainProgram0800",
   "line": "pause/disconnect flags"
  },
  "1B8D": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 7 = the voice pause is over"
  },
  "1B8F": {
   "unit": "game/mainProgram0800",
   "line": "store them back"
  },
  "1B91": {
   "unit": "game/mainProgram0800",
   "line": "no pinned message any more"
  },
  "1B93": {
   "unit": "game/mainProgram0800",
   "line": "release the status line"
  },
  "1B96": {
   "unit": "game/mainProgram0800",
   "line": "blank the status line and return"
  },
  "1B99": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "1B9A": {
   "unit": "game/mainProgram0800",
   "routine": [
    "waitForCarrier - Polling loop that runs until the comm module reports carrier (bit 6 of the debounced carrier state $E03C). Each pass it services the status/message system, runs the link watchdog when the game is in the $FF phase, waits two frames and steps a countdown in zp_18 while the raw carrier sample shows no carrier. Once carrier appears it silences the sound and returns.",
    "In:  gamePhase zp_B1, carrier state $E03C, raw carrier sample $E03D, zp_18",
    "Out: sound silenced; zp_18 counted down but never tested by anybody, so the countdown is vestigial (and zp_18 is a shared scratch that the printing inside updateStatusMessage may clobber anyway)",
    "Called from: waitForConnection at $1B88 and by its own loop"
   ],
   "line": "keep the status line and chat display alive while we wait"
  },
  "1B9D": {
   "unit": "game/mainProgram0800",
   "line": "zp_B1 = $FF marks the pre-game / side-selection phase"
  },
  "1B9F": {
   "unit": "game/mainProgram0800",
   "line": "the only phase in which a stalled link has to be noticed here"
  },
  "1BA1": {
   "unit": "game/mainProgram0800",
   "line": "any other phase - no watchdog"
  },
  "1BA3": {
   "unit": "game/mainProgram0800",
   "line": "'PHONE TROUBLE.' / 'TRY AGAIN.' watchdog on the packet exchange"
  },
  "1BA6": {
   "unit": "game/mainProgram0800",
   "line": "two frames..."
  },
  "1BA8": {
   "unit": "game/mainProgram0800",
   "line": "...of raster-timed delay per poll"
  },
  "1BAB": {
   "unit": "game/mainProgram0800",
   "line": "raw carrier sample from the last IRQ"
  },
  "1BAE": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 set = the sample sees carrier, so do not count down"
  },
  "1BB0": {
   "unit": "game/mainProgram0800",
   "line": "step the (unused) no-carrier countdown"
  },
  "1BB2": {
   "unit": "game/mainProgram0800",
   "line": "not at zero yet"
  },
  "1BB4": {
   "unit": "game/mainProgram0800",
   "line": "hold it at 1 instead of wrapping round to $FF"
  },
  "1BB6": {
   "unit": "game/mainProgram0800",
   "line": "debounced carrier state"
  },
  "1BB9": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 clear = still no carrier - keep waiting"
  },
  "1BBB": {
   "unit": "game/mainProgram0800",
   "line": "sound 0 = silence all voices"
  },
  "1BBD": {
   "unit": "game/mainProgram0800",
   "line": "silence the dial tone/noise and return to waitForConnection"
  },
  "1BC0": {
   "unit": "game/mainProgram0800",
   "block": [
    "orphanRts1BC0 - a stray $60 (RTS) between waitForCarrier and",
    "showMessageWaitForSpace. Nothing branches or jumps to it and waitForCarrier",
    "always leaves through the JMP at $1BBD, so it is unreachable - probably the",
    "tail of an earlier version of the wait loop."
   ],
   "line": "unreachable RTS"
  },
  "1BC1": {
   "unit": "game/mainProgram0800",
   "routine": [
    "showMessageWaitForSpace - Pins status message A on the message line and blocks until the player presses the space bar. It discards any buffered incoming chat text so the prompt is not overwritten, leaves chat typing mode, and while waiting keeps the message system running and the shared RNG stirring. On exit it empties the message queue, locks the keyboard out for 30 frames and clears the key latch so the space press is not seen twice.",
    "In:  A = message id (used with $17 'PICK UP PHONE THEN PRESS SPACE.' and $18 'PRESS SPACE, WAIT, HANGUP PHONE.')",
    "Out: persistentMessageId $92A1, incomingChatLength $9277 = 0, chatState $929C = 0, inputLockoutTimer $0B7D = $1E, lastTypedKey $9248 = $FF, message queue emptied",
    "Called from: hangUpModemSetState $1B44 and openCommLink $1B6B"
   ],
   "line": "pin the message so nothing queued replaces it"
  },
  "1BC4": {
   "unit": "game/mainProgram0800",
   "line": "zero"
  },
  "1BC6": {
   "unit": "game/mainProgram0800",
   "line": "throw away buffered incoming chat text so it cannot overwrite the prompt"
  },
  "1BC9": {
   "unit": "game/mainProgram0800",
   "line": "A still holds the message id: show it with its sound now"
  },
  "1BCC": {
   "unit": "game/mainProgram0800",
   "line": "no key seen yet"
  },
  "1BCE": {
   "unit": "game/mainProgram0800",
   "line": "clear the key latch"
  },
  "1BD1": {
   "unit": "game/mainProgram0800",
   "line": "zero for the three stores below"
  },
  "1BD3": {
   "unit": "game/mainProgram0800",
   "line": "leave chat typing mode"
  },
  "1BD6": {
   "unit": "game/mainProgram0800",
   "line": "accept keys immediately (no input lockout)"
  },
  "1BD9": {
   "unit": "game/mainProgram0800",
   "line": "clear the chat length again - showStatusMessage may have refilled it at $C52E"
  },
  "1BDC": {
   "unit": "game/mainProgram0800",
   "line": "service the status line and chat display"
  },
  "1BDF": {
   "unit": "game/mainProgram0800",
   "line": "keep stepping the shared RNG so the wait does not bias it"
  },
  "1BE2": {
   "unit": "game/mainProgram0800",
   "line": "last key code seen by the IRQ keyboard scan"
  },
  "1BE5": {
   "unit": "game/mainProgram0800",
   "line": "$A0 = space (the game sets bit 7 on its key codes)"
  },
  "1BE7": {
   "unit": "game/mainProgram0800",
   "line": "anything else - keep waiting"
  },
  "1BE9": {
   "unit": "game/mainProgram0800",
   "line": "empty the message queue and refresh the status line"
  },
  "1BEC": {
   "unit": "game/mainProgram0800",
   "line": "30 frames..."
  },
  "1BEE": {
   "unit": "game/mainProgram0800",
   "line": "...during which the IRQ ignores the keyboard"
  },
  "1BF1": {
   "unit": "game/mainProgram0800",
   "line": "no key"
  },
  "1BF3": {
   "unit": "game/mainProgram0800",
   "line": "clear the latch so the space press is not processed again"
  },
  "1BF6": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "1BF7": {
   "unit": "game/mainProgram0800",
   "routine": [
    "voicePauseReconnect - The VOICE PAUSE round trip: hang up so both players can talk on the phone, wait five seconds, then bring the line back up and wait for carrier. Finally it clears bit 7 of menuSelectedItem, which is the flag runInGameOptionsMenu spins on at $65D5 while the pause lasts.",
    "In:  linkSessionState $0B9C",
    "Out: zp_7B (see below), menuSelectedItem $91D5 bit 7 cleared, the link re-established",
    "Called from: the VOICE PAUSE item of runInGameOptionsMenu at $65C1, the link watchdog at $5A42 and the $C000 overlay at $C768 (when the opponent requested the pause)"
   ],
   "line": "hang up and wait 250 frames so the players can talk"
  },
  "1BFA": {
   "unit": "game/mainProgram0800",
   "line": "put the call back into data mode and wait for carrier"
  },
  "1BFD": {
   "unit": "game/mainProgram0800",
   "line": "stores whatever A the return path left; in practice bit 7 is always set ($FF on the trainer path, a screen/colour RAM page high byte otherwise), which marks the status line as free - almost certainly unintended but harmless"
  },
  "1BFF": {
   "unit": "game/mainProgram0800",
   "line": "the menu selection doubles as the pause flag"
  },
  "1C02": {
   "unit": "game/mainProgram0800",
   "line": "clear bit 7"
  },
  "1C04": {
   "unit": "game/mainProgram0800",
   "line": "this releases the wait loop in runInGameOptionsMenu at $65D5"
  },
  "1C07": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "1C08": {
   "unit": "game/mainProgram0800",
   "routine": [
    "hangUpAndPause - Hangs up for a voice pause and then waits about five seconds. The new session state is the current one, except that a negative state ($80 link up at menu level, $FF link lost) is replaced by 0.",
    "In:  linkSessionState $0B9C",
    "Out: $0B9C updated by hangUpModemSetState; 250 frames of raster-timed delay",
    "Called from: voicePauseReconnect at $1BF7"
   ],
   "line": "current session state"
  },
  "1C0B": {
   "unit": "game/mainProgram0800",
   "line": "0 or 2 - keep it as the state to restore"
  },
  "1C0D": {
   "unit": "game/mainProgram0800",
   "line": "$80/$FF becomes 0 = no session"
  },
  "1C0F": {
   "unit": "game/mainProgram0800",
   "line": "prompt, drop carrier and store the state"
  },
  "1C12": {
   "unit": "game/mainProgram0800",
   "line": "250 frames, about five seconds..."
  },
  "1C14": {
   "unit": "game/mainProgram0800",
   "line": "...to give the modems time to release the line, then return"
  },
  "1C17": {
   "unit": "game/mainProgram0800",
   "routine": [
    "disconnectFromOpponent - Ends a link session cleanly: tells the opponent with command $8C reason 5 (disconnect), pushes the packet out immediately and hangs up. Does nothing when no session is active (linkSessionState bit 7 clear).",
    "In:  linkSessionState $0B9C",
    "Out: zp_73 = 5, the $8C command queued and transmitted, the link closed and $0B9C = 0",
    "Called from: runMainMenu at $1C60 (DISCONNECT/SET VOICE) and $1C6C (before starting a solo game), the link watchdog at $5A2F, and by JMP from $56B8"
   ],
   "line": "session state; bit 7 set = link up"
  },
  "1C1A": {
   "unit": "game/mainProgram0800",
   "line": "no session - nothing to disconnect"
  },
  "1C1C": {
   "unit": "game/mainProgram0800",
   "line": "reason 5 = disconnect (cmdSessionControl sets the opponent's pauseState to $80)"
  },
  "1C1E": {
   "unit": "game/mainProgram0800",
   "line": "command argument 0"
  },
  "1C20": {
   "unit": "game/mainProgram0800",
   "line": "$8C = cmdSessionControl"
  },
  "1C22": {
   "unit": "game/mainProgram0800",
   "line": "queue the 2-byte command"
  },
  "1C25": {
   "unit": "game/mainProgram0800",
   "line": "build and hand the packet to the comm module right away"
  },
  "1C28": {
   "unit": "game/mainProgram0800",
   "line": "then drop the line, leaving linkSessionState = 0"
  },
  "1C2B": {
   "unit": "game/mainProgram0800",
   "line": "return"
  },
  "1C2C": {
   "unit": "game/mainProgram0800",
   "routine": [
    "runMainMenu - Runs the main menu whose text printOptionsList has just drawn: five items, or six when a link session is up and DISCONNECT/SET VOICE is offered. It preselects defaultMainMenuItem, marks the menu phase (selectedUnit and gamePhase = $F0, so the selector services the command exchange instead of the battle) and calls the generic selector with C = 0 (no slow-down before the last row). Item 5 just disconnects and returns to miscTabAction; every other item reloads the $6F00 overlay and jumps into handleMainMenuChoice, so the routine does not return.",
    "In:  linkSessionState $0B9C, defaultMainMenuItem $92FD, the text window left by printOptionsList",
    "Out: menuItemCount $91D4, menuSelectedItem $91D5, menuFirstRow $91D6 = $0B, selectedUnit $90F8 = $F0, gamePhase zp_B1 = $F0, zp_3D/zp_42 = $60, diskLoadDepth $0BA7 bumped around the overlay reload",
    "Called from: miscTabAction at $1AD3"
   ],
   "block": [
    "Set the menu up: item count, preselection, geometry and the menu-phase flags."
   ],
   "line": "five items: COMPETE / PRACTICE / WATCH FILM / SAVE FILM / LOAD FILM"
  },
  "1C2E": {
   "unit": "game/mainProgram0800",
   "line": "session state; bit 7 set = a link session exists"
  },
  "1C31": {
   "unit": "game/mainProgram0800",
   "line": "no session - DISCONNECT/SET VOICE was not printed"
  },
  "1C33": {
   "unit": "game/mainProgram0800",
   "line": "six items, the last one being DISCONNECT/SET VOICE"
  },
  "1C34": {
   "unit": "game/mainProgram0800",
   "line": "tell the selector how many rows there are"
  },
  "1C37": {
   "unit": "game/mainProgram0800",
   "line": "the item endGameToMainMenu or selectDefaultMenuItem thought most useful"
  },
  "1C3A": {
   "unit": "game/mainProgram0800",
   "line": "preselect it"
  },
  "1C3D": {
   "unit": "game/mainProgram0800",
   "line": "shift the window left edge to 5 so the highlight bar starts left of the text"
  },
  "1C3F": {
   "unit": "game/mainProgram0800",
   "line": "the new left edge..."
  },
  "1C41": {
   "unit": "game/mainProgram0800",
   "line": "...also stored as the cursor column; toggleMenuRowHighlight forces column 0, so this looks vestigial"
  },
  "1C43": {
   "unit": "game/mainProgram0800",
   "line": "the first menu row is screen row 11"
  },
  "1C45": {
   "unit": "game/mainProgram0800",
   "line": "the selector adds the selection index to this"
  },
  "1C48": {
   "unit": "game/mainProgram0800",
   "line": "put the cursor on that row"
  },
  "1C4B": {
   "unit": "game/mainProgram0800",
   "line": "$F0 = 'no unit is being watched'..."
  },
  "1C4D": {
   "unit": "game/mainProgram0800",
   "line": "...so the selector skips the unit-death and group-highlight logic"
  },
  "1C50": {
   "unit": "game/mainProgram0800",
   "line": "zp_B1 = $F0: menu phase, bits 6/7 set, so the selector runs serviceCommandExchange instead of the battle tick"
  },
  "1C52": {
   "unit": "game/mainProgram0800",
   "line": "colour $60: black text, EOR mask 6 for the highlight bar"
  },
  "1C54": {
   "unit": "game/mainProgram0800",
   "line": "text colour"
  },
  "1C56": {
   "unit": "game/mainProgram0800",
   "line": "and the colour used to pad centred strings"
  },
  "1C58": {
   "unit": "game/mainProgram0800",
   "line": "C = 0: no extra delay before the joystick can reach the last row"
  },
  "1C59": {
   "unit": "game/mainProgram0800",
   "line": "run the selector; it returns with Y = the chosen item"
  },
  "1C5C": {
   "unit": "game/mainProgram0800",
   "block": [
    "Act on the choice."
   ],
   "line": "item 5 = DISCONNECT/SET VOICE"
  },
  "1C5E": {
   "unit": "game/mainProgram0800",
   "line": "any other item starts or loads something"
  },
  "1C60": {
   "unit": "game/mainProgram0800",
   "line": "just drop the link and return to miscTabAction, which reprints the menu"
  }
 },
 "dataTypes": {
  "163A": [
   "byte",
   3
  ],
  "163D": [
   "byte",
   3
  ],
  "1640": [
   "byte",
   3
  ],
  "16FE": [
   "byte",
   1
  ],
  "17D8": [
   "byte",
   1
  ],
  "17D9": [
   "byte",
   1
  ],
  "1881": [
   "byte",
   4
  ],
  "1885": [
   "byte",
   4
  ],
  "1889": [
   "byte",
   1
  ],
  "188A": [
   "byte",
   1
  ],
  "1BC0": [
   "byte",
   1
  ]
 },
 "zpComments": {
  "7F": "countdown decremented every 8th frame by the IRQ, used as the timeout while waiting for the comm module (loaded with $26 by the ABORT GAME handshake at $1AAC and by hangUpModemSetState at $1B47, and with $4B by completeExchange)",
  "B1": "gamePhase: 0 while the battle runs; non-zero in the side-selection, setup, film and menu phases (bits 6/7 gate drawing and the link handling); $FF in the pre-game phase that arms the link watchdog and $F0 while the main menu is up (set by runMainMenu at $1C50)"
 }
}