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

27 KiB

The $DF00 strap option, exercised

Note: the two probe weaknesses in section 8 have since been closed. The probe now uses two patterns and requires both to read back, and aciaPageLatch $E5BF latches the page so that it is decided once per cold start instead of on every link open and every rate change - the two of the three hardenings section 8 suggested. hardenReport.md measures both. One further change since: the $DF00 fallback now reads that page's control register before it writes a probe pattern into it, so a cartridge on this strap has its own settings adopted rather than the open bus's (portCompatReport.md section 9.1, testReport.md section 11.6). The run below is left as it was taken.

Note on addresses. This report records a run made before the probe-hardening pass, which relocated six internal helpers. Addresses quoted below are the ones those routines had at the time of the run. See ADDRESS_CHANGES.md for the mapping onto the current source.

A SwiftLink has a jumper that moves its 6551 from I/O1 ($DE00) to I/O2 ($DF00), and a C64 Ultimate's SwiftLink emulation can be configured either way. The module probes $DE00 first and falls back to $DF00, but every run in testReport.md, realtimeReport.md and hotkeyReport.md answered at $DE00 on the first try, so the second half of aciaDetect had never executed. That is the loose end this report closes.

Everything below was observed in VICE 3.7.1 (x64sc) on 2026-08-22, against ../Modem_Wars_SwiftLink.d64 built from the current source with ./build.sh (checkAbi.py: 452 of 4096 bytes changed in 24 regions, ABI CHECK PASSED). Nothing is inferred from the source alone unless it says so. As in every earlier session, each run picked its own X display, its own TCP ports and its own VICE config file, and only processes these scripts started were killed.

Short answer:

  • The fallback works. With the cartridge at $DF00 the probe writes $1E to $DE03, reads back something that is not $1E, re-aims the six accessors at $DF00, and every subsequent access in an 800-stop watchpoint log lands on $DF00-$DF03. All six accessor operands read $DF at run time and the module image on disk still says $DE.
  • The game behaves identically. Same options menu, same module load, same "PICK UP PHONE THEN PRESS SPACE." prompt, same $E000 jump table, same Hayes string on the wire.
  • Two machines at $DF00 link and carry real ARQ frames. Both reached connection phase 3; at true C64 speed 300 baud gave 29.62 and 28.99 bytes/s with 599 and 682 frame lead bytes and zero link errors, and 2400 baud gave 8.98 bytes/s each way with no transmitter deadlock. A warp run got as far as the side-0/side-1 negotiation with a setup-overlay disk load over the live link.
  • Nothing else in the game touches the page. Two static passes over every image the C64 loads find exactly one instruction that can reach $DF00-$DFFF, and it is the stock driver's sta $DF59,y, which this build does not contain. A watchpoint on $DE04-$DEFF and $DF04-$DFFF
    • the register mirrors - never fired in any run.
  • Two things about the probe are worth knowing before trusting it on hardware, and neither is a regression: the read of an empty $DE00 is open bus and is not deterministic, and the probe re-runs on every link open and every baud hot key. Section 8.

1. How the rig was pointed at the other base

x64sc takes -acia1base 0xDF00 and accepts it: the monitor's io command then reports

Swiftlink:
>C:df00  00 30 00 00

and reads of $DE00-$DE07, $DF40-$DF47 and $DFFC-$DFFF all return open bus. VICE maps only the four bytes $DF00-$DF03 - it does not emulate the register mirrors a real cartridge has, which matters for how section 7's tripwire has to be read. (probeBankOut.py.)

The rig learned the strap rather than having it hard-coded. viceHarness.py gained

ACIA_BASE = int(os.environ.get("SWIFTLINK_ACIA_BASE", "0xDE00"), 16)

and aciaArgs() defaults its base to it; testBoot.py, testModemSelect.py and testRealtime.py now build their monitor commands from ACIA_BASE instead of the literal de00. The default is unchanged, so every command line in the earlier reports still means what it meant. A $DF00 run is the same command with SWIFTLINK_ACIA_BASE=0xDF00 in front of it.

