Files
Battle-for-Tismo/ECONOMY_BALANCE.md
T

9.3 KiB

Economy balance — design brief

A working document for a session on the two commodities whose world-market prices fail to settle: luxury and high-tech.


1. Shared project context (read this first)

Battle for 'Tismo is an HTML5 port of a turn-of-the-millennium strategy game. Pure JavaScript, no build step, no runtime dependencies.

  • Server-authoritative. shared/game_state.js (GameState) is a framework-free model; server/game_server.js owns one and validates orders.
  • Shared logic runs in Node and the browser. shared/ stays framework-free (no Node, no DOM).
  • GameState is mixins. shared/game_state.js imports each shared/game_state/<topic>.js and adds its methods to the prototype. A new method file must be imported and added there.
  • The economy ticks hourly (shared/game_state/resources.js _tickResources); the world can be settled without players via GameState.warmUp(days) and warmUpToStability({ maxDays }), which the server calls from configureGame (the --warmup flag). "Steady" means every commodity within 10% over the trailing seven days (pricesAreSteady).
  • Snapshot performance. The trade graph is cached across ticks and days under a stamp built from road/railway, territory and storage-node counters; getCityEconomy must not be memoised outside a snapshot. New per-day walks should be memoised against the counters that actually change them.
  • Data lives in shared/data/ (barrel: shared/data.js).

Commands

  • Install once: npm install.
  • One suite: node tests/run_tests.js --file <name>.js. Never the full suite during development — the commit hook runs it.
  • A quick way to observe prices is the server's startup settle: node server/server.js --port 27015 --bind 127.0.0.1 (logs how many days it settles, --warmup 0 skips it). There is also tests/warmup_test.js.

Conventions

  • 2-space indent; no new libraries.
  • Tests in tests/<topic>_test.js extending TestCase. Force RNG with state._random = () => value.
  • Commit only when asked; messages are one imperative sentence; the pre-commit hook runs the whole suite.

