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
|
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
|
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
|
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
|
`massScale@0x160`; Reservoir `coolantCapacity`==`thermalCapacity@0x128`; Emitter `outputVoltage`==inherited
|
||||||
name; (2) an **alarm-interior field** = a value the binary reads at `alarm+0x14` modeled as a separate member
|
`rechargeLevel@0x320`; Emitter `beamLengthRatio`==`beamScale.z@0x434`; Emitter `firingArmed`==inherited
|
||||||
(HeatSink `heatState@0x184`, Reservoir `injectActive@0x1e4`) → route to `alarm.GetLevel()`; (3) a **phantom field**
|
`useConfiguredPip@0x3E0`) → delete the member, use the inherited/existing name; (2) an **alarm-interior field** = a
|
||||||
= a member at an offset PAST the object (Generator `shortFlag@0x25C` is really `*(owner+0x190)+0x25c` the msg-manager;
|
value the binary reads at `alarm+0x14` modeled as a separate member (HeatSink `heatState@0x184`, Reservoir
|
||||||
Myomers `moverConnection@0x110` a write-only base slot) → remove it, read the real source. **⚠ THE REMAINING SCOPE
|
`injectActive@0x1e4`) → route to `alarm.GetLevel()`; (3) a **phantom field** = a member at an offset PAST the object
|
||||||
(measured, larger than the heat leaf):** making `PoweredSubsystem` byte-exact grows it +0x98 (two 0x54 alarms) and
|
(Generator `shortFlag@0x25C` is really `*(owner+0x190)+0x25c` the msg-manager; Myomers `moverConnection@0x110` a
|
||||||
cascades into EVERY subclass — `MechWeapon`/`Emitter`/`PPC`/`Sensor`/`Myomers` ALL model the 0x54 `AlarmIndicator`
|
write-only base slot; MechWeapon `segmentReference/pipSegment/hasTarget/targetPoint/muzzlePoint` past 0x3F0; Emitter
|
||||||
with 4-byte `ReconAlarm` (`AlarmIndicator`==`ReconAlarm`==4B!) or 8-byte `HeatAlarm` stand-ins and are short +
|
`beamHitPoint/beamImpact/beamImpactScalar/beamColor/beamHitData/energyRampTime` — binary writes into inherited
|
||||||
phantom-tailed; retyping PoweredSubsystem's alarms without also byte-exacting them overflows the Emitter alloc
|
`damageData`/`voltageScale` or the value is a method local) → remove it, read the real source or use a local.
|
||||||
(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
|
**✅✅ SUBSYSTEM-TREE ALARM UNIFICATION — DONE: the WHOLE PoweredSubsystem weapon/power subtree is now byte-exact
|
||||||
vs its ctor + lock) — a distinct multi-class task. `runtime Check(sizeof<=alloc)` in a factory bridge does NOT fail
|
(P7 CLOSED; full per-class map from the 8-agent `alarm-unification-map` workflow, saved scratchpad/alarm_unify_maps.txt).**
|
||||||
the build (it's a runtime assert); use a `static_assert` sizeof lock to catch alloc overflow at COMPILE time.
|
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`).**
|
**✅ 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
|
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
|
**phantom fields** removed (Generator `shortFlag@0x25C` -- really `*(owner+0x190)+0x25c`, the msg-manager, raw
|
||||||
@004b0efc; Myomers `moverConnection@0x110` -- a write-only base slot).
|
@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):**
|
**✅✅ SUBSYSTEM-TREE ALARM UNIFICATION -- DONE (P7 CLOSED; the whole PoweredSubsystem weapon/power subtree is
|
||||||
making **`PoweredSubsystem`** byte-exact (its two 0x54 alarms `electricalStateAlarm@0x264`/`modeAlarm@0x2B8`)
|
byte-exact).** Making **`PoweredSubsystem`** byte-exact (its two 0x54 alarms `electricalStateAlarm@0x264`/
|
||||||
grows it **+0x98**, which cascades into EVERY subclass -- `MechWeapon`, `Emitter`, `PPC`, `Myomers`, `Sensor` --
|
`modeAlarm@0x2B8`) grows it **+0x98**, cascading into EVERY subclass -- `MechWeapon`, `Emitter`, `PPC`, `Myomers`,
|
||||||
and they ALL model the binary's 0x54 `AlarmIndicator` with 4-byte `ReconAlarm` (`weaponAlarm`) or 8-byte
|
`Sensor` -- which ALL modeled the binary 0x54 `AlarmIndicator` with 4-byte `ReconAlarm`/8-byte `HeatAlarm`
|
||||||
`HeatAlarm` stand-ins, so they are themselves short AND carry phantom/duplicate/misplaced tail fields
|
stand-ins and were short + phantom-tailed. All byte-exacted TOGETHER in one build (an 8-agent read-only
|
||||||
(measured: `MechWeapon` 0x3C4 vs binary 0x3F0 with ~0x24 of phantom tail; `Emitter` 0x4B0 vs binary 0x478 with
|
decomp-mapping workflow decoded every ctor first; full map in scratchpad/alarm_unify_maps.txt), each
|
||||||
a duplicate `outputVoltage`==`rechargeLevel@0x320` + ~0x64 of oversized own fields). Retyping PoweredSubsystem's
|
`static_assert`-locked against its ctor + factory alloc. The verified chain:
|
||||||
alarms WITHOUT also byte-exacting MechWeapon/Emitter/PPC overflowed the Emitter factory alloc (0x478) -> a heap
|
HeatSink 0x1D0 -> **PoweredSubsystem 0x31C** (retype 2 alarms; ctor @004b0f74) ->
|
||||||
corruption during Emitter construction. So PoweredSubsystem was kept on `HeatAlarm(8)` stand-ins (marked in
|
**MechWeapon 0x3F0** (weaponAlarm@0x350 ReconAlarm->GaugeAlarm54; delete 5 phantom tail fields; ctor @004b99a8) ->
|
||||||
powersub.hpp) and the whole tree still fits its allocs. **The real fix is a subsystem-tree ALARM UNIFICATION:
|
**Emitter 0x478** (delete outputVoltage/beamLengthRatio/firingArmed aliases + beamHit*/beamColor/beamHitData/
|
||||||
retype every binary-`AlarmIndicator` stand-in (ReconAlarm/HeatAlarm) to `GaugeAlarm54(0x54)` and de-phantom
|
energyRampTime phantoms; retype beamOrientation EulerAngles->Quaternion 16B; ctor @004bb120) ->
|
||||||
each weapon/power class against its ctor** (MechWeapon @004b99a8, Emitter @004ba478, PPC, Sensor). Each class
|
**PPC 0x478** (no own fields; ctor @004bb888);
|
||||||
needs: alarm retype + phantom/alias-field removal + a `static_assert` sizeof/offset lock vs its factory alloc.
|
PoweredSubsystem -> **Sensor 0x328** (no alarm, 3 own fields; ctor @004b1d18);
|
||||||
This is a multi-class effort (a separate task, bigger than the heat leaf) -- the core heat leaf above is the
|
PoweredSubsystem -> **Myomers 0x358** (no alarm; ctor @004b8fec).
|
||||||
foundational prerequisite and is complete. Same techniques apply (ctor decomp -> offsets, alias/phantom checks,
|
**Non-layout fixes in the same wave:** (a) `outputVoltage`->`rechargeLevel` also in compiled `GAUSS.CPP:74/93`
|
||||||
`friend struct XLayoutCheck` locks). The aux-screen gauge reads + Sensor RadarPercent stay approximate until then.
|
(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
|
// Fire the weapon
|
||||||
//
|
//
|
||||||
outputVoltage = 0.0f;
|
rechargeLevel = 0.0f; // was outputVoltage: the Emitter dup was removed (== inherited rechargeLevel@0x320)
|
||||||
|
|
||||||
Check_Fpu();
|
Check_Fpu();
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ GaussRifle::GaussRifle(
|
|||||||
{
|
{
|
||||||
Check(owner);
|
Check(owner);
|
||||||
Check_Pointer(subsystem_resource);
|
Check_Pointer(subsystem_resource);
|
||||||
outputVoltage = 0.0f;
|
rechargeLevel = 0.0f; // was outputVoltage: the Emitter dup was removed (== inherited rechargeLevel@0x320)
|
||||||
Check_Fpu();
|
Check_Fpu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -199,20 +199,20 @@ void
|
|||||||
|
|
||||||
Vector3D beamVector;
|
Vector3D beamVector;
|
||||||
beamVector.Subtract(targetPoint, muzzlePoint); // FUN_00408bf8
|
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;
|
Vector3D delta;
|
||||||
delta.Subtract(targetPoint, muzzlePoint); // FUN_00408644
|
delta.Subtract(targetPoint, muzzlePoint); // FUN_00408644
|
||||||
Scalar dist = (Scalar)Sqrt(delta.x*delta.x + delta.y*delta.y + delta.z*delta.z); // FUN_004dd138
|
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
|
if (dist <= effectiveRange) // this+0x328
|
||||||
{
|
{
|
||||||
// record the hit point + impact and register the pip / fire marker
|
// The binary writes the impact point/delta/energy into the inherited Damage
|
||||||
beamHitPoint = targetPoint; // 0xf2 <- targetPoint
|
// damageData (0x3C8/0x3B0/0x3AC); the recon's beamHitPoint/beamImpact/
|
||||||
beamImpact = delta; // 0xec <- delta
|
// beamImpactScalar copies were dead (no readers) -> removed. Register the pip.
|
||||||
beamImpactScalar = damagePortion; // 0xeb <- 0x113
|
DrawWeaponPip(aimTransform); // FUN_004b9728
|
||||||
DrawWeaponPip(beamHitData); // FUN_004b9728
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stash the beam endpoint for replication (world hit point)
|
// 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
|
case 2: // Loaded -- ready; fire on the trigger's rising edge
|
||||||
if (fireEdge)
|
if (fireEdge)
|
||||||
{
|
{
|
||||||
if (firingArmed && HasActiveTarget()) // this+0x3e8 && entity+0x388
|
if (useConfiguredPip && HasActiveTarget()) // this+0x3E0 (Loaded->Firing gate) && entity+0x388
|
||||||
{
|
{
|
||||||
weaponAlarm.SetLevel(0); // -> Firing
|
weaponAlarm.SetLevel(0); // -> Firing
|
||||||
FireWeapon(); // (*vtable+0x48)()
|
FireWeapon(); // (*vtable+0x48)()
|
||||||
@@ -317,7 +317,7 @@ void
|
|||||||
if (currentLevel < seekVoltage[seekVoltageIndex])
|
if (currentLevel < seekVoltage[seekVoltageIndex])
|
||||||
currentLevel = seekVoltage[seekVoltageIndex]; // -> ComputeOutputVoltage == 1.0
|
currentLevel = seekVoltage[seekVoltageIndex]; // -> ComputeOutputVoltage == 1.0
|
||||||
ComputeOutputVoltage(); // (*vtable+0x44)()
|
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
|
weaponAlarm.SetLevel(2); // -> Loaded
|
||||||
}
|
}
|
||||||
@@ -496,27 +496,29 @@ Logical
|
|||||||
void
|
void
|
||||||
Emitter::ComputeOutputVoltage()
|
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
|
if (Fabs(currentLevel - 0.0f) > 1.0e-4f) // _DAT_004ba818 / _DAT_004ba81c
|
||||||
{
|
{
|
||||||
outputVoltage = currentLevel / seekVoltage[seekVoltageIndex]; // 0x320
|
rechargeLevel = currentLevel / seekVoltage[seekVoltageIndex]; // 0x320
|
||||||
}
|
}
|
||||||
else
|
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}
|
SetPerformance(&Emitter::EmitterSimulation); // this[7..9] = {0x004baa88,0,0}
|
||||||
}
|
}
|
||||||
|
|
||||||
firingArmed = 1; // E4: arm the Loaded->Firing gate (this+0x3e8;
|
// (E4 removed) the former `firingArmed = 1` wrote this+0x3e8 == MechWeapon::recoil,
|
||||||
// real semantic is a weapon-selected flag from
|
// NOT the Loaded->Firing gate: the binary gate reads useConfiguredPip@0x3E0 (set by
|
||||||
// the controls path -- bypassed in bring-up).
|
// the MechWeapon ctor from usesExternalModel), so no separate arming is needed.
|
||||||
weaponAlarm.SetLevel(3); // Loading
|
weaponAlarm.SetLevel(3); // Loading
|
||||||
beamEndpoint = Point3D(0.0f, 0.0f, 0.0f); // 0x460 <- (0,0,0)
|
beamEndpoint = Point3D(0.0f, 0.0f, 0.0f); // 0x460 <- (0,0,0)
|
||||||
beamFlag = 0; // 0x46c
|
beamFlag = 0; // 0x46c
|
||||||
@@ -735,9 +737,12 @@ Emitter::Emitter(
|
|||||||
Scalar v = seekVoltage[seekVoltageRecommendedIndex];
|
Scalar v = seekVoltage[seekVoltageRecommendedIndex];
|
||||||
energyCoefficient = energyTotal / (v * v * /*_DAT_004bb3b4*/ 0.5f); // 0x454
|
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
|
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)
|
// damageFraction = damageAmount / (damageAmount + heatCostToFire)
|
||||||
|
|||||||
@@ -263,49 +263,53 @@ class NotationFile;
|
|||||||
// Byte offsets into the shipped object; MechWeapon ends at +0x3EF.
|
// Byte offsets into the shipped object; MechWeapon ends at +0x3EF.
|
||||||
//
|
//
|
||||||
protected:
|
protected:
|
||||||
// --- charge curve (seek voltage) ---
|
// Byte-exact own fields, ctor @004bb120 (param_1[N] == byte N*4), 0x3F0..0x478.
|
||||||
int seekVoltageIndex; // @0x3F0 current charge-curve index
|
// The former "additional firing/beam scratch" block (beamHitPoint/beamImpact/
|
||||||
int seekVoltageRecommendedIndex;// @0x3F4 resource recommended index (reset target)
|
// beamImpactScalar/beamColor/beamHitData/firingArmed/energyRampTime) + outputVoltage
|
||||||
int seekStepCounter; // @0x3F8 reset 0 (best-effort)
|
// + beamLengthRatio were phantom/duplicate members that over-sized Emitter to 0x4B0;
|
||||||
int seekVoltageCount; // @0x3FC index modulus / count-1 (best-effort)
|
// removed (see the .cpp for where each really lives / became a local):
|
||||||
Scalar seekVoltage[5]; // @0x400 per-index target voltages (heat-scaled)
|
// * outputVoltage -> inherited MechWeapon::rechargeLevel@0x320 (ComputeOutputVoltage writes it)
|
||||||
Scalar currentLevel; // @0x414 accumulated charge (EMITTER.TCP: currentLevel)
|
// * beamHitData -> a LinearMatrix local in FireWeapon (aim transform)
|
||||||
Scalar outputVoltage; // normalised discharge readiness (EMITTER.TCP: outputVoltage)
|
// * 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 ---
|
friend struct EmitterLayoutCheck;
|
||||||
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).
|
|
||||||
|
|
||||||
// NOTE: outputVoltage is MechWeapon's recharge-level slot @0x320, reused
|
struct EmitterLayoutCheck {
|
||||||
// by Emitter as the normalised discharge readiness (EMITTER.TCP:
|
static_assert(offsetof(Emitter, seekVoltageIndex) == 0x3F0, "Emitter::seekVoltageIndex @0x3F0 (MechWeapon must end 0x3F0)");
|
||||||
// outputVoltage; == 1.0 means "fully charged -> Loaded").
|
static_assert(offsetof(Emitter, currentLevel) == 0x414, "Emitter::currentLevel @0x414");
|
||||||
|
static_assert(offsetof(Emitter, beamOrientation) == 0x41C, "Emitter::beamOrientation @0x41C (Quaternion 16B)");
|
||||||
// --- beam / firing transient state (network-replicated) ---
|
static_assert(offsetof(Emitter, beamScale) == 0x42C, "Emitter::beamScale @0x42C");
|
||||||
int firingActive; // @0x418 reset 0; FireWeapon sets 1 (best-effort)
|
static_assert(offsetof(Emitter, targetEntity) == 0x474, "Emitter::targetEntity @0x474 (last)");
|
||||||
EulerAngles beamOrientation; // @0x41C aim orientation (3 floats), zeroed at reset
|
static_assert(sizeof(Emitter) == 0x478, "sizeof(Emitter) 0x478 (== factory alloc)");
|
||||||
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
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ public:
|
|||||||
void SetLevel(int n) { level = n; }
|
void SetLevel(int n) { level = n; }
|
||||||
int GetLevel() const { return level; }
|
int GetLevel() const { return level; }
|
||||||
int Level() 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:
|
protected:
|
||||||
// Interior mirrors FUN_0041b9ec: base GaugeAlarm header (+0x00), three count
|
// Interior mirrors FUN_0041b9ec: base GaugeAlarm header (+0x00), three count
|
||||||
// words at +0x0c/+0x10/+0x14, three sub-indicators at +0x18/+0x2c/+0x40. The
|
// words at +0x0c/+0x10/+0x14, three sub-indicators at +0x18/+0x2c/+0x40. The
|
||||||
|
|||||||
@@ -655,6 +655,29 @@ Scalar
|
|||||||
return 0.0f;
|
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
|
void
|
||||||
Mech::PerformAndWatch(const Time& till, MemoryStream *update_stream)
|
Mech::PerformAndWatch(const Time& till, MemoryStream *update_stream)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -160,8 +160,9 @@ MechWeapon::MechWeapon(
|
|||||||
|
|
||||||
rangeToTarget = 0.0f; // 0x324
|
rangeToTarget = 0.0f; // 0x324
|
||||||
targetWithinRange = False; // 0x34C
|
targetWithinRange = False; // 0x34C
|
||||||
segmentReference = segmentPageIndex;
|
// segmentReference/pipSegment were phantom tail members (past the binary 0x3F0 end);
|
||||||
pipSegment = -1;
|
// 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
|
// usesExternalModel: True if the model name contains the marker substring
|
||||||
// (DAT_00511aa2); selects whether the configured pip is drawn.
|
// (DAT_00511aa2); selects whether the configured pip is drawn.
|
||||||
@@ -358,17 +359,23 @@ void
|
|||||||
char *o = (char *)owner; // inherited MechSubsystem::owner (the Mech)
|
char *o = (char *)owner; // inherited MechSubsystem::owner (the Mech)
|
||||||
if (o != 0)
|
if (o != 0)
|
||||||
position = *(Point3D *)(o + 0x37c); // MECH_TARGET_POS
|
position = *(Point3D *)(o + 0x37c); // MECH_TARGET_POS
|
||||||
else
|
// (no owner -> leave position unchanged; the phantom `targetPoint` member is gone --
|
||||||
position = targetPoint;
|
// 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).
|
// @004b9cdc -- direction from a supplied point to the weapon mount (entity+0x37c).
|
||||||
//
|
//
|
||||||
void
|
void
|
||||||
MechWeapon::GetVectorToWeapon(const Point3D &from, Vector3D &direction)
|
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
|
void
|
||||||
MechWeapon::GetMuzzlePoint(Point3D &point)
|
MechWeapon::GetMuzzlePoint(Point3D &point)
|
||||||
{
|
{
|
||||||
// @004b9948 -- the shipped code resolves segmentReference against the owning
|
// @004b9948 -- resolve the weapon's mount segment (its index is the inherited
|
||||||
// Mech's segment table (SegmentIterator) and transforms the segment origin to
|
// subsystem slot at this+0xdc, == binary param_1+0xdc) in the owning Mech's
|
||||||
// world space. That transform is produced by the owning Mech's per-frame
|
// segment table and transform it to world (FUN_00424da8). Reads live each call
|
||||||
// segment update, which caches it in muzzlePoint; we return the cached value.
|
// (the phantom cached `muzzlePoint` member is removed -- it collided with the
|
||||||
point = muzzlePoint; // FUN_00424da8 result
|
// 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
|
// 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
|
// the HUD module; the submission is wired there. We retain the gating and
|
||||||
// transform reference here.
|
// transform reference here.
|
||||||
if (this->pipSegment != -1) // FUN_0041a1a4 guard
|
if (useConfiguredPip) // this+0x3E0 (weapon draws a configured pip)
|
||||||
{
|
{
|
||||||
(void)transform;
|
(void)transform;
|
||||||
(void)pipPosition;
|
(void)pipPosition;
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ class CockpitHud;
|
|||||||
Logical targetWithinRange; // @0x34C PPC.CPP: "if (targetWithinRange)"
|
Logical targetWithinRange; // @0x34C PPC.CPP: "if (targetWithinRange)"
|
||||||
|
|
||||||
// --- damage / display alarm ---
|
// --- 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)
|
Scalar previousFireImpulse; // @0x3A4 reset 0 (fire-edge history, see @004b9608)
|
||||||
Damage damageData; // @0x3A8 damageType @0x3A8, damageAmount @0x3AC
|
Damage damageData; // @0x3A8 damageType @0x3A8, damageAmount @0x3AC
|
||||||
@@ -277,21 +277,29 @@ class CockpitHud;
|
|||||||
Logical useConfiguredPip; // @0x3E0 = (usesExternalModel == 0) (best-effort)
|
Logical useConfiguredPip; // @0x3E0 = (usesExternalModel == 0) (best-effort)
|
||||||
ResourceDescription::ResourceID
|
ResourceDescription::ResourceID
|
||||||
explosionResourceID; // @0x3E4 resource ExplosionModelFile
|
explosionResourceID; // @0x3E4 resource ExplosionModelFile
|
||||||
Scalar recoil; // @0x3E8 reset 0 (TODO: name)
|
Scalar recoil; // @0x3E8 reset 0 (Emitter::firingArmed aliases this slot)
|
||||||
int segmentPageIndex; // @0x3EC resource segmentIndex (+0x28)
|
int segmentPageIndex; // @0x3EC resource segmentIndex (+0x28) -- LAST field, binary ends 0x3F0
|
||||||
|
|
||||||
// --- targeting/segment references (best-effort; used by GetMuzzlePoint /
|
// The binary MechWeapon ends at 0x3F0 (segmentPageIndex is the last ctor write,
|
||||||
// DrawWeaponPip). segmentReference indexes the owning Mech segment
|
// @004b99a8 param_1[0xfb]). The former tail fields segmentReference/pipSegment/
|
||||||
// table; pipSegment is the HUD pip's mount segment (-1 == none). ---
|
// hasTarget/targetPoint/muzzlePoint were PHANTOM (no binary storage) and, worse,
|
||||||
int segmentReference; // muzzle segment lookup index
|
// collided with the Emitter subclass's own fields at 0x3F0+; removed:
|
||||||
int pipSegment; // HUD pip mount segment (-1 == none)
|
// * 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
|
friend struct MechWeaponLayoutCheck;
|
||||||
// per-frame targeting/segment update; consumed by UpdateTargeting,
|
};
|
||||||
// GetTargetPosition, GetMuzzlePoint, GetVectorToWeapon). ---
|
|
||||||
Logical hasTarget; // owning mech has a locked target
|
struct MechWeaponLayoutCheck {
|
||||||
Point3D targetPoint; // current target world position
|
static_assert(offsetof(MechWeapon, weaponAlarm) == 0x350, "MechWeapon::weaponAlarm @0x350 (0x54 alarm)");
|
||||||
Point3D muzzlePoint; // resolved weapon muzzle world position
|
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
|
#endif
|
||||||
|
|||||||
@@ -527,13 +527,12 @@ void Myomers::ToggleSeekVoltage()
|
|||||||
//===========================================================================//
|
//===========================================================================//
|
||||||
struct MyomersLayoutCheck
|
struct MyomersLayoutCheck
|
||||||
{
|
{
|
||||||
// OVERFLOW lock only: the PoweredSubsystem base is NOT byte-exact (its 0x54 alarms
|
// BYTE-EXACT: with PoweredSubsystem byte-exact (ends 0x31C) and the phantom
|
||||||
// are modeled as HeatAlarm(8) pending the subsystem-tree alarm unification -- see
|
// moverConnection tail removed, Myomers' own fields (ctor @004b8fec: speedEffect@0x31C
|
||||||
// powersub.hpp), so Myomers own fields don't land at their binary offsets yet. The
|
// .. accelerationEfficiency@0x354) land exactly at 0x31C..0x358.
|
||||||
// phantom moverConnection tail was removed (it was a write-only base+0x110 shim), so
|
static_assert(offsetof(Myomers, speedEffect) == 0x31C, "Myomers::speedEffect @0x31C (attr 0x12)");
|
||||||
// the object fits the 0x358 factory alloc. Byte-exact offsets return with the P7
|
static_assert(offsetof(Myomers, seekVoltage) == 0x330, "Myomers::seekVoltage @0x330 (attr 0x17)");
|
||||||
// weapon/power-subtree re-base.
|
static_assert(sizeof(Myomers) == 0x358, "sizeof(Myomers) 0x358 (factory alloc, byte-exact)");
|
||||||
static_assert(sizeof(Myomers) <= 0x358, "sizeof(Myomers) must fit the factory Memory::Allocate(0x358)");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -482,7 +482,7 @@ void
|
|||||||
source->currentTapCount -= 1; // source+0x1e8 -= 1
|
source->currentTapCount -= 1; // source+0x1e8 -= 1
|
||||||
voltageSource.Clear(); // FUN_00417a5c(this+0x1d0)
|
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
|
int auxScreenPlacement; // @0x1E0 resource +0x108
|
||||||
char auxScreenLabel[64]; // @0x1E4 resource +0x10C
|
char auxScreenLabel[64]; // @0x1E4 resource +0x10C
|
||||||
char engScreenLabel[64]; // @0x224 resource +0x14C
|
char engScreenLabel[64]; // @0x224 resource +0x14C
|
||||||
// ⚠ NOT YET BYTE-EXACT: the binary electricalStateAlarm/modeAlarm are 0x54-byte
|
// The binary alarms are 0x54-byte AlarmIndicators (ctor @004b0f74:
|
||||||
// AlarmIndicators (level @+0x14). Retyping them to GaugeAlarm54 grows PoweredSubsystem
|
// FUN_0041b9ec(param_1+0x99,5) @0x264 and (param_1+0xae,3) @0x2B8; level @+0x14
|
||||||
// +0x98, which cascades into EVERY subclass (MechWeapon/Emitter/PPC/Myomers/Sensor) --
|
// == 0x278/0x2CC). Now byte-exact via GaugeAlarm54 -- part of the subsystem-tree
|
||||||
// all of which ALSO model the binary alarm with 4-byte ReconAlarm / 8-byte HeatAlarm
|
// alarm unification (the whole PoweredSubsystem subtree byte-exacted together).
|
||||||
// stand-ins and are themselves short + phantom-tailed. Making PoweredSubsystem
|
GaugeAlarm54 electricalStateAlarm; // @0x264 5-level (ElectricalState); level @0x278 -> ends 0x2B8
|
||||||
// byte-exact therefore requires the SYSTEMIC subsystem-tree alarm unification (P7
|
GaugeAlarm54 modeAlarm; // @0x2B8 3-level (ConnectMode); level @0x2CC -> ends 0x30C
|
||||||
// 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
|
|
||||||
Scalar thermalResistivityCoefficient; // @0x30C resource +0x100
|
Scalar thermalResistivityCoefficient; // @0x30C resource +0x100
|
||||||
Scalar voltageScale; // @0x310 init 1.0f
|
Scalar voltageScale; // @0x310 init 1.0f
|
||||||
Scalar startTime; // @0x314 resource +0x18C
|
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
|
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