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:
co-authored by
Claude Fable 5
parent
2215932016
commit
2edde29671
+168
-122
@@ -24,12 +24,14 @@
|
||||
// ResetToInitialState @004bbaf8, HandleMessage @004bcabc,
|
||||
// PrintState @004bc6c8, CreateStreamedSubsystem @004bc7cc,
|
||||
// UpdateEject @004bbb50, DrawFiringCharge @004bbc78,
|
||||
// CheckForJam @004bbfcc, ComputeTimeOfFlight @004bc06c.
|
||||
// CheckForJam @004bbfcc, ComputeTimeOfFlight @004bc06c,
|
||||
// ProjectileWeaponSimulation @004bbd04 (Performance -- FULLY RECOVERED
|
||||
// by capstone disasm, Gitea #12 2026-07-19; see context/decomp-reference.md
|
||||
// s5 -- the old RivetGun-modeled body is retired).
|
||||
// best-effort (vtable slot proven, function prologue present, BODY NOT
|
||||
// recovered by the decompiler -- bodies below are reconstructed from context
|
||||
// and clearly marked):
|
||||
// FireWeapon @004bc104 (slot 18), ProjectileWeaponSimulation @004bbd04
|
||||
// (Performance), GetStatusFlags @004bbf88 (slot 12),
|
||||
// FireWeapon @004bc104 (slot 18), GetStatusFlags @004bbf88 (slot 12),
|
||||
// UpdateWeaponState @004bbc20 (slot 16).
|
||||
// excluded (belong to sibling/derived classes, NOT ProjectileWeapon):
|
||||
// Emitter family @004bb120/@004bb888/@004ba4d0-@004bb478 (energy weapons),
|
||||
@@ -228,16 +230,18 @@ Logical
|
||||
return True;
|
||||
}
|
||||
|
||||
//
|
||||
// ConsumeRound -- pull one round from the linked AmmoBin (FUN_004bd4f4
|
||||
// AmmoBin::FeedAmmo). Returns True when a round was dispensed; otherwise raises
|
||||
// the NoAmmo alarm and returns False. Shared by both FireWeapon spawn paths.
|
||||
//
|
||||
// Panel bridge (AFC100-counter fix, 2026-07-12): resolve the weapon's ammo
|
||||
// bin through the TYPED connection -- the gauge cluster's hand-rolled raw walk
|
||||
// (*(plug+8) twice at subsystem+0x43c) assumed the 1995 connection-object
|
||||
// internals and returned NULL/garbage on our layout, so the panel's ammo
|
||||
// digits never bound (fired shells consumed rounds invisibly).
|
||||
//
|
||||
// (Gitea #12: the old `ConsumeRound()` helper is RETIRED -- the binary has no
|
||||
// such method. The ammo pull is `bin->FeedAmmo()` @004bd4f4 called by the
|
||||
// SIMULATION's Loaded case @4bbee6, and a failed pull leaves the weapon Loaded
|
||||
// SILENTLY -- the helper's SetLevel(NoAmmo) on failure was port invention; the
|
||||
// authentic NoAmmo pin is gate 2 of the state machine, from the BIN's alarm.)
|
||||
void *
|
||||
BTWeaponAmmoBin(void *weapon)
|
||||
{
|
||||
@@ -245,18 +249,6 @@ void *
|
||||
? (void *)((ProjectileWeapon *)weapon)->ammoBinLink.Resolve() : 0;
|
||||
}
|
||||
|
||||
Logical
|
||||
ProjectileWeapon::ConsumeRound()
|
||||
{
|
||||
AmmoBin *bin = (AmmoBin*)ammoBinLink.Resolve(); // FUN_00417ab4(this+0x43c)
|
||||
if (bin != 0 && bin->FeedAmmo() != 0) // FUN_004bd4f4 -- dispensed?
|
||||
{
|
||||
return True;
|
||||
}
|
||||
weaponAlarm.SetLevel(NoAmmoState); // FUN_0041bbd8(this+0x350, 7)
|
||||
return False;
|
||||
}
|
||||
|
||||
|
||||
//#############################################################################
|
||||
// Construction / Destruction
|
||||
@@ -403,7 +395,11 @@ void
|
||||
percentOfEject = 0.0f; // this[0xfe] = 0
|
||||
ejectState = -1; // this[0x100] = -1
|
||||
|
||||
simulationFlags |= 0x1; // this[6] |= 1 (state dirty)
|
||||
// @4bbb47: `or word ptr [this+0x18], 1` == updateModel |= 1 == the engine
|
||||
// ForceUpdate() (replication mark). The old transcription onto
|
||||
// `simulationFlags |= 0x1` set the engine DelayWatchersFlag instead --
|
||||
// permanently muting this subsystem's audio watchers (gotcha #20, Gitea #12).
|
||||
ForceUpdate();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -589,102 +585,159 @@ Scalar
|
||||
//
|
||||
|
||||
//
|
||||
// @004bbd04 -- Performance: per-frame ProjectileWeapon update. Body NOT
|
||||
// recovered by the decompiler (prologue confirmed at 0x4bbd04). From context it
|
||||
// sequences DrawFiringCharge / UpdateEject / CheckForJam / ComputeTimeOfFlight
|
||||
// and dispatches FireWeapon (vtable slot 18) when the trigger + charge + ammo
|
||||
// conditions are met.
|
||||
// @004bbd04 -- Performance: per-frame ProjectileWeapon update. FULLY RECOVERED
|
||||
// (capstone disasm via tools/disas2.py, Gitea #12, 2026-07-19 -- every branch
|
||||
// below cites its binary address). The old RivetGun-modeled body is retired;
|
||||
// its divergences were the #12 incident mechanics: denied shots faked a full
|
||||
// Loaded->Firing->Loading pip cycle (binary: a 4-blip that STAYS Loaded, no
|
||||
// ammo pull), the fault gate read simulationFlags (binary: simulationState),
|
||||
// recoil bled every frame unconditionally and ran to -114 (binary: Loading-only
|
||||
// + clamp at 0), and slot 17 (the recharge-dial writer) was never called.
|
||||
//
|
||||
void
|
||||
ProjectileWeapon::ProjectileWeaponSimulation(Scalar time_slice)
|
||||
{
|
||||
Check(this);
|
||||
|
||||
// 0. The PoweredSubsystem step FIRST -- disasm @004bbd12 (task #10):
|
||||
// `call 0x4b0bd0` opens the body, exactly like the emitter sibling. It
|
||||
// runs HeatSink::HeatSinkSimulation (absorb pendingHeat -> temperature ->
|
||||
// conduct to the authored condenser) and the electrical state machine.
|
||||
// Without it the launcher's firing heat (task #9) accumulated in
|
||||
// pendingHeat forever and never reached the mech's thermal network.
|
||||
PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // @004b0bd0
|
||||
// @4bbd12: the PoweredSubsystem step FIRST (task #10) -- HeatSink absorb ->
|
||||
// temperature -> conduction, and the electrical state machine.
|
||||
PoweredSubsystem::PoweredSubsystemSimulation(time_slice); // call 0x4b0bd0
|
||||
|
||||
// 0b. The FAULT gate -- disasm @004bbd36-004bbd6e (task #10): flags==1, the
|
||||
// weapon's own heatAlarm at FailureHeat (this+0x184 == 2 -- THE consumer
|
||||
// of the authored FailureTemperature for the ballistic family), or the
|
||||
// owning mech destroyed -> pin recoil to the full rechargeRate and latch
|
||||
// alarm state 7 (unavailable). No return: the frame continues.
|
||||
if (simulationFlags == 1
|
||||
|| heatAlarm.GetLevel() == HeatSink::FailureHeat // this+0x184 == 2
|
||||
// @4bbd1b: the trigger edge is sampled ONCE, before the gates ([ebp-4]).
|
||||
// AUTHENTIC TRIGGER (task #5): fireImpulse = the TriggerState attribute the
|
||||
// streamed controls map binds to the pod fire buttons.
|
||||
Logical trigger = CheckFireEdge(); // call 0x4b9608
|
||||
|
||||
// @4bbd28: advance any in-progress magazine eject.
|
||||
UpdateEject(time_slice); // call 0x4bbb50
|
||||
|
||||
// GATE 1 @4bbd36-4bbd6e: weapon DESTROYED (`simulationState@0x40 == 1` --
|
||||
// NOT simulationFlags, the historic mis-transcription of gotcha #20), own
|
||||
// heatAlarm at FailureHeat (this+0x184 == 2 -- THE consumer of the authored
|
||||
// FailureTemperature for the ballistic family), or the owning mech disabled
|
||||
// (FUN_0049fb54) -> pin recoil at the full rechargeRate + latch alarm 7.
|
||||
// No return: the frame continues into the state machine.
|
||||
if (simulationState == 1 // [this+0x40] == 1 (destroyed)
|
||||
|| heatAlarm.GetLevel() == HeatSink::FailureHeat // [this+0x184] == 2
|
||||
|| (owner != 0 && owner->IsDerivedFrom(*Mech::GetClassDerivations())
|
||||
&& ((Mech *)owner)->IsDisabled())) // FUN_0049fb54
|
||||
&& ((Mech *)owner)->IsDisabled())) // FUN_0049fb54(owner)
|
||||
{
|
||||
recoil = rechargeRate; // this+0x3E8 = this+0x3DC
|
||||
weaponAlarm.SetLevel(7); // FUN_0041bbd8(this+0x350, 7)
|
||||
recoil = rechargeRate; // [this+0x3E8] = [this+0x3DC]
|
||||
weaponAlarm.SetLevel(7); // call 0x41bbd8(this+0x350, 7)
|
||||
}
|
||||
|
||||
// 1. Recover the firing charge (recoil counts down toward 0 in
|
||||
// DrawFiringCharge) and advance any in-progress magazine eject.
|
||||
DrawFiringCharge(time_slice); // @004bbc78 -- bleed recoil (+0x3E8)
|
||||
UpdateEject(time_slice); // @004bbb50 -- idle unless ejectState==0
|
||||
|
||||
// 2. AUTHENTIC TRIGGER (task #5): fireImpulse = the TriggerState attribute
|
||||
// the streamed controls map binds to the pod fire buttons (see emitter.cpp
|
||||
// note). The gBTMissileTrigger bypass is retired.
|
||||
Logical trigger = CheckFireEdge(); // MechWeapon @004b9608
|
||||
// GATE 2 @4bbd71-4bbda6: the linked AmmoBin. Feed alarm Empty(2)/level-3 or
|
||||
// bin destroyed -> re-pin alarm 7 EVERY FRAME while dry. (The binary
|
||||
// dereferences the resolved bin UNGUARDED @4bbd80; the port keeps a null
|
||||
// guard -- shipped content always links a bin, the ctor logs when it can't.)
|
||||
AmmoBin *bin = (AmmoBin *)ammoBinLink.Resolve(); // call 0x417ab4(this+0x43c)
|
||||
if (bin != 0
|
||||
&& (bin->GetAmmoState() == 2 || bin->GetAmmoState() == 3 // [bin+0x1A8]
|
||||
|| bin->GetSimulationState() == 1)) // [bin+0x40] (destroyed)
|
||||
{
|
||||
weaponAlarm.SetLevel(7);
|
||||
}
|
||||
|
||||
static int s_pwtick = 0;
|
||||
if (getenv("BT_PROJ_LOG") && (((++s_pwtick) % 240) == 0 || trigger))
|
||||
DEBUG_STREAM << "[projweap] tick " << GetName() << " trig=" << (int)trigger
|
||||
<< " trigState=" << (float)fireImpulse << " ready=" << (int)ReadyToFire()
|
||||
<< " state=" << weaponAlarm.GetState() << " recoil=" << recoil << " x\n" << std::flush;
|
||||
<< " trigState=" << (float)fireImpulse
|
||||
<< " state=" << weaponAlarm.GetState() << " recoil=" << recoil
|
||||
<< " binState=" << (bin != 0 ? bin->GetAmmoState() : -1) << "\n" << std::flush;
|
||||
|
||||
// 3. Firing state machine (weaponAlarm @0x350). Modelled on the RP analog
|
||||
// RivetGun::RivetGunSimulation (WEAPSYS.cpp): the Jammed / NoAmmo latches
|
||||
// persist and only escalate on a trigger pull; the default (ready) branch
|
||||
// rolls the heat-scaled jam chance and -- on a clean shot with charge and
|
||||
// ammo -- dispatches FireWeapon through the vtable (slot 18) so the
|
||||
// MissileLauncher override spawns Missiles.
|
||||
switch (weaponAlarm.GetState()) // *(this+0x364)
|
||||
// THE FIRING STATE MACHINE @4bbda9: switch on weaponAlarm state ([this+0x364]).
|
||||
// Only states 2/3/5/7 carry a case body; 0/1/4/6 are transient audio blips.
|
||||
switch (weaponAlarm.GetState())
|
||||
{
|
||||
case JammedState: // 5 -- cleared only by ResetToInitialState
|
||||
case 2: // Loaded @4bbec2 -- armed; everything happens on the trigger edge
|
||||
if (trigger)
|
||||
weaponAlarm.SetLevel(TriggerDuringJamState); // 6
|
||||
break;
|
||||
|
||||
case NoAmmoState: // 7 -- re-assert on a dry trigger pull
|
||||
if (trigger)
|
||||
weaponAlarm.SetLevel(NoAmmoState);
|
||||
break;
|
||||
|
||||
default: // Loaded / Loading / Firing / idle
|
||||
if (trigger && ReadyToFire())
|
||||
{
|
||||
if (CheckForJam()) // @004bbfcc -- heat-scaled jam roll
|
||||
if (viewFireEnable != 0 && HasActiveTarget()) // [this+0x3E0] && [owner+0x388]
|
||||
{
|
||||
weaponAlarm.SetLevel(JammedState); // 5
|
||||
break;
|
||||
// @4bbee6: the ammo pull happens HERE, in the caller -- a failed
|
||||
// pull (bin feeding/not-ready) just stays Loaded, silently.
|
||||
if (bin != 0 && bin->FeedAmmo() != 0) // call 0x4bd4f4(bin)
|
||||
{
|
||||
weaponAlarm.SetLevel(0); // @4bbef4 -> Firing
|
||||
ForceUpdate(); // @4bbf05 `or word [this+0x18],1` (updateModel)
|
||||
FireWeapon(); // @4bbf0d vtbl+0x48 (slot 18)
|
||||
if (bin->GetAmmoState() == 2) // @4bbf11 bin went Empty on the pull
|
||||
weaponAlarm.SetLevel(7); // -> unavailable (gate 2 re-pins it)
|
||||
else if (CheckForJam()) // @4bbf2a call 0x4bbfcc (heat-scaled roll)
|
||||
weaponAlarm.SetLevel(JammedState); // @4bbf34 -> 5
|
||||
else
|
||||
weaponAlarm.SetLevel(3); // @4bbf41 -> Loading (reload)
|
||||
ForceUpdate(); // @4bbf4c second updateModel mark
|
||||
recoil = rechargeRate; // @4bbf51 begin the recharge cooldown
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// @4bbf5f THE DENIAL BLIP (Gitea #12 fix): a shot denied by the
|
||||
// look-view gate or by having NO TARGET does SetLevel(4);
|
||||
// SetLevel(2) -- a one-frame audio blip, the pip STAYS LOADED and
|
||||
// NO ammo is pulled. (The old port routed these gates through
|
||||
// FireWeapon early-returns while the caller cycled the full
|
||||
// Firing->Loading alarm anyway -- a denied missile shot faked a
|
||||
// perfect firing cycle and launched nothing: the reported
|
||||
// "missiles cycle but never launch" phenomenology, verbatim.)
|
||||
weaponAlarm.SetLevel(4);
|
||||
weaponAlarm.SetLevel(2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// refresh the lead solution (updates leadPosition / targeting), then
|
||||
// fire. FireWeapon() consumes a round + spawns the projectile/missile
|
||||
// and (on a dry bin) latches NoAmmo itself.
|
||||
ComputeTimeOfFlight(); // @004bc06c
|
||||
FireWeapon(); // vtable slot 18 -> spawn path
|
||||
if (weaponAlarm.GetState() != NoAmmoState && weaponAlarm.GetState() != JammedState)
|
||||
weaponAlarm.SetLevel(0); // Firing
|
||||
}
|
||||
else
|
||||
case 3: // Loading @4bbdd2 -- recharging toward Loaded
|
||||
if (trigger)
|
||||
{
|
||||
// Idle/reload: drive the ready state so the WeaponState audio is correct.
|
||||
// The recon set Firing(0)/Loading(3)/Jammed/NoAmmo but NEVER a ready level,
|
||||
// so at idle the launcher sat in Loading(3) forever and its WeaponState
|
||||
// audio looped MissileLoading indefinitely (the loop stops only when the
|
||||
// alarm LEAVES the loading state). Reflect readiness: Loaded(2, charge +
|
||||
// ammo up = silent ready) once ReadyToFire, else Loading(3, reloading) --
|
||||
// SetLevel fires audio only on a real change, so this just yields the
|
||||
// natural Firing -> Loading(reload) -> Loaded(ready) cycle. [T2]
|
||||
weaponAlarm.SetLevel(ReadyToFire() ? 2 : 3);
|
||||
weaponAlarm.SetLevel(4); // @4bbdd9 TriggerDuringLoad blip
|
||||
weaponAlarm.SetLevel(3); // @4bbdea back to Loading
|
||||
}
|
||||
if (electricalStateAlarm.GetLevel() == 4) // @4bbdf5 [this+0x278] == Ready
|
||||
{
|
||||
// @4bbe04: recoil bleeds ONLY here (Loading + electrical Ready) --
|
||||
// the old port bled it unconditionally every frame with no clamp,
|
||||
// running it to -114 across a mission.
|
||||
DrawFiringCharge(time_slice); // call 0x4bbc78
|
||||
if (recoil < 0.0f) // @4bbe0c fcomp 0.0 (_DAT_004bbf84)
|
||||
{
|
||||
recoil = 0.0f; // @4bbe1d clamp AT zero
|
||||
if (bin != 0 && bin->GetAmmoState() == 1) // @4bbe25 bin Loaded (round chambered)
|
||||
weaponAlarm.SetLevel(2); // @4bbe30 -> Loaded
|
||||
}
|
||||
}
|
||||
ComputeOutputVoltage(); // @4bbe44 vtbl+0x44 (slot 17 @4b9c9c):
|
||||
// rechargeLevel = (rate - recoil)/rate
|
||||
// -- the recharge dial ANIMATES
|
||||
break;
|
||||
|
||||
case 5: // Jammed @4bbe8e -- cleared only by ResetToInitialState / msg 2
|
||||
if (trigger)
|
||||
{
|
||||
weaponAlarm.SetLevel(TriggerDuringJamState); // @4bbe95 blip 6
|
||||
weaponAlarm.SetLevel(JammedState); // @4bbea6 back to 5
|
||||
}
|
||||
recoil = rechargeRate; // @4bbeb1 held at full recoil
|
||||
break;
|
||||
|
||||
case 7: // unavailable/NoAmmo @4bbe4d -- THE ROACH MOTEL (binary-faithful:
|
||||
// the case re-asserts 7 UNCONDITIONALLY; nothing in the machine
|
||||
// ever leaves it. Entered on: weapon destroyed, FailureHeat, mech
|
||||
// disabled (gate 1) or bin empty/destroyed (gate 2). Recovery
|
||||
// paths that EXIST in the binary: ResetToInitialState (slot 10,
|
||||
// mission reset) and an inbound update record overwriting the
|
||||
// alarm on a replicant. A launcher that trips FailureHeat
|
||||
// mid-mission stays dead until mission end -- harsh but authentic;
|
||||
// whether FailureHeat is reachable at authentic play intensity is
|
||||
// the open heat-economy question (open-questions.md, MP section).
|
||||
if (trigger)
|
||||
weaponAlarm.SetLevel(1); // @4bbe54 dry-trigger blip
|
||||
weaponAlarm.SetLevel(7); // @4bbe65 re-assert
|
||||
recoil = rechargeRate; // @4bbe76
|
||||
ComputeOutputVoltage(); // @4bbe85 vtbl+0x44 -- dial pinned at 0
|
||||
break;
|
||||
|
||||
default: // 0/1/4/6 @4bbdcd: no case body (transient audio-blip states)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -695,18 +748,25 @@ void
|
||||
// @004bc104 -- slot 18 FireWeapon (overrides MechWeapon's pure-virtual trap
|
||||
// @004ba45c). Body NOT recovered by the decompiler (prologue confirmed at
|
||||
// 0x4bc104, immediately following the 1.0f literal @0x4bc100). Spawns the
|
||||
// generic projectile/tracer and starts the eject cycle. MissileLauncher
|
||||
// overrides this again at @004bcc60 to launch Missile entities.
|
||||
// generic projectile/tracer. MissileLauncher overrides this again at @004bcc60
|
||||
// to launch Missile entities.
|
||||
//
|
||||
// Gitea #12: the view/target gate, the ammo pull and the recoil set are
|
||||
// STRIPPED from this body -- they belong to the CALLER (the recovered
|
||||
// ProjectileWeaponSimulation Loaded case @4bbec2/@4bbee6/@4bbf51; the sibling
|
||||
// @004bcc60 confirms a FireWeapon body is heat + spawn ONLY). The old
|
||||
// early-returns here, combined with the caller's unconditional alarm cycling,
|
||||
// were the "denied shot fakes a full firing cycle" incident mechanic.
|
||||
//
|
||||
void
|
||||
ProjectileWeapon::FireWeapon()
|
||||
{
|
||||
Check(this);
|
||||
|
||||
// (task #68) the view gate: a weapon disabled in the CURRENT look view
|
||||
// does not fire (see mislanch.cpp / the emitter's Loaded->Firing gate).
|
||||
if (!viewFireEnable)
|
||||
return;
|
||||
// Refresh the lead solution (leadPosition / targeting) for the spawn below
|
||||
// (@004bc06c; the recovered sim no longer calls it -- best-effort placement
|
||||
// inside the unrecovered @4bc104 body, which is where the lead data is used).
|
||||
ComputeTimeOfFlight(); // @004bc06c
|
||||
|
||||
// THE FIRING HEAT (task #9; was "a separate pre-existing gap"): the
|
||||
// binary adds heatCostToFire RAW to the weapon's own pendingHeat --
|
||||
@@ -717,10 +777,10 @@ void
|
||||
pendingHeat += heatCostToFire; // HeatSink @0x1C8, raw
|
||||
}
|
||||
|
||||
// Binary @4bc136-4bc19c (task #56): the firing RECOIL kick, BEFORE the ammo
|
||||
// pull (no early-out precedes it in the binary). Gate: per-shot damage >
|
||||
// 3.0f (@0x4bc3f4) && the owner mech has a gyro. Direction (0, 0.6, -1.5)
|
||||
// (0x3f19999a/0xbfc00000), magnitude = damageAmount / 16 (@0x4bc3f8).
|
||||
// Binary @4bc136-4bc19c (task #56): the firing RECOIL kick. Gate: per-shot
|
||||
// damage > 3.0f (@0x4bc3f4) && the owner mech has a gyro. Direction
|
||||
// (0, 0.6, -1.5) (0x3f19999a/0xbfc00000), magnitude = damageAmount / 16
|
||||
// (@0x4bc3f8).
|
||||
if (damageData.damageAmount > 3.0f && owner != 0)
|
||||
{
|
||||
extern void GyroApplyDamageImpulse(Subsystem *, Scalar, Scalar, Scalar, Scalar);
|
||||
@@ -730,16 +790,6 @@ void
|
||||
damageData.damageAmount * 0.0625f);
|
||||
}
|
||||
|
||||
// Pull a round; a dry / not-ready bin latches NoAmmo and aborts the shot.
|
||||
if (!ConsumeRound()) // FUN_004bd4f4 (AmmoBin::FeedAmmo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Begin the per-shot recharge cooldown (recoil bleeds back down to 0 in
|
||||
// DrawFiringCharge before the weapon is ReadyToFire again).
|
||||
recoil = rechargeRate; // this[0xfa] = this[0xf7]
|
||||
|
||||
// Resolve the live muzzle.
|
||||
Point3D muzzle;
|
||||
GetMuzzlePoint(muzzle); // MechWeapon @004b9948
|
||||
@@ -779,21 +829,17 @@ void
|
||||
subsystemID /*messmgr explosion bundling at impact (task #7)*/);
|
||||
|
||||
// task #61: mark this shot for REPLICATION so the peer sees the enemy's
|
||||
// cannon. ++fireCounter + ForceUpdate() -- IDENTICAL to the missile path
|
||||
// (mislanch.cpp:316/326) and the emitter beam: the master's per-frame
|
||||
// subsystem serialize writes this weapon's record (WriteUpdateRecord),
|
||||
// Entity::UpdateMessageHandler routes it to the peer's replicant, and the
|
||||
// replicant's ReadUpdateRecord mirrors the shot. (The old code set only
|
||||
// `simulationFlags |= 0x1` -- the +0x28 instance flag, NOT the updateModel
|
||||
// bit WriteSimulationUpdate walks -- so the AC record never serialized: the
|
||||
// root cause of the invisible enemy autocannon.)
|
||||
// cannon. ++fireCounter; the CALLER's Loaded case makes the two
|
||||
// `updateModel |= 1` marks (@4bbf05/@4bbf4c == ForceUpdate) that serialize
|
||||
// this weapon's record (WriteUpdateRecord) to the peer's replicant.
|
||||
// Gitea #12 / gotcha #20: the old extra `simulationFlags |= 0x1` here set
|
||||
// the engine DelayWatchersFlag -- permanently muting this weapon's audio
|
||||
// watchers after the first shot; removed.
|
||||
{
|
||||
BTAcFireState &fs = BTAcFireOf(this);
|
||||
++fs.fired;
|
||||
fs.target = targetPos;
|
||||
}
|
||||
simulationFlags |= 0x1; // replication-dirty (state flag)
|
||||
ForceUpdate(); // set the updateModel bit -> serialize the record
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user