/** `reason` is a stable machine code; `message`, when set, is player-facing text the client shows on refusal. */ export type CommandValidationError = { reason: string; message?: string; }; /** * Which slice of a server has to be hydrated before some work runs against it. Both fields default to * "everything", which costs a read per member plus one per chunk — the cost that makes a large shared * world unaffordable per mutation. Narrow them when the caller knows what it will touch. */ export type CommandScope = { /** Member ids to hydrate; omit for the whole roster. */ players?: readonly string[]; /** Chunk keys to hydrate; omit for every chunk of the server, `[]` for none. */ chunkKeys?: readonly string[]; }; /** Declarative read scope; actor is resolved before host hydration. */ export type CommandScopeDefinition = Omit & { players?: readonly string[] | "actor"; }; /** Who may send a command: any client through the public transport, or only trusted host code. */ export type CommandAccess = "client" | "server"; /** Reason every host returns when a command's `parse` rejects its input. */ export declare const MALFORMED_COMMAND_INPUT_REASON = "Malformed command input"; /** Reason a public transport returns for a {@link CommandDef} marked `access: "server"`. */ export declare const SERVER_ONLY_COMMAND_REASON = "Command is server-only"; export type CommandDef = { /** * `"server"` when `input` carries facts only the host may decide (unlock state, prices, multipliers): * public transports then refuse it and only trusted host calls such as `helpers.runCommand` run it. * Defaults to `"client"`, so every field of `input` must be validated as untrusted. */ access?: CommandAccess; /** * Turns the untrusted wire value into the command's input, or `null` to refuse it with * {@link MALFORMED_COMMAND_INPUT_REASON} before `scope`, `validate` or `apply` run. Those then only ever see * parsed input, so a field checked here can't be skipped in one of them. Build it from the readers in * `runtime/commandInput`; declare typed commands with {@link defineCommand}. */ parse?: (input: unknown) => TInput | null; /** * What this command reads and writes, derived from its own input. A host that hydrates through a * scope loads only this slice instead of the whole world, and refuses the command if `apply` then * dirties a player or chunk the scope did not name — writing an unhydrated chunk would overwrite * the stored one with a blank. Omit it to load only the actor and no chunks; return `{}` to request the whole world. */ scope?: (input: TInput, actorUserId: string) => CommandScopeDefinition; /** `nowMs` is the host wall clock, in ms — read it rather than a `now` the client put in `input`. */ validate: (snapshot: import("./snapshot.js").GameRuntimeSnapshot, input: TInput, actorUserId: string, nowMs: number) => CommandValidationError | null; apply: (snapshot: import("./snapshot.js").GameRuntimeSnapshot, input: TInput, actorUserId: string, nowMs: number) => import("./snapshot.js").GameRuntimeSnapshot; }; /** * The resolved scope, defaulting to the actor and no chunks. * Evaluate it before hydration: the actor and the command's own input are both known by then. * @internal */ export declare function resolveCommandScope(commands: Record>, commandName: string, input: TInput, actorUserId: string): CommandScope; /** * Widen a scope so the actor is always hydrated. A command's own player row is dirtied by * {@link runCommand} whatever the command did, and an unhydrated player is dropped from the persist. * @internal */ export declare function scopeWithActor(scope: CommandScope | undefined, actorUserId: string): CommandScope | undefined; /** * Why a command's writes escaped the scope it was hydrated under, or `null` when they did not. * A write outside the scope is a silent overwrite of unread state, so the host refuses it. * @internal */ export declare function commandScopeEscape(scope: CommandScope | undefined, dirty: import("./snapshot.js").GameRuntimeSnapshot["dirty"], commandName: string): string | null; export type RunCommandResult = { ok: true; snapshot: import("./snapshot.js").GameRuntimeSnapshot; } | { ok: false; reason: string; message?: string; }; /** @internal */ export declare function runCommand(snapshot: import("./snapshot.js").GameRuntimeSnapshot, commands: Record>, commandName: string, input: TInput, actorUserId: string, nowMs?: number): RunCommandResult; /** * Declare a command with a typed input: `parse` narrows the wire value once, and `scope`, `validate` and * `apply` receive the parsed type. Returns the untyped {@link CommandDef} a runtime's command table holds. * @capability command-parse parse untrusted command input once so every command stage sees typed, bounded values */ export declare function defineCommand(definition: CommandDef & { parse: (input: unknown) => TInput | null; }): CommandDef;