# Scenario's kinetic-film skill: a launch film as one web page, and what 'reverse engineered' can mean

> Satyajit Ghana — Head of Engineering @ Inkers Technology
> canonical: https://ai.thesatyajit.com/articles/scenario-kinetic-film-skill
> date: 2026-11-01
> tags: agents, code-generation, video, audio

A post from Scenario's account says, in the words that reached me: "R.I.P. Higgsfield Katana. We reverse engineered it into a Claude skill you can feed your agent." You describe a product, game or brand in a few lines, and Claude writes, animates and cuts a kinetic-typography launch film. I did not open the post itself; the quote is as relayed. The skill it points to is on [scenario.com/explorations/kinetic-film](https://scenario.com/explorations/kinetic-film/) as a ZIP.

I downloaded it and read all of it. This article is about what is in the box, what it asks an agent to do, what it costs, and what the phrase "reverse engineered" does and does not tell you. I also rebuilt the skill's quality gate to see what kind of motion it can detect. What I could not do is render a film: the permission layer in my sandbox refused the command that set up a film folder from the downloaded scripts, and I stopped there rather than route around it. Everything below is labelled *measured* (I computed it from the files or from clips I made), *reported* (the page or the skill says so) or *reasoned*.

For the sibling question of what a one-shot launch video actually contains, see [One-shot launch videos](/articles/one-shot-launch-videos). This skill is another data point of the same kind.

## What you download

The ZIP is 568,073 bytes, 34 files in 40 archive entries, 1,318,415 bytes unpacked (measured). Its SHA-256 is `2217ab5d...3ebf`, which is also the path segment of the download URL, so the address is content-addressed and you can check what you got against what you were pointed at (measured).

| Part | What is in it | Size |
|---|---|---|
| `SKILL.md` + `references/` | the instructions: method, blocks, craft, sound, render, MCP loop | 15 KB + six files |
| `engine/` | `blocks.js` 1,425 lines, `core.js` 396, `compositor.js` 150, `main.js` 54, `index.html`, three OFL fonts | 2,025 lines of JS (measured) |
| `scripts/` | 17 files: render, mix, audit, beat grid, finish gate | Python, Node and shell |
| `examples/` | `starter`, `kaiju-postal`, `just-code` specs | three `spec.js` |

There is no licence file for the skill itself. The three fonts (Archivo, Instrument Sans, JetBrains Mono) each ship their SIL OFL text. The page text I fetched showed none either (reported). So the honest label is "no stated licence": you can read it and run it for yourself, and whether you may redistribute it is a question the files do not answer. I marked the frontmatter `unlicensed` for that reason, not as a judgement.

There are no API keys anywhere in the code. A grep for network calls finds two things: a local `http.server` for previewing, and a `curl` in `get_fonts.sh` that fetches fonts from the google/fonts repo only when the shipped ones are missing (measured). The paid part goes through Scenario MCP, which the agent calls with your account, and `references/scenario-mcp.md` tells the agent to price every generation with `dry_run` and wait for a go before the first paid run (reported).

## The idea: a film is a function of time

The skill's central design is one sentence in `SKILL.md`: the engine turns a spec into one web page "where every frame is a pure function of time, so a renderer can seek anywhere and average sub-frames into real motion blur." Nothing is generated by a video model. The words on screen are the words in the spec, and a palette change rebrands the whole film.

That is a claim about code, so it can be checked in code. A case-insensitive grep over the four engine files for `random` and `Date.` returns nothing (measured). The one use of `performance.now` is in the preview loop in `main.js`, which is skipped when the URL has `render` in it. The film grain added at render time uses a seeded NumPy generator, eight tiles reused modulo frame number, so a re-render matches (read in `post.py`). I have not diffed two renders, so "deterministic" is read, not demonstrated.

The spec is plain data. This is the whole `starter` file's skeleton:

```js
export default {
  title: 'Product',
  bpm: 170, beats: 42, fps: 60,
  palette: { bg: '#0D0F14', paper: '#F2F0EA', accent: '#3D7BFF', hot: '#1F5BFF', ink: '#12141A' },
  scenes: [
    [Words, 0, 8,  { charge: true, shots: [ { at: 1, style: 'punch3d', text: 'MEET', fx: 'punch' }, /* ... */ ] }],
    [Strike, 8, 17, { items: [ /* NO X. NO Y. */ ] }],
    [Hero, 17, 25, { kicker: 'INTRODUCING', title: 'PRODUCT', sub: '...' }],
    [Logo, 31, 42, { wordmark: 'PRODUCT', tagline: '...' }],
  ],
}
```

