#96: instrument the myomer heat model -- per-mech profile CONFIRMED, overheat cause LOCATED

Two player claims, both now answered from the decomp + measurement rather than
inference.

THE EQUATION (FUN_004b8d18, constants read from the image: _DAT_004b8ee4=0.5,
_DAT_004b8ee8=0.0 (the fabs), _DAT_004b8eec=1.0):

  heat += ratio^2 * (1+damageLevel) *
          [ (1-accEff)*|vy|*m*g*dt        climb   POWER
          + (1-velEff)*(0.5*m*|v|^2)      kinetic ENERGY -- NO dt
          + (1-accEff)*|v|*|a|*m*dt ]     accel   POWER

Our implementation already reproduces this verbatim, dt-less term included.

CLAIM 1 -- "each mech has a unique heating profile".  TRUE, and working, but NOT
by the mechanism the player described.  Measured across thr1/own1/mad1/vul1:
  * the myomer record is IDENTICAL on every chassis
    (velEff 0.995, accEff 0.8, gears 3000/5000/7000/9999, rec 2,
     degradeT 1000, failT 2000, thermalMass 250000)
  * the heat-family COUNT is identical too -- 6 Condensers, 1 HeatSinkBank,
    1 Reservoir, 4 Generators on all four
  * every cooling parameter is byte-identical Thor vs Owens (condenser
    conductance 315000 / mass 420000, bank 231000 / 1.39e6, reservoir
    190000 / 3.42e6)
So there is NO authored per-chassis cooling variation.  The profile emerges from
the equation instead: heat ~ m*v^2, and light mechs are faster.  Measured at
seek 4, flat out:
    thr1  mass 70000  |v| 11.34  kinetic/tick 49026
    own1  mass 35000  |v| 17.22  kinetic/tick 57350
The Owens is HALF the mass and generates 17% MORE drive heat, because v^2 beats
m.  That reproduces the player's OUTCOME (a Thor sustains seek 4, a light
chicken-walker cannot) via speed, not heatsink count.

CLAIM 2 -- "it runs too hot".  The kinetic term carries NO dt: it adds an ENERGY
every TICK, so its contribution per SECOND scales with the tick rate.  Measured
dt here is ~0.017 (~59Hz) and variable.  The other two terms are power terms and
are rate-independent.  On flat ground the climb term is additionally dead --
gravity reads 0 (the carried "environment gravity unwired" open), so hills do not
heat at all right now.
NOT yet established: the 1995 tick rate the dt-less term was calibrated against.
Until that is pinned the OVERHEAT FACTOR is unquantified -- flagged, not guessed.

Adds three diagnostics, all under BT_MYO_LOG:
  [myoheat] now splits climb/kinetic/accel + dt + the kinetic share
  [myoparm] one line per myomer: efficiencies, gears, thermal thresholds
  [hsparm]  one line per heat subsystem: conductance + thermal mass
and three benches (myoheat/myoparm/myocmp) that produced the tables above.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joe DiPrima
2026-08-01 13:14:55 -05:00
co-authored by Claude Opus 5
parent 56f15b569a
commit 9dd7d48b41
5 changed files with 116 additions and 5 deletions
+35 -5
View File
@@ -229,6 +229,25 @@ Myomers::Myomers(
recommendedSeekVoltageIndex = resource->seekVoltageRecommendedIndex; // @0x324 res+0x1AC
currentSeekVoltageIndex = recommendedSeekVoltageIndex; // @0x320
minSeekVoltageIndex = 0; // @0x328
// #96 -- "each mech has a unique heating profile". Dump what THIS mech's
// myomer record actually streamed, so per-chassis variation is a measured
// fact rather than an assumption. One line per myomer built.
if (getenv("BT_MYO_LOG"))
{
DEBUG_STREAM << "[myoparm] " << (GetName() ? GetName() : "?")
<< " velEff=" << velocityEfficiency
<< " accEff=" << accelerationEfficiency
<< " gears=[";
for (int g = 0; g <= maxSeekVoltageIndex && g < 5; ++g)
DEBUG_STREAM << (g ? "," : "") << seekVoltage[g];
DEBUG_STREAM << "] rec=" << recommendedSeekVoltageIndex
<< " max=" << maxSeekVoltageIndex
<< " degradeT=" << degradationTemperature
<< " failT=" << failureTemperature
<< " thermalMass=" << thermalMass
<< "\n" << std::flush;
}
}
@@ -726,20 +745,31 @@ void Myomers::MyomersDriveHeat(Scalar time_slice)
// 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 * mass * gravity * time_slice
+ workComplement * work
+ velComplement * velMag * accMag * mass * time_slice );
// The three terms, kept separate for diagnosis (#96 "myomers run too hot"):
// two are POWER (x dt), one is an ENERGY added per TICK.
Scalar termClimb = velComplement * vy * mass * gravity * time_slice;
Scalar termKinetic = workComplement * work; // <-- no dt, per the binary
Scalar termAccel = velComplement * velMag * accMag * mass * time_slice;
Scalar gain = ratio * ratio * damageGain;
pendingHeat /* @0x1C8 */ += gain * (termClimb + termKinetic + termAccel);
if (getenv("BT_MYO_LOG"))
{
static int s_n = 0;
if ((s_n++ % 120) == 0)
{
Scalar sum = termClimb + termKinetic + termAccel;
DEBUG_STREAM << "[myoheat] v=" << velMag << " a=" << accMag
<< " m=" << mass << " g=" << gravity
<< " ratio=" << ratio << " dmgGain=" << damageGain
<< " dt=" << time_slice
<< " | climb=" << (gain * termClimb)
<< " kinetic=" << (gain * termKinetic)
<< " accel=" << (gain * termAccel)
<< " kineticShare=" << (sum != 0.0f ? (termKinetic / sum) : 0.0f)
<< " pending=" << pendingHeat << "\n" << std::flush;
}
}
}