#81 THE ANSWER: deathPending is OUR invention -- the binary has no death latch

Read BT's own respawn code and checked it against BTL4OPT.EXE itself.

FAITHFUL: BT's Player::VehicleDeadMessageHandler (FUN_0042db80, part_003.c:12029)
gates on message->deathCount == player->deathCount AND player+0x40 != 1
(simulationState != DropZoneAcquiredState) -- exactly WinTesla's two gates --
then runs the same closest-DropZone search skipping "win*" zones, dispatches
AssignDropZone and re-posts to itself on a timer.  So the retry loop and the
cross-machine hunt are authentic 1995 behaviour, not a WinTesla artifact.

NOT FAITHFUL: BT's BTPlayer::VehicleDeadMessageHandler (FUN_004c012c,
part_013.c:10504) ENDS with *(param_1 + 0x290) = 0 -- it CLEARS the field our
reconstruction calls deathPending, at the end of every death.  Verified in the
raw binary: +0x290 is written in exactly three places in the whole executable
(0x0b75fb, 0x0bffe3, 0x0c0a05) and ALL THREE store a zeroed register (xor
ecx,ecx / xor eax,eax / xor edx,edx immediately before).  There is NO write of 1
or of any non-zero value to +0x290 anywhere in BTL4OPT.EXE.  Two are ctor/reset
sweeps; the middle one is the death handler, sitting right after the call to
Post and add esp,0x14, matching the decompiled tail exactly.

So 1995 has NO death-pending gate.  We invented it (btplayer.cpp:505) and then
needed six clear sites to patch the strandings it caused (:382 :469 :1431 :1442
:1461 :1532).  #57 and #55 are artifacts of that invention.  It is also what
makes a ghost PERMANENT: in BT a failed respawn is harmless (the 2s re-post
keeps hunting, the next death starts a clean cycle); in ours the first failure
latches the pilot and every later death is SWALLOWED forever -- exactly the
field signature of 8 cycles, 6 stranded, none recovering.

Fix proposed in the doc (match the binary: clear instead of latch, drop the
dedup gate) but NOT applied -- six sites depend on the latch and the dedup is
load-bearing, so it wants a deliberate two-node bench, not a 1am edit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-07-30 08:31:29 -05:00
co-authored by Claude Fable 5
parent bf0555ccf1
commit d4ba91bd39
+47
View File
@@ -131,6 +131,53 @@ reply whose `deathCount` is *ahead* of ours (our counter is behind) instead of d
never leave `deathPending` latched on a discard. ⚠ Neither should be guessed at blind — the
`[ghost] DROP-ZONE REPLY DISCARDED` line's mismatch direction decides it in one line of log.
## HOW THE BINARY HANDLES IT — `deathPending` is OUR INVENTION [T1, decisive]
Read BT's own respawn code (it *is* in the export, even though `munga/dropzone.cpp` is not) and
checked it against the raw binary.
**The hunt is faithful.** BT's `Player::VehicleDeadMessageHandler` = `FUN_0042db80`
(`part_003.c:12029`) gates on `message->deathCount == player->deathCount` **and**
`player+0x40 != 1` — i.e. `simulationState != DropZoneAcquiredState`, exactly WinTesla's two gates —
then runs the same closest-DropZone search skipping `"win*"` zones, dispatches `AssignDropZone`, and
**re-posts the message to itself** on a timer. So the retry loop and the cross-machine hunt are
authentic 1995 behaviour, not a WinTesla artifact.
**But the latch is not.** BT's `BTPlayer::VehicleDeadMessageHandler` = `FUN_004c012c`
(`part_013.c:10504`) ends with:
```
++deathCount; message->deathCount = deathCount; --lives;
updateModel |= 1; alarm(+0x2c, 1); save dropZoneLocation;
Post(HighPriority, self, message, Now()+delay);
*(param_1 + 0x290) = 0; // <-- the field our reconstruction calls deathPending
```
**The binary CLEARS that field at the end of every death.** Verified against `BTL4OPT.EXE` itself:
`+0x290` is written in exactly **three** places in the whole executable
(`file_off 0x0b75fb`, `0x0bffe3`, `0x0c0a05`), and **all three store a zeroed register**
`xor ecx,ecx` / `xor eax,eax` / `xor edx,edx` immediately before. There is **no write of 1, or of
any non-zero value, to `+0x290` anywhere in the binary.** Two of the three are constructor/reset
sweeps; the middle one is the death handler above (it sits right after the `call` to `Post` and
`add esp,0x14`, matching the decompiled tail exactly).
**So in 1995 there is no death-pending gate at all.** Our `btplayer.cpp:505` does
`deathPending = 1`, and six separate sites then have to clear it (`:382 :469 :1431 :1442 :1461
:1532`) — each one a patch for a different stranding scenario. Gitea **#57 and #55 are artifacts of
that invention**, not of the original design.
**And that is what makes a ghost PERMANENT.** In BT a failed respawn is harmless: the 2-second
re-post keeps hunting, and the next death starts a clean cycle. In ours the first failure latches
the pilot — every later death hits the dedup and is SWALLOWED ("deaths so far 1" forever), so the
pilot can never recover for the rest of the mission. That is precisely the field signature: 8 death
cycles, 6 stranded, none of them ever recovering.
**Proposed fix (faithful, and it removes the whole bug class):** match the binary — clear the field
at the end of the death handler instead of latching it, and drop the dedup gate that depends on it.
⚠ Six sites currently rely on that latch's existence, and the SWALLOWED dedup is load-bearing for
the "one death, one cycle" behaviour, so this wants a deliberate two-node bench rather than a
late-night edit. Caveat on the identification: `+0x290 ↔ deathPending` rests on our own
reconstruction's mapping (our compiled `BTPlayer` is only `0x28c` bytes, so the offsets cannot
match directly) — but the *behavioural* evidence (BT's death handler clears it; nothing in the
binary ever sets it) is read straight from the executable.
## What the instrumentation already killed
`slots=8` (measured, `[dz] POOL`). An 8-slot pool **cannot** be cooldown-starved by 5 players (at
most 5 slots on cooldown at once), so the shared `dropzone=one` in `tools/eggmodel.py:42` is **not**