Skip to content

Determinism

StableHash · DeterministicMath · TickDigest

Kind Invariant 6, and the three things that enforce it
Project NextIteration.Core/Simulation/StableHash.cs, DeterministicMath.cs; trace TickDigest
Guarantee same seed + same active systems + same recorded LLM responses → identical outcomes, on any platform
Checked by pr.yml cross-process golden job; scripts/check-determinism.sh
Status designed · Phase 1 (#30)
PLAN §18.7, §13.5, §18.1
Player The timeline (why replay is exact)

Replay, rewind, fork, the golden test, the harness's reproducible seeds, and multiplayer all rest on the same property: the engine is a pure function of its inputs. Three rules enforce it.

1. StableHash, never HashCode.Combine

.NET randomises HashCode.Combine and string.GetHashCode() per process. The spike's "seeded" RNG gave 17, 20 and 25 survivors in three runs of the same seed, and its determinism test passed because both runs shared a process. Every seed, tiebreak or sample that touches an outcome uses StableHash (FNV-1a):

var rng = ctx.DeterministicRng("perception_sampling", locationId.Value);   // StableHash.Combine(tick, salt, purpose)

Resolver tiebreaks hash an input that differs between rivals — (tick, actorId, intentOrdinal) — not (tick, node, key), which is identical for every intent in a conflict group. Never iterate a HashSet or Dictionary where order reaches an intent or an option.

2. DeterministicMath, never Math.Sin

Math.Sin, Exp, Pow and their MathF twins call the platform's C runtime, and Windows and Linux can differ in the last bit — a Windows save replayed on Linux CI would drift while the same-platform golden test stayed green. Anything that touches an outcome (the ambient sine, fact-confidence decay) uses DeterministicMath — polynomial approximations over float, bit-identical everywhere, accurate to about 1e-6. System.Math is allowed only where the result never reaches an intent, a score, a fact or a seed. Basic float arithmetic is IEEE-754 and already portable.

3. The per-tick digest

The trace computes a stable hash over each tick's applied intents in resolution order. The golden test runs one seed in two child processes and compares digests tick by tick, so a divergence names the tick and the first differing intent. The same digest is the fork-compare primitive (Phase 3) and an integrity check on the warm trace store.

dotnet run --project tests/NextIteration.Harness -- village --seeds 1 --days 10 --golden --processes 2

What else must hold

Systems run in (phase, priority, name) order; publishers in (priority, name); scenario spawn variance is seeded per (scenarioId, spawnIndex); LLM responses are recorded and replayed from the oracle log (Phase 5); player actions are logged so rewind and replay reproduce them.

History

Phase Change
1 Introduced: StableHash everywhere, DeterministicMath, the digest as the golden signature, the cross-process test in CI.

See also

Causal trace · Intents and the resolver · Views, snapshots and the ring buffer