Skip to content

MLP209: front-loaded dynamic object re-rasterizes a large static suffix

qual reports MLP209 when an animated, foreground, or updater-bearing object sits near the front of Cairo's effective display order — computed from the family flatten, the z_index stable sort, and the foreground list, never from root add order alone — and a provably large static suffix behind it is re-rasterized every frame of the play.

  • Default severity: info (advisory: reordering changes what draws over what)
  • Minimum confidence: medium (emitted at high when the play is on every path and the suffix is exact)
  • Implementation phase: 3
  • Profiles: Cairo only (applicable_profiles is restricted to the active Cairo profiles)
  • Fix: none

Threshold (documented constant in rules/performance/display_order.rs):

  • MLP209_STATIC_SUFFIX_GATE = N_moving_suffix >= 8 — at least 8 display-list members after the first mover must be provably static (no animation target, no foreground promotion, no family updater anywhere in a covering mover's family). Suffix members that are themselves moving re-rasterize regardless of order, so they never count toward the gate — a suffix that is entirely moving stays silent.

Quantification discipline (binding DESIGN 7.3 prose): the position and member count are reported only when the display order is Known and the moving-suffix bounds are exact. An unknown z_index (a non-literal set_z_index, an unknown mutation) poisons the order and silences the rule — no number is ever produced from an unknown order.

Wrong

class Demo(Scene):
    def construct(self):
        background = Square()
        panel = VGroup(Dot(), Dot(), Dot(), Dot(), Dot(), Dot(), Dot(), Dot())
        self.add(background, panel)
        background.add_updater(drift)   # front member now moves
        self.wait(2)                    # panel re-rasterized every frame
class Demo(Scene):
    def construct(self):
        panel = VGroup(Dot(), Dot(), Dot(), Dot(), Dot(), Dot(), Dot(), Dot())
        background = Square()
        self.add(panel, background)     # dynamic object drawn last
        background.add_updater(drift)
        self.wait(2)                    # static panel rasterized once

If the background must draw first, background.set_z_index(-1) with the add order reversed keeps the stacking while the static run stays together — verify the visual stacking, since the display order is part of the picture.