Files
Battle-for-Tismo/tests/simple_pool_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

121 lines
5.3 KiB
JavaScript

// The global resource pool and public production introduced by
// 02-resource-pool-and-production.md: one pool per player, every producer
// feeding it, and no per-tile store or private-agent accounting in the simple
// model. The hard economy must keep its per-region stores.
//
// Note on the hard fixtures: `smallState` now pins `economyModel: "hard"` so
// the historical suites keep exercising the model they were written against;
// these tests use `simpleState` for the simple model.
import { TestCase } from "./framework/test_case.js";
import { simpleState, smallState, cityOf } from "./framework/helpers.js";
import { SIMPLE_ECONOMY, STORABLE_RESOURCE_IDS } from "../shared/data.js";
export class SimplePoolTest extends TestCase {
test_a_simple_game_has_one_seeded_pool_per_civ() {
const state = simpleState();
this.assertEqual(state.resourcePools.size, state.civilisations.length, "one pool per nation");
for (let civ = 0; civ < state.civilisations.length; civ++) {
const pool = state.getResourcePool(civ);
for (const id of STORABLE_RESOURCE_IDS) {
this.assertEqual(
pool[id],
SIMPLE_ECONOMY.startingPool[id],
`${id} in civ ${civ}'s pool`
);
}
}
}
test_a_simple_game_keeps_no_per_tile_stock() {
const state = simpleState();
state.applyStartingScenario();
for (let hour = 0; hour < 24; hour++) state.advanceHour();
this.assertEqual(state.resourceStock.size, 0, "nothing is written to the per-tile store");
}
test_a_city_stock_is_a_view_of_its_owners_pool() {
const state = simpleState();
const pool = state.getResourcePool(0);
const city = cityOf(state, 0);
this.assertTrue(state.getCityResourceStock(city) === pool, "the city reads the pool");
state.getCityResourceStock(city).steel = 1234;
this.assertEqual(pool.steel, 1234, "an edit through the city view lands in the pool");
}
test_the_hard_game_keeps_per_tile_stock() {
const state = smallState();
this.assertEqual(typeof state.getResourcePool, "undefined", "no pool API on the hard model");
this.assertTrue(state.resourceStock.size > 0, "hard cities and works hold their own stock");
}
test_the_public_producers_fill_the_pool() {
const state = simpleState();
// Isolate production from the day's consumption and grid, so whatever the
// pool gains is exactly the public works' and the land's output.
state._consumeCityResources = () => {};
state._settleUpkeep = () => 1;
state._applyWorksMaintenance = () => {};
state._buildEnergyLedger = () => new Map();
const pool = state.getResourcePool(0);
for (const id of STORABLE_RESOURCE_IDS) pool[id] = 0;
state._tickResources();
const produced = STORABLE_RESOURCE_IDS.filter((id) => pool[id] > 0);
this.assertNotEmpty(produced, `a public producer fed the pool (got ${JSON.stringify(pool)})`);
}
test_procurement_is_a_pool_draw_without_a_supplier_search() {
const state = simpleState();
const pool = state.getResourcePool(0);
pool.steel = 1000;
let searched = false;
state._nearbySuppliers = () => { searched = true; return []; };
state._resourceNodes = () => { searched = true; return []; };
const city = cityOf(state, 0);
const drawn = state._drawFromNearest(0, city.coords, { steel: 400, hightech: 0 });
this.assertEqual(drawn.steel, 400, "the pool supplied the draw");
this.assertEqual(pool.steel, 600, "and was debited exactly");
this.assertFalse(searched, "no supplier search ran");
const rest = state._drawFromNearest(0, city.coords, { steel: 1000, hightech: 0 });
this.assertEqual(rest.steel, 600, "a shortfall takes only what is there");
this.assertEqual(pool.steel, 0, "the pool is empty, not negative");
}
test_a_pool_shortfall_leaves_the_need_unmet() {
const state = simpleState();
const pool = state.getResourcePool(0);
pool.steel = 100;
const missing = state._drawFromCities(0, "steel", 500);
this.assertEqual(missing, 400, "the unmet amount is reported");
this.assertEqual(pool.steel, 0);
const city = cityOf(state, 0);
this.assertEqual(
state._procureFromNeighbours(city, "steel", 400, {}, state._resourceNodes(), {}),
0,
"no bid is placed for the shortfall"
);
}
test_the_summary_reports_the_pool_and_its_public_production() {
const state = simpleState();
const summary = state.getCivResourceSummary(0);
const pool = state.getResourcePool(0);
for (const id of STORABLE_RESOURCE_IDS) {
this.assertEqual(summary.stock[id], pool[id], `${id} stock is the pool`);
}
this.assertTrue(summary.production.food > 0, "the land's harvest is public production");
}
test_a_simple_day_moves_the_pool_without_throwing() {
const state = simpleState();
state.applyStartingScenario();
const before = { ...state.getResourcePool(0) };
for (let hour = 0; hour < 24; hour++) state.advanceHour();
const after = state.getResourcePool(0);
const changed = STORABLE_RESOURCE_IDS.some((id) => before[id] !== after[id]);
this.assertTrue(changed, "the day's production and consumption moved the pool");
this.assertEqual(state.getPrivateBuildingCash(0), 0, "no private building cash is kept");
this.assertEmpty(state.getProductionAgents(0), "no private production agents exist");
}
}