Skip to content

fix(nukes): pick closest silo with a clear path around impassable terrain - #4815

Merged
evanpelle merged 1 commit into
mainfrom
fix/nuke-silo-impassable-path
Jul 31, 2026
Merged

fix(nukes): pick closest silo with a clear path around impassable terrain#4815
evanpelle merged 1 commit into
mainfrom
fix/nuke-silo-impassable-path

Conversation

@evanpelle

Copy link
Copy Markdown
Collaborator

Problem

Silo selection for nuke launches picked the closest silo by Manhattan distance with no path awareness. When that silo's parabola crossed impassable terrain, the launch was accepted (canBuild succeeded) and then silently aborted in NukeExecution — even when a slightly farther silo had a clear shot, or when the other curve direction (up vs down) from the same silo was clear.

Changes

Silo selection (PlayerImpl.nukeSpawn)

  • Ready silos are walked in Manhattan-distance order; the first one whose trajectory avoids impassable terrain on the up or down curve is chosen.
  • If no silo has a clear curve, canBuild returns false, so the UI shows the target as unbuildable up front instead of accepting a launch that would abort.
  • MIRVs keep plain closest-silo selection: they fly to a separation point high above the map and their warheads are exempt from impassable checks.

Curve direction (NukeExecution)

  • The nuke flies the requested curve direction if it is clear, automatically flips to the opposite curve if not, and aborts only when both cross impassable terrain. The recorded trajectory (used by SAMs) and the client motion plan both use the resolved direction.

Speed-independent blocked checks (PathFinder.Parabola)

  • New shared isParabolaBlocked / clearParabolaDirection helpers sample the curve at a fixed fine increment instead of the nuke's speed. Previously a fast nuke could "tunnel" through a thin impassable strip between samples that a slow nuke would hit.

