Skip to content

MLD302: Unseeded global random state read inside a frame callback

qual reports MLD302 when a provably per-frame context (updater, always_redraw, stop condition, interpolate override, ...) calls a module-level function of stdlib random (random, randint, uniform, choice, shuffle, ...) or of numpy.random, reading the process-global random state.

  • Default severity: warning
  • Minimum confidence: medium
  • Implementation phase: 4
  • Fix: none

Every render then produces different frames: the output is not reproducible, cached partial movies never match, and visual diffing in CI is impossible.

Deliberately not flagged:

  • locally seeded generators — rng = random.Random(42) / gen = numpy.random.default_rng(7) and their instance methods;
  • files that seed the global state: a random.seed(...) (or numpy.random.seed(...)) call anywhere in the file conservatively downgrades the corresponding family to silence;
  • cold contexts — global randomness in construct runs once and is layout, not per-frame nondeterminism;
  • any name rebound elsewhere in the file (resolution never guesses).

Wrong:

dot.add_updater(lambda m: m.set_x(random.uniform(-1, 1)))     # new frames every run
dot.add_updater(lambda m: m.set_y(np.random.uniform(-1, 1)))

Right:

rng = random.Random(42)                                  # seeded local generator
dot.add_updater(lambda m: m.set_x(rng.uniform(-1, 1)))   # reproducible