VDB heads: 4-head cockpit layout (VPX_COCKPIT=1), drop exploratory windows
- VPX_COCKPIT=1 places the four VGA heads as borderless windows:
Division main 800x600 @ 0,0; radar (win0) 640x480 @ 800,0; MFD heads
win3/win4 640x480 @ 1440,0 / 2080,0 (heads 3+4 are driver-spanned
into one 1280x480 canvas). VPX_MAIN / VPX_WIN<g> = x,y[,w,h]
overrides any window without a rebuild; default debug layout kept.
- Remove exploratory win1/win2 (bits 0-7 via static palettes); keep the
0/3/4 numbering so win<g>.bmp dump names stay stable.
- Sync commit copy with the build tree: per-head palette decode in
pal_draw() and device-side BMP capture (VPX_DUMPDIR).
- RIO-NOTES: document the PCSPAK DISABLE_AND_DIE patch (error-3 crash
on RIO resets); restore the terrain-shadow section heading it ate.
- Add gauge_rio{,_log}.conf launch configs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,29 @@ emulator receive latencies were fixed with new `directserial` options:
|
||||
|
||||
`game_rio.conf` uses `realport:COM1 rxpollus:100 rxburst:16`.
|
||||
|
||||
## The crash-to-desktop: PCSPAK's DISABLE_AND_DIE (patched)
|
||||
|
||||
The recurring hard fault (`Exception 0E`, write to `0xFFFFFFFF`-ish, e.g. at
|
||||
`BTL4OPT CODE+0x7D1D1` with `EAX=3`) is **deliberate**: the RIO serial packet
|
||||
driver (`CODE/RP/MUNGA_L4/PCSPAK.ASM`) was shipped built with
|
||||
`DIE_ON_ERROR equ 1`, which compiles a `DISABLE_AND_DIE <code>` debugging
|
||||
macro at 12 error sites — it retracts the UART IRQ, EOIs the PIC, and then
|
||||
"crashes loudly" by writing the error code to address `0xFFFFFFFF`.
|
||||
|
||||
Our crash is error **3** (`PCSPAK.ASM:1630`): a byte ≥ 0x80 found in the TX
|
||||
ring body (the protocol reserves high-bit bytes for commands). It gets hit
|
||||
via the ACK/NAK-interrupt → restart path — exactly what physical RIO resets
|
||||
and timeout storms exercise. On clean pod serial timing this never fired; on
|
||||
a USB-serial rig with resets it does.
|
||||
|
||||
The source's release configuration (`DIE_ON_ERROR equ 0`) makes the macro
|
||||
empty and the code **recovers** (the next instruction masks the byte with
|
||||
`and al,7Fh` and continues). We reproduce that intended behavior by patching
|
||||
all 12 die sequences (`50 52 BA FF FF FF FF B8 xx 00 00 00 89 02` → NOPs) in
|
||||
the working image's `BTL4OPT.EXE`. Original preserved as `BTL4OPT.EXE.orig`.
|
||||
Error-code map (from PCSPAK.ASM): 0/1/2 rx framing states, 3 tx body >0x7F,
|
||||
4/5 tx state.
|
||||
|
||||
## Crash-on-advance fixed: arena terrain shadows
|
||||
|
||||
With the RIO in sync the sim advances and the game crashed dereferencing the
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
[sdl]
|
||||
output=opengl
|
||||
[dosbox]
|
||||
memsize=32
|
||||
machine=svga_s3
|
||||
[cpu]
|
||||
core=dynamic
|
||||
cputype=pentium
|
||||
cycles=max
|
||||
[serial]
|
||||
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
|
||||
# few-ms ACK deadline is met; plasma COM2 later.
|
||||
serial1=directserial realport:COM1 rxpollus:100
|
||||
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 gauges (15MB heap + 640x480x16) AND the RIO -- active mission so pal0's
|
||||
rem palette-animation can be watched for selective flashing.
|
||||
set HEAPSIZE=15000000
|
||||
set L4GAUGE=640x480x16
|
||||
call setenv.bat r s n g
|
||||
32rtm.exe -x
|
||||
btl4opt.exe -egg test.egg
|
||||
32rtm.exe -u
|
||||
echo ALPHA1-RUN-DONE
|
||||
pause
|
||||
@@ -0,0 +1,27 @@
|
||||
[sdl]
|
||||
output=opengl
|
||||
[dosbox]
|
||||
memsize=32
|
||||
machine=svga_s3
|
||||
[cpu]
|
||||
core=dynamic
|
||||
cputype=pentium
|
||||
cycles=max
|
||||
[serial]
|
||||
serial1=directserial realport:COM1 rxpollus:100
|
||||
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:\
|
||||
set HEAPSIZE=15000000
|
||||
set L4GAUGE=640x480x16
|
||||
call setenv.bat r s n g
|
||||
32rtm.exe -x
|
||||
btl4opt.exe -egg test.egg > c:\riolog.txt
|
||||
32rtm.exe -u
|
||||
echo ALPHA1-RUN-DONE
|
||||
pause
|
||||
+127
-38
@@ -655,12 +655,47 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
SwapBuffers(dc);
|
||||
}
|
||||
|
||||
/* ---- VDB display windows: one 640x480 window per display -----------------
|
||||
* Renders the actual gauge framebuffer (DOSBox's 16bpp M_LIN16 video memory,
|
||||
* the encoded video stream the VDB splits) so each window shows "similar
|
||||
* output" to the DOSBox screen. (Palette-based per-display decode is TBD; for
|
||||
* now all three show the shared framebuffer.) */
|
||||
static void pal_draw(HDC dc, int g, int cw, int ch) {
|
||||
/* VPX_DUMPDIR: when a file "<dir>/DUMP" appears, each display window writes its
|
||||
* current RGB buffer to "<dir>/win<g>.bmp" (checked once per render tick). Lets
|
||||
* the host grab pixel-perfect captures without moving/raising live windows. */
|
||||
static char pal_dump_dir[512] = "";
|
||||
|
||||
static void write_bmp(const char *path, const unsigned char *rgb, int W, int H) {
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return;
|
||||
int rowsz = (W * 3 + 3) & ~3; /* 4-byte row padding */
|
||||
unsigned imgsz = (unsigned)rowsz * (unsigned)H;
|
||||
unsigned filesz = 54u + imgsz;
|
||||
unsigned char hdr[54]; memset(hdr, 0, sizeof hdr);
|
||||
hdr[0]='B'; hdr[1]='M';
|
||||
hdr[2]=(unsigned char)filesz; hdr[3]=(unsigned char)(filesz>>8);
|
||||
hdr[4]=(unsigned char)(filesz>>16); hdr[5]=(unsigned char)(filesz>>24);
|
||||
hdr[10]=54; hdr[14]=40;
|
||||
hdr[18]=(unsigned char)W; hdr[19]=(unsigned char)(W>>8);
|
||||
hdr[22]=(unsigned char)H; hdr[23]=(unsigned char)(H>>8);
|
||||
hdr[26]=1; hdr[28]=24;
|
||||
hdr[34]=(unsigned char)imgsz; hdr[35]=(unsigned char)(imgsz>>8);
|
||||
hdr[36]=(unsigned char)(imgsz>>16); hdr[37]=(unsigned char)(imgsz>>24);
|
||||
fwrite(hdr, 1, 54, f);
|
||||
static unsigned char row[640*3 + 4];
|
||||
const unsigned char pad[3] = {0,0,0};
|
||||
for (int y = H - 1; y >= 0; y--) { /* BMP bottom-up; img top-down */
|
||||
const unsigned char *s = &rgb[(size_t)y * W * 3];
|
||||
for (int x = 0; x < W; x++) { /* RGB -> BGR */
|
||||
row[x*3+0] = s[x*3+2]; row[x*3+1] = s[x*3+1]; row[x*3+2] = s[x*3+0];
|
||||
}
|
||||
fwrite(row, 1, (size_t)W * 3, f);
|
||||
if (rowsz > W*3) fwrite(pad, 1, (size_t)(rowsz - W*3), f);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* ---- VDB display windows: one 640x480 window per VGA head ----------------
|
||||
* Decodes the gauge framebuffer (DOSBox's 16bpp M_LIN16 video memory, the
|
||||
* encoded stream the VDB splits): win0 = bits 0-7 via pal0 = color radar;
|
||||
* win3/win4 = bits 8-15 via pal1/pal2 = the two MFD heads (mono MFDs ride
|
||||
* the individual R/G/B channel wires). g=1/2 (exploratory) no longer used. */
|
||||
static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) {
|
||||
const int W = 640, H = 480;
|
||||
static unsigned char img[640 * 480 * 3];
|
||||
const uint8_t *fb = vga.mem.linear;
|
||||
@@ -669,29 +704,33 @@ static void pal_draw(HDC dc, int g, int cw, int ch) {
|
||||
Bitu start = vga.config.real_start; /* visible page start (bytes) */
|
||||
Bitu stride = (Bitu)W * 2; /* 16bpp */
|
||||
if (fb) {
|
||||
/* window 0 = the color radar = bits 0-7 (3:3:2 RGB); windows g>=1 =
|
||||
* one single bit each, starting at bit 8 (g=1 -> bit 8 ... g=8 ->
|
||||
* bit 15), rendered white/black -- so the remaining bits can be
|
||||
* re-paired into their displays by eye. */
|
||||
unsigned bit = 7u + (unsigned)g; /* used for g>=1 */
|
||||
/* 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
|
||||
* are static. (g<=2 would decode bits 0-7 via pal g; only g=0 is
|
||||
* created now.) */
|
||||
for (int y = 0; y < H; y++) {
|
||||
Bitu ofs = start + (Bitu)y * stride;
|
||||
unsigned char *d = &img[(size_t)y * W * 3];
|
||||
for (int x = 0; x < W; x++, ofs += 2, d += 3) {
|
||||
uint16_t px = *(const uint16_t *)(fb + (ofs & mask));
|
||||
if (g == 0) {
|
||||
d[0] = (unsigned char)(( px & 0x7u) * 255u / 7u); /* R: bits 0-2 */
|
||||
d[1] = (unsigned char)(((px >> 3u) & 0x7u) * 255u / 7u); /* G: bits 3-5 */
|
||||
d[2] = (unsigned char)(((px >> 6u) & 0x3u) * 255u / 3u); /* B: bits 6-7 */
|
||||
} else {
|
||||
unsigned char v = ((px >> bit) & 1u) ? 255 : 0;
|
||||
d[0] = d[1] = d[2] = v;
|
||||
}
|
||||
unsigned idx; int pg;
|
||||
if (g <= 2) { idx = px & 0xFFu; pg = g; } /* bits 0-7 via pal0/1/2 */
|
||||
else { idx = (px >> 8u) & 0xFFu; pg = g - 2; } /* bits 8-15 via pal1/2 */
|
||||
const unsigned char *e = &vdb_pal[pg].ram[idx * 3u];
|
||||
d[0] = (unsigned char)((e[0] << 2) | (e[0] >> 4)); /* 6-bit DAC -> 8-bit R */
|
||||
d[1] = (unsigned char)((e[1] << 2) | (e[1] >> 4)); /* G */
|
||||
d[2] = (unsigned char)((e[2] << 2) | (e[2] >> 4)); /* B */
|
||||
}
|
||||
}
|
||||
} else {
|
||||
memset(img, 0, sizeof img);
|
||||
}
|
||||
if (dump && pal_dump_dir[0]) {
|
||||
char p[600];
|
||||
snprintf(p, sizeof p, "%s/win%d.bmp", pal_dump_dir, g);
|
||||
write_bmp(p, img, W, H);
|
||||
}
|
||||
glViewport(0, 0, cw, ch);
|
||||
glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); glDisable(GL_LIGHTING);
|
||||
@@ -709,10 +748,12 @@ static LRESULT CALLBACK rt_wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
/* Create a visible window with its own GL context (each window gets its own
|
||||
* so we can render several from one thread by wglMakeCurrent-ing each). */
|
||||
static bool make_gl_window(const char *title, int w, int h, int x, int y,
|
||||
bool borderless,
|
||||
HWND *out_wnd, HDC *out_dc, HGLRC *out_gl) {
|
||||
DWORD style = borderless ? WS_POPUP : WS_OVERLAPPEDWINDOW;
|
||||
RECT r = { 0, 0, w, h };
|
||||
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
HWND wnd = CreateWindowA("VPXGL", title, WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
AdjustWindowRect(&r, style, FALSE);
|
||||
HWND wnd = CreateWindowA("VPXGL", title, style | WS_VISIBLE,
|
||||
x, y, r.right - r.left, r.bottom - r.top, NULL, NULL,
|
||||
GetModuleHandleA(NULL), NULL);
|
||||
if (!wnd) return false;
|
||||
@@ -728,6 +769,16 @@ static bool make_gl_window(const char *title, int w, int h, int x, int y,
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Optional per-window geometry override: <name> = "x,y[,w,h]". */
|
||||
static void env_rect(const char *name, int *x, int *y, int *w, int *h) {
|
||||
const char *v = getenv(name);
|
||||
if (!v || !v[0]) return;
|
||||
int a, b, c, d;
|
||||
int n = sscanf(v, "%d,%d,%d,%d", &a, &b, &c, &d);
|
||||
if (n >= 2) { *x = a; *y = b; }
|
||||
if (n >= 4) { *w = c; *h = d; }
|
||||
}
|
||||
|
||||
static DWORD WINAPI rt_main(LPVOID) {
|
||||
WNDCLASSA wc; memset(&wc, 0, sizeof wc);
|
||||
wc.style = CS_OWNDC;
|
||||
@@ -737,23 +788,49 @@ static DWORD WINAPI rt_main(LPVOID) {
|
||||
wc.lpszClassName = "VPXGL";
|
||||
RegisterClassA(&wc);
|
||||
|
||||
HWND wnd; HDC dc; HGLRC gl;
|
||||
if (!make_gl_window("VPX VelociRender (emulated)", 832, 512, 40, 40,
|
||||
&wnd, &dc, &gl)) return 1;
|
||||
/* VPX_COCKPIT=1: borderless windows on the modernized cockpit's four VGA
|
||||
* heads -- main (Division) 800x600 @ 0,0; radar (win0) 640x480 @ 800,0;
|
||||
* the two MFD heads (win3/win4) 640x480 @ 1440,0 and 2080,0 (those two
|
||||
* outputs are driver-spanned into one 1280x480 canvas starting at
|
||||
* x=1440). VPX_MAIN / VPX_WIN<g> = "x,y[,w,h]" override any window. */
|
||||
const char *ck = getenv("VPX_COCKPIT");
|
||||
bool cockpit = (ck && ck[0] && ck[0] != '0');
|
||||
|
||||
/* window 0 = color radar (bits 0-7); windows 1-8 = single bits 8..15,
|
||||
* fanned out so the user can re-pair them into their displays. */
|
||||
const int NPAL = 9;
|
||||
static const char *pal_titles[9] = {
|
||||
"display 0 - radar (bits 0-7)",
|
||||
"bit 8", "bit 9", "bit 10", "bit 11",
|
||||
"bit 12", "bit 13", "bit 14", "bit 15" };
|
||||
HWND pwnd[9]; HDC pdc[9]; HGLRC pgl[9];
|
||||
bool phave[9];
|
||||
for (int g = 0; g < NPAL; g++)
|
||||
phave[g] = make_gl_window(pal_titles[g], 480, 360,
|
||||
700 + (g % 3) * 500, 20 + (g / 3) * 400,
|
||||
HWND wnd; HDC dc; HGLRC gl;
|
||||
int mx = 40, my = 40, mw = 832, mh = 512;
|
||||
if (cockpit) { mx = 0; my = 0; mw = 800; mh = 600; }
|
||||
env_rect("VPX_MAIN", &mx, &my, &mw, &mh);
|
||||
if (!make_gl_window("VPX VelociRender (emulated)", mw, mh, mx, my,
|
||||
cockpit, &wnd, &dc, &gl)) return 1;
|
||||
|
||||
/* Real VGA heads only: win0 = bits 0-7 via pal0 (color radar); win3/win4
|
||||
* = bits 8-15 via pal1/pal2 (the two MFD heads). The former exploratory
|
||||
* win1/win2 (bits 0-7 via the static palettes) are removed; g keeps the
|
||||
* original 0..4 numbering so pal_draw's bit/palette selection and the
|
||||
* win<g>.bmp dump names stay stable. */
|
||||
const int NPAL = 5;
|
||||
static const char *pal_titles[5] = {
|
||||
"radar (HEAD C) - bits 0-7 via pal0",
|
||||
"", "",
|
||||
"MFD head A, 2 lower (bits 8-15 via pal1)",
|
||||
"MFD head B, 3 upper (bits 8-15 via pal2)" };
|
||||
static const int ck_x[5] = { 800, 0, 0, 1440, 2080 };
|
||||
HWND pwnd[5]; HDC pdc[5]; HGLRC pgl[5];
|
||||
bool phave[5];
|
||||
int slot = 0; /* debug-grid position counter */
|
||||
for (int g = 0; g < NPAL; g++) {
|
||||
phave[g] = false;
|
||||
if (g == 1 || g == 2) continue; /* exploratory decodes, removed */
|
||||
int x, y, w, h;
|
||||
if (cockpit) { x = ck_x[g]; y = 0; w = 640; h = 480; }
|
||||
else { x = 700 + (slot % 3) * 500; y = 20 + (slot / 3) * 400;
|
||||
w = 480; h = 360; }
|
||||
slot++;
|
||||
char en[16]; snprintf(en, sizeof en, "VPX_WIN%d", g);
|
||||
env_rect(en, &x, &y, &w, &h);
|
||||
phave[g] = make_gl_window(pal_titles[g], w, h, x, y, cockpit,
|
||||
&pwnd[g], &pdc[g], &pgl[g]);
|
||||
}
|
||||
|
||||
VFrame cur;
|
||||
for (;;) {
|
||||
@@ -778,12 +855,18 @@ static DWORD WINAPI rt_main(LPVOID) {
|
||||
rt_frames++;
|
||||
}
|
||||
}
|
||||
/* redraw the display windows every tick (live) */
|
||||
/* redraw the display windows every tick (live); dump BMPs if triggered */
|
||||
bool dump = false;
|
||||
if (pal_dump_dir[0]) {
|
||||
char trig[600]; snprintf(trig, sizeof trig, "%s/DUMP", pal_dump_dir);
|
||||
FILE *tf = fopen(trig, "rb");
|
||||
if (tf) { fclose(tf); remove(trig); dump = true; }
|
||||
}
|
||||
for (int g = 0; g < NPAL; g++) {
|
||||
if (!phave[g]) continue;
|
||||
wglMakeCurrent(pdc[g], pgl[g]);
|
||||
RECT cr; GetClientRect(pwnd[g], &cr);
|
||||
pal_draw(pdc[g], g, cr.right, cr.bottom);
|
||||
pal_draw(pdc[g], g, cr.right, cr.bottom, dump);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1325,6 +1408,12 @@ void VPXLOG_Init(void) {
|
||||
if (fifo_dump_fp == NULL) LOG_MSG("VPXLOG: cannot open fifodump '%s'", fd);
|
||||
}
|
||||
|
||||
const char *dd = getenv("VPX_DUMPDIR");
|
||||
if (dd && dd[0]) {
|
||||
strncpy(pal_dump_dir, dd, sizeof pal_dump_dir - 1);
|
||||
pal_dump_dir[sizeof pal_dump_dir - 1] = '\0';
|
||||
}
|
||||
|
||||
const char *rn = getenv("VPX_RENDER");
|
||||
if (rn && rn[0] && rn[0] != '0') {
|
||||
vpx_render_start();
|
||||
|
||||
Reference in New Issue
Block a user