feat: #66 contested regions / frontlines (v0.44.0) #79

Merged
kamilkrzywda merged 1 commits from feat/66-contested into master 2026-10-16 17:26:34 +02:00
Owner

Summary

Implements Feature #66 — Contested regions / frontlines as the 12th modular LLM ability (v0.44.0): a third region state — a war frontline that is in no nation's territory and not in unowned / no-man's-land. The LLM can open a region as a contested frontline (its sole owner is at war with a party, or it is no-man's-land whose parties are at war) and resolve an existing one (released to no man's land — unless a party's concurrent claim re-owns it this turn).

Design notes

  • Deviation from the issue body (#66). The Gitea #66 body sketched a two-owner design (disputed regions with fronts, explicitly "relaxing sole-ownership"). The implemented, approved design is a third region state: a contested key is in no nation's territory and not in unowned, with open / resolve ops and a minimum of two contesting parties tied to an active war. This keeps the sacrosanct sole-ownership invariant intact (a key is never in two territories) while still modelling a war frontline.
  • Map legend stays at 7. The client derives the legend from the server RegionMap owners only; a contested key is absent from that RegionMap (it stays owned-vs-absent), so it adds no legend entry — the existing e2e legend assertions (7 items) are unaffected. The contested fill / tooltip is rendered client-side (like no-man's-land), with the contested check running before unowned/owned.

Guarantees

  • No engine randomness — the LLM declares; the engine applies fixed, deterministic rules.
  • No new runtime deps; shared/ stays runtime-dep-free; no @turf/@types/geojson in any .ts.
  • Durable GameState.contested — legacy SQLite rows load as [] via the generic ABILITIES stateFields loop in db.ts (no db.ts code change).
  • The stub is contested-inert ({ open: [], resolve: [] }), so e2e (CI, LLM_STUB=1) is unaffected; the apply path is unit-tested only (52 new tests in server/abilities/contested.test.ts, each asserting the sole-ownership invariants).
  • All existing unit tests green (additive): 682 passed (20 files).

Verification

npm run typecheck + npm run lint + npm test (682/682) + npm run build — all green locally. E2e is the CI source of truth (runs on this PR).

Checklist

  • Changelog entry (CHANGELOG.md → v0.44.0)
  • Version bump (package.json → 0.44.0)
  • Unit tests (52 new + all existing green)
  • Prompt goldens regenerated (system + user)
  • No production deploy (manual, post-merge)

Fixes #66

## Summary Implements **Feature #66 — Contested regions / frontlines** as the **12th modular LLM ability** (v0.44.0): a **third region state** — a war **frontline** that is in **no** nation's `territory` and **not** in `unowned` / no-man's-land. The LLM can **`open`** a region as a contested frontline (its sole owner is at war with a party, or it is no-man's-land whose parties are at war) and **`resolve`** an existing one (released to no man's land — unless a party's concurrent claim re-owns it this turn). ## Design notes - **Deviation from the issue body (#66).** The Gitea #66 body sketched a *two-owner* design (`disputed` regions with `fronts`, explicitly "relaxing sole-ownership"). The implemented, approved design is a **third region state**: a contested key is in **no** nation's `territory` and **not** in `unowned`, with `open` / `resolve` ops and a **minimum of two** contesting parties tied to an **active** war. This keeps the sacrosanct **sole-ownership invariant** intact (a key is never in two territories) while still modelling a war frontline. - **Map legend stays at 7.** The client derives the legend from the server `RegionMap` owners only; a contested key is **absent** from that `RegionMap` (it stays owned-vs-absent), so it adds no legend entry — the existing e2e legend assertions (7 items) are unaffected. The contested fill / tooltip is rendered client-side (like no-man's-land), with the contested check running **before** unowned/owned. ## Guarantees - **No engine randomness** — the LLM declares; the engine applies fixed, deterministic rules. - **No new runtime deps**; `shared/` stays runtime-dep-free; no `@turf`/`@types/geojson` in any `.ts`. - **Durable** `GameState.contested` — legacy SQLite rows load as `[]` via the generic `ABILITIES` `stateFields` loop in `db.ts` (no `db.ts` code change). - The **stub is contested-inert** (`{ open: [], resolve: [] }`), so **e2e (CI, `LLM_STUB=1`) is unaffected**; the apply path is unit-tested only (52 new tests in `server/abilities/contested.test.ts`, each asserting the sole-ownership invariants). - All existing unit tests green (additive): **682 passed** (20 files). ## Verification `npm run typecheck` + `npm run lint` + `npm test` (682/682) + `npm run build` — all green locally. E2e is the CI source of truth (runs on this PR). ## Checklist - [x] Changelog entry (`CHANGELOG.md` → v0.44.0) - [x] Version bump (`package.json` → 0.44.0) - [x] Unit tests (52 new + all existing green) - [x] Prompt goldens regenerated (system + user) - [x] No production deploy (manual, post-merge) Fixes #66
kamilkrzywda added 1 commit 2026-10-16 17:22:51 +02:00
feat: #66 contested regions / frontlines — 12th ability (v0.44.0)
CI / build + unit + e2e (pull_request) Successful in 2m10s
5477336537
A third region state (contested frontline): a region in no nation's territory and not in unowned, opened/resolved by the LLM, tied to an active war. Sole-ownership invariant preserved. Durable GameState.contested; contested-inert stub; map legend stays at 7.
kamilkrzywda scheduled this pull request to auto merge when all checks succeed 2026-10-16 17:26:32 +02:00
kamilkrzywda merged commit 0a6c5aef9c into master 2026-10-16 17:26:34 +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#79