Manim Quality Testkit Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Testkit that validates manim diagram geometry (overlaps, crossings, clipping, near-parallel lines) at the scene-graph level, without rendering.

Architecture: Extractor builds ParsedScene from manim mobjects → 4 check functions → pytest harness with must-fail fixtures → Dagger CI + pre-commit integration.

Tech Stack: manim, shapely, pytest


Task 1: Data model and scene extractor

Files:

  • Create: docs/diagrams/manim_quality.py

Step 1: Write the failing test

Create docs/diagrams/test_manim_quality.py with a single test that imports and calls the extractor:

# /// script
# requires-python = ">=3.10"
# dependencies = ["manim", "shapely", "pytest"]
# ///
import numpy as np
from manim import Arrow, Scene, Text
 
from manim_quality import extract_scene, ParsedScene
 
 
class SimpleTestScene(Scene):
    def construct(self):
        self.add(Text("A", font_size=28).move_to(np.array([1.0, 2.0, 0.0])))
        self.add(Arrow(np.array([0, 0, 0.0]), np.array([2, 1, 0.0]), buff=0))
 
 
def test_extract_scene_returns_parsed_scene():
    result = extract_scene(SimpleTestScene)
    assert isinstance(result, ParsedScene)
    assert result.scene_name == "SimpleTestScene"
    assert len(result.labels) == 1
    assert result.labels[0].name == "A"
    assert abs(result.labels[0].center.x - 1.0) < 0.01
    assert len(result.arrows) == 1
    assert abs(result.arrows[0].start.x) < 0.01
    assert abs(result.arrows[0].end.x - 2.0) < 0.01
    assert result.frame_width > 14
    assert result.frame_height > 7

Step 2: Run test to verify it fails

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::test_extract_scene_returns_parsed_scene -v Expected: FAIL with ModuleNotFoundError: No module named 'manim_quality'

Step 3: Write minimal implementation

Create docs/diagrams/manim_quality.py:

# /// script
# requires-python = ">=3.10"
# dependencies = ["manim", "shapely"]
# ///
"""Manim diagram quality checks — scene-graph level validation.
 
Extracts exact geometry from manim mobjects after construct() and runs
checks for overlapping labels, arrows crossing text, elements outside
viewport, and near-parallel lines.
"""
from __future__ import annotations
 
import math
from dataclasses import dataclass
 
from manim import (
    Arrow,
    DashedLine,
    Line,
    MathTex,
    Polygon,
    Scene,
    Text,
    config,
)
from manim.constants import DOWN, LEFT, RIGHT, UP
from shapely.geometry import LineString, box as shapely_box
 
 
@dataclass
class Point:
    x: float
    y: float
 
 
@dataclass
class BBox:
    x_min: float
    y_min: float
    x_max: float
    y_max: float
 
    def overlaps(self, other: BBox, *, padding: float = 0.0) -> bool:
        return (
            self.x_min - padding < other.x_max + padding
            and self.x_max + padding > other.x_min - padding
            and self.y_min - padding < other.y_max + padding
            and self.y_max + padding > other.y_min - padding
        )
 
    def to_shapely(self, *, padding: float = 0.0):
        return shapely_box(
            self.x_min - padding,
            self.y_min - padding,
            self.x_max + padding,
            self.y_max + padding,
        )
 
 
@dataclass
class Label:
    name: str
    bbox: BBox
    center: Point
 
 
@dataclass
class ArrowSegment:
    name: str
    start: Point
    end: Point
 
 
@dataclass
class LineSegment:
    name: str
    start: Point
    end: Point
 
 
@dataclass
class ParsedScene:
    scene_name: str
    labels: list[Label]
    arrows: list[ArrowSegment]
    lines: list[LineSegment]
    frame_width: float
    frame_height: float
 
 
