modemwars/swiftlink/DESIGN_NOTES.md
2026-08-23 02:09:40 -05:00

3.2 KiB

Design notes found by inspection - these MUST be honoured by the SwiftLink driver

1. The NMI handler has to survive the I/O area being banked out

The game keeps its "game film" / saved-game buffer in the RAM underneath the I/O area, at $D000-$DFF0. readFilmByte ($5866) and writeFilmByte ($5893) in disassembly/game/mainProgram0800.s therefore do this around every single byte:

sei                     ; NMI is NOT blocked by this - see below
stx CIA2_ICR            ; disable every CIA2 interrupt source (written twice as a guard)
stx CIA2_ICR
ldx #$34                ; bank RAM in over the whole $D000-$DFFF I/O area
stx CPU_PORT
lda (filmPtr),y         ; touch the film buffer
inc CPU_PORT            ; back to $35, I/O visible again
ldx ciaIcrShadow        ; $E033 - the module's copy of the CIA2 mask it wants
stx CIA2_ICR            ; re-enable the modem's NMI sources
cli

For the stock driver this is airtight: its UART interrupt is a CIA2 interrupt, so disabling CIA2 sources really does stop it firing while the registers are hidden.

A SwiftLink interrupt is not a CIA2 interrupt. Nothing in that sequence stops the ACIA asserting NMI, and sei does not mask NMI. So the handler can and will fire while $01 = $34, at which point $DE00-$DE03 are ordinary RAM and the handler would read garbage, lose the received byte and possibly wedge the receiver.

Requirement: the SwiftLink NMI handler must be bank-safe. On entry save $01, force it to $35 so the ACIA is visible, do the work, then restore the saved value before RTI. Do this before touching any ACIA register. Remember $01 is also the 6510 port used for the tape motor and character-ROM banking, so save and restore the whole byte, do not just write $35 and $34 back.

Also: $E033 (ciaIcrShadow) is still written into CIA2_ICR by those two routines, so it must hold a value that is correct for this driver. This driver wants no CIA2 interrupts at all, so it should hold $7F (clear every CIA2 source). Set it at run time - its stored byte in the module image is inside the frozen region and must not change.

2. Prefer base $DE00, and never index into the ACIA page by accident

loadBaudParameters ($E353) in the stock module writes its three bytes with sta $DF59,y where Y is $FD/$FE/$FF, so the effective addresses are $E056-$E058. That is fine on the stock hardware, but an indexed store on a 6502 performs a dummy READ from the un-carried address first - here $DF56, $DF57 and $DF58. With a SwiftLink strapped to $DF00 those land on the ACIA's mirrored registers, and $DF58 mirrors the data register, whose read consumes a received byte and clears RDRF.

The replacement for loadBaudParameters must not use that trick. Address $E056-$E058 directly. More generally: default the driver to $DE00, and document that $DF00 is second choice.

3. The game also copies pages across $D000-$DF00

mapGenerator6F00.s walks a source and a destination page over $D000..$DF00 ($7F66, $816E) for the film snapshot. Those go through the same bank-out dance. Nothing extra to do, but it is another reason the handler must be bank-safe rather than relying on interrupts being masked.