run command
boot SWIFTLINK_ACIA_BASE=0xDF00 python3 testBoot.py ../Modem_Wars_SwiftLink.d64 strapDf00Boot
modem select SWIFTLINK_ACIA_BASE=0xDF00 python3 testModemSelect.py ../Modem_Wars_SwiftLink.d64 strapSelectDf00
probe + mirrors SWIFTLINK_ACIA_BASE=0xDF00 python3 testStrap.py ../Modem_Wars_SwiftLink.d64 strapDf00
two machines, true speed SWIFTLINK_ACIA_BASE=0xDF00 python3 testRealtime.py ../Modem_Wars_SwiftLink.d64 60 300 strapdf 1 1
two machines, mirrors armed SWIFTLINK_ACIA_BASE=0xDF00 python3 testStrapPair.py ../Modem_Wars_SwiftLink.d64 150

testStrap.py is new: it watches both pages at once, which is what makes the fallback visible rather than merely plausible.


2. The probe takes the fallback

testStrap.py arms four watchpoint pairs before the module can touch anything - $DE00-$DE03, $DF00-$DF03, $DE04-$DEFF, $DF04-$DFFF - and then answers the modem prompts. These are the first six stops of the run, verbatim from testLogs/strapDf00.hits.txt:

#1 (Stop on store de03)
.C:e567  8D 03 DE    STA $DE03      - A:1E X:00 Y:24 SP:ed ..-.....  200460609
#5 (Stop on  load de03)
.C:e56b  AD 03 DE    LDA $DE03      - A:00 X:00 Y:24 SP:ed ..-...Z.  200460625
#2 (Stop on store df03)
.C:e567  8D 03 DF    STA $DF03      - A:15 X:00 Y:24 SP:ef ..-.....  200460709
#6 (Stop on  load df00)
.C:e55f  AD 00 DF    LDA $DF00      - A:00 X:00 Y:24 SP:ef ..-...Z.  200460828
#6 (Stop on  load df01)
.C:e558  AD 01 DF    LDA $DF01      - A:50 X:00 Y:24 SP:f1 ..-.....  200460841
#2 (Stop on store df02)
.C:e554  8D 02 DF    STA $DF02      - A:09 X:00 Y:24 SP:f3 ..-.....  200460975

Read it against aciaDetect $E644:

  1. LDA #$DE / JSR aciaSetPage aims all six accessors at $DE00; aciaProbe $E6F2 writes its pattern $1E through aciaCtrlWrite - and the instruction at $E567 is disassembled as STA $DE03, so the self-modified operand really did still say $DE.
  2. aciaCtrlRead at $E56B reads it back and gets $00. CMP #$1E fails, Z = 0, BEQ is not taken, and LDA #$DF / JSR aciaSetPage re-aims the accessors. The fallback is taken.
  3. The very next access is STA $DF03 with A = $15 from the same instruction at $E567 - now disassembled as STA $DF03. That is loadBaudParameters writing the real control byte: 8N1, internal baud rate generator, 300 baud on a SwiftLink.
  4. LDA $DF00 drains the receiver, LDA $DF01 reads the status ( $50 ), and STA $DF02 writes command $09 - DTR on, RTS low, receiver interrupt on, transmit interrupt off.

That is exactly the opening sequence testReport.md section 3 recorded at $DE00, with one probe failure in front of it and every register three pages higher.

The link was opened twice in that run - the script offers A and space repeatedly until the ACIA is touched, so configureUserPortLines ran a second time - and the second probe read returned $FF where the first returned $00. Same decision, different floating byte; see section 8.

Nothing else ever touched the $DE00 page. Over the whole 800-stop log:

range stops what they were
$DE03 store 2 the probe pattern $1E, once per link open
$DE03 load 2 the probe read-back: $00, then $FF
$DE00, $DE01, $DE02 0 -
$DF03 store 2 control $15, once per link open
$DF02 store 387 command $09 / $05 as the transmitter is armed and disarmed
$DF01 load 203 status reads, which are also the NMI acknowledge
$DF00 store 202 transmitted characters
$DF00 load 2 the receiver drain in configureUserPortLines
$DE04-$DEFF, $DF04-$DFFF 0 the mirrors - see section 7

3. The six accessors end up pointing at $DF00

aciaSetPage $E663 patches the operand high byte of all six one-instruction accessors. Those six bytes are the driver's entire record of where the hardware is. Read back through the monitor after the link opened, on both machines of the two-machine run:

