diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/.gitignore b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/.gitignore new file mode 100644 index 000000000..d45877158 --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/.gitignore @@ -0,0 +1,10 @@ +# Render output +remotion-src/out/ +hf-src/out/ +hf.mp4 +diff/ +strip/ + +# Remotion / HF dependencies +node_modules/ +package-lock.json diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/README.md b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/README.md new file mode 100644 index 000000000..7b496809c --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/README.md @@ -0,0 +1,41 @@ +# Tier 1 — title-card-fade + +## What it tests + +The simplest non-trivial Remotion → HyperFrames translation. A single text +element fades in over the first 0.5 s, holds for 2.0 s, and fades out over +the last 0.5 s. No audio, no media, no custom components. + +If a translation can't pass T1, it's broken on table-stakes basics: +`AbsoluteFill`, `useCurrentFrame`, `interpolate` with multi-segment input, +and the timing offset between Remotion's frame-based driver and HF's +paused-GSAP driver. + +## Translation walk-through + +| Remotion | HyperFrames | +| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| `` | `` + a positioned root div | +| `useCurrentFrame()` | dropped — HF seeks the timeline | +| `interpolate(frame, [0, 15, 75, 90], [0, 1, 1, 0])` at fps=30 | `gsap.timeline({ paused: true })` with three `.to()` calls at offsets 0s/0.5s/2.5s, each `ease: "none"` | +| `
HELLO
` | static markup; opacity is animated by the timeline | + +The Remotion→HF time conversion is `time = frame / fps`. So +`[0, 15, 75, 90]` at 30 fps becomes `[0, 0.5, 2.5, 3.0]` seconds. + +## How to render and evaluate + +```bash +# Render Remotion baseline +cd remotion-src && npm install && npm run render +# Renders to remotion-src/out/baseline.mp4 + +# Render HyperFrames translation +cd ../hf-src && npx hyperframes render --output ../hf.mp4 + +# Compare with the eval harness (from skill scripts/) +../../../scripts/render_diff.sh ./remotion-src/out/baseline.mp4 ./hf.mp4 ./diff +``` + +`expected.json` documents the SSIM threshold (0.95) for this fixture; the +calibrated mean against Remotion @ 4.0 with PNG/BT.709 output is 0.974. diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/expected.json b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/expected.json new file mode 100644 index 000000000..bccec0a59 --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/expected.json @@ -0,0 +1,26 @@ +{ + "tier": 1, + "name": "title-card-fade", + "composition_id": "TitleCard", + "description": "Solid black background, single 'HELLO' element fades in 0-0.5s, holds 0.5-2.5s, fades out 2.5-3.0s. Tests the most basic Remotion → HyperFrames translation: a single AbsoluteFill, a single useCurrentFrame-driven interpolate, no audio, no media, no custom React components.", + "duration_seconds": 3, + "fps": 30, + "width": 1280, + "height": 720, + "ssim_threshold": 0.95, + "validation": { + "measured_mean_ssim": 0.974, + "measured_min_ssim": 0.972, + "measured_p05_ssim": 0.972, + "measured_p95_ssim": 0.983, + "measured_at": "2026-04-27", + "measured_against": "remotion@4.0 (PNG output, BT.709) vs hyperframes@0.4.15-alpha.1" + }, + "translation_notes": [ + "Remotion: AbsoluteFill → HF: position:absolute;inset:0 div", + "Remotion: interpolate(frame, [0,15,75,90], [0,1,1,0]) at fps=30 → HF: paused GSAP timeline with three keyframed tweens at 0s, 0.5s, 2.5s with ease:'none' (linear matches Remotion's default linear interpolation)", + "No fonts loaded; both renderers use system Helvetica/Arial fallback. The Linux fallback diverges between Remotion's bundled Chromium and HyperFrames' chrome-headless-shell — same fontWeight:800 renders perceptibly bolder in HF. This costs ~0.025 mean SSIM and is the dominant non-translation noise floor.", + "Remotion config must use setVideoImageFormat('png') + setColorSpace('bt709'); the JPEG default writes yuvj420p (full-range) which costs ~0.05 SSIM vs HF's yuv420p (limited-range)." + ], + "rationale": "Threshold 0.95 sits ~0.02 below measured p05. A real translation regression (wrong easing, wrong durations) drops mean SSIM by 0.05+. Encoder/font drift between CI runs is bounded at ~0.01." +} diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/index.html b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/index.html new file mode 100644 index 000000000..df2570b8f --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/hf-src/index.html @@ -0,0 +1,61 @@ + + + + + tier-1-title-card + + + + +
+
+
HELLO
+
+ + +
+ + diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/package.json b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/package.json new file mode 100644 index 000000000..1c33b63b4 --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/package.json @@ -0,0 +1,14 @@ +{ + "name": "tier-1-title-card-remotion", + "version": "0.0.0", + "private": true, + "scripts": { + "render": "remotion render TitleCard out/baseline.mp4" + }, + "dependencies": { + "@remotion/cli": "^4.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "remotion": "^4.0.0" + } +} diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/remotion.config.ts b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/remotion.config.ts new file mode 100644 index 000000000..a22cafc5e --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/remotion.config.ts @@ -0,0 +1,13 @@ +import { Config } from "@remotion/cli/config"; + +// Match HyperFrames' default render so SSIM diffs measure translation +// fidelity, not encoder differences. +// +// setVideoImageFormat("png") avoids the JPEG limited-range/full-range +// colorspace flag (yuvj420p vs yuv420p) that otherwise costs ~0.05 SSIM. +// +// setColorSpace("bt709") matches HF's BT.709 SDR output. +Config.setVideoImageFormat("png"); +Config.setColorSpace("bt709"); +Config.setOverwriteOutput(true); +Config.setConcurrency(1); diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/Root.tsx b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/Root.tsx new file mode 100644 index 000000000..f8dd72770 --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/Root.tsx @@ -0,0 +1,13 @@ +import { Composition } from "remotion"; +import { TitleCard } from "./TitleCard"; + +export const RemotionRoot = () => ( + +); diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/TitleCard.tsx b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/TitleCard.tsx new file mode 100644 index 000000000..924557fff --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/TitleCard.tsx @@ -0,0 +1,34 @@ +import { AbsoluteFill, interpolate, useCurrentFrame } from "remotion"; + +export const TitleCard = () => { + const frame = useCurrentFrame(); + + // Fade in 0-15, hold 15-75, fade out 75-90. + const opacity = interpolate(frame, [0, 15, 75, 90], [0, 1, 1, 0], { + extrapolateLeft: "clamp", + extrapolateRight: "clamp", + }); + + return ( + +
+ HELLO +
+
+ ); +}; diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/index.ts b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/index.ts new file mode 100644 index 000000000..f31c790ed --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/src/index.ts @@ -0,0 +1,4 @@ +import { registerRoot } from "remotion"; +import { RemotionRoot } from "./Root"; + +registerRoot(RemotionRoot); diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/tsconfig.json b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/tsconfig.json new file mode 100644 index 000000000..eb6f0f806 --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-1-title-card/remotion-src/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2018", + "module": "ESNext", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": ["src"] +} diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/.gitignore b/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/.gitignore new file mode 100644 index 000000000..def72ea01 --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/.gitignore @@ -0,0 +1,14 @@ +# Generated by setup.sh +remotion-src/public/ +hf-src/assets/ + +# Remotion / HF dependencies +remotion-src/node_modules/ +remotion-src/package-lock.json + +# Render output +remotion-src/out/ +hf-src/out/ +hf.mp4 +diff/ +strip/ diff --git a/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/README.md b/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/README.md new file mode 100644 index 000000000..17659a683 --- /dev/null +++ b/skills/remotion-to-hyperframes/assets/test-corpus/tier-2-multi-scene/README.md @@ -0,0 +1,54 @@ +# Tier 2 — title-image-outro + +## What it tests + +Three-scene composition. Each scene exercises a different Remotion idiom: + +1. **Scene 1 (0–2 s)** — TitleScene with `spring({damping:12, stiffness:100, mass:1})` + driving a `transform: scale()` on text. Tests the lossy `spring → GSAP ease` translation. +2. **Scene 2 (2–4 s)** — ImageScene that fades in a `staticFile`-loaded image and + linearly scales it from 0.8 → 1.0. Tests asset paths + linear `interpolate`. +3. **Scene 3 (4–6 s)** — OutroScene with a 1-s linear fade-in. Sanity check after + the harder scenes. + +A silent 6-second WAV plays throughout at `volume={0.5}`. Tests `