# Hyper-Animator Critical Checklist

> Extracted from SKILL.md Step 8 (Critical Checklist) and Step 9 (Manual Check Details).
> Every generated composition MUST satisfy ALL items in the Critical Checklist before being presented to the user or rendered. These are not optional — they come from analyzing 124 working HyperFrames source files and the renderer's runtime expectations.

---

## Critical Checklist (Step 8)

### 1. Root Composition Attributes

**WHY:** The HyperFrames runtime identifies, dimensions, and schedules compositions by `data-composition-id`, `data-width`, `data-height`, and `data-duration`. Without them, the composition is invisible to the renderer.

```html
<div id="root" data-composition-id="my-composition"
     data-width="1920" data-height="1080"
     data-start="0" data-duration="30">
```

---

### 2. Paused GSAP Timeline with Registration

**WHY:** The HyperFrames renderer controls playback externally via `tl.play()`, `tl.pause()`, `tl.progress()`. A self-playing timeline cannot be synchronized. The `paused: true` flag is non-negotiable.

```javascript
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
// ... build tweens ...
tl.to({}, { duration: totalDuration }, 0);
window.__timelines["my-composition"] = tl;
```

---

### 3. Scoped CSS

**WHY:** Compositions are embedded in a shared rendering context. Unscoped CSS can leak and corrupt other compositions' visuals.

```css
[data-composition-id="my-composition"] .title {
  font-size: 64px;
}
```

Scope ALL rules under `[data-composition-id="..."]` or a unique class/id prefix.

---

### 4. Timeline Padded to Declared Duration

**WHY:** Without padding, the renderer may terminate the composition before all visual elements have completed their animation.

```javascript
tl.to({}, { duration: totalDuration }, 0); // pads from t=0
```

The timeline MUST cover exactly the duration declared in `data-duration`.

---

### 5. No Date.now() for Primary Timing

**WHY:** `Date.now()` creates desyncs during render because the JS execution clock and the render frame clock are different timelines.

```javascript
// GOOD — renderer controls progress:
tl.progress(0.5);
// OR:
tl.time(15);
```

Exception: `Date.now()` is acceptable only for logging/tracing — never for visual output, animation timing, or noise generation.

---

### 6. No setInterval for Animation Timing

**WHY:** `setInterval()` is unreliable for frame-accurate render. The execution interval drifts under load, producing jittery output.

```javascript
// GOOD — GSAP ticker for frame callbacks:
gsap.ticker.add(function() {
  if (tl.isActive()) { /* update companion state */ }
});
```

---

### 7. No Math.random() for Visual Generation

**WHY:** `Math.random()` is the #1 cause of "looks different every render" bugs. The frame-screenshot renderer evaluates JS on each frame with wall-clock time, so any `Math.random()` call produces a new value, causing flicker and unreproducible output.

```javascript
function mulberry32(a) {
  return function() {
    a |= 0; a = a + 0x6D2B79F5 | 0;
    var t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}
var rng = mulberry32(20260630); // Fixed seed
```

Replace ALL `Math.random()` calls with `rng()` for particles, glitch offsets, noise, starfields, etc.

---

### 8. Async Resource Handling + Producer Readiness Gate

**WHY:** If the timeline registers before resources (fonts, images, WebGL) are ready, the initial render frame will be blank. The producer (render engine) polls `window.__renderReady` before beginning frame capture — without it, the render may appear stuck or produce blank frames.

```javascript
// After ALL async resources are loaded, signal readiness to the producer:
Promise.all([document.fonts.ready, ...imageLoadPromises]).then(() => {
  window.__renderReady = true;  // producer waits for this before capture
  buildLayout();
  var tl = gsap.timeline({ paused: true });
  window.__timelines["my-comp"] = tl;
});
```

For WebGL/Three.js, use `Promise.all` with a CSS fallback in case WebGL fails to initialize.

For WebGL/Three.js, use `Promise.all` with a CSS fallback in case WebGL fails to initialize.

---

### 9. Four-Layer Visual Structure

**WHY:** This layering matches how the most successful HyperFrames catalog blocks are structured, providing depth and readability.

1. **Atmosphere** (background) — gradients, particles, grain overlays. Lowest z-index.
2. **Subject** (main content) — product mockups, text, charts, code. Highest visual priority.
3. **Motion Accent** — decorative animations, shimmer, scan lines that add energy.
4. **Focus/Finish** — final-frame CTA text, logo reveal, outro branding.

Each layer can be its own `<div class="clip" data-track-index="N">` element with independent timing.

---

### 10. Preview via hyperframes play

**WHY:** Injecting custom player HTML into the composition causes the HyperFrames renderer to treat the overlay as a separate composition, leading to `root_missing_composition_id` and `Cannot read properties of null` errors. The composition HTML must stay pure for rendering.

HyperFrames provides a built-in `<hyperframes-player>` web component via `hyperframes play` that handles all playback controls (play/pause/seek/volume/speed). No player UI should be injected into the composition.

**Setup:** `preview-gen.py --input <html> --output <dir>` fixes audio element ids and creates an `index.html` symlink in the output directory. This makes the directory compatible with `hyperframes play`.