accessor instruction operand high byte on the disk at run time
aciaCmdWrite $E554 sta $xx02 command $E556 $DE $DF
aciaStatusRead $E558 lda $xx01 status $E55A $DE $DF
aciaDataRead $E55F lda $xx00 receive $E561 $DE $DF
aciaDataWrite $E563 sta $xx00 transmit $E565 $DE $DF
aciaCtrlWrite $E567 sta $xx03 control $E569 $DE $DF
aciaCtrlRead $E56B lda $xx03 control read-back $E56D $DE $DF

The raw dump, which is the same thing without the interpretation:

>C:e540  ae 55 e0 20  53 e3 a9 09  20 51 e5 20  5f e5 4c 58
>C:e550  e5 8d be e5  8d 02 df 60  ad 01 df 8d  bc e5 60 ad
>C:e560  00 df 60 8d  00 df 60 8d  03 df 60 ad  03 df 60 ea

The "on the disk" column is not an assumption: scanIoAccess.py decoding build/swiftlinkDriverE000.bin prints sta $DE02, lda $DE01, lda $DE00, sta $DE00, sta $DE03, lda $DE03 at those six addresses, so the image is unmodified and the $DF bytes are the running driver's own work.


4. The control and command registers really are written at $DF00

From the ordered log in section 2, and confirmed by a direct read of the chip at the end of the run (m df00 df03 = 00 10 05 15):

  • control $DF03 = $15 - $10 (8 data bits, one stop bit, internal baud rate generator) OR the rate bits for 300 baud on a SwiftLink's doubled crystal.
  • command $DF02 = $09 when the transmitter is idle (DTR on, RTS low, receiver IRQ enabled, transmit IRQ disabled) and $05 whenever there is something to send, exactly the pattern testReport.md recorded at $DE00.
  • data $DF00 carried $0D $41 $54 $51 $30 $56 $31 $58 $31 $41 $0D = CR ATQ0V1X1A CR, the answer-mode Hayes string, followed by the $00 stream of beginByteSyncPhase. The TCP sink at the far end of the emulated serial line received 8552 bytes beginning b'\rATQ0V1X1A\r\x00\x00\x00...', so those writes left the emulated chip.

A baud hot key also reaches the right page. In the two-machine real-time run, machine B's C= + 2 landed and the state readback showed baudIndex=6 with $DF03 = $18 (2400 baud), i.e. loadBaudParameters -> aciaSetControlIdle -> aciaCtrlWrite wrote the control register at the strapped base. (Machine A's key did not land that time and the script fell back to writing $DF03 from the monitor, which is the documented fallback in testRealtime.setBaud.)


5. The game reaches the modem prompts exactly as it does at $DE00

  • Options menu. shots/strapDf00BootMenu.png against the $DE00 build's shots/swiftlinkMenu.png: 1551 pixels differ, every one of them inside y = 119..126, the single character row the menu highlight blinks on. Everything else is identical.
  • The right module is loaded. After COMPETE WITH MODEM OPPONENT, $E000 read 4c 11 e1 4c 9a e2 4c a9 e0 4c f6 e0 4c cb e3 60 - byte for byte the same jump table testReport.md section 2 recorded at $DE00.
  • The prompts. shots/strapSelectDf0003afterOpen.png and shots/strapDf0003later.png both show "PICK UP PHONE THEN PRESS SPACE." over the options menu. Against the $DE00 run's shots/modemPromptPressSpace.png each differs by 403 pixels, all inside y = 200..206 - the status line's own blink row.
  • No ACIA register is touched until both prompts are answered. The watchpoints on all four ranges were armed at the options menu and the first stop came only after the A/O and space answers, which is the same result the $DE00 run got.
  • The NMI vector is installed. d fffa fffb disassembles the two bytes as 85 E6, i.e. $E685 = commNmiHandler.

6. Two machines at $DF00

At true C64 speed (testRealtime.py, 60-second windows)

Both machines reached connection phase 3, the game's own packet protocol.

rate direction bytes throughput frame lead bytes $55 countLinkError $E403 receive-ring overflow $E505 transmitter deadlock
300 A -> B 1782 29.62 B/s = 296 bit/s 599 0 0 no
300 B -> A 1744 28.99 B/s = 290 bit/s 682 0 0 no
2400 A -> B 540 8.98 B/s 203 0 0 no
2400 B -> A 540 8.98 B/s 203 0 0 no

