/** Why a command refused: `reason` is a stable machine code; `message`, when set, is shown to the player. */ export interface CommandRejection { reason: string; /** Player-facing text ("Needs 40 more gold"). The shell shows it in the notice feed with the deny cue. */ message?: string; } export type CommandDecodeResult = { ok: true; value: TInput; } | { ok: false; reason: string; }; /** Parses raw `unknown` transport input into `TInput`, rejecting anything that doesn't match the command's declared shape. Runs before `validate`/`apply`, so a malformed payload never reaches game logic. */ export type CommandDecoder = (input: unknown) => CommandDecodeResult; export interface CommandDefinition { /** Runtime codec for this command's input. Declaring one turns the boundary strict: a payload that fails to decode is rejected before `validate`/`apply` ever see it. Omit to keep passing raw input through unchanged (existing behavior). */ decode?: CommandDecoder; validate?(state: TState, input: TInput): CommandRejection | null; apply(state: TState, input: TInput): TState | void; } export type CommandResult = { status: "applied"; state: TState; } | { status: "rejected"; reason: string; message?: string; } | { status: "unknown-command"; }; export interface CommandRegistry { define(name: string, definition: CommandDefinition): void; has(name: string): boolean; names(): string[]; run(state: TState, name: string, input: unknown): CommandResult; } export declare function createCommandRegistry(): CommandRegistry;