The central bank now charts one point a day for a rolling ninety days, shipped rebased to 100, with alternating month columns named once along the bottom so no two dates collide; short games leave future days blank and the axis floor is pinned at 0 with the 100 baseline labelled. The market no longer clamps a price to 0.5-1.8x base: the day's supply and demand set an equilibrium (base * demand/supply) and the price eases toward it, so a sustained shortage lifts a good well past its old band while a glut cheapens it, and the price settles instead of compounding to infinity. The nation modal holds each scroll position across a snapshot rebuild so the foreign reserves below the fold stay put.
539 lines
21 KiB
JavaScript
539 lines
21 KiB
JavaScript
// 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.
|
|
|
|
import {
|
|
EFFECT_ENERGY_EFFICIENCY,
|
|
EFFECT_STEEL_EFFICIENCY,
|
|
EFFECT_FOOD_EFFICIENCY,
|
|
EFFECT_LUXURY_EFFICIENCY,
|
|
EFFECT_HIGHTECH_EFFICIENCY,
|
|
} from "./effects.js";
|
|
|
|
// 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);
|
|
|
|
// The modifier each resource's efficiency technology writes. The simulation
|
|
// divides that resource's energy draw by `1 + modifier`, so a higher figure is
|
|
// more of the commodity produced for the same power.
|
|
export const RESOURCE_EFFICIENCY_EFFECT = {
|
|
energy: EFFECT_ENERGY_EFFICIENCY,
|
|
steel: EFFECT_STEEL_EFFICIENCY,
|
|
food: EFFECT_FOOD_EFFICIENCY,
|
|
luxury: EFFECT_LUXURY_EFFICIENCY,
|
|
hightech: EFFECT_HIGHTECH_EFFICIENCY,
|
|
};
|
|
|
|
// Research is the nation's stock of knowledge: produced by its science
|
|
// buildings and a few governments, and spent on the focused technology. It is a
|
|
// special kind of resource -- a unit of it cannot be bought, sold, stored or
|
|
// transported -- so it is deliberately kept out of the tradeable catalogue
|
|
// above and never appears in the market, the stockpiles or a delivery. Only
|
|
// science production and the technology tree ever show it, always as this
|
|
// erlenmeyer icon standing in for the word "research".
|
|
export const RESEARCH = {
|
|
id: "research",
|
|
name: "Research",
|
|
unit: "",
|
|
icon: "icon_research.svg",
|
|
colour: "#a142b6",
|
|
};
|
|
|
|
export function resourceById(id) {
|
|
if (id === RESEARCH.id) return RESEARCH;
|
|
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 the world market finds its price each day. The market is a smoothing
|
|
// device, not a real auction: the day's imbalance sets an equilibrium, the
|
|
// price that clears supply against demand, and the price eases toward it. There
|
|
// is no price band, so a sustained shortage lifts a good well past its base
|
|
// while a glut cheapens it, but the equilibrium keeps the price anchored: it
|
|
// settles rather than compounding without bound. `maxRatio` bounds how sharp a
|
|
// shortage the ratio may read, so a rounding-error supply cannot run away, and
|
|
// is also the ratio a day with no supply at all is treated as.
|
|
export const RESOURCE_MARKET = {
|
|
base: RESOURCE_MARKET_BASE,
|
|
adjustment: 0.2,
|
|
maxRatio: 10,
|
|
};
|
|
|
|
// The central bank charts a rolling window of the world commodity price index:
|
|
// one sample per day for this many days. The model keeps that many daily
|
|
// samples and the browser draws the same window.
|
|
export const PRICE_INDEX_WINDOW_DAYS = 90;
|
|
|
|
// 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 a month of every storable resource -- food, steel,
|
|
// luxury and high-tech -- before the month begins.
|
|
stockpileDays: 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: 10 * KWH_PER_MWH, // 10 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/1000 a
|
|
// 100 M building needs 100,000 t of steel. The steel bill is deliberately an
|
|
// order of magnitude larger than the cash price, so nothing is bought with
|
|
// money alone: the state pays with materials it has, and the currency is only
|
|
// the settlement it falls back on.
|
|
steelPerBudgetEuro: 1 / 1_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 1,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,
|
|
|
|
// A region that cannot buy the food it needs bids the price up: every tonne
|
|
// of unmet demand counts this many times over in the market, so a local famine
|
|
// shows in the national food price instead of hiding behind a balanced total.
|
|
famineBidFactor: 3,
|
|
|
|
// 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 overland for a neighbour's surplus. Sea legs are not
|
|
// counted: a shipping lane between ports reaches any coast, so a region can
|
|
// always trade with a sea-connected one, however far, at the freight's energy
|
|
// cost. The cap only keeps a city from trucking goods right across a continent.
|
|
export const RESOURCE_TRADE_RADIUS = 8;
|
|
|
|
// A region that can reach a food surplus it does not need buys extra to resell
|
|
// to nearby regions that cannot, acting as a middleman. The freight a buyer pays
|
|
// on the longer outbound leg is its margin, so it only relays toward a market
|
|
// farther than its own source, and stocks this many days of the neighbours'
|
|
// shortfall.
|
|
export const MIDDLEMAN = { coverDays: 3, markup: 0.25 };
|
|
|
|
// 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 20,000 t of steel a day, spending 20 MWh of energy per tonne.",
|
|
resource: "steel",
|
|
outputPerDay: 20_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.
|
|
// A long window means only demand that is sustained earns an upgrade, not a
|
|
// one-day spike.
|
|
salesWindow: 12,
|
|
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;
|
|
}
|