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