diff --git a/emulator/PHASE2-PROGRESS.md b/emulator/PHASE2-PROGRESS.md index c080ebd..2c32674 100644 --- a/emulator/PHASE2-PROGRESS.md +++ b/emulator/PHASE2-PROGRESS.md @@ -1,5 +1,46 @@ # Phase 2 — Boot Protocol Emulation: Progress +**Status: boot handshake + i860 download + FIFO transport solved; blocked on one +unknown constant in `velocirender_sync`. Phase 5 (RIO) validated on real hardware.** + +## Update 2 (2026-07-02): FIFO transport working; RIO validated + +- **Phase 5 — RIO passthrough validated with real hardware.** A physical RIO + was attached to the host's COM1 (Prolific USB-serial). With + `serial1=directserial realport:COM1` (see `rio.conf`) and `L4CONTROLS=RIO`, + the game's 1996 RIO driver (`L4RIO.CPP`, 9600 baud) talks to the real board: + the `RIO never came back from check request?` error **disappears**, and the + **expected activity was observed on the RIO board**. The cockpit control path + works end-to-end, unmodified, through DOSBox-X serial passthrough. + +- **FIFO fast path decoded and implemented (send side).** The board is + dual-register: the render protocol switches to a FIFO channel after the i860 + boots. From `OUTSW.ASM` + the capture: each message writes an `outsw` tag byte + to `outputData`, then pumps the payload as 16-bit words to the FIFO data port, + which DOSBox-X sees as byte writes to **0x154 (low) / 0x155 (high)**. The + payload begins with the 4-byte action. The device now parses this and + extracts the action of each FIFO message (confirmed: `vr_init`=0 with the + `/device~…` args string, then `vr_create`=1 with node-type `0x2D`). + +- **Handler-specific reply actions (from board source).** `velocirender_receive` + reads replies on the **slow path** (already served); the device echoes the + sent action, with per-handler exceptions found in `VRENDER/VR_REMOT.C`: + `velocirender_init` and `velocirender_statistics` do `*data = 1`, so their + reply action is **1**, not the sent action. The device implements this map. + +- **Remaining blocker — one constant.** The game reaches `velocirender_sync`, + which sends `vr_init` (action 0) and checks the reply action against a + specific value that is **neither 0 (echo) nor 1 (the DPL3 board's `init` + return)** — so the Rel 4.10 board returns a different value than the DPL3 + dev-tree source. Determining it needs the Rel 4.10 LIBDPL/VRENDER source or a + disassembly of `BTL4OPT.EXE` around the format string at file offset + **0x107772** (`"unexpected action %d returned in velocirender_sync"`). The + device has an experiment hook (`VPX_INIT_REPLY=`) to try candidate values. + +Original Update 1 analysis (handshake + i860 download) follows. + +--- + **Status: substantial progress; boot handshake + i860 download solved; render transport (FIFO fast path) identified as the remaining sub-protocol.** diff --git a/emulator/rio.conf b/emulator/rio.conf new file mode 100644 index 0000000..424d473 --- /dev/null +++ b/emulator/rio.conf @@ -0,0 +1,30 @@ +[sdl] +output=opengl +[dosbox] +memsize=32 +machine=svga_s3 +[cpu] +core=normal +cputype=pentium +cycles=20000 +# Phase 5 VALIDATED 2026-07-02: with a real RIO on the host's COM1, this +# passthrough makes the game's 1996 RIO driver talk to the physical board — +# the "RIO never came back from check request?" error disappears and the +# expected activity is observed on the RIO board. +[serial] +serial1=directserial realport:COM1 +serial2=disabled +[autoexec] +mount c "image" +c: +set L4CONTROLS=RIO,KEYBOARD +set L4TIMER= +set BLASTER=A220 I5 D1 H5 P330 T6 +set VIDEOFORMAT=svga +set TEMP=c:\ +set DPLARG=/tranny~.\vrendmon.btl~/i860~.\vrnostex.mng~/device~0x150~/video~svga~/pipes~1~/qual~0x14~/system_tex~0~ +32rtm.exe -x +btl4opt.exe -egg test.egg +32rtm.exe -u +echo RIO-DONE +pause diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index 367a0a0..95bbae1 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -94,7 +94,7 @@ static void parse_out_byte(unsigned char v) { /* diagnostic: log small control frames (skip 512B i860 chunks) */ if (vpx_fp && wf_last_nb <= 64) { flush_run(); - fprintf(vpx_fp, "# FRAME action=%u nb=%d\n", wf_action, wf_last_nb); + fprintf(vpx_fp, "# FRAME action=%u nb=%d (slow)\n", wf_action, wf_last_nb); fflush(vpx_fp); } wf_payload_left = -1; @@ -102,6 +102,34 @@ static void parse_out_byte(unsigned char v) { } } +/* ---- FIFO fast-path action extraction ---------------------------------- * + * After the i860 boots the host switches to the FIFO path (OUTSW.ASM): each + * message writes an outsw tag byte to outputData, then pumps the payload as + * 16-bit words to the FIFO data port (observed at 0x154/0x155, low/high). The + * payload of a control message begins with the 4-byte action. We arm on each + * outputData write and capture the next 4 FIFO bytes; if they form a valid + * action code (< 32, the vr_action enum range) we treat it as the action to + * echo. Data runs (e.g. text/coords) yield large values and are ignored. */ +static bool fifo_arm = false; +static int fifo_cap_pos = 0; +static unsigned fifo_cap = 0; +static const unsigned VR_ACTION_MAX = 24; /* enum has 24 actions (0..23) */ + +static void fifo_arm_action(void) { fifo_arm = true; fifo_cap_pos = 0; fifo_cap = 0; } + +static void parse_fifo_byte(unsigned char v) { + if (!fifo_arm) return; + fifo_cap |= ((unsigned)v) << (fifo_cap_pos * 8); + if (++fifo_cap_pos == 4) { + fifo_arm = false; + if (vpx_fp) { flush_run(); + fprintf(vpx_fp, "# FIFOCAP 0x%08X%s\n", fifo_cap, + fifo_cap < VR_ACTION_MAX ? " (action)" : ""); + fflush(vpx_fp); } + if (fifo_cap < VR_ACTION_MAX) last_action = fifo_cap; + } +} + /* A well-formed iserver "version" request (VR_COMMS.C iserver_action case 42): * length_word = nb | (sender<<16) | 0x80000000, little-endian; payload = tag. * nb is padded even for iserver; use nb=2, payload {0x2a,0x00}. sender=0. */ @@ -209,11 +237,23 @@ static Bitu vpx_read(Bitu port, Bitu /*iolen*/) { /* game is reading -> expects a renderer reply * (first is the vr_init ack after i860 boot). */ if (postboot_acks < vpx_max_postboot_acks) { - queue_render_ack((unsigned char)(last_action & 0xff)); + /* Reply action is handler-specific (board side, + * VR_REMOT.C): most handlers preserve data[0] (echo + * the sent action), but a few overwrite it: + * vr_init_action(0) -> *data = 1 + * vr_statistics_action(15) -> *data = 1 + * everything else echoes. */ + unsigned reply = last_action; + if (last_action == 0 /*init*/ || + last_action == 15 /*statistics*/) reply = 1; + /* experiment hook: override init reply action */ + { const char *o = getenv("VPX_INIT_REPLY"); + if (o && last_action == 0) reply = (unsigned)atoi(o); } + queue_render_ack((unsigned char)(reply & 0xff)); postboot_acks++; if (vpx_fp) { flush_run(); - fprintf(vpx_fp, "# post-boot: echo action %u\n", - last_action); fflush(vpx_fp); } + fprintf(vpx_fp, "# post-boot: reply action %u (sent %u)\n", + reply, last_action); fflush(vpx_fp); } ret = 0x01; } } @@ -251,10 +291,14 @@ static void vpx_write(Bitu port, Bitu val, Bitu iolen) { postboot_acks = 0; in_len = in_pos = 0; parse_frames = false; wf_need_len = 4; wf_lenbuf = 0; wf_lenshift = 0; wf_payload_left = -1; wf_action_pos = 0; wf_action = 0; last_action = 0; + fifo_arm = false; fifo_cap_pos = 0; fifo_cap = 0; note("board reset"); } else if (off == 1) { saw_write = true; /* outputData: a download/response byte */ - if (parse_frames) parse_out_byte((unsigned char)val); + if (parse_frames) { parse_out_byte((unsigned char)val); fifo_arm_action(); } + } else if (off == 4 || off == 5) { + /* FIFO data port (link B): payload words, low/high bytes. */ + if (parse_frames) parse_fifo_byte((unsigned char)val); } }