From 2250b01c6b79f00eddbcc2a03ed78e5fa33caf9b Mon Sep 17 00:00:00 2001 From: arcattack Date: Thu, 9 Jul 2026 08:37:05 -0500 Subject: [PATCH] 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 --- game/reconstructed/mech4.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 13b1fc4..08b29f3 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -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)