A sub-region can now end a turn unowned (released by a nation) and be re-claimed on a later turn.
What changed
TurnResult.unowned: string[] — region keys released to no man's land this turn. A re-claim is an ordinary gain on a currently-unowned key; a release and a claim of the same key in one turn → the claim wins.
GameState.unowned: string[] — the cumulative list of mid-game releases; required, [] at game start, and a key leaves the list when it is re-claimed.
applyTurnResult applies releases first (sole-owner only; 0-owner / 2+ owner / already-unowned / hallucinated keys are no-ops), then claims against the post-release snapshot — so the sole-ownership and "no key orphaned" invariants hold.
Map: a released key renders in a distinct visible grey (#3f3f46 fill / rgba(255,255,255,0.25) stroke) with a "No man's land" tooltip sub-line, distinct from the faint preset-unassigned look ("No owner"). The distinction is decided on the client from GameState.unowned via the shared regionFillStyle / unownedTooltipLabel helpers — never from the RegionMap. An ownerless region click leaves the dossier unchanged.
The prompt carries the current unowned list in the user payload and documents the unowned field + its rule in both tool and plain modes; the jump route's region reference includes released keys so the model can name them when re-claiming.
No save-format change:unowned defaults to [] on load (legacy SQLite rows), so this is a no-wipe deploy (history-data volume preserved).
Tests: unit 105 → 127 (new no-man's-land suite in engine.test.ts + map presentation-contract + db legacy-row tests). The deterministic stub never releases, so the stub-backed e2e suite is unchanged and stays green — no-man's-land is covered by unit tests only.
Gates (local): typecheck ✓ · lint ✓ · 127/127 unit tests ✓ · build ✓ (Playwright e2e runs in CI).
v0.22.0
Part 3 of #16 — **No man's land**.
A sub-region can now end a turn **unowned** (released by a nation) and be re-claimed on a later turn.
**What changed**
- `TurnResult.unowned: string[]` — region keys released to no man's land *this* turn. A **re-claim** is an ordinary `gain` on a currently-unowned key; a release and a claim of the *same* key in one turn → the claim wins.
- `GameState.unowned: string[]` — the cumulative list of mid-game releases; required, `[]` at game start, and a key leaves the list when it is re-claimed.
- `applyTurnResult` applies **releases first** (sole-owner only; 0-owner / 2+ owner / already-unowned / hallucinated keys are no-ops), **then claims** against the post-release snapshot — so the sole-ownership and "no key orphaned" invariants hold.
- **Map:** a released key renders in a distinct visible grey (`#3f3f46` fill / `rgba(255,255,255,0.25)` stroke) with a "No man's land" tooltip sub-line, distinct from the faint preset-unassigned look ("No owner"). The distinction is decided on the client from `GameState.unowned` via the shared `regionFillStyle` / `unownedTooltipLabel` helpers — never from the `RegionMap`. An ownerless region click leaves the dossier unchanged.
- The prompt carries the current `unowned` list in the user payload and documents the `unowned` field + its rule in both tool and plain modes; the jump route's region reference includes released keys so the model can name them when re-claiming.
- **No save-format change:** `unowned` defaults to `[]` on load (legacy SQLite rows), so this is a **no-wipe deploy** (`history-data` volume preserved).
- **Tests:** unit 105 → 127 (new no-man's-land suite in `engine.test.ts` + map presentation-contract + db legacy-row tests). The deterministic stub never releases, so the stub-backed e2e suite is unchanged and stays green — no-man's-land is covered by unit tests only.
**Gates (local):** typecheck ✓ · lint ✓ · 127/127 unit tests ✓ · build ✓ (Playwright e2e runs in CI).
v0.22.0
- shared/protocol.ts: TurnResult.unowned (keys released this turn) + the map
presentation contract (regionFillStyle / unownedTooltipLabel, hue-less grey)
- shared/types.ts: required GameState.unowned (cumulative mid-game releases;
[] at init, distinct from preset-unassigned regions)
- server/engine.ts: applyTurnResult applies releases first (sole-owner only,
never fabricating no man's land from unassigned keys), then claims against
the post-release snapshot (gain on an ownerless key = re-claim); lone
release is isMeaningful; prompts + user payload carry the unowned field
- server/stub.ts: unchanged behavior (never releases; e2e stays stable)
- server/db.ts: legacy rows load with unowned defaulted to []
- server/index.ts: create-game sets []; jump reference table includes
released keys so the model can name them for re-claim
- WorldMap/App: unowned prop -> visible grey + 'No man's land' tooltip
- unit tests: 105 -> 127 (release/re-claim suites, presentation contract,
legacy-row normalization)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part 3 of #16 — No man's land.
A sub-region can now end a turn unowned (released by a nation) and be re-claimed on a later turn.
What changed
TurnResult.unowned: string[]— region keys released to no man's land this turn. A re-claim is an ordinarygainon a currently-unowned key; a release and a claim of the same key in one turn → the claim wins.GameState.unowned: string[]— the cumulative list of mid-game releases; required,[]at game start, and a key leaves the list when it is re-claimed.applyTurnResultapplies releases first (sole-owner only; 0-owner / 2+ owner / already-unowned / hallucinated keys are no-ops), then claims against the post-release snapshot — so the sole-ownership and "no key orphaned" invariants hold.#3f3f46fill /rgba(255,255,255,0.25)stroke) with a "No man's land" tooltip sub-line, distinct from the faint preset-unassigned look ("No owner"). The distinction is decided on the client fromGameState.unownedvia the sharedregionFillStyle/unownedTooltipLabelhelpers — never from theRegionMap. An ownerless region click leaves the dossier unchanged.unownedlist in the user payload and documents theunownedfield + its rule in both tool and plain modes; the jump route's region reference includes released keys so the model can name them when re-claiming.unowneddefaults to[]on load (legacy SQLite rows), so this is a no-wipe deploy (history-datavolume preserved).engine.test.ts+ map presentation-contract + db legacy-row tests). The deterministic stub never releases, so the stub-backed e2e suite is unchanged and stays green — no-man's-land is covered by unit tests only.Gates (local): typecheck ✓ · lint ✓ · 127/127 unit tests ✓ · build ✓ (Playwright e2e runs in CI).
v0.22.0