179 lines
6.0 KiB
TypeScript
179 lines
6.0 KiB
TypeScript
// 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 {
|
||
military: number;
|
||
economy: number;
|
||
stability: number;
|
||
population: number;
|
||
}
|
||
|
||
export const STAT_KEYS = ['military', 'economy', 'stability', 'population'] as const;
|
||
export type StatKey = (typeof STAT_KEYS)[number];
|
||
|
||
export type NationType = 'major' | 'minor' | 'power' | 'alliance' | 'faction';
|
||
|
||
/** Direction of a bilateral diplomatic relationship. */
|
||
export type RelationStatus = 'ally' | 'neutral' | 'rival' | 'hostile' | 'war';
|
||
|
||
export const RELATION_STATUSES: RelationStatus[] = [
|
||
'ally',
|
||
'neutral',
|
||
'rival',
|
||
'hostile',
|
||
'war',
|
||
];
|
||
|
||
/**
|
||
* A single nation in the world. `territory` holds **region keys** (see
|
||
* `public/region-keys.json`) — one per owned sub-region, so the choropleth can
|
||
* color individual sub-regions, not whole countries (v0.19, #16 P1). The map
|
||
* resolves each sub-region's owner by its region key (see protocol.mapFeatureOwner).
|
||
*/
|
||
export interface Nation {
|
||
id: string;
|
||
name: string;
|
||
type: NationType;
|
||
stats: NationStats;
|
||
territory: string[];
|
||
relations: Record<string, RelationStatus>;
|
||
}
|
||
|
||
/** Categories a narrative event can take. */
|
||
export type EventType =
|
||
| 'military'
|
||
| 'diplomatic'
|
||
| 'economic'
|
||
| 'political'
|
||
| 'social'
|
||
| 'disaster'
|
||
| 'scientific'
|
||
| 'cultural';
|
||
|
||
export const EVENT_TYPES: EventType[] = [
|
||
'military',
|
||
'diplomatic',
|
||
'economic',
|
||
'political',
|
||
'social',
|
||
'disaster',
|
||
'scientific',
|
||
'cultural',
|
||
];
|
||
|
||
export type EventOutcome = 'success' | 'failure' | 'partial' | 'neutral';
|
||
|
||
/** A narrative event the simulation emitted on some turn. */
|
||
export interface EventEntry {
|
||
id: string;
|
||
turn: number;
|
||
date: string;
|
||
nation: string; // nation id the event concerns
|
||
type: EventType;
|
||
title: string;
|
||
text: string;
|
||
outcome: EventOutcome;
|
||
}
|
||
|
||
/**
|
||
* A single item queued in the current round, waiting to be committed by a
|
||
* "Jump forward". `kind: 'action'` is a free-text player move; `kind:
|
||
* 'diplomacy'` is a message to a specific nation (`to`).
|
||
*/
|
||
export type RoundItem = {
|
||
kind: 'action' | 'diplomacy';
|
||
text: string;
|
||
to?: string;
|
||
};
|
||
|
||
/**
|
||
* Monotonic revision counter for a game, bumped by the server on every
|
||
* mutation (queue add/remove, turn commit) and persisted with the state.
|
||
* Clients use it to tell a fresh state from a stale one: an SSE `state`/`turn`
|
||
* snapshot (e.g. the on-connect resync) that arrives *after* a fresher REST
|
||
* response carries a lower version and must be ignored, never applied
|
||
* (see the client's "never go backwards" rule in src/App.tsx). Legacy games
|
||
* saved before this field existed are treated as version 0 by consumers.
|
||
*/
|
||
export type GameVersion = number;
|
||
|
||
/** The full, persisted state of one game. Saved to SQLite by id. */
|
||
export interface GameState {
|
||
id: string;
|
||
presetId: string;
|
||
presetTitle: string;
|
||
playerNationId: string;
|
||
turn: number;
|
||
date: string;
|
||
nations: Record<string, Nation>;
|
||
events: EventEntry[];
|
||
/**
|
||
* Actions + diplomatic messages queued this round but not yet committed.
|
||
* Adding an item never advances the turn; a "Jump forward" processes the whole
|
||
* batch in a single turn and clears this back to `[]`.
|
||
*/
|
||
pending: RoundItem[];
|
||
/**
|
||
* Monotonic revision counter (see GameVersion). Bumped on every mutation so
|
||
* clients can reject stale SSE snapshots; see the "never go backwards" rule
|
||
* in src/App.tsx. Starts at 1 on game creation.
|
||
*/
|
||
version: GameVersion;
|
||
/**
|
||
* (v0.22, #16 P3) Region keys that became **unowned** — "no man's land" —
|
||
* during play: a region that was owned and was released to no owner by a
|
||
* committed turn (the LLM narrates the cause in that turn's events). This is
|
||
* deliberately distinct from the many regions a preset never assigns, which
|
||
* are also unowned but were never *released* mid-game; only the keys in this
|
||
* list render as the distinct visible no-man's-land grey (see
|
||
* protocol.regionFillStyle). A region leaves the list when any nation
|
||
* re-claims it (an ordinary `gain`). Always `[]` on a fresh game — no
|
||
* man's land can only arise mid-game. Rows saved before v0.22 load with
|
||
* this defaulted to `[]` (see server/db.ts).
|
||
*/
|
||
unowned: string[];
|
||
/**
|
||
* (v0.23, #16 P4a; v0.24, #16 P4b) 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; a split partitions the nation's region-key territory
|
||
* into the new nations' territories; a merge folds the inputs' territory
|
||
* into the survivor) and removes it; rejecting removes it without
|
||
* applying. 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[];
|
||
}
|
||
|
||
/**
|
||
* A scenario the player can start. One JSON file per preset (presets/<id>.json).
|
||
* `worldBackground` + `simulationRules` become the LLM system prompt.
|
||
*/
|
||
export interface Preset {
|
||
id: string;
|
||
title: string;
|
||
startDate: string;
|
||
summary: string;
|
||
worldBackground: string;
|
||
simulationRules: string[];
|
||
nations: Nation[];
|
||
}
|
||
|
||
/** The lightweight manifest entry used to render the preset picker cards. */
|
||
export interface PresetMeta {
|
||
id: string;
|
||
title: string;
|
||
startDate: string;
|
||
summary: string;
|
||
}
|