feedback: plasma box, explicit fonts, and per-position text coalescing

Three endpoint gaps, found building the Descent 3 score overlay - the boxed
place|score field the original games drew over the callsign:

plasma box <x> <y> <w> <h> draws an outlined box with a blanked interior, the
overlay chrome, as one ESC P graphics write. The wire addresses whole bytes
horizontally, so the write covers the byte-aligned span containing the box and
clears span pixels outside it; documented, with 8-px alignment the advice.
Boxes queue FIFO with rows.

plasma text gains an explicit font: a third numeric token after the position,
with text still following, so `plasma text 2 2 7` still displays "7". Auto-fit
picks the font by LENGTH - short text always rendered large, and a "1" that
must fit a 12-px box simply could not be sent before. ResolvePosText
generalizes the legacy Score-font special case: 0 = auto, nonzero honored.

Text coalescing is now per position. The global rule - any queued text
superseded every other queued text - meant the documented two-field layout
(callsign top, score bottom) could not survive its own send burst: the second
field silently ate the first whenever both were queued. A newer text now
replaces only a queued text at the same (x,y).

15 new tests; 472 pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-02 11:45:59 -05:00
co-authored by Claude Opus 5
parent 23453667d2
commit 46e108de89
9 changed files with 315 additions and 26 deletions
+22 -8
View File
@@ -43,11 +43,12 @@ case-insensitive. Lines over 256 bytes and UDP datagrams over 4 KB are dropped.
```
# comment (also ;)
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
lamp <addr> <state> set one lamp
lamp-all <state> set every lamp
plasma text [x y [font]] <text> write text to the plasma display
plasma clear clear the plasma display
plasma row <y> <hex32> write one full 128-px bitmap row
plasma box <x> <y> <w> <h> outlined box with a blanked interior
```
- **`<addr>`** — RIO lamp address, decimal or `0x` hex. Valid: `0x000x47`
@@ -62,17 +63,30 @@ plasma row <y> <hex32> write one full 128-px bitmap row
(`"VIPER 1-1"`; quotes stripped, no escapes). Two leading *numeric* tokens
are a cursor position `x y`; omitted (or `0 0`) auto-fits and centers
(`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.
it. A **third** numeric token after the position — with text still following
— is an explicit font: `0` auto (by length: ≤9 chars large, else small),
`2` small 5×7, `5` large 10×14. Short positioned text otherwise always
renders large, which cannot fit inside a `plasma box`. Encoding is
**Latin-1** (one byte = one char, the plasma's wire encoding) — do not send
UTF-8 for accented characters. Text coalesces **per position**: a newer
queued text replaces an older one at the same `x y` only, so multi-field
layouts (callsign + score) can update one field without losing the others.
- **`plasma row`** — one full bitmap row: `<y>` 031 (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);
coalesces — 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).
- **`plasma box`** — an outlined 1-px box with its interior blanked, in pixel
coordinates (`x` 0127, `y` 031, must fit the panel). This is the overlay
chrome the original games drew for their rank|score field over the callsign
bitmap. The wire's graphics command spans whole bytes horizontally, so the
write covers the byte-aligned span containing the box; pixels inside the
span but outside the box are cleared — place boxes on 8-px boundaries when
that matters. Boxes queue FIFO with rows.
Malformed lines are dropped and counted (first few are logged); they **never**
cost a client its connection. The endpoint sends no replies.
+10 -2
View File
@@ -107,14 +107,22 @@ What the endpoint exposes (v1):
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 text <x> <y> <font> <text>`** — as above with an explicit font
(`2` small 5×7, `5` large 10×14, `0` auto). Auto picks the font by LENGTH,
so short positioned text always renders large; the explicit form is how a
short field ("1", "1000") fits inside a score box.
- **`plasma clear`** — blank the display.
- **`plasma row <y> <hex32>`** — one full 128-px bitmap row; see
[Bitmap graphics](#bitmap-graphics-esc-p).
- **`plasma box <x> <y> <w> <h>`** — outlined box, interior blanked: the
overlay chrome for a field drawn on top of other content (the original
games' rank|score box over the callsign). Byte-aligned horizontally — put
box edges on 8-px boundaries where neighbors matter.
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 and box draw/fill.
Not exposed through the endpoint yet (small extensions when needed): text
attribute selection (intensity/underline/reverse/flash) and filled-only boxes.
### Bitmap graphics (`ESC P`)