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

Corporations

Last updated 2026-08-21
Player wikiCorporations
Source files

The corporation system lets players found and manage businesses that operate across state economies. Corporations generate revenue, employ workers, and interact with state metrics to create economic feedback loops between legislation and business outcomes.

Founding a Corporation#

Players choose from 17 sector types:

Sector Label
financial Financial
media Media
manufacturing Manufacturing
healthcare Healthcare
retail Retail
automobiles Automobiles
technology Technology
energy Energy
agriculture Agriculture
real_estate Real Estate
defense Defense
telecommunications Telecommunications
entertainment Entertainment
logistics Logistics
extraction Extraction & Mining
chemical_industries Chemical Industries
construction Construction

Sector Expansion#

Under plants, corporations expand by founding a sector and queueing physical capacity. The entry fee and starter-build price are era-scaled. A new plant begins with zero capitalStock; it does not receive a fixed $1 million revenue or 500-worker allocation.

Market Capture#

Unowned market share can be captured via "splits":

The escalation prevents spam-splitting while the decay ensures the cost resets over time. Players must balance split frequency against MS reserves.

Unowned Sector Regeneration#

In pre-plants modes, unowned sectors grow at half the average owned growth rate, with the legacy fallback. In plants worlds, unowned headroom grows from the state's economic.gdpGrowth. See .

Vacant CEO market decay#

Player corporations with no active CEO (ceoId unset/null or ceoVacant: true) lose 10% per turn of each owned sector’s revenue and workers; that revenue is $inc’d into the matching (stateId, sectorType) unowned sector document (same pool splits draw from). State-owned / nationalized corporations (countryOwnerId set or isNationalized) are excluded so public enterprises do not bleed. Runs at the start of processCorporationTurn (after lookups, before sector P&L) in .

Per-Turn Processing#

Every turn (24 turns per game-day), each corporation is processed:

Entry point: processCorporationTurn()

Processing Phases#

Phase Action Code Location
1 Build lookup maps (states, metrics, budgets, tariffs, subsidies) buildCorporationLookups()
1b Vacant-headline corps shed 10% sector revenue/workers → unowned shedVacantCeoSectorsToUnowned()
2 Process sectors (revenue, margin, share price, credit) processSectors()
3 Bulk write sector + corp updates bulkWrite()
4 Update corporate tax bases in federal + state budgets taxBases.corporateProfits
5 Refresh national budget revenue (public enterprises) refreshNationalBudgetRevenue()
6 Pay CEO salaries + dividends to characters characters.bulkWrite()
7 Fill pending share orders fillPendingShareOrders()
8 Snapshot market cap + per-corp history snapshotMarketCap()
9 Auto-resolve open shareholder votes and send closing reminders processVoteAutoResolve()

Sector Calculations#

The steps below are the legacy growth-slider model. It is still the mechanic under the earlier marketSystemMode tiers, but the live market mode is "plants", the top tier of : a sector no longer earns from a self-compounding growth rate at all. It owns priced, buildable capacity, staffs it with workers, and revenue is derived each turn from what that capacity actually produced and sold into the clearing market. See The Capacity Economy (as shipped) for the current model end to end (build queue, capacity pricing, the sector turn, staffing, and valuation); it supersedes steps 1-2 below wherever plants is enabled.

For each sector (legacy growth-slider tiers):

  1. Revenue growth: newRevenue = revenue × (1 + growthRate / TURNS_PER_DAY / 100)
  2. Growth cost: calculateDailyGrowthCost(newRevenue, perTurnGrowthRate, primeRate), scales with prime rate
  3. Profit margin modifiers: 15+ additive modifiers (unemployment, grid, corruption, commodities, tariffs, subsidies, etc.)
  4. Effective margin: min(100, baseMargin + totalModifier), can go negative (loss-making)
  5. Maintenance: hourlyRevenue × (1 - effectiveMargin / 100)
  6. Sector NPV: yearlyProfit / 0.15 (15% discount rate, NPV_ANNUAL_DISCOUNT_RATE), for balance sheet valuation

