From cf8618bcb99ad4319b2d752f61aa0e14787a32cf Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Fri, 31 Jul 2026 09:17:31 -0500 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_019Zh7PTkFy4KwTzVighLR9J --- context/subsystems.md | 31 ++++++++++- docs/SUBSYS_PLAN.md | 2 + game/reconstructed/mech4.cpp | 35 ++++++++++-- game/reconstructed/mechmppr.cpp | 42 +++++--------- game/reconstructed/myomers.cpp | 97 ++++++++++----------------------- game/reconstructed/myomers.hpp | 61 +++++++++++++-------- 6 files changed, 142 insertions(+), 126 deletions(-) diff --git a/context/subsystems.md b/context/subsystems.md index 79e2b93..516b5a5 100644 --- a/context/subsystems.md +++ b/context/subsystems.md @@ -66,8 +66,35 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER change needs its own verification pass). - **WAVE 5** — Torso (aim twist/elevation; joint-I/O reconstructed; the BLH record disables torso → faithfully no visible twist). -- **WAVE 6** — Myomers (mover-coupled; the mover feed is still inert — WAVE 6 cutover — but **DRIVE - HEAT IS LIVE**, #85 fix 2026-07-31, see below). +- **WAVE 6 — ✅ MYOMER SYSTEM COMPLETE (2026-07-31). The "mover cutover" concept is DEAD — it + was built on a misattribution.** `@004b9550/@004b95b8` (the "ConnectToMover/DisconnectFromMover" + bodies) are **MechWeapon::ConfigureMappables/ChooseButton** — their `+0x31C` is the weapon + TriggerState and `**(mech+0x128)` is the CONTROLS MAPPER's button roster (proven by the + MECHWEAP.CPP assert string + the trigger-edge detector @004b9608 adjacent). **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, all live in the port now: + 1. **Heat** — the drive-heat integrator (#85, below). + 2. **Vital death** — myomers are the ONLY `vital=1` subsystem in the authored records + (BTL4.RES `res+0x48` scan): destroying them KILLS the mech (the #80 vital-crit path). This + is also the collision ram-death mechanism (myomers are a 0.35-weight rattle target). + 3. **The assembly cap** — `RegisterMaxOutput @004b8ef0`: `mech+0x7A0 = max(cap, + AvailableOutput(topGear))` = base × ~1.4284, the gait rise-clamp both channels bound + against (part_012:12103/12334). Port: each myomer registers per tick (idempotent max(); + the 0x358 layout lock leaves no latch room) — state-identical to once-at-assembly. The + old per-frame `reverseSpeedMax2 = reverseStrideLength` heal (which would have clobbered + it) is garbage-guarded only now. `Mech+0x34C` (base) = port `reverseStrideLength`, + `Mech+0x7A0` (cap) = port `reverseSpeedMax2` — both documented misnomers. + 4. **The ENG-page power curve** — SeekVoltageResponse, now at the correct absolute scale + (OwnerBaseSpeed reads the real base instead of the 1.0 stub). + 5. **Electrical sourcing** — low seek gears keep the myomers runnable on a browned-out + generator (HasVoltage: `gear voltage <= measured`). + **Deliberately REMOVED (was an invention): the `speedDemand *= speedEffect` demand multiplier** + (mechmppr.cpp) and `BTMyomersDriveOf`. 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 + mech's damage slowdown is the LEG-damage gimp gait, a separate live mechanism. Seek gears do + NOT change speed (heat + sourcing + graph only). Verified live: a mech with dmg=0.234 + (speedEffect attr 0.766) beelines at full commanded speed with dmgGain=1.234 heat. **✅ MYOMER DRIVE HEAT LIVE (#85, 2026-07-31) [T2].** The five Mech motion operands of the drive-heat integrator `@004b8d18` were `return 0.0f` cross-family shims, so the (fully reconstructed) integrator accumulated `ratio² · damageGain · 0` every tick — myomers ice cold at diff --git a/docs/SUBSYS_PLAN.md b/docs/SUBSYS_PLAN.md index 8483e1e..9dd9313 100644 --- a/docs/SUBSYS_PLAN.md +++ b/docs/SUBSYS_PLAN.md @@ -108,6 +108,8 @@ The whole roster sits on the BT base chain `engine Subsystem → MechSubsystem dampingConstant@0x1F4, ...; recon mislabels + leaves accumulators uninit). Reverted to the stub (no NaN to the root joint). Remaining = a full ctor+integrator reconstruction from @004b3778 (bigger than the torso). Full detail: CLAUDE.md §10d "WAVE-5 GYROSCOPE". Also: the mech.cpp gyro↔torso cross-link write is broken (SubProxy::linkTarget→gyro+4) — commented out. +- **WAVE 6 — ✅ COMPLETE (2026-07-31): the "mover cutover" was a phantom.** @004b9550/@004b95b8 are MechWeapon button-mapping functions (misattribution); the binary has NO dynamic myomer→speed feed (AvailableOutput's only callers: RegisterMaxOutput at assembly + the SeekVoltageGraph sampler; speedEffect@0x31C is a gauge attribute). Landed: real drive-heat operands (#85), RegisterMaxOutput → mech+0x7A0 gait cap (idempotent per-tick), real OwnerBaseSpeed (graph scale), the invented `speedDemand *= speedEffect` multiplier REMOVED, ConnectToMover/BTMyomersDriveOf deleted. Myomer damage does not slow a live mech; destruction kills (the only authored vital=1 subsystem). Full authority: context/subsystems.md WAVE 6. + (Historical entry below kept for the record:) - **WAVE 6 — Mover-coupled: ◐ STRUCTURAL un-stub DONE + WIRED (gated `BT_MYOMERS` default ON; verified INERT, no locomotion/combat regression).** Myomers (0xBC6) constructs + ticks its real Performance via `CreateMyomersSubsystem`, but is INERT: `MyomersSimulation` early-returns on `OwnerAdvancedDamage()==False` (real gate = messmgr 0xBD3), and the mover feed (`MoverAttach`) + `SetOwnerMaxSpeed` are no-op stubs so the live JointedMover is never touched. De-shim dropped the `owner*`/`segmentFlags` shim fields (accessors return neutral defaults) to fit the exact-0x358 alloc (`sizeof` lock). ⚠ AUTHENTIC COUPLING DEFERRED: routing `SpeedEffect@0x31C` into the mover + real owner-motion accessors + the advanced-damage heat gate — this DRIVES THE LIVE MOVER, so reconcile with the gait cutover FIRST. BLH tick 26→27; `BT_MYOMERS=0` → Actuator stub. See CLAUDE.md §10d "WAVE 6". - **WAVE 7 — Projectile/missile weapons:** ProjectileWeapon (0xBCD), MissileLauncher (0xBD0). **PARTIALLY BLOCKED:** case 0xBCE calls `@4bdcb4`, which has no CLASSMAP entry — decompile it first and resolve the 0xBCD/0xBCE/0xBD0 label tangle (Risk 1) before swapping these. - **WAVE 8 — Message hub:** SubsystemMessageManager (0xBD3). Real, but **verify the cache-slot/tick-bypass interaction** (Risk 4) before enabling its tick. diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 345a7fd..b70570f 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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 diff --git a/game/reconstructed/mechmppr.cpp b/game/reconstructed/mechmppr.cpp index ee0a4be..1581a77 100644 --- a/game/reconstructed/mechmppr.cpp +++ b/game/reconstructed/mechmppr.cpp @@ -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; } diff --git a/game/reconstructed/myomers.cpp b/game/reconstructed/myomers.cpp index ed13fd3..01e2e13 100644 --- a/game/reconstructed/myomers.cpp +++ b/game/reconstructed/myomers.cpp @@ -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 //*************************************************************************** diff --git a/game/reconstructed/myomers.hpp b/game/reconstructed/myomers.hpp index c7607a0..aaa59b6 100644 --- a/game/reconstructed/myomers.hpp +++ b/game/reconstructed/myomers.hpp @@ -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"