Files
hyperframes/skills/remotion-to-hyperframes/assets/test-corpus/tier-3-data-driven
James efa7164ab4 feat(skills): remotion-to-hyperframes corpus T3 (4/7)
Adds the data-driven tier — a purpose-built fixture (option 2 from the
stack discussion, not a port of PR #214's examples/remotion-full/) that
exercises the realistic shape of a production Remotion composition
without using the runtime adapter.

Stargazed.tsx (10s @ 30fps, 1280x720):
  Sequence 0-3s    TitleScene   (title + subtitle)
  Sequence 3-7s    StatsScene   (3 reused StatCards staggered 12 frames apart)
  Sequence 7-10s   OutroScene   (UnderlinedText with scaleX-from-left underline)

Composition shape exercises:
  - <Composition schema={z.object({...})} defaultProps={...} />
  - nested array prop (stats[]) materialized as repeated HTML
  - custom React subcomponents (StatCard, AnimatedNumber, UnderlinedText)
    reused with different props
  - per-instance delay via prop (delayInFrames -> GSAP timeline offset)
  - frame-driven count-up (AnimatedNumber, manual cubic ease-out)
  - two different spring configs in the same composition
    (damping:12 -> back.out(1.4), damping:14 -> back.out(1.2))
  - useCurrentFrame, useVideoConfig

Translation choices documented in README.md and expected.json:
  - Zod props -> data-* on root #stage div
  - Custom subcomponents inline as repeated HTML using prop interface
    as the template
  - AnimatedNumber's frame-driven count-up -> GSAP onUpdate tween on a
    { v: 0 } counter object, ease power3.out
  - Two different spring configs -> two different back.out overshoots
    (1.4 vs 1.2 approximates the damping difference)
  - delayInFrames={i * 12} -> GSAP offset (i * 0.4)s

Validated end-to-end: rendered Remotion baseline + HF translation, ran
scripts/render_diff.sh.
  measured mean SSIM 0.953
  measured min  SSIM 0.927
  measured p05  SSIM 0.938
  threshold 0.90 (~0.04 below p05)

The wider gap vs T1/T2 reflects T3's bigger approximation budget
(2 spring instances + count-up timing + font fallback on multiple text
sizes). Mean SSIM below 0.90 = structural mismatch (wrong durations,
wrong stagger, missing prop wiring), not approximation drift.

Same Remotion config as PR 3: setVideoImageFormat("png") +
setColorSpace("bt709") to match HF's yuv420p output.

Lint: 9 files scanned, 0 blockers / 0 warnings / 0 infos.
oxlint, oxfmt, typecheck all pass.

The fixture is not yet wired into CI; render + diff is documented in
README.md and runs by hand via the harness from PR 2. PR 7's orchestrator
will wire all four tiers into a CI eval run.
2026-04-27 23:54:43 +00:00
..

Tier 3 — stargazed-data-driven

What it tests

A purpose-built data-driven fixture that exercises the realistic shape of a production Remotion composition without using the runtime adapter from PR #214. If a translation passes T3, the skill correctly handles:

  • A <Composition> with a z.object schema and typed defaultProps
  • Custom React subcomponents reused with different props across scenes
  • A nested data structure (stats[]) materialized as repeated HTML with per-instance attributes
  • A frame-driven count-up animation (AnimatedNumber → GSAP onUpdate)
  • Two different spring configs translated to two different back.out overshoots
  • Per-instance delays via component props (delayInFrames → GSAP timeline offsets)

Composition shape

Stargazed (10 s @ 30 fps, 1280×720)
├── Sequence 03 s   TitleScene
│                    ├── title  ← spring scale
│                    └── subtitle ← linear fade
├── Sequence 37 s   StatsScene
│                    ├── StatCard "Stars" 1247 #fbbf24 (delay 0 frames)
│                    ├── StatCard "Forks" 312 #60a5fa  (delay 12 frames)
│                    └── StatCard "Issues" 48 #f87171  (delay 24 frames)
└── Sequence 710 s  OutroScene
                     └── UnderlinedText "thanks for watching" ← scale-in underline

Each StatCard is a custom subcomponent that internally uses AnimatedNumber to count from 0 to the target. AnimatedNumber itself derives the displayed value from useCurrentFrame() + a manual 1 - (1 - t)^3 ease.

The lossy parts (and why threshold = 0.90)

  1. spring → back.out(N): two different spring configs in this composition.

    • { damping: 12, stiffness: 100, mass: 1 } (title) → back.out(1.4)
    • { damping: 14, stiffness: 90, mass: 1 } (stat card) → back.out(1.2)

    Overshoot ratio (1.4 vs 1.2) approximates the damping difference. The late-tail curve of GSAP's back ease and Remotion's spring don't match exactly — costs ~0.03 mean SSIM per spring instance.

  2. Count-up easing: AnimatedNumber uses 1 - (1 - t)^3 (cubic ease-out) manually computed in the component. GSAP's power3.out is the same curve shape — should match closely. The displayed integer is rounded each frame in both renderers; minor mismatches occur when the rounded value flips between two numbers on a sub-frame timing difference.

  3. Font rendering: same caveat as T1/T2. System Helvetica/Arial fallback produces minor anti-aliasing differences between renderers. Affects the stat card numbers (large weight 800) most.

A mean SSIM below 0.90 in T3 indicates a structural mismatch (wrong scene durations, wrong stagger timing, missing prop wiring), not approximation drift. That's the failure signal we care about. The calibrated mean against Remotion @ 4.0 with PNG/BT.709 output is 0.953.

Translation walk-through (skill cheat sheet)

Remotion HyperFrames
<Composition schema={z.object({...})} defaultProps={...} /> data-* attributes on root #stage div
nested array prop (stats[]) repeated HTML markup with per-instance data-* attrs
custom React subcomponent inline repeated HTML using the component's prop interface as the template
<AnimatedNumber from={0} to={value} dur={45} /> (cubic ease-out count-up) tween on { v: 0 } object with onUpdate rewriting textContent, ease power3.out
spring({damping:12, stiffness:100}) back.out(1.4) over ~0.7 s
spring({damping:14, stiffness:90}) back.out(1.2) over ~0.7 s
delayInFrames={i * 12} (per-instance) GSAP timeline offset (i * 0.4) s
useVideoConfig() to get fps dropped — composition fps is in data-fps on #stage

How to render and evaluate

# Render Remotion baseline (no setup.sh — no binary assets in this fixture)
cd remotion-src && npm install && npm run render

# Render HyperFrames translation
cd ../hf-src && npx hyperframes render --output ../hf.mp4

# Compare
../../../scripts/render_diff.sh ./remotion-src/out/baseline.mp4 ./hf.mp4 ./diff