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.
121 lines
5.3 KiB
JavaScript
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");
|
|
}
|
|
}
|