def extract_scene(scene_class: type[Scene]) -> ParsedScene:
    scene = scene_class()
    scene.construct()
 
    labels: list[Label] = []
    arrows: list[ArrowSegment] = []
    lines: list[LineSegment] = []
 
    arrow_idx = 0
    line_idx = 0
 
    def walk(mobject):
        nonlocal arrow_idx, line_idx
 
        if isinstance(mobject, (Text, MathTex)):
            ul = mobject.get_corner(UP + LEFT)
            dr = mobject.get_corner(DOWN + RIGHT)
            center = mobject.get_center()
            # Text content: .text for Text, tex_string for MathTex
            if isinstance(mobject, Text):
                name = mobject.text
            else:
                name = mobject.tex_string
            labels.append(Label(
                name=name,
                bbox=BBox(
                    x_min=float(ul[0]),
                    y_min=float(dr[1]),
                    x_max=float(dr[0]),
                    y_max=float(ul[1]),
                ),
                center=Point(x=float(center[0]), y=float(center[1])),
            ))
            return  # Text/MathTex don't have meaningful children
 
        if isinstance(mobject, Arrow):
            start = mobject.get_start()
            end = mobject.get_end()
            arrows.append(ArrowSegment(
                name=f"arrow_{arrow_idx}",
                start=Point(x=float(start[0]), y=float(start[1])),
                end=Point(x=float(end[0]), y=float(end[1])),
            ))
            arrow_idx += 1
            return  # Don't recurse into Arrow internals
 
        if isinstance(mobject, (Line, DashedLine)):
            start = mobject.get_start()
            end = mobject.get_end()
            lines.append(LineSegment(
                name=f"line_{line_idx}",
                start=Point(x=float(start[0]), y=float(start[1])),
                end=Point(x=float(end[0]), y=float(end[1])),
            ))
            line_idx += 1
            return
 
        if isinstance(mobject, Polygon):
            vertices = mobject.get_vertices()
            for i in range(len(vertices)):
                v1 = vertices[i]
                v2 = vertices[(i + 1) % len(vertices)]
                lines.append(LineSegment(
                    name=f"polygon_edge_{line_idx}",
                    start=Point(x=float(v1[0]), y=float(v1[1])),
                    end=Point(x=float(v2[0]), y=float(v2[1])),
                ))
                line_idx += 1
            return
 
        # Recurse into container mobjects (VGroup, etc.)
        for child in mobject.submobjects:
            walk(child)
 
    for mobject in scene.mobjects:
        walk(mobject)
 
    return ParsedScene(
        scene_name=scene_class.__name__,
        labels=labels,
        arrows=arrows,
        lines=lines,
        frame_width=float(config.frame_width),
        frame_height=float(config.frame_height),
    )

Step 4: Run test to verify it passes

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::test_extract_scene_returns_parsed_scene -v Expected: PASS

Step 5: Commit

git add docs/diagrams/manim_quality.py docs/diagrams/test_manim_quality.py
git commit -m "feat: add manim scene-graph extractor and data model"

Task 2: check_label_overlaps

Files:

  • Modify: docs/diagrams/manim_quality.py
  • Modify: docs/diagrams/test_manim_quality.py

Step 1: Write the failing test

Add to test_manim_quality.py:

from manim_quality import check_label_overlaps
 
 
class OverlappingLabelsScene(Scene):
    """Two labels at the same position — must trigger overlap check."""
    def construct(self):
        self.add(Text("Hello", font_size=36).move_to(np.array([0.0, 0.0, 0.0])))
        self.add(Text("World", font_size=36).move_to(np.array([0.0, 0.0, 0.0])))
 
 
class SeparatedLabelsScene(Scene):
    """Two labels far apart — must NOT trigger overlap check."""
    def construct(self):
        self.add(Text("Left", font_size=28).move_to(np.array([-5.0, 0.0, 0.0])))
        self.add(Text("Right", font_size=28).move_to(np.array([5.0, 0.0, 0.0])))
 
 
def test_check_label_overlaps_detects_overlapping():
    scene = extract_scene(OverlappingLabelsScene)
    errors = check_label_overlaps(scene)
    assert len(errors) >= 1
    assert "Hello" in errors[0] and "World" in errors[0]
 
 
def test_check_label_overlaps_passes_separated():
    scene = extract_scene(SeparatedLabelsScene)
    errors = check_label_overlaps(scene)
    assert len(errors) == 0

Step 2: Run test to verify it fails

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::test_check_label_overlaps_detects_overlapping -v Expected: FAIL with ImportError: cannot import name 'check_label_overlaps'

Step 3: Write minimal implementation

Add to manim_quality.py:

def check_label_overlaps(
    scene: ParsedScene,
    *,
    padding: float = 0.05,
) -> list[str]:
    errors: list[str] = []
    for i, a in enumerate(scene.labels):
        for b in scene.labels[i + 1:]:
            if a.bbox.overlaps(b.bbox, padding=padding):
                errors.append(
                    f"Labels overlap: '{a.name}' vs '{b.name}'"
                )
    return errors

Step 4: Run tests to verify they pass

Run: cd docs/diagrams && uv run pytest test_manim_quality.py -k "label_overlaps" -v Expected: 2 PASS

