You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spike: compare film backends without Claude Motion and pick one (#16 Phase 0) #17
Part of #16 (Phase 0). This is the gate for #16's later phases: they start only after the decision here is written back.
Goal
Pick the film backend for accounts without Claude Motion, based on films made the same way end to end, not on reading docs.
Work
Make the same two films with the same script.json / narration / timing.json:
A: a mechanism explainer (Workload Identity Federation)
B: a data / chart explainer
three ways:
HyperFrames render: hyperframes render → video only → our mix() + mux
HyperFrames runtime, our renderer: author in HyperFrames format, check with lint / check / snapshot, capture through render.ts (network blocked, sandbox on) via a thin page that implements window.__animation on top of window.__hf.seek
Our own minimal player: plain HTML/CSS + GSAP or WAAPI implementing window.__animation (seek + screenshot)
Routes 2 and 3 should run through the existing motion-render stills / build unchanged, which also tests that the __animation contract is enough.
Measure
How
Render time
~60 s at 1080p30, macOS (Linux if available)
Determinism
render twice, compare per-frame hashes
Japanese headlines
phrase-boundary breaks (lang="ja" + word-break: auto-phrase), glyph coverage with network off
Visual quality
side-by-side stills vs a Motion-made version of the same script, reviewed by a person (layout, hierarchy, motion, transitions, consistency)
Install friction
extra downloads, sandbox prompts in Claude Code
Security
network and file access during render (route 1 runs Chrome with --no-sandbox)
Done when
The two films exist for each route as MP4 plus mid-scene stills
A person has compared them with the Motion versions
Two films, ~56 s each at 1080p30, same script.json / narration / BGM: A Workload Identity Federation (mechanism), B Japan's population (data, count-ups).
Each film was written once as a single HyperFrames-compatible index.html (timed .clip scenes, one paused GSAP timeline) and rendered three ways.
Route 1: hyperframes render
Route 2: HF runtime + our render.ts
Route 3: our minimal player + render.ts
Picture
reference
same as route 1 (41–45 dB PSNR vs route 1's encode)
same as route 1 (44–46 dB; the gap is the encoder)
Render time, A / B
27.7 s / 30.1 s
178 s / 192 s
179 s / 192 s
Determinism (two renders)
1674/1674 frames identical
335/335 sampled frames identical
335 + 342 sampled frames identical
Network during render
reachable (test film fetched example.com)
blocked
blocked
Chromium sandbox
off (--no-sandbox in the engine)
on
on
Extra install
~104 MB npm + 205 MB chrome-headless-shell in ~/.cache/hyperframes; fetches Noto Sans JP from Google at compile; writes project history to ~/.cache
HF npm package for bundling
8 MB fonts + 72 KB GSAP + a 60-line player
Coupling
public CLI
internal: compiler-<hash>.js's bundleToSingleHtml, window.__player.renderSeek (__hf.seek is only defined by HF's own engine)
none
Other findings:
render.ts needs one change for any HTML film: a DOM page can't rasterize itself, so the html path seeks in the page and takes a Playwright screenshot. Done on spike/html-film-backend (a film.json with {"kind": "html"} selects it); the Motion path and its tests are unchanged.
A film page may control its own timers, so readiness is polled from Node (window.__film.ready()), not awaited in the page.
Fonts load per unicode-range subset on first use; the player loads every glyph on the page before frame 0 and fails if one is missing (no silent fallback). The error message needs work.
lang="ja" + word-break: auto-phrase breaks headlines at phrase boundaries in this Chromium (仕組み stays whole). It cannot split long katakana compounds; those still need shorter wording.
Count-ups via a GSAP onUpdate need seek(t, false) in the player; with that, all routes show the same numbers mid-count.
hyperframes check found 5 real problems in film A that stills review missed (text overlap during a 5 s span, step-number contrast 1.84:1, six labels under the 28 px floor, empty first frame, a 5 s dead stretch) and one in lint (letterSpacing tween snaps to device pixels). This is the QA loop Phase 2 needs.
Our capture is the bottleneck (~0.1 s/frame, one page). HF uses 5 parallel Chrome workers; parallel pages would close most of the gap.
Recommendation
Route 3, with films written in the HyperFrames-compatible subset (single file, .clip + data-start/data-duration, paused GSAP timeline in window.__timelines):
Keeps today's hardening (no network, film dir only, sandbox on), which route 1 drops and route 2 keeps only by depending on HF internals
Same picture and determinism as route 1
Smallest footprint and no third-party telemetry
Phase 1 adds parallel capture for speed; Phase 2 brings in HF's authoring guidance (Apache-2.0) and our own version of the overflow / overlap / contrast / text-size checks, since depending on hyperframes check would bring back the 300 MB install
Still open (needs a person)
Side-by-side review against Motion-made versions of the same scripts (work account): the six MP4s are local, the Motion versions are not made yet
The Motion versions of both scripts were made on a Team account (wif-motion.mp4, population-motion.mp4) and compared with the route 3 films.
Verdict (reviewer): the two paths look alike overall (layout, hierarchy, consistency). Motion's animation is somewhat richer.
Measured on the 30 fps MP4s (share of time where something on screen moves):
Motion
html (route 3)
WIF
36%
21%
Population
26%
22%
The gap is in the mechanism film, not the data film. Reading the Motion film's source shows where it comes from:
Pictograms as inline SVG (key, person) instead of text-only chips
Objects travel along arcs, several at once with staggered starts (one key copied out to three people)
Pop-ins scale up with overshoot (back-out easing)
Something keeps moving during holds: a slow push-in on the whole scene (scale 1 → 1.03), a faint "breathe" on the active card, a short shake when the key leaks
Arrival changes state: the target's border and colour switch when a chip lands
Lines and strikes are drawn (scaleX) rather than faded in
Route 3 is kept (merged in #21). The richness gap moves to #19 as authoring guidance. Closing this spike.
Activity
umiyosh commentedon Oct 24, 2026
Phase 0 results (macOS, Apple Silicon)
Two films, ~56 s each at 1080p30, same
script.json/ narration / BGM:A Workload Identity Federation (mechanism), B Japan's population (data, count-ups).
Each film was written once as a single HyperFrames-compatible
index.html(timed.clipscenes, one paused GSAP timeline) and rendered three ways.hyperframes renderrender.tsrender.ts--no-sandboxin the engine)chrome-headless-shellin~/.cache/hyperframes; fetches Noto Sans JP from Google at compile; writes project history to~/.cachecompiler-<hash>.js'sbundleToSingleHtml,window.__player.renderSeek(__hf.seekis only defined by HF's own engine)Other findings:
render.tsneeds one change for any HTML film: a DOM page can't rasterize itself, so the html path seeks in the page and takes a Playwright screenshot. Done onspike/html-film-backend(afilm.jsonwith{"kind": "html"}selects it); the Motion path and its tests are unchanged.window.__film.ready()), not awaited in the page.lang="ja"+word-break: auto-phrasebreaks headlines at phrase boundaries in this Chromium (仕組みstays whole). It cannot split long katakana compounds; those still need shorter wording.onUpdateneedseek(t, false)in the player; with that, all routes show the same numbers mid-count.hyperframes checkfound 5 real problems in film A that stills review missed (text overlap during a 5 s span, step-number contrast 1.84:1, six labels under the 28 px floor, empty first frame, a 5 s dead stretch) and one inlint(letterSpacingtween snaps to device pixels). This is the QA loop Phase 2 needs.Recommendation
Route 3, with films written in the HyperFrames-compatible subset (single file,
.clip+data-start/data-duration, paused GSAP timeline inwindow.__timelines):hyperframes checkwould bring back the 300 MB installStill open (needs a person)
umiyosh commentedon Oct 25, 2026
Side-by-side review against Motion (done)
The Motion versions of both scripts were made on a Team account (
wif-motion.mp4,population-motion.mp4) and compared with the route 3 films.Verdict (reviewer): the two paths look alike overall (layout, hierarchy, consistency). Motion's animation is somewhat richer.
Measured on the 30 fps MP4s (share of time where something on screen moves):
The gap is in the mechanism film, not the data film. Reading the Motion film's source shows where it comes from:
Route 3 is kept (merged in #21). The richness gap moves to #19 as authoring guidance. Closing this spike.