{
  "chunk": "game_ovl_E000_T34_E000",
  "unit": "game/ovl_E000_T34",
  "routines": [
    {"addr":"E000","name":"commRequest","summary":"Jump-table entry 0 (JMP L_E111). The game's per-frame/protocol request call: X selects the function (0 = service tick, 1 = request that the outgoing packet be sent, >=2 = acknowledge/consume a completed exchange). Called from the raster IRQ ($1129 with X=0) and from the packet-exchange code in main and in the $EC00 sub-overlay.","inputs":"X = function code","outputs":"see L_E111; updates $E01D exchange flags","confidence":"high"},
    {"addr":"E003","name":"commLinkControl","summary":"Jump-table entry 1 (JMP L_E29A). Link life-cycle control: X=0 open the link, X=1 cold-initialise the module, X=2 shut the UART down, X>=3 hang up then shut down. Called from $0F46 (X=1, right after the module is loaded), $1B70 (X=0, openCommLink) and $1B56 (X=3, hangUpModemSetState).","inputs":"X = function code","outputs":"see L_E29A","confidence":"high"},
    {"addr":"E006","name":"getLinkByte","summary":"Jump-table entry 2 (JMP L_E0A9). Pops one byte that arrived from the opponent (or was echoed by the modem in terminal mode) out of the 8-byte host-input ring at $E086. Used by receiveChatCharacter ($1399).","inputs":"none","outputs":"A = byte (bit 7 inverted back), C=0 if a byte was returned, C=1 if the ring was empty","confidence":"high"},
    {"addr":"E009","name":"putLinkByte","summary":"Jump-table entry 3 (JMP L_E0F6). Pushes one byte into the 16-byte host-output ring at $E091 for transmission to the opponent (chat characters); the byte is stored EOR $80. Reached by JMP from handleChatSend ($1396).","inputs":"A = byte to send","outputs":"$E091 ring, $E02E count; silently drops the byte when the ring already holds 16","confidence":"high"},
    {"addr":"E00C","name":"pollLinkStatus","summary":"Jump-table entry 4 (JMP L_E3CB). Called once per raster IRQ from $112C: services the NMI suspend/resume handshake and samples + debounces the modem carrier-detect line.","inputs":"$E039, $E03A, $E03B, CIA2_PRB","outputs":"$E03C (debounced carrier), $E03D (raw sample), $E043 (debounce counter)","confidence":"high"},
    {"addr":"E012","name":"keyboardScanHook","summary":"Jump-table entry 5. In the shipped image it is JMP L_E3B2 (= return A=$FF, no key), but the main program patches the operand bytes $E013/$E014 at $0F4B/$0F50 to $0DB7 = scanKeyboard, so at run time this entry is the keyboard matrix scanner. Called only by readKeyAndHandleModemHotkeys.","inputs":"none","outputs":"A = key code ($FF = none), Y = $80 if SHIFT held, X = $80 if the Commodore key is held","confidence":"high"},
    {"addr":"E015","name":"commKeyEntry","summary":"Jump-table entry 6 (JMP L_E2EC). The game's only keyboard read: called every frame by pollKeyboardEvent ($0E56). Runs the keyboard scan through the comm module so that Commodore-key hot keys can control the modem before the game sees them.","inputs":"none","outputs":"A = key code or $FF when the key was swallowed as a modem hot key","confidence":"high"},
    {"addr":"E018","name":"clearPacketVars","summary":"LDX #$13 / JSR clearInlineVarBlock followed by the 19 bytes it clears. Zeroes the packet-exchange variables $E01D-$E02F (exchange flags, rx/tx lengths, both 7-byte packet buffers, host-output count). Because clearInlineVarBlock discards the return address, control returns to this routine's caller. Called from initCommModule ($E2B4).","inputs":"none","outputs":"$E01D-$E02F = 0","confidence":"high"},
    {"addr":"E034","name":"clearLinkVars","summary":"LDX #$0F / JSR clearInlineVarBlock followed by the 15 bytes it clears. Zeroes the link-state variables $E039-$E047 (NMI suspend request/ack, link-active flag, carrier state, modem command phase, connection phase, error counter). Entered by JMP from L_E2AE, i.e. it is the tail of the 'close the link' path.","inputs":"none","outputs":"$E039-$E047 = 0; returns to the caller of the routine that jumped here","confidence":"high"},
    {"addr":"E067","name":"clearInlineVarBlock","summary":"Self-modifying helper used five times in this module. It pulls the return address off the stack, patches it into the STA abs,X at $E071 and then zeroes X bytes starting one byte past the JSR, i.e. the inline data block that immediately follows the call. Since the return address was consumed, the RTS returns to the caller's caller.","inputs":"X = number of bytes to clear; return address on the stack points at the inline block","outputs":"inline block zeroed; A=0, X=0; returns two levels up","confidence":"high"},
    {"addr":"E078","name":"clearProtocolVars","summary":"LDX #$2B / JSR clearInlineVarBlock followed by the 43 bytes it clears ($E07D-$E0A7): exchange counter, packet rx/tx state machines, both host rings and their indices, the in-flight counters and the service lock. $E0A8 is deliberately left alone. Called from initCommModule ($E2B1).","inputs":"none","outputs":"$E07D-$E0A7 = 0","confidence":"high"},
    {"addr":"E0A9","name":"popHostInRing","summary":"Implementation of the $E006 entry: if $E0A1 (count) is zero return C=1; otherwise read $E086,X with X = read index $E0A2, EOR #$80, decrement the index (wrapping 0 -> 7) and the count, return C=0.","inputs":"$E0A1, $E0A2, $E086-$E08D","outputs":"A = byte, C = 0/1, $E0A1, $E0A2 updated","confidence":"high"},
    {"addr":"E0C3","name":"popHostOutRing","summary":"Pops one byte out of the 16-byte host-output ring at $E091 (count $E02E, read index $E08F, index decrements and wraps 0 -> 15). Used to feed the packet transmitter, to drain the ring when the link drops, and to feed the modem transmitter in terminal mode.","inputs":"$E02E, $E08F, $E091-$E0A0","outputs":"A = byte, C=0, or C=1 when empty","confidence":"high"},
    {"addr":"E0DD","name":"pushHostInRing","summary":"Pushes A into the 8-byte host-input ring at $E086 (count $E0A1, write index $E0A3). Silently drops the byte when the ring already holds 8. Used by the packet receiver for incoming chat bytes and by the modem terminal loop to echo modem responses to the screen.","inputs":"A = byte","outputs":"$E086 ring, $E0A1, $E0A3","confidence":"high"},
    {"addr":"E0F6","name":"pushHostOutRing","summary":"Implementation of the $E009 entry: if the ring already holds 16 bytes do nothing, else store A EOR $80 at $E091,X with X = write index $E090, decrement/wrap the index and bump $E02E.","inputs":"A = byte","outputs":"$E091 ring, $E02E, $E090","confidence":"high"},
    {"addr":"E111","name":"commRequestDispatch","summary":"Dispatcher behind $E000. X=0 -> serviceCommTick; X=1 -> requestPacketSend; X>=2 -> if $E01D bit 6 (exchange complete) is set, clear $E01E, $E01F and $E01D, i.e. acknowledge and consume the finished exchange.","inputs":"X = function code, $E01D","outputs":"see the three cases","confidence":"high"},
    {"addr":"E128","name":"requestPacketSend","summary":"$E000 with X=1: increments $E01D (setting the low 'send requested' bits) and increments $E085, the count of outstanding send requests. The packet transmitter picks the request up on the next service tick.","inputs":"none","outputs":"$E01D +1, $E085 +1","confidence":"high"},
    {"addr":"E12F","name":"serviceCommTick","summary":"$E000 with X=0, called from the raster IRQ every frame. DEC/INC of $E0A7 forms a re-entrancy lock (the body only runs when $E0A7 is 1). It always runs runModemStateMachine, and when the connection has reached phase 3 and the link is active it also runs the packet receive and packet transmit state machines.","inputs":"$E0A7, $E040, $E03B","outputs":"drives the whole protocol stack; $E0A7 restored to 1","confidence":"high"},
    {"addr":"E14D","name":"receivePacketFsm","summary":"Packet-layer receiver driven by $E081. State 0 reads a header byte through receiveLinkByte: the low 3 bits are the payload length, bit 7 means the payload is chat/text bytes that go straight into the host-input ring, otherwise the payload is a command packet stored at $E020 with the length in $E01E. Header values $70-$7F are a special case: the payload is copied into the scratch buffer at $EB03 and a zero-length $7x packet does JSR $EB03. A completed packet sets bit 7 of $E01D through setExchangeFlags.","inputs":"$E081, $E082, $E080, receiveLinkByte","outputs":"$E020-$E026, $E01E, $E01D bit 7, host-input ring, $EB03 buffer","confidence":"medium"},
    {"addr":"E170","name":"beginReceivePacket","summary":"Mid-routine entry of receivePacketFsm reached only by the T35 build ($E07E): clears $E01E and adds 2 to $E01D (marking 'a packet is being received'), then falls into the body that stores the payload.","inputs":"$E082","outputs":"$E01E = 0, $E01D +2","confidence":"medium"},
    {"addr":"E1DD","name":"sendPacketFsm","summary":"Packet-layer transmitter driven by $E083. When nothing is in flight it either sends the pending command packet (header = $E01F | $E07F, then $E01F bytes from $E027) or, when the host-output ring holds chat bytes, a chat frame (header $80 | count, up to 3 bytes popped with popHostOutRing). Once the transmitter has drained it marks the send complete with bit 5 of $E01D and clears $E083.","inputs":"$E083, $E085, $E02E, $E01F, $E027-$E02D, $E0A6","outputs":"bytes handed to sendLinkByte, $E0A6 = bytes+1, $E01D bits 3/5, $E084, $E08E","confidence":"medium"},
    {"addr":"E283","name":"countCompletedExchange","summary":"Increments the 16-bit counter $E07D/$E07E and then sets bit 6 of $E01D. Reached from setExchangeFlags whenever bits 7 and 5 are both set but bit 6 is not, i.e. exactly once per completed send+receive exchange. The counter itself is never read anywhere in the image, so it looks like a leftover statistic.","inputs":"$E07D/$E07E","outputs":"$E07D/$E07E +1, $E01D bit 6 set","confidence":"medium"},
    {"addr":"E28D","name":"setExchangeFlags","summary":"ORs A into the exchange flag byte $E01D; if the result has bits 7 and 5 set and bit 6 clear (mask $E0 == $A0) the exchange is complete, so it falls into countCompletedExchange which sets bit 6 as well. A = $80 marks 'opponent packet received', $20 marks 'own packet sent', $40 marks 'complete', $08 is used as an idle/keep-alive marker.","inputs":"A = flag bits","outputs":"$E01D updated, possibly $E07D/$E07E","confidence":"high"},
    {"addr":"E29A","name":"commLinkControlDispatch","summary":"Dispatcher behind $E003. X=0 -> openCommLink; X=1 -> initCommModule; X=2 -> stopCommNmi + clearLinkVars; X>=3 -> hangUpModem, spin until $E03F <= 1, then stopCommNmi + clearLinkVars.","inputs":"X","outputs":"see cases; ends by clearing $E039-$E047","confidence":"high"},
    {"addr":"E2B1","name":"initCommModule","summary":"$E003 with X=1, called from $0F46 immediately after the comm module has been loaded. Clears the protocol vars, the packet vars, the UART/ARQ ring vars and the ARQ state block, and switches the three link I/O vectors to the framed (ARQ) implementations via selectFramedLinkVectors.","inputs":"none","outputs":"$E07D-$E0A7, $E01D-$E02F, $E414-$E46D, $EAB7-$EB02 zeroed; $E3B7/$E3BA/$E3BD patched","confidence":"high"},
    {"addr":"E2BD","name":"clearArqVars","summary":"Tail of initCommModule: JSR $EAB2 (which clears $EAB7-$EB02, the frame/ARQ layer state) and RTS.","inputs":"none","outputs":"$EAB7-$EB02 = 0","confidence":"high"},
    {"addr":"E2C1","name":"openCommLink","summary":"$E003 with X=0, called from openCommLink ($1B70) in main. If the link is not already up it reloads the baud parameters for the current baud index, installs the NMI vector at $FFFA/$FFFB, restarts the UART and sets $E03B. It then always configures the user-port lines and, unless $E041 says otherwise, queues the Hayes init string selected by $E011 (answer: 'ATQ0V1X1A', originate: 'ATQ0V1X1D'), and finally releases the service lock ($E0A7 = 1).","inputs":"$E03B, $E055, $E041, $E011","outputs":"$E03B = 1, NMI vector, CIA2 configured, modem command queued, $E0A7 = 1","confidence":"high"},
    {"addr":"E2EC","name":"readKeyAndHandleModemHotkeys","summary":"Implementation of $E015. Calls the patched keyboard hook, optionally paints the border with the link status colour from borderColourTable (indexed by connectionPhase*2 + carrier), and then, when the Commodore key is held (X bit 7) and a new key code with bit 7 set arrives, interprets it as a modem hot key: $C8 'H' toggles DTR/PB5 via the mask $E04C, $C3 'C' toggles the carrier-override bit in $E04A, $B3 '3' selects 300 baud, $B1 '1' selects 1200 baud, $8D RETURN re-opens the link without re-sending the init string, $D0 'P' hangs up, $C1 'A' selects answer mode and $CF 'O' selects originate mode. A handled key is swallowed by returning $FF.","inputs":"keyboard hook result (A/X/Y), $E03B, $E03C, $E040, $E046","outputs":"A = key code or $FF, VIC_BORDER, $E046, $E011, $E055, $E04A, CIA2_PRB, modem command queued","confidence":"medium"},
    {"addr":"E353","name":"loadBaudParameters","summary":"Copies the 3-byte entry at $E059+X into $E056-$E058 (bit-period low, bit-period high, transmit pacing divisor) using the trick 'LDY #$FD / STA $DF59,Y' so that Y=$FD..$FF addresses $E056..$E058. X=0 selects {$50,$0D,$01} = $0D50 cycles = 300 baud, X=3 selects {$53,$03,$02} = $0353 cycles = 1200 baud. Returns A=$FF (so the caller in the hot-key dispatcher swallows the key).","inputs":"X = 0 or 3","outputs":"$E056-$E058, A=$FF","confidence":"high"},
    {"addr":"E38D","name":"hangUpModem","summary":"Drops DTR (dropDtrLine writes $22 to CIA2_PRB) and then queues the string at $EB00+$EBFD ($EBD6), which the transmitter emits in reverse as [pause] '+++' [pause] CR [pause] 'ATH0' CR - the Hayes escape sequence with guard times followed by the hang-up command. Reached by $E003 X>=3 and by the 'P' hot key.","inputs":"$EBFD, $E048 bit 7","outputs":"CIA2_PRB, host-output ring loaded with the hang-up string, $E03F = $FF","confidence":"high"},
    {"addr":"E393","name":"queueModemCommandString","summary":"Loads a canned modem command string into the host-output ring. A is the low byte of a string address in page $EB; the routine patches the LDA $EB00,X at $E3A1, copies bytes until it copies a zero, and then sets the ring write index to 0, the read index and the count to the number of bytes copied. Because the ring pops with a decrementing index, the string is transmitted in reverse of how it is stored, which is why the AT strings in the $EBD6-$EBFC table are written backwards. Does nothing (returns A=$FF) when bit 7 of the modem option byte $E048 is clear.","inputs":"A = offset in page $EB, $E048","outputs":"$E092.., $E08F, $E090, $E02E, $E03F = $FF, A = $FF","confidence":"high"},
    {"addr":"E3B2","name":"returnNoKey","summary":"LDA #$FF / RTS - the 'no key / handled' return shared by the hot-key dispatcher and by queueModemCommandString when no modem is configured. Also the unpatched target of the $E012 jump-table entry.","inputs":"none","outputs":"A = $FF","confidence":"high"},
    {"addr":"E3B6","name":"sendLinkByte","summary":"JMP ($E3BF) - indirect 'send one byte to the link'. The operand byte at $E3B7 selects between $E3BF (raw: push into the UART transmit ring) and $E3C1 (framed: push into the ARQ transmit ring). initCommModule always selects the framed variant.","inputs":"A = byte","outputs":"C=1 when the target ring is full","confidence":"high"},
    {"addr":"E3B9","name":"receiveLinkByte","summary":"JMP ($E3C3) - indirect 'get one byte from the link'. The operand at $E3BA selects $E3C3 (raw: pop the UART receive ring) or $E3C5 (framed: pop the ARQ receive ring).","inputs":"none","outputs":"A = byte, C=0, or C=1 when empty","confidence":"high"},
    {"addr":"E3BC","name":"reportLinkError","summary":"JMP ($E3C7) - indirect link-error hook called by the NMI receiver on a framing error, a bad stop bit or a receive-ring overflow. The operand at $E3BD selects $E3C7 (no-op stub at $E408) or $E3C9 (count the error at $E403). initCommModule selects the counting variant.","inputs":"none","outputs":"$E047 incremented (framed variant)","confidence":"high"},
    {"addr":"E3CB","name":"pollCarrierState","summary":"Implementation of the $E00C entry, run once per raster IRQ. Calls serviceCarrierAndSuspendRequest to take a fresh carrier sample into $E03D, then debounces: while the sample differs from the accepted state $E03C it counts up in $E043 and only accepts the new state after $F0 (240) consecutive differing samples; otherwise it merges only bit 7 of the sample into $E03C. Bit 6 of $E03C is the 'carrier present' flag the game tests.","inputs":"$E03D, $E03C, $E043","outputs":"$E03C, $E043","confidence":"high"},
    {"addr":"E3F3","name":"selectFramedLinkVectors","summary":"Patches the low byte of the three JMP (abs) operands at $E3B7/$E3BA/$E3BD to $C1/$C5/$C9, moving sendLinkByte, receiveLinkByte and reportLinkError from the raw-UART implementations to the framed (ARQ) implementations and the error counter. Called once from initCommModule.","inputs":"none","outputs":"$E3B7, $E3BA, $E3BD","confidence":"high"},
    {"addr":"E403","name":"countLinkError","summary":"NOP / INC $E047 / RTS - the framed link error hook: bumps the link error counter that runModemStateMachine inspects at $E7FA. Currently rendered as .byte data by the disassembler.","inputs":"none","outputs":"$E047 +1","confidence":"high"},
    {"addr":"E408","name":"ignoreLinkError","summary":"PHP / PHA / NOP / PLA / PLP / RTS - the raw-mode link error hook, a no-op that preserves A and the flags. Followed by a stray RTS at $E40E. Currently rendered as .byte data.","inputs":"none","outputs":"none","confidence":"high"},
    {"addr":"E40F","name":"clearUartVars","summary":"LDX #$5A / JSR clearInlineVarBlock followed by the 90 bytes it clears ($E414-$E46D): the UART transmit ring (22), the UART receive ring (20), the ARQ transmit ring (18) and the ARQ receive ring (18) together with their counts and read/write indices. Called from initCommModule.","inputs":"none","outputs":"$E414-$E46D = 0","confidence":"high"},
    {"addr":"E46E","name":"popArqRxRing","summary":"Pops one byte from the 18-byte ARQ receive ring at $E45C (count $E459, read index $E45A, index decrements and wraps 0 -> 17); C=1 when empty. This is the 'framed' target of receiveLinkByte, so it is how the packet layer gets the payload the frame layer has validated. Currently rendered as .byte data by the disassembler.","inputs":"$E459, $E45A, $E45C-$E46D","outputs":"A = byte, C = 0/1","confidence":"high"},
    {"addr":"E486","name":"popArqTxRing","summary":"Pops one byte from the 18-byte ARQ transmit ring at $E447 (count $E444, read index $E445). Reached through the thunk at $E733 from the frame builder at $E8AE/$E8D4, i.e. this is where the frame layer takes the bytes the packet layer queued.","inputs":"$E444, $E445, $E447-$E458","outputs":"A = byte, C = 0/1","confidence":"high"},
    {"addr":"E49E","name":"pushArqRxRing","summary":"Pushes A into the 18-byte ARQ receive ring at $E45C (count $E459, write index $E45B); drops the byte when the ring is full. Reached through the thunk at $E736 from the frame receiver at $EA91.","inputs":"A","outputs":"$E45C ring, $E459, $E45B","confidence":"high"},
    {"addr":"E4B7","name":"pushArqTxRing","summary":"Pushes A into the 18-byte ARQ transmit ring at $E447 (count $E444, write index $E446); returns with the ring unchanged when it already holds 18. This is the 'framed' target of sendLinkByte. Currently rendered as .byte data by the disassembler.","inputs":"A","outputs":"$E447 ring, $E444, $E446","confidence":"high"},
    {"addr":"E4D0","name":"returnRingEmpty","summary":"SEC / RTS - the shared 'ring empty' exit used by four of the ring pop routines in the $E486-$E51B group.","inputs":"none","outputs":"C = 1","confidence":"high"},
    {"addr":"E4D2","name":"popUartRxRing","summary":"Pops one byte from the 20-byte raw UART receive ring at $E430 (count $E42D, read index $E42E). Used directly by the modem terminal loop ($E799, $E80A), through the thunk at $E739, and as the raw target of receiveLinkByte.","inputs":"$E42D, $E42E, $E430-$E443","outputs":"A = byte, C = 0/1","confidence":"high"},
    {"addr":"E4EA","name":"popUartTxRing","summary":"Pops one byte from the 22-byte raw UART transmit ring at $E417 (count $E414, read index $E415). Called by the NMI transmitter (startNextTxChar) and by flushUartTxRing.","inputs":"$E414, $E415, $E417-$E42C","outputs":"A = byte, C = 0/1","confidence":"high"},
    {"addr":"E502","name":"pushUartRxRing","summary":"Pushes A into the 20-byte raw UART receive ring at $E430 (count $E42D, write index $E42F); returns C untouched and leaves the ring alone when full, which the NMI receiver treats as an overflow error. Called only from the NMI at $E71F.","inputs":"A = received character","outputs":"$E430 ring, $E42D, $E42F","confidence":"high"},
    {"addr":"E51B","name":"pushUartTxRing","summary":"Pushes A into the 22-byte raw UART transmit ring at $E417 (count $E414, write index $E416); drops the byte when full. Called from queueByteForTransmit and, in raw mode, from sendLinkByte.","inputs":"A","outputs":"$E417 ring, $E414, $E416","confidence":"high"},
    {"addr":"E534","name":"flushUartTxRing","summary":"Drains the raw UART transmit ring by popping until it is empty, then sets the pending-byte count $E0A5 to the current transmitter-busy flag $E5BB. Used when the connection state changes so that stale bytes are not sent.","inputs":"$E414 ring, $E5BB","outputs":"transmit ring emptied, $E0A5","confidence":"high"},
    {"addr":"E540","name":"configureUserPortLines","summary":"Sets up CIA2 for the user-port serial link: TA latch $0003 and CRA = $51 (TA running, serial port in output mode), writes the pattern from $E051 indexed by ((3 AND $E011) EOR $E055) into CIA2_SDR, sets CIA2_DDRB to $26 (PB1/PB2/PB5 outputs = RTS, DTR and one spare), drives CIA2_PRB with $E04F,X ($26 = RTS and DTR asserted) and finally raises PA2 (TXD) to the mark level. The purpose of the CIA2_SDR write is unclear - SP2 is a user-port pin, so it may drive an external handshake line, or it may be vestigial.","inputs":"$E011, $E055, $E04D, $E04F, $E051","outputs":"CIA2_CRA, CIA2_TA, CIA2_SDR, CIA2_DDRB, CIA2_PRB, CIA2_PRA","confidence":"medium"},
    {"addr":"E574","name":"dropDtrLine","summary":"Writes $E04E ($22) to CIA2_PRB, which clears PB2 = DTR while leaving PB1/PB5 high. On a Hayes modem dropping DTR terminates the call. Called only from hangUpModem.","inputs":"$E04E","outputs":"CIA2_PRB","confidence":"high"},
    {"addr":"E57B","name":"installCommNmiVector","summary":"Writes $E685 into the RAM NMI vector at $FFFA/$FFFB so that CIA2 interrupts enter this module's bit-bang UART handler. Called from openCommLink.","inputs":"none","outputs":"$FFFA/$FFFB = $E685","confidence":"high"},
    {"addr":"E586","name":"restartUart","summary":"Clears the six bit-level UART state bytes ($E5BB-$E5C0), sets $E042 = 1 (ask the modem state machine to re-evaluate), primes the carrier poll timer $E045 with $92 and falls into setCiaNmiMask with A=$92, which enables the CIA2 FLAG (start bit) and timer B (transmit) NMI sources and marks the module as running.","inputs":"none","outputs":"$E5BB-$E5C0 = 0, $E042 = 1, $E045 = $92, CIA2 ICR/CRA/CRB, $E033, $E039, $E03A = 0","confidence":"high"},
    {"addr":"E593","name":"setCiaNmiMask","summary":"Stores A into the ICR shadow $E033, acknowledges pending CIA2 interrupts, writes the new mask to CIA2_ICR, stops both timers, and derives the suspend flags: A EOR $FF AND $80 gives $00 for an enable mask ($92 or $83) and $80 for the disable mask ($7F), which is written to both $E039 and $E03A. Ends by jumping to the 'raise TXD' tail at $E56B.","inputs":"A = CIA2 ICR command byte","outputs":"$E033, CIA2_ICR/CRA/CRB, $E039, $E03A, CIA2_PRA bit 2","confidence":"high"},
    {"addr":"E5B6","name":"clearUartState","summary":"LDX #$06 / JSR clearInlineVarBlock followed by the 6 bytes it clears ($E5BB-$E5C0): transmitter-active flag, bit-count reload, transmit bit counter, transmit shift register, receive bit counter and receive shift register.","inputs":"none","outputs":"$E5BB-$E5C0 = 0","confidence":"high"},
    {"addr":"E5C3","name":"stopCommNmi","summary":"Sets $E042 = $FF (ask the modem state machine to fall back to phase 0), calls setCiaNmiMask with $7F to disable every CIA2 NMI source (which sets $E039/$E03A = $80 = suspended) and, if a character was still being transmitted, decrements the pending count $E0A5.","inputs":"$E5BB","outputs":"CIA2 interrupts off, $E039/$E03A = $80, $E042 = $FF, $E0A5","confidence":"high"},
    {"addr":"E5D6","name":"serviceCarrierAndSuspendRequest","summary":"First half of the $E00C service. Handles the suspend handshake used by the disk loader: $E039 bit 7 set means 'suspend' and calls stopCommNmi unless $E03A already acknowledges it, $E039 clear while $E03A still says suspended calls restartUart. Then, when the link is active, it samples carrier detect - CIA2_PRB bit 4 (DCD), XORed with the polarity/override byte $E04A - and stores $F8 (carrier) or $80 (no carrier) into $E03D. $E045 is a free-running counter that periodically re-arms the sample.","inputs":"$E039, $E03A, $E03B, $E04A, CIA2_PRB, $E045","outputs":"$E03D, $E045, NMI enabled/disabled","confidence":"medium"},
    {"addr":"E5FF","name":"rearmCarrierTimer","summary":"Continuation inside serviceCarrierAndSuspendRequest (also called directly by the T35 build): stores the current carrier sample into $E045 and clears bit 7 of the value that goes into $E03D, marking the sample as 'not yet stable'.","inputs":"A","outputs":"$E045, A","confidence":"medium"},
    {"addr":"E607","name":"startNextTxChar","summary":"Starts the transmission of the next character. Returns immediately when timer B is already running or a character is still in flight; outside terminal mode it also paces characters with the $E044 counter and requires carrier. It then pops a byte from the UART transmit ring into the shift register $E5BE, sets $E5BB, the 9-bit count $E5BC, reloads the pacing counter from $E058, programs timer B with a short $0150 initial delay and starts it (CRB = $11), and finally falls through into setBitPeriod which reloads the TB latch with the full bit period so every subsequent underflow is one bit time.","inputs":"$E040, $E044, $E03C, $E03D, $E0A5, transmit ring","outputs":"$E5BB, $E5BE, $E5BC, $E044, CIA2_TB, CIA2_CRB","confidence":"high"},
    {"addr":"E654","name":"setBitPeriodFull","summary":"LDA $E057 then falls into setBitPeriod: programs the timer selected by X (X=0 timer A, X=2 timer B) with the full bit period $E057:$E056.","inputs":"X = 0 or 2","outputs":"CIA2 TA or TB latch, A = $11","confidence":"high"},
    {"addr":"E657","name":"setBitPeriod","summary":"Writes A into CIA2_TA_HI+X and $E056 into CIA2_TA_LO+X, then loads A with $11 (start + force load) for the caller to store into the corresponding control register. X=0 addresses timer A (receive clock), X=2 timer B (transmit clock).","inputs":"A = high byte, X = 0 or 2, $E056","outputs":"CIA2 timer latch, A = $11","confidence":"high"},
    {"addr":"E663","name":"nmiStartNextChar","summary":"Timer B path of the NMI when the bit counter has run out. If a character is active it reloads the bit counter from $E5BC (9) and drives TXD low to emit the start bit; otherwise it calls startNextTxChar to fetch the next byte.","inputs":"$E5BB, $E5BC, CIA2_PRA","outputs":"$E5BD, CIA2_PRA bit 2","confidence":"high"},
    {"addr":"E685","name":"commNmiHandler","summary":"The module's CIA2 NMI handler (installed at $FFFA/$FFFB by installCommNmiVector; the disassembly leaves this address unlabelled). It saves A and X, masks CIA2 interrupts, reads the ICR (with a timer-B race workaround around CIA2_TB_HI) and, when the interrupt is not from CIA2, restores and chains through JMP ($E031) to the game's NMI handler. Timer B drives the software transmitter one bit at a time out of $E5BE onto PA2 (TXD); the CIA2 FLAG line (RXD start bit) arms timer A at half a bit time and then a full bit time, and each timer A interrupt shifts CIA2_PRB bit 0 (RXD) into $E5C0 until a whole character with a valid stop bit is pushed into the UART receive ring. Framing errors, bad stop bits and receive overflow call reportLinkError.","inputs":"CIA2_ICR, CIA2_PRB bit 0, $E033, $E5BB-$E5C0","outputs":"CIA2_PRA bit 2 (TXD), UART receive ring, $E0A5, $E033, $EB00, $E045; RTI or chain to ($E031)","confidence":"high"},
    {"addr":"E69C","name":"nmiMergeIcrFlags","summary":"Two-byte continuation inside commNmiHandler (ORA CIA2_ICR) used to merge any interrupt flags that arrived between the two ICR reads.","inputs":"A","outputs":"A","confidence":"medium"},
    {"addr":"E733","name":"popArqTxRingThunk","summary":"JMP popArqTxRing - the entry the frame layer at $E8AE/$E8D4 uses to fetch payload bytes.","inputs":"none","outputs":"A, C","confidence":"high"},
    {"addr":"E736","name":"pushArqRxRingThunk","summary":"JMP pushArqRxRing - the entry the frame layer at $EA91 uses to deliver a validated payload byte to the packet layer.","inputs":"A","outputs":"ring updated","confidence":"high"},
    {"addr":"E739","name":"popUartRxRingThunk","summary":"JMP popUartRxRing - used by receiveByteTrackZeros and by the frame receiver at $E9E9.","inputs":"none","outputs":"A, C","confidence":"high"},
    {"addr":"E73C","name":"queueByteForTransmit","summary":"Pushes A into the raw UART transmit ring and increments the pending-byte count $E0A5, which the NMI decrements as each character finishes. Used by the frame layer and by the terminal loop.","inputs":"A","outputs":"transmit ring, $E0A5","confidence":"high"},
    {"addr":"E743","name":"receiveByteTrackZeros","summary":"Pops one byte from the UART receive ring and maintains $EAB8, a counter of consecutive zero bytes (reset to 0 by any non-zero byte). The frame layer uses this to detect an idle or garbage line.","inputs":"$EAB8, receive ring","outputs":"A, C, X, $EAB8","confidence":"medium"},
    {"addr":"E756","name":"runModemStateMachine","summary":"Called from serviceCommTick every frame; drives $E040, the connection phase. $E042 requests a re-evaluation ($FF forces phase 0). Phase 0 with no carrier drains the host-output ring, flushes the transmitter and enters phase 1 (modem command/terminal mode); in phase 1 characters coming back from the modem are echoed into the host-input ring so the player sees the modem's responses, and when a verbose result line ends in '0' before CR (i.e. 'CONNECT 1200') it switches to 1200 baud. Bytes going the other way are taken from the host-output ring; a byte with bit 7 set is not sent but loaded into $EB01 as a delay countdown, which is how the guard times inside the '+++'/ATH0 strings are implemented. Once carrier is detected the routine continues past $E7C9 into the connect handshake that advances to phase 2 and then phase 3, where the packet layer runs.","inputs":"$E042, $E040, $E03C, $E03F, $E047, $E048, $EB01, $EB02, both host rings, UART rings","outputs":"$E040, $E03F, $E044, $E047, $E0A4, $E0A6, $EB01, $EB02, baud parameters, host-input ring","confidence":"medium"},
    {"addr":"E773","name":"clearInFlightFlags","summary":"Mid-routine entry of runModemStateMachine (also jumped to from the T35 build): clears the frame-in-flight flag $E0A4 and the packet-in-flight count $E0A6, which is what tells sendPacketFsm that the packet has actually left the transmitter.","inputs":"X = 0","outputs":"$E0A4 = 0, $E0A6 = 0","confidence":"medium"}
  ],
  "variables": [
    {"addr":"E011","scope":"abs","name":"isOriginateMode","meaning":"0 = answer mode ('ATQ0V1X1A'), 1 = originate/dial mode ('ATQ0V1X1D'). Set by the 'A'/'O' hot keys ($C1/$CF) and by main at $0CDE; also selects the CIA2_PRB and CIA2_SDR patterns in configureUserPortLines.","confidence":"high"},
    {"addr":"E013","scope":"abs","name":"keyboardHookVector","meaning":"The two operand bytes of the JMP at $E012. Main patches them to $0DB7 (scanKeyboard) at $0F4B/$0F50, so the comm module calls back into the game's keyboard scanner.","confidence":"high"},
    {"addr":"E01D","scope":"abs","name":"exchangeFlags","meaning":"Packet exchange state. Bits 0-1 count 'send requested' / 'receive in progress', bit 3 idle marker, bit 5 = own packet has been sent, bit 6 = exchange complete (the flag the game waits on), bit 7 = opponent packet received.","confidence":"high"},
    {"addr":"E01E","scope":"abs","name":"rxPacketLength","meaning":"Number of bytes of the opponent's packet currently in $E020.","confidence":"high"},
    {"addr":"E01F","scope":"abs","name":"txPacketLength","meaning":"Number of bytes of the local packet in $E027 that the transmitter should send.","confidence":"high"},
    {"addr":"E020","scope":"abs","name":"rxPacketBuffer","meaning":"7-byte buffer holding the command packet received from the opponent (read by mergeIncomingPacket at $4F05).","confidence":"high"},
    {"addr":"E027","scope":"abs","name":"txPacketBuffer","meaning":"7-byte buffer holding the local command packet (filled by sendOutgoingPacket at $562B).","confidence":"high"},
    {"addr":"E02E","scope":"abs","name":"hostOutCount","meaning":"Number of bytes currently in the 16-byte host-output ring at $E091.","confidence":"high"},
    {"addr":"E02F","scope":"abs","name":"remoteIdentityByte","meaning":"Byte from the opponent's identity packet, stored by the $EC00 sub-overlay at $EDAD.","confidence":"low"},
    {"addr":"E030","scope":"abs","name":"commBuildId","meaning":"Identifies which $E000 module is resident: $00 in this (T34) modem build, $FF in the T35 build. Copied into $0BA5 and tested all over main to decide whether the modem prompts apply.","confidence":"high"},
    {"addr":"E031","scope":"abs","name":"nmiChainVector","meaning":"Address the comm NMI handler chains to when the interrupt was not from CIA2. Its file value is $E010 (a lone RTI byte in the module's constant pool); main overwrites it with $1298 at $0BF3/$0BFB.","confidence":"high"},
    {"addr":"E033","scope":"abs","name":"ciaIcrMaskShadow","meaning":"Shadow of the CIA2 interrupt enable mask currently in force: $92 = FLAG (RXD start bit) + timer B, $83 = timer A + timer B while a character is being received, $7F = everything off.","confidence":"high"},
    {"addr":"E039","scope":"abs","name":"nmiSuspendRequest","meaning":"Written by the game ($0F67 = $C0 suspend, $0F78 = 0 resume) to ask the comm module to stop or restart the CIA2 NMI around disk access.","confidence":"high"},
    {"addr":"E03A","scope":"abs","name":"nmiSuspendAck","meaning":"Acknowledgement of nmiSuspendRequest: $80 = NMI disabled, $00 = running. The game spins on it at $0F6A/$0F7B.","confidence":"high"},
    {"addr":"E03B","scope":"abs","name":"isLinkActive","meaning":"Non-zero once openCommLink has installed the NMI and started the UART.","confidence":"high"},
    {"addr":"E03C","scope":"abs","name":"carrierStateDebounced","meaning":"Debounced carrier state; bit 6 = carrier present (tested by main at $1B4B, $56B1, $65BC), bit 7 accumulates the sample-valid marker.","confidence":"high"},
    {"addr":"E03D","scope":"abs","name":"carrierSample","meaning":"Raw carrier sample taken each frame: $F8 = carrier (bit 6 set), $80 = no carrier.","confidence":"high"},
    {"addr":"E03F","scope":"abs","name":"modemCommandPhase","meaning":"Counts down while a canned modem command string is being sent and the line is settling; $FF while a string is queued, $8D right after the RETURN hot key. $E003 X>=3 spins until it is 1 or 0.","confidence":"medium"},
    {"addr":"E040","scope":"abs","name":"connectionPhase","meaning":"0 = down, 1 = modem command/terminal mode, 2 = connect handshake, 3 = connected (the packet layer only runs at 3).","confidence":"high"},
    {"addr":"E041","scope":"abs","name":"skipModemInitString","meaning":"Non-zero suppresses the ATQ0V1X1A/D init string in openCommLink. Only ever set by the RETURN hot key (DEC at $E366) so that RETURN re-opens the link without redialling.","confidence":"medium"},
    {"addr":"E042","scope":"abs","name":"uartRestartRequest","meaning":"1 = re-evaluate the connection phase on the next tick (set by restartUart), $FF = force the phase back to 0 (set by stopCommNmi).","confidence":"medium"},
    {"addr":"E043","scope":"abs","name":"carrierDebounceCount","meaning":"Counts consecutive carrier samples that disagree with $E03C; a new state is accepted at $F0 (240) samples, about 4-5 seconds.","confidence":"high"},
    {"addr":"E044","scope":"abs","name":"txPaceCounter","meaning":"Per-character pacing counter used outside terminal mode; reloaded from $E058 (1 at 300 baud, 2 at 1200) each time a character starts.","confidence":"medium"},
    {"addr":"E045","scope":"abs","name":"carrierPollTimer","meaning":"Free-running counter primed with $92 that periodically re-arms the carrier sample; also zeroed by the NMI whenever a character is received (i.e. an activity watchdog).","confidence":"medium"},
    {"addr":"E046","scope":"abs","name":"lastHotkeyCode","meaning":"Last key code seen by the hot-key dispatcher, used to suppress auto-repeat while the Commodore key is held.","confidence":"high"},
    {"addr":"E047","scope":"abs","name":"linkErrorCount","meaning":"Incremented by the framed error hook at $E403 on every framing / stop-bit / overflow error; inspected by the connect handshake at $E7FA and cleared at $E814.","confidence":"medium"},
    {"addr":"E048","scope":"abs","name":"modemOptionFlags","meaning":"Constant $CC. Bit 7 = a Hayes modem is present, so AT command strings may be sent (queueModemCommandString returns immediately when it is clear); bit 6 = enable the 'CONNECT 1200' auto baud switch in the terminal loop.","confidence":"medium"},
    {"addr":"E04A","scope":"abs","name":"carrierOverrideFlags","meaning":"XOR mask / option byte used when sampling DCD. Bit 7 makes the module use the byte itself as the carrier sample (force carrier), bit 6 (toggled by the 'C' hot key with mask $E04B = $40) skips the CIA2_PRB read, bit 4 inverts the DCD polarity.","confidence":"medium"},
    {"addr":"E04B","scope":"abs","name":"carrierOverrideMask","meaning":"Constant $40, the bit the 'C' hot key toggles in $E04A.","confidence":"high"},
    {"addr":"E04C","scope":"abs","name":"dtrToggleMask","meaning":"Constant $24 (PB2 = DTR and PB5), the bits the 'H' hot key toggles in CIA2_PRB.","confidence":"high"},
    {"addr":"E04D","scope":"abs","name":"userPortDdrb","meaning":"Constant $26 written to CIA2_DDRB: PB1 (RTS), PB2 (DTR) and PB5 are outputs, PB0 (RXD) and PB4 (DCD) are inputs.","confidence":"high"},
    {"addr":"E04E","scope":"abs","name":"userPortPrbHangUp","meaning":"Constant $22 written to CIA2_PRB to drop DTR (hang up) while leaving RTS asserted.","confidence":"high"},
    {"addr":"E04F","scope":"abs","name":"userPortPrbTable","meaning":"Two bytes ($26,$26) indexed by isOriginateMode giving the idle CIA2_PRB value (RTS + DTR asserted). Both entries are identical in this build.","confidence":"high"},
    {"addr":"E051","scope":"abs","name":"ciaSdrPatternTable","meaning":"Four bytes ($27,$2F,$3F,$37) indexed by ((3 AND isOriginateMode) EOR baudIndex) and written to CIA2_SDR while CIA2 timer A shifts them out on SP2. Purpose not established.","confidence":"low"},
    {"addr":"E055","scope":"abs","name":"baudIndex","meaning":"Offset into baudParameterTable: 0 = 300 baud, 3 = 1200 baud. Set by the '3'/'1' hot keys and by the CONNECT-1200 auto-detect.","confidence":"high"},
    {"addr":"E056","scope":"abs","name":"bitPeriodLo","meaning":"Low byte of the CIA timer latch that defines one bit time ($50 for 300 baud, $53 for 1200).","confidence":"high"},
    {"addr":"E057","scope":"abs","name":"bitPeriodHi","meaning":"High byte of the bit-time latch ($0D50 = 3408 cycles = 300 baud, $0353 = 851 cycles = 1200 baud).","confidence":"high"},
    {"addr":"E058","scope":"abs","name":"txPaceReload","meaning":"Reload value for txPaceCounter: 1 at 300 baud, 2 at 1200 baud.","confidence":"medium"},
    {"addr":"E059","scope":"abs","name":"baudParameterTable","meaning":"Two 3-byte entries {$50,$0D,$01} = 300 baud and {$53,$03,$02} = 1200 baud, copied to $E056-$E058 by loadBaudParameters.","confidence":"high"},
    {"addr":"E05F","scope":"abs","name":"linkStatusBorderTable","meaning":"Eight bytes ($00,$0E,$1D,$0D,$14,$04,$1A,$0A) indexed by connectionPhase*2 + carrier. The value is shifted right to give the border colour; the shifted-out bit decides whether the border is painted at all, so terminal mode (index 2/3) always shows light blue = no carrier / blue = carrier, and the other phases only paint while the Commodore key is held.","confidence":"medium"},
    {"addr":"E07D","scope":"abs","name":"completedExchangeCount","meaning":"16-bit counter of completed packet exchanges, incremented at $E283. No code anywhere in the image reads it.","confidence":"high"},
    {"addr":"E07F","scope":"abs","name":"txHeaderExtraBits","meaning":"ORed into the outgoing packet header at $E20F. Never written, so it is always 0 - reserved or dead.","confidence":"medium"},
    {"addr":"E080","scope":"abs","name":"rxHeaderByte","meaning":"The header byte of the packet currently being received (bit 7 = chat payload, low 3 bits = length, $70-$7F = code-download packet).","confidence":"medium"},
    {"addr":"E081","scope":"abs","name":"rxFsmState","meaning":"State of receivePacketFsm: 0 = expect header, 1/2 = collecting payload, bit 7 set = collecting chat bytes.","confidence":"high"},
    {"addr":"E082","scope":"abs","name":"rxBytesRemaining","meaning":"Payload bytes still to be read for the packet in progress.","confidence":"high"},
    {"addr":"E083","scope":"abs","name":"txFsmState","meaning":"State of sendPacketFsm: 0 = idle, bit 0 = sending the payload, bit 1 = waiting for the transmitter to drain, bit 7 = the frame in flight was a chat frame (no exchange acknowledgement).","confidence":"medium"},
    {"addr":"E084","scope":"abs","name":"txByteIndex","meaning":"Index of the next byte of txPacketBuffer to hand to sendLinkByte.","confidence":"high"},
    {"addr":"E085","scope":"abs","name":"sendRequestCount","meaning":"Number of outstanding 'send my packet' requests posted through $E000 with X=1.","confidence":"high"},
    {"addr":"E086","scope":"abs","name":"hostInRing","meaning":"8-byte ring of bytes waiting to be handed to the game through $E006 (chat characters from the opponent, or modem responses echoed in terminal mode).","confidence":"high"},
    {"addr":"E08E","scope":"abs","name":"txChatBurstCount","meaning":"Number of chat bytes (max 3) being packed into the frame currently being built.","confidence":"high"},
    {"addr":"E08F","scope":"abs","name":"hostOutReadIndex","meaning":"Read index of the host-output ring; decrements and wraps from 0 to 15.","confidence":"high"},
    {"addr":"E090","scope":"abs","name":"hostOutWriteIndex","meaning":"Write index of the host-output ring; decrements and wraps from 0 to 15.","confidence":"high"},
    {"addr":"E091","scope":"abs","name":"hostOutRing","meaning":"16-byte ring of bytes the game wants to send ($E009), or the reversed modem command string loaded by queueModemCommandString.","confidence":"high"},
    {"addr":"E0A1","scope":"abs","name":"hostInCount","meaning":"Number of bytes in hostInRing (max 8).","confidence":"high"},
    {"addr":"E0A2","scope":"abs","name":"hostInReadIndex","meaning":"Read index of hostInRing.","confidence":"high"},
    {"addr":"E0A3","scope":"abs","name":"hostInWriteIndex","meaning":"Write index of hostInRing.","confidence":"high"},
    {"addr":"E0A4","scope":"abs","name":"frameInFlightFlag","meaning":"Set by the frame layer ($E8A8) when a frame has been handed to the transmitter; cleared together with $E0A6 by runModemStateMachine once the UART transmit queue has drained.","confidence":"medium"},
    {"addr":"E0A5","scope":"abs","name":"txPendingCount","meaning":"Number of bytes queued in or being shifted out of the software UART; incremented by queueByteForTransmit and decremented by the NMI when a character completes.","confidence":"high"},
    {"addr":"E0A6","scope":"abs","name":"packetInFlightCount","meaning":"Set to (packet length + 1) when sendPacketFsm hands a frame over, and cleared when the transmitter drains; while non-zero the packet layer will not start another frame or acknowledge the exchange. Always addressed as $E0A5,X with X = $E0A8 = 1.","confidence":"medium"},
    {"addr":"E0A7","scope":"abs","name":"serviceLock","meaning":"Re-entrancy lock for serviceCommTick: normally 1, decremented to 0 for the duration of the body, so an interrupt that re-enters simply returns.","confidence":"high"},
    {"addr":"E0A8","scope":"abs","name":"packetSlotIndex","meaning":"Constant $01 used as the index into $E0A5,X so that the packet layer works on $E0A6. Deliberately excluded from the block that clearProtocolVars zeroes.","confidence":"high"},
    {"addr":"E414","scope":"abs","name":"uartTxCount","meaning":"Bytes in the raw UART transmit ring (max 22).","confidence":"high"},
    {"addr":"E415","scope":"abs","name":"uartTxReadIndex","meaning":"Read index of the UART transmit ring (wraps 0 -> 21).","confidence":"high"},
    {"addr":"E416","scope":"abs","name":"uartTxWriteIndex","meaning":"Write index of the UART transmit ring.","confidence":"high"},
    {"addr":"E417","scope":"abs","name":"uartTxRing","meaning":"22-byte transmit FIFO drained one character at a time by the NMI transmitter.","confidence":"high"},
    {"addr":"E42D","scope":"abs","name":"uartRxCount","meaning":"Bytes in the raw UART receive ring (max 20).","confidence":"high"},
    {"addr":"E42E","scope":"abs","name":"uartRxReadIndex","meaning":"Read index of the UART receive ring.","confidence":"high"},
    {"addr":"E42F","scope":"abs","name":"uartRxWriteIndex","meaning":"Write index of the UART receive ring, advanced by the NMI receiver.","confidence":"high"},
    {"addr":"E430","scope":"abs","name":"uartRxRing","meaning":"20-byte receive FIFO filled by the NMI receiver.","confidence":"high"},
    {"addr":"E444","scope":"abs","name":"arqTxCount","meaning":"Bytes in the frame-layer transmit ring (max 18).","confidence":"high"},
    {"addr":"E445","scope":"abs","name":"arqTxReadIndex","meaning":"Read index of the frame-layer transmit ring.","confidence":"high"},
    {"addr":"E446","scope":"abs","name":"arqTxWriteIndex","meaning":"Write index of the frame-layer transmit ring.","confidence":"high"},
    {"addr":"E447","scope":"abs","name":"arqTxRing","meaning":"18-byte queue of payload bytes the packet layer has produced and the frame layer at $E8xx has not yet packed into a frame.","confidence":"high"},
    {"addr":"E459","scope":"abs","name":"arqRxCount","meaning":"Bytes in the frame-layer receive ring (max 18); also tested by the frame layer at $EA73.","confidence":"high"},
    {"addr":"E45A","scope":"abs","name":"arqRxReadIndex","meaning":"Read index of the frame-layer receive ring.","confidence":"high"},
    {"addr":"E45B","scope":"abs","name":"arqRxWriteIndex","meaning":"Write index of the frame-layer receive ring.","confidence":"high"},
    {"addr":"E45C","scope":"abs","name":"arqRxRing","meaning":"18-byte queue of validated payload bytes the frame layer has delivered for the packet layer to read.","confidence":"high"},
    {"addr":"E5BB","scope":"abs","name":"txCharActive","meaning":"Non-zero while a character is being shifted out by the NMI transmitter.","confidence":"high"},
    {"addr":"E5BC","scope":"abs","name":"txBitCountReload","meaning":"Number of bit times per character, always 9 (start bit plus 8 data bits; the stop bit is the idle level).","confidence":"high"},
    {"addr":"E5BD","scope":"abs","name":"txBitCounter","meaning":"Bits left in the character being transmitted; going negative asks for the next character.","confidence":"high"},
    {"addr":"E5BE","scope":"abs","name":"txShiftRegister","meaning":"Character being shifted out LSB first onto PA2 (TXD).","confidence":"high"},
    {"addr":"E5BF","scope":"abs","name":"rxBitCounter","meaning":"Receive bit counter: $83 = waiting for the start bit to be validated, then 9 down to 0 for the data bits and the stop bit.","confidence":"high"},
    {"addr":"E5C0","scope":"abs","name":"rxShiftRegister","meaning":"Character being assembled from CIA2_PRB bit 0 (RXD), LSB first.","confidence":"high"},
    {"addr":"EB00","scope":"abs","name":"rxIdleWatchdog","meaning":"Cleared by the NMI whenever a character is received and counted up by the frame layer; reaching $10 forces the connect handshake to restart.","confidence":"medium"},
    {"addr":"EB01","scope":"abs","name":"txDelayCounter","meaning":"Negative countdown loaded from a bit-7-set byte pulled out of the host-output ring; while it is negative the terminal loop sends nothing, which implements the Hayes guard times embedded in the '+++'/ATH0 string.","confidence":"high"},
    {"addr":"EB02","scope":"abs","name":"lastTerminalChar","meaning":"Previous character received in terminal mode; compared with '0' when a CR arrives to recognise a 'CONNECT 1200' result line.","confidence":"medium"},
    {"addr":"EB03","scope":"abs","name":"remoteCodeBuffer","meaning":"Scratch buffer that packets with header $70-$7F are copied into and that a zero-length $7x packet then calls with JSR. Its file content is a single RTS, so the facility is inert unless the peer uses it.","confidence":"low"},
    {"addr":"EBFD","scope":"abs","name":"hangUpStringOffset","meaning":"Page-$EB offset ($D6) of the reversed '+++ / ATH0' hang-up string used by hangUpModem.","confidence":"high"},
    {"addr":"EBFE","scope":"abs","name":"initStringOffsetTable","meaning":"Two page-$EB offsets indexed by isOriginateMode: $E3 = 'ATQ0V1X1A' (answer), $F0 = 'ATQ0V1X1D' (dial/originate).","confidence":"high"},
    {"addr":"FFFA","scope":"abs","name":"nmiVector","meaning":"RAM NMI vector; installCommNmiVector points it at $E685 while the link is up.","confidence":"high"}
  ],
  "dataBlocks": [
    {"addr":"E000","length":24,"type":"addrTable","name":"commJumpTable","description":"Seven JMP entries at $E000/$E003/$E006/$E009/$E00C/$E012/$E015, with a two-byte constant pool at $E00F ($60 = RTS, $E010 = $40 = RTI, the file's default nmiChainVector target) and the isOriginateMode flag at $E011 wedged between them."},
    {"addr":"E01D","length":19,"type":"byteTable","name":"packetVarBlock","description":"Inline variable block zeroed by clearPacketVars: exchangeFlags, rx/tx packet lengths, the two 7-byte packet buffers, hostOutCount and remoteIdentityByte. The disassembler renders the first byte as BRK."},
    {"addr":"E030","length":4,"type":"byteTable","name":"commBuildAndNmiVars","description":"commBuildId ($00 for this modem build), the two-byte nmiChainVector ($E010 in the file, patched to $1298 by the game) and the CIA2 ICR mask shadow ($90 in the file, $92/$83/$7F at run time)."},
    {"addr":"E039","length":15,"type":"byteTable","name":"linkVarBlock","description":"Inline variable block zeroed by clearLinkVars: NMI suspend request/ack, isLinkActive, carrier state and sample, $E03E (unused), modemCommandPhase, connectionPhase, skipModemInitString, uartRestartRequest, carrierDebounceCount, txPaceCounter, carrierPollTimer, lastHotkeyCode and linkErrorCount. Its file content spells 'DTH' at $E045-$E047, which suggests the page was captured from a running machine (this page comes from track 18 sector 7, outside the encrypted area)."},
    {"addr":"E048","length":31,"type":"byteTable","name":"modemConfigTables","description":"Constants that survive every clear: modemOptionFlags $CC, carrierOverrideFlags, the toggle/port masks $40/$24/$26/$22, the CIA2_PRB table, the CIA2_SDR pattern table, baudIndex, the live bit-period triple, the two-entry baudParameterTable and the eight-entry linkStatusBorderTable at $E05F-$E066."},
    {"addr":"E07D","length":44,"type":"byteTable","name":"protocolVarBlock","description":"Inline variable block zeroed by clearProtocolVars ($E07D-$E0A7) plus the constant packetSlotIndex at $E0A8: the completed-exchange counter, the rx/tx packet state machines, the 8-byte host-input ring and the 16-byte host-output ring with their counts and indices, the in-flight counters and the service lock."},
    {"addr":"E3B5","length":1,"type":"unknown","name":"padByteE3B5","description":"Stray $10 between returnNoKey and the indirect link I/O jumps; not referenced."},
    {"addr":"E3BF","length":12,"type":"addrTable","name":"linkIoVectorTable","description":"Three pairs of alternative targets for the indirect jumps at $E3B6/$E3B9/$E3BC: send = $E51B (raw UART) or $E4B7 (framed), receive = $E4D2 (raw UART) or $E46E (framed), error = $E408 (ignore) or $E403 (count). selectFramedLinkVectors picks the second of each pair."},
    {"addr":"E414","length":90,"type":"byteTable","name":"ringBufferBlock","description":"Inline variable block zeroed by clearUartVars ($E414-$E46D): uartTxCount/indices + 22-byte ring, uartRxCount/indices + 20-byte ring, arqTxCount/indices + 18-byte ring and arqRxCount/indices + 18-byte ring. The last two bytes of the ARQ receive ring ($E46C/$E46D) sit immediately before the popArqRxRing code."},
    {"addr":"E5BB","length":6,"type":"byteTable","name":"uartBitStateBlock","description":"Inline variable block zeroed by clearUartState: txCharActive, txBitCountReload, txBitCounter, txShiftRegister, rxBitCounter and rxShiftRegister - the entire state of the software UART."}
  ],
  "misclassified": [
    {"addr":"E01D","length":1,"actual":"data","evidence":"Rendered as BRK, but it is the first byte of the 19-byte block that clearInlineVarBlock zeroes; sub_E067 pops the return address so control never reaches $E01D. The same applies to the BRK bytes shown at $E039, $E07D, $E414, $E5BB (and $EAB7 outside this chunk)."},
    {"addr":"E403","length":12,"actual":"code","evidence":"$E403: EA EE 47 E0 60 = NOP / INC $E047 / RTS, the framed link-error hook; $E408: 08 48 EA 68 28 60 = PHP/PHA/NOP/PLA/PLP/RTS, the raw no-op hook, plus a stray RTS at $E40E. Both are the targets named in the vector table at $E3C7/$E3C9."},
    {"addr":"E46E","length":24,"actual":"code","evidence":"$E46E: AD 59 E4 / F0 5D / AE 5A E4 / BD 5C E4 / CA / 10 02 / A2 11 / 8E 5A E4 / CE 59 E4 / 18 / 60 - the pop routine for the ARQ receive ring, byte-for-byte the same shape as popArqTxRing at $E486 but on $E45C/$E459/$E45A. It is the vector-table target $E3C5 that selectFramedLinkVectors installs into receiveLinkByte."},
    {"addr":"E4B7","length":25,"actual":"code","evidence":"$E4B7: AE 44 E4 / E0 12 / B0 11 / AE 46 E4 / 9D 47 E4 / CA / 10 02 / A2 11 / 8E 46 E4 / EE 44 E4 / 60 - the push routine for the ARQ transmit ring, the mirror of pushArqRxRing at $E49E. It is the vector-table target $E3C1 that selectFramedLinkVectors installs into sendLinkByte."},
    {"addr":"E685","length":132,"actual":"code","evidence":"Correctly disassembled but left unlabelled: $E685 is the CIA2 NMI handler, reached only through the vector installCommNmiVector writes to $FFFA/$FFFB, so no JSR/JMP points at it."}
  ],
  "insights": [
    "The T34 $E000 module is a complete three-layer software modem stack. Layer 1 is a bit-banged RS-232 UART on the C64 user port driven entirely from the CIA2 NMI at $E685: CIA2 FLAG (user-port pin B = RXD edge) detects the start bit, timer A samples the data bits mid-bit, timer B clocks the transmitter, PA2 = TXD, PB0 = RXD, PB1 = RTS, PB2 = DTR, PB4 = DCD, and CIA2_DDRB = $26. Layer 2 (outside this chunk, $E8xx-$EAxx) builds checksummed, sequence-numbered frames with retransmission. Layer 3 ($E14D/$E1DD, in this chunk) carries the game's 7-byte command packets and chat characters.",
    "The layers are wired together by four ring buffers plus a three-entry indirect vector table at $E3BF. The vectors ($E3B6 send, $E3B9 receive, $E3BC error) can point either at the raw UART rings ($E51B/$E4D2/$E408) or at the frame-layer rings ($E4B7/$E46E/$E403); initCommModule always selects the framed set, so the raw set is effectively dead code. Ring inventory: $E417 (22, UART tx), $E430 (20, UART rx), $E447 (18, frame tx), $E45C (18, frame rx), $E086 (8, host in, read by $E006) and $E091 (16, host out, written by $E009). Every ring uses a decrementing index and a separate count byte.",
    "Baud rates are exact CIA timer latches: $0D50 = 3408 cycles = 300 baud and $0353 = 851 cycles = 1200 baud, held in $E056/$E057. Both the transmitter and the receiver start their timer with a deliberately short first interval (transmit $0150, receive half a bit time) and then fall through into setBitPeriod to reload the latch with the full bit period, so the first edge is quick and every following one is a whole bit apart.",
    "Hayes modem control is real. $EBD6-$EBFC holds three AT strings stored BACKWARDS (the host-output ring pops with a decrementing index, so queueModemCommandString stores them forwards and they come out reversed): 'ATQ0V1X1A' (answer, offset $E3), 'ATQ0V1X1D' (originate/dial, offset $F0) and the '+++'/'ATH0' escape+hangup pair (offset $D6). Bytes with bit 7 set inside these strings are not transmitted - runModemStateMachine loads them into $EB01 as a delay countdown, which is exactly the guard time the '+++' escape sequence needs. Hang-up also drops DTR by writing $22 to CIA2_PRB.",
    "Because the init strings use V1 (verbose) and X1 (extended result codes), the terminal loop can auto-detect the line speed: when a CR arrives and the previous character was '0' it assumes the response line was 'CONNECT 1200' and switches $E055 to the 1200-baud parameters ($E7A7-$E7B4). A plain 'CONNECT' (300 baud) ends in 'T' and is ignored.",
    "Modem hot keys are Commodore-key combinations intercepted inside $E015 before the game ever sees the key (the key is swallowed by returning $FF): C=+A answer mode, C=+O originate/dial, C=+3 300 baud, C=+1 1200 baud, C=+H toggle DTR, C=+C toggle the carrier override, C=+P hang up, C=+RETURN re-open the link without re-sending the init string. This is also where the border colour link indicator is painted: in terminal mode the border is light blue with no carrier and blue with carrier; while C= is held the other phases show black/yellow (down), light red/red (handshaking) and light green/green (connected).",
    "$E000 X=0 is called from the raster IRQ every frame and is protected by a DEC/INC re-entrancy lock on $E0A7 rather than by SEI. It runs the modem/connection state machine ($E756) unconditionally and the packet send/receive state machines only once connectionPhase ($E040) has reached 3.",
    "Packet framing on the wire: one header byte whose low three bits are the payload length. Bit 7 set means the payload is chat/text bytes that go straight into the host-input ring for $E006; bit 7 clear means a game command packet that lands in $E020 with the length in $E01E. Header values $70-$7F are a third case - the payload is accumulated in the scratch buffer at $EB03 and a zero-length $7x packet executes it with JSR $EB03. $EB03 contains only an RTS in the shipped image, so nothing uses this remote-code path unless the peer sends one.",
    "The $E000 modules of tracks 34 and 35 are NOT two builds of the same communications code. T35 contains no CIA references at all (grep for CIA2_ in ovl_E000_T35.s returns zero hits) and instead touches nextGameRandom, the map at $F6xx and work RAM at $89xx, while several of its jump-table entries point at bare SEC/CLC/RTS stubs. T34 is the real modem module; T35 looks like the solo-trainer / computer-opponent module that synthesises the opponent's packets locally behind the same seven-entry interface. That fits main's behaviour (build 1 skips all modem prompts, makes PRACTICE the default menu item and is selected by $0BA5/$E030 bit 7) and corrects the earlier guess that build 1 is a null-modem variant.",
    "The five 'BRK' instructions the earlier survey noticed in this module ($E01D, $E039, $E07D, $E414, $E5BB, plus $EAB7) are not BRKs at all. Each is the first byte of an inline variable block that follows a 'LDX #n / JSR $E067' pair; clearInlineVarBlock pulls the return address off the stack, uses it as the base of a self-modified STA abs,X to zero the n bytes after the JSR, and its RTS therefore returns to the caller's caller. Nothing ever executes those bytes.",
    "$E039/$E03A are a two-way handshake, not a single flag: the game writes $C0 to $E039 and spins until $E03A goes negative before any disk access (suspendCommModule $0F5F), and writes 0 and spins until $E03A goes positive afterwards. The comm module only acts on the request inside its own frame service, so the NMI is never torn down mid-character.",
    "$E07D/$E07E is a 16-bit count of completed packet exchanges maintained at $E283 that no code in the entire image ever reads - almost certainly a leftover debug statistic."
  ]
}
