From 2fd42c10cffb65f16fcade914a0ced411304cd85 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sat, 6 Jun 2026 10:18:19 -0400 Subject: [PATCH] feat(rng-seam): SeededRandomSource mirrors the engine's two System.Random streams --- SVSim.BattleEngine.Tests/RngSeamTests.cs | 18 ++++++++++++++++ SVSim.BattleEngine/Rng/SeededRandomSource.cs | 22 ++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 SVSim.BattleEngine/Rng/SeededRandomSource.cs diff --git a/SVSim.BattleEngine.Tests/RngSeamTests.cs b/SVSim.BattleEngine.Tests/RngSeamTests.cs index 62b0f50..21f7edb 100644 --- a/SVSim.BattleEngine.Tests/RngSeamTests.cs +++ b/SVSim.BattleEngine.Tests/RngSeamTests.cs @@ -17,5 +17,23 @@ namespace SVSim.BattleEngine.Tests Assert.That(RandomSourceBridge.Range(3, 0.5), Is.EqualTo(1)); // floor(1.5) = 1 (middle of 3) Assert.That(RandomSourceBridge.Range(1, 0.5), Is.EqualTo(0)); // floor(0.5) = 0 } + + // SeededRandomSource(seed) must reproduce the engine's own generators EXACTLY: BattleManagerBase + // seeds both _stableRandom and _stableRandomOnlySelf as `new System.Random(RandomSeed)` + // (BattleManagerBase.cs:721-722). NextUnit() == synced.NextDouble(); NextSelf(max) == self.Next(max). + [Test] + public void SeededSource_reproduces_two_System_Random_streams() + { + const int seed = 12345; + var src = new SeededRandomSource(seed); + + var refSynced = new System.Random(seed); // mirrors _stableRandom + var refSelf = new System.Random(seed); // mirrors _stableRandomOnlySelf (separate stream) + + for (int i = 0; i < 8; i++) + Assert.That(src.NextUnit(), Is.EqualTo(refSynced.NextDouble()), $"NextUnit drift at {i}"); + for (int i = 0; i < 8; i++) + Assert.That(src.NextSelf(100), Is.EqualTo(refSelf.Next(100)), $"NextSelf drift at {i}"); + } } } diff --git a/SVSim.BattleEngine/Rng/SeededRandomSource.cs b/SVSim.BattleEngine/Rng/SeededRandomSource.cs new file mode 100644 index 0000000..b5eae4a --- /dev/null +++ b/SVSim.BattleEngine/Rng/SeededRandomSource.cs @@ -0,0 +1,22 @@ +using System; + +namespace SVSim.BattleEngine.Rng +{ + // Default source. Faithfully reproduces the engine's own RNG: BattleManagerBase seeds two separate + // System.Random(RandomSeed) instances (_stableRandom synced, _stableRandomOnlySelf self-only) at + // BattleManagerBase.cs:721-722. The authoritative server uses this; tests use ScriptedRandomSource. + public sealed class SeededRandomSource : IRandomSource + { + private readonly Random _synced; + private readonly Random _self; + + public SeededRandomSource(int seed) + { + _synced = new Random(seed); + _self = new Random(seed); + } + + public double NextUnit() => _synced.NextDouble(); + public int NextSelf(int max) => _self.Next(max); + } +}