{
 "chunk": "game_mainProgram0800_57BE",
 "unit": "game/mainProgram0800",
 "unitLabels": {
  "game/mainProgram0800": {}
 },
 "notes": {
  "57BE": {
   "unit": "game/mainProgram0800",
   "routine": [
    "writeFilmTimestamp - Writes the time stamp of one game-film record at (filmPtr),Y. The stamp is the game clock, stored as a 4-bit delta against the clock of the previous record whenever the clock has moved by 0..14 ticks (the usual case, since the clock ticks down once per round), otherwise as a full byte. The low nibble of the first byte always carries commandSliceIndex ($923C), the number of the 10-unit slice of the round this exchange belongs to; a zero high nibble is what tells the reader that an absolute clock byte follows. The previous clock is kept in the SBC operand at $57C3, which startGameSession ($7911) clears when a new film starts.",
    "In:  Y = offset of the next free byte inside the record; gameClock $91CB; commandSliceIndex $923C; filmPtr $BC/$BD; the previous clock in the operand L_57C2+1 ($57C3)",
    "Out: 1 or 2 bytes written into the film buffer under the I/O area; Y advanced past them; L_57C2+1 updated to the current clock",
    "Called from: recordExchangeToFilm ($57AA) for every recorded exchange and finalizeFilm ($5808) for the end-of-film marker.  The playback counterpart is inside processCommandExchange at $4DE1-$4E04."
   ],
   "line": "current game clock (a round counter that runs down to 0: 63, 127 or 254 at kick-off depending on the game type)"
  },
  "57E7": {
   "unit": "game/mainProgram0800",
   "routine": [
    "advanceFilmPtr - Adds the record length in Y to the 16-bit film write pointer filmPtr ($BC/$BD), so the next record starts where this one ended.",
    "In:  Y = number of bytes just written; filmPtr $BC/$BD",
    "Out: filmPtr advanced by Y; A clobbered",
    "Called from: processCommandExchange ($4E30) after replaying a record, recordExchangeToFilm ($57BA) and finalizeFilm ($580B)."
   ],
   "line": "Y = number of bytes the record occupied"
  },
  "57F2": {
   "unit": "game/mainProgram0800",
   "routine": [
    "finalizeFilm - Closes a game film that was being recorded: appends the end-of-film marker record ($FE = the game played itself out, $FF = it was aborted) with a time stamp if the buffer still has room, then rewinds and fills in the header at $D000 - total film length, format id, final clock, both 16-bit scores and the checksum over $D000-$D032 - and finally leaves filmPtr pointing at the end of the recording again.",
    "In:  filmPtr $BC/$BD, filmEndPtr $92F1/$92F2, gameEndReason $0BA8 (bit 0 = aborted), gameVersionByte $0B94, gameClock $91CB, the two scores $92A2-$92A5, playerSide $0B9F",
    "Out: end marker appended, header bytes $D000/$D001 (length), $D02F (format id), $D031 (final clock), $D00D-$D010 (scores) and $D033 (checksum) written; filmEndPtr = end of the recording; filmPtr = filmEndPtr",
    "Called from: endGameToMainMenu ($1B18) only, and only when a film was actually being recorded."
   ],
   "line": "C=1 when the recording has already reached the $DFF0 limit"
  },
  "585C": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setFilmPtrToEnd - Points filmPtr ($BC/$BD) at filmEndPtr ($92F1/$92F2), i.e. back at the end of the film after the header has been read or written.",
    "In:  filmEndPtr $92F1/$92F2",
    "Out: filmPtr = filmEndPtr; A clobbered",
    "Called from: the tail of finalizeFilm (fall-through from $5859) and finishFilmPlayback ($59F9)."
   ],
   "line": "end of the recording, low byte"
  },
  "5867": {
   "unit": "game/mainProgram0800",
   "routine": [
    "saveFilmEndAndRewind - Remembers the current film position as the end of the recording (filmEndPtr = filmPtr) and then rewinds filmPtr to the start of the buffer at $D000, ready for header access.",
    "In:  filmPtr $BC/$BD",
    "Out: filmEndPtr $92F1/$92F2 = old filmPtr; filmPtr = $D000; A clobbered",
    "Called from: finalizeFilm ($580E) when a recording is closed and finishFilmPlayback ($59D0) when a replay reaches the end."
   ],
   "line": "current film position, low byte"
  },
  "5874": {
   "unit": "game/mainProgram0800",
   "routine": [
    "readFilmByte - Reads one byte of the game film out of the 4 KB of RAM that lies underneath the I/O area ($D000-$DFFF).  Interrupts are disabled, the CIA2 interrupt mask (the modem NMI) is cleared, $01 is switched from $35 to $34 so the RAM shows through, the byte is fetched and everything is put back, including the mask the comm module keeps in $E033.  X is preserved through the self-modified LDX # at $5890.",
    "In:  Y = offset from filmPtr $BC/$BD",
    "Out: A = the byte; X, Y, filmPtr unchanged; CPU_PORT back at $35 and CIA2_ICR back at the comm module's mask",
    "Called from: the film playback path in processCommandExchange ($4DD4-$4E23), verifyFilmHeader, computeFilmHeaderChecksum and finishFilmPlayback in this file, from the film directory/load code of overlay A ($7CDB-$7D35) and from the modem driver's $EE00 tail ($EE54-$EEAB)."
   ],
   "line": "self-modify the LDX # at $5891 so X survives the call"
  },
  "5893": {
   "unit": "game/mainProgram0800",
   "routine": [
    "writeFilmByte - The write half of readFilmByte: stores A at (filmPtr),Y in the RAM under the I/O area, with the same 'sei / kill the CIA2 NMI / $01=$34 / restore' dance.  X is preserved through the self-modified LDX # at $58AF.",
    "In:  A = byte to store; Y = offset from filmPtr $BC/$BD",
    "Out: one byte written to the film buffer; A, X, Y unchanged",
    "Called from: recordExchangeToFilm and writeFilmTimestamp ($57A6-$57DC), finalizeFilm ($5804-$5859), writeFilmSetupSnapshot ($5936), initFilmStream ($59A1/$59A7) and the save-film code of overlay A ($79F9-$7AD0)."
   ],
   "line": "self-modify the LDX # at $58B0 so X survives the call"
  },
  "58B2": {
   "unit": "game/mainProgram0800",
   "routine": [
    "copyPageUnderIo - Copies one 256-byte page with the I/O area banked out, used to move the unit tables in and out of the film's start-of-game snapshot.  Source and destination are the operands of the LDA abs,Y at $58C3/$58C4 and the STA abs,Y at $58C6/$58C7; the caller patches them first.  As disassembled both instructions show $FFFB (irqVectorHi) because that is the unpatched byte pair sitting in the file.",
    "In:  source address in the operand at $58C3/$58C4, destination in the operand at $58C6/$58C7",
    "Out: 256 bytes copied; A, X, Y clobbered; CPU_PORT and CIA2_ICR restored",
    "Called from: writeFilmSetupSnapshot ($5952/$596B) here, from overlay A ($7F87, $8184) and from the modem driver's $EE00 tail ($EE73, $EE8C); the operand bytes are also patched by all three."
   ],
   "line": "$7F clears every mask bit of a CIA interrupt-control register"
  },
  "58D5": {
   "unit": "game/mainProgram0800",
   "routine": [
    "rewindFilmPtr - Sets filmPtr ($BC/$BD) to $D000, the first byte of the film / saved-game buffer in the RAM under the I/O area.",
    "In:  -",
    "Out: filmPtr = $D000; A clobbered",
    "Called from: resetGameVariables ($0C5B), verifyFilmHeader, computeFilmHeaderChecksum, writeFilmSetupSnapshot, initFilmStream, the tail of saveFilmEndAndRewind ($5871) and the modem driver's $EE00 tail ($EE99)."
   ],
   "line": "low byte of $D000"
  },
  "58DE": {
   "unit": "game/mainProgram0800",
   "routine": [
    "verifyFilmHeader - Sanity check on a film that has just been loaded from disk: recomputes the checksum over header bytes $D000-$D032 and compares it with the stored one at $D033, then checks that the format id at $D02F matches this build's gameVersionByte ($0B94).  On success it returns the film's solo/modem flag from $D004.",
    "In:  the film header in the RAM under I/O",
    "Out: C=0 and A = $D004 (the isSoloTrainer flag the film was recorded with) when the film is good; C=1 when it is not, and the caller prints 'NOT A GAME FILM!' (message $09)",
    "Called from: the LOAD GAME FILM code of overlay A ($81F4)."
   ],
   "line": "point filmPtr at the start of the header"
  },
  "5900": {
   "unit": "game/mainProgram0800",
   "routine": [
    "computeFilmHeaderChecksum - Adds up the 51 header bytes $D000-$D032 into scratch18 ($18).  The ADC inside the loop is never preceded by a CLC, so the carry out of each byte feeds into the next one; readFilmByte is careful not to disturb the carry.  Also leaves filmPtr rewound to $D000.",
    "In:  the film header in the RAM under I/O",
    "Out: scratch18 = the checksum; filmPtr = $D000; A/Y clobbered",
    "Called from: finalizeFilm ($5854) when writing a film and verifyFilmHeader ($58E1) when checking one."
   ],
   "line": "point filmPtr at $D000 so Y is a plain header offset"
  },
  "5915": {
   "unit": "game/mainProgram0800",
   "routine": [
    "waitForExchangeIdle - Spins on the per-frame command exchange until the packet exchange in flight has completed, which the exchange code signals by clearing linkTimeoutCounter ($9223); checkLinkTimeout sets bit 7 of that counter while a packet is outstanding.",
    "In:  linkTimeoutCounter $9223",
    "Out: returns when $9223 is zero; everything serviceCommandExchange touches",
    "Called from: overlay A after sending setup commands ($797C, $7C09, $7C1C)."
   ],
   "line": "one pass of the lock-step exchange plus the status-line service ($4DA9)"
  },
  "591E": {
   "unit": "game/mainProgram0800",
   "routine": [
    "compareFilmPtrToEnd - 16-bit unsigned compare of filmPtr against filmEndPtr.  While recording, filmEndPtr is the $DFF0 buffer limit, so C=1 means the film buffer is full; while playing back it is the real end of the film, so C=1 means the replay has run out of records.",
    "In:  filmPtr $BC/$BD, filmEndPtr $92F1/$92F2",
    "Out: C=1 when filmPtr >= filmEndPtr; A clobbered",
    "Called from: processCommandExchange ($4DC1) on the playback path, recordExchangeToFilm ($5793) and finalizeFilm ($57F2)."
   ],
   "line": "film pointer, low byte"
  },
  "5929": {
   "unit": "game/mainProgram0800",
   "routine": [
    "writeFilmSetupSnapshot - Writes the start-of-game snapshot into the film header so that a replay can rebuild the battlefield exactly: the 20 setup bytes $92A2-$92B5 (scores, recycler mode and position, terrain points, drones, missiles, comcen speeds) go to header offsets $34-$47, the eight 100-byte unit arrays $F640-$F95F (column, row, flags, type, destination column/row, waypoint column/row) are copied to $D048-$D367, and the game clock is stored at $D030.",
    "In:  $92A2-$92B5, the unit arrays at $F640-$F95F, gameClock $91CB",
    "Out: $D034-$D047, $D048-$D367 and $D030 filled; filmPtr left at $D000; the copyPageUnderIo operands at $58C3-$58C7 left patched",
    "Called from: startGameSession in overlay A ($7B80) when a recorded game begins."
   ],
   "line": "point filmPtr at header offset $34 ($D034)..."
  },
  "5983": {
   "unit": "game/mainProgram0800",
   "routine": [
    "getFilmSnapshotDestAddr - Returns the constant address $D268 in A (low) and X (high): the highest destination page of the unit snapshot inside the film header, and the page just below the start of the command stream at $D368.  It exists as a routine so that the modem driver's $EE00 tail can reach the same constant when it restores a snapshot.",
    "In:  -",
    "Out: A = $68, X = $D2",
    "Called from: writeFilmSetupSnapshot ($5955), initFilmStream ($59AA) and the modem driver's $EE00 tail ($EE76)."
   ],
   "line": "low byte of $D268"
  },
  "5988": {
   "unit": "game/mainProgram0800",
   "routine": [
    "getFilmSnapshotSrcAddr - Returns the constant address $F860 in A (low) and X (high): the highest source page of the unit snapshot, i.e. the last full page of the eight unit arrays that end at $F95F.  Shared with the snapshot restore code in the modem driver's $EE00 tail.",
    "In:  -",
    "Out: A = $60, X = $F8",
    "Called from: writeFilmSetupSnapshot ($595E) and the modem driver's $EE00 tail ($EE7F)."
   ],
   "line": "low byte of $F860"
  },
  "598D": {
   "unit": "game/mainProgram0800",
   "routine": [
    "setFilmLimitToBufferEnd - Sets filmEndPtr to $DFF0, the highest address a recording may reach.  While a film is being recorded compareFilmPtrToEnd tests against this value, so it is the point where recording silently stops; the last 16 bytes of the $D000 page are left unused.",
    "In:  -",
    "Out: filmEndPtr $92F1/$92F2 = $DFF0; A clobbered",
    "Called from: startGameSession in overlay A ($78D1) at the start of every game."
   ],
   "line": "low byte of $DFF0"
  },
  "5998": {
   "unit": "game/mainProgram0800",
   "routine": [
    "initFilmStream - Prepares the film for recording once the snapshot is in place: writes the offset of the first command record ($0368) into header bytes $D002/$D003 and sets filmPtr to $D368, the first byte after the 800-byte unit snapshot.",
    "In:  -",
    "Out: $D002/$D003 = $0368; filmPtr = $D368; A/X/Y clobbered",
    "Called from: startGameSession in overlay A ($7B8C), right after writeFilmSetupSnapshot."
   ],
   "line": "point filmPtr at $D000 so Y is a plain header offset"
  },
  "59B3": {
   "unit": "game/mainProgram0800",
   "routine": [
    "isCmdRingNearlyFull - Works out how much room is left in the 40-byte outgoing command ring at $9065-$908C and reports C=1 when fewer than 5 bytes are free, which is less than the longest command plus its arguments.  checkFirePressed calls it first, so while the ring is congested the fire button simply does nothing and no order can be lost.  Equal read and write indices mean the ring is empty, which is reported as plenty of room.",
    "In:  outgoingCmdWriteIndex $908D, outgoingCmdReadIndex $908E",
    "Out: C=1 = nearly full (refuse new orders), C=0 = room to spare; A clobbered",
    "Called from: checkFirePressed ($0EB1) only."
   ],
   "line": "index the transmitter has reached"
  },
  "59D0": {
   "unit": "game/mainProgram0800",
   "routine": [
    "finishFilmPlayback - End of a game film: reached when the replay runs past the last record, when the record length byte is $FF or when the aborted-game end marker is read.  It rewinds, restores the game clock and both 16-bit scores from the header (which finalizeFilm wrote when the film was made), marks the game as over, queues message 2 'END OF FILM.', locks the player out of the controls and starts the short end-of-game countdown.",
    "In:  the film header at $D000, playerSide $0B9F",
    "Out: gameClock $91CB, scores $92A2-$92A5, gameEndReason $0BA8 incremented, filmPtr back at the end, gameOverFlags $923F = $C0, persistentMessageId $92A1 and chatMessageLine $0548 = $9E, gameEndCountdown $92C9 = $7D",
    "Called from: processCommandExchange ($4DC6) - both from the end-of-buffer test at $4DC4 and from the end-marker test at $4E0F."
   ],
   "line": "remember where the film ended and rewind filmPtr to $D000"
  },
  "5A14": {
   "unit": "game/mainProgram0800",
   "routine": [
    "checkLinkTimeout - Watchdog used while the game is waiting for the opponent's packet.  It arms linkTimeoutCounter ($9223) by setting bit 7, which is what makes the raster IRQ count it up (one step every 8 frames, or every 16 while gamePhase is $FF, saturating at $FF).  Below $E1 it just returns C=0 and the caller goes back to its frame loop; from $E1 on it puts 'PHONE TROUBLE.' on the status line and returns C=1, which makes the caller busy-wait instead; at $FF the link is declared dead - during the battle it hangs up, redials and starts the wait again, in the setup phases it disconnects, says 'TRY AGAIN.' and jumps back to the main menu.",
    "In:  linkTimeoutCounter $9223, gamePhase $B1 (bits 6-7 clear = the battle is running), sndVoiceSoundId $67C8",
    "Out: C=1 = keep waiting in a tight loop, C=0 = come back next frame; $9223 bit 7 set; may set linkSessionState $0B9C = $FF, prevSoloTrainerFlag $0BA6 = $C8 and jump to returnToMainMenu",
    "Called from: waitForCarrier ($1BA3) and the wait-for-packet loop of processCommandExchange ($4E50)."
   ],
   "line": "how long the current packet has been outstanding"
  },
  "5AD7": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawOverviewMap - Draws the whole strategic overview: the 40x40 battlefield squeezed into the 20x20 character window at screen (1,1)-(20,20), four map cells (a 2x2 block) per character.  For every character it clears the 8-byte build buffer at $90B8, ORs in the four cell patterns as quadrants, overlays the quarter/midfield lines, copies the 8 scanlines into the $A000 bitmap, writes $62 into screen RAM (upper nibble 6 = blue for own units, lower nibble 2 = red for the enemy) and the terrain colour into colour RAM at +$4C00.",
    "In:  the $F000 map, the unit arrays, the patched side branches at $5BF7/$5BFE",
    "Out: 400 characters of bitmap, screen RAM and colour RAM rewritten; vicBg0Shadow $9004 = 7 (yellow); mapCellPtr $48/$49, scratch18/scratch19 and glyphBuildBuffer clobbered",
    "Called from: manualLookupCheck ($09E3), the battlefield screen builder ($2682, $4020, $4137), overlay A/B in many places ($76B8, $7702, $774C, $7B0C, $7D5C, $83F8, $8553), the $0400 block ($0758) and the text engine ($C794)."
   ],
   "line": "colour 7 = yellow: the plain-ground background of the whole overview"
  },
  "5B6C": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawOverviewQuarterLine - Puts the field markings back on top of an overview character.  Modem Wars is played on a football pitch, and the overview shows the quarter lines at map rows 10, 20 and 30, which land on the first scanline of character rows 6, 11 and 16.  On the midfield row (11) both pixel pairs of the character get colour 1, on rows 6 and 16 only the right-hand one; a half that already reads %1010 - a solid enemy-unit blob - is left alone so the line does not put a blue dot in the middle of a red unit.  Only scanline 0 of the buffer at $90B8 is touched.",
    "In:  Y = character row of the overview (1-20); glyphBuildBuffer $90B8",
    "Out: glyphBuildBuffer[0] possibly modified; A/X/Y clobbered; self-modifies the operands at $5B95 and $5BA8",
    "Called from: drawOverviewMap ($5B18) for every character and drawMapCell ($5CFD) when a single cell is redrawn."
   ],
   "line": "X = 0 means 'draw both pixel pairs'"
  },
  "5BAD": {
   "unit": "game/mainProgram0800",
   "routine": [
    "buildOverviewQuadrant - Colour-aware entry of the overview cell renderer.  It looks the terrain colour of map cell A up (terrainClassTable $07BE indexed by the terrain index, then terrainColourTable $07EA) and leaves it in tileColour ($32) for the caller to write into colour RAM, then falls into buildOverviewQuadrantNoColour to OR the pattern into quadrant X.  A cell holding a unit is coloured after the terrain hidden under it.  Cell $40 and any out-of-range value leave the colour untouched, so the character keeps the colour of the previous quadrant.",
    "In:  A = map cell byte ($21-$6B terrain, or $80|unitIndex); X = quadrant 0-3; Y = anything (restored)",
    "Out: tileColour $32 = the terrain colour nibble; the quadrant ORed into glyphBuildBuffer $90B8; A/X/Y restored",
    "Called from: drawOverviewMap ($5AFC, $5B03, $5B0B, $5B12), once per cell of the 2x2 block."
   ],
   "line": "stash Y in the LDY # at $5C7A so the exit can restore it"
  },
  "5BD4": {
   "unit": "game/mainProgram0800",
   "routine": [
    "buildOverviewQuadrantNoColour - ORs the 2x4 pixel pattern of map cell A into quadrant X of the 8-byte character buffer at $90B8, without touching the colour.  Terrain cells pick their 4-byte pattern through overviewPatternLo/Hi.  A cell holding a unit gets the solid blob $5AB7 (or $5ABF when the unit is cloaked, bit 7 of the waypoint-column array $F898) masked with overviewUnitPattern $922B, which is $55 (every pixel pair %01 = blue) for our own units and $AA (%10 = red) for the opponent's.  A unit is drawn only if it is ours, if the omniscient replay view is on, if reveal-all is set or if it has been spotted (bit 7 of the energy array $F9C4); otherwise, and for dead units and while the comcen map system is knocked out ($9242 bit 7), the terrain hidden under the unit is drawn instead.",
    "In:  A = map cell byte; X = quadrant 0-3; Y = anything (restored); overviewUnitPattern $922B, comcenBattleStatus $9242, filmPlaybackMode $0B9B, revealAllUnits $92FC, unit arrays $F76C/$F898/$F9C4/$FAF0",
    "Out: four scanlines of glyphBuildBuffer $90B8 ORed; A/X/Y restored; self-modifies the operands at $5C19, $5C65, $5C66, $5C68, $5C6A, $5C74, $5C78, $5C7A",
    "Called from: drawMapCell ($5CF6) and, past its two save instructions, from buildOverviewQuadrant ($5BD1)."
   ],
   "line": "stash Y in the LDY # at $5C7A so the exit can restore it"
  },
  "5C7C": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawMapCell - Redraws a single battlefield cell everywhere it can currently be seen.  On the battlefield screen that means the 16x16 tile in the 7x5 tactical view (only when the cell is inside it) plus one 2x4-pixel quadrant of the strategic overview, which is rebuilt in place: the character is read back out of the bitmap, the quadrant's old pixels are cleared, the new pattern is ORed in, the field markings are restored and the character is written back.  On the comcen radar screen it hands the work to drawScreen1Cell.  Nothing is drawn at all during the comcen knock-out blackout.",
    "In:  A = the map cell byte, X = map column, Y = map row (saved in savedRegA/X/Y $54-$56); currentScreen $90FB; viewOriginCol/Row $A5/$A6; comcenStunStatus $922A bit 6",
    "Out: bitmap, screen RAM and colour RAM updated; A/X/Y restored to the values passed in; self-modifies the operands at $5CD1, $5CE9, $5CEF, $5CFA and $5CFC",
    "Called from: the unit redraw helpers at $3F89 and $437D and, as a tail jump, from $48AE - i.e. whenever a unit moves, is hit or is removed."
   ],
   "line": "$922A: bit 6 is set while the comcen knock-out blackout sequence runs"
  },
  "5D1A": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawViewportCell - Draws one map cell as a 16x16 pixel tile (2x2 characters) in the 7x5 tactical view on the right of the battlefield screen.  The screen position is character column 2*(col - viewOriginCol) + 25 and row 2*(row - viewOriginRow) + 1.  renderMapTile reads the cell's own map coordinates out of viewOriginCol/Row, so those are borrowed for the duration and restored afterwards; it builds the 32-byte tile in the buffer at $04C8 and writes the four screen RAM colour bytes itself, and this routine copies the 32 bytes into the bitmap - the first 16 at the tile origin, the second 16 one character row (320 bytes) lower.",
    "In:  savedRegA/X/Y ($54/$55/$56) = cell byte, map column, map row; viewOriginCol/Row $A5/$A6",
    "Out: four character cells of the $A000 bitmap and the matching screen/colour RAM written; drawCellSaveCol/Row $90F6/$90F7 used as scratch; viewOriginCol/Row restored; self-modifies the operand at $5D58/$5D59",
    "Called from: drawMapCell ($5C9E) and the view scroller scrollViewBitmap ($1F1B, $1F44) when a new row or column is exposed."
   ],
   "line": "current left edge of the tactical view"
  },
  "5D7F": {
   "unit": "game/mainProgram0800",
   "routine": [
    "drawScreen1Cell - Redraws one map cell on screen 1, the comcen radar/scanner map, where the window is 21 cells wide by 17 tall at character (3,3) and every cell is a single 8x8 glyph.  If the cell holds our own comcen the window has to follow it, so the whole job is handed to the overlay routine at $7959; otherwise the cell is clipped against the window, the bitmap and colour RAM pointers are built from the row/column tables at $9300 with a +3 character offset, the radar palette is set (dark grey for our units and the terrain, white for the enemy) and the overlay routines at $74C3 and $7396 turn the cell byte into a glyph and plot it.  viewOriginCol/Row are borrowed as the cell's coordinates while that happens.",
    "In:  savedRegA/X/Y ($54/$55/$56) = cell byte, map column, map row; viewOriginCol/Row $A5/$A6 = top-left cell of the radar window; comcenUnit $9236",
    "Out: one glyph in the bitmap and one byte in colour RAM; radarOwnUnitColour/radarEnemyUnitColour/radarTerrainColour $9237-$9239 set; bitmapPtr/screenPtr and drawCellSaveCol/Row $90F6/$90F7 used; viewOriginCol/Row restored",
    "Called from: drawMapCell ($5D14) and, in both $6F00 overlay variants, from $794F."
   ],
   "line": "the map cell byte"
  },
  "580E": {
   "unit": "game/mainProgram0800",
   "block": [
    "Rewind and fill in the 52-byte film header at $D000.  Offsets used here: $00/$01 length,",
    "$2F format id, $31 final clock, $0D-$10 the two scores, $33 checksum.  The rest of the header",
    "($02/$03 command-stream offset, $04 solo flag, $05 side, $06-$0B map name, $0C options, $11-$24",
    "player names, $2B-$2D map seed, $30 starting clock) is written by initFilmStream,",
    "writeFilmSetupSnapshot and the save-film code in overlay A."
   ],
   "line": "filmEndPtr = end of the recording, then filmPtr back to $D000"
  },
  "583E": {
   "unit": "game/mainProgram0800",
   "block": [
    "Write both 16-bit scores, own side first: $D00F/$D010 = this machine's score,",
    "$D00D/$D00E = the opponent's.  X alternates between the two sides with EOR #$01 and Y walks",
    "down $10, $0F, $0E, $0D.  finishFilmPlayback reads them back in exactly the same order, so a",
    "film always shows the scores from the point of view of whoever is watching it."
   ],
   "line": "score high byte of side X ($92A4 + side)"
  },
  "5933": {
   "unit": "game/mainProgram0800",
   "block": [
    "Copy the 20 bytes of per-game settings $92A2-$92B5 (both 16-bit scores, recycler mode,",
    "recycler column and row per side, terrain points, drones left, missiles left, comcen reveal",
    "timers and the two comcen speed classes) into header offsets $34-$47."
   ],
   "line": "$92A2 + Y: the 20-byte block of per-game settings starting with the two 16-bit scores"
  },
  "593E": {
   "unit": "game/mainProgram0800",
   "block": [
    "Snapshot the first eight unit arrays ($F640-$F95F, 800 bytes) into $D048-$D367.  The copy is",
    "done with four 256-byte page copies whose destination is always source - $25F8; because 800 is",
    "not a multiple of 256 the four pages overlap, so only the first $20 bytes of the very first copy",
    "($F640-$F65F) actually survive - the rest is redone by the three copies that follow.  The arrays",
    "covered are column, row, flags/facing, type, destination column, destination row, waypoint column",
    "and waypoint row, i.e. everything needed to place the units again at the start of a replay."
   ],
   "line": "destination low byte $48"
  },
  "59E0": {
   "unit": "game/mainProgram0800",
   "block": [
    "Read both 16-bit scores back out of the header in the same own-side-first order",
    "finalizeFilm wrote them in, so a replay always shows US and THEM from the point of view of",
    "whoever is watching."
   ],
   "line": "score high byte from the header"
  },
  "5A59": {
   "unit": "game/mainProgram0800",
   "block": [
    "Overview-map tile patterns.  On the strategic map each battlefield cell is only 2 multicolour",
    "pixels wide and 4 scanlines tall, so a whole cell is described by four bytes - one per scanline -",
    "each of which carries both horizontal halves of the character (bits 7-4 for the left cell of the",
    "pair, bits 3-0 for the right); the quadrant mask in quadrantPixelMask picks the half that is",
    "wanted.  A pixel pair of %00 is background (VIC background 0 = 7 yellow), %01 is the screen RAM",
    "upper nibble (6 blue = own units), %10 the lower nibble (2 red = enemy units) and %11 the colour",
    "RAM nibble (the terrain colour).  overviewPatternLo/Hi are a split pointer table indexed by the",
    "terrain index (map cell - $40, or cell - $20 for the $21-$3F contour set), 33 entries covering",
    "indices 0-$20; buildOverviewQuadrantNoColour reads them at $5C40/$5C44."
   ],
   "line": "terrain 0 = blank ($5A9B), 1-4 = the four lower-contour edges ($5A9F/A3/A7/AB)"
  },
  "5A7A": {
   "unit": "game/mainProgram0800",
   "block": [
    "High bytes of the same 33 pointers - every pattern lives in this page, so all entries are $5A."
   ],
   "line": "all patterns are in page $5A (entries 0-7)"
  },
  "5A9B": {
   "unit": "game/mainProgram0800",
   "block": [
    "The 15 distinct 4-byte patterns, one row per pattern.  Two of them are not reached through the",
    "pointer table at all: $5AB7 (solid) is the unit blob loaded at $5C14 and $5ABF (every other",
    "scanline) is the cloaked-unit blob loaded at $5C1C.  In the bit art below $33 lights the right",
    "pixel of a half, $CC the left one and $FF both."
   ],
   "line": "$5A9B blank - terrain index 0 (map cell $40), all background"
  },
  "5AE8": {
   "unit": "game/mainProgram0800",
   "block": [
    "Outer loop over the 20 character rows.  Each pass covers two map rows; after the inner loop",
    "has walked 20 characters (40 map cells) the pointer is advanced by another 40 to skip the odd",
    "map row that the same characters already covered."
   ],
   "line": "first character column of the window"
  },
  "5AEC": {
   "unit": "game/mainProgram0800",
   "block": [
    "Inner loop: build one overview character out of a 2x2 block of map cells."
   ],
   "line": "8 scanlines to clear"
  },
  "5B64": {
   "unit": "game/mainProgram0800",
   "block": [
    "Quadrant geometry of an overview character.  Quadrant 0 = top-left, 1 = top-right,",
    "2 = bottom-left, 3 = bottom-right; each is 2 multicolour pixels wide and 4 scanlines tall."
   ],
   "line": "AND mask per quadrant: $F0 keeps the left pixel pair of the character, $0F the right one"
  },
  "5C2B": {
   "unit": "game/mainProgram0800",
   "block": [
    "Shared terrain path: turn the cell byte into a terrain index 0-$20 and fetch the matching",
    "4-byte pattern out of the split pointer table at $5A59/$5A7A."
   ],
   "line": "prepare a true subtraction of the terrain base"
  },
  "5C49": {
   "unit": "game/mainProgram0800",
   "block": [
    "Plot the chosen 4-byte pattern into the quadrant: each of the four scanlines is masked with",
    "the unit colour mask, then with the half-of-the-character mask, and ORed into the build buffer."
   ],
   "line": "self-modify the LDA abs,Y at $5C64 with the pattern address, low byte"
  },
  "5CA1": {
   "unit": "game/mainProgram0800",
   "block": [
    "Overview update: find the character that holds this cell, read it back out of the bitmap,",
    "erase just the quadrant belonging to the cell, OR the new pattern in, restore the field marking",
    "and write the character back.  The colour RAM nibble is deliberately left alone - a single cell",
    "cannot change the colour of the 2x2 block it shares."
   ],
   "line": "$C030: A/X/Y = cell byte, column, row again"
  },
  "57C1": {
   "unit": "game/mainProgram0800",
   "line": "clear carry so the SBC below subtracts one extra: delta = clock - previousClock - 1"
  },
  "57C2": {
   "unit": "game/mainProgram0800",
   "line": "operand L_57C2+1 ($57C3) = clock stored with the previous record, reset to 0 by startGameSession ($7911)"
  },
  "57C4": {
   "unit": "game/mainProgram0800",
   "line": "delta $F1..$FF means the clock fell by 0..14 ticks, which fits into one nibble"
  },
  "57C6": {
   "unit": "game/mainProgram0800",
   "line": "branch when the clock moved by more than 14 ticks (or not backwards at all): the long two-byte form is needed"
  },
  "57C8": {
   "unit": "game/mainProgram0800",
   "line": "shift the delta nibble up into bits 4-7 of the time-stamp byte (1 of 4)"
  },
  "57C9": {
   "unit": "game/mainProgram0800",
   "line": "shift the delta nibble up (2 of 4)"
  },
  "57CA": {
   "unit": "game/mainProgram0800",
   "line": "shift the delta nibble up (3 of 4)"
  },
  "57CB": {
   "unit": "game/mainProgram0800",
   "line": "shift the delta nibble up (4 of 4); the low nibble is now free"
  },
  "57CC": {
   "unit": "game/mainProgram0800",
   "line": "low nibble = command slice number (unitOrderIndex/10, 0-9); playback compares it at $4DE3 and rejects the record if it does not match"
  },
  "57CF": {
   "unit": "game/mainProgram0800",
   "line": "one byte is enough - go and store it"
  },
  "57D2": {
   "unit": "game/mainProgram0800",
   "line": "long form: a first byte whose high nibble is 0 tells the reader that an absolute clock byte follows"
  },
  "57D5": {
   "unit": "game/mainProgram0800",
   "line": "store it at (filmPtr),Y in the RAM hidden under the I/O area"
  },
  "57D8": {
   "unit": "game/mainProgram0800",
   "line": "step to the next byte of the record"
  },
  "57D9": {
   "unit": "game/mainProgram0800",
   "line": "second byte of the long form = the absolute game clock"
  },
  "57DC": {
   "unit": "game/mainProgram0800",
   "line": "store the time-stamp byte"
  },
  "57DF": {
   "unit": "game/mainProgram0800",
   "line": "the clock this record was stamped with..."
  },
  "57E2": {
   "unit": "game/mainProgram0800",
   "line": "...becomes the base of the next record's delta (self-modifies the SBC operand at $57C3)"
  },
  "57E5": {
   "unit": "game/mainProgram0800",
   "line": "count the time-stamp byte that was just written"
  },
  "57E6": {
   "unit": "game/mainProgram0800",
   "line": "back to the caller with Y past the time stamp"
  },
  "57E8": {
   "unit": "game/mainProgram0800",
   "line": "plain 16-bit add, no carry in"
  },
  "57E9": {
   "unit": "game/mainProgram0800",
   "line": "add to the low byte of the film pointer"
  },
  "57EB": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "57ED": {
   "unit": "game/mainProgram0800",
   "line": "skip the high byte unless the add wrapped past $FF"
  },
  "57EF": {
   "unit": "game/mainProgram0800",
   "line": "carry into the high byte of the film pointer"
  },
  "57F1": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "57F5": {
   "unit": "game/mainProgram0800",
   "line": "buffer full - there is no room for a marker, just write the header"
  },
  "57F7": {
   "unit": "game/mainProgram0800",
   "line": "$FE = 'the game reached its own end'; playback at $4E0B simply stops on it"
  },
  "57F9": {
   "unit": "game/mainProgram0800",
   "line": "why the game ended: 0 running, 1 aborted, $08 time up, $A0/$C0 a comcen was knocked out"
  },
  "57FC": {
   "unit": "game/mainProgram0800",
   "line": "bit 0 is set only for reason 1 = the game was aborted"
  },
  "57FE": {
   "unit": "game/mainProgram0800",
   "line": "played out to the end - keep the $FE marker"
  },
  "5800": {
   "unit": "game/mainProgram0800",
   "line": "$FF = aborted game; playback at $4E0F turns this into finishFilmPlayback"
  },
  "5801": {
   "unit": "game/mainProgram0800",
   "line": "the marker byte"
  },
  "5802": {
   "unit": "game/mainProgram0800",
   "line": "offset 0 of the record = where a normal record keeps its length byte"
  },
  "5804": {
   "unit": "game/mainProgram0800",
   "line": "write the marker"
  },
  "5807": {
   "unit": "game/mainProgram0800",
   "line": "offset 1"
  },
  "5808": {
   "unit": "game/mainProgram0800",
   "line": "the marker carries a time stamp like any other record"
  },
  "580B": {
   "unit": "game/mainProgram0800",
   "line": "filmPtr += 2 or 3 bytes of marker record"
  },
  "5811": {
   "unit": "game/mainProgram0800",
   "line": "header offset 0"
  },
  "5813": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the end address..."
  },
  "5816": {
   "unit": "game/mainProgram0800",
   "line": "...minus the start of the buffer"
  },
  "5817": {
   "unit": "game/mainProgram0800",
   "line": "$D000 low byte: length = filmEndPtr - $D000"
  },
  "5819": {
   "unit": "game/mainProgram0800",
   "line": "park the low byte of the length in X"
  },
  "581A": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the end address..."
  },
  "581D": {
   "unit": "game/mainProgram0800",
   "line": "...minus $D0 = high byte of the length"
  },
  "581F": {
   "unit": "game/mainProgram0800",
   "line": "keep the high byte on the stack, writeFilmByte needs A"
  },
  "5820": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the length back into A"
  },
  "5821": {
   "unit": "game/mainProgram0800",
   "line": "$D000 = film length, low byte"
  },
  "5824": {
   "unit": "game/mainProgram0800",
   "line": "header offset 1"
  },
  "5825": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the length"
  },
  "5826": {
   "unit": "game/mainProgram0800",
   "line": "$D001 = film length, high byte"
  },
  "5829": {
   "unit": "game/mainProgram0800",
   "line": "$0B94 = the game/film format id also used in the link version handshake"
  },
  "582C": {
   "unit": "game/mainProgram0800",
   "line": "header offset $2F"
  },
  "582E": {
   "unit": "game/mainProgram0800",
   "line": "$D02F = format id; verifyFilmHeader refuses a film whose id differs from this build's"
  },
  "5831": {
   "unit": "game/mainProgram0800",
   "line": "offset $30 already holds the clock at kick-off (writeFilmSetupSnapshot wrote it)"
  },
  "5832": {
   "unit": "game/mainProgram0800",
   "line": "step over it to offset $31"
  },
  "5833": {
   "unit": "game/mainProgram0800",
   "line": "clock left on the board when the game ended"
  },
  "5836": {
   "unit": "game/mainProgram0800",
   "line": "$D031 = final clock; finishFilmPlayback reads it back"
  },
  "5839": {
   "unit": "game/mainProgram0800",
   "line": "start with the local player's score (playerSide 0 or 1)"
  },
  "583C": {
   "unit": "game/mainProgram0800",
   "line": "header offset $10 = high byte of the recorder's own score"
  },
  "5841": {
   "unit": "game/mainProgram0800",
   "line": "store it"
  },
  "5844": {
   "unit": "game/mainProgram0800",
   "line": "next header offset down"
  },
  "5845": {
   "unit": "game/mainProgram0800",
   "line": "score low byte of side X ($92A2 + side)"
  },
  "5848": {
   "unit": "game/mainProgram0800",
   "line": "store it"
  },
  "584B": {
   "unit": "game/mainProgram0800",
   "line": "next header offset down"
  },
  "584C": {
   "unit": "game/mainProgram0800",
   "line": "side index"
  },
  "584D": {
   "unit": "game/mainProgram0800",
   "line": "flip to the other side"
  },
  "584F": {
   "unit": "game/mainProgram0800",
   "line": "back into X"
  },
  "5850": {
   "unit": "game/mainProgram0800",
   "line": "stop once both score pairs ($D010..$D00D) are written"
  },
  "5852": {
   "unit": "game/mainProgram0800",
   "line": "second pass for the opponent's score"
  },
  "5854": {
   "unit": "game/mainProgram0800",
   "line": "sum $D000-$D032 into scratch18 (rewinds filmPtr as a side effect)"
  },
  "5857": {
   "unit": "game/mainProgram0800",
   "line": "header offset $33"
  },
  "5859": {
   "unit": "game/mainProgram0800",
   "line": "$D033 = header checksum; falls straight into setFilmPtrToEnd"
  },
  "585F": {
   "unit": "game/mainProgram0800",
   "line": "into the film pointer"
  },
  "5861": {
   "unit": "game/mainProgram0800",
   "line": "end of the recording, high byte"
  },
  "5864": {
   "unit": "game/mainProgram0800",
   "line": "into the film pointer"
  },
  "5866": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "5869": {
   "unit": "game/mainProgram0800",
   "line": "becomes the end-of-film limit"
  },
  "586C": {
   "unit": "game/mainProgram0800",
   "line": "current film position, high byte"
  },
  "586E": {
   "unit": "game/mainProgram0800",
   "line": "becomes the end-of-film limit"
  },
  "5871": {
   "unit": "game/mainProgram0800",
   "line": "and rewind to $D000 so the header can be read or written"
  },
  "5877": {
   "unit": "game/mainProgram0800",
   "line": "$7F clears every mask bit when written to a CIA interrupt-control register"
  },
  "5879": {
   "unit": "game/mainProgram0800",
   "line": "no interrupts while the I/O area is banked away"
  },
  "587A": {
   "unit": "game/mainProgram0800",
   "line": "disable all CIA2 interrupt sources, i.e. the modem UART's NMI"
  },
  "587D": {
   "unit": "game/mainProgram0800",
   "line": "3-cycle gap: the mask is written twice as a guard against an NMI arriving mid-store"
  },
  "587E": {
   "unit": "game/mainProgram0800",
   "line": "second write of the mask"
  },
  "5881": {
   "unit": "game/mainProgram0800",
   "line": "$34 = RAM everywhere except that the KERNAL/BASIC ROMs stay out; the $D000 I/O block disappears"
  },
  "5883": {
   "unit": "game/mainProgram0800",
   "line": "switch the film buffer into view"
  },
  "5885": {
   "unit": "game/mainProgram0800",
   "line": "read the film byte"
  },
  "5887": {
   "unit": "game/mainProgram0800",
   "line": "$34 -> $35 with one instruction: I/O visible again"
  },
  "5889": {
   "unit": "game/mainProgram0800",
   "line": "the comm module's copy of the CIA2 interrupt mask it wants installed"
  },
  "588C": {
   "unit": "game/mainProgram0800",
   "line": "re-enable the modem NMI sources"
  },
  "588F": {
   "unit": "game/mainProgram0800",
   "line": "interrupts back on"
  },
  "5890": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5874: restores the caller's X"
  },
  "5892": {
   "unit": "game/mainProgram0800",
   "line": "A holds the film byte"
  },
  "5896": {
   "unit": "game/mainProgram0800",
   "line": "$7F clears every mask bit of a CIA interrupt-control register"
  },
  "5898": {
   "unit": "game/mainProgram0800",
   "line": "no interrupts while the I/O area is banked away"
  },
  "5899": {
   "unit": "game/mainProgram0800",
   "line": "disable all CIA2 interrupt sources (the modem NMI)"
  },
  "589C": {
   "unit": "game/mainProgram0800",
   "line": "3-cycle gap between the two mask writes"
  },
  "589D": {
   "unit": "game/mainProgram0800",
   "line": "second write of the mask"
  },
  "58A0": {
   "unit": "game/mainProgram0800",
   "line": "$34 = the $D000 I/O block replaced by the RAM underneath"
  },
  "58A2": {
   "unit": "game/mainProgram0800",
   "line": "switch the film buffer into view"
  },
  "58A4": {
   "unit": "game/mainProgram0800",
   "line": "store the film byte"
  },
  "58A6": {
   "unit": "game/mainProgram0800",
   "line": "$34 -> $35: I/O visible again"
  },
  "58A8": {
   "unit": "game/mainProgram0800",
   "line": "the comm module's wanted CIA2 interrupt mask"
  },
  "58AB": {
   "unit": "game/mainProgram0800",
   "line": "re-enable the modem NMI sources"
  },
  "58AE": {
   "unit": "game/mainProgram0800",
   "line": "interrupts back on"
  },
  "58AF": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5893: restores the caller's X"
  },
  "58B1": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "58B4": {
   "unit": "game/mainProgram0800",
   "line": "no interrupts while the I/O area is banked away"
  },
  "58B5": {
   "unit": "game/mainProgram0800",
   "line": "disable all CIA2 interrupt sources (the modem NMI)"
  },
  "58B8": {
   "unit": "game/mainProgram0800",
   "line": "3-cycle gap between the two mask writes"
  },
  "58B9": {
   "unit": "game/mainProgram0800",
   "line": "second write of the mask"
  },
  "58BC": {
   "unit": "game/mainProgram0800",
   "line": "$34 = the $D000 I/O block replaced by the RAM underneath"
  },
  "58BE": {
   "unit": "game/mainProgram0800",
   "line": "bank the film buffer in"
  },
  "58C0": {
   "unit": "game/mainProgram0800",
   "line": "copy 256 bytes, index 0 upwards"
  },
  "58C2": {
   "unit": "game/mainProgram0800",
   "line": "source operand at $58C3/$58C4, patched by the caller (shown as $FFFB = the unpatched bytes)"
  },
  "58C5": {
   "unit": "game/mainProgram0800",
   "line": "destination operand at $58C6/$58C7, patched by the caller (shown as $FFFB = the unpatched bytes)"
  },
  "58C8": {
   "unit": "game/mainProgram0800",
   "line": "next byte"
  },
  "58C9": {
   "unit": "game/mainProgram0800",
   "line": "loop until Y wraps back to 0, i.e. after all 256 bytes"
  },
  "58CB": {
   "unit": "game/mainProgram0800",
   "line": "$34 -> $35: I/O visible again"
  },
  "58CD": {
   "unit": "game/mainProgram0800",
   "line": "the comm module's wanted CIA2 interrupt mask"
  },
  "58D0": {
   "unit": "game/mainProgram0800",
   "line": "re-enable the modem NMI sources"
  },
  "58D3": {
   "unit": "game/mainProgram0800",
   "line": "interrupts back on"
  },
  "58D4": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "58D7": {
   "unit": "game/mainProgram0800",
   "line": "into the film pointer"
  },
  "58D9": {
   "unit": "game/mainProgram0800",
   "line": "high byte of $D000 - the film buffer starts right under the VIC registers"
  },
  "58DB": {
   "unit": "game/mainProgram0800",
   "line": "into the film pointer"
  },
  "58DD": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "58E1": {
   "unit": "game/mainProgram0800",
   "line": "sum $D000-$D032 into scratch18"
  },
  "58E4": {
   "unit": "game/mainProgram0800",
   "line": "header offset $33 = the stored checksum"
  },
  "58E6": {
   "unit": "game/mainProgram0800",
   "line": "read it"
  },
  "58E9": {
   "unit": "game/mainProgram0800",
   "line": "compare with the checksum just computed"
  },
  "58EB": {
   "unit": "game/mainProgram0800",
   "line": "mismatch: this is not a valid film"
  },
  "58ED": {
   "unit": "game/mainProgram0800",
   "line": "header offset $2F = the format id the film was written with"
  },
  "58EF": {
   "unit": "game/mainProgram0800",
   "line": "read it"
  },
  "58F2": {
   "unit": "game/mainProgram0800",
   "line": "must equal this build's gameVersionByte $0B94"
  },
  "58F5": {
   "unit": "game/mainProgram0800",
   "line": "same version - accept the film"
  },
  "58F7": {
   "unit": "game/mainProgram0800",
   "line": "C=1 tells the caller to reject the file"
  },
  "58F8": {
   "unit": "game/mainProgram0800",
   "line": "reject"
  },
  "58F9": {
   "unit": "game/mainProgram0800",
   "line": "header offset $04 = the solo-trainer flag stored with the film"
  },
  "58FB": {
   "unit": "game/mainProgram0800",
   "line": "read it into A for the caller"
  },
  "58FE": {
   "unit": "game/mainProgram0800",
   "line": "C=0 = the film is valid"
  },
  "58FF": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "5903": {
   "unit": "game/mainProgram0800",
   "line": "start the running sum at zero"
  },
  "5905": {
   "unit": "game/mainProgram0800",
   "line": "scratch18 accumulates the checksum"
  },
  "5907": {
   "unit": "game/mainProgram0800",
   "line": "walk header offsets $32 down to $00 - 51 bytes, everything except the checksum byte itself"
  },
  "5909": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry once; from here it chains from byte to byte"
  },
  "590A": {
   "unit": "game/mainProgram0800",
   "line": "fetch header byte Y (readFilmByte leaves the carry alone)"
  },
  "590D": {
   "unit": "game/mainProgram0800",
   "line": "add it to the running sum, carrying in from the previous add"
  },
  "590F": {
   "unit": "game/mainProgram0800",
   "line": "keep the sum"
  },
  "5911": {
   "unit": "game/mainProgram0800",
   "line": "next header offset down"
  },
  "5912": {
   "unit": "game/mainProgram0800",
   "line": "loop until offset 0 has been added and Y goes negative"
  },
  "5914": {
   "unit": "game/mainProgram0800",
   "line": "scratch18 now holds the checksum"
  },
  "5918": {
   "unit": "game/mainProgram0800",
   "line": "non-zero while a packet exchange is still outstanding (bit 7 is set by checkLinkTimeout)"
  },
  "591B": {
   "unit": "game/mainProgram0800",
   "line": "keep servicing the link until the exchange settles"
  },
  "591D": {
   "unit": "game/mainProgram0800",
   "line": "exchange complete"
  },
  "5920": {
   "unit": "game/mainProgram0800",
   "line": "compare with the limit, low byte (sets the borrow for the SBC below)"
  },
  "5923": {
   "unit": "game/mainProgram0800",
   "line": "film pointer, high byte"
  },
  "5925": {
   "unit": "game/mainProgram0800",
   "line": "subtract the limit's high byte: C=1 when filmPtr >= filmEndPtr"
  },
  "5928": {
   "unit": "game/mainProgram0800",
   "line": "only the carry matters to the caller"
  },
  "592B": {
   "unit": "game/mainProgram0800",
   "line": "...low byte"
  },
  "592D": {
   "unit": "game/mainProgram0800",
   "line": "...high byte"
  },
  "592F": {
   "unit": "game/mainProgram0800",
   "line": "filmPtr = $D034, so Y counts from 0 through the setup block"
  },
  "5931": {
   "unit": "game/mainProgram0800",
   "line": "start at the first setup byte"
  },
  "5936": {
   "unit": "game/mainProgram0800",
   "line": "store it in the header"
  },
  "5939": {
   "unit": "game/mainProgram0800",
   "line": "next setting"
  },
  "593A": {
   "unit": "game/mainProgram0800",
   "line": "20 bytes, $92A2 through $92B5"
  },
  "593C": {
   "unit": "game/mainProgram0800",
   "line": "loop over the whole settings block"
  },
  "5940": {
   "unit": "game/mainProgram0800",
   "line": "into the STA operand of copyPageUnderIo ($58C6)"
  },
  "5943": {
   "unit": "game/mainProgram0800",
   "line": "destination high byte $D0"
  },
  "5945": {
   "unit": "game/mainProgram0800",
   "line": "-> destination $D048"
  },
  "5948": {
   "unit": "game/mainProgram0800",
   "line": "source low byte $40"
  },
  "594A": {
   "unit": "game/mainProgram0800",
   "line": "into the LDA operand of copyPageUnderIo ($58C3)"
  },
  "594D": {
   "unit": "game/mainProgram0800",
   "line": "source high byte $F6"
  },
  "594F": {
   "unit": "game/mainProgram0800",
   "line": "-> source $F640, the unit column array"
  },
  "5952": {
   "unit": "game/mainProgram0800",
   "line": "copy $F640-$F73F to $D048-$D147 (only the first $20 bytes of this survive - see the block note)"
  },
  "5955": {
   "unit": "game/mainProgram0800",
   "line": "A=$68, X=$D2: the top destination page $D268"
  },
  "5958": {
   "unit": "game/mainProgram0800",
   "line": "destination low byte"
  },
  "595B": {
   "unit": "game/mainProgram0800",
   "line": "destination high byte"
  },
  "595E": {
   "unit": "game/mainProgram0800",
   "line": "A=$60, X=$F8: the matching top source page $F860"
  },
  "5961": {
   "unit": "game/mainProgram0800",
   "line": "source low byte"
  },
  "5964": {
   "unit": "game/mainProgram0800",
   "line": "source high byte"
  },
  "5967": {
   "unit": "game/mainProgram0800",
   "line": "three more page copies to do (counter runs 2, 1, 0)"
  },
  "5969": {
   "unit": "game/mainProgram0800",
   "line": "scratch19 is the page counter"
  },
  "596B": {
   "unit": "game/mainProgram0800",
   "line": "copy one page; first $F860->$D268, then $F760->$D168, then $F660->$D068"
  },
  "596E": {
   "unit": "game/mainProgram0800",
   "line": "step the source down one page"
  },
  "5971": {
   "unit": "game/mainProgram0800",
   "line": "step the destination down one page"
  },
  "5974": {
   "unit": "game/mainProgram0800",
   "line": "one page done"
  },
  "5976": {
   "unit": "game/mainProgram0800",
   "line": "loop while the counter is still non-negative - three iterations"
  },
  "5978": {
   "unit": "game/mainProgram0800",
   "line": "back to $D000 so Y is a plain header offset again"
  },
  "597B": {
   "unit": "game/mainProgram0800",
   "line": "header offset $30"
  },
  "597D": {
   "unit": "game/mainProgram0800",
   "line": "clock at kick-off; finalizeFilm later stores the final clock at offset $31"
  },
  "5980": {
   "unit": "game/mainProgram0800",
   "line": "$D030 = starting clock, and return through writeFilmByte"
  },
  "5985": {
   "unit": "game/mainProgram0800",
   "line": "high byte of $D268 - the last page of the unit snapshot in the film header"
  },
  "5987": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "598A": {
   "unit": "game/mainProgram0800",
   "line": "high byte of $F860 - the top source page of the unit-array snapshot"
  },
  "598C": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "598F": {
   "unit": "game/mainProgram0800",
   "line": "into the recording limit"
  },
  "5992": {
   "unit": "game/mainProgram0800",
   "line": "high byte of $DFF0 - the last 16 bytes of the buffer are kept free"
  },
  "5994": {
   "unit": "game/mainProgram0800",
   "line": "into the recording limit"
  },
  "5997": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "599B": {
   "unit": "game/mainProgram0800",
   "line": "low byte of the command-stream offset $0368"
  },
  "599D": {
   "unit": "game/mainProgram0800",
   "line": "X builds the high byte of the offset and then of the address"
  },
  "599F": {
   "unit": "game/mainProgram0800",
   "line": "header offset $02"
  },
  "59A1": {
   "unit": "game/mainProgram0800",
   "line": "$D002 = $68"
  },
  "59A4": {
   "unit": "game/mainProgram0800",
   "line": "header offset $03"
  },
  "59A5": {
   "unit": "game/mainProgram0800",
   "line": "X = 3"
  },
  "59A6": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the offset $0368"
  },
  "59A7": {
   "unit": "game/mainProgram0800",
   "line": "$D003 = $03; the film records therefore start $0368 bytes into the file"
  },
  "59AA": {
   "unit": "game/mainProgram0800",
   "line": "A=$68, X=$D2 - the last snapshot page"
  },
  "59AD": {
   "unit": "game/mainProgram0800",
   "line": "filmPtr low byte = $68"
  },
  "59AF": {
   "unit": "game/mainProgram0800",
   "line": "one page past the snapshot: $D2 -> $D3"
  },
  "59B0": {
   "unit": "game/mainProgram0800",
   "line": "filmPtr = $D368, the first free byte for command records"
  },
  "59B2": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "59B6": {
   "unit": "game/mainProgram0800",
   "line": "prepare a true subtraction"
  },
  "59B7": {
   "unit": "game/mainProgram0800",
   "line": "free space = readIndex - writeIndex when the reader is ahead"
  },
  "59BA": {
   "unit": "game/mainProgram0800",
   "line": "both indices equal: the ring is empty, so it is certainly not full"
  },
  "59BC": {
   "unit": "game/mainProgram0800",
   "line": "reader ahead of the writer: the difference is already the free space"
  },
  "59BE": {
   "unit": "game/mainProgram0800",
   "line": "writer has wrapped past the reader..."
  },
  "59C2": {
   "unit": "game/mainProgram0800",
   "line": "...so add the ring size of 40 bytes ($9065-$908C)..."
  },
  "59C5": {
   "unit": "game/mainProgram0800",
   "line": "...before taking the difference again"
  },
  "59C8": {
   "unit": "game/mainProgram0800",
   "line": "5 bytes is the safety margin - the longest command is 4 bytes"
  },
  "59CA": {
   "unit": "game/mainProgram0800",
   "line": "less than 5 bytes free: report 'nearly full'"
  },
  "59CC": {
   "unit": "game/mainProgram0800",
   "line": "C=0: there is room for another order"
  },
  "59CD": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "59CE": {
   "unit": "game/mainProgram0800",
   "line": "C=1: the caller must refuse the order"
  },
  "59CF": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "59D3": {
   "unit": "game/mainProgram0800",
   "line": "header offset $31 = the clock the recorded game ended on"
  },
  "59D5": {
   "unit": "game/mainProgram0800",
   "line": "read it"
  },
  "59D8": {
   "unit": "game/mainProgram0800",
   "line": "show the same time left as the original game did"
  },
  "59DB": {
   "unit": "game/mainProgram0800",
   "line": "the viewer's own side (0 or 1)"
  },
  "59DE": {
   "unit": "game/mainProgram0800",
   "line": "header offset $10 = high byte of the recorder's score, which becomes the viewer's"
  },
  "59E3": {
   "unit": "game/mainProgram0800",
   "line": "$92A4 + side"
  },
  "59E6": {
   "unit": "game/mainProgram0800",
   "line": "next header offset down"
  },
  "59E7": {
   "unit": "game/mainProgram0800",
   "line": "score low byte from the header"
  },
  "59EA": {
   "unit": "game/mainProgram0800",
   "line": "$92A2 + side"
  },
  "59ED": {
   "unit": "game/mainProgram0800",
   "line": "next header offset down"
  },
  "59EE": {
   "unit": "game/mainProgram0800",
   "line": "side index"
  },
  "59EF": {
   "unit": "game/mainProgram0800",
   "line": "flip to the other side"
  },
  "59F1": {
   "unit": "game/mainProgram0800",
   "line": "back into X"
  },
  "59F2": {
   "unit": "game/mainProgram0800",
   "line": "stop once both score pairs ($D010..$D00D) have been read"
  },
  "59F4": {
   "unit": "game/mainProgram0800",
   "line": "second pass for the other side"
  },
  "59F6": {
   "unit": "game/mainProgram0800",
   "line": "0 means 'a game is running'; anything else ends it - here it just has to become non-zero"
  },
  "59F9": {
   "unit": "game/mainProgram0800",
   "line": "put filmPtr back at the end so compareFilmPtrToEnd keeps saying 'finished'"
  },
  "59FC": {
   "unit": "game/mainProgram0800",
   "line": "message 2 = 'END OF FILM.' ($8707, sound 3)"
  },
  "59FE": {
   "unit": "game/mainProgram0800",
   "line": "queue it for the status line"
  },
  "5A01": {
   "unit": "game/mainProgram0800",
   "line": "bits 7 and 6: game over, and bit 6 blocks the fire button in checkFirePressedInteractive"
  },
  "5A03": {
   "unit": "game/mainProgram0800",
   "line": "$923F = $C0"
  },
  "5A06": {
   "unit": "game/mainProgram0800",
   "line": "$9E is a bit-7 terminator that prints nothing"
  },
  "5A08": {
   "unit": "game/mainProgram0800",
   "line": "a negative persistentMessageId makes the status bar fall back to the CLICKS/US/THEM score line"
  },
  "5A0B": {
   "unit": "game/mainProgram0800",
   "line": "and blank the chat line at $0548"
  },
  "5A0E": {
   "unit": "game/mainProgram0800",
   "line": "$7D: the IRQ counts this up towards $80 every 8 frames, so about 24 frames of grace"
  },
  "5A10": {
   "unit": "game/mainProgram0800",
   "line": "non-zero gameEndCountdown also stops runGameFrames and the cursor handling"
  },
  "5A13": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "5A17": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 arms the counter: updateFrameTimers ($12B4) only advances it while it is negative"
  },
  "5A19": {
   "unit": "game/mainProgram0800",
   "line": "write it back"
  },
  "5A1C": {
   "unit": "game/mainProgram0800",
   "line": "$E1 = 97 steps past the armed value $80; at one step per 8 frames that is roughly 13 seconds of silence"
  },
  "5A1E": {
   "unit": "game/mainProgram0800",
   "line": "still early days - let the caller carry on with its normal frame loop"
  },
  "5A20": {
   "unit": "game/mainProgram0800",
   "line": "$FF = the counter has saturated: the link is gone"
  },
  "5A22": {
   "unit": "game/mainProgram0800",
   "line": "between $E1 and $FE: only warn"
  },
  "5A24": {
   "unit": "game/mainProgram0800",
   "line": "0 while the battle runs; bits 6-7 mark the side-selection, setup, film and menu phases"
  },
  "5A26": {
   "unit": "game/mainProgram0800",
   "line": "isolate those two phase bits"
  },
  "5A28": {
   "unit": "game/mainProgram0800",
   "line": "the battle is running: try to get the call back rather than throwing the game away"
  },
  "5A2A": {
   "unit": "game/mainProgram0800",
   "line": "$FF = link lost"
  },
  "5A2C": {
   "unit": "game/mainProgram0800",
   "line": "record it in the session state"
  },
  "5A2F": {
   "unit": "game/mainProgram0800",
   "line": "send the disconnect command and hang the modem up"
  },
  "5A32": {
   "unit": "game/mainProgram0800",
   "line": "message $23 = 'TRY AGAIN.' ($0608, sound 3)"
  },
  "5A34": {
   "unit": "game/mainProgram0800",
   "line": "show it on the status line"
  },
  "5A37": {
   "unit": "game/mainProgram0800",
   "line": "$C8 in prevSoloTrainerFlag forces the GET OPPONENT ON PHONE prompt next time round"
  },
  "5A39": {
   "unit": "game/mainProgram0800",
   "line": "store it"
  },
  "5A3C": {
   "unit": "game/mainProgram0800",
   "line": "A is still $C8, so this waits 200 frames before leaving the message"
  },
  "5A3F": {
   "unit": "game/mainProgram0800",
   "line": "abandon the game and rebuild the main menu"
  },
  "5A42": {
   "unit": "game/mainProgram0800",
   "line": "battle case: hang up, ask for a voice pause and dial again"
  },
  "5A45": {
   "unit": "game/mainProgram0800",
   "line": "restart the watchdog from zero..."
  },
  "5A47": {
   "unit": "game/mainProgram0800",
   "line": "...always taken: re-enter at $5A17, which ORs in bit 7 and returns C=0"
  },
  "5A49": {
   "unit": "game/mainProgram0800",
   "line": "sound $15 is the phone-trouble warble"
  },
  "5A4B": {
   "unit": "game/mainProgram0800",
   "line": "voice 0 of the sound driver"
  },
  "5A4E": {
   "unit": "game/mainProgram0800",
   "line": "already complaining - do not restart the message"
  },
  "5A50": {
   "unit": "game/mainProgram0800",
   "line": "message $22 = 'PHONE TROUBLE.' ($05FA, sound $15)"
  },
  "5A52": {
   "unit": "game/mainProgram0800",
   "line": "show it on the status line"
  },
  "5A55": {
   "unit": "game/mainProgram0800",
   "line": "C=1: the caller keeps spinning here instead of running game frames"
  },
  "5A56": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "5A57": {
   "unit": "game/mainProgram0800",
   "line": "C=0: nothing wrong yet, carry on normally"
  },
  "5A58": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "5A61": {
   "unit": "game/mainProgram0800",
   "line": "terrain 5-8 repeat the same four contour edges, 9-12 all use the full-height stripe $5AAF"
  },
  "5A69": {
   "unit": "game/mainProgram0800",
   "line": "terrain 13-20 all use $5AB3 (a single solid scanline), 21-23 start the river set ($5AC7/CB/CF)"
  },
  "5A71": {
   "unit": "game/mainProgram0800",
   "line": "terrain 24 = $5AD3 (river), 25-28 = $5AC3 (river bank), 29-31 = $5ABB (forest stipple)"
  },
  "5A79": {
   "unit": "game/mainProgram0800",
   "line": "terrain $20 = $5ABB, the last forest entry"
  },
  "5A82": {
   "unit": "game/mainProgram0800",
   "line": "entries 8-15"
  },
  "5A8A": {
   "unit": "game/mainProgram0800",
   "line": "entries 16-23"
  },
  "5A92": {
   "unit": "game/mainProgram0800",
   "line": "entries 24-31"
  },
  "5A9A": {
   "unit": "game/mainProgram0800",
   "line": "entry 32, the last terrain index"
  },
  "5A9F": {
   "unit": "game/mainProgram0800",
   "line": "$5A9F contour edge: the lower two scanlines only"
  },
  "5AA3": {
   "unit": "game/mainProgram0800",
   "line": "$5AA3 contour edge: lower two scanlines plus one pixel on scanline 1"
  },
  "5AA7": {
   "unit": "game/mainProgram0800",
   "line": "$5AA7 contour edge: the top scanline only"
  },
  "5AAB": {
   "unit": "game/mainProgram0800",
   "line": "$5AAB contour edge: top two scanlines"
  },
  "5AAF": {
   "unit": "game/mainProgram0800",
   "line": "$5AAF full-height stripe - terrain indices 9-12"
  },
  "5AB3": {
   "unit": "game/mainProgram0800",
   "line": "$5AB3 one solid scanline - terrain indices 13-$20's base shape"
  },
  "5AB7": {
   "unit": "game/mainProgram0800",
   "line": "$5AB7 solid 2x4 blob = a unit; loaded directly at $5C14 and recoloured by the $55/$AA mask"
  },
  "5ABB": {
   "unit": "game/mainProgram0800",
   "line": "$5ABB alternating stipple - the forest terrain"
  },
  "5ABF": {
   "unit": "game/mainProgram0800",
   "line": "$5ABF every other scanline = a cloaked unit; loaded directly at $5C1C"
  },
  "5AC3": {
   "unit": "game/mainProgram0800",
   "line": "$5AC3 river bank"
  },
  "5AC7": {
   "unit": "game/mainProgram0800",
   "line": "$5AC7 river, notched at the top"
  },
  "5ACB": {
   "unit": "game/mainProgram0800",
   "line": "$5ACB river, notched at the top on the other side"
  },
  "5ACF": {
   "unit": "game/mainProgram0800",
   "line": "$5ACF river, notched at the bottom"
  },
  "5AD3": {
   "unit": "game/mainProgram0800",
   "line": "$5AD3 river, notched at the bottom on the other side"
  },
  "5AD9": {
   "unit": "game/mainProgram0800",
   "line": "VIC background 0 shadow; the IRQ copies it to $D021"
  },
  "5ADC": {
   "unit": "game/mainProgram0800",
   "line": "map pointer low byte..."
  },
  "5AE0": {
   "unit": "game/mainProgram0800",
   "line": "...and high byte: the first cell of the 40x40 map at $F000"
  },
  "5AE4": {
   "unit": "game/mainProgram0800",
   "line": "first character row of the window"
  },
  "5AE6": {
   "unit": "game/mainProgram0800",
   "line": "scratch18 = character row 1..20"
  },
  "5AEA": {
   "unit": "game/mainProgram0800",
   "line": "scratch19 = character column 1..20"
  },
  "5AEE": {
   "unit": "game/mainProgram0800",
   "line": "blank"
  },
  "5AF0": {
   "unit": "game/mainProgram0800",
   "line": "clear scanline X of the character being built at $90B8"
  },
  "5AF3": {
   "unit": "game/mainProgram0800",
   "line": "next scanline down"
  },
  "5AF4": {
   "unit": "game/mainProgram0800",
   "line": "until all 8 are blank"
  },
  "5AF6": {
   "unit": "game/mainProgram0800",
   "line": "quadrant 0 = top-left"
  },
  "5AF8": {
   "unit": "game/mainProgram0800",
   "line": "map offset 0 = the cell at (col, row)"
  },
  "5AFA": {
   "unit": "game/mainProgram0800",
   "line": "read the map cell"
  },
  "5AFC": {
   "unit": "game/mainProgram0800",
   "line": "OR its 2x4 pattern into the top-left quadrant and pick up its terrain colour"
  },
  "5AFF": {
   "unit": "game/mainProgram0800",
   "line": "map offset 1 = the cell to the right"
  },
  "5B00": {
   "unit": "game/mainProgram0800",
   "line": "quadrant 1 = top-right"
  },
  "5B01": {
   "unit": "game/mainProgram0800",
   "line": "read the map cell"
  },
  "5B03": {
   "unit": "game/mainProgram0800",
   "line": "OR it into the top-right quadrant"
  },
  "5B06": {
   "unit": "game/mainProgram0800",
   "line": "map offset $28 = 40 = the same column one map row down"
  },
  "5B08": {
   "unit": "game/mainProgram0800",
   "line": "quadrant 2 = bottom-left"
  },
  "5B09": {
   "unit": "game/mainProgram0800",
   "line": "read the map cell"
  },
  "5B0B": {
   "unit": "game/mainProgram0800",
   "line": "OR it into the bottom-left quadrant"
  },
  "5B0E": {
   "unit": "game/mainProgram0800",
   "line": "map offset $29 = the cell diagonally down-right"
  },
  "5B0F": {
   "unit": "game/mainProgram0800",
   "line": "quadrant 3 = bottom-right"
  },
  "5B10": {
   "unit": "game/mainProgram0800",
   "line": "read the map cell"
  },
  "5B12": {
   "unit": "game/mainProgram0800",
   "line": "OR it into the bottom-right quadrant; its terrain colour is the one that ends up in colour RAM"
  },
  "5B15": {
   "unit": "game/mainProgram0800",
   "line": "bitmapPtr/screenPtr for character (scratch19, scratch18); returns Y = character row, X = column"
  },
  "5B18": {
   "unit": "game/mainProgram0800",
   "line": "add the quarter or midfield line to scanline 0 if this character row carries one"
  },
  "5B1B": {
   "unit": "game/mainProgram0800",
   "line": "8 scanlines to store"
  },
  "5B1D": {
   "unit": "game/mainProgram0800",
   "line": "scanline Y of the finished character"
  },
  "5B20": {
   "unit": "game/mainProgram0800",
   "line": "into the $A000 bitmap"
  },
  "5B22": {
   "unit": "game/mainProgram0800",
   "line": "next scanline"
  },
  "5B23": {
   "unit": "game/mainProgram0800",
   "line": "until all 8 are written"
  },
  "5B25": {
   "unit": "game/mainProgram0800",
   "line": "$62: multicolour pair %01 -> colour 6 blue (own units), %10 -> colour 2 red (enemy units)"
  },
  "5B27": {
   "unit": "game/mainProgram0800",
   "line": "one byte per character"
  },
  "5B29": {
   "unit": "game/mainProgram0800",
   "line": "into screen RAM at $8C00"
  },
  "5B2B": {
   "unit": "game/mainProgram0800",
   "line": "screen RAM page..."
  },
  "5B2E": {
   "unit": "game/mainProgram0800",
   "line": "...+ $4C00 turns $8Cxx into $D8xx = colour RAM"
  },
  "5B32": {
   "unit": "game/mainProgram0800",
   "line": "terrain colour of the last cell of the 2x2 block, left in tileColour by buildOverviewQuadrant"
  },
  "5B34": {
   "unit": "game/mainProgram0800",
   "line": "colour RAM supplies the %11 pixel pairs, i.e. the terrain itself"
  },
  "5B36": {
   "unit": "game/mainProgram0800",
   "line": "advance the map pointer..."
  },
  "5B39": {
   "unit": "game/mainProgram0800",
   "line": "...by two cells - one character wide"
  },
  "5B3D": {
   "unit": "game/mainProgram0800",
   "line": "carry into the map pointer high byte"
  },
  "5B43": {
   "unit": "game/mainProgram0800",
   "line": "next character column"
  },
  "5B47": {
   "unit": "game/mainProgram0800",
   "line": "columns 1 to 20 inclusive"
  },
  "5B49": {
   "unit": "game/mainProgram0800",
   "line": "keep going along the row"
  },
  "5B4B": {
   "unit": "game/mainProgram0800",
   "line": "the row just drawn consumed 40 cells = one map row..."
  },
  "5B4E": {
   "unit": "game/mainProgram0800",
   "line": "...so skip the second map row it also covered"
  },
  "5B52": {
   "unit": "game/mainProgram0800",
   "line": "carry into the map pointer high byte"
  },
  "5B58": {
   "unit": "game/mainProgram0800",
   "line": "next character row"
  },
  "5B5C": {
   "unit": "game/mainProgram0800",
   "line": "rows 1 to 20 inclusive"
  },
  "5B5E": {
   "unit": "game/mainProgram0800",
   "line": "all 20 rows done"
  },
  "5B60": {
   "unit": "game/mainProgram0800",
   "line": "next character row of the overview"
  },
  "5B63": {
   "unit": "game/mainProgram0800",
   "line": "the whole 20x20 overview has been redrawn"
  },
  "5B68": {
   "unit": "game/mainProgram0800",
   "line": "first scanline per quadrant: 0 for the two top quadrants, 4 for the two bottom ones"
  },
  "5B6E": {
   "unit": "game/mainProgram0800",
   "line": "character row 11 = map rows 20/21 - the midfield line"
  },
  "5B70": {
   "unit": "game/mainProgram0800",
   "line": "midfield: leave X at 0 so both halves get a marker"
  },
  "5B72": {
   "unit": "game/mainProgram0800",
   "line": "character row 6 = map rows 10/11 - the first quarter line"
  },
  "5B76": {
   "unit": "game/mainProgram0800",
   "line": "character row 16 = map rows 30/31 - the third quarter line"
  },
  "5B78": {
   "unit": "game/mainProgram0800",
   "line": "any other row carries no field marking at all"
  },
  "5B7A": {
   "unit": "game/mainProgram0800",
   "line": "X = $FF: quarter lines are drawn in the right-hand pixel only"
  },
  "5B7B": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the LDA # at $5B95 - it gates the left-hand half below"
  },
  "5B7E": {
   "unit": "game/mainProgram0800",
   "line": "scanline 0 of the character being built"
  },
  "5B81": {
   "unit": "game/mainProgram0800",
   "line": "keep the left pixel pair (pixels 0 and 1)"
  },
  "5B83": {
   "unit": "game/mainProgram0800",
   "line": "hold it in Y while the right half is dealt with"
  },
  "5B84": {
   "unit": "game/mainProgram0800",
   "line": "scanline 0 again"
  },
  "5B87": {
   "unit": "game/mainProgram0800",
   "line": "keep the right pixel pair (pixels 2 and 3)"
  },
  "5B89": {
   "unit": "game/mainProgram0800",
   "line": "hold it in X"
  },
  "5B8A": {
   "unit": "game/mainProgram0800",
   "line": "%1010 = both pixels colour 2 = a solid enemy blob"
  },
  "5B8C": {
   "unit": "game/mainProgram0800",
   "line": "leave an enemy unit alone rather than dotting it"
  },
  "5B8E": {
   "unit": "game/mainProgram0800",
   "line": "the right half"
  },
  "5B8F": {
   "unit": "game/mainProgram0800",
   "line": "keep pixel 2, clear pixel 3"
  },
  "5B91": {
   "unit": "game/mainProgram0800",
   "line": "pixel 3 (the rightmost of the character) := colour 1"
  },
  "5B93": {
   "unit": "game/mainProgram0800",
   "line": "back into X"
  },
  "5B94": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5B7B: $FF for a quarter line, $00 for the midfield line"
  },
  "5B96": {
   "unit": "game/mainProgram0800",
   "line": "quarter line: only the right-hand pixel is drawn, skip the left half"
  },
  "5B98": {
   "unit": "game/mainProgram0800",
   "line": "the left half"
  },
  "5B99": {
   "unit": "game/mainProgram0800",
   "line": "%10100000 = both pixels colour 2 = a solid enemy blob"
  },
  "5B9B": {
   "unit": "game/mainProgram0800",
   "line": "leave an enemy unit alone"
  },
  "5B9D": {
   "unit": "game/mainProgram0800",
   "line": "the left half again"
  },
  "5B9E": {
   "unit": "game/mainProgram0800",
   "line": "keep pixel 0, clear pixel 1"
  },
  "5BA0": {
   "unit": "game/mainProgram0800",
   "line": "pixel 1 := colour 1"
  },
  "5BA2": {
   "unit": "game/mainProgram0800",
   "line": "back into Y"
  },
  "5BA3": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the ORA # at $5BA8 with the finished left half"
  },
  "5BA6": {
   "unit": "game/mainProgram0800",
   "line": "the finished right half"
  },
  "5BA7": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5BA3: recombine the two halves"
  },
  "5BA9": {
   "unit": "game/mainProgram0800",
   "line": "write scanline 0 of the character back"
  },
  "5BAC": {
   "unit": "game/mainProgram0800",
   "line": "done (also the exit for rows that carry no line)"
  },
  "5BB0": {
   "unit": "game/mainProgram0800",
   "line": "stash the quadrant number in the LDX # at $5C78; $5C4F reads it back"
  },
  "5BB3": {
   "unit": "game/mainProgram0800",
   "line": "keep the raw cell byte for the pattern lookup"
  },
  "5BB4": {
   "unit": "game/mainProgram0800",
   "line": "test bit 7 of the cell byte"
  },
  "5BB5": {
   "unit": "game/mainProgram0800",
   "line": "plain terrain cell - use it directly"
  },
  "5BB7": {
   "unit": "game/mainProgram0800",
   "line": "a unit stands here: colour after the terrain it hides ($FAF0 + index, reached as $FA70 + $80 + index)"
  },
  "5BBB": {
   "unit": "game/mainProgram0800",
   "line": "terrain cells $41-$60 become indices 1-$20"
  },
  "5BBD": {
   "unit": "game/mainProgram0800",
   "line": "cell $40 is the blank tile: keep the colour of the previous quadrant"
  },
  "5BBF": {
   "unit": "game/mainProgram0800",
   "line": "cell above $40 - the index is already right"
  },
  "5BC1": {
   "unit": "game/mainProgram0800",
   "line": "cells below $40 (the $21-$3F contour set) become index cell-$20"
  },
  "5BC3": {
   "unit": "game/mainProgram0800",
   "line": "33 valid terrain indices, 0 to $20"
  },
  "5BC5": {
   "unit": "game/mainProgram0800",
   "line": "out of range: keep the colour of the previous quadrant"
  },
  "5BC7": {
   "unit": "game/mainProgram0800",
   "line": "index the class table with it"
  },
  "5BC8": {
   "unit": "game/mainProgram0800",
   "line": "terrainClassTable read as $07BE + index: class 0-8 (0 plain, 1 river, 2 forest, 3/5 contour, 4/6 hill, 7 recycler)"
  },
  "5BCB": {
   "unit": "game/mainProgram0800",
   "line": "$07EA: 7 yellow plain, 3 cyan river, 5 green forest, 8 orange contours and hills, 0 black"
  },
  "5BCE": {
   "unit": "game/mainProgram0800",
   "line": "the caller stores this in colour RAM for the whole character"
  },
  "5BD0": {
   "unit": "game/mainProgram0800",
   "line": "the cell byte again"
  },
  "5BD1": {
   "unit": "game/mainProgram0800",
   "line": "enter buildOverviewQuadrantNoColour past the two saves we have already done"
  },
  "5BD7": {
   "unit": "game/mainProgram0800",
   "line": "stash the quadrant number in the LDX # at $5C78; $5C4F reads it back"
  },
  "5BDA": {
   "unit": "game/mainProgram0800",
   "line": "$FF = no unit recolouring, let the pattern through unchanged"
  },
  "5BDC": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the AND # at $5C68 (the unit colour mask)"
  },
  "5BDF": {
   "unit": "game/mainProgram0800",
   "line": "Y = the cell byte, so unit arrays are indexed through their $80-biased aliases"
  },
  "5BE0": {
   "unit": "game/mainProgram0800",
   "line": "bit 7 clear = plain terrain: skip everything unit related"
  },
  "5BE2": {
   "unit": "game/mainProgram0800",
   "line": "$9242 = the comcen's map system; bit 7 set means it has been knocked out"
  },
  "5BE5": {
   "unit": "game/mainProgram0800",
   "line": "no map system: the overview shows bare terrain, units are invisible"
  },
  "5BE7": {
   "unit": "game/mainProgram0800",
   "line": "waypoint-column array $F898 + index (via the $F818 alias); bit 7 = the unit is cloaked"
  },
  "5BEA": {
   "unit": "game/mainProgram0800",
   "line": "park it in the LDX # at $5C19 - the cloak test runs after the pattern has been chosen"
  },
  "5BED": {
   "unit": "game/mainProgram0800",
   "line": "unit type array $F76C + index (via the $F6EC alias); bit 7 = destroyed"
  },
  "5BF0": {
   "unit": "game/mainProgram0800",
   "line": "dead unit: draw the terrain it is lying on instead"
  },
  "5BF2": {
   "unit": "game/mainProgram0800",
   "line": "$922B = $55: every pixel pair %01, i.e. screen RAM upper nibble = colour 6 blue"
  },
  "5BF5": {
   "unit": "game/mainProgram0800",
   "line": "$B2 = $80|50: C=1 for unit indices 50-99, the second half of the unit array"
  },
  "5BF7": {
   "unit": "game/mainProgram0800",
   "line": "opcode patched by patchOwnSideBranches ($7BE1): BCC for side 0, BCS for side 1, so it is taken for OUR units"
  },
  "5BF9": {
   "unit": "game/mainProgram0800",
   "line": "opponent's unit: $55 becomes $AA, every pixel pair %10 = screen RAM lower nibble = colour 2 red"
  },
  "5BFB": {
   "unit": "game/mainProgram0800",
   "line": "this mask recolours the solid $FF pattern (self-modifies the AND # at $5C68)"
  },
  "5BFE": {
   "unit": "game/mainProgram0800",
   "line": "opcode patched by patchUnitSideBranches ($2A16), inverted when a replay is watching THEM: units of the watched side are always visible"
  },
  "5C00": {
   "unit": "game/mainProgram0800",
   "line": "$0B9B: bit 7 = film playback, bits 0-1 = the viewpoint"
  },
  "5C03": {
   "unit": "game/mainProgram0800",
   "line": "$80|2 = playback with the SHOW BOTH viewpoint"
  },
  "5C05": {
   "unit": "game/mainProgram0800",
   "line": "omniscient replay: the enemy unit is drawn too"
  },
  "5C07": {
   "unit": "game/mainProgram0800",
   "line": "$92FC bit 7 = reveal every unit (set when the game ends)"
  },
  "5C0A": {
   "unit": "game/mainProgram0800",
   "line": "reveal-all: draw the enemy unit"
  },
  "5C0C": {
   "unit": "game/mainProgram0800",
   "line": "energy array $F9C4 + index (via the $F944 alias); bit 7 = this enemy has been spotted"
  },
  "5C0F": {
   "unit": "game/mainProgram0800",
   "line": "never spotted: only the terrain it is standing on is shown"
  },
  "5C11": {
   "unit": "game/mainProgram0800",
   "line": "dead instruction - A is overwritten by the LDA # below and no flag is tested (leftover)"
  },
  "5C14": {
   "unit": "game/mainProgram0800",
   "line": "pattern $5AB7 = the solid 2x4 unit blob"
  },
  "5C16": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the pattern address"
  },
  "5C18": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5BEA with the unit's waypoint-column byte; bit 7 = cloaked"
  },
  "5C1A": {
   "unit": "game/mainProgram0800",
   "line": "not cloaked: keep the solid blob"
  },
  "5C1C": {
   "unit": "game/mainProgram0800",
   "line": "cloaked: pattern $5ABF, only every other scanline is drawn"
  },
  "5C1E": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the pattern address"
  },
  "5C20": {
   "unit": "game/mainProgram0800",
   "line": "go and plot it"
  },
  "5C23": {
   "unit": "game/mainProgram0800",
   "line": "$FF = no unit recolouring..."
  },
  "5C25": {
   "unit": "game/mainProgram0800",
   "line": "...so the terrain pattern goes through untouched (self-modifies the AND # at $5C68)"
  },
  "5C28": {
   "unit": "game/mainProgram0800",
   "line": "terrain byte hidden underneath the unit ($FAF0 + index, via the $FA70 alias)"
  },
  "5C2C": {
   "unit": "game/mainProgram0800",
   "line": "terrain cells $41-$60 become indices 1-$20"
  },
  "5C2E": {
   "unit": "game/mainProgram0800",
   "line": "cell $40 is the blank tile"
  },
  "5C30": {
   "unit": "game/mainProgram0800",
   "line": "cell above $40 - the index is already right"
  },
  "5C32": {
   "unit": "game/mainProgram0800",
   "line": "cells below $40 (the $21-$3F contour set) become index cell-$20"
  },
  "5C34": {
   "unit": "game/mainProgram0800",
   "line": "33 valid terrain indices, 0 to $20"
  },
  "5C36": {
   "unit": "game/mainProgram0800",
   "line": "in range - look the pattern up"
  },
  "5C38": {
   "unit": "game/mainProgram0800",
   "line": "blank pattern $5A9B: nothing but background"
  },
  "5C3A": {
   "unit": "game/mainProgram0800",
   "line": "high byte of the pattern address"
  },
  "5C3C": {
   "unit": "game/mainProgram0800",
   "line": "go and plot it"
  },
  "5C3F": {
   "unit": "game/mainProgram0800",
   "line": "terrain index into Y"
  },
  "5C40": {
   "unit": "game/mainProgram0800",
   "line": "low byte of this terrain's 4-byte pattern"
  },
  "5C43": {
   "unit": "game/mainProgram0800",
   "line": "park it while the high byte is fetched"
  },
  "5C44": {
   "unit": "game/mainProgram0800",
   "line": "high byte (always $5A)"
  },
  "5C47": {
   "unit": "game/mainProgram0800",
   "line": "into Y for the store below"
  },
  "5C48": {
   "unit": "game/mainProgram0800",
   "line": "low byte back into A"
  },
  "5C4C": {
   "unit": "game/mainProgram0800",
   "line": "...and high byte"
  },
  "5C4F": {
   "unit": "game/mainProgram0800",
   "line": "the quadrant number saved on entry"
  },
  "5C52": {
   "unit": "game/mainProgram0800",
   "line": "$F0 = the left pixel pair of the character, $0F = the right one"
  },
  "5C55": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the second AND # at $5C6A"
  },
  "5C58": {
   "unit": "game/mainProgram0800",
   "line": "0 for the top quadrants, 4 for the bottom ones"
  },
  "5C5B": {
   "unit": "game/mainProgram0800",
   "line": "X = first scanline of the quadrant inside the 8-byte buffer"
  },
  "5C5D": {
   "unit": "game/mainProgram0800",
   "line": "each quadrant is 4 scanlines tall"
  },
  "5C5F": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the CPX # at $5C74 with the end scanline"
  },
  "5C62": {
   "unit": "game/mainProgram0800",
   "line": "start at byte 0 of the pattern"
  },
  "5C64": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5C49: scanline Y of the chosen pattern"
  },
  "5C67": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5BDC/$5BFB/$5C25: $55 own unit, $AA enemy unit, $FF terrain"
  },
  "5C69": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5C55: keep only this quadrant's half of the byte"
  },
  "5C6B": {
   "unit": "game/mainProgram0800",
   "line": "OR into the character being built at $90B8"
  },
  "5C6E": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "5C71": {
   "unit": "game/mainProgram0800",
   "line": "next pattern byte"
  },
  "5C72": {
   "unit": "game/mainProgram0800",
   "line": "next scanline of the character"
  },
  "5C73": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5C5F: first scanline + 4"
  },
  "5C75": {
   "unit": "game/mainProgram0800",
   "line": "four scanlines per quadrant"
  },
  "5C77": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5BB0/$5BD7: restore the caller's quadrant number"
  },
  "5C79": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5BAD/$5BD4: restore the caller's Y"
  },
  "5C7B": {
   "unit": "game/mainProgram0800",
   "line": "done (drawMapCell borrows this RTS for its blackout exit)"
  },
  "5C7F": {
   "unit": "game/mainProgram0800",
   "line": "blackout in progress - draw nothing (borrows the RTS at the end of the routine above)"
  },
  "5C81": {
   "unit": "game/mainProgram0800",
   "line": "$C0E8: A/X/Y -> savedRegA/savedRegX/savedRegY = cell byte, column, row"
  },
  "5C84": {
   "unit": "game/mainProgram0800",
   "line": "0 = battlefield screen, 1 = comcen radar screen, 3 = menus"
  },
  "5C87": {
   "unit": "game/mainProgram0800",
   "line": "battlefield: the tactical view and the overview both need updating"
  },
  "5C89": {
   "unit": "game/mainProgram0800",
   "line": "any other screen: skip straight to the screen-1 test"
  },
  "5C8C": {
   "unit": "game/mainProgram0800",
   "line": "map column of the cell"
  },
  "5C8F": {
   "unit": "game/mainProgram0800",
   "line": "position relative to the left edge of the tactical view"
  },
  "5C91": {
   "unit": "game/mainProgram0800",
   "line": "the view is 7 cells wide; an unsigned compare also rejects columns left of the origin"
  },
  "5C93": {
   "unit": "game/mainProgram0800",
   "line": "outside the view - only the overview needs the update"
  },
  "5C95": {
   "unit": "game/mainProgram0800",
   "line": "map row of the cell"
  },
  "5C98": {
   "unit": "game/mainProgram0800",
   "line": "position relative to the top edge of the tactical view"
  },
  "5C9A": {
   "unit": "game/mainProgram0800",
   "line": "the view is 5 cells tall"
  },
  "5C9C": {
   "unit": "game/mainProgram0800",
   "line": "outside the view"
  },
  "5C9E": {
   "unit": "game/mainProgram0800",
   "line": "redraw the 16x16 tile inside the tactical view"
  },
  "5CA4": {
   "unit": "game/mainProgram0800",
   "line": "map column"
  },
  "5CA5": {
   "unit": "game/mainProgram0800",
   "line": "two map columns share one overview character"
  },
  "5CA7": {
   "unit": "game/mainProgram0800",
   "line": "the overview window starts at screen column 1"
  },
  "5CA9": {
   "unit": "game/mainProgram0800",
   "line": "X = character column"
  },
  "5CAA": {
   "unit": "game/mainProgram0800",
   "line": "map row"
  },
  "5CAB": {
   "unit": "game/mainProgram0800",
   "line": "two map rows share one overview character"
  },
  "5CAD": {
   "unit": "game/mainProgram0800",
   "line": "the overview window starts at screen row 1"
  },
  "5CAF": {
   "unit": "game/mainProgram0800",
   "line": "Y = character row"
  },
  "5CB0": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the LDY # at $5CFC - the quarter-line pass needs the character row"
  },
  "5CB3": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the LDX # at $5CFA (the value it loads is never used - see $5CF9)"
  },
  "5CB6": {
   "unit": "game/mainProgram0800",
   "line": "bitmapPtr/screenPtr of that character cell"
  },
  "5CB9": {
   "unit": "game/mainProgram0800",
   "line": "8 scanlines"
  },
  "5CBB": {
   "unit": "game/mainProgram0800",
   "line": "read scanline Y of the character out of the bitmap..."
  },
  "5CBD": {
   "unit": "game/mainProgram0800",
   "line": "...into the build buffer at $90B8"
  },
  "5CC0": {
   "unit": "game/mainProgram0800",
   "line": "next scanline"
  },
  "5CC1": {
   "unit": "game/mainProgram0800",
   "line": "until all 8 have been copied back"
  },
  "5CC3": {
   "unit": "game/mainProgram0800",
   "line": "map row of the cell"
  },
  "5CC5": {
   "unit": "game/mainProgram0800",
   "line": "odd row = the lower half of the character"
  },
  "5CC7": {
   "unit": "game/mainProgram0800",
   "line": "quadrants 2 and 3 are the lower ones"
  },
  "5CC8": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the ADC # at $5CD1"
  },
  "5CCB": {
   "unit": "game/mainProgram0800",
   "line": "map column of the cell"
  },
  "5CCD": {
   "unit": "game/mainProgram0800",
   "line": "odd column = the right half of the character"
  },
  "5CD0": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5CC8: quadrant = (row&1)*2 + (col&1), so 0-3"
  },
  "5CD2": {
   "unit": "game/mainProgram0800",
   "line": "X = quadrant number"
  },
  "5CD3": {
   "unit": "game/mainProgram0800",
   "line": "keep it across the clearing loop"
  },
  "5CD4": {
   "unit": "game/mainProgram0800",
   "line": "$F0 or $0F: the half of the byte this quadrant owns"
  },
  "5CD7": {
   "unit": "game/mainProgram0800",
   "line": "invert it into an AND mask that clears exactly those pixels"
  },
  "5CD9": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the AND # at $5CE9"
  },
  "5CDC": {
   "unit": "game/mainProgram0800",
   "line": "first scanline of the quadrant, 0 or 4"
  },
  "5CDF": {
   "unit": "game/mainProgram0800",
   "line": "X = scanline index into the build buffer"
  },
  "5CE0": {
   "unit": "game/mainProgram0800",
   "line": "+4 scanlines; the carry is still clear from the ADC at $5CD0, whose result can never exceed 3"
  },
  "5CE2": {
   "unit": "game/mainProgram0800",
   "line": "self-modify the CPX # at $5CEF with the end scanline"
  },
  "5CE5": {
   "unit": "game/mainProgram0800",
   "line": "scanline X of the character"
  },
  "5CE8": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5CD9: blank this quadrant's pixels, leave the neighbour's alone"
  },
  "5CEA": {
   "unit": "game/mainProgram0800",
   "line": "store it back"
  },
  "5CED": {
   "unit": "game/mainProgram0800",
   "line": "next scanline"
  },
  "5CEE": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5CE2: first scanline + 4"
  },
  "5CF0": {
   "unit": "game/mainProgram0800",
   "line": "four scanlines per quadrant"
  },
  "5CF2": {
   "unit": "game/mainProgram0800",
   "line": "quadrant number back off the stack"
  },
  "5CF3": {
   "unit": "game/mainProgram0800",
   "line": "into X for the call below"
  },
  "5CF4": {
   "unit": "game/mainProgram0800",
   "line": "the new map cell byte"
  },
  "5CF6": {
   "unit": "game/mainProgram0800",
   "line": "OR its pattern into the freshly cleared quadrant (the colour is left untouched)"
  },
  "5CF9": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5CB3; dead - drawOverviewQuarterLine opens with LDX #$00 (leftover)"
  },
  "5CFB": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5CB0: the character row, which the quarter-line test needs"
  },
  "5CFD": {
   "unit": "game/mainProgram0800",
   "line": "put the quarter or midfield line back on scanline 0 if this row carries one"
  },
  "5D00": {
   "unit": "game/mainProgram0800",
   "line": "8 scanlines"
  },
  "5D02": {
   "unit": "game/mainProgram0800",
   "line": "scanline Y of the rebuilt character"
  },
  "5D05": {
   "unit": "game/mainProgram0800",
   "line": "back into the $A000 bitmap"
  },
  "5D07": {
   "unit": "game/mainProgram0800",
   "line": "next scanline"
  },
  "5D08": {
   "unit": "game/mainProgram0800",
   "line": "until all 8 are written"
  },
  "5D0A": {
   "unit": "game/mainProgram0800",
   "line": "which screen is up"
  },
  "5D0D": {
   "unit": "game/mainProgram0800",
   "line": "screen 1 = the comcen radar/scanner map"
  },
  "5D0F": {
   "unit": "game/mainProgram0800",
   "line": "not the radar screen - nothing more to do"
  },
  "5D11": {
   "unit": "game/mainProgram0800",
   "line": "A/X/Y = cell byte, column, row"
  },
  "5D14": {
   "unit": "game/mainProgram0800",
   "line": "redraw the cell in the radar window as well"
  },
  "5D17": {
   "unit": "game/mainProgram0800",
   "line": "leave with A/X/Y exactly as the caller passed them"
  },
  "5D1C": {
   "unit": "game/mainProgram0800",
   "line": "keep it: viewOriginCol is about to be borrowed"
  },
  "5D1F": {
   "unit": "game/mainProgram0800",
   "line": "current top edge of the tactical view"
  },
  "5D21": {
   "unit": "game/mainProgram0800",
   "line": "keep it too"
  },
  "5D24": {
   "unit": "game/mainProgram0800",
   "line": "map row of the cell"
  },
  "5D26": {
   "unit": "game/mainProgram0800",
   "line": "map column of the cell"
  },
  "5D2A": {
   "unit": "game/mainProgram0800",
   "line": "0-6: position of the cell inside the 7-cell-wide view"
  },
  "5D2C": {
   "unit": "game/mainProgram0800",
   "line": "renderMapTile reads the cell's own column out of $A5"
  },
  "5D2E": {
   "unit": "game/mainProgram0800",
   "line": "each cell is 2 characters wide"
  },
  "5D30": {
   "unit": "game/mainProgram0800",
   "line": "the tactical view starts at screen character column 25"
  },
  "5D32": {
   "unit": "game/mainProgram0800",
   "line": "X = character column of the tile"
  },
  "5D35": {
   "unit": "game/mainProgram0800",
   "line": "0-4: position of the cell inside the 5-cell-tall view"
  },
  "5D37": {
   "unit": "game/mainProgram0800",
   "line": "renderMapTile reads the cell's own row out of $A6"
  },
  "5D39": {
   "unit": "game/mainProgram0800",
   "line": "each cell is 2 characters tall"
  },
  "5D3B": {
   "unit": "game/mainProgram0800",
   "line": "the tactical view starts at screen character row 1"
  },
  "5D3D": {
   "unit": "game/mainProgram0800",
   "line": "Y = character row of the tile"
  },
  "5D3E": {
   "unit": "game/mainProgram0800",
   "line": "bitmapPtr = the tile's top-left character, screenPtr = its screen RAM byte"
  },
  "5D41": {
   "unit": "game/mainProgram0800",
   "line": "renderMapTile writes screen and colour RAM at (screenPtr),Y"
  },
  "5D43": {
   "unit": "game/mainProgram0800",
   "line": "the map cell byte"
  },
  "5D45": {
   "unit": "game/mainProgram0800",
   "line": "build the 32-byte 16x16 multicolour tile in $04C8 and colour the four screen cells"
  },
  "5D48": {
   "unit": "game/mainProgram0800",
   "line": "bitmap destination, low byte..."
  },
  "5D4A": {
   "unit": "game/mainProgram0800",
   "line": "...into the STA operand at $5D58"
  },
  "5D4D": {
   "unit": "game/mainProgram0800",
   "line": "...high byte..."
  },
  "5D4F": {
   "unit": "game/mainProgram0800",
   "line": "...into the STA operand at $5D59"
  },
  "5D52": {
   "unit": "game/mainProgram0800",
   "line": "32 tile bytes, index 0 upwards"
  },
  "5D54": {
   "unit": "game/mainProgram0800",
   "line": "byte Y of the tile buffer at $04C8"
  },
  "5D57": {
   "unit": "game/mainProgram0800",
   "line": "operand patched at $5D4A/$5D68: the current row of characters in the $A000 bitmap"
  },
  "5D5A": {
   "unit": "game/mainProgram0800",
   "line": "next tile byte"
  },
  "5D5B": {
   "unit": "game/mainProgram0800",
   "line": "32 bytes = four 8-byte character cells"
  },
  "5D5D": {
   "unit": "game/mainProgram0800",
   "line": "whole tile copied"
  },
  "5D5F": {
   "unit": "game/mainProgram0800",
   "line": "after 16 bytes the upper pair of characters is done"
  },
  "5D61": {
   "unit": "game/mainProgram0800",
   "line": "still in the upper row - keep copying"
  },
  "5D64": {
   "unit": "game/mainProgram0800",
   "line": "bitmap origin of the tile..."
  },
  "5D66": {
   "unit": "game/mainProgram0800",
   "line": "...plus $130; Y is already $10, so the next store lands at origin + $140 = 320 bytes = one character row lower"
  },
  "5D68": {
   "unit": "game/mainProgram0800",
   "line": "new destination, low byte"
  },
  "5D6F": {
   "unit": "game/mainProgram0800",
   "line": "new destination, high byte"
  },
  "5D72": {
   "unit": "game/mainProgram0800",
   "line": "always taken - a bitmap address in $A000-$BFFF never has a zero high byte"
  },
  "5D74": {
   "unit": "game/mainProgram0800",
   "line": "the real left edge of the view"
  },
  "5D77": {
   "unit": "game/mainProgram0800",
   "line": "put it back"
  },
  "5D79": {
   "unit": "game/mainProgram0800",
   "line": "the real top edge of the view"
  },
  "5D7C": {
   "unit": "game/mainProgram0800",
   "line": "put it back"
  },
  "5D7E": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "5D81": {
   "unit": "game/mainProgram0800",
   "line": "plain terrain - nothing special about it"
  },
  "5D83": {
   "unit": "game/mainProgram0800",
   "line": "strip the unit marker to get the unit index"
  },
  "5D85": {
   "unit": "game/mainProgram0800",
   "line": "$9236 = 49 or 99, this player's own command centre"
  },
  "5D88": {
   "unit": "game/mainProgram0800",
   "line": "some other unit - draw it normally"
  },
  "5D8A": {
   "unit": "game/mainProgram0800",
   "line": "the radar window is centred on our comcen, so a comcen move scrolls the whole window ($7959)"
  },
  "5D8D": {
   "unit": "game/mainProgram0800",
   "line": "map column of the cell"
  },
  "5D90": {
   "unit": "game/mainProgram0800",
   "line": "column relative to the left edge of the radar window"
  },
  "5D92": {
   "unit": "game/mainProgram0800",
   "line": "keep it in X for the bitmap column table"
  },
  "5D93": {
   "unit": "game/mainProgram0800",
   "line": "the radar window shows 21 columns"
  },
  "5D95": {
   "unit": "game/mainProgram0800",
   "line": "inside the window"
  },
  "5D97": {
   "unit": "game/mainProgram0800",
   "line": "off the window - nothing to redraw"
  },
  "5D98": {
   "unit": "game/mainProgram0800",
   "line": "map row of the cell"
  },
  "5D9B": {
   "unit": "game/mainProgram0800",
   "line": "row relative to the top edge of the radar window"
  },
  "5D9D": {
   "unit": "game/mainProgram0800",
   "line": "keep it in Y for the bitmap row table"
  },
  "5D9E": {
   "unit": "game/mainProgram0800",
   "line": "the radar window shows 17 rows"
  },
  "5DA0": {
   "unit": "game/mainProgram0800",
   "line": "off the window"
  },
  "5DA2": {
   "unit": "game/mainProgram0800",
   "line": "dead instruction - the next one loads the very same value (leftover)"
  },
  "5DA4": {
   "unit": "game/mainProgram0800",
   "line": "colour $0B = dark grey"
  },
  "5DA6": {
   "unit": "game/mainProgram0800",
   "line": "$9237: the colour our own units are plotted in on the radar"
  },
  "5DA9": {
   "unit": "game/mainProgram0800",
   "line": "$9239: and the colour the terrain glyphs get"
  },
  "5DAC": {
   "unit": "game/mainProgram0800",
   "line": "colour 1 = white"
  },
  "5DAE": {
   "unit": "game/mainProgram0800",
   "line": "$9238: enemy blips stand out against the grey"
  },
  "5DB2": {
   "unit": "game/mainProgram0800",
   "line": "bitmap row address table $9300, offset by 3 rows - the window starts at character row 3"
  },
  "5DB5": {
   "unit": "game/mainProgram0800",
   "line": "column*8 table $9332, offset by 3 columns - and at character column 3"
  },
  "5DB8": {
   "unit": "game/mainProgram0800",
   "line": "bitmapPtr low byte"
  },
  "5DBA": {
   "unit": "game/mainProgram0800",
   "line": "high half of the row table ($9319 + 3)"
  },
  "5DBD": {
   "unit": "game/mainProgram0800",
   "line": "high half of the column table ($935A + 3)"
  },
  "5DC0": {
   "unit": "game/mainProgram0800",
   "line": "bitmapPtr high byte"
  },
  "5DC3": {
   "unit": "game/mainProgram0800",
   "line": "screen RAM row address table $9382, offset by 3 rows"
  },
  "5DC6": {
   "unit": "game/mainProgram0800",
   "line": "plus the 3-column indent of the window"
  },
  "5DC8": {
   "unit": "game/mainProgram0800",
   "line": "screenPtr low byte"
  },
  "5DCA": {
   "unit": "game/mainProgram0800",
   "line": "high half of the screen row table ($939B + 3)"
  },
  "5DCD": {
   "unit": "game/mainProgram0800",
   "line": "propagate the carry"
  },
  "5DCF": {
   "unit": "game/mainProgram0800",
   "line": "screenPtr high byte"
  },
  "5DD2": {
   "unit": "game/mainProgram0800",
   "line": "the cell's column inside the window"
  },
  "5DD3": {
   "unit": "game/mainProgram0800",
   "line": "add it to the row address"
  },
  "5DD9": {
   "unit": "game/mainProgram0800",
   "line": "$8C00 + $4C00 = $D800: point straight at colour RAM"
  },
  "5DDD": {
   "unit": "game/mainProgram0800",
   "line": "current left edge of the radar window"
  },
  "5DDF": {
   "unit": "game/mainProgram0800",
   "line": "keep it: viewOriginCol is about to be borrowed"
  },
  "5DE2": {
   "unit": "game/mainProgram0800",
   "line": "current top edge of the radar window"
  },
  "5DE4": {
   "unit": "game/mainProgram0800",
   "line": "keep it too"
  },
  "5DE7": {
   "unit": "game/mainProgram0800",
   "line": "A = cell byte, X = map column, Y = map row"
  },
  "5DEA": {
   "unit": "game/mainProgram0800",
   "line": "getRadarCellGlyph reads the cell's own column out of $A5"
  },
  "5DEC": {
   "unit": "game/mainProgram0800",
   "line": "...and its row out of $A6"
  },
  "5DEE": {
   "unit": "game/mainProgram0800",
   "line": "Y = 0, the offset drawRadarGlyph stores the colour byte at"
  },
  "5DF0": {
   "unit": "game/mainProgram0800",
   "line": "the map cell byte"
  },
  "5DF2": {
   "unit": "game/mainProgram0800",
   "line": "overlay routine $74C3: cell byte -> glyph index in A (bit 7 = noise) and colour in tileColour"
  },
  "5DF5": {
   "unit": "game/mainProgram0800",
   "line": "overlay routine $7396: plot the 8x8 glyph at (bitmapPtr) and its colour at (screenPtr),Y"
  },
  "5DF8": {
   "unit": "game/mainProgram0800",
   "line": "the real left edge of the radar window"
  },
  "5DFB": {
   "unit": "game/mainProgram0800",
   "line": "put it back"
  },
  "5DFD": {
   "unit": "game/mainProgram0800",
   "line": "the real top edge of the radar window"
  },
  "5E00": {
   "unit": "game/mainProgram0800",
   "line": "put it back"
  },
  "5E02": {
   "unit": "game/mainProgram0800",
   "line": "done"
  },
  "59C1": {
   "unit": "game/mainProgram0800",
   "line": "prepare a plain add"
  },
  "59C4": {
   "unit": "game/mainProgram0800",
   "line": "prepare a true subtraction"
  },
  "5ADE": {
   "unit": "game/mainProgram0800",
   "line": "mapCellPtr low byte"
  },
  "5AE2": {
   "unit": "game/mainProgram0800",
   "line": "mapCellPtr high byte - the pointer now addresses map cell (0,0)"
  },
  "5B2D": {
   "unit": "game/mainProgram0800",
   "line": "carry must be clear before the page add below"
  },
  "5B30": {
   "unit": "game/mainProgram0800",
   "line": "screenPtr now points at the colour RAM byte of this character"
  },
  "5B38": {
   "unit": "game/mainProgram0800",
   "line": "plain 16-bit add of the two-cell step"
  },
  "5B3B": {
   "unit": "game/mainProgram0800",
   "line": "store the advanced map pointer, low byte"
  },
  "5B3F": {
   "unit": "game/mainProgram0800",
   "line": "propagate the carry into the high byte"
  },
  "5B41": {
   "unit": "game/mainProgram0800",
   "line": "store the advanced map pointer, high byte"
  },
  "5B45": {
   "unit": "game/mainProgram0800",
   "line": "the character column just finished"
  },
  "5B4D": {
   "unit": "game/mainProgram0800",
   "line": "plain 16-bit add of the 40-cell step"
  },
  "5B50": {
   "unit": "game/mainProgram0800",
   "line": "store the advanced map pointer, low byte"
  },
  "5B54": {
   "unit": "game/mainProgram0800",
   "line": "propagate the carry into the high byte"
  },
  "5B56": {
   "unit": "game/mainProgram0800",
   "line": "store the advanced map pointer, high byte"
  },
  "5B5A": {
   "unit": "game/mainProgram0800",
   "line": "the character row just finished"
  },
  "5B74": {
   "unit": "game/mainProgram0800",
   "line": "row 6: only the right-hand pixel carries this quarter line"
  },
  "5BBA": {
   "unit": "game/mainProgram0800",
   "line": "prepare a true subtraction of the terrain base"
  },
  "5C5C": {
   "unit": "game/mainProgram0800",
   "line": "plain add, no carry in"
  },
  "5C8E": {
   "unit": "game/mainProgram0800",
   "line": "prepare a true subtraction"
  },
  "5C97": {
   "unit": "game/mainProgram0800",
   "line": "prepare a true subtraction"
  },
  "5CA6": {
   "unit": "game/mainProgram0800",
   "line": "the LSR above may have left the carry set - clear it before the add"
  },
  "5CAC": {
   "unit": "game/mainProgram0800",
   "line": "the LSR above may have left the carry set - clear it before the add"
  },
  "5CCF": {
   "unit": "game/mainProgram0800",
   "line": "clear the carry for the quadrant add below"
  },
  "5D28": {
   "unit": "game/mainProgram0800",
   "line": "map column of the cell"
  },
  "5D29": {
   "unit": "game/mainProgram0800",
   "line": "prepare a true subtraction"
  },
  "5D2F": {
   "unit": "game/mainProgram0800",
   "line": "the ASL above may have set the carry - clear it before the add"
  },
  "5D33": {
   "unit": "game/mainProgram0800",
   "line": "map row of the cell"
  },
  "5D34": {
   "unit": "game/mainProgram0800",
   "line": "prepare a true subtraction"
  },
  "5D3A": {
   "unit": "game/mainProgram0800",
   "line": "the ASL above may have set the carry - clear it before the add"
  },
  "5D63": {
   "unit": "game/mainProgram0800",
   "line": "plain 16-bit add of the $130 step"
  },
  "5D6B": {
   "unit": "game/mainProgram0800",
   "line": "bitmap origin of the tile, high byte..."
  },
  "5D6D": {
   "unit": "game/mainProgram0800",
   "line": "...plus the carry and the $100 of the $130 step"
  },
  "5D8F": {
   "unit": "game/mainProgram0800",
   "line": "prepare a true subtraction"
  },
  "5D9A": {
   "unit": "game/mainProgram0800",
   "line": "prepare a true subtraction"
  },
  "5DB1": {
   "unit": "game/mainProgram0800",
   "line": "plain 16-bit add of row base + column offset"
  },
  "5DC2": {
   "unit": "game/mainProgram0800",
   "line": "plain 16-bit add"
  },
  "5DD1": {
   "unit": "game/mainProgram0800",
   "line": "plain 16-bit add"
  },
  "5DD5": {
   "unit": "game/mainProgram0800",
   "line": "screenPtr low byte = row base + 3 + window column"
  },
  "5DD7": {
   "unit": "game/mainProgram0800",
   "line": "screenPtr high byte..."
  },
  "5DDB": {
   "unit": "game/mainProgram0800",
   "line": "screenPtr now addresses colour RAM directly"
  }
 },
 "dataTypes": {
  "5A59": [
   "byte",
   33
  ],
  "5A7A": [
   "byte",
   33
  ],
  "5A9B": [
   "grid",
   60,
   4
  ],
  "5B64": [
   "byte",
   4
  ],
  "5B68": [
   "byte",
   4
  ]
 },
 "zpComments": {
  "48": "pointer (lo) to a cell of the $F000 battlefield map, set by readMapCell ($25FA); also the running pointer of the block fill/copy loops and the cell pointer drawOverviewMap walks over the map, two cells per overview character and an extra 40 per character row",
  "BC": "pointer (lo) into the game film buffer in the RAM under the I/O area ($D000-$DFEF); reached only through readFilmByte/writeFilmByte with Y as the offset, so it doubles as the header base ($D000) and as the write position of the command stream",
  "18": "general scratch, used by every unit: multiply/divide operand ($FCCD/$FD0A), character row in the text plotters, the film header checksum accumulated by $5900, the overview character row in drawOverviewMap, loop counters and sums",
  "19": "general scratch, partner of scratch18: product/quotient, character column in the text plotters, the page counter of the film unit snapshot ($5967), the overview character column in drawOverviewMap"
 }
}