Under plants, margin modifiers (step 3) still apply, but revenue and maintenance (steps 1, 2, 5) are replaced by the physical production-and-clearing pass described in The Capacity Economy (as shipped): sector.revenue becomes the nameplate capitalStock × mixPrice, and profit is realized revenue minus physical cost lines, not a margin percentage applied to an asserted revenue figure.

Corporate Tax#

Entry point: (per-sector apportionment loop)

Each jurisdiction sets two independent corporate tax rates, a domestic rate (applied to corps headquartered in the same country as the sector) and a foreign rate (applied to corps headquartered elsewhere). Rates are selected per-sector:

const isDomestic = corp.countryId === sector.countryId;
const federalRate = isDomestic
  ? (lookups.domesticCorpTaxRateByCountry.get(sector.countryId) ?? 0)
  : (lookups.foreignCorpTaxRateByCountry.get(sector.countryId) ?? 0);
const stateRate = isDomestic
  ? (lookups.domesticStateCorpTaxRateByState.get(sector.stateId) ?? 0)
  : (lookups.foreignStateCorpTaxRateByState.get(sector.stateId) ?? 0);

Rates are stored on federalBudget.taxRates.{domestic,foreign}CorporateTax and stateBudgets.taxRates.{domestic,foreign}CorporateTax, populated by the *_domestic_corporate_tax_rate and *_foreign_corporate_tax_rate legislation bills per country. See the design archive for the political-economy rationale and stance distribution.

Tariff & Subsidy Modifiers#

Entry point: src/lib/turn/corporation/sectorCalculations.ts:137-162

Foreign tariff penalty: Corps operating outside home country pay margin penalty based on target country's tariff rates against corp's home country.

Domestic tariff malus: Home-country corps absorb supply-chain friction from broad tariffs (smaller penalty).

Tariff blend weights: Commodity modifiers blend 50% global + 25% national + 25% local (state) prices at baseline; the national weight shifts up (global weight shifts down, local fixed at 25%) as effective tariff coverage rises, up to 25% global / 50% national / 25% local at 100% coverage (getTariffBlendWeights()).

Subsidy bonus: +7.5pp margin per active subsidy (SUBSIDY_MARGIN_BONUS, federal + state stack). Qualifying sectors:

Functions: getForeignTariffMarginModifier(), getDomesticTariffMalus(), getTariffBlendWeights(), getSubsidyMarginModifier()

Income Distribution#

// 1. Corporate tax
corporateTaxOwed = incomePreDividends × (taxRate / 100)

// 2. Dividends (from after-tax income)
afterTaxIncome = incomePreDividends - corporateTaxOwed
hourlyDividendPayout = afterTaxIncome × (dividendRate / 100)

// 3. Final income to corporation
income = incomePreDividends - corporateTaxOwed - hourlyDividendPayout
  1. Split escalation decay: splitEscalation = max(0, splitEscalation - 1), cost halves each turn

Setting a new growth-rate target has no lump-sum cash or MS cost, and downsizing pays no refund; the only cost is the ongoing per-turn calculateDailyGrowthCost charge described above (item 2), which scales with the active growth rate and prime rate.

CEO Salary (implemented)#

The CEO can configure a daily salary paid from the corporation's liquid capital each turn. Salary is set via POST /api/corporations/[id]/settings as a daily dollar amount and is divided evenly across TURNS_PER_DAY turns.

Marketing Budget & Marketing Strength (implemented)#

Marketing Strength (MS) determines how much unowned market share is captured per split. It grows each turn based on the marketing budget (daily dollar spend), configured via POST /api/corporations/[id]/settings.

Growth formula per turn:

baseGrowth = 1 MS (if any spend)
scaledGrowth = 0.65 × ln(1 + budget / 100,000)

