From 9e80d534fb43bca8c6a671d124e962c16d9995f8 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Wed, 10 Jun 2026 16:26:29 -0700 Subject: [PATCH] Align nuke trajectory preview silo selection with NukeExecution The preview arc could show a nuke originating from a silo the game would never fire from. The renderer's silo selection had diverged from the authoritative PlayerImpl.nukeSpawn that NukeExecution uses: - Eligibility: the renderer only excluded inactive silos, but the game also excludes silos that are reloading (isInCooldown) or under construction. - Distance: the renderer used Euclidean distance; the game uses Manhattan. Add isInCooldown() to UnitView mirroring UnitImpl, and update the trajectory preview to filter on isActive && !isInCooldown && !isUnderConstruction and pick the nearest silo by Manhattan distance. When no silo is eligible the trajectory clears, matching canBuild returning false. --- .../controllers/BuildPreviewController.ts | 19 ++++++++++++------- src/client/view/UnitView.ts | 3 +++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/client/controllers/BuildPreviewController.ts b/src/client/controllers/BuildPreviewController.ts index b9f771df8..416bc2cdb 100644 --- a/src/client/controllers/BuildPreviewController.ts +++ b/src/client/controllers/BuildPreviewController.ts @@ -281,9 +281,16 @@ export class BuildPreviewController implements Controller { return; } + // Mirror PlayerImpl.nukeSpawn (the source NukeExecution actually fires + // from): only silos that are active, not reloading, and not under + // construction are eligible, and the nearest is chosen by Manhattan + // distance. Keeping these in sync prevents the preview arc from + // originating from a silo the game wouldn't use. const silos = myPlayer .units(UnitType.MissileSilo) - .filter((u) => u.isActive()); + .filter( + (u) => u.isActive() && !u.isInCooldown() && !u.isUnderConstruction(), + ); if (silos.length === 0) { this.clearNukeTrajectory(); return; @@ -292,15 +299,13 @@ export class BuildPreviewController implements Controller { const dstX = this.game.x(tileRef); const dstY = this.game.y(tileRef); let bestSilo = silos[0]; - let bestDistSq = Infinity; + let bestDist = Infinity; for (const s of silos) { const sx = this.game.x(s.tile()); const sy = this.game.y(s.tile()); - const dx = sx - dstX; - const dy = sy - dstY; - const d = dx * dx + dy * dy; - if (d < bestDistSq) { - bestDistSq = d; + const d = Math.abs(sx - dstX) + Math.abs(sy - dstY); + if (d < bestDist) { + bestDist = d; bestSilo = s; } } diff --git a/src/client/view/UnitView.ts b/src/client/view/UnitView.ts index 2fc81132a..e1b4c7095 100644 --- a/src/client/view/UnitView.ts +++ b/src/client/view/UnitView.ts @@ -226,6 +226,9 @@ export class UnitView { isUnderConstruction(): boolean { return this.state.underConstruction; } + isInCooldown(): boolean { + return this.state.missileTimerQueue.length === this.state.level; + } targetUnitId(): number | undefined { return this.state.targetUnitId ?? undefined; }