frameyard 0.8.0 → 0.10.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 (327) hide show
  1. package/CHANGELOG.md +177 -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/fonts/LICENSE-figtree.txt +93 -0
  108. package/dist/preview/fonts/LICENSE-space-mono.txt +93 -0
  109. package/dist/preview/fonts/README.md +23 -0
  110. package/dist/preview/fonts/figtree-latin-wght-normal.woff2 +0 -0
  111. package/dist/preview/fonts/space-mono-latin-400-normal.woff2 +0 -0
  112. package/dist/preview/fonts/space-mono-latin-700-normal.woff2 +0 -0
  113. package/dist/preview/main.js +5 -2
  114. package/dist/preview/main.js.map +1 -1
  115. package/dist/preview/timeline-data.d.ts +17 -69
  116. package/dist/preview/timeline-data.d.ts.map +1 -1
  117. package/dist/preview/timeline-host.d.ts.map +1 -1
  118. package/dist/preview/timeline-host.js +70 -45
  119. package/dist/preview/timeline-host.js.map +1 -1
  120. package/dist/preview/timeline-main.d.ts.map +1 -1
  121. package/dist/preview/timeline-main.js +88 -27
  122. package/dist/preview/timeline-main.js.map +1 -1
  123. package/dist/project.d.ts +10 -3
  124. package/dist/project.d.ts.map +1 -1
  125. package/dist/project.js +20 -17
  126. package/dist/project.js.map +1 -1
  127. package/dist/renderer/audio.d.ts +6 -0
  128. package/dist/renderer/audio.d.ts.map +1 -1
  129. package/dist/renderer/audio.js +3 -1
  130. package/dist/renderer/audio.js.map +1 -1
  131. package/dist/renderer/browser.d.ts.map +1 -1
  132. package/dist/renderer/browser.js +13 -0
  133. package/dist/renderer/browser.js.map +1 -1
  134. package/dist/renderer/concurrency.d.ts +6 -0
  135. package/dist/renderer/concurrency.d.ts.map +1 -0
  136. package/dist/renderer/concurrency.js +27 -0
  137. package/dist/renderer/concurrency.js.map +1 -0
  138. package/dist/renderer/ffmpeg.d.ts +104 -5
  139. package/dist/renderer/ffmpeg.d.ts.map +1 -1
  140. package/dist/renderer/ffmpeg.js +580 -67
  141. package/dist/renderer/ffmpeg.js.map +1 -1
  142. package/dist/renderer/index.js +1 -1
  143. package/dist/renderer/index.js.map +1 -1
  144. package/dist/renderer/keyframe-expressions.d.ts +39 -0
  145. package/dist/renderer/keyframe-expressions.d.ts.map +1 -0
  146. package/dist/renderer/keyframe-expressions.js +180 -0
  147. package/dist/renderer/keyframe-expressions.js.map +1 -0
  148. package/dist/renderer/session-pool.d.ts +28 -0
  149. package/dist/renderer/session-pool.d.ts.map +1 -0
  150. package/dist/renderer/session-pool.js +91 -0
  151. package/dist/renderer/session-pool.js.map +1 -0
  152. package/dist/renderer/timeline.d.ts +2 -0
  153. package/dist/renderer/timeline.d.ts.map +1 -1
  154. package/dist/renderer/timeline.js +600 -147
  155. package/dist/renderer/timeline.js.map +1 -1
  156. package/dist/renderer/timings.d.ts +13 -0
  157. package/dist/renderer/timings.d.ts.map +1 -0
  158. package/dist/renderer/timings.js +29 -0
  159. package/dist/renderer/timings.js.map +1 -0
  160. package/dist/runtime/host.d.ts.map +1 -1
  161. package/dist/runtime/host.js +4 -0
  162. package/dist/runtime/host.js.map +1 -1
  163. package/dist/scaffold.d.ts +3 -0
  164. package/dist/scaffold.d.ts.map +1 -1
  165. package/dist/scaffold.js +48 -320
  166. package/dist/scaffold.js.map +1 -1
  167. package/dist/server/composition-html.d.ts +2 -0
  168. package/dist/server/composition-html.d.ts.map +1 -1
  169. package/dist/server/composition-html.js +7 -0
  170. package/dist/server/composition-html.js.map +1 -1
  171. package/dist/server/preview-html.d.ts.map +1 -1
  172. package/dist/server/preview-html.js +74 -188
  173. package/dist/server/preview-html.js.map +1 -1
  174. package/dist/server/preview-style.d.ts +11 -0
  175. package/dist/server/preview-style.d.ts.map +1 -0
  176. package/dist/server/preview-style.js +298 -0
  177. package/dist/server/preview-style.js.map +1 -0
  178. package/dist/server/server.d.ts.map +1 -1
  179. package/dist/server/server.js +31 -1
  180. package/dist/server/server.js.map +1 -1
  181. package/dist/server/timeline-preview-data.d.ts.map +1 -1
  182. package/dist/server/timeline-preview-data.js +12 -34
  183. package/dist/server/timeline-preview-data.js.map +1 -1
  184. package/dist/server/timeline-preview-html.d.ts.map +1 -1
  185. package/dist/server/timeline-preview-html.js +140 -148
  186. package/dist/server/timeline-preview-html.js.map +1 -1
  187. package/dist/templates.d.ts +16 -0
  188. package/dist/templates.d.ts.map +1 -0
  189. package/dist/templates.js +186 -0
  190. package/dist/templates.js.map +1 -0
  191. package/dist/timeline/easing.d.ts +36 -0
  192. package/dist/timeline/easing.d.ts.map +1 -0
  193. package/dist/timeline/easing.js +225 -0
  194. package/dist/timeline/easing.js.map +1 -0
  195. package/dist/timeline/keyframes.d.ts +23 -0
  196. package/dist/timeline/keyframes.d.ts.map +1 -0
  197. package/dist/timeline/keyframes.js +95 -0
  198. package/dist/timeline/keyframes.js.map +1 -0
  199. package/dist/timeline/plan.d.ts +6 -12
  200. package/dist/timeline/plan.d.ts.map +1 -1
  201. package/dist/timeline/plan.js +17 -30
  202. package/dist/timeline/plan.js.map +1 -1
  203. package/dist/timeline/resolve.d.ts +12 -0
  204. package/dist/timeline/resolve.d.ts.map +1 -1
  205. package/dist/timeline/resolve.js +105 -9
  206. package/dist/timeline/resolve.js.map +1 -1
  207. package/dist/timeline/scene.d.ts +126 -0
  208. package/dist/timeline/scene.d.ts.map +1 -0
  209. package/dist/timeline/scene.js +109 -0
  210. package/dist/timeline/scene.js.map +1 -0
  211. package/dist/timeline/schema.d.ts +31 -0
  212. package/dist/timeline/schema.d.ts.map +1 -1
  213. package/dist/timeline/schema.js +240 -9
  214. package/dist/timeline/schema.js.map +1 -1
  215. package/dist/timeline/transitions.d.ts +21 -0
  216. package/dist/timeline/transitions.d.ts.map +1 -0
  217. package/dist/timeline/transitions.js +23 -0
  218. package/dist/timeline/transitions.js.map +1 -0
  219. package/dist/version.d.ts +1 -1
  220. package/dist/version.d.ts.map +1 -1
  221. package/dist/version.js +1 -1
  222. package/dist/version.js.map +1 -1
  223. package/docs/adr/0007-make-html-documents-the-composition-unit.md +1 -1
  224. package/docs/adr/0010-make-timelines-the-render-unit.md +3 -2
  225. package/docs/adr/0012-render-timelines-with-segmented-lossless-clip-intermediates.md +1 -1
  226. package/docs/adr/0014-compose-timeline-clips-with-css-blend-modes.md +2 -1
  227. package/docs/adr/0015-inherit-composition-format-from-the-project.md +2 -2
  228. package/docs/adr/0016-author-compositions-as-html-fragments.md +1 -1
  229. package/docs/adr/0018-animate-timeline-clips-with-keyframes.md +35 -0
  230. package/docs/adr/0019-make-same-track-overlap-the-transition-window.md +36 -0
  231. package/docs/adr/0020-crossfade-audio-clips-on-audio-tracks.md +27 -0
  232. package/docs/adr/0021-project-timeline-scenes-once.md +32 -0
  233. package/docs/adr/0022-gate-heavy-operations-with-a-workspace-resource-policy.md +35 -0
  234. package/docs/adr/0023-pin-fonts-as-project-assets.md +98 -0
  235. package/docs/adr/0024-stream-composition-captures-and-skip-trivial-composites.md +86 -0
  236. package/docs/adr/0025-capture-timeline-layers-in-parallel-with-a-global-limit.md +67 -0
  237. package/docs/adr/0026-reuse-loaded-composition-sessions-across-segments.md +70 -0
  238. package/docs/adr/0027-encode-two-full-frame-layers-through-dual-pipes.md +76 -0
  239. package/docs/adr/0028-encode-full-frame-video-segments-directly.md +65 -0
  240. package/docs/migration-0.4.md +2 -2
  241. package/docs/preview-testing.md +27 -6
  242. package/docs/{v0.1-design.md → v0.x-design.md} +57 -32
  243. package/examples/blend/README.md +3 -3
  244. package/examples/demo/README.md +2 -2
  245. package/examples/intro/README.md +71 -0
  246. package/examples/intro/assets/fonts/figtree/LICENSE.txt +93 -0
  247. package/examples/intro/assets/fonts/figtree/figtree-latin-wght-normal.woff2 +0 -0
  248. package/examples/intro/assets/fonts/instrument-serif/LICENSE.txt +93 -0
  249. package/examples/intro/assets/fonts/instrument-serif/instrument-serif-latin-400-italic.woff2 +0 -0
  250. package/examples/intro/assets/fonts/space-mono/LICENSE.txt +93 -0
  251. package/examples/intro/assets/fonts/space-mono/space-mono-latin-400-normal.woff2 +0 -0
  252. package/examples/intro/assets/fonts/space-mono/space-mono-latin-700-normal.woff2 +0 -0
  253. package/examples/intro/assets/grain.png +0 -0
  254. package/examples/intro/assets/score.ogg +0 -0
  255. package/examples/intro/compositions/brand.html +412 -0
  256. package/examples/intro/compositions/chrome.controller.js +63 -0
  257. package/examples/intro/compositions/chrome.html +256 -0
  258. package/examples/intro/compositions/lattice.controller.js +148 -0
  259. package/examples/intro/compositions/lattice.html +308 -0
  260. package/examples/intro/compositions/opening.html +504 -0
  261. package/examples/intro/compositions/studio.html +930 -0
  262. package/examples/intro/compositions/theme.css +218 -0
  263. package/examples/intro/fy.config.js +13 -0
  264. package/examples/intro/fy.fonts.json +100 -0
  265. package/examples/intro/scripts/make-grain.mjs +79 -0
  266. package/examples/intro/scripts/make-score.mjs +702 -0
  267. package/examples/intro/timelines/intro.timeline.json +60 -0
  268. package/examples/keyframes/README.md +76 -0
  269. package/examples/keyframes/compositions/backdrop.html +60 -0
  270. package/examples/keyframes/compositions/card.html +99 -0
  271. package/examples/keyframes/fy.config.js +10 -0
  272. package/examples/keyframes/timelines/keyframes.timeline.json +60 -0
  273. package/examples/nested/README.md +112 -0
  274. package/examples/nested/assets/pulse.wav +0 -0
  275. package/examples/nested/compositions/backdrop.html +84 -0
  276. package/examples/nested/compositions/card.html +275 -0
  277. package/examples/nested/compositions/overlay.html +307 -0
  278. package/examples/nested/fy.config.js +13 -0
  279. package/examples/nested/scripts/make-pulse.sh +20 -0
  280. package/examples/nested/timelines/chapter-a.timeline.json +39 -0
  281. package/examples/nested/timelines/chapter-b.timeline.json +39 -0
  282. package/examples/nested/timelines/nested-reel.timeline.json +52 -0
  283. package/examples/static/README.md +2 -2
  284. package/examples/transitions/README.md +128 -0
  285. package/examples/transitions/assets/alpha.wav +0 -0
  286. package/examples/transitions/assets/beta.wav +0 -0
  287. package/examples/transitions/assets/gamma.wav +0 -0
  288. package/examples/transitions/compositions/alpha.html +56 -0
  289. package/examples/transitions/compositions/beta.html +56 -0
  290. package/examples/transitions/compositions/gamma.html +56 -0
  291. package/examples/transitions/fy.config.js +12 -0
  292. package/examples/transitions/timelines/audio-crossfade.timeline.json +44 -0
  293. package/examples/transitions/timelines/transitions.timeline.json +31 -0
  294. package/examples/video/README.md +4 -4
  295. package/examples/webgl/README.md +1 -1
  296. package/package.json +13 -6
  297. package/scaffolds/init/AGENTS.md +131 -0
  298. package/scaffolds/init/README.md +56 -0
  299. package/scaffolds/init/compositions/hello.html +79 -0
  300. package/scaffolds/init/fy.config.js +9 -0
  301. package/scaffolds/init/gitignore.template +1 -0
  302. package/scaffolds/init/package.json +11 -0
  303. package/scaffolds/init/props/hello.json +4 -0
  304. package/scaffolds/init/props/main.json +6 -0
  305. package/scaffolds/init/timelines/main.timeline.json +20 -0
  306. package/scaffolds/local-models/LOCAL-MODELS.md +77 -0
  307. package/scaffolds/local-models/fy.policy.json +5 -0
  308. package/scaffolds/local-models/opencode/opencode.jsonc +17 -0
  309. package/scaffolds/local-models/pi/APPEND_SYSTEM.md +21 -0
  310. package/scaffolds/local-models/pi/sandbox.json +15 -0
  311. package/scaffolds/local-models/pi/settings.json +15 -0
  312. package/skills/frameyard/README.md +1 -0
  313. package/skills/frameyard/SKILL.md +59 -61
  314. package/skills/frameyard/references/audio-assets.md +32 -3
  315. package/skills/frameyard/references/common-mistakes.md +53 -2
  316. package/skills/frameyard/references/deterministic-authoring.md +10 -3
  317. package/skills/frameyard/references/diagnostics.md +31 -1
  318. package/skills/frameyard/references/failure-recovery.md +32 -6
  319. package/skills/frameyard/references/fonts.md +132 -0
  320. package/skills/frameyard/references/json-results.md +119 -6
  321. package/skills/frameyard/references/quick-reference.md +222 -0
  322. package/skills/frameyard/references/timelines.md +97 -7
  323. /package/examples/blend/{video.config.js → fy.config.js} +0 -0
  324. /package/examples/demo/{video.config.js → fy.config.js} +0 -0
  325. /package/examples/static/{video.config.js → fy.config.js} +0 -0
  326. /package/examples/video/{video.config.js → fy.config.js} +0 -0
  327. /package/examples/webgl/{video.config.js → fy.config.js} +0 -0
