Move RIO hardware + firmware archive to the TeslaRel410 repo
The physical RIO board docs (photos, schematics, GAL decode) and the
board firmware (dumps, disassembly, make_patch.py, RIO 4.3 + FastRIO
images, analysis, testlogs) describe hardware shared across all Tesla
cockpits with a lifecycle independent of this Windows app — a
native-game-only cabinet runs the firmware and never touches RIOjoy.
They now live in TeslaRel410/restoration/{rio-hardware,rio-firmware}
with full history preserved (git subtree).
Kept here: docs/PROTOCOL.md (this app's interface contract) and the
RioSerialMonitor bench harness (--mash/--e0test, C# on RioJoy.Core).
README + code comments now point at the TeslaRel410 archive.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@@ -22,7 +22,7 @@ Red Planet — talk to the RIO directly and do not use this app.)
|
||||
| [`tools/XcfRegionExtract`](tools/XcfRegionExtract/) | Extracts cockpit label regions from `riojoy.xcf` → `regions.json` |
|
||||
| [`docs/PLAN.md`](docs/PLAN.md) | Full modernization plan (7 phases) |
|
||||
| [`docs/PROTOCOL.md`](docs/PROTOCOL.md) | RIO wire format + `iRIO` input-map reference |
|
||||
| [`docs/hardware/`](docs/hardware/) | Board photos, scanned schematics, GAL fuse dump — the physical RIO board set |
|
||||
| _RIO board hardware & firmware_ | Moved to the [TeslaRel410 `restoration/`](https://gitea.mysticmachines.com/VWE/TeslaRel410/src/branch/main/restoration) archive — board photos, schematics, GAL decode (`restoration/rio-hardware`) and the RIO 4.3 board firmware (`restoration/rio-firmware`) |
|
||||
| [`docs/reference/`](docs/reference/) | Cockpit overlay art & the legacy labeling pipeline |
|
||||
| [`legacy/`](legacy/) | Original C++/vJoy implementation, kept as reference |
|
||||
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
# RIO cockpit hardware — boards, schematics, GAL dump
|
||||
|
||||
Physical documentation of the VWE Tesla-cockpit **RIO** (Remote Input/Output)
|
||||
board set, gathered during the 2026 restoration: board photos (Signal,
|
||||
2026-07-15), a scan of the original paper wire schematics, and the fuse map
|
||||
read out of the RIO board's GAL address decoder.
|
||||
|
||||
All boards are **Copyright 1994 V.W.E. Inc.** — the schematic title blocks
|
||||
spell it out: **Virtual World Entertainment, 1100 W Cermak Suite B404,
|
||||
Chicago, Illinois**, every sheet and silkscreen credited **"Design by M.C."**
|
||||
Schematic plots are dated 1/11/1996 (peripheral boards) and 6/26/1996 (RIO
|
||||
board), so these drawings post-date the Rev. 0/1 boards they describe.
|
||||
|
||||
## Board family
|
||||
|
||||
| P/N | Title | Rev photographed | Key silicon |
|
||||
|-----|-------|------------------|-------------|
|
||||
| **1407** | RIO board ("CPU board") | Rev. 3 | Toshiba **TMP68HC11** @ 8 MHz, GAL20V8A decoder, 27C512 EPROM, LH52B256 32K SRAM, 5× HCTL-2016 |
|
||||
| **1401** | Buttons board | Rev. 0 | 2× UCN5821A lamp drivers, 2× 74HCT165, 74HCT164, 74LS00/05 |
|
||||
| **1402** | Keypad board | Rev. 1 | 2× 74HCT164, CD74HCT165, 74LS00/05 |
|
||||
| **1408** | Display board ("RIO external display") | Rev. 1 | Intersil **ICM7228B** 8-digit driver, 8× LTS313A 7-segment |
|
||||
| **1413** | Quad Amplifier Board | Rev. 1 | **Pyramid PB-150P "Pro Plus"** car amp (bolt-on) + passive 3-way crossovers |
|
||||
|
||||
Plus the cockpit position sensors: **HP HEDS-5700** two-channel quadrature
|
||||
optical encoders (option code **I** = 512 counts/rev; the photographed unit is
|
||||
date-coded 9515, Singapore), one per analog axis, read by the five HCTL-2016
|
||||
counters on the RIO board (`OPT1`–`OPT5` = stick X/Y, throttle, left/right
|
||||
pedals).
|
||||
|
||||
## RIO board 1407 (schematic sheets 1–3, `RIO_1407`)
|
||||
|
||||
**Sheet 1 — CPU : Memory Decoder.** The TMP68HC11 runs in expanded
|
||||
multiplexed mode: a 74HCT573 latches A[0:7] off the AD bus, and **U7 — drawn
|
||||
as a 20L8, fitted as a GAL20V8A-15LP** — decodes A[8:15] into `ROM_SEL*`
|
||||
(27C512 EPROM, U10), `RAM_SEL*` (LH52B256-70LL 32K SRAM, U11), `PH_SEL*`
|
||||
(HCTL-2016 bank via two 74LS138s), `DSP_SEL*` (external display port) and
|
||||
`BND_SEL` (I/O-interface latch, sheet 3). Each HCTL-2016 feeds a 5-pin
|
||||
header (`J4`–`J8`) carrying CHA/CHB from a HEDS-5700. The DB9 COM port goes
|
||||
through a MAX231/MAX232-class level shifter; the schematic's `IN_RESET*` from
|
||||
the serial side is the hardware behind the **DTR reset pulse** described in
|
||||
[PROTOCOL.md](../PROTOCOL.md) §1. The "display interface" block (ALS541
|
||||
buffers, HEADER14) exports latched data lines `HD[00:04]` + digit address
|
||||
`AS0-2` to the display board — this is a memory-mapped port, *not* the pod
|
||||
bus. A 22V10 site (U31) is drawn mostly N/C and is **unpopulated** on the
|
||||
photographed board.
|
||||
|
||||
**Sheet 2 — Power & Reset Interface.** A MAX690 supervisor provides
|
||||
power-fail reset and a **watchdog (WDI)**; reset can also come from the
|
||||
front-panel `RESET` toggle (SW1) or remotely through an **H11L1 optocoupler**
|
||||
fed from an isolated 3-pin `+5 VDC` input (J8) — the game PC can hard-reset
|
||||
the board. The `ABORT` toggle (SW2) pulls the HC11 `IRQ*`. A 74LS241 drives
|
||||
the status LED bank: `POWER ON`, `FAULT`, `RESET`, `RUN`, `TX`, `RX`. Power
|
||||
enters on J9 through 5 A/10 A fuses, LC filters and 1N5341B clamp zeners,
|
||||
producing separate logic `VCC` and peripheral `VCC_EXT` rails.
|
||||
|
||||
**Sheet 3 — I/O Interface ("pod bus").** Eight identical 16-pin IDC ports
|
||||
(`J11`–`J18`) fan out through ALS541 buffers and SIP resistor packs. All
|
||||
ports share the outbound signals; each port has its own return line
|
||||
(`DI[0..7]`, selected by a 74LS138 from `SEL0-2`) and its own buffered
|
||||
brightness line (`BRT_CTR0-7`, all copies of one PWM signal). A 74HC574
|
||||
latch written via `BND_SEL` holds the bus control bits (`SEL0-2`, `RD_SB`,
|
||||
`WR_SB`, `D_OUT`, `CCK`) — so the firmware bit-bangs the bus by rewriting
|
||||
this latch.
|
||||
|
||||
### Pod bus signal set (per port)
|
||||
|
||||
| Signal | Direction | Purpose |
|
||||
|--------|-----------|---------|
|
||||
| `CCK` | RIO → pod | shift clock |
|
||||
| `DD_IN` | RIO → pod | serial data into the pod's 74HCT164s (address select + payload) |
|
||||
| `WR_SB` | RIO → pod | write strobe (latch lamp/display data) |
|
||||
| `RD_SB` | RIO → pod | read strobe (parallel-load the pod's 74HCT165) |
|
||||
| `DD_OUT` | pod → RIO | serial readback, **open-collector 74LS05** so pods can share a cable |
|
||||
| `BRT_CTR` | RIO → pod | global lamp-brightness PWM, gates the UCN5821A output enable |
|
||||
| `VCC_EXT`, GND | — | peripheral power rail |
|
||||
|
||||
Every pod board carries a 74HCT164 whose parallel outputs go to an
|
||||
**`ADR. SEL.` jumper block (`A-0`…`A-7`)**: the RIO shifts an address
|
||||
pattern down the cable, the jumper picks which bit this board answers to,
|
||||
and 74LS00 gates qualify `RD_SB`/`WR_SB` with that selection. This
|
||||
hardware addressing is what the 0x00–0x6F logical input map in
|
||||
[PROTOCOL.md](../PROTOCOL.md) ultimately resolves to.
|
||||
|
||||
## Peripheral boards
|
||||
|
||||
- **1401 Buttons board** (`PBE_1401`) — eight 4-pin connectors (`J3`–`J10`),
|
||||
pinout `1 = buttons GND, 2 = button in, 3 = lamps +5V, 4 = lamps logic GND`.
|
||||
Two UCN5821A serial-in latched sink drivers light the button lamps
|
||||
(brightness = `BRT_CTR` PWM on their OE), two 74HCT165s read the buttons
|
||||
back, one of them also echoing board status/address.
|
||||
- **1402 Keypad board** (`KEY_1402`) — hosts the COTS 16-key hex pad (0–F)
|
||||
on an 8-line header (4×4 matrix). A second 74HCT164 drives the matrix
|
||||
rows through open-collector 74LS05 inverters; the 74HCT165 reads the four
|
||||
columns (`IN0-3`, 2.7K pull-ups) plus a board-detect line `BRD_DT*`.
|
||||
- **1408 Display board** (`DSP_1408`) — ICM7228B driving eight LTS313A
|
||||
digits (hex decode) from the RIO's dedicated 14-pin *external display*
|
||||
port; write-only, plus an isolated 26-pin **"IBM printer port"** LED
|
||||
monitor block. Silkscreen reads "P/N 140B" but the schematic title block
|
||||
confirms **1408**. Full pin-level output definition — and what the
|
||||
firmware actually displays (boot banner + the keypad-A-9-E test mode) —
|
||||
in [display-board-1408.md](display-board-1408.md).
|
||||
- **1413 Quad Amplifier Board** (`AMP_PLT`, silkscreen "Quad Amplifier
|
||||
Board … P/N 1413 REV. 1") — the actual amplification is a stock
|
||||
**Pyramid PB-150P "Pro Plus"** 4-channel 12 V car-audio amplifier bolted
|
||||
to the board as a module; the schematic only drew the VWE-designed
|
||||
passive circuitry around it. The board feeds the Pyramid's `B+12V / GND
|
||||
/ REMOTE` screw terminals and speaker-level `HIGH INPUT` connectors, and
|
||||
its four outputs (`1CH/L`…`4CH/R`) return through terminal strips into
|
||||
per-corner 3-way passive crossovers: Dale IHB-3 inductors (820 µH woofer
|
||||
low-pass, 180 µH midrange, date-coded 9531), MMP 100 VDC film capacitors
|
||||
(2.2 µF/10 µF), and Dale CP-5 2 Ω 5 W pad resistors. Corner speaker
|
||||
connectors J1–J4 (front/back × left/right) are 6-pin: `1 woofer+,
|
||||
2 midrange+, 3 tweeter+, 4 woofer−, 5 midrange−, 6 tweeter−` (silkscreen,
|
||||
matching sheet 5). Power enters on J5 (4-pin: `+12VDC ×2, GND ×2`) with a
|
||||
green POWER ON LED; the per-channel red LED pairs across the outputs are
|
||||
the schematic's "speaker outputs checking circuit". PCB etch NT 1-0A 3695
|
||||
— the same fab batch series (week 36 '95) as the buttons board.
|
||||
|
||||
## GAL dump — [`gal/GAL20v8a_5764.JED`](gal/GAL20v8a_5764.JED)
|
||||
|
||||
Fuse map read 2023-03-09 from **U7 on the 1407 board** (GAL20V8A-15LP,
|
||||
hand-labeled **"5764"** — which matches the JEDEC fuse checksum `*C5764`, the
|
||||
source of the file name). Per sheet 1 it is the CPU **memory decoder**
|
||||
(drawn as a 20L8). **Decoded** — equations, the recovered memory map
|
||||
(`$2000-$9FFF` SRAM, `$A000/$A010/$A020` I/O windows, `$C000-$FFFF` EPROM)
|
||||
and the firmware cross-check are in [`gal/README.md`](gal/README.md); the
|
||||
chip's programmed signature even reads `U7`.
|
||||
|
||||
## Schematic scan — [`schematics/Scans_018-014.pdf`](schematics/Scans_018-014.pdf)
|
||||
|
||||
| PDF page | Sheet | Title block |
|
||||
|----------|-------|-------------|
|
||||
| 1 | RIO_1407 sheet 1/3 | CPU : Memory Decoder |
|
||||
| 2 | RIO_1407 sheet 2/3 | Power & Reset Interface |
|
||||
| 3 | RIO_1407 sheet 3/3 | I/O Interface |
|
||||
| 4 | PBE_1401 1/1 | Buttons Board |
|
||||
| 5 | AMP_PLT (P/N 1413) 1/1 | Amplifier Board |
|
||||
| 6 | KEY_1402 1/1 | Keypad Board |
|
||||
| 7 | DSP_1408 1/1 | Display Board |
|
||||
|
||||
## Photo index ([`photos/`](photos/))
|
||||
|
||||
Original Signal filenames preserved here for provenance (taken 2026-07-15;
|
||||
amplifier board 2026-07-19).
|
||||
|
||||
| File | Original | Shows |
|
||||
|------|----------|-------|
|
||||
| `encoder-heds5700-control-assembly.jpeg` | `signal-2026-07-15-093011.jpeg` | HP HEDS-5700 I02 encoder (9515 A, Singapore) on a control-mechanism shaft |
|
||||
| `display-board-1408-front-digits.jpeg` | `signal-2026-07-15-093017.jpeg` | Display board digit side, 8× LTS313A (U2–U9), PCB etch NT 1-0 5094 |
|
||||
| `display-board-1408-back-driver.jpeg` | `signal-2026-07-15-093023.jpeg` | Display board driver side: ICM7228BIPI (date 9438), 16-pin IDC, "RIO EXTERNAL DISPLAY / P/N 140B REV. 1" |
|
||||
| `keypad-1402-front-keys.jpeg` | `signal-2026-07-15-093032.jpeg` | 16-key hex keypad module (keys 0–F), front bezel |
|
||||
| `keypad-board-1402-back-logic.jpeg` | `signal-2026-07-15-093038.jpeg` | Keypad board logic: DM74LS00N, 2× MM74HCT164N, SN74LS05N, CD74HCT165E, ADR. SEL. jumpers, "P/N 1402 REV. 1" |
|
||||
| `buttons-board-1401-solder-side.jpeg` | `signal-2026-07-15-093045.jpeg` | Buttons board solder side, PCB etch NT 1-0 3695 |
|
||||
| `buttons-board-1401-component-side.jpeg` | `signal-2026-07-15-093051.jpeg` | Buttons board: 2× CD74HCT165E, 74HCT00AP, 2× UCN5821A-type drivers, 74HCT164N, 8× lamp/button connectors, "P/N 1401 REV. 0" |
|
||||
| `rio-board-1407-solder-side.jpeg` | `signal-2026-07-15-093058.jpeg` | RIO board solder side, PCB etch NT 3-0 4996 |
|
||||
| `rio-board-1407-component-side.jpeg` | `signal-2026-07-15-093105.jpeg` | RIO board: TMP68HC11 QFP, GAL20V8A ("5764" sticker), AM27C512 EPROM, LH52B256 SRAM, 5× HCTL-2016 + OPT1–5, J11–J18 pod ports, status LEDs, "P/N 1407 REV. 3" |
|
||||
| `amp-board-1413-component-side.jpeg` | `signal-2026-07-19-134932.jpeg` | Quad Amplifier Board full view: Pyramid PB-150P module, corner crossovers, "P/N 1413 REV. 1", J5 pinout silkscreen |
|
||||
| `amp-board-1413-solder-side.jpeg` | `signal-2026-07-19-134926.jpeg` | Amplifier board solder side, PCB etch NT 1-0A 3695 |
|
||||
| `amp-board-1413-pyramid-inputs.jpeg` | `signal-2026-07-19-134921.jpeg` | Pyramid input face: HIGH INPUT connectors, LEVEL pots, LOW INPUT RCAs; front-corner crossovers |
|
||||
| `amp-board-1413-pyramid-outputs.jpeg` | `signal-2026-07-19-134915.jpeg` | Pyramid output face: SPEAKERS terminals 1CH/L–4CH/R, blade FUSE, B+12V/GND/REMOTE; check LEDs, J1–J4 pinout silkscreen |
|
||||
|
||||
## Cross-references
|
||||
|
||||
- [`../PROTOCOL.md`](../PROTOCOL.md) — the 9600 8N1 wire protocol this
|
||||
hardware speaks; its DTR reset pulse and 0x00–0x6F input map both have
|
||||
their physical explanation above.
|
||||
- [`../../rio-firmware/`](../../rio-firmware/) — v4.2 EPROM dump (from the
|
||||
AM27C512 at U10) and 68HC11 disassembly/patches.
|
||||
- [`../Win32RIO/`](../Win32RIO/) — FASA's original Windows driver for the
|
||||
same hardware.
|
||||
@@ -1,148 +0,0 @@
|
||||
# Display Board 1408 — outputs, and what the RIO shows on it
|
||||
|
||||
Complete definition of the RIO External Display board (P/N 1408 Rev. 1,
|
||||
`DSP_1408` sheet 7), from the schematic, the board photos, the GAL decode,
|
||||
and the firmware disassembly (`RIOv4_2.disasm.asm`).
|
||||
|
||||
## Outputs: light only — the board is electrically write-only
|
||||
|
||||
No signal leaves the board toward the RIO or anything else. J1 has no
|
||||
readback line, matching the GAL's write-only `DSP_SEL*` decode, and the
|
||||
PC↔RIO protocol has no display command — only the firmware writes it.
|
||||
|
||||
### 1. Eight multiplexed 7-segment digits (U2–U9, LTS313A, red, common-cathode)
|
||||
|
||||
The ICM7228B (U1) does all driving. `HEX/B/SHD` is strapped high via 2.7 K
|
||||
(R1): **hexadecimal decode mode, always enabled** — each digit renders 0–F
|
||||
plus a decimal point; no raw-segment access exists as built.
|
||||
|
||||
- **Segment bus** — U1 pins 16, 17, 20, 18, 21, 22, 23, 15 drive
|
||||
`SEGA`–`SEGG`, `SEGDP`, shared by all digits (digit pins A–G, DP =
|
||||
10, 9, 8, 5, 4, 2, 3, 7). Segments source the multiplexed current
|
||||
(~20 mA peak).
|
||||
- **Digit commons** — U1 outputs `DSP1`–`DSP8` (pins 4, 25, 3, 1, 26, 2,
|
||||
27, 24) each sink one digit's tied-together commons (CC1+CC2, pins 6+1).
|
||||
`DSP1`→U2 (leftmost) … `DSP8`→U9 (rightmost).
|
||||
- Multiplex scanning is internal to the ICM7228; the host only writes
|
||||
digit registers.
|
||||
|
||||
From the 68HC11 the digits are bytes at **`$A000`–`$A007`** (`AS0-2` =
|
||||
A0–A2, strobed by `DSP_SEL*` = `E & /R/W`-qualified, arriving as the
|
||||
7228's `/WR` on J1 pin 7). Data format per write: bits 0–3 = hex value
|
||||
(`MD[00:03]`→`ID0-3`), bit 4 = decimal point (`MD[04]`→`ID7`; DP lights
|
||||
when the bit is **low** — the firmware always sets it, so the DP is never
|
||||
used). **`$A000` = leftmost digit (U2), `$A007` = rightmost (U9)**; the
|
||||
firmware's render buffer (`$2038`, routine `$CB49`) fills right-to-left so
|
||||
multi-digit hex numbers read naturally.
|
||||
|
||||
### 2. Twelve passive monitor LEDs on the "IBM printer port" header (J2) — an input, not an output
|
||||
|
||||
J2 (26-pin IDC, standard LPT-ribbon mapping to a DB25) is electrically
|
||||
isolated from the display circuit; its `DD` nets touch nothing but LEDs
|
||||
and 1 K resistors referenced to the PC's own ground (`IBM_GND`). With a PC
|
||||
parallel port plugged in, the LEDs show: **D0–D7** = data bits (J2 pins
|
||||
3, 5, 7, 9, 11, 13, 15, 17), **D8** = `/STROBE` (pin 1), **D9–D11** =
|
||||
`/AUTOFD`, `/INIT`, `/SELECTIN` (pins 2, 6, 8). The LPT status returns are
|
||||
strapped to ground so a PC streams without waiting. It is a built-in
|
||||
parallel-port activity monitor / bench tool, powered entirely by the PC.
|
||||
|
||||
### 3. Nothing else
|
||||
|
||||
J1 (14-pin, from the RIO's EXTERNAL DISPLAY port) is all input:
|
||||
`MD[00:04]` on pins 2, 4, 6, 8, 10; `AS0-2` on 1, 3, 5; `BF_DSP_SEL*` on
|
||||
7; GND on 9, 11, 13; VCC on 12, 14.
|
||||
|
||||
## What the RIO firmware displays (v4.2)
|
||||
|
||||
All display writes go through `$CB49` (buffer render) or five canned
|
||||
patterns at `$CB7C–$CC52`, driven from four contexts: initialization,
|
||||
the built-in **test mode**, the **serial error/crash displays**, and
|
||||
the **CheckRequest self-test** (all below). During normal, healthy
|
||||
gameplay the display is static.
|
||||
|
||||
**Normal operation:** `F0000000` — written at cold boot (`$C068`) and warm
|
||||
re-init (`$C64B`), then left alone.
|
||||
|
||||
**Test mode** — entered by typing the keypad sequence **A, 9, E** on a
|
||||
cockpit keypad (`$C1CB` tracks the sequence at `$20F8`); entry sets flag
|
||||
`$2421`, shows `F1000000`, and notifies the PC (`TestModeChange`, cmd
|
||||
`0x8C` — see [PROTOCOL.md](../PROTOCOL.md)). Within test mode the next
|
||||
keypad digit selects a sub-test; **F** ends a sub-test (back to
|
||||
`F1000000`), **D** exits test mode (back to `F0000000`).
|
||||
|
||||
Display format convention: **left pair = test number, right = value**,
|
||||
zeros as filler; the decimal points are never used.
|
||||
|
||||
| Key | Display | Test |
|
||||
|-----|---------|------|
|
||||
| *(entry)* | `F1000000` | test-mode banner |
|
||||
| `0` | `00000000` | encoder-counter reset: presets the five 16-bit accumulators to `$FAFA` and re-reads all HCTL-2016s (`$C446`) |
|
||||
| `1` | `0100hhll` | axis test, channel 01 — counter at `$A028/$A038` |
|
||||
| `2` | `0200hhll` | axis test, channel 02 — `$A020/$A030` |
|
||||
| `3` | `0300hhll` | axis test, channel 03 — `$A022/$A032` |
|
||||
| `C` | `0C00hhll` | axis test, channel 0C — `$A024/$A034` |
|
||||
| `4` | `0400hhll` | axis test, channel 04 — `$A026/$A036` |
|
||||
| `5` | `050000nn` | button test — `nn` = index of the pressed input (scan at `$D004` over the pod input state at `$2152`), with lamp feedback |
|
||||
| `6` | `06000003` | lamp test, pattern 3 (`$D34A`) — right digit is the pattern code |
|
||||
| `7` | `07000001` | lamp test, pattern 1 (`$D370`) |
|
||||
| `8` | `080000kk` | keypad test — `kk` = keypad status byte `$204B`, refreshed while scanning (`$CC7E`) |
|
||||
|
||||
In the axis tests `hhll` is the **live 16-bit quadrature count in hex**
|
||||
(high:low = `$201D:$201E`, alternating with the raw latched bytes
|
||||
`$2026:$2025` as the loop runs) — spin the stick/throttle/pedal encoder
|
||||
and watch the number move. The channel IDs are literally the keypad keys
|
||||
that start them, which is why the fourth axis reads `0C`.
|
||||
|
||||
## The CheckRequest self-test leftover: `04000000`
|
||||
|
||||
The host `CheckRequest` (`0x80`) triggers a full self-test in the
|
||||
handler at `$C5A6`: it sets the same display flag test mode uses
|
||||
(`$2421`), brackets itself with `TestModeChange 0x8C` notifications,
|
||||
flashes a lamp pattern, scans the pods, and sweeps the five encoder
|
||||
channels — rendering each channel's `cc00hhll` frame as it goes. It
|
||||
clears the flag on exit but **never repaints the display**, so after
|
||||
every status check the digits are left showing the last frame:
|
||||
channel 4, usually `04000000` with idle encoders. It is a stale
|
||||
snapshot (axis movement does not update it — bench-confirmed
|
||||
2026-07-19), purely cosmetic, and the board keeps operating normally.
|
||||
Any reset repaints `F0000000`.
|
||||
|
||||
## Error displays (the third use)
|
||||
|
||||
Two more display writers live in the serial machinery — the display is
|
||||
also the RIO's **error annunciator**:
|
||||
|
||||
- **`E0` serial-diagnostics readout** (`$D5F2`) — `E0xxyyzz` where
|
||||
`xx` = TX-ring overflow count (`$3187`, outbound byte dropped because
|
||||
the ring was full, `$D66A`), `yy` = give-up count (`$3184`, `$D96A` —
|
||||
reply cycles whose 5 timeout retransmits all went unACKed), `zz` =
|
||||
reply-teardown count (`$3185`, `$DA2F` — any reply cycle that needed
|
||||
at least one retransmission; the counter behind a lone `E0000001`).
|
||||
Semantics bench-confirmed 2026-07-19: display read `E0000105` after
|
||||
one give-up + five teardowns. Each increment site immediately re-renders,
|
||||
so the display live-updates as serial trouble accumulates. If a
|
||||
cockpit's display reads `E0…` instead of `F0000000`, the serial link
|
||||
has been dropping or retrying. Note there is **no threshold in stock
|
||||
firmware** — the first event paints `E0000001` permanently; the
|
||||
**RIO 4.3** firmware (`make_patch.py --e0thresh`, see
|
||||
[RIOv4_2-ANALYSIS.md](../../rio-firmware/RIOv4_2-ANALYSIS.md)) keeps
|
||||
the display quiet until a counter reaches 5, and repaints honestly
|
||||
after every check self-test.
|
||||
- **`dEAd` crash screen** (`$DAB8`, loop at `$DAB2`) — the TX-ISR ring
|
||||
drain (`$D8C2`) looks each outgoing command byte up in an 8-entry
|
||||
dispatch table at `$3158`; an unmatched byte means internal state
|
||||
corruption, and the firmware jumps to an infinite loop that renders
|
||||
`#$DE #$AD` → **`dEAd`** followed by RAM bytes `$0000/$0001` (nothing
|
||||
in this build writes them — vestigial debug data). The board is dead
|
||||
until reset; there is no watchdog to rescue it (the MAX690's WDI is
|
||||
not connected to the CPU and no strobe code exists in the firmware).
|
||||
|
||||
## Cross-references
|
||||
|
||||
- [README.md](README.md) — board family, photos, schematic page map
|
||||
- [gal/README.md](gal/README.md) — `DSP_SEL*` decode (`$A000-$A00F`,
|
||||
write-only) recovered from the GAL fuse map
|
||||
- [`rio-firmware/RIOv4_2.disasm.asm`](../../rio-firmware/RIOv4_2.disasm.asm)
|
||||
— display routines `$CB20/$CB36/$CB49` (buffer render), `$CB7C` (`F0`),
|
||||
`$CBA7` (`F1`), `$CBD2`/`$CBFD` (lamp-test banners), `$CC28` (zeros),
|
||||
test dispatcher `$C241`, entry-sequence tracker `$C1CB`
|
||||
@@ -1,38 +0,0 @@
|
||||
|
||||
|
||||
Device : GAL20V8A
|
||||
|
||||
Created By: http://www.autoelectric.cn
|
||||
|
||||
Date : 2023-03-09 19:02
|
||||
|
||||
*QP24
|
||||
|
||||
*QF2706
|
||||
|
||||
*G0
|
||||
|
||||
*F0
|
||||
|
||||
*L00000 11111111111111111111111111111111
|
||||
|
||||
*L00032 11111111111111011111111111111111
|
||||
|
||||
*L00064 11111111110111110000000000000000
|
||||
|
||||
*L00320 11111111111111111111111111111111
|
||||
|
||||
*L00352 11111111111111111111111111111111
|
||||
|
||||
*L00384 11111110111111110000000000000000
|
||||
|
||||
*L00640 11111111111111111111111111111111
|
||||
|
||||
*L00672 11111111100101111011101110111011
|
||||
|
||||
*L00704 10111011101101110000000000000000
|
||||
|
||||
*L00960 11111111111111111111111111111111
|
||||
|
||||
*L00992 11111111111001111111111111111111
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
# U7 GAL20V8A decode — the RIO memory map, recovered
|
||||
|
||||
[`GAL20v8a_5764.JED`](GAL20v8a_5764.JED) is the fuse map read out of **U7**
|
||||
on the RIO board (P/N 1407 Rev. 3) — the chip the schematic draws as a
|
||||
"20L8" memory decoder. Decoding the fuses (see below for how) yields the
|
||||
complete address decode, which cross-checks perfectly against both the
|
||||
schematic net names and the firmware disassembly.
|
||||
|
||||
Three provenance details line up: the chip's hand-written sticker reads
|
||||
**"5764"** = the JED fuse checksum (`*C5764`); the fuse map is in **complex
|
||||
(20L8-emulation) mode** exactly as the schematic symbol suggests; and the
|
||||
programmed electronic signature (UES) literally reads **`U7`** — VWE
|
||||
signed the chip with its own reference designator.
|
||||
|
||||
## Recovered equations
|
||||
|
||||
Inputs: `A[15:3]` (A3 arrives via the pin-16 feedback path), `E`, `R/W`.
|
||||
All outputs are combinatorial, always enabled.
|
||||
|
||||
```
|
||||
/ROM_SEL = A15 & A14 ; pin 15
|
||||
/RAM_SEL = /A15 & A14 & E | /A15 & A13 & E | A15 & /A14 & /A13 & E ; pin 19
|
||||
/PH_SEL = A15 & /A14 & A13 & /A12../A6 & A5 ; pin 20
|
||||
/BRD_SEL = A15 & /A14 & A13 & /A12../A6 & /A5 & A4 & E & /RW ; pin 18
|
||||
/DSP_SEL = A15 & /A14 & A13 & /A12../A6 & /A5 & /A4 & E & /RW ; pin 17
|
||||
/OE = E & RW ; pin 22
|
||||
NOT_A3 = /A3 ; pin 21
|
||||
```
|
||||
|
||||
(`/A12../A6` abbreviates the run `/A12 & /A11 & /A10 & /A9 & /A8 & /A7 & /A6`;
|
||||
full machine output comes from `galdecode.py`, below.)
|
||||
|
||||
## The memory map
|
||||
|
||||
| Address | Select | Device | Notes |
|
||||
|---------|--------|--------|-------|
|
||||
| `$0000-$1FFF` | *(none)* | 68HC11 internal | RAM `$0000-$00FF`, registers `$1000-$103F` |
|
||||
| `$2000-$9FFF` | `RAM_SEL*` | LH52B256 32K SRAM | E-qualified, read/write; exactly 32K |
|
||||
| `$A000-$A00F` | `DSP_SEL*` | External-display write port | write-only (`E & /RW`) |
|
||||
| `$A010-$A01F` | `BRD_SEL` | Pod-bus control latch (74HC574, sheet 3) | write-only (`E & /RW`) |
|
||||
| `$A020-$A03F` | `PH_SEL*` | HCTL-2016 encoder counters ×5 | `A3` picks high/low count byte via `NOT_A3`, `A[2:0]` picks chip via the 74LS138s |
|
||||
| `$C000-$FFFF` | `ROM_SEL*` | 27C512 EPROM | top 16K of the 64K device |
|
||||
|
||||
`OE*` (`E & R/W`) is the shared read-side output enable for
|
||||
EPROM/SRAM/counters; the write-only decodes double as write strobes.
|
||||
|
||||
**Firmware cross-check** — [`RIOv4_2.disasm.asm`](../../../rio-firmware/RIOv4_2.disasm.asm)
|
||||
touches precisely these windows and nothing else in `$A0xx`: digit writes to
|
||||
`$A000-$A007`, pod-bus latch writes to `$A010`, and encoder reads at
|
||||
`$A021/$A023/$A025/$A027/$A029` + `$A028/$A030-$A038` (five chips × two count
|
||||
bytes). It also explains the EPROM dump being `FF` below `$C000`: with
|
||||
`ROM_SEL* = A15·A14` and all 16 address lines wired, only the top quarter of
|
||||
the 27C512 is ever addressed.
|
||||
|
||||
## How the decode was done
|
||||
|
||||
[`galdecode.py`](galdecode.py) parses the JED and applies the GAL20V8
|
||||
complex-mode fuse geometry — row/column-to-pin tables, product-term-disable
|
||||
fuses, XOR polarity, SYN/AC0 mode bits — taken from MAME's `jedutil.cpp`
|
||||
(the reference implementation for PLD fuse maps). Pin-to-net names come
|
||||
from schematic sheet 1 (`RIO_1407`) and live in
|
||||
[`u7-pin-names.txt`](u7-pin-names.txt). Reproduce with:
|
||||
|
||||
```sh
|
||||
py docs/hardware/gal/galdecode.py docs/hardware/gal/GAL20v8a_5764.JED docs/hardware/gal/u7-pin-names.txt
|
||||
```
|
||||
|
||||
The JED itself was read from the chip on 2023-03-09 (header says an
|
||||
autoelectric.cn / XGecu-style programmer). Note `*F0` in the header: unlisted
|
||||
fuse rows default to 0, i.e. unused product terms are all-connected
|
||||
(always-false) rather than blown — the decoder handles this.
|
||||
@@ -1,91 +0,0 @@
|
||||
"""Decode a GAL20V8 JED (complex mode) into boolean equations.
|
||||
|
||||
Fuse-map semantics taken from MAME jedutil.cpp (config_gal20v8_pins,
|
||||
get_pin_fuse_state, is_gal20v8_product_term_enabled,
|
||||
does_output_enable_fuse_row_allow_output).
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
JEDPATH = sys.argv[1] if len(sys.argv) > 1 else r"C:\VWE\riojoy\docs\hardware\gal\GAL20v8a_5764.JED"
|
||||
|
||||
QF = 2706
|
||||
fuses = [0] * QF # *F0 default
|
||||
text = open(JEDPATH).read()
|
||||
for m in re.finditer(r"\*L(\d+)\s+([01]+)", text):
|
||||
start = int(m.group(1))
|
||||
for i, ch in enumerate(m.group(2)):
|
||||
fuses[start + i] = int(ch)
|
||||
|
||||
SYN, AC0 = fuses[2704], fuses[2705]
|
||||
mode = {(1, 1): "complex", (1, 0): "simple", (0, 1): "registered", (0, 0): "registered"}[(SYN, AC0)]
|
||||
print(f"SYN={SYN} AC0={AC0} -> {mode} mode")
|
||||
assert mode == "complex", "decoder only handles complex mode"
|
||||
|
||||
# UES signature 2568-2631, 8 bytes MSB-first
|
||||
ues = bytes(
|
||||
int("".join(map(str, fuses[2568 + 8 * i : 2576 + 8 * i])), 2) for i in range(8)
|
||||
)
|
||||
print("UES signature:", ues, "->", ues.decode("ascii", "replace"))
|
||||
|
||||
# complex-mode column map: pin -> (lowfusecolumn, highfusecolumn)
|
||||
COLS = {1: (3, 2), 2: (1, 0), 3: (5, 4), 4: (9, 8), 5: (13, 12), 6: (17, 16),
|
||||
7: (21, 20), 8: (25, 24), 9: (29, 28), 10: (33, 32), 11: (37, 36),
|
||||
13: (39, 38), 14: (35, 34), 16: (31, 30), 17: (27, 26), 18: (23, 22),
|
||||
19: (19, 18), 20: (15, 14), 21: (11, 10), 23: (7, 6)}
|
||||
# complex-mode rows: pin -> (OE row fuse, first PT fuse, last PT fuse)
|
||||
ROWS = {22: (0, 40, 280), 21: (320, 360, 600), 20: (640, 680, 920),
|
||||
19: (960, 1000, 1240), 18: (1280, 1320, 1560), 17: (1600, 1640, 1880),
|
||||
16: (1920, 1960, 2200), 15: (2240, 2280, 2520)}
|
||||
XOR = {15: 2567, 16: 2566, 17: 2565, 18: 2564, 19: 2563, 20: 2562, 21: 2561, 22: 2560}
|
||||
|
||||
# Optional net names for pins, filled after reading the schematic:
|
||||
NAMES = {}
|
||||
if len(sys.argv) > 2:
|
||||
for kv in open(sys.argv[2]):
|
||||
kv = kv.strip()
|
||||
if kv and not kv.startswith("#"):
|
||||
p, n = kv.split(None, 1)
|
||||
NAMES[int(p)] = n
|
||||
|
||||
def name(pin):
|
||||
return NAMES.get(pin, f"pin{pin}")
|
||||
|
||||
def pt_enabled(fuserow):
|
||||
return fuses[2640 + fuserow // 40] == 1
|
||||
|
||||
def term(fuserow):
|
||||
"""Return list of literals, or None if term is always-false, '1' if always-true."""
|
||||
lits = []
|
||||
for pin in sorted(COLS):
|
||||
lo, hi = COLS[pin]
|
||||
l, h = fuses[fuserow + lo], fuses[fuserow + hi]
|
||||
if l == 0 and h == 1:
|
||||
lits.append("/" + name(pin))
|
||||
elif l == 1 and h == 0:
|
||||
lits.append(name(pin))
|
||||
elif l == 0 and h == 0:
|
||||
return None # both intact -> always false
|
||||
return lits if lits else ["1"]
|
||||
|
||||
for pin in sorted(ROWS, reverse=True):
|
||||
oerow, first, last = ROWS[pin]
|
||||
oe = term(oerow) if pt_enabled(oerow) else None
|
||||
if oe is None:
|
||||
print(f"\npin {pin} ({name(pin)}): OE never true -> used as INPUT")
|
||||
continue
|
||||
pol = "active-HIGH" if fuses[XOR[pin]] else "active-LOW"
|
||||
pts = []
|
||||
for f in range(first, last + 40, 40):
|
||||
if not pt_enabled(f):
|
||||
continue
|
||||
t = term(f)
|
||||
if t is not None:
|
||||
pts.append(" & ".join(t))
|
||||
lhs = name(pin) if fuses[XOR[pin]] else "/" + name(pin)
|
||||
oestr = " & ".join(oe)
|
||||
print(f"\npin {pin} ({name(pin)}): {pol}, OE = {oestr}")
|
||||
if pts:
|
||||
print(f" {lhs} = " + ("\n" + " " * (len(lhs) + 5) + "| ").join(pts))
|
||||
else:
|
||||
print(f" {lhs} = 0 (no active product terms)")
|
||||
@@ -1,23 +0,0 @@
|
||||
# U7 (GAL20V8A, "20L8" on schematic RIO_1407 sheet 1) pin -> net name
|
||||
1 A15
|
||||
2 A14
|
||||
3 A13
|
||||
4 A12
|
||||
5 A11
|
||||
6 A10
|
||||
7 A9
|
||||
8 A8
|
||||
9 A7
|
||||
10 A6
|
||||
11 A5
|
||||
13 A4
|
||||
14 E
|
||||
23 RW
|
||||
16 A3
|
||||
15 ROM_SEL
|
||||
22 OE
|
||||
21 NOT_A3
|
||||
20 PH_SEL
|
||||
19 RAM_SEL
|
||||
18 BRD_SEL
|
||||
17 DSP_SEL
|
||||
|
Before Width: | Height: | Size: 738 KiB |
|
Before Width: | Height: | Size: 637 KiB |
|
Before Width: | Height: | Size: 647 KiB |
|
Before Width: | Height: | Size: 587 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 858 KiB |
|
Before Width: | Height: | Size: 844 KiB |
|
Before Width: | Height: | Size: 712 KiB |
|
Before Width: | Height: | Size: 614 KiB |
|
Before Width: | Height: | Size: 828 KiB |
|
Before Width: | Height: | Size: 987 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 916 KiB |
@@ -1,100 +0,0 @@
|
||||
# RIO board firmware
|
||||
|
||||
> **Pod owner, not an engineer?** Read
|
||||
> [wedge-explained.md](wedge-explained.md) — a plain-language account of
|
||||
> the "stick died mid-battle" bug that RIO 4.3 fixes.
|
||||
|
||||
- **`RIOv4_2.bin`** — RIO cockpit I/O board firmware **v4.2**, dumped
|
||||
2026-07-04 from one of our own boards' EPROM: an **AMD AM27C512-150**
|
||||
(64K x 8 UV EPROM, 150ns — the image fills it exactly).
|
||||
sha256 `60a88718835c654b6135dbec7721c40ef99dca07df2ad4b57eedeb24037a5f73`.
|
||||
For the eventual patched burn: a pin-compatible Winbond W27C512
|
||||
(electrically erasable, TL866-friendly) drops straight into the socket;
|
||||
the original AMD chip gets labeled and preserved unmodified.
|
||||
|
||||
## First-look analysis (from the image alone, confirmed on hardware)
|
||||
|
||||
- MCU: **Toshiba TMP68HC11** (read off the chip; the code fingerprint
|
||||
agrees — 6800-family opcodes with writes into the 68HC11 internal
|
||||
register block at `$10xx`).
|
||||
- Memory map: image is FF up to **0xC000**; 16KB of code occupies
|
||||
`$C000-$FFFF` (EPROM mapped at the top of the HC11 address space).
|
||||
- Startup at `$C000`: `SEI; LDS #$8000; STAA $1024 (TMSK2);
|
||||
STAA $1022 (TMSK1); ...` then a long `JSR` init chain — textbook HC11
|
||||
bring-up.
|
||||
- Vector table (`$FFD6-$FFFF`, big-endian):
|
||||
- `$FFFE` RESET → `$C000`
|
||||
- **`$FFD6` SCI (serial) → `$D630`** — the entry point of the board's
|
||||
receive/protocol interrupt handler. The suspected board-side
|
||||
DISABLE_AND_DIE-style wedge (see RIO-NOTES.md: the board mirrors the
|
||||
game's PCSPAK state machine, and mash-stress leaves the reply path
|
||||
dead while the button/event path stays alive) is reachable from here.
|
||||
- `$FFE4` → `$C1B2`, `$FFE6` → `$C18E` (timer output-compares); most
|
||||
other vectors → `$DB07..$DB3D` stubs.
|
||||
|
||||
## Why this exists
|
||||
|
||||
The remaining RIO reliability issue is board-side: under button-mash
|
||||
stress the board's reply/analog state machine wedges (RX dead, TX alive;
|
||||
a button press or power cycle revives it), reproduced identically on two
|
||||
different USB serial adapters. The game-side half of the protocol was
|
||||
binary-patched for tolerance (BTL4OPT patches v2-v4); the board firmware
|
||||
is the other half. Plan (RIO-NOTES.md "Board firmware patch plan"):
|
||||
disassemble as 68HC11 from `$C000` with the vector entries as roots, find
|
||||
the SCI state machine (protocol constants FC=ACK FD=NAK FE=RESTART
|
||||
FF=IDLE, idle-reload-4 patterns), patch the early-ACK/error wedge path or
|
||||
widen its window, burn a new EPROM, keep this original safe.
|
||||
|
||||
## RIO 4.3 — `RIOv4_3.bin` (current production firmware)
|
||||
|
||||
**Christened 2026-07-19** after full on-hardware certification. Built
|
||||
by `make_patch.py --e0thresh=5 --checkrepaint --reportversion=4.3
|
||||
RIOv4_2.bin RIOv4_3.bin`; sha256
|
||||
`6d67a2fc77130b601fdb0ac02042dd4d0a98ac7e29a8077d588987a81073939c`
|
||||
(69 bytes changed vs stock). 9600 baud, native-game compatible. On the
|
||||
stock v4.2 base it carries:
|
||||
|
||||
1. **Reply-latch wedge fix** (edits 1-2) — bench-certified.
|
||||
2. **E0-display threshold, N=5** (edit 5) — certified: display holds
|
||||
`F0000000` through sub-threshold error events, flips to the live
|
||||
`E0` readout at the 5th teardown (`E0000105` on the bench).
|
||||
3. **Check self-test display repaint** (edit 6) — certified both ways:
|
||||
version+check leaves `F0000000` when healthy, and re-renders the E0
|
||||
readout when counters are over threshold (`E0000305` on the bench)
|
||||
instead of stock's stale `04000000`.
|
||||
4. **Version bump** (edit 7, `--reportversion=4.3`) — the VersionReply
|
||||
now announces **4.3** (one operand byte at `$C6FF`). Nothing
|
||||
host-side validates the value (native games verified not to check
|
||||
it). The certification runs below were made on the pre-edit-7 bytes;
|
||||
the only delta is the version literal. Chips burned before this edit
|
||||
report 4.2 — re-burn to announce 4.3.
|
||||
|
||||
Certification runs (all in `testlogs/` + RIOv4_2-ANALYSIS.md): two
|
||||
`--e0test` acceptance passes (display verified by eye at each step) and
|
||||
a 120 s mash at **549 presses/min sustained** — zero wedges, board
|
||||
counters flat, zero NAK, 247 resends all healed
|
||||
(`riomash-rio43rc1-9600-20260719-164901.log`).
|
||||
|
||||
## FastRIO 4.3 — `RIOv4_3_fastrio.bin`
|
||||
|
||||
Same generation for the RIOJoy-only cockpits: `make_patch.py
|
||||
--baud31250 --widen-ackwait --e0thresh=5 --checkrepaint
|
||||
--reportversion=4.3 RIOv4_2.bin RIOv4_3_fastrio.bin`; sha256
|
||||
`ee807831fe0df9dc147eb5cdc3302672ae21f6ff349da30babea27fc863234b9`
|
||||
(71 bytes vs stock — differs from `RIOv4_3.bin` in exactly two bytes:
|
||||
`$D62B` baud `30→02` and `$D9E7` ACK-wait `04→28`). 31250 baud,
|
||||
FTDI-class adapter required, **not** native-game compatible.
|
||||
Disassembly-verified; run the acceptance ladder on the FastRIO cockpit
|
||||
before deploying (`--e0test COM1` auto-probes 31250, then a mash
|
||||
spot-check at `--baud 31250`).
|
||||
|
||||
## Archive
|
||||
|
||||
All prior generations live in [`archive/`](archive/) with their
|
||||
disassemblies: `RIOv4_2_patched` (wedge fix only), `_31250`/`_31250v2`
|
||||
(FastRIO speed builds), `_62500`/`_125000` (speed-ladder science runs),
|
||||
and the `_e0t5` pair (threshold without check-repaint). Bench verdicts
|
||||
and per-edit history: RIOv4_2-ANALYSIS.md. The pristine dump
|
||||
`RIOv4_2.bin` stays at top level — it is the source every patch builds
|
||||
from. RIO 4.3 is in the cockpit socket as of 2026-07-19; the original
|
||||
AMD chip and the retired e0t5 burn are labeled and preserved.
|
||||
@@ -1,559 +0,0 @@
|
||||
# RIO v4.2 firmware — protocol wedge analysis
|
||||
|
||||
Reverse-engineering of `RIOv4_2.bin` (Toshiba TMP68HC11, AM27C512) to find
|
||||
the board-side cause of the "reply path wedges under stress, button-press
|
||||
revives it" fault. Disassembly by `disasm_6811.py` →
|
||||
`RIOv4_2.disasm.asm`. **Research only** — the fix below is proposed, not
|
||||
yet burned or tested (no spare EPROM on hand). Validate on hardware with
|
||||
the `RIO_TAP` mash test before trusting.
|
||||
|
||||
Addresses are CPU = file offset (EPROM at `$C000-$FFFF`; reset `$FFFE`→
|
||||
`$C000`). RAM lives at `$20xx-$31xx`.
|
||||
|
||||
## How the serial protocol is structured
|
||||
|
||||
- **SCI interrupt** (`$FFD6`→`$D630`): `JSR $D634; RTI`. `$D634` runs BOTH
|
||||
workers every interrupt: `JSR $D6EA` (RX) then `JSR $D887` (TX). So the
|
||||
transmitter is poked after every received byte, not only on TX-empty
|
||||
interrupts.
|
||||
- **RX ISR** `$D6EA`: reads SCSR/SCDR, stores the byte at `$3172`, then
|
||||
`LDX $292F; JMP $00,X` — dispatches through a **state-handler pointer**
|
||||
at `$292F`. Handlers classify bytes (`$D717`: `FE`=RESTART, `FF`=IDLE,
|
||||
`FC`/`FD`=game ACK/NAK, `$82`=analog request, table lookup at `$3144`),
|
||||
accumulate the body + checksum (`AND $7F`), and on a complete packet run
|
||||
the ACK/NAK decision at `$D81F`.
|
||||
- **TX ISR** `$D887`: if TDRE, send a pending ACK (`$316F`→`$FC`) or NAK
|
||||
(`$3170`→`$FD`), else dispatch through the TX state pointer `$2D3B`
|
||||
(`$D8C2` ring-drain → `$D90E` reply/retry machine). When idle it disarms
|
||||
the TX interrupt (SCCR2 `#$2C`, TIE off) at `$D918`; the enqueue routine
|
||||
`$D63B` re-arms it (SCCR2 `#$AC`, TIE on) at `$D664`.
|
||||
|
||||
## The wedge: an orphaned "reply-in-progress" latch (`$2521`)
|
||||
|
||||
`$2521` = "an analog reply is in progress." The analog-request handler
|
||||
gates on it:
|
||||
|
||||
```
|
||||
D74F CMPB #$82 ; analog request from the game
|
||||
D753 LDAA #$01
|
||||
D755 STAA $2520 ; arm reply generation
|
||||
D758 TST $2521 ; already replying?
|
||||
D75B BNE $D77A ; YES -> D77A: CLR $2520, drop this request
|
||||
```
|
||||
|
||||
So while `$2521` is set, **every analog request is silently dropped**.
|
||||
The latch is set when a reply is generated:
|
||||
|
||||
```
|
||||
D847 JSR $C5EC ; build the analog reply
|
||||
D84C STAA $2521 ; reply-in-progress = 1
|
||||
```
|
||||
|
||||
and is cleared in only three places: power-on init (`$C0A3`), a host
|
||||
reset/init command handler (`$C686`), and the reply **success** teardown
|
||||
(`$DA00`). The success teardown is reached at `$D9C1` when the game ACKs
|
||||
the reply, and clears the latch — but only conditionally:
|
||||
|
||||
```
|
||||
DA21 LDAA $2522 ; did the $87 reply byte actually start sending?
|
||||
DA24 CMPA #$01
|
||||
DA26 BNE $DA2E ; if not, skip the clears <-- fragile
|
||||
DA28 CLR $2521
|
||||
DA2B CLR $2522
|
||||
```
|
||||
|
||||
`$2521` is set the instant the reply is *generated* (`$D84C`), but `$2522`
|
||||
is set only once the `$87` command byte *starts transmitting* (`$D8FD`).
|
||||
|
||||
**The leak** is the retry-exhausted give-up path, which is *separate* from
|
||||
the success teardown. When the game fails to ACK a reply, `$D90E`/`$D9BE`
|
||||
retries up to 4 times, then gives up:
|
||||
|
||||
```
|
||||
D9D5 LDAB #$FE ; give up: send RESTART
|
||||
D9D7 STAB $102F ; SCDR
|
||||
D9DA INC $317A
|
||||
D9DD JMP $DA2F ; teardown -- but DA2F never touches $2521
|
||||
```
|
||||
|
||||
`$DA2F` resets the TX pointers and calls `$D5F2` (a debug-counter
|
||||
formatter that does *not* clear the latch), then returns. **`$2521` is
|
||||
left set forever.** From then on every `$82` analog request is dropped at
|
||||
`$D758` → the board is mute to analog while its RX/event path stays fully
|
||||
alive.
|
||||
|
||||
### Why a button press / new game revives it
|
||||
|
||||
The only mid-run code that clears `$2521` is the host command handler at
|
||||
`$C669-$C689` (it clears `$2520`/`$2521`/`$2522` plus a raft of state).
|
||||
That runs for a host-level reset/init command — exactly what the game
|
||||
sends at game-start / on the player's opening button actions. Mid-mission
|
||||
button-mashing sends no such command, so the leaked latch stays stuck
|
||||
until the next game-start reset. This matches the field observation
|
||||
precisely: the board goes mute under stress and only a new-game/button
|
||||
resync brings analog back.
|
||||
|
||||
### Why mash stress triggers it
|
||||
|
||||
Button-event traffic floods the link while the board is mid-analog-reply;
|
||||
the reply's ACKs collide/drop, the 4-retry budget exhausts, and the
|
||||
give-up path (`$DA2F`) fires — leaking the latch. Light traffic rarely
|
||||
exhausts the retries, so it's a stress-only fault. Two different USB
|
||||
adapters showed the identical stall because the defect is in the board,
|
||||
not the transport — consistent with this being firmware, not timing.
|
||||
|
||||
## Proposed fix (minimal, in-place; UNTESTED)
|
||||
|
||||
Clear `$2521` on *every* reply teardown, not just the `$2522`-gated
|
||||
success path. Two edits, no code-size change, 8 KB of free ROM exists at
|
||||
`$DFF0-$FFBF` for the stub:
|
||||
|
||||
1. **Give-up path** — redirect its teardown through a stub that clears the
|
||||
latch first. At `$D9DD` change `JMP $DA2F` (`7E DA 2F`) →
|
||||
`JMP $DFF0` (`7E DF F0`), and place at `$DFF0`:
|
||||
```
|
||||
DFF0 7F 25 21 CLR $2521
|
||||
DFF3 7F 25 22 CLR $2522
|
||||
DFF6 7E DA 2F JMP $DA2F
|
||||
```
|
||||
2. **Success path** — make the clear unconditional (belt-and-suspenders,
|
||||
covers an abort before `$87` is sent). Replace `$DA21-$DA2D` (13 bytes)
|
||||
in place:
|
||||
```
|
||||
DA21 7F 25 21 CLR $2521
|
||||
DA24 7F 25 22 CLR $2522
|
||||
DA27 01 01 01 01 01 01 01 (NOP x7)
|
||||
DA2E 39 RTS (unchanged)
|
||||
```
|
||||
|
||||
Rationale: `$2521` means "a reply is in progress"; any path that tears
|
||||
down reply state must release it. There is no case where you reset the
|
||||
reply machine yet want the latch to stay set, so unconditional clearing is
|
||||
safe. This is the board-side analogue of the game-side "make collisions
|
||||
harmless" patches (BTL4OPT v2-v4) — instead of widening a timing window it
|
||||
removes the latch leak entirely.
|
||||
|
||||
### Patched binary — built & statically verified (2026-07-04)
|
||||
|
||||
`make_patch.py` applies both edits to `RIOv4_2.bin` (asserting the exact
|
||||
original bytes at each site first) → **`RIOv4_2_patched.bin`**
|
||||
(sha256 `3fc8170caf60e2580641724ff995176c93c4f2e706f31487beded8233142493f`,
|
||||
23 bytes changed). Re-disassembling it (`RIOv4_2_patched.disasm.asm`) and
|
||||
diffing against the original confirms the change is confined to exactly
|
||||
three regions with no downstream desync:
|
||||
- `$D9DD` `JMP $DA2F` → `JMP $DFF0`
|
||||
- `$DFF0` new stub: `CLR $2521 ; CLR $2522 ; JMP $DA2F`
|
||||
- `$DA21` `CLR $2521 ; CLR $2522 ; NOP×7` (RTS at `$DA2E` intact)
|
||||
|
||||
Flash `RIOv4_2_patched.bin` directly to the W27C512 (DIP-28). This is
|
||||
static verification only; dynamic proof still needs the burned chip.
|
||||
|
||||
### 31250-baud variant (2026-07-17) — `RIOv4_2_patched_31250.bin`
|
||||
|
||||
`make_patch.py --baud31250` adds **one byte** to the wedge patch: the SCI
|
||||
init at `$D62A` (`LDAA #$30 ; STAA $102B BAUD`) becomes `LDAA #$02`.
|
||||
BAUD `$30` = prescale ÷13, divider ÷1 → 2 MHz E-clock / (16·13) = 9615
|
||||
("9600"); `$02` = prescale ÷1, divider ÷4 → 2 MHz / (16·4) = **31250
|
||||
exactly** (3.3× faster; analog exchange ~15 ms → ~4.6 ms; 96-lamp burst
|
||||
~0.4 s → ~0.12 s; every collision window shrinks 3×). The BAUD write
|
||||
confirms the 8 MHz crystal / 2 MHz E-clock — 19200/38400 are unreachable.
|
||||
|
||||
sha256 `9f866cf353d04906e1d3e3847b6375eaae251c987b656f68f093e61ba1bd545b`,
|
||||
24 bytes changed (the 23 wedge bytes + `$D62B: 30→02`). Re-disassembly
|
||||
diff vs the classic patched image shows exactly the one operand line.
|
||||
|
||||
**Caveats:** 31250 is non-standard — FTDI-class USB adapters make it
|
||||
exactly, classic 16550 UARTs cannot; the **native games still speak
|
||||
9600**, so a 31250 chip is bench/RIOJoy-only until Firestorm/Red Planet
|
||||
get BTL4OPT-style baud patches; and the 2 MHz HC11's RX ISR has ~640
|
||||
cycles/byte at this rate (fine) — do NOT be tempted to `$01`/62500 or
|
||||
`$00`/125 k without cycle-counting the `$D630` ISR worst path first.
|
||||
PC side: `SerialPortTransport` takes a baud parameter and the
|
||||
monitor/mash tools accept `--baud 31250`. Validate the wedge patch at
|
||||
9600 FIRST (one variable at a time), then A/B this chip with
|
||||
`--mash COM1 300 --label patched-31250 --baud 31250`.
|
||||
|
||||
### Validation plan (when a chip is available)
|
||||
Burn the two edits to a W27C512, socket it (preserve the original AMD
|
||||
chip), then run the `RIO_TAP` two-handed 8-button mash test. Expect: no
|
||||
permanent analog mute; any collision self-recovers without a game-start
|
||||
reset. Compare dropout counts to the 2026-07-03/04 baseline taps.
|
||||
|
||||
**Instrumented harness** (2026-07-17): the mash protocol is mechanized in
|
||||
`tools/RioSerialMonitor` —
|
||||
|
||||
dotnet run --project tools/RioSerialMonitor -- --mash COM1 300 --label patched
|
||||
|
||||
It disables the app's own >5 s reset-recovery (a wedge must stay observable),
|
||||
echoes lamps on every press (lamp/reply collisions are the trigger), and logs
|
||||
to `riomash-<label>-<stamp>.log`: analog-gap histogram + longest gaps, WEDGE
|
||||
events with beep/banner and a self-recovered vs button-revived classification
|
||||
(button within 300 ms of resume = the unpatched revival signature), and the
|
||||
board's own RestartCount/AbandonCount/FullBufferCount before/after with the
|
||||
delta (7-bit wrap-aware). Exit 0 = no wedge, 1 = wedge seen. Run once with
|
||||
`--label baseline` on the original chip (control: prove the test still bites),
|
||||
then `--label patched`; the fixed-layout summary blocks diff directly.
|
||||
`--mash --selftest` runs a scripted in-memory board that fakes a
|
||||
button-revived wedge — use it to sanity-check the alarm before a session.
|
||||
|
||||
## Firmware memory map (as decoded so far)
|
||||
|
||||
| addr | meaning |
|
||||
|---|---|
|
||||
| `$292F` | RX state-handler pointer (`JMP $00,X` dispatch) |
|
||||
| `$2D3B` | TX state-handler pointer |
|
||||
| `$2D34/$36/$38` | TX ring read/write/aux pointers (ring `$2932-$2D31`) |
|
||||
| `$2520` | reply gate (analog request pending) |
|
||||
| `$2521` | **reply-in-progress latch — the wedge** |
|
||||
| `$2522` | `$87` analog-reply-byte-sent flag |
|
||||
| `$316C/$6D` | game ACK / NAK received |
|
||||
| `$316E` | unknown-command seen |
|
||||
| `$316F/$70` | ACK / NAK pending to send (→ TX ISR) |
|
||||
| `$3172` | last received byte |
|
||||
| `$3173/$74/$75` | ACK/NAK/wait retry counters (limit 4) |
|
||||
| `$317A/$7B` | RESTART / IDLE keep-alive counters |
|
||||
| `$3184/$85` | give-up / error diagnostic counters |
|
||||
| `$3186` | RX overrun flag (set at `$D701`, **never read** — not the cause) |
|
||||
| `$102D/$2E/$2F` | SCCR2 / SCSR / SCDR (HC11 SCI) |
|
||||
|
||||
## Bench validation results (2026-07-18) — PATCH CONFIRMED
|
||||
|
||||
Rig: real RIO board, FTDI FT232R on COM1, `RioSerialMonitor --mash`
|
||||
(app auto-recovery disabled, lamp echo on). Logs in `testlogs/`.
|
||||
|
||||
| | baseline (original AMD chip) | patched (W27C512) |
|
||||
|---|---|---|
|
||||
| startup version/check exchange | **wedged at 0.62s** (reply/reply collision) | survived |
|
||||
| analog replies | 6 total, then dead for 300s | 2419 @ 152s, steady ~16/s |
|
||||
| mash | 951 presses (peak 460/min) | 1315 presses (peak 635/min) |
|
||||
| worst analog gap | infinite — no recovery in 300s despite 951 presses | 0.58s, self-recovered |
|
||||
| wedge events | 1, unresolved (power cycle required) | 0 |
|
||||
|
||||
Notes: the baseline wedge didn't even need the mash — the tool's own
|
||||
version+check request colliding with the analog poll killed the reply
|
||||
path instantly, and (contrary to the earlier RIO-NOTES observation)
|
||||
button presses did NOT revive it. The patched chip's single 0.58s gap
|
||||
is the intended failure mode: collision → teardown → latch cleared by
|
||||
the $DFF0 stub → self-recovery. Patched run cut at ~155s by operator
|
||||
(finger fatigue); given the baseline's 0.62s time-to-wedge, 2.5 min of
|
||||
heavier mash is decisive. Remaining (cabinet): overnight idle soak +
|
||||
a native-game session. Both check snapshots also reported a board/lamp
|
||||
fault item — persistent across chips, likely a real tired lamp; inspect
|
||||
separately.
|
||||
|
||||
## 31250-baud bench run (2026-07-18) — patch holds; retry window is byte-scaled
|
||||
|
||||
`testlogs/riomash-patched-31250-20260718-003114.log` (127s mash, FTDI
|
||||
COM1 @ 31250, latency timer already 1ms): **zero wedges**, but
|
||||
framing=5655, analog replies at 215% of poll slots (duplicates), and
|
||||
AbandonCount=65 — a reply-retry storm. Reading: the board's reply
|
||||
ACK-wait window scales with BYTE TIME, not wall clock. At 9600 the
|
||||
window (~5+ms) hides USB turnaround entirely (zero framing in both
|
||||
9600 runs); at 31250 it shrinks to ~1.6ms, which USB cannot reliably
|
||||
beat, so most replies retransmit and 65 exhausted all 4 retries.
|
||||
|
||||
Two conclusions:
|
||||
1. **Brutal confirmation of the wedge patch**: 65 trips through the
|
||||
give-up path in 127s, every one self-recovered ($DFF0 stub). The
|
||||
unpatched firmware would have latched dead on the first.
|
||||
2. **31250 needs one more firmware tweak to be clean**: widen the
|
||||
reply-retry wait (the pacing/limit around the $D90E/$D9BE retry
|
||||
machine) so the window stays >=10ms of wall clock at 31250. Until
|
||||
then: cabinets run the 9600 patched chip (validated clean); 31250
|
||||
stays a bench branch. Note the duplicate replies also mean the raw
|
||||
reply rate overstates throughput — unique samples are still capped
|
||||
by the host's 55ms poll; harvesting the speed needs a faster poll
|
||||
AFTER the retry window is fixed.
|
||||
|
||||
### 31250 v2 (2026-07-18) — `RIOv4_2_patched_31250v2.bin` — widened ACK-wait
|
||||
|
||||
Root cause of the v1 retry storm CONFIRMED in the disassembly: the reply
|
||||
ACK-wait loop at `$D9E0` self-clocks in **byte times** — each tick sends an
|
||||
IDLE (`$FF`) keep-alive and re-enters on that byte's TX-complete interrupt,
|
||||
so the `CMPA #$04` limit means ~5 byte times of grace: ~5.2ms at 9600
|
||||
(USB ACK wins; zero framing in both 9600 runs) vs ~1.6ms at 31250 (USB
|
||||
loses; framing 5655 / Abandon 65 on the v1 bench run). Those keep-alive/
|
||||
RESTART bytes landing mid-packet are exactly the host-side framing resyncs.
|
||||
|
||||
`make_patch.py --baud31250 --widen-ackwait` → one more byte,
|
||||
`$D9E7: $04 → $28` (40 ticks ≈ 12.8ms at 31250, clears FTDI worst case
|
||||
with margin). The NAK-retry limit ($3175) is event-counted, untouched.
|
||||
sha256 `420d4cfc6b513651687982a70db2daeecda7bd00a5324321e272f35b95dca753`,
|
||||
25 bytes vs original; re-disassembly diff vs the v1 31250 image is exactly
|
||||
the one CMPA operand line. Expected on the bench: framing 5655 → ~0,
|
||||
Abandon 65 → ~0, wedges 0 → 0.
|
||||
|
||||
### 31250 v2 bench results (2026-07-18) — CLEAN. Byte-clock theory confirmed.
|
||||
|
||||
`testlogs/riomash-patched-31250v2-20260718-110853.log` (192s mash):
|
||||
|
||||
| metric | v1 @31250 | v2 @31250 (ACK-wait $28) |
|
||||
|-------------------|----------------|--------------------------|
|
||||
| framing resyncs | 5655 (~45/s) | **54 (~0.3/s, -99.4%)** |
|
||||
| Abandon delta | 65 | **+0** |
|
||||
| Restart delta | 2 | **+0** |
|
||||
| analog vs polls | 215% (dupes) | **88.6% (no dupes)** |
|
||||
| wedge events | 0 | **0** |
|
||||
| worst mid-run gap | 0.09s (steady) | 0.14s (tail <100ms) |
|
||||
|
||||
The 88.6% delivery ratio equals the healthy 9600 patched profile (87.5%)
|
||||
— the deficit is the $D758 in-flight drop gate, identical at both rates,
|
||||
i.e. structural, not a v2 artifact. Counters perfectly flat: the board
|
||||
never entered retry escalation in 192s. FastRIO is now *clean*, not
|
||||
just survivable. To actually harvest the bandwidth, the next dial is
|
||||
host-side: drop the 55ms analog poll interval (the board now answers in
|
||||
~4.6ms of wire time). Cabinet adoption still gated on game-side baud
|
||||
patches; 9600 patched chip remains the production standard meanwhile.
|
||||
|
||||
### 125000 bench results (2026-07-18) — CPU wall found; RX overrun confirmed
|
||||
|
||||
`testlogs/riomash-patched-125000-20260718-125310.log` (103s): zero wedges
|
||||
(all gaps self-recovered <=0.52s even here), but the board's RX drowns:
|
||||
NAK=1082 (~2/3 of the ~1650 lamp commands arrived checksum-corrupt —
|
||||
overrun-dropped bytes; lamps visibly dead), analog delivery 60.6% (2-byte
|
||||
requests survive better than 4-byte packets), both status exchanges
|
||||
failed. Direction asymmetry nails the cause: board->PC was near-perfect
|
||||
(framing 5), so the MAX232 leg is fine at 125k — the 2MHz HC11 RX ISR
|
||||
(~160 E-cycles/byte, 1-byte buffer) is the wall, exactly as the cycle
|
||||
estimate predicted. Verdict: 125000 NOT viable. Sweet-spot candidate:
|
||||
`RIOv4_2_patched_62500.bin` (320 cycles/byte, MAX232 in-spec, ACK-wait
|
||||
80 ticks = 12.8ms), sha256 below, awaiting bench.
|
||||
|
||||
### 62500 bench results (2026-07-18) — the ladder is complete
|
||||
|
||||
`testlogs/riomash-patched-62500-20260718-130333.log` (132s): analog
|
||||
pristine (88.1% delivery = the structural drop-gate ratio; worst mid-run
|
||||
gap 0.14s; counters flat; zero wedges; status exchanges fine) but
|
||||
**NAK=69 of ~1060 lamp commands (~6.5%)** — 4-byte bursts still outrun
|
||||
the RX ISR occasionally, and fire-and-forget lamp writes turn each loss
|
||||
into a visible stuck-bright / missed-bright lamp. The ISR's worst-case
|
||||
path therefore sits right at ~320 E-cycles.
|
||||
|
||||
**The RIO speed ladder (2 MHz E-clock, all with wedge fix):**
|
||||
| rate | bytes OK inbound | verdict |
|
||||
|---|---|---|
|
||||
| 9600 | all | production (native-game compatible) |
|
||||
| 31250 v2 | all (NAK 0.3%) | CLEAN — FastRIO recommended |
|
||||
| 62500 | 2-byte yes, 4-byte ~6.5% loss | usable ONLY with host NAK-retransmit |
|
||||
| 125000 | ~40-65% loss | not viable — CPU wall |
|
||||
|
||||
Host-side NAK-driven retransmit (the protocol's intended use of ACK/NAK;
|
||||
RioSerialLink currently fire-and-forgets outbound) would make 62500
|
||||
lamp-solid (1 retry -> ~0.4% residual, 2 -> ~0.03%) and is good
|
||||
robustness at any rate. Until then, 31250 v2 is the honest FastRIO.
|
||||
|
||||
### 62500 final verdict (2026-07-19) — SHELVED. Chord-driven RX overrun.
|
||||
|
||||
Three rounds of host-side hardening at 62500 (evidence in `testlogs/`,
|
||||
implementations in RioJoy.Core RioSerialLink):
|
||||
1. **NAK-race resend** — resends tracked NAKs 1:1 (321/321) yet lamps
|
||||
still glitched: under bursts the NAK arrives after a newer command is
|
||||
already "latest", so the wrong packet was resent; total shreds never
|
||||
NAK at all.
|
||||
2. **Stop-and-wait** (one command in flight; ACK/NAK/50ms-timeout;
|
||||
retransmit the SAME packet, limit 2) — still glitched: reply
|
||||
resolution was type-blind, so an analog reply in USB transit from the
|
||||
previous poll falsely confirmed the next lamp command.
|
||||
3. **Typed resolution** (a reply only resolves its matching request;
|
||||
ACK/NAK safe type-blind because the board's TX ISR prioritizes them
|
||||
ahead of reply data; 10ms settle after budget-exhausted drops) —
|
||||
improved, but **chorded presses still glitch even at slow press
|
||||
rates**.
|
||||
|
||||
The chord observation closes the case: a chord makes the board scan,
|
||||
queue and transmit N button events at once — worst-case ISR latency —
|
||||
exactly when our 4-byte lamp replies arrive at 160us/byte into a 2-byte
|
||||
RX buffer. Corruption at 62500 is chord-shaped, not rate-shaped; no
|
||||
host-side protocol can prevent the board losing bytes it never latched,
|
||||
and retries can land in the same busy window. Fixing it for real means
|
||||
firmware ISR restructuring (or sequence numbers) — out of scope.
|
||||
|
||||
**Final ladder verdict: 31250 v2 is the FastRIO standard** (3.3x stock,
|
||||
clean without any host-side compensation — NAK 5 in 192s of mash).
|
||||
The stop-and-wait + typed-resolution machinery stays enabled at every
|
||||
rate as delivery insurance (it hardens 9600 production cabinets against
|
||||
ordinary electrical noise, and its retries are near-zero on clean links).
|
||||
62500 images remain in the repo for future firmware work.
|
||||
|
||||
## FINAL CERTIFICATION (2026-07-19) — 31250 v2 ships
|
||||
|
||||
Operator-verified at 31250 v2 with stop-and-wait active: **zero lamp
|
||||
misses/hangs** through the slow-chord test (the exact scenario that
|
||||
condemned 62500) and a 392-presses/min mash.
|
||||
|
||||
`testlogs/riomash-31250v2-final`: analog 89.0% (structural), **NAK = 0**,
|
||||
25 resends — all timeout-triggered, i.e. the retry machinery silently
|
||||
healing occasional INBOUND response loss (29 host-side framing events),
|
||||
which is exactly the insurance it exists to provide. Counters flat,
|
||||
zero wedges, gaps <100ms except run-start/stop artifacts.
|
||||
|
||||
`testlogs/riomash-31250v2-poll20` (--poll 20): delivered ~31 Hz analog
|
||||
(2036 replies/65s) vs the legacy 18 Hz — 1.7×. The 62.6%-of-slots figure
|
||||
is a host artifact, not board saturation: the poll loop delays AFTER each
|
||||
awaited exchange (~6-9 ms), so the real cadence is interval+exchange.
|
||||
Absolute-schedule pacing would approach 40+ Hz; ~94% of actually-issued
|
||||
polls were answered. Future host tweak, not firmware.
|
||||
|
||||
### Production configuration (updated 2026-07-19: RIO 4.3 christened)
|
||||
| deployment | chip | config.json |
|
||||
|---|---|---|
|
||||
| native-game cabinets | **`RIOv4_3.bin`** (9600; supersedes `archive/RIOv4_2_patched.bin`) | defaults (RioBaudRate 9600, AnalogPollMs 55) |
|
||||
| RIOJoy-only cockpits (FastRIO) | **`RIOv4_3_fastrio.bin`** (built 2026-07-19, disassembly-verified; run the acceptance ladder on-cockpit before deploying — supersedes `archive/RIOv4_2_patched_31250v2.bin`) | RioBaudRate 31250, AnalogPollMs 20-25, FTDI-class adapter |
|
||||
|
||||
AppConfig.RioBaudRate + AnalogPollMs are plumbed through RioCoordinator;
|
||||
stop-and-wait delivery is always on (near-zero cost on clean links).
|
||||
Firmware images, hashes, disassemblies, the mechanized mash harness, and
|
||||
all eleven bench logs live in this directory. The original AMD chip is
|
||||
retired, preserved unmodified.
|
||||
|
||||
## Error-handling inventory (v4.2, unpatched baseline)
|
||||
|
||||
Complete map of what the board does — and pointedly does not do — when
|
||||
things go wrong. Complements the wedge analysis above; display-side
|
||||
detail in [`docs/hardware/display-board-1408.md`](../docs/hardware/display-board-1408.md).
|
||||
|
||||
### Host-facing (protocol)
|
||||
|
||||
- **Inbound packet checksum** (7-bit sum, verified at the `$D81F`
|
||||
decision): good packet → queue **ACK `$FC`** (`$316F`); bad → **NAK
|
||||
`$FD`** (`$3170`). The TX ISR sends pending ACK/NAK ahead of reply
|
||||
data (`$D8A6`). Control bytes `$FE` RESTART / `$FF` IDLE.
|
||||
- **Outbound reply delivery**: replies must be ACKed by the game; the
|
||||
retry machine (`$D90E`/`$D9BE`) retransmits — **5 times** (bench
|
||||
2026-07-19; the earlier code reading of "4" was wrong) — then gives
|
||||
up: sends `$FE` RESTART, `INC $317A`, `INC $3184` (give-up counter),
|
||||
teardown at `$DA2F` (`INC $3185` — teardowns: ANY reply cycle that
|
||||
needed at least one retransmission ends here, including NAK-recovered
|
||||
ones, so `$3185` is the counter behind a lone cockpit `E0000001`).
|
||||
The give-up teardown is where the `$2521` wedge leak lives (see
|
||||
above; fixed in the patched images). Bench-observed await behavior:
|
||||
the ACK wait arms only after the COMPLETE reply frame is sent
|
||||
(mid-frame ACK/NAK/RESTART is ignored); a game NAK of the completed
|
||||
frame triggers exactly one immediate counted retransmit; once the
|
||||
first retry timeout lapses the cycle runs blind to give-up — a late
|
||||
ACK cannot rescue it.
|
||||
- **RX overrun: unhandled.** The RX ISR reads SCSR/SCDR but never acts
|
||||
on the overrun flag — dropped bytes just corrupt the frame and
|
||||
surface as checksum NAKs. This is why the 125000-baud bench run
|
||||
produced NAK=1082 rather than any explicit overrun signal.
|
||||
|
||||
### Internal
|
||||
|
||||
- **TX-ring overflow** (`$D63B` enqueue): if the ring would overtake
|
||||
the drain pointer, the byte is silently dropped, `INC $3187`, and the
|
||||
E0 diagnostic display refreshes. The host is not told.
|
||||
- **TX dispatcher sanity trap** (`$D8C2`): each outgoing command byte
|
||||
is looked up in an 8-entry table at `$3158`; no match ⇒ internal
|
||||
corruption ⇒ `JMP $DAB2` — the **`dEAd` crash screen**, an infinite
|
||||
loop rendering `dEAd` + RAM `$0000/$0001` on the 8-digit display.
|
||||
Permanent until reset.
|
||||
- **Unused interrupt vectors** → `NOP; BRA self` stubs at
|
||||
`$DB07-$DB3D`: silent hang, no display, no recovery.
|
||||
- **Error counters** `$3173-$317B` (ACK/NAK/retry/give-up bookkeeping)
|
||||
and the three displayed ones `$3187/$3184/$3185` — rendered as
|
||||
`E0 <ring-ovf> <give-ups> <teardowns>` by `$D5F2` at every increment.
|
||||
|
||||
### Hardware (RIO_1407 sheet 2)
|
||||
|
||||
- **MAX690 supervisor**: power-on/brownout reset via the PFI
|
||||
comparator. The front-panel RESET toggle (SW1) and the optocoupled
|
||||
remote-reset input both work by pulling PFI low — a "commanded
|
||||
power-fail". **FAULT LED = PFO**, so it lights during any such event.
|
||||
- **No working watchdog.** WDI has no off-sheet tag (never reaches the
|
||||
CPU) and the firmware contains no strobe code — so software hangs
|
||||
(`dEAd`, vector stubs, the reply wedge) persist until a human or the
|
||||
game PC resets the board. This is corroborating evidence for the
|
||||
bench-observed behavior: a wedged board stays wedged indefinitely.
|
||||
- **RUN LED = buffered `AS*`** (address strobe): it indicates "clock
|
||||
alive", not "software alive" — it stays lit even inside the `dEAd`
|
||||
loop. TX/RX LEDs are buffered `SER_OUT`/`SER_IN`.
|
||||
|
||||
### E0-display threshold patch (edit 5, `--e0thresh[=N]`)
|
||||
|
||||
Stock firmware has **no threshold**: the first increment of any of the
|
||||
three displayed counters repaints the cockpit display from `F0000000` to
|
||||
the `E0` readout and it never reverts — so a single benign reply
|
||||
give-up (e.g. one lost ACK healed by host-side stop-and-wait) leaves a
|
||||
permanent `E0000001`. `make_patch.py --e0thresh=N` (default 5) gates the
|
||||
renderer: `$D5F4`'s `LDX #$2038` becomes `JMP $E000`, and a 30-byte cave
|
||||
at `$E000` (erased region, clear of the `$DFF0` wedge stub) compares
|
||||
`$3187`/`$3184`/`$3185` against N — all below ⇒ exit through the
|
||||
routine's own epilogue (`$D61D`, registers restored, display untouched);
|
||||
any at/above ⇒ resume the render at `$D5F7`. Counters still accumulate
|
||||
regardless, so the diagnostic history is intact — the display just stays
|
||||
quiet until real trouble (N events since power-on).
|
||||
|
||||
Built variants (wedge fix + threshold 5):
|
||||
|
||||
| image | config | sha256 (first 12) |
|
||||
|---|---|---|
|
||||
| `RIOv4_2_patched_e0t5.bin` | 9600, native-game compatible | `9c21ac7199fb` |
|
||||
| `RIOv4_2_patched_31250v2_e0t5.bin` | 31250 + widened ACK-wait (FastRIO) | `b43032b016d7` |
|
||||
|
||||
**Bench-verified 2026-07-19 (9600 image, burned): PASS.** Driven by
|
||||
`RioSerialMonitor --e0test` (DTR reset → clean handshake → four
|
||||
NAK-then-ACK sub-threshold teardown cycles → one unACKed give-up
|
||||
cycle): the display held `F0000000` through the handshake and all four
|
||||
sub-threshold teardowns — stock paints `E0…` at the first — and flipped
|
||||
exactly at the 5th teardown to **`E0000105`** (`$3187`=00 overflows,
|
||||
`$3184`=01 give-up, `$3185`=05 teardowns), confirmed by eye on the
|
||||
cockpit display. Mash regression (120s, lamps on,
|
||||
`testlogs/riomash-patched-e0t5-9600-20260719-154259.log`): **PASS** �
|
||||
0 wedges, 236/236 presses, 88.0% analog fill, NAK 0, board counters
|
||||
flat. The 31250v2_e0t5 image is the same patch on the certified
|
||||
FastRIO base and is expected to behave identically.
|
||||
|
||||
**Post-run display `04000000` — the CheckRequest self-test (corrected
|
||||
finding).** The first read of this (keypad A-9-E chord entering test
|
||||
mode) was wrong — disproved on the bench: the display is static (axis
|
||||
movement does not change it) and version-only exchanges leave it alone.
|
||||
The real mechanism is the **CheckRequest (`0x80`) handler at `$C5A6`**:
|
||||
a full built-in self-test that sets the test-display flag `$2421`
|
||||
(`$C5B1`), sends `TestModeChange 0x8C` to the host (`$C70E`), runs a
|
||||
lamp pattern (`$D34A`), scans the pods, sweeps all five encoder
|
||||
channels (`$CC53`, ending on channel 4 — whose `cc00hhll` frame is the
|
||||
last thing rendered), enqueues the fault reports, clears the flag
|
||||
(`$C5E1`), notifies `0x8C` again — and **returns without repainting the
|
||||
display**. Every status check therefore leaves a stale
|
||||
`04 00 <ch4 count>` snapshot (idle: `04000000`) on the digits. The
|
||||
board is fully healthy afterwards; no reset is needed. Proper fix:
|
||||
**edit 6** (`make_patch.py --checkrepaint`) — a 10-byte cave at `$E020`
|
||||
hooked from the handler's final notify (`$C5E4`): `JSR $C70E` (original
|
||||
notify), `JSR $CB7C` (repaint `F0000000`), `JSR $D5F2` (re-render the
|
||||
E0 readout, which the edit-5 gate suppresses below threshold), `RTS`.
|
||||
Built into **`RIOv4_3rc1.bin`** (with edits 1-2 and 5; sha256
|
||||
`dc59bd51cae3…`); prior generations moved to `archive/`.
|
||||
|
||||
Why `04000000` was never seen in period (user-verified by re-slotting
|
||||
the ORIGINAL stock chip, 2026-07-19): stock leaves the same stale frame
|
||||
after every check, but the ungated E0 readout papers over it at the
|
||||
first counter event — `04000000` flashes momentarily, then `E000000x`
|
||||
as errors accrue and the reply-latch wedge trips under load. The wedge
|
||||
fix plus the E0 threshold gate are what exposed the stale frame.
|
||||
|
||||
**RC1 acceptance (burned 2026-07-19): PASS on both edit-6 paths.**
|
||||
Below threshold: a version+check exchange leaves the display at
|
||||
`F0000000`. Over threshold: the full `--e0test` sequence reproduced the
|
||||
certified behavior (held `F0000000` through four sub-threshold events,
|
||||
flipped at the 5th teardown), and the closing CheckRequest — 65 status
|
||||
frames — left the display at **`E0000305`**: repaint + live E0
|
||||
re-render, not the stale `04000000`. The `$3184`=03 (vs predicted 01)
|
||||
pins down the counter semantics one notch further: two of the 65 check
|
||||
frames hit a timeout-retry that our late ACK then rescued — so
|
||||
**`$3184` counts started timeout-retry sequences** (give-ups are the
|
||||
subset that exhaust all 5 retransmits), and a timeout-recovered cycle
|
||||
exits via the success teardown (no `$3185`), while NAK-recovered cycles
|
||||
tear down through `$DA2F` (`$3185`). Every observation from both bench
|
||||
sessions fits this model.
|
||||
|
||||
**CHRISTENED RIO 4.3 (2026-07-19).** Final gate: 120 s mash at 549
|
||||
presses/min sustained (1129 presses — the heaviest stress run of the
|
||||
campaign), zero wedges, counters flat, NAK 0, 247 resends all healed
|
||||
(`testlogs/riomash-rio43rc1-9600-20260719-164901.log`). `RIOv4_3rc1`
|
||||
renamed to **`RIOv4_3.bin`** — the production firmware for native-game
|
||||
cabinets. Post-christening, **edit 7** (`--reportversion=4.3`, one
|
||||
operand byte at `$C6FF` in the VersionReply builder `$C6EA`) makes the
|
||||
board announce 4.3; final sha256 `6d67a2fc7713…` (69 bytes vs stock).
|
||||
No host software validates the version value (native games verified
|
||||
not to check it — Cyd, 2026-07-19); tools will print `firmware: 4.3`
|
||||
after the next burn.
|
||||
@@ -1,236 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Recursive-descent 68HC11 disassembler for the RIO board firmware.
|
||||
|
||||
The RIO board (Toshiba TMP68HC11 + AM27C512) speaks the PCSPAK serial
|
||||
protocol to the game. This tool disassembles RIOv4_2.bin to help locate the
|
||||
receive/reply state machine and the wedge where the reply path dies under
|
||||
stress. Address == file offset for this image (EPROM occupies $C000-$FFFF,
|
||||
reset vector $FFFE -> $C000 confirms).
|
||||
|
||||
Usage: python disasm_6811.py [RIOv4_2.bin] > RIOv4_2.disasm.asm
|
||||
"""
|
||||
import sys
|
||||
|
||||
# ---- HC11 internal register block ($1000-$103F) annotations --------------
|
||||
REG = {
|
||||
0x00:"PORTA",0x02:"PIOC",0x03:"PORTC",0x04:"PORTB",0x05:"PORTCL",
|
||||
0x07:"DDRC",0x08:"PORTD",0x09:"DDRD",0x0A:"PORTE",0x0B:"CFORC",
|
||||
0x0C:"OC1M",0x0D:"OC1D",0x0E:"TCNT",0x10:"TIC1",0x12:"TIC2",
|
||||
0x14:"TIC3",0x16:"TOC1",0x18:"TOC2",0x1A:"TOC3",0x1C:"TOC4",
|
||||
0x1E:"TI4O5",0x20:"TCTL1",0x21:"TCTL2",0x22:"TMSK1",0x23:"TFLG1",
|
||||
0x24:"TMSK2",0x25:"TFLG2",0x26:"PACTL",0x27:"PACNT",0x28:"SPCR",
|
||||
0x29:"SPSR",0x2A:"SPDR",0x2B:"BAUD",0x2C:"SCCR1",0x2D:"SCCR2",
|
||||
0x2E:"SCSR",0x2F:"SCDR",0x30:"ADCTL",0x31:"ADR1",0x32:"ADR2",
|
||||
0x33:"ADR3",0x34:"ADR4",0x39:"OPTION",0x3A:"COPRST",0x3B:"PPROG",
|
||||
0x3C:"HPRIO",0x3D:"INIT",0x3E:"TEST1",0x3F:"CONFIG",
|
||||
}
|
||||
def reg_ann(addr):
|
||||
if 0x1000 <= addr <= 0x103F and (addr-0x1000) in REG:
|
||||
return " ; "+REG[addr-0x1000]
|
||||
return ""
|
||||
|
||||
# addressing modes and their extra operand byte counts
|
||||
INH,IMM8,IMM16,DIR,EXT,IDX,IDY,REL = range(8)
|
||||
BSET_DIR,BCLR_DIR,BRSET_DIR,BRCLR_DIR = range(8,12)
|
||||
BSET_IDX,BCLR_IDX,BRSET_IDX,BRCLR_IDX = range(12,16)
|
||||
MODELEN = {INH:0,IMM8:1,IMM16:2,DIR:1,EXT:2,IDX:1,IDY:1,REL:1,
|
||||
BSET_DIR:2,BCLR_DIR:2,BRSET_DIR:3,BRCLR_DIR:3,
|
||||
BSET_IDX:2,BCLR_IDX:2,BRSET_IDX:3,BRCLR_IDX:3}
|
||||
|
||||
# base page opcode table: opcode -> (mnemonic, mode)
|
||||
OP = {
|
||||
0x00:("TEST",INH),0x01:("NOP",INH),0x02:("IDIV",INH),0x03:("FDIV",INH),
|
||||
0x04:("LSRD",INH),0x05:("LSLD",INH),0x06:("TAP",INH),0x07:("TPA",INH),
|
||||
0x08:("INX",INH),0x09:("DEX",INH),0x0A:("CLV",INH),0x0B:("SEV",INH),
|
||||
0x0C:("CLC",INH),0x0D:("SEC",INH),0x0E:("CLI",INH),0x0F:("SEI",INH),
|
||||
0x10:("SBA",INH),0x11:("CBA",INH),0x12:("BRSET",BRSET_DIR),0x13:("BRCLR",BRCLR_DIR),
|
||||
0x14:("BSET",BSET_DIR),0x15:("BCLR",BCLR_DIR),0x16:("TAB",INH),0x17:("TBA",INH),
|
||||
0x19:("DAA",INH),0x1B:("ABA",INH),
|
||||
0x1C:("BSET",BSET_IDX),0x1D:("BCLR",BCLR_IDX),0x1E:("BRSET",BRSET_IDX),0x1F:("BRCLR",BRCLR_IDX),
|
||||
0x20:("BRA",REL),0x21:("BRN",REL),0x22:("BHI",REL),0x23:("BLS",REL),
|
||||
0x24:("BCC",REL),0x25:("BCS",REL),0x26:("BNE",REL),0x27:("BEQ",REL),
|
||||
0x28:("BVC",REL),0x29:("BVS",REL),0x2A:("BPL",REL),0x2B:("BMI",REL),
|
||||
0x2C:("BGE",REL),0x2D:("BLT",REL),0x2E:("BGT",REL),0x2F:("BLE",REL),
|
||||
0x30:("TSX",INH),0x31:("INS",INH),0x32:("PULA",INH),0x33:("PULB",INH),
|
||||
0x34:("DES",INH),0x35:("TXS",INH),0x36:("PSHA",INH),0x37:("PSHB",INH),
|
||||
0x38:("PULX",INH),0x39:("RTS",INH),0x3A:("ABX",INH),0x3B:("RTI",INH),
|
||||
0x3C:("PSHX",INH),0x3D:("MUL",INH),0x3E:("WAI",INH),0x3F:("SWI",INH),
|
||||
0x40:("NEGA",INH),0x43:("COMA",INH),0x44:("LSRA",INH),0x46:("RORA",INH),
|
||||
0x47:("ASRA",INH),0x48:("LSLA",INH),0x49:("ROLA",INH),0x4A:("DECA",INH),
|
||||
0x4C:("INCA",INH),0x4D:("TSTA",INH),0x4F:("CLRA",INH),
|
||||
0x50:("NEGB",INH),0x53:("COMB",INH),0x54:("LSRB",INH),0x56:("RORB",INH),
|
||||
0x57:("ASRB",INH),0x58:("LSLB",INH),0x59:("ROLB",INH),0x5A:("DECB",INH),
|
||||
0x5C:("INCB",INH),0x5D:("TSTB",INH),0x5F:("CLRB",INH),
|
||||
0x60:("NEG",IDX),0x63:("COM",IDX),0x64:("LSR",IDX),0x66:("ROR",IDX),
|
||||
0x67:("ASR",IDX),0x68:("LSL",IDX),0x69:("ROL",IDX),0x6A:("DEC",IDX),
|
||||
0x6C:("INC",IDX),0x6D:("TST",IDX),0x6E:("JMP",IDX),0x6F:("CLR",IDX),
|
||||
0x70:("NEG",EXT),0x73:("COM",EXT),0x74:("LSR",EXT),0x76:("ROR",EXT),
|
||||
0x77:("ASR",EXT),0x78:("LSL",EXT),0x79:("ROL",EXT),0x7A:("DEC",EXT),
|
||||
0x7C:("INC",EXT),0x7D:("TST",EXT),0x7E:("JMP",EXT),0x7F:("CLR",EXT),
|
||||
0x80:("SUBA",IMM8),0x81:("CMPA",IMM8),0x82:("SBCA",IMM8),0x83:("SUBD",IMM16),
|
||||
0x84:("ANDA",IMM8),0x85:("BITA",IMM8),0x86:("LDAA",IMM8),0x88:("EORA",IMM8),
|
||||
0x89:("ADCA",IMM8),0x8A:("ORAA",IMM8),0x8B:("ADDA",IMM8),0x8C:("CPX",IMM16),
|
||||
0x8D:("BSR",REL),0x8E:("LDS",IMM16),0x8F:("XGDX",INH),
|
||||
0x90:("SUBA",DIR),0x91:("CMPA",DIR),0x92:("SBCA",DIR),0x93:("SUBD",DIR),
|
||||
0x94:("ANDA",DIR),0x95:("BITA",DIR),0x96:("LDAA",DIR),0x97:("STAA",DIR),
|
||||
0x98:("EORA",DIR),0x99:("ADCA",DIR),0x9A:("ORAA",DIR),0x9B:("ADDA",DIR),
|
||||
0x9C:("CPX",DIR),0x9D:("JSR",DIR),0x9E:("LDS",DIR),0x9F:("STS",DIR),
|
||||
0xA0:("SUBA",IDX),0xA1:("CMPA",IDX),0xA2:("SBCA",IDX),0xA3:("SUBD",IDX),
|
||||
0xA4:("ANDA",IDX),0xA5:("BITA",IDX),0xA6:("LDAA",IDX),0xA7:("STAA",IDX),
|
||||
0xA8:("EORA",IDX),0xA9:("ADCA",IDX),0xAA:("ORAA",IDX),0xAB:("ADDA",IDX),
|
||||
0xAC:("CPX",IDX),0xAD:("JSR",IDX),0xAE:("LDS",IDX),0xAF:("STS",IDX),
|
||||
0xB0:("SUBA",EXT),0xB1:("CMPA",EXT),0xB2:("SBCA",EXT),0xB3:("SUBD",EXT),
|
||||
0xB4:("ANDA",EXT),0xB5:("BITA",EXT),0xB6:("LDAA",EXT),0xB7:("STAA",EXT),
|
||||
0xB8:("EORA",EXT),0xB9:("ADCA",EXT),0xBA:("ORAA",EXT),0xBB:("ADDA",EXT),
|
||||
0xBC:("CPX",EXT),0xBD:("JSR",EXT),0xBE:("LDS",EXT),0xBF:("STS",EXT),
|
||||
0xC0:("SUBB",IMM8),0xC1:("CMPB",IMM8),0xC2:("SBCB",IMM8),0xC3:("ADDD",IMM16),
|
||||
0xC4:("ANDB",IMM8),0xC5:("BITB",IMM8),0xC6:("LDAB",IMM8),0xC8:("EORB",IMM8),
|
||||
0xC9:("ADCB",IMM8),0xCA:("ORAB",IMM8),0xCB:("ADDB",IMM8),0xCC:("LDD",IMM16),
|
||||
0xCE:("LDX",IMM16),0xCF:("STOP",INH),
|
||||
0xD0:("SUBB",DIR),0xD1:("CMPB",DIR),0xD2:("SBCB",DIR),0xD3:("ADDD",DIR),
|
||||
0xD4:("ANDB",DIR),0xD5:("BITB",DIR),0xD6:("LDAB",DIR),0xD7:("STAB",DIR),
|
||||
0xD8:("EORB",DIR),0xD9:("ADCB",DIR),0xDA:("ORAB",DIR),0xDB:("ADDB",DIR),
|
||||
0xDC:("LDD",DIR),0xDD:("STD",DIR),0xDE:("LDX",DIR),0xDF:("STX",DIR),
|
||||
0xE0:("SUBB",IDX),0xE1:("CMPB",IDX),0xE2:("SBCB",IDX),0xE3:("ADDD",IDX),
|
||||
0xE4:("ANDB",IDX),0xE5:("BITB",IDX),0xE6:("LDAB",IDX),0xE7:("STAB",IDX),
|
||||
0xE8:("EORB",IDX),0xE9:("ADCB",IDX),0xEA:("ORAB",IDX),0xEB:("ADDB",IDX),
|
||||
0xEC:("LDD",IDX),0xED:("STD",IDX),0xEE:("LDX",IDX),0xEF:("STX",IDX),
|
||||
0xF0:("SUBB",EXT),0xF1:("CMPB",EXT),0xF2:("SBCB",EXT),0xF3:("ADDD",EXT),
|
||||
0xF4:("ANDB",EXT),0xF5:("BITB",EXT),0xF6:("LDAB",EXT),0xF7:("STAB",EXT),
|
||||
0xF8:("EORB",EXT),0xF9:("ADCB",EXT),0xFA:("ORAB",EXT),0xFB:("ADDB",EXT),
|
||||
0xFC:("LDD",EXT),0xFD:("STD",EXT),0xFE:("LDX",EXT),0xFF:("STX",EXT),
|
||||
}
|
||||
# page 2 ($18): Y-register/Y-indexed forms
|
||||
OP18 = {
|
||||
0x08:("INY",INH),0x09:("DEY",INH),0x1C:("BSET",BSET_IDX),0x1D:("BCLR",BCLR_IDX),
|
||||
0x1E:("BRSET",BRSET_IDX),0x1F:("BRCLR",BRCLR_IDX),0x30:("TSY",INH),0x35:("TYS",INH),
|
||||
0x38:("PULY",INH),0x3A:("ABY",INH),0x3C:("PSHY",INH),0x60:("NEG",IDY),
|
||||
0x63:("COM",IDY),0x64:("LSR",IDY),0x66:("ROR",IDY),0x67:("ASR",IDY),
|
||||
0x68:("LSL",IDY),0x69:("ROL",IDY),0x6A:("DEC",IDY),0x6C:("INC",IDY),
|
||||
0x6D:("TST",IDY),0x6E:("JMP",IDY),0x6F:("CLR",IDY),0x8C:("CPY",IMM16),
|
||||
0x8F:("XGDY",INH),0x9C:("CPY",DIR),0xA0:("SUBA",IDY),0xA1:("CMPA",IDY),
|
||||
0xA2:("SBCA",IDY),0xA3:("SUBD",IDY),0xA4:("ANDA",IDY),0xA5:("BITA",IDY),
|
||||
0xA6:("LDAA",IDY),0xA7:("STAA",IDY),0xA8:("EORA",IDY),0xA9:("ADCA",IDY),
|
||||
0xAA:("ORAA",IDY),0xAB:("ADDA",IDY),0xAC:("CPY",IDY),0xAD:("JSR",IDY),
|
||||
0xAE:("LDS",IDY),0xAF:("STS",IDY),0xBC:("CPY",EXT),0xCE:("LDY",IMM16),
|
||||
0xDE:("LDY",DIR),0xDF:("STY",DIR),0xE0:("SUBB",IDY),0xE1:("CMPB",IDY),
|
||||
0xE2:("SBCB",IDY),0xE3:("ADDD",IDY),0xE4:("ANDB",IDY),0xE5:("BITB",IDY),
|
||||
0xE6:("LDAB",IDY),0xE7:("STAB",IDY),0xE8:("EORB",IDY),0xE9:("ADCB",IDY),
|
||||
0xEA:("ORAB",IDY),0xEB:("ADDB",IDY),0xEC:("LDD",IDY),0xED:("STD",IDY),
|
||||
0xEE:("LDY",IDY),0xEF:("STY",IDY),0xBE:("LDY",EXT),0xBF:("STY",EXT),
|
||||
}
|
||||
OP1A = {0x83:("CPD",IMM16),0x93:("CPD",DIR),0xA3:("CPD",IDX),0xB3:("CPD",EXT),
|
||||
0xAC:("CPY",IDX),0xEE:("LDY",IDX),0xEF:("STY",IDX)}
|
||||
OPCD = {0xA3:("CPD",IDY),0xAC:("CPX",IDY),0xEE:("LDX",IDY),0xEF:("STX",IDY)}
|
||||
|
||||
def u16(d,a): return (d[a]<<8)|d[a+1]
|
||||
|
||||
class Insn:
|
||||
__slots__=("addr","end","mnem","mode","opbytes","txt","target","flow")
|
||||
def __init__(s,**k):
|
||||
for n,v in k.items(): setattr(s,n,v)
|
||||
|
||||
def decode(d,a):
|
||||
"""Decode one instruction at address a. Returns Insn or None (illegal)."""
|
||||
start=a; op=d[a]; a+=1; pfx=None; table=OP
|
||||
if op==0x18: pfx=0x18; table=OP18; op=d[a]; a+=1
|
||||
elif op==0x1A: pfx=0x1A; table=OP1A; op=d[a]; a+=1
|
||||
elif op==0xCD: pfx=0xCD; table=OPCD; op=d[a]; a+=1
|
||||
ent=table.get(op)
|
||||
if ent is None: return None
|
||||
mnem,mode=ent; n=MODELEN[mode]; ops=d[a:a+n]; a+=n
|
||||
idxreg="Y" if (mode in (IDY,) or pfx in (0x18,) and mode in (IDX,)) else "X"
|
||||
if pfx==0xCD: idxreg="Y"
|
||||
if pfx==0x1A and mode==IDY: idxreg="Y"
|
||||
tgt=None; flow="seq"; ann=""
|
||||
if mode==INH: txt=mnem
|
||||
elif mode==IMM8: txt=f"{mnem} #${ops[0]:02X}"
|
||||
elif mode==IMM16:
|
||||
v=(ops[0]<<8)|ops[1]; txt=f"{mnem} #${v:04X}"
|
||||
elif mode==DIR:
|
||||
txt=f"{mnem} ${ops[0]:02X}"; ann=reg_ann(ops[0])
|
||||
elif mode==EXT:
|
||||
v=(ops[0]<<8)|ops[1]; txt=f"{mnem} ${v:04X}"; ann=reg_ann(v)
|
||||
if mnem=="JSR": tgt=v; flow="call"
|
||||
elif mnem=="JMP": tgt=v; flow="jump"
|
||||
elif mode==IDX or mode==IDY:
|
||||
txt=f"{mnem} ${ops[0]:02X},{idxreg}"
|
||||
elif mode==REL:
|
||||
rel=ops[0]-256 if ops[0]>127 else ops[0]; tgt=a+rel
|
||||
txt=f"{mnem} ${tgt:04X}"
|
||||
if mnem=="BRA": flow="jump"
|
||||
elif mnem=="BSR": flow="call"
|
||||
else: flow="branch"
|
||||
elif mode in (BSET_DIR,BCLR_DIR):
|
||||
txt=f"{mnem} ${ops[0]:02X},#${ops[1]:02X}"; ann=reg_ann(ops[0])
|
||||
elif mode in (BSET_IDX,BCLR_IDX):
|
||||
txt=f"{mnem} ${ops[0]:02X},{idxreg},#${ops[1]:02X}"
|
||||
elif mode in (BRSET_DIR,BRCLR_DIR):
|
||||
rel=ops[2]-256 if ops[2]>127 else ops[2]; tgt=a+rel
|
||||
txt=f"{mnem} ${ops[0]:02X},#${ops[1]:02X},${tgt:04X}"; ann=reg_ann(ops[0]); flow="branch"
|
||||
elif mode in (BRSET_IDX,BRCLR_IDX):
|
||||
rel=ops[2]-256 if ops[2]>127 else ops[2]; tgt=a+rel
|
||||
txt=f"{mnem} ${ops[0]:02X},{idxreg},#${ops[1]:02X},${tgt:04X}"; flow="branch"
|
||||
else: txt=mnem
|
||||
if mnem in ("RTS","RTI","JMP","BRA","STOP","WAI") and flow not in ("call","branch"):
|
||||
if mnem in ("RTS","RTI","STOP"): flow="end"
|
||||
ob=bytes([d[i] for i in range(start,a)])
|
||||
return Insn(addr=start,end=a,mnem=mnem,mode=mode,opbytes=ob,
|
||||
txt=txt+ann,target=tgt,flow=flow)
|
||||
|
||||
def main():
|
||||
path=sys.argv[1] if len(sys.argv)>1 else "RIOv4_2.bin"
|
||||
d=open(path,"rb").read()
|
||||
LO,HI=0xC000,0x10000
|
||||
# entry points: reset + all IRQ vectors
|
||||
entries=set()
|
||||
for va in range(0xFFD6,0x10000,2):
|
||||
entries.add(u16(d,va))
|
||||
# The RX/TX protocol runs as a pointer state machine: handlers are stored
|
||||
# into dispatch-pointer RAM vars and reached via `JMP $00,X`, which a
|
||||
# recursive tracer can't follow. Seed every handler by scanning for
|
||||
# `LDX #imm16 ; STX <dispatchvar>` (CE iw FF pp) and taking imm16.
|
||||
DISPATCH={0x292F,0x2927,0x2929,0x292B,0x2D3B,0x2D34,0x2D36,0x2D38}
|
||||
for a in range(LO,HI-5):
|
||||
if d[a]==0xCE and d[a+3]==0xFF:
|
||||
iw=(d[a+1]<<8)|d[a+2]; pp=(d[a+4]<<8)|d[a+5]
|
||||
if pp in DISPATCH and LO<=iw<HI:
|
||||
entries.add(iw)
|
||||
insns={}; labels=set(); calls=set()
|
||||
work=[e for e in entries if LO<=e<HI]
|
||||
for e in work: labels.add(e)
|
||||
seen=set()
|
||||
while work:
|
||||
a=work.pop()
|
||||
while LO<=a<HI and a not in insns:
|
||||
ins=decode(d,a)
|
||||
if ins is None: break
|
||||
insns[a]=ins
|
||||
if ins.target is not None and LO<=ins.target<HI:
|
||||
labels.add(ins.target)
|
||||
if ins.flow in ("call",): calls.add(ins.target)
|
||||
if ins.flow in ("call","branch","jump"):
|
||||
if ins.target not in insns: work.append(ins.target)
|
||||
if ins.flow in ("end","jump"): break
|
||||
a=ins.end
|
||||
# emit listing
|
||||
out=[]
|
||||
a=LO
|
||||
while a<HI:
|
||||
if a in insns:
|
||||
ins=insns[a]
|
||||
lbl=f"L{a:04X}:" if a in labels else ""
|
||||
mark=" <<<CALLED" if a in calls else ""
|
||||
hexb=" ".join(f"{b:02X}" for b in ins.opbytes)
|
||||
out.append(f"{a:04X} {hexb:<20} {lbl:<8}{ins.txt}{mark}")
|
||||
a=ins.end
|
||||
else:
|
||||
# data byte
|
||||
out.append(f"{a:04X} {d[a]:02X} .byte ${d[a]:02X}")
|
||||
a+=1
|
||||
sys.stdout.write("\n".join(out)+"\n")
|
||||
sys.stderr.write(f"decoded {len(insns)} insns, {len(labels)} labels, "
|
||||
f"{len(calls)} call targets\n")
|
||||
|
||||
if __name__=="__main__":
|
||||
main()
|
||||
@@ -1,195 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Apply the RIO v4.2 reply-wedge fix to RIOv4_2.bin -> RIOv4_2_patched.bin.
|
||||
|
||||
Fix (see RIOv4_2-ANALYSIS.md): clear the reply-in-progress latch $2521 on
|
||||
EVERY reply teardown, not just the $2522-gated success path.
|
||||
|
||||
1. Give-up path: redirect $D9DD `JMP $DA2F` to a stub at free ROM $DFF0
|
||||
that clears $2521/$2522 then continues to $DA2F.
|
||||
2. Success path: make $DA00's clear of $2521/$2522 unconditional.
|
||||
|
||||
With --baud31250, additionally retune the SCI from 9600 to 31250 baud
|
||||
(-> RIOv4_2_patched_31250.bin): the init at $D62A loads BAUD ($102B) with
|
||||
$30 (prescale /13, divider /1 -> 2MHz E / (16*13) = 9615). $02 selects
|
||||
prescale /1, divider /4 -> 2MHz / (16*4) = 31250 exactly. One operand
|
||||
byte at $D62B. NOTE: 31250 is a non-standard rate — FTDI-class USB
|
||||
adapters produce it exactly; classic 16550 UARTs cannot. The native
|
||||
games still expect 9600, so a 31250 chip is bench/RIOJoy-only until the
|
||||
games are patched.
|
||||
|
||||
Each edit asserts the exact original bytes first, so a wrong assumption
|
||||
aborts instead of corrupting the image. Address == file offset.
|
||||
"""
|
||||
import sys, hashlib
|
||||
|
||||
# Baud retunes: flag -> (BAUD register value, ACK-wait ticks for ~12.8ms, name).
|
||||
# The ACK-wait loop self-clocks in byte times (see edit 4), so each rate needs
|
||||
# its own tick count to hold the same ~12.8ms wall-clock ACK grace window.
|
||||
BAUD_OPTS = {
|
||||
"--baud31250": (0x02, 0x28, "31250"), # /1 /4 -> 31250 (40 ticks)
|
||||
"--baud62500": (0x01, 0x50, "62500"), # /1 /2 -> 62500 (80 ticks)
|
||||
"--baud125000": (0x00, 0xA0, "125000"), # /1 /1 -> 125000 (160 ticks)
|
||||
}
|
||||
selected = [f for f in BAUD_OPTS if f in sys.argv]
|
||||
assert len(selected) <= 1, f"pick one baud flag, got {selected}"
|
||||
BAUD_VAL, ACKWAIT_VAL, BAUD_NAME = BAUD_OPTS[selected[0]] if selected else (None, None, None)
|
||||
|
||||
# --widen-ackwait: required semantics for 62500/125000 (implied there); optional
|
||||
# for 31250 to keep the historical v1 image reproducible.
|
||||
WIDEN_ACKWAIT = "--widen-ackwait" in sys.argv or BAUD_NAME in ("62500", "125000")
|
||||
assert not ("--widen-ackwait" in sys.argv) or selected, \
|
||||
"--widen-ackwait only makes sense with a baud retune (the wait is byte-scaled)"
|
||||
|
||||
# --e0thresh[=N]: gate the E0 error display (see edit 5). Default N=5.
|
||||
E0T = None
|
||||
for a in sys.argv:
|
||||
if a.startswith("--e0thresh"):
|
||||
E0T = int(a.split("=", 1)[1]) if "=" in a else 5
|
||||
assert E0T is None or 1 <= E0T <= 255, f"--e0thresh out of range: {E0T}"
|
||||
|
||||
# --checkrepaint: repaint the display after the CheckRequest self-test (edit 6).
|
||||
CHECKREPAINT = "--checkrepaint" in sys.argv
|
||||
assert not CHECKREPAINT or E0T is not None, \
|
||||
"--checkrepaint requires --e0thresh (it re-renders E0 via the gated $D5F2)"
|
||||
|
||||
# --reportversion=M.N (edit 7): the major.minor bytes the VersionReply returns.
|
||||
REPORTVER = None
|
||||
for a in sys.argv:
|
||||
if a.startswith("--reportversion="):
|
||||
REPORTVER = tuple(int(x) for x in a.split("=", 1)[1].split("."))
|
||||
assert len(REPORTVER) == 2 and all(0 <= v <= 127 for v in REPORTVER), \
|
||||
f"--reportversion must be M.N with 7-bit values: {REPORTVER}"
|
||||
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
||||
SRC = args[0] if len(args) > 0 else "RIOv4_2.bin"
|
||||
if len(args) > 1:
|
||||
DST = args[1]
|
||||
elif BAUD_NAME == "31250":
|
||||
DST = "RIOv4_2_patched_31250v2.bin" if WIDEN_ACKWAIT else "RIOv4_2_patched_31250.bin"
|
||||
elif BAUD_NAME:
|
||||
DST = f"RIOv4_2_patched_{BAUD_NAME}.bin"
|
||||
else:
|
||||
DST = "RIOv4_2_patched.bin"
|
||||
if E0T is not None and len(args) <= 1:
|
||||
DST = DST[:-4] + f"_e0t{E0T}.bin"
|
||||
if CHECKREPAINT and len(args) <= 1:
|
||||
DST = DST[:-4] + "_ckr.bin"
|
||||
|
||||
d = bytearray(open(SRC, "rb").read())
|
||||
assert len(d) == 0x10000, f"expected 64KB image, got {len(d)}"
|
||||
orig_sha = hashlib.sha256(d).hexdigest()
|
||||
assert orig_sha == "60a88718835c654b6135dbec7721c40ef99dca07df2ad4b57eedeb24037a5f73", \
|
||||
f"unexpected source image {orig_sha}"
|
||||
|
||||
def patch(addr, expect, new):
|
||||
got = bytes(d[addr:addr+len(expect)])
|
||||
assert got == bytes(expect), (
|
||||
f"@${addr:04X}: expected {got.hex()} to be {bytes(expect).hex()}")
|
||||
assert len(new) == len(expect), "length mismatch"
|
||||
d[addr:addr+len(new)] = bytes(new)
|
||||
|
||||
# --- edit 1: give-up path redirect ---------------------------------------
|
||||
# $D9DD 7E DA 2F JMP $DA2F -> 7E DF F0 JMP $DFF0
|
||||
patch(0xD9DD, [0x7E, 0xDA, 0x2F], [0x7E, 0xDF, 0xF0])
|
||||
|
||||
# stub at $DFF0 (was erased $FF): CLR $2521; CLR $2522; JMP $DA2F
|
||||
patch(0xDFF0, [0xFF]*8,
|
||||
[0x7F, 0x25, 0x21, # CLR $2521
|
||||
0x7F, 0x25, 0x22, # CLR $2522
|
||||
0x7E, 0xDA]) # JMP $DA2F (hi + first target byte)
|
||||
patch(0xDFF8, [0xFF], [0x2F]) # JMP low byte
|
||||
|
||||
# --- edit 2: success teardown, unconditional clear -----------------------
|
||||
# $DA21 B6 25 22 LDAA $2522
|
||||
# $DA24 81 01 CMPA #$01
|
||||
# $DA26 26 06 BNE $DA2E
|
||||
# $DA28 7F 25 21 CLR $2521
|
||||
# $DA2B 7F 25 22 CLR $2522 (13 bytes $DA21-$DA2D; $DA2E RTS untouched)
|
||||
# -> CLR $2521 ; CLR $2522 ; NOP x7
|
||||
patch(0xDA21,
|
||||
[0xB6,0x25,0x22, 0x81,0x01, 0x26,0x06, 0x7F,0x25,0x21, 0x7F,0x25,0x22],
|
||||
[0x7F,0x25,0x21, 0x7F,0x25,0x22, 0x01,0x01,0x01,0x01,0x01,0x01,0x01])
|
||||
assert d[0xDA2E] == 0x39, "RTS at $DA2E must be intact"
|
||||
|
||||
# --- edit 3 (--baud31250 only): SCI 9600 -> 31250 --------------------------
|
||||
# $D62A 86 30 LDAA #$30 (SCP=/13, SCR=/1) -> 86 02 (SCP=/1, SCR=/4)
|
||||
# then STAA $102B (BAUD). 2MHz E-clock: 2e6/(16*13)=9615 -> 2e6/(16*4)=31250.
|
||||
if BAUD_VAL is not None:
|
||||
assert d[0xD62A] == 0x86 and bytes(d[0xD62C:0xD62F]) == bytes([0xB7,0x10,0x2B]), \
|
||||
"expected LDAA #imm ; STAA $102B at $D62A"
|
||||
patch(0xD62B, [0x30], [BAUD_VAL])
|
||||
|
||||
# --- edit 4 (--widen-ackwait; implied for 62500/125000): scale the ACK-wait --
|
||||
# The no-ACK wait loop at $D9E0 self-clocks in BYTE TIMES: each tick transmits
|
||||
# an IDLE ($FF) keep-alive and re-enters on that byte's TX-complete interrupt.
|
||||
# The stock limit (4 ticks) = ~5.2ms at 9600 (USB ACK wins) but shrinks with
|
||||
# baud (retry storm at 31250; bench 2026-07-18: framing 5655, Abandon 65).
|
||||
# Each retune holds the window at ~12.8ms wall clock: 40/80/160 ticks.
|
||||
# $D9E6 81 04 CMPA #$04 -> 81 <ticks>
|
||||
if WIDEN_ACKWAIT:
|
||||
assert d[0xD9E6] == 0x81 and bytes(d[0xD9E3:0xD9E6]) == bytes([0xB6,0x31,0x7B]), \
|
||||
"expected LDAA $317B ; CMPA #imm at $D9E3"
|
||||
patch(0xD9E7, [0x04], [ACKWAIT_VAL])
|
||||
|
||||
# --- edit 5 (--e0thresh=N): threshold-gate the E0 error display -----------
|
||||
# Stock behavior: every increment of $3187 (TX-ring overflow), $3184 (reply
|
||||
# retransmit) or $3185 (reply teardown/give-up) immediately calls $D5F2,
|
||||
# which paints 'E0'+counters over the F0 banner — so ONE benign give-up
|
||||
# shows E0000001 forever. Gate the render: skip unless any counter >= N.
|
||||
# $D5F2 prologue (PSHX/PSHA) has already run when we take over its
|
||||
# LDX #$2038; the cave may clobber A/X because both exits restore them —
|
||||
# below-threshold leaves via the routine's own epilogue at $D61D
|
||||
# (PULA/PULX/RTS), at-or-above resumes the render at $D5F7.
|
||||
if E0T is not None:
|
||||
patch(0xD5F4, [0xCE, 0x20, 0x38], [0x7E, 0xE0, 0x00]) # LDX #$2038 -> JMP $E000
|
||||
cave = [0xB6, 0x31, 0x87, # $E000 LDAA $3187 (ring overflows)
|
||||
0x81, E0T, # $E003 CMPA #N
|
||||
0x24, 0x11, # $E005 BCC $E018 (>=N -> render)
|
||||
0xB6, 0x31, 0x84, # $E007 LDAA $3184 (reply retransmits)
|
||||
0x81, E0T, # $E00A CMPA #N
|
||||
0x24, 0x0A, # $E00C BCC $E018
|
||||
0xB6, 0x31, 0x85, # $E00E LDAA $3185 (teardowns/give-ups)
|
||||
0x81, E0T, # $E011 CMPA #N
|
||||
0x24, 0x03, # $E013 BCC $E018
|
||||
0x7E, 0xD6, 0x1D, # $E015 JMP $D61D (below threshold: epilogue)
|
||||
0xCE, 0x20, 0x38, # $E018 LDX #$2038 (displaced instruction)
|
||||
0x7E, 0xD5, 0xF7] # $E01B JMP $D5F7 (resume render)
|
||||
patch(0xE000, [0xFF] * len(cave), cave)
|
||||
|
||||
# --- edit 6 (--checkrepaint): repaint the display after the check self-test --
|
||||
# The CheckRequest handler ($C5A6) renders its five-channel encoder sweep to
|
||||
# the cockpit display (it sets the test-display flag $2421 for the duration)
|
||||
# and returns without repainting, leaving a stale '04 00 <ch4 count>' frame
|
||||
# (idle: 04000000) after every status check. Hook its final TestModeChange
|
||||
# notify ($C5E4 JSR $C70E) through a cave that also repaints F0000000
|
||||
# ($CB7C) and then re-renders the E0 error readout via $D5F2 — which, with
|
||||
# the edit-5 gate, paints only when a counter is at/over threshold. Display
|
||||
# after any check: F0000000 when healthy, E0... when not.
|
||||
if CHECKREPAINT:
|
||||
patch(0xC5E4, [0xBD, 0xC7, 0x0E], [0xBD, 0xE0, 0x20]) # JSR $C70E -> JSR $E020
|
||||
cave6 = [0xBD, 0xC7, 0x0E, # $E020 JSR $C70E (original notify, kept)
|
||||
0xBD, 0xCB, 0x7C, # $E023 JSR $CB7C (repaint F0000000)
|
||||
0xBD, 0xD5, 0xF2, # $E026 JSR $D5F2 (E0 readout iff >= threshold)
|
||||
0x39] # $E029 RTS
|
||||
patch(0xE020, [0xFF] * len(cave6), cave6)
|
||||
|
||||
# --- edit 7 (--reportversion=M.N): version bytes in the VersionReply --------
|
||||
# The reply builder at $C6EA loads the payload with LDAA #$04 ($C6F7) and
|
||||
# LDAA #$02 ($C6FE) — hardcoded "4.2". Nothing host-side validates the value
|
||||
# (legacy app prints it, RIOJoy parses it, native games ignore it — verified
|
||||
# by Cyd 2026-07-19), so the christened firmware can announce itself.
|
||||
if REPORTVER is not None:
|
||||
assert d[0xC6F7] == 0x86 and d[0xC6FE] == 0x86, "expected LDAA #imm pair at $C6F7/$C6FE"
|
||||
patch(0xC6F8, [0x04], [REPORTVER[0]])
|
||||
patch(0xC6FF, [0x02], [REPORTVER[1]])
|
||||
|
||||
open(DST, "wb").write(d)
|
||||
new_sha = hashlib.sha256(d).hexdigest()
|
||||
# byte-diff report
|
||||
diffs = [(a, orig, d[a]) for a, orig in
|
||||
enumerate(open(SRC,"rb").read()) if d[a] != orig]
|
||||
print(f"source : {SRC} sha256 {orig_sha}")
|
||||
print(f"patched: {DST} sha256 {new_sha}")
|
||||
print(f"{len(diffs)} bytes changed:")
|
||||
for a, o, n in diffs:
|
||||
print(f" ${a:04X}: {o:02X} -> {n:02X}")
|
||||
@@ -1,28 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 31250 8N1, 300s, chip label '31250v2-final' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, poll 55ms, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-31250v2-final-20260719-141437.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 31.83s] progress: presses=157 (157/min) analog=500 maxGap=0.28s wedges=0
|
||||
[ 61.84s] progress: presses=432 (419/min) analog=976 maxGap=0.28s wedges=0
|
||||
[ 65.45s] operator stop (Ctrl+C) — ending run, snapshotting counters...
|
||||
|
||||
== MASH SUMMARY [31250v2-final] ==
|
||||
run : 65s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 436 / 436 (392 presses/min)
|
||||
analog replies : 1051 (~1181 poll slots; 89.0%), sentinels 0
|
||||
framing / NAK : 29 / 0 (command resends NAK/timeout: 25)
|
||||
gap histogram : <100ms:1048 100-250ms:0 250-500ms:2 0.5-1s:0 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.33s@65.4s, 0.28s@0.6s, 0.09s@45.0s, 0.08s@19.8s, 0.08s@22.4s, 0.08s@41.7s, 0.08s@24.5s, 0.08s@8.5s, 0.08s@37.5s, 0.08s@14.6s
|
||||
wedge events : 0
|
||||
counters before : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counters after : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : RestartCount=+0 AbandonCount=+0 FullBufferCount=+0
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : C:\VWE\riojoy\riomash-31250v2-final-20260719-141437.log
|
||||
@@ -1,28 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 31250 8N1, 120s, chip label '31250v2-poll20' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, poll 20ms, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-31250v2-poll20-20260719-141620.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 120s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 31.97s] progress: presses=63 (63/min) analog=986 maxGap=0.51s wedges=0
|
||||
[ 62.02s] progress: presses=314 (304/min) analog=1890 maxGap=0.51s wedges=0
|
||||
[ 65.66s] operator stop (Ctrl+C) — ending run, snapshotting counters...
|
||||
|
||||
== MASH SUMMARY [31250v2-poll20] ==
|
||||
run : 65s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 318 / 318 (285 presses/min)
|
||||
analog replies : 2036 (~3250 poll slots; 62.6%), sentinels 0
|
||||
framing / NAK : 73 / 0 (command resends NAK/timeout: 57)
|
||||
gap histogram : <100ms:2032 100-250ms:1 250-500ms:1 0.5-1s:1 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.51s@0.7s, 0.29s@65.7s, 0.13s@50.6s, 0.08s@55.1s, 0.07s@50.9s, 0.07s@50.7s, 0.07s@50.8s, 0.07s@54.3s, 0.06s@48.5s, 0.06s@57.9s
|
||||
wedge events : 0
|
||||
counters before : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counters after : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : RestartCount=+0 AbandonCount=+0 FullBufferCount=+0
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : C:\VWE\riojoy\riomash-31250v2-poll20-20260719-141620.log
|
||||
@@ -1,20 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 9600 8N1, 300s, chip label 'baseline' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-baseline-20260718-000311.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
|
||||
[ 2.70s] *** WEDGE: no analog reply for >2.0s (last at 0.62s). Keep pressing buttons — do NOT power cycle. ***
|
||||
[ 31.89s] progress: presses=196 (196/min) analog=6 maxGap=0.08s wedges=0 [WEDGED NOW]
|
||||
[ 62.00s] progress: presses=475 (460/min) analog=6 maxGap=0.08s wedges=0 [WEDGED NOW]
|
||||
[ 92.03s] progress: presses=663 (432/min) analog=6 maxGap=0.08s wedges=0 [WEDGED NOW]
|
||||
[ 122.14s] progress: presses=781 (384/min) analog=6 maxGap=0.08s wedges=0 [WEDGED NOW]
|
||||
[ 152.22s] progress: presses=894 (352/min) analog=6 maxGap=0.08s wedges=0 [WEDGED NOW]
|
||||
[ 182.28s] progress: presses=894 (294/min) analog=6 maxGap=0.08s wedges=0 [WEDGED NOW]
|
||||
[ 212.28s] progress: presses=951 (269/min) analog=6 maxGap=0.08s wedges=0 [WEDGED NOW]
|
||||
[ 242.36s] progress: presses=951 (235/min) analog=6 maxGap=0.08s wedges=0 [WEDGED NOW]
|
||||
[ 272.40s] progress: presses=951 (209/min) analog=6 maxGap=0.08s wedges=0 [WEDGED NOW]
|
||||
[ 301.88s] *** RUN ENDED WHILE WEDGED (301.3s and counting) ***
|
||||
@@ -1,29 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 125000 8N1, 300s, chip label 'patched-125000' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-patched-125000-20260718-125310.log
|
||||
firmware: 4.2
|
||||
counters before: (no status reply — reply path dead?)
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 33.05s] progress: presses=147 (147/min) analog=344 maxGap=0.31s wedges=0
|
||||
[ 63.12s] progress: presses=472 (449/min) analog=668 maxGap=0.37s wedges=0
|
||||
[ 93.14s] progress: presses=741 (477/min) analog=999 maxGap=0.44s wedges=0
|
||||
[ 103.09s] operator stop (Ctrl+C) — ending run, snapshotting counters...
|
||||
|
||||
== MASH SUMMARY [patched-125000] ==
|
||||
run : 103s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 827 / 824 (470 presses/min)
|
||||
analog replies : 1134 (~1872 poll slots; 60.6%), sentinels 0
|
||||
framing / NAK : 5 / 1082
|
||||
gap histogram : <100ms:804 100-250ms:298 250-500ms:30 0.5-1s:1 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.52s@95.7s, 0.44s@63.3s, 0.44s@86.0s, 0.38s@79.8s, 0.37s@56.4s, 0.37s@73.4s, 0.37s@81.5s, 0.34s@95.3s, 0.31s@40.8s, 0.31s@58.4s
|
||||
wedge events : 0
|
||||
counters before : (no status reply — reply path dead?)
|
||||
counters after : (no status reply — reply path dead?)
|
||||
counter delta : (incomplete — a snapshot got no reply)
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : C:\VWE\riojoy\riomash-patched-125000-20260718-125310.log
|
||||
@@ -1,16 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 9600 8N1, 300s, chip label 'patched' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-patched-20260718-001800.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
|
||||
[ 31.82s] progress: presses=227 (227/min) analog=501 maxGap=0.58s wedges=0
|
||||
[ 61.91s] progress: presses=556 (539/min) analog=984 maxGap=0.58s wedges=0
|
||||
[ 91.99s] progress: presses=862 (562/min) analog=1462 maxGap=0.58s wedges=0
|
||||
[ 122.05s] progress: presses=1292 (635/min) analog=1941 maxGap=0.58s wedges=0
|
||||
[ 152.13s] progress: presses=1315 (519/min) analog=2419 maxGap=0.58s wedges=0
|
||||
[ 182.17s] progress: presses=1315 (433/min) analog=2899 maxGap=0.58s wedges=0
|
||||
[ 212.18s] progress: presses=1315 (372/min) analog=3378 maxGap=0.58s wedges=0
|
||||
@@ -1,30 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 31250 8N1, 300s, chip label 'patched-31250' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-patched-31250-20260718-003114.log
|
||||
firmware: (no version reply!)
|
||||
counters before: (no status reply — reply path dead?)
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 31.90s] progress: presses=188 (188/min) analog=1185 maxGap=0.09s wedges=0
|
||||
[ 61.90s] progress: presses=395 (383/min) analog=2370 maxGap=0.09s wedges=0
|
||||
[ 91.93s] progress: presses=798 (521/min) analog=3559 maxGap=0.09s wedges=0
|
||||
[ 121.96s] progress: presses=932 (459/min) analog=4713 maxGap=0.09s wedges=0
|
||||
[ 126.86s] operator stop (Ctrl+C) — ending run, snapshotting counters...
|
||||
|
||||
== MASH SUMMARY [patched-31250] ==
|
||||
run : 126s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 946 / 977 (443 presses/min)
|
||||
analog replies : 4927 (~2290 poll slots; 215.2%), sentinels 0
|
||||
framing / NAK : 5655 / 5
|
||||
gap histogram : <100ms:4925 100-250ms:0 250-500ms:0 0.5-1s:1 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.54s@126.9s, 0.09s@28.6s, 0.08s@29.5s, 0.08s@121.9s, 0.08s@24.3s, 0.08s@108.9s, 0.08s@21.8s, 0.08s@85.9s, 0.08s@88.9s, 0.08s@83.5s
|
||||
wedge events : 0
|
||||
counters before : (no status reply — reply path dead?)
|
||||
counters after : RestartCount=2 AbandonCount=65 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : (incomplete — a snapshot got no reply)
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : C:\VWE\riojoy\riomash-patched-31250-20260718-003114.log
|
||||
@@ -1,32 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 31250 8N1, 300s, chip label 'patched-31250v2' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-patched-31250v2-20260718-110853.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 33.03s] progress: presses=63 (63/min) analog=523 maxGap=0.28s wedges=0
|
||||
[ 63.05s] progress: presses=165 (157/min) analog=1002 maxGap=0.28s wedges=0
|
||||
[ 93.08s] progress: presses=185 (119/min) analog=1481 maxGap=0.28s wedges=0
|
||||
[ 123.11s] progress: presses=347 (169/min) analog=1958 maxGap=0.28s wedges=0
|
||||
[ 153.22s] progress: presses=504 (197/min) analog=2440 maxGap=0.28s wedges=0
|
||||
[ 183.26s] progress: presses=763 (250/min) analog=2919 maxGap=0.28s wedges=0
|
||||
[ 191.98s] operator stop (Ctrl+C) — ending run, snapshotting counters...
|
||||
|
||||
== MASH SUMMARY [patched-31250v2] ==
|
||||
run : 191s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 775 / 775 (241 presses/min)
|
||||
analog replies : 3076 (~3472 poll slots; 88.6%), sentinels 0
|
||||
framing / NAK : 54 / 5
|
||||
gap histogram : <100ms:3068 100-250ms:5 250-500ms:2 0.5-1s:0 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.30s@192.0s, 0.28s@1.8s, 0.14s@57.2s, 0.13s@107.8s, 0.12s@98.5s, 0.11s@130.9s, 0.10s@165.4s, 0.10s@24.5s, 0.10s@151.9s, 0.10s@167.4s
|
||||
wedge events : 0
|
||||
counters before : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counters after : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : RestartCount=+0 AbandonCount=+0 FullBufferCount=+0
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : C:\VWE\riojoy\riomash-patched-31250v2-20260718-110853.log
|
||||
@@ -1,30 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 62500 8N1, 300s, chip label 'patched-62500' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-patched-62500-20260718-130333.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 33.04s] progress: presses=156 (156/min) analog=521 maxGap=0.22s wedges=0
|
||||
[ 63.12s] progress: presses=299 (284/min) analog=998 maxGap=0.22s wedges=0
|
||||
[ 93.15s] progress: presses=396 (255/min) analog=1478 maxGap=0.22s wedges=0
|
||||
[ 123.17s] progress: presses=505 (246/min) analog=1954 maxGap=0.22s wedges=0
|
||||
[ 132.35s] operator stop (Ctrl+C) — ending run, snapshotting counters...
|
||||
|
||||
== MASH SUMMARY [patched-62500] ==
|
||||
run : 132s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 530 / 530 (238 presses/min)
|
||||
analog replies : 2115 (~2400 poll slots; 88.1%), sentinels 0
|
||||
framing / NAK : 24 / 69
|
||||
gap histogram : <100ms:2103 100-250ms:11 250-500ms:0 0.5-1s:0 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.25s@132.3s, 0.22s@1.8s, 0.14s@54.9s, 0.14s@117.2s, 0.13s@43.6s, 0.13s@14.8s, 0.13s@43.1s, 0.13s@115.0s, 0.12s@124.0s, 0.12s@94.9s
|
||||
wedge events : 0
|
||||
counters before : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counters after : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : RestartCount=+0 AbandonCount=+0 FullBufferCount=+0
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : C:\VWE\riojoy\riomash-patched-62500-20260718-130333.log
|
||||
@@ -1,29 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 62500 8N1, 300s, chip label 'patched-62500-retx' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-patched-62500-retx-20260719-131037.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 33.03s] progress: presses=245 (245/min) analog=515 maxGap=0.27s wedges=0
|
||||
[ 63.05s] progress: presses=568 (541/min) analog=992 maxGap=0.27s wedges=0
|
||||
[ 93.11s] progress: presses=709 (457/min) analog=1470 maxGap=0.27s wedges=0
|
||||
[ 95.30s] operator stop (Ctrl+C) — ending run, snapshotting counters...
|
||||
|
||||
== MASH SUMMARY [patched-62500-retx] ==
|
||||
run : 95s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 709 / 710 (440 presses/min)
|
||||
analog replies : 1522 (~1727 poll slots; 88.1%), sentinels 0
|
||||
framing / NAK : 61 / 321 (NAK-triggered resends: 321)
|
||||
gap histogram : <100ms:1510 100-250ms:9 250-500ms:2 0.5-1s:0 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.27s@1.8s, 0.25s@95.3s, 0.13s@77.1s, 0.13s@26.6s, 0.12s@26.3s, 0.12s@57.4s, 0.12s@12.7s, 0.12s@11.5s, 0.12s@18.8s, 0.12s@31.3s
|
||||
wedge events : 0
|
||||
counters before : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counters after : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : RestartCount=+0 AbandonCount=+0 FullBufferCount=+0
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : C:\VWE\riojoy\riomash-patched-62500-retx-20260719-131037.log
|
||||
@@ -1,28 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 62500 8N1, 300s, chip label 'patched-62500-sw' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-patched-62500-sw-20260719-132841.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 31.90s] progress: presses=115 (115/min) analog=492 maxGap=0.22s wedges=0
|
||||
[ 61.94s] progress: presses=224 (217/min) analog=968 maxGap=0.22s wedges=0
|
||||
[ 80.54s] operator stop (Ctrl+C) — ending run, snapshotting counters...
|
||||
|
||||
== MASH SUMMARY [patched-62500-sw] ==
|
||||
run : 80s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 285 / 285 (209 presses/min)
|
||||
analog replies : 1279 (~1454 poll slots; 88.0%), sentinels 0
|
||||
framing / NAK : 22 / 48 (command resends NAK/timeout: 67)
|
||||
gap histogram : <100ms:1261 100-250ms:17 250-500ms:0 0.5-1s:0 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.24s@80.5s, 0.22s@0.6s, 0.13s@47.9s, 0.13s@77.5s, 0.12s@21.2s, 0.12s@20.4s, 0.12s@22.9s, 0.11s@21.4s, 0.11s@49.3s, 0.11s@28.9s
|
||||
wedge events : 0
|
||||
counters before : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counters after : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : RestartCount=+0 AbandonCount=+0 FullBufferCount=+0
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : C:\VWE\riojoy\riomash-patched-62500-sw-20260719-132841.log
|
||||
@@ -1,27 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 62500 8N1, 300s, chip label 'patched-62500-sw' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, app auto-recovery DISABLED
|
||||
log: C:\VWE\riojoy\riomash-patched-62500-sw-20260719-133628.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 300s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 31.90s] progress: presses=72 (72/min) analog=497 maxGap=0.21s wedges=0
|
||||
[ 58.27s] operator stop (Ctrl+C) — ending run, snapshotting counters...
|
||||
|
||||
== MASH SUMMARY [patched-62500-sw] ==
|
||||
run : 58s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 123 / 123 (124 presses/min)
|
||||
analog replies : 931 (~1054 poll slots; 88.3%), sentinels 0
|
||||
framing / NAK : 7 / 20 (command resends NAK/timeout: 34)
|
||||
gap histogram : <100ms:921 100-250ms:9 250-500ms:0 0.5-1s:0 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.22s@58.3s, 0.21s@0.6s, 0.14s@41.9s, 0.14s@26.8s, 0.13s@23.6s, 0.11s@12.4s, 0.11s@24.4s, 0.10s@54.1s, 0.10s@53.4s, 0.10s@16.5s
|
||||
wedge events : 0
|
||||
counters before : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counters after : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : RestartCount=+0 AbandonCount=+0 FullBufferCount=+0
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : C:\VWE\riojoy\riomash-patched-62500-sw-20260719-133628.log
|
||||
@@ -1,28 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 9600 8N1, 120s, chip label 'patched-e0t5-9600' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, poll 55ms, app auto-recovery DISABLED
|
||||
log: c:\VWE\riojoy\riomash-patched-e0t5-9600-20260719-154259.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 120s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 31.88s] progress: presses=44 (44/min) analog=496 maxGap=0.63s wedges=0
|
||||
[ 61.96s] progress: presses=226 (219/min) analog=952 maxGap=0.63s wedges=0
|
||||
[ 91.97s] progress: presses=236 (154/min) analog=1428 maxGap=0.63s wedges=0
|
||||
|
||||
== MASH SUMMARY [patched-e0t5-9600] ==
|
||||
run : 120s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 236 / 236 (115 presses/min)
|
||||
analog replies : 1919 (~2181 poll slots; 88.0%), sentinels 0
|
||||
framing / NAK : 75 / 0 (command resends NAK/timeout: 69)
|
||||
gap histogram : <100ms:1911 100-250ms:5 250-500ms:0 0.5-1s:2 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.66s@121.8s, 0.63s@0.6s, 0.14s@40.3s, 0.13s@42.0s, 0.13s@37.2s, 0.11s@76.9s, 0.11s@58.3s, 0.10s@53.4s, 0.10s@112.9s, 0.10s@35.4s
|
||||
wedge events : 0
|
||||
counters before : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counters after : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : RestartCount=+0 AbandonCount=+0 FullBufferCount=+0
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : c:\VWE\riojoy\riomash-patched-e0t5-9600-20260719-154259.log
|
||||
@@ -1,28 +0,0 @@
|
||||
== RIO mash test :: COM1 @ 9600 8N1, 120s, chip label 'rio43rc1-9600' ==
|
||||
lamp echo ON (drives reply/lamp collisions), wedge threshold 2.0s, poll 55ms, app auto-recovery DISABLED
|
||||
log: c:\VWE\riojoy\riomash-rio43rc1-9600-20260719-164901.log
|
||||
firmware: 4.2
|
||||
counters before: RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
|
||||
>>> MASH NOW: two hands, 8 lamp buttons, as fast as you can. <<<
|
||||
>>> Test runs 120s. A wedge alarm will beep + banner. <<<
|
||||
>>> Ctrl+C ends the run early and still prints the summary. <<<
|
||||
|
||||
[ 31.92s] progress: presses=258 (258/min) analog=474 maxGap=0.64s wedges=0
|
||||
[ 61.98s] progress: presses=588 (569/min) analog=918 maxGap=0.64s wedges=0
|
||||
[ 91.98s] progress: presses=922 (601/min) analog=1356 maxGap=0.64s wedges=0
|
||||
|
||||
== MASH SUMMARY [rio43rc1-9600] ==
|
||||
run : 120s on COM1, lamps on, wedge threshold 2.0s
|
||||
firmware : 4.2
|
||||
presses/releases : 1129 / 1118 (549 presses/min)
|
||||
analog replies : 1811 (~2181 poll slots; 83.0%), sentinels 0
|
||||
framing / NAK : 298 / 0 (command resends NAK/timeout: 247)
|
||||
gap histogram : <100ms:1778 100-250ms:29 250-500ms:0 0.5-1s:3 1-2s:0 2-5s:0 >5s:0
|
||||
longest gaps : 0.68s@121.9s, 0.64s@0.6s, 0.57s@122.6s, 0.15s@32.2s, 0.14s@84.3s, 0.13s@52.3s, 0.13s@51.5s, 0.13s@93.5s, 0.13s@60.1s, 0.13s@15.2s
|
||||
wedge events : 0
|
||||
counters before : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counters after : RestartCount=0 AbandonCount=0 FullBufferCount=0 [board/lamp faults reported!]
|
||||
counter delta : RestartCount=+0 AbandonCount=+0 FullBufferCount=+0
|
||||
verdict : PASS — no wedge; compare gap histogram + counter delta to baseline
|
||||
log file : c:\VWE\riojoy\riomash-rio43rc1-9600-20260719-164901.log
|
||||
@@ -1,34 +0,0 @@
|
||||
# Why your pod's stick sometimes "died" mid-battle — and why it won't anymore
|
||||
|
||||
*A plain-language explanation for pod owners. The technical write-up is in
|
||||
[RIOv4_2-ANALYSIS.md](RIOv4_2-ANALYSIS.md).*
|
||||
|
||||
Inside every pod is a small computer board from 1994 (the **RIO**) whose job
|
||||
is to tell the game what you're doing: which buttons you pressed, and where
|
||||
you've moved the stick, throttle, and pedals.
|
||||
|
||||
The board and the game talk like two people on walkie-talkies. Every time the
|
||||
board reports your stick position, it waits for the game to answer *"got it."*
|
||||
During calm flying that works fine. But in a frantic firefight — lamps
|
||||
flashing, buttons hammering — the channel gets crowded, and once in a while
|
||||
that little *"got it"* gets drowned out.
|
||||
|
||||
When that happens, the board repeats itself a few times and then, sensibly,
|
||||
gives up and moves on. Here's the bug: while it's sending a position report,
|
||||
the board puts up a mental sticky note that says *"busy — already sending
|
||||
one."* When a report goes through normally, it takes the note down. But on the
|
||||
give-up path, the 1994 programmers forgot one line — **the note never comes
|
||||
down.** From that moment the board looks at the sticky note, decides it's still
|
||||
busy, and quietly throws away every stick and throttle update forever after.
|
||||
|
||||
That's the "wedge" you may have felt: mid-mission, your stick and throttle
|
||||
just stop doing anything, while the buttons and lamps still work fine —
|
||||
because those travel a different path that the sticky note doesn't block.
|
||||
Starting a new mission always cured it, since a new game tells the board to
|
||||
clean house, sticky note included.
|
||||
|
||||
The **RIO 4.3** firmware adds the one missing instruction: whenever the board
|
||||
gives up on a report, it now takes the sticky note down too. We stress-tested
|
||||
it by mashing buttons at over nine presses *per second* for two minutes
|
||||
straight — the old board wedged under that abuse; the new one never misses a
|
||||
beat.
|
||||
@@ -12,8 +12,8 @@ public sealed class AppConfig
|
||||
|
||||
/// <summary>
|
||||
/// RIO link baud rate. 9600 = stock/wedge-patched firmware (native-game
|
||||
/// compatible). 31250 = the validated FastRIO chip
|
||||
/// (rio-firmware/RIOv4_2_patched_31250v2.bin) — needs an FTDI-class
|
||||
/// compatible). 31250 = the FastRIO chip (TeslaRel410
|
||||
/// restoration/rio-firmware/RIOv4_3_fastrio.bin) — needs an FTDI-class
|
||||
/// adapter; classic 16550 UARTs cannot make this rate.
|
||||
/// </summary>
|
||||
public int RioBaudRate { get; set; } = 9600;
|
||||
|
||||
@@ -25,8 +25,8 @@ public sealed class SerialPortTransport : IRioTransport
|
||||
|
||||
/// <param name="portName">COM port name, e.g. "COM1".</param>
|
||||
/// <param name="baudRate">
|
||||
/// Link rate; default 9600 (stock firmware). The 31250-baud firmware
|
||||
/// variant (rio-firmware/, --baud31250) needs an adapter that can make
|
||||
/// Link rate; default 9600 (stock firmware). The 31250-baud FastRIO
|
||||
/// firmware (TeslaRel410 restoration/rio-firmware) needs an adapter that can make
|
||||
/// non-standard rates exactly — FTDI-class USB serial can, classic
|
||||
/// 16550 UARTs cannot.
|
||||
/// </param>
|
||||
|
||||
@@ -4,7 +4,7 @@ using RioJoy.Core.Serial;
|
||||
namespace RioSerialMonitor;
|
||||
|
||||
/// <summary>
|
||||
/// E0-threshold firmware verification (rio-firmware `--e0thresh` images).
|
||||
/// E0-threshold firmware verification (TeslaRel410 restoration/rio-firmware `--e0thresh` images).
|
||||
///
|
||||
/// Two phases against a DTR-reset board (counters zeroed, display F0000000):
|
||||
/// - Phase A (gate hold): analog requests answered NAK-then-ACK — NAKing
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace RioSerialMonitor;
|
||||
|
||||
/// <summary>
|
||||
/// Instrumented firmware mash test (the RIO_TAP protocol, mechanized) for
|
||||
/// validating the RIOv4.2 reply-latch wedge patch — see
|
||||
/// rio-firmware/RIOv4_2-ANALYSIS.md "Validation plan".
|
||||
/// validating the RIOv4.2 reply-latch wedge patch — see the firmware
|
||||
/// analysis in the TeslaRel410 repo (restoration/rio-firmware/RIOv4_2-ANALYSIS.md).
|
||||
///
|
||||
/// Runs the live link with the app's own >5s reset-recovery DISABLED (so a
|
||||
/// board wedge stays observable instead of being revived by our watchdog),
|
||||
|
||||
@@ -12,10 +12,10 @@ using RioJoy.Core.Serial;
|
||||
// Firmware wedge-patch validation (RIO_TAP mash test, see MashTest.cs):
|
||||
// dotnet run --project tools/RioSerialMonitor -- --mash [port] [seconds]
|
||||
// [--label baseline|patched] [--no-lamps] [--wedge seconds] [--baud rate]
|
||||
// --baud: 31250 for the retuned firmware variant (rio-firmware --baud31250).
|
||||
// --baud: 31250 for the retuned FastRIO firmware (TeslaRel410 restoration/rio-firmware, make_patch.py --baud31250).
|
||||
// Exit: 0 = ran, 2 = could not open the port (mash: 1 = wedge detected).
|
||||
|
||||
// E0-threshold firmware verification (rio-firmware --e0thresh images, see E0Test.cs):
|
||||
// E0-threshold firmware verification (TeslaRel410 restoration/rio-firmware --e0thresh images, see E0Test.cs):
|
||||
// dotnet run --project tools/RioSerialMonitor -- --e0test [port] [--baud rate] [--events n]
|
||||
if (args.Contains("--e0test"))
|
||||
return await RioSerialMonitor.E0Test.RunAsync(args);
|
||||
|
||||