Death-state sequence: collapse + subsystem shutdown + freeze (wreck stays)
Reconstructs the mech DEATH state machine — the un-exported master-perf death
branch (region 0x4a9770-0x4ab188) — from its EXPORTED consumers + the RP
VTV::DeathShutdown analog, wired into the active path (the bring-up drive
override bypasses the authentic Simulate where this normally lives).
Mech::UpdateDeathState() + IsMechDestroyed() (mech4.cpp), called for every mech
early in PerformAndWatch: on a vital kill (graphicAlarm >= 9, raised by the
damage side) it
1. sets movementMode = 5 -> the collapse clip's one-shot latch in
AdvanceBodyAnimation (fall direction 5-8 is un-exported -> 5 [T3]);
2. loops the roster calling Subsystem::DeathShutdown(1) (RP VTV::DeathShutdown
analog; the base is a no-op virtual, overrides act -- a SHUTDOWN not a
teardown, so it frees nothing and never removes the entity: the wreck STAYS);
3. next frame settles to movementMode = 9 -> IsDisabled -> locomotion frozen.
The drive's lone `movementMode = 1` write (mech4.cpp ~1309) is guarded on
!IsMechDestroyed so the death state is not clobbered back to a live gait.
Runtime-verified (forced kill, BT_DEATH_LOG=1):
[death] mech destroyed -> collapse + subsystem shutdown (wreck stays)
[death] mech settled -> disabled (IsDisabled=1, frozen wreck)
mech frozen in place, subsystem tick + renderer keep running, no crash, wreck
stays.
Also records the full death-sequence decomp map in combat-damage.md (the
exported consumers: AdvanceLeg/BodyAnimation collapse latch, IsDestroyed, the
MechDeathHandler effect engine; and the un-exported orchestration gap).
Deferred (honest): the visible collapse ANIMATION latch through the active path
(gait SM shows state=0 post-death; spawned mechs don't advance body anim);
MechDeathHandler (FUN_0042a984/FUN_0042aa2c -- the exported per-subsystem
destroyed-skin + explosion engine, still a stub) for death explosions; the
whole-mech DeathSplash radius damage (un-exported).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2af401eef8
commit
a9467481c5
@@ -170,6 +170,51 @@ thing: an EXPLICIT `JointedMover::~JointedMover()` in the reconstructed `~Mech`
|
||||
chain TWICE (the dtor-epilogue rule, [[reconstruction-gotchas]] §7) → a double-free of `collisionLists`
|
||||
+ the segment table. Removing it fixed BOTH the death-row crash AND app-exit crashes. [T2]
|
||||
|
||||
## 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.
|
||||
- `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]`) —
|
||||
the **per-subsystem destroyed-skin + explosion engine**. Each tick walks the damage subsystems
|
||||
(mech+0x120[mech+0x11c]); as a subsystem's damage crosses a descriptor-table threshold (`FUN_0042a5f4`
|
||||
detects the crossing, `FUN_0042a664` looks up the state) it applies the `GraphicState` ("Destroyed" = enum 1,
|
||||
parsed @part_003.c:9099) to the subsystem graphic (`sub+0xd4`) AND dispatches an effect-creation message
|
||||
(`FUN_0043663c`→`FUN_004364e4` to the effect mgr `app+0x38`). **Fully exported but a no-op STUB** in the
|
||||
recon (mech.cpp:221). Gamedata `DeathSplashDamage`/`Radius`/`Effect` load into a descriptor @ +0x74/78/7c
|
||||
(`FUN_004a2da8`). Destroyed-skin variant set (`destroyed`, `destroyeds`…`destroyedd`) @part_003.c:5720.
|
||||
**NOT EXPORTED (master-perf gap 0x4a9770-0x4ab188 + the un-exported TakeDamage) [T4]:**
|
||||
- The `movementMode` WRITER (sets 5-8 then 2/9 on death) + the fall-direction (5/6/7/8) selection.
|
||||
- The `Mech::DeathShutdown` roster loop (RP `VTV::DeathShutdown` analog — loop subsystems calling the
|
||||
`Subsystem::DeathShutdown(int)` vtable slot; the base is an empty virtual, SUBSYSTM.h:166).
|
||||
- The whole-mech `DeathEffect`/`DeathSplash` radius dispatch + the `graphicAlarm.SetLevel(9)` call site (the
|
||||
recon already raises the alarm in mechdmg.cpp:426/586).
|
||||
**The problem:** the orchestration lives in the per-frame Simulate that the **bring-up drive override
|
||||
bypasses** (btstubs.cpp:116-119), so the death consumer must be reconstructed from its exported consumers +
|
||||
the RP analog and wired into the active path (mech4.cpp `PerformAndWatch`).
|
||||
|
||||
### ✅ Death-STATE core DONE (2026-07-08) [T2]
|
||||
`Mech::UpdateDeathState()` + `IsMechDestroyed()` (mech4.cpp) reconstruct the un-exported death branch from
|
||||
its exported consumers + the RP analog, called for EVERY mech early in `PerformAndWatch`: on `graphicAlarm
|
||||
>= 9` it (1) sets `movementMode = 5` → the collapse clip's latch in `AdvanceBodyAnimation` (fall direction
|
||||
5-8 un-exported → 5 [T3]); (2) loops the roster calling `Subsystem::DeathShutdown(1)` (RP `VTV::DeathShutdown`
|
||||
analog; base is a no-op virtual, a SHUTDOWN not teardown → safe, wreck STAYS); (3) next frame settles to
|
||||
`movementMode = 9` → `IsDisabled` → locomotion frozen. The drive's lone `movementMode = 1` write (mech4.cpp
|
||||
~1309) is guarded on `!IsMechDestroyed` so the death state isn't clobbered. **Runtime-verified** (forced kill):
|
||||
`[death] … collapse + subsystem shutdown` → `[death] … settled -> disabled (IsDisabled=1, frozen wreck)`,
|
||||
mech frozen in place (pos unchanged), subsystem tick + renderer keep running, **no crash, wreck stays**.
|
||||
Env gate `BT_DEATH_LOG=1`.
|
||||
**Deferred (honest):** (a) the visible **collapse ANIMATION** may not latch through the active path — the
|
||||
gait SM showed `state=0` post-death (the clip trigger via `AdvanceBodyAnimation` needs the death `movementMode`
|
||||
to reach its latch; and spawned NON-player mechs don't advance their body anim at all). (b) **`MechDeathHandler`**
|
||||
(the exported per-subsystem destroyed-skin + explosion engine, `FUN_0042a984`/`FUN_0042aa2c`) is still the
|
||||
no-op stub → no death explosions/skin-swaps yet. (c) the whole-mech **DeathSplash** radius damage (un-exported).
|
||||
|
||||
## Key Relationships
|
||||
- Weapons/roster: [[subsystems]]. Aim source: [[locomotion]] (drive/facing). Effects: [[rendering]].
|
||||
- P5 forensics: `docs/HARD_PROBLEMS.md`. Data: [[decomp-reference]] §4-5.
|
||||
|
||||
@@ -98,8 +98,15 @@ authentic path scoped.
|
||||
container ctors + the lookup as a `BTCylinderResolveZone` bridge (ground the cell zone-dict stream format vs
|
||||
the real .RES bytes); 6d register the handler + verify. Feasible; the only open risk is the cell stream format.
|
||||
The earlier-cited `FUN_004a0230`/`FUN_0049ed0c` don't exist.
|
||||
- Death: reconstruct `DeathShutdown` (RP `VTV::DeathShutdown` analog) + collapse anim + destroyed
|
||||
skin. The wreck-stays behavior we ship is faithful; do NOT issue DestroyEntityMessage on death.
|
||||
- **Death sequence — CORE DONE 2026-07-08 [T2]; effects+anim deferred.** `Mech::UpdateDeathState()`
|
||||
(mech4.cpp) reconstructs the death STATE machine (collapse `movementMode` → RP `VTV::DeathShutdown`
|
||||
subsystem loop → settle to disabled/frozen; wreck stays, no crash) — runtime-verified (`BT_DEATH_LOG`).
|
||||
Full map + citations in [[combat-damage]] "Death SEQUENCE". **Remaining** (all in the un-exported master-perf
|
||||
gap 0x4a9770-0x4ab188 OR a big exported reconstruction): (a) the visible **collapse ANIMATION** latch
|
||||
through the active path (+ spawned mechs don't advance body anim); (b) reconstruct **`MechDeathHandler`**
|
||||
(`FUN_0042a984`/`FUN_0042aa2c`) — the EXPORTED per-subsystem destroyed-skin + explosion engine (still a
|
||||
stub, mech.cpp:221) — the biggest visible payoff; (c) the whole-mech **DeathSplash** radius damage. Do NOT
|
||||
issue DestroyEntityMessage on death.
|
||||
|
||||
## Multiplayer (Phase 7 / P6)
|
||||
- Cross-pod COMBAT (target a replicant + route damage to the owning master).
|
||||
|
||||
@@ -667,6 +667,10 @@ protected:
|
||||
Scalar AdvanceBodyAnimationAirborne(Scalar time_slice, int loop);
|
||||
Scalar AdvanceLegAnimationAirborne(Scalar time_slice);
|
||||
Logical IsDisabled(); // FUN_0049fb54
|
||||
// --- death sequence (the un-exported master-perf death branch, rebuilt
|
||||
// from its exported consumers + the RP VTV::DeathShutdown analog) ---
|
||||
Logical IsMechDestroyed(); // damage-side death flag: graphicAlarm level >= 9
|
||||
void UpdateDeathState(); // collapse movementMode + subsystem DeathShutdown + freeze
|
||||
|
||||
// --- clip loaders (mech3) -------------------------------------------
|
||||
ResourceDescription::ResourceID *
|
||||
|
||||
@@ -843,6 +843,61 @@ static void
|
||||
}
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// Death sequence -- the un-exported master-perf death branch (region
|
||||
// 0x4a9770-0x4ab188), reconstructed from its EXPORTED consumers + the RP
|
||||
// VTV::DeathShutdown analog. See context/combat-damage.md "Death SEQUENCE".
|
||||
//
|
||||
// On a vital kill the damage side raises graphicAlarm to 9 (mechdmg.cpp:426/586).
|
||||
// The authentic per-frame death transition (movementMode 5-8 = collapse -> 2/9 =
|
||||
// disabled) lives in the Simulate the bring-up drive bypasses, so it is run here.
|
||||
//###########################################################################
|
||||
Logical
|
||||
Mech::IsMechDestroyed()
|
||||
{
|
||||
// graphicAlarm level 9 = a vital zone / leg gone -> going down (mechdmg.cpp).
|
||||
return graphicAlarm.GetLevel() >= 9 ? True : False;
|
||||
}
|
||||
|
||||
void
|
||||
Mech::UpdateDeathState()
|
||||
{
|
||||
if (!IsMechDestroyed()) // alive
|
||||
return;
|
||||
if (movementMode == 9) // already settled (disabled/frozen)
|
||||
return;
|
||||
|
||||
if (movementMode < 5 || movementMode > 8) // FIRST destroyed frame
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Mech::PerformAndWatch(const Time& till, MemoryStream *update_stream)
|
||||
{
|
||||
@@ -859,6 +914,12 @@ void
|
||||
const Logical isPlayerMech =
|
||||
(application != 0 && (Entity *)this == application->GetViewpointEntity());
|
||||
|
||||
// DEATH consumer -- runs for EVERY mech (player + spawned targets). On a
|
||||
// vital kill it collapses + shuts the subsystems down + settles to disabled;
|
||||
// the drive's movementMode write below is guarded on !IsMechDestroyed so a
|
||||
// dead mech keeps its death state instead of reverting to a live gait.
|
||||
UpdateDeathState();
|
||||
|
||||
// WAVE 7 Phase B: advance/render/impact the flying projectiles once per frame (viewpoint).
|
||||
if (isPlayerMech && dt > 0.0001f)
|
||||
BTUpdateProjectiles(dt);
|
||||
@@ -1306,7 +1367,8 @@ void
|
||||
}
|
||||
if (!(forwardCycleRate >= 25.0f && forwardCycleRate < 10000.0f))
|
||||
forwardCycleRate = 25.0f;
|
||||
movementMode = 1; // ground, non-death, non-airborne
|
||||
if (!IsMechDestroyed()) // a dead mech keeps its death movementMode
|
||||
movementMode = 1; // ground, non-death, non-airborne
|
||||
// reverseSpeedMax2@0x7a0 is the run-cycle bodyCycleSpeed CLAMP (AdvanceBody
|
||||
// Animation case 12/13); LoadLocomotionClips does not set it -> it reads
|
||||
// 0xCDCDCDCD (-4.3e8) and the clamp clobbers bodyCycleSpeed -> the run cycle
|
||||
|
||||
Reference in New Issue
Block a user