frameyard 0.8.0 → 0.9.0

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.
Files changed (285) hide show
  1. package/CHANGELOG.md +110 -14
  2. package/GLOSSARY.md +112 -2
  3. package/README.md +302 -265
  4. package/dist/assets/integrations.d.ts +8 -4
  5. package/dist/assets/integrations.d.ts.map +1 -1
  6. package/dist/assets/integrations.js +8 -4
  7. package/dist/assets/integrations.js.map +1 -1
  8. package/dist/assets/provenance.d.ts +2 -2
  9. package/dist/assets/provenance.d.ts.map +1 -1
  10. package/dist/assets/provenance.js +5 -3
  11. package/dist/assets/provenance.js.map +1 -1
  12. package/dist/assets/tts-supertonic.d.ts +28 -0
  13. package/dist/assets/tts-supertonic.d.ts.map +1 -0
  14. package/dist/assets/tts-supertonic.js +156 -0
  15. package/dist/assets/tts-supertonic.js.map +1 -0
  16. package/dist/assets/tts.d.ts +18 -6
  17. package/dist/assets/tts.d.ts.map +1 -1
  18. package/dist/assets/tts.js +219 -97
  19. package/dist/assets/tts.js.map +1 -1
  20. package/dist/cli.js +1685 -93
  21. package/dist/cli.js.map +1 -1
  22. package/dist/composition/format.d.ts +1 -1
  23. package/dist/composition/format.d.ts.map +1 -1
  24. package/dist/composition/format.js +1 -1
  25. package/dist/composition/format.js.map +1 -1
  26. package/dist/context.d.ts +82 -0
  27. package/dist/context.d.ts.map +1 -0
  28. package/dist/context.js +277 -0
  29. package/dist/context.js.map +1 -0
  30. package/dist/contracts.d.ts +25 -0
  31. package/dist/contracts.d.ts.map +1 -1
  32. package/dist/contracts.js.map +1 -1
  33. package/dist/doctor.d.ts +14 -1
  34. package/dist/doctor.d.ts.map +1 -1
  35. package/dist/doctor.js +166 -7
  36. package/dist/doctor.js.map +1 -1
  37. package/dist/errors.d.ts +1 -1
  38. package/dist/errors.d.ts.map +1 -1
  39. package/dist/errors.js.map +1 -1
  40. package/dist/fonts/add.d.ts +41 -0
  41. package/dist/fonts/add.d.ts.map +1 -0
  42. package/dist/fonts/add.js +259 -0
  43. package/dist/fonts/add.js.map +1 -0
  44. package/dist/fonts/binary.d.ts +4 -0
  45. package/dist/fonts/binary.d.ts.map +1 -0
  46. package/dist/fonts/binary.js +21 -0
  47. package/dist/fonts/binary.js.map +1 -0
  48. package/dist/fonts/css.d.ts +4 -0
  49. package/dist/fonts/css.d.ts.map +1 -0
  50. package/dist/fonts/css.js +50 -0
  51. package/dist/fonts/css.js.map +1 -0
  52. package/dist/fonts/fontsource.d.ts +107 -0
  53. package/dist/fonts/fontsource.d.ts.map +1 -0
  54. package/dist/fonts/fontsource.js +565 -0
  55. package/dist/fonts/fontsource.js.map +1 -0
  56. package/dist/fonts/index.d.ts +11 -0
  57. package/dist/fonts/index.d.ts.map +1 -0
  58. package/dist/fonts/index.js +11 -0
  59. package/dist/fonts/index.js.map +1 -0
  60. package/dist/fonts/install.d.ts +24 -0
  61. package/dist/fonts/install.d.ts.map +1 -0
  62. package/dist/fonts/install.js +145 -0
  63. package/dist/fonts/install.js.map +1 -0
  64. package/dist/fonts/manifest.d.ts +21 -0
  65. package/dist/fonts/manifest.d.ts.map +1 -0
  66. package/dist/fonts/manifest.js +96 -0
  67. package/dist/fonts/manifest.js.map +1 -0
  68. package/dist/fonts/options.d.ts +9 -0
  69. package/dist/fonts/options.d.ts.map +1 -0
  70. package/dist/fonts/options.js +66 -0
  71. package/dist/fonts/options.js.map +1 -0
  72. package/dist/fonts/schema.d.ts +4 -0
  73. package/dist/fonts/schema.d.ts.map +1 -0
  74. package/dist/fonts/schema.js +316 -0
  75. package/dist/fonts/schema.js.map +1 -0
  76. package/dist/fonts/summary.d.ts +16 -0
  77. package/dist/fonts/summary.d.ts.map +1 -0
  78. package/dist/fonts/summary.js +22 -0
  79. package/dist/fonts/summary.js.map +1 -0
  80. package/dist/fonts/types.d.ts +56 -0
  81. package/dist/fonts/types.d.ts.map +1 -0
  82. package/dist/fonts/types.js +8 -0
  83. package/dist/fonts/types.js.map +1 -0
  84. package/dist/fonts/verify.d.ts +33 -0
  85. package/dist/fonts/verify.d.ts.map +1 -0
  86. package/dist/fonts/verify.js +200 -0
  87. package/dist/fonts/verify.js.map +1 -0
  88. package/dist/html/validate.d.ts.map +1 -1
  89. package/dist/html/validate.js +59 -0
  90. package/dist/html/validate.js.map +1 -1
  91. package/dist/index.d.ts +3 -3
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +1 -1
  94. package/dist/index.js.map +1 -1
  95. package/dist/inspect.d.ts +61 -0
  96. package/dist/inspect.d.ts.map +1 -0
  97. package/dist/inspect.js +325 -0
  98. package/dist/inspect.js.map +1 -0
  99. package/dist/lint.d.ts +63 -0
  100. package/dist/lint.d.ts.map +1 -0
  101. package/dist/lint.js +265 -0
  102. package/dist/lint.js.map +1 -0
  103. package/dist/policy.d.ts +69 -0
  104. package/dist/policy.d.ts.map +1 -0
  105. package/dist/policy.js +361 -0
  106. package/dist/policy.js.map +1 -0
  107. package/dist/preview/timeline-data.d.ts +17 -69
  108. package/dist/preview/timeline-data.d.ts.map +1 -1
  109. package/dist/preview/timeline-host.d.ts.map +1 -1
  110. package/dist/preview/timeline-host.js +70 -45
  111. package/dist/preview/timeline-host.js.map +1 -1
  112. package/dist/preview/timeline-main.d.ts.map +1 -1
  113. package/dist/preview/timeline-main.js +47 -4
  114. package/dist/preview/timeline-main.js.map +1 -1
  115. package/dist/project.d.ts +10 -3
  116. package/dist/project.d.ts.map +1 -1
  117. package/dist/project.js +20 -17
  118. package/dist/project.js.map +1 -1
  119. package/dist/renderer/ffmpeg.d.ts +42 -2
  120. package/dist/renderer/ffmpeg.d.ts.map +1 -1
  121. package/dist/renderer/ffmpeg.js +254 -22
  122. package/dist/renderer/ffmpeg.js.map +1 -1
  123. package/dist/renderer/index.js +1 -1
  124. package/dist/renderer/index.js.map +1 -1
  125. package/dist/renderer/keyframe-expressions.d.ts +39 -0
  126. package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
  127. package/dist/renderer/keyframe-expressions.js +180 -0
  128. package/dist/renderer/keyframe-expressions.js.map +1 -0
  129. package/dist/renderer/timeline.d.ts +2 -0
  130. package/dist/renderer/timeline.d.ts.map +1 -1
  131. package/dist/renderer/timeline.js +89 -116
  132. package/dist/renderer/timeline.js.map +1 -1
  133. package/dist/runtime/host.d.ts.map +1 -1
  134. package/dist/runtime/host.js +4 -0
  135. package/dist/runtime/host.js.map +1 -1
  136. package/dist/scaffold.d.ts +3 -0
  137. package/dist/scaffold.d.ts.map +1 -1
  138. package/dist/scaffold.js +48 -320
  139. package/dist/scaffold.js.map +1 -1
  140. package/dist/server/composition-html.d.ts +2 -0
  141. package/dist/server/composition-html.d.ts.map +1 -1
  142. package/dist/server/composition-html.js +7 -0
  143. package/dist/server/composition-html.js.map +1 -1
  144. package/dist/server/server.d.ts.map +1 -1
  145. package/dist/server/server.js +31 -1
  146. package/dist/server/server.js.map +1 -1
  147. package/dist/server/timeline-preview-data.d.ts.map +1 -1
  148. package/dist/server/timeline-preview-data.js +12 -34
  149. package/dist/server/timeline-preview-data.js.map +1 -1
  150. package/dist/server/timeline-preview-html.d.ts.map +1 -1
  151. package/dist/server/timeline-preview-html.js +18 -2
  152. package/dist/server/timeline-preview-html.js.map +1 -1
  153. package/dist/templates.d.ts +16 -0
  154. package/dist/templates.d.ts.map +1 -0
  155. package/dist/templates.js +186 -0
  156. package/dist/templates.js.map +1 -0
  157. package/dist/timeline/easing.d.ts +36 -0
  158. package/dist/timeline/easing.d.ts.map +1 -0
  159. package/dist/timeline/easing.js +225 -0
  160. package/dist/timeline/easing.js.map +1 -0
  161. package/dist/timeline/keyframes.d.ts +23 -0
  162. package/dist/timeline/keyframes.d.ts.map +1 -0
  163. package/dist/timeline/keyframes.js +95 -0
  164. package/dist/timeline/keyframes.js.map +1 -0
  165. package/dist/timeline/plan.d.ts +6 -12
  166. package/dist/timeline/plan.d.ts.map +1 -1
  167. package/dist/timeline/plan.js +17 -30
  168. package/dist/timeline/plan.js.map +1 -1
  169. package/dist/timeline/resolve.d.ts +12 -0
  170. package/dist/timeline/resolve.d.ts.map +1 -1
  171. package/dist/timeline/resolve.js +105 -9
  172. package/dist/timeline/resolve.js.map +1 -1
  173. package/dist/timeline/scene.d.ts +126 -0
  174. package/dist/timeline/scene.d.ts.map +1 -0
  175. package/dist/timeline/scene.js +109 -0
  176. package/dist/timeline/scene.js.map +1 -0
  177. package/dist/timeline/schema.d.ts +31 -0
  178. package/dist/timeline/schema.d.ts.map +1 -1
  179. package/dist/timeline/schema.js +240 -9
  180. package/dist/timeline/schema.js.map +1 -1
  181. package/dist/timeline/transitions.d.ts +21 -0
  182. package/dist/timeline/transitions.d.ts.map +1 -0
  183. package/dist/timeline/transitions.js +23 -0
  184. package/dist/timeline/transitions.js.map +1 -0
  185. package/dist/version.d.ts +1 -1
  186. package/dist/version.js +1 -1
  187. package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
  188. package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
  189. package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +2 -1
  190. package/docs/adr/0015-inherit-composition-format-from-the-project.md +2 -2
  191. package/docs/adr/0016-author-compositions-as-html-fragments.md +1 -1
  192. package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
  193. package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
  194. package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
  195. package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
  196. package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
  197. package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
  198. package/docs/migration-0.4.md +2 -2
  199. package/docs/preview-testing.md +19 -6
  200. package/docs/{v0.1-design.md → v0.x-design.md} +55 -30
  201. package/examples/blend/README.md +3 -3
  202. package/examples/demo/README.md +2 -2
  203. package/examples/intro/README.md +71 -0
  204. package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
  205. package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
  206. package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
  207. package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
  208. package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
  209. package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
  210. package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
  211. package/examples/intro/assets/grain.png +0 -0
  212. package/examples/intro/assets/score.ogg +0 -0
  213. package/examples/intro/compositions/brand.html +412 -0
  214. package/examples/intro/compositions/chrome.controller.js +63 -0
  215. package/examples/intro/compositions/chrome.html +256 -0
  216. package/examples/intro/compositions/lattice.controller.js +148 -0
  217. package/examples/intro/compositions/lattice.html +308 -0
  218. package/examples/intro/compositions/opening.html +504 -0
  219. package/examples/intro/compositions/studio.html +930 -0
  220. package/examples/intro/compositions/theme.css +218 -0
  221. package/examples/intro/fy.config.js +13 -0
  222. package/examples/intro/fy.fonts.json +100 -0
  223. package/examples/intro/scripts/make-grain.mjs +79 -0
  224. package/examples/intro/scripts/make-score.mjs +702 -0
  225. package/examples/intro/timelines/intro.timeline.json +60 -0
  226. package/examples/keyframes/README.md +76 -0
  227. package/examples/keyframes/compositions/backdrop.html +60 -0
  228. package/examples/keyframes/compositions/card.html +99 -0
  229. package/examples/keyframes/fy.config.js +10 -0
  230. package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
  231. package/examples/nested/README.md +112 -0
  232. package/examples/nested/assets/pulse.wav +0 -0
  233. package/examples/nested/compositions/backdrop.html +84 -0
  234. package/examples/nested/compositions/card.html +275 -0
  235. package/examples/nested/compositions/overlay.html +307 -0
  236. package/examples/nested/fy.config.js +13 -0
  237. package/examples/nested/scripts/make-pulse.sh +20 -0
  238. package/examples/nested/timelines/chapter-a.timeline.json +39 -0
  239. package/examples/nested/timelines/chapter-b.timeline.json +39 -0
  240. package/examples/nested/timelines/nested-reel.timeline.json +52 -0
  241. package/examples/static/README.md +2 -2
  242. package/examples/transitions/README.md +128 -0
  243. package/examples/transitions/assets/alpha.wav +0 -0
  244. package/examples/transitions/assets/beta.wav +0 -0
  245. package/examples/transitions/assets/gamma.wav +0 -0
  246. package/examples/transitions/compositions/alpha.html +56 -0
  247. package/examples/transitions/compositions/beta.html +56 -0
  248. package/examples/transitions/compositions/gamma.html +56 -0
  249. package/examples/transitions/fy.config.js +12 -0
  250. package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
  251. package/examples/transitions/timelines/transitions.timeline.json +31 -0
  252. package/examples/video/README.md +4 -4
  253. package/examples/webgl/README.md +1 -1
  254. package/package.json +12 -5
  255. package/scaffolds/init/AGENTS.md +131 -0
  256. package/scaffolds/init/README.md +56 -0
  257. package/scaffolds/init/compositions/hello.html +79 -0
  258. package/scaffolds/init/fy.config.js +9 -0
  259. package/scaffolds/init/gitignore.template +1 -0
  260. package/scaffolds/init/package.json +11 -0
  261. package/scaffolds/init/props/hello.json +4 -0
  262. package/scaffolds/init/props/main.json +6 -0
  263. package/scaffolds/init/timelines/main.timeline.json +20 -0
  264. package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
  265. package/scaffolds/local-models/fy.policy.json +5 -0
  266. package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
  267. package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
  268. package/scaffolds/local-models/pi/sandbox.json +15 -0
  269. package/scaffolds/local-models/pi/settings.json +15 -0
  270. package/skills/frameyard/README.md +1 -0
  271. package/skills/frameyard/SKILL.md +59 -61
  272. package/skills/frameyard/references/audio-assets.md +32 -3
  273. package/skills/frameyard/references/common-mistakes.md +53 -2
  274. package/skills/frameyard/references/deterministic-authoring.md +10 -3
  275. package/skills/frameyard/references/diagnostics.md +31 -1
  276. package/skills/frameyard/references/failure-recovery.md +32 -6
  277. package/skills/frameyard/references/fonts.md +132 -0
  278. package/skills/frameyard/references/json-results.md +119 -6
  279. package/skills/frameyard/references/quick-reference.md +222 -0
  280. package/skills/frameyard/references/timelines.md +97 -7
  281. /package/examples/blend/{video.config.js → fy.config.js} +0 -0
  282. /package/examples/demo/{video.config.js → fy.config.js} +0 -0
  283. /package/examples/static/{video.config.js → fy.config.js} +0 -0
  284. /package/examples/video/{video.config.js → fy.config.js} +0 -0
  285. /package/examples/webgl/{video.config.js → fy.config.js} +0 -0