@@ -1,92 +1,90 @@
1
1
  ---
2
2
  name: frameyard
3
- description: Create, validate, inspect, and render deterministic Frameyard videos authored with HTML compositions and JSON timelines. Use when a user asks to build or operate a Frameyard project, produce parameterized video, assemble clips into a timeline, add a local TTS or transcription asset through Frameyard's wrappers, or diagnose a Frameyard render. Do not use for general-purpose media generation outside a Frameyard project.
4
- frameyard-api-version: 0.8.0
3
+ description: Create, validate, inspect, and render deterministic Frameyard videos authored with HTML compositions and JSON timelines. Use when a user asks to build or operate a Frameyard project, produce parameterized video, assemble clips into a timeline, search or install project fonts, add a local TTS or transcription asset through Frameyard's wrappers, or diagnose a Frameyard render. Do not use for general-purpose media generation outside a Frameyard project.
4
+ frameyard-api-version: 0.10.0
5
5
  ---
6
6
 
7
7
  # Frameyard
8
8
 
9
- Use Frameyard's public project format and CLI. Do not introduce an agent-only runtime, a hosted service, or direct calls to `qwen-tts` or `whisper-cli`.
9
+ Use the public project format and CLI. Do not add an agent-only runtime, a hosted service, or direct calls to `qwen-tts`, `sherpa-onnx-offline-tts`, or `whisper-cli`.
10
10
 
11
- ## First decisions
11
+ ## First moves
12
12
 
13
- 1. Find the project: look for `video.config.js` from the working directory upward, or run `npx frameyard init <directory>` to scaffold one.
14
- 2. Read the `format`, `compositions`, and `timelines` maps in `video.config.js`. Entries are explicit `.html` composition fragments and `.json` timeline documents; there is no directory scanning.
15
- 3. Read the timeline JSON, the referenced composition fragment, its optional controller, and the props files before changing behavior.
16
- 4. If the task needs speech or transcription, confirm that inference is requested or explicitly approved before producing assets.
13
+ 1. Find `fy.config.js` from the working directory upward, or run `npx frameyard init <directory>`.
14
+ 2. Run `npx frameyard context --json`; read `format`, `compositions`, `timelines`, `fonts`, the target, its props, and its controller before editing.
15
+ 3. Confirm that speech or transcription is requested or explicitly approved before producing assets.
16
+ 4. If context is small, read [quick reference](references/quick-reference.md) first. Router: static/card → quick reference; timelines → [timelines](references/timelines.md); fonts → [fonts](references/fonts.md); captions → [subtitles and karaoke](references/subtitles-karaoke.md); failures → [diagnostics](references/diagnostics.md) and [failure recovery](references/failure-recovery.md); Canvas/WebGL → [controllers](references/controllers.md).
17
17
 
18
18
  ## Workflow
19
19
 
