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
+20 -11
View File
@@ -160,8 +160,9 @@ MechWeapon::MechWeapon(
rangeToTarget = 0.0f; // 0x324
targetWithinRange = False; // 0x34C
segmentReference = segmentPageIndex;
pipSegment = -1;
// segmentReference/pipSegment were phantom tail members (past the binary 0x3F0 end);
// removed. The muzzle segment index is the inherited base slot this+0xdc (used by
// GetMuzzlePoint); the pip mount is gated by useConfiguredPip in DrawWeaponPip.
// usesExternalModel: True if the model name contains the marker substring
// (DAT_00511aa2); selects whether the configured pip is drawn.
@@ -358,17 +359,23 @@ void
char *o = (char *)owner; // inherited MechSubsystem::owner (the Mech)
if (o != 0)
position = *(Point3D *)(o + 0x37c); // MECH_TARGET_POS
else
position = targetPoint;
// (no owner -> leave position unchanged; the phantom `targetPoint` member is gone --
// binary GetTargetPosition @004b9cbc unconditionally reads the owner target slot.)
}
// Faithful FUN_004b9948 muzzle resolve, defined in mech4.cpp (a complete-Mech TU);
// mechweap.cpp reaches the owner Mech only as a raw pointer, so it bridges via void*.
extern void BTResolveWeaponMuzzle(void *ownerMech, int segIndex, Point3D &out);
//
// @004b9cdc -- direction from a supplied point to the weapon mount (entity+0x37c).
//
void
MechWeapon::GetVectorToWeapon(const Point3D &from, Vector3D &direction)
{
direction.Subtract(muzzlePoint, from); // FUN_00408644
Point3D muzzle;
GetMuzzlePoint(muzzle); // resolve live (phantom muzzlePoint member removed)
direction.Subtract(muzzle, from); // FUN_00408644
}
//
@@ -378,11 +385,13 @@ void
void
MechWeapon::GetMuzzlePoint(Point3D &point)
{
// @004b9948 -- the shipped code resolves segmentReference against the owning
// Mech's segment table (SegmentIterator) and transforms the segment origin to
// world space. That transform is produced by the owning Mech's per-frame
// segment update, which caches it in muzzlePoint; we return the cached value.
point = muzzlePoint; // FUN_00424da8 result
// @004b9948 -- resolve the weapon's mount segment (its index is the inherited
// subsystem slot at this+0xdc, == binary param_1+0xdc) in the owning Mech's
// segment table and transform it to world (FUN_00424da8). Reads live each call
// (the phantom cached `muzzlePoint` member is removed -- it collided with the
// Emitter subclass's own fields at 0x3F0+).
int segIndex = *(int *)((char *)this + 0xdc); // inherited segment index (byte-exact base)
BTResolveWeaponMuzzle(owner, segIndex, point); // mech4.cpp bridge (null-guarded)
}
//
@@ -422,7 +431,7 @@ void
// CROSS-FAMILY (hud): the cockpit HUD manager AddElement entry point lives in
// the HUD module; the submission is wired there. We retain the gating and
// transform reference here.
if (this->pipSegment != -1) // FUN_0041a1a4 guard
if (useConfiguredPip) // this+0x3E0 (weapon draws a configured pip)
{
(void)transform;
(void)pipPosition;