P7: subsystem-tree alarm unification -- the whole PoweredSubsystem weapon/power subtree is byte-exact

The reconstruction modeled the binary's 0x54 AlarmIndicator (FUN_0041b9ec) with undersized
stand-ins (AlarmIndicator==ReconAlarm==4B; HeatAlarm==8B) across the entire subsystem tree, so
every field above an alarm sat at the wrong compiled offset.  Retype every such stand-in to
GaugeAlarm54(0x54) and de-phantom each class against its ctor, so the whole PoweredSubsystem
subtree becomes byte-exact.  An 8-agent read-only decomp-mapping workflow decoded every ctor
first; then hands-on implementation.  static_assert-locked chain (verified vs the raw ctors):

  HeatSink 0x1D0
   -> PoweredSubsystem 0x31C   retype electricalStateAlarm@0x264 + modeAlarm@0x2B8   @004b0f74
   -> MechWeapon      0x3F0   retype weaponAlarm@0x350; delete 5 phantom tail fields  @004b99a8
   -> Emitter         0x478   delete outputVoltage/beamLengthRatio/firingArmed aliases
                              + beamHit*/beamColor/beamHitData/energyRampTime phantoms;
                              retype beamOrientation EulerAngles->Quaternion(16B)       @004bb120
   -> PPC             0x478   (no own fields)                                           @004bb888
  PoweredSubsystem -> Sensor  0x328   (no alarm; 3 own fields)                          @004b1d18
  PoweredSubsystem -> Myomers 0x358   (no alarm)                                        @004b8fec

New systemic bug-class instances fixed (added to the CLAUDE.md checklist):
  * alias field    - Emitter outputVoltage==inherited rechargeLevel@0x320; beamLengthRatio==
                     beamScale.z@0x434; firingArmed==inherited useConfiguredPip@0x3E0
  * phantom field  - MechWeapon segmentReference/pipSegment/hasTarget/targetPoint/muzzlePoint
                     (past 0x3F0); Emitter beamHitPoint/beamImpact/beamImpactScalar/beamColor/
                     beamHitData/energyRampTime (binary writes inherited damageData/voltageScale
                     or the value is a method local)

Non-layout fixes required in the same wave:
  * outputVoltage->rechargeLevel also in the compiled GAUSS.CPP:74/93 (external readers of the
    removed Emitter field -- must grep EVERY TU, not just the class's own .cpp)
  * MechWeapon::GetMuzzlePoint reimplemented faithfully (removed muzzlePoint collided with
    Emitter's own fields at 0x3F0) via a BTResolveWeaponMuzzle void* bridge in mech4.cpp,
    resolving the weapon's mount segment (inherited this+0xdc) through the owner segment table
  * DetachFromVoltageSource fixed to set electricalStateAlarm not modeAlarm (raw @004b0e30
    writes the 0x264 alarm)

The vehicleSubSystems aux-screen gauge raw reads (btl4gau2.cpp:868/952 at subsystem+0x2b8/+0x278)
and the Sensor RadarPercent path now read the CORRECT byte offsets (garbage under the short layout).

Verified: combat DESTROYED-in-8, 28 shots, 0 crashes, heat heatEnergy=1.34e7, every static_assert
lock passes, heapcheck-clean through construction (the phase the isolated PoweredSubsystem retype
had overflowed).  LESSON: a factory-bridge runtime Check(sizeof<=alloc) does NOT fail the build --
only a static_assert sizeof lock catches alloc overflow at compile time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-07 11:49:58 -05:00
co-authored by Claude Opus 4.8
parent 1356870e56
commit 9d82be46a1
13 changed files with 235 additions and 140 deletions
+24 -16
View File
@@ -256,19 +256,27 @@ Reservoir `coolantCapacity`==inherited `thermalCapacity@0x128`, `injectActive`==
**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.
**✅✅ SUBSYSTEM-TREE ALARM UNIFICATION -- DONE (P7 CLOSED; the whole PoweredSubsystem weapon/power subtree is
byte-exact).** Making **`PoweredSubsystem`** byte-exact (its two 0x54 alarms `electricalStateAlarm@0x264`/
`modeAlarm@0x2B8`) grows it **+0x98**, cascading into EVERY subclass -- `MechWeapon`, `Emitter`, `PPC`, `Myomers`,
`Sensor` -- which ALL modeled the binary 0x54 `AlarmIndicator` with 4-byte `ReconAlarm`/8-byte `HeatAlarm`
stand-ins and were short + phantom-tailed. All byte-exacted TOGETHER in one build (an 8-agent read-only
decomp-mapping workflow decoded every ctor first; full map in scratchpad/alarm_unify_maps.txt), each
`static_assert`-locked against its ctor + factory alloc. The verified chain:
HeatSink 0x1D0 -> **PoweredSubsystem 0x31C** (retype 2 alarms; ctor @004b0f74) ->
**MechWeapon 0x3F0** (weaponAlarm@0x350 ReconAlarm->GaugeAlarm54; delete 5 phantom tail fields; ctor @004b99a8) ->
**Emitter 0x478** (delete outputVoltage/beamLengthRatio/firingArmed aliases + beamHit*/beamColor/beamHitData/
energyRampTime phantoms; retype beamOrientation EulerAngles->Quaternion 16B; ctor @004bb120) ->
**PPC 0x478** (no own fields; ctor @004bb888);
PoweredSubsystem -> **Sensor 0x328** (no alarm, 3 own fields; ctor @004b1d18);
PoweredSubsystem -> **Myomers 0x358** (no alarm; ctor @004b8fec).
**Non-layout fixes in the same wave:** (a) `outputVoltage`->`rechargeLevel` also in compiled `GAUSS.CPP:74/93`
(external readers of the removed Emitter field -- grep EVERY TU); (b) `MechWeapon::GetMuzzlePoint` reimplemented
(the removed `muzzlePoint` collided with Emitter's own fields) via a `BTResolveWeaponMuzzle` void* bridge in
mech4.cpp resolving the weapon's mount segment (`this+0xdc`) through the owner segment table; (c)
`DetachFromVoltageSource` fixed to set `electricalStateAlarm` not `modeAlarm` (raw @004b0e30 writes 0x264).
VERIFIED: combat DESTROYED-in-8, 28 shots, 0 crashes, heat `heatEnergy=1.34e7`, all locks pass, heapcheck-clean
through construction (the exact phase the isolated PoweredSubsystem retype had overflowed). **The vehicleSubSystems
aux-screen gauge raw reads (`btl4gau2.cpp:868/952` at `subsystem+0x2b8`/`+0x278`) + Sensor RadarPercent now read
the CORRECT byte offsets** (garbage under the short layout). ⚠ LESSON: a factory-bridge `runtime Check(sizeof<=alloc)`
does NOT fail the build -- use a `static_assert` sizeof lock to catch alloc overflow at COMPILE time.