diff --git a/docs/GAUGE_COMPOSITE.md b/docs/GAUGE_COMPOSITE.md index 1a7e58c..efeb757 100644 --- a/docs/GAUGE_COMPOSITE.md +++ b/docs/GAUGE_COMPOSITE.md @@ -474,3 +474,86 @@ fill the gaps (bind not-yet-needed ids to a shared read-only pad member). bindings -- reads the mech's subsystem roster internally). Find its Make/ctor/Execute via the .data methodDescription table (the "vehicleSubSystems" string is a .data const, not inlined in the decomp). A focused effort (~250-300 lines across 3-5 functions). + +--- + +## Gauge data-binding wave (2026-07) — publish attributes + reconstruct unbuilt widgets + +Goal (user directive: "all of this needs to work as the game originally played. no shortcuts"): make every +cockpit gauge read + reflect LIVE game state exactly as the 1995 pod. The `gauge-databinding-map` workflow +(5 surface maps + 2 adversarial verifies) mapped every binding → subsystem/member → published-vs-missing → +authentic value → fix. **KEY REFRAME (verified against the shipped .SUB data + the binary sim): the Heat MFD +is authentically NEAR-STATIC, not a live heat-accumulation display.** `currentTemperature = +heatEnergy/thermalMass`; ThermalMass=1.39e6, so heatEnergy_init = 1.07e8 whose float32 ULP is 8.0 — a +3.5-heat laser shot ROUNDS TO ZERO (reaching Degradation 5000 needs ~2 billion shots). Coolant is +DAMAGE-gated (not fire), the valve moves only on player toggle, AmbientTemperature is a hardcoded 300. So the +wave is **"publish attributes + reconstruct unbuilt widgets + wire the 2 damage-driven formulas + the valve", +NOT "revive a dynamic heat model"**. Diagnostic infra: `BT_GAUGE_ATTR_LOG` (GAUGREND ParseAttribute logs +`[attr] OK/NULL` per config binding). + +### Phase 1 — PUBLISHING (DONE; commits 070af40, 64975ec) +Most gauges resolved NULL because the reconstructed subsystems published only a fraction of the bound +attributes. All ids kept a dense prefix from each parent's `NextAttributeID` (AttributeIndexSet::Build +strcmps every slot — a gap AVs). Done: +- **HeatSink table** dense-append: DegradationTemperature@0x118 / FailureTemperature@0x11C (the condenser + temp-bar warn/max endpoints — were NULL so the two-part bars couldn't scale), NormalizedPressure→heatLoad, + DegradationPressure→coolantEfficiency, CoolantMassLeakRate→coolantDraw, HeatSink→linkedSinks. Condenser/ + Reservoir inherit it → all 6 condenser temp bars resolve. +- **PoweredSubsystem::GetAttributeIndex()** (new) publishes InputVoltage→voltageSource@0x1D0 — the cluster + power-branch gate (SubsystemCluster ctor skips the power-lamp/generator-voltage/state-lamp sub-branch when + it resolves NULL). Flows to Sensor/Myomers/weapons automatically (they chain to it). +- **MechWeapon::GetAttributeIndex()** (new) publishes OutputVoltage/PercentDone→rechargeLevel@0x320; + 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 (SegmentArc270) — were blank TEMP/STATUS.** +- **Searchlight** publishes "LightOn"→lightState@0x1D8 (config binds the NAME "LightOn"; enum renamed + LightOnAttributeID). **Mech::SetTargetRange** un-stubbed (radarRange=range) → radar map scale + range + readout track the mapper's zoom. +- **FIXED a genuine recon bug** (heat.cpp UpdateCoolant + heatfamily_reslice RefrigerationSimulation): both + read `linkedSinks.Resolve()->heatEnergy` where the binary reads `*(this[0x38]+0x158)` = the subsystem's + OWN DamageZone.damageLevel (@0xE0). The misread would SLAM the live CoolantMass bars to empty (coolantDraw + = master.heatEnergy~1.3e7 * heatLoad) and clamp massScale permanently to 1.0. Now reads + `this->Subsystem::damageZone->damageLevel` (qualified past the MechSubsystem shadow) → 0 undamaged → + no leak / massScale 3.0 = authentic pristine. **After P1 every config-string binding resolves OK except + HeatSink/AmbientTemperature** (needs the aggregate bank, P3). + +### Phase 2 — WIDGET RECONSTRUCTION (TODO — the biggest remaining visible gaps) +These config keywords are UNREGISTERED in `BTL4MethodDescription[]` (btl4grnd.cpp) so their lines are +parse-skipped and the gauge is never built (even after publishing). Each needs a REAL Make/ctor/Execute/dtor +(no prose — the /FORCE trap: a prose-only vtable slot AVs on first call) + registration. Grep the link log +for `unresolved external ` before registering. +- **VertNormalSlider** (condenser valve slider @2; vtable PTR_FUN_00518c34) + **LeakGauge** (== BitMapInverseWipe + @4c5b7c, @6) — completes the heat panel's valve + leak columns. +- **GeneratorCluster** (ctor @004c746c + 5 children: VertTwoPartBar @004c4724, ScalarBarGauge @004c72ac, + LeakGauge @004c5b7c, 2× TwoState @004739d8 + real dtor @004c7778) — the 4 generator panels (buttons 9-12, + currently blank). Publish the Generator OutputVoltage→outputVoltage@0x1DC entry (temps inherit HeatSink). +- **PilotList** (Comm KILLS/DEATHS/SELECT-TARGET/NEAREST roster; Make @004ca90c, ctor @004ca958 reads + DAT_0051af88 8×3 layout, BecameActive @004caba4, Execute @004cabd0, dtor @004cab10, DrawMechIcon @004cad70). + Roster FEED is already live (MechControlsMapper::FillPilotArray). Needs DAT_0051af88 PE-parsed (24 ints). +- **PrepEngrScreen** (Make @004c7b30/ctor @004c7bf0/Execute @004c7e48) — engineering full-screen readout. +- **SectorDisplay** (ctor @004c9e10/Execute @004ca07c) + **MessageBoard** (Make @004cb678) — sector coords + + message board (lower priority). **SeekVoltageGraph::Execute** (@004c6934) + real FindAttributeIndex (the + weapon-cluster seek-voltage plot; currently an inert stub). + +### Phase 3 — SIM/MODEL (TODO) +- **Aggregate HeatSink-bank** (0xBBE; ctor @4ae8d0, sim @4ae73c, parser @4ae9dc — currently #if 0'd in + heatfamily_reslice.cpp, a plain HeatSink built instead): publish HeatSinkCount + AmbientTemperature (a + constant 300); rewire CreateHeatSinkBankSubsystem + bump the alloc. Clears the last config-binding NULL. +- **Condenser valve**: reconstruct SetValveSetting (vtable+0x48 → coolantFlowScale@0x15C) + the real MoveValve + msg-handler @4ae464 (the reconstructed @4afbe0 is MISLABELED = the MechControlsMapper) so a player valve + toggle moves the slider AND changes heat flow. Add the Condenser OWN table (ValveSetting→coolantFlowScale) + + #if 0 the heat.cpp Condenser dup-ctor stubs (the real heatfamily_reslice def currently wins by link order). +- **Combat SCORING feed** (KILLS/SCORE/RANK): bring-up combat delivers Entity::TakeDamageMessage without + posting BTPlayer ScoreMessages (ScoreInflicted @004c0200, ScoreMessageHandler @004c02e4, VehicleDead + @004c012c) → killCount@0x27c/currentScore@0x278/playerRanking stay 0. Wire the weapon-death→ScoreMessage path. +- **duckState** writer (button-4 crouch lamp; only ctor-zeroed today). + +### Phase 4 — DEEP/DEFERRED +Byte-exact re-base of the PoweredSubsystem:HeatSink LEAF (Sensor/Emitter/Myomers) → radarPercent heat penalty +(delete the sensor.cpp:287-288 guard) + the raw-offset cluster reads; radar pip/name symbology + staticEntities; +CycleDisplayMode→ModeManager mask (activates cmArmor/cmCrit/cmHeat schematic layers); numericSpeed units. + +### Authentically-static (do NOT "fix") +currentTemperature ~77 (huge thermalMass); Degradation/Failure temps (fixed threshold markers); AmbientTemperature +300; MaxRunSpeed (per-mech constant); cmArmor/cmCrit all-green (undamaged solo player — BT is PvP-only); +CoolantMassLeakRate 0 on a pristine mech (damage-gated); Player RANK 1 in solo.