MLD301: Updater applies a fixed per-frame step without dt scaling¶
qual reports MLD301 when a callback registered through
Mobject.add_updater applies a relative mutation with a provably fixed
step (shift, rotate, scale, increment_value with arguments built
only from numeric literals and Manim constants such as RIGHT or PI)
while declaring no parameter named dt.
- Default severity:
warning - Minimum confidence:
high - Implementation phase:
4 - Fix: none
Manim inspects the callback signature and calls it as (mobject, dt) only
when a parameter literally named dt exists; otherwise the callback runs
once per rendered frame with no time information (DESIGN 3.3). A fixed
relative step then advances per frame: the same scene moves twice as
fast at 60 FPS as at 30 FPS, so rendering with a different frame-rate
profile changes the animation itself.
The rule does not forbid one-argument updaters. Absolute dependencies
(next_to, move_to, tracker-driven values) are correct without dt,
and any step the analyzer cannot prove constant (a closure variable, a
ValueTracker read, a branch-dependent coefficient) stays silent.
Scene-level updaters (Scene.add_updater) have a different (dt)
contract and are out of scope.
Wrong:
dot.add_updater(lambda m: m.shift(0.1 * RIGHT)) # 6 units/s at 60 FPS, 3 at 30
square.add_updater(lambda m: m.rotate(0.05)) # rotation rate tracks FPS
Right: