behavior_tree

A pure MoonBit implementation of Behavior Tree (BT) for Game AI and Agents.

game-ai
behavior-tree
agent
decision-making
moon add mhh12345678/behavior_tree@0.1.2
Download zip
Version
0.1.2
License
Apache-2.0
Last updated
yesterday
Downloads
10
README

#MoonBit Behavior Tree

CI License: Apache-2.0

一个使用纯 MoonBit 编写的、轻量、高性能且零外部依赖的行为树(Behavior Tree)决策引擎库

适用于游戏 NPC AI 控制、智能体(Agent)的任务流程决策与复杂业务编排,支持 WebAssembly (wasm-gc)、JavaScript 以及 Native 全平台。

#特性

模块说明
Blackboard共享键值上下文,支持 snapshot/restore 沙盒、事务、差异补丁与原子校验
Sequence顺序节点(AND 门),支持跨帧 Running 状态恢复
Selector选择节点(OR 门),支持跨帧 Running 状态恢复
Parallel并行节点,SuccessAll/SuccessOne 策略可配置
RandomSelector随机顺序选择(LCG 伪随机),适合非确定性 AI
Inverter逆变修饰器(取反 Success/Failure)
Succeeder始终成功修饰器
Repeater重复N次修饰器
Limiter执行频率限制修饰器
Retry失败重试修饰器(最多N次)
UntilSuccess持续重试直到成功
UntilFailure持续重试直到失败
Timeout超时降级修饰器(超过N帧强制Failure)
Action / Condition叶子节点,Action 支持 Running 跨帧状态
EventBus事件总线,支持 emit/consume/clear 与一次性 handler dispatch
EventRouter有界 FIFO 事件路由,支持带 payload 事件、Running 跨帧处理和 fallback
InterruptNode事件中断节点,外部事件抢占正在运行的子树
ConditionGuardNode反应式前置条件守卫,持续监控断言
Scheduler多树并发调度器,per-tree 独立黑板,支持暂停/恢复
BudgetScheduler按优先级和每帧预算调度大量 NPC/Agent,支持暂停、重置和积压报告
Builder链式声明式 API,结构自解释,极易维护
TraceNode调试追踪包装器,实时打印 Tick 执行路径
TreeRunner管理单棵树的黑板、帧计数、重置与终止运行
NodeMetrics统计 Success/Failure/Running、连续 Running 与 CSV 快照
TraceBuffer有界执行追踪窗口,支持环形保留、状态统计和 CSV 导出
Quorum / Race阈值并行与竞速策略,适合多传感器或多代理协调
Safety policies执行预算、once、cooldown、稳定状态与上下文守卫
Scenario harness跨 wasm-gc/JS/Native 的可重复逻辑基准和 CSV 报告

#安装

#方法一:moon add(推荐)

moon add mhh12345678/behavior_tree

#方法二:手动添加依赖

如果是手动配置依赖,首先在包目录的 moon.mod 中添加依赖声明:

[deps] "mhh12345678/behavior_tree" = "0.1.2"

然后在你需要使用它的包的 moon.pkg 中导入:

import { "mhh12345678/behavior_tree" @bt }

#快速开始

// 1. 创建共享黑板
let bb = @bt.Blackboard::new()
bb.set_int("health", 45)
bb.set_bool("enemy_in_sight", true)

// 2. 使用 Builder 声明式构建行为树
let tree = @bt.Builder::new()
.selector()
// 血量低时逃跑(优先级最高)
.sequence()
.condition(fn(bb) { bb.get_int("health").unwrap_or(100) < 50 })
.action(fn(_) { println("Low health! Fleeing..."); @bt.BTSuccess })
.end()
// 发现敌人则追击
.sequence()
.condition(fn(bb) { bb.get_bool("enemy_in_sight").unwrap_or(false) })
.action(fn(_) { println("Enemy spotted! Chasing..."); @bt.BTSuccess })
.end()
// 默认巡逻
.action(fn(_) { println("Patrolling..."); @bt.BTSuccess })
.end()
.build()

// 3. 执行单次 Tick
let status = tree.tick(bb)
println("Result: \{status.to_string()}")
// 输出: Low health! Fleeing...
// 输出: Result: Success

#高级用法

#事件中断(InterruptNode)

let bus = @bt.EventBus::new()
let tree = @bt.interrupt_node(bus, "enemy_spotted", patrol_tree, attack_tree)

// 在任意时刻发出事件,下一次 tick 会切换到 attack_tree
bus.emit("enemy_spotted")
let _ = tree.tick(bb)

如果应用更适合使用闭包处理事件,也可以注册 handler 并显式派发;事件最多被派发一次:

bus.on("damage", fn(bb) {
bb.set_int("last_damage", 5)
@bt.BTSuccess
})
bus.emit("damage")
let _ = bus.dispatch("damage", bb)

#多 NPC 并发调度(Scheduler)

let sched = @bt.Scheduler::new()
sched.add(@bt.SchedulerEntry::new("guard1", guard_tree, @bt.Blackboard::new()))
sched.add(@bt.SchedulerEntry::new("guard2", patrol_tree, @bt.Blackboard::new()))

// 单次帧调用,所有 NPC 同时 tick
let results = sched.tick()
for r in results {
println("\{r.name}: \{r.status.to_string()}")
}

#黑板快照沙盒

bb.set_int("hp", 100)
bb.snapshot() // 保存当前状态
bb.set_int("hp", 0) // 模拟伤害
// 测试沙盒内的行为...
bb.restore() // 回滚到快照
// bb.get_int("hp") == Some(100)

#原子状态事务与差异补丁

复杂动作可以先在隔离黑板中试算,只有校验通过才提交;这适合商店购买、战斗规划、存档和网络同步:

let before = bb.clone()
let accepted = bb.transactional(fn(working) {
working.set_int("credits", working.get_int_or("credits", 0) - 20)
working.set_string("last_action", "buy_potion")
working.get_int_or("credits", 0) >= 0
})
let patch = before.diff(bb)
let _ = patch.to_text()

#有界事件路由

EventRouter 将消息队列接入帧循环,每帧最多处理固定数量事件;返回 Running 的 handler 会自动保留原事件,适合资源加载、网络重试和多帧动画:

let queue = @bt.EventQueue::new()
let router = @bt.EventRouter::new(queue, 4)
let _ = router.on("damage", fn(bb, event) {
bb.set_int("last_damage", event.payload().unwrap().as_int().unwrap())
@bt.BTSuccess
})
let _ = queue.emit_value("damage", @bt.Value::Int(5))
let report = router.tick(bb)
println(report.to_line())

#优先级与帧预算调度

当场景中有大量独立 NPC 或 Agent 时,可使用 BudgetScheduler 避免单帧工作量失控:

let scheduler = @bt.BudgetScheduler::new(8)
let _ = scheduler.add(
@bt.BudgetTask::new("guard-1", 10, guard_tree, @bt.Blackboard::new()),
)
let report = scheduler.tick()
println("pending=\{report.pending()}, skipped=\{report.skipped()}")

#有界执行追踪

TraceBuffer 只保留最近 N 条节点结果,不会让长时间运行的服务无限增长日志内存:

let trace = @bt.TraceBuffer::new(256)
let traced = @bt.trace_buffer_node("combat", combat_tree, trace)
let _ = traced.tick(bb)
println(trace.to_csv())

#项目结构

