leonx.ai Don't trust, verify
← Home

中文 · English

To stop rewriting on gut feel every time, I boiled my taste into gates — and the one that matters most won't boil down

2026-07-21 · build log of an explainer-video engine

I built an explainer-video engine: feed it one concept, out comes a portrait explainer video — spine animation, narration, subtitles, a mascot called Doudou, the whole chain. Going in, I figured the hard part was "make it look good" or "make the prompt smart." Doing it for real, the hard part was something else: I always had this bit of an eye — I could tell at a glance a take was "not quite right," yet I couldn't say what was off, and I certainly couldn't get the machine to watch for it every time on my behalf.

This is an honest log of how, over the past couple of weeks, I moved that eye into code, one line at a time. A few of them I moved wrong, then moved back.

The robot boils a fuzzy cloud of 'taste' down into a hard 'gate'
Ladling in the "gut feel" you can't put into words and boiling it down into a hard gate that blocks anything failing it — that's half of what building this engine is. (Labels: 品味 = taste, 闸门 = gate.)

The first cut isn't the renderer, it's directing

My first version, I kept fussing over renderers drawing things crudely. Only later did it land: the real first cut is further upstream — what should this concept be drawn as, as "a thing that continuously deforms"? For dilution, I first reflexively drew a line sloping down — concentration drops over time, right? Deeply awkward. The real subject of dilution is "not a grain of salt was lost, it's the water that grew," and that's a container: the liquid rises, the salt grains thin out. Switched to the container spine and it clicked.

I call this layer directing: first pick the right spine by concept (container, particles, forces, ray optics, number line… some twenty of them), then talk rendering. Pick the wrong spine and no amount of fine rendering downstream saves it — it stays awkward, and it looks like every other clip. Stealing one line from 3b1b: make "the concept = a geometric quantity you can see," don't paper over it with a second metaphor.

So what if you can't decide which spine to use?

Fall back to the line chart. But whenever a dedicated spine fits, don't force the line chart — what you force out is exactly that "every clip looks the same" flavor.

Props, where I ate the potholes

For the opening I like a slice-of-life shot: boiling water, a piggy bank, a battery. The engine generates the SVG prop from a one-line description of mine. Plenty of potholes. I wrote "a glass cup heating on a stove," and it drew a glass cup perched over an open flame — off. I wrote "use a thermometer to show growth," and viewers have no clue why a thermometer stands for compound interest.

The rule I learned is dumb and simple: a prop gets one or two concrete nouns (piggy bank, kettle, battery) — no actions, no numbers, no abstractions dressed up as objects. Keep only what renders as an icon; what won't, better left out.

Taste drifts, and I didn't want to hold the line with my eyes every time

The most annoying thing is drift. This diagram I used this green, that one I reached for another green; this line 2px, that one 3px, all on whatever felt right at the moment. Each looks fine alone; strung together it's a fruit-salad look. I didn't want to keep holding that line with my eyes.

So I collected "tone" into one single source of truth, signature.js: eleven semantic roles (main stroke, misconception red, data green, accent yellow…), twenty-plus stroke widths collapsed into four tiers, three corner radii. Renderers may no longer write raw hex or hand-pick line widths — they reference roles, period. And a ratchet gate sits beside it: the count of colors and of line-width kinds can only go down, never up — today's cleanliness can't drift back tomorrow, the gate won't allow it.

stroke: { hair: 1, base: 2, emph: 3, heavy: 5 }   // 20+ widths collapsed to 4 tiers
accent: "#ffd166"  // accent · most-used today (73×) — anchor first, converge later

Motion, same idea. I first thought "not quite right" motion meant a missing library; after enough tries I found GSAP was long maxed out, and what was missing was easing and pacing. I nailed it into a "motion signature" layer: default ease-in-out (linear reads mechanical), actions finish cleanly then hold (don't smear one across a whole line of narration — that's the root of "painfully slow"), move duration scales with distance. In the comments I left myself a line:

