Two days ago we described how we render the AI Budżet ads in code: screenshot reels assembled in Python, and 30-second Canvas 2D films with a synthesized score, written by Claude Code agents. This is the sequel, and it is about two changes. The films got a second house style — pencil on paper instead of an inked cartoon — and the debts film got a new cast: instead of two invented guys in a bar, the characters are now caricatures of real people, starting with me. Below: what prompted it, what we took from someone else's method, what a pencil look takes in code, how HyperFrames did on a Windows laptop, and why one child in the frame moved the whole scene out of a bar.

Why a part two

First, where the idea comes from. Films drawn entirely in code started with Kevin Ngo's post “The life of a fruit fly” on X on 15 September 2026: a short film about a fruit fly's life with every frame drawn by a program. Inspired by it, Dean Kuhn built the open agent skill procedural-film (MIT licence), and that skill is what our film A pipeline is a fixed, predefined sequence of processing steps — unlike an agent it does not choose the order, it always runs the same one. Full definition → from part one is built on.

Right after part one, a methodology called «Мультики кодом на Opus 5.5» ("Cartoons in code on Opus 5.5") reached us — a Russian-language guide written by the YouTube channel Produktivny Sovet (Продуктивный Совет) for its video “My personal animator on Opus 5.5: cartoons without a single hand-drawn frame”. That was a good excuse to audit our process against someone else's checklist instead of our own memory.

The first half of the audit was reassuring. Most of the guide is already how we work: the drawing is code (JavaScript on a canvas), drawings are held on twos — 12 drawings a second at 24 fps — lines boil from a seeded random generator, there is hatching and there are construction lines, the sound is synthesized and its cues are visible in the frame, the order of work is storyboard → scenes → frame checks → sound → render, critic agents review rendered frames, and the style lives in a skill.

Practice from the guideBeforeWhat we did
Drawing as code (JavaScript, canvas)Had it—
Drawings on twos: 12/s at 24 fpsHad it—
Boiling lines from a seeded generatorHad it—
Hatching and construction linesHad itGrown into a pencil kit
Synthesized sound, cues visible in the frameHad it—
Storyboard → scenes → frames → sound → renderHad it—
Critic agents review frames; style in a skillHad it—
A cumulative pitfalls listMissingPITFALLS.md, ~35 rows
Explicit story rulesMissingFour rules in the skill
A pencil look, palettes as rolesMissingpencil-film, six themes
The HyperFrames rendererMissingTried: good tooling, not faster

We also do three things the guide does not ask for. A six-check gate, determinism included, that every film must pass (described in part one). A shared cast module, so a character drawn by twelve scene agents working in parallel is still the same character in every shot. And fact-checking: every claim the film makes on screen is checked against the product's code and wiki.

The second half was less comfortable. Four things were missing: a cumulative list of pitfalls inside the skill, explicit story rules, a real pencil look with palettes defined as roles, and the HyperFrames renderer. We took the first three and tried the fourth.

A pitfalls file and four story rules

Five films in, we had paid for some lessons twice, because they lived in session transcripts rather than in the skill. The fix (commit ABA-632 in the product repository) is boring on purpose: a file called PITFALLS.md with 32 rows of "symptom → cause and fix", collected from all five films. The agent reads it at the start of every film and appends to it after delivery. A few rows give the flavour.

Events that do not fit their window. A storyboard asks for N events "on the eighths" and nobody checks that N eighth notes actually fit the time the shot has. Now that is counted before the scene is written.

A key chip only 13–16 px tall on a phone. The status chip that carries the whole point of a shot is perfectly legible in a 1080-pixel frame and unreadable on an actual phone. The fix is a magnifier lens over the chip.

A speech-bubble tail through a face. The tail points at the speaker, and the straight line from the bubble to the mouth happens to cross another character's face.

Windows paths eaten by escapes. A path like D:\Work\tools\films pasted into a JavaScript string came out as D:\Work, a tab, "ools", a form feed, "ilms" — because \t and \f are escape sequences.

A push that says "50 PLN". The notification in the film reads "50 PLN", not "50 zł". It looks odd in a Polish film, but that is what the real app sends, and a film shows the product as it is.

