#94: replicant un-wreck now rides the DEATH-STATE EXIT edge -- the zone
falling-edge latch was blind to zone-less (collision) deaths The peer wreck lifecycle listened to two different signals: wreck ON came from the replicated death explosion (unconditional), wreck OFF from a damage-zone falling edge (wasWrecked latch, armed only when a zone crossed >= 1.0 on the observer). The #83 collision damage prices as internal rattle and never moves a zone, so a fresh mech killed by ramming could NEVER un-wreck on peers -- the live respawned mech drove around wearing the hulk (night-7 field report: SAURON's Loki, screenshotted by both observers at the exact minute the log analysis places the ram death). MechDeathHandler::Tick now tracks prevMode and fires the rebuild + warp when the replicated MovementMode leaves the death modes {2,9} -- only Mech::Reset ever does that, and the state rides every record header, so the trigger is cause-agnostic. The zone falling edge just refreshes the cache. Side effect fixed for free: the peer respawn warp vortex now plays for zone-less deaths too (same gate). Bench (scratchpad/night7/mp_zoneless.sh + the new BT_SELF_DAMAGE_TYPE=collision harness option, which dispatches type-0 through the real TakeDamage divert): 14/14 zone-less deaths un-wrecked (mode 9->1) with zero zone rises on the observer; explosive regression leg 12/12 with zone destruction active. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019Zh7PTkFy4KwTzVighLR9J
This commit is contained in:
co-authored by
Claude Fable 5
parent
e91d447405
commit
054c3f2eee
@@ -5786,6 +5786,15 @@ void
|
||||
++s_sdTicks;
|
||||
Damage dmg;
|
||||
dmg.damageType = Damage::ExplosiveDamageType;
|
||||
// BT_SELF_DAMAGE_TYPE=collision: dispatch as type-0 instead, so
|
||||
// the TakeDamage hub's collision divert prices it as INTERNAL
|
||||
// rattle (armor/zones never move) -- the deterministic bench for
|
||||
// a ZONE-LESS death (#94: the field ram-kill, without the ram).
|
||||
{
|
||||
const char *ty = getenv("BT_SELF_DAMAGE_TYPE");
|
||||
if (ty != 0 && !stricmp(ty, "collision"))
|
||||
dmg.damageType = Damage::CollisionDamageType;
|
||||
}
|
||||
float want = (float)atof(getenv("BT_SELF_DAMAGE"));
|
||||
dmg.damageAmount = (want > 0.0f) ? want : 20.0f;
|
||||
dmg.burstCount = 1;
|
||||
|
||||
@@ -1149,7 +1149,7 @@ extern void BTSpawnDamageEffect(Mech *mech, int effect_resource, int segment_ind
|
||||
extern void BTRemakeMechModel(Entity *entity);
|
||||
|
||||
MechDeathHandler::MechDeathHandler(Mech *mech) // @0042a984
|
||||
: owner(mech), wasWrecked(0)
|
||||
: owner(mech), prevMode(0)
|
||||
{
|
||||
// per-zone last-damage cache, zeroed (binary this[0x10], size mech+0x11c).
|
||||
int count = (mech != 0 && mech->damageZoneCount > 0) ? mech->damageZoneCount : 0;
|
||||
@@ -1172,6 +1172,40 @@ void
|
||||
if (owner == 0)
|
||||
return;
|
||||
|
||||
// REPLICANT UN-WRECK (#94): fire on the DEATH-STATE EXIT edge of the
|
||||
// replicated SimulationState dial (MovementMode 9/2 are the death modes;
|
||||
// they never revert except through Mech::Reset, whose SetMovementMode(0)
|
||||
// + ForceUpdate(0x1f) reaches the peer in every record header). This is
|
||||
// the cause-agnostic respawn signal: the previous trigger inferred the
|
||||
// respawn from a damage-zone falling edge, which a COLLISION death never
|
||||
// produces (type-0 damage prices as internal rattle, never armor -- the
|
||||
// zones of a fresh mech killed by ramming sit at 0 for its whole life),
|
||||
// so the peer's wreck could never un-wreck: the live respawned mech drove
|
||||
// around wearing the hulk (night-7 field report, SAURON's Loki). Gated
|
||||
// to ReplicantInstance: the master un-wrecks locally in Mech::Reset.
|
||||
{
|
||||
const int mode = (int)owner->MovementMode();
|
||||
const int oldMode = prevMode;
|
||||
const bool nowDead = (mode == 2 || mode == 9);
|
||||
const bool prevDead = (oldMode == 2 || oldMode == 9);
|
||||
prevMode = mode;
|
||||
if (prevDead && !nowDead
|
||||
&& owner->GetInstance() == Entity::ReplicantInstance)
|
||||
{
|
||||
extern void BTRebuildMechModel(Entity *entity);
|
||||
extern void BTStartWarpEffect(float x, float y, float z);
|
||||
BTRebuildMechModel((Entity *)owner);
|
||||
BTStartWarpEffect((float)owner->localOrigin.linearPosition.x,
|
||||
(float)owner->localOrigin.linearPosition.y,
|
||||
(float)owner->localOrigin.linearPosition.z);
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[respawn] replicant un-wrecked + warp (mode "
|
||||
<< oldMode << "->" << mode << ") at ("
|
||||
<< owner->localOrigin.linearPosition.x << ","
|
||||
<< owner->localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
int n = owner->damageZoneCount;
|
||||
for (int i = 0; i < n && i < (int)lastLevel.size(); ++i)
|
||||
{
|
||||
@@ -1183,35 +1217,13 @@ void
|
||||
Scalar prev = lastLevel[i];
|
||||
|
||||
// FALLING edge = the master's Mech::Reset healed + re-broadcast this zone
|
||||
// (ForceUpdate(DamageZoneUpdateModelFlag)). The rising-damage swap below is
|
||||
// one-way; on a REPLICANT observer, reverse it here -- drop the destroyed
|
||||
// segment meshes (BTRebuildMechModel) and play the recovery warp -- so the
|
||||
// peer's wreck un-wrecks + warps back on the observer's screen. Gated to
|
||||
// ReplicantInstance: the master already un-wrecks + warps locally in
|
||||
// Mech::Reset (mech4.cpp), so it must NOT double-fire here. The authentic
|
||||
// engine drives this off the type-0 graphic-state transition in
|
||||
// Mech::ReadUpdateRecord (mech.cpp case 0 -> ResetPose); our port has no
|
||||
// ported RemakeEntity state, so we react to the replicated zone level.
|
||||
// (ForceUpdate(DamageZoneUpdateModelFlag)). Just refresh the cache: the
|
||||
// respawn un-wreck no longer rides this edge -- it fires from the
|
||||
// death-state exit watcher above, which also covers deaths that never
|
||||
// moved a zone (#94).
|
||||
if (level < prev)
|
||||
{
|
||||
lastLevel[i] = level;
|
||||
if (wasWrecked && prev >= 1.0f && level < 1.0f)
|
||||
{
|
||||
wasWrecked = 0; // one un-wreck per death->respawn
|
||||
if (owner->GetInstance() == Entity::ReplicantInstance)
|
||||
{
|
||||
extern void BTRebuildMechModel(Entity *entity);
|
||||
extern void BTStartWarpEffect(float x, float y, float z);
|
||||
BTRebuildMechModel((Entity *)owner);
|
||||
BTStartWarpEffect((float)owner->localOrigin.linearPosition.x,
|
||||
(float)owner->localOrigin.linearPosition.y,
|
||||
(float)owner->localOrigin.linearPosition.z);
|
||||
if (getenv("BT_DEATH_LOG"))
|
||||
DEBUG_STREAM << "[respawn] replicant un-wrecked + warp at ("
|
||||
<< owner->localOrigin.linearPosition.x << ","
|
||||
<< owner->localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (level <= prev) // unchanged -- no new damage
|
||||
@@ -1231,7 +1243,6 @@ void
|
||||
const Mech__DamageZone::DamageDescriptor *d = zone->DescriptorForLevel(level);
|
||||
if (level >= 1.0f && prev < 1.0f) // just destroyed -> the Destroyed descriptor
|
||||
{
|
||||
wasWrecked = 1; // latch: a wreck the observer must later reverse
|
||||
const Mech__DamageZone::DamageDescriptor *dd =
|
||||
zone->DescriptorForGraphicState(DamageZone::DestroyedGraphicState); // enum 1
|
||||
if (dd != 0)
|
||||
|
||||
@@ -367,12 +367,17 @@ class MechDeathHandler
|
||||
private:
|
||||
Mech *owner; // @0x14
|
||||
std::vector<Scalar> lastLevel; // @0x10 per-zone last damageLevel cache
|
||||
// PORT (respawn replication): latched when any zone is destroyed, so a
|
||||
// REPLICANT observer can reverse the one-way wreck swap when the master's
|
||||
// Mech::Reset heals + re-broadcasts the zones (falling damage edge). Not a
|
||||
// binary field -- the authentic un-wreck rides the type-0 graphic-state hook
|
||||
// (Mech::ReadUpdateRecord case 0), which our port drives via the zone levels.
|
||||
Logical wasWrecked;
|
||||
// PORT (respawn replication): last-tick MovementMode, so a REPLICANT
|
||||
// observer can reverse the one-way wreck swap on the DEATH-STATE EXIT
|
||||
// edge (9/2 -> 0/1, only Mech::Reset ever leaves the death modes). The
|
||||
// state rides EVERY record header, so the edge fires for ANY death
|
||||
// cause. Replaces a zone-level falling-edge latch (wasWrecked), which
|
||||
// was blind to zone-less deaths: collision/internal-rattle kills
|
||||
// (gitea #94) never move a damage zone, so the latch never armed and
|
||||
// the peer's wreck could never un-wreck. Not a binary field -- the
|
||||
// authentic un-wreck rides the type-0 graphic-state hook
|
||||
// (Mech::ReadUpdateRecord case 0), which our port has not reconstructed.
|
||||
int prevMode;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user