v0.28.0 (#46): per-region stats — area×population-weighted-median Nation.stats (PR 1 of 2) #47

Merged
kamilkrzywda merged 1 commits from feat/46-region-stats into master 2026-10-15 02:20:55 +02:00
Owner

Fixes #46

PR 1 of 2 for the per-region stats work: stats now live at region-key granularity
(GameState.regionStats); Nation.stats is derived as the area×population-weighted median
of a nation's owned regions; the LLM lever (per-region stats in the prompt) is PR 2 and is
not in this change. Not deployed — production updates when PR 2 ships.

What changed

  • Data: new committed public/region-geo.json (1,800 entries: area km² @2dp + integer
    population per region key) and public/ne-admin0-pop.json (Natural Earth admin-0 POP_EST,
    249 codes — the small committed source; the 13 MB raw file is never committed). Population is
    spread from each country's POP_EST to its sub-regions proportional to area by
    largest-remainder, so per-country totals are preserved exactly (verified: 0 mismatches /
    177 countries; global total 7,654,092,021). scripts/build-region-geo.mjs +
    scripts/extract-admin0-pop.mjs (new npm scripts build:region-geo / extract:admin0-pop).
  • Types/protocol: RegionStats + required GameState.regionStats (shared/types.ts);
    pure helpers in shared/protocol.ts: weightedMedian, deriveNationStats,
    seedRegionStats, migrateRegionStats.
  • Server: server/regions.ts loads the geo manifest (probes public/ then dist/,
    optional/degrading) with geoOf/hasGeo; game creation seeds regionStats; db.load
    migrates legacy rows (v0.28+ rows load unchanged — no drift); applyTurnResult maintains the
    table (release deletes, claim re-inits from the gainer) inside the load-bearing order
    events → relations → territory → stateChanges → recompute → markers → nationOps, and
    recomputes every nation's derived stats (0-region guard).
  • No behavior drift: the prompt is unchanged, the stub is untouched, and existing unit +
    e2e assertions pass unchanged (identical-value seeding ⇒ weighted median = original;
    0-region guard). Tests: 225 (was 189) incl. new server/protocol.test.ts and
    scripts/region-geo.test.ts.

Verification

  • npm run typecheck / lint / test (225 passed) / build all green locally; region-geo.json
    • ne-admin0-pop.json land in dist/ at build.
  • e2e is the CI source of truth (LLM_STUB=1).
Fixes #46 **PR 1 of 2** for the per-region stats work: stats now live at region-key granularity (`GameState.regionStats`); `Nation.stats` is derived as the area×population-weighted median of a nation's owned regions; the LLM lever (per-region stats in the prompt) is PR 2 and is **not** in this change. **Not deployed** — production updates when PR 2 ships. ## What changed - **Data:** new committed `public/region-geo.json` (1,800 entries: area km² @2dp + integer population per region key) and `public/ne-admin0-pop.json` (Natural Earth admin-0 `POP_EST`, 249 codes — the small committed source; the 13 MB raw file is never committed). Population is spread from each country's `POP_EST` to its sub-regions proportional to area by largest-remainder, so per-country totals are preserved exactly (verified: 0 mismatches / 177 countries; global total 7,654,092,021). `scripts/build-region-geo.mjs` + `scripts/extract-admin0-pop.mjs` (new npm scripts `build:region-geo` / `extract:admin0-pop`). - **Types/protocol:** `RegionStats` + required `GameState.regionStats` (`shared/types.ts`); pure helpers in `shared/protocol.ts`: `weightedMedian`, `deriveNationStats`, `seedRegionStats`, `migrateRegionStats`. - **Server:** `server/regions.ts` loads the geo manifest (probes `public/` then `dist/`, optional/degrading) with `geoOf`/`hasGeo`; game creation seeds `regionStats`; `db.load` migrates legacy rows (v0.28+ rows load unchanged — no drift); `applyTurnResult` maintains the table (release deletes, claim re-inits from the gainer) inside the load-bearing order events → relations → territory → stateChanges → recompute → markers → nationOps, and recomputes every nation's derived stats (0-region guard). - **No behavior drift:** the prompt is unchanged, the stub is untouched, and existing unit + e2e assertions pass unchanged (identical-value seeding ⇒ weighted median = original; 0-region guard). Tests: 225 (was 189) incl. new `server/protocol.test.ts` and `scripts/region-geo.test.ts`. ## Verification - `npm run typecheck` / `lint` / `test` (225 passed) / `build` all green locally; `region-geo.json` + `ne-admin0-pop.json` land in `dist/` at build. - e2e is the CI source of truth (`LLM_STUB=1`).
kamilkrzywda added 1 commit 2026-10-15 02:18:22 +02:00
kamilkrzywda scheduled this pull request to auto merge when all checks succeed 2026-10-15 02:18:26 +02:00
kamilkrzywda merged commit 7fbc00ebbe into master 2026-10-15 02:20:55 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OpenCode/History#47