behavior-tree
Import from @pierre/ecs/modules/behavior-tree.
A reactive behaviour tree built from pure functions. A node is
(ctx) => BtStatus (success / failure / running); composites and
decorators are higher-order functions that combine child nodes. Compose a
tree once and re-tick it from the root every frame — a higher-priority
branch preempts a lower one the moment its condition flips.
The step up from @pierre/ecs/modules/fsm: instead of “which state am I
in”, you express prioritised, hierarchical behaviour — a higher-priority
branch preempts a lower one the moment its condition flips. See
examples/critters for needs-driven
creatures (flee > eat > sleep > wander).
Reactive, blackboard-on-context
Section titled “Reactive, blackboard-on-context”Nodes are stateless; the tree is re-evaluated from the root each tick
so priorities are always live. Any cross-tick memory (a target, a timer)
lives on the context you pass in — the “blackboard” — not inside
nodes. That lets one shared tree drive many agents: set the per-agent
blackboard on ctx, then tick.
// one shared treeconst brain = selector<Ctx>( sequence(condition(isThreatened), action(flee)), // highest priority sequence(condition(isHungry), action(goToFood), action(eat)), action(wander), // fallback);
// each tick, per agent:for (const agent of agents) { ctx.active = agent; // blackboard brain(ctx);}type BtStatus = 'success' | 'failure' | 'running';type BtNode<TCtx> = (ctx: TCtx) => BtStatus;
// Compositesfunction sequence<TCtx>(...children: BtNode<TCtx>[]): BtNode<TCtx>; // AND: fail-fast, running short-circuits, success if all succeedfunction selector<TCtx>(...children: BtNode<TCtx>[]): BtNode<TCtx>; // OR / fallback: succeed-fast, running short-circuits, failure if all fail
// Decoratorfunction inverter<TCtx>(child: BtNode<TCtx>): BtNode<TCtx>; // success ⇄ failure; running passes through
// Leavesfunction condition<TCtx>(pred: (ctx: TCtx) => boolean): BtNode<TCtx>; // success if pred else failurefunction action<TCtx>(fn: (ctx: TCtx) => BtStatus): BtNode<TCtx>; // use fn's statusStatus semantics of the composites:
| Node | returns success |
returns running |
returns failure |
|---|---|---|---|
sequence |
all children succeed | first child running | first child fails |
selector |
first child succeeds | first child running | all children fail |
- Zero dependencies, ECS-decoupled. Nodes are plain functions over a
caller-defined
ctx. No component coupling; the tree is a shared value like a system. runningis the crux. A multi-tick action (travel, wait) returnsrunning; the enclosingsequence/selectorshort-circuits and resumes the same branch next frame until the action reportssuccess/failure. Instant effects returnsuccess.- Composes with movement. The tree is the brain (which behaviour);
pair leaf actions with
@pierre/ecs/modules/steeringfor the muscles (afleeleaf drives velocity viasteering.flee). - Deferred nodes.
parallel,cooldown,repeat/repeatUntilare less-universal decorators — add them when a consumer needs one, per the rule of three.