Both values apply diminishing returns once MS exceeds 100, growth slows significantly above that threshold. The formula prevents unlimited MS accumulation through raw spending.

Function: calcMarketingGrowth(dailyBudget, currentStrength) in

R&D Budget & Innovation (implemented)#

R&D spending accumulates an R&D Score that drives periodic breakthroughs, one-off revenue boosts to a random sector, plus permanent state resource capacity growth for extraction corps. The system mirrors the marketing/logistics pattern for budget handling, score accumulation, and UI surface.

Score accumulation per turn:

baseGain = 1.0 (if any R&D budget is set)
scaledGain = 0.65 × ln(1 + budget / 100,000)
decay = 3% of current score
newScore = max(0, (1 − decay) × oldScore + (baseGain + scaledGain) × diminishing(oldScore))

Functions: calcRdGrowth, calcRdScoreAfterTurn in .

Innovation check (every 6 turns):

Each corporation rolls once every RD_INNOVATION_INTERVAL turns. Innovation probability scales linearly with score:

probability = min(1, rdScore / 200)

At score 200 every 6-turn window produces a breakthrough; at 100 one in two windows; at 0 none. rdScore only governs how often a breakthrough fires, the magnitude is a separate uniform random roll, not interpolated from score (an earlier score-interpolated version made high-rdScore corps a guaranteed cap-hit, making R&D strictly dominant over Growth). When a breakthrough fires, one sector owned by the corporation is selected (for extraction corps, the sector closest to its capacity limit; otherwise random):

Boosts are $inc'd directly on the sector's revenue in corp-local currency, no FX conversion in the boost path. The breakthrough also fires a rd_breakthrough notification to the CEO's user.

Extraction state capacity growth:

Each extraction breakthrough increases, for every extractable resource in the sector's active strategy supply map, the state's capacity for that resource independently, each resource gets its own uniform random roll of RD_CAPACITY_BOOST_MIN_PCT to RD_CAPACITY_BOOST_MAX_PCT (1%-15%) of that resource's current state capacity:

per-resource increase = currentCapacity[resource] × uniformRoll(0.01, 0.15)

So the oil_gas strategy (produces oil and natural_gas) rolls an independent 1-15% increase for oil and a separate independent 1-15% increase for natural gas, the resources are not splitting a shared pool. States without an existing stateResourceCapacity document, or a resource whose current capacity is 0, are skipped.

Capacity policy (permanent discovery): capacity is unbounded and has no decay. R&D literally unlocks new deposits, once added, the capacity stays. This contradicts the "fixed capacity per turn" framing in docs/design/resources.md; see that doc's note on R&D-driven capacity growth.

States without a stateResourceCapacity document are "uncapped" (legacy/pre-migration) and are skipped by the capacity boost, no auto-insertion mid-turn.

Turn phase: runs as Phase 3b of processCorporationTurn (), immediately after the base sector writes so the $inc composes with the turn's revenue update.

Key files:

Production Policy (implemented)#

Each sector has a production policy level, a continuous numeric scale from -25 to +25 (not discrete modes). The CEO sets a target via the sector settings panel; the active level trends toward the target at 1 unit per turn.

The UI displays these as Aggressive / Normal / Conservative labels, but the underlying mechanic is the continuous scale. Revenue and margin multipliers are applied via getRevenueMultiplier(policyLevel).

Economic Effects#

Unemployment -> Profit Margin (implemented)#

State unemployment modifies corporate sector profit margins by up to ±5%:

Formula: getUnemploymentMarginModifier(unemploymentRate) in

Corporate-Driven GDP Growth (implemented)#

State GDP growth is computed as a revenue-weighted average of owned corporate sectors' growth rates. States with no owned sectors show N/A. National GDP growth uses GDP-weighted averaging.

Formula: updateCorporateDrivenGdpGrowth(db) in src/lib/turn/corporateGdpGrowth.ts

