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

31 KiB

The probe hardening and the 38400 retirement, verified

Superseded in part. This report records the build that retired 38400 by blanking its hot-key code, and quotes that build's addresses and its "461 of 4096, in 32 region(s)" figure. The retirement was later reversed: wedgeDiagnosis.md found the real cause (NMI re-entrancy exhausting the stack) and portCompatReport.md measured 38400 as healthy once it was fixed, so the hot key is back. The probe hardening described below is still current. See ADDRESS_CHANGES.md for the addresses that have moved since.

Note: the 38400 retirement has been reversed. Blanking that entry's hot-key code hid a defect rather than fixing one. wedgeDiagnosis.md established the defect - unbounded re-entrancy in commNmiHandler, not the rate - and with the re-entrancy guard in place C= + 8 selects 38400 again (portCompatReport.md sections 3 and 4, testReport.md section 11; testAdopt.py measured the key putting baudIndex = 18 and the control register = $1F). The page-latch half of this report stands, and section 5's "C= + 8 does nothing" is the before for that later measurement. Nothing below has been rewritten: it is the record of the build of the day.

Two changes went into the driver since highRateReport.md was written. The first had been looked at once - a re-run of testStrap.py on the $DF00 strap, covering two link opens and no hot keys at all, quoted in README.md section 5 - and the second had not been checked at all:

  • the page latch. aciaDetect $E644 used to probe $DE00 on every call, and loadBaudParameters calls it on every link open and every baud hot key - so the old build wrote its probe pattern into $DE03 once per open and once per rate change, on a live link, on a page that is very often some other cartridge's registers. aciaPageLatch $E5BF now records the answer, aciaDetect returns in five bytes while it is non-zero, and only selectFramedLinkVectors $E3F3 - which initCommModule calls once, right after the overlay loads - puts it back to zero. The probe also became a two-pattern probe, because an empty $DE00 reads back open bus.
  • the 38400 retirement. baudEntryTable's last entry keeps its control byte $1F, but its hot-key code is now $00, which the search at $E344 can never match because that search only runs on key codes with bit 7 set. 38400 wedged four boots out of four (highRateReport.md sections 5 and 6).

Everything below is VICE 3.7.1 (x64sc), NTSC, -acia1mode 1 -acia1irq 1, base $DE00 or $DF00, on 2026-08-22, against ../Modem_Wars_SwiftLink.d64 built from the current source with ./build.sh. Every emulator pair runs at true C64 speed: warp is used for the loader only and switched off through the monitor's own warp command before the options menu. As in every earlier session each run picked its own X display, its own TCP ports and its own private VICE config file, and only processes these scripts started were killed.

Short answer:

  • Cold detect still works on both straps. On $DE00 the probe writes $1E, reads $1E back, writes $15, reads $15 back, and latches $DE. On $DF00 it writes $1E to $DE03, reads back $F6 - open bus - never writes the second pattern, and aims everything at $DF00. All six accessors at $E551-$E56E and aciaPageLatch $E5BF agreed on the right page on all four machines, immediately after the cold detect and again at the end of each run.
  • The probe does not run again. Across the two runs the link was opened twice per machine and six speed hot keys landed per machine - eight calls to loadBaudParameters, and so eight calls to aciaDetect - and the probe ran once. During the whole hot-key sweep the control register received exactly six writes, $17 $18 $1A $1C $1E $15: one per landed key, in the order the keys were pressed, with no probe pattern in front of any of them.
  • Nothing goes to the other page. On the $DF00 strap each machine touched $DE00-$DE03 exactly twice in the whole session - the single probe write and its single failed read-back, both at cold start - and zero times during the hot-key sweep or the 20-second census that followed. On the $DE00 strap, $DF00-$DF03 was touched zero times, ever.
  • 38400 is unreachable from the keyboard. C= + 8 on a live phase-3 link, on both straps, changed neither the control register nor baudIndex, and the screen was pixel-identical before and after. A breakpoint on the swallow at $E38A caught the keystroke with A = $B8 and lastHotkeyCode $E046 = $B8, so the key does reach the hot-key layer and is answered with $FF = "no key". The other six rates all selected first press: $15, $17, $18, $1A, $1C, $1E.
  • No regression at 1200 baud. Two boots, four 75-second windows, 303.0 seconds of measured link time at true C64 speed; both machines reached connection phase 3 on both boots, none of 592 one-second sampling intervals carried zero bytes, linkErrorCount $E047 read $00 at all 24 window edges, and the transmitter-deadlock shape did not occur once.
  • Two faults were found in the test rig, not in the driver, and both are fixed. One of them was hiding every receive error: testRealtime.py's tracepoint was still on countLinkError's old address. Section 7.

1. The build under test

$ ./build.sh
wrote ../Modem_Wars_SwiftLink.d64: module installed in track 18 sector 7 and track 34 sectors 1-15
$ python3 checkAbi.py build/swiftlinkDriverE000.bin ../disassembly/build/game_modemDriverE000.orig.bin
bytes changed: 461 of 4096, in 32 region(s)
...
ABI CHECK PASSED: every address the rest of the game depends on is unchanged

The bytes that carry the two changes are in the image:

Address Bytes What it is
$E644 AD BF E5 D0 0B A9 DE 20 F2 E6 F0 04 A9 DF D0 0F aciaDetect: LDA aciaPageLatch / BNE done first, then the probe
$E6F2 20 63 E6 A9 1E 20 67 E5 49 1E D0 07 A9 15 20 67 E5 49 15 60 aciaProbe: aim, write $1E, read back, write $15, read back
$E402 A9 00 8D BF E5 60 the cold-start latch clear inside selectFramedLinkVectors
$E718 00 1F 00 baudEntryTable index 18: control $1F, hot-key code $00

scanIoAccess.py re-run against this image still finds exactly the six accessors and nothing else naming an address in $DE00-$DFFF, in both its alignment-aware and its superset pass, and still finds the only pass-1 hits elsewhere to be the three instructions in the stock driver this build replaces.

Three static facts about reachability, taken by scanning the whole 4096-byte image for JSR/JMP to each address:

  • $E3F3 (selectFramedLinkVectors, the only thing that clears the latch) is reached from one instruction: JSR $E3F3 at $E2BA, inside initCommModule - the $E003 X=1 case, which the game calls once, from $0F46, immediately after the overlay is loaded.
  • $E644 (aciaDetect) is reached from one instruction: JSR $E644 at $E353, the first thing loadBaudParameters does.
  • $E6F2 (aciaProbe) is reached from one instruction: JSR $E6F2 at $E64B, inside aciaDetect, on the far side of the latch test.

So the only way to probe the hardware at all is through the latch test, and the only way to clear the latch is a cold start. The rest of this report is that argument measured rather than read.

How it was measured

testHarden.py (new) boots two machines cross-connected through the socket null modem, so every measurement below is taken on a link that reached connection phase 3 - the game's own packet protocol

  • rather than on a machine talking to itself. It runs in four phases: the cold detect with both cartridge pages watched; the seven speed hot keys with the other page and the control register watched; a 20-second census of every register access; and the C= + 8 breakpoint.

Unlike testStrap.py, which used watchpoints and stopped the emulator 800 times, this uses VICE tracepoints, which print the accessing instruction and its A register and let the machine run on. That matters: a watchpoint stopping the emulator on every ACIA access makes it impossible to press a hot key at true C64 speed, and it is also what let the census run for 20 seconds of live link without disturbing anything.

python3 testHarden.py ../Modem_Wars_SwiftLink.d64 de00
SWIFTLINK_ACIA_BASE=0xDF00 python3 testHarden.py ../Modem_Wars_SwiftLink.d64 df00
python3 testRealtime.py ../Modem_Wars_SwiftLink.d64 75 1200 hard1200  2 0
python3 testRealtime.py ../Modem_Wars_SwiftLink.d64 75 1200 hard1200b 2 0

2. Cold detect, on both straps

Both machines of both pairs reached connectionPhase 3. These are the first accesses each machine made to either cartridge page, in order, decoded from the raw tracepoint logs.

$DE00 strap (testLogs/harden.de00.A.cold.txt; machine B is identical)

store $DE03  STA $DE03 at $E567  A=$1E     aciaProbe pattern 1
load  $DE03  LDA $DE03 at $E56B  A=$1E     read back through the fall-through - it matches
store $DE03  STA $DE03 at $E567  A=$15     aciaProbe pattern 2
load  $DE03  LDA $DE03 at $E56B  A=$15     it matches too: a 6551 really is here, latch = $DE
store $DE03  STA $DE03 at $E567  A=$15     loadBaudParameters: $10 OR 300-baud bits
load  $DE03  LDA $DE03 at $E56B  A=$15     the read-back aciaSetControl falls into
load  $DE00  LDA $DE00 at $E55F  A=$00     drain the receiver
load  $DE01  LDA $DE01 at $E558  A=$50     status
store $DE02  STA $DE02 at $E554  A=$09     command: DTR on, RTS low, receiver IRQ on, transmit IRQ off

That is exactly the sequence testReport.md section 3 recorded, with the second probe pattern now in it. Both probe patterns were needed and both came back, which is the first time the second pattern has been seen to run at all.

$DF00 strap (testLogs/harden.df00.A.cold.txt; machine B is identical)

store $DE03  STA $DE03 at $E567  A=$1E     aciaProbe pattern 1, on the default page
load  $DE03  LDA $DE03 at $E56B  A=$F6     open bus - not $1E, so this page is not a 6551
store $DF03  STA $DF03 at $E567  A=$15     the fallback is taken: loadBaudParameters at $DF00
load  $DF03  LDA $DF03 at $E56B  A=$15
load  $DF00  LDA $DF00 at $E55F  A=$00     drain the receiver
load  $DF01  LDA $DF01 at $E558  A=$50     status
store $DF02  STA $DF02 at $E554  A=$09     command

The second pattern is never written on this strap, because the first one already decided the question - which is the whole point of writing the cheap one first. The $F6 the read returned is the value probeOpenBus.py saw in 51.7% of its 120 samples of an empty $DE03; nothing about it is $1E, and the probe fell through correctly.

The six accessors and the latch

Read back through the monitor after the cold detect, and again at the end of each run - all four machines, all eight readings:

Accessor Instruction Operand high byte at $DE00 run $DF00 run
aciaCmdWrite $E554 sta $xx02 $E556 $DE $DF
aciaStatusRead $E558 lda $xx01 $E55A $DE $DF
aciaDataRead $E55F lda $xx00 $E561 $DE $DF
aciaDataWrite $E563 sta $xx00 $E565 $DE $DF
aciaCtrlWrite $E567 sta $xx03 $E569 $DE $DF
aciaCtrlRead $E56B lda $xx03 $E56D $DE $DF
aciaPageLatch - $E5BF $DE $DF

The latch and the six accessors were in step in every reading, which is what makes the latch an honest stand-in for them.


3. The probe does not run again

Two independent measurements say so, and neither depends on the other.

testHarden.openLink offers A/O and space until an ACIA register is touched, and on both runs it took two rounds, so openCommLink - and therefore loadBaudParameters, and therefore aciaDetect - ran twice on every machine before the hot keys were even reached. What the control register received in that phase:

Run Machine Control-register writes, in order Probe writes Link opens
$DE00 A $1E $15 then $15 $15 2 (one probe) 2
$DE00 B $1E $15 then $15 $15 2 (one probe) 2
$DF00 A $1E to $DE03, then $15 $15 to $DF03 1 (one probe) 2
$DF00 B $1E to $DE03, then $15 $15 to $DF03 1 (one probe) 2

The old build would have written the probe pattern once per open. This one wrote it once per cold start.

For this phase the tracepoints were narrowed to the two things the question turns on: all four registers of the other page, and the control register of the strapped page. (Data and command register traffic on the strapped page is the ordinary business of a running link - the census in section 4 counts it - and it would have buried the six accesses that matter.)

