{
 "chunk": "game_mainProgram0800_2976",
 "unit": "game/mainProgram0800",
 "unitLabels": {
  "game/mainProgram0800": {
   "2A29": "useDrawColourFlag",
   "2A2A": "drawColourByte",
   "2A2B": "blockFillFlag",
   "2A2C": "blockFillChar",
   "2A2D": "windowOriginRow",
   "2A2E": "windowOriginCol",
   "2A85": "colourFillOpcode",
   "2AF3": "glyphBitmapOpcode",
   "2B00": "glyphColourOpcode",
   "2E0D": "magnifyWidthOdd",
   "2E0E": "magnifyHeightOdd",
   "2F8D": "invertedBitMasks",
   "2FD3": "installCursorShape"
  }
 },
 "notes": {
  "2976": {
   "unit": "game/mainProgram0800",
   "block": [
    "Knockout ending of computeGameResult (reached from $28FF when gameEndReason $0BA8 bit 7 is set).",
    "Bits 5-6 of gameEndReason were built at $3BBC-$3BC9 from the destroyed comcen's side bit and ORed",
    "into the old value, so $20 = side 0's comcen died, $40 = side 1's, $60 = both died in one exchange.",
    "X ends up holding the LOSING side.  Both score slots are indexed by the OPPONENT of the side they",
    "belong to: hits are added at $3B88 with X = the side of the unit that was hit, and",
    "drawStatusScoreLine prints slot (playerSide EOR 1) as 'US'.  Storing into slot X therefore credits",
    "the winner, and setOtherPlayerPoints fills in the loser's slot."
   ],
   "line": "assume side 0 is the loser (X = index of the knocked-out side)"
  },
  "2978": {
   "unit": "game/mainProgram0800",
   "line": "keep only the two knockout bits of gameEndReason (bit 5 = side 0 lost its comcen, bit 6 = side 1)"
  },
  "297A": {
   "unit": "game/mainProgram0800",
   "line": "both comcens destroyed in the same exchange?"
  },
  "297C": {
   "unit": "game/mainProgram0800",
   "line": "yes - reuse the draw path at $291B: 250 points each and message $13 'IT WAS A DRAW.'"
  },
  "297E": {
   "unit": "game/mainProgram0800",
   "line": "otherwise test the side-1 bit ($40) on its own"
  },
  "2980": {
   "unit": "game/mainProgram0800",
   "line": "only bit 5 set - side 0 was knocked out, leave X = 0"
  },
  "2982": {
   "unit": "game/mainProgram0800",
   "line": "bit 6 set - side 1 was knocked out, so the loser is side 1"
  },
  "2983": {
   "unit": "game/mainProgram0800",
   "block": [
    "Award the knockout scores: 500 to the winner, 150 to the loser."
   ],
   "line": "low byte of the winner's final total, $01F4 = 500 points"
  },
  "2985": {
   "unit": "game/mainProgram0800",
   "line": "slot X is the loser's opponent, i.e. the winner"
  },
  "2988": {
   "unit": "game/mainProgram0800",
   "line": "high byte of 500"
  },
  "298A": {
   "unit": "game/mainProgram0800",
   "line": "store the winner's 16-bit total"
  },
  "298D": {
   "unit": "game/mainProgram0800",
   "line": "message $14 = 'WE WERE KNOCKED OUT!' (string $87BA)"
  },
  "298F": {
   "unit": "game/mainProgram0800",
   "line": "was the knocked-out side the side this machine plays?"
  },
  "2992": {
   "unit": "game/mainProgram0800",
   "line": "yes - keep the 'we were knocked out' text"
  },
  "2994": {
   "unit": "game/mainProgram0800",
   "line": "no - message $15 = 'WE WON BY A KNOCKOUT!' (string $87CE)"
  },
  "2995": {
   "unit": "game/mainProgram0800",
   "line": "remember the result message id so the status line can reshow it"
  },
  "2998": {
   "unit": "game/mainProgram0800",
   "line": "the knocked-out side keeps only $96 = 150 points"
  },
  "299A": {
   "unit": "game/mainProgram0800",
   "line": "write 150 into the other slot (X EOR 1), which belongs to the loser"
  },
  "299D": {
   "unit": "game/mainProgram0800",
   "block": [
    "Common tail of every ending (draw, points and knockout): show the result once, then park the",
    "status line and leave chat entry."
   ],
   "line": "the result message id chosen above ($11-$15)"
  },
  "29A0": {
   "unit": "game/mainProgram0800",
   "line": "push it onto the status-line message queue ($C566)"
  },
  "29A3": {
   "unit": "game/mainProgram0800",
   "line": "$9E = the 'nothing pending' marker used by the message system"
  },
  "29A5": {
   "unit": "game/mainProgram0800",
   "line": "stop the status line from repeating the result message"
  },
  "29A8": {
   "unit": "game/mainProgram0800",
   "line": "and blank the chat text line slot at $0548 the same way"
  },
  "29AB": {
   "unit": "game/mainProgram0800",
   "line": "leave chat/typing mode"
  },
  "29AD": {
   "unit": "game/mainProgram0800",
   "line": "clear the chat state machine at $929C"
  },
  "29B0": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $28F1 with the caller's Y, so Y is returned unchanged"
  },
  "29B2": {
   "unit": "game/mainProgram0800",
   "line": "back to endGame ($4032)"
  },
  "29B3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setOtherPlayerPoints - Writes Y as the 16-bit point total of the side OTHER than X and returns with X",
    "already flipped.  The two totals live in the parallel slots sideScoreLo/sideScoreHi ($92A2/$92A3 and",
    "$92A4/$92A5) which are indexed by the opponent of the side that owns them, so computeGameResult uses",
    "this to fill in the second player after storing the first with sideScoreLo,x.",
    "In:  X = side index 0/1, Y = the point value (0-255)",
    "Out: sideScoreLo[X EOR 1] = Y, sideScoreHi[X EOR 1] = 0, X = X EOR 1, A = 0",
    "Called from: computeGameResult ($291F, $2924 draw path; $2970 points path; $299A knockout path)"
   ],
   "line": "take the side index that was just written"
  },
  "29B4": {
   "unit": "game/mainProgram0800",
   "line": "flip it: 0 <-> 1"
  },
  "29B6": {
   "unit": "game/mainProgram0800",
   "line": "index the other side's score slot"
  },
  "29B7": {
   "unit": "game/mainProgram0800",
   "line": "the value the caller passed in Y"
  },
  "29B8": {
   "unit": "game/mainProgram0800",
   "line": "low byte of that side's 16-bit total"
  },
  "29BB": {
   "unit": "game/mainProgram0800",
   "line": "result totals are always below 256 ..."
  },
  "29BD": {
   "unit": "game/mainProgram0800",
   "line": "... so clear the high byte"
  },
  "29C0": {
   "unit": "game/mainProgram0800",
   "line": "X is left flipped, which is what the caller relies on"
  },
  "29C1": {
   "unit": "game/mainProgram0800",
   "routine": [
    "shakeScreenScroll - One frame of the screen-shake effect used when a comcen is hit.  Takes one byte",
    "from the game PRNG and offers bits 0-2 as a new $D011 fine Y scroll and bits 3-5 as a new $D016 fine X",
    "scroll; each is used only if it is non-zero and smaller than the remaining shake amount",
    "screenShakeTimer, otherwise the resting values ($0B for $D011, $08 for $D016) are put back.  The",
    "chosen low nibbles are merged into the shadow registers vicCtrl1Shadow/vicCtrl2Shadow (the raster IRQ",
    "copies those to the VIC) and kept in spriteScrollFineY/spriteScrollFineX so the IRQ can shift the",
    "sprites by the same amount at $1174-$1186.  Because the low nibble is replaced wholesale, an accepted",
    "shake value also clears bit 3 (RSEL/CSEL), so a shaking frame drops to 24 rows / 38 columns - part of",
    "the jolt.",
    "In:  screenShakeTimer ($92C8), vicCtrl1Shadow ($9000), vicCtrl2Shadow ($9001), PRNG state zp_57-59",
    "Out: vicCtrl1Shadow, vicCtrl2Shadow, spriteScrollFineY ($92F5), spriteScrollFineX ($92F4);",
    "     self-modified operands at $29D6, $29E6, $29F3, $2A03",
    "Called from: the battle main loop at $41C7, once per frame while screenShakeTimer counts down"
   ],
   "line": "one byte from the 24-bit game PRNG at $FD5D (both machines step it in lock step)"
  },
  "29C4": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA #imm at $29D5 with the raw random byte (vertical candidate)"
  },
  "29C7": {
   "unit": "game/mainProgram0800",
   "line": "shift the byte down three places ..."
  },
  "29C8": {
   "unit": "game/mainProgram0800",
   "line": "... so that bits 3-5 become bits 0-2 ..."
  },
  "29C9": {
   "unit": "game/mainProgram0800",
   "line": "... giving an independent horizontal candidate"
  },
  "29CA": {
   "unit": "game/mainProgram0800",
   "line": "patch the LDA #imm at $29F2 with it"
  },
  "29CD": {
   "unit": "game/mainProgram0800",
   "line": "current $D011 shadow"
  },
  "29D0": {
   "unit": "game/mainProgram0800",
   "line": "keep bits 4-7 (DEN, BMM, ECM, RST8) and drop the RSEL/Y-scroll nibble"
  },
  "29D2": {
   "unit": "game/mainProgram0800",
   "line": "patch the ORA #imm at $29E5 with the preserved high nibble"
  },
  "29D5": {
   "unit": "game/mainProgram0800",
   "block": [
    "Vertical jitter: pick a new $D011 low nibble."
   ],
   "line": "operand patched at $29C4 = the random byte"
  },
  "29D7": {
   "unit": "game/mainProgram0800",
   "line": "reduce it to a 0-7 fine Y scroll"
  },
  "29D9": {
   "unit": "game/mainProgram0800",
   "line": "0 would not move anything - use the resting value instead"
  },
  "29DB": {
   "unit": "game/mainProgram0800",
   "line": "only accept a jitter smaller than the shake energy still left"
  },
  "29DE": {
   "unit": "game/mainProgram0800",
   "line": "accepted: this frame is displaced by A raster lines"
  },
  "29E0": {
   "unit": "game/mainProgram0800",
   "line": "resting $D011 low nibble: RSEL 25 rows (bit 3) + Y-scroll 3"
  },
  "29E2": {
   "unit": "game/mainProgram0800",
   "line": "remember it so the IRQ can offset the sprites by the same amount"
  },
  "29E5": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $29D2 = the preserved bits 4-7 of $D011"
  },
  "29E7": {
   "unit": "game/mainProgram0800",
   "line": "the raster IRQ writes this shadow to $D011"
  },
  "29EA": {
   "unit": "game/mainProgram0800",
   "block": [
    "Horizontal jitter: the same again for the $D016 low nibble."
   ],
   "line": "current $D016 shadow"
  },
  "29ED": {
   "unit": "game/mainProgram0800",
   "line": "keep bits 4-7 (MCM and the unused high bits) and drop the CSEL/X-scroll nibble"
  },
  "29EF": {
   "unit": "game/mainProgram0800",
   "line": "patch the ORA #imm at $2A02 with it"
  },
  "29F2": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $29CA = the random byte shifted down 3"
  },
  "29F4": {
   "unit": "game/mainProgram0800",
   "line": "reduce it to a 0-7 fine X scroll"
  },
  "29F6": {
   "unit": "game/mainProgram0800",
   "line": "0 would not move anything"
  },
  "29F8": {
   "unit": "game/mainProgram0800",
   "line": "again, only a jitter below the remaining shake energy"
  },
  "29FB": {
   "unit": "game/mainProgram0800",
   "line": "accepted: this frame is displaced by A pixels sideways"
  },
  "29FD": {
   "unit": "game/mainProgram0800",
   "line": "resting $D016 low nibble: CSEL 40 columns (bit 3) + X-scroll 0"
  },
  "29FF": {
   "unit": "game/mainProgram0800",
   "line": "remember it for the sprite offset at $117F"
  },
  "2A02": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $29EF = the preserved bits 4-7 of $D016"
  },
  "2A04": {
   "unit": "game/mainProgram0800",
   "line": "the raster IRQ writes this shadow to $D016"
  },
  "2A07": {
   "unit": "game/mainProgram0800",
   "line": "the caller decrements screenShakeTimer afterwards"
  },
  "2A08": {
   "unit": "game/mainProgram0800",
   "routine": [
    "patchUnitSideBranches - Re-points the two 'is this unit mine?' branches at the viewpoint currently",
    "being drawn.  Overlay A stores $90 (BCC) or $B0 (BCS) at $2840 from playerSide (see $7BC6), which makes",
    "the 'cpy #$32' unit-index test at $283E select the local side.  This routine copies that opcode byte",
    "into the following visibility test at $2846 (renderMapTile) and into its twin at $5BFE (the overview",
    "map renderer), inverting BCC <-> BCS with EOR $20 when filmPlaybackMode bit 0 is set, i.e. when a game",
    "film is being replayed from the opponent's point of view (viewpoint 1 = THEM).  Viewpoint 2 = BOTH has",
    "bit 0 clear and is handled separately by the 'cmp #$82' tests at $284B/$5C03, which reveal everything.",
    "In:  filmPlaybackMode ($0B9B) bit 0, the opcode byte at $2840",
    "Out: the opcode bytes at $2846 and $5BFE",
    "Called from: start-up ($0AD9), the battlefield screen rebuild ($2639) and the replay viewpoint key ($4134)"
   ],
   "line": "film playback flags: bit 7 = replaying, bits 0-1 = viewpoint 0 US / 1 THEM / 2 BOTH"
  },
  "2A0B": {
   "unit": "game/mainProgram0800",
   "line": "shift viewpoint bit 0 into the carry"
  },
  "2A0C": {
   "unit": "game/mainProgram0800",
   "line": "the side-select branch opcode overlay A wrote: $90 = BCC (side 0), $B0 = BCS (side 1)"
  },
  "2A0F": {
   "unit": "game/mainProgram0800",
   "line": "viewpoint bit 0 clear (US or BOTH) - use the opcode unchanged"
  },
  "2A11": {
   "unit": "game/mainProgram0800",
   "line": "viewpoint THEM - flip BCC <-> BCS so the enemy's units count as 'ours'"
  },
  "2A13": {
   "unit": "game/mainProgram0800",
   "line": "patch the opcode of the branch at $2846 inside renderMapTile"
  },
  "2A16": {
   "unit": "game/mainProgram0800",
   "line": "and the identical branch at $5BFE in the overview-map renderer"
  },
  "2A1A": {
   "unit": "game/mainProgram0800",
   "routine": [
    "printOptionWord - Prints one of the three 4-byte DAMAGE option words held at $0652 in the message",
    "block - 'FULL' ($0652), 'HALF' ($0656), 'NONE' ($065A), each bit-7 terminated - through the text",
    "engine's string printer $C133.  The overlays call it with the per-side damage setting recyclerMode",
    "($92A6,side) to fill the XXX field of the rules summary line at $0636.",
    "In:  A = word index 0 FULL / 1 HALF / 2 NONE",
    "Out: the word is printed at the current text cursor; A/Y = the string address handed to printString",
    "Called from: the setup screens of both $6F00 overlays ($843B, $85D8, $85E9)"
   ],
   "line": "index * 2 ..."
  },
  "2A1B": {
   "unit": "game/mainProgram0800",
   "line": "... * 4: the words are 4 bytes apart"
  },
  "2A1C": {
   "unit": "game/mainProgram0800",
   "line": "prepare the 16-bit add"
  },
  "2A1D": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the 'FULL' entry, $0652"
  },
  "2A1F": {
   "unit": "game/mainProgram0800",
   "line": "keep the low byte while the high byte is built"
  },
  "2A20": {
   "unit": "game/mainProgram0800",
   "line": "start the high byte at 0 ..."
  },
  "2A22": {
   "unit": "game/mainProgram0800",
   "line": "... plus $06 and any carry, giving $06xx"
  },
  "2A24": {
   "unit": "game/mainProgram0800",
   "line": "printString wants the high byte in Y"
  },
  "2A25": {
   "unit": "game/mainProgram0800",
   "line": "and the low byte in A"
  },
  "2A26": {
   "unit": "game/mainProgram0800",
   "line": "tail-call the text engine's string printer"
  },
  "2A29": {
   "unit": "game/mainProgram0800",
   "block": [
    "Parameter block shared by every glyph plotter in this file and by both $6F00 overlays (all seven bytes",
    "just happen to be initialised to $01).  Every screen builder sets them before calling drawWindowFrame,",
    "drawCharBlock, fillScreenColourRect or drawCharAt:",
    "  +0 useDrawColourFlag  non-zero = ignore the per-glyph colour table and colour with drawColourByte",
    "  +1 drawColourByte     the screen-RAM colour byte (high nibble foreground, low nibble background)",
    "  +2 blockFillFlag      non-zero = drawCharBlock ignores its character data and fills with blockFillChar",
    "  +3 blockFillChar      the character used for that fill",
    "  +4 windowOriginRow    top screen row (0-24) of the window the plotters draw into",
    "  +5 windowOriginCol    left screen column (0-39) of that window",
    "  +6 (spare)            never read by any traced code"
   ],
   "line": "useDrawColourFlag: read by plotGlyph at $2AD1; 0 = take the colour from fontColorTable"
  },
  "2A2A": {
   "unit": "game/mainProgram0800",
   "line": "drawColourByte: written by fillScreenColourRect and used by plotGlyph and $2A85"
  },
  "2A2B": {
   "unit": "game/mainProgram0800",
   "line": "blockFillFlag: tested by drawCharBlock at $2B54"
  },
  "2A2C": {
   "unit": "game/mainProgram0800",
   "line": "blockFillChar: the glyph drawCharBlock repeats when the flag is set"
  },
  "2A2D": {
   "unit": "game/mainProgram0800",
   "line": "windowOriginRow: top row used by fillScreenColourRect, drawCharBlock, drawFrameBox, magnifyWindow2x"
  },
  "2A2E": {
   "unit": "game/mainProgram0800",
   "line": "windowOriginCol: left column for the same routines; the 7th byte is a spare nobody reads"
  },
  "2A30": {
   "unit": "game/mainProgram0800",
   "block": [
    "The two opcode tables that drive the four draw modes.  Mode 0 replace, 1 OR, 2 EOR, 3 keep",
    "existing (the CMP just throws the new value away, leaving the byte that was read first)."
   ],
   "line": "(zp),Y opcodes for the bitmap combine at $2AF3/$C22B: $B1 LDA, $11 ORA, $51 EOR, $D1 CMP"
  },
  "2A34": {
   "unit": "game/mainProgram0800",
   "line": "absolute opcodes for the colour combine at $2B00/$2A85: $AD LDA, $0D ORA, $4D EOR, $CD CMP"
  },
  "2A38": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setBitmapDrawMode - Chooses how a glyph's eight bitmap bytes are combined with what is already in the",
    "$A000 bitmap.  It overwrites the opcode of the second LDA of the read-modify-write pair inside plotGlyph",
    "($2AF3) and of the identical pair in the text engine's own glyph plotter ($C22B), so mode 0 replaces,",
    "1 ORs the glyph in, 2 EORs it (used for highlighting) and 3 leaves the bitmap untouched.  A is",
    "preserved through the self-modified operand at $2A45.",
    "In:  Y = draw mode 0-3, A = value to preserve",
    "Out: the opcode bytes at $2AF3 and $C22B; A unchanged",
    "Called from: resetDrawModes ($2A5B) and the setup/menu screens of both overlays ($701E, $7AA1)"
   ],
   "line": "stash A in the operand of the LDA #imm at $2A44 so it survives"
  },
  "2A3B": {
   "unit": "game/mainProgram0800",
   "line": "pick the (zp),Y opcode for this mode"
  },
  "2A3E": {
   "unit": "game/mainProgram0800",
   "line": "patch the bitmap combine instruction inside plotGlyph"
  },
  "2A41": {
   "unit": "game/mainProgram0800",
   "line": "and the same instruction in the $C000 text engine's plotter"
  },
  "2A44": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $2A38 - restores the caller's A"
  },
  "2A47": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setColourDrawMode - The colour-plane twin of setBitmapDrawMode: chooses how drawColourByte is combined",
    "with the colour byte already in screen RAM by patching the absolute LDA at $2B00 (inside plotGlyph) and",
    "at $2A85 (inside fillScreenColourRect).  Mode 0 replaces, 1 ORs, 2 EORs (xorColourBlock2x2 uses this to",
    "flash a tile), 3 keeps the existing colour.  A is preserved through the operand at $2A54.",
    "In:  Y = draw mode 0-3, A = value to preserve",
    "Out: the opcode bytes at $2B00 and $2A85; A unchanged",
    "Called from: resetDrawModes ($2A5E), xorColourBlock2x2 ($2BD0) and overlay screen code ($7B5B)"
   ],
   "line": "stash A in the operand of the LDA #imm at $2A53"
  },
  "2A4A": {
   "unit": "game/mainProgram0800",
   "line": "pick the absolute opcode for this mode"
  },
  "2A4D": {
   "unit": "game/mainProgram0800",
   "line": "patch the colour combine inside plotGlyph"
  },
  "2A50": {
   "unit": "game/mainProgram0800",
   "line": "and the one inside fillScreenColourRect"
  },
  "2A53": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $2A47 - restores the caller's A"
  },
  "2A56": {
   "unit": "game/mainProgram0800",
   "routine": [
    "resetDrawModes - Puts both planes back to mode 0 (plain replace) after a highlight or overlay effect.",
    "Y is preserved through the self-modified operand at $2A62.",
    "In:  Y = value to preserve",
    "Out: the opcodes at $2AF3, $C22B, $2B00 and $2A85 are all LDA again; Y unchanged",
    "Called from: $1689 and xorColourBlock2x2 ($2BDA) here, and from the overlays ($7033, $70F2, $7AB6,",
    "     $7B40, $7B86, $810E)"
   ],
   "line": "stash Y in the operand of the LDY #imm at $2A61"
  },
  "2A59": {
   "unit": "game/mainProgram0800",
   "line": "mode 0 = replace"
  },
  "2A5B": {
   "unit": "game/mainProgram0800",
   "line": "patch the two bitmap combine instructions"
  },
  "2A5E": {
   "unit": "game/mainProgram0800",
   "line": "patch the two colour combine instructions"
  },
  "2A61": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $2A56 - restores the caller's Y"
  },
  "2A64": {
   "unit": "game/mainProgram0800",
   "routine": [
    "fillScreenColourRect - Paints an X columns by Y rows rectangle of the multicolour bitmap's colour cells",
    "(screen RAM at $8C00, 40 bytes per row) starting at the window origin windowOriginRow/windowOriginCol",
    "with the colour byte A, combined according to the current colour draw mode.  Only the colour plane is",
    "touched - the bitmap itself keeps whatever it held, which is how panels are recoloured without redrawing.",
    "In:  A = screen-RAM colour byte, X = width in cells (1-40), Y = height in rows,",
    "     windowOriginRow/windowOriginCol, the mode opcode at $2A85",
    "Out: the rectangle recoloured; drawColourByte = A; zp_B6/B7, zp_1B (rows left) and zp_1C (width-1)",
    "     clobbered",
    "Called from: everywhere - $1782, $18DC, $18EC, $18FC, $1909, $2BD7 and jumps at $16B4, $1916, $192A",
    "     here, plus a dozen sites in each $6F00 overlay"
   ],
   "line": "remember the colour byte where the patched instruction at $2A85 can read it"
  },
  "2A67": {
   "unit": "game/mainProgram0800",
   "line": "zp_1B counts the rows still to fill"
  },
  "2A69": {
   "unit": "game/mainProgram0800",
   "line": "the window's top screen row"
  },
  "2A6C": {
   "unit": "game/mainProgram0800",
   "line": "high byte of $8C00 + row*40 from the screen row table"
  },
  "2A71": {
   "unit": "game/mainProgram0800",
   "line": "low byte of $8C00 + row*40"
  },
  "2A75": {
   "unit": "game/mainProgram0800",
   "line": "step right to the window's left column"
  },
  "2A78": {
   "unit": "game/mainProgram0800",
   "line": "zp_B6/B7 now points at the first cell of the top row"
  },
  "2A7A": {
   "unit": "game/mainProgram0800",
   "line": "no carry out of the column add"
  },
  "2A7C": {
   "unit": "game/mainProgram0800",
   "line": "carry - bump the high byte"
  },
  "2A7E": {
   "unit": "game/mainProgram0800",
   "line": "width-1 = the highest column offset ..."
  },
  "2A7F": {
   "unit": "game/mainProgram0800",
   "line": "... kept in zp_1C as the per-row start index"
  },
  "2A81": {
   "unit": "game/mainProgram0800",
   "block": [
    "Outer loop: one screen row per pass.  Inner loop walks the row right to left."
   ],
   "line": "start each row at its rightmost cell"
  },
  "2A83": {
   "unit": "game/mainProgram0800",
   "line": "read the cell's current colour (needed by the OR/EOR/keep modes)"
  },
  "2A85": {
   "unit": "game/mainProgram0800",
   "line": "opcode patched by setColourDrawMode: LDA / ORA / EOR / CMP drawColourByte"
  },
  "2A88": {
   "unit": "game/mainProgram0800",
   "line": "write the combined colour back"
  },
  "2A8A": {
   "unit": "game/mainProgram0800",
   "line": "next cell to the left"
  },
  "2A8B": {
   "unit": "game/mainProgram0800",
   "line": "until offset 0 has been done"
  },
  "2A8D": {
   "unit": "game/mainProgram0800",
   "line": "one row finished"
  },
  "2A8F": {
   "unit": "game/mainProgram0800",
   "line": "all rows done"
  },
  "2A92": {
   "unit": "game/mainProgram0800",
   "line": "advance the pointer ..."
  },
  "2A94": {
   "unit": "game/mainProgram0800",
   "line": "... by one screen row (40 bytes)"
  },
  "2A98": {
   "unit": "game/mainProgram0800",
   "line": "no page crossed"
  },
  "2A9A": {
   "unit": "game/mainProgram0800",
   "line": "carry into the high byte"
  },
  "2A9C": {
   "unit": "game/mainProgram0800",
   "line": "always taken - back for the next row"
  },
  "2A9F": {
   "unit": "game/mainProgram0800",
   "routine": [
    "calcCellPointers - Loads the character cell coordinates from scratch18 (row) and scratch19 (column) and",
    "falls into calcCellPointersXY.  This is the entry every routine in this file uses.",
    "In:  scratch18 = screen row 0-24, scratch19 = screen column 0-39",
    "Out: as calcCellPointersXY; Y = row, X = column",
    "Called from: $26EA, drawCharAt ($2AC4), drawCharBlock ($2B4E), setMagnifyCellPointers ($2F44 and the",
    "     tail jump at $2F67) and $5B15"
   ],
   "line": "Y = character row"
  },
  "2AA1": {
   "unit": "game/mainProgram0800",
   "line": "X = character column"
  },
  "2AA3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "calcCellPointersXY - Turns a character cell (Y = row, X = column) into the two pointers every plotter",
    "needs: bitmapPtr = $A000 + row*320 + column*8 (the eight bytes of that cell in the multicolour bitmap)",
    "and screenPtr = $8C00 + row*40 + column (its colour byte).  Both are looked up in the six address",
    "tables at $9300 rather than multiplied: bitmap row lo/hi at $9300/$9319 (25 entries), column*8 lo/hi at",
    "$9332/$935A (40 entries) and screen row lo/hi at $9382/$939B (25 entries).",
    "In:  Y = screen row 0-24, X = screen column 0-39",
    "Out: bitmapPtr/bitmapPtrHi (zp_B4/B5), screenPtr/screenPtrHi (zp_B6/B7); X and Y unchanged",
    "Called from: $5CB6, $5D3E here, the text engine's cell setter ($C0A7) and the overlays ($864B)"
   ],
   "line": "low byte of $A000 + row*320"
  },
  "2AA7": {
   "unit": "game/mainProgram0800",
   "line": "plus column*8 (low)"
  },
  "2AAC": {
   "unit": "game/mainProgram0800",
   "line": "high byte of $A000 + row*320"
  },
  "2AAF": {
   "unit": "game/mainProgram0800",
   "line": "plus column*8 (high: 0 for columns 0-31, 1 for 32-39)"
  },
  "2AB2": {
   "unit": "game/mainProgram0800",
   "line": "zp_B4/B5 = address of the cell's 8 bitmap bytes"
  },
  "2AB4": {
   "unit": "game/mainProgram0800",
   "line": "the column is also the byte offset within a screen row"
  },
  "2AB6": {
   "unit": "game/mainProgram0800",
   "line": "plus the low byte of $8C00 + row*40"
  },
  "2ABB": {
   "unit": "game/mainProgram0800",
   "line": "high byte of $8C00 + row*40 ($8C-$8F)"
  },
  "2ABE": {
   "unit": "game/mainProgram0800",
   "line": "add only the carry - the screen fits in four pages"
  },
  "2AC0": {
   "unit": "game/mainProgram0800",
   "line": "zp_B6/B7 = address of the cell's colour byte"
  },
  "2AC3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawCharAt - Draws character A at screen row scratch18, column scratch19: computes the cell pointers and",
    "falls straight into plotGlyph.",
    "In:  A = character code 0-113, scratch18 = row, scratch19 = column",
    "Out: see plotGlyph",
    "Called from: drawWindowInnerCorners, drawTallGlyph, drawFrameBox, drawFrameRow/Column here ($2B95,",
    "     $2B9E, $2BA7, $2BB3, $2BBD, $2BC8, $2CC5, $2CD0, $2CDB, $2CE7, $2D23, $2D35) and from the overlays",
    "     ($6F6F, $6F76, $6F7D)"
   ],
   "line": "the cell pointer helper clobbers A"
  },
  "2AC4": {
   "unit": "game/mainProgram0800",
   "line": "work out where row/column lands in the bitmap and in screen RAM"
  },
  "2AC7": {
   "unit": "game/mainProgram0800",
   "line": "get the character code back"
  },
  "2AC8": {
   "unit": "game/mainProgram0800",
   "routine": [
    "plotGlyph - Draws one 8x8 glyph of the game's own font (114 glyphs at $9426, 8 bytes each) into the",
    "bitmap cell at bitmapPtr, combining the bytes according to the bitmap draw mode, then sets the cell's",
    "colour at screenPtr.  The colour is the glyph's own default from fontColorTable ($93B4) when",
    "useDrawColourFlag is 0 and that entry is non-zero; otherwise it is drawColourByte combined according to",
    "the colour draw mode.  When the display is in multicolour mode ($D016 bit 4 set in vicCtrl2Shadow) the",
    "routine also writes textColour into the matching colour RAM cell, reached by adding $4C to the high",
    "byte of screenPtr ($8C00 + $4C00 = $D800).",
    "In:  A = character code 0-113; bitmapPtr/screenPtr from calcCellPointers; useDrawColourFlag,",
    "     drawColourByte, textColour (zp_3D), vicCtrl2Shadow; the mode opcodes at $2AF3 and $2B00",
    "Out: 8 bitmap bytes, 1 screen-RAM colour byte and optionally 1 colour RAM byte written; glyphPtr",
    "     (zp_B2/B3) = the glyph's font address; screenPtrHi is left pointing into colour RAM in",
    "     multicolour mode; A/X/Y clobbered",
    "Called from: drawCharBlock ($2B5F) and by falling through from drawCharAt"
   ],
   "line": "keep the character code in Y for the font-colour lookup"
  },
  "2ACB": {
   "unit": "game/mainProgram0800",
   "line": "dead store - glyphPtr is overwritten at $2AE7 before it is used"
  },
  "2ACD": {
   "unit": "game/mainProgram0800",
   "line": "clear the high byte so the three ROLs below can shift into it"
  },
  "2ACF": {
   "unit": "game/mainProgram0800",
   "line": "default colour value 0 = 'this glyph has no colour of its own'"
  },
  "2AD1": {
   "unit": "game/mainProgram0800",
   "line": "does the caller insist on drawColourByte for every glyph?"
  },
  "2AD4": {
   "unit": "game/mainProgram0800",
   "line": "yes - keep the 0 so the fill colour is used"
  },
  "2AD6": {
   "unit": "game/mainProgram0800",
   "line": "no - take this glyph's default colour byte from the 114-entry table at $93B4"
  },
  "2AD9": {
   "unit": "game/mainProgram0800",
   "line": "park it in X across the glyph copy"
  },
  "2ADA": {
   "unit": "game/mainProgram0800",
   "line": "character code back into A"
  },
  "2ADB": {
   "unit": "game/mainProgram0800",
   "line": "code * 2 ..."
  },
  "2ADE": {
   "unit": "game/mainProgram0800",
   "line": "... * 4 ..."
  },
  "2AE1": {
   "unit": "game/mainProgram0800",
   "line": "... * 8: each glyph is 8 bytes"
  },
  "2AE5": {
   "unit": "game/mainProgram0800",
   "line": "add the low byte of the font base $9426"
  },
  "2AEB": {
   "unit": "game/mainProgram0800",
   "line": "and its high byte"
  },
  "2AED": {
   "unit": "game/mainProgram0800",
   "line": "zp_B2/B3 now points at the glyph's 8 rows"
  },
  "2AEF": {
   "unit": "game/mainProgram0800",
   "block": [
    "Copy the glyph into the bitmap cell, bottom row first."
   ],
   "line": "8 pixel rows, counted 7 down to 0"
  },
  "2AF1": {
   "unit": "game/mainProgram0800",
   "line": "read the byte already in the bitmap (only the OR/EOR/keep modes need it)"
  },
  "2AF3": {
   "unit": "game/mainProgram0800",
   "line": "opcode patched by setBitmapDrawMode: LDA / ORA / EOR / CMP (glyphPtr),y"
  },
  "2AF5": {
   "unit": "game/mainProgram0800",
   "line": "store the combined pixel row"
  },
  "2AF8": {
   "unit": "game/mainProgram0800",
   "line": "until row 0 has been written"
  },
  "2AFA": {
   "unit": "game/mainProgram0800",
   "block": [
    "Now the colour plane: one screen-RAM byte, plus a colour RAM byte in multicolour mode."
   ],
   "line": "Y = 0, the single colour byte of this cell"
  },
  "2AFB": {
   "unit": "game/mainProgram0800",
   "line": "the glyph's default colour saved in X"
  },
  "2AFC": {
   "unit": "game/mainProgram0800",
   "line": "non-zero - write it straight in, ignoring the draw mode"
  },
  "2AFE": {
   "unit": "game/mainProgram0800",
   "line": "otherwise read the colour that is already there"
  },
  "2B00": {
   "unit": "game/mainProgram0800",
   "line": "opcode patched by setColourDrawMode: LDA / ORA / EOR / CMP drawColourByte"
  },
  "2B03": {
   "unit": "game/mainProgram0800",
   "line": "store the cell colour into screen RAM at $8C00+"
  },
  "2B05": {
   "unit": "game/mainProgram0800",
   "line": "shadow of $D016"
  },
  "2B08": {
   "unit": "game/mainProgram0800",
   "line": "bit 4 = MCM; the game runs hires ($C8) for text and multicolour ($D8) for the map"
  },
  "2B0A": {
   "unit": "game/mainProgram0800",
   "line": "hires - one colour byte is enough"
  },
  "2B0F": {
   "unit": "game/mainProgram0800",
   "line": "$8C00 + $4C00 = $D800: the same cell in colour RAM"
  },
  "2B13": {
   "unit": "game/mainProgram0800",
   "line": "the text engine's current colour supplies the third multicolour pair"
  },
  "2B18": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawCharBlock - Draws a rectangular block of characters at windowOriginRow/windowOriginCol.  A/Y point",
    "at the block: byte 0 = width in cells, byte 1 = height, then width*height character codes stored row by",
    "row.  When blockFillFlag is non-zero the character data is ignored and every cell is drawn with",
    "blockFillChar instead (the 2-byte header is still read), which is how the screen builders erase a panel.",
    "The header values are also left in frameWidth/frameHeight for drawWindowInnerCorners.",
    "In:  A/Y = lo/hi address of the block; windowOriginRow, windowOriginCol, blockFillFlag, blockFillChar",
    "Out: frameWidth ($9221) = width, frameHeight ($9222) = height; blockPtr (zp_B8/B9) = the character data;",
    "     scratch18/19 = last cell, scratch1B = row, scratch1C = column, scratch1D = data index",
    "Called from: the main menu builder ($16A9) and the panel builders of both overlays ($6F13, $6F24, $6F53,",
    "     $70A3, $70D6, $7B1B and the jump at $726E)"
   ],
   "line": "keep the block address in zp_B8/B9"
  },
  "2B1E": {
   "unit": "game/mainProgram0800",
   "line": "header byte 0 = width in character cells"
  },
  "2B20": {
   "unit": "game/mainProgram0800",
   "line": "also published for drawWindowInnerCorners"
  },
  "2B23": {
   "unit": "game/mainProgram0800",
   "line": "step past the width byte"
  },
  "2B29": {
   "unit": "game/mainProgram0800",
   "line": "header byte 1 = height in character rows"
  },
  "2B2E": {
   "unit": "game/mainProgram0800",
   "line": "step past the height byte - zp_B8/B9 now addresses the character data"
  },
  "2B36": {
   "unit": "game/mainProgram0800",
   "line": "row counter = 0"
  },
  "2B38": {
   "unit": "game/mainProgram0800",
   "line": "index into the character data = 0"
  },
  "2B3A": {
   "unit": "game/mainProgram0800",
   "block": [
    "Row loop: scratch1B counts rows, scratch1C counts columns, scratch1D walks the data linearly."
   ],
   "line": "start at the left column of this row"
  },
  "2B3C": {
   "unit": "game/mainProgram0800",
   "line": "column counter back to 0 for this row"
  },
  "2B3E": {
   "unit": "game/mainProgram0800",
   "line": "the window's top row ..."
  },
  "2B42": {
   "unit": "game/mainProgram0800",
   "line": "... plus the row we are on"
  },
  "2B44": {
   "unit": "game/mainProgram0800",
   "line": "target screen row for drawCharAt"
  },
  "2B46": {
   "unit": "game/mainProgram0800",
   "block": [
    "Column loop: one character per cell."
   ],
   "line": "the window's left column ..."
  },
  "2B4A": {
   "unit": "game/mainProgram0800",
   "line": "... plus the column we are on"
  },
  "2B4C": {
   "unit": "game/mainProgram0800",
   "line": "target screen column"
  },
  "2B4E": {
   "unit": "game/mainProgram0800",
   "line": "point zp_B4-zp_B7 at that cell"
  },
  "2B51": {
   "unit": "game/mainProgram0800",
   "line": "assume the fill character"
  },
  "2B54": {
   "unit": "game/mainProgram0800",
   "line": "is this a plain fill rather than a character block?"
  },
  "2B57": {
   "unit": "game/mainProgram0800",
   "line": "yes - draw blockFillChar and leave the data untouched"
  },
  "2B59": {
   "unit": "game/mainProgram0800",
   "line": "no - index the next byte of the block data"
  },
  "2B5B": {
   "unit": "game/mainProgram0800",
   "line": "fetch the character for this cell"
  },
  "2B5D": {
   "unit": "game/mainProgram0800",
   "line": "and advance the data index"
  },
  "2B5F": {
   "unit": "game/mainProgram0800",
   "line": "draw it into the cell the pointers already address"
  },
  "2B62": {
   "unit": "game/mainProgram0800",
   "line": "next column"
  },
  "2B69": {
   "unit": "game/mainProgram0800",
   "line": "keep going until the whole row is drawn"
  },
  "2B6B": {
   "unit": "game/mainProgram0800",
   "line": "next row"
  },
  "2B72": {
   "unit": "game/mainProgram0800",
   "line": "keep going until every row is drawn"
  },
  "2B75": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawWindowInnerCorners - Draws the small hollow-ring glyph $38 (font bytes at $95E6) in the four cells",
    "just inside the corners of the current window: rows windowOriginRow+1 and windowOriginRow+frameHeight-1,",
    "columns windowOriginCol+1 and windowOriginCol+frameWidth-1.  It is always called straight after",
    "drawWindowFrame or drawCharBlock, so frameWidth/frameHeight are the extents those routines left behind.",
    "Only the two $6F00 overlays use it, to stud the corners of their menu and setup panels.",
    "In:  windowOriginRow, windowOriginCol, frameWidth ($9221), frameHeight ($9222)",
    "Out: four characters drawn; scratch18/19 (cell), scratch1A (right column), scratch1B (bottom row)",
    "Called from: overlay panel builders only ($6FFD, $7092, $7A80, $7B0A, $7B4C)"
   ],
   "line": "window top row"
  },
  "2B7B": {
   "unit": "game/mainProgram0800",
   "line": "plus the height ..."
  },
  "2B7E": {
   "unit": "game/mainProgram0800",
   "line": "... = the bottom row of the frame"
  },
  "2B80": {
   "unit": "game/mainProgram0800",
   "line": "window left column"
  },
  "2B86": {
   "unit": "game/mainProgram0800",
   "line": "plus the width ..."
  },
  "2B89": {
   "unit": "game/mainProgram0800",
   "line": "... = the right column of the frame"
  },
  "2B8B": {
   "unit": "game/mainProgram0800",
   "line": "move one row down, inside the frame"
  },
  "2B8D": {
   "unit": "game/mainProgram0800",
   "line": "and one column right"
  },
  "2B8F": {
   "unit": "game/mainProgram0800",
   "line": "one column left of the right edge"
  },
  "2B91": {
   "unit": "game/mainProgram0800",
   "line": "one row up from the bottom edge"
  },
  "2B93": {
   "unit": "game/mainProgram0800",
   "line": "glyph $38 = the small hollow ring at $95E6"
  },
  "2B95": {
   "unit": "game/mainProgram0800",
   "line": "top-left stud"
  },
  "2B9A": {
   "unit": "game/mainProgram0800",
   "line": "move to the right-hand column"
  },
  "2B9E": {
   "unit": "game/mainProgram0800",
   "line": "top-right stud"
  },
  "2BA3": {
   "unit": "game/mainProgram0800",
   "line": "move down to the bottom row"
  },
  "2BA7": {
   "unit": "game/mainProgram0800",
   "line": "bottom-right stud"
  },
  "2BAA": {
   "unit": "game/mainProgram0800",
   "line": "back to the window's left column ..."
  },
  "2BAF": {
   "unit": "game/mainProgram0800",
   "line": "... plus one, inside the frame"
  },
  "2BB3": {
   "unit": "game/mainProgram0800",
   "line": "bottom-left stud"
  },
  "2BB7": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawTallGlyph - Draws one of the ten two-cell-tall symbols whose character-code pairs live at $9810",
    "(20 bytes: top code at $9810+A*2, bottom code at $9810+A*2+1).  The top half goes at scratch18/scratch19",
    "and the bottom half one row lower; scratch18 is restored on the way out.  Used by the overlays for the",
    "double-height icons on the comcen and setup screens.",
    "In:  A = symbol index 0-9, scratch18 = top row, scratch19 = column",
    "Out: two characters drawn; scratch18/scratch19 unchanged",
    "Called from: overlay screens only ($6FD9, $6FE1, $6FE9, $7055, $7AD8, $7AE0)"
   ],
   "line": "two character codes per symbol"
  },
  "2BB8": {
   "unit": "game/mainProgram0800",
   "line": "keep the doubled index across the first draw"
  },
  "2BBA": {
   "unit": "game/mainProgram0800",
   "line": "top-half character code"
  },
  "2BBD": {
   "unit": "game/mainProgram0800",
   "line": "draw the top cell"
  },
  "2BC0": {
   "unit": "game/mainProgram0800",
   "line": "the bottom half sits one row lower"
  },
  "2BC4": {
   "unit": "game/mainProgram0800",
   "line": "the odd entry of the pair"
  },
  "2BC5": {
   "unit": "game/mainProgram0800",
   "line": "bottom-half character code"
  },
  "2BC8": {
   "unit": "game/mainProgram0800",
   "line": "draw the bottom cell"
  },
  "2BCB": {
   "unit": "game/mainProgram0800",
   "line": "put the caller's row back"
  },
  "2BCE": {
   "unit": "game/mainProgram0800",
   "routine": [
    "xorColourBlock2x2 - Flashes one battlefield tile by EORing the colour byte A into the 2x2 screen-RAM",
    "colour cells at windowOriginRow/windowOriginCol (a map cell is 16x16 pixels = 2x2 character cells in the",
    "magnified view), then puts both draw modes back to plain replace.  Calling it twice with the same value",
    "restores the original colours.",
    "In:  A = colour byte to toggle, windowOriginRow/windowOriginCol",
    "Out: four screen-RAM colour bytes toggled; drawColourByte = A; both draw modes reset to 0",
    "Called from: the overlays only ($75DA, $807F, $814D)"
   ],
   "line": "colour draw mode 2 = EOR"
  },
  "2BD0": {
   "unit": "game/mainProgram0800",
   "line": "patch the two colour combine instructions"
  },
  "2BD3": {
   "unit": "game/mainProgram0800",
   "line": "2 cells wide ..."
  },
  "2BD5": {
   "unit": "game/mainProgram0800",
   "line": "... and 2 cells tall = one map tile"
  },
  "2BD7": {
   "unit": "game/mainProgram0800",
   "line": "toggle the four colour bytes"
  },
  "2BDA": {
   "unit": "game/mainProgram0800",
   "line": "restore replace mode and return through it"
  },
  "2BDD": {
   "unit": "game/mainProgram0800",
   "block": [
    "The 27 predefined screen windows, held as four parallel 27-byte tables plus a table of frame tile-set",
    "pointers, all indexed by the window number passed to drawWindowFrame in X.  The ones used by this file",
    "are 0 = the 22x22 overview panel at (0,0), 1 = the 16x12 battlefield view frame at column 24, 2 = the",
    "16x11 console panel below it, 3 and 4 = the 38x3 message frame at row 22 (3 in battle, 4 elsewhere),",
    "$18/$19 = the full-width main-menu frames.  Windows 5-$16 belong to the $6F00 overlays' setup, stats and",
    "comcen screens."
   ],
   "line": "left screen column of windows 0-7: overview 0, view 24, console 24, message 1, message 1, ..."
  },
  "2BE5": {
   "unit": "game/mainProgram0800",
   "line": "windows 8-15"
  },
  "2BED": {
   "unit": "game/mainProgram0800",
   "line": "windows $10-$17"
  },
  "2BF5": {
   "unit": "game/mainProgram0800",
   "line": "windows $18-$1A: the two main-menu frames at columns 0 and 1, and a 30-wide box at column 5"
  },
  "2BF8": {
   "unit": "game/mainProgram0800",
   "line": "top screen row of windows 0-7: overview 0, view 0, console 11, message 22, message 22, ..."
  },
  "2C00": {
   "unit": "game/mainProgram0800",
   "line": "windows 8-15"
  },
  "2C08": {
   "unit": "game/mainProgram0800",
   "line": "windows $10-$17"
  },
  "2C10": {
   "unit": "game/mainProgram0800",
   "line": "windows $18-$1A"
  },
  "2C13": {
   "unit": "game/mainProgram0800",
   "line": "width in cells (frame included) of windows 0-7: 22, 16, 16, 38, 38, ..."
  },
  "2C1B": {
   "unit": "game/mainProgram0800",
   "line": "windows 8-15"
  },
  "2C23": {
   "unit": "game/mainProgram0800",
   "line": "windows $10-$17"
  },
  "2C2B": {
   "unit": "game/mainProgram0800",
   "line": "windows $18-$1A: 40, 38 and 30 cells wide"
  },
  "2C2E": {
   "unit": "game/mainProgram0800",
   "line": "height in cells (frame included) of windows 0-7: 22, 12, 11, 3, 3, ..."
  },
  "2C36": {
   "unit": "game/mainProgram0800",
   "line": "windows 8-15"
  },
  "2C3E": {
   "unit": "game/mainProgram0800",
   "line": "windows $10-$17"
  },
  "2C46": {
   "unit": "game/mainProgram0800",
   "line": "windows $18-$1A: 20, 18 and 7 rows tall"
  },
  "2C49": {
   "unit": "game/mainProgram0800",
   "block": [
    "One little-endian pointer per window to its 9-byte frame tile set in $9824-$987D.  A set is laid out",
    "top-left, top edge, top-right, left edge, centre (never used), right edge, bottom-left, bottom edge,",
    "bottom-right.  $9824, $982D, $9836 and $983F are four different line styles, $9848 is all glyph $03",
    "(a solid block) and $9851 is all glyph $00 (blank, i.e. the frame erases itself), while $985A, $9863,",
    "$986C and $9875 are the four heavy panel styles used by windows 0-3."
   ],
   "line": "window 0 - overview panel"
  },
  "2C4B": {
   "unit": "game/mainProgram0800",
   "line": "window 1 - battlefield view"
  },
  "2C4D": {
   "unit": "game/mainProgram0800",
   "line": "window 2 - console panel"
  },
  "2C4F": {
   "unit": "game/mainProgram0800",
   "line": "window 3 - message frame during the battle"
  },
  "2C51": {
   "unit": "game/mainProgram0800",
   "line": "window 4 - message frame outside the battle"
  },
  "2C53": {
   "unit": "game/mainProgram0800",
   "line": "window 5"
  },
  "2C55": {
   "unit": "game/mainProgram0800",
   "line": "window 6"
  },
  "2C57": {
   "unit": "game/mainProgram0800",
   "line": "window 7"
  },
  "2C59": {
   "unit": "game/mainProgram0800",
   "line": "window 8"
  },
  "2C5B": {
   "unit": "game/mainProgram0800",
   "line": "window 9"
  },
  "2C5D": {
   "unit": "game/mainProgram0800",
   "line": "window $0A"
  },
  "2C5F": {
   "unit": "game/mainProgram0800",
   "line": "window $0B"
  },
  "2C61": {
   "unit": "game/mainProgram0800",
   "line": "window $0C - solid block frame"
  },
  "2C63": {
   "unit": "game/mainProgram0800",
   "line": "window $0D"
  },
  "2C65": {
   "unit": "game/mainProgram0800",
   "line": "window $0E"
  },
  "2C67": {
   "unit": "game/mainProgram0800",
   "line": "window $0F"
  },
  "2C69": {
   "unit": "game/mainProgram0800",
   "line": "window $10"
  },
  "2C6B": {
   "unit": "game/mainProgram0800",
   "line": "window $11"
  },
  "2C6D": {
   "unit": "game/mainProgram0800",
   "line": "window $12"
  },
  "2C6F": {
   "unit": "game/mainProgram0800",
   "line": "window $13"
  },
  "2C71": {
   "unit": "game/mainProgram0800",
   "line": "window $14 - blank tiles, so drawing this window erases the frame"
  },
  "2C73": {
   "unit": "game/mainProgram0800",
   "line": "window $15 - blank tiles as well"
  },
  "2C75": {
   "unit": "game/mainProgram0800",
   "line": "window $16 - solid block frame"
  },
  "2C77": {
   "unit": "game/mainProgram0800",
   "line": "window $17 - solid block frame"
  },
  "2C79": {
   "unit": "game/mainProgram0800",
   "line": "window $18 - main menu outer frame"
  },
  "2C7B": {
   "unit": "game/mainProgram0800",
   "line": "window $19 - main menu inner frame"
  },
  "2C7D": {
   "unit": "game/mainProgram0800",
   "line": "window $1A"
  },
  "2C7F": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawWindowFrame - Draws one of the 27 predefined windows selected by X.  Copies its left column, top",
    "row, width and height from the four parallel tables into windowOriginCol/windowOriginRow/frameWidth/",
    "frameHeight, fetches its 9-byte frame tile set from windowFramePtrTable and falls into drawFrameBox.",
    "In:  X = window number 0-26",
    "Out: windowOriginRow/Col and frameWidth/frameHeight set (the latter two are then decremented by",
    "     drawFrameBox, so they end up as the right/bottom extents); the frame is drawn; A/X/Y clobbered",
    "Called from: $1567, $156C, $269E and the jumps at $2695, $26BE, $28EE here, and from sixteen sites in",
    "     each $6F00 overlay"
   ],
   "line": "left screen column of this window"
  },
  "2C85": {
   "unit": "game/mainProgram0800",
   "line": "top screen row"
  },
  "2C8B": {
   "unit": "game/mainProgram0800",
   "line": "width in cells"
  },
  "2C91": {
   "unit": "game/mainProgram0800",
   "line": "height in cells"
  },
  "2C97": {
   "unit": "game/mainProgram0800",
   "line": "the pointer table has two bytes per window ..."
  },
  "2C98": {
   "unit": "game/mainProgram0800",
   "line": "... so double the index"
  },
  "2C9A": {
   "unit": "game/mainProgram0800",
   "line": "low byte of this window's tile set"
  },
  "2C9E": {
   "unit": "game/mainProgram0800",
   "line": "high byte - drawFrameBox expects A/Y"
  },
  "2CA1": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawFrameBox - Draws a rectangular frame from a 9-byte tile set at windowOriginRow/windowOriginCol,",
    "frameWidth by frameHeight cells.  The tile set is indexed 0 top-left, 1 top edge, 2 top-right, 3 left",
    "edge, 4 centre (never read), 5 right edge, 6 bottom-left, 7 bottom edge, 8 bottom-right.  The four",
    "corners are drawn first, then the bottom, top, left and right runs through drawFrameRow/drawFrameColumn,",
    "each of which stops before the corner it would overwrite.  Width and height are decremented on entry so",
    "they become the right/bottom offsets, and are left that way for drawWindowInnerCorners.",
    "In:  A/Y = lo/hi address of the 9-byte tile set; windowOriginRow, windowOriginCol, frameWidth,",
    "     frameHeight; the current colour state (useDrawColourFlag / drawColourByte / fontColorTable)",
    "Out: the frame is drawn into the bitmap and screen RAM; frameWidth and frameHeight are each 1 smaller;",
    "     scratch1A = right column, scratch1B = bottom row, blockPtr (zp_B8/B9) = the tile set",
    "Called from: drawWindowFrame by fall-through, and directly by overlay B ($724A) with its own tile set"
   ],
   "line": "keep the tile set address in zp_B8/B9"
  },
  "2CA5": {
   "unit": "game/mainProgram0800",
   "line": "width in cells becomes the offset of the rightmost column"
  },
  "2CA8": {
   "unit": "game/mainProgram0800",
   "line": "height in cells becomes the offset of the bottom row"
  },
  "2CAB": {
   "unit": "game/mainProgram0800",
   "line": "window top row ..."
  },
  "2CAE": {
   "unit": "game/mainProgram0800",
   "line": "... is the row of the top corners"
  },
  "2CB1": {
   "unit": "game/mainProgram0800",
   "line": "top row + height-1 ..."
  },
  "2CB4": {
   "unit": "game/mainProgram0800",
   "line": "... = the bottom row, kept in scratch1B as the column loops' stop value"
  },
  "2CB6": {
   "unit": "game/mainProgram0800",
   "line": "window left column ..."
  },
  "2CB9": {
   "unit": "game/mainProgram0800",
   "line": "... is the column of the left corners"
  },
  "2CBC": {
   "unit": "game/mainProgram0800",
   "line": "left column + width-1 ..."
  },
  "2CBF": {
   "unit": "game/mainProgram0800",
   "line": "... = the right column, kept in scratch1A as the row loops' stop value"
  },
  "2CC1": {
   "unit": "game/mainProgram0800",
   "block": [
    "The four corners first, so the edge runs can stop one cell short of each."
   ],
   "line": "tile 0 = top-left corner"
  },
  "2CC5": {
   "unit": "game/mainProgram0800",
   "line": "draw it at (top, left)"
  },
  "2CC8": {
   "unit": "game/mainProgram0800",
   "line": "move to the right column"
  },
  "2CCC": {
   "unit": "game/mainProgram0800",
   "line": "tile 2 = top-right corner"
  },
  "2CD0": {
   "unit": "game/mainProgram0800",
   "line": "draw it at (top, right)"
  },
  "2CD3": {
   "unit": "game/mainProgram0800",
   "line": "move down to the bottom row"
  },
  "2CD7": {
   "unit": "game/mainProgram0800",
   "line": "tile 8 = bottom-right corner"
  },
  "2CDB": {
   "unit": "game/mainProgram0800",
   "line": "draw it at (bottom, right)"
  },
  "2CDE": {
   "unit": "game/mainProgram0800",
   "line": "back to the left column"
  },
  "2CE3": {
   "unit": "game/mainProgram0800",
   "line": "tile 6 = bottom-left corner"
  },
  "2CE7": {
   "unit": "game/mainProgram0800",
   "line": "draw it at (bottom, left)"
  },
  "2CEA": {
   "unit": "game/mainProgram0800",
   "block": [
    "Now the four edges.  Row and column are still at the bottom-left corner."
   ],
   "line": "start one column right of the bottom-left corner"
  },
  "2CEC": {
   "unit": "game/mainProgram0800",
   "line": "tile 7 = bottom edge"
  },
  "2CEE": {
   "unit": "game/mainProgram0800",
   "line": "run it along the bottom row up to (not including) the right corner"
  },
  "2CF1": {
   "unit": "game/mainProgram0800",
   "line": "back to the top row"
  },
  "2CF6": {
   "unit": "game/mainProgram0800",
   "line": "and one column right of the left corner"
  },
  "2CFC": {
   "unit": "game/mainProgram0800",
   "line": "tile 1 = top edge"
  },
  "2CFE": {
   "unit": "game/mainProgram0800",
   "line": "run it along the top row"
  },
  "2D01": {
   "unit": "game/mainProgram0800",
   "line": "one row below the top-left corner"
  },
  "2D07": {
   "unit": "game/mainProgram0800",
   "line": "in the window's left column"
  },
  "2D0C": {
   "unit": "game/mainProgram0800",
   "line": "tile 3 = left edge"
  },
  "2D0E": {
   "unit": "game/mainProgram0800",
   "line": "run it down the left side up to (not including) the bottom corner"
  },
  "2D11": {
   "unit": "game/mainProgram0800",
   "line": "one row below the top-right corner"
  },
  "2D17": {
   "unit": "game/mainProgram0800",
   "line": "in the right-hand column"
  },
  "2D1B": {
   "unit": "game/mainProgram0800",
   "line": "tile 5 = right edge - and fall into drawFrameColumn to draw it"
  },
  "2D1D": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawFrameColumn - Repeats the tile at offset Y of the current tile set down column scratch19, starting",
    "at row scratch18 and stopping when the row reaches scratch1B (exclusive), so the corner already drawn",
    "there is not overwritten.  Used for the left and right frame edges.",
    "In:  Y = tile offset in the set, blockPtr = the tile set, scratch18 = first row, scratch19 = column,",
    "     scratch1B = stop row",
    "Out: scratch18 = scratch1B, scratch1C = the tile's character code, the cells drawn",
    "Called from: drawFrameBox ($2D0E) and by fall-through from $2D1B"
   ],
   "line": "fetch the edge tile's character code once"
  },
  "2D21": {
   "unit": "game/mainProgram0800",
   "block": [
    "One cell per row until the bottom corner is reached."
   ],
   "line": "the edge tile"
  },
  "2D23": {
   "unit": "game/mainProgram0800",
   "line": "draw it at the current row/column"
  },
  "2D26": {
   "unit": "game/mainProgram0800",
   "line": "step one row down"
  },
  "2D2A": {
   "unit": "game/mainProgram0800",
   "line": "reached the row holding the corner?"
  },
  "2D2C": {
   "unit": "game/mainProgram0800",
   "line": "not yet - keep going"
  },
  "2D2F": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawFrameRow - Repeats the tile at offset Y of the current tile set along row scratch18, starting at",
    "column scratch19 and stopping when the column reaches scratch1A (exclusive).  Used for the top and",
    "bottom frame edges.",
    "In:  Y = tile offset in the set, blockPtr = the tile set, scratch18 = row, scratch19 = first column,",
    "     scratch1A = stop column",
    "Out: scratch19 = scratch1A, scratch1C = the tile's character code, the cells drawn",
    "Called from: drawFrameBox ($2CEE bottom edge, $2CFE top edge)"
   ],
   "line": "fetch the edge tile's character code once"
  },
  "2D33": {
   "unit": "game/mainProgram0800",
   "block": [
    "One cell per column until the corner column is reached."
   ],
   "line": "the edge tile"
  },
  "2D35": {
   "unit": "game/mainProgram0800",
   "line": "draw it at the current row/column"
  },
  "2D38": {
   "unit": "game/mainProgram0800",
   "line": "step one column right"
  },
  "2D3C": {
   "unit": "game/mainProgram0800",
   "line": "reached the column holding the corner?"
  },
  "2D3E": {
   "unit": "game/mainProgram0800",
   "line": "not yet - keep going"
  },
  "2D41": {
   "unit": "game/mainProgram0800",
   "routine": [
    "initBitmapScreen - Builds the game's display from scratch: a full-screen text window, grey border and",
    "background, colour RAM filled with grey, all 1024 screen-RAM bytes set to $CC (grey foreground on grey",
    "background so an empty bitmap looks uniform), the whole 8000-byte bitmap $A000-$BF3F cleared, and then",
    "the three VIC control registers and their shadows set for a hires bitmap in VIC bank 2 - $D016 = $C8",
    "(40 columns, hires), $D018 = $38 (video matrix $8C00, bitmap $A000) and $D011 = $3B (bitmap mode, 25",
    "rows, Y-scroll 3).  Finally initSpritePointers rewrites the eight sprite pointers the screen fill just",
    "overwrote.",
    "In:  none",
    "Out: VIC_BORDER/BG0/CTRL1/CTRL2/MEM_PTR and the shadows $9000-$9004; colour RAM, screen RAM and bitmap",
    "     cleared; screenBuiltFlag = 1, decimalValueHigh = 0, spriteEnableShadow = 0, sprite pointers",
    "     $8FF8-$8FFF = $28-$2F; mapCellPtr (zp_48/49) clobbered",
    "Called from: mainStart ($0AAF), drawBattlefieldScreen ($2636), the screen switcher ($06E8) and the",
    "     start-up block ($0318 by JMP)"
   ],
   "line": "text window = the whole 40x25 screen"
  },
  "2D46": {
   "unit": "game/mainProgram0800",
   "line": "every screen builder sets this flag; nothing ever reads it"
  },
  "2D48": {
   "unit": "game/mainProgram0800",
   "line": "colour $0C = medium grey"
  },
  "2D4A": {
   "unit": "game/mainProgram0800",
   "line": "shadow the IRQ writes back to $D020"
  },
  "2D4D": {
   "unit": "game/mainProgram0800",
   "line": "grey border"
  },
  "2D50": {
   "unit": "game/mainProgram0800",
   "line": "shadow of $D021"
  },
  "2D53": {
   "unit": "game/mainProgram0800",
   "line": "grey background"
  },
  "2D56": {
   "unit": "game/mainProgram0800",
   "block": [
    "Fill all four pages of colour RAM ($D800-$DBFF) with grey."
   ],
   "line": "256 offsets per page"
  },
  "2D5A": {
   "unit": "game/mainProgram0800",
   "line": "$D800-$D8FF"
  },
  "2D5D": {
   "unit": "game/mainProgram0800",
   "line": "$D900-$D9FF"
  },
  "2D60": {
   "unit": "game/mainProgram0800",
   "line": "$DA00-$DAFF"
  },
  "2D63": {
   "unit": "game/mainProgram0800",
   "line": "$DB00-$DBFF (only $D800-$DBE7 is real colour RAM)"
  },
  "2D67": {
   "unit": "game/mainProgram0800",
   "line": "256 bytes per page"
  },
  "2D69": {
   "unit": "game/mainProgram0800",
   "block": [
    "Fill the video matrix $8C00-$8FFF with $CC: in bitmap mode the high nibble is the '1' colour",
    "and the low nibble the '0' colour, so grey on grey hides the empty bitmap."
   ],
   "line": "256 offsets per page"
  },
  "2D6B": {
   "unit": "game/mainProgram0800",
   "line": "$CC = grey foreground, grey background"
  },
  "2D6D": {
   "unit": "game/mainProgram0800",
   "line": "$8C00-$8CFF"
  },
  "2D70": {
   "unit": "game/mainProgram0800",
   "line": "$8D00-$8DFF"
  },
  "2D73": {
   "unit": "game/mainProgram0800",
   "line": "$8E00-$8EFF"
  },
  "2D76": {
   "unit": "game/mainProgram0800",
   "line": "$8F00-$8FFF (this also wipes the sprite pointers at $8FF8, restored below)"
  },
  "2D7C": {
   "unit": "game/mainProgram0800",
   "block": [
    "Clear the 8000-byte bitmap $A000-$BF3F (25 rows x 320 bytes)."
   ],
   "line": "point zp_48/49 at the start of the bitmap ..."
  },
  "2D7E": {
   "unit": "game/mainProgram0800",
   "line": "start of the bitmap ..."
  },
  "2D82": {
   "unit": "game/mainProgram0800",
   "line": "... $A000"
  },
  "2D86": {
   "unit": "game/mainProgram0800",
   "line": "clear the third byte of printDecimal's 24-bit accumulator (zp_7E); the printer clears it again"
  },
  "2D88": {
   "unit": "game/mainProgram0800",
   "line": "fill each page from offset $FF downwards"
  },
  "2D8F": {
   "unit": "game/mainProgram0800",
   "line": "wrapped past 0 - the page is done"
  },
  "2D93": {
   "unit": "game/mainProgram0800",
   "line": "next page"
  },
  "2D97": {
   "unit": "game/mainProgram0800",
   "line": "$BF00 is the last, partial page"
  },
  "2D99": {
   "unit": "game/mainProgram0800",
   "line": "still below it - clear a whole page"
  },
  "2D9B": {
   "unit": "game/mainProgram0800",
   "line": "past $BF00 - the bitmap is finished"
  },
  "2D9D": {
   "unit": "game/mainProgram0800",
   "line": "$BF00-$BF3F: the final 64 bytes of the 8000-byte bitmap"
  },
  "2D9F": {
   "unit": "game/mainProgram0800",
   "line": "always taken"
  },
  "2DA1": {
   "unit": "game/mainProgram0800",
   "block": [
    "Switch the VIC into the game's display mode."
   ],
   "line": "$D016 = $C8: hires (bit 4 clear), 40 columns (bit 3), X-scroll 0"
  },
  "2DA9": {
   "unit": "game/mainProgram0800",
   "line": "$D018 = $38: video matrix at bank+$0C00 = $8C00, bitmap at bank+$2000 = $A000"
  },
  "2DB1": {
   "unit": "game/mainProgram0800",
   "line": "$D011 = $3B: display on, bitmap mode, 25 rows, Y-scroll 3"
  },
  "2DB9": {
   "unit": "game/mainProgram0800",
   "line": "put the eight sprite pointers back after the screen fill"
  },
  "2DBD": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setTerrainTilePtr - Points glyphPtr at the 8-byte terrain tile definition number A in the table at",
    "$9C6E and turns off the per-glyph colour override so the caller's drawColourByte is used.  The caller at",
    "$27B2 forms A by subtracting $40 from the map byte, and $27C2 expands the 8 bytes into the 16x16-pixel",
    "cell of the magnified battlefield view.",
    "In:  A = terrain tile index (map cell byte - $40)",
    "Out: glyphPtr/glyphPtrHi (zp_B2/B3) = $9C6E + A*8, useDrawColourFlag = 0, Y = 0",
    "Called from: renderMapTile ($27B8) and the radar tile renderer of overlay B ($73A4)"
   ],
   "line": "Y = 0 is used as the zero source for the three stores below"
  },
  "2DC1": {
   "unit": "game/mainProgram0800",
   "line": "clear the high byte so the three ROLs can shift into it"
  },
  "2DC3": {
   "unit": "game/mainProgram0800",
   "line": "0 = let the caller's drawColourByte colour the cell"
  },
  "2DC6": {
   "unit": "game/mainProgram0800",
   "line": "index * 2 ..."
  },
  "2DC9": {
   "unit": "game/mainProgram0800",
   "line": "... * 4 ..."
  },
  "2DCC": {
   "unit": "game/mainProgram0800",
   "line": "... * 8: eight bytes per terrain tile"
  },
  "2DD0": {
   "unit": "game/mainProgram0800",
   "line": "add the low byte of the tile table base $9C6E"
  },
  "2DD6": {
   "unit": "game/mainProgram0800",
   "line": "and its high byte"
  },
  "2DDB": {
   "unit": "game/mainProgram0800",
   "routine": [
    "clearMessageLineAndScreen - Wipes the status/message line first, then falls into",
    "clearCurrentScreenWindows to clear the rest of whatever screen is showing.",
    "In:  currentScreen ($90FB)",
    "Out: see clearCurrentScreenWindows",
    "Called from: the F1/F3/F5/F7 screen switcher at $070C"
   ],
   "line": "blank the bottom message line ($C39F)"
  },
  "2DDE": {
   "unit": "game/mainProgram0800",
   "routine": [
    "clearCurrentScreenWindows - Re-clears the content areas of whichever screen is active, dispatching on",
    "currentScreen: 0 = battlefield (sprites off, text colour 0, the console info panel unless the game is",
    "paused, then the overview window and the viewport text area), 1 = the comcen radar screen (overlay B's",
    "$7A6B), 2 = the drone screen (only sprite 1 enabled, then overlay B's $821F), 3 = the menu screen.  All",
    "four exits are tail calls.",
    "In:  currentScreen ($90FB), pauseState (zp_BE)",
    "Out: spriteEnableShadow = 0 (or 2 on the drone screen), textColour = 0 on the battlefield; the",
    "     per-screen clear routine is jumped to",
    "Called from: clearMessageLineAndScreen by fall-through, and from the text engine at $C75E (after the",
    "     text window has been saved, e.g. when a pause ends)"
   ],
   "line": "0 is used both as the sprite mask and as the text colour below"
  },
  "2DE0": {
   "unit": "game/mainProgram0800",
   "line": "hide every sprite while the screen is rebuilt"
  },
  "2DE3": {
   "unit": "game/mainProgram0800",
   "line": "which screen is showing? 0 battlefield, 1 comcen radar, 2 drone, 3 menu"
  },
  "2DE6": {
   "unit": "game/mainProgram0800",
   "line": "not the battlefield - dispatch below"
  },
  "2DE8": {
   "unit": "game/mainProgram0800",
   "line": "battlefield: draw the panels in colour 0"
  },
  "2DEA": {
   "unit": "game/mainProgram0800",
   "line": "pause / disconnect flags"
  },
  "2DEC": {
   "unit": "game/mainProgram0800",
   "line": "while paused the console keeps the pause text - do not wipe it"
  },
  "2DEE": {
   "unit": "game/mainProgram0800",
   "line": "clear the console info panel (rows 12-21, columns 25-38)"
  },
  "2DF1": {
   "unit": "game/mainProgram0800",
   "line": "clear the 20x20 overview map window"
  },
  "2DF4": {
   "unit": "game/mainProgram0800",
   "line": "clear the text area of the battlefield viewport and return through it"
  },
  "2DF7": {
   "unit": "game/mainProgram0800",
   "line": "screen 1?"
  },
  "2DF8": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "2DFA": {
   "unit": "game/mainProgram0800",
   "line": "comcen radar screen - overlay B redraws the radar map window"
  },
  "2DFD": {
   "unit": "game/mainProgram0800",
   "line": "screen 2?"
  },
  "2DFE": {
   "unit": "game/mainProgram0800",
   "line": "no - it must be screen 3, the menu"
  },
  "2E00": {
   "unit": "game/mainProgram0800",
   "line": "drone screen: only sprite 1 (the drone marker) stays visible"
  },
  "2E05": {
   "unit": "game/mainProgram0800",
   "line": "argument 0 = clear the whole drone view window"
  },
  "2E07": {
   "unit": "game/mainProgram0800",
   "line": "overlay B's drone window filler (variant A holds unrelated code at this address)"
  },
  "2E0A": {
   "unit": "game/mainProgram0800",
   "line": "menu screen: plain clear of the menu window"
  },
  "2E0D": {
   "unit": "game/mainProgram0800",
   "block": [
    "Two work flags of magnifyWindow2x, set when the source rectangle has an odd width or height so",
    "that its outermost row/column is drawn at half size and the zoom stays centred."
   ],
   "line": "magnifyWidthOdd: $FF when frameWidth is odd"
  },
  "2E0E": {
   "unit": "game/mainProgram0800",
   "line": "magnifyHeightOdd: $FF when frameHeight is odd"
  },
  "2E0F": {
   "unit": "game/mainProgram0800",
   "routine": [
    "magnifyWindow2x - Redraws the character rectangle at windowOriginRow/windowOriginCol, frameWidth by",
    "frameHeight cells, at twice the size in place and centred on itself: every source cell (its 8 bitmap",
    "bytes plus its screen-RAM colour byte) is re-plotted as a 2x2 cell block through the text engine's",
    "double-size glyph renderer plotGlyphDoubleSize ($C24B), at",
    "(windowOriginRow - ceil(h/2) + 2*row, windowOriginCol - ceil(w/2) + 2*col).  The rectangle is walked as",
    "four quadrants, each from the outer edge inwards, so no source cell is destroyed before it has been",
    "read.  An odd width or height makes the routine step half a cell beyond each edge and suppress the",
    "outer half of those cells through the quadrant mask magnifyEdgeMask (zp_BA), which keeps the zoom",
    "centred.  It first waits for raster line $80 to reduce tearing.",
    "In:  windowOriginRow, windowOriginCol, frameWidth ($9221), frameHeight ($9222)",
    "Out: the area redrawn twice the size; frameWidth/frameHeight decremented when even; scratch20 =",
    "     ceil(w/2), scratch21 = ceil(h/2), textCursorCol/textCursorRow (zp_3F/zp_40) = destination origin,",
    "     magnifySrcRow/Col (zp_30/31) = counters, textColour = the current cell's colour, magnifyEdgeMask",
    "     left at 0, zp_B2-zp_B9 clobbered",
    "Called from: overlay B's zoom-in animation at $756B, four times over a 7-wide by 6-tall area at row 9,",
    "     column 16"
   ],
   "line": "clear both odd-size flags before measuring the rectangle"
  },
  "2E11": {
   "unit": "game/mainProgram0800",
   "line": "assume an even width ..."
  },
  "2E14": {
   "unit": "game/mainProgram0800",
   "line": "... and an even height"
  },
  "2E17": {
   "unit": "game/mainProgram0800",
   "line": "source rectangle width in cells"
  },
  "2E1A": {
   "unit": "game/mainProgram0800",
   "line": "half of it, carry = the odd bit"
  },
  "2E1B": {
   "unit": "game/mainProgram0800",
   "line": "zp_20 = how many columns each half-pass covers"
  },
  "2E1D": {
   "unit": "game/mainProgram0800",
   "line": "even width - nothing special to do"
  },
  "2E1F": {
   "unit": "game/mainProgram0800",
   "line": "odd: round the half width up"
  },
  "2E23": {
   "unit": "game/mainProgram0800",
   "line": "remember that the left/right edge cells are drawn half width"
  },
  "2E26": {
   "unit": "game/mainProgram0800",
   "line": "always taken ($FF is negative)"
  },
  "2E28": {
   "unit": "game/mainProgram0800",
   "line": "even width: turn the count into the index of the last column"
  },
  "2E2B": {
   "unit": "game/mainProgram0800",
   "line": "source left column ..."
  },
  "2E2F": {
   "unit": "game/mainProgram0800",
   "line": "... minus half the width = destination left column"
  },
  "2E31": {
   "unit": "game/mainProgram0800",
   "line": "zp_3F holds the destination origin column (the text cursor is free here)"
  },
  "2E33": {
   "unit": "game/mainProgram0800",
   "line": "source rectangle height in cells"
  },
  "2E36": {
   "unit": "game/mainProgram0800",
   "line": "half of it"
  },
  "2E37": {
   "unit": "game/mainProgram0800",
   "line": "zp_21 = how many rows each half-pass covers"
  },
  "2E39": {
   "unit": "game/mainProgram0800",
   "line": "even height"
  },
  "2E3B": {
   "unit": "game/mainProgram0800",
   "line": "odd: round the half height up"
  },
  "2E3F": {
   "unit": "game/mainProgram0800",
   "line": "remember that the top/bottom edge cells are drawn half height"
  },
  "2E42": {
   "unit": "game/mainProgram0800",
   "line": "always taken"
  },
  "2E44": {
   "unit": "game/mainProgram0800",
   "line": "even height: turn the count into the index of the last row"
  },
  "2E47": {
   "unit": "game/mainProgram0800",
   "line": "source top row ..."
  },
  "2E4B": {
   "unit": "game/mainProgram0800",
   "line": "... minus half the height = destination top row"
  },
  "2E4D": {
   "unit": "game/mainProgram0800",
   "line": "zp_40 holds the destination origin row"
  },
  "2E4F": {
   "unit": "game/mainProgram0800",
   "block": [
    "Wait for the raster to reach line $80 so the redraw starts at a predictable place on screen."
   ],
   "line": "current raster line"
  },
  "2E52": {
   "unit": "game/mainProgram0800",
   "line": "line 128, roughly the middle of the display"
  },
  "2E54": {
   "unit": "game/mainProgram0800",
   "line": "spin until it comes round"
  },
  "2E56": {
   "unit": "game/mainProgram0800",
   "block": [
    "Quadrant pass 1 and 2: the top half of the rectangle, row 0 downwards.  For each row the left",
    "half is drawn left-to-right and then the right half right-to-left, so a cell is always read",
    "before the enlarged copy of its neighbour can cover it."
   ],
   "line": "top-half row counter"
  },
  "2E58": {
   "unit": "game/mainProgram0800",
   "line": "start at the topmost source row"
  },
  "2E5A": {
   "unit": "game/mainProgram0800",
   "line": "odd height?"
  },
  "2E5D": {
   "unit": "game/mainProgram0800",
   "line": "no - no vertical clipping on this row"
  },
  "2E5F": {
   "unit": "game/mainProgram0800",
   "line": "bit 0 = suppress the top cells of the 2x2 block ..."
  },
  "2E61": {
   "unit": "game/mainProgram0800",
   "line": "... so the top row is drawn only half tall"
  },
  "2E65": {
   "unit": "game/mainProgram0800",
   "line": "start at the leftmost source column"
  },
  "2E67": {
   "unit": "game/mainProgram0800",
   "line": "odd width?"
  },
  "2E6A": {
   "unit": "game/mainProgram0800",
   "line": "no - no horizontal clipping on this column"
  },
  "2E6E": {
   "unit": "game/mainProgram0800",
   "line": "bit 2 = suppress the left cells of the 2x2 block"
  },
  "2E72": {
   "unit": "game/mainProgram0800",
   "line": "set up source and destination pointers for this cell"
  },
  "2E77": {
   "unit": "game/mainProgram0800",
   "line": "the cell's colour byte, read from the source screen RAM copy in zp_B8/B9"
  },
  "2E79": {
   "unit": "game/mainProgram0800",
   "line": "plotGlyphDoubleSize colours all four cells with zp_3D"
  },
  "2E7B": {
   "unit": "game/mainProgram0800",
   "line": "expand the cell into a 2x2 block at the destination"
  },
  "2E80": {
   "unit": "game/mainProgram0800",
   "line": "clear the left/right suppression bits, keep the top/bottom ones for the rest of the row"
  },
  "2E84": {
   "unit": "game/mainProgram0800",
   "line": "next column to the right"
  },
  "2E8A": {
   "unit": "game/mainProgram0800",
   "line": "keep going until the middle of the row"
  },
  "2E8C": {
   "unit": "game/mainProgram0800",
   "line": "now the right half: start at the outermost column ..."
  },
  "2E8F": {
   "unit": "game/mainProgram0800",
   "line": "... which for an odd width is half a cell past the rectangle"
  },
  "2E91": {
   "unit": "game/mainProgram0800",
   "line": "odd width?"
  },
  "2E94": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "2E98": {
   "unit": "game/mainProgram0800",
   "line": "bit 3 = suppress the right cells, so this column is drawn half wide"
  },
  "2E9C": {
   "unit": "game/mainProgram0800",
   "line": "set up the pointers for this cell"
  },
  "2EA1": {
   "unit": "game/mainProgram0800",
   "line": "its colour byte"
  },
  "2EA5": {
   "unit": "game/mainProgram0800",
   "line": "expand it"
  },
  "2EAA": {
   "unit": "game/mainProgram0800",
   "line": "drop the left/right suppression again"
  },
  "2EAE": {
   "unit": "game/mainProgram0800",
   "line": "next column to the left"
  },
  "2EB4": {
   "unit": "game/mainProgram0800",
   "line": "keep going while we are still right of the middle"
  },
  "2EB8": {
   "unit": "game/mainProgram0800",
   "line": "row finished: drop the top/bottom suppression, keep nothing else"
  },
  "2EBC": {
   "unit": "game/mainProgram0800",
   "line": "next row down"
  },
  "2EC2": {
   "unit": "game/mainProgram0800",
   "line": "keep going until the middle row"
  },
  "2EC4": {
   "unit": "game/mainProgram0800",
   "block": [
    "Quadrant pass 3 and 4: the bottom half, walked upwards from the outermost row."
   ],
   "line": "start at the outermost row ..."
  },
  "2EC7": {
   "unit": "game/mainProgram0800",
   "line": "... which for an odd height is half a cell below the rectangle"
  },
  "2EC9": {
   "unit": "game/mainProgram0800",
   "line": "odd height?"
  },
  "2ECC": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "2ECE": {
   "unit": "game/mainProgram0800",
   "line": "bit 1 = suppress the bottom cells ..."
  },
  "2ED0": {
   "unit": "game/mainProgram0800",
   "line": "... so this row is drawn only half tall"
  },
  "2ED4": {
   "unit": "game/mainProgram0800",
   "line": "start at the leftmost source column again"
  },
  "2ED6": {
   "unit": "game/mainProgram0800",
   "line": "odd width?"
  },
  "2ED9": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "2EDD": {
   "unit": "game/mainProgram0800",
   "line": "bit 2 = suppress the left cells"
  },
  "2EE1": {
   "unit": "game/mainProgram0800",
   "line": "set up the pointers for this cell"
  },
  "2EE6": {
   "unit": "game/mainProgram0800",
   "line": "its colour byte"
  },
  "2EEA": {
   "unit": "game/mainProgram0800",
   "line": "expand it"
  },
  "2EEF": {
   "unit": "game/mainProgram0800",
   "line": "drop the left/right suppression"
  },
  "2EF3": {
   "unit": "game/mainProgram0800",
   "line": "next column to the right"
  },
  "2EF9": {
   "unit": "game/mainProgram0800",
   "line": "until the middle of the row"
  },
  "2EFB": {
   "unit": "game/mainProgram0800",
   "line": "right half of this row: outermost column ..."
  },
  "2F00": {
   "unit": "game/mainProgram0800",
   "line": "odd width?"
  },
  "2F03": {
   "unit": "game/mainProgram0800",
   "line": "no"
  },
  "2F07": {
   "unit": "game/mainProgram0800",
   "line": "bit 3 = suppress the right cells"
  },
  "2F0B": {
   "unit": "game/mainProgram0800",
   "line": "set up the pointers for this cell"
  },
  "2F10": {
   "unit": "game/mainProgram0800",
   "line": "its colour byte"
  },
  "2F14": {
   "unit": "game/mainProgram0800",
   "line": "expand it"
  },
  "2F19": {
   "unit": "game/mainProgram0800",
   "line": "drop the left/right suppression"
  },
  "2F1D": {
   "unit": "game/mainProgram0800",
   "line": "next column to the left"
  },
  "2F23": {
   "unit": "game/mainProgram0800",
   "line": "while still right of the middle"
  },
  "2F27": {
   "unit": "game/mainProgram0800",
   "line": "row finished - this also leaves magnifyEdgeMask at 0 on the final pass"
  },
  "2F2B": {
   "unit": "game/mainProgram0800",
   "line": "next row up"
  },
  "2F31": {
   "unit": "game/mainProgram0800",
   "line": "keep going while still below the middle row"
  },
  "2F34": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setMagnifyCellPointers - Helper of magnifyWindow2x.  Computes the pointers of the source cell",
    "(windowOriginRow + magnifySrcRow, windowOriginCol + magnifySrcCol), copies them into glyphPtr (so the",
    "bitmap cell is used as the 'glyph' to enlarge) and blockPtr (so its screen-RAM colour byte can be",
    "read), then computes the destination cell (zp_40 + 2*row, zp_3F + 2*column) into bitmapPtr/screenPtr.",
    "In:  magnifySrcRow/magnifySrcCol (zp_30/31), windowOriginRow/Col, textCursorRow/Col (zp_40/zp_3F)",
    "Out: glyphPtr (zp_B2/B3) = source bitmap cell, blockPtr (zp_B8/B9) = source screen cell,",
    "     bitmapPtr/screenPtr (zp_B4-B7) = destination cell, scratch18/19 = destination row/column",
    "Called from: magnifyWindow2x ($2E72, $2E9C, $2EE1, $2F0B)"
   ],
   "line": "clear the carry for the row add"
  },
  "2F35": {
   "unit": "game/mainProgram0800",
   "line": "row offset within the rectangle ..."
  },
  "2F37": {
   "unit": "game/mainProgram0800",
   "line": "... plus the rectangle's top row"
  },
  "2F3A": {
   "unit": "game/mainProgram0800",
   "line": "absolute source row"
  },
  "2F3D": {
   "unit": "game/mainProgram0800",
   "line": "column offset within the rectangle ..."
  },
  "2F3F": {
   "unit": "game/mainProgram0800",
   "line": "... plus the rectangle's left column"
  },
  "2F42": {
   "unit": "game/mainProgram0800",
   "line": "absolute source column"
  },
  "2F44": {
   "unit": "game/mainProgram0800",
   "line": "zp_B4-zp_B7 now point at the source cell"
  },
  "2F47": {
   "unit": "game/mainProgram0800",
   "block": [
    "Copy both source pointers aside before they are recomputed for the destination.  LDA zp,Y does",
    "not exist on the 6502, so these are absolute accesses into page zero."
   ],
   "line": "two bytes each, high byte first"
  },
  "2F49": {
   "unit": "game/mainProgram0800",
   "line": "bitmapPtr -> ..."
  },
  "2F4C": {
   "unit": "game/mainProgram0800",
   "line": "... glyphPtr: plotGlyphDoubleSize reads the source cell as if it were a font glyph"
  },
  "2F4F": {
   "unit": "game/mainProgram0800",
   "line": "screenPtr -> ..."
  },
  "2F52": {
   "unit": "game/mainProgram0800",
   "line": "... blockPtr: the caller reads the source cell's colour byte through it"
  },
  "2F58": {
   "unit": "game/mainProgram0800",
   "block": [
    "Now the destination cell: the zoom doubles every offset."
   ],
   "line": "row offset within the rectangle"
  },
  "2F5A": {
   "unit": "game/mainProgram0800",
   "line": "row offset * 2"
  },
  "2F5C": {
   "unit": "game/mainProgram0800",
   "line": "plus the destination top row"
  },
  "2F62": {
   "unit": "game/mainProgram0800",
   "line": "column offset * 2 (no CLC needed - the offset is always below $80, so ASL clears the carry)"
  },
  "2F63": {
   "unit": "game/mainProgram0800",
   "line": "plus the destination left column"
  },
  "2F67": {
   "unit": "game/mainProgram0800",
   "line": "leave zp_B4-zp_B7 pointing at the destination cell and return through it"
  },
  "2F6A": {
   "unit": "game/mainProgram0800",
   "routine": [
    "initSpritePointers - Rewrites the eight sprite pointers in the last bytes of the video matrix,",
    "$8FF8-$8FFF, with $28..$2F.  In VIC bank 2 a pointer value n means shape data at $8000 + n*64, so",
    "sprite 0 takes its shape from $8A00 and sprite 7 from $8BC0 - the 512-byte block clearAllSpriteData",
    "wipes.  Also clears the sprite enable shadow.  Needed after initBitmapScreen fills $8C00-$8FFF.",
    "In:  none",
    "Out: spriteEnableShadow = 0, $8FF8-$8FFF = $28-$2F; mapCellPtr (zp_48/49) = $8FF8; A/X/Y clobbered",
    "Called from: initBitmapScreen ($2DB9) only"
   ],
   "line": "no sprite is on screen while the display is rebuilt"
  },
  "2F6C": {
   "unit": "game/mainProgram0800",
   "line": "no sprites visible yet"
  },
  "2F75": {
   "unit": "game/mainProgram0800",
   "line": "zp_48/49 = $8FF8, the first sprite pointer"
  },
  "2F77": {
   "unit": "game/mainProgram0800",
   "line": "eight pointers, counted down"
  },
  "2F79": {
   "unit": "game/mainProgram0800",
   "line": "$2F = sprite 7's shape at $8000 + $2F*64 = $8BC0"
  },
  "2F7C": {
   "unit": "game/mainProgram0800",
   "line": "$8FF8+Y = the pointer of sprite Y"
  },
  "2F7E": {
   "unit": "game/mainProgram0800",
   "line": "next shape block down"
  },
  "2F80": {
   "unit": "game/mainProgram0800",
   "line": "until sprite 0 gets $28 = $8A00"
  },
  "2F82": {
   "unit": "game/mainProgram0800",
   "line": "leftover: A becomes $29/$2A and is never used by the only caller"
  },
  "2F85": {
   "unit": "game/mainProgram0800",
   "block": [
    "The two bit-mask tables.  spriteBitMasks is the set mask for bit n and is used both for sprite",
    "enable bits ($3065) and as the keyboard column mask ($0E16); invertedBitMasks is its complement,",
    "used to clear a sprite bit ($337D) and as the keyboard row mask ($0E08)."
   ],
   "line": "set masks $01,$02,$04 ... $80 for bit / sprite / keyboard column n"
  },
  "2F8D": {
   "unit": "game/mainProgram0800",
   "line": "clear masks $FE,$FD,$FB ... $7F - the same table complemented (was D_2F8D)"
  },
  "2F95": {
   "unit": "game/mainProgram0800",
   "block": [
    "Sprite shape addresses split into two tables so that $8A00 + n*64 can be formed with a single",
    "16-bit add (see clearSpriteShape)."
   ],
   "line": "low byte of n*64 for sprites 0-7"
  },
  "2F9D": {
   "unit": "game/mainProgram0800",
   "line": "high byte of n*64: 0 for sprites 0-3, 1 for sprites 4-7"
  },
  "2FA5": {
   "unit": "game/mainProgram0800",
   "routine": [
    "clearAllSpriteData - Zeroes $8A00-$8BFF, the 64-byte shape blocks of all eight sprites, in one 256-pass",
    "loop that writes two pages at a time.",
    "In:  none",
    "Out: $8A00-$8BFF = 0; A = 0, X = 0",
    "Called from: the start-up block at $0386 (by JMP, but the routine ends in RTS)"
   ],
   "line": "one pass over 256 offsets"
  },
  "2FA7": {
   "unit": "game/mainProgram0800",
   "line": "the fill value is 0"
  },
  "2FA8": {
   "unit": "game/mainProgram0800",
   "line": "sprites 0-3 ($8A00-$8AFF)"
  },
  "2FAB": {
   "unit": "game/mainProgram0800",
   "line": "sprites 4-7 ($8B00-$8BFF)"
  },
  "2FAF": {
   "unit": "game/mainProgram0800",
   "line": "256 offsets, both pages at once"
  },
  "2FB2": {
   "unit": "game/mainProgram0800",
   "routine": [
    "clearSpriteShape - Zeroes the 64-byte shape of sprite A at $8A00 + A*64.  The address is built from the",
    "two half tables and patched into the operand of the STA abs,Y at $2FC8, which in the raw image still",
    "holds its unpatched $FFFF operand (hence the 'sta irqVectorHi,y' the disassembler shows).",
    "In:  A = sprite number 0-7",
    "Out: that sprite's 64 shape bytes are 0; the operand at $2FC9/$2FCA is patched; A = 0, Y = $FF",
    "Called from: $2245, $224A, setCursorShapeSquare ($2FDB), setCursorShapeBlock ($2FFD), $3385, $346A,",
    "     $3CFF and the jump at $3D53, plus the overlay screens ($7184, $76A1, $7781, $8241)"
   ],
   "line": "index the two half tables with the sprite number"
  },
  "2FB6": {
   "unit": "game/mainProgram0800",
   "line": "low byte of sprite*64"
  },
  "2FB9": {
   "unit": "game/mainProgram0800",
   "line": "patch the low operand byte of the STA at $2FC8"
  },
  "2FBC": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the shape area base $8A00 ..."
  },
  "2FBE": {
   "unit": "game/mainProgram0800",
   "line": "... plus the high byte of sprite*64"
  },
  "2FC1": {
   "unit": "game/mainProgram0800",
   "line": "patch the high operand byte"
  },
  "2FC4": {
   "unit": "game/mainProgram0800",
   "line": "64 bytes per sprite shape"
  },
  "2FC8": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $2FB9/$2FC1 - really STA $8A00+sprite*64,Y"
  },
  "2FCF": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setCursorShapeSquare - Gives sprite 0, the map cursor, the 18-byte (6 rows of 24 pixels) hollow square",
    "at $9E6E, sets its colour shadow to 0 and enables it.  The sprite is Y-expanded elsewhere so the six",
    "rows cover a whole 12-pixel map cell.",
    "In:  none",
    "Out: sprite 0's shape at $8A00, spriteColourShadow ($9008) = 0, sprite 0 enabled",
    "Called from: $2015, $4C53, $5E40, $5E87, $61F5, $6393 - whenever a unit is picked up or ordered"
   ],
   "line": "low byte of the hollow-square shape at $9E6E"
  },
  "2FD1": {
   "unit": "game/mainProgram0800",
   "line": "high byte"
  },
  "2FD3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "installCursorShape - Shared tail of setCursorShapeSquare and setCursorShapeCross: copies the 18-byte",
    "shape whose address is in A/Y into sprite 0, after blanking the rest of the sprite, and enables it in",
    "black.  The source address is planted in the operand of the LDA abs,Y at $2FE0.",
    "In:  A/Y = lo/hi address of an 18-byte (6 row) sprite shape",
    "Out: sprite 0's shape rewritten, spriteColourShadow = 0, spriteEnableShadow bit 0 set; the operand at",
    "     $2FE1/$2FE2 patched",
    "Called from: setCursorShapeSquare by fall-through and setCursorShapeCross ($2FF7 by JMP)"
   ],
   "line": "patch the low operand byte of the LDA at $2FE0"
  },
  "2FD6": {
   "unit": "game/mainProgram0800",
   "line": "and its high operand byte"
  },
  "2FD9": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0 = the map cursor"
  },
  "2FDB": {
   "unit": "game/mainProgram0800",
   "line": "blank all 64 bytes so the previous shape cannot show through"
  },
  "2FDE": {
   "unit": "game/mainProgram0800",
   "line": "18 bytes = 6 rows of 3 bytes"
  },
  "2FE0": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $2FD3/$2FD6 - really LDA $9E6E,Y or $9E80,Y"
  },
  "2FE3": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0's shape data at $8A00"
  },
  "2FE9": {
   "unit": "game/mainProgram0800",
   "line": "colour 0 = black; shadow values below $10 are steady, $10 and above flash between two nibbles"
  },
  "2FEE": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0's enable bit"
  },
  "2FF0": {
   "unit": "game/mainProgram0800",
   "line": "OR it into the enable shadow and return through it"
  },
  "2FF3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setCursorShapeCross - The same as setCursorShapeSquare but installs the thick plus/cross shape at $9E80",
    "instead.  Used for the ranged attack cursor, when cursorTargetMode (zp_BB) is positive.",
    "In:  none",
    "Out: sprite 0 = the cross shape, colour 0, enabled",
    "Called from: the cursor selector at $2195"
   ],
   "line": "low byte of the cross shape at $9E80"
  },
  "2FF5": {
   "unit": "game/mainProgram0800",
   "line": "high byte"
  },
  "2FF7": {
   "unit": "game/mainProgram0800",
   "line": "share the copy/enable tail"
  },
  "2FFA": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setCursorShapeBlock - Loads the small 4x4 solid block at $9E92 into the sprite named by",
    "activeSpriteIndex ($91CC) and sets that sprite's colour shadow to $0C (grey) - except for sprite 0,",
    "which keeps whatever colour the cursor code gave it.  Note the shape address is formed with six ASLs, so",
    "it only reaches sprites 0-3; both callers pass 0 or 1.",
    "In:  activeSpriteIndex ($91CC) = sprite number (0 or 1 in practice)",
    "Out: that sprite's 64-byte shape holds the block; spriteColourShadow[n] = $0C for n other than 0",
    "Called from: the cursor selector at $21A8 (plain battlefield cursor) and $2550 (sprite 1 when no group",
    "     box is up)"
   ],
   "line": "which sprite is being dressed"
  },
  "2FFD": {
   "unit": "game/mainProgram0800",
   "line": "blank its 64 shape bytes first"
  },
  "3003": {
   "unit": "game/mainProgram0800",
   "line": "sprite number * 2 ..."
  },
  "3008": {
   "unit": "game/mainProgram0800",
   "line": "... * 64 = its offset inside the shape block (wraps above sprite 3)"
  },
  "300C": {
   "unit": "game/mainProgram0800",
   "line": "the 18-byte solid block shape at $9E92"
  },
  "300F": {
   "unit": "game/mainProgram0800",
   "line": "into $8A00 + sprite*64"
  },
  "3014": {
   "unit": "game/mainProgram0800",
   "line": "18 bytes = 6 rows of 3"
  },
  "3018": {
   "unit": "game/mainProgram0800",
   "line": "colour $0C = medium grey"
  },
  "301D": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0 keeps the cursor colour set by installCursorShape"
  },
  "301F": {
   "unit": "game/mainProgram0800",
   "line": "any other sprite is drawn grey"
  },
  "2A19": {
   "unit": "game/mainProgram0800",
   "line": "the two renderers now test 'is this unit on the viewed side?' the right way round"
  },
  "2A46": {
   "unit": "game/mainProgram0800",
   "line": "back to the caller with A intact"
  },
  "2A55": {
   "unit": "game/mainProgram0800",
   "line": "back to the caller with A intact"
  },
  "2A63": {
   "unit": "game/mainProgram0800",
   "line": "back to the caller with Y intact"
  },
  "2A6F": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the first cell of the top row"
  },
  "2A74": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry before adding the column offset"
  },
  "2A91": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry before the row step"
  },
  "2A96": {
   "unit": "game/mainProgram0800",
   "line": "pointer now addresses the same column one row down"
  },
  "2A9E": {
   "unit": "game/mainProgram0800",
   "line": "the rectangle is filled"
  },
  "2AA6": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the 16-bit add"
  },
  "2AAA": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the cell's bitmap address"
  },
  "2AB5": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the screen-RAM add"
  },
  "2AB9": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the cell's screen-RAM address"
  },
  "2AC2": {
   "unit": "game/mainProgram0800",
   "line": "both pointers are ready for plotGlyph"
  },
  "2AC9": {
   "unit": "game/mainProgram0800",
   "line": "clear the glyph pointer before building it"
  },
  "2ADC": {
   "unit": "game/mainProgram0800",
   "line": "carry the overflow into the high byte"
  },
  "2ADF": {
   "unit": "game/mainProgram0800",
   "line": "carry again"
  },
  "2AE2": {
   "unit": "game/mainProgram0800",
   "line": "and again - glyphPtrHi now holds bits 8-10 of code*8"
  },
  "2AE4": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the base-address add"
  },
  "2AE7": {
   "unit": "game/mainProgram0800",
   "line": "low byte of $9426 + code*8"
  },
  "2AE9": {
   "unit": "game/mainProgram0800",
   "line": "bits 8-10 of code*8"
  },
  "2AF7": {
   "unit": "game/mainProgram0800",
   "line": "next pixel row up"
  },
  "2B0C": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the pointer bump"
  },
  "2B0D": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the screen-RAM cell address"
  },
  "2B11": {
   "unit": "game/mainProgram0800",
   "line": "zp_B6/B7 now addresses the same cell in colour RAM"
  },
  "2B15": {
   "unit": "game/mainProgram0800",
   "line": "third colour of the multicolour pixel pair"
  },
  "2B17": {
   "unit": "game/mainProgram0800",
   "line": "one cell drawn"
  },
  "2B1A": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the block address"
  },
  "2B1C": {
   "unit": "game/mainProgram0800",
   "line": "read the 2-byte header through (blockPtr),y"
  },
  "2B25": {
   "unit": "game/mainProgram0800",
   "line": "no page crossing"
  },
  "2B27": {
   "unit": "game/mainProgram0800",
   "line": "carry into the high byte"
  },
  "2B2B": {
   "unit": "game/mainProgram0800",
   "line": "also published for drawWindowInnerCorners"
  },
  "2B30": {
   "unit": "game/mainProgram0800",
   "line": "no page crossing"
  },
  "2B32": {
   "unit": "game/mainProgram0800",
   "line": "carry into the high byte"
  },
  "2B34": {
   "unit": "game/mainProgram0800",
   "line": "start at the top row of the block"
  },
  "2B41": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the row add"
  },
  "2B49": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the column add"
  },
  "2B64": {
   "unit": "game/mainProgram0800",
   "line": "column just drawn"
  },
  "2B66": {
   "unit": "game/mainProgram0800",
   "line": "have we reached the block's width?"
  },
  "2B6D": {
   "unit": "game/mainProgram0800",
   "line": "row just finished"
  },
  "2B6F": {
   "unit": "game/mainProgram0800",
   "line": "have we reached the block's height?"
  },
  "2B74": {
   "unit": "game/mainProgram0800",
   "line": "the whole block is on screen"
  },
  "2B78": {
   "unit": "game/mainProgram0800",
   "line": "scratch18 = the row of the top studs (bumped below)"
  },
  "2B7A": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the height add"
  },
  "2B83": {
   "unit": "game/mainProgram0800",
   "line": "scratch19 = the column of the left studs (bumped below)"
  },
  "2B85": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the width add"
  },
  "2B98": {
   "unit": "game/mainProgram0800",
   "line": "the column just inside the right edge"
  },
  "2B9C": {
   "unit": "game/mainProgram0800",
   "line": "the same ring glyph again"
  },
  "2BA1": {
   "unit": "game/mainProgram0800",
   "line": "the row just inside the bottom edge"
  },
  "2BA5": {
   "unit": "game/mainProgram0800",
   "line": "the same ring glyph again"
  },
  "2BAD": {
   "unit": "game/mainProgram0800",
   "line": "back to the window's left column"
  },
  "2BB1": {
   "unit": "game/mainProgram0800",
   "line": "the fourth ring glyph"
  },
  "2BB6": {
   "unit": "game/mainProgram0800",
   "line": "all four studs drawn"
  },
  "2BB9": {
   "unit": "game/mainProgram0800",
   "line": "index the top half of the pair"
  },
  "2BC2": {
   "unit": "game/mainProgram0800",
   "line": "get the doubled index back"
  },
  "2BC3": {
   "unit": "game/mainProgram0800",
   "line": "index it again for the bottom half"
  },
  "2BCD": {
   "unit": "game/mainProgram0800",
   "line": "both halves of the symbol are drawn"
  },
  "2C82": {
   "unit": "game/mainProgram0800",
   "line": "left column of the window the plotters will use"
  },
  "2C88": {
   "unit": "game/mainProgram0800",
   "line": "top row of that window"
  },
  "2C8E": {
   "unit": "game/mainProgram0800",
   "line": "width in cells, before drawFrameBox decrements it"
  },
  "2C94": {
   "unit": "game/mainProgram0800",
   "line": "height in cells, before drawFrameBox decrements it"
  },
  "2C99": {
   "unit": "game/mainProgram0800",
   "line": "index the pointer table"
  },
  "2C9D": {
   "unit": "game/mainProgram0800",
   "line": "step to the high byte of the pointer"
  },
  "2CA3": {
   "unit": "game/mainProgram0800",
   "line": "zp_B8/B9 = the 9-byte tile set"
  },
  "2CB0": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the height add"
  },
  "2CBB": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the width add"
  },
  "2CC3": {
   "unit": "game/mainProgram0800",
   "line": "tile 0 of the set"
  },
  "2CCA": {
   "unit": "game/mainProgram0800",
   "line": "move to the right-hand column"
  },
  "2CCE": {
   "unit": "game/mainProgram0800",
   "line": "tile 2 of the set"
  },
  "2CD5": {
   "unit": "game/mainProgram0800",
   "line": "move down to the bottom row"
  },
  "2CD9": {
   "unit": "game/mainProgram0800",
   "line": "tile 8 of the set"
  },
  "2CE1": {
   "unit": "game/mainProgram0800",
   "line": "back to the left-hand column"
  },
  "2CE5": {
   "unit": "game/mainProgram0800",
   "line": "tile 6 of the set"
  },
  "2CF4": {
   "unit": "game/mainProgram0800",
   "line": "row of the top edge"
  },
  "2CF9": {
   "unit": "game/mainProgram0800",
   "line": "one column right of the top-left corner"
  },
  "2CFA": {
   "unit": "game/mainProgram0800",
   "line": "first column of the top edge run"
  },
  "2D04": {
   "unit": "game/mainProgram0800",
   "line": "one row below the top-left corner"
  },
  "2D05": {
   "unit": "game/mainProgram0800",
   "line": "first row of the left edge run"
  },
  "2D0A": {
   "unit": "game/mainProgram0800",
   "line": "the window's left column"
  },
  "2D14": {
   "unit": "game/mainProgram0800",
   "line": "one row below the top-right corner"
  },
  "2D15": {
   "unit": "game/mainProgram0800",
   "line": "first row of the right edge run"
  },
  "2D19": {
   "unit": "game/mainProgram0800",
   "line": "the window's right column"
  },
  "2D1F": {
   "unit": "game/mainProgram0800",
   "line": "keep it in zp_1C - drawCharAt clobbers A"
  },
  "2D28": {
   "unit": "game/mainProgram0800",
   "line": "the row we have just moved to"
  },
  "2D2E": {
   "unit": "game/mainProgram0800",
   "line": "the column is finished one cell above the corner"
  },
  "2D31": {
   "unit": "game/mainProgram0800",
   "line": "keep it in zp_1C - drawCharAt clobbers A"
  },
  "2D3A": {
   "unit": "game/mainProgram0800",
   "line": "the column we have just moved to"
  },
  "2D40": {
   "unit": "game/mainProgram0800",
   "line": "the run is finished one cell before the corner"
  },
  "2D44": {
   "unit": "game/mainProgram0800",
   "line": "mark the screen as (re)built"
  },
  "2D58": {
   "unit": "game/mainProgram0800",
   "line": "grey again (the STAs below clobber A)"
  },
  "2D66": {
   "unit": "game/mainProgram0800",
   "line": "next offset in the page"
  },
  "2D79": {
   "unit": "game/mainProgram0800",
   "line": "next offset in the page"
  },
  "2D7A": {
   "unit": "game/mainProgram0800",
   "line": "all four pages of the video matrix"
  },
  "2D80": {
   "unit": "game/mainProgram0800",
   "line": "..."
  },
  "2D84": {
   "unit": "game/mainProgram0800",
   "line": "0 for the store below and for the bitmap fill"
  },
  "2D8A": {
   "unit": "game/mainProgram0800",
   "line": "the bitmap is cleared to background pixels"
  },
  "2D8C": {
   "unit": "game/mainProgram0800",
   "line": "clear one bitmap byte"
  },
  "2D8E": {
   "unit": "game/mainProgram0800",
   "line": "work down through the page"
  },
  "2D91": {
   "unit": "game/mainProgram0800",
   "line": "not wrapped yet - keep clearing"
  },
  "2D95": {
   "unit": "game/mainProgram0800",
   "line": "check how far up the bitmap we have got"
  },
  "2DA3": {
   "unit": "game/mainProgram0800",
   "line": "shadow the raster IRQ writes back to $D016"
  },
  "2DA6": {
   "unit": "game/mainProgram0800",
   "line": "and the register itself"
  },
  "2DAB": {
   "unit": "game/mainProgram0800",
   "line": "shadow of $D018"
  },
  "2DAE": {
   "unit": "game/mainProgram0800",
   "line": "and the register itself"
  },
  "2DB3": {
   "unit": "game/mainProgram0800",
   "line": "shadow of $D011"
  },
  "2DB6": {
   "unit": "game/mainProgram0800",
   "line": "and the register itself - the bitmap screen is now live"
  },
  "2DBC": {
   "unit": "game/mainProgram0800",
   "line": "the display is ready"
  },
  "2DBF": {
   "unit": "game/mainProgram0800",
   "line": "clear the tile pointer before building it"
  },
  "2DC7": {
   "unit": "game/mainProgram0800",
   "line": "carry the overflow into the high byte"
  },
  "2DCA": {
   "unit": "game/mainProgram0800",
   "line": "carry again"
  },
  "2DCD": {
   "unit": "game/mainProgram0800",
   "line": "and again - glyphPtrHi holds bits 8-10 of index*8"
  },
  "2DCF": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the base-address add"
  },
  "2DD2": {
   "unit": "game/mainProgram0800",
   "line": "low byte of $9C6E + index*8"
  },
  "2DD4": {
   "unit": "game/mainProgram0800",
   "line": "bits 8-10 of index*8"
  },
  "2DD8": {
   "unit": "game/mainProgram0800",
   "line": "zp_B2/B3 now points at the 8-byte terrain tile"
  },
  "2DDA": {
   "unit": "game/mainProgram0800",
   "line": "the caller can now read the eight tile bytes through (glyphPtr),y"
  },
  "2E02": {
   "unit": "game/mainProgram0800",
   "line": "%00000010 - sprite 1 only"
  },
  "2E21": {
   "unit": "game/mainProgram0800",
   "line": "$FF = odd"
  },
  "2E2E": {
   "unit": "game/mainProgram0800",
   "line": "prepare the subtraction"
  },
  "2E3D": {
   "unit": "game/mainProgram0800",
   "line": "$FF = odd"
  },
  "2E4A": {
   "unit": "game/mainProgram0800",
   "line": "prepare the subtraction"
  },
  "2E63": {
   "unit": "game/mainProgram0800",
   "line": "left-half column counter"
  },
  "2E6C": {
   "unit": "game/mainProgram0800",
   "line": "add to the mask that is already there"
  },
  "2E70": {
   "unit": "game/mainProgram0800",
   "line": "so the leftmost column is drawn half wide"
  },
  "2E75": {
   "unit": "game/mainProgram0800",
   "line": "the colour byte is the single byte at the source screen cell"
  },
  "2E7E": {
   "unit": "game/mainProgram0800",
   "line": "read the mask back"
  },
  "2E82": {
   "unit": "game/mainProgram0800",
   "line": "only the top/bottom bits survive into the next cell"
  },
  "2E86": {
   "unit": "game/mainProgram0800",
   "line": "the column just drawn"
  },
  "2E88": {
   "unit": "game/mainProgram0800",
   "line": "compare against the half width"
  },
  "2E96": {
   "unit": "game/mainProgram0800",
   "line": "add to the mask that is already there"
  },
  "2E9A": {
   "unit": "game/mainProgram0800",
   "line": "so the rightmost column is drawn half wide"
  },
  "2E9F": {
   "unit": "game/mainProgram0800",
   "line": "the colour byte is the single byte at the source screen cell"
  },
  "2EA3": {
   "unit": "game/mainProgram0800",
   "line": "colour for the 2x2 block"
  },
  "2EA8": {
   "unit": "game/mainProgram0800",
   "line": "read the mask back"
  },
  "2EAC": {
   "unit": "game/mainProgram0800",
   "line": "only the top/bottom bits survive into the next cell"
  },
  "2EB0": {
   "unit": "game/mainProgram0800",
   "line": "the column just drawn"
  },
  "2EB2": {
   "unit": "game/mainProgram0800",
   "line": "compare against the half width"
  },
  "2EB6": {
   "unit": "game/mainProgram0800",
   "line": "read the mask back"
  },
  "2EBA": {
   "unit": "game/mainProgram0800",
   "line": "only the left/right bits survive into the next row"
  },
  "2EBE": {
   "unit": "game/mainProgram0800",
   "line": "the row just finished"
  },
  "2EC0": {
   "unit": "game/mainProgram0800",
   "line": "compare against the half height"
  },
  "2ED2": {
   "unit": "game/mainProgram0800",
   "line": "left-half column counter"
  },
  "2EDB": {
   "unit": "game/mainProgram0800",
   "line": "add to the mask that is already there"
  },
  "2EDF": {
   "unit": "game/mainProgram0800",
   "line": "so the leftmost column is drawn half wide"
  },
  "2EE4": {
   "unit": "game/mainProgram0800",
   "line": "read the source cell's colour byte"
  },
  "2EE8": {
   "unit": "game/mainProgram0800",
   "line": "colour for the 2x2 block"
  },
  "2EED": {
   "unit": "game/mainProgram0800",
   "line": "read the mask back"
  },
  "2EF1": {
   "unit": "game/mainProgram0800",
   "line": "only the top/bottom bits survive into the next cell"
  },
  "2EF5": {
   "unit": "game/mainProgram0800",
   "line": "the column just drawn"
  },
  "2EF7": {
   "unit": "game/mainProgram0800",
   "line": "compare against the half width"
  },
  "2EFE": {
   "unit": "game/mainProgram0800",
   "line": "start the right half at the outermost column"
  },
  "2F05": {
   "unit": "game/mainProgram0800",
   "line": "add to the mask that is already there"
  },
  "2F09": {
   "unit": "game/mainProgram0800",
   "line": "so the rightmost column is drawn half wide"
  },
  "2F0E": {
   "unit": "game/mainProgram0800",
   "line": "read the source cell's colour byte"
  },
  "2F12": {
   "unit": "game/mainProgram0800",
   "line": "colour for the 2x2 block"
  },
  "2F17": {
   "unit": "game/mainProgram0800",
   "line": "read the mask back"
  },
  "2F1B": {
   "unit": "game/mainProgram0800",
   "line": "only the top/bottom bits survive into the next cell"
  },
  "2F1F": {
   "unit": "game/mainProgram0800",
   "line": "the column just drawn"
  },
  "2F21": {
   "unit": "game/mainProgram0800",
   "line": "compare against the half width"
  },
  "2F25": {
   "unit": "game/mainProgram0800",
   "line": "read the mask back"
  },
  "2F29": {
   "unit": "game/mainProgram0800",
   "line": "only the left/right bits survive into the next row"
  },
  "2F2D": {
   "unit": "game/mainProgram0800",
   "line": "the row just finished"
  },
  "2F2F": {
   "unit": "game/mainProgram0800",
   "line": "compare against the half height"
  },
  "2F33": {
   "unit": "game/mainProgram0800",
   "line": "the whole rectangle has been redrawn at double size"
  },
  "2F3C": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the column add"
  },
  "2F55": {
   "unit": "game/mainProgram0800",
   "line": "high byte then low byte"
  },
  "2F56": {
   "unit": "game/mainProgram0800",
   "line": "two bytes of each pointer copied"
  },
  "2F5B": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the origin add"
  },
  "2F5E": {
   "unit": "game/mainProgram0800",
   "line": "destination row"
  },
  "2F60": {
   "unit": "game/mainProgram0800",
   "line": "column offset within the rectangle"
  },
  "2F65": {
   "unit": "game/mainProgram0800",
   "line": "destination column"
  },
  "2F6F": {
   "unit": "game/mainProgram0800",
   "line": "low byte of $8FF8 ..."
  },
  "2F71": {
   "unit": "game/mainProgram0800",
   "line": "..."
  },
  "2F73": {
   "unit": "game/mainProgram0800",
   "line": "... high byte"
  },
  "2F7B": {
   "unit": "game/mainProgram0800",
   "line": "the pointer value for this sprite"
  },
  "2F7F": {
   "unit": "game/mainProgram0800",
   "line": "next sprite down"
  },
  "2F84": {
   "unit": "game/mainProgram0800",
   "line": "the eight sprite pointers are back in place"
  },
  "2FAE": {
   "unit": "game/mainProgram0800",
   "line": "next offset"
  },
  "2FB1": {
   "unit": "game/mainProgram0800",
   "line": "all eight sprite shapes are blank"
  },
  "2FB3": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the 16-bit add"
  },
  "2FB4": {
   "unit": "game/mainProgram0800",
   "line": "the low half of the base address $8A00 is zero"
  },
  "2FC6": {
   "unit": "game/mainProgram0800",
   "line": "the fill value"
  },
  "2FCB": {
   "unit": "game/mainProgram0800",
   "line": "work down through the 64 bytes"
  },
  "2FCC": {
   "unit": "game/mainProgram0800",
   "line": "all 64 bytes cleared"
  },
  "2FCE": {
   "unit": "game/mainProgram0800",
   "line": "the sprite is blank and ready to be filled"
  },
  "2FE6": {
   "unit": "game/mainProgram0800",
   "line": "work down through the 18 bytes"
  },
  "2FE7": {
   "unit": "game/mainProgram0800",
   "line": "18 bytes copied"
  },
  "2FEB": {
   "unit": "game/mainProgram0800",
   "line": "sprite 0's colour shadow, applied to $D027 by the raster IRQ"
  },
  "3000": {
   "unit": "game/mainProgram0800",
   "line": "the sprite number again"
  },
  "3004": {
   "unit": "game/mainProgram0800",
   "line": "... * 4 ..."
  },
  "3005": {
   "unit": "game/mainProgram0800",
   "line": "... * 8 ..."
  },
  "3006": {
   "unit": "game/mainProgram0800",
   "line": "... * 16 ..."
  },
  "3007": {
   "unit": "game/mainProgram0800",
   "line": "... * 32 ..."
  },
  "3009": {
   "unit": "game/mainProgram0800",
   "line": "X = the byte offset of this sprite's shape inside $8A00"
  },
  "300A": {
   "unit": "game/mainProgram0800",
   "line": "start at the first byte of the shape"
  },
  "3012": {
   "unit": "game/mainProgram0800",
   "line": "next destination byte"
  },
  "3013": {
   "unit": "game/mainProgram0800",
   "line": "next source byte"
  },
  "3016": {
   "unit": "game/mainProgram0800",
   "line": "18 bytes = 6 rows of 3"
  },
  "301A": {
   "unit": "game/mainProgram0800",
   "line": "which sprite was dressed?"
  },
  "3022": {
   "unit": "game/mainProgram0800",
   "line": "done - the cursor sprite is dressed"
  }
 },
 "dataTypes": {
  "2A29": [
   "byte",
   7
  ],
  "2A30": [
   "byte",
   4
  ],
  "2A34": [
   "byte",
   4
  ],
  "2BDD": [
   "byte",
   27
  ],
  "2BF8": [
   "byte",
   27
  ],
  "2C13": [
   "byte",
   27
  ],
  "2C2E": [
   "byte",
   27
  ],
  "2C49": [
   "addr",
   54
  ],
  "2E0D": [
   "byte",
   2
  ],
  "2F85": [
   "byte",
   16
  ],
  "2F95": [
   "byte",
   8
  ],
  "2F9D": [
   "byte",
   8
  ]
 },
 "zpComments": {
  "1D": "general scratch: bounds mode flag of computeGroupBounds (bit 7 = use destinations), division remainder (hi), running index into the character data of drawCharBlock",
  "3F": "text engine: cursor column relative to textWindowLeft; magnifyWindow2x borrows it as the destination left column of the 2x zoom",
  "40": "text engine: absolute cursor row (0-24); magnifyWindow2x borrows it as the destination top row of the 2x zoom",
  "BA": "quadrant suppression mask for plotGlyphDoubleSize (bit 0 = skip the top cells, 1 = bottom, 2 = left, 3 = right); only magnifyWindow2x ever writes it, so double-size text relies on it being left at 0; the boot loader uses the same byte as KERNAL FA"
 }
}