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:
autocommit 2026-04-16 15:22:11 -07:00
parent 441e1252a2
commit 6be9f950c2

View 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());
}