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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 |
|
||||
|
||||
+10
-5
@@ -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
|
||||
|
||||
|
||||
@@ -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 <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 <x> <y> <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:<UdpPort>`) 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.
|
||||
Reference in New Issue
Block a user