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 void
HeatSink::UpdateCoolant(Scalar time_slice) HeatSink::UpdateCoolant(Scalar time_slice)
{ {
HeatSink *master = (HeatSink *)linkedSinks.Resolve(); // this[0x38] linked master // AUTHENTIC (heatmodel decode, FUN_004adbf8): the binary reads *(this[0x38]+0x158)
// NULL-master guard (reconstruction omission -- the sibling ConductHeat/BalanceCoolant // = this subsystem's OWN DamageZone.damageLevel (the engine base zone at @0xE0,
// guard this same Resolve(); UpdateCoolant didn't, so a Condenser/Reservoir with no // word 0x38), NOT linkedSinks->heatEnergy. The earlier reconstruction confused
// linked master null-deref'd here on its first tick). No master -> no coolant draw. // word 0x38/@0xE0 (the DamageZone) with linkedSinks@0x164, so when the link
coolantDraw = master ? master->heatEnergy * heatLoad : 0.0f; // *(this[0x38]+0x158) * this[0x48] // 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) if (coolantDraw < CoolantDrawEpsilon)
{ {
coolantDraw = 0.0f; coolantDraw = 0.0f;
+10 -2
View File
@@ -122,9 +122,17 @@ Logical
void void
Condenser::RefrigerationSimulation(Scalar time_slice) // FUN_004ae4d8 Condenser::RefrigerationSimulation(Scalar time_slice) // FUN_004ae4d8
{ {
HeatSink *master = (HeatSink *)linkedSinks.Resolve(); // this[0x38] // AUTHENTIC (heatmodel decode): massScale = clamp>=1( (1 - ownDamageZone.damageLevel)
// * refrigerationFactor ). The binary reads *(this[0x38]+0x158) = this condenser's
// OWN DamageZone.damageLevel (the engine base zone @0xE0, word 0x38): undamaged 0 ->
// massScale = refrigerationFactor (3.0), degrading toward 1.0 as the condenser is
// damaged. The earlier recon read linkedSinks->heatEnergy (~1.3e7) -> (1 - 1.3e7)
// clamps massScale permanently to 1.0 (minimum refrigeration) AND null-derefs a
// Condenser whose linkedSinks is skipped (the ctor guard).
::DamageZone *ownZone = this->Subsystem::damageZone; // @0xE0 (word 0x38)
Scalar zoneDamage = (ownZone != 0) ? ownZone->damageLevel : 0.0f; // +0x158
massScale = // refrigeration output reuses massScale @0x160 (word 0x58) massScale = // refrigeration output reuses massScale @0x160 (word 0x58)
(1.0f - master->heatEnergy) // _DAT_004ae530 = 1.0f ; master +0x158 (1.0f - zoneDamage) // _DAT_004ae530 = 1.0f ; own DamageZone +0x158
* refrigerationFactor; // this[0x76] * refrigerationFactor; // this[0x76]
if (massScale < 1.0f) // _DAT_004ae530 if (massScale < 1.0f) // _DAT_004ae530
{ {
+14 -1
View File
@@ -50,8 +50,21 @@ Derivation
Receiver::MessageHandlerSet Receiver::MessageHandlerSet
Searchlight::MessageHandlers; // includes { "ToggleLamp", &Searchlight::ToggleLamp } @00511210 Searchlight::MessageHandlers; // includes { "ToggleLamp", &Searchlight::ToggleLamp } @00511210
// gauge data-binding wave: publish "LightOn" -> lightState@0x1D8 (the button-5
// searchlight lamp; the empty default-constructed index resolved NOTHING).
// Chained to PowerWatcher's dense index.
const Searchlight::IndexEntry
Searchlight::AttributePointers[]= // @005111e0
{
ATTRIBUTE_ENTRY(Searchlight, LightOn, lightState) // @0x1D8 (reported on/off)
};
Searchlight::AttributeIndexSet Searchlight::AttributeIndexSet
Searchlight::AttributeIndex; Searchlight::AttributeIndex(
ELEMENTS(Searchlight::AttributePointers),
Searchlight::AttributePointers,
PowerWatcher::GetAttributeIndex()
);
Searchlight::SharedData Searchlight::SharedData
Searchlight::DefaultData( // @00511198 Searchlight::DefaultData( // @00511198
+4 -1
View File
@@ -104,7 +104,10 @@
// //
public: public:
enum { enum {
LightStateAttributeID = PowerWatcher::NextAttributeID, // 5 // The cockpit button-5 lamp binds "Searchlight/LightOn" (L4GAUGE.CFG:5017);
// the published NAME must be "LightOn" for Find() to hit, bound to the
// reported on/off member lightState@0x1D8.
LightOnAttributeID = PowerWatcher::NextAttributeID, // 5
NextAttributeID NextAttributeID
}; };