2. The problem (from ROADMAP.md)

  • Luxury and high-tech prices climb to a plateau far above their base (the economy simulator's index reaches about 500-650 for luxury and 200-230 for high-tech) instead of settling; revisit the supply and demand balance of the two rarer goods.

(Index 100 = the base price.) The two rare goods are in permanent structural shortage, so the equilibrium price is high and the easing never reaches steady.


3. How the market works today

  • Base prices (shared/data/resources.js RESOURCE_MARKET_BASE): energy 0.051 /kWh, steel 10,200 /t, food 5,100 /t, luxury 1,020 /carat, high-tech 13,600 /unit.
  • Market model (RESOURCE_MARKET): each day the imbalance sets an equilibrium — the price that clears supply against demand — and the price eases toward it by adjustment (0.2). maxRatio (10) bounds how sharp a shortage may read (and is also the ratio a zero-supply day is treated as).
  • Demand for the rare goods:
    • luxury: luxuryPerPersonPerDay: 0.01 carat per person per day.
    • high-tech: science buildings spend highTechPerResearchPoint: 1 unit per research point, advanced buildings/units pay high-tech upkeep (upkeepHighTechPerEuro), and construction of science/advanced buildings costs constructionHighTechPerLevel: 100 per level.
  • Supply: gold/diamond mines (energyPerLuxuryCarat), chip foundries (energyPerHighTechUnit). The opening works are generated from the world seed and are fixed per map; nothing in normal play adds supply except a player raising more works.
  • Energy gates supply: every unit of luxury/high-tech costs a large amount of electricity, and food synthesis competes for the same power (foodSynthesisQuadratic). So a tight electricity balance throttles rare-good supply from above.
  • Food is the one good a region synthesises from energy, with a reserve-based price signal (_tickResources grows toward need + (stockpileTarget - store) - harvest; _consumeCityResources bids the empty-pantry appetite). Rare goods have no such synthesis.

4. Why it plateaus (hypotheses to test)

  1. Demand scales with population/research, supply does not. Luxury demand grows with every person; high-tech demand grows with research points and the advanced-building stock. The seeded mine/foundry supply is a fixed number, so a mid-game world is structurally short and maxRatio caps the price high.
  2. The equilibrium is computed, but the price only eases 20% a day, so if the equilibrium itself is far above base the plateau is simply the equilibrium.
  3. No elasticity on demand. A very high rare-good price should tempt someone to build more mines/foundries; the AI may not, and the player may not either, so supply never answers the signal.
  4. Energy competition. The power needed for more supply may be going to food synthesis, pinning supply.
  5. Stockpile behaviour. Cities aim to hold stockpileDays: 30 of luxury and high-tech, so a one-off demand spike is absorbed then restocked, keeping demand elevated.

5. Design space (settle before coding)

  1. What is the target? Should luxury/high-tech settle near their base (index ~100), or is a moderate premium intended (they are rare)? The roadmap says "settle", implying they should stop climbing; a stable plateau near, say, 120-200 may be acceptable. Decide the target band first, then tune.
  2. Make the equilibrium self-correcting. Options: relax maxRatio so a shortage cannot pin the read; make the equilibrium elastic in supply (higher price → modelled extra production) so the price cannot run away; or add a slow mean-reversion term toward base when demand is met.
  3. Add synthesis for the rare goods (like food) so energy can always buy more at a rising marginal cost — the food model is the template (foodSynthesisQuadratic).
  4. Retune the physical ratios (energyPerLuxuryCarat, energyPerHighTechUnit, per-person luxury, upkeep/high-tech coefficients) so the world's seeded supply covers its steady-state demand.
  5. Seed more supply at world generation (more mines/foundries) — a blunt fix, but it changes the opening economy, so measure with the warm-up.
  6. Cap the demand side: e.g. research high-tech spend or advanced upkeep scaled down.
  7. Warm-up as the acceptance test. After tuning, warmUpToStability should stop well under the cap with luxury/high-tech within the 10%/7-day band.

6. Suggested order of work

  1. Write a small measurement harness (a test or script) that runs warmUpToStability on the standard map and prints the final price indices for all five goods and the day count. Use it for every change.
  2. Identify whether the equilibrium is the plateau (hypothesis 2) by logging supply/demand/equilibrium for the two goods over the settle.
  3. Pick the mechanism (synthesis, elastic equilibrium, retune, or reseed) and tune to the target band.
  4. Re-run warmup_test.js and the economy suites; add a regression test that the rare-good indices stay within a band after a fixed settle.
  5. Tick the roadmap line.

7. Tests to add / extend

  • A test that settles the standard map and asserts each good's price index is within a stated band (e.g. luxury and high-tech both < some multiple of base) and that pricesAreSteady holds.
  • Unit tests for any new synthesis curve or elastic-equilibrium function (monotonic, as expected).
  • Keep tests/warmup_test.js, economy_test.js, trade_graph_test.js, resources_test.js, stockpile_test.js, food_synthesis_test.js, food_power_test.js green.

8. Gotchas

  • Warm-up cost. warmUpToStability runs real simulation days; a tuning that makes it run to the cap will slow every server start and the tests. Keep the settle bounded.
  • The trade graph cache is keyed on network/territory/storage counters, not tile improvements; do not fold new counters into its stamp without reason.
  • getCityEconomy must not be memoised outside a snapshot; a test pins that a simulation read reflects a direct tilePopulation edit at once.
  • Prices are global — a change helps/hurts every nation at once; watch the affordability of construction/upkeep, which is priced off the same figures.
  • Energy is deliberately cheap (base.energy: 0.051); do not raise it casually, or every material's energy bill dwarfs its value.

9. Relevant files

  • shared/data/resources.js — base prices, RESOURCE_MARKET, RESOURCE_RULES (all the ratios), PRICE_INDEX_WINDOW_DAYS, FAMINE.
  • shared/game_state/resources.js — _tickResources, _consumeCityResources, supply/demand/equilibrium, food synthesis, payCombatResources.
  • shared/game_state/economy.js — production, stockpiles, construction costs.
  • server/server.js / server/game_server.js — configureGame warm-up.
  • Tests: tests/warmup_test.js, tests/economy_test.js, tests/resources_test.js, tests/stockpile_test.js, tests/trade_graph_test.js, tests/food_synthesis_test.js.

INTELLIGENCE.md, STACKS.md, AIR_MOVEMENT.md, POLITICS_PERFORMANCE.md, TRAINING_UI.md, ICONS.md (repo root).