@@ -2,9 +2,9 @@
2
2
 
3
3
  **Status:** Accepted
4
4
  **Initial acceptance:** 2026-10-27
5
- **Last design revision:** 2026-11-03
5
+ **Last design revision:** 2026-11-04
6
6
 
7
- This document records the decisions confirmed during design sessions. It is the living product and architecture baseline; later changes revise this document explicitly rather than silently broaden the scope. The original v0.1 baseline has been revised through 0.8 and extended with static clip blend modes. Architecture decisions that changed the baseline are recorded in `docs/adr/`.
7
+ This document records the decisions confirmed during design sessions. It is the living product and architecture baseline; later changes revise this document explicitly rather than silently broaden the scope. Architecture decisions that changed the baseline are recorded in `docs/adr/`.
8
8
 
9
9
  ## 1. Product contract
10
10
 
@@ -31,7 +31,7 @@ The agent-enabled success criterion additionally requires an agent using the off
31
31
  ### Project
32
32
 
33
33
  - Projects use ordinary HTML, CSS, static assets, and native JavaScript controllers, either inline in a composition fragment or in external modules.
34
- - `video.config.js` explicitly maps project IDs to source files. There is no directory scanning. It has three optional maps:
34
+ - `fy.config.js` explicitly maps project IDs to source files. There is no directory scanning. It has three optional maps:
35
35
  - `format`: the project's fixed `width`, `height`, and `fps`.
