Skip to main content

proof_engine/behavior/
tree.rs

1//! Behavior Tree core — nodes, blackboard, tick engine, and tree builder.
2//!
3//! # Design
4//! Every node returns [`NodeStatus`] on each `tick()` call.  The tree walks
5//! from the root, short-circuiting Sequences on Failure and Selectors on
6//! Success.  Parallel nodes run all children each tick and aggregate results
7//! according to their policy.  Decorators wrap a single child and modify its
8//! return value or control how often it runs.
9//!
10//! The [`Blackboard`] is a typed key-value store shared by all nodes in a
11//! tree.  It is passed by mutable reference into every tick call so nodes can
12//! read sensor data, write intermediate results, and communicate.
13//!
14//! The [`TreeBuilder`] provides a fluent API for assembling trees without
15//! manually constructing the [`BehaviorNode`] enum.  Subtrees can be stored
16//! and reused by name via [`SubtreeRegistry`].
17
18use std::collections::HashMap;
19use std::time::{Duration, Instant};
20
21// ── NodeStatus ────────────────────────────────────────────────────────────────
22
23/// The three-valued result every behavior-tree node returns each tick.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum NodeStatus {
26    /// The node is still executing and wants to be ticked again.
27    Running,
28    /// The node finished successfully.
29    Success,
30    /// The node failed.
31    Failure,
32}
33
34impl NodeStatus {
35    pub fn is_running(self)  -> bool { self == NodeStatus::Running  }
36    pub fn is_success(self)  -> bool { self == NodeStatus::Success  }
37    pub fn is_failure(self)  -> bool { self == NodeStatus::Failure  }
38    pub fn is_terminal(self) -> bool { self != NodeStatus::Running  }
39
40    /// Invert Success↔Failure; Running passes through unchanged.
41    pub fn invert(self) -> NodeStatus {
42        match self {
43            NodeStatus::Success => NodeStatus::Failure,
44            NodeStatus::Failure => NodeStatus::Success,
45            NodeStatus::Running => NodeStatus::Running,
46        }
47    }
48}
49
50// ── BlackboardValue ───────────────────────────────────────────────────────────
51
52/// All value types storable on a [`Blackboard`].
53#[derive(Debug, Clone, PartialEq)]
54pub enum BlackboardValue {
55    Bool(bool),
56    Int(i64),
57    Float(f64),
58    Text(String),
59    Vec2(glam::Vec2),
60    Vec3(glam::Vec3),
61    EntityId(u64),
62    List(Vec<BlackboardValue>),
63    Map(HashMap<String, BlackboardValue>),
64}
65
66impl BlackboardValue {
67    pub fn as_bool(&self) -> Option<bool> {
68        match self {
69            BlackboardValue::Bool(v)  => Some(*v),
70            BlackboardValue::Int(v)   => Some(*v != 0),
71            BlackboardValue::Float(v) => Some(*v != 0.0),
72            _ => None,
73        }
74    }
75
76    pub fn as_int(&self) -> Option<i64> {
77        match self {
78            BlackboardValue::Int(v)   => Some(*v),
79            BlackboardValue::Bool(v)  => Some(if *v { 1 } else { 0 }),
80            BlackboardValue::Float(v) => Some(*v as i64),
81            _ => None,
82        }
83    }
84
85    pub fn as_float(&self) -> Option<f64> {
86        match self {
87            BlackboardValue::Float(v) => Some(*v),
88            BlackboardValue::Int(v)   => Some(*v as f64),
89            BlackboardValue::Bool(v)  => Some(if *v { 1.0 } else { 0.0 }),
90            _ => None,
91        }
92    }
93
94    pub fn as_str(&self) -> Option<&str> {
95        match self {
96            BlackboardValue::Text(s) => Some(s.as_str()),
97            _ => None,
98        }
99    }
100
101    pub fn as_vec2(&self) -> Option<glam::Vec2> {
102        match self { BlackboardValue::Vec2(v) => Some(*v), _ => None }
103    }
104
105    pub fn as_vec3(&self) -> Option<glam::Vec3> {
106        match self { BlackboardValue::Vec3(v) => Some(*v), _ => None }
107    }
108
109    pub fn as_entity_id(&self) -> Option<u64> {
110        match self { BlackboardValue::EntityId(id) => Some(*id), _ => None }
111    }
112}
113
114impl From<bool>       for BlackboardValue { fn from(v: bool)       -> Self { BlackboardValue::Bool(v)     } }
115impl From<i64>        for BlackboardValue { fn from(v: i64)        -> Self { BlackboardValue::Int(v)      } }
116impl From<i32>        for BlackboardValue { fn from(v: i32)        -> Self { BlackboardValue::Int(v as i64) } }
117impl From<f64>        for BlackboardValue { fn from(v: f64)        -> Self { BlackboardValue::Float(v)    } }
118impl From<f32>        for BlackboardValue { fn from(v: f32)        -> Self { BlackboardValue::Float(v as f64) } }
119impl From<String>     for BlackboardValue { fn from(v: String)     -> Self { BlackboardValue::Text(v)     } }
120impl From<&str>       for BlackboardValue { fn from(v: &str)       -> Self { BlackboardValue::Text(v.to_string()) } }
121impl From<glam::Vec2> for BlackboardValue { fn from(v: glam::Vec2) -> Self { BlackboardValue::Vec2(v)    } }
122impl From<glam::Vec3> for BlackboardValue { fn from(v: glam::Vec3) -> Self { BlackboardValue::Vec3(v)    } }
123impl From<u64>        for BlackboardValue { fn from(v: u64)        -> Self { BlackboardValue::EntityId(v) } }
124
125// ── Blackboard ────────────────────────────────────────────────────────────────
126
127/// A typed key-value store shared by all nodes in a behavior tree.
128///
129/// Entries can optionally expire after a time-to-live (TTL) measured in
130/// seconds.  Call [`Blackboard::tick`] every frame with `dt` to process
131/// expirations.
132#[derive(Debug, Default)]
133pub struct Blackboard {
134    entries: HashMap<String, BlackboardEntry>,
135    /// Running simulation time in seconds (accumulated from `tick(dt)` calls).
136    pub time: f64,
137}
138
139#[derive(Debug, Clone)]
140struct BlackboardEntry {
141    value:      BlackboardValue,
142    /// Absolute expiry time (seconds, same clock as `Blackboard::time`).
143    expires_at: Option<f64>,
144}
145
146impl Blackboard {
147    pub fn new() -> Self { Self::default() }
148
149    /// Advance the blackboard clock and expire stale entries.
150    pub fn tick(&mut self, dt: f64) {
151        self.time += dt;
152        let t = self.time;
153        self.entries.retain(|_, e| {
154            e.expires_at.map_or(true, |exp| t < exp)
155        });
156    }
157
158    /// Insert a key-value pair with no expiry.
159    pub fn set<V: Into<BlackboardValue>>(&mut self, key: &str, value: V) {
160        self.entries.insert(key.to_string(), BlackboardEntry {
161            value: value.into(),
162            expires_at: None,
163        });
164    }
165
166    /// Insert a key-value pair that will expire after `ttl` seconds.
167    pub fn set_with_ttl<V: Into<BlackboardValue>>(&mut self, key: &str, value: V, ttl: f64) {
168        self.entries.insert(key.to_string(), BlackboardEntry {
169            value: value.into(),
170            expires_at: Some(self.time + ttl),
171        });
172    }
173
174    /// Retrieve a value by key.
175    pub fn get(&self, key: &str) -> Option<&BlackboardValue> {
176        self.entries.get(key).map(|e| &e.value)
177    }
178
179    pub fn get_bool(&self, key: &str) -> Option<bool> {
180        self.get(key)?.as_bool()
181    }
182
183    pub fn get_int(&self, key: &str) -> Option<i64> {
184        self.get(key)?.as_int()
185    }
186
187    pub fn get_float(&self, key: &str) -> Option<f64> {
188        self.get(key)?.as_float()
189    }
190
191    pub fn get_str(&self, key: &str) -> Option<&str> {
192        self.get(key)?.as_str()
193    }
194
195    pub fn get_vec2(&self, key: &str) -> Option<glam::Vec2> {
196        self.get(key)?.as_vec2()
197    }
198
199    pub fn get_vec3(&self, key: &str) -> Option<glam::Vec3> {
200        self.get(key)?.as_vec3()
201    }
202
203    pub fn get_entity(&self, key: &str) -> Option<u64> {
204        self.get(key)?.as_entity_id()
205    }
206
207    pub fn contains(&self, key: &str) -> bool {
208        self.entries.contains_key(key)
209    }
210
211    pub fn remove(&mut self, key: &str) -> Option<BlackboardValue> {
212        self.entries.remove(key).map(|e| e.value)
213    }
214
215    pub fn clear(&mut self) {
216        self.entries.clear();
217    }
218
219    /// Number of live entries.
220    pub fn len(&self) -> usize { self.entries.len() }
221    pub fn is_empty(&self) -> bool { self.entries.is_empty() }
222
223    /// Iterate over all live key-value pairs.
224    pub fn iter(&self) -> impl Iterator<Item = (&str, &BlackboardValue)> {
225        self.entries.iter().map(|(k, e)| (k.as_str(), &e.value))
226    }
227
228    /// Compare a float entry against a threshold.
229    pub fn float_gt(&self, key: &str, threshold: f64) -> bool {
230        self.get_float(key).map_or(false, |v| v > threshold)
231    }
232
233    pub fn float_lt(&self, key: &str, threshold: f64) -> bool {
234        self.get_float(key).map_or(false, |v| v < threshold)
235    }
236
237    pub fn float_gte(&self, key: &str, threshold: f64) -> bool {
238        self.get_float(key).map_or(false, |v| v >= threshold)
239    }
240
241    pub fn float_lte(&self, key: &str, threshold: f64) -> bool {
242        self.get_float(key).map_or(false, |v| v <= threshold)
243    }
244
245    /// Increment an integer counter by `delta`.  Missing keys start at 0.
246    pub fn increment(&mut self, key: &str, delta: i64) {
247        let current = self.get_int(key).unwrap_or(0);
248        self.set(key, current + delta);
249    }
250
251    /// Decrement an integer counter by `delta`.
252    pub fn decrement(&mut self, key: &str, delta: i64) {
253        self.increment(key, -delta);
254    }
255
256    /// Toggle a boolean entry.  Missing keys become `true`.
257    pub fn toggle(&mut self, key: &str) {
258        let current = self.get_bool(key).unwrap_or(false);
259        self.set(key, !current);
260    }
261}
262
263// ── ParallelPolicy ────────────────────────────────────────────────────────────
264
265/// Controls how a [`BehaviorNode::Parallel`] node determines its own status.
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub enum ParallelPolicy {
268    /// Succeed when ALL children succeed; fail when ANY child fails.
269    RequireAll,
270    /// Succeed when ANY child succeeds; fail when ALL children fail.
271    RequireOne,
272    /// Succeed when a minimum count of children succeed.
273    RequireN(usize),
274}
275
276impl Default for ParallelPolicy {
277    fn default() -> Self { ParallelPolicy::RequireAll }
278}
279
280// ── DecoratorKind ─────────────────────────────────────────────────────────────
281
282/// The flavour of behavior a decorator applies to its single child.
283#[derive(Debug, Clone)]
284pub enum DecoratorKind {
285    /// Flip Success↔Failure; Running is unchanged.
286    Invert,
287    /// Always return Success regardless of child result.
288    AlwaysSucceed,
289    /// Always return Failure regardless of child result.
290    AlwaysFail,
291    /// Force the child to return Running until it succeeds.
292    UntilSuccess,
293    /// Force the child to return Running until it fails.
294    UntilFailure,
295    /// Repeat child `count` times; return its final status.
296    Repeat { count: u32 },
297    /// Repeat child forever (returns Running indefinitely unless interrupted).
298    RepeatForever,
299    /// Return Failure if child is still Running after `timeout_secs`.
300    Timeout { timeout_secs: f32 },
301    /// Rate-limit: only tick child if at least `cooldown_secs` have elapsed.
302    Cooldown { cooldown_secs: f32 },
303    /// Only tick if a blackboard boolean key is true.
304    BlackboardGuard { key: String, expected: bool },
305    /// Succeed immediately if blackboard key equals expected; else Failure.
306    BlackboardCheck { key: String, expected: BlackboardValue },
307}
308
309// ── BehaviorNode ──────────────────────────────────────────────────────────────
310
311/// The complete behavior tree node type.
312///
313/// Nodes are stored in a `Box<BehaviorNode>` tree; the root is owned by a
314/// [`BehaviorTree`].  Leaf nodes contain a closure (`Box<dyn FnMut(…)>`) so
315/// they carry arbitrary game-logic without external look-up tables.
316pub enum BehaviorNode {
317    /// Run children in order; stop and return Failure on the first child that
318    /// fails.  Return Success only when all children succeed.
319    Sequence {
320        name:     String,
321        children: Vec<BehaviorNode>,
322        /// Index of the currently-active child (persists across ticks).
323        cursor:   usize,
324    },
325
326    /// Run children in order; stop and return Success on the first child that
327    /// succeeds.  Return Failure only when all children fail.
328    Selector {
329        name:     String,
330        children: Vec<BehaviorNode>,
331        cursor:   usize,
332    },
333
334    /// Tick all children every frame regardless of their individual statuses.
335    Parallel {
336        name:     String,
337        children: Vec<BehaviorNode>,
338        policy:   ParallelPolicy,
339    },
340
341    /// Wraps exactly one child and modifies how its status is interpreted.
342    Decorator {
343        name:  String,
344        kind:  DecoratorKind,
345        child: Box<BehaviorNode>,
346        /// Internal counter used by Repeat / Timeout / Cooldown decorators.
347        state: DecoratorState,
348    },
349
350    /// A leaf with user-supplied tick logic.
351    Leaf {
352        name:   String,
353        /// Called once when the node first becomes active (optional).
354        on_enter: Option<Box<dyn FnMut(&mut Blackboard)>>,
355        /// Main tick function. Return the node's status.
356        on_tick:  Box<dyn FnMut(&mut Blackboard, f32) -> NodeStatus>,
357        /// Called when the node exits (success, failure, or abort).
358        on_exit:  Option<Box<dyn FnMut(&mut Blackboard, NodeStatus)>>,
359        /// Whether on_enter has been called for the current activation.
360        entered:  bool,
361    },
362
363    /// A subtree stored by name in a [`SubtreeRegistry`].  Resolved at tick
364    /// time; if the name is unknown the node returns Failure.
365    SubtreeRef {
366        name: String,
367    },
368}
369
370impl std::fmt::Debug for BehaviorNode {
371    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
372        match self {
373            BehaviorNode::Sequence  { name, .. }     => write!(f, "Sequence({name})"),
374            BehaviorNode::Selector  { name, .. }     => write!(f, "Selector({name})"),
375            BehaviorNode::Parallel  { name, .. }     => write!(f, "Parallel({name})"),
376            BehaviorNode::Decorator { name, kind, .. }=> write!(f, "Decorator({name}, {kind:?})"),
377            BehaviorNode::Leaf      { name, .. }     => write!(f, "Leaf({name})"),
378            BehaviorNode::SubtreeRef{ name }         => write!(f, "SubtreeRef({name})"),
379        }
380    }
381}
382
383// ── DecoratorState ────────────────────────────────────────────────────────────
384
385/// Mutable state owned by a `Decorator` node.
386#[derive(Debug, Default, Clone)]
387pub struct DecoratorState {
388    /// Ticks/loops completed (used by Repeat).
389    pub repeat_count:   u32,
390    /// Time the current activation started (used by Timeout).
391    pub activated_at:   Option<Instant>,
392    /// Wall-clock time of last tick (used by Cooldown).
393    pub last_tick_time: Option<Instant>,
394    /// Simulation-time of last tick (used by Cooldown, in seconds).
395    pub last_tick_sim:  f64,
396}
397
398impl DecoratorState {
399    fn reset(&mut self) {
400        self.repeat_count   = 0;
401        self.activated_at   = None;
402        self.last_tick_time = None;
403        self.last_tick_sim  = 0.0;
404    }
405}
406
407// ── BehaviorNode tick ─────────────────────────────────────────────────────────
408
409impl BehaviorNode {
410    /// Recursively tick this node and return its status.
411    ///
412    /// `dt` is the elapsed time in seconds since the last tick.
413    /// `bb` is the shared blackboard for this tree.
414    /// `registry` is used to resolve SubtreeRef nodes.
415    pub fn tick(
416        &mut self,
417        dt:       f32,
418        bb:       &mut Blackboard,
419        registry: &SubtreeRegistry,
420    ) -> NodeStatus {
421        match self {
422            // ── Sequence ──────────────────────────────────────────────────────
423            BehaviorNode::Sequence { children, cursor, .. } => {
424                while *cursor < children.len() {
425                    let status = children[*cursor].tick(dt, bb, registry);
426                    match status {
427                        NodeStatus::Success => { *cursor += 1; }
428                        NodeStatus::Running => return NodeStatus::Running,
429                        NodeStatus::Failure => {
430                            *cursor = 0;
431                            return NodeStatus::Failure;
432                        }
433                    }
434                }
435                *cursor = 0;
436                NodeStatus::Success
437            }
438
439            // ── Selector ──────────────────────────────────────────────────────
440            BehaviorNode::Selector { children, cursor, .. } => {
441                while *cursor < children.len() {
442                    let status = children[*cursor].tick(dt, bb, registry);
443                    match status {
444                        NodeStatus::Failure => { *cursor += 1; }
445                        NodeStatus::Success => {
446                            *cursor = 0;
447                            return NodeStatus::Success;
448                        }
449                        NodeStatus::Running => return NodeStatus::Running,
450                    }
451                }
452                *cursor = 0;
453                NodeStatus::Failure
454            }
455
456            // ── Parallel ──────────────────────────────────────────────────────
457            BehaviorNode::Parallel { children, policy, .. } => {
458                let mut successes = 0usize;
459                let mut failures  = 0usize;
460                let total = children.len();
461
462                for child in children.iter_mut() {
463                    match child.tick(dt, bb, registry) {
464                        NodeStatus::Success => successes += 1,
465                        NodeStatus::Failure => failures  += 1,
466                        NodeStatus::Running => {}
467                    }
468                }
469
470                match *policy {
471                    ParallelPolicy::RequireAll => {
472                        if failures > 0        { NodeStatus::Failure }
473                        else if successes == total { NodeStatus::Success }
474                        else                    { NodeStatus::Running  }
475                    }
476                    ParallelPolicy::RequireOne => {
477                        if successes > 0         { NodeStatus::Success }
478                        else if failures == total { NodeStatus::Failure }
479                        else                     { NodeStatus::Running  }
480                    }
481                    ParallelPolicy::RequireN(n) => {
482                        if successes >= n           { NodeStatus::Success }
483                        else if total - failures < n { NodeStatus::Failure }
484                        else                         { NodeStatus::Running  }
485                    }
486                }
487            }
488
489            // ── Decorator ─────────────────────────────────────────────────────
490            BehaviorNode::Decorator { kind, child, state, .. } => {
491                tick_decorator(kind, child, state, dt, bb, registry)
492            }
493
494            // ── Leaf ──────────────────────────────────────────────────────────
495            BehaviorNode::Leaf { on_enter, on_tick, on_exit, entered, .. } => {
496                if !*entered {
497                    *entered = true;
498                    if let Some(enter_fn) = on_enter {
499                        enter_fn(bb);
500                    }
501                }
502                let status = on_tick(bb, dt);
503                if status.is_terminal() {
504                    *entered = false;
505                    if let Some(exit_fn) = on_exit {
506                        exit_fn(bb, status);
507                    }
508                }
509                status
510            }
511
512            // ── SubtreeRef ────────────────────────────────────────────────────
513            BehaviorNode::SubtreeRef { name } => {
514                // SubtreeRef is resolved by the caller (BehaviorTree::tick)
515                // into an owned copy; here we just return Failure as a
516                // safety fallback if somehow ticked directly.
517                log::warn!("SubtreeRef({name}) ticked directly — subtree not found");
518                NodeStatus::Failure
519            }
520        }
521    }
522
523    /// Reset the cursor / state of this node and all its children, so the
524    /// tree starts fresh on the next tick.
525    pub fn reset(&mut self) {
526        match self {
527            BehaviorNode::Sequence  { children, cursor, .. } => {
528                *cursor = 0;
529                for c in children.iter_mut() { c.reset(); }
530            }
531            BehaviorNode::Selector  { children, cursor, .. } => {
532                *cursor = 0;
533                for c in children.iter_mut() { c.reset(); }
534            }
535            BehaviorNode::Parallel  { children, .. } => {
536                for c in children.iter_mut() { c.reset(); }
537            }
538            BehaviorNode::Decorator { child, state, .. } => {
539                state.reset();
540                child.reset();
541            }
542            BehaviorNode::Leaf { entered, .. } => {
543                *entered = false;
544            }
545            BehaviorNode::SubtreeRef { .. } => {}
546        }
547    }
548
549    /// Return the name of this node.
550    pub fn name(&self) -> &str {
551        match self {
552            BehaviorNode::Sequence  { name, .. } => name,
553            BehaviorNode::Selector  { name, .. } => name,
554            BehaviorNode::Parallel  { name, .. } => name,
555            BehaviorNode::Decorator { name, .. } => name,
556            BehaviorNode::Leaf      { name, .. } => name,
557            BehaviorNode::SubtreeRef{ name }     => name,
558        }
559    }
560
561    /// Recursively collect the names of all nodes into `out`.
562    pub fn collect_names(&self, out: &mut Vec<String>) {
563        out.push(self.name().to_string());
564        match self {
565            BehaviorNode::Sequence  { children, .. }
566            | BehaviorNode::Selector{ children, .. }
567            | BehaviorNode::Parallel{ children, .. } => {
568                for c in children { c.collect_names(out); }
569            }
570            BehaviorNode::Decorator { child, .. } => child.collect_names(out),
571            _ => {}
572        }
573    }
574}
575
576// ── Decorator tick helper ─────────────────────────────────────────────────────
577
578fn tick_decorator(
579    kind:     &mut DecoratorKind,
580    child:    &mut BehaviorNode,
581    state:    &mut DecoratorState,
582    dt:       f32,
583    bb:       &mut Blackboard,
584    registry: &SubtreeRegistry,
585) -> NodeStatus {
586    match kind {
587        DecoratorKind::Invert => child.tick(dt, bb, registry).invert(),
588
589        DecoratorKind::AlwaysSucceed => {
590            child.tick(dt, bb, registry);
591            NodeStatus::Success
592        }
593
594        DecoratorKind::AlwaysFail => {
595            child.tick(dt, bb, registry);
596            NodeStatus::Failure
597        }
598
599        DecoratorKind::UntilSuccess => {
600            match child.tick(dt, bb, registry) {
601                NodeStatus::Success => NodeStatus::Success,
602                _ => NodeStatus::Running,
603            }
604        }
605
606        DecoratorKind::UntilFailure => {
607            match child.tick(dt, bb, registry) {
608                NodeStatus::Failure => NodeStatus::Failure,
609                _ => NodeStatus::Running,
610            }
611        }
612
613        DecoratorKind::Repeat { count } => {
614            let target = *count;
615            loop {
616                match child.tick(dt, bb, registry) {
617                    NodeStatus::Running => return NodeStatus::Running,
618                    NodeStatus::Failure => {
619                        state.repeat_count = 0;
620                        child.reset();
621                        return NodeStatus::Failure;
622                    }
623                    NodeStatus::Success => {
624                        state.repeat_count += 1;
625                        child.reset();
626                        if state.repeat_count >= target {
627                            state.repeat_count = 0;
628                            return NodeStatus::Success;
629                        }
630                    }
631                }
632            }
633        }
634
635        DecoratorKind::RepeatForever => {
636            match child.tick(dt, bb, registry) {
637                NodeStatus::Success | NodeStatus::Failure => {
638                    child.reset();
639                }
640                NodeStatus::Running => {}
641            }
642            NodeStatus::Running
643        }
644
645        DecoratorKind::Timeout { timeout_secs } => {
646            let limit = *timeout_secs;
647            let now = Instant::now();
648            let started = state.activated_at.get_or_insert(now);
649            if now.duration_since(*started) >= Duration::from_secs_f32(limit) {
650                state.activated_at = None;
651                child.reset();
652                return NodeStatus::Failure;
653            }
654            let status = child.tick(dt, bb, registry);
655            if status.is_terminal() {
656                state.activated_at = None;
657            }
658            status
659        }
660
661        DecoratorKind::Cooldown { cooldown_secs } => {
662            let cooldown = Duration::from_secs_f32(*cooldown_secs);
663            let now = Instant::now();
664            if let Some(last) = state.last_tick_time {
665                if now.duration_since(last) < cooldown {
666                    return NodeStatus::Failure;
667                }
668            }
669            state.last_tick_time = Some(now);
670            child.tick(dt, bb, registry)
671        }
672
673        DecoratorKind::BlackboardGuard { key, expected } => {
674            let key_clone = key.clone();
675            let exp = *expected;
676            let ok = bb.get_bool(&key_clone).unwrap_or(false) == exp;
677            if ok { child.tick(dt, bb, registry) } else { NodeStatus::Failure }
678        }
679
680        DecoratorKind::BlackboardCheck { key, expected } => {
681            let key_clone = key.clone();
682            match bb.get(&key_clone) {
683                Some(v) if v == expected => NodeStatus::Success,
684                _ => NodeStatus::Failure,
685            }
686        }
687    }
688}
689
690// ── SubtreeRegistry ───────────────────────────────────────────────────────────
691
692/// A named registry that maps string keys to reusable subtree factories.
693///
694/// Subtrees are stored as closures that produce a fresh `BehaviorNode` on
695/// demand; this avoids shared mutable state while still allowing reuse.
696pub struct SubtreeRegistry {
697    builders: HashMap<String, Box<dyn Fn() -> BehaviorNode>>,
698}
699
700impl Default for SubtreeRegistry {
701    fn default() -> Self { Self { builders: HashMap::new() } }
702}
703
704impl SubtreeRegistry {
705    pub fn new() -> Self { Self::default() }
706
707    /// Register a subtree factory under `name`.
708    pub fn register<F>(&mut self, name: &str, factory: F)
709    where
710        F: Fn() -> BehaviorNode + 'static,
711    {
712        self.builders.insert(name.to_string(), Box::new(factory));
713    }
714
715    /// Instantiate the subtree named `name`, or `None` if not registered.
716    pub fn instantiate(&self, name: &str) -> Option<BehaviorNode> {
717        self.builders.get(name).map(|f| f())
718    }
719
720    pub fn contains(&self, name: &str) -> bool {
721        self.builders.contains_key(name)
722    }
723
724    pub fn names(&self) -> impl Iterator<Item = &str> {
725        self.builders.keys().map(|s| s.as_str())
726    }
727}
728
729impl std::fmt::Debug for SubtreeRegistry {
730    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
731        let names: Vec<&str> = self.names().collect();
732        f.debug_struct("SubtreeRegistry").field("subtrees", &names).finish()
733    }
734}
735
736// ── BehaviorTree ──────────────────────────────────────────────────────────────
737
738/// The top-level behavior tree that owns a root node, a blackboard, and an
739/// optional subtree registry.
740#[derive(Debug)]
741pub struct BehaviorTree {
742    /// Human-readable label for debugging.
743    pub name:     String,
744    root:         BehaviorNode,
745    pub bb:       Blackboard,
746    pub registry: SubtreeRegistry,
747    /// Status returned by the last `tick()` call.
748    pub last_status: NodeStatus,
749    /// Total accumulated simulation time in seconds.
750    pub sim_time: f64,
751    /// Whether the tree is currently active.
752    pub active:   bool,
753}
754
755impl BehaviorTree {
756    pub fn new(name: &str, root: BehaviorNode) -> Self {
757        Self {
758            name:        name.to_string(),
759            root,
760            bb:          Blackboard::new(),
761            registry:    SubtreeRegistry::new(),
762            last_status: NodeStatus::Running,
763            sim_time:    0.0,
764            active:      true,
765        }
766    }
767
768    /// Tick the entire tree by `dt` seconds.  Advances the blackboard clock
769    /// and resolves any `SubtreeRef` nodes via the registry.
770    pub fn tick(&mut self, dt: f32) -> NodeStatus {
771        if !self.active {
772            return self.last_status;
773        }
774        self.sim_time += dt as f64;
775        self.bb.tick(dt as f64);
776        let status = tick_with_registry(&mut self.root, dt, &mut self.bb, &self.registry);
777        self.last_status = status;
778        status
779    }
780
781    /// Reset the whole tree so it starts fresh next tick.
782    pub fn reset(&mut self) {
783        self.root.reset();
784        self.last_status = NodeStatus::Running;
785    }
786
787    /// Pause / resume ticking.
788    pub fn set_active(&mut self, active: bool) { self.active = active; }
789
790    /// Access the blackboard immutably.
791    pub fn blackboard(&self) -> &Blackboard { &self.bb }
792
793    /// Access the blackboard mutably (useful for injecting sensor data).
794    pub fn blackboard_mut(&mut self) -> &mut Blackboard { &mut self.bb }
795
796    /// Access the subtree registry mutably (register subtrees after build).
797    pub fn registry_mut(&mut self) -> &mut SubtreeRegistry { &mut self.registry }
798
799    /// Collect all node names in the tree (depth-first).
800    pub fn node_names(&self) -> Vec<String> {
801        let mut names = Vec::new();
802        self.root.collect_names(&mut names);
803        names
804    }
805}
806
807/// Tick a node, expanding SubtreeRef nodes via the registry.
808fn tick_with_registry(
809    node:     &mut BehaviorNode,
810    dt:       f32,
811    bb:       &mut Blackboard,
812    registry: &SubtreeRegistry,
813) -> NodeStatus {
814    // SubtreeRef is special: we need to instantiate a temporary subtree.
815    // Since we cannot replace the node in place without unsafe, we detect
816    // SubtreeRef here and drive it separately.
817    if let BehaviorNode::SubtreeRef { name } = node {
818        match registry.instantiate(name) {
819            Some(mut subtree) => subtree.tick(dt, bb, registry),
820            None => {
821                log::warn!("SubtreeRef: unknown subtree '{name}'");
822                NodeStatus::Failure
823            }
824        }
825    } else {
826        node.tick(dt, bb, registry)
827    }
828}
829
830// ── TreeBuilder ───────────────────────────────────────────────────────────────
831
832/// Fluent builder for assembling [`BehaviorNode`] trees.
833///
834/// # Example
835/// ```ignore
836/// let root = TreeBuilder::sequence("root")
837///     .leaf("idle", |_bb, _dt| NodeStatus::Success)
838///     .selector("patrol_or_attack")
839///         .leaf("patrol", patrol_fn)
840///         .leaf("attack", attack_fn)
841///     .end()
842///     .build();
843/// ```
844pub struct TreeBuilder {
845    /// Stack of in-progress composite nodes (name, kind, children).
846    stack: Vec<BuildFrame>,
847    /// The final completed node, set when the outermost frame is popped.
848    result: Option<BehaviorNode>,
849}
850
851struct BuildFrame {
852    kind:     FrameKind,
853    name:     String,
854    children: Vec<BehaviorNode>,
855    /// For Decorator frames, the decorator kind.
856    dec_kind: Option<DecoratorKind>,
857    /// For Parallel frames, the policy.
858    par_policy: Option<ParallelPolicy>,
859}
860
861#[derive(Debug, Clone, Copy, PartialEq, Eq)]
862enum FrameKind { Sequence, Selector, Parallel, Decorator }
863
864impl TreeBuilder {
865    fn new_frame(kind: FrameKind, name: &str) -> BuildFrame {
866        BuildFrame { kind, name: name.to_string(), children: Vec::new(),
867                     dec_kind: None, par_policy: None }
868    }
869
870    /// Begin a new top-level Sequence.
871    pub fn sequence(name: &str) -> Self {
872        let mut b = TreeBuilder { stack: Vec::new(), result: None };
873        b.stack.push(Self::new_frame(FrameKind::Sequence, name));
874        b
875    }
876
877    /// Begin a new top-level Selector.
878    pub fn selector(name: &str) -> Self {
879        let mut b = TreeBuilder { stack: Vec::new(), result: None };
880        b.stack.push(Self::new_frame(FrameKind::Selector, name));
881        b
882    }
883
884    /// Begin a new top-level Parallel.
885    pub fn parallel(name: &str, policy: ParallelPolicy) -> Self {
886        let mut b = TreeBuilder { stack: Vec::new(), result: None };
887        let mut frame = Self::new_frame(FrameKind::Parallel, name);
888        frame.par_policy = Some(policy);
889        b.stack.push(frame);
890        b
891    }
892
893    /// Push a Sequence child onto the current composite.
894    pub fn sequence_child(mut self, name: &str) -> Self {
895        self.stack.push(Self::new_frame(FrameKind::Sequence, name));
896        self
897    }
898
899    /// Push a Selector child onto the current composite.
900    pub fn selector_child(mut self, name: &str) -> Self {
901        self.stack.push(Self::new_frame(FrameKind::Selector, name));
902        self
903    }
904
905    /// Push a Parallel child onto the current composite.
906    pub fn parallel_child(mut self, name: &str, policy: ParallelPolicy) -> Self {
907        let mut frame = Self::new_frame(FrameKind::Parallel, name);
908        frame.par_policy = Some(policy);
909        self.stack.push(frame);
910        self
911    }
912
913    /// Push a Decorator child onto the current composite.
914    pub fn decorator(mut self, name: &str, kind: DecoratorKind) -> Self {
915        let mut frame = Self::new_frame(FrameKind::Decorator, name);
916        frame.dec_kind = Some(kind);
917        self.stack.push(frame);
918        self
919    }
920
921    /// Add a leaf node to the current composite or decorator.
922    pub fn leaf<F>(mut self, name: &str, tick_fn: F) -> Self
923    where
924        F: FnMut(&mut Blackboard, f32) -> NodeStatus + 'static,
925    {
926        let node = BehaviorNode::Leaf {
927            name:     name.to_string(),
928            on_enter: None,
929            on_tick:  Box::new(tick_fn),
930            on_exit:  None,
931            entered:  false,
932        };
933        self.push_child(node);
934        self
935    }
936
937    /// Add a full leaf with enter/exit callbacks.
938    pub fn leaf_full<Enter, Tick, Exit>(
939        mut self,
940        name:     &str,
941        enter_fn: Enter,
942        tick_fn:  Tick,
943        exit_fn:  Exit,
944    ) -> Self
945    where
946        Enter: FnMut(&mut Blackboard) + 'static,
947        Tick:  FnMut(&mut Blackboard, f32) -> NodeStatus + 'static,
948        Exit:  FnMut(&mut Blackboard, NodeStatus) + 'static,
949    {
950        let node = BehaviorNode::Leaf {
951            name:     name.to_string(),
952            on_enter: Some(Box::new(enter_fn)),
953            on_tick:  Box::new(tick_fn),
954            on_exit:  Some(Box::new(exit_fn)),
955            entered:  false,
956        };
957        self.push_child(node);
958        self
959    }
960
961    /// Add a subtree reference node.
962    pub fn subtree_ref(mut self, name: &str) -> Self {
963        self.push_child(BehaviorNode::SubtreeRef { name: name.to_string() });
964        self
965    }
966
967    /// Add an already-built node as a child.
968    pub fn node(mut self, node: BehaviorNode) -> Self {
969        self.push_child(node);
970        self
971    }
972
973    /// Close the current composite/decorator and return to the parent.
974    pub fn end(mut self) -> Self {
975        let completed = self.pop_frame();
976        if self.stack.is_empty() {
977            self.result = Some(completed);
978        } else {
979            self.push_child(completed);
980        }
981        self
982    }
983
984    /// Finish building and return the root `BehaviorNode`.
985    /// Panics if not at the outermost frame.
986    pub fn build(mut self) -> BehaviorNode {
987        // Implicitly close any remaining open frames.
988        while !self.stack.is_empty() {
989            let completed = self.pop_frame();
990            if self.stack.is_empty() {
991                self.result = Some(completed);
992            } else {
993                self.push_child(completed);
994            }
995        }
996        self.result.expect("TreeBuilder::build called with no nodes")
997    }
998
999    /// Convenience: build and wrap in a [`BehaviorTree`].
1000    pub fn into_tree(self, tree_name: &str) -> BehaviorTree {
1001        let root = self.build();
1002        BehaviorTree::new(tree_name, root)
1003    }
1004
1005    // ── internal helpers ──────────────────────────────────────────────────────
1006
1007    fn push_child(&mut self, node: BehaviorNode) {
1008        if let Some(frame) = self.stack.last_mut() {
1009            frame.children.push(node);
1010        } else {
1011            // No open frame; this becomes the sole result.
1012            self.result = Some(node);
1013        }
1014    }
1015
1016    fn pop_frame(&mut self) -> BehaviorNode {
1017        let frame = self.stack.pop().expect("TreeBuilder: pop_frame on empty stack");
1018        match frame.kind {
1019            FrameKind::Sequence => BehaviorNode::Sequence {
1020                name:     frame.name,
1021                children: frame.children,
1022                cursor:   0,
1023            },
1024            FrameKind::Selector => BehaviorNode::Selector {
1025                name:     frame.name,
1026                children: frame.children,
1027                cursor:   0,
1028            },
1029            FrameKind::Parallel => BehaviorNode::Parallel {
1030                name:     frame.name,
1031                children: frame.children,
1032                policy:   frame.par_policy.unwrap_or_default(),
1033            },
1034            FrameKind::Decorator => {
1035                let kind  = frame.dec_kind.expect("Decorator frame missing kind");
1036                let child = frame.children.into_iter().next()
1037                    .expect("Decorator frame must have exactly one child");
1038                BehaviorNode::Decorator {
1039                    name:  frame.name,
1040                    kind,
1041                    child: Box::new(child),
1042                    state: DecoratorState::default(),
1043                }
1044            }
1045        }
1046    }
1047}
1048
1049// ── Convenience constructors ──────────────────────────────────────────────────
1050
1051/// Create a simple leaf node from a closure.
1052pub fn leaf<F>(name: &str, tick_fn: F) -> BehaviorNode
1053where
1054    F: FnMut(&mut Blackboard, f32) -> NodeStatus + 'static,
1055{
1056    BehaviorNode::Leaf {
1057        name:     name.to_string(),
1058        on_enter: None,
1059        on_tick:  Box::new(tick_fn),
1060        on_exit:  None,
1061        entered:  false,
1062    }
1063}
1064
1065/// Create a Sequence node from a list of children.
1066pub fn sequence(name: &str, children: Vec<BehaviorNode>) -> BehaviorNode {
1067    BehaviorNode::Sequence { name: name.to_string(), children, cursor: 0 }
1068}
1069
1070/// Create a Selector node from a list of children.
1071pub fn selector(name: &str, children: Vec<BehaviorNode>) -> BehaviorNode {
1072    BehaviorNode::Selector { name: name.to_string(), children, cursor: 0 }
1073}
1074
1075/// Create a Parallel node from a list of children.
1076pub fn parallel(name: &str, children: Vec<BehaviorNode>, policy: ParallelPolicy) -> BehaviorNode {
1077    BehaviorNode::Parallel { name: name.to_string(), children, policy }
1078}
1079
1080/// Wrap a node in an Invert decorator.
1081pub fn invert(name: &str, child: BehaviorNode) -> BehaviorNode {
1082    BehaviorNode::Decorator {
1083        name:  name.to_string(),
1084        kind:  DecoratorKind::Invert,
1085        child: Box::new(child),
1086        state: DecoratorState::default(),
1087    }
1088}
1089
1090/// Wrap a node in a Repeat(n) decorator.
1091pub fn repeat(name: &str, count: u32, child: BehaviorNode) -> BehaviorNode {
1092    BehaviorNode::Decorator {
1093        name:  name.to_string(),
1094        kind:  DecoratorKind::Repeat { count },
1095        child: Box::new(child),
1096        state: DecoratorState::default(),
1097    }
1098}
1099
1100/// Wrap a node in a Timeout decorator.
1101pub fn timeout(name: &str, secs: f32, child: BehaviorNode) -> BehaviorNode {
1102    BehaviorNode::Decorator {
1103        name:  name.to_string(),
1104        kind:  DecoratorKind::Timeout { timeout_secs: secs },
1105        child: Box::new(child),
1106        state: DecoratorState::default(),
1107    }
1108}
1109
1110/// Wrap a node in a Cooldown decorator.
1111pub fn cooldown(name: &str, secs: f32, child: BehaviorNode) -> BehaviorNode {
1112    BehaviorNode::Decorator {
1113        name:  name.to_string(),
1114        kind:  DecoratorKind::Cooldown { cooldown_secs: secs },
1115        child: Box::new(child),
1116        state: DecoratorState::default(),
1117    }
1118}
1119
1120// ── Tests ─────────────────────────────────────────────────────────────────────
1121
1122#[cfg(test)]
1123mod tests {
1124    use super::*;
1125
1126    fn succeed() -> BehaviorNode {
1127        leaf("succeed", |_, _| NodeStatus::Success)
1128    }
1129    fn fail() -> BehaviorNode {
1130        leaf("fail", |_, _| NodeStatus::Failure)
1131    }
1132
1133    #[test]
1134    fn sequence_all_succeed() {
1135        let mut bb  = Blackboard::new();
1136        let reg = SubtreeRegistry::new();
1137        let mut node = sequence("s", vec![succeed(), succeed(), succeed()]);
1138        assert_eq!(node.tick(0.016, &mut bb, &reg), NodeStatus::Success);
1139    }
1140
1141    #[test]
1142    fn sequence_first_fails() {
1143        let mut bb  = Blackboard::new();
1144        let reg = SubtreeRegistry::new();
1145        let mut node = sequence("s", vec![fail(), succeed()]);
1146        assert_eq!(node.tick(0.016, &mut bb, &reg), NodeStatus::Failure);
1147    }
1148
1149    #[test]
1150    fn selector_first_succeeds() {
1151        let mut bb  = Blackboard::new();
1152        let reg = SubtreeRegistry::new();
1153        let mut node = selector("sel", vec![succeed(), fail()]);
1154        assert_eq!(node.tick(0.016, &mut bb, &reg), NodeStatus::Success);
1155    }
1156
1157    #[test]
1158    fn selector_all_fail() {
1159        let mut bb  = Blackboard::new();
1160        let reg = SubtreeRegistry::new();
1161        let mut node = selector("sel", vec![fail(), fail()]);
1162        assert_eq!(node.tick(0.016, &mut bb, &reg), NodeStatus::Failure);
1163    }
1164
1165    #[test]
1166    fn invert_decorator() {
1167        let mut bb  = Blackboard::new();
1168        let reg = SubtreeRegistry::new();
1169        let mut node = invert("inv", succeed());
1170        assert_eq!(node.tick(0.016, &mut bb, &reg), NodeStatus::Failure);
1171    }
1172
1173    #[test]
1174    fn blackboard_set_get() {
1175        let mut bb = Blackboard::new();
1176        bb.set("health", 80.0f64);
1177        assert!(bb.float_gt("health", 50.0));
1178        assert!(!bb.float_lt("health", 50.0));
1179    }
1180
1181    #[test]
1182    fn blackboard_ttl_expiry() {
1183        let mut bb = Blackboard::new();
1184        bb.set_with_ttl("temp", true, 1.0);
1185        assert!(bb.contains("temp"));
1186        bb.tick(2.0);
1187        assert!(!bb.contains("temp"));
1188    }
1189
1190    #[test]
1191    fn repeat_decorator() {
1192        let mut bb  = Blackboard::new();
1193        let reg = SubtreeRegistry::new();
1194        let mut node = repeat("rep", 3, succeed());
1195        assert_eq!(node.tick(0.016, &mut bb, &reg), NodeStatus::Success);
1196    }
1197
1198    #[test]
1199    fn parallel_require_all() {
1200        let mut bb  = Blackboard::new();
1201        let reg = SubtreeRegistry::new();
1202        let mut node = parallel("par", vec![succeed(), succeed()], ParallelPolicy::RequireAll);
1203        assert_eq!(node.tick(0.016, &mut bb, &reg), NodeStatus::Success);
1204    }
1205
1206    #[test]
1207    fn parallel_require_one_first_succeeds() {
1208        let mut bb  = Blackboard::new();
1209        let reg = SubtreeRegistry::new();
1210        let mut node = parallel("par", vec![succeed(), fail()], ParallelPolicy::RequireOne);
1211        assert_eq!(node.tick(0.016, &mut bb, &reg), NodeStatus::Success);
1212    }
1213
1214    #[test]
1215    fn tree_builder_round_trip() {
1216        let root = TreeBuilder::sequence("root")
1217            .leaf("a", |_, _| NodeStatus::Success)
1218            .leaf("b", |_, _| NodeStatus::Success)
1219            .build();
1220        let mut tree = BehaviorTree::new("test", root);
1221        assert_eq!(tree.tick(0.016), NodeStatus::Success);
1222    }
1223}