The unification flattened BT_STEAM into the always-on exe; the user flagged
the constraint that makes it the one legitimate compile gate: the Steamworks
SDK license bars a public release with Steam inside, so a release build must
exclude it entirely.
- CMakeLists: option(BT_STEAM OFF) restored around the Steamworks block --
an OFF configure touches no SDK headers, no steam_api.lib, no DLL copy.
BT_GLASS stays retired (always defined); ONE build dir either way.
Dev checkout: build/ configured once with -DBT_STEAM=ON.
- mkdist: reads BT_STEAM from build/CMakeCache.txt. Steam-OFF zips exclude
play_steam.bat + steam_api.dll (explicitly filtering a STALE DLL left by
an earlier ON build), strip the README steam section ({STEAM} line
markers), and take a -nosteam name suffix so the flavors never clobber
(found live: both stamped 4.11.436 and the release zip overwrote the dev
zip).
Verified both flavors:
ON (dev build): steam smoke degrades to Winsock without the client;
zip carries play_steam.bat + steam_api.dll + README steam lines.
OFF (scratch build-relcheck, deleted after): 0 compile errors; exe dir =
btl4.exe + OpenAL32.dll only; boots + simulates; BT_STEAM_NET=1 inert
(transport not in the binary); end-to-end zip has zero steam bits and
a clean README.
KB: context/glass-cockpit.md unified-build section records the license gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The three build dirs (build\ pod, build-glass\, build-steam\) were compile-
time splits of the same game; the tax was real: a stale steam exe shipped 2
days behind, the felt keymap flipped with the boot flavor, and cockpit
clicks were silently dead in the pod build (the trigger-config incident).
Now everything compiles into the single build\ exe:
- CMakeLists: BT_GLASS/BT_STEAM options removed; the glass TUs (PadRIO,
bindings, panel, glass windows, plasma) + FE/console + Steam transport
compile unconditionally; both macros always defined (seam markers).
- Steam: steam_api.lib linked with /DELAYLOAD:steam_api.dll + delayimp --
the DLL maps only when SteamAPI is first called, and every call site
gates on BT_STEAM_NET=1 (BTSteamNet_* short-circuit on steamActive), so
machines without the DLL run everything but Steam sessions.
- CABINET GUARD (btl4main): the miniconsole menu previously claimed any
zero-arg launch -- unified, that would hijack the pod cabinet's boot
shape. The menu now requires BT_PLATFORM=glass (or BT_FE_MENU=1);
platform parse moved above the fe_menu_mode computation. A bare boot
behaves exactly like the old pod build (DEV profile, wait for egg).
- Bats (play_solo/join/join_lan/play_steam) + the btoperator.py template:
single build\ path + BT_PLATFORM=glass. mkdist: one exe (+DLL scan
picks up OpenAL32 + steam_api). build-glass\/build-steam\/build-glass2\
(a stale Jul-20 cache) deleted.
Verified matrix on the unified exe:
bare zero-arg boot == old pod exe (same DEV profile + egg-load path;
the segfault on a missing egg is pre-existing, baselined vs the zip exe)
glass + BT_BTNTEST scripted click -> [cfgmap] ENTER/EXIT (trigger config)
runs with steam_api.dll ABSENT (delay-load proven)
glass zero-arg -> miniconsole menu
2-node loopback MP: 145 ticks each, cross-connected
BT_STEAM_NET=1 without Steam client -> "staying on Winsock", game runs
KB: context/glass-cockpit.md compile-gates section rewritten for the
unified model.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
context/glass-cockpit.md: record the BT_PLATFORM=glass/PadRIO click gating
(the dead-clicks root cause), the verified live regroup loop, the [cfgmap]/
BT_BTNTEST diagnostics, and the one-keymap settlement (babf5b1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clicking a per-display glass Engineering panel button (0x21 on an Eng page)
hard-crashed via a wild call through an uninitialised pointer (eip=cdcdcdcd
debug / 0x01048748 release), zero btl4 frames above Receiver::Receive.
Root cause is the dense message-handler-table gap hazard (reconstruction-
gotchas #11), the message-side twin of the attribute-table one -- NOT a /FORCE
unresolved external (the glass link log carried only the known-benign mech3.obj
CreateStreamedSubsystem set). Receiver::MessageHandlerSet::Find(id) returns
messageHandlers[id-1].entryHandler for any id <= entryCount with no populated-
check, and Receive() calls it when != NullHandler. The streamed Eng-page .CTL
dispatches subsystem msg id 3/0xb to whatever subsystem is shown; a weapon
(Emitter) registers only PoweredSubsystem 4-8 + MechWeapon 9-10, so id 3 is a
gap below entryCount 10. Build did new HandlerEntry[entryCount] and left gaps
uninitialised -> (this->*garbage)(msg). The 1995 binary has the identical non-
zeroing new[] (part_002.c Build) and survived only on fresh-heap-zero luck: a
weapon receiving id 3 was always meant to IGNORE it (id 3 = a Condenser/
Reservoir action in a different subsystem branch; the uniform Eng-page button
template makes buttons for unimplemented actions authentically inert). The new
per-display windows just made the id reachable live.
Fix (engine, class-wide, faithful): Build now null-inits every slot
(entryID=0/entryName=""/entryHandler=NullHandler) before copying inherited /
placing supplied, so a gap is deterministically NullHandler -> Receive drops it
(the authentic "Receiver ignores unhandled messages"), with an empty never-NULL
name so the name-based Find() strcmp can't deref a gap. Correct dispatch
contract, not a glass-path guard -> protects the whole dead-button class (#14).
Also lands the [glasswin] CLICK crash-forensics log (names the button before
dispatch).
Verified under cdb: click-soaked every MFD bank (Engineering/L+R Weapons/Heat/
Comm, ~130 clicks through Quad<->Eng page cycles) -> zero AVs, reconstructed
mapper preset selects still fire ([mode] preset (1,1)/(2,1)...); pod build +
solo mission un-regressed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One window per pod display (Heat/Engineering/Comm/Weapons x2/radar +
Flight Controls), each carrying its surface with its RIO button bank
arranged pod-faithfully; CPU-expanded surfaces, buttons under the
imagery. Runtime gate BT_GLASS_PANELS (=0 = the legacy single panel).
Merged on top of today's landed fix pile (audio-crash, experience,
parallax); the L4PADRIO overlap with the gait-detent commit resolved
preserving BOTH (his window refactor + our at-rest band snap).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
# context/glass-cockpit.md
Break the desktop glass cockpit's secondary displays out of the single
combined pad panel + D3D gauge strip into ONE window per pod display, each
carrying that display's surface with its RIO button bank, arranged
pod-faithfully around the main view. New TU engine/MUNGA_L4/L4GLASSWIN.*
(BT_GLASS-only), selected by the -platform glass preset via the new runtime
gate BT_GLASS_PANELS (=0 falls back to the legacy single panel + dock).
- Surfaces are CPU-expanded from the shared gauge buffer
(SVGA16::ExpandPlaneToBGRA, no D3D) and StretchDIBits'd in -- the D3D
dev-composite path (dock / window / overlay) stands down while active.
- Buttons sit UNDER the imagery: big hidden click targets that reach into
the display, with only a small protruding edge showing as a lamp light.
Red MFD banks tile the top/bottom; radar rails run the sides + a bottom
row; a Flight Controls window hosts the no-display banks.
- Windows: Heat / Engineering / Comm / Left Weapons / Right Weapons / radar
+ Flight Controls; edge-to-edge (no in-client margin/title; OS caption
labels each).
- Fix blank surfaces: application/ghWnd are duplicate-defined and bind
non-deterministically under /FORCE, so the fresh L4GLASSWIN TU read NULL.
Reach the renderer/window via BTResolveGaugeRenderer/BTResolveMainWindow
defined in btl4main.cpp off the real btl4App/hWnd pointers. New gotcha:
reconstruction-gotchas.md S6 duplicate-GLOBAL corollary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Human re-test on the fresh build: zero-stop OK, but the lever could PARK inside
the walk/run hysteresis band (walkStrideLength@0x534 .. reverseSpeedMax@0x538)
where the gait SM has no stable state -- walk<->run hunt, EngineShiftFwd/Rev
retriggering ("mixed walk/run mode + repeated acceleration sample"). The
pod-build keyboard lever already snaps out of the band to the NEARER edge at
key-rest (mech4.cpp GAIT DETENT); the PadRIO slew channel lacked it.
Faithful port, not an invention: mech4.cpp publishes the band in lever units
every driven frame (gBTGaitDetentLo/Hi seam, same guards + the same +0.002
engage margin); L4PADRIO Poll() applies the identical nearer-edge snap to the
Throttle channel when the slew rests (no slew key held, no pad slew axis
deflected). Sweeping through the band while held stays continuous (the
authentic moving lever, one authentic shift).
Verified (DEV.EGG glass, graduated parks each ~0.10 of the sweep,
BT_GAIT_TRACE+BT_MPPR_TRACE): band [0.358026,0.50388) = demand 22.02-30.87;
in-band parks snap both directions (0.4137->lo, 0.465126->hi); no at-rest
sample in the band; exactly ONE walk->run clip (state 10) up and ONE run->walk
(state 14) down across the whole sweep; zero-stop unaffected; pod build
rebuilt + BT_AUTODRIVE smoke clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The live-reported glass regressions (arrows dead / lever+detent missing /
V+J/K/L+N inert / weapons into the ground) were build-glass/Release/btl4.exe
built at 07:47 running the pre-5dd3536 image: no suppression tables, no V/J/K/L
keys, unpinned RIO ids. content\bindings.txt already carried the merged map
(arrows = W/S lever + A/D pedals) and parsed clean. Clean rebuild closed every
symptom; SendInput re-verification: lever holds on release + stops at zero,
LEFT/RIGHT turn in MID (wire-sign correct), V toggles with BT_SHOT pair, level
boresight at spawn, R/F elevation drive; pod build rebuilt + smoked un-regressed.
Code delta: BT_MPPR_TRACE [mppr-c] line enriched with mode/pedals/stick/turn/elev
so one env-gated line proves the whole control surface.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs/GLASS_COCKPIT.md gains the step-4 ledger (seam/transport/lobby + THE
FAKEIP LESSON); NEW context/steam-networking.md topic (+ router row) and
docs/STEAM_TEST.md (the 2-machine live procedure = the step-4 exit criterion
on user hardware). context/glass-cockpit.md: steps 3+4 status. Final purity
re-check: pod build clean, forced-walk mission runs, ZERO glass/steam tags in
the pod log.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs/GLASS_COCKPIT.md gains the 2b-2e detail + the full verification matrix
(purity build + seam audit, pod forced-walk, 2-node loopback MP via btconsole
relay on the POD build -- both nodes reach the running mission, PadRIO
authentic-path input, panel-click survival). context/glass-cockpit.md status
updated: step 2 DONE [T2]; steps 3-4 pending.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The streamed L4 mapping resource carries the binary's positional attribute ids
(stick=3, throttle=4) but MechControlsMapper chained from Subsystem::NextAttributeID
== 2 -- every streamed record resolved ONE MEMBER LATE (attr 4 -> pedalsPosition,
verified via the new permanent BT_CTRLMAP_LOG diagnostic in CreateStreamedMappings).
A latent real-pod bug: the serial RIO throttle would drive the pedals member; the
dev keyboard bridge masked it. Fix per the mechweap/mech attrPad idiom: ids pinned
to the binary numbering, id-2 gap padded, static_assert-locked. Torso + weapon
chains verified already aligned. Regression: BT_FORCE_THROTTLE headless walk clean
(speedDemand=61.501 through authentic InterpretControls, gait cycles, no faults).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>