v0.28.0 (#46): per-region stats — area×population-weighted-median Nation.stats (PR 1 of 2)
CI / build + unit + e2e (pull_request) Successful in 2m34s

This commit is contained in:
2026-10-15 00:17:19 +00:00
parent 327fffb789
commit 22805a3be6
19 changed files with 8743 additions and 29 deletions
+14 -2
View File
@@ -8,7 +8,7 @@ import { existsSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { deepClone } from '../shared/protocol';
import { deepClone, seedRegionStats } from '../shared/protocol';
import type {
AddActionRequest,
AddDiplomacyRequest,
@@ -22,7 +22,7 @@ import { createLlmClient } from './llm';
import { makeStubLlmClient } from './stub';
import { askAdvisor, confirmNationOp, queueRoundItem, rejectNationOp, removeRoundItem, runTurn } from './engine';
import { buildRegionMap } from './map';
import { allTerritoryKeys, centroidOf, expandCountriesToKeys, hasAdjacency, hasCentroids, loadRegions, neighboursOf, referenceForKeys, validKeys } from './regions';
import { allTerritoryKeys, centroidOf, expandCountriesToKeys, geoOf, hasAdjacency, hasCentroids, hasGeo, loadRegions, neighboursOf, referenceForKeys, validKeys } from './regions';
import { createPresetStore } from './presets';
import { openDb, type Db } from './db';
@@ -136,11 +136,18 @@ app.post('/api/games', (req: Request, res) => {
pendingNationOps: [],
// (v0.27, #44) No markers on a fresh game; the LLM places them over time.
markers: [],
// (v0.28, #46) Per-region stats are seeded below, once territories hold
// real region keys — each region starts as a copy of its owner's initial
// stats, so the derived aggregate equals the nation's stats on turn 1.
regionStats: {},
};
// v0.19 (#16 P1): expand each nation's ISO_A3 country list to the region keys
// of all its sub-regions, so ownership (and the choropleth) is at region
// granularity from the very first turn. Preset JSONs stay country-level.
for (const n of Object.values(state.nations)) n.territory = expandCountriesToKeys(n.territory);
// (v0.28, #46): seed per-region stats AFTER the expansion so each owned
// region key gets its owner's initial stats (see seedRegionStats).
state.regionStats = seedRegionStats(state.nations);
db.save(state);
res.json({ game: state });
});
@@ -295,6 +302,11 @@ app.post('/api/games/:id/jump', async (req, res) => {
// centroid (without it, marker adds are dropped; existing markers are
// still maintained).
centroidOf: hasCentroids() ? (key) => centroidOf(key) : undefined,
// (v0.28, #46) when the region-geo manifest (area + population) loaded,
// hand the engine the resolver so the end-of-turn recompute weights each
// owned region by area×population (without it the aggregate degrades to
// an unweighted median — still region-consistent, just unweighted).
geoOf: hasGeo() ? (key) => geoOf(key) : undefined,
});
db.save(state);
broadcast(id, 'turn', { state, result });