1use std::collections::HashMap;
9use glam::Vec3;
10
11#[derive(Clone, Debug, Default)]
15pub struct Blackboard {
16 floats: HashMap<String, f32>,
17 bools: HashMap<String, bool>,
18 vec3s: HashMap<String, Vec3>,
19 strings: HashMap<String, String>,
20}
21
22impl Blackboard {
23 pub fn new() -> Self { Self::default() }
24
25 pub fn set_float(&mut self, k: &str, v: f32) { self.floats.insert(k.into(), v); }
26 pub fn get_float(&self, k: &str) -> f32 { self.floats.get(k).copied().unwrap_or(0.0) }
27 pub fn set_bool(&mut self, k: &str, v: bool) { self.bools.insert(k.into(), v); }
28 pub fn get_bool(&self, k: &str) -> bool { self.bools.get(k).copied().unwrap_or(false) }
29 pub fn set_vec3(&mut self, k: &str, v: Vec3) { self.vec3s.insert(k.into(), v); }
30 pub fn get_vec3(&self, k: &str) -> Vec3 { self.vec3s.get(k).copied().unwrap_or(Vec3::ZERO) }
31 pub fn set_str(&mut self, k: &str, v: &str) { self.strings.insert(k.into(), v.into()); }
32 pub fn get_str(&self, k: &str) -> &str { self.strings.get(k).map(|s| s.as_str()).unwrap_or("") }
33
34 pub fn has_float(&self, k: &str) -> bool { self.floats.contains_key(k) }
35 pub fn has_bool(&self, k: &str) -> bool { self.bools.contains_key(k) }
36 pub fn has_vec3(&self, k: &str) -> bool { self.vec3s.contains_key(k) }
37
38 pub fn clear(&mut self) {
39 self.floats.clear();
40 self.bools.clear();
41 self.vec3s.clear();
42 self.strings.clear();
43 }
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum StateResult {
51 Continue,
53 Transition(usize),
55 Pop,
57}
58
59pub trait FsmState<Context>: Send + Sync {
61 fn name(&self) -> &str;
62 fn on_enter(&mut self, _ctx: &mut Context, _bb: &mut Blackboard) {}
63 fn on_exit(&mut self, _ctx: &mut Context, _bb: &mut Blackboard) {}
64 fn tick(&mut self, ctx: &mut Context, bb: &mut Blackboard, dt: f32) -> StateResult;
65}
66
67pub struct StateMachine<Context> {
69 states: Vec<Box<dyn FsmState<Context>>>,
70 current: usize,
71 pub bb: Blackboard,
72 history: Vec<usize>,
73 pub active: bool,
74}
75
76impl<Context> StateMachine<Context> {
77 pub fn new() -> Self {
78 Self {
79 states: Vec::new(),
80 current: 0,
81 bb: Blackboard::new(),
82 history: Vec::new(),
83 active: false,
84 }
85 }
86
87 pub fn add_state(&mut self, state: Box<dyn FsmState<Context>>) -> usize {
89 let idx = self.states.len();
90 self.states.push(state);
91 idx
92 }
93
94 pub fn start(&mut self, ctx: &mut Context, idx: usize) {
96 self.current = idx;
97 self.active = true;
98 self.states[self.current].on_enter(ctx, &mut self.bb);
99 }
100
101 pub fn tick(&mut self, ctx: &mut Context, dt: f32) {
103 if !self.active || self.states.is_empty() { return; }
104 let result = self.states[self.current].tick(ctx, &mut self.bb, dt);
105 match result {
106 StateResult::Continue => {}
107 StateResult::Transition(next) if next < self.states.len() && next != self.current => {
108 self.states[self.current].on_exit(ctx, &mut self.bb);
109 self.history.push(self.current);
110 self.current = next;
111 self.states[self.current].on_enter(ctx, &mut self.bb);
112 }
113 StateResult::Transition(_) => {}
114 StateResult::Pop => {
115 if let Some(prev) = self.history.pop() {
116 self.states[self.current].on_exit(ctx, &mut self.bb);
117 self.current = prev;
118 self.states[self.current].on_enter(ctx, &mut self.bb);
119 } else {
120 self.active = false;
121 }
122 }
123 }
124 }
125
126 pub fn current_state_name(&self) -> &str {
127 self.states.get(self.current).map(|s| s.name()).unwrap_or("none")
128 }
129
130 pub fn current_index(&self) -> usize { self.current }
131}
132
133impl<C> Default for StateMachine<C> {
134 fn default() -> Self { Self::new() }
135}
136
137#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub enum BtStatus {
142 Success,
143 Failure,
144 Running,
145}
146
147pub trait BtNode: Send + Sync {
149 fn tick(&mut self, bb: &mut Blackboard, dt: f32) -> BtStatus;
150 fn reset(&mut self) {}
151 fn name(&self) -> &str { "BtNode" }
152}
153
154pub struct Sequence {
158 children: Vec<Box<dyn BtNode>>,
159 current: usize,
160}
161
162impl Sequence {
163 pub fn new(children: Vec<Box<dyn BtNode>>) -> Self {
164 Self { children, current: 0 }
165 }
166}
167
168impl BtNode for Sequence {
169 fn name(&self) -> &str { "Sequence" }
170
171 fn tick(&mut self, bb: &mut Blackboard, dt: f32) -> BtStatus {
172 while self.current < self.children.len() {
173 match self.children[self.current].tick(bb, dt) {
174 BtStatus::Success => self.current += 1,
175 BtStatus::Failure => { self.current = 0; return BtStatus::Failure; }
176 BtStatus::Running => return BtStatus::Running,
177 }
178 }
179 self.current = 0;
180 BtStatus::Success
181 }
182
183 fn reset(&mut self) {
184 self.current = 0;
185 for c in &mut self.children { c.reset(); }
186 }
187}
188
189pub struct Selector {
191 children: Vec<Box<dyn BtNode>>,
192 current: usize,
193}
194
195impl Selector {
196 pub fn new(children: Vec<Box<dyn BtNode>>) -> Self {
197 Self { children, current: 0 }
198 }
199}
200
201impl BtNode for Selector {
202 fn name(&self) -> &str { "Selector" }
203
204 fn tick(&mut self, bb: &mut Blackboard, dt: f32) -> BtStatus {
205 while self.current < self.children.len() {
206 match self.children[self.current].tick(bb, dt) {
207 BtStatus::Failure => self.current += 1,
208 BtStatus::Success => { self.current = 0; return BtStatus::Success; }
209 BtStatus::Running => return BtStatus::Running,
210 }
211 }
212 self.current = 0;
213 BtStatus::Failure
214 }
215
216 fn reset(&mut self) {
217 self.current = 0;
218 for c in &mut self.children { c.reset(); }
219 }
220}
221
222pub struct Parallel {
224 children: Vec<Box<dyn BtNode>>,
225 success_count: usize,
226}
227
228impl Parallel {
229 pub fn new(children: Vec<Box<dyn BtNode>>, success_count: usize) -> Self {
230 Self { children, success_count }
231 }
232
233 pub fn all(children: Vec<Box<dyn BtNode>>) -> Self {
234 let n = children.len();
235 Self::new(children, n)
236 }
237
238 pub fn any(children: Vec<Box<dyn BtNode>>) -> Self {
239 Self::new(children, 1)
240 }
241}
242
243impl BtNode for Parallel {
244 fn name(&self) -> &str { "Parallel" }
245
246 fn tick(&mut self, bb: &mut Blackboard, dt: f32) -> BtStatus {
247 let mut successes = 0;
248 let mut failures = 0;
249 for child in &mut self.children {
250 match child.tick(bb, dt) {
251 BtStatus::Success => successes += 1,
252 BtStatus::Failure => failures += 1,
253 BtStatus::Running => {}
254 }
255 }
256 let remaining = self.children.len() - failures;
257 if successes >= self.success_count { return BtStatus::Success; }
258 if remaining < self.success_count { return BtStatus::Failure; }
259 BtStatus::Running
260 }
261
262 fn reset(&mut self) {
263 for c in &mut self.children { c.reset(); }
264 }
265}
266
267pub struct RandomSelector {
269 children: Vec<Box<dyn BtNode>>,
270 order: Vec<usize>,
271 current: usize,
272 rng: u64,
273}
274
275impl RandomSelector {
276 pub fn new(children: Vec<Box<dyn BtNode>>) -> Self {
277 let n = children.len();
278 Self { children, order: (0..n).collect(), current: 0, rng: 9876543210 }
279 }
280
281 fn shuffle(&mut self) {
282 let n = self.order.len();
283 for i in (1..n).rev() {
284 self.rng ^= self.rng << 13;
285 self.rng ^= self.rng >> 7;
286 self.rng ^= self.rng << 17;
287 let j = (self.rng as usize) % (i + 1);
288 self.order.swap(i, j);
289 }
290 }
291}
292
293impl BtNode for RandomSelector {
294 fn name(&self) -> &str { "RandomSelector" }
295
296 fn tick(&mut self, bb: &mut Blackboard, dt: f32) -> BtStatus {
297 if self.current == 0 { self.shuffle(); }
298 while self.current < self.order.len() {
299 let idx = self.order[self.current];
300 match self.children[idx].tick(bb, dt) {
301 BtStatus::Failure => self.current += 1,
302 BtStatus::Success => { self.current = 0; return BtStatus::Success; }
303 BtStatus::Running => return BtStatus::Running,
304 }
305 }
306 self.current = 0;
307 BtStatus::Failure
308 }
309
310 fn reset(&mut self) {
311 self.current = 0;
312 for c in &mut self.children { c.reset(); }
313 }
314}
315
316pub struct Inverter { pub child: Box<dyn BtNode> }
320
321impl BtNode for Inverter {
322 fn name(&self) -> &str { "Inverter" }
323 fn tick(&mut self, bb: &mut Blackboard, dt: f32) -> BtStatus {
324 match self.child.tick(bb, dt) {
325 BtStatus::Success => BtStatus::Failure,
326 BtStatus::Failure => BtStatus::Success,
327 BtStatus::Running => BtStatus::Running,
328 }
329 }
330 fn reset(&mut self) { self.child.reset(); }
331}
332
333pub struct Succeeder { pub child: Box<dyn BtNode> }
335
336impl BtNode for Succeeder {
337 fn name(&self) -> &str { "Succeeder" }
338 fn tick(&mut self, bb: &mut Blackboard, dt: f32) -> BtStatus {
339 self.child.tick(bb, dt);
340 BtStatus::Success
341 }
342 fn reset(&mut self) { self.child.reset(); }
343}
344
345pub struct Repeater {
347 pub child: Box<dyn BtNode>,
348 pub max: u32,
349 count: u32,
350 until_fail: bool,
351}
352
353impl Repeater {
354 pub fn n_times(child: Box<dyn BtNode>, n: u32) -> Self {
355 Self { child, max: n, count: 0, until_fail: false }
356 }
357
358 pub fn until_failure(child: Box<dyn BtNode>) -> Self {
359 Self { child, max: 0, count: 0, until_fail: true }
360 }
361
362 pub fn forever(child: Box<dyn BtNode>) -> Self {
363 Self { child, max: 0, count: 0, until_fail: false }
364 }
365}
366
367impl BtNode for Repeater {
368 fn name(&self) -> &str { "Repeater" }
369 fn tick(&mut self, bb: &mut Blackboard, dt: f32) -> BtStatus {
370 loop {
371 let status = self.child.tick(bb, dt);
372 if status == BtStatus::Running { return BtStatus::Running; }
373 if self.until_fail && status == BtStatus::Failure { return BtStatus::Success; }
374 self.child.reset();
375 self.count += 1;
376 if self.max > 0 && self.count >= self.max {
377 self.count = 0;
378 return BtStatus::Success;
379 }
380 if self.max == 0 { return BtStatus::Running; }
382 }
383 }
384 fn reset(&mut self) { self.count = 0; self.child.reset(); }
385}
386
387pub struct Cooldown {
389 pub child: Box<dyn BtNode>,
390 pub cooldown: f32,
391 timer: f32,
392}
393
394impl Cooldown {
395 pub fn new(child: Box<dyn BtNode>, cooldown: f32) -> Self {
396 Self { child, cooldown, timer: 0.0 }
397 }
398}
399
400impl BtNode for Cooldown {
401 fn name(&self) -> &str { "Cooldown" }
402 fn tick(&mut self, bb: &mut Blackboard, dt: f32) -> BtStatus {
403 self.timer = (self.timer - dt).max(0.0);
404 if self.timer > 0.0 { return BtStatus::Failure; }
405 let status = self.child.tick(bb, dt);
406 if status == BtStatus::Success {
407 self.timer = self.cooldown;
408 }
409 status
410 }
411 fn reset(&mut self) { self.timer = 0.0; self.child.reset(); }
412}
413
414pub struct AlwaysSuccess;
418impl BtNode for AlwaysSuccess {
419 fn name(&self) -> &str { "AlwaysSuccess" }
420 fn tick(&mut self, _: &mut Blackboard, _: f32) -> BtStatus { BtStatus::Success }
421}
422
423pub struct AlwaysFailure;
425impl BtNode for AlwaysFailure {
426 fn name(&self) -> &str { "AlwaysFailure" }
427 fn tick(&mut self, _: &mut Blackboard, _: f32) -> BtStatus { BtStatus::Failure }
428}
429
430pub struct CheckFlag { pub key: String }
432impl BtNode for CheckFlag {
433 fn name(&self) -> &str { "CheckFlag" }
434 fn tick(&mut self, bb: &mut Blackboard, _: f32) -> BtStatus {
435 if bb.get_bool(&self.key) { BtStatus::Success } else { BtStatus::Failure }
436 }
437}
438
439pub struct CheckFloat { pub key: String, pub threshold: f32, pub above: bool }
441impl BtNode for CheckFloat {
442 fn name(&self) -> &str { "CheckFloat" }
443 fn tick(&mut self, bb: &mut Blackboard, _: f32) -> BtStatus {
444 let v = bb.get_float(&self.key);
445 let ok = if self.above { v >= self.threshold } else { v < self.threshold };
446 if ok { BtStatus::Success } else { BtStatus::Failure }
447 }
448}
449
450pub struct SetFlag { pub key: String, pub value: bool }
452impl BtNode for SetFlag {
453 fn name(&self) -> &str { "SetFlag" }
454 fn tick(&mut self, bb: &mut Blackboard, _: f32) -> BtStatus {
455 bb.set_bool(&self.key, self.value);
456 BtStatus::Success
457 }
458}
459
460pub struct Wait { pub duration: f32, elapsed: f32 }
462impl Wait {
463 pub fn new(duration: f32) -> Self { Self { duration, elapsed: 0.0 } }
464}
465impl BtNode for Wait {
466 fn name(&self) -> &str { "Wait" }
467 fn tick(&mut self, _: &mut Blackboard, dt: f32) -> BtStatus {
468 self.elapsed += dt;
469 if self.elapsed >= self.duration {
470 self.elapsed = 0.0;
471 BtStatus::Success
472 } else {
473 BtStatus::Running
474 }
475 }
476 fn reset(&mut self) { self.elapsed = 0.0; }
477}
478
479pub struct BehaviorTree {
483 root: Box<dyn BtNode>,
484 pub bb: Blackboard,
485 pub status: BtStatus,
486}
487
488impl BehaviorTree {
489 pub fn new(root: Box<dyn BtNode>) -> Self {
490 Self { root, bb: Blackboard::new(), status: BtStatus::Running }
491 }
492
493 pub fn tick(&mut self, dt: f32) -> BtStatus {
494 self.status = self.root.tick(&mut self.bb, dt);
495 self.status
496 }
497
498 pub fn reset(&mut self) {
499 self.root.reset();
500 self.status = BtStatus::Running;
501 }
502}
503
504pub trait UtilityAction: Send + Sync {
508 fn name(&self) -> &str;
509 fn score(&self, bb: &Blackboard) -> f32;
511 fn execute(&mut self, bb: &mut Blackboard, dt: f32) -> bool;
513 fn reset(&mut self) {}
515}
516
517#[derive(Clone, Copy, Debug)]
519pub enum UtilityCurve {
520 Linear { m: f32, b: f32 }, Quadratic { m: f32, k: f32, b: f32 }, Logistic { k: f32, x0: f32 }, Exponential { k: f32 }, Constant(f32),
525}
526
527impl UtilityCurve {
528 pub fn evaluate(&self, x: f32) -> f32 {
529 let y = match self {
530 UtilityCurve::Linear { m, b } => m * x + b,
531 UtilityCurve::Quadratic { m, k, b } => m * (x - k).powi(2) + b,
532 UtilityCurve::Logistic { k, x0 } => 1.0 / (1.0 + (-k * (x - x0)).exp()),
533 UtilityCurve::Exponential { k } => (k * x).exp().min(1.0),
534 UtilityCurve::Constant(c) => *c,
535 };
536 y.clamp(0.0, 1.0)
537 }
538}
539
540pub struct Consideration {
542 pub name: String,
543 pub key: String, pub min: f32,
545 pub max: f32,
546 pub curve: UtilityCurve,
547 pub weight: f32,
548}
549
550impl Consideration {
551 pub fn new(name: &str, key: &str, min: f32, max: f32, curve: UtilityCurve) -> Self {
552 Self { name: name.into(), key: key.into(), min, max, curve, weight: 1.0 }
553 }
554
555 pub fn with_weight(mut self, w: f32) -> Self { self.weight = w; self }
556
557 pub fn evaluate(&self, bb: &Blackboard) -> f32 {
558 let raw = bb.get_float(&self.key);
559 let t = ((raw - self.min) / (self.max - self.min).max(f32::EPSILON)).clamp(0.0, 1.0);
560 self.curve.evaluate(t) * self.weight
561 }
562}
563
564pub struct UtilityActionDef {
566 pub name: String,
567 pub considerations: Vec<Consideration>,
568 pub combine: ConsiderationCombine,
570 pub cooldown: f32,
572 cooldown_timer: f32,
573 execute_fn: Box<dyn Fn(&mut Blackboard, f32) -> bool + Send + Sync>,
575 elapsed: f32,
576 pub max_duration: f32,
577}
578
579#[derive(Clone, Copy, Debug)]
580pub enum ConsiderationCombine {
581 Multiply,
583 Average,
585 Min,
587 Max,
589}
590
591impl UtilityActionDef {
592 pub fn new(
593 name: &str,
594 considerations: Vec<Consideration>,
595 execute_fn: impl Fn(&mut Blackboard, f32) -> bool + Send + Sync + 'static,
596 ) -> Self {
597 Self {
598 name: name.into(),
599 considerations,
600 combine: ConsiderationCombine::Multiply,
601 cooldown: 0.0,
602 cooldown_timer: 0.0,
603 execute_fn: Box::new(execute_fn),
604 elapsed: 0.0,
605 max_duration: f32::MAX,
606 }
607 }
608
609 pub fn with_cooldown(mut self, c: f32) -> Self { self.cooldown = c; self }
610 pub fn with_max_duration(mut self, d: f32) -> Self { self.max_duration = d; self }
611 pub fn with_combine(mut self, c: ConsiderationCombine) -> Self { self.combine = c; self }
612}
613
614impl UtilityAction for UtilityActionDef {
615 fn name(&self) -> &str { &self.name }
616
617 fn score(&self, bb: &Blackboard) -> f32 {
618 if self.cooldown_timer > 0.0 { return 0.0; }
619 if self.considerations.is_empty() { return 0.5; }
620
621 let scores: Vec<f32> = self.considerations.iter().map(|c| c.evaluate(bb)).collect();
622 match self.combine {
623 ConsiderationCombine::Multiply => scores.iter().product(),
624 ConsiderationCombine::Average => scores.iter().sum::<f32>() / scores.len() as f32,
625 ConsiderationCombine::Min => scores.iter().cloned().fold(f32::MAX, f32::min),
626 ConsiderationCombine::Max => scores.iter().cloned().fold(0.0_f32, f32::max),
627 }
628 }
629
630 fn execute(&mut self, bb: &mut Blackboard, dt: f32) -> bool {
631 self.elapsed += dt;
632 let done = (self.execute_fn)(bb, dt) || self.elapsed >= self.max_duration;
633 if done {
634 self.cooldown_timer = self.cooldown;
635 self.elapsed = 0.0;
636 }
637 done
638 }
639
640 fn reset(&mut self) { self.elapsed = 0.0; }
641}
642
643pub struct UtilityAI {
645 actions: Vec<Box<dyn UtilityAction>>,
646 pub bb: Blackboard,
647 current_action: Option<usize>,
648 pub reeval_interval: f32,
650 reeval_timer: f32,
651 pub inertia: f32,
653}
654
655impl UtilityAI {
656 pub fn new() -> Self {
657 Self {
658 actions: Vec::new(),
659 bb: Blackboard::new(),
660 current_action: None,
661 reeval_interval: 0.1,
662 reeval_timer: 0.0,
663 inertia: 0.05,
664 }
665 }
666
667 pub fn add_action(&mut self, action: Box<dyn UtilityAction>) {
668 self.actions.push(action);
669 }
670
671 pub fn tick(&mut self, dt: f32) {
672 self.reeval_timer -= dt;
673
674 let should_reeval = self.reeval_timer <= 0.0;
679 if should_reeval { self.reeval_timer = self.reeval_interval; }
680
681 if should_reeval || self.current_action.is_none() {
682 let bb = &self.bb;
683 let inertia = self.inertia;
684 let current = self.current_action;
685 let mut best_score = -1.0_f32;
686 let mut best_idx = None;
687
688 for (i, action) in self.actions.iter().enumerate() {
689 let mut score = action.score(bb);
690 if Some(i) == current { score += inertia; }
692 if score > best_score {
693 best_score = score;
694 best_idx = Some(i);
695 }
696 }
697
698 if best_idx != self.current_action {
699 if let Some(old) = self.current_action {
700 self.actions[old].reset();
701 }
702 self.current_action = best_idx;
703 }
704 }
705
706 if let Some(idx) = self.current_action {
708 let bb = &mut self.bb;
709 let done = self.actions[idx].execute(bb, dt);
710 if done {
711 self.current_action = None;
712 }
713 }
714 }
715
716 pub fn current_action_name(&self) -> Option<&str> {
717 self.current_action.and_then(|i| self.actions.get(i)).map(|a| a.name())
718 }
719
720 pub fn scores(&self) -> Vec<(&str, f32)> {
721 self.actions.iter().map(|a| (a.name(), a.score(&self.bb))).collect()
722 }
723}
724
725impl Default for UtilityAI {
726 fn default() -> Self { Self::new() }
727}
728
729pub mod keys {
733 pub const HEALTH: &str = "health";
734 pub const MAX_HEALTH: &str = "max_health";
735 pub const DISTANCE_TO_PLAYER: &str = "dist_player";
736 pub const DISTANCE_TO_COVER: &str = "dist_cover";
737 pub const AMMO: &str = "ammo";
738 pub const IN_COVER: &str = "in_cover";
739 pub const PLAYER_VISIBLE: &str = "player_visible";
740 pub const TARGET_POS: &str = "target_pos";
741 pub const SELF_POS: &str = "self_pos";
742 pub const ALERT_LEVEL: &str = "alert_level";
743 pub const AGGRESSION: &str = "aggression";
744 pub const CAN_ATTACK: &str = "can_attack";
745 pub const IS_FLEEING: &str = "is_fleeing";
746 pub const TIME_IN_STATE: &str = "time_in_state";
747}
748
749#[cfg(test)]
752mod tests {
753 use super::*;
754
755 #[test]
758 fn blackboard_set_get() {
759 let mut bb = Blackboard::new();
760 bb.set_float("hp", 80.0);
761 bb.set_bool("alive", true);
762 bb.set_vec3("pos", Vec3::new(1.0, 2.0, 3.0));
763 assert!((bb.get_float("hp") - 80.0).abs() < 1e-5);
764 assert!(bb.get_bool("alive"));
765 assert_eq!(bb.get_vec3("pos"), Vec3::new(1.0, 2.0, 3.0));
766 }
767
768 #[test]
771 fn sequence_succeeds_when_all_succeed() {
772 let mut seq = Sequence::new(vec![
773 Box::new(AlwaysSuccess),
774 Box::new(AlwaysSuccess),
775 ]);
776 let mut bb = Blackboard::new();
777 assert_eq!(seq.tick(&mut bb, 0.016), BtStatus::Success);
778 }
779
780 #[test]
781 fn sequence_fails_on_child_failure() {
782 let mut seq = Sequence::new(vec![
783 Box::new(AlwaysSuccess),
784 Box::new(AlwaysFailure),
785 Box::new(AlwaysSuccess),
786 ]);
787 let mut bb = Blackboard::new();
788 assert_eq!(seq.tick(&mut bb, 0.016), BtStatus::Failure);
789 }
790
791 #[test]
792 fn selector_succeeds_on_first_success() {
793 let mut sel = Selector::new(vec![
794 Box::new(AlwaysFailure),
795 Box::new(AlwaysSuccess),
796 ]);
797 let mut bb = Blackboard::new();
798 assert_eq!(sel.tick(&mut bb, 0.016), BtStatus::Success);
799 }
800
801 #[test]
802 fn selector_fails_when_all_fail() {
803 let mut sel = Selector::new(vec![
804 Box::new(AlwaysFailure),
805 Box::new(AlwaysFailure),
806 ]);
807 let mut bb = Blackboard::new();
808 assert_eq!(sel.tick(&mut bb, 0.016), BtStatus::Failure);
809 }
810
811 #[test]
812 fn inverter_flips_success() {
813 let mut inv = Inverter { child: Box::new(AlwaysSuccess) };
814 let mut bb = Blackboard::new();
815 assert_eq!(inv.tick(&mut bb, 0.016), BtStatus::Failure);
816 }
817
818 #[test]
819 fn wait_runs_and_completes() {
820 let mut w = Wait::new(0.1);
821 let mut bb = Blackboard::new();
822 assert_eq!(w.tick(&mut bb, 0.05), BtStatus::Running);
823 assert_eq!(w.tick(&mut bb, 0.06), BtStatus::Success);
824 }
825
826 #[test]
827 fn check_flag_reads_blackboard() {
828 let mut bb = Blackboard::new();
829 bb.set_bool("alive", true);
830 let mut node = CheckFlag { key: "alive".into() };
831 assert_eq!(node.tick(&mut bb, 0.016), BtStatus::Success);
832 bb.set_bool("alive", false);
833 assert_eq!(node.tick(&mut bb, 0.016), BtStatus::Failure);
834 }
835
836 #[test]
837 fn check_float_threshold() {
838 let mut bb = Blackboard::new();
839 bb.set_float("hp", 20.0);
840 let mut node = CheckFloat { key: "hp".into(), threshold: 50.0, above: false };
841 assert_eq!(node.tick(&mut bb, 0.016), BtStatus::Success); }
843
844 #[test]
845 fn cooldown_blocks_repeat() {
846 let mut cd = Cooldown::new(Box::new(AlwaysSuccess), 1.0);
847 let mut bb = Blackboard::new();
848 assert_eq!(cd.tick(&mut bb, 0.016), BtStatus::Success); assert_eq!(cd.tick(&mut bb, 0.016), BtStatus::Failure); cd.tick(&mut bb, 1.0);
852 assert_eq!(cd.tick(&mut bb, 0.016), BtStatus::Success); }
854
855 #[test]
858 fn utility_curve_linear() {
859 let c = UtilityCurve::Linear { m: 1.0, b: 0.0 };
860 assert!((c.evaluate(0.5) - 0.5).abs() < 1e-5);
861 }
862
863 #[test]
864 fn utility_curve_clamps() {
865 let c = UtilityCurve::Linear { m: 2.0, b: 0.0 };
866 assert!((c.evaluate(1.0) - 1.0).abs() < 1e-5); assert!((c.evaluate(-1.0) - 0.0).abs() < 1e-5); }
869
870 #[test]
871 fn utility_ai_selects_best() {
872 let mut ai = UtilityAI::new();
873 ai.add_action(Box::new(UtilityActionDef::new(
875 "action_a",
876 vec![Consideration::new("pref_a", "prefer_a",
877 0.0, 1.0, UtilityCurve::Linear { m: 1.0, b: 0.0 })],
878 |_, _| true,
879 )));
880 ai.add_action(Box::new(UtilityActionDef::new(
882 "action_b",
883 vec![Consideration::new("const", "dummy",
884 0.0, 1.0, UtilityCurve::Constant(0.1))],
885 |_, _| true,
886 )));
887
888 ai.bb.set_float("prefer_a", 0.9);
889 ai.bb.set_float("dummy", 0.5);
890 ai.tick(0.016);
891
892 assert_eq!(ai.current_action_name(), Some("action_a"));
893 }
894}