CydandClaude Fable 5 697cf3129b Pipe endpoints join the connection pickers; no more COM11/COM12 auto-bind
Each device row now serves one endpoint at a time, picked from a list of
pipe:vrio / pipe:vplasma (first, default) plus the machine's COM ports —
nothing opens automatically at startup. The pipe services gained an
IsListening property and are started/stopped by the row's Open/Close
button instead of running for the whole app lifetime, which also retires
the dual-transport warning. Status line shows the active endpoint;
OFFLINE when none. Standalone vPLASMA is unchanged (hardwired COM12 +
permanent pipe). Also fixes the xUnit1031 warnings in the plasma pipe
tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 23:17:34 -05:00

vRIO — virtual RIO cockpit device

A software replica of the cockpit RIO (Remote Input/Output) board: it opens a COM port and speaks the device side of the RIO serial protocol, so any host that expects the real hardware — most importantly RIOJoy — can talk to it without a cockpit attached. (The cockpit's other serial device gets the same treatment: see vPLASMA below.)

The window is an interactive version of the cockpit control panel that RIOJoy's profile editor draws (the same functional layout from the original Win32 RIO design: five MFD clusters, four board columns, two keypads, encoder gauges). But where the editor edits bindings, vRIO's cells are the physical controls:

  • Left-click a cell — momentary button press (ButtonPressed/Released or KeyPressed/Released on the wire). Right-click — latch it down.
  • Drag the X/Y box and the Z / L / R gauges — the five analog axes, returned by the next AnalogReply (14-bit signed, 7-bit-pair packed). Gauges span each axis' realistic hardware travel, not the full 14-bit range: Z/L/R rest at raw 0 at the gauge bottom and travel to 800 (throttle — forward travel runs negative, matching RIOJoy's ratchet) or +500 (spring-loaded pedals), and the stick covers ±80 around center — the windows RIOJoy's calibrator expects.
  • Cells shade to the lamp state the host commands (LampRequest: off / dim / bright, with slow/med/fast flash), so RIOJoy's press-feedback lights the on-screen panel just like the real buttons.
  • Lamps can mirror onto an RGB keyboard (Mirror lamps on RGB keyboard, via Windows 11 Dynamic Lighting): keys bound to lamp-capable buttons glow with the panel's palette and blink in step with the on-screen flash modes; per-key keyboards get the full button field, zone-lit keyboards show the strongest current lamp board-wide, and a picker narrows the mirror to one keyboard when several are attached. For the LEDs to stay lit while the game has focus, vRIO needs package identity: run pkg\Register-vRIO.ps1 once (Developer Mode required; pass the exe folder for a deployed copy), launch vRIO from its Start menu entry (a direct exe launch runs without identity), then drag vRIO to the top of Settings → Personalization → Dynamic Lighting → Background light control.
  • Keyboard and Xbox (XInput) controller input drive the same controls through a bindings file (%APPDATA%\vRIO\bindings.txt, created with commented defaults on first run — Edit bindings… opens it, Reload bindings applies edits live). Keys and pad buttons press any RIO address; pad sticks/triggers and keys drive the axes in each axis' realistic travel window, with deflect (spring-back), rate (throttle-style, position holds), deadzone, and invert options. The default profile makes the controller mandatory: all five axes live on the pad (left stick / triggers / right stick = stick / pedals / throttle) and the keyboard covers the button field — number row + QWERTY row = the upper MFD bank, home + bottom rows = the lower MFD bank (4-key blocks split by an unbound gap key), F1F6 / F7F12 = the Secondary / Screen columns, numpad = internal keypad (hex keys on the operators), arrows + Space = hat + main, with ABXY / dpad / shoulders on the pad's named buttons.
  • Capture one keyboard in the background (Capture keyboard picker in the Input panel): by default keyboard input follows the focused window, so vRIO goes deaf the moment the sim takes focus. Selecting a specific keyboard instead taps it through the Windows Raw Input API (RIDEV_INPUTSINK), so its keys drive the panel while the game has focus — the input-side twin of the lamp mirror writing that keyboard's LEDs under the same condition. Raw Input observes without intercepting: the keystroke still reaches the focused app, so this is meant for a keyboard dedicated to the panel, not the one you also type on. Leaving the picker on All keyboards keeps the plain focus-only behavior. (No package identity needed — unlike the lamp side, this is a plain Win32 tap.)

Wire behavior

Protocol per riojoy/docs/PROTOCOL.md (9600 8N1, [cmd][payload…][7-bit checksum], ACK 0xFC / NAK 0xFD / RESTART 0xFE / IDLE 0xFF), with device behavior grounded in the real v4.2 firmware dump (riojoy/rio-firmware/RIOv4_2-ANALYSIS.md):

  • ACKs every well-formed packet; NAKs bad-checksum packets.
  • TX is paced at the wire rate — one byte per 10-bit frame (~1.04 ms at 9600 8N1), never closer. A virtual null-modem has no UART, so unpaced writes would land at the host in microsecond bursts no real board could produce; vRIO's writer thread schedules each byte against a monotonic slot deadline instead, so e.g. the 51-byte CheckRequest response takes the same ~53 ms it takes real hardware.
  • CheckRequest → the real board's init handshake: TestModeChange enter, one BoardOk CheckReply per board (the 11 boards from the legacy firmware's table), then TestModeChange exit. Hosts wait (≤5 s per step) on both test-mode packets and send nothing while test mode is active, so the exit is mandatory. VersionRequest → configurable version, default 4.2.
  • ResetRequest re-zeroes the targeted axis (or all).
  • A NAK re-sends the last event up to 4 times, then gives up with a RESTART byte — the real board's retry budget.
  • Optional v4.2 reply-wedge emulation (in VRio.Core; the UI toggles were removed): after retry exhaustion, analog requests are silently dropped — still ACK'd, RX path alive — until a host ResetRequest, reproducing the latch-leak fault the firmware analysis documents.

