manim teaching assets, semantic chapters, and chapter preview tooling - #146
manim teaching assets, semantic chapters, and chapter preview tooling#146DonIsmaelito wants to merge 15 commits into
Conversation
There was a problem hiding this comment.
37 issues found across 47 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="skills/manim-video/references/mobjects.md">
<violation number="1" location="skills/manim-video/references/mobjects.md:110">
P2: This example references `label`, but no `label` variable is defined in the snippet — `labeled_shape` is the only variable holding the mixed Text+shape group. An agent that copies this example gets a NameError at runtime. Use the already-defined `labeled_shape`: `self.play(FadeOut(labeled_shape))` or `self.play(FadeOut(Group(labeled_shape)))`.</violation>
</file>
<file name="skills/manim-video/assets/teaching.py">
<violation number="1" location="skills/manim-video/assets/teaching.py:319">
P2: When a shorthand binding is cleared while a later binding remains, `len(self._bindings)` reuses the later binding’s name and registration fails. Generate shorthand IDs from a monotonic counter or skip names already present in `_bindings`.</violation>
<violation number="2" location="skills/manim-video/assets/teaching.py:508">
P2: When `target` is a child of a scene root, `_unrelated_roots` excludes that entire root because it shares the target’s family, so sibling parts never receive the dim opacity. Compute unrelated descendants for child targets, or restrict targets to top-level scene objects.</violation>
<violation number="3" location="skills/manim-video/assets/teaching.py:601">
P2: When a semantic object is transformed into a plain `Mobject`, this condition skips semantic adoption but leaves the source registries pointing at the old representation. Clear those registries or explicitly rebind them for non-semantic targets.</violation>
</file>
<file name="skills/manim-video/assets/domains/_common.py">
<violation number="1" location="skills/manim-video/assets/domains/_common.py:111">
P2: When a caller passes labeled boxes or circle nodes, `arrow_between` starts and ends at their centers, so the default arrowhead is drawn inside the target and hidden. Compute boundary points for Mobject endpoints before constructing the arrow.</violation>
<violation number="2" location="skills/manim-video/assets/domains/_common.py:181">
P2: `ensure_index()` silently truncates fractional indices, so `1.9` operates on item 1. Require the input to equal its integer conversion before performing the bounds check.</violation>
<violation number="3" location="skills/manim-video/assets/domains/_common.py:188">
P2: `ensure_amount()` accepts NaN because both range comparisons are false, allowing transfer methods to corrupt source and target balances. Reject non-finite amounts before the range checks.</violation>
<violation number="4" location="skills/manim-video/assets/domains/_common.py:191">
P2: When a transfer exceeds the available amount by at most `1e-9`, this tolerance accepts it and leaves the source slightly negative. Clamp accepted near-equal values to `available` or reject every excess before returning.</violation>
<violation number="5" location="skills/manim-video/assets/domains/_common.py:211">
P2: When the two rows have different lengths, `two_row_flow` links only the common prefix after independently centering the rows, so links are diagonally misaligned and extra boxes are silently unlinked. Require equal row lengths or align both rows to shared columns.</violation>
</file>
<file name="install.md">
<violation number="1" location="install.md:135">
P2: On a clean install, this new verification command fails because the documented install does not install pytest and the uv environment is not activated. Install pytest and run it through the selected environment, such as `uv run`, before this check.</violation>
</file>
<file name="skills/manim-video/assets/domains/math.py">
<violation number="1" location="skills/manim-video/assets/domains/math.py:235">
P2: The constructor accepts an out-of-range initial `x_value` even though `set_x()` rejects one, creating an invalid point and guide before frame fitting. Validate the initial value against `self.x_range` before calling `axes.c2p()`.</violation>
<violation number="2" location="skills/manim-video/assets/domains/math.py:326">
P2: After a long `ProbabilityMass` has been frame-fitted, `transfer_probability()` expands the bars back to their unscaled layout and breaks the frame-safe guarantee. Scale and move the replacement to the current `bars` part before transforming.</violation>
</file>
<file name="skills/manim-video/assets/domains/finance.py">
<violation number="1" location="skills/manim-video/assets/domains/finance.py:55">
P2: Non-finite mapping values currently pass validation and can corrupt displayed values or generate invalid Manim geometry. Reject non-finite amounts and counts before constructing the shapes, and apply the same finite-value check to `CompoundTimeline`'s numeric inputs.</violation>
<violation number="2" location="skills/manim-video/assets/domains/finance.py:92">
P2: When a flow has enough entries to trigger the initial frame fit, each transfer replaces the scaled box group with an unscaled group. Reuse the current part scale for the replacement in both flow classes so boxes stay aligned with the scaled links and inside the frame.</violation>
<violation number="3" location="skills/manim-video/assets/domains/finance.py:241">
P2: With a dozen or more factors, the capped circle radius makes factor boxes overlap and labels unreadable. Scale nodes for larger counts or reject counts that cannot fit legibly instead of silently rendering an overlapping loop.</violation>
</file>
<file name="skills/manim-video/assets/domains/physics.py">
<violation number="1" location="skills/manim-video/assets/domains/physics.py:81">
P2: When `move_body` is played after connections or forces exist, only the body subgroup moves, leaving lines and force arrows detached. Update those dependent parts with the body movement so the system remains physically connected.</violation>
<violation number="2" location="skills/manim-video/assets/domains/physics.py:139">
P2: `set_vector()` moves the arrow but leaves its label at the old tip. Animate or reposition the registered label alongside the replacement arrow so the force annotation remains attached.</violation>
<violation number="3" location="skills/manim-video/assets/domains/physics.py:265">
P2: Repeated `flow()` calls shift charges beyond the finite top wire and never wrap them around the circuit. Keep each charge on a loop path or wrap its position when it crosses a wire endpoint.</violation>
</file>
<file name="skills/manim-video/scripts/preview_scene.py">
<violation number="1" location="skills/manim-video/scripts/preview_scene.py:64">
P2: When `--manim-bin` is a relative path, `require_executable()` returns it unchanged but `_run()` executes it from `script.parent`, so preview cannot find the requested binary. Return an absolute path before changing the subprocess working directory.</violation>
<violation number="2" location="skills/manim-video/scripts/preview_scene.py:294">
P2: When a project has two animation scripts with the same filename and chapter name, both previews share one artifact directory and overwrite each other. Key the directory by a stable project-relative script path or another collision-resistant script identifier.</violation>
<violation number="3" location="skills/manim-video/scripts/preview_scene.py:303">
P2: When a reused preview directory contains an older scene video and the current render does not replace it, `_find_scene_video()` reports the old video as the new preview. Clear the per-scene media directory before rendering, or require the selected file to be created by the current render.</violation>
<violation number="4" location="skills/manim-video/scripts/preview_scene.py:396">
P2: When preview output is unwritable or an extracted image is unreadable, `main()` lets the filesystem/Pillow exception escape as a traceback. Catch the relevant `OSError` failures at the CLI boundary or wrap them in `PreviewError` so users receive the promised actionable failure.</violation>
</file>
<file name="skills/manim-video/assets/domains/biology.py">
<violation number="1" location="skills/manim-video/assets/domains/biology.py:118">
P2: `SequenceProcess` exposes no semantic anchors, so callers cannot attach overlays or choreography to its DNA, RNA, or protein rows. Register at least one anchor for each row before frame fitting.</violation>
<violation number="2" location="skills/manim-video/assets/domains/biology.py:138">
P2: After `transcribe()` or `translate()`, the replacement row grows from the single placeholder created in `__init__`, but its corresponding label is never repositioned. The RNA or protein label therefore overlaps the new boxes; animate or rebuild the label with each replacement.</violation>
<violation number="3" location="skills/manim-video/assets/domains/biology.py:169">
P2: Non-finite population counts pass this validation, and a non-finite transfer amount can poison `self.counts` with `nan` or `inf`. Reject non-finite counts and amounts before mutating the population state.</violation>
<violation number="4" location="skills/manim-video/assets/domains/biology.py:206">
P2: When a caller shifts or scales `PopulationFlow` before `transfer()`, the fresh compartment target is at the origin and unit scale. The compartments therefore jump away from the links and caption; position and scale the replacement from the current compartment group before transforming.</violation>
</file>
<file name="skills/manim-video/SKILL.md">
<violation number="1" location="skills/manim-video/SKILL.md:186">
P1: The required easing imports do not exist in the referenced asset, so authors following this example cannot start a render. Add these helpers to `concept_explainer.py` or change the instruction to import an actually exported Manim rate function.</violation>
</file>
<file name="skills/manim-video/assets/concept_explainer.py">
<violation number="1" location="skills/manim-video/assets/concept_explainer.py:69">
P2: `fit_mobject()` raises `ZeroDivisionError` for zero-width or zero-height mobjects, including valid degenerate content passed to `safe_panel()`. Guard each dimension with a small positive denominator as `fit_to_frame()` does.</violation>
<violation number="2" location="skills/manim-video/assets/concept_explainer.py:154">
P2: When a caller passes a negative `caption_rail_fraction`, this guard accepts objects below the frame. Validate the fraction using the same 0-to-0.5 check as `content_frame()` before calculating `bottom`.</violation>
</file>
<file name="skills/manim-video/assets/domains/systems.py">
<violation number="1" location="skills/manim-video/assets/domains/systems.py:128">
P2: For six or more services, the fixed ellipse makes adjacent nodes overlap and `buff=0.82` consumes more than the entire distance between adjacent centers, producing malformed or invisible links. Scale the ellipse with the service count or compute a buffer that fits the actual node spacing.</violation>
<violation number="2" location="skills/manim-video/assets/domains/systems.py:215">
P2: For a queue large enough to trigger `frame_safe()`, enqueueing or dequeueing expands the fitted row because `_slots()` creates an unscaled transform target. Fit each replacement to the current `slots` dimensions before returning its transform.</violation>
</file>
<file name="skills/manim-video/assets/domains/computing.py">
<violation number="1" location="skills/manim-video/assets/domains/computing.py:50">
P2: At the documented 1920×1080 delivery floor, `ArrayModel` renders index labels at `font_size=16`. Raise these labels to at least 24 so array indices remain readable in production renders.</violation>
<violation number="2" location="skills/manim-video/assets/domains/computing.py:208">
P2: When a valid transition points right-to-left or loops to the same state, this hard-coded edge geometry draws the arrow through the state circles instead of along a transition. Route reverse and self-transitions with curved arrows or reject them explicitly.</violation>
</file>
<file name="tests/test_manim_domains.py">
<violation number="1" location="tests/test_manim_domains.py:34">
P2: This test can pass for a component that is entirely off-screen because it checks only size, not bounds. Assert the left, right, top, and bottom coordinates against the frame margins so the claimed frame-safety contract is actually enforced.</violation>
</file>
<file name="tests/test_manim_teaching.py">
<violation number="1" location="tests/test_manim_teaching.py:131">
P3: The linked-value test proves that the bar callback ran, not that the bar changed. Assert `bar.width` for each value so a callback that records the update but fails to update its representation is caught.</violation>
<violation number="2" location="tests/test_manim_teaching.py:161">
P2: This test does not verify the full ThreeD camera restoration promised by its name. Save and compare zoom, orientation, focal distance, and frame center so focus regressions cannot pass after leaving the camera zoomed or rotated.</violation>
</file>
<file name="skills/manim-video/references/concept-explainer.md">
<violation number="1" location="skills/manim-video/references/concept-explainer.md:193">
P2: The shared-frame rules list `cutaway` and `picture_in_picture` as valid `composition_regions()` modes, but `assets/concept_explainer.py` only implements `split_left` and `split_right`; any other mode raises `ValueError("unknown composition ...")`. An author following this contract gets a runtime error. Either implement the two missing modes in `composition_regions()` or document only the supported `split_left`/`split_right` (plus a collision-checked inset for the others).</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| - **Narration mapping** for every significant visual action; captions remain composed last through the existing EDL renderer | ||
| - **One shared `VisualTheme`** for semantic color and typography roles | ||
| - **Named objects and beats** through `remember()` and `begin_beat()` | ||
| - **Portable easing imports** — import `ease_out_cubic` and |
There was a problem hiding this comment.
P1: The required easing imports do not exist in the referenced asset, so authors following this example cannot start a render. Add these helpers to concept_explainer.py or change the instruction to import an actually exported Manim rate function.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/manim-video/SKILL.md, line 186:
<comment>The required easing imports do not exist in the referenced asset, so authors following this example cannot start a render. Add these helpers to `concept_explainer.py` or change the instruction to import an actually exported Manim rate function.</comment>
<file context>
@@ -108,84 +123,104 @@ project-name/
+- **Narration mapping** for every significant visual action; captions remain composed last through the existing EDL renderer
+- **One shared `VisualTheme`** for semantic color and typography roles
+- **Named objects and beats** through `remember()` and `begin_beat()`
+- **Portable easing imports** — import `ease_out_cubic` and
+ `ease_in_out_cubic` from the copied concept-explainer asset; do not assume
+ unqualified easing names are exported by `from manim import *`
</file context>
| value = ensure_amount(amount, available=self.counts[source_index]) | ||
| self.counts[source_index] -= value | ||
| self.counts[target_index] += value | ||
| return Transform(self.part("compartments"), self._compartments()) |
There was a problem hiding this comment.
P2: When a caller shifts or scales PopulationFlow before transfer(), the fresh compartment target is at the origin and unit scale. The compartments therefore jump away from the links and caption; position and scale the replacement from the current compartment group before transforming.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/manim-video/assets/domains/biology.py, line 206:
<comment>When a caller shifts or scales `PopulationFlow` before `transfer()`, the fresh compartment target is at the origin and unit scale. The compartments therefore jump away from the links and caption; position and scale the replacement from the current compartment group before transforming.</comment>
<file context>
@@ -0,0 +1,209 @@
+ value = ensure_amount(amount, available=self.counts[source_index])
+ self.counts[source_index] -= value
+ self.counts[target_index] += value
+ return Transform(self.part("compartments"), self._compartments())
+
+
</file context>
| # fill the rna row with the complement of each dna base | ||
| def transcribe(self) -> Transform: | ||
| self.rna = "".join(self._TRANSCRIPTION[base] for base in self.dna) | ||
| replacement = self._row(self.rna, color=self.theme.secondary).move_to(self.part("rna")) |
There was a problem hiding this comment.
P2: After transcribe() or translate(), the replacement row grows from the single placeholder created in __init__, but its corresponding label is never repositioned. The RNA or protein label therefore overlaps the new boxes; animate or rebuild the label with each replacement.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/manim-video/assets/domains/biology.py, line 138:
<comment>After `transcribe()` or `translate()`, the replacement row grows from the single placeholder created in `__init__`, but its corresponding label is never repositioned. The RNA or protein label therefore overlaps the new boxes; animate or rebuild the label with each replacement.</comment>
<file context>
@@ -0,0 +1,209 @@
+ # fill the rna row with the complement of each dna base
+ def transcribe(self) -> Transform:
+ self.rna = "".join(self._TRANSCRIPTION[base] for base in self.dna)
+ replacement = self._row(self.rna, color=self.theme.secondary).move_to(self.part("rna"))
+ return Transform(self.part("rna"), replacement)
+
</file context>
| value = float(amount) | ||
| if value < 0: | ||
| raise ValueError("amount cannot be negative") | ||
| if available is not None and value > available + 1e-9: |
There was a problem hiding this comment.
P2: When a transfer exceeds the available amount by at most 1e-9, this tolerance accepts it and leaves the source slightly negative. Clamp accepted near-equal values to available or reject every excess before returning.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/manim-video/assets/domains/_common.py, line 191:
<comment>When a transfer exceeds the available amount by at most `1e-9`, this tolerance accepts it and leaves the source slightly negative. Clamp accepted near-equal values to `available` or reject every excess before returning.</comment>
<file context>
@@ -0,0 +1,241 @@
+ value = float(amount)
+ if value < 0:
+ raise ValueError("amount cannot be negative")
+ if available is not None and value > available + 1e-9:
+ raise ValueError(f"amount {value} exceeds available value {available}")
+ return value
</file context>
| if not 0 <= int(index) < size: | ||
| raise IndexError(f"{name} {index} is outside 0..{size - 1}") |
There was a problem hiding this comment.
P2: ensure_index() silently truncates fractional indices, so 1.9 operates on item 1. Require the input to equal its integer conversion before performing the bounds check.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/manim-video/assets/domains/_common.py, line 181:
<comment>`ensure_index()` silently truncates fractional indices, so `1.9` operates on item 1. Require the input to equal its integer conversion before performing the bounds check.</comment>
<file context>
@@ -0,0 +1,241 @@
+
+# validate that an index falls inside zero to size minus one
+def ensure_index(index: int, size: int, *, name: str = "index") -> int:
+ if not 0 <= int(index) < size:
+ raise IndexError(f"{name} {index} is outside 0..{size - 1}")
+ return int(index)
</file context>
| if not 0 <= int(index) < size: | |
| raise IndexError(f"{name} {index} is outside 0..{size - 1}") | |
| value = int(index) | |
| if value != index or not 0 <= value < size: | |
| raise IndexError(f"{name} {index} is outside 0..{size - 1}") |
| color: str | None = None, | ||
| buff: float = 0.12, | ||
| ) -> Arrow: | ||
| start_point = start.get_center() if isinstance(start, Mobject) else np.asarray(start, dtype=float) |
There was a problem hiding this comment.
P2: When a caller passes labeled boxes or circle nodes, arrow_between starts and ends at their centers, so the default arrowhead is drawn inside the target and hidden. Compute boundary points for Mobject endpoints before constructing the arrow.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/manim-video/assets/domains/_common.py, line 111:
<comment>When a caller passes labeled boxes or circle nodes, `arrow_between` starts and ends at their centers, so the default arrowhead is drawn inside the target and hidden. Compute boundary points for Mobject endpoints before constructing the arrow.</comment>
<file context>
@@ -0,0 +1,241 @@
+ color: str | None = None,
+ buff: float = 0.12,
+) -> Arrow:
+ start_point = start.get_center() if isinstance(start, Mobject) else np.asarray(start, dtype=float)
+ end_point = end.get_center() if isinstance(end, Mobject) else np.asarray(end, dtype=float)
+ return Arrow(
</file context>
| for value in (0.0, 0.5, 1.0): | ||
| linked.set_value(value) | ||
| assert dot.get_center()[0] == pytest.approx(value) | ||
| assert {name for name, recorded in seen[-2:] if recorded == value} == {"dot", "bar"} |
There was a problem hiding this comment.
P3: The linked-value test proves that the bar callback ran, not that the bar changed. Assert bar.width for each value so a callback that records the update but fails to update its representation is caught.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_manim_teaching.py, line 131:
<comment>The linked-value test proves that the bar callback ran, not that the bar changed. Assert `bar.width` for each value so a callback that records the update but fails to update its representation is caught.</comment>
<file context>
@@ -0,0 +1,161 @@
+ for value in (0.0, 0.5, 1.0):
+ linked.set_value(value)
+ assert dot.get_center()[0] == pytest.approx(value)
+ assert {name for name, recorded in seen[-2:] if recorded == value} == {"dot", "bar"}
+
+
</file context>
…ames in the skill contract
363a09b to
9d5dc81
Compare
…ls in order and clear the footer margin
9d5dc81 to
4919783
Compare
Rewrites the Manim skill so the agent can build original explainer videos. Adds reusable diagram parts for six subject areas, a required planning file, measurable rules for legibility and pacing, and a preview script to check chapters before a full render.
Summary by cubic
Reworks the Manim skill from a prompt-to-animation workflow into a pipeline for original, narrated explainers. It now plans semantic chapters, preserves visual state across beats, and previews chapters before full renders.
TeachingScene,SemanticMobject,LinkedValue,VisualTheme, and reusable components for biology, computing, finance, math, physics, and systems.edit/visual_plan.md, independently renderable chapter classes, generated narration with word-timed captions by default, and measurable limits for scope, pacing, and label legibility.scripts/preview_scene.pyplus references for chapter planning, teaching APIs, rendering, and production quality.uv sync --extra animations; Manim-dependent tests skip when it is unavailable.Guardrails and validation
AGENTS.md, module-docstring and plain-comment checks, and a skill-contract test for hard rules and documented paths.Written for commit 4919783. Summary will update on new commits.