From efa92be5ab15b5899e72ee9817f0ef1dee5a827b Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 1 Jun 2026 23:24:43 +0800 Subject: [PATCH] feat(bt): implement subtree status clearing in TaskRunner Introduce `clearSubtree` to recursively reset status for an entity and all its descendants. This ensures that when a task reaches a terminal state, its entire branch is properly reset. Also refactor the Tetris example to use a sequential task node within the behavior tree. --- examples/tetris/main.ts | 20 ++++++++++++++------ src/bt/runner.ts | 12 ++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/examples/tetris/main.ts b/examples/tetris/main.ts index 7c59f23..507ebbd 100644 --- a/examples/tetris/main.ts +++ b/examples/tetris/main.ts @@ -55,6 +55,9 @@ world.add(game, TickTimer); // Create blessed UI const ui = createUI(); +// Spawn the first piece +spawnPiece(); + // ── Command handlers ───────────────────────────────── const commands = new CommandQueue(world); @@ -186,24 +189,29 @@ const runner = new TaskRunner(world); // Build the BT structure: // root (repeat) -// ├── handleInput (leaf) -// ├── gravityTick (leaf) -// └── render (leaf) +// └── seq (sequential) +// ├── handleInput (leaf) +// ├── gravityTick (leaf) +// └── render (leaf) const root = world.spawn(); world.add(root, Task, { kind: "repeat" }); +const seq = world.spawn(); +world.add(seq, Task, { kind: "sequential" }); +world.relate(seq, ChildOf, root); + const handleInputTask = world.spawn(); world.add(handleInputTask, Task, { kind: "leaf" }); -world.relate(handleInputTask, ChildOf, root); +world.relate(handleInputTask, ChildOf, seq); const gravityTask = world.spawn(); world.add(gravityTask, Task, { kind: "leaf" }); -world.relate(gravityTask, ChildOf, root); +world.relate(gravityTask, ChildOf, seq); const renderTask = world.spawn(); world.add(renderTask, Task, { kind: "leaf" }); -world.relate(renderTask, ChildOf, root); +world.relate(renderTask, ChildOf, seq); // ── Leaf handlers ──────────────────────────────────── runner.onLeaf = (_w, entity) => { diff --git a/src/bt/runner.ts b/src/bt/runner.ts index 4d31b52..2d9d27e 100644 --- a/src/bt/runner.ts +++ b/src/bt/runner.ts @@ -44,6 +44,14 @@ function clearStatus(world: World, entity: Entity): void { if (world.has(entity, Running)) world.remove(entity, Running); } +/** Recursively clear status from an entity and all its descendants. */ +function clearSubtree(world: World, entity: Entity): void { + clearStatus(world, entity); + for (const child of childrenOf(world, entity)) { + clearSubtree(world, child); + } +} + function childrenOf(world: World, parent: Entity): Entity[] { return world.getRelatedTo(parent, ChildOf); } @@ -258,9 +266,9 @@ export class TaskRunner { const child = children[0]; - // If child reached a terminal, reset it and schedule again + // If child reached a terminal, reset the entire subtree and schedule again if (isTerminal(this._world, child)) { - clearStatus(this._world, child); + clearSubtree(this._world, child); this._world.add(child, Scheduled); return; }