Whirlwind respawn: authentic death->drop-zone->recreate cycle (task #52)

Death transition (mech4) now dispatches VehicleDead(-1) to the owning
player; BTPlayer::VehicleDeadMessageHandler restructured to the authentic
@004c05c4 three-way branch: -1 = death bookkeeping + sever playerVehicle
(wreck stays) + 5s re-post; >=0 = engine drop-zone hunt -> DropZoneReply
-> CreatePlayerVehicle (new mech); -2 = the acquire probe. Guarded on a
DropZones group so a zone-less mission stays dead instead of aborting.

Three latent bugs the respawn path exposed, all fixed:
- IsMechDestroyed latched on graphicAlarm>=9 alone; a later leg hit on the
  wreck rewrites the alarm to 4/3, un-latching -> the death transition
  re-ran (double kill/score, abort). Now latches on movementMode 2||9.
- Score handlers dereferenced the severed playerVehicle during the dead
  window; guarded.
- The console score flush routed a NetworkClient::Message through the
  player's Entity::Dispatch, which stamps entityID past the smaller struct
  -> /RTC1 stack overflow. Sent via application->SendMessage instead.
- Renderer LoadMission re-entry (per viewpoint-make) re-read the env INI;
  its light block Fail'd on stale sceneLightCount. Reset it in
  DPLReadEnvironment so the respawn's second read is clean.

Verified 2-node self-drive: B killed repeatedly by A respawns each time
(wreck stays), ticks + reloads + takes fresh damage on the new mech, no
abort/hang across multiple death->respawn cycles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-09 20:26:55 -05:00
co-authored by Claude Opus 4.8
parent e430e474cb
commit 83b3f31957
6 changed files with 283 additions and 54 deletions
+47 -1
View File
@@ -141,6 +141,9 @@
#include <BOXSOLID.hpp> // BoxedSolid / BoxedSolidCollision / BoxedSolidCollisionList
#include <cultural.hpp> // CulturalIcon::IsStoppingCollisionVolume / GetClassDerivations
#include <hostmgr.hpp> // HostManager::GetEntityPointer (band-effect attacker resolve)
#if !defined(PLAYER_HPP)
# include <player.hpp> // Player::VehicleDeadMessage -- the death->respawn notification (task #52)
#endif
#if !defined(EMITTER_HPP)
# include <emitter.hpp> // Emitter/PPC beam state (the per-weapon beam render walk)
#endif
@@ -976,7 +979,20 @@ void
Logical
Mech::IsMechDestroyed()
{
// graphicAlarm level 9 = a vital zone / leg gone -> going down (mechdmg.cpp).
// AUTHENTIC LATCH first: IsDestroyed == (movementMode 2 || 9) -- the death
// modes (FUN_004ab1c8 zeroes locomotion for exactly these) [T1]. The death
// modes never revert, so a wreck STAYS destroyed.
//
// graphicAlarm >= 9 is only the vital-kill TRIGGER that enters the death
// transition (mechdmg.cpp:407/426). It is a STATUS indicator, not a latch:
// a later leg hit on the wreck legitimately rewrites it to 4/3
// (mechdmg.cpp:417/419), which -- when this predicate was the alarm alone --
// "resurrected" the wreck (movementMode reverted to 1) and let the next
// vital hit run the WHOLE death transition again: double kill score, and a
// Score dispatched into the respawn window's severed playerVehicle -> the
// engine's Check(playerVehicle) abort (task #52 cdb catch).
if (movementMode == 2 || movementMode == 9)
return True;
return graphicAlarm.GetLevel() >= 9 ? True : False;
}
@@ -1132,6 +1148,36 @@ void
<< " DESTROYED (death effects dispatched from the death transition) ***\n"
<< std::flush;
}
//
// --- RESPAWN CYCLE (task #52): notify the owning player of the death. ---
// The BTPlayer VehicleDead handler @004c05c4 receives this with
// deathCount == -1 (the ctor default = "immediate death notification"),
// does the death bookkeeping, severs playerVehicle (this wreck entity
// STAYS in the world) and re-posts the message to itself at +5 seconds
// (5.0f @004c0830) -> the engine drop-zone hunt -> DropZoneReply ->
// CreatePlayerVehicle (a NEW mech). Only the owner pod's master has a
// live playerLink, which is exactly where the cycle must run; replicant
// wrecks just mirror the master's death. [T1 the handler @004c05c4;
// T3 this dispatch site -- the binary's exact sender is undecoded, but
// the once-per-death transition is the only death edge and the message
// ctor's deathCount=-1 default exists for precisely this notification.]
//
{
Player *owner = GetPlayerLink();
if (owner != 0)
{
Player::VehicleDeadMessage
vehicle_dead(
Player::VehicleDeadMessageID,
sizeof(Player::VehicleDeadMessage)
);
owner->Dispatch(&vehicle_dead);
if (getenv("BT_DEATH_LOG"))
DEBUG_STREAM << "[death] VehicleDead(-1) dispatched to the owning player\n"
<< std::flush;
}
}
}
void