WAVE 7 Phase A: wire the projectile/missile weapon SUBSYSTEMS byte-exact (un-mislabeled)

An 8-agent decomp-mapping workflow mapped every weapon-family ctor + fire/spawn path
(scratchpad/wave7_maps.txt).  KEY IDENTITY CORRECTION via VDATA.h enum (base 3000=0xBB8):
the factory ctor-address comments were right but the built CLASS names were stubs/base --
    0xBCD == ProjectileWeapon  (was building the base MechWeapon stub)
    0xBCE == GaussRifle         (NOT MissileLauncher -- was a MissileLauncher stub)
    0xBD0 == MissileLauncher    (NOT BallisticWeapon -- was a BallisticWeapon stub)
Un-mislabeled + wired via Create<Class>Subsystem bridges (mirroring CreateEmitterSubsystem),
each static_assert-locked byte-exact on the now-locked MechWeapon(0x3F0)/Emitter(0x478) bases:

  * ProjectileWeapon (0xBCD, @4bc3fc : MechWeapon, sizeof 0x448)
      FIX: AmmoBinConnection was an empty 1-byte struct -> retyped to the binary's 0xC
           SharedData::Connection (was making the object 8 bytes short, sliding
           MissileLauncher's missileCount off 0x448).
  * MissileLauncher (0xBD0, @4bcff0 : ProjectileWeapon, sizeof 0x44C)
      FIX: deleted muzzleVelocity (ALIAS of inherited ProjectileWeapon launchVelocity@0x410)
           and salvoCount (SHADOW of inherited MechWeapon damageData.burstCount@0x3d4),
           keeping the single own field missileCount@0x448.
  * GaussRifle (0xBCE, @4bdcb4 : Emitter, sizeof 0x484 = Emitter + Vector3D muzzleVelocity)
      DEDICATED bridge (not CreateEmitter): its FireWeapon is a no-op (mov [eax+0x414],0) --
      a non-functional weapon in this 1995 build.  Also fixed GAUSS.CPP's discharge write
      rechargeLevel -> currentLevel@0x414 (the binary writes this+0x414).

Phase A safe-stubs FireWeapon (consume round + recoil, NO spawn): the reconstructed
Projectile (@4be1bc, 0x340) + Missile (@4bf5b4, 0x368) ENTITY classes are NOT spawn-ready
(Entity-base phantom members overflow their New() alloc -> heap corruption; New() takes
(int)this instead of the 0xD4 descriptor; Entity base size unconfirmed 2007-vs-1995, the
Mech 0x638-vs-0x854 problem).  The workflow's adversarial verify flagged the live spawn as a
heap-overflow hazard and recommended exactly this phasing.  Phase B = the entity byte-exactness
+ descriptor build + New(MakeMessage*) so a fired shot becomes a flying entity that damages.

Factory now 18 of 20 cases wired to real ticking classes (remaining: SubsystemMessageManager
0xBD3 [WAVE 8], Gyroscope 0xBC4 [deferred]).  Verified: Mad Cat (LRM/ballistic) + BLH construct
the real weapon classes + tick authentic charge/ammo/heat/recoil, DESTROYED-in-8, 0 crashes,
heapcheck-clean through construction.  Energy weapons already damage via the mech4 beam path,
so combat is unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-07 13:35:43 -05:00
co-authored by Claude Opus 4.8
parent 9d82be46a1
commit 2bcca26cea
8 changed files with 167 additions and 49 deletions
+27 -11
View File
@@ -107,14 +107,19 @@ class NotationFile;
static SharedData DefaultData;
// Embedded ref-counted connection to the AmmoBin subsystem (resource
// AmmoBin index). The shipped layout is a SharedData::Connection; the
// reconstruction models it with this thin proxy (Construct/Connect/
// Resolve/Destruct) so the recovered call sites compile unchanged.
// AmmoBin index). The shipped layout is a 0xC-byte SharedData::Connection
// (ctor FUN_004bcbb0->FUN_004179d4; resolver FUN_00417ab4 two-level-derefs +8),
// NOT an empty proxy -- modeling it as 1 byte made ProjectileWeapon 8 bytes
// short (0x440 vs the binary 0x448) and slid MissileLauncher's missileCount off
// 0x448. Named access (Construct/Connect/Resolve/Destruct) is unchanged + inert
// in bring-up (no plug resolves). Same 0xC pattern as SubsystemConnection.
struct AmmoBinConnection {
void Construct() {}
template<class T> void Connect(T) {}
void Destruct() {}
void *Resolve() { return 0; }
void *linked; // +0x00 (SharedData::Connection; inert here)
int _reserved[2]; // pad to the binary 0xC connection
void Construct() { linked = 0; _reserved[0] = _reserved[1] = 0; }
template<class T> void Connect(T s) { linked = (void *)s; }
void Destruct() { linked = 0; }
void *Resolve() { return linked; }
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -309,10 +314,21 @@ class NotationFile;
// --- ammunition ---
int tracerInterval; // @0x438 resource TracerInterval (+0x1BC)
AmmoBinConnection
ammoBinLink; // @0x43C embedded connection to the AmmoBin subsystem
// (12 bytes; ctor @004bcbb0, dtor @004bcbcf,
// link vtable @00512424; resolved via the
// resource AmmoBin index at +0x1C0)
ammoBinLink; // @0x43C embedded 0xC connection to the AmmoBin subsystem
// (ctor @004bcbb0, dtor @004bcbcf, link vtable
// @00512424; resolved via the resource AmmoBin
// index at +0x1C0) -- LAST field, object ends 0x448
friend struct ProjectileWeaponLayoutCheck;
}; // sizeof == 0x448
struct ProjectileWeaponLayoutCheck {
static_assert(offsetof(ProjectileWeapon, totalTimeToEject) == 0x3F0, "ProjectileWeapon::totalTimeToEject @0x3F0 (MechWeapon must end 0x3F0)");
static_assert(offsetof(ProjectileWeapon, launchVelocity) == 0x410, "ProjectileWeapon::launchVelocity @0x410");
static_assert(offsetof(ProjectileWeapon, leadPosition) == 0x420, "ProjectileWeapon::leadPosition @0x420");
static_assert(offsetof(ProjectileWeapon, tracerInterval) == 0x438, "ProjectileWeapon::tracerInterval @0x438");
static_assert(offsetof(ProjectileWeapon, ammoBinLink) == 0x43C, "ProjectileWeapon::ammoBinLink @0x43C");
static_assert(sizeof(ProjectileWeapon) == 0x448, "sizeof(ProjectileWeapon) 0x448 (== factory alloc)");
};
#endif