#85: myomer drive heat LIVE -- the five Mech motion operands were return-0

shims, so the reconstructed integrator @004b8d18 accumulated zero forever

The integrator, its gates and the heat plumbing were all reconstructed and
running -- but OwnerVelocityMag/OwnerDriveMag/OwnerVelocityY/OwnerMotionGain/
OwnerMass were cross-family compile shims returning 0.0f, so the myomers
priced ratio^2 * damageGain * 0 every tick: ice cold at any seek (Oracle's
night-7 panel-confirmed report).

Operands re-grounded against the raw disasm + decomp and mapped to NAMED
members via a complete-Mech-TU bridge (BTMechMyomerMotionSample, mech4.cpp),
per the databinding rule:
  +0x1C4 vec  -> localVelocity.linearMotion
  +0x82C vec  -> localAcceleration.linearMotion (the authentic 15-ring
                 smoothed AccelerationLastFrame the port already maintains)
  +0x20C      -> moverMass (the collision divert's cell; the old "motion
                 gain" label was a misread)
  *(+0x250)   -> environment gravity (FUN_00421e2c: vy -= **(+0x250)/tick),
                 via GetEnvironment()->gravityConstant
So the heat physics reads: 0.005 * 1/2 m v^2 (kinetic work, per-tick/no-dt
in the binary -- kept verbatim, frame-rate note documented) + 0.2 * m g
|vy| dt (climb power) + 0.2 * m |v||a| dt (acceleration power), all scaled
by ratio^2 and (1 + zone damage).  Restored the binary's fabs(v.y) (the
draft had signed vy -- descending would have COOLED).

Authored tuning extracted from BTL4.RES (18 mech records, one shared
tuning): VelocityEfficiency=0.995, AccelerationEfficiency=0.8, gears
0.3/0.5/0.7/0.9999 rec idx 2; myomer thermal profile 77/1000/2000,
conductance 1.9e5, thermalMass 2.5e5.

Live-verified (solo arena, BT_MYO_LOG + BT_HEAT_LOG): standstill generates
~0; hard circling drove Myomers T 77 -> 1225 (past the 1000 degradation
line) and the coolant loop conducted it back to ~520 equilibrium on
slowing; damageGain live at 1.048 from grind rattle.

