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.
This commit is contained in:
hypercross 2026-06-01 23:24:43 +08:00
parent c3c24d2350
commit efa92be5ab
2 changed files with 24 additions and 8 deletions

View File

@ -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,6 +189,7 @@ const runner = new TaskRunner(world);
// Build the BT structure:
// root (repeat)
// └── seq (sequential)
// ├── handleInput (leaf)
// ├── gravityTick (leaf)
// └── render (leaf)
@ -193,17 +197,21 @@ const runner = new TaskRunner(world);
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) => {

View File

@ -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;
}