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
+34 -13
View File
@@ -1295,19 +1295,40 @@ two-level-derefs +8) and **`GaugeAlarm54` = 0x54** (the real `AlarmIndicator` `F
conduction reads the REAL `heatEnergy=1.34e7`; combat DESTROYED-in-8, 0 crashes, heapcheck-clean through
construction. **THREE new systemic bug classes fixed (add to the checklist):** (1) an **alias field** = a subclass
member re-declaring an inherited slot the ctor reuses under a new name (Condenser `refrigerationOutput`==inherited
`massScale@0x160`; Reservoir `coolantCapacity`==`thermalCapacity@0x128`) → delete the member, use the inherited
name; (2) an **alarm-interior field** = a value the binary reads at `alarm+0x14` modeled as a separate member
(HeatSink `heatState@0x184`, Reservoir `injectActive@0x1e4`) → route to `alarm.GetLevel()`; (3) a **phantom field**
= a member at an offset PAST the object (Generator `shortFlag@0x25C` is really `*(owner+0x190)+0x25c` the msg-manager;
Myomers `moverConnection@0x110` a write-only base slot) → remove it, read the real source. **⚠ THE REMAINING SCOPE
(measured, larger than the heat leaf):** making `PoweredSubsystem` byte-exact grows it +0x98 (two 0x54 alarms) and
cascades into EVERY subclass — `MechWeapon`/`Emitter`/`PPC`/`Sensor`/`Myomers` ALL model the 0x54 `AlarmIndicator`
with 4-byte `ReconAlarm` (`AlarmIndicator`==`ReconAlarm`==4B!) or 8-byte `HeatAlarm` stand-ins and are short +
phantom-tailed; retyping PoweredSubsystem's alarms without also byte-exacting them overflows the Emitter alloc
(0x478) → heap corruption. So PoweredSubsystem stays on `HeatAlarm(8)` stand-ins (marked, powersub.hpp) pending a
**subsystem-tree ALARM UNIFICATION** (retype every stand-in to `GaugeAlarm54` + de-phantom each weapon/power class
vs its ctor + lock) — a distinct multi-class task. `runtime Check(sizeof<=alloc)` in a factory bridge does NOT fail
the build (it's a runtime assert); use a `static_assert` sizeof lock to catch alloc overflow at COMPILE time.
`massScale@0x160`; Reservoir `coolantCapacity`==`thermalCapacity@0x128`; Emitter `outputVoltage`==inherited
`rechargeLevel@0x320`; Emitter `beamLengthRatio`==`beamScale.z@0x434`; Emitter `firingArmed`==inherited
`useConfiguredPip@0x3E0`) → delete the member, use the inherited/existing name; (2) an **alarm-interior field** = a
value the binary reads at `alarm+0x14` modeled as a separate member (HeatSink `heatState@0x184`, Reservoir
`injectActive@0x1e4`) → route to `alarm.GetLevel()`; (3) a **phantom field** = a member at an offset PAST the object
(Generator `shortFlag@0x25C` is really `*(owner+0x190)+0x25c` the msg-manager; Myomers `moverConnection@0x110` a
write-only base slot; MechWeapon `segmentReference/pipSegment/hasTarget/targetPoint/muzzlePoint` past 0x3F0; Emitter
`beamHitPoint/beamImpact/beamImpactScalar/beamColor/beamHitData/energyRampTime` — binary writes into inherited
`damageData`/`voltageScale` or the value is a method local) → remove it, read the real source or use a local.
**✅✅ SUBSYSTEM-TREE ALARM UNIFICATION — DONE: the WHOLE PoweredSubsystem weapon/power subtree is now byte-exact
(P7 CLOSED; full per-class map from the 8-agent `alarm-unification-map` workflow, saved scratchpad/alarm_unify_maps.txt).**
Making `PoweredSubsystem` byte-exact (its two 0x54 alarms `electricalStateAlarm@0x264`/`modeAlarm@0x2B8`) grows it
+0x98, cascading into every subclass — so all were byte-exacted TOGETHER in one build (each `static_assert`-locked
against its ctor + factory alloc; the chain HeatSink 0x1D0 → **PoweredSubsystem 0x31C** → **MechWeapon 0x3F0** →
**Emitter 0x478** → PPC 0x478; PoweredSubsystem → **Sensor 0x328**, → **Myomers 0x358**, all verified against the raw
ctors @004b0f74/@004b99a8/@004bb120/@004b1d18/@004b8fec). Each class: retype its `AlarmIndicator`(==`ReconAlarm`==4B)
/`HeatAlarm`(8B) stand-in for the binary 0x54 alarm → `GaugeAlarm54`; delete phantom/alias/duplicate fields;
`friend struct XLayoutCheck` with `offsetof`/`sizeof` locks (protected members need the friend; public fields work
at namespace scope). **KEY non-layout fixes required in the same wave:** (a) `outputVoltage`→`rechargeLevel` also in
the compiled **`GAUSS.CPP:74/93`** (external readers of the removed Emitter field — grep EVERY TU, not just the
class's own .cpp); (b) `MechWeapon::GetMuzzlePoint` reimplemented faithfully (the removed `muzzlePoint` member
collided with Emitter's own fields at 0x3F0) via a **`BTResolveWeaponMuzzle` void\* bridge in mech4.cpp** (a
complete-Mech TU with the segment API; mechweap.cpp reaches `owner` only as a raw pointer) resolving the weapon's
mount segment (inherited `this+0xdc`) through `GetSegment(int)`+segment→world; (c) `DetachFromVoltageSource` was
setting the WRONG alarm (`modeAlarm`→`electricalStateAlarm`, raw @004b0e30 writes 0x264). Verified: combat
DESTROYED-in-8, 28 shots, 0 crashes, heat `heatEnergy=1.34e7`, all locks pass. **The vehicleSubSystems aux-screen
gauge raw reads (`btl4gau2.cpp:868/952` at `subsystem+0x2b8`/`+0x278`) + the Sensor RadarPercent path now read the
CORRECT byte offsets** (they were garbage under the short layout). ⚠ `runtime Check(sizeof<=alloc)` in a factory
bridge does NOT fail the build (it's a runtime assert → heap overflow at construction); use a `static_assert`
sizeof lock to catch alloc overflow at COMPILE time (this is why the earlier isolated PoweredSubsystem retype
silently overflowed the Emitter alloc). **TECHNIQUE:** a read-only decomp-mapping Workflow (one agent per class
decodes its ctor → offset map + phantom/alias findings, + adversarial verify of the composed chain) then hands-on
implementation — the right shape for a systemic multi-class byte-exact re-base.
**✅ MechControlsMapper REVIVED — the real input-interpretation tick runs LIVE (gated `BT_REAL_CONTROLS=1`).**
The fully-reconstructed mapper family (mechmppr.cpp `MechControlsMapper` @004afbe0-@004b08c0; btl4mppr.cpp