P7: byte-exact re-base of the CORE heat leaf (HeatSink/Condenser/Reservoir/Generator/Myomers)

The reconstruction modeled the binary's shared alarm/connection types with undersized
stand-ins, sliding every field above them low (the 72-byte auxScreenNumber gap). Fix the
foundational heat-leaf classes byte-exact + static_assert-lock them, from the ctor decomp:

Shared types corrected:
  * SubsystemConnection 4 -> 0xC  (binary link node FUN_004af9cf; FUN_00417ab4 derefs +8)
  * GaugeAlarm54 = 0x54           (real AlarmIndicator FUN_0041b9ec; STATUS level at +0x14,
                                   so subsystem+0x184 == heatAlarm+0x14 == GetLevel())
    WatcherGaugeAlarm now typedefs GaugeAlarm54 (Watcher branch locks stay valid).

Byte-exact + locked (ctor-verified):
  * HeatSink   heatEnergy@0x158 linkedSinks@0x164 heatAlarm@0x170 resource@0x1C4
               pendingHeat@0x1C8, sizeof 0x1D0  (@004adda0)
  * Condenser  valveState@0x1D0 condenserAlarm@0x1DC  (@004ae568)
  * Reservoir  reservoirAlarm@0x1D0 ... squirtEfficiency@0x22C, sizeof 0x230  (@4aef78)
  * Generator  stateAlarm@0x1FC, sizeof 0x250  (@004b225c)
  * Myomers    phantom moverConnection tail removed (fits 0x358)

Three systemic bug classes fixed (added to the checklist in CLAUDE.md / HARD_PROBLEMS.md):
  * alias field    - a subclass member re-declaring an inherited slot the ctor reuses
                     (Condenser refrigerationOutput==massScale@0x160; Reservoir
                     coolantCapacity==thermalCapacity@0x128) -> use the inherited name
  * alarm-interior - a value read at alarm+0x14 modeled as a separate member
                     (HeatSink heatState@0x184, Reservoir injectActive@0x1e4)
                     -> route to alarm.GetLevel()
  * phantom field  - a member past the object end (Generator shortFlag@0x25C is really
                     *(owner+0x190)+0x25c the msg-manager, @004b0efc; Myomers
                     moverConnection@0x110 a write-only base slot) -> remove it

Heat conduction now reads the REAL heatEnergy=1.34e7 (not garbage); combat DESTROYED-in-8,
0 crashes, heapcheck-clean through construction.

