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
+36 -9
View File
@@ -182,15 +182,42 @@ transition, HUD all landed since P6): console egg → mesh → RunningMission on
wreck SWAP does appear on the killer's screen, but the death SINK/burial does not (the collapse+
burial runs only in the owning pod's death transition). Fix path: transcribe the recovered
Mech-level WriteUpdateRecord death/knockdown record types (5/6 — disasm preserved, task #51).
3b. **Respawn cycle** (task #52): a dead player currently stays dead (and pre-gate could ghost-drive
the wreck — fixed: the drive input zeroes on IsMechDestroyed, input-only so the collapse still
plays). The authentic respawn (user recollection: a BLUE WHIRLWIND recovers the dead mech, then a
re-drop) — scouting: no whirlwind strings in the exe; BTL4.RES type-12 `*int.scp` = per-mech INTRO
(arrival/drop) scripts but they are BINARY ref-records {ver, off, size, refCount, refIds} pointing
at other resources (not text like the type-13 explosion scripts); `adrop` (id 1994) sits in the
MAP type (14). The machinery: death detection (BTPlayer) → recovery/drop effect → AssignDropZone →
Mech::Reset (@0049fb74, the respawn placement — its full subsystem-reset sweep is still a
bring-up TODO) → roster ResetToInitialState.
3b. **Respawn cycle** (task #52) — the AUTHENTIC cycle is now reconstructed [T1 @004c05c4 +
engine PLAYER.cpp]: mech death transition (mech4.cpp) dispatches `VehicleDead` (ctor default
`deathCount = -1` = the death notification) to `GetPlayerLink()``BTPlayer::
VehicleDeadMessageHandler` (@004c05c4 structure): MissionEnding(state 4) skips all; -1 →
dedup on deathPending, ++deathCount, debit `scenarioRole` life, sever `playerVehicle`
(the WRECK entity stays), re-post the same message at **now + 5.0 s** (5.0f @004c0830)
stamped with deathCount; out of lives → the binary posts msgID 0x18 at +10 s (mission
review; deferred). deathCount ≥ 0 / -2 with a vehicle in hand = acquired (clears
deathPending); without one → engine base @0042db80 (PLAYER.cpp:214: closest non-"win"
DropZone → AssignDropZone → 2s re-post probe) → DropZoneReply → CreatePlayerVehicle (a NEW
mech; `deathCount = 0`) + viewpoint relink (Verify(viewpointEntity==NULL) is compiled out at
DEBUG_LEVEL 0 — release overwrites, same as the 1995 build). @0049fe0c = roster-wide
slot-11 DeathShutdown broadcast (the death transition already runs that pass).
CORRECTIONS to the earlier scouting: BTL4.RES **type 12 = InternalAudioStream** (the engine's
own resource-type table) — `*int.scp` are internal-AUDIO scripts, NOT per-mech intro scripts;
and `adrop` (id 1994) is a **type-14 MakeMessageStream** = the map's four DropZone make
records (0xE0 each: classID 3, origin, facing, name "two"/"three"/…), i.e. the respawn
infrastructure, not a whirlwind. The whirlwind/recovery VISUAL remains unfound: no
whirlwind/tornado/vortex-named asset in BTL4.RES (ModelList/VideoModel/GameModel/Animation
sweeps) and no vortex-like .PFX; still open. Mech::Reset (@0049fb74) full subsystem-reset
sweep is still a bring-up TODO.
**Three respawn bugs found + fixed on the way (all [T2], 2-node force-damage verified):**
(i) **death-latch un-latch**`IsMechDestroyed` tested `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,
double `VehicleDead(-1)`). Fixed to latch on `movementMode 2||9` (see combat-damage.md
"Death-latch correction"). (ii) **score into severed vehicle**`BTPlayer::ScoreMessageHandler`
/ `ScoreInflictedMessageHandler` deref `playerVehicle` unguarded; during the 5s dead window it is
0 → guard added (apply the award, skip the vehicle response). (iii) **renderer LoadMission
re-entry** — the KILLER bug: `MakeAndLinkViewpointEntity` (APP.cpp:1265) calls
`videoRenderer->LoadMission` on EVERY viewpoint-make, so the respawn re-runs
`DPLReadEnvironment``DPLReadINIPage`; its light block `Fail`s ("All lights must be defined on a
single INI page") because `sceneLightCount` was never reset between reads (ctor-only init). Fixed
in L4VIDEO.cpp `DPLReadEnvironment`: tear down the prior mission's `sceneLight[]` +
`sceneLightCount=0` before re-reading (the per-page invariant is intra-read only). This is a
PORT-layer re-entrancy gap in the 2007 renderer, not BT logic. Caught via cdb `bp ucrtbased!abort`
— a `Fail`/`Check` is an abort() MessageBox, not an AV, so `sxe av` misses it.
4.**Cross-pod beam visuals — DONE (task #51, 2026-07-09).** The keepalive theory needed one
correction: `FUN_0041c350` = (a) queue the LOCAL deferred beam-effect callback (@0x4bac0c) on the
app+0x34 manager (our per-weapon render walk plays that role) AND (b) set the subsystem DIRTY bit —