feat: #65 sea control — 11th ability (v0.43.0)
CI / build + unit + e2e (pull_request) Successful in 2m33s
CI / build + unit + e2e (pull_request) Successful in 2m33s
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.
This commit is contained in:
@@ -548,6 +548,41 @@ history/
|
||||
re-claim of no-man's-land; the implemented, approved design is this richer four-kind
|
||||
nation-to-nation **stat** layer that deliberately never moves territory (a no-man's-land
|
||||
re-claim stays the `regionChanges` lever's job).
|
||||
- **Sea control — sole control of a fixed set of 12 named sea zones with a deterministic per-turn bonus (v0.43.0, #65).**
|
||||
`TurnResult.sea = SeaOps = { control: SeaControlOp[] }` (required, default `{ control: [] }`);
|
||||
`SeaControlOp = { zone, by }` where `zone` is one of the 12 fixed sea-zone ids from
|
||||
`SEA_ZONES` in `shared/protocol.ts` (`north-atlantic`, `south-atlantic`, `pacific`, `indian`,
|
||||
`mediterranean`, `north-sea`, `baltic`, `arctic`, `black-sea`, `gulf-mexico`, `red-sea`,
|
||||
`sargasso`) and `by` is a nation ref (id first, then a case-insensitive unique name, via the
|
||||
shared `resolveNationRef`; unknown / ambiguous → dropped, never thrown). Like the wars and
|
||||
industry layers this is **durable** (unlike the turn-transient hybrid / disasters / migration
|
||||
layers): it owns the `GameState.seaZones` field (required, `[]` at creation; a legacy SQLite
|
||||
row without the field loads as `[]` — via the generic `ABILITIES` `stateFields` loop in
|
||||
`db.ts`, no `db.ts` code change needed). `applySeaControl` (run in the `ABILITIES` loop after
|
||||
territory / wars / economy, before markers) upserts the turn's ops into `next.seaZones`:
|
||||
per zone (in first-appearance order) a single surviving op sets or replaces the zone's
|
||||
controller (replace in place — preserving position — or append); two or more surviving ops
|
||||
on the same zone for different resolved nations make the zone **contested** — the higher
|
||||
current `stats.military` wins (a tie goes to the earlier-listed op) — deterministic, no
|
||||
hidden randomness. The layer **never touches `territory` / `unowned`** — it is fully parallel
|
||||
to but independent of land. Every committed turn (including a turn with an empty `control`),
|
||||
`applySeaZoneBonuses` grants each existing claim's controller `SEA_ZONE_BONUS = 1` military
|
||||
+ 1 economy (clamped to 0–100, mirrored to every region the nation owns, then the nation's
|
||||
derived `stats` re-derived with the same 0-region / no-entry guards as the economy / migration
|
||||
layers, so a 0-region nation keeps its directly-mutated value); a nation controlling several
|
||||
zones stacks the bonus; a claim whose nation no longer exists (removed by a merge / split
|
||||
this turn) is skipped — the claim is kept, never thrown. `isMeaningful` is
|
||||
`control.length > 0`. The system prompt documents the field + constraints (at most 1–2 claims
|
||||
per turn, each narrated in `events`); the user payload **always** carries the current
|
||||
`seaZones` list (even when empty) — `payload()` → `{ seaZones: ctx.seaZones ?? [] }` — the
|
||||
one ability whose `payload()` is not `{}` (the durable list, unlike the turn-transient
|
||||
layers). The **stub never** proposes a sea op (`{ control: [] }`), so e2e (CI, `LLM_STUB=1`)
|
||||
is unaffected and the path is unit-tested only (`server/abilities/sea.test.ts`).
|
||||
**Design note:** the Gitea #65 body sketched a `naval` layer with a free-form `zones`
|
||||
array; the implemented, approved design is this richer `sea` layer with a **fixed set of
|
||||
12 named zones**, sole-control semantics, contest-by-higher-military, and a durable
|
||||
`seaZones` list — a cleaner, more deterministic model that the LLM can reason about without
|
||||
inventing zone names each turn.
|
||||
- **Map click → dossier target (owner resolved from the RegionMap, by region key).**
|
||||
Clicking a sub-region does NOT open a popup — it switches the dossier to that sub-region's
|
||||
owning nation (`focusNation(ownerId, {name})` in `App.tsx`, which also records which region was
|
||||
|
||||
Reference in New Issue
Block a user