Ten new scenes, with 'minimal' first: the family was empty, so intros and
breakdowns fell through to flow/organic and every track opened at full
density. Quiet sections now land on a restful family 48/48 times across 24
seeds, 28 of them minimal.
New: Horizon Lines, Spectrum Sculpture, Slow Orb (minimal); Curl Flow
(flow); Plasma Bloom, Metaballs (organic); Kaleido Tunnel, Moiré Grid
(geometric); Ridge Terrain (structural); Scan Tear (glitch).
Four real bugs, three of which the existing gates could not have caught:
1. SHADER PROGRAMS LINK ASYNCHRONOUSLY. three.js uses
KHR_parallel_shader_compile, so draws against an unlinked program render
wrong. The heaviest scene had its first TEN frames differ from every
later render of the same frames. Preview hides this entirely; export
renders each frame once, so those frames would ship broken. Added
Engine.prime() — WebGLRenderer.compile() plus a discarded warm frame —
and the exporter now primes before encoding. Rendering a throwaway frame
and reading it back is NOT sufficient; measured, it left 3-5 frames wrong.
2. Moiré Grid declared a param on u_width, which the shader contract already
uses for stereo width. GLSL redefinition, and the only symptom was a
black frame. Lint now rejects any param uniform colliding with the
contract.
3. Spectrum Sculpture strobed at 4 flashes/s. Two causes: rotation measured
in turns meant bar-crossing frequency was bars x rate (82 bars put a
slow-looking 0.12 turns/s at 10 Hz), and hard band-tier boundaries made
every bar switch band simultaneously. Rotation is now in segment units so
the rate IS the crossing frequency, bands interpolate, and the range is
capped where the flash meter measures zero.
4. Particle Field was being chosen as a primary background despite being
mostly empty by design. Scenes now declare role: 'accent'; those are
never primary and are judged on variance rather than luminance.
Three checks were themselves wrong and were rebuilt: mean-distance metrics
unfairly fail sparse scenes for being tasteful rather than static, so
"animates" and "no duplicates" now use max channel delta.
PLAN.md §1 gains two refinements: programs must be primed before the first
frame, and even same-machine the heaviest shaders vary by one LSB under
differing GPU load — so the per-scene criterion is max delta <= 1 rather
than an identical hash. A real bug scores in the tens there.
Full suite 67/67 across all seven phases. Worst 4K frame 3.8ms,
worst flash rate 0/s, worst determinism delta 1/255. Adds README.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three timescales now stack: per-frame reactivity, per-section seeded LFO
drift, and whole-song scene changes with lookahead. Layer instances are
cached per section and reused across crossfades — rebuilding them per frame
would recompile shaders every transition.
Crossfades run forward from a boundary: the outgoing scene holds while the
incoming one fades in over it.
Three real bugs, each found by a check that had to be rewritten first:
1. A pop exactly at every transition. buildSlope is discontinuous by
construction (~1 before a boundary, 0 after), and the outgoing layer is
still on screen when it flips — collapsing its lookahead ramp in one
frame. It now holds the slope it had entering the boundary.
2. FeatureTrack.at() returns a REUSED row object, and _boundarySlope()
called at() again mid-render, rewriting the features the layer was about
to read. Symptom: a frame correct on every repeat and wrong the first
time — invisible to fresh-vs-fresh comparison, and wrong in every export,
since export renders each frame exactly once. Now indexes the typed array
directly, with the aliasing hazard documented on at(), and a new check
covers the whole bug class.
3. Warm-up converged to 1%, leaving a visible 0.015 difference at heavy
feedback settings. Now targets 0.1%.
Two checks were themselves wrong and were rebuilt: a raw delta threshold
and an outlier-vs-local-median test both flag beat flashes as pops, and a
control window taken from a different scene reads an ordinary busy scene as
a 9x spike. The working formulation A/Bs each boundary against the interior
of the two scenes adjacent to it.
PLAN.md §6 corrected: boundary seeks are NOT exact for free. Layer state is
re-seeded there but the feedback buffer is global and carries across.
Clearing it at boundaries would buy exactness for a visible flash at every
transition; warm-up is the better trade and applies everywhere.
Gate 9/9.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Design document for a new generator that produces full-length, non-story
music videos from a track alone, with no sourced footage.
Core decisions:
- Deterministic engine: audio is analyzed up front into a frame-indexed
feature table, so realtime preview and offline export render from the
same code path and produce the same frames.
- Whole-song analysis enables lookahead, so builds can anticipate drops.
- Scenes declare their parameters in a schema, which drives uniform
binding, generated UI, seeded per-track variation and arc automation.
- Hybrid compositor: fragment-shader and three.js layers in one stack.
- Preview is a first-class surface with section jumping, live param
editing and segment test-renders.
- Forked from party-stage by copy, then fully detached.
Includes per-phase gates and the validation tooling they depend on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>