#108 THE EJECT GHOST: the death-edge latch tested the wrong field
One substitution, three field symptoms. Mech::TakeDamageMessageHandler arms
the whole death tail -- kill report, VehicleDead, death blast -- from a
was-alive-at-entry latch:
const int deathBlastArmed = !IsMechDestroyed(); // graphicAlarm >= 9
The binary tests movementMode 9|10 there (@0x4a0303). The port swapped in the
graphic alarm and justified it: "the death transition sets mode 9 synchronously
with the structural flag on every path through here, so the edges coincide".
True of every DAMAGE path. False of the one that matters:
Mech::EjectPilotMessageHandler raises graphicAlarm to 10 (the EJECT state)
BEFORE dispatching its self-damage, while movementMode is still 1. So on an
eject the handler entered already reading "destroyed", the latch never armed,
and the death tail was skipped entirely -- including VehicleDead, which IS the
respawn trigger.
Everything the field reported on night 13 follows from that:
* "they all self destructed with panic button and didn't respawn properly"
-- no VehicleDead, so no drop-zone hunt, so no respawn;
* the EJECT GHOST -- the peer wrecks the mech and never un-wrecks it, because
the un-wreck rides the master's respawn. Normal deaths replicated fine all
along (9 deaths -> 8 un-wrecks, benched), which is why only ejects ghosted;
* the manual chart's "-1000 ejecting" never materialised -- the negated kill
award and the death cost both live in the tail that never ran.
Fix: use the binary's own predicate. MovementMode is untouched by the eject's
alarm write, so the latch arms on an eject exactly as on a combat death.
WHY SEVEN RIGS MISSED IT: the punch-out was being REFUSED, not undelivered.
EvaluateEjectPermission (@0049fa1c) grants only on
liveWeapons < ejectMinWeapons || liveGenerators == 0 || coolantFrac < 0.05
|| (leg-gimped && !simLive)
-- armour damage satisfies none of them, and every bench ejected a healthy
mech. An [ejecttest] receipt (2 lines) proved the dispatch fired every time
and the handler declined; the "[eject] REFUSED (mech not crippled enough)" line
was sitting in the very first bench log, ungrepped. BT_KILL_SUBSYS's
comma-list form ("GeneratorA,GeneratorB,...") was already built for this bench.
Verified 2-node (scratchpad/night13/ejectreal.sh), before -> after:
PUNCH-OUT landed 0 (785 refusals) -> 1, charge=500
peer wreck-enters 1 -> 1
peer UN-WRECKS 0 (the ghost) -> 1
eject score (type=2) absent -> award=-1000.00, score 1000 -> 0
death cost never ran -> APPLYING, penalty=500
That -1000 is the manual chart's eject row to the digit: killBonus 500 plus the
500 self-damage tally, negated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ
This commit is contained in:
co-authored by
Claude Opus 5
parent
29b4d68ba6
commit
61f21107b4
@@ -1070,7 +1070,31 @@ void
|
||||
// Capturing it after the divert (where #89 first placed it) meant a
|
||||
// COLLISION death could never arm the tail.
|
||||
//
|
||||
const int deathBlastArmed = !IsMechDestroyed(); // [ebp-0x10], inverted
|
||||
// WAS-ALIVE-AT-ENTRY, on the BINARY's predicate (movementMode), not the
|
||||
// graphic alarm. FIXED 2026-08-07 -- this substitution was the eject-ghost.
|
||||
//
|
||||
// The binary tests movementMode 9|10 here; the port used IsMechDestroyed()
|
||||
// (graphicAlarm >= 9) and justified it with "the death transition sets mode
|
||||
// 9 synchronously with the structural flag on every path through here, so
|
||||
// the edges coincide". That is true of every DAMAGE path and false of the
|
||||
// one that matters: Mech::EjectPilotMessageHandler raises graphicAlarm to
|
||||
// 10 (the EJECT state) BEFORE dispatching its self-damage, while
|
||||
// movementMode is still 1. So on an eject the handler entered already
|
||||
// reading "destroyed", the latch never armed, and the whole death tail was
|
||||
// skipped -- no VehicleDead, which IS the respawn trigger.
|
||||
//
|
||||
// Consequences, all three reported from the field on the same night:
|
||||
// * the ejecting player never respawns ("panic button, didn't respawn");
|
||||
// * the peer wrecks the mech and never un-wrecks it, because the un-wreck
|
||||
// rides the master's respawn -> the permanent EJECT GHOST (#108);
|
||||
// * the eject scores only its -500 self-damage: no negated kill award and
|
||||
// no -500 death cost, because both live in the tail that never ran --
|
||||
// which is why the manual chart's "-1000 ejecting" never materialised.
|
||||
//
|
||||
// MovementMode 9|10 is untouched by the eject's alarm write, so the latch
|
||||
// now arms on the eject exactly as it does on a combat death.
|
||||
const int deathBlastArmed =
|
||||
!(MovementMode() == 9 || MovementMode() == 10); // [ebp-0x10], inverted (@0x4a0303)
|
||||
//
|
||||
// The zone the reports + VehicleDead carry: msg+0x24 as of loop entry.
|
||||
// The binary never rewrites msg+0x24 after the initial cylinder resolve;
|
||||
|
||||
Reference in New Issue
Block a user