A House Divided A House DividedDocumentation
Changelog
Game Design/Economy & Finance

Price Indexing & Nominal Repricing

Last updated 2026-08-21
Source files

Status: Partially shipped. The household price index described below now exists; full nominal repricing of constants remains a plan. Scope: Cumulative price-level index per country, global weighted inflation series for the forex page, and a nominal-repricing layer for in-game constants (commodity base prices, action costs, startup capital).

Reviewed against: , , , , , , docs/design/currency-exchange.md.


1. What the stored inflation value actually represents#

The per-turn budget.economicFactors.inflationRate is an annualized inflation estimate for this turn's conditions. It is not a measured trailing 48-turn rate. Inflation uses INERTIA = 0.35 plus mean reversion in .

The shipped country household index lives in . It starts at 1.0 and advances each turn using 75% CPI passthrough divided by TURNS_PER_YEAR. The real-economy panel uses it to deflate median income. It does not yet reprice commodity bases, action costs, startup capital, or the other constants proposed later in this document.

Consequence: we cannot sum/average these values across turns to get "cumulative inflation." We need a separately-maintained price-level index.

2. Price-level index - math bridge#

Note on convention: This is a forward-looking compounding of a rolling annualized estimate - not a retrospective YoY. Document this so nobody later "fixes" it by replacing with a 48-turn trailing average (which would subtly change the meaning of the index).

3. Storage - a separate, uncapped collection#

CentralBank.inflationHistory is pruned to FOREX_AND_MACRO_CHART_HISTORY_TURNS (240 turns = 5 in-game years). A cumulative index must not live in a capped ring buffer or it loses its base.

Proposal:

Why two: the "current" doc gives O(1) reads for the repricing resolver (hot path); the history collection gives charting without inflating the central-bank document.

4. Global weighted inflation series#

For the forex page's per-turn global number:

π_global_t = Σ_c w_{c,t} × π_{c,t}

Market-cap weighting creates a feedback loop: high-inflation country → nominal market cap up → larger weight in global inflation → loops back into macro signals the chart plots. Options:

Weight Pros Cons
GDP (same-turn) Smoother, inflation-neutral in real terms Also drifts w/ nominal GDP growth
Real market cap (cap / own price level) Removes inflation feedback Adds one extra division, less intuitive
Lagged market cap (t-1) Simple, breaks tight feedback Still nominal; minor feedback at cycle scale
Market cap (same-turn) Most "financial-markets" flavor Strong feedback loop (reject)

Default for the cumulative global index: GDP-weighted. Acceptable for the forex-page YoY chart (where financial-market context is appropriate): lagged market cap, read from marketCapHistory.exchangeCaps via exchangeRegistry's 1:1 exchange↔country mapping. These can legitimately be two different series - label both clearly.

4b. Global price level#

Compound the weighted per-period factor into a global priceLevels row keyed _id: "global". Use the same weights each turn for internal consistency (weight by GDP share or cap share - don't mix per panel).

5. API + chart#

6. Nominal repricing layer#

6a. Rule#

6b. Per-country vs global deflator#

Default: per-country. A US player's action cost is scaled by US's price level; a JP player's by JP's. This is fairer cross-country.

Override to global: a small whitelist (effectively "the world market") - e.g. COMMODITY_BASE_PRICES, the anchor for commodityPressure. The resolver accepts "global" as an explicit scope.

6c. Forex baselines and commodity base prices are NOT repriced#

6d. What the resolver applies to (audit)#

The plan's "inventory" must be a grep-based discovery, not a guess. Starting commands:

grep -rn "funds:\s*[0-9]\|cashOnHand:\s*[0-9]" src/     # startup/hardcoded funds
grep -rn "estimatedCost\|COST\|_COST\s*=" src/lib        # action/fundraise cost constants
grep -rn "COMMODITY_BASE_PRICES\|basePrice\b" src/       # commodity anchors (most are off-limits - see 6c)
grep -rn "STARTING_\|INITIAL_\|DEFAULT_" src/lib/constants

For each hit, decide: (a) real constant → route through resolver, (b) already nominal balance → leave alone, (c) locked anchor (forex baseline, commodity base) → do not touch.

7. Feedback-loop check (mandatory before ship)#

Add a test that simulates a one-shot rate shock and verifies inflation/forex/commodity-pressure converge rather than diverge over, say, 100 turns. Flag anything that explodes.

8. Golden tests#

9. Phased sequencing#

  1. Math + storage - write priceLevels + priceLevelHistory, add the writer phase, seed one base snapshot. No consumers yet.
  2. Validate - let it run one game-day (48 turns) in dev; sanity-check that a flat 2% produces ~2% cumulative.
  3. Forex page series - add API fields + dedicated "Global inflation tracker" chart. Only after (2) is stable.
  4. Repricing layer - introduce nominalCredits() resolver; audit constants via greps in 6d; route real constants through the resolver. Nominal balances untouched.
  5. Docs - update currency-exchange.md and economic-systems.md with links to this doc and the weight-basis decision.

10. Known tensions / open questions#

Connected pages