The story rules are four lines. One turn, placed at 35–45 % of the length. The last ~3 seconds are a static poster. Frame 0 is never empty. Up to 15 seconds is one scene; from 20 seconds on, scenes are chained. None of this is new to anyone who edits video, but written into the skill it stops being a matter of taste in each session.

A pencil kit

The second house style is a separate kit, pencil-film. "Pencil" turned out to be a list of small, specific behaviours rather than a texture. The pen lifts off the paper. A closed contour overshoots its own start, the way a hand closes a circle. A second, thinner sketch pass runs alongside the main line. The line itself is a tapered ribbon with grain. Shadows are hatching on light paper rather than fills. There are eraser marks and half-erased ghost lines, and construction lines that fade once the drawing is in place.

Colour is defined by role, not by value. A scene never asks for "orange"; it draws with "line", "soft", "accent" and "warm", and a theme maps those roles to colours. Six themes exist — notebook, kraft, chalk, blueprint, neon and mystic — and switching between them is one call.

One poster frame in six themes: notebook, kraft, chalk, blueprint, neon and mystic. The scene code is the same; only the role-to-colour map changes

Handwriting is the part we like most. Text is not set in a handwriting font, it is written: stroke by stroke, with the pen visibly moving. That requires single-stroke fonts, where each letter is a path for a pen rather than an outline to fill, so we used the public-domain Hershey fonts from the US National Bureau of Standards (NBS) — Script 1-stroke and Sans 1-stroke. Polish letters do not exist in them, so ą ę ć ń ó ś ź ż ł are composed from the base glyph plus a mark stroke, written in the same pass. One letter we drew by hand: the original script "z" has a descender loop, and in "zł" it read as "ʒł". The new "z" has no loop.

The handwriting sheet: Hershey Script and Sans 1-stroke with Polish diacritics, the same word at four stages of being written, and Caveat for poster titles

For large poster titles we kept Caveat (an OFL font), where an outline font simply looks better. The poster also composites the real app icon PNG rather than a drawn imitation of it. And a new project is one command: new-film.cjs <slug> --theme notebook --seconds 12.

HyperFrames, honestly

HyperFrames is HeyGen's command-line renderer (the npm package hyperframes, pinned at 0.8.111). It seeks a paused GSAP timeline frame by frame and screenshots the page; our canvas simply draws from the time it is given, which fits our scene contract exactly.

The guide promised 11–15 seconds to render 12 seconds of video on an M3 MacBook. On our Windows laptop it took about 80 seconds: screenshot capture runs at roughly 8 frames a second, plus the encode. With -q standard -w 6 we got it down to about 45 seconds; fewer workers made it slower, not faster. So it is not faster than our own Playwright + ffmpeg renderer. It is, however, good tooling: lint, check, snapshots, contact sheets and audio muxing come with it.

One trap is worth knowing. snapshot --at rounds the time to 30 fps. Our cue at 13.25 s therefore needs --at 13.29 to show the drawing that is actually on screen at that cue.

Our own faces

The pencil style got its first real job on the debts film, "Oddam piętnastego" ("I'll pay you back on the 15th"). The plot is unchanged: a friend borrows 50 zł "until the 15th", and the app reminds the lender three days before the date and on the day after. Only the lender — never the friend. What changed is who is in the frame. The lender is now a caricature of me, Michał, and the friend who borrows "until the 15th" is Kasia, a caricature of my wife, with our son in a short cameo.

The same moment of the same film, shot 02 "the promise", at about 3.9 s: the ink version in a bar (left) and the pencil version in a café (right)
From a generic character to a caricature: the reference photo, the hero of the ink version and the pencil Michał

The method is a likeness sheet: the reference photo next to the drawing, plus the face in several expressions. The photo exists only on the sheet; it never appears in the film. The rest is human feedback in short rounds, and it is worth listing them, because this is where the time actually went.

The beard went from a mottled "gravel" fill to pencil hatching that follows the direction of hair growth. Then it got shorter and neater, with a separate small moustache. The width of the face went back and forth more than once. The eyes became brown, and then slightly narrower.

Michał's likeness sheet: the photo (sheet only), the caricature at two sizes and six expressions used in the film

Kasia's hair took the longest. The first version was a dense "wig" of ringlets. The second was long wavy ribbons — which read as tentacles. The third, the one in the film, is a big rounded mass of corkscrew curls. Her top was also made cooler and darker, so it no longer merged with my cream shirt where we stand side by side.

