Files
BT412/docs/RESOURCE_AUDIT.md
arcattackandClaude Opus 4.8 7b7d465e5e Initial commit: bt411 -- standalone Windows BattleTech (Tesla 4.10 port)
Clean, self-contained extraction of the BattleTech-specific work from the
reverse-engineering workspace -- engine + game + content + build, with nothing
from Red Planet or the raw archive dumps. Builds green (Win32) and runs the
single-player drive->animate->target->fire->damage->destroy loop out of the box.

Layout:
  engine/   MUNGA + MUNGA_L4 shared 2007 engine, carrying our BT render/loader
            work (bgfload/L4D3D/L4VIDEO: BSL bit-slice decode, LOD/ground/shadow
            models) + image codec; the minimal rp/ headers the audio HAL needs
  game/     reconstructed BT logic + surviving-original BT source + fwd shims
            + WinMain launcher
  content/  full runtime tree (BTL4.RES, VIDEO/, GAUGE/, AUDIO/, eggs, BTDPL.INI)
  docs/     format specs + reconstruction ledgers
  reference/ raw Ghidra pseudocode (recon source-of-truth) + decomp exporter
  tools/    MP console emulator + map/resource scanners

One top-level CMake builds munga_engine lib + bt410_l4 game lib + btl4.exe.
All paths relativized (186 fwd shims + ~437 CMake abs paths -> repo-relative);
DXSDK is the one external, overridable via -DDXSDK. Verified: builds to a
byte-identical 2.27MB exe and runs combat (TARGET DESTROYED, 0 crashes) against
the bundled content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 21:03:40 -05:00

5.2 KiB

BT Port — Resource-Layout Audit (resource-layout-audit workflow, 33 agents, adversarially confirmed)

STATUS: ALL 8 BUGS FIXED & VERIFIED (commits 802a7a6 A, 7de66ad B, 975a397 Gyro+Searchlight). Each layout locked with compile-time static_assert(offsetof/sizeof) guards; full build green; combat + heat-flow un-regressed. The plan below is retained as the record of what was found and done.

Verdict: 24 *__SubsystemResource structs audited → 8 confirmed bugs (all high-severity, review-upheld, none refuted), 16 clean. All 8 root to 2 broken base resources (HeatWatcher, PowerWatcher) + 2 struct-specific field defects (Gyroscope, Searchlight).

Same class as the heat-resource bug fixed earlier (§10d): a resource struct that doesn't mirror its class hierarchy reads every field from the wrong offset — SILENT garbage (non-fatal). These 6 Watcher- family subsystems (AmmoBin, Gyroscope, Searchlight, ThermalSight, Torso, HUD) currently TICK on garbage resource data. Combat is unaffected (mech/weapons/damage/heat don't depend on them) — it bites when WAVE 4-8 consumes gyro/torso motion, sensor/searchlight/thermalsight readouts, and ammo.

Raw-decomp verification (done this session — the fix is bigger than "one-liner")

  • HeatWatcher::CreateStreamedSubsystem = FUN_004aec54 chains to FUN_004ac9ec (the MechSubsystem parse, fills 0x30..0xE4 — armorByFacing/videoObjectName/alarmModel/... verified) then adds only DegradationTemperature@0xE8 + FailureTemperature@0xEC. Record ends 0xF0 (MechSubsystem + 3).
  • HeatWatcher ctor @4aeb40 chains to FUN_004ac644 (MechSubsystem-level base ctor, shared with the HeatSink ctor) and reads only res+0xe4/0xe8/0xec — NO thermal-mass/heatSinkIndex reads. So HeatWatcher is genuinely MechSubsystem-based, not a full HeatableSubsystem.
  • PowerWatcher ctor chains to FUN_004aeb40 = HeatWatcher's ctorPowerWatcher : HeatWatcher (powersub.hpp already comments "real base is HeatWatcher"). Adds MinVoltagePercent@0xF0, record ends 0xF4.
  • The reconstruction implemented BOTH classes as : public HeatableSubsystem (an approximation, with the real base in // FUN_... comments) → the resource was made HeatableSubsystem-based (0xFC) → every Watcher field slid +0x18 (HeatWatcher) / +0xC (PowerWatcher). Fixing the resource base alone will NOT compile (the ctor passes the resource to the HeatableSubsystem base ctor) → class+ctor+parser must change together.

Fix plan (bases before children; each verified by offset logging (char*)&res->f - (char*)res)

  1. Root cause A — HeatWatcher → MechSubsystem (heatfamily_reslice.hpp/.cpp):
    • class HeatWatcher : public MechSubsystem (was HeatableSubsystem)
    • resource HeatWatcher__SubsystemResource : public MechSubsystem__SubsystemResource (was HeatableSubsystem::SubsystemResource); keep int watchedSubsystem;(0xE4) Scalar degradationTemperature;(0xE8) Scalar failureTemperature;(0xEC) → ends 0xF0
    • ctor chains MechSubsystem(...); parser chains the MechSubsystem parse (FUN_004ac9ec)
    • de-shadow any fields HeatWatcher inherited from HeatableSubsystem it no longer has
    • Fixes HeatWatcher + AmmoBin (child; fields realign to 0xF0/0xF4/0xF8/0xFC/0x100, size 0x104). Target stamp: HeatWatcher 0xF0, AmmoBin 0x104.
  2. Root cause B — PowerWatcher → HeatWatcher (powersub.hpp/.cpp; depends on A):
    • class PowerWatcher : public HeatWatcher; resource ... : public HeatWatcher::SubsystemResource, keep Scalar minVoltagePercent;(0xF0) → ends 0xF4 (do NOT pad — would break the 0xF4 stamp)
    • powersub.hpp must #include the HeatWatcher resource (heatfamily_reslice.hpp)
    • ctor chains HeatWatcher(...); parser chains HeatWatcher parse (FUN_004aec54, WatchedSubsystem/Degradation/Failure keys)
    • Fixes PowerWatcher + HUD + ThermalSight + Torso (children realign, no child edits). Target stamps: PowerWatcher 0xF4, Torso 0x158.
  3. Gyroscope (gyro.hpp; after B): add Scalar field_f4;(0xF4, ctor FUN_004b3778 reads it, init -1.0f — best-effort name) BEFORE exageration(0xF8). Then fields align through 0x21C.
  4. Searchlight (searchlight.hpp:75 + searchlight.cpp:96; independent, after B): delete int segmentDefault; (compiles OOB at sizeof(PowerWatcher)); the binary reads resource+0x28 = inherited segmentIndex → change searchlight.cpp:96 to commandedOn = subsystem_resource->segmentIndex;.

Clean / no action (16): Emitter, HeatableSubsystem (correctly 0xFC — do NOT touch; HeatSink/Generator/ PoweredSubsystem/Reservoir/Condenser depend on it), Condenser, Reservoir, MechSubsystem, MechTech, MechWeapon, SubsystemMessageManager, MissileLauncher, MissileThruster, Myomers, PoweredSubsystem, Generator, ProjectileWeapon, Seeker, Sensor.

Rejected proposal: aliasing HeatWatcher fields onto the 0xFC HeatableSubsystem base via accessors/union — leaves sizeof=0xFC, contradicts the 0xF0 stamp, cascades the wrong size into PowerWatcher. Use the re-base.

Verify after each step: offset logging vs stamps (HeatWatcher 0xF0, PowerWatcher 0xF4, AmmoBin 0x104, Torso 0x158, Gyroscope ends 0x21C); build green; combat/heat/torso un-regressed.