36
36
  - `compositions`: IDs mapped to `.html` composition fragments.
37
37
  - `timelines`: IDs mapped to JSON timeline documents.
@@ -55,7 +55,7 @@ The agent-enabled success criterion additionally requires an agent using the off
55
55
 
56
56
  - A timeline is a versioned JSON document. It references compositions and nested timelines by project ID and media clips by project-relative path.
57
57
  - A timeline has a project-fixed format, an ordered list of tracks, and a list of clips. It may declare a `background` color and an optional `durationInFrames`.
58
- - Tracks are `video` or `audio`. Video tracks composite from bottom to top; audio tracks mix. Each clip belongs to exactly one track. Clips on the same track cannot overlap; clips on different tracks can. Touching endpoints are allowed. Transitions are deferred, so overlap within a track would have ambiguous precedence.
58
+ - Tracks are `video` or `audio`. Video tracks composite from bottom to top; audio tracks mix. Each clip belongs to exactly one track. Clips on the same track cannot overlap unless a timeline-level `transitions` entry explicitly pairs them; clips on different tracks can. Touching endpoints are allowed but are not a transition. A transition's window is the overlap, and its declared geometry is `from.start < to.start < from.end < to.end`. See [ADR 0019](./adr/0019-make-same-track-overlap-the-transition-window.md).
59
59
  - A clip has an ID, a track, a source, a timeline start, a source in-point, a duration, and clip-specific presentation settings. Source in-point, duration, and timeline start are integer project frames.
60
60
  - Clip sources are:
61
61
  - a composition, identified by its project ID;
@@ -64,12 +64,14 @@ The agent-enabled success criterion additionally requires an agent using the off
64
64
  - A composition clip fixes props for that instance and may trim the composition's source range. The same composition may appear multiple times with different props, source ranges, transforms, and audio settings. A clip whose composition source is static behaves like an image: it must declare `duration`, must use `sourceIn: 0`, has no intrinsic source duration, and contributes no audio or transcripts. The renderer captures one transparent PNG per composition/props pair and reuses it across segments.
65
65
  - A nested timeline clip is a visual and audio source. It uses the project format, trims with the same source in-point and duration, contributes its audio, and is rendered as a group so parent opacity, transform, and blend mode apply to the whole nested result. A blend mode inside a nested timeline sees only that timeline's own backdrop; a transparent nested timeline does not see the parent's layers, and a declared nested background is part of the group. Cycles are rejected statically. Timeline clips do not override props of compositions inside the nested timeline.
66
66
  - Media clips use local project files only. Video and audio durations, dimensions, and audio presence are probed before frame production. Images and static composition clips require an explicit duration. A video or audio clip that exceeds its source range is an error; an omitted duration uses the remaining source.
67
- - Visual clips support a static transform: fit policy (`contain`, `cover`, `stretch`), an optional explicit `width`/`height` box (both fields together, mutually exclusive with `scale`) or a `scale` multiplier, pixel offset, rotation, and opacity. `fit` applies inside the box; without one, the project frame is the box.
68
- - Visual clips may declare a static `blend` mode. It uses CSS `mix-blend-mode` semantics against the accumulated backdrop of all lower layers and the timeline background, respects the clip's source alpha, and defaults to `normal`. The supported modes are `normal`, `multiply`, `screen`, `overlay`, `darken`, `lighten`, `color-dodge`, `color-burn`, `hard-light`, `soft-light`, `difference`, and `exclusion`; `hue`, `saturation`, `color`, and `luminosity` are rejected. There are no keyframes, masks, filters, or other effects.
67
+ - Visual clips support a static transform: fit policy (`contain`, `cover`, `stretch`), an optional explicit `width`/`height` box (both fields together, mutually exclusive with `scale`) or a `scale` multiplier, pixel offset, rotation, and opacity. `fit` applies inside the box; without one, the project frame is the box. Rotation expands to the full rotated bounds rather than clipping corners.
68
+ - Visual clips may animate `x`, `y`, `scale`, `rotation`, and `opacity` with a per-channel `keyframes` object. Keys use clip-local integer frames; `0` is the clip start boundary and `duration` the end boundary. Keys hold before the first and after the last key, never extend the clip, and use a CSS timing function on the interval after each key. A keyed channel cannot also be an explicit scalar in `transform`. `opacity` and `scale` are clamped after easing; position and rotation may overshoot. `keyframes.scale` is mutually exclusive with an explicit `width`/`height` box. Timeline document version stays 1.
69
+ - A timeline may declare `transitions`: `{ from, to, kind, easing? }`, where `kind` is `dissolve` on a video track or `crossfade` on an audio track, and `easing` uses the keyframe CSS timing-function vocabulary. Both clips stay on one track and overlap; the window is `to.start .. from.end`. A dissolve composites the incoming clip above the outgoing clip with alpha `clipOpacity × eased progress`; both kinds apply equal-power audio gain (`sqrt(1 - t)` and `sqrt(t)`) to contributed audio or to the two audio clips. An overlap without a declared transition, a non-overlapping pair, reversed or contained geometry, cross-track pairs, a kind that does not match the track, duplicate participation, and unknown kinds are errors. Timeline document version stays 1.
70
+ - Visual clips may declare a static `blend` mode. It uses CSS `mix-blend-mode` semantics against the accumulated backdrop of all lower layers and the timeline background, respects the clip's source alpha, and defaults to `normal`. The supported modes are `normal`, `multiply`, `screen`, `overlay`, `darken`, `lighten`, `color-dodge`, `color-burn`, `hard-light`, `soft-light`, `difference`, and `exclusion`; `hue`, `saturation`, `color`, and `luminosity` are rejected. There are no animated blend modes, masks, filters, or other effects.
69
71
  - Visual clips contribute their audio to the timeline mix unless muted: composition audio, embedded video audio, and nested timeline audio. Static compositions contribute no audio; clip audio settings on a static composition are ignored with a warning. Audio-only clips live on audio tracks and may source audio or video files. Per-clip and per-audio-track gain and mute are supported. There are no linked A/V pairs; a J/L cut is expressed by muting a video clip and placing a separate audio clip with a different source in-point.
70
72
  - A timeline's duration is derived from the latest clip end. An explicit `durationInFrames` may extend it but not cut clips. A timeline with no clips is invalid; an audio-only timeline is valid and renders black video with audio.
71
73
  - A timeline background is transparent by default. Blend modes see the accumulated backdrop including this background. The final video composites it over black; a nested timeline remains transparent so lower layers can show through. A composition that paints no background is transparent and may be used as an overlay.
