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.
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.
Promote the bare 'war' relation to a named War entity in GameState.wars:
- War: { id, sides[], declaredTurn, declaredDate?, aims?, status, front? }
- TurnResult.wars: { declare: WarDeclareOp[], end: WarEndOp[] }
- syncWarRelations: derive 'war' from active wars, fallback to 'hostile' on end
- Mutual-defense drag-in: belligerent allies added to sides (one level)
- Bare 'war' back-compat: never downgraded, never force-created
- Stub: empty wars (e2e unaffected); ~30 new unit tests
v0.33.0
- Add a hard military conquest gate (canConquer, CONQUEST_MILITARY_MARGIN=15)
enforced in applyTurnResult: a contested invasion is repelled when the
attacker's military is more than 15 below the defender's; re-claims of
unowned regions are never gated. Emits a new 'repelled' event.
- Document the soft levers in the system prompt: stability<30 + gathering
marker -> non-military takeover; economy growth/decay drives other stats.
- Emit bounded frontierMilitary (per-neighbour balance) and takeoverRisks
hints in the user payload (only when present).
- repelled outcome color (#9f1239); full unit test coverage; stub untouched.
- Bump to v0.30.0.
Fixes#50