Known-inert remainder (filed in open-questions): the climb term reads g=0
because Mover::localEnvironment is never populated in the port
(EnvironmentZone res type 23 unwired); the mover feed (speedEffect ->
locomotion) stays WAVE 6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Zh7PTkFy4KwTzVighLR9J
This commit is contained in:
Joe DiPrima
2026-07-31 08:14:05 -05:00
co-authored by Claude Fable 5
parent 37dd7f9663
commit 1570983fde
5 changed files with 116 additions and 21 deletions
+10
View File
@@ -161,6 +161,16 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
(task #8 "landed" vs the kShotDamage=12 bring-up residue) — audit before reworking.
## Deferred subsystems / feeds (authentic path scoped, marked in code)
- **Environment gravity unwired — the myomer CLIMB-heat term is inert (found 2026-07-31, #85).**
`Mover::localEnvironment` (MOVER.h:271) is declared and never populated anywhere in the port;
`GetEnvironment()->gravityConstant` is the named analog of the 1995 Mover's gravity POINTER at
`+0x250` (`FUN_00421e2c` does `vy -= **(+0x250)` per tick), which the myomer drive-heat
integrator uses for its `m·g·|vy|·dt` climb-work term. The bridge (`BTMechMyomerMotionSample`)
null-guards it, so climbing currently generates no myomer heat (flat maps: moot; cavern slopes:
a missing cost). EnvironmentZone resources (RES type 23) exist in BTL4.RES — wiring them (or at
least a constant-gravity Environment on mover birth) revives the term. Also relevant to any
future airborne/jump physics (`ApplyAirResistanceAndGravity` reads the same member).
[[subsystems]]
- **Revolving-door Phase 2 (true mid-mission drop-in) — PLANNED, gated on a spike**
(`docs/REVOLVING_DOOR_PLAN.md`, 2026-07-24). Open questions before any engine work:
(a) can an `Entity::MakeMessage` be REGENERATED post-birth (serializing current state), or
+23 -1
View File
@@ -66,7 +66,29 @@ 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; structural un-stub is INERT — no live mover/heat coupling).
- **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).
**✅ 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
any seek (Oracle's night-7 panel-confirmed report). Operands re-grounded from the raw disasm +
the decomp and mapped to named members via the complete-TU bridge `BTMechMyomerMotionSample`
(mech4.cpp): `+0x1C4` vec = `localVelocity.linearMotion`; `+0x82C` vec =
`localAcceleration.linearMotion` (the authentic 15-ring smoothed AccelerationLastFrame);
`+0x20C` = `moverMass` (the collision divert's cell — the old "motion gain" label was wrong);
`*(+0x250)` = the environment **gravity pointer** (`FUN_00421e2c` does `vy -= **(+0x250)`/tick).
Physics: `heat += ratio²·(1+dmg)·[0.005·½mv² + 0.2·m·g·|vy|·dt + 0.2·m|v||a|·dt]` — kinetic work
+ climb power + acceleration power. The binary `fabs`es v.y (the port's old draft didn't).
**Authored tuning extracted from BTL4.RES** (all 18 mech variants share one record):
VelocityEfficiency=0.995, AccelerationEfficiency=0.8, seek gears 0.3/0.5/0.7/0.9999 rec idx 2;
myomer thermal profile startT=77 / degradation=1000 / failure=2000 / conductance=1.9e5 /
thermalMass=2.5e5. Live-verified (solo arena, BT_MYO_LOG): standstill ≈ 0 generation, hard
circling drove T 77→1225 (past degradation) and the coolant loop pulled it back to ~520
equilibrium on slowing. ⚠ Two carried caveats: (1) the kinetic work term has **no dt** in the
binary (per-tick accumulate, a 1995 fixed-frame assumption) → heat/s scales mildly with frame
rate; kept verbatim, field-calibrate against pod veterans. (2) the climb term is wired but
inert: `Mover::localEnvironment` is never populated in the port (EnvironmentZone res type 23
unwired), so g reads 0 — see [[open-questions]].
⚠ **The registered Performance is the WRAPPER @004b8b9c, not the inner integrator @004b8d18**
[T1, fixed 2026-07-28]. The ctor @004b8fec stores `[0x511620]` into activePerformance, and that
pointer resolves to `0x4b8b9c`, whose first instruction is `call 0x4b0bd0` =
+30
View File
@@ -1606,6 +1606,36 @@ static void
}
}
//###########################################################################
// BTMechMyomerMotionSample -- the five Mech motion operands of the myomer
// drive-heat integrator @004b8d18 (issue #85), read here in a complete-Mech
// TU and mapped to NAMED engine members per the databinding rule:
// binary mech+0x1C4 vec -> localVelocity.linearMotion (|v|, v.y --
// the same vector the crash block's iv2 squares, operand-confirmed)
// binary mech+0x82C vec -> localAcceleration.linearMotion (the published
// "AccelerationLastFrame" attribute, decomp-reference entity table)
// binary mech+0x20C -> moverMass (same cell the collision divert
// divides by -- NOT a "motion gain")
// binary *(mech+0x250) -> GetEnvironment()->gravityConstant (the 1995
// Mover holds a POINTER to the environment gravity: FUN_00421e2c does
// vy -= **(+0x250) per tick. So the integrator's velTerm is m*g*|vy|
// = climb work, and the heat physics reads: kinetic energy + climb
// power + acceleration power, scaled by the authored inefficiencies.)
//###########################################################################
void BTMechMyomerMotionSample(void *mech_v,
float *vel_mag, float *acc_mag, float *vel_y, float *mass, float *gravity)
{
Mech *m = (Mech *)mech_v;
const Vector3D &v = m->localVelocity.linearMotion;
const Vector3D &a = m->localAcceleration.linearMotion;
*vel_mag = (float)Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
*acc_mag = (float)Sqrt(a.x * a.x + a.y * a.y + a.z * a.z);
*vel_y = (float)v.y;
*mass = (float)m->moverMass;
Environment *env = m->GetEnvironment();
*gravity = (env != 0) ? (float)env->gravityConstant : 0.0f;
}
//###########################################################################
// MechDeathHandler effect-spawn bridge (mechdmg.cpp calls this)
//
+49 -15
View File
@@ -659,30 +659,62 @@ void Myomers::MyomersSimulation(Scalar time_slice)
//
void Myomers::MyomersDriveHeat(Scalar time_slice)
{
if (!OwnerAdvancedDamage()) // FUN_004ad7d4 gate
return;
if (!OwnerAdvancedDamage()) // FUN_004ad7d4 gate (binary
return; // gates the ACCUMULATE; same outcome)
Scalar velMag = OwnerVelocityMag(); // |Mech velocity| (+0x1C4/0x1C8/0x1CC)
Scalar accMag = OwnerDriveMag(); // |Mech drive vector| (+0x82C/0x830/0x834)
Scalar vy = OwnerVelocityY(); // Mech +0x1C8
Scalar k = OwnerMotionGain(); // Mech +0x20C
Scalar m = OwnerMass(); // *Mech[0x250]
// #85 fix (2026-07-31): the five Mech motion operands were `return 0.0f`
// cross-family shims, so this integrator faithfully accumulated ratio^2 *
// damageGain * ZERO every tick -- the myomers stayed ice cold at any seek
// (Oracle's night-7 panel-confirmed report). Now sampled for real via the
// complete-Mech-TU bridge (mech4.cpp), operands re-grounded against the
// raw disasm of @004b8d18 + the authored resource (BTL4.RES 0x0BC6 records:
// VelocityEfficiency=0.995, AccelerationEfficiency=0.8, gears
// 0.3/0.5/0.7/0.9999 rec idx 2 -- one shared tuning across all 18 mechs).
float velMag, accMag, vy, mass, gravity;
{
extern void BTMechMyomerMotionSample(void *mech,
float *vel_mag, float *acc_mag, float *vel_y,
float *mass, float *gravity);
BTMechMyomerMotionSample(owner, &velMag, &accMag, &vy, &mass, &gravity);
}
Scalar velComplement = 1.0f - accelerationEfficiency; // @0x354
Scalar workComplement = 1.0f - velocityEfficiency; // @0x350
Scalar velComplement = 1.0f - accelerationEfficiency; // @0x354 (= 0.2 authored)
Scalar workComplement = 1.0f - velocityEfficiency; // @0x350 (= 0.005 authored)
Scalar damageGain = 1.0f + DamageStructureLevel(); // this[0xE0]+0x158
Scalar ratio = seekVoltage[currentSeekVoltageIndex] // @0x330[@0x320]
/ seekVoltage[recommendedSeekVoltageIndex];// @0x330[@0x324]
if (ratio < 1.0f) ratio = 1.0f;
Scalar work = k * (velMag * velMag) * 0.5f; // dot(v,v)*0.5 == |v|^2*0.5 (_DAT_004b8ee4)
// The binary fabs()es all three motion operands (the jbe/fchs pairs
// @4b8dd5/@4b8df0/@4b8e16); |v| and |a| are sqrt-positive already, but
// v.y is signed -- descending must heat like climbing, not cool.
if (vy < 0.0f) vy = -vy;
// Physics (operands nailed 2026-07-31): mass@0x20C, gravity=**(0x250) --
// work = 1/2 m v^2 (kinetic energy; NO dt in the
// binary -- the accumulate is per-TICK, an authentic 1995
// fixed-frame assumption, so heat/s scales with frame rate;
// kept verbatim, field-calibrate if the pod veterans object)
// velTerm = m g |vy| dt (climb power)
// accTerm = m |v| |a| dt (acceleration power)
Scalar work = mass * (velMag * velMag) * 0.5f; // dot(v,v)*0.5 (_DAT_004b8ee4)
pendingHeat /* @0x1C8 */ +=
ratio * ratio * damageGain *
( velComplement * vy * k * m * time_slice
( velComplement * vy * mass * gravity * time_slice
+ workComplement * work
+ velComplement * velMag * accMag * k * time_slice );
+ velComplement * velMag * accMag * mass * time_slice );
if (getenv("BT_MYO_LOG"))
{
static int s_n = 0;
if ((s_n++ % 120) == 0)
DEBUG_STREAM << "[myoheat] v=" << velMag << " a=" << accMag
<< " m=" << mass << " g=" << gravity
<< " ratio=" << ratio << " dmgGain=" << damageGain
<< " pending=" << pendingHeat << "\n" << std::flush;
}
}
@@ -807,9 +839,11 @@ struct MyomersLayoutCheck
// The real class at 0xBC6 (ctor @004b8fec) is Myomers (the mech's artificial-
// muscle drive); the factory built an Actuator RECON_SUBSYS stub. alloc 0x358.
// The subsystem constructs (resolves its Generator + builds the seek-voltage
// gear table) and ticks its real Performance, but is currently INERT w.r.t.
// locomotion/heat (no-op mover feed + the OwnerAdvancedDamage()==False sim gate)
// -- so it cannot regress the gait/drive. Authentic coupling = a follow-up.
// gear table) and ticks its real Performance. DRIVE HEAT IS LIVE (#85 fix
// 2026-07-31: the motion operands are sampled from the real Mech). Still
// inert: the LOCOMOTION coupling (no-op mover feed -- speedEffect is computed
// but nothing consumes it; WAVE 6 cutover) and the climb heat term (the
// environment gravity is unwired in the port, so g reads 0).
//===========================================================================//
Subsystem *CreateMyomersSubsystem(Mech *owner, int id, void *seg)
{
+4 -5
View File
@@ -331,11 +331,10 @@ class Mech;
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)
Scalar OwnerVelocityMag() const { return 0.0f; } // |Mech velocity|
Scalar OwnerDriveMag() const { return 0.0f; } // |Mech drive/accel|
Scalar OwnerVelocityY() const { return 0.0f; } // Mech velocity.y
Scalar OwnerMotionGain() const { return 0.0f; } // Mech +0x20C
Scalar OwnerMass() const { return 0.0f; } // *Mech +0x250
// (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.)
// 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