Files
BT411/context/build-and-run.md
T
Joe DiPrimaandClaude Fable 5 bc27313f20 sweep the knowledge base up to this session: log day, crash symbolization, a misnamed accessor
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>
2026-07-28 19:23:29 -05:00

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
wintesla-port
decomp-reference
reconstruction-method
BTL4OPT
cdb
BTL4.RES
EGG
WinTesla

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.

# 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 at C:\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::CreateSubsystemStream references void*-signature CreateStreamedSubsystem for every class; never called at runtime). ⚠ /FORCE also 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 a DPLRenderer/d3d_OBJECT class 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 resolves BTL4.RES, VIDEO\, BTDPL.INI, eggs relative to cwd (the loadTables gotcha: L4VIDEO.cpp:849 fopen("VIDEO\\REPLACEMATS.tbl") is relative + unchecked → fread on NULL if cwd is wrong). Logs to content\<stem>_YYYYMMDD.log (or BT_LOG=<file>). [T2] Log naming (2026-07-28): stem = the launcher (solo/steam/join/joyconfig, else btl4), chosen from BT_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 and lastrun_<stem>.txt each call GetLocalTime separately and carry true local time. ⚠ BT_LOG=<file> is used VERBATIM and truncates unless BT_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 no BTL4.RES it probes from the exe's own directory (..\..\content for the shipped layout, plus flattened / in-content cases) and SetCurrentDirectorys there, before the log file opens. The boot line says so when it had to look. ⚠ The landmine it defuses (field report): a bare btl4.exe launch from build\Release found no resources ("Resource file btl4.res v1.0.0.0 is obsolete!"), wrote a stray bindings.txt/environ.ini/btl4_*.log NEXT TO THE EXE — so the player's real ones in content\ 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 launcher cds to content\ 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 :btwait loop that waits for the handed-off process before printing its sign-off. That loop used to poll tasklist /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 the if /I "%%P"=="btl4.exe" guard — parsing tasklist with tokens=2 alone 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.EXE sits next to BTL4.RES/VIDEO\/GAUGE\/AUDIO\ (it is still there in content\) — and RP411/RP412 keep that shape (pack-dist.ps1 copies 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.py zips 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 in docs/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's vehicle=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:

  1. No Release PDB is produced. Only build/Debug/btl4.pdb exists and the shipped exe embeds no PDB path, so btl4+0xNNNN from a tester cannot be resolved to a function.
  2. Frame pointers are omitted. Release flags are /O2 /Ob2 /DNDEBUG with no /Oy- anywhere, and /O2 implies /Oy on x86 — so the EBP walk is unreliable and the [crash] stack: chain is often shallow or wrong. The first line is still exact (addr comes from the exception record, not the walk). FIX when field stacks are wanted: add /Oy- to the Release flags and /Zi + linker /DEBUG so a PDB is generated, then ARCHIVE that PDB per build (never ship it in the zip). The session header already stamps build=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.cmakebuild/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.pydist/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