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:
arcattack
2026-07-05 23:01:31 -05:00
co-authored by Claude Opus 4.8
parent 5aa791b245
commit 8b36440d05
14 changed files with 421 additions and 141 deletions
+45 -5
View File
@@ -126,7 +126,8 @@ AmmoBin::AmmoBin(
{
// 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)
InitCookOffHeatSource(&cookOffHeatSource); // @0x1F0 FUN_0041db7c(this+0x7c)
reserved = 0; // @0x228
@@ -171,7 +172,7 @@ AmmoBin::~AmmoBin()
//
int AmmoBin::FeedAmmo()
{
if (statusState == 1) // this+0x40 == 1 (destroyed)
if (simulationState == 1) // this+0x40 == 1 (destroyed)
return 0;
switch (ammoAlarm.Level()) // this+0x1A8
@@ -243,7 +244,7 @@ void AmmoBin::AmmoBinSimulation(Scalar time_slice)
{
WatchSimulation(time_slice); // FUN_004aeac4(this) -- drive this+0x140
if (statusState == 1) // this+0x40 (destroyed)
if (simulationState == 1) // this+0x40 (destroyed)
ammoAlarm.SetLevel(Empty);
// feed timer (only while a round is in transit)
@@ -313,9 +314,9 @@ Logical AmmoBin::HandleMessage(int message)
//
void AmmoBin::ReadUpdateRecord(UpdateRecord *update)
{
int previousState = statusState; // this+0x40
int previousState = simulationState; // this+0x40 (inherited MechSubsystem)
HeatWatcher::ReadUpdateRecord(update); // FUN_004aea84
if (statusState == 1 && previousState != 1)
if (simulationState == 1 && previousState != 1)
{
ammoCount = 0; // this+0x180
ammoAlarm.SetLevel(Empty); // 2
@@ -430,3 +431,42 @@ Logical AmmoBin::TestClass(Mech &)
{
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);
}