subsystems: un-stub Wave 4 readouts + Wave 6 Myomers (10->15 factory cases)
WAVE 4 (standalone readouts) -- Sensor(0xBC3)/Searchlight(0xBD8)/ ThermalSight(0xBDE)/AmmoBin(0xBCB) un-stubbed via Create<Class>Subsystem bridges + Torso-style de-shim (drop cross-family shadow fields, redirect accessors to the real inherited base, static_assert layout locks). - FIX: Searchlight/ThermalSight ctors gated their Performance on the shadow segmentFlags(=0) so it NEVER installed; switched to owner->simulationFlags. - AmmoBin: retype ammoAlarm HeatAlarm->WatcherGaugeAlarm(0x54) + drop the statusState@0x40 shadow -> exact 0x22C layout. - Guard HeatWatcher::WatchSimulation against the unresolved watchedLink (null-deref exposed once these sims run; faithful fix = resolve the link). - Heat-leaf branch (Sensor) is not byte-exact -> overflow-lock only. WAVE 6 (Myomers 0xBC6, mover-coupled) -- structural un-stub, gated BT_MYOMERS (default on; =0 -> Actuator stub). Wired INERT: MyomersSimulation early-returns (advanced-damage gate stubbed) + no-op mover feed, so the live JointedMover is untouched and the gait cannot regress. De-shim drops the owner*/segmentFlags shims to fit the exact-0x358 alloc. Authentic mover/heat coupling deferred (needs messmgr 0xBD3 + reconciling the mover feed with the gait cutover). Verified: BLH tick 20->27, Mad Cat 24, combat DESTROYED un-regressed, locomotion un-regressed, 0 crashes, 0 heap detections under BT_HEAPCHECK. 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
5aa791b245
commit
8b36440d05
@@ -997,6 +997,71 @@ pre-init the seek table in the ctor so it's valid when the ctor src is null); E8
|
|||||||
but never set `owner`** (mechsub.cpp) -> every subsystem's `owner` was garbage once heat de-shadowed; now
|
but never set `owner`** (mechsub.cpp) -> every subsystem's `owner` was garbage once heat de-shadowed; now
|
||||||
set in both MechSubsystem ctors. Also: the Mech ctor now zeroes the target slots (+0x37c/+0x388/+0x38c) so
|
set in both MechSubsystem ctors. Also: the Mech ctor now zeroes the target slots (+0x37c/+0x388/+0x38c) so
|
||||||
an un-targeted mech (spawned enemy) reads "no target" (else its weapons fire at a garbage pointer).
|
an un-targeted mech (spawned enemy) reads "no target" (else its weapons fire at a garbage pointer).
|
||||||
|
**✅ WAVE 4 (standalone readouts) DONE — Sensor / Searchlight / ThermalSight / AmmoBin un-stubbed + ticking**
|
||||||
|
(verified: **BLH tick 20→26 executable**, Mad Cat 24, combat **DESTROYED** un-regressed on both, 0 crashes).
|
||||||
|
The 4 readout classes were reconstructed but built as `RECON_SUBSYS` stubs; each is now wired via a
|
||||||
|
`Create<Class>Subsystem` bridge (in its own .cpp) at the mislabel-correct factory case: 0xBC3→**Sensor**
|
||||||
|
(`MyomersClassID` label), 0xBD8→**Searchlight** (`LegSubsystem`), 0xBDE→**ThermalSight** (`MechDisplay`),
|
||||||
|
0xBCB→**AmmoBin** (`JumpJet`). Each got the Torso de-shim: delete the cross-family shadow fields, redirect
|
||||||
|
accessors to the real inherited base, add a `friend struct <Class>LayoutCheck` with `static_assert`
|
||||||
|
offset/sizeof locks. Redirects by branch: **Sensor** (PoweredSubsystem:HeatSink leaf) → `heatAlarm`/
|
||||||
|
`electricalStateAlarm`/`simulationState`/`heatEnergy`/`simulationFlags|=8`; **Searchlight/ThermalSight**
|
||||||
|
(PowerWatcher) → `watchdogAlarm`/`heatAlarm`/`simulationState`/`ForceUpdate()`/`simulationFlags`; **AmmoBin**
|
||||||
|
(HeatWatcher) → `simulationState` (deleted the `statusState`@0x40 shadow).
|
||||||
|
- **⭐ FUNCTIONAL BUG FIXED (Searchlight + ThermalSight): the ctor gate read the shadow `segmentFlags` (=0), so
|
||||||
|
`((0&0xC)==0 && (0&0x100)!=0)` was ALWAYS FALSE → their Performance NEVER installed (dead since
|
||||||
|
reconstruction).** The de-shim switches the gate to `owner->simulationFlags` (binary-exact part_013.c:
|
||||||
|
6012/6140; mirrors torso.cpp:181) → they now tick. (Sensor uses the simpler `!=4` gate; AmmoBin's gate was
|
||||||
|
already correct.)
|
||||||
|
- **AmmoBin layout: `ammoAlarm` retyped `HeatAlarm`(8B)→`WatcherGaugeAlarm`(0x54) + the `statusState` shadow
|
||||||
|
deleted → `ammoModelFile` lands at the binary 0x1E8, `sizeof==0x22C` exact (locked, the load-bearing lock).**
|
||||||
|
⚠ CORRECTION to an earlier assumption: the all-laser Blackhawk is NOT AmmoBin-free — it DOES carry AmmoBins
|
||||||
|
(the un-stub exposed the crash below ON THE BLH, and BLH tick rose 20→26 = Sensor+Searchlight+Thermal+AmmoBins).
|
||||||
|
- **⚠ SHARED LATENT BUG EXPOSED + GUARDED (`HeatWatcher::WatchSimulation`, heatfamily_reslice.cpp:305):** the
|
||||||
|
un-stub made `AmmoBinSimulation` (and via `PowerWatcher::Simulation`, the light/thermal sims) call the
|
||||||
|
HeatWatcher base `WatchSimulation`, which derefs `watchedLink.Resolve()->currentTemperature` — the watch
|
||||||
|
link isn't resolved in bring-up (the "WatchedSubsystem" name→roster-segment bind doesn't happen), so
|
||||||
|
`Resolve()` returns null → AV (`AmmoBinSimulation`→`WatchSimulation`, caught under cdb). The binary never
|
||||||
|
null-checks (its link always resolves at load). GUARDED (marked): `if (watched==0){ heatAlarm.SetLevel(0);
|
||||||
|
return; }` — same pattern as `Emitter::TrackSeekVoltage`. FAITHFUL FOLLOW-UP = resolve `watchedLink` so the
|
||||||
|
heat-watch alarm tracks the real watched temperature (it gates cook-off / light-blank / thermal-blank —
|
||||||
|
secondary behaviors, inert until then).
|
||||||
|
- **LAYOUT-LOCK FINDING (a new systemic fact): the heat-leaf branch (PoweredSubsystem:HeatSink) is NOT
|
||||||
|
byte-exact to the binary** — Sensor's exact-offset asserts (radarPercent@0x31C) FAILED at compile time,
|
||||||
|
unlike the re-based WATCHER branch (Searchlight/ThermalSight/AmmoBin offsets ARE exact and locked). So
|
||||||
|
Sensor uses only the OVERFLOW lock (`sizeof<=0x328`); its own fields don't land at the binary offsets —
|
||||||
|
fine until the attribute-table RAW read (the RadarPercent gauge) is wired (a Phase-4 HUD follow-up needing
|
||||||
|
the heat-leaf branch re-based, same surgery the Watcher branch got). Nothing reads these subsystems at raw
|
||||||
|
offsets today, so named-member access (compiled-consistent) is correct meanwhile.
|
||||||
|
- **Factory now: 14 of 20 cases wired to real ticking classes** (was 10). Remaining stubs: Myomers (0xBC6,
|
||||||
|
WAVE 6), ProjectileWeapon/MissileLauncher + the unresolved 0xBCE (0xBCD/0xBD0, WAVE 7), SubsystemMessageManager
|
||||||
|
(0xBD3, WAVE 8), Gyroscope (0xBC4, deferred — NaN integrator). Kept DEFAULT-ON (no env gate — pure readouts,
|
||||||
|
no bring-up fallback needed).
|
||||||
|
**✅ WAVE 6 (Myomers 0xBC6, mover-coupled) — STRUCTURAL un-stub DONE (gated `BT_MYOMERS`, default ON; verified:
|
||||||
|
BLH tick 26→27, locomotion + combat un-regressed, 0 crashes; `BT_MYOMERS=0` falls back to the Actuator stub →
|
||||||
|
26).** The real Myomers (the mech's artificial-muscle drive — voltage→`speedEffect`, throttled by heat +
|
||||||
|
damage; ctor @4b8fec, `PoweredSubsystem:HeatSink` leaf) is now built at factory case 0xBC6 ("Actuator"
|
||||||
|
mislabel) via `CreateMyomersSubsystem`. **KEY: it is wired INERT** — it constructs + ticks its real Performance
|
||||||
|
but does NOT affect gameplay, which is exactly what makes a mover-coupled subsystem safe to land: (a)
|
||||||
|
`MyomersSimulation` early-returns on `OwnerAdvancedDamage()` (the advanced-damage sim gate; the real source is
|
||||||
|
the still-stubbed SubsystemMessageManager 0xBD3, so it's a `False` constant → no motion-heat); (b) the mover
|
||||||
|
feed (`ConnectToMover`→`MoverAttach`) + `SetOwnerMaxSpeed` are no-op/local stubs → **the live JointedMover is
|
||||||
|
never touched → the gait/drive cannot regress** (confirmed: the mech walks normally, pos advances, adv ramps
|
||||||
|
0.13→1.05). The ctor DOES do real work: resolves the powering Generator (`ResolveVoltageSource`) + builds the
|
||||||
|
`seekVoltage[]` gear table from the resource. **De-shim:** the ~10 cross-family Mech/Mover/DamageZone shim
|
||||||
|
BACKING fields (ownerBaseSpeed/…/damageStructureLevel + segmentFlags) were DROPPED and their accessors return
|
||||||
|
the neutral defaults the ctor primed — necessary because the real object is EXACTLY 0x358 (zero headroom) and
|
||||||
|
the shim fields would overflow the alloc; locked `static_assert(sizeof(Myomers)<=0x358)` (heat-leaf branch not
|
||||||
|
byte-exact → overflow-lock only, like Sensor). The `Myomers` **name collision** is already handled in
|
||||||
|
myomers.hpp (`#define Myomers Myomers__powersub_stale` around the powersub.hpp include, since powersub.hpp
|
||||||
|
still carries the OLD Sensor-mislabel "Myomers" — that stale class should eventually be deleted from
|
||||||
|
powersub.hpp). **⚠ AUTHENTIC COUPLING = the real Wave-6 payoff, DEFERRED:** making the mech actually SLOW when
|
||||||
|
damaged/overheated + motion GENERATE heat needs (1) the advanced-damage gate from a real messmgr (0xBD3,
|
||||||
|
WAVE 8), (2) the `owner*` shims redirected to real Mech motion/mass/motion-gain accessors, and (3)
|
||||||
|
`MoverAttach` actually routing `speedEffect@0x31C` into the JointedMover roster — which **drives the LIVE
|
||||||
|
mover**, so it must be reconciled with the gait cutover FIRST (both would feed the mover) before enabling.
|
||||||
|
Factory now: **15 of 20 cases wired to real classes** (remaining: ProjectileWeapon/0xBCE/MissileLauncher
|
||||||
|
0xBCD/0xBD0 [WAVE 7], SubsystemMessageManager 0xBD3 [WAVE 8], Gyroscope 0xBC4 [deferred]).
|
||||||
**✅ HEAT-FLOW LINKAGE CONNECTED — weapon heat now conducts to the central sink** (verified: `[heat]
|
**✅ HEAT-FLOW LINKAGE CONNECTED — weapon heat now conducts to the central sink** (verified: `[heat]
|
||||||
conduct self.E=.. -> other.E`, sustained fire `FIRED #1..#21+`, combat intact, no crash). THREE fixes,
|
conduct self.E=.. -> other.E`, sustained fire `FIRED #1..#21+`, combat intact, no crash). THREE fixes,
|
||||||
each faithful to the decomp (no stand-ins):
|
each faithful to the decomp (no stand-ins):
|
||||||
@@ -1796,8 +1861,10 @@ brightness is correct — the bug was saturation, not the lack of fade.
|
|||||||
**Remaining (real weapon fidelity):** per-weapon color from the beam model (blue PPC, yellow standard lasers)
|
**Remaining (real weapon fidelity):** per-weapon color from the beam model (blue PPC, yellow standard lasers)
|
||||||
once weapon enumeration is wired (factory class IDs, not simulationFlags); the throttled real-fire beam (drive
|
once weapon enumeration is wired (factory class IDs, not simulationFlags); the throttled real-fire beam (drive
|
||||||
off Emitter::FireWeapon, heat); damage by the aimed zone (cylinder lookup, STEP 6).
|
off Emitter::FireWeapon, heat); damage by the aimed zone (cylinder lookup, STEP 6).
|
||||||
Remaining subsystem families (gyro/torso/myomers motion, sensor/searchlight/thermalsight readouts,
|
Remaining subsystem families (projectile/missile weapons 0xBCD/0xBCE/0xBD0 [WAVE 7]; SubsystemMessageManager
|
||||||
projectile/missile weapons 0xBCD/0xBCE/0xBD0) per SUBSYS_PLAN.md WAVEs 4-8.
|
0xBD3 [WAVE 8]; Gyroscope 0xBC4 deferred — NaN integrator) per SUBSYS_PLAN.md. DONE: Torso (WAVE 5) +
|
||||||
|
Sensor/Searchlight/ThermalSight/AmmoBin readouts (WAVE 4) + Myomers structural un-stub (WAVE 6, INERT —
|
||||||
|
authentic mover/heat coupling deferred).
|
||||||
|
|
||||||
**✅ TORSO twist/elevation aim — joint-I/O + simulation RECONSTRUCTED (dormant; blocked on the WAVE-4 un-stub).**
|
**✅ TORSO twist/elevation aim — joint-I/O + simulation RECONSTRUCTED (dormant; blocked on the WAVE-4 un-stub).**
|
||||||
The torso's aim→skeleton path is now fully reconstructed and builds green with zero combat regression, but is
|
The torso's aim→skeleton path is now fully reconstructed and builds green with zero combat regression, but is
|
||||||
|
|||||||
+2
-2
@@ -88,7 +88,7 @@ The whole roster sits on the BT base chain `engine Subsystem → MechSubsystem
|
|||||||
- **WAVE 3 — Energy weapons (user: "emitter/PPC second").**
|
- **WAVE 3 — Energy weapons (user: "emitter/PPC second").**
|
||||||
- **3a (dependency):** power bus — Generator (0xBC1) + PoweredSubsystem (0xBC2). Low-risk, foundational; energy weapons need it to charge (`GetVoltageState()==4`).
|
- **3a (dependency):** power bus — Generator (0xBC1) + PoweredSubsystem (0xBC2). Low-risk, foundational; energy weapons need it to charge (`GetVoltageState()==4`).
|
||||||
- **3b:** Emitter (0xBC8) + PPC (0xBD4). Construct+tick+fire underneath; **keep the explosion stand-in for the visual** (beam renderable deferred — see §4).
|
- **3b:** Emitter (0xBC8) + PPC (0xBD4). Construct+tick+fire underneath; **keep the explosion stand-in for the visual** (beam renderable deferred — see §4).
|
||||||
- **WAVE 4 — Standalone readouts (low risk, DONE classes):** Sensor (0xBC3), Searchlight (0xBD8), ThermalSight (0xBDE), AmmoBin (0xBCB). Pure power/heat-gated readouts; run standalone.
|
- **WAVE 4 — Standalone readouts: ✅ DONE + WIRED (live, verified).** Sensor (0xBC3), Searchlight (0xBD8), ThermalSight (0xBDE), AmmoBin (0xBCB) all un-stubbed via `Create<Class>Subsystem` bridges + de-shim + `<Class>LayoutCheck` static_asserts. BLH tick 20→26, Mad Cat 24, combat DESTROYED un-regressed, 0 crashes. KEY finds: (a) Searchlight/ThermalSight ctor gate read the shadow `segmentFlags`(=0) → Performance NEVER installed → fixed to `owner->simulationFlags`; (b) AmmoBin `ammoAlarm` retyped `HeatAlarm`→`WatcherGaugeAlarm`(0x54) + `statusState`@0x40 shadow deleted; (c) the heat-leaf branch (Sensor's PoweredSubsystem:HeatSink) is NOT layout-exact → overflow-lock only (offset re-base deferred to Phase-4 HUD); (d) shared `HeatWatcher::WatchSimulation` null-deref on the unresolved `watchedLink` GUARDED (faithful follow-up = resolve the watch link). See CLAUDE.md §10d "WAVE 4".
|
||||||
- **WAVE 5 — Torso (0xBC5): ✅ DONE (live, binary-exact layout, zero regression).** The joint I/O
|
- **WAVE 5 — Torso (0xBC5): ✅ DONE (live, binary-exact layout, zero regression).** The joint I/O
|
||||||
(`Mech::ResolveJoint`/`Torso::PushTwist` + corrected `TorsoSimulation` + per-frame `UpdateJoints()`) AND the
|
(`Mech::ResolveJoint`/`Torso::PushTwist` + corrected `TorsoSimulation` + per-frame `UpdateJoints()`) AND the
|
||||||
base-chain re-base are complete. Hierarchy is `Torso : PowerWatcher : HeatWatcher : MechSubsystem` (a SEPARATE
|
base-chain re-base are complete. Hierarchy is `Torso : PowerWatcher : HeatWatcher : MechSubsystem` (a SEPARATE
|
||||||
@@ -108,7 +108,7 @@ The whole roster sits on the BT base chain `engine Subsystem → MechSubsystem
|
|||||||
dampingConstant@0x1F4, ...; recon mislabels + leaves accumulators uninit). Reverted to the stub (no NaN to the root
|
dampingConstant@0x1F4, ...; recon mislabels + leaves accumulators uninit). Reverted to the stub (no NaN to the root
|
||||||
joint). Remaining = a full ctor+integrator reconstruction from @004b3778 (bigger than the torso). Full detail: CLAUDE.md
|
joint). Remaining = a full ctor+integrator reconstruction from @004b3778 (bigger than the torso). Full detail: CLAUDE.md
|
||||||
§10d "WAVE-5 GYROSCOPE". Also: the mech.cpp gyro↔torso cross-link write is broken (SubProxy::linkTarget→gyro+4) — commented out.
|
§10d "WAVE-5 GYROSCOPE". Also: the mech.cpp gyro↔torso cross-link write is broken (SubProxy::linkTarget→gyro+4) — commented out.
|
||||||
- **WAVE 6 — Mover-coupled:** Myomers (0xBC6). Routes `SpeedEffect` into the Mech mover (`Mech+0x128`), writes `maxSpeed@0x7A0`, loops the myomer list `@0x1EB`. Verify those Mech offsets are valid before enabling (Risk 7), else no-op/wild writes.
|
- **WAVE 6 — Mover-coupled: ◐ STRUCTURAL un-stub DONE + WIRED (gated `BT_MYOMERS` default ON; verified INERT, no locomotion/combat regression).** Myomers (0xBC6) constructs + ticks its real Performance via `CreateMyomersSubsystem`, but is INERT: `MyomersSimulation` early-returns on `OwnerAdvancedDamage()==False` (real gate = messmgr 0xBD3), and the mover feed (`MoverAttach`) + `SetOwnerMaxSpeed` are no-op stubs so the live JointedMover is never touched. De-shim dropped the `owner*`/`segmentFlags` shim fields (accessors return neutral defaults) to fit the exact-0x358 alloc (`sizeof` lock). ⚠ AUTHENTIC COUPLING DEFERRED: routing `SpeedEffect@0x31C` into the mover + real owner-motion accessors + the advanced-damage heat gate — this DRIVES THE LIVE MOVER, so reconcile with the gait cutover FIRST. BLH tick 26→27; `BT_MYOMERS=0` → Actuator stub. See CLAUDE.md §10d "WAVE 6".
|
||||||
- **WAVE 7 — Projectile/missile weapons:** ProjectileWeapon (0xBCD), MissileLauncher (0xBD0). **PARTIALLY BLOCKED:** case 0xBCE calls `@4bdcb4`, which has no CLASSMAP entry — decompile it first and resolve the 0xBCD/0xBCE/0xBD0 label tangle (Risk 1) before swapping these.
|
- **WAVE 7 — Projectile/missile weapons:** ProjectileWeapon (0xBCD), MissileLauncher (0xBD0). **PARTIALLY BLOCKED:** case 0xBCE calls `@4bdcb4`, which has no CLASSMAP entry — decompile it first and resolve the 0xBCD/0xBCE/0xBD0 label tangle (Risk 1) before swapping these.
|
||||||
- **WAVE 8 — Message hub:** SubsystemMessageManager (0xBD3). Real, but **verify the cache-slot/tick-bypass interaction** (Risk 4) before enabling its tick.
|
- **WAVE 8 — Message hub:** SubsystemMessageManager (0xBD3). Real, but **verify the cache-slot/tick-bypass interaction** (Risk 4) before enabling its tick.
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ AmmoBin::AmmoBin(
|
|||||||
{
|
{
|
||||||
// vtable installed by the compiler (PTR_FUN_0051286c)
|
// vtable installed by the compiler (PTR_FUN_0051286c)
|
||||||
|
|
||||||
statusState = 0;
|
// (WAVE 4) statusState=0 removed -- the base MechSubsystem ctor owns
|
||||||
|
// simulationState@0x40; the reads below use the inherited member.
|
||||||
ammoAlarm.Initialize(6); // @0x194 FUN_0041b9ec(this+0x65, 6)
|
ammoAlarm.Initialize(6); // @0x194 FUN_0041b9ec(this+0x65, 6)
|
||||||
InitCookOffHeatSource(&cookOffHeatSource); // @0x1F0 FUN_0041db7c(this+0x7c)
|
InitCookOffHeatSource(&cookOffHeatSource); // @0x1F0 FUN_0041db7c(this+0x7c)
|
||||||
reserved = 0; // @0x228
|
reserved = 0; // @0x228
|
||||||
@@ -171,7 +172,7 @@ AmmoBin::~AmmoBin()
|
|||||||
//
|
//
|
||||||
int AmmoBin::FeedAmmo()
|
int AmmoBin::FeedAmmo()
|
||||||
{
|
{
|
||||||
if (statusState == 1) // this+0x40 == 1 (destroyed)
|
if (simulationState == 1) // this+0x40 == 1 (destroyed)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
switch (ammoAlarm.Level()) // this+0x1A8
|
switch (ammoAlarm.Level()) // this+0x1A8
|
||||||
@@ -243,7 +244,7 @@ void AmmoBin::AmmoBinSimulation(Scalar time_slice)
|
|||||||
{
|
{
|
||||||
WatchSimulation(time_slice); // FUN_004aeac4(this) -- drive this+0x140
|
WatchSimulation(time_slice); // FUN_004aeac4(this) -- drive this+0x140
|
||||||
|
|
||||||
if (statusState == 1) // this+0x40 (destroyed)
|
if (simulationState == 1) // this+0x40 (destroyed)
|
||||||
ammoAlarm.SetLevel(Empty);
|
ammoAlarm.SetLevel(Empty);
|
||||||
|
|
||||||
// feed timer (only while a round is in transit)
|
// feed timer (only while a round is in transit)
|
||||||
@@ -313,9 +314,9 @@ Logical AmmoBin::HandleMessage(int message)
|
|||||||
//
|
//
|
||||||
void AmmoBin::ReadUpdateRecord(UpdateRecord *update)
|
void AmmoBin::ReadUpdateRecord(UpdateRecord *update)
|
||||||
{
|
{
|
||||||
int previousState = statusState; // this+0x40
|
int previousState = simulationState; // this+0x40 (inherited MechSubsystem)
|
||||||
HeatWatcher::ReadUpdateRecord(update); // FUN_004aea84
|
HeatWatcher::ReadUpdateRecord(update); // FUN_004aea84
|
||||||
if (statusState == 1 && previousState != 1)
|
if (simulationState == 1 && previousState != 1)
|
||||||
{
|
{
|
||||||
ammoCount = 0; // this+0x180
|
ammoCount = 0; // this+0x180
|
||||||
ammoAlarm.SetLevel(Empty); // 2
|
ammoAlarm.SetLevel(Empty); // 2
|
||||||
@@ -430,3 +431,42 @@ Logical AmmoBin::TestClass(Mech &)
|
|||||||
{
|
{
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//#############################################################################
|
||||||
|
// WAVE 4 -- compile-time layout locks (ZERO-headroom: sizeof MUST be 0x22C).
|
||||||
|
// Own fields are protected -> the friend AmmoBinLayoutCheck grants offsetof.
|
||||||
|
// The `ammoModelFile == 0x1E8` lock is LOAD-BEARING: it fails the build if
|
||||||
|
// either the ammoAlarm retype (HeatAlarm 8B -> WatcherGaugeAlarm 0x54) or the
|
||||||
|
// statusState deletion is missed. HeatWatcher is locked at 0x180.
|
||||||
|
//#############################################################################
|
||||||
|
struct AmmoBinLayoutCheck
|
||||||
|
{
|
||||||
|
static_assert(sizeof(AmmoBin) == 0x22C, "sizeof(AmmoBin) must be 0x22C (factory alloc, part_012.c:10091)");
|
||||||
|
static_assert(offsetof(AmmoBin, ammoCount) == 0x180, "AmmoBin own block starts at 0x180 (HeatWatcher ends 0x180)");
|
||||||
|
static_assert(offsetof(AmmoBin, ammoAlarm) == 0x194, "AmmoBin ammoAlarm @0x194 (0x54-byte WatcherGaugeAlarm)");
|
||||||
|
static_assert(offsetof(AmmoBin, ammoModelFile) == 0x1E8, "AmmoBin ammoModelFile @0x1E8 (proves ammoAlarm==0x54 AND statusState deleted)");
|
||||||
|
static_assert(offsetof(AmmoBin, explosionModelFile) == 0x1EC, "AmmoBin explosionModelFile @0x1EC");
|
||||||
|
static_assert(offsetof(AmmoBin, heatPerRound) == 0x1F4, "AmmoBin heatPerRound @0x1F4");
|
||||||
|
static_assert(offsetof(AmmoBin, initialAmmoCount) == 0x220, "AmmoBin initialAmmoCount @0x220");
|
||||||
|
static_assert(offsetof(AmmoBin, feedRate) == 0x224, "AmmoBin feedRate @0x224");
|
||||||
|
static_assert(offsetof(AmmoBin, reserved) == 0x228, "AmmoBin reserved @0x228 (+4 => sizeof 0x22C)");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================//
|
||||||
|
// Factory bridge -- AmmoBin (factory case 0xBCB, "JumpJet" label).
|
||||||
|
// The real class at 0xBCB (ctor @004bd5c4) is AmmoBin; the factory built a
|
||||||
|
// JumpJet RECON_SUBSYS stub. alloc 0x22c, ctor(owner,id,resource) -- the ctor
|
||||||
|
// supplies AmmoBin::DefaultData to the HeatWatcher base itself. No cache
|
||||||
|
// write; not a weapon (raw part_012.c:10090-10099 stores roster-only).
|
||||||
|
// NOTE: the Blackhawk (all-laser) carries NO 0xBCB; exercise with a ballistic/
|
||||||
|
// missile mech (MECH2.EGG = Mad Cat, LRMs). Its FeedAmmo consumer
|
||||||
|
// (ProjectileWeapon/MissileLauncher 0xBCD/0xBD0) is still stubbed, so full
|
||||||
|
// effect couples to the weapons wave -- but the bin's own feed/cook-off ticks.
|
||||||
|
//===========================================================================//
|
||||||
|
Subsystem *CreateAmmoBinSubsystem(Mech *owner, int id, void *seg)
|
||||||
|
{
|
||||||
|
return (Subsystem *) new (Memory::Allocate(0x22c))
|
||||||
|
AmmoBin(owner, id, (AmmoBin::SubsystemResource *)seg);
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
class AmmoBin:
|
class AmmoBin:
|
||||||
public HeatWatcher
|
public HeatWatcher
|
||||||
{
|
{
|
||||||
|
friend struct AmmoBinLayoutCheck; // compile-time offset locks (ammobin.cpp)
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Shared Data Support
|
// Shared Data Support
|
||||||
//
|
//
|
||||||
@@ -203,8 +204,11 @@
|
|||||||
int ammoClassID; // @0x188 (word 0x62) entity class spawned per round
|
int ammoClassID; // @0x188 (word 0x62) entity class spawned per round
|
||||||
int cookOffArmed; // @0x18C (word 0x63) cook-off timer running
|
int cookOffArmed; // @0x18C (word 0x63) cook-off timer running
|
||||||
int cookOffTime; // @0x190 (word 0x64) game clock at which it detonates
|
int cookOffTime; // @0x190 (word 0x64) game clock at which it detonates
|
||||||
HeatAlarm ammoAlarm; // @0x194 (word 0x65) 6-level feed alarm (level @0x1A8)
|
WatcherGaugeAlarm ammoAlarm; // @0x194 (0x54) 6-level feed alarm (level @0x1A8)
|
||||||
int statusState; // base subsystem damage-state mirror (this+0x40)
|
// (WAVE 4) `int statusState` DELETED -- it was a this+0x40 SHADOW of the
|
||||||
|
// inherited MechSubsystem::simulationState; the reads now use that base
|
||||||
|
// member. The 0x54 ammoAlarm (was an 8-byte HeatAlarm) lands ammoModelFile
|
||||||
|
// at 0x1E8 -- exact binary layout, locked by AmmoBinLayoutCheck.
|
||||||
int ammoModelFile; // @0x1E8 (word 0x7A) round model index
|
int ammoModelFile; // @0x1E8 (word 0x7A) round model index
|
||||||
int explosionModelFile; // @0x1EC (word 0x7B) cook-off explosion model index
|
int explosionModelFile; // @0x1EC (word 0x7B) cook-off explosion model index
|
||||||
// @0x1F0 (word 0x7C) cook-off heat-injection descriptor, inited by
|
// @0x1F0 (word 0x7C) cook-off heat-injection descriptor, inited by
|
||||||
|
|||||||
@@ -302,6 +302,23 @@ void
|
|||||||
HeatWatcher::WatchSimulation(Scalar /*time_slice*/) // FUN_004aeac4
|
HeatWatcher::WatchSimulation(Scalar /*time_slice*/) // FUN_004aeac4
|
||||||
{
|
{
|
||||||
HeatableSubsystem *watched = (HeatableSubsystem *)watchedLink.Resolve(); // FUN_00417ab4(this+0x114)
|
HeatableSubsystem *watched = (HeatableSubsystem *)watchedLink.Resolve(); // FUN_00417ab4(this+0x114)
|
||||||
|
|
||||||
|
// BRING-UP GUARD (marked deviation): in the shipped game watchedLink ALWAYS
|
||||||
|
// resolves -- the HeatWatcher resource's "WatchedSubsystem" name binds to a
|
||||||
|
// roster segment at load -- so the binary (FUN_004aeac4) derefs `watched`
|
||||||
|
// unconditionally. Our port does not yet resolve the watch link, so Resolve()
|
||||||
|
// returns null; the deref AVs the moment a HeatWatcher-derived subsystem's
|
||||||
|
// Performance actually runs (exposed by the WAVE 4 un-stub: AmmoBinSimulation
|
||||||
|
// calls this directly; PowerWatcher::Simulation for Searchlight/ThermalSight).
|
||||||
|
// With no resolved source, hold the heat alarm at Normal (same established
|
||||||
|
// pattern as Emitter::TrackSeekVoltage's null-source guard). FAITHFUL
|
||||||
|
// FOLLOW-UP = resolve watchedLink so the alarm tracks the real temperature.
|
||||||
|
if (watched == 0)
|
||||||
|
{
|
||||||
|
heatAlarm.SetLevel(0); // NormalHeat (no watched source yet)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Scalar temp = watched->currentTemperature; // *(watched + 0x114)
|
Scalar temp = watched->currentTemperature; // *(watched + 0x114)
|
||||||
|
|
||||||
if (temp > failureTemperature) // > this+0x124
|
if (temp > failureTemperature) // > this+0x124
|
||||||
|
|||||||
+38
-15
@@ -196,6 +196,11 @@ extern Subsystem *CreateGeneratorSubsystem(Mech *, int, void *); // 0xBC1 (WAVE
|
|||||||
extern Subsystem *CreatePoweredSubsystem(Mech *, int, void *); // 0xBC2 (WAVE 3a)
|
extern Subsystem *CreatePoweredSubsystem(Mech *, int, void *); // 0xBC2 (WAVE 3a)
|
||||||
extern Subsystem *CreateEmitterSubsystem(Mech *, int, void *); // 0xBC8/0xBD4 (WAVE 3b)
|
extern Subsystem *CreateEmitterSubsystem(Mech *, int, void *); // 0xBC8/0xBD4 (WAVE 3b)
|
||||||
extern Subsystem *CreateTorsoSubsystem(Mech *, int, void *); // 0xBC5 (WAVE 4 -- base re-based, layout locked)
|
extern Subsystem *CreateTorsoSubsystem(Mech *, int, void *); // 0xBC5 (WAVE 4 -- base re-based, layout locked)
|
||||||
|
extern Subsystem *CreateSensorSubsystem(Mech *, int, void *); // 0xBC3 (WAVE 4 readouts -- de-shimmed, layout locked)
|
||||||
|
extern Subsystem *CreateSearchlightSubsystem(Mech *, int, void *); // 0xBD8 (WAVE 4 readouts -- de-shimmed + gate fixed)
|
||||||
|
extern Subsystem *CreateThermalSightSubsystem(Mech *, int, void *); // 0xBDE (WAVE 4 readouts -- de-shimmed + gate fixed)
|
||||||
|
extern Subsystem *CreateAmmoBinSubsystem(Mech *, int, void *); // 0xBCB (WAVE 4 -- AmmoBin : HeatWatcher, layout locked)
|
||||||
|
extern Subsystem *CreateMyomersSubsystem(Mech *, int, void *); // 0xBC6 (WAVE 6 -- mover-coupled drive; INERT, gated BT_MYOMERS)
|
||||||
// CreateGyroSubsystem (0xBC4) is reconstructed + ready in gyro.cpp (layout re-based,
|
// CreateGyroSubsystem (0xBC4) is reconstructed + ready in gyro.cpp (layout re-based,
|
||||||
// joint I/O real), but NOT wired -- the gyro ctor/integrator reconstruction is
|
// joint I/O real), but NOT wired -- the gyro ctor/integrator reconstruction is
|
||||||
// incomplete (garbage output); see the 0xBC4 case below.
|
// incomplete (garbage output); see the 0xBC4 case below.
|
||||||
@@ -689,9 +694,11 @@ Mech::Mech(
|
|||||||
subsystemArray[id] = CreatePoweredSubsystem(this, id, seg); // FUN_004b0f74
|
subsystemArray[id] = CreatePoweredSubsystem(this, id, seg); // FUN_004b0f74
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MyomersClassID: // 0xBC3
|
case MyomersClassID: // 0xBC3 -> real class Sensor (ctor @004b1d18, "Myomers" mislabel)
|
||||||
subsystemArray[id] = (Subsystem *)
|
// WAVE 4: the real class at 0xBC3 is Sensor (string pool @0050fae0 +
|
||||||
new (Memory::Allocate(0x328)) Myomers(this, id, seg); // FUN_004b1d18
|
// SENSOR.HPP); MyomersClassID is a factory-enum mislabel (real Myomers
|
||||||
|
// is 0xBC6). De-shimmed + layout-locked (radarPercent@0x31C, sizeof==0x328).
|
||||||
|
subsystemArray[id] = CreateSensorSubsystem(this, id, seg); // FUN_004b1d18
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GyroClassID: // 0xBC4 -> real class Gyroscope (ctor @004b3778)
|
case GyroClassID: // 0xBC4 -> real class Gyroscope (ctor @004b3778)
|
||||||
@@ -717,9 +724,19 @@ Mech::Mech(
|
|||||||
sinkSourceSubsystem = subsystemArray[id]; // Wword(0x10e) -- gyro links to +0x1D8
|
sinkSourceSubsystem = subsystemArray[id]; // Wword(0x10e) -- gyro links to +0x1D8
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ActuatorClassID: // 0xBC6
|
case ActuatorClassID: // 0xBC6 -> real class Myomers (ctor @004b8fec, "Actuator" stub)
|
||||||
subsystemArray[id] = (Subsystem *)
|
// WAVE 6: the real class at 0xBC6 is Myomers (the artificial-muscle
|
||||||
new (Memory::Allocate(0x358)) Actuator(this, id, seg); // FUN_004b8fec
|
// drive). GATED BT_MYOMERS (default ON): the real class constructs +
|
||||||
|
// ticks but is INERT w.r.t. locomotion/heat (no-op mover feed + the
|
||||||
|
// advanced-damage sim gate is False), so it can't regress the gait.
|
||||||
|
// BT_MYOMERS=0 falls back to the Actuator stub. Authentic mover/heat
|
||||||
|
// coupling is a follow-up (needs real Mech accessors + messmgr 0xBD3,
|
||||||
|
// and must reconcile the mover feed with the gait cutover first).
|
||||||
|
if (BTEnvOn("BT_MYOMERS", 1))
|
||||||
|
subsystemArray[id] = CreateMyomersSubsystem(this, id, seg); // FUN_004b8fec
|
||||||
|
else
|
||||||
|
subsystemArray[id] = (Subsystem *)
|
||||||
|
new (Memory::Allocate(0x358)) Actuator(this, id, seg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WeaponEmitterClassID: // 0xBC8 -> real Emitter beam weapon
|
case WeaponEmitterClassID: // 0xBC8 -> real Emitter beam weapon
|
||||||
@@ -727,9 +744,11 @@ Mech::Mech(
|
|||||||
++weaponCount;
|
++weaponCount;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case JumpJetClassID: // 0xBCB
|
case JumpJetClassID: // 0xBCB -> real class AmmoBin (ctor @004bd5c4, mislabeled)
|
||||||
subsystemArray[id] = (Subsystem *)
|
// WAVE 4: AmmoBin : HeatWatcher : MechSubsystem; own block @0x180,
|
||||||
new (Memory::Allocate(0x22c)) JumpJet(this, id, seg); // FUN_004bd5c4
|
// sizeof 0x22C compile-time locked. Feeds a ProjectileWeapon/
|
||||||
|
// MissileLauncher (0xBCD/0xBD0) via a SharedData connection (still stubbed).
|
||||||
|
subsystemArray[id] = CreateAmmoBinSubsystem(this, id, seg); // FUN_004bd5c4
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MechWeaponClassID: // 0xBCD
|
case MechWeaponClassID: // 0xBCD
|
||||||
@@ -766,9 +785,11 @@ Mech::Mech(
|
|||||||
hudSubsystem = subsystemArray[id]; // raw part_012.c:10164 param_1[0x16d] = HUD
|
hudSubsystem = subsystemArray[id]; // raw part_012.c:10164 param_1[0x16d] = HUD
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LegSubsystemClassID: // 0xBD8
|
case LegSubsystemClassID: // 0xBD8 -> real class Searchlight (ctor @004b84dc, mislabeled)
|
||||||
subsystemArray[id] = (Subsystem *)
|
// WAVE 4: de-shimmed + GATE FIXED (was gating on the shadow
|
||||||
new (Memory::Allocate(0x238)) LegSubsystem(this, id, seg); // FUN_004b84dc
|
// segmentFlags -> Performance never installed). Searchlight :
|
||||||
|
// PowerWatcher; own fields layout-locked at 0x1D8+.
|
||||||
|
subsystemArray[id] = CreateSearchlightSubsystem(this, id, seg); // FUN_004b84dc
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HeatableClassID: // 0xBDC -> real class MechTech (mislabeled).
|
case HeatableClassID: // 0xBDC -> real class MechTech (mislabeled).
|
||||||
@@ -778,9 +799,11 @@ Mech::Mech(
|
|||||||
// the 0x5b4 cache belongs to case 0xbd6 (the HUD) -- earlier recon had it swapped.
|
// the 0x5b4 cache belongs to case 0xbd6 (the HUD) -- earlier recon had it swapped.
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DisplayClassID: // 0xBDE
|
case DisplayClassID: // 0xBDE -> real class ThermalSight (ctor @004b8718, "MechDisplay" stub)
|
||||||
subsystemArray[id] = (Subsystem *)
|
// WAVE 4: de-shimmed + GATE FIXED. ThermalSight : PowerWatcher;
|
||||||
new (Memory::Allocate(0x234)) MechDisplay(this, id, seg); // FUN_004b8718
|
// own fields layout-locked at 0x1D8+. The cockpit IR view-mode
|
||||||
|
// render hooks (pvision/viewport) are clearly-marked no-ops.
|
||||||
|
subsystemArray[id] = CreateThermalSightSubsystem(this, id, seg); // FUN_004b8718
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -142,18 +142,10 @@ Myomers::Myomers(
|
|||||||
// (heat-base default coefficient @0x150 = 0.15f; inherited HeatSink slot)
|
// (heat-base default coefficient @0x150 = 0.15f; inherited HeatSink slot)
|
||||||
// (Subsystem flags @0x28 |= 8; optional graphic-state hook -- see header note)
|
// (Subsystem flags @0x28 |= 8; optional graphic-state hook -- see header note)
|
||||||
|
|
||||||
// CROSS-FAMILY shim backing fields -- neutral defaults; the real values are
|
// (WAVE 6 de-shim) the owner*/damageStructure shim backing fields are gone --
|
||||||
// supplied by the owning Mech / its mover / this segment's DamageZone.
|
// their accessors return neutral defaults directly. Only moverConnection
|
||||||
ownerBaseSpeed = 1.0f;
|
// (used by ConnectToMover) remains a member.
|
||||||
ownerMaxSpeed = 0.0f;
|
moverConnection = -1; // @0x110 (detached)
|
||||||
ownerVelocityMag = 0.0f;
|
|
||||||
ownerDriveMag = 0.0f;
|
|
||||||
ownerVelocityY = 0.0f;
|
|
||||||
ownerMotionGain = 0.0f;
|
|
||||||
ownerMass = 0.0f;
|
|
||||||
ownerAdvancedDamage = False;
|
|
||||||
damageStructureLevel = 0.0f;
|
|
||||||
moverConnection = -1; // @0x110
|
|
||||||
|
|
||||||
// Register the per-tick Performance. The binary ctor @004b8fec stores the
|
// Register the per-tick Performance. The binary ctor @004b8fec stores the
|
||||||
// MyomersSimulation pointer-to-member into activePerformance (this[7..9] =
|
// MyomersSimulation pointer-to-member into activePerformance (this[7..9] =
|
||||||
@@ -163,11 +155,8 @@ Myomers::Myomers(
|
|||||||
// torso this[7..9]=PTR_FUN_00510c10). The earlier reconstruction mislabeled
|
// torso this[7..9]=PTR_FUN_00510c10). The earlier reconstruction mislabeled
|
||||||
// this store a "graphic-state hook" and omitted the registration, leaving
|
// this store a "graphic-state hook" and omitted the registration, leaving
|
||||||
// activePerformance at the DoNothingOnce default (drops out after frame 1).
|
// activePerformance at the DoNothingOnce default (drops out after frame 1).
|
||||||
// segmentFlags is the CROSS-FAMILY Mech-segment field, primed to the live-
|
// (WAVE 6) the segmentFlags shim is removed; the gate reads OWNER
|
||||||
// master value so the drive/heat integrator actually ticks.
|
// simulationFlags (param_2+0x28) directly -- the oracle-verified source.
|
||||||
segmentFlags = MasterHeatSinkFlag;
|
|
||||||
// INTEGRATION (gate reconcile): read OWNER simulationFlags (param_2+0x28) —
|
|
||||||
// the oracle-verified authoritative source — not the local primed shim.
|
|
||||||
if ((owner->simulationFlags & SegmentCopyMask) == 0
|
if ((owner->simulationFlags & SegmentCopyMask) == 0
|
||||||
&& (owner->simulationFlags & MasterHeatSinkFlag) != 0) // owner flags & 0x100 (binary @004b8fec)
|
&& (owner->simulationFlags & MasterHeatSinkFlag) != 0) // owner flags & 0x100 (binary @004b8fec)
|
||||||
{
|
{
|
||||||
@@ -527,3 +516,33 @@ void Myomers::ToggleSeekVoltage()
|
|||||||
{
|
{
|
||||||
// cycle currentSeekVoltageIndex within [minSeekVoltageIndex, maxSeekVoltageIndex]
|
// cycle currentSeekVoltageIndex within [minSeekVoltageIndex, maxSeekVoltageIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================//
|
||||||
|
// WAVE 6 -- compile-time OVERFLOW lock (the real class must fit the 0x358
|
||||||
|
// factory alloc). Like Sensor, the heat-leaf base (PoweredSubsystem:HeatSink)
|
||||||
|
// is NOT byte-exact to the binary, so we lock only the overflow bound, not the
|
||||||
|
// exact field offsets -- nothing reads Myomers at a raw offset today (the mover
|
||||||
|
// feed + gauge readouts are inert) and MyomersSimulation uses named members.
|
||||||
|
// The de-shim dropped the owner*/segmentFlags backing fields to make room.
|
||||||
|
//===========================================================================//
|
||||||
|
struct MyomersLayoutCheck
|
||||||
|
{
|
||||||
|
static_assert(sizeof(Myomers) <= 0x358, "sizeof(Myomers) must fit the factory Memory::Allocate(0x358)");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================//
|
||||||
|
// WAVE 6 factory bridge -- Myomers (factory case 0xBC6, "Actuator" stub).
|
||||||
|
// The real class at 0xBC6 (ctor @004b8fec) is Myomers (the mech's artificial-
|
||||||
|
// muscle drive); the factory built an Actuator RECON_SUBSYS stub. alloc 0x358.
|
||||||
|
// The subsystem constructs (resolves its Generator + builds the seek-voltage
|
||||||
|
// gear table) and ticks its real Performance, but is currently INERT w.r.t.
|
||||||
|
// locomotion/heat (no-op mover feed + the OwnerAdvancedDamage()==False sim gate)
|
||||||
|
// -- so it cannot regress the gait/drive. Authentic coupling = a follow-up.
|
||||||
|
//===========================================================================//
|
||||||
|
Subsystem *CreateMyomersSubsystem(Mech *owner, int id, void *seg)
|
||||||
|
{
|
||||||
|
return (Subsystem *) new (Memory::Allocate(0x358))
|
||||||
|
Myomers(owner, id, (Myomers::SubsystemResource *)seg);
|
||||||
|
}
|
||||||
|
|||||||
@@ -282,43 +282,41 @@ class Mech;
|
|||||||
// belong on Mech / JointedMover / DamageZone -- see report CROSS-FAMILY NEEDS.
|
// belong on Mech / JointedMover / DamageZone -- see report CROSS-FAMILY NEEDS.
|
||||||
//
|
//
|
||||||
protected:
|
protected:
|
||||||
Scalar OwnerBaseSpeed() const { return ownerBaseSpeed; } // Mech +0x34C base-speed scalar
|
// WAVE 6 un-stub -- CROSS-FAMILY Mech/Mover/DamageZone couplings. These
|
||||||
Scalar OwnerMaxSpeed() const { return ownerMaxSpeed; } // Mech +0x7A0 top speed
|
// logically read the owning Mech's motion state, its JointedMover roster,
|
||||||
void SetOwnerMaxSpeed(Scalar s) { ownerMaxSpeed = s; }
|
// and this segment's DamageZone -- none reachable from a PoweredSubsystem
|
||||||
Scalar OwnerVelocityMag() const { return ownerVelocityMag; } // |Mech velocity| (+0x1C4..)
|
// here (owner is an incomplete Mech*, JointedMover exposes no roster API,
|
||||||
Scalar OwnerDriveMag() const { return ownerDriveMag; } // |Mech drive/accel| (+0x82C..)
|
// DamageZone is not in this hierarchy). To let the real class FIT its 0x358
|
||||||
Scalar OwnerVelocityY() const { return ownerVelocityY; } // Mech velocity.y (+0x1C8)
|
// factory alloc, the shim BACKING FIELDS are dropped and the accessors
|
||||||
Scalar OwnerMotionGain() const { return ownerMotionGain; } // Mech +0x20C
|
// return the neutral defaults the ctor used to prime, so the subsystem
|
||||||
Scalar OwnerMass() const { return ownerMass; } // *Mech +0x250
|
// CONSTRUCTS + TICKS but is INERT w.r.t. locomotion/heat: the mover feed is
|
||||||
Logical OwnerAdvancedDamage() const { return ownerAdvancedDamage; } // FUN_004ad7d4 sim-control gate
|
// a no-op and MyomersSimulation early-returns on OwnerAdvancedDamage()==False.
|
||||||
Scalar DamageStructureLevel() const{ return damageStructureLevel; }// DamageZone +0x158
|
// ⚠ FAITHFUL FOLLOW-UP: redirect these to real Mech/JointedMover/DamageZone
|
||||||
|
// accessors + the advanced-damage gate (SubsystemMessageManager 0xBD3) so the
|
||||||
|
// mech actually slows when damaged/overheated and motion generates heat --
|
||||||
|
// that path DOES drive the live mover, so reconcile it with the gait cutover
|
||||||
|
// FIRST (both would feed the mover).
|
||||||
|
Scalar OwnerBaseSpeed() const { return 1.0f; } // Mech +0x34C (neutral)
|
||||||
|
Scalar OwnerMaxSpeed() const { return 0.0f; } // Mech +0x7A0
|
||||||
|
void SetOwnerMaxSpeed(Scalar) { } // no-op (real: write Mech top speed)
|
||||||
|
Scalar OwnerVelocityMag() const { return 0.0f; } // |Mech velocity|
|
||||||
|
Scalar OwnerDriveMag() const { return 0.0f; } // |Mech drive/accel|
|
||||||
|
Scalar OwnerVelocityY() const { return 0.0f; } // Mech velocity.y
|
||||||
|
Scalar OwnerMotionGain() const { return 0.0f; } // Mech +0x20C
|
||||||
|
Scalar OwnerMass() const { return 0.0f; } // *Mech +0x250
|
||||||
|
Logical OwnerAdvancedDamage() const { return False; } // advanced-damage gate (keeps sim INERT)
|
||||||
|
Scalar DamageStructureLevel() const{ return 0.0f; } // DamageZone +0x158
|
||||||
|
|
||||||
void MoverAttach(int index, Scalar *feed) { (void)index; (void)feed; } // JointedMover vtable +0x38
|
void MoverAttach(int index, Scalar *feed) { (void)index; (void)feed; } // JointedMover vtable +0x38 (no-op)
|
||||||
void MoverDetach(int index) { (void)index; } // JointedMover vtable +0x3C
|
void MoverDetach(int index) { (void)index; } // JointedMover vtable +0x3C (no-op)
|
||||||
void MoverRelease(int handle, Scalar *feed) { (void)handle; (void)feed; } // JointedMover vtable +0x44
|
void MoverRelease(int handle, Scalar *feed) { (void)handle; (void)feed; } // JointedMover vtable +0x44 (no-op)
|
||||||
|
|
||||||
Scalar ownerBaseSpeed;
|
// Live-master gate constants (the ctor reads owner->simulationFlags).
|
||||||
Scalar ownerMaxSpeed;
|
|
||||||
Scalar ownerVelocityMag;
|
|
||||||
Scalar ownerDriveMag;
|
|
||||||
Scalar ownerVelocityY;
|
|
||||||
Scalar ownerMotionGain;
|
|
||||||
Scalar ownerMass;
|
|
||||||
Logical ownerAdvancedDamage;
|
|
||||||
Scalar damageStructureLevel;
|
|
||||||
int moverConnection; // @0x110 mover attachment handle (-1 == detached)
|
|
||||||
|
|
||||||
// CROSS-FAMILY: owning-Mech segment flags (read at owner+0x28 in the
|
|
||||||
// binary ctor @004b8fec). The Performance is registered only for the
|
|
||||||
// live MASTER segment: (flags & 0xC)==0 && (flags & 0x100). Backed
|
|
||||||
// locally and primed to the live-master value so MyomersSimulation is
|
|
||||||
// installed and the drive/heat integrator ticks; the real flags belong
|
|
||||||
// on the Mech segment. See report CROSS-FAMILY NEEDS.
|
|
||||||
enum {
|
enum {
|
||||||
SegmentCopyMask = 0x0C, // (flags & 0xC): 0 == master, 4 == damaged copy
|
SegmentCopyMask = 0x0C, // (flags & 0xC): 0 == master, 4 == damaged copy
|
||||||
MasterHeatSinkFlag = 0x0100 // live-master segment bit
|
MasterHeatSinkFlag = 0x0100 // live-master segment bit
|
||||||
};
|
};
|
||||||
Word segmentFlags;
|
int moverConnection; // @0x110 mover attachment handle (-1 == detached; set by ConnectToMover)
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Local data. Offsets are byte offsets into the shipped object.
|
// Local data. Offsets are byte offsets into the shipped object.
|
||||||
|
|||||||
@@ -82,21 +82,20 @@ Searchlight::Searchlight(
|
|||||||
Check(owner);
|
Check(owner);
|
||||||
Check_Pointer(subsystem_resource);
|
Check_Pointer(subsystem_resource);
|
||||||
|
|
||||||
segmentFlags = 0;
|
// (WAVE 4 de-shim) the 7 cross-family shim fields are gone; the accessors
|
||||||
hostShutDown = False;
|
// read the inherited base state directly. Only the own fields init here.
|
||||||
watchedVoltageLevel = 4; // Ready
|
|
||||||
heatStateLevel = 0; // NormalHeat
|
|
||||||
controlsAllowLights = True;
|
|
||||||
graphicsDirty = False;
|
|
||||||
instanceFlags = 0;
|
|
||||||
|
|
||||||
lightState = 0; // @0x1D8
|
lightState = 0; // @0x1D8
|
||||||
requestedOn = 0; // @0x1E0
|
requestedOn = 0; // @0x1E0
|
||||||
stateAlarm.Initialize(2); // FUN_0041b9ec(this+0x1E4, 2)
|
stateAlarm.Initialize(2); // FUN_0041b9ec(this+0x1E4, 2)
|
||||||
commandedOn = subsystem_resource->segmentIndex; // @0x1DC <- inherited resource +0x28
|
commandedOn = subsystem_resource->segmentIndex; // @0x1DC <- inherited resource +0x28
|
||||||
|
|
||||||
if (((GetSegmentFlags() & 0xC) == 0) &&
|
// GATE FIX (the functional bug the de-shim unblocks): the binary reads OWNER
|
||||||
((GetSegmentFlags() & 0x100) != 0))
|
// simulationFlags (param_2+0x28, raw part_013.c:6012), NOT the old shadow
|
||||||
|
// `segmentFlags` -- which was seeded 0, so the gate was ALWAYS FALSE and
|
||||||
|
// SearchlightSimulation NEVER installed. A live master segment arms it; a
|
||||||
|
// copy/replicant gets DontExecute (flag 2). Mirrors torso.cpp:181-182.
|
||||||
|
if (((owner->simulationFlags & 0xC) == 0) &&
|
||||||
|
((owner->simulationFlags & 0x100) != 0))
|
||||||
{
|
{
|
||||||
SetPerformance(&Searchlight::SearchlightSimulation); // this[7..9] <- PTR_FUN_00511200
|
SetPerformance(&Searchlight::SearchlightSimulation); // this[7..9] <- PTR_FUN_00511200
|
||||||
}
|
}
|
||||||
@@ -256,3 +255,34 @@ int
|
|||||||
|
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================//
|
||||||
|
// WAVE 4 -- compile-time layout locks. PowerWatcher is Torso-proven at 0x1D8,
|
||||||
|
// so the own fields land at their EXACT binary offsets; sizeof is bounded to
|
||||||
|
// the factory alloc. (BtAlarm is a 4-byte stand-in for the binary's 0x54
|
||||||
|
// GaugeAlarm -- immaterial: nothing reads Searchlight at a raw offset past its
|
||||||
|
// own fields, and the shared BT_LOCAL_ALARM_SHIM stays 4 bytes because
|
||||||
|
// thermalsight.hpp shares that guard.)
|
||||||
|
//===========================================================================//
|
||||||
|
struct SearchlightLayoutCheck
|
||||||
|
{
|
||||||
|
static_assert(offsetof(Searchlight, lightState) == 0x1D8, "Searchlight lightState @0x1D8 (PowerWatcher ends 0x1D8)");
|
||||||
|
static_assert(offsetof(Searchlight, commandedOn) == 0x1DC, "Searchlight commandedOn @0x1DC (res+0x28)");
|
||||||
|
static_assert(offsetof(Searchlight, requestedOn) == 0x1E0, "Searchlight requestedOn @0x1E0");
|
||||||
|
static_assert(offsetof(Searchlight, stateAlarm) == 0x1E4, "Searchlight stateAlarm @0x1E4 (proves base ends 0x1D8)");
|
||||||
|
static_assert(sizeof(Searchlight) <= 0x238, "sizeof(Searchlight) must fit the factory Memory::Allocate(0x238)");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================//
|
||||||
|
// WAVE 4 factory bridge -- Searchlight (factory case 0xBD8, "LegSubsystem" label).
|
||||||
|
// The real class at 0xBD8 (ctor @004b84dc) is Searchlight; the factory built a
|
||||||
|
// LegSubsystem RECON_SUBSYS stub. No cache write; not a weapon (raw
|
||||||
|
// part_012.c:10166-10175 stores roster-only).
|
||||||
|
//===========================================================================//
|
||||||
|
Subsystem *CreateSearchlightSubsystem(Mech *owner, int id, void *seg)
|
||||||
|
{
|
||||||
|
return (Subsystem *) new (Memory::Allocate(0x238))
|
||||||
|
Searchlight(owner, id, (Searchlight::SubsystemResource *)seg, Searchlight::DefaultData);
|
||||||
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
class Searchlight:
|
class Searchlight:
|
||||||
public PowerWatcher
|
public PowerWatcher
|
||||||
{
|
{
|
||||||
|
friend struct SearchlightLayoutCheck; // compile-time offset locks (searchlight.cpp)
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Shared Data Support
|
// Shared Data Support
|
||||||
//
|
//
|
||||||
@@ -131,23 +132,31 @@
|
|||||||
// are backed by local members so this module compiles; see report.
|
// are backed by local members so this module compiles; see report.
|
||||||
//
|
//
|
||||||
public:
|
public:
|
||||||
Word GetSegmentFlags() const { return segmentFlags; }
|
// De-shimmed (WAVE 4 un-stub): the 7 CROSS-FAMILY backing fields
|
||||||
Logical HostShutDown() const { return hostShutDown; }
|
// (segmentFlags/hostShutDown/watchedVoltageLevel/heatStateLevel/
|
||||||
int WatchedVoltageLevel() const { return watchedVoltageLevel; } // PowerWatcher API
|
// controlsAllowLights/graphicsDirty/instanceFlags) are DELETED. They
|
||||||
int HeatStateLevel() const { return heatStateLevel; } // HeatSink API
|
// duplicated engine-base state AND -- via the old shadow `segmentFlags`
|
||||||
Logical ControlsAllowLights() const { return controlsAllowLights; } // Mech controls mapper
|
// (seeded 0) -- made the ctor gate ((0&0xC)==0 && (0&0x100)!=0) FALSE, so
|
||||||
void SetGraphicsDirty() { graphicsDirty = True; } // this[0x18] |= 1
|
// SearchlightSimulation was NEVER installed. Accessors now read the REAL
|
||||||
void SetInstanceFlag(int bits) { instanceFlags |= bits; } // this[10] |= bits
|
// inherited base members (mirrors torso.hpp/torso.cpp):
|
||||||
|
// HostShutDown <- MechSubsystem::simulationState == Destroyed (this+0x40)
|
||||||
|
// WatchedVoltageLevel <- PowerWatcher::watchdogAlarm level (this+0x198)
|
||||||
|
// HeatStateLevel <- HeatWatcher::heatAlarm level (this+0x140)
|
||||||
|
// SetGraphicsDirty -> engine Simulation::ForceUpdate() (this+0x18)
|
||||||
|
// SetInstanceFlag -> engine Simulation::simulationFlags |= bits (this+0x28)
|
||||||
|
// GetSegmentFlags() removed (the ctor gate now reads owner->simulationFlags).
|
||||||
|
// ControlsAllowLights(): no ported base member (the real read is the
|
||||||
|
// owner controls-mapper lightsEnabled via an unsafe raw owner-offset
|
||||||
|
// chain) -> return True for bring-up; the only consumer (ToggleLamp) is
|
||||||
|
// dormant. Faithful follow-up = a real Mech::ControlsAllowLights().
|
||||||
|
Logical HostShutDown() const { return simulationState == 1; /* Destroyed */ }
|
||||||
|
int WatchedVoltageLevel() const { return watchdogAlarm.GetLevel(); } // @0x198
|
||||||
|
int HeatStateLevel() const { return heatAlarm.GetLevel(); } // @0x140
|
||||||
|
Logical ControlsAllowLights() const { return True; } // bring-up (see above)
|
||||||
|
void SetGraphicsDirty() { ForceUpdate(); } // this[0x18] |= 1
|
||||||
|
void SetInstanceFlag(int bits) { simulationFlags |= bits; } // this[10] |= bits
|
||||||
// The "ToggleLamp" command arg is carried at message+0xC (decomp).
|
// The "ToggleLamp" command arg is carried at message+0xC (decomp).
|
||||||
static int MessageArg(Message &m) { return *(const int *)((const char *)&m + 0xC); }
|
static int MessageArg(Message &m) { return *(const int *)((const char *)&m + 0xC); }
|
||||||
protected:
|
|
||||||
Word segmentFlags; // Mech-supplied segment flags
|
|
||||||
Logical hostShutDown; // this[0x40] host-power shutdown flag
|
|
||||||
int watchedVoltageLevel; // @0x198 watched-voltage alarm level
|
|
||||||
int heatStateLevel; // @0x140 heat-state alarm level
|
|
||||||
Logical controlsAllowLights; // owner controls-mapper lightsEnabled
|
|
||||||
Logical graphicsDirty; // this[0x18] graphics-dirty bit
|
|
||||||
int instanceFlags; // this[10] instance flags
|
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Simulation Support
|
// Simulation Support
|
||||||
|
|||||||
@@ -136,12 +136,8 @@ Sensor::Sensor(
|
|||||||
Check(owner);
|
Check(owner);
|
||||||
Check_Pointer(subsystem_resource);
|
Check_Pointer(subsystem_resource);
|
||||||
|
|
||||||
segmentFlags = 0;
|
// (WAVE 4 de-shim) the 5 cross-family shim fields are gone; the accessors
|
||||||
hasActivePerformance = False;
|
// now read the inherited base state directly. Only the 3 own fields init here.
|
||||||
heatStateLevel = NormalHeat;
|
|
||||||
heatModelOff = False;
|
|
||||||
electricalState = Ready;
|
|
||||||
|
|
||||||
radarPercent = RadarBaseline; // @0x31C = 1.0f
|
radarPercent = RadarBaseline; // @0x31C = 1.0f
|
||||||
selfTest = False; // @0x320 = 0
|
selfTest = False; // @0x320 = 0
|
||||||
badVoltage = False; // @0x324 = 0
|
badVoltage = False; // @0x324 = 0
|
||||||
@@ -363,3 +359,37 @@ int
|
|||||||
Check_Fpu();
|
Check_Fpu();
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================//
|
||||||
|
// WAVE 4 -- compile-time OVERFLOW lock (the property that matters for the
|
||||||
|
// placement-new into the 0x328 factory alloc).
|
||||||
|
// NOTE: the reconstructed HEAT-LEAF base chain (PoweredSubsystem : HeatSink :
|
||||||
|
// HeatableSubsystem) is NOT byte-exact to the binary (unlike the re-based
|
||||||
|
// WATCHER branch Torso sits on), so radarPercent does NOT land at the binary's
|
||||||
|
// 0x31C. That is fine here: SensorSimulation reads every field through NAMED
|
||||||
|
// members (compiled-consistent), and NOTHING reads Sensor at a raw binary
|
||||||
|
// offset yet. The one raw-offset consumer is the attribute table
|
||||||
|
// (RadarPercent -> 0x31C), used only by the (un-drawn) cockpit gauge -- so the
|
||||||
|
// exact-offset re-base of the heat-leaf branch is a Phase-4 HUD follow-up.
|
||||||
|
// Until then, no-overflow (sizeof <= alloc) is the correct, sufficient lock.
|
||||||
|
//===========================================================================//
|
||||||
|
struct SensorLayoutCheck
|
||||||
|
{
|
||||||
|
static_assert(sizeof(Sensor) <= 0x328, "sizeof(Sensor) must fit the factory Memory::Allocate(0x328)");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================//
|
||||||
|
// WAVE 4 factory bridge -- Sensor (factory case 0xBC3, "Myomers" mislabel).
|
||||||
|
// The real class at 0xBC3 (ctor @004b1d18) is Sensor (string pool @0050fae0 +
|
||||||
|
// surviving SENSOR.HPP); MyomersClassID is a factory-enum mislabel (the real
|
||||||
|
// Myomers is 0xBC6/@4b8fec). Object is 0x328 == the factory alloc (zero
|
||||||
|
// headroom; locked by SensorLayoutCheck above). Sensor's ctor is 3-arg
|
||||||
|
// (DefaultData applied internally) -- no shared_data argument.
|
||||||
|
//===========================================================================//
|
||||||
|
Subsystem *CreateSensorSubsystem(Mech *owner, int id, void *seg)
|
||||||
|
{
|
||||||
|
return (Subsystem *) new (Memory::Allocate(0x328))
|
||||||
|
Sensor(owner, id, (Sensor::SubsystemResource *)seg);
|
||||||
|
}
|
||||||
|
|||||||
@@ -111,24 +111,27 @@
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
public:
|
public:
|
||||||
Word
|
// De-shimmed (WAVE 4 un-stub): the 5 CROSS-FAMILY backing fields
|
||||||
GetSegmentFlags() const { return segmentFlags; }
|
// (segmentFlags/hasActivePerformance/heatStateLevel/heatModelOff/
|
||||||
|
// electricalState) are DELETED -- they over-sized the object past its
|
||||||
|
// zero-headroom 0x328 factory alloc AND duplicated engine-base state.
|
||||||
|
// The accessors now read the REAL inherited base members:
|
||||||
|
// HeatStateLevel <- HeatSink::heatAlarm level (this+0x170)
|
||||||
|
// ElectricalState <- PoweredSubsystem::electricalStateAlarm level (this+0x278)
|
||||||
|
// HeatModelOff <- MechSubsystem::simulationState == Destroyed (this+0x40)
|
||||||
|
// HeatMasterEnergy <- inherited HeatSink::heatEnergy (this+0x158)
|
||||||
|
// SetHasPerformanceFlag -> engine Simulation::simulationFlags |= 8 (this+0x28)
|
||||||
|
// GetSegmentFlags() removed (unused; the ctor gate reads owner->simulationFlags).
|
||||||
HeatSink::HeatState
|
HeatSink::HeatState
|
||||||
HeatStateLevel() const { return heatStateLevel; }
|
HeatStateLevel() const { return (HeatSink::HeatState)heatAlarm.GetLevel(); }
|
||||||
Logical
|
Logical
|
||||||
HeatModelOff() const { return heatModelOff; }
|
HeatModelOff() const { return simulationState == 1; /* MechSubsystem::DestroyedState */ }
|
||||||
Scalar
|
Scalar
|
||||||
HeatMasterEnergy() const { return heatEnergy; } // own inherited sink
|
HeatMasterEnergy() const { return heatEnergy; } // inherited HeatSink sink
|
||||||
PoweredSubsystem::ElectricalState
|
PoweredSubsystem::ElectricalState
|
||||||
ElectricalStateLevel() const { return electricalState; }
|
ElectricalStateLevel() const { return (PoweredSubsystem::ElectricalState)electricalStateAlarm.GetLevel(); }
|
||||||
void
|
void
|
||||||
SetHasPerformanceFlag() { hasActivePerformance = True; }
|
SetHasPerformanceFlag() { simulationFlags |= 8; } // engine Simulation flag bit 3
|
||||||
protected:
|
|
||||||
Word segmentFlags; // @this[0x28]/segment flags (Mech-supplied)
|
|
||||||
Logical hasActivePerformance;
|
|
||||||
HeatSink::HeatState heatStateLevel; // heatAlarm level (cross-family base)
|
|
||||||
Logical heatModelOff; // thermal model disabled flag
|
|
||||||
PoweredSubsystem::ElectricalState electricalState; // electricalStateAlarm level
|
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Attribute Support
|
// Attribute Support
|
||||||
|
|||||||
@@ -83,17 +83,18 @@ ThermalSight::ThermalSight(
|
|||||||
Check(owner);
|
Check(owner);
|
||||||
Check_Pointer(subsystem_resource);
|
Check_Pointer(subsystem_resource);
|
||||||
|
|
||||||
segmentFlags = 0;
|
// (WAVE 4 de-shim) the 4 cross-family shim fields are gone; the accessors
|
||||||
hostShutDown = False;
|
// read the inherited base state directly. Only the own fields init here.
|
||||||
watchedVoltageLevel = 4; // Ready
|
|
||||||
heatStateLevel = 0; // NormalHeat
|
|
||||||
|
|
||||||
thermalActive = 0; // @0x1D8
|
thermalActive = 0; // @0x1D8
|
||||||
requestedOn = 0; // @0x1DC
|
requestedOn = 0; // @0x1DC
|
||||||
stateAlarm.Initialize(2); // FUN_0041b9ec(this+0x1E0, 2)
|
stateAlarm.Initialize(2); // FUN_0041b9ec(this+0x1E0, 2)
|
||||||
|
|
||||||
if (((GetSegmentFlags() & 0xC) == 0) &&
|
// GATE FIX (the functional bug the de-shim unblocks): the binary reads OWNER
|
||||||
((GetSegmentFlags() & 0x100) != 0))
|
// simulationFlags (param_2+0x28, raw part_013.c:6140), NOT the old shadow
|
||||||
|
// `segmentFlags` (seeded 0 -> gate always FALSE -> Performance never
|
||||||
|
// installed). No else branch (matches the binary; unlike Searchlight/Torso).
|
||||||
|
if (((owner->simulationFlags & 0xC) == 0) &&
|
||||||
|
((owner->simulationFlags & 0x100) != 0))
|
||||||
{
|
{
|
||||||
SetPerformance(&ThermalSight::ThermalSightSimulation); // this[7..9] <- PTR_FUN_00511290
|
SetPerformance(&ThermalSight::ThermalSightSimulation); // this[7..9] <- PTR_FUN_00511290
|
||||||
}
|
}
|
||||||
@@ -166,9 +167,9 @@ void
|
|||||||
|
|
||||||
stateAlarm.SetLevel(thermalActive); // FUN_0041bbd8(this+0x1E0, thermalActive)
|
stateAlarm.SetLevel(thermalActive); // FUN_0041bbd8(this+0x1E0, thermalActive)
|
||||||
|
|
||||||
if ((previous != thermalActive) && (LocalViewport()->viewingThisMech != 0))
|
if ((previous != thermalActive) && IsLocallyViewed()) // was LocalViewport()->viewingThisMech
|
||||||
{
|
{
|
||||||
ToggleGlobalThermalVision(); // FUN_0045fe44
|
ToggleGlobalThermalVision(); // FUN_0045fe44 -- bring-up no-op (pvision unported)
|
||||||
}
|
}
|
||||||
|
|
||||||
Check_Fpu();
|
Check_Fpu();
|
||||||
@@ -207,3 +208,33 @@ int
|
|||||||
|
|
||||||
return True;
|
return True;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================//
|
||||||
|
// WAVE 4 -- compile-time layout locks. PowerWatcher is Torso-proven at 0x1D8,
|
||||||
|
// so the own fields land at their EXACT binary offsets; sizeof is bounded to
|
||||||
|
// the factory alloc. (BtAlarm is a 4-byte stand-in for the binary's 0x54
|
||||||
|
// GaugeAlarm -- immaterial: nothing reads ThermalSight at a raw offset past
|
||||||
|
// its own fields, and the shared BT_LOCAL_ALARM_SHIM stays 4 bytes because
|
||||||
|
// searchlight.hpp shares that guard.)
|
||||||
|
//===========================================================================//
|
||||||
|
struct ThermalSightLayoutCheck
|
||||||
|
{
|
||||||
|
static_assert(offsetof(ThermalSight, thermalActive) == 0x1D8, "ThermalSight thermalActive @0x1D8 (PowerWatcher ends 0x1D8)");
|
||||||
|
static_assert(offsetof(ThermalSight, requestedOn) == 0x1DC, "ThermalSight requestedOn @0x1DC");
|
||||||
|
static_assert(offsetof(ThermalSight, stateAlarm) == 0x1E0, "ThermalSight stateAlarm @0x1E0 (proves base ends 0x1D8)");
|
||||||
|
static_assert(sizeof(ThermalSight) <= 0x234, "sizeof(ThermalSight) must fit the factory Memory::Allocate(0x234)");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================//
|
||||||
|
// WAVE 4 factory bridge -- ThermalSight (factory case 0xBDE, "MechDisplay" stub).
|
||||||
|
// The real class at 0xBDE (ctor @004b8718) is ThermalSight; the factory built
|
||||||
|
// a MechDisplay RECON_SUBSYS stub. No cache write; not a weapon (raw
|
||||||
|
// part_012.c:10186-10195 stores roster-only).
|
||||||
|
//===========================================================================//
|
||||||
|
Subsystem *CreateThermalSightSubsystem(Mech *owner, int id, void *seg)
|
||||||
|
{
|
||||||
|
return (Subsystem *) new (Memory::Allocate(0x234))
|
||||||
|
ThermalSight(owner, id, (ThermalSight::SubsystemResource *)seg, ThermalSight::DefaultData);
|
||||||
|
}
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
class ThermalSight:
|
class ThermalSight:
|
||||||
public PowerWatcher
|
public PowerWatcher
|
||||||
{
|
{
|
||||||
|
friend struct ThermalSightLayoutCheck; // compile-time offset locks (thermalsight.cpp)
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Shared Data Support
|
// Shared Data Support
|
||||||
//
|
//
|
||||||
@@ -128,20 +129,28 @@
|
|||||||
// module compiles; see report.
|
// module compiles; see report.
|
||||||
//
|
//
|
||||||
public:
|
public:
|
||||||
struct BtViewport { int viewingThisMech; BtViewport(): viewingThisMech(0) {} };
|
// De-shimmed (WAVE 4 un-stub): the 5 CROSS-FAMILY backing fields
|
||||||
|
// (segmentFlags/hostShutDown/watchedVoltageLevel/heatStateLevel/
|
||||||
Word GetSegmentFlags() const { return segmentFlags; }
|
// localViewport) + struct BtViewport are DELETED. They duplicated
|
||||||
Logical HostShutDown() const { return hostShutDown; }
|
// engine-base state AND -- via the old shadow `segmentFlags` (seeded 0) --
|
||||||
int WatchedVoltageLevel() const { return watchedVoltageLevel; } // PowerWatcher API
|
// made the ctor gate ((0&0xC)==0 && (0&0x100)!=0) FALSE, so
|
||||||
int HeatStateLevel() const { return heatStateLevel; } // HeatSink API
|
// ThermalSightSimulation was NEVER installed. Accessors now read the REAL
|
||||||
BtViewport* LocalViewport() { return &localViewport; } // PTR_DAT_004fd4b8
|
// inherited base members (mirrors torso.hpp / searchlight.hpp):
|
||||||
void ToggleGlobalThermalVision() {} // FUN_0045fe44
|
// HostShutDown <- MechSubsystem::simulationState == Destroyed (this+0x40)
|
||||||
protected:
|
// WatchedVoltageLevel <- PowerWatcher::watchdogAlarm level (this+0x198)
|
||||||
Word segmentFlags; // Mech-supplied segment flags
|
// HeatStateLevel <- HeatWatcher::heatAlarm level (this+0x140)
|
||||||
Logical hostShutDown; // this[0x40] host-power shutdown flag
|
// The cockpit IR view-mode RENDER hooks have no ported target and stay
|
||||||
int watchedVoltageLevel; // @0x198 watched-voltage alarm level
|
// clearly-marked no-ops for bring-up (thermalActive/stateAlarm are still
|
||||||
int heatStateLevel; // @0x140 heat-state alarm level
|
// computed faithfully):
|
||||||
BtViewport localViewport; // active L4 viewport (+0x48 viewingThisMech)
|
// IsLocallyViewed() -- was localViewport->viewingThisMech (L4 viewport
|
||||||
|
// global PTR_DAT_004fd4b8+0x48); no ported accessor.
|
||||||
|
// ToggleGlobalThermalVision() -- was FUN_0045fe44 (global "pvision"
|
||||||
|
// palette flip); the D3D9 renderer has no pvision.
|
||||||
|
Logical HostShutDown() const { return simulationState == 1; /* Destroyed */ }
|
||||||
|
int WatchedVoltageLevel() const { return watchdogAlarm.GetLevel(); } // @0x198
|
||||||
|
int HeatStateLevel() const { return heatAlarm.GetLevel(); } // @0x140
|
||||||
|
Logical IsLocallyViewed() const { return False; } // bring-up no-op (see above)
|
||||||
|
void ToggleGlobalThermalVision() {} // bring-up no-op (pvision unported)
|
||||||
|
|
||||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Simulation Support
|
// Simulation Support
|
||||||
|
|||||||
Reference in New Issue
Block a user