Per second, from testLogs/realtime.strapdf.*.samples.txt: at 300 baud 27 to 32 bytes in each second on conn0 and 14 to 31 on conn1, at 2400 baud 6 to 12 each way, and no second at zero in either window.

The emulators' own cycle counters put both windows at 100.7% to 101.2% of real time, so this is true C64 speed and not warp. 30 bytes/s is the whole of what 300 baud can carry at 8N1, and the 2400-baud figures match realtimeReport.md section 8's post-fix $DE00 numbers (8.94 to 8.98 B/s) to within a byte. The $55 counts are the ARQ frame lead byte: these are real frames, not a byte-sync flood. The 2400-baud rate was reached by switching on a live link, and the link came back to phase 3 after the switch.

In warp, with the mirror tripwire armed (testStrapPair.py, 150-second window)

direction bytes frame lead bytes $55 eight-byte $00 runs
conn0 -> peer 21642 5988 53
conn1 -> peer 20511 6259 30

Both machines ended in connectionPhase = $03, isLinkActive = $01, linkErrorCount = $00, with all six accessors still reading $DF. The tail of the relay log is frame traffic - 55 ed 54 55 05 6b 55 05 6b ... - lead byte, then length/control/sequence/checksum.

More useful than the byte count: machine A reached the GAME TYPE menu and machine B showed "OPPONENT PICKING GAME/MAP." (shots/pairDf0003aLater.png, shots/pairDf0003bLater.png). That is the state the side-0/side-1 negotiation ends in, and getting there means the two agreed over the link which of them plays which side, and that machine A loaded the setup overlay from disk while the link was open - the suspend/resume handshake - with the mirror watchpoints armed throughout.


7. The hazard: does anything else reach into the $DF00 page?

This is the question spec_uart.md raised (hazard 5, and the loadBaudParameters note): a real SwiftLink decodes only A0 and A1 inside its page, so all 64 four-byte windows of $DF00-$DFFF are the same four registers. Any read where address AND 3 = 0 consumes a received character and clears RDRF; where address AND 3 = 1 it clears the interrupt flag. And a 6502 absolute-indexed store performs a dummy read at the un-carried address first, so sta $DF59,y reads $DF56, $DF57, $DF58 on its way - and $DF58 AND 3 = 0 is the data register. That instruction is at $E358 in the stock module. It is gone from this build; the question is whether anything else does it.

7.1 Static: two passes over every image the C64 loads, and one over the indirect pointers

scanIoAccess.py (new, in this directory) disassembles the code regions of all 22 loaded images - the complement of disassembly/build/dataMap.json, so data tables are not decoded as instructions - and reports every absolute or absolute-indexed instruction whose effective address, or whose dummy read, can land in $DE00-$DFFF. Whole-game result:

game/modemDriverE000 $E358: sta $DF59,y   reaches $DF59-$E058, dummy read in $DF00-$DFFF
game/modemDriverE000 $E657: sta $DD05,x   reaches $DD05-$DE04, dummy read in $DD00-$DDFF
game/modemDriverE000 $E65D: sta $DD04,x   reaches $DD04-$DE03, dummy read in $DD00-$DDFF

All three are in the stock modem driver, which this build replaces. $E358 is the known one. The other two are setBitPeriod writing CIA2's timer latches with X = 0 or 2, so they reach $DD04 to $DD07 in practice and their dummy read is in the CIA2 page, not the cartridge page; in this build both addresses are a bare RTS. Nothing in the main program, the overlays, the text engine, the high-memory block, the fast loader, the trainer or the trainer overlays appears at all.

The same scan over build/swiftlinkDriverE000.bin finds only the six accessors, all plain absolute:

swiftlinkDriverE000.bin $E554: sta $DE02      $E558: lda $DE01      $E55F: lda $DE00
swiftlinkDriverE000.bin $E563: sta $DE00      $E567: sta $DE03      $E56B: lda $DE03

A second, alignment-independent pass decodes every three-byte window of every image as if it were an instruction, which catches a misaligned entry, a self-modified operand or code hidden inside a data region. It adds fourteen hits and every one was checked by hand against the .s files:

hit what it really is
boot/titleBitmapA000 x 9 title-screen bitmap data; the file is data end to end and is never executed
boot/fastLoaderC000 $C047, game/mainProgram0800 $08A8 the middle of lda $DD00 / and #$DF - AD 00 DD 29 DF read one byte late
game/mainProgram0800 $0D9B inside the keyboard matrix code table at $0D98; the $DE is a PETSCII code
game/mainProgram0800 $1E76 the operand high byte of sta scrollScreenStepOpcode at $1E74
game/modemDriverE000 $E358 the stock sta $DF59,y again

