"""seedlock.py — Deterministic RNG wrappers for all SilverSight shims. Usage: from seedlock import SeededRNG, lock lock(42) # called once at program start rng = SeededRNG(42) rng.random() # reproducible Python random rng.np_random() # reproducible NumPy random Enforcement: After lock(), direct random.seed() and np.random.seed() raise RuntimeError. """ from __future__ import annotations import os import random import sys from typing import Optional import numpy as np _LOCKED = False _GLOBAL_SEED: int = 0 class SeededRNG: """Deterministic RNG using Python's random.Random as the canonical source. Python's random.Random is specified by CPython and stable across platforms. NumPy's Generator may differ by version — NumPy is only used for array ops. """ def __init__(self, seed: int = 0): self._seed = seed self._py_rng = random.Random(seed) self._np_rng = np.random.default_rng(seed) def random(self) -> float: return self._py_rng.random() def randint(self, a: int, b: int) -> int: return self._py_rng.randint(a, b) def choice(self, seq): return self._py_rng.choice(seq) def shuffle(self, lst: list) -> None: self._py_rng.shuffle(lst) def np_random(self) -> np.random.Generator: return self._np_rng def seed(self, new_seed: int) -> None: self._seed = new_seed self._py_rng = random.Random(new_seed) self._np_rng = np.random.default_rng(new_seed) def lock(seed: int = 0): """Lock global RNG. After this call, direct unseeded RNG calls raise RuntimeError.""" global _LOCKED, _GLOBAL_SEED _GLOBAL_SEED = seed _LOCKED = True random.seed = _forbidden_seed # type: ignore # NumPy's random module may not have seed attribute in newer versions try: np.random.seed = _forbidden_seed # type: ignore except AttributeError: pass def is_locked() -> bool: return _LOCKED def global_seed() -> int: return _GLOBAL_SEED def _forbidden_seed(*args, **kwargs): raise RuntimeError( "Direct random.seed() call detected. " "Use seedlock.SeededRNG(seed) for explicit seeding. " "Layer 0 determinism violation." )