{
 "chunk": "game_main_0800_0800",
 "unit": "game/main_0800",
 "routines": [
  {
   "addr": "0800",
   "name": "gameEntry",
   "summary": "Entry point jumped to by the boot code at $02B8; just JMPs to mainStart.",
   "inputs": "none",
   "outputs": "none",
   "confidence": "high"
  },
  {
   "addr": "0804",
   "name": "loadSectors",
   "summary": "Fast-loader read: reads A consecutive sectors of track Y starting at sector X into the address set by setLoadDest (one page per sector). On a drive error it shows msgDiskError via $C3D8 and retries the same sector forever; once a retried read succeeds it calls $C6FF with $92A1 to restore the message area.",
   "inputs": "A=sector count, Y=track, X=first sector, loadDestLo/Hi",
   "outputs": "C=0, memory filled, loadDestHi advanced, ldTrack/ldSector/ldCount",
   "confidence": "high"
  },
  {
   "addr": "0843",
   "name": "writeSectors",
   "summary": "Fast-loader write: writes A consecutive sectors to track Y from sector X, source address from setLoadDest. Used by the overlay-A save routines ($7E85, $8194).",
   "inputs": "A=count, Y=track, X=first sector, loadDestLo/Hi",
   "outputs": "C=1 on failure, C=0 on success",
   "confidence": "high"
  },
  {
   "addr": "085C",
   "name": "setLoadDest",
   "summary": "Stores X/Y as the low/high byte of the next transfer address (self-modifying operands loadDestLo/loadDestHi).",
   "inputs": "X=lo, Y=hi",
   "outputs": "loadDestLo, loadDestHi",
   "confidence": "high"
  },
  {
   "addr": "0863",
   "name": "writeOneSector",
   "summary": "Sends command $60, track and sector to the drive, stops CIA1 timer A during the transfer, sends 256 bytes and reads the status byte.",
   "inputs": "Y=track, X=sector, loadDestLo/Hi",
   "outputs": "C=1 on error (from finishTransfer)",
   "confidence": "high"
  },
  {
   "addr": "088F",
   "name": "sendSectorData",
   "summary": "Sends the 256 bytes at (wrSrcPtr) to the drive one byte at a time, then falls into finishTransfer to read the status.",
   "inputs": "wrSrcPtr",
   "outputs": "C=1 on error",
   "confidence": "high"
  },
  {
   "addr": "089C",
   "name": "sendByteToDrive",
   "summary": "Clocks one byte to the drive MSB first through CIA2 port A bit 5 with a handshake on bits 4/6/7; simultaneously shifts in a byte from the drive into recvByteShift, which is returned in A (used to fetch the status byte).",
   "inputs": "A=byte",
   "outputs": "A=byte received from the drive, C=0, X=0",
   "confidence": "high"
  },
  {
   "addr": "08EB",
   "name": "readOneSector",
   "summary": "Requests track Y sector X with command $80 and receives 256 bytes into the load destination; CIA1 timer A is stopped for the duration.",
   "inputs": "Y=track, X=sector, loadDestLo/Hi",
   "outputs": "C=1 on error, ldTrack/ldSector",
   "confidence": "high"
  },
  {
   "addr": "08FE",
   "name": "readSectorBody",
   "summary": "Sends the read command, saves and clears the sprite enable register, computes the bad-line avoidance constant ($D011&7 + $2F) into the operand at $095F, then receives the sector and the status byte.",
   "inputs": "ldTrack, ldSector, loadDestLo/Hi",
   "outputs": "C=1 on error, savedSpriteEnable, ldCia2Bits",
   "confidence": "high"
  },
  {
   "addr": "0943",
   "name": "finishTransfer",
   "summary": "Restores the sprite enable register and reads the drive status byte; C=1 if the status is nonzero.",
   "inputs": "savedSpriteEnable",
   "outputs": "C, A=status",
   "confidence": "high"
  },
  {
   "addr": "0950",
   "name": "receiveSectorData",
   "summary": "Timing-critical 2-bit-at-a-time receive loop for 256 bytes; waits for a raster line that is not a bad line before each byte.",
   "inputs": "ldDestPtr, ldCia2Bits",
   "outputs": "256 bytes at (ldDestPtr)",
   "confidence": "high"
  },
  {
   "addr": "09A6",
   "name": "manualLookupCheck",
   "summary": "Copy protection (disabled in this build: the JSR at $0A9B was patched to BIT). Saves mapSeed ($0B95-$0B97) and $0BA9-$0BAE, picks answer entry ($09C3 index & $1F) from the 32x3 table at $0401 into mapSeed, draws the map (sub_6F04, sub_5AD7, sub_261C) and prints msgIdentifyMap; the player gets 3 tries ($92C7) to type the 3 answer bytes into $5A-$5C, otherwise memory is wiped from $C000 down and the machine hangs. On success it restores the saved bytes.",
   "inputs": "$0401 table, $5A-$5C player input",
   "outputs": "$0B95-$0B97 and $0BA9-$0BAE restored, zp_3D/zp_42=0",
   "confidence": "high"
  },
  {
   "addr": "0A6C",
   "name": "mainStart",
   "summary": "Game start-up: disables CIA IRQs, points NMI/IRQ at irqRti ($1298), loads the runtime overlays ($C000, overlay A, comm module), loads the setup block, runs the setup-block code at $0300, initialises IRQ/input state, sets currentScreen=0, calls sub_2636/sub_5E03, (manual check disabled), sets playerSide=$FE and falls into returnToMainMenu.",
   "inputs": "none",
   "outputs": "whole system initialised",
   "confidence": "high"
  },
  {
   "addr": "0AA3",
   "name": "returnToMainMenu",
   "summary": "Re-entry point used to abort to the main menu (also jumped to from $5A3F, $C876, $EE0C and by the IRQ handler on SHIFT+C=+F7): VIC IRQs off, sprites off, stack reset to $FD, sub_2D41, initIrqAndInputState, message queue reset, loadingDepth=0, currentScreen=3, showMainMenu, then continues at L_1A86 (link status / menu flow).",
   "inputs": "none",
   "outputs": "currentScreen=3, loadingDepth=0, stack reset",
   "confidence": "medium"
  },
  {
   "addr": "0AC8",
   "name": "startGameFromSetup",
   "summary": "Jumped to from overlay A ($7E3F) when setup is complete. Resets the game variables, waits for the game disk, reloads the comm module if the link type flag changed ($0BA5 vs $E030), initialises the comm module, shows the modem prompt and waits for A/O when switching to modem mode, loads the map and setup block, reloads overlay B if needed, builds the owner name string and jumps to sub_1FD0 (battle start).",
   "inputs": "$0B9B, $0B9C, $0BA5, $0BA6, $87FF, $E030",
   "outputs": "map at $F000, setup at $0200, comm module configured, loadingDepth=0",
   "confidence": "medium"
  },
  {
   "addr": "0B4F",
   "name": "loadCommTailBuild1",
   "summary": "Inside startGameFromSetup: when $0BA5 bit 7 is set, loads $EC00-$EFFF from track 35 s12-15 (comm build 1 tail). Not a separate routine (the setLoadDest arguments are set up at $0B4B).",
   "inputs": "X=0,Y=$EC",
   "outputs": "$EC00-$EFFF",
   "confidence": "high"
  },
  {
   "addr": "0BB5",
   "name": "initIrqAndInputState",
   "summary": "Silences the sound, installs the raster IRQ, enables sub_3F57 (patches its first byte to NOP), sets the input lockout to $9E frames, $0549=$9E, clears $92C9/$92E4/$922A and sets currentKey=$FF.",
   "inputs": "none",
   "outputs": "IRQ installed, inputLockoutTimer, currentKey",
   "confidence": "medium"
  },
  {
   "addr": "0BD8",
   "name": "disableCiaInterrupts",
   "summary": "Writes $7F to both CIA ICRs and reads them back to acknowledge pending interrupts.",
   "inputs": "none",
   "outputs": "CIA interrupts disabled",
   "confidence": "high"
  },
  {
   "addr": "0BE7",
   "name": "initInterruptVectors",
   "summary": "Resets the BRK-exit operand of the IRQ handler ($10FF/$1100) to irqExit ($1293), and sets the comm module's NMI vector copy ($E031/$E032) and the hardware NMI vector to irqRti ($1298).",
   "inputs": "none",
   "outputs": "$10FF/$1100, $E031/$E032, $FFFA/$FFFB",
   "confidence": "medium"
  },
  {
   "addr": "0C02",
   "name": "setNmiVectorRti",
   "summary": "Sets the NMI vector to irqRti ($1298); jumped to after loading comm build 1.",
   "inputs": "none",
   "outputs": "$FFFA/$FFFB",
   "confidence": "high"
  },
  {
   "addr": "0C0D",
   "name": "jumpToCd00IfPatched",
   "summary": "If $CD00 holds a JMP opcode ($4C) it disables VIC IRQs and jumps there, else returns via the RTS at $0C0C. Not referenced by any traced code; at run time $CD00 holds data in the $C000 overlay, so this looks like dead/debug code.",
   "inputs": "$CD00",
   "outputs": "VIC_IRQ_MASK=0 when taken",
   "confidence": "low"
  },
  {
   "addr": "0C1C",
   "name": "resetGameVariables",
   "summary": "Clears zero page $15-$7F and $8C-$FF and the variable area $9008-$92FF, sets $9248-$9250, $90EA and $B1 to $FF, calls sub_58D5, sets currentScreen=3 and $0548=$9E (via $C62A). Also called from the setup-block code at $0300.",
   "inputs": "none",
   "outputs": "variables cleared",
   "confidence": "medium"
  },
  {
   "addr": "0C66",
   "name": "installRasterIrq",
   "summary": "Installs rasterIrqHandler ($10F1) as the IRQ vector (and its shadow irqVectorLo/Hi), sets the raster line to $FA, clears irqInProgress, acknowledges and enables the VIC raster interrupt.",
   "inputs": "none",
   "outputs": "$FFFE/$FFFF, zp_AA-zp_AC, VIC_RASTER, VIC_IRQ_MASK",
   "confidence": "high"
  },
  {
   "addr": "0C8C",
   "name": "showModemSetupPrompt",
   "summary": "Stores the caller's string (A/Y) in message slot $20 and 'PRESS SPACE WHEN READY.' ($8837) in slot $21, calls resetMessagesAndBeep and jumps into overlay A at $7DB8. Called from overlay A ($79E3).",
   "inputs": "A/Y=string address",
   "outputs": "message slots $20/$21",
   "confidence": "medium"
  },
  {
   "addr": "0CA0",
   "name": "resetMessagesAndBeep",
   "summary": "Resets the message queue, queues message $20, plays sound 3 twice with a 20-frame pause and sets messageState ($92A1) to $21.",
   "inputs": "none",
   "outputs": "$92A1=$21",
   "confidence": "medium"
  },
  {
   "addr": "0CBD",
   "name": "waitForAnswerOriginateKey",
   "summary": "After resetMessagesAndBeep, loops (stirring the RNG) until the last typed key ($9248) is 'A' ($C1) or 'O' ($CF), stores 0 (answer) or 1 (originate) in the comm module flag $E011 and resets the message queue.",
   "inputs": "$9248 key from IRQ",
   "outputs": "$E011 answer/originate, $0B7D=0",
   "confidence": "high"
  },
  {
   "addr": "0CE8",
   "name": "buildOwnerNameString",
   "summary": "Builds the 21-character ownerNameString at $0B7F from the two 10-byte name fields at $FFDE and $FFE8 (first field right-aligned, second left-aligned, spaces squeezed) and terminates it with bit 7. If the first field is blank ($A0) it is replaced by 'OURS'.",
   "inputs": "$FFDE-$FFF1",
   "outputs": "$0B7F-$0B93",
   "confidence": "medium"
  },
  {
   "addr": "0D49",
   "name": "setJoystickRepeatDelay",
   "summary": "Joystick auto-repeat: increments joyHoldCounter ($91CA) masked by joyCenteredMask ($91C9, cleared by the IRQ when the stick is centred) and sets timer zp_6C to 16 frames for the first step or 6 frames for repeats; preserves A.",
   "inputs": "$91C9, $91CA",
   "outputs": "zp_6C, $91CA, $91C9=$7F",
   "confidence": "medium"
  },
  {
   "addr": "0DB7",
   "name": "scanKeyboard",
   "summary": "Keyboard matrix scanner installed into the comm module vector $E013/$E014 and called through $E012/$E015. Returns no key if a joystick is disturbing either CIA1 port, tests RSHIFT, LSHIFT and C= as modifiers, then scans rows 7..0 using the masks at $2F8D/$2F85 and translates the first pressed key through keyMatrixCodeTable ($FE entries are skipped, $FF = CTRL aborts).",
   "inputs": "CIA1 ports",
   "outputs": "A=key code ($FF none), Y=$80 if shift held, X=$80 if C= held",
   "confidence": "high"
  },
  {
   "addr": "0E45",
   "name": "testKeyMask",
   "summary": "Selects keyboard row Y on CIA1 port A, reads port B debounced and ANDs it with mask A (self-modified operand). Z=1 when that key is pressed.",
   "inputs": "Y=row mask, A=column bit",
   "outputs": "A/Z",
   "confidence": "high"
  },
  {
   "addr": "0E56",
   "name": "pollKeyboardEvent",
   "summary": "Called from the IRQ every frame: calls the comm module key entry $E015, stores raw key/shift/C= in $92A0/$92E4/$92E5, handles key repeat with a 20-frame timer (zp_70), sets currentKey (zp_66), lastKeyCode ($90E9) and newKeyEvent ($90EA); for codes >= $80 also stores lastTypedKey ($9248) and applies shiftedKeyTable to $AC-$BB when shift is held.",
   "inputs": "$E015 result",
   "outputs": "zp_66, zp_70, $90E9, $90EA, $9248, $92A0, $92E4, $92E5",
   "confidence": "medium"
  },
  {
   "addr": "0EB1",
   "name": "checkFirePressed",
   "summary": "Edge-detects a new fire button press: returns A=0 (Z=1) when fire is down (zp_5F=0) after having been released (zp_79), starting a 10-frame debounce (zp_6D) and playing click sound 1; otherwise A=1. Also fails when sub_59B3 returns C=1. Callers loop on BNE to wait for the button.",
   "inputs": "zp_5F, zp_79, zp_6D",
   "outputs": "A/Z, zp_79, zp_6D",
   "confidence": "medium"
  },
  {
   "addr": "0ED5",
   "name": "checkFirePressedNotSolo",
   "summary": "Like checkFirePressed but when a press is detected returns soloModeFlag ($0B9B) if negative, else $923F & $40 - i.e. A=0 only for a fresh press in non-solo mode with $923F bit 6 clear.",
   "inputs": "zp_5F, $0B9B, $923F",
   "outputs": "A/Z",
   "confidence": "low"
  },
  {
   "addr": "0EE5",
   "name": "getSoloOrFlag923F",
   "summary": "Orphan entry (A=0 then jumps into checkFirePressedNotSolo at $0EDA): returns $0B9B if negative else $923F & $40. Not referenced by traced code.",
   "inputs": "$0B9B, $923F",
   "outputs": "A/Z",
   "confidence": "low"
  },
  {
   "addr": "0EEA",
   "name": "isFireHeld",
   "summary": "Returns A=0 while the fire button is held (after one release), without debounce or sound; A=1 otherwise.",
   "inputs": "zp_5F, zp_79, zp_6D",
   "outputs": "A/Z, zp_79",
   "confidence": "medium"
  },
  {
   "addr": "0EFE",
   "name": "stopAllSound",
   "summary": "Plays 'sound 0' = silences all voices ($C8A8 with A=0). NOTE: the seed labelled this loadRuntimeOverlays, but the overlay loading actually starts at $0F03.",
   "inputs": "none",
   "outputs": "$92E7-$92EA",
   "confidence": "high"
  },
  {
   "addr": "0F03",
   "name": "loadRuntimeOverlays",
   "summary": "Called once from mainStart: $C000 <- track 30 s0-15, then loadOverlayA (+strings+comm tail), then falls into loadCommModule.",
   "inputs": "$0BA5",
   "outputs": "$C000-$CFFF, $6F00-$89FF, $E000-$EFFF",
   "confidence": "high"
  },
  {
   "addr": "0F16",
   "name": "loadCommModule",
   "summary": "Loads the communications module to $E000: build 1 from track 35 s0-11 when linkTypeFlag ($0BA5) bit 7 is set (then NMI vector -> RTI), otherwise build 2 from track 18 s7 + track 34 s1-11; ends in initCommModule.",
   "inputs": "$0BA5",
   "outputs": "$E000-$EBFF, comm module initialised",
   "confidence": "high"
  },
  {
   "addr": "0F31",
   "name": "loadCommBuild2",
   "summary": "Loads comm build 2: track 18 sector 7 to $E000 then track 34 s1-11 to $E100.",
   "inputs": "loadDest=$E000",
   "outputs": "$E000-$EBFF",
   "confidence": "high"
  },
  {
   "addr": "0F41",
   "name": "loadCommFinish",
   "summary": "Shared tail: loadSectors with the prepared registers, then initCommModule.",
   "inputs": "A,X,Y for loadSectors",
   "outputs": "comm module loaded and initialised",
   "confidence": "high"
  },
  {
   "addr": "0F44",
   "name": "initCommModule",
   "summary": "Calls comm entry $E003 with X=1 (initialise), installs scanKeyboard ($0DB7) into the comm module's keyboard vector $E013/$E014 and enables interrupts.",
   "inputs": "comm module at $E000",
   "outputs": "$E013/$E014, I flag cleared",
   "confidence": "high"
  },
  {
   "addr": "0F55",
   "name": "loadSectorsDelayed",
   "summary": "Waits 8 frames ($C37F is a raster-timed frame delay) then performs loadSectors. Seed name loadSectorsRetry.",
   "inputs": "A=count, Y=track, X=sector",
   "outputs": "as loadSectors",
   "confidence": "high"
  },
  {
   "addr": "0F5F",
   "name": "suspendCommModule",
   "summary": "If the link is active ($E03B != 0) writes request $C0 to $E039 and waits until $E03A bit 7 is set (comm module acknowledges it is paused); preserves A. Used before disk access, which needs interrupts off.",
   "inputs": "$E03B",
   "outputs": "$E039=$C0",
   "confidence": "medium"
  },
  {
   "addr": "0F71",
   "name": "resumeCommModule",
   "summary": "If the link is active writes 0 to $E039 and waits until $E03A bit 7 clears (comm module resumed).",
   "inputs": "$E03B",
   "outputs": "$E039=0",
   "confidence": "medium"
  },
  {
   "addr": "0FB1",
   "name": "checkDiskId",
   "summary": "Reads the BAM (track 18 sector 0) to $0200 and compares the disk id at $02A2/$02A3 with diskIdTable entry X (X=0 'OZ' game disk, X=2 'EA' data disk; X saved in $92F0). For the game disk in non-solo mode copies the 10-byte name/serial at $02AB to $FFDE. A on entry is remembered: bit 7 set = fail silently, otherwise shows 'INSERT GAME DISK.' or 'NOT A DATA DISK!'.",
   "inputs": "A=silent flag, X=id index",
   "outputs": "C=0 ok, C=1 wrong disk/error, $FFDE-$FFE7, $92F0",
   "confidence": "high"
  },
  {
   "addr": "1018",
   "name": "diskErrorExit",
   "summary": "Shows 'DISK ERROR.' through showDiskMessageAndFail.",
   "inputs": "none",
   "outputs": "C=1",
   "confidence": "high"
  },
  {
   "addr": "101C",
   "name": "showDiskMessageAndFail",
   "summary": "Plays sound 3, shows the message at A/Y, waits 100 frames, clears the message area ($C39F) and returns C=1.",
   "inputs": "A/Y=message",
   "outputs": "C=1",
   "confidence": "medium"
  },
  {
   "addr": "1030",
   "name": "reloadOverlayForVariant",
   "summary": "Suspends the comm module, waits for the game disk, then reloads according to overlayVariantFlag ($87FF): bit 7 clear -> strings and comm tail only (variant B stays), set -> overlay A (+strings+tail); finally resumes the comm module.",
   "inputs": "$87FF",
   "outputs": "overlays reloaded",
   "confidence": "medium"
  },
  {
   "addr": "1047",
   "name": "loadOverlayA",
   "summary": "$6F00 <- track 31 s0-16, $8000 <- track 32 s0-7, then falls into loadStringsAndCommTail.",
   "inputs": "none",
   "outputs": "$6F00-$87FF variant A",
   "confidence": "high"
  },
  {
   "addr": "1060",
   "name": "loadStringsAndCommTail",
   "summary": "$8800 <- track 29 s9-10 (message strings) then falls into loadCommTailBuild2.",
   "inputs": "none",
   "outputs": "$8800-$89FF, $EC00-$EFFF",
   "confidence": "high"
  },
  {
   "addr": "1070",
   "name": "loadCommTailBuild2",
   "summary": "$EC00 <- track 34 s12-15 (comm build 2 tail). Also called from overlay A ($7B33) to restore it after a sub-overlay was loaded there.",
   "inputs": "none",
   "outputs": "$EC00-$EFFF",
   "confidence": "high"
  },
  {
   "addr": "1080",
   "name": "loadOverlayBIfNeeded",
   "summary": "Loads overlay B when overlayVariantFlag ($87FF) bit 7 is clear (i.e. the resident overlay should be B), else returns.",
   "inputs": "$87FF",
   "outputs": "see loadOverlayB",
   "confidence": "high"
  },
  {
   "addr": "1086",
   "name": "loadOverlayB",
   "summary": "Suspends the comm module, $6F00 <- track 33 s0-16, $8000 <- track 32 s8-15, patches overlay byte $750A with the value at $206B, then resumes the comm module.",
   "inputs": "$206B",
   "outputs": "$6F00-$87FF variant B, $750A",
   "confidence": "high"
  },
  {
   "addr": "10AB",
   "name": "loadMapAndSetup",
   "summary": "$F000 <- track 18 s8-14 (40x40 battlefield map) then falls into loadSetupBlock.",
   "inputs": "none",
   "outputs": "$F000-$F6FF, $0200-$03FF",
   "confidence": "high"
  },
  {
   "addr": "10BB",
   "name": "loadSetupBlock",
   "summary": "$0200 <- track 18 s15-16 (saved setup block including the code at $0300).",
   "inputs": "none",
   "outputs": "$0200-$03FF",
   "confidence": "high"
  },
  {
   "addr": "10CB",
   "name": "suspendCommForDiskAccess",
   "summary": "suspendCommModule then $C853 (sets messageState=1 and shows the message line - presumably a loading notice).",
   "inputs": "none",
   "outputs": "$92A1=1",
   "confidence": "medium"
  },
  {
   "addr": "10D1",
   "name": "resumeCommAfterDiskAccess",
   "summary": "resumeCommModule, clears messageState ($92A1) and redraws/clears the message area via $C39F.",
   "inputs": "none",
   "outputs": "$92A1=0",
   "confidence": "medium"
  },
  {
   "addr": "10DC",
   "name": "waitForGameDiskInserted",
   "summary": "Suspends the comm module and loops: checkDiskId(X=0, verbose); on failure calls $C85B (shows 'press button' prompt and waits for fire) and retries until the game disk is present.",
   "inputs": "none",
   "outputs": "returns only when the OZ disk is in the drive",
   "confidence": "high"
  },
  {
   "addr": "10F1",
   "name": "rasterIrqHandler",
   "summary": "Raster IRQ at line $FA (and BRK handler). Ignores BRK (jumps through the patchable operand at $10FF). Re-arms the raster line and vector, guards against re-entry with irqInProgress; copies the VIC shadow registers ($9000-$9006) and calls comm module entries $E000 (X=0) and $E00C; returns early if loadingDepth bit 7 is set. Then copies the sprite shadows (colours with 2-nibble flashing, positions plus fine-scroll offsets from $92F4/$92F5 when $92C8 != 0, MSB, expand, enable), bumps the 24-bit frame counter, enables IRQs, and unless inputLockoutTimer is counting down polls the keyboard (pollKeyboardEvent) and joystick port 2 into joyDirection/joyFireReleased. Updates joystick idle/centre counters, the RNG, six countdown timers zp_6C-zp_71, detects SHIFT+C=+F7 (abort to main menu), blinks the cursor colour, updates frame timers, handles chat send/receive, sound tick ($C8E9) and overlay hooks $6728/$6637.",
   "inputs": "VIC, CIA1, shadows at $9000-$9024, comm module",
   "outputs": "VIC registers, zp_45-zp_47, zp_5D/zp_5F/zp_60/zp_61, zp_66, timers, chat buffers",
   "confidence": "high"
  },
  {
   "addr": "1293",
   "name": "irqExit",
   "summary": "Restores Y, X, A and RTIs; also the BRK exit target patched into $10FF/$1100.",
   "inputs": "stack",
   "outputs": "none",
   "confidence": "high"
  },
  {
   "addr": "1298",
   "name": "irqRti",
   "summary": "A bare RTI used as the NMI vector target (and the IRQ vector during start-up).",
   "inputs": "none",
   "outputs": "none",
   "confidence": "high"
  },
  {
   "addr": "129C",
   "name": "updateFrameTimers",
   "summary": "Per-frame housekeeping: increments $92F8/$92FB; every 8 frames bumps $9223 (while negative, saturating) and $92C9 (while positive, up to $80) and decrements zp_7F; every 4 frames decrements zp_72 and $9163; every frame decrements $90FC, $91C8, $9234 (unless $80) and $923E while nonzero.",
   "inputs": "zp_45, zp_B1",
   "outputs": "the timers listed",
   "confidence": "medium"
  },
  {
   "addr": "130A",
   "name": "stepRandomSeed",
   "summary": "Seeds the 24-bit RNG state zp_57-zp_59 with the joystick direction when it is all zero, then steps the LFSR (sub_FD5D).",
   "inputs": "zp_57-zp_59, zp_5D",
   "outputs": "zp_57-zp_59, A=random byte",
   "confidence": "medium"
  },
  {
   "addr": "131B",
   "name": "blinkCursorColor",
   "summary": "Every 30 frames (counter at $131A) toggles bit 0 of sprite colour shadow $9008 and copies it to $900A - a blinking cursor sprite colour; only run when currentScreen=0 and zp_B1 is non-negative.",
   "inputs": "$131A, $9008",
   "outputs": "$9008, $900A",
   "confidence": "medium"
  },
  {
   "addr": "1331",
   "name": "handleChatSend",
   "summary": "Chat/message-typing state machine run each frame when the link is active ($E03B) and $92E6 is zero: RETURN ($8D in newKeyEvent) or chatState $7F enters typing mode (chatState=$C0, $9278=$9E, sound 2); when chatState bit 7 is set it sends the pending character count/marker to the remote via $C7D5 and comm entry $E009. Clears chatState when the link is down.",
   "inputs": "$929C, $90EA, $E03B, $E02E, $92E6",
   "outputs": "$929C, $9278, $90EA, $9248",
   "confidence": "low"
  },
  {
   "addr": "1399",
   "name": "receiveChatCharacter",
   "summary": "Fetches one received byte from the comm module ($E006; C=1 none) and, for codes >= $80, handles RETURN (marks message complete, $9277=$80, sound 5), DEL (backspace in the incoming message) or appends the character via appendChatCharacter.",
   "inputs": "$E006 result, $9277",
   "outputs": "$9253 buffer, $9277, zp_72",
   "confidence": "medium"
  },
  {
   "addr": "13C4",
   "name": "appendChatCharacter",
   "summary": "Appends character A to the 36-byte incoming message buffer at $9253 (index $9277, scrolling the buffer left when full) and rewrites the text with bit 7 set into the display line at $0549 (with $0548=$1E).",
   "inputs": "A=char, $9277",
   "outputs": "$9253-$9276, $0548-$056C, $9277",
   "confidence": "medium"
  },
  {
   "addr": "1414",
   "name": "enterMainMenuKeepSelection",
   "summary": "Main-menu entry from the F-key screen switch at $076D: picks the default item (selectDefaultMenuItem), clears the text window and joins mainMenuLoop at the item-changed handler.",
   "inputs": "none",
   "outputs": "menuSelection",
   "confidence": "medium"
  },
  {
   "addr": "141D",
   "name": "enterMainMenu",
   "summary": "Main-menu entry from the F7 screen switch at $0706: sets $1412=$7F, menuSelection from linkState bit 7, draws the menu (showMainMenu) and enters mainMenuLoop.",
   "inputs": "$922A",
   "outputs": "menuSelection, screen",
   "confidence": "medium"
  },
  {
   "addr": "1439",
   "name": "mainMenuLoop",
   "summary": "Main menu: calls sub_40B2/sub_16B7 (highlight blink), redraws link-dependent text, checks F-key screen switches (sub_06BA), moves the selection with joystick left/right among 3 items (20-frame repeat, sound 2) and on fire dispatches through menuItemHandlerTable; when the selection changes it draws the item panel (item 0 = game setup summary, 1 = sub_175A, 2 = sub_19F3).",
   "inputs": "zp_5D, zp_6C, $90F9",
   "outputs": "$90F9 menuSelection, screen",
   "confidence": "medium"
  },
  {
   "addr": "14F0",
   "name": "snapshotSetupId",
   "summary": "Copies $9241-$9243 to $92D7-$92D9 and map-table byte $F9C4[$9236] & $3F to $92D6 - remembers the identity of the current setup.",
   "inputs": "$9241-$9243, $9236",
   "outputs": "$92D6-$92D9",
   "confidence": "low"
  },
  {
   "addr": "1507",
   "name": "selectDefaultMenuItem",
   "summary": "Chooses the default main-menu item: 2 if $0BA8 is nonzero, else 1 if linkState bit 7 is set or the current setup differs from the snapshot in $92D6-$92D9, else 2.",
   "inputs": "$0BA8, $922A, $9241-$9243, $92D6-$92D9",
   "outputs": "$90F9",
   "confidence": "medium"
  },
  {
   "addr": "153B",
   "name": "showMainMenu",
   "summary": "Draws the main-menu screen: clears $2A29-$2A2C, background colour shadow $9004=$0C, $9226=0, zp_41=1, draws the title/credits text (sub_1643), the menu frame (drawMenuFrame) and continues at $C5D7.",
   "inputs": "none",
   "outputs": "screen",
   "confidence": "medium"
  },
  {
   "addr": "1565",
   "name": "drawMenuFrame",
   "summary": "Draws two frame elements (sub_2C7F with X=$18 and $19) then falls into clearMenuTextWindow.",
   "inputs": "none",
   "outputs": "screen",
   "confidence": "low"
  },
  {
   "addr": "156F",
   "name": "clearMenuTextWindow",
   "summary": "Sets the menu text attribute and window bounds and clears the window ($C06A).",
   "inputs": "$922A, $90F9",
   "outputs": "zp_39-zp_40, zp_3D",
   "confidence": "low"
  },
  {
   "addr": "1575",
   "name": "setMenuTextWindow",
   "summary": "Sets the text attribute from link state (sub_16EB) then the menu window bounds.",
   "inputs": "$922A",
   "outputs": "zp_3D, zp_39-zp_40",
   "confidence": "low"
  },
  {
   "addr": "1578",
   "name": "setMenuWindowBounds",
   "summary": "Text window for the menu panel: cursor col 0, top row 7, cursor row 7, left column 2, bottom 22, right column 36.",
   "inputs": "none",
   "outputs": "zp_39=7, zp_3A=$16, zp_3B=2, zp_3C=$24, zp_3F=0, zp_40=7",
   "confidence": "medium"
  },
  {
   "addr": "158F",
   "name": "clearMenuWindowPlain",
   "summary": "Attribute 0, menu window bounds, clear window; jumped to from $2E0A.",
   "inputs": "none",
   "outputs": "zp_3D=0, screen",
   "confidence": "low"
  },
  {
   "addr": "1599",
   "name": "drawGameSetupSummary",
   "summary": "Main-menu item 0 panel: prints 'STANDARD' or 'CUSTOM' (gameOptions $0BA3 bit 6), the game type name from the 8-byte table at $CCC8 (Scrimmage/QB Sneak/The Bomb/Face-Off/Sluggers/Full War/Defender, $0BA3 & 7), '1ST PLAY'/'2ND PLAY' from firstPlayFlag ($0BA1), the owner name string and overlay text $86AC, and for custom games the text at $0636.",
   "inputs": "$0BA3, $0BA1, $0B7F",
   "outputs": "screen, zp_3B/zp_3D/zp_3F",
   "confidence": "medium"
  }
 ],
 "variables": [
  {
   "addr": "18",
   "scope": "zp",
   "name": "tempByte18",
   "meaning": "scratch (seen-nonspace flag in buildOwnerNameString, map index in manualLookupCheck, string lo in $C100)",
   "confidence": "medium"
  },
  {
   "addr": "33",
   "scope": "zp",
   "name": "spriteFlashPhase",
   "meaning": "frame counter bits 0/1 rotated into bits 7/6 by the IRQ; bit 7 gates the two-colour sprite flashing",
   "confidence": "medium"
  },
  {
   "addr": "38",
   "scope": "zp",
   "name": "irqInProgress",
   "meaning": "re-entrancy guard of rasterIrqHandler (nonzero while the handler runs with interrupts enabled)",
   "confidence": "high"
  },
  {
   "addr": "39",
   "scope": "zp",
   "name": "textWindowTop",
   "meaning": "text window top row used by the $C000 text routines ($C06A copies it to zp_40)",
   "confidence": "medium"
  },
  {
   "addr": "3A",
   "scope": "zp",
   "name": "textWindowBottom",
   "meaning": "text window bottom row / height ($16 for the menu panel)",
   "confidence": "low"
  },
  {
   "addr": "3B",
   "scope": "zp",
   "name": "textWindowLeft",
   "meaning": "text window left column; $C099 positions the cursor at zp_3B+zp_3F",
   "confidence": "medium"
  },
  {
   "addr": "3C",
   "scope": "zp",
   "name": "textWindowRight",
   "meaning": "text window right column ($24)",
   "confidence": "low"
  },
  {
   "addr": "3D",
   "scope": "zp",
   "name": "textAttribute",
   "meaning": "current text colour/attribute byte (e.g. $6F, $61, $21); saved and replaced by zp_42 in $C133 when a message is active",
   "confidence": "medium"
  },
  {
   "addr": "3F",
   "scope": "zp",
   "name": "textCursorCol",
   "meaning": "cursor column relative to the window left edge",
   "confidence": "medium"
  },
  {
   "addr": "40",
   "scope": "zp",
   "name": "textCursorRow",
   "meaning": "cursor row",
   "confidence": "medium"
  },
  {
   "addr": "41",
   "scope": "zp",
   "name": "flag41",
   "meaning": "set to 1 by showMainMenu, cleared by sub_1FD0 (battle start); menu-vs-battle state flag",
   "confidence": "low"
  },
  {
   "addr": "42",
   "scope": "zp",
   "name": "messageAttribute",
   "meaning": "text attribute used while a message is displayed",
   "confidence": "low"
  },
  {
   "addr": "45",
   "scope": "zp",
   "name": "frameCounter",
   "meaning": "low byte of the 24-bit frame counter incremented by the IRQ (zp_45-zp_47)",
   "confidence": "high"
  },
  {
   "addr": "46",
   "scope": "zp",
   "name": "frameCounterMid",
   "meaning": "middle byte of the frame counter",
   "confidence": "high"
  },
  {
   "addr": "47",
   "scope": "zp",
   "name": "frameCounterHi",
   "meaning": "high byte of the frame counter",
   "confidence": "high"
  },
  {
   "addr": "48",
   "scope": "zp",
   "name": "fillPtr",
   "meaning": "pointer (lo) used for memory fills (resetGameVariables) and screen writes",
   "confidence": "medium"
  },
  {
   "addr": "49",
   "scope": "zp",
   "name": "fillPtrHi",
   "meaning": "pointer (hi)",
   "confidence": "medium"
  },
  {
   "addr": "4A",
   "scope": "zp",
   "name": "fillEndPtr",
   "meaning": "end address (lo) compared by $C3F9",
   "confidence": "medium"
  },
  {
   "addr": "4B",
   "scope": "zp",
   "name": "fillEndPtrHi",
   "meaning": "end address (hi)",
   "confidence": "medium"
  },
  {
   "addr": "57",
   "scope": "zp",
   "name": "rngState",
   "meaning": "24-bit LFSR random state (zp_57-zp_59) stepped by sub_FD5D; seeded from the joystick direction when zero",
   "confidence": "high"
  },
  {
   "addr": "58",
   "scope": "zp",
   "name": "rngStateMid",
   "meaning": "random state byte 2",
   "confidence": "high"
  },
  {
   "addr": "59",
   "scope": "zp",
   "name": "rngStateHi",
   "meaning": "random state byte 3",
   "confidence": "high"
  },
  {
   "addr": "5A",
   "scope": "zp",
   "name": "manualAnswer0",
   "meaning": "first byte of the player's answer to the manual look-up check (typed via sub_7717)",
   "confidence": "medium"
  },
  {
   "addr": "5B",
   "scope": "zp",
   "name": "manualAnswer1",
   "meaning": "second answer byte",
   "confidence": "medium"
  },
  {
   "addr": "5C",
   "scope": "zp",
   "name": "manualAnswer2",
   "meaning": "third answer byte",
   "confidence": "medium"
  },
  {
   "addr": "5D",
   "scope": "zp",
   "name": "joyDirection",
   "meaning": "joystick port 2 direction bits, active low: bit0 up, bit1 down, bit2 left, bit3 right; $0F = centred (also $0F when a key < $80 is held or a key event is pending)",
   "confidence": "high"
  },
  {
   "addr": "5F",
   "scope": "zp",
   "name": "joyFireReleased",
   "meaning": "1 when the fire button is up, 0 when pressed (CIA bit 4)",
   "confidence": "high"
  },
  {
   "addr": "60",
   "scope": "zp",
   "name": "joyDirectionCopy",
   "meaning": "second copy of joyDirection written by the IRQ at the same time (separate consumer, probably the comm/transmit side)",
   "confidence": "low"
  },
  {
   "addr": "61",
   "scope": "zp",
   "name": "joyFireReleasedCopy",
   "meaning": "second copy of joyFireReleased",
   "confidence": "low"
  },
  {
   "addr": "66",
   "scope": "zp",
   "name": "currentKey",
   "meaning": "key code from the latest poll ($FF none; 0-3 = F1/F3/F5/F7, >= $80 PETSCII-like); forced to $FF while loading",
   "confidence": "high"
  },
  {
   "addr": "6C",
   "scope": "zp",
   "name": "joyRepeatTimer",
   "meaning": "frame countdown (IRQ decrements zp_6C-zp_71): joystick auto-repeat delay",
   "confidence": "high"
  },
  {
   "addr": "6D",
   "scope": "zp",
   "name": "fireDebounceTimer",
   "meaning": "frame countdown set to 10 after a fire press",
   "confidence": "high"
  },
  {
   "addr": "6E",
   "scope": "zp",
   "name": "menuBlinkTimer",
   "meaning": "frame countdown (10) used by sub_16B7 to blink the highlighted menu item",
   "confidence": "medium"
  },
  {
   "addr": "6F",
   "scope": "zp",
   "name": "countdownTimer6F",
   "meaning": "general frame countdown decremented by the IRQ",
   "confidence": "low"
  },
  {
   "addr": "70",
   "scope": "zp",
   "name": "keyRepeatTimer",
   "meaning": "frame countdown (20) before a held key repeats",
   "confidence": "high"
  },
  {
   "addr": "71",
   "scope": "zp",
   "name": "countdownTimer71",
   "meaning": "general frame countdown decremented by the IRQ",
   "confidence": "low"
  },
  {
   "addr": "72",
   "scope": "zp",
   "name": "messageTimer72",
   "meaning": "countdown decremented every 4 frames; cleared when the message queue is reset or a chat message completes",
   "confidence": "low"
  },
  {
   "addr": "79",
   "scope": "zp",
   "name": "fireWasReleased",
   "meaning": "latch used by checkFirePressed to detect a new press (nonzero after the button was seen released)",
   "confidence": "medium"
  },
  {
   "addr": "7F",
   "scope": "zp",
   "name": "slowTimer7F",
   "meaning": "countdown decremented every 8 frames",
   "confidence": "low"
  },
  {
   "addr": "AA",
   "scope": "zp",
   "name": "rasterIrqLine",
   "meaning": "raster line re-armed by the IRQ handler ($FA)",
   "confidence": "high"
  },
  {
   "addr": "AB",
   "scope": "zp",
   "name": "irqVectorLo",
   "meaning": "IRQ handler address (lo) re-installed into $FFFE each interrupt",
   "confidence": "high"
  },
  {
   "addr": "AC",
   "scope": "zp",
   "name": "irqVectorHi",
   "meaning": "IRQ handler address (hi) re-installed into $FFFF each interrupt",
   "confidence": "high"
  },
  {
   "addr": "B1",
   "scope": "zp",
   "name": "gameRunState",
   "meaning": "$FF after resetGameVariables, 0 at game start; bit 7 set suppresses the cursor blink; compared with $FF in updateFrameTimers",
   "confidence": "low"
  },
  {
   "addr": "E0",
   "scope": "zp",
   "name": "ldTrack",
   "meaning": "loader: current track",
   "confidence": "high"
  },
  {
   "addr": "E1",
   "scope": "zp",
   "name": "ldSector",
   "meaning": "loader: current sector",
   "confidence": "high"
  },
  {
   "addr": "E7",
   "scope": "zp",
   "name": "ldCount",
   "meaning": "loader: sectors left",
   "confidence": "high"
  },
  {
   "addr": "0548",
   "scope": "abs",
   "name": "chatLineMarker",
   "meaning": "byte before the displayed chat line: $1E after a received character, $9E when reset ($C62A, initIrqAndInputState)",
   "confidence": "low"
  },
  {
   "addr": "0549",
   "scope": "abs",
   "name": "chatDisplayLine",
   "meaning": "36-byte copy of the incoming chat message with bit 7 set on every character",
   "confidence": "medium"
  },
  {
   "addr": "0B7D",
   "scope": "abs",
   "name": "inputLockoutTimer",
   "meaning": "frames during which the IRQ skips keyboard/joystick polling (set to $9E at init, $FF on abort)",
   "confidence": "high"
  },
  {
   "addr": "0B7E",
   "scope": "abs",
   "name": "setupByte0B7E",
   "meaning": "settings-block byte ($DB) read by the $EC00 sub-overlay at $ED89",
   "confidence": "low"
  },
  {
   "addr": "0B7F",
   "scope": "abs",
   "name": "ownerNameString",
   "meaning": "21-byte owner name built from $FFDE/$FFE8, bit-7 terminated at $0B93; printed in the setup summary",
   "confidence": "medium"
  },
  {
   "addr": "0B94",
   "scope": "abs",
   "name": "setupByte0B94",
   "meaning": "settings-block byte read by $5829/$58F2/$ED83",
   "confidence": "low"
  },
  {
   "addr": "0B95",
   "scope": "abs",
   "name": "mapSeed",
   "meaning": "3 bytes ($0B95-$0B97) that the map drawing code (sub_6F04 etc.) consumes; manualLookupCheck temporarily replaces them with the manual answer entry so the protection map can be drawn",
   "confidence": "low"
  },
  {
   "addr": "0B9B",
   "scope": "abs",
   "name": "soloModeFlag",
   "meaning": "bit 7 set = no-opponent/solo mode: skips modem handling, disk-serial copy and link messages (initial $FF)",
   "confidence": "medium"
  },
  {
   "addr": "0B9C",
   "scope": "abs",
   "name": "linkOptionFlag",
   "meaning": "bit 7 set skips comm module initialisation and the modem prompt at game start (related start-up option, exact meaning unknown)",
   "confidence": "low"
  },
  {
   "addr": "0B9F",
   "scope": "abs",
   "name": "playerSide",
   "meaning": "this player's side index (0/1; $FE = not chosen yet); overlay code uses it EOR 1 as the opponent index",
   "confidence": "medium"
  },
  {
   "addr": "0BA0",
   "scope": "abs",
   "name": "playerSideSetup",
   "meaning": "side chosen in the setup screens, copied to playerSide at game start",
   "confidence": "low"
  },
  {
   "addr": "0BA1",
   "scope": "abs",
   "name": "firstPlayFlag",
   "meaning": "nonzero = this player moves first ('1ST PLAY' in the setup summary)",
   "confidence": "medium"
  },
  {
   "addr": "0BA3",
   "scope": "abs",
   "name": "gameOptions",
   "meaning": "bits 0-2 = game type index into the names at $CCC8 (0 Scrimmage, 1 QB Sneak, 2 The Bomb, 3 Face-Off, 4 Sluggers, 5 Full War, 6 Defender), bit 6 = custom rules",
   "confidence": "medium"
  },
  {
   "addr": "0BA5",
   "scope": "abs",
   "name": "linkTypeFlag",
   "meaning": "bit 7 selects the comm module build (set = build 1 from track 35; clear = build 2, the Hayes modem build with the Answer/Originate prompt); compared with $E030 to detect a needed reload",
   "confidence": "medium"
  },
  {
   "addr": "0BA6",
   "scope": "abs",
   "name": "prevLinkTypeFlag",
   "meaning": "previous value of linkTypeFlag; a change from set to clear triggers the 'GET OPPONENT ON PHONE' prompt",
   "confidence": "medium"
  },
  {
   "addr": "0BA7",
   "scope": "abs",
   "name": "loadingDepth",
   "meaning": "nonzero while disk loads are in progress (keys suppressed); bit 7 makes the IRQ return right after servicing the comm module",
   "confidence": "medium"
  },
  {
   "addr": "0BA8",
   "scope": "abs",
   "name": "resumeGameFlag",
   "meaning": "nonzero makes menu item 2 the default and alters many screens; probably 'a game is in progress / saved'",
   "confidence": "low"
  },
  {
   "addr": "0BA9",
   "scope": "abs",
   "name": "setupName0BA9",
   "meaning": "6 bytes (shifted spaces by default) saved/restored around the manual check and copied from $0BAF by sub_1C87; a setup text field",
   "confidence": "low"
  },
  {
   "addr": "0BAF",
   "scope": "abs",
   "name": "setupName0BAF",
   "meaning": "6-byte backup of setupName0BA9",
   "confidence": "low"
  },
  {
   "addr": "0C8A",
   "scope": "abs",
   "name": "perSideValueTable",
   "meaning": "2 bytes ($31,$63) indexed by player side in sub_3F57 and others",
   "confidence": "low"
  },
  {
   "addr": "10EF",
   "scope": "abs",
   "name": "spriteEnableScratch",
   "meaning": "copy of the sprite enable shadow written by the IRQ each frame",
   "confidence": "medium"
  },
  {
   "addr": "10FF",
   "scope": "abs",
   "name": "irqBrkVector",
   "meaning": "operand of the JMP taken when the interrupt was caused by BRK (normally irqExit $1293)",
   "confidence": "high"
  },
  {
   "addr": "131A",
   "scope": "abs",
   "name": "cursorBlinkTimer",
   "meaning": "30-frame countdown for blinkCursorColor",
   "confidence": "high"
  },
  {
   "addr": "1412",
   "scope": "abs",
   "name": "menuFlag1412",
   "meaning": "set to $7F by enterMainMenu; not read inside this chunk",
   "confidence": "low"
  },
  {
   "addr": "1413",
   "scope": "abs",
   "name": "menuLinkStateCopy",
   "meaning": "copy of linkState ($922A) taken when the menu panel was drawn; a bit-7 change triggers a redraw",
   "confidence": "medium"
  },
  {
   "addr": "9000",
   "scope": "abs",
   "name": "vicCtrl1Shadow",
   "meaning": "shadow of $D011 copied by the IRQ",
   "confidence": "high"
  },
  {
   "addr": "9001",
   "scope": "abs",
   "name": "vicCtrl2Shadow",
   "meaning": "shadow of $D016",
   "confidence": "high"
  },
  {
   "addr": "9002",
   "scope": "abs",
   "name": "vicMemPtrShadow",
   "meaning": "shadow of $D018",
   "confidence": "high"
  },
  {
   "addr": "9003",
   "scope": "abs",
   "name": "borderColorShadow",
   "meaning": "shadow of $D020",
   "confidence": "high"
  },
  {
   "addr": "9004",
   "scope": "abs",
   "name": "bgColorShadow",
   "meaning": "shadow of $D021",
   "confidence": "high"
  },
  {
   "addr": "9005",
   "scope": "abs",
   "name": "bgColor1Shadow",
   "meaning": "shadow of $D022",
   "confidence": "high"
  },
  {
   "addr": "9006",
   "scope": "abs",
   "name": "bgColor2Shadow",
   "meaning": "shadow of $D023",
   "confidence": "high"
  },
  {
   "addr": "9008",
   "scope": "abs",
   "name": "spriteColorShadow",
   "meaning": "8 bytes -> $D027-$D02E; values >= $10 hold two colours in the nibbles that alternate every other frame pair (flashing)",
   "confidence": "high"
  },
  {
   "addr": "9010",
   "scope": "abs",
   "name": "spriteXShadow",
   "meaning": "8 sprite X positions (low byte), copied to $D000.. plus the fine-scroll X offset",
   "confidence": "high"
  },
  {
   "addr": "9018",
   "scope": "abs",
   "name": "spriteYShadow",
   "meaning": "8 sprite Y positions, copied to $D001.. plus the fine-scroll Y offset",
   "confidence": "high"
  },
  {
   "addr": "9020",
   "scope": "abs",
   "name": "spriteXMsbShadow",
   "meaning": "shadow of $D010",
   "confidence": "high"
  },
  {
   "addr": "9022",
   "scope": "abs",
   "name": "spriteEnableShadow",
   "meaning": "shadow of $D015",
   "confidence": "high"
  },
  {
   "addr": "9023",
   "scope": "abs",
   "name": "spriteXExpandShadow",
   "meaning": "shadow of $D01D",
   "confidence": "high"
  },
  {
   "addr": "9024",
   "scope": "abs",
   "name": "spriteYExpandShadow",
   "meaning": "shadow of $D017",
   "confidence": "high"
  },
  {
   "addr": "90E9",
   "scope": "abs",
   "name": "lastKeyCode",
   "meaning": "key code seen on the previous poll (repeat detection), $FF none",
   "confidence": "high"
  },
  {
   "addr": "90EA",
   "scope": "abs",
   "name": "newKeyEvent",
   "meaning": "key code of a fresh press/repeat this frame, $FF none; consumers reset it to $FF",
   "confidence": "high"
  },
  {
   "addr": "90EE",
   "scope": "abs",
   "name": "joyIdleCounter",
   "meaning": "counts frames with the stick centred and fire released (saturates at $FF); reset while the stick is deflected",
   "confidence": "medium"
  },
  {
   "addr": "90F9",
   "scope": "abs",
   "name": "menuSelection",
   "meaning": "main-menu item index 0-2",
   "confidence": "high"
  },
  {
   "addr": "90FB",
   "scope": "abs",
   "name": "currentScreen",
   "meaning": "active screen selected by F1/F3/F5/F7 key codes 0-3 (0 = battlefield, 1 = overlay screen $7587, 2 = overlay screen $7FE6, 3 = main menu)",
   "confidence": "medium"
  },
  {
   "addr": "90FC",
   "scope": "abs",
   "name": "countdown90FC",
   "meaning": "per-frame countdown timer",
   "confidence": "medium"
  },
  {
   "addr": "9163",
   "scope": "abs",
   "name": "countdown9163",
   "meaning": "countdown decremented every 4 frames",
   "confidence": "medium"
  },
  {
   "addr": "91C8",
   "scope": "abs",
   "name": "countdown91C8",
   "meaning": "per-frame countdown timer",
   "confidence": "medium"
  },
  {
   "addr": "91C9",
   "scope": "abs",
   "name": "joyCenteredMask",
   "meaning": "cleared by the IRQ while the stick is centred, set to $7F by setJoystickRepeatDelay; masks joyHoldCounter",
   "confidence": "medium"
  },
  {
   "addr": "91CA",
   "scope": "abs",
   "name": "joyHoldCounter",
   "meaning": "number of consecutive auto-repeat steps for a held joystick direction",
   "confidence": "medium"
  },
  {
   "addr": "9223",
   "scope": "abs",
   "name": "counter9223",
   "meaning": "incremented every 8 frames while negative (saturates at $FF)",
   "confidence": "low"
  },
  {
   "addr": "922A",
   "scope": "abs",
   "name": "linkState",
   "meaning": "bit 7 = link/modem session state used to choose menu text colours and default item; bits 6/4-5 set in the $0709 screen code",
   "confidence": "low"
  },
  {
   "addr": "9234",
   "scope": "abs",
   "name": "countdown9234",
   "meaning": "per-frame countdown that stops at 0 or $80",
   "confidence": "low"
  },
  {
   "addr": "9236",
   "scope": "abs",
   "name": "currentMapIndex",
   "meaning": "index into the table at $F9C4 (low 6 bits of the entry identify the map/setup)",
   "confidence": "low"
  },
  {
   "addr": "923E",
   "scope": "abs",
   "name": "countdown923E",
   "meaning": "per-frame countdown timer",
   "confidence": "medium"
  },
  {
   "addr": "9248",
   "scope": "abs",
   "name": "lastTypedKey",
   "meaning": "last key code >= $80 (PETSCII-like, shift-translated); menus poll it; $FF none",
   "confidence": "high"
  },
  {
   "addr": "9251",
   "scope": "abs",
   "name": "messageQueueHead",
   "meaning": "message queue index (9-entry queue at $9249-$9251 managed by $C566/$C6EF)",
   "confidence": "low"
  },
  {
   "addr": "9252",
   "scope": "abs",
   "name": "messageQueueTail",
   "meaning": "message queue index",
   "confidence": "low"
  },
  {
   "addr": "9253",
   "scope": "abs",
   "name": "chatInputBuffer",
   "meaning": "36-byte buffer of the incoming chat message",
   "confidence": "medium"
  },
  {
   "addr": "9277",
   "scope": "abs",
   "name": "chatInputLength",
   "meaning": "length/cursor of the incoming chat message; $80 = message complete",
   "confidence": "medium"
  },
  {
   "addr": "9278",
   "scope": "abs",
   "name": "chatFlag9278",
   "meaning": "set to $9E when chat typing mode is entered",
   "confidence": "low"
  },
  {
   "addr": "929C",
   "scope": "abs",
   "name": "chatState",
   "meaning": "chat typing state: 0 idle, $C0 typing, $7F pending, bit 7 = pending send",
   "confidence": "low"
  },
  {
   "addr": "92A0",
   "scope": "abs",
   "name": "rawKeyCode",
   "meaning": "raw result of the comm module key poll ($E015)",
   "confidence": "high"
  },
  {
   "addr": "92A1",
   "scope": "abs",
   "name": "messageState",
   "meaning": "message-line state (0 none, 1 loading notice, $21 modem prompt) passed to $C6FF after a retried load",
   "confidence": "low"
  },
  {
   "addr": "92C7",
   "scope": "abs",
   "name": "retryCounter",
   "meaning": "manual-check tries left; cleared at game start",
   "confidence": "medium"
  },
  {
   "addr": "92C8",
   "scope": "abs",
   "name": "spriteScrollActive",
   "meaning": "nonzero = add the fine-scroll offsets from $92F4/$92F5 to the sprite positions",
   "confidence": "medium"
  },
  {
   "addr": "92C9",
   "scope": "abs",
   "name": "counter92C9",
   "meaning": "incremented every 8 frames while positive, saturating at $80",
   "confidence": "low"
  },
  {
   "addr": "92D6",
   "scope": "abs",
   "name": "setupIdSnapshot",
   "meaning": "4 bytes: map id and $9241-$9243 copied by snapshotSetupId, compared by selectDefaultMenuItem",
   "confidence": "low"
  },
  {
   "addr": "92E4",
   "scope": "abs",
   "name": "shiftKeyFlag",
   "meaning": "$80 when a shift key is held (from scanKeyboard)",
   "confidence": "high"
  },
  {
   "addr": "92E5",
   "scope": "abs",
   "name": "commodoreKeyFlag",
   "meaning": "$80 when the C= key is held",
   "confidence": "high"
  },
  {
   "addr": "92E6",
   "scope": "abs",
   "name": "chatDisabled",
   "meaning": "nonzero disables chat typing",
   "confidence": "low"
  },
  {
   "addr": "92F0",
   "scope": "abs",
   "name": "diskIdIndex",
   "meaning": "diskIdTable index passed to checkDiskId (0 game disk, 2 data disk)",
   "confidence": "high"
  },
  {
   "addr": "92F4",
   "scope": "abs",
   "name": "scrollFineX",
   "meaning": "low 3 bits = sprite X fine-scroll offset",
   "confidence": "medium"
  },
  {
   "addr": "92F5",
   "scope": "abs",
   "name": "scrollFineY",
   "meaning": "low 3 bits minus 3 = sprite Y fine-scroll offset",
   "confidence": "medium"
  },
  {
   "addr": "92F8",
   "scope": "abs",
   "name": "frameTick92F8",
   "meaning": "incremented every frame by updateFrameTimers",
   "confidence": "high"
  },
  {
   "addr": "92FB",
   "scope": "abs",
   "name": "frameTick92FB",
   "meaning": "incremented every frame by updateFrameTimers",
   "confidence": "high"
  },
  {
   "addr": "92FE",
   "scope": "abs",
   "name": "linkTypeAtStart",
   "meaning": "copy of linkTypeFlag taken at game start",
   "confidence": "low"
  },
  {
   "addr": "E011",
   "scope": "abs",
   "name": "commAnswerOriginate",
   "meaning": "comm module flag: 0 = answer ('A'), 1 = originate ('O')",
   "confidence": "high"
  },
  {
   "addr": "E013",
   "scope": "abs",
   "name": "commKeyScanVector",
   "meaning": "operand of the JMP at $E012; the game points it at scanKeyboard ($0DB7)",
   "confidence": "high"
  },
  {
   "addr": "E030",
   "scope": "abs",
   "name": "commBuildFlag",
   "meaning": "bit 7 identifies which comm build is resident; copied to linkTypeFlag",
   "confidence": "medium"
  },
  {
   "addr": "E031",
   "scope": "abs",
   "name": "commNmiVector",
   "meaning": "2-byte NMI vector copy kept by the comm module, set to irqRti",
   "confidence": "medium"
  },
  {
   "addr": "E039",
   "scope": "abs",
   "name": "commRequest",
   "meaning": "request byte to the comm module: $C0 = pause, 0 = resume",
   "confidence": "medium"
  },
  {
   "addr": "E03A",
   "scope": "abs",
   "name": "commStatus",
   "meaning": "bit 7 set while the comm module is paused (acknowledge)",
   "confidence": "medium"
  },
  {
   "addr": "E03B",
   "scope": "abs",
   "name": "commLinkActive",
   "meaning": "nonzero when the comm link is up (pause/resume and chat only then)",
   "confidence": "medium"
  },
  {
   "addr": "E02E",
   "scope": "abs",
   "name": "commPendingCount",
   "meaning": "comm module counter checked before sending chat (must be 0 / < $10)",
   "confidence": "low"
  },
  {
   "addr": "FFDE",
   "scope": "abs",
   "name": "ownerName1",
   "meaning": "10-byte first name field copied from the BAM (offset $AB) of the game disk",
   "confidence": "medium"
  },
  {
   "addr": "FFE8",
   "scope": "abs",
   "name": "ownerName2",
   "meaning": "10-byte second name field (written by overlay code)",
   "confidence": "low"
  }
 ],
 "dataBlocks": [
  {
   "addr": "0803",
   "length": 1,
   "type": "byteTable",
   "name": "loaderStatus",
   "description": "bit 7 = a read error occurred"
  },
  {
   "addr": "08E8",
   "length": 1,
   "type": "byteTable",
   "name": "sendByteShift",
   "description": "shift register for the byte being sent"
  },
  {
   "addr": "08E9",
   "length": 2,
   "type": "byteTable",
   "name": "recvByteShift",
   "description": "shift register for the byte received during a send; $08EA unused"
  },
  {
   "addr": "0993",
   "length": 15,
   "type": "text",
   "name": "strBlockExecuteLeftover",
   "description": "'B-E 2 0 1 17',0,'#',0 - leftover drive command strings from the shared EA loader, unused by the game"
  },
  {
   "addr": "09A2",
   "length": 1,
   "type": "byteTable",
   "name": "loadDestLo",
   "description": "transfer address low byte (self-modified)"
  },
  {
   "addr": "09A3",
   "length": 2,
   "type": "byteTable",
   "name": "loadDestHi",
   "description": "transfer address high byte; $09A4 unused"
  },
  {
   "addr": "09A5",
   "length": 1,
   "type": "byteTable",
   "name": "savedSpriteEnable",
   "description": "sprite enable saved during a sector transfer"
  },
  {
   "addr": "0A47",
   "length": 1,
   "type": "unknown",
   "name": "padA47",
   "description": "single $20 byte before msgIdentifyMap"
  },
  {
   "addr": "0A48",
   "length": 36,
   "type": "text",
   "name": "msgIdentifyMap",
   "description": "copy-protection prompt"
  },
  {
   "addr": "0B7D",
   "length": 2,
   "type": "byteTable",
   "name": "inputLockoutTimer",
   "description": "$0B7D lockout timer, $0B7E settings byte"
  },
  {
   "addr": "0B7F",
   "length": 21,
   "type": "text",
   "name": "ownerNameString",
   "description": "owner name string built at run time"
  },
  {
   "addr": "0B94",
   "length": 33,
   "type": "byteTable",
   "name": "gameSettingsBlock",
   "description": "$0B94-$0BB4: game settings/state bytes (mapSeed $0B95-97, flags $0B9B-$0BA8, two 6-byte text fields $0BA9/$0BAF)"
  },
  {
   "addr": "0C8A",
   "length": 2,
   "type": "byteTable",
   "name": "perSideValueTable",
   "description": "$31,$63 indexed by player side"
  },
  {
   "addr": "0CE4",
   "length": 4,
   "type": "text",
   "name": "strOurs",
   "description": "'OURS' default owner name"
  },
  {
   "addr": "0D67",
   "length": 64,
   "type": "byteTable",
   "name": "keyMatrixCodeTable",
   "description": "8x8 keyboard matrix -> key code (row = CIA1 PA line 0-7, column = PB bit 0-7); letters/digits are PETSCII|$80, F1/F3/F5/F7 = 0-3, DEL $88, RETURN $8D, CRSR $8E/$8B, SPACE $A0, $FE = modifier (ignored), $FF = CTRL (no key)"
  },
  {
   "addr": "0DA7",
   "length": 16,
   "type": "byteTable",
   "name": "shiftedKeyTable",
   "description": "shifted replacements for key codes $AC-$BB (',' '-' '.' '/' '0'-'9' ':' ';')"
  },
  {
   "addr": "0F81",
   "length": 16,
   "type": "text",
   "name": "msgNotADataDisk",
   "description": "'NOT A DATA DISK!'"
  },
  {
   "addr": "0F91",
   "length": 17,
   "type": "text",
   "name": "msgInsertGameDisk",
   "description": "'INSERT GAME DISK.'"
  },
  {
   "addr": "0FA2",
   "length": 11,
   "type": "text",
   "name": "msgDiskError",
   "description": "'DISK ERROR.'"
  },
  {
   "addr": "0FAD",
   "length": 4,
   "type": "text",
   "name": "diskIdTable",
   "description": "'OZ','EA' disk ids (first char at $0FAD+X, second at $0FAE+X)"
  },
  {
   "addr": "10EF",
   "length": 2,
   "type": "byteTable",
   "name": "spriteEnableScratch",
   "description": "$10EF written by the IRQ with the sprite enable shadow; $10F0 unused"
  },
  {
   "addr": "1299",
   "length": 3,
   "type": "unknown",
   "name": "padIrq1299",
   "description": "$00,$09,$13 between irqRti and updateFrameTimers; unreferenced"
  },
  {
   "addr": "131A",
   "length": 1,
   "type": "byteTable",
   "name": "cursorBlinkTimer",
   "description": "30-frame countdown used by blinkCursorColor"
  },
  {
   "addr": "1412",
   "length": 2,
   "type": "byteTable",
   "name": "menuFlag1412",
   "description": "$1412 menu flag, $1413 link-state copy"
  },
  {
   "addr": "1535",
   "length": 6,
   "type": "addrTable",
   "name": "menuItemHandlerTable",
   "description": "3 handler addresses for fire on menu item 0-2: $158E (RTS), $17DA, $1A49"
  },
  {
   "addr": "163A",
   "length": 3,
   "type": "byteTable",
   "name": "menuItemColumnTable",
   "description": "$0B,$13,$1B - column/position of the three menu items"
  },
  {
   "addr": "163D",
   "length": 3,
   "type": "byteTable",
   "name": "menuItemNormalAttrTable",
   "description": "attribute bytes for unhighlighted menu items"
  },
  {
   "addr": "1640",
   "length": 3,
   "type": "byteTable",
   "name": "menuItemHighlightAttrTable",
   "description": "attribute bytes for the highlighted menu item (0 = use normal)"
  }
 ],
 "misclassified": [
  {
   "addr": "0C0D",
   "length": 15,
   "actual": "code",
   "evidence": "AD 00 CD C9 4C D0 F8 A9 00 8D 1A D0 4C 00 CD = lda $CD00 / cmp #$4C / bne $0C0C / lda #0 / sta $D01A / jmp $CD00; unreferenced, probably dead"
  },
  {
   "addr": "0DB7",
   "length": 250,
   "actual": "code",
   "evidence": "scanKeyboard/testKeyMask/pollKeyboardEvent: reached via the vector $E013/$E014 written by sub_0F44 (= $0DB7) and jsr $0E56 at $11E7 inside the untraced IRQ handler; disassembles cleanly (CIA1 matrix scan with self-modifying operands at $0E2B/$0E3A/$0E41/$0E43/$0E54) and ends with RTS at $0EB0"
  },
  {
   "addr": "0EE5",
   "length": 5,
   "actual": "code",
   "evidence": "A9 00 4C DA 0E = lda #0 / jmp $0EDA (into sub_0ED5); unreferenced alternate entry"
  },
  {
   "addr": "10F1",
   "length": 424,
   "actual": "code",
   "evidence": "raster IRQ handler installed by sub_0C66 ($AB/$AC = $10F1 -> $FFFE/$FFFF); starts PHA TXA PHA TYA PHA TSX LDA $0104,X AND #$10 (BRK test), ends with PLA TAY PLA TAX PLA RTI at $1293-$1298"
  },
  {
   "addr": "129C",
   "length": 110,
   "actual": "code",
   "evidence": "called by jsr $129C at $127A inside the IRQ handler; timer updates ending RTS at $1309"
  },
  {
   "addr": "130A",
   "length": 16,
   "actual": "code",
   "evidence": "called by jsr $130A at $1238; lda $57 / ora $58 / ora $59 ... jsr $FD5D / rts"
  },
  {
   "addr": "131B",
   "length": 22,
   "actual": "code",
   "evidence": "called by jsr $131B at $1277; dec $131A ... rts at $1330 ($131A is a data byte)"
  },
  {
   "addr": "1331",
   "length": 225,
   "actual": "code",
   "evidence": "jsr $1331 at $127D and jsr $1399 at $1280; chat send/receive code using comm entries $E006/$E009 and $C7D5/$C8A8, ends RTS at $1411"
  }
 ],
 "insights": [
  "The raster IRQ handler is at $10F1 (line $FA), installed by $0C66 with the vector shadowed in zp $AB/$AC and re-written into $FFFE/$FFFF every interrupt; $1298 is the RTI used for NMI. The handler also receives BRK and routes it through the patchable JMP operand at $10FF/$1100 (normally to the plain exit $1293) - the comm module contains BRK instructions at $E01D/$E039.",
  "VIC shadow registers live at $9000-$9024: $9000 D011, $9001 D016, $9002 D018, $9003-$9006 border/background colours, $9008-$900F sprite colours (>= $10 means two nibble colours that flash), $9010 sprite X, $9018 sprite Y, $9020 X MSB, $9022 enable, $9023 X expand, $9024 Y expand. When $92C8 is nonzero the sprite positions get fine-scroll offsets from $92F4 (x & 7) and $92F5 ((y & 7) - 3). Game code should write the shadows, never the VIC directly.",
  "Input model: the IRQ polls the keyboard through the comm module entry $E015 (which calls the game's scanKeyboard at $0DB7 via the vector $E013/$E014) and joystick port 2 ($DC00 with all rows deselected). Results: zp_5D direction (active-low bits, $0F centred), zp_5F fire released (1 = up), zp_66 current key, $90EA new key event, $9248 last typed PETSCII key, $92E4/$92E5 shift and C= flags. F1/F3/F5/F7 produce key codes 0-3 and sub_06BA switches screens on them ($90FB). SHIFT + C= + F7 aborts to the main menu ($0AA3).",
  "Six frame countdown timers at zp $6C-$71 are decremented by the IRQ: $6C joystick auto-repeat (16 then 6 frames via sub_0D49), $6D fire debounce (10), $6E menu blink (10), $70 key repeat (20). The 24-bit frame counter is zp $45-$47. sub_FD5D in high memory is a 24-bit LFSR RNG on zp $57-$59.",
  "Disk access protocol with the comm module: before any load the game calls suspendCommModule ($0F5F: $E039=$C0, wait for $E03A bit 7) and afterwards resumeCommModule ($0F71). $E03B is the link-active flag, $E011 answer/originate (0/1), $E030 identifies the resident comm build, $E006 = get received byte (C=1 none), $E009 = send byte, $E000/$E00C are called every IRQ.",
  "The settings block $0B7D-$0BB4 holds the start-up/game options: $0B9B solo mode (bit 7), $0B9C link option, $0B9F/$0BA0 player side (0/1, $FE unset), $0BA1 first play, $0BA3 game type (bits 0-2 index the names at $CCC8: Scrimmage, QB Sneak, The Bomb, Face-Off, Sluggers, Full War, Defender; bit 6 custom), $0BA5/$0BA6 link type (comm build), $0BA7 loading depth, $0BA8 resume flag. $0B7F is a 21-char owner name built from the disk's personalised BAM bytes ($FFDE/$FFE8), default 'OURS'.",
  "Seed label corrections: $0EFE is stopAllSound (lda #0 / jmp $C89A), the overlay loader described in the seed note is sub_0F03; $10A4 (seed loadMapAndSetup) is not an instruction boundary - the routine is sub_10AB; $1081 (seed loadOverlayBIfNeeded) is mid-instruction - the routine starts at $1080; $0F55 does an 8-frame delay ($C37F) rather than a retry. Calls rendered as bootReceiveSectorData ($C0EF) from game code actually hit the runtime $C000 overlay routine at $C0EF (print A newlines), not the boot loader.",
  "Incoming chat text is buffered at $9253 (36 chars, length in $9277) and mirrored with bit 7 set at $0549 (marker $0548); RETURN on a live link enters typing mode ($929C=$C0). Message slots for the status line are a pointer table at $0500/$0524 (lo/hi, index X) written by $C924, and a 9-entry message queue at $9249-$9252 managed by $C566/$C6EF."
 ]
}