plasma: ESC P bitmap rows through the feedback endpoint (plasma row)
PlasmaCommands.GraphicsWrite/GraphicsRow port the display firmware graphics command (ESC P s y x w h, MSB-left); PlasmaDisplay.RowAsync writes a locked whole-row update; the line protocol gains `plasma row <y> <hex32>`. The router plasma slot becomes a bounded FIFO queue: rows stream in order (a frame must not tear), texts still coalesce to the newest, clear flushes, cap 128 with counted drops. Docs updated; 442 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+14
-3
@@ -47,6 +47,7 @@ lamp <addr> <state> set one lamp
|
||||
lamp-all <state> set every lamp
|
||||
plasma text [x y] <text> write text to the plasma display
|
||||
plasma clear clear the plasma display
|
||||
plasma row <y> <hex32> write one full 128-px bitmap row
|
||||
```
|
||||
|
||||
- **`<addr>`** — RIO lamp address, decimal or `0x` hex. Valid: `0x00–0x47`
|
||||
@@ -63,6 +64,15 @@ plasma clear clear the plasma display
|
||||
(`PlasmaPosText`). To display something that starts with two numbers, quote
|
||||
it. Encoding is **Latin-1** (one byte = one char, the plasma's wire
|
||||
encoding) — do not send UTF-8 for accented characters.
|
||||
- **`plasma row`** — one full bitmap row: `<y>` 0–31 (decimal or `0x` hex),
|
||||
then exactly **32 hex digits** = 16 bytes = 128 pixels, **MSB = leftmost**.
|
||||
Rows are written strictly in arrival order (unlike `plasma text`, which
|
||||
coalesces to the newest — a bitmap frame is many rows and must not tear);
|
||||
`plasma clear` discards any queued rows/text. Up to 128 commands queue;
|
||||
beyond that incoming rows are dropped and counted — pace full-frame pushes
|
||||
(a 32-row frame is ~0.77 s of wire time at 9600 baud; stream changed rows,
|
||||
as the native games did). See
|
||||
[OUTPUT-INTEGRATION.md](OUTPUT-INTEGRATION.md#bitmap-graphics-esc-p).
|
||||
|
||||
Malformed lines are dropped and counted (first few are logged); they **never**
|
||||
cost a client its connection. The endpoint sends no replies.
|
||||
@@ -99,9 +109,10 @@ Such drops are logged once per address. `lamp-all` silently skips owned lamps.
|
||||
**Rate:** lamp commands share the 9600-baud RIO link with the ~55 ms analog
|
||||
poll, so RIOJoy coalesces per-lamp state (latest wins) and sends at most one
|
||||
*changed* lamp per 25 ms. Spam freely — identical states cost nothing — but a
|
||||
`lamp-all` sweep takes ~3 s to fully land. Plasma writes are single-flight,
|
||||
latest-pending-wins: flood-updating a score means only the newest pending text
|
||||
is written.
|
||||
`lamp-all` sweep takes ~3 s to fully land. Plasma writes are single-flight
|
||||
over a bounded queue: flooded `text` updates coalesce to the newest value,
|
||||
`clear` flushes everything queued before it, and bitmap `row`s stream in
|
||||
order.
|
||||
|
||||
## Rumble → lamps
|
||||
|
||||
|
||||
+31
-12
@@ -107,11 +107,13 @@ What the endpoint exposes (v1):
|
||||
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.
|
||||
- **`plasma row <y> <hex32>`** — one full 128-px bitmap row; see
|
||||
[Bitmap graphics](#bitmap-graphics-esc-p).
|
||||
|
||||
Text is **Latin-1** (one byte per char) — don't send UTF-8.
|
||||
|
||||
Not exposed through the endpoint yet (small extensions when needed): explicit
|
||||
font/attribute selection, box draw/fill, and the bitmap mode below.
|
||||
font/attribute selection and box draw/fill.
|
||||
|
||||
### Bitmap graphics (`ESC P`)
|
||||
|
||||
@@ -134,20 +136,37 @@ game diffs and streams only changed rows — an animation that touches a few
|
||||
rows per tick is smooth; full-frame repaints are ~1 fps. Text mode is far
|
||||
cheaper for text; reserve bitmaps for logos, custom gauges, and icons.
|
||||
|
||||
Status: the hardware and the vPlasma emulator fully support `ESC P`, but
|
||||
RIOJoy's `PlasmaCommands` port and the feedback endpoint don't expose it yet.
|
||||
The natural extension is a line command (e.g.
|
||||
`plasma row <y> <32-hex-digit row>` or a base64 block form) — if an
|
||||
integration needs it, that plus a `PlasmaCommands.GraphicsWrite` builder is a
|
||||
small, self-contained addition.
|
||||
The endpoint exposes whole-row writes as a line command:
|
||||
|
||||
```
|
||||
plasma row <y> <32 hex digits>
|
||||
```
|
||||
|
||||
`y` is 0–31; the 32 hex digits are the row's 16 bytes left-to-right, MSB =
|
||||
leftmost pixel. Rows stream strictly in arrival order (up to 128 queued;
|
||||
overflow drops the incoming row and counts it), so push a frame as rows 0–31
|
||||
and it lands intact. Example — a horizontal rule across row 16 and a lit
|
||||
top-left corner block:
|
||||
|
||||
```
|
||||
plasma row 16 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
plasma row 0 F0000000000000000000000000000000
|
||||
```
|
||||
|
||||
For animation, keep a 1-bpp frame buffer client-side and send only the rows
|
||||
that changed since the last tick — exactly what the native game does. The
|
||||
partial-span form of `ESC P` (arbitrary `x/w/h`) exists in
|
||||
`PlasmaCommands.GraphicsWrite` for host-side code but is not exposed as a
|
||||
line command.
|
||||
|
||||
### 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.
|
||||
Plasma writes are **single-flight over a bounded queue** whose rules follow
|
||||
what each command means: flooded `text` updates coalesce to the newest value
|
||||
(safe to spam a score — the display shows the latest), `clear` discards
|
||||
everything queued before it, and bitmap `row`s stream strictly in order
|
||||
(never coalesced — a frame is many rows). Still: 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
|
||||
|
||||
+6
-2
@@ -436,7 +436,7 @@ XP consumes pre-rendered wallpapers.
|
||||
|
||||
### Phase 9 — Game feedback (game → cockpit) — code-complete ✅
|
||||
Inbound feedback endpoint + plasma runtime wiring + rumble→lamp mapping, in
|
||||
`src/RioJoy.Core/Feedback` (425 xUnit tests total across the suite); protocol
|
||||
`src/RioJoy.Core/Feedback` (442 xUnit tests total across the suite); protocol
|
||||
spec + client snippets in [`docs/FEEDBACK.md`](FEEDBACK.md). Delivers the
|
||||
§Profiles promises "Lamp behavior" and "Plasma/VFD content (or 'off')".
|
||||
- **Endpoint**: `FeedbackPipeServer` serves `\\.\pipe\riojoy-feedback`
|
||||
@@ -447,7 +447,11 @@ spec + client snippets in [`docs/FEEDBACK.md`](FEEDBACK.md). Delivers the
|
||||
`AppConfig.Feedback.UdpPort` — the transport sim export scripts speak
|
||||
natively). One shared text line protocol: `FeedbackLineParser` +
|
||||
`FeedbackLineBuffer` (Latin-1, LF/CRLF, forgiving — malformed lines drop and
|
||||
log, never the connection). `FeedbackService` façades the lot; it lives in
|
||||
log, never the connection), including `plasma row <y> <hex32>` **bitmap
|
||||
streaming** (`PlasmaCommands.GraphicsWrite` ports the display's `ESC P`
|
||||
graphics command per vRIO's recovered `PlasmaProtocol`; the router queues
|
||||
rows strictly FIFO while texts coalesce and clear flushes, bounded at 128).
|
||||
`FeedbackService` façades the lot; it lives in
|
||||
`RioCoordinator` for the **app lifetime**, so clients keep their connection
|
||||
across profile switches and dormancy — only command *application* is gated.
|
||||
- **Rate governor**: `CoalescingLampScheduler` — per-address desired/last-sent
|
||||
|
||||
Reference in New Issue
Block a user