Skip to main content

stratifykit_core/
hash.rs

1/// Deterministic hash of `(seed, s)` -- the tie-breaking/spreading mechanism bounded top-K
2/// sampling uses to pick "which items" deterministically. Each part is hashed with an explicit
3/// length prefix so a naive concatenation can't collide across the seed/string boundary.
4pub fn seeded_hash(seed: u64, s: &str) -> u64 {
5    let mut h = blake3::Hasher::new();
6    for part in [&seed.to_le_bytes()[..], s.as_bytes()] {
7        h.update(&(part.len() as u64).to_le_bytes());
8        h.update(part);
9    }
10    u64::from_le_bytes(h.finalize().as_bytes()[..8].try_into().unwrap())
11}
12
13#[cfg(test)]
14mod tests {
15    use super::*;
16
17    #[test]
18    fn same_seed_and_input_are_deterministic() {
19        assert_eq!(seeded_hash(42, "abc"), seeded_hash(42, "abc"));
20    }
21
22    #[test]
23    fn different_seeds_spread_the_same_input() {
24        assert_ne!(seeded_hash(1, "abc"), seeded_hash(2, "abc"));
25    }
26
27    #[test]
28    fn different_inputs_spread_the_same_seed() {
29        assert_ne!(seeded_hash(1, "ab"), seeded_hash(1, "ab_extra"));
30    }
31
32    // Why hard-coded, not just "assert it matches itself": blake3's digest for a fixed input is
33    // stable forever by spec, so a literal expected value is a real regression guard, catching a
34    // future accidental change to the hashing scheme -- ported verbatim from the pre-extraction
35    // implementation in shogiesa-cli to prove this crate's algorithm is byte-for-byte identical.
36    #[test]
37    fn seeded_hash_is_a_stable_golden_value() {
38        assert_eq!(seeded_hash(7, "startpos"), 13402537162744184401);
39    }
40}