Audit of what actually landed in context/ versus what only ever got said in
chat. Five gaps and one stale claim repeated in four files.
New env gates were the biggest hole -- BT_TORSO_LOG, BT_MYOMERS_LOG,
BT_MYOMERS_REPAIR_TEST, BT_SELF_DAMAGE and BT_POWER_DETACH_TEST were all in the
code and none of them in decomp-reference §6, which is supposed to be the hub.
Added with the reasoning that makes them usable: why the myomers probe samples
every frame while unpowered (the NoVoltage window is about a second and a 1 Hz
probe steps straight over it), why the repair test also forces Manual (or
AutoConnect restores power a frame later), why SELF_DAMAGE latches off at the
first death, and why DETACH_TEST taking a NAME is what proves failover rather
than a same-generator re-attach.
The 6am log day was in the code and the player bats but not the KB. Now in
build-and-run with the stem table, the unconditional append, the 8 MB part
roll-over and the BT_LOG-truncates trap, plus the operator-facing note in
operator-console and OPERATOR_GUIDE that you ask for the NEWEST log, never
"today's".
Recorded the crash-symbolization gap, which is the one with teeth. BTCrashFilter
writes a stack into the day log and its own comment claims "we hold the PDB". We
do not: no Release PDB is produced at all, and /O2 implies /Oy so the EBP walk is
unreliable anyway. When the Owens crash finally lands we get an address we cannot
resolve. Written down with the fix (/Oy-, /Zi + /DEBUG, archive the PDB per
build) rather than left as something I mentioned once.
New gotcha 22: HeatModelOff() reads simulationState == 1, i.e. "am I destroyed",
and has nothing to do with the heat model. Behaviour at the call sites is right,
the name is not, and while chasing #70 it reads as "novice pilots cannot twist"
and sends you hunting an experience-level bug that does not exist. Same misnomer
in torso/gyro/sensor headers.
Also documented the dist flavor trap, having nearly shipped it myself: mkdist
reads build/CMakeCache.txt, so a tree configured BT_STEAM=OFF silently produces
a -nosteam zip with no play_steam.bat and no steam_api.dll. OFF is the CMake
default; ON is the documented dev state.
Swept the stale `btl4.log` filename out of reconstruction-gotchas,
reconstruction-method, build-and-run and CLAUDE.md itself -- the log has been
<stem>_YYYYMMDD.log since 1777d5a and every "read btl4.log" instruction was
pointing at a file that is no longer written.
checkctx CLEAN.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
10 KiB
id, title, status, source_sections, related_topics, key_terms
| id | title | status | source_sections | related_topics | key_terms | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| build-and-run | Build / Run / Debug — recipe, repo layout, env gates | established | PROGRESS_LOG.md §10a, §10a-bis; README.md |
|
|
Build / Run / Debug
The one top-level CMakeLists.txt builds munga_engine (engine) + bt410_l4 (reconstructed BT
game lib) + btl4.exe. Full recipe + repo-layout map: docs/PROGRESS_LOG.md §10a / §10a-bis +
README.md. Env-gate table: decomp-reference §6.
Build (Win32 / VS2019 BuildTools — the DXSDK link libs are Lib/x86)
# configure once:
cmake -S C:\git\bt411 -B C:\git\bt411\build -G "Visual Studio 16 2019" -A Win32 \
-DCMAKE_GENERATOR_INSTANCE="C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools"
# build:
cmake --build C:\git\bt411\build --config Debug
- Links DXSDK d3d9/d3dx9/dinput8 + OpenAL/libsndfile (
engine/lib/). DXSDK June 2010 atC:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\(overridable-DDXSDK). [T2] - Linker uses
/FORCE— tolerates header-defined globals + the dead offline-factory unresolved externals in mech3.cpp (Mech::CreateSubsystemStreamreferencesvoid*-signatureCreateStreamedSubsystemfor every class; never called at runtime). ⚠/FORCEalso HIDES real unresolved symbols as runtime AVs — see reconstruction-gotchas §6. [T2] - Editing an
engine/file rebuilds the engine lib automatically (one project). A NEW member on aDPLRenderer/d3d_OBJECTclass needs the game objs that embed its layout recompiled — delete stale objs if layout-mismatch corruption appears. [T2] - ⚠ Kill the running exe before rebuilding (
taskkill //F //IM btl4.exe) or you get LNK1104.
Run
run\run.cmd [EGG] # default DEV.EGG; cd's to content\ and runs btl4.exe -egg <EGG>
- cwd MUST be
content\— the engine resolvesBTL4.RES,VIDEO\,BTDPL.INI, eggs relative to cwd (theloadTablesgotcha:L4VIDEO.cpp:849fopen("VIDEO\\REPLACEMATS.tbl")is relative + unchecked → fread on NULL if cwd is wrong). Logs tocontent\<stem>_YYYYMMDD.log(orBT_LOG=<file>). [T2] Log naming (2026-07-28): stem = the launcher (solo/steam/join/joyconfig, elsebtl4), chosen fromBT_FE_SOLO/BT_STEAM_NET/BT_FE_JOIN/BT_RELAY/BT_JOYCONFIG. A self-named day file always appends (the default stream mode truncates, so a second launch would erase the morning — that is how the 2026-07-27 Owens crash stacks were lost under the old rotation). Nothing is ever deleted; past 8 MB the NEXT launch opens<stem>_YYYYMMDD.1.log(checked only at open, so a live session is never split). ⚠ The log DAY runs 06:00 → 06:00 local, not midnight — a late session stays filed under the evening it started, so one playtest night is one file. Only the FILENAME is shifted; the session header andlastrun_<stem>.txteach callGetLocalTimeseparately and carry true local time. ⚠BT_LOG=<file>is used VERBATIM and truncates unlessBT_LOG_APPEND=1— the player bats clear it for exactly this reason. Since 2026-07-26 the exe enforces this itself (BTEnsureContentDirectory, btl4main.cpp): if cwd has noBTL4.RESit probes from the exe's own directory (..\..\contentfor the shipped layout, plus flattened / in-content cases) andSetCurrentDirectorys there, before the log file opens. The boot line says so when it had to look. ⚠ The landmine it defuses (field report): a barebtl4.exelaunch frombuild\Releasefound no resources ("Resource file btl4.res v1.0.0.0 is obsolete!"), wrote a straybindings.txt/environ.ini/btl4_*.logNEXT TO THE EXE — so the player's real ones incontent\looked like they were never created — and killed the mission generation the menu launched (the child inherits the parent's cwd). Invisible for years because every launchercds tocontent\first; it became reachable once glass became the desktop default and a zero-arg launch started opening the menu. - The launcher's handoff wait must track ITS OWN generation [T2, 2026-07-27]. The front end
does not stay resident — it
CreateProcesses the mission generation and exits — so every launcher ends in a:btwaitloop that waits for the handed-off process before printing its sign-off. That loop used to polltasklist /FI "IMAGENAME eq btl4.exe", which is machine-wide: a second client, the operator's own pod, or an orphan from a crash kept the window spinning forever, which is the field report "closing the game leaves the terminal open". Fixed by snapshotting the PIDs alive before the launch and waiting only on PIDs absent from that snapshot. ⚠ Keep theif /I "%%P"=="btl4.exe"guard — parsing tasklist withtokens=2alone reads its "INFO: No tasks are running" line as a PID and spins forever with nothing running. Full investigation, including which windows exit vs merely hide:phases/phase-12-orphan-processes.md. - Why this is a BT411-only hazard [T1]. The 1995 pod shipped ONE folder —
BTL4OPT.EXEsits next toBTL4.RES/VIDEO\/GAUGE\/AUDIO\(it is still there incontent\) — and RP411/RP412 keep that shape (pack-dist.ps1copies the exe and every asset dir into one dist root). BT411's two-folder split (build\Release\btl4.exe+content\) is not a design decision:mkdist.pyzips TRACKED REPO PATHS verbatim, so the dist inherited the repo's developer layout. In the authentic single-folder shape cwd is right by construction and a double-click just works, which is why the other games never hit this. A proposal to ship the authentic shape is written up indocs/DIST_LAYOUT_PLAN.md— PROPOSED, not implemented, awaiting a decision between the authors. The cwd guard stays either way (it protects the developer tree, where the split is real and permanent). - Interactive: WASD drive; weapon groups (keyboard, task #43) 1/Space = lasers, 2 =
PPCs, 3/Ctrl = missiles; X all-stop; V cockpit/chase view. Default egg =
DEV.EGG(map=grass, time=day). Swap mech via the egg'svehicle=— ALL 18 ModelList names CERTIFIED playable (2026-07-18 vehicle sweep): the canonical 8 (avatar, bhk1/blkhawk, loki, madcat, owens, sunder, thor, vulture) + the short variants (ava1, lok1/lok2, mad1/mad2, own1, snd1, thr1, vul1); each boots a solo mission, spawns, animates, no crash. The code path is mech-agnostic. [T2]
Debug (cdb x86)
"C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe". Pattern for a faulting stack (cwd
= content): -g -c ".lines;sxe av;g;kp 24;q" with BT_ASSERT_TO_DEBUGGER=1. Debug CRT fills fresh
heap 0xCDCDCDCD (uninit) + freed 0xFEEEFEEE — invaluable for "was this ever constructed?".
BT_HEAPCHECK=1 = whole-heap validation every alloc/free (O(n²) at mission load — SLOW). To attach
to a frozen abort dialog: cdb -p <pid> -c ".lines;~*kp 30;q". [T2]
⚠ A FIELD crash stack is currently NOT symbolizable — two gaps, 2026-07-28 [T1].
BTCrashFilter (btl4main.cpp:193) catches unhandled exceptions and writes
[crash] UNHANDLED EXCEPTION code=… addr=… (btl4+0xNNNN) + an EBP-chain walk straight into the day
log — no minidump, no separate file. Its comment claims "we hold the PDB". We do not:
- No Release PDB is produced. Only
build/Debug/btl4.pdbexists and the shipped exe embeds no PDB path, sobtl4+0xNNNNfrom a tester cannot be resolved to a function. - Frame pointers are omitted. Release flags are
/O2 /Ob2 /DNDEBUGwith no/Oy-anywhere, and/O2implies/Oyon x86 — so the EBP walk is unreliable and the[crash] stack:chain is often shallow or wrong. The first line is still exact (addrcomes from the exception record, not the walk). FIX when field stacks are wanted: add/Oy-to the Release flags and/Zi+ linker/DEBUGso a PDB is generated, then ARCHIVE that PDB per build (never ship it in the zip). The session header already stampsbuild=4.11.<n> (<hash>), so an archived PDB matches a tester's log exactly.
Repo layout (bt411)
engine/MUNGA/+engine/MUNGA_L4/— the shared 2007 MUNGA engine + Win32/D3D9 HAL (carries our BT render/loader work: bgfload/L4D3D/L4VIDEO + the image codec).engine/shim/(ATL),engine/lib/(OpenAL/libsndfile),engine/rp/(RP headers the audio HAL includes).game/reconstructed/— the reconstructed BT source (the bulk).game/original/BT/+BT_L4/— surviving original BT.cpp+ all BT headers (the include path).game/fwd/— ~186 forwarding shims (#include <EXPLODE.hpp>→../../engine/MUNGA/<NAME>.h).game/btl4main.cpp— the WinMain launcher.content/— the runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI). Run cwd.reference/decomp/— the raw Ghidra pseudocode (all/part_*.c) — the source-of-truth.docs/— the detailed ledgers (incl.PROGRESS_LOG.md, the full pre-restructure CLAUDE.md).tools/— btconsole.py, disas2.py, map/res scanners.context/— this knowledge base.
Versioning (2026-07-18)
4.10 = the 1995 arcade release; 4.11 = this win32 reconstruction; dev builds =
4.11.<git commit count> + short hash, + suffix = built from an uncommitted tree
(e.g. 4.11.311 (980c9cd+)). Stamped every build by tools/btversion.cmake →
build/btversion.h (BT_VERSION_* macros); shown in the boot banner (day-log head) and
the window title. Ask a tester for their title bar, or read the ===== BT411 SESSION … build=… =====
header that starts every session inside content\<stem>_YYYYMMDD.log, to identify a build.
Cutting a tester zip: python tools/mkdist.py → dist/BT411_4.11.<n>.zip (name from
build/btversion.h; ships git-TRACKED content/ only, so working-tree junk stays out).
⚠ It reads build/CMakeCache.txt for the flavor, so configure BEFORE building: with
BT_STEAM=OFF the zip is renamed -nosteam and carries neither play_steam.bat nor
steam_api.dll — useless for a Steam playtest, and easy to ship by accident since OFF is the CMake
default (-DBT_STEAM=ON is the documented dev-checkout state). BT_EXPIRE=ON (default) gives the
14-day tester window; an expire-OFF zip is renamed -noexpire and warns. Verify by extracting the
zip somewhere clean and booting it with no repo present — that is what catches a missing runtime DLL.
Key Relationships
- Base: wintesla-port (the engine build recipe).
- Verify loop: reconstruction-method; env gates: decomp-reference §6.