Disassembly confirmed the bench theory: the no-ACK wait loop at $D9E0
self-clocks in byte times (each tick transmits an IDLE keep-alive and
re-enters on its TX-complete interrupt), so raising the baud silently
shrank the ACK grace from ~5.2ms to ~1.6ms - under USB turnaround,
hence the v1 retry storm. --widen-ackwait (requires --baud31250) sets
the limit to 40 ticks (~12.8ms at 31250). v2 image: 25 bytes changed,
sha256 420d4cfc...; re-disasm diff vs v1 is exactly the CMPA operand.
v1/classic hashes reproduce unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
127s mash at 31250: zero wedges — but 5655 framing resyncs, duplicate
replies (215% of poll slots) and AbandonCount=65 reveal the board's
reply ACK-wait scales with byte time: ~5+ms at 9600 (USB beats it,
zero framing) vs ~1.6ms at 31250 (USB cannot). All 65 retry
exhaustions self-recovered through the patched give-up path — the
strongest field proof of the latch fix yet; unpatched firmware would
have wedged on the first. Verdict recorded in ANALYSIS.md: cabinets
run the 9600 patched chip; 31250 stays a bench branch until the retry
window is widened in firmware.
Tool: before-snapshot now retries once (can lose the post-DTR boot
race, as this run showed); evidence log archived.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
testlogs/ carries the raw records behind the CONFIRMED verdict:
baseline wedged at 0.62s and stayed dead 300s; patched ran clean
through 152s of heavier mash, worst gap 0.58s self-recovered.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Baseline (original chip): reply path wedged 0.62s into the run — the
tool's own version/check exchange colliding with analog polling was
enough, no mash required — and stayed dead for all 300s despite 951
button presses (no button revival; power cycle only). Patched chip:
survived the same startup collision, 2419 analog replies at a steady
~16/s over 152s under heavier mash (peak 635/min), zero wedges, worst
gap 0.58s self-recovered — the $DFF0 latch-clear stub doing its job.
Results table + evidence logs committed (rio-firmware/testlogs/).
Remaining: cabinet soak + native-game session; 31250 variant A/B.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
make_patch.py gains --baud31250: one byte beyond the wedge patch — the SCI
init operand at $D62B ($30 -> $02). BAUD $30 = /13 prescale, /1 divider
(2MHz E / 208 = 9615); $02 = /1, /4 -> 31250 exactly, 3.3x faster. The
init write also confirms the 8MHz crystal, which is why 19200/38400 are
unreachable and why 62500/125k are left alone pending an ISR cycle count.
- 24 bytes changed vs original (sha256 9f866cf3...); re-disassembly diff
vs the classic patched image shows exactly the one operand line, and
the classic build still reproduces 3fc8170c... (script regression-safe).
- PC side: SerialPortTransport takes an optional baudRate (default 9600,
runtime untouched); RioSerialMonitor + --mash accept --baud 31250.
- Caveats documented in README/ANALYSIS: non-standard rate (FTDI-class
adapters only, no 16550s); native games still speak 9600 so this chip
is bench/RIOJoy-only; validate the wedge patch at 9600 first.
275 tests green; mash --selftest regression unchanged (FAIL/exit-1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New --mash mode (tools/RioSerialMonitor/MashTest.cs) mechanizes the
wedge-patch validation plan from RIOv4_2-ANALYSIS.md:
- Runs the live link with the app's >5s reset-recovery DISABLED so a
board wedge stays observable, and echoes lamps on every press
(lamp/reply collisions are the wedge trigger).
- Gap timing uses ANY AnalogReply packet (0xFE sentinels included -
a sentinel still proves the reply path is alive); logs a gap
histogram + top-10 longest gaps with timestamps.
- WEDGE detector: analog silent past the threshold (default 2s) ->
beep + banner; on resume, classifies self-recovered (patched
expectation) vs button-revived (button event within 300ms of resume,
the unpatched signature) vs unresolved at run end.
- Board self-reported RestartCount/AbandonCount/FullBufferCount
snapshotted before/after via CheckRequest, delta printed
(7-bit wrap-aware).
- Fixed-layout summary teed to riomash-<label>-<stamp>.log so
baseline-vs-patched runs diff directly. Exit 0 = no wedge, 1 = wedge.
--mash --selftest drives the whole instrument against a scripted
in-memory board (SelftestTransport) that goes silent at t=4.0s and
revives 200ms after a button at t=6.5s: verified end-to-end - alarm at
6.0s, wedge classified button-revived (2.75s), counter delta +4/+0/+1,
verdict FAIL, exit 1. Use it to sanity-check the alarm at the cabinet.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- RIOv4_2.bin: 64K image dumped from the board's AM27C512 (code at
$C000-$FFFF, TMP68HC11).
- disasm_6811.py + RIOv4_2.disasm.asm: vector-rooted 68HC11 disassembly;
SCI ISR at $D630 traced to the $2521 reply-in-progress latch leak that
wedges the analog reply path under button-mash stress.
- make_patch.py + RIOv4_2_patched.bin: two in-place edits (abort-path stub
at $DFF0, unconditional latch clear at $DA21) statically verified by
re-disassembly diff. Dynamic proof awaits a burned W27C512.
- Analysis + burn/validation plan in RIOv4_2-ANALYSIS.md and README.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>