/** * Authored behavior triggers on editor volumes and markers — a small `on`/`action` meta vocabulary * so "when player enters this volume, spawn wave 2" is scene data, not game code. Games declare * actions (typed via ParamSchema); the editor inspector renders their fields; a pure runtime * watches membership and dispatches to handlers. */ import type { SceneDocumentLike, SceneMarkerLike, SceneVolumeLike } from "../world/sceneShapes.js"; import { type ParamSchema, type ParsedParams } from "./sceneKinds.js"; /** Event edge that can fire an authored trigger. */ export type TriggerEvent = "enter" | "exit" | "interact"; /** Document collection a trigger source lives on. */ export type TriggerSourceKind = "marker" | "volume"; /** Reserved meta keys for the trigger vocabulary — action params use every other schema field. @internal */ export declare const TRIGGER_ON_KEY = "on"; /** @internal */ export declare const TRIGGER_ACTION_KEY = "action"; /** @internal */ export declare const TRIGGER_RADIUS_KEY = "triggerRadius"; /** @internal */ export declare const TRIGGER_LIST_KEY = "triggers"; /** Default proximity radius (m) for marker enter/exit/interact when neither triggerRadius nor radius is set. @internal */ export declare const DEFAULT_TRIGGER_RADIUS = 2.5; /** * A game-declared action the editor can assign to a volume/marker trigger. Schema drives the * inspector params; `targets`/`events` optionally narrow where it appears. */ export interface TriggerActionDefinition { id: string; /** Human label for the inspector action dropdown. */ label: string; /** Param surface for this action — fields land flat on the object's `meta` (alongside `on`/`action`). */ schema: ParamSchema; /** Which document collections accept this action. Default both. */ targets?: readonly TriggerSourceKind[]; /** Which events this action accepts. Default all (`enter`/`exit`/`interact`). */ events?: readonly TriggerEvent[]; } /** One resolved trigger binding from a document object. */ export interface AuthoredTrigger { sourceId: string; sourceKind: TriggerSourceKind; objectKind: string; on: TriggerEvent; action: string; params: ParsedParams; /** Proximity radius for marker sources; unused for volumes (shape owns containment). */ radius: number; } /** Fired when a watched actor trips an authored trigger edge. */ export interface TriggerDispatchEvent { on: TriggerEvent; action: string; sourceId: string; sourceKind: TriggerSourceKind; objectKind: string; actorId: string; params: ParsedParams; } /** Handler map keyed by action id — unknown actions are skipped unless `onDispatch` is set. */ export type TriggerHandlers = Readonly void>>; /** * Declare a game action the editor can assign to volume/marker triggers. Idempotent per `id` * (last registration wins). Call at module load next to catalogs. * * @capability authored-triggers schema'd on/action vocabulary on volumes and markers with runtime dispatch */ export declare function registerTriggerAction(definition: TriggerActionDefinition): void; /** Registered definition for an action id, or undefined when the game never declared it. */ export declare function getTriggerAction(id: string): TriggerActionDefinition | undefined; /** Every registered action, optionally filtered by target collection. */ export declare function listTriggerActions(target?: TriggerSourceKind): TriggerActionDefinition[]; /** Clears the action registry — tests only. @internal */ export declare function clearTriggerActions(): void; /** * The engine's built-in trigger actions — the shared `announce` / `win` / `advance` vocabulary every * game gets for free, so "enter this zone → win" is authored entirely in the editor with no bespoke * action code. Their params land flat on the object's `meta` like any registered action; drive them * at runtime with the handlers from {@link createTriggerOutcome}. * * @capability authored-triggers built-in announce/win/advance actions authorable on any marker or volume */ export declare const BUILTIN_TRIGGER_ACTIONS: readonly TriggerActionDefinition[]; /** * Register the engine's built-in {@link BUILTIN_TRIGGER_ACTIONS} so they appear in the editor's * trigger inspector. Idempotent; call at module load (a game may still register its own actions or * re-register a built-in id to override it). * * @capability authored-triggers register the shared announce/win/advance actions */ export declare function registerBuiltinTriggerActions(): void; /** Resolve the proximity radius for a marker trigger from meta. @internal */ export declare function triggerRadiusOf(meta: Record | undefined, fallback?: number): number; /** * Read every valid `on`/`action` binding off a raw meta bag. Unknown events/actions are dropped; * params are schema-parsed when the action is registered, else empty. * @internal */ export declare function readTriggerSpecs(meta: Record | undefined): readonly { on: TriggerEvent; action: string; params: ParsedParams; }[]; /** * Collect every authored trigger on a document's markers and volumes. Pure — no runtime state. * Action params use the live {@link registerTriggerAction} registry when present. * * @capability authored-triggers schema'd on/action vocabulary on volumes and markers with runtime dispatch */ export declare function collectAuthoredTriggers(document: SceneDocumentLike): AuthoredTrigger[]; /** * True when `point` is inside an editor volume (sphere / cylinder / box). Cylinder height defaults * to diameter when omitted; sphere ignores y for the common ground-plane case only when the volume * radius covers the full vertical span — here y is tested for sphere and box too. */ export declare function pointInVolume(volume: SceneVolumeLike, point: { x: number; y: number; z: number; }): boolean; /** True when `point` is within `radius` of a marker's position (sphere for enter/exit/interact). @internal */ export declare function pointNearMarker(marker: SceneMarkerLike, point: { x: number; y: number; z: number; }, radius: number): boolean; /** @internal */ export interface TriggerActor { id: string; position: readonly [number, number, number] | { x: number; y: number; z: number; }; } /** @internal */ export interface AuthoredTriggerStepInput { actors: readonly TriggerActor[]; /** Actor ids that pressed interact this frame — fires `on: "interact"` sources in range. */ interact?: readonly string[]; } /** Runtime handle that watches authored triggers against moving actors each tick. */ export interface AuthoredTriggerRuntime { /** Advance membership one tick; returns every edge fired and invokes handlers. */ step(input: { actors: readonly { id: string; position: readonly [number, number, number] | { x: number; y: number; z: number; }; }[]; /** Actor ids that pressed interact this frame — fires `on: "interact"` sources in range. */ interact?: readonly string[]; }): readonly TriggerDispatchEvent[]; /** Drop membership so the next step re-fires enters for still-inside actors. */ reset(): void; /** Snapshot of the triggers this runtime was built from. */ triggers(): readonly AuthoredTrigger[]; } /** * Build a runtime that watches a document's authored triggers against moving actors and dispatches * to per-action handlers (and optional catch-all). Pure membership math; the game supplies actors * each tick from its own player/entity poses. * * @capability authored-triggers schema'd on/action vocabulary on volumes and markers with runtime dispatch */ export declare function createAuthoredTriggerRuntime(options: { document: SceneDocumentLike; handlers?: TriggerHandlers; /** Invoked for every dispatch after the matching handler (if any). */ onDispatch?: (event: TriggerDispatchEvent) => void; /** Override the collected trigger list (tests / hot-reload). Default: {@link collectAuthoredTriggers}. */ triggers?: readonly AuthoredTrigger[]; }): AuthoredTriggerRuntime; /** Serializable read-model the built-in trigger actions write — the HUD/game reads it each frame. */ export interface TriggerOutcome { /** True once a `win` action fired. */ won: boolean; /** Latest announce/win/advance message to surface, or null before any fires. */ message: string | null; /** Tone for the latest message: `"info" | "warn" | "good"`. */ tone: string; /** Current objective id from the latest `advance`, or null. */ objective: string | null; /** Bumped on every change — a cheap key for `useSyncExternalStore`. */ revision: number; } /** Outcome store: the built-in action handlers plus a subscribable snapshot of what they produced. */ export interface TriggerOutcomeStore { /** Current outcome snapshot (stable reference until the next change). */ get(): TriggerOutcome; /** Subscribe to outcome changes; returns an unsubscribe fn. */ subscribe(listener: () => void): () => void; /** Handlers for `createAuthoredTriggerRuntime({ handlers })` — wires announce/win/advance. */ handlers: TriggerHandlers; /** Reset to the initial (un-won) outcome — new run / retry. */ reset(): void; } /** * Default runtime for the built-in {@link BUILTIN_TRIGGER_ACTIONS}: a small serializable outcome the * HUD renders, with no engine-side UI. Pass {@link TriggerOutcomeStore.handlers} to * {@link createAuthoredTriggerRuntime} and read {@link TriggerOutcomeStore.get} from the HUD via * `useSyncExternalStore` — that is the whole zero-code "reach the zone to win" wiring. * * @capability authored-triggers headless win/announce/objective outcome for the built-in actions */ export declare function createTriggerOutcome(): TriggerOutcomeStore; /** * Build a meta patch that sets a single flat trigger (`on` + `action` + optional param fields) and * clears a multi-trigger list if present. Used by the editor inspector. * @internal */ export declare function triggerMetaPatch(on: TriggerEvent | "", action: string, params?: Record): Record; /** Read the single flat trigger (`on`/`action`) from meta for the inspector, ignoring non-empty multi-lists. @internal */ export declare function readFlatTrigger(meta: Record | undefined): { on: TriggerEvent | ""; action: string; };