drop-zone / respawn instrumentation: make the GHOST MECH visible in field logs

The ghost (a player who dies, never respawns, stays dead-but-driveable and is a
wreck on every peer) traces to a respawn stalling on drop-zone acquisition:
DropZone::AssignDropZoneMessageHandler grants a slot only if IsAvailable(), and
when none is free it reposts to ITSELF every 0.1s at MaxEventPriority and never
replies -- so deathPending is never cleared.  This whole subsystem was DARK: not
one drop-zone line in a full night of field logs.

ALWAYS-ON (no env gate -- rare, catastrophic, field-only, same rationale as the
#57/#59 guards; volume bounded and rate-limited):
  [dz] POOL   -- slots + downTime + proximity radius, once per mission
  [dz] GRANTED-- one per respawn, with the WAIT time; tags (GHOST RECOVERED)
                 when a long-stalled request finally lands (what testers see as
                 "it fixed itself")
  [dz] STALL / GHOST LIKELY -- escalating, per-waiter rate-limited, and it names
                 WHY each slot is busy (age + last user), which discriminates
                 cooldown saturation from proximity blocking -- different fixes
  [dzreq]     -- the REQUESTER half: re-try count for this death, and msgDeath vs
                 our own deathCount.  The engine gates the hunt on those being
                 equal AND the DropZone resend net is keyed on the same value, so
                 a mismatch breaks both silently (cf #45, tally replication).
BT_DROPZONE_LOG=1 adds the verbose per-request slot dump for bench work.

First run already overturned a theory: slots=8, so an 8-slot pool cannot be
cooldown-starved by 5 players (max 5 on cooldown at once) -- the shared
dropzone=one in eggmodel.py is NOT the bottleneck.  Suspicion moves to the
handshake (lost reply / deathCount mismatch).

Verified with NO env vars set, exactly as a tester runs it: 4 probe lines for a
full death+respawn cycle, all in the normal day log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-07-30 00:13:14 -05:00
co-authored by Claude Fable 5
parent e1c15eb806
commit ba6756c40a
2 changed files with 151 additions and 0 deletions
+27
View File
@@ -413,6 +413,33 @@ void
<< message->deathCount << " deathCount=" << deathCount
<< ")\n" << std::flush;
// ALWAYS-ON GHOST TRACE (#81 field diagnostics). The requester half of
// the drop-zone handshake, paired with the [dz] lines from the DropZone
// entity. Two things are worth recording on every re-post:
// * how many times we have re-posted for THIS death -- the respawn is
// supposed to complete on the first or second try, so a climbing
// count is a ghost forming;
// * msgDeath vs our own deathCount -- the engine gates the hunt on
// message->deathCount == deathCount, and the DropZone's re-send
// safety net is keyed on lastDeathCount == message->deathCount, so a
// MISMATCH silently breaks both (Gitea #45: the death tally is known
// not to replicate correctly). This is the cheapest way to see it.
{
static int s_tries = 0;
static int s_forDeath = -999;
if (s_forDeath != message->deathCount) { s_forDeath = message->deathCount; s_tries = 0; }
++s_tries;
if (s_tries <= 3 || (s_tries % 5) == 0)
DEBUG_STREAM << "[dzreq] player " << BTMatchHostOf(GetEntityID())
<< ":" << (int)GetEntityID() << " asking for a drop zone"
<< " try=" << s_tries
<< " msgDeath=" << message->deathCount
<< " ourDeathCount=" << deathCount
<< (message->deathCount != deathCount ? " *** MISMATCH -- hunt gate + resend net both break ***" : "")
<< (s_tries >= 4 ? " (respawn not completing -- ghost forming)" : "")
<< "\n" << std::flush;
}
//
// Respawning needs a drop zone. A dev mission without a "DropZones"
// group would hit the engine base's ACTIVE Check(dropzones) -> an