diff --git a/PlasmaNew/FIRMWARE.md b/PlasmaNew/FIRMWARE.md
index 2b67fc5..25fa481 100644
--- a/PlasmaNew/FIRMWARE.md
+++ b/PlasmaNew/FIRMWARE.md
@@ -127,10 +127,14 @@ at `$8000`). It's how the new `ESC R/I/Q/i/K` commands were first spotted, e.g.
## What this means
-**For vPLASMA:** the recovered commands can replace guessed behavior. Notably
-`ESC Q`/`ESC R` are the real cursor-positioning commands (row/col), and the
-`ESC P` "screen" byte selects one of 10 double-buffered pages — vPLASMA
-currently models a single buffer.
+**For vPLASMA (folded in 2026-07-16):** the recovered spec replaced the
+guessed behavior. vPLASMA now uses the **8 real ROM fonts** (extracted to
+`src/VPlasma.Core/Device/PlasmaFonts.cs`), a **pixel-addressed cursor** with
+the real `ESC Q` (row) / `ESC R` (column) positioning, `ESC K` 0–7 font
+select, and `ESC H` attributes as the low 4 bits. Verified: 24 unit tests +
+the three self-test pages render the real glyphs. Still deferred (documented,
+single-page model retained): the 10 double-buffered pages (`ESC I`/`ESC i`)
+and the vector-graphics primitives (`ESC A`–`F`).
**For the replica:** this *is* the spec. The firmware confirms a clean model —
a byte-stream command parser, a 512-byte-per-page frame buffer, 10 pages with
diff --git a/README.md b/README.md
index e4c3e3c..309a730 100644
--- a/README.md
+++ b/README.md
@@ -157,12 +157,15 @@ recovered grammar lives in `src/VPlasma.Core/Protocol/PlasmaProtocol.cs`.
- **Graphics** — `ESC P screen y xbyte width rows` + packed 1bpp row data,
MSB = leftmost pixel. This is everything the game sends, so it is the
wire path a pod's plasma actually sees.
-- **Text** — printable ASCII renders through a 5×7 font at a cursor:
- fonts 0–3 give a 21×4 cell grid, fonts 4–7 the same glyphs doubled to
- 10×2. Half-intensity draws dimmer, reverse/underline render in the
- cell, flashing text (and the flashing cursor) blink on the glass. The
- panel's own ROM glyphs are lost with the hardware, so the classic
- public-domain 5×7 set stands in.
+- **Text** — printable ASCII renders at a **pixel-addressed cursor**
+ (`ESC Q` sets row Y 0–31, `ESC R` sets column X 0–127) through the
+ **real ROM character generator** — the eight Babcock fonts recovered
+ from the firmware dump (`ESC K` 0–7): 6×8, 6×10, 7×10, and the large
+ 12×16 / 12×20. Attributes (`ESC H`, low 4 bits) — half-intensity
+ draws dimmer, underline/reverse render in the cell, flashing text (and
+ the flashing cursor) blink on the glass. Extracted from the U3 EPROM;
+ see [`PlasmaNew/FIRMWARE.md`](PlasmaNew/FIRMWARE.md). (Deferred, and
+ documented there: the 10 double-buffered pages and vector graphics.)
- **Double-click** the glass to cycle three self-test pages (banner,
charset, graphics pattern) through the same parser the wire feeds —
useful without a host. **Right-click** resets the display to its
@@ -178,7 +181,7 @@ recovered grammar lives in `src/VPlasma.Core/Protocol/PlasmaProtocol.cs`.
|------|----------|
| `src/VRio.Core` | Protocol framing/builder/parser, the `VRioDevice` state machine, serial pump, panel layout data (class library) |
| `src/VRio.App` | WinForms panel UI |
-| `src/VPlasma.Core` | Plasma command-stream parser, the `VPlasmaDevice` display state machine, 5×7 font, serial listener (class library) |
+| `src/VPlasma.Core` | Plasma command-stream parser, the `VPlasmaDevice` display state machine, the 8 real ROM fonts (`PlasmaFonts`), serial listener (class library) |
| `src/VPlasma.App` | WinForms dot-matrix display UI |
| `pkg` | Sparse-package manifest + registration script: grants VRio.App.exe the package identity Dynamic Lighting's background-control list requires |
| `tests/VRio.Core.Tests` | xUnit tests for the protocol + device engine |
diff --git a/src/VPlasma.App/PlasmaCanvas.cs b/src/VPlasma.App/PlasmaCanvas.cs
index f42b393..bee6c42 100644
--- a/src/VPlasma.App/PlasmaCanvas.cs
+++ b/src/VPlasma.App/PlasmaCanvas.cs
@@ -30,7 +30,7 @@ internal sealed class PlasmaCanvas : Control
private bool _blinkPhase = true;
private bool _anythingBlinks;
- private VPlasmaDevice.Point _cursor;
+ private int _cursorX, _cursorY;
private PlasmaCursorMode _cursorMode;
private int _cellWidth = 6, _cellHeight = 8;
@@ -62,10 +62,11 @@ internal sealed class PlasmaCanvas : Control
public void UpdateFrom(VPlasmaDevice device)
{
device.CopyFrame(_frame);
- _cursor = device.CursorCell;
+ _cursorX = device.CursorX;
+ _cursorY = device.CursorY;
_cursorMode = device.CursorMode;
- _cellWidth = device.CellWidth;
- _cellHeight = device.CellHeight;
+ _cellWidth = device.FontWidth;
+ _cellHeight = device.FontHeight;
_anythingBlinks = _cursorMode == PlasmaCursorMode.Flashing;
if (!_anythingBlinks)
@@ -113,8 +114,8 @@ internal sealed class PlasmaCanvas : Control
if (_cursorMode == PlasmaCursorMode.Steady
|| (_cursorMode == PlasmaCursorMode.Flashing && _blinkPhase))
{
- int cx = _cursor.Col * _cellWidth;
- int cy = _cursor.Row * _cellHeight + _cellHeight - 1;
+ int cx = _cursorX;
+ int cy = _cursorY + _cellHeight - 1;
if (cy < VPlasmaDevice.Height)
for (int i = 0; i < _cellWidth && cx + i < VPlasmaDevice.Width; ++i)
g.FillRectangle(lit, Bezel + (cx + i) * _dotPitch, Bezel + cy * _dotPitch, _dotSize, _dotSize);
diff --git a/src/VPlasma.Core/Device/PlasmaFont.cs b/src/VPlasma.Core/Device/PlasmaFont.cs
deleted file mode 100644
index c88f2be..0000000
--- a/src/VPlasma.Core/Device/PlasmaFont.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-namespace VPlasma.Core.Device;
-
-///
-/// The display's character generator: a classic 5×7 dot-matrix font for
-/// ASCII 0x20..0x7E, stored column-major (5 column bytes per glyph, bit 0 =
-/// top row) — the layout every KS0108-era controller used. The real panel's
-/// ROM glyphs are lost with the hardware; this is the standard public-domain
-/// 5×7 set, which is what such panels shipped with. Codes outside the range
-/// render as a solid block so stream corruption is visible on the glass.
-///
-public static class PlasmaFont
-{
- public const int GlyphWidth = 5;
- public const int GlyphHeight = 7;
- public const byte First = 0x20;
- public const byte Last = 0x7E;
-
- ///
- /// Column bits for 's glyph. Bit r of
- /// result[c] is the dot at column c, row r (row 0 at the top).
- ///
- public static void GetColumns(byte code, Span columns)
- {
- if (code < First || code > Last)
- {
- columns.Slice(0, GlyphWidth).Fill(0x7F); // solid block
- return;
- }
- Glyphs.AsSpan((code - First) * GlyphWidth, GlyphWidth).CopyTo(columns);
- }
-
- private static readonly byte[] Glyphs =
- {
- 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 ' '
- 0x00, 0x00, 0x5F, 0x00, 0x00, // 0x21 '!'
- 0x00, 0x07, 0x00, 0x07, 0x00, // 0x22 '"'
- 0x14, 0x7F, 0x14, 0x7F, 0x14, // 0x23 '#'
- 0x24, 0x2A, 0x7F, 0x2A, 0x12, // 0x24 '$'
- 0x23, 0x13, 0x08, 0x64, 0x62, // 0x25 '%'
- 0x36, 0x49, 0x55, 0x22, 0x50, // 0x26 '&'
- 0x00, 0x05, 0x03, 0x00, 0x00, // 0x27 '''
- 0x00, 0x1C, 0x22, 0x41, 0x00, // 0x28 '('
- 0x00, 0x41, 0x22, 0x1C, 0x00, // 0x29 ')'
- 0x08, 0x2A, 0x1C, 0x2A, 0x08, // 0x2A '*'
- 0x08, 0x08, 0x3E, 0x08, 0x08, // 0x2B '+'
- 0x00, 0x50, 0x30, 0x00, 0x00, // 0x2C ','
- 0x08, 0x08, 0x08, 0x08, 0x08, // 0x2D '-'
- 0x00, 0x60, 0x60, 0x00, 0x00, // 0x2E '.'
- 0x20, 0x10, 0x08, 0x04, 0x02, // 0x2F '/'
- 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0x30 '0'
- 0x00, 0x42, 0x7F, 0x40, 0x00, // 0x31 '1'
- 0x42, 0x61, 0x51, 0x49, 0x46, // 0x32 '2'
- 0x21, 0x41, 0x45, 0x4B, 0x31, // 0x33 '3'
- 0x18, 0x14, 0x12, 0x7F, 0x10, // 0x34 '4'
- 0x27, 0x45, 0x45, 0x45, 0x39, // 0x35 '5'
- 0x3C, 0x4A, 0x49, 0x49, 0x30, // 0x36 '6'
- 0x01, 0x71, 0x09, 0x05, 0x03, // 0x37 '7'
- 0x36, 0x49, 0x49, 0x49, 0x36, // 0x38 '8'
- 0x06, 0x49, 0x49, 0x29, 0x1E, // 0x39 '9'
- 0x00, 0x36, 0x36, 0x00, 0x00, // 0x3A ':'
- 0x00, 0x56, 0x36, 0x00, 0x00, // 0x3B ';'
- 0x00, 0x08, 0x14, 0x22, 0x41, // 0x3C '<'
- 0x14, 0x14, 0x14, 0x14, 0x14, // 0x3D '='
- 0x41, 0x22, 0x14, 0x08, 0x00, // 0x3E '>'
- 0x02, 0x01, 0x51, 0x09, 0x06, // 0x3F '?'
- 0x32, 0x49, 0x79, 0x41, 0x3E, // 0x40 '@'
- 0x7E, 0x11, 0x11, 0x11, 0x7E, // 0x41 'A'
- 0x7F, 0x49, 0x49, 0x49, 0x36, // 0x42 'B'
- 0x3E, 0x41, 0x41, 0x41, 0x22, // 0x43 'C'
- 0x7F, 0x41, 0x41, 0x22, 0x1C, // 0x44 'D'
- 0x7F, 0x49, 0x49, 0x49, 0x41, // 0x45 'E'
- 0x7F, 0x09, 0x09, 0x09, 0x01, // 0x46 'F'
- 0x3E, 0x41, 0x49, 0x49, 0x7A, // 0x47 'G'
- 0x7F, 0x08, 0x08, 0x08, 0x7F, // 0x48 'H'
- 0x00, 0x41, 0x7F, 0x41, 0x00, // 0x49 'I'
- 0x20, 0x40, 0x41, 0x3F, 0x01, // 0x4A 'J'
- 0x7F, 0x08, 0x14, 0x22, 0x41, // 0x4B 'K'
- 0x7F, 0x40, 0x40, 0x40, 0x40, // 0x4C 'L'
- 0x7F, 0x02, 0x0C, 0x02, 0x7F, // 0x4D 'M'
- 0x7F, 0x04, 0x08, 0x10, 0x7F, // 0x4E 'N'
- 0x3E, 0x41, 0x41, 0x41, 0x3E, // 0x4F 'O'
- 0x7F, 0x09, 0x09, 0x09, 0x06, // 0x50 'P'
- 0x3E, 0x41, 0x51, 0x21, 0x5E, // 0x51 'Q'
- 0x7F, 0x09, 0x19, 0x29, 0x46, // 0x52 'R'
- 0x46, 0x49, 0x49, 0x49, 0x31, // 0x53 'S'
- 0x01, 0x01, 0x7F, 0x01, 0x01, // 0x54 'T'
- 0x3F, 0x40, 0x40, 0x40, 0x3F, // 0x55 'U'
- 0x1F, 0x20, 0x40, 0x20, 0x1F, // 0x56 'V'
- 0x3F, 0x40, 0x38, 0x40, 0x3F, // 0x57 'W'
- 0x63, 0x14, 0x08, 0x14, 0x63, // 0x58 'X'
- 0x07, 0x08, 0x70, 0x08, 0x07, // 0x59 'Y'
- 0x61, 0x51, 0x49, 0x45, 0x43, // 0x5A 'Z'
- 0x00, 0x7F, 0x41, 0x41, 0x00, // 0x5B '['
- 0x02, 0x04, 0x08, 0x10, 0x20, // 0x5C '\'
- 0x00, 0x41, 0x41, 0x7F, 0x00, // 0x5D ']'
- 0x04, 0x02, 0x01, 0x02, 0x04, // 0x5E '^'
- 0x40, 0x40, 0x40, 0x40, 0x40, // 0x5F '_'
- 0x00, 0x01, 0x02, 0x04, 0x00, // 0x60 '`'
- 0x20, 0x54, 0x54, 0x54, 0x78, // 0x61 'a'
- 0x7F, 0x48, 0x44, 0x44, 0x38, // 0x62 'b'
- 0x38, 0x44, 0x44, 0x44, 0x20, // 0x63 'c'
- 0x38, 0x44, 0x44, 0x48, 0x7F, // 0x64 'd'
- 0x38, 0x54, 0x54, 0x54, 0x18, // 0x65 'e'
- 0x08, 0x7E, 0x09, 0x01, 0x02, // 0x66 'f'
- 0x0C, 0x52, 0x52, 0x52, 0x3E, // 0x67 'g'
- 0x7F, 0x08, 0x04, 0x04, 0x78, // 0x68 'h'
- 0x00, 0x44, 0x7D, 0x40, 0x00, // 0x69 'i'
- 0x20, 0x40, 0x44, 0x3D, 0x00, // 0x6A 'j'
- 0x7F, 0x10, 0x28, 0x44, 0x00, // 0x6B 'k'
- 0x00, 0x41, 0x7F, 0x40, 0x00, // 0x6C 'l'
- 0x7C, 0x04, 0x18, 0x04, 0x78, // 0x6D 'm'
- 0x7C, 0x08, 0x04, 0x04, 0x78, // 0x6E 'n'
- 0x38, 0x44, 0x44, 0x44, 0x38, // 0x6F 'o'
- 0x7C, 0x14, 0x14, 0x14, 0x08, // 0x70 'p'
- 0x08, 0x14, 0x14, 0x14, 0x7C, // 0x71 'q'
- 0x7C, 0x08, 0x04, 0x04, 0x08, // 0x72 'r'
- 0x48, 0x54, 0x54, 0x54, 0x20, // 0x73 's'
- 0x04, 0x3F, 0x44, 0x40, 0x20, // 0x74 't'
- 0x3C, 0x40, 0x40, 0x20, 0x7C, // 0x75 'u'
- 0x1C, 0x20, 0x40, 0x20, 0x1C, // 0x76 'v'
- 0x3C, 0x40, 0x30, 0x40, 0x3C, // 0x77 'w'
- 0x44, 0x28, 0x10, 0x28, 0x44, // 0x78 'x'
- 0x0C, 0x50, 0x50, 0x50, 0x3C, // 0x79 'y'
- 0x44, 0x64, 0x54, 0x4C, 0x44, // 0x7A 'z'
- 0x00, 0x08, 0x36, 0x41, 0x00, // 0x7B '{'
- 0x00, 0x00, 0x7F, 0x00, 0x00, // 0x7C '|'
- 0x00, 0x41, 0x36, 0x08, 0x00, // 0x7D '}'
- 0x08, 0x04, 0x08, 0x10, 0x08, // 0x7E '~'
- };
-}
diff --git a/src/VPlasma.Core/Device/PlasmaFonts.cs b/src/VPlasma.Core/Device/PlasmaFonts.cs
new file mode 100644
index 0000000..03e6bd9
--- /dev/null
+++ b/src/VPlasma.Core/Device/PlasmaFonts.cs
@@ -0,0 +1,450 @@
+// AUTO-GENERATED from the PD01D221 firmware (tms27pc512.BIN) by
+// PlasmaNew tooling. The real Babcock character generator, all 8 fonts.
+// Each glyph row is a ushort; bit 15 = leftmost pixel. See PlasmaNew/FIRMWARE.md.
+namespace VPlasma.Core.Device;
+
+/// The display's 8 built-in fonts, extracted from ROM.
+public static class PlasmaFonts
+{
+ /// One font face: character range, cell size, and packed glyph rows.
+ public sealed class Face
+ {
+ public int Index { get; }
+ public byte First { get; }
+ public byte Last { get; }
+ public int Width { get; }
+ public int Height { get; }
+ private readonly ushort[] _rows; // (c-First)*Height + row; bit15 = leftmost
+ public Face(int index, byte first, byte last, int width, int height, string hex)
+ {
+ Index = index; First = first; Last = last; Width = width; Height = height;
+ _rows = new ushort[hex.Length / 4];
+ for (int i = 0; i < _rows.Length; i++)
+ _rows[i] = (ushort)((HexNibble(hex[i*4]) << 12) | (HexNibble(hex[i*4+1]) << 8) | (HexNibble(hex[i*4+2]) << 4) | HexNibble(hex[i*4+3]));
+ }
+ private static int HexNibble(char c) => c <= '9' ? c - '0' : (c & 0x5F) - 'A' + 10;
+ /// True if this face has a glyph for .
+ public bool Has(byte code) => code >= First && code <= Last;
+ /// Row of glyph (bit15 = leftmost), or 0.
+ public ushort Row(byte code, int row)
+ => Has(code) && (uint)row < (uint)Height ? _rows[(code - First) * Height + row] : (ushort)0;
+ }
+
+ // font 0: 6x8, chars 0x20-0xFF, 224 glyphs
+ private static readonly Face F0 = new(0, 0x20, 0xFF, 6, 8,
+ "00000000000000000000000000000000200020002000200000000000200000005000500050000000000000000000000050005000F8005000F8005000"
+ + "5000000020007800A00070002800F00020000000C000C80010002000400098001800000060009000A0004000A8009000680000006000200040000000"
+ + "0000000000000000100020004000400040002000100000004000200010001000100020004000000000002000A8007000A80020000000000000002000"
+ + "2000F800200020000000000000000000000000006000200040000000000000000000F800000000000000000000000000000000000000600060000000"
+ + "00000800100020004000800000000000700088009800A800C80088007000000020006000200020002000200070000000700088000800100020004000"
+ + "F8000000F80010002000100008000800F00000001000300050009000F800100010000000F8008000F000080008000800F0000000300040008000F000"
+ + "8800880070000000F8000800100020004000400040000000700088008800700088008800700000007000880088007800080010006000000000006000"
+ + "600000006000600000000000000060006000000060002000400000001000200040008000400020001000000000000000F8000000F800000000000000"
+ + "80004000200010002000400080000000700088000800100020000000200000007000880008006800A800A800700000007000880088008800F8008800"
+ + "88000000F00088008800F00088008800F000000070008800800080008000880070000000E00090008800880088009000E0000000F80080008000F000"
+ + "80008000F8000000F80080008000F0008000800080000000700088008000B8008800880070000000880088008800F800880088008800000070002000"
+ + "2000200020002000700000003800100010001000100090006000000088009000A000C000A000900088000000800080008000800080008000F8000000"
+ + "8800D800A800A800880088008800000088008800C800A800980088008800000070008800880088008800880070000000F00088008800F00080008000"
+ + "800000007000880088008800A800900068000000F00088008800F000A000900088000000780080008000700008000800F0000000F800200020002000"
+ + "20002000200000008800880088008800880088007000000088008800880088008800500020000000880088008800A800A800A8005000000088008800"
+ + "50002000500088008800000088008800880050002000200020000000F80008001000200040008000F800000070004000400040004000400070000000"
+ + "000080004000200010000800000000007000100010001000100010007000000020005000880000000000000000000000000000000000000000000000"
+ + "FC000000600040002000000000000000000000000000000070000800780088007800000080008000B000C80088008800F00000000000000070008000"
+ + "8000880070000000080008006800980088008800780000000000000070008800F8008000700000001000280020007000200020002000000000000000"
+ + "780088007800080070000000800080008000B000C8008800880000000000200000002000200020002000000010000000300010001000900060000000"
+ + "800080009000A000C000A000900000006000200020002000200020007000000000000000D000A800A80088008800000000000000B000C80088008800"
+ + "88000000000000007000880088008800700000000000B000C8008800F0008000800000000000680098008800780008000800000000000000B000C800"
+ + "8000800080000000000000007800800070000800F000000000002000F800200020002800100000000000000088008800880098006800000000000000"
+ + "8800880088005000200000000000000088008800A800A800500000000000000088005000200050008800000000000000880050002000200040000000"
+ + "00000000F800100020004000F80000003000400040008000400040003000000020002000200000002000200020000000600010001000080010001000"
+ + "6000000008007000800000000000000000000000FC00FC00FC00FC00FC00FC00FC00FC00000000000000F80000000000000000002000200020002000"
+ + "2000200020002000200020002000E0000000000000000000000000000000700020002000200020002000200020007000000000000000000000000000"
+ + "0000E000200020002000200020002000200070002000200020002000200020002000E0002000200020002000200020002000F8002000200020002000"
+ + "200020002000F8000000000000000000000000000000F800200020002000200000000000000008001000200020002000000000000000800040002000"
+ + "200020002000200040008000000000000000000020002000100008000000000000000000600060006000F800F800600060006000000000000000F800"
+ + "F80000000000000060006000600040006000600060007000600060006000E000E0000000000000000000000000007800780060006000700060006000"
+ + "600078007800000000000000000000000000E000E00060006000600060006000600078007800600060006000600060006000E000E000600060006000"
+ + "000020007000A80020002000200020000000000040002000F0002000400000000000000020004000F8004000200000000000000020002000E0000000"
+ + "000000000000200020002000A80070002000000000002000500088005000500070000000000020007000F800700070007000000000004000F800F800"
+ + "F800400000000000000090009000080008009000E00000000000000000000000E000A000E00000000000700040004000400000000000000000000000"
+ + "0000400040004000C000000000000000000000008000400020000000000000000000C000C0000000000000000000F8000800F8000800100020000000"
+ + "00000000F8000800300020004000000000000000100020006000A00020000000000000002000F8008800080030000000000000000000F80020002000"
+ + "F8000000000000001000F8003000500090000000000000004000F8004800500040000000000000000000700010001000F800000000000000F0001000"
+ + "F0001000F0000000000000000000A800A800080030000000000000000000F0000000000000000000F800080028003000200020004000000008001000"
+ + "20006000A0002000200000002000F800880088000800100020000000F80020002000200020002000F80000001000F800100030005000900010000000"
+ + "4000F8004800500048004800900000002000F8002000F800200020002000000000000000780050008800100020000000400078009000100010001000"
+ + "200000000000F8000800080008000800F80000005000F800500050001000200040000000C0000000C800080008001000600000000000F80008001000"
+ + "20005000880000004000F800C8005000400040003800000000008800880048000800100060000000000078005000A8001C001000600000001000E000"
+ + "2000F80020002000400000000000A800A800A800080010002000000070000000F80020002000200040000000800080008000C000A000800080000000"
+ + "20002000F80020002000400080000000000070000000000000000000F80000000000F8000800500020005000800000002000F800100020007000A800"
+ + "20000000100010001000100010002000400000000000000040002000900090009000000080008000F800800080008000780000000000F80008000800"
+ + "080010006000000000004000A000100008000800000000002000F80020002000A800A800200000000000F8000800080050002000100000000000E000"
+ + "0000E0000000E0001000000000002000400080008800F80008000000000008000800500020005000800000000000F8004000F8004000400038000000"
+ + "40004000F80048005000400040000000000070001000100010001000F80000000000F8000800F80008000800F800000070000000F800080010002000"
+ + "40000000900090009000900010002000400000002000A000A000A000A800A8003000000080008000800088009000A000C000000000000000F8008800"
+ + "88008800F80000000000F8008800080008001000200000000000C0000000080008001000E0000000000020009000400000000000000000000000E000"
+ + "A000E00000000000000000002000000020004000800090006000000010002000700088008800F8008800000080004000700088008800F80088000000"
+ + "50000000700088008800F800880000007000880080008000880070002000600010002000F0008000C0008000F000000080004000F0008000C0008000"
+ + "F00000007000880070002000200020007000000050000000700088008800880070000000500000008800880088008800700000000800100070000800"
+ + "780088007000000070008800700008007800880070000000800040007000080078008800700000005000000070000800780088007000000000007000"
+ + "8000800088007000200060001000000070008800F8008000700000007000880070008800F0008000700000008000000070008800F000800070000000"
+ + "080050000000C00040004000E00000007000A80000006000200020007000000080005000000030001000100038000000500020000000600020002000"
+ + "700000006800B8000000B000C80088008800000008001000000070008800880070000000700088000000700088008800700000008000400000007000"
+ + "880088007000000000005000000070008800880070000000F0008800B00088008800D000B00000002000400000008800880098006800000070008800"
+ + "0000880088009800680000008000400000008800880098006800000000005000000088008800980068000000");
+
+ // font 1: 6x8, chars 0x40-0x7F, 64 glyphs
+ private static readonly Face F1 = new(1, 0x40, 0x7F, 6, 8,
+ "100020004000200010000000F8000000400020001000200040000000F800000020002000F800200020000000F8000000000020000000F80000002000"
+ + "00000000200050005000500088008800F800000008001000F8002000F80040008000000020007000A800A800A800A80070002000F800400040004000"
+ + "40004000E00000000000F8000000F8000000F80000000000080070008000080070008000000000000800700080000000F80000000000000004000400"
+ + "4800A8002800100010000000200050005000500088008800880000007000880008006800980088009000600000000000880050002000500088000000"
+ + "000020007000F800700020000000000020007000A800A000A8007000200000000000F800880088008800880088000000700088008800A80088008800"
+ + "70000000000000005000A8005000000000000000F80040002000100020004000F8000000300048004800300000000000000000000000000000000000"
+ + "000000000000000000000000000000000000000000000000000070008800880088005000D8000000F80000000000700000000000F8000000A800A800"
+ + "A80070002000200020000000000010007000A800A800A8007000400000000000000000000000000000000000F4005C00540054005400000000000000"
+ + "20007000A8002000200020002000200020002000200020002000A80070002000300048004000F00040004800B0000000000000000000680090009000"
+ + "6800000070008800880070008800C800B000000000008000880050002000500088000800700088004000200050008800880070000000000070008800"
+ + "6000880070000000000020007000A800A800A800700020000000000088008800500050007000200000000000B000C800880088008800080000000000"
+ + "00006000200020003000000000001000A800A800A80070002000000000000000480050006000500048000000C0004000200010002800440044000000"
+ + "0000000088008800C800B000800080000000000000008800880050002000000000000000700088008800880070000000880050002000F8002000F800"
+ + "2000000000000000F80050005000500048000000700088008800F80088008800700000000000000070008800C800B000800080000000000078009000"
+ + "8800880070000000000008007000A00020002000100000000000000000009000480048003000000000000000F80050008800A8005000000000000000"
+ + "000050008800A80050000000400030004000300040004000380008002000A800A8007000200020002000000040003000400040004000400038000800"
+ + "000010000800FC000800100000000000000020004000FC0040002000000000000000000000000000000000000000000020007000A80020002000A800"
+ + "70002000");
+
+ // font 2: 6x10, chars 0x20-0xFF, 224 glyphs
+ private static readonly Face F2 = new(2, 0x20, 0xFF, 6, 10,
+ "000000000000000000000000000000000000000000002000200020002000000000002000000000000000500050005000000000000000000000000000"
+ + "000050005000F8005000F8005000500000000000000020007800A00070002800F0002000000000000000C000C8001000200040009800180000000000"
+ + "000060009000A0004000A800900068000000000000006000200040000000000000000000000000000000100020004000400040002000100000000000"
+ + "0000400020001000100010002000400000000000000000002000A8007000A80020000000000000000000000020002000F80020002000000000000000"
+ + "00000000000000000000000060002000400000000000000000000000F800000000000000000000000000000000000000000000006000600000000000"
+ + "00000000080010002000400080000000000000000000700088009800A800C80088007000000000000000200060002000200020002000700000000000"
+ + "0000700088000800100020004000F800000000000000F80010002000100008000800F0000000000000001000300050009000F8001000100000000000"
+ + "0000F8008000F000080008000800F000000000000000300040008000F000880088007000000000000000F80008001000200040004000400000000000"
+ + "000070008800880070008800880070000000000000007000880088007800080010006000000000000000000060006000000060006000000000000000"
+ + "00000000600060000000600020004000000000000000100020004000800040002000100000000000000000000000F8000000F8000000000000000000"
+ + "0000800040002000100020004000800000000000000070008800080010002000000020000000000000007000880008006800A800A800700000000000"
+ + "00007000880088008800F80088008800000000000000F00088008800F00088008800F000000000000000700088008000800080008800700000000000"
+ + "0000E00090008800880088009000E000000000000000F80080008000F00080008000F800000000000000F80080008000F00080008000800000000000"
+ + "0000700088008000B800880088007000000000000000880088008800F800880088008800000000000000700020002000200020002000700000000000"
+ + "0000380010001000100010009000600000000000000088009000A000C000A00090008800000000000000800080008000800080008000F80000000000"
+ + "00008800D800A800A80088008800880000000000000088008800C800A800980088008800000000000000700088008800880088008800700000000000"
+ + "0000F00088008800F0008000800080000000000000007000880088008800A80090006800000000000000F00088008800F000A0009000880000000000"
+ + "0000780080008000700008000800F000000000000000F800200020002000200020002000000000000000880088008800880088008800700000000000"
+ + "00008800880088008800880050002000000000000000880088008800A800A800A8005000000000000000880088005000200050008800880000000000"
+ + "00008800880088005000200020002000000000000000F80008001000200040008000F800000000000000700040004000400040004000700000000000"
+ + "000080004000200010000800000000000000000000007000100010001000100010007000000000000000200050008800000000000000000000000000"
+ + "0000000000000000000000000000FC000000000000006000400020000000000000000000000000000000000000007000080078008800780000000000"
+ + "000080008000B000C80088008800F0000000000000000000000070008000800088007000000000000000080008003400980088008800780000000000"
+ + "00000000000070008800F800800070000000000000001000280020007000200020002000000000000000000000007800880088007800080008007000"
+ + "0000800080008000B000C800880088000000000000000000200000002000200020002000000000000000100000003000100010001000100090006000"
+ + "0000800080009000A000C000A0009000000000000000600020002000200020002000700000000000000000000000D000A800A8008800880000000000"
+ + "000000000000B000C800880088008800000000000000000000007000880088008800700000000000000000000000B000C8008800F000800080008000"
+ + "0000000000006800980088007800080008000800000000000000B000C800800080008000000000000000000000007800800070000800F00000000000"
+ + "000000002000F80020002000280010000000000000000000000088008800880098006800000000000000000000008800880088005000200000000000"
+ + "00000000000088008800A800A80050000000000000000000000088005000200050008800000000000000000000008800880088007800080008007000"
+ + "000000000000F800100020004000F8000000000000003000400040008000400040003000000000000000200020002000000020002000200000000000"
+ + "00006000100010000800100010006000000000000000080070008000000000000000000000000000FC00FC00FC00FC00FC00FC00FC00FC00FC00FC00"
+ + "0000000000000000FC000000000000000000000010001000100010001000100010001000100010001000100010001000F00000000000000000000000"
+ + "00000000000000001C001000100010001000100010001000100010001C00000000000000000000000000000000000000F00010001000100010001000"
+ + "10001000100010001C00100010001000100010001000100010001000F000100010001000100010001000100010001000FC0010001000100010001000"
+ + "1000100010001000FC00000000000000000000000000000000000000FC00100010001000100010000000000000000000040008001000100010001000"
+ + "0000000000000000C000200010001000100010001000100010002000C000000000000000000000001000100010000800040000000000000000000000"
+ + "380038003800FC00FC00FC003800380038003800000000000000FC00FC00FC0000000000000000003800380038003800380038003800380038003800"
+ + "380038003800F800F800F80000000000000000000000000000003C003C003C0038003800380038003800380038003C003C003C000000000000000000"
+ + "000000000000F800F800F80038003800380038003800380038003C003C003C003800380038003800380038003800F800F800F8003800380038003800"
+ + "000000001000380054001000100010001000000000000000000020001000F8001000200000000000000000000000100020007C002000100000000000"
+ + "00000000000010001000F000000000000000000000000000100010001000540038001000000000000000000010002800440028002800380000000000"
+ + "00000000100038007C00380038003800000000000000000020007C00FC007C0020000000000000000000000048004800840084004800300000000000"
+ + "000000000000000000007000500070000000000000000000380020002000200000000000000000000000000000000000200020002000E00000000000"
+ + "00000000000000000000400020001000000000000000000000000000600060000000000000000000000000007C0004007C0004000800100000000000"
+ + "0000000000007C00040018001000200000000000000000000000080010003000500010000000000000000000000010007C0044000400180000000000"
+ + "00000000000000007C00100010007C000000000000000000000008007C001800280048000000000000000000000020007C0024002800200000000000"
+ + "00000000000000003800080008007C000000000000000000000078000800780008007800000000000000000000000000540054000400180000000000"
+ + "000000000000000078000000000000000000000000007C00040014001800100010002000000000000000040008001000300050001000100000000000"
+ + "000010007C00440044000400080010000000000000007C00100010001000100010007C0000000000000008007C000800180028004800080000000000"
+ + "000020007C002400240024002400480000000000000010007C0010007C00100010001000000000000000000000003C00240044000800100000000000"
+ + "000020003C004800080008000800100000000000000000007C0004000400040004007C0000000000000028007C002800280008001000200000000000"
+ + "0000600000006400040004000800300000000000000000007C000400080010002800440000000000000020007C0024002800200020001C0000000000"
+ + "0000000044004400240004000800300000000000000000003C00240054000C00080030000000000000000800700010007C0010001000200000000000"
+ + "00000000540054005400040008001000000000000000380000007C001000100010002000000000000000400040004000600050004000400000000000"
+ + "0000100010007C0010001000200040000000000000000000380000000000000000007C0000000000000000007C000400280010002800400000000000"
+ + "000010007C00080010003800540010000000000000000800080008000800080010002000000000000000000000002000100048004800480000000000"
+ + "0000400040007C004000400040003C0000000000000000007C0004000400040008003000000000000000000020005000080004000400000000000000"
+ + "000010007C001000100054005400100000000000000000007C0004000400280010000800000000000000000070000000700000007000080000000000"
+ + "0000000010002000400044007C000400000000000000000004000400280010002800400000000000000000007C0020007C00200020001C0000000000"
+ + "0000200020007C0024002800200020000000000000000000380008000800080008007C0000000000000000007C0004007C00040004007C0000000000"
+ + "0000380000007C0004000800100020000000000000004800480048004800080010002000000000000000100050005000500054005400180000000000"
+ + "00004000400040004400480050006000000000000000000000007C004400440044007C0000000000000000007C004400040004000800100000000000"
+ + "000000006000000004000400080070000000000000000000100048002000000000000000000000000000000070005000700000000000000000000000"
+ + "000010000000100020004000480030000000000008001000700088008800F800880088000000000080004000700088008800F8008800880000000000"
+ + "50000000700088008800F8008800880000000000000070008800800080008000880070002000600008001000F8008000F00080008000F80000000000"
+ + "80004000F8008000F00080008000F8000000000070008800700020002000200020007000000000005000000070008800880088008800700000000000"
+ + "500000008800880088008800880070000000000008001000000070000800780088007800000000007000880000007000080078008800780000000000"
+ + "800040000000700008007800880078000000000000005000000070000800780088007800000000000000000000007000800080008800700020006000"
+ + "08001000000070008800F800800070000000000070008800000070008800F000800070000000000080004000000070008800F8008000700000000000"
+ + "0800100040000000C00040004000E0000000000070008800200000006000200020007000000000008000400010000000300010001000380000000000"
+ + "28000000100000003000100010003800000000006800B80000007000C800880088008800000000000800100000007000880088008800700000000000"
+ + "700088000000700088008800880070000000000080004000000070008800880088007000000000000000500000007000880088008800700000000000"
+ + "0000780044005800440044006400D8000000000010002000000088008800880098006800000000007000880000008800880088009800680000000000"
+ + "80004000000088008800880098006800000000000000500000008800880088009800680000000000");
+
+ // font 3: 6x10, chars 0x40-0x7F, 64 glyphs
+ private static readonly Face F3 = new(3, 0x40, 0x7F, 6, 10,
+ "0000100020004000200010000000F800000000000000400020001000200040000000F80000000000000020002000F800200020000000F80000000000"
+ + "0000000020000000F800000020000000000000000000200050005000500088008800F80000000000000008001000F8002000F8004000800000000000"
+ + "20007000A800A800A800A800A8007000200000000000F80040004000400040004000E0000000000000000000F8000000F8000000F800000000000000"
+ + "000008007000800008007000800000000000000000000800700080000000F800000000000000000000000400040004004800A8002800100010000000"
+ + "0000200050005000500088008800880000000000000070008800080068008C0088009000600000000000000000008800500020005000880000000000"
+ + "0000000020007000F80070002000000000000000000020007000A800A000A80070002000000000000000F80088008800880088008800880000000000"
+ + "0000700088008800A800880088007000000000000000000000005000A800500000000000000000000000F80040002000100020004000F80000000000"
+ + "000030004800480030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ + "0000000070008800880088005000D800000000000000F80000000000700000000000F800000000000000A800A800A800700020002000200000000000"
+ + "0000100010007000A800A800A80070004000400000000000000000000000000000000000000000000000F4005C005400540000000000000000000000"
+ + "20007000A80020002000200020002000200020002000200020002000200020002000A800700020000000300048004000F00040004800B00000000000"
+ + "00000000000000006800900090006800000000000000700088008800F0008800C800B000800000000000000080008800500020005000880008000000"
+ + "000070008800400020005000880088007000000000000000000070008800600088007000000000000000000020007000A800A800A800700020000000"
+ + "0000000000008800880050005000700020002000000000000000B000C800880088008800080000000000000000000000600020002000300000000000"
+ + "0000000000001000A800A800A80070002000000000000000000048005000600050004800000000000000C00040002000200050008800880000000000"
+ + "00000000000088008800C800B00080008000000000000000000000008800880050002000000000000000000000007000880088008800700000000000"
+ + "0000880050002000F8002000F800200000000000000000000000F8005000500050004800000000000000700088008800F80088008800700000000000"
+ + "00000000000070008800C800B00080008000000000000000000078009000880088007000000000000000000008007000A00020002000100000000000"
+ + "0000000000008000500048004800300000000000000000000000F80050008800A800500000000000000000000000000050008800A800500000000000"
+ + "0000400030004000300040004000380008000000000000002000A800A800A80070002000200000000000400030004000400040004000380008000000"
+ + "0000000010000800FC00080010000000000000000000000020004000FC00400020000000000000000000000000000000000000000000000000000000"
+ + "1000380054001000100010001000540038001000");
+
+ // font 4: 12x16, chars 0x20-0x7F, 96 glyphs
+ private static readonly Face F4 = new(4, 0x20, 0x7F, 12, 16,
+ "00000000000000000000000000000000000000000000000000000000000000000C000C000C000C000C000C000C000C000C000C00000000000C000C00"
+ + "0000000033003300330033003300330000000000000000000000000000000000000000003300330033003300FFC0FFC033003300FFC0FFC033003300"
+ + "33003300000000000C000C003FC07FC0CC00CC007F003F800CC00CC0FF80FF000C000C0000000000F000F000F0C0F1C0038007000E001C0038007000"
+ + "E3C0C3C003C003C0000000003C007E00C300C700CE00DC0078007800DCC0CFC0C700C7007EC03CC0000000001E001E00060006001C00180000000000"
+ + "0000000000000000000000000000000000C001C0038007000E000C000C000C000C000E000700038001C000C000000000300038001C000E0007000300"
+ + "03000300030007000E001C003800300000000000000000000C000C00CCC0EDC07F807F80EDC0CCC00C000C000000000000000000000000000C000C00"
+ + "0C000C00FFC0FFC00C000C000C000C000000000000000000000000000000000000000000000000000F000F00030003000E000C000000000000000000"
+ + "0000000000000000FFC0FFC00000000000000000000000000000000000000000000000000000000000000000000000000F000F000F000F0000000000"
+ + "0000000000C001C0038007000E001C0038007000E000C00000000000000000003F007F80E1C0C0C0C1C0C2C0C4C0C8C0D0C0E0C0C0C0E1C07F803F00"
+ + "000000000C001C003C000C000C000C000C000C000C000C000C000C003F003F00000000003F007F80E1C0C0C000C001C0038007000E001C0038007000"
+ + "FFC0FFC000000000FFC0FFC0038007000E000E000700038001C000C000C001C0FF80FF000000000007000F001F003B0033006300E300C300FFC0FFC0"
+ + "030003000300030000000000FFC0FFC0C000C000C000C000FF00FF8001C000C000C001C0FF80FF00000000000F001F0038007000E000C000FF00FF80"
+ + "C1C0C0C0C0C0E1C07F803F0000000000FFC0FFC000C001C0038007000E001C00380030003000300030003000000000003F007F80E1C0C0C0C0C0E1C0"
+ + "7F807F80E1C0C0C0C0C0E1C07F803F00000000003F007F80E1C0C0C0C0C0E0C07FC03FC000C001C0038007003E003C0000000000000000000F000F00"
+ + "0F000F00000000000F000F000F000F000000000000000000000000000F000F000F000F00000000000F000F00030003000E000C000000000000C001C0"
+ + "038007000E001C00380038001C000E000700038001C000C0000000000000000000000000FFC0FFC000000000FFC0FFC0000000000000000000000000"
+ + "300038001C000E000700038001C001C0038007000E001C0038003000000000003F007F80E1C0C0C000C001C0038007000E000C00000000000C000C00"
+ + "000000003F007F80E1C0C0C000C000C038C07CC0CCC0CCC0CCC0CCC07F803F00000000003F007F80E1C0C0C0C0C0C0C0C0C0C0C0FFC0FFC0C0C0C0C0"
+ + "C0C0C0C000000000FF00FF80C1C0C0C0C0C0C1C0FF80FF80C1C0C0C0C0C0C1C0FF80FF00000000003F007F80E1C0C0C0C0C0C000C000C000C000C0C0"
+ + "C0C0C1C07F803F0000000000FF00FF80C1C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C1C0FF80FF0000000000FFC0FFC0C000C000C000C000FF00FF00"
+ + "C000C000C000C000FFC0FFC000000000FFC0FFC0C000C000C000C000FF00FF00C000C000C000C000C000C000000000003F007F80E1C0C0C0C000C000"
+ + "C000C7C0C7C0C0C0C0C0E1C07F803F0000000000C0C0C0C0C0C0C0C0C0C0C0C0FFC0FFC0C0C0C0C0C0C0C0C0C0C0C0C0000000003F003F000C000C00"
+ + "0C000C000C000C000C000C000C000C003F003F00000000000FC00FC003000300030003000300030003000300C300E7007E003C0000000000C0C0C1C0"
+ + "C380C700CE00DC00F800F800DC00CE00C700C380C1C0C0C000000000C000C000C000C000C000C000C000C000C000C000C000C000FFC0FFC000000000"
+ + "C0C0E1C0F3C0F3C0DEC0CCC0CCC0C0C0C0C0C0C0C0C0C0C0C0C0C0C000000000C0C0C0C0C0C0E0C0F0C0F8C0DCC0CEC0C7C0C3C0C1C0C0C0C0C0C0C0"
+ + "000000003F007F80E1C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0E1C07F803F0000000000FF00FF80C1C0C0C0C0C0C1C0FF80FF00C000C000C000C000"
+ + "C000C000000000003F007F80E1C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C2C0E1C07F803F4000000000FF00FF80C1C0C0C0C0C0C1C0FF80FF00DC00CE00"
+ + "C700C380C1C0C0C0000000003FC07FC0E000C000C000E0007F003F8001C000C000C001C0FF80FF0000000000FFC0FFC00C000C000C000C000C000C00"
+ + "0C000C000C000C000C000C0000000000C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0E1C07F803F0000000000C0C0C0C0C0C0C0C0C0C0C0C0"
+ + "C0C0C0C0C0C0E1C0738033001E000C0000000000C0C0C0C0C0C0C0C0CCC0CCC0CCC0CCC0CCC0CCC0CCC0CCC07F80330000000000C0C0C0C0C0C0E1C0"
+ + "73803F001E001E003F007380E1C0C0C0C0C0C0C000000000C0C0C0C0C0C0C0C0C0C0E1C073803F001E000C000C000C000C000C0000000000FFC0FFC0"
+ + "00C001C0038007000E001C0038007000E000C000FFC0FFC0000000003F003F0030003000300030003000300030003000300030003F003F0000000000"
+ + "00000000C000E000700038001C000E000700038001C000C000000000000000003F003F0003000300030003000300030003000300030003003F003F00"
+ + "000000000C001E003F007380E1C0C0C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
+ + "FFC0FFC0000000000C000E000700038001C000C0000000000000000000000000000000000000000000000000000000003F003F8000C000C03FC07FC0"
+ + "C0C0C0C07FC03FC000000000C000C000C000C000C000C000DF00FF80F1C0C0C0C0C0C1C0FF80FF000000000000000000000000003F007F80E1C0C0C0"
+ + "C000C000C0C0E1C07F803F000000000000C000C000C000C000C000C03EC07FC0E3C0C0C0C0C0E0C07FC03FC00000000000000000000000003F007F80"
+ + "C0C0C0C0FF80FF00C000C0007F003F00000000000F001F8039C030C030003000FC00FC00300030003000300030003000000000000000000000000000"
+ + "3FC07FC0C0C0C0C07FC03FC000C000C0FF80FF0000000000C000C000C000C000C000C000DF00FF80E1C0C0C0C0C0C0C0C0C0C0C0000000000C000C00"
+ + "000000003C003C000C000C000C000C000C000C003F003F000000000000C000C00000000000C000C000C000C000C000C030C039C01F800F0000000000"
+ + "300030003000300030C031C0338037003E003E003700338031C030C0000000003C003C000C000C000C000C000C000C000C000C000C000C003F003F00"
+ + "000000000000000000000000D300FF80FFC0CCC0CCC0CCC0CCC0CCC0CCC0CCC0000000000000000000000000CF00FF80F1C0C0C0C0C0C0C0C0C0C0C0"
+ + "C0C0C0C00000000000000000000000003F007F80E1C0C0C0C0C0C0C0C0C0E1C07F803F000000000000000000DF00FF80F1C0C0C0C0C0C1C0FF80FF00"
+ + "C000C000C000C00000000000000000003EC07FC0E3C0C0C0C0C0E0C07FC03FC000C000C000C000C0000000000000000000000000DF00FF80F1C0C0C0"
+ + "C000C000C000C000C000C0000000000000000000000000003FC07FC0C000C0007F003F8000C000C0FF80FF000000000030003000FC00FC0030003000"
+ + "300030003000300030C039C01F800F00000000000000000000000000C0C0C0C0C0C0C0C0C0C0C0C0C0C0E3C07FC03CC0000000000000000000000000"
+ + "C0C0C0C0C0C0C0C0C0C0E1C0738033001E000C00000000000000000000000000C0C0C0C0C0C0C0C0C0C0CCC0CCC0CCC07F8033000000000000000000"
+ + "00000000C0C0E1C073803F001E001E003F007380E1C0C0C0000000000000000000000000C0C0E1C073803F001E000C000C001C003800300000000000"
+ + "0000000000000000FFC0FFC0030007000E001C0038007000FFC0FFC00000000000C001C003800300030006000C000C00060003000300038001C000C0"
+ + "000000000C000C000C000C000C000C00000000000C000C000C000C000C000C0000000000300038001C000C000C0006000300030006000C000C001C00"
+ + "380030000000000000C001C00F801F00380030000000000000000000000000000000000000000000FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0"
+ + "FFF0FFF0FFF0FFF0FFF0FFF0");
+
+ // font 5: 12x20, chars 0x20-0x7F, 96 glyphs
+ private static readonly Face F5 = new(5, 0x20, 0x7F, 12, 20,
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000C000C000C000C000C000C000C000C00"
+ + "0C000C00000000000C000C00000000000000000000000000330033003300330033003300000000000000000000000000000000000000000000000000"
+ + "000000003300330033003300FFC0FFC033003300FFC0FFC033003300330033000000000000000000000000000C000C003FC07FC0CC00CC007F003F80"
+ + "0CC00CC0FF80FF000C000C00000000000000000000000000F000F000F0C0F1C0038007000E001C0038007000E3C0C3C003C003C00000000000000000"
+ + "000000003C007E00C300C700CE00DC0078007800DCC0CFC0C700C7007EC03CC00000000000000000000000001E001E00060006001C00180000000000"
+ + "00000000000000000000000000000000000000000000000000C001C0038007000E000C000C000C000C000E000700038001C000C00000000000000000"
+ + "00000000300038001C000E000700030003000300030007000E001C0038003000000000000000000000000000000000000C000C00CCC0EDC07F807F80"
+ + "EDC0CCC00C000C0000000000000000000000000000000000000000000C000C000C000C00FFC0FFC00C000C000C000C00000000000000000000000000"
+ + "000000000000000000000000000000000000000000000000000000000F000F00030003000E000C0000000000000000000000000000000000FFC0FFC0"
+ + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F000F000F000F000000000000000000"
+ + "000000000000000000C001C0038007000E001C0038007000E000C000000000000000000000000000000000003F007F80E1C0C0C0C1C0C2C0C4C0C8C0"
+ + "D0C0E0C0C0C0E1C07F803F000000000000000000000000000C001C003C000C000C000C000C000C000C000C000C000C003F003F000000000000000000"
+ + "000000003F007F80E1C0C0C000C001C0038007000E001C0038007000FFC0FFC0000000000000000000000000FFC0FFC0038007000E000E0007000380"
+ + "01C000C000C001C0FF80FF0000000000000000000000000007000F001F003B0033006300E300C300FFC0FFC003000300030003000000000000000000"
+ + "00000000FFC0FFC0C000C000C000C000FF00FF8001C000C000C001C0FF80FF000000000000000000000000000F001F0038007000E000C000FF00FF80"
+ + "C1C0C0C0C0C0E1C07F803F00000000000000000000000000FFC0FFC000C001C0038007000E001C003800300030003000300030000000000000000000"
+ + "000000003F007F80E1C0C0C0C0C0E1C07F807F80E1C0C0C0C0C0E1C07F803F000000000000000000000000003F007F80E1C0C0C0C0C0E0C07FC03FC0"
+ + "00C001C0038007003E003C00000000000000000000000000000000000F000F000F000F00000000000F000F000F000F00000000000000000000000000"
+ + "00000000000000000F000F000F000F00000000000F000F00030003000E000C0000000000000000000000000000C001C0038007000E001C0038003800"
+ + "1C000E000700038001C000C00000000000000000000000000000000000000000FFC0FFC000000000FFC0FFC000000000000000000000000000000000"
+ + "00000000300038001C000E000700038001C001C0038007000E001C00380030000000000000000000000000003F007F80E1C0C0C000C001C003800700"
+ + "0E000C00000000000C000C000000000000000000000000003F007F80E1C0C0C000C000C038C07CC0CCC0CCC0CCC0CCC07F803F000000000000000000"
+ + "000000003F007F80E1C0C0C0C0C0C0C0C0C0C0C0FFC0FFC0C0C0C0C0C0C0C0C0000000000000000000000000FF00FF80C1C0C0C0C0C0C1C0FF80FF80"
+ + "C1C0C0C0C0C0C1C0FF80FF000000000000000000000000003F007F80E1C0C0C0C0C0C000C000C000C000C0C0C0C0C1C07F803F000000000000000000"
+ + "00000000FF00FF80C1C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C1C0FF80FF00000000000000000000000000FFC0FFC0C000C000C000C000FF00FF00"
+ + "C000C000C000C000FFC0FFC0000000000000000000000000FFC0FFC0C000C000C000C000FF00FF00C000C000C000C000C000C0000000000000000000"
+ + "000000003F007F80E1C0C0C0C000C000C000C7C0C7C0C0C0C0C0E1C07F803F00000000000000000000000000C0C0C0C0C0C0C0C0C0C0C0C0FFC0FFC0"
+ + "C0C0C0C0C0C0C0C0C0C0C0C00000000000000000000000003F003F000C000C000C000C000C000C000C000C000C000C003F003F000000000000000000"
+ + "000000000FC00FC003000300030003000300030003000300C300E7007E003C00000000000000000000000000C0C0C1C0C380C700CE00DC00F800F800"
+ + "DC00CE00C700C380C1C0C0C0000000000000000000000000C000C000C000C000C000C000C000C000C000C000C000C000FFC0FFC00000000000000000"
+ + "00000000C0C0E1C0F3C0F3C0DEC0CCC0CCC0C0C0C0C0C0C0C0C0C0C0C0C0C0C0000000000000000000000000C0C0C0C0C0C0E0C0F0C0F8C0DCC0CEC0"
+ + "C7C0C3C0C1C0C0C0C0C0C0C00000000000000000000000003F007F80E1C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0E1C07F803F000000000000000000"
+ + "00000000FF00FF80C1C0C0C0C0C0C1C0FF80FF00C000C000C000C000C000C0000000000000000000000000003F007F80E1C0C0C0C0C0C0C0C0C0C0C0"
+ + "C0C0C0C0C2C0E1C07F803F40000000000000000000000000FF00FF80C1C0C0C0C0C0C1C0FF80FF00DC00CE00C700C380C1C0C0C00000000000000000"
+ + "000000003FC07FC0E000C000C000E0007F003F8001C000C000C001C0FF80FF00000000000000000000000000FFC0FFC00C000C000C000C000C000C00"
+ + "0C000C000C000C000C000C00000000000000000000000000C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0E1C07F803F000000000000000000"
+ + "00000000C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0E1C0738033001E000C00000000000000000000000000C0C0C0C0C0C0C0C0CCC0CCC0CCC0CCC0"
+ + "CCC0CCC0CCC0CCC07F803300000000000000000000000000C0C0C0C0C0C0E1C073803F001E001E003F007380E1C0C0C0C0C0C0C00000000000000000"
+ + "00000000C0C0C0C0C0C0C0C0C0C0E1C073803F001E000C000C000C000C000C00000000000000000000000000FFC0FFC000C001C0038007000E001C00"
+ + "38007000E000C000FFC0FFC00000000000000000000000003F003F0030003000300030003000300030003000300030003F003F000000000000000000"
+ + "0000000000000000C000E000700038001C000E000700038001C000C0000000000000000000000000000000003F003F00030003000300030003000300"
+ + "03000300030003003F003F000000000000000000000000000C001E003F007380E1C0C0C0000000000000000000000000000000000000000000000000"
+ + "0000000000000000000000000000000000000000000000000000000000000000FFC0FFC000000000000000000C000E000700038001C000C000000000"
+ + "00000000000000000000000000000000000000000000000000000000000000003F003F8000C000C03FC07FC0C0C0C0C07FC03FC00000000000000000"
+ + "00000000C000C000C000C000C000C000DF00FF80F1C0C0C0C0C0C1C0FF80FF0000000000000000000000000000000000000000003F007F80E1C0C0C0"
+ + "C000C000C0C0E1C07F803F0000000000000000000000000000C000C000C000C000C000C03EC07FC0E3C0C0C0C0C0E0C07FC03FC00000000000000000"
+ + "0000000000000000000000003F007F80C0C0C0C0FF80FF00C000C0007F003F000000000000000000000000000F001F8039C030C030003000FC00FC00"
+ + "30003000300030003000300000000000000000000000000000000000000000003FC07FC0E0C0C0C0C0C0E0C07FC03FC000C000C000C001C03F803F00"
+ + "00000000C000C000C000C000C000C000DF00FF80E1C0C0C0C0C0C0C0C0C0C0C00000000000000000000000000C000C00000000003C003C000C000C00"
+ + "0C000C000C000C003F003F0000000000000000000000000003000300000000000F000F00030003000300030003000300C300C300C300E7007E003C00"
+ + "00000000300030003000300030C031C0338037003E003E003700338031C030C00000000000000000000000003C003C000C000C000C000C000C000C00"
+ + "0C000C000C000C003F003F000000000000000000000000000000000000000000D300FF80FFC0CCC0CCC0CCC0CCC0CCC0CCC0CCC00000000000000000"
+ + "000000000000000000000000CF00FF80F1C0C0C0C0C0C0C0C0C0C0C0C0C0C0C000000000000000000000000000000000000000003F007F80E1C0C0C0"
+ + "C0C0C0C0C0C0E1C07F803F000000000000000000000000000000000000000000CF00DF80F9C0F0C0E0C0C1C0FF80FF00C000C000C000C000C000C000"
+ + "0000000000000000000000003CC07EC0E7C0C3C0C1C0E0C07FC03FC000C000C000C000C000C000C0000000000000000000000000DF00FF80F1C0C0C0"
+ + "C000C000C000C000C000C00000000000000000000000000000000000000000003FC07FC0C000C0007F003F8000C000C0FF80FF000000000000000000"
+ + "0000000030003000FC00FC0030003000300030003000300030C039C01F800F000000000000000000000000000000000000000000C0C0C0C0C0C0C0C0"
+ + "C0C0C0C0C0C0E3C07FC03CC00000000000000000000000000000000000000000C0C0C0C0C0C0C0C0C0C0E1C0738033001E000C000000000000000000"
+ + "000000000000000000000000C0C0C0C0C0C0C0C0C0C0CCC0CCC0CCC07F8033000000000000000000000000000000000000000000C0C0E1C073803F00"
+ + "1E001E003F007380E1C0C0C00000000000000000000000000000000000000000C0C0C0C0C0C0C0C0C0C0E0C07FC03FC000C000C000C001C03F803F00"
+ + "000000000000000000000000FFC0FFC0030007000E001C0038007000FFC0FFC000000000000000000000000000C001C003800300030006000C000C00"
+ + "060003000300038001C000C00000000000000000000000000C000C000C000C000C000C00000000000C000C000C000C000C000C000000000000000000"
+ + "00000000300038001C000C000C0006000300030006000C000C001C003800300000000000000000000000000000C001C00F801F003800300000000000"
+ + "000000000000000000000000000000000000000000000000FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF0FFF000000000");
+
+ // font 6: 7x10, chars 0x20-0xFF, 224 glyphs
+ private static readonly Face F6 = new(6, 0x20, 0xFF, 7, 10,
+ "000000000000000000000000000000000000000000001000100010001000000000001000000000000000280028002800000000000000000000000000"
+ + "0000280028007C0028007C002800280000000000000010003C0050003800140078001000000000000000600064000800100020004C000C0000000000"
+ + "000030004800500020005400480034000000000000003000100020000000000000000000000000000000080010002000200020001000080000000000"
+ + "0000200010000800080008001000200000000000000000001000540038005400100000000000000000000000100010007C0010001000000000000000"
+ + "000000000000000000000000300010002000000000000000000000007C00000000000000000000000000000000000000000000003000300000000000"
+ + "00000000040008001000200040000000000000000000380044004C005400640044003800000000000000100030001000100010001000380000000000"
+ + "00003800440004000800100020007C000000000000007C0008001000080004000400780000000000000008001800280048007C000800080000000000"
+ + "00007C0040007800040004000400780000000000000018002000400078004400440038000000000000007C0004000800100020002000200000000000"
+ + "000038004400440038004400440038000000000000003800440044003C00040008003000000000000000000030003000000030003000000000000000"
+ + "000000003000300000003000100020000000000000000800100020004000200010000800000000000000000000007C0000007C000000000000000000"
+ + "000040002000100008001000200040000000000000003800440004000800100000001000000000000000380044000400340054005400380000000000"
+ + "000038004400440044007C00440044000000000000007800440044007800440044007800000000000000380044004000400040004400380000000000"
+ + "000078004400440044004400440078000000000000007C00400040007800400040007C000000000000007C0040004000780040004000400000000000"
+ + "00003800440040005C004400440038000000000000004400440044007C00440044004400000000000000380010001000100010001000380000000000"
+ + "00001C0008000800080008004800300000000000000044004800500060005000480044000000000000004000400040004000400040007C0000000000"
+ + "000044006C005400540044004400440000000000000044004400640054004C0044004400000000000000380044004400440044004400380000000000"
+ + "000078004400440078004000400040000000000000003800440044004400540048003400000000000000780044004400780050004800440000000000"
+ + "00003C004000400038000400040078000000000000007C00100010001000100010001000000000000000440044004400440044004400380000000000"
+ + "000044004400440044004400280010000000000000004400440044005400540054002800000000000000440044002800100028004400440000000000"
+ + "000044004400440028001000100010000000000000007C00040008001000200040007C00000000000000380020002000200020002000380000000000"
+ + "000040002000100008000400000000000000000000003800080008000800080008003800000000000000100028004400000000000000000000000000"
+ + "000000000000000000000000000000000000FE000000300020001000000000000000000000000000000000000000380004003C0044003C0000000000"
+ + "0000400040005800640044004400780000000000000000000000380040004000400038000000000000000400040034004C00440044003C0000000000"
+ + "000000000000380044007C00400038000000000000001800240020007000200020002000000000000000000000003C00440044003C00040004003800"
+ + "000040004000580064004400440044000000000000001000000030001000100010003800000000000000080000001800080008000800080048003000"
+ + "000040004000480050006000500048000000000000003000100010001000100010003800000000000000000000006800540054004400440000000000"
+ + "000000000000580064004400440044000000000000000000000038004400440044003800000000000000000000005800640044007800400040004000"
+ + "00000000000034004C0044003C0004000400040000000000000058006400400040004000000000000000000000003C00400038000400780000000000"
+ + "000000002000F8002000200028001000000000000000000000004400440044004C003400000000000000000000004400440044002800100000000000"
+ + "000000000000440044005400540028000000000000000000000044002800100028004400000000000000000000004400440044003C00040004003800"
+ + "0000000000007C000800100020007C000000000000000800100010002000100010000800000000000000100010001000000010001000100000000000"
+ + "00002000100010000800100010002000000000000000040038004000000000000000000000000000FE00FE00FE00FE00FE00FE00FE00FE00FE00FE00"
+ + "0000000000000000FE000000000000000000000008000800080008000800080008000800080008000800080008000800F80000000000000000000000"
+ + "00000000000000000E000800080008000800080008000800080008000E00000000000000000000000000000000000000F80008000800080008000800"
+ + "08000800080008000E00080008000800080008000800080008000800F800080008000800080008000800080008000800FE0008000800080008000800"
+ + "0800080008000800FE00000000000000000000000000000000000000FE00080008000800080008000000000000000000020004000800080008000800"
+ + "0000000000000000E000100008000800080008000800080008001000E000000000000000000000000800080008000400020000000000000000000000"
+ + "1C001C001C00FE00FE00FE001C001C001C001C00000000000000FE00FE00FE0000000000000000001C001C001C001C001C001C001C001C001C001C00"
+ + "1C001C001C00FC00FC00FC0000000000000000000000000000001E001E001E001C001C001C001C001C001C001C001E001E001E000000000000000000"
+ + "000000000000FC00FC00FC001C001C001C001C001C001C001C001E001E001E001C001C001C001C001C001C001C00FC00FC00FC001C001C001C001C00"
+ + "000000001000380054001000100010001000000000000000000020001000F8001000200000000000000000000000080010003E001000080000000000"
+ + "00000000000008000800F8000000000000000000000008000800080008002A001C0008000000000000000000080014002200140014001C0000000000"
+ + "0000000008001C003E001C001C001C00000000000000000010003E007E003E0010000000000000000000000054004400820082008200440038000000"
+ + "000000000000000000007000500070000000000000001C00100010001000000000000000000000000000000000000000100010001000700000000000"
+ + "00000000000000000000400020001000000000000000000000000000300030000000000000000000000000007C0004007C0004000800100000000000"
+ + "0000000000007C00040018001000200000000000000000000000080010003000500010000000000000000000000010007C0044000400180000000000"
+ + "00000000000000007C00100010007C000000000000000000000008007C001800280048000000000000000000000020007C0024002800200000000000"
+ + "00000000000000003800080008007C000000000000000000000078000800780008007800000000000000000000000000540054000400180000000000"
+ + "0000000000007C0000000000000000000000000000007C00040014001800100010002000000000000000040008001000300050001000100000000000"
+ + "000010007C004400440004000800100000000000000000007C0010001000100010007C0000000000000008007C000800180028004800080000000000"
+ + "000020007C002400240024002400480000000000000010007C0010007C0010001000100000000000000000003C002400440004000800300000000000"
+ + "000020003C004800080008000800100000000000000000007C0004000400040004007C0000000000000028007C002800280008001000200000000000"
+ + "0000000060000400640004000800700000000000000000007C000400080010002800440000000000000020007C0024002800200020001C0000000000"
+ + "0000000044004400240004000800300000000000000000003C00240054000C00080030000000000000000800700010007C0010001000200000000000"
+ + "00000000540054005400040008001000000000000000380000007C001000100010002000000000000000200020002000300028002000200000000000"
+ + "0000100010007C0010001000200040000000000000000000380000000000000000007C0000000000000000007C000400280010002800400000000000"
+ + "000010007C00080010003800540010000000000000000800080008000800080010002000000000000000000010000800440044004400440000000000"
+ + "0000400040007C004000400040003C0000000000000000007C0004000400040008003000000000000000000020005000080004000400000000000000"
+ + "000010007C001000100054005400100000000000000000007C0004000400280010000800000000000000000038000000380000003800040000000000"
+ + "0000000010002000400044007C000400000000000000000004000400280010002800400000000000000000007C0020007C00200020001C0000000000"
+ + "0000200020007C0024002800200020000000000000000000380008000800080008007C0000000000000000007C0004007C00040004007C0000000000"
+ + "0000380000007C0004000400080010000000000000004800480048004800080010002000000000000000100050005000500054005400180000000000"
+ + "0000400040004000440048005000600000000000000000007C0044004400440044007C0000000000000000007C004400440004000800100000000000"
+ + "000000006000000004000400080070000000000000001000480020000000000000000000000000000000700050007000000000000000000000000000"
+ + "0000100000001000200040004400380000000000040008003800440044007C004400440000000000400020003800440044007C004400440000000000"
+ + "280000003800440044007C0044004400000000000000380044004000400040004400380010003000040008007C0040007800400040007C0000000000"
+ + "400020007C0040007800400040007C000000000038004400380010001000100010003800000000002800000038004400440044004400380000000000"
+ + "2800000044004400440044004400380000000000040008000000380004003C0044003C0000000000380044000000380004003C0044003C0000000000"
+ + "400020000000380004003C0044003C0000000000000028000000380004003C0044003C00000000000000000000003800400040004400380010003000"
+ + "040008000000380044007C004000380000000000380044000000380044007C004000380000000000400020000000380044007C004000380000000000"
+ + "040008002000000060002000200070000000000038004400100000003000100010003800000000008000400010000000300010001000380000000000"
+ + "280000001000000030001000100038000000000034005C00000058006400440044004400000000000400080000003800440044004400380000000000"
+ + "380044000000380044004400440038000000000040002000000038004400440044003800000000000000280000003800440044004400380000000000"
+ + "0000380044005800440044006400D800000000000800100000004400440044004C003400000000003800440000004400440044004C00340000000000"
+ + "4000200000004400440044004C003400000000000000280000004400440044004C00340000000000");
+
+ // font 7: 7x10, chars 0x40-0x7F, 64 glyphs
+ private static readonly Face F7 = new(7, 0x40, 0x7F, 7, 10,
+ "00000800100020001000080000007C000000000000002000100008001000200000007C00000000000000100010007C001000100000007C0000000000"
+ + "00000000100000007C000000100000000000000000001000280028002800440044007C00000000000000040008007C0010007C002000400000000000"
+ + "100038005400540054005400540038001000000000007C0020002000200020002000700000000000000000007C0000007C0000007C00000000000000"
+ + "0000040038004000040038004000000000000000000004003800400000007C00000000000000000000000400040004004800A8002800100010000000"
+ + "0000100028002800280044004400440000000000000038004400040034004C0044004800300000000000000000004400280010002800440000000000"
+ + "00000000100038007C0038001000000000000000000010003800540050005400380010000000000000007C0044004400440044004400440000000000"
+ + "0000380044004400540044004400380000000000000000000000280054002800000000000000000000007C00200010000800100020007C0000000000"
+ + "00001800240024001800000000000000000000000000380044009200BA0092004400380000000000000038004400AA009200AA004400380000000000"
+ + "00000000380044004400440028006C000000000000007C00000000003800000000007C00000000000000540054005400380010001000100000000000"
+ + "00003A0044008A009200A2004400B800000000000000380044009A00A2009A00440038000000000000007A002E002A002A0000000000000000000000"
+ + "08001C002A00080008000800080008000800080008000800080008000800080008002A001C0008000000180024002000780020002400580000000000"
+ + "000000000000340048004800340000000000000000003800440044007800440064004C00400000000000000040004400280010002800440004000000"
+ + "000038004400200010002800440044003800000000000000000038004400300044003800000000000000000010003800540054005400380010000000"
+ + "000000000000440044002800280038001000100000000000000058006400440044004400040000000000000000000000300010001000180000000000"
+ + "000000000000080054005400540038001000000000000000000024002800300028002400000000000000600020001000100028004400440000000000"
+ + "000000000000440044006400580040004000000000000000000044004400280028001000000000000000000000003800440044004400380000000000"
+ + "00004400280010007C0010007C001000000000000000000000007C0028002800280024000000000000003800440044007C0044004400380000000000"
+ + "00000000000038004400640058004000400000000000000000003C004800440044003800000000000000000004003800500010001000080000000000"
+ + "00000000000040002800240024001800000000000000000000007C002800440054002800000000000000000000000000280044005400280000000000"
+ + "00002000180020001800200020001C0004000000000000001000540054003800100010000000000000002000180020002000200020001C0004000000"
+ + "00000000080004007E00040008000000000000000000000020004000FE00400020000000000000000000000028004400FE0044002800000000000000"
+ + "08001C002A0008000800080008002A001C000800");
+
+ /// All 8 faces, indexed 0-7 (matches ESC K).
+ public static readonly Face[] All = { F0, F1, F2, F3, F4, F5, F6, F7 };
+
+ /// The default/power-on font (font 0: 6x8, full 0x20-0xFF range).
+ public static Face Default => F0;
+}
diff --git a/src/VPlasma.Core/Device/PlasmaSelfTest.cs b/src/VPlasma.Core/Device/PlasmaSelfTest.cs
index 584efb6..f49f5a1 100644
--- a/src/VPlasma.Core/Device/PlasmaSelfTest.cs
+++ b/src/VPlasma.Core/Device/PlasmaSelfTest.cs
@@ -50,10 +50,11 @@ public static class PlasmaSelfTest
b.Add(PlasmaProtocol.LineFeed);
Text(b, "128x32 PLASMA DISPLAY"); // exactly one 21-cell row
+ // Attributes are the low 4 bits: 1=half, 2=underline, 4=reverse, 8=flash.
Esc(b, PlasmaProtocol.CmdAttributes, 1); Text(b, "HALF ");
Esc(b, PlasmaProtocol.CmdAttributes, 2); Text(b, "UNDER ");
- Esc(b, PlasmaProtocol.CmdAttributes, 3); Text(b, "REV");
- Esc(b, PlasmaProtocol.CmdAttributes, 4); Text(b, " FLASH");
+ Esc(b, PlasmaProtocol.CmdAttributes, 4); Text(b, "REV");
+ Esc(b, PlasmaProtocol.CmdAttributes, 8); Text(b, " FLASH");
Esc(b, PlasmaProtocol.CmdAttributes, PlasmaProtocol.OperandDefault);
return b.ToArray();
}
diff --git a/src/VPlasma.Core/Device/VPlasmaDevice.cs b/src/VPlasma.Core/Device/VPlasmaDevice.cs
index ff1c0e0..6a567d9 100644
--- a/src/VPlasma.Core/Device/VPlasmaDevice.cs
+++ b/src/VPlasma.Core/Device/VPlasmaDevice.cs
@@ -10,7 +10,7 @@ public enum PlasmaCursorMode
Flashing,
}
-/// Text rendering attributes (set with ESC H).
+/// Text rendering attributes (set with ESC H, low 4 bits).
[Flags]
public enum PlasmaAttributes : byte
{
@@ -27,17 +27,23 @@ public enum PlasmaAttributes : byte
/// COM2. Feed raw wire bytes to ; the parser is a
/// state machine, so commands may arrive split across any chunk boundaries.
///
+/// Grounded in the real firmware (PlasmaNew/FIRMWARE.md): the
+/// cursor is a pixel position — ESC Q sets its row (Y, 0–31),
+/// ESC R its column (X, 0–127) — and glyphs are drawn there and advance
+/// X by the font's width. The eight fonts are the real ROM character generator
+/// (): 6×8, 6×10, 7×10, and the large 12×16 / 12×20,
+/// selected with ESC K (0–7). Attributes are the low 4 bits of the
+/// ESC H operand.
+///
/// Pixels carry flags rather than a plain bit: graphics writes set
-/// full-intensity dots, while text can stamp half-intensity or flashing dots
-/// (ESC H); the UI renders dimmer and blinks
-/// . The command set itself is documented on
+/// full-intensity dots, while text can stamp half-intensity or flashing dots;
+/// the UI renders dimmer and blinks
+/// . The command set is documented on
/// .
///
-/// Grid geometry: fonts 0–3 are the 5×7 set in a 6×8 cell (21 columns
-/// × 4 rows), fonts 4–7 the same glyphs doubled into a 12×16 cell
-/// (10 × 2). Which of the eight slots the real panel mapped to which face is
-/// lost with the hardware; two sizes cover what the surviving software
-/// exercises.
+/// Not yet folded in from the firmware (documented, deferred): the 10
+/// double-buffered pages (ESC I/ESC i) and the vector-graphics
+/// primitives (ESC A–F). vPLASMA models a single page.
///
/// Thread-safe: the serial reader feeds bytes while the UI snapshots
/// frames. Events are raised outside the lock, on the caller's thread.
@@ -58,8 +64,9 @@ public sealed class VPlasmaDevice
// ---- text-mode state -------------------------------------------------
private int _font; // 0..7
+ private PlasmaFonts.Face _face = PlasmaFonts.Default;
private PlasmaAttributes _attributes;
- private int _col, _row; // cursor, in cells of the current grid
+ private int _x, _y; // cursor, in pixels (0..127, 0..31)
private PlasmaCursorMode _cursorMode = PlasmaCursorMode.Steady; // power-on default; the game hides it
// ---- parser state ----------------------------------------------------
@@ -99,16 +106,14 @@ public sealed class VPlasmaDevice
public int Font { get { lock (_sync) return _font; } }
public PlasmaAttributes Attributes { get { lock (_sync) return _attributes; } }
- // Current font grid, for the UI's cursor overlay and status line.
- private int FontScale => _font >= 4 ? 2 : 1;
- public int CellWidth { get { lock (_sync) return 6 * FontScale; } }
- public int CellHeight { get { lock (_sync) return 8 * FontScale; } }
- private int Columns => Width / (6 * FontScale);
- private int Rows => Height / (8 * FontScale);
- public Point CursorCell { get { lock (_sync) return new Point(_col, _row); } }
-
- /// A cursor cell position (avoids dragging in System.Drawing).
- public readonly record struct Point(int Col, int Row);
+ /// Cursor column X in pixels (0–127).
+ public int CursorX { get { lock (_sync) return _x; } }
+ /// Cursor row Y in pixels (0–31).
+ public int CursorY { get { lock (_sync) return _y; } }
+ /// Current font cell width in pixels.
+ public int FontWidth { get { lock (_sync) return _face.Width; } }
+ /// Current font cell height in pixels.
+ public int FontHeight { get { lock (_sync) return _face.Height; } }
/// Copy the frame into (Width*Height flag bytes).
public void CopyFrame(byte[] destination)
@@ -125,8 +130,9 @@ public sealed class VPlasmaDevice
lock (_sync)
{
Array.Clear(_pixels, 0, _pixels.Length);
- _col = _row = 0;
+ _x = _y = 0;
_font = 0;
+ _face = PlasmaFonts.Default;
_attributes = PlasmaAttributes.None;
_cursorMode = PlasmaCursorMode.Steady;
_state = State.Text;
@@ -187,7 +193,7 @@ public sealed class VPlasmaDevice
return;
case PlasmaProtocol.BackSpace:
- if (_col > 0) _col--;
+ _x = Math.Max(0, _x - _face.Width);
_dirty = true;
return;
@@ -197,17 +203,18 @@ public sealed class VPlasmaDevice
return;
case PlasmaProtocol.LineFeed:
- _row = (_row + 1) % Rows;
+ NextLine();
_dirty = true;
return;
case PlasmaProtocol.VerticalTab:
- _row = (_row + Rows - 1) % Rows;
+ _y -= _face.Height;
+ if (_y < 0) _y = Math.Max(0, Height - _face.Height);
_dirty = true;
return;
case PlasmaProtocol.CarriageReturn:
- _col = 0;
+ _x = 0;
_dirty = true;
return;
}
@@ -232,8 +239,9 @@ public sealed class VPlasmaDevice
{
case PlasmaProtocol.CmdClearScreen:
Array.Clear(_pixels, 0, _pixels.Length);
- _col = _row = 0;
+ _x = _y = 0;
_font = 0;
+ _face = PlasmaFonts.Default;
_attributes = PlasmaAttributes.None;
_dirty = true;
Log("Clear screen (ESC @)");
@@ -241,7 +249,7 @@ public sealed class VPlasmaDevice
break;
case PlasmaProtocol.CmdHomeCursor:
- _col = _row = 0;
+ _x = _y = 0;
_dirty = true;
Log("Home cursor (ESC L)");
_graphicsLogArmed = true;
@@ -250,6 +258,8 @@ public sealed class VPlasmaDevice
case PlasmaProtocol.CmdCursorMode:
case PlasmaProtocol.CmdFontSelect:
case PlasmaProtocol.CmdAttributes:
+ case PlasmaProtocol.CmdSetRow:
+ case PlasmaProtocol.CmdSetColumn:
_pendingCommand = b;
_state = State.Operand;
break;
@@ -282,50 +292,52 @@ public sealed class VPlasmaDevice
break;
case PlasmaProtocol.CmdFontSelect:
- _font = operand == PlasmaProtocol.OperandDefault ? 0 : operand & 0x07;
- // The cursor keeps its cell coordinates but the grid changed size.
- _col = Math.Min(_col, Columns - 1);
- _row = Math.Min(_row, Rows - 1);
- _dirty = true;
- Log($"Font {_font}: {Columns}×{Rows} cells (ESC K {operand:X2})");
+ // Firmware: operands 0–7 select a real font (8 fonts); it range-
+ // checks and ignores anything larger, so ESC K FF is a no-op.
+ if (operand < PlasmaFonts.All.Length)
+ {
+ _font = operand;
+ _face = PlasmaFonts.All[operand];
+ _x = Math.Min(_x, Width - 1);
+ _y = Math.Min(_y, Height - 1);
+ _dirty = true;
+ Log($"Font {_font}: {_face.Width}×{_face.Height} (ESC K {operand:X2})");
+ }
+ else
+ {
+ Log($"Font select ignored (ESC K {operand:X2} out of range)");
+ }
break;
case PlasmaProtocol.CmdAttributes:
- _attributes = DecodeAttributes(operand);
+ // Firmware stores the low 4 bits directly as flags.
+ _attributes = (PlasmaAttributes)(operand & 0x0F);
Log($"Attributes {(_attributes == PlasmaAttributes.None ? "default" : _attributes.ToString())} (ESC H {operand:X2})");
break;
+
+ case PlasmaProtocol.CmdSetRow:
+ // ESC Q: set cursor row Y in pixels; firmware range-checks 0–31.
+ if (operand < Height)
+ {
+ _y = operand;
+ _dirty = true;
+ Log($"Cursor row Y={operand} (ESC Q {operand:X2})");
+ }
+ break;
+
+ case PlasmaProtocol.CmdSetColumn:
+ // ESC R: set cursor column X in pixels; firmware range-checks 0–127.
+ if (operand < Width)
+ {
+ _x = operand;
+ _dirty = true;
+ Log($"Cursor col X={operand} (ESC R {operand:X2})");
+ }
+ break;
}
_graphicsLogArmed = true;
}
- ///
- /// ESC H's operand indexes the style list PLASMA.EXE enumerates
- /// (its /s option): the 17 intensity/underline/reverse/flash
- /// combos below, in the tool's own order. FF (and anything out of range)
- /// restores the defaults.
- ///
- private static PlasmaAttributes DecodeAttributes(byte operand) => operand switch
- {
- 0 => PlasmaAttributes.None,
- 1 => PlasmaAttributes.HalfIntensity,
- 2 => PlasmaAttributes.Underline,
- 3 => PlasmaAttributes.Reverse,
- 4 => PlasmaAttributes.Flash,
- 5 => PlasmaAttributes.Underline,
- 6 => PlasmaAttributes.Reverse,
- 7 => PlasmaAttributes.Flash,
- 8 => PlasmaAttributes.HalfIntensity | PlasmaAttributes.Underline,
- 9 => PlasmaAttributes.HalfIntensity | PlasmaAttributes.Reverse,
- 10 => PlasmaAttributes.HalfIntensity | PlasmaAttributes.Flash,
- 11 => PlasmaAttributes.Underline | PlasmaAttributes.Reverse,
- 12 => PlasmaAttributes.Underline | PlasmaAttributes.Flash,
- 13 => PlasmaAttributes.Underline | PlasmaAttributes.Reverse | PlasmaAttributes.Flash,
- 14 => PlasmaAttributes.HalfIntensity | PlasmaAttributes.Underline | PlasmaAttributes.Reverse,
- 15 => PlasmaAttributes.HalfIntensity | PlasmaAttributes.Underline | PlasmaAttributes.Flash,
- 16 => PlasmaAttributes.HalfIntensity | PlasmaAttributes.Underline | PlasmaAttributes.Reverse | PlasmaAttributes.Flash,
- _ => PlasmaAttributes.None,
- };
-
// ---- graphics writes (ESC P) -------------------------------------------
private void BeginGraphicsData()
@@ -374,34 +386,35 @@ public sealed class VPlasmaDevice
private void DrawChar(byte code)
{
- int scale = FontScale;
- int cellW = 6 * scale, cellH = 8 * scale;
- int ox = _col * cellW, oy = _row * cellH;
-
- Span columns = stackalloc byte[PlasmaFont.GlyphWidth];
- PlasmaFont.GetColumns(code, columns);
+ // The firmware ignores a character outside the current font's range.
+ if (!_face.Has(code))
+ return;
+ int w = _face.Width, h = _face.Height;
bool reverse = (_attributes & PlasmaAttributes.Reverse) != 0;
bool underline = (_attributes & PlasmaAttributes.Underline) != 0;
byte litFlags = PixelLit;
if ((_attributes & PlasmaAttributes.HalfIntensity) != 0) litFlags |= PixelHalf;
if ((_attributes & PlasmaAttributes.Flash) != 0) litFlags |= PixelFlash;
- for (int cy = 0; cy < cellH; ++cy)
+ for (int row = 0; row < h; ++row)
{
- int glyphRow = cy / scale; // 0..7; row 7 is the gap/underline row
- int rowOffset = (oy + cy) * Width + ox;
- for (int cx = 0; cx < cellW; ++cx)
+ int py = _y + row;
+ if ((uint)py >= Height)
+ continue;
+ ushort bits = _face.Row(code, row); // bit 15 = leftmost pixel
+ int rowOffset = py * Width;
+ for (int col = 0; col < w; ++col)
{
- int glyphCol = cx / scale; // 0..5; column 5 is the gap column
- bool on = glyphCol < PlasmaFont.GlyphWidth
- && glyphRow < PlasmaFont.GlyphHeight
- && (columns[glyphCol] >> glyphRow & 1) != 0;
- if (underline && glyphRow == 7)
+ int px = _x + col;
+ if ((uint)px >= Width)
+ continue;
+ bool on = (bits & (0x8000 >> col)) != 0;
+ if (underline && row == h - 1)
on = true;
if (reverse)
on = !on;
- _pixels[rowOffset + cx] = on ? litFlags : (byte)0;
+ _pixels[rowOffset + px] = on ? litFlags : (byte)0;
}
}
@@ -410,15 +423,21 @@ public sealed class VPlasmaDevice
AdvanceCursor();
}
+ /// Advance the cursor one cell, wrapping at the right/bottom edges.
private void AdvanceCursor()
{
- if (++_col >= Columns)
- {
- _col = 0;
- // No scroll on these panels: writing past the last row wraps to the top.
- if (++_row >= Rows)
- _row = 0;
- }
+ _x += _face.Width;
+ if (_x > Width - _face.Width)
+ NextLine();
+ }
+
+ private void NextLine()
+ {
+ _x = 0;
+ _y += _face.Height;
+ // No scroll on these panels: past the last line wraps to the top.
+ if (_y > Height - _face.Height)
+ _y = 0;
}
// ---- event plumbing --------------------------------------------------------
diff --git a/src/VPlasma.Core/Protocol/PlasmaProtocol.cs b/src/VPlasma.Core/Protocol/PlasmaProtocol.cs
index 2fa1be0..3e9a690 100644
--- a/src/VPlasma.Core/Protocol/PlasmaProtocol.cs
+++ b/src/VPlasma.Core/Protocol/PlasmaProtocol.cs
@@ -58,6 +58,11 @@ public static class PlasmaProtocol
public const byte CmdFontSelect = (byte)'K';
public const byte CmdHomeCursor = (byte)'L';
public const byte CmdGraphicsWrite = (byte)'P';
+ // Cursor positioning, confirmed from the firmware dump (FIRMWARE.md): the
+ // cursor is a pixel position, ESC Q sets its row (Y, 0-31) and ESC R its
+ // column (X, 0-127) — the exact range checks the firmware enforces.
+ public const byte CmdSetRow = (byte)'Q';
+ public const byte CmdSetColumn = (byte)'R';
/// Operand meaning "restore the default" for ESC K / ESC H.
public const byte OperandDefault = 0xFF;
diff --git a/tests/VPlasma.Core.Tests/VPlasmaDeviceTests.cs b/tests/VPlasma.Core.Tests/VPlasmaDeviceTests.cs
index 891a62b..4bd1e90 100644
--- a/tests/VPlasma.Core.Tests/VPlasmaDeviceTests.cs
+++ b/tests/VPlasma.Core.Tests/VPlasmaDeviceTests.cs
@@ -53,7 +53,6 @@ public class VPlasmaDeviceTests
{
byte[] wire = GraphicsRow(3, Enumerable.Repeat((byte)0xFF, 16).ToArray());
- // Split the same command at every possible boundary.
for (int split = 1; split < wire.Length; ++split)
{
var device = new VPlasmaDevice();
@@ -71,7 +70,6 @@ public class VPlasmaDeviceTests
public void GraphicsBlock_MultipleRowsAdvanceY()
{
var device = new VPlasmaDevice();
- // screen 0, y=10, xbyte=0, 1 byte/row, 3 rows.
Feed(device, Esc, (byte)'P', 0, 10, 0, 1, 3, 0x80, 0x80, 0x80);
Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 0, 10));
@@ -98,10 +96,8 @@ public class VPlasmaDeviceTests
{
var device = new VPlasmaDevice();
Feed(device, GraphicsRow(40, Enumerable.Repeat((byte)0xFF, 16).ToArray()));
- Feed(device, (byte)'!'); // parser must be back in text mode
+ Feed(device, (byte)'H'); // parser must be back in text mode
- var frame = new byte[VPlasmaDevice.Width * VPlasmaDevice.Height];
- device.CopyFrame(frame);
Assert.Equal(1, device.TextCharsDrawn);
Assert.Equal(0, device.GraphicsRows);
}
@@ -110,64 +106,83 @@ public class VPlasmaDeviceTests
public void GraphicsWrite_OverwritesTextAttributes()
{
var device = new VPlasmaDevice();
- Feed(device, Esc, (byte)'H', 4); // flashing text
- Feed(device, (byte)'H'); // glyph col 0 is a full bar at x=0
+ Feed(device, Esc, (byte)'H', 8); // flashing text
+ Feed(device, (byte)'H'); // font-0 'H' lights (0,0)
Assert.Equal(VPlasmaDevice.PixelLit | VPlasmaDevice.PixelFlash, Pixel(device, 0, 0));
Feed(device, GraphicsRow(0, 0x80)); // repaint that row from the wire
Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 0, 0));
}
- // ---- text mode ---------------------------------------------------------
+ // ---- text mode (real font 0 = 6×8) -----------------------------------
[Fact]
- public void Text_DrawsGlyphAndAdvancesCursor()
+ public void Text_DrawsRealGlyphAndAdvancesByFontWidth()
{
var device = new VPlasmaDevice();
- Feed(device, (byte)'H'); // 5×7 'H': column 0 = 0x7F (all seven rows)
+ Feed(device, (byte)'H'); // font-0 'H': row0 = pixels at x0 and x4
Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 0, 0));
- Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 0, 6));
- Assert.Equal(0, Pixel(device, 0, 7)); // gap row
- Assert.Equal(0, Pixel(device, 5, 0)); // gap column
- Assert.Equal(new VPlasmaDevice.Point(1, 0), device.CursorCell);
+ Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 4, 0));
+ Assert.Equal(0, Pixel(device, 1, 0));
+ Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 0, 3)); // crossbar row
+ Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 4, 3));
+ Assert.Equal(0, Pixel(device, 0, 7)); // blank bottom row
+ Assert.Equal(6, device.CursorX); // advanced one 6-px cell
+ Assert.Equal(0, device.CursorY);
Assert.Equal(1, device.TextCharsDrawn);
}
[Fact]
- public void ControlChars_MoveTheCursor()
+ public void ControlChars_MoveCursorByPixels()
{
- var device = new VPlasmaDevice();
+ var device = new VPlasmaDevice(); // font 0: 6 wide, 8 tall
- Feed(device, 0x09, 0x09, 0x09); // HT ×3
- Assert.Equal(new VPlasmaDevice.Point(3, 0), device.CursorCell);
+ Feed(device, 0x09, 0x09, 0x09); // HT ×3 → x = 18
+ Assert.Equal(18, device.CursorX);
- Feed(device, 0x08); // BS
- Assert.Equal(new VPlasmaDevice.Point(2, 0), device.CursorCell);
+ Feed(device, 0x08); // BS → x = 12
+ Assert.Equal(12, device.CursorX);
- Feed(device, 0x0A); // LF
- Assert.Equal(new VPlasmaDevice.Point(2, 1), device.CursorCell);
+ Feed(device, 0x0A); // LF → y = 8
+ Assert.Equal(8, device.CursorY);
+ Assert.Equal(0, device.CursorX);
- Feed(device, 0x0D); // CR
- Assert.Equal(new VPlasmaDevice.Point(0, 1), device.CursorCell);
+ Feed(device, (byte)'H', 0x0D); // draw + CR → x back to 0
+ Assert.Equal(0, device.CursorX);
- Feed(device, 0x0B); // VT
- Assert.Equal(new VPlasmaDevice.Point(0, 0), device.CursorCell);
-
- Feed(device, 0x0B); // VT off the top wraps to the bottom row
- Assert.Equal(new VPlasmaDevice.Point(0, 3), device.CursorCell);
+ Feed(device, 0x0B); // VT → y back to 0
+ Assert.Equal(0, device.CursorY);
}
[Fact]
- public void Text_WrapsAtRowAndScreenEnd()
+ public void EscQ_EscR_SetPixelCursor()
{
var device = new VPlasmaDevice();
- Feed(device, Enumerable.Repeat((byte)'X', 22)); // one full 21-cell row + 1
- Assert.Equal(new VPlasmaDevice.Point(1, 1), device.CursorCell);
+ Feed(device, Esc, (byte)'Q', 20); // set row Y=20
+ Feed(device, Esc, (byte)'R', 37); // set col X=37
+ Assert.Equal(37, device.CursorX);
+ Assert.Equal(20, device.CursorY);
- Feed(device, Enumerable.Repeat((byte)'X', 21 * 3 - 1)); // exactly to the end
- Assert.Equal(new VPlasmaDevice.Point(0, 0), device.CursorCell); // wrapped to top
+ Feed(device, Esc, (byte)'Q', 40); // out of range (>31): ignored
+ Assert.Equal(20, device.CursorY);
+ Feed(device, Esc, (byte)'R', 200); // out of range (>127): ignored
+ Assert.Equal(37, device.CursorX);
+ }
+
+ [Fact]
+ public void Text_WrapsAtRightAndBottom()
+ {
+ var device = new VPlasmaDevice(); // 21 cells of 6px per line, 4 lines
+
+ Feed(device, Enumerable.Repeat((byte)'X', 21)); // fills first line, wraps
+ Assert.Equal(0, device.CursorX);
+ Assert.Equal(8, device.CursorY);
+
+ Feed(device, Enumerable.Repeat((byte)'X', 21 * 3)); // to the end, wraps to top
+ Assert.Equal(0, device.CursorX);
+ Assert.Equal(0, device.CursorY);
}
[Fact]
@@ -178,7 +193,8 @@ public class VPlasmaDeviceTests
Feed(device, Esc, (byte)'@');
Assert.Equal(0, Pixel(device, 0, 0));
- Assert.Equal(new VPlasmaDevice.Point(0, 0), device.CursorCell);
+ Assert.Equal(0, device.CursorX);
+ Assert.Equal(0, device.CursorY);
Assert.Equal(0, device.Font);
Assert.Equal(PlasmaAttributes.None, device.Attributes);
}
@@ -189,7 +205,8 @@ public class VPlasmaDeviceTests
var device = new VPlasmaDevice();
Feed(device, (byte)'H', Esc, (byte)'L');
- Assert.Equal(new VPlasmaDevice.Point(0, 0), device.CursorCell);
+ Assert.Equal(0, device.CursorX);
+ Assert.Equal(0, device.CursorY);
Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 0, 0)); // glyph survives
}
@@ -206,42 +223,42 @@ public class VPlasmaDeviceTests
}
[Fact]
- public void EscK_SelectsFontGrids()
+ public void EscK_SelectsRealFonts()
{
var device = new VPlasmaDevice();
- Feed(device, Esc, (byte)'K', 4); // large: 12×16 cells
+ Feed(device, Esc, (byte)'K', 4); // large font: 12×16
Assert.Equal(4, device.Font);
- Assert.Equal(12, device.CellWidth);
- Assert.Equal(16, device.CellHeight);
+ Assert.Equal(12, device.FontWidth);
+ Assert.Equal(16, device.FontHeight);
- Feed(device, (byte)'A'); // large glyph: 'A' col 1 (0x11) row 0 → 2×2 dots at (2..3, 0..1)
- Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 2, 0));
- Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 3, 1));
-
- Feed(device, Esc, (byte)'K', 0xFF); // FF → default font 0
+ Feed(device, Esc, (byte)'K', 0); // back to font 0: 6×8
+ Assert.Equal(0, device.Font);
+ Assert.Equal(6, device.FontWidth);
+ Assert.Equal(8, device.FontHeight);
+
+ Feed(device, Esc, (byte)'K', 0xFF); // out of range: firmware ignores it
Assert.Equal(0, device.Font);
- Assert.Equal(6, device.CellWidth);
}
[Fact]
- public void EscH_AppliesAttributes()
+ public void EscH_AppliesAttributesAsLowFourBits()
{
var device = new VPlasmaDevice();
Feed(device, Esc, (byte)'H', 1, (byte)'H'); // half intensity
Assert.Equal(VPlasmaDevice.PixelLit | VPlasmaDevice.PixelHalf, Pixel(device, 0, 0));
- Feed(device, Esc, (byte)'H', 2, (byte)'H'); // underline: gap row lit
- Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 6, 7));
+ Feed(device, Esc, (byte)'L', Esc, (byte)'@'); // clear
+ Feed(device, Esc, (byte)'H', 2, (byte)'H'); // underline: bottom row (y=7) lit
+ Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 0, 7));
- Feed(device, Esc, (byte)'H', 3, (byte)' '); // reverse: a space renders solid
- Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 12, 0));
- Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 17, 7));
+ Feed(device, Esc, (byte)'@');
+ Feed(device, Esc, (byte)'H', 4, (byte)' '); // reverse: a space renders solid
+ Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 0, 0));
+ Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 5, 7));
- Feed(device, Esc, (byte)'H', 0xFF, (byte)'H'); // defaults restored
- Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 18, 0));
- Assert.Equal(0, Pixel(device, 18, 7));
+ Feed(device, Esc, (byte)'H', 0); // defaults restored
Assert.Equal(PlasmaAttributes.None, device.Attributes);
}
@@ -251,7 +268,7 @@ public class VPlasmaDeviceTests
public void UnknownEscape_IsConsumedAndTextResumes()
{
var device = new VPlasmaDevice();
- Feed(device, Esc, (byte)'Z', (byte)'H');
+ Feed(device, Esc, (byte)'\\', (byte)'H'); // ESC '\' is not a command
Assert.Equal(1, device.TextCharsDrawn);
Assert.Equal(VPlasmaDevice.PixelLit, Pixel(device, 0, 0));
@@ -260,11 +277,10 @@ public class VPlasmaDeviceTests
[Fact]
public void GameStartupSequence_HidesCursor()
{
- // L4PLASMA.CPP's constructor sends exactly ESC 'G' 0x00.
var device = new VPlasmaDevice();
Assert.Equal(PlasmaCursorMode.Steady, device.CursorMode); // power-on default
- Feed(device, 27, (byte)'G', 0x00);
+ Feed(device, 27, (byte)'G', 0x00); // L4PLASMA.CPP's boot byte
Assert.Equal(PlasmaCursorMode.Hidden, device.CursorMode);
}
@@ -302,6 +318,7 @@ public class VPlasmaDeviceTests
Assert.Equal(0, device.Font);
Assert.Equal(PlasmaAttributes.None, device.Attributes);
Assert.Equal(PlasmaCursorMode.Steady, device.CursorMode);
- Assert.Equal(new VPlasmaDevice.Point(0, 0), device.CursorCell);
+ Assert.Equal(0, device.CursorX);
+ Assert.Equal(0, device.CursorY);
}
}