gauge wave P1b: Searchlight/LightOn + fix coolant/refrigeration source misread

- Searchlight publishes "LightOn" -> lightState@0x1D8 (the button-5 searchlight
  lamp; the empty default-constructed index resolved NOTHING).  The config binds
  the name "LightOn", so the enum id is renamed LightOnAttributeID and the table
  is chained to PowerWatcher's dense index.  Verified: LightOn resolves OK.

- FIX a genuine reconstruction bug the heatmodel decode caught (heat.cpp
  UpdateCoolant + heatfamily_reslice RefrigerationSimulation): both read
  linkedSinks.Resolve()->heatEnergy where the binary reads *(this[0x38]+0x158) =
  the subsystem's OWN DamageZone.damageLevel (the engine base zone @0xE0).
  Consequences of the misread: coolantDraw = master.heatEnergy(~1.3e7) * heatLoad
  would SLAM the live CoolantMass bars to empty whenever the link resolved; and
  RefrigerationSimulation clamped massScale permanently to 1.0 (minimum) instead
  of the authentic 3.0, plus null-deref'd a Condenser whose linkedSinks is skipped.
  Now both read this->Subsystem::damageZone->damageLevel (qualified past the
  MechSubsystem shadow, per mechweap.cpp:252): undamaged 0 -> coolantDraw 0 (no
  leak, coolant bars stay full = authentic pristine) / massScale = 3.0, rising
  only as the sink/condenser itself takes battle damage.

After this only HeatSink/AmbientTemperature remains NULL (the #if 0'd aggregate
HeatSink bank -> P3).  Verified DBASE+dev gauges: no crash, combat un-regressed
(TARGET DESTROYED).  (Also noted: the Condenser dup-ctor links to the REAL
heatfamily_reslice definition -- LNK4006 keeps the first/real one; the heat.cpp
stub is ignored -- so no bug today; #if 0 cleanup deferred to the Condenser table.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-07 16:27:57 -05:00
co-authored by Claude Opus 4.8
parent 070af409f7
commit 64975ec22f
4 changed files with 41 additions and 9 deletions
+13 -5
View File
@@ -753,11 +753,19 @@ void
void
HeatSink::UpdateCoolant(Scalar time_slice)
{
HeatSink *master = (HeatSink *)linkedSinks.Resolve(); // this[0x38] linked master
// NULL-master guard (reconstruction omission -- the sibling ConductHeat/BalanceCoolant
// guard this same Resolve(); UpdateCoolant didn't, so a Condenser/Reservoir with no
// linked master null-deref'd here on its first tick). No master -> no coolant draw.
coolantDraw = master ? master->heatEnergy * heatLoad : 0.0f; // *(this[0x38]+0x158) * this[0x48]
// AUTHENTIC (heatmodel decode, FUN_004adbf8): the binary reads *(this[0x38]+0x158)
// = this subsystem's OWN DamageZone.damageLevel (the engine base zone at @0xE0,
// word 0x38), NOT linkedSinks->heatEnergy. The earlier reconstruction confused
// word 0x38/@0xE0 (the DamageZone) with linkedSinks@0x164, so when the link
// resolved (heatEnergy ~1.3e7) coolantDraw became ~2e6 and would SLAM coolantLevel
// to empty every frame -- the opposite of the authentic near-static behavior.
// An undamaged subsystem has damageLevel 0 -> coolantDraw 0 -> NO leak (the coolant
// bars stay full on a pristine mech); the draw rises only as the heat sink /
// condenser itself takes battle damage. (Read the qualified engine zone -- the
// nearer MechSubsystem::damageZone is a shim SHADOW; see mechweap.cpp:252.)
::DamageZone *ownZone = this->Subsystem::damageZone; // @0xE0 (word 0x38)
Scalar zoneDamage = (ownZone != 0) ? ownZone->damageLevel : 0.0f; // +0x158
coolantDraw = zoneDamage * heatLoad; // *(this[0x38]+0x158) * this[0x48]
if (coolantDraw < CoolantDrawEpsilon)
{
coolantDraw = 0.0f;