20
- 1. **Author compositions** as partial HTML fragments with one top-level `<fy-composition>` root. Put metadata, default props, markup, CSS animation, bindings, audio, and transcripts in the fragment. Do not add `<!doctype>`, `<html>`, `<head>`, `<body>`, `<title>`, or `<meta>`; Frameyard generates the document shell. Use an optional controller for Canvas 2D, WebGL, karaoke, or per-frame computation CSS cannot express. Omit both `duration-in-frames` and `fps` only when the composition is intentionally static; a static composition is captured once, renders to PNG, and cannot use animation scheduling, media, or transcripts. Read [deterministic authoring](references/deterministic-authoring.md), [bindings](references/bindings.md), and [controllers](references/controllers.md).
21
- 2. **Assemble the timeline** in JSON: project format, video/audio tracks, composition, media, and nested-timeline clips, source ranges, static transforms and blend modes, and audio. Read [timelines](references/timelines.md).
22
- 3. **Provide props** as JSON. Composition props merge over defaults; timeline render props are a map keyed by composition clip ID. Validate domain-specific requirements with a controller's `validateProps()`.
23
- 4. **Produce optional assets** with `npx frameyard tts` and `npx frameyard transcribe`, never with the upstream executables. Read [audio assets](references/audio-assets.md). Do not overwrite or regenerate existing generated assets without permission.
24
- 5. **Validate** with `npx frameyard check <id> --json`, where `<id>` is a timeline or composition. Static validation runs before Chromium; then every animated frame is evaluated, or a static composition's single state is inspected once. Read the structured `diagnostics` array and the result `kind`. Read [diagnostics](references/diagnostics.md) and [common mistakes](references/common-mistakes.md).
25
- 6. **Inspect** representative frames with `npx frameyard still <id> --frame <spec> --out <path> --json`, where `<spec>` is a frame number, an inclusive range like `2-12`, or a comma-separated list with at most 32 unique frames. With one frame, `--out` is a `.png` file; with multiple frames, it is a directory and the files are `still-<id>-<frame>.png`. View the PNGs when the host supports image viewing. If visual inspection is impossible, say so explicitly.
26
- 7. **Render** with `npx frameyard render <id> [--out <file>] [--props <file>] --json`. Animated composition renders accept `--concurrency <n>`; timeline renders accept `--workers <n>` and `--scratch <dir>`. `--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, defaulting to `auto`. The output extension must match the container. The default output is `out/<id>.<ext>` for video and `out/<id>.png` for a static composition. A static composition render rejects `--container`, `--codec`, `--encoder`, and `--concurrency`; use a timeline when the deliverable must be video.
27
- 8. **Report** the project path, timeline and composition entries, controller, props, generated assets, provenance manifest, validation results, diagnostics, still paths and inspection status, final video path, and any failures.
20
+ 1. **Author** one `<fy-composition>` fragment. Props, the controller, import maps, and styles are top-level siblings; never add a document shell. Omit `duration-in-frames` and `fps` only for a static composition, which renders to PNG and cannot use animation/media/transcripts. Use a controller only for Canvas, WebGL, karaoke, or per-frame math. Read [deterministic authoring](references/deterministic-authoring.md).
21
+ 2. **Assemble** a version-1 timeline with explicit tracks and clips. Sources are composition IDs, nested timeline IDs, or local media. `start`, `sourceIn`, and `duration` are integer frames; no remote URLs. Read [timelines](references/timelines.md).
22
+ 3. **Provide props** as JSON. Composition props merge over defaults; timeline overrides are keyed by composition clip ID. Use `validateProps()` for domain rules.
23
+ 4. **Lint** with `npx frameyard lint <id> --json`. It validates the document/timeline, resolves references, projects every timeline scene/keyframe channel, scans controller determinism, and probes media metadata without Chromium. It is the edit loop, not the full gate.
24
+ 5. **Check** with `npx frameyard check <id> --json`. Every animated frame is evaluated, or a static composition once. Read `diagnostics` and `kind`; treat warnings as actionable.
25
+ 6. **Inspect** with `npx frameyard still <id> --frame <spec> --out <path> --json`. A spec is a frame, an inclusive range, or a comma-separated list. The policy allows at most 32 unique frames in `full` (4 in `authoring`, 1 in `light`). One frame uses a `.png` file; multiple frames use a directory. Use `npx frameyard inspect`/`compare` for text facts, and view the PNGs when the host supports it; if not, say so.
26
+ 7. **Render** with `npx frameyard render <id> [--out <file>] [--props <file>] --json`. Composition renders accept `--concurrency`; timeline renders accept `--workers` and `--scratch`; `--container`, `--codec`, and `--encoder` select delivery. A static composition rejects those four flags. If the policy blocks render, use `--dry-run` and hand off the command; do not claim a render.
27
+ 8. **Report** the project, entries, controller, props, generated assets, validation, diagnostics, still paths and inspection status, video path, and failures. Never claim success when `check` fails, a still is missing, a render exits nonzero, or inspection was skipped.
28
+
29
+ ## Authoring contract
30
+
31
+ - A composition is a partial fragment with one top-level `<fy-composition>` and a top-level `data-fy="props"` block. Do not use `data-fy-host` or `data-fy="format"`. `width`, `height`, and `fps` inherit the project `format`; declare both dimensions together.
32
+ - A static composition has neither `duration-in-frames` nor `fps`; it runs `mount()`/`validateProps()`, not `update()`, and has no frame-state variables. Declaring `fps` without `duration-in-frames` is an error. A static timeline clip needs an explicit `duration` and `sourceIn: 0`.
33
+ - Bindings: `data-fy-text`, `data-fy-attr-*`, `data-fy-class-*`, `data-fy-if`, and `<template data-fy-repeat>`. Primitive top-level props are also `--fy-prop-<name>` CSS variables. Bindings apply once, before `mount`.
34
+ - CSS/WAAPI animations are seeked from the frame clock. `data-fy-anim-start`, `data-fy-anim-duration`, and `data-fy-anim-easing` schedule one animation in frames. Frame state: `--fy-frame`, `--fy-time`, `--fy-progress`, `--fy-fps`, `--fy-duration`, `--fy-duration-frames`.
35
+ - Exactly one optional controller, inline or external, is the only executable code. It may export `mount`, `update`, `dispose`, and `validateProps`; at least one is required. Other scripts and `on*` handlers are rejected.
36
+ - Media and transcript `src` values resolve from the project root; controller `src` and ordinary HTML URLs resolve from the composition URL. Embedded media uses `data-fy-start` (frames), `data-fy-trim` (seconds), optional `data-fy-duration` (seconds), and `data-fy-volume` (0-1). Transcripts use `<fy-transcript src words>`.
37
+ - An optional top-level import map may add bare specifiers for controllers; `frameyard` and `frameyard/` are reserved.
38
+
39
+ ## Determinism
40
+
41
+ - A composition frame depends only on its integer `frame`, immutable `props`, and assets loaded before frame production. A timeline frame derives from its active clips.
42
+ - Use `random("stable:key")` from `frameyard/runtime`; never `Math.random()`. Never use `Date.now()`, `performance.now()`, timers, independent `requestAnimationFrame` loops, or CSS transitions as animation inputs.
43
+ - Load remote data, fonts, and images before frame production. Network is blocked during capture. Prefer project fonts installed with `npx frameyard font add`; remote font URLs produce `FONT_REMOTE` and are not reproducible. Keep `update({ frame, props })` synchronous and seek-safe; do not carry state between frames.
44
+ - Let the runtime seek embedded video; do not play, pause, or set `currentTime` in `update()`. Redraw WebGL from the current frame; production pins software WebGL.
45
+ - Do not run model inference inside `mount()`, `update()`, or frame production.
28
46
 
29
- Never claim success when `check` fails, a still is missing, a render exits nonzero, or visual inspection was skipped. A partial or unpublished render is not a successful result.
47
+ ## Resource policy
30
48
 
31
- ## Command rules
49
+ `fy.policy.json` is read from the working directory upward, independent of `fy.config.js`; `FRAMEYARD_POLICY` overrides it.
32
50
 
33
- - Use `--json` for every non-interactive command. Parse stdout as the structured result; treat stderr as progress and diagnostics.
34
- - Keep the current output files unless the user explicitly approves replacement. Use `--force` only with that approval.
35
- - Use `--project <dir>` when not running from the project root.
36
- - `render` writes to `out/<id>.mp4` in the project root for timelines and animated compositions, and to `out/<id>.png` for static compositions, unless `--out` is passed. Explicit `--out` paths are relative to the current working directory.
37
- - Use `--concurrency <n>` only for animated composition renders; the default is `1` and the maximum is `16`. Use `--workers <n>` for timeline renders; the default is `8`. Timeline renders also accept `--scratch <dir>` for FFV1 intermediates.
38
- - `still --frame <spec>` accepts `n`, `n-m`, or a comma-separated list; ranges are inclusive, duplicates are removed preserving the first occurrence, order is preserved, and one call captures at most 32 unique frames. A static composition exposes only frame 0. Do not use `still` as an image-sequence exporter. With multiple frames, `--out` is a directory and files are named `still-<id>-<frame>.png`; with one frame, `--out` is a `.png` file.
39
- - `--encoder` selects the encoder: `auto` (default) probes NVENC, then Quick Sync, then the codec's software encoder; `software` forces `libx264`, `libx265`, or `libvpx-vp9`; `nvenc` and `qsv` force a vendor. `--codec <h264|hevc|vp9>` and `--container <mp4|mov|mkv|webm>` select the delivery pair: MP4 and MOV hold H.264/HEVC with AAC, MKV holds H.264/HEVC/VP9, and WebM holds VP9 with Opus and defaults to VP9. `--out` must use the container's extension. The probe is a real one-frame encode. Encoders, codecs, and containers change encoded bytes and file size, not captured frames; do not switch them to fix a visual difference. With `nvenc`, timeline `--workers` is capped at eight, the consumer driver session limit, and a larger value reports an `ENCODER_WARNING`. HEVC MP4/MOV files use the `hvc1` tag and do not play in browsers without platform HEVC decoding.
40
- - For a timeline, `--props` is a JSON object keyed by top-level composition clip ID. For a composition, it is the props object.
41
- - Use `npx frameyard doctor --json` before reporting an environment problem. It inspects without repairing or downloading.
42
- - Read [JSON results](references/json-results.md) for result and error shapes, and [failure recovery](references/failure-recovery.md) for actionable error codes.
43
-
44
- ## Authoring rules
45
-
46
- - A composition is a partial HTML fragment with one top-level `<fy-composition>` root and a top-level `data-fy="props"` default-props block. Props, the controller, and import maps are top-level siblings of the root; top-level `<style>` and `<link rel="stylesheet">` elements are hoisted into the generated head. Do not use the reserved `data-fy-host` attribute or `data-fy="format"` block. `width`, `height`, and `fps` are optional and inherit from the project `format`; declare them to override it.
47
- - A composition with neither `duration-in-frames` nor `fps` is static. It is captured once and renders to PNG. It may use `<img>`, CSS, SVG, and canvas, but not `data-fy-anim-*`, `<video>`, `<audio>`, or `<fy-transcript>`. Its controller runs `mount()` and `validateProps()`; `update()` is not called and frame-state variables are not set. CSS animations are paused at time 0. Declaring `fps` without `duration-in-frames` is an error. A static composition clip in a timeline needs an explicit `duration` and `sourceIn: 0`.
48
- - A timeline is a versioned JSON document with explicit tracks and clips. Sources are composition IDs, nested timeline IDs, or local video/image/audio paths. Timeline frames, sourceIn, and duration are integer frames. No remote URLs.
49
- - Bindings are explicit: `data-fy-text`, `data-fy-attr-*`, `data-fy-class-*`, `data-fy-if`, and `data-fy-repeat` on `<template>`. Primitive top-level props are also `--fy-prop-<name>` CSS variables.
50
- - CSS `@keyframes` and the Web Animations API are seeked from the frame clock. `data-fy-anim-start`, `data-fy-anim-duration`, and `data-fy-anim-easing` schedule one animation per element in frames.
51
- - Frame-state variables on `:root`: `--fy-frame`, `--fy-time`, `--fy-progress`, `--fy-fps`, `--fy-duration`, and `--fy-duration-frames`.
52
- - A composition has exactly one optional controller, inline or external. It may export `mount`, `update`, `dispose`, and `validateProps`; at least one is required. Use inline for small controllers; extract to an external module for Canvas 2D, WebGL, karaoke, reuse, tests, or a large controller.
53
- - The controller is the only executable code. Other executable scripts and `on*` handlers are rejected.
54
- - Media and transcript `src` values resolve from the project root. The controller `src` and ordinary HTML asset URLs resolve relative to the composition URL.
55
- - Embedded video/audio use `data-fy-start` (frames), `data-fy-trim` (seconds), optional `data-fy-duration` (seconds), and `data-fy-volume` (0-1). Transcripts use `<fy-transcript src words>`.
56
- - An optional top-level `<script type="importmap">` may add bare specifiers for controllers. Frameyard hoists it before the host module; `frameyard` and `frameyard/` specifiers are reserved.
57
-
58
- ## Determinism guardrails
59
-
60
- - A composition frame may depend only on its integer `frame`, immutable `props`, and assets loaded before frame production. A timeline frame derives from its active clips and the same composition rules.
61
- - Use `random("stable:key")` from `frameyard/runtime`; never use `Math.random()`.
62
- - Never use `Date.now()`, `performance.now()`, timers, independent `requestAnimationFrame` loops, or CSS transitions as animation inputs.
63
- - Finish remote data, fonts, and image loading during the loading phase. Network requests are blocked during frame capture.
64
- - Keep `update({ frame, props })` synchronous and seek-safe. Do not carry state between frames.
65
- - Let the runtime seek embedded `<video>` elements. Do not play, pause, or set `currentTime` in `update()`.
66
- - Redraw WebGL canvases from the current frame. Production pins software WebGL; do not depend on GPU-specific results or the previous frame's drawing buffer.
67
- - Do not run model inference inside `mount()`, `update()`, or frame production.
51
+ - `light`: one still frame; blocks `check`, `tts`, `transcribe`, and `render`.
52
+ - `authoring`: four still frames and `check` at low priority; blocks `tts`, `transcribe`, and `render`.
53
+ - `full`: everything up to the 32-frame still cap.
54
+ - `lint`, `context`, `doctor`, `add`, `inspect`, `compare`, and `--dry-run` are available in every tier. `font search`, `font list`, and `font verify` are available in every tier; `font add` requires `authoring` or `full`; `font remove`, `font add --force`, and `font add --url` without `--license`/`--license-file` require `full`.
55
+ - When blocked, run `--dry-run`, report `deferred: true`, and give the user `commandLine`, `prerequisites`, and `blockers`. Never claim a render or asset exists.
56
+ - `--profile shared-cpu` caps timeline workers at 2 and composition concurrency at 1 and lowers priority; `--low-priority` lowers priority without caps. Values above a cap fail with `RESOURCE_PROFILE_CONFLICT`; do not clamp silently.
57
+ - `light`/`authoring` deny `--force` and `--overwrite`; ask before replacing an output. Do not edit the policy file, host configuration, or skill source. The policy is a guardrail, not a security boundary.
68
58
 
69
- ## Diagnostics rule
59
+ ## Command rules
60
+
61
+ - Use `--json` for non-interactive commands; parse stdout, keep stderr for progress/diagnostics. Use `--project <dir>` outside the project root.
62
+ - Outputs default to `out/<id>.<ext>` for video and `out/<id>.png` for static compositions; explicit `--out` is relative to the current directory. Replace outputs only with approval.
63
+ - For a timeline, `--props` is a map keyed by top-level composition clip ID; for a composition, it is the props object. Use `--concurrency` for animated compositions, `--workers`/`--scratch` for timelines. `--container <mp4|mov|mkv|webm>`, `--codec <h264|hevc|vp9>`, and `--encoder <auto|software|nvenc|qsv>` select delivery; the output extension must match the container.
64
+ - Produce speech and transcripts only with `npx frameyard tts` and `npx frameyard transcribe`, never upstream executables; read [audio assets](references/audio-assets.md). Do not overwrite generated assets without approval.
65
+ - Use `npx frameyard add --list --json` and `add composition|timeline <id> --template <name> --json` for templates; merge the returned registration object into `fy.config.js`. `add` never edits the config or overwrites an entry.
66
+ - Use `npx frameyard font search <query> --json` to find a Fontsource family, then `npx frameyard font add <fontsource-id> --json` to pin and vendor it. Use `font add --file` for a local or brand font, or `font add --url` for an explicit HTTPS source. Reference the returned CSS family. `font list`, `font verify`, and `font remove` manage the manifest; never edit `fy.fonts.json` hashes by hand. Read [fonts](references/fonts.md).
67
+ - Use `npx frameyard doctor --json` before reporting an environment problem. Read [JSON results](references/json-results.md) and [failure recovery](references/failure-recovery.md).
70
68
 
71
- `check`, `still`, and `render` run static composition validation before Chromium. Errors fail before a browser starts; warnings allow frame evaluation. Every diagnostic has a stable `code`, a `message`, and optional `details` with source locations and suggestions. Treat warnings as actionable. Do not ignore `BINDING_PATH_MISSING`, `ANIMATION_NOT_FOUND`, `ANIMATION_MULTIPLE`, `STATIC_UPDATE_IGNORED`, `STATIC_ANIMATION_PAUSED`, or `DETERMINISM_WARNING`. `STATIC_ANIMATION_UNSUPPORTED` and `STATIC_MEDIA_UNSUPPORTED` mean the composition should be animated with `duration-in-frames`/`fps` or the media should move to a timeline.
69
+ ## Diagnostics and karaoke
72
70
 
73
- ## Subtitle and karaoke rule
71
+ `check`, `still`, and `render` run static validation before Chromium; errors stop, warnings allow frames. Diagnostics carry a stable `code`, message, and optional source location/suggestion. Treat `BINDING_PATH_MISSING`, `ANIMATION_*`, `STATIC_*`, and `DETERMINISM_WARNING` as actionable.
74
72
 
75
- Use canonical transcripts produced by `npx frameyard transcribe`. Declare them with `<fy-transcript src="assets/voice.transcript.json" words>`. Convert integer source-relative milliseconds to composition frames with the audio clip's start frame, trim start, and the composition `fps`. Karaoke highlighting requires actual `words` timings; never interpolate segment timings. Read [subtitles and karaoke](references/subtitles-karaoke.md).
73
+ Use transcripts from `npx frameyard transcribe`. Convert source-relative milliseconds to composition frames with the clip start, trim start, and `fps`. Karaoke requires measured `words`; never interpolate segment timings.
76
74
 
77
75
  ## CLI invocation
78
76
 
79
- Use `npx frameyard` (or an installed `frameyard` binary) directly. `scripts/frameyard.mjs` is a fallback for hosts that cannot resolve package binaries: it prefers the package-local `dist/cli.js`, then the `frameyard` command on `PATH`. When the skill is copied out of the package, set `FRAMEYARD_CLI` to an installed CLI path if that command is unavailable.
77
+ Use `npx frameyard` or an installed binary. `scripts/frameyard.mjs` is the fallback; set `FRAMEYARD_CLI` when the command is unavailable. Pin the CLI with `npx frameyard@<version>` when it differs from `frameyard-api-version`.
80
78
 
81
79
  ```sh