. ├── blackboard.mbt # 黑板上下文(snapshot/restore) ├── blackboard_patch.mbt # 事务、差异补丁和原子状态提交 ├── node.mbt # Status / Ref / Node / Sequence / Selector / Parallel / RandomSelector ├── decorator.mbt # 8 种修饰器节点 ├── leaf.mbt # Action / Condition 叶子节点 ├── builder.mbt # 链式 Builder API ├── parser.mbt # Trace 调试节点包装器 ├── event.mbt # EventBus / InterruptNode / ConditionGuardNode ├── event_router.mbt # 有界 payload 事件路由器 ├── scheduler.mbt # 多树 Scheduler 调度器 ├── budget_scheduler.mbt # 优先级与帧预算调度器 ├── helpers.mbt # 便捷工厂函数 ├── runtime.mbt / telemetry.mbt / health.mbt # 运行时生命周期与可观测性 ├── trace_buffer.mbt # 有界环形执行追踪 ├── policy_nodes.mbt / safety.mbt / quorum.mbt # 生产策略与安全边界 ├── benchmark.mbt / scenario.mbt # 可重复基准和场景矩阵 ├── behavior_tree_wbtest.mbt # 核心单元测试 ├── integration_test.mbt / boundary_test.mbt # 集成与边界测试 ├── production_runtime_test.mbt # 事务、路由、追踪和预算调度场景 ├── lib_doc.mbt # 库文档快速入门示例 ├── cmd/main/main.mbt # 守卫 NPC AI 仿真演示 └── .github/workflows/ci.yml # CI:wasm-gc / js / native 全平台

#运行与测试

# 类型检查(全平台) moon check --target all # 执行 80 个测试用例(全部通过) moon test --target all # 运行守卫 NPC AI 仿真演示 moon run cmd/main

#可重复基准

基准只统计逻辑 Tick 与三种状态,不依赖机器时钟,因此可在不同后端和 CI 上复现:

let csv = @bt.standard_benchmark_csv()
println(csv)

当前固定场景为启用工作流、禁用工作流和带执行预算的持续运行节点;边界覆盖空集合、零/负预算、阈值恰好命中、缺失黑板键、嵌套快照回滚和独立黑板克隆。

#严格验收命令

moon fmt --check moon check --deny-warn moon test --deny-warn moon info moon run cmd/main

本地 Native 构建需要系统 C 编译器;CI 会在 Linux、macOS、Windows 上执行标准检查。仓库当前有效 .mbt 源码超过 5200 行,其中排除测试后的生产代码约 4200 行;生成目录 _build/ 不计入源码规模。

#示例输出

=== MoonBit Behavior Tree Guard AI Simulation === --- Tick 1: Patrol mode --- -> Tick: Patrol Action AI ACTION: Patrol waypoint 1 <- Patrol Action Result: Success --- Tick 3: Enemy spotted at distance 8.0 --- -> Tick: Combat Branch AI ACTION: Chasing enemy... distance 5 <- Combat Branch Result: Running --- Tick 7: Guard HP=20, flees --- -> Tick: Flee Branch AI ACTION: Low health! Fleeing... <- Flee Branch Result: Success

#许可证

本项目采用 Apache-2.0 开源许可证。

项目代码为原创 MoonBit 实现,未复制第三方源文件或生成代码;行为树概念仅参考公开算法资料。测试与示例均为本仓库原创,第三方依赖为空。若未来引入外部代码、数据集或生成文件,必须在本节或单独的 NOTICE/THIRD_PARTY.md 中记录来源、版本、许可证和再分发范围。


#参考资料

  • Behavior Trees in AI:https://en.wikipedia.org/wiki/Behavior_tree_(artificial_intelligence,_robotics_and_control)
  • MoonBit 文档:https://docs.moonbitlang.com
  • mooncakes.io:https://mooncakes.io

#
BatchMode

pub(all) enum BatchMode {
ContinueOnFailure
StopOnFailure
} derive(Eq,
Debug
)

Failure behavior for a batch.

#
BatchMode::continue_on_failure

fn BatchMode::continue_on_failure() -> BatchMode

Continue after a failed task.

#
BatchMode::stop_on_failure

fn BatchMode::stop_on_failure() -> BatchMode

Stop immediately after a failed task.

#
BatchReport

pub struct BatchReport {
status : Status
cursor : Int
total : Int
failures : Int
}

A stable progress snapshot.

#
BatchReport::cursor

fn BatchReport::cursor(self : BatchReport) -> Int

Index of the next task.

#
BatchReport::failures

fn BatchReport::failures(self : BatchReport) -> Int

Number of failed tasks observed so far.

#
BatchReport::status

fn BatchReport::status(self : BatchReport) -> Status

Status observed for this report.

#
BatchReport::total

fn BatchReport::total(self : BatchReport) -> Int

Number of tasks in the batch.

#
BatchRunner

pub struct BatchRunner {
tasks : Array[Node]
bb : Blackboard
mode : BatchMode
cursor : Ref[Int]
failures : Ref[Int]
stopped : Ref[Bool]
}

A sequential, incremental task batch.

#
BatchRunner::add

fn BatchRunner::add(self : BatchRunner, task : Node) -> Unit

Append a task before execution begins.

#
BatchRunner::blackboard

fn BatchRunner::blackboard(self : BatchRunner) -> Blackboard

The blackboard shared by all tasks.

#
BatchRunner::is_done

fn BatchRunner::is_done(self : BatchRunner) -> Bool

Whether all tasks have been processed or the batch has stopped.

#
BatchRunner::new

fn BatchRunner::new(bb : Blackboard, mode : BatchMode) -> BatchRunner

Create an empty batch.

#
BatchRunner::reset

fn BatchRunner::reset(self : BatchRunner) -> Unit

Reset all tasks and start the batch again.

#
BatchRunner::size

fn BatchRunner::size(self : BatchRunner) -> Int

Number of tasks in the batch.

#
BatchRunner::tick

fn BatchRunner::tick(self : BatchRunner) -> BatchReport

Execute at most one task and return a progress report.

#
BenchmarkResult

pub struct BenchmarkResult {
name : String
frames : Int
successes : Int
failures : Int
running : Int
}

Counts the observable outcomes of one benchmark run.

#
BenchmarkResult::failures

fn BenchmarkResult::failures(self : BenchmarkResult) -> Int

Number of failed frames.

#
BenchmarkResult::frames

fn BenchmarkResult::frames(self : BenchmarkResult) -> Int

Access the number of executed frames.

#
BenchmarkResult::name

fn BenchmarkResult::name(self : BenchmarkResult) -> String

Access the scenario name.

#
BenchmarkResult::running

fn BenchmarkResult::running(self : BenchmarkResult) -> Int

Number of running frames.

#
BenchmarkResult::successes

fn BenchmarkResult::successes(self : BenchmarkResult) -> Int

Number of successful frames.

#
BenchmarkResult::summary

fn BenchmarkResult::summary(self : BenchmarkResult) -> String

Return a compact human-readable summary.

#
BenchmarkResult::to_csv

fn BenchmarkResult::to_csv(self : BenchmarkResult) -> String

Return a stable CSV row for benchmark archives.

#
Blackboard

pub struct Blackboard {
data : Map[String, Value]
snapshots : Array[Map[String, Value]]
}

Blackboard is a shared key-value context for all BT nodes.

#
Blackboard::apply_patch_if

fn Blackboard::apply_patch_if(self : Blackboard, patch : BlackboardPatch, validate : (Blackboard) -> Bool) -> Bool

Apply a patch only when a caller-provided validation callback accepts the resulting state. This is useful for atomic action planning and save games.

#
Blackboard::begin_transaction

fn Blackboard::begin_transaction(self : Blackboard) -> BlackboardTransaction

Begin a transaction. Changes are invisible to the target until commit.

#
Blackboard::clear

fn Blackboard::clear(self : Blackboard) -> Unit

Clear all keys from the blackboard. Snapshots are not affected.

#
Blackboard::clone

fn Blackboard::clone(self : Blackboard) -> Blackboard

Create an independent blackboard copy without copying snapshot history.

#
Blackboard::diff

fn Blackboard::diff(self : Blackboard, target : Blackboard) -> BlackboardPatch

Compute the ordered changes that transform self into target. Existing keys keep the source insertion order; newly added keys follow the target insertion order. This makes patches stable in logs and fixtures.

#
Blackboard::discard_snapshot

fn Blackboard::discard_snapshot(self : Blackboard) -> Unit

Discard the most recent snapshot without restoring it.

