option(BT412 ... ON) gates the steamification layer for the merge-back into BT411, so the unified repo serves both fronts: - Default (BT412=ON): pod/dev game + PadRIO / plasma / front end / lobby -- all runtime-gated, so a -egg/-net launch still behaves as 4.11. - Pod-minimal (-DBT412=OFF, build-pod/): compiles OUT L4PADRIO, L4PADBINDINGS, L4KEYLIGHT, L4PLASMASCREEN, L4STEAMTRANSPORT, btl4fe, btl4console, btl4lobby. Seam sites carry #ifdef BT412: L4CTRL.cpp PAD token (log + ignore), L4GREND.cpp L4PLASMA=SCREEN (log + ignore), L4VB16.cpp (inert PadRIO stub -- call sites identical in both flavors), btl4main.cpp (front-end/marshal/lobby blocks out; zero-arg launch = 4.11 behavior). - The NetTransport seam ALWAYS compiles -- it is the wire for both flavors (WinsockNetTransport, verified byte-identical to the pre-seam arcade path). - BT412_STEAM now requires BT412 (configure-time FATAL_ERROR; negative-tested). Verified: OFF -- clean v143 link, solo DEV.EGG mission runs (31 subsystems tick, gait, targeting). ON -- zero-arg launch engages the front-end menu. STEAM -- links clean, ships steam_api.dll. Also: README/context docs for the flavors + the merge-back; corrected the stale Phase 3b DEFERRED note (the single-window cockpit landed 07-16) and the README's leftover 0xBD3 valve-gate claim (it is a damage/explosion hub). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
21 KiB
id, title, status, source_sections, related_topics, key_terms, open_questions
| id | title | status | source_sections | related_topics | key_terms | open_questions | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| steamification | BT412 Steamification — status, seams, and merge notes | living | docs/BT412-ROADMAP.md; RP412 repo (C:\vwe\RP412) git history + docs |
|
|
|
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]: each checkout talks ONLY to its own gitea remote — C:\VWE\BT412
origin = VWE/BT412.git, C:\VWE\BT411 origin = VWE/BT411.git. Cross-repo sync
is local-path only (git fetch C:/VWE/BT412 master from BT411 and vice versa) —
never a cross remote. Forked at BT411 4e72f0c (2026-07-14); merged back 2026-07-17
(see the merge-back section below) — BT411 is now the unified repo carrying both lines
behind the BT412 compile gate.
Merge-back into BT411 + the BT412 compile gate (2026-07-17)
The whole steamification line was merged back into BT411 so one repo serves both fronts (pod work + internet MP). Mechanics [T2]:
- BT411's head was exactly
abed41e— the catch-up point BT412 had already merged — so the merge was a clean fast-forward of BT411 onto BT412's line via a local-path fetch (no gitea remote touched from either side). BT411's dirtyrun/run.cmdbench edit (pod-net 1501restart loop) was stashed and restored. - Compile gate: CMake
option(BT412 ... ON)+ compile definitionBT412on all three targets. Gated OUT under-DBT412=OFF(the pod-minimal flavor,build-pod/):L4PADRIO,L4PADBINDINGS,L4KEYLIGHT,L4PLASMASCREEN,L4STEAMTRANSPORT,btl4fe,btl4console,btl4lobby, plus#ifdef BT412seams inL4CTRL.cpp(PAD token),L4GREND.cpp(L4PLASMA=SCREEN),L4VB16.cpp(inertPadRIOstub),btl4main.cpp(front-end/marshal/lobby blocks; zero-arg = 4.11 behavior). The NetTransport seam always compiles (the wire for both flavors).BT412_STEAMrequiresBT412(configure-timeFATAL_ERROR, negative-tested). All three flavors verified: OFF solo mission un-regressed (31 subsystems, gait, targeting); ON zero-arg front end engages; STEAM links + shipssteam_api.dll. Details: build-and-run §Build flavors. handoff/padrio/(the interim BT411 transfer copy, commit2e475f4) was retired — superseded by the merge itself (the real modules now live in BT411; a drifting duplicate in the same tree would be a hazard).
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 .wavs, docs/AUDIO_FIDELITY.md),
missiles/rear-fire/HUD/gyro/gait tasks (#66–68), FOGDAY.EGG, and refreshed context
docs. Only 5 files overlapped the steamification; resolutions:
.gitignore,CMakeLists.txt,mech4.cpp— trivial / auto-merged (thedeviceOwnsInputgating 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 onNetTransport_Get()->Connect().L4NETTRANSPORT.cpp— folded BT411's TCP_NODELAY latency fix intoWinsockNetTransport::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 thedevice_owns_inputgating with BT411's task-#68 look-behind ('V' rear-view), gating thelookBehindwrite 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 to4e72f0c, dev-staterun/run.cmdworking-tree edit discarded (committed version is the documented solo launcher), identity pass (README/CLAUDE.md/CMakeproject(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 ingame/reconstructed/mech3.cppare neutralized behindBT412_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(rioPointerisRIOBase*),L4CTRL.cppPAD-token hook (→primaryControlType=PrimaryRIO, so the stockMechRIOMapperengages — confirmed), newL4PADRIO.*/L4PADBINDINGS.*/L4KEYLIGHT.*(gates renamedBT412KEYLIGHT; KeyLight TU builds/std:c++17 /permissive-per-file). Defaultbindings.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.txtis gitignored (written on first PAD run). BT-side changes PadRIO forced: (1) both keyboard input bridges (mech4.cpp write of mapper attrs; mechmppr.cppBT_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.CTLstick mapping wrotethrottlePosition. 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=1walks). 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.cppselector 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: theL4GAUGEgauge 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) — DONE 2026-07-16 (initially deferred 2026-07-14; the blockers fell). Landed across commits0c964c0→59c454b+2b238a8: the 1920×1080 single-window cockpit arrangement (RP412 layout) via theL4VB16.cpphand-merge, on-screen clickable lamp-lit RIO button banks framing the MFDs (4-above/4-below strips — BT-derived placement, not vRIO geometry, which has no BT data), green MFD phosphor tint, button-lamp bitfield decode (subdued idle baseline),L4CONTROLS=PADas the cockpit default (before the DEV keyboard default), the gauge NULL-source guard extended to the split path, and the backtick () toggle for the secondary displays. Mouse presses inject into PadRIO as screen buttons (BTPadRIOScreenButton, WndProc →L4VB16.cpp`). [T2] - Phase 4 (NetTransport seam + Steam transport) — DONE 2026-07-14.
L4NET.CPPtaken from RP412 post-seam (all ~24 Winsock call sites now route throughNetTransport_Get()); BT's 3BT_NET_TRACEblocks re-sited onto their code anchors (the drop path, the point-to-point send beforeNetTransport_Get()->Send, the pad-buffer receive) — they read message/packet metadata, not sockets, so no collision.L4NETTRANSPORT.*(seam +WinsockNetTransportdefault) andL4STEAMTRANSPORT.*(gate renamedBT412_STEAM, self-test envBT412STEAMSELFTEST) 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/publicinclude +steam_api.lib+ post-buildsteam_api.dllcopy.steam_appid.txtgitignored (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-rxtraces fire throughNetTransport_Get()); (2)BT412_STEAM=ONconfig + 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 (5a–5d). 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), aBTFeMissionconfig struct, andBTFeMission_WriteEggthat emits the exact console egg format (verified againstcontent/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-eggpath. game/btl4main.cppWinMain: front-end mode — no-eggand no-netbuildsfrontend.egglocally and points the engine at it (BTFrontEnd_Run).-egg/-netruns 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 toengine/MUNGA/APPMGR.*, called on the game thread inRunMissions), so itsapplication->Dispatch(StopMissionMessage)is safe. Owns the mission clock and stops it at the chosenlength=; 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 (
CreateProcessWafter the marshal stop), NOT an in-processfor(;;)— in-process re-init crashes on the stale reconstructedgBT*globals (cdb-traced toBTL4VideoRenderer::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 RP412RPL4LOBBY) builds both gates — stubs withoutBT412_STEAM, fullISteamMatchmakingroom 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 noncedgolaunch roster →SteamNetTransport_RegisterPeerfor every peer;Push/PullMissionResultsover lobby data (reskey) rebuilding the shared score sheet viaBTLocalConsole_ClearResults/InjectResult. The menu offers HOST/JOIN whenBTLobby_Available()(Steam transport up, installed by WinMain onBT412STEAMenv). A host launch bakes every member into the egg's[pilots](meshFakeIP:1502addresses, round-robin drop zones) viaBTFrontEnd_SetHostedPilots; a member enters as a network pod on:1501(SetNetworkCommonFlatAddress+gConsoleLossEndsMission=True). Host→member wire egg-feed marshal — DONE (2026-07-17):BTLocalConsole_InstallNetworkMission(btl4console.cpp) makes the owner play the arcade console in-process over the NetTransport seam (Winsock TCP or Steam SDR): connect to each member's console channel, feed the chunked egg (NetworkManager::ReceiveEggFileMessage, \n→NUL wire image liketools/btconsole.py) retry-until-ACK, poll state (StateQueryMessage), and when the whole mesh is staged atWaitingForLaunchdispatchRunMissionto all + locally at once;StopMissionat expiry. The owner's own pod is fed viaL4NetworkManager::FeedLocalEgg. Scores are BT's kills/deaths snapshotted from the (meshed) roster at the stop — NO EndMission wire intake (BT's mission-end flow is a stub; the mesh already put every pilot in the host's roster).- Engine change (ported from RP412):
gConsoleMarshalsLaunch(APPMGR.h/.cpp) + the!gConsoleMarshalsLaunch &&guard in APP.cpp's WaitingForLaunch self-launch — holds the owner (which has no console connection to itself) at WaitingForLaunch so the in-process console launches the mesh together instead of the owner self-launching early. Default False = stock behavior; solo boot un-regressed. - WinMain host path honors
BT412HOSTPODS(+BT412HOSTPORT) for the lobby host AND a classic LAN host:SetNetworkCommonFlatAddress+InstallNetworkMission(secs, "frontend.egg", pods), falling back to a solo marshal if no member is reachable. - Verified (loopback): two
btl4.exe -netpods fed bytools/btconsole.pyreach "All connections completed!" and run after the engine change — the exact protocol + launch handshake the marshal uses. Both gates build+link; the Release dist boots and the Steam transport comes up (FakeIP allocated). - Remaining: the live multi-machine Steam mission (FakeIP mesh + pilot-slot matching across
machines) — untestable here (needs Steam + ≥2 accounts/machines). Procedure:
docs/STEAM-3-MACHINE-TEST.md. The lobby object does NOT survive the per-mission relaunch (single-binary limitation — everyone returns to a fresh menu, not the room).
- Engine change (ported from RP412):
- Phase 7 (packaging) — IN PROGRESS.
pack-dist.ps1landed (assembles a runnable Steam test dist; wraps the zip in a singleBT412/folder). KeyLight landed with Phase 2 (L4KEYLIGHT, gateBT412KEYLIGHT). Remaining: release proper (real Steam AppID, store assets, depot config) + the Phase 6 live 3-machine test. - Merge-back into BT411 — DONE 2026-07-17 (see the merge-back section above): one
repo, both lines, behind the
BT412compile gate.
The seams (what plugs in where) [T0 unless noted]
- Input:
LBE4ControlsManagerPUSH model;L4CTRL.cppparsesL4CONTROLStokens; RP412 adds tokenPAD→new PadRIO()(aRIOBaseimplementation from XInput+keyboard), and the stock RIO mapper runs against it unchanged. BT's mapper selection:btl4app.cpp MakeViewpointEntityswitch oncontrols->primaryControlType(+0xc98) →MechRIOMapperfor PrimaryRIO(4). - Plasma: BT's plasma is identical hardware to RP's (128×32,
L4PLASMA.h); RP412'sPlasmaScreenrenders the sameVideo8BitBufferedsurface into a desktop window, selected inL4GREND.cpponL4PLASMA=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
SVGA16pixelBuffer (see gauges-hud, pod-hardware). RP412'sMFDSplitView(L4MFDVIEW.*+L4VB16.cppsplit mode,L4MFDSPLIT=1) composes child panes on a fixed 1920×1080 canvas; 3D presents into the viewscreen pane viaPresent(hDestWindowOverride). - Networking: arcade stack unchanged (see multiplayer); RP412 moved the wire
behind
NetTransport(L4NETTRANSPORT.*), defaultWinsockNetTransport, Steam implementationL4STEAMTRANSPORT.*(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.pyis 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 ~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 throughNetTransport_Get(); BT's 5BT_NET_TRACEdiagnostic 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
.cppneeds its ownadd_libraryline or it silently doesn't compile. New deps:xinput9_1_0.lib(PadRIO), C++/WinRT (KeyLight, per-file C++17), Steamworks SDK +steam_api.libbehindoption(BT412_STEAM). - No
/Zp1in 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)