Skip to content

Make MIRVs take fixed time to fire regardless of launch location 🚀#3770

Open
FloPinguin wants to merge 2 commits intomainfrom
mirv-fixed-fire-time
Open

Make MIRVs take fixed time to fire regardless of launch location 🚀#3770
FloPinguin wants to merge 2 commits intomainfrom
mirv-fixed-fire-time

Conversation

@FloPinguin
Copy link
Copy Markdown
Contributor

Description:

Multiple people asked for that in the game-feedback DC channel, so here is a PR for it: MIRV flight time from launch to warhead separation is now a fixed 75 ticks (enough reaction time for the targeted player?) regardless of where the launching silo sits relative to the target.

(The video was taken with just 50 ticks configured)

2026-04-26.00-16-23.mp4

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • 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:

FloPinguin

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Walkthrough

MIRVExecution defers computing speed and creating pathFinder until spawnTile and separateDst are known. A new computeFixedTimeSpeed() measures the parabola path length with a high-resolution temporary pathfinder and sets speed to reach the target in fixedFireTicks, falling back to defaultNukeSpeed() if measurement fails.

Changes

Cohort / File(s) Summary
MIRV Execution Timing
src/core/execution/MIRVExecution.ts
Removed early parabola pathFinder setup in init(); deferred initialization to tick() once spawnTile and separateDst exist. Added computeFixedTimeSpeed() which builds a temporary parabola pathfinder with increment: 1 to measure path length, computes this.speed = pathLength / fixedFireTicks, and falls back to defaultNukeSpeed() for non-positive measurements. pathFinder is then created with increment: this.speed for fixed-duration flight progression.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🎯 Parabola traced in careful light,

speed tuned to meet the fixed-tick flight,
a tiny pathfinder scopes the way,
fallback ready if numbers stray,
MIRV arcs true at launch-time bright.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: MIRV flight time is now fixed regardless of launch location, which directly aligns with the primary modification in the changeset.
Description check ✅ Passed The description is well-related to the changeset, explaining the motivation (user feedback), the specific behavior change (75 ticks fixed flight time), and confirming all required checks including tests and translations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/core/execution/MIRVExecution.ts (2)

121-137: Measuring the path with increment: 1 works, but it is a lot of work — and a small wording fix.

For every MIRV launch this builds an entire parabolic path at one-tile resolution and then walks it to sum Euclidean segments. For long-range silos that is a non-trivial amount of allocation just to derive a single number. Two cheaper options that keep the rest of the change intact:

  • Compute the parabolic arc length analytically from spawnTileseparateDst (and the apex implied by line 87). For a quadratic/parabolic curve this is a closed form and avoids any pathfinder allocation.
  • Or sample with a coarser increment (e.g. 8–16); the relative error on arc length is tiny for a smooth parabola and the work drops by the same factor.

Also: the comment says "~1 px apart" but the units here are map tiles, not pixels.