#
Blackboard::get_bool

fn Blackboard::get_bool(self : Blackboard, key : String) -> Bool?

Retrieve a Bool stored under the given key. Returns None if missing or wrong type.

#
Blackboard::get_bool_or

fn Blackboard::get_bool_or(self : Blackboard, key : String, fallback : Bool) -> Bool

Read a Boolean or return a caller-provided default.

#
Blackboard::get_double

fn Blackboard::get_double(self : Blackboard, key : String) -> Double?

Retrieve a Double stored under the given key. Returns None if missing or wrong type.

#
Blackboard::get_double_or

fn Blackboard::get_double_or(self : Blackboard, key : String, fallback : Double) -> Double

Read a Double or return a caller-provided default.

#
Blackboard::get_int

fn Blackboard::get_int(self : Blackboard, key : String) -> Int?

Retrieve an Int stored under the given key. Returns None if missing or wrong type.

#
Blackboard::get_int_or

fn Blackboard::get_int_or(self : Blackboard, key : String, fallback : Int) -> Int

Read an integer or return a caller-provided default.

#
Blackboard::get_string

fn Blackboard::get_string(self : Blackboard, key : String) -> String?

Retrieve a String stored under the given key. Returns None if missing or wrong type.

#
Blackboard::get_string_or

fn Blackboard::get_string_or(self : Blackboard, key : String, fallback : String) -> String

Read a String or return a caller-provided default.

#
Blackboard::get_value

fn Blackboard::get_value(self : Blackboard, key : String) -> Value?

Read a value without committing to a concrete value type.

#
Blackboard::has

fn Blackboard::has(self : Blackboard, key : String) -> Bool

Check whether the given key exists on the blackboard.

#
Blackboard::keys

fn Blackboard::keys(self : Blackboard) -> Array[String]

Return keys in the map's stable insertion order.

#
Blackboard::merge

fn Blackboard::merge(self : Blackboard, other : Blackboard) -> Unit

Copy all values from another blackboard into this one.

#
Blackboard::new

fn Blackboard::new() -> Blackboard

Create a new empty Blackboard.

#
Blackboard::remove

fn Blackboard::remove(self : Blackboard, key : String) -> Unit

Remove a key from the blackboard. No-op if the key does not exist.

#
Blackboard::restore

fn Blackboard::restore(self : Blackboard) -> Unit

Roll back to the most recent snapshot taken with snapshot(). If no snapshot exists, this is a no-op.

#
Blackboard::retain

fn Blackboard::retain(self : Blackboard, keys : Array[String]) -> Unit

Remove all keys except the supplied allow-list.

#
Blackboard::select

fn Blackboard::select(self : Blackboard, keys : Array[String]) -> Blackboard

Copy a selected set of keys into a fresh blackboard.

#
Blackboard::set_bool

fn Blackboard::set_bool(self : Blackboard, key : String, val : Bool) -> Unit

Store a Bool under the given key.

#
Blackboard::set_double

fn Blackboard::set_double(self : Blackboard, key : String, val : Double) -> Unit

Store a Double under the given key.

#
Blackboard::set_if_absent

fn Blackboard::set_if_absent(self : Blackboard, key : String, value : Value) -> Bool

Insert a value only when the key does not already exist.

#
Blackboard::set_int

fn Blackboard::set_int(self : Blackboard, key : String, val : Int) -> Unit

Store an Int under the given key.

#
Blackboard::set_string

fn Blackboard::set_string(self : Blackboard, key : String, val : String) -> Unit

Store a String under the given key.

#
Blackboard::set_value

fn Blackboard::set_value(self : Blackboard, key : String, value : Value) -> Unit

Store a previously captured value.

#
Blackboard::size

fn Blackboard::size(self : Blackboard) -> Int

Returns the number of keys currently stored on the blackboard.

#
Blackboard::snapshot

fn Blackboard::snapshot(self : Blackboard) -> Unit

Take a snapshot of the current data. Subsequent changes can be rolled back via restore(). Snapshots are stacked, so multiple nested saves are safe.

#
Blackboard::snapshot_depth

fn Blackboard::snapshot_depth(self : Blackboard) -> Int

Return the number of active nested snapshots.

#
Blackboard::to_text

fn Blackboard::to_text(self : Blackboard) -> String

Export entries in insertion order as key=value lines.

#
Blackboard::transactional

fn Blackboard::transactional(self : Blackboard, update : (Blackboard) -> Bool) -> Bool

Run an isolated update and commit only when the callback accepts it.

#
BlackboardChange

pub(all) enum BlackboardChange {
Added(String, Value)
Updated(String, Value, Value)
Removed(String, Value)
}

A single change needed to transform one blackboard into another.

#
BlackboardChange::after

Return the value after applying the change, when the key remains present.

#
BlackboardChange::before

fn BlackboardChange::before(self : BlackboardChange) -> Value?

Return the previous value, when the key existed before the change.

#
BlackboardChange::key

fn BlackboardChange::key(self : BlackboardChange) -> String

Return the key affected by a change.

#
BlackboardChange::kind

fn BlackboardChange::kind(self : BlackboardChange) -> String

Return a stable change category for logs and replication metrics.

#
BlackboardChange::to_text

fn BlackboardChange::to_text(self : BlackboardChange) -> String

Format one change as a compact audit line.

#
BlackboardPatch

pub struct BlackboardPatch {
changes : Array[BlackboardChange]
}

A deterministic collection of blackboard changes.

#
BlackboardPatch::apply

fn BlackboardPatch::apply(self : BlackboardPatch, bb : Blackboard) -> Int

Apply a patch and return the number of changes accepted.

#
BlackboardPatch::changes

Return a copy of the ordered changes for inspection or persistence.

#
BlackboardPatch::is_empty

fn BlackboardPatch::is_empty(self : BlackboardPatch) -> Bool

Return whether applying this patch would leave the target unchanged.

#
BlackboardPatch::new

Create a patch from an array of changes.

#
BlackboardPatch::size

fn BlackboardPatch::size(self : BlackboardPatch) -> Int

Return the number of changes in the patch.

#
BlackboardPatch::to_text

fn BlackboardPatch::to_text(self : BlackboardPatch) -> String

Format the patch as one change per line.

#
BlackboardSchema

pub struct BlackboardSchema {
fields : Array[FieldSpec]
}

A collection of field contracts for a behavior tree.

#
BlackboardSchema::add

Add a field contract and return the same schema for fluent setup.

#
BlackboardSchema::apply_defaults

fn BlackboardSchema::apply_defaults(self : BlackboardSchema, bb : Blackboard) -> Int

Install declared defaults without overwriting caller state.

#
BlackboardSchema::is_valid

fn BlackboardSchema::is_valid(self : BlackboardSchema, bb : Blackboard) -> Bool

Return whether the blackboard satisfies every schema field.

#
BlackboardSchema::new

Create an empty schema.

#
BlackboardSchema::size

fn BlackboardSchema::size(self : BlackboardSchema) -> Int

Number of fields in the schema.

#
BlackboardSchema::validate

Validate all fields without mutating the blackboard.

#
BlackboardTransaction

pub struct BlackboardTransaction {
target : Blackboard
original : Blackboard
working : Blackboard
closed : Ref[Bool]
}

A live blackboard transaction with isolated writes.

#
BlackboardTransaction::blackboard

Return the isolated working blackboard for a transaction.

#
BlackboardTransaction::changes

Return the pending changes since the transaction began.

#
BlackboardTransaction::commit

fn BlackboardTransaction::commit(self : BlackboardTransaction) -> Bool

Commit all working values atomically and return whether the commit happened.

#
BlackboardTransaction::is_open

fn BlackboardTransaction::is_open(self : BlackboardTransaction) -> Bool

Return whether the transaction can still be committed.

#
BlackboardTransaction::rollback

fn BlackboardTransaction::rollback(self : BlackboardTransaction) -> Unit

Discard working values. Rollback is idempotent and never changes the target.

#
BudgetReport

pub struct BudgetReport {
frame : Int
attempted : Int
skipped : Int
pending : Int
results : Array[BudgetTick]
}

Aggregate outcome of one budget scheduler frame.

#
BudgetReport::attempted

fn BudgetReport::attempted(self : BudgetReport) -> Int

Number of tasks ticked in this frame.

#
BudgetReport::frame

fn BudgetReport::frame(self : BudgetReport) -> Int

Current scheduler frame.

#
BudgetReport::pending

fn BudgetReport::pending(self : BudgetReport) -> Int

Number of tasks not yet terminal, including paused tasks.

#
BudgetReport::results

fn BudgetReport::results(self : BudgetReport) -> Array[BudgetTick]

Copy the results in scheduling order.

#
BudgetReport::skipped

fn BudgetReport::skipped(self : BudgetReport) -> Int

Number of runnable tasks left out by the budget.

#
BudgetReport::to_csv

fn BudgetReport::to_csv(self : BudgetReport) -> String

Export the frame report as CSV.

#
BudgetScheduler

pub struct BudgetScheduler {
tasks : Array[BudgetTask]
budget : Ref[Int]
frame : Ref[Int]
}

A scheduler with a hard per-frame task budget.

#
BudgetScheduler::add

fn BudgetScheduler::add(self : BudgetScheduler, task : BudgetTask) -> Bool

Add a task unless another task has the same name.

#
BudgetScheduler::budget

fn BudgetScheduler::budget(self : BudgetScheduler) -> Int

Return the maximum number of tasks per frame.

#
BudgetScheduler::clear

fn BudgetScheduler::clear(self : BudgetScheduler) -> Unit

Remove all tasks and reset the frame counter.

#
BudgetScheduler::frame

fn BudgetScheduler::frame(self : BudgetScheduler) -> Int

Current scheduler frame number.

#
BudgetScheduler::has

fn BudgetScheduler::has(self : BudgetScheduler, name : String) -> Bool

Return whether a named task exists.

#
BudgetScheduler::new

fn BudgetScheduler::new(max_tasks_per_frame : Int) -> BudgetScheduler

Create an empty priority scheduler.

#
BudgetScheduler::pause

fn BudgetScheduler::pause(self : BudgetScheduler, name : String) -> Bool

Pause one task without changing its node state.

#
BudgetScheduler::pending

fn BudgetScheduler::pending(self : BudgetScheduler) -> Int

Number of tasks that have not reached a terminal result.

#
BudgetScheduler::remove

fn BudgetScheduler::remove(self : BudgetScheduler, name : String) -> Bool

Remove a named task. The task's node is not reset after removal.

#
BudgetScheduler::reset

fn BudgetScheduler::reset(self : BudgetScheduler, name : String) -> Bool

Reset one task and make it runnable again.

#
BudgetScheduler::reset_all

fn BudgetScheduler::reset_all(self : BudgetScheduler) -> Unit

Reset all tasks and the scheduler frame counter.

#
BudgetScheduler::resume_task

fn BudgetScheduler::resume_task(self : BudgetScheduler, name : String) -> Bool

Resume one paused task.

#
BudgetScheduler::set_budget

fn BudgetScheduler::set_budget(self : BudgetScheduler, max_tasks_per_frame : Int) -> Unit

Update the frame budget; invalid values become one.

#
BudgetScheduler::set_priority

fn BudgetScheduler::set_priority(self : BudgetScheduler, name : String, priority : Int) -> Bool

Change one task's priority.

#
BudgetScheduler::size

fn BudgetScheduler::size(self : BudgetScheduler) -> Int

Number of registered tasks.

#
BudgetScheduler::task

fn BudgetScheduler::task(self : BudgetScheduler, name : String) -> BudgetTask?

Return the first task with the requested name, if any.

#
BudgetScheduler::task_names

fn BudgetScheduler::task_names(self : BudgetScheduler) -> Array[String]

Return a copy of all registered task names.

#
BudgetScheduler::tick

Tick the highest-priority runnable tasks up to the frame budget.

#
BudgetTask

pub struct BudgetTask {
name : String
priority : Int
root : Node
bb : Blackboard
paused : Ref[Bool]
done : Ref[Bool]
last : Ref[Status?]
ticks : Ref[Int]
}

One independently scheduled behavior tree.

#
BudgetTask::blackboard

fn BudgetTask::blackboard(self : BudgetTask) -> Blackboard

Return the task's isolated blackboard.

#
BudgetTask::is_done

fn BudgetTask::is_done(self : BudgetTask) -> Bool

Whether the task completed and is waiting for an explicit reset.

#
BudgetTask::is_paused

fn BudgetTask::is_paused(self : BudgetTask) -> Bool

Whether the task is paused.

#
BudgetTask::last_status

fn BudgetTask::last_status(self : BudgetTask) -> Status?

Return the last observed status, if the task has run.

#
BudgetTask::name

fn BudgetTask::name(self : BudgetTask) -> String

Task name used in reports and control operations.

#
BudgetTask::new

fn BudgetTask::new(name : String, priority : Int, root : Node, bb : Blackboard) -> BudgetTask

Create a scheduled task with an isolated blackboard.

#
BudgetTask::priority

fn BudgetTask::priority(self : BudgetTask) -> Int

Current scheduling priority. Larger values run first.

#
BudgetTask::set_priority

fn BudgetTask::set_priority(self : BudgetTask, priority : Int) -> Unit

Change priority without resetting the task.

#
BudgetTask::ticks

fn BudgetTask::ticks(self : BudgetTask) -> Int

Number of ticks delivered to this task since reset.

#
BudgetTick

pub struct BudgetTick {
name : String
priority : Int
frame : Int
status : Status
}

One task result from a budget scheduler frame.

#
BudgetTick::frame

fn BudgetTick::frame(self : BudgetTick) -> Int

Scheduler frame in which the result was produced.

#
BudgetTick::name

fn BudgetTick::name(self : BudgetTick) -> String

Name of the task that was ticked.

#
BudgetTick::priority

fn BudgetTick::priority(self : BudgetTick) -> Int

Priority used for this result.

#
BudgetTick::status

fn BudgetTick::status(self : BudgetTick) -> Status

Status returned by the task.

#
BudgetTick::to_csv

fn BudgetTick::to_csv(self : BudgetTick) -> String

Stable CSV row for a task result.

#
Builder

pub struct Builder {
stack : Array[(FrameType, Array[Node])]
root : Node?
}

#
Builder::action

fn Builder::action(self : Builder, act : (Blackboard) -> Status) -> Builder

#
Builder::build

fn Builder::build(self : Builder) -> Node

#
Builder::condition

fn Builder::condition(self : Builder, cond : (Blackboard) -> Bool) -> Builder

#
Builder::delay

fn Builder::delay(self : Builder, delay_ticks : Int) -> Builder

#
Builder::end

fn Builder::end(self : Builder) -> Builder

#
Builder::inverter

fn Builder::inverter(self : Builder) -> Builder

#
Builder::limited_sequence

fn Builder::limited_sequence(self : Builder, limit : Int) -> Builder

#
Builder::limiter

fn Builder::limiter(self : Builder, min_ticks : Int) -> Builder

#
Builder::new

fn Builder::new() -> Builder

#
Builder::parallel

fn Builder::parallel(self : Builder, policy : ParallelPolicy) -> Builder

#
Builder::repeater

fn Builder::repeater(self : Builder, max_repeats : Int) -> Builder

#
Builder::selector

fn Builder::selector(self : Builder) -> Builder

#
Builder::sequence

fn Builder::sequence(self : Builder) -> Builder

#
Builder::succeeder

fn Builder::succeeder(self : Builder) -> Builder

#
Builder::trace

fn Builder::trace(self : Builder, name : String) -> Builder

#
Builder::wait_until

fn Builder::wait_until(self : Builder, cond : (Blackboard) -> Bool) -> Builder

#
EventBus