Power Grid Reliability -> Profit Margin (implemented)#

Gated effect: no impact when grid uptime is above 95%. Below 95%, linear penalty scaling to -4% at 85% or lower. Affects ALL sectors, every business needs electricity.

Formula: getGridReliabilityMarginModifier(reliability) in

Corruption -> Profit Margin (implemented)#

Higher corruption increases costs from bribes, unpredictable enforcement, regulatory shakedowns, and contract uncertainty. Affects ALL sectors.

Formula: getCorruptionMarginModifier(corruptionIndex) in

All Profit Margin Modifiers (implemented)#

A single source of truth function computeAllMarginModifiers() in computes all modifiers for both display and turn processing.

Modifier Max Effect Sectors Affected Threshold/Pivot
Sector Type Match +5% / -15% All Match vs mismatch
Home Location +10% / +5% All HQ state = +10%, same country = +5%
Unemployment ±5% All Pivot at 3%
Power Grid -4% All Gate 95%, floor 85%
Corruption -3% All Linear to index 100
Inflation +2% to -8% All (country-wide) Bonus below 2% target; penalty above
Debt-to-GDP -5% cap All (country-wide) Penalty starts at 50% D/GDP
Deficit-to-GDP +5% max All (country-wide) Stimulative bonus: +0.5% per 1% deficit
Workforce Skill ±4% Technology, Chemical Industries, Healthcare, Manufacturing, Defense Pivot at skill index 50
Crime Rate -5% Retail, Real Estate, Entertainment 1500→3500 per 100k
Broadband Access -4% Technology, Telecom, Media, Financial Gate 70%, floor 40%
Road Condition ±3% Manufacturing, Retail, Agriculture, Automobiles, Construction, Logistics, Extraction Pivot at condition index 60
Carbon Emissions -3% Energy, Chemical Industries, Manufacturing, Automobiles, Extraction 3→25 MT/capita
Cost of Living ±3% Chemical Industries, Manufacturing, Retail, Agriculture, Construction, Logistics, Extraction Pivot at index 100
Commodity Markets Uncapped Sector-dependent Logarithmic D/S ratio
Subsidies +7.5% per subsidy Qualifying sector types Federal and state stack separately
Logistical Sprawl Uncapped All (corp-wide) >15 sectors threshold

Home Location Bonus (implemented)#

Sectors in the corporation's HQ state receive a +10% margin bonus. Sectors in the same country as the HQ (but a different state) receive a +5% bonus. Sectors in a foreign country receive no home location bonus. Stacks additively with all other modifiers.

Function: getHomeLocationMarginBonus() in

Inflation → Profit Margin (implemented)#

Country-level inflation modifies all corporate sector margins. The target rate is 2.0%. Deflation and low inflation provide a modest bonus; high inflation significantly hurts margins.

Function: getInflationMarginModifier(inflationRate) in

Debt-to-GDP → Profit Margin (implemented)#

High sovereign debt crowds out private investment, raising borrowing costs and reducing confidence. Applies to all sectors at country level.

Function: getDebtToGdpMarginModifier() in

Deficit-to-GDP → Profit Margin (implemented)#

Government deficit spending acts as a short-term economic stimulus, boosting business activity across all sectors. Applies at country level.

Function: getDeficitToGdpMarginModifier() in

Sector Type Match / Mismatch (implemented)#

Sectors that match the parent corporation's primary type receive a +5% margin bonus. Sectors matching the secondary type (if set) receive +2.5%. All other sectors receive a -15% penalty. This encourages focused corporations while allowing some diversification via secondary type.

Formula: getSectorTypeMatchModifier(sectorType, corporationType, secondaryType?) in

Secondary Corporation Type (implemented)#

Corporations can declare a secondary sector focus from the CEO page. This provides a half-strength sector type match bonus (+2.5%) for sectors of the secondary type, but doubles the base sprawl penalty from -0.5% to -1.0% per pair over 15 sectors. Logistics spending still reduces the effective penalty. The secondary type cannot be the same as the primary type and can be cleared by setting it to "None".

