Adds sea control as the 11th modular LLM ability (server/abilities/sea.ts) — a fixed set of 12 named sea zones with sole-control semantics, fully parallel to but independent of land.
TurnResult.sea=SeaOps={control: SeaControlOp[]}// SeaControlOp = { zone, by }
Zones — 12 fixed ids (SEA_ZONES in shared/protocol.ts): north-atlantic, south-atlantic, pacific, indian, mediterranean, north-sea, baltic, arctic, black-sea, gulf-mexico, red-sea, sargasso.
Contest — multiple ops on the same zone: the higher currentstats.military wins (tie → the earlier-listed op). Deterministic, no hidden randomness.
Durable — owns GameState.seaZones (required, [] at creation; legacy SQLite rows load as [] via the generic ABILITIESstateFields loop in db.ts — nodb.ts code change).
Per-turn bonus — every committed turn (even with an empty control), each existing claim grants its controller +1 military + 1 economy (clamped, mirrored to owned regions, derived stats re-derived; multi-zone claims stack; a stale controller is skipped, claim kept).
Land untouched — the layer never touches territory / unowned; sole ownership is sacrosanct.
Always-emitted payload — payload() → { seaZones: ctx.seaZones ?? [] } (the one ability whose user-payload contribution is not {}); the user.txt golden intentionally gains "seaZones": [] after industry. System goldens re-baselined (+1 schema line, +1 constraint bullet).
Stub-inert — the stub never proposes a sea op ({ control: [] }), so the CI e2e (LLM_STUB=1) is unaffected; the apply path is unit-tested (server/abilities/sea.test.ts, ~40 tests).
Design note — naval → sea
Gitea #65's body sketched a free-form naval layer with an arbitrary zones array (the LLM would invent zone names each turn). The implemented, approved design is this sea layer: a fixed set of 12 named zones with sole-control semantics and contest-by-higher-military — a cleaner, fully deterministic model the LLM can reason about without inventing names. The deviation is documented in CHANGELOG.md and the new AGENTS.md convention bullet.
Verification
npm run typecheck ✓ · npm run lint ✓ · npm test (629 tests) ✓ · npm run build ✓
## Summary
Adds **sea control** as the **11th modular LLM ability** (`server/abilities/sea.ts`) — a fixed set of **12 named sea zones** with sole-control semantics, fully parallel to but independent of land.
```ts
TurnResult.sea = SeaOps = { control: SeaControlOp[] } // SeaControlOp = { zone, by }
```
- **Zones** — 12 fixed ids (`SEA_ZONES` in `shared/protocol.ts`): `north-atlantic, south-atlantic, pacific, indian, mediterranean, north-sea, baltic, arctic, black-sea, gulf-mexico, red-sea, sargasso`.
- **Contest** — multiple ops on the same zone: the higher **current** `stats.military` wins (tie → the earlier-listed op). Deterministic, no hidden randomness.
- **Durable** — owns `GameState.seaZones` (required, `[]` at creation; legacy SQLite rows load as `[]` via the generic `ABILITIES` `stateFields` loop in `db.ts` — **no** `db.ts` code change).
- **Per-turn bonus** — every committed turn (even with an empty `control`), each existing claim grants its controller `+1 military` + `1 economy` (clamped, mirrored to owned regions, derived stats re-derived; multi-zone claims stack; a stale controller is skipped, claim kept).
- **Land untouched** — the layer never touches `territory` / `unowned`; sole ownership is sacrosanct.
- **Always-emitted payload** — `payload()` → `{ seaZones: ctx.seaZones ?? [] }` (the one ability whose user-payload contribution is not `{}`); the `user.txt` golden intentionally gains `"seaZones": []` after `industry`. System goldens re-baselined (+1 schema line, +1 constraint bullet).
- **Stub-inert** — the stub never proposes a sea op (`{ control: [] }`), so the CI e2e (`LLM_STUB=1`) is unaffected; the apply path is unit-tested (`server/abilities/sea.test.ts`, ~40 tests).
### Design note — naval → sea
Gitea #65's body sketched a free-form **`naval`** layer with an arbitrary `zones` array (the LLM would invent zone names each turn). The implemented, approved design is this **`sea`** layer: a **fixed** set of 12 named zones with sole-control semantics and contest-by-higher-military — a cleaner, fully deterministic model the LLM can reason about without inventing names. The deviation is documented in `CHANGELOG.md` and the new `AGENTS.md` convention bullet.
## Verification
- `npm run typecheck` ✓ · `npm run lint` ✓ · `npm test` (629 tests) ✓ · `npm run build` ✓
- e2e is the CI source of truth (`LLM_STUB=1`)
Fixes #65
Adds the sea ability: a fixed set of 12 named sea zones with sole-control
semantics, contest-by-higher-military, and a deterministic per-turn
+1 military/+1 economy bonus for each claimed zone. Durable (owns
GameState.seaZones), additive migration, stub-inert (e2e unaffected).
Fixes#65
Design note: Gitea #65 sketched a free-form 'naval' layer; the implemented
approved design is this richer 'sea' layer with a fixed zone set.
kamilkrzywda
scheduled this pull request to auto merge when all checks succeed 2026-10-16 14:53:47 +02:00
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.
Summary
Adds sea control as the 11th modular LLM ability (
server/abilities/sea.ts) — a fixed set of 12 named sea zones with sole-control semantics, fully parallel to but independent of land.SEA_ZONESinshared/protocol.ts):north-atlantic, south-atlantic, pacific, indian, mediterranean, north-sea, baltic, arctic, black-sea, gulf-mexico, red-sea, sargasso.stats.militarywins (tie → the earlier-listed op). Deterministic, no hidden randomness.GameState.seaZones(required,[]at creation; legacy SQLite rows load as[]via the genericABILITIESstateFieldsloop indb.ts— nodb.tscode change).control), each existing claim grants its controller+1 military+1 economy(clamped, mirrored to owned regions, derived stats re-derived; multi-zone claims stack; a stale controller is skipped, claim kept).territory/unowned; sole ownership is sacrosanct.payload()→{ seaZones: ctx.seaZones ?? [] }(the one ability whose user-payload contribution is not{}); theuser.txtgolden intentionally gains"seaZones": []afterindustry. System goldens re-baselined (+1 schema line, +1 constraint bullet).{ control: [] }), so the CI e2e (LLM_STUB=1) is unaffected; the apply path is unit-tested (server/abilities/sea.test.ts, ~40 tests).Design note — naval → sea
Gitea #65's body sketched a free-form
navallayer with an arbitraryzonesarray (the LLM would invent zone names each turn). The implemented, approved design is thissealayer: a fixed set of 12 named zones with sole-control semantics and contest-by-higher-military — a cleaner, fully deterministic model the LLM can reason about without inventing names. The deviation is documented inCHANGELOG.mdand the newAGENTS.mdconvention bullet.Verification
npm run typecheck✓ ·npm run lint✓ ·npm test(629 tests) ✓ ·npm run build✓LLM_STUB=1)Fixes #65