{
 "_comment": "generated by tools/mergeSurvey.py from survey/*.json - do not edit by hand",
 "notes": {
  "C01A": {
   "routine": [
    "printChar - Central character output of the game. Saves A/X/Y (saveRegsAXY), and unless rawGlyphMode (zp_43 bit 7) is set masks bit 7 off, sends codes < $20 to handleControlChar and translates codes $20-$5F through asciiToGlyphTable ($987E) into a glyph index; then plots the glyph and advances the cursor. Registers are restored before it returns, so callers can print inside loops.",
    "In:  A = character (ASCII $20-$5F, control $08/$0D, or a raw glyph index when zp_43 bit 7 is set), text window state zp_39-zp_44, cursor pointers zp_B4-zp_B7",
    "Out: one glyph drawn in the bitmap plus its colour byte, cursor advanced (zp_3F/zp_40 and zp_B2-zp_B7 updated); A/X/Y preserved via zp_54/zp_55/zp_56"
   ],
   "unit": "game/textEngineC000"
  },
  "C030": {
   "routine": [
    "restoreRegsAXY - Loads A, X and Y back from the zp_54/zp_55/zp_56 save slots filled by saveRegsAXY. Tail of printChar and also used directly by the viewport drawing code at $5CA1/$5D11/$5DE7 to recover a cell coordinate triple.",
    "In:  zp_54 (A), zp_55 (X), zp_56 (Y)",
    "Out: A, X, Y restored; N/Z reflect A"
   ],
   "unit": "game/textEngineC000"
  },
  "C03D": {
   "routine": [
    "handleControlChar - Handles the two control codes understood by printChar: $0D = carriage return (printNewline) and $08 = backspace (step back one column, print the blank glyph 0 to erase, step back again). Any other code below $20 is ignored.",
    "In:  A = control code",
    "Out: cursor moved, possibly one cell erased; cursor pointers recomputed"
   ],
   "unit": "game/textEngineC000"
  },
  "C041": {
   "routine": [
    "printNewline - Carriage return: cursor column zp_3F = 0 and cursor row zp_40 += 1 (+2 when doubleSizeText zp_44 bit 7 is set), then recomputes the cell pointers. The window is never scrolled - the comparison of the new row against the window bottom zp_3A at $C04D-$C051 branches to the next instruction either way (vestigial code).",
    "In:  zp_39-zp_44 window state",
    "Out: zp_3F = 0, zp_40 advanced, zp_B4-zp_B7 recomputed"
   ],
   "unit": "game/textEngineC000"
  },
  "C06A": {
   "routine": [
    "clearTextWindow - Homes the cursor to the top left of the current window (column 0, row zp_39) and falls into clearToWindowBottom, so the whole window is filled with the current blank character zp_3E in the current colour zp_3D. The standard 'clear window' call of the whole game.",
    "In:  window zp_39-zp_3E",
    "Out: window filled with zp_3E, cursor left at the top left"
   ],
   "unit": "game/textEngineC000"
  },
  "C075": {
   "routine": [
    "clearToWindowBottom - Fills from the current cursor position up to and including the window bottom row zp_3A with the blank character zp_3E, then restores the cursor position it started from.",
    "In:  cursor zp_3F/zp_40, window zp_3A/zp_3C, blank char zp_3E, colour zp_3D",
    "Out: cells filled; cursor unchanged on exit"
   ],
   "unit": "game/textEngineC000"
  },
  "C07A": {
   "routine": [
    "clearToEndOfLine - Same fill loop as clearToWindowBottom but with the stop row set to zp_40 + 1, i.e. it blanks the remainder of the current line only. Used to pad the message line after a status string and by the overlay screens.",
    "In:  cursor zp_3F/zp_40, window width zp_3C, blank char zp_3E",
    "Out: rest of the line filled with zp_3E; cursor restored; self-modifies the compare operand at $C090"
   ],
   "unit": "game/textEngineC000"
  },
  "C097": {
   "routine": [
    "setCursorRow - Sets the cursor row zp_40 = A and falls into updateCursorPointers. Convenience entry used all over the game to move to a row without touching the column.",
    "In:  A = row",
    "Out: zp_40 = A, zp_B4-zp_B7 recomputed"
   ],
   "unit": "game/textEngineC000"
  },
  "C099": {
   "routine": [
    "updateCursorPointers - Recomputes the bitmap and colour pointers for the current cursor cell: absolute column = zp_3F + window left zp_3B, row = zp_40, passed to calcCellPointersXY ($2AA3). When the display is in multicolour mode (D_9001 = $D016 shadow, bit 4 set) it adds $4C to zp_B7, turning the screen-RAM pointer ($8C00) into a colour-RAM pointer ($D800). X and Y are preserved through self-modified operands.",
    "In:  zp_3F, zp_40, zp_3B, D_9001",
    "Out: zp_B4/zp_B5 = bitmap cell address, zp_B6/zp_B7 = screen or colour RAM address; X/Y preserved"
   ],
   "unit": "game/textEngineC000"
  },
  "C0C0": {
   "routine": [
    "plotCharAndAdvance - Plots glyph A at the cursor (plotGlyph), advances the column by 1 (by 2 when doubleSizeText zp_44 bit 7 is set), wraps to the next line when the column reaches the window width zp_3C, and recomputes the cell pointers.",
    "In:  A = glyph index, window/cursor state",
    "Out: glyph drawn, cursor advanced, pointers updated"
   ],
   "unit": "game/textEngineC000"
  },
  "C0D7": {
   "routine": [
    "setFullScreenWindow - Resets the text window to the whole screen: top zp_39 = 0, left zp_3B = 0, bottom zp_3A = $19 (25), width zp_3C = $28 (40). Called on its own by screen builders and as the tail of saveTextWindow.",
    "In:  none",
    "Out: zp_39 = 0, zp_3A = $19, zp_3B = 0, zp_3C = $28"
   ],
   "unit": "game/textEngineC000"
  },
  "C0E8": {
   "routine": [
    "saveRegsAXY - Stores A, X and Y in the scratch triple zp_54/zp_55/zp_56 so they can be recovered with restoreRegsAXY. Used at the head of printChar and by the viewport code at $5C81.",
    "In:  A, X, Y",
    "Out: zp_54 = A, zp_55 = X, zp_56 = Y"
   ],
   "unit": "game/textEngineC000"
  },
  "C0EF": {
   "routine": [
    "printFillChars - Prints the current blank character zp_3E A times through plotCharAndAdvance (does nothing for A = 0). Used for padding fields, for the centring pad inside printStringAtPointer and for drawing horizontal bars.",
    "In:  A = repeat count, zp_3E = fill character, zp_3D = colour",
    "Out: A cells filled, cursor advanced"
   ],
   "unit": "game/textEngineC000"
  },
  "C100": {
   "routine": [
    "printTableEntry - Prints entry X of a table of fixed 8-byte, bit-7-terminated strings whose base address is passed in A (low) / Y (high): computes base + X*8 with a 16-bit shift and tail-calls printString. Used for unit type names, menu item names and the STANDARD/CUSTOM words.",
    "In:  A/Y = table base, X = entry index",
    "Out: string printed; zp_18/zp_19/zp_1A clobbered"
   ],
   "unit": "game/textEngineC000"
  },
  "C121": {
   "routine": [
    "printStringCentred - Sets the centring field width D_90EF to the current window width zp_3C and tail-calls printString, so the string is padded on the left to appear centred in the window. A is preserved across the D_90EF store.",
    "In:  A/Y = string pointer, zp_3C = window width",
    "Out: D_90EF = zp_3C (cleared again by printStringAtPointer), string printed centred"
   ],
   "unit": "game/textEngineC000"
  },
  "C12B": {
   "routine": [
    "printNextStringCentred - Like printStringCentred but continues with the string that follows the one just printed (pointer zp_A2/zp_A3 already advanced past the terminator), i.e. prints the next line of a multi-string block centred in the window.",
    "In:  zp_A2/zp_A3 = string pointer, zp_3C = window width",
    "Out: D_90EF = zp_3C, next string printed centred"
   ],
   "unit": "game/textEngineC000"
  },
  "C133": {
   "routine": [
    "printString - Sets the string pointer zp_A2/zp_A3 = A/Y and falls into printStringAtPointer. This is the main string output call of the game (used from the main program, both $6F00 overlay variants and the comm module).",
    "In:  A = pointer low, Y = pointer high; the string ends with a character that has bit 7 set",
    "Out: string printed at the cursor, zp_A2/zp_A3 left just past the terminator"
   ],
   "unit": "game/textEngineC000"
  },
  "C137": {
   "routine": [
    "printStringAtPointer - Prints the bit-7-terminated string at zp_A2/zp_A3. If the centring width D_90EF is non-zero it first measures the string and prints (D_90EF - length)/2 blank characters in the pad colour zp_42 (zp_3D is saved and restored around the pad), then prints every character through printChar, terminator included, and finally clears D_90EF. Entered directly to print the next string of a block.",
    "In:  zp_A2/zp_A3 = string, D_90EF = field width or 0, zp_42 = pad colour, zp_3E = blank character",
    "Out: text drawn, zp_A2/zp_A3 advanced past the terminator, D_90EF = 0; self-modifies the length operand at $C159"
   ],
   "unit": "game/textEngineC000"
  },
  "C17D": {
   "routine": [
    "printDecimal - Prints an unsigned 24-bit value as decimal without leading zeros: converts it with convertToDecimalDigits and then prints the digits (printDecimalDigits).",
    "In:  A = value low, Y = value middle, zp_7E = value high byte",
    "Out: digits printed, digit buffer $92DA filled, zp_7E cleared, zp_18-zp_1F clobbered"
   ],
   "unit": "game/textEngineC000"
  },
  "C183": {
   "routine": [
    "convertToDecimalDigits - Converts the 24-bit value in A (low) / Y (middle) / zp_7E (high) to decimal by repeated 24-bit division by 10 (24-iteration shift/subtract loop). Digits are stored as $B0 + digit (ASCII with bit 7 set) from the end of the buffer at $92DA backwards; zp_1E ends up pointing at the most significant digit and zp_7E is cleared.",
    "In:  A/Y/zp_7E = 24-bit value",
    "Out: $92DA+1..$92DA+8 digits, zp_1E = index of the first digit, zp_7E = 0, zp_18-zp_1F clobbered"
   ],
   "unit": "game/textEngineC000"
  },
  "C1E2": {
   "routine": [
    "printDecimalDigits - Prints the digit buffer at $92DA from index zp_1E up to index 8 through printChar. Only entered by the JMP at the end of printDecimal, but it is a separate routine body.",
    "In:  zp_1E = first digit index, $92DA buffer",
    "Out: digits printed"
   ],
   "unit": "game/textEngineC000"
  },
  "C1F0": {
   "routine": [
    "plotGlyph_C1F0 - Draws glyph A into the bitmap cell at zp_B4/zp_B5 and writes the colour byte zp_3D through zp_B6/zp_B7. The glyph source is $98BE + A*8 in multicolour mode (D_9001 bit 4 set) or $9A96 + A*8 in hires mode - two 60-glyph fonts with identical shapes. When doubleSizeText (zp_44 bit 7) is set it hands over to plotGlyphDoubleSize instead. The 8-byte copy loop at $C227-$C231 loads the existing bitmap byte first and the following opcode at $C22B is patched by setBitmapDrawMode ($2A41) to LDA/ORA/EOR/CMP (zp),Y, giving replace/OR/EOR/transparent drawing.",
    "In:  A = glyph index, zp_B4-zp_B7 cell pointers, zp_3D colour, zp_44, D_9001",
    "Out: 8 bitmap bytes and one colour byte written; zp_B2/zp_B3 = glyph pointer; A preserved"
   ],
   "unit": "game/textEngineC000"
  },
  "C24B": {
   "routine": [
    "plotGlyphDoubleSize - Draws the glyph at zp_B2/zp_B3 magnified 2x2 into the four character cells starting at zp_B4/zp_B5 (top-left cell, +8 = top-right, +$138 = bottom-left, +8 = bottom-right) and colours them with colourDoubleSizeCell. The 8 source bytes are copied to the scratch buffer $90B8 first; each quadrant expands 4 source rows and one nibble of each row. The mask zp_BA suppresses quadrants (bit 0 = top cells, bit 1 = bottom cells, bit 2 = left cells, bit 3 = right cells) so half-size edges can be drawn. A, X and Y are preserved through self-modified operands. Called by magnifyWindow2x ($2E7B/$2EA5/$2EEA/$2F14) and by plotGlyph when zp_44 bit 7 is set.",
    "In:  zp_B2/zp_B3 = glyph, zp_B4-zp_B7 = destination cell, zp_3D = colour, zp_BA = edge mask",
    "Out: up to 4 cells (32 bitmap bytes) and their colour bytes written; $90B8-$90BF, zp_1A-zp_1D clobbered; operands at $C301/$C303/$C305/$C33A/$C33B/$C33D/$C33E self-modified"
   ],
   "unit": "game/textEngineC000"
  },
  "C307": {
   "routine": [
    "plotGlyphLeftHalf - Quadrant helper of plotGlyphDoubleSize for the left-hand cell: returns immediately when zp_BA bit 2 is set, otherwise selects the high nibble (zp_1D = $F0) and runs expandGlyphNibbles.",
    "In:  zp_BA, zp_1B = source row offset (0 or 4)",
    "Out: 8 bitmap bytes of one cell; zp_1D = $F0"
   ],
   "unit": "game/textEngineC000"
  },
  "C315": {
   "routine": [
    "plotGlyphRightHalf - Quadrant helper of plotGlyphDoubleSize for the right-hand cell: returns immediately when zp_BA bit 3 is set, otherwise selects the low nibble (zp_1D = $0F) and falls into expandGlyphNibbles.",
    "In:  zp_BA, zp_1B = source row offset",
    "Out: 8 bitmap bytes of one cell; zp_1D = $0F"
   ],
   "unit": "game/textEngineC000"
  },
  "C320": {
   "routine": [
    "expandGlyphNibbles - Inner loop of the 2x magnifier: takes 4 glyph rows from the scratch buffer $90B8 starting at offset zp_1B, masks each with zp_1D (shifting the high nibble down), looks the nibble up in pixelDoubleTable ($C23B) to double its pixels horizontally and stores the result to two destination bytes (the self-modified absolute addresses at $C33A and $C33D, one bitmap row apart), doubling it vertically as well.",
    "In:  $90B8 glyph rows, zp_1B = 0 or 4, zp_1D = $F0 or $0F, destination operands at $C33A/$C33D",
    "Out: 8 bitmap bytes written; zp_1A = byte counter"
   ],
   "unit": "game/textEngineC000"
  },
  "C34B": {
   "routine": [
    "colourDoubleSizeCell - Writes the colour byte zp_3D into the colour/screen cells of the 2x2 block drawn by plotGlyphDoubleSize: the first row pair unless zp_BA bit 0 is set, then zp_B6/zp_B7 is advanced by 40 and the second row pair is written unless bit 1 is set.",
    "In:  zp_B6/zp_B7 = cell colour pointer, zp_3D, zp_BA",
    "Out: up to 4 colour bytes written; zp_B6/zp_B7 advanced by one screen row"
   ],
   "unit": "game/textEngineC000"
  },
  "C367": {
   "routine": [
    "colourCellPair - Writes zp_3D into the two horizontally adjacent colour cells at (zp_B6),0 and (zp_B6),1, skipping the left one when zp_BA bit 2 is set and the right one when bit 3 is set.",
    "In:  zp_B6/zp_B7, zp_3D, zp_BA",
    "Out: up to 2 colour bytes written"
   ],
   "unit": "game/textEngineC000"
  },
  "C37F": {
   "routine": [
    "waitFrames - Busy-waits A frames by polling the raster register: for each frame it waits until VIC_RASTER reads $F0 and then until it changes again. Returns at once for A = 0. Used for the pauses in the boot/menu sequences, sound effects and the modem dialogue.",
    "In:  A = number of frames",
    "Out: returns after A frames; A = 0"
   ],
   "unit": "game/textEngineC000"
  },
  "C397": {
   "routine": [
    "waitRasterBelowScreen - Spins until VIC_RASTER >= $E0, i.e. until the raster is below the visible screen, so the caller can update the bitmap without tearing. Only caller is $3CFA.",
    "In:  none",
    "Out: returns with the raster in the lower border; A = raster value"
   ],
   "unit": "game/textEngineC000"
  },
  "C39F": {
   "routine": [
    "clearMessageLine - Blanks the bottom status/message line: saves the current text window, switches to the message-line window, clears that line and restores the window. Called when a message expires, after disk access and from clearMessageLineAndScreen ($2DDB).",
    "In:  D_90FB (screen id), D_9001",
    "Out: message line filled with zp_3E; text window restored from $92B6"
   ],
   "unit": "game/textEngineC000"
  },
  "C3AB": {
   "routine": [
    "setMessageLineWindow - Sets the text window to the one-line status bar: left column zp_3B = 2, width zp_3C = $24 (36 columns, i.e. columns 2-37), cursor column 0, row zp_40 = window bottom zp_3A = $17 (23), or $16 (22) when currentScreenId D_90FB >= 3 (the STATS/REPAIR/MISC menu screen). The colour zp_3D and pad colour zp_42 are set to 0 in multicolour mode and to 7 in hires mode.",
    "In:  D_90FB, D_9001",
    "Out: zp_3A-zp_3D, zp_3F, zp_40, zp_42 set; cell pointers recomputed"
   ],
   "unit": "game/textEngineC000"
  },
  "C3D8": {
   "routine": [
    "showMessageString - Prints the bit-7-terminated string A/Y centred in the 36-column status line and restores the previous text window. This is the standard 'put this text in the status bar' call (disk errors, queued messages, the clock line).",
    "In:  A/Y = string pointer",
    "Out: status line rewritten; text window restored"
   ],
   "unit": "game/textEngineC000"
  },
  "C3E0": {
   "routine": [
    "printOnMessageLine - Worker behind showMessageString: remembers the string pointer in the self-modified operands at $C3F0/$C3F2, sets the centring width D_90EF = X, saves the text window, selects the message-line window, prints the string and pads the rest of the line. The caller restores the window (restoreTextWindow).",
    "In:  A/Y = string pointer, X = centring width (0 = left aligned)",
    "Out: message line drawn, window still set to the message line on return"
   ],
   "unit": "game/textEngineC000"
  },
  "C3F9": {
   "routine": [
    "advanceFillPointer - Loop helper for the block-fill routines: compares the 16-bit pointer zp_48/zp_49 with the limit zp_4A/zp_4B (carry set when the limit has been reached), then increments the pointer and the 16-bit counter zp_4E/zp_4F. Callers store a byte and loop while carry is clear (memory clear at $0C44, the setup block at $0333 and two loops in the $EC00 sub-overlay).",
    "In:  zp_48/zp_49 = current address, zp_4A/zp_4B = last address",
    "Out: C = 1 when the pointer had reached the limit; zp_48/zp_49 and zp_4E/zp_4F incremented"
   ],
   "unit": "game/textEngineC000"
  },
  "C40E": {
   "routine": [
    "restoreTextWindow - Copies the 12 saved bytes at $92B6 back into zp_39-zp_44 (window geometry, colours, cursor and mode flags) and recomputes the cell pointers. Counterpart of saveTextWindow; there is only one save slot, so the pair cannot be nested.",
    "In:  $92B6-$92C1",
    "Out: zp_39-zp_44 restored, cell pointers recomputed"
   ],
   "unit": "game/textEngineC000"
  },
  "C41B": {
   "routine": [
    "saveTextWindow - Saves zp_39-zp_44 (the whole text-window state) to $92B6 and then resets the window to the full screen (setFullScreenWindow). Used before drawing the message line, the repair window and everything else that must not disturb the caller's window.",
    "In:  zp_39-zp_44",
    "Out: $92B6-$92C1 = window state; window reset to 0,0,25,40"
   ],
   "unit": "game/textEngineC000"
  },
  "C428": {
   "routine": [
    "splitDecimalDigits - Splits the byte in A into three decimal digits by repeated subtraction: ones -> $C450, tens -> $C44F, hundreds -> $C44E. The digits are used as glyph indices by drawTallGlyph for the drone fuel and missile counters ($6F9C, $7ABD). The 'bne/lda #$00/beq' sequence at $C444-$C448 has no effect (leftover of a leading-zero suppression).",
    "In:  A = value 0-255",
    "Out: $C44E/$C44F/$C450 = hundreds/tens/ones; X clobbered"
   ],
   "unit": "game/textEngineC000"
  },
  "C451": {
   "routine": [
    "checkUiContinue - Decides whether a modal UI (menu selector, info panel, group editor) may keep running. Returns A = 1 to continue. Returns A = 0 and arms a 60-frame input lockout ($0B7D) when the game is ending (D_92C9 != 0 and not in film/solo mode), when a screen change is pending (currentKey zp_66 >= 0), when SPACE was typed (D_9248 = $A0) or when checkSetupUiContinue says stop. A pause (zp_BE != 0) or gamePhase zp_B1 bits 6/7 keep the UI alive.",
    "In:  D_0B9B (film/solo flag), D_92C9, zp_BE, zp_B1, zp_66, D_9248",
    "Out: A = 1 continue / 0 abort; $0B7D = $3C on abort"
   ],
   "unit": "game/textEngineC000"
  },
  "C470": {
   "routine": [
    "checkSetupUiContinue - Tail of checkUiContinue, also called on its own from the map main loop at $202D during the setup phase: returns 1 while gamePhase zp_B1 is 0 or D_92C6 is 0 (or D_92C6 is negative and the local side D_0B9F is 0), otherwise 1 only if the low two bits of zp_B1 are both set, else 0 with the input lockout armed.",
    "In:  zp_B1 gamePhase, D_92C6, D_0B9F",
    "Out: A = 1 continue / 0 abort; $0B7D = $3C on abort",
    "(confidence: medium)"
   ],
   "unit": "game/textEngineC000"
  },
  "C493": {
   "routine": [
    "serviceChatDisplay - First half of updateStatusMessage: handles the chat/typing state D_929C. A positive non-zero state means the local typing line must be shown - it clears the state, draws the typed line, remembers the pending message id in zp_7B, sets zp_7A = $81 and expires the display timer. While typing (bit 7 set), holding key $8B shows message 0 (the received chat line at $0548) instead and sets bit 6; releasing it clears bit 6 and redraws the typed line. In both typing cases it pulls two bytes off the stack so that updateStatusMessage returns to its own caller without touching the message queue.",
    "In:  D_929C chat state, D_92A0 raw key code, zp_7A",
    "Out: D_929C updated, status line redrawn, zp_7A/zp_7B/zp_72 updated; may discard updateStatusMessage's return address",
    "(confidence: medium)"
   ],
   "unit": "game/textEngineC000"
  },
  "C4DA": {
   "routine": [
    "drawTypedChatLine - Draws the locally typed chat line: prints the bit-7-terminated string buffer at $9278 left aligned on the message line, then sets the colour to $11 and prints one $A0 (space) as the cursor block, and restores the text window.",
    "In:  $9278 line buffer",
    "Out: message line redrawn with a cursor; zp_3D = $11; window restored"
   ],
   "unit": "game/textEngineC000"
  },
  "C4EF": {
   "routine": [
    "updateStatusMessage - Per-frame service of the status bar, called from every polling loop (waitForCarrier, updateGameFrame, the command exchange at $4DA9, ...). Runs serviceChatDisplay and the message review handler $C631, returns while the display timer zp_72 is still running, otherwise finishes the current message (popNextMessage $C5EE), takes the pending id zp_7A, prints its string from the pointer tables $0500 (lo) / $0524 (hi), restarts the 60-tick timer and plays the message sound. When nothing is pending it refreshes the clock/score line ($C6B6) whenever the game clock D_91CB has changed during the battle. Starts with a NOP that no code patches.",
    "In:  zp_72, zp_7A, zp_7B, D_9277, D_92FA, zp_B1, D_91CB, D_9208, message tables $0500/$0524",
    "Out: status line updated, zp_72 = $3C, zp_7A bit 7 set, D_9208 = clock; self-modifies the sound flag at $C54B"
   ],
   "unit": "game/textEngineC000"
  },
  "C557": {
   "routine": [
    "playMessageSound - Plays the sound effect assigned to message id Y: the sound number comes from the per-message table at $0612, a negative entry ($80) means silent.",
    "In:  Y = message id, table $0612",
    "Out: sound started via sub_C89A"
   ],
   "unit": "game/textEngineC000"
  },
  "C560": {
   "routine": [
    "queueMessageUnlessFilm - Queues message id A like queueMessage but drops it silently while D_0B9B bit 7 (film playback / solo trainer) is set. Used for the combat messages ('UNIT RECYCLED', 'COMCEN STUNNED!', ...).",
    "In:  A = message id, D_0B9B",
    "Out: message queued or ignored"
   ],
   "unit": "game/textEngineC000"
  },
  "C566": {
   "routine": [
    "queueMessage - Inserts message id A into the 8-entry ring queue at $9249 (write index D_9251, read index D_9252) keeping it sorted so that higher ids are shown first: it scans from the read index for the first entry <= A, shifts the entries in between one slot forward and stores the new id there. The queue is dropped when full. If the insert lands at the read index the display timer zp_72 is zeroed so the message appears immediately, and a currently displayed message (zp_7B bit 7 set) is marked so that updateStatusMessage pops the queue on its next pass. Y is preserved.",
    "In:  A = message id, Y preserved, $9249-$9252",
    "Out: queue updated, zp_72/zp_7B possibly updated; self-modifies operands at $C5B0, $C5B4 and $C5BB",
    "(confidence: medium)"
   ],
   "unit": "game/textEngineC000"
  },
  "C5C9": {
   "routine": [
    "decMessageIndex - Decrements the message-queue index in Y modulo 8 (wraps to 7).",
    "In:  Y",
    "Out: Y = (Y - 1) mod 8"
   ],
   "unit": "game/textEngineC000"
  },
  "C5CF": {
   "routine": [
    "incMessageIndex - Increments the message-queue index in Y modulo 8 (wraps to 0).",
    "In:  Y",
    "Out: Y = (Y + 1) mod 8"
   ],
   "unit": "game/textEngineC000"
  },
  "C5D7": {
   "routine": [
    "redrawStatusMessage - Redraws whatever the status bar should currently show after a screen rebuild (jumped to from $1562, $268C and the overlays at $7131/$7A68): the message currently displayed (zp_7B with bit 7 masked off), message 0 (the received chat line) when chat text is buffered ($9277 != 0), or the next queued message via popNextMessage.",
    "In:  zp_7B, D_9277",
    "Out: status line redrawn"
   ],
   "unit": "game/textEngineC000"
  },
  "C5E4": {
   "routine": [
    "showMessageById - Looks message id A up in the pointer tables $0500 (low) / $0524 (high) and tail-calls showMessageString, so the message text is drawn centred in the status line straight away without touching the queue.",
    "In:  A = message id 0-$23",
    "Out: status line redrawn; X = message id"
   ],
   "unit": "game/textEngineC000"
  },
  "C000": {
   "unit": "game/textEngineC000",
   "block": [
    "bitPairHighTable: byteTable, 4 bytes. $00,$40,$80,$C0 - a two-bit value shifted into bit positions 7-6. No reference in the disassembly (the label bootEntry on it belongs to the boot loader that used to live here)."
   ]
  },
  "C004": {
   "unit": "game/textEngineC000",
   "block": [
    "solidColourByteTable: byteTable, 4 bytes. $00,$55,$AA,$FF - the byte that fills a whole multicolour bitmap row with colour 0-3. No reference in the disassembly."
   ]
  },
  "C008": {
   "unit": "game/textEngineC000",
   "block": [
    "bitMaskTable_C008: byteTable, 8 bytes. $80,$40,$20,$10,$08,$04,$02,$01 - single bit masks, read as L_C008,x by the sprite/bit plotting code at $30B9 (ORA, set bit) and $3217 (LDA, test/EOR)."
   ]
  },
  "C010": {
   "unit": "game/textEngineC000",
   "block": [
    "pixelPairMaskTable: byteTable, 4 bytes. $C0,$30,$0C,$03 - the four multicolour pixel-pair masks of a bitmap byte. No reference in the disassembly."
   ]
  },
  "C014": {
   "unit": "game/textEngineC000",
   "block": [
    "pixelPairClearMaskTable: byteTable, 4 bytes. $3F,$CF,$F3,$FC - complements of pixelPairMaskTable. No reference in the disassembly."
   ]
  },
  "C018": {
   "unit": "game/textEngineC000",
   "block": [
    "unusedC018: unknown, 2 bytes. $A9,$8D - two bytes between the mask tables and printChar; no reference, meaning unknown."
   ]
  },
  "C23B": {
   "unit": "game/textEngineC000",
   "block": [
    "pixelDoubleTable: byteTable, 16 bytes. $00,$03,$0C,$0F,$30,$33,$3C,$3F,$C0,$C3,$CC,$CF,$F0,$F3,$FC,$FF - expands the four bits of a nibble into eight pixels (each bit doubled), used by expandGlyphNibbles for the 2x glyph magnifier. The stale labels bootShowTitlePicture ($C23F) and L_C249 sit inside this table."
   ]
  },
  "C44E": {
   "unit": "game/textEngineC000",
   "block": [
    "decimalDigits: byteTable, 3 bytes. Three RAM bytes inside the code holding the hundreds/tens/ones digits produced by splitDecimalDigits; read by the drone fuel ($6FD6/$6FDE/$6FE6) and missile counter ($7AD5/$7ADD) displays."
   ]
  },
  "C5EE": {
   "routine": [
    "showNextQueuedMessage - Picks the next status-line message. If the 8-entry message queue is not empty (tail $9252 != head $9251) it takes the id at the tail, advances the tail and leaves it in currentMessageId zp_7A. If the queue is empty it either re-arms the idle state (zp_7A = $80, chat line cleared with clearChatStatusLine, then persistentMessageId $92A1 decides: negative = redraw the CLICKS/US/THEM score line at L_C6B6, zero = blank the status line via sub_C39F, otherwise print that message with sub_C5E4) or, when a chat line is pending (chatLineMarker $0548 not negative and zp_7A != $80), selects message id 0 (the chat line) and suppresses its sound by patching $C54B to $FF.",
    "In:  D_9251/D_9252 queue indices, D_9249 queue, zp_7A, D_0548, D_92A1",
    "Out: zp_7A, D_9252, D_0548 (via sub_C62A), sound-suppress operand $C54B; may print the status line and clobber A/Y"
   ],
   "unit": "game/textEngineC000"
  },
  "C62A": {
   "routine": [
    "clearChatStatusLine - Stores $9E (a bit-7 terminator that prints nothing) into chatLineMarker $0548, i.e. makes message slot 0 - whose text pointer is $0548 - an empty status line. Called from showNextQueuedMessage and by JMP from the game's start-up init at $0C63.",
    "In:  none",
    "Out: D_0548 = $9E, A = $9E"
   ],
   "unit": "game/textEngineC000"
  },
  "C631": {
   "routine": [
    "handleMessageReviewKeys - Per-frame hook of the message service (called from sub_C4EF at $C4F3). CRSR up/down (lastKeyCode $9248 == $8B) enters/steps the message-history scrollback: the current queue tail is saved in messageReviewIndex $C630 with bit 7 set, then each press walks one entry backwards through the ring $9249 (sound 3 when there is no older entry), shows it immediately (message timer zp_72 = 0, sound suppressed via $C54B = $80) and sets $0548 = $1E so the chat line stays live; when zp_72 drops below $23 without another press the saved index is restored and review mode ends. CRSR left/right ($8E) is consumed and, only during battle (zp_B1 == 0), arms a 60-frame input lockout, sets zp_72 = $3C, clears bit 7 of zp_7B and falls into drawStatusScoreLine.",
    "In:  D_C630, D_9248, zp_72, D_9251/D_9252, D_9249, zp_7A, zp_7B, zp_B1",
    "Out: D_C630, D_9252, D_9248 = $FF, zp_7A, zp_7B, zp_72, D_0548, $C54B, inputLockoutTimer $0B7D; may draw the score line"
   ],
   "unit": "game/textEngineC000"
  },
  "C6B6": {
   "routine": [
    "drawStatusScoreLine - Redraws the bottom status line as '   CLICKS:<n>    US:<n>   THEM:<n>'. Saves the text window (sub_C41B), selects the status-line window (sub_C3AB), prints the string trio at $065E/$0668/$066F with sub_C133/sub_C137, the game clock gameClockClicks $91CB and the two 16-bit point totals through printPlayerPoints (first the local side D_0B9F EOR 1, then the opponent D_0B9F), pads the line (sub_C07A) and restores the window (sub_C40E). Entered by JMP only - from $C521 (clock changed while the last-quarter score display flag $92FA is set) and from handleMessageReviewKeys.",
    "In:  D_91CB, D_0B9F, D_92A2/D_92A4, strings at $065E",
    "Out: status line redrawn; zp_39-zp_44 window state saved/restored via $92B6; zp_7E, D_92DA digit buffer clobbered"
   ],
   "unit": "game/textEngineC000"
  },
  "C6E5": {
   "routine": [
    "printPlayerPoints - A = player side index (0/1). Loads that side's 16-bit point total from playerPointsLo $92A2,x / playerPointsHi $92A4,x and tail-calls the decimal printer sub_C17D.",
    "In:  A = side index; D_92A2, D_92A4",
    "Out: digits printed; X = side index, zp_18-zp_1F and D_92DA clobbered"
   ],
   "unit": "game/textEngineC000"
  },
  "C6EF": {
   "routine": [
    "resetMessageQueue - Empties the message queue by copying the head index $9251 into the tail $9252, clears persistentMessageId $92A1 and the message timer zp_72, then falls through to showNextQueuedMessage so the status line is refreshed at once. Called from a dozen places whenever a screen changes or a game phase starts/ends.",
    "In:  D_9251",
    "Out: D_9252 = D_9251, D_92A1 = 0, zp_72 = 0, status line redrawn"
   ],
   "unit": "game/textEngineC000"
  },
  "C6FF": {
   "routine": [
    "showStatusMessage - A = message id. Plays the sound associated with that id (sub_C557 looks it up in the per-message sound table at $0612; $80 = silent), stores the id in pendingMessageId zp_7B and prints the message text immediately through sub_C5E4, bypassing the queue. Used by the disk-error retry path ($082D), the link-status screens and the comm module.",
    "In:  A = message id; D_0612, D_0500/D_0524",
    "Out: zp_7B = id, message printed, sound queued"
   ],
   "unit": "game/textEngineC000"
  },
  "C705": {
   "routine": [
    "showStatusMessageNoSound - Tail/alternate entry of showStatusMessage that skips the sound lookup: stores A in pendingMessageId zp_7B and prints that message. Reached by JMP from showWorkingMessage ($C858).",
    "In:  A = message id",
    "Out: zp_7B = id, message printed"
   ],
   "unit": "game/textEngineC000"
  },
  "C70A": {
   "routine": [
    "handlePauseKey - RUN/STOP pause handling, called every frame from updateGameFrame ($40BF/$413D). Returns at once unless gamePhase zp_B1 == 0 and a game is running ($0BA8 == 0). In solo/film mode ($0B9B bit 7) RUN/STOP toggles commPauseFlags zp_BE; while paused persistentMessageId is $19 ('TIMEOUT. RUN/STOP TO RESUME.'), only handleMenuSessionPacket is serviced and two PLAs discard the caller's return address so the rest of the frame update is skipped. In live play the first RUN/STOP shows message 1 and sends command $8C reason 3; once zp_BE is set it saves the sprite-enable mask, clears the screen windows (unless currentScreen == 3), then either performs a voice pause (zp_BE bit 7 -> voicePauseReconnect) or spins calling serviceCommandExchange until zp_BE clears, sending $8C reason 4 (and showWaitingForOpponent) on each further RUN/STOP. On resume it redraws the overview map and viewport on screen 0 and restores the sprites.",
    "In:  zp_B1, D_0BA8, D_0B9B, zp_BE, D_9022, D_90FB, D_0B7D",
    "Out: zp_BE, D_92A1, D_9022 restored via enableSprites, commands $8C/3 and $8C/4 queued, screen redrawn; may return to the caller's caller"
   ],
   "unit": "game/textEngineC000"
  },
  "C7A3": {
   "routine": [
    "sendPauseCommand - A = reason code. Stores it in cmdParam0 zp_73 and queues the 2-byte network command $8C via queueCmd2 ($561F). Reason 3 = request pause, 4 = request resume (reason 2 = ready for new game and 5 = voice pause/disconnect are sent elsewhere).",
    "In:  A = reason",
    "Out: zp_73, outgoing command ring"
   ],
   "unit": "game/textEngineC000"
  },
  "C7AA": {
   "routine": [
    "consumeRunStopKey - Edge test for the RUN/STOP key: if lastKeyCode $9248 holds $93 it arms a 60-frame input lockout ($0B7D), clears the key, plays click sound 2 and returns A = 0 (Z = 1); otherwise it returns with Z = 0. All three call sites are inside handlePauseKey.",
    "In:  D_9248",
    "Out: A/Z, D_9248 = $FF, D_0B7D = $3C, sound 2"
   ],
   "unit": "game/textEngineC000"
  },
  "C7C3": {
   "routine": [
    "showWaitingForOpponent - A = commPauseFlags. When the comm build flag $0BA5 bit 7 is clear and A bit 0 is set, sets persistentMessageId to $0B ('WAITING FOR OPPONENT...') and queues it with sub_C566. Also called from overlay A ($7C00).",
    "In:  A, D_0BA5",
    "Out: D_92A1 = $0B, message queued"
   ],
   "unit": "game/textEngineC000"
  },
  "C7D5": {
   "routine": [
    "handleTextEntryKey - One-key line editor shared by the in-game chat (called from handleChatSend at $1387, inside the raster IRQ, with A = $A3) and the SAVE GAME FILM name field ($8029, A = $10). A = maximum length with bit 7 = 'scroll left when full', Y = cursor position in the 36-byte buffer $9278. It consumes newKeyEvent $90EA: RETURN ($8D) returns with bit 7 set in Y, DEL ($88) backspaces, codes $20-$5F are validated through the ASCII-to-glyph table ($985E,x; entry $3B means 'no glyph', which rejects backslash, caret and underscore) and stored, and the buffer is re-terminated with $9E. Overflow either scrolls the buffer one byte left or beeps (sound 3); an accepted key clicks (sound 2) and clears $90EA and $9248. Returns Z = 1 when nothing was accepted, otherwise A = the accepted key code.",
    "In:  A = max length/flags, Y = cursor position, D_90EA, D_985E table",
    "Out: A = key code or $FF, Z flag, Y advanced (bit 7 on RETURN), D_9278 buffer, D_90EA = $FF, D_9248 = $FF, sound; self-modifies $C813, $C817, $C84F"
   ],
   "unit": "game/textEngineC000"
  },
  "C853": {
   "routine": [
    "showWorkingMessage - Sets persistentMessageId $92A1 to 1 and shows message 1 ('WORKING...') immediately through showStatusMessageNoSound. Called before slow operations (disk access, game start, pause request).",
    "In:  none",
    "Out: D_92A1 = 1, zp_7B = 1, message printed"
   ],
   "unit": "game/textEngineC000"
  },
  "C85B": {
   "routine": [
    "promptPressButtonWhenReady - Prints 'PRESS BUTTON WHEN READY.' ($CBBE) on the status line, then loops stirring the game RNG (nextGameRandom) until checkFirePressed reports a fresh fire-button click, and finally restores/blanks the status line with sub_C39F. Only caller is waitForGameDiskInserted ($10E8), so the wait also randomises the map seed.",
    "In:  fire button (zp_5F/zp_79)",
    "Out: returns after a click; RNG advanced, status line redrawn"
   ],
   "unit": "game/textEngineC000"
  },
  "C86D": {
   "routine": [
    "exitIfGameEnded - If gameEndFlag $92C9 has bit 7 set the routine never returns: it jumps to returnToMainMenu ($0AA3) when gamePhase zp_B1 is non-zero (setup/menu/film) or to endGameToMainMenu ($1ADC) during battle. Otherwise it simply returns. Called at the top of updateGameFrame ($40B2) and from the command-exchange loop ($4E41).",
    "In:  D_92C9, zp_B1",
    "Out: returns, or transfers control to the menu (stack is rebuilt there)"
   ],
   "unit": "game/textEngineC000"
  },
  "C89A": {
   "routine": [
    "playSound - Public sound entry used by ~50 call sites: A = sound id 0-28, calls requestSound and restores X and Y afterwards through two self-modified operands ($C8A4, $C8A6). Because the save slots are static this entry is not re-entrant; the IRQ-side callers ($1370, $140E) deliberately call requestSound directly instead.",
    "In:  A = sound id",
    "Out: sound request queued; X and Y preserved"
   ],
   "unit": "game/textEngineC000"
  },
  "C8A8": {
   "routine": [
    "requestSound - Core of the sound request queue. A = 0 silences everything: slots $92E7-$92E9 become $FF and slot $92EA becomes 0, so the IRQ dequeues sound id 0 and stops all voices. Otherwise the class byte from soundClassTable $C87D[id] decides: a negative class forces the request into slot (class AND 3) - $80/$81/$82 are voices 0/1/2 and $FF is the multi-voice slot 3 - while a positive class (0, 1 or 2) is a plain voice number that is only taken when that slot is idle ($FF) and the sound currently playing on that voice (sndVoiceSoundId $67C8,x) is either silent or has a non-negative (low priority) class. Ids >= $1D are discarded.",
    "In:  A = sound id 0-28; D_C87D, D_92E7-D_92EA, D_67C8",
    "Out: one of D_92E7-D_92EA set to the id (or all cleared); self-modifies $C8DC; A/X/Y clobbered"
   ],
   "unit": "game/textEngineC000"
  },
  "C8E9": {
   "routine": [
    "dequeueSoundRequest - Scans the four request slots from 3 down to 0 for a pending id; the first non-negative one is returned in X and its slot is reset to $FF. X = $FF when all four are empty. Called once per frame from the raster IRQ at $1283, which then hands X to startSound ($6728).",
    "In:  D_92E7-D_92EA",
    "Out: X = sound id or $FF, that slot cleared; A/Y clobbered"
   ],
   "unit": "game/textEngineC000"
  },
  "C8FA": {
   "routine": [
    "updateComcenMoveSound - Keeps the own command centre's engine sound in step with its movement. Runs only during live battle (gamePhase zp_B1 == 0 and playModeFlags $0B9B == 0). If the own comcen ($9236) is moving (unit idle flag $FB54 bit 7 clear) and SID voice 2 is silent it requests sound $14; if the comcen has become idle while voice 2 is still playing $14 or $16 it requests the stop sound $1C. Called for every unit step from processUnitStep ($42A0).",
    "In:  zp_B1, D_0B9B, D_9236, D_FB54, D_67CA (voice 2 current sound)",
    "Out: sound $14 or $1C requested",
    "(confidence: medium)"
   ],
   "unit": "game/textEngineC000"
  },
  "C924": {
   "routine": [
    "setMessageSlot - X = message id, A/Y = string address. Writes the run-time message pointer tables: low byte into $0500,x and high byte into $0524,x. Used for the dynamic slots $1E, $20 and $21 by the boot code ($0B1D/$0B26/$0C8E/$0C97), overlay A ($7BAC/$81C2/$821C/$86B5/$8724) and the comm-module tail ($EED6/$EEFF).",
    "In:  A = string low byte, Y = string high byte, X = message id",
    "Out: D_0500[x], D_0524[x]; A = high byte on exit"
   ],
   "unit": "game/textEngineC000"
  },
  "CD0A": {
   "routine": [
    "showUnitPictureSprites - X = unit type 0-4 (GRUNT/RIDER/BOOMER/SPY/COMCEN). Fetches that type's RLE block address from unitPictureLoTable $CD05,x / unitPictureHiTable $CD00,x, unpacks it into $0200-$03FF with unpackRleTo0200 and copies the first 192 bytes to $8B40, which is the shape area of sprites 5, 6 and 7 (pointers $2D-$2F). Then sets those three sprite colours to white, X positions to 236/268/300 (the two MSBs via $9020 |= $C0), Y position to 162 and enables sprites 5-7 with mask $E0. Only caller is showUnitInfoPanel ($22F1), whose LDX operand at $22EF is patched with the unit type.",
    "In:  X = unit type 0-4",
    "Out: $0200-$03FF overwritten, $8B40-$8BFF = three 24x21 sprite frames, sprite shadows $900D-$900F, $9015-$9017, $901D-$901F, $9020, $9022; zp_B2/B3/1A/1C clobbered"
   ],
   "unit": "game/textEngineC000"
  },
  "C630": {
   "unit": "game/textEngineC000",
   "block": [
    "messageReviewIndex: byteTable, 1 bytes. Single state byte for the message-history scrollback, stored between routines (bit 7 = active, low 7 bits = saved queue read index)."
   ]
  },
  "C87D": {
   "unit": "game/textEngineC000",
   "block": [
    "soundClassTable: byteTable, 29 bytes. Class/priority byte per sound id 0-28 used by requestSound. $00/$01/$02 = normal-priority request for SID voice 0/1/2 (only accepted if that voice is free or busy with another normal sound); $80/$81/$82 = force onto voice 0/1/2; $FF = force onto the whole-chip slot 3 (three-voice fanfares such as ids 13, 14 and 25). Contents: FF 00 00 00 00 00 FF 80 81 01 01 01 80 FF FF 82 82 82 82 82 02 FF 82 82 82 FF 80 81 82."
   ]
  },
  "C92C": {
   "unit": "game/textEngineC000",
   "block": [
    "groupNameTable: text, 56 bytes. Seven 8-byte group names printed with sub_C100 (index = unitIdleFlags bits 2-4): ALPHA, BAKER, CHARLIE, DELTA, EASY, FOXTROT, GROVER. Used by printUnitStatusLines ($23B1), runJoinGroupMenu ($6162) and runGroupOptionsMenu ($634E). An index of 7 would fall into unitTypeNameTable and print GRUNT."
   ]
  },
  "C964": {
   "unit": "game/textEngineC000",
   "block": [
    "unitTypeNameTable: text, 40 bytes. Five 8-byte unit type names indexed by unitTypeTable bits 0-2: GRUNT, RIDER, BOOMER, SPY, COMCEN. Printed by showUnitInfoPanel ($22C9)."
   ]
  },
  "C98C": {
   "unit": "game/textEngineC000",
   "block": [
    "unitStatusNameTable: text, 70 bytes. Nine 8-byte status words: 0 READY, 1 RELOAD, 2 FIRED, 3 ' -   ', 4 REPAIR, 5 MOVE, 6 PINNED, 7 STUN'D, 8 DIGGIN. The WEAPON: line indexes it from $C98C, the ACTION: line from entry 3 ($C9A4) so entries 3-8 become ' -   '/REPAIR/MOVE/PINNED/STUN'D/DIGGIN. The last entry is only 6 bytes long because the next block starts right after it."
   ]
  },
  "C9D2": {
   "unit": "game/textEngineC000",
   "block": [
    "unitOptionMenuText: text, 186 bytes. Twelve $8D-terminated menu lines for the UNIT/GROUP OPTIONS menus, printed in sequence with sub_C133/sub_C137 from $5FCA and $6387: 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: text, 45 bytes. Formation sub-menu, one string with embedded $0D line breaks: 'SWEEP LEFT / SWEEP RIGHT / REVERSE / CUSTOM / (EXIT)'."
   ]
  },
  "CAB9": {
   "unit": "game/textEngineC000",
   "block": [
    "consoleMiscMenuText: text, 48 bytes. Six $8D-terminated lines used by the command-console panels: ' ENERGY', ' BATTLE', ' RADAR', '  DRONE', '  DIG OUT', 'DIG IN'."
   ]
  },
  "CAE9": {
   "unit": "game/textEngineC000",
   "block": [
    "unitInfoLabelText: text, 33 bytes. Label column of the unit info panel, one string with $0D breaks and a $BA terminator: 'GROUP:' + three blank lines + 'ENERGY:' 'WEAPON:' 'ACTION:'. Printed by showUnitInfoPanel ($22E0)."
   ]
  },
  "CB0A": {
   "unit": "game/textEngineC000",
   "block": [
    "consoleHeadingText: text, 21 bytes. Two headings: '    UNIT' ($CB0A) and '   OPTIONS' + $0D ($CB13)."
   ]
  },
  "CB1F": {
   "unit": "game/textEngineC000",
   "block": [
    "uiPromptStrings: text, 218 bytes. Pool of short bit-7-terminated prompts and status words used by the unit/group menus, the command console tabs and the info panel: 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, used by promptPressButtonWhenReady), 'REPAIR / WHICH:' ($CBD6), 'ATTEMPTING REPAIR...' ($CBE5). MISC/STATS/REPAIR are the three command-console tab labels drawn around $1655-$1675."
   ]
  },
  "CBF9": {
   "unit": "game/textEngineC000",
   "block": [
    "gameHeaderText: text, 24 bytes. Status header fragments: ' PLAY' ($CBF9), '1ST' ($CBFE), '2ND' ($CC01) and '\\r  SCENARIO\\r' ($CC04)."
   ]
  },
  "CC11": {
   "unit": "game/textEngineC000",
   "block": [
    "rulesNameTable: text, 14 bytes. Two 8-byte-stride entries selected with sub_C100 at $15C9: STANDARD ($CC11) and CUSTOM ($CC19, only 6 bytes because the next block follows)."
   ]
  },
  "CC1F": {
   "unit": "game/textEngineC000",
   "block": [
    "mainMenuItemText: text, 122 bytes. The six main-menu lines printed in sequence: 'COMPETE WITH MODEM OPPONENT', 'PRACTICE WITH SOLO TRAINER', 'WATCH GAME FILM', 'SAVE GAME FILM' ($CC1F block), 'LOAD GAME FILM' ($CC75) and 'DISCONNECT/SET VOICE' ($CC84)."
   ]
  },
  "CC99": {
   "unit": "game/textEngineC000",
   "block": [
    "inGameMenuText: text, 36 bytes. ENEMY ($CC99), MISSILES ($CC9E), 'ABORT GAME' ($CCA6) and 'VOICE PAUSE' ($CCB1) - the items of the in-game MISC options menu plus two panel captions."
   ]
  },
  "CCBD": {
   "unit": "game/textEngineC000",
   "block": [
    "strGameTypeLabel: text, 11 bytes. 'GAME TYPE:' header printed before the scenario name at $15C5."
   ]
  },
  "CCC8": {
   "unit": "game/textEngineC000",
   "block": [
    "scenarioNameTable: text, 56 bytes. Seven 8-byte scenario names indexed by gameTypeOptions $0BA3 bits 0-2 (printed with sub_C100 at $15D9): SCRIMAGE, QB SNEAK, THE BOMB, FACE-OFF, SLUGGERS, FULL WAR, DEFENDER."
   ]
  },
  "CD00": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureHiTable: byteTable, 5 bytes. High bytes of the five unit-picture RLE block addresses ($CD, $CD, $CE, $CE, $CF). Also probed as a build hook: $0C0D tests whether $CD00 holds $4C (JMP) and, if so, disables VIC IRQs and jumps there - in the shipped overlay it holds table data, so the hook is inert; showUnitInfoPanel ($22EA) only tests that $CD00 is negative (pictures present)."
   ]
  },
  "CD05": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureLoTable: byteTable, 5 bytes. Low bytes of the five unit-picture RLE block addresses ($52, $D3, $3B, $CD, $3A) giving $CD52, $CDD3, $CE3B, $CECD and $CF3A."
   ]
  },
  "CD52": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureGrunt: sprite, 129 bytes. RLE block (escape marker $05) for unit type 0 = GRUNT; the first 192 unpacked bytes are three 24x21 sprite frames (a helmeted figure, a running figure and a burst)."
   ]
  },
  "CDD3": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureRider: sprite, 104 bytes. RLE block (escape marker $07) for unit type 1 = RIDER; three 24x21 sprite frames."
   ]
  },
  "CE3B": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureBoomer: sprite, 146 bytes. RLE block (escape marker $08) for unit type 2 = BOOMER; three 24x21 sprite frames (launcher, missile in flight, emplacement)."
   ]
  },
  "CECD": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureSpy: sprite, 109 bytes. RLE block (escape marker $05) for unit type 3 = SPY; three 24x21 sprite frames (antenna/aerial shapes)."
   ]
  },
  "CF3A": {
   "unit": "game/textEngineC000",
   "block": [
    "unitPictureComcen: sprite, 163 bytes. RLE block (escape marker $0A) for unit type 4 = COMCEN; three 24x21 sprite frames of the command centre building. Its 192nd unpacked byte (the unused pad of sprite 7) is fetched from $CFDD, which is already inside the leftover code fragment at the end of the page."
   ]
  }
 }
}