From 94d32a1f065be28c593ce6fe564c0833a5213b6c Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 31 Jul 2026 20:13:55 -0500 Subject: [PATCH] docs: plasma/output integration guide for game and sim integrators Lamp address map by functional group (keypads have no lamps), 112x32 plasma model with auto-fit font rules and update semantics, rate-budget guidance, and recipes: rumble config, DCS Export.lua, SimHub, log tailing, vRIO/vPlasma bench testing. Cross-linked from FEEDBACK.md (now the wire reference) and README; corrects the lamp count there (72, per CockpitPanel, not 96). Co-Authored-By: Claude Fable 5 --- README.md | 1 + docs/FEEDBACK.md | 15 ++- docs/OUTPUT-INTEGRATION.md | 215 +++++++++++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+), 5 deletions(-) create mode 100644 docs/OUTPUT-INTEGRATION.md diff --git a/README.md b/README.md index 5fb14e6..f7c90f0 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Red Planet — talk to the RIO directly and do not use this app.) | [`docs/PLAN.md`](docs/PLAN.md) | Full modernization plan | | [`docs/PROTOCOL.md`](docs/PROTOCOL.md) | RIO wire format + `iRIO` input-map reference | | [`docs/FEEDBACK.md`](docs/FEEDBACK.md) | Game→cockpit feedback endpoint (lamps + plasma over pipe/UDP, rumble) | +| [`docs/OUTPUT-INTEGRATION.md`](docs/OUTPUT-INTEGRATION.md) | Integrator's guide: lamp address map, plasma display model, per-game recipes | | _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/FEEDBACK.md b/docs/FEEDBACK.md index e3a4968..4f7f66f 100644 --- a/docs/FEEDBACK.md +++ b/docs/FEEDBACK.md @@ -1,11 +1,16 @@ # Game feedback endpoint (game → RIOJoy → cockpit) Phase 9 lets external programs drive the cockpit's **output** hardware through -the running RIOJoy tray app: the 96 lamps (with board-side flash) and the -plasma/VFD text display. Anything that can write a line of text to a named pipe -or a UDP socket can use it — a DCS `Export.lua`, a SimHub plugin, a game mod, a -PowerShell one-liner. XInput **rumble** is mapped separately (no client needed; -see [Rumble → lamps](#rumble--lamps)). +the running RIOJoy tray app: the 72 button lamps (with board-side flash) and +the plasma/VFD text display. Anything that can write a line of text to a named +pipe or a UDP socket can use it — a DCS `Export.lua`, a SimHub plugin, a game +mod, a PowerShell one-liner. XInput **rumble** is mapped separately (no client +needed; see [Rumble → lamps](#rumble--lamps)). + +This page is the wire-protocol reference. For the integrator's view — which +lamp addresses map to which physical buttons, the plasma display's geometry +and fonts, and per-game recipes — see +[OUTPUT-INTEGRATION.md](OUTPUT-INTEGRATION.md). ## Endpoints diff --git a/docs/OUTPUT-INTEGRATION.md b/docs/OUTPUT-INTEGRATION.md new file mode 100644 index 0000000..bac610f --- /dev/null +++ b/docs/OUTPUT-INTEGRATION.md @@ -0,0 +1,215 @@ +# Plasma & output integration guide + +How to make a game, sim, or companion tool drive the cockpit's **outputs** +through RIOJoy: the lighted buttons (lamps) and the plasma/VFD text display. +This is the integrator's view — what the hardware can show, which addresses +mean what, and how to feed them. The exact wire grammar lives in +[FEEDBACK.md](FEEDBACK.md); the RIO serial protocol in +[PROTOCOL.md](PROTOCOL.md). + +## The four output channels + +| Channel | Trigger | Effort | Good for | +|---|---|---|---| +| **Automatic lamp feedback** | button press/release | none — built in | lighting the button the player just pressed | +| **Feedback endpoint** (`lamp` / `plasma` lines) | your code writes a text line to a pipe or UDP | small script | warning lights, status flashes, callsigns, scores | +| **Rumble → lamps** | game sets XInput vibration | config only, no code | damage/fire effects from **unmodified** games | +| **Plasma greeting** | profile activation | config only | a static per-game banner | + +All four apply only while a profile is **active**; the profile opts into the +endpoint and rumble channels with its `"Feedback"` JSON section (see +[Gating in FEEDBACK.md](FEEDBACK.md#gating)). When RIOJoy is dormant or a +native game owns the ports, endpoint clients stay connected but commands drop. + +## The lamp model + +### What a lamp is + +Each of the **72 cockpit buttons** (RIO addresses `0x00–0x47`) has a built-in +lamp. A lamp is set with a single state byte combining a **flash mode** +(solid / slow / med / fast) and a **brightness** (off / dim / bright). The +**board runs the blink itself** — one command starts a sustained flash, another +ends it. There is no per-frame cost to a flashing lamp. + +The two 4×4 keypads (`0x50–0x5F` internal, `0x60–0x6F` external) are valid +protocol addresses but have **no physical lamps** — writes to them are +accepted and do nothing visible. + +### Address map (functional groups) + +From the panel model (`RioJoy.Core.Editing.CockpitPanel`, mirrored in the +profile editor): + +| Group | Addresses | Layout | +|---|---|---| +| Lower Right MFD | `0x00–0x07` | 4×2; top row `07 06 05 04`, bottom `03 02 01 00` | +| Lower Left MFD | `0x08–0x0F` | 4×2; top `0F 0E 0D 0C`, bottom `0B 0A 09 08` | +| Secondary column | `0x10–0x17` | vertical 8 | +| Screen column | `0x18–0x1F` | vertical 8 | +| Upper Middle MFD | `0x20–0x27` | 4×2; top `27 26 25 24`, bottom `23 22 21 20` | +| Upper Left MFD | `0x28–0x2F` | 4×2; top `2F 2E 2D 2C`, bottom `2B 2A 29 28` | +| Upper Right MFD | `0x30–0x37` | 4×2; top `37 36 35 34`, bottom `33 32 31 30` | +| Throttle column | `0x38–0x3F` | vertical 8 (`3D` Panic, `3F` Throttle) | +| Joystick cluster | `0x40–0x47` | `40` Main, `41–44` hat B/U/R/L, `45` Pinky, `46` Middle, `47` Upper | +| Internal keypad | `0x50–0x5F` | 4×4, **no lamps** | +| External keypad | `0x60–0x6F` | 4×4, **no lamps** | + +(`0x48–0x4F` is a gap — not valid addresses.) + +### Ownership: pick lamps the profile doesn't use + +Lamps on buttons the active profile maps as *lighted* (`Lit` in the editor, +`iRIO` bit `0x8000`) belong to the automatic press/release feedback — endpoint +writes to them are **dropped** (logged once per address) so your effect can't +fight the built-in behavior. Design your effects on buttons the profile leaves +unlit — a dedicated "warning" MFD cluster the game doesn't bind, for example — +or deliberately leave the target buttons un-Lit in the profile. + +### Rate budget + +The RIO link is 9600 baud, shared with the ~55 ms analog poll. RIOJoy +coalesces lamp state per address (latest wins) and sends **at most one changed +lamp per 25 ms** (~40/s). Practical consequences: + +- Send *state changes*, not periodic refreshes. Repeating the current state + costs nothing but also does nothing. +- A whole-panel effect (`lamp-all`, or sweeping many addresses) takes ~25 ms × + changed-lamp-count to fully land — a 104-lamp sweep is ~3 s. Fine for an + attract mode; wrong for a fast strobe. For fast effects, use the board's own + flash modes on a few lamps instead. + +## The plasma display model + +The plasma/VFD is a **112 × 32 pixel** text display on its own serial port +(so text updates never contend with the input link). Two glyph sizes exist: + +| Font | Cell | Fits per line | Auto-selected when | +|---|---|---|---| +| large (font 5) | 10×14 px | ~11 chars | text ≤ 9 chars | +| small (font 2) | 5×7 px | ~22 chars | text 10–20 chars (longer is truncated to 20) | + +What the endpoint exposes (v1): + +- **`plasma text `** — auto-fit: short text renders large, longer text + small, centered on the display (the `PlasmaPosText` behavior the original + games used). This is the right default for callsigns, scores, and status + words. +- **`plasma text `** — explicit cursor position in **pixels** + (top-left origin). You choose the position; the font still auto-fits by + length. Use this to keep two fields on screen at once (e.g. callsign top + line, score bottom line: `plasma text 2 2 "VIPER 1-1"` + + `plasma text 2 18 "SCORE 4200"`). +- **`plasma clear`** — blank the display. + +Text is **Latin-1** (one byte per char) — don't send UTF-8. + +Not exposed through the endpoint yet (the underlying `PlasmaCommands` port has +them, so they're a small extension when needed): explicit font/attribute +selection and box draw/fill. + +### Update semantics + +Plasma writes are **single-flight, latest-pending-wins**: while one text is +being written, only the *newest* pending command survives. Flooding a score +update every frame is safe — the display shows the latest value — but +interleaving *two different fields* at high rate from one client can starve +one of them. Update fields on change, not on a timer. + +Lifecycle: on profile activation the display clears and shows the profile's +`PlasmaGreeting` (if set); your first `plasma` command replaces it. On +teardown (profile switch, dormancy, native-game yield) RIOJoy blanks the +display and releases the port. + +`plasma text` does **not** clear the rest of the display — it draws at a +position. When a new value is shorter than the old one (`SCORE 900` after +`SCORE 1200`), stale pixels can remain; pad the text to a fixed width or +`plasma clear` first when the layout changes. + +## Recipes + +### Any XInput game — rumble, zero code + +Add to the profile's JSON (`%APPDATA%\RIOJoy\config.json`): + +```json +"Feedback": { "Rumble": { "LargeMotorLamps": [32, 33], "SmallMotorLamps": [34], "Threshold": 24 } } +``` + +Vibration now flashes Upper-Middle-MFD lamps `0x20–0x22`: off below the +threshold, slow/med/fast flash as intensity rises, per motor. Works with any +game that rumbles the ViGEm pad (net48 flavor only). + +### DCS World — Export.lua + +A pipe opens as a file on Windows; write lines, flush, done: + +```lua +local rio = io.open("\\\\.\\pipe\\riojoy-feedback", "w") +local wasCaution = nil + +function LuaExportAfterNextFrame() + if not rio then return end + local caution = LoGetMCPState and LoGetMCPState().MasterWarning + if caution ~= wasCaution then -- send changes, not frames + wasCaution = caution + rio:write(caution and "lamp 0x12 fast bright\n" or "lamp 0x12 off\n") + rio:flush() + end +end + +function LuaExportStart() + if rio then + rio:write('plasma text "' .. (LoGetPilotName() or "PILOT") .. '"\n') + rio:flush() + end +end +``` + +(Or configure `"UdpPort"` and use DCS's `socket` library — same lines over +UDP, one or more per datagram.) + +### SimHub / other telemetry hubs + +Any plugin that can emit custom TCP/UDP/serial output can target the UDP +endpoint (`127.0.0.1:`) with protocol lines. Map telemetry properties +to `lamp` lines (e.g. shift light → `lamp 0x3D fast bright`) and text +properties to `plasma text`. + +### Games with no API — log tailing + +```powershell +$p = New-Object IO.Pipes.NamedPipeClientStream '.', 'riojoy-feedback', ([IO.Pipes.PipeDirection]::Out) +$p.Connect(2000) +$w = New-Object IO.StreamWriter $p, ([Text.Encoding]::GetEncoding(28591)) +Get-Content 'C:\games\thegame\events.log' -Wait -Tail 0 | ForEach-Object { + if ($_ -match 'PLAYER_HIT') { $w.WriteLine('lamp 0x24 fast bright'); $w.Flush() } + if ($_ -match 'SCORE=(\d+)') { $w.WriteLine("plasma text SCORE $($Matches[1])"); $w.Flush() } +} +``` + +## Testing without hardware + +Both output ports accept `pipe:` endpoints, so the [vRIO](https://gitea.mysticmachines.com/VWE/VRIO) +emulators stand in for the cabinet: + +- Profile `RioComPort: "pipe:vrio"` → vRIO's board emulator; its panel shows + lamp states, including flash. +- Profile `PlasmaComPort: "pipe:vplasma"` → the vPlasma display emulator + renders the 112×32 output. On a machine with neither display nor COM2, set + `PlasmaComPort: "off"` instead (the app default is `COM2`). + +Then drive the feedback pipe from PowerShell (snippets in +[FEEDBACK.md](FEEDBACK.md#client-snippets)) and watch the emulators. + +## Checklist for a new integration + +1. Give the game's profile a `"Feedback"` section (it's off otherwise). +2. Choose effect lamps that the profile does **not** mark Lit; note their + addresses from the map above. +3. Decide plasma layout: one auto-centered field, or fixed pixel positions for + multiple fields (pad to fixed width). +4. Emit on **state change** only; let the board do the blinking. +5. Reconnect logic: on pipe write failure, close, reopen, retry — RIOJoy's + endpoint accepts reconnects forever, and commands sent while dormant are + dropped by design (your client doesn't need to track RIOJoy's state). +6. Bench-test against `pipe:vrio` / `pipe:vplasma` before touching the cabinet.