RP VDB SOLVED: gauge bank granularity 4KB->64KB (L4GAUGE.INI) -- full displays

RP's cockpit displays decoded near-empty (top-strip only, a copy off-screen at
VRAM 0x100000) while BT's were full. Root cause was NOT the VDB decode (byte
split/palettes/stride all correct, proven by BT) but an SVGA bank-switch
GRANULARITY mismatch: RP's REL410/RP/GAUGE/L4GAUGE.INI [640x480x16] used
granularityInKB=4 vs BT's 64 on the same STB Horizon+ (CL-GD5434). DOSBox-X's
CL-GD5434 bank emulation handles 64KB granularity (BT full) but mishandles the
4KB-granular paged gauge writes, scattering them off-screen.

FIX (config, no rebuild): RP L4GAUGE.INI granularityInKB 4->64 (matches BT,
same card). VERIFIED live: VRAM content scan went from 0x0+0x100000 (top-strip
+ off-screen) to the FULL framebuffer 0x0..0x90000 populated; radar +
upper-center MFD confirmed correct, rest under reference check.

Also commits the VDB diagnostic tooling that found it (vpxlog.cpp, all
default-off): VDB_SCAN (VRAM content locator -- decisive), VDB_PALDUMP,
pixel-mask value logging, VDB_REALSTRIDE + live mode/stride/start logging,
VDB_BASE read-base override, VDB_APPLYMASK DAC-mask honoring. Full writeup in
VDB-NOTES.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-09 16:57:12 -05:00
co-authored by Claude Fable 5
parent 1ceb0dc90f
commit 6a595cc565
3 changed files with 105 additions and 27 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ mode=273 ;0111h
width=640
height=480
sizeInKB=64
granularityInKB=4
granularityInKB=64
bytesPerLine=1280
pageFcnPtr=796182 ;C000:2616
special=0
+49 -24
View File
@@ -97,26 +97,35 @@ dumps each group's 768-byte CLUT; pixel-mask writes now log their value; masks
default 0xFF; `VDB_APPLYMASK=1` ANDs the index with the mask (correct DAC
emulation, default off = BT-identical).
## RP vs BT — the OPEN decode problem (2026-07-10)
## RP vs BT — the decode problem, SOLVED (2026-07-10)
Same board, same driver. Captured live for RP (VDB_PALDUMP): framebuffer
identical (mode 0x111, 640x480); **Secondary/pal0 loads ALL ZEROS** (BT drives
it as the dynamic color radar) -> our radar head is black; Aux1=green,
Aux2=RGB are driven; the RP MFD heads decode as SPARSE fragments in the top
strip. So under the shared mechanism, RP simply puts NO display in the low
byte (Secondary empty is legitimate), and its high-byte MFD content is not
landing where our BT-tuned head/channel split expects.
**ROOT CAUSE: an SVGA bank-switch GRANULARITY mismatch, NOT a VDB decode bug.**
RP's `REL410/RP/GAUGE/L4GAUGE.INI` `[640x480x16]` mode was byte-identical to
BT's EXCEPT `granularityInKB=4` (RP) vs `=64` (BT) -- same card (STB Horizon+
= CL-GD5434). The MUNGA gauge renderer (L4GREND.CPP reads L4GAUGE ->
L4GAUGE.INI -> SVGA16 ctor with that granularity) uses it to compute VESA
bank/page numbers for its paged writes down the gauge framebuffer. DOSBox-X's
CL-GD5434 bank emulation handles 64KB granularity correctly (BT gauges full)
but MISHANDLES the 4KB-granular paging, so RP's gauge writes scattered/landed
off-screen -- symptom: the visible page held only a top ~51-row strip while a
copy sat at VRAM 0x100000, and the heads decoded near-empty. Everything else
(low/high byte split, 3 palettes, stride, the whole VDB model) was ALREADY
CORRECT -- proven by BT and by the fix.
**Operator's steer: the card is dumb, so the fix is glaringly simple** — not
a driver-logic difference. Leading suspects to check next (empirically, like
the first BT pass): (a) the framebuffer PAGE/`real_start` — is RP double-
buffering so we read a stale/wrong SVGA page? (`SVGASetPage` exists); (b) the
device DUMP path stopped writing win*.bmp for RP (fires in pal_draw) — worth
finding why, it may reveal the head-render path isn't running the same;
(c) which bit groups/channels RP's displays actually claim (dump a BT vs RP
Aux palette side-by-side; the ramp/channel structure shows the bit layout);
(d) confirm the low/high byte order and the 640x480 vs a smaller gauge region
(top-strip content hints RP may draw gauges in fewer rows).
**FIX (config, no rebuild): set RP's L4GAUGE.INI `[640x480x16]`
`granularityInKB=64` to match BT.** Safe because it's the same card BT drives
at 64KB. VERIFIED live 2026-07-10: VRAM content scan went from `0x0 + 0x100000`
(top-strip + off-screen) to the FULL framebuffer `0x0..0x90000` all populated;
operator confirmed the radar + upper-center MFD render correctly (others being
checked vs reference). RP cockpit displays now decode like BT's.
The tooling that found it (all in vpxlog.cpp, default-off diagnostics, kept):
`VDB_PALDUMP` (dump the 3 CLUTs), pixel-mask value logging, `VDB_REALSTRIDE`
+ live scan_len/addr_add/real_start/mode logging, `VDB_SCAN` (VRAM
content-region locator -- THE decisive tool), `VDB_BASE` (read-base override),
`VDB_APPLYMASK` (honor the DAC pixel-mask). Note: proper long-term fix could
instead be DOSBox-X honoring 4KB CL-GD5434 bank granularity, but the INI match
is correct and sufficient.
## Physical hardware (documented from the operator's sample, 2026-07-10)
@@ -140,12 +149,28 @@ CONFIRMS the reverse-engineering below the chip level:
connector that fans out to the 5 monochrome MFDs** (= Aux1/0x30A R,G = 2
lower MFDs, Aux2/0x312 R,G,B = 3 upper MFDs; 5 mono wires total, one channel
unused). The DB25 fan-out is the "octopus/pentapus" cable.
- **Reset-tie component**: a part wired in PARALLEL with the PC's front-panel
RESET switch — either a remote-reboot path OR a means to force the VDB into
a known state when the PC is reset (so the CPLD/DACs re-init cleanly).
OPERATOR RESEARCHING. (Plausible: the CPLD needs a hard reset synchronized
with the host, since it has no software reset in the 0x300-0x31A map -- only
clock on/off; a reset tie guarantees the splitter isn't left mid-state.)
- **Host hard-reset circuit (remote reboot)**: an **HSSR-8060** — a 6-pin
opto-isolated solid-state relay (MOSFET output, Agilent/Broadcom) — with its
**output switch (pins 4 & 6) wired to a 2-pin header silk-screened
`RESET_OUT`**, which is in parallel with the PC's front-panel RESET switch.
So energizing the SSR's input LED = "pressing reset" = a hardware reboot of
the pod's host PC. **What drives the SSR input is still unknown (operator
researching).** This is almost certainly a REMOTE/AUTOMATED REBOOT path: an
arcade operator (or the ops console over the net) could hard-reset a wedged
pod without physically touching it. Candidate input drivers to trace: (a) the
MACH130 CPLD decoding a "reset" write in the 0x300-0x31A I/O space (a soft
command -> hard reset, e.g. a watchdog or console-commanded reboot); (b) a
discrete line from the network/console board; (c) a watchdog timer. NOTE for
the emulator: our HLE has no reset-out port yet; if the driver ever writes a
reset trigger it'd appear as an unhandled 0x300-0x31A write — worth watching
the vpxresp log for writes outside the known palette/clock offsets.
**RESOLVED (operator, 2026-07-10): the SSR INPUT is driven from a spare pair
on the RADAR display's 9-pin video cable.** So it is NOT software/CPLD-driven
— it's a COCKPIT-LOCAL reset button: the reset signal is carried UP the radar
cable's unused conductors from a button in the cockpit down to the SSR, which
closes RESET_OUT across the PC front-panel reset. Lets the operator hard-
reboot a wedged pod PC from the cockpit without opening the chassis. Pure
hardware; nothing for the emulator to model.
### Still worth capturing for the archive
- Photos of each chip + the board silkscreen; the DB25 pinout (which pin ->
+55 -2
View File
@@ -1334,7 +1334,56 @@ static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) {
Bitu mask = vga.draw.linear_mask ? vga.draw.linear_mask
: (vga.mem.memsize ? vga.mem.memsize - 1 : 0);
Bitu start = vga.config.real_start; /* visible page start (bytes) */
/* VDB_BASE=<hex>: override the read base. RP renders its cockpit displays
* to an OFF-SCREEN page (content found at 0x100000 by VDB_SCAN) while the
* visible page (real_start) is near-empty; the real VDB is fed from that
* page. Point us at it. (Proper fix = track the CL-GD5434 start register.) */
{
static const char *be = getenv("VDB_BASE");
if (be && *be) start = (Bitu)strtoul(be, NULL, 0);
}
/* VDB_REALSTRIDE=1: use the CRT's actual per-scanline byte advance instead
* of the hardcoded 640*2. RP may set a wider CL-GD5434 pitch or page-flip;
* BT uses a plain 1280 so this is a no-op there. Logged once/sec so we can
* see the real scan_len/addr_add/start and whether the page flips. */
static const bool real_stride = getenv("VDB_REALSTRIDE") != NULL;
Bitu stride = (Bitu)W * 2; /* 16bpp */
if (real_stride && vga.draw.address_add >= (Bitu)W*2
&& vga.draw.address_add <= (Bitu)W*8) {
stride = vga.draw.address_add;
}
if (vpx_fp && g == 0) {
static double last_log = 0.0; double nowt = (double)GetTickCount();
if (nowt - last_log > 1000.0) {
last_log = nowt;
fprintf(vpx_fp, "# VDB pal_draw: real_start=0x%X scan_len*2=%u "
"addr_add=%u using_stride=%u LIVE mode=%d w=%u h=%u memsize=%uK\n",
(unsigned)start, (unsigned)(vga.config.scan_len*2u),
(unsigned)vga.draw.address_add, (unsigned)stride,
(int)vga.mode, (unsigned)vga.draw.width,
(unsigned)vga.draw.height, (unsigned)(vga.mem.memsize/1024));
/* VDB_SCAN=1: find WHERE RP's display content actually lives in the
* Cirrus VRAM. The visible page (real_start) looks near-empty for RP
* -- so scan the whole framebuffer in 64K windows and report which
* hold significant non-black 16bpp pixels. */
if (fb && getenv("VDB_SCAN")) {
Bitu msz = vga.mem.memsize ? vga.mem.memsize : 0;
const Bitu WIN = 0x10000;
fprintf(vpx_fp, "# VRAM content scan (64K windows w/ >2%% lit):");
for (Bitu base = 0; base + WIN <= msz && base < 0x400000; base += WIN) {
unsigned lit = 0;
for (Bitu o = base; o < base + WIN; o += 2*64) { /* sample */
if (*(const uint16_t *)(fb + (o & mask))) lit++;
}
if (lit > (WIN/(2*64))/50) /* >2% of samples lit */
fprintf(vpx_fp, " 0x%X(%u%%)", (unsigned)base,
(unsigned)(lit*100/(WIN/(2*64))));
}
fprintf(vpx_fp, "\n");
}
fflush(vpx_fp);
}
}
if (fb) {
/* win0 = framebuffer LOW byte (bits 0-7) through pal0; win3/win4 =
* framebuffer HIGH byte (bits 8-15) through pal1/pal2. All as 8-bit
@@ -2518,10 +2567,14 @@ static void vdb_write(Bitu port, Bitu val, Bitu /*iolen*/) {
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)");
if (vpx_fp && CurMode) { flush_run();
fprintf(vpx_fp, "# VDB framebuffer mode: 0x%X type=%d %ux%u pitch=%u\n",
fprintf(vpx_fp, "# VDB framebuffer mode: 0x%X type=%d %ux%u pitch=%u"
" REAL scan_len*2=%u addr_add=%u real_start=0x%X\n",
(unsigned)CurMode->mode, (int)CurMode->type,
(unsigned)CurMode->swidth, (unsigned)CurMode->sheight,
(unsigned)CurMode->pitch); fflush(vpx_fp); }
(unsigned)CurMode->pitch,
(unsigned)(vga.config.scan_len * 2u),
(unsigned)vga.draw.address_add,
(unsigned)vga.config.real_start); fflush(vpx_fp); }
return; }
int g = vdb_group_of(off);
if (g < 0) return;