use std::cell::Cell;
use std::fmt;
use crate::{AdaptiveConfig, ExplorationConfig};
#[derive(Debug)]
pub enum AdaptiveTestError {
Init(std::io::Error),
StatsUnavailable,
NoForks {
total: u64,
},
NoForkPoints {
points: u64,
},
EnergyExceeded {
total: u64,
limit: u64,
},
EnergyNegative {
energy: i64,
},
PoolNegative {
pool: i64,
},
}
impl fmt::Display for AdaptiveTestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Init(e) => write!(f, "init failed: {e}"),
Self::StatsUnavailable => write!(f, "stats unavailable"),
Self::NoForks { total } => {
write!(f, "expected forked children, got total_timelines={total}")
}
Self::NoForkPoints { points } => {
write!(f, "expected fork points, got fork_points={points}")
}
Self::EnergyExceeded { total, limit } => {
write!(
f,
"energy limit exceeded: total_timelines={total} (expected <= {limit})"
)
}
Self::EnergyNegative { energy } => {
write!(f, "energy went negative: global_energy={energy}")
}
Self::PoolNegative { pool } => {
write!(f, "realloc pool went negative: realloc_pool={pool}")
}
}
}
}
impl std::error::Error for AdaptiveTestError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Init(e) => Some(e),
_ => None,
}
}
}
impl From<std::io::Error> for AdaptiveTestError {
fn from(e: std::io::Error) -> Self {
Self::Init(e)
}
}
thread_local! {
pub static RNG_STATE: Cell<u64> = const { Cell::new(1) };
pub static CALL_COUNT: Cell<u64> = const { Cell::new(0) };
}
pub fn count() -> u64 {
CALL_COUNT.with(|c| c.get())
}
pub fn reseed(seed: u64) {
RNG_STATE.with(|c| c.set(if seed == 0 { 1 } else { seed }));
CALL_COUNT.with(|c| c.set(0));
}
pub fn next_random() -> u64 {
CALL_COUNT.with(|c| c.set(c.get() + 1));
RNG_STATE.with(|c| {
let mut s = c.get();
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
c.set(s);
s
})
}
pub fn random_below(divisor: u32) -> u32 {
(next_random() % divisor as u64) as u32
}
pub fn run_adaptive_maze_cascade() -> Result<(), AdaptiveTestError> {
crate::set_rng_hooks(count, reseed);
reseed(42);
crate::init(ExplorationConfig {
max_depth: 8,
timelines_per_split: 4,
global_energy: 150,
adaptive: Some(AdaptiveConfig {
batch_size: 4,
min_timelines: 4,
max_timelines: 20,
per_mark_energy: 15,
warm_min_timelines: None,
}),
parallelism: None,
})?;
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "maze_entry");
let g0a = random_below(10) < 3;
if g0a {
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "maze_g0a");
let g0b = random_below(10) < 3;
if g0b {
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "maze_lock0");
let g1a = random_below(10) < 3;
if g1a {
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "maze_g1a");
let g1b = random_below(10) < 3;
if g1b {
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "maze_lock1");
let g2a = random_below(10) < 3;
if g2a {
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "maze_g2a");
let g2b = random_below(10) < 3;
if g2b {
crate::assertion_bool(
crate::AssertKind::Sometimes,
true,
true,
"maze_lock2",
);
if crate::explorer_is_child() {
crate::exit_child(42);
}
}
}
}
}
}
}
if crate::explorer_is_child() {
crate::exit_child(0);
}
let stats = crate::exploration_stats().ok_or(AdaptiveTestError::StatsUnavailable)?;
crate::cleanup();
if stats.total_timelines == 0 {
return Err(AdaptiveTestError::NoForks {
total: stats.total_timelines,
});
}
if stats.fork_points == 0 {
return Err(AdaptiveTestError::NoForkPoints {
points: stats.fork_points,
});
}
Ok(())
}
pub fn run_adaptive_dungeon_floors() -> Result<(), AdaptiveTestError> {
crate::set_rng_hooks(count, reseed);
reseed(7777);
crate::init(ExplorationConfig {
max_depth: 7,
timelines_per_split: 4,
global_energy: 200,
adaptive: Some(AdaptiveConfig {
batch_size: 4,
min_timelines: 4,
max_timelines: 25,
per_mark_energy: 20,
warm_min_timelines: None,
}),
parallelism: None,
})?;
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "dungeon_entry");
let mut floors_cleared = 0u32;
for floor in 0..5 {
let has_key = random_below(5) == 0;
if !has_key {
break;
}
let name = match floor {
0 => "dungeon_f0",
1 => "dungeon_f1",
2 => "dungeon_f2",
3 => "dungeon_f3",
4 => "dungeon_f4",
_ => break,
};
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, name);
floors_cleared = floor + 1;
}
if floors_cleared == 5 && crate::explorer_is_child() {
crate::exit_child(42);
}
if crate::explorer_is_child() {
crate::exit_child(0);
}
let stats = crate::exploration_stats().ok_or(AdaptiveTestError::StatsUnavailable)?;
crate::cleanup();
if stats.total_timelines == 0 {
return Err(AdaptiveTestError::NoForks {
total: stats.total_timelines,
});
}
if stats.fork_points == 0 {
return Err(AdaptiveTestError::NoForkPoints {
points: stats.fork_points,
});
}
Ok(())
}
pub fn run_adaptive_energy_budget() -> Result<(), AdaptiveTestError> {
crate::set_rng_hooks(count, reseed);
reseed(99);
crate::init(ExplorationConfig {
max_depth: 3,
timelines_per_split: 4,
global_energy: 8,
adaptive: Some(AdaptiveConfig {
batch_size: 2,
min_timelines: 2,
max_timelines: 6,
per_mark_energy: 3,
warm_min_timelines: None,
}),
parallelism: None,
})?;
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "energy_a");
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "energy_b");
crate::assertion_bool(crate::AssertKind::Sometimes, true, true, "energy_c");
if crate::explorer_is_child() {
crate::exit_child(0);
}
let stats = crate::exploration_stats().ok_or(AdaptiveTestError::StatsUnavailable)?;
crate::cleanup();
if stats.total_timelines > 8 {
return Err(AdaptiveTestError::EnergyExceeded {
total: stats.total_timelines,
limit: 8,
});
}
if stats.global_energy < 0 {
return Err(AdaptiveTestError::EnergyNegative {
energy: stats.global_energy,
});
}
if stats.realloc_pool_remaining < 0 {
return Err(AdaptiveTestError::PoolNegative {
pool: stats.realloc_pool_remaining,
});
}
Ok(())
}