From 4d924ef8d1c4bef6c9c40e39c66532454c7579a1 Mon Sep 17 00:00:00 2001 From: Restart2008 Date: Sat, 25 Oct 2025 20:51:30 -0700 Subject: [PATCH] Fix: Standardize MIRV travel time Addresses player report regarding unfair and unintuitive MIRV launch delays. MIRV travel time is now fixed at 5 ticks, independent of click location. - Modified MIRVExecution.ts to dynamically calculate speed based on a fixed travel time. - Added MIRVExecution.test.ts to verify the fixed travel time. --- src/core/execution/MIRVExecution.ts | 6 +- tests/core/executions/MIRVExecution.test.ts | 69 +++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 tests/core/executions/MIRVExecution.test.ts diff --git a/src/core/execution/MIRVExecution.ts b/src/core/execution/MIRVExecution.ts index 7f5c1687f..e1d085745 100644 --- a/src/core/execution/MIRVExecution.ts +++ b/src/core/execution/MIRVExecution.ts @@ -22,6 +22,7 @@ export class MirvExecution implements Execution { private mirvRange = 1500; private warheadCount = 350; + private MIRV_FIXED_TRAVEL_TIME = 5; // Ticks private random: PseudoRandom; @@ -43,7 +44,7 @@ export class MirvExecution implements Execution { this.mg = mg; this.pathFinder = new ParabolaPathFinder(mg); this.targetPlayer = this.mg.owner(this.dst); - this.speed = this.mg.config().defaultNukeSpeed(); + // Record stats this.mg.stats().bombLaunch(this.player, this.targetPlayer, UnitType.MIRV); @@ -75,6 +76,9 @@ export class MirvExecution implements Execution { const y = Math.max(0, this.mg.y(this.dst) - 500) + 50; this.separateDst = this.mg.ref(x, y); this.pathFinder.computeControlPoints(spawn, this.separateDst); + // Calculate speed for fixed travel time + const distance = this.mg.euclideanDist(spawn, this.separateDst); + this.speed = distance / this.MIRV_FIXED_TRAVEL_TIME; this.mg.displayIncomingUnit( this.nuke.id(), diff --git a/tests/core/executions/MIRVExecution.test.ts b/tests/core/executions/MIRVExecution.test.ts new file mode 100644 index 000000000..8186d949c --- /dev/null +++ b/tests/core/executions/MIRVExecution.test.ts @@ -0,0 +1,69 @@ +import { MirvExecution } from "../../../src/core/execution/MIRVExecution"; +import { SpawnExecution } from "../../../src/core/execution/SpawnExecution"; +import { + Game, + Player, + PlayerInfo, + PlayerType, + UnitType, +} from "../../../src/core/game/Game"; +import { TileRef } from "../../../src/core/game/GameMap"; +import { setup } from "../../util/Setup"; +import { constructionExecution, executeTicks } from "../../util/utils"; + +let game: Game; +let player: Player; + +describe("MirvExecution", () => { + beforeEach(async () => { + game = await setup("plains", { infiniteGold: true, instantBuild: true }); + const playerInfo = new PlayerInfo( + "player_id", + PlayerType.Human, + null, + "player_id", + ); + game.addPlayer(playerInfo); + + game.addExecution( + new SpawnExecution(game.player(playerInfo.id).info(), game.ref(1, 1)), + ); + + while (game.inSpawnPhase()) { + game.executeNextTick(); + } + + player = game.player("player_id"); + + constructionExecution(game, player, 1, 1, UnitType.MissileSilo); + }); + + test("MIRV should have a fixed travel time regardless of target distance", async () => { + const nearTarget = game.ref(5, 5); + const farTarget = game.ref(50, 50); + + const mirvNear = new MirvExecution(player, nearTarget); + game.addExecution(mirvNear); + const mirvFar = new MirvExecution(player, farTarget); + game.addExecution(mirvFar); + + // Execute ticks until both MIRVs have landed + let ticksNear = 0; + let ticksFar = 0; + + while (mirvNear.isActive() || mirvFar.isActive()) { + game.executeNextTick(); + if (mirvNear.isActive()) { + ticksNear++; + } + if (mirvFar.isActive()) { + ticksFar++; + } + } + + // The travel times should be approximately the same + // Allowing for a small margin of error due to pathfinding or tick execution nuances + const tolerance = 2; // Ticks + expect(Math.abs(ticksNear - ticksFar)).toBeLessThanOrEqual(tolerance); + }); +});