Step 5: Commit

git add docs/diagrams/manim_quality.py docs/diagrams/test_manim_quality.py
git commit -m "feat: add check_label_overlaps to manim quality testkit"

Task 3: check_arrow_crosses_label

Files:

  • Modify: docs/diagrams/manim_quality.py
  • Modify: docs/diagrams/test_manim_quality.py

Step 1: Write the failing test

Add to test_manim_quality.py:

from manim_quality import check_arrow_crosses_label
 
 
class ArrowCrossesLabelScene(Scene):
    """Arrow passes directly through a text label."""
    def construct(self):
        self.add(Text("Target", font_size=36).move_to(np.array([1.0, 0.5, 0.0])))
        self.add(Arrow(np.array([0, 0, 0.0]), np.array([2, 1, 0.0]), buff=0))
 
 
class ArrowMissesLabelScene(Scene):
    """Arrow and label are far apart."""
    def construct(self):
        self.add(Text("Safe", font_size=28).move_to(np.array([0.0, 3.0, 0.0])))
        self.add(Arrow(np.array([0, -2, 0.0]), np.array([2, -2, 0.0]), buff=0))
 
 
def test_check_arrow_crosses_label_detects_crossing():
    scene = extract_scene(ArrowCrossesLabelScene)
    errors = check_arrow_crosses_label(scene)
    assert len(errors) >= 1
    assert "Target" in errors[0]
 
 
def test_check_arrow_crosses_label_passes_separated():
    scene = extract_scene(ArrowMissesLabelScene)
    errors = check_arrow_crosses_label(scene)
    assert len(errors) == 0

Step 2: Run test to verify it fails

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::test_check_arrow_crosses_label_detects_crossing -v Expected: FAIL with ImportError

Step 3: Write minimal implementation

Add to manim_quality.py:

def check_arrow_crosses_label(
    scene: ParsedScene,
    *,
    padding: float = 0.08,
) -> list[str]:
    errors: list[str] = []
    for arrow in scene.arrows:
        arrow_line = LineString([
            (arrow.start.x, arrow.start.y),
            (arrow.end.x, arrow.end.y),
        ])
        for label in scene.labels:
            label_poly = label.bbox.to_shapely(padding=padding)
            if arrow_line.intersects(label_poly):
                errors.append(
                    f"Arrow {arrow.name} crosses label '{label.name}'"
                )
    return errors

Step 4: Run tests to verify they pass

Run: cd docs/diagrams && uv run pytest test_manim_quality.py -k "arrow_crosses" -v Expected: 2 PASS

Step 5: Commit

git add docs/diagrams/manim_quality.py docs/diagrams/test_manim_quality.py
git commit -m "feat: add check_arrow_crosses_label to manim quality testkit"

Task 4: check_outside_frame

Files:

  • Modify: docs/diagrams/manim_quality.py
  • Modify: docs/diagrams/test_manim_quality.py

Step 1: Write the failing test

Add to test_manim_quality.py:

from manim_quality import check_outside_frame
 
 
class OutOfFrameScene(Scene):
    """Label placed well beyond the visible frame."""
    def construct(self):
        self.add(Text("Lost", font_size=28).move_to(np.array([20.0, 0.0, 0.0])))
 
 
class InsideFrameScene(Scene):
    """Everything comfortably inside the frame."""
    def construct(self):
        self.add(Text("Safe", font_size=28).move_to(np.array([0.0, 0.0, 0.0])))
        self.add(Arrow(np.array([-2, 0, 0.0]), np.array([2, 0, 0.0]), buff=0))
 
 
def test_check_outside_frame_detects_clipping():
    scene = extract_scene(OutOfFrameScene)
    errors = check_outside_frame(scene)
    assert len(errors) >= 1
    assert "Lost" in errors[0]
 
 
def test_check_outside_frame_passes_inside():
    scene = extract_scene(InsideFrameScene)
    errors = check_outside_frame(scene)
    assert len(errors) == 0

Step 2: Run test to verify it fails

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::test_check_outside_frame_detects_clipping -v Expected: FAIL with ImportError

Step 3: Write minimal implementation

Add to manim_quality.py:

