myomer damage reaches the wheels, legs gimp at half structure, reverse refuses -- and an ODR trap unmasked (#75/#78)

The speed-demand site (MechControlsMapper::InterpretControls) now applies the
drive scale the mover's feed roster applied in 1995: speedDemand multiplies by
the myomers' live speedEffect (gear ratio, thermal curve, 1 - zone damage, via
the BTMyomersDriveOf bridge) and, while GIMPED, by 0.5 [T3: VGL Lynx's
"roughly 50%", BT_GIMP_SPEED overrides]. The gimp states were already being
raised -- mechdmg sets graphicAlarm 3 (left) / 4 (right) when a LegDamageZone-
flagged zone crosses half structure -- but nothing downstream ever saw them.
While gimped, reverse input is refused ("reverse disabled"), matching the
old-timers' account; the pod's audio cue rides the alarm's watchers.

Bench, one trajectory, arithmetically exact: a deterministic right-leg ramp
(the new BT_SELF_DAMAGE_ZONE harness) crosses 0.5 and the demand goes
44.837 -> 6.726 = 44.837 x 0.5 (gimp) x 0.3 (a crit-chewed myomers from the
same ramp -- the #80 crits composing with #75's scale, unprompted).

The reason "nothing downstream ever saw them" is the real find of the night,
now gotcha #23: AlarmIndicator is typedef'd to DIFFERENT TYPES per header
family -- mech.hpp says ReconAlarm (4 bytes), heat.hpp says GaugeAlarm (0x54)
-- so Mech::graphicAlarm and EVERY member after it sit at different offsets
depending on a TU's include order. mechdmg wrote level 4 and read it back;
mechmppr read 0 from the same object, same expression. No compiler error can
catch it: each TU only ever sees one definition. Until the split is audited,
cross-TU reads of the gimp level go through BTMechGimpLevel (compiled in
mechdmg's TU) -- and the same split-brain explains why the port carries the
binary's ONE movementMode cell as two live members (engine simulationState vs
graphicAlarm level) that never meet.

Also landed en route: the Myomers un-powered self-repair observed healing in
the field logs at exactly 0.011 x the authored Explosive scale per tick --
the 2026-07-29 reversal confirmed live; and Mech message 0x15 "RealMaxSpeed"
raw-decoded (@0x49f604: sets mech+0x7a0 from the message unless the +0x7a4
latch holds -- a console-tunable top speed).

Open on #78, documented in locomotion.md: the Gimp animation clips (authored
keys in the binary's model-record parser; mech2's state enum vs mech3's
reverse-fix disagree about slots 0x12-0x17 -- reconcile before wiring) and
the audio-cue binding.

Diags: BT_SELF_DAMAGE_ZONE, BT_DRIVE_LOG, BT_GIMP_SPEED.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-07-29 14:29:36 -05:00
co-authored by Claude Fable 5
parent 8a99972f22
commit 520f6eecd3
8 changed files with 191 additions and 1 deletions
+23
View File
@@ -414,6 +414,29 @@ 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;
}
Scalar BTMyomersSeekSample(void *subsystem, Scalar voltage)
{
Entity *entity = (Entity *)subsystem;