MLD304: Renderer-divergent effect reached without a guard in a multi-renderer run¶
qual reports MLD304 only when the run actually targets more
than one renderer (--profile all with both a Cairo and an OpenGL
profile) and the analyzer proves that a renderer-divergent effect is
reached on every path.
- Default severity:
warning - Minimum confidence:
medium - Implementation phase:
4 - Fix: none
Two divergence families are implemented.
Fixed-object removal (DESIGN 3.5)¶
ThreeDScene.remove_fixed_in_frame_mobjects /
remove_fixed_orientation_mobjects only unregister the camera fix under
Cairo, but the OpenGL branch additionally removes the object from the
Scene. Rendering the same scene with both renderers therefore produces
different membership โ the object stays visible under Cairo and vanishes
under OpenGL. The diagnostic's evidence names the affected registry
(fixed_in_frame / fixed_orientation) and points back at the certain
registration site of the removed object.
Wrong (with --profile all over cairo + opengl profiles):
self.add_fixed_in_frame_mobjects(label)
self.remove_fixed_in_frame_mobjects(label) # visible on cairo, gone on opengl
self.wait()
Right:
self.add_fixed_in_frame_mobjects(label)
self.remove_fixed_in_frame_mobjects(label)
self.remove(label) # pin the membership explicitly
MovingCameraScene camera-frame semantics¶
Under Cairo, self.camera on a MovingCameraScene is a MovingCamera
whose frame is an animatable mobject (and auto_zoom exists). The
OpenGL renderer ignores camera_class and installs an OpenGLCamera
without that contract (scene.py Scene.__init__,
moving_camera_scene.py), so an unguarded self.camera.frame /
self.camera.auto_zoom path diverges between the targeted renderers.
The rule fires only when the scene's camera contract is proven
MovingCamera (fully resolved bases) and the access sits on the
straight-line prefix of construct โ reached on every render.
Wrong (with --profile all over cairo + opengl profiles):
class Tour(MovingCameraScene):
def construct(self):
square = Square()
self.add(square)
self.play(self.camera.frame.animate.move_to(square)) # cairo-only contract
Right:
class Tour(MovingCameraScene):
def construct(self):
square = Square()
self.add(square)
if config.renderer == "cairo": # guard the cairo-only path
self.play(self.camera.frame.animate.move_to(square))
(or restrict the scene to a Cairo-only profile so the run is single-renderer.)
Silent by design¶
- single-renderer runs โ a project that explicitly targets one renderer is never asked for renderer guards (DESIGN 7.4 prose);
- any fixed-removal call behind a branch: the event certainty joins to
Maybe, so a renderer guard (or any other condition) silences the rule; - camera-frame uses behind a branch, loop,
try,with, earlyreturn, lambda, comprehension, conditional-expression branch, or in a helper method: not an all-paths use; - a
MovingCameraScenesubclass that never touches the camera frame: subclassing alone is not a divergence certainty; - mixed or unresolved base chains: the camera kind stays
Unknownand is never guessed.