pub struct EventBus {
pending : Map[String, Bool]
handlers : Map[String, (Blackboard) -> Status]
}

EventBus holds a set of pending events and registered handlers.

#
EventBus::clear_all

fn EventBus::clear_all(self : EventBus) -> Unit

Clear all pending events.

#
EventBus::consume

fn EventBus::consume(self : EventBus, name : String) -> Unit

Consume (clear) a named event after it has been handled.

#
EventBus::dispatch

fn EventBus::dispatch(self : EventBus, name : String, bb : Blackboard) -> Status?

Dispatch a pending event to its registered handler exactly once. Returns None when the event is not pending or has no handler.

#
EventBus::emit

fn EventBus::emit(self : EventBus, name : String) -> Unit

Raise a named event so it will be consumed on the next tick.

#
EventBus::has_event

fn EventBus::has_event(self : EventBus, name : String) -> Bool

Check if a named event is currently pending.

#
EventBus::new

fn EventBus::new() -> EventBus

Create a new EventBus.

#
EventBus::on

fn EventBus::on(self : EventBus, name : String, handler : (Blackboard) -> Status) -> Unit

Register a handler closure for a named event. When the event fires and the InterruptNode is ticked, this handler runs.

#
EventQueue

pub struct EventQueue {
items : Array[EventRecord]
next_id : Ref[Int]
}

FIFO event queue.

#
EventQueue::clear

fn EventQueue::clear(self : EventQueue) -> Unit

Discard pending events but preserve monotonically increasing IDs.

#
EventQueue::contains

fn EventQueue::contains(self : EventQueue, name : String) -> Bool

Whether at least one event with this name is pending.

#
EventQueue::drain

fn EventQueue::drain(self : EventQueue) -> Array[EventRecord]

Remove every pending event and return the removed records.

#
EventQueue::emit

fn EventQueue::emit(self : EventQueue, name : String) -> Int

Append an event without a payload.

#
EventQueue::emit_value

fn EventQueue::emit_value(self : EventQueue, name : String, payload : Value) -> Int

Append an event with a payload.

#
EventQueue::new

fn EventQueue::new() -> EventQueue

Create an empty queue.

#
EventQueue::pending

fn EventQueue::pending(self : EventQueue) -> Array[EventRecord]

Copy pending events in FIFO order without consuming them.

#
EventQueue::poll

fn EventQueue::poll(self : EventQueue) -> EventRecord?

Remove and return the first event, if any.

#
EventQueue::poll_name

fn EventQueue::poll_name(self : EventQueue, name : String) -> EventRecord?

Remove and return the first matching event.

#
EventQueue::size

fn EventQueue::size(self : EventQueue) -> Int

Number of pending events.

#
EventRecord

pub struct EventRecord {
id : Int
name : String
payload : Value?
}

One ordered event with an optional typed payload.

#
EventRecord::id

fn EventRecord::id(self : EventRecord) -> Int

Event sequence number.

#
EventRecord::name

fn EventRecord::name(self : EventRecord) -> String

Event name.

#
EventRecord::new

fn EventRecord::new(id : Int, name : String, payload : Value?) -> EventRecord

Create an event record.

#
EventRecord::payload

fn EventRecord::payload(self : EventRecord) -> Value?

Optional payload.

#
EventRoute

pub struct EventRoute {
name : String
handler : (Blackboard, EventRecord) -> Status
}

A named event handler used by EventRouter.

#
EventRoute::name

fn EventRoute::name(self : EventRoute) -> String

Return the event name handled by this route.

#
EventRoute::new

fn EventRoute::new(name : String, handler : (Blackboard, EventRecord) -> Status) -> EventRoute

Register a handler for an event name.

#
EventRouter

pub struct EventRouter {
queue : EventQueue
routes : Array[EventRoute]
fallback : Ref[EventRoute?]
max_events : Ref[Int]
active : Ref[EventRecord?]
}

A bounded event-to-handler dispatcher.

#
EventRouter::active_event

fn EventRouter::active_event(self : EventRouter) -> EventRecord?

Return the active event being resumed, if a handler returned Running.

#
EventRouter::cancel_active

fn EventRouter::cancel_active(self : EventRouter) -> Unit

Cancel the active handler without consuming queued events.

#
EventRouter::clear_fallback

fn EventRouter::clear_fallback(self : EventRouter) -> Unit

Remove the fallback handler.

#
EventRouter::deliver

fn EventRouter::deliver(self : EventRouter, name : String, bb : Blackboard, payload : Value?) -> Status

Deliver one event immediately through a named route without queueing it.

#
EventRouter::has_route

fn EventRouter::has_route(self : EventRouter, name : String) -> Bool

Return whether a named route exists.

#
EventRouter::is_running

fn EventRouter::is_running(self : EventRouter) -> Bool

Return whether a handler is waiting for another frame.

#
EventRouter::max_events

fn EventRouter::max_events(self : EventRouter) -> Int

Current per-tick event budget.

#
EventRouter::new

fn EventRouter::new(queue : EventQueue, max_events : Int) -> EventRouter

Create a router over an existing FIFO queue.

#
EventRouter::on

fn EventRouter::on(self : EventRouter, name : String, handler : (Blackboard, EventRecord) -> Status) -> Bool

Add or replace a named route. Returns true when a route was inserted.

#
EventRouter::queue

fn EventRouter::queue(self : EventRouter) -> EventQueue

Return the queue used by this router.

#
EventRouter::queued

fn EventRouter::queued(self : EventRouter) -> Int

Return the number of queued events, excluding an active event.

#
EventRouter::remove

fn EventRouter::remove(self : EventRouter, name : String) -> Bool

Remove one named route. Returns whether a route was removed.

#
EventRouter::reset

fn EventRouter::reset(self : EventRouter) -> Unit

Clear queued and active work, preserving route registrations.

#
EventRouter::route_count

fn EventRouter::route_count(self : EventRouter) -> Int

Number of registered routes.

#
EventRouter::route_names

fn EventRouter::route_names(self : EventRouter) -> Array[String]

Return all registered route names in declaration order.

#
EventRouter::set_fallback

fn EventRouter::set_fallback(self : EventRouter, handler : (Blackboard, EventRecord) -> Status) -> Unit

Install a handler for events without a named route.

#
EventRouter::set_max_events

fn EventRouter::set_max_events(self : EventRouter, max_events : Int) -> Unit

Set the maximum number of new events processed in one tick.

#
EventRouter::tick

Process at most the configured number of events.

A Running handler keeps the current EventRecord active. The event is not polled from the queue again, so handlers can safely perform multi-frame animation, network retry, or resource loading work.

#
EventRouterReport

pub struct EventRouterReport {
processed : Int
succeeded : Int
failed : Int
running : Bool
pending : Int
status : Status
}

A bounded router tick report for monitoring and backpressure decisions.

#
EventRouterReport::failed

fn EventRouterReport::failed(self : EventRouterReport) -> Int

Number of events that failed during this tick.

#
EventRouterReport::is_running

fn EventRouterReport::is_running(self : EventRouterReport) -> Bool

Whether a handler is still running across frames.

#
EventRouterReport::pending

fn EventRouterReport::pending(self : EventRouterReport) -> Int

Number of queued or active events after this tick.

#
EventRouterReport::processed

fn EventRouterReport::processed(self : EventRouterReport) -> Int

Number of events delivered during this router tick.

#
EventRouterReport::status

Overall status for the router tick.

#
EventRouterReport::succeeded

fn EventRouterReport::succeeded(self : EventRouterReport) -> Int

Number of events that completed successfully during this tick.

#
EventRouterReport::to_line

fn EventRouterReport::to_line(self : EventRouterReport) -> String

Format the report for a log line or metrics sample.

#
FieldSpec

pub struct FieldSpec {
key : String
kind : ValueKind
required : Bool
default_value : Value?
}

Describe one required or optional blackboard field.

#
FieldSpec::optional

fn FieldSpec::optional(key : String, kind : ValueKind) -> FieldSpec