The whole hot-key phase - 182 seconds of live phase-3 link between the end of the cold-detect log and the end of the sweep, seven keys pressed on each machine, C= + 1, C= + 2, C= + 4, C= + 8, C= + 9, C= + 0, C= + 3 - produced twelve accesses to that traced set on every one of the four machines, and they are the same twelve everywhere:

store $xx03  A=$17      load $xx03  A=$17       C= + 1   1200
store $xx03  A=$18      load $xx03  A=$18       C= + 2   2400
store $xx03  A=$1A      load $xx03  A=$1A       C= + 4   4800
store $xx03  A=$1C      load $xx03  A=$1C       C= + 9   9600
store $xx03  A=$1E      load $xx03  A=$1E       C= + 0  19200
store $xx03  A=$15      load $xx03  A=$15       C= + 3    300

Six writes for six landed keys, each one the rate that was asked for, in the order it was asked for, and nothing else at all - no probe pattern in front of any of them, not one access to the other page, and nothing whatever attributable to C= + 8. The matching load after each store is not a second access to look at: aciaSetControl falls through a NOP into aciaGetControl by design, so every control write reads itself back, probe or not.

Had the probe re-run on every hot key, as it did before the latch, this sweep would have looked quite different. On the $DE00 strap each key would have written $1E, $15 and then the rate: eighteen control-register writes instead of six. On the $DF00 strap each key would have written $1E into $DE03, failed to read it back, and then written the rate into $DF03: six writes to a page this driver has no business touching, instead of none.

One thing in the script's own output should be ignored. testHarden.py counts back-to-back $1E,$15 pairs as "the probe's signature" and reported 1 on the $DF00 run. That is a false positive and the heuristic is worthless: $1E is 19200's control byte and $15 is 300's, and the last two keys in the sweep are C= + 0 followed by C= + 3. The pair it found is those two hot keys. The measurement that carries the claim is the one above - six writes, matching six keys - not that counter.


4. Nothing is written to the other page

Counted the way strapReport.md counted it, per page, but over three separate phases of the same session rather than one 800-stop log. A tracepoint does not stop the emulator, so these are complete counts of a running machine rather than a sampled subset.

$DF00 strap - the case that matters

Phase Machine $DE00-$DE03 $DF00-$DF03
cold detect + two link opens + the Hayes string + byte sync A 2 530
B 2 433
the 182-second hot-key sweep, seven keys A 0 12
B 0 12
a 20-second census of a live phase-3 link A 0 2739
B 0 2444
whole session A 2 3281
B 2 2889

The two accesses are the single probe write of $1E to $DE03 and the single read that returned $F6. There is no third. The census breaks down as $DF00 591 loads / 662 stores (received and transmitted characters), $DF01 1246 loads (status reads, which are also the NMI acknowledge) and $DF02 240 stores (command $09/$05 as the transmitter is armed and disarmed) on machine A - the ordinary traffic of a link carrying ARQ frames, and none of it on the wrong page.

$DE00 strap - the mirror-image check

Phase Machine $DE00-$DE03 $DF00-$DF03
cold detect + two link opens A 553 0
B 451 0
hot-key sweep A 12 0
B 12 0
20-second census A 2721 0
B 2418 0

Nothing on this build ever named $DF00 when the cartridge answered at $DE00, which is what the six self-modified accessors being the module's only knowledge of the hardware predicts.


5. 38400 is unreachable, and the other six rates are not

Pressed once on each machine, Commodore key (Tab) and 8 held down together for 600 ms - about 36 of the game's once-a-frame keyboard scans - with both machines in phase 3. State read immediately before and immediately after:

Run Machine before after
$DE00 A baudIndex 9, $DE03 = $1A, phase 3 baudIndex 9, $DE03 = $1A, phase 3
$DE00 B baudIndex 9, $DE03 = $1A, phase 3 baudIndex 9, $DE03 = $1A, phase 3
$DF00 A baudIndex 9, $DF03 = $1A, phase 3 baudIndex 9, $DF03 = $1A, phase 3
$DF00 B baudIndex 9, $DF03 = $1A, phase 3 baudIndex 9, $DF03 = $1A, phase 3

$1F was never written to any control register in any phase of either run, and the tracepoint on the control register recorded no access at all between the C= + 4 pair and the C= + 9 pair. The link survived it: the next hot key, C= + 9, landed normally on both machines in both runs.

The keystroke reaches the driver and is swallowed

"Nothing changed" on its own would also be what a key that never arrived looks like, so the swallow itself was caught. $E38A is the LDA #$FF that answers the game "no key" for any Commodore-key combination the hot-key layer does not recognise, and it is the only way out of that layer for C= + 8 now that the baud walk cannot match it. With a breakpoint armed there before the key went down (entering the monitor clears VICE's emulated key matrix, so the order matters):

#1 (Stop on  exec e38a)   34/$022,  61/$3d
.C:e38a  A9 FF       LDA #$FF       - A:B8 X:00 Y:00 SP:e9 N.-.....  374331486

lastHotkeyCode $E046 = $B8, baudIndex $E055 = 0, bitPeriodLo $E056 = $15, $DE03 = $15

A = $B8 is the C= + 8 key code itself, arriving at the swallow; $E046 = $B8 says the hot-key layer logged it as a fresh Commodore-key code; and the rate bytes are untouched. The same breakpoint fired the same way on the $DF00 run. So the key is seen, walked past every hot key in the chain including the baud table, and answered with $FF - which is what stops the game seeing it.

Corroboration from the screen: shots/hdde0003aBeforeC8.png against shots/hdde0004aAfterC8.png, and shots/hddf0003aBeforeC8.png against shots/hddf0004aAfterC8.png, are pixel-identical (0 of 384x247 pixels differ in either pair). The game was on "OPPONENT PICKING GAME/MAP." and did not act on an 8.

The other six rates

Every one landed on both machines, on both straps, at the first press:

Hot key Rate baudIndex $E055 Control register bitPeriodLo $E056 Landed
C= + 3 300 0 $15 $15 yes
C= + 1 1200 3 $17 $17 yes
C= + 2 2400 6 $18 $18 yes
C= + 4 4800 9 $1A $1A yes
C= + 9 9600 12 $1C $1C yes
C= + 0 19200 15 $1E $1E yes
C= + 8 38400 unchanged unchanged unchanged no

$15, $17, $18, $1A, $1C and $1E were each seen in the register with the matching baudIndex four times over - two machines, two straps.

A note on what happened around the sweep, recorded because it is the honest picture: driving a pair up to 19200 and back down inside three minutes is not a healthy way to run a link, and it showed. On the $DF00 run, one reading at 19200 caught machine A in phase 2 with linkErrorCount = 13 and uartPendingCount = $C9; by the next reading, before the following hot key, it was back to linkErrorCount = 0, uartPendingCount = $00, txCharActive = $00 and a self-consistent command shadow, and the run finished with both machines linked at 300 baud and a 20-second census recording 2739 and 2444 register accesses. That is the "degrades and recovers" behaviour highRateReport.md section 3 measured at 9600 and 19200, not something new. Over all 56 state readings of the two runs, linkErrorCount was $00 in 55 of them and the transmitter-deadlock shape

  • txCharActive set while command bit 2 is clear - occurred zero times.

6. No regression: 1200 baud, two boots, four windows

testRealtime.py, 1200 baud selected with C= + 1 at the "PRESS A OR O" prompt on both machines before the link was opened, two consecutive 75-second windows per boot, nothing sent to either monitor inside a window.

Boot Window Wall A speed B speed A -> B B -> A $55 lead bytes
1 1 75.99 s 100.9% 100.5% 3793 B = 49.91 B/s 3151 B = 41.46 B/s 697 / 714
1 2 75.69 s 101.3% 100.9% 3133 B = 41.39 B/s 3545 B = 46.84 B/s 778 / 727
2 1 75.72 s 101.2% 100.8% 2193 B = 28.96 B/s 2032 B = 26.84 B/s 631 / 705
2 2 75.62 s 101.2% 100.8% 3034 B = 40.12 B/s 2574 B = 34.04 B/s 530 / 494

303.0 seconds of measured link time, 12153 bytes one way and 11302 the other. Every machine in every window ran at 100.5% of real time or better against its own free-running cycle counter, so this is true C64 speed and not warp.

The five things this run had to answer, one at a time:

  • Both machines reached phase 3, on both boots. waitForPhase returned {'A': 3, 'B': 3} before the first window of each boot, and the $55 counts - 494 to 778 ARQ frame lead bytes per window - say frames kept crossing for the whole of every window, which only happens in the packet phase. At the individual window edges connectionPhase was found at 2 as well as 3, which is the ordinary 1200-baud picture: a receive-ring overflow forces a byte-sync round, the round completes, the packet layer carries on.
  • Traffic flowed both ways every second. The relay's byte counts are sampled once a second inside each window: 592 one-second sampling intervals over the two boots - 148 per window, 74 in each direction - and not one of them carried zero bytes. Per-interval rates ran from 11.9 B/s to 109.3 B/s, and 109.3 is 91% of the 120 B/s an 8N1 line carries at 1200 baud, so the line does still get saturated in bursts.
  • linkErrorCount $E047 stayed at $00. All 24 state readings taken with the link up read $00. (The two readings taken before the link opened read 72 = $48, which is the leftover ASCII H the module ships with at $E047; clearLinkVars zeroes it when the link opens.)
  • The transmitter never parked. In all 24 live readings txCharActive $E5BB and aciaCommandShadow $E5BE were only ever the two self-consistent pairs $00/$09 (idle) and $01/$05 (a byte of ours in the chip). The fault shape - $E5BB set while $E5BE bit 2 is clear - occurred zero times, and probeTxDeadlock never fired, so nothing in these numbers is the test repairing the driver. uartPendingCount $E0A5 read $00, $01 or $03 at every edge and was never once out of range.
  • The errors that did occur were the documented ones. The countLinkError tracepoint counted 22 rejected characters across the four windows (4, 4, 7, 7), and the conditional tracepoint on the CPX #$14 inside pushUartRxRing returned identical counts (4, 4, 7, 7). Every single one was therefore a receive-ring overflow, not a bad character - the same fingerprint rate1200Report.md found for all 32 of its overflows. That the byte-sync rounds they caused completed is an inference here rather than a measurement: the $E805/$E855 sync tracepoints were not armed in these windows, so what says the link recovered is that frames kept crossing, that no sampling interval carried nothing, and that connectionPhase was still being found at 3 at the window edges. Neither emulator logged a CPU JAM.

That is the same overflow rate rate1200Report.md measured on the pre-hardening build, to within the noise: 32 in 452 s there, 0.071 a second, against 22 in 303 s here, 0.073 a second. Two boots is not enough to call a 3% difference anything at all; what the two runs agree on is the shape - every error a ring overflow, every overflow followed by a recovery, and the link never stopping.


7. Two faults in the test rig, found and fixed

Both are in the rig, not the driver, and both would have made this report wrong.

  • testRealtime.py was tracing an address that no longer exists. countLinkError moved from $E403 to $E73F when selectFramedLinkVectors had to grow the five bytes that clear the page latch - that move is part of the hardening being verified here. testRealtime.py and testHighRates.py still said trace exec $e403, and $E403 is now the operand byte of an LDA #$00: the program counter never equals it, so the tracepoint can never fire. Left alone, this report would have claimed zero receive errors at 1200 baud with a straight face. Both scripts now trace $e73f, which is where the 22 overflows above came from.
  • The tracepoint parsers dropped every access whose value contained a hex letter. VICE prints the program counter in lowercase (.C:e567) and the register values in uppercase (- A:1E), and the DISASM pattern in testStrap.py - which testHarden.py inherited - matched A: only against [0-9a-f]. So A:1E did not match, the header was paired with a later line, and 58 of 553 accesses in the first log were mis-parsed, including the probe's own first write. The pattern now accepts both cases in testStrap.py and testHarden.py, and parseTrace.py (new) re-parses a saved log offline, which is where every number in sections 2 to 4 comes from. The raw logs were never affected - only the parsing of them.