Type Switching Penalty (implemented)#

Changing the primary or secondary corporation type incurs a -10% margin penalty on ALL sectors for 24 hours (TYPE_SWITCH_PENALTY_TURNS = 24), followed by a 48-hour cooldown (TYPE_SWITCH_COOLDOWN_TURNS = 48) before another type change is allowed. The total lockout is 72 hours (penalty + cooldown).

Logistical Sprawl Penalty (implemented)#

Corporations with more than 15 sectors incur a -0.5% margin penalty for every 2 sectors over the threshold (doubled to -1.0% if a secondary type is set). A corporation with 15 or fewer sectors has no penalty. Logistics spending reduces both the threshold and penalty slope:

Formula: getSprawlModifier(totalSectors, logisticsStrength?, hasSecondaryType?) in

Planned Sector Effects#

Future state metric effects (not yet implemented):

Metric Effect Sectors Affected
Population Growth +0-2% revenue growth bonus Retail, Real Estate, Healthcare, Agriculture
Renewable Energy -3% energy margin, +2% manufacturing margin at high levels Energy, Manufacturing
Income Inequality +2% financial margin, -2% retail revenue when high Financial, Retail

Commodity Market (implemented)#

22 commodity types trade between sectors: Steel, Electronics, Energy (Electricity), Chemicals, Pharmaceuticals, Fertilizers, Food, Building Materials, Construction Services, Healthcare Services, Real Estate Services, Software, Financial Services, Advertising, Vehicles, Consumer Goods (retail), Freight, Consulting Services, Iron Ore, Coal, Crude Oil, and Rare Earth Minerals.

Supply & Demand#

Each sector type supplies and demands specific commodities at defined rates (fraction of daily revenue). Retail demands all commodities as inputs and also supplies the "Consumer Goods" (retail) commodity (rate 0.5). Retail input demand is scaled by GDP growth (50% national + 50% regional). Consumer demand for the retail commodity is also GDP-driven: demand = retail supply × GDP multiplier, so positive GDP growth pushes retail prices up and negative GDP shrinks them. Only owned sectors participate.

Retail sectors face only 25% of negative commodity input penalties (RETAIL_NEGATIVE_COMMODITY_PENALTY_FACTOR = 0.25), reflecting their ability to substitute or absorb supply chain shortages. Positive modifiers (oversupply benefits) are unaffected.

Units: units = (sector daily revenue × rate) / basePrice

Dynamic Pricing#

Market price is computed per scope (global / national / regional) from a logarithmic supply/demand pressure ratio, not a simple clamp: computeMarketPrice() takes the log of the effective (soft-kneed) supply/demand ratio and applies it as a multiplier on basePrice (COMMODITY_PRICE_LOG_SCALE = 0.7), with a soft knee at 3x pressure (COMMODITY_PRESSURE_SOFT_KNEE) that compresses extreme shortages/oversupply.

Blended price per state = 50% global price + 25% national price + 25% regional (state-level) price (blendPrice(), GLOBAL_PRICE_WEIGHT/NATIONAL_PRICE_WEIGHT/REGIONAL_PRICE_WEIGHT in ).

Prices recalculate each turn via processCommodityPriceTurn().

Margin Modifiers (Logarithmic)#

Both input cost penalties and output demand bonuses use the same symmetric logarithmic curve:

modifier = K × Σ(rate_i × ln(demand_i / supply_i))

Where K = COMMODITY_LOG_K = 40 ().

Buyers (input costs): Modifier is negated, shortage raises costs, oversupply lowers them. Sellers (output demand): Modifier is positive, shortage boosts margins, oversupply compresses them.

Reference values (single commodity, rate = 1.0):

