// Resources: the five material groups nations produce, store, trade and // consume. Everything is expressed in one canonical unit per resource so the // simulation never has to convert: energy in kWh, steel and food in tonnes, // luxury in carats and high-tech in whole units. Display formatting converts to // the friendly unit (MWh, Mt, kt, ...). The market prices and conversion ratios // here are the main tuning knobs for the whole resource economy; the per-person // needs and the energy-per-material figures come straight from the roadmap. // Energy is stored and moved as kWh. These keep the catalogue readable. export const KWH_PER_MWH = 1_000; export const KWH_PER_GWH = 1_000_000; // One tonne of any material counts as this much energy on the wire, so material // deliveries and energy deliveries share one transport-cost formula. export const ENERGY_PER_MATERIAL_TONNE = 10 * KWH_PER_GWH; // 10 GWh/t export const RESOURCES = [ { id: "energy", name: "Energy", unit: "kWh", icon: "icon_electricity.svg", colour: "#ffd633", // Mass of one canonical unit, so a delivery leg can charge transport per // tonne whatever the good is: 10 GWh of energy weighs a tonne. tonnesPerUnit: 1 / ENERGY_PER_MATERIAL_TONNE, // Energy is a flow, not a stock: it comes from the grid and is spent the // same day. Only cities and resource buildings can hold the other four. storable: false, }, { id: "steel", colour: "#9aa7b8", name: "Steel", unit: "t", icon: "icon_steel.svg", tonnesPerUnit: 1, storable: true, }, { id: "food", colour: "#7ed957", name: "Food", unit: "t", icon: "icon_food.svg", tonnesPerUnit: 1, storable: true, }, { id: "luxury", colour: "#d98cff", name: "Luxury", unit: "ct", icon: "icon_luxury.svg", // A carat is 0.2 g, so 200,000 ct weigh a tonne. tonnesPerUnit: 0.0002 / 1000, storable: true, }, { id: "hightech", colour: "#5cc8ff", name: "High-tech", unit: "u", icon: "icon_hightech.svg", // A high-tech unit is a crate of components, about a kilogram. tonnesPerUnit: 0.001, storable: true, }, ]; export const RESOURCE_IDS = RESOURCES.map((r) => r.id); export const RESOURCE_INDEX = Object.fromEntries(RESOURCES.map((r, i) => [r.id, i])); // The four stockable resources, in catalogue order. Energy is handled as a flow. export const STORABLE_RESOURCE_IDS = RESOURCES.filter((r) => r.storable).map((r) => r.id); export function resourceById(id) { return RESOURCES[RESOURCE_INDEX[id]] || null; } // Global market prices, in euro per canonical unit. These are what a city pays // when it cannot meet a need from its own store or a neighbour. Energy is // deliberately cheap: the roadmap's energy-per-material ratios are large, so a // high electric price would make every material's energy bill dwarf its value. // // The five figures are the old nominal prices raised roughly seventeen-fold so // the production-based GDP (what a nation makes times these prices) lands in // the same range as the money economy it replaced. The scaling is what makes a // fresh nation's food output alone read as a plausible per-capita GDP rather // than a few hundred euro; construction, upkeep and imports are priced off the // same figures. export const RESOURCE_MARKET_BASE = { energy: 0.051, // per kWh steel: 10_200, // per tonne food: 5_100, // per tonne luxury: 1_020, // per carat hightech: 13_600, // per unit }; // How strongly a day's world-wide imbalance moves the price, and how far it may // drift from the base. The market is a smoothing device, not a real auction: // it lets a shortage raise the cost of imports and a glut cheapen them. export const RESOURCE_MARKET = { base: RESOURCE_MARKET_BASE, adjustment: 0.2, minMultiplier: 0.5, maxMultiplier: 1.8, }; // A region whose food store runs dry and cannot feed its people is in famine: // the whole world is told the day it begins, and the region loses this share of // its people every day until food comes back. export const FAMINE = { dailyMortality: 0.01, }; // The physical rules of the resource economy. Every per-person figure and every // energy-per-material ratio below is fixed by the design; the prices above are // what we tune against them. export const RESOURCE_RULES = { // Each person eats 1 kg of food and consumes 1 kg of steel a day. Both are // canonical tonnes, so 1 kg is 0.001 t. foodPerPersonPerDay: 0.001, steelPerPersonPerDay: 0.001, // Luxuries are rarer: 0.01 carat a person a day. luxuryPerPersonPerDay: 0.01, // Cities aim to hold 130% of a month's food before the month begins. foodMonthlyBuffer: 1.3, daysPerMonth: 30, // A city's baseline electric appetite, in kWh per euro of yearly GDP. This is // the "kWh per euro of GDP" economic metric; the top bar does not show it. gdpEnergyIntensity: 0.15, // Energy the material producers consume to make one canonical unit. energyPerFoodTonne: 1 * KWH_PER_MWH, // 1 MWh per tonne // Food a region can force from energy beyond its own harvest, in greenhouses // and synthesis. The energy for `x` extra tonnes a day is this coefficient // times `x^2`, so each further tonne costs more energy than the last and a // region grows only while its marginal energy beats the market price of food. foodSynthesisQuadratic: 20, // kWh per tonne squared energyPerSteelTonne: 20 * KWH_PER_MWH, // 20 MWh per tonne energyPerLuxuryCarat: { gold_mine: 10 * KWH_PER_MWH, // 10 MWh per carat diamond_mine: 5 * KWH_PER_MWH, // 5 MWh per carat }, energyPerHighTechUnit: 100 * KWH_PER_MWH, // 100 MWh per unit // Science buildings spend one high-tech unit for every research point. highTechPerResearchPoint: 1, // Delivering one tonne of material one tile, in kWh of transport energy. // Roads use trucks, railways trains; without a road a truck burns the // off-road figure times the tile's movement cost, and sea legs go by ship. deliveryEnergy: { truckRoad: 10 * KWH_PER_MWH, // 10 MWh per tonne per tile truckOffRoad: 50 * KWH_PER_MWH, // 50 MWh per tonne per tile, times cost train: 1 * KWH_PER_MWH, // 1 MWh per tonne per tile ship: 50, // 50 kWh per tonne per tile }, // Steel every construction spends per euro of its money cost. At 1/10000 a // 100 M building needs 10,000 t of steel. steelPerBudgetEuro: 1 / 10_000, // The electricity a tile work draws as it is built, per euro of its money // cost. It is roughly the power embodied in the steel it spends: steel costs // 20 MWh a tonne and 1 t per 10,000 euro, so building is powered as well as // supplied. Tile improvements pay this up front. constructionEnergyPerBudgetEuro: 2, // kWh per euro // High-tech a science or advanced building needs to be raised, per level. constructionHighTechPerLevel: 100, // Upkeep is materials, not money: every building, unit and improvement wears // out and the state buys what it needs. Each figure scales with the thing's // money value -- a building level's cost, a unit's training cost -- so one set // of coefficients tunes the whole load. `advanced` things (science, air, // radar, chips, fusion) also need high-tech. upkeepSteelPerEuro: 5e-7, // t per euro of value per day upkeepEnergyPerEuro: 1e-3, // kWh per euro of value per day upkeepHighTechPerEuro: 2e-6, // u per euro of value per day (advanced only) // Transport is the exception: its build cost is enormous but its upkeep is a // per-tile wear figure, not a share of that cost. transportUpkeepSteel: { road: 0.02, railway: 0.1 }, // Combat is the other exception: guns and aircraft are cheap to keep parked // and expensive to fire. These are the materials one use spends. Air defense // fires per hostile aircraft pass, artillery per hourly shelling, an aircraft // per sortie. combatConsumption: { airDefenseVolley: { steel: 20, hightech: 5 }, artilleryVolley: { steel: 1, hightech: 0 }, airStrike: { steel: 100, hightech: 20 }, }, // Infantry eat. A unit carries this many soldiers, and each is fed like a // civilian: double rations in combat, triple while wounded and resting. // Training or healing a unit draws its soldiers out of the region's people. unitPopulation: 1_000, infantryCombatFoodMultiplier: 2, infantryRestingFoodMultiplier: 3, // Supply. A unit carries this many days of its own food. At home, or in // foreign territory with a clear route to an allied city, it tops up from // that city; cut off, the days run down and the unit starts to starve. unitSupplyDays: 50, starvationHpFractionPerDay: 0.1, // Repairs: damage leaves a materials debt the owner pays down, and only then // does the structure recover. A razed city level costs what it cost to build. repair: { steelPerTileHp: 0.05, // t to restore one hit point of a tile work tileHpPerDay: 60, // hit points a funded repair restores each day steelPerDamage: 0.5, // t of debt per point of structure damage unitSteelPerHp: 0.01, // t to heal one hit point of a ground or naval unit unitHighTechPerHp: 0.002, // u to heal one hit point of an advanced unit }, // Fraction of a missing need that turns into a popularity penalty per day, // and the same for a GDP-per-capita shortfall, so shortages are felt. shortagePopularityPenalty: 2, shortageGdpPenalty: 0.05, // Food comes from the land, not from the number of people standing on it: // every worked tile grows `foodPerTilePerDay` tonnes a day scaled by its // terrain, so moving people between regions changes only income per head. foodPerTilePerDay: 100, // How fertile each terrain is. A worked tile grows its base output scaled by // this, so forests and plains feed cities while deserts and ice do not. tileFoodFactor: { Forest: 1.4, Hills: 0.7, Land: 1.2, Desert: 0.1, Mountain: 0.2, Tundra: 0.2, Ice: 0, }, }; // How far a city will look for a neighbour's surplus before falling back to the // global market. export const RESOURCE_TRADE_RADIUS = 8; // Resource-producing buildings. Built only outside cities, they occupy a land // tile like a military improvement and are managed through the same tile-works // queue. A power plant produces energy; every other building consumes energy // and produces its material. `outputPerDay` is in the resource's canonical // unit, `energyPerDay` in kWh (positive means consumed, negative produced). export const RESOURCE_BUILDINGS = [ { id: "coal_power_plant", name: "Coal power plant", description: "Burns coal to make 250 GWh of electricity a day. Cheap to raise, but the fuel bill never stops.", resource: "energy", outputPerDay: 250 * KWH_PER_GWH, fuelEnergyPerDay: 250 * KWH_PER_GWH * 0.25, energyPerDay: 0, buildCost: 500_000_000, maintenance: 20_000, icon: "icon_coal_power_plant.svg", }, { id: "natural_gas_power_plant", name: "Natural gas power plant", description: "The cleanest fossil plant: 300 GWh a day for a higher fuel bill.", resource: "energy", outputPerDay: 300 * KWH_PER_GWH, fuelEnergyPerDay: 300 * KWH_PER_GWH * 0.25, energyPerDay: 0, buildCost: 700_000_000, maintenance: 25_000, icon: "icon_gas_power_plant.svg", }, { id: "oil_power_plant", name: "Oil power plant", description: "Burns oil for 270 GWh of electricity a day.", resource: "energy", outputPerDay: 270 * KWH_PER_GWH, fuelEnergyPerDay: 270 * KWH_PER_GWH * 0.25, energyPerDay: 0, buildCost: 600_000_000, maintenance: 22_000, icon: "icon_oil_power_plant.svg", }, { id: "solar_power_plant", name: "Solar power plant", description: "A field of panels producing a steady 30 GWh a day with no fuel and no upkeep.", resource: "energy", outputPerDay: 30 * KWH_PER_GWH, energyPerDay: 0, renewable: true, buildCost: 300_000_000, maintenance: 0, icon: "icon_solar_power_plant.svg", }, { id: "geothermal_plant", name: "Geothermal plant", description: "Taps the earth's heat for 60 GWh a day of free electricity.", resource: "energy", outputPerDay: 60 * KWH_PER_GWH, energyPerDay: 0, renewable: true, buildCost: 800_000_000, maintenance: 0, icon: "icon_geothermal_plant.svg", }, { id: "wind_turbines", name: "Wind turbines", description: "A hilltop wind farm making 25 GWh a day for free.", resource: "energy", outputPerDay: 25 * KWH_PER_GWH, energyPerDay: 0, renewable: true, buildCost: 200_000_000, maintenance: 0, icon: "icon_wind_turbines.svg", }, { id: "offshore_wind_turbines", name: "Offshore wind turbines", description: "Turbines out at sea making 50 GWh a day. Needs a coastal tile.", resource: "energy", outputPerDay: 50 * KWH_PER_GWH, energyPerDay: 0, renewable: true, coastal: true, buildCost: 500_000_000, maintenance: 0, icon: "icon_offshore_wind_turbines.svg", }, { id: "fusion_power_plant", advanced: true, name: "Fusion power plant", description: "A late-game reactor producing 4 TWh of electricity a day.", resource: "energy", outputPerDay: 4_000 * KWH_PER_GWH, energyPerDay: 0, renewable: true, buildCost: 50_000_000_000, maintenance: 50_000, // A wonder of the endgame: only a nation that has mastered fusion may raise // one. Its upkeep is deliberately high but not the raw share of its enormous // build cost. requiresTechnology: "fusion_power", materialUpkeep: { steel: 1_000, energy: 0, hightech: 500 }, icon: "icon_fusion_power_plant.svg", }, { id: "steel_mill", name: "Steel mill", description: "Smelts iron into 2000 t of steel a day, spending 20 MWh of energy per tonne.", resource: "steel", outputPerDay: 2_000, energyPerDay: 2_000 * RESOURCE_RULES.energyPerSteelTonne, buildCost: 800_000_000, maintenance: 10_000, icon: "icon_steel_mill.svg", }, { id: "gold_mine", name: "Gold mine", description: "Produces 20,000 carats of luxury a day, at 10 MWh a carat.", resource: "luxury", outputPerDay: 20_000, energyPerDay: 20_000 * RESOURCE_RULES.energyPerLuxuryCarat.gold_mine, buildCost: 400_000_000, maintenance: 5_000, icon: "icon_gold_mine.svg", }, { id: "diamond_mine", name: "Diamond mine", description: "Produces 20,000 carats of luxury a day, at 5 MWh a carat.", resource: "luxury", outputPerDay: 20_000, energyPerDay: 20_000 * RESOURCE_RULES.energyPerLuxuryCarat.diamond_mine, buildCost: 500_000_000, maintenance: 5_000, icon: "icon_diamond_mine.svg", }, { id: "chip_foundry", advanced: true, name: "Chip foundry", description: "Fabricates 5000 units of high-tech a day, spending 100 MWh of energy per unit.", resource: "hightech", outputPerDay: 5_000, energyPerDay: 5_000 * RESOURCE_RULES.energyPerHighTechUnit, buildCost: 2_000_000_000, maintenance: 10_000, icon: "icon_chip_foundry.svg", }, ]; // One work stands for a whole industry: its output is scaled up so a nation // needs far fewer of them on the map. The energy and fuel it draws scale with // the output too, so a converter stays exactly as efficient as it was authored. export const RESOURCE_BUILDING_OUTPUT_SCALE = 10; for (const building of RESOURCE_BUILDINGS) { building.outputPerDay = (building.outputPerDay || 0) * RESOURCE_BUILDING_OUTPUT_SCALE; building.energyPerDay = (building.energyPerDay || 0) * RESOURCE_BUILDING_OUTPUT_SCALE; building.fuelEnergyPerDay = (building.fuelEnergyPerDay || 0) * RESOURCE_BUILDING_OUTPUT_SCALE; } // The resource buildings decay in battle like the military improvements do. for (const building of RESOURCE_BUILDINGS) { if (building.takesCombatDamage === undefined) building.takesCombatDamage = true; } // Every resource building -- a material producer or a power plant -- is an // independent private-sector agent. It keeps its own cash from what it sells, // tries to grow when demand for its output has been rising, and spends that // cash to upgrade itself. Power is a paid flow: the plants are paid for the // power the grid's consumers draw, and a plant that keeps selling out expands. // // An upgrade to level u raises throughput exponentially (`outputPerDay` times // `alpha^u`) but the extra output costs more power: the operating input grows // linearly (`1 + inputPerLevel*u`). Each further level costs `costAlpha^u` more // steel and high-tech, so the benefit compounds slower than the bill. export const RESOURCE_BUILDING_UPGRADE = { alpha: 1.1, inputPerLevel: 1, costAlpha: 1.1, // A ceiling on the compounding, so a very old building cannot run away. maxLevel: 12, // How many days of sales the agent watches for the demand trend, and how much // its recent average must beat its older one before it commits to an upgrade. salesWindow: 6, demandGrowth: 0, // A producer that sells at least this share of what it makes is demand-bound // (it sells out), so it grows even when its sales look flat. sellThrough: 0.9, // The cash headroom over the quoted material bill an agent wants before it // starts, so a price swing cannot leave an upgrade half-bought. cashMargin: 1.15, }; // A production building's output per day at upgrade `level`, exponential in the // level. Shared by the simulation and the browser so both quote the same figure. export function resourceBuildingOutputAt(proto, level) { const base = (proto && proto.outputPerDay) || 0; return base * Math.pow(RESOURCE_BUILDING_UPGRADE.alpha, Math.max(0, level || 0)); } // A production building's operating input at upgrade `level`: the plant's fuel // or a converter's energy, growing linearly with the level. `field` is the // proto field to scale (`energyPerDay` or `fuelEnergyPerDay`). export function resourceBuildingInputAt(proto, level, field = "energyPerDay") { const base = (proto && proto[field]) || 0; return base * (1 + RESOURCE_BUILDING_UPGRADE.inputPerLevel * Math.max(0, level || 0)); } // The money an agent pays to reach the next level from `level`: the build cost // compounded by `costAlpha` per level already gained. export function resourceBuildingUpgradeMoneyCost(proto, level) { const base = (proto && proto.buildCost) || 0; return base * Math.pow(RESOURCE_BUILDING_UPGRADE.costAlpha, Math.max(0, level || 0)); } export const RESOURCE_BUILDING_BY_ID = Object.fromEntries( RESOURCE_BUILDINGS.map((building) => [building.id, building]) ); export function resourceBuildingById(id) { return RESOURCE_BUILDING_BY_ID[id] || null; }