Create an optional field specification.

#
FieldSpec::required

fn FieldSpec::required(key : String, kind : ValueKind) -> FieldSpec

Create a required field specification.

#
FieldSpec::with_default

fn FieldSpec::with_default(key : String, kind : ValueKind, default_value : Value) -> FieldSpec

Create a field with a value to install when absent.

#
FrameType

pub enum FrameType {
Seq
Sel
Par(ParallelPolicy)
Inv
Suc
Rep(Int)
Lim(Int)
DelayFrm(Int)
LimSeq(Int)
}

#
IntComparison

pub(all) enum IntComparison {
LessThan
LessOrEqual
EqualTo
GreaterOrEqual
GreaterThan
} derive(Eq,
Debug
)

Compare a blackboard integer with a threshold.

#
MetricsSnapshot

pub struct MetricsSnapshot {
name : String
total : Int
success : Int
failure : Int
running : Int
}

A named telemetry sample suitable for exporting to text or logs.

#
MetricsSnapshot::to_csv

fn MetricsSnapshot::to_csv(self : MetricsSnapshot) -> String

Format a snapshot as one CSV row for simple observability pipelines.

#
Node

pub struct Node {
tick_fn : (Blackboard) -> Status
reset_fn : () -> Unit
}

#
Node::new

fn Node::new(tick_fn : (Blackboard) -> Status, reset_fn : () -> Unit) -> Node

Node::new creates a node from tick and reset closures.

#
Node::reset

fn Node::reset(self : Node) -> Unit

reset restores the node to its initial state.

#
Node::tick

fn Node::tick(self : Node, bb : Blackboard) -> Status

tick executes the node for one frame and returns its Status.

#
NodeMetrics

pub struct NodeMetrics {
total_ticks : Ref[Int]
successes : Ref[Int]
failures : Ref[Int]
running : Ref[Int]
consecutive_running : Ref[Int]
last : Ref[Status?]
}

Aggregate counters for one instrumented node.

#
NodeMetrics::consecutive_running

fn NodeMetrics::consecutive_running(self : NodeMetrics) -> Int

Longest current running streak.

#
NodeMetrics::failures

fn NodeMetrics::failures(self : NodeMetrics) -> Int

Number of failed observations.

#
NodeMetrics::last_status

fn NodeMetrics::last_status(self : NodeMetrics) -> Status?

Most recently observed status.

#
NodeMetrics::new

Create empty metrics.

#
NodeMetrics::record

fn NodeMetrics::record(self : NodeMetrics, status : Status) -> Unit

Record one status observation.

#
NodeMetrics::reset

fn NodeMetrics::reset(self : NodeMetrics) -> Unit

Reset all counters.

#
NodeMetrics::running

fn NodeMetrics::running(self : NodeMetrics) -> Int

Number of running observations.

#
NodeMetrics::successes

fn NodeMetrics::successes(self : NodeMetrics) -> Int

Number of successful observations.

#
NodeMetrics::total_ticks

fn NodeMetrics::total_ticks(self : NodeMetrics) -> Int

Number of observed ticks.

#
ParallelPolicy

pub(all) enum ParallelPolicy {
SuccessAll
SuccessOne
} derive(Eq,
Debug
)

#
Ref

pub struct Ref[T] {
val : T
}

#
Ref::get

fn[T] Ref::get(self : Ref[T]) -> T

Get the current value.

#
Ref::new

fn[T] Ref::new(val : T) -> Ref[T]

Create a new Ref holding the given value.

#
Ref::set

fn[T] Ref::set(self : Ref[T], val : T) -> Unit

Set the value.

#
RunnerHealth

pub struct RunnerHealth {
frame : Int
terminal : Bool
status : String
}

A compact health record for a tree runner.

#
RunnerHealth::frame

fn RunnerHealth::frame(self : RunnerHealth) -> Int

Return the inspected frame number.

#
RunnerHealth::is_terminal

fn RunnerHealth::is_terminal(self : RunnerHealth) -> Bool

Return whether the inspected runner is terminal.

#
RunnerHealth::status

fn RunnerHealth::status(self : RunnerHealth) -> String

Return the status label without formatting.

#
RunnerHealth::to_line

fn RunnerHealth::to_line(self : RunnerHealth) -> String

Return a stable line suitable for a readiness endpoint.

#
Scenario

pub struct Scenario {
name : String
root : Node
bb : Blackboard
frames : Int
}

A named tree workload with a fixed frame budget.

#
Scenario::new

fn Scenario::new(name : String, root : Node, bb : Blackboard, frames : Int) -> Scenario

Construct a scenario.

#
Scenario::run

fn Scenario::run(self : Scenario) -> BenchmarkResult

Execute a scenario and return its deterministic outcome report.

#
Scheduler

pub struct Scheduler {
entries : Array[SchedulerEntry]
}

Scheduler drives multiple named behavior trees in priority order.

#
Scheduler::add

fn Scheduler::add(self : Scheduler, entry : SchedulerEntry) -> Unit

Add a behavior tree to the scheduler. Trees are ticked in the order they are added.

#
Scheduler::has

fn Scheduler::has(self : Scheduler, name : String) -> Bool

Returns true if a tree with the given name exists in the scheduler.

#
Scheduler::new

fn Scheduler::new() -> Scheduler

Create a new empty Scheduler.

#
Scheduler::pause

fn Scheduler::pause(self : Scheduler, name : String) -> Unit

Pause a named tree so it is skipped during tick.

#
Scheduler::remove

fn Scheduler::remove(self : Scheduler, name : String) -> Unit

Remove a behavior tree by name.

#
Scheduler::reset_all

fn Scheduler::reset_all(self : Scheduler) -> Unit

Reset all trees managed by this scheduler.

#
Scheduler::restart

fn Scheduler::restart(self : Scheduler, name : String) -> Unit

Resume a paused tree.

#
Scheduler::size

fn Scheduler::size(self : Scheduler) -> Int

Returns the number of entries registered in the scheduler.

#
Scheduler::tick

fn Scheduler::tick(self : Scheduler) -> Array[TickResult]

Tick all non-paused trees once and return per-tree results.

#
SchedulerEntry

pub struct SchedulerEntry {
name : String
root : Node
bb : Blackboard
paused : Ref[Bool]
}

Entry wraps a behavior tree with its dedicated blackboard and runtime state.

#
SchedulerEntry::new

fn SchedulerEntry::new(name : String, root : Node, bb : Blackboard) -> SchedulerEntry

SchedulerEntry::new creates an entry for the given tree root.

#
Status

pub(all) enum Status {
BTSuccess
BTFailure
BTRunning
} derive(Eq,
Debug
)

impl Show for Status

#
Status::to_string

fn Status::to_string(self : Status) -> String

to_string returns a human-readable representation of the Status.

#
TickResult

pub struct TickResult {
name : String
status : Status
}

TickResult records the outcome of a single tree tick in the scheduler.

#
TraceBuffer

pub struct TraceBuffer {
entries : Array[TraceEntry]
capacity : Int
next_sequence : Ref[Int]
}

A bounded FIFO trace window.

#
TraceBuffer::capacity

fn TraceBuffer::capacity(self : TraceBuffer) -> Int

Maximum number of entries retained by this buffer.

#
TraceBuffer::clear

fn TraceBuffer::clear(self : TraceBuffer) -> Unit

Remove all observations while keeping sequence numbers monotonic.

#
TraceBuffer::count_status

fn TraceBuffer::count_status(self : TraceBuffer, expected : Status) -> Int

Count observations with one status in the current window.

#
TraceBuffer::entries

fn TraceBuffer::entries(self : TraceBuffer) -> Array[TraceEntry]

Return retained entries from oldest to newest.

#
TraceBuffer::filter_status

fn TraceBuffer::filter_status(self : TraceBuffer, expected : Status) -> Array[TraceEntry]

Export only entries whose status matches the requested value.

#
TraceBuffer::find_label

