From 7bf6bfd4b995990cffe1dc836f4b0168bef3f8dd Mon Sep 17 00:00:00 2001 From: Abdallah Bahrawi <140177728+abdallahbahrawi1@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:12:56 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=B4=20Major=20SAM=20Targeting=20Fix=20?= =?UTF-8?q?(#3223)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description: Bug (before): In multi-nuke scenarios (e.g., 6 incoming nukes), SAMs, when stacked, could behave like they only engage one nuke at a time, letting other nukes slip through and wipe the SAM stack. This was most noticeable when nukes entered from certain angles/entry sides (from what I’ve noticed, it happens more often in the 2 o’clock to 6 o’clock direction). asadasada What was fixed: 1- Removed permanent "unreachable" caching: When a nuke was out of SAM range on first evaluation, it was cached as null (permanently unreachable) and never re-evaluated. Since nukes move each tick, they could later enter interception range but would be ignored. The fix removes this permanent cache, so nukes are re-evaluated every tick. 2 - Moved targetedBySAM filter into the targeting system: The targetedBySAM() check was at the launch decision, if the highest-priority nuke was already claimed by another SAM, the launcher skipped firing entirely instead of falling back to the next best target. The fix moves the filter inside getSingleTarget() so claimed nukes are excluded before ranking. 3 - Cleared targetedBySAM flag on SAM missile abort: When a SAM missile aborted (for example target became allied), the targetedBySAM flag was never cleared. This permanently prevented all other SAMs from targeting that nuke. The fix calls setTargetedBySAM(false) on abort so the nuke becomes available for re-targeting. **Videos:** I uploaded a before/after clip showing SAM performance intercepting 6 nukes, before the fix, nukes could break through, but after the fix, SAMs consistently intercept as expected. Before: https://github.com/user-attachments/assets/d5a85354-f35c-4aca-82f8-902f5966312b after: https://github.com/user-attachments/assets/54074c09-fbdf-44d5-a88c-b1d54b20fee2 - Deployed for further testing ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: abodcraft1 --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> --- src/core/execution/SAMLauncherExecution.ts | 13 +++++++------ src/core/execution/SAMMissileExecution.ts | 4 ++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/core/execution/SAMLauncherExecution.ts b/src/core/execution/SAMLauncherExecution.ts index c723df0d8..d7d55aafa 100644 --- a/src/core/execution/SAMLauncherExecution.ts +++ b/src/core/execution/SAMLauncherExecution.ts @@ -26,8 +26,8 @@ type InterceptionTile = { */ class SAMTargetingSystem { // Interception tiles are computed a single time, but it may not be reachable yet. - // Store the result so it can be intercepted at the proper time, rather than recomputing each ticks - // Null interception tile means there are no interception tiles in range. Store it to + // Store the result so it can be intercepted at the proper time, rather than recomputing each tick. + // Null interception tile means there are no interception tiles in range. Store it to avoid recomputing. private readonly precomputedNukes: Map = new Map(); private readonly missileSpeed: number; @@ -91,7 +91,8 @@ class SAMTargetingSystem { return ( isUnit(unit) && unit.owner() !== this.sam.owner() && - !this.sam.owner().isFriendly(unit.owner()) + !this.sam.owner().isFriendly(unit.owner()) && + !unit.targetedBySAM() ); }, ); @@ -105,7 +106,7 @@ class SAMTargetingSystem { const cached = this.precomputedNukes.get(nukeId); if (cached !== undefined) { if (cached === null) { - // Known unreachable, skip. + // Already computed as unreachable, skip continue; } if (cached.tick === ticks) { @@ -259,8 +260,8 @@ export class SAMLauncherExecution implements Execution { } } - const isSingleTarget = target && !target.unit.targetedBySAM(); - if (isSingleTarget || mirvWarheadTargets.length > 0) { + // target is already filtered to exclude nukes targeted by other SAMs + if (target || mirvWarheadTargets.length > 0) { this.sam.launch(); const type = mirvWarheadTargets.length > 0 diff --git a/src/core/execution/SAMMissileExecution.ts b/src/core/execution/SAMMissileExecution.ts index 7fb4346ce..657166a80 100644 --- a/src/core/execution/SAMMissileExecution.ts +++ b/src/core/execution/SAMMissileExecution.ts @@ -50,6 +50,10 @@ export class SAMMissileExecution implements Execution { this.target.owner() === this.SAMMissile.owner() || !nukesWhitelist.includes(this.target.type()) ) { + // Clear the flag so other SAMs can re-target this nuke + if (this.target.isActive()) { + this.target.setTargetedBySAM(false); + } this.SAMMissile.delete(false); this.active = false; return;