82
80
  npx frameyard check <id> --json
83
- node scripts/frameyard.mjs check <id> --json # fallback
81
+ node scripts/frameyard.mjs check <id> --json
84
82
  ```
85
83
 
86
- When the CLI version differs from `frameyard-api-version`, pin it with `npx frameyard@<version>`.
87
-
88
84
  ## References
89
85
 
86
+ - [Quick reference](references/quick-reference.md)
87
+ - [Fonts](references/fonts.md)
90
88
  - [Timelines](references/timelines.md)
91
89
  - [Deterministic HTML authoring](references/deterministic-authoring.md)
92
90
  - [Bindings](references/bindings.md)
@@ -34,7 +34,9 @@ Declare required transcripts with:
34
34
 
35
35
  ## Local speech synthesis
36
36
 
37
- Use Frameyard, not `qwen-tts` directly:
37
+ Use Frameyard instead of calling the upstream executables directly:
38
+
39
+ ### qwentts.cpp (default)
38
40
 
39
41
  ```sh
40
42
  npx frameyard tts \
@@ -47,6 +49,7 @@ npx frameyard tts \
47
49
  --json
48
50
  ```
49
51
 
52
+ - `--engine qwen` is the default and may be omitted.
50
53
  - CustomVoice only: `--speaker` is required.
51
54
  - Text comes from `--text-file` or stdin.
52
55
  - `--style` maps to the upstream style instruction.
@@ -58,6 +61,28 @@ npx frameyard tts \
58
61
  - Executable resolution: `--executable`, then `FRAMEYARD_QWEN_TTS`, then `PATH`.
59
62
  - Frameyard does not build qwentts.cpp, download models, or redistribute weights.
60
63
 
64
+ ### Supertonic 3
65
+
66
+ ```sh
67
+ npx frameyard tts \
68
+ --engine supertonic \
69
+ --speaker 0 \
70
+ --model-dir models/sherpa-onnx-supertonic-3-tts-int8-2026-05-11 \
71
+ --language es \
72
+ --text-file script.txt \
73
+ --out assets/voice-es.wav \
74
+ --json
75
+ ```
76
+
77
+ - The engine covers 31 languages and requires one of their codes (`es`, `en`, `fr`, `de`, ...); `--language` is mandatory.
78
+ - `--speaker` is the numeric `--sid`, not a name.
79
+ - `--model-dir` points at the extracted Supertonic 3 archive with its seven files; do not pass individual files.
80
+ - `--style` and `--device` are qwen-only. Put Supertonic expression tags such as `<laugh>`, `<breath>`, and `<sigh>` in the text.
81
+ - The engine writes its native WAV; Frameyard normalizes it to 24 kHz mono and records `sourceSampleRate` and `sourceChannels` in the manifest.
82
+ - `--force` is required to replace an existing asset. Do not pass it without user approval.
83
+ - Executable resolution: `--executable`, then `FRAMEYARD_SHERPA_ONNX_TTS`, then `PATH`.
84
+ - Frameyard does not build sherpa-onnx, download the model archive, or redistribute weights. The upstream Supertonic repository is archived and its weights use the OpenRAIL-M license.
85
+
61
86
  ## Local transcription
62
87
 
63
88
  ```sh
