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

54 lines
3.2 KiB
Markdown

# 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.