agentvfx 0.0.0-stage → 0.3.4

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 (483) hide show
  1. package/AGENTS.md +8 -0
  2. package/GEMINI.md +1 -0
  3. package/LICENSE +172 -0
  4. package/README.md +236 -2
  5. package/package.json +35 -4
  6. package/skills/agentvfx/LICENSE +172 -0
  7. package/skills/agentvfx/NOTICE +44 -0
  8. package/skills/agentvfx/SKILL.md +90 -0
  9. package/skills/agentvfx/engine/core/constants.mjs +84 -0
  10. package/skills/agentvfx/engine/core/flash-constants.mjs +23 -0
  11. package/skills/agentvfx/engine/core/flash.mjs +240 -0
  12. package/skills/agentvfx/engine/core/mark.mjs +28 -0
  13. package/skills/agentvfx/engine/core/palettes.mjs +36 -0
  14. package/skills/agentvfx/engine/core/schedule.mjs +85 -0
  15. package/skills/agentvfx/engine/core/templates.mjs +43 -0
  16. package/skills/agentvfx/engine/core/util.mjs +167 -0
  17. package/skills/agentvfx/engine/core/validate.mjs +334 -0
  18. package/skills/agentvfx/engine/node/analysis.mjs +172 -0
  19. package/skills/agentvfx/engine/node/audio.mjs +621 -0
  20. package/skills/agentvfx/engine/node/board.mjs +24 -0
  21. package/skills/agentvfx/engine/node/chrome.mjs +349 -0
  22. package/skills/agentvfx/engine/node/encode.mjs +110 -0
  23. package/skills/agentvfx/engine/node/eye.mjs +77 -0
  24. package/skills/agentvfx/engine/node/footage.mjs +129 -0
  25. package/skills/agentvfx/engine/node/frame-worker.mjs +39 -0
  26. package/skills/agentvfx/engine/node/host.mjs +31 -0
  27. package/skills/agentvfx/engine/node/job.mjs +181 -0
  28. package/skills/agentvfx/engine/node/png.mjs +112 -0
  29. package/skills/agentvfx/engine/node/render.mjs +161 -0
  30. package/skills/agentvfx/engine/node/stage-capture.mjs +187 -0
  31. package/skills/agentvfx/engine/node/stage-serve.mjs +72 -0
  32. package/skills/agentvfx/engine/node/stills.mjs +100 -0
  33. package/skills/agentvfx/engine/node/ws-server.mjs +132 -0
  34. package/skills/agentvfx/engine/web/gl.mjs +319 -0
  35. package/skills/agentvfx/engine/web/glsl-common.mjs +78 -0
  36. package/skills/agentvfx/engine/web/init.js +23 -0
  37. package/skills/agentvfx/engine/web/prelude.mjs +150 -0
  38. package/skills/agentvfx/engine/web/render.html +11 -0
  39. package/skills/agentvfx/engine/web/render.mjs +388 -0
  40. package/skills/agentvfx/engine/web/scale.mjs +15 -0
  41. package/skills/agentvfx/engine/web/text.mjs +158 -0
  42. package/skills/agentvfx/plates/terminalcore/README.md +61 -0
  43. package/skills/agentvfx/plates/terminalcore/__pycache__/crt.cpython-313.pyc +0 -0
  44. package/skills/agentvfx/plates/terminalcore/__pycache__/font.cpython-313.pyc +0 -0
  45. package/skills/agentvfx/plates/terminalcore/__pycache__/gallery.cpython-313.pyc +0 -0
  46. package/skills/agentvfx/plates/terminalcore/__pycache__/gallery_mats.cpython-313.pyc +0 -0
  47. package/skills/agentvfx/plates/terminalcore/__pycache__/gallery_room.cpython-313.pyc +0 -0
  48. package/skills/agentvfx/plates/terminalcore/__pycache__/materials.cpython-313.pyc +0 -0
  49. package/skills/agentvfx/plates/terminalcore/__pycache__/nodekit.cpython-313.pyc +0 -0
  50. package/skills/agentvfx/plates/terminalcore/__pycache__/room.cpython-313.pyc +0 -0
  51. package/skills/agentvfx/plates/terminalcore/__pycache__/shots.cpython-313.pyc +0 -0
  52. package/skills/agentvfx/plates/terminalcore/__pycache__/textures.cpython-313.pyc +0 -0
  53. package/skills/agentvfx/plates/terminalcore/__pycache__/wall.cpython-313.pyc +0 -0
  54. package/skills/agentvfx/plates/terminalcore/crt.py +143 -0
  55. package/skills/agentvfx/plates/terminalcore/font.py +80 -0
  56. package/skills/agentvfx/plates/terminalcore/gallery.py +61 -0
  57. package/skills/agentvfx/plates/terminalcore/gallery_mats.py +144 -0
  58. package/skills/agentvfx/plates/terminalcore/gallery_room.py +126 -0
  59. package/skills/agentvfx/plates/terminalcore/materials.py +170 -0
  60. package/skills/agentvfx/plates/terminalcore/nodekit.py +87 -0
  61. package/skills/agentvfx/plates/terminalcore/room.py +82 -0
  62. package/skills/agentvfx/plates/terminalcore/scene.py +219 -0
  63. package/skills/agentvfx/plates/terminalcore/shots.py +48 -0
  64. package/skills/agentvfx/plates/terminalcore/textures.py +79 -0
  65. package/skills/agentvfx/plates/terminalcore/wall.py +103 -0
  66. package/skills/agentvfx/references/plates.md +22 -0
  67. package/skills/agentvfx/references/template-format.md +149 -0
  68. package/skills/agentvfx/references/web.md +63 -0
  69. package/skills/agentvfx/runtime/agentvfx.js +708 -0
  70. package/skills/agentvfx/schemas/fx-score.schema.json +98 -0
  71. package/skills/agentvfx/scripts/export-web.mjs +155 -0
  72. package/skills/agentvfx/scripts/loop.mjs +129 -0
  73. package/skills/agentvfx/scripts/make-beat.mjs +133 -0
  74. package/skills/agentvfx/scripts/plate.mjs +82 -0
  75. package/skills/agentvfx/scripts/vfx.mjs +173 -0
  76. package/skills/agentvfx/templates/_test/feedback-counter/fx.json +11 -0
  77. package/skills/agentvfx/templates/_test/feedback-counter/main.frag +4 -0
  78. package/skills/agentvfx/templates/_test/feedback-counter/pass0.frag +4 -0
  79. package/skills/agentvfx/templates/_test/gradient/fx.json +23 -0
  80. package/skills/agentvfx/templates/_test/gradient/main.frag +5 -0
  81. package/skills/agentvfx/templates/_test/identity/fx.json +10 -0
  82. package/skills/agentvfx/templates/_test/identity/main.frag +4 -0
  83. package/skills/agentvfx/templates/_test/strobe/fx.json +14 -0
  84. package/skills/agentvfx/templates/_test/strobe/main.frag +6 -0
  85. package/skills/agentvfx/templates/_test/text-probe/fx.json +15 -0
  86. package/skills/agentvfx/templates/_test/text-probe/main.frag +6 -0
  87. package/skills/agentvfx/templates/brutal/NOTES.md +14 -0
  88. package/skills/agentvfx/templates/brutal/_src/boot-log/def.mjs +57 -0
  89. package/skills/agentvfx/templates/brutal/_src/boot-log/main.src.frag +129 -0
  90. package/skills/agentvfx/templates/brutal/_src/build.mjs +33 -0
  91. package/skills/agentvfx/templates/brutal/_src/halftone-field/def.mjs +72 -0
  92. package/skills/agentvfx/templates/brutal/_src/halftone-field/main.src.frag +94 -0
  93. package/skills/agentvfx/templates/brutal/_src/hud-deck/deck-left.src.glsl +100 -0
  94. package/skills/agentvfx/templates/brutal/_src/hud-deck/deck-right.src.glsl +61 -0
  95. package/skills/agentvfx/templates/brutal/_src/hud-deck/deck-tiles.src.glsl +153 -0
  96. package/skills/agentvfx/templates/brutal/_src/hud-deck/def.mjs +82 -0
  97. package/skills/agentvfx/templates/brutal/_src/hud-deck/main.src.frag +110 -0
  98. package/skills/agentvfx/templates/brutal/_src/hud-deck/pass0.src.frag +38 -0
  99. package/skills/agentvfx/templates/brutal/_src/shared/defs.mjs +28 -0
  100. package/skills/agentvfx/templates/brutal/_src/shared/icons.glsl +72 -0
  101. package/skills/agentvfx/templates/brutal/_src/shared/lib.glsl +115 -0
  102. package/skills/agentvfx/templates/brutal/_src/shared/screen.glsl +43 -0
  103. package/skills/agentvfx/templates/brutal/boot-log/demo-acid.json +1 -0
  104. package/skills/agentvfx/templates/brutal/boot-log/demo-phosphor.json +1 -0
  105. package/skills/agentvfx/templates/brutal/boot-log/demo-ultramarine.json +1 -0
  106. package/skills/agentvfx/templates/brutal/boot-log/fx.json +257 -0
  107. package/skills/agentvfx/templates/brutal/boot-log/main.frag +242 -0
  108. package/skills/agentvfx/templates/brutal/collage/_src/00-lib.glsl +73 -0
  109. package/skills/agentvfx/templates/brutal/collage/_src/10-spectrum-scope.glsl +73 -0
  110. package/skills/agentvfx/templates/brutal/collage/_src/20-meter-readout-cross.glsl +87 -0
  111. package/skills/agentvfx/templates/brutal/collage/_src/30-footage-fillers.glsl +92 -0
  112. package/skills/agentvfx/templates/brutal/collage/_src/40-editorial.glsl +232 -0
  113. package/skills/agentvfx/templates/brutal/collage/_src/50-typefx.glsl +163 -0
  114. package/skills/agentvfx/templates/brutal/collage/_src/60-scene-lib.glsl +104 -0
  115. package/skills/agentvfx/templates/brutal/collage/_src/61-scene-specimen.glsl +71 -0
  116. package/skills/agentvfx/templates/brutal/collage/_src/62-scene-wiring.glsl +96 -0
  117. package/skills/agentvfx/templates/brutal/collage/_src/63-scene-fieldcut.glsl +119 -0
  118. package/skills/agentvfx/templates/brutal/collage/_src/64-scene-pattern.glsl +64 -0
  119. package/skills/agentvfx/templates/brutal/collage/_src/641-scene-anatomy.glsl +49 -0
  120. package/skills/agentvfx/templates/brutal/collage/_src/642-scene-deck.glsl +78 -0
  121. package/skills/agentvfx/templates/brutal/collage/_src/643-scene-bootlog.glsl +60 -0
  122. package/skills/agentvfx/templates/brutal/collage/_src/644-scene-tunnel.glsl +48 -0
  123. package/skills/agentvfx/templates/brutal/collage/_src/645-scene-runner.glsl +42 -0
  124. package/skills/agentvfx/templates/brutal/collage/_src/646-scene-ascii.glsl +66 -0
  125. package/skills/agentvfx/templates/brutal/collage/_src/647-scene-cubewall.glsl +80 -0
  126. package/skills/agentvfx/templates/brutal/collage/_src/648-scene-vortex.glsl +46 -0
  127. package/skills/agentvfx/templates/brutal/collage/_src/649-scene-sticker.glsl +72 -0
  128. package/skills/agentvfx/templates/brutal/collage/_src/65-scene-main.glsl +1 -0
  129. package/skills/agentvfx/templates/brutal/collage/_src/650-scene-ribbon.glsl +55 -0
  130. package/skills/agentvfx/templates/brutal/collage/_src/651-scene-slab.glsl +38 -0
  131. package/skills/agentvfx/templates/brutal/collage/_src/652-scene-framedial.glsl +79 -0
  132. package/skills/agentvfx/templates/brutal/collage/_src/653-scene-tilt.glsl +40 -0
  133. package/skills/agentvfx/templates/brutal/collage/_src/654-scene-marquee.glsl +45 -0
  134. package/skills/agentvfx/templates/brutal/collage/_src/655-scene-lens.glsl +57 -0
  135. package/skills/agentvfx/templates/brutal/collage/_src/656-scene-liquid.glsl +35 -0
  136. package/skills/agentvfx/templates/brutal/collage/_src/657-scene-panelgrid.glsl +75 -0
  137. package/skills/agentvfx/templates/brutal/collage/_src/658-scene-mirror.glsl +49 -0
  138. package/skills/agentvfx/templates/brutal/collage/_src/66-scene-main.glsl +27 -0
  139. package/skills/agentvfx/templates/brutal/collage/_src/90-main.glsl +120 -0
  140. package/skills/agentvfx/templates/brutal/collage/_src/build.mjs +24 -0
  141. package/skills/agentvfx/templates/brutal/collage/_src/sync-presets.mjs +16 -0
  142. package/skills/agentvfx/templates/brutal/collage/demo-bone-lime.json +1 -0
  143. package/skills/agentvfx/templates/brutal/collage/demo-lime-sparse.json +1 -0
  144. package/skills/agentvfx/templates/brutal/collage/demo-orange-dense.json +1 -0
  145. package/skills/agentvfx/templates/brutal/collage/demo-ultra-slow.json +1 -0
  146. package/skills/agentvfx/templates/brutal/collage/fx.json +4882 -0
  147. package/skills/agentvfx/templates/brutal/collage/layout.mjs +97 -0
  148. package/skills/agentvfx/templates/brutal/collage/layouts.json +366 -0
  149. package/skills/agentvfx/templates/brutal/collage/main.frag +2367 -0
  150. package/skills/agentvfx/templates/brutal/collage/pass0.frag +265 -0
  151. package/skills/agentvfx/templates/brutal/halftone-field/demo-acid.json +1 -0
  152. package/skills/agentvfx/templates/brutal/halftone-field/demo-ascii.json +1 -0
  153. package/skills/agentvfx/templates/brutal/halftone-field/demo-dither.json +1 -0
  154. package/skills/agentvfx/templates/brutal/halftone-field/demo-orange.json +1 -0
  155. package/skills/agentvfx/templates/brutal/halftone-field/demo-phosphor.json +1 -0
  156. package/skills/agentvfx/templates/brutal/halftone-field/demo-ultramarine.json +1 -0
  157. package/skills/agentvfx/templates/brutal/halftone-field/fx.json +474 -0
  158. package/skills/agentvfx/templates/brutal/halftone-field/main.frag +248 -0
  159. package/skills/agentvfx/templates/brutal/hud-deck/demo-acid.json +1 -0
  160. package/skills/agentvfx/templates/brutal/hud-deck/demo-night.json +1 -0
  161. package/skills/agentvfx/templates/brutal/hud-deck/demo-octagon.json +1 -0
  162. package/skills/agentvfx/templates/brutal/hud-deck/demo-ultramarine.json +1 -0
  163. package/skills/agentvfx/templates/brutal/hud-deck/fx.json +601 -0
  164. package/skills/agentvfx/templates/brutal/hud-deck/main.frag +645 -0
  165. package/skills/agentvfx/templates/brutal/hud-deck/pass0.frag +38 -0
  166. package/skills/agentvfx/templates/brutal/lattice/demo-cobalt-fine.json +1 -0
  167. package/skills/agentvfx/templates/brutal/lattice/demo-ink-yellow.json +1 -0
  168. package/skills/agentvfx/templates/brutal/lattice/demo-vermilion.json +1 -0
  169. package/skills/agentvfx/templates/brutal/lattice/demo-violet-slow.json +1 -0
  170. package/skills/agentvfx/templates/brutal/lattice/fx.json +130 -0
  171. package/skills/agentvfx/templates/brutal/lattice/main.frag +175 -0
  172. package/skills/agentvfx/templates/brutal/lattice/pass0.frag +29 -0
  173. package/skills/agentvfx/templates/glitch/block-glitch/demo-mosaic.json +1 -0
  174. package/skills/agentvfx/templates/glitch/block-glitch/demo-onset.json +1 -0
  175. package/skills/agentvfx/templates/glitch/block-glitch/demo-static.json +1 -0
  176. package/skills/agentvfx/templates/glitch/block-glitch/demo-tear.json +1 -0
  177. package/skills/agentvfx/templates/glitch/block-glitch/fx.json +54 -0
  178. package/skills/agentvfx/templates/glitch/block-glitch/main.frag +132 -0
  179. package/skills/agentvfx/templates/glitch/block-glitch/pass0.frag +12 -0
  180. package/skills/agentvfx/templates/glitch/pixel-sort/NOTES.md +13 -0
  181. package/skills/agentvfx/templates/glitch/pixel-sort/build-passes.mjs +162 -0
  182. package/skills/agentvfx/templates/glitch/pixel-sort/demo-chunk.json +1 -0
  183. package/skills/agentvfx/templates/glitch/pixel-sort/demo-hue.json +1 -0
  184. package/skills/agentvfx/templates/glitch/pixel-sort/demo-kick.json +1 -0
  185. package/skills/agentvfx/templates/glitch/pixel-sort/demo-streak.json +1 -0
  186. package/skills/agentvfx/templates/glitch/pixel-sort/demo-vertical.json +1 -0
  187. package/skills/agentvfx/templates/glitch/pixel-sort/fx.json +60 -0
  188. package/skills/agentvfx/templates/glitch/pixel-sort/main.frag +83 -0
  189. package/skills/agentvfx/templates/glitch/pixel-sort/pass0.frag +64 -0
  190. package/skills/agentvfx/templates/glitch/pixel-sort/pass1.frag +66 -0
  191. package/skills/agentvfx/templates/glitch/pixel-sort/pass2.frag +66 -0
  192. package/skills/agentvfx/templates/glitch/pixel-sort/pass3.frag +66 -0
  193. package/skills/agentvfx/templates/glitch/pixel-sort/pass4.frag +66 -0
  194. package/skills/agentvfx/templates/glitch/pixel-sort/pass5.frag +66 -0
  195. package/skills/agentvfx/templates/glitch/pixel-sort/pass6.frag +66 -0
  196. package/skills/agentvfx/templates/glitch/pixel-sort/pass7.frag +66 -0
  197. package/skills/agentvfx/templates/glitch/pixel-sort/pass8.frag +66 -0
  198. package/skills/agentvfx/templates/glitch/rgb-split/demo-diagonal-stutter.json +1 -0
  199. package/skills/agentvfx/templates/glitch/rgb-split/demo-hard.json +1 -0
  200. package/skills/agentvfx/templates/glitch/rgb-split/demo-kick.json +1 -0
  201. package/skills/agentvfx/templates/glitch/rgb-split/demo-radial.json +1 -0
  202. package/skills/agentvfx/templates/glitch/rgb-split/fx.json +46 -0
  203. package/skills/agentvfx/templates/glitch/rgb-split/main.frag +76 -0
  204. package/skills/agentvfx/templates/glitch/vhs/demo-clean.json +1 -0
  205. package/skills/agentvfx/templates/glitch/vhs/demo-crash.json +1 -0
  206. package/skills/agentvfx/templates/glitch/vhs/demo-tape.json +1 -0
  207. package/skills/agentvfx/templates/glitch/vhs/demo-worn.json +1 -0
  208. package/skills/agentvfx/templates/glitch/vhs/fx.json +46 -0
  209. package/skills/agentvfx/templates/glitch/vhs/main.frag +96 -0
  210. package/skills/agentvfx/templates/impact/impact-1bit/demo-onebit-bayer.json +1 -0
  211. package/skills/agentvfx/templates/impact/impact-1bit/fx.json +45 -0
  212. package/skills/agentvfx/templates/impact/impact-1bit/main.frag +64 -0
  213. package/skills/agentvfx/templates/impact/impact-invert/demo-invert-snap.json +1 -0
  214. package/skills/agentvfx/templates/impact/impact-invert/fx.json +37 -0
  215. package/skills/agentvfx/templates/impact/impact-invert/main.frag +44 -0
  216. package/skills/agentvfx/templates/impact/punch-shake/demo-punch-hard.json +1 -0
  217. package/skills/agentvfx/templates/impact/punch-shake/fx.json +36 -0
  218. package/skills/agentvfx/templates/impact/punch-shake/main.frag +62 -0
  219. package/skills/agentvfx/templates/impact/rgb-hit/demo-rgb-slam.json +1 -0
  220. package/skills/agentvfx/templates/impact/rgb-hit/fx.json +39 -0
  221. package/skills/agentvfx/templates/impact/rgb-hit/main.frag +62 -0
  222. package/skills/agentvfx/templates/impact/signal-cut/demo-lime-hold.json +1 -0
  223. package/skills/agentvfx/templates/impact/signal-cut/demo-lime.json +1 -0
  224. package/skills/agentvfx/templates/impact/signal-cut/demo-orange.json +1 -0
  225. package/skills/agentvfx/templates/impact/signal-cut/demo-ultramarine.json +1 -0
  226. package/skills/agentvfx/templates/impact/signal-cut/fx.json +83 -0
  227. package/skills/agentvfx/templates/impact/signal-cut/main.frag +101 -0
  228. package/skills/agentvfx/templates/impact/signal-cut/pass0.frag +68 -0
  229. package/skills/agentvfx/templates/impact/speedline-burst/demo-speed-white.json +1 -0
  230. package/skills/agentvfx/templates/impact/speedline-burst/fx.json +44 -0
  231. package/skills/agentvfx/templates/impact/speedline-burst/main.frag +68 -0
  232. package/skills/agentvfx/templates/impact/strobe-burst/demo-strobe-white-16.json +1 -0
  233. package/skills/agentvfx/templates/impact/strobe-burst/fx.json +42 -0
  234. package/skills/agentvfx/templates/impact/strobe-burst/main.frag +43 -0
  235. package/skills/agentvfx/templates/light/anamorphic-streak/demo-anamorphic-cinema.json +10 -0
  236. package/skills/agentvfx/templates/light/anamorphic-streak/demo-data.json +10 -0
  237. package/skills/agentvfx/templates/light/anamorphic-streak/fx.json +47 -0
  238. package/skills/agentvfx/templates/light/anamorphic-streak/main.frag +45 -0
  239. package/skills/agentvfx/templates/light/anamorphic-streak/pass0.frag +22 -0
  240. package/skills/agentvfx/templates/light/anamorphic-streak/pass1.frag +16 -0
  241. package/skills/agentvfx/templates/light/anamorphic-streak/pass2.frag +16 -0
  242. package/skills/agentvfx/templates/light/anamorphic-streak/pass3.frag +16 -0
  243. package/skills/agentvfx/templates/light/bloom-glow/demo-clip01-low.json +11 -0
  244. package/skills/agentvfx/templates/light/bloom-glow/demo-neon-night.json +10 -0
  245. package/skills/agentvfx/templates/light/bloom-glow/demo-phosphor.json +11 -0
  246. package/skills/agentvfx/templates/light/bloom-glow/fx.json +53 -0
  247. package/skills/agentvfx/templates/light/bloom-glow/main.frag +85 -0
  248. package/skills/agentvfx/templates/light/bloom-glow/pass0.frag +21 -0
  249. package/skills/agentvfx/templates/light/bloom-glow/pass1.frag +50 -0
  250. package/skills/agentvfx/templates/light/bloom-glow/pass2.frag +44 -0
  251. package/skills/agentvfx/templates/light/bloom-glow/pass3.frag +32 -0
  252. package/skills/agentvfx/templates/light/bloom-glow/pass4.frag +48 -0
  253. package/skills/agentvfx/templates/light/bloom-glow/pass5.frag +46 -0
  254. package/skills/agentvfx/templates/light/god-rays/demo-golden-hour.json +10 -0
  255. package/skills/agentvfx/templates/light/god-rays/demo-point-lamp.json +10 -0
  256. package/skills/agentvfx/templates/light/god-rays/fx.json +39 -0
  257. package/skills/agentvfx/templates/light/god-rays/main.frag +54 -0
  258. package/skills/agentvfx/templates/light/lens-flare/demo-clip01-bar.json +11 -0
  259. package/skills/agentvfx/templates/light/lens-flare/demo-eye-bar.json +10 -0
  260. package/skills/agentvfx/templates/light/lens-flare/demo-sun-ghosts.json +10 -0
  261. package/skills/agentvfx/templates/light/lens-flare/fx.json +49 -0
  262. package/skills/agentvfx/templates/light/lens-flare/main.frag +86 -0
  263. package/skills/agentvfx/templates/light/lens-flare/pass0.frag +24 -0
  264. package/skills/agentvfx/templates/light/light-leak/demo-slit.json +10 -0
  265. package/skills/agentvfx/templates/light/light-leak/demo-vintage-burn.json +10 -0
  266. package/skills/agentvfx/templates/light/light-leak/fx.json +40 -0
  267. package/skills/agentvfx/templates/light/light-leak/main.frag +46 -0
  268. package/skills/agentvfx/templates/light/light-stage/fx.json +29 -0
  269. package/skills/agentvfx/templates/light/light-stage/main.frag +56 -0
  270. package/skills/agentvfx/templates/light/light-sweep/demo-hard-bar.json +12 -0
  271. package/skills/agentvfx/templates/light/light-sweep/demo-logo-sweep.json +10 -0
  272. package/skills/agentvfx/templates/light/light-sweep/demo-onset.json +11 -0
  273. package/skills/agentvfx/templates/light/light-sweep/fx.json +45 -0
  274. package/skills/agentvfx/templates/light/light-sweep/main.frag +29 -0
  275. package/skills/agentvfx/templates/light/light-sweep/pass0.frag +17 -0
  276. package/skills/agentvfx/templates/light/light-sweep/pass1.frag +15 -0
  277. package/skills/agentvfx/templates/light/neon-saber/demo-hairline.json +10 -0
  278. package/skills/agentvfx/templates/light/neon-saber/demo-saber-laser.json +10 -0
  279. package/skills/agentvfx/templates/light/neon-saber/fx.json +48 -0
  280. package/skills/agentvfx/templates/light/neon-saber/main.frag +27 -0
  281. package/skills/agentvfx/templates/light/neon-saber/pass0.frag +46 -0
  282. package/skills/agentvfx/templates/light/neon-saber/pass1.frag +15 -0
  283. package/skills/agentvfx/templates/psychedelic/echo-flow/demo-trail.json +1 -0
  284. package/skills/agentvfx/templates/psychedelic/echo-flow/fx.json +38 -0
  285. package/skills/agentvfx/templates/psychedelic/echo-flow/main.frag +19 -0
  286. package/skills/agentvfx/templates/psychedelic/echo-flow/pass0.frag +16 -0
  287. package/skills/agentvfx/templates/psychedelic/echo-flow/pass1.frag +20 -0
  288. package/skills/agentvfx/templates/psychedelic/echo-flow/pass2.frag +11 -0
  289. package/skills/agentvfx/templates/psychedelic/echo-flow/pass3.frag +4 -0
  290. package/skills/agentvfx/templates/psychedelic/hologram-scan/demo-cyan.json +1 -0
  291. package/skills/agentvfx/templates/psychedelic/hologram-scan/fx.json +36 -0
  292. package/skills/agentvfx/templates/psychedelic/hologram-scan/main.frag +30 -0
  293. package/skills/agentvfx/templates/psychedelic/kaleido-fold/demo-six.json +1 -0
  294. package/skills/agentvfx/templates/psychedelic/kaleido-fold/fx.json +37 -0
  295. package/skills/agentvfx/templates/psychedelic/kaleido-fold/main.frag +10 -0
  296. package/skills/agentvfx/templates/psychedelic/kaleido-fold/pass0.frag +34 -0
  297. package/skills/agentvfx/templates/psychedelic/mosh-melt/demo-classic.json +1 -0
  298. package/skills/agentvfx/templates/psychedelic/mosh-melt/fx.json +38 -0
  299. package/skills/agentvfx/templates/psychedelic/mosh-melt/main.frag +20 -0
  300. package/skills/agentvfx/templates/psychedelic/mosh-melt/pass0.frag +22 -0
  301. package/skills/agentvfx/templates/psychedelic/sacred/NOTES.md +13 -0
  302. package/skills/agentvfx/templates/psychedelic/sacred/demo-classic.json +1 -0
  303. package/skills/agentvfx/templates/psychedelic/sacred/demo-halo.json +1 -0
  304. package/skills/agentvfx/templates/psychedelic/sacred/demo-slow-tide.json +1 -0
  305. package/skills/agentvfx/templates/psychedelic/sacred/demo-thermal.json +1 -0
  306. package/skills/agentvfx/templates/psychedelic/sacred/fx.json +263 -0
  307. package/skills/agentvfx/templates/psychedelic/sacred/main.frag +109 -0
  308. package/skills/agentvfx/templates/psychedelic/sacred/pass0.frag +25 -0
  309. package/skills/agentvfx/templates/psychedelic/sacred/pass1.frag +23 -0
  310. package/skills/agentvfx/templates/psychedelic/tidal-bands/demo-classic.json +1 -0
  311. package/skills/agentvfx/templates/psychedelic/tidal-bands/fx.json +37 -0
  312. package/skills/agentvfx/templates/psychedelic/tidal-bands/main.frag +54 -0
  313. package/skills/agentvfx/templates/scope/_chrome.glsl +242 -0
  314. package/skills/agentvfx/templates/scope/goniometer/demo-boot-green.json +1 -0
  315. package/skills/agentvfx/templates/scope/goniometer/demo-field-lime.json +1 -0
  316. package/skills/agentvfx/templates/scope/goniometer/demo-field-ultramarine.json +1 -0
  317. package/skills/agentvfx/templates/scope/goniometer/demo-glow-phosphor.json +1 -0
  318. package/skills/agentvfx/templates/scope/goniometer/demo-hud-black.json +1 -0
  319. package/skills/agentvfx/templates/scope/goniometer/fx.json +635 -0
  320. package/skills/agentvfx/templates/scope/goniometer/main.frag +33 -0
  321. package/skills/agentvfx/templates/scope/goniometer/pass0.frag +58 -0
  322. package/skills/agentvfx/templates/scope/goniometer/pass1.frag +43 -0
  323. package/skills/agentvfx/templates/scope/goniometer/pass2.frag +12 -0
  324. package/skills/agentvfx/templates/scope/goniometer/pass3.frag +12 -0
  325. package/skills/agentvfx/templates/scope/goniometer/pass4.frag +12 -0
  326. package/skills/agentvfx/templates/scope/goniometer/pass5.frag +12 -0
  327. package/skills/agentvfx/templates/scope/goniometer/pass6.frag +251 -0
  328. package/skills/agentvfx/templates/scope/scope-polar/demo-boot-green.json +1 -0
  329. package/skills/agentvfx/templates/scope/scope-polar/demo-field-lime.json +1 -0
  330. package/skills/agentvfx/templates/scope/scope-polar/demo-field-ultramarine.json +1 -0
  331. package/skills/agentvfx/templates/scope/scope-polar/demo-glow-phosphor.json +1 -0
  332. package/skills/agentvfx/templates/scope/scope-polar/demo-hud-black.json +1 -0
  333. package/skills/agentvfx/templates/scope/scope-polar/fx.json +686 -0
  334. package/skills/agentvfx/templates/scope/scope-polar/main.frag +39 -0
  335. package/skills/agentvfx/templates/scope/scope-polar/pass0.frag +84 -0
  336. package/skills/agentvfx/templates/scope/scope-polar/pass1.frag +49 -0
  337. package/skills/agentvfx/templates/scope/scope-polar/pass2.frag +12 -0
  338. package/skills/agentvfx/templates/scope/scope-polar/pass3.frag +12 -0
  339. package/skills/agentvfx/templates/scope/scope-polar/pass4.frag +12 -0
  340. package/skills/agentvfx/templates/scope/scope-polar/pass5.frag +12 -0
  341. package/skills/agentvfx/templates/scope/scope-polar/pass6.frag +251 -0
  342. package/skills/agentvfx/templates/scope/scope-triggered/demo-boot-green.json +1 -0
  343. package/skills/agentvfx/templates/scope/scope-triggered/demo-field-lime.json +1 -0
  344. package/skills/agentvfx/templates/scope/scope-triggered/demo-field-ultramarine.json +1 -0
  345. package/skills/agentvfx/templates/scope/scope-triggered/demo-glow-phosphor.json +1 -0
  346. package/skills/agentvfx/templates/scope/scope-triggered/demo-hud-black.json +1 -0
  347. package/skills/agentvfx/templates/scope/scope-triggered/fx.json +712 -0
  348. package/skills/agentvfx/templates/scope/scope-triggered/main.frag +36 -0
  349. package/skills/agentvfx/templates/scope/scope-triggered/pass0.frag +78 -0
  350. package/skills/agentvfx/templates/scope/scope-triggered/pass1.frag +50 -0
  351. package/skills/agentvfx/templates/scope/scope-triggered/pass2.frag +12 -0
  352. package/skills/agentvfx/templates/scope/scope-triggered/pass3.frag +12 -0
  353. package/skills/agentvfx/templates/scope/scope-triggered/pass4.frag +12 -0
  354. package/skills/agentvfx/templates/scope/scope-triggered/pass5.frag +12 -0
  355. package/skills/agentvfx/templates/scope/scope-triggered/pass6.frag +251 -0
  356. package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-boot-green.json +1 -0
  357. package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-field-lime.json +1 -0
  358. package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-field-ultramarine.json +1 -0
  359. package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-glow-phosphor.json +1 -0
  360. package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-hud-black.json +1 -0
  361. package/skills/agentvfx/templates/scope/spectrogram-scroll/fx.json +595 -0
  362. package/skills/agentvfx/templates/scope/spectrogram-scroll/main.frag +83 -0
  363. package/skills/agentvfx/templates/scope/spectrogram-scroll/pass0.frag +52 -0
  364. package/skills/agentvfx/templates/scope/spectrogram-scroll/pass1.frag +251 -0
  365. package/skills/agentvfx/templates/scope/xy-phosphor/demo-boot-green.json +1 -0
  366. package/skills/agentvfx/templates/scope/xy-phosphor/demo-field-lime.json +1 -0
  367. package/skills/agentvfx/templates/scope/xy-phosphor/demo-field-ultramarine.json +1 -0
  368. package/skills/agentvfx/templates/scope/xy-phosphor/demo-glow-phosphor.json +1 -0
  369. package/skills/agentvfx/templates/scope/xy-phosphor/demo-hud-black.json +1 -0
  370. package/skills/agentvfx/templates/scope/xy-phosphor/fx.json +642 -0
  371. package/skills/agentvfx/templates/scope/xy-phosphor/main.frag +33 -0
  372. package/skills/agentvfx/templates/scope/xy-phosphor/pass0.frag +56 -0
  373. package/skills/agentvfx/templates/scope/xy-phosphor/pass1.frag +44 -0
  374. package/skills/agentvfx/templates/scope/xy-phosphor/pass2.frag +12 -0
  375. package/skills/agentvfx/templates/scope/xy-phosphor/pass3.frag +12 -0
  376. package/skills/agentvfx/templates/scope/xy-phosphor/pass4.frag +12 -0
  377. package/skills/agentvfx/templates/scope/xy-phosphor/pass5.frag +12 -0
  378. package/skills/agentvfx/templates/scope/xy-phosphor/pass6.frag +251 -0
  379. package/skills/agentvfx/templates/spectrum/_src/bars-linear/fx.src.json +61 -0
  380. package/skills/agentvfx/templates/spectrum/_src/bars-linear/main.src.frag +112 -0
  381. package/skills/agentvfx/templates/spectrum/_src/bars-linear/pass0.src.frag +2 -0
  382. package/skills/agentvfx/templates/spectrum/_src/bars-mirror/fx.src.json +51 -0
  383. package/skills/agentvfx/templates/spectrum/_src/bars-mirror/main.src.frag +72 -0
  384. package/skills/agentvfx/templates/spectrum/_src/bars-mirror/pass0.src.frag +2 -0
  385. package/skills/agentvfx/templates/spectrum/_src/bass-circle/fx.src.json +56 -0
  386. package/skills/agentvfx/templates/spectrum/_src/bass-circle/main.src.frag +136 -0
  387. package/skills/agentvfx/templates/spectrum/_src/bass-circle/pass0.src.frag +2 -0
  388. package/skills/agentvfx/templates/spectrum/_src/build.mjs +68 -0
  389. package/skills/agentvfx/templates/spectrum/_src/label-bars/fx.src.json +54 -0
  390. package/skills/agentvfx/templates/spectrum/_src/label-bars/main.src.frag +102 -0
  391. package/skills/agentvfx/templates/spectrum/_src/label-bars/pass0.src.frag +2 -0
  392. package/skills/agentvfx/templates/spectrum/_src/shared/bars-fn.glsl +64 -0
  393. package/skills/agentvfx/templates/spectrum/_src/shared/bars-pass.glsl +22 -0
  394. package/skills/agentvfx/templates/spectrum/_src/shared/clock.glsl +7 -0
  395. package/skills/agentvfx/templates/spectrum/_src/shared/hud.glsl +50 -0
  396. package/skills/agentvfx/templates/spectrum/_src/shared/stage.glsl +74 -0
  397. package/skills/agentvfx/templates/spectrum/_src/spectrum-particles/fx.src.json +58 -0
  398. package/skills/agentvfx/templates/spectrum/_src/spectrum-particles/main.src.frag +119 -0
  399. package/skills/agentvfx/templates/spectrum/_src/spectrum-particles/pass0.src.frag +33 -0
  400. package/skills/agentvfx/templates/spectrum/_src/spectrum-radial/fx.src.json +56 -0
  401. package/skills/agentvfx/templates/spectrum/_src/spectrum-radial/main.src.frag +95 -0
  402. package/skills/agentvfx/templates/spectrum/_src/spectrum-radial/pass0.src.frag +2 -0
  403. package/skills/agentvfx/templates/spectrum/_src/waveform-line/fx.src.json +60 -0
  404. package/skills/agentvfx/templates/spectrum/_src/waveform-line/main.src.frag +98 -0
  405. package/skills/agentvfx/templates/spectrum/_src/waveform-line/pass0.src.frag +43 -0
  406. package/skills/agentvfx/templates/spectrum/bars-linear/demo-graph-acid.json +1 -0
  407. package/skills/agentvfx/templates/spectrum/bars-linear/demo-graph-black.json +1 -0
  408. package/skills/agentvfx/templates/spectrum/bars-linear/fx.json +632 -0
  409. package/skills/agentvfx/templates/spectrum/bars-linear/main.frag +233 -0
  410. package/skills/agentvfx/templates/spectrum/bars-linear/pass0.frag +91 -0
  411. package/skills/agentvfx/templates/spectrum/bars-mirror/demo-field-acid.json +1 -0
  412. package/skills/agentvfx/templates/spectrum/bars-mirror/demo-field-ultramarine.json +1 -0
  413. package/skills/agentvfx/templates/spectrum/bars-mirror/fx.json +539 -0
  414. package/skills/agentvfx/templates/spectrum/bars-mirror/main.frag +193 -0
  415. package/skills/agentvfx/templates/spectrum/bars-mirror/pass0.frag +91 -0
  416. package/skills/agentvfx/templates/spectrum/bass-circle/demo-bracket-black.json +1 -0
  417. package/skills/agentvfx/templates/spectrum/bass-circle/demo-bracket-ultramarine.json +1 -0
  418. package/skills/agentvfx/templates/spectrum/bass-circle/fx.json +552 -0
  419. package/skills/agentvfx/templates/spectrum/bass-circle/main.frag +258 -0
  420. package/skills/agentvfx/templates/spectrum/bass-circle/pass0.frag +91 -0
  421. package/skills/agentvfx/templates/spectrum/label-bars/demo-deck-black.json +1 -0
  422. package/skills/agentvfx/templates/spectrum/label-bars/demo-deck-ultramarine.json +1 -0
  423. package/skills/agentvfx/templates/spectrum/label-bars/fx.json +552 -0
  424. package/skills/agentvfx/templates/spectrum/label-bars/main.frag +223 -0
  425. package/skills/agentvfx/templates/spectrum/label-bars/pass0.frag +91 -0
  426. package/skills/agentvfx/templates/spectrum/spectrum-particles/demo-cloud-black.json +1 -0
  427. package/skills/agentvfx/templates/spectrum/spectrum-particles/demo-cloud-ultramarine.json +1 -0
  428. package/skills/agentvfx/templates/spectrum/spectrum-particles/fx.json +584 -0
  429. package/skills/agentvfx/templates/spectrum/spectrum-particles/main.frag +240 -0
  430. package/skills/agentvfx/templates/spectrum/spectrum-particles/pass0.frag +101 -0
  431. package/skills/agentvfx/templates/spectrum/spectrum-radial/demo-starburst-black.json +1 -0
  432. package/skills/agentvfx/templates/spectrum/spectrum-radial/demo-starburst-ultramarine.json +1 -0
  433. package/skills/agentvfx/templates/spectrum/spectrum-radial/fx.json +566 -0
  434. package/skills/agentvfx/templates/spectrum/spectrum-radial/main.frag +217 -0
  435. package/skills/agentvfx/templates/spectrum/spectrum-radial/pass0.frag +91 -0
  436. package/skills/agentvfx/templates/spectrum/waveform-line/demo-strip-acid.json +1 -0
  437. package/skills/agentvfx/templates/spectrum/waveform-line/demo-strip-black.json +1 -0
  438. package/skills/agentvfx/templates/spectrum/waveform-line/fx.json +481 -0
  439. package/skills/agentvfx/templates/spectrum/waveform-line/main.frag +219 -0
  440. package/skills/agentvfx/templates/spectrum/waveform-line/pass0.frag +48 -0
  441. package/skills/agentvfx/templates/spectrum/waveform-line/pass1.frag +86 -0
  442. package/skills/agentvfx/test/_render_light.mjs +38 -0
  443. package/skills/agentvfx/test/audio-stereo.test.mjs +45 -0
  444. package/skills/agentvfx/test/audio.test.mjs +257 -0
  445. package/skills/agentvfx/test/brutal.test.mjs +190 -0
  446. package/skills/agentvfx/test/concept-collage-layout.test.mjs +236 -0
  447. package/skills/agentvfx/test/concept-collage-scenes.test.mjs +305 -0
  448. package/skills/agentvfx/test/concept-collage-typefx.test.mjs +153 -0
  449. package/skills/agentvfx/test/concept-collage.test.mjs +84 -0
  450. package/skills/agentvfx/test/concept-lattice.test.mjs +133 -0
  451. package/skills/agentvfx/test/concept-signal-cut.test.mjs +172 -0
  452. package/skills/agentvfx/test/credit-overlay.test.mjs +58 -0
  453. package/skills/agentvfx/test/credit.test.mjs +42 -0
  454. package/skills/agentvfx/test/doctor.test.mjs +31 -0
  455. package/skills/agentvfx/test/encode.test.mjs +22 -0
  456. package/skills/agentvfx/test/fixtures/r7-collage-LAYOUTS.json +5780 -0
  457. package/skills/agentvfx/test/fixtures/score-bad-duration.json +7 -0
  458. package/skills/agentvfx/test/fixtures/score-bad-safety.json +8 -0
  459. package/skills/agentvfx/test/fixtures/score-identity-footage.json +8 -0
  460. package/skills/agentvfx/test/fixtures/score-missing-template.json +7 -0
  461. package/skills/agentvfx/test/fixtures/score-ok.json +8 -0
  462. package/skills/agentvfx/test/fixtures/score-strobe-12hz.json +8 -0
  463. package/skills/agentvfx/test/fixtures/score-text-probe.json +1 -0
  464. package/skills/agentvfx/test/flash-audit.test.mjs +150 -0
  465. package/skills/agentvfx/test/flash.test.mjs +83 -0
  466. package/skills/agentvfx/test/footage.test.mjs +88 -0
  467. package/skills/agentvfx/test/glitch.test.mjs +272 -0
  468. package/skills/agentvfx/test/impact.test.mjs +231 -0
  469. package/skills/agentvfx/test/imports.test.mjs +39 -0
  470. package/skills/agentvfx/test/light.test.mjs +200 -0
  471. package/skills/agentvfx/test/loop.test.mjs +80 -0
  472. package/skills/agentvfx/test/psychedelic.test.mjs +99 -0
  473. package/skills/agentvfx/test/render.test.mjs +73 -0
  474. package/skills/agentvfx/test/sacred.test.mjs +175 -0
  475. package/skills/agentvfx/test/schema.test.mjs +103 -0
  476. package/skills/agentvfx/test/scope.test.mjs +306 -0
  477. package/skills/agentvfx/test/spectrum.test.mjs +165 -0
  478. package/skills/agentvfx/test/text.test.mjs +142 -0
  479. package/skills/agentvfx/test/web.test.mjs +397 -0
  480. package/skills/agentvfx/test-media/SOURCES.md +39 -0
  481. package/skills/agentvfx/test-media/beat-132.mp3 +0 -0
  482. package/skills/agentvfx/test-media/clip-01.mp4 +0 -0
  483. package/skills/agentvfx/test-media/clip-02.mp4 +0 -0
