emulator: in-fork virtual RIO cockpit board (serial1=rio)
Emulate the cockpit RIO board natively inside the DOSBox-X fork, driven by
an SDL game controller + host keyboard -- no external vRIO process and no
named pipe. The game<->board round trip runs in-process at the emulated
UART's own cadence, so the serial round-trip dropouts (livelock, TXMAXIDLE,
rxburst, 15s retry) can't occur.
- serialrio.{cpp,h}: RIO 9600-8N1 device state machine + protocol codec +
input mapping, transcribed from the vRIO app (VRioDevice + Protocol +
InputRouter/BindingProfileFormat), minus transport/pacer/UI/locks.
B0 = gamepad (5 axes + joystick column). B1 = keyboard field: MFD banks on
the letter rows, F-keys = Secondary/Screen, numpad = flight controls,
LShift/LCtrl throttle slew; PAUSE/ScrollLock toggle panel<->DOS; vRIO-
grammar bindings file via bindings:/VWE_RIO_BINDINGS.
- vpx-device/README.md: device notes + apply steps (the DOSBox-X src tree is
git-ignored, so this dir is the tracked source of truth; the three sdlmain
keyboard-hook edits are documented there).
- net_{loop,rp}_rio.conf + deploy templates; render-bridge/
gauge_arena_rio_sound.conf (standalone -egg trim); pod-launch --rio
(mutually exclusive with --pipe).
Additive: real pods keep directserial realport:COM1; vRIO-over-pipe keeps
namedpipe pipe:vrio. Validated live 2026-07-22 (pad + keyboard field +
console networking) on the dist install.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
# DEPLOY TEMPLATE -- rendered to net_loop_rio.conf by deploy/configure.ps1 at install.
|
||||
# IN-FORK RIO TRIM: the RIO cockpit board is emulated NATIVELY inside DOSBox-X
|
||||
# (serial1=rio) and driven by an SDL game controller -- no vRIO process, no
|
||||
# pipe (launch: pod-launch <mode> --rio). Self-contained standalone/dev trim;
|
||||
# arcade cockpits with the REAL RIO/plasma on COM ports use the non-suffixed
|
||||
# confs, the vRIO-app dev rig uses the _pipe confs.
|
||||
# Tokens (filled by postinstall): @@ROOT@@ = package dir (C:\games\<folder>),
|
||||
# @@REALNIC@@ = this pod's NIC GUID fragment, @@MACADDR@@ = stable derived MAC.
|
||||
# BattleTech, GO.BAT-style netnub loop (pod stays connected + picks up missions).
|
||||
[sdl]
|
||||
output=opengl
|
||||
priority=highest,highest
|
||||
[dosbox]
|
||||
memsize=32
|
||||
machine=svga_s3
|
||||
[cpu]
|
||||
core=dynamic
|
||||
cputype=pentium
|
||||
cycles=max
|
||||
[ne2000]
|
||||
ne2000=true
|
||||
nicbase=340
|
||||
# IRQ 10, NOT 3: COM2 (plasma) owns IRQ 3 in-game.
|
||||
nicirq=10
|
||||
macaddr=@@MACADDR@@
|
||||
backend=pcap
|
||||
[ethernet, pcap]
|
||||
realnic=@@REALNIC@@
|
||||
[sblaster]
|
||||
sbtype=sb16
|
||||
sbbase=220
|
||||
irq=5
|
||||
dma=1
|
||||
hdma=5
|
||||
[mixer]
|
||||
rate=44100
|
||||
blocksize=1024
|
||||
prebuffer=60
|
||||
[joystick]
|
||||
# the in-fork RIO owns the game controller (SDL); keep the DOS gameport +
|
||||
# mapper auto-binding off so nothing else grabs the pad.
|
||||
joysticktype=none
|
||||
[serial]
|
||||
# in-fork virtual RIO board on COM1 (pad:<n> and inverty are optional args)
|
||||
serial1=rio
|
||||
serial2=namedpipe pipe:vplasma
|
||||
[autoexec]
|
||||
mount c "@@ROOT@@\ALPHA_1"
|
||||
mount d "@@ROOT@@\net-boot"
|
||||
d:
|
||||
echo === loading NE2000 packet-driver stack (pcap/bridge, port 340) ===
|
||||
d:\lsl
|
||||
d:\ne2000
|
||||
d:\odipkt
|
||||
c:
|
||||
cd \rel410\bt
|
||||
set VIDEOFORMAT=svga
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A240 I7 D3 H6 P300 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
set TEMP=c:\
|
||||
set HEAPSIZE=15000000
|
||||
set L4GAUGE=640x480x16
|
||||
call setenv.bat r f s p
|
||||
echo === entering GO.BAT-style netnub loop (waits for console missions) ===
|
||||
call d:\loop.bat
|
||||
@@ -0,0 +1,69 @@
|
||||
# DEPLOY TEMPLATE -- rendered to net_rp_rio.conf by deploy/configure.ps1 at install.
|
||||
# IN-FORK RIO TRIM: the RIO cockpit board is emulated NATIVELY inside DOSBox-X
|
||||
# (serial1=rio) and driven by an SDL game controller -- no vRIO process, no
|
||||
# pipe (launch: pod-launch <mode> --rio). Self-contained standalone/dev trim;
|
||||
# arcade cockpits with the REAL RIO/plasma on COM ports use the non-suffixed
|
||||
# confs, the vRIO-app dev rig uses the _pipe confs.
|
||||
# Tokens (filled by postinstall): @@ROOT@@ = package dir (C:\games\<folder>),
|
||||
# @@REALNIC@@ = this pod's NIC GUID fragment, @@MACADDR@@ = stable derived MAC.
|
||||
# Red Planet 4.10, networked pod boot (console pushes the mission egg over TCP).
|
||||
# Loops netnub (loop_rp.bat) so the pod stays connected + picks up missions;
|
||||
# runs until the supervisor (pod-launch) kills DOSBox. No single-shot in deploy.
|
||||
[sdl]
|
||||
output=opengl
|
||||
priority=highest,highest
|
||||
[dosbox]
|
||||
memsize=32
|
||||
machine=svga_s3
|
||||
[cpu]
|
||||
core=dynamic
|
||||
cputype=pentium
|
||||
cycles=max
|
||||
[ne2000]
|
||||
ne2000=true
|
||||
nicbase=340
|
||||
nicirq=10
|
||||
macaddr=@@MACADDR@@
|
||||
backend=pcap
|
||||
[ethernet, pcap]
|
||||
realnic=@@REALNIC@@
|
||||
[sblaster]
|
||||
sbtype=sb16
|
||||
sbbase=220
|
||||
irq=5
|
||||
dma=1
|
||||
hdma=5
|
||||
[mixer]
|
||||
rate=44100
|
||||
blocksize=1024
|
||||
prebuffer=60
|
||||
[joystick]
|
||||
# the in-fork RIO owns the game controller (SDL); keep the DOS gameport +
|
||||
# mapper auto-binding off so nothing else grabs the pad.
|
||||
joysticktype=none
|
||||
[serial]
|
||||
# in-fork virtual RIO board on COM1 (pad:<n> and inverty are optional args)
|
||||
serial1=rio
|
||||
serial2=namedpipe pipe:vplasma
|
||||
[autoexec]
|
||||
mount c "@@ROOT@@\ALPHA_1"
|
||||
mount d "@@ROOT@@\net-boot"
|
||||
d:
|
||||
echo === loading NE2000 packet-driver stack (pcap/bridge, port 340) ===
|
||||
d:\lsl
|
||||
d:\ne2000
|
||||
d:\odipkt
|
||||
c:
|
||||
cd \rel410\rp
|
||||
set VIDEOFORMAT=svga
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A240 I7 D3 H6 P300 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
set TEMP=c:\
|
||||
set HEAPSIZE=15000000
|
||||
set L4GAUGE=640x480x16
|
||||
call setenv.bat r f s p
|
||||
echo === entering netnub loop (waits for console missions; supervisor kills to stop) ===
|
||||
call d:\loop_rp.bat
|
||||
@@ -0,0 +1,65 @@
|
||||
# IN-FORK vRIO variant of net_loop_pipe.conf: the RIO cockpit board is emulated
|
||||
# natively inside DOSBox-X (serial1=rio) and driven by an SDL game controller --
|
||||
# no external vRIO process and no named pipe. Self-contained flyable cockpit.
|
||||
# Everything else is identical to net_loop_pipe.conf (see it for the network /
|
||||
# GO.BAT-loop rationale). The plasma readout stays on the vPLASMA pipe (optional;
|
||||
# an unconnected pipe is just discarded). Real pods keep net_loop.conf
|
||||
# (directserial realport:COM1); this file is dev / standalone only.
|
||||
[sdl]
|
||||
output=opengl
|
||||
priority=highest,highest
|
||||
[dosbox]
|
||||
memsize=32
|
||||
machine=svga_s3
|
||||
[cpu]
|
||||
core=dynamic
|
||||
cputype=pentium
|
||||
cycles=max
|
||||
[ne2000]
|
||||
ne2000=true
|
||||
nicbase=340
|
||||
# IRQ 10, NOT 3: COM2 (plasma) owns IRQ 3 in-game; see net_full.conf.
|
||||
nicirq=10
|
||||
backend=pcap
|
||||
[ethernet, pcap]
|
||||
realnic=F70BADD7A
|
||||
[sblaster]
|
||||
sbtype=sb16
|
||||
sbbase=220
|
||||
irq=5
|
||||
dma=1
|
||||
hdma=5
|
||||
[mixer]
|
||||
rate=44100
|
||||
blocksize=1024
|
||||
prebuffer=60
|
||||
[joystick]
|
||||
# the in-fork RIO owns the game controller (SDL); keep the DOS gameport +
|
||||
# mapper auto-binding off so nothing else grabs the pad.
|
||||
joysticktype=none
|
||||
[serial]
|
||||
# in-fork virtual RIO board on COM1 (pad:<n> and inverty are optional args)
|
||||
serial1=rio
|
||||
serial2=namedpipe pipe:vplasma
|
||||
[autoexec]
|
||||
mount c "C:\VWE\TeslaRel410\ALPHA_1"
|
||||
mount d "C:\VWE\TeslaRel410\emulator\net-boot"
|
||||
d:
|
||||
echo === loading NE2000 packet-driver stack (pcap/bridge, port 340) ===
|
||||
d:\lsl
|
||||
d:\ne2000
|
||||
d:\odipkt
|
||||
c:
|
||||
cd \rel410\bt
|
||||
set VIDEOFORMAT=svga
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A240 I7 D3 H6 P300 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
set TEMP=c:\
|
||||
set HEAPSIZE=15000000
|
||||
set L4GAUGE=640x480x16
|
||||
call setenv.bat r f s p
|
||||
echo === entering GO.BAT-style netnub loop (waits for console missions) ===
|
||||
call d:\loop.bat
|
||||
@@ -0,0 +1,68 @@
|
||||
# IN-FORK vRIO variant of net_rp_pipe.conf: the RIO cockpit board is emulated
|
||||
# natively inside DOSBox-X (serial1=rio) and driven by an SDL game controller --
|
||||
# no external vRIO process and no named pipe. Self-contained flyable cockpit.
|
||||
# Everything else is identical to net_rp_pipe.conf (see it for the RP boot
|
||||
# rationale). Plasma stays on the vPLASMA pipe (optional). Real pods keep
|
||||
# net_rp.conf (directserial realport:COM1); this file is dev / standalone only.
|
||||
[sdl]
|
||||
output=opengl
|
||||
priority=highest,highest
|
||||
[dosbox]
|
||||
memsize=32
|
||||
machine=svga_s3
|
||||
[cpu]
|
||||
core=dynamic
|
||||
cputype=pentium
|
||||
cycles=max
|
||||
[ne2000]
|
||||
ne2000=true
|
||||
nicbase=340
|
||||
nicirq=10
|
||||
backend=pcap
|
||||
[ethernet, pcap]
|
||||
realnic=F70BADD7A
|
||||
[sblaster]
|
||||
sbtype=sb16
|
||||
sbbase=220
|
||||
irq=5
|
||||
dma=1
|
||||
hdma=5
|
||||
[mixer]
|
||||
rate=44100
|
||||
blocksize=1024
|
||||
prebuffer=60
|
||||
[joystick]
|
||||
# the in-fork RIO owns the game controller (SDL); keep the DOS gameport +
|
||||
# mapper auto-binding off so nothing else grabs the pad.
|
||||
joysticktype=none
|
||||
[serial]
|
||||
# in-fork virtual RIO board on COM1 (pad:<n> and inverty are optional args)
|
||||
serial1=rio
|
||||
serial2=namedpipe pipe:vplasma
|
||||
[autoexec]
|
||||
mount c "C:\VWE\TeslaRel410\ALPHA_1"
|
||||
mount d "C:\VWE\TeslaRel410\emulator\net-boot"
|
||||
d:
|
||||
echo === loading NE2000 packet-driver stack (pcap/bridge, port 340) ===
|
||||
d:\lsl
|
||||
d:\ne2000
|
||||
d:\odipkt
|
||||
c:
|
||||
cd \rel410\rp
|
||||
set VIDEOFORMAT=svga
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A240 I7 D3 H6 P300 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
set TEMP=c:\
|
||||
set HEAPSIZE=15000000
|
||||
set L4GAUGE=640x480x16
|
||||
call setenv.bat r f s p
|
||||
echo === launching Red Planet via NetNub (RIO + sound; waits for console) ===
|
||||
32rtm.exe -x
|
||||
netnub -p -f rpl4opt > nn.log
|
||||
32rtm.exe -u
|
||||
echo === RP-NET-RUN-DONE ===
|
||||
pause
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace VwePod
|
||||
public bool NoBridge;
|
||||
public bool NoSound;
|
||||
public bool Pipe;
|
||||
public bool Rio;
|
||||
public bool Mipmap;
|
||||
public bool NoFocus;
|
||||
public bool DryRun;
|
||||
@@ -78,6 +79,7 @@ namespace VwePod
|
||||
case "--no-bridge": o.NoBridge = true; break;
|
||||
case "--no-sound": o.NoSound = true; break;
|
||||
case "--pipe": o.Pipe = true; break;
|
||||
case "--rio": o.Rio = true; break;
|
||||
case "--mipmap": o.Mipmap = true; break;
|
||||
case "--no-focus": o.NoFocus = true; break;
|
||||
case "--dry-run": o.DryRun = true; break;
|
||||
@@ -105,9 +107,14 @@ namespace VwePod
|
||||
?? throw new ArgumentException("dosbox-x.exe not found under root; pass --dosbox");
|
||||
o.DosBoxDir = Path.GetDirectoryName(o.DosBox);
|
||||
|
||||
if (o.Pipe && o.Rio)
|
||||
throw new ArgumentException("--pipe and --rio are mutually exclusive (both select a COM1 backend)");
|
||||
|
||||
// --pipe: the <mode>_pipe conf variant (serials as named pipes to
|
||||
// vRIO/vPLASMA -- the dev-rig trim; real cockpits keep COM ports).
|
||||
string confBase = o.Mode.ConfBase + (o.Pipe ? "_pipe" : "");
|
||||
// --rio: the <mode>_rio conf variant (RIO board emulated IN-FORK,
|
||||
// driven by an SDL game controller -- no vRIO process, no pipe).
|
||||
string confBase = o.Mode.ConfBase + (o.Rio ? "_rio" : o.Pipe ? "_pipe" : "");
|
||||
o.Conf = confArg ?? FirstExisting(Path.Combine(o.Root, confBase + ".conf"));
|
||||
if (o.Conf == null)
|
||||
{
|
||||
@@ -190,6 +197,10 @@ namespace VwePod
|
||||
--pipe use the <mode>_pipe conf: serial1/2 as named pipes to
|
||||
vRIO (pipe:vrio) + vPLASMA (pipe:vplasma). Dev-rig
|
||||
default trim; arcade cockpits keep real COM ports
|
||||
--rio use the <mode>_rio conf: the RIO cockpit board is
|
||||
emulated IN-FORK (serial1=rio) and driven by an SDL
|
||||
game controller -- no vRIO process, no pipe. Self-
|
||||
contained; dev/standalone only. Excludes --pipe
|
||||
--mipmap VRVIEW_MIPMAP=1 (RP floor shimmer fix; non-authentic)
|
||||
--no-focus skip the window layout/focus pass
|
||||
--dry-run resolve paths + print the launch plan, then exit");
|
||||
|
||||
@@ -71,6 +71,7 @@ see `../CAMERA-REVIEW-NOTES.md`. Default mode: `bt`.
|
||||
| `--no-bridge` | pod only, no GL render window |
|
||||
| `--no-focus` | skip the renderer-topmost / DOSBox-focus pass |
|
||||
| `--pipe` | use the `<mode>_pipe` conf: serials as named pipes to vRIO (`pipe:vrio`) + vPLASMA (`pipe:vplasma`), no com0com. **Dev-rig policy (2026-07-17): always `--pipe` on the dev machine** — vRIO dist ≥ `20260716-68e3d1f` speaks the pipe; the plain confs (real COM ports) are for cockpits with the physical RIO/plasma |
|
||||
| `--rio` | use the `<mode>_rio` conf: the RIO cockpit board is emulated **in-fork** (`serial1=rio`) and driven by an **SDL game controller + the host keyboard** inside DOSBox-X — no vRIO process, no pipe, no serial round-trip. Self-contained cockpit: pad = stick/throttle/pedals + trigger/hat/torso; keyboard = the 72-button MFD/Secondary/Screen field + internal keypad (vRIO's default layout; **PAUSE toggles** keyboard between panel and DOS; Ctrl/Alt chords always pass through; remap via a vRIO-grammar bindings file, conf `bindings:<path>` or env `VWE_RIO_BINDINGS`). See the fork's `serialrio.cpp`. Dev/standalone only; mutually exclusive with `--pipe`. Real pods still use the plain confs (physical RIO on COM1) |
|
||||
|
||||
### Window placement (per-rig; normally fixed, overridable per-launch)
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# IN-FORK RIO variant of gauge_arena_pipe_sound.conf: bare -egg arena1 run
|
||||
# (no netnub/console) with the RIO board emulated NATIVELY inside DOSBox-X
|
||||
# (serial1=rio) -- SDL game controller + host keyboard field (PAUSE toggles
|
||||
# panel/DOS). No vRIO process, no pipe. Everything else identical to the
|
||||
# pipe trim (see its comments for the rationale of each section).
|
||||
[sdl]
|
||||
output=opengl
|
||||
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
|
||||
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
|
||||
priority=higher,higher
|
||||
[dosbox]
|
||||
memsize=32
|
||||
machine=svga_s3
|
||||
[cpu]
|
||||
core=dynamic
|
||||
cputype=pentium
|
||||
cycles=max
|
||||
[sblaster]
|
||||
sbtype=sb16
|
||||
sbbase=220
|
||||
irq=5
|
||||
dma=1
|
||||
hdma=5
|
||||
[mixer]
|
||||
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
|
||||
# emulation-thread stalls (RIO retry recovery) don't audibly chop
|
||||
rate=44100
|
||||
blocksize=1024
|
||||
prebuffer=60
|
||||
[ne2000]
|
||||
# bare -egg run WITH the packet stack resident (as production AUTOEXEC did);
|
||||
# slirp = user-mode NAT, no LAN peers -- fine for a solo run.
|
||||
ne2000=true
|
||||
nicbase=340
|
||||
nicirq=3
|
||||
backend=slirp
|
||||
[joystick]
|
||||
# the in-fork RIO owns the game controller (SDL); keep the DOS gameport +
|
||||
# mapper auto-binding off so nothing else grabs the pad.
|
||||
joysticktype=none
|
||||
[serial]
|
||||
# in-fork virtual RIO board on COM1 (pad:<n> / inverty / bindings:<path>
|
||||
# optional); plasma display stays on the vPLASMA pipe (optional).
|
||||
serial1=rio
|
||||
serial2=namedpipe pipe:vplasma
|
||||
[autoexec]
|
||||
mount c "C:\VWE\TeslaRel410\ALPHA_1"
|
||||
mount d "C:\VWE\TeslaRel410\emulator\net-boot"
|
||||
d:
|
||||
echo === loading NE2000 packet-driver stack (as production AUTOEXEC did) ===
|
||||
d:\lsl
|
||||
d:\ne2000
|
||||
d:\odipkt
|
||||
c:
|
||||
cd \REL410\BT
|
||||
set VIDEOFORMAT=svga
|
||||
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
|
||||
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
|
||||
rem driver relies on; skipping it left the cards uninitialized (silent).
|
||||
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
|
||||
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
|
||||
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A240 I7 D3 H6 P300 T6
|
||||
c:\sb16\diagnose /s
|
||||
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||
set TEMP=c:\
|
||||
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day), RIO attached,
|
||||
rem bare -egg launch (no netnub) = the real-world test-egg setup.
|
||||
set HEAPSIZE=15000000
|
||||
set L4GAUGE=640x480x16
|
||||
call setenv.bat r f s p
|
||||
32rtm.exe -x
|
||||
rem stdout -> log (this optimized build still emits DEBUG_STREAM lines).
|
||||
btl4opt.exe -egg testarn.egg > c:\weaponlog.txt
|
||||
32rtm.exe -u
|
||||
echo ALPHA1-RUN-DONE
|
||||
pause
|
||||
|
||||
@@ -24,6 +24,42 @@ version control because the DOSBox-X source tree itself
|
||||
bits`); the full wire contract is in the header comment (pinned with the
|
||||
vRIO session). Supports the directserial `rxpollus`/`rxburst`/`rxdelay`
|
||||
low-latency knobs. Smoke-tested end-to-end 2026-07-12.
|
||||
- **`serialrio.cpp` / `serialrio.h`** — `serial<n>=rio` backend (2026-07-21,
|
||||
keyboard field 2026-07-22): the cockpit RIO board emulated **in-fork**. It
|
||||
speaks the device side of the RIO 9600-8N1 protocol straight to the game's
|
||||
UART (no external process, no pipe) and sources pilot input from an **SDL
|
||||
game controller + the host keyboard** inside the emulator. Because the
|
||||
game↔board round trip is in-process at the emulated UART's own cadence —
|
||||
like the real ISA UART on a pod — the serial round-trip dropouts (livelock,
|
||||
`TXMAXIDLE`, `rxburst`, 15s retry) can't occur. The protocol state machine +
|
||||
input mapping are transcribed from the validated vRIO app (`C:\VWE\vrio`
|
||||
VRio.Core: `VRioDevice`, the `Protocol` codec, `Input/InputRouter` +
|
||||
`BindingProfileFormat`); the transport, wire pacer, panel UI and thread
|
||||
locks are dropped (single emulator thread). RX byte pacing reuses
|
||||
directserial's state machine (same `rxpollus`/`rxburst` knobs), draining a
|
||||
queue the board itself fills.
|
||||
**Keyboard field (B1):** sdlmain offers every SDL key to `RIO_HostKeyEvent()`
|
||||
first; while capture is ON (default) bound keys press RIO addresses or drive
|
||||
axes and are swallowed; unbound keys and Ctrl/Alt/Gui chords pass through
|
||||
(host hotkeys keep working; modifier keys BOUND as inputs are exempt from
|
||||
the chord rule). Default field: number/QWERTY rows = upper MFD bank,
|
||||
home/bottom rows = lower bank, F1–F12 = Secondary/Screen, arrows+Space =
|
||||
hat+main, and keyboard FLIGHT controls (operator 2026-07-22): numpad
|
||||
8/2/4/6 = stick, 7/9 = pedals, LShift/LCtrl = throttle slew ±0.7/s,
|
||||
numpad-5 = throttle zero; the internal keypad is unbound (mission-review
|
||||
plumbing only). **PAUSE or SCROLL LOCK toggles capture** panel↔DOS
|
||||
(toggling off releases everything; needed at the netnub loop / DOS
|
||||
prompts); `togglekey:<sdl-name>` adds a third toggle key for keyboards
|
||||
lacking both. Focus loss releases held keys (`RIO_HostFocusLost` in
|
||||
`GFX_LosingFocus`). A bindings file in vRIO's grammar REPLACES the default
|
||||
profile (conf `bindings:<path>`, else env `VWE_RIO_BINDINGS`; key-axis
|
||||
modes `deflect|slew|rate|set`, sign may be a separate token; vRIO's .NET
|
||||
key names are aliased so a vRIO `bindings.txt` ports over).
|
||||
Conf: `serial1=rio [pad:<n>] [inverty] [bindings:<path>]
|
||||
[togglekey:<key>] [rxpollus:<us>] [rxburst:<n>]`. **Additive** — real pods keep `directserial realport:COM1`;
|
||||
vRIO-over-pipe keeps `namedpipe pipe:vrio`. Confs: `net_loop_rio.conf` /
|
||||
`net_rp_rio.conf`; `pod-launch --rio`. B0 (pad) LIVE-CONFIRMED 2026-07-22
|
||||
from the repacked dist; keyboard-field live test pending.
|
||||
|
||||
## Applying to a DOSBox-X source checkout
|
||||
|
||||
@@ -66,6 +102,36 @@ Tested against DOSBox-X `v2026.06.02`, MSYS2 mingw64.
|
||||
copy the pattern.
|
||||
- `src/src/dosbox.cpp`: add `"namedpipe"` to the `serials[]` allowed-values
|
||||
list (without this the config parser silently falls back to `dummy`).
|
||||
3c. The in-fork RIO serial backend (needs SDL2 — the whole-program link already
|
||||
pulls SDL2 in, and the vendored SDL2 include dir is on the global compile
|
||||
flags, so no per-subdir cflag edits):
|
||||
```
|
||||
cp emulator/vpx-device/serialrio.cpp emulator/src/src/hardware/serialport/
|
||||
cp emulator/vpx-device/serialrio.h emulator/src/src/hardware/serialport/
|
||||
```
|
||||
plus the same four stock edits as the namedpipe backend:
|
||||
- `src/src/hardware/serialport/Makefile.am`: append
|
||||
`serialrio.cpp serialrio.h` to `libserial_a_SOURCES` (same generated-
|
||||
Makefile caveat as step 2).
|
||||
- `src/include/serialport.h`: add `SERIAL_TYPE_RIO` to `SerialTypesE`
|
||||
(UNguarded — SDL is cross-platform, unlike the WIN32-only namedpipe;
|
||||
placed right after `SERIAL_TYPE_MOUSE`).
|
||||
- `src/src/hardware/serialport/serialport.cpp`: `#include "serialrio.h"`;
|
||||
add the `type=="rio"` case to BOTH dispatch switches and `"rio"` to the
|
||||
`serialTypes[]` string table (copy the `namedpipe` pattern; keep it
|
||||
unguarded).
|
||||
- `src/src/dosbox.cpp`: add `"rio"` to the `serials[]` allowed-values list.
|
||||
- `src/src/gui/sdlmain.cpp` (the B1 keyboard field, three small edits):
|
||||
(a) declare the hooks above `GFX_LosingFocus`:
|
||||
`bool RIO_HostKeyEvent(int sdl_scancode, bool pressed, bool repeat,
|
||||
unsigned int sdl_mods); void RIO_HostFocusLost(void);`
|
||||
(b) call `RIO_HostFocusLost();` inside `GFX_LosingFocus` right after
|
||||
`MAPPER_LosingFocus();`
|
||||
(c) in GFX_Events' SDL2 `case SDL_KEYDOWN: case SDL_KEYUP:` block, right
|
||||
after the SCREEN_GAMELINK early-out, offer the event to
|
||||
`RIO_HostKeyEvent((int)event.key.keysym.scancode, event.type ==
|
||||
SDL_KEYDOWN, event.key.repeat != 0, event.key.keysym.mod)` and `break;`
|
||||
when it returns true (consumed keys must never reach the mapper).
|
||||
4. Build:
|
||||
```
|
||||
cd emulator/src
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* VWE fork: in-fork virtual RIO cockpit board (serial1=rio).
|
||||
*
|
||||
* A native DOSBox-X serial backend that *is* the cockpit RIO board: it speaks
|
||||
* the device side of the RIO 9600-8N1 protocol to the game's UART, and sources
|
||||
* its pilot input from an SDL game controller + the host keyboard inside the
|
||||
* emulator. There is no external process and no pipe, so the game<->board
|
||||
* round trip happens entirely in-process at the emulated UART's own byte
|
||||
* cadence -- the same timing a real ISA UART sees on a physical pod, which
|
||||
* removes the whole class of serial / pipe round-trip-latency dropouts
|
||||
* (livelock, TXMAXIDLE, rxburst, 15s retry).
|
||||
*
|
||||
* The protocol state machine + input mapping are transcribed from the
|
||||
* validated vRIO app (C:\VWE\vrio VRio.Core: VRioDevice, the Protocol codec,
|
||||
* Input/InputRouter + BindingProfileFormat), minus its transport, wire pacer,
|
||||
* panel UI and thread locks -- CSerial callbacks, the SDL reads and the host
|
||||
* keyboard hook all run on the single emulator thread.
|
||||
*
|
||||
* Keyboard field (B1): sdlmain's SDL2 event loop offers every key to
|
||||
* RIO_HostKeyEvent() first. While capture is ON (default; PAUSE or
|
||||
* SCROLL LOCK toggles it -- plus an optional `togglekey:<name>`) a key bound
|
||||
* in the profile presses its RIO address (button 0x00-0x47 / keypad
|
||||
* 0x50-0x6F) or drives an axis, and is swallowed before the DOS keyboard
|
||||
* sees it; unbound keys and Ctrl/Alt/Gui chords pass through (bound modifier
|
||||
* keys are exempt from the chord rule), so DOSBox-X host hotkeys keep
|
||||
* working. Toggling capture OFF releases everything held and gives the whole
|
||||
* keyboard back to DOS (needed at the netnub loop / DOS prompts).
|
||||
*
|
||||
* Bindings: the built-in default profile = vRIO's pad + button field
|
||||
* (number/QWERTY rows = upper MFD bank, home/bottom rows = lower bank,
|
||||
* F1-F12 = Secondary/Screen columns, arrows+Space = hat+main) plus keyboard
|
||||
* FLIGHT controls (operator 2026-07-22): numpad 8/2/4/6 = stick, 7/9 =
|
||||
* pedals, LShift/LCtrl = throttle slew, numpad-5 = throttle zero; the
|
||||
* internal keypad is unbound (mission-review-only plumbing). A bindings file
|
||||
* in vRIO's grammar (`key <name> button <addr> [toggle]` / `key <name> axis
|
||||
* <axis> deflect|slew|rate|set <n>` / `pad <button> button <addr> [toggle]`
|
||||
* / `padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>]`, '#'
|
||||
* comments, sign may be a separate token) REPLACES the default: conf arg
|
||||
* `bindings:<path>`, else env VWE_RIO_BINDINGS. vRIO's .NET key names (D1,
|
||||
* NumPad0, OemMinus, ...) are aliased, so a vRIO bindings.txt ports over.
|
||||
*
|
||||
* This is an ADDITIVE dev / standalone backend. Real pods keep the physical
|
||||
* board on `serial1=directserial realport:COM1`; vRIO-over-pipe keeps
|
||||
* `serial1=namedpipe pipe:vrio`. Neither is touched by this file.
|
||||
*
|
||||
* Conf: serial1=rio [pad:<n>] [inverty] [bindings:<path>] [togglekey:<key>]
|
||||
* [rxpollus:<us>] [rxburst:<n>]
|
||||
* pad:<n> game-controller index to use (default: first present)
|
||||
* inverty send the joystick Y in the physical direction (up = +)
|
||||
* bindings:<p> bindings file (vRIO grammar; replaces the default)
|
||||
* togglekey:<k> extra capture-toggle key (SDL name, e.g. End, F24)
|
||||
* rx opts directserial semantics (board->game delivery pacing)
|
||||
*/
|
||||
|
||||
#ifndef DOSBOX_SERIALRIO_H
|
||||
#define DOSBOX_SERIALRIO_H
|
||||
|
||||
#include "dosbox.h"
|
||||
#include "serialport.h"
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// ---- host-side hooks (called from sdlmain's SDL2 event loop) ----------------
|
||||
// Both are cheap no-ops when no serial<n>=rio port is installed.
|
||||
// RIO_HostKeyEvent returns true when the key was consumed by the cockpit panel
|
||||
// (bound + captured, or the PAUSE capture toggle) -- the caller must then skip
|
||||
// its normal keyboard handling for that event.
|
||||
bool RIO_HostKeyEvent(int sdl_scancode, bool pressed, bool repeat, unsigned int sdl_mods);
|
||||
void RIO_HostFocusLost(void);
|
||||
|
||||
// ---- binding profile (transcribed from vRIO BindingProfile) -----------------
|
||||
// key-axis modes: DEFLECT holds the axis at value while the key is down and
|
||||
// springs back; RATE ("slew" in bindings files) walks the axis by value/second
|
||||
// and the position sticks; SET snaps the axis position to value on key press.
|
||||
enum { RIO_KAX_DEFLECT = 0, RIO_KAX_RATE = 1, RIO_KAX_SET = 2 };
|
||||
struct RioKeyButtonBind { int scancode; int addr; bool toggle; };
|
||||
struct RioKeyAxisBind { int scancode; int axis; int mode; float value; };
|
||||
struct RioPadButtonBind { uint16_t bit; int addr; bool toggle; };
|
||||
struct RioPadAxisBind { int src; int axis; bool invert; float dz; float rate; };
|
||||
|
||||
class CSerialRio : public CSerial {
|
||||
public:
|
||||
CSerialRio(Bitu id, CommandLine* cmd);
|
||||
virtual ~CSerialRio();
|
||||
|
||||
void updatePortConfig(uint16_t divider, uint8_t lcr);
|
||||
void updateMSR();
|
||||
void transmitByte(uint8_t val, bool first);
|
||||
void setBreak(bool value);
|
||||
void setRTSDTR(bool rts, bool dtr);
|
||||
void setRTS(bool val);
|
||||
void setDTR(bool val);
|
||||
void handleUpperEvent(uint16_t type);
|
||||
|
||||
// host keyboard (forwarded by the RIO_Host* free functions)
|
||||
bool hostKeyEvent(int sdl_scancode, bool pressed, bool repeat, unsigned int sdl_mods);
|
||||
void hostFocusLost();
|
||||
|
||||
private:
|
||||
// ---- board -> game byte delivery (UART RX pacing; mirrors directserial) --
|
||||
bool doReceive();
|
||||
std::deque<uint8_t> rxq; // bytes the board wants to send to the game
|
||||
float rx_poll_ms = 1.0f;
|
||||
float rx_burst_div = 1.0f;
|
||||
Bitu rx_retry = 0;
|
||||
Bitu rx_retry_max = 0;
|
||||
Bitu rx_state = 0;
|
||||
|
||||
// ---- game -> board protocol RX parser --------------------------------
|
||||
void feedByte(uint8_t ch);
|
||||
uint8_t parse_buf[16] = {};
|
||||
int parse_remaining = 0; // bytes still expected (0 = not in a packet)
|
||||
int parse_count = 0; // bytes stored for the current packet
|
||||
|
||||
// ---- device (board) state --------------------------------------------
|
||||
void onPacket(const uint8_t* body, int bodyLen, bool checksumValid);
|
||||
void onControl(uint8_t b);
|
||||
void applyReset(uint8_t target);
|
||||
void emit(const uint8_t* p, int n); // queue board->game bytes
|
||||
void emitControl(uint8_t c);
|
||||
void sendEvent(const uint8_t* p, int n); // remember (for NAK resend) + emit
|
||||
void pressAddress(int addr);
|
||||
void releaseAddress(int addr);
|
||||
void setAxis(int axis, int value);
|
||||
|
||||
int16_t axes[5] = {};
|
||||
uint8_t lamps[72] = {};
|
||||
std::vector<uint8_t> lastEventPacket; // last button/key/test event
|
||||
int resends = 0;
|
||||
bool analogMuted = false; // v4.2 wedge bug -- default OFF
|
||||
uint8_t verMajor = 4, verMinor = 2;
|
||||
bool invertY = false;
|
||||
long analogRequests = 0;
|
||||
bool dtr_state = false;
|
||||
|
||||
// ---- input profile + routing state ------------------------------------
|
||||
void loadDefaultProfile();
|
||||
bool loadBindingsFile(const std::string& path);
|
||||
void computeBoundModMask();
|
||||
void openPad();
|
||||
void pollInput();
|
||||
void incHold(int addr);
|
||||
void decHold(int addr);
|
||||
void toggleAddress(int addr);
|
||||
void releaseAllKeys();
|
||||
|
||||
std::vector<RioKeyButtonBind> keyButtons;
|
||||
std::vector<RioKeyAxisBind> keyAxes;
|
||||
std::vector<RioPadButtonBind> padButtons;
|
||||
std::vector<RioPadAxisBind> padAxes;
|
||||
|
||||
void* pad = nullptr; // SDL_GameController* (opaque here)
|
||||
int pad_index = -1; // requested index; -1 = first
|
||||
double next_pad_open_ms = 0.0;
|
||||
double last_tick_ms = 0.0;
|
||||
float rate_i[5] = {}; // rate-integrator per axis
|
||||
int last_sent[5] = {};
|
||||
bool last_sent_valid[5] = {};
|
||||
std::map<int,int> holdCounts; // held count per RIO address
|
||||
std::set<int> heldKeys; // scancodes routed to the panel
|
||||
std::set<int> toggled; // latched (toggle) addresses
|
||||
uint16_t prevPadButtons = 0;
|
||||
bool captureKeys = true; // toggle keys flip panel <-> DOS
|
||||
int toggleKeys[3] = { 0, 0, 0 }; // scancodes; defaults Pause+ScrollLock,
|
||||
// slot 2 = togglekey:<name> conf arg
|
||||
unsigned int boundModMask = 0; // modifier bits bound as inputs
|
||||
// (exempt from chord passthrough)
|
||||
|
||||
bool logv = false; // VWE_RIO_LOG stderr trace
|
||||
void logf(const char* fmt, ...);
|
||||
};
|
||||
|
||||
#endif // DOSBOX_SERIALRIO_H
|
||||
Reference in New Issue
Block a user