Region-level ownership foundation (Part 1 of #16) (v0.19.0) #34

Merged
kamilkrzywda merged 6 commits from feat/16-p1-region-ownership into master 2026-10-14 11:18:12 +02:00
Owner

Part 1 of #16 — region-level ownership foundation.

Flips internal territory ownership from whole countries (ISO_A3) to individual sub-regions,
addressed by stable, human-readable region keys (<name-slug>-<4char>, e.g. moselle-rygy,
ain-lqtt, western-alun).

What changed

  • Manifest: scripts/build-region-keys.mjs + public/region-keys.json — 1,796
    deterministic, globally-unique region keys derived from each sub-region's name slug + a
    4-char hash of its stable id (salted on within-country name collisions).
  • Contract flip (shared/): Nation.territory, RegionMap, TerritoryChange,
    regionOwner, and mapFeatureOwner now key by region key. mapFeatureOwner reads the
    feature's stamped properties.regionKey (never the repacked numeric feature.id, never the
    raw sub-region id — the #6 guard, now region-keyed).
  • Server (server/): new turf-free regions.ts loads the manifest + geo once at boot,
    expands each nation's country list to its sub-regions' keys at game creation, builds the LLM
    regions reference table (key → " (parent)"), and exposes the valid-key set;
    engine's runTurn/applyTurnResult/buildUserPrompt gain optional region-aware params so
    existing unit tests keep passing and hallucinated keys are ignored. The deterministic stub is
    unchanged.
  • Client: WorldMap resolves owner/flash/hover/click per region key and stamps
    properties.regionKey; NationPanel shows region names in the territory chips; the dossier's
    selected-region label drops the now-unused parent code.

Notes

  • Supersedes the abandoned feat/sub-region-territory branch, which prototyped raw
    feature-id keys (DEU-03). This branch (fresh off master) uses the slugged,
    human-readable key scheme instead — do not merge feat/sub-region-territory.
  • ⚠ Breaking: pre-v0.19 saved games are dropped. territory now holds region keys, not
    ISO_A3 codes, so old saves can't be read. Deploy = docker compose down -v (drop the
    history-data volume) then up -d --build; no migration.
  • #16 stays open — this references it but does not close it: P2 (LLM tools/function-calling
    for reliable region ops) and the no-man's-land rename/split/merge phases follow on top of this
    foundation.

Verification

Typecheck, lint, and the unit suite (now 71 tests / 7 files) pass; production build clean. The
format-agnostic e2e suite (legend/stats/dates) is covered by CI.

Part 1 of #16 — region-level ownership foundation. Flips internal territory ownership from whole countries (ISO_A3) to individual sub-regions, addressed by stable, human-readable region keys (`<name-slug>-<4char>`, e.g. `moselle-rygy`, `ain-lqtt`, `western-alun`). ## What changed - **Manifest**: `scripts/build-region-keys.mjs` + `public/region-keys.json` — 1,796 deterministic, globally-unique region keys derived from each sub-region's name slug + a 4-char hash of its stable id (salted on within-country name collisions). - **Contract flip** (`shared/`): `Nation.territory`, `RegionMap`, `TerritoryChange`, `regionOwner`, and `mapFeatureOwner` now key by region key. `mapFeatureOwner` reads the feature's stamped `properties.regionKey` (never the repacked numeric `feature.id`, never the raw sub-region id — the #6 guard, now region-keyed). - **Server** (`server/`): new turf-free `regions.ts` loads the manifest + geo once at boot, expands each nation's country list to its sub-regions' keys at game creation, builds the LLM `regions` reference table (key → "<name> (parent)"), and exposes the valid-key set; `engine`'s `runTurn`/`applyTurnResult`/`buildUserPrompt` gain optional region-aware params so existing unit tests keep passing and hallucinated keys are ignored. The deterministic stub is unchanged. - **Client**: `WorldMap` resolves owner/flash/hover/click per region key and stamps `properties.regionKey`; `NationPanel` shows region names in the territory chips; the dossier's selected-region label drops the now-unused parent code. ## Notes - **Supersedes the abandoned `feat/sub-region-territory` branch**, which prototyped raw feature-id keys (`DEU-03`). This branch (fresh off `master`) uses the slugged, human-readable key scheme instead — do not merge `feat/sub-region-territory`. - **⚠ Breaking: pre-v0.19 saved games are dropped.** `territory` now holds region keys, not ISO_A3 codes, so old saves can't be read. Deploy = `docker compose down -v` (drop the `history-data` volume) then `up -d --build`; no migration. - **#16 stays open** — this references it but does not close it: P2 (LLM tools/function-calling for reliable region ops) and the no-man's-land rename/split/merge phases follow on top of this foundation. ## Verification Typecheck, lint, and the unit suite (now 71 tests / 7 files) pass; production build clean. The format-agnostic e2e suite (legend/stats/dates) is covered by CI.
kamilkrzywda added 6 commits 2026-10-14 11:16:29 +02:00
Deterministic <name-slug>-<4char> key per sub-region: name via NFKD strip +
fold table (ə ø ð đ œ ł) + a-z0-9 collapse; 4-char from FNV-1a of the stable
feature id, salted on within-slug collision so all 1,796 keys are unique.
Builtins only (no @turf). Idempotent (byte-identical on re-run).
The single source of truth now keys ownership by region key (one per owned
sub-region) instead of a parent-country ISO_A3:

