Make the wrong number impossible, not unlikely
When a machine writes teaching material, the failure that matters is a confident wrong number in a caption. Do not defend against it with review. Defend against it with structure.
September 14, 2026 · From building tirimba
I built a pipeline that produces short explainer videos for a graduate finance course: animated payoff diagrams, synthesized narration, synchronized captions. One command, ten minutes, a finished MP4. The first one explains put-call parity in two minutes and forty-three seconds.
The interesting problem was not the animation. It was this: when a machine writes teaching material, the failure that matters is a confident wrong number in a caption. A student will believe it. A reviewer who has watched the same video nine times will stop seeing it.
The usual defense is review. Review does not scale, and a review that happens after the number is on screen is already too late in the pipeline to be reliable. So the rule I set was that the wrong number should be impossible, not unlikely.
One module owns every number
A single file, values.py, computes every value that can reach the screen from the inputs: the present value of the strike, the price of the put, both portfolios at two probe prices. It asserts the expected results to three decimals at import time. Then it formats the display strings.
The scene file, the one that draws the animation, contains no numeric literal that can appear on screen. It imports display strings from the values module. There is nowhere else a number could have come from.
The consequence is that a corrupted input stops the build before anything renders. I tested this by changing the call premium from 1.00 to 1.10. The assertion failed on import of the audio builder and again on import of the scene, before the text-to-speech model or the renderer ran. That was an acceptance criterion, tested and reverted, not assumed.
The same move for time
Do not type an animation duration. Every narration segment is synthesized, then measured with ffprobe, and the durations are written to a file. Every animation’s run time is a fraction of the measured segment it belongs to. Sync cannot drift, because there is no second clock to drift against.
The build fails if the total runtime falls outside the window the brief set. A video that is too long is a bug, not a note for the editor.
And for words
The narration is locked in one file. It is never edited by hand during a build. If a line synthesizes badly, the build stops and says so, and the fix is in the text-to-speech configuration. If the runtime is wrong, the speaking rate changes, not the script.
The thing a reviewer approved is the thing that ships. That sentence is the whole principle, applied three times.
Where it held up under pressure
The principle earned its keep in a place the brief did not mention: axis tick labels. The first draft of the scene had x_range=[0, 40, 10] written directly in the code. That is a number on screen. It went into the values module as multiples of the strike price. When a later request came to tighten the axis range, it was a one-constant change and the tick labels followed. Under the old draft it would have been a hand edit in a file that was not supposed to contain numbers.
The test for whether a literal belongs in the scene file is simple: could a viewer read it? Positions, font sizes and animation fractions are layout and can live in the scene. A course code on a title card is a number and cannot.
Why this matters beyond one video
Language models have the same failure with the same defense. In a separate project I have a parser compute the facts about a class session before the model reads anything, and the prompt tells the model those numbers were measured and must not be recomputed. Same split, different medium: the machine that is good at fluency should not be the machine that is responsible for arithmetic.
Most of the AI reliability conversation I hear is about review, guardrails and human-in-the-loop. Those are fine. But a human in the loop is a person who gets tired, and a guardrail is a filter that can be talked around. Structure is neither. If the wrong number has no path to the screen, you do not need to catch it.
Questions about this one? Ask the site, or emailgreg@corporateprofessor.com.