# Epic 3 — the song brings its own cast

Epic 2 asked whether the output was worth watching. This one asks a narrower question that
the variety harness has now answered numerically, twice, with the same result:

> Two different songs are about as different from each other as one video is from itself
> five minutes later.

Everything below follows from taking that seriously.

---

## 1. What the measurements actually say

From `checks.html?variety=1` and `?songs=1`, both run against the song bank:

| | seed variety | song variety |
|---|---|---|
| floor — one video against itself | 0.143 | 0.145 |
| observed — two seeds / two songs | 0.145 | 0.151 |
| colour block | 111% of reference | 92% |
| coupling — musical distance → visual distance | — | no signal at n=21 |

Two numbers matter more than the rest.

**The floor is enormous.** A video differs from itself, across its own sections, by nearly as
much as it differs from a video of another song. That is not a subtle failure — it says the
thing we are shipping has no identity. A viewer cannot recognise a video as *this song's
video*, because its own opening and its own drop have less in common than its drop has with
some other track's drop.

**Colour is the only register doing work.** It scores at or above the reference while every
structural register sits below. The generator varies the palette and shuffles which shader
runs; it does not vary *what is on screen*.

The last round of fixes (per-track casting pools, soft signature weighting, motion character)
raised every raw structural distance — motion by 57% — and made all 61 scenes reachable. It
did not close the gap, because it raised the floor by as much as it raised the ceiling. More
scenes reachable means more rotation *within* a video too. Pulling harder on the same lever
will keep doing that.

**The lever is wrong, not weak.** Scene choice is a choice of *container*. Two containers
showing the same nothing look alike, and the same container showing two different things
looks different. We have been varying containers.

---

## 2. The inversion

Today a scene is self-contained. `metaballs.js` knows how to make metaballs and needs nothing
from the track except a palette and eighteen `u_sig*` uniforms it is free to ignore — and
most do, because they are modifiers on an image the shader already had.

The proposal turns that around.

> A song generates an **identity**: a small set of design decisions that produce concrete,
> reusable **artifacts**. Visualizers are **stages** that know how to arrange, draw and
> animate artifacts they are given, and a song picks two to five of them. A stage with no
> artifacts has no image.

The comic-book framing is the useful one. A comic is not held together by its panel layouts.
It is held together by the fact that the same characters, drawn in the same hand, keep walking
through it. Change the panel layouts and it is still recognisably the same comic; change the
characters and it is a different book even if every panel is laid out identically.

Panel layouts are what we have been varying.

The hard rule that makes this work, and the one thing that cannot be compromised:

> **An artifact is content a stage could not have invented for itself.** If a stage renders
> acceptably when the artifact is replaced by a default, the artifact is a modifier and it
> will be ignored exactly the way `u_sigSides` is ignored today.

---

## 3. The five registers

An identity is decided before any stage is cast, in five registers plus a timeline. Each
register is a set of *decisions*; each decision produces *artifacts* that stages consume.

### CAST — who is on screen

Two to four members, each with a role. Roles matter more than counts: a cast where everyone
is equal reads as wallpaper.

- **protagonist** — the form that carries most screen time, usually large and few.
- **chorus** — many small copies of a second form; the texture of the piece.
- **antagonist** — optional, and only present in some identities. The thing that interrupts:
  a form that does not belong to the same family and shows up on transients.

Each member is one of a few *kinds*, and the kind determines what artifact it bakes:

| kind | what it is | artifact produced |
|---|---|---|
| `glyph` | a closed silhouette | SDF baked to a texture: sides, corner radius, elongation, notch depth/count, hollow ratio, aspect |
| `filament` | a line or ribbon | thickness profile along its length, taper, waviness, dash pattern, end caps |
| `field` | an unbounded surface | cell structure (voronoi / reaction / flow / weave), scale, anisotropy, contrast |
| `aperture` | a hole or window — a negative character | the same SDF machinery, used as a mask |

`shape.sides` / `roundness` / `elongation` already exist in `Personality.js` and are the seed
of this. The difference is that today they are numbers a shader may consult, and here they are
baked into an actual distance field that a stage *draws*. A stage that draws elements draws
**this** form, or it does not get to be in this video.

### INK — how they are drawn

The hand. Same cast, different ink, is a different book.

