Use DetRng
DetRng is the random number generator for models. It keys each draw on the
run context and a salt, so the result does not depend on how work is scheduled
across threads.
Why not a thread-local RNG
A generator with mutable state — a thread-local, or one stored on the world —
produces draws in the order rows are visited. Under Rayon's work stealing that
order changes with the thread count, which changes the trajectory. DetRng
avoids this by keying each draw on coordinates that do not depend on scheduling.
Keying on the run context
Inside a system, open a stream from the run context and a salt:
let context = ecs.run_context();
let mut rng = DetRng::from_context(context, salt);
let u = rng.next_u64();
let f = rng.next_f64(); // in [0, 1)
let k = rng.next_below(6); // in [0, 6)
from_context folds (simulation_seed, tick, system_id, salt). Two draws with
the same coordinates produce the same value; different salts give independent
streams. The simulation_seed comes from ModelBuilder::with_seed.
Per-agent streams
When a loop over agents draws randomness, salt the stream with the agent's identity, so the draw depends on the agent rather than on the position at which it is visited:
ecs.for_each_entity_w1::<Position>(query, move |entity, pos| {
let mut rng = DetRng::from_context(context, u64::from(entity.index()));
pos.x += if rng.next_below(2) == 0 { -1 } else { 1 };
})?;
The first_model example uses this pattern, which keeps its walk identical at
one and eight threads.
Draw helpers
next_u64,next_f64,next_f32— raw draws.next_below(upper)— au64in[0, upper).next_index(len)— ausizein[0, len), for choosing an element.
Randomness outside a system
Population generation happens before any tick, so there is no run context. Use a
separate DetRng::from_seed(seed) there, keyed on the model's configured seed,
and keep it distinct from the per-tick streams.