myomer system COMPLETE -- the 'WAVE 6 mover cutover' was a phantom built on
a misattribution; the authentic couplings are all live
THE CORRECTION [T1]: @004b9550/@004b95b8 ('ConnectToMover'/'Disconnect
FromMover') are MechWeapon::ConfigureMappables/ChooseButton -- their +0x31C
is the weapon TriggerState and **(mech+0x128) is the CONTROLS MAPPER's
button roster (the MECHWEAP.CPP assert string + the trigger-edge detector
@004b9608 sit adjacent in the image). The binary has NO dynamic myomer->
speed feed: AvailableOutput has exactly TWO callers in the whole image --
RegisterMaxOutput (assembly) and the SeekVoltageGraph sampler -- and
speedEffect@0x31C is a published GAUGE attribute.
The authentic myomer system (now fully live):
1. drive heat (#85, this morning);
2. VITAL death -- myomers are the only authored vital=1 subsystem
(BTL4.RES res+0x48 scan): destroying them kills the mech via the #80
vital-crit path (this is also the ram-death mechanism -- myomers are a
0.35-weight collision-rattle target);
3. the assembly-time gait cap: RegisterMaxOutput @004b8ef0 raises
mech+0x7A0 to AvailableOutput(top gear) = base x ~1.4284 -- now called
(idempotent max, per tick; the 0x358 layout lock leaves no latch room)
with real OwnerBaseSpeed/OwnerMaxSpeed bridges; the old per-frame
reverseSpeedMax2 heal that would have clobbered it is garbage-guarded;
4. the ENG-page power curve at correct absolute scale (real base speed);
5. electrical sourcing (low gears run on a browned-out generator).
REMOVED (inventions): the mechmppr `speedDemand *= speedEffect` demand
multiplier + BTMyomersDriveOf + the Myomers ConnectToMover/MoverAttach
family. Damaged-but-alive myomers heat faster and drop the power curve
but do NOT slow the mech (gitea #75's premise is falsified by the binary;
the real damage slowdown is the LEG-damage gimp gait). Seek gears change
heat/sourcing/graph -- never speed.
Verified live: a mech at myomer dmg=0.234 (speedEffect attr 0.766)
beelines at full commanded speed with dmgGain=1.234 heat; 0 faults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Zh7PTkFy4KwTzVighLR9J
This commit is contained in:
co-authored by
Claude Fable 5
parent
31552c0444
commit
cf8618bcb9
@@ -1636,6 +1636,30 @@ void BTMechMyomerMotionSample(void *mech_v,
|
||||
*gravity = (env != 0) ? (float)env->gravityConstant : 0.0f;
|
||||
}
|
||||
|
||||
// Myomer owner speed-cell bridges (myomer-system completion, 2026-07-31):
|
||||
// Mech+0x34C = the run-speed base the mapper multiplies demand by (port
|
||||
// member `reverseStrideLength` -- documented misnomer), the
|
||||
// base AvailableOutput scales by;
|
||||
// Mech+0x7A0 = the gait rise-CLAMP (port member `reverseSpeedMax2` --
|
||||
// misnomer) that Myomers::RegisterMaxOutput raises to
|
||||
// AvailableOutput(top gear) = base x ~1.4284.
|
||||
float BTMechRunSpeedBase(void *mech_v)
|
||||
{
|
||||
Mech *m = (Mech *)mech_v;
|
||||
return (m != 0) ? (float)m->reverseStrideLength : 0.0f;
|
||||
}
|
||||
float BTMechTopSpeedGet(void *mech_v)
|
||||
{
|
||||
Mech *m = (Mech *)mech_v;
|
||||
return (m != 0) ? (float)m->reverseSpeedMax2 : 0.0f;
|
||||
}
|
||||
void BTMechTopSpeedSet(void *mech_v, float v)
|
||||
{
|
||||
Mech *m = (Mech *)mech_v;
|
||||
if (m != 0)
|
||||
m->reverseSpeedMax2 = (Scalar)v;
|
||||
}
|
||||
|
||||
//###########################################################################
|
||||
// MechDeathHandler effect-spawn bridge (mechdmg.cpp calls this)
|
||||
//
|
||||
@@ -3944,10 +3968,13 @@ void
|
||||
// reverseSpeedMax2@0x7a0 is the run-cycle bodyCycleSpeed CLAMP (AdvanceBody
|
||||
// Animation case 12/13); LoadLocomotionClips does not set it -> it reads
|
||||
// 0xCDCDCDCD (-4.3e8) and the clamp clobbers bodyCycleSpeed -> the run cycle
|
||||
// explodes. Bring-up: clamp the run cadence to the run stride (reverseStride
|
||||
// Length, the case-12 divisor) so bodyCycleSpeed/reverseStrideLength ~ 1, like
|
||||
// walk. (The authentic value comes from the model/LoadLocomotionClips.)
|
||||
reverseSpeedMax2 = reverseStrideLength;
|
||||
// explodes. GARBAGE-GUARDED only (myomer completion 2026-07-31): the
|
||||
// authentic writer is Myomers::RegisterMaxOutput -- ctor zeroes it, each
|
||||
// myomer raises it to AvailableOutput(top gear) = base x ~1.4284 -- and the
|
||||
// old unconditional per-frame heal CLOBBERED that registration back to
|
||||
// base x 1.0 every frame. Heal only a value that is clearly uninit junk.
|
||||
if (reverseSpeedMax2 < 0.001f || reverseSpeedMax2 > 500.0f)
|
||||
reverseSpeedMax2 = reverseStrideLength;
|
||||
// Gait SELECTION by throttle: walk aims for walkStrideLength; run aims for
|
||||
// reverseSpeedMax (the run/reverse cap). Exceeding walkStrideLength makes the
|
||||
// walk handler transition "up" to the run cycle (state 11 -> 12/13) via the real
|
||||
|
||||
@@ -969,31 +969,19 @@ void
|
||||
speedDemand = -mech->reverseStrideLength * throttlePosition;
|
||||
}
|
||||
|
||||
// #75 + #78: the DRIVE SCALE. The authentic coupling attached the
|
||||
// myomers' speedEffect into the mech's mover, which multiplied every
|
||||
// attached feed into its drive; the 2007 engine's mover has no feed
|
||||
// roster, so the same multiplication is applied to the speed demand here.
|
||||
// speedEffect is the wrapper's live 0..1 output (gear ratio, thermal
|
||||
// curve, 1 - zone damage); a mech with no Myomers drives at 1.0.
|
||||
// GIMP slowdown: the binary's real mechanism is the gimp gait machines
|
||||
// (FUN_004a5bf8/71f4/6344/7970) -- the LIVE demand is clamped to the wg
|
||||
// clip's speed (GimpLegClipFinished writes it back) and the limp cycle
|
||||
// cadence enforces it; an EXTRA multiply here would double-slow. The
|
||||
// earlier T3 0.5 stand-in is therefore retired; BT_GIMP_SPEED remains as
|
||||
// an optional override multiplier (unset = 1.0 = authentic).
|
||||
// (The myomer `speedDemand *= speedEffect` DRIVE SCALE is GONE -- myomer-
|
||||
// system completion, 2026-07-31. It was an invention built on the
|
||||
// ConnectToMover misattribution: the binary has NO dynamic myomer->speed
|
||||
// coupling. AvailableOutput's only callers are RegisterMaxOutput (the
|
||||
// assembly-time gait cap, now live via MyomersSimulation) and the ENG-page
|
||||
// graph sampler; damaged-but-alive myomers heat faster and drop the power
|
||||
// curve but do NOT slow the mech, and DESTROYED myomers KILL it (the
|
||||
// authored vital flag + the #80 vital-crit death). GIMP slowdown: the
|
||||
// binary's real mechanism is the gimp gait machines -- the LIVE demand is
|
||||
// clamped to the wg clip's speed and the limp cadence enforces it;
|
||||
// BT_GIMP_SPEED remains as an optional override multiplier (unset = 1.0 =
|
||||
// authentic).)
|
||||
{
|
||||
extern Scalar BTMyomersDriveOf(void *subsystem);
|
||||
Scalar drive = 1.0f;
|
||||
int n = mech->GetSubsystemCount();
|
||||
for (int i = 2; i < n; ++i) // 0/1 = mapper + voltage bus
|
||||
{
|
||||
Scalar f = BTMyomersDriveOf(mech->GetSubsystem(i));
|
||||
if (f >= 0.0f) // -1 = not a Myomers
|
||||
{
|
||||
drive = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
extern int BTMechGimpLevel(void *mech_v); // mechdmg.cpp (the TU-safe read)
|
||||
int mm = BTMechGimpLevel(mech); // the gimp cell (see above)
|
||||
if (mm == 3 || mm == 4)
|
||||
@@ -1005,17 +993,15 @@ void
|
||||
s_gimp = (e != 0 && *e != '\0') ? (Scalar)atof(e) : 1.0f; // 1.0 = authentic (clamp lives in the gimp SM)
|
||||
if (s_gimp < 0.0f || s_gimp > 1.0f) s_gimp = 1.0f;
|
||||
}
|
||||
drive *= s_gimp;
|
||||
speedDemand *= s_gimp;
|
||||
}
|
||||
speedDemand *= drive;
|
||||
if (getenv("BT_DRIVE_LOG"))
|
||||
{
|
||||
static float s_dAcc = 0.0f; s_dAcc += time_slice;
|
||||
if (s_dAcc >= 1.0f)
|
||||
{
|
||||
s_dAcc = 0.0f;
|
||||
DEBUG_STREAM << "[drive] n=" << n << " drive=" << drive
|
||||
<< " mm=" << mm << " dmd=" << speedDemand
|
||||
DEBUG_STREAM << "[drive] mm=" << mm << " dmd=" << speedDemand
|
||||
<< " mech=" << (int)mech->GetEntityID()
|
||||
<< " @" << (void *)mech << "\n" << std::flush;
|
||||
}
|
||||
|
||||
@@ -414,28 +414,13 @@ Scalar Myomers::SeekVoltageResponse(Scalar input_voltage)
|
||||
// (see emitter.cpp BTSeekVoltageSample). Guarded: a non-Myomers subsystem
|
||||
// samples 0 (only emitter/myomer pages own a graph in the shipped config).
|
||||
//***************************************************************************
|
||||
//***************************************************************************
|
||||
// #75 bridge -- the myomers' live drive fraction for the mech's speed demand.
|
||||
//
|
||||
// The authentic coupling: ConnectToMover attached &speedEffect into the
|
||||
// mech's mover, which multiplied every attached feed into its drive. The
|
||||
// 2007 engine's mover has no such roster, so the port applies the same
|
||||
// multiplication at the speed-demand site (mechmppr.cpp): demand *= this.
|
||||
// speedEffect@0x31C is the wrapper's live 0..1 output -- gear ratio, thermal
|
||||
// curve and (1 - zone damage) -- already republished every tick. A mech
|
||||
// with no Myomers (or no roster yet) drives at 1.0; a DESTROYED myomers
|
||||
// drives 0.0 (no drive -- the subsystem that moves the mech is gone).
|
||||
//***************************************************************************
|
||||
Scalar BTMyomersDriveOf(void *subsystem)
|
||||
{
|
||||
Entity *entity = (Entity *)subsystem;
|
||||
if (entity == 0 || !entity->IsDerivedFrom(Myomers::ClassDerivations))
|
||||
return -1.0f; // not a Myomers
|
||||
Scalar f = ((Myomers *)entity)->SpeedEffect();
|
||||
if (f < 0.0f) f = 0.0f;
|
||||
if (f > 1.0f) f = 1.0f;
|
||||
return f;
|
||||
}
|
||||
// (BTMyomersDriveOf is DELETED -- myomer-system completion, 2026-07-31. The
|
||||
// demand-side `speedDemand *= speedEffect` coupling it fed (mechmppr.cpp) was
|
||||
// an invention built on the ConnectToMover misattribution. The binary's only
|
||||
// runtime AvailableOutput consumers are RegisterMaxOutput (assembly) and the
|
||||
// SeekVoltageGraph sampler; myomer damage does NOT slow a live mech -- a
|
||||
// DESTROYED myomers KILLS it, via the authored vital flag (the only vital=1
|
||||
// subsystem in the RES) + the #80 vital-crit death path.)
|
||||
|
||||
Scalar BTMyomersSeekSample(void *subsystem, Scalar voltage)
|
||||
{
|
||||
@@ -505,6 +490,20 @@ static const Scalar MyomerRecoveryPerTick = -0.010999999940395355f; // 0xbc34395
|
||||
//
|
||||
void Myomers::MyomersSimulation(Scalar time_slice)
|
||||
{
|
||||
// 0. RegisterMaxOutput (@004b8ef0) -- the assembly-time top-speed cap.
|
||||
// The binary calls it from the mech-assembly loop over the myomer chain
|
||||
// (part_012.c:10322), after the model record is streamed:
|
||||
// mech[0x7A0] = max(mech[0x7A0], AvailableOutput(seekVoltage[maxIdx]))
|
||||
// = base x (topGear/recGear) = base x ~1.4284 on a healthy assembly --
|
||||
// the gait rise-clamp both channels bound against (part_012:12103/12334).
|
||||
// The port's capability-chain fill is the SubProxy stub (same situation as
|
||||
// the flush's chain walk), so each myomer registers ITSELF here. Called
|
||||
// per tick instead of once (the 0x358 layout lock leaves no room for a
|
||||
// latch member): RegisterMaxOutput is an idempotent max() -- best falls as
|
||||
// heat/damage degrade AvailableOutput, and max() never lowers the cap --
|
||||
// so the resulting state is identical to the binary's once-at-assembly.
|
||||
RegisterMaxOutput(); // @004b8ef0
|
||||
|
||||
// 1. @004b8bab -- the base electrical state machine. MUST come first, and
|
||||
// must precede any heat-model gate: OwnerAdvancedDamage() is off below
|
||||
// veteran, so gating ahead of this denied the machine to most pilots.
|
||||
@@ -739,52 +738,14 @@ void Myomers::ResetToInitialState(Logical powered)
|
||||
|
||||
|
||||
//***************************************************************************
|
||||
// Myomers::ConnectToMover (mislabeled)
|
||||
//***************************************************************************
|
||||
//
|
||||
// ⚠ MISATTRIBUTION (task #5 correction): @004b9550/@004b95b8 are NOT Myomers
|
||||
// functions -- the weapon message-table @0x511860 proves they are
|
||||
// MechWeapon::ConfigureMappables (id 9) / ChooseButton (id 10), the weapon->
|
||||
// button grouping handlers (the +0x31C coincidence: Myomers speedEffect vs
|
||||
// MechWeapon TriggerState). The bodies below are retained as the port's
|
||||
// Myomers stand-ins but carry no binary address.
|
||||
//
|
||||
// Attaches the live SpeedEffect output (@0x31C) into the Mech's JointedMover
|
||||
// roster (the mover at **(Mech[0xD0]+0x128)). Driven by a connect message
|
||||
// whose payload index is at message+0xC:
|
||||
// index = abs(message->value) - 1;
|
||||
// if (message->value < 1) { mover->Detach(index); this[0x110] = -1; }
|
||||
// else { this[0x110] = 0; mover->Attach(index, &speedEffect, this, 10, 9, 0); }
|
||||
// (mover vtable +0x38 = Attach, +0x3C = Detach.)
|
||||
//
|
||||
void Myomers::ConnectToMover(SubsystemMessage &message)
|
||||
{
|
||||
int index = abs(message.value) - 1; // message+0xC
|
||||
if (message.value < 1) {
|
||||
MoverDetach(index); // owner mover vtable +0x3C
|
||||
/* base+0x110 = -1 (detached) -- inert, no base accessor yet */
|
||||
} else {
|
||||
/* base+0x110 = 0 (attached) -- inert, no base accessor yet */
|
||||
MoverAttach(index, &speedEffect); // owner mover vtable +0x38, feed @0x31C
|
||||
// (original args: index,&speedEffect,this,10,9,0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//***************************************************************************
|
||||
// Myomers::DisconnectFromMover (stand-in; no binary address)
|
||||
//***************************************************************************
|
||||
// Counterpart of ConnectToMover: detaches the SpeedEffect feed.
|
||||
// if (message->value > 0)
|
||||
// mover->Release(message->value, &speedEffect); // mover vtable +0x44
|
||||
void Myomers::DisconnectFromMover(SubsystemMessage &message)
|
||||
{
|
||||
if (message.value > 0) { // message+0xC
|
||||
MoverRelease(message.value, &speedEffect); // owner mover vtable +0x44, feed @0x31C
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// (ConnectToMover / DisconnectFromMover are DELETED -- myomer-system
|
||||
// completion, 2026-07-31. Their source functions @004b9550/@004b95b8 are
|
||||
// MechWeapon::ConfigureMappables / ChooseButton (the +0x31C there is the
|
||||
// weapon TriggerState and **(mech+0x128) is the CONTROLS MAPPER's button
|
||||
// roster -- proven by the MECHWEAP.CPP assert string and the trigger-edge
|
||||
// detector @004b9608 adjacent in the image). The binary has NO dynamic
|
||||
// myomer->mover feed; see the RegisterMaxOutput latch in MyomersSimulation
|
||||
// for the real (assembly-time cap) locomotion coupling.)
|
||||
//***************************************************************************
|
||||
// Myomers::ToggleSeekVoltageMessageHandler @004b8a48
|
||||
//***************************************************************************
|
||||
|
||||
@@ -274,16 +274,19 @@ class Mech;
|
||||
HasVoltage(Subsystem *source = 0); // slot 16, @004b8f3c (overrides PoweredSubsystem::HasVoltage; Gitea #62 rename)
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Mover-connection message handlers (bound via the simulation message map;
|
||||
// route the live SpeedEffect @0x31C into / out of the Mech's JointedMover
|
||||
// roster at owner Mech[0x128]).
|
||||
// (The "mover-connection" handlers are GONE -- myomer-system completion,
|
||||
// 2026-07-31. @004b9550/@004b95b8 are MechWeapon::ConfigureMappables /
|
||||
// ChooseButton (the +0x31C there is TriggerState, and **(mech+0x128) is
|
||||
// the CONTROLS MAPPER's button roster, not a mover) -- confirmed by the
|
||||
// decomp neighborhood (the MECHWEAP.CPP assert string + the trigger-edge
|
||||
// detector @004b9608 immediately after). The binary has NO dynamic
|
||||
// myomer->speed feed: AvailableOutput has exactly two callers in the whole
|
||||
// image (RegisterMaxOutput at assembly + the SeekVoltageGraph sampler),
|
||||
// and speedEffect@0x31C is a published GAUGE attribute. The authentic
|
||||
// locomotion couplings are the assembly-time top-speed cap (Mech+0x7A0)
|
||||
// and the VITAL flag (myomers destroyed = mech dead -- the only vital
|
||||
// subsystem in the authored records).)
|
||||
//
|
||||
protected:
|
||||
void
|
||||
ConnectToMover(SubsystemMessage &message); // @004b9550 (attach SpeedEffect feed)
|
||||
void
|
||||
DisconnectFromMover(SubsystemMessage &message); // @004b95b8 (detach SpeedEffect feed)
|
||||
|
||||
public:
|
||||
// ToggleSeekVoltage message handler @004b8a48 (Myomers handler table
|
||||
// @0x51158C: {9, "ToggleSeekVoltage"}). Issue #19: was an EMPTY stub +
|
||||
@@ -328,13 +331,31 @@ class Mech;
|
||||
// mech actually slows when damaged/overheated and motion generates heat --
|
||||
// that path DOES drive the live mover, so reconcile it with the gait cutover
|
||||
// FIRST (both would feed the mover).
|
||||
Scalar OwnerBaseSpeed() const { return 1.0f; } // Mech +0x34C (neutral)
|
||||
Scalar OwnerMaxSpeed() const { return 0.0f; } // Mech +0x7A0
|
||||
void SetOwnerMaxSpeed(Scalar) { } // no-op (real: write Mech top speed)
|
||||
// Myomer-system completion (2026-07-31): the owner speed cells, via
|
||||
// complete-Mech-TU bridges (mech4.cpp), per the databinding rule:
|
||||
// Mech+0x34C = the run-speed base (port member reverseStrideLength --
|
||||
// documented misnomer; the mapper's demand multiplier)
|
||||
// Mech+0x7A0 = the gait rise-CLAMP RegisterMaxOutput raises at
|
||||
// assembly (port member reverseSpeedMax2 -- misnomer)
|
||||
Scalar OwnerBaseSpeed() const
|
||||
{
|
||||
extern float BTMechRunSpeedBase(void *mech);
|
||||
Scalar v = (Scalar)BTMechRunSpeedBase(owner);
|
||||
return (v > 0.0f) ? v : 1.0f; // pre-stream guard (record not read yet)
|
||||
}
|
||||
Scalar OwnerMaxSpeed() const
|
||||
{
|
||||
extern float BTMechTopSpeedGet(void *mech);
|
||||
return (Scalar)BTMechTopSpeedGet(owner);
|
||||
}
|
||||
void SetOwnerMaxSpeed(Scalar v)
|
||||
{
|
||||
extern void BTMechTopSpeedSet(void *mech, float v);
|
||||
BTMechTopSpeedSet(owner, (float)v);
|
||||
}
|
||||
// (The five drive-heat motion shims are GONE -- #85 fix 2026-07-31:
|
||||
// MyomersDriveHeat samples the real Mech via the complete-TU bridge
|
||||
// BTMechMyomerMotionSample (mech4.cpp). The mover-feed trio below is
|
||||
// still the WAVE-6 remainder.)
|
||||
// BTMechMyomerMotionSample (mech4.cpp).)
|
||||
// FUN_004ad7d4 [T1] -- the SAME player+0x260 heat-model experience gate
|
||||
// as HeatSink::HeatModelActive() (the "advanced damage" name was a
|
||||
// misread; the old `return False` stub kept movement heat off for
|
||||
@@ -355,20 +376,12 @@ class Mech;
|
||||
// which sensor.cpp:312 already reads the same way.
|
||||
Scalar DamageStructureLevel() const{ return GetSubsystemDamageLevel(); } // DamageZone +0x158
|
||||
|
||||
void MoverAttach(int index, Scalar *feed) { (void)index; (void)feed; } // JointedMover vtable +0x38 (no-op)
|
||||
void MoverDetach(int index) { (void)index; } // JointedMover vtable +0x3C (no-op)
|
||||
void MoverRelease(int handle, Scalar *feed) { (void)handle; (void)feed; } // JointedMover vtable +0x44 (no-op)
|
||||
|
||||
// Live-master gate constants (the ctor reads owner->simulationFlags).
|
||||
enum {
|
||||
SegmentCopyMask = 0x0C, // (flags & 0xC): 0 == master, 4 == damaged copy
|
||||
MasterHeatSinkFlag = 0x0100 // live-master segment bit
|
||||
};
|
||||
// The mover attachment handle lives at BASE+0x110 (a MechSubsystem slot), NOT a
|
||||
// Myomers own field -- declaring it here appended a 4-byte tail that overflowed the
|
||||
// 0x358 alloc once the PoweredSubsystem base became byte-exact. The coupling is
|
||||
// inert (ConnectToMover -> no-op MoverAttach), so the handle is write-only today;
|
||||
// when the real mover feed is revived, write base+0x110 via a base accessor.
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Local data. Offsets are byte offsets into the shipped object.
|
||||
@@ -376,8 +389,8 @@ class Mech;
|
||||
protected:
|
||||
Scalar speedEffect; // @0x31C attr 0x12; ctor 1.0f -- live drive fed to the mover
|
||||
public:
|
||||
// #75: the live drive fraction for the speed-demand coupling
|
||||
// (BTMyomersDriveOf -> mechmppr's demand scale).
|
||||
// speedEffect is a published GAUGE attribute (attr 0x12) -- the demand
|
||||
// coupling that briefly read it was an invention (removed 2026-07-31).
|
||||
Scalar SpeedEffect() const { return speedEffect; }
|
||||
protected:
|
||||
int currentSeekVoltageIndex; // @0x320 attr 0x13; ctor = recommended -- selected drive "gear"
|
||||
|
||||
Reference in New Issue
Block a user