/// 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);
}