REMAINING (measured; a distinct larger task): making PoweredSubsystem byte-exact grows it
+0x98 and cascades into MechWeapon/Emitter/PPC/Sensor/Myomers -- all model the 0x54
AlarmIndicator with 4-byte ReconAlarm / 8-byte HeatAlarm stand-ins and are short +
phantom-tailed; retyping without byte-exacting them overflows the Emitter alloc (heap
corruption). PoweredSubsystem kept on HeatAlarm(8) stand-ins (marked) pending a
subsystem-tree ALARM UNIFICATION. See docs/HARD_PROBLEMS.md P7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-07 09:47:37 -05:00
co-authored by Claude Opus 4.8
parent 16ad741282
commit 1356870e56
11 changed files with 251 additions and 78 deletions
+35
View File
@@ -237,3 +237,38 @@ point tagged `pre-heatleaf-rebase`. The `heat-leaf-layout-audit` workflow derive
MEASUREMENT TECHNIQUE (reusable): a `template<int N> struct HSOff;` + `friend struct XProbe;` +
`struct XProbe { HSOff<offsetof(Class, field)> a; ... };` -- the "uses undefined struct 'HSOff<NNN>'" build
error reports each field's COMPILED offset in decimal. Compare to the binary offset from the ctor decomp.
### P7 — STATUS: the CORE heat leaf is DONE + byte-exact; PoweredSubsystem + the weapon subtree are a larger follow-up
**✅ DONE this session (byte-exact, static_assert-LOCKED, combat+heat verified):** `HeatSink`,
`HeatableSubsystem`, `Condenser`, `Reservoir`, `Generator` (a HeatSink subclass, alloc 0x250) + `Myomers`
(phantom tail removed). The shared types are now correct: **`SubsystemConnection` = 0xC** (was 4; the binary
`FUN_004af9cf` link node, `FUN_00417ab4` two-level derefs +8) and **`GaugeAlarm54` = 0x54** (the real
`AlarmIndicator` from `FUN_0041b9ec`; its STATUS level is at **+0x14**, so `subsystem+0x184 == heatAlarm+0x14
== GetLevel()`; `WatcherGaugeAlarm` is now a typedef of it). Confirmed from the ctors: `HeatSink` heatEnergy@0x158,
linkedSinks@0x164, heatAlarm@0x170, resource@0x1C4, pendingHeat@0x1C8, sizeof **0x1D0** (ctor @004adda0);
`Condenser` valveState@0x1D0, condenserAlarm@0x1DC (ctor @004ae568); `Reservoir` reservoirAlarm@0x1D0,
sizeof **0x230** (raw @4aef78); `Generator` stateAlarm@0x1FC, sizeof **0x250** (ctor @004b225c). Heat conduction
now reads the REAL `heatEnergy=1.34e7` (not garbage); combat DESTROYED-in-8, 0 crashes, heapcheck-clean through
construction. `heatState`/`heatModelFlag`/`field_1d0` deleted (reads -> `heatAlarm.GetLevel()` / `coolantActive`);
several **alias-field bugs** fixed the same way (Condenser `refrigerationOutput`==inherited `massScale@0x160`;
Reservoir `coolantCapacity`==inherited `thermalCapacity@0x128`, `injectActive`==`reservoirAlarm.GetLevel()`) and
**phantom fields** removed (Generator `shortFlag@0x25C` -- really `*(owner+0x190)+0x25c`, the msg-manager, raw
@004b0efc; Myomers `moverConnection@0x110` -- a write-only base slot).
**◐ REMAINING (the newly-measured true scope -- a SYSTEMIC alarm-stand-in unification, NOT a heat-only fix):**
making **`PoweredSubsystem`** byte-exact (its two 0x54 alarms `electricalStateAlarm@0x264`/`modeAlarm@0x2B8`)
grows it **+0x98**, which cascades into EVERY subclass -- `MechWeapon`, `Emitter`, `PPC`, `Myomers`, `Sensor` --
and they ALL model the binary's 0x54 `AlarmIndicator` with 4-byte `ReconAlarm` (`weaponAlarm`) or 8-byte
`HeatAlarm` stand-ins, so they are themselves short AND carry phantom/duplicate/misplaced tail fields
(measured: `MechWeapon` 0x3C4 vs binary 0x3F0 with ~0x24 of phantom tail; `Emitter` 0x4B0 vs binary 0x478 with
a duplicate `outputVoltage`==`rechargeLevel@0x320` + ~0x64 of oversized own fields). Retyping PoweredSubsystem's
alarms WITHOUT also byte-exacting MechWeapon/Emitter/PPC overflowed the Emitter factory alloc (0x478) -> a heap
corruption during Emitter construction. So PoweredSubsystem was kept on `HeatAlarm(8)` stand-ins (marked in
powersub.hpp) and the whole tree still fits its allocs. **The real fix is a subsystem-tree ALARM UNIFICATION:
retype every binary-`AlarmIndicator` stand-in (ReconAlarm/HeatAlarm) to `GaugeAlarm54(0x54)` and de-phantom
each weapon/power class against its ctor** (MechWeapon @004b99a8, Emitter @004ba478, PPC, Sensor). Each class
needs: alarm retype + phantom/alias-field removal + a `static_assert` sizeof/offset lock vs its factory alloc.
This is a multi-class effort (a separate task, bigger than the heat leaf) -- the core heat leaf above is the
foundational prerequisite and is complete. Same techniques apply (ctor decomp -> offsets, alias/phantom checks,
`friend struct XLayoutCheck` locks). The aux-screen gauge reads + Sensor RadarPercent stay approximate until then.