From 3d94ac7158e0bec836873ca43d8c6520f4b38aaf Mon Sep 17 00:00:00 2001 From: Cyd Date: Thu, 13 Aug 2026 12:58:19 -0500 Subject: [PATCH] Linux is Proton's problem to solve, not ours to port to Cyd asked what it would take to run on Linux. The answer is filed rather than acted on: we are mid-playtest, and this is a decision for the end of it. Proton is the recommendation. The build is 32-bit fixed-function D3D9, which is the most-travelled path Proton has, and Valve keeps i386 alive in the Steam runtime containers specifically to carry it; Wine 10 fixed child-window Vulkan rendering, which is the one thing that would have stopped the cockpit dead, since the 3D scene presents into a child STATIC with GDI panes clipped over it. Deck Verified is achievable from a Windows-only build and is what comparable Win32 titles ship. The native port is scoped in the doc rather than dismissed, because the engine is more portable than it looks: RP is clean C++, MUNGA is ~90% clean, single-threaded, with one timing seam and one file seam already named. What stops it is mechanical and total - 1,800 of 1,844 include lines cite a filename in a case no case-sensitive filesystem will find - and then strategic: the wire format and .RES carry size_t under /Zp1, so a 64-bit build silently changes the protocol while a 32-bit one targets a runtime Valve is walking away from. Five things need doing to the Windows build either way, and they are all improvements to it on their own: the Dynamic Lighting mirror imports windowsapp.lib statically, so its careful try/catch degradation never runs under Wine - the image fails to load first; the plasma window and the exploded MFD view are extra top-level windows, which is what gamescope handles worst; the viewscreen relies on the stock STATIC proc returning HTTRANSPARENT for mouse fall-through rather than saying so; the 1080 canvas downscales 1.5x on the Deck, against a 9px legibility floor; and the front end is mouse-driven, which Deck Verified will not pass. Nothing here has been run on Linux. The first step when this resumes is an afternoon with a Linux box confirming the child-window Present, before anything is promised. Co-Authored-By: Claude Fable 5 --- README.md | 1 + docs/RP412-LINUX.md | 192 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 docs/RP412-LINUX.md diff --git a/README.md b/README.md index e825ea9..2186056 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,7 @@ mission. Full map with pad and keyboard diagrams: | [docs/RP412-ROADMAP.md](docs/RP412-ROADMAP.md) | The original plan and workstreams | | [docs/RP412-FRONTEND-DESIGN.md](docs/RP412-FRONTEND-DESIGN.md) | TeslaConsole analysis, the egg format, the console protocol, and the Steam mapping — with status notes as each layer landed | | [docs/STEAM-3-MACHINE-TEST.md](docs/STEAM-3-MACHINE-TEST.md) | Multiplayer test procedure, Steam Input notes, the abort key | +| [docs/RP412-LINUX.md](docs/RP412-LINUX.md) | Linux compatibility assessment — Proton path vs native port; parked until late playtesting | | [BUILD.md](BUILD.md) | Toolchain and build steps | Dev tooling: `tools/two-pod-test.ps1` races two pods on loopback, diff --git a/docs/RP412-LINUX.md b/docs/RP412-LINUX.md new file mode 100644 index 0000000..abde85d --- /dev/null +++ b/docs/RP412-LINUX.md @@ -0,0 +1,192 @@ +# RP 4.12 on Linux — compatibility assessment + +Requirements exploration for making RP412 playable on Linux (desktop and Steam +Deck). Status: assessment complete (2026-08-12), **parked until late +playtesting** — nothing has been run on Linux yet; the Proton test matrix in +§3 is the next concrete step when this resumes. A formatted copy of this +report lives at +. + +**Verdict: target Proton as the official Linux path; treat a native port as a +separately-scoped future project.** The existing 32-bit Win32/D3D9 build is +squarely on Proton's best-supported path, every Proton-blocking issue found is +fixable inside the Windows build, and Valve's Deck Verified program accepts +Windows-only titles. A native port is a genuine second platform layer +(~77k lines of `MUNGA_L4` plus ~6,300 lines of raw Win32 UI in `RP_L4`) and it +forces the 32/64-bit question, which reaches into the wire protocol and the +`.RES` format. Nothing on the Proton path is throwaway: each fix is an +improvement to the shipping Windows build. + +Provenance: code findings below are measured against this repo (four +exploration sweeps over `MUNGA`, `MUNGA_L4`, `RP`, `RP_L4`, `DivLoader` and +the build files, 2026-08-12, main @ 1e0c412). External claims — Proton/DXVK +behavior, Steam Runtime direction, Deck Verified criteria — are from Valve +partner docs, the DXVK source, the steam-runtime repository, and Wine release +notes as of August 2026, with unconfirmed items called out as needing live +tests. + +## 1. How the Windows dependency actually distributes + +The architecture is far more portable than the mechanics. The engine is +single-threaded by construction, has exactly one timing seam (`SystemClock`, +implemented in [MUNGA_L4/L4TIME.cpp](../MUNGA_L4/L4TIME.cpp)), one file-I/O +seam ([MUNGA/FILESTUB.cpp](../MUNGA/FILESTUB.cpp), six syscalls), a formal +network transport interface with all real socket calls in one ~350-line class +([MUNGA_L4/L4NETTRANSPORT.cpp](../MUNGA_L4/L4NETTRANSPORT.cpp)), and audio +already speaks OpenAL. But the mechanical surface is total: **1,800 of 1,844 +quoted `#include` lines cite a filename in a case that doesn't exist on disk, +and 334 use backslash separators** — on a case-sensitive filesystem, nothing +compiles until a repo-wide normalization pass is done. + +| Tree | Lines | Portability | +|------|-------|-------------| +| `RP\` | 16,441 | **Clean.** Zero Win32 API, zero Win32 types, zero threads. Only 16 `stricmp` and 4 `getenv`. | +| `MUNGA\` | 116,695 | **~90% clean** (267 of 297 files carry no Win32/MSVC token). The leaks are few and named: `PostQuitMessage` as the abort path (32 sites in 9 files; the portable prototype still sits commented out at `MUNGA/STYLE.H:31`), [MUNGA/MATRIX.h](../MUNGA/MATRIX.h) line 4 including `` (which transitively feeds `windows.h` to most of the engine), `HWND`/`SOCKADDR_IN` carried as opaque values in a handful of headers, and `SPOOLER`'s `CreateDirectoryA`/`CopyFileA`. | +| `RP_L4\` | 22,588 | **Mixed.** 20 of 34 files clean. The rest is `WinMain`/`WndProc` ([RP_L4/RPL4.CPP](../RP_L4/RPL4.CPP)) plus ~6,300 lines of hand-written Win32+GDI UI (front end, lobby, console board) behind no platform interface. | +| `MUNGA_L4\` | 76,575 | **The platform layer.** 41 of 100 files touch D3D9 / DirectInput / XInput / Winsock / serial / WinRT / Steam. This is what a native port rewrites. | +| `DivLoader\` | 1,714 | **No callers.** Entirely non-portable (D3D9 in its public API, an implementation-defined bitfield read off disk) — and nothing in this tree calls it. Delete, don't port. | + +Inventories that came back smaller than expected: + +- **Input**: the only XInput entry point used anywhere is `XInputGetState` + (3 call sites). The live DirectInput reader + ([MUNGA_L4/L4JOY.cpp](../MUNGA_L4/L4JOY.cpp)) models 8 axes / 32 buttons / + 4 hats — a direct match for SDL's joystick model. +- **Audio**: OpenAL (~91 calls) + libsndfile (3 calls), **zero** + winmm/mmsystem anywhere. EFX is already optional and self-degrading. One + engine-core leak: `MUNGA/AUDIO.cpp` calls three `al*` functions directly. +- **Networking**: TCP only, no UDP, no `select()`/`poll()` — a pure per-frame + nonblocking poll loop branching on exactly three error codes + (`WSAECONNREFUSED`/`WSAECONNRESET`/`WSAEWOULDBLOCK`). +- **Steam**: four interfaces total (NetworkingSockets, NetworkingUtils, + Matchmaking, Friends+User), all gated behind the + `SteamNetTransport_ClientLibraryPresent()` probe. The vendored SDK already + ships `linux32/linux64 libsteam_api.so`. Steam Input is not used. +- **Renderer**: 100% fixed-function D3D9 — no shaders, no D3D9Ex. All ~193 + device calls sit in five `MUNGA_L4` files. D3DX usage is mesh loading + (`D3DXLoadMeshFromXA`), texture loading (PNG), and matrix helpers. + +The leaky seam is **windowing**: of ~430 user32/gdi32 call sites, about +two-thirds live *outside* the platform layer, and the per-frame message pump +sits in engine core (`MUNGA/APPMGR.cpp:126`). The cockpit composition is +genuinely exotic Win32: up to 11 top-level windows, D3D `Present` into a child +`STATIC` control via `hDestWindowOverride`, GDI-painted panes clipped over the +3D viewscreen, and hit-testing that relies on the stock STATIC proc returning +`HTTRANSPARENT`. + +## 2. Path A — Proton as the official Linux target (recommended) + +External findings (August 2026): 32-bit D3D9 through DXVK is Proton's +most-traveled path and is deliberately preserved — Steam Runtime 4 dropped +most i386 libraries *except* the ones Proton needs. Wine 10 (in current +Proton) merged Vulkan child-window rendering, removing the one historical +blocker that would have hit the viewscreen pane directly. Valve's stated +position is neutral between native and Proton, Deck Verified is achievable +with a Windows-only build, and the prevailing small-studio practice for +legacy Win32 titles is to ship Proton officially. Wine resolves filenames +case-insensitively, so the repo's asset-case inconsistencies cost nothing on +this path. + +One dated fact that matters: a 32-bit-specific Proton bug corrupted +`ISteamNetworkingSockets::ReceiveMessagesOnConnection` data — exactly RP412's +receive path — and was fixed only in **Proton 10.0-4b / 11.0-1b (July 2026)**. +The multiplayer test matrix should pin that as the minimum version. + +### Fix in the Windows build (confirmed in code) + +| Item | Evidence | Candidate fix | +|------|----------|---------------| +| **WinRT lamp mirror is a static import.** `windowsapp.lib` via `#pragma comment` — unlike `steam_api.dll`, not delay-loaded. Wine has no `windows.devices.lights` at all; the in-code try/catch guards only run if the image loads. | [MUNGA_L4/L4KEYLIGHT.cpp](../MUNGA_L4/L4KEYLIGHT.cpp) line 33 | Delay-load it the way `steam_api.dll` is handled, or move activation behind a `LoadLibrary` probe. Cheap, and hardens the Windows build too. | +| **Second top-level window fights gamescope.** The "Plasma Display" window plus the exploded `L4MFDSPLIT=2` mode (6 top-level panes) hit gamescope's known multi-window weakness on Deck. | [MUNGA_L4/L4PLASMASCREEN.cpp](../MUNGA_L4/L4PLASMASCREEN.cpp) line 271, [MUNGA_L4/L4MFDVIEW.cpp](../MUNGA_L4/L4MFDVIEW.cpp) line 297 | On Deck/gamescope, default the plasma glass into the cockpit window (the `SetParent` path already exists at `L4PLASMASCREEN.cpp:91`) and treat exploded view as desktop-only. | +| **Implicit hit-test transparency.** Clicks fall through the viewscreen only because the stock STATIC proc returns `HTTRANSPARENT`; no explicit `WM_NCHITTEST` handler exists. If Wine's STATIC differs, all mouse input over the 3D view dies silently. | [MUNGA_L4/L4VB16.cpp](../MUNGA_L4/L4VB16.cpp) line 4659 | Handle `WM_NCHITTEST` explicitly in the viewscreen subclass. Removes the dependency on an undocumented control behavior everywhere. | +| **Deck legibility floor.** The 1920×1080 canvas lands on Deck at 1280×720 — a 1.5× shrink. Valve's gate is 9 px minimum font height, so anything under ~14 px on the canvas fails. | Fixed canvas: [MUNGA_L4/L4APP.cpp](../MUNGA_L4/L4APP.cpp) line 269 | Audit the smallest cockpit and front-end type; bump or provide a Deck-scale preset. | +| **Gamepad-complete front end.** The menu/lobby is mouse-driven GDI; callsign entry is a Win32 `EDIT` control. Deck Verified requires the full flow on pad alone, with the Steam on-screen keyboard for text. | [RP_L4/RPL4FE.cpp](../RP_L4/RPL4FE.cpp) line 2467 | Add pad navigation to the front end and invoke `ShowFloatingGamepadTextInput` for the callsign field. The largest Path-A work item. | + +### Verify under Proton (needs live testing; no code change assumed) + +- **Child-window Present**: `Present(…, hDestWindowOverride)` into a child + STATIC under DXVK — supported per the DXVK source (per-HWND presenter map) + and Wine 10's child-window Vulkan work, but this exact composition (GDI + siblings clipped over the presented pane) is the least-exercised path in any + D3D9 stack. First thing to smoke-test. +- **Steam networking end-to-end**: repeat the three-machine SDR/FakeIP race + ([STEAM-3-MACHINE-TEST.md](STEAM-3-MACHINE-TEST.md)) with one or more peers + on Proton ≥ 10.0-4b. No Proton-specific FakeIP defects are on record, but + absence of reports is not confirmation. +- **Wine's d3dx9**: `D3DXLoadMeshFromXA` (.x meshes) and + `D3DXCreateTextureFromFile` (PNG) are the two heaviest leans on Wine's + reimplementation. +- **Input odds and ends**: the `IG_` RawInput device-path heuristic for XInput + de-duplication (`L4JOY.cpp:72-146`) assumes Windows-shaped HID paths; + `GetAsyncKeyState` + foreground-window focus gating under gamescope's focus + model; the legacy DirectInput paths' `DISCL_EXCLUSIVE` claims. +- **The joyconfig wizard** (`AllocConsole` + `conio`, `L4JOY.cpp:811-1392`) + very likely misbehaves under Proton — it is optional, and pad/keyboard + defaults don't need it; document rather than block on it. +- **Multi-adapter gauge windows** (up to 4 D3D devices, one per adapter, + `L4VB16.cpp:207`) are thin ice under DXVK — but that's the arcade + multi-monitor config, not the consumer default. Confirm the default path + never creates them. + +## 3. Path B — the native port, honestly scoped + +Feasible — the clean core makes it a real option, not a rewrite of the game — +but it is a phased engineering project, not a compatibility fix: + +0. **Mechanical compile pass — everything blocks on this.** Normalize include + case and separators repo-wide (267 of 289 distinct include names don't + match disk case; renaming files inside a case-insensitive checkout takes + care), shim the MSVC CRT tail (100 `stricmp`, the Annex K `_s` family, the + `_time64` family, `itoa`/`strlwr`, 9 `std::ostrstream` sites), fix + `Fail_To_Debugger`'s non-const `char*` signature (GCC/Clang reject it + outright, so the `DEBUG_LEVEL>0` build doesn't compile at all), and address + the `SKIPPY_CAST` strict-aliasing punning (`MUNGA/STYLE.H:108`). +1. **Drain the core leaks.** Replace `PostQuitMessage`-as-abort with the + portable hook whose prototype still sits commented out in `STYLE.H:31` + (32 sites); strip `` out of `MATRIX.h` and give it a neutral + interop type; typedef-shim the `HWND`/`HINSTANCE`/`SOCKADDR_IN` values + carried in core headers; split `RIOBase` out of + [MUNGA_L4/L4RIO.h](../MUNGA_L4/L4RIO.h) so the controls stack stops + dragging `` through the serial-packet header; port `FILESTUB` + (six syscalls) and `L4TIME` (one file); add a path-separator/case layer to + `FILEUTIL` and the 19 hardcoded `"dir\\"` literals. +2. **The 32/64-bit fork — the strategic decision.** The wire protocol and + `.RES` format are `/Zp1`-packed host-order struct dumps carrying `size_t` + and `unsigned long` fields (`MUNGA/RECEIVER.h:172`, + `MUNGA/RESOURCE.h:208-210`). A **32-bit native build** keeps every format + byte-identical but targets Steam's legacy *scout* runtime — swimming + against the current. A **64-bit build** (what Steam Runtime 4 wants) means + retyping the serialized fields to fixed widths, discovering and + pack-annotating the serialized struct set (46 raw stream overloads, no + manifest exists), and versioning the wire so Linux and Windows builds can + still race each other. Dovetails with the un-`/Zp1` cleanup already on the + roadmap; if the native port ever happens, 64-bit with fixed-width formats + is the right side of the fork. +3. **The new platform layer.** SDL for window/events/input (XInput's one + function → `SDL_GameController`; `L4JOY`'s 8-axis/32-button model maps + directly to `SDL_Joystick`, and SDL makes the RawInput de-dup unnecessary); + OpenGL for the fixed-function renderer (~193 device calls in five files, + no shaders to translate — the real work is replacing D3DX mesh/texture + loading); the CPU-side gauge canvases become streaming textures instead of + GDI blits; OpenAL-soft is a near drop-in; sockets go BSD (mechanically + small: 3 errno branches, ~45 `SOCKADDR_IN` occurrences); Steam loads via + `dlopen` through the flat API (~30 symbols) since ELF has no `/DELAYLOAD`. +4. **Rebuild the desktop UI.** The front end, lobby, and console board — + ~6,300 lines of GDI drawing and Win32 controls behind no interface — get + redrawn through the engine or SDL, and the ~580-line console-mode joyconfig + wizard gets a portable rewrite. This is also where the four separate + message pumps collapse into one event loop. +5. **Delete, don't port.** The legacy DirectInput paths (`L4DINPUT`, + `ThrustMaster`), DOS-era `L4KEYBD`/`L4MOUSE` stubs, the AWE32 audio layer + and `sos/`, `JOYSTICK.asm`, DivLoader (no call sites), and Windows Dynamic + Lighting (OS-exclusive by nature). The serial RIO path is optional hardware + support — stub it, or port it to termios only if pod preservation on Linux + ever matters. + +## 4. Sequencing + +The Path-A fixes (lamp-mirror delay-load, explicit hit-test, Deck window +policy, font audit, pad-driven front end) all improve the Windows build on +their own, and none are wasted if Path B ever runs. The reverse is not true. +When this resumes: an afternoon smoke test of the current build under Proton +on a Linux box — child-window Present first — before committing to anything.