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:
2026-10-14 12:47:58 +00:00
parent edaa8f55d1
commit e9fa267fd0
10 changed files with 615 additions and 12 deletions
+19
View File
@@ -1,5 +1,9 @@
// Domain types — the single source of truth shared by the frontend (src/), the
// backend (server/), and the LLM prompt contract. No runtime dependencies here.
// (The single `import type` below erases at compile time, so no runtime
// dependency on protocol.ts is introduced — see the GameState.pendingNationOps
// doc comment.)
import type { NationOp } from './protocol';
/** A nation's 0–100 status indices. Every value is clamped to 0–100 by the engine. */
export interface NationStats {
@@ -133,6 +137,21 @@ export interface GameState {
* this defaulted to `[]` (see server/db.ts).
*/
unowned: string[];
/**
* (v0.23, #16 P4a) Nation-management ops the LLM has proposed but the player
* has not yet resolved: a *pending proposal* list, as opposed to `pending`
* (the current round's queued player actions). A turn NEVER applies one —
* `applyTurnResult` appends the turn's `nationOps` to this list, and each
* proposal survives across turns until the player resolves it: confirming
* applies it (a rename changes `Nation.name` only — the id, its territory,
* its relations, and the player pointer all stay valid) and removes it;
* rejecting removes it without applying. Split/merge are declared for P4b
* and are a safe no-op on confirm until then. Always `[]` on a fresh game;
* rows saved before v0.23 load with this defaulted to `[]` (see server/db.ts).
* (The `NationOp` type lives in protocol.ts; the import there is type-only,
* so this file still has no runtime dependencies.)
*/
pendingNationOps: NationOp[];
}
/**