🔴 Major SAM Targeting Fix (#3223)

## 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).

<img width="734" height="743" alt="asadasada"
src="https://github.com/user-attachments/assets/7ca6c9ef-b2b4-47ea-bed2-249a84c8f5ed"
/>

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>
This commit is contained in:
Abdallah Bahrawi
2026-02-18 01:12:56 +02:00
committed by GitHub
parent 1443b62207
commit 7bf6bfd4b9
2 changed files with 11 additions and 6 deletions
+7 -6
View File
@@ -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<number, InterceptionTile | null> =
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
@@ -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;