From 47fe174c6e8432550285e9cda6c1e318a9ae80ea Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 3 Jul 2026 15:55:00 -0500 Subject: [PATCH] Add VDB (VWE LBE4 video splitter) device + fault disassembly findings The VDB is the ISA card that fans the PC's Cirrus Logic framebuffer to the six secondary cockpit displays (5 mono + 1 color), dividing the pixel clock. From the driver (CODE/RP/MUNGA_L4/L4SVGA16.ASM, L4VB16.CPP; "Adam's port decoder design" -- Adam G., VWE hardware): 0x300/0x308/0x310 three VGA-DAC-style palette groups 0x31A / 0x319 splitter high-color clock divider ON / OFF New emulated VDB device in vpxlog.cpp at I/O 0x300-0x31A (active when logging, disable with VDB=0): decodes palette write-address/data/mask/read-address and the clock strobes, recording palette contents + splitter state so the six- display encoding can be decoded later. Validated: with gauges enabled (gauge.conf, setenv arg4=g) the game issues "splitter clock ON (0x31A)" -> captured. The board is write-only (game never reads it, per driver + owner), so its absence is not the crash. Crash diagnosis (BTL4OPT.EXE CODE+0x123B): the faulting routine is a heap free() with boundary-tag coalescing; it was handed a garbage block pointer (value 2). Symptom of upstream corruption, reached only when the RIO is in sync and the sim advances. HISTORY.md gains a "Cockpit display hardware -- the VDB" section with the attribution. Co-Authored-By: Claude Opus 4.8 --- HISTORY.md | 28 +++++++++ emulator/gauge.conf | 26 +++++++++ emulator/vpx-device/vpxlog.cpp | 103 +++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 emulator/gauge.conf diff --git a/HISTORY.md b/HISTORY.md index 290a832..00a5491 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -64,6 +64,34 @@ the compiled SDK in this repository: the conclusion: *"The problem disappeared when the same geometry was built without smoothing."* +## Cockpit display hardware — the VDB (VWE video splitter board) + +Each cockpit drives **seven displays**. The **main** out-the-window view comes +straight off the Division VPX card's VGA output. The other six — **five +monochrome + one color** instrument/secondary displays — are fanned out by a +VWE-designed ISA card, the **VDB (Video Distribution Board)**, also called the +**"LBE4 video splitter board"** in the driver source. + +The VDB taps the PC's basic (Cirrus Logic) VGA output off the **feature +connector** via ribbon cable and divides that single 640×480×16 framebuffer to +the six monitors by dividing the pixel clock. The driver +(`CODE/RP/MUNGA_L4/L4SVGA16.ASM` and `L4VB16.CPP`, class `SVGA16`) shows its +ISA register map, credited in a source comment to **"Adam's port decoder +design"** — **Adam G.**, a VWE hardware engineer: + +- `0x300 / 0x308 / 0x310` — three palette register groups (VGA-DAC-style: + write-address / data / pixel-mask / read-address), one per display group so + the mono and color displays get their own color maps. +- `0x31A` — write enables the splitter's high-color clock divider + (`VWE_HC_ON`); `0x319` — write disables it (`VWE_HC_OFF`). + +The board is **write-only** — the game never reads it; it loads palettes and +flips the clock divider on with a single `out`, then renders the combined +framebuffer normally and the VDB splits it passively in hardware. An emulated +VDB device (I/O 0x300–0x31A) in the DOSBox-X fork records the palette contents +and clock state so this six-display encoding can be decoded later; see +`emulator/` and `emulator/RIO-NOTES.md`. + ## The shipped games, in runnable form `BTLIVE/`, `BTRAVINE/`, `RPLIVE/`, and `RPDAVE/` are complete, runnable game diff --git a/emulator/gauge.conf b/emulator/gauge.conf new file mode 100644 index 0000000..bd9559d --- /dev/null +++ b/emulator/gauge.conf @@ -0,0 +1,26 @@ +[sdl] +output=opengl +[dosbox] +memsize=32 +machine=svga_s3 +[cpu] +core=dynamic +cputype=pentium +cycles=max +[serial] +serial1=disabled +serial2=disabled +[autoexec] +mount c "C:\VWE\TeslaRel410\ALPHA_1" +c: +cd \REL410\BT +set VIDEOFORMAT=svga +set BLASTER=A220 I5 D1 H5 P330 T6 +set TEMP=c:\ +rem arg4=g enables the gauge/secondary displays -> exercises the VDB splitter +call setenv.bat r s n g +32rtm.exe -x +btl4opt.exe -egg test.egg +32rtm.exe -u +echo ALPHA1-RUN-DONE +pause diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index 2f41369..4cec028 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -716,6 +716,97 @@ static void scene_reset(void) {} static void vpx_render_start(void) {} #endif +/* ================= VDB: VWE LBE4 video splitter board ==================== * + * ISA card (I/O 0x300-0x31A) that taps the PC's Cirrus Logic VGA output off + * the feature connector and fans the single framebuffer out to the six + * secondary cockpit displays (5 mono + 1 color), dividing the pixel clock. + * Register map from the driver (CODE/RP/MUNGA_L4/L4SVGA16.ASM + L4VB16.CPP; + * "Adam's port decoder design" -- Adam G., VWE hardware): + * + * 0x300 / 0x308 / 0x310 three palette register groups, each VGA-DAC-like: + * +0 write-address +1 data (R,G,B triplets) +2 pixel-mask +3 read-address + * 0x319 write => splitter high-color clock divider OFF (VWE_HC_OFF) + * 0x31A write => splitter high-color clock divider ON (VWE_HC_ON) + * + * The game only WRITES to the VDB (fire-and-forget, no status/ACK -- confirmed + * from the driver and by the hardware owner). This device records the palette + * contents + clock-divider state so the six-display encoding can be decoded + * later; reads return 0xFF. Active when VPXLOG is set; logs to the same file. */ +static const io_port_t VDB_BASE = 0x300; + +struct VDBPalette { unsigned char waddr, raddr, sub, mask; unsigned char ram[768]; }; +static VDBPalette vdb_pal[3]; +static bool vdb_splitter_on = false; +/* lazy coalescing of palette data-byte writes so a 768-byte load is one line */ +static int vdb_data_group = -1; +static unsigned long vdb_data_count = 0; + +static int vdb_group_of(io_port_t off) { /* group index for 0x300/0x308/0x310 */ + if (off <= 3) return 0; + if (off >= 8 && off <= 11) return 1; + if (off >= 16 && off <= 19) return 2; + return -1; +} +static void vdb_flush_data(void) { + if (vpx_fp && vdb_data_group >= 0 && vdb_data_count) { + flush_run(); + fprintf(vpx_fp, "# VDB pal%d loaded %lu data bytes\n", + vdb_data_group, vdb_data_count); + fflush(vpx_fp); + } + vdb_data_group = -1; vdb_data_count = 0; +} +static void vdb_note(const char *msg) { + if (vpx_fp) { vdb_flush_data(); flush_run(); + fprintf(vpx_fp, "# VDB %s\n", msg); fflush(vpx_fp); } +} + +static void vdb_write(Bitu port, Bitu val, Bitu /*iolen*/) { + io_port_t off = (io_port_t)port - VDB_BASE; + unsigned char v = (unsigned char)val; + if (port == 0x319) { vdb_splitter_on = false; vdb_note("splitter clock OFF (0x319)"); return; } + if (port == 0x31A) { vdb_splitter_on = true; vdb_note("splitter clock ON (0x31A)"); return; } + int g = vdb_group_of(off); + if (g < 0) return; + VDBPalette &p = vdb_pal[g]; + switch (off & 3) { + case 0: /* write-address */ + vdb_flush_data(); + p.waddr = v; p.sub = 0; + if (vpx_fp) { flush_run(); + fprintf(vpx_fp, "# VDB pal%d write-addr=%u\n", g, v); fflush(vpx_fp); } + break; + case 1: /* data: R,G,B triplet with auto-increment */ + p.ram[((unsigned)p.waddr * 3 + p.sub) % 768] = v; + if (++p.sub == 3) { p.sub = 0; p.waddr++; } + if (vdb_data_group != g) { vdb_flush_data(); vdb_data_group = g; } + vdb_data_count++; + break; + case 2: /* pixel-mask */ + p.mask = v; vdb_note("pixel-mask set"); + break; + case 3: /* read-address */ + p.raddr = v; p.sub = 0; vdb_note("read-addr set"); + break; + } +} +static Bitu vdb_read(Bitu port, Bitu /*iolen*/) { + /* the game does not read the VDB; provide DAC-style read-back anyway. */ + io_port_t off = (io_port_t)port - VDB_BASE; + int g = vdb_group_of(off); + if (g >= 0 && (off & 3) == 1) { + VDBPalette &p = vdb_pal[g]; + unsigned char v = p.ram[((unsigned)p.raddr * 3 + p.sub) % 768]; + if (++p.sub == 3) { p.sub = 0; p.raddr++; } + return v; + } + return 0xFF; +} +static void vdb_reset(void) { + memset(vdb_pal, 0, sizeof vdb_pal); + vdb_splitter_on = false; vdb_data_group = -1; vdb_data_count = 0; +} + void VPXLOG_Init(void) { const char *env = getenv("VPXLOG"); if (env == NULL || env[0] == '\0') return; @@ -748,6 +839,18 @@ void VPXLOG_Init(void) { IO_RegisterReadHandler(VPX_BASE, vpx_read, IO_MB, 18); IO_RegisterWriteHandler(VPX_BASE, vpx_write, IO_MB, 18); + /* VDB video splitter board (0x300-0x31A). Registered whenever we are + * logging, unless VDB=0. Records palette + splitter-clock state. */ + const char *vdbenv = getenv("VDB"); + if (!(vdbenv && vdbenv[0] == '0')) { + vdb_reset(); + IO_RegisterReadHandler(VDB_BASE, vdb_read, IO_MB, 0x1B); /* 0x300-0x31A */ + IO_RegisterWriteHandler(VDB_BASE, vdb_write, IO_MB, 0x1B); + if (vpx_fp) { fprintf(vpx_fp, + "# VDB video splitter board at 0x%03X-0x31A (palettes 300/308/310, clock 319/31A)\n", + VDB_BASE); fflush(vpx_fp); } + } + if (vpx_fp) { fprintf(vpx_fp, "# VPX link adapter, base 0x%03X, respond=%d handshakes=%d\n", VPX_BASE, vpx_respond ? 1 : 0, vpx_max_handshakes);