simulation.py calls np.random.seed(seed) once in run_with(), then draws randomness via the global-state functions np.random.randn() / np.random.rand() at 8+ call sites (ARMAX noise, quality-latching lab noise, filter-clog variance, sensor intermittence).
This works as long as nothing else in the process touches np.random between calls, but it's implicit shared mutable state — exactly what the project's byte-identical fingerprint contract shouldn't depend on. Contrast with fouling_modes.py, which does this correctly: FoulingModeStepper.step() takes an explicit rng: np.random.Generator parameter.
Suggested fix: thread an explicit np.random.default_rng(seed) through run_with() and its helper functions, matching the pattern already used in fouling_modes.py.
Files: bdsim/simulation.py
simulation.pycallsnp.random.seed(seed)once inrun_with(), then draws randomness via the global-state functionsnp.random.randn()/np.random.rand()at 8+ call sites (ARMAX noise, quality-latching lab noise, filter-clog variance, sensor intermittence).This works as long as nothing else in the process touches
np.randombetween calls, but it's implicit shared mutable state — exactly what the project's byte-identical fingerprint contract shouldn't depend on. Contrast withfouling_modes.py, which does this correctly:FoulingModeStepper.step()takes an explicitrng: np.random.Generatorparameter.Suggested fix: thread an explicit
np.random.default_rng(seed)throughrun_with()and its helper functions, matching the pattern already used infouling_modes.py.Files:
bdsim/simulation.py