The simple economy is now the default and the old simulation is frozen as the hard model, selectable per game. A nation keeps one resource pool available in every region, fed by public production works rather than private per-region stores, and construction and training begin at once, paced by the region's spare power instead of gathering materials over days. Money is euro only: a per-inhabitant daily tax funds a treasury spent in a global market, where buy and sell lots trade against the world stock and move its price. The hard economy keeps its per-nation currencies, central banks and exchange rates. Simple games skip the price-settling warmup and economic migration; the snapshot carries the economy model, the market and the pool figures the client draws. Added the matching test suites and the design briefs under docs/simple_economy.
107 lines
5.4 KiB
JavaScript
107 lines
5.4 KiB
JavaScript
// 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: 3_000,
|
||
// The least spare power a region is credited with, so a blacked-out region
|
||
// still builds -- slowly -- instead of dividing by zero or finishing at
|
||
// once. A tenth of a solar plant's daily output.
|
||
minEnergy: 30_000_000,
|
||
// 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 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;
|
||
}
|