So the superset pass adds nothing: no instruction in the shipped code of this build can name an address in the cartridge page except the six accessors.

7.2 The one place the game does address $DF00-$DFFF - and why it is safe

Neither static pass can see an operand that only exists at run time, and there is one: copyPageUnderIo $58B2 copies 256 bytes with lda abs,y / sta abs,y whose operands its callers patch. The film-buffer callers walk a whole page pointer over the film:

7F64  lda #$D0 / sta D_58C7    ; "the destination page walks $D000..$DF00"   (load a saved film)
816C  lda #$D0 / sta D_58C4    ; "the source page walks $D000..$DF00"        (save a film)

and readFilmByte $5874 / writeFilmByte $5893 dereference filmPtr $BC/$BD, which rewindFilmPtr $58D5 sets to $D000 and which then walks the same 4 KB.

scanIoAccess.py pass 3 lists all eighteen zero-page pointers the loaded code dereferences with (zp),y or (zp,x) and the immediate high bytes stored into each. $BC/$BD is the only one whose high byte is ever given a value in $D0-$DF, and it has exactly two indirect uses in the whole game - $5885 and $58A4, the two inside those routines. That pass is a narrowing, not a proof: a pointer can also be given a computed high byte, and filmPtr is, since it is incremented across the buffer. What closes it is that the two instructions which dereference it are both inside the banked- out window below.

All four instructions - the two inside copyPageUnderIo and the two that dereference filmPtr - run with $01 = $34, which replaces the $D000-$DFFF I/O block with the RAM underneath, and every one of the three routines puts $01 back afterwards. That is checked in the emulator rather than argued (probeBankOut.py):

control register written through the I/O window: $1E
same address written again with $01 = $34, read back banked out: $55
control register with the I/O area banked in again: $1E
the banked-out write reached the cartridge: no, it went to RAM

The write to $DF03 with the I/O area banked out landed in RAM and the 6551's control register was untouched, which is what a real C64's PLA does too: it does not assert /IO2 in a memory configuration that has no I/O. So the film system's traffic through $DF00-$DFFF cannot reach a $DF00-strapped cartridge.

This is also the reason DESIGN_NOTES.md item 1 exists - the NMI handler has to bank the I/O area back in itself, because it can fire in the middle of exactly these routines.

7.3 Empirical: a tripwire on the mirrors

VICE maps only $DF00-$DF03, so $DE04-$DEFF and $DF04-$DFFF answer nothing in the emulator - which makes them a clean tripwire. A watchpoint there costs nothing while it stays silent, and a stop is an instruction reaching into the cartridge page that would be a register access on real hardware.

run what it covered mirror stops
testStrap.py options menu, module load, both prompts, link open, Hayes dialogue, byte sync, then 45 s more with only the mirror watch armed 0
testStrapPair.py machine A boot to the GAME TYPE menu, including a setup-overlay disk load over the live link, 150 s window 0
testStrapPair.py machine B boot to "OPPONENT PICKING GAME/MAP.", 150 s window 0

Verdict. Static and empirical agree: with the cartridge at $DF00, the only code that reaches the page is the driver's six accessors, and the only other code that names addresses in it - the film buffer - does so with the I/O area banked out, where the cartridge is not selected.


