Files
Battle-for-Tismo/shared/data/simple_economy.js
T

132 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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;
}