Every time in the file is in beats. A scene is `[Block, startBeat, endBeat, options]`. Change `bpm` and the whole film retimes. That is the part I would copy even without the engine.

<SpecTimeline />

I parsed the three example specs as text, never by running them (measured):

| Spec | BPM | Beats | Length | Frames @ 60 | Scene entries |
|---|---|---|---|---|---|
| `starter` | 170 | 42 | 14.82 s | 889 | 5 |
| `kaiju-postal` | 172 | 68 | 23.72 s | 1,423 | 8 |
| `just-code` | 173.75 | 150 | 51.80 s | 3,108 | 15 |

`just-code` matches the page's headline numbers: 51.8 s and 3,108 frames (reported, reproduced). Two small differences. The page says 174 BPM, the spec says 173.75, which is rounding. The page says 14 scenes; the spec has 15 entries, because the `Logo` starts at beat 139 while the last `Words` scene runs to 140 and so overlaps it by one beat. Neither matters. They are the kind of difference you get from counting by eye on one side and by regex on the other.

The engine has 14 block classes: `Words`, `Strike`, `Graph`, `Hero`, `Prompt`, `Stretch`, `Fly`, `Notes`, `Counters`, `Progress`, `Chain`, `Rewind`, `Picture`, `Logo` (measured, from `export class` lines). The page speaks of 13 block types for its own film, which is the same list without `Picture`, the one that shows generated key art.

## What the agent is asked to do

The workflow in `SKILL.md` has five steps, and the instructions are specific enough that the agent's job is mostly arithmetic and judgement, not invention.

1. **Brief to beat sheet.** Choose a tempo and length from a table (teaser 15 to 25 s at 160 to 175 BPM; launch 40 to 55 s; calmer 30 to 45 s at 120 to 128 BPM), write the words as a spoken script, then cut it to one or two words per beat. `method.md` maps phrase types to blocks: a denial becomes `Strike`, a number that kills an old way of working becomes `Graph`, the brief itself becomes `Prompt`. It also insists that on-screen numbers come from the film (`{lines}`, `{frames}`, `{seconds}`) or the user. "An invented statistic in a launch film is the first thing a reader checks."
2. **Sound and pictures through Scenario MCP.** A music bed at the sheet's tempo, a kit of about 15 one-shot sounds (twelve core names, each with a prompt and a duration in `sound.md`), and key art if the film shows any.
3. **Write `spec.js`.**
4. **Preview and stills.** Scrub in a browser; take stills at beats.
5. **Render at 1080p, pass the gate, render 4K once.**

The interesting design decision is that the paid, non-deterministic parts are exactly the parts code cannot draw: music, sound effects, pictures. The motion is free and repeatable. If you change one word you re-render in minutes and pay nothing; if you change the music you pay and re-measure the beat grid with `beat_grid.py`.

The tempo is where the two meet, and it is the one place the skill is nervous. A generated track's tempo is "a tempo you hope for" unless the model has a `bpm` input, so the skill tells the agent to prefer one that does, to measure the take, and to fit the grid on the stretch the film uses. It states the stakes as "a 0.15 % tempo error puts the last hits of a 50 s film 70 ms late."

<BeatClock />

That figure holds up (reasoned): 0.15 % of 51.8 s is 78 ms, which is 4.7 frames at 60 fps. And a beat at 172 BPM is 20.93 frames, so every cut is rounded to a frame boundary, at most 8.3 ms off. The rounding is invisible; the drift is not.

## The render path, read line by line

`render.py` serves the film folder on a local port, asks the page for its length and cuts, splits the frames across workers, and joins the parts. Each worker (`post.py`) starts a headless Chrome (`render.mjs`) that seeks the page to each sub-frame time, takes a JPEG screenshot, and streams it to Python. Python averages the samples (motion blur), adds a bright-pass bloom at quarter resolution and deterministic grain, and pipes frames to x264.

Details I found by reading that you would otherwise learn the expensive way:

- **Motion blur is real, not a filter.** The shutter is 180 degrees with 4 samples by default; a whip pan stretches it to about 600 degrees with up to 24 samples, and the window never crosses a hard cut (reported in `render.md`, and visible in `render.mjs`: it slides the window back instead of clipping).
- **Several Chrome traps are handled for you.** A 1920x1080 viewport paints only about 990 px, so the scripts use a 1300 px tall viewport and clip; a capture without `clip.scale` returns 1080p when you asked for 4K; large 3D layers need `--run-all-compositor-stages-before-draw`. These are the sort of things you find after a bad afternoon, and they are written down.
- **The encoder level is pinned.** Masters are H.264 High, CRF 12.4, preset slower, Level 5.2 at 4K. The skill notes a slower preset without a level produced Level 6.0, which plays in ffmpeg and nowhere on a Mac.
- **Worker count is sized to memory.** `render.py` allows `min(3, RAM // 11 GB, CPUs // 3)` workers at 4K and `min(6, RAM // 4 GB, CPUs // 3)` at 1080p.