72
- - The preview provides a read-only timeline view: tracks, clips, playhead, program monitor, and diagnostics. It is not a visual editor.
74
+ - The preview provides a read-only timeline view: tracks, clips, keyframe markers, playhead, program monitor, and diagnostics. It is not a visual editor.
73
75
 
74
76
  ## 3. Deterministic execution
75
77
 
@@ -81,10 +83,10 @@ The timeline frame is the master clock. A composition's frame is derived from th
81
83
  - JavaScript animation runs in the controller's `update()`, which must be synchronous and seek-safe. It cannot depend on previously visited frames.
82
84
  - Declarative bindings apply once per load, before the controller mounts. Controller-created DOM is the controller's responsibility.
83
85
  - Independent `requestAnimationFrame` loops, timers, CSS transitions, wall-clock reads, and unseeded randomness are not supported animation drivers.
84
- - Timeline clips use static transforms and static blend modes. Timeline-level animation, transitions, and other effects are not supported.
86
+ - Timeline clip keyframes animate `x`, `y`, `scale`, `rotation`, and `opacity` from the integer clip-local frame. They are evaluated against absolute timeline frames, so segment boundaries do not change values. Preview evaluates the exact CSS timing function; production generates FFmpeg expressions and approximates the curve. Blend modes remain static. Transition progress is likewise evaluated from the absolute timeline frame; only the incoming clip carries transition alpha during a dissolve, and both kinds use the same eased progress for equal-power audio gain.
85
87
  - Preview and production seek composition clips explicitly. The timeline compositor is deterministic given the captured clip frames, media frames, clip settings, and segment boundaries.
86
88
  - The resolved format—declared width, height, and fps combined with the project format—is part of the immutable frame environment, alongside props and loaded assets. Layouts that use viewport units or percentages therefore depend on the resolved format by design; preview and production always resolve the same values.
87
- - Assets, remote data, images, fonts, generated audio, transcripts, and media are resolved before frame production. For a segmented timeline render, each segment's composition and nested-timeline clips are loaded and settled before that segment's frames are captured; no frame production depends on a new network request. Network is blocked during capture.
89
+ - Assets, remote data, images, fonts, generated audio, transcripts, and media are resolved before frame production. Project fonts are vendored locally, recorded in `fy.fonts.json`, and injected as `@font-face` rules; the host waits for `document.fonts.ready` and fails a used face that reports an error. For a segmented timeline render, each segment's composition and nested-timeline clips are loaded and settled before that segment's frames are captured; no frame production depends on a new network request. Network is blocked during capture.
88
90
  - AI model inference is never part of the loading phase, controller lifecycle, or frame production.
89
91
  - The baseline supports DOM, CSS, SVG, Canvas 2D, WebGL, and frame-seeked embedded video. Canvas 2D and WebGL are drawn from the current frame by the controller.
90
92
  - Embedded video and audio inside compositions are declared with `data-fy-*` attributes. The runtime loads local files, pauses them, and seeks video from the frame-derived source time before capture. An unmuted video contributes its audio track to the timeline mix unless the composition opts out or the clip mutes it.
@@ -100,12 +102,12 @@ Preview and production share composition behavior and capture composition frames
100
102
 
101
103
  ## 4. Preview and production rendering
102
104
 
103
- Preview and production execute the same composition sources and host runtime. They differ in which component drives the frame clock, which compositor combines timeline layers, and whether frames are captured.
105
+ Preview and production execute the same composition sources and host runtime. They differ in which component drives the frame clock, which compositor combines timeline layers, and whether frames are captured. Both also project the same timeline scene from the resolved timeline: the ordered active clips with their transition roles, composite order, frame-to-source mapping, capture modes, and presentation defaults. The scene projection is browser-safe; capture sessions, audio mixing, and both compositors stay separate.
104
106
 
105
107
  The preview provides:
106
108
 
107
109
  - Composition preview: composition selection, JSON props editing, play/pause, frame-accurate scrubbing, frame-by-frame stepping with buttons or arrow keys, one-second jumps with buttons or Shift+arrow keys, current frame and time, audio controls, and diagnostics. A static composition loads once and disables the transport.
108
- - Timeline preview: timeline selection, a read-only track/clip view, playhead, program monitor, frame-by-frame stepping with buttons or arrow keys, one-second jumps with buttons or Shift+arrow keys, audio controls, and diagnostics. A static composition layer stays loaded and is not seeked per frame.
110
+ - Timeline preview: timeline selection, a read-only track/clip view with keyframe markers, playhead, program monitor, frame-by-frame stepping with buttons or arrow keys, one-second jumps with buttons or Shift+arrow keys, audio controls, and diagnostics. A static composition layer stays loaded and is not seeked per frame.
109
111
  - A bounded pool of active composition and nested-timeline clips. It loads clips around the playhead instead of preloading a whole long timeline.
110
112
 
111
113
  The preview server serves each composition fragment at its project path, wraps it in the fixed document shell, and injects the framework import map and host runtime. It serves timeline documents and a timeline preview host. The timeline preview host drives active composition/nested-timeline clips, seeks them, and exposes a timeline API to the preview. Preview audio is mixed by the preview page from the resolved timeline audio clips.
@@ -114,15 +116,15 @@ Production composition rendering uses one Chromium session per composition. An a
114
116
 
115
117
  Production timeline rendering is segmented and parallel:
116
118
 
117
- 1. Run static timeline validation and resolve the render plan.
119
+ 1. Run static timeline validation, resolve the timeline, and project the scene and render plan.
118
120
  2. Split the timeline into segments at active-layer changes or a maximum segment duration. Each segment has a fixed set of visual layers and a fixed filter graph.
119
121
  3. In parallel workers, capture the active composition and nested-timeline clip frames for each segment with Chromium and encode them to lossless FFV1 intermediates. Transparent clips are captured with an alpha-preserving background. A static composition is captured once per props variant as a transparent PNG and reused as an image layer across segments and workers.
120
- 4. Decode media clips, apply trims, fit, static transforms, and opacity, composite the segment with FFmpeg, and encode H.264, HEVC, or VP9.
122
+ 4. Decode media clips, apply trims, fit, presentation keyframes, rotation into an expanded bound, and opacity, composite the segment with FFmpeg, and encode H.264, HEVC, or VP9.
121
123
  5. Join the ordered segments with a stream copy. Segment boundaries never change frame order or timing.
122
124
  6. Mix the timeline audio once with FFmpeg and mux it into the final output.
123
125
  7. Publish the output atomically; delete scratch intermediates on success and failure.
124
126
 
125
- The default timeline worker count is 8, configurable up to the machine's capacity. With `--encoder nvenc` the count is capped at eight concurrent encode sessions, the consumer driver limit; a larger `--workers` value produces an `ENCODER_WARNING` and the lower count. Scratch is a configurable directory with a bounded budget; it must not depend on `/tmp` alone. The reference long-form target is a representative 60-minute 1920×1080 30 fps timeline with a 50/50 composition/media split by layer-seconds, 60% one active layer, 30% two, and 10% three, rendered in 30 minutes or less on the reference machine. The 10-second benchmark measured 10.7 s for two composition captures, 35.2 MB of FFV1, and 6.1 s to composite and encode. Continuous three-layer stress runs measured 52.4 s and 58.5 s for 60 s of timeline, about 1× real time, and are documented as worst-case rather than the target workload. The representative gate measured 29.39 s for 60 s with four workers, exact 1800-frame output, and a 1080p media fast path, extrapolating to about 29.4 minutes for 60 minutes. The full 60-minute composition-only gate rendered 108,000 frames in 35.35 minutes with exact 108,000-frame / 3,600 s output; it is stricter than the 50/50 target mix because every frame is an HTML composition capture. The fast path copies full-frame, untransformed video whose source matches the project format and has keyframes at segment boundaries; media that needs scaling or transforms is re-encoded and adds cost not included in that projection.
127
+ The default timeline worker count is 8, configurable up to the machine's capacity. With `--encoder nvenc` the count is capped at eight concurrent encode sessions, the consumer driver limit; a larger `--workers` value produces an `ENCODER_WARNING` and the lower count. Scratch is a configurable directory with a bounded budget; it must not depend on `/tmp` alone. The reference long-form target is a representative 60-minute 1920×1080 30 fps timeline with a 50/50 composition/media split by layer-seconds, 60% one active layer, 30% two, and 10% three, rendered in 30 minutes or less on the reference machine. The 10-second benchmark measured 10.7 s for two composition captures, 35.2 MB of FFV1, and 6.1 s to composite and encode. Continuous three-layer stress runs measured 52.4 s and 58.5 s for 60 s of timeline, about 1× real time, and are documented as worst-case rather than the target workload. The representative gate measured 29.39 s for 60 s with four workers, exact 1800-frame output, and a 1080p media fast path, extrapolating to about 29.4 minutes for 60 minutes. The full 60-minute composition-only gate rendered 108,000 frames in 35.35 minutes with exact 108,000-frame / 3,600 s output; it is stricter than the 50/50 target mix because every frame is an HTML composition capture. The fast path copies full-frame, untransformed video whose source matches the project format and whose source keyframes (I-frames) fall on segment boundaries; media that needs scaling or transforms is re-encoded and adds cost not included in that projection. Timeline clip keyframes add per-frame filter expressions and, for animated opacity or scale, an alpha-mask pass per affected layer; the keyframe benchmark reports current timings and is not part of the 60-minute projection.
126
128
 