- **weight** — hairline, medium, heavy, variable-width.
- **edge** — hard vector, soft glow, dry brush, chalk, aliased/pixelated.
- **fill** — flat, gradient ramp, hatch, stipple, halftone dots, empty (outline only).
- **outline** — none, thin, heavy, double-struck, offset (misregistered).
- **value structure** — high-key, low-key, two-tone posterised, full range.

Artifacts: a **stroke profile** (a small 1D LUT of width and alpha across an edge), a **fill
texture** (hatch/stipple/halftone tile, generated), and a **value curve** (1D LUT). All three
are cheap textures every stage samples the same way. This is the register that most cheaply
makes two videos unmistakably different, because it changes every pixel of every stage at
once — and unlike a palette swap it changes *structure*, so the harness will see it.

### STAGING — where they are placed

- **lattice** — grid, radial, spiral, scattered (poisson), stacked/strata, horizon-anchored,
  packed (apollonian-ish).
- **scale distribution** — uniform, few-large-many-small (power law), bimodal.
- **depth** — flat, layered parallax, true perspective.
- **occupancy** — how much of the frame is used, and whether the centre or the edges carry it.

Artifact: a **point set** with per-point scale, rotation and depth — generated once per song,
possibly a few hundred entries in a texture. Stages that place things place them *here*.

This is the register that fixes the `layout` block, which has been the weakest structural
number in every run. It is weak because every scene composes itself and they all converge on
"interesting thing, middle of frame".

### CHOREOGRAPHY — how they move

- **locomotion** — drift, orbit, pulse-in-place, march, tumble, swarm, fall, breathe.
- **timing** — continuous, beat-quantised, swung, stuttered, triggered-and-decay.
- **coherence** — do cast members move together or independently?
- **response map** — which audio feature drives which property. This is currently the
  `reactive` block on each scene, decided by the scene author. It should be decided by the
  identity, so a song has *one* idea about what a transient does to the picture.

Artifacts: a **motion path** (parametric curve or short keyframe list) and a **response
table**. The existing `motion.stillness` / `churn` characters are the first two knobs of this.

### EFFECTS — what happens to the whole frame

Mostly exists (`post`, `feedback`, `grain`). Worth adding the comic vocabulary, because it is
event-driven rather than constant and events are what the current output lacks:

- speed lines and impact bursts on transients
- registration offset / misprint on a drop
- screen tone and halftone as a *treatment* rather than as two separate scenes
- panel splits — the frame divided, two stages visible at once
- ghosting and echo tied to the beat grid rather than to a decay constant

### BEATS — what happens when

The narrative layer, and the one that turns a set of decisions into an authored piece:

- **entrance** — the cast does not all arrive at once. The chorus enters on the first build.
- **escalation** — cast count, density and ink weight climb across the track.
- **reduction** — a breakdown drops to the protagonist alone, held, on an empty stage.
- **payoff** — the antagonist appears exactly once, at the biggest moment.

This is the register the current system has nothing at all for, and it is why five minutes
feels long.

---

## 4. What an artifact is, technically

Everything above has to survive contact with a fragment-shader pipeline. Four transport
mechanisms cover all of it:

1. **SDF atlas texture** — glyphs and apertures baked once at load into an R8 or RG16F
   texture. New engine capability: generating and uploading a texture. Modest work; the
   renderer already manages framebuffers.
2. **1D LUT textures** — stroke profiles, value curves, palette ramps. Trivial.
3. **Tile textures** — hatch, stipple, halftone, field structure. Generated procedurally into
   a texture once, then sampled — which also makes them cheaper than computing them per pixel
   per frame, as scenes do today.
4. **Uniform blocks** — point sets, response tables, motion coefficients. The point set may
   want a data texture if it exceeds a few dozen entries.

All four are *data*. None of them is code. That is what makes an artifact reusable across
stages that have never heard of each other, and it is what makes the identity serialisable —
which the editor, the preset system and the check harness all need.

---

## 5. Stages replace scenes

A stage declares what it consumes and what it can express:

```js
export const swarmStage = {
    name: 'Swarm',
    consumes: ['glyph', 'pointSet', 'strokeProfile'],   // hard requirements
    optional: ['fillTile', 'aperture'],
    expresses: {
        locomotion: ['swarm', 'drift', 'orbit'],
        depth: ['flat', 'parallax'],
        scaleDistribution: ['power', 'uniform'],
    },
    params: { /* as today */ },
};
```

Casting becomes a **matching** problem instead of a trait-filter problem: which stages can
express *this* identity's choreography and staging, given the artifacts it produced? That is a
much better question than the one `sceneHonours` asks, because it is about capability rather
than about which traits a scene author happened to list — the accident that put eleven
over-declared scenes in half of all videos.

Two to five stages per song, as proposed, is the right number and worth defending: it is
enough to cut between so the video is not static, and few enough that the cast is recognisable
in all of them. It should also be *derived*, not fixed — a long dynamic track earns five, a
four-minute ambient piece wants two.

---

## 6. Mapping functions

"Song + seed picks one function" is the generalisation of the current `director`, and it is
the right place for it. A mapping function is a *style of interpretation*: given an identity,
how do artifacts bind to stages?

- **literal** — protagonist is drawn plainly and large; one stage per section kind.
- **abstract** — the cast is never shown directly, only as apertures, shadows and negative
  space. Same artifacts, unrecognisable result.
- **escalating** — cast count and ink weight climb monotonically; the video is one long build.
- **antagonistic** — two members are in visual conflict all the way through; stages are
  chosen in pairs that disagree.
- **serial** — each section is a variation on the previous one rather than a cut to something
  new; stages are chosen for adjacency on the library's structural map.

Directors already prove the mechanism works and that it is worth having several. Five
interpretations over a rich identity is a far larger space than five family orderings over a
fixed library.

---

## 7. Why this should move the numbers

Falsifiable predictions, so this can be checked rather than believed:

- **Floor drops sharply.** Two to five stages sharing one cast, one ink and one point set will
  produce probes that agree on `scale`, `texture` and `orient` across a video's own length.
  This is the single largest available win — the floor is currently 0.143 out of an observed
  0.145.
- **Observed rises.** Artifacts are content. Two songs differ in what is on screen, not in
  which of 61 shaders is running.
- **Coupling gets somewhere to attach.** Artifact generation is a natural place to read the
  audio, and unlike trait-weight tilting it produces a *continuous* mapping: a slightly
  brighter track gets a slightly sharper ink, not a different scene.
- **Colour stops dominating** — not by dialling the palette back, but because the structural
  registers finally vary as much as it does.
- **The structural twins stop mattering.** `Tide Rings ≈ Quasicrystal ≈ Classic Wave` are
  twins because they all draw their own generic content. Give them different casts and they
  are different images.
- **The ceiling problem may dissolve.** Three constructions have failed because any reference
  that restricts casting also flattens the reference's own richness. With an identity layer
  there is a much better reference available: *the same song with a different identity*.

---

## 8. What happens to the 61 scenes

The library is real work and most of it survives — but not untouched, and pretending otherwise
would be the way this epic quietly fails.

- **Natural stages** (~20): anything that already places discrete elements — `metaballs`,
  `voronoi-shatter`, `isometric-blocks`, `scale-mosaic`, `firefly-drift`, `floating-geometry`.
  These want a glyph and a point set and mostly have parameters for both already.
- **Natural fields** (~15): `curl-flow`, `turing-bloom`, `mycelium-web`, `plasma-bloom`. These
  become `field` artifact *producers* as much as consumers — a field character can be baked
  from them and then sampled by other stages.
- **Natural treatments** (~10): `halftone-misprint`, `analog-wow`, `scan-tear`, `signal-decay`,
  `time-smear`. These are ink and effects, not stages. Promoting them out of the scene library
  and into the identity is a strict improvement — they are currently competing for screen time
  with actual images.
- **Hard cases** (~15): scenes whose whole identity is one fixed image — `apollonian-gasket`,
  `truchet-fold`, `quasicrystal`. Either they take a glyph as their repeating unit, which is
  usually a small change and a large payoff, or they stay as legacy self-contained scenes with
  a lower casting weight.

Incremental path: artifacts are *optional* at first, with neutral defaults, so every existing
scene keeps working. A scene opts in by declaring `consumes`. The variety harness then answers
the only question that matters — does a video built from opted-in stages score better than one
built from legacy scenes? Measure that on five stages before touching the other fifty-six.

---

## 9. Risks

- **Homogenisation within a song.** Sharing a cast across stages is the point, but overdone it
  makes every shot the same shot. The floor is currently far too high; it is possible to
  overshoot into far too low, and a video with no internal variation is boring in a new way.
  The harness measures both directions, so this is checkable — but nobody will check it unless
  the target is stated as a *range* rather than "lower".
- **The artifact contract is a real API.** `shader-contract.js` and `lint-scenes` need to
  understand `consumes`/`expresses`, and getting that wrong makes every scene harder to write.
  `HOWTO-visualizers.md` is currently a good document because the contract is simple.
- **SDF baking is new machinery** in a renderer that has so far only ever managed
  framebuffers.
- **Migration is the real cost.** Sixty-one scenes is a lot of surface, and a half-migrated
  library where some stages honour the cast and some do not is *worse* than either end state,
  because the ones that ignore it read as the shot filmed somewhere else.

---

## 9a. What the first slice actually measured

Four stages shipped, and the A/B ran: same songs, same instrument, same pool
size, the only difference being whether the scenes draw the song's cast or their
own content.

    arm                              floor   observed   spread
    stages, four of them            0.0801     0.1133   +0.0332
    legacy scenes, four of them     0.0830     0.1122   +0.0292
    the unrestricted generator      0.1058     0.1146   +0.0088

The stages-versus-legacy comparison is the internally valid one — everything
except the content sharing is held constant — and it says the inversion helps by
about 14%. Real, in the predicted direction, and much smaller than hoped.

The third row looked at first like the headline: that a small roster, not the
content sharing, was carrying the improvement. It is not. That arm varies two
things at once — the pool is smaller AND it is the same pool for every song —
and a direct sweep of pool size alone (§ `checks.html?sweep=1`, twelve songs,
three draws each) finds differences of 0.003 to 0.007 against a run-to-run noise
of ±0.003 to ±0.005. Pool size does nothing measurable between 4 and 32.

Two lessons worth keeping. Arms that differ in more than one way cannot be read
as if they differed in one. And this metric's noise floor at seven songs is
large enough to invent findings — anything under about 0.01 of spread needs
repeats before it is believed.

## 9b. Where the difference comes from — and the correction to §5

Two cheap measurements settled what three expensive ones could not.

**The identity is not the bottleneck.** `tools/identity-census.js` over twelve
songs: mean identity distance 0.43, no near-identical pairs, and full coverage
of every decision space — six of six fills, five of five lattices, six of six
protagonist forms, three of three element scales. The songs are handed genuinely
different designs.

**The identity does reach the frame.** `checks.html?decompose=1` holds the stage
fixed and varies only the identity, then holds the identity fixed and varies
only the stage:

    identity only    0.0299     one stage, each song's cast, ink and lattice
    container only   0.0557     one identity, four different stages
    both             0.1101     what the generator actually does
    neither          0.0000     the same thing twice — the noise floor

Identity is worth 54% of what the container is worth, on a noise floor of
exactly zero, and the two compose to more than their sum.

That is the mechanism working, and it corrects the framing in §5. The proposal
there was that a song picks two to five stages — that the identity would
*replace* container variety. It does not. Container variety is still the larger
of the two effects, and identity adds to it rather than substituting for it. A
generator with four stages and a rich identity throws away the 0.056 it could
have had from the library.

So the direction is not "a small set of stages". It is **the whole library
consuming the cast**: keep the sixty-one containers, and make them draw the
song's content instead of their own. The migration in §8 stops being a nice-to-
have and becomes the entire point — and the payoff is additive with everything
the library already provides.

## 10. The smallest experiment worth running first

One artifact, three stages, one measurement. Do not build the whole identity layer on a
prediction.

1. Implement `glyph` only: an SDF baked from `shape.sides/roundness/elongation/notches` into a
   texture, plus the plumbing to bind it.
2. Convert three existing element-placing scenes to consume it — `metaballs`,
   `floating-geometry`, `scale-mosaic` are the least invasive.
3. Generate a look forced to those three stages, and run the seed and song variety tests
   against it.

The prediction is specific: **the floor should drop and the `texture` and `orient` blocks
should agree across probes within a video**, while the between-song distance holds or rises.
If the floor does not move, sharing a cast is not sufficient and the ink register is where the
work actually is — which is worth knowing after two days rather than after two months.
