From 070af409f7f5aefbe3c2be5d63094cccdfe78f4c Mon Sep 17 00:00:00 2001 From: arcattack Date: Tue, 7 Jul 2026 16:22:45 -0500 Subject: [PATCH] gauge wave P1a: publish heat/power/weapon attributes + radar zoom The gauge-databinding-map workflow found most cockpit gauges resolve NULL because the reconstructed subsystems publish only a fraction of the attributes the config binds. First publishing batch (attribute tables are read-only static data; ids kept a dense prefix from each parent's NextAttributeID): - HeatSink table dense-append: DegradationTemperature/FailureTemperature (the condenser temp-bar warn/max endpoints -- were NULL, so the two-part bars could not scale), NormalizedPressure/DegradationPressure/CoolantMassLeakRate, and the HeatSink link. Condenser/Reservoir inherit this -> all 6 condenser temp bars now resolve current/warn/max (verified: BT_GAUGE_ATTR_LOG all OK). - PoweredSubsystem::GetAttributeIndex() (new) publishes InputVoltage->voltageSource -- the cluster power-branch gate (the power-lamp/generator-voltage/state-lamp sub-branch is skipped when it resolves NULL). Flows to Sensor/Myomers/weapons. - MechWeapon::GetAttributeIndex() (new) publishes OutputVoltage/PercentDone-> rechargeLevel; Emitter/PPC/ProjectileWeapon/MissileLauncher/GaussRifle DefaultData re-pointed at it (they carried an EMPTY default-constructed index -> resolved NOTHING). Verified: the ER MED LASER / PPC / STREAK weapon clusters now render live recharge dials (were blank TEMP/STATUS). - Mech::SetTargetRange un-stubbed (radarRange = range) -> the radar map scale + overlay range readout track the mapper's zoom (was frozen at 1000). - GAUGREND ParseAttribute: env-gated per-binding resolution trace (BT_GAUGE_ATTR_LOG) -- durable diagnostic infra for the wave. Verified DBASE+dev gauges: no startup/gauge-construction crash (dense chain intact), combat un-regressed (TARGET DESTROYED), clusters build with InputVoltage resolving. Remaining config-binding NULLs: HeatSink/AmbientTemperature (aggregate bank, P3) + Searchlight/LightOn (P1b). Co-Authored-By: Claude Opus 4.8 (1M context) --- engine/MUNGA/GAUGREND.cpp | 9 +++++++++ game/original/BT/GAUSS.CPP | 2 +- game/original/BT/PPC.CPP | 2 +- game/reconstructed/btstubs.cpp | 10 ++++++++-- game/reconstructed/emitter.cpp | 2 +- game/reconstructed/heat.cpp | 13 ++++++++++++- game/reconstructed/heat.hpp | 9 +++++++++ game/reconstructed/mechweap.cpp | 25 +++++++++++++++++++++++-- game/reconstructed/mechweap.hpp | 22 ++++++++++++++++++++++ game/reconstructed/mislanch.cpp | 2 +- game/reconstructed/powersub.cpp | 25 +++++++++++++++++++++++++ game/reconstructed/powersub.hpp | 21 +++++++++++++++++++++ game/reconstructed/projweap.cpp | 2 +- 13 files changed, 134 insertions(+), 10 deletions(-) diff --git a/engine/MUNGA/GAUGREND.cpp b/engine/MUNGA/GAUGREND.cpp index ed6ea65..67fa8c9 100644 --- a/engine/MUNGA/GAUGREND.cpp +++ b/engine/MUNGA/GAUGREND.cpp @@ -2180,6 +2180,12 @@ void * string_pointer << "' not found\n" ); } + // gauge data-binding wave: a per-binding resolution trace to the + // log file (env BT_GAUGE_ATTR_LOG) -- proves which cockpit bindings + // resolve to a live member vs read NULL (unpublished attribute). + if (getenv("BT_GAUGE_ATTR_LOG")) + DEBUG_STREAM << "[attr] " << string_pointer + << (attribute_pointer ? " OK\n" : " NULL\n") << std::flush; Check_Fpu(); return attribute_pointer; } @@ -2198,6 +2204,9 @@ void * source << "' not found\n" ); } + if (getenv("BT_GAUGE_ATTR_LOG")) + DEBUG_STREAM << "[attr] " << source + << (attribute_pointer ? " OK (entity)\n" : " NULL (entity)\n") << std::flush; Check_Fpu(); return attribute_pointer; } diff --git a/game/original/BT/GAUSS.CPP b/game/original/BT/GAUSS.CPP index 7fabddd..cbc026f 100644 --- a/game/original/BT/GAUSS.CPP +++ b/game/original/BT/GAUSS.CPP @@ -27,7 +27,7 @@ GaussRifle::SharedData GaussRifle::DefaultData( &GaussRifle::ClassDerivations, GaussRifle::MessageHandlers, - GaussRifle::AttributeIndex, + MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty) GaussRifle::StateCount ); diff --git a/game/original/BT/PPC.CPP b/game/original/BT/PPC.CPP index ae7d84a..1838f79 100644 --- a/game/original/BT/PPC.CPP +++ b/game/original/BT/PPC.CPP @@ -33,7 +33,7 @@ PPC::SharedData PPC::DefaultData( &PPC::ClassDerivations, PPC::MessageHandlers, - PPC::AttributeIndex, + MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty) PPC::StateCount ); diff --git a/game/reconstructed/btstubs.cpp b/game/reconstructed/btstubs.cpp index 2446876..0ca706b 100644 --- a/game/reconstructed/btstubs.cpp +++ b/game/reconstructed/btstubs.cpp @@ -123,9 +123,15 @@ Logical Mech::IsDisabled() return (movementMode == 2 || movementMode == 9) ? True : False; } -// TODO(bring-up): writes mech+0x404 (HUD/weapon target range). -void Mech::SetTargetRange(Scalar /*range*/) +// mech+0x404 = the radar display scale. Both the radar map's currentScale and +// the overlay range readout bind Mech/RadarRange -> radarRange. The controls +// mapper slews targetRangeExponent and calls SetTargetRange(250*2^exp) every +// frame (btl4mppr.cpp:407) off the zoom buttons, so writing the member makes the +// radar map zoom + the range numeric track the zoom (default exp 2.0 -> 1000, +// matching the historic frozen value). Faithful body (the binary writes [this+0x404]). +void Mech::SetTargetRange(Scalar range) { + radarRange = range; } // Install the cockpit-mapping subsystem into the Mech's reserved ControlsMapper diff --git a/game/reconstructed/emitter.cpp b/game/reconstructed/emitter.cpp index 85a1775..d02071a 100644 --- a/game/reconstructed/emitter.cpp +++ b/game/reconstructed/emitter.cpp @@ -125,7 +125,7 @@ Emitter::SharedData Emitter::DefaultData( &Emitter::ClassDerivations, Emitter::MessageHandlers, - Emitter::AttributeIndex, + MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty) Emitter::StateCount ); diff --git a/game/reconstructed/heat.cpp b/game/reconstructed/heat.cpp index 2f67c94..c271564 100644 --- a/game/reconstructed/heat.cpp +++ b/game/reconstructed/heat.cpp @@ -332,7 +332,18 @@ const HeatSink::IndexEntry { ATTRIBUTE_ENTRY(HeatSink, CoolantMass, coolantLevel), // @0x12C (live: UpdateCoolant depletes it) ATTRIBUTE_ENTRY(HeatSink, CoolantCapacity, thermalCapacity), // @0x128 - ATTRIBUTE_ENTRY(HeatSink, CurrentTemperature, currentTemperature) // @0x114 (inherited from HeatableSubsystem) + ATTRIBUTE_ENTRY(HeatSink, CurrentTemperature, currentTemperature), // @0x114 (inherited from HeatableSubsystem) + // --- gauge data-binding wave: the config's GenericHeatGauges vertBar binds + // Condenser/DegradationTemperature (@4=warn) + FailureTemperature (@5=max); + // without these the two-part temp bar's warn/max endpoints were NULL -> the + // bar could not scale (division by a zero max). Condenser/Reservoir inherit + // this table, so publishing here covers all 6 condensers + both coolant banks. + ATTRIBUTE_ENTRY(HeatSink, DegradationTemperature, degradationTemperature), // @0x118 (constant warn threshold) + ATTRIBUTE_ENTRY(HeatSink, FailureTemperature, failureTemperature), // @0x11C (constant max threshold) + ATTRIBUTE_ENTRY(HeatSink, NormalizedPressure, heatLoad), // @0x120 (smoothed radiated heat) + ATTRIBUTE_ENTRY(HeatSink, DegradationPressure, coolantEfficiency), // @0x124 + ATTRIBUTE_ENTRY(HeatSink, CoolantMassLeakRate, coolantDraw), // @0x130 (LeakGauge; damage-driven) + ATTRIBUTE_ENTRY(HeatSink, HeatSink, linkedSinks) // @0x164 (Eng linked-sink temp readout) }; HeatSink::AttributeIndexSet& diff --git a/game/reconstructed/heat.hpp b/game/reconstructed/heat.hpp index b39fb89..3b0c0fa 100644 --- a/game/reconstructed/heat.hpp +++ b/game/reconstructed/heat.hpp @@ -334,6 +334,15 @@ inline int CoolantMassAttributeID = HeatableSubsystem::NextAttributeID, CoolantCapacityAttributeID, CurrentTemperatureAttributeID, + // --- dense-append (gauge data-binding wave): the config binds these + // per condenser/heat-sink (L4GAUGE.CFG GenericHeatGauges1/2 + the Eng + // clusters). Ids stay contiguous so AttributeIndexSet::Build has no gap. + DegradationTemperatureAttributeID, // @0x118 constant amber warn line + FailureTemperatureAttributeID, // @0x11C constant max reference + NormalizedPressureAttributeID, // @0x120 heatLoad (smoothed radiated heat) + DegradationPressureAttributeID, // @0x124 coolantEfficiency + CoolantMassLeakRateAttributeID, // @0x130 coolantDraw (damage-driven leak) + HeatSinkAttributeID, // @0x164 linkedSinks (link to master sink) NextAttributeID }; private: diff --git a/game/reconstructed/mechweap.cpp b/game/reconstructed/mechweap.cpp index 09f6149..433b80e 100644 --- a/game/reconstructed/mechweap.cpp +++ b/game/reconstructed/mechweap.cpp @@ -81,13 +81,34 @@ Derivation ); Receiver::MessageHandlerSet MechWeapon::MessageHandlers; -Simulation::AttributeIndexSet MechWeapon::AttributeIndex; +Simulation::AttributeIndexSet MechWeapon::AttributeIndex; // legacy empty member (superseded by GetAttributeIndex) + +// gauge data-binding wave: OutputVoltage/PercentDone -> rechargeLevel (1.0 == +// ready; ComputeOutputVoltage/the charge state machine drive it, so both animate +// as the weapon recharges). Chained to PoweredSubsystem's dense index. +const MechWeapon::IndexEntry + MechWeapon::AttributePointers[]= +{ + ATTRIBUTE_ENTRY(MechWeapon, OutputVoltage, rechargeLevel), // @0x320 (charge/ready level) + ATTRIBUTE_ENTRY(MechWeapon, PercentDone, rechargeLevel) // @0x320 (recharge dial fraction) +}; + +MechWeapon::AttributeIndexSet& + MechWeapon::GetAttributeIndex() +{ + static MechWeapon::AttributeIndexSet attributeIndex( + ELEMENTS(MechWeapon::AttributePointers), + MechWeapon::AttributePointers, + PoweredSubsystem::GetAttributeIndex() + ); + return attributeIndex; +} MechWeapon::SharedData MechWeapon::DefaultData( &MechWeapon::ClassDerivations, MechWeapon::MessageHandlers, - MechWeapon::AttributeIndex, + MechWeapon::GetAttributeIndex(), MechWeapon::StateCount ); diff --git a/game/reconstructed/mechweap.hpp b/game/reconstructed/mechweap.hpp index c09cb9a..342d43f 100644 --- a/game/reconstructed/mechweap.hpp +++ b/game/reconstructed/mechweap.hpp @@ -109,6 +109,28 @@ class CockpitHud; static AttributeIndexSet AttributeIndex; static SharedData DefaultData; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Attribute Support (gauge data-binding wave) + // + // The energy/ballistic weapon engineering clusters read OutputVoltage (the + // charge/ready level -- animates as the weapon recharges after firing) and + // PercentDone (the recharge dial), both onto rechargeLevel (1.0 == ready). + // GetAttributeIndex() chains to PoweredSubsystem's so InputVoltage + the + // HeatSink temps also resolve; Emitter/PPC/ProjectileWeapon/MissileLauncher + // point their DefaultData at MechWeapon::GetAttributeIndex() to inherit it + // (they carried an EMPTY default-constructed index -> resolved NOTHING). + // + public: + enum { + OutputVoltageAttributeID = PoweredSubsystem::NextAttributeID, + PercentDoneAttributeID, + NextAttributeID + }; + private: + static const IndexEntry AttributePointers[]; + public: + static AttributeIndexSet& GetAttributeIndex(); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Update record (replication) -- extends the base Simulation record with // the weapon-specific alarm/idle state appended by WriteUpdateRecord. diff --git a/game/reconstructed/mislanch.cpp b/game/reconstructed/mislanch.cpp index 94b3346..1211d7a 100644 --- a/game/reconstructed/mislanch.cpp +++ b/game/reconstructed/mislanch.cpp @@ -93,7 +93,7 @@ MissileLauncher::SharedData MissileLauncher::DefaultData( &MissileLauncher::ClassDerivations, MissileLauncher::MessageHandlers, - MissileLauncher::AttributeIndex, + MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty) MissileLauncher::StateCount ); diff --git a/game/reconstructed/powersub.cpp b/game/reconstructed/powersub.cpp index 6a6b544..29d30e2 100644 --- a/game/reconstructed/powersub.cpp +++ b/game/reconstructed/powersub.cpp @@ -82,6 +82,31 @@ static const Scalar MinVoltageScale = 1.0f; // _DAT_004b1924 (PowerWatcher thr //########################################################################### //########################################################################### +//############################################################################# +// Attribute Support (gauge data-binding wave) +// +// InputVoltage -> voltageSource (the 0xC connection to the powering Generator; +// the cluster's power-branch gate + the source of GeneratorVoltageConnection). +// Chained to HeatSink's dense index; a function-local static so the parent +// index is safely constructed on first use regardless of static-init order. +// +const PoweredSubsystem::IndexEntry + PoweredSubsystem::AttributePointers[]= +{ + ATTRIBUTE_ENTRY(PoweredSubsystem, InputVoltage, voltageSource) // @0x1D0 +}; + +PoweredSubsystem::AttributeIndexSet& + PoweredSubsystem::GetAttributeIndex() +{ + static PoweredSubsystem::AttributeIndexSet attributeIndex( + ELEMENTS(PoweredSubsystem::AttributePointers), + PoweredSubsystem::AttributePointers, + HeatSink::GetAttributeIndex() + ); + return attributeIndex; +} + //############################################################################# // Shared Data Support // diff --git a/game/reconstructed/powersub.hpp b/game/reconstructed/powersub.hpp index bffa89c..cc57ab1 100644 --- a/game/reconstructed/powersub.hpp +++ b/game/reconstructed/powersub.hpp @@ -90,6 +90,27 @@ class Generator; AutoConnect = 2 // scan all GeneratorClassID segments and attach }; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Attribute Support (gauge data-binding wave) + // + // The engineering subsystem clusters bind PoweredSubsystem/InputVoltage -- + // the power-source gate that decides whether the power-lamp / generator- + // voltage-bar / state-lamp sub-branch is even constructed (SubsystemCluster + // ctor skips it when InputVoltage resolves NULL). Published on top of + // HeatSink's dense index; Sensor/Myomers/MechWeapon chain to GetAttributeIndex() + // below and inherit it (so InputVoltage + the HeatSink temps reach every + // powered/weapon subsystem's cluster). + // + public: + enum { + InputVoltageAttributeID = HeatSink::NextAttributeID, + NextAttributeID + }; + private: + static const IndexEntry AttributePointers[]; + public: + static AttributeIndexSet& GetAttributeIndex(); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Streamed model resource. // diff --git a/game/reconstructed/projweap.cpp b/game/reconstructed/projweap.cpp index 986982b..401ee5d 100644 --- a/game/reconstructed/projweap.cpp +++ b/game/reconstructed/projweap.cpp @@ -130,7 +130,7 @@ ProjectileWeapon::SharedData ProjectileWeapon::DefaultData( &ProjectileWeapon::ClassDerivations, ProjectileWeapon::MessageHandlers, - ProjectileWeapon::AttributeIndex, + MechWeapon::GetAttributeIndex(), // gauge wave: inherit temps/InputVoltage/OutputVoltage/PercentDone (was empty) ProjectileWeapon::StateCount );