127
129
  The renderer uses a bounded segment plan, a worker pool, and a scratch budget. It never publishes a partial output, and it cleans scratch on success and failure. Segment boundaries preserve timeline frame order and timing; the final audio mix is muxed once.
128
130
 
@@ -153,16 +155,23 @@ A render fails immediately on an uncaught exception, unhandled rejection, requir
153
155
 
154
156
  Project and composition commands are:
155
157
 
156
- - `init`: scaffold a timeline-first project with a composition, a one-clip timeline, a project format, an agent guide, a `.gitignore` for `out/`, and a private `package.json` with pinned `dev`, `check`, `render`, and `still` scripts.
158
+ - `init [directory] [--local-models]`: scaffold a timeline-first project with a composition, a one-clip timeline, a project format, an agent guide, a `.gitignore` for `out/`, and a private `package.json` with pinned `dev`, `check`, `render`, and `still` scripts. Without a directory, an interactive terminal prompts for a project name (default `my-video`) and creates that directory; non-interactive and `--json` runs use `my-video`. `--local-models` also installs the optional workspace policy, OpenCode permissions, and Pi settings.
157
159
  - `dev`: start the preview with composition and timeline modes.
158
160
  - `render <id> [--out video]`: render a timeline, an animated composition, or a static composition. Timeline renders accept `--workers <n>` and `--scratch <dir>`; composition renders accept `--concurrency <n>` from 1 to 16. `--container <mp4|mov|mkv|webm>` and `--codec <h264|hevc|vp9>` select the delivery pair (MP4/H.264 by default; WebM defaults to VP9), and `--encoder <auto|software|nvenc|qsv>` selects how it is encoded; `auto` is the default and probes NVENC then Quick Sync before falling back to software. The output extension must match the container. The default output is `out/<id>.<ext>`. A static composition writes `out/<id>.png` and rejects `--container`, `--codec`, `--encoder`, and `--concurrency`. The JSON result is a discriminated union with `kind: "video"` or `kind: "image"`.
161
+ - `context`: print a read-only project digest with the format, composition metadata, prop keys and types, controller declarations, timeline clip summaries, unique assets, installed fonts, diagnostics, active policy, and next commands. It never launches Chromium.
162
+ - `lint <id>`: run static validation, timeline resolution, scene and keyframe projection for every timeline frame, a controller determinism scan, and media metadata probing without Chromium. It does not run `validateProps` or composition runtime; `check` remains the full gate.
163
+ - `inspect <image>` and `compare <first> <second>`: decode images through FFmpeg and report blank/alpha/color/content facts or a pixel and 3×3 region diff. These are text-only inspection aids, not a substitute for viewing a still.
164
+ - `add --list`, `add local-models`, and `add <composition|timeline> <id> --template <name>`: list templates and scaffolds, install the optional local-model host scaffold, or write a valid template entry and print the exact `fy.config.js` registration snippet. `add` never edits the config and never overwrites an existing file or registered id.
165
+ - `font search`, `font add <id>`, `font add --file`, `font add --url`, `font list`, `font verify`, and `font remove <id>`: search Fontsource, vendor a Fontsource/local/URL font into `assets/fonts/<id>/`, record it in `fy.fonts.json`, inspect its license/hash status, or remove it. The server serves the generated `@font-face` sheet at `/__frameyard/fonts.css` and injects it into composition documents. Fontsource installs pin `npmVersion` and vendor the package license; URL imports require HTTPS outside loopback tests and record an unknown license only in the `full` tier.
159
166
  - `check <id>`: run static validation, then validate configuration, props, assets, media, audio, and transcript requirements, load the target, and evaluate every frame without capturing or encoding. A static composition is loaded and inspected once and returns `kind: "static"` with `framesChecked: 1`.
160
167
  - `still <id> --frame <spec>`: capture one or more requested frames as PNGs through the clip capture and timeline composite path. A static composition exposes exactly frame 0. `<spec>` is a frame number (`42`), an inclusive range (`2-12`), or a comma-separated list (`0,118-126,240`); duplicates are removed preserving the first occurrence, input order is preserved, and one invocation captures at most 32 unique frames. There is no step or sequence mode. A single frame keeps the existing `--out <file.png>` behavior; multiple frames use `--out <directory>` and write `still-<id>-<frame>.png` files, defaulting to the current working directory. Frames and output conflicts are validated before Chromium, all frames are captured to temporary files before publication, and a failed capture publishes no files. JSON results add `frames` and keep the singular `frame` and `path` only when one frame was captured. See [ADR 0013](./adr/0013-capture-bounded-multi-frame-stills-for-inspection.md).
161
- - `doctor`: inspect core requirements and optional integrations without repairing or downloading anything.
168
+ - `doctor`: inspect core requirements, optional integrations, the active resource policy, installed font manifests, and basic CPU/load/memory resources without repairing or downloading anything.
162
169
 
163
- `check`, `still`, and `render` run a Node-side static validation before launching Chromium. For a timeline it validates the JSON document, tracks, clips, references, nested timelines, cycles, frame-rate compatibility, overlaps, source bounds, media, transforms, audio settings, props, and every referenced composition. Static validation and the composition host read the composition grammar through one browser-safe reader with document and fragment scopes. Timeline diagnostics carry stable codes and optional source locations or JSON paths, and composition diagnostics from inside a timeline carry the clip and track context.
170
+ `lint`, `check`, `still`, and `render` run a Node-side static validation; `check`, `still`, and `render` then launch Chromium. For a timeline it validates the JSON document, tracks, clips, references, nested timelines, cycles, frame-rate compatibility, overlaps and transitions, source bounds, media, transforms, keyframes, audio settings, props, and every referenced composition. Static validation and the composition host read the composition grammar through one browser-safe reader with document and fragment scopes. Timeline diagnostics carry stable codes and optional source locations or JSON paths, and composition diagnostics from inside a timeline carry the clip and track context.
164
171
 
