Skip to content

MLP224: point_from_proportion on a confirmed-long path in a per-frame callback

qual reports MLP224 when point_from_proportion runs inside a per-frame callback on a receiver whose point count is confirmed large: every call measures the length of each curve in the path to build an arc-length table before locating the sample, and per frame on a long path that repeated measurement dominates.

  • Default severity: info
  • Minimum confidence: high
  • Implementation phase: 3
  • Fix: none
  • Supersedes: MLP203 (the generic family-walk rule routes point_from_proportion entries here and never reports them itself)

Emission gate (cited in evidence): P >= 1024 (large-points-gate) — only a proven lower bound confirms; loop-built paths widen to open intervals and stay silent.

Scope: receiver resolution uses the updater-lambda-host contract (the callee is exactly <param>.<method> directly inside a Mobject.add_updater lambda), so queries against other objects inside a callback stay silent. The catalog's general apply_function leg is also out of scope: no fact layer proves that an apply_function callback iterates the full point set of a specific receiver, so it is never guessed.

Proven execution (liveness)

A hot context only proves the call is reachable from a per-frame entry point. The diagnostic additionally requires at least one play where the callback provably executes per frame (DESIGN 3.2/3.3):

  • the registration is active at the play (registered on every path and not removed before it),
  • the host is in the scene family there,
  • the host is not suspended: Animation.begin() suspends the animated mobject's updaters — recursively over its family — for the whole play (animation.py), unless the animation was constructed with suspend_mobject_updating=False,
  • the play actually runs callbacks: a play(...), or a wait that is provably dynamic (scene.py should_update_mobjects). A one-argument updater leaves the default wait() a static frame.

self.add(label); self.play(FadeIn(label), run_time=2); self.wait(3) therefore stays silent: the only play suspends the updater and the wait is static, so the callback runs about once — never once per frame. Evidence lists the counted plays (execution) so the claim is auditable; quantities sum over the proven plays only, and only-maybe evidence (a branch-dependent registration, an unknown animation that may suspend the host) never reaches this rule's high confidence.

Wrong

class Demo(Scene):
    def construct(self):
        anchor = Square()
        path = VGroup(*many_circles)  # >= 1024 points, confirmed
        path.add_updater(lambda m: m.move_to(m.point_from_proportion(0.5)))
        self.add(path, anchor)
        self.play(FadeIn(anchor), run_time=2)  # path stays live: its updater runs
class Demo(Scene):
    def construct(self):
        path = VGroup(*many_circles)
        midpoint = path.point_from_proportion(0.5)  # measured once
        marker = Dot()
        marker.add_updater(lambda m: m.move_to(midpoint))
        self.add(path, marker)
        self.play(FadeIn(path), run_time=2)