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.mdfound the real cause (NMI re-entrancy exhausting the stack) andportCompatReport.mdmeasured 38400 as healthy once it was fixed, so the hot key is back. The probe hardening described below is still current. SeeADDRESS_CHANGES.mdfor 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.mdestablished the defect - unbounded re-entrancy incommNmiHandler, not the rate - and with the re-entrancy guard in place C= + 8 selects 38400 again (portCompatReport.mdsections 3 and 4,testReport.mdsection 11;testAdopt.pymeasured the key puttingbaudIndex= 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$E644used to probe$DE00on every call, andloadBaudParameterscalls it on every link open and every baud hot key - so the old build wrote its probe pattern into$DE03once per open and once per rate change, on a live link, on a page that is very often some other cartridge's registers.aciaPageLatch$E5BFnow records the answer,aciaDetectreturns in five bytes while it is non-zero, and onlyselectFramedLinkVectors$E3F3- whichinitCommModulecalls once, right after the overlay loads - puts it back to zero. The probe also became a two-pattern probe, because an empty$DE00reads 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$E344can never match because that search only runs on key codes with bit 7 set. 38400 wedged four boots out of four (highRateReport.mdsections 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
$DE00the probe writes$1E, reads$1Eback, writes$15, reads$15back, and latches$DE. On$DF00it writes$1Eto$DE03, reads back$F6- open bus - never writes the second pattern, and aims everything at$DF00. All six accessors at$E551-$E56EandaciaPageLatch$E5BFagreed 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 toaciaDetect- 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
$DF00strap each machine touched$DE00-$DE03exactly 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$DE00strap,$DF00-$DF03was 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$E38Acaught the keystroke withA=$B8andlastHotkeyCode$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$E047read$00at 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 oncountLinkError'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 $E3F3at$E2BA, insideinitCommModule- the$E003X=1 case, which the game calls once, from$0F46, immediately after the overlay is loaded.$E644(aciaDetect) is reached from one instruction:JSR $E644at$E353, the first thingloadBaudParametersdoes.$E6F2(aciaProbe) is reached from one instruction:JSR $E6F2at$E64B, insideaciaDetect, 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.
3.1 The link was opened twice, and the probe ran once
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.
3.2 Seven hot keys on a live link, six control-register writes
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
C= + 8 on a live link
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
txCharActiveset 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.
waitForPhasereturned{'A': 3, 'B': 3}before the first window of each boot, and the$55counts - 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 edgesconnectionPhasewas 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$E047stayed 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 ASCIIHthe module ships with at$E047;clearLinkVarszeroes it when the link opens.)- The transmitter never parked. In all 24 live readings
txCharActive$E5BBandaciaCommandShadow$E5BEwere only ever the two self-consistent pairs$00/$09(idle) and$01/$05(a byte of ours in the chip). The fault shape -$E5BBset while$E5BEbit 2 is clear - occurred zero times, andprobeTxDeadlocknever fired, so nothing in these numbers is the test repairing the driver.uartPendingCount$E0A5read$00,$01or$03at every edge and was never once out of range. - The errors that did occur were the documented ones. The
countLinkErrortracepoint counted 22 rejected characters across the four windows (4, 4, 7, 7), and the conditional tracepoint on theCPX #$14insidepushUartRxRingreturned identical counts (4, 4, 7, 7). Every single one was therefore a receive-ring overflow, not a bad character - the same fingerprintrate1200Report.mdfound for all 32 of its overflows. That the byte-sync rounds they caused completed is an inference here rather than a measurement: the$E805/$E855sync 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 thatconnectionPhasewas 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.pywas tracing an address that no longer exists.countLinkErrormoved from$E403to$E73FwhenselectFramedLinkVectorshad to grow the five bytes that clear the page latch - that move is part of the hardening being verified here.testRealtime.pyandtestHighRates.pystill saidtrace exec $e403, and$E403is now the operand byte of anLDA #$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 theDISASMpattern intestStrap.py- whichtestHarden.pyinherited - matchedA:only against[0-9a-f]. SoA:1Edid 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 intestStrap.pyandtestHarden.py, andparseTrace.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
$DF00strap's probe read returned$F6because 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.pydid not arm the mirror tripwire thattestStrap.pyuses on$DE04-$DEFF/$DF04-$DFFF; that tripwire recorded 0 stops in every run ofstrapReport.mdsection 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
lastHotkeyCodeis overwritten with$FFby 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 plain8in a context where8means something, because the game was on "OPPONENT PICKING GAME/MAP." both times. aciaDetectstill does not verify$DF00. When$DE00does not answer, the accessors are aimed at$DF00on trust and the latch is set - so a machine with no cartridge on either page latches$DFand 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.pyrun 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.pywarns about it. They are the$DE00run's two cold logs and its machine-A census, and the$DF00run'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 fourtestHarden.pymachines 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 |