165
- Local asset-production commands are `tts` and `transcribe`. Every non-interactive command supports `--json` with stable result and error schemas. Structured results go to stdout; human diagnostics and inference progress go to stderr.
172
+ Local asset-production commands are `tts` and `transcribe`. Every non-interactive command supports `--json` with stable result and error schemas. Structured results go to stdout; human diagnostics and inference progress go to stderr. Project font acquisition is a separate authoring operation: `fy.fonts.json` records vendored files, licenses, and SHA-256 values; there is no shared font cache and no runtime font CDN. Fontsource search and download use the public Fontsource API and pinned CDN versions; local files and explicit HTTPS URLs cover other sources. The generated `@font-face` sheet is served at `/__frameyard/fonts.css` and injected before author styles.
173
+
174
+ A workspace `fy.policy.json` gates expensive operations. The nearest file from the working directory upward applies, independent of `fy.config.js`; `FRAMEYARD_POLICY` replaces it. `light` allows one still frame and blocks `check`, `tts`, `transcribe`, and `render`; `authoring` allows four still frames and `check` at low priority and blocks the three heavy operations; `full` allows everything up to the 32-frame still cap. Font list/verify/search are available in every tier; `font add` requires authoring or full; `font remove`, `font add --force`, and an unlicensed `font add --url` require full. A file can only tighten its tier. `--profile shared-cpu` caps timeline workers at two and composition concurrency at one and lowers process priority. `tts`, `transcribe`, and `render` accept `--dry-run`, which validates inputs and returns `plan`, `commandLine`, `prerequisites`, and `blockers` without writing outputs, loading a model, launching Chromium, or encoding. The policy is a guardrail, not a security boundary; see [ADR 0022](./adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md).
166
175
 
167
176
  The built-in server serves native ES modules, static assets, composition entries, timeline documents, and the timeline preview host. It wraps each configured composition fragment in the fixed document shell, injects the framework import map and composition host, and serves an inline controller through a virtual module URL. It performs no dependency pre-bundling. Third-party libraries must initially be browser-ready ES modules referenced explicitly. Running through the CLI server is required; direct `file://` execution is unsupported for deterministic preview or rendering.
168
177
 
@@ -184,20 +193,24 @@ The skill:
184
193
  - Uses the normal project format, HTML composition contract, JSON timeline contract, optional controller, and CLI available to humans.
185
194
  - Does not introduce an agent-only runtime, MCP server, hosted control plane, or separate rendering API.
186
195
  - Triggers for Frameyard authoring and operation rather than claiming to be a general-purpose media-generation skill.
187
- - Uses Frameyard's audio wrappers instead of invoking `qwen-tts` or `whisper-cli` directly.
188
- - Includes references for deterministic HTML authoring, timeline JSON authoring, tracks, clips, source ranges, props, media, nested timelines, static transforms, audio, validation, diagnostics, common mistakes, subtitle and karaoke recipes, JSON command results, long-form worker/scratch settings, and failure recovery.
196
+ - Uses Frameyard's audio wrappers instead of invoking `qwen-tts`, `sherpa-onnx-offline-tts`, or `whisper-cli` directly.
197
+ - Includes references for deterministic HTML authoring, timeline JSON authoring, tracks, clips, source ranges, props, media, nested timelines, static transforms, clip keyframes, clip transitions, audio, validation, diagnostics, common mistakes, subtitle and karaoke recipes, JSON command results, long-form worker/scratch settings, and failure recovery, plus a compact quick reference for limited-context hosts.
189
198
  - Invokes speech synthesis or transcription only when requested or explicitly approved as part of the brief.
190
199
  - Does not overwrite or regenerate existing generated assets without permission.
200
+ - Uses `lint` for the edit loop, `check` for the full gate, and `--dry-run` to hand off heavy operations when the workspace resource policy blocks them.
201
+ - Respects `fy.policy.json` and does not edit the policy file, host configuration, or skill source.
191
202
  - Runs `check`, inspects representative `still` images when the host supports image viewing, and discloses when visual inspection was impossible.
192
203
  - Does not claim success when checks fail or rendering is partial.
193
204
 
194
205
  A successful agent workflow preserves and reports the source project, timeline document, props, generated assets, provenance manifest, validation results, and final video path. The user must be able to inspect, modify, and reproduce the result rather than receiving only an opaque MP4.
195
206
 
207
+ Local-model host setup is optional and separate from a Frameyard project: `scaffolds/local-models/` contains an `authoring` policy, OpenCode permissions, and Pi settings that a workspace can copy when a local model and rendering share the machine. Without the scaffold, the CLI defaults to `full` and no host configuration applies.
208
+
196
209
  ## 8. Local speech synthesis
197
210
 