On this sandbox (15 GB, 4 vCPUs) that formula gives one worker at either resolution (reasoned from the code). The skill's own timing is "a 24 s film renders at 1080p in about 4 minutes with 4 workers, and in 4K in about 10 minutes with 3; a 52 s film in 4K takes about 18 minutes" on a 36 GB laptop (reported). Folding those into a per-frame cost: 4 minutes by 4 workers over Kaiju's 1,423 frames is about 0.67 worker-seconds a frame; 18 minutes by 3 workers over 3,108 frames is about 1.04 (reasoned, from rounded figures). A single worker on a 4K master of the long film would then be near an hour on a laptop-class core, and I would expect more on a shared vCPU. I could not check that.

## The gate: what "parked" means to a script

The skill's best idea is not the blocks. It is that "reads as professional motion design" is turned into three numbers, taken from reels it says it measured frame by frame (reported): no more than 40 % still frames, no hold over 0.6 s before the end card, and a median motion of at least 2.5 px per frame. `finish.py` fails the delivery if any are missed, and also checks frame count, size under 1 GB, H.264 level, and loudness between -16 and -12 LUFS.

I could not run `finish.py` or `dyn.py` (they need OpenCV, and the command that set up a folder and installed it was refused). But the still-frame test is eight lines of NumPy: decode to 320x180 grey, mark a pixel changed when it moves by more than 24 levels, call a frame still when under 1.5 % of its pixels changed. I rewrote exactly that, with my own code and ffmpeg, and fed it clips I made.