fn TraceBuffer::find_label(self : TraceBuffer, label : String) -> TraceEntry?

Return the first retained entry with the supplied label.

#
TraceBuffer::is_empty

fn TraceBuffer::is_empty(self : TraceBuffer) -> Bool

Return whether the trace window has no observations.

#
TraceBuffer::is_full

fn TraceBuffer::is_full(self : TraceBuffer) -> Bool

Return whether the buffer has reached its retention limit.

#
TraceBuffer::last

fn TraceBuffer::last(self : TraceBuffer) -> TraceEntry?

Return the newest retained entry, if any.

#
TraceBuffer::new

fn TraceBuffer::new(capacity : Int) -> TraceBuffer

Create a trace buffer. Non-positive capacities are normalized to one.

#
TraceBuffer::record

fn TraceBuffer::record(self : TraceBuffer, label : String, status : Status, bb : Blackboard) -> Unit

Record one node result and evict the oldest entry when full.

#
TraceBuffer::recorded_total

fn TraceBuffer::recorded_total(self : TraceBuffer) -> Int

Return the total number of observations recorded since construction.

#
TraceBuffer::size

fn TraceBuffer::size(self : TraceBuffer) -> Int

Number of entries currently retained.

#
TraceBuffer::summary

fn TraceBuffer::summary(self : TraceBuffer) -> TraceSummary

Summarize the currently retained window.

#
TraceBuffer::to_csv

fn TraceBuffer::to_csv(self : TraceBuffer) -> String

Export a stable header and all retained observations as CSV.

#
TraceEntry

pub struct TraceEntry {
sequence : Int
label : String
status : Status
blackboard_size : Int
}

One observed node execution in a trace window.

#
TraceEntry::blackboard_size

fn TraceEntry::blackboard_size(self : TraceEntry) -> Int

Number of values visible when the node returned.

#
TraceEntry::label

fn TraceEntry::label(self : TraceEntry) -> String

Application-provided node label.

#
TraceEntry::sequence

fn TraceEntry::sequence(self : TraceEntry) -> Int

Monotonic sequence number of this observation.

#
TraceEntry::status

fn TraceEntry::status(self : TraceEntry) -> Status

Status returned by the node.

#
TraceEntry::to_csv

fn TraceEntry::to_csv(self : TraceEntry) -> String

Convert a trace entry into a stable CSV row.

#
TraceSummary

pub struct TraceSummary {
total : Int
successes : Int
failures : Int
running : Int
}

Aggregate counts for the current trace window.

#
TraceSummary::failures

fn TraceSummary::failures(self : TraceSummary) -> Int

Failed observations in the summary.

#
TraceSummary::running

fn TraceSummary::running(self : TraceSummary) -> Int

Running observations in the summary.

#
TraceSummary::successes

fn TraceSummary::successes(self : TraceSummary) -> Int

Successful observations in the summary.

#
TraceSummary::total

fn TraceSummary::total(self : TraceSummary) -> Int

Total observations in the summary.

#
TreeRunner

pub struct TreeRunner {
root : Node
bb : Blackboard
frame : Ref[Int]
last : Ref[Status?]
}

#
TreeRunner::blackboard

fn TreeRunner::blackboard(self : TreeRunner) -> Blackboard

Return the runner's isolated blackboard.

#
TreeRunner::frame

fn TreeRunner::frame(self : TreeRunner) -> Int

Return the number of frames ticked since the last reset.

#
TreeRunner::is_terminal

fn TreeRunner::is_terminal(self : TreeRunner) -> Bool

Return whether the last tick completed.

#
TreeRunner::last_status

fn TreeRunner::last_status(self : TreeRunner) -> Status?

Return the most recent status, if the runner has been ticked.

#
TreeRunner::new

fn TreeRunner::new(root : Node, bb : Blackboard) -> TreeRunner

Create a runner with an isolated blackboard.

#
TreeRunner::reset

fn TreeRunner::reset(self : TreeRunner) -> Unit

Reset the tree and frame-local lifecycle state.

#
TreeRunner::run_until_terminal

fn TreeRunner::run_until_terminal(self : TreeRunner, max_ticks : Int) -> Status

Run at most max_ticks frames, returning early on a terminal status.

#
TreeRunner::tick

fn TreeRunner::tick(self : TreeRunner) -> Status

Tick one frame and record the last observed status.

#
UtilityCandidate

pub struct UtilityCandidate {
name : String
score : (Blackboard) -> Double
node : Node
}

One named candidate in a utility selector.

#
UtilityCandidate::evaluate

fn UtilityCandidate::evaluate(self : UtilityCandidate, bb : Blackboard) -> Double

Evaluate a candidate against a blackboard.

#
UtilityCandidate::name

fn UtilityCandidate::name(self : UtilityCandidate) -> String

Candidate name for diagnostics.

#
UtilityCandidate::new

fn UtilityCandidate::new(name : String, score : (Blackboard) -> Double, node : Node) -> UtilityCandidate

Construct a utility candidate.

#
ValidationIssue

pub enum ValidationIssue {
MissingField(String)
WrongType(String, ValueKind, ValueKind)
} derive(Eq)

A single schema validation finding.

#
ValidationIssue::to_string

fn ValidationIssue::to_string(self : ValidationIssue) -> String

Format a validation finding for logs and test reports.

#
Value

pub(all) enum Value {
Bool(Bool)
Int(Int)
Double(Double)
Str(String)
}

#
Value::as_bool

fn Value::as_bool(self : Value) -> Bool?

Read a Bool from a generic value.

#
Value::as_double

fn Value::as_double(self : Value) -> Double?

Read a Double from a generic value.

#
Value::as_int

fn Value::as_int(self : Value) -> Int?

Read an Int from a generic value.

#
Value::as_string

fn Value::as_string(self : Value) -> String?

Read a String from a generic value.

#
Value::kind

fn Value::kind(self : Value) -> ValueKind

Return the category of a concrete value.

#
Value::same

fn Value::same(self : Value, other : Value) -> Bool

Compare two supported values without relying on representation details.

#
Value::to_text

fn Value::to_text(self : Value) -> String

Return a stable textual representation for logs and fixtures.

#
Value::type_name

fn Value::type_name(self : Value) -> String

Convert a Value into a stable type label for diagnostics.

#
ValueKind

pub(all) enum ValueKind {
BoolKind
IntKind
DoubleKind
StringKind
} derive(Eq)

The four value categories supported by Blackboard.

#
ValueKind::to_string

fn ValueKind::to_string(self : ValueKind) -> String

Return a stable name for a value category.

#
action_node

fn action_node(action : (Blackboard) -> Status) -> Node

#
all_condition_node

fn all_condition_node(predicates : Array[(Blackboard) -> Bool]) -> Node

A condition node backed by a set of predicates.

#
all_conditions

fn all_conditions(predicates : Array[(Blackboard) -> Bool], bb : Blackboard) -> Bool

Evaluate all predicates from left to right.

#
all_int_comparisons

fn all_int_comparisons() -> Array[IntComparison]

Return every comparison in a stable order for configuration UIs.

#
any_condition_node

fn any_condition_node(predicates : Array[(Blackboard) -> Bool]) -> Node

A condition node that succeeds when any predicate is true.

#
any_conditions

fn any_conditions(predicates : Array[(Blackboard) -> Bool], bb : Blackboard) -> Bool

Evaluate predicates until one is true.

#
benchmark_csv

fn benchmark_csv(results : Array[BenchmarkResult]) -> String

Format results as a CSV document with a stable header.

#
benchmark_health

fn benchmark_health(result : BenchmarkResult) -> String

Return a one-line health summary for a benchmark result.

#
benchmark_node

fn benchmark_node(name : String, root : Node, bb : Blackboard, frames : Int) -> BenchmarkResult

Execute exactly frames ticks and collect outcome counts.

#
benchmark_workload

fn benchmark_workload() -> Node

Build a deterministic mixed workload used by examples and CI.

#
blackboard_value_type

