Files
OpenFrontIO/tests/economy/ConstructionGold.test.ts
Aotumuri f1d162825e feat: remove spawn timer on singleplayer (#3199)
Resolves #1041 

## Description:

Remove the singleplayer spawn countdown so the game starts when the
player spawns, spawn nations immediately after player spawn, and align
game timer/max-timer timing with the new start point.

Added a singleplayer regression test for spawn-immunity timing
(GameImpl.test.ts) and updated spawn-phase loop tests to use gameType:
GameType.Public where singleplayer behavior is not under test (e.g.
MIRV/AI/Spawn/WinCheck-related suites), eliminating inSpawnPhase()
timeout hangs after the new singleplayer start logic.


https://github.com/user-attachments/assets/c07a585f-1153-490e-88ca-a91fc7ae5756

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] 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:
aotumuri
2026-05-11 12:44:44 -07:00

101 lines
3.2 KiB
TypeScript

import { ConstructionExecution } from "../../src/core/execution/ConstructionExecution";
import { NukeExecution } from "../../src/core/execution/NukeExecution";
import {
Game,
Player,
PlayerInfo,
PlayerType,
UnitType,
} from "../../src/core/game/Game";
import { setup } from "../util/Setup";
describe("Construction economy", () => {
let game: Game;
let player: Player;
let other: Player;
const builderInfo = new PlayerInfo(
"builder",
PlayerType.Human,
null,
"builder_id",
);
const otherInfo = new PlayerInfo("other", PlayerType.Human, null, "other_id");
beforeEach(async () => {
game = await setup(
"plains",
{
infiniteGold: false,
instantBuild: false,
infiniteTroops: true,
},
[builderInfo, otherInfo],
);
player = game.player(builderInfo.id);
other = game.player(otherInfo.id);
player.conquer(game.ref(0, 10));
other.conquer(game.ref(10, 10));
});
test("City charges gold once and no refund thereafter (allow passive income)", () => {
const target = game.ref(0, 10);
const cost = game.unitInfo(UnitType.City).cost(game, player);
player.addGold(cost);
expect(player.gold()).toBe(cost);
const startTick = game.ticks();
game.addExecution(new ConstructionExecution(player, UnitType.City, target));
// First tick usually initializes the execution, second tick performs build and deduction
game.executeNextTick();
game.executeNextTick();
const afterBuild = player.gold();
const ticksAfterBuild = BigInt(game.ticks() - startTick);
const passivePerTick = 100n; // DefaultConfig goldAdditionRate for humans
expect(afterBuild < cost).toBe(true); // cost was deducted
expect(afterBuild <= ticksAfterBuild * passivePerTick).toBe(true); // only passive income allowed
// Advance through construction duration
const duration = game.unitInfo(UnitType.City).constructionDuration ?? 0;
for (let i = 0; i <= duration + 2; i++) game.executeNextTick();
const finalGold = player.gold();
const ticksElapsed = BigInt(game.ticks() - startTick);
// Ensure no refund equal to cost snuck back in; only passive income accumulated
expect(finalGold < cost).toBe(true);
expect(finalGold <= ticksElapsed * passivePerTick).toBe(true);
// Structure exists and is active
expect(player.units(UnitType.City)).toHaveLength(1);
expect(
(player.units(UnitType.City)[0] as any).isUnderConstruction?.() ?? false,
).toBe(false);
});
test("MIRV gets more expensive with each launch", () => {
expect(game.config().unitInfo(UnitType.MIRV).cost(game, other)).toBe(
25_000_000n,
);
player.addGold(100_000_000n);
player.conquer(game.ref(1, 1));
player.buildUnit(UnitType.MissileSilo, game.ref(1, 1), {});
other.conquer(game.ref(10, 10));
game.addExecution(
new NukeExecution(UnitType.MIRV, player, game.ref(10, 10)),
);
game.executeNextTick(); // init
game.executeNextTick(); // create MIRV unit
game.executeNextTick();
expect(player.units(UnitType.MIRV)).toHaveLength(1);
// Price of the MIRV increases for everyone with each launch.
expect(game.config().unitInfo(UnitType.MIRV).cost(game, other)).toBe(
40_000_000n,
);
});
});