Grand Century Grand CenturyDocumentation
Play
Reference/Design

Master Document — "Grand Century" (working title)

Last updated 2026-08-27

A single-player, browser-based grand strategy game about the long nineteenth century.


1. Context — why this document exists#

The goal is a from-scratch, single-player web game with a living 19th-century world you steer as a nation, watching population, industry, and armies evolve on a historical map. It must run entirely in the browser (client-side, no server sim), feel performant, and look beautiful.

This is greenfield — there is no existing codebase. This document is the master spec: it defines the vision, the simulation model, the tech architecture, the data pipeline, the milestone roadmap, and the AI-assisted execution workflow.

Confirmed decisions (from scoping)#

Decision Choice Consequence
Scope Broad-but-shallow full loop Economy, population, politics, diplomacy, and war are all present but initially simplified; the whole loop is playable end-to-end from an early milestone.
Setting Historical 1830 Earth Opens on the post-Napoleonic settlement and independence era. Requires a province map plus a source-backed historical content pipeline (Section 6).
Pillar War & expansion War is the star: it gets the most mechanical depth; every other system is designed to feed war (economy funds it, pops man it, politics gates it, diplomacy sets it up).
Stack "Most performant & beautiful" → my call TS + Vite + React UI + WebGL map + Web-Worker sim (Section 4).

2. Design vision & pillars#

One-line pitch: Take a nation in 1830 and reshape the long nineteenth century through industry, reform, diplomacy, and conquest, with a world market and population that live and react on their own.

Design pillars (ranked, war-first):

  1. War & expansion is the payoff. Mobilizing pops into armies, fronts that push and break, war goals, occupation, peace deals, the great-power pecking order, and the colonial land-grab. This is where the player spends the climaxes of a game.
  2. A world that lives without you. Pops grow/migrate/promote, factories boom and bust, prices move on a shared world market, AI nations pursue their own wars. The player nudges a system, they don't micromanage a spreadsheet.
  3. Everything feeds the war engine. Economy → money & goods for armies. Pops → soldiers & taxes. Politics → what reforms/mobilization you can enact. Diplomacy → alliances, casus belli, spheres. No pillar is a dead end.
  4. Legible depth. We keep systemic depth but expose why things happen through tooltips that trace every number to its inputs.

Explicit non-goals (to protect scope): multiplayer; modding tools; an encyclopedic scripted event tree; historically exact pop/goods numbers (we seed plausible history, then let the sim diverge).


3. Simulation model (broad-but-shallow, war-weighted)#

The sim is real-time-with-pause: five speeds plus pause. Base unit is a day/tick; heavier systems resolve on coarser cadences to stay cheap.

3.1 Tick cadences#

Cadence Systems
Daily Army movement, combat resolution, siege/occupation progress, construction progress, market price adjustment.
Weekly Pop needs satisfaction & consumption, factory production run, trade clearing.
Monthly Budget (taxes/tariffs/spending), pop growth, migration, pop promotion/demotion, research points, diplomacy AI, prestige/GP ranking recompute.

3.2 Core entities#

3.3 Pillar summaries (each intentionally simplified)#

Economy (supporting — funds war). RGOs produce raw goods; factories in states convert raw → manufactured; pops demand goods by need tier (life/everyday/luxury). Supply vs demand on the world market sets prices weekly. Nation earns from taxes (per pop-type slider), tariffs, and state-owned production; spends on army upkeep, construction, admin, and reform costs. Bankruptcy is possible and cripples war capacity.

Population (supporting — mans war & pays for it). Pops grow with need-satisfaction, migrate toward jobs/higher living standards, and promote/demote between types (e.g. farmer→laborer→craftsman; anyone→soldier when soldier jobs & culture allow). Militancy rises with unmet needs & political suppression → rebellions.

Politics (gate — decides what war you can wage). A short reform tree (economic, political, social, military). Upper house / party in power gates which reforms are legal and sets tax/economic policy. Military reforms directly unlock mobilization size, conscription, and army tech throughput. Elections (if the government type allows) shift the ruling party.

Diplomacy (setup — creates wars). Relations, alliances, guarantees, casus belli / war goals, infamy (badboy) as the aggression brake, great-power ranking (score = industry + military + prestige), sphere of influence over secondary/unciv nations via influence points. Declaring war requires a valid CB; peace is settled by war score against attached war goals.

★ War & expansion (the star — deepest system).

3.4 Simplifications table#

Full-complexity feature Our initial model
Per-nation/per-sphere markets One global market + tariffs
~50+ goods, multi-input recipes ~30 goods, ≤3-input recipes
Full event/decision tree Small hand-authored event set + generated flavor
Rebel factions & civil wars in depth Militancy → uprising stacks + reform pressure
Exact historical pops Plausible seeded pops, sim diverges
~2500 provinces ~800–1500 provinces (perf, Section 7)

4. Technical architecture (performant + beautiful)#

Guiding principle: the UI thread only renders; the world only simulates in a worker. They exchange snapshots. This is what keeps a heavy grand-strategy tick from ever stuttering the map.

┌────────────────────────── Main thread (60fps) ──────────────────────────┐
│  React + TypeScript  ── panel UI (ledgers, diplomacy, budget, army)      │
│  Zustand store       ── UI state + latest world snapshot (read-only)     │
│  WebGL map renderer  ── MapLibre GL / deck.gl, GPU province fills        │
└───────────────▲───────────────────────────────────┬────────────────────┘
                │ snapshot (structured-clone/SharedArrayBuffer)            │ commands (player intents)