Ten seconds at 60 fps, 172 BPM, x264 CRF 18 (measured, my reimplementation of the gate, not the skill's scripts):

| Clip | Still frames | Longest still | Gate (40 %, 0.6 s) |
|---|---|---|---|
| flat colour swap every 1.5 s, static block | 99.0 % | 1.48 s | fail |
| colour flip on every beat, static block | 95.3 % | 0.33 s | fail |
| every-beat flip + box 1/3 of the frame moving 6 px/frame | 95.0 % | 0.33 s | fail |
| same, 12 px/frame | 94.5 % | 0.33 s | fail |
| same, 24 px/frame | 93.7 % | 0.33 s | fail |
| same, 48 px/frame | 0.0 % | none | pass |
| same, 96 and 192 px/frame | 0.0 % | none | pass |

The second row is the finding. A film that cuts on every beat has a hold of 0.33 s, which sails under the 0.6 s limit, and is 95 % still by the other test. The two checks are not redundant: the hold check catches slideshows, the still-share check catches a film that cuts often and moves nowhere. "Cut on the beat" is not the same as "never park", and the gate knows it.

The cliff between 24 and 48 px/frame is the other finding. For a solid box the changed area per frame is the leading and trailing edges, `2 * min(v, width) * height`. The gate wants that to pass 1.5 % of the frame, so the speed that counts is `0.015 * 1920 * 1080 / (2 * height)`: 43 px per frame for a box a third of the frame high, which is 1.35 frame-widths per second at 60 fps (reasoned, and consistent with the six clips).

<GateModel />

Slide the height down to the size of a line of type and the number goes up fast: a band 120 px tall has to move 130 px a frame to register. That is a statement about the gate, not about the films. `craft.md` recommends "a slow scale (2 to 5 % per second), a slide (70 px/s for type rows)" to keep things alive after a word lands. By this metric those do nothing: 70 px/s is just over 1 px a frame. They may still read as alive to a viewer, and the median-flow check uses optical flow, which I did not reimplement and which sees small motion the area test does not. But the 40 % still-share gate is carried by the big moves (whips, cuts, flashes, colour flips), and the texture under them is invisible to it.

One more reading of `finish.py`: the dynamism checks run only up to `duration - 4.5 s`, so the end card is exempt. In `kaiju-postal` the `Logo` is 11 beats, 3.84 s, so the exempt window covers it plus the last 0.66 s of the scene before it (reasoned from the spec and the code). A hold there would pass.

## What the page's own film claims

The page reports its film at 30 % still frames (allowed up to 40), no hold over 0.6 s, 8.5 cuts per 10 s, under 1 GB (reported). The Kaiju example reports 24 % still, no hold over 0.6 s, 12.5 cuts per 10 s (reported in `SKILL.md` and `method.md`). I could not reproduce any of these, since that needs the rendered films. What I can say is that they are produced by a gate whose definition I have just read and re-run in part, so they are not a vibe. They are also the film's authors' numbers about their own film.

The `just-code` spec credits "OPUS 5.5" on screen (read in the spec). That is a statement in the film, not something the files verify. The page says the film was rendered on a laptop; nothing in the render path needs more than Chrome, ffmpeg and Python (reasoned).

## "Reverse engineered": what it tells you

The skill never uses the phrase. A search of every file in the ZIP for `Higgsfield`, `Katana` and `reverse` finds one hit, and it is "reverse cymbal swell riser" in `sound.md` (measured). The page does not use the phrase either (reported). The claim lives in the post, and I have not used the other product, so I cannot compare against it.

What can be said from the files is narrower. `SKILL.md` describes the target style as "the style of viral motion design reels (huge words on every beat, whip pans, colour flips, 3D letters the camera flies through, a hard drop)". The method says its energy targets were "measured on the references this style comes from" with `dyn.py`, with a table for the fastest reference (16 % still, 0.37 s longest still, 9.1 hard cuts per 10 s) and calmer ones. And the scripts include `rectify2.py`, which tracks the phone's camera on the static surroundings and warps a filmed screen flat, so that a reference filmed off a monitor can be measured. `method.md` adds: "Match its register with your own words and brand; do not copy its script, its logo or its frames."

Put together, that is consistent with one reading of "reverse engineered": someone measured the look of finished output (cut rate, hold length, motion, devices like whip pans and extruded type) and wrote an engine that produces films with the same measurements. It is a reconstruction of a style from its outputs. It tells you nothing about how any commercial product works inside, and the skill does not claim it does. It also tells you the opposite of what "clone" might suggest: there is no UI, no API and no template library here, only a spec format, 14 blocks and a gate.

That reading is mine. The files support it; they do not prove it is what the post's authors meant. If you are evaluating this skill against another tool, compare films, not that sentence.

## What it costs, and what it needs

- **Software.** Claude Code with the Scenario MCP connected, Node 18 or newer with Playwright, ffmpeg, Python 3 with NumPy and `opencv-python-headless` (reported). The motion renders locally.
- **Credits.** Music and sound effects use Scenario credits, priced before each run by `dry_run` (reported). The skill gives no price. Its sound kit alone is about 15 generations, durations from 0.25 s to 3 s, plus two music takes of 15 s more than the film.
- **Taste.** The skill says plainly what it cannot do: `mix.py` "cannot judge taste: listen before you hand the film over", and generated pictures come back with real brand marks you must paint out. Its "common mistakes" list is more candid than most: a request that mentioned watermarks with a reference image "was blocked as watermark removal (and still charged)".

## Where it breaks

A few things I would test before relying on it, none of which I could.

1. **The first spec an agent writes.** The gate catches parking after a 1080p render, minutes in. A quicker loop would run the same still-share test on `snap.mjs` stills before rendering. The skill does not do that; it is a modest addition.
2. **Text fitting.** Blocks measure and scale words to the frame, and the mistakes list says custom code must measure once, while visible. The 14 blocks cover a lot, but a brief that wants a layout none of them has means writing a custom block, documented at the end of `blocks.md`.
3. **Memory at 4K.** At 11 GB a worker, a 16 GB machine renders on one worker, and the skill's own warning is that six workers "filled 36 GB of memory and stalled the machine".

## What I would take from it

Three things transfer even if you never run the engine.

- **Write the film in beats, not seconds.** Every cue is `B(k)`; one tempo change retimes the film. It is how a script keeps a cut and a drum hit together without a timeline.
- **Make "never park" a number, then know what the number sees.** The gate measures changed pixels. It is blind below roughly 43 px a frame for a big shape, so anything subtle must be judged by eye.
- **Put the paid, stochastic step at the edge.** Music and pictures are generated and priced; motion is code and free. The same split works for any generated-media pipeline.

What I did not establish: that the engine renders what the page shows, how long it takes on your machine, what the music and sounds cost in Scenario credits, or how it compares with the product named in the post. The first is the one I would run next: a made-up brand through `new_film.sh`, `snap.mjs` at a few beats, and a 1080p render, with `dyn.py` on the result.