def check_outside_frame(
    scene: ParsedScene,
    *,
    margin: float = 0.1,
) -> list[str]:
    errors: list[str] = []
    hw = scene.frame_width / 2 - margin
    hh = scene.frame_height / 2 - margin
 
    for label in scene.labels:
        bb = label.bbox
        if bb.x_min < -hw or bb.x_max > hw or bb.y_min < -hh or bb.y_max > hh:
            errors.append(
                f"Label '{label.name}' extends outside frame"
            )
 
    for arrow in scene.arrows:
        for pt_name, pt in [("start", arrow.start), ("end", arrow.end)]:
            if abs(pt.x) > hw or abs(pt.y) > hh:
                errors.append(
                    f"Arrow {arrow.name} {pt_name} outside frame "
                    f"({pt.x:.2f}, {pt.y:.2f})"
                )
 
    return errors

Step 4: Run tests to verify they pass

Run: cd docs/diagrams && uv run pytest test_manim_quality.py -k "outside_frame" -v Expected: 2 PASS

Step 5: Commit

git add docs/diagrams/manim_quality.py docs/diagrams/test_manim_quality.py
git commit -m "feat: add check_outside_frame to manim quality testkit"

Task 5: check_line_near_parallel

Files:

  • Modify: docs/diagrams/manim_quality.py
  • Modify: docs/diagrams/test_manim_quality.py

Step 1: Write the failing test

Add to test_manim_quality.py:

from manim import DashedLine
from manim_quality import check_line_near_parallel
 
 
class ParallelLinesScene(Scene):
    """Two lines almost on top of each other — visually indistinguishable."""
    def construct(self):
        self.add(Line(np.array([0, 0, 0.0]), np.array([3, 0, 0.0])))
        self.add(Arrow(np.array([0, 0.05, 0.0]), np.array([3, 0.05, 0.0]), buff=0))
 
 
class PerpendicularLinesScene(Scene):
    """Two lines at 90 degrees — no parallelism issue."""
    def construct(self):
        self.add(Line(np.array([0, 0, 0.0]), np.array([3, 0, 0.0])))
        self.add(Arrow(np.array([1.5, -1, 0.0]), np.array([1.5, 1, 0.0]), buff=0))
 
 
def test_check_line_near_parallel_detects_parallel():
    scene = extract_scene(ParallelLinesScene)
    errors = check_line_near_parallel(scene)
    assert len(errors) >= 1
 
 
def test_check_line_near_parallel_passes_perpendicular():
    scene = extract_scene(PerpendicularLinesScene)
    errors = check_line_near_parallel(scene)
    assert len(errors) == 0

Step 2: Run test to verify it fails

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::test_check_line_near_parallel_detects_parallel -v Expected: FAIL with ImportError

Step 3: Write minimal implementation

Add to manim_quality.py:

def _midpoint(start: Point, end: Point) -> Point:
    return Point(x=(start.x + end.x) / 2, y=(start.y + end.y) / 2)
 
 
def _direction_angle(start: Point, end: Point) -> float:
    """Angle in degrees, normalized to [0, 180)."""
    dx = end.x - start.x
    dy = end.y - start.y
    angle = math.degrees(math.atan2(dy, dx)) % 180
    return angle
 
 
def _point_to_line_distance(
    pt: Point,
    line_start: Point,
    line_end: Point,
) -> float:
    """Perpendicular distance from pt to the infinite line through start→end."""
    dx = line_end.x - line_start.x
    dy = line_end.y - line_start.y
    length = math.hypot(dx, dy)
    if length < 1e-9:
        return math.hypot(pt.x - line_start.x, pt.y - line_start.y)
    return abs(dy * pt.x - dx * pt.y + line_end.x * line_start.y - line_end.y * line_start.x) / length
 
 
def check_line_near_parallel(
    scene: ParsedScene,
    *,
    distance: float = 0.15,
    angle_degrees: float = 10.0,
) -> list[str]:
    errors: list[str] = []
    all_segments: list[tuple[str, Point, Point]] = []
    for arrow in scene.arrows:
        all_segments.append((arrow.name, arrow.start, arrow.end))
    for line in scene.lines:
        all_segments.append((line.name, line.start, line.end))
 
    for i, (name_a, start_a, end_a) in enumerate(all_segments):
        angle_a = _direction_angle(start_a, end_a)
        mid_a = _midpoint(start_a, end_a)
        for name_b, start_b, end_b in all_segments[i + 1:]:
            angle_b = _direction_angle(start_b, end_b)
            angle_diff = abs(angle_a - angle_b)
            if angle_diff > 90:
                angle_diff = 180 - angle_diff
            if angle_diff > angle_degrees:
                continue
            dist = _point_to_line_distance(mid_a, start_b, end_b)
            if dist < distance:
                errors.append(
                    f"Near-parallel: {name_a} and {name_b} "
                    f"(distance={dist:.3f}, angle_diff={angle_diff:.1f}°)"
                )
    return errors

