music-video-gen/flow-state/EPIC-2.md
Dejvino 189328587d Epic 2.4: a slow axis, and an honest account of what it fixes
Eleven scenes changed as much in half a second as in two minutes. The cause
was that everything moving in them was cyclic: ArcDriver's drift is a 20-70
second LFO, and an LFO returns, so ten cycles of it over five minutes is not
five minutes of anything. The eye adapts in about two seconds and then there
is nothing left to find — violently animated, and reads as static.

ArcDriver._slowAxisFor adds the missing timescale: a param that travels ONE
WAY across the whole track, keyed on the module so a scene returning in the
last section arrives further along rather than resetting. Rate params are
excluded for the reason schema.js already gives.

Two things were learned by getting them wrong first, and both are recorded
where the next person will hit them.

The first version chose the param at random from everything eligible and
measured as doing NOTHING — identical structural change with the axis applied
and with it disabled. Which param you move decides everything: sweeping Moiré
Grid's `width` moves its time-averaged structure by 0.110 and its `offset` by
0.002, and a random draw finds the second kind almost every time. So the axis
is now DECLARED, `slowAxis: true`, validated by the schema (and rejected on
rate params, where walking one would jump the animation phase).

The second is that single-frame distance cannot measure this at all. A
churning scene's consecutive frames are already ~0.6 apart, so every pair of
its frames scores the same whether the structure moved or not — the metric is
saturated by the churn it exists to see through, and the first gate passed
while the mechanism was provably inert. The gate now compares TEN-SECOND
time-averaged frames. The window was measured rather than guessed: at one
second Moiré Grid's frozen control still reads 0.024, at ten it reads 0.0095
while the signal holds at 0.037.

Per EPIC-2.md §4, the metric is verified by breaking what it should catch:
a second check runs the identical measurement with the axis disabled and
requires it to read ~1.0. It reads exactly 1.00x on all three scenes.

The honest scope, measured across ten candidates and written into EPIC-2.md
§3.4: the axis works on Moiré Grid (3.93x), Gate Corridor (2.88x) and Truchet
Fold (1.40x). Curl Flow, Signal Decay and Circuit Bloom have NO parameter that
changes their structure and still need shader work — parameter automation
cannot substitute for structure a scene does not have. Four more already
develop on their own and were never the problem. The flag is set only where it
is proven, so it means something.

69/70 on phases 3,4,7-11 slow; the one failure is the known load-dependent
Horizon Lines flake recorded in d14d473.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 07:54:35 +02:00