198
211
  The `tts` command integrates [`qwentts.cpp`](https://github.com/ServeurpersoCom/qwentts.cpp) through its external `qwen-tts` executable. It does not link the native library or require the upstream server.
199
212
 
200
- 0.6 supports CustomVoice mode only:
213
+ The qwen engine supports CustomVoice mode only:
201
214
 
202
215
  - A named speaker is required.
203
216
  - An optional style instruction and language may be supplied.
@@ -207,7 +220,15 @@ The `tts` command integrates [`qwentts.cpp`](https://github.com/ServeurpersoCom/
207
220
  - One invocation produces one frozen 24 kHz mono WAV from one complete text input.
208
221
  - Automatic long-text chunking, streaming, and alternative audio formats are deferred.
209
222
 
210
- Users acquire and provide models explicitly. Frameyard does not build `qwentts.cpp`, download or redistribute models, or imply that upstream code licenses settle model-weight licensing. Executable resolution uses an explicit option, then environment configuration, then PATH.
223
+ `tts --engine supertonic` integrates [`sherpa-onnx`](https://github.com/k2-fsa/sherpa-onnx) through its external `sherpa-onnx-offline-tts` executable and the Supertonic 3 model archive:
224
+
225
+ - The engine covers 31 languages and requires one of their language codes, a numeric speaker id, and a directory containing the seven model files.
226
+ - The engine writes its native WAV; Frameyard normalizes it to the same frozen 24 kHz mono WAV contract with FFmpeg and records `sourceSampleRate` and `sourceChannels` in the manifest.
227
+ - The manifest records the seven model identities, the pinned sherpa-onnx revision, and the model archive name.
228
+ - Supertonic expression tags belong in the text; the qwen-only style and device options are rejected for this engine.
229
+ - The upstream Supertonic repository is archived and its weights use the OpenRAIL-M license. Users acquire the executable and weights explicitly; Frameyard does not redistribute them.
230
+
231
+ Users acquire and provide models explicitly. Frameyard does not build `qwentts.cpp` or `sherpa-onnx`, download or redistribute models, or imply that upstream code licenses settle model-weight licensing. Executable resolution uses an explicit option, then environment configuration, then PATH.
211
232
 
212
233
  ## 9. Local transcription and timed text
213
234
 
@@ -247,14 +268,14 @@ Generated artifacts follow a shared contract:
247
268
 
248
269
  - Outputs are written atomically to explicit paths.
249
270
  - Existing outputs are not overwritten unless `--force` is supplied.
250
- - A versioned project-local provenance manifest records artifact content hashes, generator details, model identities, and parameters. The default manifest is `frameyard.assets.json` in the project root; `--manifest` selects another path.
271
+ - A versioned project-local provenance manifest records artifact content hashes, generator details, model identities, and parameters. The default manifest is `fy.assets.json` in the project root; `--manifest` selects another path.
251
272
  - Transcription provenance also identifies the source asset by content hash.
252
273
  - Partial operations leave neither apparently valid artifacts nor manifest entries.
253
- - `doctor` validates executables, model readability, capabilities, and supported integration versions without modifying the environment.
274
+ - `doctor` validates executables, model readability, capabilities, and supported integration versions when the upstream CLI exposes a version, without modifying the environment.
254
275
 
255
276
  Each operation runs one inference subprocess, and the official agent workflow executes inference sequentially to avoid unexpected CPU/GPU contention. There is no arbitrary fixed timeout. Cancellation terminates the child process and removes partial outputs.
256
277
 
257
- Release documentation must identify the exact `qwentts.cpp` and `whisper.cpp` revisions against which the wrappers are tested. Frameyard does not claim compatibility with arbitrary upstream revisions.
278
+ Release documentation must identify the exact `qwentts.cpp`, `sherpa-onnx`, and `whisper.cpp` revisions against which the wrappers are tested. Frameyard does not claim compatibility with arbitrary upstream revisions.
258
279
 
259
280
  ## 11. Verification strategy
260
281
 
@@ -262,7 +283,7 @@ Tests prioritize public behavior and end-to-end evidence over implementation-onl
262
283
 
263
284
  - HTML compositions validate metadata, default props, bindings, conditionals, repeats, and controller declarations, including inline/external parity and the exactly-one-controller rule.
264
285
  - Static composition validation catches metadata, root/controller count, binding, animation, script, element, and asset-reference errors before Chromium. It resolves declared format values against the project format and reports an unresolved format to the command rather than the document. A composition with neither time attribute is static; `fps` without `duration-in-frames`, `data-fy-anim-*`, media, and transcripts on a static composition are errors.
265
- - Timeline JSON validation catches schema, track, clip, reference, cycle, frame-rate, overlap, bounds, media, transform, audio, and props errors before Chromium.
286
+ - Timeline JSON validation catches schema, track, clip, reference, cycle, frame-rate, overlap, transition, bounds, media, transform, audio, and props errors before Chromium.
266
287
  - Diagnostics carry stable codes and source locations or JSON paths; JSON results expose the composition or timeline entry, clips, tracks, and top-level diagnostics.
267
288
  - Sequential, individual, random-order, and parallel composition renders produce equivalent images; timeline segment workers produce ordered output and identical captured composition frames.
268
289
  - Determinism checks compare captured composition pixels or stills. The final encoded bytes and the FFmpeg composite versus Chromium preview are not reproducibility oracles.
@@ -274,18 +295,19 @@ Tests prioritize public behavior and end-to-end evidence over implementation-onl
274
295
  - Nested timelines preserve group opacity and transform semantics; cycles fail static validation.
275
296
  - Media clips trim and fit deterministically; images require durations; source overruns fail.
276
297
  - Props overrides for timeline composition clips merge over clip props and are reflected in the render.
277
- - `check` exercises every animated frame and inspects a static composition once; `still` follows the clip capture and timeline composite path. A static composition render captures one PNG; a timeline reuses one static image capture per props variant.
298
+ - `check` exercises every animated frame and inspects a static composition once; it also evaluates every keyframe channel at every timeline frame. `still` follows the clip capture and timeline composite path. A static composition render captures one PNG; a timeline reuses one static image capture per props variant.
278
299
  - The 10-second and 60-second long-form benchmarks record capture, FFV1, composite, encode, disk, worker, and audio numbers. Continuous three-layer runs measured 52.4 s and 58.5 s for 60 s of timeline and are documented as worst-case (~1× real time). The representative 50/50, 60/30/10 occupancy gate measured 29.39 s for 60 s with four workers and a 1080p media fast path, extrapolating to about 29.4 minutes for 60 minutes with exact 1800-frame output. The full 60-minute composition-only gate rendered 108,000 frames in 35.35 minutes with exact output and clean scratch, documenting composition-heavy performance above 30 minutes.
279
300
  - Golden-frame tests cover representative composition and timeline visual behavior.
280
- - Normal CI uses fake `qwen-tts` and `whisper-cli` executables and small fixtures to verify arguments, artifacts, manifests, cancellation, and errors without model downloads or GPU hardware.
301
+ - Normal CI uses fake `qwen-tts`, `sherpa-onnx-offline-tts`, and `whisper-cli` executables and small fixtures to verify arguments, artifacts, manifests, cancellation, and errors without model downloads or GPU hardware.
281
302
  - Real-model integration tests are explicitly opt-in and use documented upstream revisions.
282
303
  - Agent-workflow evaluations verify source preservation, provenance, validation, still inspection behavior, failure reporting, and final renders.
304
+ - Local-model evals are opt-in and run against a llama.cpp router: eight authoring tasks, first-pass lint, repair turns, token and wall-time counts, and sampled server CPU/RSS/GPU. They exercise the same quick reference and CLI as an agent, never render, and stay outside `pnpm check`.
283
305
 
284
306
  ## 12. Deferred
285
307
 
286
308
  These are scope boundaries, not permanent rejections:
287
309
 
288
- - Transitions, filters, keyframes, masks, and audio fades
310
+ - Animated blend modes, filters, masks, and audio fades
289
311
  - Full visual timeline editing, drag-and-drop, clip handles, and undo/redo
290
312
  - Timeline-level variables and prop overrides for compositions inside nested timelines
291
313
  - Track solo and video-track visibility
@@ -299,9 +321,12 @@ These are scope boundaries, not permanent rejections:
299
321
  - Built-in subtitle rendering, automatic caption burn-in, and declarative karaoke elements
300
322
  - Nested `data-fy-repeat` scopes and a general template language
301
323
  - Declarative JSON Schema validation
324
+ - A shared font cache, hosted font catalog, or first-class Google Fonts/Fontshare/Bunny providers; Fontsource plus local/HTTPS imports cover acquisition
325
+ - Typography roles, font-pair presets, a preview font picker, and variable axes other than `wght`
302
326
  - Arbitrary executable scripts in composition HTML
303
327
  - Automatic upstream builds, model downloads, weight redistribution, and environment repair
304
328
  - Agent-only runtimes, MCP servers, hosted agent control planes, and host-specific skill installers
329
+ - Automatic idle scheduling, job queues, and OS-level resource enforcement; the workspace policy is a guardrail, not a sandbox
305
330
  - React or other UI-framework adapters in the core
306
331
  - Public plugin APIs and a broad integration ecosystem
307
332
  - Dynamic composition or timeline metadata derived from props
@@ -320,4 +345,4 @@ These are scope boundaries, not permanent rejections:
320
345
 
321
346
  ## 13. Release posture
322
347
 
323
- Frameyard is MIT-licensed. Initial releases use `0.x` versioning, and composition, timeline, configuration, CLI, JSON-result, transcript, and skill-contract changes are documented explicitly until those contracts stabilize. 0.6.0 adds timelines, project formats, segmented timeline rendering, and a timeline-first scaffold without breaking the existing HTML composition authoring contract. See [ADR 0010](./adr/0010-make-timelines-the-render-unit.md), [ADR 0011](./adr/0011-compose-timeline-frames-in-a-live-browser-host.md), and [ADR 0012](./adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md). 0.7.0 adds delivery container, codec, and encoder selection, bounded multi-frame still inspection, and optional encoder probes in `doctor` without breaking the timeline or composition contracts; bounded still inspection is recorded in [ADR 0013](./adr/0013-capture-bounded-multi-frame-stills-for-inspection.md). Static clip blend modes are recorded in [ADR 0014](./adr/0014-compose-timeline-clips-with-css-blend-modes.md). Optional composition `width`, `height`, and `fps` with project-format inheritance are recorded in [ADR 0015](./adr/0015-inherit-composition-format-from-the-project.md). 0.8.0 changes compositions to HTML fragments, inherits their format from the project, and renders timeless compositions as still images; the contracts are recorded in [ADR 0015](./adr/0015-inherit-composition-format-from-the-project.md), [ADR 0016](./adr/0016-author-compositions-as-html-fragments.md), and [ADR 0017](./adr/0017-render-timeless-compositions-as-stills.md).
348
+ Frameyard is MIT-licensed. Initial releases use `0.x` versioning, and features and contract changes are documented explicitly until those contracts stabilize.
@@ -62,11 +62,11 @@ examples/blend/
62
62
  timelines/blend.timeline.json
63
63
  assets/ink.mp4 procedural 12-second source clip
64
64
  scripts/make-ink.sh regenerate assets/ink.mp4
65
- video.config.js
65
+ fy.config.js
66
66
  ```
67
67
 
68
68
  The timeline lives in `timelines/blend.timeline.json`; the project format and
69
- entries are fixed in `video.config.js`. `backdrop.html` is a static composition
69
+ entries are fixed in `fy.config.js`. `backdrop.html` is a static composition
70
70
  that omits all dimensions and time metadata, so it inherits the project's
71
71
  1920×1080 size. `aurora.html` is a static composition that declares its
72
72
  dimensions but no time metadata. `labels.html` is an animated composition and
@@ -81,7 +81,7 @@ pnpm build
81
81
  node dist/cli.js check blend-timeline --project examples/blend
82
82
  node dist/cli.js still blend-timeline --frame 15,45,75,105,135,165,195,225,255,285,315,345 --project examples/blend --out blend-stills
83
83
  node dist/cli.js still blend-timeline --frame 285 --project examples/blend --out blend-soft-light.png
84
- node dist/cli.js render blend-timeline --project examples/blend
84
+ FRAMEYARD_POLICY=full node dist/cli.js render blend-timeline --project examples/blend
85
85
  node dist/cli.js dev --project examples/blend
86
86
  ```
87
87
 
@@ -19,13 +19,13 @@ From the repository root:
19
19
  pnpm build
20
20
  node dist/cli.js check demo-timeline --project examples/demo
21
21
  node dist/cli.js still demo-timeline --frame 240 --project examples/demo --out demo-still.png
22
- node dist/cli.js render demo-timeline --project examples/demo --props examples/demo/props/demo.json
22
+ FRAMEYARD_POLICY=full node dist/cli.js render demo-timeline --project examples/demo --props examples/demo/props/demo.json
23
23
  ```
24
24
 
25
25
  The composition remains renderable on its own:
26
26
 
27
27
  ```sh
28
- node dist/cli.js render demo --project examples/demo --props examples/demo/props/demo.json
28
+ FRAMEYARD_POLICY=full node dist/cli.js render demo --project examples/demo --props examples/demo/props/demo.json
29
29
  ```
30
30
 
31
31
  Start the interactive preview with:
@@ -0,0 +1,71 @@
1
+ # Frameyard intro film
2
+
3
+ A 20-second, 1920×1080, 60 fps launch film for Frameyard, authored entirely
4
+ with HTML compositions and one JSON timeline.
5
+
6
+ - four scene compositions — `opening`, `studio`, `lattice`, and `brand`
7
+ - a persistent `chrome` HUD overlay with a live frame counter, progress bar,
8
+ and chapter label driven by an external controller
9
+ - three 30-frame dissolves on one video track
10
+ - a deterministic Canvas 2D frame tunnel in `lattice.controller.js`
11
+ - Figtree, Space Mono, and Instrument Serif vendored as project fonts
12
+ - an original 120 BPM score, synthesized from scratch and mixed in Node
13
+ - deterministic film grain generated as a reusable PNG
14
+
15
+ The story runs **author → compose → render → frameyard**, and the timeline
16
+ mirrors it:
17
+
18
+ | Frames | Scene | Copy |
19
+ | --- | --- | --- |
20
+ | 0–300 | `opening` | “THE WEB / is the studio.” |
21
+ | 270–600 | `studio` | “THE TIMELINE / is the score.” |
22
+ | 570–900 | `lattice` | “THE FRAME / is the truth.” |
23
+ | 870–1200 | `brand` | FRAMEYARD lockup and `npx frameyard init my-video` |
24
+
25
+ The scene clips overlap by 30 frames, and `timelines/intro.timeline.json`
26
+ declares one dissolve per overlap. The HUD is a separate clip on its own track,
27
+ so it stays above every scene and survives the transitions.
28
+
29
+ ## Run it
30
+
31
+ From the repository root:
32
+
33
+ ```sh
34
+ pnpm build
35
+ node dist/cli.js lint intro --project examples/intro
36
+ node dist/cli.js check intro --project examples/intro
37
+ node dist/cli.js still intro --frame 1140 --project examples/intro --out examples/intro/out/intro-still.png
38
+ FRAMEYARD_POLICY=full node dist/cli.js render intro --project examples/intro
39
+ ```
40
+
41
+ `render` writes `examples/intro/out/intro.mp4`. Preview the full timeline with:
42
+
43
+ ```sh
44
+ node dist/cli.js dev --project examples/intro
45
+ ```
46
+
47
+ ## Assets
48
+
49
+ The score and grain are generated, not downloaded, and both scripts are
50
+ deterministic. Regenerate them with:
51
+
52
+ ```sh
53
+ node examples/intro/scripts/make-score.mjs
54
+ node examples/intro/scripts/make-grain.mjs
55
+ ```
56
+
57
+ `assets/score.ogg` is a 20-second, 48 kHz stereo mix, encoded as Ogg Vorbis
58
+ (q6) from the deterministic WAV master: D minor pads and a plucked arpeggio
59
+ through the first three movements, a D major impact and shimmer for the final
60
+ lockup, and a short fade at the tail.
61
+
62
+ The fonts are pinned in `fy.fonts.json` and served as generated
63
+ `@font-face` rules. Verify the files and hashes with:
64
+
65
+ ```sh
66
+ node dist/cli.js font verify --project examples/intro
67
+ ```
68
+
69
+ Each of the four scenes reads its copy from a `data-fy="props"` block, so a
70
+ composition render can override strings or the accent color through `--props`
71
+ without editing the HTML.
@@ -0,0 +1,93 @@
1
+ Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree) Figtree-Italic[wght].ttf: Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Copyright 2022 The Instrument Serif Project Authors (https://github.com/Instrument/instrument-serif) InstrumentSerif-Italic.ttf: Copyright 2022 The Instrument Serif Project Authors (https://github.com/Instrument/instrument-serif)
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+
8
+ -----------------------------------------------------------
9
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10
+ -----------------------------------------------------------
11
+
12
+ PREAMBLE
13
+ The goals of the Open Font License (OFL) are to stimulate worldwide
14
+ development of collaborative font projects, to support the font creation
15
+ efforts of academic and linguistic communities, and to provide a free and
16
+ open framework in which fonts may be shared and improved in partnership
17
+ with others.
18
+
19
+ The OFL allows the licensed fonts to be used, studied, modified and
20
+ redistributed freely as long as they are not sold by themselves. The
21
+ fonts, including any derivative works, can be bundled, embedded,
22
+ redistributed and/or sold with any software provided that any reserved
23
+ names are not used by derivative works. The fonts and derivatives,
24
+ however, cannot be released under any other type of license. The
25
+ requirement for fonts to remain under this license does not apply
26
+ to any document created using the fonts or their derivatives.
27
+
28
+ DEFINITIONS
29
+ "Font Software" refers to the set of files released by the Copyright
30
+ Holder(s) under this license and clearly marked as such. This may
31
+ include source files, build scripts and documentation.
32
+
33
+ "Reserved Font Name" refers to any names specified as such after the
34
+ copyright statement(s).
35
+
36
+ "Original Version" refers to the collection of Font Software components as
37
+ distributed by the Copyright Holder(s).
38
+
39
+ "Modified Version" refers to any derivative made by adding to, deleting,
40
+ or substituting -- in part or in whole -- any of the components of the
41
+ Original Version, by changing formats or by porting the Font Software to a
42
+ new environment.
43
+
44
+ "Author" refers to any designer, engineer, programmer, technical
45
+ writer or other person who contributed to the Font Software.
46
+
47
+ PERMISSION & CONDITIONS
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
50
+ redistribute, and sell modified and unmodified copies of the Font
51
+ Software, subject to the following conditions:
52
+
53
+ 1) Neither the Font Software nor any of its individual components,
54
+ in Original or Modified Versions, may be sold by itself.
55
+
56
+ 2) Original or Modified Versions of the Font Software may be bundled,
57
+ redistributed and/or sold with any software, provided that each copy
58
+ contains the above copyright notice and this license. These can be
59
+ included either as stand-alone text files, human-readable headers or
60
+ in the appropriate machine-readable metadata fields within text or
61
+ binary files as long as those fields can be easily viewed by the user.
62
+
63
+ 3) No Modified Version of the Font Software may use the Reserved Font
64
+ Name(s) unless explicit written permission is granted by the corresponding
65
+ Copyright Holder. This restriction only applies to the primary font name as
66
+ presented to the users.
67
+
68
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69
+ Software shall not be used to promote, endorse or advertise any
70
+ Modified Version, except to acknowledge the contribution(s) of the
71
+ Copyright Holder(s) and the Author(s) or with their explicit written
72
+ permission.
73
+
74
+ 5) The Font Software, modified or unmodified, in part or in whole,
75
+ must be distributed entirely under this license, and must not be
76
+ distributed under any other license. The requirement for fonts to
77
+ remain under this license does not apply to any document created
78
+ using the Font Software.
79
+
80
+ TERMINATION
81
+ This license becomes null and void if any of the above conditions are
82
+ not met.
83
+
84
+ DISCLAIMER
85
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93
+ OTHER DEALINGS IN THE FONT SOFTWARE.