Files
OpenFrontIO/tests/core/GameRunner.test.ts
T
Evan Pelle 43d07ca85f refactor: rename core-public -> engine-public; fold helpers in; drop shared
Tighten the package model around the determinism invariant: everything the
engine imports must itself be deterministic.

- Rename core-public -> engine-public (package, dir, `engine-public/*` alias,
  all 118 import sites). It is the deterministic public surface the engine
  depends on and that client/server also use.
- Move the pure formatting helpers (renderNumber/renderTroops) from shared
  into engine-public/format — the engine uses them and they're deterministic.
- Drop the now-empty shared package (and its alias). `shared` was intended for
  client/server-shared, engine-forbidden code; there is none yet, so it is
  removed and can be re-added when such code appears.

Final graph: engine-public (leaf) <- engine <- {client, server};
client/server also -> engine-public.

Verified: tsc clean; 1364 + 65 tests pass; prod build succeeds; engine-public
is a leaf; engine has no client/server imports. Lockfile regenerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 20:32:32 +00:00

106 lines
3.2 KiB
TypeScript

import { NationExecution } from "engine/execution/NationExecution";
import { SpawnExecution } from "engine/execution/SpawnExecution";
import { Cell, Nation, PlayerInfo, PlayerType } from "engine/game/Game";
import { GameConfig, GameID } from "engine-public/Schemas";
import { setup } from "../util/Setup";
import { executeTicks } from "../util/utils";
const gameID: GameID = "test_game_id";
async function createTestGame(
randomSpawn: boolean,
nationCells: { x: number; y: number }[],
) {
const game = await setup(
"plains",
{ randomSpawn } as Partial<GameConfig>,
[],
undefined,
undefined,
false,
);
const humanInfo = new PlayerInfo(
"human",
PlayerType.Human,
"client_1",
"human_id",
);
game.addPlayer(humanInfo);
const nations: { info: PlayerInfo; nation: Nation }[] = [];
for (let i = 0; i < nationCells.length; i++) {
const info = new PlayerInfo(
nationCells.length === 1 ? "TestNation" : `Nation${i}`,
PlayerType.Nation,
null,
nationCells.length === 1 ? "nation_id" : `nation_${i}`,
);
const nation = new Nation(
new Cell(nationCells[i].x, nationCells[i].y),
info,
);
game.addPlayer(info);
nations.push({ info, nation });
}
return { game, humanInfo, nations };
}
describe("Nation spawn ordering with random spawn", () => {
test("nation spawns in singleplayer with random spawn", async () => {
const { game, humanInfo, nations } = await createTestGame(true, [
{ x: 50, y: 50 },
]);
// Mirror GameRunner.init() ordering: nation first, then human.
game.addExecution(new NationExecution(gameID, nations[0].nation));
game.addExecution(
new SpawnExecution(gameID, game.player(humanInfo.id).info()),
);
executeTicks(game, 4);
expect(game.player(humanInfo.id).hasSpawned()).toBe(true);
expect(game.player(nations[0].info.id).hasSpawned()).toBe(true);
expect(game.player(nations[0].info.id).isAlive()).toBe(true);
});
test("multiple nations spawn in singleplayer with random spawn", async () => {
const cells = Array.from({ length: 5 }, (_, i) => ({
x: 20 + i * 15,
y: 20 + i * 15,
}));
const { game, humanInfo, nations } = await createTestGame(true, cells);
// Nation executions first (mirrors GameRunner.init()).
for (const { nation } of nations) {
game.addExecution(new NationExecution(gameID, nation));
}
// Human spawn execution second.
game.addExecution(
new SpawnExecution(gameID, game.player(humanInfo.id).info()),
);
executeTicks(game, 8);
expect(game.player(humanInfo.id).hasSpawned()).toBe(true);
for (const { info } of nations) {
const player = game.player(info.id);
expect(player.hasSpawned()).toBe(true);
expect(player.isAlive()).toBe(true);
}
});
test("nation spawns in singleplayer without random spawn", async () => {
const { game, nations } = await createTestGame(false, [{ x: 50, y: 50 }]);
game.addExecution(new NationExecution(gameID, nations[0].nation));
executeTicks(game, 4);
expect(game.player(nations[0].info.id).hasSpawned()).toBe(true);
expect(game.player(nations[0].info.id).isAlive()).toBe(true);
});
});