From d9254736ab87adab3951163b5c7c6de4a7eb322a Mon Sep 17 00:00:00 2001 From: arcattack Date: Wed, 8 Jul 2026 14:50:12 -0500 Subject: [PATCH] Death is a FREEZE, not a collapse -- fall-latch vestige proven; band-effect impact frames Task #32 (death collapse animation) resolved by decomp evidence -- BT 4.11 has NO collapse animation; the movementMode 5-8 fall latch (clips 0x1c-0x1f) is an engine-lineage vestige: (1) the clip-table loader FUN_004a80d4 fills slots 0x00-0x1b + 0x20 (bmp knockdown) and returns -- slots 0x1c-0x1f are never written; (2) mech+0x63c..0x648 appear in NO exported function; (3) no fall clip exists in the shipped 27-clip set; (4) firing the latch would bind resource id 0 -- a StaticAudioStream -- as keyframes. Authentic death modes are the FREEZE modes (IsDestroyed == mode 2||9), so UpdateDeathState now settles straight to 9 (was a [T3] mode-5 guess that would trip the garbage latch in the binary). The death READ = freeze + dnboom + ddthsmk smoke plume + destroyed skins + shutdown; the wreck stands. Also: damage-band effects orient toward the ATTACKER (impact frame) via lastInflictingID -- which was declared but never written (recon gap); now maintained by Mech::TakeDamageMessageHandler, unblocking the DamageZone LOD same-attacker redirect too. KB corrected + proofs recorded (combat-damage "Death SEQUENCE", open-questions). Co-Authored-By: Claude Fable 5 --- context/combat-damage.md | 29 +++++++++++-------- context/open-questions.md | 8 ++++-- game/reconstructed/mech4.cpp | 54 ++++++++++++++++++------------------ scratchpad/res0.py | 12 ++++++++ 4 files changed, 61 insertions(+), 42 deletions(-) create mode 100644 scratchpad/res0.py diff --git a/context/combat-damage.md b/context/combat-damage.md index 5d78377..b491e72 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -186,18 +186,23 @@ chain TWICE (the dtor-epilogue rule, [[reconstruction-gotchas]] §7) → a doubl ## Death SEQUENCE reconstruction — the map (2026-07-08) Full decomp map of the death path. Two tiers: **EXPORTED (reconstructable) [T1 read]:** -- `Mech::AdvanceLegAnimation` (`FUN_004a5028`) / `AdvanceBodyAnimation` (`FUN_004a5678`) — the **collapse-anim - latch**: `movementMode` (mech+0x40) **5/6/7/8** = the four fall directions → clips **0x1c-0x1f** (via - `SetLegAnimation`/`SetBodyAnimation` = `FUN_004a7fc4`/`FUN_004a800c`), one-shot via `deathAnimationLatched` - (mech+0x650). Already reconstructed (mech2.cpp:550-557/817-822). The active drive path DOES call - `AdvanceBodyAnimation` (mech4.cpp:317) → reachable. - ⚠ **CONFLICT with the shipped clip inventory (2026-07-08) [T4 until re-read]:** BTL4.RES holds exactly - **27 named clip codes per mech** (`swr wwl wwr wsl wsr wrl wrr rrl rrr rwl rwr bmp bbl bbr bsl bsr sbl - sbr ggl ggr gsl gsr wgl wgr sqd squ trn`, + `*i` inside-skeleton variants) — NO explicit death/fall clip - name, and indexes 0x1c-0x1f (28-31) exceed a 27-entry table unless the table interleaves the i-variants - or carries non-resource entries. The "clips 0x1c-0x1f" claim needs the FUN_004a5028 latch region re-read - against the real StandingAnimation table before the collapse is built (task #32). Candidate authentic - visuals: the `bmp` (knockdown) family or `sqd` (squat/crumple). +- `Mech::AdvanceLegAnimation` (`FUN_004a5028`) / `AdvanceBodyAnimation` (`FUN_004a5678`) — the **fall-anim + latch**: `movementMode` (mech+0x40) **5/6/7/8** = the four fall directions → animation-TABLE slots + **0x1c-0x1f** (`table[i]` at `mech+0x5cc+i*4`; `SetLegAnimation`/`SetBodyAnimation` = + `FUN_004a7fc4`/`FUN_004a800c` @part_012.c:13557/13570), one-shot via `deathAnimationLatched` (mech+0x650). + Already reconstructed (mech2.cpp:550-557/817-822). + ✅ **RESOLVED (2026-07-08, task #32): the fall latch is a VESTIGE — BT 4.11 has NO collapse animation.** + [T1] Four convergent proofs: (1) the clip-table loader `FUN_004a80d4` (@part_012.c:13604) fills slots + 0x00-0x1b + 0x20 (the `bmp` knockdown) by name and RETURNS — slots 0x1c-0x1f are never written; (2) the + offsets `mech+0x63c..0x648` appear in NO exported function at all; (3) BTL4.RES ships **27 clip codes + per mech** (`swr wwl wwr wsl wsr wrl wrr rrl rrr rwl rwr bmp bbl bbr bsl bsr sbl sbr ggl ggr gsl gsr + wgl wgr sqd squ trn` + `*i` variants) — no death/fall clip; (4) firing the latch would bind the + zero-initialised slot = **resource id 0 = a StaticAudioStream** as keyframes (garbage) — dead code in a + shipped arcade build. The authentic BT death modes are the FREEZE modes: `IsDestroyed()` == + (`movementMode` 2 || 9), locomotion zeroed by `FUN_004ab1c8`. **The death READ = instant freeze + the + dnboom explosion (psfx 7) + the ddthsmk smoke plume (psfx 1) + destroyed skins + subsystem shutdown — + the wreck stands.** `UpdateDeathState` (mech4.cpp) settles straight to 9 (never 5-8, which would trip + the garbage latch). If period pod footage ever shows a mech falling, that's NEW evidence — reopen then. - `Mech::IsDestroyed()` (`FUN_0049fb54`) = `movementMode==2||9` (reconstructed as `IsDisabled`, btstubs.cpp); `FUN_004ab1c8` freezes locomotion (zeros mech+0x298) when destroyed. - **`MechDeathHandler`** (ctor `FUN_0042a984` + Performance `FUN_0042aa2c`, cached mech+0x850 / `mech[0x214]`) — diff --git a/context/open-questions.md b/context/open-questions.md index fb5e4c4..22eac06 100644 --- a/context/open-questions.md +++ b/context/open-questions.md @@ -107,9 +107,11 @@ authentic path scoped. Full map + citations in [[combat-damage]] "Death SEQUENCE". ✅ (b) `MechDeathHandler` DONE (mechdmg.cpp) + ✅ the render-side **RemakeEntity** destroyed-mesh swap DONE 2026-07-08 (btl4vid.cpp `RemakeEntityRenderables` + `BTRemakeMechModel` bridge; in-place `SetDrawObj` swap — the tree dtor does - NOT cascade so never rebuild). **Remaining**: (a) the visible **collapse ANIMATION** latch - through the active path (+ spawned mechs don't advance body anim); (c) the whole-mech **DeathSplash** - radius damage. Do NOT issue DestroyEntityMessage on death. + NOT cascade so never rebuild). ✅ (a) **RESOLVED — no collapse animation exists in BT 4.11** [T1]: the + fall latch (modes 5-8 → table slots 0x1c-0x1f) is a vestige — no loader fills the slots, no fall clip + ships, and firing it would bind resource 0 (an AUDIO stream) as keyframes. Authentic death = FREEZE + (mode 9) + dnboom + ddthsmk plume + destroyed skins ([[combat-damage]] "Death SEQUENCE" for the proofs). + **Remaining**: (c) the whole-mech **DeathSplash** radius damage. Do NOT issue DestroyEntityMessage on death. - **Critical-subsystem plugs UNBOUND (43 skips/mech) [T3].** `MechCriticalSubsystem::subsystemPlug` never gets WIRED to its live subsystem (the ctor's Resolve() at mechdmg.cpp:276 only READS; the binding write is elsewhere/unreconstructed), so `SendSubsystemDamage` skips every entry via the diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index bd14732..87b4be1 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -950,35 +950,35 @@ void if (movementMode == 9) // already settled (disabled/frozen) return; - if (movementMode < 5 || movementMode > 8) // FIRST destroyed frame + // + // DEATH = FREEZE, not collapse (decomp-verified 2026-07-08, task #32). + // The fall modes 5-8 latch leg/body clips from animation-table slots + // 0x1c-0x1f (FUN_004a5028) -- but NO loader anywhere in the binary fills + // those slots (mech+0x63c..0x648 appear in no exported function; the table + // loader FUN_004a80d4 stops at the knockdown slot 0x20), NO fall clip + // exists in the shipped 27-clip set, and firing the latch would bind + // resource id 0 -- a StaticAudioStream -- as keyframes (garbage). The + // fall modes are engine-lineage vestiges; BT's death modes are the FREEZE + // modes: IsDestroyed() == (movementMode 2 || 9), and FUN_004ab1c8 zeroes + // locomotion for them. So: shut the subsystems down and settle straight + // to 9 (disabled/frozen wreck) -- never 5-8. The death READ comes from + // the effect layer (dnboom + the ddthsmk plume) + the destroyed skins. + // + // RP VTV::DeathShutdown analog: shut every subsystem down. The base + // Subsystem::DeathShutdown is an empty virtual (SUBSYSTM.h); overrides + // act. This is a SHUTDOWN, not teardown -- it frees nothing and never + // removes the entity, so it is safe (the wreck STAYS). + for (int i = 0; i < GetSubsystemCount(); ++i) { - // Collapse: a fall movementMode fires the one-shot death clip via - // AdvanceBodyAnimation's latch (fall DIRECTION 5-8 is un-exported -> 5). - movementMode = 5; // [T3] direction not determinable from the export - // RP VTV::DeathShutdown analog: shut every subsystem down. The base - // Subsystem::DeathShutdown is an empty virtual (SUBSYSTM.h); overrides - // act. This is a SHUTDOWN, not teardown -- it frees nothing and never - // removes the entity, so it is safe (the wreck STAYS). - for (int i = 0; i < GetSubsystemCount(); ++i) - { - Subsystem *s = GetSubsystem(i); - if (s != 0) - s->DeathShutdown(1); - } - if (getenv("BT_DEATH_LOG")) - DEBUG_STREAM << "[death] mech " << GetEntityID() - << " destroyed -> collapse + subsystem shutdown (wreck stays)\n" << std::flush; - } - else // SECOND frame (movementMode 5-8): settle to disabled - { - // The collapse clip (latched frame 1 if the anim path ran) plays out via - // the controller after this; movementMode 9 => IsDisabled => locomotion - // frozen regardless of whether the clip latched. - movementMode = 9; - if (getenv("BT_DEATH_LOG")) - DEBUG_STREAM << "[death] mech " << GetEntityID() - << " settled -> disabled (IsDisabled=" << (int)IsDisabled() << ", frozen wreck)\n" << std::flush; + Subsystem *s = GetSubsystem(i); + if (s != 0) + s->DeathShutdown(1); } + movementMode = 9; // disabled: IsDisabled() true, locomotion frozen + if (getenv("BT_DEATH_LOG")) + DEBUG_STREAM << "[death] mech " << GetEntityID() + << " destroyed -> subsystem shutdown + frozen wreck (IsDisabled=" + << (int)IsDisabled() << ")\n" << std::flush; } void diff --git a/scratchpad/res0.py b/scratchpad/res0.py new file mode 100644 index 0000000..15fa281 --- /dev/null +++ b/scratchpad/res0.py @@ -0,0 +1,12 @@ +import struct +data = open(r'C:\git\bt411\content\BTL4.RES', 'rb').read() +labOnly, maxID = struct.unpack_from(' id=%d type=%d name=%r' % (i, rid, rt, nm))