agentvfx 0.0.0-stage → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -0
- package/GEMINI.md +1 -0
- package/LICENSE +202 -0
- package/README.md +228 -2
- package/package.json +35 -4
- package/skills/agentvfx/LICENSE +202 -0
- package/skills/agentvfx/NOTICE +50 -0
- package/skills/agentvfx/SKILL.md +90 -0
- package/skills/agentvfx/engine/core/constants.mjs +84 -0
- package/skills/agentvfx/engine/core/flash-constants.mjs +23 -0
- package/skills/agentvfx/engine/core/flash.mjs +240 -0
- package/skills/agentvfx/engine/core/mark.mjs +25 -0
- package/skills/agentvfx/engine/core/palettes.mjs +36 -0
- package/skills/agentvfx/engine/core/schedule.mjs +85 -0
- package/skills/agentvfx/engine/core/templates.mjs +43 -0
- package/skills/agentvfx/engine/core/util.mjs +167 -0
- package/skills/agentvfx/engine/core/validate.mjs +334 -0
- package/skills/agentvfx/engine/node/analysis.mjs +172 -0
- package/skills/agentvfx/engine/node/audio.mjs +621 -0
- package/skills/agentvfx/engine/node/board.mjs +24 -0
- package/skills/agentvfx/engine/node/chrome.mjs +349 -0
- package/skills/agentvfx/engine/node/encode.mjs +110 -0
- package/skills/agentvfx/engine/node/eye.mjs +77 -0
- package/skills/agentvfx/engine/node/footage.mjs +129 -0
- package/skills/agentvfx/engine/node/frame-worker.mjs +39 -0
- package/skills/agentvfx/engine/node/host.mjs +31 -0
- package/skills/agentvfx/engine/node/job.mjs +181 -0
- package/skills/agentvfx/engine/node/png.mjs +112 -0
- package/skills/agentvfx/engine/node/render.mjs +161 -0
- package/skills/agentvfx/engine/node/stage-capture.mjs +187 -0
- package/skills/agentvfx/engine/node/stage-serve.mjs +72 -0
- package/skills/agentvfx/engine/node/stills.mjs +100 -0
- package/skills/agentvfx/engine/node/ws-server.mjs +132 -0
- package/skills/agentvfx/engine/web/gl.mjs +319 -0
- package/skills/agentvfx/engine/web/glsl-common.mjs +78 -0
- package/skills/agentvfx/engine/web/init.js +23 -0
- package/skills/agentvfx/engine/web/prelude.mjs +150 -0
- package/skills/agentvfx/engine/web/render.html +11 -0
- package/skills/agentvfx/engine/web/render.mjs +388 -0
- package/skills/agentvfx/engine/web/scale.mjs +15 -0
- package/skills/agentvfx/engine/web/text.mjs +158 -0
- package/skills/agentvfx/plates/terminalcore/README.md +61 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/crt.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/font.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/gallery.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/gallery_mats.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/gallery_room.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/materials.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/nodekit.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/room.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/shots.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/textures.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/__pycache__/wall.cpython-313.pyc +0 -0
- package/skills/agentvfx/plates/terminalcore/crt.py +143 -0
- package/skills/agentvfx/plates/terminalcore/font.py +80 -0
- package/skills/agentvfx/plates/terminalcore/gallery.py +61 -0
- package/skills/agentvfx/plates/terminalcore/gallery_mats.py +144 -0
- package/skills/agentvfx/plates/terminalcore/gallery_room.py +126 -0
- package/skills/agentvfx/plates/terminalcore/materials.py +170 -0
- package/skills/agentvfx/plates/terminalcore/nodekit.py +87 -0
- package/skills/agentvfx/plates/terminalcore/room.py +82 -0
- package/skills/agentvfx/plates/terminalcore/scene.py +219 -0
- package/skills/agentvfx/plates/terminalcore/shots.py +48 -0
- package/skills/agentvfx/plates/terminalcore/textures.py +79 -0
- package/skills/agentvfx/plates/terminalcore/wall.py +103 -0
- package/skills/agentvfx/references/plates.md +22 -0
- package/skills/agentvfx/references/template-format.md +149 -0
- package/skills/agentvfx/references/web.md +63 -0
- package/skills/agentvfx/runtime/agentvfx.js +708 -0
- package/skills/agentvfx/schemas/fx-score.schema.json +98 -0
- package/skills/agentvfx/scripts/export-web.mjs +157 -0
- package/skills/agentvfx/scripts/loop.mjs +129 -0
- package/skills/agentvfx/scripts/make-beat.mjs +133 -0
- package/skills/agentvfx/scripts/plate.mjs +82 -0
- package/skills/agentvfx/scripts/vfx.mjs +173 -0
- package/skills/agentvfx/templates/_test/feedback-counter/fx.json +11 -0
- package/skills/agentvfx/templates/_test/feedback-counter/main.frag +4 -0
- package/skills/agentvfx/templates/_test/feedback-counter/pass0.frag +4 -0
- package/skills/agentvfx/templates/_test/gradient/fx.json +23 -0
- package/skills/agentvfx/templates/_test/gradient/main.frag +5 -0
- package/skills/agentvfx/templates/_test/identity/fx.json +10 -0
- package/skills/agentvfx/templates/_test/identity/main.frag +4 -0
- package/skills/agentvfx/templates/_test/strobe/fx.json +14 -0
- package/skills/agentvfx/templates/_test/strobe/main.frag +6 -0
- package/skills/agentvfx/templates/_test/text-probe/fx.json +15 -0
- package/skills/agentvfx/templates/_test/text-probe/main.frag +6 -0
- package/skills/agentvfx/templates/brutal/NOTES.md +14 -0
- package/skills/agentvfx/templates/brutal/_src/boot-log/def.mjs +57 -0
- package/skills/agentvfx/templates/brutal/_src/boot-log/main.src.frag +129 -0
- package/skills/agentvfx/templates/brutal/_src/build.mjs +33 -0
- package/skills/agentvfx/templates/brutal/_src/halftone-field/def.mjs +72 -0
- package/skills/agentvfx/templates/brutal/_src/halftone-field/main.src.frag +94 -0
- package/skills/agentvfx/templates/brutal/_src/hud-deck/deck-left.src.glsl +100 -0
- package/skills/agentvfx/templates/brutal/_src/hud-deck/deck-right.src.glsl +61 -0
- package/skills/agentvfx/templates/brutal/_src/hud-deck/deck-tiles.src.glsl +153 -0
- package/skills/agentvfx/templates/brutal/_src/hud-deck/def.mjs +82 -0
- package/skills/agentvfx/templates/brutal/_src/hud-deck/main.src.frag +110 -0
- package/skills/agentvfx/templates/brutal/_src/hud-deck/pass0.src.frag +38 -0
- package/skills/agentvfx/templates/brutal/_src/shared/defs.mjs +28 -0
- package/skills/agentvfx/templates/brutal/_src/shared/icons.glsl +72 -0
- package/skills/agentvfx/templates/brutal/_src/shared/lib.glsl +115 -0
- package/skills/agentvfx/templates/brutal/_src/shared/screen.glsl +43 -0
- package/skills/agentvfx/templates/brutal/boot-log/demo-acid.json +1 -0
- package/skills/agentvfx/templates/brutal/boot-log/demo-phosphor.json +1 -0
- package/skills/agentvfx/templates/brutal/boot-log/demo-ultramarine.json +1 -0
- package/skills/agentvfx/templates/brutal/boot-log/fx.json +257 -0
- package/skills/agentvfx/templates/brutal/boot-log/main.frag +242 -0
- package/skills/agentvfx/templates/brutal/collage/_src/00-lib.glsl +73 -0
- package/skills/agentvfx/templates/brutal/collage/_src/10-spectrum-scope.glsl +73 -0
- package/skills/agentvfx/templates/brutal/collage/_src/20-meter-readout-cross.glsl +87 -0
- package/skills/agentvfx/templates/brutal/collage/_src/30-footage-fillers.glsl +92 -0
- package/skills/agentvfx/templates/brutal/collage/_src/40-editorial.glsl +232 -0
- package/skills/agentvfx/templates/brutal/collage/_src/50-typefx.glsl +163 -0
- package/skills/agentvfx/templates/brutal/collage/_src/60-scene-lib.glsl +104 -0
- package/skills/agentvfx/templates/brutal/collage/_src/61-scene-specimen.glsl +71 -0
- package/skills/agentvfx/templates/brutal/collage/_src/62-scene-wiring.glsl +96 -0
- package/skills/agentvfx/templates/brutal/collage/_src/63-scene-fieldcut.glsl +119 -0
- package/skills/agentvfx/templates/brutal/collage/_src/64-scene-pattern.glsl +64 -0
- package/skills/agentvfx/templates/brutal/collage/_src/641-scene-anatomy.glsl +49 -0
- package/skills/agentvfx/templates/brutal/collage/_src/642-scene-deck.glsl +78 -0
- package/skills/agentvfx/templates/brutal/collage/_src/643-scene-bootlog.glsl +60 -0
- package/skills/agentvfx/templates/brutal/collage/_src/644-scene-tunnel.glsl +48 -0
- package/skills/agentvfx/templates/brutal/collage/_src/645-scene-runner.glsl +42 -0
- package/skills/agentvfx/templates/brutal/collage/_src/646-scene-ascii.glsl +66 -0
- package/skills/agentvfx/templates/brutal/collage/_src/647-scene-cubewall.glsl +80 -0
- package/skills/agentvfx/templates/brutal/collage/_src/648-scene-vortex.glsl +46 -0
- package/skills/agentvfx/templates/brutal/collage/_src/649-scene-sticker.glsl +72 -0
- package/skills/agentvfx/templates/brutal/collage/_src/65-scene-main.glsl +1 -0
- package/skills/agentvfx/templates/brutal/collage/_src/650-scene-ribbon.glsl +55 -0
- package/skills/agentvfx/templates/brutal/collage/_src/651-scene-slab.glsl +38 -0
- package/skills/agentvfx/templates/brutal/collage/_src/652-scene-framedial.glsl +79 -0
- package/skills/agentvfx/templates/brutal/collage/_src/653-scene-tilt.glsl +40 -0
- package/skills/agentvfx/templates/brutal/collage/_src/654-scene-marquee.glsl +45 -0
- package/skills/agentvfx/templates/brutal/collage/_src/655-scene-lens.glsl +57 -0
- package/skills/agentvfx/templates/brutal/collage/_src/656-scene-liquid.glsl +35 -0
- package/skills/agentvfx/templates/brutal/collage/_src/657-scene-panelgrid.glsl +75 -0
- package/skills/agentvfx/templates/brutal/collage/_src/658-scene-mirror.glsl +49 -0
- package/skills/agentvfx/templates/brutal/collage/_src/66-scene-main.glsl +27 -0
- package/skills/agentvfx/templates/brutal/collage/_src/90-main.glsl +120 -0
- package/skills/agentvfx/templates/brutal/collage/_src/build.mjs +24 -0
- package/skills/agentvfx/templates/brutal/collage/_src/sync-presets.mjs +16 -0
- package/skills/agentvfx/templates/brutal/collage/demo-bone-lime.json +1 -0
- package/skills/agentvfx/templates/brutal/collage/demo-lime-sparse.json +1 -0
- package/skills/agentvfx/templates/brutal/collage/demo-orange-dense.json +1 -0
- package/skills/agentvfx/templates/brutal/collage/demo-ultra-slow.json +1 -0
- package/skills/agentvfx/templates/brutal/collage/fx.json +4882 -0
- package/skills/agentvfx/templates/brutal/collage/layout.mjs +97 -0
- package/skills/agentvfx/templates/brutal/collage/layouts.json +366 -0
- package/skills/agentvfx/templates/brutal/collage/main.frag +2367 -0
- package/skills/agentvfx/templates/brutal/collage/pass0.frag +265 -0
- package/skills/agentvfx/templates/brutal/halftone-field/demo-acid.json +1 -0
- package/skills/agentvfx/templates/brutal/halftone-field/demo-ascii.json +1 -0
- package/skills/agentvfx/templates/brutal/halftone-field/demo-dither.json +1 -0
- package/skills/agentvfx/templates/brutal/halftone-field/demo-orange.json +1 -0
- package/skills/agentvfx/templates/brutal/halftone-field/demo-phosphor.json +1 -0
- package/skills/agentvfx/templates/brutal/halftone-field/demo-ultramarine.json +1 -0
- package/skills/agentvfx/templates/brutal/halftone-field/fx.json +474 -0
- package/skills/agentvfx/templates/brutal/halftone-field/main.frag +248 -0
- package/skills/agentvfx/templates/brutal/hud-deck/demo-acid.json +1 -0
- package/skills/agentvfx/templates/brutal/hud-deck/demo-night.json +1 -0
- package/skills/agentvfx/templates/brutal/hud-deck/demo-octagon.json +1 -0
- package/skills/agentvfx/templates/brutal/hud-deck/demo-ultramarine.json +1 -0
- package/skills/agentvfx/templates/brutal/hud-deck/fx.json +601 -0
- package/skills/agentvfx/templates/brutal/hud-deck/main.frag +645 -0
- package/skills/agentvfx/templates/brutal/hud-deck/pass0.frag +38 -0
- package/skills/agentvfx/templates/brutal/lattice/demo-cobalt-fine.json +1 -0
- package/skills/agentvfx/templates/brutal/lattice/demo-ink-yellow.json +1 -0
- package/skills/agentvfx/templates/brutal/lattice/demo-vermilion.json +1 -0
- package/skills/agentvfx/templates/brutal/lattice/demo-violet-slow.json +1 -0
- package/skills/agentvfx/templates/brutal/lattice/fx.json +130 -0
- package/skills/agentvfx/templates/brutal/lattice/main.frag +175 -0
- package/skills/agentvfx/templates/brutal/lattice/pass0.frag +29 -0
- package/skills/agentvfx/templates/glitch/block-glitch/demo-mosaic.json +1 -0
- package/skills/agentvfx/templates/glitch/block-glitch/demo-onset.json +1 -0
- package/skills/agentvfx/templates/glitch/block-glitch/demo-static.json +1 -0
- package/skills/agentvfx/templates/glitch/block-glitch/demo-tear.json +1 -0
- package/skills/agentvfx/templates/glitch/block-glitch/fx.json +54 -0
- package/skills/agentvfx/templates/glitch/block-glitch/main.frag +132 -0
- package/skills/agentvfx/templates/glitch/block-glitch/pass0.frag +12 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/NOTES.md +13 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/build-passes.mjs +162 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/demo-chunk.json +1 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/demo-hue.json +1 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/demo-kick.json +1 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/demo-streak.json +1 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/demo-vertical.json +1 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/fx.json +60 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/main.frag +83 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/pass0.frag +64 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/pass1.frag +66 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/pass2.frag +66 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/pass3.frag +66 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/pass4.frag +66 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/pass5.frag +66 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/pass6.frag +66 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/pass7.frag +66 -0
- package/skills/agentvfx/templates/glitch/pixel-sort/pass8.frag +66 -0
- package/skills/agentvfx/templates/glitch/rgb-split/demo-diagonal-stutter.json +1 -0
- package/skills/agentvfx/templates/glitch/rgb-split/demo-hard.json +1 -0
- package/skills/agentvfx/templates/glitch/rgb-split/demo-kick.json +1 -0
- package/skills/agentvfx/templates/glitch/rgb-split/demo-radial.json +1 -0
- package/skills/agentvfx/templates/glitch/rgb-split/fx.json +46 -0
- package/skills/agentvfx/templates/glitch/rgb-split/main.frag +76 -0
- package/skills/agentvfx/templates/glitch/vhs/demo-clean.json +1 -0
- package/skills/agentvfx/templates/glitch/vhs/demo-crash.json +1 -0
- package/skills/agentvfx/templates/glitch/vhs/demo-tape.json +1 -0
- package/skills/agentvfx/templates/glitch/vhs/demo-worn.json +1 -0
- package/skills/agentvfx/templates/glitch/vhs/fx.json +46 -0
- package/skills/agentvfx/templates/glitch/vhs/main.frag +96 -0
- package/skills/agentvfx/templates/impact/impact-1bit/demo-onebit-bayer.json +1 -0
- package/skills/agentvfx/templates/impact/impact-1bit/fx.json +45 -0
- package/skills/agentvfx/templates/impact/impact-1bit/main.frag +64 -0
- package/skills/agentvfx/templates/impact/impact-invert/demo-invert-snap.json +1 -0
- package/skills/agentvfx/templates/impact/impact-invert/fx.json +37 -0
- package/skills/agentvfx/templates/impact/impact-invert/main.frag +44 -0
- package/skills/agentvfx/templates/impact/punch-shake/demo-punch-hard.json +1 -0
- package/skills/agentvfx/templates/impact/punch-shake/fx.json +36 -0
- package/skills/agentvfx/templates/impact/punch-shake/main.frag +62 -0
- package/skills/agentvfx/templates/impact/rgb-hit/demo-rgb-slam.json +1 -0
- package/skills/agentvfx/templates/impact/rgb-hit/fx.json +39 -0
- package/skills/agentvfx/templates/impact/rgb-hit/main.frag +62 -0
- package/skills/agentvfx/templates/impact/signal-cut/demo-lime-hold.json +1 -0
- package/skills/agentvfx/templates/impact/signal-cut/demo-lime.json +1 -0
- package/skills/agentvfx/templates/impact/signal-cut/demo-orange.json +1 -0
- package/skills/agentvfx/templates/impact/signal-cut/demo-ultramarine.json +1 -0
- package/skills/agentvfx/templates/impact/signal-cut/fx.json +83 -0
- package/skills/agentvfx/templates/impact/signal-cut/main.frag +101 -0
- package/skills/agentvfx/templates/impact/signal-cut/pass0.frag +68 -0
- package/skills/agentvfx/templates/impact/speedline-burst/demo-speed-white.json +1 -0
- package/skills/agentvfx/templates/impact/speedline-burst/fx.json +44 -0
- package/skills/agentvfx/templates/impact/speedline-burst/main.frag +68 -0
- package/skills/agentvfx/templates/impact/strobe-burst/demo-strobe-white-16.json +1 -0
- package/skills/agentvfx/templates/impact/strobe-burst/fx.json +42 -0
- package/skills/agentvfx/templates/impact/strobe-burst/main.frag +43 -0
- package/skills/agentvfx/templates/light/anamorphic-streak/demo-anamorphic-cinema.json +10 -0
- package/skills/agentvfx/templates/light/anamorphic-streak/demo-data.json +10 -0
- package/skills/agentvfx/templates/light/anamorphic-streak/fx.json +47 -0
- package/skills/agentvfx/templates/light/anamorphic-streak/main.frag +45 -0
- package/skills/agentvfx/templates/light/anamorphic-streak/pass0.frag +22 -0
- package/skills/agentvfx/templates/light/anamorphic-streak/pass1.frag +16 -0
- package/skills/agentvfx/templates/light/anamorphic-streak/pass2.frag +16 -0
- package/skills/agentvfx/templates/light/anamorphic-streak/pass3.frag +16 -0
- package/skills/agentvfx/templates/light/bloom-glow/demo-clip01-low.json +11 -0
- package/skills/agentvfx/templates/light/bloom-glow/demo-neon-night.json +10 -0
- package/skills/agentvfx/templates/light/bloom-glow/demo-phosphor.json +11 -0
- package/skills/agentvfx/templates/light/bloom-glow/fx.json +53 -0
- package/skills/agentvfx/templates/light/bloom-glow/main.frag +85 -0
- package/skills/agentvfx/templates/light/bloom-glow/pass0.frag +21 -0
- package/skills/agentvfx/templates/light/bloom-glow/pass1.frag +50 -0
- package/skills/agentvfx/templates/light/bloom-glow/pass2.frag +44 -0
- package/skills/agentvfx/templates/light/bloom-glow/pass3.frag +32 -0
- package/skills/agentvfx/templates/light/bloom-glow/pass4.frag +48 -0
- package/skills/agentvfx/templates/light/bloom-glow/pass5.frag +46 -0
- package/skills/agentvfx/templates/light/god-rays/demo-golden-hour.json +10 -0
- package/skills/agentvfx/templates/light/god-rays/demo-point-lamp.json +10 -0
- package/skills/agentvfx/templates/light/god-rays/fx.json +39 -0
- package/skills/agentvfx/templates/light/god-rays/main.frag +54 -0
- package/skills/agentvfx/templates/light/lens-flare/demo-clip01-bar.json +11 -0
- package/skills/agentvfx/templates/light/lens-flare/demo-eye-bar.json +10 -0
- package/skills/agentvfx/templates/light/lens-flare/demo-sun-ghosts.json +10 -0
- package/skills/agentvfx/templates/light/lens-flare/fx.json +49 -0
- package/skills/agentvfx/templates/light/lens-flare/main.frag +86 -0
- package/skills/agentvfx/templates/light/lens-flare/pass0.frag +24 -0
- package/skills/agentvfx/templates/light/light-leak/demo-slit.json +10 -0
- package/skills/agentvfx/templates/light/light-leak/demo-vintage-burn.json +10 -0
- package/skills/agentvfx/templates/light/light-leak/fx.json +40 -0
- package/skills/agentvfx/templates/light/light-leak/main.frag +46 -0
- package/skills/agentvfx/templates/light/light-stage/fx.json +29 -0
- package/skills/agentvfx/templates/light/light-stage/main.frag +56 -0
- package/skills/agentvfx/templates/light/light-sweep/demo-hard-bar.json +12 -0
- package/skills/agentvfx/templates/light/light-sweep/demo-logo-sweep.json +10 -0
- package/skills/agentvfx/templates/light/light-sweep/demo-onset.json +11 -0
- package/skills/agentvfx/templates/light/light-sweep/fx.json +45 -0
- package/skills/agentvfx/templates/light/light-sweep/main.frag +29 -0
- package/skills/agentvfx/templates/light/light-sweep/pass0.frag +17 -0
- package/skills/agentvfx/templates/light/light-sweep/pass1.frag +15 -0
- package/skills/agentvfx/templates/light/neon-saber/demo-hairline.json +10 -0
- package/skills/agentvfx/templates/light/neon-saber/demo-saber-laser.json +10 -0
- package/skills/agentvfx/templates/light/neon-saber/fx.json +48 -0
- package/skills/agentvfx/templates/light/neon-saber/main.frag +27 -0
- package/skills/agentvfx/templates/light/neon-saber/pass0.frag +46 -0
- package/skills/agentvfx/templates/light/neon-saber/pass1.frag +15 -0
- package/skills/agentvfx/templates/psychedelic/echo-flow/demo-trail.json +1 -0
- package/skills/agentvfx/templates/psychedelic/echo-flow/fx.json +38 -0
- package/skills/agentvfx/templates/psychedelic/echo-flow/main.frag +19 -0
- package/skills/agentvfx/templates/psychedelic/echo-flow/pass0.frag +16 -0
- package/skills/agentvfx/templates/psychedelic/echo-flow/pass1.frag +20 -0
- package/skills/agentvfx/templates/psychedelic/echo-flow/pass2.frag +11 -0
- package/skills/agentvfx/templates/psychedelic/echo-flow/pass3.frag +4 -0
- package/skills/agentvfx/templates/psychedelic/hologram-scan/demo-cyan.json +1 -0
- package/skills/agentvfx/templates/psychedelic/hologram-scan/fx.json +36 -0
- package/skills/agentvfx/templates/psychedelic/hologram-scan/main.frag +30 -0
- package/skills/agentvfx/templates/psychedelic/kaleido-fold/demo-six.json +1 -0
- package/skills/agentvfx/templates/psychedelic/kaleido-fold/fx.json +37 -0
- package/skills/agentvfx/templates/psychedelic/kaleido-fold/main.frag +10 -0
- package/skills/agentvfx/templates/psychedelic/kaleido-fold/pass0.frag +34 -0
- package/skills/agentvfx/templates/psychedelic/mosh-melt/demo-classic.json +1 -0
- package/skills/agentvfx/templates/psychedelic/mosh-melt/fx.json +38 -0
- package/skills/agentvfx/templates/psychedelic/mosh-melt/main.frag +20 -0
- package/skills/agentvfx/templates/psychedelic/mosh-melt/pass0.frag +22 -0
- package/skills/agentvfx/templates/psychedelic/sacred/NOTES.md +13 -0
- package/skills/agentvfx/templates/psychedelic/sacred/demo-classic.json +1 -0
- package/skills/agentvfx/templates/psychedelic/sacred/demo-halo.json +1 -0
- package/skills/agentvfx/templates/psychedelic/sacred/demo-slow-tide.json +1 -0
- package/skills/agentvfx/templates/psychedelic/sacred/demo-thermal.json +1 -0
- package/skills/agentvfx/templates/psychedelic/sacred/fx.json +263 -0
- package/skills/agentvfx/templates/psychedelic/sacred/main.frag +109 -0
- package/skills/agentvfx/templates/psychedelic/sacred/pass0.frag +25 -0
- package/skills/agentvfx/templates/psychedelic/sacred/pass1.frag +23 -0
- package/skills/agentvfx/templates/psychedelic/tidal-bands/demo-classic.json +1 -0
- package/skills/agentvfx/templates/psychedelic/tidal-bands/fx.json +37 -0
- package/skills/agentvfx/templates/psychedelic/tidal-bands/main.frag +54 -0
- package/skills/agentvfx/templates/scope/_chrome.glsl +242 -0
- package/skills/agentvfx/templates/scope/goniometer/demo-boot-green.json +1 -0
- package/skills/agentvfx/templates/scope/goniometer/demo-field-lime.json +1 -0
- package/skills/agentvfx/templates/scope/goniometer/demo-field-ultramarine.json +1 -0
- package/skills/agentvfx/templates/scope/goniometer/demo-glow-phosphor.json +1 -0
- package/skills/agentvfx/templates/scope/goniometer/demo-hud-black.json +1 -0
- package/skills/agentvfx/templates/scope/goniometer/fx.json +635 -0
- package/skills/agentvfx/templates/scope/goniometer/main.frag +33 -0
- package/skills/agentvfx/templates/scope/goniometer/pass0.frag +58 -0
- package/skills/agentvfx/templates/scope/goniometer/pass1.frag +43 -0
- package/skills/agentvfx/templates/scope/goniometer/pass2.frag +12 -0
- package/skills/agentvfx/templates/scope/goniometer/pass3.frag +12 -0
- package/skills/agentvfx/templates/scope/goniometer/pass4.frag +12 -0
- package/skills/agentvfx/templates/scope/goniometer/pass5.frag +12 -0
- package/skills/agentvfx/templates/scope/goniometer/pass6.frag +251 -0
- package/skills/agentvfx/templates/scope/scope-polar/demo-boot-green.json +1 -0
- package/skills/agentvfx/templates/scope/scope-polar/demo-field-lime.json +1 -0
- package/skills/agentvfx/templates/scope/scope-polar/demo-field-ultramarine.json +1 -0
- package/skills/agentvfx/templates/scope/scope-polar/demo-glow-phosphor.json +1 -0
- package/skills/agentvfx/templates/scope/scope-polar/demo-hud-black.json +1 -0
- package/skills/agentvfx/templates/scope/scope-polar/fx.json +686 -0
- package/skills/agentvfx/templates/scope/scope-polar/main.frag +39 -0
- package/skills/agentvfx/templates/scope/scope-polar/pass0.frag +84 -0
- package/skills/agentvfx/templates/scope/scope-polar/pass1.frag +49 -0
- package/skills/agentvfx/templates/scope/scope-polar/pass2.frag +12 -0
- package/skills/agentvfx/templates/scope/scope-polar/pass3.frag +12 -0
- package/skills/agentvfx/templates/scope/scope-polar/pass4.frag +12 -0
- package/skills/agentvfx/templates/scope/scope-polar/pass5.frag +12 -0
- package/skills/agentvfx/templates/scope/scope-polar/pass6.frag +251 -0
- package/skills/agentvfx/templates/scope/scope-triggered/demo-boot-green.json +1 -0
- package/skills/agentvfx/templates/scope/scope-triggered/demo-field-lime.json +1 -0
- package/skills/agentvfx/templates/scope/scope-triggered/demo-field-ultramarine.json +1 -0
- package/skills/agentvfx/templates/scope/scope-triggered/demo-glow-phosphor.json +1 -0
- package/skills/agentvfx/templates/scope/scope-triggered/demo-hud-black.json +1 -0
- package/skills/agentvfx/templates/scope/scope-triggered/fx.json +712 -0
- package/skills/agentvfx/templates/scope/scope-triggered/main.frag +36 -0
- package/skills/agentvfx/templates/scope/scope-triggered/pass0.frag +78 -0
- package/skills/agentvfx/templates/scope/scope-triggered/pass1.frag +50 -0
- package/skills/agentvfx/templates/scope/scope-triggered/pass2.frag +12 -0
- package/skills/agentvfx/templates/scope/scope-triggered/pass3.frag +12 -0
- package/skills/agentvfx/templates/scope/scope-triggered/pass4.frag +12 -0
- package/skills/agentvfx/templates/scope/scope-triggered/pass5.frag +12 -0
- package/skills/agentvfx/templates/scope/scope-triggered/pass6.frag +251 -0
- package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-boot-green.json +1 -0
- package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-field-lime.json +1 -0
- package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-field-ultramarine.json +1 -0
- package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-glow-phosphor.json +1 -0
- package/skills/agentvfx/templates/scope/spectrogram-scroll/demo-hud-black.json +1 -0
- package/skills/agentvfx/templates/scope/spectrogram-scroll/fx.json +595 -0
- package/skills/agentvfx/templates/scope/spectrogram-scroll/main.frag +83 -0
- package/skills/agentvfx/templates/scope/spectrogram-scroll/pass0.frag +52 -0
- package/skills/agentvfx/templates/scope/spectrogram-scroll/pass1.frag +251 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/demo-boot-green.json +1 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/demo-field-lime.json +1 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/demo-field-ultramarine.json +1 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/demo-glow-phosphor.json +1 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/demo-hud-black.json +1 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/fx.json +642 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/main.frag +33 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/pass0.frag +56 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/pass1.frag +44 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/pass2.frag +12 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/pass3.frag +12 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/pass4.frag +12 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/pass5.frag +12 -0
- package/skills/agentvfx/templates/scope/xy-phosphor/pass6.frag +251 -0
- package/skills/agentvfx/templates/spectrum/_src/bars-linear/fx.src.json +61 -0
- package/skills/agentvfx/templates/spectrum/_src/bars-linear/main.src.frag +112 -0
- package/skills/agentvfx/templates/spectrum/_src/bars-linear/pass0.src.frag +2 -0
- package/skills/agentvfx/templates/spectrum/_src/bars-mirror/fx.src.json +51 -0
- package/skills/agentvfx/templates/spectrum/_src/bars-mirror/main.src.frag +72 -0
- package/skills/agentvfx/templates/spectrum/_src/bars-mirror/pass0.src.frag +2 -0
- package/skills/agentvfx/templates/spectrum/_src/bass-circle/fx.src.json +56 -0
- package/skills/agentvfx/templates/spectrum/_src/bass-circle/main.src.frag +136 -0
- package/skills/agentvfx/templates/spectrum/_src/bass-circle/pass0.src.frag +2 -0
- package/skills/agentvfx/templates/spectrum/_src/build.mjs +68 -0
- package/skills/agentvfx/templates/spectrum/_src/label-bars/fx.src.json +54 -0
- package/skills/agentvfx/templates/spectrum/_src/label-bars/main.src.frag +102 -0
- package/skills/agentvfx/templates/spectrum/_src/label-bars/pass0.src.frag +2 -0
- package/skills/agentvfx/templates/spectrum/_src/shared/bars-fn.glsl +64 -0
- package/skills/agentvfx/templates/spectrum/_src/shared/bars-pass.glsl +22 -0
- package/skills/agentvfx/templates/spectrum/_src/shared/clock.glsl +7 -0
- package/skills/agentvfx/templates/spectrum/_src/shared/hud.glsl +50 -0
- package/skills/agentvfx/templates/spectrum/_src/shared/stage.glsl +74 -0
- package/skills/agentvfx/templates/spectrum/_src/spectrum-particles/fx.src.json +58 -0
- package/skills/agentvfx/templates/spectrum/_src/spectrum-particles/main.src.frag +119 -0
- package/skills/agentvfx/templates/spectrum/_src/spectrum-particles/pass0.src.frag +33 -0
- package/skills/agentvfx/templates/spectrum/_src/spectrum-radial/fx.src.json +56 -0
- package/skills/agentvfx/templates/spectrum/_src/spectrum-radial/main.src.frag +95 -0
- package/skills/agentvfx/templates/spectrum/_src/spectrum-radial/pass0.src.frag +2 -0
- package/skills/agentvfx/templates/spectrum/_src/waveform-line/fx.src.json +60 -0
- package/skills/agentvfx/templates/spectrum/_src/waveform-line/main.src.frag +98 -0
- package/skills/agentvfx/templates/spectrum/_src/waveform-line/pass0.src.frag +43 -0
- package/skills/agentvfx/templates/spectrum/bars-linear/demo-graph-acid.json +1 -0
- package/skills/agentvfx/templates/spectrum/bars-linear/demo-graph-black.json +1 -0
- package/skills/agentvfx/templates/spectrum/bars-linear/fx.json +632 -0
- package/skills/agentvfx/templates/spectrum/bars-linear/main.frag +233 -0
- package/skills/agentvfx/templates/spectrum/bars-linear/pass0.frag +91 -0
- package/skills/agentvfx/templates/spectrum/bars-mirror/demo-field-acid.json +1 -0
- package/skills/agentvfx/templates/spectrum/bars-mirror/demo-field-ultramarine.json +1 -0
- package/skills/agentvfx/templates/spectrum/bars-mirror/fx.json +539 -0
- package/skills/agentvfx/templates/spectrum/bars-mirror/main.frag +193 -0
- package/skills/agentvfx/templates/spectrum/bars-mirror/pass0.frag +91 -0
- package/skills/agentvfx/templates/spectrum/bass-circle/demo-bracket-black.json +1 -0
- package/skills/agentvfx/templates/spectrum/bass-circle/demo-bracket-ultramarine.json +1 -0
- package/skills/agentvfx/templates/spectrum/bass-circle/fx.json +552 -0
- package/skills/agentvfx/templates/spectrum/bass-circle/main.frag +258 -0
- package/skills/agentvfx/templates/spectrum/bass-circle/pass0.frag +91 -0
- package/skills/agentvfx/templates/spectrum/label-bars/demo-deck-black.json +1 -0
- package/skills/agentvfx/templates/spectrum/label-bars/demo-deck-ultramarine.json +1 -0
- package/skills/agentvfx/templates/spectrum/label-bars/fx.json +552 -0
- package/skills/agentvfx/templates/spectrum/label-bars/main.frag +223 -0
- package/skills/agentvfx/templates/spectrum/label-bars/pass0.frag +91 -0
- package/skills/agentvfx/templates/spectrum/spectrum-particles/demo-cloud-black.json +1 -0
- package/skills/agentvfx/templates/spectrum/spectrum-particles/demo-cloud-ultramarine.json +1 -0
- package/skills/agentvfx/templates/spectrum/spectrum-particles/fx.json +584 -0
- package/skills/agentvfx/templates/spectrum/spectrum-particles/main.frag +240 -0
- package/skills/agentvfx/templates/spectrum/spectrum-particles/pass0.frag +101 -0
- package/skills/agentvfx/templates/spectrum/spectrum-radial/demo-starburst-black.json +1 -0
- package/skills/agentvfx/templates/spectrum/spectrum-radial/demo-starburst-ultramarine.json +1 -0
- package/skills/agentvfx/templates/spectrum/spectrum-radial/fx.json +566 -0
- package/skills/agentvfx/templates/spectrum/spectrum-radial/main.frag +217 -0
- package/skills/agentvfx/templates/spectrum/spectrum-radial/pass0.frag +91 -0
- package/skills/agentvfx/templates/spectrum/waveform-line/demo-strip-acid.json +1 -0
- package/skills/agentvfx/templates/spectrum/waveform-line/demo-strip-black.json +1 -0
- package/skills/agentvfx/templates/spectrum/waveform-line/fx.json +481 -0
- package/skills/agentvfx/templates/spectrum/waveform-line/main.frag +219 -0
- package/skills/agentvfx/templates/spectrum/waveform-line/pass0.frag +48 -0
- package/skills/agentvfx/templates/spectrum/waveform-line/pass1.frag +86 -0
- package/skills/agentvfx/test/_render_light.mjs +38 -0
- package/skills/agentvfx/test/audio-stereo.test.mjs +45 -0
- package/skills/agentvfx/test/audio.test.mjs +257 -0
- package/skills/agentvfx/test/brutal.test.mjs +190 -0
- package/skills/agentvfx/test/concept-collage-layout.test.mjs +236 -0
- package/skills/agentvfx/test/concept-collage-scenes.test.mjs +305 -0
- package/skills/agentvfx/test/concept-collage-typefx.test.mjs +153 -0
- package/skills/agentvfx/test/concept-collage.test.mjs +84 -0
- package/skills/agentvfx/test/concept-lattice.test.mjs +133 -0
- package/skills/agentvfx/test/concept-signal-cut.test.mjs +172 -0
- package/skills/agentvfx/test/credit-overlay.test.mjs +58 -0
- package/skills/agentvfx/test/credit.test.mjs +54 -0
- package/skills/agentvfx/test/doctor.test.mjs +31 -0
- package/skills/agentvfx/test/encode.test.mjs +22 -0
- package/skills/agentvfx/test/fixtures/r7-collage-LAYOUTS.json +5780 -0
- package/skills/agentvfx/test/fixtures/score-bad-duration.json +7 -0
- package/skills/agentvfx/test/fixtures/score-bad-safety.json +8 -0
- package/skills/agentvfx/test/fixtures/score-identity-footage.json +8 -0
- package/skills/agentvfx/test/fixtures/score-missing-template.json +7 -0
- package/skills/agentvfx/test/fixtures/score-ok.json +8 -0
- package/skills/agentvfx/test/fixtures/score-strobe-12hz.json +8 -0
- package/skills/agentvfx/test/fixtures/score-text-probe.json +1 -0
- package/skills/agentvfx/test/flash-audit.test.mjs +150 -0
- package/skills/agentvfx/test/flash.test.mjs +83 -0
- package/skills/agentvfx/test/footage.test.mjs +88 -0
- package/skills/agentvfx/test/glitch.test.mjs +272 -0
- package/skills/agentvfx/test/impact.test.mjs +231 -0
- package/skills/agentvfx/test/imports.test.mjs +39 -0
- package/skills/agentvfx/test/light.test.mjs +200 -0
- package/skills/agentvfx/test/loop.test.mjs +80 -0
- package/skills/agentvfx/test/psychedelic.test.mjs +99 -0
- package/skills/agentvfx/test/render.test.mjs +73 -0
- package/skills/agentvfx/test/sacred.test.mjs +175 -0
- package/skills/agentvfx/test/schema.test.mjs +103 -0
- package/skills/agentvfx/test/scope.test.mjs +306 -0
- package/skills/agentvfx/test/spectrum.test.mjs +165 -0
- package/skills/agentvfx/test/text.test.mjs +142 -0
- package/skills/agentvfx/test/web.test.mjs +400 -0
- package/skills/agentvfx/test-media/SOURCES.md +39 -0
- package/skills/agentvfx/test-media/beat-132.mp3 +0 -0
- package/skills/agentvfx/test-media/clip-01.mp4 +0 -0
- package/skills/agentvfx/test-media/clip-02.mp4 +0 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
// agentvfx (Apache-2.0, see LICENSE). The render page: WebGL2 multipass renderer for template layers with
|
|
2
|
+
// persistent RGBA16F buffers. The host (engine/node/render.mjs) calls window.__litPrepare once, then
|
|
3
|
+
// window.__litStep(f, plan) for every frame in order, and captures the canvas after each step.
|
|
4
|
+
// No requestAnimationFrame, no wall clock: time comes only from the plan.
|
|
5
|
+
import { VERT, buildFragment, OUTPUT_FRAG } from "./prelude.mjs";
|
|
6
|
+
import { drawTextLayer, warmFonts } from "./text.mjs";
|
|
7
|
+
|
|
8
|
+
const TEXT_UNIT = 14;
|
|
9
|
+
|
|
10
|
+
const canvas = document.getElementById("c");
|
|
11
|
+
let gl = null;
|
|
12
|
+
let W = 0;
|
|
13
|
+
let H = 0;
|
|
14
|
+
let fps = 30;
|
|
15
|
+
let seed01 = 0;
|
|
16
|
+
let palette = [];
|
|
17
|
+
let probeNondeterminism = false;
|
|
18
|
+
const programs = new Map(); // `${template}#${pass}` -> { prog, uniforms }
|
|
19
|
+
const templates = new Map(); // id -> { inputs, passes: [{ target, persistent }] }
|
|
20
|
+
const instances = new Map(); // layer key -> { targets: Map, out: { tex, fbo } }
|
|
21
|
+
let vao = null;
|
|
22
|
+
let outputProgram = null;
|
|
23
|
+
let blackTex = null;
|
|
24
|
+
let creditProgram = null;
|
|
25
|
+
let creditTex = null;
|
|
26
|
+
let footageTex = null;
|
|
27
|
+
let hasFootage = false;
|
|
28
|
+
let audioTex = null;
|
|
29
|
+
let fftTex = null;
|
|
30
|
+
|
|
31
|
+
function compile(type, src, label) {
|
|
32
|
+
const sh = gl.createShader(type);
|
|
33
|
+
gl.shaderSource(sh, src);
|
|
34
|
+
gl.compileShader(sh);
|
|
35
|
+
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) throw new Error(`shader compile failed (${label}): ${gl.getShaderInfoLog(sh)}`);
|
|
36
|
+
return sh;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function link(fragSrc, label) {
|
|
40
|
+
const prog = gl.createProgram();
|
|
41
|
+
gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT, `${label} vert`));
|
|
42
|
+
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, fragSrc, label));
|
|
43
|
+
gl.linkProgram(prog);
|
|
44
|
+
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) throw new Error(`program link failed (${label}): ${gl.getProgramInfoLog(prog)}`);
|
|
45
|
+
const uniforms = new Map();
|
|
46
|
+
const n = gl.getProgramParameter(prog, gl.ACTIVE_UNIFORMS);
|
|
47
|
+
for (let i = 0; i < n; i++) {
|
|
48
|
+
const info = gl.getActiveUniform(prog, i);
|
|
49
|
+
const name = info.name.replace(/\[0\]$/u, "");
|
|
50
|
+
uniforms.set(name, gl.getUniformLocation(prog, info.name));
|
|
51
|
+
}
|
|
52
|
+
return { prog, uniforms };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function floatTexture(w, h, data = null) {
|
|
56
|
+
const tex = gl.createTexture();
|
|
57
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
58
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, w, h, 0, gl.RGBA, gl.FLOAT, data);
|
|
59
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
60
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
61
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
62
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
63
|
+
return tex;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function target(w, h) {
|
|
67
|
+
const tex = floatTexture(w, h);
|
|
68
|
+
const fbo = gl.createFramebuffer();
|
|
69
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
70
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
|
|
71
|
+
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
|
|
72
|
+
if (status !== gl.FRAMEBUFFER_COMPLETE) throw new Error(`RGBA16F framebuffer incomplete (0x${status.toString(16)})`);
|
|
73
|
+
gl.viewport(0, 0, w, h);
|
|
74
|
+
gl.clearColor(0, 0, 0, 0);
|
|
75
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
76
|
+
return { tex, fbo };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function dropTarget(t) {
|
|
80
|
+
gl.deleteTexture(t.tex);
|
|
81
|
+
gl.deleteFramebuffer(t.fbo);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const srgbToLinear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
|
|
85
|
+
|
|
86
|
+
async function fetchText(url) {
|
|
87
|
+
const r = await fetch(url);
|
|
88
|
+
if (!r.ok) throw new Error(`could not load ${url} (${r.status})`);
|
|
89
|
+
return r.text();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function prepare(opts) {
|
|
93
|
+
W = opts.width;
|
|
94
|
+
H = opts.height;
|
|
95
|
+
fps = opts.fps;
|
|
96
|
+
seed01 = opts.seed01;
|
|
97
|
+
palette = opts.palette ?? [];
|
|
98
|
+
probeNondeterminism = Boolean(opts.probeNondeterminism);
|
|
99
|
+
canvas.width = W;
|
|
100
|
+
canvas.height = H;
|
|
101
|
+
canvas.style.width = `${W}px`;
|
|
102
|
+
canvas.style.height = `${H}px`;
|
|
103
|
+
gl = canvas.getContext("webgl2", { alpha: false, antialias: false, depth: false, stencil: false, preserveDrawingBuffer: true, premultipliedAlpha: false, powerPreference: "high-performance" });
|
|
104
|
+
if (!gl) throw new Error("BLOCKED_NO_WEBGL2: no WebGL2 context");
|
|
105
|
+
if (!gl.getExtension("EXT_color_buffer_float")) throw new Error("BLOCKED_NO_WEBGL2: EXT_color_buffer_float missing");
|
|
106
|
+
gl.getExtension("OES_texture_float_linear");
|
|
107
|
+
vao = gl.createVertexArray();
|
|
108
|
+
outputProgram = link(OUTPUT_FRAG, "output");
|
|
109
|
+
blackTex = floatTexture(1, 1, new Float32Array([0, 0, 0, 1]));
|
|
110
|
+
if (opts.credit) {
|
|
111
|
+
creditProgram = link(CREDIT_FRAG, "credit");
|
|
112
|
+
creditTex = creditTexture(opts.credit);
|
|
113
|
+
}
|
|
114
|
+
audioTex = floatTexture(512, 2, new Float32Array(512 * 2 * 4).fill(0.5));
|
|
115
|
+
fftTex = floatTexture(512, 1, new Float32Array(512 * 4));
|
|
116
|
+
for (const [id, t] of Object.entries(opts.templates)) {
|
|
117
|
+
const passes = t.passes;
|
|
118
|
+
const targets = passes.map((p) => p.target).filter(Boolean);
|
|
119
|
+
templates.set(id, { inputs: t.inputs, passes, text: t.text ?? null });
|
|
120
|
+
if (t.text) await warmFonts(t.text, H);
|
|
121
|
+
for (let i = 0; i < passes.length; i++) {
|
|
122
|
+
const body = await fetchText(`/lit/templates/${t.rel}/${passes[i].file}`);
|
|
123
|
+
programs.set(`${id}#${i}`, link(buildFragment({ inputs: t.inputs, targets, body }), `${id} ${passes[i].file}`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const dbg = gl.getExtension("WEBGL_debug_renderer_info");
|
|
127
|
+
return { ok: true, renderer: dbg ? String(gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL)) : "unknown" };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function instanceFor(key, templateId) {
|
|
131
|
+
let inst = instances.get(key);
|
|
132
|
+
if (!inst) {
|
|
133
|
+
const tpl = templates.get(templateId);
|
|
134
|
+
const targets = new Map();
|
|
135
|
+
for (const p of tpl.passes) {
|
|
136
|
+
if (p.target && !targets.has(p.target)) targets.set(p.target, { pair: [target(W, H), target(W, H)], read: 0, persistent: Boolean(p.persistent) });
|
|
137
|
+
}
|
|
138
|
+
inst = { targets, out: target(W, H), text: null };
|
|
139
|
+
if (tpl.text) {
|
|
140
|
+
const c = document.createElement("canvas");
|
|
141
|
+
c.width = W;
|
|
142
|
+
c.height = H;
|
|
143
|
+
const tex = gl.createTexture();
|
|
144
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
145
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, W, H, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
146
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
147
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
148
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
149
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
150
|
+
inst.text = { canvas: c, ctx: c.getContext("2d", { willReadFrequently: false }), tex };
|
|
151
|
+
}
|
|
152
|
+
instances.set(key, inst);
|
|
153
|
+
}
|
|
154
|
+
return inst;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function setParam(u, input, value) {
|
|
158
|
+
const loc = u.get(input.NAME);
|
|
159
|
+
if (loc === undefined || loc === null) return;
|
|
160
|
+
switch (input.TYPE) {
|
|
161
|
+
case "float": gl.uniform1f(loc, value); break;
|
|
162
|
+
case "long": gl.uniform1i(loc, value | 0); break;
|
|
163
|
+
case "bool": gl.uniform1i(loc, value ? 1 : 0); break;
|
|
164
|
+
case "color": gl.uniform4f(loc, srgbToLinear(value[0]), srgbToLinear(value[1]), srgbToLinear(value[2]), value[3]); break;
|
|
165
|
+
case "point2D": gl.uniform2f(loc, value[0], value[1]); break;
|
|
166
|
+
default: break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function setCommon(u, { t, frame, passIndex, input, hasInput, audio }) {
|
|
171
|
+
const set1f = (n, v) => { const l = u.get(n); if (l) gl.uniform1f(l, v); };
|
|
172
|
+
const set1i = (n, v) => { const l = u.get(n); if (l) gl.uniform1i(l, v); };
|
|
173
|
+
set1f("TIME", t);
|
|
174
|
+
set1f("TIMEDELTA", 1 / fps);
|
|
175
|
+
set1i("FRAMEINDEX", frame);
|
|
176
|
+
set1i("PASSINDEX", passIndex);
|
|
177
|
+
if (u.get("RENDERSIZE")) gl.uniform2f(u.get("RENDERSIZE"), W, H);
|
|
178
|
+
set1f("SEED", probeNondeterminism ? crypto.getRandomValues(new Uint32Array(1))[0] / 4294967296 : seed01);
|
|
179
|
+
set1i("HAS_INPUT", hasInput ? 1 : 0);
|
|
180
|
+
if (u.get("PALETTE")) {
|
|
181
|
+
const flat = new Float32Array(24);
|
|
182
|
+
palette.slice(0, 8).forEach((c, i) => flat.set(c, i * 3));
|
|
183
|
+
gl.uniform3fv(u.get("PALETTE"), flat);
|
|
184
|
+
}
|
|
185
|
+
set1i("PALETTE_COUNT", Math.min(8, palette.length));
|
|
186
|
+
const a = audio ?? {};
|
|
187
|
+
const scal = { AUDIO_RMS: "rms", AUDIO_PEAK: "peak", AUDIO_BASS: "bass", AUDIO_MID: "mid", AUDIO_TREB: "treb", AUDIO_BASS_ATT: "bass_att", AUDIO_MID_ATT: "mid_att", AUDIO_TREB_ATT: "treb_att", AUDIO_FLUX: "flux", AUDIO_ONSET: "onset", AUDIO_BEAT: "beat", AUDIO_BEATPHASE: "beatPhase", AUDIO_BPM: "bpm" };
|
|
188
|
+
for (const [name, key] of Object.entries(scal)) set1f(name, Number(a[key] ?? 0));
|
|
189
|
+
if (u.get("AUDIO_BANDS")) gl.uniform1fv(u.get("AUDIO_BANDS"), new Float32Array(Array.from({ length: 32 }, (_, i) => Number(a.bands?.[i] ?? 0))));
|
|
190
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
191
|
+
gl.bindTexture(gl.TEXTURE_2D, input);
|
|
192
|
+
set1i("inputImage", 0);
|
|
193
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
194
|
+
gl.bindTexture(gl.TEXTURE_2D, audioTex);
|
|
195
|
+
set1i("audio", 1);
|
|
196
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
197
|
+
gl.bindTexture(gl.TEXTURE_2D, fftTex);
|
|
198
|
+
set1i("audioFFT", 2);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function uploadAudio(audio) {
|
|
202
|
+
if (!audio) return;
|
|
203
|
+
if (audio.wave) {
|
|
204
|
+
const d = new Float32Array(512 * 2 * 4);
|
|
205
|
+
for (let row = 0; row < 2; row++) {
|
|
206
|
+
const src = row === 0 ? (audio.waveL ?? audio.wave) : (audio.waveR ?? audio.wave);
|
|
207
|
+
for (let i = 0; i < 512; i++) {
|
|
208
|
+
const v = 0.5 + 0.5 * Number(src[i] ?? 0);
|
|
209
|
+
d.set([v, v, v, 1], (row * 512 + i) * 4);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
gl.bindTexture(gl.TEXTURE_2D, audioTex);
|
|
213
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 512, 2, gl.RGBA, gl.FLOAT, d);
|
|
214
|
+
}
|
|
215
|
+
if (audio.spec) {
|
|
216
|
+
const d = new Float32Array(512 * 4);
|
|
217
|
+
for (let i = 0; i < 512; i++) {
|
|
218
|
+
const v = Number(audio.spec[i] ?? 0);
|
|
219
|
+
d.set([v, v, v, 1], i * 4);
|
|
220
|
+
}
|
|
221
|
+
gl.bindTexture(gl.TEXTURE_2D, fftTex);
|
|
222
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 512, 1, gl.RGBA, gl.FLOAT, d);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function renderLayer(layer, inputTex, hasInput, audio) {
|
|
227
|
+
const tpl = templates.get(layer.template);
|
|
228
|
+
if (!tpl) throw new Error(`template not prepared: ${layer.template}`);
|
|
229
|
+
const inst = instanceFor(layer.key, layer.template);
|
|
230
|
+
const written = new Set();
|
|
231
|
+
const frame = Math.round(layer.t * fps);
|
|
232
|
+
if (inst.text) {
|
|
233
|
+
const set = Number(layer.params?.textSet ?? 0);
|
|
234
|
+
const items = tpl.text.some((s) => s.set) ? tpl.text.filter((s) => (s.set ?? 0) === set) : tpl.text;
|
|
235
|
+
const specs = layer.textStyle ? items.map((s) => ({ ...s, ...layer.textStyle[s.key] })) : items;
|
|
236
|
+
drawTextLayer(inst.text.ctx, W, H, specs, layer.labels, layer.t, frame, Math.floor(seed01 * 4294967296));
|
|
237
|
+
gl.bindTexture(gl.TEXTURE_2D, inst.text.tex);
|
|
238
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
239
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, inst.text.canvas);
|
|
240
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
241
|
+
}
|
|
242
|
+
for (let i = 0; i < tpl.passes.length; i++) {
|
|
243
|
+
const pass = tpl.passes[i];
|
|
244
|
+
const { prog, uniforms } = programs.get(`${layer.template}#${i}`);
|
|
245
|
+
const last = i === tpl.passes.length - 1;
|
|
246
|
+
let dest;
|
|
247
|
+
if (last) dest = inst.out;
|
|
248
|
+
else {
|
|
249
|
+
if (!pass.target) throw new Error(`${layer.template}: pass ${i} has no TARGET`);
|
|
250
|
+
const tg = inst.targets.get(pass.target);
|
|
251
|
+
dest = tg.pair[1 - tg.read];
|
|
252
|
+
}
|
|
253
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, dest.fbo);
|
|
254
|
+
gl.viewport(0, 0, W, H);
|
|
255
|
+
gl.useProgram(prog);
|
|
256
|
+
setCommon(uniforms, { t: layer.t, frame, passIndex: i, input: inputTex, hasInput, audio });
|
|
257
|
+
let unit = 3;
|
|
258
|
+
for (const [name, tg] of inst.targets) {
|
|
259
|
+
const loc = uniforms.get(name);
|
|
260
|
+
if (!loc) continue;
|
|
261
|
+
const tex = written.has(name) ? tg.pair[1 - tg.read].tex : tg.pair[tg.read].tex;
|
|
262
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
263
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
264
|
+
gl.uniform1i(loc, unit);
|
|
265
|
+
unit += 1;
|
|
266
|
+
}
|
|
267
|
+
gl.activeTexture(gl.TEXTURE0 + TEXT_UNIT);
|
|
268
|
+
gl.bindTexture(gl.TEXTURE_2D, inst.text ? inst.text.tex : blackTex);
|
|
269
|
+
if (uniforms.get("TEXTMASK")) gl.uniform1i(uniforms.get("TEXTMASK"), TEXT_UNIT);
|
|
270
|
+
if (uniforms.get("HAS_TEXT")) gl.uniform1i(uniforms.get("HAS_TEXT"), inst.text ? 1 : 0);
|
|
271
|
+
for (const input of tpl.inputs) if (layer.params[input.NAME] !== undefined) setParam(uniforms, input, layer.params[input.NAME]);
|
|
272
|
+
gl.bindVertexArray(vao);
|
|
273
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
274
|
+
if (!last) written.add(pass.target);
|
|
275
|
+
}
|
|
276
|
+
for (const name of written) {
|
|
277
|
+
const tg = inst.targets.get(name);
|
|
278
|
+
tg.read = 1 - tg.read;
|
|
279
|
+
}
|
|
280
|
+
return inst.out.tex;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function step(f, plan) {
|
|
284
|
+
uploadAudio(plan.audio);
|
|
285
|
+
const active = new Set(plan.layers.map((l) => l.key));
|
|
286
|
+
for (const [key, inst] of instances) {
|
|
287
|
+
if (active.has(key)) continue;
|
|
288
|
+
for (const tg of inst.targets.values()) tg.pair.forEach(dropTarget);
|
|
289
|
+
dropTarget(inst.out);
|
|
290
|
+
if (inst.text) gl.deleteTexture(inst.text.tex);
|
|
291
|
+
instances.delete(key);
|
|
292
|
+
}
|
|
293
|
+
let tex = plan.footage && hasFootage ? footageTex : blackTex;
|
|
294
|
+
let hasInput = Boolean(plan.footage && hasFootage);
|
|
295
|
+
for (const layer of plan.layers) {
|
|
296
|
+
tex = renderLayer(layer, tex, hasInput, plan.audio);
|
|
297
|
+
hasInput = true;
|
|
298
|
+
}
|
|
299
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
300
|
+
gl.viewport(0, 0, W, H);
|
|
301
|
+
gl.useProgram(outputProgram.prog);
|
|
302
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
303
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
304
|
+
gl.uniform1i(outputProgram.uniforms.get("src"), 0);
|
|
305
|
+
gl.uniform1i(outputProgram.uniforms.get("FRAMEINDEX"), f);
|
|
306
|
+
gl.uniform1f(outputProgram.uniforms.get("SEED"), probeNondeterminism ? crypto.getRandomValues(new Uint32Array(1))[0] / 4294967296 : seed01);
|
|
307
|
+
gl.bindVertexArray(vao);
|
|
308
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
309
|
+
if (creditTex) {
|
|
310
|
+
gl.enable(gl.BLEND);
|
|
311
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
312
|
+
gl.useProgram(creditProgram.prog);
|
|
313
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
314
|
+
gl.bindTexture(gl.TEXTURE_2D, creditTex);
|
|
315
|
+
gl.uniform1i(creditProgram.uniforms.get("credit"), 0);
|
|
316
|
+
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
317
|
+
gl.disable(gl.BLEND);
|
|
318
|
+
}
|
|
319
|
+
gl.finish();
|
|
320
|
+
const err = gl.getError();
|
|
321
|
+
if (err !== gl.NO_ERROR) throw new Error(`WebGL error 0x${err.toString(16)} on frame ${f}`);
|
|
322
|
+
return { frame: f, layers: plan.layers.length };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const CREDIT_FRAG = `#version 300 es
|
|
326
|
+
precision highp float;
|
|
327
|
+
in vec2 vUv;
|
|
328
|
+
out vec4 fragColor;
|
|
329
|
+
uniform sampler2D credit;
|
|
330
|
+
void main() { fragColor = texture(credit, vUv); }`;
|
|
331
|
+
|
|
332
|
+
/** The credit as a frame-sized RGBA texture: small text in the bottom-right corner, white with a dark edge. */
|
|
333
|
+
function creditTexture(text) {
|
|
334
|
+
const c = document.createElement("canvas");
|
|
335
|
+
c.width = W;
|
|
336
|
+
c.height = H;
|
|
337
|
+
const ctx = c.getContext("2d");
|
|
338
|
+
const px = Math.max(11, Math.round(H * 0.022));
|
|
339
|
+
ctx.font = `600 ${px}px "Helvetica Neue", Helvetica, Arial, sans-serif`;
|
|
340
|
+
ctx.textAlign = "right";
|
|
341
|
+
ctx.textBaseline = "alphabetic";
|
|
342
|
+
const x = W - Math.round(px * 1.1);
|
|
343
|
+
const y = H - Math.round(px * 0.9);
|
|
344
|
+
ctx.lineJoin = "round";
|
|
345
|
+
ctx.lineWidth = Math.max(2, px * 0.18);
|
|
346
|
+
ctx.strokeStyle = "rgba(0, 0, 0, 0.55)";
|
|
347
|
+
ctx.strokeText(text, x, y);
|
|
348
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
|
|
349
|
+
ctx.fillText(text, x, y);
|
|
350
|
+
const tex = gl.createTexture();
|
|
351
|
+
gl.bindTexture(gl.TEXTURE_2D, tex);
|
|
352
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
353
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, c);
|
|
354
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
355
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
356
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
357
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
358
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
359
|
+
return tex;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Footage frame upload: RGBA8 sRGB bytes of the footage's own size. */
|
|
363
|
+
function footage(bytes, w, h) {
|
|
364
|
+
if (!footageTex) {
|
|
365
|
+
footageTex = gl.createTexture();
|
|
366
|
+
gl.bindTexture(gl.TEXTURE_2D, footageTex);
|
|
367
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.SRGB8_ALPHA8, w, h);
|
|
368
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
369
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
370
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
371
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
372
|
+
}
|
|
373
|
+
gl.bindTexture(gl.TEXTURE_2D, footageTex);
|
|
374
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
375
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, bytes);
|
|
376
|
+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
|
377
|
+
hasFootage = true;
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
window.__litPrepare = (opts) => prepare(opts).catch((e) => ({ ok: false, error: String(e.message ?? e) }));
|
|
382
|
+
window.__litStep = (f, plan) => step(f, plan);
|
|
383
|
+
window.__litFootageFetch = async (url, w, h) => {
|
|
384
|
+
const r = await fetch(url);
|
|
385
|
+
if (!r.ok) throw new Error(`footage frame ${url} missing (${r.status})`);
|
|
386
|
+
return footage(new Uint8Array(await r.arrayBuffer()), w, h);
|
|
387
|
+
};
|
|
388
|
+
window.__litStageState = () => ({ ready: Boolean(gl), width: W, height: H });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Output scale, read once from the page URL (`?scale=2` renders 3840x2160). Scenes keep laying out
|
|
2
|
+
// in logical 1920x1080 px at every scale. Adapted from mexicat/pdoom-video app/src/engine/scale.ts
|
|
3
|
+
// (MIT, see ../NOTICE).
|
|
4
|
+
function readScale() {
|
|
5
|
+
if (typeof location === "undefined") return 1;
|
|
6
|
+
const value = Math.round(Number(new URLSearchParams(location.search).get("scale") ?? "1"));
|
|
7
|
+
return Number.isFinite(value) && value >= 1 ? Math.min(value, 4) : 1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Physical px per logical px (integer 1..4). */
|
|
11
|
+
export const SCALE = readScale();
|
|
12
|
+
export const W = 1920;
|
|
13
|
+
export const H = 1080;
|
|
14
|
+
export const PW = W * SCALE;
|
|
15
|
+
export const PH = H * SCALE;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// agentvfx (Apache-2.0, see LICENSE). Engine text layer, drawn in the render page with Canvas 2D and system fonts
|
|
2
|
+
// (nothing bundled). Output is a mask: R = glyph and line ink, G = solid box/bar fill. Templates colour
|
|
3
|
+
// it. Reveal modes are frame-deterministic: "type" prints characters over time with a block cursor,
|
|
4
|
+
// "decode" settles each character out of seeded random glyphs.
|
|
5
|
+
|
|
6
|
+
export const FONT_STACKS = Object.freeze({
|
|
7
|
+
display: { family: '"DIN Condensed", "Avenir Next Condensed", "Arial Narrow", sans-serif', weight: 700, stretchY: 1.0 },
|
|
8
|
+
tall: { family: '"Avenir Next Condensed", "Helvetica Neue", "Arial Narrow", sans-serif', weight: 300, stretchY: 1.75 },
|
|
9
|
+
grotesk: { family: '"Helvetica Neue", Helvetica, Arial, sans-serif', weight: 800, stretchY: 1.0 },
|
|
10
|
+
mono: { family: 'Menlo, "SF Mono", Monaco, "Courier New", monospace', weight: 400, stretchY: 1.0 },
|
|
11
|
+
serif: { family: '"Times New Roman", Times, serif', weight: 400, stretchY: 1.0 },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const SCRAMBLE = "ABCDEFGHJKLMNPQRSTUVWXYZ0123456789#%&*+=<>/\\";
|
|
15
|
+
const INK = "rgb(255,0,0)";
|
|
16
|
+
const FILL = "rgb(0,255,0)";
|
|
17
|
+
/** Glyph ink colour per item `channel`: r (default) is the shared sheet, g and b are extra independent sheets. */
|
|
18
|
+
export const CHANNEL_INK = Object.freeze({ r: INK, g: FILL, b: "rgb(0,0,255)" });
|
|
19
|
+
|
|
20
|
+
function hash(a, b, c) {
|
|
21
|
+
let x = (Math.imul(a | 0, 0x9e3779b1) ^ Math.imul(b | 0, 0x85ebca77) ^ Math.imul(c | 0, 0xc2b2ae3d)) >>> 0;
|
|
22
|
+
x ^= x >>> 16;
|
|
23
|
+
x = Math.imul(x, 0x7feb352d) >>> 0;
|
|
24
|
+
x ^= x >>> 15;
|
|
25
|
+
return x >>> 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The visible string for one item at time t (seconds). */
|
|
29
|
+
export function revealText(spec, text, t, frame, seedInt) {
|
|
30
|
+
const start = spec.revealStart ?? 0;
|
|
31
|
+
const dur = Math.max(1e-3, spec.revealDur ?? 1);
|
|
32
|
+
const p = Math.min(1, Math.max(0, (t - start) / dur));
|
|
33
|
+
if (spec.reveal === "type") {
|
|
34
|
+
if (t < start) return "";
|
|
35
|
+
const n = Math.floor(p * text.length);
|
|
36
|
+
const blink = Math.floor(t * 4) % 2 === 0;
|
|
37
|
+
return text.slice(0, n) + (p < 1 || blink ? "█" : "");
|
|
38
|
+
}
|
|
39
|
+
if (spec.reveal === "decode") {
|
|
40
|
+
if (t < start) return "";
|
|
41
|
+
const tick = Math.floor(frame / 2);
|
|
42
|
+
return [...text].map((ch, i) => {
|
|
43
|
+
if (ch === " " || ch === "\n") return ch;
|
|
44
|
+
const settle = (i + 1) / text.length;
|
|
45
|
+
return p >= settle ? ch : SCRAMBLE[hash(i, tick, seedInt) % SCRAMBLE.length];
|
|
46
|
+
}).join("");
|
|
47
|
+
}
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function roundRect(ctx, x, y, w, h, r) {
|
|
52
|
+
ctx.beginPath();
|
|
53
|
+
ctx.moveTo(x + r, y);
|
|
54
|
+
ctx.arcTo(x + w, y, x + w, y + h, r);
|
|
55
|
+
ctx.arcTo(x + w, y + h, x, y + h, r);
|
|
56
|
+
ctx.arcTo(x, y + h, x, y, r);
|
|
57
|
+
ctx.arcTo(x, y, x + w, y, r);
|
|
58
|
+
ctx.closePath();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Installed font family names an item may name: letters (any script), digits, spaces, dots, hyphens, underscores. */
|
|
62
|
+
export const FAMILY_PATTERN = /^[\p{L}\p{N} ._-]{1,64}$/u;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The CSS font stack of one item: its generic `font` stack, with an installed `family` (referenced by name only, nothing is
|
|
66
|
+
* bundled) put in front so an absent family falls through to the generic stack.
|
|
67
|
+
*/
|
|
68
|
+
export function fontStack(spec) {
|
|
69
|
+
const base = FONT_STACKS[spec.font ?? "display"];
|
|
70
|
+
const family = typeof spec.family === "string" && FAMILY_PATTERN.test(spec.family) ? `"${spec.family}", ` : "";
|
|
71
|
+
return { ...base, family: `${family}${base.family}` };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Draw every text item of a layer into ctx (cleared first). */
|
|
75
|
+
export function drawTextLayer(ctx, W, H, specs, labels, t, frame, seedInt) {
|
|
76
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
77
|
+
ctx.globalCompositeOperation = "source-over";
|
|
78
|
+
ctx.fillStyle = "black";
|
|
79
|
+
ctx.fillRect(0, 0, W, H);
|
|
80
|
+
ctx.globalCompositeOperation = "lighter";
|
|
81
|
+
ctx.textBaseline = "alphabetic";
|
|
82
|
+
for (const spec of specs) {
|
|
83
|
+
const raw = labels?.[spec.key] ?? spec.default;
|
|
84
|
+
const text = revealText(spec, raw, t, frame, seedInt);
|
|
85
|
+
const font = fontStack(spec);
|
|
86
|
+
const px = Math.max(4, spec.size * H);
|
|
87
|
+
const sy = font.stretchY;
|
|
88
|
+
ctx.font = `${spec.weight ?? font.weight} ${px}px ${font.family}`;
|
|
89
|
+
ctx.letterSpacing = `${(spec.tracking ?? 0) * px}px`;
|
|
90
|
+
const lines = text.split("\n");
|
|
91
|
+
const lh = px * sy * (spec.lineHeight ?? 1.15);
|
|
92
|
+
const widths = lines.map((l) => ctx.measureText(l).width);
|
|
93
|
+
const maxW = Math.max(1, ...widths, spec.box && spec.box !== "none" ? ctx.measureText(raw.split("\n")[0]).width : 0);
|
|
94
|
+
const align = spec.align ?? "left";
|
|
95
|
+
const x0 = spec.x * W;
|
|
96
|
+
const yTop = spec.y * H;
|
|
97
|
+
const left = align === "left" ? x0 : align === "center" ? x0 - maxW / 2 : x0 - maxW;
|
|
98
|
+
const blockH = lh * lines.length;
|
|
99
|
+
const ink = CHANNEL_INK[spec.channel ?? "r"];
|
|
100
|
+
const pad = px * 0.28;
|
|
101
|
+
const stroke = Math.max(1.5, px * 0.035);
|
|
102
|
+
if (spec.box === "bar") {
|
|
103
|
+
ctx.fillStyle = FILL;
|
|
104
|
+
ctx.fillRect(left - pad, yTop - pad * 0.6, maxW + 2 * pad, blockH + pad * 0.8);
|
|
105
|
+
} else if (spec.box === "frame") {
|
|
106
|
+
ctx.strokeStyle = INK;
|
|
107
|
+
ctx.lineWidth = stroke;
|
|
108
|
+
roundRect(ctx, left - pad, yTop - pad * 0.6, maxW + 2 * pad, blockH + pad * 0.8, pad * 0.6);
|
|
109
|
+
ctx.stroke();
|
|
110
|
+
} else if (spec.box === "tab") {
|
|
111
|
+
// index card: square box with the top-right corner cut at 45 degrees and a solid spine on the left edge
|
|
112
|
+
const bx = left - pad;
|
|
113
|
+
const by = yTop - pad * 0.6;
|
|
114
|
+
const bw = maxW + 2 * pad;
|
|
115
|
+
const bh = blockH + pad * 0.8;
|
|
116
|
+
const cut = Math.min(bh * 0.42, px * 0.5);
|
|
117
|
+
ctx.strokeStyle = INK;
|
|
118
|
+
ctx.lineWidth = stroke;
|
|
119
|
+
ctx.beginPath();
|
|
120
|
+
ctx.moveTo(bx, by);
|
|
121
|
+
ctx.lineTo(bx + bw - cut, by);
|
|
122
|
+
ctx.lineTo(bx + bw, by + cut);
|
|
123
|
+
ctx.lineTo(bx + bw, by + bh);
|
|
124
|
+
ctx.lineTo(bx, by + bh);
|
|
125
|
+
ctx.closePath();
|
|
126
|
+
ctx.stroke();
|
|
127
|
+
ctx.fillStyle = INK;
|
|
128
|
+
ctx.fillRect(bx - stroke * 0.5, by, Math.max(stroke * 2.5, px * 0.09), bh);
|
|
129
|
+
}
|
|
130
|
+
lines.forEach((line, i) => {
|
|
131
|
+
const w = widths[i];
|
|
132
|
+
const lx = align === "left" ? left : align === "center" ? x0 - w / 2 : x0 - w;
|
|
133
|
+
const baseline = yTop + lh * i + px * sy * 0.82;
|
|
134
|
+
ctx.save();
|
|
135
|
+
ctx.translate(lx, baseline);
|
|
136
|
+
ctx.scale(1, sy);
|
|
137
|
+
if (spec.style === "outline") {
|
|
138
|
+
ctx.strokeStyle = ink;
|
|
139
|
+
ctx.lineWidth = stroke / sy;
|
|
140
|
+
ctx.strokeText(line, 0, 0);
|
|
141
|
+
} else {
|
|
142
|
+
ctx.fillStyle = ink;
|
|
143
|
+
ctx.fillText(line, 0, 0);
|
|
144
|
+
}
|
|
145
|
+
ctx.restore();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Make sure the system faces (the generic stacks and every item's named family) are resolved before the first frame (no fallback race). */
|
|
151
|
+
export async function warmFonts(specs, H) {
|
|
152
|
+
if (!document.fonts?.load) return;
|
|
153
|
+
const stacks = [...Object.values(FONT_STACKS), ...specs.filter((s) => s.family).map(fontStack)];
|
|
154
|
+
const loads = specs.flatMap((s) => stacks.map((f) => (
|
|
155
|
+
document.fonts.load(`${s.weight ?? f.weight} ${Math.max(4, s.size * H)}px ${f.family}`, s.default || "A").catch(() => null)
|
|
156
|
+
)));
|
|
157
|
+
await Promise.all(loads);
|
|
158
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# terminalcore plate
|
|
2
|
+
|
|
3
|
+
Procedural, fully self-made Blender scene (no downloaded assets, HDRIs, textures or font files) that renders
|
|
4
|
+
ray-traced background footage in a Terminalcore / CRT-futurism / cybernetic-brutalist look, to be fed to
|
|
5
|
+
agentvfx templates as `input.video`.
|
|
6
|
+
|
|
7
|
+
What is in the scene
|
|
8
|
+
- Board-formed raw concrete room (tie holes, panel joints, streaks), polished reflective floor, brutalist piers + lintel.
|
|
9
|
+
- A rack bank of 45 chunky CRT monitors: bevelled bezels, curved dusty glass with real reflections, vents, knobs, LEDs.
|
|
10
|
+
- Screens are emissive and procedural: terminal text drawn from a hand-made 5x7 bitmap font (`font.py`), scrolling,
|
|
11
|
+
with scanlines, edge vignette, flicker and a slow hum bar. Green / amber / cold-white phosphor, a few dead tubes.
|
|
12
|
+
- Cable tray, thick cables (drops, floor runs), status-LED array in the lintel, steel rack hardware.
|
|
13
|
+
- Volumetric haze (world scatter) with one cold key light entering through a slit window in the left wall.
|
|
14
|
+
- Cycles (Metal GPU), OIDN denoise, AgX view transform, shallow depth of field, compositor bloom, light handheld wander.
|
|
15
|
+
|
|
16
|
+
Shots
|
|
17
|
+
- `wall` wide, slow push-in across the whole CRT wall.
|
|
18
|
+
- `closeup` one CRT close, oblique slide, text scrolling, glass reflections, DOF falloff over neighbouring tubes.
|
|
19
|
+
|
|
20
|
+
- `gallery` the same CRT wall as a clean, hyper-real exhibition-room installation (see below).
|
|
21
|
+
|
|
22
|
+
Gallery variant (`--shot gallery`)
|
|
23
|
+
- Room: architectural cast-concrete panels (1.2 x 2.35 m, real 5 mm joints with tiny bevels, dark reveal gaps at floor and
|
|
24
|
+
ceiling, round form-tie holes, per-panel tone shift, pores, clouding), polished sealed concrete floor with soft reflections and
|
|
25
|
+
saw-cut joints, concrete soffit with three flush linear light slots, track rails with grazing spots, a wall-wash over the back wall.
|
|
26
|
+
- Installation: strict 9 x 5 grid of identical CRTs (no jitter, mirrored amber/green layout, a few dark tubes) in a black
|
|
27
|
+
powder-coat frame on a dark polished plinth; cables hidden behind the frame; one pale stone bench for scale. No text or branding.
|
|
28
|
+
- Look: AgX, exposure +0.15, haze only a touch (density 0.002), light bloom, lighter film grain in the encode.
|
|
29
|
+
- Camera: level, centred, one-point perspective with lens shift; steady 1.4 m dolly-in (-9.8 to -8.4), no shake, so the bench base keeps a clear margin above the bottom edge on the last frame. DOF is deep (f/11).
|
|
30
|
+
- Powered-off tubes are dark tinted glass (`glass_off` in `gallery_mats.py`): soft low-gloss reflection, no mirror glints of the ceiling slots. The bench is honed limestone with cloudy tone, thin veins, hone grain and a 13 mm edge bevel.
|
|
31
|
+
- Cost: measured 2.0 s/frame at 640x360, 56 samples, preview quality (240 s for the 4 s / 120-frame clip).
|
|
32
|
+
- Files: `gallery.py` (build + installation + bench), `gallery_room.py` (shell, ceiling, lights, haze), `gallery_mats.py` (materials).
|
|
33
|
+
- The screen layout is `LAYOUT` in `gallery.py` (bottom row first, columns mirrored); light intensity scales with `--key`.
|
|
34
|
+
|
|
35
|
+
Usage (from the agentvfx skill root)
|
|
36
|
+
|
|
37
|
+
node scripts/plate.mjs --scene terminalcore --shot wall --out ../../gallery/plates/terminalcore-wall \
|
|
38
|
+
--width 640 --height 360 --seconds 4 --fps 30 --samples 40
|
|
39
|
+
|
|
40
|
+
Writes `film.mp4` (H.264, yuv420p, light grain + vignette added in post; `--no-post` to skip), `still_first|mid|last.png`,
|
|
41
|
+
`plate.json` (measured seconds per frame) and `blender.log`.
|
|
42
|
+
|
|
43
|
+
Quality: `--quality preview` (default) is the fast look-dev path. `--quality final` raises samples to 256 (unless `--samples` is
|
|
44
|
+
given), tightens the adaptive threshold (0.01), uses the accurate OIDN denoise, enables Cycles camera motion blur (shutter 0.5; the camera
|
|
45
|
+
is keyframed on every frame because Cycles does not see a frame-change handler) and encodes at crf 14. Film grain stays the light
|
|
46
|
+
encode-time grain. Measured: 1920x1080 final, gallery, Metal GPU: about 109 s/frame (one test frame), so a 120-frame clip is roughly 3.6 h.
|
|
47
|
+
|
|
48
|
+
Options: `--quality preview|final`, `--frame N` renders one test still (`still_NNN.png`, no video), `--exposure EV`, `--bloom X` (0 = off), `--key X`
|
|
49
|
+
(key-light multiplier), `--haze X` (haze density multiplier), `--keep-frames`. Blender is found via `BLENDER_PATH`, `PATH`, or the macOS app bundle; ffmpeg via
|
|
50
|
+
`FFMPEG_PATH` / `PATH`.
|
|
51
|
+
|
|
52
|
+
Then use the clip as footage: pass the resulting `film.mp4` as the template's `input.video`.
|
|
53
|
+
|
|
54
|
+
Files: `scene.py` (entry, render setup, animation), `room.py` (shell, haze, key light), `wall.py` (rack, monitors, cables),
|
|
55
|
+
`crt.py` (monitor mesh), `materials.py` + `nodekit.py` (procedural shaders), `textures.py` + `font.py` (terminal text),
|
|
56
|
+
`shots.py` (camera moves).
|
|
57
|
+
|
|
58
|
+
Limits: preview-oriented defaults (640x360, 40 samples). For finals use `--quality final` and raise `--width/--height`
|
|
59
|
+
(time scales roughly with pixels x samples). Preview has no motion blur; final has camera motion blur only (the gallery dolly is slow,
|
|
60
|
+
about 12 mm per frame, so the blur is subtle; the scrolling screen text is a shader value and is not blurred). A faint film grain is
|
|
61
|
+
added in the encode step rather than in Cycles.
|
|
Binary file
|
|
Binary file
|