Skip to content

spike: compare film backends without Claude Motion and pick one (#16 Phase 0) #17

Closed

Description

@umiyosh

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:

  1. HyperFrames render: hyperframes render → video only → our mix() + mux
  2. 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
  3. 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

Activity

  1. umiyosh commented on Oct 24, 2026

    @umiyosh
    OwnerAuthor

    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 .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
    • Decision on the route
  2. umiyosh commented on Oct 25, 2026

    @umiyosh
    OwnerAuthor

    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):

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions