use rand::SeedableRng;
use rand::{
RngExt,
distr::{Distribution, StandardUniform, uniform::SampleUniform},
};
use rand_chacha::ChaCha8Rng;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
thread_local! {
static SIM_RNG: RefCell<ChaCha8Rng> = RefCell::new(ChaCha8Rng::seed_from_u64(0));
static CURRENT_SEED: RefCell<u64> = const { RefCell::new(0) };
static RNG_CALL_COUNT: Cell<u64> = const { Cell::new(0) };
static RNG_BREAKPOINTS: RefCell<VecDeque<(u64, u64)>> = const { RefCell::new(VecDeque::new()) };
static CONFIG_RNG: RefCell<ChaCha8Rng> = RefCell::new(ChaCha8Rng::seed_from_u64(0));
static SWARM_OP_SEED: Cell<Option<u64>> = const { Cell::new(None) };
static SELECT_RNG: RefCell<ChaCha8Rng> = RefCell::new(ChaCha8Rng::seed_from_u64(0));
}
const CONFIG_RNG_SALT: u64 = 0x6D6F_6F6E_7377_726D;
const SWARM_OP_SALT: u64 = 0x6F70_6D61_736B_7372;
const SELECT_RNG_SALT: u64 = 0x7365_6C62_726E_6368;
fn pre_sample() {
RNG_CALL_COUNT.with(|c| c.set(c.get() + 1));
check_rng_breakpoint();
}
fn check_rng_breakpoint() {
RNG_BREAKPOINTS.with(|bp| {
let mut breakpoints = bp.borrow_mut();
while let Some(&(target_count, new_seed)) = breakpoints.front() {
let count = RNG_CALL_COUNT.with(std::cell::Cell::get);
if count > target_count {
breakpoints.pop_front();
SIM_RNG.with(|rng| {
*rng.borrow_mut() = ChaCha8Rng::seed_from_u64(new_seed);
});
CURRENT_SEED.with(|s| {
*s.borrow_mut() = new_seed;
});
RNG_CALL_COUNT.with(|c| c.set(1));
} else {
break;
}
}
});
}
#[must_use]
pub fn sim_random<T>() -> T
where
StandardUniform: Distribution<T>,
{
pre_sample();
SIM_RNG.with(|rng| rng.borrow_mut().sample(StandardUniform))
}
pub fn sim_random_range<T>(range: std::ops::Range<T>) -> T
where
T: SampleUniform + PartialOrd,
{
pre_sample();
SIM_RNG.with(|rng| rng.borrow_mut().random_range(range))
}
pub fn sim_random_range_or_default<T>(range: std::ops::Range<T>) -> T
where
T: SampleUniform + PartialOrd + Clone,
{
if range.start >= range.end {
range.start
} else {
sim_random_range(range)
}
}
pub fn set_sim_seed(seed: u64) {
SIM_RNG.with(|rng| {
*rng.borrow_mut() = ChaCha8Rng::seed_from_u64(seed);
});
CURRENT_SEED.with(|current| {
*current.borrow_mut() = seed;
});
}
#[must_use]
pub fn sim_random_f64() -> f64 {
pre_sample();
SIM_RNG.with(|rng| rng.borrow_mut().sample(StandardUniform))
}
#[must_use]
pub fn current_sim_seed() -> u64 {
CURRENT_SEED.with(|current| *current.borrow())
}
pub fn reset_sim_rng() {
SIM_RNG.with(|rng| {
*rng.borrow_mut() = ChaCha8Rng::seed_from_u64(0);
});
CURRENT_SEED.with(|current| {
*current.borrow_mut() = 0;
});
RNG_CALL_COUNT.with(|c| c.set(0));
RNG_BREAKPOINTS.with(|bp| bp.borrow_mut().clear());
}
#[must_use]
pub fn rng_call_count() -> u64 {
RNG_CALL_COUNT.with(std::cell::Cell::get)
}
pub fn reset_rng_call_count() {
RNG_CALL_COUNT.with(|c| c.set(0));
}
pub fn set_rng_breakpoints(breakpoints: Vec<(u64, u64)>) {
RNG_BREAKPOINTS.with(|bp| {
*bp.borrow_mut() = VecDeque::from(breakpoints);
});
}
pub fn clear_rng_breakpoints() {
RNG_BREAKPOINTS.with(|bp| bp.borrow_mut().clear());
}
pub fn set_config_seed(seed: u64) {
CONFIG_RNG.with(|rng| {
*rng.borrow_mut() = ChaCha8Rng::seed_from_u64(seed ^ CONFIG_RNG_SALT);
});
}
pub fn reset_config_rng() {
CONFIG_RNG.with(|rng| {
*rng.borrow_mut() = ChaCha8Rng::seed_from_u64(0);
});
}
pub fn set_select_seed(seed: u64) {
SELECT_RNG.with(|rng| {
*rng.borrow_mut() = ChaCha8Rng::seed_from_u64(seed ^ SELECT_RNG_SALT);
});
moonpool_core::select_support::set_select_offset_override(Some(select_offset_from_stream));
}
pub fn reset_select_rng() {
SELECT_RNG.with(|rng| {
*rng.borrow_mut() = ChaCha8Rng::seed_from_u64(0);
});
moonpool_core::select_support::set_select_offset_override(None);
}
fn select_offset_from_stream(branches: u32) -> u32 {
SELECT_RNG.with(|rng| rng.borrow_mut().random_range(0..branches))
}
#[must_use]
pub fn config_random_f64() -> f64 {
CONFIG_RNG.with(|rng| rng.borrow_mut().sample(StandardUniform))
}
#[must_use]
pub fn config_random_bool(p: f64) -> bool {
config_random_f64() < p
}
pub fn set_swarm_op_seed(seed: Option<u64>) {
SWARM_OP_SEED.with(|s| s.set(seed));
}
fn splitmix64(mut x: u64) -> u64 {
x = (x ^ (x >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
x = (x ^ (x >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
x ^ (x >> 31)
}
#[must_use]
pub fn swarm_op_enabled(op_id: u8) -> bool {
match SWARM_OP_SEED.with(Cell::get) {
None => true,
Some(seed) => {
let mixed = splitmix64(seed ^ SWARM_OP_SALT ^ u64::from(op_id).rotate_left(32));
mixed & (1 << 63) != 0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_f64_eq(left: f64, right: f64) {
assert_eq!(left.to_bits(), right.to_bits(), "{left} != {right}");
}
fn assert_f64_ne(left: f64, right: f64) {
assert_ne!(left.to_bits(), right.to_bits(), "{left} == {right}");
}
#[test]
fn test_deterministic_randomness() {
set_sim_seed(42);
let value1: f64 = sim_random();
let value2: u32 = sim_random();
let value3: bool = sim_random();
set_sim_seed(42);
assert_f64_eq(value1, sim_random::<f64>());
assert_eq!(value2, sim_random::<u32>());
assert_eq!(value3, sim_random::<bool>());
}
#[test]
fn test_different_seeds_produce_different_values() {
set_sim_seed(1);
let value1_seed1: f64 = sim_random();
let value2_seed1: f64 = sim_random();
set_sim_seed(2);
let value1_seed2: f64 = sim_random();
let value2_seed2: f64 = sim_random();
assert_f64_ne(value1_seed1, value1_seed2);
assert_f64_ne(value2_seed1, value2_seed2);
}
#[test]
fn test_sim_random_range() {
set_sim_seed(42);
for _ in 0..100 {
let value = sim_random_range(10..20);
assert!(value >= 10);
assert!(value < 20);
}
for _ in 0..100 {
let value = sim_random_range(0.0..1.0);
assert!(value >= 0.0);
assert!(value < 1.0);
}
}
#[test]
fn test_range_determinism() {
set_sim_seed(123);
let value1 = sim_random_range(100..1000);
let value2 = sim_random_range(0.0..10.0);
set_sim_seed(123);
assert_eq!(value1, sim_random_range(100..1000));
assert_f64_eq(value2, sim_random_range(0.0..10.0));
}
#[test]
fn test_reset_clears_state() {
set_sim_seed(42);
let _advance1: f64 = sim_random();
let _advance2: f64 = sim_random();
let after_advance: f64 = sim_random();
reset_sim_rng();
set_sim_seed(42);
let first_value: f64 = sim_random();
assert_f64_ne(after_advance, first_value);
}
#[test]
fn test_sequence_persistence_within_thread() {
set_sim_seed(42);
let value1: f64 = sim_random();
let value2: f64 = sim_random();
let value3: f64 = sim_random();
set_sim_seed(42);
assert_f64_eq(value1, sim_random::<f64>());
assert_f64_eq(value2, sim_random::<f64>());
assert_f64_eq(value3, sim_random::<f64>());
}
#[test]
fn test_multiple_resets_and_seeds() {
for seed in [1, 42, 12345] {
reset_sim_rng();
set_sim_seed(seed);
let first: f64 = sim_random();
reset_sim_rng();
set_sim_seed(seed);
assert_f64_eq(first, sim_random::<f64>());
}
}
#[test]
fn test_current_sim_seed() {
set_sim_seed(12345);
assert_eq!(current_sim_seed(), 12345);
set_sim_seed(98765);
assert_eq!(current_sim_seed(), 98765);
reset_sim_rng();
assert_eq!(current_sim_seed(), 0);
}
#[test]
fn test_call_counting() {
reset_sim_rng();
set_sim_seed(42);
assert_eq!(rng_call_count(), 0);
let _: f64 = sim_random();
assert_eq!(rng_call_count(), 1);
let _: u32 = sim_random();
assert_eq!(rng_call_count(), 2);
let _ = sim_random_range(0..100);
assert_eq!(rng_call_count(), 3);
let _ = sim_random_f64();
assert_eq!(rng_call_count(), 4);
let _ = sim_random_range_or_default(0..100);
assert_eq!(rng_call_count(), 5);
let _ = sim_random_range_or_default(100..100);
assert_eq!(rng_call_count(), 5);
}
#[test]
fn test_breakpoint_reseed() {
reset_sim_rng();
set_sim_seed(100);
let mut old_values = Vec::new();
for _ in 0..5 {
old_values.push(sim_random::<f64>());
}
reset_sim_rng();
set_sim_seed(200);
let new_seed_first: f64 = sim_random();
reset_sim_rng();
set_sim_seed(100);
set_rng_breakpoints(vec![(5, 200)]);
for (i, expected) in old_values.iter().enumerate() {
let actual: f64 = sim_random();
assert_eq!(
expected.to_bits(),
actual.to_bits(),
"Mismatch at call {}",
i + 1
);
}
let after_breakpoint: f64 = sim_random();
assert_f64_eq(after_breakpoint, new_seed_first);
assert_eq!(rng_call_count(), 1);
assert_eq!(current_sim_seed(), 200);
}
#[test]
fn test_chained_breakpoints() {
reset_sim_rng();
set_sim_seed(10);
set_rng_breakpoints(vec![(3, 20), (2, 30)]);
let _: f64 = sim_random(); let _: f64 = sim_random(); let _: f64 = sim_random(); assert_eq!(current_sim_seed(), 10);
let _: f64 = sim_random();
assert_eq!(current_sim_seed(), 20);
assert_eq!(rng_call_count(), 1);
let _: f64 = sim_random();
let _: f64 = sim_random();
assert_eq!(current_sim_seed(), 30);
assert_eq!(rng_call_count(), 1);
}
#[test]
fn test_replay_determinism() {
reset_sim_rng();
set_sim_seed(42);
let _: f64 = sim_random();
let _: f64 = sim_random();
let _: f64 = sim_random();
let fork_count = rng_call_count();
set_sim_seed(99);
reset_rng_call_count();
let post_fork_1: f64 = sim_random();
let post_fork_2: f64 = sim_random();
reset_sim_rng();
set_sim_seed(42);
set_rng_breakpoints(vec![(fork_count, 99)]);
let _: f64 = sim_random();
let _: f64 = sim_random();
let _: f64 = sim_random();
let replay_1: f64 = sim_random();
let replay_2: f64 = sim_random();
assert_f64_eq(post_fork_1, replay_1);
assert_f64_eq(post_fork_2, replay_2);
}
#[test]
fn test_config_rng_does_not_perturb_sim_rng() {
reset_sim_rng();
set_sim_seed(42);
let control: Vec<f64> = (0..5).map(|_| sim_random::<f64>()).collect();
let control_count = rng_call_count();
reset_sim_rng();
set_sim_seed(42);
set_config_seed(42);
let mut experiment = Vec::new();
for _ in 0..5 {
let _ = config_random_bool(0.5);
let _ = config_random_f64();
experiment.push(sim_random::<f64>());
}
for (c, e) in control.iter().zip(experiment.iter()) {
assert_f64_eq(*c, *e);
}
assert_eq!(rng_call_count(), control_count);
}
#[test]
fn test_config_rng_determinism_and_independence_from_seed() {
set_config_seed(7);
let a: Vec<f64> = (0..4).map(|_| config_random_f64()).collect();
set_config_seed(7);
let b: Vec<f64> = (0..4).map(|_| config_random_f64()).collect();
for (x, y) in a.iter().zip(b.iter()) {
assert_f64_eq(*x, *y);
}
set_sim_seed(7);
let sim_first: f64 = sim_random();
set_config_seed(7);
let config_first = config_random_f64();
assert_f64_ne(sim_first, config_first);
}
#[test]
fn test_reset_clears_everything_including_breakpoints() {
set_sim_seed(42);
let _: f64 = sim_random();
let _: f64 = sim_random();
set_rng_breakpoints(vec![(10, 99)]);
assert_eq!(rng_call_count(), 2);
reset_sim_rng();
assert_eq!(rng_call_count(), 0);
assert_eq!(current_sim_seed(), 0);
set_sim_seed(42);
let _: f64 = sim_random();
assert_eq!(rng_call_count(), 1);
assert_eq!(current_sim_seed(), 42); }
#[test]
fn select_rng_does_not_perturb_sim_rng_and_replays() {
reset_sim_rng();
set_sim_seed(42);
let control: Vec<f64> = (0..5).map(|_| sim_random::<f64>()).collect();
let control_count = rng_call_count();
reset_sim_rng();
set_sim_seed(42);
set_select_seed(42);
let mut offsets_a = Vec::new();
let mut experiment = Vec::new();
for _ in 0..5 {
offsets_a.push(select_offset_from_stream(8));
experiment.push(sim_random::<f64>());
}
for (c, e) in control.iter().zip(experiment.iter()) {
assert_f64_eq(*c, *e);
}
assert_eq!(
rng_call_count(),
control_count,
"select offsets must not touch the SIM_RNG call count"
);
set_select_seed(42);
let offsets_b: Vec<u32> = (0..5).map(|_| select_offset_from_stream(8)).collect();
assert_eq!(offsets_a, offsets_b);
assert!(
offsets_a.iter().any(|&o| o != offsets_a[0]),
"offset stream should vary"
);
reset_select_rng();
}
#[test]
fn swarm_op_disabled_enables_full_alphabet() {
set_swarm_op_seed(None);
for op in 0..32u8 {
assert!(
swarm_op_enabled(op),
"op {op} must be enabled when swarm is off"
);
}
}
#[test]
fn swarm_op_mask_is_idempotent_and_order_independent() {
const N: u8 = 16;
set_swarm_op_seed(Some(123));
let forward: Vec<bool> = (0..N).map(swarm_op_enabled).collect();
set_swarm_op_seed(Some(123));
let mut reverse = vec![false; usize::from(N)];
for op in (0..N).rev() {
let _ = swarm_op_enabled(op);
reverse[usize::from(op)] = swarm_op_enabled(op);
}
assert_eq!(
forward, reverse,
"mask must be idempotent and order-independent"
);
set_swarm_op_seed(None);
}
#[test]
fn swarm_op_varies_across_seeds_and_reaches_extremes() {
const N: u8 = 10;
let mut min_enabled = usize::MAX;
let mut max_enabled = 0usize;
for seed in 0..4000u64 {
set_swarm_op_seed(Some(seed));
let count = (0..N).filter(|&op| swarm_op_enabled(op)).count();
min_enabled = min_enabled.min(count);
max_enabled = max_enabled.max(count);
}
assert!(
min_enabled <= 1,
"expected a near-empty subset; min was {min_enabled}"
);
assert_eq!(
max_enabled,
usize::from(N),
"expected a full subset; max was {max_enabled}"
);
set_swarm_op_seed(None);
}
#[test]
fn swarm_op_query_does_not_perturb_sim_rng() {
reset_sim_rng();
set_sim_seed(99);
let _: f64 = sim_random();
let before = rng_call_count();
set_swarm_op_seed(Some(5));
for op in 0..50u8 {
let _ = swarm_op_enabled(op);
}
assert_eq!(
rng_call_count(),
before,
"swarm_op_enabled must not touch the SIM_RNG call count"
);
set_swarm_op_seed(None);
}
}