// Domain types — the single source of truth shared by the frontend (src/), the // backend (server/), and the LLM prompt contract. No runtime dependencies here. /** 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 **sub-region ids** (e.g. * `FR-57`) — one entry per individually-ownable sub-region, at sub-region * granularity rather than whole-country ISO_A3. The map colors each sub-region * with this nation's stable color (see protocol.nationColor). At scenario start * a nation owns **all** sub-regions of the countries it holds (the server * expands each ISO_A3 to its sub-regions; see server/regions.expandTerritory), * so no region starts unowned. A sub-region that is in **no** nation's list is * unowned (no-man's-land) — the map renders it grey and it can be gained or * lost region-by-region by the LLM. */ export interface Nation { id: string; name: string; type: NationType; stats: NationStats; territory: string[]; relations: Record; } /** 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; 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; } /** * A scenario the player can start. One JSON file per preset (presets/.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; }