feat: nation-op split/merge — apply on confirm (playerPart, fresh ids, relations union) (P4b, #16)

This commit is contained in:
2026-10-14 13:42:46 +00:00
parent f72a59bf5e
commit 458e9be419
5 changed files with 562 additions and 71 deletions
+39 -24
View File
@@ -30,15 +30,14 @@ export interface TerritoryChange {
lose: string[];
}
// --- Nation-management ops (v0.23, #16 P4a: the hybrid "LLM proposes, player
// confirms" model) -----------------------------------------------------------
// --- Nation-management ops (v0.23 P4a / v0.24 P4b, #16: the hybrid "LLM
// proposes, player confirms" model) ------------------------------------------
//
// Unlike stat / relation / territory deltas, a NationOp proposed in a turn is
// NOT applied by that turn. It is stored as a *pending proposal* in
// `GameState.pendingNationOps` and only takes effect when the player confirms
// it (optionally editing it) or is dropped when the player rejects it. Rename
// is implemented in v0.23 (P4a); split and merge are declared here so the union
// is stable for P4b, and are defensively accepted-but-not-applied until then.
// it (optionally editing it) or is dropped when the player rejects it. All
// three kinds are implemented: rename (v0.23 P4a), split and merge (v0.24 P4b).
/**
* The identity reference a nation is addressed by inside a {@link NationOp}.
@@ -63,9 +62,13 @@ export interface RenameOp {
}
/**
* A **split** proposal (declared for #16 P4b; **not** applied by the engine yet
* — it is accepted into the pending list, but confirming it is a no-op until
* P4b lands). Describes the new nations a source nation is divided into.
* A **split** proposal (v0.24, #16 P4b). Describes the new nations a source
* nation is divided into. On confirm the source is dissolved: each part gets a
* fresh id, the region keys it was given (restricted to keys the source
* actually owns — first claim wins), fresh baseline stats, and a copy of the
* source's relations; any of the source's unclaimed keys go to `parts[0]`.
* When the source is the player's own nation the player chooses which part
* they lead (the confirm request's `edits.playerPart`, default `parts[0]`).
*/
export interface SplitOp {
kind: 'split';
@@ -76,9 +79,13 @@ export interface SplitOp {
}
/**
* A **merge** proposal (declared for #16 P4b; **not** applied by the engine yet
* — accepted into the pending list, no-op on confirm until P4b). Folds several
* nations into a single surviving nation.
* A **merge** proposal (v0.24, #16 P4b). Folds several nations into a single
* surviving nation. On confirm the survivor keeps its id (renamed to `into`,
* or to the confirm request's `edits.name`), its territory becomes the union
* of all inputs' territories, it keeps its own stats, and it inherits the
* union of all inputs' relations (its own winning per third party, else the
* first input's); every non-survivor input is removed. If the player's nation
* was a non-survivor input the player pointer is re-pointed to the survivor.
*/
export interface MergeOp {
kind: 'merge';
@@ -93,8 +100,8 @@ export interface MergeOp {
/**
* A structural operation on the set of nations that the LLM may propose in a
* turn (`TurnResult.nationOps`). See the section comment above for the hybrid
* propose→confirm semantics. Rename is implemented (P4a); split/merge are
* declared for P4b and are a safe no-op until then.
* propose→confirm semantics. All three kinds are implemented: rename (P4a),
* and split / merge (P4b, v0.24).
*/
export type NationOp = RenameOp | SplitOp | MergeOp;
@@ -125,12 +132,13 @@ export interface TurnResult {
*/
unowned: string[];
/**
* (v0.23, #16 P4a) Nation-management ops the LLM proposes this turn. These
* are NOT applied by the turn: each is appended to
* (v0.23 P4a / v0.24 P4b, #16) Nation-management ops the LLM proposes this
* turn. These are NOT applied by the turn: each is appended to
* `GameState.pendingNationOps` as a proposal awaiting the player's
* confirm/reject. Rename is the only kind the engine currently applies;
* split/merge are declared for P4b and are accepted-but-dropped. Empty on
* turns where the model proposes none.
* confirm/reject. All three kinds (rename, split, merge) are implemented —
* a confirmed rename changes `Nation.name`; a confirmed split dissolves the
* source into its parts; a confirmed merge folds its inputs into the
* survivor. Empty on turns where the model proposes none.
*/
nationOps: NationOp[];
}
@@ -240,15 +248,22 @@ export interface RemoveQueueItemResponse {
}
/**
* (v0.23, #16 P4a) Confirm a pending nation-op proposal by its index in
* `game.pendingNationOps`. A rename takes effect (the player may override the
* proposed name via `edits.name`); split/merge proposals are a no-op on
* confirm until P4b. No LLM call, no turn advance.
* (v0.23 P4a / v0.24 P4b, #16) Confirm a pending nation-op proposal by its
* index in `game.pendingNationOps`. A rename takes effect (the player may
* override the proposed name via `edits.name`); a split and a merge are fully
* applied (v0.24 P4b). No LLM call, no turn advance.
*/
export interface ConfirmNationOpRequest {
index: number;
/** Optional player edits applied on confirm (rename: the final display name). */
edits?: { name?: string };
/**
* Optional player edits applied on confirm.
* - `name`: the final display name — overrides a rename's `to`, a merge's
* `into`, or (for a split) the name of the part the player leads.
* - `playerPart`: for a split of the player's own nation, which part (index
* into the op's `parts`) the player will lead (default `0` when missing or
* out of range, so the player is never orphaned).
*/
edits?: { name?: string; playerPart?: number };
}
export interface ConfirmNationOpResponse {
game: GameState;