/// Deterministic seedable PRNG for the game engine. /// /// dart:math's Random is implementation-unspecified across platforms; piece /// sequences must be byte-identical on iOS, Android, and in tests, so we own /// the algorithm (PCG32, https://www.pcg-random.org). class SeededRng { SeededRng(int seed, [int sequence = 0]) : _state = 0, _inc = ((sequence << 1) | 1) { _nextRaw(); _state = _state + seed; _nextRaw(); } // Dart native ints are 64-bit with wrapping arithmetic, which is exactly // what the PCG32 state transition needs. int _state; final int _inc; static const _multiplier = 6364136223846793005; static const _mask32 = 0xFFFFFFFF; /// One PCG32 step: 32-bit output via xorshift-high + random rotation. int _nextRaw() { final old = _state; _state = old * _multiplier + _inc; final xorshifted = (((old >>> 18) ^ old) >>> 27) & _mask32; final rot = old >>> 59; return ((xorshifted >>> rot) | (xorshifted << ((-rot) & 31))) & _mask32; } /// Uniform integer in [0, max), bias-free via rejection sampling. int nextInt(int max) { assert(max > 0); final threshold = (0x100000000 - (0x100000000 % max)) & _mask32; if (threshold == 0) return _nextRaw() % max; while (true) { final r = _nextRaw(); if (r < threshold) return r % max; } } /// Uniform double in [0, 1). double nextDouble() => _nextRaw() / 4294967296.0; /// Derives an independent deterministic stream (e.g. per retry attempt). SeededRng fork(int streamId) => SeededRng(_nextRaw() ^ streamId, streamId); }