582 Commits
Author SHA1 Message Date
Miguel Ángel 857b870459 chore: release v0.4.36 v0.4.36 2026-04-29 14:55:45 +00:00
Miguel Ángel 8b234be20c fix(producer): handle no-audio stream gracefully in resolveMediaDuration
When an <audio> element referenced a file with no audio stream (e.g. a
silent screen-recording used as an audio src, or a video-only clip),
extractAudioMetadata threw "[FFmpeg] No audio stream found". The error
propagated uncaught through Promise.all in compileHtmlFile and crashed
the entire render.

Apply the same graceful-skip pattern already used for missing files and
failed downloads: catch the probe error and return { duration: 0 } so
the element is excluded from the composition without aborting the render.

Confirmed via 7 production HyperframeRenderWorkflow failures all sharing
the same TemporalMagicEditActivity.RENDER_PREVIEW stack trace.
2026-04-29 16:53:35 +02:00
Miguel Ángel eb065260dc chore: release v0.4.35 v0.4.35 2026-04-29 13:47:22 +00:00
Miguel Ángel 47b801fbf2 feat: add Studio NLE playback controls (#530)
## Problem

HyperFrames Studio made frame-accurate playback review slower than expected for editor-style workflows. Issue #527 called out missing loop playback, frame display/jump controls, preview-focused Space handling, frame stepping, and NLE-style J/K/L shuttle controls.

## What this fixes

- Adds a persistent Studio loop toggle and makes the playback loop restart when enabled.
- Adds a time/frame display toggle plus a jump-to-frame input in the player controls.
- Adds frame math helpers and frame-step behavior at the Studio preview frame rate.
- Expands keyboard handling so preview-focused Space toggles playback, ArrowLeft/ArrowRight step frames, Shift+Arrow steps 10 frames, and J/K/L shuttle controls work from the preview/timeline surface while ignoring form/button/slider targets.
- Adds J/K/L shuttle behavior: J plays backward, K pauses, L plays forward, repeated J/L ramps 1x -> 2x -> 4x, and K-held J/L frame-steps.
- Makes the preview wrapper focusable so keyboard playback shortcuts work after focusing the preview area.

## Root cause

The Studio playback layer only exposed mouse scrubbing, basic play/pause, a seconds-based readout, and slider-local arrow-key nudges. The global Space shortcut was also gated to `document.body`, so it stopped working once the actual preview/editor surface had focus. Studio needed a single playback-control layer above the runtime adapter that could translate editor keyboard intent into deterministic seek/play/pause operations.

## Verification

### Local checks

- `bun install`
- `bun run --filter @hyperframes/core build:hyperframes-runtime`
- `bunx oxfmt --check packages/studio/src/player/lib/time.ts packages/studio/src/player/lib/time.test.ts packages/studio/src/player/store/playerStore.ts packages/studio/src/player/store/playerStore.test.ts packages/studio/src/player/hooks/useTimelinePlayer.ts packages/studio/src/player/components/PlayerControls.tsx packages/studio/src/player/components/PlayerControls.test.ts packages/studio/src/components/nle/NLEPreview.tsx`
- `bunx oxlint packages/studio/src/player/lib/time.ts packages/studio/src/player/lib/time.test.ts packages/studio/src/player/store/playerStore.ts packages/studio/src/player/store/playerStore.test.ts packages/studio/src/player/hooks/useTimelinePlayer.ts packages/studio/src/player/components/PlayerControls.tsx packages/studio/src/player/components/PlayerControls.test.ts packages/studio/src/components/nle/NLEPreview.tsx`
- `bun run --filter @hyperframes/studio test -- src/player/lib/time.test.ts src/player/store/playerStore.test.ts src/player/components/PlayerControls.test.ts src/player/hooks/useTimelinePlayer.test.ts` -> 4 files passed, 52 tests passed
- `bun run --filter @hyperframes/studio typecheck`
- `bun run --filter @hyperframes/studio build`
- `git diff --check`
- Lefthook during commit -> lint, format, typecheck, commitlint pass

### Browser verification

- Created a temp project at `/tmp/hf-studio-nle-controls` with an animated 10s GSAP timeline.
- Started local Studio preview via `bun run --filter @hyperframes/cli dev -- preview /tmp/hf-studio-nle-controls` at `http://localhost:5194`.
- Used `agent-browser` to verify:
  - loop toggle changes to active state
  - frame display shows `current / total` frames
  - jump-to-frame input moves the seek position to frame 45 / frame 150
  - focused preview accepts Space play/pause
  - ArrowRight advances one frame from preview focus
  - J plays backward from frame 150 to a lower frame, then K stops
  - agent-browser-driven recording of the tested flow completed

## Notes

- Local proof artifacts are intentionally not committed:
  - `qa-artifacts/studio-nle-controls/frame-controls.png`
  - `qa-artifacts/studio-nle-controls/playback-controls.webm`
- Closes #527.
2026-04-29 05:04:46 +02:00
Miguel Ángel 5fe53a0f9c fix: keep caption shortcuts outside component 2026-04-28 22:44:23 -04:00
Miguel Ángel 9dc17ae30d fix: scope studio playback shortcuts 2026-04-28 22:32:23 -04:00
James Russo 02e8bac7e9 Merge pull request #547 from heygen-com/docs/readme-discord-badge
docs(readme): add Discord community badge
2026-04-28 17:04:28 -07:00
JamesandClaude Opus 4.7 be1fcfc4e6 docs(readme): add Discord community badge
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 23:59:24 +00:00
Miguel Ángel 31c4f974ea chore: release v0.4.34 v0.4.34 2026-04-28 23:39:28 +00:00
Vance Ingalls ad44c3133a perf(hdr): reduce layered composite overhead (#538) 2026-04-28 15:30:35 -07:00
Miguel Ángel fc3c601870 Merge pull request #541 from heygen-com/feat/studio-timeline-pinch-zoom
feat(studio): add timeline pinch zoom
2026-04-29 00:22:19 +02:00
Miguel Ángel 0a542f8c8e Merge pull request #540 from heygen-com/fix/studio-gsap-clip-offset
fix(studio): keep GSAP timeline clips stable
2026-04-29 00:09:40 +02:00
Miguel Ángel a45f900af7 feat: add Studio NLE playback controls 2026-04-28 17:51:38 -04:00
Miguel Ángel 635fca124d feat(studio): adapt timeline ruler density 2026-04-28 17:41:39 -04:00
Miguel Ángel 7ff1734db7 fix(studio): capture timeline pinch zoom 2026-04-28 17:31:49 -04:00
Miguel Ángel 820b07ddaa feat(studio): support trackpad timeline pinch zoom 2026-04-28 17:28:04 -04:00
Miguel Ángel 78d949b844 fix(studio): keep gsap timeline clips stable 2026-04-28 17:20:24 -04:00
Miguel Ángel e568235a1d Merge pull request #531 from heygen-com/lint/subcomposition-guidance
feat(lint): warn on dense compositions
2026-04-28 23:08:34 +02:00
Miguel Ángel e1d1103ef0 Merge pull request #532 from heygen-com/fix/snapshot-runtime-multi-strategy
fix(cli): use multi-strategy runtime resolver for snapshot
2026-04-28 23:08:08 +02:00
Miguel Ángel f7de8ebcb1 Merge pull request #539 from heygen-com/fix/subcomposition-audio-coverage
test: cover sub-composition audio extraction
2026-04-28 23:04:35 +02:00
Miguel Ángel 54cc0f1cba test: cover sub-composition audio extraction 2026-04-28 17:00:40 -04:00
Miguel Ángel f142a63e63 Merge pull request #523 from heygen-com/fix/cli-validate-smoke-ci
fix: validate CLI smoke paths
2026-04-28 22:44:54 +02:00
Miguel Ángel 4988b42362 ci: relax CLI smoke validate timeout 2026-04-28 16:18:46 -04:00
Miguel Ángel e14ff9b485 feat(lint): warn on dense compositions 2026-04-28 15:45:32 -04:00
ukimsanovandClaude Opus 4.7 ce4bcc47c0 fix(cli): use multi-strategy runtime resolver for snapshot
Use loadRuntimeSource() from runtimeSource.ts instead of a single
hardcoded path. Tries: build from source (dev), inlined constant
(production), pre-built artifact (fallback). Fixes snapshot in dev
mode where __dirname is src/commands/ not dist/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 14:36:34 -04:00
Vance IngallsandClaude Opus 4.6 8f97edb2b9 fix(hdr): filter zero-opacity elements and support overflow:hidden clip rects (#522)
* fix(hdr): filter zero-opacity elements and support overflow:hidden clip rects in HDR compositor

Two bugs in the HDR render pipeline:

1. Child data-start elements inside a parent with opacity:0 were still
   composited as independent layers, painting over content in later scenes.
   Fix: filter elements with effective opacity 0 before groupIntoLayers().

2. CSS overflow:hidden on ancestor elements was ignored for HDR video layers,
   causing videos inside clipped containers (e.g. split-screen halves) to
   render full-frame. Fix: add clipRect to ElementStackingInfo, compute it
   from ancestor overflow:hidden in queryElementStacking(), and crop the
   source buffer to clip bounds before blitting in blitHdrVideoLayer().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(hdr): move opacity filter into blit loop to preserve hide-list correctness

The previous approach filtered zero-opacity elements before groupIntoLayers(),
which broke the DOM screenshot hide-list — invisible video elements' <img>
replacements weren't properly hidden from sibling layer screenshots, causing
the vignelli-stacking regression.

Fix: keep all elements in groupIntoLayers() for correct hide-list generation.
Skip zero-opacity HDR elements only during the actual blit step with an early
`continue` in the compositing loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(hdr): route identity-matrix HDR elements through region blit for clip rect support

parseTransformMatrix returns a valid matrix even for untransformed HDR
elements (Chrome reports matrix(1,0,0,1,0,0)). This made the affine blit
path always run, bypassing the region blit path which is the only one that
applies clip rects from overflow:hidden ancestors.

Fix: detect identity matrices and route them through the region path so
the cropRgb48le clip logic is reachable for split-screen layouts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(hdr): handle translation-only matrices for clip rect support

The previous isIdentity check only caught matrix(1,0,0,1,0,0). Elements
with layout translation (e.g. right-half split at left:960px reporting
matrix(1,0,0,1,960,0)) still routed through the affine path where clip
rects are not applied.

Fix: check for translation-only matrices (scale=1, rotation=0, any tx/ty)
and route those through the region blit path. el.x/el.y from
getBoundingClientRect already include the translation, so the region path
handles positioning correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(render): auto-detect HDR from media probes, add --sdr flag

Replace the --hdr opt-in model with automatic detection. When no flags
are passed, the renderer probes all video/image sources and enables HDR
output if any HDR color space is detected. Existing --hdr flag becomes
a force override. New --sdr flag forces SDR output.

Behavior matrix:
  (no flags) + HDR content → HDR output
  (no flags) + SDR content → SDR output
  --hdr → force HDR (defaults to HLG if no HDR sources)
  --sdr → force SDR (skips probing)
  --hdr --sdr → error

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Revert "feat(render): auto-detect HDR from media probes, add --sdr flag"

This reverts commit 69fb52196f.

* chore(hdr): simplify review fixes — remove redundant guard, add image clip warning

- Remove redundant viewportMatrix.length >= 6 check (parseTransformMatrix
  always returns 6-element array or null)
- Add clip rect warning log to blitHdrImageLayer for parity with video path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 10:15:25 -07:00
Miguel Ángel 46a4cacea2 chore: release v0.4.33 v0.4.33 2026-04-28 12:45:02 -04:00
Miguel Ángel 84d58011f1 ci: address CLI smoke review feedback 2026-04-28 11:02:27 -04:00
Miguel Ángel 82ce413fd9 ci: install ffmpeg in CLI smoke job 2026-04-28 10:57:50 -04:00
Miguel Ángel 9c7983211a fix: validate CLI smoke paths and warn on oversized compositions 2026-04-28 10:57:50 -04:00
Vance IngallsandClaude Opus 4.6 8e5593b6ba feat(render): auto-detect HDR from media probes, add --sdr flag (#526)
* feat(render): auto-detect HDR from media probes, add --sdr flag

Replace the --hdr opt-in model with automatic detection. When no flags
are passed, the renderer probes all video/image sources and enables HDR
output if any HDR color space is detected. Existing --hdr flag becomes
a force override. New --sdr flag forces SDR output.

Behavior matrix:
  (no flags) + HDR content → HDR output
  (no flags) + SDR content → SDR output
  --hdr → force HDR (defaults to HLG if no HDR sources)
  --sdr → force SDR (skips probing)
  --hdr --sdr → error

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align HDR auto-detect docs and tests

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-28 01:39:51 -07:00
James Russo 4d0e262eb3 Merge pull request #511 from heygen-com/renovate/google-genai-1.x
chore(deps): update dependency @google/genai to ^1.50.1
2026-04-27 22:34:28 -07:00
James Russo 2d8a29e076 Merge pull request #510 from heygen-com/renovate/github-actions
chore(deps): pin dependencies
2026-04-27 22:33:47 -07:00
James Russo d01685d695 Merge pull request #517 from heygen-com/skill/r2hf-skill-body
feat(skills): remotion-to-hyperframes SKILL.md + orchestrator (7/7)
2026-04-27 22:19:03 -07:00
James Russo f22d9bfabe Merge pull request #516 from heygen-com/skill/r2hf-references
feat(skills): remotion-to-hyperframes references (6/7)
2026-04-27 22:18:44 -07:00
James Russo 40339a65b9 Merge pull request #515 from heygen-com/skill/r2hf-corpus-t4
feat(skills): remotion-to-hyperframes corpus T4 (5/7)
2026-04-27 22:18:14 -07:00
James Russo 12945238cc Merge pull request #509 from heygen-com/skill/r2hf-corpus-t3
feat(skills): remotion-to-hyperframes corpus T3 (4/7)
2026-04-27 22:17:55 -07:00
James Russo 4ab4576adf Merge pull request #508 from heygen-com/skill/r2hf-corpus-t1-t2
feat(skills): remotion-to-hyperframes corpus T1+T2 (3/7)
2026-04-27 22:17:37 -07:00
James Russo b27385ba1a Merge pull request #507 from heygen-com/skill/r2hf-eval-harness
feat(skills): remotion-to-hyperframes eval harness (2/7)
2026-04-27 22:14:29 -07:00
James Russo 3fa094c5c5 Merge pull request #506 from heygen-com/skill/r2hf-scaffold
feat(skills): scaffold remotion-to-hyperframes skill (1/7)
2026-04-27 22:12:11 -07:00
Miguel Ángel a18f66db18 Merge pull request #525 from heygen-com/fix/shader-audio-worker-fallback
fix: budget workers for expensive captures
2026-04-28 05:32:03 +02:00
Miguel Ángel 36b3fc8cd9 fix: budget workers for expensive captures 2026-04-27 22:53:27 -04:00
Miguel Ángel 87fe549ff1 Merge pull request #524 from heygen-com/docs/video-editor-cheatsheet
docs: add Video Editor Cheatsheet guide
2026-04-28 04:02:45 +02:00
Miguel Ángel 37827cdaec chore: release v0.4.32 v0.4.32 2026-04-27 21:18:36 -04:00
Miguel Ángel 8918ba748d docs: add Video Editor Cheatsheet guide
Fast reference for non-technical video editors and creatives — covers the
fast loop, terminal shortcuts, Studio keyboard shortcuts, CLI commands,
timing attributes, render presets, publish/share, and quick fixes.
2026-04-28 00:57:01 +00:00
James b7769b2364 feat(skills): remotion-to-hyperframes SKILL.md + orchestrator (7/7)
The leaf PR. Replaces the placeholder SKILL.md from PR 1 with the real
5-step workflow that loads the per-topic references on demand
(skill-creator's progressive-disclosure pattern), and adds a top-level
orchestrator that runs every tier and reports a pass/fail summary.

SKILL.md changes:
  - Frontmatter unchanged from PR 1 (already covers the trigger phrases
    and out-of-scope cases)
  - Body rewritten as a 5-step workflow:
      1. Lint (load escape-hatch.md if blockers)
      2. Plan (load api-map.md, then per-topic references on demand)
      3. Generate (HF index.html with paused GSAP timeline)
      4. Validate (render_diff.sh against per-tier threshold)
      5. Document gaps (TRANSLATION_NOTES.md if needed)
  - Includes a "Source contains -> Load reference" table so the agent
    only loads the references the source actually needs
  - Documents the validated baseline numbers (T1 0.974, T2 0.985,
    T3 0.953, T4 8/8) so reviewers can reproduce
  - Calls out the critical Remotion encoder config (PNG + BT.709) that
    avoids the ~0.05 SSIM hit from yuvj420p vs yuv420p

Orchestrator (assets/test-corpus/run.sh):
  - Iterates tier-1-* through tier-4-* directories
  - T1-T3: setup -> lint -> npm install (lazy) -> render Remotion ->
           render HF -> SSIM diff at the fixture's expected threshold ->
           generate strip on failure
  - T4: validate.sh (lint-only)
  - Emits run-report.json with per-tier pass/fail and aggregate counts
  - Accepts a single-tier argument for fast iteration: ./run.sh tier-1-title-card

Validated end-to-end on a clean checkout:
    ▶ tier-1-title-card → mean SSIM 0.9739 (≥ 0.95) ✓
    ▶ tier-2-multi-scene → mean SSIM 0.985292 (≥ 0.95) ✓
    ▶ tier-3-data-driven → mean SSIM 0.952941 (≥ 0.9) ✓
    ▶ tier-4-escape-hatch → 8/8 cases ✓
    passed 4/4, failed 0, skipped 0

Closes the 7-PR stack: scaffold, eval harness, 4 tiers of corpus,
references, and now the SKILL.md body that ties everything together.
2026-04-28 00:00:10 +00:00
James 890f305cd1 feat(skills): remotion-to-hyperframes references (6/7)
Adds 11 progressively-disclosed reference files that the skill loads on
demand during translation. Total ~1500 LOC, every file under 200 lines
(skill-creator's progressive-disclosure budget).

  api-map.md         the comprehensive Remotion -> HF translation table
                     (the index; loaded at start of translation)
  timing.md          interpolate, spring (validated configs), easing,
                     count-up, stagger
  sequencing.md      Sequence, Series, Loop, Freeze, AbsoluteFill,
                     Composition root
  media.md           Audio, Video, Img, IFrame, OffthreadVideo,
                     staticFile, asset paths
  transitions.md     @remotion/transitions presentations -> manual GSAP
                     crossfades or HF shader-transitions
  lottie.md          @remotion/lottie -> HF lottie adapter (incl. AE
                     feature limitations note)
  fonts.md           Google Fonts loading, local @font-face, system
                     fallback noise floor
  parameters.md      Zod schemas, defaultProps, sync vs async
                     calculateMetadata
  escape-hatch.md    when to bow out + the runtime interop pattern
                     from PR #214
  limitations.md     known caveat patterns (volume ramps, Loop with
                     state, custom presentations, code-split components)
  eval.md            how to run the validation harness, threshold rule
                     of thumb, what the noise floor looks like

The references are evidence-driven rather than speculative: every spring
config, easing curve, and SSIM threshold is documented from the
validated T1/T2/T3 calibration runs (mean 0.974 / 0.985 / 0.953). The
escape-hatch boundaries match the lint blockers in PR 2 and the T4
fixtures in PR 5.

Replaces the placeholder .gitkeep from PR 1.
2026-04-27 23:55:51 +00:00
James 7509916227 feat(skills): remotion-to-hyperframes corpus T4 (5/7)
Adds the escape-hatch tier — lint-only fixtures that test the skill's
ability to refuse translation cleanly when it sees patterns that don't map
to HF's seek-driven model.

Cases (8 total):
  01-use-state.tsx          blocker: r2hf/use-state
  02-use-effect-deps.tsx    blocker: r2hf/use-effect-deps (multi-line body
                            with internal commas — regression target for
                            the regex bug fix in PR 2)
  03-async-metadata.tsx     blocker: r2hf/async-metadata
  04-third-party-react.tsx  blocker: r2hf/third-party-react-ui (@mui/material)
  05-lambda-config.tsx      blocker: r2hf/lambda-import
  06-warnings-only.tsx      warnings: delayRender / useCallback / useMemo
                            (no blockers — translates after dropping wrappers)
  07-custom-hook.tsx        warning: r2hf/custom-hook (pure useFadeIn)
  08-mixed.tsx              multiple blockers + warnings (aggregate test)

Each case documents:
  - The Remotion pattern it demonstrates
  - Why it's a blocker / warning / info
  - What the skill should do (refuse / drop-and-translate / translate-as-is)

Validation harness (validate.sh):
  Runs lint_source.py against each case, asserts:
    - Each expected blocker rule fires with severity="blocker"
    - Each expected warning rule fires with severity="warning"
    - lint_source.py exit code is 1 when blockers expected, 0 otherwise

T4 has no renders to diff. The skill is graded on lint correctness — that's
the gate that decides whether to translate or recommend the runtime interop
pattern from PR #214.

Result: 8/8 cases pass.
2026-04-27 23:55:24 +00:00
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
James 9ff46d79b7 feat(skills): remotion-to-hyperframes corpus T1+T2 (3/7)
Adds the first two test fixtures the skill is graded against. Each fixture
ships:
  - remotion-src/  full Remotion project (package.json, src/, remotion.config.ts, tsconfig.json)
  - hf-src/        hand-translated HyperFrames composition (index.html)
  - expected.json  tier metadata + SSIM threshold + translation notes + measured validation
  - README.md      human walk-through of the translation choices
  - setup.sh       (T2 only) generates binary assets (PNG, WAV) via ffmpeg

T1 — title-card-fade
- 3 s @ 30 fps, 1280x720
- Single AbsoluteFill, single useCurrentFrame interpolate
  with multi-segment input [0,15,75,90] -> [0,1,1,0]
- Validated mean SSIM 0.974, threshold 0.95
  (~0.025 gap from font-fallback divergence between Remotion's bundled
   Chromium and HF's chrome-headless-shell)

T2 — title-image-outro
- 6 s @ 30 fps, 1280x720, three Sequences (TitleScene, ImageScene, OutroScene)
- Exercises spring, interpolate, Audio, Img, staticFile
- Spring -> GSAP back.out(1.4) translation
- Validated mean SSIM 0.985, threshold 0.95
  (translation came out cleaner than predicted; spring->back.out drift was
   smaller than the ~0.05 budget I'd expected)
- setup.sh generates a 200x200 blue PNG and a 6 s silent WAV via ffmpeg
  so binaries stay out of the repo

Calibration done end-to-end: rendered Remotion baseline + HF translation,
ran scripts/render_diff.sh, set thresholds ~0.02 below measured p05.

Critical Remotion config: setVideoImageFormat("png") + setColorSpace("bt709").
The default JPEG output writes yuvj420p (full-range) which costs ~0.05 SSIM
vs HF's yuv420p (limited-range). Both fixtures' remotion.config.ts encode
this so render_diff.sh measures translation fidelity, not encoder differences.

Both fixtures lint clean (0 blockers via scripts/lint_source.py).
T2 staticFile() references correctly flagged as info-level findings.

The fixtures are not yet wired into CI — that comes with PR 7's orchestrator.
For now, render and eval are documented in each README and run by hand.
2026-04-27 23:54:28 +00:00