- shared/types.ts, shared/protocol.ts: TerritoryChange / RegionMap / regionOwner
  doc flips; mapFeatureOwner now reads the feature's stamped properties.regionKey
  (never the repacked numeric feature.id, nor the sub-region id — the #6 guard).
- server/regions.ts (new, turf-free): loads region-keys.json + regions.geo.json
  once at boot; expands a nation's ISO_A3 list to its sub-regions' keys, builds
  the LLM 'regions' reference table (key -> "name (parent)"), and exposes the
  set of valid keys. Degrades gracefully when the files are missing.
- server/engine.ts: buildSystemPrompt instructs the model to use region keys;
  buildUserPrompt gains an optional regionReference table; applyTurnResult gains
  an optional validKeys set so hallucinated keys are ignored.
- server/index.ts: loads region data at boot; expands each nation's country list
  to region keys at game creation; the jump route passes the reference + valid
  keys through runTurn.

The stub is unchanged (it emits an empty, region-key-conformant territoryChanges).
- src/lib/regions.ts (new): cached, turf-free same-origin fetches of
  /region-keys.json (feature id -> key) and /regions.geo.json (id -> name) so
  WorldMap and NationPanel share one fetch each.
- WorldMap.tsx: stamps each feature's properties.regionKey (from the manifest,
  by its stable id) and resolves owner / flash / hover / click from that key via
  the shared mapFeatureOwner; the per-region flash lights the exact sub-region
  whose owner changed; click carries only the sub-region name.
- NationPanel.tsx: territory chips show the region's human name (falling back
  to the raw key) with the key in the title / data-attr.
- App.tsx: the selected-region dossier label drops the now-unused parent code.
- map.test.ts: rewrite the fixture for region keys; the #6 regression guard now
  proves the owner resolves via properties.regionKey while the repacked numeric
  feature.id and the sub-region regionId do not.
- regions.test.ts (new): the loader sees 1,796 valid keys; FRA expands to its
  42 sub-region keys (all valid, all labelled '… (FRA)'); allTerritoryKeys
  unions correctly; and with no data it degrades to the raw input.
- engine.test.ts: buildUserPrompt includes a regions table only when provided;
  applyTurnResult with a validKeys set applies real keys and ignores bogus ones,
  and without one applies all (the legacy/unit path).
Bump to v0.19.0: region-level ownership (Part 1 of #16)
CI / build + unit + e2e (pull_request) Successful in 1m36s
b9ac79c57f
Version 0.19.0 + changelog. Breaking: pre-v0.19 saved games are dropped
(territory now holds region keys, not ISO_A3 codes) — deploy wipes the
history-data volume.
kamilkrzywda scheduled this pull request to auto merge when all checks succeed 2026-10-14 11:16:41 +02:00
kamilkrzywda merged commit 99ee93f6da into master 2026-10-14 11:18:12 +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#34