The cockpit's second serial device joins vRIO: the 128x32 dot-matrix plasma on COM2 (9600 8N1) that the game draws mission text and status graphics on. VPlasma.App opens the device end of a COM port, decodes the display's command stream, and renders the matrix in plasma orange. The command set is recovered from two Tesla 4.10 artifacts: the game's driver (L4PLASMA.CPP — ESC P packed-bitmap row writes, ESC G 0 cursor hide at boot) and the factory test tool PLASMA.EXE, whose data segment pairs each command literal with its printf description (BS/HT/LF/VT/CR motion, ESC @ clear, ESC L home, ESC G cursor, ESC K fonts, ESC H attributes: intensity/underline/reverse/flash). Text renders through the classic public-domain 5x7 set standing in for the lost ROM glyphs; fonts 0-3 give 21x4 cells, fonts 4-7 the doubled 10x2. A Self test cycles banner/charset/graphics pages through the same parser the wire feeds, and the wire log shows every decoded command. Verified end-to-end over the second com0com pair (host writing COM2, vPLASMA listening on COM12). The verify skill gains the cross-process combo-box lesson: string-carrying CB_* messages hang across processes, so select ports by locally computed index via CB_SETCURSEL. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
102 lines
3.6 KiB
C#
102 lines
3.6 KiB
C#
using VPlasma.Core.Protocol;
|
||
|
||
namespace VPlasma.Core.Device;
|
||
|
||
/// <summary>
|
||
/// Canned wire streams for the UI's <i>Self test</i> button: each page is a
|
||
/// byte sequence exactly as a host would send it over COM2, fed through the
|
||
/// same parser as live traffic — so the button doubles as an end-to-end
|
||
/// exercise of the command set without a host attached.
|
||
/// </summary>
|
||
public static class PlasmaSelfTest
|
||
{
|
||
public const int PageCount = 3;
|
||
|
||
/// <summary>The wire bytes for self-test page <paramref name="page"/> (0-based).</summary>
|
||
public static byte[] BuildPage(int page) => page switch
|
||
{
|
||
0 => BuildBanner(),
|
||
1 => BuildCharset(),
|
||
2 => BuildGraphics(),
|
||
_ => throw new ArgumentOutOfRangeException(nameof(page)),
|
||
};
|
||
|
||
private static void Esc(List<byte> b, byte cmd, params byte[] operands)
|
||
{
|
||
b.Add(PlasmaProtocol.Esc);
|
||
b.Add(cmd);
|
||
b.AddRange(operands);
|
||
}
|
||
|
||
private static void Text(List<byte> b, string s)
|
||
{
|
||
foreach (char c in s)
|
||
b.Add((byte)c);
|
||
}
|
||
|
||
/// <summary>Big-font banner over small-font attribute samples.</summary>
|
||
private static byte[] BuildBanner()
|
||
{
|
||
var b = new List<byte>();
|
||
Esc(b, PlasmaProtocol.CmdClearScreen);
|
||
Esc(b, PlasmaProtocol.CmdCursorMode, 0x00);
|
||
|
||
Esc(b, PlasmaProtocol.CmdFontSelect, 0x04); // 12×16 cells: 10 × 2
|
||
Text(b, " vPLASMA");
|
||
|
||
Esc(b, PlasmaProtocol.CmdFontSelect, 0x00); // 6×8 cells: 21 × 4
|
||
Esc(b, PlasmaProtocol.CmdHomeCursor);
|
||
b.Add(PlasmaProtocol.LineFeed);
|
||
b.Add(PlasmaProtocol.LineFeed);
|
||
Text(b, "128x32 PLASMA DISPLAY"); // exactly one 21-cell row
|
||
|
||
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, PlasmaProtocol.OperandDefault);
|
||
return b.ToArray();
|
||
}
|
||
|
||
/// <summary>The printable set 0x20..0x6F — a full 21×4 grid, minus a cell.</summary>
|
||
private static byte[] BuildCharset()
|
||
{
|
||
var b = new List<byte>();
|
||
Esc(b, PlasmaProtocol.CmdClearScreen);
|
||
Esc(b, PlasmaProtocol.CmdCursorMode, 0x00);
|
||
for (byte c = 0x20; c <= 0x6F; ++c)
|
||
b.Add(c);
|
||
return b.ToArray();
|
||
}
|
||
|
||
/// <summary>The rest of the charset plus an ESC P pattern block.</summary>
|
||
private static byte[] BuildGraphics()
|
||
{
|
||
var b = new List<byte>();
|
||
Esc(b, PlasmaProtocol.CmdClearScreen);
|
||
Esc(b, PlasmaProtocol.CmdCursorMode, 0x00);
|
||
for (byte c = 0x70; c <= 0x7E; ++c)
|
||
b.Add(c);
|
||
|
||
// A framed checkerboard sent the way the game sends everything:
|
||
// full-width ESC P rows (screen 0, xbyte 0, 16 bytes, 1 row each).
|
||
for (int y = 10; y < VPlasmaDevice.Height; ++y)
|
||
{
|
||
Esc(b, PlasmaProtocol.CmdGraphicsWrite,
|
||
0, (byte)y, 0, VPlasmaDevice.WidthBytes, 1);
|
||
bool edge = y is 10 or VPlasmaDevice.Height - 1;
|
||
for (int x = 0; x < VPlasmaDevice.WidthBytes; ++x)
|
||
{
|
||
byte fill = edge ? (byte)0xFF : (y & 2) == 0 ? (byte)0xAA : (byte)0x55;
|
||
if (!edge)
|
||
{
|
||
if (x == 0) fill |= 0x80; // left frame edge
|
||
if (x == VPlasmaDevice.WidthBytes - 1) fill |= 0x01; // right frame edge
|
||
}
|
||
b.Add(fill);
|
||
}
|
||
}
|
||
return b.ToArray();
|
||
}
|
||
}
|