diff --git a/README.md b/README.md index 2d2cd1b..87bdf2e 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/docs/hardware/README.md b/docs/hardware/README.md deleted file mode 100644 index 858b184..0000000 --- a/docs/hardware/README.md +++ /dev/null @@ -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. diff --git a/docs/hardware/display-board-1408.md b/docs/hardware/display-board-1408.md deleted file mode 100644 index db44d77..0000000 --- a/docs/hardware/display-board-1408.md +++ /dev/null @@ -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` diff --git a/docs/hardware/gal/GAL20v8a_5764.JED b/docs/hardware/gal/GAL20v8a_5764.JED deleted file mode 100644 index fb43e5c..0000000 --- a/docs/hardware/gal/GAL20v8a_5764.JED +++ /dev/null @@ -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 -*L01024 11111111110111110110111111111111 -*L01056 11111111111111111101111110011011 -*L01088 11111111111111111111111111011111 -*L01280 11111111111111111111111111111111 -*L01312 11111111100101101011101110111011 -*L01344 10111011100110010000000000000000 -*L01600 11111111111111111111111111111111 -*L01632 11111111100101101011101110111011 -*L01664 10111011100110100000000000000000 -*L02240 11111111111111111111111111111111 -*L02272 11111111010111111111111111111111 -*L02304 11111111111111110000000000000000 -*L02560 01000000010101010011011100101110 -*L02592 00101110001011100010111000000000 -*L02624 00000000111111111111111111111111 -*L02656 11111111111111111111111111111111 -*L02688 111111111111111111 -*C5764 -*F676 diff --git a/docs/hardware/gal/README.md b/docs/hardware/gal/README.md deleted file mode 100644 index 17f6e57..0000000 --- a/docs/hardware/gal/README.md +++ /dev/null @@ -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. diff --git a/docs/hardware/gal/galdecode.py b/docs/hardware/gal/galdecode.py deleted file mode 100644 index cba3afb..0000000 --- a/docs/hardware/gal/galdecode.py +++ /dev/null @@ -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)") diff --git a/docs/hardware/gal/u7-pin-names.txt b/docs/hardware/gal/u7-pin-names.txt deleted file mode 100644 index 3ba5c11..0000000 --- a/docs/hardware/gal/u7-pin-names.txt +++ /dev/null @@ -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 diff --git a/docs/hardware/photos/amp-board-1413-component-side.jpeg b/docs/hardware/photos/amp-board-1413-component-side.jpeg deleted file mode 100644 index 3c81490..0000000 Binary files a/docs/hardware/photos/amp-board-1413-component-side.jpeg and /dev/null differ diff --git a/docs/hardware/photos/amp-board-1413-pyramid-inputs.jpeg b/docs/hardware/photos/amp-board-1413-pyramid-inputs.jpeg deleted file mode 100644 index 523deda..0000000 Binary files a/docs/hardware/photos/amp-board-1413-pyramid-inputs.jpeg and /dev/null differ diff --git a/docs/hardware/photos/amp-board-1413-pyramid-outputs.jpeg b/docs/hardware/photos/amp-board-1413-pyramid-outputs.jpeg deleted file mode 100644 index c54793e..0000000 Binary files a/docs/hardware/photos/amp-board-1413-pyramid-outputs.jpeg and /dev/null differ diff --git a/docs/hardware/photos/amp-board-1413-solder-side.jpeg b/docs/hardware/photos/amp-board-1413-solder-side.jpeg deleted file mode 100644 index d582514..0000000 Binary files a/docs/hardware/photos/amp-board-1413-solder-side.jpeg and /dev/null differ diff --git a/docs/hardware/photos/buttons-board-1401-component-side.jpeg b/docs/hardware/photos/buttons-board-1401-component-side.jpeg deleted file mode 100644 index 90f32c3..0000000 Binary files a/docs/hardware/photos/buttons-board-1401-component-side.jpeg and /dev/null differ diff --git a/docs/hardware/photos/buttons-board-1401-solder-side.jpeg b/docs/hardware/photos/buttons-board-1401-solder-side.jpeg deleted file mode 100644 index 1aead52..0000000 Binary files a/docs/hardware/photos/buttons-board-1401-solder-side.jpeg and /dev/null differ diff --git a/docs/hardware/photos/display-board-1408-back-driver.jpeg b/docs/hardware/photos/display-board-1408-back-driver.jpeg deleted file mode 100644 index bd55569..0000000 Binary files a/docs/hardware/photos/display-board-1408-back-driver.jpeg and /dev/null differ diff --git a/docs/hardware/photos/display-board-1408-front-digits.jpeg b/docs/hardware/photos/display-board-1408-front-digits.jpeg deleted file mode 100644 index 111ad66..0000000 Binary files a/docs/hardware/photos/display-board-1408-front-digits.jpeg and /dev/null differ diff --git a/docs/hardware/photos/encoder-heds5700-control-assembly.jpeg b/docs/hardware/photos/encoder-heds5700-control-assembly.jpeg deleted file mode 100644 index b29bf35..0000000 Binary files a/docs/hardware/photos/encoder-heds5700-control-assembly.jpeg and /dev/null differ diff --git a/docs/hardware/photos/keypad-1402-front-keys.jpeg b/docs/hardware/photos/keypad-1402-front-keys.jpeg deleted file mode 100644 index 89dae75..0000000 Binary files a/docs/hardware/photos/keypad-1402-front-keys.jpeg and /dev/null differ diff --git a/docs/hardware/photos/keypad-board-1402-back-logic.jpeg b/docs/hardware/photos/keypad-board-1402-back-logic.jpeg deleted file mode 100644 index 9c7b46c..0000000 Binary files a/docs/hardware/photos/keypad-board-1402-back-logic.jpeg and /dev/null differ diff --git a/docs/hardware/photos/rio-board-1407-component-side.jpeg b/docs/hardware/photos/rio-board-1407-component-side.jpeg deleted file mode 100644 index 471dfde..0000000 Binary files a/docs/hardware/photos/rio-board-1407-component-side.jpeg and /dev/null differ diff --git a/docs/hardware/photos/rio-board-1407-solder-side.jpeg b/docs/hardware/photos/rio-board-1407-solder-side.jpeg deleted file mode 100644 index 3993e1e..0000000 Binary files a/docs/hardware/photos/rio-board-1407-solder-side.jpeg and /dev/null differ diff --git a/docs/hardware/schematics/Scans_018-014.pdf b/docs/hardware/schematics/Scans_018-014.pdf deleted file mode 100644 index be55bab..0000000 Binary files a/docs/hardware/schematics/Scans_018-014.pdf and /dev/null differ diff --git a/rio-firmware/README.md b/rio-firmware/README.md deleted file mode 100644 index 27abd47..0000000 --- a/rio-firmware/README.md +++ /dev/null @@ -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. diff --git a/rio-firmware/RIOv4_2-ANALYSIS.md b/rio-firmware/RIOv4_2-ANALYSIS.md deleted file mode 100644 index 4bc0404..0000000 --- a/rio-firmware/RIOv4_2-ANALYSIS.md +++ /dev/null @@ -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-