Files
Battle-for-Tismo/tests/stockpile_test.js
adrien 4600b16399 Stockpiled a month of every material and cached the trade walks
Food, steel, luxury and high-tech now share one flat month-long reserve. A region keeps bidding for the shortfall while its store is thin, so a war that empties a pantry refills it from the grid instead of starving; the harvest is served before the city load, unmet food bids the price up, the army's rations count as demand, and a converter that cannot cover its power bill idles instead of making a loss.

The delivery graph walks sea lanes any distance, so its walks are cached across days behind a road/railway/work/territory stamp and only dropped by a wartime unit move. Units are indexed per tile, market access and node populations are memoised for the day, and the server can settle a fresh world before serving (--warmup, 150 days by default).

Roadmap records the remaining follow-ups -- the politics/migration hot path and the luxury/high-tech price plateau -- and that freight's heavy energy draw is intended.
2026-09-23 15:59:28 +02:00

67 lines
3.2 KiB
JavaScript

import { TestCase } from "./framework/test_case.js";
import { smallState, cityOf, grantBuilding } from "./framework/helpers.js";
import { RESOURCE_IDS, RESOURCE_RULES } from "../shared/data.js";
import { key } from "../shared/hex.js";
import { steelNeedPerDay, luxuryNeedPerDay, stockpileTarget } from "../shared/resources.js";
// Puts a reachable producer for `resource` on an adjacent owned land tile, with
// a bottomless stock, so a city can always top its reserve up. `index` picks a
// different neighbour for each producer.
function placeProducer(state, city, improvementId, resource, amount, index = 0) {
const owned = state.topology.neighbours(city.coords.x, city.coords.y)
.filter((n) => state._isLand(n) && state.civAt(n) === city.civ && !state.cityAt(n));
const neighbour = owned[index];
const k = key(neighbour.x, neighbour.y);
if (!state.roads.has(k) && !state.railways.has(k)) state.roads.add(k);
state.tileImprovements.set(k, improvementId);
state.tileImprovementOwner.set(k, city.civ);
state.tileImprovementHp.set(k, 1000);
state._ensureResourceStock(k)[resource] = amount;
return k;
}
function marketOf(state) {
return new Map(RESOURCE_IDS.map((id) => [
id, { supply: 0, demand: 0, price: state.getResourcePrice(id) },
]));
}
export class StockpileTest extends TestCase {
test_a_month_of_every_material_is_one_flat_horizon() {
this.assertApprox(stockpileTarget(100), 100 * RESOURCE_RULES.stockpileDays);
this.assertApprox(stockpileTarget(0), 0);
}
test_regions_stockpile_a_month_of_steel_and_luxury() {
const state = smallState(["france"], 12345, { citiesPerCiv: 1 });
const city = cityOf(state, 0);
const population = state.getCityEconomy(city).population;
const store = state.getCityResourceStock(city);
// Below the target but enough to cover today: one day should top it up.
store.steel = steelNeedPerDay(population) * 5;
store.luxury = luxuryNeedPerDay(population) * 5;
placeProducer(state, city, "steel_mill", "steel", 1e9, 0);
placeProducer(state, city, "gold_mine", "luxury", 1e9, 1);
state._consumeCityResources(new Map(), marketOf(state));
this.assertApprox(store.steel, stockpileTarget(steelNeedPerDay(population)), 1e-3);
this.assertApprox(store.luxury, stockpileTarget(luxuryNeedPerDay(population)), 1e-3);
}
test_a_region_reserves_a_month_of_high_tech_against_its_share_of_the_burn() {
const state = smallState(["france"], 12345, { citiesPerCiv: 1 });
const city = cityOf(state, 0);
grantBuilding(state, city, "research_lab");
placeProducer(state, city, "chip_foundry", "hightech", 1e9);
const population = state.getCityEconomy(city).population;
const burn = state.getCivUpkeepResources(0).hightech;
this.assertGreater(burn, 0, "the lab and foundry wear high-tech");
const need = (burn / state.getPlayerPopulation(0)) * population;
const store = state.getCityResourceStock(city);
store.hightech = 0;
const market = marketOf(state);
state._consumeCityResources(new Map(), market);
this.assertApprox(store.hightech, stockpileTarget(need), 1e-3);
this.assertApprox(market.get("hightech").demand, need, 1e-3, "the appetite is priced in");
}
}