Ride + corridor showed no corridor: with a room present the viz plays on its screens (radio/window/wall), so a dark model viz on the Ride's dash radio is invisible, and the shared rig was filming it through the room camera grammar meant for another world (directCamera rooms now keep the subject grammar + viz framing). Battery pins switched to combos that visibly read (Chamber + Kaleido, Stage + Assembly); scenario text states the visibility rule so a dead pin can't hide again.
8.7 KiB
8.7 KiB
flow-state — design decisions
The decisions this project has taken, and why. New work must obey them; changing one means updating this file and the gate that enforces it.
Doc map: PLAN.md is the full design, EPIC-2/3/4/5-3D.md are the epic
designs, HOWTO-visualizers.md / HOWTO-variety.md are the scene-author
references, MIGRATION.md is the shader→stage migration recipe.
1. Determinism spine
- The audio is fully analysed before the first frame renders, into a
frame-indexed
FeatureTracktable. Nothing reads a liveAnalyserNode. Preview mapscurrentTime → frameIndex; export counts frames. Same rows, same image. - No wall-clock (
performance.now,Date) and noMath.randominengine/scenes/look/audio/params/actors/venues. SeededRngonly; motion is analyticf(t, seed)— seek === playback, preview === export. dtis constant (1/fps); dropped preview frames hitch, never diverge.- Feedback/ping-pong targets are explicitly cleared; shader programs are
primed (
Engine.prime) before frame-exact rendering — unlinked programs rendered the first ~10 frames wrong on the heaviest scene. - Scene math lives in normalized aspect-corrected coords (
uv,p); onlyu_pixelScalecarries pixels, so 720p preview matches 4K export. - Guarantee is per-machine bit-identical; cross-machine is perceptual. Same-machine allows max channel delta ≤ 1 (GPU load varies one LSB under heavy shaders); real bugs score in the tens.
- Seeks need warm-up (
log(0.001)/log(decay)frames) whenever feedback is on. Section boundaries are NOT exact for free — the compositor's feedback buffer is global and carries across them. Clearing it per boundary would flash every transition; warm-up is the better trade.
2. Lookahead
Offline analysis is what buys anticipation: buildSlope ramps through the
bars before a higher-energy section, so visuals arrive at the drop already
at tension. A causal analyser can never do this. The outgoing layer of a
crossfade keeps its pre-boundary slope — feeding it the new section's
features collapses its ramp in one frame (a visible pop at the edit).
3. Scenes: shader + schema, nothing else
- A scene is a fragment shader (or model build/update) plus a params block. Uniform binding, UI sliders, seeded sampling, arc drift and phase gates are all derived from the schema. No per-scene wiring.
- Six families (
flow geometric organic structural minimal glitch) chosen by section kind — a breakdown never lands on a strobing glitch scene. - Personality traits (
shape camera space style) are a contract: each track is built on 1–2 traits and a scene honouring fewer is never cast. The library intentionally shrinks per track. Declared traits need machine-checked evidence (lint grep + render-delta gate); a dead camera (sigCameraassigned, result ignored) shipped once and is now linted. rate: trueis mandatory for anything multiplyingu_time. Phase iselapsed × rate; modulating a rate jumps phase byelapsed × Δrate— measured as 6 flashes/s, twice the WCAG 2.3.1 ceiling.- Whole-frame luminance never pulses on the beat (3-flashes/s ceiling). Pulse something local; quantise glitches to the bar grid.
- A scene reading
prev()must paint a base image first (black first frames, seek-fragile otherwise).prev()readers can never be grounds. - No contract-uniform collisions, no hardcoded saturated colours — the
look generator recolours through
pal()/palRamp()only. - Loops need constant bounds with an early
breakon the deciding param (// lint: fixed-costopt-out for genuinely fixed work).
4. Continuity: nothing disappears mid-shot
Actors and objects change only at camera cuts:
- Discrete params (
int/bool, plus floats markeddiscrete: truethat drive loop bounds / instance counts) hold constant for the whole cue. No drift, no lookahead ramp, no reactive modulation — those would pop members in/out mid-shot. They still vary per section/variant sample, so drops can be denser than intros; the change lands on a cut. - A discrete param MAY declare
slowAxis: the driver steps its travel on the section-held journey instead of sliding it. - Reactive on a discrete param and (unstated) drift of one are lint errors.
- Venue furniture never hard-toggles on interpolated state: chamber junk
and stage pylons fade (ride-crew pattern); ride roadside density is
arrangement-only. Re-hangs happen only on story jumps, always under a
camera cut (
venueJumpAt); the venue layer itself is persistent and never dissolved (old-room/new-room ghosting read as a glitch).
5. Time structure: cues, cuts, camera
- The arc driver works in cues (one per shot): a shot cut and a section change take the same code path, differing only in transition length.
- Default transition is a slow dissolve (2 bars calm, 1 bar loud, capped at 40% of the shot). Hard cuts only above the energy threshold — on calm material a cut reads as a glitch. Even hard cuts are 2-frame ramps (a 1-frame swap of bright scenes is a flash).
- Framing lives at the shot level (size constant per shot, gaze live); the outgoing shot keeps travelling as it fades — freezing it stops the old image dead before it disappears.
- Sharp transitions cut colour instantly with the image and always reframe (a bigger reframe than a dissolve earns).
- Story variables are staged, not ramped: flat inside a section, blended ~1 bar after boundaries. Moments (arrival/turn/climax/resolution) are found in the audio; the story never overrides the song (bounded bias terms). All scenes share one axis sign so the video travels one way.
6. Colour and variety
- Palettes in OKLCH; hue from spectral tilt (log treble/body ratio — centroid and plain body-fraction both collapsed across masters); both ways round the wheel; directors choose 1–4 palettes, blended in OKLCH across dissolves.
- Per-track temperament (intensity/pace/detail/extremity) moves every param dial so one scene cast in two videos isn't the same footage twice.
- Overlays on 5–55% of stacks, never
normalblend, never above 0.6 opacity — occasionally layered, never permanently cluttered. - A scene varies when the SONG changes what's on screen: draw
castMain/castChorus(formin 3D), size viastageScale(), place viastageNode(). No effect-knobs (glow/grain/chroma belong to post). - Grain is a per-track treatment (mode/cell/rate/mask/chroma, ~45% of
tracks get none); scene
texture: 0..2scales only its share. - Absolute animation speed follows the song: motion bias is
tempo-dominated and every
rate: trueparam scales by per-trackrateScale— slow songs got fast scenes before this.
7. Grounds and composition
surfaceis measured (metadata.json), never declared — nine scenes claimedcanvaswhile painting under a third of the frame.- A ground fills the frame on average and never vanishes under any
identity (worst case counts, not the mean); hollow grounds are
flattened via
groundPersonalityFrom. - Model layers share one
Compositorperspective/depth rig; shader depth stays faked. Venue rooms are furnished with the track's monolith body so the room reads as this track's place. - With a room present the viz plays ON its screens (window/wall/radio),
never composited over it; model viz additionally stand in the shared
world for shared-rig rooms. A
directCameraroom (ride) owns only its own rig — the shared rig keeps the subject grammar, or the viz is filmed through a room camera meant for another world and vanishes.
8. 3D on the same terms
ActorSpecis data, not scene graph (serialisable, hashable); geometry viaactorToGeometryfrom the sameIdentity.formthe shaders march.- Model motion is analytic, instanced, seeded; kit deformation reuses the
song's silhouette params;
castSolidimpostors remain the fallback. - New actors are a file plus a registry entry, gated like scenes.
9. Export and review
- WebCodecs primary, frame-dump+ffmpeg fallback; title card, edge fades, progress/cancel; A/V sync within one frame; survives a real upload test.
- Preview is the primary surface: transport, section jump/loop, live param edit, timeline strip, re-roll + locks, draft/full quality, 20s segment test-renders. Never commit to a full render blind.
- Every phase gate is measured; the battery (6 tracks ambient→hard EDM) is watched end to end — monotony is invisible to automation.
10. History
- Forked from
party-stage, then detached: zero cross-imports. - Corrections kept: boundary seeks need warm-up (not exact for free);
layoutis blind on full-frame fields; aggregate ratios (e.g.separation) swing run-to-run — steer by direct render comparisons.