feat: #66 contested regions / frontlines — 12th ability (v0.44.0)
CI / build + unit + e2e (pull_request) Successful in 2m10s
CI / build + unit + e2e (pull_request) Successful in 2m10s
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.
This commit is contained in:
@@ -783,6 +783,53 @@ export interface SeaOps {
|
||||
control: SeaControlOp[];
|
||||
}
|
||||
|
||||
/**
|
||||
* (v0.44.0, #66) A single `open` op: make a region **contested** — a war
|
||||
* frontline that is in **no** nation's `territory` and not in `unowned` (a
|
||||
* third region state, besides "owned by one nation" and "no man's land").
|
||||
* `regionKey` is the region key (or a resolvable region name); `parties` are
|
||||
* two or more nation refs (ids or names), resolved via the shared
|
||||
* `resolveNationRef` — the stored `ContestedRegion.parties` are the resolved
|
||||
* nation ids.
|
||||
*/
|
||||
export interface ContestOp {
|
||||
/** The region key to make contested (the model names it by key or name). */
|
||||
regionKey: string;
|
||||
/**
|
||||
* The nations contesting the region — two or more refs (ids or names). At
|
||||
* least two distinct, resolvable nations are required, and the region's
|
||||
* sole owner (if any) must be at war with one of them (or, for a
|
||||
* no-man's-land region, two of the parties must be at war with each other).
|
||||
*/
|
||||
parties: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The contested-region block of a `TurnResult`. Required; default
|
||||
* `{ open: [], resolve: [] }`. `open` makes a region a contested frontline
|
||||
* (it leaves its owner's `territory` and `unowned` — sole ownership is
|
||||
* preserved); `resolve` ends a contest, releasing the region to no man's
|
||||
* land — unless a concurrent winning claim by one of the parties re-owns it
|
||||
* (re-initialising its `regionStats`).
|
||||
*/
|
||||
export interface ContestedOps {
|
||||
/** Regions to make contested this turn. */
|
||||
open: ContestOp[];
|
||||
/** Region keys to resolve (release to no man's land) this turn. */
|
||||
resolve: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A contested region — a region key that is a war frontline: in **no**
|
||||
* nation's `territory` and not in `unowned`, contested by `parties` (resolved
|
||||
* nation ids, two or more). Stored in `GameState.contested` (unique by
|
||||
* `regionKey`).
|
||||
*/
|
||||
export interface ContestedRegion {
|
||||
regionKey: string;
|
||||
parties: string[];
|
||||
}
|
||||
|
||||
/** The exact JSON object the world-simulator LLM must return for a turn. */
|
||||
export interface TurnResult {
|
||||
date: string;
|
||||
@@ -992,6 +1039,19 @@ export interface TurnResult {
|
||||
* `{ control: [] }`.
|
||||
*/
|
||||
sea: SeaOps;
|
||||
/**
|
||||
* (v0.44.0, #66) Contested regions / frontlines: `open` makes a region a
|
||||
* contested war frontline (it leaves its owner's `territory` and `unowned`
|
||||
* — a third region state in no territory and not no man's land — and is
|
||||
* contested by two or more nations at war); `resolve` ends a contest,
|
||||
* releasing the region to no man's land unless a concurrent winning claim
|
||||
* by one of the parties re-owns it. A region with a sole owner not at war
|
||||
* with any party, or two or more owners, cannot be contested. Sole
|
||||
* ownership is never broken; a still-contested key is not a valid normal
|
||||
* claim target. At most 1–2 per turn, each narrated in `events`. Required;
|
||||
* default `{ open: [], resolve: [] }`.
|
||||
*/
|
||||
contested: ContestedOps;
|
||||
/**
|
||||
* (v0.27, #44) Map-marker ops this turn. `add` places new named markers
|
||||
* (the engine positions each at its region's centroid + a small
|
||||
@@ -1233,6 +1293,27 @@ export const UNOWNED_STROKE = 'rgba(255,255,255,0.14)';
|
||||
/** The border of an owned region (brighter than unowned). */
|
||||
export const OWNED_STROKE = 'rgba(255,255,255,0.30)';
|
||||
|
||||
/**
|
||||
* The distinct, visible fill + stroke for a **contested** region (a war
|
||||
* frontline — a key that is in no nation's `territory` and not in `unowned`).
|
||||
* Reddish/orange to read as a "hot" frontline on the dark basemap — clearly
|
||||
* distinct from a nation color and from the no-man's-land grey. The contested
|
||||
* state is decided by the client (via `GameState.contested`), never by the
|
||||
* `RegionMap` (which only knows owned vs absent).
|
||||
*/
|
||||
export const CONTESTED_FILL = '#f97316';
|
||||
export const CONTESTED_STROKE = 'rgba(255,237,213,0.55)';
|
||||
|
||||
/**
|
||||
* The hover-tooltip sub-line for a contested region: a short "Contested:
|
||||
* A vs B" line naming the contesting parties. A missing party name (a nation
|
||||
* removed by a merge/split) degrades to a dash, never crashes.
|
||||
*/
|
||||
export function contestedTooltipLabel(parties: string[]): string {
|
||||
const names = parties.map((p) => p || '—');
|
||||
return `Contested: ${names.join(' vs ')}`;
|
||||
}
|
||||
|
||||
/** The hover-tooltip sub-line for a region with no owning nation. */
|
||||
export function unownedTooltipLabel(isNoMansLand: boolean): string {
|
||||
return isNoMansLand ? "No man's land" : 'No owner';
|
||||
|
||||
Reference in New Issue
Block a user