@@ -77,9 +102,13 @@ npx frameyard transcribe assets/voice.wav \
77
102
  - Executable resolution: `--executable`, then `FRAMEYARD_WHISPER_CLI`, then `PATH`.
78
103
  - Frameyard does not download Whisper models or execute Whisper's generated karaoke shell script.
79
104
 
105
+ ## Pinned upstream revisions
106
+
107
+ The wrappers target the CLI surfaces of qwentts.cpp commit `5916bf7c79dbb4e66fc059cdc507a79a23a28bb4`, sherpa-onnx commit `af2d8d81dbfa61c4cea4c568da78f3982edca005` (v2.0.1), and whisper.cpp commit `284029c07012d2000941a08d38a31ffb71f4734e`.
108
+
80
109
  ## Provenance manifest
81
110
 
82
- Both commands update `frameyard.assets.json` in the project root unless `--manifest <file>` is supplied. The manifest records:
111
+ Both commands update `fy.assets.json` in the project root unless `--manifest <file>` is supplied. The manifest records:
83
112
 
84
113
  - artifact path, content hash, and size;
85
114
  - generator command, Frameyard version, executable, and integration revision;
@@ -93,5 +122,5 @@ Commit the manifest with the project. It explains how generated inputs were prod
93
122
  - Do not regenerate or overwrite an existing generated asset without permission.
94
123
  - Do not silently switch models, speakers, language, or text.
95
124
  - Report the manifest path and the exact asset paths after success.
96
- - `npx frameyard doctor` verifies manifest integrity, including artifact, source, model, and derived-export hashes, and optional integration executables; it does not download or repair anything. Pass `--talker-model`, `--codec-model`, or `--model` to inspect model readability and identity before generation.
125
+ - `npx frameyard doctor` verifies manifest integrity, including artifact, source, model, and derived-export hashes, and optional integration executables; it does not download or repair anything. Pass `--talker-model`, `--codec-model`, `--model-dir`, or `--model` to inspect model readability and identity before generation.
97
126
  - Once assets are frozen, checking and rendering do not need the model or inference executable.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Using the old authoring model
4
4
 
5
- - **JavaScript composition modules / `defineComposition`** were removed in 0.4. A composition is a `.html` fragment listed in `video.config.js`.
5
+ - **JavaScript composition modules / `defineComposition`** were removed in 0.4. A composition is a `.html` fragment listed in `fy.config.js`.
6
6
  - **`data-frameyard-*`** media attributes were removed in 0.4. Use `data-fy-*`.
7
7
 
8
8
  ## Document structure
@@ -47,8 +47,18 @@
47
47
  - Importing libraries from executable `<script>` tags; import them from the controller instead.
48
48
  - Using `Math.random()`, `Date.now()`, `performance.now()`, timers, independent `requestAnimationFrame` loops, or CSS transitions.
49
49
 
50
- ## Media and transcripts
50
+ ## Fonts
51
+
52
+ - Using a remote Google Fonts `<link>` in a composition that must render reproducibly. Search Fontsource with `font search` and vendor the file; remote fonts produce `FONT_REMOTE`.
53
+ - Installing a Fontsource variable family and then expecting static weights from the same record. Use `--static --weight 400,700` for static files, or `--force` to replace the family.
54
+ - Passing `--license` with a Fontsource id; the provider supplies the license and the flag is rejected. Use `--license` only with `--file`/`--url`.
55
+ - Importing a font by URL without a license. It records `unknown`, warns, and requires `full`; add `--license` and `--license-file` when the license allows redistribution.
56
+ - Adding a `.woff2` file but forgetting the license or `--family`; `font add` needs the family name and warns when the license is unknown.
57
+ - Hand-editing a path or hash in `fy.fonts.json`. Reinstall the face; `check`, `still`, and `render` verify hashes before Chromium.
58
+ - Installing only weight 400 and using `font-weight: 700`, which asks the browser to synthesize a bold face. Add the weight or a variable range.
59
+ - Moving or renaming a vendored font file without updating the manifest; `font verify` reports `FONT_FILE_MISSING`.
51
60
 
