/** * Behavior Tree Plugin for ECSpresso * * Provides composable, priority-driven AI via behavior trees. Shared immutable * tree definitions drive per-entity runtime state. Uses hybrid traversal * (Approach C): re-evaluate from root each tick to preserve priority, resume * running leaves, and abort diverged running nodes via `onAbort`. * * Each entity gets a `behaviorTree` component referencing a shared definition * plus a typed blackboard for per-entity AI memory. One system processes all * behavior-tree entities each tick. * * Top-level node helpers such as `action(...)` and `condition(...)` type the * callback ECS as the behavior-tree plugin world. If a leaf needs app-specific * components, resources, or events, use `ecs.getHelpers(createBehaviorTreeHelpers)` * after `.build()` and destructure helpers from that bound result. * * Node types: * Composites — sequence, selector, parallel * Decorators — inverter, repeat, cooldown, guard * Leaves — action (tick → NodeStatus, optional onAbort), condition (predicate) */ import { type BasePluginOptions, type BaseWorld, type ComponentsConfig, type EventsConfig } from 'ecspresso'; /** * Return value from behavior tree node ticks. * * - `Success` (0) — node completed successfully * - `Failure` (1) — node failed * - `Running` (2) — node still executing, will resume next tick */ export declare const NodeStatus: { readonly Success: 0; readonly Failure: 1; readonly Running: 2; }; export type NodeStatus = (typeof NodeStatus)[keyof typeof NodeStatus]; /** BaseWorld narrowed to behavior-tree components for typed access in helpers. */ type BehaviorTreeWorld = BaseWorld; /** * Context passed to all leaf node callbacks (action tick, condition check, * onAbort, guard predicates). * * @template BB - Blackboard type for per-entity AI memory * @template W - World interface type (default: BehaviorTreeWorld) */ export interface BehaviorTreeContext, W extends BaseWorld = BehaviorTreeWorld> { readonly ecs: W; readonly entityId: number; readonly dt: number; readonly blackboard: BB; } /** * Action leaf — executes behavior each tick. * Returns `Running` for multi-frame actions. Optional `onAbort` fires * when a higher-priority branch preempts this running action. */ export interface ActionNode> { readonly type: 'action'; readonly name: string; readonly tick: (ctx: BehaviorTreeContext) => NodeStatus; readonly onAbort?: (ctx: BehaviorTreeContext) => void; nodeIndex: number; } /** * Condition leaf — checks a predicate. Returns Success or Failure, never Running. */ export interface ConditionNode> { readonly type: 'condition'; readonly name: string; readonly check: (ctx: BehaviorTreeContext) => boolean; nodeIndex: number; } /** * Sequence composite — runs children left-to-right. * Fails on first failure, succeeds when all succeed. * Resumes from stored child index when a running node exists. */ export interface SequenceNode> { readonly type: 'sequence'; readonly children: readonly BehaviorTreeNode[]; nodeIndex: number; } /** * Selector composite — runs children left-to-right. * Succeeds on first success, fails when all fail. * Always re-evaluates from child 0 to preserve priority ordering. */ export interface SelectorNode> { readonly type: 'selector'; readonly children: readonly BehaviorTreeNode[]; nodeIndex: number; } /** * Parallel composite — ticks all children each frame. * Configurable success/failure thresholds. * * Limitation (v1): only one running leaf is tracked for abort. * Other running children in a parallel stop being ticked if the * tree path diverges but do not receive an `onAbort` call. */ export interface ParallelNode> { readonly type: 'parallel'; readonly children: readonly BehaviorTreeNode[]; readonly successThreshold: number; readonly failureThreshold: number; nodeIndex: number; } /** Decorator — inverts child result (Success↔Failure), passes Running through. */ export interface InverterNode> { readonly type: 'inverter'; readonly child: BehaviorTreeNode; nodeIndex: number; } /** Decorator — repeats child `count` times (or forever when count is -1). */ export interface RepeatNode> { readonly type: 'repeat'; readonly child: BehaviorTreeNode; readonly count: number; nodeIndex: number; } /** Decorator — prevents child re-entry for `duration` seconds after completion. */ export interface CooldownNode> { readonly type: 'cooldown'; readonly child: BehaviorTreeNode; readonly duration: number; nodeIndex: number; } /** Decorator — conditional gate. Ticks child only when condition passes. */ export interface GuardNode> { readonly type: 'guard'; readonly child: BehaviorTreeNode; readonly condition: (ctx: BehaviorTreeContext) => boolean; nodeIndex: number; } /** Union of all behavior tree node types. */ export type BehaviorTreeNode> = ActionNode | ConditionNode | SequenceNode | SelectorNode | ParallelNode | InverterNode | RepeatNode | CooldownNode | GuardNode; /** * Create an action leaf node. * * @param name - Human-readable name (used in abort events) * @param tick - Called each frame while this node is active; return NodeStatus * @param options - Optional `onAbort` callback fired when preempted by a higher-priority branch */ export declare function action(name: string, tick: (ctx: BehaviorTreeContext) => NodeStatus, options?: { onAbort?: (ctx: BehaviorTreeContext) => void; }): ActionNode; /** * Create a condition leaf node. * * @param name - Human-readable name * @param check - Predicate returning true (Success) or false (Failure). Never Running. */ export declare function condition(name: string, check: (ctx: BehaviorTreeContext) => boolean): ConditionNode; /** * Create a sequence composite. Runs children L→R, fails on first failure. */ export declare function sequence(children: BehaviorTreeNode[]): SequenceNode; /** * Create a selector composite. Runs children L→R, succeeds on first success. * Always starts from child 0 to re-evaluate priority. */ export declare function selector(children: BehaviorTreeNode[]): SelectorNode; /** * Create a parallel composite. Ticks all children each frame. * * @param children - Child nodes to tick in parallel * @param options.successThreshold - Successes needed for parallel to succeed (default: all) * @param options.failureThreshold - Failures needed for parallel to fail (default: all) */ export declare function parallel(children: BehaviorTreeNode[], options?: { successThreshold?: number; failureThreshold?: number; }): ParallelNode; /** Create an inverter decorator. Flips Success↔Failure, passes Running. */ export declare function inverter(child: BehaviorTreeNode): InverterNode; /** * Create a repeat decorator. * * @param child - Node to repeat * @param count - Number of repetitions, or -1 for infinite (default: -1) */ export declare function repeat(child: BehaviorTreeNode, count?: number): RepeatNode; /** * Create a cooldown decorator. Prevents re-entry for `duration` seconds * after child completes (Success or Failure). */ export declare function cooldown(child: BehaviorTreeNode, duration: number): CooldownNode; /** * Create a guard decorator. Ticks child only when condition returns true. * Returns Failure when condition is false. */ export declare function guard(cond: (ctx: BehaviorTreeContext) => boolean, child: BehaviorTreeNode): GuardNode; /** * Immutable behavior tree definition. Shared across entities. * * @template BB - Blackboard type for per-entity AI memory */ export interface BehaviorTreeDefinition> { readonly id: string; readonly root: BehaviorTreeNode; readonly nodeCount: number; } /** * Define a behavior tree with a typed blackboard. * * The `blackboard` value serves as both the type source and the default * initial state cloned for each entity via `createBehaviorTree`. * * @param id - Unique identifier for this tree definition * @param config - `{ blackboard, root }` — default blackboard + root node * @returns Frozen BehaviorTreeDefinition * * @example * ```typescript * const tree = defineBehaviorTree('patrol', { * blackboard: { targetId: null as number | null, timer: 0 }, * root: selector([ * guard(ctx => ctx.blackboard.targetId !== null, action('chase', ...)), * action('wander', ...), * ]), * }); * ``` */ export declare function defineBehaviorTree(id: string, config: { blackboard: BB; root: BehaviorTreeNode; }): BehaviorTreeDefinition; /** * Runtime behavior tree state stored on each entity. * * The `blackboard` is typed as `object` at the component level. * Inside tree callbacks, the `BehaviorTreeContext` provides * typed access to the blackboard via the tree definition's generic. * Outside the tree, cast the blackboard to the specific BB type. */ export interface BehaviorTreeComponent { readonly definition: BehaviorTreeDefinition>; blackboard: object; /** Index of the currently running leaf, or -1 if none. */ runningNodeIndex: number; /** * Dense per-node state array (sized to `definition.nodeCount`). * Semantics vary by node type: * - sequence/selector: child progress index * - repeat: completed iteration count * - cooldown: expiry timestamp (elapsedTime when cooldown ends) */ nodeState: Float64Array; /** Accumulated time (seconds) for cooldown tracking. */ elapsedTime: number; } /** * Component types provided by the behavior tree plugin. */ export interface BehaviorTreeComponentTypes { behaviorTree: BehaviorTreeComponent; } /** * Event published when a running action is preempted (aborted) by a * higher-priority branch taking over. */ export interface BehaviorTreeAbortEvent { entityId: number; /** nodeIndex of the aborted action */ nodeIndex: number; /** Human-readable name of the aborted action */ nodeName: string; /** Definition id of the behavior tree */ definitionId: string; } /** * Event types provided by the behavior tree plugin. */ export interface BehaviorTreeEventTypes { behaviorTreeAbort: BehaviorTreeAbortEvent; } /** * WorldConfig representing the behavior tree plugin's provided types. */ export type BehaviorTreeWorldConfig = ComponentsConfig & EventsConfig; /** * Create a `behaviorTree` component from a definition. * * @param definition - Shared tree definition * @param blackboard - Optional partial overrides for the default blackboard * @returns Component object suitable for spreading into spawn() * * @example * ```typescript * ecs.spawn({ * ...createBehaviorTree(villagerTree, { hunger: 80 }), * ...createLocalTransform(100, 200), * }); * ``` */ export declare function createBehaviorTree(definition: BehaviorTreeDefinition, blackboard?: Partial): Pick; /** * Check whether an entity's behavior tree has a running action. */ export declare function isBehaviorTreeRunning(ecs: { getComponent(entityId: number, name: 'behaviorTree'): BehaviorTreeComponent | undefined; }, entityId: number): boolean; /** * Reset an entity's behavior tree: abort any running action, clear all * composite progress, and optionally reset the blackboard. */ export declare function resetBehaviorTree(ecs: BehaviorTreeWorld, entityId: number, blackboard?: Partial>): void; /** * Typed helpers for the behavior tree plugin. * Creates helpers that validate callback parameters against the world type W. * Call after `.build()` so behavior-tree leaves can access app-specific * components, resources, and events through the full built ECS type. */ export interface BehaviorTreeHelpers> { defineBehaviorTree: (id: string, config: { blackboard: BB; root: BehaviorTreeNode; }) => BehaviorTreeDefinition; action: (name: string, tick: (ctx: BehaviorTreeContext) => NodeStatus, options?: { onAbort?: (ctx: BehaviorTreeContext) => void; }) => ActionNode; condition: (name: string, check: (ctx: BehaviorTreeContext) => boolean) => ConditionNode; guard: (cond: (ctx: BehaviorTreeContext) => boolean, child: BehaviorTreeNode) => GuardNode; } /** * Create typed behavior tree helpers bound to a specific world type. * * @example * ```typescript * const ecs = ECSpresso.create() * .withPlugin(createBehaviorTreePlugin()) * .withComponentTypes<{ enemy: { hp: number } }>() * .build(); * * const { defineBehaviorTree, action, condition, guard } = ecs.getHelpers(createBehaviorTreeHelpers); * * action('read enemy', ({ ecs, entityId }) => { * const enemy = ecs.getComponent(entityId, 'enemy'); * return enemy ? NodeStatus.Success : NodeStatus.Failure; * }); * ``` */ export declare function createBehaviorTreeHelpers = BehaviorTreeWorld>(_world?: W): BehaviorTreeHelpers; /** * Configuration options for the behavior tree plugin. */ export interface BehaviorTreePluginOptions extends BasePluginOptions { } /** * Create a behavior tree plugin for ECSpresso. * * Provides composable, priority-driven AI via behavior trees with: * - Hybrid traversal: re-evaluate from root each tick, resume running leaves * - Automatic abort with `onAbort` callback when preempted * - Typed blackboard for per-entity AI memory * - `behaviorTreeAbort` events on preemption * * @example * ```typescript * const ecs = ECSpresso.create() * .withPlugin(createBehaviorTreePlugin()) * .build(); * * const { defineBehaviorTree, action, condition, guard } = ecs.getHelpers(createBehaviorTreeHelpers); * * const tree = defineBehaviorTree('villager', { * blackboard: { hunger: 100, targetId: null as number | null }, * root: selector([ * guard(ctx => ctx.blackboard.hunger < 30, action('eat', ...)), * action('wander', ...), * ]), * }); * * ecs.spawn({ * ...createBehaviorTree(tree), * ...createLocalTransform(100, 200), * }); * ``` */ export declare function createBehaviorTreePlugin(options?: BehaviorTreePluginOptions): import("ecspresso").Plugin, BehaviorTreeEventTypes>, import("ecspresso").EmptyConfig, "behavior-tree-update", G, never, never>; export {};