41 lines
2.5 KiB
Markdown
41 lines
2.5 KiB
Markdown
# Task 4: Deterministic RNG
|
|
|
|
**Depends on:** Task 1
|
|
|
|
## Context Files
|
|
- `.dev/2026-05-28-procgen-map-core/spec/design.md` — §4.4 (RNG) and §7 (determinism contract).
|
|
- `reikhelm-core/src/lib.rs` — append the module declaration here.
|
|
|
|
## Files
|
|
- Create: `reikhelm-core/src/rng.rs` — `Rng`.
|
|
- Modify: `reikhelm-core/src/lib.rs` — append `pub mod rng;`.
|
|
|
|
## What to Build
|
|
A deterministic RNG wrapping `rand_chacha::ChaCha8Rng` (spec §4.4). Reproducible across machines — never `thread_rng`. The keystone feature is `fork`: deriving an independent child stream from a **stable basis** (the originating seed) plus a label, so that forks are independent of draw order and of each other. The `Pipeline` (Task 6) calls `fork` once per pass; this is what makes editing the pipeline non-destructive to existing seeds (spec §7).
|
|
|
|
**Critical determinism detail:** `fork`'s label→seed mixing must be stable across runs and crate versions. Do **not** use `std::collections::hash_map::DefaultHasher` (unspecified, version-unstable). Use an explicit, fixed integer mix (e.g. a splitmix64 / FNV-style combine of the base seed and the label bytes). Document the chosen mixing.
|
|
|
|
## Interface Dependencies
|
|
- **Produces:** `Rng` with:
|
|
- `from_seed(seed: u64) -> Self` (stores the seed as the fork basis)
|
|
- `fork(&self, label: &str) -> Rng` — returns `Rng::from_seed(mix(self.basis_seed, label))`; **does not** advance `self`; order-independent.
|
|
- `range(&mut self, lo: i32, hi: i32) -> i32` (half-open or inclusive — document which)
|
|
- `chance(&mut self, p: f64) -> bool`
|
|
- `choose<'a, T>(&mut self, items: &'a [T]) -> Option<&'a T>`
|
|
- `shuffle<T>(&mut self, items: &mut [T])`
|
|
|
|
## Tests
|
|
- Same seed → identical sequence of `range` draws (reproducibility).
|
|
- `fork` is **order-independent**: `r.fork("a")` produces the same stream regardless of how many values were drawn from `r` beforehand.
|
|
- Distinct labels produce distinct (non-identical) streams: `fork("a") != fork("b")`.
|
|
- `fork("name#0")` and `fork("name#1")` differ (the occurrence-keying scheme the pipeline relies on works).
|
|
- `choose` on an empty slice returns `None`; `shuffle` is a permutation (same multiset).
|
|
|
|
## Success Criteria
|
|
- [ ] `Rng` wraps ChaCha8; `fork` derives from a stable basis with an explicit (non-`DefaultHasher`) mix.
|
|
- [ ] `fork` is order-independent and does not mutate the parent.
|
|
- [ ] `pub mod rng;` added to `lib.rs`.
|
|
- [ ] All tests pass: `cargo test -p reikhelm-core rng`
|
|
|
|
## Commit
|
|
`"feat(core): deterministic ChaCha8 RNG with order-independent fork"`
|