Using it with RIOJoy on one PC

The two apps need a crossed serial link. Install a com0com virtual null-modem pair (e.g. COM1 ⇄ COM11), then:

  1. Run VRio.App, pick COM11, Open.
  2. RIOJoy will always point to COM1.

RIOJoy's DTR open-pulse shows up in the wire log (DSR handshake), its ~55 ms analog polling drives the "analog polls served" counter, and every click on the vRIO panel arrives at RIOJoy as real cockpit input. Two physical PCs with a null-modem cable work the same way.

Named pipes for DOSBox-X (no com0com)

When the game runs in the patched DOSBox-X (the fork's namedpipe serial backend), the virtual ports aren't needed at all: each device's connection picker lists its named-pipe endpoint — pipe:vrio, pipe:vplasma — alongside the machine's COM ports. Select it and Open to serve \\.\pipe\vrio / \\.\pipe\vplasma; DOSBox-X connects as the client, retrying in the background until the pipe exists:

[serial]
serial1 = namedpipe pipe:vrio    rxpollus:100 rxburst:16
serial2 = namedpipe pipe:vplasma

A pipe is a plain byte stream, so serial data and modem lines travel as typed frames (0x00 len bytes data, 0x01 bits DTR/RTS) — the game's DTR reset pulse keeps its exact position in the byte stream, and a disconnect reads as all-lines-low (cable unplugged). The contract lives in src/VRio.Core/Device/PipeFraming.cs on this side and serialnamedpipe.h on the fork's. TX is paced exactly like the COM path — with the 9600-baud wire gone, the pacer is the only thing between the host and an impossible burst. Each row serves one endpoint at a time — the RIO is a single-host device — and nothing opens automatically at startup; the COM path is unchanged for RIOJoy and real pods. vRIO's built-in glass and the standalone vPLASMA share the vplasma pipe name — whoever starts second retries until the name frees up.

vPLASMA — the companion plasma display

The cockpit's second serial device is the plasma display: a 128×32 dot-matrix panel on COM2 (9600 8N1, no flow control) that the game draws mission text and status graphics on. The software replica comes in two forms. Built into vRIO: the panel's encoder strip hosts the glass at top left, with its own connection row in the control strip (label colour = connection status; picker offers pipe:vplasma and the COM ports). And standalone, VPlasma.App: a bare-glass window that opens COM12 (the device end of the plasma's null-modem pair) on startup — retrying while the port is missing or busy, port status in the title bar. Both decode the display's command stream and render the dot matrix in plasma orange, text mode included; only one can hold COM12 at a time.

The command set was recovered from two Tesla 4.10 artifacts: CODE\RP\MUNGA_L4\L4PLASMA.CPP (the game's driver — it renders everything into a local 1bpp buffer and streams changed rows as ESC P graphics writes, hiding the cursor with ESC G 0 at boot) and the factory test tool VWETEST\VGLTEST\PLASMA.EXE (the text mode: BS/HT/LF/VT/CR cursor motion, ESC @ clear, ESC L home, ESC G cursor visibility, ESC K font select, ESC H attributes — intensity/underline/reverse/flash). The full recovered grammar lives in src/VPlasma.Core/Protocol/PlasmaProtocol.cs.

  • GraphicsESC P screen y xbyte width rows + packed 1bpp row data, MSB = leftmost pixel. This is everything the game sends, so it is the wire path a pod's plasma actually sees.
  • Text — printable ASCII renders through a 5×7 font at a cursor: fonts 03 give a 21×4 cell grid, fonts 47 the same glyphs doubled to 10×2. Half-intensity draws dimmer, reverse/underline render in the cell, flashing text (and the flashing cursor) blink on the glass. The panel's own ROM glyphs are lost with the hardware, so the classic public-domain 5×7 set stands in.
  • Double-click the glass to cycle three self-test pages (banner, charset, graphics pattern) through the same parser the wire feeds — useful without a host. Right-click resets the display to its power-on state.
  • Pair it with the game like vRIO ↔ RIOJoy: a second com0com null-modem pair COM2 ⇄ COM12 — the game's plasma output writes COM2, vPLASMA listens on COM12. The plasma never talks back, so there is no transmit path to pace.

Repository layout

Path Contents
src/VRio.Core Protocol framing/builder/parser, the VRioDevice state machine, serial pump, panel layout data (class library)
src/VRio.App WinForms panel UI
src/VPlasma.Core Plasma command-stream parser, the VPlasmaDevice display state machine, 5×7 font, serial listener (class library)
src/VPlasma.App WinForms dot-matrix display UI
pkg Sparse-package manifest + registration script: grants VRio.App.exe the package identity Dynamic Lighting's background-control list requires
tests/VRio.Core.Tests xUnit tests for the protocol + device engine
tests/VPlasma.Core.Tests xUnit tests for the plasma parser + display engine

Building

Same toolchain as RIOJoy: .NET SDK (8.0+) with the .NET Framework 4.8 targeting pack; apps target net48 so deployed builds run in-box on Windows 10/11.

dotnet build VRio.sln -c Release
dotnet test VRio.sln

Interop is additionally verified against RIOJoy's real RioSerialLink (version/check/analog/lamp/button/keypad/reset round-trips over an in-memory transport) — see the RIOJoy repo for the host side.

S
Description
No description provided
Readme
4 MiB
Languages
C# 97.4%
PowerShell 1.8%
QMake 0.8%