Step 4: Run tests to verify they pass

Run: cd docs/diagrams && uv run pytest test_manim_quality.py -k "near_parallel" -v Expected: 2 PASS

Step 5: Commit

git add docs/diagrams/manim_quality.py docs/diagrams/test_manim_quality.py
git commit -m "feat: add check_line_near_parallel to manim quality testkit"

Task 6: run_all_checks aggregator and TestFixturesMustFail

Files:

  • Modify: docs/diagrams/manim_quality.py
  • Modify: docs/diagrams/test_manim_quality.py

Step 1: Write the failing test

Add to test_manim_quality.py:

import pytest
from manim_quality import run_all_checks
 
BAD_SCENES = [
    OverlappingLabelsScene,
    ArrowCrossesLabelScene,
    OutOfFrameScene,
    ParallelLinesScene,
]
 
 
class TestFixturesMustFail:
    @pytest.fixture(params=BAD_SCENES, ids=lambda s: s.__name__)
    def bad_scene(self, request):
        return extract_scene(request.param)
 
    def test_fixture_fails_at_least_one_check(self, bad_scene):
        errors = run_all_checks(bad_scene)
        assert errors, (
            f"{bad_scene.scene_name} passed ALL checks — "
            f"the checks are too lenient"
        )

Step 2: Run test to verify it fails

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::TestFixturesMustFail -v Expected: FAIL with ImportError: cannot import name 'run_all_checks'

Step 3: Write minimal implementation

Add to manim_quality.py:

def run_all_checks(scene: ParsedScene) -> list[str]:
    errors: list[str] = []
    errors.extend(check_label_overlaps(scene))
    errors.extend(check_arrow_crosses_label(scene))
    errors.extend(check_outside_frame(scene))
    errors.extend(check_line_near_parallel(scene))
    return errors

Step 4: Run tests to verify they pass

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::TestFixturesMustFail -v Expected: 4 PASS (one per bad scene)

Step 5: Commit

git add docs/diagrams/manim_quality.py docs/diagrams/test_manim_quality.py
git commit -m "feat: add run_all_checks and TestFixturesMustFail"

Task 7: Parametrized tests for real diagram scenes

Files:

  • Modify: docs/diagrams/test_manim_quality.py

Important: Real scenes use MathTex which requires LaTeX. The tests must skip gracefully when LaTeX is unavailable.

Step 1: Write the tests

Add to test_manim_quality.py:

import shutil
 
# Import the real scenes — this import works even without LaTeX
from multivariable_calculus_manim import (  # uses underscore for import
    CoordinateFreeCentroid,
    ArcLengthComparison,
    GradientPerpendicularToLevelCurves,
    CriticalPointTypes,
    LagrangeTangentLevelCurves,
)
 
HAS_LATEX = shutil.which("latex") is not None
 
REAL_SCENES = [
    CoordinateFreeCentroid,
    ArcLengthComparison,
    GradientPerpendicularToLevelCurves,
    CriticalPointTypes,
    LagrangeTangentLevelCurves,
]
 
 
@pytest.fixture(params=REAL_SCENES, ids=lambda s: s.__name__)
def real_scene(request):
    if not HAS_LATEX:
        pytest.skip("LaTeX not available — MathTex scenes need latex binary")
    return extract_scene(request.param)
 
 
class TestDiagramQuality:
    def test_no_label_overlaps(self, real_scene):
        errors = check_label_overlaps(real_scene)
        assert not errors, "\n".join(errors)
 
    def test_no_arrow_crosses_label(self, real_scene):
        errors = check_arrow_crosses_label(real_scene)
        assert not errors, "\n".join(errors)
 
    def test_nothing_outside_frame(self, real_scene):
        errors = check_outside_frame(real_scene)
        assert not errors, "\n".join(errors)
 
    def test_no_near_parallel_lines(self, real_scene):
        errors = check_line_near_parallel(real_scene)
        assert not errors, "\n".join(errors)

Note on imports: The manim script is named multivariable-calculus-manim.py (with hyphens). Python can’t import filenames with hyphens. Two options:

  • Rename to multivariable_calculus_manim.py (breaking change for existing references)
  • Use importlib to load it

The plan uses importlib to avoid renaming:

import importlib.util
 