♻️ Small wording fix
   private computeFixedTimeSpeed(): number {
     // Measure the actual parabolic path length, then divide by the desired
     // tick count. A temporary fine-grained pathfinder gives a precise
-    // length estimate (cached points are spaced ~1 px apart).
+    // length estimate (cached points are spaced ~1 tile apart).
     const measure = UniversalPathFinding.Parabola(this.mg, { increment: 1 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/execution/MIRVExecution.ts` around lines 121 - 137,
computeFixedTimeSpeed currently builds a dense Parabola path with
UniversalPathFinding.Parabola(this.mg, { increment: 1 }) and walks every segment
to compute arc length (using spawnTile, separateDst and fixedFireTicks), which
is wasteful and misdocuments units; replace this with either an analytic
quadratic arc-length computation for the parabola defined by spawnTile →
separateDst and the apex (preferred) or, if you want a minimal change, pass a
coarser increment (e.g. 8 or 16) into UniversalPathFinding.Parabola to
drastically reduce allocations, then sum segments as before and keep the
fallback to this.mg.config().defaultNukeSpeed(); also update the comment to say
"tiles" (not "px") and mention the chosen sampling increment or the analytic
formula used.

90-95: The speed-equals-increment trick is correct, but consider optimizing the path length calculation.

The mechanism works as intended: computeFixedTimeSpeed() calculates speed = pathLength / 75, then each tick() call advances by that speed distance. With DistanceBasedBezierCurve.increment() accumulating distance (not counting points), 75 iterations reach exactly pathLength, yielding the fixed 75-tick flight time.

However, computing a full fine-grained path (increment: 1) every launch and summing Euclidean distances is expensive for distant targets. For a pure parabola, arc length can be derived analytically or sampled with coarser steps, reducing work significantly.

Also, the comment says "~1 px apart"—the units are tiles, not pixels.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/execution/MIRVExecution.ts` around lines 90 - 95, The current
computeFixedTimeSpeed() computes pathLength by generating a fine-grained
DistanceBasedBezierCurve (increment: 1) via UniversalPathFinding.Parabola and
summing distances, which is expensive for distant targets; change
computeFixedTimeSpeed() (or the path length calculation it calls) to avoid full
1-unit sampling—either compute the parabola arc length analytically for the
parabola parameters used, or sample with a coarser step and optionally refine
(e.g., Simpson/gauss integration or adaptive sampling) to approximate length
much faster; keep the interface so this.speed and the Parabola path finder still
use the computed speed, and update the comment that units are tiles (not
pixels).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/core/execution/MIRVExecution.ts`:
- Around line 121-137: computeFixedTimeSpeed currently builds a dense Parabola
path with UniversalPathFinding.Parabola(this.mg, { increment: 1 }) and walks
every segment to compute arc length (using spawnTile, separateDst and
fixedFireTicks), which is wasteful and misdocuments units; replace this with
either an analytic quadratic arc-length computation for the parabola defined by
spawnTile → separateDst and the apex (preferred) or, if you want a minimal
change, pass a coarser increment (e.g. 8 or 16) into
UniversalPathFinding.Parabola to drastically reduce allocations, then sum
segments as before and keep the fallback to this.mg.config().defaultNukeSpeed();
also update the comment to say "tiles" (not "px") and mention the chosen
sampling increment or the analytic formula used.
- Around line 90-95: The current computeFixedTimeSpeed() computes pathLength by
generating a fine-grained DistanceBasedBezierCurve (increment: 1) via
UniversalPathFinding.Parabola and summing distances, which is expensive for
distant targets; change computeFixedTimeSpeed() (or the path length calculation
it calls) to avoid full 1-unit sampling—either compute the parabola arc length
analytically for the parabola parameters used, or sample with a coarser step and
optionally refine (e.g., Simpson/gauss integration or adaptive sampling) to
approximate length much faster; keep the interface so this.speed and the
Parabola path finder still use the computed speed, and update the comment that
units are tiles (not pixels).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5ddd293c-4629-4e50-853c-c13e939469c0

📥 Commits

Reviewing files that changed from the base of the PR and between 32a254b and 6379cc4.

📒 Files selected for processing (1)
  • src/core/execution/MIRVExecution.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 25, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/core/execution/MIRVExecution.ts (2)

29-29: Optional: hoist fixedFireTicks to game config.

75 ticks is the new fixed flight duration. If balance tuning is expected (similar to other MIRV constants), exposing it via this.mg.config() would make tuning easier without code changes. Inline is fine for now.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/execution/MIRVExecution.ts` at line 29, Hoist the magic constant
`fixedFireTicks` out of the class and into game config: replace the inline
`private fixedFireTicks = 75` in `MIRVExecution` with a value read from
`this.mg.config()` (e.g., `this.mg.config('mirv.fixedFireTicks')`) with a
default of 75, and add that key to the config defaults/typing; update any code
that references `fixedFireTicks` to use the new config-backed value so tuning
can be done without code changes.

121-137: Approach is sound; consider a warn on the fallback path.

Sampling the parabola at increment: 1 and summing Euclidean segment lengths is a fine, simple way to estimate arc length, and length / fixedFireTicks correctly turns that into a per-tick step. The ?? [] plus length <= 0 guard is robust against an empty/missing path.

One minor point: when the fallback to defaultNukeSpeed() triggers, the MIRV silently reverts to the old variable-time behavior, which is exactly the thing this PR is trying to remove. A short console.warn there would make any future regression visible without changing behavior.

🪵 Suggested fallback log
     if (length <= 0) {
+      console.warn(
+        "MIRV: failed to measure parabola path length; falling back to default speed",
+      );
       return this.mg.config().defaultNukeSpeed();
     }
     return length / this.fixedFireTicks;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/execution/MIRVExecution.ts` around lines 121 - 137, In
computeFixedTimeSpeed(), detect when the measured path length is <= 0 and before
returning this.mg.config().defaultNukeSpeed(), emit a warning (e.g.,
console.warn) indicating the parabola path measurement failed and you're falling
back to defaultNukeSpeed; reference the UniversalPathFinding.Parabola(...)
result, the inputs this.spawnTile and this.separateDst, and this.fixedFireTicks
in the message so future regressions are easier to trace.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/core/execution/MIRVExecution.ts`:
- Line 29: Hoist the magic constant `fixedFireTicks` out of the class and into
game config: replace the inline `private fixedFireTicks = 75` in `MIRVExecution`
with a value read from `this.mg.config()` (e.g.,
`this.mg.config('mirv.fixedFireTicks')`) with a default of 75, and add that key
to the config defaults/typing; update any code that references `fixedFireTicks`
to use the new config-backed value so tuning can be done without code changes.
- Around line 121-137: In computeFixedTimeSpeed(), detect when the measured path
length is <= 0 and before returning this.mg.config().defaultNukeSpeed(), emit a
warning (e.g., console.warn) indicating the parabola path measurement failed and
you're falling back to defaultNukeSpeed; reference the
UniversalPathFinding.Parabola(...) result, the inputs this.spawnTile and
this.separateDst, and this.fixedFireTicks in the message so future regressions
are easier to trace.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ba5ab5cf-0eed-49ad-b1b1-77a91b890736

📥 Commits

Reviewing files that changed from the base of the PR and between 6379cc4 and 47acd30.

📒 Files selected for processing (1)
  • src/core/execution/MIRVExecution.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

1 participant