FIXES (compile clean; exe link pending -- a game client still holds btl4.exe): #57 respawn latch: deathPending was released in ONE place (btplayer.cpp:343) and only if a LATER re-post happened to find the mech alive -- so if the +5s re-post landed before the drop-zone reply, or never arrived, the flag stayed set while the pilot respawned and fought on, and their NEXT death hit the dedup at :391 and was swallowed whole (no warp, no tally, no hunt) for the rest of the process. Tonight 3 of 3 deaths in the final round were swallowed, each by a player who had respawned successfully earlier in the same process. Clear moved to the actual completion point (after Mech::Reset at the drop zone) + the four abandon paths that also stranded it. Cycle logging promoted to always-on (START / RESET / SWALLOWED warning) + a RESPAWN matchlog record. #58 reticle callsign plate drew as a SOLID RECTANGLE on some maps: my dpl2d_DrawTexturedRect bound a texture but never set COLOROP/ALPHAOP, and L4D3D.cpp:1085 sets both to SELECTARG1 from TFACTOR (ignores the texture, fills with a constant). Which state was live depended on what the 3D pass drew last -- hence 'depends on the level'. Now sets MODULATE texture x diffuse for both channels and restores. Latent sibling noted (CameraHUDDrawQuad, same omission). ROOT-CAUSE DOC (no code): docs/KD_SCOREBOARD_PLAN.md. Headlines: - the port reads DEATHS from the WRONG FIELD: the binary's PilotList draws +0x27c/+0x280; we labelled +0x280 'pad_0x280', never write it, and substituted the engine's Player::deathCount (+0x200, the respawn identity number seeded to -2) -> remote rows read a clamped fake 0 by construction; - BTPlayer::VehicleDeadMessageHandler @0x4c05c4 is MISSING from our decomp export (functions_index.tsv jumps 4c052c -> 4c083c, verified) -- that silence is what produced the 'pad' belief. An absent function is not evidence of an absent behaviour; - neither counter rides an update record, so divergence never self-heals and bystanders show 0/0 all mission; BTPlayerCountObservedDeath is unreachable dead code; - 'a kill I didn't earn' = last-hitter-takes-all via lastInflictingID, and COLLISIONS count (5 of 47 deaths had a type=0 killing blow); - the phantom kill is authentic 1995 (the kill handler bumps the victim's killCount too); - btplayer.cpp:453 does simulationFlags |= 0x1 where the binary does ForceUpdate() -- bit 0 is DelayWatchersFlag and nothing clears it, so the first death of any pilot permanently disables ExecuteWatchers on that player's simulation. Filing separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg
This commit is contained in:
co-authored by
Claude Opus 5
parent
326023812a
commit
5174c638ce
@@ -319,6 +319,11 @@ void
|
||||
// before we respawn, drop it now so the world can never stay black.
|
||||
if (this == (BTPlayer *)application->GetMissionPlayer())
|
||||
{ extern void BTWarpForceUnmask(); BTWarpForceUnmask(); }
|
||||
// ABANDONED CYCLE -> release the latch (Gitea #57). Teardown swallows
|
||||
// this death, so the pilot must not carry a set `deathPending` into the
|
||||
// next round -- the pods live across rounds, so a flag stranded here
|
||||
// silently swallowed every death of the following mission.
|
||||
deathPending = 0; // this+0x290
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
@@ -366,9 +371,13 @@ void
|
||||
// world-mask now so we don't stay black waiting for a reincarnation.
|
||||
if (this == (BTPlayer *)application->GetMissionPlayer())
|
||||
{ extern void BTWarpForceUnmask(); BTWarpForceUnmask(); }
|
||||
if (getenv("BT_SCORE_LOG"))
|
||||
DEBUG_STREAM << "[score] VehicleDead: no DropZones group -- "
|
||||
"respawn unavailable in this mission\n" << std::flush;
|
||||
// ABANDONED CYCLE -> release the latch (Gitea #57): this mission
|
||||
// can never respawn anyone, so holding the flag would swallow
|
||||
// every later death silently instead of just failing this one.
|
||||
deathPending = 0; // this+0x290
|
||||
DEBUG_STREAM << "[respawn] no DropZones group -- respawn "
|
||||
"unavailable in this mission; death cycle abandoned\n"
|
||||
<< std::flush;
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
@@ -390,10 +399,21 @@ void
|
||||
//
|
||||
if (deathPending != 0)
|
||||
{
|
||||
// A death arriving while a cycle is already in flight is authentically
|
||||
// deduped -- but until Gitea #57 this was also the SILENT failure mode
|
||||
// for a stranded latch, and it logged nothing at all. Always-on now: if
|
||||
// this ever fires twice for one pilot, the latch is stuck again.
|
||||
DEBUG_STREAM << "[respawn] WARNING: death for player "
|
||||
<< BTMatchHostOf(GetEntityID()) << ":" << (int)GetEntityID()
|
||||
<< " SWALLOWED -- a death cycle is already pending (deaths so far "
|
||||
<< deathCount << ")\n" << std::flush;
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
deathPending = 1; // this+0x290
|
||||
DEBUG_STREAM << "[respawn] player " << BTMatchHostOf(GetEntityID())
|
||||
<< ":" << (int)GetEntityID() << " death cycle START (death #"
|
||||
<< (deathCount + 1) << ") -- drop-zone hunt in 5s\n" << std::flush;
|
||||
|
||||
// WARP (task #52): our OWN death = the engine's Idle -> InitialCollapse (trigger
|
||||
// becomes == control state, POVTranslocateRenderable). Collapse the eye-centred
|
||||
@@ -1223,6 +1243,9 @@ void
|
||||
{
|
||||
if (GetSimulationState() == MissionEndingState) // state == 4
|
||||
{
|
||||
// ABANDONED CYCLE -> release the latch (Gitea #57): the reply arrived
|
||||
// during teardown so no reset will happen; do not strand the flag.
|
||||
deathPending = 0; // this+0x290
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
@@ -1239,6 +1262,9 @@ void
|
||||
if (!(mech != 0 && mech->GetClassID() == Mech::MechClassID
|
||||
&& mech->IsMechDestroyed()))
|
||||
{
|
||||
// Already alive (a duplicate reply, or the 2s probe after a completed
|
||||
// reset) -> the cycle is DONE, so the latch must be down (Gitea #57).
|
||||
deathPending = 0; // this+0x290
|
||||
Check_Fpu();
|
||||
return;
|
||||
}
|
||||
@@ -1261,6 +1287,34 @@ void
|
||||
Mech *mech = (Mech *)playerVehicle;
|
||||
mech->Reset(message->dropZoneLocation, 1 /* True */); // FUN_0049fb74 (heal+move)
|
||||
|
||||
//
|
||||
// THE RESPAWN IS NOW COMPLETE -- release the death-cycle latch HERE
|
||||
// (Gitea #57). Previously `deathPending` was cleared in exactly one
|
||||
// place: the `deathCount != -1` re-post branch (:343), and only if that
|
||||
// re-post happened to find the mech already alive. So the clear
|
||||
// depended on a LATER message arriving after the reset:
|
||||
// * if the +5s re-post landed BEFORE this reply (a respawn slower than
|
||||
// 5s), it saw the mech still dead, did NOT clear, and fell through to
|
||||
// another hunt -- and nothing cleared the flag afterwards;
|
||||
// * if no further re-post/probe arrived at all, the flag simply stayed
|
||||
// set while the pilot was alive and fighting.
|
||||
// Either way the NEXT death hit the `deathPending != 0` dedup at :391 and
|
||||
// was swallowed whole -- no warp, no PLAYER_DEAD, no drop-zone hunt --
|
||||
// leaving the pilot driving a dead mech for the rest of the process.
|
||||
// That is the field bug: tonight 3 of 3 deaths in the final round were
|
||||
// swallowed, each by a player who had respawned successfully earlier in
|
||||
// the same process. The cycle's completion is HERE, so clear it here.
|
||||
//
|
||||
deathPending = 0; // this+0x290
|
||||
DEBUG_STREAM << "[respawn] player " << BTMatchHostOf(GetEntityID())
|
||||
<< ":" << (int)GetEntityID() << " RESET at drop zone (death #"
|
||||
<< deathCount << ") -- death cycle complete, latch cleared\n"
|
||||
<< std::flush;
|
||||
if (BTMatchLogActive())
|
||||
BTMatchLog("RESPAWN", "player=%d:%d deaths=%d alive=%d",
|
||||
BTMatchHostOf(GetEntityID()), (int)GetEntityID(), deathCount,
|
||||
(mech->IsMechDestroyed() ? 0 : 1));
|
||||
|
||||
// WARP (task #52): our OWN respawn = the engine's WaitForReincarnate -> Expand
|
||||
// release (POVTranslocateRenderable): drop the SetIsDead world mask and blast
|
||||
// the eye-centred sphere open to reveal the reborn world. LOCAL player only (a
|
||||
|
||||
Reference in New Issue
Block a user