def _import_manim_scenes():
    spec = importlib.util.spec_from_file_location(
        "manim_scenes",
        Path(__file__).parent / "multivariable-calculus-manim.py",
    )
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod
 
_scenes_mod = _import_manim_scenes()
CoordinateFreeCentroid = _scenes_mod.CoordinateFreeCentroid
# ... etc

Step 2: Run tests

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::TestDiagramQuality -v Expected: If LaTeX available, tests run (some may FAIL on current diagrams — that’s expected, the centroid diagram has known overlaps). If no LaTeX, all 20 tests SKIP.

Step 3: Commit

git add docs/diagrams/test_manim_quality.py
git commit -m "feat: add parametrized quality tests for real diagram scenes"

Task 8: Update Dagger CI to run quality checks first

Files:

  • Modify: dagger/main.go — update validateDiagrams function

Step 1: Modify the Dagger function

The validateDiagrams function currently only does pixel-diff. Add a quality-check step before rendering. Update the pip install line to include shapely and pytest. Add a pytest exec before the render-and-compare exec:

In dagger/main.go, change the pip install line from:

WithExec([]string{"pip", "install", "manim"}).

to:

WithExec([]string{"pip", "install", "manim", "shapely", "pytest"}).

And insert a new WithExec call before the render bash script:

ctr = ctr.WithExec([]string{
    "python", "-m", "pytest",
    "docs/diagrams/test_manim_quality.py", "-v",
    "--tb=short",
})

Step 2: Verify Go compiles

Run: cd dagger && go build -o /dev/null . Expected: clean compile

Step 3: Commit

git add dagger/main.go
git commit -m "ci: run manim quality checks before pixel-diff in validate-diagrams"

Task 9: Fix the centroid diagram coordinates

Files:

  • Modify: docs/diagrams/multivariable-calculus-manim.pyCoordinateFreeCentroid class

Context: The current diagram has known quality issues (overlapping labels, arrow running along edge). After the testkit is in place, fix the coordinates so the tests pass.

Step 1: Run quality tests to identify failures

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::TestDiagramQuality -v (requires LaTeX)

Step 2: Fix coordinates in CoordinateFreeCentroid

Key changes needed:

  • Move origin O further out (e.g. [-5.5, -3.0, 0.0]) so position vectors fan out without overlapping triangle edges
  • Widen the triangle asymmetrically (e.g. a=[-3.5, -1.5, 0], b=[4.0, -1.5, 0], c=[0.5, 2.5, 0]) so medians and labels have room
  • Adjust label offsets based on actual geometry rather than hardcoded nudges
  • Place centroid label further from midpoint M

Step 3: Run quality tests to verify fixes

Run: cd docs/diagrams && uv run pytest test_manim_quality.py -k CoordinateFreeCentroid -v Expected: PASS on all 4 checks

Step 4: Re-render the PNG (requires LaTeX)

Run: cd docs/diagrams && uv run python multivariable-calculus-manim.py or render individually via manim CLI.

Step 5: Commit

git add docs/diagrams/multivariable-calculus-manim.py "2 - Areas/Math, Geometry, Engineering, Physics/Math/Calculus/diagrams/coordinate-free-centroid.png"
git commit -m "fix: redesign centroid diagram coordinates to pass quality checks"

Task 10: Fix remaining 4 diagram scenes

Files:

  • Modify: docs/diagrams/multivariable-calculus-manim.py

Step 1: Run quality tests on all scenes

Run: cd docs/diagrams && uv run pytest test_manim_quality.py::TestDiagramQuality -v

Step 2: Fix any failing scenes

For each scene that fails, adjust coordinates and label positions until checks pass. Run tests between each change.

Step 3: Re-render all PNGs

Run the manim render script to regenerate all 5 PNGs.

Step 4: Commit

git add docs/diagrams/multivariable-calculus-manim.py "2 - Areas/Math, Geometry, Engineering, Physics/Math/Calculus/diagrams/"
git commit -m "fix: adjust all diagram coordinates to pass quality checks"

Task 11: Run full test suite and verify CI

Step 1: Run all tests locally

Run: cd docs/diagrams && uv run pytest test_manim_quality.py -v Expected: All tests PASS (must-fail fixtures fail correctly, real scenes pass quality checks, or skip if no LaTeX)

Step 2: Run Dagger validate-diagrams locally

Run: cd dagger && go run . validate-diagrams (requires Docker + LaTeX in container) Expected: Quality checks pass, then pixel-diff passes

Step 3: Commit any final adjustments and push

git push gitlab docs/multivariable-calculus-foundations