Nation-op proposals (P4a, #16): protocol types, pendingNationOps state, engine propose/resolve (stage 1)
- shared/protocol.ts: RenameOp/SplitOp/MergeOp + NationOp union; TurnResult.nationOps (required); ConfirmNationOp/RejectNationOp request+response types - shared/types.ts: GameState.pendingNationOps (type-only import of NationOp — no runtime cycle) - server/engine.ts: validateNationOps parser wired into validateTurnResult; isMeaningful counts proposals; applyTurnResult appends to the surviving pending list (never applies); runTurn fallback literal; system prompt gains the nationOps JSON field + rename proposal rule (both modes); new resolveNationRef/applyNationOp/confirmNationOp/rejectNationOp (id-first then unique case-insensitive name; rename changes Nation.name only; split/merge no-op until P4b; index validation returns null for 400) - server/db.ts: legacy rows load with pendingNationOps defaulted to [] - server/index.ts: fresh games start with pendingNationOps: [] - server/stub.ts: deterministicTurn returns nationOps: [] (the stub never proposes — unit-only coverage, matching P3) - tests: fixture updates (engine/db/map/regions) + new suites for resolveNationRef, validateNationOps, proposal lifecycle, confirm/reject (151 tests total)
This commit is contained in:
@@ -30,6 +30,74 @@ export interface TerritoryChange {
|
||||
lose: string[];
|
||||
}
|
||||
|
||||
// --- Nation-management ops (v0.23, #16 P4a: 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.
|
||||
|
||||
/**
|
||||
* The identity reference a nation is addressed by inside a {@link NationOp}.
|
||||
* The LLM may use either the nation's stable `id` (the `nations` record key,
|
||||
* e.g. `f`) or its human-readable `name` (e.g. `France`); the engine resolves
|
||||
* it to a concrete `nationId` (id first, then a unique name match — see the
|
||||
* engine's `resolveNationRef`).
|
||||
*/
|
||||
|
||||
/**
|
||||
* A **rename** proposal: change a nation's human-readable `name`. Only the
|
||||
* `name` changes — the stable `id`, its territory keys, its relations (keyed
|
||||
* by id), and the player pointer are all left untouched, so nothing downstream
|
||||
* breaks. `to` is the new display name.
|
||||
*/
|
||||
export interface RenameOp {
|
||||
kind: 'rename';
|
||||
/** The target nation's id or current name (see the note above). */
|
||||
nationRef: string;
|
||||
/** The new display name to apply when the player confirms. */
|
||||
to: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export interface SplitOp {
|
||||
kind: 'split';
|
||||
/** The id/name of the nation being split. */
|
||||
nationRef: string;
|
||||
/** The new nations created from the split. */
|
||||
parts: Array<{ name: string; territory?: string[] }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export interface MergeOp {
|
||||
kind: 'merge';
|
||||
/** The id/name of every nation folded into the result. */
|
||||
nationRefs: string[];
|
||||
/** The id/name of the nation that survives and keeps its identity. */
|
||||
survivingRef: string;
|
||||
/** The name to give the merged nation. */
|
||||
into: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export type NationOp = RenameOp | SplitOp | MergeOp;
|
||||
|
||||
/** One narrative event the LLM proposes for this turn. */
|
||||
export interface TurnEventSpec {
|
||||
nation: string;
|
||||
@@ -56,6 +124,15 @@ export interface TurnResult {
|
||||
* empty on normal turns.
|
||||
*/
|
||||
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
|
||||
* `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.
|
||||
*/
|
||||
nationOps: NationOp[];
|
||||
}
|
||||
|
||||
/** Per-region map payload: region key -> owning nation + its stable color. */
|
||||
@@ -162,6 +239,32 @@ export interface RemoveQueueItemResponse {
|
||||
game: GameState;
|
||||
}
|
||||
|
||||
/**
|
||||
* (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.
|
||||
*/
|
||||
export interface ConfirmNationOpRequest {
|
||||
index: number;
|
||||
/** Optional player edits applied on confirm (rename: the final display name). */
|
||||
edits?: { name?: string };
|
||||
}
|
||||
export interface ConfirmNationOpResponse {
|
||||
game: GameState;
|
||||
}
|
||||
|
||||
/**
|
||||
* (v0.23, #16 P4a) Reject (drop) a pending nation-op proposal by its index
|
||||
* in `game.pendingNationOps` without applying it. No LLM call, no turn advance.
|
||||
*/
|
||||
export interface RejectNationOpRequest {
|
||||
index: number;
|
||||
}
|
||||
export interface RejectNationOpResponse {
|
||||
game: GameState;
|
||||
}
|
||||
|
||||
export interface AdvisorRequest {
|
||||
/** Optional prompt to steer the brainstorm. */
|
||||
prompt?: string;
|
||||
|
||||
Reference in New Issue
Block a user