Files
BT412/context/steamification.md
T
CydandClaude Opus 4.8 101562978d Terminology: BattleTech missions, not races
"Race" is Red Planet's term (it's a racing game) and rode in with the
RPL4LOBBY port. In BattleTech the gameplay unit is a MISSION. Sweep it out
of the front-end / lobby / marshal code + the roadmap and steamification
digest: rename the public API BTLobby_Push/PullRaceResults ->
Push/PullMissionResults, the internal PrimeHostedRace -> PrimeHostedMission,
the deferred marshal InstallNetworkRace -> InstallNetworkMission, and the
user-facing strings ("STEAM MISSION LOBBY", "L A U N C H  M I S S I O N",
"HOST/JOIN STEAM MISSION"). Comments follow. No behavior change; both gates
(default + BT412_STEAM) still build + link clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 23:29:38 -05:00

252 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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** (the gitea URL) — no remote, no fetch/push to it. Forked at BT411
`4e72f0c` (2026-07-14). *Bringing BT411 work forward is allowed via a **local-path**
merge (`git fetch C:/vwe/BT411 master`) — that never contacts the gitea BT411 remote;
see the merge note below.*
## BT411 catch-up merge (2026-07-14)
Merged BT411's post-fork line (`4e72f0c..abed41e`, 91 commits) into BT412 via a
**local-path** merge — brought the whole **audio-fidelity system** (engine `AUD*` +
`L4AUD*` + `audiopresets.cpp` + ~600 content `.wav`s, `docs/AUDIO_FIDELITY.md`),
missiles/rear-fire/HUD/gyro/gait tasks (#6668), `FOGDAY.EGG`, and refreshed context
docs. Only **5 files** overlapped the steamification; resolutions:
- `.gitignore`, `CMakeLists.txt`, `mech4.cpp` — trivial / auto-merged (the
`deviceOwnsInput` gating survived).
- `L4NET.CPP` — took BT411's task-#50 fix (don't close the game listener on console
loss) over the seam's adaptation of the old buggy close; the point-to-point send
stays on `NetTransport_Get()->Connect()`.
- `L4NETTRANSPORT.cpp` — folded BT411's **TCP_NODELAY** latency fix into
`WinsockNetTransport::Connect` (the seam already had BT411's retry-while-refused +
non-blocking). Accept-side game-socket NODELAY stayed at the L4NET call site.
- `mechmppr.cpp` — combined the `device_owns_input` gating with BT411's task-#68
look-behind ('V' rear-view), gating the `lookBehind` write too (a live RIO device
feeds it through its mapped button).
Verified after merge: clean build; solo front-end mode; loopback MP through the seam
(mesh completes, both tick, replication, no NODELAY warnings). [T2]
## 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
23) → 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) — DONE 2026-07-16 (5a5d).** Interactive menu + local egg builder
+ in-process marshal + results + single-binary loop all landed and boot-verified.
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).
**Completed since (2026-07-16):**
- Interactive on-screen catalog **menu** (green-on-black GDI, `btl4fe.cpp`): map / mech /
color / time / weather / length groups + pilot-name edit, framed LAUNCH (and, under
Steam, HOST / JOIN) buttons. Zero-arg launch shows it; a chosen LAUNCH builds the egg.
- In-process **LocalConsole marshal** (`btl4console.{hpp,cpp}`): NOT a thread — it runs on
the engine's per-frame hook (`gPerFrameHook`, added to `engine/MUNGA/APPMGR.*`, called on
the game thread in `RunMissions`), so its `application->Dispatch(StopMissionMessage)` is
safe. Owns the mission clock and stops it at the chosen `length=`; snapshots
per-pilot kills/deaths at the stop (`BTResolveRosterPilot`/`BTPilotKills`/`BTPilotDeaths`).
- **Score intake + results screen**: BT's own kills/deaths model (no `gConsoleScoreSink`
that is RP-layer; BT has no working mission-end wire flow, msgID 0x18 stays a stub). A
GDI "MISSION COMPLETE" scoreboard (`BTFrontEnd_ShowResults`) reads the marshal snapshot
(`BTLocalConsole_GetResult`/`GetResultName`).
- **Single-binary loop** = RELAUNCH per mission (`CreateProcessW` after the marshal stop),
NOT an in-process `for(;;)` — in-process re-init crashes on the stale reconstructed
`gBT*` globals (cdb-traced to `BTL4VideoRenderer::MakeEntityRenderables`
`gBTTerrainEntity`). Verified: 3 distinct PIDs across the menu→mission→results→menu cycle.
- WinMain launch-mode branch (`BTFrontEnd_LastLaunchMode()`): 0 solo, 1 lobby host, 2
lobby member (see Phase 6).
- **Phase 6 (Steam lobby) — CODE LANDED 2026-07-16; live 3-machine test DEFERRED.**
`game/reconstructed/btl4lobby.{hpp,cpp}` (ported from RP412 `RPL4LOBBY`) builds both gates
— stubs without `BT412_STEAM`, full `ISteamMatchmaking` room under it (both configs
link clean). What works: the room screen; member data (`ip`/`cp`/`gp`/`nm`/`vh`/`cl`/`bd` =
FakeIP + fake console/game ports + persona + mech/color/badge); the nonced `go` launch
roster → `SteamNetTransport_RegisterPeer` for every peer; `Push`/`PullMissionResults` over
lobby data (`res` key) rebuilding the shared score sheet via `BTLocalConsole_ClearResults`/
`InjectResult`. The menu offers HOST/JOIN when `BTLobby_Available()` (Steam transport up,
installed by WinMain on `BT412STEAM` env). A host launch bakes every member into the egg's
`[pilots]` (mesh `FakeIP:1502` addresses, round-robin drop zones) via
`BTFrontEnd_SetHostedPilots`; a member enters as a network pod on `:1501`
(`SetNetworkCommonFlatAddress` + `gConsoleLossEndsMission=True`).
**Deferred / not built:** the host→member **wire egg-feed marshal** (`InstallNetworkMission`
the owner streaming eggs + RunMission to remote pods, as `tools/btconsole.py` does). Without
it a member waits for a console connection nothing supplies, so a live host+member mission
is blocked on it. The lobby object does NOT survive the per-mission relaunch (the
single-binary limitation — everyone returns to a fresh menu, not the room).
`docs/STEAM-3-MACHINE-TEST.md` for BT is not yet authored.
- Phase 7: packaging (`pack-dist.ps1`), KeyLight, release — 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/<f>`); 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 ~38464046 (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 ~2226 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)