Song separation went from not computable — the reference sat below the floor — to 0.38, with the floor falling from 0.1449 to 0.1185 and the gap widening. Every structural block moved: scale 40 to 59%, orient 65 to 89%, layout 60 to 111%, texture 89 to 81%, motion 53 to 65% of the reference. Container variety rose throughout, 0.0557 to 0.0815 to 0.0901, which is the stop condition holding: the library did not homogenise as it converged on shared content. Coupling did not move and is recorded as such. Whether a song looks different in proportion to how it sounds different is still unsolved, and the migration was never aimed at it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
249 lines
8.9 KiB
Markdown
249 lines
8.9 KiB
Markdown
# Migrating a scene onto the identity artifacts
|
|
|
|
The recipe `tools/migration-status.js` reports progress against. Sixty-one
|
|
scenes is too many to convert from taste, so this is written to be followed
|
|
mechanically and to produce the same result twice.
|
|
|
|
Background is EPIC-3.md. The one-line version: a scene draws its own content, a
|
|
migrated scene draws the *song's* content — its cast, in its ink, on its lattice
|
|
— and the measured payoff is additive with everything the scene already did.
|
|
|
|
```bash
|
|
node tools/migration-status.js # the queue, and what each scene needs
|
|
node tools/migration-status.js --all # every scene and its tier
|
|
```
|
|
|
|
---
|
|
|
|
## The rule that decides everything
|
|
|
|
> **An artifact is content a scene could not have invented for itself.**
|
|
|
|
If the scene still looks right with the identity switched off, it has used the
|
|
artifact as a modifier and the migration has not happened. That is not a style
|
|
preference — it is the exact failure mode of `u_sigSides`, which thirty scenes
|
|
declare and most quietly ignore, and it is why the gate below exists.
|
|
|
|
---
|
|
|
|
## The four tiers
|
|
|
|
The classifier assigns these from the shader source. Check its answer, don't
|
|
trust it: it reads patterns, and a scene that places elements in an unusual way
|
|
will be misfiled.
|
|
|
|
| tier | what it looks like | consumes |
|
|
|---|---|---|
|
|
| **drawn** | loops over discrete elements at computed positions | `cast, ink, staging` |
|
|
| **figure** | one or a few forms, its own composition | `cast, ink` |
|
|
| **field** | a continuous surface — noise, flow, terrain | `ink` |
|
|
| **treatment** | an effect over an image rather than an image | `ink` |
|
|
|
|
Accents are excluded. They are mostly-empty depth passes, not subjects.
|
|
|
|
---
|
|
|
|
## Recipe: `drawn`
|
|
|
|
The highest payoff and the least invention. Four edits.
|
|
|
|
**1. Declare it.** Add to the module, above `params`:
|
|
|
|
```js
|
|
consumes: ['cast', 'ink', 'staging'],
|
|
```
|
|
|
|
**2. Replace the primitive with the cast.** Whatever the loop was drawing —
|
|
a circle, a box, `sigShape`, a bespoke SDF — becomes one of:
|
|
|
|
```glsl
|
|
float d = castMain(q) * size; // the protagonist: large, few
|
|
float d = castChorus(q) * size; // the chorus: small, many
|
|
```
|
|
|
|
`q` is the element-local coordinate, `(p - pos) / size`. Multiplying the result
|
|
back by `size` restores it to scene units, which is what the ink expects.
|
|
|
|
Use `castMain` when the scene draws a handful of things and `castChorus` when it
|
|
draws a crowd. A scene that draws both should use both — that is what the two
|
|
members are for.
|
|
|
|
**3. Replace the placement with the lattice.**
|
|
|
|
```glsl
|
|
vec3 node = stageNode(fi, float(u_count)); // xy position, z scale multiplier
|
|
vec2 pos = node.xy;
|
|
float size = u_size * node.z;
|
|
```
|
|
|
|
Keep whatever the scene did that was *motion* — a drift, an orbit, a wander, a
|
|
march. Give up what was *composition*. The split is the point: identity owns
|
|
where things are, the scene owns what they do.
|
|
|
|
A scene whose composition IS its identity — a spiral that must be a spiral — can
|
|
keep it and take `stageScale()` alone, which is the song's element size. Declare
|
|
`staging` either way.
|
|
|
|
**4. Replace the edge with the ink.**
|
|
|
|
```glsl
|
|
col = mix(col, pal(i + 1), inkMask(d, uv));
|
|
```
|
|
|
|
`inkMask` does fill, fill treatment (hatch, stipple, halftone), outline and edge
|
|
hardness in one call. Delete the scene's own `smoothstep(soft, -soft, d)` and
|
|
its `sigEdge` — the ink supersedes both.
|
|
|
|
Then wrap the return:
|
|
|
|
```glsl
|
|
return vec4(inkValue(col), 1.0);
|
|
```
|
|
|
|
**Do not** delete `sigCamera`, `sigGrain`, `sigHorizonY` or `sigAir`. Traits and
|
|
artifacts are different layers and both still apply.
|
|
|
|
---
|
|
|
|
## Recipe: `figure`
|
|
|
|
Steps 1, 2 and 4, skipping the lattice. Declare `consumes: ['cast', 'ink']`.
|
|
Take `stageScale()` if the figure has a size worth scaling and add `staging` if
|
|
you do.
|
|
|
|
---
|
|
|
|
## Recipe: `field` and `treatment`
|
|
|
|
There are no elements to replace, so this is one edit plus a judgement.
|
|
|
|
```js
|
|
consumes: ['ink'],
|
|
```
|
|
|
|
```glsl
|
|
return vec4(inkValue(col), 1.0);
|
|
```
|
|
|
|
If the field already dithers, hatches or posterises internally, replace that
|
|
with `inkPattern(uv)` so the treatment is the song's rather than the scene's.
|
|
If it does not, `inkValue` alone is the whole migration — a value structure
|
|
shared across every scene in a video is worth having and costs one line.
|
|
|
|
Be honest about `treatment` scenes. Most of them are effects wearing a scene's
|
|
clothes, and EPIC-3 §8 argues they should move into the identity's EFFECTS
|
|
register rather than compete for screen time as subjects. Migrating one is a
|
|
holding action, not the answer.
|
|
|
|
---
|
|
|
|
## Verifying — the part that makes this reproducible
|
|
|
|
A migration is not done when the code looks right. It is done when the gate
|
|
passes:
|
|
|
|
```
|
|
checks.html?scene=<Scene%20Name>
|
|
```
|
|
|
|
Every artifact in `consumes` must show a passing line:
|
|
|
|
```
|
|
PASS consumes: cast delta 255/255 (floor 24)
|
|
PASS consumes: ink delta 255/255 (floor 24)
|
|
PASS consumes: staging delta 255/255 (floor 24)
|
|
```
|
|
|
|
That check renders the scene twice under two deliberately distant identities and
|
|
requires the picture to change. A scene that declares `cast` and ignores it
|
|
fails here, which is the only reason `consumes` can be trusted at library scale.
|
|
|
|
`npm run lint:scenes` enforces the other half in both directions: declaring an
|
|
artifact without calling it, and calling one without declaring it. The second
|
|
matters more than it looks — an undeclared artifact hides the scene from this
|
|
report and from anything that later selects scenes on capability.
|
|
|
|
Then the usual battery still applies. `renders something`, `animates`,
|
|
`deterministic`, `distinct`, `param sweep`, `flash rate` and every declared
|
|
trait must all still pass. A migration that breaks `distinct` has made the scene
|
|
into one of its neighbours, which is a real risk here: the more scenes share a
|
|
cast, the more two weakly-composed ones converge.
|
|
|
|
---
|
|
|
|
## Measuring the payoff
|
|
|
|
Per scene, the gate. Across the library, two numbers:
|
|
|
|
```
|
|
checks.html?decompose=1 identity against container, measured apart
|
|
checks.html?experiment=1 stages against legacy, with error bars
|
|
```
|
|
|
|
`decompose` is the one to watch. Before the migration, over the four purpose-
|
|
built stages:
|
|
|
|
```
|
|
identity only 0.0299 container only 0.0557 identity = 54% of container
|
|
```
|
|
|
|
After the first eighteen scenes, over the eight that consumed the cast:
|
|
|
|
```
|
|
identity only 0.1287 container only 0.0815 identity = 158% of container
|
|
```
|
|
|
|
Complete, over all thirty-two:
|
|
|
|
```
|
|
identity only 0.1054 container only 0.0901 identity = 117% of container
|
|
```
|
|
|
|
The number that matters in that row is `container only`, which went 0.0557 →
|
|
0.0815 → 0.0901 as the migration progressed. That is the stop condition below
|
|
holding: the library did not homogenise. `identity only` is noisier than it
|
|
looks because the scene it probes is picked from the migrated set and changes
|
|
between runs — Constellation, then Metaballs, then Floating Geometry — so read
|
|
it as "comparable to the container" rather than as a precise ratio.
|
|
|
|
End to end, song variety across twelve songs before and after the epic:
|
|
|
|
```
|
|
floor observed separation
|
|
before 0.1449 0.1508 not computable (the reference sat below the floor)
|
|
after 0.1185 0.1376 0.38
|
|
```
|
|
|
|
The floor fell and the gap widened, which is both halves of what the inversion
|
|
was supposed to buy. Every structural block moved with it — scale 40→59%,
|
|
orient 65→89%, layout 60→111%, texture 89→81%, motion 53→65% of reference.
|
|
|
|
Coupling did not move: -0.09, still indistinguishable from zero. Whether a song
|
|
LOOKS different in proportion to how it SOUNDS different remains unsolved, and
|
|
nothing in the migration addressed it.
|
|
|
|
Read that with its caveat: the stage set changed underneath the measurement, so
|
|
part of the jump is that Metaballs expresses a cast more strongly than
|
|
Constellation did rather than that the migration itself moved anything. What it
|
|
does establish is the thing worth knowing — a migrated LIBRARY scene carries the
|
|
identity better than a stage written from scratch to carry it. The bespoke
|
|
stages were the wrong shape of effort. As scenes migrate, the
|
|
`identity only` number should climb while `container only` holds — because the
|
|
whole point of the correction in EPIC-3 §9b is that these add rather than trade.
|
|
If `container only` falls as scenes migrate, the migration is homogenising the
|
|
library and should stop.
|
|
|
|
---
|
|
|
|
## A caution learned the expensive way
|
|
|
|
This harness's noise floor is large enough to invent findings. Three claims in
|
|
this epic were made from single runs and withdrawn after repeats: that roster
|
|
size was the dominant lever, that stages beat legacy by 14%, and that the
|
|
identity's range was the bottleneck. Anything under about 0.01 of spread needs
|
|
`&repeats=3` before it is believed, and a difference that changes sign with the
|
|
sample size is not a difference.
|
|
|
|
Migrate in batches, measure after each batch, and expect the per-batch effect to
|
|
be inside the noise. The trend across batches is the signal.
|