8. What this does not say

  • No real hardware, and no C64 Ultimate. Everything here is VICE 3.7.1. Nothing has been run on a physical SwiftLink, a real cable, a real modem, or the device this exercise is aimed at.
  • VICE's open bus is not the C64's. The $DF00 strap's probe read returned $F6 because that is what VICE puts on the bus for an unmapped I/O read at that moment. The case the second pattern exists for - a page that answers the first pattern and not the second - has still never been produced, on this rig or any other, and cannot be: on this rig the first pattern decides every case. The two-pattern probe is therefore installed and exercised on the path where both patterns match, and unexercised on the path it was written for.
  • VICE does not emulate the register mirrors, so "nothing touched the other page" is a statement about the four bytes VICE maps. testHarden.py did not arm the mirror tripwire that testStrap.py uses on $DE04-$DEFF / $DF04-$DFFF; that tripwire recorded 0 stops in every run of strapReport.md section 7.3, and the static scan in section 1 above still finds no instruction in this build that could reach it.
  • The C= + 8 evidence is in two halves and each is partial. During the rate sweep the proof is negative - nothing changed - because lastHotkeyCode is overwritten with $FF by the next keyboard scan, so the snapshot cannot show that the key arrived. The breakpoint run proves the key arrives and is swallowed, but it stops the emulator to do it. Neither run watched what the game does with a plain 8 in a context where 8 means something, because the game was on "OPPONENT PICKING GAME/MAP." both times.
  • aciaDetect still does not verify $DF00. When $DE00 does not answer, the accessors are aimed at $DF00 on trust and the latch is set - so a machine with no cartridge on either page latches $DF and never probes again until the module is reloaded. That is deliberate (the source says so), it is unchanged by this work, and it was not tested.
  • The 20-second census is 20 seconds. It says nothing about a machine that has been linked for an hour, and the whole live-link portion of each testHarden.py run is about four minutes.
  • Four of the twelve tracepoint logs end with one header whose disassembly line was cut off by the end of the capture window; parseTrace.py warns about it. They are the $DE00 run's two cold logs and its machine-A census, and the $DF00 run's machine-B cold log - one access out of 553, 451, 2721 and 435 respectively. Each is counted in the per-address totals, because the header carries the address and the direction, but contributes no value byte.
  • The transmit-overwrite counts are noise at this sample size. VICE logs ACIA: data register written although data has not been sent yet. when the driver writes a data register that has not emptied - one transmitted character lost, recovered by the ARQ layer. The four testHarden.py machines logged 27, 21, 108 and 87 of them, and the four 1200-baud machines logged 32, 100, 12 and 28. Two runs of the identical script differed by a factor of four, and no pre-hardening run ever counted them at 1200 baud, so these numbers cannot be read as a regression or as its absence. What can be said is that no window lost the link and every receive error counted at 1200 was a ring overflow rather than a bad character.
  • One boot per strap for the hardening runs, two for 1200 baud. Nothing here is a soak test.
  • No complete game was played, at either strap or either rate.

Files

File What it is
testHarden.py new: two machines, either strap, tracepoints on both cartridge pages; the cold detect, the seven hot keys on a live link, a 20-second access census, and the C= + 8 breakpoint
parseTrace.py new: re-parses a saved tracepoint log into an ordered access list and a per-address / per-page census
testRealtime.py, testHighRates.py changed: the countLinkError tracepoint moved from $E403 to $E73F
testStrap.py changed: the register-value pattern now accepts uppercase hex
testLogs/harden.de00.txt, harden.df00.txt the two testHarden.py transcripts
testLogs/harden.{de00,df00}.{A,B}.{cold,hotkeys,census}.txt the twelve raw tracepoint logs every count above is taken from
testLogs/harden1200.txt, harden1200b.txt the two 1200-baud transcripts
testLogs/realtime.hard1200*.w{1,2}.samples.txt / .trace.txt per-second byte counts and raw tracepoint output for the four windows
shots/hdde00*.png, shots/hddf00*.png, shots/rthard1200*.png the screenshots cited above