gauge-complete P4a: heat-leaf data fixes (radar heat-degradation authentic)

The gauge-complete-decode workflow found the "deep heat-leaf byte-exact re-base" is
STALE -- the leaf layout already shipped byte-exact (alarm-unification/P7), and the
real work is a small misidentified-field data fix (highest value, lowest risk):

- Sensor radarPercent read the INHERITED heatEnergy (~1.3e7) -> hugely negative ->
  needed a bring-up guard forcing it to 1.0.  The binary (@004b1c4c:1829) reads
  *(this[0x38]+0x158) = the subsystem's OWN DamageZone::damageLevel [0,1] (0 intact ..
  1 destroyed).  Fixed: radarPercent = RadarBaseline - GetSubsystemDamageLevel(); guard
  DELETED (damageLevel is engine-clamped).  So the radar authentically degrades with
  sensor structural damage (RadarBaseline-damage, x0.5 on DegradationHeat, 0 on Failure).
- New databinding-SAFE accessor MechSubsystem::GetSubsystemDamageLevel()/Set... reads the
  ENGINE DamageZone::damageLevel NAMED member (NOT the ReconDamageZone proxy, whose
  offset-0 structureLevel aliases the vtable ptr as a float = garbage).
- HeatSink::HandleMessage msg==1: same misidentification -> SetSubsystemDamageLevel(0.5)
  (was linkedSinks->heatEnergy=0.5, wrong object+field).
- MyomerCluster seek lamp: subsys+0x800 (OOB for 0x358 Myomers) -> currentSeekVoltageIndex
  @0x320 (the Emitter+0x3f0 analog; INFERRED -- ctor not in the assert-anchored export).
- numericSpeed (~225 vs 61.5): verified NOT A BUG (NumericDisplaySpeed applies x3.6; the
  feed is correct u/s) -- closed, no code.

