{
 "chunk": "game_mainProgram0800_1C63",
 "unit": "game/mainProgram0800",
 "unitLabels": {
  "game/mainProgram0800": {
   "1E89": "scrollBitmapStepOpcode",
   "1EBE": "scrollScreenStepOpcode"
  }
 },
 "notes": {
  "1C7B": {
   "unit": "game/mainProgram0800",
   "routine": [
    "isAsymmetricGameType - Tests the game type held in gameTypeOptions bits 0-2 and returns Z=1 for",
    "type 1 (QB SNEAK) or type 6 (DEFENDER), the two scenarios in which the two sides have different",
    "jobs and the setup summary therefore prints '1ST PLAY' / '2ND PLAY'.",
    "In:  gameTypeOptions ($0BA3) bits 0-2",
    "Out: A = game type 0-6, Z=1 when the type is asymmetric",
    "Called from: drawGameSetupSummary ($15B6 and $15E0)"
   ],
   "line": "game options: bits 0-2 type, bit 3 unit menus, bit 4 damage, bits 6-7 custom rules/map"
  },
  "1C87": {
   "unit": "game/mainProgram0800",
   "routine": [
    "restoreSettingsAfterFilm - Undoes what WATCH GAME FILM overwrote.  When the film-playback flag is",
    "set it leaves film mode and, unless the backup slot is marked empty ($FF), copies the pre-film map",
    "name, map seed, play order, map orientation and game options back over the live ones, so the main",
    "menu again describes the player's own game instead of the film's.",
    "In:  filmPlaybackMode ($0B9B) bit 7; prevGameTypeOptions ($0BA4) = $FF when nothing was saved; the",
    "     backups $0BAF-$0BB4, $0B98-$0B9A, $0BA2 and $0B9E",
    "Out: filmPlaybackMode = 0; mapNameText $0BA9-$0BAE, mapSeed $0B95-$0B97, playsFirstFlag $0BA1,",
    "     mapFlipFlag $0B9D and gameTypeOptions $0BA3 restored",
    "Called from: the main-menu flow at $1ACD, just before the options list is reprinted"
   ],
   "line": "$0B9B bit 7 = a game film is being watched"
  },
  "1CC1": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setViewVerticalLimit - Sets viewRowLimit, the exclusive map row limit the centre cell of the",
    "battlefield view has to stay under: 40 during the battle, 20 in the setup/film/menu phases where a",
    "player may only look at his own half of the map.  In the restricted case the row passed in Y is",
    "biased by -20 as well, so the caller can test it against 0..19; scrollViewBy adds the 20 back",
    "before it stores the new view origin.",
    "In:  Y = a map row (a row delta from scrollViewBy, the view origin from keepGroupBoxInsideMap),",
    "     gamePhase (zp_B1)",
    "Out: viewRowLimit (zp_F8) = $28 or $14, Y reduced by $14 in the restricted phase, A clobbered",
    "Called from: scrollViewBy ($1CD7) and keepGroupBoxInsideMap ($1F56)"
   ],
   "line": "40 = the full height of the battlefield map"
  },
  "1CD3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "scrollViewBy - Moves the 7x5-cell tactical view by one map cell: X = column delta, Y = row delta,",
    "each -1, 0 or +1.  The cursor is always the centre cell (viewOriginCol+3, viewOriginRow+2), so a",
    "step is refused when that cell - or, while a group formation box is being dragged, any edge of",
    "the box - would leave the 40-column map or the row window set by setViewVerticalLimit.  A refused",
    "axis has its delta zeroed; the deltas that survive stay in scrollDeltaCol/scrollDeltaRow, the",
    "mini-map cursor sprite is moved, and control falls through into scrollViewBitmap, which shifts the",
    "pixels.",
    "In:  X = dx, Y = dy (-1/0/+1); viewOriginCol/Row (zp_A5/zp_A6); gamePhase; groupBoxActive ($920A)",
    "     bit 7 with the box edge offsets $922E-$9231",
    "Out: viewOriginCol/Row updated; scrollDeltaCol/scrollDeltaRow (zp_F0/zp_F1) = the deltas actually",
    "     applied; viewRowLimit set; activeSpriteIndex = 0 and sprite 0 repositioned; jumps into",
    "     scrollViewBitmap and returns from there.  Self-modifies the LDA # operands at $1CF6+1 and",
    "     $1D2E+1",
    "Called from: scrollViewToUnit ($1D8F) and scrollViewportByDirection ($2179)"
   ],
   "line": "remember the requested column step (-1/0/+1)"
  },
  "1D4E": {
   "unit": "game/mainProgram0800",
   "routine": [
    "scrollViewToUnit - Scrolls the battlefield view until unit X sits under the centre cursor, one map",
    "cell per step along a Bresenham line that starts at the cell the caller left in lineCurRow/",
    "lineCurCol (normally the current cursor cell).  With A = 0 the whole walk runs at once; with A != 0",
    "each step waits A frames on timer zp_6E while updateGameFrame keeps the game, the sound and the",
    "link running, and the walk is abandoned as soon as the player touches the joystick.  The target is",
    "re-read every step, so the view follows a unit that is still moving.",
    "In:  A = frames per step (0 = no animation), X = unit index, lineCurRow/lineCurCol (zp_92/zp_93) =",
    "     the cell the walk starts from",
    "Out: view scrolled (viewOriginCol/Row); scrollTargetUnit/scrollStepDelay; line stepper state",
    "     zp_8C-zp_97; zp_6E used as the step timer",
    "Called from: recenterViewportOnUnit ($25B3, A=1, the selected unit) and $485C (A=0, the unit shown",
    "     in the info panel)"
   ],
   "line": "frames between steps (0 = immediate)"
  },
  "1D97": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setLineTargetToUnit - Points the line stepper's end point at unit X.  The unit records are parallel",
    "100-byte arrays $64 apart from $F640, so the unit's column and row are unitColTable[X] and",
    "unitRowTable[X].",
    "In:  X = unit index 0-99",
    "Out: lineEndCol (zp_95), lineEndRow (zp_94)",
    "Called from: scrollViewToUnit ($1D54 and $1D80)"
   ],
   "line": "$F640+unit = the unit's map column"
  },
  "1DE4": {
   "unit": "game/mainProgram0800",
   "routine": [
    "scrollViewBitmap - Shifts the whole 7x5-cell tactical view one map cell (16 pixels = 2 characters)",
    "in the direction left in scrollDeltaCol/scrollDeltaRow and then redraws the cells that scrolled",
    "into sight.  Both deltas are first biased to 0/1/2 = -1/0/+1 and used to index the 22 tables at",
    "$1DA2; the values are poked into two self-modified copy loops - one moving 96 or 112 bitmap bytes",
    "per character row, one moving 12 or 14 bytes of screen RAM and of colour RAM - which are run once",
    "per character row (8 or 10 of them) with the row stride added between passes.  A one-cell shift is",
    "16 bitmap bytes or 2 characters, so a 12-character window is copied 2 characters over and the",
    "exposed column (5 cells) and/or row (7 cells) is drawn from the map.",
    "In:  scrollDeltaCol/scrollDeltaRow (zp_F0/zp_F1) = -1/0/+1; viewOriginCol/Row, already updated by",
    "     scrollViewBy",
    "Out: bitmap $A208.., screen RAM $8C41.. and colour RAM $D841.. scrolled and the new edge drawn;",
    "     zp_F0/zp_F1 left biased to 0/1/2; zp_F2-zp_F7 and $54-$56 used as scratch.  Self-modifies",
    "     $1E81+1, $1E83+1/+2, $1E86+1/+2, $1E89, $1E8A+1, $1EB0+1, $1EB2+1/+2, $1EB5+1/+2, $1EB8+1/+2,",
    "     $1EBB+1/+2, $1EBE and $1EBF+1",
    "Called from: scrollViewBy ($1D49, by JMP - this is that routine's tail)"
   ],
   "line": "bias the column step -1/0/+1 into a table index 0/1/2"
  },
  "1F4E": {
   "unit": "game/mainProgram0800",
   "routine": [
    "keepGroupBoxInsideMap - Nudges the view origin one cell at a time until the whole group formation",
    "box (the cursor cell plus the signed edge offsets $922E-$9231 built by buildGroupFormationSprite)",
    "fits inside the map: 40 columns wide and, vertically, inside the row window set by",
    "setViewVerticalLimit.  Every nudge restarts the four edge tests; if at least one nudge was needed",
    "the view is repainted.",
    "In:  viewOriginCol/Row (zp_A5/zp_A6); groupBoxLeftOffset/RightOffset/TopOffset/BottomOffset",
    "     ($922F/$922E/$9231/$9230); gamePhase",
    "Out: viewOriginCol/Row corrected, viewRowLimit set, zp_18/zp_19 scratch; tail-jumps to drawViewport",
    "     when anything moved",
    "Called from: buildGroupFormationSprite ($319D), right after the box offsets are computed"
   ],
   "line": "1 so the first DEC below leaves 0"
  },
  "1FC3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "enterMapScreen - Entry point of the battlefield screen (game mode 0).  Restores the viewport origin",
    "the player left it at and repaints the whole screen - the three frames, the strategic mini-map, the",
    "tactical view and the status bar - then falls straight into runMapMainLoop.",
    "In:  savedViewOriginCol/Row ($92C3/$92C4)",
    "Out: viewOriginCol/Row set, screen redrawn; does not return - falls into runMapMainLoop",
    "Called from: switchToRequestedScreen ($06F5, by JMP, when F1 selects screen 0)"
   ],
   "line": "$92C3, written every pass of the map main loop"
  },
  "1FD0": {
   "unit": "game/mainProgram0800",
   "routine": [
    "runMapMainLoop - The interactive loop of the battlefield screen and of the unit-placement phase.",
    "It resets the cursor state, draws the view, and then loops: one game frame, then hover handling",
    "(resting 15 frames on one of your own units pops up the info panel) and click handling (a click",
    "selects the unit, or destroys it during placement while DESTROY mode is armed), then joystick",
    "scrolling with auto-repeat.  During the battle (gamePhase = 0) an F1/F3/F5/F7 request is honoured",
    "by switchToRequestedScreen, which rebuilds the stack and jumps to the new screen instead of",
    "returning; during unit placement the",
    "overlay-A '*' hook is polled every pass and the routine returns when F1 is pressed or the phase",
    "ends.  Units of the other side are filtered out by two 'cmp #$32' tests whose following branch",
    "opcode ($206B, $2084) is written at run time from the local player's side.",
    "In:  gamePhase (zp_B1), joyDirection (zp_5D), inputIdleFrames ($90EE), requestedScreenKey (zp_66),",
    "     gameEndCountdown ($92C9), revealAllUnits ($92FC), filmPlaybackMode ($0B9B), comcenUnit ($9236),",
    "     comcenCloakedFlag ($9245), setupToggleFlag ($92F3), the unit arrays and the map",
    "Out: savedViewOriginCol/Row kept up to date; selectedUnit ($90F8); info panel, cursor sprites and",
    "     view redrawn; command $91 queued in DESTROY mode; returns only from the placement phase",
    "Called from: enterMapScreen (fall-through), startGameFromSetup ($0B7A), the screen dispatcher",
    "     ($075E) and the setup phase of overlay A ($7B4E)"
   ],
   "line": "no key"
  },
  "20F4": {
   "unit": "game/mainProgram0800",
   "routine": [
    "scrollViewportByDirection - Turns a direction code 0-7 into a column/row step and scrolls the view",
    "one cell with scrollViewBy.  Codes 0-3 move on one axis only; codes 4-7 read the packed byte in",
    "diagonalDirectionTable for both axes and stretch the auto-repeat delay by 1.5 so a diagonal does",
    "not cover ground faster than a straight move.  In the range-limited target mode (cursorTargetMode",
    "positive, i.e. aiming a BOOMER) the step is only taken if the new cursor cell is still inside the",
    "selected unit's firing range.",
    "In:  A = direction code 0-7; joyRepeatTimer (zp_6C); cursorTargetMode (zp_BB); selectedUnit",
    "     ($90F8); viewOriginCol/Row; unitTerrainUnderTable ($FAF0)",
    "Out: view scrolled; joyRepeatTimer stretched for diagonals; fireDebounceTimer (zp_6D) = 10;",
    "     fireButtonArmed (zp_79) = 0; zp_22/zp_24/zp_27/zp_28 scratch.  Self-modifies the LDX/LDY",
    "     operands at $2175+1 and $2177+1",
    "Called from: runMapMainLoop ($20EA), runTargetCursorLoop ($2233) and the console tab loop ($6435)"
   ],
   "line": "default row step 0"
  },
  "2185": {
   "unit": "game/mainProgram0800",
   "routine": [
    "runTargetCursorLoop - Modal loop that lets the player drive the cursor to a cell and then acts on",
    "it.  cursorTargetMode picks the flavour: 0 = choose a destination for the selected unit, or for its",
    "whole group when uiLevel is 2 (the formation ghost is built into sprite 0); positive = choose a",
    "target for a BOOMER (cross cursor, movement limited to its range); negative = only report the cell",
    "the player stops on in pickedCellCol/pickedCellRow.  Each pass runs one game frame, previews the",
    "route after 90 idle frames, scrolls on joystick, and on a fire click issues the order.  The normal",
    "exits scroll the view back onto the unit and restore the plain battlefield cursor.",
    "In:  cursorTargetMode (zp_BB), selectedUnit ($90F8), uiLevel ($9209), gamePhase bit 6,",
    "     stepDelayTimer ($90FC), inputIdleFrames ($90EE), joystick and fire button",
    "Out: an order queued by orderSelectedUnitToCursor / sendGroupMoveToCursor / orderAttackUnitAtCursor,",
    "     or pickedCellCol/Row ($91D8/$91D9) for a free pick; cursorTargetMode = 0, groupBoxActive = 0,",
    "     sprites 0 and 1 blanked and un-expanded, sprite 2 back to the box cursor",
    "Called from: the unit destination flow ($4BE1, $5F64, $650A) and overlay A's setup screen ($83FF)"
   ],
   "line": "no unit"
  },
  "2264": {
   "unit": "game/mainProgram0800",
   "routine": [
    "previewPathToCursor - Idle-time animation: while the player leaves the controls alone with a move",
    "cursor up, the marker sprite crawls along the straight line from the selected unit to the cursor",
    "cell so the intended route can be seen on the strategic mini-map.  The target is re-read every pass",
    "(the view may still scroll) and the animation stops as soon as checkUserInterrupt reports stick,",
    "button, key or link activity.",
    "In:  selectedUnit ($90F8), viewOriginCol/Row, unit arrays $F640/$F6A4/$F76C",
    "Out: lineEndCol/lineEndRow (zp_95/zp_94), marker state $24E1-$24E3, sprite 1 animated and finally",
    "     hidden",
    "Called from: runTargetCursorLoop ($21CB) after 90 idle frames"
   ],
   "line": "the unit being ordered"
  },
  "22A2": {
   "unit": "game/mainProgram0800",
   "routine": [
    "showUnitInfoPanel - Fills the info panel under the tactical view (text window rows 12-20, columns",
    "25-38) with the description of unit selectedUnit: the type name and unit number, the GROUP /",
    "ENERGY / WEAPON / ACTION labels, the values printed by printUnitStatusLines and the three sprites",
    "that make up the unit picture.  It then holds that display while the cursor rests on the unit,",
    "reprinting the values whenever the unit is being stepped or shot at or a lock-step exchange has",
    "been applied, and animating the destination marker.  Moving off the unit, an interrupt or the end",
    "of the placement phase clears the panel and the picture sprites again.",
    "In:  selectedUnit ($90F8), uiLevel ($9209), the unit arrays, stepDelayTimer ($90FC), currentUnit",
    "     ($90FA), fireTargetUnit ($91F0), exchangeAppliedFlag ($920E), highlightedGroupKey ($9205),",
    "     cursorTargetMode, gamePhase",
    "Out: panel drawn and cleared again; infoPanelUnit ($91D7) = the unit while it is shown and $FF",
    "     afterwards; textCentreWidth = 0; sprites 5-7 disabled; text window reset to full screen.",
    "     Self-modifies the LDX # operand at $22EF+1",
    "Called from: runMapMainLoop ($2070, hovering), handleOwnUnitClick ($5E4C) and the console unit list",
    "     ($63F5)"
   ],
   "line": "clear"
  },
  "1C63": {
   "unit": "game/mainProgram0800",
   "block": [
    "Tail of runMainMenu, reached for menu items 0-4 (0 COMPETE WITH MODEM OPPONENT, 1 PRACTICE WITH",
    "SOLO TRAINER, 2 WATCH GAME FILM, 3 SAVE GAME FILM, 4 LOAD GAME FILM; item 5 DISCONNECT/SET VOICE",
    "was dealt with at $1C60).  The chosen item is still in Y and is acted on by overlay A."
   ],
   "line": "15 frames"
  },
  "1D4C": {
   "unit": "game/mainProgram0800",
   "block": [
    "scrollTargetUnit / scrollStepDelay - the two parameters of scrollViewToUnit.  They live in memory",
    "because that routine gives the registers up to updateGameFrame between steps."
   ],
   "line": "scrollTargetUnit: unit index the view is being scrolled onto"
  },
  "1D4D": {
   "unit": "game/mainProgram0800",
   "block": [
    "scrollStepDelay - frames between two scroll steps; 0 makes scrollViewToUnit jump to the unit in one",
    "go without animating."
   ],
   "line": "scrollStepDelay: frames between steps; 0 = jump there without animating"
  },
  "1DA2": {
   "unit": "game/mainProgram0800",
   "block": [
    "Parameter tables for scrollViewBitmap: 22 tables of three bytes, all indexed by a scroll direction",
    "biased to 0/1/2 = -1/0/+1.  The 9 tables read with X describe the horizontal shift (which byte or",
    "character range of a row is copied, and in which direction), the 13 read with Y describe the",
    "vertical shift (which character row the copy starts on, where it goes and the row stride).",
    "The view is 7x5 map cells of 2x2 characters at screen column 25, character row 1: bitmap $A208,",
    "screen RAM $8C41, colour RAM $D841 (screen RAM + $4C00).  One character row of it is 14 characters",
    "= 112 consecutive bitmap bytes, and one map cell is 2 characters = 16 bitmap bytes.",
    "scrollXStartIndex - first Y index of the bitmap byte copy."
   ],
   "line": "$5F,$00,$00 = first Y index of the bitmap byte copy (count down from $5F, or up from 0)"
  },
  "1E81": {
   "unit": "game/mainProgram0800",
   "block": [
    "One character row of the view: move 96 bytes (a 12-character window shifted by one map cell) or all",
    "112 bytes of the row in the bitmap, then the matching 12 or 14 bytes of screen RAM and of colour",
    "RAM.  Every operand that the listing shows as irqVectorHi ($FFFF) is a placeholder that was",
    "rewritten above, and the INY at $1E89/$1EBE may have been replaced by a DEY, so that a shifted copy",
    "walks away from the overlap instead of into it."
   ],
   "line": "start index, patched at $1DEF ($5F downwards, or 0 upwards)"
  },
  "1EFB": {
   "unit": "game/mainProgram0800",
   "block": [
    "The cells that scrolled into sight still hold the old picture: redraw the newly exposed column",
    "(5 cells) and/or the newly exposed row (7 cells) straight from the map.  $54/$55/$56 are the cell",
    "byte / column / row triple that drawViewportCell expects."
   ],
   "line": "left edge of the view"
  },
  "1FA6": {
   "unit": "game/mainProgram0800",
   "block": [
    "joystickDirectionTable - joystick nibble to direction code.  The raster IRQ latches bits 0-4 of",
    "CIA1 port A (joystick 2) into zp_5D, active low, so $0F means centred: bit 0 up, bit 1 down,",
    "bit 2 left, bit 3 right.  The table maps that nibble to a direction code 0-7 (0 N, 1 S, 2 E, 3 W,",
    "4 NW, 5 NE, 6 SE, 7 SW) or to $FF for 'no usable direction'.  Logically it is 16 bytes; the last",
    "four ($FF,$01,$00,$FF for nibbles $0C-$0F) were split off by the disassembler and are the first",
    "four bytes of the block labelled diagonalDirectionTable."
   ],
   "line": "nibbles $00-$07: only $05 (down+right) -> 6 SE, $06 (up+right) -> 5 NE and $07 (right) -> 2 E are real"
  },
  "1FB2": {
   "unit": "game/mainProgram0800",
   "block": [
    "$1FB2-$1FB5 are still joystickDirectionTable (nibble $0C impossible, $0D = down -> 1 S, $0E = up ->",
    "0 N, $0F = centred -> $FF).",
    "diagonalDirectionTable proper is $1FB6-$1FB9: for direction codes 4-7 it packs (index of the column",
    "delta << 4) | index of the row delta into directionDeltaTable - NW = (-1,-1), NE = (+1,-1),",
    "SE = (+1,+1), SW = (-1,+1).  scrollViewportByDirection ($2111), the comcen screens ($736F, $76CF)",
    "and the drone AI ($EF58) all index it with the direction code, so only offsets 4-7 are ever read.",
    "The eight bytes at $1FBA (01 00 03 02 06 07 04 05) are an opposite-direction table - N<->S, E<->W,",
    "NW<->SE, NE<->SW - that no unit references; together with the $00 pad at $1FC2 they look like",
    "leftover data."
   ],
   "line": "$1FB2-$1FB5 finish joystickDirectionTable; $1FB6-$1FB9 = diagonals 4-7; $1FBA-$1FC1 unused"
  },
  "2018": {
   "unit": "game/mainProgram0800",
   "block": [
    "Main loop.  One pass is one game frame plus one look at the joystick."
   ],
   "line": "remember where the player is looking..."
  },
  "2042": {
   "unit": "game/mainProgram0800",
   "block": [
    "Hovering: after 15 idle frames on one of your own units the info panel is opened.  Everything below",
    "runs only while the stick is centred."
   ],
   "line": "$90EE = frames since the last stick or button activity"
  },
  "2076": {
   "unit": "game/mainProgram0800",
   "block": [
    "Fire button: select one of your own units, or - during unit placement with DESTROY mode armed -",
    "send the self-destruct command for it."
   ],
   "line": "A = 0 only for a fresh click that counts (not in a film, not after game over)"
  },
  "20D7": {
   "unit": "game/mainProgram0800",
   "block": [
    "Joystick scrolling with auto-repeat.  Reached from every path above, so a click and a scroll can",
    "never happen in the same pass."
   ],
   "line": "zp_6C, the auto-repeat delay counted down by the IRQ"
  },
  "20F0": {
   "unit": "game/mainProgram0800",
   "block": [
    "directionDeltaTable - the signed one-cell steps shared by every direction decoder in the game (the",
    "comcen missile screen and the drone AI read the same four bytes): index 0 = row delta for north,",
    "1 = row delta for south, 2 = column delta for east, 3 = column delta for west."
   ],
   "line": "$FF,$01,$01,$FF = -1 north, +1 south, +1 east, -1 west"
  },
  "212E": {
   "unit": "game/mainProgram0800",
   "block": [
    "In BOOMER target mode the cursor may not leave the unit's firing range, so the step is tried out",
    "on paper first: squared distance from the unit to the cell we would move to, against r*(r+1) for",
    "the unit's range."
   ],
   "line": "zp_BB: 0 = move order, >0 = range-limited target, <0 = free cell pick"
  },
  "21AE": {
   "unit": "game/mainProgram0800",
   "block": [
    "Cursor loop: one pass per frame until the player clicks, the unit dies or the UI is aborted."
   ],
   "line": "zp_B1 bit 6 marks the phases in which the engine must not run"
  },
  "2239": {
   "unit": "game/mainProgram0800",
   "block": [
    "Leaving the target cursor: put the sprites and the cursor mode back the way the battlefield loop",
    "expects them.  $2239 re-centres the view on the unit first; $223C is entered directly when the UI",
    "was aborted."
   ],
   "line": "scroll the view back until the unit is under the cursor again"
  },
  "2302": {
   "unit": "game/mainProgram0800",
   "block": [
    "Panel loop: hold the display while the cursor stays on this unit, reprinting the values only when",
    "something can actually have changed."
   ],
   "line": "$90FC: is an engine step due?"
  },
  "234D": {
   "unit": "game/mainProgram0800",
   "block": [
    "Leaving the panel: wipe it, drop the marker and the three unit-picture sprites."
   ],
   "line": "no unit"
  },
  "1C65": {
   "unit": "game/mainProgram0800",
   "line": "let the player see the item he picked before the drive starts up"
  },
  "1C68": {
   "unit": "game/mainProgram0800",
   "line": "menu item 1 = PRACTICE WITH SOLO TRAINER?"
  },
  "1C6A": {
   "unit": "game/mainProgram0800",
   "line": "any other choice leaves an open modem link alone"
  },
  "1C6C": {
   "unit": "game/mainProgram0800",
   "line": "a solo game has nobody on the line: send $8C reason 5 and hang up first"
  },
  "1C6F": {
   "unit": "game/mainProgram0800",
   "line": "$0BA7 non-zero makes the raster IRQ swallow keys while the drive is busy"
  },
  "1C72": {
   "unit": "game/mainProgram0800",
   "line": "reload the $6F00 overlay variant named by $87FF plus the message strings and comm tail"
  },
  "1C75": {
   "unit": "game/mainProgram0800",
   "line": "load finished - the keyboard is live again"
  },
  "1C78": {
   "unit": "game/mainProgram0800",
   "line": "enter overlay A at $7DE3, which acts on menu item Y; it never comes back here"
  },
  "1C7E": {
   "unit": "game/mainProgram0800",
   "line": "keep only the game type 0-6 (SCRIMAGE, QB SNEAK, THE BOMB, FACE-OFF, SLUGGERS, FULL WAR, DEFENDER)"
  },
  "1C80": {
   "unit": "game/mainProgram0800",
   "line": "type 1 = QB SNEAK: one side sneaks, the other defends"
  },
  "1C82": {
   "unit": "game/mainProgram0800",
   "line": "asymmetric - return with Z=1"
  },
  "1C84": {
   "unit": "game/mainProgram0800",
   "line": "type 6 = DEFENDER, the other asymmetric scenario; this compare sets the returned Z"
  },
  "1C86": {
   "unit": "game/mainProgram0800",
   "line": "Z=1 the two sides have different jobs, Z=0 both play the same game"
  },
  "1C8A": {
   "unit": "game/mainProgram0800",
   "line": "not a film: the live settings are already the real ones"
  },
  "1C8C": {
   "unit": "game/mainProgram0800",
   "line": "leave film mode"
  },
  "1C8E": {
   "unit": "game/mainProgram0800",
   "line": "$0B9B = 0 also lets outgoing commands be queued again"
  },
  "1C91": {
   "unit": "game/mainProgram0800",
   "line": "$0BA4 = the game options saved before the film was started"
  },
  "1C94": {
   "unit": "game/mainProgram0800",
   "line": "$FF marks 'nothing was ever saved here'"
  },
  "1C96": {
   "unit": "game/mainProgram0800",
   "line": "no backup: keep whatever the film left behind"
  },
  "1C98": {
   "unit": "game/mainProgram0800",
   "line": "6 bytes: the 5-character map name plus its bit-7 terminator"
  },
  "1C9A": {
   "unit": "game/mainProgram0800",
   "line": "$0BAF+y = the saved map id text"
  },
  "1C9D": {
   "unit": "game/mainProgram0800",
   "line": "$0BA9+y = the map id the STATS tab and the setup summary print"
  },
  "1CA0": {
   "unit": "game/mainProgram0800",
   "line": "next byte down"
  },
  "1CA1": {
   "unit": "game/mainProgram0800",
   "line": "loop over all 6 bytes"
  },
  "1CA3": {
   "unit": "game/mainProgram0800",
   "line": "3 bytes of the 24-bit map seed"
  },
  "1CA5": {
   "unit": "game/mainProgram0800",
   "line": "$0B98+y = saved seed byte"
  },
  "1CA8": {
   "unit": "game/mainProgram0800",
   "line": "$0B95+y = the seed the generator turns into the battlefield"
  },
  "1CAB": {
   "unit": "game/mainProgram0800",
   "line": "next seed byte down"
  },
  "1CAC": {
   "unit": "game/mainProgram0800",
   "line": "loop over all three"
  },
  "1CAE": {
   "unit": "game/mainProgram0800",
   "line": "$0BA2 = who had the first play before the film"
  },
  "1CB1": {
   "unit": "game/mainProgram0800",
   "line": "$0BA1 drives the '1ST PLAY' / '2ND PLAY' line"
  },
  "1CB4": {
   "unit": "game/mainProgram0800",
   "line": "$0B9E = saved map orientation (0 normal, 1 rotated 180 degrees)"
  },
  "1CB7": {
   "unit": "game/mainProgram0800",
   "line": "$0B9D, read by the map generator"
  },
  "1CBA": {
   "unit": "game/mainProgram0800",
   "line": "and finally the scenario and rule bits"
  },
  "1CBD": {
   "unit": "game/mainProgram0800",
   "line": "$0BA3 back to the player's own game type"
  },
  "1CC0": {
   "unit": "game/mainProgram0800",
   "line": "done - the main menu can be reprinted"
  },
  "1CC3": {
   "unit": "game/mainProgram0800",
   "line": "zp_F8 bounds the centre (cursor) row of the view"
  },
  "1CC5": {
   "unit": "game/mainProgram0800",
   "line": "zp_B1 is 0 only while the battle proper runs"
  },
  "1CC7": {
   "unit": "game/mainProgram0800",
   "line": "battle: all 40 rows may be looked at"
  },
  "1CC9": {
   "unit": "game/mainProgram0800",
   "line": "20 rows"
  },
  "1CCB": {
   "unit": "game/mainProgram0800",
   "line": "setup/film/menu phase: a player may only see his own half of the map"
  },
  "1CCD": {
   "unit": "game/mainProgram0800",
   "line": "take the row value the caller handed in"
  },
  "1CCE": {
   "unit": "game/mainProgram0800",
   "line": "prepare an exact subtraction"
  },
  "1CCF": {
   "unit": "game/mainProgram0800",
   "line": "shift it down by 20 so map rows 20-39 test as 0-19"
  },
  "1CD1": {
   "unit": "game/mainProgram0800",
   "line": "hand the biased row back in Y"
  },
  "1CD2": {
   "unit": "game/mainProgram0800",
   "line": "zp_F8 is set either way"
  },
  "1CD5": {
   "unit": "game/mainProgram0800",
   "line": "and the requested row step"
  },
  "1CD7": {
   "unit": "game/mainProgram0800",
   "line": "zp_F8 = 40 or 20; in the restricted phases Y comes back biased by -20"
  },
  "1CDA": {
   "unit": "game/mainProgram0800",
   "line": "row delta"
  },
  "1CDB": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1CDC": {
   "unit": "game/mainProgram0800",
   "line": "candidate new top row of the view"
  },
  "1CDE": {
   "unit": "game/mainProgram0800",
   "line": "keep the candidate origin in Y"
  },
  "1CDF": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1CE0": {
   "unit": "game/mainProgram0800",
   "line": "the cursor cell sits 2 rows below the top edge of the 5-cell-high view"
  },
  "1CE2": {
   "unit": "game/mainProgram0800",
   "line": "is the new cursor row still inside the allowed rows?"
  },
  "1CE4": {
   "unit": "game/mainProgram0800",
   "line": "off the map (or out of your own half): refuse the vertical step"
  },
  "1CE6": {
   "unit": "game/mainProgram0800",
   "line": "$920A bit 7 = a group formation box is riding on the cursor"
  },
  "1CE9": {
   "unit": "game/mainProgram0800",
   "line": "no box: the centre cell alone decides"
  },
  "1CEB": {
   "unit": "game/mainProgram0800",
   "line": "self-modifying: park the candidate cursor row in the LDA # operand at $1CF6+1"
  },
  "1CEE": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1CEF": {
   "unit": "game/mainProgram0800",
   "line": "$9231 = signed offset from the cursor to the top edge of the box"
  },
  "1CF2": {
   "unit": "game/mainProgram0800",
   "line": "unsigned compare, so a wrapped (negative) row fails too"
  },
  "1CF4": {
   "unit": "game/mainProgram0800",
   "line": "the box would hang off the top of the map: refuse"
  },
  "1CF6": {
   "unit": "game/mainProgram0800",
   "line": "operand written at $1CEB: the candidate cursor row"
  },
  "1CF8": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1CF9": {
   "unit": "game/mainProgram0800",
   "line": "$9230 = signed offset to the bottom edge of the box"
  },
  "1CFC": {
   "unit": "game/mainProgram0800",
   "line": "bottom edge still on the map?"
  },
  "1CFE": {
   "unit": "game/mainProgram0800",
   "line": "the box would hang off the bottom: refuse"
  },
  "1D00": {
   "unit": "game/mainProgram0800",
   "line": "which phase are we in?"
  },
  "1D02": {
   "unit": "game/mainProgram0800",
   "line": "battle: the candidate row needs no correction"
  },
  "1D04": {
   "unit": "game/mainProgram0800",
   "line": "candidate origin row, still biased"
  },
  "1D05": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1D06": {
   "unit": "game/mainProgram0800",
   "line": "undo the -20 bias setViewVerticalLimit applied"
  },
  "1D08": {
   "unit": "game/mainProgram0800",
   "line": "back into Y for the store below"
  },
  "1D09": {
   "unit": "game/mainProgram0800",
   "line": "accept the vertical scroll"
  },
  "1D0B": {
   "unit": "game/mainProgram0800",
   "line": "now try the horizontal step"
  },
  "1D0E": {
   "unit": "game/mainProgram0800",
   "line": "no vertical movement"
  },
  "1D10": {
   "unit": "game/mainProgram0800",
   "line": "vertical step clamped: tell scrollViewBitmap not to shift any rows"
  },
  "1D12": {
   "unit": "game/mainProgram0800",
   "line": "column delta"
  },
  "1D13": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1D14": {
   "unit": "game/mainProgram0800",
   "line": "candidate new left column of the view"
  },
  "1D16": {
   "unit": "game/mainProgram0800",
   "line": "keep the candidate origin in X"
  },
  "1D17": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1D18": {
   "unit": "game/mainProgram0800",
   "line": "the cursor cell sits 3 columns right of the left edge of the 7-cell-wide view"
  },
  "1D1A": {
   "unit": "game/mainProgram0800",
   "line": "40 map columns"
  },
  "1D1C": {
   "unit": "game/mainProgram0800",
   "line": "off the map: refuse the horizontal step"
  },
  "1D1E": {
   "unit": "game/mainProgram0800",
   "line": "group formation box active?"
  },
  "1D21": {
   "unit": "game/mainProgram0800",
   "line": "no box: accept"
  },
  "1D23": {
   "unit": "game/mainProgram0800",
   "line": "self-modifying: park the candidate cursor column in the LDA # operand at $1D2E+1"
  },
  "1D26": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1D27": {
   "unit": "game/mainProgram0800",
   "line": "$922F = signed offset to the left edge of the box"
  },
  "1D2A": {
   "unit": "game/mainProgram0800",
   "line": "left edge still on the map?"
  },
  "1D2C": {
   "unit": "game/mainProgram0800",
   "line": "box would stick out to the left: refuse"
  },
  "1D2E": {
   "unit": "game/mainProgram0800",
   "line": "operand written at $1D23: the candidate cursor column"
  },
  "1D30": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1D31": {
   "unit": "game/mainProgram0800",
   "line": "$922E = signed offset to the right edge of the box"
  },
  "1D34": {
   "unit": "game/mainProgram0800",
   "line": "right edge still on the map?"
  },
  "1D36": {
   "unit": "game/mainProgram0800",
   "line": "box would stick out to the right: refuse"
  },
  "1D38": {
   "unit": "game/mainProgram0800",
   "line": "accept the horizontal scroll"
  },
  "1D3A": {
   "unit": "game/mainProgram0800",
   "line": "both axes decided"
  },
  "1D3D": {
   "unit": "game/mainProgram0800",
   "line": "no horizontal movement"
  },
  "1D3F": {
   "unit": "game/mainProgram0800",
   "line": "horizontal step clamped: no column shift"
  },
  "1D41": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0"
  },
  "1D43": {
   "unit": "game/mainProgram0800",
   "line": "$91CC = the sprite positionCursorSprite works on: the mini-map cursor box"
  },
  "1D46": {
   "unit": "game/mainProgram0800",
   "line": "move it onto the new centre cell (the mini-map draws each map cell 4x4 pixels)"
  },
  "1D49": {
   "unit": "game/mainProgram0800",
   "line": "shift bitmap, screen and colour RAM and redraw the cells that came into sight"
  },
  "1D51": {
   "unit": "game/mainProgram0800",
   "line": "unit to centre the view on"
  },
  "1D54": {
   "unit": "game/mainProgram0800",
   "line": "line end point (zp_95/zp_94) = the unit's map cell"
  },
  "1D57": {
   "unit": "game/mainProgram0800",
   "line": "set up the Bresenham walk from the cursor cell to the unit"
  },
  "1D5A": {
   "unit": "game/mainProgram0800",
   "line": "animated or immediate?"
  },
  "1D5D": {
   "unit": "game/mainProgram0800",
   "line": "no animation wanted: take the next step straight away"
  },
  "1D5F": {
   "unit": "game/mainProgram0800",
   "line": "updateGameFrame steps the units through the same line stepper,"
  },
  "1D61": {
   "unit": "game/mainProgram0800",
   "line": "so save the walker's row"
  },
  "1D62": {
   "unit": "game/mainProgram0800",
   "line": "and its column"
  },
  "1D64": {
   "unit": "game/mainProgram0800",
   "line": "on the stack across the call"
  },
  "1D65": {
   "unit": "game/mainProgram0800",
   "line": "one frame of engine, message, sound and link work"
  },
  "1D68": {
   "unit": "game/mainProgram0800",
   "line": "restore our own walk position"
  },
  "1D69": {
   "unit": "game/mainProgram0800",
   "line": "zp_93 = current column"
  },
  "1D6B": {
   "unit": "game/mainProgram0800",
   "line": "and the row"
  },
  "1D6C": {
   "unit": "game/mainProgram0800",
   "line": "zp_92"
  },
  "1D6E": {
   "unit": "game/mainProgram0800",
   "line": "zp_5D = joystick nibble latched by the raster IRQ"
  },
  "1D70": {
   "unit": "game/mainProgram0800",
   "line": "$0F = stick centred"
  },
  "1D72": {
   "unit": "game/mainProgram0800",
   "line": "the player took over: stop the automatic scroll"
  },
  "1D74": {
   "unit": "game/mainProgram0800",
   "line": "zp_6E, an IRQ frame countdown reused here as the step timer"
  },
  "1D76": {
   "unit": "game/mainProgram0800",
   "line": "this step is not due yet: spin another frame"
  },
  "1D78": {
   "unit": "game/mainProgram0800",
   "line": "step delay"
  },
  "1D7B": {
   "unit": "game/mainProgram0800",
   "line": "reload the timer for the next step"
  },
  "1D7D": {
   "unit": "game/mainProgram0800",
   "line": "the unit we are chasing"
  },
  "1D80": {
   "unit": "game/mainProgram0800",
   "line": "re-read its position: it may have moved a cell meanwhile"
  },
  "1D83": {
   "unit": "game/mainProgram0800",
   "line": "re-aim the line from the cell we are on now"
  },
  "1D86": {
   "unit": "game/mainProgram0800",
   "line": "walk one cell; the applied deltas land in $90F4/$90F5"
  },
  "1D89": {
   "unit": "game/mainProgram0800",
   "line": "$90F5 = column step just taken (-1/0/+1)"
  },
  "1D8C": {
   "unit": "game/mainProgram0800",
   "line": "$90F4 = row step just taken (-1/0/+1)"
  },
  "1D8F": {
   "unit": "game/mainProgram0800",
   "line": "scroll the view by that one cell"
  },
  "1D92": {
   "unit": "game/mainProgram0800",
   "line": "zp_91 = steps still to walk"
  },
  "1D94": {
   "unit": "game/mainProgram0800",
   "line": "keep going until the unit is under the cursor"
  },
  "1D96": {
   "unit": "game/mainProgram0800",
   "line": "view centred (or the player interrupted)"
  },
  "1D9A": {
   "unit": "game/mainProgram0800",
   "line": "zp_95 = target column of the line stepper"
  },
  "1D9C": {
   "unit": "game/mainProgram0800",
   "line": "$F6A4+unit = the unit's map row (the next 100-byte array, $64 further on)"
  },
  "1D9F": {
   "unit": "game/mainProgram0800",
   "line": "zp_94 = target row"
  },
  "1DA1": {
   "unit": "game/mainProgram0800",
   "line": "the caller now calls initLineStepper"
  },
  "1DA5": {
   "unit": "game/mainProgram0800",
   "line": "$FF,$70,$60 = index the copy stops at: $5F..0 down, 0..$6F up, 0..$5F up"
  },
  "1DA8": {
   "unit": "game/mainProgram0800",
   "line": "$88 DEY (copy right, backwards) or $C8 INY (copy left / straight, forwards)"
  },
  "1DAB": {
   "unit": "game/mainProgram0800",
   "line": "$00,$00,$10 = +16 bytes (2 characters = 1 map cell) is added to the source when the view moves right"
  },
  "1DAE": {
   "unit": "game/mainProgram0800",
   "line": "$10,$00,$00 = the same 16-byte shift on the destination when the view moves left"
  },
  "1DB1": {
   "unit": "game/mainProgram0800",
   "line": "$C8,$08,$88 = low byte of $AAC8/$A208/$A488, the first bitmap row copied (char rows 8/1/3)"
  },
  "1DB4": {
   "unit": "game/mainProgram0800",
   "line": "$AA,$A2,$A4 = high bytes of those three bitmap addresses"
  },
  "1DB7": {
   "unit": "game/mainProgram0800",
   "line": "$48,$08,$08 = low byte of $AD48/$A208/$A208, where that row is copied to (char rows 10/1/1)"
  },
  "1DBA": {
   "unit": "game/mainProgram0800",
   "line": "$AD,$A2,$A2 = high bytes of the destinations"
  },
  "1DBD": {
   "unit": "game/mainProgram0800",
   "line": "$C0,$40,$40 = low byte of the row stride -320/+320/+320 (one character row of the bitmap)"
  },
  "1DC0": {
   "unit": "game/mainProgram0800",
   "line": "$FE,$01,$01 = high byte of the same stride"
  },
  "1DC3": {
   "unit": "game/mainProgram0800",
   "line": "8,10,8 character rows to copy: all 10 when only columns move, 8 when 2 rows are freed"
  },
  "1DC6": {
   "unit": "game/mainProgram0800",
   "line": "$0B,$00,$00 = first Y index of the 14-character screen/colour copy"
  },
  "1DC9": {
   "unit": "game/mainProgram0800",
   "line": "$FF,$0E,$0C = index it stops at: $0B..0 down, 0..$0D up, 0..$0B up"
  },
  "1DCC": {
   "unit": "game/mainProgram0800",
   "line": "$00,$00,$02 = 2 characters added to the screen source when the view moves right"
  },
  "1DCF": {
   "unit": "game/mainProgram0800",
   "line": "$02,$00,$00 = 2 characters added to the screen destination when the view moves left"
  },
  "1DD2": {
   "unit": "game/mainProgram0800",
   "line": "$59,$41,$91 = low byte of $8D59/$8C41/$8C91: screen RAM column 25 of char rows 8/1/3"
  },
  "1DD5": {
   "unit": "game/mainProgram0800",
   "line": "$8D,$8C,$8C = high bytes; the colour RAM copy uses the same addresses + $4C00"
  },
  "1DD8": {
   "unit": "game/mainProgram0800",
   "line": "$A9,$41,$41 = low byte of $8DA9/$8C41/$8C41: column 25 of char rows 10/1/1"
  },
  "1DDB": {
   "unit": "game/mainProgram0800",
   "line": "$8D,$8C,$8C = high bytes of the screen destinations"
  },
  "1DDE": {
   "unit": "game/mainProgram0800",
   "line": "$D8,$28,$28 = low byte of the screen row stride -40/+40/+40"
  },
  "1DE1": {
   "unit": "game/mainProgram0800",
   "line": "$FF,$00,$00 = high byte of the screen row stride"
  },
  "1DE6": {
   "unit": "game/mainProgram0800",
   "line": "X selects the horizontal-shift parameters"
  },
  "1DE8": {
   "unit": "game/mainProgram0800",
   "line": "same for the row step"
  },
  "1DEA": {
   "unit": "game/mainProgram0800",
   "line": "Y selects the vertical-shift parameters"
  },
  "1DEC": {
   "unit": "game/mainProgram0800",
   "line": "start index of the bitmap copy"
  },
  "1DEF": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDY # that opens the bitmap loop"
  },
  "1DF2": {
   "unit": "game/mainProgram0800",
   "line": "end index of the bitmap copy"
  },
  "1DF5": {
   "unit": "game/mainProgram0800",
   "line": "patch the CPY # that closes it"
  },
  "1DF8": {
   "unit": "game/mainProgram0800",
   "line": "first bitmap row to read from"
  },
  "1DFB": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1DFC": {
   "unit": "game/mainProgram0800",
   "line": "+16 bytes = one map cell to the right on the source when the view scrolls right"
  },
  "1DFF": {
   "unit": "game/mainProgram0800",
   "line": "patch the low byte of the LDA abs,y source operand"
  },
  "1E02": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the source row address"
  },
  "1E05": {
   "unit": "game/mainProgram0800",
   "line": "take the carry out of the low byte"
  },
  "1E07": {
   "unit": "game/mainProgram0800",
   "line": "patch the high byte of the source operand"
  },
  "1E0A": {
   "unit": "game/mainProgram0800",
   "line": "bitmap row to write to"
  },
  "1E0D": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1E0E": {
   "unit": "game/mainProgram0800",
   "line": "+16 bytes on the destination instead when the view scrolls left"
  },
  "1E11": {
   "unit": "game/mainProgram0800",
   "line": "patch the low byte of the STA abs,y destination operand"
  },
  "1E14": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the destination row address"
  },
  "1E17": {
   "unit": "game/mainProgram0800",
   "line": "carry out of the low byte"
  },
  "1E19": {
   "unit": "game/mainProgram0800",
   "line": "patch the high byte of the destination operand"
  },
  "1E1C": {
   "unit": "game/mainProgram0800",
   "line": "$88 DEY or $C8 INY"
  },
  "1E1F": {
   "unit": "game/mainProgram0800",
   "line": "patch the step instruction of the bitmap loop"
  },
  "1E22": {
   "unit": "game/mainProgram0800",
   "line": "8 or 10 character rows"
  },
  "1E25": {
   "unit": "game/mainProgram0800",
   "line": "zp_F6 counts the rows still to move"
  },
  "1E27": {
   "unit": "game/mainProgram0800",
   "line": "+320 or -320 = one character row of the bitmap"
  },
  "1E2A": {
   "unit": "game/mainProgram0800",
   "line": "zp_F2 low byte of the bitmap row stride"
  },
  "1E2C": {
   "unit": "game/mainProgram0800",
   "line": "sign/high byte of the stride"
  },
  "1E2F": {
   "unit": "game/mainProgram0800",
   "line": "zp_F3"
  },
  "1E31": {
   "unit": "game/mainProgram0800",
   "line": "start index of the screen/colour copy"
  },
  "1E34": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDY # that opens the screen loop"
  },
  "1E37": {
   "unit": "game/mainProgram0800",
   "line": "end index of the screen copy"
  },
  "1E3A": {
   "unit": "game/mainProgram0800",
   "line": "patch its CPY #"
  },
  "1E3D": {
   "unit": "game/mainProgram0800",
   "line": "first screen RAM row to read from"
  },
  "1E40": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1E41": {
   "unit": "game/mainProgram0800",
   "line": "+2 characters on the source when the view scrolls right"
  },
  "1E44": {
   "unit": "game/mainProgram0800",
   "line": "patch the screen RAM source operand (low)"
  },
  "1E47": {
   "unit": "game/mainProgram0800",
   "line": "the colour RAM source has the same low byte"
  },
  "1E4A": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the screen row address ($8C/$8D)"
  },
  "1E4D": {
   "unit": "game/mainProgram0800",
   "line": "carry out of the low byte"
  },
  "1E4F": {
   "unit": "game/mainProgram0800",
   "line": "patch the screen RAM source operand (high) - screen RAM is at $8C00"
  },
  "1E52": {
   "unit": "game/mainProgram0800",
   "line": "+$4C00 turns a screen RAM address into the matching colour RAM address ($D800)"
  },
  "1E54": {
   "unit": "game/mainProgram0800",
   "line": "patch the colour RAM source operand (high)"
  },
  "1E57": {
   "unit": "game/mainProgram0800",
   "line": "screen RAM row to write to"
  },
  "1E5A": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1E5B": {
   "unit": "game/mainProgram0800",
   "line": "+2 characters on the destination instead when the view scrolls left"
  },
  "1E5E": {
   "unit": "game/mainProgram0800",
   "line": "patch the screen RAM destination operand (low)"
  },
  "1E61": {
   "unit": "game/mainProgram0800",
   "line": "colour RAM destination shares the low byte"
  },
  "1E64": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the destination row"
  },
  "1E67": {
   "unit": "game/mainProgram0800",
   "line": "carry out of the low byte"
  },
  "1E69": {
   "unit": "game/mainProgram0800",
   "line": "patch the screen RAM destination operand (high)"
  },
  "1E6C": {
   "unit": "game/mainProgram0800",
   "line": "same $4C00 bias into colour RAM"
  },
  "1E6E": {
   "unit": "game/mainProgram0800",
   "line": "patch the colour RAM destination operand (high)"
  },
  "1E71": {
   "unit": "game/mainProgram0800",
   "line": "$88 DEY or $C8 INY again"
  },
  "1E74": {
   "unit": "game/mainProgram0800",
   "line": "patch the step instruction of the screen loop"
  },
  "1E77": {
   "unit": "game/mainProgram0800",
   "line": "+40 or -40 = one screen row"
  },
  "1E7A": {
   "unit": "game/mainProgram0800",
   "line": "zp_F4 low byte of the screen row stride"
  },
  "1E7C": {
   "unit": "game/mainProgram0800",
   "line": "sign/high byte"
  },
  "1E7F": {
   "unit": "game/mainProgram0800",
   "line": "zp_F5"
  },
  "1E83": {
   "unit": "game/mainProgram0800",
   "line": "source byte of this bitmap row (operand patched at $1DFF/$1E07)"
  },
  "1E86": {
   "unit": "game/mainProgram0800",
   "line": "destination byte (operand patched at $1E11/$1E19)"
  },
  "1E89": {
   "unit": "game/mainProgram0800",
   "line": "INY or DEY, patched at $1E1F: copy forwards or backwards so a shift cannot eat itself"
  },
  "1E8A": {
   "unit": "game/mainProgram0800",
   "line": "end index, patched at $1DF5"
  },
  "1E8C": {
   "unit": "game/mainProgram0800",
   "line": "next byte of this character row (96 or 112 bytes)"
  },
  "1E8E": {
   "unit": "game/mainProgram0800",
   "line": "step the destination pointer one character row on"
  },
  "1E91": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1E92": {
   "unit": "game/mainProgram0800",
   "line": "+320 or -320"
  },
  "1E94": {
   "unit": "game/mainProgram0800",
   "line": "write the new low byte back into the STA operand"
  },
  "1E97": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the destination"
  },
  "1E9A": {
   "unit": "game/mainProgram0800",
   "line": "with the sign of the stride"
  },
  "1E9C": {
   "unit": "game/mainProgram0800",
   "line": "write it back"
  },
  "1E9F": {
   "unit": "game/mainProgram0800",
   "line": "the same for the source pointer"
  },
  "1EA2": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1EA3": {
   "unit": "game/mainProgram0800",
   "line": "+320 or -320"
  },
  "1EA5": {
   "unit": "game/mainProgram0800",
   "line": "write the new low byte into the LDA operand"
  },
  "1EA8": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the source"
  },
  "1EAB": {
   "unit": "game/mainProgram0800",
   "line": "with the sign of the stride"
  },
  "1EAD": {
   "unit": "game/mainProgram0800",
   "line": "write it back"
  },
  "1EB0": {
   "unit": "game/mainProgram0800",
   "line": "start index of the character copy, patched at $1E34"
  },
  "1EB2": {
   "unit": "game/mainProgram0800",
   "line": "screen RAM source (the tile's colour nibbles), operand patched at $1E44/$1E4F"
  },
  "1EB5": {
   "unit": "game/mainProgram0800",
   "line": "screen RAM destination, patched at $1E5E/$1E69"
  },
  "1EB8": {
   "unit": "game/mainProgram0800",
   "line": "the same character in colour RAM at $D800, patched at $1E47/$1E54"
  },
  "1EBB": {
   "unit": "game/mainProgram0800",
   "line": "colour RAM destination, patched at $1E61/$1E6E"
  },
  "1EBE": {
   "unit": "game/mainProgram0800",
   "line": "INY or DEY, patched at $1E74"
  },
  "1EBF": {
   "unit": "game/mainProgram0800",
   "line": "end index, patched at $1E3A"
  },
  "1EC1": {
   "unit": "game/mainProgram0800",
   "line": "next character of this row (12 or 14 of them)"
  },
  "1EC3": {
   "unit": "game/mainProgram0800",
   "line": "step the screen destination one row on"
  },
  "1EC6": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1EC7": {
   "unit": "game/mainProgram0800",
   "line": "+40 or -40"
  },
  "1EC9": {
   "unit": "game/mainProgram0800",
   "line": "write it back into the screen STA operand"
  },
  "1ECC": {
   "unit": "game/mainProgram0800",
   "line": "and into the colour RAM STA operand, which shares the low byte"
  },
  "1ECF": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the screen destination"
  },
  "1ED2": {
   "unit": "game/mainProgram0800",
   "line": "with the sign of the stride"
  },
  "1ED4": {
   "unit": "game/mainProgram0800",
   "line": "write it back"
  },
  "1ED7": {
   "unit": "game/mainProgram0800",
   "line": "prepare the colour RAM bias"
  },
  "1ED8": {
   "unit": "game/mainProgram0800",
   "line": "screen page + $4C = colour RAM page"
  },
  "1EDA": {
   "unit": "game/mainProgram0800",
   "line": "write the colour RAM destination high byte"
  },
  "1EDD": {
   "unit": "game/mainProgram0800",
   "line": "step the screen source one row on"
  },
  "1EE0": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1EE1": {
   "unit": "game/mainProgram0800",
   "line": "+40 or -40"
  },
  "1EE3": {
   "unit": "game/mainProgram0800",
   "line": "write it back into the screen LDA operand"
  },
  "1EE6": {
   "unit": "game/mainProgram0800",
   "line": "and into the colour RAM LDA operand"
  },
  "1EE9": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the screen source"
  },
  "1EEC": {
   "unit": "game/mainProgram0800",
   "line": "with the sign of the stride"
  },
  "1EEE": {
   "unit": "game/mainProgram0800",
   "line": "write it back"
  },
  "1EF1": {
   "unit": "game/mainProgram0800",
   "line": "prepare the colour RAM bias"
  },
  "1EF2": {
   "unit": "game/mainProgram0800",
   "line": "+$4C pages = $D800"
  },
  "1EF4": {
   "unit": "game/mainProgram0800",
   "line": "write the colour RAM source high byte"
  },
  "1EF7": {
   "unit": "game/mainProgram0800",
   "line": "one character row of the view is done"
  },
  "1EF9": {
   "unit": "game/mainProgram0800",
   "line": "8 or 10 rows in all"
  },
  "1EFD": {
   "unit": "game/mainProgram0800",
   "line": "0/1/2 = the view moved left / not at all / right"
  },
  "1EFF": {
   "unit": "game/mainProgram0800",
   "line": "1 = no horizontal movement"
  },
  "1F01": {
   "unit": "game/mainProgram0800",
   "line": "nothing new appeared at the sides"
  },
  "1F03": {
   "unit": "game/mainProgram0800",
   "line": "moved left: the freshly exposed column is the left edge itself"
  },
  "1F05": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1F06": {
   "unit": "game/mainProgram0800",
   "line": "moved right: it is the 7th (rightmost) column of the view"
  },
  "1F08": {
   "unit": "game/mainProgram0800",
   "line": "$55 = column of the cell to draw"
  },
  "1F0A": {
   "unit": "game/mainProgram0800",
   "line": "top row of the view"
  },
  "1F0C": {
   "unit": "game/mainProgram0800",
   "line": "$56 = row of the cell to draw"
  },
  "1F0E": {
   "unit": "game/mainProgram0800",
   "line": "the view is 5 cells tall"
  },
  "1F10": {
   "unit": "game/mainProgram0800",
   "line": "zp_F7 counts the cells still to draw"
  },
  "1F12": {
   "unit": "game/mainProgram0800",
   "line": "X = map column"
  },
  "1F14": {
   "unit": "game/mainProgram0800",
   "line": "Y = map row"
  },
  "1F16": {
   "unit": "game/mainProgram0800",
   "line": "read the map byte: terrain code, or $80 | unit index when a unit stands there"
  },
  "1F19": {
   "unit": "game/mainProgram0800",
   "line": "$54 = the cell byte drawViewportCell expects"
  },
  "1F1B": {
   "unit": "game/mainProgram0800",
   "line": "render the 16x16 pixel tile into the bitmap"
  },
  "1F1E": {
   "unit": "game/mainProgram0800",
   "line": "next row down the exposed column"
  },
  "1F20": {
   "unit": "game/mainProgram0800",
   "line": "one cell done"
  },
  "1F22": {
   "unit": "game/mainProgram0800",
   "line": "5 cells of the column"
  },
  "1F24": {
   "unit": "game/mainProgram0800",
   "line": "top row of the view"
  },
  "1F26": {
   "unit": "game/mainProgram0800",
   "line": "0/1/2 = the view moved up / not at all / down"
  },
  "1F28": {
   "unit": "game/mainProgram0800",
   "line": "1 = no vertical movement"
  },
  "1F2A": {
   "unit": "game/mainProgram0800",
   "line": "nothing new appeared above or below: done"
  },
  "1F2C": {
   "unit": "game/mainProgram0800",
   "line": "moved up: the freshly exposed row is the top edge itself"
  },
  "1F2E": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1F2F": {
   "unit": "game/mainProgram0800",
   "line": "moved down: it is the 5th (bottom) row of the view"
  },
  "1F31": {
   "unit": "game/mainProgram0800",
   "line": "$56 = row of the cells to draw"
  },
  "1F33": {
   "unit": "game/mainProgram0800",
   "line": "start at the left edge"
  },
  "1F35": {
   "unit": "game/mainProgram0800",
   "line": "$55 = column of the cell to draw"
  },
  "1F37": {
   "unit": "game/mainProgram0800",
   "line": "the view is 7 cells wide"
  },
  "1F39": {
   "unit": "game/mainProgram0800",
   "line": "zp_F7 counts them down"
  },
  "1F3B": {
   "unit": "game/mainProgram0800",
   "line": "X = map column"
  },
  "1F3D": {
   "unit": "game/mainProgram0800",
   "line": "Y = map row"
  },
  "1F3F": {
   "unit": "game/mainProgram0800",
   "line": "read the map byte"
  },
  "1F42": {
   "unit": "game/mainProgram0800",
   "line": "$54 = cell byte"
  },
  "1F44": {
   "unit": "game/mainProgram0800",
   "line": "draw the 16x16 tile"
  },
  "1F47": {
   "unit": "game/mainProgram0800",
   "line": "next column along the exposed row"
  },
  "1F49": {
   "unit": "game/mainProgram0800",
   "line": "one cell done"
  },
  "1F4B": {
   "unit": "game/mainProgram0800",
   "line": "7 cells of the row"
  },
  "1F4D": {
   "unit": "game/mainProgram0800",
   "line": "the view now matches the map again"
  },
  "1F50": {
   "unit": "game/mainProgram0800",
   "line": "zp_19 = 0 means 'the view has not been nudged yet'"
  },
  "1F52": {
   "unit": "game/mainProgram0800",
   "line": "count this pass; any pass after the first leaves zp_19 non-zero"
  },
  "1F54": {
   "unit": "game/mainProgram0800",
   "line": "current top row of the view"
  },
  "1F56": {
   "unit": "game/mainProgram0800",
   "line": "zp_F8 = 40 or 20; Y comes back biased by -20 in the setup phase"
  },
  "1F59": {
   "unit": "game/mainProgram0800",
   "line": "top row"
  },
  "1F5A": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1F5B": {
   "unit": "game/mainProgram0800",
   "line": "cursor row = top edge + 2"
  },
  "1F5D": {
   "unit": "game/mainProgram0800",
   "line": "zp_18 keeps the cursor row for the second test"
  },
  "1F5F": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1F60": {
   "unit": "game/mainProgram0800",
   "line": "$9231 = signed offset to the top edge of the group box"
  },
  "1F63": {
   "unit": "game/mainProgram0800",
   "line": "still inside the allowed rows?"
  },
  "1F65": {
   "unit": "game/mainProgram0800",
   "line": "yes: test the bottom edge"
  },
  "1F67": {
   "unit": "game/mainProgram0800",
   "line": "box sticks out above the map: slide the view one row down"
  },
  "1F69": {
   "unit": "game/mainProgram0800",
   "line": "and start the four tests again"
  },
  "1F6C": {
   "unit": "game/mainProgram0800",
   "line": "cursor row again"
  },
  "1F6E": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1F6F": {
   "unit": "game/mainProgram0800",
   "line": "$9230 = signed offset to the bottom edge of the box"
  },
  "1F72": {
   "unit": "game/mainProgram0800",
   "line": "still inside the allowed rows?"
  },
  "1F74": {
   "unit": "game/mainProgram0800",
   "line": "yes: go on to the columns"
  },
  "1F76": {
   "unit": "game/mainProgram0800",
   "line": "box sticks out below: slide the view one row up"
  },
  "1F78": {
   "unit": "game/mainProgram0800",
   "line": "and re-test"
  },
  "1F7B": {
   "unit": "game/mainProgram0800",
   "line": "current left column of the view"
  },
  "1F7D": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1F7E": {
   "unit": "game/mainProgram0800",
   "line": "cursor column = left edge + 3"
  },
  "1F80": {
   "unit": "game/mainProgram0800",
   "line": "zp_18 keeps the cursor column"
  },
  "1F82": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1F83": {
   "unit": "game/mainProgram0800",
   "line": "$922F = signed offset to the left edge of the box"
  },
  "1F86": {
   "unit": "game/mainProgram0800",
   "line": "40 map columns"
  },
  "1F88": {
   "unit": "game/mainProgram0800",
   "line": "inside: test the right edge"
  },
  "1F8A": {
   "unit": "game/mainProgram0800",
   "line": "box sticks out to the left: slide the view one column right"
  },
  "1F8C": {
   "unit": "game/mainProgram0800",
   "line": "and re-test"
  },
  "1F8F": {
   "unit": "game/mainProgram0800",
   "line": "cursor column again"
  },
  "1F91": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "1F92": {
   "unit": "game/mainProgram0800",
   "line": "$922E = signed offset to the right edge of the box"
  },
  "1F95": {
   "unit": "game/mainProgram0800",
   "line": "40 map columns"
  },
  "1F97": {
   "unit": "game/mainProgram0800",
   "line": "the whole box fits: done"
  },
  "1F99": {
   "unit": "game/mainProgram0800",
   "line": "box sticks out to the right: slide the view one column left"
  },
  "1F9B": {
   "unit": "game/mainProgram0800",
   "line": "and re-test"
  },
  "1F9E": {
   "unit": "game/mainProgram0800",
   "line": "0 only if the box already fitted on the very first pass"
  },
  "1FA0": {
   "unit": "game/mainProgram0800",
   "line": "nothing moved: leave the screen alone"
  },
  "1FA2": {
   "unit": "game/mainProgram0800",
   "line": "the view moved: repaint all 35 cells and return from there"
  },
  "1FA5": {
   "unit": "game/mainProgram0800",
   "line": "view origin now keeps the whole group box on the map"
  },
  "1FC6": {
   "unit": "game/mainProgram0800",
   "line": "zp_A5 = map column of the left edge of the 7x5 view"
  },
  "1FC8": {
   "unit": "game/mainProgram0800",
   "line": "$92C4"
  },
  "1FCB": {
   "unit": "game/mainProgram0800",
   "line": "zp_A6 = map row of the top edge of the view"
  },
  "1FCD": {
   "unit": "game/mainProgram0800",
   "line": "rebuild frames, mini-map, tactical view and status bar, then fall into the main loop"
  },
  "1FD2": {
   "unit": "game/mainProgram0800",
   "line": "$9248: forget anything typed before the battlefield came up"
  },
  "1FD5": {
   "unit": "game/mainProgram0800",
   "line": "$91D7 = no unit is being shown in the info panel"
  },
  "1FD8": {
   "unit": "game/mainProgram0800",
   "line": "clear"
  },
  "1FDA": {
   "unit": "game/mainProgram0800",
   "line": "$24E1 = the mini-map path marker has no passes left to run"
  },
  "1FDD": {
   "unit": "game/mainProgram0800",
   "line": "zp_BB = 0: the cursor is a plain move cursor, not a target picker"
  },
  "1FDF": {
   "unit": "game/mainProgram0800",
   "line": "zp_41, a spare slot of the saved text state; nothing ever reads it (dead store)"
  },
  "1FE1": {
   "unit": "game/mainProgram0800",
   "line": "return to the part of the map the player was looking at"
  },
  "1FE4": {
   "unit": "game/mainProgram0800",
   "line": "zp_A5"
  },
  "1FE6": {
   "unit": "game/mainProgram0800",
   "line": "$92C4"
  },
  "1FE9": {
   "unit": "game/mainProgram0800",
   "line": "zp_A6"
  },
  "1FEB": {
   "unit": "game/mainProgram0800",
   "line": "sprite 2 = the expanded box that frames the centre cell of the view"
  },
  "1FEE": {
   "unit": "game/mainProgram0800",
   "line": "0 = the battle proper, non-zero = unit placement"
  },
  "1FF0": {
   "unit": "game/mainProgram0800",
   "line": "battle: no standing message"
  },
  "1FF2": {
   "unit": "game/mainProgram0800",
   "line": "message 10 = 'SETUP UNITS. PRESS F 1 WHEN DONE.'"
  },
  "1FF4": {
   "unit": "game/mainProgram0800",
   "line": "$92A1 = the message the status line falls back to"
  },
  "1FF7": {
   "unit": "game/mainProgram0800",
   "line": "and show it right now as well"
  },
  "1FFA": {
   "unit": "game/mainProgram0800",
   "line": "clear"
  },
  "1FFC": {
   "unit": "game/mainProgram0800",
   "line": "$9209 = 0: just browsing, no unit or group selected"
  },
  "1FFF": {
   "unit": "game/mainProgram0800",
   "line": "$91CC = sprite 0 for positionCursorSprite"
  },
  "2002": {
   "unit": "game/mainProgram0800",
   "line": "zp_3D = 0 so the panel is cleared to black"
  },
  "2004": {
   "unit": "game/mainProgram0800",
   "line": "blank the info panel window (rows 12-20, columns 25-38)"
  },
  "2007": {
   "unit": "game/mainProgram0800",
   "line": "no highlight"
  },
  "2009": {
   "unit": "game/mainProgram0800",
   "line": "$9204 = no unit is drawn inverted in the view"
  },
  "200C": {
   "unit": "game/mainProgram0800",
   "line": "$9205 = no group is drawn inverted either"
  },
  "200F": {
   "unit": "game/mainProgram0800",
   "line": "repaint the 7x5 tactical view from the map"
  },
  "2012": {
   "unit": "game/mainProgram0800",
   "line": "put the mini-map cursor sprite on the centre cell"
  },
  "2015": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0 = the hollow square cursor"
  },
  "201A": {
   "unit": "game/mainProgram0800",
   "line": "$92C3, so the console and drone screens can come back to this spot"
  },
  "201D": {
   "unit": "game/mainProgram0800",
   "line": "same for the row"
  },
  "201F": {
   "unit": "game/mainProgram0800",
   "line": "$92C4"
  },
  "2022": {
   "unit": "game/mainProgram0800",
   "line": "which phase?"
  },
  "2024": {
   "unit": "game/mainProgram0800",
   "line": "0 = the battle proper"
  },
  "2026": {
   "unit": "game/mainProgram0800",
   "line": "unit placement: let overlay A's '*' handler ($8658) toggle DESTROY mode"
  },
  "2029": {
   "unit": "game/mainProgram0800",
   "line": "zp_66: 0-3 = F1/F3/F5/F7 pressed, $FF = none"
  },
  "202B": {
   "unit": "game/mainProgram0800",
   "line": "F1 means 'finished placing units'"
  },
  "202D": {
   "unit": "game/mainProgram0800",
   "line": "may the placement UI keep running?"
  },
  "2030": {
   "unit": "game/mainProgram0800",
   "line": "yes: carry on"
  },
  "2032": {
   "unit": "game/mainProgram0800",
   "line": "back to the setup code in overlay A"
  },
  "2033": {
   "unit": "game/mainProgram0800",
   "line": "battle: F1/F3/F5/F7 switch screens, and that call never returns"
  },
  "2036": {
   "unit": "game/mainProgram0800",
   "line": "one game frame: engine step, messages, chat, sound and the link"
  },
  "2039": {
   "unit": "game/mainProgram0800",
   "line": "joystick nibble latched by the IRQ"
  },
  "203B": {
   "unit": "game/mainProgram0800",
   "line": "$0F = centred"
  },
  "203D": {
   "unit": "game/mainProgram0800",
   "line": "resting on a cell: look at what is under the cursor"
  },
  "203F": {
   "unit": "game/mainProgram0800",
   "line": "stick deflected: straight to the scroll handler"
  },
  "2045": {
   "unit": "game/mainProgram0800",
   "line": "15 frames, about a quarter of a second"
  },
  "2047": {
   "unit": "game/mainProgram0800",
   "line": "not hovering long enough yet"
  },
  "2049": {
   "unit": "game/mainProgram0800",
   "line": "map byte under the cursor cell (viewOriginCol+3, viewOriginRow+2)"
  },
  "204C": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 clear = plain terrain, no unit to describe"
  },
  "204E": {
   "unit": "game/mainProgram0800",
   "line": "$92C9 counts down while the end-of-game sequence runs"
  },
  "2051": {
   "unit": "game/mainProgram0800",
   "line": "do not open panels while the game is ending"
  },
  "2053": {
   "unit": "game/mainProgram0800",
   "line": "low 7 bits of an occupied cell = the unit index"
  },
  "2055": {
   "unit": "game/mainProgram0800",
   "line": "$92FC bit 7 = beginner display, every enemy unit visible"
  },
  "2058": {
   "unit": "game/mainProgram0800",
   "line": "then any unit may be inspected"
  },
  "205A": {
   "unit": "game/mainProgram0800",
   "line": "$0B9B bit 7 = watching a game film"
  },
  "205D": {
   "unit": "game/mainProgram0800",
   "line": "a film viewer may inspect both sides"
  },
  "205F": {
   "unit": "game/mainProgram0800",
   "line": "$9236 = your own COMCEN's unit index (49 or 99)"
  },
  "2062": {
   "unit": "game/mainProgram0800",
   "line": "your comcen can always be inspected"
  },
  "2064": {
   "unit": "game/mainProgram0800",
   "line": "$9245 bit 7 = your own comcen is cloaked"
  },
  "2067": {
   "unit": "game/mainProgram0800",
   "line": "a cloaked comcen is off the air: no unit reports"
  },
  "2069": {
   "unit": "game/mainProgram0800",
   "line": "50 = the boundary between the two sides' halves of the unit arrays"
  },
  "206B": {
   "unit": "game/mainProgram0800",
   "line": "opcode patched by patchOwnSideBranches ($7BE6) to BCC or BCS: skip the opponent's units"
  },
  "206D": {
   "unit": "game/mainProgram0800",
   "line": "$90F8 = the unit the panel will describe"
  },
  "2070": {
   "unit": "game/mainProgram0800",
   "line": "print its stats and hold them while the cursor rests on the unit"
  },
  "2073": {
   "unit": "game/mainProgram0800",
   "line": "if SPACE closed the panel, swallow the key so nothing else acts on it"
  },
  "2079": {
   "unit": "game/mainProgram0800",
   "line": "no click: fall through to scrolling"
  },
  "207B": {
   "unit": "game/mainProgram0800",
   "line": "map byte under the cursor"
  },
  "207E": {
   "unit": "game/mainProgram0800",
   "line": "clicked on empty terrain"
  },
  "2080": {
   "unit": "game/mainProgram0800",
   "line": "unit index under the cursor"
  },
  "2082": {
   "unit": "game/mainProgram0800",
   "line": "50 = the side boundary again"
  },
  "2084": {
   "unit": "game/mainProgram0800",
   "line": "opcode patched by patchOwnSideBranches ($7BE9): ignore clicks on the opponent's units"
  },
  "2086": {
   "unit": "game/mainProgram0800",
   "line": "$90F8 = the clicked unit"
  },
  "2089": {
   "unit": "game/mainProgram0800",
   "line": "Y = unit index for the array lookups"
  },
  "208A": {
   "unit": "game/mainProgram0800",
   "line": "$F960: bits 0-2 = stun counter, bits 5-7 = dug-in state"
  },
  "208D": {
   "unit": "game/mainProgram0800",
   "line": "keep the stun counter"
  },
  "208F": {
   "unit": "game/mainProgram0800",
   "line": "a stunned unit takes no orders"
  },
  "2091": {
   "unit": "game/mainProgram0800",
   "line": "is this the comcen itself?"
  },
  "2094": {
   "unit": "game/mainProgram0800",
   "line": "the comcen is always reachable"
  },
  "2096": {
   "unit": "game/mainProgram0800",
   "line": "$9245 bit 7 = own comcen cloaked"
  },
  "2099": {
   "unit": "game/mainProgram0800",
   "line": "with the comcen cloaked you cannot give orders"
  },
  "209B": {
   "unit": "game/mainProgram0800",
   "line": "battle or unit placement?"
  },
  "209D": {
   "unit": "game/mainProgram0800",
   "line": "battle: open the unit selection / options flow"
  },
  "209F": {
   "unit": "game/mainProgram0800",
   "line": "$92F3 bit 7 = DESTROY mode, armed with '*' during placement"
  },
  "20A2": {
   "unit": "game/mainProgram0800",
   "line": "normal placement click: pick the unit up instead"
  },
  "20A4": {
   "unit": "game/mainProgram0800",
   "line": "clear"
  },
  "20A6": {
   "unit": "game/mainProgram0800",
   "line": "DESTROY is one-shot: disarm it again"
  },
  "20A9": {
   "unit": "game/mainProgram0800",
   "line": "$FB54: bit 6 = the unit is in a group, bits 2-4 = which group"
  },
  "20AC": {
   "unit": "game/mainProgram0800",
   "line": "test the 'in a group' bit"
  },
  "20AE": {
   "unit": "game/mainProgram0800",
   "line": "a loner: destroy just this unit"
  },
  "20B0": {
   "unit": "game/mainProgram0800",
   "line": "unit index"
  },
  "20B1": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 of the argument means 'the whole group this unit belongs to'"
  },
  "20B3": {
   "unit": "game/mainProgram0800",
   "line": "back into Y"
  },
  "20B4": {
   "unit": "game/mainProgram0800",
   "line": "zp_73 = the command's argument byte"
  },
  "20B6": {
   "unit": "game/mainProgram0800",
   "line": "command $91 = self destruct"
  },
  "20B8": {
   "unit": "game/mainProgram0800",
   "line": "queue the 2-byte command for the engine and for the opponent"
  },
  "20BB": {
   "unit": "game/mainProgram0800",
   "line": "back to message 10, 'SETUP UNITS. PRESS F 1 WHEN DONE.'"
  },
  "20BD": {
   "unit": "game/mainProgram0800",
   "line": "$92A1 = fallback message"
  },
  "20C0": {
   "unit": "game/mainProgram0800",
   "line": "zp_7B = the message on the status line right now"
  },
  "20C2": {
   "unit": "game/mainProgram0800",
   "line": "60 frames = one second"
  },
  "20C4": {
   "unit": "game/mainProgram0800",
   "line": "zp_6C: no scrolling for a second"
  },
  "20C6": {
   "unit": "game/mainProgram0800",
   "line": "$0B7D: the IRQ ignores stick and keys too, so one click destroys exactly one unit"
  },
  "20C9": {
   "unit": "game/mainProgram0800",
   "line": "join the scroll handler"
  },
  "20CC": {
   "unit": "game/mainProgram0800",
   "line": "battle: unit or group selection, info panel, destination or options menu"
  },
  "20CF": {
   "unit": "game/mainProgram0800",
   "line": "no key"
  },
  "20D1": {
   "unit": "game/mainProgram0800",
   "line": "drop whatever key the menus left behind"
  },
  "20D4": {
   "unit": "game/mainProgram0800",
   "line": "rebuild the browsing state: uiLevel 0, panel cleared, view repainted"
  },
  "20D9": {
   "unit": "game/mainProgram0800",
   "line": "still inside the delay: do not scroll yet"
  },
  "20DB": {
   "unit": "game/mainProgram0800",
   "line": "the latched joystick nibble"
  },
  "20DD": {
   "unit": "game/mainProgram0800",
   "line": "clear"
  },
  "20DF": {
   "unit": "game/mainProgram0800",
   "line": "$0B7D = 0: stick and keyboard are live again"
  },
  "20E2": {
   "unit": "game/mainProgram0800",
   "line": "nibble -> direction code 0-7, or $FF when centred/impossible"
  },
  "20E5": {
   "unit": "game/mainProgram0800",
   "line": "no usable direction"
  },
  "20E7": {
   "unit": "game/mainProgram0800",
   "line": "16 frames before the first repeat, 6 between repeats"
  },
  "20EA": {
   "unit": "game/mainProgram0800",
   "line": "move the view one cell in that direction"
  },
  "20ED": {
   "unit": "game/mainProgram0800",
   "line": "next pass of the main loop"
  },
  "20F6": {
   "unit": "game/mainProgram0800",
   "line": "default column step 0"
  },
  "20F8": {
   "unit": "game/mainProgram0800",
   "line": "codes 4-7 are the diagonals"
  },
  "20FA": {
   "unit": "game/mainProgram0800",
   "line": "handle them separately"
  },
  "20FC": {
   "unit": "game/mainProgram0800",
   "line": "codes 2-3 are east and west"
  },
  "20FE": {
   "unit": "game/mainProgram0800",
   "line": "those move on the column axis"
  },
  "2100": {
   "unit": "game/mainProgram0800",
   "line": "codes 0-1 (north/south) index the delta table directly"
  },
  "2101": {
   "unit": "game/mainProgram0800",
   "line": "-1 for north, +1 for south"
  },
  "2104": {
   "unit": "game/mainProgram0800",
   "line": "Y = row step"
  },
  "2105": {
   "unit": "game/mainProgram0800",
   "line": "column step stays 0"
  },
  "2108": {
   "unit": "game/mainProgram0800",
   "line": "codes 2-3 select entries 2-3"
  },
  "2109": {
   "unit": "game/mainProgram0800",
   "line": "+1 for east, -1 for west"
  },
  "210C": {
   "unit": "game/mainProgram0800",
   "line": "X = column step"
  },
  "210D": {
   "unit": "game/mainProgram0800",
   "line": "row step stays 0"
  },
  "2110": {
   "unit": "game/mainProgram0800",
   "line": "direction code 4-7"
  },
  "2111": {
   "unit": "game/mainProgram0800",
   "line": "$30 NW, $20 NE, $21 SE, $31 SW = (column delta index << 4) | row delta index"
  },
  "2114": {
   "unit": "game/mainProgram0800",
   "line": "keep the packed byte"
  },
  "2115": {
   "unit": "game/mainProgram0800",
   "line": "low nibble = index of the row delta"
  },
  "2117": {
   "unit": "game/mainProgram0800",
   "line": "into Y"
  },
  "2118": {
   "unit": "game/mainProgram0800",
   "line": "-1 or +1"
  },
  "211B": {
   "unit": "game/mainProgram0800",
   "line": "Y = row step"
  },
  "211C": {
   "unit": "game/mainProgram0800",
   "line": "packed byte back"
  },
  "211D": {
   "unit": "game/mainProgram0800",
   "line": "shift the high nibble down..."
  },
  "211E": {
   "unit": "game/mainProgram0800",
   "line": "..."
  },
  "211F": {
   "unit": "game/mainProgram0800",
   "line": "..."
  },
  "2120": {
   "unit": "game/mainProgram0800",
   "line": "= index of the column delta"
  },
  "2121": {
   "unit": "game/mainProgram0800",
   "line": "into X"
  },
  "2122": {
   "unit": "game/mainProgram0800",
   "line": "-1 or +1"
  },
  "2125": {
   "unit": "game/mainProgram0800",
   "line": "X = column step"
  },
  "2126": {
   "unit": "game/mainProgram0800",
   "line": "current auto-repeat delay"
  },
  "2128": {
   "unit": "game/mainProgram0800",
   "line": "half of it"
  },
  "2129": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "212A": {
   "unit": "game/mainProgram0800",
   "line": "delay * 1.5"
  },
  "212C": {
   "unit": "game/mainProgram0800",
   "line": "a diagonal step covers more ground, so it repeats more slowly"
  },
  "2130": {
   "unit": "game/mainProgram0800",
   "line": "move order: scroll freely"
  },
  "2132": {
   "unit": "game/mainProgram0800",
   "line": "free cell pick: scroll freely"
  },
  "2134": {
   "unit": "game/mainProgram0800",
   "line": "self-modifying: stash the column step in the LDX # operand at $2175+1"
  },
  "2137": {
   "unit": "game/mainProgram0800",
   "line": "and the row step in the LDY # operand at $2177+1"
  },
  "213A": {
   "unit": "game/mainProgram0800",
   "line": "column step"
  },
  "213B": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "213C": {
   "unit": "game/mainProgram0800",
   "line": "left edge of the view"
  },
  "213E": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "213F": {
   "unit": "game/mainProgram0800",
   "line": "+3 = the cursor cell after the move"
  },
  "2141": {
   "unit": "game/mainProgram0800",
   "line": "zp_22 = column of point A for distanceSquared"
  },
  "2143": {
   "unit": "game/mainProgram0800",
   "line": "row step"
  },
  "2144": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "2145": {
   "unit": "game/mainProgram0800",
   "line": "top edge of the view"
  },
  "2147": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "2148": {
   "unit": "game/mainProgram0800",
   "line": "+2 = the cursor row after the move"
  },
  "214A": {
   "unit": "game/mainProgram0800",
   "line": "zp_24 = row of point A"
  },
  "214C": {
   "unit": "game/mainProgram0800",
   "line": "the unit that is doing the shooting"
  },
  "214F": {
   "unit": "game/mainProgram0800",
   "line": "$F640+unit"
  },
  "2152": {
   "unit": "game/mainProgram0800",
   "line": "zp_27 = column of point B (the unit)"
  },
  "2154": {
   "unit": "game/mainProgram0800",
   "line": "$F6A4+unit"
  },
  "2157": {
   "unit": "game/mainProgram0800",
   "line": "zp_28 = row of point B"
  },
  "2159": {
   "unit": "game/mainProgram0800",
   "line": "A = dx*dx + dy*dy, each delta capped at 15 and the sum at $FF"
  },
  "215C": {
   "unit": "game/mainProgram0800",
   "line": "reuse zp_22 to hold that squared distance"
  },
  "215E": {
   "unit": "game/mainProgram0800",
   "line": "the firing unit again"
  },
  "2161": {
   "unit": "game/mainProgram0800",
   "line": "$FAF0 = the terrain code hidden underneath the unit"
  },
  "2164": {
   "unit": "game/mainProgram0800",
   "line": "$077E indexed by the raw cell value = terrain class 0-8"
  },
  "2167": {
   "unit": "game/mainProgram0800",
   "line": "$35BB = 0,0,0,1,1,2,2,0 extra cells of reach for standing high"
  },
  "216A": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "216B": {
   "unit": "game/mainProgram0800",
   "line": "a BOOMER shoots 6 cells, plus the terrain bonus"
  },
  "216D": {
   "unit": "game/mainProgram0800",
   "line": "range 6-8 as a table index"
  },
  "216E": {
   "unit": "game/mainProgram0800",
   "line": "$35B0 = r*(r+1), the squared distance a target has to stay under"
  },
  "2171": {
   "unit": "game/mainProgram0800",
   "line": "compare it with the distance to the cell we would move to"
  },
  "2173": {
   "unit": "game/mainProgram0800",
   "line": "out of range: leave the cursor where it is"
  },
  "2175": {
   "unit": "game/mainProgram0800",
   "line": "column step, operand patched at $2134"
  },
  "2177": {
   "unit": "game/mainProgram0800",
   "line": "row step, operand patched at $2137"
  },
  "2179": {
   "unit": "game/mainProgram0800",
   "line": "scroll the view, and the cursor with it, by one cell"
  },
  "217C": {
   "unit": "game/mainProgram0800",
   "line": "10 frames"
  },
  "217E": {
   "unit": "game/mainProgram0800",
   "line": "zp_6D: ignore the fire button briefly after a scroll"
  },
  "2180": {
   "unit": "game/mainProgram0800",
   "line": "clear"
  },
  "2182": {
   "unit": "game/mainProgram0800",
   "line": "zp_79: the button must be seen released again before the next click counts"
  },
  "2184": {
   "unit": "game/mainProgram0800",
   "line": "the caller loops back and reads the stick again"
  },
  "2187": {
   "unit": "game/mainProgram0800",
   "line": "$91D7: nothing is being described while the target cursor is up"
  },
  "218A": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0"
  },
  "218C": {
   "unit": "game/mainProgram0800",
   "line": "$91CC = the sprite that carries the cursor shape"
  },
  "218F": {
   "unit": "game/mainProgram0800",
   "line": "zp_BB selects the flavour of this loop"
  },
  "2191": {
   "unit": "game/mainProgram0800",
   "line": "0 = choosing a destination"
  },
  "2193": {
   "unit": "game/mainProgram0800",
   "line": "negative = free cell pick: plain block cursor"
  },
  "2195": {
   "unit": "game/mainProgram0800",
   "line": "positive = attack target: thick cross into sprite 0"
  },
  "2198": {
   "unit": "game/mainProgram0800",
   "line": "sprite 2 is left as the caller set it"
  },
  "219B": {
   "unit": "game/mainProgram0800",
   "line": "$9209: 0 browsing, 1 single unit, 2 whole group"
  },
  "219E": {
   "unit": "game/mainProgram0800",
   "line": "2 or more = a group is selected"
  },
  "21A0": {
   "unit": "game/mainProgram0800",
   "line": "single unit: plain block cursor"
  },
  "21A2": {
   "unit": "game/mainProgram0800",
   "line": "group move: sprite 0 shows every member as a dot, and the box offsets are built"
  },
  "21A5": {
   "unit": "game/mainProgram0800",
   "line": "on to the crosshair"
  },
  "21A8": {
   "unit": "game/mainProgram0800",
   "line": "small solid block into sprite $91CC"
  },
  "21AB": {
   "unit": "game/mainProgram0800",
   "line": "sprite 2 = the diamond crosshair over the centre cell"
  },
  "21B0": {
   "unit": "game/mainProgram0800",
   "line": "normal game: run a whole frame"
  },
  "21B2": {
   "unit": "game/mainProgram0800",
   "line": "setup/film phase: only keep the lock-step link serviced"
  },
  "21B5": {
   "unit": "game/mainProgram0800",
   "line": "skip the frame update"
  },
  "21B8": {
   "unit": "game/mainProgram0800",
   "line": "$90FC counts down to the next engine step"
  },
  "21BB": {
   "unit": "game/mainProgram0800",
   "line": "one frame update per engine step is enough"
  },
  "21BD": {
   "unit": "game/mainProgram0800",
   "line": "engine, messages, sound, link"
  },
  "21C0": {
   "unit": "game/mainProgram0800",
   "line": "$90EE = frames since the last input"
  },
  "21C3": {
   "unit": "game/mainProgram0800",
   "line": "90 frames = one and a half seconds"
  },
  "21C5": {
   "unit": "game/mainProgram0800",
   "line": "player is still busy"
  },
  "21C7": {
   "unit": "game/mainProgram0800",
   "line": "only the plain move cursor previews a route"
  },
  "21C9": {
   "unit": "game/mainProgram0800",
   "line": "target or free-pick cursor: no preview"
  },
  "21CB": {
   "unit": "game/mainProgram0800",
   "line": "crawl the marker sprite from the unit to the cursor cell"
  },
  "21CE": {
   "unit": "game/mainProgram0800",
   "line": "the unit being ordered"
  },
  "21D1": {
   "unit": "game/mainProgram0800",
   "line": "$F76C: bit 7 set = the unit has been destroyed"
  },
  "21D4": {
   "unit": "game/mainProgram0800",
   "line": "still alive: carry on"
  },
  "21D6": {
   "unit": "game/mainProgram0800",
   "line": "it died while we were aiming: clean up and re-centre"
  },
  "21D9": {
   "unit": "game/mainProgram0800",
   "line": "SPACE, a function key or the end of the game abort every modal UI"
  },
  "21DC": {
   "unit": "game/mainProgram0800",
   "line": "A = 1: keep going"
  },
  "21DE": {
   "unit": "game/mainProgram0800",
   "line": "aborted: clean up without scrolling back"
  },
  "21E1": {
   "unit": "game/mainProgram0800",
   "line": "joystick nibble"
  },
  "21E3": {
   "unit": "game/mainProgram0800",
   "line": "$0F = centred"
  },
  "21E5": {
   "unit": "game/mainProgram0800",
   "line": "deflected: scroll the cursor"
  },
  "21E7": {
   "unit": "game/mainProgram0800",
   "line": "fresh press of the fire button?"
  },
  "21EA": {
   "unit": "game/mainProgram0800",
   "line": "no click: fall through to the scroll handler"
  },
  "21EC": {
   "unit": "game/mainProgram0800",
   "line": "which kind of pick is this?"
  },
  "21EE": {
   "unit": "game/mainProgram0800",
   "line": "0 = a move order"
  },
  "21F0": {
   "unit": "game/mainProgram0800",
   "line": "negative = the caller only wants the cell"
  },
  "21F2": {
   "unit": "game/mainProgram0800",
   "line": "positive = attack: queue command $87 against the unit under the cursor"
  },
  "21F5": {
   "unit": "game/mainProgram0800",
   "line": "order sent: clean up"
  },
  "21F8": {
   "unit": "game/mainProgram0800",
   "line": "left edge of the view"
  },
  "21FA": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "21FB": {
   "unit": "game/mainProgram0800",
   "line": "+3 = the cursor column"
  },
  "21FD": {
   "unit": "game/mainProgram0800",
   "line": "$91D8 = the cell the caller asked for"
  },
  "2200": {
   "unit": "game/mainProgram0800",
   "line": "top edge of the view"
  },
  "2202": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "2203": {
   "unit": "game/mainProgram0800",
   "line": "+2 = the cursor row"
  },
  "2205": {
   "unit": "game/mainProgram0800",
   "line": "$91D9"
  },
  "2208": {
   "unit": "game/mainProgram0800",
   "line": "return at once and leave the sprites to the caller"
  },
  "220B": {
   "unit": "game/mainProgram0800",
   "line": "$9209: single unit or whole group?"
  },
  "220E": {
   "unit": "game/mainProgram0800",
   "line": "2 = group"
  },
  "2210": {
   "unit": "game/mainProgram0800",
   "line": "group move"
  },
  "2212": {
   "unit": "game/mainProgram0800",
   "line": "single unit: queue command $80 unit,column,row"
  },
  "2215": {
   "unit": "game/mainProgram0800",
   "line": "order sent: clean up"
  },
  "2218": {
   "unit": "game/mainProgram0800",
   "line": "queue command $81, move the whole group to the cursor cell"
  },
  "221B": {
   "unit": "game/mainProgram0800",
   "line": "sendGroupMoveToCursor always returns C=0, so this 'stay in the loop' branch is dead code"
  },
  "221D": {
   "unit": "game/mainProgram0800",
   "line": "order sent: clean up"
  },
  "2220": {
   "unit": "game/mainProgram0800",
   "line": "zp_6C auto-repeat delay"
  },
  "2222": {
   "unit": "game/mainProgram0800",
   "line": "still inside the delay"
  },
  "2224": {
   "unit": "game/mainProgram0800",
   "line": "joystick nibble"
  },
  "2226": {
   "unit": "game/mainProgram0800",
   "line": "clear"
  },
  "2228": {
   "unit": "game/mainProgram0800",
   "line": "$0B7D = 0: input is live"
  },
  "222B": {
   "unit": "game/mainProgram0800",
   "line": "nibble -> direction code 0-7, $FF when centred"
  },
  "222E": {
   "unit": "game/mainProgram0800",
   "line": "no usable direction"
  },
  "2230": {
   "unit": "game/mainProgram0800",
   "line": "16 frames for the first step, 6 for the repeats"
  },
  "2233": {
   "unit": "game/mainProgram0800",
   "line": "move the cursor one cell (range-checked in target mode)"
  },
  "2236": {
   "unit": "game/mainProgram0800",
   "line": "next pass of the cursor loop"
  },
  "223C": {
   "unit": "game/mainProgram0800",
   "line": "clear"
  },
  "223E": {
   "unit": "game/mainProgram0800",
   "line": "$920A: the formation box is no longer being dragged"
  },
  "2241": {
   "unit": "game/mainProgram0800",
   "line": "zp_BB back to the plain move cursor"
  },
  "2243": {
   "unit": "game/mainProgram0800",
   "line": "sprite 1"
  },
  "2245": {
   "unit": "game/mainProgram0800",
   "line": "blank its 64-byte shape (the path marker)"
  },
  "2248": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0"
  },
  "224A": {
   "unit": "game/mainProgram0800",
   "line": "blank its shape (cursor or formation ghost)"
  },
  "224D": {
   "unit": "game/mainProgram0800",
   "line": "$9023 = shadow of VIC_SPR_EXP_X"
  },
  "2250": {
   "unit": "game/mainProgram0800",
   "line": "clear bits 0-1: sprites 0 and 1 back to normal width"
  },
  "2252": {
   "unit": "game/mainProgram0800",
   "line": "the IRQ copies the shadow to the VIC"
  },
  "2255": {
   "unit": "game/mainProgram0800",
   "line": "$9024 = shadow of VIC_SPR_EXP_Y"
  },
  "2258": {
   "unit": "game/mainProgram0800",
   "line": "sprites 0 and 1 back to normal height"
  },
  "225A": {
   "unit": "game/mainProgram0800",
   "line": "write the shadow back"
  },
  "225D": {
   "unit": "game/mainProgram0800",
   "line": "park the path marker back on the unit, ready for five more passes"
  },
  "2260": {
   "unit": "game/mainProgram0800",
   "line": "sprite 2 back to the normal centre-cell box"
  },
  "2263": {
   "unit": "game/mainProgram0800",
   "line": "back to the menu or setup code that asked for a cell"
  },
  "2267": {
   "unit": "game/mainProgram0800",
   "line": "left edge of the view"
  },
  "2269": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "226A": {
   "unit": "game/mainProgram0800",
   "line": "+3 = cursor column"
  },
  "226C": {
   "unit": "game/mainProgram0800",
   "line": "$F640+unit"
  },
  "226F": {
   "unit": "game/mainProgram0800",
   "line": "different column: there is a route to show"
  },
  "2271": {
   "unit": "game/mainProgram0800",
   "line": "top edge of the view"
  },
  "2273": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "2274": {
   "unit": "game/mainProgram0800",
   "line": "+2 = cursor row"
  },
  "2276": {
   "unit": "game/mainProgram0800",
   "line": "$F6A4+unit"
  },
  "2279": {
   "unit": "game/mainProgram0800",
   "line": "cursor sits on the unit itself: nothing to preview"
  },
  "227B": {
   "unit": "game/mainProgram0800",
   "line": "put the marker on the unit and give it five passes"
  },
  "227E": {
   "unit": "game/mainProgram0800",
   "line": "the unit being ordered"
  },
  "2281": {
   "unit": "game/mainProgram0800",
   "line": "$F76C bit 7 = destroyed"
  },
  "2284": {
   "unit": "game/mainProgram0800",
   "line": "it died: stop the animation"
  },
  "2286": {
   "unit": "game/mainProgram0800",
   "line": "keep the game and the link running while we animate"
  },
  "2289": {
   "unit": "game/mainProgram0800",
   "line": "3"
  },
  "228B": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "228C": {
   "unit": "game/mainProgram0800",
   "line": "left edge of the view"
  },
  "228E": {
   "unit": "game/mainProgram0800",
   "line": "zp_95 = cursor column, re-read every pass in case the view scrolled"
  },
  "2290": {
   "unit": "game/mainProgram0800",
   "line": "2"
  },
  "2292": {
   "unit": "game/mainProgram0800",
   "line": "plain add"
  },
  "2293": {
   "unit": "game/mainProgram0800",
   "line": "top edge of the view"
  },
  "2295": {
   "unit": "game/mainProgram0800",
   "line": "zp_94 = cursor row"
  },
  "2297": {
   "unit": "game/mainProgram0800",
   "line": "advance the marker one cell every 4 frames"
  },
  "229A": {
   "unit": "game/mainProgram0800",
   "line": "C=1 as soon as the player or the link wants attention"
  },
  "229D": {
   "unit": "game/mainProgram0800",
   "line": "still idle: keep animating"
  },
  "229F": {
   "unit": "game/mainProgram0800",
   "line": "turn the marker sprite off and return"
  },
  "22A4": {
   "unit": "game/mainProgram0800",
   "line": "$90EF = 0: print flush left, no centring"
  },
  "22A7": {
   "unit": "game/mainProgram0800",
   "line": "zp_3E: blanks are glyph 0"
  },
  "22A9": {
   "unit": "game/mainProgram0800",
   "line": "zp_43: characters go through the normal ASCII translation"
  },
  "22AB": {
   "unit": "game/mainProgram0800",
   "line": "$9209: 0 browsing, 1 unit selected, 2 group selected"
  },
  "22AE": {
   "unit": "game/mainProgram0800",
   "line": "$5E14 = 9 / 5 / 4: the panel text colour for that level"
  },
  "22B1": {
   "unit": "game/mainProgram0800",
   "line": "zp_3D = colour byte written for every character cell"
  },
  "22B3": {
   "unit": "game/mainProgram0800",
   "line": "open and blank the info panel window (rows 12-20, columns 25-38)"
  },
  "22B6": {
   "unit": "game/mainProgram0800",
   "line": "column 2"
  },
  "22B8": {
   "unit": "game/mainProgram0800",
   "line": "zp_3F: start two characters in from the left edge of the panel"
  },
  "22BA": {
   "unit": "game/mainProgram0800",
   "line": "recompute the bitmap and colour pointers for that cell"
  },
  "22BD": {
   "unit": "game/mainProgram0800",
   "line": "the unit on display"
  },
  "22C0": {
   "unit": "game/mainProgram0800",
   "line": "$F76C: type in the low bits, bit 7 = dead"
  },
  "22C3": {
   "unit": "game/mainProgram0800",
   "line": "0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN"
  },
  "22C5": {
   "unit": "game/mainProgram0800",
   "line": "X = table index"
  },
  "22C6": {
   "unit": "game/mainProgram0800",
   "line": "self-modifying: the LDX # at $22EF+1 that passes the type to the picture routine"
  },
  "22C9": {
   "unit": "game/mainProgram0800",
   "line": "low byte of $C964"
  },
  "22CB": {
   "unit": "game/mainProgram0800",
   "line": "high byte: the five 8-byte unit type names in the $C000 overlay"
  },
  "22CD": {
   "unit": "game/mainProgram0800",
   "line": "print entry X of that table"
  },
  "22D0": {
   "unit": "game/mainProgram0800",
   "line": "space"
  },
  "22D2": {
   "unit": "game/mainProgram0800",
   "line": "separate the name from the number"
  },
  "22D5": {
   "unit": "game/mainProgram0800",
   "line": "the unit index 0-99"
  },
  "22D8": {
   "unit": "game/mainProgram0800",
   "line": "middle byte of the 24-bit value = 0 (zp_7E holds the high byte)"
  },
  "22DA": {
   "unit": "game/mainProgram0800",
   "line": "print it in decimal without leading zeros"
  },
  "22DD": {
   "unit": "game/mainProgram0800",
   "line": "next panel row"
  },
  "22E0": {
   "unit": "game/mainProgram0800",
   "line": "low byte of $CAE9"
  },
  "22E2": {
   "unit": "game/mainProgram0800",
   "line": "high byte: the GROUP / ENERGY / WEAPON / ACTION label block"
  },
  "22E4": {
   "unit": "game/mainProgram0800",
   "line": "print the four labels"
  },
  "22E7": {
   "unit": "game/mainProgram0800",
   "line": "fill in the value column beside them"
  },
  "22EA": {
   "unit": "game/mainProgram0800",
   "line": "$CD00 = first high byte of the unit picture address table"
  },
  "22ED": {
   "unit": "game/mainProgram0800",
   "line": "not a high address: the $C000 overlay holds no pictures, skip them"
  },
  "22EF": {
   "unit": "game/mainProgram0800",
   "line": "unit type, operand patched at $22C6"
  },
  "22F1": {
   "unit": "game/mainProgram0800",
   "line": "unpack the picture and show it as sprites 5, 6 and 7"
  },
  "22F4": {
   "unit": "game/mainProgram0800",
   "line": "the path marker must not sit over the panel"
  },
  "22F7": {
   "unit": "game/mainProgram0800",
   "line": "the unit on display"
  },
  "22FA": {
   "unit": "game/mainProgram0800",
   "line": "$F7D0: bit 7 set = the unit has no destination"
  },
  "22FD": {
   "unit": "game/mainProgram0800",
   "line": "nowhere to go: skip the marker set-up"
  },
  "22FF": {
   "unit": "game/mainProgram0800",
   "line": "park the marker on the unit, ready to walk toward the destination"
  },
  "2305": {
   "unit": "game/mainProgram0800",
   "line": "no: only animate the marker and test for input"
  },
  "2307": {
   "unit": "game/mainProgram0800",
   "line": "the unit on display"
  },
  "230A": {
   "unit": "game/mainProgram0800",
   "line": "$91D7 tells the rest of the game which unit the panel is showing"
  },
  "230D": {
   "unit": "game/mainProgram0800",
   "line": "one full game frame"
  },
  "2310": {
   "unit": "game/mainProgram0800",
   "line": "map byte under the cursor"
  },
  "2313": {
   "unit": "game/mainProgram0800",
   "line": "cursor moved onto empty terrain: close the panel"
  },
  "2315": {
   "unit": "game/mainProgram0800",
   "line": "unit index under the cursor"
  },
  "2317": {
   "unit": "game/mainProgram0800",
   "line": "still the same unit?"
  },
  "231A": {
   "unit": "game/mainProgram0800",
   "line": "a different unit: close the panel"
  },
  "231C": {
   "unit": "game/mainProgram0800",
   "line": "$90FA = the unit the engine is stepping right now"
  },
  "231F": {
   "unit": "game/mainProgram0800",
   "line": "it is moving: reprint its status"
  },
  "2321": {
   "unit": "game/mainProgram0800",
   "line": "$91F0 = the unit currently being shot at"
  },
  "2324": {
   "unit": "game/mainProgram0800",
   "line": "it is under fire: reprint its status"
  },
  "2326": {
   "unit": "game/mainProgram0800",
   "line": "$920E bit 7 = a lock-step exchange was applied (one turn tick)"
  },
  "2329": {
   "unit": "game/mainProgram0800",
   "line": "nothing has changed since the last print"
  },
  "232B": {
   "unit": "game/mainProgram0800",
   "line": "clear"
  },
  "232D": {
   "unit": "game/mainProgram0800",
   "line": "consume the tick"
  },
  "2330": {
   "unit": "game/mainProgram0800",
   "line": "reprint group, energy, weapon and action"
  },
  "2333": {
   "unit": "game/mainProgram0800",
   "line": "zp_BB < 0 = free cell pick"
  },
  "2335": {
   "unit": "game/mainProgram0800",
   "line": "then always show where the unit is heading"
  },
  "2337": {
   "unit": "game/mainProgram0800",
   "line": "$9205 = $FF when no group is highlighted"
  },
  "233A": {
   "unit": "game/mainProgram0800",
   "line": "a group is highlighted: no individual destination marker"
  },
  "233C": {
   "unit": "game/mainProgram0800",
   "line": "crawl the marker from the unit toward its destination"
  },
  "233F": {
   "unit": "game/mainProgram0800",
   "line": "C=1 on stick, button, key or link activity"
  },
  "2342": {
   "unit": "game/mainProgram0800",
   "line": "player wants something: close the panel"
  },
  "2344": {
   "unit": "game/mainProgram0800",
   "line": "battle or unit placement?"
  },
  "2346": {
   "unit": "game/mainProgram0800",
   "line": "battle: keep the panel up"
  },
  "2348": {
   "unit": "game/mainProgram0800",
   "line": "placement: let overlay A see the '*' key"
  },
  "234B": {
   "unit": "game/mainProgram0800",
   "line": "C=0, nothing happened: keep the panel up"
  },
  "234F": {
   "unit": "game/mainProgram0800",
   "line": "$91D7 = the panel is empty again"
  },
  "2352": {
   "unit": "game/mainProgram0800",
   "line": "colour 0"
  },
  "2354": {
   "unit": "game/mainProgram0800",
   "line": "zp_3D: clear the panel to black"
  },
  "2356": {
   "unit": "game/mainProgram0800",
   "line": "wipe the panel window"
  },
  "2359": {
   "unit": "game/mainProgram0800",
   "line": "path marker sprite off"
  },
  "235C": {
   "unit": "game/mainProgram0800",
   "line": "$9022 = shadow of VIC_SPR_ENA"
  },
  "235F": {
   "unit": "game/mainProgram0800",
   "line": "clear bits 5-7: the three unit-picture sprites go away"
  },
  "2361": {
   "unit": "game/mainProgram0800",
   "line": "the IRQ writes the shadow to the VIC"
  },
  "2364": {
   "unit": "game/mainProgram0800",
   "line": "text window back to the whole screen, and return through it"
  }
 },
 "dataTypes": {
  "1D4C": [
   "byte",
   1
  ],
  "1D4D": [
   "byte",
   1
  ],
  "1FA6": [
   "byte",
   12
  ],
  "1FB2": [
   "byte",
   17
  ],
  "20F0": [
   "byte",
   4
  ],
  "1DA2": [
   "byte",
   3
  ],
  "1DA5": [
   "byte",
   3
  ],
  "1DA8": [
   "byte",
   3
  ],
  "1DAB": [
   "byte",
   3
  ],
  "1DAE": [
   "byte",
   3
  ],
  "1DB1": [
   "byte",
   3
  ],
  "1DB4": [
   "byte",
   3
  ],
  "1DB7": [
   "byte",
   3
  ],
  "1DBA": [
   "byte",
   3
  ],
  "1DBD": [
   "byte",
   3
  ],
  "1DC0": [
   "byte",
   3
  ],
  "1DC3": [
   "byte",
   3
  ],
  "1DC6": [
   "byte",
   3
  ],
  "1DC9": [
   "byte",
   3
  ],
  "1DCC": [
   "byte",
   3
  ],
  "1DCF": [
   "byte",
   3
  ],
  "1DD2": [
   "byte",
   3
  ],
  "1DD5": [
   "byte",
   3
  ],
  "1DD8": [
   "byte",
   3
  ],
  "1DDB": [
   "byte",
   3
  ],
  "1DDE": [
   "byte",
   3
  ],
  "1DE1": [
   "byte",
   3
  ]
 },
 "zpComments": {
  "6E": "frame countdown between menu-highlight toggles and console-tab blinks; scrollViewToUnit reuses it as the delay between animated scroll steps",
  "BB": "target cursor mode read by runTargetCursorLoop and scrollViewportByDirection: 0 = choose a destination for the unit or group, positive = choose a BOOMER target (the cursor may not leave the unit's range), negative = free cell pick reported in $91D8/$91D9",
  "F0": "column step applied to the battlefield view: set to -1/0/+1 by scrollViewBy (0 when the step was clamped), then biased to a table index 0/1/2 by scrollViewBitmap, which leaves it that way",
  "F1": "row step applied to the battlefield view, same encoding as scrollDeltaCol",
  "F7": "cells left to redraw along the edge that a scroll exposed: 5 for a column, 7 for a row",
  "F8": "exclusive map-row limit for the centre cell of the battlefield view: 40 during the battle, 20 while gamePhase is non-zero so a player only sees his own half"
 }
}