emulator: in-fork plasma display (serial2=plasma) window in the explode layout

Bring the pod's 128x32 plasma readout in-fork, completing the self-contained
glass cockpit -- no vPLASMA process, no pipe.

- serialplasma.{cpp,h}: CSerialPlasma, a serial<n>=plasma backend that parses
  the game's ESC P graphics stream (L4PLASMA streams whole changed rows;
  receive-only, no replies) into a 128x32 1bpp framebuffer. Transcribed from
  vPLASMA's PlasmaProtocol + VPlasmaDevice (graphics path); the factory
  text-mode/fonts aren't used by the game and aren't rendered, but their escape
  operands are still consumed so the parser can't desync. Seam PLASMA_GetFrame()
  for the renderer.
- vpxlog.cpp: draw the framebuffer as an amber-on-black window in the explode
  layout, lazily created once a plasma port exists, parked centered under the
  lower-left MFD (VPX_PLASMA="x,y,w,h" overrides); WS_EX_NOACTIVATE so it can't
  steal DOSBox focus.
- Register SERIAL_TYPE_PLASMA (serialport.h/.cpp, dosbox.cpp -- documented in
  the vpx-device README step 3d, since those stock files live in the git-ignored
  src tree).
- Confs: serial2 in the _rio confs + deploy templates switches from
  namedpipe pipe:vplasma to the in-fork plasma.

Live-validated 2026-07-24. Real pods keep directserial realport:COM2; the
standalone vPLASMA app keeps namedpipe pipe:vplasma.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-24 12:48:07 -05:00
co-authored by Claude Opus 4.8
parent e6eba36713
commit 844c11f792
9 changed files with 313 additions and 5 deletions
+51
View File
@@ -1728,6 +1728,30 @@ static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) {
SwapBuffers(dc);
}
/* ---- in-fork plasma display (serial=plasma) --------------------------------
* Render serialplasma's 128x32 1bpp framebuffer (16 bytes/row, MSB=leftmost)
* as an amber-on-black window in the explode layout. Seam = PLASMA_GetFrame
* (returns false when no serial=plasma port -> no plasma window). */
extern bool PLASMA_GetFrame(uint8_t out[512]);
static void draw_plasma(HDC dc, int cw, int ch, const uint8_t *fb) {
static unsigned char img[128 * 32 * 3];
for (int y = 0; y < 32; y++)
for (int x = 0; x < 128; x++) {
int lit = (fb[y * 16 + (x >> 3)] >> (7 - (x & 7))) & 1;
unsigned char *d = &img[(y * 128 + x) * 3];
if (lit) { d[0] = 255; d[1] = 150; d[2] = 30; } /* amber plasma glow */
else { d[0] = 8; d[1] = 4; d[2] = 0; } /* near-black glass */
}
glViewport(0, 0, cw, ch);
glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); glDisable(GL_LIGHTING);
glPixelZoom((float)cw / 128.0f, -(float)ch / 32.0f); /* scale + flip y */
glRasterPos2f(-1.0f, 1.0f);
glDrawPixels(128, 32, GL_RGB, GL_UNSIGNED_BYTE, img);
SwapBuffers(dc);
}
/* ---- click-to-press: map a click in a head window to a RIO button ----------
* Mirrors the bezel geometry (draw_mfd_bezel / draw_radar_bezel): returns the
* RIO address under (mx,my) in client coords, or -1 for a non-button click.
@@ -1913,6 +1937,7 @@ static DWORD WINAPI rt_main(LPVOID) {
static const int ex_y[10] = { 560, 0,0,0,0, 560, 560, 20, 20, 20 };
HWND pwnd[10]; HDC pdc[10]; HGLRC pgl[10];
bool phave[10];
HWND pl_wnd = NULL; HDC pl_dc = NULL; HGLRC pl_gl = NULL; bool pl_have = false; /* plasma window */
int slot = 0; /* debug-grid position counter */
for (int g = 0; g < NWIN; g++) {
phave[g] = false;
@@ -1979,6 +2004,32 @@ static DWORD WINAPI rt_main(LPVOID) {
RECT cr; GetClientRect(pwnd[g], &cr);
pal_draw(pdc[g], g, cr.right, cr.bottom, dump);
}
/* in-fork plasma window (explode only): appears once a serial=plasma
* port exists; lazily created below the layout, then rendered live. */
if (explode) {
uint8_t pfb[512];
if (PLASMA_GetFrame(pfb)) {
if (!pl_have) {
int pw = 512, ph = 128, px, py;
if (phave[5]) { /* park centered just under the lower-left MFD (g5) */
RECT r; GetWindowRect(pwnd[5], &r);
px = r.left + ((r.right - r.left) - pw) / 2;
py = r.bottom + 6;
} else { px = 84; py = 1070; } /* explode-layout fallback */
env_rect("VPX_PLASMA", &px, &py, &pw, &ph);
pl_have = make_gl_window("Plasma display (128x32)", pw, ph, px, py,
false, &pl_wnd, &pl_dc, &pl_gl);
if (pl_have) /* don't steal foreground from DOSBox */
SetWindowLongPtrA(pl_wnd, GWL_EXSTYLE,
GetWindowLongPtrA(pl_wnd, GWL_EXSTYLE) | WS_EX_NOACTIVATE);
}
if (pl_have) {
wglMakeCurrent(pl_dc, pl_gl);
RECT cr; GetClientRect(pl_wnd, &cr);
draw_plasma(pl_dc, cr.right, cr.bottom, pfb);
}
}
}
/* explode mode: once the DOSBox main screen exists, park it
* centered under the Division window (one-shot; user can move it
* afterwards). */