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:
@@ -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);
|
||||
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user