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 if self.timer > 0.0 {
404 self.timer = (self.timer - dt).max(0.0);
405 return BtStatus::Failure;
406 }
407 let status = self.child.tick(bb, dt);
408 if status == BtStatus::Success {
409 self.timer = self.cooldown;
410 }
411 status
412 }
413 fn reset(&mut self) { self.timer = 0.0; self.child.reset(); }
414}
415
416pub struct AlwaysSuccess;
420impl BtNode for AlwaysSuccess {
421 fn name(&self) -> &str { "AlwaysSuccess" }
422 fn tick(&mut self, _: &mut Blackboard, _: f32) -> BtStatus { BtStatus::Success }
423}
424
425pub struct AlwaysFailure;
427impl BtNode for AlwaysFailure {
428 fn name(&self) -> &str { "AlwaysFailure" }
429 fn tick(&mut self, _: &mut Blackboard, _: f32) -> BtStatus { BtStatus::Failure }
430}
431
432pub struct CheckFlag { pub key: String }
434impl BtNode for CheckFlag {
435 fn name(&self) -> &str { "CheckFlag" }
436 fn tick(&mut self, bb: &mut Blackboard, _: f32) -> BtStatus {
437 if bb.get_bool(&self.key) { BtStatus::Success } else { BtStatus::Failure }
438 }
439}
440
441pub struct CheckFloat { pub key: String, pub threshold: f32, pub above: bool }
443impl BtNode for CheckFloat {
444 fn name(&self) -> &str { "CheckFloat" }
445 fn tick(&mut self, bb: &mut Blackboard, _: f32) -> BtStatus {
446 let v = bb.get_float(&self.key);
447 let ok = if self.above { v >= self.threshold } else { v < self.threshold };
448 if ok { BtStatus::Success } else { BtStatus::Failure }
449 }
450}
451
452pub struct SetFlag { pub key: String, pub value: bool }
454impl BtNode for SetFlag {
455 fn name(&self) -> &str { "SetFlag" }
456 fn tick(&mut self, bb: &mut Blackboard, _: f32) -> BtStatus {
457 bb.set_bool(&self.key, self.value);
458 BtStatus::Success
459 }
460}
461
462pub struct Wait { pub duration: f32, elapsed: f32 }
464impl Wait {
465 pub fn new(duration: f32) -> Self { Self { duration, elapsed: 0.0 } }
466}
467impl BtNode for Wait {
468 fn name(&self) -> &str { "Wait" }
469 fn tick(&mut self, _: &mut Blackboard, dt: f32) -> BtStatus {
470 self.elapsed += dt;
471 if self.elapsed >= self.duration {
472 self.elapsed = 0.0;
473 BtStatus::Success
474 } else {
475 BtStatus::Running
476 }
477 }
478 fn reset(&mut self) { self.elapsed = 0.0; }
479}
480
481pub struct BehaviorTree {
485 root: Box<dyn BtNode>,
486 pub bb: Blackboard,
487 pub status: BtStatus,
488}
489
490impl BehaviorTree {
491 pub fn new(root: Box<dyn BtNode>) -> Self {
492 Self { root, bb: Blackboard::new(), status: BtStatus::Running }
493 }
494
495 pub fn tick(&mut self, dt: f32) -> BtStatus {
496 self.status = self.root.tick(&mut self.bb, dt);
497 self.status
498 }
499
500 pub fn reset(&mut self) {
501 self.root.reset();
502 self.status = BtStatus::Running;
503 }
504}
505
506pub trait UtilityAction: Send + Sync {
510 fn name(&self) -> &str;
511 fn score(&self, bb: &Blackboard) -> f32;
513 fn execute(&mut self, bb: &mut Blackboard, dt: f32) -> bool;
515 fn reset(&mut self) {}
517}
518
519#[derive(Clone, Copy, Debug)]
521pub enum UtilityCurve {
522 Linear { m: f32, b: f32 }, Quadratic { m: f32, k: f32, b: f32 }, Logistic { k: f32, x0: f32 }, Exponential { k: f32 }, Constant(f32),
527}
528
529impl UtilityCurve {
530 pub fn evaluate(&self, x: f32) -> f32 {
531 let y = match self {
532 UtilityCurve::Linear { m, b } => m * x + b,
533 UtilityCurve::Quadratic { m, k, b } => m * (x - k).powi(2) + b,
534 UtilityCurve::Logistic { k, x0 } => 1.0 / (1.0 + (-k * (x - x0)).exp()),
535 UtilityCurve::Exponential { k } => (k * x).exp().min(1.0),
536 UtilityCurve::Constant(c) => *c,
537 };
538 y.clamp(0.0, 1.0)
539 }
540}
541
542pub struct Consideration {
544 pub name: String,
545 pub key: String, pub min: f32,
547 pub max: f32,
548 pub curve: UtilityCurve,
549 pub weight: f32,
550}
551
552impl Consideration {
553 pub fn new(name: &str, key: &str, min: f32, max: f32, curve: UtilityCurve) -> Self {
554 Self { name: name.into(), key: key.into(), min, max, curve, weight: 1.0 }
555 }
556
557 pub fn with_weight(mut self, w: f32) -> Self { self.weight = w; self }
558
559 pub fn evaluate(&self, bb: &Blackboard) -> f32 {
560 let raw = bb.get_float(&self.key);
561 let t = ((raw - self.min) / (self.max - self.min).max(f32::EPSILON)).clamp(0.0, 1.0);
562 self.curve.evaluate(t) * self.weight
563 }
564}
565
566pub struct UtilityActionDef {
568 pub name: String,
569 pub considerations: Vec<Consideration>,
570 pub combine: ConsiderationCombine,
572 pub cooldown: f32,
574 cooldown_timer: f32,
575 execute_fn: Box<dyn Fn(&mut Blackboard, f32) -> bool + Send + Sync>,
577 elapsed: f32,
578 pub max_duration: f32,
579}
580
581#[derive(Clone, Copy, Debug)]
582pub enum ConsiderationCombine {
583 Multiply,
585 Average,
587 Min,
589 Max,
591}
592
593impl UtilityActionDef {
594 pub fn new(
595 name: &str,
596 considerations: Vec<Consideration>,
597 execute_fn: impl Fn(&mut Blackboard, f32) -> bool + Send + Sync + 'static,
598 ) -> Self {
599 Self {
600 name: name.into(),
601 considerations,
602 combine: ConsiderationCombine::Multiply,
603 cooldown: 0.0,
604 cooldown_timer: 0.0,
605 execute_fn: Box::new(execute_fn),
606 elapsed: 0.0,
607 max_duration: f32::MAX,
608 }
609 }
610
611 pub fn with_cooldown(mut self, c: f32) -> Self { self.cooldown = c; self }
612 pub fn with_max_duration(mut self, d: f32) -> Self { self.max_duration = d; self }
613 pub fn with_combine(mut self, c: ConsiderationCombine) -> Self { self.combine = c; self }
614}
615
616impl UtilityAction for UtilityActionDef {
617 fn name(&self) -> &str { &self.name }
618
619 fn score(&self, bb: &Blackboard) -> f32 {
620 if self.cooldown_timer > 0.0 { return 0.0; }
621 if self.considerations.is_empty() { return 0.5; }
622
623 let scores: Vec<f32> = self.considerations.iter().map(|c| c.evaluate(bb)).collect();
624 match self.combine {
625 ConsiderationCombine::Multiply => scores.iter().product(),
626 ConsiderationCombine::Average => scores.iter().sum::<f32>() / scores.len() as f32,
627 ConsiderationCombine::Min => scores.iter().cloned().fold(f32::MAX, f32::min),
628 ConsiderationCombine::Max => scores.iter().cloned().fold(0.0_f32, f32::max),
629 }
630 }
631
632 fn execute(&mut self, bb: &mut Blackboard, dt: f32) -> bool {
633 self.elapsed += dt;
634 let done = (self.execute_fn)(bb, dt) || self.elapsed >= self.max_duration;
635 if done {
636 self.cooldown_timer = self.cooldown;
637 self.elapsed = 0.0;
638 }
639 done
640 }
641
642 fn reset(&mut self) { self.elapsed = 0.0; }
643}
644
645pub struct UtilityAI {
647 actions: Vec<Box<dyn UtilityAction>>,
648 pub bb: Blackboard,
649 current_action: Option<usize>,
650 pub reeval_interval: f32,
652 reeval_timer: f32,
653 pub inertia: f32,
655}
656
657impl UtilityAI {
658 pub fn new() -> Self {
659 Self {
660 actions: Vec::new(),
661 bb: Blackboard::new(),
662 current_action: None,
663 reeval_interval: 0.1,
664 reeval_timer: 0.0,
665 inertia: 0.05,
666 }
667 }
668
669 pub fn add_action(&mut self, action: Box<dyn UtilityAction>) {
670 self.actions.push(action);
671 }
672
673 pub fn tick(&mut self, dt: f32) {
674 self.reeval_timer -= dt;
675
676 let should_reeval = self.reeval_timer <= 0.0;
681 if should_reeval { self.reeval_timer = self.reeval_interval; }
682
683 if should_reeval || self.current_action.is_none() {
684 let bb = &self.bb;
685 let inertia = self.inertia;
686 let current = self.current_action;
687 let mut best_score = -1.0_f32;
688 let mut best_idx = None;
689
690 for (i, action) in self.actions.iter().enumerate() {
691 let mut score = action.score(bb);
692 if Some(i) == current { score += inertia; }
694 if score > best_score {
695 best_score = score;
696 best_idx = Some(i);
697 }
698 }
699
700 if best_idx != self.current_action {
701 if let Some(old) = self.current_action {
702 self.actions[old].reset();
703 }
704 self.current_action = best_idx;
705 }
706 }
707
708 if let Some(idx) = self.current_action {
710 let bb = &mut self.bb;
711 self.actions[idx].execute(bb, dt);
712 }
713 }
714
715 pub fn current_action_name(&self) -> Option<&str> {
716 self.current_action.and_then(|i| self.actions.get(i)).map(|a| a.name())
717 }
718
719 pub fn scores(&self) -> Vec<(&str, f32)> {
720 self.actions.iter().map(|a| (a.name(), a.score(&self.bb))).collect()
721 }
722}
723
724impl Default for UtilityAI {
725 fn default() -> Self { Self::new() }
726}
727
728pub mod keys {
732 pub const HEALTH: &str = "health";
733 pub const MAX_HEALTH: &str = "max_health";
734 pub const DISTANCE_TO_PLAYER: &str = "dist_player";
735 pub const DISTANCE_TO_COVER: &str = "dist_cover";
736 pub const AMMO: &str = "ammo";
737 pub const IN_COVER: &str = "in_cover";
738 pub const PLAYER_VISIBLE: &str = "player_visible";
739 pub const TARGET_POS: &str = "target_pos";
740 pub const SELF_POS: &str = "self_pos";
741 pub const ALERT_LEVEL: &str = "alert_level";
742 pub const AGGRESSION: &str = "aggression";
743 pub const CAN_ATTACK: &str = "can_attack";
744 pub const IS_FLEEING: &str = "is_fleeing";
745 pub const TIME_IN_STATE: &str = "time_in_state";
746}
747
748#[cfg(test)]
751mod tests {
752 use super::*;
753
754 #[test]
757 fn blackboard_set_get() {
758 let mut bb = Blackboard::new();
759 bb.set_float("hp", 80.0);
760 bb.set_bool("alive", true);
761 bb.set_vec3("pos", Vec3::new(1.0, 2.0, 3.0));
762 assert!((bb.get_float("hp") - 80.0).abs() < 1e-5);
763 assert!(bb.get_bool("alive"));
764 assert_eq!(bb.get_vec3("pos"), Vec3::new(1.0, 2.0, 3.0));
765 }
766
767 #[test]
770 fn sequence_succeeds_when_all_succeed() {
771 let mut seq = Sequence::new(vec![
772 Box::new(AlwaysSuccess),
773 Box::new(AlwaysSuccess),
774 ]);
775 let mut bb = Blackboard::new();
776 assert_eq!(seq.tick(&mut bb, 0.016), BtStatus::Success);
777 }
778
779 #[test]
780 fn sequence_fails_on_child_failure() {
781 let mut seq = Sequence::new(vec![
782 Box::new(AlwaysSuccess),
783 Box::new(AlwaysFailure),
784 Box::new(AlwaysSuccess),
785 ]);
786 let mut bb = Blackboard::new();
787 assert_eq!(seq.tick(&mut bb, 0.016), BtStatus::Failure);
788 }
789
790 #[test]
791 fn selector_succeeds_on_first_success() {
792 let mut sel = Selector::new(vec![
793 Box::new(AlwaysFailure),
794 Box::new(AlwaysSuccess),
795 ]);
796 let mut bb = Blackboard::new();
797 assert_eq!(sel.tick(&mut bb, 0.016), BtStatus::Success);
798 }
799
800 #[test]
801 fn selector_fails_when_all_fail() {
802 let mut sel = Selector::new(vec![
803 Box::new(AlwaysFailure),
804 Box::new(AlwaysFailure),
805 ]);
806 let mut bb = Blackboard::new();
807 assert_eq!(sel.tick(&mut bb, 0.016), BtStatus::Failure);
808 }
809
810 #[test]
811 fn inverter_flips_success() {
812 let mut inv = Inverter { child: Box::new(AlwaysSuccess) };
813 let mut bb = Blackboard::new();
814 assert_eq!(inv.tick(&mut bb, 0.016), BtStatus::Failure);
815 }
816
817 #[test]
818 fn wait_runs_and_completes() {
819 let mut w = Wait::new(0.1);
820 let mut bb = Blackboard::new();
821 assert_eq!(w.tick(&mut bb, 0.05), BtStatus::Running);
822 assert_eq!(w.tick(&mut bb, 0.06), BtStatus::Success);
823 }
824
825 #[test]
826 fn check_flag_reads_blackboard() {
827 let mut bb = Blackboard::new();
828 bb.set_bool("alive", true);
829 let mut node = CheckFlag { key: "alive".into() };
830 assert_eq!(node.tick(&mut bb, 0.016), BtStatus::Success);
831 bb.set_bool("alive", false);
832 assert_eq!(node.tick(&mut bb, 0.016), BtStatus::Failure);
833 }
834
835 #[test]
836 fn check_float_threshold() {
837 let mut bb = Blackboard::new();
838 bb.set_float("hp", 20.0);
839 let mut node = CheckFloat { key: "hp".into(), threshold: 50.0, above: false };
840 assert_eq!(node.tick(&mut bb, 0.016), BtStatus::Success); }
842
843 #[test]
844 fn cooldown_blocks_repeat() {
845 let mut cd = Cooldown::new(Box::new(AlwaysSuccess), 1.0);
846 let mut bb = Blackboard::new();
847 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);
851 assert_eq!(cd.tick(&mut bb, 0.016), BtStatus::Success); }
853
854 #[test]
857 fn utility_curve_linear() {
858 let c = UtilityCurve::Linear { m: 1.0, b: 0.0 };
859 assert!((c.evaluate(0.5) - 0.5).abs() < 1e-5);
860 }
861
862 #[test]
863 fn utility_curve_clamps() {
864 let c = UtilityCurve::Linear { m: 2.0, b: 0.0 };
865 assert!((c.evaluate(1.0) - 1.0).abs() < 1e-5); assert!((c.evaluate(-1.0) - 0.0).abs() < 1e-5); }
868
869 #[test]
870 fn utility_ai_selects_best() {
871 let mut ai = UtilityAI::new();
872 ai.add_action(Box::new(UtilityActionDef::new(
874 "action_a",
875 vec![Consideration::new("pref_a", "prefer_a",
876 0.0, 1.0, UtilityCurve::Linear { m: 1.0, b: 0.0 })],
877 |_, _| true,
878 )));
879 ai.add_action(Box::new(UtilityActionDef::new(
881 "action_b",
882 vec![Consideration::new("const", "dummy",
883 0.0, 1.0, UtilityCurve::Constant(0.1))],
884 |_, _| true,
885 )));
886
887 ai.bb.set_float("prefer_a", 0.9);
888 ai.bb.set_float("dummy", 0.5);
889 ai.tick(0.016);
890
891 assert_eq!(ai.current_action_name(), Some("action_a"));
892 }
893}