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