@@ -0,0 +1,397 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { loadLibrary } from "../engine/core/templates.mjs";
9
+ import { validateScore } from "../engine/core/validate.mjs";
10
+ import { APP_ORIGIN, ChromeSession, findChrome, stageFlags } from "../engine/node/chrome.mjs";
11
+ import { decodePng } from "../engine/node/png.mjs";
12
+ import { renderScore } from "../engine/node/render.mjs";
13
+
14
+ const root = fileURLToPath(new URL("../", import.meta.url));
15
+ const exportScript = path.join(root, "scripts/export-web.mjs");
16
+ const URL_RE = /https?:\/\//iu;
17
+
18
+ function runExport(args) {
19
+ return spawnSync(process.execPath, [exportScript, ...args], { encoding: "utf8" });
20
+ }
21
+
22
+ function assertNoExternalUrl(dir) {
23
+ for (const name of ["agentvfx.js", "templates.js", "demo.html"]) {
24
+ const text = readFileSync(path.join(dir, name), "utf8");
25
+ assert.equal(URL_RE.test(text), false, `${name} contains an external URL`);
26
+ }
27
+ }
28
+
29
+ function meanAbs(a, b) {
30
+ const width = a.width ?? b.width;
31
+ const height = a.height ?? b.height;
32
+ const ap = a.pixels ?? a;
33
+ const bp = b.pixels ?? b;
34
+ const ac = a.channels ?? 4;
35
+ const bc = b.channels ?? 4;
36
+ let sum = 0;
37
+ let count = 0;
38
+ for (let y = 0; y < height; y++) {
39
+ for (let x = 0; x < width; x++) {
40
+ const ia = (y * width + x) * ac;
41
+ const ib = (y * width + x) * bc;
42
+ sum += Math.abs(ap[ia] - bp[ib]) + Math.abs(ap[ia + 1] - bp[ib + 1]) + Math.abs(ap[ia + 2] - bp[ib + 2]);
43
+ count += 3;
44
+ }
45
+ }
46
+ return sum / count;
47
+ }
48
+
49
+ function meanRgb(image) {
50
+ const pixels = image.pixels ?? image;
51
+ const channels = image.channels ?? 4;
52
+ const n = image.width && image.height ? image.width * image.height : pixels.length / channels;
53
+ let sum = 0;
54
+ for (let i = 0; i < n; i++) {
55
+ const o = i * channels;
56
+ sum += pixels[o] + pixels[o + 1] + pixels[o + 2];
57
+ }
58
+ return sum / (n * 3);
59
+ }
60
+
61
+ async function shot(session, width, height) {
62
+ const png = await session.send("Page.captureScreenshot", {
63
+ format: "png",
64
+ fromSurface: true,
65
+ captureBeyondViewport: false,
66
+ clip: { x: 0, y: 0, width, height, scale: 1 },
67
+ });
68
+ const image = decodePng(Buffer.from(png.data, "base64"));
69
+ assert.equal(image.width, width, `screenshot width ${image.width}`);
70
+ assert.equal(image.height, height, `screenshot height ${image.height}`);
71
+ return image;
72
+ }
73
+
74
+ async function withChrome(dir, { width, height, reduced = false }, fn) {
75
+ const chrome = findChrome();
76
+ assert.ok(chrome, "Chrome/Chromium not found");
77
+ const profileDir = mkdtempSync(path.join(tmpdir(), "agentvfx-web-"));
78
+ const routes = (pathname) => {
79
+ const rel = decodeURIComponent(pathname).replace(/^\/+/u, "");
80
+ if (!rel || rel.includes("..")) return null;
81
+ const file = path.join(dir, rel);
82
+ if (!existsSync(file) || !statSync(file).isFile()) return null;
83
+ return file;
84
+ };
85
+ const session = await ChromeSession.launch({
86
+ chrome,
87
+ flags: stageFlags({ width, height, rung: "gpu" }),
88
+ profileDir,
89
+ routes,
90
+ });
91
+ try {
92
+ await session.openPage();
93
+ await session.send("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor: 1, mobile: false });
94
+ if (reduced) {
95
+ await session.send("Emulation.setEmulatedMedia", { features: [{ name: "prefers-reduced-motion", value: "reduce" }] });
96
+ }
97
+ return await fn(session);
98
+ } finally {
99
+ await session.close();
100
+ }
101
+ }
102
+
103
+ async function open(session, page, readyExpr) {
104
+ await session.navigate(`${APP_ORIGIN}/${page}`);
105
+ const deadline = Date.now() + 20000;
106
+ for (;;) {
107
+ const ready = await session.evaluate(readyExpr).catch(() => false);
108
+ if (ready) return;
109
+ if (Date.now() > deadline) {
110
+ const logs = session.logs.join("\n");
111
+ throw new Error(`page did not become ready: ${page}\n${logs}`);
112
+ }
113
+ await new Promise((r) => setTimeout(r, 50));
114
+ }
115
+ }
116
+
117
+ const PROBE = `<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;background:#fff}canvas{display:block;width:320px;height:180px}</style></head>
118
+ <body><canvas id="c"></canvas><canvas id="bad"></canvas>
119
+ <script type="module">
120
+ import { AgentVfx } from "./agentvfx.js";
121
+ import templates from "./templates.js";
122
+ window.__fx = await AgentVfx.mount(document.getElementById("c"), { templates, template: "test-gradient", params: { speed: 0 }, motion: "always", dpr: 1 });
123
+ const broken = { ...templates, broken: { fx: { DESCRIPTION: "broken", INPUTS: [], X_AGENTVFX: { id: "broken", family: "_test", input: "generative", spdx: "MIT", flash: "none", presets: {} } }, passes: [{ target: null, persistent: false, src: "void main() { this is not glsl }" }] } };
124
+ try { await AgentVfx.mount(document.getElementById("bad"), { templates: broken, template: "broken", motion: "always", dpr: 1 }); window.__badThrew = false; } catch (e) { window.__badThrew = true; }
125
+ window.__litReady = true;
126
+ </script></body></html>`;
127
+
128
+ test("trigger() hits a still hero, a broken shader stays contained, and the context recovers after loss", async () => {
129
+ const dir = mkdtempSync(path.join(tmpdir(), "agentvfx-probe-"));
130
+ try {
131
+ const exported = runExport(["test-gradient", "test-strobe", "--out", dir]);
132
+ assert.equal(exported.status, 0, exported.stderr || exported.stdout);
133
+ writeFileSync(path.join(dir, "probe.html"), PROBE);
134
+ await withChrome(dir, { width: 320, height: 360 }, async (session) => {
135
+ await open(session, "probe.html", "window.__litReady === true");
136
+ const a = await shot(session, 320, 180);
137
+ await new Promise((r) => setTimeout(r, 300));
138
+ const b = await shot(session, 320, 180);
139
+ assert.ok(meanAbs(a, b) < 1, "the still hero (speed 0) should not change");
140
+ await session.evaluate("window.__fx.trigger('test-strobe', { preset: 'hold', durationMs: 600 })");
141
+ await new Promise((r) => setTimeout(r, 120));
142
+ const c = await shot(session, 320, 180);
143
+ assert.ok(meanAbs(b, c) > 20, `trigger() did not change the next capture (${meanAbs(b, c).toFixed(2)})`);
144
+ assert.equal(await session.evaluate("window.__badThrew"), false, "a compile error must not throw into the page");
145
+ assert.ok(session.logs.some((l) => /broken/u.test(l)), `expected a console error naming the broken template\n${session.logs.join("\n")}`);
146
+ await new Promise((r) => setTimeout(r, 700));
147
+ const before = await shot(session, 320, 180);
148
+ await session.evaluate("(() => { const gl = document.getElementById('c').getContext('webgl2'); window.__lose = gl.getExtension('WEBGL_lose_context'); window.__lose.loseContext(); return true; })()");
149
+ await new Promise((r) => setTimeout(r, 200));
150
+ await session.evaluate("window.__lose.restoreContext(), true");
151
+ await new Promise((r) => setTimeout(r, 600));
152
+ const after = await shot(session, 320, 180);
153
+ assert.ok(meanRgb(after) > 8, "canvas is blank after context restore");
154
+ assert.ok(meanAbs(before, after) < 3, `restored frame differs (${meanAbs(before, after).toFixed(2)})`);
155
+ });
156
+ } finally {
157
+ rmSync(dir, { recursive: true, force: true });
158
+ }
159
+ });
160
+
161
+ const AUDIO_PROBE = `<!doctype html><html><head><meta charset="utf-8"></head>
162
+ <body><canvas id="c" style="width:160px;height:90px"></canvas>
163
+ <script type="module">
164
+ import { AgentVfx } from "./agentvfx.js";
165
+ import templates from "./templates.js";
166
+ const Base = window.AudioContext;
167
+ window.__ctxs = [];
168
+ window.AudioContext = class extends Base { constructor(...a) { super(...a); window.__ctxs.push(this); } };
169
+ const fx = await AgentVfx.mount(document.getElementById("c"), { templates, template: "test-gradient", motion: "always", dpr: 1 });
170
+ fx.audio(new Audio());
171
+ fx.destroy();
172
+ window.__litReady = true;
173
+ </script></body></html>`;
174
+
175
+ test("destroy() closes the AudioContext that audio() opened", async () => {
176
+ const dir = mkdtempSync(path.join(tmpdir(), "agentvfx-audio-"));
177
+ try {
178
+ const exported = runExport(["test-gradient", "--out", dir]);
179
+ assert.equal(exported.status, 0, exported.stderr || exported.stdout);
180
+ writeFileSync(path.join(dir, "audio.html"), AUDIO_PROBE);
181
+ await withChrome(dir, { width: 320, height: 180 }, async (session) => {
182
+ await open(session, "audio.html", "window.__litReady === true");
183
+ await new Promise((r) => setTimeout(r, 200));
184
+ assert.equal(await session.evaluate("window.__ctxs.length"), 1);
185
+ assert.equal(await session.evaluate("window.__ctxs[0].state"), "closed");
186
+ });
187
+ } finally {
188
+ rmSync(dir, { recursive: true, force: true });
189
+ }
190
+ });
191
+
192
+ const GUARD_PROBE = `<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;background:#fff}canvas{display:block;width:320px;height:180px}</style></head>
193
+ <body><canvas id="c"></canvas>
194
+ <script type="module">
195
+ import { AgentVfx } from "./agentvfx.js";
196
+ import templates from "./templates.js";
197
+ const q = new URLSearchParams(location.search);
198
+ window.__trips = [];
199
+ window.__fx = await AgentVfx.mount(document.getElementById("c"), {
200
+ templates, template: q.get("t"), params: JSON.parse(q.get("p") || "{}"), motion: "always", dpr: 1,
201
+ onFlashGuard: (info) => window.__trips.push(info),
202
+ });
203
+ window.__litReady = true;
204
+ </script></body></html>`;
205
+
206
+ async function guardRun(dir, query, ms) {
207
+ return withChrome(dir, { width: 320, height: 180 }, async (session) => {
208
+ await open(session, `guard.html?${query}`, "window.__litReady === true");
209
+ await new Promise((r) => setTimeout(r, ms));
210
+ const trips = await session.evaluate("JSON.stringify(window.__trips)").then(JSON.parse);
211
+ const a = await shot(session, 320, 180);
212
+ await new Promise((r) => setTimeout(r, 400));
213
+ const b = await shot(session, 320, 180);
214
+ let resumed = null;
215
+ if (trips.length) {
216
+ await session.evaluate("window.__fx.resume(), true");
217
+ await new Promise((r) => setTimeout(r, 800));
218
+ resumed = await session.evaluate("window.__trips.length");
219
+ }
220
+ return { trips, still: meanAbs(a, b), resumed };
221
+ });
222
+ }
223
+
224
+ test("the live flash guard stops a strobe before a fourth flash in one second and shows no tripping frame", async () => {
225
+ const dir = mkdtempSync(path.join(tmpdir(), "agentvfx-guard-"));
226
+ try {
227
+ const exported = runExport(["test-strobe", "test-gradient", "--out", dir]);
228
+ assert.equal(exported.status, 0, exported.stderr || exported.stdout);
229
+ writeFileSync(path.join(dir, "guard.html"), GUARD_PROBE);
230
+ const strobe = await guardRun(dir, "t=test-strobe&p=%7B%22rate%22%3A12%7D", 1500);
231
+ assert.equal(strobe.trips.length, 1, "a 12 Hz strobe must trip the guard once");
232
+ assert.equal(strobe.trips[0].reason, "general");
233
+ assert.ok(strobe.trips[0].shownTransitions <= 6, `shown transitions ${strobe.trips[0].shownTransitions} must stay at three flashes or fewer`);
234
+ assert.ok(strobe.still < 0.5, "after the trip the canvas must hold still");
235
+ assert.equal(strobe.resumed, 2, "resume() must run the effect again under a re-armed guard, which stops it again");
236
+ const red = await guardRun(dir, "t=test-strobe&p=%7B%22rate%22%3A2%2C%22red%22%3Atrue%7D", 2500);
237
+ assert.equal(red.trips.length, 1, "a slow saturated-red flash must trip the guard");
238
+ assert.equal(red.trips[0].reason, "red");
239
+ assert.ok(red.trips[0].shownRedTransitions <= 1, "no complete red flash may be shown");
240
+ const calm = await guardRun(dir, "t=test-gradient", 1500);
241
+ assert.equal(calm.trips.length, 0, "a calm gradient must not trip the guard");
242
+ } finally {
243
+ rmSync(dir, { recursive: true, force: true });
244
+ }
245
+ });
246
+
247
+ test("export-web bundles templates whose shader credits carry links, keeping the credit without the scheme", () => {
248
+ const dir = mkdtempSync(path.join(tmpdir(), "agentvfx-export-"));
249
+ try {
250
+ const result = runExport(["boot-log", "halftone-field", "hud-deck", "vhs", "--out", dir]);
251
+ assert.equal(result.status, 0, result.stderr);
252
+ assertNoExternalUrl(dir);
253
+ const bundled = readFileSync(path.join(dir, "templates.js"), "utf8");
254
+ assert.match(bundled, /Inigo Quilez, iquilezles\.org\/articles\/distfunctions2d/u);
255
+ assert.match(bundled, /en\.wikipedia\.org\/wiki\/YIQ/u);
256
+ } finally {
257
+ rmSync(dir, { recursive: true, force: true });
258
+ }
259
+ });
260
+
261
+ test("export-web writes the LICENSE next to the bundle and keeps the licence notice in the runtime", () => {
262
+ const dir = mkdtempSync(path.join(tmpdir(), "agentvfx-export-"));
263
+ try {
264
+ const result = runExport(["test-gradient", "--out", dir]);
265
+ assert.equal(result.status, 0, result.stderr);
266
+ assertNoExternalUrl(dir);
267
+ const license = readFileSync(path.join(dir, "LICENSE"), "utf8");
268
+ assert.equal(license, readFileSync(path.join(root, "LICENSE"), "utf8"), "LICENSE is copied verbatim");
269
+ assert.match(license, /^Required Notice: Copyright \(c\) 2026 Woojin Go \(https:\/\/github\.com\/wjgoarxiv\/agentvfx\)$/mu);
270
+ const runtime = readFileSync(path.join(dir, "agentvfx.js"), "utf8");
271
+ assert.match(runtime, /^\/\/ agentvfx licence: the full terms are in the LICENSE file in this folder/mu);
272
+ assert.match(runtime, /^\/\/ Required Notice: Copyright \(c\) 2026 Woojin Go \(github\.com\/wjgoarxiv\/agentvfx\)$/mu);
273
+ assert.match(readFileSync(path.join(dir, "demo.html"), "utf8"), /Required Notice: Copyright \(c\) 2026 Woojin Go/u);
274
+ } finally {
275
+ rmSync(dir, { recursive: true, force: true });
276
+ }
277
+ });
278
+
279
+ test("export-web rejects an unknown template id", () => {
280
+ const dir = mkdtempSync(path.join(tmpdir(), "agentvfx-export-"));
281
+ try {
282
+ const result = runExport(["no-such-template", "--out", dir]);
283
+ assert.notEqual(result.status, 0);
284
+ assert.match(`${result.stderr}\n${result.stdout}`, /no-such-template/u);
285
+ } finally {
286
+ rmSync(dir, { recursive: true, force: true });
287
+ }
288
+ });
289
+
290
+ test("exported demo animates, freezes under reduced motion, and matches the offline frame", async () => {
291
+ const dir = mkdtempSync(path.join(tmpdir(), "agentvfx-export-"));
292
+ const offlineDir = mkdtempSync(path.join(tmpdir(), "agentvfx-offline-"));
293
+ try {
294
+ const exported = runExport(["test-gradient", "sacred", "test-strobe", "--out", dir]);
295
+ assert.equal(exported.status, 0, exported.stderr || exported.stdout);
296
+ for (const name of ["agentvfx.js", "templates.js", "demo.html"]) {
297
+ assert.equal(existsSync(path.join(dir, name)), true, name);
298
+ }
299
+ assertNoExternalUrl(dir);
300
+ const demo = readFileSync(path.join(dir, "demo.html"), "utf8");
301
+ assert.match(demo, /test-gradient/u);
302
+ assert.match(demo, /textSource/u);
303
+ assert.match(demo, /type="file"/u);
304
+ assert.match(demo, /trigger\(/u);
305
+
306
+ const W = 640;
307
+ const H = 360;
308
+ await withChrome(dir, { width: W, height: H }, async (session) => {
309
+ await open(session, "demo.html", "window.__litReady === true");
310
+ const a = await shot(session, W, H);
311
+ assert.ok(meanRgb(a) > 8, `demo canvas looks black (${meanRgb(a)})`);
312
+ await new Promise((r) => setTimeout(r, 500));
313
+ const b = await shot(session, W, H);
314
+ assert.ok(meanAbs(a, b) > 1, "demo did not change across 500 ms");
315
+ await session.evaluate("window.__hit()");
316
+ await new Promise((r) => setTimeout(r, 100));
317
+ const c = await shot(session, W, H);
318
+ assert.ok(meanAbs(b, c) > 1, "trigger() did not change the next capture");
319
+ const warned = session.logs.some((line) => /WCAG 2\.3\.1/u.test(line) && /test-strobe/u.test(line));
320
+ assert.equal(warned, true, `expected a WCAG 2.3.1 warning for test-strobe\n${session.logs.join("\n")}`);
321
+ });
322
+
323
+ await withChrome(dir, { width: W, height: H, reduced: true }, async (session) => {
324
+ await open(session, "demo.html", "window.__litReady === true");
325
+ const a = await shot(session, W, H);
326
+ await new Promise((r) => setTimeout(r, 500));
327
+ const b = await shot(session, W, H);
328
+ assert.equal(meanAbs(a, b), 0, "reduced motion captures differ");
329
+ await session.evaluate("window.__hit()");
330
+ await new Promise((r) => setTimeout(r, 100));
331
+ const c = await shot(session, W, H);
332
+ assert.equal(meanAbs(b, c), 0, "reduced motion did not suppress trigger()");
333
+ });
334
+
335
+ const lib = loadLibrary({ includeTest: true });
336
+ const checked = validateScore({
337
+ version: 1,
338
+ template: "test-gradient",
339
+ preset: "default",
340
+ palette: "rainbow",
341
+ durationSec: 3,
342
+ fps: 30,
343
+ seed: 1,
344
+ out: "parity",
345
+ }, lib);
346
+ assert.equal(checked.ok, true, JSON.stringify(checked.errors));
347
+ const frame = 15;
348
+ let offline = null;
349
+ await renderScore(checked.value, lib, {
350
+ outDir: offlineDir,
351
+ width: 256,
352
+ height: 144,
353
+ replay: false,
354
+ onFrame: (f, analysed) => {
355
+ if (f === frame) offline = analysed.rgba;
356
+ },
357
+ });
358
+ assert.ok(offline, "offline frame missing");
359
+
360
+ const harness = `<!DOCTYPE html>
361
+ <html><head><meta charset="utf-8"></head>
362
+ <body style="margin:0;background:#000">
363
+ <canvas id="c" width="256" height="144" style="width:256px;height:144px;display:block"></canvas>
364
+ <script type="module">
365
+ import { AgentVfx } from "./agentvfx.js";
366
+ import templates from "./templates.js";
367
+ const canvas = document.getElementById("c");
368
+ const fx = await AgentVfx.mount(canvas, {
369
+ templates, template: "test-gradient", preset: "default", palette: "rainbow",
370
+ seed: 1, dpr: 1, fps: 30, motion: "always"
371
+ });
372
+ window.__fx = fx;
373
+ window.__litReady = true;
374
+ </script>
375
+ </body></html>`;
376
+ writeFileSync(path.join(dir, "harness.html"), harness);
377
+ await withChrome(dir, { width: 256, height: 144 }, async (session) => {
378
+ await open(session, "harness.html", "window.__litReady === true");
379
+ await session.evaluate(`window.__fx.renderAt(${frame}/30)`);
380
+ const live = await shot(session, 256, 144);
381
+ const diff = meanAbs(live, { pixels: offline, channels: 4, width: 256, height: 144 });
382
+ assert.ok(diff <= 2, `live vs offline mean abs ${diff} > 2`);
383
+ });
384
+ } finally {
385
+ rmSync(dir, { recursive: true, force: true });
386
+ rmSync(offlineDir, { recursive: true, force: true });
387
+ }
388
+ });
389
+
390
+ test("live analyser reads the same level window as the render, so loud masters do not pin every bar", () => {
391
+ const runtime = readFileSync(path.join(root, "runtime/agentvfx.js"), "utf8");
392
+ // Render: dB of power vs a full-scale sine, over 70 dB (engine/node/audio.mjs). AnalyserNode dB sit about 7 dB
393
+ // higher on the same audio, so the live window is -63..+7 dB, unsmoothed (templates do their own attack/release).
394
+ assert.match(runtime, /analyser\.minDecibels = -63;/u);
395
+ assert.match(runtime, /analyser\.maxDecibels = 7;/u);
396
+ assert.match(runtime, /analyser\.smoothingTimeConstant = 0;/u);
397
+ });
@@ -0,0 +1,39 @@
1
+ # Test media sources
2
+
3
+ Access date for every file: 2026-11-01.
4
+
5
+ Licence text below is the visible Usage wording on the archive.org item page (the `rel="license"` link), not the API `licenseurl` alone.
6
+
7
+ `beat-132.mp3` is not sourced from anywhere: `scripts/make-beat.mjs` synthesises it (132 BPM, 10 s) and it ships under the same licence as the code (see LICENSE).
8
+
9
+ ## clip-01.mp4
10
+
11
+ - Page URL: https://archive.org/details/JuniorPr1946
12
+ - Direct file URL: https://archive.org/download/JuniorPr1946/JuniorPr1946.mp4
13
+ - Title: Junior Prom
14
+ - Author: Simmel-Meservey
15
+ - Licence: Public Domain
16
+ - Licence link on the page: http://creativecommons.org/licenses/publicdomain/ (link title "Public Domain")
17
+ - Trim window: 849.0 s–859.0 s (source time). Colour prom dance: faces, a light dress against dark suits, strong luminance change.
18
+ - ffmpeg command:
19
+
20
+ ```
21
+ ffmpeg -y -ss 849 -t 10.0 -i JuniorPr1946.mp4 -vf "fps=30,scale=1920:1440:flags=lanczos" -frames:v 300 -af "atrim=end=10,asetpts=PTS-STARTPTS" -c:v libx264 -pix_fmt yuv420p -r 30 -fps_mode cfr -c:a aac -b:a 160k -ar 48000 -ac 2 -movflags +faststart test-media/clip-01.mp4
22
+ ```
23
+
24
+ ## clip-02.mp4
25
+
26
+ - Page URL: https://archive.org/details/DuckandC1951
27
+ - Direct file URL: https://archive.org/download/DuckandC1951/DuckandC1951.mp4
28
+ - Title: Duck and Cover
29
+ - Author: Archer Productions, Inc.
30
+ - Licence: Public Domain
31
+ - Licence link on the page: http://creativecommons.org/licenses/publicdomain/ (link title "Public Domain")
32
+ - Trim window: 118.0 s–128.0 s (source time). Different subject from clip-01: a street, cars, and a dark figure against a bright road.
33
+ - ffmpeg command:
34
+
35
+ ```
36
+ ffmpeg -y -ss 118 -t 10.0 -i DuckandC1951.mp4 -vf "fps=30,scale=1472:1920:flags=lanczos" -frames:v 300 -af "atrim=end=10,asetpts=PTS-STARTPTS" -c:v libx264 -pix_fmt yuv420p -r 30 -fps_mode cfr -c:a aac -b:a 160k -ar 48000 -ac 2 -movflags +faststart test-media/clip-02.mp4
37
+ ```
38
+
39
+ The archive file is 368×480 with no rotate tag, so the long edge of 1920 is the height (1472×1920).