#81 GHOST MECH FIXED: release the death latch + stop the duplicate VehicleDead

Two defects that were masking each other, both now fixed and benched.

1) THE LATCH NEVER RELEASED ON FAILURE.  The binary's FUN_004c012c tail is
   Post(...) ; *(this+0x290)=0 ; *(this+0x258)=0   (part_013.c:10519-10523).
   We had the Post and the suppressConsole and were missing the middle
   instruction, so deathPending cleared only on SUCCESS paths.  One failed
   respawn latched the pilot for the whole mission: every later death hit the
   dedup and was SWALLOWED, so the cycle could never restart -- a transient
   hiccup became a PERMANENT ghost (dead, un-Reset, still driveable, a burning
   wreck on every peer that sinks after ~18s and can never be drawn again).
   Binary evidence: +0x290 is written in exactly THREE places in all of
   BTL4OPT.EXE (0x0b75fb, 0x0bffe3, 0x0c0a05) and all three store a ZEROED
   register; there is no write of 1 -- or any non-zero, in any instruction form
   -- anywhere.  The dedup gate itself IS authentic (@004c05c4 does
   mov edx,[ebx+0x290]; test edx,edx; jne ret), so it is kept.

2) VehicleDeadMessage WAS DISPATCHED TWICE PER DEATH.  BTPostKillScore
   (btplayer.cpp:2263) sent a second one "to credit a death", but that message
   is the RESPAWN-CYCLE TRIGGER, not a scoreboard increment, and the tally is
   already credited by the handler's ++deathTally (:538).  Both fire inside the
   same death transition (BTPostKillScore at mech4.cpp:2006, the hardened
   authentic notify at :2110), so they were always paired.  Removed; the kill
   credit above it is untouched (it correctly uses a ScoreMessage).

THEY HID EACH OTHER: the duplicate made the latch look necessary, and the latch
made the duplicate invisible.  Every "death ... SWALLOWED" warning in the field
logs was just the latch deduping our own duplicate -- 8 of 8 deaths, a 100% base
rate, which is exactly why it correlated with nothing when tested.  Fixing
either alone makes things visibly worse (the first bench of fix 1 alone produced
a DOUBLE cycle: deathCount double-incremented, cycle 1's re-post gone stale and
tripping the drop-zone MISMATCH).  That is why earlier passes at #57/#55 kept
adding clear-sites instead of finding the root; the binary broke the tie.

VERIFIED
  solo: 17 consecutive death/respawn cycles, every one START->RESET,
        0 swallowed / 0 mismatch / 0 crash.  Pre-fix this strands permanently
        after cycle 1.
  MP  : two nodes over a real network path with cross-machine drop-zone replies
        (each node's request is answered by the OTHER machine) -- A 11 cycles,
        B 12, 0 swallowed / 0 mismatch / 0 discarded / 0 crash.
  harness: BT_SELF_DAMAGE_REPEAT=1 re-arms the self-damage bench after respawn
        so multiple cycles can be driven (a one-death harness can never
        exercise this fix).  scratchpad/night6/mp_ghost.sh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-07-30 08:49:27 -05:00
co-authored by Claude Fable 5
parent d4ba91bd39
commit a357dc4265
4 changed files with 95 additions and 15 deletions
+27 -2
View File
@@ -1,7 +1,32 @@
# The GHOST MECH / zombie-wreck failure — field analysis 2026-07-29
**Status:** mechanism established [T1/T2]; the missing link (why the drop-zone handshake fails) is
still open. Field session: 2026-07-29 night, Steam MP, 5 players, build 4.11.642. Raw logs and the
## ✅ FIXED 2026-07-30 — two defects that were masking each other
**1. The death latch never released on failure.** The binary's `FUN_004c012c` tail is
`Post(...)``*(this+0x290) = 0``*(this+0x258) = 0`. We had the `Post` and the
`suppressConsole` and were **missing the middle instruction**, so `deathPending` cleared only on
SUCCESS paths. One failed respawn latched the pilot for the rest of the mission — every later death
hit the dedup and was SWALLOWED, so the cycle could never restart. That is what made a transient
hiccup a PERMANENT ghost, and it matches the field signature exactly (8 cycles, 6 stranded, none
recovering). Fixed in `btplayer.cpp` (death-handler tail).
**2. `VehicleDeadMessage` was dispatched TWICE per death.** `BTPostKillScore` (`btplayer.cpp:2263`)
sent a second one "to credit a death" — but that message is the RESPAWN-CYCLE TRIGGER, not a
scoreboard increment, and the tally is already credited by the handler's `++deathTally` (`:538`).
Both fire inside the same death transition (`mech4.cpp:2006` and `:2110`), so they were always
paired. Removed.
**They hid each other:** the duplicate made the latch look necessary, and the latch made the
duplicate invisible — every `SWALLOWED` warning in the field logs was simply the latch deduping our
own duplicate (8 of 8 deaths, a 100 % base rate, which is why it correlated with nothing). Fixing
either alone makes things visibly worse, which is why earlier passes at #57/#55 kept adding
clear-sites instead of finding the root. The binary broke the tie.
**Verified:** solo **17 consecutive death/respawn cycles**, every one `START``RESET`, 0 swallowed /
0 mismatch / 0 crash (pre-fix this strands permanently after cycle 1). Two-node MP over a real
network path with cross-machine drop-zone replies: A 11 cycles, B 12, 0 swallowed / 0 mismatch /
0 discarded / 0 crash. Bench: `scratchpad/night6/mp_ghost.sh`, `BT_SELF_DAMAGE_REPEAT=1`.
**Status of the analysis below:** the mechanism write-up that led to the fix. The instrumentation
(`[dz]`, `[dzreq]`, `[ghost]`) stays in — it is what will prove the fix in the field, and the
cross-machine dependency in §4 is still a real fragility even with the latch fixed. Field session: 2026-07-29 night, Steam MP, 5 players, build 4.11.642. Raw logs and the
verbatim agent findings are in `scratchpad/night6/` (uncommitted — they contain machine names and
Steam identities). Tracker: #81 (zombie wreck), #57 (deathPending latch), #45 (tally replication).