#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:
co-authored by
Claude Fable 5
parent
440cee1e4f
commit
8165cee9da
@@ -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;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -203,7 +203,9 @@
|
||||
targetRangeExponentDemand, // @0x1a0 panel "zoom" demand (init 2.0, range 0..5)
|
||||
targetRangeExponent; // @0x1a4 smoothed value -> mech target range (2^x)
|
||||
int
|
||||
previousMissionReviewMode; // @0x1a8 last-seen mech mission-review flag
|
||||
previousEjectPermitted; // @0x1a8 last-seen mech ejectPermitted (@0x414) --
|
||||
// the panic-arm edge detector (was mislabeled
|
||||
// "mission-review flag"; corrected 2026-08-03)
|
||||
|
||||
//
|
||||
// Per-platform input staging slots (filled by the derived ctor's
|
||||
|
||||
@@ -369,8 +369,13 @@ BTL4PlaybackApplication::SharedData
|
||||
|
||||
//
|
||||
// Controls only matter in interactive ("scrub") review mode.
|
||||
// The binary reads the GLOBAL review-mode cell DAT_004fd550 here --
|
||||
// NOT mech+0x414 (that cell is ejectPermitted; the old
|
||||
// GetMissionReviewMode() call was a mislabel, corrected 2026-08-03).
|
||||
// The port never enters scrub mode, so the global stays 0.
|
||||
//
|
||||
if (GetMissionReviewMode() == 2) // DAT_004fd550
|
||||
extern int gMissionReviewMode; // DAT_004fd550 (btstubs)
|
||||
if (gMissionReviewMode == 2)
|
||||
{
|
||||
Check(controlsManager);
|
||||
controlsManager->Execute(); // vtbl+0x18
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
// body recovered from BTL4OPT.EXE (the binary oracle) / the RP analogue. //
|
||||
// //
|
||||
// == RUNTIME BRING-UP WORKLIST (replace these) == //
|
||||
// Mech::GetMissionReviewMode / IsAirborne / SetTargetRange / //
|
||||
// Mech::IsAirborne / SetTargetRange / //
|
||||
// SetMappingSubsystem / RaiseStatusAlarm //
|
||||
// Mech__DamageZone::LoadCriticalSubsystems //
|
||||
// MechSubsystem::TakeDamage / OnAlarmChanged //
|
||||
@@ -97,11 +97,11 @@ void Notify_Objective_Reached(int * /*objective_subsystem*/, Mech * /*mech*/)
|
||||
// Mech method stubs.
|
||||
//===========================================================================//
|
||||
|
||||
// TODO(bring-up): reads mech+0x414 (mission-review playback flag).
|
||||
int Mech::GetMissionReviewMode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// (GetMissionReviewMode stub RETIRED 2026-08-03: it was a mislabel of
|
||||
// mech+0x414 = ejectPermitted, now served by the inline GetEjectPermitted in
|
||||
// mech.hpp.) The REAL mission-review mode is this GLOBAL (DAT_004fd550);
|
||||
// the port never enters scrub/review, so it stays 0.
|
||||
int gMissionReviewMode = 0;
|
||||
|
||||
// TODO(bring-up): true while the mech is off the ground (jump-jet / fall state).
|
||||
int Mech::IsAirborne()
|
||||
|
||||
@@ -357,7 +357,11 @@ struct ShotDescriptor
|
||||
enum ResetMode { MissionReviewReset = 0 }; // reset-mode selector (Reset arg, value 0)
|
||||
|
||||
void SetMappingSubsystem(Subsystem *mapper); // btl4app.cpp:567
|
||||
Logical GetMissionReviewMode(); // reads this+0x414 (btl4mppr.cpp:366)
|
||||
// ejectPermitted (@0x414) accessor -- the panic-arm watcher's read
|
||||
// (btl4mppr InterpretControls @004d196c). [The old GetMissionReviewMode
|
||||
// here was a mislabel of the same cell; the real review mode is the
|
||||
// GLOBAL DAT_004fd550 (btl4pb.cpp). Corrected 2026-08-03.]
|
||||
int GetEjectPermitted() const { return ejectPermitted; }
|
||||
void SetTargetRange(Scalar range); // writes this+0x404 (btl4mppr.cpp:407)
|
||||
void Reset(const Origin &origin, int mode); // btl4pb.cpp:555 (FUN_0049fb74)
|
||||
|
||||
|
||||
@@ -2466,6 +2466,18 @@ void
|
||||
Scalar dt = till - lastPerformance;
|
||||
lastPerformance = till;
|
||||
|
||||
// AUTHENTIC (byte-scan 2026-08-03): the binary's master performance
|
||||
// (FUN_004a9b5c) CALLS the eject-permission evaluator (@0049fa1c) at
|
||||
// +0x10 -- its first act every frame. The refreshed ejectPermitted
|
||||
// (+0x414) is what L4MechControlsMapper::InterpretControls (@004d196c)
|
||||
// edge-detects into the panic-armed mode 0x200000 (physical PANIC lamp
|
||||
// via MakeLinkedLamp, eject-mode gauge elements, keypad routing). This
|
||||
// call was the missing link in the port: without it the flag stayed at
|
||||
// its ctor 0 forever and the eject option never came alive (#118).
|
||||
// Masters only, like the binary (FUN_004a9b5c never runs on replicants).
|
||||
if (GetInstance() == MasterInstance)
|
||||
EvaluateEjectPermission();
|
||||
|
||||
// The bring-up DRIVE + ANIMATION path is single-player scaffolding: it reads
|
||||
// the global gBTDrive and a single shared gBodyAnim, and integrates THIS body's
|
||||
// origin. It must run ONLY for the local player's mech (the viewpoint entity);
|
||||
|
||||
Reference in New Issue
Block a user