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 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-03 15:55:00 -05:00
co-authored by Claude Opus 4.8
parent 833fe85d63
commit 47fe174c6e
3 changed files with 157 additions and 0 deletions
+28
View File
@@ -64,6 +64,34 @@ the compiled SDK in this repository:
the conclusion: *"The problem disappeared when the same geometry was built the conclusion: *"The problem disappeared when the same geometry was built
without smoothing."* 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 0x3000x31A) 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 ## The shipped games, in runnable form
`BTLIVE/`, `BTRAVINE/`, `RPLIVE/`, and `RPDAVE/` are complete, runnable game `BTLIVE/`, `BTRAVINE/`, `RPLIVE/`, and `RPDAVE/` are complete, runnable game
+26
View File
@@ -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
+103
View File
@@ -716,6 +716,97 @@ static void scene_reset(void) {}
static void vpx_render_start(void) {} static void vpx_render_start(void) {}
#endif #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) { void VPXLOG_Init(void) {
const char *env = getenv("VPXLOG"); const char *env = getenv("VPXLOG");
if (env == NULL || env[0] == '\0') return; 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_RegisterReadHandler(VPX_BASE, vpx_read, IO_MB, 18);
IO_RegisterWriteHandler(VPX_BASE, vpx_write, 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) { if (vpx_fp) {
fprintf(vpx_fp, "# VPX link adapter, base 0x%03X, respond=%d handshakes=%d\n", fprintf(vpx_fp, "# VPX link adapter, base 0x%03X, respond=%d handshakes=%d\n",
VPX_BASE, vpx_respond ? 1 : 0, vpx_max_handshakes); VPX_BASE, vpx_respond ? 1 : 0, vpx_max_handshakes);