test(simulator): ✅ Add comprehensive test cases for AI models and edge cases in the simulator
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
441e1252a2
commit
6be9f950c2
1 changed files with 78 additions and 0 deletions
78
src/simulator/crates/mc-ai/tests/mcts_basic.rs
Normal file
78
src/simulator/crates/mc-ai/tests/mcts_basic.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
//! Basic tree-growth tests for the MCTS scaffold.
|
||||
|
||||
use mc_ai::mcts::XorShift64;
|
||||
use mc_ai::mcts_tree::{Tree, TreeState};
|
||||
|
||||
/// Toy state: a non-negative counter with configurable branching and depth.
|
||||
/// Legal actions = integers `0..branching` while `depth > 0`. Each action decrements depth.
|
||||
#[derive(Clone, Debug)]
|
||||
struct ToyState {
|
||||
depth: u32,
|
||||
branching: u32,
|
||||
}
|
||||
|
||||
impl TreeState for ToyState {
|
||||
type Action = u32;
|
||||
|
||||
fn legal_actions(&self) -> Vec<u32> {
|
||||
if self.depth == 0 {
|
||||
Vec::new()
|
||||
} else {
|
||||
(0..self.branching).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply(&self, _action: &u32) -> Self {
|
||||
Self { depth: self.depth - 1, branching: self.branching }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_starts_with_all_actions_untried() {
|
||||
let tree = Tree::new(ToyState { depth: 2, branching: 3 });
|
||||
assert_eq!(tree.root().untried.len(), 3);
|
||||
assert_eq!(tree.root().children.len(), 0);
|
||||
assert_eq!(tree.root().visits, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_creates_child_and_shrinks_untried() {
|
||||
let mut tree = Tree::new(ToyState { depth: 2, branching: 3 });
|
||||
let child = tree.expand(0).expect("root has untried actions");
|
||||
assert_eq!(tree.root().untried.len(), 2);
|
||||
assert_eq!(tree.root().children, vec![child]);
|
||||
assert_eq!(tree.nodes[child].parent, Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backpropagate_updates_ancestors() {
|
||||
let mut tree = Tree::new(ToyState { depth: 2, branching: 2 });
|
||||
let c1 = tree.expand(0).unwrap();
|
||||
let c2 = tree.expand(c1).unwrap();
|
||||
tree.backpropagate(c2, 1.0);
|
||||
assert_eq!(tree.nodes[c2].visits, 1);
|
||||
assert_eq!(tree.nodes[c1].visits, 1);
|
||||
assert_eq!(tree.root().visits, 1);
|
||||
assert!((tree.root().wins - 1.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterate_grows_tree_and_accumulates_visits() {
|
||||
let mut tree = Tree::new(ToyState { depth: 3, branching: 2 });
|
||||
let mut rng = XorShift64::new(7);
|
||||
for _ in 0..32 {
|
||||
tree.iterate(&mut rng);
|
||||
}
|
||||
assert_eq!(tree.root().visits, 32);
|
||||
// Tree must have grown past the root.
|
||||
assert!(tree.nodes.len() > 1);
|
||||
// Stubbed simulate returns 0.5 → root total wins == 0.5 * visits.
|
||||
assert!((tree.root().wins - 0.5 * 32.0).abs() < 1e-4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_state_has_no_legal_actions() {
|
||||
let state = ToyState { depth: 0, branching: 4 };
|
||||
assert!(state.is_terminal());
|
||||
assert!(state.legal_actions().is_empty());
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue