From 3141e9fda4210c9fa3eceb5a6aa5aa016cfaa703 Mon Sep 17 00:00:00 2001 From: Cyd Date: Mon, 10 Aug 2026 00:56:55 -0500 Subject: [PATCH] BT410 5.3.129: shot-up muscles slow the mech -- the mobility scan lands and the mobilityScale [T1] retires The master perf's opening scan maxes the myomer chain's speedEffect into the mobility scale and applies it straight to the pilot's command cells: the throttle is multiplied by it, and a scale inside the 1e-4 deadzone zeroes the turn outright -- a mech with dead muscles cannot even pivot. It is the same cell the duck driver already read as "the legs still work", so that gate stops being a constant. THE CHAIN IDENTITY IS NOW PROVEN RATHER THAN INFERRED. My 5.3.122 note pinned mech+0x7ac to Myomers "by adjacency to the string pool", which nearly went wrong: the very cell the scan reads, +0x31c, is a MechWeapon trigger over in the weapon TU. The decisive evidence is arithmetic -- the Myomers cap-raise reads seekVoltage at +0x330 indexed by maxSeekVoltageIndex at +0x32c, and walking our own member order backwards from there lands speedEffect exactly on +0x31c. The ctor's myomer sweep also carries the other half: walking the chain is what sets the "I computed my own top speed" flag, so a mech WITH myomers broadcasts its cap and one without adopts the streamed value. Safe by construction against the obvious hazard: the mapper reassigns its demands from scratch in InterpretControls, which ticks before the mech's performance, so multiplying its cell in place cannot compound frame to frame. Soak: [mobility] scale 1 over 1 myomers on an undamaged bhk1 -- throttle unscaled, drive unchanged, zero faults. Co-Authored-By: Claude Fable 5 --- restoration/source410/BT/MECH.CPP | 99 +++++++++++++++++++++++++ restoration/source410/BT/MECH.HPP | 8 +- restoration/source410/BT/MECH4.NOTES.md | 31 ++++++++ restoration/source410/BT/MECHMPPR.HPP | 18 +++++ restoration/source410/BT/MYOMERS.HPP | 8 ++ 5 files changed, 162 insertions(+), 2 deletions(-) diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index 4f10f8e6..764144ce 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -171,6 +171,7 @@ Mech::Mech( controllableSubsystems(this), watchedSubsystems(this), heatableSubsystems(this), + myomerSubsystems(this), weaponRoster(this), damageableSubsystems(this) { @@ -597,6 +598,30 @@ Mech::Mech( } RedistributeCoolantShares(); + // + //----------------------------------------------------------------------- + // THE MYOMER CHAIN (binary ctor sweep, part_012.c:15871: the mech+0x7ac + // chain). Walking it is also what tells the mech it has computed its + // OWN top speed: the binary calls the cap-raise per myomer and then + // sets +0x7a4 (part_012.c:15874), which is what makes this mech the one + // that BROADCASTS its real max speed instead of adopting a streamed one. + //----------------------------------------------------------------------- + // + { + for (int my = 2; my < subsystemCount; ++my) + { + if ( + subsystemArray[my] != NULL && + subsystemArray[my]->IsDerivedFrom(Myomers::ClassDerivations) + ) + { + myomerSubsystems.Add(subsystemArray[my]); + ((Myomers *)subsystemArray[my])->RegisterMaxOutput(); + runSpeedMaxKnown = 1; + } + } + } + // //----------------------------------------------------------------------- // Source the authentic per-mech locomotion params from the GameModel @@ -1312,6 +1337,65 @@ void } } +// +//############################################################################# +// UpdateMobilityScale -- binary master perf @0x4a9cf0-0x4a9da2. The scale +// is the BEST surviving myomer's speed effect (max over the chain), and it +// multiplies the pilot's throttle command outright: shot-up muscles mean a +// mech that will not run however hard the throttle is pushed. A scale +// inside the 1e-4 deadzone also zeroes the turn command and is what the +// duck driver reads as "the legs still work". +// +// The scan runs ONLY when the mech actually has myomers (the binary's +// count guard) -- a chassis without them keeps a scale of 1 and is +// unaffected. +//############################################################################# +// +void + Mech::UpdateMobilityScale() +{ + Check(this); + + int myomer_count = 0; + Scalar best = 0.0f; + + ChainIteratorOf scan(myomerSubsystems); + Subsystem *muscle; + while ((muscle = scan.ReadAndNext()) != NULL) + { + ++myomer_count; + Scalar effect = ((Myomers *)muscle)->SpeedEffectOf(); + if (effect > best) + { + best = effect; + } + } + + if (myomer_count > 0) + { + mobilityScale = best; + + MechControlsMapper *scale_mapper = + (MechControlsMapper *)subsystemArray[0]; + if (scale_mapper != NULL) + { + scale_mapper->ApplyMobilityScale(mobilityScale); + } + + if (getenv("BT_MECH_LOG")) + { + static Scalar s_lastScale = -1.0f; + if (mobilityScale != s_lastScale) + { + s_lastScale = mobilityScale; + DEBUG_STREAM << "[mobility] scale " << mobilityScale + << " over " << myomer_count << " myomers" + << endl << flush; + } + } + } +} + // //############################################################################# // UpdateTelemetryRings -- binary master perf tail @0x4aafba-0x4ab04a. Push @@ -2302,6 +2386,21 @@ void // THE SUPER STOP (master perf @0x4aa353): the brake latch, the accel // swap and the cockpit lurch, evaluated from this frame's demand. // + // + // THE MOBILITY SCAN (master perf @0x4a9cf0), BEFORE the demands are + // consumed: damaged myomers scale the throttle the pilot actually gets. + // + UpdateMobilityScale(); + speedDemand = 0.0f; + turnDemand = 0.0f; + if (subsystemArray != NULL && subsystemArray[0] != NULL) + { + MechControlsMapper *rescan = (MechControlsMapper *)subsystemArray[0]; + speedDemand = rescan->GetSpeedDemand(); + turnDemand = rescan->GetTurnDemand(); + } + bodyTargetSpeed = speedDemand; + UpdateSuperStop(time_slice, speedDemand); // diff --git a/restoration/source410/BT/MECH.HPP b/restoration/source410/BT/MECH.HPP index 90da41a3..3042ef71 100644 --- a/restoration/source410/BT/MECH.HPP +++ b/restoration/source410/BT/MECH.HPP @@ -721,6 +721,8 @@ UpdateInstability(Scalar speed_demand); void UpdateTelemetryRings(Scalar time_slice); + void + UpdateMobilityScale(); DamageLookupTable* GetDamageLookupTable() const { Check(this); return damageLookupTable; } @@ -754,6 +756,7 @@ ChainOf controllableSubsystems; ChainOf watchedSubsystems; ChainOf heatableSubsystems; + ChainOf myomerSubsystems; // binary mech+0x7ac ChainOf weaponRoster; ChainOf damageableSubsystems; @@ -984,8 +987,9 @@ // effectiveness (item +0x31c) over the +0x7ac leg roster, // it scales the throttle command, zeroes the turn command // when ~0, and the duck-down pick reads it as the "legs - // still work" gate. Our leg-roster scan is not - // reconstructed yet, so the cell HOLDS 1.0 -- [T1] staging. + // still work" gate. DERIVED since 5.3.129 (the [T1] hold is + // retired): Mech::UpdateMobilityScale maxes the myomer + // chain's speedEffect and applies it to the mapper. // collisionVolumeState (+0x4c4) the volume alarm: level 1 = // STANDING, 0 = DUCKED. On an edge the collision template's // maxY (BoxedSolid +0xc -- the box TOP; the ground probe's diff --git a/restoration/source410/BT/MECH4.NOTES.md b/restoration/source410/BT/MECH4.NOTES.md index 4932ae57..4356a14d 100644 --- a/restoration/source410/BT/MECH4.NOTES.md +++ b/restoration/source410/BT/MECH4.NOTES.md @@ -416,3 +416,34 @@ dominated by the gun-the-engine term -- because the mech demands 35.9 u/s and the reconstructed gait delivers 7.0. Whether that gap is authentic acceleration or a gait shortfall is a SEPARATE open question, and the new gauge is now the instrument for answering it (BT_TELEMETRY_LOG). + +## 5.3.129 -- THE MOBILITY SCAN (landed; the mobilityScale [T1] retired) + +**Chain identity PROVEN, not inferred.** My 5.3.122 note pinned the ++0x7ac chain to Myomers "by static-layout adjacency to the string pool" -- +a weak argument that nearly went wrong, because the first cell I checked +(+0x31c) is a MechWeapon trigger in the weapon TU. The decisive evidence +is arithmetic: the Myomers cap-raise @004b8ef0 (our +`Myomers::RegisterMaxOutput`) reads `seekVoltage` at +0x330 indexed by +`maxSeekVoltageIndex` at +0x32c. Walking OUR member order backwards -- +maxSeek 0x32c, minSeek 0x328, recommended 0x324, current 0x320 -- lands +`speedEffect` exactly on +0x31c, the cell the scan maxes. GUID 0x51155c +== Myomers, confirmed. (0x511830 == MechWeapon was independently +confirmed by the census, which tests it and then tests 0x5121a8 for the +dry-launcher exclusion.) + +The scan: max `speedEffect` over the chain -> the mobility scale, applied +straight to the mapper's command cells (throttle *= scale; a scale inside +the 1e-4 deadzone ALSO zeroes the turn -- a mech with dead muscles cannot +even pivot). It is the same cell the duck driver reads as "the legs still +work". Guarded by the myomer count, so a chassis without them is +untouched. SAFE BY CONSTRUCTION: the mapper reassigns its demands from +scratch in InterpretControls, which ticks before the mech's own +performance, so the in-place multiply cannot compound frame to frame. + +The ctor's myomer sweep is also what sets +0x7a4 -- so a mech WITH myomers +computes and broadcasts its own top speed, and one without adopts the +streamed value. Both halves now land together. + +Soak: `[mobility] scale 1 over 1 myomers` on an undamaged bhk1 -- throttle +unscaled, drive unchanged (demand 35.9, speed 7.0), zero faults. diff --git a/restoration/source410/BT/MECHMPPR.HPP b/restoration/source410/BT/MECHMPPR.HPP index 0d1365fd..edb99fca 100644 --- a/restoration/source410/BT/MECHMPPR.HPP +++ b/restoration/source410/BT/MECHMPPR.HPP @@ -169,6 +169,24 @@ Scalar GetTurnDemand() const { Check(this); return turnDemand; } + // + // The master perf writes the mobility scale straight into the + // mapper's command cells (binary @0x4a9d5d: throttle *= scale, and + // a scale inside the 1e-4 deadzone zeroes the turn outright -- a + // mech with dead myomers cannot even pivot). Applied here so the + // cells stay owned by the mapper. + // + void + ApplyMobilityScale(Scalar scale) + { + Check(this); + speedDemand *= scale; + if (scale <= 0.0001f && scale >= -0.0001f) + { + turnDemand = 0.0f; + } + } + // // The LIMP machines write the demand cell DOWN to the damaged side's // speed cap (binary GimpLegClipFinished @004a7970 preamble writes diff --git a/restoration/source410/BT/MYOMERS.HPP b/restoration/source410/BT/MYOMERS.HPP index 7a92521c..87b5d1e9 100644 --- a/restoration/source410/BT/MYOMERS.HPP +++ b/restoration/source410/BT/MYOMERS.HPP @@ -114,6 +114,14 @@ void RegisterMaxOutput(); + // + // The mobility contribution (binary myomers+0x31c). The master + // perf maxes this across the mech's myomer chain into the mobility + // scale -- see Mech::UpdateMobilityScale. + // + Scalar + SpeedEffectOf() const { Check(this); return speedEffect; } + protected: Scalar speedEffect; int currentSeekVoltageIndex;