feat(#46 PR 2): per-region stat lever (regionChanges) + region-stat hover
CI / build + unit + e2e (pull_request) Successful in 2m31s

v0.29.0 — the LLM can now target individual regions' stats via
TurnResult.regionChanges (set/add per stat, clamped 0-100), applied
after nation-wide stateChanges and before the weighted-median
recompute. The map hover tooltip shows each region's own four stats.
The stub stays deterministic (regionChanges: []).

Fixes #46 (PR 2 of 2)
This commit is contained in:
2026-10-15 01:06:53 +00:00
parent 22805a3be6
commit cdabf16052
9 changed files with 625 additions and 32 deletions
+9 -2
View File
@@ -20,9 +20,9 @@ import type {
import type { GameState } from '../shared/types';
import { createLlmClient } from './llm';
import { makeStubLlmClient } from './stub';
import { askAdvisor, confirmNationOp, queueRoundItem, rejectNationOp, removeRoundItem, runTurn } from './engine';
import { askAdvisor, confirmNationOp, queueRoundItem, rejectNationOp, removeRoundItem, resolveRegionKey, runTurn } from './engine';
import { buildRegionMap } from './map';
import { allTerritoryKeys, centroidOf, expandCountriesToKeys, geoOf, hasAdjacency, hasCentroids, hasGeo, loadRegions, neighboursOf, referenceForKeys, validKeys } from './regions';
import { allTerritoryKeys, centroidOf, expandCountriesToKeys, geoOf, hasAdjacency, hasCentroids, hasGeo, loadRegions, neighboursOf, referenceForKeys, regionName, validKeys } from './regions';
import { createPresetStore } from './presets';
import { openDb, type Db } from './db';
@@ -307,6 +307,13 @@ app.post('/api/games/:id/jump', async (req, res) => {
// 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,
// (v0.29, #46 PR 2) region ref resolver: lets the model name a
// regionChanges target by its human-readable name (resolved
// case-insensitively against the region-key manifest) or its key;
// unknown / ambiguous refs are dropped. Always on — the resolver is
// pure (validKeys + regionName) and degrades to a key-only match when
// the manifest is absent.
regionKeyResolver: (ref) => resolveRegionKey(ref, validKeys(), (k) => regionName(k)),
});
db.save(state);
broadcast(id, 'turn', { state, result });