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:
@@ -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
@@ -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 ->
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user