From 12c4254aaef8d778eff16ca6c6bc37bdfc4d15e2 Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 7 Jul 2026 20:03:07 -0500 Subject: [PATCH] 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) --- game/reconstructed/btl4gau2.cpp | 6 +++++- game/reconstructed/heat.cpp | 7 +++++-- game/reconstructed/mechsub.cpp | 21 +++++++++++++++++++++ game/reconstructed/mechsub.hpp | 9 +++++++++ game/reconstructed/sensor.cpp | 21 +++++++++------------ 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/game/reconstructed/btl4gau2.cpp b/game/reconstructed/btl4gau2.cpp index 4c41b25..6343d06 100644 --- a/game/reconstructed/btl4gau2.cpp +++ b/game/reconstructed/btl4gau2.cpp @@ -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 diff --git a/game/reconstructed/heat.cpp b/game/reconstructed/heat.cpp index 8e489d8..5f02431 100644 --- a/game/reconstructed/heat.cpp +++ b/game/reconstructed/heat.cpp @@ -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 diff --git a/game/reconstructed/mechsub.cpp b/game/reconstructed/mechsub.cpp index 91d5b2a..a645deb 100644 --- a/game/reconstructed/mechsub.cpp +++ b/game/reconstructed/mechsub.cpp @@ -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 diff --git a/game/reconstructed/mechsub.hpp b/game/reconstructed/mechsub.hpp index 0233841..9d0f119 100644 --- a/game/reconstructed/mechsub.hpp +++ b/game/reconstructed/mechsub.hpp @@ -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 // diff --git a/game/reconstructed/sensor.cpp b/game/reconstructed/sensor.cpp index 9188c85..7910fc8 100644 --- a/game/reconstructed/sensor.cpp +++ b/game/reconstructed/sensor.cpp @@ -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 {