{
 "chunk": "game_ovl_C000_C000",
 "unit": "game/ovl_C000",
 "routines": [
  {
   "addr": "C01A",
   "name": "printChar",
   "summary": "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.",
   "inputs": "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",
   "outputs": "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",
   "confidence": "high"
  },
  {
   "addr": "C030",
   "name": "restoreRegsAXY",
   "summary": "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.",
   "inputs": "zp_54 (A), zp_55 (X), zp_56 (Y)",
   "outputs": "A, X, Y restored; N/Z reflect A",
   "confidence": "high"
  },
  {
   "addr": "C03D",
   "name": "handleControlChar",
   "summary": "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.",
   "inputs": "A = control code",
   "outputs": "cursor moved, possibly one cell erased; cursor pointers recomputed",
   "confidence": "high"
  },
  {
   "addr": "C041",
   "name": "printNewline",
   "summary": "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).",
   "inputs": "zp_39-zp_44 window state",
   "outputs": "zp_3F = 0, zp_40 advanced, zp_B4-zp_B7 recomputed",
   "confidence": "high"
  },
  {
   "addr": "C06A",
   "name": "clearTextWindow",
   "summary": "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.",
   "inputs": "window zp_39-zp_3E",
   "outputs": "window filled with zp_3E, cursor left at the top left",
   "confidence": "high"
  },
  {
   "addr": "C075",
   "name": "clearToWindowBottom",
   "summary": "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.",
   "inputs": "cursor zp_3F/zp_40, window zp_3A/zp_3C, blank char zp_3E, colour zp_3D",
   "outputs": "cells filled; cursor unchanged on exit",
   "confidence": "high"
  },
  {
   "addr": "C07A",
   "name": "clearToEndOfLine",
   "summary": "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.",
   "inputs": "cursor zp_3F/zp_40, window width zp_3C, blank char zp_3E",
   "outputs": "rest of the line filled with zp_3E; cursor restored; self-modifies the compare operand at $C090",
   "confidence": "high"
  },
  {
   "addr": "C097",
   "name": "setCursorRow",
   "summary": "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.",
   "inputs": "A = row",
   "outputs": "zp_40 = A, zp_B4-zp_B7 recomputed",
   "confidence": "high"
  },
  {
   "addr": "C099",
   "name": "updateCursorPointers",
   "summary": "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.",
   "inputs": "zp_3F, zp_40, zp_3B, D_9001",
   "outputs": "zp_B4/zp_B5 = bitmap cell address, zp_B6/zp_B7 = screen or colour RAM address; X/Y preserved",
   "confidence": "high"
  },
  {
   "addr": "C0C0",
   "name": "plotCharAndAdvance",
   "summary": "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.",
   "inputs": "A = glyph index, window/cursor state",
   "outputs": "glyph drawn, cursor advanced, pointers updated",
   "confidence": "high"
  },
  {
   "addr": "C0D7",
   "name": "setFullScreenWindow",
   "summary": "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.",
   "inputs": "none",
   "outputs": "zp_39 = 0, zp_3A = $19, zp_3B = 0, zp_3C = $28",
   "confidence": "high"
  },
  {
   "addr": "C0E8",
   "name": "saveRegsAXY",
   "summary": "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.",
   "inputs": "A, X, Y",
   "outputs": "zp_54 = A, zp_55 = X, zp_56 = Y",
   "confidence": "high"
  },
  {
   "addr": "C0EF",
   "name": "printFillChars",
   "summary": "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.",
   "inputs": "A = repeat count, zp_3E = fill character, zp_3D = colour",
   "outputs": "A cells filled, cursor advanced",
   "confidence": "high"
  },
  {
   "addr": "C100",
   "name": "printTableEntry",
   "summary": "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.",
   "inputs": "A/Y = table base, X = entry index",
   "outputs": "string printed; zp_18/zp_19/zp_1A clobbered",
   "confidence": "high"
  },
  {
   "addr": "C121",
   "name": "printStringCentred",
   "summary": "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.",
   "inputs": "A/Y = string pointer, zp_3C = window width",
   "outputs": "D_90EF = zp_3C (cleared again by printStringAtPointer), string printed centred",
   "confidence": "high"
  },
  {
   "addr": "C12B",
   "name": "printNextStringCentred",
   "summary": "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.",
   "inputs": "zp_A2/zp_A3 = string pointer, zp_3C = window width",
   "outputs": "D_90EF = zp_3C, next string printed centred",
   "confidence": "high"
  },
  {
   "addr": "C133",
   "name": "printString",
   "summary": "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).",
   "inputs": "A = pointer low, Y = pointer high; the string ends with a character that has bit 7 set",
   "outputs": "string printed at the cursor, zp_A2/zp_A3 left just past the terminator",
   "confidence": "high"
  },
  {
   "addr": "C137",
   "name": "printStringAtPointer",
   "summary": "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.",
   "inputs": "zp_A2/zp_A3 = string, D_90EF = field width or 0, zp_42 = pad colour, zp_3E = blank character",
   "outputs": "text drawn, zp_A2/zp_A3 advanced past the terminator, D_90EF = 0; self-modifies the length operand at $C159",
   "confidence": "high"
  },
  {
   "addr": "C17D",
   "name": "printDecimal",
   "summary": "Prints an unsigned 24-bit value as decimal without leading zeros: converts it with convertToDecimalDigits and then prints the digits (printDecimalDigits).",
   "inputs": "A = value low, Y = value middle, zp_7E = value high byte",
   "outputs": "digits printed, digit buffer $92DA filled, zp_7E cleared, zp_18-zp_1F clobbered",
   "confidence": "high"
  },
  {
   "addr": "C183",
   "name": "convertToDecimalDigits",
   "summary": "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.",
   "inputs": "A/Y/zp_7E = 24-bit value",
   "outputs": "$92DA+1..$92DA+8 digits, zp_1E = index of the first digit, zp_7E = 0, zp_18-zp_1F clobbered",
   "confidence": "high"
  },
  {
   "addr": "C1E2",
   "name": "printDecimalDigits",
   "summary": "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.",
   "inputs": "zp_1E = first digit index, $92DA buffer",
   "outputs": "digits printed",
   "confidence": "high"
  },
  {
   "addr": "C1F0",
   "name": "plotGlyph",
   "summary": "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.",
   "inputs": "A = glyph index, zp_B4-zp_B7 cell pointers, zp_3D colour, zp_44, D_9001",
   "outputs": "8 bitmap bytes and one colour byte written; zp_B2/zp_B3 = glyph pointer; A preserved",
   "confidence": "high"
  },
  {
   "addr": "C24B",
   "name": "plotGlyphDoubleSize",
   "summary": "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.",
   "inputs": "zp_B2/zp_B3 = glyph, zp_B4-zp_B7 = destination cell, zp_3D = colour, zp_BA = edge mask",
   "outputs": "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",
   "confidence": "high"
  },
  {
   "addr": "C307",
   "name": "plotGlyphLeftHalf",
   "summary": "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.",
   "inputs": "zp_BA, zp_1B = source row offset (0 or 4)",
   "outputs": "8 bitmap bytes of one cell; zp_1D = $F0",
   "confidence": "high"
  },
  {
   "addr": "C315",
   "name": "plotGlyphRightHalf",
   "summary": "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.",
   "inputs": "zp_BA, zp_1B = source row offset",
   "outputs": "8 bitmap bytes of one cell; zp_1D = $0F",
   "confidence": "high"
  },
  {
   "addr": "C320",
   "name": "expandGlyphNibbles",
   "summary": "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.",
   "inputs": "$90B8 glyph rows, zp_1B = 0 or 4, zp_1D = $F0 or $0F, destination operands at $C33A/$C33D",
   "outputs": "8 bitmap bytes written; zp_1A = byte counter",
   "confidence": "high"
  },
  {
   "addr": "C34B",
   "name": "colourDoubleSizeCell",
   "summary": "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.",
   "inputs": "zp_B6/zp_B7 = cell colour pointer, zp_3D, zp_BA",
   "outputs": "up to 4 colour bytes written; zp_B6/zp_B7 advanced by one screen row",
   "confidence": "high"
  },
  {
   "addr": "C367",
   "name": "colourCellPair",
   "summary": "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.",
   "inputs": "zp_B6/zp_B7, zp_3D, zp_BA",
   "outputs": "up to 2 colour bytes written",
   "confidence": "high"
  },
  {
   "addr": "C37F",
   "name": "waitFrames",
   "summary": "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.",
   "inputs": "A = number of frames",
   "outputs": "returns after A frames; A = 0",
   "confidence": "high"
  },
  {
   "addr": "C397",
   "name": "waitRasterBelowScreen",
   "summary": "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.",
   "inputs": "none",
   "outputs": "returns with the raster in the lower border; A = raster value",
   "confidence": "high"
  },
  {
   "addr": "C39F",
   "name": "clearMessageLine",
   "summary": "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).",
   "inputs": "D_90FB (screen id), D_9001",
   "outputs": "message line filled with zp_3E; text window restored from $92B6",
   "confidence": "high"
  },
  {
   "addr": "C3AB",
   "name": "setMessageLineWindow",
   "summary": "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.",
   "inputs": "D_90FB, D_9001",
   "outputs": "zp_3A-zp_3D, zp_3F, zp_40, zp_42 set; cell pointers recomputed",
   "confidence": "high"
  },
  {
   "addr": "C3D8",
   "name": "showMessageString",
   "summary": "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).",
   "inputs": "A/Y = string pointer",
   "outputs": "status line rewritten; text window restored",
   "confidence": "high"
  },
  {
   "addr": "C3E0",
   "name": "printOnMessageLine",
   "summary": "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).",
   "inputs": "A/Y = string pointer, X = centring width (0 = left aligned)",
   "outputs": "message line drawn, window still set to the message line on return",
   "confidence": "high"
  },
  {
   "addr": "C3F9",
   "name": "advanceFillPointer",
   "summary": "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).",
   "inputs": "zp_48/zp_49 = current address, zp_4A/zp_4B = last address",
   "outputs": "C = 1 when the pointer had reached the limit; zp_48/zp_49 and zp_4E/zp_4F incremented",
   "confidence": "high"
  },
  {
   "addr": "C40E",
   "name": "restoreTextWindow",
   "summary": "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.",
   "inputs": "$92B6-$92C1",
   "outputs": "zp_39-zp_44 restored, cell pointers recomputed",
   "confidence": "high"
  },
  {
   "addr": "C41B",
   "name": "saveTextWindow",
   "summary": "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.",
   "inputs": "zp_39-zp_44",
   "outputs": "$92B6-$92C1 = window state; window reset to 0,0,25,40",
   "confidence": "high"
  },
  {
   "addr": "C428",
   "name": "splitDecimalDigits",
   "summary": "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).",
   "inputs": "A = value 0-255",
   "outputs": "$C44E/$C44F/$C450 = hundreds/tens/ones; X clobbered",
   "confidence": "high"
  },
  {
   "addr": "C451",
   "name": "checkUiContinue",
   "summary": "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.",
   "inputs": "D_0B9B (film/solo flag), D_92C9, zp_BE, zp_B1, zp_66, D_9248",
   "outputs": "A = 1 continue / 0 abort; $0B7D = $3C on abort",
   "confidence": "high"
  },
  {
   "addr": "C470",
   "name": "checkSetupUiContinue",
   "summary": "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.",
   "inputs": "zp_B1 gamePhase, D_92C6, D_0B9F",
   "outputs": "A = 1 continue / 0 abort; $0B7D = $3C on abort",
   "confidence": "medium"
  },
  {
   "addr": "C493",
   "name": "serviceChatDisplay",
   "summary": "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.",
   "inputs": "D_929C chat state, D_92A0 raw key code, zp_7A",
   "outputs": "D_929C updated, status line redrawn, zp_7A/zp_7B/zp_72 updated; may discard updateStatusMessage's return address",
   "confidence": "medium"
  },
  {
   "addr": "C4DA",
   "name": "drawTypedChatLine",
   "summary": "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.",
   "inputs": "$9278 line buffer",
   "outputs": "message line redrawn with a cursor; zp_3D = $11; window restored",
   "confidence": "high"
  },
  {
   "addr": "C4EF",
   "name": "updateStatusMessage",
   "summary": "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.",
   "inputs": "zp_72, zp_7A, zp_7B, D_9277, D_92FA, zp_B1, D_91CB, D_9208, message tables $0500/$0524",
   "outputs": "status line updated, zp_72 = $3C, zp_7A bit 7 set, D_9208 = clock; self-modifies the sound flag at $C54B",
   "confidence": "high"
  },
  {
   "addr": "C557",
   "name": "playMessageSound",
   "summary": "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.",
   "inputs": "Y = message id, table $0612",
   "outputs": "sound started via sub_C89A",
   "confidence": "high"
  },
  {
   "addr": "C560",
   "name": "queueMessageUnlessFilm",
   "summary": "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!', ...).",
   "inputs": "A = message id, D_0B9B",
   "outputs": "message queued or ignored",
   "confidence": "high"
  },
  {
   "addr": "C566",
   "name": "queueMessage",
   "summary": "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.",
   "inputs": "A = message id, Y preserved, $9249-$9252",
   "outputs": "queue updated, zp_72/zp_7B possibly updated; self-modifies operands at $C5B0, $C5B4 and $C5BB",
   "confidence": "medium"
  },
  {
   "addr": "C5C9",
   "name": "decMessageIndex",
   "summary": "Decrements the message-queue index in Y modulo 8 (wraps to 7).",
   "inputs": "Y",
   "outputs": "Y = (Y - 1) mod 8",
   "confidence": "high"
  },
  {
   "addr": "C5CF",
   "name": "incMessageIndex",
   "summary": "Increments the message-queue index in Y modulo 8 (wraps to 0).",
   "inputs": "Y",
   "outputs": "Y = (Y + 1) mod 8",
   "confidence": "high"
  },
  {
   "addr": "C5D7",
   "name": "redrawStatusMessage",
   "summary": "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.",
   "inputs": "zp_7B, D_9277",
   "outputs": "status line redrawn",
   "confidence": "high"
  },
  {
   "addr": "C5E4",
   "name": "showMessageById",
   "summary": "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.",
   "inputs": "A = message id 0-$23",
   "outputs": "status line redrawn; X = message id",
   "confidence": "high"
  }
 ],
 "variables": [
  {"addr": "18", "scope": "zp", "name": "mathWorkLo", "meaning": "Low byte of the 24-bit dividend in convertToDecimalDigits and of the table base in printTableEntry", "confidence": "high"},
  {"addr": "19", "scope": "zp", "name": "mathWorkMid", "meaning": "Middle byte of the 24-bit value / high byte of the table base", "confidence": "high"},
  {"addr": "1A", "scope": "zp", "name": "mathWorkHi", "meaning": "High byte of the 24-bit value; also the byte counter inside expandGlyphNibbles", "confidence": "high"},
  {"addr": "1B", "scope": "zp", "name": "divRemainderLo", "meaning": "Low byte of the division remainder; in the 2x glyph magnifier it is the source row offset (0 = top half, 4 = bottom half)", "confidence": "high"},
  {"addr": "1C", "scope": "zp", "name": "divRemainderMid", "meaning": "Middle byte of the division remainder", "confidence": "high"},
  {"addr": "1D", "scope": "zp", "name": "divRemainderHi", "meaning": "High byte of the division remainder; in the magnifier it is the nibble mask $F0 (left half) or $0F (right half)", "confidence": "high"},
  {"addr": "1E", "scope": "zp", "name": "decimalDigitIndex", "meaning": "Index of the most significant digit in the decimal buffer at $92DA", "confidence": "high"},
  {"addr": "1F", "scope": "zp", "name": "divBitCounter", "meaning": "Bit counter ($18 = 24) of the divide-by-10 loop", "confidence": "high"},
  {"addr": "39", "scope": "zp", "name": "windowTopRow", "meaning": "Text window top row (home row used by clearTextWindow)", "confidence": "high"},
  {"addr": "3A", "scope": "zp", "name": "windowBottomRow", "meaning": "Text window bottom row (last row cleared by clearToWindowBottom)", "confidence": "high"},
  {"addr": "3B", "scope": "zp", "name": "windowLeftColumn", "meaning": "Text window left column, added to the cursor column to get the screen column", "confidence": "high"},
  {"addr": "3C", "scope": "zp", "name": "windowWidth", "meaning": "Text window width in characters; also the default centring width", "confidence": "high"},
  {"addr": "3D", "scope": "zp", "name": "textColour", "meaning": "Colour byte written for each character cell - into colour RAM ($D800) in multicolour mode, into screen RAM ($8C00) in hires mode", "confidence": "high"},
  {"addr": "3E", "scope": "zp", "name": "blankGlyph", "meaning": "Glyph index used as the fill/blank character by the clear and pad routines", "confidence": "high"},
  {"addr": "3F", "scope": "zp", "name": "cursorColumn", "meaning": "Cursor column relative to the window left edge", "confidence": "high"},
  {"addr": "40", "scope": "zp", "name": "cursorRow", "meaning": "Cursor row (absolute screen row 0-24)", "confidence": "high"},
  {"addr": "41", "scope": "zp", "name": "textStateSpare", "meaning": "Part of the 12-byte text state saved to $92B6; written elsewhere ($1555, $1FDF, $2D46, $7112, $7A4F) but not read by this chunk", "confidence": "low"},
  {"addr": "42", "scope": "zp", "name": "padColour", "meaning": "Colour used for the blanks printed by the centring pad in printStringAtPointer", "confidence": "high"},
  {"addr": "43", "scope": "zp", "name": "rawGlyphMode", "meaning": "Bit 7 set = printChar takes A as a glyph index and skips the ASCII translation and control-code handling", "confidence": "high"},
  {"addr": "44", "scope": "zp", "name": "doubleSizeText", "meaning": "Bit 7 set = characters are drawn 2x2 cells (plotGlyphDoubleSize) and the cursor advances two columns / two rows", "confidence": "high"},
  {"addr": "48", "scope": "zp", "name": "fillPointer", "meaning": "16-bit current address of the block-fill loops driven by advanceFillPointer (with zp_49)", "confidence": "high"},
  {"addr": "4A", "scope": "zp", "name": "fillLimit", "meaning": "16-bit last address of the block-fill loops (with zp_4B)", "confidence": "high"},
  {"addr": "4E", "scope": "zp", "name": "fillCounter", "meaning": "16-bit counter incremented by advanceFillPointer (with zp_4F); no code in the disassembly reads it", "confidence": "medium"},
  {"addr": "54", "scope": "zp", "name": "savedA", "meaning": "A saved by saveRegsAXY / restored by restoreRegsAXY", "confidence": "high"},
  {"addr": "55", "scope": "zp", "name": "savedX", "meaning": "X saved by saveRegsAXY", "confidence": "high"},
  {"addr": "56", "scope": "zp", "name": "savedY", "meaning": "Y saved by saveRegsAXY", "confidence": "high"},
  {"addr": "66", "scope": "zp", "name": "currentKey", "meaning": "Pending screen/mode change key from the IRQ key poller; a non-negative value aborts modal UI (checkUiContinue)", "confidence": "medium"},
  {"addr": "72", "scope": "zp", "name": "messageTimer", "meaning": "Countdown (set to $3C, decremented every 4 frames by updateFrameTimers) for how long the current status message stays up; 0 = ready for the next message", "confidence": "high"},
  {"addr": "7A", "scope": "zp", "name": "pendingMessageId", "meaning": "Message id waiting to be shown; bit 7 set = already shown / nothing pending", "confidence": "high"},
  {"addr": "7B", "scope": "zp", "name": "shownMessageId", "meaning": "Message id currently on the status line; bit 7 set = the line is free", "confidence": "high"},
  {"addr": "7E", "scope": "zp", "name": "decimalValueHigh", "meaning": "High (third) byte of the 24-bit value passed to printDecimal; cleared by the conversion", "confidence": "high"},
  {"addr": "A2", "scope": "zp", "name": "stringPointer", "meaning": "Pointer to the string being printed (with zp_A3); left just past the terminator so the next string can follow", "confidence": "high"},
  {"addr": "B1", "scope": "zp", "name": "gamePhase", "meaning": "0 = battle running, non-zero = setup/other phases; bits 6/7 checked by checkUiContinue", "confidence": "medium"},
  {"addr": "B2", "scope": "zp", "name": "glyphPointer", "meaning": "Pointer to the 8 bytes of the glyph being drawn (with zp_B3)", "confidence": "high"},
  {"addr": "B4", "scope": "zp", "name": "bitmapPointer", "meaning": "Bitmap address of the current character cell ($A000 + row*320 + col*8), set by calcCellPointersXY", "confidence": "high"},
  {"addr": "B6", "scope": "zp", "name": "colourPointer", "meaning": "Colour destination of the current cell: screen RAM $8C00 + row*40 + col, or colour RAM ($D800 + ...) in multicolour mode", "confidence": "high"},
  {"addr": "BA", "scope": "zp", "name": "magnifyEdgeMask", "meaning": "Quadrant suppression mask for plotGlyphDoubleSize: bit 0 = skip top cells, bit 1 = skip bottom cells, bit 2 = skip left cells, bit 3 = skip right cells (set only by magnifyWindow2x)", "confidence": "high"},
  {"addr": "BE", "scope": "zp", "name": "pauseState", "meaning": "Non-zero while the session is paused; keeps modal UI alive in checkUiContinue", "confidence": "medium"},
  {"addr": "0500", "scope": "abs", "name": "messagePtrLoTable", "meaning": "Low bytes of the 36 status message string pointers (ids 0-$23); copy of overlay $7000, some slots rewritten at run time by $C924", "confidence": "high"},
  {"addr": "0524", "scope": "abs", "name": "messagePtrHiTable", "meaning": "High bytes of the 36 status message string pointers", "confidence": "high"},
  {"addr": "0548", "scope": "abs", "name": "chatLineBuffer", "meaning": "Message 0 string - the received chat line ($0549 onwards, marker byte $0548, $9E = empty)", "confidence": "high"},
  {"addr": "0612", "scope": "abs", "name": "messageSoundTable", "meaning": "Sound number per message id; a negative entry ($80) means the message is silent", "confidence": "high"},
  {"addr": "0B7D", "scope": "abs", "name": "inputLockoutTimer", "meaning": "Frames during which joystick/keyboard input is ignored; set to $3C when checkUiContinue aborts", "confidence": "high"},
  {"addr": "0B9B", "scope": "abs", "name": "filmPlaybackFlag", "meaning": "Bit 7 set during film playback / solo trainer; suppresses queueMessageUnlessFilm and part of checkUiContinue", "confidence": "high"},
  {"addr": "0B9F", "scope": "abs", "name": "localPlayerIndex", "meaning": "Which side the local player is (0/1)", "confidence": "medium"},
  {"addr": "9001", "scope": "abs", "name": "vicD016Shadow", "meaning": "Shadow of $D016; bit 4 (multicolour) selects the multicolour font at $98BE and colour-RAM output, otherwise the hires font at $9A96 and screen-RAM output", "confidence": "high"},
  {"addr": "90B8", "scope": "abs", "name": "glyphScratchBuffer", "meaning": "8-byte copy of the glyph being magnified by plotGlyphDoubleSize", "confidence": "high"},
  {"addr": "90EF", "scope": "abs", "name": "textCentreWidth", "meaning": "Field width for centring the next string (0 = no centring); cleared again by printStringAtPointer", "confidence": "high"},
  {"addr": "90FB", "scope": "abs", "name": "currentScreenId", "meaning": "Active screen; >= 3 (menu screen) moves the message line from row 23 to row 22", "confidence": "high"},
  {"addr": "91CB", "scope": "abs", "name": "gameClock", "meaning": "Game clock shown on the status line; a change triggers the clock refresh in updateStatusMessage", "confidence": "high"},
  {"addr": "9208", "scope": "abs", "name": "lastShownClock", "meaning": "Value of gameClock the last time the status line clock was drawn", "confidence": "high"},
  {"addr": "9248", "scope": "abs", "name": "lastTypedKey", "meaning": "Last typed key code; $A0 (SPACE) aborts modal UI, $8B/$8E drive the message review handler", "confidence": "high"},
  {"addr": "9249", "scope": "abs", "name": "messageQueue", "meaning": "8-entry ring buffer of pending message ids ($9249-$9250)", "confidence": "high"},
  {"addr": "9251", "scope": "abs", "name": "messageQueueHead", "meaning": "Write index (next free slot) of messageQueue, 0-7", "confidence": "high"},
  {"addr": "9252", "scope": "abs", "name": "messageQueueTail", "meaning": "Read index (next message to show) of messageQueue, 0-7", "confidence": "high"},
  {"addr": "9277", "scope": "abs", "name": "chatTextLength", "meaning": "Number of received chat characters buffered at $9253; non-zero makes the status line show the chat line", "confidence": "high"},
  {"addr": "9278", "scope": "abs", "name": "typedLineBuffer", "meaning": "Locally typed chat / name entry line, bit-7 terminated, drawn by drawTypedChatLine", "confidence": "high"},
  {"addr": "929C", "scope": "abs", "name": "chatState", "meaning": "Chat/typing state: bit 7 = typing mode, bit 6 = the incoming chat line is being peeked at (key $8B held), positive non-zero = the typed line must be redrawn once", "confidence": "medium"},
  {"addr": "92A0", "scope": "abs", "name": "rawKeyCode", "meaning": "Raw key code of the key currently held, stored by the IRQ key poller ($0E59)", "confidence": "high"},
  {"addr": "92A1", "scope": "abs", "name": "persistentMessageId", "meaning": "Message id redisplayed when the queue runs dry ($9E = none)", "confidence": "high"},
  {"addr": "92B6", "scope": "abs", "name": "savedTextWindow", "meaning": "12-byte save area for zp_39-zp_44 used by saveTextWindow/restoreTextWindow (single slot, not nestable)", "confidence": "high"},
  {"addr": "92C6", "scope": "abs", "name": "setupUiStopFlag", "meaning": "Written at $7C4A; together with gamePhase it decides in checkSetupUiContinue whether the setup-phase UI may continue", "confidence": "low"},
  {"addr": "92C9", "scope": "abs", "name": "gameEndCountdown", "meaning": "Non-zero while the game is ending; makes checkUiContinue abort every modal UI", "confidence": "high"},
  {"addr": "92DA", "scope": "abs", "name": "decimalDigitBuffer", "meaning": "9-byte buffer holding the decimal digits produced by convertToDecimalDigits (digits at +1..+8, stored as $B0 + digit)", "confidence": "high"},
  {"addr": "92FA", "scope": "abs", "name": "clockDisplayEnable", "meaning": "Checked by updateStatusMessage (must be negative) before the status line falls back to the clock/score display", "confidence": "low"},
  {"addr": "987E", "scope": "abs", "name": "asciiToGlyphTable", "meaning": "64-byte translation table ASCII $20-$5F -> glyph index 0-$3B used by printChar", "confidence": "high"},
  {"addr": "98BE", "scope": "abs", "name": "fontMulticolour", "meaning": "60 glyphs of 8 bytes ($98BE-$9A95) drawn in multicolour mode; all pixels use bit pair 11 so the colour comes from colour RAM", "confidence": "high"},
  {"addr": "9A96", "scope": "abs", "name": "fontHires", "meaning": "The same 60 glyphs in single-bit-per-pixel form, used when $D016 bit 4 is clear", "confidence": "high"},
  {"addr": "C22B", "scope": "abs", "name": "glyphDrawOpcode", "meaning": "Opcode byte of the glyph fetch inside plotGlyph, patched by setBitmapDrawMode ($2A41) to LDA/ORA/EOR/CMP (zp),Y", "confidence": "high"},
  {"addr": "C44E", "scope": "abs", "name": "decimalHundreds", "meaning": "Hundreds digit produced by splitDecimalDigits", "confidence": "high"},
  {"addr": "C44F", "scope": "abs", "name": "decimalTens", "meaning": "Tens digit produced by splitDecimalDigits", "confidence": "high"},
  {"addr": "C450", "scope": "abs", "name": "decimalOnes", "meaning": "Ones digit produced by splitDecimalDigits", "confidence": "high"}
 ],
 "dataBlocks": [
  {"addr": "C000", "length": 4, "type": "byteTable", "name": "bitPairHighTable", "description": "$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)."},
  {"addr": "C004", "length": 4, "type": "byteTable", "name": "solidColourByteTable", "description": "$00,$55,$AA,$FF - the byte that fills a whole multicolour bitmap row with colour 0-3. No reference in the disassembly."},
  {"addr": "C008", "length": 8, "type": "byteTable", "name": "bitMaskTable", "description": "$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)."},
  {"addr": "C010", "length": 4, "type": "byteTable", "name": "pixelPairMaskTable", "description": "$C0,$30,$0C,$03 - the four multicolour pixel-pair masks of a bitmap byte. No reference in the disassembly."},
  {"addr": "C014", "length": 4, "type": "byteTable", "name": "pixelPairClearMaskTable", "description": "$3F,$CF,$F3,$FC - complements of pixelPairMaskTable. No reference in the disassembly."},
  {"addr": "C018", "length": 2, "type": "unknown", "name": "unusedC018", "description": "$A9,$8D - two bytes between the mask tables and printChar; no reference, meaning unknown."},
  {"addr": "C23B", "length": 16, "type": "byteTable", "name": "pixelDoubleTable", "description": "$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."},
  {"addr": "C44E", "length": 3, "type": "byteTable", "name": "decimalDigits", "description": "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."}
 ],
 "misclassified": [
  {"addr": "C0BD", "length": 3, "actual": "code", "evidence": "The three bytes $A9,$04,$60 disassemble as 'lda #$04 / rts'. They sit between the RTS of updateCursorPointers ($C0BC) and plotCharAndAdvance ($C0C0) and nothing branches, jumps or calls there - unreferenced dead code left in the overlay, not data."}
 ],
 "insights": [
  "This chunk is the game's text/glyph engine and status-message system, not a loader: the file still carries the boot loader's labels (bootEntry, bootLoadSectors, bootSetDest, bootReadOneSector, bootReceiveSectorData, bootDestLo, bootMain, rtiVector, bootShowTitlePicture, bootModemInit, rs232Params, strHayesInit, strBlockExecute, strGameChar, bootSavedCpuPort, bootDiskIdTable, bootDone) because boot/loader_C000.s occupies the same addresses. In the overlay every one of those labels lands in the middle of an unrelated instruction or table; only the comments already marked 'stale' warn about it. XREF.txt merges the two units as well, so caller lists for $C0xx-$C3xx contain both boot-loader and overlay addresses.",
  "Text API (matches and extends the notes in game_main_0800_163A): window = zp_39 top, zp_3A bottom, zp_3B left, zp_3C width; cursor = zp_3F column (relative to zp_3B) and zp_40 row (absolute); zp_3D = colour byte, zp_3E = blank glyph, zp_42 = pad colour, zp_43 bit 7 = raw glyph mode, zp_44 bit 7 = double-size characters. The whole block zp_39-zp_44 is saved/restored to $92B6 by $C41B/$C40E - a single slot, so the pair must not be nested.",
  "Character rendering: printChar translates ASCII $20-$5F through the 64-byte table at $987E into glyph indices 0-$3B; glyph 0 is blank. Two 60-glyph fonts of 8 bytes each hold the same shapes: $98BE-$9A95 for multicolour mode (every pixel is bit pair 11, so the colour byte zp_3D goes to colour RAM $D800) and $9A96 for hires mode (colour byte goes to screen RAM $8C00). updateCursorPointers ($C099) is what turns the screen pointer into a colour-RAM pointer by adding $4C to zp_B7 when $D016 shadow D_9001 bit 4 is set. This font is distinct from the 114-glyph map font at $9426 used by plotGlyph ($2AC8).",
  "The glyph copy loop at $C227-$C231 reads the existing bitmap byte first and its second opcode ($C22B) is patched by setBitmapDrawMode ($2A41) to LDA/ORA/EOR/CMP (zp),Y, so text can be drawn opaque, ORed, EORed (highlight) or transparently. The matching colour instruction at $C233 ('lda (zp_B6),y' followed by 'lda zp_3D') is never patched, so the colour byte is always replaced - the load at $C233 is dead.",
  "$C24B is a general 2x2 magnifier used both by the double-size text mode and by magnifyWindow2x ($2E0F): it expands one glyph into four cells via the nibble-doubling table at $C23B, with zp_BA masking off quadrants (bit 0 top, bit 1 bottom, bit 2 left, bit 3 right) so odd-sized zoom rectangles keep half-size edges. Note that zp_BA is only ever written by magnifyWindow2x, so double-size text depends on whatever mask that routine left behind.",
  "Status bar: setMessageLineWindow ($C3AB) defines it as one row, columns 2-37 (left 2, width $24), row 23 normally and row 22 when currentScreenId $90FB >= 3; the colour is 0 in multicolour mode and 7 in hires. showMessageString ($C3D8) centres a string there and restores the caller's window; clearMessageLine ($C39F) blanks it.",
  "Message system: ids 0-$23 index the pointer tables $0500 (lo) / $0524 (hi) and the sound table $0612 ($80 = silent). queueMessage ($C566) keeps an 8-entry ring at $9249 (write index $9251, read index $9252) sorted so higher ids win; updateStatusMessage ($C4EF), called from every polling loop, shows the pending id for $3C ticks (zp_72), plays its sound, and when nothing is queued either shows the persistent message $92A1 or refreshes the clock/score line at $C6B6 whenever gameClock $91CB changes. zp_7A = pending id, zp_7B = displayed id, bit 7 set means 'none'.",
  "Chat integration: while typing (chatState $929C bit 7) the status line shows the local line buffer $9278 with a $11-coloured cursor block; holding key $8B peeks at the received chat line (message 0, text at $0548) and releasing it restores the typed line. serviceChatDisplay pops two bytes off the stack in that case so updateStatusMessage returns to its caller without touching the queue.",
  "checkUiContinue ($C451) is the common 'may this modal UI keep running' test used by the menu selector, the info panel and the group editor: it returns 0 (and arms the 60-frame input lockout $0B7D) on game end, on a pending screen change (zp_66 >= 0), on SPACE ($9248 = $A0) or when the setup-phase test at $C470 says stop.",
  "Leftovers worth knowing: the newline routine compares the row against the window bottom and branches to the next instruction either way ($C04D-$C053), so windows never scroll; splitDecimalDigits has a dead leading-zero test at $C444-$C448; advanceFillPointer keeps a 16-bit counter in zp_4E/zp_4F that nothing reads; three unreferenced mask tables sit at $C000/$C004/$C010/$C014 and three unreferenced bytes 'lda #$04 / rts' at $C0BD; updateStatusMessage starts with a NOP that no code patches.",
  "Register conventions: printChar preserves A/X/Y using the zp_54/zp_55/zp_56 triple ($C0E8 save, $C030 restore); the same triple is borrowed by the viewport drawing code at $5C81-$5CA1. updateCursorPointers and plotGlyphDoubleSize preserve X/Y through self-modified immediate operands rather than the stack."
 ]
}
