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
@@ -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
|
||||
|
||||
+24
-16
@@ -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.
|
||||
|
||||
@@ -71,7 +71,7 @@ void
|
||||
//
|
||||
// Fire the weapon
|
||||
//
|
||||
outputVoltage = 0.0f;
|
||||
rechargeLevel = 0.0f; // was outputVoltage: the Emitter dup was removed (== inherited rechargeLevel@0x320)
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
@@ -90,7 +90,7 @@ GaussRifle::GaussRifle(
|
||||
{
|
||||
Check(owner);
|
||||
Check_Pointer(subsystem_resource);
|
||||
outputVoltage = 0.0f;
|
||||
rechargeLevel = 0.0f; // was outputVoltage: the Emitter dup was removed (== inherited rechargeLevel@0x320)
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -263,49 +263,53 @@ class NotationFile;
|
||||
// Byte offsets into the shipped object; MechWeapon ends at +0x3EF.
|
||||
//
|
||||
protected:
|
||||
// --- charge curve (seek voltage) ---
|
||||
int seekVoltageIndex; // @0x3F0 current charge-curve index
|
||||
int seekVoltageRecommendedIndex;// @0x3F4 resource recommended index (reset target)
|
||||
int seekStepCounter; // @0x3F8 reset 0 (best-effort)
|
||||
int seekVoltageCount; // @0x3FC index modulus / count-1 (best-effort)
|
||||
Scalar seekVoltage[5]; // @0x400 per-index target voltages (heat-scaled)
|
||||
Scalar currentLevel; // @0x414 accumulated charge (EMITTER.TCP: currentLevel)
|
||||
Scalar outputVoltage; // normalised discharge readiness (EMITTER.TCP: outputVoltage)
|
||||
// Byte-exact own fields, ctor @004bb120 (param_1[N] == byte N*4), 0x3F0..0x478.
|
||||
// The former "additional firing/beam scratch" block (beamHitPoint/beamImpact/
|
||||
// beamImpactScalar/beamColor/beamHitData/firingArmed/energyRampTime) + outputVoltage
|
||||
// + beamLengthRatio were phantom/duplicate members that over-sized Emitter to 0x4B0;
|
||||
// removed (see the .cpp for where each really lives / became a local):
|
||||
// * outputVoltage -> inherited MechWeapon::rechargeLevel@0x320 (ComputeOutputVoltage writes it)
|
||||
// * beamHitData -> a LinearMatrix local in FireWeapon (aim transform)
|
||||
// * beamHitPoint/beamImpact/beamImpactScalar -> binary writes into the inherited
|
||||
// Damage damageData (0x3C8/0x3B0/0x3AC); the recon assignments were dead -> deleted
|
||||
// * beamColor -> never read (WriteUpdateRecord builds colour from targetEntity)
|
||||
// * firingArmed -> the Loaded->Firing gate reads inherited useConfiguredPip@0x3E0
|
||||
// * energyRampTime -> binary writes the base slot voltageScale@0x310; recon computes a local
|
||||
// * beamLengthRatio -> is beamScale.z@0x434
|
||||
int seekVoltageIndex; // @0x3F0 (param_1[0xfc]) current charge-curve index
|
||||
int seekVoltageRecommendedIndex;// @0x3F4 (param_1[0xfd]) recommended index
|
||||
int seekStepCounter; // @0x3F8 (param_1[0xfe]) reset 0
|
||||
int seekVoltageCount; // @0x3FC (param_1[0xff]) index modulus / count-1
|
||||
Scalar seekVoltage[5]; // @0x400 (param_1[0x100..0x104]) per-index target voltages
|
||||
Scalar currentLevel; // @0x414 (param_1[0x105]) accumulated charge
|
||||
int firingActive; // @0x418 (param_1[0x106]) reset 0; FireWeapon sets 1
|
||||
Quaternion beamOrientation; // @0x41C (param_1[0x107..0x10a], 16B) aim orientation
|
||||
Vector3D beamScale; // @0x42C (param_1[0x10b..0x10d]) beam scale; .z = length ratio
|
||||
Scalar graphicLength; // @0x438 (param_1[0x10e]) resource GraphicLength
|
||||
Scalar dischargeTime; // @0x43C (param_1[0x10f]) resource DischargeTime
|
||||
Scalar dischargeTimer; // @0x440 (param_1[0x110]) beam-on countdown
|
||||
Scalar damageFraction; // @0x444 (param_1[0x111]) damageAmount / (damageAmount + heatCostToFire)
|
||||
Scalar energyTotal; // @0x448 (param_1[0x112]) (damageAmount + heatCostToFire) * 1e7
|
||||
Scalar damagePortion; // @0x44C (param_1[0x113]) per-shot damage energy
|
||||
Scalar heatPortion; // @0x450 (param_1[0x114]) per-shot self-heat energy
|
||||
Scalar energyCoefficient; // @0x454 (param_1[0x115]) energyTotal / (seekVoltage^2 * k)
|
||||
Scalar seekReserved; // @0x458 (param_1[0x116]) reset 0
|
||||
Scalar seekRate; // @0x45C (param_1[0x117]) d(level)/dt working value
|
||||
Point3D beamEndpoint; // @0x460 (param_1[0x118..0x11a]) last hit / beam-end point
|
||||
int beamFlag; // @0x46C (param_1[0x11b]) beam-active flag
|
||||
int targetLocalFlag; // @0x470 (param_1[0x11c]) target-relative vs world
|
||||
Entity *targetEntity; // @0x474 (param_1[0x11d]) current beam target -- LAST, ends 0x478
|
||||
|
||||
// --- additional firing/beam scratch referenced by the recovered bodies ---
|
||||
Point3D beamHitPoint; // last hit point
|
||||
Vector3D beamImpact; // impact delta
|
||||
Scalar beamImpactScalar; // per-shot damage energy at impact
|
||||
RGBColor beamColor; // beam/team colour
|
||||
LinearMatrix beamHitData; // pip transform for DrawWeaponPip
|
||||
int firingArmed; // trigger-armed flag
|
||||
Scalar energyRampTime; // seek-curve ramp time working value
|
||||
// heatAccumulator removed -- it SHADOWED the inherited HeatSink::pendingHeat
|
||||
// (@0x1c8, the heat sim's per-frame input); FireWeapon now writes pendingHeat (E5).
|
||||
friend struct EmitterLayoutCheck;
|
||||
};
|
||||
|
||||
// NOTE: outputVoltage is MechWeapon's recharge-level slot @0x320, reused
|
||||
// by Emitter as the normalised discharge readiness (EMITTER.TCP:
|
||||
// outputVoltage; == 1.0 means "fully charged -> Loaded").
|
||||
|
||||
// --- beam / firing transient state (network-replicated) ---
|
||||
int firingActive; // @0x418 reset 0; FireWeapon sets 1 (best-effort)
|
||||
EulerAngles beamOrientation; // @0x41C aim orientation (3 floats), zeroed at reset
|
||||
Vector3D beamScale; // @0x42C reset (1,1,1) (best-effort)
|
||||
Scalar beamLengthRatio; // @0x434 range / graphicLength (best-effort)
|
||||
Scalar graphicLength; // @0x438 resource GraphicLength
|
||||
Scalar dischargeTime; // @0x43C resource DischargeTime
|
||||
Scalar dischargeTimer; // @0x440 beam-on countdown (reset = dischargeTime)
|
||||
Scalar damageFraction; // @0x444 damageAmount / (damageAmount + heatCostToFire)
|
||||
Scalar energyTotal; // @0x448 (damageAmount + heatCostToFire) * 1.0e7 (best-effort)
|
||||
Scalar damagePortion; // @0x44C per-shot damage energy (best-effort)
|
||||
Scalar heatPortion; // @0x450 per-shot self-heat energy (best-effort)
|
||||
Scalar energyCoefficient; // @0x454 energyTotal / (seekVoltage^2 * k) (best-effort)
|
||||
Scalar seekReserved; // @0x458 reset 0 (best-effort)
|
||||
Scalar seekRate; // @0x45C d(level)/dt working value (best-effort)
|
||||
Point3D beamEndpoint; // @0x460 last hit / beam-end point (replicated)
|
||||
int beamFlag; // @0x46C beam-active flag (replicated)
|
||||
int targetLocalFlag; // @0x470 target-relative vs world (replicated)
|
||||
Entity *targetEntity; // @0x474 current beam target (replicated)
|
||||
}; // sizeof ~= 0x478
|
||||
struct EmitterLayoutCheck {
|
||||
static_assert(offsetof(Emitter, seekVoltageIndex) == 0x3F0, "Emitter::seekVoltageIndex @0x3F0 (MechWeapon must end 0x3F0)");
|
||||
static_assert(offsetof(Emitter, currentLevel) == 0x414, "Emitter::currentLevel @0x414");
|
||||
static_assert(offsetof(Emitter, beamOrientation) == 0x41C, "Emitter::beamOrientation @0x41C (Quaternion 16B)");
|
||||
static_assert(offsetof(Emitter, beamScale) == 0x42C, "Emitter::beamScale @0x42C");
|
||||
static_assert(offsetof(Emitter, targetEntity) == 0x474, "Emitter::targetEntity @0x474 (last)");
|
||||
static_assert(sizeof(Emitter) == 0x478, "sizeof(Emitter) 0x478 (== factory alloc)");
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -96,6 +96,9 @@ public:
|
||||
void SetLevel(int n) { level = n; }
|
||||
int GetLevel() const { return level; }
|
||||
int Level() const { return level; }
|
||||
// ReconAlarm/AlarmIndicator API aliases (callers that predate the retype use these):
|
||||
void SetState(unsigned n){ level = (int)n; }
|
||||
unsigned GetState() const { return (unsigned)level; }
|
||||
protected:
|
||||
// Interior mirrors FUN_0041b9ec: base GaugeAlarm header (+0x00), three count
|
||||
// words at +0x0c/+0x10/+0x14, three sub-indicators at +0x18/+0x2c/+0x40. The
|
||||
|
||||
@@ -655,6 +655,29 @@ Scalar
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// BTResolveWeaponMuzzle -- the faithful FUN_004b9948 (MechWeapon::GetMuzzlePoint)
|
||||
// muzzle resolve: look up the weapon's mount segment (index, from the subsystem's
|
||||
// inherited this+0xdc slot) in the owner Mech's segment table and transform it to
|
||||
// world. Lives here (a complete-Mech TU with the segment API); mechweap.cpp treats
|
||||
// `owner` as a raw pointer so it calls this via a void* bridge instead of the Mech API.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
void
|
||||
BTResolveWeaponMuzzle(void *ownerMech, int segIndex, Point3D &out)
|
||||
{
|
||||
Mech *m = (Mech *)ownerMech;
|
||||
if (m == 0) { out = Point3D(0.0f, 0.0f, 0.0f); return; }
|
||||
EntitySegment *seg = m->GetSegment(segIndex); // owner+0x300 table, GetNth(index)
|
||||
if (seg != 0)
|
||||
{
|
||||
AffineMatrix mw;
|
||||
mw.Multiply(seg->GetSegmentToEntity(), m->localToWorld); // segment -> world (== mech4 gun-port path)
|
||||
out = mw; // Point3D = matrix W_Axis translation
|
||||
}
|
||||
else
|
||||
out = m->localOrigin.linearPosition; // safe non-garbage fallback (owner origin)
|
||||
}
|
||||
|
||||
void
|
||||
Mech::PerformAndWatch(const Time& till, MemoryStream *update_stream)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -266,7 +266,7 @@ class CockpitHud;
|
||||
Logical targetWithinRange; // @0x34C PPC.CPP: "if (targetWithinRange)"
|
||||
|
||||
// --- damage / display alarm ---
|
||||
AlarmIndicator weaponAlarm; // @0x350 alarm indicator (level count from ctor)
|
||||
GaugeAlarm54 weaponAlarm; // @0x350 0x54 AlarmIndicator (ctor FUN_0041b9ec); level @0x364 -> ends 0x3A4
|
||||
|
||||
Scalar previousFireImpulse; // @0x3A4 reset 0 (fire-edge history, see @004b9608)
|
||||
Damage damageData; // @0x3A8 damageType @0x3A8, damageAmount @0x3AC
|
||||
@@ -277,21 +277,29 @@ class CockpitHud;
|
||||
Logical useConfiguredPip; // @0x3E0 = (usesExternalModel == 0) (best-effort)
|
||||
ResourceDescription::ResourceID
|
||||
explosionResourceID; // @0x3E4 resource ExplosionModelFile
|
||||
Scalar recoil; // @0x3E8 reset 0 (TODO: name)
|
||||
int segmentPageIndex; // @0x3EC resource segmentIndex (+0x28)
|
||||
Scalar recoil; // @0x3E8 reset 0 (Emitter::firingArmed aliases this slot)
|
||||
int segmentPageIndex; // @0x3EC resource segmentIndex (+0x28) -- LAST field, binary ends 0x3F0
|
||||
|
||||
// --- targeting/segment references (best-effort; used by GetMuzzlePoint /
|
||||
// DrawWeaponPip). segmentReference indexes the owning Mech segment
|
||||
// table; pipSegment is the HUD pip's mount segment (-1 == none). ---
|
||||
int segmentReference; // muzzle segment lookup index
|
||||
int pipSegment; // HUD pip mount segment (-1 == none)
|
||||
// The binary MechWeapon ends at 0x3F0 (segmentPageIndex is the last ctor write,
|
||||
// @004b99a8 param_1[0xfb]). The former tail fields segmentReference/pipSegment/
|
||||
// hasTarget/targetPoint/muzzlePoint were PHANTOM (no binary storage) and, worse,
|
||||
// collided with the Emitter subclass's own fields at 0x3F0+; removed:
|
||||
// * segmentReference -> the muzzle segment is the inherited base slot this+0xdc
|
||||
// (GetMuzzlePoint resolves it live via the mech4 BTResolveWeaponMuzzle bridge)
|
||||
// * pipSegment -> DrawWeaponPip gates on useConfiguredPip
|
||||
// * hasTarget -> dead (HasActiveTarget reads owner+0x388)
|
||||
// * targetPoint -> GetTargetPosition reads owner+0x37c
|
||||
// * muzzlePoint -> resolved live by GetMuzzlePoint
|
||||
|
||||
// --- cached targeting kinematics (populated by the owning Mech's
|
||||
// per-frame targeting/segment update; consumed by UpdateTargeting,
|
||||
// GetTargetPosition, GetMuzzlePoint, GetVectorToWeapon). ---
|
||||
Logical hasTarget; // owning mech has a locked target
|
||||
Point3D targetPoint; // current target world position
|
||||
Point3D muzzlePoint; // resolved weapon muzzle world position
|
||||
friend struct MechWeaponLayoutCheck;
|
||||
};
|
||||
|
||||
struct MechWeaponLayoutCheck {
|
||||
static_assert(offsetof(MechWeapon, weaponAlarm) == 0x350, "MechWeapon::weaponAlarm @0x350 (0x54 alarm)");
|
||||
static_assert(offsetof(MechWeapon, previousFireImpulse) == 0x3A4, "MechWeapon::previousFireImpulse @0x3A4 (alarm ends here)");
|
||||
static_assert(offsetof(MechWeapon, recoil) == 0x3E8, "MechWeapon::recoil @0x3E8");
|
||||
static_assert(offsetof(MechWeapon, segmentPageIndex) == 0x3EC, "MechWeapon::segmentPageIndex @0x3EC (last field)");
|
||||
static_assert(sizeof(MechWeapon) == 0x3F0, "sizeof(MechWeapon) 0x3F0");
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -527,13 +527,12 @@ void Myomers::ToggleSeekVoltage()
|
||||
//===========================================================================//
|
||||
struct MyomersLayoutCheck
|
||||
{
|
||||
// OVERFLOW lock only: the PoweredSubsystem base is NOT byte-exact (its 0x54 alarms
|
||||
// are modeled as HeatAlarm(8) pending the subsystem-tree alarm unification -- see
|
||||
// powersub.hpp), so Myomers own fields don't land at their binary offsets yet. The
|
||||
// phantom moverConnection tail was removed (it was a write-only base+0x110 shim), so
|
||||
// the object fits the 0x358 factory alloc. Byte-exact offsets return with the P7
|
||||
// weapon/power-subtree re-base.
|
||||
static_assert(sizeof(Myomers) <= 0x358, "sizeof(Myomers) must fit the factory Memory::Allocate(0x358)");
|
||||
// BYTE-EXACT: with PoweredSubsystem byte-exact (ends 0x31C) and the phantom
|
||||
// moverConnection tail removed, Myomers' own fields (ctor @004b8fec: speedEffect@0x31C
|
||||
// .. accelerationEfficiency@0x354) land exactly at 0x31C..0x358.
|
||||
static_assert(offsetof(Myomers, speedEffect) == 0x31C, "Myomers::speedEffect @0x31C (attr 0x12)");
|
||||
static_assert(offsetof(Myomers, seekVoltage) == 0x330, "Myomers::seekVoltage @0x330 (attr 0x17)");
|
||||
static_assert(sizeof(Myomers) == 0x358, "sizeof(Myomers) 0x358 (factory alloc, byte-exact)");
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -482,7 +482,7 @@ void
|
||||
source->currentTapCount -= 1; // source+0x1e8 -= 1
|
||||
voltageSource.Clear(); // FUN_00417a5c(this+0x1d0)
|
||||
}
|
||||
modeAlarm.SetLevel(1); // FUN_0041bbd8(this+0x264, 1) TODO: confirm which alarm
|
||||
electricalStateAlarm.SetLevel(NoVoltage); // FUN_0041bbd8(this+0x264, 1) -- 0x264 IS electricalStateAlarm (was wrongly modeAlarm@0x2B8)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -205,20 +205,29 @@ class Generator;
|
||||
int auxScreenPlacement; // @0x1E0 resource +0x108
|
||||
char auxScreenLabel[64]; // @0x1E4 resource +0x10C
|
||||
char engScreenLabel[64]; // @0x224 resource +0x14C
|
||||
// ⚠ NOT YET BYTE-EXACT: the binary electricalStateAlarm/modeAlarm are 0x54-byte
|
||||
// AlarmIndicators (level @+0x14). Retyping them to GaugeAlarm54 grows PoweredSubsystem
|
||||
// +0x98, which cascades into EVERY subclass (MechWeapon/Emitter/PPC/Myomers/Sensor) --
|
||||
// all of which ALSO model the binary alarm with 4-byte ReconAlarm / 8-byte HeatAlarm
|
||||
// stand-ins and are themselves short + phantom-tailed. Making PoweredSubsystem
|
||||
// byte-exact therefore requires the SYSTEMIC subsystem-tree alarm unification (P7
|
||||
// follow-up, docs/HARD_PROBLEMS.md). Kept as HeatAlarm(8) here so the whole tree
|
||||
// still fits its factory allocs; the aux-screen offsets are approximate until then.
|
||||
HeatAlarm electricalStateAlarm; // @0x264 (binary; approx here) 5-level; level @0x278
|
||||
HeatAlarm modeAlarm; // @0x2B8 (binary; approx here) 3-level; level @0x2CC
|
||||
// The binary alarms are 0x54-byte AlarmIndicators (ctor @004b0f74:
|
||||
// FUN_0041b9ec(param_1+0x99,5) @0x264 and (param_1+0xae,3) @0x2B8; level @+0x14
|
||||
// == 0x278/0x2CC). Now byte-exact via GaugeAlarm54 -- part of the subsystem-tree
|
||||
// alarm unification (the whole PoweredSubsystem subtree byte-exacted together).
|
||||
GaugeAlarm54 electricalStateAlarm; // @0x264 5-level (ElectricalState); level @0x278 -> ends 0x2B8
|
||||
GaugeAlarm54 modeAlarm; // @0x2B8 3-level (ConnectMode); level @0x2CC -> ends 0x30C
|
||||
Scalar thermalResistivityCoefficient; // @0x30C resource +0x100
|
||||
Scalar voltageScale; // @0x310 init 1.0f
|
||||
Scalar startTime; // @0x314 resource +0x18C
|
||||
Scalar startTimer; // @0x318 init = startTime, counts up while Starting
|
||||
Scalar startTimer; // @0x318 init = startTime, counts up while Starting -> ends 0x31C
|
||||
|
||||
friend struct PoweredSubsystemLayoutCheck;
|
||||
};
|
||||
|
||||
struct PoweredSubsystemLayoutCheck {
|
||||
static_assert(offsetof(PoweredSubsystem, voltageSource) == 0x1D0, "PoweredSubsystem::voltageSource @0x1D0");
|
||||
static_assert(offsetof(PoweredSubsystem, auxScreenNumber) == 0x1DC, "PoweredSubsystem::auxScreenNumber @0x1DC (res +0x104)");
|
||||
static_assert(offsetof(PoweredSubsystem, auxScreenLabel) == 0x1E4, "PoweredSubsystem::auxScreenLabel @0x1E4");
|
||||
static_assert(offsetof(PoweredSubsystem, engScreenLabel) == 0x224, "PoweredSubsystem::engScreenLabel @0x224");
|
||||
static_assert(offsetof(PoweredSubsystem, electricalStateAlarm) == 0x264, "PoweredSubsystem::electricalStateAlarm @0x264");
|
||||
static_assert(offsetof(PoweredSubsystem, modeAlarm) == 0x2B8, "PoweredSubsystem::modeAlarm @0x2B8");
|
||||
static_assert(offsetof(PoweredSubsystem, thermalResistivityCoefficient)== 0x30C, "PoweredSubsystem::thermalResistivityCoefficient @0x30C");
|
||||
static_assert(sizeof(PoweredSubsystem) == 0x31C, "sizeof(PoweredSubsystem) 0x31C");
|
||||
};
|
||||
|
||||
//###########################################################################
|
||||
|
||||
@@ -407,7 +407,13 @@ int
|
||||
//===========================================================================//
|
||||
struct SensorLayoutCheck
|
||||
{
|
||||
static_assert(sizeof(Sensor) <= 0x328, "sizeof(Sensor) must fit the factory Memory::Allocate(0x328)");
|
||||
// Now BYTE-EXACT: with PoweredSubsystem byte-exact (ends 0x31C), Sensor's 3 own
|
||||
// fields (ctor @004b1d18: radarPercent@0x31C / selfTest@0x320 / badVoltage@0x324)
|
||||
// land exactly at 0x31C..0x328. (Public fields -> namespace-scope offsetof works.)
|
||||
static_assert(offsetof(Sensor, radarPercent) == 0x31C, "Sensor::radarPercent @0x31C (attr 0x12)");
|
||||
static_assert(offsetof(Sensor, selfTest) == 0x320, "Sensor::selfTest @0x320");
|
||||
static_assert(offsetof(Sensor, badVoltage) == 0x324, "Sensor::badVoltage @0x324");
|
||||
static_assert(sizeof(Sensor) == 0x328, "sizeof(Sensor) 0x328 (factory alloc, byte-exact)");
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user