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.