MAJOR RESULT -- THE GAIT IS CORRECT, the enum was mislabeled:
LoadLocomotionClips (binary-verified slot map) proves namedClip==&animationClips[5]:
clips 6/7 = wwr/wwl (WALK cycle), 12/13 = rrr/rrl (RUN cycle -- what autodrive
uses at full throttle), 10/11 = wrr/wrl, 14/15 = rwr/rwl, 16-21 = reverse set.
The mech2.cpp "MechAnimationState" enum (0x3c-stride name table) does NOT match
the runtime clip ids -- it is the mislabel (12/13 are NOT StandToReverse).
The authored AudioStateTriggers on AnimationState (statecfg dump: states
1,2,5,8,9,10,11,14,15,16,17,20,21,22,23,26,27 -> Start) align EXACTLY with the
TRANSITION clips under the true map; the CYCLING strides (6/7, 12/13) have no
state triggers because the FootStep pulse attribute serves them -- our FootStep
reconstruction is the authentic design. DO NOT renumber the gait.
Footstep chain, fully mapped (all empirtical):
pulse -> AudioMatchOf(match=1) -> Start on the FootFall DirectPatchSource ->
DROPPED at volume 0. Volume path: an AudioControlMixer (2 inputs, never fed)
<- an AudioControlSplitter <- AudioControlSequence timeline events. The
sequences DO fire -- but only value-0 events (the tick-0 initializers); the
later (real-volume) events never land.
NEW ROOT-CAUSE CANDIDATE [T3]: audio clock unit mismatch. The renderer is
constructed with DefaultRendererRate=30 (calibrationRate: Seconds_To_Frames =
s*30) but AudioHead::Execute sets audioFrameCount = Now().ticks which advances
~550/s (measured via the [audioclock] probe) -- sequence event scheduling runs
~18x off the authored timing. Fix candidate: calibrate the audio renderer to
the actual Now().ticks rate (or drive audioFrameCount at the calibrated 30/s).
Also corrected en route: FUN_004b9550/95b8 are MechWeapon ConfigureMappables/
ChooseButton (mapper EnterConfiguration +0x38), NOT Myomers::ConnectToMover;
+0x31c there is fireImpulse. Myomers::speedEffect has NO binary writer found
beyond the ctor 1.0f (its 'mover coupling' stand-in is spurious).
Diagnostics added: [statecfg] AudioStateTrigger ctor dump, [split] splitter
forwards, [seqev] sequence timeline events, [audioclock] frame-rate probe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The audio subsystem binds AudioStateWatchers (class 28) to per-subsystem state
attrs (GeneratorState/CondenserState/ReservoirState) BY NAME. Recovered the
watcher TYPE per attribute via a MakeObjectImplementation ClassID trace.
Root cause of the subsystem-state crash: the binary's 0x54-byte subsystem alarm
(FUN_0041b9ec, reconstructed as GaugeAlarm54) IS a StateIndicator -- its "three
sub-indicators" @0x18/0x2c/0x40 are the audio/video/gauge watcher SChains, and
level@0x14 is currentState. The reconstruction had modeled that tail as an opaque
`_tail`, so the sockets were never constructed -> AddAudioWatcher AV'd on 0xCDCDCDCD.
- GaugeAlarm54: give it the three REAL, constructed SChainOf<Component*> sockets +
AddAudioWatcher/Video/Gauge; SetLevel now fires the watchers on a level CHANGE
(empty sockets = no-op, so the ~all other alarms are unaffected). levelB (weapon
LevelCountB "reset source") is left untouched -- weapons unchanged. sizeof stays
0x54 (static_assert holds -> SChainOf<Component*> is 0x14, layout exact).
- Register the state attrs -> the subsystem's own alarm (own AttributePointers[]
chained to the parent): Generator.GeneratorState->stateAlarm, Condenser.
CondenserState->condenserAlarm, Reservoir.ReservoirState->reservoirAlarm. Those
alarms are already SetLevel'd by the sim, so the state audio fires on transition.
- AudioStateWatcher guard now validates the +0x18 SOCKET (what AddAudioWatcher
touches), not the +0 vtable -- GaugeAlarm54 is non-polymorphic at +0 (raw header)
but has a real socket at +0x18.
audiostate skips 85 -> 10 (only AmmoBin AmmoState/SimulationState left). The
Logical/Scalar/Enum subsystem attrs (ReportLeak/GeneratorOn/ConfigureActivePress/
MotionState/SpeedOfTorsoHorizontal/TargetRangeExponent) stay inert (read 0, silent,
non-crashing) -- they need backing members in size-locked layouts (a polish wave).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enabling audio unlocked L4AudioRenderer::CreateEntityAudioObjects, which binds
AudioStateWatchers to entity/subsystem state attrs BY NAME (attribute-pointer
databinding) and calls AddAudioWatcher on them. The reconstruction pointed the
Mech's state attrs at the scalar read-pad `attrPad` (fine for gauges that READ a
value, fatal for a state watcher that calls a METHOD) -> AV in SChainOf::Add on
0xCDCDCDCD. Root-caused via cdb + a BT_ATTRBIND_LOG trace in AttributeWatcher.
Mech-level state audio now REAL + driven (verified: walking fires SetupPatch +
PlayNote/alSourcePlay, patches 78/80/116):
- AnimationState (0x1f) / ReplicantAnimationState (0x20): real StateIndicators
(0x21 states covering MechAnimationState 0..0x20), driven from SetBodyAnimation
so footstep/gait/transition sounds fire on animation change.
- CollisionState (0x16): real 4-state StateIndicator driven from ProcessCollision
via collisionTemporaryState (the deferred @4aa741 per-frame zero + the
part_012.c:15406-15413 contact tail, InitialHit accumulation; the 1-vs-2 Slide
split awaits the 0x240/0x244 floats still stubbed by fieldAt()).
- @0x44c CORRECTED: "ammoState (0/1 leaking/2 dry)" was a mislabel -> it is
collisionTemporaryState (never read as ammo).
Bring-up guards [T3, temporary, self-clearing] so audio-on is STABLE while the
subsystem state models are still stubs (Generators/Condensers/Myomers/Torso/
Avionics/Reservoir/ControlsMapper -- ~15 subsystems, ~40 attrs: GeneratorState,
CondenserState, ReportLeak, ...): a NULL subsystem attr redirects to an inert pad
(was a fatal Fail); an AudioStateWatcher on an unconstructed StateIndicator (null/
0xCDCDCDCD) skips instead of AV. Both pass automatically once the subsystem is
reconstructed. Those subsystem sounds stay silent until then (their audio wave).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>