@amg555/frameforge-mcp 2.4.8 → 2.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/plugins/video-pipeline/skills/html-renderer/references/motion-craft.md +92 -0
- package/plugins/video-pipeline/skills/html-renderer/references/render-contract.md +66 -0
- package/plugins/video-pipeline/skills/video-pipeline/references/motion-craft.md +91 -0
- package/plugins/video-pipeline/skills/video-pipeline/references/render-contract.md +65 -0
- package/scripts/deep-validate.js +8 -2
- package/src/harness/bin/harness.mjs +32 -2
- package/src/harness/lib/verify-quality.mjs +252 -0
- package/src/mcp-server/server.js +227 -4
- package/src/video-renderer/renderer.js +7 -1
- package/src/video-renderer/scene-animator.js +1016 -10
- package/src/workflows/assets.js +4 -0
- package/src/workflows/render.js +2 -0
- package/src/workflows/script.js +18 -10
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amg555/frameforge-mcp",
|
|
3
|
-
"version": "2.4.
|
|
4
|
-
"description": "FrameForge — AI video production engine for agentic IDEs. npx @amg555/frameforge-mcp. FFmpeg + TTS run on your machine.
|
|
3
|
+
"version": "2.4.10",
|
|
4
|
+
"description": "FrameForge — AI video production engine for agentic IDEs. npx @amg555/frameforge-mcp. FFmpeg + TTS run on your machine. 23 MCP tools.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"start:local-render": "cross-env RENDER_MODE=full WORKER_PORT=3211 node src/worker/server-render-free.js",
|
|
32
32
|
"db:migrate": "node scripts/db-migrate.js",
|
|
33
33
|
"validate": "node scripts/deep-validate.js",
|
|
34
|
-
"test": "node --test src/pipeline/retry-policy.test.js src/pipeline/telemetry.test.js src/pipeline/artifact-manifest.test.js src/pipeline/artifact-retention.test.js src/pipeline/external-adapter.test.js src/workflows/publishing.test.js src/workflows/script.test.js src/workflows/ingest.test.js src/animation/animation-system.test.js src/fact-checker/review-gate.test.js src/fact-checker/checker.test.js src/utils/log-redaction.test.js src/worker/server-render-free.test.js src/worker/server.test.js src/mcp-server/server.test.js src/quality-gate/gate.test.js src/shorts-extractor/extractor.test.js src/audio-mixer/mixer.test.js src/video-renderer/scene-animator.test.js src/video-renderer/renderer.test.js src/video-editor/editor.test.js plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.test.js src/harness/lib/pixels.test.js src/harness/lib/verify-purity.test.js src/harness/harness.test.js src/integration.test.js",
|
|
34
|
+
"test": "node --test src/pipeline/retry-policy.test.js src/pipeline/telemetry.test.js src/pipeline/artifact-manifest.test.js src/pipeline/artifact-retention.test.js src/pipeline/external-adapter.test.js src/workflows/publishing.test.js src/workflows/script.test.js src/workflows/ingest.test.js src/animation/animation-system.test.js src/fact-checker/review-gate.test.js src/fact-checker/checker.test.js src/utils/log-redaction.test.js src/worker/server-render-free.test.js src/worker/server.test.js src/mcp-server/server.test.js src/quality-gate/gate.test.js src/shorts-extractor/extractor.test.js src/audio-mixer/mixer.test.js src/video-renderer/scene-animator.test.js src/video-renderer/renderer.test.js src/video-editor/editor.test.js plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.test.js src/harness/lib/pixels.test.js src/harness/lib/verify-purity.test.js src/harness/lib/verify-quality.test.js src/harness/harness.test.js src/integration.test.js",
|
|
35
35
|
"lint": "echo 'No linter configured — add eslint for production'",
|
|
36
36
|
"check-credentials": "node src/utils/log-redaction.js",
|
|
37
37
|
"generate-assets": "node scripts/generate-initial-assets.js",
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Motion Craft Reference — 10 Hard Motion Craft Rules
|
|
2
|
+
|
|
3
|
+
This guide defines the production motion design standards implemented across FrameForge and the HTML-as-compositor rendering pipeline.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Springs, Not Linear Easing
|
|
8
|
+
Real physical motion never moves at constant velocity or abrupt linear ramps. Every entrance and transition must model a damped harmonic oscillator:
|
|
9
|
+
|
|
10
|
+
$$\ddot{x} + 2\zeta\omega\dot{x} + \omega^2 x = 0$$
|
|
11
|
+
|
|
12
|
+
- **Default tension**: `160 - 200`
|
|
13
|
+
- **Default friction**: `13 - 16`
|
|
14
|
+
- **Closed-form function**: `spring(t, delay, tension, friction)` maps pure time $t$ to normalized displacement $[0, 1]$ with natural overshoot and settling.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 2. Three-Property Entrances
|
|
19
|
+
Never animate a single property (such as opacity alone or scale alone). High-production visuals orchestrate three coordinated properties for every entering entity:
|
|
20
|
+
1. **Scale**: $0.88 \to 1.0$ (spatial arrival)
|
|
21
|
+
2. **Opacity**: $0.0 \to 1.0$ (perceptual fade)
|
|
22
|
+
3. **Translation**: $Y: +40\text{px} \to 0\text{px}$ or $X: \pm 60\text{px} \to 0\text{px}$ (directional momentum)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 3. Staggered Entrance Choreography
|
|
27
|
+
Never trigger all elements simultaneously. When rendering cards, list items, or diagram nodes:
|
|
28
|
+
- Stagger siblings by $0.15\text{s} - 0.28\text{s}$ relative to base timestamp.
|
|
29
|
+
- Hierarchy flows from primary anchor $\to$ structural components $\to$ explanatory callouts $\to$ micro-badges.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 4. Asymmetric Timing: Exits Faster Than Entrances
|
|
34
|
+
- **Entrances**: Informative, welcoming, gentle settling ($0.5\text{s} - 0.8\text{s}$ duration).
|
|
35
|
+
- **Exits**: Crisp, decisive, purposeful ($0.2\text{s} - 0.35\text{s}$ duration). Exits must clear the stage without lingering.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 5. Five-Layer Depth Stack
|
|
40
|
+
Every scene must maintain clear atmospheric depth across 5 distinct visual layers:
|
|
41
|
+
- **Layer 1: Ambient Backdrop** — Radial gradients, atmospheric tint, depth falloff.
|
|
42
|
+
- **Layer 2: Environment / Context** — Architectural wainscoting, blueprint grid, floor plane, distant mountain ridges, or starry cosmos.
|
|
43
|
+
- **Layer 3: Primary Visual Entity** — The core subject (doodle character, cutaway engine, split comparison cards, neural insight core, or optical loupe).
|
|
44
|
+
- **Layer 4: Explanatory Callouts & Overlays** — Connecting vector lines, animated pulse packets, telemetry tags, and indicator badges.
|
|
45
|
+
- **Layer 5: Foreground Framing & Vignette** — Subtle radial corner shading, specular glass arcs, or foreground foliage.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 6. Continuous Audio-Driven Camera Progression
|
|
50
|
+
Scenes must never switch camera angles at arbitrary fixed timer intervals. Camera motion is a continuous parametric function of speech duration:
|
|
51
|
+
|
|
52
|
+
$$p = \frac{t}{\text{DURATION}}$$
|
|
53
|
+
|
|
54
|
+
- **Dynamic zoom**: $1.0 + p \times 0.08$
|
|
55
|
+
- **Dynamic pan**: Organic Lissajous drift ($\sin(0.4t) \times 6\text{px}$)
|
|
56
|
+
- **Cut synchronization**: Scene cuts occur strictly on spoken sentence boundaries and audio cues.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 7. Continuous Life (Zero Frozen PowerPoint Slides)
|
|
61
|
+
Every frame must contain subtle life. An audience detects static images instantly:
|
|
62
|
+
- Rotating gears ($t \times \omega$)
|
|
63
|
+
- Oscillating telemetry needles and wave pulses
|
|
64
|
+
- Flowing data packets along pipeline tracks
|
|
65
|
+
- Gentle character breathing and foliage sway ($\sin(3t) \times 4\text{px}$)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 8. Hand-Drawn Boil Wobble at 12fps
|
|
70
|
+
When rendering hand-drawn doodle characters and sketch line work, apply authentic 12fps line boil wobble:
|
|
71
|
+
- Quantize step to 12fps: `step = Math.floor(t * 12)`
|
|
72
|
+
- Use seeded deterministic trigonometric noise:
|
|
73
|
+
`Math.sin(seed * 19.37 + step * 2.41) * 2.4`
|
|
74
|
+
- Never use `Math.random()` inside the render loop.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 9. Regional Typography & HarfBuzz Text Shaping
|
|
79
|
+
Ensure professional typographic hierarchy:
|
|
80
|
+
- Auto-detect script (Malayalam: Manjari / Gayathri / Chilanka; Devanagari: Noto Sans Devanagari; Latin: Inter / Rajdhani / Cinzel).
|
|
81
|
+
- Maintain WCAG AAA contrast ratios against backgrounds.
|
|
82
|
+
- Gate on-screen subtitles: display narration text only when explicitly requested in prompt.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 10. Closed-Loop Visual QA Gate
|
|
87
|
+
Never deliver rendered video blind. The production harness must execute automated quality checks across 4 axes:
|
|
88
|
+
1. **Content Presence**: Non-blank ($> 1.5\%$ foreground variance).
|
|
89
|
+
2. **Luminance / Exposure**: Non-blown ($< 96\%$ overexposure).
|
|
90
|
+
3. **Compositional Distribution**: Not corner-confined ($> 12\%$ bbox coverage).
|
|
91
|
+
4. **Motion Delta Verification**: Test frame delta between $t = 30\%$ and $t = 70\%$ to guarantee continuous animation.
|
|
92
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Render Contract — Deterministic Frame Specification
|
|
2
|
+
|
|
3
|
+
This document specifies the strict mathematical and runtime contract required for all FrameForge HTML-as-compositor rendering engines.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Pure Mathematical Function of Time: $F(t) \to \text{Pixels}$
|
|
8
|
+
|
|
9
|
+
Every scene page must behave as a pure, stateless function of time $t$ in seconds:
|
|
10
|
+
|
|
11
|
+
$$\text{Frame}(t) = \mathcal{R}(\text{SceneState}(t))$$
|
|
12
|
+
|
|
13
|
+
- **Zero Memory Leaks**: Frame $N$ must never depend on whether Frame $N-1$ was previously rendered.
|
|
14
|
+
- **Out-of-Order Parallelism**: Frames can be rendered out of sequence across multiple worker threads (e.g. Worker 1 renders $t=1.0\text{s}$, Worker 2 renders $t=0.2\text{s}$, Worker 3 renders $t=2.5\text{s}$).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 2. Virtual Clock Interception
|
|
19
|
+
|
|
20
|
+
The harness injects a virtual clock prior to page script execution (`capture.mjs`):
|
|
21
|
+
|
|
22
|
+
- **Overridden APIs**:
|
|
23
|
+
- `window.performance.now()` $\to$ returns $t \times 1000$
|
|
24
|
+
- `Date.now()` $\to$ returns `virtualEpoch + t * 1000`
|
|
25
|
+
- `window.Date` constructor $\to$ anchored to `VirtualDate(t)`
|
|
26
|
+
- `requestAnimationFrame(cb)` $\to$ registered into a synchronized callback queue
|
|
27
|
+
- `CSS Animations` $\to$ synced via `document.getAnimations().forEach(a => a.currentTime = t * 1000)`
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 3. Seeded PRNG Determinism Invariant
|
|
32
|
+
|
|
33
|
+
Standard `Math.random()` produces divergent frames on repeated renders and breaks cache hashing.
|
|
34
|
+
- **Forbidden**: `Math.random()`
|
|
35
|
+
- **Required**: Seeded PRNG (`sfc32` or deterministic integer hashing).
|
|
36
|
+
- **Time-Synchronized Reseed**:
|
|
37
|
+
```javascript
|
|
38
|
+
let _seed = (0x2F6E2B1 ^ Math.imul(Math.floor(t * 1000), 0x9E3779B9)) | 0;
|
|
39
|
+
function prng() {
|
|
40
|
+
_seed = (_seed + 0x6D2B79F5) | 0;
|
|
41
|
+
let x = Math.imul(_seed ^ (_seed >>> 15), 1 | _seed);
|
|
42
|
+
x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;
|
|
43
|
+
return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 4. Three-Point Purity Gate (`verify-purity.mjs`)
|
|
50
|
+
|
|
51
|
+
Every scene must pass the 3-point purity gate prior to full-rate production encoding:
|
|
52
|
+
1. **Repeat Check**: Sampling $t = 1.0\text{s}$ twice in succession must yield identical SHA-256 frame hashes.
|
|
53
|
+
2. **Wander Check**: Seeking forward $0\text{s} \to 2.5\text{s}$ and then jumping back to $1.0\text{s}$ must yield the identical hash as the cold seek.
|
|
54
|
+
3. **Cold Jump Check**: Spawning a fresh browser session and seeking immediately to $1.0\text{s}$ must match the sequential seek.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 5. Master Encoding Contract (`encode.mjs`)
|
|
59
|
+
|
|
60
|
+
Output MP4s must comply with broadcast and streaming color standards:
|
|
61
|
+
- **Codec**: `libx264` (Profile: High, Level: 4.2)
|
|
62
|
+
- **Pixel Format**: `yuv420p`
|
|
63
|
+
- **Color Matrix**: `BT.709` tags:
|
|
64
|
+
`-colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv`
|
|
65
|
+
- **Audio Synchronization**: Exact audio mux with 18dB voiceover sidechain ducking.
|
|
66
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Motion Craft Reference — 10 Hard Motion Craft Rules
|
|
2
|
+
|
|
3
|
+
This guide defines the production motion design standards implemented across FrameForge and the HTML-as-compositor rendering pipeline.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Springs, Not Linear Easing
|
|
8
|
+
Real physical motion never moves at constant velocity or abrupt linear ramps. Every entrance and transition must model a damped harmonic oscillator:
|
|
9
|
+
|
|
10
|
+
$$\ddot{x} + 2\zeta\omega\dot{x} + \omega^2 x = 0$$
|
|
11
|
+
|
|
12
|
+
- **Default tension**: `160 - 200`
|
|
13
|
+
- **Default friction**: `13 - 16`
|
|
14
|
+
- **Closed-form function**: `spring(t, delay, tension, friction)` maps pure time $t$ to normalized displacement $[0, 1]$ with natural overshoot and settling.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 2. Three-Property Entrances
|
|
19
|
+
Never animate a single property (such as opacity alone or scale alone). High-production visuals orchestrate three coordinated properties for every entering entity:
|
|
20
|
+
1. **Scale**: $0.88 \to 1.0$ (spatial arrival)
|
|
21
|
+
2. **Opacity**: $0.0 \to 1.0$ (perceptual fade)
|
|
22
|
+
3. **Translation**: $Y: +40\text{px} \to 0\text{px}$ or $X: \pm 60\text{px} \to 0\text{px}$ (directional momentum)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 3. Staggered Entrance Choreography
|
|
27
|
+
Never trigger all elements simultaneously. When rendering cards, list items, or diagram nodes:
|
|
28
|
+
- Stagger siblings by $0.15\text{s} - 0.28\text{s}$ relative to base timestamp.
|
|
29
|
+
- Hierarchy flows from primary anchor $\to$ structural components $\to$ explanatory callouts $\to$ micro-badges.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 4. Asymmetric Timing: Exits Faster Than Entrances
|
|
34
|
+
- **Entrances**: Informative, welcoming, gentle settling ($0.5\text{s} - 0.8\text{s}$ duration).
|
|
35
|
+
- **Exits**: Crisp, decisive, purposeful ($0.2\text{s} - 0.35\text{s}$ duration). Exits must clear the stage without lingering.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 5. Five-Layer Depth Stack
|
|
40
|
+
Every scene must maintain clear atmospheric depth across 5 distinct visual layers:
|
|
41
|
+
- **Layer 1: Ambient Backdrop** — Radial gradients, atmospheric tint, depth falloff.
|
|
42
|
+
- **Layer 2: Environment / Context** — Architectural wainscoting, blueprint grid, floor plane, distant mountain ridges, or starry cosmos.
|
|
43
|
+
- **Layer 3: Primary Visual Entity** — The core subject (doodle character, cutaway engine, split comparison cards, neural insight core, or optical loupe).
|
|
44
|
+
- **Layer 4: Explanatory Callouts & Overlays** — Connecting vector lines, animated pulse packets, telemetry tags, and indicator badges.
|
|
45
|
+
- **Layer 5: Foreground Framing & Vignette** — Subtle radial corner shading, specular glass arcs, or foreground foliage.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 6. Continuous Audio-Driven Camera Progression
|
|
50
|
+
Scenes must never switch camera angles at arbitrary fixed timer intervals. Camera motion is a continuous parametric function of speech duration:
|
|
51
|
+
|
|
52
|
+
$$p = \frac{t}{\text{DURATION}}$$
|
|
53
|
+
|
|
54
|
+
- **Dynamic zoom**: $1.0 + p \times 0.08$
|
|
55
|
+
- **Dynamic pan**: Organic Lissajous drift ($\sin(0.4t) \times 6\text{px}$)
|
|
56
|
+
- **Cut synchronization**: Scene cuts occur strictly on spoken sentence boundaries and audio cues.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 7. Continuous Life (Zero Frozen PowerPoint Slides)
|
|
61
|
+
Every frame must contain subtle life. An audience detects static images instantly:
|
|
62
|
+
- Rotating gears ($t \times \omega$)
|
|
63
|
+
- Oscillating telemetry needles and wave pulses
|
|
64
|
+
- Flowing data packets along pipeline tracks
|
|
65
|
+
- Gentle character breathing and foliage sway ($\sin(3t) \times 4\text{px}$)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 8. Hand-Drawn Boil Wobble at 12fps
|
|
70
|
+
When rendering hand-drawn doodle characters and sketch line work, apply authentic 12fps line boil wobble:
|
|
71
|
+
- Quantize step to 12fps: `step = Math.floor(t * 12)`
|
|
72
|
+
- Use seeded deterministic trigonometric noise:
|
|
73
|
+
`Math.sin(seed * 19.37 + step * 2.41) * 2.4`
|
|
74
|
+
- Never use `Math.random()` inside the render loop.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 9. Regional Typography & HarfBuzz Text Shaping
|
|
79
|
+
Ensure professional typographic hierarchy:
|
|
80
|
+
- Auto-detect script (Malayalam: Manjari / Gayathri / Chilanka; Devanagari: Noto Sans Devanagari; Latin: Inter / Rajdhani / Cinzel).
|
|
81
|
+
- Maintain WCAG AAA contrast ratios against backgrounds.
|
|
82
|
+
- Gate on-screen subtitles: display narration text only when explicitly requested in prompt.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 10. Closed-Loop Visual QA Gate
|
|
87
|
+
Never deliver rendered video blind. The production harness must execute automated quality checks across 4 axes:
|
|
88
|
+
1. **Content Presence**: Non-blank ($> 1.5\%$ foreground variance).
|
|
89
|
+
2. **Luminance / Exposure**: Non-blown ($< 96\%$ overexposure).
|
|
90
|
+
3. **Compositional Distribution**: Not corner-confined ($> 12\%$ bbox coverage).
|
|
91
|
+
4. **Motion Delta Verification**: Test frame delta between $t = 30\%$ and $t = 70\%$ to guarantee continuous animation.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Render Contract — Deterministic Frame Specification
|
|
2
|
+
|
|
3
|
+
This document specifies the strict mathematical and runtime contract required for all FrameForge HTML-as-compositor rendering engines.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Pure Mathematical Function of Time: $F(t) \to \text{Pixels}$
|
|
8
|
+
|
|
9
|
+
Every scene page must behave as a pure, stateless function of time $t$ in seconds:
|
|
10
|
+
|
|
11
|
+
$$\text{Frame}(t) = \mathcal{R}(\text{SceneState}(t))$$
|
|
12
|
+
|
|
13
|
+
- **Zero Memory Leaks**: Frame $N$ must never depend on whether Frame $N-1$ was previously rendered.
|
|
14
|
+
- **Out-of-Order Parallelism**: Frames can be rendered out of sequence across multiple worker threads (e.g. Worker 1 renders $t=1.0\text{s}$, Worker 2 renders $t=0.2\text{s}$, Worker 3 renders $t=2.5\text{s}$).
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 2. Virtual Clock Interception
|
|
19
|
+
|
|
20
|
+
The harness injects a virtual clock prior to page script execution (`capture.mjs`):
|
|
21
|
+
|
|
22
|
+
- **Overridden APIs**:
|
|
23
|
+
- `window.performance.now()` $\to$ returns $t \times 1000$
|
|
24
|
+
- `Date.now()` $\to$ returns `virtualEpoch + t * 1000`
|
|
25
|
+
- `window.Date` constructor $\to$ anchored to `VirtualDate(t)`
|
|
26
|
+
- `requestAnimationFrame(cb)` $\to$ registered into a synchronized callback queue
|
|
27
|
+
- `CSS Animations` $\to$ synced via `document.getAnimations().forEach(a => a.currentTime = t * 1000)`
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 3. Seeded PRNG Determinism Invariant
|
|
32
|
+
|
|
33
|
+
Standard `Math.random()` produces divergent frames on repeated renders and breaks cache hashing.
|
|
34
|
+
- **Forbidden**: `Math.random()`
|
|
35
|
+
- **Required**: Seeded PRNG (`sfc32` or deterministic integer hashing).
|
|
36
|
+
- **Time-Synchronized Reseed**:
|
|
37
|
+
```javascript
|
|
38
|
+
let _seed = (0x2F6E2B1 ^ Math.imul(Math.floor(t * 1000), 0x9E3779B9)) | 0;
|
|
39
|
+
function prng() {
|
|
40
|
+
_seed = (_seed + 0x6D2B79F5) | 0;
|
|
41
|
+
let x = Math.imul(_seed ^ (_seed >>> 15), 1 | _seed);
|
|
42
|
+
x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;
|
|
43
|
+
return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 4. Three-Point Purity Gate (`verify-purity.mjs`)
|
|
50
|
+
|
|
51
|
+
Every scene must pass the 3-point purity gate prior to full-rate production encoding:
|
|
52
|
+
1. **Repeat Check**: Sampling $t = 1.0\text{s}$ twice in succession must yield identical SHA-256 frame hashes.
|
|
53
|
+
2. **Wander Check**: Seeking forward $0\text{s} \to 2.5\text{s}$ and then jumping back to $1.0\text{s}$ must yield the identical hash as the cold seek.
|
|
54
|
+
3. **Cold Jump Check**: Spawning a fresh browser session and seeking immediately to $1.0\text{s}$ must match the sequential seek.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 5. Master Encoding Contract (`encode.mjs`)
|
|
59
|
+
|
|
60
|
+
Output MP4s must comply with broadcast and streaming color standards:
|
|
61
|
+
- **Codec**: `libx264` (Profile: High, Level: 4.2)
|
|
62
|
+
- **Pixel Format**: `yuv420p`
|
|
63
|
+
- **Color Matrix**: `BT.709` tags:
|
|
64
|
+
`-colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv`
|
|
65
|
+
- **Audio Synchronization**: Exact audio mux with 18dB voiceover sidechain ducking.
|
package/scripts/deep-validate.js
CHANGED
|
@@ -20,12 +20,14 @@ check('batch handling', code.includes('Array.isArray(body)'));
|
|
|
20
20
|
check('ping handler', code.includes("method === 'ping'"));
|
|
21
21
|
check('notifications/init', code.includes("notifications/initialized"));
|
|
22
22
|
check('body size limit', code.includes('MAX_BODY_BYTES'));
|
|
23
|
-
check('SERVER_INFO version 2.4.
|
|
23
|
+
check('SERVER_INFO version 2.4.10', code.includes("version: '2.4.10'"));
|
|
24
24
|
check('18 tools defined (incl get_job_status)', code.includes('get_job_status'));
|
|
25
25
|
check('export_to_editor tool defined', code.includes('export_to_editor'));
|
|
26
26
|
check('edit_video tool defined', code.includes('edit_video'));
|
|
27
27
|
check('generate_scene_code tool defined', code.includes('generate_scene_code'));
|
|
28
28
|
check('preview_scene tool defined', code.includes('preview_scene'));
|
|
29
|
+
check('verify_scene_quality tool defined', code.includes('verify_scene_quality'));
|
|
30
|
+
check('plan_video_edit tool defined', code.includes('plan_video_edit'));
|
|
29
31
|
check('video-editor exists', fs.existsSync('./src/video-editor/editor.js'));
|
|
30
32
|
check('voxforge TTS provider', code.includes('voxforge'));
|
|
31
33
|
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|
@@ -287,10 +289,12 @@ check('harness/lib/capture.mjs exists', fs.existsSync('./src/harness/lib/c
|
|
|
287
289
|
check('harness/lib/jobs.mjs exists', fs.existsSync('./src/harness/lib/jobs.mjs'));
|
|
288
290
|
check('harness/lib/sheets.mjs exists', fs.existsSync('./src/harness/lib/sheets.mjs'));
|
|
289
291
|
check('harness/lib/verify-purity.mjs exists', fs.existsSync('./src/harness/lib/verify-purity.mjs'));
|
|
292
|
+
check('harness/lib/verify-quality.mjs exists', fs.existsSync('./src/harness/lib/verify-quality.mjs'));
|
|
290
293
|
check('harness/lib/pixels.mjs exists', fs.existsSync('./src/harness/lib/pixels.mjs'));
|
|
291
294
|
check('harness/lib/encode.mjs exists', fs.existsSync('./src/harness/lib/encode.mjs'));
|
|
292
295
|
check('asset harness.mjs exists', fs.existsSync(`${engineAssetBase}/scripts/harness.mjs`));
|
|
293
296
|
check('asset verify-purity.mjs exists', fs.existsSync(`${engineAssetBase}/scripts/verify-purity.mjs`));
|
|
297
|
+
check('asset verify-quality.mjs exists', fs.existsSync(`${engineAssetBase}/scripts/verify-quality.mjs`));
|
|
294
298
|
|
|
295
299
|
// ── Cloudflare Worker rate limiting ───────────────────────────────────────
|
|
296
300
|
console.log('\n=== Cloudflare Worker rate limiting ===');
|
|
@@ -299,13 +303,15 @@ check('cf-worker has checkRateLimit', cfWorker.includes('checkRateLimit'));
|
|
|
299
303
|
check('cf-worker hashes token (SHA-256)', cfWorker.includes('SHA-256'));
|
|
300
304
|
check('cf-worker uses RATE_LIMITS KV', cfWorker.includes('env.RATE_LIMITS'));
|
|
301
305
|
check('cf-worker returns 429 on limit', cfWorker.includes('429'));
|
|
302
|
-
check('cf-worker version 2.4.
|
|
306
|
+
check('cf-worker version 2.4.10', cfWorker.includes("'2.4.10'"));
|
|
303
307
|
check('wrangler.toml has kv_namespaces',fs.readFileSync('./cloudflare-mcp/wrangler.toml','utf8').includes('kv_namespaces'));
|
|
304
308
|
|
|
305
309
|
// ── plugins/skills present ────────────────────────────────────────────────
|
|
306
310
|
console.log('\n=== Plugin skills ===');
|
|
307
311
|
check('video-pipeline SKILL.md exists', fs.existsSync('./plugins/video-pipeline/skills/video-pipeline/SKILL.md'));
|
|
308
312
|
check('html-renderer SKILL.md exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/SKILL.md'));
|
|
313
|
+
check('motion-craft reference exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/references/motion-craft.md'));
|
|
314
|
+
check('render-contract reference exists', fs.existsSync('./plugins/video-pipeline/skills/html-renderer/references/render-contract.md'));
|
|
309
315
|
|
|
310
316
|
// ── Security: no .kiro references in committed source ────────────────────
|
|
311
317
|
console.log('\n=== No stale .kiro references in source ===');
|
|
@@ -21,6 +21,7 @@ import { fileURLToPath } from 'url';
|
|
|
21
21
|
import { doctorBrowser, launchHarnessBrowser } from '../lib/browser.mjs';
|
|
22
22
|
import { openPageWithVirtualClock } from '../lib/capture.mjs';
|
|
23
23
|
import { verifyScenePurity } from '../lib/verify-purity.mjs';
|
|
24
|
+
import { verifySceneQuality } from '../lib/verify-quality.mjs';
|
|
24
25
|
import { renderSheet, renderStrip, renderCrop, renderStoryboard } from '../lib/sheets.mjs';
|
|
25
26
|
import { renderFrameJobs } from '../lib/jobs.mjs';
|
|
26
27
|
import { encodeFramesToMp4 } from '../lib/encode.mjs';
|
|
@@ -305,12 +306,40 @@ async function cmdPixels() {
|
|
|
305
306
|
}
|
|
306
307
|
}
|
|
307
308
|
|
|
309
|
+
async function cmdQuality() {
|
|
310
|
+
console.log(`🎯 Running Closed-Loop Visual QA Gate (1–10 Scoring) on: ${HTML_PATH}`);
|
|
311
|
+
const { browser } = await launchHarnessBrowser();
|
|
312
|
+
const sheetTarget = path.join(CHECK_DIR, 'sheet.jpg');
|
|
313
|
+
const res = await verifySceneQuality({
|
|
314
|
+
browser,
|
|
315
|
+
htmlPath: HTML_PATH,
|
|
316
|
+
sheetPath: sheetTarget,
|
|
317
|
+
});
|
|
318
|
+
await browser.close();
|
|
319
|
+
|
|
320
|
+
console.log(` Overall Scene Score: ${res.overallScore}/10 [${res.passed ? 'PASSED ✅' : 'FAILED ❌'}]`);
|
|
321
|
+
console.log(` Motion Delta: ${(res.motionDelta * 100).toFixed(0)}%`);
|
|
322
|
+
for (const f of res.sampledFrames) {
|
|
323
|
+
const issueStr = f.issues.length > 0 ? ` (${f.issues.join(', ')})` : '';
|
|
324
|
+
console.log(` t=${f.time.toFixed(2)}s: ${f.score}/10${issueStr}`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const qualityReportPath = path.join(CHECK_DIR, 'quality_report.json');
|
|
328
|
+
fs.writeFileSync(qualityReportPath, JSON.stringify(res, null, 2), 'utf8');
|
|
329
|
+
console.log(` Quality report saved: ${qualityReportPath}`);
|
|
330
|
+
|
|
331
|
+
if (!res.passed && !flags['skip-quality']) {
|
|
332
|
+
throw new Error(`Scene failed closed-loop quality gate (Score: ${res.overallScore}/10). Fix worst frames: ${res.worstFrames.map(w => `${w.time}s`).join(', ')}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
308
336
|
async function cmdAll() {
|
|
309
|
-
console.log('🚀 Running Complete Production Pipeline: Storyboard → Verify → Frames → Encode → Inspect');
|
|
337
|
+
console.log('🚀 Running Complete Production Pipeline: Storyboard → Verify → Quality → Frames → Encode → Inspect');
|
|
310
338
|
await cmdDoctor();
|
|
311
339
|
await cmdStoryboard();
|
|
312
340
|
await cmdSheet();
|
|
313
341
|
await cmdVerify();
|
|
342
|
+
await cmdQuality();
|
|
314
343
|
await cmdFrames();
|
|
315
344
|
await cmdEncode();
|
|
316
345
|
console.log('\n✨ Production complete! Output ready at out/film.mp4');
|
|
@@ -321,6 +350,7 @@ try {
|
|
|
321
350
|
case 'doctor': await cmdDoctor(); break;
|
|
322
351
|
case 'storyboard': await cmdStoryboard(); break;
|
|
323
352
|
case 'verify': await cmdVerify(); break;
|
|
353
|
+
case 'quality': await cmdQuality(); break;
|
|
324
354
|
case 'sheet': await cmdSheet(); break;
|
|
325
355
|
case 'strip': await cmdStrip(); break;
|
|
326
356
|
case 'crop': await cmdCrop(); break;
|
|
@@ -329,7 +359,7 @@ try {
|
|
|
329
359
|
case 'pixels': await cmdPixels(); break;
|
|
330
360
|
case 'all': await cmdAll(); break;
|
|
331
361
|
default:
|
|
332
|
-
console.log(`Usage: harness <doctor|storyboard|verify|sheet|strip|crop|frames|encode|pixels|all> [options]`);
|
|
362
|
+
console.log(`Usage: harness <doctor|storyboard|verify|quality|sheet|strip|crop|frames|encode|pixels|all> [options]`);
|
|
333
363
|
break;
|
|
334
364
|
}
|
|
335
365
|
} catch (err) {
|