Kasia's likeness sheet: a family photo (sheet only, never in the film), her expressions and our son's cameo poses

Recasting without rewriting a single scene

The film has twelve scene files, and none of them had to change for the new cast. The cast module kept its function names and An API is the interface two systems use to exchange data automatically, with no Excel exports and no copying by hand. Full definition →: CAST.kuba still draws "the friend", it just draws Kasia now. Every scene that called it picked her up unchanged.

What did change is the text on screen, because Polish has grammatical gender. "Everyone has a friend like that" became "Każdy ma taką kumpelę." — the feminine form. "Remind him?" became "Przypomnieć jej?…", "Maybe he remembers…" became "Może sama pamięta…", and the push title became "Dług za 3 dni: Kasia". Her lines now also name me: „Michał, pożycz 50 zł. Oddam piętnastego!”.

A child in the frame, so no beer

Once the child appeared next to the stack of coins, the bar stopped working. A child next to alcohol in an ad reads badly, and ad platforms review that combination more strictly. So the bar became a café — coffee, lemonade, a vitrine with cakes — and the boy got a pink ice cream.

The payoff at about 24.3 s: the debt is repaid, 50 / 50 zł, and our son, ice cream in hand, stacks the coins

The café also exposed a drawing bug in the hug. Kasia's arm was drawn in front of me instead of around me. The fix was clipping her arm with my silhouette, so the arm passes behind my back and only her hand shows on my far shoulder.

Delivery: loudness and two false alarms

The single-pass loudnorm with I=-14:TP=-1.5 came out at −12.2 LUFS with a peak of +0.7 dBFS — too loud and clipping. A two-pass loudnorm (measure first, then apply with linear=true) followed by alimiter landed exactly on −14.0 LUFS and −1.5 dBFS.

The sync checker flagged two cues as off: one by 169 ms, the other by 45 ms. Both were false positives: the card or the hand starts moving into the region before the hit, and the hit itself lands exactly on the cue frame. Every other cue was within 2 ms.

The result, and which style when

There are two versions, Polish and English, each 30 seconds, 1080×1920 at 24 fps. The English one opens with "Everyone has that one friend", the loan is €50 and the coins are bimetallic €1 pieces, and the push uses the app's real English copy, "You lent EUR 50 — repayment due soon". The soundtrack is reused from the ink film: the shots have the same boundaries and all 51 cues line up.

So we now have two styles and a rule of thumb for choosing. Ink is for 30-second stories with characters and app screens. Pencil is for short "work out the number" ads — and, as of this film, also for character comedy with real faces.

The whole thing is still code in the product repository: reviewable, reproducible, and checkable against what the app really does. You can try AI Budżet at ai-budget.pl or on Google Play. If you want a video pipeline like this for your product — screenshot reels, procedural films, or your own team drawn into the ad — write to us at development@mi-code.pl.

Frequently asked questions

Can you put real people into an ad drawn in code?
Yes, as caricatures and with the people's consent. We work from a likeness sheet: the reference photo next to the drawing, plus the face in several expressions. The photo lives only on the sheet and never appears in the film, and the likeness is refined in short rounds of feedback — beard, face width, eyes, hair.
Does HyperFrames render faster than Playwright and ffmpeg?
Not for us. The guide promised 11–15 s for 12 s of video on an M3 MacBook; on a Windows laptop it took about 80 s, and about 45 s after tuning (-q standard -w 6). That is not faster than our Playwright and ffmpeg renderer, but HyperFrames brings good tooling: lint, check, snapshots, contact sheets and audio muxing.
How do you keep characters consistent when twelve scene agents work in parallel?
No scene draws a character itself — a shared cast module does, and scenes only call it. That makes recasting a change in one module: CAST.kuba kept its name and API but now draws Kasia, and twelve scene files picked her up unchanged.
Why give the agent a pitfalls file?
Because lessons from earlier films lived in session transcripts and we kept paying for them again. PITFALLS.md has 32 rows of 'symptom → cause and fix' from five films — from a UI chip too small to read on a phone to Windows paths eaten by \t and \f. The agent reads it at the start of every film and appends new rows after delivery.