Fire-channel pulses moved out of the enemy block (post-kill firing froze)

User report: after killing the enemy, wandering around, weapons won't
fire. Not a lock requirement -- the trigger pulse generator (the
1,0,1,0 edge feed for CheckFireEdge) sat inside `if (gEnemyMech != 0)`.
Once the wreck buried and gEnemyMech nulled, the channels froze at
their last value -> no rising edges -> no fire, even with a valid
terrain pick in the target slot. Latent since the world-pick model made
no-enemy firing meaningful (task #41); the weapon-group split kept the
pulses in the same wrong place.

The pulses now run unconditionally (before the enemy block); the
weapon's own HasActiveTarget gate remains the only fire arbiter --
terrain downrange fires at the scenery, a true sky shot (nothing within
1200) authentically refuses.

Verified: BT_AIM=0.5,0.3 (terrain-only picks, enemy never aimed at) ->
sustained fire, 123 laser + 82 PPC beam samples.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-09 08:37:05 -05:00
co-authored by Claude Fable 5
parent 14c0dd06f5
commit 2250b01c6b
+19 -13
View File
@@ -2522,6 +2522,24 @@ void
gBTHudPrimary = ((int)targetReticle.reticleElementMask & 0x20) != 0;
}
// E8: pulse the three fire channels per frame (1,0,1,0...) so each
// weapon sim's CheckFireEdge sees clean rising edges. UNCONDITIONAL --
// NOT inside the enemy block: firing needs only the world PICK (task
// #41: the beam goes to the scenery downrange with no enemy alive; the
// weapon's OWN HasActiveTarget gate refuses only a true sky shot).
// Leaving these inside `if (gEnemyMech)` froze the channels after the
// wreck buried -> no trigger edges -> "can't fire after the kill"
// (user-reported regression). targetInArc is the explicit BT_FIRE_ARC
// presentation clamp (default true). BT_AUTOFIRE holds all three.
{
const int laserWanted = gBTDrive.fireForced || gBTLaserKey;
const int ppcWanted = gBTDrive.fireForced || gBTPPCKey;
const int missileWanted = gBTDrive.fireForced || gBTMissileKey;
gBTWeaponTrigger = (laserWanted && targetInArc) ? (gBTWeaponTrigger ? 0 : 1) : 0;
gBTPPCTrigger = (ppcWanted && targetInArc) ? (gBTPPCTrigger ? 0 : 1) : 0;
gBTMissileTrigger = (missileWanted && targetInArc) ? (gBTMissileTrigger ? 0 : 1) : 0;
}
if (gEnemyMech != 0)
{
Point3D enemyPos = ((Mech *)gEnemyMech)->localOrigin.linearPosition;
@@ -2583,19 +2601,7 @@ void
gFireCooldown -= dt;
const int fireWanted = gBTDrive.fireForced || gBTDrive.fire;
// E8: pulse the three fire channels per frame (1,0,1,0...) so each
// weapon sim's CheckFireEdge sees clean rising edges. AUTHENTIC
// gating: the trigger passes through and the weapon's OWN
// HasActiveTarget gate decides (FireWeapon -- part_013.c:7758; no
// aim/arc test exists). targetInArc is the explicit BT_FIRE_ARC
// presentation clamp (default true). BT_AUTOFIRE holds all three.
const int laserWanted = gBTDrive.fireForced || gBTLaserKey;
const int ppcWanted = gBTDrive.fireForced || gBTPPCKey;
const int missileWanted = gBTDrive.fireForced || gBTMissileKey;
gBTWeaponTrigger = (laserWanted && targetInArc) ? (gBTWeaponTrigger ? 0 : 1) : 0;
gBTPPCTrigger = (ppcWanted && targetInArc) ? (gBTPPCTrigger ? 0 : 1) : 0;
gBTMissileTrigger = (missileWanted && targetInArc) ? (gBTMissileTrigger ? 0 : 1) : 0;
// (the fire-channel pulses moved ABOVE the enemy block -- task #43a)
// Resolve the "explode" effect resource once.
if (gExplodeReady == 0)