A pure MoonBit implementation of Behavior Tree (BT) for Game AI and Agents.
| 模块 | 说明 |
|---|---|
| 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 mhh12345678/behavior_tree[deps]
"mhh12345678/behavior_tree" = "0.1.2"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: Successlet 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)bus.on("damage", fn(bb) {
bb.set_int("last_damage", 5)
@bt.BTSuccess
})
bus.emit("damage")
let _ = bus.dispatch("damage", bb)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()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())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()}")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/mainlet csv = @bt.standard_benchmark_csv()
println(csv)moon fmt --check
moon check --deny-warn
moon test --deny-warn
moon info
moon run cmd/main=== 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: Successpub struct BenchmarkResult {
name : String
frames : Int
successes : Int
failures : Int
running : Int
}fn Blackboard::apply_patch_if(self : Blackboard, patch : BlackboardPatch, validate : (Blackboard) -> Bool) -> Boolpub struct BlackboardTransaction {
target : Blackboard
original : Blackboard
working : Blackboard
closed : Ref[Bool]
}pub struct BudgetReport {
frame : Int
attempted : Int
skipped : Int
pending : Int
results : Array[BudgetTick]
}pub struct EventRouter {
queue : EventQueue
routes : Array[EventRoute]
fallback : Ref[EventRoute?]
max_events : Ref[Int]
active : Ref[EventRecord?]
}fn EventRouter::deliver(self : EventRouter, name : String, bb : Blackboard, payload : Value?) -> Statusfn EventRouter::on(self : EventRouter, name : String, handler : (Blackboard, EventRecord) -> Status) -> Boolfn EventRouter::set_fallback(self : EventRouter, handler : (Blackboard, EventRecord) -> Status) -> Unitpub struct EventRouterReport {
processed : Int
succeeded : Int
failed : Int
running : Bool
pending : Int
status : Status
}pub enum FrameType {
Seq
Sel
Par(ParallelPolicy)
Inv
Suc
Rep(Int)
Lim(Int)
DelayFrm(Int)
LimSeq(Int)
}pub struct MetricsSnapshot {
name : String
total : Int
success : Int
failure : Int
running : Int
}pub struct Ref[T] {
val : T
}pub struct RunnerHealth {
frame : Int
terminal : Bool
status : String
}fn TraceBuffer::record(self : TraceBuffer, label : String, status : Status, bb : Blackboard) -> Unitpub struct TraceSummary {
total : Int
successes : Int
failures : Int
running : Int
}fn UtilityCandidate::new(name : String, score : (Blackboard) -> Double, node : Node) -> UtilityCandidatepub(all) enum Value {
Bool(Bool)
Int(Int)
Double(Double)
Str(String)
}fn recoverable_retry_node(child : Node, max_retries : Int, should_retry : (Blackboard) -> Bool) -> Nodefn standard_benchmark_csv() -> Stringfn threshold_utility_node(candidates : Array[UtilityCandidate], threshold : Double, fallback : Node) -> NodeA pure MoonBit implementation of Behavior Tree (BT) for Game AI and Agents.