┌───────────────┴───────────────────────────────────▼────────────────────┐
│  Web Worker — the simulation                                            │
│  Pure-TS deterministic engine: pops · market · politics · diplo · WAR   │
│  Fixed-timestep tick loop; seeded RNG; produces snapshots per frame     │
└─────────────────────────────────────────────────────────────────────────┘
        │ save/load
   IndexedDB (via idb) — compressed save blobs

4.1 Stack (my recommendation for "most performant & beautiful")#

4.2 Why this beats the alternatives#


5. Repository layout & conventions#

/grand-century
  /src
    /sim            # pure TS, no DOM — runs in worker; the whole game model
      /systems      # market.ts, pops.ts, politics.ts, diplomacy.ts, war/*.ts
      /model        # entity types, world state, struct-of-arrays stores
      world.ts      # tick loop, cadence dispatch, snapshot serialization
      rng.ts
    /worker         # worker entry: command intake + snapshot emit
    /ui             # React components (panels, mapmodes, hud)
    /map            # MapLibre/deck.gl setup, province paint, camera
    /data           # loaders for the built game data (from /content)
    /shared         # domain types shared by sim + ui
  /content          # SOURCE data + build scripts → baked game data (Section 6)
  /public
  /tests

Conventions: sim code imports nothing from ui/ or the DOM (enforced by lint boundary). All player actions are commands posted to the worker; all reads come from the snapshot. One shared/types.ts is the contract both sides trust.


6. Content pipeline (1830 Earth data)#

Historical Earth is the biggest content risk. Treat data as a build step, not hand-typed constants.

Sources → baked artifacts:

  1. Province geometry: start from open historical and Natural Earth-derived geometry. Simplify to ~800–1500 provinces (Section 7). Bake to compact binary plus a province-id ↔ polygon index.
  2. Nations (1830): a JSON table of starting polities, capitals, constitutional relationships, government type, primary/accepted cultures, starting techs/reforms, and GP status.
  3. Province seed: owner, terrain, RGO good+level, starting fort/naval-base, starting pop stacks (type/size/culture/religion) — plausible, generated from a small ruleset + a few historical anchors, not exhaustively hand-authored.
  4. Goods & recipes: the ~30-good list + factory recipes + pop need baskets.
  5. Tech/reform trees: compact JSON.

Build script (/content/build.ts, run via node) validates and emits versioned artifacts into /src/data. an AI coding tool is well-suited to generating and validating these large data tables in batch — a natural delegation target (Section 8).


7. Performance budget#


9. Milestone roadmap#

Each milestone is independently playable/verifiable — the "broad-but-shallow full loop" means we get end-to-end early, then deepen war.

# Milestone Deliverable Acceptance
M0 Scaffold Vite + TS + React + worker + Zustand skeleton; empty MapLibre map renders; tick loop posts snapshots; save/load stub. App boots; clock advances; map pans at 60fps.
M1 Map & provinces 1830 province GeoJSON baked and painted; click a province → info panel; mapmodes (political first). ~1000 provinces selectable and recolor instantly.
M2 Pops & economy Pops, RGOs, factories, world market, weekly production/consumption, monthly budget & pop growth. Prices move with supply/demand; a nation can go bankrupt; pops grow.
M3 Politics Reform tree, ruling party/upper house, taxes/tariffs sliders, militancy → unrest, elections. Player can enact a legal reform; unmet needs raise militancy.
M4 Diplomacy Relations, alliances, CB/war goals, infamy, GP ranking, spheres. Player can form an alliance and fabricate/declare a valid war.
M5 ★ War & expansion Recruit regiments from soldier pops, armies + generals, movement, combat, sieges/occupation, navies + landings, war score & peace, colonization. A full war can be fought and won; a colonial claim can be planted.
M6 AI, balance & polish AI nations play the loop (economy + war), event/flavor pass, tooltips-that-trace-numbers, save/load hardening, perf pass, audio/art polish. AI wages its own wars; a 20-year game runs stable end-to-end.

Vertical-slice note: M2–M5 each add a pillar but the game is playable after each. War (M5) is deliberately the fattest milestone and may split into M5a land / M5b naval+colonial.


10. Verification strategy#


11. Risks & mitigations#

Risk Mitigation
Historical map/data is a content black hole Cap provinces at 800–1500; generate seeds from rules + anchors, not by hand; make data a build step (Section 6) and delegate table generation to an AI coding tool.
Monthly full-world pop pass is slow Struct-of-arrays, pop merging, worker isolation, WASM only if profiled. Perf test in CI.
War (the star) balloons in scope Split M5 into land / naval+colonial; ship land war playable first.
"Broad-but-shallow" silently becomes broad-but-broken Each milestone has hard acceptance criteria; don't advance until green.
AI is the make-or-break for single-player fun M6 gives AI real weight; start with a competent economy+war heuristic AI, not a scripted one.
SharedArrayBuffer needs COOP/COEP headers Fall back to structured-clone snapshots if hosting can't set headers; measure before committing.
System opacity Tooltips that trace every displayed number to its inputs, built in as a shared component from M2.

12. Open questions (resolve before or during M0)#

  1. Map renderer: MapLibre-only vs MapLibre+deck.gl overlay — decide via a 1-day M1 spike.
  2. Art direction: archival nineteenth-century atlas vs cleaner modern flat — pick a reference before M1 styling.
  3. Time span & end date: 1830 → 1930 as the authored century, or open-ended after 1920?
  4. Save size / autosave cadence acceptable to you (IndexedDB quota is generous but not infinite).
  5. Hosting target (static host is enough; only matters for the SharedArrayBuffer header question).

This is the living master document. Each AI coding tools session should cite the section it implements and update this file when a milestone's acceptance criteria change.

Ask