1+ using System ;
2+ using System . Runtime . CompilerServices ;
3+
4+ namespace FixedMathSharp . Utility
5+ {
6+ /// <summary>
7+ /// Fast, seedable, deterministic RNG suitable for lockstep sims and map gen.
8+ /// Uses xoroshiro128++ with splitmix64 seeding. No allocations, no time/GUID.
9+ /// </summary>
10+ public struct DeterministicRandom
11+ {
12+ // xoroshiro128++ state
13+ private ulong _s0 ;
14+ private ulong _s1 ;
15+
16+ #region Construction / Seeding
17+
18+ public DeterministicRandom ( ulong seed )
19+ {
20+ // Expand a single seed into two 64-bit state words via splitmix64.
21+ _s0 = SplitMix64 ( ref seed ) ;
22+ _s1 = SplitMix64 ( ref seed ) ;
23+
24+ // xoroshiro requires non-zero state; repair pathological seed.
25+ if ( _s0 == 0UL && _s1 == 0UL )
26+ _s1 = 0x9E3779B97F4A7C15UL ;
27+ }
28+
29+ /// <summary>
30+ /// Create a stream deterministically
31+ /// Derived from (worldSeed, featureKey[,index]).
32+ /// </summary>
33+ public static DeterministicRandom FromWorldFeature ( ulong worldSeed , ulong featureKey , ulong index = 0 )
34+ {
35+ // Simple reversible mix (swap for a stronger mix if required).
36+ ulong seed = Mix64 ( worldSeed , featureKey ) ;
37+ seed = Mix64 ( seed , index ) ;
38+ return new DeterministicRandom ( seed ) ;
39+ }
40+
41+ #endregion
42+
43+ #region Core PRNG
44+
45+ /// <summary>
46+ /// xoroshiro128++ next 64 bits.
47+ /// </summary>
48+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
49+ public ulong NextU64 ( )
50+ {
51+ ulong s0 = _s0 , s1 = _s1 ;
52+ ulong result = RotL ( s0 + s1 , 17 ) + s0 ;
53+
54+ s1 ^= s0 ;
55+ _s0 = RotL ( s0 , 49 ) ^ s1 ^ ( s1 << 21 ) ; // a,b
56+ _s1 = RotL ( s1 , 28 ) ; // c
57+
58+ return result ;
59+ }
60+
61+ /// <summary>
62+ /// Next non-negative Int32 in [0, int.MaxValue].
63+ /// </summary>
64+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
65+ public int Next ( )
66+ {
67+ // Take high bits for better quality; mask to 31 bits non-negative.
68+ return ( int ) ( NextU64 ( ) >> 33 ) ;
69+ }
70+
71+ /// <summary>
72+ /// Unbiased int in [0, maxExclusive).
73+ /// </summary>
74+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
75+ public int Next ( int maxExclusive )
76+ {
77+ return maxExclusive <= 0
78+ ? throw new ArgumentOutOfRangeException ( nameof ( maxExclusive ) )
79+ : ( int ) NextBounded ( ( uint ) maxExclusive ) ;
80+ }
81+
82+ /// <summary>
83+ /// Unbiased int in [min, maxExclusive).
84+ /// </summary>
85+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
86+ public int Next ( int minInclusive , int maxExclusive )
87+ {
88+ if ( minInclusive >= maxExclusive )
89+ throw new ArgumentException ( "min >= max" ) ;
90+ uint range = ( uint ) ( maxExclusive - minInclusive ) ;
91+ return minInclusive + ( int ) NextBounded ( range ) ;
92+ }
93+
94+ /// <summary>
95+ /// Double in [0,1).
96+ /// </summary>
97+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
98+ public double NextDouble ( )
99+ {
100+ // 53 random bits -> [0,1)
101+ return ( NextU64 ( ) >> 11 ) * ( 1.0 / ( 1UL << 53 ) ) ;
102+ }
103+
104+ /// <summary>
105+ /// Fill span with random bytes.
106+ /// </summary>
107+ public void NextBytes ( Span < byte > buffer )
108+ {
109+ int i = 0 ;
110+ while ( i + 8 <= buffer . Length )
111+ {
112+ ulong v = NextU64 ( ) ;
113+ Unsafe . WriteUnaligned ( ref buffer [ i ] , v ) ;
114+ i += 8 ;
115+ }
116+ if ( i < buffer . Length )
117+ {
118+ ulong v = NextU64 ( ) ;
119+ while ( i < buffer . Length )
120+ {
121+ buffer [ i ++ ] = ( byte ) v ;
122+ v >>= 8 ;
123+ }
124+ }
125+ }
126+
127+ #endregion
128+
129+ #region Fixed64 helpers
130+
131+ /// <summary>
132+ /// Random Fixed64 in [0,1).
133+ /// </summary>
134+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
135+ public Fixed64 NextFixed6401 ( )
136+ {
137+ // Produce a raw value in [0, One.m_rawValue)
138+ ulong rawOne = ( ulong ) Fixed64 . One . m_rawValue ;
139+ ulong r = NextBounded ( rawOne ) ;
140+ return Fixed64 . FromRaw ( ( long ) r ) ;
141+ }
142+
143+ /// <summary>
144+ /// Random Fixed64 in [0, maxExclusive).
145+ /// </summary>
146+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
147+ public Fixed64 NextFixed64 ( Fixed64 maxExclusive )
148+ {
149+ if ( maxExclusive <= Fixed64 . Zero )
150+ throw new ArgumentOutOfRangeException ( nameof ( maxExclusive ) , "max must be > 0" ) ;
151+ ulong rawMax = ( ulong ) maxExclusive . m_rawValue ;
152+ ulong r = NextBounded ( rawMax ) ;
153+ return Fixed64 . FromRaw ( ( long ) r ) ;
154+ }
155+
156+ /// <summary>
157+ /// Random Fixed64 in [minInclusive, maxExclusive).
158+ /// </summary>
159+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
160+ public Fixed64 NextFixed64 ( Fixed64 minInclusive , Fixed64 maxExclusive )
161+ {
162+ if ( minInclusive >= maxExclusive )
163+ throw new ArgumentException ( "min >= max" ) ;
164+ ulong span = ( ulong ) ( maxExclusive . m_rawValue - minInclusive . m_rawValue ) ;
165+ ulong r = NextBounded ( span ) ;
166+ return Fixed64 . FromRaw ( ( long ) r + minInclusive . m_rawValue ) ;
167+ }
168+
169+ #endregion
170+
171+ #region Internals: unbiased range, splitmix64, mixing, rotations
172+
173+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
174+ private ulong NextBounded ( ulong bound )
175+ {
176+ // Rejection to avoid modulo bias.
177+ // threshold = 2^64 % bound, but expressed as (-bound) % bound
178+ ulong threshold = unchecked ( ( ulong ) - ( long ) bound ) % bound ;
179+ while ( true )
180+ {
181+ ulong r = NextU64 ( ) ;
182+ if ( r >= threshold )
183+ return r % bound ;
184+ }
185+ }
186+
187+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
188+ private static ulong RotL ( ulong x , int k ) => ( x << k ) | ( x >> ( 64 - k ) ) ;
189+
190+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
191+ private static ulong SplitMix64 ( ref ulong state )
192+ {
193+ ulong z = ( state += 0x9E3779B97F4A7C15UL ) ;
194+ z = ( z ^ ( z >> 30 ) ) * 0xBF58476D1CE4E5B9UL ;
195+ z = ( z ^ ( z >> 27 ) ) * 0x94D049BB133111EBUL ;
196+ return z ^ ( z >> 31 ) ;
197+ }
198+
199+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
200+ private static ulong Mix64 ( ulong a , ulong b )
201+ {
202+ // Simple reversible mix (variant of splitmix finalizer).
203+ ulong x = a ^ ( b + 0x9E3779B97F4A7C15UL ) ;
204+ x = ( x ^ ( x >> 30 ) ) * 0xBF58476D1CE4E5B9UL ;
205+ x = ( x ^ ( x >> 27 ) ) * 0x94D049BB133111EBUL ;
206+ return x ^ ( x >> 31 ) ;
207+ }
208+
209+ #endregion
210+ }
211+ }
0 commit comments