61
+ ## Media and transcripts
52
62
  - Using remote, live, or unseekable media sources.
53
63
  - Using `<source>` or `<track>` children; use a local `src` and `<fy-transcript>`.
54
64
  - Adding or changing media in `update()`; prepare it in the fragment or in `mount()`.
@@ -56,6 +66,15 @@
56
66
  - Assuming media/transcript `src` is document-relative. It is project-root-relative.
57
67
  - Assuming the controller `src` is project-root-relative. It is relative to the composition URL.
58
68
 
69
+ ## Generated audio
70
+
71
+ - Passing `--talker-model` or `--codec-model` with `--engine supertonic`, or `--model-dir` with `--engine qwen`. Flags are engine-specific.
72
+ - Using a speaker name with `--engine supertonic`; `--speaker` is the numeric `--sid`.
73
+ - Omitting `--language` with Supertonic or using a code outside its 31-language list.
74
+ - Expecting `--style` or `--device` to work with Supertonic; both are qwen-only. Put expression tags in the text.
75
+ - Pointing `--model-dir` at individual files instead of the extracted seven-file archive.
76
+ - Regenerating an existing speech asset without `--force` and user approval (`OUTPUT_EXISTS`).
77
+
59
78
  ## Timelines
60
79
 
61
80
  - Putting `blend` on an audio clip. It is only valid on visual clips.
@@ -66,6 +85,18 @@
66
85
  - Omitting `duration` on a static composition clip, or setting `sourceIn` above 0. A static source has no intrinsic duration or frames.
67
86
  - Expecting a static composition clip to contribute audio or transcripts. It is visual-only.
68
87
 
88
+ ## Timeline keyframes
89
+
90
+ - Treating `frame` as a timeline frame or a source frame. Keyframe frames are clip-local; `0` is the clip start boundary and `duration` is the end boundary.
91
+ - Expecting keyframes to extend a clip. A key past `duration` is a `TIMELINE_CLIP_OUT_OF_RANGE` error; declare a longer clip instead.
92
+ - Declaring the same channel as a scalar in `transform` and as a keyframe track. Keyed channels and static channels are separate; defaults do not conflict.
93
+ - Combining `keyframes.scale` with a `transform.width`/`height` box. Use a keyed scale without a box, or keep the box and animate `x`, `y`, `rotation`, and `opacity`.
94
+ - Putting keyframes on an audio clip. Only visual clips have presentation channels.
95
+ - Using seconds instead of integer clip-local frames, or putting keys out of order.
96
+ - Expecting the final key’s `easing` to do anything. Easing belongs to the interval after a key; the last key has no following interval.
97
+ - Expecting preview and encoded keyframe motion to match exactly. Preview uses the exact CSS curve; production approximates it in FFmpeg and may differ by a subpixel.
98
+ - Expecting an animated `blend` mode. Blend remains static; only `opacity` can animate the layer’s contribution to a blend.
99
+
69
100
  ## Static compositions
70
101
 
71
102
  - Expecting `update()` or `--fy-*` frame-state variables to run. A static composition only runs `mount()` and prop validation; use CSS, markup, or mount-time canvas drawing.
@@ -74,6 +105,26 @@
74
105
  - Passing `--container`, `--codec`, `--encoder`, or `--concurrency` to `render` for a static composition. Use a timeline image clip if the deliverable must be video.
75
106
  - Requesting a still frame other than 0 from a static composition.
76
107
 
108
+ ## Context, inspection, and templates
109
+
110
+ - Reading the whole repository before a small edit. Run `npx frameyard context --json` first; it returns the format, props, timelines, assets, policy, and next commands.
111
+ - Treating `inspect`/`compare` as a vision model. They return facts such as blank, alpha coverage, bbox, and changed pixels; they do not judge whether a design looks right.
112
+ - Expecting `compare` to exit 0 when the images differ. It exits 1 and still returns `ok: true` with `identical: false`.
113
+ - Forgetting that `add` does not edit `fy.config.js`. Merge the returned `register` object yourself, then run `lint`.
114
+ - Adding a composition template to a project without `format`. Composition templates inherit the project format; add `format { width, height, fps }` first.
115
+ - Adding the slideshow timeline before `assets/slide-1.png` through `assets/slide-3.png` exist. `check` will report the missing assets.
116
+
117
+ ## Resource policy and lint
118
+
119
+ - Using `check` as the edit loop. Use `lint`; it resolves the timeline, projects every scene and keyframe channel, and scans the controller without Chromium.
120
+ - Treating a clean `lint` as a successful `check`. Lint does not run `validateProps` or composition runtime.
121
+ - Running `tts`, `transcribe`, or `render` while the workspace policy is `authoring` or `light`. Use `--dry-run` and hand off the command.
122
+ - Editing `fy.policy.json`, `.opencode/`, `.pi/`, or `skills/frameyard/` to bypass the policy. Those files are protected by host permissions and are not part of the authoring task.
123
+ - Passing `--force` or `--overwrite` outside `full`. Ask the user before replacing an output; the CLI will fail with `RESOURCE_POLICY_BLOCKED`.
124
+ - Requesting more still frames than the tier allows. The caps are 1 for `light`, 4 for `authoring`, and 32 for `full`; split larger ranges.
125
+ - Passing `--workers 8` with `--profile shared-cpu`. The profile caps workers at 2 and fails with `RESOURCE_PROFILE_CONFLICT` instead of clamping.
126
+ - Forgetting that the policy is discovered from the working directory upward, independent of `fy.config.js`. Running from a nested directory still finds the workspace file.
127
+
77
128
  ## Validation
78
129
 
79
130
  - Replacing `check` with a single `still`.
@@ -4,7 +4,7 @@ Frameyard compositions are partial HTML fragments. The preview server wraps them
4
4
 
5
5
  ## Project configuration
6
6
 
7
- `video.config.js` lists every composition explicitly:
7
+ `fy.config.js` lists every composition explicitly:
8
8
 
9
9
  ```js
10
10
  export default {
@@ -99,6 +99,12 @@ CSS `@keyframes` and the Web Animations API are paused and seeked by the runtime
99
99
  - The root exposes `--fy-frame`, `--fy-time`, `--fy-progress`, `--fy-fps`, `--fy-duration`, and `--fy-duration-frames`. Use them in `calc()` for simple computed motion and progress bars.
100
100
  - Do not use CSS transitions. They are diagnosed as non-deterministic.
101
101
 
102
+ ## Timeline clip keyframes
103
+
104
+ CSS `@keyframes` animate elements *inside* a composition. A timeline clip's `keyframes` animate the clip's placement in the edit: `x`, `y`, `scale`, `rotation`, and `opacity`. The two mechanisms compose: a composition may animate internally while its timeline clip also fades or moves.
105
+
106
+ Timeline keyframes use clip-local integer frames, not composition frames or seconds. Keyframe values are derived only from the integer frame and the declared easing, so they are seek-safe. Preview evaluates the exact CSS timing function; production generates per-frame FFmpeg expressions and approximates the curve. See [timelines](timelines.md) for the contract.
107
+
102
108
  ## Controllers
103
109
 
104
110
  An optional controller handles Canvas 2D, WebGL, karaoke, and per-frame computation CSS cannot express.
@@ -215,17 +221,18 @@ Declare canonical transcript requirements with:
215
221
 
216
222
  Remote data, images, fonts, and other assets must resolve during the loading phase before frame production. The production browser blocks network requests after loading begins. A fetch from `update()` is a bug.
217
223
 
218
- Images declared in the fragment are awaited by the host. Static fonts should be loaded with `document.fonts.ready` or an explicit `FontFace` before frame production.
224
+ Images declared in the fragment are awaited by the host. Fonts should be vendored into the project with `npx frameyard font search`/`font add <id>` or `font add --file`; the server injects the generated `@font-face` sheet and the host awaits `document.fonts.ready`. A used face that fails to load fails the capture with `FONT_LOAD_FAILED`. A remote font URL produces a `FONT_REMOTE` warning and is not reproducible. See [fonts](fonts.md).
219
225
 
220
226
  ## Validation and inspection
221
227
 
222
228
  ```sh
229
+ npx frameyard lint hello --json
223
230
  npx frameyard check hello --json
224
231
  npx frameyard still hello --frame 42 --out hello-42.png --json
225
232
  npx frameyard still hello --frame 0,118-126 --out out/stills --json
226
233
  ```
227
234
 
228
- `check` runs static validation before Chromium, then evaluates every animated frame and validates audio and declared transcripts without capturing or encoding. A static composition is inspected once. Read the structured `diagnostics` array. Do not replace `check` with a single still. Inspect several representative frames in one `still` call with a range or list; the batch seeks sequentially in one session and is limited to 32 unique frames. A static composition exposes only frame 0.
235
+ `lint` runs static validation, timeline resolution, scene and keyframe projection for every timeline frame, and a controller determinism scan without Chromium. Use it while editing; it does not run `validateProps` or composition runtime. `check` runs static validation before Chromium, then evaluates every animated frame and validates audio and declared transcripts without capturing or encoding. A static composition is inspected once. Read the structured `diagnostics` array. Do not replace `check` with a single still. Inspect several representative frames in one `still` call with a range or list; the batch seeks sequentially in one session and is limited to 32 unique frames in `full` (4 in `authoring`, 1 in `light`). A static composition exposes only frame 0.
229
236
 
230
237
  ## Metadata is fixed
231
238
 
@@ -35,7 +35,7 @@ Static validation covers:
35
35
  }
36
36
  ```
