{
 "chunk": "variables",
 "unit": "-",
 "zp": {
  "00": [
   "cpuPortDdr",
   "6510 data direction register (listings show CPU_DDR); the film code drives it together with $01"
  ],
  "01": [
   "cpuPortReg",
   "6510 port (CPU_PORT): $35 = I/O visible, no ROMs; briefly $34 while the film buffer under $D000 is read/written"
  ],
  "04": [
   "oldLoadDestLo",
   "destination pointer (lo) of an earlier build of the fast loader; only touched by the unreachable leftoverCheckDiskId at $C3C4"
  ],
  "05": [
   "oldLoadDestHi",
   "high byte of oldLoadDestLo (dead leftover)"
  ],
  "18": [
   "scratch18",
   "general scratch, used by every unit: multiply/divide operand ($FCCD/$FD0A), character row in the text plotters, loop counters and sums"
  ],
  "19": [
   "scratch19",
   "general scratch, partner of scratch18: product/quotient, character column in the text plotters, second half of 16-bit sums"
  ],
  "1A": [
   "scratch1A",
   "general scratch: third math temp, right column of a frame box, low byte of the trainer's 16-bit scores"
  ],
  "1B": [
   "scratch1B",
   "general scratch: bottom row of a frame box, division remainder (lo), max column returned by computeGroupBounds"
  ],
  "1C": [
   "scratch1C",
   "general scratch: frame tile code, division remainder (mid), member count / max row of a group"
  ],
  "1D": [
   "scratch1D",
   "general scratch: bounds mode flag of computeGroupBounds (bit 7 = use destinations), division remainder (hi)"
  ],
  "1E": [
   "scratch1E",
   "general scratch: running terrain height in the line-of-sight walk, decimal digit index, group destination centre column"
  ],
  "1F": [
   "scratch1F",
   "general scratch: line-of-sight rise flag, divide bit counter, group destination centre row"
  ],
  "20": [
   "scratch20",
   "general scratch: half width in magnifyWindow2x, scan mode (bit 7 = attack pass), unit type being counted on the STATS tab"
  ],
  "21": [
   "scratch21",
   "general scratch: half height in magnifyWindow2x, index of the unit being scanned, terrain neighbour mask in the map generator"
  ],
  "22": [
   "cellColA",
   "map column of point A for distanceSquared ($3870)/cellDistance ($E82A); the current column of an area scan"
  ],
  "23": [
   "scratch23",
   "scratch: first (clipped) column of the area scan square; distance/direction accumulator in the trainer's move search"
  ],
  "24": [
   "cellRowA",
   "map row of point A for distanceSquared/cellDistance; the current row of an area scan"
  ],
  "25": [
   "scanColCount",
   "number of columns still to scan on the current row of an area scan (2r+1, clipped); reused as scratch by the trainer"
  ],
  "26": [
   "scanRowCount",
   "number of rows still to scan in an area scan; reused as the chosen direction by the trainer"
  ],
  "27": [
   "cellColB",
   "map column of point B (the centre/reference point) for distanceSquared and cellDistance"
  ],
  "28": [
   "cellRowB",
   "map row of point B (the centre/reference point) for distanceSquared and cellDistance"
  ],
  "29": [
   "scanListIndex",
   "index of the last entry written to the area-scan result lists at $0400/$0432/$0464 ($FF = empty)"
  ],
  "2A": [
   "scratch2A",
   "scratch: hill-site / forest-cluster index in the map generator, group destination column delta in the trainer"
  ],
  "2B": [
   "scratch2B",
   "scratch: lane or cluster-seed counter in the map generator, group destination row delta in the trainer"
  ],
  "2D": [
   "contourScanRow",
   "map generator: row of the cell being examined by applyContourTiles"
  ],
  "2E": [
   "contourScanCol",
   "map generator: column of the cell being examined by applyContourTiles"
  ],
  "2F": [
   "scanResultCount",
   "index of the last entry of the area-scan result lists (count-1, $FF = none); also the viewport row counter while the 7x5 view is drawn"
  ],
  "30": [
   "magnifySrcRow",
   "source row counter of magnifyWindow2x"
  ],
  "31": [
   "magnifySrcCol",
   "source column counter of magnifyWindow2x; also the viewport column counter while the 7x5 view is drawn"
  ],
  "32": [
   "tileColour",
   "colour byte of the map tile / radar glyph being rendered (terrain colour, then unit colour)"
  ],
  "33": [
   "spriteFlashPhase",
   "frame counter rotated right twice inside the raster IRQ so bit 7 mirrors bit 0 of frameCounter; gates the two-colour sprite flashing"
  ],
  "34": [
   "zp_34",
   "not a real variable: the only reference is the stray byte pair $CE,$34 at the very end of the text engine ($CFFE)"
  ],
  "38": [
   "irqInProgress",
   "re-entrancy guard of rasterIrqHandler (non-zero while the handler runs with interrupts enabled)"
  ],
  "39": [
   "textWindowTop",
   "text engine: first screen row of the text window"
  ],
  "3A": [
   "textWindowBottom",
   "text engine: row limit (exclusive) of the text window, used by the window clears"
  ],
  "3B": [
   "textWindowLeft",
   "text engine: left screen column of the text window; added to textCursorCol to get the absolute column"
  ],
  "3C": [
   "textWindowWidth",
   "text engine: window width in characters (wrap point) and the default centring width; also the width of a menu highlight bar"
  ],
  "3D": [
   "textColour",
   "text engine: colour byte written for each printed character cell, and the EOR mask used for the menu highlight"
  ],
  "3E": [
   "textFillGlyph",
   "text engine: glyph used as the blank/fill character by the clear and pad routines"
  ],
  "3F": [
   "textCursorCol",
   "text engine: cursor column relative to textWindowLeft"
  ],
  "40": [
   "textCursorRow",
   "text engine: absolute cursor row (0-24)"
  ],
  "41": [
   "screenBuiltFlag",
   "set to 1 by every screen builder and never read anywhere; a spare slot of the 12-byte text state saved to savedTextWindow"
  ],
  "42": [
   "textPadColour",
   "text engine: colour used for the blanks that pad a centred string"
  ],
  "43": [
   "rawGlyphMode",
   "text engine: bit 7 set = A is a glyph index, skipping the ASCII translation and control-code handling"
  ],
  "44": [
   "doubleSizeText",
   "text engine: bit 7 set = characters are drawn 2x2 cells and the cursor advances two columns/rows"
  ],
  "45": [
   "frameCounter",
   "low byte of the free-running 24-bit frame counter incremented by the raster IRQ"
  ],
  "46": [
   "frameCounterMid",
   "middle byte of frameCounter"
  ],
  "47": [
   "frameCounterHi",
   "high byte of frameCounter"
  ],
  "48": [
   "mapCellPtr",
   "pointer (lo) to a cell of the $F000 battlefield map, set by readMapCell ($25FA); also the running pointer of the block fill/copy loops"
  ],
  "49": [
   "mapCellPtrHi",
   "high byte of mapCellPtr"
  ],
  "4A": [
   "fillEndPtr",
   "end address (lo) compared by advanceFillPointer ($C3F9); reused as the film-directory entry pointer and the trainer's plan column pointer"
  ],
  "4B": [
   "fillEndPtrHi",
   "high byte of fillEndPtr"
  ],
  "4C": [
   "planRowPtr",
   "trainer setup: pointer (lo) to the 50 destination rows of the selected opening play inside the loaded playbook"
  ],
  "4D": [
   "planRowPtrHi",
   "high byte of planRowPtr"
  ],
  "4E": [
   "fillCounter",
   "16-bit counter incremented as a side effect by advanceFillPointer ($C3F9); nothing reads it"
  ],
  "4F": [
   "fillCounterHi",
   "high byte of fillCounter"
  ],
  "54": [
   "savedRegA",
   "A save slot of saveRegsAXY/restoreRegsAXY ($C027/$C030); the map drawing code uses the same triple as the cell byte"
  ],
  "55": [
   "savedRegX",
   "X save slot of saveRegsAXY/restoreRegsAXY; the map drawing code uses it as the cell column"
  ],
  "56": [
   "savedRegY",
   "Y save slot of saveRegsAXY/restoreRegsAXY; the map drawing code uses it as the cell row"
  ],
  "57": [
   "gameRngState0",
   "byte 0 of the 24-bit game RNG stepped by nextGameRandom ($FD5D); seeded with $49 at $030B so both machines match"
  ],
  "58": [
   "gameRngState1",
   "byte 1 of the game RNG state (seeded $19)"
  ],
  "59": [
   "gameRngState2",
   "byte 2 of the game RNG state (seeded $02)"
  ],
  "5A": [
   "scenarioSeed0",
   "byte 0 of the 24-bit scenario/map seed stepped by nextScenarioRandom ($FD4A); also the first answer byte of the manual look-up check"
  ],
  "5B": [
   "scenarioSeed1",
   "byte 1 of the scenario seed; its bit 7 gives the sign in addRandomJitter; second manual answer byte"
  ],
  "5C": [
   "scenarioSeed2",
   "byte 2 of the scenario seed; third manual answer byte"
  ],
  "5D": [
   "joyDirection",
   "joystick 2 direction nibble latched by the IRQ, active low ($0F = centred); indexes joystickDirectionTable"
  ],
  "5E": [
   "droneJoyDirection",
   "direction code 0-7 of the last joystick steer on the drone screen; mapped to a heading through joyDirToHeadingTable"
  ],
  "5F": [
   "joyFireUp",
   "joystick 2 fire state latched by the IRQ: 0 = button down, 1 = released"
  ],
  "60": [
   "joyDirectionCopy",
   "second copy of joyDirection written by the IRQ at $1216; no reader anywhere (dead store)"
  ],
  "61": [
   "joyFireUpCopy",
   "second copy of joyFireUp written by the IRQ at $1210; no reader anywhere (dead store)"
  ],
  "66": [
   "requestedScreenKey",
   "function key from the IRQ poll: 0-3 = F1/F3/F5/F7 = requested screen, $FF = none; forced to $FF while a disk load runs"
  ],
  "67": [
   "gridCursorCol",
   "missile screen: column 0-20 of the target cursor in the 21x17 radar grid (comcen at 10)"
  ],
  "68": [
   "gridCursorRow",
   "missile screen: row 0-16 of the target cursor (comcen at 8)"
  ],
  "69": [
   "mergeFillIndex",
   "number of bytes already placed in mergedCmdBuffer while the own and received packets are merged"
  ],
  "6A": [
   "packetReadIndex",
   "read offset into the packet currently being merged into mergedCmdBuffer"
  ],
  "6B": [
   "targetDirection",
   "direction 0-3 of the unit's chosen target, copied into the facing bits of unitFlagsTable; the map generator reuses it as the blob's tried-direction mask"
  ],
  "6C": [
   "joyRepeatTimer",
   "first of the six IRQ frame countdowns $6C-$71: joystick auto-repeat delay"
  ],
  "6D": [
   "fireDebounceTimer",
   "frame countdown after an accepted fire press; clicks are ignored while it is non-zero"
  ],
  "6E": [
   "menuBlinkTimer",
   "frame countdown between highlight toggles of the selected menu row / console tab"
  ],
  "6F": [
   "markerBlinkTimer",
   "frame countdown for the repair-marker and missile-button blink"
  ],
  "70": [
   "keyRepeatTimer",
   "frame countdown before a held key produces another key event"
  ],
  "71": [
   "frameTimer71",
   "sixth of the IRQ frame countdowns $6C-$71; nothing ever loads it (spare slot)"
  ],
  "72": [
   "messageTimer",
   "frames the current status-line message stays up, decremented every 4th frame; 0 = ready for the next message"
  ],
  "73": [
   "cmdParam0",
   "first argument byte of the command being queued or executed (usually the unit index or group key)"
  ],
  "74": [
   "cmdParam1",
   "second argument byte of the command (usually the map column)"
  ],
  "75": [
   "cmdParam2",
   "third argument byte of the command (usually the map row)"
  ],
  "76": [
   "cmdFromOpponent",
   "bit 7 set while the command being merged or executed came from the opponent (its coordinates are mirrored with 39-v)"
  ],
  "77": [
   "ownUnitColour",
   "colour RAM value for the local player's units (6 = blue)"
  ],
  "78": [
   "enemyUnitColour",
   "colour RAM value for the opponent's units (2 = red)"
  ],
  "79": [
   "fireButtonArmed",
   "fire-button edge latch: set when the button is seen released, cleared when a press is accepted"
  ],
  "7A": [
   "pendingMessageId",
   "id of the message the status line should print next; $80 = idle, bit 7 set = nothing pending"
  ],
  "7B": [
   "shownMessageId",
   "id of the message currently on the status line; bit 7 set = the line is free"
  ],
  "7D": [
   "unusedByte7D",
   "written with 0 by animateRingBurst ($3D2A) and read nowhere (dead store)"
  ],
  "7E": [
   "decimalValueHigh",
   "high (third) byte of the 24-bit value passed to printDecimal; cleared by the conversion"
  ],
  "7F": [
   "linkWaitTimer",
   "countdown decremented every 8th frame, used as the timeout while waiting for the comm module"
  ],
  "80": [
   "loaderTimingPad",
   "never written: BIT $80 is a 3-cycle delay in the fast-loader receive loop ($096F, $C10E)"
  ],
  "8C": [
   "lineErrRow",
   "line stepper: Bresenham error accumulator of the row axis; the map generator reuses it as the random-bit count"
  ],
  "8D": [
   "lineErrCol",
   "line stepper: error accumulator of the column axis; the map generator reuses it as the random bit buffer"
  ],
  "8E": [
   "lineMajorLen",
   "line stepper: length of the line along its major axis, max(|dRow|,|dCol|)"
  ],
  "8F": [
   "lineDeltaRow",
   "line stepper: |end row - start row|"
  ],
  "90": [
   "lineDeltaCol",
   "line stepper: |end column - start column|"
  ],
  "91": [
   "lineStepsLeft",
   "line stepper: steps still to walk (0 = finished); returned in A by initLineStepper/stepLine"
  ],
  "92": [
   "lineCurRow",
   "line stepper: current map row (start row on entry); also the cursor row of the battlefield view"
  ],
  "93": [
   "lineCurCol",
   "line stepper: current map column (start column on entry); also the cursor column of the battlefield view"
  ],
  "94": [
   "lineEndRow",
   "line stepper: target map row"
  ],
  "95": [
   "lineEndCol",
   "line stepper: target map column"
  ],
  "96": [
   "lineStepRow",
   "line stepper: row increment per major step ($01 or $FF)"
  ],
  "97": [
   "lineStepCol",
   "line stepper: column increment per major step ($01 or $FF)"
  ],
  "98": [
   "spritePlotPtr",
   "pointer (lo) into the sprite shape being plotted by plotSpritePixel; also the copy source of loadDirectionShapeSprite"
  ],
  "99": [
   "spritePlotPtrHi",
   "high byte of spritePlotPtr ($8A/$90)"
  ],
  "9D": [
   "kernalMsgFlag",
   "KERNAL MSGFLG; the ea boot file stores 0 so the KERNAL LOAD stays silent (not a game variable)"
  ],
  "A2": [
   "stringPtr",
   "pointer (lo) to the bit-7 terminated string being printed; left just past the terminator"
  ],
  "A3": [
   "stringPtrHi",
   "high byte of stringPtr"
  ],
  "A5": [
   "viewOriginCol",
   "map column of the left edge of the 7x5 battlefield view (may be negative); the cursor cell is viewOriginCol+3"
  ],
  "A6": [
   "viewOriginRow",
   "map row of the top edge of the battlefield view; the cursor cell is viewOriginRow+2"
  ],
  "A7": [
   "blobOriginCol",
   "map generator: column where the current terrain blob started"
  ],
  "A8": [
   "blobOriginRow",
   "map generator: row where the current terrain blob started"
  ],
  "A9": [
   "isDiagonalStep",
   "1 when the last stepLine moved in both axes; costs 8 extra movement points"
  ],
  "AA": [
   "rasterIrqLine",
   "raster line the IRQ handler re-arms VIC_RASTER with ($FA)"
  ],
  "AB": [
   "irqVectorShadowLo",
   "low byte of the IRQ vector that rasterIrqHandler writes back to $FFFE on every interrupt"
  ],
  "AC": [
   "irqVectorShadowHi",
   "high byte of the IRQ vector written back to $FFFF on every interrupt"
  ],
  "AD": [
   "circleDxPtr",
   "pointer (lo) to the dx list of the circle radius being drawn into a sprite"
  ],
  "AE": [
   "circleDxPtrHi",
   "high byte of circleDxPtr"
  ],
  "AF": [
   "circleDyPtr",
   "pointer (lo) to the dy list of the circle radius being drawn"
  ],
  "B0": [
   "circleDyPtrHi",
   "high byte of circleDyPtr"
  ],
  "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)"
  ],
  "B2": [
   "glyphPtr",
   "pointer (lo) to the 8 bytes of the glyph/tile being drawn; also the blob stack index and the RLE destination"
  ],
  "B3": [
   "glyphPtrHi",
   "high byte of glyphPtr"
  ],
  "B4": [
   "bitmapPtr",
   "pointer (lo) to the current 8x8 cell in the $A000 bitmap, from the row/column tables at $9300"
  ],
  "B5": [
   "bitmapPtrHi",
   "high byte of bitmapPtr"
  ],
  "B6": [
   "screenPtr",
   "pointer (lo) to the colour byte of the current cell in screen RAM $8C00 (+$4C00 reaches colour RAM)"
  ],
  "B7": [
   "screenPtrHi",
   "high byte of screenPtr"
  ],
  "B8": [
   "blockPtr",
   "pointer (lo) to the character-block/frame-tile data being drawn"
  ],
  "B9": [
   "blockPtrHi",
   "high byte of blockPtr"
  ],
  "BA": [
   "magnifyEdgeMask",
   "quadrant suppression mask for plotGlyphDoubleSize (bits 0-3 = skip top/bottom/left/right); the boot loader uses the same byte as KERNAL FA"
  ],
  "BB": [
   "cursorTargetMode",
   "target cursor mode: 0 = move order, >0 = range-limited attack cursor, <0 = free cell pick reported in pickedCellCol/Row"
  ],
  "BC": [
   "filmPtr",
   "pointer (lo) into the game film buffer in the RAM under the I/O area ($D000-$DFEF)"
  ],
  "BD": [
   "filmPtrHi",
   "high byte of filmPtr"
  ],
  "BE": [
   "pauseState",
   "pause/disconnect state: bit 0 own pause, bit 1 opponent pause, 3 after a timeout, bit 7 = voice pause / hang-up"
  ],
  "D8": [
   "soundScriptPtr",
   "sound script read pointer (lo) of SID voice 0; the driver indexes it with voice*2, so voices 1 and 2 use $DA/$DB and $DC/$DD"
  ],
  "D9": [
   "soundScriptPtrHi",
   "high byte of soundScriptPtr (voice 0)"
  ],
  "DA": [
   "soundScriptPtr1",
   "sound script read pointer (lo) of SID voice 1"
  ],
  "DB": [
   "soundScriptPtr1Hi",
   "high byte of soundScriptPtr1"
  ],
  "DC": [
   "soundScriptPtr2",
   "sound script read pointer (lo) of SID voice 2"
  ],
  "DD": [
   "soundScriptPtr2Hi",
   "high byte of soundScriptPtr2"
  ],
  "E0": [
   "ldTrack",
   "fast loader: track of the sector being transferred"
  ],
  "E1": [
   "ldSector",
   "fast loader: sector being transferred, incremented after each good sector"
  ],
  "E2": [
   "ldDestPtr",
   "fast loader: destination pointer (lo) of the 256-byte receive loop"
  ],
  "E3": [
   "ldDestPtrHi",
   "fast loader: destination pointer (hi), advanced one page per sector"
  ],
  "E4": [
   "ldCia2Bits",
   "fast loader: CIA2 port A bits 0-1 (VIC bank) rewritten on every handshake strobe and EORed out of the assembled byte"
  ],
  "E5": [
   "wrSrcPtr",
   "fast loader: source pointer (lo) of the sector being written back to disk"
  ],
  "E6": [
   "wrSrcPtrHi",
   "fast loader: source pointer (hi) of the sector being written"
  ],
  "E7": [
   "ldCount",
   "fast loader: sectors left to transfer"
  ],
  "F0": [
   "scrollDeltaCol",
   "horizontal scroll step applied to the battlefield view (0/1/2 = -1/0/+1)"
  ],
  "F1": [
   "scrollDeltaRow",
   "vertical scroll step applied to the battlefield view (0/1/2 = -1/0/+1)"
  ],
  "F2": [
   "bitmapRowStride",
   "signed 16-bit stride (lo) added between bitmap rows while the view scrolls (+320 or -320)"
  ],
  "F3": [
   "bitmapRowStrideHi",
   "high byte of bitmapRowStride"
  ],
  "F4": [
   "screenRowStride",
   "signed 16-bit stride (lo) between screen RAM rows while the view scrolls (+40 or -40)"
  ],
  "F5": [
   "screenRowStrideHi",
   "high byte of screenRowStride"
  ],
  "F6": [
   "scrollRowCount",
   "number of character rows to copy while the view scrolls (8 or 10)"
  ],
  "F7": [
   "redrawCellCount",
   "number of cells to redraw along the edge exposed by a scroll (5 or 7)"
  ],
  "F8": [
   "viewRowLimit",
   "exclusive map-row limit for the view centre: 40 during the battle, 20 during the setup phase"
  ],
  "FB": [
   "driveCmdStrPtr",
   "C128 boot sector: pointer (lo) to the DOS command string being sent; not used by the game"
  ],
  "FC": [
   "driveCmdStrPtrHi",
   "high byte of driveCmdStrPtr (always $0B)"
  ]
 },
 "labels": {
  "8A00": "sprite0Shape",
  "8A40": "sprite1Shape",
  "8A80": "sprite2Shape",
  "8A95": "sprite2ShapeRow7",
  "8AC0": "sprite3Shape",
  "8B00": "sprite4Shape",
  "8B40": "sprite5Shape",
  "8B80": "sprite6Shape",
  "8BC0": "sprite7Shape",
  "9000": "vicCtrl1Shadow",
  "9001": "vicCtrl2Shadow",
  "9002": "vicMemPtrShadow",
  "9003": "vicBorderShadow",
  "9004": "vicBg0Shadow",
  "9005": "vicBg1Shadow",
  "9006": "vicBg2Shadow",
  "9008": "spriteColourShadow",
  "9009": "sprite1ColourShadow",
  "900A": "sprite2ColourShadow",
  "900B": "sprite3ColourShadow",
  "900C": "sprite4ColourShadow",
  "900D": "sprite5ColourShadow",
  "900E": "sprite6ColourShadow",
  "900F": "sprite7ColourShadow",
  "9010": "spriteXShadow",
  "9011": "sprite1XShadow",
  "9012": "sprite2XShadow",
  "9013": "sprite3XShadow",
  "9014": "sprite4XShadow",
  "9015": "sprite5XShadow",
  "9016": "sprite6XShadow",
  "9017": "sprite7XShadow",
  "9018": "spriteYShadow",
  "9019": "sprite1YShadow",
  "901A": "sprite2YShadow",
  "901B": "sprite3YShadow",
  "901C": "sprite4YShadow",
  "901D": "sprite5YShadow",
  "901E": "sprite6YShadow",
  "901F": "sprite7YShadow",
  "9020": "spriteXMsbShadow",
  "9022": "spriteEnableShadow",
  "9023": "spriteXExpandShadow",
  "9024": "spriteYExpandShadow",
  "9025": "spriteBuildBuffer",
  "9065": "outgoingCmdRing",
  "908C": "outgoingCmdRingEnd",
  "908D": "outgoingCmdWriteIndex",
  "908E": "outgoingCmdReadIndex",
  "908F": "mergedCmdBuffer",
  "90B7": "mergedCmdReadIndex",
  "90B8": "glyphBuildBuffer",
  "90E9": "prevKeyCode",
  "90EA": "newKeyEvent",
  "90EE": "inputIdleFrames",
  "90EF": "textCentreWidth",
  "90F4": "lastStepRowDelta",
  "90F5": "lastStepColDelta",
  "90F6": "drawCellSaveCol",
  "90F7": "drawCellSaveRow",
  "90F8": "selectedUnit",
  "90F9": "menuTabIndex",
  "90FA": "currentUnit",
  "90FB": "currentScreen",
  "90FC": "stepDelayTimer",
  "90FD": "unitProcessOrder",
  "9162": "unitOrderIndex",
  "9163": "linkPollDelay",
  "9164": "unitBaseOrder",
  "91C8": "shotLineTimer",
  "91C9": "joyRepeatMask",
  "91CA": "joyHoldCounter",
  "91CB": "gameClock",
  "91CC": "activeSpriteIndex",
  "91CE": "menuItemIdList",
  "91D4": "menuItemCount",
  "91D5": "menuSelectedItem",
  "91D6": "menuFirstRow",
  "91D7": "infoPanelUnit",
  "91D8": "pickedCellCol",
  "91D9": "pickedCellRow",
  "91DA": "currentUnitCol",
  "91DB": "currentUnitRow",
  "91DC": "boomerTargetTable",
  "91F0": "fireTargetUnit",
  "91F1": "fireTargetDistance",
  "91F2": "circleStepsLeft",
  "91F3": "spritePlotMode",
  "91F4": "groupStatusTable",
  "9204": "highlightedUnit",
  "9205": "highlightedGroupKey",
  "9206": "selectedGroupKey",
  "9207": "unusedFlag9207",
  "9208": "lastShownClock",
  "9209": "uiLevel",
  "920A": "groupBoxActive",
  "920B": "droneHeading",
  "920D": "droneControlFlags",
  "920E": "exchangeAppliedFlag",
  "920F": "radarGhostCountdown",
  "9210": "radarNoiseMasks",
  "9220": "droneFuel",
  "9221": "frameWidth",
  "9222": "frameHeight",
  "9223": "linkTimeoutCounter",
  "9224": "droneRadarOwnColour",
  "9225": "droneRadarEnemyColour",
  "9226": "cellBackgroundColour",
  "9227": "scanUnitTerrain",
  "9228": "scanUnitType",
  "9229": "scanUnitTerrainHeight",
  "922A": "comcenStunStatus",
  "922B": "overviewUnitPattern",
  "922C": "currentUnitCloakBits",
  "922D": "radarHalfToneFlag",
  "922E": "groupBoxRightOffset",
  "922F": "groupBoxLeftOffset",
  "9230": "groupBoxBottomOffset",
  "9231": "groupBoxTopOffset",
  "9232": "radarMissilesLeft",
  "9233": "radarDronesLeft",
  "9234": "viewRedrawTimer",
  "9236": "comcenUnit",
  "9237": "radarOwnUnitColour",
  "9238": "radarEnemyUnitColour",
  "9239": "radarTerrainColour",
  "923A": "pendingRedrawGroup",
  "923B": "pendingRedrawUnit",
  "923C": "commandSliceIndex",
  "923D": "attackerType",
  "923E": "markerSpriteTimer",
  "923F": "gameOverFlags",
  "9240": "hitDamage",
  "9241": "comcenSystemStatus",
  "9242": "comcenBattleStatus",
  "9243": "comcenRadarStatus",
  "9244": "comcenDroneStatus",
  "9245": "comcenCloakedFlag",
  "9246": "rleMarkerByte",
  "9247": "rleUnpackFlag",
  "9248": "lastTypedKey",
  "9249": "messageQueue",
  "9251": "messageQueueHead",
  "9252": "messageQueueTail",
  "9253": "incomingChatBuffer",
  "9254": "incomingChatScrollSrc",
  "9277": "incomingChatLength",
  "9278": "typedLineBuffer",
  "9279": "typedLineScrollSrc",
  "929C": "chatState",
  "929D": "lastOrderedGroupKey",
  "929E": "pendingGroupOrderCount",
  "929F": "pendingOwnCmdCount",
  "92A0": "rawKeyCode",
  "92A1": "persistentMessageId",
  "92A2": "sideScoreLo",
  "92A3": "side1ScoreLo",
  "92A4": "sideScoreHi",
  "92A5": "side1ScoreHi",
  "92A6": "recyclerMode",
  "92A7": "side1RecyclerMode",
  "92A8": "recyclerCol",
  "92A9": "side1RecyclerCol",
  "92AA": "recyclerRow",
  "92AC": "terrainPointsBySide",
  "92AE": "dronesLeft",
  "92AF": "opponentDronesLeft",
  "92B0": "missilesLeft",
  "92B1": "opponentMissilesLeft",
  "92B2": "comcenRevealTimer",
  "92B4": "comcenSpeedClass",
  "92B5": "opponentComcenSpeedClass",
  "92B6": "savedTextWindow",
  "92C2": "exchangeActivityFlag",
  "92C3": "savedViewOriginCol",
  "92C4": "savedViewOriginRow",
  "92C5": "menuBlinkState",
  "92C6": "setupPhaseMode",
  "92C7": "manualCheckTries",
  "92C8": "screenShakeTimer",
  "92C9": "gameEndCountdown",
  "92CA": "savedLineState",
  "92D6": "comcenStatusSnapshot",
  "92DA": "decimalDigitBuffer",
  "92E3": "stepIntervalFrames",
  "92E4": "shiftHeldFlag",
  "92E5": "commodoreHeldFlag",
  "92E6": "filmNameLength",
  "92E7": "soundRequestSlots",
  "92E8": "soundRequestVoice1",
  "92E9": "soundRequestVoice2",
  "92EA": "soundRequestMultiVoice",
  "92EB": "mergedCmdLength",
  "92EC": "mergedCmdSplitIndex",
  "92ED": "mergedCmdOwnerFlag",
  "92EE": "currentCommandLength",
  "92EF": "lastQuarterRound",
  "92F0": "diskIdIndex",
  "92F1": "filmEndPtr",
  "92F2": "filmEndPtrHi",
  "92F3": "setupToggleFlag",
  "92F4": "spriteScrollFineX",
  "92F5": "spriteScrollFineY",
  "92F6": "droneViewOriginCol",
  "92F7": "droneViewOriginRow",
  "92F8": "missileFlightTick",
  "92F9": "droneArrivalFlags",
  "92FA": "lastQuarterFlag",
  "92FB": "radarSweepTick",
  "92FC": "revealAllUnits",
  "92FD": "defaultMainMenuItem",
  "92FE": "soloTrainerAtGameStart",
  "F707": "side1ComcenRow",
  "F708": "unitFlagsTable",
  "F73A": "side1UnitFlagsTable",
  "F76B": "side1ComcenFlags",
  "F76C": "unitTypeTable",
  "F79D": "side0ComcenType",
  "F79E": "side1UnitTypeTable",
  "F7D0": "unitDestColTable",
  "F802": "side1UnitDestColTable",
  "F818": "unitPathColTableAlias",
  "F833": "side1ComcenDestCol",
  "F834": "unitDestRowTable",
  "F866": "side1UnitDestRowTable",
  "F897": "side1ComcenDestRow",
  "F898": "unitPathColTable",
  "F8FC": "unitPathRowTable",
  "F944": "unitEnergyTableAlias",
  "F960": "unitStunTable",
  "F9C3": "side1ComcenStun",
  "F9C4": "unitEnergyTable",
  "F9F5": "side0ComcenEnergy",
  "FA27": "side1ComcenEnergy",
  "FA28": "unitMovePointsTable",
  "FA70": "unitTerrainUnderAlias",
  "FA8C": "unitHitFlashTable",
  "FAF0": "unitTerrainUnderTable",
  "FB54": "unitDisplayFlagsTable",
  "FBB7": "side1ComcenDisplayFlags",
  "FFDE": "ownPlayerName",
  "FFE8": "opponentPlayerName",
  "FFFA": "nmiVector",
  "FFFB": "nmiVectorHi",
  "FFFC": "resetVector",
  "FFFD": "resetVectorHi",
  "FFFE": "irqVector",
  "FFFF": "irqVectorHi"
 },
 "unitLabels": {
  "game/mainProgram0800": {
   "0B7D": "inputLockoutTimer",
   "0B7E": "programChecksum",
   "0B7F": "ownerNameString",
   "0B93": "ownerNameEnd",
   "0B94": "gameVersionByte",
   "0B95": "mapSeed",
   "0B96": "mapSeed1",
   "0B97": "mapSeed2",
   "0B98": "prevMapSeed",
   "0B99": "prevMapSeed1",
   "0B9A": "prevMapSeed2",
   "0B9B": "filmPlaybackMode",
   "0B9C": "linkSessionState",
   "0B9D": "mapFlipFlag",
   "0B9E": "prevMapFlipFlag",
   "0B9F": "playerSide",
   "0BA0": "setupPlayerSide",
   "0BA1": "playsFirstFlag",
   "0BA2": "savedPlaysFirstFlag",
   "0BA3": "gameTypeOptions",
   "0BA4": "prevGameTypeOptions",
   "0BA5": "isSoloTrainer",
   "0BA6": "prevSoloTrainerFlag",
   "0BA7": "diskLoadDepth",
   "0BA8": "gameEndReason",
   "0BA9": "mapNameText",
   "0BAE": "mapNameTerminator",
   "0BAF": "mapNameBackup"
  },
  "game/battlefieldMapF000": {
   "F640": "unitColTable",
   "F671": "side0ComcenCol",
   "F672": "side1UnitColTable",
   "F688": "unitFlagsTableAlias",
   "F6A3": "side1ComcenCol",
   "F6A4": "unitRowTable",
   "F6D5": "side0ComcenRow",
   "F6D6": "side1UnitRowTable",
   "F6EC": "unitTypeTableAlias"
  },
  "game/mapGenerator6F00": {
   "87FF": "overlayVariantFlag"
  },
  "game/comcenScreens6F00": {
   "87FF": "overlayVariantFlag"
  }
 },
 "notes": {
  "0B7D": {
   "unit": "game/mainProgram0800",
   "line": "frames during which the IRQ ignores the keyboard and joystick and keeps the previous state"
  },
  "0B7E": {
   "unit": "game/mainProgram0800",
   "line": "checksum of $0402-$87FF computed by verifyProgramChecksum and exchanged with the opponent so both sides can detect a different build"
  },
  "0B7F": {
   "unit": "game/mainProgram0800",
   "line": "21-byte owner/player name built at run time from ownPlayerName and opponentPlayerName, terminated with bit 7 set"
  },
  "0B93": {
   "unit": "game/mainProgram0800",
   "line": "last byte of ownerNameString (the bit-7 terminator position)"
  },
  "0B94": {
   "unit": "game/mainProgram0800",
   "line": "game/film format id: written to film header offset $2F and verified on load, and sent in the link version handshake",
   "block": [
    "$0B94-$0BB4 - the game settings block: everything that describes the game being played and has to match on both machines. gameVersionByte and programChecksum are exchanged in the version handshake, mapSeed and mapFlipFlag pick the battlefield, gameTypeOptions the rules, playerSide and playsFirstFlag the sides, and the two 6-byte text fields at mapNameText and mapNameBackup hold the printable map name. The film header carries a copy of most of it."
   ]
  },
  "0B95": {
   "unit": "game/mainProgram0800",
   "line": "24-bit seed of the current battlefield ($0B95-$0B97) = the 5-letter map name packed 5 bits per letter; sent with command $8B/$A6"
  },
  "0B96": {
   "unit": "game/mainProgram0800",
   "line": "byte 1 of mapSeed"
  },
  "0B97": {
   "unit": "game/mainProgram0800",
   "line": "byte 2 of mapSeed"
  },
  "0B98": {
   "unit": "game/mainProgram0800",
   "line": "3-byte copy of the previous game's mapSeed; if it is unchanged the saved map name is restored, otherwise the first-play flag is reset"
  },
  "0B99": {
   "unit": "game/mainProgram0800",
   "line": "byte 1 of prevMapSeed"
  },
  "0B9A": {
   "unit": "game/mainProgram0800",
   "line": "byte 2 of prevMapSeed"
  },
  "0B9B": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 = a game film is being played back (commands come from the film, player input is locked out); bits 0-1 = the replay viewpoint 0 US, 1 THEM, 2 BOTH"
  },
  "0B9C": {
   "unit": "game/mainProgram0800",
   "line": "link session: 0 = none, 2 = game session agreed, $80 = link up at menu level, $FF = link lost"
  },
  "0B9D": {
   "unit": "game/mainProgram0800",
   "line": "0/1: the chosen map is shown rotated 180 degrees (F1 TO FLIP); XORed with playerSide to decide the orientation and sent as command $A6 instead of $8B"
  },
  "0B9E": {
   "unit": "game/mainProgram0800",
   "line": "mapFlipFlag of the previous game; a change forces the first-play flag back to 1"
  },
  "0B9F": {
   "unit": "game/mainProgram0800",
   "line": "side this machine plays, 0 or 1 ($FE = not chosen yet); EOR 1 gives the opponent, and units 0-49 belong to side 0, 50-99 to side 1"
  },
  "0BA0": {
   "unit": "game/mainProgram0800",
   "line": "side chosen in the setup screens, copied into playerSide when the game starts"
  },
  "0BA1": {
   "unit": "game/mainProgram0800",
   "line": "non-zero = this side has 1ST PLAY; it alternates every game and is forced to 1 when the map or the options change"
  },
  "0BA2": {
   "unit": "game/mainProgram0800",
   "line": "copy of playsFirstFlag taken at game start and restored after a film playback"
  },
  "0BA3": {
   "unit": "game/mainProgram0800",
   "line": "bits 0-2 game type (0 SCRIMAGE .. 6 DEFENDER, names at $CCC8), bit 3 UNIT MENUS, bit 4 DAMAGE, bit 6 CUSTOM rules, bit 7 custom map"
  },
  "0BA4": {
   "unit": "game/mainProgram0800",
   "line": "gameTypeOptions of the previous game; a difference forces the first-play flag back to 1"
  },
  "0BA5": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 set = PRACTICE WITH SOLO TRAINER (the track 35 computer opponent is behind the $E000 jump table); clear = a modem game (track 34)"
  },
  "0BA6": {
   "unit": "game/mainProgram0800",
   "line": "previous value of isSoloTrainer; a change from set to clear triggers the GET OPPONENT ON PHONE prompt"
  },
  "0BA7": {
   "unit": "game/mainProgram0800",
   "line": "nesting counter bumped around every disk load and overlay swap; while it is non-zero the IRQ throws keypresses away"
  },
  "0BA8": {
   "unit": "game/mainProgram0800",
   "line": "0 while a game runs; 1 = aborted / no game, $08 = time up, $A0/$C0 = the comcen of side 0/1 was knocked out"
  },
  "0BA9": {
   "unit": "game/mainProgram0800",
   "line": "5-character map name ($0BA9-$0BAD) from the 32-letter alphabet at $890C - the printable form of mapSeed"
  },
  "0BAE": {
   "unit": "game/mainProgram0800",
   "line": "byte after mapNameText: $8D while it is printed, '!' | $80 when the name is hidden from the opponent"
  },
  "0BAF": {
   "unit": "game/mainProgram0800",
   "line": "6-byte copy of mapNameText and its terminator, kept with prevMapSeed and restored when the same map is used again"
  },
  "87FF": {
   "line": "identity byte of the resident $6F00 overlay: $00 = variant A (map generator and setup), $FF = variant B (comcen missile and drone screens)"
  },
  "8A00": {
   "line": "64-byte shape of sprite 0: the battlefield cursor box / crosshair and the group formation ghost"
  },
  "8A40": {
   "line": "64-byte shape of sprite 1: the path/destination marker and the ring burst"
  },
  "8A80": {
   "line": "64-byte shape of sprite 2: the expanded viewport cursor frame, and the drone camera reticle copied from $FF93"
  },
  "8A95": {
   "line": "sprite2Shape+$15 = row 7 of sprite 2's shape, written directly at $338D"
  },
  "8AC0": {
   "line": "64-byte shape of sprite 3: the shot/route line drawn by drawLineInPathSprite"
  },
  "8B00": {
   "line": "64-byte shape of sprite 4 (comcen rings)"
  },
  "8B40": {
   "line": "64-byte shape of sprite 5; sprites 5-7 ($8B40-$8BFF) together hold the three frames of the unit picture"
  },
  "8B80": {
   "line": "64-byte shape of sprite 6 (second frame of the unit picture)"
  },
  "8BC0": {
   "line": "64-byte shape of sprite 7 (third frame of the unit picture)"
  },
  "9000": {
   "line": "shadow of VIC_CTRL1 $D011 ($3B = bitmap mode); copied by the IRQ even while a disk load blocks the rest of the handler"
  },
  "9001": {
   "line": "shadow of VIC_CTRL2 $D016; bit 4 (multicolour) selects the multicolour font and colour-RAM output in the text engine"
  },
  "9002": {
   "line": "shadow of VIC_MEM_PTR $D018 ($38 = screen $8C00, bitmap $A000)"
  },
  "9003": {
   "line": "shadow of the border colour $D020"
  },
  "9004": {
   "line": "shadow of background colour 0 $D021"
  },
  "9005": {
   "line": "shadow of background colour 1 $D022"
  },
  "9006": {
   "line": "shadow of background colour 2 $D023"
  },
  "9008": {
   "line": "8 sprite colour shadows $9008-$900F copied to $D027+; a value >= $10 packs two colours in its nibbles that the IRQ alternates to make the sprite flash"
  },
  "9009": {
   "line": "colour shadow of sprite 1 ($12 = flashing white/red ring burst)"
  },
  "900A": {
   "line": "colour shadow of sprite 2 (0 = black cursor, $0A = comcen marker)"
  },
  "900B": {
   "line": "colour shadow of sprite 3 (1 = white shot line)"
  },
  "900C": {
   "line": "colour shadow of sprite 4 ($12 = flashing comcen rings)"
  },
  "900D": {
   "line": "colour shadow of sprite 5 (unit picture / drone silhouette)"
  },
  "900E": {
   "line": "colour shadow of sprite 6 (unit picture)"
  },
  "900F": {
   "line": "colour shadow of sprite 7 (unit picture)"
  },
  "9010": {
   "line": "8 sprite X shadows $9010-$9017; the IRQ adds the fine-scroll bias from spriteScrollFineX before writing $D000+2n"
  },
  "9011": {
   "line": "X shadow of sprite 1"
  },
  "9012": {
   "line": "X shadow of sprite 2"
  },
  "9013": {
   "line": "X shadow of sprite 3"
  },
  "9014": {
   "line": "X shadow of sprite 4"
  },
  "9015": {
   "line": "X shadow of sprite 5"
  },
  "9016": {
   "line": "X shadow of sprite 6"
  },
  "9017": {
   "line": "X shadow of sprite 7"
  },
  "9018": {
   "line": "8 sprite Y shadows $9018-$901F; the IRQ adds (spriteScrollFineY & 7) - 3 before writing $D001+2n"
  },
  "9019": {
   "line": "Y shadow of sprite 1"
  },
  "901A": {
   "line": "Y shadow of sprite 2"
  },
  "901B": {
   "line": "Y shadow of sprite 3"
  },
  "901C": {
   "line": "Y shadow of sprite 4"
  },
  "901D": {
   "line": "Y shadow of sprite 5"
  },
  "901E": {
   "line": "Y shadow of sprite 6"
  },
  "901F": {
   "line": "Y shadow of sprite 7"
  },
  "9020": {
   "line": "shadow of VIC_SPR_HI_X $D010 (sprite X bit 8)"
  },
  "9022": {
   "line": "shadow of VIC_SPR_ENA $D015"
  },
  "9023": {
   "line": "shadow of VIC_SPR_EXP_X $D01D"
  },
  "9024": {
   "line": "shadow of VIC_SPR_EXP_Y $D017"
  },
  "9025": {
   "line": "64-byte off-screen sprite shape: plotSpritePixel targets it when activeSpriteIndex is negative, buildGroupFormationSprite composes the formation here"
  },
  "9065": {
   "line": "40-byte ring buffer ($9065-$908C) of command bytes queued for the opponent"
  },
  "908C": {
   "line": "last byte of outgoingCmdRing; $534F uses it as an index base so that outgoingCmdRingEnd+index+3 addresses mergedCmdBuffer"
  },
  "908D": {
   "line": "write index into outgoingCmdRing (0-39)"
  },
  "908E": {
   "line": "read index into outgoingCmdRing (next byte to send)"
  },
  "908F": {
   "line": "40-byte buffer ($908F-$90B6) holding one exchange: side 0's commands followed by side 1's; also the payload of a game film record"
  },
  "90B7": {
   "line": "read index into mergedCmdBuffer while the dispatcher executes commands; each handler advances it past its own arguments"
  },
  "90B8": {
   "line": "8-byte scratch glyph: the overview-map character being assembled, and the copy of the glyph being magnified by plotGlyphDoubleSize"
  },
  "90E9": {
   "line": "key code seen on the previous poll, used to detect a new press ($FF = none)"
  },
  "90EA": {
   "line": "code of a key pressed or auto-repeated this frame ($FF = none); consumers store $FF back when they take it"
  },
  "90EE": {
   "line": "frames since the joystick last moved or fire was pressed, saturating at $FF; any input resets it to 0"
  },
  "90EF": {
   "line": "non-zero = centre the next string in this many columns, padding with textPadColour; cleared again after printing"
  },
  "90F4": {
   "line": "row delta (-1/0/+1) applied by the last stepLine call"
  },
  "90F5": {
   "line": "column delta (-1/0/+1) applied by the last stepLine call"
  },
  "90F6": {
   "line": "viewOriginCol saved while the drawing code walks it cell by cell"
  },
  "90F7": {
   "line": "viewOriginRow saved while the drawing code walks it cell by cell"
  },
  "90F8": {
   "line": "index 0-99 of the unit the menus and orders act on; $FF = the repair menu, $FE = a menu with no unit"
  },
  "90F9": {
   "line": "selected item of the main menu / console tab (0 STATS, 1 REPAIR, 2 MISC)"
  },
  "90FA": {
   "line": "index of the unit the engine is currently stepping"
  },
  "90FB": {
   "line": "screen currently displayed: 0 battlefield map, 1 missile screen, 2 drone screen, 3 console/main menu"
  },
  "90FC": {
   "line": "frames until the next engine step, reloaded from stepIntervalFrames and decremented by the IRQ"
  },
  "90FD": {
   "line": "100-byte list of unit indices in this round's processing order, rebuilt at the end of every round"
  },
  "9162": {
   "line": "position in unitProcessOrder of the next unit to process (100 = round complete)"
  },
  "9163": {
   "line": "countdown (4-frame units) reloaded with 10 after each packet exchange; the exchange stays idle while it is non-zero"
  },
  "9164": {
   "line": "100-byte base order of unit indices that unitProcessOrder is shuffled from with the lock-step RNG"
  },
  "91C8": {
   "line": "frames the shot-line sprite stays visible (8); sprite 3 is switched off when it reaches 0"
  },
  "91C9": {
   "line": "$7F while the stick is held, 0 once it returns to centre; ANDed into joyHoldCounter so the first auto-repeat step waits longer"
  },
  "91CA": {
   "line": "number of consecutive auto-repeat steps taken while a joystick direction is held"
  },
  "91CB": {
   "line": "game clock in rounds/clicks, counting down to 0 (game over on points); stamped into every film record"
  },
  "91CC": {
   "line": "sprite number the shape plotters and loadDirectionShapeSprite target; bit 7 set = plot into spriteBuildBuffer instead"
  },
  "91CE": {
   "line": "item ids of the current menu in display order ($91CE-$91D3), filled by addMenuItem"
  },
  "91D4": {
   "line": "number of rows in the current menu"
  },
  "91D5": {
   "line": "highlighted/chosen menu row; on the REPAIR tab it doubles as the comcen system index 0-3"
  },
  "91D6": {
   "line": "screen row of menu row 0"
  },
  "91D7": {
   "line": "unit whose info panel is displayed ($FF = none)"
  },
  "91D8": {
   "line": "map column returned by runTargetCursorLoop in free-pick mode (cursorTargetMode < 0)"
  },
  "91D9": {
   "line": "map row returned by runTargetCursorLoop in free-pick mode"
  },
  "91DA": {
   "line": "column the current unit occupied before its step (cell to redraw); the map generator reuses it as the blob's horizontal spread"
  },
  "91DB": {
   "line": "row the current unit occupied before its step; the map generator reuses it as the blob's vertical spread"
  },
  "91DC": {
   "line": "one byte per BOOMER (indexed by its ordinal among type-2 units, $91DC-$91EF): bit 7 = target locked, bits 0-6 = target unit, 0 = none"
  },
  "91F0": {
   "line": "unit chosen as the fire target of the unit being processed ($FF = none)"
  },
  "91F1": {
   "line": "distance of fireTargetUnit; also the search radius passed to the area scan"
  },
  "91F2": {
   "line": "entries of the dx/dy lists still to plot in drawCircleInSprite"
  },
  "91F3": {
   "line": "pixel operation of the sprite plotter: 0 = clear (AND), $40 = set (ORA), $80 = toggle (EOR)"
  },
  "91F4": {
   "line": "16 entries ($91F4-$9203) indexed by group key (7 groups per side): bits 0-2 slowest speed class, bits 3-5 worst terrain, bit 6 blitz, bit 7 cloaked"
  },
  "9204": {
   "line": "unit drawn inverted in the viewport ($FF = none)"
  },
  "9205": {
   "line": "group key whose members are drawn inverted in the viewport ($FF = none)"
  },
  "9206": {
   "line": "group key the group UI works on: group number in bits 2-4, side in bit 5; membership test is (flags & $7C) == key | $40"
  },
  "9207": {
   "line": "written with $FF by the join-group menu and the group editor and read nowhere (dead store)"
  },
  "9208": {
   "line": "gameClock value the last time the status line clock/score was drawn; a difference triggers the refresh"
  },
  "9209": {
   "line": "0 = browsing the map, 1 = a unit is selected, 2 = a group is selected; picks the info-panel colour and the scope of the target cursor"
  },
  "920A": {
   "line": "bit 7 set while a group formation box is attached to the cursor (offsets in groupBoxLeftOffset..)"
  },
  "920B": {
   "line": "heading 0-7 clockwise from north of the own drone; also the index of the aircraft silhouette and the first byte of command $8D"
  },
  "920D": {
   "line": "drone control: bit 7 = own drone in flight, bit 6 = steered locally, bit 5 = strike pending, bit 0 = a drone move was executed"
  },
  "920E": {
   "line": "bit 7 set after each merged command exchange has been applied; the console screens consume it to refresh the STATS tab and advance the drone camera"
  },
  "920F": {
   "line": "counts unspotted enemy units drawn on the drone radar; when it underflows the next one is drawn as a noise ghost and it is reseeded with RNG & 7"
  },
  "9210": {
   "line": "16 AND masks ($9210-$921F), each $FF with four random bits cleared, used to draw unspotted enemy units as noisy radar blips"
  },
  "9220": {
   "line": "flight time left of the own drone: 100 at launch, one less per exchange, 0 ends the flight; reused as a loop counter by the film menus"
  },
  "9221": {
   "line": "width in character cells of the window/character block being drawn (decremented in place)"
  },
  "9222": {
   "line": "height in character cells of the window/character block being drawn"
  },
  "9223": {
   "line": "packet watchdog: cleared when a packet arrives, armed with bit 7 by checkLinkTimeout and counted up every 8 frames ($E1 = PHONE TROUBLE, $FF = give up)"
  },
  "9224": {
   "line": "colour nibble ($0B) used for the local player's units on the drone radar"
  },
  "9225": {
   "line": "colour nibble ($09) used for spotted enemy units on the drone radar"
  },
  "9226": {
   "line": "low colour nibble ORed into the screen-RAM byte of every map/radar cell ($0A on the drone screen, 0 elsewhere)"
  },
  "9227": {
   "line": "map cell byte under the unit whose sight/fire range is being scanned"
  },
  "9228": {
   "line": "type 0-4 of the unit being scanned"
  },
  "9229": {
   "line": "terrain height under the scanning unit; non-zero lets it see over friendly units"
  },
  "922A": {
   "line": "own comcen stun state: bits 0-3 stun counter, bit 7 stunned, $FF triggers the knock-out sequence, bit 6 set while it runs"
  },
  "922B": {
   "line": "multicolour pixel pattern ($55) used for the local player's units on the overview map, EOR $FF for the enemy"
  },
  "922C": {
   "line": "cloak bits 6-7 of the current unit's path column, re-applied when the column is rewritten"
  },
  "922D": {
   "line": "$FF while a cloaked unit is drawn on the radar: only the odd glyph rows are copied; cleared after each glyph"
  },
  "922E": {
   "line": "column offset (>= 0) from the cursor to the right edge of the group formation box"
  },
  "922F": {
   "line": "column offset (<= 0) from the cursor to the left edge of the group formation box"
  },
  "9230": {
   "line": "row offset (>= 0) from the cursor to the bottom edge of the group formation box"
  },
  "9231": {
   "line": "row offset (<= 0) from the cursor to the top edge of the group formation box"
  },
  "9232": {
   "line": "missile screen's working copy of the player's missiles left; decremented on each launch and shown by drawMissileCount"
  },
  "9233": {
   "line": "drone screen's working copy of the player's drones left; decremented at launch and shown by the drone count box"
  },
  "9234": {
   "line": "countdown that freezes at the sentinel $80; the battlefield view is redrawn (erasing the shot line) when it reaches it"
  },
  "9236": {
   "line": "unit index of the local player's COMCEN: 49 for side 0, 99 for side 1"
  },
  "9237": {
   "line": "colour the radar cell classifier uses for the local player's units"
  },
  "9238": {
   "line": "colour the radar cell classifier uses for spotted enemy units"
  },
  "9239": {
   "line": "colour the radar cell classifier uses for terrain and for unspotted (noise) units"
  },
  "923A": {
   "line": "group whose members the tick loop must redraw ($FF = none)"
  },
  "923B": {
   "line": "unit the tick loop must redraw ($FF = none)"
  },
  "923C": {
   "line": "index of the current command time slice inside the round (unitOrderIndex / 10); its low nibble goes into the film time stamp"
  },
  "923D": {
   "line": "type of the attacking unit; 2 (BOOMER) makes the shot line dotted and picks another sound"
  },
  "923E": {
   "line": "countdown after which the marker sprite 4 is switched off"
  },
  "923F": {
   "line": "set to $C0 when a game ends, is aborted or a film finishes; bit 6 blocks all further player commands"
  },
  "9240": {
   "line": "amount of the hit currently being applied to a unit"
  },
  "9241": {
   "line": "four comcen status bytes: [0] ENERGY (0 ok, $40 low, $80 critical), [1] BATTLE, [2] RADAR, [3] DRONE ($80 = damaged)"
  },
  "9242": {
   "line": "comcenSystemStatus[1]: $80 = the BATTLE system is damaged, so the overview map stops showing units"
  },
  "9243": {
   "line": "comcenSystemStatus[2]: $80 = the RADAR system is damaged, so sweeps are randomly blanked and spotted cells no longer stay lit"
  },
  "9244": {
   "line": "comcenSystemStatus[3]: $80 = the DRONE system is damaged, so only one launch in four succeeds and the camera shows static"
  },
  "9245": {
   "line": "bit 7 set while the local COMCEN is cloaked: unit inspection, missile screen and drone launches are blocked"
  },
  "9246": {
   "line": "escape byte of the RLE stream being unpacked (first byte of the block)"
  },
  "9247": {
   "line": "cleared by the main RLE unpacker and set by the overlay one; no reader found"
  },
  "9248": {
   "line": "last printable key typed (PETSCII with bit 7 set, $FF = none); consumers store $FF back"
  },
  "9249": {
   "line": "8-entry priority-sorted ring of pending status-message ids ($9249-$9250, $FF = empty)"
  },
  "9251": {
   "line": "insert index of messageQueue; the queue is empty when it equals messageQueueTail"
  },
  "9252": {
   "line": "read index of messageQueue; rewound temporarily while the player scrolls back through old messages"
  },
  "9253": {
   "line": "36-byte buffer ($9253-$9276) of the chat line being received from the opponent"
  },
  "9254": {
   "line": "incomingChatBuffer+1: source of the scroll-left loop that drops the oldest character when the line is full"
  },
  "9277": {
   "line": "number of characters in incomingChatBuffer; $80 marks a completed message"
  },
  "9278": {
   "line": "36-byte line editor buffer ($9278-$929B) shared by the outgoing chat line and the film name entry, always terminated with the cursor glyph $9E"
  },
  "9279": {
   "line": "typedLineBuffer+1: source of the scroll-left loop that drops the oldest character"
  },
  "929C": {
   "line": "chat typing state: bit 7 = typing to the opponent, bit 6 = the chat line is shown, bits 0-5 = characters typed"
  },
  "929D": {
   "line": "group key of the most recent NEW POSITION ($98) order ($FF = none)"
  },
  "929E": {
   "line": "number of $98 orders for lastOrderedGroupKey that were queued but not yet executed"
  },
  "929F": {
   "line": "number of own commands queued but not yet executed by the lock-step exchange"
  },
  "92A0": {
   "line": "raw code of the key currently held, as returned by the keyboard scanner (before repeat filtering)"
  },
  "92A1": {
   "line": "message id the status line falls back to when the queue drains ($9E = none, negative = show the CLICKS/US/THEM score line)"
  },
  "92A2": {
   "line": "low bytes of the two 16-bit point totals, indexed by the side scored against; the status line prints entry [playerSide EOR 1] as US"
  },
  "92A3": {
   "line": "sideScoreLo[1]"
  },
  "92A4": {
   "line": "high bytes of the two 16-bit point totals"
  },
  "92A5": {
   "line": "sideScoreHi[1]"
  },
  "92A6": {
   "line": "recycler setting per absolute side (2 bytes): 0 FULL, 1 HALF, 2 NONE"
  },
  "92A7": {
   "line": "recyclerMode[1]"
  },
  "92A8": {
   "line": "map column of each side's recycler (2 bytes); bit 7 set = no recycler"
  },
  "92A9": {
   "line": "recyclerCol[1]"
  },
  "92AA": {
   "line": "map row of each side's recycler (2 bytes)"
  },
  "92AC": {
   "line": "TERRAIN PTS option per absolute side (2 bytes, 0-2): points awarded when one of that side's units penetrates the enemy half"
  },
  "92AE": {
   "line": "drones still available: [0] = this machine's side, [1] = the opponent"
  },
  "92AF": {
   "line": "dronesLeft[1]"
  },
  "92B0": {
   "line": "missiles still available: [0] = this machine's side, [1] = the opponent"
  },
  "92B1": {
   "line": "missilesLeft[1]"
  },
  "92B2": {
   "line": "per side (2 bytes): rounds during which a comcen that used radar or a drone stays visible"
  },
  "92B4": {
   "line": "COMCEN SPEED per side (2 bytes); class 4 pins the comcen in place"
  },
  "92B5": {
   "line": "comcenSpeedClass[1]"
  },
  "92B6": {
   "line": "12-byte save area ($92B6-$92C1) for the text state $39-$44; a single slot, not nestable"
  },
  "92C2": {
   "line": "bit 0 set whenever a command was executed in an exchange; during setup the clock only advances on exchanges with activity"
  },
  "92C3": {
   "line": "viewOriginCol preserved across screen switches"
  },
  "92C4": {
   "line": "viewOriginRow preserved across screen switches"
  },
  "92C5": {
   "line": "0 while the selected menu row is drawn highlighted, 1 while it is drawn normally"
  },
  "92C6": {
   "line": "unit setup phase for the current game type: 0 = none, 1 = both sides deploy, $80 = only side 0 deploys"
  },
  "92C7": {
   "line": "tries left in the manual look-up protection; cleared at game start"
  },
  "92C8": {
   "line": "frames of screen shake left (8 after the own comcen is hit); while non-zero the IRQ adds the fine-scroll offsets to the sprite positions"
  },
  "92C9": {
   "line": "non-zero while the end-of-game sequence runs: counts up every 8 frames to $80 and then returns to the main menu"
  },
  "92CA": {
   "line": "12-byte save area ($92CA-$92D5) for the line stepper state $8C-$97"
  },
  "92D6": {
   "line": "4-byte copy of comcenSystemStatus taken when the REPAIR tab was last used; a difference auto-selects that tab"
  },
  "92DA": {
   "line": "9-byte buffer holding the decimal digits produced by convertToDecimalDigits (digits at +1..+8 as $B0 + digit)"
  },
  "92E3": {
   "line": "frames between engine steps (4 in a live game; the film replay speed keys set 9 - digit)"
  },
  "92E4": {
   "line": "$80 when a SHIFT key was held during the last keyboard scan"
  },
  "92E5": {
   "line": "$80 when the C= key was held during the last keyboard scan"
  },
  "92E6": {
   "line": "number of characters typed into typedLineBuffer for a film slot name; non-zero also blocks chat typing"
  },
  "92E7": {
   "line": "four sound request slots $92E7-$92EA ($FF = empty): slots 0-2 are the SID voices, slot 3 is a whole-chip sound"
  },
  "92E8": {
   "line": "soundRequestSlots[1]: sound request for SID voice 1 ($FF = empty)"
  },
  "92E9": {
   "line": "soundRequestSlots[2]: sound request for SID voice 2 ($FF = empty)"
  },
  "92EA": {
   "line": "soundRequestSlots[3]: request slot for the sounds that use all three voices ($FF = empty)"
  },
  "92EB": {
   "line": "number of valid bytes in mergedCmdBuffer for the current exchange (the length nibble of a film record)"
  },
  "92EC": {
   "line": "offset in mergedCmdBuffer where side 1's commands begin (the high nibble of a film record header)"
  },
  "92ED": {
   "line": "$80 while the commands being executed belong to side 1; XORed with the local side into cmdFromOpponent"
  },
  "92EE": {
   "line": "length (1-4 bytes including the command byte) of the command currently being copied or executed"
  },
  "92EF": {
   "line": "gameClock value at which the LAST QTR WARNING! message is issued (total / 4)"
  },
  "92F0": {
   "line": "which disk id checkDiskId last matched: 0 = 'OZ' game disk, 2 = 'EA' data disk"
  },
  "92F1": {
   "line": "end pointer (lo) of the game film: the $DFF0 recording limit, or the real end after the film was finalised"
  },
  "92F2": {
   "line": "high byte of filmEndPtr"
  },
  "92F3": {
   "line": "bit 7 = state of the '*' key toggle: CUSTOM rules on the scenario screen, pick-up/destroy mode during unit placement"
  },
  "92F4": {
   "line": "horizontal fine scroll of the tactical view; the IRQ adds its low three bits to every sprite X shadow"
  },
  "92F5": {
   "line": "vertical fine scroll of the tactical view; the IRQ adds (value & 7) - 3 to every sprite Y shadow"
  },
  "92F6": {
   "line": "map column of the left edge of the drone camera window; the drone itself sits at +6"
  },
  "92F7": {
   "line": "map row of the top edge of the drone camera window; the drone itself sits at +5"
  },
  "92F8": {
   "line": "free-running frame tick incremented by updateFrameTimers; the missile screen uses it as the flight animation clock and resets it at launch"
  },
  "92F9": {
   "line": "drone life cycle: 0 at launch, bit 7 = arrived or destroyed (a $92 seen in either packet), bit 6 = detonated, bit 4 = own arrival executed"
  },
  "92FA": {
   "line": "$80 once the LAST QTR warning has been given; also gates the clock/score fallback on the status line"
  },
  "92FB": {
   "line": "free-running frame tick used to pace the radar sweep (2 frames per step); reset by restartRadarSweep"
  },
  "92FC": {
   "line": "bit 7 set = draw every enemy unit whether spotted or not (beginner battle display / solo trainer / film)"
  },
  "92FD": {
   "line": "main menu item preselected on entry (0 COMPETE, 1 PRACTICE, 2 WATCH FILM)"
  },
  "92FE": {
   "line": "copy of isSoloTrainer taken when the game started; film header byte 4"
  },
  "F640": {
   "line": "map column of each of the 100 units; the parallel unit arrays follow every $64 bytes (0-49 side 0, 50-99 side 1, 49/99 = the comcens)",
   "block": [
    "The 100 unit records are kept as 14 parallel 100-byte arrays, $64 apart, filling $F640-$FBB7: $F640 column, $F6A4 row, $F708 flags/facing/group, $F76C type, $F7D0/$F834 ordered destination column/row, $F898/$F8FC path column/row (with the cloak and blitz bits), $F960 stun and dig-in, $F9C4 energy and visibility, $FA28 movement points, $FA8C hit flash, $FAF0 the terrain hidden under the unit and $FB54 the display flags. Indices 0-49 are player 0 and 50-99 player 1; 49 and 99 are the two COMCENs. Five of the arrays also have an alias label $80 lower so they can be indexed with a raw map byte ($80 | unit). The bytes on the disk here are stale leftovers - the generator fills every array at the start of a game."
   ]
  },
  "F671": {
   "line": "unitColTable[49]: column of side 0's COMCEN"
  },
  "F672": {
   "line": "unitColTable+50: the columns of side 1's units, indexed by that side's ordinal 0-49"
  },
  "F688": {
   "line": "unitFlagsTable - $80, so it can be indexed with a raw map byte ($80 | unit)"
  },
  "F6A3": {
   "line": "unitColTable[99]: column of side 1's COMCEN"
  },
  "F6A4": {
   "line": "map row of each of the 100 units"
  },
  "F6D5": {
   "line": "unitRowTable[49]: row of side 0's COMCEN"
  },
  "F6D6": {
   "line": "unitRowTable+50: the rows of side 1's units"
  },
  "F6EC": {
   "line": "unitTypeTable - $80, so it can be indexed with a raw map byte ($80 | unit)"
  },
  "F707": {
   "line": "unitRowTable[99]: row of side 1's COMCEN"
  },
  "F708": {
   "line": "authoritative per-unit flags: bits 0-1 facing, bits 2-4 group number, bit 5 side, bit 6 group member, bit 7 blitz charge pending"
  },
  "F73A": {
   "line": "unitFlagsTable+50: the flags of side 1's units"
  },
  "F76B": {
   "line": "unitFlagsTable[99]: flags of side 1's COMCEN"
  },
  "F76C": {
   "line": "per-unit type byte: bits 0-2 type (0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN), bits 3-4 enemy zones scored, bit 6 dug in, bit 7 dead ($FF = free slot)"
  },
  "F79D": {
   "line": "unitTypeTable[49]: type byte of side 0's COMCEN"
  },
  "F79E": {
   "line": "unitTypeTable+50: the type bytes of side 1's units"
  },
  "F7D0": {
   "line": "per-unit ordered destination column in bits 0-5; bit 7 set = the unit is stationary (destination reached)"
  },
  "F802": {
   "line": "unitDestColTable+50: the ordered destination columns of side 1's units"
  },
  "F818": {
   "line": "unitPathColTable - $80, so it can be indexed with a raw map byte ($80 | unit)"
  },
  "F833": {
   "line": "unitDestColTable[99]: ordered destination column of side 1's COMCEN"
  },
  "F834": {
   "line": "per-unit ordered destination row in bits 0-5; bit 7 set = the unit is repairing (regaining energy while stationary)"
  },
  "F866": {
   "line": "unitDestRowTable+50: the ordered destination rows of side 1's units"
  },
  "F897": {
   "line": "unitDestRowTable[99]: ordered destination row of side 1's COMCEN"
  },
  "F898": {
   "line": "per-unit logical (path) column in bits 0-5, which may lie on another unit's cell; bits 6-7 = cloak ordered / cloak active"
  },
  "F8FC": {
   "line": "per-unit logical (path) row in bits 0-5; bits 6-7 = blitz ordered / blitz active"
  },
  "F944": {
   "line": "unitEnergyTable - $80, so it can be indexed with a raw map byte ($80 | unit)"
  },
  "F960": {
   "line": "per-unit: bits 0-3 stun counter, bits 5-6 dig-in progress, bit 7 dug in"
  },
  "F9C3": {
   "line": "unitStunTable[99]: stun state of side 1's COMCEN"
  },
  "F9C4": {
   "line": "per-unit energy in bits 0-5 (0-50, shown as twice that in percent); bit 6 = spotted this round, bit 7 = currently visible"
  },
  "F9F5": {
   "line": "unitEnergyTable[49]: energy and visibility of side 0's COMCEN"
  },
  "FA27": {
   "line": "unitEnergyTable[99]: energy and visibility of side 1's COMCEN"
  },
  "FA28": {
   "line": "per-unit signed movement-point balance: the move rate is added each round, terrain cost subtracted per step, a hit pins the unit"
  },
  "FA70": {
   "line": "unitTerrainUnderTable - $80, so it can be indexed with a raw map byte ($80 | unit)"
  },
  "FA8C": {
   "line": "per-unit hit effect: non-zero while an explosion is shown on the unit's cell (low nibble counts down, $85 = destroyed comcen)"
  },
  "FAF0": {
   "line": "map byte hidden under each unit (the map cell itself holds $80 | unit); $FF = the unit is not on the map"
  },
  "FB54": {
   "line": "per-unit UI copy of the group bits (2-6) that the menus update immediately, plus bit 0 reloading, bit 1 hit/pinned, bit 7 at destination"
  },
  "FBB7": {
   "line": "unitDisplayFlagsTable[99]: UI flags of side 1's COMCEN"
  },
  "FFDE": {
   "line": "10 bytes copied from the game disk's BAM (offset $AB) by checkDiskId: the owner/player name, which the link protocol compares so both sides must run the same disk"
  },
  "FFE8": {
   "line": "10-byte opponent name received in the link handshake ('TRAINER   ' in a solo game, 'ENEMY' when the peer sends a blank name)"
  },
  "FFFA": {
   "line": "RAM NMI vector (low byte); the modem driver points it at its UART interrupt while the link is up"
  },
  "FFFB": {
   "line": "high byte of nmiVector"
  },
  "FFFC": {
   "line": "RAM RESET vector (low byte); never written by the game"
  },
  "FFFD": {
   "line": "high byte of resetVector"
  },
  "FFFE": {
   "line": "RAM IRQ/BRK vector (low byte), rewritten from irqVectorShadowLo on every interrupt"
  },
  "FFFF": {
   "line": "high byte of irqVector; it is also the placeholder operand of many instructions whose address bytes are patched at run time"
  }
 }
}