Title: Why We Switched to a Custom Xoshiro256++ RNG for Our Game
Content:
In our ongoing effort to improve save/load determinism and replay reliability in our game, we recently evaluated the random number generator (RNG) system at the core of our gameplay mechanics. Here’s the detailed reasoning behind the change, what we discovered, and what we decided.
The Problem with System.Random
C# provides a built-in random number generator via System.Random. While this generator is statistically high-quality in modern .NET (using xoshiro256** on 64-bit systems and xoshiro128** on 32-bit), it has a critical limitation for our use case: its internal state is not accessible.
This creates two major issues for a game like ours:
- Save/Load Determinism: To perfectly reproduce a game state after saving, we need to pause and restore the RNG exactly where it left off. With
System.Random, there’s no way to capture or restore the internal state, so recreating the same sequence of random events becomes impossible. - Replay/Debugging: Our game requires deterministic replays for debugging and potential multiplayer lockstep simulation. Without access to the RNG state, reproducing exact sequences of events is not feasible.
Workarounds—like generating a new seed from the RNG and reinitializing it—are possible but cumbersome, error-prone, and inefficient.
Evaluating Alternatives
We considered several options:
| RNG | Quality | Ease of Serialization | Notes |
|---|---|---|---|
| LCG | Fair | Very easy (1 ulong) | Simple, but statistically weak |
| PCG32 | Excellent | Easy (2 ulongs) | Widely used in games, very fast |
| Xoshiro256++ | Excellent | Moderate (4 ulongs) | Extremely fast, excellent statistical properties, recommended for reproducible simulations |
| System.Random | Excellent | Not accessible | Cannot serialize or restore state, even though underlying algorithm is strong |
While System.Random is statistically solid, it fails the determinism requirement. LCGs are too weak for long-term simulations. PCG32 and Xoshiro256++ are both excellent candidates.
Decision: Xoshiro256++
We decided to implement Xoshiro256++ for several reasons:
- High Quality Randomness: Xoshiro256++ provides top-tier statistical randomness, far exceeding the needs of our game, ensuring no patterns or correlations will affect gameplay.
- Deterministic State Control: The RNG state consists of four 64-bit integers (
ulong s0, s1, s2, s3), which can be fully saved and restored. This allows us to perfectly reproduce game states during save/load or replay. - Performance: Xoshiro256++ is extremely fast—faster than PCG32 in most scenarios—making it suitable for procedural generation, combat rolls, loot drops, and AI decisions without any noticeable impact on performance.
- Future-Proof: It supports “jump” and “long jump” features for independent RNG streams, useful if we want separate RNGs for world generation vs. combat vs. AI.
Implementation Highlights
- Seeding: We use
SplitMix64to expand a single seed into four 64-bit state variables. - Serialization: The state can be saved to a save file and restored exactly, ensuring reproducibility.
- Next Functions: The implementation supports
Next(),Next(max),Next(min, max), andNextDouble().
This change ensures that every random number generated in our game is fully deterministic and reproducible, solving a critical limitation we had with System.Random while maintaining excellent performance and statistical quality.
Conclusion
While System.Random uses a modern xoshiro-based algorithm and is statistically excellent, the inability to access or serialize its internal state makes it unsuitable for deterministic game mechanics. By switching to a custom Xoshiro256++ RNG, we now have:
- Full control over RNG state
- Perfect reproducibility for saves and replays
- High-quality randomness without compromise
- A fast, future-proof foundation for procedural generation
This change allows us to build more reliable and predictable gameplay experiences while maintaining high performance and statistical integrity.