37
37
 
38
- `details` may include `path`, `line`, `column`, `endLine`, `endColumn`, `element`, `attribute`, `value`, `suggestion`, `frame`, and `controller`.
38
+ `details` may include `path`, `line`, `column`, `endLine`, `endColumn`, `element`, `attribute`, `value`, `suggestion`, `frame`, and `controller`. Timeline diagnostics may also include `timeline`, `clip`, `track`, `channel`, `keyframe`, `jsonPath`, `timelineFrame`, `sourceFrame`, `from`, `to`, and `nesting`; keyframe errors use `channel`, the zero-based `keyframe` index, and the clip-local `frame`, and transition errors use `from`/`to`.
39
39
 
40
40
  ## Common codes
41
41
 
@@ -66,14 +66,38 @@ Static validation covers:
66
66
  | `ELEMENT_NOT_SUPPORTED` | error | `iframe`, `object`, `embed`, `base`, refresh/CSP meta, `source`, `track`, or declarative shadow DOM |
67
67
  | `ELEMENT_UNKNOWN` | warning | Unknown `fy-*` element |
68
68
  | `CONTENT_OUTSIDE_ROOT` | warning | Visual content outside `<fy-composition>` |
69
+ | `TIMELINE_TRANSITION_INVALID` | error | Unknown transition field, invalid clip id, or unsupported easing |
70
+ | `TIMELINE_TRANSITION_UNKNOWN_CLIP` | error | Transition names a missing or self clip |
71
+ | `TIMELINE_TRANSITION_WRONG_TRACK` | error | Transition clips are on different tracks |
72
+ | `TIMELINE_TRANSITION_AUDIO_TRACK` | error | A `dissolve` is declared on an audio track; use `crossfade` |
73
+ | `TIMELINE_TRANSITION_VIDEO_TRACK` | error | A `crossfade` is declared on a video track; use `dissolve` |
74
+ | `TIMELINE_TRANSITION_NOT_OVERLAPPING` | error | Declared transition has no overlap |
75
+ | `TIMELINE_TRANSITION_GEOMETRY` | error | Reversed order or the incoming clip ends before the outgoing clip |
76
+ | `TIMELINE_TRANSITION_DUPLICATE` | error | Repeated pair or a clip used twice on one side |
77
+ | `TIMELINE_TRANSITION_KIND` | error | Unsupported or missing transition `kind` |
69
78
  | `MEDIA_INVALID` | error | Invalid or missing audio/video source or attributes |
70
79
  | `TRANSCRIPT_INVALID` | error | Invalid or missing transcript source |
71
80
  | `ASSET_NOT_FOUND` | error | Missing image, stylesheet, media, or transcript file |
81
+ | `FONT_MANIFEST_INVALID` | error | Invalid font manifest schema, path, face, weight, or duplicate family |
82
+ | `FONT_FILE_MISSING` | error | A manifest font file is missing |
83
+ | `FONT_FILE_INVALID` | error | Wrong extension, signature, or non-file font path |
84
+ | `FONT_HASH_MISMATCH` | error | A vendored font file changed size or SHA-256 |
85
+ | `FONT_LICENSE_MISSING` | warning | Font license type or file is unknown or absent |
86
+ | `FONT_LOAD_FAILED` | error | Chromium failed to load a used `@font-face` |
87
+ | `FONT_PROVIDER_UNAVAILABLE` | error | Fontsource API/CDN unreachable or invalid response |
88
+ | `FONT_DOWNLOAD_FAILED` | error | A Fontsource or URL response was not a valid font file |
89
+ | `FONT_REMOTE` | warning | CSS references a remote font or stylesheet |
90
+ | `FONT_NOT_FOUND` | error | Fontsource id or `font remove` id is not installed |
72
91
  | `AUDIO_WARNING` | warning | Audio timeline overrun or silent embedded media |
73
92
  | `TRANSCRIPT_WARNING` | warning | Empty transcript text or no timed segments |
74
93
  | `DETERMINISM_WARNING` | warning | Forbidden clock/randomness, CSS transition, or runtime diagnostic |
75
94
  | `COMPOSITION_NOT_READY` | error | `seek`/`settle` before a successful load |
76
95
  | `COMPOSITION_DISPOSED` | error | Operation after the host was disposed |
96
+ | `IMAGE_INVALID` | error | An inspect/compare input is missing, unreadable, not an image, or has different dimensions in a comparison |
97
+ | `POLICY_INVALID` | error | `fy.policy.json` or `FRAMEYARD_POLICY` is missing a required field, has an unknown field, or uses an invalid value |
98
+ | `RESOURCE_POLICY_BLOCKED` | error | The active tier blocks the operation, `--force`, or `--overwrite`; use `--dry-run` or ask the user for a `full` run |
99
+ | `RESOURCE_POLICY_LIMIT` | error | A still request exceeds the active tier's frame cap |
100
+ | `RESOURCE_PROFILE_CONFLICT` | error | `--workers` or `--concurrency` exceeds the `shared-cpu` profile cap |
77
101
 
78
102
  ## Results
79
103
 
@@ -87,6 +111,12 @@ Static validation covers:
87
111
 
88
112
  Failures use the same envelope with `error.code`, `error.message`, optional `error.details`, and top-level `diagnostics`.
89
113
 
114
+ ## Lint and resource policy
115
+
116
+ `lint` returns the same diagnostic shape as `check` but does not launch Chromium. It reports static composition and timeline errors, scene and keyframe projection errors for every timeline frame, media metadata errors, and `DETERMINISM_WARNING` for controller source that calls wall-clock or randomness APIs. It does not run `validateProps` or composition runtime.
117
+
118
+ Policy failures use the normal error envelope. `RESOURCE_POLICY_BLOCKED` details include `policy`, `operation`, `allowed`, and a `suggestion`; `RESOURCE_POLICY_LIMIT` includes `limit` and `requested`; `RESOURCE_PROFILE_CONFLICT` includes `profile`, `option`, `limit`, and `requested`. Run the blocked `tts`, `transcribe`, or `render` command with `--dry-run` to get the handoff plan instead of treating the block as a render failure. `font add` requires `authoring` or `full`; `font remove` and `font add --force` require `full`.
119
+
90
120
  ## Working with diagnostics
91
121
 
92
122
  1. Run `npx frameyard check <id> --json`.
@@ -7,13 +7,13 @@ Do not claim success after a nonzero exit. Fix the cause, rerun `check`, and onl
7
7
  | Code | Meaning | Action |
8
8
  | --- | --- | --- |
9
9
  | `USAGE` | Invalid flags or missing required arguments | Read `npx frameyard <command> --help`. |
10
- | `CONFIG_NOT_FOUND` | No `video.config.js` found | Run from the project root or pass `--project`. |
11
- | `CONFIG_INVALID` | Invalid config or composition entry | Fix `video.config.js`; entries must be explicit, local, `.html`, and exist. |
12
- | `COMPOSITION_NOT_FOUND` | ID is not listed | Add the entry to `video.config.js`. |
10
+ | `CONFIG_NOT_FOUND` | No `fy.config.js` found | Run from the project root or pass `--project`. |
11
+ | `CONFIG_INVALID` | Invalid config or composition entry | Fix `fy.config.js`; entries must be explicit, local, `.html`, and exist. |
12
+ | `COMPOSITION_NOT_FOUND` | ID is not listed | Add the entry to `fy.config.js`. |
13
13
  | `COMPOSITION_INVALID` | Invalid root, metadata, fragment structure, or reserved attribute | Fix the composition fragment; check `details.line` and `details.attribute`. |
14
14
  | `COMPOSITION_DOCUMENT_SHELL` | A full document shell is present | Remove `<!doctype>`, `<html>`, `<head>`, and `<body>`; keep the fragment body and top-level metadata. See the 0.8 migration guide. |
15
15
  | `IMPORT_MAP_INVALID` | Invalid import map placement, JSON, attributes, or reserved specifier | Keep one or more top-level import maps; remove `frameyard`/`frameyard/` keys and forbidden attributes. |
16
- | `COMPOSITION_FORMAT_UNRESOLVED` | The composition omits `width`/`height` and the project has no `format` | Declare `width`/`height` on `<fy-composition>`, add `format { width, height, fps }` to `video.config.js`, or render through a timeline. |
16
+ | `COMPOSITION_FORMAT_UNRESOLVED` | The composition omits `width`/`height` and the project has no `format` | Declare `width`/`height` on `<fy-composition>`, add `format { width, height, fps }` to `fy.config.js`, or render through a timeline. |
17
17
  | `COMPOSITION_ERROR` | Mount, update, or browser page error | Read the frame location; remove forbidden clocks/randomness or fix the controller. |
