Fix 1 (Gitea #12): disasm-exact ProjectileWeaponSimulation @004bbd04 + the dirty-bit sweep

Replace the RivetGun-modeled ProjectileWeaponSimulation body with the fully
recovered binary machine (capstone disasm, every branch address-cited):

- gate 1 @4bbd36: simulationState@0x40==1 (destroyed) || heatAlarm==FailureHeat
  || owner disabled -> recoil=rechargeRate + alarm 7 (was: simulationFlags==1)
- gate 2 @4bbd71: bin alarm 2/3 or bin destroyed -> alarm 7 re-pinned per frame
- Loaded @4bbec2: DENIED shots (viewFireEnable off / no owner target) blip
  SetLevel(4);SetLevel(2) and STAY LOADED, no ammo pull (the old FireWeapon
  early-returns under an unconditionally-cycling caller faked a full firing
  pip cycle -- the "missiles cycle but never launch" incident mechanic);
  the ammo pull is bin->FeedAmmo @4bbee6 in the CALLER; both updateModel|=1
  marks (@4bbf05/@4bbf4c = ForceUpdate) bracket FireWeapon; recoil set @4bbf51
- Loading @4bbdd2: recoil bleeds ONLY here at electrical Ready and CLAMPS at 0
  (it ran to -114 before); bin Loaded -> weapon Loaded
- 7 @4bbe4d: binary-faithful roach motel (re-asserted unconditionally;
  recovery = ResetToInitialState only)
- slot 17 @004b9c9c reconstructed as MechWeapon::ComputeOutputVoltage
  (rechargeLevel=(rechargeRate-recoil)/rechargeRate, Emitter overrides with
  @4ba738) and called from Loading/7 -- the launcher recharge dial ANIMATES
  (the old "authentically static" claim was wrong)

FireWeapon bodies stripped to heat+spawn only per @004bcc60 (no view gate, no
ConsumeRound, no recoil); ConsumeRound retired (not a binary method).

Gotcha #20 sweep (all sites disasm-verified): simulationFlags|=0x1 == engine
DelayWatchersFlag (audio watchers muted forever) removed everywhere --
projweap/mislanch fire marks, ProjectileWeapon::ResetToInitialState (@4bbb47
= updateModel|=1); Emitter::SetDirty retired and split per binary site into
ForceUpdate() (@4bafaa/@4ba55d) vs ExecuteOnUpdate() (@4ba99a/@4ba943);
Emitter fault gate GetFlags()==1 -> simulationState==1 (@4baab9).

AmmoBin::GetAmmoState() accessor added (named-member read of ammoAlarm@0x1A8).
BT_LOOK_TEST=<frame> scripted verify added (mech4.cpp): holds the rear-view
button 300f on/300f off to drive viewFireEnable headlessly.

Verified solo ARENA1 (bhk1, autofire): 54-60 [projectile] PUSH + impact smoke;
look-back denial window = trigger pulses, state stays 2, zero launches,
resumes on release; enemy (disabled) mech launchers pinned 7 + full recoil;
STREAK-6 recharge dial sweeps across BT_SHOT frames; lasers fire throughout;
zero gauge faults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-19 23:26:11 -05:00
co-authored by Claude Fable 5
parent 2215932016
commit 2edde29671
11 changed files with 292 additions and 165 deletions
+12 -21
View File
@@ -267,11 +267,13 @@ void MissileLauncher::FireWeapon()
{
Check(this);
// (task #68) the view gate: a weapon disabled in the CURRENT look view
// (rear rack in the forward view, anything in a side view) does not fire
// -- the same +0x3E0 gate the emitter's Loaded->Firing transition reads.
if (!viewFireEnable)
return;
// Gitea #12: this body is heat + spawn ONLY, per the recovered @004bcc60
// (part_013.c:8741+): NO viewFireEnable gate, NO ammo pull, NO recoil set.
// All of that lives in the CALLER -- the recovered ProjectileWeaponSimulation
// Loaded case (@4bbec2 view/target denial blip, @4bbee6 bin->FeedAmmo,
// @4bbf51 recoil). The old early-returns here, under a caller that cycled
// the alarm unconditionally, faked a full firing pip cycle on every denied
// shot -- the "missiles cycle but never launch" incident mechanic.
// THE FIRING HEAT (task #9): heatCostToFire RAW into the launcher's own
// pendingHeat (pre-scaled 1e7-unit resources: SRM6 = 5e7, LRM15 = 6.5e7)
@@ -281,14 +283,6 @@ void MissileLauncher::FireWeapon()
pendingHeat += heatCostToFire; // HeatSink @0x1C8, raw
}
// A salvo draws one ammo unit from the linked bin; a dry / not-ready bin
// latches NoAmmo (ConsumeRound) and aborts the launch.
if (!ConsumeRound()) // FUN_004bd4f4 (AmmoBin::FeedAmmo)
return;
// Begin the per-shot recharge cooldown (recoil bleeds to 0 before ReadyToFire).
recoil = rechargeRate;
// Resolve the muzzle / lead geometry (seeds each flying missile).
Point3D muzzle;
GetMuzzlePoint(muzzle); // @004b9948
@@ -328,19 +322,16 @@ void MissileLauncher::FireWeapon()
// Salvo replication (missile-visibility wave): bump the fire counter + stamp
// the aim point; the extended update record carries both to peer nodes.
// The CALLER's Loaded case makes the two `updateModel |= 1` marks
// (@4bbf05/@4bbf4c == ForceUpdate) that serialize the record.
// Gitea #12 / gotcha #20: the old `simulationFlags |= 0x1` here set the
// engine DelayWatchersFlag -- permanently muting this launcher's audio
// watchers after the first salvo; removed.
{
BTSalvoState &s = BTSalvoOf(this);
++s.fired;
s.target = targetPos;
}
simulationFlags |= 0x1; // replication-dirty
// ENQUEUE the record (visibility fix): the dirty flag alone never
// serializes -- ForceUpdate sets the updateModel bit that makes the next
// PerformAndWatch write THIS subsystem's record into the mech's update
// stream (the exact mechanism the Emitter's beam replication uses,
// emitter.cpp:512). Without it the peer never saw a salvo record at all.
ForceUpdate();
Check_Fpu();
}