VDB-NOTES: full board documentation (RE from driver + HLE; physical sample exists)
Comprehensive VWE Video Display/splitter Board writeup: what it is (dumb ISA splitter tapping the companion Cirrus Logic SVGA feature connector), the definitive I/O register map from L4SVGA16.ASM (Secondary 0x302 / Aux1 0x30A / Aux2 0x312, each +0 mask/+1 read/+2 write/+3 data, 6-bit DAC; clock divider 0x319 off / 0x31A on), the byte-lane split (low byte->Secondary, high byte->Aux1/Aux2 -> separate VGA heads -> octopus cable -> 5 mono MFDs + color radar), the driver's bitMask+channelEnable CLUT-build trick that packs multiple displays into one framebuffer, the pixel-mask flash + Secondary-only fade, our emulation, the OPEN RP decode problem, and a physical-sample verification checklist (port-decoder PAL, 3 RAMDACs, octopus pinout, Cirrus chip id). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# The VDB — VWE Video Display/Splitter Board
|
||||
|
||||
Reverse-engineered from the shared MUNGA_L4 driver (`CODE/RP/MUNGA_L4/
|
||||
L4SVGA16.ASM` + `L4VB16.CPP`/`.HPP`) and the pod emulator's HLE
|
||||
(`emulator/vpx-device/vpxlog.cpp`). **A physical VDB sample + its companion
|
||||
Cirrus Logic SVGA card exist** (operator, 2026-07-10) — see the hardware
|
||||
verification checklist at the end. This board is IDENTICAL between BattleTech
|
||||
and Red Planet (shared driver); only the game-side display layout differs.
|
||||
|
||||
## What it is
|
||||
|
||||
An ISA card that **taps the companion Cirrus Logic SVGA card's pixel output**
|
||||
off the VGA feature connector and fans that ONE framebuffer out to the six
|
||||
secondary cockpit displays (5 mono MFDs + 1 color radar) via three RAMDAC-like
|
||||
palette groups. It is a DUMB splitter: no framebuffer of its own, no
|
||||
compute — it re-clocks the SVGA pixel stream and routes byte-lanes through
|
||||
palette CLUTs to separate VGA heads. "Adam's port decoder design" (Adam G.,
|
||||
VWE hardware) per the driver comments.
|
||||
|
||||
The game renders cockpit gauges to the Cirrus SVGA in a **640x480x16bpp**
|
||||
mode (VBE mode 0x111; `L4GAUGE=640x480x16`). The Division/VPX card is separate
|
||||
(the out-the-window 3D). So the SVGA framebuffer the VDB taps = GAUGES ONLY.
|
||||
|
||||
## Register map (definitive, from L4SVGA16.ASM)
|
||||
|
||||
I/O 0x300-0x31A. Three palette groups, each a 6-bit VGA-DAC-style CLUT. The
|
||||
driver's port table (`L4VB16.CPP` ~3685) passes the group base as **base+2**:
|
||||
|
||||
```
|
||||
Group base(+2) mask read-addr write-addr data
|
||||
NativePalette 0x3C6 (the Cirrus's own VGA DAC — 0x3C6/0x3C8/0x3C9)
|
||||
SecondaryPalette 0x302 0x302 0x303 0x304 0x305
|
||||
AuxiliaryPal1 0x30A 0x30A 0x30B 0x30C 0x30D
|
||||
AuxiliaryPal2 0x312 0x312 0x313 0x314 0x315
|
||||
|
||||
Clock divider: 0x319 write = high-color divider OFF (VWE_HC_OFF)
|
||||
0x31A write = high-color divider ON (VWE_HC_ON) (any value)
|
||||
```
|
||||
|
||||
Per-group offsets (VGA-DAC layout): **+0 pixel-mask, +1 read-address,
|
||||
+2 write-address, +3 data** (auto-incrementing R,G,B triplets). DAC is
|
||||
**6-bit**: `SVGAWriteFullPalette` does `shr al,2` (8→6) on write,
|
||||
`SVGAReadFullPalette` does `shl al,2` (6→8) on read. Palette load =
|
||||
set write-addr to 0, then `rep outsb` 256*3 bytes. Mask write = one `out`
|
||||
to base+0. The game only WRITES the VDB (fire-and-forget, no status/ACK);
|
||||
reads return DAC read-back.
|
||||
|
||||
## How the split works (the dumb part)
|
||||
|
||||
**High-color clock divider (0x31A on):** in 16bpp mode each pixel is two
|
||||
bytes. The divider clocks the LOW byte and HIGH byte of each pixel into
|
||||
SEPARATE palette lanes → separate heads. So:
|
||||
- **LOW byte (bits 0-7) -> Secondary palette (0x302 group) -> one head**
|
||||
- **HIGH byte (bits 8-15) -> Aux1/Aux2 palettes -> the other heads**
|
||||
|
||||
Each head is a physical VGA output off the splitter; an "octopus" cable fans
|
||||
the (up to) three head signals to the six cockpit displays. Decode confirmed
|
||||
live for BT: radar = low byte via Secondary; the 5 mono MFDs = high byte via
|
||||
Aux1/Aux2 color channels. [[vdb-three-vga-head-decode]]
|
||||
|
||||
## How the game packs multiple displays into one framebuffer
|
||||
|
||||
The card is dumb, but the DRIVER is clever about loading the CLUTs so several
|
||||
displays share the same pixels. Each cockpit display (`L4GraphicsPort`) has:
|
||||
- **`bitMask`** — which framebuffer pixel BITS belong to this display.
|
||||
- **`channelEnable`** — which DAC color channel it drives: Red / Green / Blue
|
||||
/ AllChannels (+ ...TransparentZero variants that leave index 0 undefined).
|
||||
|
||||
`BuildSecondaryPalette` (low byte = `bitMask & 0xFF`) and
|
||||
`BuildAuxiliaryPalette` (high byte = `(bitMask>>8) & 0xFF`) walk the display's
|
||||
bits with a `BitWrangler` and write that display's color/ramp into its channel
|
||||
of the shared 256-entry CLUT. So N mono displays coexist in one byte-lane by
|
||||
owning disjoint bit groups and different color channels; the CLUT decodes each
|
||||
combination back to the right per-display brightness. `BuildAuxiliaryPalette`
|
||||
generates a linear brightness RAMP across the display's bit values (mono
|
||||
MFD = beam intensity); `BuildSecondaryPalette` copies source colors (color
|
||||
radar).
|
||||
|
||||
## Palette flash + fade (the pixel-mask cycling)
|
||||
|
||||
`SVGA16::UpdatePalette` (`L4VB16.CPP` ~4196): each palette can FLASH by
|
||||
cycling its **pixel-mask** through `mask[]` states at `flashRate` (this is the
|
||||
`0x302` mask writes seen cycling e.g. 0x3F/0x7F/0xBF/0xFF — a blink, NOT index
|
||||
decode). **Only the Secondary palette is allowed to FADE** (its RGB scaled
|
||||
over time). So the pixel-mask register is used as a per-display blink control,
|
||||
not (usually) as a decode gate.
|
||||
|
||||
## Our emulation (vpxlog.cpp)
|
||||
|
||||
`vdb_write`/`vdb_read` mirror the register map (`VDB_BASE=0x300`,
|
||||
`vdb_group_of`/`vdb_group_base`); `vdb_pal[0/1/2]` = Secondary/Aux1/Aux2.
|
||||
`pal_draw` reads the live Cirrus framebuffer (`vga.mem.linear` at
|
||||
`vga.config.real_start`, 640x480, stride 1280) and for each pixel looks up
|
||||
the low byte via pal0 and high byte via pal1/pal2 (one color channel per mono
|
||||
head, pentapus split). 6-bit DAC expanded to 8-bit. `VDB_PALDUMP=<prefix>`
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
**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).
|
||||
|
||||
## Physical-sample verification checklist (have the board + Cirrus card)
|
||||
|
||||
Photograph and record for the archive:
|
||||
- **Splitter board**: the port-decoder PAL/GAL (mark/part#) at 0x300-0x31A;
|
||||
the THREE RAMDAC chips (part#, are they real Bt476/Bt478-class or a Cirrus
|
||||
DAC?) and how their pixel-mask/addr/data pins map to 0x302/0x30A/0x312; the
|
||||
high-color-divider logic tied to 0x319/0x31A; the feature-connector tap.
|
||||
- **Octopus cable**: pinout — which of the 3 head outputs feeds which of the
|
||||
6 cockpit displays (confirms the pentapus win5-9 + radar mapping).
|
||||
- **Companion Cirrus Logic card**: exact chip (CL-GD54xx?) + BIOS, to pin the
|
||||
640x480x16 mode/pitch and the feature-connector timing the divider assumes.
|
||||
- Any onboard ROM/PAL dump (like the RIO EPROM) for full preservation.
|
||||
|
||||
Related: [[vdb-three-vga-head-decode]], [[tesla-cockpit-emulator-state]].
|
||||
Reference in New Issue
Block a user