--- id: steamification title: "BT412 Steamification — status, seams, and merge notes" status: living source_sections: "docs/BT412-ROADMAP.md; RP412 repo (C:\\vwe\\RP412) git history + docs" related_topics: [pod-hardware, multiplayer, gauges-hud, build-and-run, wintesla-port] key_terms: [egg, RIO, console] open_questions: - "BT cockpit button-bank geometry source (no vRIO CockpitLayout exists for BT): l4gauge.cfg / pod photos / Nick" - "Does BT's rebuilt L4D3D still have RP's NULL-texture AddRef crash pattern?" - "Does L4CONTROLS=PAD set primaryControlType=PrimaryRIO so MechRIOMapper engages unchanged?" --- # BT412 Steamification — status, seams, and merge notes BT412 = BT411 made distributable on Steam and playable over the internet with no pod hardware, following the recipe RP412 proved end-to-end (RP411 → RP412). The full plan and phase order: `docs/BT412-ROADMAP.md`. RP412 (`C:\vwe\RP412`) is the reference implementation — its git history from commit `467934c` onward is the steamification, grouped roughly: toolchain → PadRIO input → MFD-split cockpit → front end/LocalConsole → NetTransport/Steam sockets → lobby → bindings/KeyLight → packaging. **Repo rule [T0]:** `origin` = `VWE/BT412.git` only. This repo must never touch the BT411 remote (no cross-pull remote — deliberate difference from RP412's `rp411` pattern). Forked at BT411 `4e72f0c` (2026-07-14). ## Status ledger - **Phase 0 bootstrap — DONE 2026-07-14.** Tree copied (working tree + full history, `build/` excluded), origin re-pointed, fast-forwarded to `4e72f0c`, dev-state `run/run.cmd` working-tree edit discarded (committed version is the documented solo launcher), identity pass (README/CLAUDE.md/CMake `project(bt412)`), this topic + roadmap authored. - **Phase 1 (VS2022 v143) — DONE 2026-07-14.** Generator moved to `Visual Studio 17 2022` (v143, Win32). One real change was forced: the v143 linker fails the image on unresolved externals even under `/FORCE`, so the dead offline-tool ladders in `game/reconstructed/mech3.cpp` are neutralized behind `BT412_OFFLINE_TOOLS` (default off) and the exe now links `/FORCE:MULTIPLE` (UNRESOLVED retired — real unresolved symbols are hard errors now). None of RP412's other v143 source fixes (TIME.h, NETWORK.h, L4DINPUT, CAMMGR) were needed — BT's tree was already v142-clean. Verified: clean build; solo DEV.EGG runs (gait/target/ 31-subsystem ticks); two-instance loopback MP via btconsole.py — mesh forms (console + 1502↔1602 game ports, "All connections completed!"), mission runs both sides, each world ticks its master + the remote replicant. [T2] - **Phase 2 (PadRIO input layer) — DONE 2026-07-14.** Ported from RP412: `L4RIO.h` (RIOBase split; serial RIO re-parented), `L4CTRL.h` (`rioPointer` is `RIOBase*`), `L4CTRL.cpp` PAD-token hook (→ `primaryControlType=PrimaryRIO`, so the stock `MechRIOMapper` engages — confirmed), new `L4PADRIO.*` / `L4PADBINDINGS.*` / `L4KEYLIGHT.*` (gates renamed `BT412KEYLIGHT`; KeyLight TU builds `/std:c++17 /permissive-` per-file). Default `bindings.txt` = the shared Tesla board layout (numpad flight, letter rows = MFD banks, F-keys = Secondary columns, joystick head 0x40-0x47 on Space/arrows/pad). `content/bindings.txt` is gitignored (written on first PAD run). **BT-side changes PadRIO forced:** (1) both keyboard input bridges (mech4.cpp write of mapper attrs; mechmppr.cpp `BT_KEY_BRIDGE`) now stand down when a RIO device exists (`bridge_controls->rioPointer != 0`) — they clobbered the engine push every frame; M mode-cycle / X recenter conveniences stay live. (2) **the mapper attribute chain was off by one** — see [[reconstruction-gotchas]] §11 (short-parent-chain positional trap): the `.CTL` stick mapping wrote `throttlePosition`. Fixed with a pad slot + binary-locked enum. This was a latent REAL-POD bug (Phase 8 would have hit it). Verified live: `L4CONTROLS=PAD` → throttle lever ramps (rate axis, sticky), stick deflects (turnDemand=1 + the authentic speed-vs-turn clamp: 61.5→22.0 u/s while turning), mech drives across the map, XInput pad detected + 3 s re-probe, bindings parse (59 key buttons/8 axes/12 pad buttons/5 pad axes). Keyboard fallback regression-verified headless (`BT_FORCE_THROTTLE=1` walks). New diagnostics: `BT_CTRLMAP_LOG=1` (streamed-mapping dump), `BT_STICK_LOG=1` (RIO stick push trace). - **Phase 3a (PlasmaScreen, `L4PLASMA=SCREEN`) — DONE 2026-07-14.** `L4PLASMASCREEN.*` copied verbatim; `L4GREND.cpp` selector diff applied (BT's only divergence there is a comment at line 440 — no collision). Renders BT's 128×32 plasma into a desktop window. Verified: a window titled "Plasma Display" opens and the game ticks normally beside it (`L4GAUGE=640x480x16 L4PLASMA=SCREEN`). Note: the `L4GAUGE` gauge path binds a listen socket at startup → a first-run Windows Firewall prompt (expected; unrelated to plasma). - **Phase 3b (single-window MFD-split cockpit, `L4MFDSPLIT=1`) — DEFERRED 2026-07-14.** Two blockers make this the wrong thing to land now: 1. **No BT cockpit-layout geometry.** RP412's `L4MFDVIEW` button-bank anchors/addresses come from vRIO's `CockpitLayout` for the Red Planet pod; vRIO has **zero** BattleTech data. The BT pod's panel geometry has no reference (needs pod photos / `l4gauge.cfg` mining / Nick) — the clickable lamp-lit banks can't be authentically placed. 2. **BT's MFD compositing is an unfinished upstream beachhead.** Per `docs/GAUGE_COMPOSITE.md`, only the radar/secondary surface composites on a dev box; the five mono MFDs don't render yet (port-name mismatch, Step 2). RP412's split panes would be empty until that upstream work lands. 3. **Merge is HIGH-risk** (quantified above): BT's 521-line L4VB16 divergence (dev-gauge docking + a 476-line block) overlaps RP412's 402-line split rewrite in the ctor and `Update` regions — a true hand-merge, not a drop-in. The 3-way diffs are staged in the scratchpad (`rp412-L4VB16.diff`, `bt-L4VB16.diff`). Prerequisite order when resumed: finish BT's MFD dev-composite (GAUGE_COMPOSITE.md Steps 2–3) → obtain BT cockpit geometry → then hand-merge `MFDSplitView`. Until then the game ships single-window with the dev-gauge dock (`BT_DEV_GAUGES`) as its cockpit composite. - **Phase 4 (NetTransport seam + Steam transport) — DONE 2026-07-14.** `L4NET.CPP` taken from RP412 post-seam (all ~24 Winsock call sites now route through `NetTransport_Get()`); BT's 3 `BT_NET_TRACE` blocks re-sited onto their code anchors (the drop path, the point-to-point send before `NetTransport_Get()->Send`, the pad-buffer receive) — they read message/packet metadata, not sockets, so no collision. `L4NETTRANSPORT.*` (seam + `WinsockNetTransport` default) and `L4STEAMTRANSPORT.*` (gate renamed `BT412_STEAM`, self-test env `BT412STEAMSELFTEST`) copied. `extern/steamworks_sdk_164/` vendored (headers + win32 redistributable_bin). One engine global ported from RP412's APPMGR: `gConsoleLossEndsMission` (default False = arcade re-listen; the marshaled/lobby path sets it in Phase 5/6). CMake: seam always compiled; `option(BT412_STEAM)` (default OFF) gates the transport define + `sdk/public` include + `steam_api.lib` + post-build `steam_api.dll` copy. `steam_appid.txt` gitignored (Spacewar 480 by hand until a real AppID). Verified: (1) default TCP build — loopback MP through the seam is byte-for-byte the old behavior (console connects, egg msgID-3 chunks delivered, "All connections completed!", both instances tick, `net-tx`/`net-rx` traces fire through `NetTransport_Get()`); (2) `BT412_STEAM=ON` config + build (compile-check of the Steam transport TU against the vendored SDK; a live Steam session needs the Phase 6 lobby + 3 machines). - **Phase 5 (front end) — FOUNDATION DONE 2026-07-14; marshal/menu/results IN PROGRESS.** Landed the local **mission-egg builder** — the core "single-player without the operator console" capability: - `game/reconstructed/btl4fe.{hpp,cpp}`: catalogs (8 maps, 8 mechs + variants, colors), a `BTFeMission` config struct, and `BTFeMission_WriteEgg` that emits the exact console egg format (verified against `content/MP.EGG`): `[mission]`, `[ordinals]` + the 4 static rank-place plasma bitmaps, `[pilots]`, per-pilot loadout sections, `[largebitmap]`/`[smallbitmap]` with **GDI-rendered** 128×32 / 64×16 pilot-name plasma bitmaps, and the role models. - Two engine setters ported from RP412's L4APP.H (`SetEggNotationFileName`, `SetNetworkCommonFlatAddress`) so the front end feeds the standard `-egg` path. - `game/btl4main.cpp` WinMain: **front-end mode** — no `-egg` and no `-net` builds `frontend.egg` locally and points the engine at it (`BTFrontEnd_Run`). `-egg`/`-net` runs are untouched. Verified: zero-argument launch → `[frontend] built frontend.egg` → mission loads and runs (ticks, gait, targeting); the generated egg is structurally identical to MP.EGG (32×32/16×16 name bitmaps with real glyph pixels, all sections present). **Remaining in Phase 5** (each substantial, deferred): interactive on-screen catalog menu (RP412 `RPL4FE` is 1536 lines of render-loop UI); the in-process **LocalConsole marshal** thread (mission clock + `StopMissionMessage` at expiry + single-binary menu→race→results→menu loop); and the **score-intake + results screen**, which is genuinely NEW for BT — BT has no working mission-end flow (mission-review msgID 0x18 is a stub, `scoreAward=0`); v1 should collect kills/deaths from the live Comm-MFD `pilotList` tally at stop (see [[gauges-hud]], [[multiplayer]]). - Phases 6–7: Steam lobby (`btl4lobby`) + packaging remain. ## The seams (what plugs in where) [T0 unless noted] - **Input**: `LBE4ControlsManager` PUSH model; `L4CTRL.cpp` parses `L4CONTROLS` tokens; RP412 adds token `PAD` → `new PadRIO()` (a `RIOBase` implementation from XInput+keyboard), and the stock RIO mapper runs against it unchanged. BT's mapper selection: `btl4app.cpp MakeViewpointEntity` switch on `controls->primaryControlType` (+0xc98) → `MechRIOMapper` for PrimaryRIO(4). - **Plasma**: BT's plasma is identical hardware to RP's (128×32, `L4PLASMA.h`); RP412's `PlasmaScreen` renders the same `Video8BitBuffered` surface into a desktop window, selected in `L4GREND.cpp` on `L4PLASMA=SCREEN`. - **Displays**: BT pod = main 800×600 3D view + 640×480 radar + five mono MFDs on one spanned 1280×480 surface (+ plasma). All cockpit surfaces are bit-plane masks over one shared `SVGA16` pixelBuffer (see [[gauges-hud]], [[pod-hardware]]). RP412's `MFDSplitView` (`L4MFDVIEW.*` + `L4VB16.cpp` split mode, `L4MFDSPLIT=1`) composes child panes on a fixed 1920×1080 canvas; 3D presents into the viewscreen pane via `Present(hDestWindowOverride)`. - **Networking**: arcade stack unchanged (see [[multiplayer]]); RP412 moved the wire behind `NetTransport` (`L4NETTRANSPORT.*`), default `WinsockNetTransport`, Steam implementation `L4STEAMTRANSPORT.*` (`ISteamNetworkingSockets`, FakeIP + SDR, degrade-to-TCP on any failure). Addresses stay SOCKADDR_IN-shaped so the egg `[pilots] ip[:port]` roster works over both wires. - **Session**: console protocol (egg msgID 3, 1040-byte chunks + ACK; RunMission msgID 5 ×2; console must stay connected) — `tools/btconsole.py` is the working feeder and the spec for the in-process LocalConsole marshal (Phase 5). ## Known merge risks (from the 3-way BT411 / RP411-baseline / RP412 comparison) **Quantified 2026-07-14 [T1]** — BT-vs-RP411-baseline divergence measured with `diff --strip-trailing-cr` (baseline extracted from RP412 history, `git show 3b8b729:MUNGA_L4/`); RP412-side change sizes from `git diff 3b8b729..HEAD`: | File | BT vs baseline | RP412 change | Treatment | |------|----------------|--------------|-----------| | `L4RIO.h` | identical | 125 lines (RIOBase split) | RP412 verbatim | | `L4RIO.cpp` | identical | 0 | nothing to do | | `L4CTRL.h`, `L4NET.H`, `L4PLASMA.*`, `L4KEYBD.h` | identical | small | RP412 verbatim | | `L4CTRL.cpp` | 29 diff lines | 17 (PadRIO hook) | apply RP412 diff | | `L4GREND.cpp` | 6 diff lines | 15 (PlasmaScreen selector) | apply RP412 diff | | `L4VIDEO.cpp` | **2725** diff lines (BT render work) | only 14 | apply RP412 diff — small despite BT divergence | | `L4VIDEO.h` | 29 diff lines | 7 | apply RP412 diff | | `L4NET.CPP` | 23 diff lines (BT_NET_TRACE ×5) | 345 (seam refactor) | **MEDIUM** — apply seam refactor, re-site traces | | `L4VB16.cpp` | **514** diff lines (dev-gauge dock) | 350 (split view) | **HIGH** — true hand-merge | | `L4VB16.h` | 14 diff lines | 32 | hand-merge with L4VB16.cpp | - **`L4VB16.cpp` — HIGH.** RP412 rebuilt ~3846–4046 (splitViews/canvas/present); BT rebuilt the same window-management region for dev-gauge docking (`BT_DEV_GAUGES`, `BT_DEV_GAUGES_WINDOW`, `BT_DEV_GAUGES_DOCK`, `BT_GAUGE_SCALE`, `BT_GAUGE_SEC_ROT`, `BT_ALL` — 15 sites). Hand-merge; write the 3-way diff notes HERE before editing. - **`L4NET.CPP` — MEDIUM.** RP412 rerouted all ~22–26 Winsock call sites through `NetTransport_Get()`; BT's 5 `BT_NET_TRACE` diagnostic blocks sit exactly on those send/receive sites and must be re-sited onto the seam calls. - **CMake**: engine sources are explicit per-file lines (no globs) — every ported `.cpp` needs its own `add_library` line or it silently doesn't compile. New deps: `xinput9_1_0.lib` (PadRIO), C++/WinRT (KeyLight, per-file C++17), Steamworks SDK + `steam_api.lib` behind `option(BT412_STEAM)`. - **No `/Zp1` in BT's build** (RP's engine builds `/Zp1`): RP412's packing-mismatch workarounds (`WINDOWS_IGNORE_PACKING_MISMATCH`, KeyLight default-packing carve-out, `pack(push,8)` around Steam headers) are no-ops here — keep them for verbatim-ness, but eyeball any copied code for /Zp1-layout assumptions. ## Key Relationships - Uses: [[pod-hardware]] · [[multiplayer]] · [[gauges-hud]] · [[build-and-run]] - Feeds: [[open-questions]] (steamification investigate items)