#118: the eject option comes alive -- per-frame arm chain closed (FUN_004a9b5c+0x10)

The evaluator's "unexported caller" found by E8-scan: the Mech MASTER
PERFORMANCE (FUN_004a9b5c, in the 004a977x..004ab188 export hole) calls
EvaluateEjectPermission as its FIRST act every frame. Without that call the
port's ejectPermitted stayed at ctor-0 forever -- the panic-armed mode never
raised, so the PANIC lamp never lit and testers "never saw the option for
eject come alive". Restored at the top of Mech::PerformAndWatch (masters
only, like the binary).

Mislabel swept: mech+0x414 is ejectPermitted, NOT "missionReviewMode" -- the
mapper's @004d196c edge-watch arms PANIC mode 0x200000 from it (the real
review mode is the GLOBAL DAT_004fd550; btl4pb now reads that global, the
GetMissionReviewMode stub is retired, mapper member renamed
previousEjectPermitted).

Verified live: kill 4 generators -> "[eject] panic-arm mode ON" -> the
miniconsole Panic button lights dark->bright (pixel captures; the pad panel
shades it from the same PadRIO lamp state the pod's physical button uses) ->
BT_EJECT_AT press -> PUNCH-OUT -> respawn heals -> mode disarms -> healthy
presses REFUSED. BT_EJECT_LOG=1 logs the arm edges.

Note: no shipped gauge rides mode 0x200000 -- the 1995 eject indication is
the button lamp; the weapon-eng MFD "UNJAM/EJECT" is the ammo-jam indicator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-03 13:53:09 -05:00
co-authored by Claude Fable 5
parent 440cee1e4f
commit 8165cee9da
7 changed files with 75 additions and 20 deletions
+21 -11
View File
@@ -28,7 +28,7 @@
// Function -> method map:
// L4MechControlsMapper ----------------------------- vtable @0051e440
// @004d17ac ctor @004d1814 dtor
// @004d196c InterpretControls (target-range ramp + review-mode watch)
// @004d196c InterpretControls (target-range ramp + panic-arm watch)
// @004d1b64 ZoomTargetRangeIn @004d1b9c ZoomTargetRangeOut
// @004d1acc NotifyOfControlModeChange (+0x48; forwards to base no-op @004b048c)
// @004d1ae4 NotifyOfDisplayModeChange (+0x4C; the secondary-view mask swap
@@ -91,7 +91,7 @@
// DAT_0051dcd0[8] = {0x37,0x36,0x35,0x34,0x33,0x32,0x31,0x30} hotbox buttons
//
// ModeManager (app+0x50): +0x4 currentMode mask, +0x8 savedMode mask.
// Mech (mapper owner @this+0xd0): +0x404 targetRange, +0x414 missionReviewMode.
// Mech (mapper owner @this+0xd0): +0x404 targetRange, +0x414 ejectPermitted.
//
#include <bt.hpp>
@@ -329,7 +329,7 @@ L4MechControlsMapper::MessageHandlerSet&
//
// Chains to MechControlsMapper (FUN_004b02f0); stamps vtable &0051e440 and
// initialises the target-range zoom (exponent 2.0 == 250*2^2 == 1000m) and the
// review-mode watcher.
// panic-arm watcher (ejectPermitted edge).
//
L4MechControlsMapper::L4MechControlsMapper(
Mech *owner,
@@ -349,7 +349,7 @@ L4MechControlsMapper::MessageHandlerSet&
)
{
(void)class_ID;
previousMissionReviewMode = 0; // this[0x6a] @0x1a8
previousEjectPermitted = 0; // this[0x6a] @0x1a8
targetRangeExponentDemand = 2.0f; // this[0x68] @0x1a0
targetRangeExponent = 2.0f; // this[0x69] @0x1a4
Check_Fpu();
@@ -374,8 +374,14 @@ L4MechControlsMapper::MessageHandlerSet&
// The L4-layer performance, run every frame ahead of the in-Mech mapper:
// 1. snap the throttle to a full-throttle detent,
// 2. (re)build the pilot roster,
// 3. watch the Mech's mission-review flag and toggle the corresponding mode
// bit (0x200000) on the application mode manager,
// 3. watch the Mech's EJECT-PERMISSION flag (ejectPermitted @0x414 --
// refreshed each frame by the master performance, FUN_004a9b5c+0x10 ->
// @0049fa1c) and toggle the PANIC-ARMED mode bit (0x200000) on the
// application mode manager. That mode bit is what lights the pod's
// physical PANIC button (MakeLinkedLamp, btl4mppr RIO ctor) and any
// gauge elements carrying the mode in their ModeMask. [Corrected
// 2026-08-03: this cell was mislabeled "mission-review mode" -- the
// real review mode is the GLOBAL DAT_004fd550 (btl4pb), not mech+0x414.]
// 4. slew the smoothed target-range exponent toward the panel demand and
// push the resulting range (250 * 2^exponent) onto the Mech,
// 5. delegate to MechControlsMapper::InterpretControls for the actual
@@ -405,19 +411,19 @@ L4MechControlsMapper::MessageHandlerSet&
BuildPilotArray(); // FUN_004b0600
//
// (3) Mission-review mode change watcher.
// (3) Panic-arm watcher: edge-detect ejectPermitted -> mode 0x200000.
//
Mech *mech = GetMech();
int review_mode = mech->GetMissionReviewMode(); // (mech)+0x414
if (review_mode != previousMissionReviewMode) // @0x1a8
int eject_armed = mech->GetEjectPermitted(); // (mech)+0x414
if (eject_armed != previousEjectPermitted) // @0x1a8
{
previousMissionReviewMode = review_mode;
previousEjectPermitted = eject_armed;
BTL4ModeManager *mode_manager =
(BTL4ModeManager*)application->GetModeManager(); // DAT_004efc94+0x50
// ModeManager API: Add/RemoveModeMask save the old mask into
// previousModeMask (the recovered "savedMode = currentMode" step).
if (review_mode == 0)
if (eject_armed == 0)
{
mode_manager->RemoveModeMask(0x200000);
}
@@ -425,6 +431,10 @@ L4MechControlsMapper::MessageHandlerSet&
{
mode_manager->AddModeMask(0x200000);
}
if (getenv("BT_EJECT_LOG"))
DEBUG_STREAM << "[eject] panic-arm mode "
<< (eject_armed ? "ON" : "off") << " ("
<< mech->GetEntityID() << ")\n" << std::flush;
}
//