Consumers kept in sync

  • Nation AI: isTrajectoryBlockedByImpassable now means "blocked on both curves" (matching the sim's flip behavior); the per-target check in maybeSendNuke was removed since canBuild now guarantees it.
  • Build preview: the trajectory arc originates from the silo the sim would actually pick and draws the curve direction that will actually fly, falling back to the nearest silo + requested direction (red X) when everything is blocked.

Tests

Five new tests in ImpassableTerrain.test.ts (terrain builder now supports partial-height walls so the two curves can be blocked independently):

  • a closer blocked silo is skipped for a farther clear one (verified through full flight to reachedTarget())
  • up curve blocked → flips to down curve; down curve blocked → flips to up curve
  • both curves blocked from every silo → canBuild returns false and the execution never builds a unit
  • MIRV silo selection ignores impassable trajectories

Full suite passes (2842 tests).

🤖 Generated with Claude Code

…rain

Silo selection for nuke launches previously picked the closest silo by
Manhattan distance with no path awareness, so a launch whose parabola
crossed impassable terrain was accepted and then silently aborted even
when another silo had a clear shot.

- nukeSpawn now returns the closest ready silo whose trajectory avoids
  impassable terrain on the up OR down curve; canBuild returns false
  when no silo qualifies (MIRVs keep plain closest-silo selection —
  warheads are exempt from impassable checks)
- NukeExecution flies the requested curve direction if clear, flips to
  the opposite curve if not, and aborts only when both are blocked
- blocked checks sample the curve at a fixed fine increment instead of
  the nuke's speed, so a fast nuke can no longer tunnel through a thin
  impassable strip that a slow one would hit
- shared clearParabolaDirection/isParabolaBlocked helpers keep the sim,
  nation AI, and build-preview arc consistent; the preview now draws
  from the silo and curve direction the sim would actually use

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Nuke trajectory handling now samples both parabola directions for impassable terrain. Silo selection prefers the nearest valid silo. Preview and execution reuse the resolved direction. MIRV launches retain their terrain bypass.

Changes

Nuke trajectory fallback

Layer / File(s) Summary
Parabola clearance utilities
src/core/pathfinding/PathFinder.Parabola.ts
Adds sampled terrain checks and clearParabolaDirection, which tries the preferred curve and then the opposite curve.
Silo selection and preview resolution
src/core/game/PlayerImpl.ts, src/client/controllers/BuildPreviewController.ts
Sorts ready silos by Manhattan distance. Selects the nearest silo with a clear trajectory and caches the resolved direction for preview rebuilds.
Launch validation and coverage
src/core/execution/NukeExecution.ts, src/core/execution/nation/NationNukeBehavior.ts, tests/ImpassableTerrain.test.ts
Uses directional fallback during validation, rebuilds the pathfinder when needed, rejects launches when both curves are blocked, and covers MIRV behavior and silo selection.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Player
  participant BuildPreviewController
  participant PlayerImpl
  participant PathFinder
  participant NukeExecution

  Player->>BuildPreviewController: choose nuke target
  BuildPreviewController->>PlayerImpl: find valid silo
  PlayerImpl->>PathFinder: check preferred parabola
  PathFinder-->>PlayerImpl: clear or blocked
  PlayerImpl->>PathFinder: check opposite parabola when needed
  PathFinder-->>PlayerImpl: resolved direction or no path
  PlayerImpl-->>BuildPreviewController: silo and direction
  BuildPreviewController-->>Player: render trajectory
  Player->>NukeExecution: launch nuke
  NukeExecution->>PathFinder: validate and rebuild path when direction changes
Loading

Possibly related PRs

Suggested labels: Bugfix

Suggested reviewers: flopinguin, variablevince, tktk123456

Poem

Two arcs rise through the air,
One blocked, the other clear.
Silos sort and paths align,
MIRVs keep their special line.
Launch only when a curve can steer.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: selecting the closest silo with a clear nuke trajectory around impassable terrain.
Description check ✅ Passed The description directly explains the silo selection, trajectory fallback, MIRV behavior, synchronization changes, and added tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@evanpelle evanpelle added this to the v33 milestone Jul 31, 2026
@evanpelle
evanpelle merged commit bf38c58 into main Jul 31, 2026
14 of 16 checks passed
@evanpelle
evanpelle deleted the fix/nuke-silo-impassable-path branch July 31, 2026 21:15
@github-project-automation github-project-automation Bot moved this from Triage to Complete in OpenFront Release Management Jul 31, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/core/pathfinding/PathFinder.Parabola.ts (1)

14-41: 🚀 Performance & Scalability | 🔵 Trivial

Fine-grained sampling adds cost to hot paths.

BLOCKED_CHECK_INCREMENT = 1 makes isParabolaBlocked sample every unit of curve length. This is correct for the tunneling fix, but it runs inside several hot loops:

  • PlayerImpl.nukeSpawn calls clearParabolaDirection (up to 2 samples) for each ready silo, in distance order, until one is clear.
  • NationNukeBehavior.maybeSendNuke evaluates up to ~40 candidate tiles per AI decision; each candidate goes through canBuildnukeSpawn.
  • NationNukeBehavior.maybeDestroyEnemySam calls isTrajectoryBlockedByImpassable once per silo per candidate SAM target.

For nations with several silos and terrain-heavy maps, this can multiply into hundreds of fine-grained curve samples per AI turn. Consider watching for this in profiling, or caching a resolved direction per (silo, target) pair within a single AI evaluation pass if it shows up as a bottleneck.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/pathfinding/PathFinder.Parabola.ts` around lines 14 - 41, Profile
the hot paths invoking isParabolaBlocked, especially PlayerImpl.nukeSpawn and
NationNukeBehavior.maybeSendNuke/maybeDestroyEnemySam, and reduce repeated
fine-grained sampling if it is a bottleneck. Prefer caching the resolved
parabola direction or blocked result per silo-target pair within a single AI
evaluation pass, while preserving the tunneling fix and existing behavior.
src/client/controllers/BuildPreviewController.ts (1)

337-343: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Reuse this.game.manhattanDist for the silo sort.

BuildPreviewController.game is a GameView, GameView.manhattanDist() delegates to the shared GameMapImpl.manhattanDist() implementation, and PlayerImpl.nukeSpawn uses that same API for the source silo. Use this.game.manhattanDist(a.tile(), tileRef) and this.game.manhattanDist(b.tile(), tileRef) instead.

♻️ Proposed refactor
     silos.sort(
       (a, b) =>
-        Math.abs(this.game.x(a.tile()) - dstX) +
-        Math.abs(this.game.y(a.tile()) - dstY) -
-        (Math.abs(this.game.x(b.tile()) - dstX) +
-          Math.abs(this.game.y(b.tile()) - dstY)),
+        this.game.manhattanDist(a.tile(), tileRef) -
+        this.game.manhattanDist(b.tile(), tileRef),
     );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/controllers/BuildPreviewController.ts` around lines 337 - 343,
Update the silo comparator in BuildPreviewController to use
this.game.manhattanDist(a.tile(), tileRef) and this.game.manhattanDist(b.tile(),
tileRef) instead of manually calculating coordinate differences, preserving the
existing ascending distance sort.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/execution/nation/NationNukeBehavior.ts`:
- Around line 631-644: Update the trajectory handling between
isTrajectoryBlockedByImpassable and isTrajectoryInterceptableBySam to resolve
the flyable parabola direction with clearParabolaDirection, using the fallback
direction selected by NukeExecution when the upward curve is blocked. Pass that
resolved direction into the SAM pathfinder instead of hardcoding directionUp:
true, while preserving interception checks for unreachable trajectories.

---

Nitpick comments:
In `@src/client/controllers/BuildPreviewController.ts`:
- Around line 337-343: Update the silo comparator in BuildPreviewController to
use this.game.manhattanDist(a.tile(), tileRef) and
this.game.manhattanDist(b.tile(), tileRef) instead of manually calculating
coordinate differences, preserving the existing ascending distance sort.

In `@src/core/pathfinding/PathFinder.Parabola.ts`:
- Around line 14-41: Profile the hot paths invoking isParabolaBlocked,
especially PlayerImpl.nukeSpawn and
NationNukeBehavior.maybeSendNuke/maybeDestroyEnemySam, and reduce repeated
fine-grained sampling if it is a bottleneck. Prefer caching the resolved
parabola direction or blocked result per silo-target pair within a single AI
evaluation pass, while preserving the tunneling fix and existing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 797a32a8-f2ae-4bd2-a287-58e8ed29fe1b

📥 Commits

Reviewing files that changed from the base of the PR and between 3fa1a8e and 31f442e.

📒 Files selected for processing (6)
  • src/client/controllers/BuildPreviewController.ts
  • src/core/execution/NukeExecution.ts
  • src/core/execution/nation/NationNukeBehavior.ts
  • src/core/game/PlayerImpl.ts
  • src/core/pathfinding/PathFinder.Parabola.ts
  • tests/ImpassableTerrain.test.ts

Comment on lines 631 to 644
/**
* Check if the parabolic nuke trajectory from spawnTile to targetTile
* crosses any impassable terrain. Mirrors the check in NukeExecution that
* aborts such launches
* crosses impassable terrain on BOTH curve directions. Mirrors
* NukeExecution, which flips to the opposite curve when the requested one
* is blocked and aborts only when both are.
*/
private isTrajectoryBlockedByImpassable(
spawnTile: TileRef,
targetTile: TileRef,
): boolean {
const pathFinder = UniversalPathFinding.Parabola(this.game, {
increment: this.game.config().nukeSpeed(UnitType.AtomBomb),
distanceBasedHeight: true,
directionUp: true,
});
const path = pathFinder.findPath(spawnTile, targetTile) ?? [];
for (const tile of path) {
if (this.game.isImpassable(tile)) {
return true;
}
}
return false;
return (
clearParabolaDirection(this.game, spawnTile, targetTile, true) === null
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

isTrajectoryInterceptableBySam still assumes the nuke always flies "up," which is no longer guaranteed.

isTrajectoryBlockedByImpassable now correctly treats a target as reachable when either curve direction is clear (matching NukeExecution's fallback). Because of that, a candidate tile can now reach nukeTileScore/salvo planning purely because the DOWN curve is clear while the UP curve is blocked by terrain.

isTrajectoryInterceptableBySam (unchanged, lines 548-629) always builds its SAM-coverage check with directionUp: true ("AI nukes always go 'up' for now"). For a target where only the down curve is actually flyable, this function evaluates SAM coverage along a curve the nuke will never fly (the up curve, blocked by terrain), instead of the curve NukeExecution will actually use. This can make the AI:

  • On Hard/Impossible, wrongly treat a safe (down-curve) launch as SAM-interceptable, or vice versa.
  • In maybeDestroyEnemySam's salvo planning, compute wrong flight times/interception odds for silos whose actual flight path is the down curve.

Before this PR, the removed upward-only terrain filter guaranteed every candidate that reached this check had a clear up curve, so the hardcoded assumption held. Now that guarantee is gone. Consider resolving the actual direction with clearParabolaDirection before computing SAM interception, and passing that resolved direction into isTrajectoryInterceptableBySam's pathfinder instead of hardcoding true.

#!/bin/bash
# Inspect all call sites and the hardcoded direction assumption.
rg -n "isTrajectoryInterceptableBySam|directionUp: true" src/core/execution/nation/NationNukeBehavior.ts -B3 -A3
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/execution/nation/NationNukeBehavior.ts` around lines 631 - 644,
Update the trajectory handling between isTrajectoryBlockedByImpassable and
isTrajectoryInterceptableBySam to resolve the flyable parabola direction with
clearParabolaDirection, using the fallback direction selected by NukeExecution
when the upward curve is blocked. Pass that resolved direction into the SAM
pathfinder instead of hardcoding directionUp: true, while preserving
interception checks for unreachable trajectories.

@github-project-automation github-project-automation Bot moved this from Complete to Development in OpenFront Release Management Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

1 participant