Files
Battle-for-Tismo/tests/economy_model_test.js
adrien d5305da449 Gave the world a simple economy: one pool per nation and a global market
The simple economy is now the default and the old simulation is frozen as the hard model, selectable per game. A nation keeps one resource pool available in every region, fed by public production works rather than private per-region stores, and construction and training begin at once, paced by the region's spare power instead of gathering materials over days.

Money is euro only: a per-inhabitant daily tax funds a treasury spent in a global market, where buy and sell lots trade against the world stock and move its price. The hard economy keeps its per-nation currencies, central banks and exchange rates.

Simple games skip the price-settling warmup and economic migration; the snapshot carries the economy model, the market and the pool figures the client draws. Added the matching test suites and the design briefs under docs/simple_economy.
2026-09-25 14:58:49 +02:00

141 lines
5.0 KiB
JavaScript

// The economy-model switch introduced by 01-decoupling-hard-economy.md: the
// simple model is the default, the hard model is the original simulation kept
// selectable, and the snapshot tells the client which one is running.
import { TestCase } from "./framework/test_case.js";
import { GameState } from "../shared/game_state.js";
import { GameServer } from "../server/game_server.js";
import { smallConfig, withServer, SEED } from "./framework/helpers.js";
import { CIVILISATIONS } from "../shared/data.js";
function civs() {
return ["france", "britain"].map((id) => CIVILISATIONS.find((c) => c.id === id));
}
// A configured state on the small fixture map. `options` is passed straight to
// `configure`, so a test can pin the model or leave it out entirely.
function stateWithModel(options) {
const state = new GameState();
state.mapConfig = smallConfig();
state.configure(civs(), SEED, options);
return state;
}
function makeServer(setup = {}) {
const network = { getPeerIds: () => [], send: () => {} };
const server = new GameServer(network);
server.configureGame({
seed: SEED,
player_civ: 0,
civilisations: ["france", "britain"],
mapConfig: smallConfig(),
...setup,
});
return server;
}
export class EconomyModelTest extends TestCase {
test_a_new_state_defaults_to_the_simple_economy() {
const state = new GameState();
this.assertEqual(state.economyModel, "simple", "the constructor defaults to simple");
this.assertTrue(state.isSimpleEconomy(), "and reports it");
}
test_configure_without_options_defaults_to_simple() {
const state = new GameState();
state.mapConfig = smallConfig();
// The two-argument call is the existing one and must keep working.
state.configure(civs(), SEED);
this.assertEqual(state.economyModel, "simple");
this.assertTrue(state.isSimpleEconomy());
}
test_configure_accepts_each_known_model_verbatim() {
this.assertEqual(stateWithModel({ economyModel: "simple" }).economyModel, "simple");
const hard = stateWithModel({ economyModel: "hard" });
this.assertEqual(hard.economyModel, "hard");
this.assertFalse(hard.isSimpleEconomy());
}
test_unknown_models_coerce_to_simple() {
for (const value of ["", "SIMPLE", "rich", null, 42, {}]) {
const state = stateWithModel({ economyModel: value });
this.assertEqual(state.economyModel, "simple", `coerced from ${JSON.stringify(value)}`);
this.assertTrue(state.isSimpleEconomy(), "and is treated as simple");
}
}
test_the_snapshot_carries_the_model() {
this.assertEqual(stateWithModel({}).snapshot(0).economyModel, "simple");
this.assertEqual(stateWithModel({ economyModel: "hard" }).snapshot(0).economyModel, "hard");
}
test_a_simple_game_runs_a_day_without_throwing() {
const state = stateWithModel({ economyModel: "simple" });
for (let hour = 0; hour < 24; hour++) state.advanceHour();
this.assertEqual(state.totalHours, 24, "the day advanced");
}
test_a_hard_game_runs_a_day_without_throwing() {
const state = stateWithModel({ economyModel: "hard" });
for (let hour = 0; hour < 24; hour++) state.advanceHour();
this.assertEqual(state.totalHours, 24, "the day advanced");
}
test_the_server_defaults_to_simple() {
this.assertEqual(makeServer().state.economyModel, "simple");
}
test_the_server_accepts_the_hard_model() {
this.assertEqual(makeServer({ economyModel: "hard" }).state.economyModel, "hard");
}
test_the_server_coerces_an_unknown_model() {
this.assertEqual(makeServer({ economyModel: "bogus" }).state.economyModel, "simple");
}
async test_start_server_forwards_the_model() {
await withServer(async (port, server) => {
this.assertEqual(server.gameServer.state.economyModel, "hard");
}, { economyModel: "hard" });
}
// 04-warmup.md: the simple model never announces or runs a settle, and says
// so when `--warmup` was asked for explicitly; the hard model still settles.
async test_start_server_skips_the_settle_for_simple() {
const lines = [];
await withServer(async (port, server) => {
this.assertEqual(server.gameServer.settledDays, 0, "no settle ran");
this.assertEqual(server.gameServer.state.totalHours, 0, "the clock never advanced");
}, {
economyModel: "simple",
warmupDays: 150,
warmupExplicit: true,
log: (message) => lines.push(message),
});
this.assertFalse(
lines.some((line) => /settling the world/.test(line)),
"no settle was announced"
);
this.assertTrue(
lines.some((line) => /needs no warm-up/.test(line)),
"the ignored flag is reported rather than dropped in silence"
);
}
async test_start_server_still_settles_for_hard() {
const lines = [];
await withServer(async (port, server) => {
this.assertGreater(server.gameServer.settledDays, 0, "the hard settle ran");
}, {
economyModel: "hard",
warmupDays: 2,
log: (message) => lines.push(message),
});
this.assertTrue(
lines.some((line) => /settling the world/.test(line)),
"the settle is announced"
);
}
}