- diff — compare two frames/images/encodings (.bin ESC P / .png / .txt
bitmap=), each reduced to 128x32; reports differing dots and match %, writes
a red-on-mismatch image, exits 1 if they differ. Closes the differential-test
loop: diff a cropped panel/replica photo against the vPLASMA golden image.
- text / encode / decode — ported from TeslaSuite's three plasma tools (Plasma
Font Tool, Bitmap Decoder, Editor) via PlasmaBitmap.cs:
* text — render text in an auto-sized Windows font to a plasma bitmap.
* encode — an image → plasma bitmap.
* decode — the game's bitmap= hex encoding → a preview PNG.
Each bridges to an ESC P wire stream (and/or the bitmap= hex encoding), so
authored content replays straight to vPLASMA, the replica, or the real panel.
Verified: text "ALERT 12" → ESC P → vPLASMA renders it; the ESC P and hex
paths diff as byte-identical (504/504 lit, 100% match).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plasma display replica — Adafruit Matrix Portal S3 + HUB75
A hardware replacement for the failing Babcock PD01D221 cockpit plasma display (../README.md, ../FIRMWARE.md). A modern microcontroller reads the same serial command stream the game sends and renders it to a modern LED matrix — a drop-in from the host's point of view, with none of the plasma physics or high voltage.
The firmware's command parser and fonts are ported verbatim from vRIO's
vPLASMA emulator (src/VPlasma.Core), which is the reference oracle. Feed
the replica and vPLASMA the same byte stream and they produce the same frame.
Bill of materials
| Part | Notes |
|---|---|
| Adafruit Matrix Portal S3 | ESP32-S3 controller; plugs into the HUB75 header; native USB-C = the virtual COM port |
| 2 × Adafruit 64×32 RGB LED Matrix (P-pitch to taste; 1/16 scan) | chained → 128×32, the panel's native resolution |
| 5 V power supply, ≥ 4 A | the panels are the load; USB cannot power them |
| USB-C cable | data (and logic power) from the host PC |
An amber-leaning pitch and diffuser best mimic the neon-orange plasma; the
firmware already renders in orange (255,96,0 full / 110,40,0 half).
Wiring
- Chain the panels: panel A
OUT→ panel BIN, left-to-right, so the pair reads as one 128-wide canvas (x 0–63 = panel A, 64–127 = panel B). If the image comes out swapped, reverse the chain order. - Mount the Matrix Portal S3 onto panel A's
INHUB75 header. - Power: 5 V ≥ 4 A into the Matrix Portal's screw terminals; run the panels' power pigtails from the same 5 V. USB-C carries data (and powers the S3 logic) — do not rely on USB for panel current.
Build & flash
Arduino IDE (or arduino-cli):
- Boards Manager → install esp32 (Espressif). Select board "Adafruit
Matrix Portal S3". Set USB CDC On Boot: Enabled (so
Serialis the USB port the game opens). - Library Manager → install Adafruit Protomatter (pulls in Adafruit GFX / BusIO).
- Open
MatrixPortalPlasma/MatrixPortalPlasma.ino(keepPlasmaDisplay.*,plasma_fonts.h,demo_screens.hbeside it) and Upload.
The HUB75 pin arrays at the top of the .ino are Adafruit's published Matrix
Portal S3 values; if the panel garbles, verify them against your installed
Protomatter version.
Using it with the game
The Matrix Portal enumerates as a USB CDC COM port. In Windows Device Manager you can pin it to the COM number the host expects. Point the game's plasma output at it — under the DOSBox-X fork:
serial2 = directserial realport:COMx
Baud is cosmetic over USB CDC (the 9600 line-coding is accepted as a no-op),
so the display keeps up regardless. The link is one-way (the game writes, the
display listens), exactly as the cockpit drove the real panel.
No-host testing (onboard buttons)
- UP — toggle the built-in firmware demonstration (the real 10-screen
PLASMADOT demo,
demo_screens.h, extracted from the ROM). - DOWN — toggle the panel test: cycles diagnostic patterns (solid, border+grid, horizontal/vertical stripes, checkerboard) to expose dead dots and addressing faults — the multi-pattern sequence the real firmware runs.
- Power-on briefly lights every dot (confirms both panels), then clears.
Files
| File | |
|---|---|
MatrixPortalPlasma/MatrixPortalPlasma.ino |
sketch: Protomatter init, USB serial, render loop, buttons |
MatrixPortalPlasma/PlasmaDisplay.h/.cpp |
the PD01D221 parser + 128×32 framebuffer — a C++ port of VPlasmaDevice |
MatrixPortalPlasma/plasma_fonts.h |
the 8 real ROM fonts (PROGMEM), generated from tms27pc512.BIN |
MatrixPortalPlasma/demo_screens.h |
the 10 firmware demo screens (PROGMEM) |
Keeping it faithful — the differential test
PlasmaDisplay is a line-for-line port of VPlasmaDevice; keep the two in
sync (same commands, fonts, orientation/plot mapping). Validate the replica
by sending identical byte streams to the replica, to vPLASMA, and to the
real panel, then comparing the glass.
The VPlasma.Wire tool (tools/VPlasma.Wire, dotnet run --project tools/VPlasma.Wire -- …) drives this:
# 1) Build a repeatable stream (or capture a real one — see below).
VPlasma.Wire synth --kind demo --out demo.bin
# 2) The vPLASMA golden image (pixel-exact reference PNG).
VPlasma.Wire render --in demo.bin --out demo-golden.png --scale 8
# 3) Replay the SAME bytes to each target, and photograph the glass:
VPlasma.Wire replay --in demo.bin --port COM3 # the real panel (RS-232)
VPlasma.Wire replay --in demo.bin --port COM7 # the Matrix Portal (USB-CDC)
Compare the two photos against demo-golden.png. synth kinds: selftest,
demo, banner, charset. render takes --orient v for vertical.
Capture a real session to build a test corpus non-intrusively — insert the
tool in the path (point the game at COM12, tool tees on to the real display
on COM3):
VPlasma.Wire capture --port COM12 --out session.bin --tee COM3
Then render/replay session.bin like any other stream.
Auto-compare a photo (cropped to the glass) against the golden image, or two streams against each other — each is reduced to 128×32 and diffed:
VPlasma.Wire diff --a demo-golden.png --b panel-photo.png --out delta.png
# prints "differing dots=N/4096 match=XX%", writes a red-on-mismatch image,
# exits 1 if they differ. Inputs may be .bin (ESC P) / .png / .txt (bitmap=).
Authoring plasma content (from TeslaSuite's plasma tools)
Generate display content from text or images and push it over the wire:
# Text in a Windows font, auto-sized to the panel → preview PNG + ESC P + hex:
VPlasma.Wire text --text "ALERT 12" --font "Arial" --png a.png --bin a.bin --hex a.txt
VPlasma.Wire replay --in a.bin --port COM7 # show it on the replica
# An image → the ESC P wire stream (and/or the game's bitmap= encoding):
VPlasma.Wire encode --in logo.png --bin logo.bin --hex logo.txt
# A bitmap= encoding → a preview PNG (and/or ESC P):
VPlasma.Wire decode --in logo.txt --png logo.png
Still deferred (as in vPLASMA, documented in ../FIRMWARE.md): the 10
double-buffered pages (ESC I/ESC i are consumed but single-page) and the
vector-graphics primitives (ESC A–F).