mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 20:16:44 +00:00
71af72606a
## Summary Fixes #4226 (Release Blocker, V32 regression). The WebGL nuke trajectory preview built its SAM threat set by unconditionally excluding own + allied SAMs (`BuildPreviewController.updateNukeTrajectoryPreview`). But when the strike targets allied territory, the alliance breaks at launch — `NukeExecution.maybeBreakAlliances()` — so the betrayed ally's SAMs **do** engage the nuke. The preview therefore showed a fully white trajectory with no intercept X over an allied SAM, even though the bomb would be shot down (V31 previewed this correctly). ## Fix - Compute the would-be-betrayed player set with `listNukeBreakAlliance()` — the exact function the sim uses at launch, so preview and sim can't drift. - Keep an allied SAM in the threat set iff its owner is in that set (extracted as pure `samThreatensNukePreview()`). - Other (non-betrayed) allies' SAMs remain excluded, matching sim behavior where only alliances over the blast threshold break. Both missing artifacts in the issue (red post-intercept segment and X marker) come from `tSamIntercept` staying at 1.0 because no SAM was supplied, so this one change restores both. Cost note: this adds one `circleSearch` per throttled ghost update (50ms) when the player has allies — same order as the existing `wouldNukeBreakAlliance` call for the red warning circle. ## Testing - Unit tests for the new threat-set predicate (4 cases) in `tests/client/controllers/BuildPreviewController.test.ts` - `tsc --noEmit`, ESLint, Prettier clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
56 lines
2.3 KiB
TypeScript
56 lines
2.3 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import {
|
|
samThreatensNukePreview,
|
|
shouldPreserveGhostAfterBuild,
|
|
} from "../../../src/client/controllers/BuildPreviewController";
|
|
import { UnitType } from "../../../src/core/game/Game";
|
|
|
|
describe("BuildPreviewController ghost preservation (locked nuke / Enter confirm)", () => {
|
|
describe("shouldPreserveGhostAfterBuild", () => {
|
|
test("returns true for AtomBomb so ghost is not cleared after placement", () => {
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.AtomBomb)).toBe(true);
|
|
});
|
|
|
|
test("returns true for HydrogenBomb so ghost is not cleared after placement", () => {
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.HydrogenBomb)).toBe(true);
|
|
});
|
|
|
|
test("returns false for City so ghost is cleared after placement", () => {
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.City)).toBe(false);
|
|
});
|
|
|
|
test("returns false for Factory so ghost is cleared after placement", () => {
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.Factory)).toBe(false);
|
|
});
|
|
|
|
test("returns false for other buildable types (Port, DefensePost, MissileSilo, SAMLauncher, Warship, MIRV)", () => {
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.Port)).toBe(false);
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.DefensePost)).toBe(false);
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.MissileSilo)).toBe(false);
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.SAMLauncher)).toBe(false);
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.Warship)).toBe(false);
|
|
expect(shouldPreserveGhostAfterBuild(UnitType.MIRV)).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("samThreatensNukePreview (nuke trajectory threat set, #4226)", () => {
|
|
const allies = new Set([2, 3]);
|
|
|
|
test("non-allied SAM threatens the trajectory", () => {
|
|
expect(samThreatensNukePreview(5, allies, new Set())).toBe(true);
|
|
});
|
|
|
|
test("allied SAM does not threaten when the strike breaks no alliance", () => {
|
|
expect(samThreatensNukePreview(2, allies, new Set())).toBe(false);
|
|
});
|
|
|
|
test("would-be-betrayed ally's SAM threatens (alliance breaks at launch)", () => {
|
|
expect(samThreatensNukePreview(2, allies, new Set([2]))).toBe(true);
|
|
});
|
|
|
|
test("other allies' SAMs still excluded when a different ally is betrayed", () => {
|
|
expect(samThreatensNukePreview(3, allies, new Set([2]))).toBe(false);
|
|
});
|
|
});
|