EJECT/PANIC wired: Mech::EjectPilot (id 0x19 @0049f854) + the crippled-mech gate

The button died twice before reaching game logic: no handler (0x19 was
unregistered) and no sender (the pod's panic was a control bit, not a
mappable). Both halves reconstructed from raw disasm -- the handler AND
its permission evaluator sat in export gaps.

@0049f854 EjectPilot: press-only; gated on ejectPermitted (@0x414) and
!IsDisabled; console eject notice (relay wire = tracked tail; the
suppressConsole@0x258 latch that prevents the death double-notify IS
wired); graphicAlarm -> 10, which kills via the >=9 predicate; then a
self TakeDamage {inflicting=SELF, zone -1, Explosive, amount =
ScenarioRole::killBonus}. Role layout byte-settled via the role reader
@00429bec dest offsets + the ctor record copy: +0x1c IS killBonus, +0x20
is the 4.10-only SpecialCaseDeathPenalty, +0x28 returnFromDeath (all
prior citations reconciled). KillBonus authors NOWHERE in shipped
content -> the charge is 0 and the ALARM does the killing: our bench
outcome is the pod outcome.

@0049fa1c EvaluateEjectPermission: eject only from a CRIPPLED mech --
bank coolant fraction < 0.05 | zero live generators | live weapons
below mech+0x448 (no exported writer: zero, clause inert) | leg-gimped
novice. A healthy mech REFUSES the button; no free resets.

Input: binding-engine "Eject" action -- Backspace / pad LeftThumb
(default profile + shipped CONTROLS.MAP). Bridges per the databinding
rule: weapon/generator/bank/player reads land in their complete TUs;
MechSubsystem gains the both-cells destroyed accessors (gotcha #22).
Bench scalpels: BT_EJECT_AT=<frame> (path-identical synthetic press),
BT_KILL_SUBSYS now takes a comma list.

Verified single-node: healthy press REFUSED (x2), four generators
killed, next press PUNCH-OUT -> death, wreck smoke, respawn; the
respawned mech refuses again (permission re-evaluates after Reset).
Death rides the normal damage/death chain, so MP replication is the
proven path. Tails tracked: console relay notice, RIO 0x38 panic
control, alarm-10 eject audio/canopy, SpecialCaseDeathPenalty consumer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-02 16:45:54 -05:00
co-authored by Claude Fable 5
parent e1ae92264a
commit e996be249d
16 changed files with 345 additions and 14 deletions
+28
View File
@@ -2388,3 +2388,31 @@ Entity *BTPlayerObjectiveMechOf(void *player)
return 0;
return (Entity *)((BTPlayer *)player)->GetObjectiveMech(); // @0x284
}
//#############################################################################
// BTPlayerEjectBookkeeping -- complete-type bridge for Mech::EjectPilot
// (@0049f854, the PANIC/EJECT punch-out). The handler's two player-side
// writes, verbatim from the raw disasm:
// * suppressConsole (+0x258) = 1 -- the eject already notified the console
// (its own message, the tracked relay tail), so the DEATH that the
// returned charge causes must not double-notify;
// * the self-destruct amount = ScenarioRole::killBonus (role+0x1c -- the
// record map is byte-verified: reader @00429bec dest offsets + the role
// ctor's record copy [7]=rec[0]).
// Returns the charge; 0 with no player/role (binary derefs unguarded).
//#############################################################################
Scalar BTPlayerEjectBookkeeping(void *player_v)
{
BTPlayer *player = (BTPlayer *)player_v;
if (player == 0)
{
return 0.0f;
}
player->suppressConsole = 1; // +0x258
const ScenarioRole *role = player->GetScenarioRole(); // +0x208
if (role == 0)
{
return 0.0f;
}
return role->GetKillBonus(); // role+0x1c
}