D/S Ratio Modifier
0.5× ∓27.7%
1× (balanced) 0%
1.5× ±16.2%
±27.7%
±43.9%
±64.4%
10× ±92.1%

The logarithmic curve is self-balancing (diminishing returns) and uncapped. Near-zero supply is floored at demand/1000 to avoid infinity.

Formula: computeCommodityMarginModifier() and computeCommoditySurplusBonus() in

Key Files#

Operating Strategies (implemented)#

Each sector type has 3-4 operating strategies that alter its commodity supply and demand rates. Every sector defaults to "Standard" but can be switched by the CEO.

Switching#

Strategy Confirmation#

The strategy switch confirmation panel shows:

Key Files#

Shares & Dividends#

Share Trading#

Players can buy and sell shares from the Shares tab on any corporation page:

CEO Share Issuance#

Shareholder Governance Votes#

Public corporations use corporationVotes for governance changes, HQ relocation, dissolution authorization, and public share issuance. The CEO opens a vote via POST /api/corporations/[id]/votes; each cast ballot carries a voteShares weight equal to the voter's current holdings.

Dividends#

CEO election (shareholder vote)#

When the CEO office is contested, each shareholder casts at most one ballot choice, but the tally is weighted by shares: a voter with 500 shares contributes 500 votes to their chosen candidate (not one vote per holder). You may vote for yourself if you are a candidate. Eligible candidates must reside in the corporation's headquarters state (same rule as accepting the CEO role).

Share Price Formula#

Entry point: (computeSharePrices()), called from the turn's share price step

Three-component fundamental value formula: fundamentalValue = tangibleBookWeight × tangibleBookPerShare + (earningsPowerWeight × earningsPowerPerShare + growthPremiumWeight × growthPremiumPerShare) × reliancePenalty. Sentiment and order-flow multipliers are applied separately by the 15-minute price-update cron; this module computes the fundamental leg only.

// 1. Tangible book per share (liquidation floor): cash + sector NPV + construction
// in progress + tech assets + haircut bond holdings, minus issued bond debt.
const tangibleBook =
  liquidCapitalAnchor +
  sectorNPVAnchor +
  constructionInProgressAnchor +
  techAssetValueAnchor +
  BOND_INCOME_SHARE_PRICE_DISCOUNT * bondHoldingsAnchor -
  issuedBondDebt;
const tangibleBookPerShare = Math.max(0, tangibleBook) / totalShares;

// 2. Earnings power per share (risk-adjusted earnings capitalized at cost of capital)
const earningsPowerPerShare =
  riskAdjustedEarnings / costOfCapital / totalShares;

// 3. Growth premium per share (Gordon Growth Model terminal value, g capped below costOfCapital)
const growthPremiumPerShare =
  (riskAdjustedEarnings * gCapped) / (costOfCapital - gCapped) / totalShares;

const fundamentalValue =
  FUNDAMENTAL_TANGIBLE_BOOK_WEIGHT * tangibleBookPerShare +
  (FUNDAMENTAL_EARNINGS_POWER_WEIGHT * earningsPowerPerShare +
    FUNDAMENTAL_GROWTH_PREMIUM_WEIGHT * growthPremiumPerShare) *
    reliancePenalty;

// 4. Per-turn rate limiter: raw price can move at most ±35% from the previous price
const rawPrice = rateLimitPrice(fundamentalValue, previousSharePrice);

Bond-coupon income over-reliance (>75% of normalized earnings) applies a graduated valuation penalty to the earnings-derived components, ramping to a 0.5x floor at 100% reliance (bondRelianceValuationPenalty()). Insider concentration (character CEO holding >65% of shares) applies a quadratic discount reaching -30% at 100% ownership (insiderConcentrationMultiplier()); broad index-fund ownership earns a mirrored premium. Public corps mid-cooldown after a stock split/reverse split blend toward the pre-split price instead of the per-turn rate limiter.

Constants:

