Skip to main content

holodeck_lib/
seed.rs

1//! Deterministic seed computation for reproducible simulations.
2//!
3//! When no explicit `--seed` is provided, a seed is derived from the
4//! simulation parameters so that identical inputs always produce identical
5//! output across runs and processes.
6
7/// Compute a deterministic seed by hashing a string description of the
8/// simulation parameters.
9///
10/// Uses FNV-1a hashing which is deterministic across runs (unlike `ahash`
11/// which uses per-process random keys). The same input always produces the
12/// same seed.
13#[must_use]
14pub fn compute_seed(description: &str) -> u64 {
15    // FNV-1a hash — deterministic, fast, no external dependencies.
16    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
17    for byte in description.bytes() {
18        hash ^= u64::from(byte);
19        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
20    }
21    hash
22}
23
24/// Resolve the effective seed: use the explicit seed if provided, otherwise
25/// derive one from the given parameter description string.
26#[must_use]
27pub fn resolve_seed(explicit: Option<u64>, description: &str) -> u64 {
28    explicit.unwrap_or_else(|| compute_seed(description))
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn test_compute_seed_deterministic() {
37        let a = compute_seed("hello:42:3.14");
38        let b = compute_seed("hello:42:3.14");
39        assert_eq!(a, b);
40    }
41
42    #[test]
43    fn test_compute_seed_known_value() {
44        // Pin a known value so we detect if the hash implementation changes.
45        let seed = compute_seed("test");
46        assert_eq!(seed, 0xf9e6_e6ef_197c_2b25);
47    }
48
49    #[test]
50    fn test_compute_seed_different_inputs() {
51        let a = compute_seed("hello");
52        let b = compute_seed("world");
53        assert_ne!(a, b);
54    }
55
56    #[test]
57    fn test_resolve_seed_explicit() {
58        let seed = resolve_seed(Some(42), "ignored");
59        assert_eq!(seed, 42);
60    }
61
62    #[test]
63    fn test_resolve_seed_derived() {
64        let seed = resolve_seed(None, "hello");
65        let expected = compute_seed("hello");
66        assert_eq!(seed, expected);
67    }
68}