8. What this does not say, and two things worth knowing

  • No real hardware. Every result is VICE 3.7.1 with -acia1mode 1 -acia1base 0xDF00 -acia1irq 1. Nothing has been run on a physical SwiftLink or on a C64 Ultimate.

  • VICE does not emulate the register mirrors. Its SwiftLink claims four bytes, not the page. So section 7.3's tripwire proves that no instruction addresses the mirror range; it cannot show what a real cartridge would do if one did. The mirror behaviour itself is taken from spec_uart.md, not observed here. The same limitation means an accidental mirror access would be harmless in this emulator and destructive on hardware - which is precisely why the static scan matters.

  • The probe's read of an empty $DE00 is open bus, and it is not deterministic. On a C64 an unmapped I/O read returns the last byte the VIC put on the bus. The driver's own two probe reads in one session returned $00 and $FF; a memory dump later in the same session read f6 f6 f6 f6 across $DE00-$DE03; a bare machine sitting in the KERNAL read $00 in one run and $FF in another; and probeOpenBus.py, sampling $DE03 120 times at different points in the frame a few seconds after the loader finished, got:

    $F6  62 times (51.7%)     $2C  32 times (26.7%)
    $2D  20 times (16.7%)     $2E   6 times (5.0%)
    reads that would have made the probe believe an ACIA is at $DE00 ($1E): 0 of 120
    

    So on this screen the byte is nowhere near $1E - but the value is screen data the driver does not control, and nothing rules $1E out on a different screen. If it ever reads back $1E, the probe concludes the cartridge is at $DE00, aims all six accessors at open bus, and the link is dead until something re-probes. Note also that when the probe does fall back, it does not verify $DF00 at all - aciaDetect says so in its own comment: "aim the accessors there and hope".

  • The probe re-runs on every open and every rate change. aciaDetect is the first thing loadBaudParameters $E353 does, and the baud hot keys fall through into loadBaudParameters from $E350. Two consequences on a $DF00 machine: every speed hot key re-decides which page the cartridge is on, so the open-bus gamble above is taken again on a live link; and every open and every rate change writes $1E into $DE03, which is a foreign device's register if anything else is plugged in at $DE00 - and something else at $DE00 is a common reason to strap a SwiftLink to $DF00 in the first place. If that device reads back $1E from its own offset 3, it shadows the SwiftLink permanently.

    None of this is a regression and none of it was hit in any run here. Three cheap hardenings, in increasing order of intrusiveness, if it is ever worth closing: probe with two different patterns and require both to read back; latch the page once a probe has succeeded so a live link cannot be re-aimed; or probe only on a cold open rather than on every rate change.

  • Only 300 and 2400 baud were exercised at $DF00, matching what the earlier reports covered at $DE00. The other five rates were not driven.

  • Only one hot key was seen to land at $DF00 - C= + 2 on one machine, which programmed $DF03 = $18. C= + A, C= + O, C= + H, C= + P, C= + C and C= + RETURN were not exercised on this strap.

  • No complete game was played, at either base.

  • One rig failure, recorded for honesty. The first testStrapPair.py run produced a session in which machine A never left the options menu: testTwoMachines.answerModemPrompts decides the link is open as soon as $E5BE reads non-zero, and $E5BE is ordinary RAM until the module is loaded over it - it happened to hold $AC, so the script stopped pressing keys. Only one machine ever connected to the relay and it poured out 2188 bytes of byte-sync $00. That run says nothing about the strap and its numbers are not used here. testStrapPair.py now requires two independent conditions instead - the module's jump table resident at $E000 and the control register programmed - and the re-run linked first time. testTwoMachines.py itself was left alone.


Files

file what it is
testStrap.py new: one machine at $DF00, watchpoints on both pages and both mirror ranges, ordered access log, accessor read-back
testStrapPair.py new: two machines at $DF00 with the mirror tripwire armed for the whole session
scanIoAccess.py new: the three static passes of section 7, over every loaded image and any extra binary given as path@LOADADDR
probeBankOut.py new: VICE's I/O map, and the banked-out write experiment of section 7.2
probeOpenBus.py new: what an empty $DE00 reads back, sampled across the frame
viceHarness.py changed: ACIA_BASE from SWIFTLINK_ACIA_BASE, default $DE00
testBoot.py, testModemSelect.py, testRealtime.py changed: monitor commands built from ACIA_BASE instead of a literal de00
testLogs/strapDf00.hits.txt the 800 watchpoint stops of section 2
testLogs/strapDf00.mirror.txt the mirror-only watch of section 7.3 - empty, which is the result
testLogs/strapSelectDf00.hits.txt the modem-select run's 300 stops
testLogs/strapDf00Probe.txt, strapSelectDf00.txt, strapRealtimeDf00.txt, strapPairDf00.txt, strapOpenBus.txt the five run transcripts
testLogs/realtime.strapdf.run300.w1.samples.txt, realtime.strapdf.run2400.w1.samples.txt per-second byte counts for the two real-time windows
shots/strapDf00*.png, shots/strapSelectDf00*.png, shots/pairDf00*.png, shots/rtstrapdf*.png the screenshots cited above