diff --git a/context/decomp-reference.md b/context/decomp-reference.md index 52a29d1..5568adf 100644 --- a/context/decomp-reference.md +++ b/context/decomp-reference.md @@ -341,6 +341,28 @@ From the weapon `.SUB` records + the charge-curve `.data` constants (PE-parsed a BTWeaponCountsForEject / BTGeneratorCountsForEject / BTHeatSinkBankCoolantFraction / BTPlayerEjectBookkeeping; input = binding-engine `Eject` action (Backspace / pad LeftThumb; BENCH `BT_EJECT_AT=`, `BT_KILL_SUBSYS` now comma-list). +- **EJECT ARM CHAIN closed 2026-08-03 [T1 E8-scan / T2 lamp verified]:** the + evaluator's "unexported caller" FOUND — one relative CALL from + `0x004a9b6c` = **`FUN_004a9b5c` (the Mech MASTER PERFORMANCE) + 0x10: its + FIRST act each frame is EvaluateEjectPermission** (masters only; the fn sits + in the 004a977x-004ab188 export hole, located by E8 rel32 scan of the CODE + section). Downstream: `@004d196c` L4MechControlsMapper::InterpretControls + edge-detects @0x414 (cache @mapper+0x1a8) → Add/RemoveModeMask(**0x200000 = + the PANIC-ARMED mode** — the old "missionReviewMode" reading of mech+0x414 + was a MISLABEL, swept 2026-08-03; the real review mode is the GLOBAL + `DAT_004fd550`, btl4pb) → `MakeLinkedLamp(ButtonPanic 0x3d, 0x200000)` + (`FUN_00476fc0`) lights the pod PANIC button; on glass the pad panel shades + the miniconsole Panic button from the same PadRIO lamp state + (L4PADPANEL:295, verified lit dark→bright on generator kill). NO gauge in + the shipped L4GAUGE.CFG rides mode 0x200000 — the eject indication is the + BUTTON LAMP, not an MFD element ("weapon-eng MFD eject lamp" reports = the + weapon page's UNJAM/EJECT ammo-jam indicator, a different feature). Lamp is + SOLID on arm (engine linked-lamp semantic); FLASH = the GaugeAlarm + `SetAlertState` overlay (RIO::flashFast), not authored for panic. Port fix: + per-frame `EvaluateEjectPermission()` at the top of Mech::PerformAndWatch + (master-gated) + `GetEjectPermitted()` accessor; `BT_EJECT_LOG=1` logs the + arm edges. Adjacent table fact: id 0x1a `DuckRequest@0049fa00` latches + mech+0x398 = 1 — that latch has NO reader anywhere in the image (vestigial). - **DEATH SCORE COST decoded 2026-08-02 (#118 tail) [T1]:** the death handler tail `@004c07cd-0x4c0828` (inside the @004c05c4 export gap — missing from the #52 reconstruction) gates on `advancedDamageOn`(+0x264) and hands the ENGINE base diff --git a/game/reconstructed/btl4mppr.cpp b/game/reconstructed/btl4mppr.cpp index bec20b0..b3ee8e0 100644 --- a/game/reconstructed/btl4mppr.cpp +++ b/game/reconstructed/btl4mppr.cpp @@ -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 @@ -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; } // diff --git a/game/reconstructed/btl4mppr.hpp b/game/reconstructed/btl4mppr.hpp index e40b6fe..5c120c9 100644 --- a/game/reconstructed/btl4mppr.hpp +++ b/game/reconstructed/btl4mppr.hpp @@ -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 diff --git a/game/reconstructed/btl4pb.cpp b/game/reconstructed/btl4pb.cpp index ca249c6..2a9e558 100644 --- a/game/reconstructed/btl4pb.cpp +++ b/game/reconstructed/btl4pb.cpp @@ -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 diff --git a/game/reconstructed/btstubs.cpp b/game/reconstructed/btstubs.cpp index 5201059..509a6c7 100644 --- a/game/reconstructed/btstubs.cpp +++ b/game/reconstructed/btstubs.cpp @@ -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() diff --git a/game/reconstructed/mech.hpp b/game/reconstructed/mech.hpp index 367e8b2..e8f1bd7 100644 --- a/game/reconstructed/mech.hpp +++ b/game/reconstructed/mech.hpp @@ -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) diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index eff4cf2..1b47a3b 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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);