import { validateModule } from '../params/schema.js'; // STAGES — scenes that draw the song's cast rather than their own content. // See scenes/stage/README.md and EPIC-3.md. import { procession } from './stage/procession.js'; import { constellation } from './stage/constellation.js'; import { soloist } from './stage/soloist.js'; import { swarm } from './stage/swarm.js'; import { nebula } from './shader/nebula.js'; import { classicWave } from './shader/classic-wave.js'; import { floatingGeometry } from './shader/floating-geometry.js'; import { synthwaveRun } from './shader/synthwave-run.js'; import { psychedelicDrift } from './shader/psychedelic-drift.js'; import { particleField } from './layers3d/particles.js'; import { horizonLines } from './shader/horizon-lines.js'; import { spectrumSculpture } from './shader/spectrum-sculpture.js'; import { slowOrb } from './shader/slow-orb.js'; import { curlFlow } from './shader/curl-flow.js'; import { plasmaBloom } from './shader/plasma-bloom.js'; import { metaballs } from './shader/metaballs.js'; import { kaleidoTunnel } from './shader/kaleido-tunnel.js'; import { moireGrid } from './shader/moire-grid.js'; import { ridgeTerrain } from './shader/ridge-terrain.js'; import { scanTear } from './shader/scan-tear.js'; import { blockMosh } from './shader/block-mosh.js'; import { neonCity } from './shader/neon-city.js'; import { flora } from './shader/flora.js'; import { fireflyDrift } from './shader/firefly-drift.js'; import { silkRibbon } from './shader/silk-ribbon.js'; import { pylonGrid } from './shader/pylon-grid.js'; import { prismBloom } from './shader/prism-bloom.js'; import { pitchShatter } from './shader/pitch-shatter.js'; import { auroraVeil } from './shader/aurora-veil.js'; import { tideRings } from './shader/tide-rings.js'; import { dustChamber } from './shader/dust-chamber.js'; import { cargoBelt } from './shader/cargo-belt.js'; import { circuitBloom } from './shader/circuit-bloom.js'; import { truchetFold } from './shader/truchet-fold.js'; import { signalDecay } from './shader/signal-decay.js'; import { inkBleed } from './shader/ink-bleed.js'; import { saltFlat } from './shader/salt-flat.js'; import { stormRift } from './shader/storm-rift.js'; import { vortexDrift } from './shader/vortex-drift.js'; import { gateCorridor } from './shader/gate-corridor.js'; import { smokeColumn } from './shader/smoke-column.js'; import { cellDivide } from './shader/cell-divide.js'; import { eclipseField } from './shader/eclipse-field.js'; import { girderLattice } from './shader/girder-lattice.js'; import { quasicrystal } from './shader/quasicrystal.js'; import { timeSmear } from './shader/time-smear.js'; import { rainColumn } from './shader/rain-column.js'; import { magnetLines } from './shader/magnet-lines.js'; import { karmanStreet } from './shader/karman-street.js'; import { turingBloom } from './shader/turing-bloom.js'; import { myceliumWeb } from './shader/mycelium-web.js'; import { scaleMosaic } from './shader/scale-mosaic.js'; import { pendulumTrace } from './shader/pendulum-trace.js'; import { contourMap } from './shader/contour-map.js'; import { shojiGrid } from './shader/shoji-grid.js'; import { balanceStack } from './shader/balance-stack.js'; import { suspensionSpan } from './shader/suspension-span.js'; import { stairwellDescent } from './shader/stairwell-descent.js'; import { aqueductMarch } from './shader/aqueduct-march.js'; import { dataAisle } from './shader/data-aisle.js'; import { voronoiShatter } from './shader/voronoi-shatter.js'; import { apollonianGasket } from './shader/apollonian-gasket.js'; import { isometricBlocks } from './shader/isometric-blocks.js'; import { halftoneMisprint } from './shader/halftone-misprint.js'; import { drosteFeedback } from './shader/droste-feedback.js'; import { analogWow } from './shader/analog-wow.js'; /** * The scene library. Families exist so the arc driver can choose by section * character rather than at random — a breakdown never lands on a strobing glitch * scene, and an intro never opens at full density. */ export const FAMILIES = { flow: { label: 'Flow', energy: [0.0, 0.7] }, organic: { label: 'Organic', energy: [0.0, 0.8] }, minimal: { label: 'Minimal', energy: [0.0, 0.45] }, structural: { label: 'Structural', energy: [0.3, 0.9] }, geometric: { label: 'Geometric', energy: [0.4, 1.0] }, glitch: { label: 'Glitch', energy: [0.6, 1.0] }, }; const MODULES = [ nebula, classicWave, floatingGeometry, synthwaveRun, psychedelicDrift, particleField, // Phase 7 additions. 'minimal' came first: with the family empty, intros and // breakdowns fell through to flow/organic and every track opened at density. horizonLines, spectrumSculpture, slowOrb, curlFlow, plasmaBloom, metaballs, kaleidoTunnel, moireGrid, ridgeTerrain, scanTear, blockMosh, neonCity, flora, fireflyDrift, silkRibbon, pylonGrid, prismBloom, pitchShatter, // Ten added to widen the library past the point where a track's rosters // start repeating: the casting rule shrinks the pool per track, so depth in // every family is what keeps two videos from drawing the same four scenes. // Weighted toward the 'space' and 'shape' traits, which were the thinnest // and therefore the signatures most likely to run out of cast. auroraVeil, tideRings, dustChamber, cargoBelt, circuitBloom, truchetFold, signalDecay, inkBleed, saltFlat, stormRift, vortexDrift, gateCorridor, smokeColumn, cellDivide, eclipseField, girderLattice, quasicrystal, timeSmear, rainColumn, magnetLines, karmanStreet, turingBloom, myceliumWeb, scaleMosaic, pendulumTrace, contourMap, shojiGrid, balanceStack, suspensionSpan, stairwellDescent, aqueductMarch, dataAisle, voronoiShatter, apollonianGasket, isometricBlocks, halftoneMisprint, drosteFeedback, analogWow, // Stages. Registered alongside the scenes so every existing gate covers // them; what makes them different is `consumes`, not where they live. procession, constellation, soloist, swarm, ]; const errors = []; for (const m of MODULES) { const e = validateModule(m); if (e.length) errors.push(...e); if (m.family && !FAMILIES[m.family]) errors.push(`${m.name}: unknown family '${m.family}'`); } if (errors.length) { // Fail loudly at import: a malformed scene must never reach the compositor, // where the symptom would be a black frame with no explanation. console.error('[registry] invalid scene modules:\n' + errors.join('\n')); } export const scenes = MODULES; export const sceneErrors = errors; export function sceneByName(name) { return MODULES.find((m) => m.name === name) || null; } export function scenesInFamily(family) { return MODULES.filter((m) => m.family === family); } export function familyNames() { return Object.keys(FAMILIES); } export default scenes;