18
18
  | `COMPOSITION_UPDATE_ASYNC` | `update()` returned a promise | Move async work to `mount()`. |
19
19
  | `COMPOSITION_NOT_READY` | `seek`/`settle` before a successful load | Await `load`/`ready`. |
@@ -42,17 +42,41 @@ Do not claim success after a nonzero exit. Fix the cause, rerun `check`, and onl
42
42
  | `FRAME_OUT_OF_RANGE` | Frame outside `0..durationInFrames - 1` | Choose valid frames; a batch error lists every invalid frame and the valid range. |
43
43
  | `AUDIO_INVALID` | Bad clip or unreadable/unsupported audio | Use a local project-root-relative file with a decodable audio stream. |
44
44
  | `AUDIO_WARNING` | Audio timeline overrun or silent embedded media | Adjust timing/trim or remove the silent source. |
45
+ | `TIMELINE_TRANSITION_UNKNOWN_CLIP` | A transition names a missing or self clip | Use two distinct resolved clip IDs in this timeline. |
46
+ | `TIMELINE_TRANSITION_WRONG_TRACK` | The pair is on different tracks | Keep both clips on one video track. |
47
+ | `TIMELINE_TRANSITION_AUDIO_TRACK` | A `dissolve` is on an audio track | Use `"kind": "crossfade"`. |
48
+ | `TIMELINE_TRANSITION_VIDEO_TRACK` | A `crossfade` is on a video track | Use `"kind": "dissolve"`. |
49
+ | `TIMELINE_TRANSITION_NOT_OVERLAPPING` | The incoming clip starts at or after the outgoing clip ends | Move `to.start` earlier; touching endpoints are not a transition. |
50
+ | `TIMELINE_TRANSITION_GEOMETRY` | Reversed order or the incoming clip ends before the outgoing clip | Use `from.start < to.start < from.end < to.end`. |
51
+ | `TIMELINE_TRANSITION_DUPLICATE` | Repeated pair or a clip used twice on one side | Declare each pair once; a clip may chain `from → to → from`. |
52
+ | `TIMELINE_TRANSITION_KIND` | Unsupported or missing `kind` | Use `"kind": "dissolve"` on a video track or `"kind": "crossfade"` on an audio track. |
53
+ | `TIMELINE_TRANSITION_INVALID` | Unknown transition field, invalid id, or unsupported easing | Fix the entry; easing uses the keyframe timing-function vocabulary. |
45
54
  | `MEDIA_INVALID` | Invalid media source or attributes | Use local, seekable files and valid `data-fy-*` values. |
46
55
  | `MEDIA_SEEK_FAILED` | Embedded video did not reach the requested time | Re-encode with a regular keyframe interval or replace the source. |
47
56
  | `TRANSCRIPT_INVALID` | Invalid/missing transcript declaration or JSON | Regenerate with `npx frameyard transcribe`; do not hand-edit timestamps. |
48
57
  | `TRANSCRIPT_WORDS_UNAVAILABLE` | Karaoke requested without measured word timings | Re-run transcription with `--words` or remove `words`. |
49
58
  | `TRANSCRIPT_WARNING` | Empty transcript text or no timed segments | Regenerate the transcript. |
50
59
  | `ASSET_NOT_FOUND` | Required file is missing or unreadable | Restore the asset, model, or text file. |
60
+ | `FONT_MANIFEST_INVALID` | Invalid or unknown manifest field, version, id, family, path, face, or weight | Fix `fy.fonts.json`; reinstall with `npx frameyard font add --file` instead of hand-editing paths. |
61
+ | `FONT_FILE_MISSING` | A font file in the manifest does not exist | Restore the file or remove the face/family. |
62
+ | `FONT_FILE_INVALID` | Wrong extension, signature, or non-file path | Reinstall the font from a valid WOFF2/WOFF/TTF/OTF source. |
63
+ | `FONT_HASH_MISMATCH` | A vendored font file changed size or hash | Restore the file, or reinstall with `npx frameyard font add --force` after approval. |
64
+ | `FONT_LICENSE_MISSING` | License type/file is unknown or absent | Add `--license`/`--license-file`; do not redistribute the project until resolved. |
65
+ | `FONT_LOAD_FAILED` | Chromium could not load a used `@font-face` | Run `npx frameyard font verify`; fix the manifest or file. |
66
+ | `FONT_PROVIDER_UNAVAILABLE` | Fontsource API/CDN is unreachable or returned an invalid response | Retry, check the network, or install the file with `font add --file`/`--url`. |
67
+ | `FONT_DOWNLOAD_FAILED` | A Fontsource or URL response was not a valid font file | Retry the install; verify the URL and family metadata. |
68
+ | `FONT_REMOTE` | CSS still references a remote font/stylesheet | Vendor the font with `font add`; remote fonts are not reproducible. |
69
+ | `FONT_NOT_FOUND` | Fontsource id or `font remove` id is not installed | Search with `npx frameyard font search --json`, or list installed ids. |
51
70
  | `DETERMINISM_WARNING` | Forbidden clock/randomness, CSS transition, or runtime warning | Derive state from `frame`/`props`; use `random("stable:key")`. |
52
71
  | `OUTPUT_EXISTS` | Output exists and replacement was not approved | Reuse it, choose another path, or pass `--overwrite`/`--force` with permission. |
72
+ | `IMAGE_INVALID` | An inspected or compared image is missing, unreadable, or has different dimensions | Check the paths; for `compare`, use images with the same width and height. |
73
+ | `POLICY_INVALID` | The workspace policy file or `FRAMEYARD_POLICY` is malformed | Read the `details.path`/`jsonPath`, fix the field, or ask the user to remove the override. |
74
+ | `RESOURCE_POLICY_BLOCKED` | The active tier blocks the operation, `--force`, or `--overwrite` | Use `--dry-run` for a handoff plan, or ask the user to run the heavy command with `FRAMEYARD_POLICY=full`. |
75
+ | `RESOURCE_POLICY_LIMIT` | The still request exceeds the tier frame cap | Split the frames into smaller calls; do not assume the 32-frame library cap is available. |
76
+ | `RESOURCE_PROFILE_CONFLICT` | `--workers` or `--concurrency` exceeds the `shared-cpu` cap | Lower the value or omit `--profile`; the CLI does not clamp it. |
53
77
  | `OUTPUT_FAILED` | An output or manifest could not be written atomically | Check directory permissions and free space; rerun. |
54
- | `EXECUTABLE_NOT_FOUND` | `qwen-tts` or `whisper-cli` could not be resolved | Pass `--executable`, set the environment variable, or fix `PATH`. |
55
- | `TTS_FAILED` | Speech synthesis failed or produced an invalid WAV | Read stderr; verify the talker/codec models and CLI compatibility. |
78
+ | `EXECUTABLE_NOT_FOUND` | `qwen-tts`, `sherpa-onnx-offline-tts`, or `whisper-cli` could not be resolved | Pass `--executable`, set `FRAMEYARD_QWEN_TTS`, `FRAMEYARD_SHERPA_ONNX_TTS`, or `FRAMEYARD_WHISPER_CLI`, or fix `PATH`. |
79
+ | `TTS_FAILED` | Speech synthesis failed or produced an invalid WAV | Read stderr; verify the engine-specific models (`--talker-model`/`--codec-model` for qwen, `--model-dir` for Supertonic) and CLI compatibility. |
56
80
  | `TRANSCRIBE_FAILED` | Normalization or `whisper-cli` failed | Verify FFmpeg, the source media, and the Whisper model. |
57
81
  | `MANIFEST_INVALID` | Provenance manifest is corrupt or unsupported | Restore or regenerate the manifest; do not invent hashes. |
58
82
  | `FFMPEG_FAILED` | FFmpeg is missing, lacks encoders, or failed | Run `npx frameyard doctor --json`; install FFmpeg with libx264 and AAC. |
@@ -62,6 +86,8 @@ Do not claim success after a nonzero exit. Fix the cause, rerun `check`, and onl
62
86
  | `CANCELLED` | The operation was interrupted | Rerun when ready. Partial outputs are removed. |
63
87
  | `INTERNAL` | Unexpected internal failure | Rerun with `FRAMEYARD_DEBUG=1` and report the stack. |
64
88
 
89
+ When a heavy command is blocked, run it once with `--dry-run`. The result contains `commandLine`, `prerequisites`, and `blockers`; report `deferred: true` and the command instead of claiming the render or asset exists. The command line already includes `FRAMEYARD_POLICY=full` when the active tier is not `full`.
90
+
65
91
  ## Behavior to rely on
66
92
 
67
93
  - A failed render writes to a temporary file and does not publish a partial output.