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:
co-authored by
Claude Opus 4.8
parent
1356870e56
commit
9d82be46a1
@@ -199,20 +199,20 @@ void
|
||||
|
||||
Vector3D beamVector;
|
||||
beamVector.Subtract(targetPoint, muzzlePoint); // FUN_00408bf8
|
||||
ComputeAimOrientation(beamHitData, beamVector); // FUN_004b9864 -> aim matrix
|
||||
LinearMatrix aimTransform; // was the phantom beamHitData member
|
||||
ComputeAimOrientation(aimTransform, beamVector); // FUN_004b9864 -> aim matrix
|
||||
|
||||
Vector3D delta;
|
||||
delta.Subtract(targetPoint, muzzlePoint); // FUN_00408644
|
||||
Scalar dist = (Scalar)Sqrt(delta.x*delta.x + delta.y*delta.y + delta.z*delta.z); // FUN_004dd138
|
||||
beamLengthRatio = dist / graphicLength; // 0x434 = dist / 0x438
|
||||
beamScale.z = dist / graphicLength; // 0x434 (== beamScale[2]) = dist / 0x438
|
||||
|
||||
if (dist <= effectiveRange) // this+0x328
|
||||
{
|
||||
// record the hit point + impact and register the pip / fire marker
|
||||
beamHitPoint = targetPoint; // 0xf2 <- targetPoint
|
||||
beamImpact = delta; // 0xec <- delta
|
||||
beamImpactScalar = damagePortion; // 0xeb <- 0x113
|
||||
DrawWeaponPip(beamHitData); // FUN_004b9728
|
||||
// The binary writes the impact point/delta/energy into the inherited Damage
|
||||
// damageData (0x3C8/0x3B0/0x3AC); the recon's beamHitPoint/beamImpact/
|
||||
// beamImpactScalar copies were dead (no readers) -> removed. Register the pip.
|
||||
DrawWeaponPip(aimTransform); // FUN_004b9728
|
||||
}
|
||||
|
||||
// stash the beam endpoint for replication (world hit point)
|
||||
@@ -285,7 +285,7 @@ void
|
||||
case 2: // Loaded -- ready; fire on the trigger's rising edge
|
||||
if (fireEdge)
|
||||
{
|
||||
if (firingArmed && HasActiveTarget()) // this+0x3e8 && entity+0x388
|
||||
if (useConfiguredPip && HasActiveTarget()) // this+0x3E0 (Loaded->Firing gate) && entity+0x388
|
||||
{
|
||||
weaponAlarm.SetLevel(0); // -> Firing
|
||||
FireWeapon(); // (*vtable+0x48)()
|
||||
@@ -317,7 +317,7 @@ void
|
||||
if (currentLevel < seekVoltage[seekVoltageIndex])
|
||||
currentLevel = seekVoltage[seekVoltageIndex]; // -> ComputeOutputVoltage == 1.0
|
||||
ComputeOutputVoltage(); // (*vtable+0x44)()
|
||||
if (outputVoltage == 1.0f) // _DAT_004bac04 -- fully charged
|
||||
if (rechargeLevel == 1.0f) // _DAT_004bac04 -- fully charged (== inherited @0x320)
|
||||
{
|
||||
weaponAlarm.SetLevel(2); // -> Loaded
|
||||
}
|
||||
@@ -496,27 +496,29 @@ Logical
|
||||
void
|
||||
Emitter::ComputeOutputVoltage()
|
||||
{
|
||||
// outputVoltage IS the inherited MechWeapon::rechargeLevel@0x320 (the binary keeps
|
||||
// no separate Emitter slot; the recon's own `outputVoltage` was a duplicate).
|
||||
if (Fabs(currentLevel - 0.0f) > 1.0e-4f) // _DAT_004ba818 / _DAT_004ba81c
|
||||
{
|
||||
outputVoltage = currentLevel / seekVoltage[seekVoltageIndex]; // 0x320
|
||||
rechargeLevel = currentLevel / seekVoltage[seekVoltageIndex]; // 0x320
|
||||
}
|
||||
else
|
||||
{
|
||||
outputVoltage = 0.0f;
|
||||
rechargeLevel = 0.0f;
|
||||
}
|
||||
|
||||
if (Fabs(outputVoltage - 1.0f) <= 0.01f) // _DAT_004ba820 / _DAT_004ba824
|
||||
if (Fabs(rechargeLevel - 1.0f) <= 0.01f) // _DAT_004ba820 / _DAT_004ba824
|
||||
{
|
||||
outputVoltage = 1.0f;
|
||||
rechargeLevel = 1.0f;
|
||||
}
|
||||
|
||||
if (outputVoltage < 0.0f) // _DAT_004ba818
|
||||
if (rechargeLevel < 0.0f) // _DAT_004ba818
|
||||
{
|
||||
outputVoltage = 0.0f; // _DAT_004ba828
|
||||
rechargeLevel = 0.0f; // _DAT_004ba828
|
||||
}
|
||||
else if (outputVoltage > 1.0f) // _DAT_004ba820
|
||||
else if (rechargeLevel > 1.0f) // _DAT_004ba820
|
||||
{
|
||||
outputVoltage = 0.0f; // _DAT_004ba830 (NB: also 0.0f)
|
||||
rechargeLevel = 0.0f; // _DAT_004ba830 (NB: also 0.0f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -674,9 +676,9 @@ Emitter::Emitter(
|
||||
SetPerformance(&Emitter::EmitterSimulation); // this[7..9] = {0x004baa88,0,0}
|
||||
}
|
||||
|
||||
firingArmed = 1; // E4: arm the Loaded->Firing gate (this+0x3e8;
|
||||
// real semantic is a weapon-selected flag from
|
||||
// the controls path -- bypassed in bring-up).
|
||||
// (E4 removed) the former `firingArmed = 1` wrote this+0x3e8 == MechWeapon::recoil,
|
||||
// NOT the Loaded->Firing gate: the binary gate reads useConfiguredPip@0x3E0 (set by
|
||||
// the MechWeapon ctor from usesExternalModel), so no separate arming is needed.
|
||||
weaponAlarm.SetLevel(3); // Loading
|
||||
beamEndpoint = Point3D(0.0f, 0.0f, 0.0f); // 0x460 <- (0,0,0)
|
||||
beamFlag = 0; // 0x46c
|
||||
@@ -735,9 +737,12 @@ Emitter::Emitter(
|
||||
Scalar v = seekVoltage[seekVoltageRecommendedIndex];
|
||||
energyCoefficient = energyTotal / (v * v * /*_DAT_004bb3b4*/ 0.5f); // 0x454
|
||||
|
||||
// voltage-curve coefficient -> ramp time (expf of the seek curve)
|
||||
// voltage-curve coefficient -> ramp time (expf of the seek curve). The binary
|
||||
// stores this in the base slot voltageScale@0x310 and never reads it back, so the
|
||||
// recon's own `energyRampTime` member was a phantom -> computed into a local.
|
||||
Scalar curve = /*_DAT_004bb3c4 - _DAT_004bb3b8 * v*/ VoltageCurve(v); // best-effort
|
||||
energyRampTime = (rechargeRate / -curve) / energyCoefficient; // this+0x328 working term
|
||||
Scalar energyRampTime = (rechargeRate / -curve) / energyCoefficient;
|
||||
(void)energyRampTime;
|
||||
}
|
||||
|
||||
// damageFraction = damageAmount / (damageAmount + heatCostToFire)
|
||||
|
||||
Reference in New Issue
Block a user