#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:
co-authored by
Claude Fable 5
parent
bf0555ccf1
commit
d4ba91bd39
@@ -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**
|
||||
|
||||
Reference in New Issue
Block a user