No layout change (already byte-exact); pure data-read retargets.  Verified DBASE+dev
gauges: no crash, combat un-regressed (TARGET DESTROYED).  (radarPercent degrade is only
OBSERVABLE once per-subsystem damage routes to each engine DamageZone -- a separate task;
today the sensor is undamaged so radarPercent = full 1.0, which is correct.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-07 20:03:07 -05:00
co-authored by Claude Opus 4.8
parent 13f9c1f274
commit 12c4254aae
5 changed files with 49 additions and 15 deletions
+5 -1
View File
@@ -1270,7 +1270,11 @@ MyomerCluster::MyomerCluster(
seekStepLamp = new OneOfSeveralInt(ChildRate(), eng_mode, renderer_in, // @004c5148
engPort, 0xf, 0, "bteseek.pcc", 1, 4,
(int *)((char *)subsystem_in + 0x800), "OneOfSeveralInt"); // BEST-EFFORT raw
// gauge-complete wave: 0x800 was OOB for a 0x358 Myomers (garbage) -> the seek
// gear step is Myomers::currentSeekVoltageIndex@0x320 (the analog of the
// EnergyWeaponCluster's Emitter::seekVoltageIndex@0x3f0; same 1..4 semantics).
// INFERRED (the MyomerCluster ctor @004c8df4 isn't in the assert-anchored export).
(int *)((char *)subsystem_in + 0x320), "OneOfSeveralInt");
void *inputVoltage = AttributePointerOf(subsystem_in, "InputVoltage");
AddConnection(new GeneratorVoltageConnection(&seekValue, inputVoltage)); // @004c3288
+5 -2
View File
@@ -842,8 +842,11 @@ Logical
{
if (message == 1)
{
HeatSink *master = (HeatSink *)linkedSinks.Resolve();
master->heatEnergy = 0.5f; // *(this[0x38]+0x158) = 0.5f
// @004add6c:16948 *(this[0x38]+0x158) = 0.5 == damageZone->damageLevel = 0.5
// this[0x38] is the inherited MechSubsystem damageZone (@0xE0), NOT linkedSinks
// (@0x164): a message-driven "set this sink to half structural damage". The
// prior read targeted the linked master's heatEnergy -- wrong object + field.
SetSubsystemDamageLevel(0.5f);
return True;
}
return HeatableSubsystem::HandleMessage(message); // FUN_004ac1d4
+21
View File
@@ -205,6 +205,27 @@ MechSubsystem::~MechSubsystem()
Logical MechSubsystem::TestClass(Mech &) { return True; }
Logical MechSubsystem::TestInstance() const { return IsDerivedFrom(ClassDerivations); }
//
// gauge-complete wave: this subsystem's OWN structural damage level [0,1] (0=intact,
// 1=destroyed). The zone at this+0xE0 is an ENGINE DamageZone (new DamageZone(this,0)
// above); read/write its public NAMED damageLevel (DAMAGE.h) via the ENGINE type --
// NOT the ReconDamageZone proxy (whose offset-0 structureLevel aliases the vtable ptr
// as a float = garbage). The binary reads/writes *(this[0x38]+0x158) for exactly this
// (SensorSimulation @004b1c4c, UpdateCoolant @004adbf8, HeatSink::HandleMessage @004add6c).
//
Scalar MechSubsystem::GetSubsystemDamageLevel() const
{
return damageZone ? ((DamageZone *)damageZone)->damageLevel : 0.0f;
}
void MechSubsystem::SetSubsystemDamageLevel(Scalar level)
{
if (damageZone)
{
((DamageZone *)damageZone)->damageLevel = level;
}
}
//#############################################################################
// Subsystem virtual overrides
+9
View File
@@ -239,6 +239,15 @@ class Damage;
ReconDamageZone *
GetDamageZoneProxy() const { return damageZone; }
// gauge-complete wave: this subsystem's OWN structural damage [0,1] (0=intact,
// 1=destroyed) -- the engine DamageZone::damageLevel of the zone the ctor built
// (new DamageZone(this,0) @0xE0). Read via the ENGINE type's NAMED member (not
// the ReconDamageZone proxy, whose offset-0 structureLevel aliases the vtable
// ptr as a float = garbage). The binary reads *(this[0x38]+0x158) here (== the
// same anchor UpdateCoolant/SensorSimulation use). Defined in mechsub.cpp.
Scalar GetSubsystemDamageLevel() const;
void SetSubsystemDamageLevel(Scalar level);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Internal helpers
//
+9 -12
View File
@@ -274,18 +274,15 @@ void
PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // FUN_004b0bd0
// this[0x38] is the resolved thermal master sink; +0x158 == heatEnergy.
radarPercent = RadarBaseline - HeatMasterEnergy();
// ⚠ BRING-UP GUARD (heat-leaf branch not byte-exact): the inherited heatEnergy
// reads un-normalized/garbage here (~3850) so radarPercent goes hugely negative
// and the radar reports non-operational. radarPercent is authentically a [0,1]
// capability ratio, so when the heat term is out of range treat it as no penalty
// (full radar). A real overheat penalty needs the heat-energy normalization
// (deferred with the heat-leaf re-base). Exposed once the map gauge began
// reading Avionics/RadarPercent; inert before.
if (HeatMasterEnergy() < 0.0f || HeatMasterEnergy() > RadarBaseline)
radarPercent = RadarBaseline;
// @004b1c4c:1829 radarPercent = RadarBaseline - *(this[0x38]+0x158)
// this[0x38] == the inherited MechSubsystem::damageZone (this+0xE0, an ENGINE
// DamageZone); +0x158 == DamageZone::damageLevel = this Sensor's OWN structural
// damage [0,1] (0 intact .. 1 destroyed, engine-clamped by DAMAGE.cpp:380). So
// radarPercent is a natural [0,1] capability ratio -- NO guard/normalization
// needed. (The prior read used the INHERITED heatEnergy ~1.3e7 -- a misidentified
// field -- which drove radarPercent hugely negative and needed the bring-up guard,
// now DELETED. Same anchor UpdateCoolant/heat.cpp:769 reads as zoneDamage.)
radarPercent = RadarBaseline - GetSubsystemDamageLevel();
if (HeatModelOff()) // this[0x10] == 1
{