feat(motion,runtime): wire the three craft capabilities to the video-authoring dev (#544)

Playwright, the taste-skill design bar, and hyperframes were all
implemented — and each stopped one hop short of the hands doing video
work: playwright reached only fe-qa/ux-qa, the design bar's web-UI dials
actively pointed a video task at 'dense product UI -> motion 2-3', and
hyperframes' agent-facing doctrine never reached any agent. Three wires:

- vendor the official HyperFrames agent skills (hyperframes-core,
  -keyframes, -creative) under motion/skills/ at a pinned upstream
  commit (Apache-2.0, attribution headers; prose reflowed to house
  style, re-vendor note in each header); README and the dev video
  prompt block point at them
- register the playwright MCP for a ux-dev spawned onto a source=video
  task (_is_video_authoring_spawn: fail-closed role/team/task-source
  probe) so the composition author can watch their HTML live in a real
  browser between renders — gating-only, agent-ux already bakes the
  browser; QA gating unchanged, be-qa/ordinary ux-dev still excluded
- design bar video-mode override in the ux_ui team prompt: video tasks
  are films, the web dials do not apply — use the cinematography bar
  and the vendored doctrine instead

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-17 05:26:42 +02:00
committed by GitHub
co-authored by Renn F
parent 1416bd1de9
commit fd621f0dba
7 changed files with 487 additions and 20 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ A panel-demo clip is a FILM of software being used, not a screenshot with captio
- **A cursor that doesn't behave like a hand.** Drive it with `data-waypoints`: it fades in, travels with eased legs, rests with an idle sway (kit adds this), clicks with a visible cause→effect (the click precedes the thing it triggers), and leaves the frame — it never pops in, freezes pixel-still, or blinks out mid-scene.
- **A metronome.** Identical beat lengths with identical flat entrances read as a slideshow. Vary entrance energy (the kit cards take a springy overshoot well), let a beat breathe after a click, and give the climax (stats/receipt) a different rhythm than the build.
- **Verify motion, not stills.** After `request_render`, sample PAIRS of frames ~0.5s apart around each cursor/camera beat and compare positions — a single frame proves presence, only a pair proves movement.
- **The vendor ships agent skills for this.** HyperFrames publishes official authoring skills (`npx skills add heygen-com/hyperframes --skill hyperframes-keyframes` for seek-safe keyframe rules across runtimes, `hyperframes-creative` for beat planning, `hyperframes-core` for the composition contract; router skill `/hyperframes`). Consult them before authoring a new register. Note the vendor's primary seek-safe animation primitive is GSAP tweens registered on `window.__timelines` — this kit's CSS-animation register is a house pattern, and the clip-window rule below is its empirically-derived seek-safety companion.
- **The vendor's authoring doctrine is vendored in `skills/`.** Read `skills/hyperframes-core.md` (composition contract), `skills/hyperframes-keyframes.md` (seek-safe keyframes across runtimes), and `skills/hyperframes-creative.md` (beat planning) before authoring — they are the official HyperFrames agent skills, vendored at a pinned upstream commit (Apache-2.0, header in each file; re-vendor when bumping `@hyperframes/producer`). Note the vendor's primary seek-safe animation primitive is GSAP tweens registered on `window.__timelines` — this kit's CSS-animation register is a house pattern, and the clip-window rule below is its empirically-derived seek-safety companion.
- **Clip windows are for structural layers only.** The renderer's per-clip visibility scheduler drifts badly behind the encoded timeline on long compositions (measured live: a 40s cut whose per-beat clips only reached the ~19s mark by the final frame — the entire tail silently missing from the MP4). Give `class="clip"` + `data-start`/`data-duration` only to full-length structural layers (the cold-open, the panel frame), and drive every BEAT inside them with the kit's pattern instead: base-hidden styles (`opacity: 0`) plus a delayed CSS animation (`animation-delay` + `forwards`/`both` fill) — those run on the correct clock. If a beat must also disappear, give it an exit animation, not a clip window.
## Panel-demo kit (`kit/`)
+83
View File
@@ -0,0 +1,83 @@
<!-- Vendored from https://github.com/heygen-com/hyperframes (skills/hyperframes-core/SKILL.md @ 9d148d28, Apache-2.0).
Authoring doctrine for the HyperFrames renderer the video sidecar runs. Re-vendor when bumping @hyperframes/producer, then run make reflow-docs (prose is reflowed to house style). -->
---
name: hyperframes-core description: The HyperFrames composition contract — build one renderable project. Use for composition structure, the `data-*` timing attributes, `class="clip"`, tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Also covers Tailwind projects and the STORYBOARD.md / SCRIPT.md plan formats. Read before writing composition HTML.
---
# HyperFrames Core
HyperFrames renders video from HTML. A composition is an HTML file whose DOM declares timing with `data-*` attributes, whose animation runtime is seekable, and whose media playback is owned by the framework.
This skill is the **technical contract** — how to build one hyperframes project. The body below is the build guide; per-topic detail lives in `references/` (index next), read on demand. Other concerns live in the sibling domain skills — `hyperframes-animation`, `hyperframes-creative`, `media-use`, `hyperframes-cli`, `hyperframes-registry`. The capability map in `/hyperframes` says what each one covers.
## References
| File | Read it to… |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `references/minimal-composition.md` | start from the smallest renderable composition skeleton |
| `references/composition-patterns.md` | choose monolithic vs modular; structure a modular `index.html`; pick a sub-comp archetype |
| `references/data-attributes.md` | look up any `data-*` (root / clip / sub-comp host / legacy aliases); use `class="clip"` |
| `references/tracks-and-clips.md` | pick `data-track-index`, handle same-track overlap / z-index, time a clip relative to another |
| `references/sub-compositions.md` | wire a sub-composition (host attrs, `<template>`, per-instance vars) and animate inside it |
| `references/variables-and-media.md` | declare variables; place `<video>`/`<audio>`, set volume, trim |
| `references/determinism-rules.md` | build a seekable timeline; determinism bans; the animatable-property allowlist; layout / text fit |
| `references/full-screen-motion.md` | author full-frame motion with shared backgrounds |
| `references/storyboard-format.md` | author a `STORYBOARD.md` plan (+ the parsed manifest) |
| `references/review-loop.md` | run the plan → sketch → build review passes on a live board — shared by every storyboard-planning workflow |
| `references/production-loop.md` | take an approved plan to a delivered video — the stage dependencies (audio, frames, assembly, transitions, captions, verify, deliver) a freeform build follows directly |
| `references/brief-contract.md` | the brief's ground rules — mode derivation (collaborative / autonomous), shared field registry, question invariants (the asking itself lives in `/hyperframes` → the intent layer) |
| `references/brief-format.md` | author `BRIEF.md` — the confirmed intent document a workflow's Setup writes and every later step reads |
| `references/script-format.md` | author the optional `SCRIPT.md` locked narration |
| `references/subagent-dispatch.md` | map subagent dispatch verbs (parallel fan-out / background / wait) to your harness |
| `references/tailwind.md` | work in a Tailwind v4 project (`init --tailwind`; runtime contract differs from Studio's v3) |
For animation runtime specifics (GSAP API, Lottie, Three.js, etc.) go to `hyperframes-animation``adapters/<runtime>.md`.
## Building a composition
### Two root forms (not interchangeable)
- **Standalone** (top-level `index.html`) — root `<div data-composition-id="…">` sits directly in `<body>`, **no `<template>` wrapper** (wrapping it hides all content and breaks rendering).
- **Sub-composition** (loaded via `data-composition-src`) — root **must** be wrapped in `<template>`.
> ⚠ Transport rule: the runtime **only clones `<template>` contents**; everything outside (incl. `<head>` styles/scripts) is discarded — put `<style>`/`<script>` **inside** the template.
> ⚠ Host-id rule: the host slot's `data-composition-id` must **exactly equal** the inner template's `data-composition-id` **and** the `window.__timelines["<id>"]` key — no `-mount`/`-slot`/`-host` suffix.
File shape, host wiring, and the pre-render checklist → `references/sub-compositions.md`.
### Root must be sized (silent layout bug)
The standalone root needs an explicit **sized box** (`width`/`height` in px), and every ancestor down to a `height:100%` element must have a resolved height — otherwise a flex/`100%` child collapses to ~0 and content piles into the top-left corner. Do not rely on automated gates alone to catch this; inspect a snapshot. Skeleton → `references/minimal-composition.md`.
### One paused timeline
Each composition registers **exactly one** `gsap.timeline({ paused: true })` at `window.__timelines["<id>"]` (key = root `data-composition-id`), built **synchronously** at page load. Render duration = root `data-duration`, not timeline length. Don't manually nest sub-timelines into the host. Full contract (incl. non-GSAP runtimes) → `references/determinism-rules.md` + `hyperframes-animation/adapters/`.
### Non-negotiable rules (silent bugs automated gates may miss)
Surfaced here; full rationale in the linked reference. Do not violate:
- No render-time clocks / unseeded `Math.random` / network / input-state; no `repeat: -1` (use a finite count). → `determinism-rules.md`
- Animate only the visual-property allowlist; never tween `display` or raw `visibility`. GSAP `autoAlpha` and zero-duration timeline boundary sets are the only visibility exceptions, and only on non-clip elements or wrappers inside a clip. The framework alone controls `.clip` visibility. Do not `gsap.set` later-scene clips at page load. → `determinism-rules.md`
- No `<br>` in body text; transformed elements must be block-level + sized; pulsing absolute decoratives need peak clearance. → `determinism-rules.md`
- `<video>`/`<audio>` must be a **direct child of the host root** (never inside a sub-comp `<template>`/wrapper); the framework owns playback. → `variables-and-media.md`
- Every `id` must be unique across the **assembled** page; inside a sub-comp, prefix ids with the composition id (`#<id>-hero`). Duplicate `<video>`/`<img>` ids render **blank** — the producer injects frames by `getElementById`, and cross-file dupes slip past `lint`. → `composition-patterns.md`
- A full-screen scene fill goes on a full-bleed **child** (`position:absolute; inset:0`), never on the composition root itself — the producer's frame compositing can drop the root element's own `background` (the frame renders **black**) even though preview/`snapshot` show it correctly. → `composition-patterns.md`
## Editing existing compositions
- Read the files first. Preserve unrelated timing, tracks, IDs, variables, media paths.
- Match existing composition IDs and timeline keys.
- Adding a clip: pick a non-overlapping `data-track-index` or adjust surrounding timing intentionally.
- `data-hidden` on any composition element hides it in BOTH preview and render, overriding its time window; it is non-destructive/reversible and toggled by Studio's timeline eye icon.
- Adding a sub-composition: verify its internal `data-composition-id` before wiring the host.
## Validation
Use `hyperframes-cli` for command details
- [ ] `npx hyperframes check` passes (0 findings across lint, runtime, layout, motion, and contrast)
- [ ] Projects with sub-compositions: `npx hyperframes snapshot --at <midpoints>` and eyeball each frame
- [ ] `npx hyperframes preview` for review (the user can edit anything in Studio's timeline)
- [ ] `npx hyperframes render` only after the user approves
+73
View File
@@ -0,0 +1,73 @@
<!-- Vendored from https://github.com/heygen-com/hyperframes (skills/hyperframes-creative/SKILL.md @ 9d148d28, Apache-2.0).
Authoring doctrine for the HyperFrames renderer the video sidecar runs. Re-vendor when bumping @hyperframes/producer, then run make reflow-docs (prose is reflowed to house style). -->
---
name: hyperframes-creative description: Non-animation creative direction for HyperFrames videos. Use for design spec (frame.md / design.md) handling, palettes, typography, narration, beat planning, audio-reactive visuals, composition patterns, and brand / style decisions. For atomic motion patterns and scene blueprints, use `hyperframes-animation`.
---
# HyperFrames Creative
Brand, pacing, style, narration, and composition direction. Use after the technical contract from `hyperframes-core` is in place.
For motion patterns, scene blueprints, transitions, and CSS marker effects, use `hyperframes-animation` — this skill is intentionally non-animation.
> **Read these two FIRST for any non-trivial composition — they override web instincts:**
>
> - `references/house-style.md` — "interpret the prompt, generate real content," the lazy-default list, and the background/foreground layer recipe. This is what turns a literal restyle into a _concept_.
> - `references/video-composition.md` — video-medium scale, depth, and foreground detail. It explains how to avoid empty web-page layouts without imposing a universal element count.
>
> Skipping these is the single biggest cause of generic, web-page-looking output. They are not optional rows in the routing table below — for anything beyond a one-line edit, open both before you choose colors or write HTML.
## Workflow
1. If a project has a design spec, **read it first** and treat its frontmatter tokens as brand truth (colors, fonts, spacing, tone, constraints). Which file to read (precedence `frame.md``design.md``DESIGN.md`) and how to parse it (frontmatter = normative, prose = context) are defined once in [`references/design-spec.md`](references/design-spec.md) — resolve and load per that doc.
2. If no design spec exists and the user asks for visual direction, choose a route:
- Ready-made frame-preset (optional) → `frame-presets/` (adopt a `FRAME.md` as `frame.md`; see `references/design-spec.md`)
- Named style or mood → `references/visual-styles.md`
- Fast defaults → `references/house-style.md`
- Interactive selection → `references/design-picker.md`
3. For multi-scene work, plan beats and rhythm before writing HTML → `references/beat-direction.md`. For scene transitions, jump to `hyperframes-animation/transitions/`.
4. For motion-heavy work, read `references/motion-principles.md` (high-level guardrails), then go to `hyperframes-animation` for atomic rules.
## Routing
| Topic | Read |
| ----------------------------------------------------------------------------- | ---------------------------------------------- |
| Adopt a ready-made frame-preset as `frame.md` (optional) | `frame-presets/` · `references/design-spec.md` |
| Default palettes, motion, typography, lazy defaults to question | `references/house-style.md` |
| Named style presets, mood-to-style routing | `references/visual-styles.md` |
| Palette-specific color tokens | `palettes/*.md` |
| Composition patterns — PiP, text-behind-subject, title card, slide show | `references/composition-patterns.md` |
| Stats / infographic presentation | `references/data-in-motion.md` |
| Structured expansion for open-ended prompts | `references/prompt-expansion.md` |
| Video-medium density, scale, color, frame composition | `references/video-composition.md` |
| Per-beat direction, rhythm planning, transition timing | `references/beat-direction.md` |
| Post-authoring spec verification (colors, type, corners, spacing, depth) | `references/design-adherence.md` |
| High-level motion guardrails and GSAP-quality rules | `references/motion-principles.md` |
| Font selection, pairings, rendered-video type guardrails | `references/typography.md` |
| Story doctrine — hook language, value-before-evidence, storyboard-as-proposal | `references/story-spine.md` |
| Script pacing, tone, openings, number pronunciation | `references/narration.md` |
| Precomputed audio bands mapped to motion | `references/audio-reactive.md` |
## Scripts
- `scripts/contrast-report.mjs` — inspect contrast warnings from rendered frames.
- `scripts/extract-audio-data.py` — pre-extract audio bands for audio-reactive compositions.
- `scripts/package-loader.mjs` — support script for bundled creative tooling.
`contrast-report.mjs` resolves helper packages from the current project first, then can bootstrap the bundled HyperFrames package version. Set `HYPERFRAMES_SKILL_PKG_VERSION=<version>` only when running the skill outside the bundled CLI/skill install and you need to pin that bootstrap version explicitly.
Run from the repo root with explicit paths, for example:
```bash
python skills/hyperframes-creative/scripts/extract-audio-data.py <audio-file>
```
Animation analysis (`animation-map.mjs`) lives in `hyperframes-animation/scripts/`.
## Boundaries
- Do not override `hyperframes-core` technical rules.
- Do not require a design system for a minimal technical composition.
- Do not add extra scenes, narration, music, captions, or transitions unless the request calls for them or you first propose the expansion.
- Keep recipe references task-specific; do not read every reference for simple edits.
+226
View File
@@ -0,0 +1,226 @@
<!-- Vendored from https://github.com/heygen-com/hyperframes (skills/hyperframes-keyframes/SKILL.md @ 9d148d28, Apache-2.0).
Authoring doctrine for the HyperFrames renderer the video sidecar runs. Re-vendor when bumping @hyperframes/producer, then run make reflow-docs (prose is reflowed to house style). -->
---
name: hyperframes-keyframes description: > Use when a HyperFrames composition needs seek-safe 2D/3D keyframes, GSAP timelines, CSS keyframes, Anime.js, WAAPI, FLIP, paths, masks, SVG morph/draw, text trails, 3D depth, or `hyperframes keyframes` diagnostics. Don't use for broad scene strategy, brand design, media sourcing, captions, or general video planning.
---
# HyperFrames Keyframes
Keyframes are a pose contract: visible states, continuous subject identity, seek-safe runtime, verified pixels.
Use `hyperframes-animation` for broad scene recipes. Use `hyperframes-cli` for full command docs. Use `references/keyframe-patterns.md` only when choosing implementation mechanisms, not visual style.
## Procedure
1. Identify the animated subject, visible states, final state, and runtime.
2. Choose the smallest mechanism that proves the prompt. Read `references/keyframe-patterns.md` only if the mechanism is unclear.
3. Author seek-safe keyframes in the declared runtime. Build synchronously and register the runtime instance.
4. Verify with `hyperframes lint`, `hyperframes check`, `hyperframes keyframes`, one focused `--shot`, and snapshots at proof times.
5. If proof fails, fix the source keyframes and rerun the smallest failing diagnostic before rendering.
## Contract
- Name the moving subject.
- Name the poses needed to prove the intended motion, including the final state.
- Keyframe visible channels, not hidden helper state.
- Preserve object identity when continuity matters.
- Crossfade only when the intended motion is replacement or dissolve.
- Hold readable or semantic states long enough to see.
- Final frame is part of the animation, not cleanup.
- Do not reset to rest unless requested.
- Do not end on black unless requested.
- If editing a starter scene, preserve layout, copy, assets, colors, and final state unless asked to redesign.
## Runtime Rules
GSAP:
- build synchronously at page load
- use `gsap.timeline({ paused: true })`
- register as `window.__timelines[compositionId]`
- registry key must match `data-composition-id`
- do not call `tl.play()` for render-critical motion
- keep repeats finite
CSS keyframes:
- finite duration and iteration count
- deterministic delay
- `animation-fill-mode: both`
- use `data-start` when timing belongs to a clip
Anime.js:
- create synchronously
- `autoplay: false`
- finite duration and loops
- push every instance to `window.__hfAnime`
WAAPI:
- finite `duration`
- `fill: "both"`
- deterministic construction
- the text surface does not list WAAPI; verify with `--shot` (it seeks WAAPI) and snapshots
Never use for render-critical motion:
- `Date.now()`
- `performance.now()`
- unseeded `Math.random()`
- hover/scroll triggers
- timers
- async-created timelines
- unregistered `requestAnimationFrame`
- infinite loops
## GSAP Skeleton
```js
const root = document.querySelector("[data-composition-id]");
const compositionId = root.dataset.compositionId;
const tl = gsap.timeline({ paused: true });
tl.addLabel("state-a", 0);
tl.to(".subject", {
keyframes: [
{ x: 0, opacity: 1, duration: 0.2 },
{ x: 120, opacity: 1, duration: 0.4, ease: "power2.out" },
{ x: 100, opacity: 1, duration: 0.2, ease: "power2.inOut" },
],
ease: "none",
});
window.__timelines = window.__timelines || {};
window.__timelines[compositionId] = tl;
```
Use labels for semantic states. Use position parameters instead of chained delays. Use `immediateRender: false` for later `from()`/`fromTo()` tweens touching the same property.
## Keyframe Forms
- Array keyframes: pose ladder with per-step duration/ease.
- Percentage keyframes: exact timing inside one tween.
- Property arrays: compact multi-stop changes.
- `ease: "none"` on the parent when each stop carries its own easing.
- `easeEach` when every segment should share the same feel.
Do not copy numeric distances or timing from examples. Derive them from the actual composition geometry and duration.
For one subject moving between two boxes, prefer one continuous transform tween or FLIP. Split `x/y/scale` into multiple eased keyframes only when the viewer should feel distinct beats; every segment changes velocity and can read as a hitch.
## Channels
Prefer compositor/visual channels: `x/y/z`, `xPercent/yPercent`, `scale`, `rotationX/Y/Z`, `skew`, `transformOrigin`, `svgOrigin`, `opacity`, `autoAlpha`, `clip-path`, masks, CSS vars, SVG path/dash values, camera transforms, shader uniforms.
Avoid layout/lifecycle channels: `top/left/right/bottom`, `width/height`, `margin/padding`, `display`, `visibility`, late DOM creation, helper overlays doing subject motion.
For visibility changes, use `autoAlpha` on the registered seekable GSAP timeline, or a zero-duration `tl.set()` at an explicit boundary. Target only a non-clip element or a wrapper inside the clip; never target `.clip` itself. Never duration-tween raw `visibility`, and never tween `display`.
## Mechanism Choice
Choose the smallest mechanism that proves the prompt:
| Need | Mechanism |
| ------------------------------------- | -------------------------------------------------- |
| Same subject changes box or hierarchy | shared element / FLIP |
| Subject travels a visible route | path travel |
| Stroke grows or traces | stroke draw |
| Shape becomes another shape | shape interpolation |
| Reveal boundary is visible | clip, mask, or shader uniform |
| Many items move with order | stagger / indexed delay |
| Text itself moves | line, word, character, or band subdivision |
| Surface bends, stretches, or crops | parent/child counter-transform |
| UI has states | explicit state machine |
| Scene has depth | DOM 3D, Three.js, or WebGL camera/object keyframes |
Mechanisms can combine, but each one must clarify the idea. Decoration is not proof.
## Timing
- Anticipation only when it clarifies cause or direction.
- Acceleration leaves rest.
- Peak proof shows the mechanism unmistakably.
- Follow-through sells energy and direction.
- Overshoot only when the subject should feel elastic or tactile.
- Constant-speed path travel usually needs `ease: "none"`.
- Discrete UI states usually need a sharp ease-out.
- Repeated elements need ordered offsets, not identical timing.
- Final lockups need longer holds than transition poses.
- Smoothness means continuous velocity on the same subject.
- Do not overlap tweens that write the same transform property unless the overlap is intentional and verified.
- Avoid animating large `clip-path`/mask changes while the same hero surface is also scaling or traveling; use nested reveals after the main move settles.
## Text
Preserve line boxes, word spacing, readability, and final fit. If text moves internally, move the glyphs or masked bands, not only decorations around the text. Snapshot readable frames.
## SVG
For stroke growth prefer `DrawSVGPlugin`, then `stroke-dasharray`/`stroke-dashoffset`. For shape interpolation prefer `MorphSVGPlugin`; convert primitives to paths when needed and split complex silhouettes into simpler parts.
## 3D
Scale alone is fake depth. Use perspective on a stable parent, `transform-style: preserve-3d`, z travel, rotation, camera/world motion, occlusion, and layer order when objects cross.
Use one or two diagnostic angles that expose the depth relationship. If angled proof shows no depth crossing, improve z/camera/occlusion.
## Canvas / WebGL
Keyframe camera position, camera target, object transform, material opacity, shader uniforms, and postprocess intensity through deterministic state. Render from HyperFrames time. Use `--ghost` because marker boxes cannot see internal canvas motion.
## CLI Proof
```bash
npx hyperframes lint
npx hyperframes check
npx hyperframes keyframes .
npx hyperframes keyframes . --json
npx hyperframes keyframes . --runtime all
npx hyperframes keyframes . --selector "<selector>" --shot "<file>" --samples <n>
npx hyperframes keyframes . --selector "<selector>" --shot "<file>" --layout strip --from <t0> --to <t1>
npx hyperframes keyframes . --shot "<file>" --ghost --angle <angle>
npx hyperframes snapshot . --at <times>
```
Choose `<selector>` for the real animated subject. Choose `<times>` for first frame, proof poses, final-minus-hold, and exact final. Choose `<angle>` only when depth must be proven.
| Tool | Proves |
| ---------------- | --------------------------------------------------------------------------------------------------- |
| `keyframes` | targets, explicit stops, paths, traces, composed parent/child motion, CSS stops, Anime registration |
| `--shot` | ghosts, route shape, time spacing, DOM 3D projection, focused selector proof |
| `--layout strip` | in-place motion, overlaps, contact, subtle scale/opacity, text waves |
| `--ghost` | canvas, WebGL, shader motion, rendered 3D |
| `snapshot --at` | masks, text readability, full state, final lockup, black/reset tails |
If selector proof looks wrong:
1. rerun `--json`
2. find the actual animated target
3. shoot that target
4. snapshot full frames
5. trust painted pixels over logs
## Diagnostic Reading
`flat` means no explicit middle poses. `keyframes` means explicit stops exist. `motionPath` means a route exists. `trace` means multi-stroke drawing. `composed with` means child motion inherits parent motion.
Even ghost spacing means constant speed. Clustered ghosts mean slow-in or settle. Large gaps mean fast travel.
A helper-selector shot is not proof. An onion shot over a broken full frame is not proof.
## Error Handling
| Failure | Fix |
| ------------------ | ---------------------------------------------------------------------------------- |
| endpoint-only | add middle poses, hold peak proof, rerun `--shot` |
| identity break | keep one element alive, use shared source/final boxes, remove substitute crossfade |
| fake 3D | add z/camera travel, occlusion, angled proof |
| wrong final | add final hold, snapshot final-minus-hold and exact final |
| unseekable runtime | pause autoplay, register instance, remove timers, build synchronously |
| unreadable text | preserve line boxes, reduce displacement, add final hold, snapshot text frames |
## Done
Run `hyperframes lint`, `hyperframes check`, `hyperframes keyframes`, one focused `--shot`, and snapshots. Confirm first frame, proof poses, final-minus-hold, exact final, subject-owned motion, and no debug overlays.