From 420558cf4a88370550d35c3919c5d13f9d8d25af Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Tue, 11 Aug 2026 01:09:13 -0500 Subject: [PATCH] #162 eject-arm predicate fix, canary-caught: destroyedAtEntry now samples the binary's MODE-based FUN_0049fb54 (movementMode 2||9), not alarm-based IsMechDestroyed -- EjectPilot's alarm-10 pre-write made a real punch-out read 'already destroyed' and silently ate the suicide type-2 (-499 arithmetic) while cook-off suicides scored; the alarm-vs-mode trap #108 fixed for deathBlastArmed, one latch over. Benches: eject canary 6/6 deaths+costs+type-2s (5 suicides incl. the real eject, 1 kill); primary kd_bench 7/7/7/0 re-verified Co-Authored-By: Claude Fable 5 --- context/combat-damage.md | 9 +++++++-- game/reconstructed/mech.cpp | 20 +++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/context/combat-damage.md b/context/combat-damage.md index 98e1d4b..2d0f00c 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -1294,8 +1294,13 @@ deviation that makes boards consistent). **Full evidence record** (every dispatch/arm/layout with decomp line cites, the field receipt A/B, and the fix archaeology): `docs/KD_CHAIN_AUDIT_20260811.md`. -**Port rules that keep it correct:** (1) the report block AND the death tail are both -master-gated + entry-latched (mech.cpp) -- replicant-side damage application is a +**Port rules that keep it correct:** (0) **the entry latch is MODE-based (movementMode +2||9 == FUN_0049fb54), NEVER the alarm-based IsMechDestroyed()** -- EjectPilot raises +graphicAlarm to 10 BEFORE its killBonus self-damage, so an alarm-based latch reads a real +punch-out as "already destroyed" and silently eats the suicide type-2 (the -499 arithmetic) +while cook-off suicides still score; caught by the eject canary 2026-08-11, the same +alarm-vs-mode trap #108 fixed for deathBlastArmed; (1) the report block AND the death tail +are both master-gated + entry-latched (mech.cpp) -- replicant-side damage application is a visual-only deviation and must never emit; (2) advancedDamageOn comes from the mission egg per-pilot page -- every stock egg stamps `advancedDamage=1` on every page, and the FE egg writer now stamps the host's menu choice mission-wide (a memset-zero pilot diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index eccc064..2f25c63 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -1033,14 +1033,28 @@ void Check(message); // #150 -- the binary's ONCE-PER-DEATH latch (local_14 @0x4a0230 body, - // part_012.c:14625-14629): IsMechDestroyed() sampled AT ENTRY. The kill - // report posts only on the 0->1 edge (!destroyedAtEntry && destroyed-now). + // part_012.c:14625-14629). The kill report posts only on the 0->1 edge + // (!destroyedAtEntry && destroyed-now). // CORRECTED by the #162 audit: a later salvo of the killing volley is NOT // "inflicted-only" -- it emits type-0 Inflicted (shooter) AND type-1 // Received (victim, latch-independent, part_012.c:14736-14759); what the // latch suppresses is the type-2 kill and the death tail. Full map: // docs/KD_CHAIN_AUDIT_20260811.md. - const int destroyedAtEntry = IsMechDestroyed(); + // + // ⚠ PREDICATE (eject canary, 2026-08-11): the binary's local_14 samples + // FUN_0049fb54 = movementMode 2||9 (part_012.c:14174-14188) -- the MODE, + // not the alarm. The first cut here used the alarm-based + // IsMechDestroyed(), and the EJECT arm broke exactly the way #108's + // deathBlastArmed once did: EjectPilot raises graphicAlarm to 10 BEFORE + // dispatching its killBonus self-damage, so the handler entered reading + // "already destroyed" and the SUICIDE type-2 was suppressed -- a real + // punch-out charged the -500 cost but never the -(basis+tally) award, + // while ammo cook-off suicides (no alarm pre-write) scored correctly. + // Mode 2|9 is untouched by the eject's alarm write, so the mode form + // latches post-death volley rounds exactly as before AND lets the eject's + // suicide report through -- byte-faithful to the 1995 predicate. + const int destroyedAtEntry = + (MovementMode() == 2 || MovementMode() == 9); // FUN_0049fb54 // MP DIAGNOSTIC (task #47): confirm the handler runs on the OWNING MASTER // for a cross-pod (network-delivered) TakeDamage + what state it carries.