**Preview command:** `hyperframes play <dir>` — starts a local server and opens the browser with the built-in player.

---

### 10b. Interactive Debugging via Studio

**WHY:** When a composition renders with visual defects, the user can click
the problematic element in Studio. The Agent Context Bridge
(`hyperframes preview --context --json --context-fields selection`) returns
precise element metadata, enabling targeted fixes instead of guesswork.

**Workflow:**
1. User reports a visual issue
2. User opens Studio: `hyperframes preview <dir>`
3. User clicks the problematic element
4. Query: `hyperframes preview --context --json --context-fields selection`
5. AI reads `hfId`/`selector` and makes a precise fix

---

### 11. Audio Integration (Conditional)

**WHY:** The frame-screenshot renderer cannot capture real-time Web Audio API (AudioContext) synthesis. All audio must be pre-rendered as WAV files and referenced via `<audio>` elements with `data-start`, `data-duration`, and `data-track-index`.

```html
<audio class="clip" data-start="0" data-duration="72"
       data-track-index="10" data-volume="0.3"
       src="assets/bgm-full.wav"></audio>
```

Three paths depending on Round 3 audio choices:
- **Path A (SFX only):** Individual `<audio>` elements at transition timestamps.
- **Path B (SFX + BGM with beat sync):** Single pre-mixed WAV file containing BGM + all SFX; beat data still drives visual timing.
- **Path C (no audio):** No `<audio>` elements. Silent composition (default).

Audio track index convention: BGM=10, Narration=11, SFX=12.

---

### 12. Beat-Synced Keyframe Timing

**Why:** Without beat sync, animation transitions drift from the music — the most common user complaint.

#### Rule (BGM present)

1. After `beat-detector.py` produces `<name>-beat.json`, inline the first 64 beat timestamps:
   ```javascript
   var __beats = [0, 468, 937, 1406, 1875, ...];
   function beat(n) { return (__beats[n] || (n * 60000 / __bpm)) / 1000; }
   ```
2. ALL scene transitions use `beat(n)` — transitions on phrase boundaries (beat 0, 16, 32, 48...).
3. `beat_in_bar: 1` (downbeats) → strongest emphasis (title drops, logo reveals).
4. Energy peaks from beat JSON → biggest visual moments (scale up, brightness boost).

GOOD:
```javascript
tl.from('.scene-2', { opacity: 0 }, beat(16));
tl.to('.hero', { scale: 1.1 }, beat(32));
```

BAD:
```javascript
tl.from('.scene-2', { opacity: 0 }, 8.5); // hardcoded — will drift
```

#### Rule (no BGM)

Skip beat sync. Use scene-based durations or hardcoded time.

#### Quality Gate

`has-beat-sync` (warning): checks for `__beats` pattern in HTML when BGM audio is present.

---

### 13. Visual Layout Inspection (recommended)

**WHY:** Code-level linting cannot detect visual issues — text overflowing containers, overlapping elements, motion that never triggers. `hyperframes inspect` renders the composition in headless Chrome and reports:

- `text_box_overflow` — text wider than its container
- `content_overlap` — two text blocks occupying the same space
- `text_occluded` — text hidden behind opaque elements
- `motion_appears_late` — entrance animation never triggers within expected time

**Command:** `hyperframes inspect <dir> --json`

**Tip:** Add `data-layout-allow-overflow` to elements where overflow is intentional (entrance animations, etc.).

---

## Appendix: Quality Gate Failures (Step 9)

Fix guidance for each failure message. If any error-severity gate fails, DO NOT present the HTML for user validation. Fix first, then re-run quality gates.

| Gate ID | Failure Message & Fix Guidance |
|---------|-------------------------------|
| **has-fixed-dimensions** | "Missing data-width and data-height. Add these to the root composition element with values matching the plan dimensions." |
| **has-composition-id** | "Missing data-composition-id. Add a unique kebab-case identifier." |
| **has-paused-timeline** | "Timeline not created with paused:true. Use gsap.timeline({ paused: true })." |
| **has-timeline-registration** | "Timeline not registered on window.__timelines. Add registration before the script closes." |
| **has-duration-coverage** | "Missing data-duration. Declare duration matching the total composition length." |
| **no-date-now** | "Found Date.now(). Replace with tl.time() or tl.progress() for timing." |
| **no-setinterval** | "Found setInterval. Replace with GSAP ticker or timeline-based callbacks." |
| **no-math-random** | "Found Math.random(). Replace with seeded PRNG (mulberry32). HyperFrames evaluates JS per-frame and Math.random() changes every pass, causing flicker. Use var rng = mulberry32(FIXED_SEED) and replace all Math.random() calls with rng()." |
| **no-paste-comments** | "Found unresolved paste comments. Replace with real snippets or switch to generate mode." |
| **async-readiness** | "Async resources not handled. Wrap timeline registration in Promise.all for fonts and assets." |
| **no-stale-beat-path** | "beat(n) call references index beyond __beats array. Ensure all beat indices are within the detected range. If the composition is longer than the BGM, the fallback BPM-based calculation handles it." |