Constant Value Description
FUNDAMENTAL_TANGIBLE_BOOK_WEIGHT 1.0 Weight on tangible-book-per-share
FUNDAMENTAL_EARNINGS_POWER_WEIGHT 0.4 Weight on earnings-power-per-share
FUNDAMENTAL_GROWTH_PREMIUM_WEIGHT 0.1 Weight on growth-premium-per-share
BOND_INCOME_SHARE_PRICE_DISCOUNT 0.75 Haircut on bond-coupon-derived earnings and held bonds in book
BOND_INCOME_RELIANCE_THRESHOLD 0.75 Bond-coupon share of earnings above which the reliance penalty starts
BOND_INCOME_MAX_RELIANCE_PENALTY 0.5 Floor multiplier on earnings components at 100% bond reliance
SHARE_PRICE_MAX_TURN_MOVE 0.35 Max fractional per-turn price move (rate limiter)
SHARE_PRICE_RATE_LIMIT_MIN_PREV $1.00 Rate limiter skipped at/below this previous price
INSIDER_CONCENTRATION_THRESHOLD 0.65 CEO ownership fraction above which the concentration discount starts
INSIDER_CONCENTRATION_MAX_PENALTY 0.3 Max discount (-30%) at 100% CEO ownership
STOCK_SPLIT_PRICE_SMOOTHING_TURNS 2 Turns of post-split smoothing before the rate limiter resumes
STOCK_SPLIT_SMOOTHING_PREV_WEIGHT 0.7 Weight on previous price during post-split smoothing
MIN_SHARE_PRICE $0.01 Hard floor on share price
NPV_ANNUAL_DISCOUNT_RATE 0.15 15% discount rate for sector NPV

Collections#

Bonds#

Corporations can issue bonds to raise capital. Bonds are fixed-income debt instruments.

Bond Issuance#

Credit Rating#

Composite score (0-100) from four components:

Rating determines the credit spread added to the prime rate for the coupon.

Bond Trading#

Distressed Debt Trading#

Bond Holdings#

The bonds API returns holdings, bonds the corporation owns in other companies, with issuer names, units, market values.

Collections#

Key Files#

National Corporations#

National corporations are government-owned enterprises that operate within the budget system. They are not player-founded and cannot be attacked or acquired.

Characteristics#

UK Public Healthcare (NHS)#

The UK has an NHS-style public healthcare corporation seeded at game setup. Healthcare sectors are sized appropriately for the UK economy and appear in the UK treasury panel alongside other government spending categories.

Sovereign Bonds#

Governments can issue sovereign debt instruments, extending the corporate bond system to national-level finance.

Key Files#

Sector Production Policy#

Production policy is a continuous target from -25 to +25, not three discrete modes. The active value trends toward the target by 1 point per turn. Positive values increase revenue and commodity throughput; negative values reduce output and cut input consumption more sharply. The UI may label the ends Aggressive and Conservative, with zero as Neutral, but every integer level is meaningful.

HQ Relocation#

CEOs can relocate corporate headquarters to another state or region (including in another country) via POST /api/corporations/[id]/relocate. This changes the corporation's tax jurisdiction, home-nation sector bonuses, and coupon-rate calculations on future bonds.

Players who are CEOs can also combine their own relocation with a corp HQ move via the region-page "Relocate here" button (see Relocation, "Combined character + corporation relocation"). In that flow the CEO role is preserved because the character ends up at the new HQ.

Treasury and sector revenue on cross-country relocation#

When a cross-country HQ move crosses a currency boundary (e.g. UK → JP), the corp's treasury and sector economics convert from the source currency to the destination currency at the spot FX rate at time of submission:

What does NOT convert:

Open share orders + listings cancelled on conversion. Escrow amounts are denominated in the old currency; the cancel-refund helpers read the corp's current liquidCurrencyCode to interpret them, so the conversion must happen AFTER cancellation. Escrow refunds route through the standard share-order / share-listing cancel paths (buyers get their money back in their own native currency). The player is responsible for re-placing orders in the new currency after the move.