Smoothness is a "must be right every time" mechanical guarantee → it lives in
this layer of code, not in the director / prompt.
The director only gives intent (pick the spine, the drive target, the shot pacing);
this layer decides "how it moves."

That line became the watershed for every decision after: anything that "must be right every time," don't count on the model being conscientious inside a prompt — put it in code.

I bank the taste — I don't toss it after one look

When shipping, I pick every take myself: for one concept, three angles first, I pick one, and the pick plus the two rejects drop into a jsonl. Not pick-and-forget — banking. Looking back is interesting — "consecutive percent change: up 20% then down 20%, does the price come back?" — I picked a single price bar going up then down (you can see the second 20% comes off 120, landing at 96), not the multi-line-on-one-axis, not the sliced proportion bar.

{"concept":"consecutive up-then-down — up 20% then down 20%, back to the start?",
 "chosen":{"spine":"pctbar", … "the final value isn't the original price" …},
 "rejected":[{"spine":"multiGraph", …}, {"spine":"bar", …}]}

These are my anchor for the next problem of the same kind — and the ready-made feed for the day I actually train a judge to "pre-pick for me."

Boil what's computable into gates

Taste alone isn't enough; it has to be right every time. Every dimension I can compute deterministically, I squeeze into a hard gate — fail it, no shipping:

These gates share one thing: the criterion is geometry, is a formula — not "I think it's good."

Every fix has to be mailed back

The most important practice is how the loop closes. I spot a flaw by eye, and "just fix this one plan" is the most useless move. I have to ask: can this flaw be mechanized? If yes, sink it into a gate, a directing rule, or a renderer change.

Real example: one lever clip, the narration says "the effort arm is twice the load arm, so half the force balances it," but at that instant the beam on screen is visibly tilted — I hadn't pinned the "balanced" beat to the true balance point. Fixing the one clip is easy, but I promoted it to a rule: whenever narration lands a conclusion like "exactly balances" or "just needs… to…," there must be a beat pinning the state to the value where the machine can actually compute that conclusion, so the sync gate bites it. No one will ever again say "balanced" over a tilted beam.

How do you tell whether a fix is worth sinking?

Anything I catch a second time, sink it for sure. Catching the same class of flaw by hand twice = a process bug, not a slip.

The self-repair loop has hard rules too: it only terminates on "all deterministic gates green," never on the model saying "looks good"; there's an iteration cap; if fixing one dimension makes another worse, roll back.

Last

The takeaway, one line: the checkable goes to code, the uncheckable honestly goes to a human, and don't put a fudge gate in between — a fake green light is worse than no gate.

The good part: I can batch-ship now, mechanical flaws (overflow, collision, A/V drift, expression overuse) trend monotonically down, and my eyes are freed to watch one dimension only — "is it actually explained clearly?"

Machines auto-stamp the mechanical checks; the last checkpoint, 'is it clear,' still needs the robot's own eyes
Overflow, collision, A/V drift — the mechanical flaws get stamped by machines automatically; the last checkpoint, "is it actually explained clearly," still needs my own eyes. (Machine labels: 超框 overflow · 碰撞 collision · 声画 A/V; 讲清楚没有 = is it clear?)

The honest part: that one dimension I still can't gate by machine. I went and checked — those short-video "golden three seconds," "optimal length is N seconds" are almost all marketing posts; not one survives adversarial verification, so don't make up seconds. "Explained clearly" is the same — no machine-checkable criterion, still me eyeballing one by one. Get a VLM judge? Even less trustworthy: the more hidden the error, the less it catches — that cube's number is exactly what it waved through. So it earns a spot as side advice only, not as a gate.

This "checkable to code, uncheckable to a human" isn't just about video — for any work where you want AI to produce reliably every time and have no ready gold standard in hand, the hard part isn't generation, it's how, dimension by dimension, you turn "is it right" into a criterion the machine can watch for you. The video engine is just the first block I used to get this smooth.

If you're also chewing on verification / education / agents, come say hi.