{
 "chunk": "game_textEngineC000_C5EE",
 "unit": "game/textEngineC000",
 "unitLabels": {
  "game/textEngineC000": {
   "C60C": "statusLineGoIdle",
   "C619": "showPersistentMessage",
   "C61E": "popQueuedMessage",
   "C627": "storePendingMessageId",
   "C647": "messageReviewActive",
   "C662": "stepMessageReviewBack",
   "C677": "beepNoOlderMessage",
   "C67C": "showReviewedMessage",
   "C698": "messageReviewDone",
   "C699": "checkScoreLineKey",
   "C733": "checkSoloPauseActive",
   "C73C": "pauseKeyDone",
   "C73D": "livePauseEntry",
   "C74E": "enterPauseLoop",
   "C764": "choosePauseKind",
   "C76E": "pauseSpinLoop",
   "C78C": "resumeFromPause",
   "C79D": "restorePauseSprites",
   "C7A2": "pauseKeyExit",
   "C7C2": "runStopKeyReturn",
   "C7D4": "waitingMessageDone",
   "C7F2": "textEntryCheckDelete",
   "C7FE": "textEntryCheckPrintable",
   "C812": "textEntryLengthCheck",
   "C816": "textEntryScrollCheck",
   "C81D": "scrollTypedLineLoop",
   "C82A": "storeTypedChar",
   "C830": "textEntryRefuse",
   "C835": "textEntryNoKey",
   "C83C": "terminateTypedLine",
   "C841": "textEntryAccepted",
   "C846": "textEntryConsumeKey",
   "C84E": "textEntryReturnCode",
   "C850": "textEntryReturn",
   "C862": "waitForFireLoop",
   "C879": "endBattleToMenu",
   "C87C": "gameStillRunning",
   "C8A3": "playSoundRestoreY",
   "C8A5": "playSoundRestoreX",
   "C8B9": "classifySoundRequest",
   "C8D0": "checkVoiceBusy",
   "C8DB": "queueSoundInSlot",
   "C8E8": "soundRequestDone",
   "C8EB": "scanSoundSlotsLoop",
   "C8F6": "nextSoundSlot",
   "C913": "comcenHasStopped",
   "C91E": "requestComcenStopSound",
   "C923": "comcenSoundDone",
   "CBED": "strAttemptingRepairMid",
   "CBF2": "strAttemptingRepairEnd",
   "CD15": "copyUnitPictureLoop",
   "CFDD": "leftoverFrameTimerTail"
  }
 },
 "notes": {
  "C5EE": {
   "unit": "game/textEngineC000",
   "routine": [
    "showNextQueuedMessage - chooses what the status line should show next and leaves the answer in pendingMessageId zp_7A; the actual printing is done by updateStatusMessage on its next pass.  If the 8-entry message ring $9249 is not empty it pops the entry at the read index.  If the ring is empty it either falls back to the chat line (message id 0) when one is waiting, or goes idle: zp_7A = $80, the chat line is blanked, and persistentMessageId $92A1 then decides - negative redraws the CLICKS/US/THEM score line, zero blanks the status line, anything else is printed as a message id.",
    "In:  messageQueueTail $9252 (read index), messageQueueHead $9251 (write index), messageQueue $9249, pendingMessageId zp_7A, chatMessageLine $0548 (marker byte of message 0), persistentMessageId $92A1",
    "Out: zp_7A = the next message id or $80, $9252 advanced, $0548 possibly blanked, the sound-suppress operand L_C54A+1 possibly set negative; the status line may be redrawn; A/Y clobbered",
    "Called from: updateStatusMessage ($C4FF), redrawStatusMessage ($C5E0 falls in), resetMessageQueue ($C6FC)"
   ],
   "line": "read index of the 8-entry message ring"
  },
  "C5F1": {
   "unit": "game/textEngineC000",
   "line": "compare it with the write index"
  },
  "C5F4": {
   "unit": "game/textEngineC000",
   "line": "differ = at least one message is queued, go and pop it"
  },
  "C5F6": {
   "unit": "game/textEngineC000",
   "line": "$80 = the 'nothing pending' marker of pendingMessageId"
  },
  "C5F8": {
   "unit": "game/textEngineC000",
   "line": "is the status line already idle?"
  },
  "C5FA": {
   "unit": "game/textEngineC000",
   "line": "yes - keep it idle (A is already the $80 that gets stored there)"
  },
  "C5FC": {
   "unit": "game/textEngineC000",
   "line": "marker byte of message 0 at $0548: $1E = chat text present, $9E = empty"
  },
  "C5FF": {
   "unit": "game/textEngineC000",
   "line": "bit 7 set ($9E) = no chat line waiting, so go idle"
  },
  "C601": {
   "unit": "game/textEngineC000",
   "line": "message id 0 = the chat line at $0548"
  },
  "C603": {
   "unit": "game/textEngineC000",
   "line": "show the chat line next"
  },
  "C605": {
   "unit": "game/textEngineC000",
   "line": "$FF = negative"
  },
  "C607": {
   "unit": "game/textEngineC000",
   "line": "patch the 'lda #$04' operand at $C54A in updateStatusMessage negative so this message is printed without its sound"
  },
  "C60A": {
   "unit": "game/textEngineC000",
   "line": "always taken (Y = $FF); A is still 0, so the store at $C627 just repeats it"
  },
  "C60C": {
   "unit": "game/textEngineC000",
   "line": "A = $80: nothing is pending any more",
   "block": [
    "Queue empty and no chat line: park the status line.  A = $80 here."
   ]
  },
  "C60E": {
   "unit": "game/textEngineC000",
   "line": "make message 0 print as an empty line ($0548 = $9E)"
  },
  "C611": {
   "unit": "game/textEngineC000",
   "line": "$92A1, the message the status line falls back to when the ring drains"
  },
  "C614": {
   "unit": "game/textEngineC000",
   "line": "positive = a plain message id (0 = blank line)"
  },
  "C616": {
   "unit": "game/textEngineC000",
   "line": "negative = show the CLICKS / US / THEM score line instead"
  },
  "C619": {
   "unit": "game/textEngineC000",
   "line": "non-zero = print that message id right now"
  },
  "C61B": {
   "unit": "game/textEngineC000",
   "line": "zero = simply blank the status line"
  },
  "C61E": {
   "unit": "game/textEngineC000",
   "line": "take the oldest id out of the ring",
   "block": [
    "Queue not empty: pop the entry the read index points at."
   ]
  },
  "C621": {
   "unit": "game/textEngineC000",
   "line": "advance the read index modulo 8"
  },
  "C624": {
   "unit": "game/textEngineC000",
   "line": "store the new read index"
  },
  "C627": {
   "unit": "game/textEngineC000",
   "line": "hand the id to updateStatusMessage"
  },
  "C629": {
   "unit": "game/textEngineC000",
   "line": "the caller prints it, this routine only selects"
  },
  "C62A": {
   "unit": "game/textEngineC000",
   "routine": [
    "clearChatStatusLine - empties message slot 0 (the chat line, text at $0548) by storing $9E in its first byte.  printChar throws away every code below $20, and $9E is $1E with the string-terminator bit 7 set, so a string that starts with it prints absolutely nothing: the status line comes out blank.",
    "In:  none",
    "Out: chatMessageLine $0548 = $9E, A = $9E",
    "Called from: showNextQueuedMessage ($C60E) and by JMP from the game's start-up init ($0C63)"
   ],
   "line": "$1E | $80: a control code (nothing is drawn) that also carries the end-of-string bit"
  },
  "C62C": {
   "unit": "game/textEngineC000",
   "line": "message slot 0 now terminates on its first byte = an empty status line"
  },
  "C62F": {
   "unit": "game/textEngineC000",
   "line": "A = $9E on exit, which the start-up init at $0C63 relies on"
  },
  "C630": {
   "unit": "game/textEngineC000",
   "block": [
    "messageReviewIndex - the single state byte of the message-history scrollback.",
    "bit 7 = review mode active, bits 0-6 = the real messageQueueTail saved when review started,",
    "so it can be put back when the player stops scrolling.  0 = not reviewing."
   ],
   "line": "review state: $00 = off, $80|savedReadIndex = scrolling back through old messages"
  },
  "C631": {
   "unit": "game/textEngineC000",
   "routine": [
    "handleMessageReviewKeys - per-frame keyboard hook of the status-message service.  CRSR up/down (key code $8B) enters and then steps the message history: the real ring read index is saved in messageReviewIndex with bit 7 set and each further press walks one entry backwards through the 8-entry ring, showing it at once (messageTimer = 0, sound suppressed).  When the player stops pressing, the saved read index is restored and review mode ends.  CRSR left/right ($8E) is consumed and, during the battle only, pops up the CLICKS/US/THEM score line for 60 ticks by falling into drawStatusScoreLine.",
    "In:  messageReviewIndex $C630, lastTypedKey $9248, messageTimer zp_72, messageQueue/Head/Tail $9249/$9251/$9252, pendingMessageId zp_7A, shownMessageId zp_7B, gamePhase zp_B1",
    "Out: $C630, $9252, $9248 = $FF (key consumed), zp_7A, zp_7B, zp_72, chatMessageLine $0548 = $1E, the sound-suppress operand L_C54A+1 = $80, inputLockoutTimer $0B7D; may draw the score line",
    "Called from: updateStatusMessage ($C4F3), i.e. from every polling loop in the game"
   ],
   "line": "review state byte"
  },
  "C634": {
   "unit": "game/textEngineC000",
   "line": "bit 7 set = already scrolling back, skip the entry test"
  },
  "C636": {
   "unit": "game/textEngineC000",
   "line": "last key the IRQ decoded ($FF = none)"
  },
  "C639": {
   "unit": "game/textEngineC000",
   "line": "$8B = CRSR up/down, the scrollback key"
  },
  "C63B": {
   "unit": "game/textEngineC000",
   "line": "not that key - try the score-line key instead"
  },
  "C63D": {
   "unit": "game/textEngineC000",
   "line": "remember where the ring read index really is"
  },
  "C640": {
   "unit": "game/textEngineC000",
   "line": "bit 7 marks review mode as active"
  },
  "C642": {
   "unit": "game/textEngineC000",
   "line": "save it so the ring can be rewound afterwards"
  },
  "C645": {
   "unit": "game/textEngineC000",
   "line": "always taken (bit 7 was just set): show the previous message"
  },
  "C647": {
   "unit": "game/textEngineC000",
   "line": "ticks left on the message currently displayed",
   "block": [
    "Review mode is running.  Wait until the message on screen has had its minimum",
    "airtime ($3C down to $23), then either step further back or leave review mode."
   ]
  },
  "C649": {
   "unit": "game/textEngineC000",
   "line": "$3C is loaded on display; below $23 means it has been up for about 25 ticks (100 frames)"
  },
  "C64B": {
   "unit": "game/textEngineC000",
   "line": "still fresh - do nothing, which rate-limits the scrollback and delays the exit"
  },
  "C64D": {
   "unit": "game/textEngineC000",
   "line": "has CRSR up/down been pressed again?"
  },
  "C650": {
   "unit": "game/textEngineC000",
   "line": "$8B = CRSR up/down"
  },
  "C652": {
   "unit": "game/textEngineC000",
   "line": "yes - step one more entry backwards"
  },
  "C654": {
   "unit": "game/textEngineC000",
   "line": "no further press: recover the saved read index"
  },
  "C657": {
   "unit": "game/textEngineC000",
   "line": "strip the 'review active' bit 7"
  },
  "C659": {
   "unit": "game/textEngineC000",
   "line": "put the ring read index back where it was"
  },
  "C65C": {
   "unit": "game/textEngineC000",
   "line": "and clear bit 7, leaving review mode"
  },
  "C65F": {
   "unit": "game/textEngineC000",
   "line": "done for this frame"
  },
  "C662": {
   "unit": "game/textEngineC000",
   "line": "$FF = 'no key'",
   "block": [
    "Step one entry backwards through the ring and display it immediately."
   ]
  },
  "C664": {
   "unit": "game/textEngineC000",
   "line": "consume the key so it is not seen again next frame"
  },
  "C667": {
   "unit": "game/textEngineC000",
   "line": "current ring read index"
  },
  "C66A": {
   "unit": "game/textEngineC000",
   "line": "step one entry backwards modulo 8"
  },
  "C66D": {
   "unit": "game/textEngineC000",
   "line": "have we walked back onto the write index?"
  },
  "C670": {
   "unit": "game/textEngineC000",
   "line": "yes - there is no older message, beep"
  },
  "C672": {
   "unit": "game/textEngineC000",
   "line": "id stored in that ring slot"
  },
  "C675": {
   "unit": "game/textEngineC000",
   "line": "a real id (bit 7 clear) - show it"
  },
  "C677": {
   "unit": "game/textEngineC000",
   "line": "sound 3 = the error/refusal beep"
  },
  "C679": {
   "unit": "game/textEngineC000",
   "line": "tail call: beep and return"
  },
  "C67C": {
   "unit": "game/textEngineC000",
   "line": "$1E = 'message 0 has text'; the code itself prints nothing"
  },
  "C67E": {
   "unit": "game/textEngineC000",
   "line": "keep the chat line marked as printable so the status line returns to it when review ends"
  },
  "C681": {
   "unit": "game/textEngineC000",
   "line": "id currently selected"
  },
  "C683": {
   "unit": "game/textEngineC000",
   "line": "set bit 7 = 'the status line is free'"
  },
  "C685": {
   "unit": "game/textEngineC000",
   "line": "so updateStatusMessage prints the reviewed message immediately"
  },
  "C687": {
   "unit": "game/textEngineC000",
   "line": "the ring read index now points at the reviewed entry"
  },
  "C68A": {
   "unit": "game/textEngineC000",
   "line": "fetch that entry again"
  },
  "C68D": {
   "unit": "game/textEngineC000",
   "line": "and make it the message to display"
  },
  "C68F": {
   "unit": "game/textEngineC000",
   "line": "zero the display timer"
  },
  "C691": {
   "unit": "game/textEngineC000",
   "line": "so the next updateStatusMessage pass prints it at once"
  },
  "C693": {
   "unit": "game/textEngineC000",
   "line": "$80 = negative"
  },
  "C695": {
   "unit": "game/textEngineC000",
   "line": "patch the 'lda #$04' operand at $C54A so the reviewed message is shown silently"
  },
  "C698": {
   "unit": "game/textEngineC000",
   "line": "nothing more to do this frame"
  },
  "C699": {
   "unit": "game/textEngineC000",
   "line": "last key again",
   "block": [
    "Not the scrollback key: CRSR left/right pops the score line up during the battle."
   ]
  },
  "C69C": {
   "unit": "game/textEngineC000",
   "line": "$8E = CRSR left/right, the 'show the score' key"
  },
  "C69E": {
   "unit": "game/textEngineC000",
   "line": "some other key - ignore it"
  },
  "C6A0": {
   "unit": "game/textEngineC000",
   "line": "$FF = 'no key'"
  },
  "C6A2": {
   "unit": "game/textEngineC000",
   "line": "consume the key"
  },
  "C6A5": {
   "unit": "game/textEngineC000",
   "line": "zp_B1 is 0 only while the battle is running"
  },
  "C6A7": {
   "unit": "game/textEngineC000",
   "line": "in setup, film or menu phases there is no score line to show"
  },
  "C6A9": {
   "unit": "game/textEngineC000",
   "line": "$3C = 60"
  },
  "C6AB": {
   "unit": "game/textEngineC000",
   "line": "lock the keyboard and joystick out for 60 frames"
  },
  "C6AE": {
   "unit": "game/textEngineC000",
   "line": "and keep the score line up for 60 message ticks"
  },
  "C6B0": {
   "unit": "game/textEngineC000",
   "line": "id on the status line"
  },
  "C6B2": {
   "unit": "game/textEngineC000",
   "line": "clear bit 7 = 'the line is occupied' so the score line is not overwritten at once"
  },
  "C6B4": {
   "unit": "game/textEngineC000",
   "line": "store it back, then fall into drawStatusScoreLine"
  },
  "C6B6": {
   "unit": "game/textEngineC000",
   "routine": [
    "drawStatusScoreLine - redraws the bottom status line as '   CLICKS:<clock>    US:<points>   THEM:<points>'.  The three labels are three consecutive bit-7 terminated strings at $065E/$0668/$066F, so after the first one printString's pointer is simply run on with printStringAtPointer.  sideScore is indexed by the side that was scored against, so entry [playerSide EOR 1] is the local player's own point total.",
    "In:  gameClock $91CB, playerSide $0B9F, sideScoreLo/Hi $92A2/$92A4, the label strings at $065E",
    "Out: status line redrawn; the caller's text window is saved to $92B6 and restored; decimalValueHigh zp_7E, the digit buffer $92DA and zp_18-zp_1F are clobbered",
    "Called from: by JMP only - updateStatusMessage ($C521, when the clock changed while the last-quarter flag $92FA is set) and handleMessageReviewKeys (fall-through at $C6B4)"
   ],
   "line": "stash the caller's text window in $92B6 and select the full screen"
  },
  "C6B9": {
   "unit": "game/textEngineC000",
   "line": "switch to the one-row status bar window (columns 2-37 of row 22 or 23)"
  },
  "C6BC": {
   "unit": "game/textEngineC000",
   "line": "low byte of '   CLICKS:' at $065E"
  },
  "C6BE": {
   "unit": "game/textEngineC000",
   "line": "high byte of $065E"
  },
  "C6C0": {
   "unit": "game/textEngineC000",
   "line": "print it, leaving the string pointer just past its terminator"
  },
  "C6C3": {
   "unit": "game/textEngineC000",
   "line": "$91CB, the game clock in clicks (it counts down to 0)"
  },
  "C6C6": {
   "unit": "game/textEngineC000",
   "line": "middle byte 0; the high byte zp_7E was left 0 by the previous conversion"
  },
  "C6C8": {
   "unit": "game/textEngineC000",
   "line": "print it as decimal without leading zeros"
  },
  "C6CB": {
   "unit": "game/textEngineC000",
   "line": "carry straight on with the next string in memory, '    US:' at $0668"
  },
  "C6CE": {
   "unit": "game/textEngineC000",
   "line": "$0B9F = the side this machine plays"
  },
  "C6D1": {
   "unit": "game/textEngineC000",
   "line": "the other side, which is the index of the points WE scored"
  },
  "C6D3": {
   "unit": "game/textEngineC000",
   "line": "print our 16-bit point total"
  },
  "C6D6": {
   "unit": "game/textEngineC000",
   "line": "carry on with '   THEM:' at $066F"
  },
  "C6D9": {
   "unit": "game/textEngineC000",
   "line": "our own side index = the points scored against us"
  },
  "C6DC": {
   "unit": "game/textEngineC000",
   "line": "print the opponent's point total"
  },
  "C6DF": {
   "unit": "game/textEngineC000",
   "line": "pad the rest of the 36-column status line with the blank glyph"
  },
  "C6E2": {
   "unit": "game/textEngineC000",
   "line": "restore the caller's text window and return"
  },
  "C6E5": {
   "unit": "game/textEngineC000",
   "routine": [
    "printPlayerPoints - prints one side's 16-bit point total as decimal.  The two point totals are indexed by the side that was scored against, so A = playerSide EOR 1 gives our score and A = playerSide gives the opponent's.",
    "In:  A = score index 0/1; sideScoreLo $92A2, sideScoreHi $92A4",
    "Out: digits printed at the cursor; X = the index, zp_18-zp_1F, zp_7E and the digit buffer $92DA clobbered",
    "Called from: drawStatusScoreLine ($C6D3, $C6DC)"
   ],
   "line": "use the side index as a table index"
  },
  "C6E6": {
   "unit": "game/textEngineC000",
   "line": "low byte of that side's point total"
  },
  "C6E9": {
   "unit": "game/textEngineC000",
   "line": "high byte (printDecimal takes A = low, Y = middle, zp_7E = high)"
  },
  "C6EC": {
   "unit": "game/textEngineC000",
   "line": "tail call the decimal printer; zp_7E is left 0 by it for the next call"
  },
  "C6EF": {
   "unit": "game/textEngineC000",
   "routine": [
    "resetMessageQueue - throws away every queued status message: the read index is set equal to the write index (empty ring), the fallback message and the display timer are cleared, and it falls into showNextQueuedMessage so the status line is refreshed immediately.",
    "In:  messageQueueHead $9251",
    "Out: messageQueueTail $9252 = head, persistentMessageId $92A1 = 0, messageTimer zp_72 = 0, status line redrawn",
    "Called from: a dozen places that change screen or game phase - $0AB5, $0B6A, $0CA0, $0CE1 (jmp), $1BE9, $28F4, $5258 in the main program and $6F8E, $7B0F, $7DD5, $7F9E, $820B in the $6F00 overlays"
   ],
   "line": "ring write index"
  },
  "C6F2": {
   "unit": "game/textEngineC000",
   "line": "make the read index equal to it: the queue is now empty"
  },
  "C6F5": {
   "unit": "game/textEngineC000",
   "line": "0"
  },
  "C6F7": {
   "unit": "game/textEngineC000",
   "line": "no fallback message either (0 = blank status line)"
  },
  "C6FA": {
   "unit": "game/textEngineC000",
   "line": "clear the display timer so the next message appears at once"
  },
  "C6FC": {
   "unit": "game/textEngineC000",
   "line": "pick whatever should be shown now (normally a blank line)"
  },
  "C6FF": {
   "unit": "game/textEngineC000",
   "routine": [
    "showStatusMessage - prints message id A on the status line immediately, bypassing the queue, and plays the sound the per-message table $0612 assigns to it ($80 = silent).",
    "In:  A = message id 0-$23; messageSoundTable $0612, messagePtrLoTable/HiTable $0500/$0524",
    "Out: shownMessageId zp_7B = the id, the text is drawn centred in the status line, a sound request may be queued",
    "Called from: the disk-error retry path ($082D), the end-of-game and link-status screens ($1AC4, $1B81, $1BC9, $5A34, $5A52), the modem driver ($EDFE) and handlePauseKey ($C728)"
   ],
   "line": "keep the id for the print below"
  },
  "C700": {
   "unit": "game/textEngineC000",
   "line": "playMessageSound indexes its table with Y"
  },
  "C701": {
   "unit": "game/textEngineC000",
   "line": "look the id up in $0612 and start that sound unless it is $80 = silent"
  },
  "C704": {
   "unit": "game/textEngineC000",
   "line": "get the message id back"
  },
  "C705": {
   "unit": "game/textEngineC000",
   "routine": [
    "showStatusMessageNoSound - the tail of showStatusMessage, also used as an entry point of its own when the message should be silent: records A as the message now on the status line and prints it.",
    "In:  A = message id",
    "Out: shownMessageId zp_7B = A, message printed",
    "Called from: by JMP from showWorkingMessage ($C858); fall-through from showStatusMessage"
   ],
   "line": "bit 7 clear = the status line is now busy with this id"
  },
  "C707": {
   "unit": "game/textEngineC000",
   "line": "look the id up in $0500/$0524 and draw it centred"
  },
  "C70A": {
   "unit": "game/textEngineC000",
   "routine": [
    "handlePauseKey - RUN/STOP pause handling, called once per frame from updateGameFrame.  It does nothing unless the battle is actually running (gamePhase zp_B1 = 0 and gameEndReason $0BA8 = 0).  In a solo or film game RUN/STOP simply toggles bit 0 of pauseState zp_BE; while paused the persistent message is $19 'TIMEOUT. RUN/STOP TO RESUME.', only the menu-level link poll is serviced, and two PLAs throw away this routine's return address so the rest of the frame update is skipped.  In a live modem game the first RUN/STOP shows 'WORKING...' and sends command $8C reason 3 (request pause); the opponent's answer sets zp_BE, after which this routine owns the frame: it saves and clears the screen, then either performs a voice pause or spins on serviceCommandExchange until zp_BE clears, sending $8C reason 4 (request resume) on every further RUN/STOP.  On release it redraws the battlefield and puts the sprites back.",
    "In:  gamePhase zp_B1, gameEndReason $0BA8, filmPlaybackMode $0B9B, pauseState zp_BE, spriteEnableShadow $9022, currentScreen $90FB, inputLockoutTimer $0B7D",
    "Out: zp_BE, persistentMessageId $92A1, commands $8C/3 and $8C/4 queued, screen cleared and redrawn, sprite mask restored through enableSprites; may return to its caller's caller",
    "Called from: updateGameFrame ($40BF and $413D)"
   ],
   "line": "zp_B1 is 0 only during the battle itself"
  },
  "C70C": {
   "unit": "game/textEngineC000",
   "line": "in setup, film or menu phases RUN/STOP is not the pause key"
  },
  "C70E": {
   "unit": "game/textEngineC000",
   "line": "$0BA8 is 0 only while a game is actually running"
  },
  "C711": {
   "unit": "game/textEngineC000",
   "line": "the game is already over or was never started - nothing to pause"
  },
  "C713": {
   "unit": "game/textEngineC000",
   "line": "$0B9B bit 7 = a game film is being replayed (also set for the solo trainer)"
  },
  "C716": {
   "unit": "game/textEngineC000",
   "line": "bit 7 clear = a live opponent, use the negotiated pause protocol"
  },
  "C718": {
   "unit": "game/textEngineC000",
   "block": [
    "Solo trainer / film playback: no opponent to negotiate with, so RUN/STOP just",
    "toggles bit 0 of pauseState and the frame update is skipped while it is set."
   ],
   "line": "was RUN/STOP pressed this frame? (Z = 1 if yes, key consumed)"
  },
  "C71B": {
   "unit": "game/textEngineC000",
   "line": "not pressed - just check whether we are still paused"
  },
  "C71D": {
   "unit": "game/textEngineC000",
   "line": "pause flags"
  },
  "C71F": {
   "unit": "game/textEngineC000",
   "line": "toggle bit 0 = our own pause request"
  },
  "C721": {
   "unit": "game/textEngineC000",
   "line": "store the new state"
  },
  "C723": {
   "unit": "game/textEngineC000",
   "line": "non-zero = we have just paused"
  },
  "C725": {
   "unit": "game/textEngineC000",
   "line": "A = 0: no fallback message any more"
  },
  "C728": {
   "unit": "game/textEngineC000",
   "line": "show message 0 (the chat line, silent) to wipe the TIMEOUT text off the status line"
  },
  "C72B": {
   "unit": "game/textEngineC000",
   "line": "message $19 = 'TIMEOUT. RUN/STOP TO RESUME.'"
  },
  "C72D": {
   "unit": "game/textEngineC000",
   "line": "make it the fallback message so it stays on screen"
  },
  "C730": {
   "unit": "game/textEngineC000",
   "line": "and queue it so it appears right now"
  },
  "C733": {
   "unit": "game/textEngineC000",
   "line": "are we paused?"
  },
  "C735": {
   "unit": "game/textEngineC000",
   "line": "no - let updateGameFrame carry on normally"
  },
  "C737": {
   "unit": "game/textEngineC000",
   "line": "paused: only the menu-level link poll keeps running"
  },
  "C73A": {
   "unit": "game/textEngineC000",
   "line": "discard this routine's return address..."
  },
  "C73B": {
   "unit": "game/textEngineC000",
   "line": "...so the RTS below returns into updateGameFrame's caller and the rest of the frame is skipped"
  },
  "C73C": {
   "unit": "game/textEngineC000",
   "line": "normal exit"
  },
  "C73D": {
   "unit": "game/textEngineC000",
   "block": [
    "Live modem game.  zp_BE is 0 until the pause has been agreed with the opponent,",
    "so the first RUN/STOP only sends the request; the command interpreter sets zp_BE",
    "when the answer arrives and the next call falls into the pause loop below."
   ],
   "line": "pause flags: bit 0 own pause, bit 1 opponent pause, bit 7 voice pause"
  },
  "C73F": {
   "unit": "game/textEngineC000",
   "line": "already in a pause - run the pause loop"
  },
  "C741": {
   "unit": "game/textEngineC000",
   "line": "was RUN/STOP pressed?"
  },
  "C744": {
   "unit": "game/textEngineC000",
   "line": "no - carry on with the frame"
  },
  "C746": {
   "unit": "game/textEngineC000",
   "line": "put 'WORKING...' on the status line while the request is in flight"
  },
  "C749": {
   "unit": "game/textEngineC000",
   "line": "reason 3 = request pause"
  },
  "C74B": {
   "unit": "game/textEngineC000",
   "line": "queue the 2-byte command $8C,3 for the opponent and return"
  },
  "C74E": {
   "unit": "game/textEngineC000",
   "line": "current sprite-enable mask"
  },
  "C751": {
   "unit": "game/textEngineC000",
   "line": "patch it into the 'lda #$FF' operand at $C79D so it can be restored on resume"
  },
  "C754": {
   "unit": "game/textEngineC000",
   "line": "0 battlefield, 1 missile screen, 2 drone screen, 3 console/menu"
  },
  "C757": {
   "unit": "game/textEngineC000",
   "line": "screen 3 is the console/menu"
  },
  "C759": {
   "unit": "game/textEngineC000",
   "line": "the menu screen keeps its windows"
  },
  "C75B": {
   "unit": "game/textEngineC000",
   "line": "save the caller's text window"
  },
  "C75E": {
   "unit": "game/textEngineC000",
   "line": "blank the windows of the current screen (this also zeroes $9022)"
  },
  "C761": {
   "unit": "game/textEngineC000",
   "line": "and restore the text window"
  },
  "C764": {
   "unit": "game/textEngineC000",
   "line": "pause flags again"
  },
  "C766": {
   "unit": "game/textEngineC000",
   "line": "bit 7 clear = an ordinary pause"
  },
  "C768": {
   "unit": "game/textEngineC000",
   "line": "bit 7 = voice pause: hang up, let the players talk, then dial back"
  },
  "C76B": {
   "unit": "game/textEngineC000",
   "line": "and go straight to the redraw"
  },
  "C76E": {
   "unit": "game/textEngineC000",
   "block": [
    "Pause spin loop: keep the link alive and watch for RUN/STOP until the pause",
    "flags clear (which happens when the command interpreter receives the opponent's",
    "resume).  serviceCommandExchange also keeps the status line and the IRQ fed."
   ],
   "line": "keep exchanging command packets with the opponent while paused"
  },
  "C771": {
   "unit": "game/textEngineC000",
   "line": "still paused?"
  },
  "C773": {
   "unit": "game/textEngineC000",
   "line": "no - both sides have resumed"
  },
  "C775": {
   "unit": "game/textEngineC000",
   "line": "keys are ignored while this 60-frame lockout runs"
  },
  "C778": {
   "unit": "game/textEngineC000",
   "line": "still locked out - keep spinning"
  },
  "C77A": {
   "unit": "game/textEngineC000",
   "line": "RUN/STOP pressed again?"
  },
  "C77D": {
   "unit": "game/textEngineC000",
   "line": "no - keep spinning"
  },
  "C77F": {
   "unit": "game/textEngineC000",
   "line": "pass the pause flags to the message helper"
  },
  "C781": {
   "unit": "game/textEngineC000",
   "line": "show 'WAITING FOR OPPONENT...' if the pause is ours and the link is real"
  },
  "C784": {
   "unit": "game/textEngineC000",
   "line": "reason 4 = request resume"
  },
  "C786": {
   "unit": "game/textEngineC000",
   "line": "queue command $8C,4"
  },
  "C789": {
   "unit": "game/textEngineC000",
   "line": "and go back to spinning until the opponent agrees"
  },
  "C78C": {
   "unit": "game/textEngineC000",
   "block": [
    "Resume: the battlefield screen was blanked above, so rebuild it."
   ],
   "line": "which screen are we on?"
  },
  "C78F": {
   "unit": "game/textEngineC000",
   "line": "only the battlefield map (screen 0) was cleared and needs rebuilding"
  },
  "C791": {
   "unit": "game/textEngineC000",
   "line": "save the text window around the redraw"
  },
  "C794": {
   "unit": "game/textEngineC000",
   "line": "redraw the 40x40 overview map in the left panel"
  },
  "C797": {
   "unit": "game/textEngineC000",
   "line": "redraw the 7x5 tactical viewport"
  },
  "C79A": {
   "unit": "game/textEngineC000",
   "line": "restore the text window"
  },
  "C79D": {
   "unit": "game/textEngineC000",
   "line": "operand patched at $C751 with the sprite-enable mask saved before the screen was cleared"
  },
  "C79F": {
   "unit": "game/textEngineC000",
   "line": "OR the sprites back into $9022"
  },
  "C7A2": {
   "unit": "game/textEngineC000",
   "line": "back to updateGameFrame"
  },
  "C7A3": {
   "unit": "game/textEngineC000",
   "routine": [
    "sendPauseCommand - queues the 2-byte network command $8C (pause/session control) with A as its reason byte.  Reason 3 = request pause, 4 = request resume; reason 2 (ready for a new game) and 5 (voice pause / hang up) are sent from elsewhere.",
    "In:  A = reason code",
    "Out: cmdParam0 zp_73 = A, two bytes appended to the outgoing command ring",
    "Called from: handlePauseKey ($C74B by JMP, $C786 by JSR)"
   ],
   "line": "the command's single argument byte"
  },
  "C7A5": {
   "unit": "game/textEngineC000",
   "line": "$8C = the pause / session-control command"
  },
  "C7A7": {
   "unit": "game/textEngineC000",
   "line": "append [$8C, reason] to the outgoing command ring and return"
  },
  "C7AA": {
   "unit": "game/textEngineC000",
   "routine": [
    "consumeRunStopKey - edge test for RUN/STOP.  If the last decoded key was $93 (RUN/STOP) it clears the key, arms a 60-frame input lockout, plays the click sound and returns A = 0 (Z = 1).  Otherwise it returns with the key code still in A and Z = 0.",
    "In:  lastTypedKey $9248",
    "Out: Z = 1 when RUN/STOP was pressed; $9248 = $FF, inputLockoutTimer $0B7D = $3C, sound 2 queued",
    "Called from: handlePauseKey only ($C718, $C741, $C77A)"
   ],
   "line": "last key code the IRQ decoded ($FF = none)"
  },
  "C7AD": {
   "unit": "game/textEngineC000",
   "line": "$93 = RUN/STOP in the game's own key table"
  },
  "C7AF": {
   "unit": "game/textEngineC000",
   "line": "some other key - return with Z = 0"
  },
  "C7B1": {
   "unit": "game/textEngineC000",
   "line": "$3C = 60 frames"
  },
  "C7B3": {
   "unit": "game/textEngineC000",
   "line": "ignore the keyboard and joystick for about a second"
  },
  "C7B6": {
   "unit": "game/textEngineC000",
   "line": "$FF = 'no key'"
  },
  "C7B8": {
   "unit": "game/textEngineC000",
   "line": "consume the key so the press is only acted on once"
  },
  "C7BB": {
   "unit": "game/textEngineC000",
   "line": "sound 2 = the key click"
  },
  "C7BD": {
   "unit": "game/textEngineC000",
   "line": "click (X and Y are preserved by playSound)"
  },
  "C7C0": {
   "unit": "game/textEngineC000",
   "line": "A = 0 so the caller's BNE falls through"
  },
  "C7C2": {
   "unit": "game/textEngineC000",
   "line": "Z = 1 means 'RUN/STOP was pressed'"
  },
  "C7C3": {
   "unit": "game/textEngineC000",
   "routine": [
    "showWaitingForOpponent - if this is a real modem game and the pause is our own (bit 0 of the flags passed in A), makes message $0B 'WAITING FOR OPPONENT...' the persistent status message and queues it.  In a solo game there is nobody to wait for, so it does nothing.",
    "In:  A = pauseState flags, isSoloTrainer $0BA5 (bit 7 set = solo trainer)",
    "Out: persistentMessageId $92A1 = $0B and the message queued, or nothing",
    "Called from: handlePauseKey ($C781) and the setup overlay ($7C00)"
   ],
   "line": "$0BA5 bit 7 = the solo trainer is the opponent"
  },
  "C7C6": {
   "unit": "game/textEngineC000",
   "line": "solo game - there is no remote player to wait for"
  },
  "C7C8": {
   "unit": "game/textEngineC000",
   "line": "bit 0 = we are the side that asked for the pause"
  },
  "C7CA": {
   "unit": "game/textEngineC000",
   "line": "the opponent paused us - do not claim to be waiting"
  },
  "C7CC": {
   "unit": "game/textEngineC000",
   "line": "message $0B = 'WAITING FOR OPPONENT...'"
  },
  "C7CE": {
   "unit": "game/textEngineC000",
   "line": "keep it on the line until something else replaces it"
  },
  "C7D1": {
   "unit": "game/textEngineC000",
   "line": "and queue it so it is shown now"
  },
  "C7D4": {
   "unit": "game/textEngineC000",
   "line": "shared exit of both 'nothing to say' paths"
  },
  "C7D5": {
   "unit": "game/textEngineC000",
   "routine": [
    "handleTextEntryKey - the one-key line editor shared by the in-game chat line and the SAVE GAME FILM name field.  It consumes at most one key event per call: RETURN ($8D) ends the line (bit 7 is set in the returned cursor position), INST/DEL ($88) backspaces, and printable codes $20-$5F are checked against the ASCII-to-glyph table before being stored in the 36-byte buffer at $9278, which is always re-terminated with $9E.  When the line is full the buffer either scrolls one character left (bit 7 of A) or the key is refused with a beep.",
    "In:  A = maximum length, bit 7 = 'scroll left when full'; Y = cursor position in $9278; newKeyEvent $90EA; the ASCII-to-glyph table $985E (= $987E - $20, indexed by the character code itself)",
    "Out: A = the accepted key code or $FF, Z = 1 when nothing was accepted; Y advanced (bit 7 set on RETURN); typedLineBuffer $9278 updated; $90EA and lastTypedKey $9248 = $FF; sound 2 (accepted) or 3 (refused); self-modifies the operands at $C813, $C817 and $C84F",
    "Called from: handleChatSend ($1387, inside the raster IRQ, A = $A3 = 35 characters with scrolling) and the film-name field of the setup overlay ($8029, A = $10 = 16 characters, no scrolling)",
    "Note: because this runs inside the IRQ for the chat line, its calls to playSound can corrupt the static X/Y save slots of a playSound call in progress on the main thread."
   ],
   "line": "keep the raw parameter (bit 7 = may scroll) in the LDX operand at $C816"
  },
  "C7D8": {
   "unit": "game/textEngineC000",
   "line": "strip the scroll flag"
  },
  "C7DA": {
   "unit": "game/textEngineC000",
   "line": "leaving the maximum length, which becomes the CPY operand at $C812"
  },
  "C7DD": {
   "unit": "game/textEngineC000",
   "line": "key event produced this frame ($FF = none)"
  },
  "C7E0": {
   "unit": "game/textEngineC000",
   "line": "$FF = nothing typed"
  },
  "C7E2": {
   "unit": "game/textEngineC000",
   "line": "no key - return with Z = 1 and A = $FF"
  },
  "C7E4": {
   "unit": "game/textEngineC000",
   "line": "remember the key code as this call's return value (operand of the LDA at $C84E)"
  },
  "C7E7": {
   "unit": "game/textEngineC000",
   "line": "$8D = RETURN"
  },
  "C7E9": {
   "unit": "game/textEngineC000",
   "line": "not RETURN - test the other special keys"
  },
  "C7EB": {
   "unit": "game/textEngineC000",
   "line": "cursor position"
  },
  "C7EC": {
   "unit": "game/textEngineC000",
   "line": "set bit 7 of the cursor position as the 'line finished' flag"
  },
  "C7EE": {
   "unit": "game/textEngineC000",
   "line": "bit 7 set tells the caller the line has been completed"
  },
  "C7EF": {
   "unit": "game/textEngineC000",
   "line": "click and finish without touching the buffer"
  },
  "C7F2": {
   "unit": "game/textEngineC000",
   "line": "$88 = INST/DEL"
  },
  "C7F4": {
   "unit": "game/textEngineC000",
   "line": "not a delete either - treat it as a printable character"
  },
  "C7F6": {
   "unit": "game/textEngineC000",
   "line": "already at the start of the line?"
  },
  "C7F8": {
   "unit": "game/textEngineC000",
   "line": "nothing to delete - refuse with a beep"
  },
  "C7FA": {
   "unit": "game/textEngineC000",
   "line": "step the cursor back over the last character"
  },
  "C7FB": {
   "unit": "game/textEngineC000",
   "line": "re-terminate the buffer there and click"
  },
  "C7FE": {
   "unit": "game/textEngineC000",
   "line": "key codes carry bit 7; strip it to get plain ASCII"
  },
  "C800": {
   "unit": "game/textEngineC000",
   "line": "keep the character code for the table lookup and the store"
  },
  "C801": {
   "unit": "game/textEngineC000",
   "line": "prepare the range test"
  },
  "C802": {
   "unit": "game/textEngineC000",
   "line": "codes below space are control keys"
  },
  "C804": {
   "unit": "game/textEngineC000",
   "line": "not printable - swallow it silently, no beep"
  },
  "C806": {
   "unit": "game/textEngineC000",
   "line": "$20..$5F is the printable range ($40 codes)"
  },
  "C808": {
   "unit": "game/textEngineC000",
   "line": "above 'Z' - refuse with a beep"
  },
  "C80A": {
   "unit": "game/textEngineC000",
   "line": "$985E,x = asciiToGlyphTable[code - $20]"
  },
  "C80D": {
   "unit": "game/textEngineC000",
   "line": "$3B is the 'no glyph' entry (backslash, caret and underscore)"
  },
  "C80F": {
   "unit": "game/textEngineC000",
   "line": "the font cannot draw it - refuse with a beep"
  },
  "C811": {
   "unit": "game/textEngineC000",
   "line": "get the character code back"
  },
  "C812": {
   "unit": "game/textEngineC000",
   "line": "operand = the maximum line length patched at $C7DA"
  },
  "C814": {
   "unit": "game/textEngineC000",
   "line": "still room in the line - just store it"
  },
  "C816": {
   "unit": "game/textEngineC000",
   "line": "operand = the caller's parameter patched at $C7D5; bit 7 = 'scroll when full'"
  },
  "C818": {
   "unit": "game/textEngineC000",
   "line": "no scrolling allowed - the line is full, beep"
  },
  "C81A": {
   "unit": "game/textEngineC000",
   "line": "keep the character while the buffer is shifted",
   "block": [
    "The line is full but scrolling is allowed (chat line): shift the whole 36-byte",
    "buffer one character to the left, then store the new character at the end."
   ]
  },
  "C81B": {
   "unit": "game/textEngineC000",
   "line": "start at the first byte of the buffer"
  },
  "C81D": {
   "unit": "game/textEngineC000",
   "line": "read one byte further on ($9279 = buffer + 1)"
  },
  "C820": {
   "unit": "game/textEngineC000",
   "line": "and write it one place to the left"
  },
  "C823": {
   "unit": "game/textEngineC000",
   "line": "next byte"
  },
  "C824": {
   "unit": "game/textEngineC000",
   "line": "$24 = 36, the length of the line buffer"
  },
  "C826": {
   "unit": "game/textEngineC000",
   "line": "keep shifting; note the last pass reads $929C (chatState) one byte past the buffer, but that byte is overwritten by the terminator below"
  },
  "C828": {
   "unit": "game/textEngineC000",
   "line": "the whole line moved left, so the cursor did too"
  },
  "C829": {
   "unit": "game/textEngineC000",
   "line": "recover the character"
  },
  "C82A": {
   "unit": "game/textEngineC000",
   "line": "store it at the cursor"
  },
  "C82D": {
   "unit": "game/textEngineC000",
   "line": "and advance the cursor"
  },
  "C82E": {
   "unit": "game/textEngineC000",
   "line": "always taken (Y can never wrap to 0 here)"
  },
  "C830": {
   "unit": "game/textEngineC000",
   "line": "sound 3 = the refusal beep"
  },
  "C832": {
   "unit": "game/textEngineC000",
   "line": "beep"
  },
  "C835": {
   "unit": "game/textEngineC000",
   "line": "$FF = 'no key accepted'"
  },
  "C837": {
   "unit": "game/textEngineC000",
   "line": "overwrite the return value stored at $C7E4"
  },
  "C83A": {
   "unit": "game/textEngineC000",
   "line": "always taken"
  },
  "C83C": {
   "unit": "game/textEngineC000",
   "line": "$9E = the terminator that prints nothing"
  },
  "C83E": {
   "unit": "game/textEngineC000",
   "line": "terminate the line at the cursor"
  },
  "C841": {
   "unit": "game/textEngineC000",
   "line": "sound 2 = the key click"
  },
  "C843": {
   "unit": "game/textEngineC000",
   "line": "click"
  },
  "C846": {
   "unit": "game/textEngineC000",
   "line": "$FF = 'no key'"
  },
  "C848": {
   "unit": "game/textEngineC000",
   "line": "consume the key event..."
  },
  "C84B": {
   "unit": "game/textEngineC000",
   "line": "...and the decoded key, so no other handler sees it"
  },
  "C84E": {
   "unit": "game/textEngineC000",
   "line": "operand = the accepted key code, or $FF when nothing was taken"
  },
  "C850": {
   "unit": "game/textEngineC000",
   "line": "Z = 1 tells the caller that nothing was accepted"
  },
  "C852": {
   "unit": "game/textEngineC000",
   "line": "A = the key code (or $FF), Y = the new cursor position"
  },
  "C853": {
   "unit": "game/textEngineC000",
   "routine": [
    "showWorkingMessage - puts message 1 'WORKING...' on the status line at once and makes it the persistent message, so it stays there until something clears it.  Message 1's sound entry is $80, i.e. silent.",
    "In:  none",
    "Out: persistentMessageId $92A1 = 1, shownMessageId zp_7B = 1, message drawn",
    "Called from: before every slow operation - $10CE (jmp) and $1AA4 in the main program, $6F2D, $79A4, $79EF, $7B1D in the setup overlay, and handlePauseKey ($C746)"
   ],
   "line": "message id 1 = 'WORKING...' ($056C)"
  },
  "C855": {
   "unit": "game/textEngineC000",
   "line": "keep it on the line until the queue or a screen change replaces it"
  },
  "C858": {
   "unit": "game/textEngineC000",
   "line": "print it now, without a sound lookup"
  },
  "C85B": {
   "unit": "game/textEngineC000",
   "routine": [
    "promptPressButtonWhenReady - shows 'PRESS BUTTON WHEN READY.' centred on the status line and busy-waits for a fresh fire-button click, stirring the 24-bit game RNG on every pass.  Because the number of passes depends on the player, this is also what randomises the map seed before a game.",
    "In:  the fire button (through checkFirePressed), RNG state $57-$59",
    "Out: returns after a click; the RNG has been advanced an unpredictable number of times and the status line is blanked",
    "Called from: waitForGameDiskInserted ($10E8)"
   ],
   "line": "low byte of 'PRESS BUTTON WHEN READY.' at $CBBE"
  },
  "C85D": {
   "unit": "game/textEngineC000",
   "line": "high byte of $CBBE"
  },
  "C85F": {
   "unit": "game/textEngineC000",
   "line": "draw it centred in the 36-column status line"
  },
  "C862": {
   "unit": "game/textEngineC000",
   "line": "advance the pseudo-random shift register - the player's reaction time is the entropy"
  },
  "C865": {
   "unit": "game/textEngineC000",
   "line": "Z = 1 as soon as a fresh press is seen (it also plays click sound 1)"
  },
  "C868": {
   "unit": "game/textEngineC000",
   "line": "no click yet - keep stirring"
  },
  "C86A": {
   "unit": "game/textEngineC000",
   "line": "blank the status line and return"
  },
  "C86D": {
   "unit": "game/textEngineC000",
   "routine": [
    "exitIfGameEnded - if the end-of-game display has been up long enough this routine never returns: it transfers control to the main menu instead.  gameEndCountdown $92C9 is set to a start value when the game ends and counted UP by the IRQ until it reaches $80, so bit 7 set means 'the result has been shown, get out'.",
    "In:  gameEndCountdown $92C9, gamePhase zp_B1",
    "Out: returns normally, or jumps to the menu (which rebuilds the stack, so the caller is abandoned)",
    "Called from: the top of updateGameFrame ($40B2) and the command-exchange loop ($4E41)"
   ],
   "line": "$92C9: 0 = no game over pending, otherwise it counts up to $80"
  },
  "C870": {
   "unit": "game/textEngineC000",
   "line": "still counting (or nothing pending) - just go back"
  },
  "C872": {
   "unit": "game/textEngineC000",
   "line": "zp_B1 is 0 only during the battle"
  },
  "C874": {
   "unit": "game/textEngineC000",
   "line": "in the battle the session has to be tidied up first"
  },
  "C876": {
   "unit": "game/textEngineC000",
   "line": "setup, film or menu phase: reset the stack and show the main menu"
  },
  "C879": {
   "unit": "game/textEngineC000",
   "line": "battle: close the film, free the link and then go to the menu"
  },
  "C87C": {
   "unit": "game/textEngineC000",
   "line": "the game is still running"
  },
  "C87D": {
   "unit": "game/textEngineC000",
   "block": [
    "soundClassTable - one class/priority byte per sound id 0-28, read by requestSound.",
    "  $00/$01/$02 = ordinary request for SID voice 0/1/2: only accepted when that request",
    "                slot is free and the voice is silent or busy with another ordinary sound.",
    "  $80/$81/$82 = force the sound onto voice 0/1/2, overriding whatever plays there.",
    "  $FF         = force it into request slot 3, the whole-chip slot used by the three-voice",
    "                fanfares (ids $0D, $0E, $19).",
    "requestSound only uses the low two bits as the slot number, so $FF selects slot 3.",
    "Entry 0 is never used as a class: requestSound(0) is the 'silence everything' request."
   ],
   "line": "ids 0-7: 0 unused (silence request), 1-5 ordinary voice 0, 6 whole-chip, 7 forced voice 0"
  },
  "C885": {
   "unit": "game/textEngineC000",
   "line": "ids 8-$0F: 8 forced voice 1, 9-$0B ordinary voice 1, $0C forced voice 0, $0D/$0E whole-chip fanfares, $0F forced voice 2"
  },
  "C88D": {
   "unit": "game/textEngineC000",
   "line": "ids $10-$17: $10-$13 forced voice 2, $14 ordinary voice 2 (comcen engine), $15 whole-chip, $16/$17 forced voice 2"
  },
  "C895": {
   "unit": "game/textEngineC000",
   "line": "ids $18-$1C: $18 forced voice 2, $19 whole-chip fanfare, $1A forced voice 0, $1B forced voice 1, $1C forced voice 2 (comcen engine stop)"
  },
  "C89A": {
   "unit": "game/textEngineC000",
   "routine": [
    "playSound - the public sound entry used by about fifty call sites: queues sound id A and gives X and Y back to the caller.  X and Y are preserved in two self-modified operands rather than on the stack, so this entry is NOT re-entrant - the IRQ-side callers at $1370 and $140E deliberately call requestSound directly.",
    "In:  A = sound id 0-28",
    "Out: the sound is queued in one of the four request slots; X and Y unchanged, A clobbered",
    "Called from: everywhere - see XREF; within this file $C55C, $C679 (jmp), $C7BD, $C832, $C843, $C910 (jmp), $C920 (jmp)"
   ],
   "line": "save Y in the operand of the LDY at $C8A3"
  },
  "C89D": {
   "unit": "game/textEngineC000",
   "line": "save X in the operand of the LDX at $C8A5"
  },
  "C8A0": {
   "unit": "game/textEngineC000",
   "line": "queue the request"
  },
  "C8A3": {
   "unit": "game/textEngineC000",
   "line": "operand patched at $C89A: the caller's Y"
  },
  "C8A5": {
   "unit": "game/textEngineC000",
   "line": "operand patched at $C89D: the caller's X"
  },
  "C8A7": {
   "unit": "game/textEngineC000",
   "line": "only A is clobbered"
  },
  "C8A8": {
   "unit": "game/textEngineC000",
   "routine": [
    "requestSound - the core of the sound request queue.  A = 0 is the 'silence everything' request: slots 0-2 become $FF (empty) and slot 3 gets id 0, so the raster IRQ dequeues sound 0 and stops the chip.  Otherwise the class byte from soundClassTable decides where the request goes: a negative class forces it into slot (class AND 3), while a positive class is a plain voice number that is only taken when that request slot is free and the voice is either silent or playing another low-priority sound.  Ids of $1D and above are dropped.",
    "In:  A = sound id 0-28; soundClassTable $C87D, the four request slots $92E7-$92EA, sndVoiceSoundId $67C8",
    "Out: one of $92E7-$92EA holds the id (or all four are reset); self-modifies the operand at $C8DC; A/X/Y clobbered",
    "Called from: playSound ($C8A0) and directly from the two IRQ-side call sites $1370 (jmp) and $140E"
   ],
   "line": "Y = the sound id for the table lookups below"
  },
  "C8A9": {
   "unit": "game/textEngineC000",
   "line": "id 0 is the request to silence everything"
  },
  "C8AB": {
   "unit": "game/textEngineC000",
   "line": "slot 3 (the whole-chip slot) receives id 0 = 'stop all voices'"
  },
  "C8AE": {
   "unit": "game/textEngineC000",
   "line": "Y = $FF"
  },
  "C8AF": {
   "unit": "game/textEngineC000",
   "line": "voice 2 request slot: empty"
  },
  "C8B2": {
   "unit": "game/textEngineC000",
   "line": "voice 1 request slot: empty"
  },
  "C8B5": {
   "unit": "game/textEngineC000",
   "line": "voice 0 request slot: empty"
  },
  "C8B8": {
   "unit": "game/textEngineC000",
   "line": "the IRQ will pick up the id 0 in slot 3 on its next pass"
  },
  "C8B9": {
   "unit": "game/textEngineC000",
   "line": "class/priority byte of this sound (ids >= $1D read past the table, but the range test at $C8E1 drops them before anything is stored)"
  },
  "C8BC": {
   "unit": "game/textEngineC000",
   "line": "patch it into the LDA operand at $C8DB - the slot number is derived from it below"
  },
  "C8BF": {
   "unit": "game/textEngineC000",
   "line": "negative class = forced, no priority test needed"
  },
  "C8C1": {
   "unit": "game/textEngineC000",
   "line": "X = the voice number 0-2"
  },
  "C8C2": {
   "unit": "game/textEngineC000",
   "line": "is a request already waiting for that voice?"
  },
  "C8C5": {
   "unit": "game/textEngineC000",
   "line": "$FF = free, so only the voice itself has to be checked"
  },
  "C8C7": {
   "unit": "game/textEngineC000",
   "line": "X = the sound id already queued there"
  },
  "C8C8": {
   "unit": "game/textEngineC000",
   "line": "its class"
  },
  "C8CB": {
   "unit": "game/textEngineC000",
   "line": "the queued sound is a forced one - do not displace it"
  },
  "C8CD": {
   "unit": "game/textEngineC000",
   "line": "restore X = our own voice number"
  },
  "C8D0": {
   "unit": "game/textEngineC000",
   "line": "$67C8+voice = the id the SID voice is playing right now (0 = silent)"
  },
  "C8D3": {
   "unit": "game/textEngineC000",
   "line": "silent - the request can be taken"
  },
  "C8D5": {
   "unit": "game/textEngineC000",
   "line": "X = the sound currently playing"
  },
  "C8D6": {
   "unit": "game/textEngineC000",
   "line": "its class"
  },
  "C8D9": {
   "unit": "game/textEngineC000",
   "line": "a forced sound is playing - do not interrupt it"
  },
  "C8DB": {
   "unit": "game/textEngineC000",
   "line": "operand = the class byte patched at $C8BC"
  },
  "C8DD": {
   "unit": "game/textEngineC000",
   "line": "the low two bits are the request slot: 0-2 = SID voices, 3 = the whole-chip slot ($FF AND 3)"
  },
  "C8DF": {
   "unit": "game/textEngineC000",
   "line": "X = slot"
  },
  "C8E0": {
   "unit": "game/textEngineC000",
   "line": "A = the sound id again"
  },
  "C8E1": {
   "unit": "game/textEngineC000",
   "line": "29 sounds are defined (ids 0-$1C)"
  },
  "C8E3": {
   "unit": "game/textEngineC000",
   "line": "out of range - drop it"
  },
  "C8E5": {
   "unit": "game/textEngineC000",
   "line": "queue the id; the raster IRQ dequeues it with dequeueSoundRequest"
  },
  "C8E8": {
   "unit": "game/textEngineC000",
   "line": "shared exit: either the request was queued or it was dropped"
  },
  "C8E9": {
   "unit": "game/textEngineC000",
   "routine": [
    "dequeueSoundRequest - takes one pending sound request out of the four slots, scanning from slot 3 (the whole-chip slot, highest priority) down to slot 0.  The first non-empty slot is emptied and its id returned in X; X = $FF when all four are empty.",
    "In:  the four request slots $92E7-$92EA ($FF = empty)",
    "Out: X = the sound id or $FF, that slot reset to $FF; A/Y clobbered",
    "Called from: the raster IRQ ($1283), which hands X straight to startSound ($6728)"
   ],
   "line": "start at slot 3, the whole-chip slot"
  },
  "C8EB": {
   "unit": "game/textEngineC000",
   "line": "id waiting in this slot"
  },
  "C8EE": {
   "unit": "game/textEngineC000",
   "line": "$FF = empty, try the next slot down"
  },
  "C8F0": {
   "unit": "game/textEngineC000",
   "line": "$FF = empty"
  },
  "C8F2": {
   "unit": "game/textEngineC000",
   "line": "take the request out of the slot"
  },
  "C8F5": {
   "unit": "game/textEngineC000",
   "line": "X = the id to start"
  },
  "C8F6": {
   "unit": "game/textEngineC000",
   "line": "next slot down (2, 1, 0)"
  },
  "C8F7": {
   "unit": "game/textEngineC000",
   "line": "keep scanning while the index is still non-negative"
  },
  "C8F9": {
   "unit": "game/textEngineC000",
   "line": "X = $FF: nothing to start this frame"
  },
  "C8FA": {
   "unit": "game/textEngineC000",
   "routine": [
    "updateComcenMoveSound - keeps the engine sound of the local player's command centre in step with its movement.  It only acts during a live battle.  When the comcen is still moving and SID voice 2 is silent it starts the engine loop (sound $14); when the comcen has arrived and voice 2 is still running the engine ($14) or its variant ($16) it plays the engine-stop sound ($1C).",
    "In:  gamePhase zp_B1, filmPlaybackMode $0B9B, comcenUnit $9236, unitDisplayFlagsTable $FB54 (bit 7 = the unit has reached its destination), sndVoiceSoundId+2 $67CA (the sound on SID voice 2)",
    "Out: sound $14 or $1C requested through playSound",
    "Called from: processUnitStep ($42A0), i.e. once per unit movement step"
   ],
   "line": "zp_B1 is 0 only during the battle"
  },
  "C8FC": {
   "unit": "game/textEngineC000",
   "line": "$0B9B is 0 only in a live game (not film playback, not solo)"
  },
  "C8FF": {
   "unit": "game/textEngineC000",
   "line": "any other state - stay quiet"
  },
  "C901": {
   "unit": "game/textEngineC000",
   "line": "unit index of our own comcen (49 for side 0, 99 for side 1)"
  },
  "C904": {
   "unit": "game/textEngineC000",
   "line": "its UI flag byte; bit 7 = the unit is standing on its destination"
  },
  "C907": {
   "unit": "game/textEngineC000",
   "line": "bit 7 set = the comcen has stopped"
  },
  "C909": {
   "unit": "game/textEngineC000",
   "line": "the sound SID voice 2 is playing (0 = silent)"
  },
  "C90C": {
   "unit": "game/textEngineC000",
   "line": "voice 2 is busy - do not start the engine over it"
  },
  "C90E": {
   "unit": "game/textEngineC000",
   "line": "sound $14 = the comcen engine loop"
  },
  "C910": {
   "unit": "game/textEngineC000",
   "line": "start it (its class $02 means it yields to forced sounds)"
  },
  "C913": {
   "unit": "game/textEngineC000",
   "line": "the sound on voice 2"
  },
  "C916": {
   "unit": "game/textEngineC000",
   "line": "$14 = the engine loop"
  },
  "C918": {
   "unit": "game/textEngineC000",
   "line": "yes - stop it"
  },
  "C91A": {
   "unit": "game/textEngineC000",
   "line": "$16 = the other comcen movement sound"
  },
  "C91C": {
   "unit": "game/textEngineC000",
   "line": "voice 2 is playing something else - leave it alone"
  },
  "C91E": {
   "unit": "game/textEngineC000",
   "line": "sound $1C = the engine-stop sound (forced onto voice 2)"
  },
  "C920": {
   "unit": "game/textEngineC000",
   "line": "play it"
  },
  "C923": {
   "unit": "game/textEngineC000",
   "line": "nothing to do: wrong game phase, or voice 2 is busy with another sound"
  },
  "C924": {
   "unit": "game/textEngineC000",
   "routine": [
    "setMessageSlot - installs a string address into the run-time message pointer tables, so a message id can be re-pointed at different text.  Used for the dynamic slots $1E, $20 and $21.",
    "In:  X = message id, A = string low byte, Y = string high byte",
    "Out: messagePtrLoTable $0500[X] = A, messagePtrHiTable $0524[X] = Y; A = the high byte on exit",
    "Called from: the start-up code ($0B1D, $0B26, $0C8E, $0C97), the setup overlay ($7BAC, $81C2, $821C, $86B5, $8724) and the modem driver tail ($EED6, $EEFF)"
   ],
   "line": "low byte of the string address into slot X"
  },
  "C927": {
   "unit": "game/textEngineC000",
   "line": "high byte"
  },
  "C928": {
   "unit": "game/textEngineC000",
   "line": "into the parallel table $24 bytes further on"
  },
  "C92B": {
   "unit": "game/textEngineC000",
   "line": "A = the high byte, which some callers rely on"
  },
  "C92C": {
   "unit": "game/textEngineC000",
   "block": [
    "-------------------------------------------------------------------",
    "The game's vocabulary: $C92C-$CCFF is one pool of text, most of it in",
    "fixed 8-byte-stride tables that printTableEntry ($C100) indexes with A/Y",
    "= base address and X = entry number, the rest bit-7 terminated strings",
    "printed in sequence with printString ($C133) / printStringAtPointer ($C137).",
    "Several tables are deliberately truncated because the next block starts",
    "immediately - see the notes on each one.",
    "-------------------------------------------------------------------",
    "groupNameTable - seven 8-byte group names, index = the group id in bits 2-4",
    "of the unit's UI flag byte $FB54: 0 ALPHA, 1 BAKER, 2 CHARLIE, 3 DELTA,",
    "4 EASY, 5 FOXTROT, 6 GROVER.  Read by printUnitStatusLines ($23B1),",
    "runJoinGroupMenu ($6162) and runGroupOptionsMenu ($634E).  Index 7 does not",
    "exist and would run into unitTypeNameTable below, printing GRUNT."
   ]
  },
  "C964": {
   "unit": "game/textEngineC000",
   "block": [
    "unitTypeNameTable - five 8-byte unit type names indexed by the unit type in",
    "bits 0-2 of unitTypeTable $F76C: 0 GRUNT, 1 RIDER, 2 BOOMER, 3 SPY, 4 COMCEN.",
    "Each name is right-aligned inside its 8-byte slot with leading spaces, so no",
    "padding is needed at the print site (showUnitInfoPanel $22C9)."
   ]
  },
  "C98C": {
   "unit": "game/textEngineC000",
   "block": [
    "unitStatusNameTable - nine 8-byte status words used by the two status lines of",
    "the unit info panel: 0 READY, 1 RELOAD, 2 FIRED, 3 ' -   ', 4 REPAIR, 5 MOVE,",
    "6 PINNED, 7 STUN'D, 8 DIGGIN.  The WEAPON: line indexes the table from $C98C",
    "(entries 0-2 plus STUN'D), the ACTION: line from entry 3 at $C9A4, so its",
    "indices 0-5 read ' -   '/REPAIR/MOVE/PINNED/STUN'D/DIGGIN.  The last entry is",
    "only 6 bytes long because unitOptionMenuText starts right after it."
   ]
  },
  "C9D2": {
   "unit": "game/textEngineC000",
   "block": [
    "unitOptionMenuText - the twelve lines of the UNIT OPTIONS and GROUP OPTIONS",
    "menus, each terminated with $8D (carriage return with bit 7 set), so the menu",
    "printers at $5FCA and $6387 just call printStringAtPointer repeatedly and the",
    "pointer walks from one line to the next: FORMATION, MOVE TO GOAL, CLOAKING ON,",
    "NO CLOAKING, CLEAR TARGET, SET TARGET, MEMBERSHIP, JOIN GROUP, DIRECT UNIT,",
    "SELF DESTRCT, SET BLITZ, BLITZ OFF."
   ]
  },
  "CA8C": {
   "unit": "game/textEngineC000",
   "block": [
    "formationMenuText - the formation sub-menu as a single string with embedded",
    "$0D line breaks, so one printString call draws all five rows:",
    "SWEEP LEFT / SWEEP RIGHT / REVERSE / CUSTOM / (EXIT)."
   ]
  },
  "CAB9": {
   "unit": "game/textEngineC000",
   "block": [
    "consoleMiscMenuText - six $8D-terminated lines printed in sequence by the",
    "command console: ' ENERGY', ' BATTLE', ' RADAR', '  DRONE', '  DIG OUT',",
    "'DIG IN'.  The leading spaces do the centring inside the narrow console column."
   ]
  },
  "CAE9": {
   "unit": "game/textEngineC000",
   "block": [
    "unitInfoLabelText - the label column of the unit info panel as one string with",
    "$0D line breaks and a bit-7 terminator: 'GROUP:', three blank lines (where the",
    "unit picture sprites sit), 'ENERGY:', 'WEAPON:', 'ACTION:'.  Printed by",
    "showUnitInfoPanel ($22E0); the values are printed alongside by",
    "printUnitStatusLines ($2384)."
   ]
  },
  "CB0A": {
   "unit": "game/textEngineC000",
   "block": [
    "consoleHeadingText - two $8D-terminated headings drawn above the console menus:",
    "'    UNIT' ($CB0A) and '   OPTIONS' followed by a blank line ($CB13)."
   ]
  },
  "CB1F": {
   "unit": "game/textEngineC000",
   "block": [
    "uiPromptStrings - a pool of short bit-7 terminated words and prompts shared by",
    "the unit/group menus, the three command-console tabs and the info panel.  They",
    "are addressed individually, not as a table: GROUP $CB1F, 'WHICH GROUP:' $CB25,",
    "'  TOO FAR!' $CB31, ADDED $CB3B, REMOVED $CB40, FINISHED $CB47, SETTING $CB4F,",
    "DESTINATION $CB56, TARGET $CB61, 'FUEL:' $CB67, MISC $CB6C, STATS $CB70,",
    "REPAIR $CB75, CLOAKED $CB7B, BLITZ $CB82, 'DUG IN' $CB87, 'NOT IN GROUP' $CB8D,",
    "RE-POSITIONED $CB99, 'NEW POSITION' $CBA6, ' EXIT ' $CBB2, ENERGY $CBB8,",
    "'PRESS BUTTON WHEN READY.' $CBBE, 'REPAIR/WHICH:' $CBD6 and",
    "'ATTEMPTING REPAIR...' $CBE5.  MISC/STATS/REPAIR are the three console tab",
    "labels drawn around $1655-$1675."
   ]
  },
  "CBE5": {
   "unit": "game/textEngineC000",
   "block": [
    "'ATTEMPTING REPAIR...' - shown by the REPAIR tab while it tries to un-stun the",
    "comcen ($17DA).  NOTE: the two labels inside this string, at $CBED and $CBF2,",
    "are spurious.  They come from XREF entries 'call:C3D0' and 'call:C3D9', which",
    "belong to the boot loader that used to occupy $C000-$C3FF; in this overlay",
    "$C3D0 and $C3D9 are operand bytes of LDY #$00 and LDX #$24 inside",
    "printOnMessageLine.  Nothing in the running game calls $CBED or $CBF2 - they",
    "are the middle of a string, not code."
   ]
  },
  "CBF9": {
   "unit": "game/textEngineC000",
   "block": [
    "gameHeaderText - the fragments of the play/scenario header on the console:",
    "' PLAY' ($CBF9), '1ST' ($CBFE) and '2ND' ($CC01) - the last two are 3-byte",
    "entries selected as a pair - and a $0D-wrapped '  SCENARIO' ($CC04)."
   ]
  },
  "CC11": {
   "unit": "game/textEngineC000",
   "block": [
    "rulesNameTable - two entries on the usual 8-byte stride, selected with",
    "printTableEntry at $15C9: 0 STANDARD, 1 CUSTOM.  The CUSTOM entry is only",
    "6 bytes long because mainMenuItemText starts immediately after it, which is",
    "safe because it is the last entry."
   ]
  },
  "CC1F": {
   "unit": "game/textEngineC000",
   "block": [
    "mainMenuItemText - the main menu, printed line by line: 'COMPETE WITH MODEM",
    "OPPONENT', 'PRACTICE WITH SOLO TRAINER', 'WATCH GAME FILM' and 'SAVE GAME FILM'",
    "are one $0D-separated run ending in $8D at $CC74, then 'LOAD GAME FILM' ($CC75)",
    "and 'DISCONNECT/SET VOICE' ($CC84) follow as separate $8D-terminated lines."
   ]
  },
  "CC99": {
   "unit": "game/textEngineC000",
   "block": [
    "inGameMenuText - ENEMY ($CC99) and MISSILES ($CC9E) are panel captions,",
    "'ABORT GAME' ($CCA6) and 'VOICE PAUSE' ($CCB1) are the two items of the in-game",
    "MISC options menu.  The modem driver reads this block too ($ED6F)."
   ]
  },
  "CCBD": {
   "unit": "game/textEngineC000",
   "block": [
    "strGameTypeLabel - 'GAME TYPE:' printed at $15C5 just before the scenario name."
   ]
  },
  "CCC8": {
   "unit": "game/textEngineC000",
   "block": [
    "scenarioNameTable - seven 8-byte scenario names indexed by bits 0-2 of the game",
    "type byte $0BA3 and printed with printTableEntry at $15D9: 0 SCRIMAGE,",
    "1 QB SNEAK, 2 THE BOMB, 3 FACE-OFF, 4 SLUGGERS, 5 FULL WAR, 6 DEFENDER."
   ]
  },
  "CD00": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureHiTable / unitPictureLoTable - the addresses of the five unit-picture",
    "RLE blocks, split into a high-byte table at $CD00 and a low-byte table at $CD05,",
    "both indexed by unit type 0-4.  They resolve to $CD52 GRUNT, $CDD3 RIDER,",
    "$CE3B BOOMER, $CECD SPY, $CF3A COMCEN.",
    "$CD00 doubles as an inert build hook: the start-up code at $0C0D tests whether",
    "it holds $4C (a JMP opcode) and would turn the VIC IRQ off and jump there.  In",
    "the shipped overlay it holds $CD, so the hook never fires; showUnitInfoPanel",
    "($22EA) only tests that $CD00 is negative, meaning 'unit pictures are loaded'."
   ],
   "line": "high bytes for types 0-4: $CD52, $CDD3, $CE3B, $CECD, $CF3A"
  },
  "CD05": {
   "unit": "game/textEngineC000",
   "line": "low bytes of the same five addresses"
  },
  "CD0A": {
   "unit": "game/textEngineC000",
   "routine": [
    "showUnitPictureSprites - draws the picture of one unit type in the info panel.  It unpacks that type's RLE block into the scratch page $0200-$03FF, copies the first 192 bytes (three 24x21 sprite shapes) to $8B40 - the shape area addressed by sprite pointers $2D/$2E/$2F, i.e. sprites 5, 6 and 7 - and then positions and enables those three sprites side by side across the panel.",
    "In:  X = unit type 0-4 (GRUNT/RIDER/BOOMER/SPY/COMCEN)",
    "Out: $0200-$03FF overwritten with the unpacked block, $8B40-$8BFF = the three sprite frames, sprite shadows $900D-$900F (colour), $9015-$9017 (X), $901D-$901F (Y), $9020 (X MSBs) and $9022 (enable) updated; zp_B2/zp_B3/zp_1A/zp_1C clobbered by the unpacker",
    "Called from: showUnitInfoPanel ($22F1), whose LDX operand at $22EF is patched with the unit type"
   ],
   "line": "low byte of this type's RLE block"
  },
  "CD0D": {
   "unit": "game/textEngineC000",
   "line": "high byte of it"
  },
  "CD10": {
   "unit": "game/textEngineC000",
   "line": "expand the run-length stream into $0200-$03FF (512 bytes)"
  },
  "CD13": {
   "unit": "game/textEngineC000",
   "line": "copy from the start of the unpacked page"
  },
  "CD15": {
   "unit": "game/textEngineC000",
   "block": [
    "Copy the first three sprite frames (3 x 64 bytes) out of the unpacked page",
    "into the shape area of sprites 5, 6 and 7; the remaining 320 unpacked bytes",
    "are padding and are ignored."
   ],
   "line": "unpacked byte (the RLE destination is the contour-rule page $0200)"
  },
  "CD18": {
   "unit": "game/textEngineC000",
   "line": "$8B40 = $8000 + $2D*64, the shape block sprite pointer $2D selects"
  },
  "CD1B": {
   "unit": "game/textEngineC000",
   "line": "next byte"
  },
  "CD1C": {
   "unit": "game/textEngineC000",
   "line": "$C0 = 192 = three 64-byte sprite shapes"
  },
  "CD1E": {
   "unit": "game/textEngineC000",
   "line": "keep copying"
  },
  "CD20": {
   "unit": "game/textEngineC000",
   "line": "VIC colour 1 = white"
  },
  "CD22": {
   "unit": "game/textEngineC000",
   "line": "sprite 5 colour"
  },
  "CD25": {
   "unit": "game/textEngineC000",
   "line": "sprite 6 colour"
  },
  "CD28": {
   "unit": "game/textEngineC000",
   "line": "sprite 7 colour"
  },
  "CD2B": {
   "unit": "game/textEngineC000",
   "line": "X = 236: the left-hand picture, still below 256 so its MSB stays clear"
  },
  "CD2D": {
   "unit": "game/textEngineC000",
   "line": "sprite 5 X position"
  },
  "CD30": {
   "unit": "game/textEngineC000",
   "line": "X = 268 ($10C): low byte $0C with the MSB set below"
  },
  "CD32": {
   "unit": "game/textEngineC000",
   "line": "sprite 6 X position"
  },
  "CD35": {
   "unit": "game/textEngineC000",
   "line": "X = 300 ($12C): low byte $2C with the MSB set below"
  },
  "CD37": {
   "unit": "game/textEngineC000",
   "line": "sprite 7 X position"
  },
  "CD3A": {
   "unit": "game/textEngineC000",
   "line": "Y = 162, the picture row of the info panel"
  },
  "CD3C": {
   "unit": "game/textEngineC000",
   "line": "sprite 5 Y position"
  },
  "CD3F": {
   "unit": "game/textEngineC000",
   "line": "sprite 6 Y position"
  },
  "CD42": {
   "unit": "game/textEngineC000",
   "line": "sprite 7 Y position"
  },
  "CD45": {
   "unit": "game/textEngineC000",
   "line": "shadow of VIC_SPR_HI_X $D010"
  },
  "CD48": {
   "unit": "game/textEngineC000",
   "line": "bits 6 and 7 = the X MSBs of sprites 6 and 7"
  },
  "CD4A": {
   "unit": "game/textEngineC000",
   "line": "store it back"
  },
  "CD4D": {
   "unit": "game/textEngineC000",
   "line": "$E0 = sprites 5, 6 and 7"
  },
  "CD4F": {
   "unit": "game/textEngineC000",
   "line": "OR them into the sprite-enable shadow $9022 and return"
  },
  "CD52": {
   "unit": "game/textEngineC000",
   "block": [
    "-------------------------------------------------------------------",
    "The five unit pictures, in unit-type order, as run-length coded blocks for",
    "unpackRleTo0200 ($67CB).  The first byte of a block is its escape marker; in",
    "the stream 'marker, count, value' emits value count times and every other byte",
    "is a literal.  Each block unpacks to 512 bytes but only the first 192 matter:",
    "three 24x21 monochrome sprite frames of 63 bytes plus one pad byte, shown side",
    "by side in the info panel by showUnitPictureSprites.  Each block's stream ends",
    "exactly where the next one begins.",
    "-------------------------------------------------------------------",
    "unitPictureGrunt - type 0, escape marker $05: a helmeted figure, a running",
    "figure and a burst."
   ]
  },
  "CDD3": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureRider - type 1, escape marker $07: three 24x21 frames of the",
    "mounted/wheeled unit."
   ]
  },
  "CE3B": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureBoomer - type 2, escape marker $08: launcher, missile in flight",
    "and emplacement."
   ]
  },
  "CECD": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureSpy - type 3, escape marker $05: three antenna/aerial frames."
   ]
  },
  "CF3A": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureComcen - type 4, escape marker $0A: three frames of the command",
    "centre building.  The block runs out at $CFDC, so the 192nd unpacked byte -",
    "the unused pad byte of sprite 7 - is the first byte of the leftover code",
    "fragment that follows."
   ]
  },
  "CFDD": {
   "unit": "game/textEngineC000",
   "block": [
    "-------------------------------------------------------------------",
    "DEAD CODE.  The last 35 bytes of the overlay are a byte-for-byte copy of the",
    "tail of updateFrameTimers ($12DD-$12FF in the main program) that the build",
    "system left in the sector.  Nothing references $CFDD, the game's timers are",
    "decremented by the copy at $12DD inside the raster IRQ, and the fragment is",
    "not even complete: its relative branches were assembled for $12xx, so here",
    "they point at $D001 (VIC_SPR0_Y), and the final DEC runs off the end of the",
    "page after two of its three bytes.",
    "-------------------------------------------------------------------"
   ],
   "line": "leftover: link poll delay $9163"
  },
  "CFE0": {
   "unit": "game/textEngineC000",
   "line": "leftover: skip if it has already expired"
  },
  "CFE2": {
   "unit": "game/textEngineC000",
   "line": "leftover: count it down"
  },
  "CFE5": {
   "unit": "game/textEngineC000",
   "line": "leftover: unit step delay $90FC"
  },
  "CFE8": {
   "unit": "game/textEngineC000",
   "line": "leftover: skip if zero"
  },
  "CFEA": {
   "unit": "game/textEngineC000",
   "line": "leftover: count it down"
  },
  "CFED": {
   "unit": "game/textEngineC000",
   "line": "leftover: shot-line display timer $91C8"
  },
  "CFF0": {
   "unit": "game/textEngineC000",
   "line": "leftover: skip if zero"
  },
  "CFF2": {
   "unit": "game/textEngineC000",
   "line": "leftover: count it down"
  },
  "CFF5": {
   "unit": "game/textEngineC000",
   "line": "leftover: viewport redraw timer $9234"
  },
  "CFF8": {
   "unit": "game/textEngineC000",
   "line": "leftover: the branch target of the $12DD original lands at $D001 here (VIC_SPR0_Y), not at any code"
  },
  "CFFA": {
   "unit": "game/textEngineC000",
   "line": "leftover: $80 means 'hold the redraw indefinitely', so it is not counted down"
  },
  "CFFC": {
   "unit": "game/textEngineC000",
   "line": "leftover: same stray branch to $D001"
  },
  "CFFE": {
   "unit": "game/textEngineC000",
   "line": "leftover: the first two bytes of 'dec $9234'; the third byte would be at $D000, so the copy is truncated by the end of the page"
  }
 },
 "dataTypes": {
  "C630": [
   "byte",
   1
  ],
  "C87D": [
   "byte",
   29
  ],
  "C92C": [
   "text",
   56
  ],
  "C964": [
   "text",
   40
  ],
  "C98C": [
   "text",
   70
  ],
  "C9D2": [
   "text",
   186
  ],
  "CA8C": [
   "text",
   45
  ],
  "CAB9": [
   "text",
   48
  ],
  "CAE9": [
   "text",
   33
  ],
  "CB0A": [
   "text",
   21
  ],
  "CB1F": [
   "text",
   218
  ],
  "CBF9": [
   "text",
   24
  ],
  "CC11": [
   "text",
   14
  ],
  "CC1F": [
   "text",
   122
  ],
  "CC99": [
   "text",
   36
  ],
  "CCBD": [
   "text",
   11
  ],
  "CCC8": [
   "text",
   56
  ],
  "CD00": [
   "byte",
   5
  ],
  "CD05": [
   "byte",
   5
  ],
  "CD52": [
   "sprite",
   129
  ],
  "CDD3": [
   "sprite",
   104
  ],
  "CE3B": [
   "sprite",
   146
  ],
  "CECD": [
   "sprite",
   109
  ],
  "CF3A": [
   "sprite",
   163
  ]
 },
 "zpComments": {
  "72": "frames the current status-line message stays up, decremented every 4th frame by updateFrameTimers; 0 = ready for the next message. handleMessageReviewKeys also uses it as the minimum airtime of a scrolled-back message (it only steps on once the timer has fallen below $23)",
  "BE": "pause/disconnect state: bit 0 own pause, bit 1 opponent pause, 3 after a timeout, bit 7 = voice pause / hang-up. handlePauseKey toggles bit 0 directly in a solo or film game and otherwise waits for the command interpreter to set it from the opponent's $8C packet"
 }
}