New relocation bond (if selected as payment method) stamps in the NEW currency. The bond is issued after the conversion completes, so resolveCorpLiquidCurrencyCode(corporation) at stamping time returns the destination currency.

Pre-forex corps (no liquidCurrencyCode) are backfilled. Source rate defaults to 1.0 (₳ passthrough), new currency gets stamped.

All three HQ-move paths converge on the same converter:

Bond denomination on relocation#

Bonds retain their original currencyCode on any relocation, player-initiated (same-country only) and admin-initiated cross-country HQ moves via PATCH /api/admin/corporations/[id]/hq. A JPY-denominated corporate bond pays JPY coupons and returns JPY face value at maturity for its entire life, regardless of subsequent HQ moves. This matches real-world bond contracts (denomination fixed at issuance) and keeps bond.totalIssued, coupon cash-flows, and market price fluctuations all denominated in the single currency the bond was issued in.

Concretely, post-Task-18B bond.currencyCode is the canonical FX key for every bond cash-flow path: turn-processing coupon payouts, maturity face-value payouts, issuer deductions, buy/sell/buyback routes, portfolio valuations, credit scoring, and net-worth aggregation. Code paths must resolve a bond's currency from bond.currencyCode (falling back to COUNTRY_CURRENCY_MAP[bond.countryId] only for pre-migration rows) and never from the issuing corp's current countryId.

Shareholder Address#

CEOs can broadcast a formatted message to all current shareholders as system notifications.

CEO Residence Rules#

CEO Tab Organization#

The CEO Office tab on the corporation page is organized into subtabs:

Subtab Contents
Overview Corporation summary, sector list, key metrics
Budget Dashboard Revenue and cost breakdowns per sector, net income analysis
Settings Dividend rate, growth rates, production modes, share management

Admin-facing sector cards include +/- buttons for manual growth rate adjustment.

Stock Exchange#

Corporations are listed on country-specific exchanges:

Exchange pages display: market cap, share price, total revenue, income, CEO info, sector type, and headquarters. Price history is visualized with OHLC candlestick charts showing open, high, low, and close prices per period.

Discord Bot Integration#

API routes supporting Discord bot queries:

Currency storage (v0.2.6)#

Every corp-economic money field is stored in the corp's liquidCurrencyCode. Cross-corp aggregation (global market cap, commodity flows, stockmarket totals) anchor-normalizes via readCorpEconomicAnchor / sumAsAnchor; intra-corp math stays unit-preserving. UI renders via formatAmount(anchorValue, nativeCurrencyCode) so the wallet preference (internal / home / pinned / local) is a display-time concern only.

Domain Stored in
Corp fields (liquidCapital, marketingBudget, logisticsBudget, ceoSalary, sharePrice) corporation.liquidCurrencyCode
Sector fields (revenue, currentGrowthCost) parent corp's liquidCurrencyCode (not the state's currency)
Corporate bond face value / coupon / totalIssued bond.currencyCode (stamped at issuance from issuing corp's liquidCurrencyCode)
Tax bases written from corp turn (corporateProfits, taxableSales) country's currency (corp turn accumulates in ₳ then multiplies by country FX at write)
Cross-corp aggregates (global GDP, global market cap, commodity demand) computed in ₳; displayed via wallet preference
sharePriceFormula intermediate ₳ (anchor), converted to corp-local at persistence boundary
corporationHistory, marketCapHistory, corporationPortfolioHistory money columns corp's liquidCurrencyCode at time of write (currencyCode stamped on each row)

History backfill (option 3): the v0.2.6 migration rescales every existing history row at today's FX rate so charts stay visually continuous across the migration moment. Historical FX accuracy is intentionally sacrificed.

Migration scripts (idempotent via migrationsRun markers, run in order):

See for the dry-run checklist.

Collections#

Key Files#