fn blackboard_value_type(bb : Blackboard, key : String) -> String?

Return the type name stored under a key, or None when absent.

#
compare_int

fn compare_int(bb : Blackboard, key : String, comparison : IntComparison, expected : Int) -> Bool

Return whether an integer field satisfies a comparison.

#
condition_guard_node

fn condition_guard_node(predicate : (Blackboard) -> Bool, child : Node) -> Node

condition_guard_node creates a reactive guard wrapper. It ticks child only while predicate(bb) is true. If the predicate fails mid-execution, the child is reset and Failure is returned.

#
condition_node

fn condition_node(cond : (Blackboard) -> Bool) -> Node

#
context_guard_node

fn context_guard_node(required : Array[String], child : Node) -> Node

Execute a tree only when its required context is complete.

#
cooldown_node

fn cooldown_node(child : Node, cooldown_ticks : Int) -> Node

A decorator that blocks a completed child for a number of future frames.

#
copy_value

fn copy_value(source : Blackboard, target : Blackboard, key : String) -> Bool

Copy one value from a source blackboard when its type matches.

#
copy_values

fn copy_values(source : Blackboard, target : Blackboard, keys : Array[String]) -> Int

Copy many named values and return the number found.

#
count_present_keys

fn count_present_keys(bb : Blackboard, required : Array[String]) -> Int

Count how many required keys are present.

#
count_terminal

fn count_terminal(children : Array[Node], bb : Blackboard) -> Int

Return the number of child outcomes that are already terminal.

#
create_always_failure

fn create_always_failure() -> Node

#
create_always_running

fn create_always_running() -> Node

#
create_always_success

fn create_always_success() -> Node

#
defer_terminal_node

fn defer_terminal_node(child : Node) -> Node

Convert a terminal result into Running for one frame before completing.

#
delay_node

fn delay_node(child : Node, delay_ticks : Int) -> Node

#
execution_budget_node

fn execution_budget_node(child : Node, max_ticks : Int) -> Node

Limit the total number of child ticks during one activation. This protects service loops from accidental infinite-running subtrees.

#
has_required_keys

fn has_required_keys(bb : Blackboard, required : Array[String]) -> Bool

Check that a blackboard contains all required keys.

#
inspect_runner

fn inspect_runner(runner : TreeRunner) -> RunnerHealth

Inspect a runner without changing its execution state.

#
int_changed_node

fn int_changed_node(key : String) -> Node

Detect whether an integer value changed since the previous tick.

#
int_comparison_node

fn int_comparison_node(key : String, comparison : IntComparison, expected : Int) -> Node

Make an integer comparison node.

#
int_range_guard_node

fn int_range_guard_node(key : String, minimum : Int, maximum : Int, child : Node) -> Node

Run a child only while a blackboard integer is within an inclusive range.

#
interrupt_node

fn interrupt_node(bus : EventBus, event_name : String, child : Node, interrupt_handler : Node) -> Node

interrupt_node creates a node that monitors a named event. When the event is raised, interrupt_handler runs instead of child.

#
inverter_node

fn inverter_node(child : Node) -> Node

#
limited_sequence

fn limited_sequence(children : Array[Node], limit : Int) -> Node

#
limiter_node

fn limiter_node(child : Node, min_ticks : Int) -> Node

#
make_parallel_all

fn make_parallel_all(children : Array[Node]) -> Node

#
make_parallel_one

fn make_parallel_one(children : Array[Node]) -> Node

#
make_selector

fn make_selector(children : Array[Node]) -> Node

#
make_sequence

fn make_sequence(children : Array[Node]) -> Node

#
metrics_node

fn metrics_node(child : Node, metrics : NodeMetrics) -> Node

Wrap a node and record every tick without changing its behavior.

#
not_condition

fn not_condition(predicate : (Blackboard) -> Bool) -> ((Blackboard) -> Bool)

Create the logical negation of a predicate.

#
once_node

fn once_node(child : Node) -> Node

Execute a child at most once per activation and remember its outcome.

#
parallel_node

fn parallel_node(children : Array[Node], policy : ParallelPolicy) -> Node

parallel_node creates a Parallel composite node.

#
quorum_node

fn quorum_node(children : Array[Node], successes_needed : Int) -> Node

Tick all children until at least successes_needed succeed. A quorum can fail early when the remaining children cannot reach it.

#
race_node

fn race_node(children : Array[Node]) -> Node

Select the first child that succeeds, resetting a running loser on success.

#
random_selector_node

fn random_selector_node(children : Array[Node], seed : Ref[Int]) -> Node

random_selector_node creates a non-deterministic Selector. seed is a mutable seed Ref for the internal LCG.

#
recoverable_retry_node

fn recoverable_retry_node(child : Node, max_retries : Int, should_retry : (Blackboard) -> Bool) -> Node

Retry only when a blackboard predicate says the action is recoverable.

#
repeater_node

fn repeater_node(child : Node, max_repeats : Int) -> Node

#
require_node

fn require_node(predicate : (Blackboard) -> Bool, child : Node) -> Node

Fail when a predicate is false, otherwise delegate to the child.

#
retry_node

fn retry_node(child : Node, max_retries : Int) -> Node

#
route_node

fn route_node(key : String, routes : Map[String, Node], fallback : Node) -> Node

Route to one child using a string key on the blackboard.

#
run_scenarios

fn run_scenarios(scenarios : Array[Scenario]) -> Array[BenchmarkResult]

Execute several scenarios in declaration order.

#
selector_node

fn selector_node(children : Array[Node]) -> Node

selector_node creates a Selector composite node.

#
sequence_node

fn sequence_node(children : Array[Node]) -> Node

sequence_node creates a Sequence composite node.

#
set_bool_on_failure_node

fn set_bool_on_failure_node(key : String, value : Bool, child : Node) -> Node

Set a blackboard value after a child fails.

#
set_int_on_success_node

fn set_int_on_success_node(key : String, value : Int, child : Node) -> Node

Set a blackboard value after a child completes successfully.

#
snapshot_metrics

fn snapshot_metrics(name : String, metrics : NodeMetrics) -> MetricsSnapshot

Read a stable value snapshot from live metrics.

#
stable_failure_node

fn stable_failure_node(child : Node, required : Int) -> Node

Return Failure after required consecutive failed child ticks.

#
stable_success_node

fn stable_success_node(child : Node, required : Int) -> Node

Return Success after required consecutive successful child ticks.

#
standard_benchmark_csv

fn standard_benchmark_csv() -> String

Run the standard matrix and return portable CSV output.

#
standard_scenarios

fn standard_scenarios() -> Array[Scenario]

Build a small but representative workload matrix for local regression.

#
succeeder_node

fn succeeder_node(child : Node) -> Node

#
threshold_utility_node

fn threshold_utility_node(candidates : Array[UtilityCandidate], threshold : Double, fallback : Node) -> Node

Select a candidate only when its score meets a minimum threshold.

#
tick_for_frames

fn tick_for_frames(root : Node, bb : Blackboard, frames : Int) -> Array[Status]

Tick a tree a fixed number of times. Useful for deterministic simulations.

#
timeout_node

fn timeout_node(child : Node, max_ticks : Int) -> Node

#
trace_buffer_node

fn trace_buffer_node(label : String, child : Node, trace : TraceBuffer) -> Node

Wrap a node and record every returned status in a bounded trace.

#
trace_node

fn trace_node(name : String, child : Node) -> Node

#
trace_runner_tick

fn trace_runner_tick(runner : TreeRunner, trace : TraceBuffer) -> Status

Record a completed tree-runner frame with a standard label.

#
until_failure_node

fn until_failure_node(child : Node) -> Node

#
until_success_node

fn until_success_node(child : Node) -> Node

#
utility_selector_node

fn utility_selector_node(candidates : Array[UtilityCandidate], fallback : Node) -> Node

Select the highest-scoring candidate and preserve it while Running.

#
wait_until_node

fn wait_until_node(condition : (Blackboard) -> Bool) -> Node