219 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Epic 2 — from texture generator to something worth watching
PLAN.md took this from nothing to a deterministic engine with a 42-scene library, a
production-design layer, and an editor. Epic 2 is the answer to a different question, asked
after watching the result rather than after building it:
> Is it varied enough per track? Interesting enough to hold five minutes? Pleasant enough to
> watch for long? Is there a unifying factor? Are we reusing the same visuals between tracks?
> Are the visuals active enough?
The honest answers were: partly, no, mixed, yes, yes, and no in both directions. This document
is what those answers turn into.
---
## 1. What was measured
Everything below is a number off the real system, not an impression. The measurements are
reproducible from the check harness and several have become gates (§4).
**Shot rhythm is a metronome.** A five-minute track at 90 BPM gets sixteen shots of 19.3,
18.7, 18.7, 18.7 … 18.7, 19.3 seconds. At 124 BPM it is 15.5/15.5/13.6 repeating. `planShots`
picks a bar count *once per section* and then divides the section evenly, so every cut inside
a section lands on the same pulse. Over five minutes that is the most fatiguing edit rhythm
available.
**The palette is frozen for the whole track.** One `look.palette`, pushed to every layer of
every section for the entire runtime. Colour is the strongest perceptual variable the system
has and it never moves.
**39% of the library does not develop.** Rendering every scene at generated parameters and
comparing frames 0.5 s / 5 s / 30 s / 2 min apart, normalised for brightness:
| behaviour | count | examples (0.5 s → 2 min) |
|---|---|---|
| churn — changes as much in half a second as in two minutes | 11 | Curl Flow 0.81 → 0.75, Moiré Grid 1.42 → 1.40, Circuit Bloom 2.86 → 2.83 |
| nearly static | 5 | Pylon Grid 0.15 at 2 min, Dust Chamber 0.39, Salt Flat 0.64 |
| genuinely evolving | ~25 | Flora 0.29 → 3.25, Silk Ribbon 0.26 → 2.68, Slow Orb 0.09 → 1.21 |
Churn is the more interesting failure. Those scenes are violently animated and read as
*static*, because motion without structure is texture: the eye adapts in about two seconds and
then there is nothing left to find. "Active enough" fails at both ends of the range.
**The most-cast scenes are among the least developing.** Across twelve tracks, four scenes
appeared in ten to twelve of them — Dust Chamber, Slow Orb, Salt Flat, Eclipse Field — and two
of those are in the nearly-static bucket. Twenty-nine of forty-two scenes appeared in none of
the twelve.
**Every track makes the same genre decisions.** `FAMILY_BY_KIND` is a module constant: an
intro is always minimal/flow/organic, a drop always geometric/glitch/structural, and intro and
outro are literally the same list. Cross-track sameness is baked into a table.
**A five-minute track shows five or six distinct images.**
**The production-design layer works and is not in scope for this epic.** Changing only the
personality moves the image 0.846; changing only the parameters 0.882; changing only the
palette 0.467. The unifying mechanism is as strong as the variation mechanism. Leave it alone.
---
## 2. The diagnosis
Two separate problems, and it matters that they are separate:
**The system has no arc longer than fifteen seconds.** It has a per-frame arc (reactivity), a
per-shot arc (cuts), and a per-section arc (drift), and then it stops. Nothing is built to pay
off at minute four. Held attention over five minutes is a property of *structure over
minutes*, and there is currently no mechanism that operates on that timescale.
**Every scene is the same shot.** A locked-off, full-frame, abstract texture. There is no
scale, no framing, no composition — no wide against close, no negative space, no subject the
eye can track across a cut. Music-video attention is held by framing changes at least as much
as by content changes, and the vocabulary for that does not exist here at all.
The first is a set of bugs in disguise. The second is a ceiling.
---
## 3. The work
Five workstreams, ordered by impact per unit of effort. Each is independently shippable and
independently valuable; nothing here is a prerequisite for anything below it except where
noted.
### 3.1 Break the cut metronome
Shot lengths must vary *within* a section. The target is a phrase shape rather than a pulse: a
long hold, two quick ones, a long hold. Cuts still land on downbeats and still respect the
floor and ceiling — this changes where the cuts are, not the rules they obey.
The mechanism is a per-section **rhythm pattern**: a small set of bar-length sequences (`[8,
8, 4, 4, 16]` and friends) chosen by seed and energy, walked in order and repeated as needed,
instead of one bar count divided evenly. A section then reads as edited rather than as
metered.
Two things to preserve, both load-bearing and both easy to break here: cuts must stay on
phrase lines, and `MIN_SHOT_SECONDS` / `MAX_SHOT_SECONDS` remain hard.
### 3.2 A per-track director
`FAMILY_BY_KIND` becomes one of several mappings, chosen per track at look-generation time.
Each mapping is a coherent point of view about what a song looks like — one that answers a
drop with geometry, one that answers it with glitch, one that stays organic throughout, one
that treats the intro as structural rather than minimal.
This is a small change that directly attacks the largest source of cross-track sameness, and
it makes the twenty-nine unused scenes reachable without touching the library or the casting
weights.
### 3.3 Let the palette move
Per-section colour movement, driven by the arc that already exists: a hue rotation, a
saturation shift, or a scheme change into a drop. The track keeps one identity — the movement
is *from* the track's palette, not a replacement for it — so this does not fight §3.5 of
PLAN.md or the personality layer.
Constraint: the contrast floor the Phase 3 palette gate enforces has to hold at every point in
the movement, not just at the endpoints.
### 3.4 Give the churn scenes a slow axis
Each of the eleven needs at least one parameter on a multi-minute ramp, so that the frame at
three minutes is not the frame at thirty seconds. This is per-scene work and it is mechanical,
but it is the only thing that fixes the specific failure of "animated and yet static".
The five nearly-static scenes get the opposite treatment, and Phase 7's movement gate already
describes what "enough" means.
**Status after the first pass: mechanism delivered, most of the scene work still open.**
The mechanism exists and is proven. `ArcDriver._slowAxisFor` walks a declared param across the
whole track, monotonically — the existing drift is a 20-70 second LFO, and an LFO returns,
which is precisely why ten cycles of it over five minutes reads as static.
Two things were learned the hard way and are worth not relearning:
*Which param you move decides everything.* The first version chose at random from everything
eligible and measured as doing **nothing whatsoever** — identical structural change with the
axis applied and with it disabled. Sweeping Moiré Grid's `width` moves its time-averaged
structure by 0.110 and its `offset` by 0.002; a random draw finds the second kind almost every
time. Hence `slowAxis: true` as a declaration rather than a heuristic.
*Single-frame distance cannot measure this.* A churning scene's consecutive frames are already
~0.6 apart, so every pair of its frames scores the same whether the structure moved or not.
The gate measures **ten-second time-averaged** frames, which cancels the churn and leaves the
structure. The window was measured: at one second, Moiré Grid's frozen-parameter control still
reads 0.024; at ten it reads 0.0095 while the axis-driven signal stays at 0.037.
Measured across ten candidate scenes (ratio of axis-driven structural change to what the scene
does on its own):
| works | Moiré Grid 3.93× · Gate Corridor 2.88× · Truchet Fold 1.40× |
|---|---|
| **no parameter helps** | Curl Flow 1.31× · Signal Decay 1.21× · Circuit Bloom 1.10× |
| **already develops; never the problem** | Firefly Drift · Vortex Drift · Kaleido Tunnel · Plasma Bloom |
The middle row is the remaining work, and it is **not** mechanical: those scenes have no
parameter that changes their structure, so they need shader changes that introduce one.
Parameter automation cannot substitute for structure a scene does not have.
### 3.5 A framing layer
The one that raises the ceiling rather than the floor. A shared zoom / crop / scale envelope
sitting above the scenes, so the same image can be played as a wide and as a close, and so a
cut can change the framing without changing the subject.
This is deliberately last: it is the largest change, it interacts with resolution
independence (§1 of PLAN.md) and with the feedback buffer, and the four items above will have
changed what it needs to do.
---
## 4. Validation
The rule from PLAN.md §11 stands: variety is a property of a population, not of one render, so
every gate here samples many tracks and asks about a spread.
| workstream | gate |
|---|---|
| 3.1 rhythm | Within one section, shot lengths must have real spread (no section where every shot is within a few percent of the mean). Cuts still land on downbeats; floor and ceiling still hold. |
| 3.2 director | Across a population, the same section kind must draw from more than one family set. Every scene in the library must be reachable by some director. No director may leave a kind with too few scenes to build a roster from. |
| 3.3 palette | Contrast floor holds at every sampled point of the movement, not just the endpoints. Colour actually moves across a track — measurably, not decoratively. |
| 3.4 slow axis | Every scene's two-minute frame distance must exceed its half-second distance by a real margin. This is the churn number from §1, promoted to a gate. |
| 3.5 framing | Resolution independence survives (the existing dual-resolution diff). Framing changes are visible. Determinism holds. |
Two failure modes are worth naming in advance, because both have already happened once in this
project's history:
- **A gate that measures the wrong thing.** The Phase 3 look-space check was propped up by
grain until grain was removed. Any new gate here should be sanity-checked by deliberately
breaking the thing it is supposed to catch.
- **A change that a gate rewards and a viewer does not.** Cut variety that is *random* rather
than *phrased* would pass 3.1's gate and look worse. The gates are necessary and not
sufficient; the end-to-end watch stays the real test.
---
## 5. Sequencing
1. **3.1 rhythm** — biggest felt change, smallest diff, no dependencies.
2. **3.2 director** — small, orthogonal, attacks cross-track sameness directly.
3. **3.3 palette** — medium, touches the arc driver.
4. **3.4 slow axis** — large but mechanical, eleven-plus scenes, one at a time.
5. **3.5 framing** — largest, and best attempted once the other four have landed.
Each ships as its own commit with its gate green.
---
## 6. What this epic does not do
- **It does not touch the personality layer.** Measured, it works.
- **It does not grow the library.** Twenty-nine scenes are currently unreachable; making them
reachable (3.2) is worth more than adding a forty-third.
- **It does not add audio analysis.** Every mechanism here is driven by features that already
exist in the FeatureTrack.
- **It does not relax determinism.** Every rule in PLAN.md §1 still applies, and the seek /
export / repeat gates are non-negotiable.