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:
Cyd
2026-07-31 20:45:38 -05:00
co-authored by Claude Fable 5
parent 66c3cbdb57
commit 44b636ddd3
14 changed files with 435 additions and 41 deletions
+71 -1
View File
@@ -145,7 +145,7 @@ public static class FeedbackLineParser
string? sub = NextToken(s, ref pos);
if (sub is null)
{
error = "plasma needs a subcommand (text|clear)";
error = "plasma needs a subcommand (text|clear|row)";
return false;
}
@@ -163,12 +163,82 @@ public static class FeedbackLineParser
case "text":
return TryParsePlasmaText(s, pos, out command, out error);
case "row":
return TryParsePlasmaRow(s, pos, out command, out error);
default:
error = $"unknown plasma subcommand '{sub}'";
return false;
}
}
// plasma row <y> <32 hex digits>: one full 128-px bitmap row (16 bytes,
// MSB = leftmost pixel), matching the native game's whole-row streaming.
private static bool TryParsePlasmaRow(
string s, int pos, out FeedbackCommand? command, out string? error)
{
command = null;
error = null;
string? yToken = NextToken(s, ref pos);
if (yToken is null || !TryParseNumber(yToken, out int y))
{
error = "plasma row needs a row number and 32 hex digits";
return false;
}
if (y is < 0 or > 31)
{
error = $"plasma row {y} out of range (0-31)";
return false;
}
string? hex = NextToken(s, ref pos);
if (hex is null)
{
error = "plasma row needs 32 hex digits of row data";
return false;
}
if (NextToken(s, ref pos) is string extra)
{
error = $"unexpected token '{extra}'";
return false;
}
if (hex.Length != 32)
{
error = $"plasma row data must be exactly 32 hex digits (16 bytes), got {hex.Length}";
return false;
}
var data = new byte[16];
for (int i = 0; i < 16; i++)
{
int hi = HexNibble(hex[i * 2]);
int lo = HexNibble(hex[i * 2 + 1]);
if (hi < 0 || lo < 0)
{
error = $"plasma row data has a non-hex character ('{hex[hi < 0 ? i * 2 : i * 2 + 1]}')";
return false;
}
data[i] = (byte)((hi << 4) | lo);
}
command = new FeedbackCommand
{
Kind = FeedbackCommandKind.PlasmaRow,
Y = (byte)y,
Data = data,
};
return true;
}
private static int HexNibble(char c) => c switch
{
>= '0' and <= '9' => c - '0',
>= 'a' and <= 'f' => c - 'a' + 10,
>= 'A' and <= 'F' => c - 'A' + 10,
_ => -1,
};
private static bool TryParsePlasmaText(
string s, int pos, out FeedbackCommand? command, out string? error)
{