From 64975ec22fbc07db8824ab02e4cb7020bb924cbf Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 7 Jul 2026 16:27:57 -0500 Subject: [PATCH] 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) --- game/reconstructed/heat.cpp | 18 +++++++++++++----- game/reconstructed/heatfamily_reslice.cpp | 12 ++++++++++-- game/reconstructed/searchlight.cpp | 15 ++++++++++++++- game/reconstructed/searchlight.hpp | 5 ++++- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/game/reconstructed/heat.cpp b/game/reconstructed/heat.cpp index c271564..5212dbb 100644 --- a/game/reconstructed/heat.cpp +++ b/game/reconstructed/heat.cpp @@ -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; diff --git a/game/reconstructed/heatfamily_reslice.cpp b/game/reconstructed/heatfamily_reslice.cpp index 1915999..4fe5e91 100644 --- a/game/reconstructed/heatfamily_reslice.cpp +++ b/game/reconstructed/heatfamily_reslice.cpp @@ -122,9 +122,17 @@ Logical void 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) - (1.0f - master->heatEnergy) // _DAT_004ae530 = 1.0f ; master +0x158 + (1.0f - zoneDamage) // _DAT_004ae530 = 1.0f ; own DamageZone +0x158 * refrigerationFactor; // this[0x76] if (massScale < 1.0f) // _DAT_004ae530 { diff --git a/game/reconstructed/searchlight.cpp b/game/reconstructed/searchlight.cpp index 72bfddb..aec572a 100644 --- a/game/reconstructed/searchlight.cpp +++ b/game/reconstructed/searchlight.cpp @@ -50,8 +50,21 @@ Derivation Receiver::MessageHandlerSet 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::AttributeIndex; + Searchlight::AttributeIndex( + ELEMENTS(Searchlight::AttributePointers), + Searchlight::AttributePointers, + PowerWatcher::GetAttributeIndex() + ); Searchlight::SharedData Searchlight::DefaultData( // @00511198 diff --git a/game/reconstructed/searchlight.hpp b/game/reconstructed/searchlight.hpp index b5f9c1d..2b626e2 100644 --- a/game/reconstructed/searchlight.hpp +++ b/game/reconstructed/searchlight.hpp @@ -104,7 +104,10 @@ // public: 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 };