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 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-10 00:56:55 -05:00
co-authored by Claude Fable 5
parent 79aba77309
commit 3141e9fda4
5 changed files with 162 additions and 2 deletions
+99
View File
@@ -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<Subsystem*> 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);
//
+6 -2
View File
@@ -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<Subsystem*> controllableSubsystems;
ChainOf<Subsystem*> watchedSubsystems;
ChainOf<Subsystem*> heatableSubsystems;
ChainOf<Subsystem*> myomerSubsystems; // binary mech+0x7ac
ChainOf<Subsystem*> weaponRoster;
ChainOf<Subsystem*> 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
+31
View File
@@ -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.
+18
View File
@@ -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
+8
View File
@@ -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;