Emulator: VDB heads mirror TEXT mode -- boot scroll + suite menus, like the

real cockpit

The splitter passed the analog VGA signal through whenever the game's
packed mode wasn't driving it: a person in the cockpit watched the PC boot
sequence scroll across every secondary display, and the test suite's menus
were readable on the heads. pal_draw now rasterizes M_TEXT (80x25 through
the live font tables + attribute palette -> VGA DAC, 8-dot cells, centered
640x400) with the mono MFD windows showing single-wire green-phosphor
luminance.

Two DOSBox-X planar-layout lessons encoded in comments: text cells live in
linear_base as one latch DWORD per character address (char/attr = plane
0/1 bytes, cell stride 4<<addr_shift, lvida = vidstart<<2), and
font_tables index PLANAR vram too -- one font byte per dword, so the glyph
fetch is ((chr<<5)+line)*4. Plus a 2s textdiag line (row-0 decode) in the
vpx log for future text-path debugging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-17 07:48:04 -05:00
co-authored by Claude Fable 5
parent e02a346bb0
commit bce9a443b0
+72 -1
View File
@@ -1396,7 +1396,78 @@ static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) {
if (lin8) stride = (vga.draw.address_add >= (Bitu)W &&
vga.draw.address_add <= (Bitu)W * 4)
? vga.draw.address_add : (Bitu)W;
if (fb) {
if (fb && vga.mode == M_TEXT) {
/* Pass-through, TEXT mode: the real splitter mirrored the analog VGA
* signal always -- someone in the cockpit saw the PC BOOT SEQUENCE
* scroll across every secondary display, and the test suite's menus
* are only identifiable on the heads because of it. Rasterize the
* 80x25 text screen through the live font tables + attribute palette,
* 8-dot cells = 640x400 centered in the 480-line head. */
memset(img, 0, sizeof img);
const unsigned cols = 80, chh = 16;
const int chn0 = (g > 4) ? ((g <= 6) ? g - 5 : g - 7) : -1;
/* EGA/VGA text lives in linear_base as 32-bit planar latches: one
* DWORD per character address, char = plane-0 byte, attr = plane-1
* byte (see EGAVGA_TEXT_Combined_Draw_Line: lvida = vidstart<<2,
* cell skip = 4<<addr_shift). NOT the CGA 2-byte layout. */
const uint8_t *vram = vga.draw.linear_base;
const Bitu vidmask = vga.draw.linear_mask;
const Bitu cell_vs = (Bitu)1u << vga.config.addr_shift;
if (vpx_fp && g == 0) { /* decode diagnostic: row 0 as we read it */
static double last_txt = 0.0; double nowt = (double)GetTickCount();
if (nowt - last_txt > 2000.0) {
last_txt = nowt;
fprintf(vpx_fp, "# VDB textdiag addr_shift=%u addr_add=%u start=0x%X row0: ",
(unsigned)vga.config.addr_shift,
(unsigned)vga.draw.address_add,
(unsigned)vga.config.real_start);
for (unsigned c = 0; c < 40; c++) {
Bitu vs0 = vga.config.real_start + (Bitu)c * cell_vs;
unsigned ch0 = vram[(vs0 << 2) & vidmask];
fputc((ch0 >= 32 && ch0 < 127) ? (int)ch0 : '.', vpx_fp);
}
fprintf(vpx_fp, "\n"); fflush(vpx_fp);
}
}
for (unsigned r = 0; r < 25; r++) {
for (unsigned c = 0; c < cols; c++) {
Bitu vs = vga.config.real_start +
(Bitu)r * vga.draw.address_add + (Bitu)c * cell_vs;
Bitu addr = (vs << 2) & vidmask;
unsigned chr = vram[addr];
unsigned attr = vram[addr + 1];
unsigned fgi = vga.attr.palette[attr & 0xFu] & 0x3Fu;
unsigned bgi = vga.attr.palette[(attr >> 4) & 0x7u] & 0x3Fu;
/* font_tables point into PLANAR vram: one font byte per
* latch DWORD (plane-2 lane), hence the *4 stride -- same
* as the real renderer's ((chr<<5)+line)*4 */
const uint8_t *ftab = vga.draw.font_tables[(attr >> 3) & 1u];
for (unsigned ln = 0; ln < chh; ln++) {
int y = 40 + (int)(r * chh + ln);
if (y >= H) break;
unsigned char *d = &img[((size_t)y * W + c * 8u) * 3];
unsigned bits = ftab[((Bitu)chr * 32u + ln) * 4u];
for (unsigned b = 0; b < 8; b++, d += 3) {
unsigned di = (bits & (0x80u >> b)) ? fgi : bgi;
unsigned char r6 = (unsigned char)vga.dac.rgb[di].red;
unsigned char g6 = (unsigned char)vga.dac.rgb[di].green;
unsigned char b6 = (unsigned char)vga.dac.rgb[di].blue;
if (chn0 < 0) {
d[0] = (unsigned char)((r6 << 2) | (r6 >> 4));
d[1] = (unsigned char)((g6 << 2) | (g6 >> 4));
d[2] = (unsigned char)((b6 << 2) | (b6 >> 4));
} else {
unsigned char c6 = (chn0 == 0) ? r6 : (chn0 == 1) ? g6 : b6;
unsigned char v = (unsigned char)((c6 << 2) | (c6 >> 4));
d[0] = (unsigned char)(v >> 3);
d[1] = v;
d[2] = (unsigned char)(v >> 3);
}
}
}
}
}
} else 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
* RGB (6-bit VGA-DAC expanded to 8-bit). pal0 is dynamic; pal1/pal2