// Simple-economy figures, in their own namespace so they cannot collide with // the hard economy's ECONOMY, MONEY and RESOURCE_* constants. The simple model // is the default from now on; the rich per-region simulation is kept intact as // the "hard" model and selected explicitly. // The two models a game may run, and the one used when none is given. export const ECONOMY_MODELS = ["simple", "hard"]; export const DEFAULT_ECONOMY_MODEL = "simple"; export const SIMPLE_ECONOMY = { // Tax: every inhabitant pays this many euros a day, scaled by twice the // region's approval (a 0..2 factor) and any tax-income modifiers. taxPerInhabitant: 100, approvalTaxFactor: 2, // The one shared currency: there is no conversion, every nation quotes prices // in euros. euro: { code: "EUR", symbol: "€", name: "Euro" }, // The fixed euro reserve every nation opens with. Money buys only market // goods in the simple economy, so this is day-one buying power rather than a // working balance the state spends internally. startingTreasury: 1_000_000_000, // Propaganda is the one thing culture is spent on: a running campaign drains // culture at its hourly rate (the opinion shift scales with that rate just as // the hard economy's money spend did), and lapses when the nation can no // longer fund it. The hard economy's figures are money, so the simple model // keeps its own, sized near a culture building's hourly output. campaign: { defaultCulturePerHour: 8, maxCulturePerHour: 40, }, // The comfortable working stock every nation's single resource pool opens // with. Unlike the hard economy's year of per-region reserves this is a fixed // figure, not derated from consumption: with one pool per player there is // nowhere for a surplus region to hoard, so the opening stock only has to // bridge the first works coming online. Energy is not pooled -- it stays a // per-region flow (see `03-construction.md`) -- so only the four storable // goods appear here. startingPool: { steel: 1_000_000, food: 500_000, luxury: 500_000, hightech: 20_000, }, // Construction in the simple economy starts the moment it is ordered: there // is no materials-gathering phase, and the speed comes from the spare power // of the region the work stands in rather than from national GDP. construction: { // The cost/energy exchange at the heart of simple construction: one euro of // build cost needs this many kWh a day of spare regional power to be raised // in one hour. A work's base hours are `cost * energyPerConstructionHour / // max(available, minEnergy)`, then the building/training ratio below and the // usual approval and technology multipliers. A tighter grid raises the same // work slower, the opposite of the hard economy's richer-nation-builds-faster. energyPerConstructionHour: 1_600, // The construction power every region is credited with even when its grid // has nothing to spare. This is the guarantee behind the opening pace: a // modern-infantry unit (cost 100M) always trains in 45 game hours -- 45 real // seconds, one hour a second -- in a starter city on any map, however // starved its grid. A region with more spare power builds the same work // faster; none builds it slower. Sized as `100M * energyPerConstructionHour // / 45`, so the reference work lands exactly on 45 hours at the floor. minEnergy: (100_000_000 * 1_600) / 45, // The hard economy builds ten times faster than it trains a unit of the same // cost; the simple economy keeps that ratio. buildingTimeMultiplier: 0.1, // No work finishes in under an hour, so even a surging grid cannot make a // build instant. minHours: 1, // The fuel-free plants, in preference order, used to guarantee every opening // region a power plant. All three are land buildings with no fuel bill, so // the simple economy never implies a fuel resource. seedPlantIds: ["solar_power_plant", "wind_turbines", "geothermal_plant"], }, }; // The base hours a simple-economy work of `cost` takes at `energy` kWh a day of // spare regional power. `kind` is "unit" for training, anything else for a // building, tile work or upgrade, which the figures in `construction` run // faster. Energy is floored at the region's guaranteed construction power, so a // blacked-out starter region still raises the reference unit in 45 hours and // divides by no zero; more power than the floor only makes a work quicker. // Shared by the server's clock and the browser's quoted times, so the two can // never disagree. export function simpleConstructionHours(cost, energy, kind) { const available = Math.max( Number.isFinite(energy) ? energy : 0, SIMPLE_ECONOMY.construction.minEnergy ); const base = Math.max(0, cost) * SIMPLE_ECONOMY.construction.energyPerConstructionHour / available; const scaled = kind === "unit" ? base : base * SIMPLE_ECONOMY.construction.buildingTimeMultiplier; return Math.max(SIMPLE_ECONOMY.construction.minHours, scaled); } // The global market every simple-economy nation buys and sells from. One shared // world stock per storable good opens at this figure, and the price is the base // price scaled by how far the stock has fallen from it: half the stock doubles // the price, a glut cheapens it. Buying drains the stock (raising the price), // selling refills it (lowering it), so the two sides of the trade price // themselves. A single shared stock, not a per-player order book, matches the // formula and the opening figure. export const MARKET_START_STOCK = 1_000_000; // The only quantities a market order may name, in canonical units. The client // offers exactly these three buttons and the server re-validates the lot. export const MARKET_LOTS = [100, 2_000, 50_000]; // The stock a price is never allowed to read below, so an emptied market cannot // send the price to infinity (or to NaN on a zero). One thousandth of the // opening stock caps the dearest a good can get at a thousand times its base. export const MARKET_MIN_STOCK = 1_000; // The market price of one good at a given world stock: // `basePrice × (stock / MARKET_START_STOCK) ^ -1`, clamped so a low or broken // stock stays finite. This is the single source of truth the tick, the order // handler and the snapshot all price a good through. export function marketPriceFor(basePrice, stock) { const available = Number.isFinite(stock) && stock > MARKET_MIN_STOCK ? stock : MARKET_MIN_STOCK; return basePrice * Math.pow(available / MARKET_START_STOCK, -1); } // Coerces a stored or configured model name to a known one. An unknown value // falls back to the default rather than throwing, so a typo or a stale save // cannot stop a game from starting. export function normalizeEconomyModel(value) { return ECONOMY_MODELS.includes(value) ? value : DEFAULT_ECONOMY_MODEL; }