Commit Graph

235 Commits

Author SHA1 Message Date
Dejvino
3a0cf26e6c The filmstrip fills in as it renders
A full build is a couple of minutes of GPU work, and the page spent all of it
showing an empty screen and then everything at once. The interesting failure —
a strip whose frames could be shuffled without anyone noticing — is visible in
the first row, so waiting for the seventeenth to look at the first is a
needlessly slow way to find it out.

Rows are appended as they land, in bank order, and re-sorted once at the end
when there is finally something to sort by. One row renderer handles both raw
pixels, which is what exists mid-build, and the compressed blobs the cache
holds afterwards, so what you watch appear is what you keep.

The song is announced BEFORE its render rather than only after it, too:
synthesising and rendering one is tens of seconds, and a page that says nothing
until the first one lands looks broken for exactly as long as that takes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 09:02:10 +02:00
Dejvino
89e05459c0 Every shot stands on something, and the frame has two ends
A section used to be one scene, and two thirds of the library is composable —
sparse by design, elements ON something. Cast as backgrounds anyway, they left
9 of 40 sampled frames under 20% painted, the darkest at 0.3%: a minute and a
half of a few bright things on black, invisible to every gate because every
gate on the stack was a limit rather than a floor.

Every section now stands on a GROUND: a canvas that fills the frame, cast per
section kind so a shot cut changes the shot and not the world. When the shot
fills the frame itself it IS the ground — two canvases stacked is two pictures
fighting. Above that, a coverage BUDGET: director appetite times the section's
energy times where the story is, capped at two frames' worth of material.

The measured facts move into the repo. scenes/metadata.json is generated from
the gallery — coverage as a shot, coverage as a bed, variety, the structural
profile — tracked in git, stamped with a fingerprint of the scenes and the
metric definitions, and refreshed from gallery.html. `surface` is derived from
it rather than declared; nine scenes claimed `canvas` while painting under a
third of the frame, and declaring it is now a lint error. The generator weights
every layering choice by measured structural distance, because family labels
and the render disagree: two `geometric` scenes can be 0.31 apart and a `flow`
and an `organic` scene 0.04.

The gallery's 0.1 red line is gone. It was right when a section was one scene
and wrong now — nineteen scenes were failing a bar for being consistent, which
is a virtue in an ingredient.

Chasing the numbers turned up four real faults:

  * A scene that reads prev() cannot be a ground. It returns the whole
    composited frame including the layers above it, so a datamosh under a shot
    is eating it: the render stopped reproducing from a seek and two WebGL
    contexts diverged by 91/255 against a tolerance of 4.
  * Screen was the wrong operator for a shot over a bed. It lightens, so a
    median quarter of every frame clipped to paper and whole sections rendered
    100% white. Replaced by a lumakey — the shot's brightness is its alpha.
  * Feedback was an accumulator: a still image settled at 2.3x its own
    brightness. Fine over black, fatal over a filled ground. Normalised at 0.6,
    plus a highlight shoulder so the top rolls off instead of clipping.
  * useTrack never prewarmed, so a fresh Show's first frame differed from every
    later render of it — the export-breaking hazard Compositor.prime documents.

Blazing is a decision now, not a side effect: directors declare an appetite for
it, a section must be loud and late in the story to earn one, and quiet kinds
never do. The ceiling gate matches that — a hard cap per section, and no more
than a fifth of them hot at all.

Rendered across twelve videos, middle of every section:

    painted        51% mean, darkest 0.3%  ->  87% mean, darkest 43%
    clipped white  24% median, worst 100%  ->   1% mean, worst 30%
    separation     0.10                    ->  0.44

Seven scenes can ground a section — five geometric, two organic — so every
quiet section of every video stands on one of two beds. That is the library's
largest hole and it is scene work: there is no minimal or flow canvas that
fills half the frame without reading prev().

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 09:02:01 +02:00
Dejvino
7d151be6e1 Pylon Grid stands on the chorus instead of on a strut
The legs were the last thing in this scene it had invented for itself: a
fixed-width vertical capsule, the same object in every video, with only the
palette separating one track's structure from another's. Against migrated crowns
they read as scaffolding left up around a finished building — and structure is
half this frame, so half of it was still saying what the SCENE is rather than
what the song is.

A leg is now a stack of the song's chorus. How many members make up a column is
the song's element size deciding it — a track of few huge forms stands on three,
a track of many small ones on a ladder of nine — and how wide the column is comes
from the chorus's own proportions. Each member turns at its own angle, so a
column is one form seen many ways rather than one form printed nine times. That
makes `staging` a third declared artifact, which the lint caught before the gate
had to.

Measured on the crop that contains the change, since the frame-level score
cannot see it: over six identities the bottom third of the frame moves 13.4/255
with the old strut and 22.2/255 with the chorus stacked. The whole-frame variety
score goes 0.1322 to 0.1348, and the header now explains why the two disagree
and why both are right — the crowns dominate a descriptor averaged over the
whole image.

Cost is unchanged in practice: 7.7ms/frame at 4K, 12.4ms with every param at the
top of its range, against a 60ms ceiling. The column's bounding box is two
comparisons for a pixel outside it, which with a hundred and sixty pylons is
most of the budget.

The crown is resolved before the leg now. Resolved the other way round, the
first-wins rule let the column paint over the object standing on it and every
subject was cut off at the waist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:49:29 +02:00
Dejvino
61e0c66f61 Pylon Grid stands the song's object on its pylons
The scene already had the depth axis the solid wants — near rows large, far rows
small, converging on the track's horizon — which made the flat crown the one
element in it drawn without perspective: a sticker facing the viewer on top of a
receding structure. Each crown is now marched, and each pylon turns its own at
its own angle, so a single field shows the form from a dozen sides at once.

12/12 on the gate, 7ms/frame at 4K against a 60ms ceiling. A hundred and sixty
instances stay affordable on the two guards MIGRATION.md now documents, and here
first-wins is the correct occlusion as well as the cheap one, because the rows
are already walked near to far.

IT MEASURES SLIGHTLY WORSE and the scene's header says so: 0.141 to 0.134 over
8 songs x 8 seeds, nearly all of it layout (0.156 to 0.126). Two candidate
causes were tested and both came back flat — keeping the far rows stamped scored
0.132, and running the lit surface through inkPattern scored 0.132 here and
moved Effigy by 0.0009. It is the solid itself: a body always fills its own
silhouette, where a stamped cast form paints anywhere between an outline and a
disc depending on whether the identity is hollow, and that swing was worth three
hundredths of layout variety.

Kept because the score does not measure what was wrong. A crown that ignores the
perspective of the field it stands in is a defect no coverage variance reports,
and the header records the number and the two-line revert for whoever disagrees.

The inkPattern experiment is reverted rather than left in as a plausible-looking
no-op, with its measurements written where the next person will look for them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:24:28 +02:00
Dejvino
0a89676cc8 One scene, wide: songs against seeds
Six thumbnails is enough to rank sixty-eight scenes and far too few to study
one. Worse, six songs at one seed each confounds the two inputs: a scene whose
frames are interchangeable might be ignoring the identity or ignoring the music,
and the library gallery cannot say which.

So a scene's name in the gallery now opens it on a grid — songs down, seeds
across, up to every song in the bank by ten draws. Buttons pick the size and
walk onto a fresh block of seeds, which is a much better answer to "is it flat
or was that ten unlucky rolls" than staring at the same ten. The state lives in
the URL, so every button is also a back button.

Three scores rather than one, and the two new ones are the diagnosis: ACROSS
SEEDS is the same song with a different draw, so low means the scene ignores the
identity; ACROSS SONGS is the same draw against different music, so low means it
ignores the song. They point at different fixes. The closest pair is outlined,
because at 170 cells no eye is finding it.

The first version hashed the song name into the seed so a column would not be
one roll repeated down the grid. It reads better and measures nothing: with the
seed varying on both axes the two scores are the same comparison, and they came
back within 0.001 of each other for every scene tried. A column holds its seed
fixed now, which is what makes it attributable to the music — and the scores
separate, with the seed moving every scene tried more than the song does.

170 cells in 9s. The songs are the slow part and are analysed one at a time so
the count moves, then cached for the session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:16:15 +02:00
Dejvino
89277705c4 Nothing is held for two minutes, not even one visual
planShots skipped cut planning entirely when a section had a single visual to
show — "nothing to cut to" — and the exemption swallowed the ceiling with it.
Adding a scene shifted the casting enough to land a 150 BPM track in that case,
and it held one image for 151.98 seconds against a 22-second limit. Any new
scene could have exposed it; the shot-length gate had simply never met a
single-visual section before.

The premise was wrong rather than the arithmetic. A shot boundary changes the
FRAMING as well as the image, so the same visual filmed again at another size is
a shot, and the ceiling is about how long one image is held rather than about how
many images a section has. Single-visual sections now plan shots like any other.

What the single case still suppresses is the hard cut: cutting straight between
two framings of one image is a jump cut, so those boundaries always dissolve.

Two intermediate versions were wrong in ways worth not repeating: splitting the
section into equal spans put a 23.5s shot past the ceiling once the cuts snapped
to downbeats, and read as a metronome (cv 0.048) to the rhythm check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:16:02 +02:00
Dejvino
144c8c918e The song brings a body, not only an outline
A silhouette is the same picture from every angle, so a scene that turns one is
showing you the same shape rotated. That is the ceiling the cast has been under:
it can be notched and hollowed and it still cannot be walked around.

So the identity generates an ASSEMBLY — two to six parts, each a primitive with
an offset, a scale, a rotation and a boolean op, all under a symmetry. The
symmetry is the load-bearing half: parts unioned at random positions read as
debris, the same parts folded read as designed, and only a designed object is
worth calling a protagonist. Prism parts are the existing 2D profile extruded,
so the solid and the silhouette stay one character rather than two generators
running side by side.

It travels as data like every other artifact: three vec4 rows per part in
u_formPart, plus the scalars. Scenes declare `consumes: ['form']` and get
castSDF3, castMarch, castSolid, castChorusSolid and castLit; with no identity
they fall back to the flat profile extruded, so the helpers are safe to call
unconditionally. The chorus is the same rows with fewer parts and its own
proportions — a relative, not a second generator, and no extra uniforms.

Three scenes carry it. Effigy is new and holds the object still while it turns.
Floating Geometry and Swarm were already loops of stamps and are now loops of
bodies; Swarm is what the chorus solid exists for. That is 34.8% of videos
containing a 3D cast, against 11.9% when only Effigy had it.

Measured, the outline does change rather than merely spin: over one turn the lit
area of Effigy's subject varies 14-113% against Soloist's 3-51% for the same
rotation. Whether that reaches the variety blocks is not yet measured, and
HOWTO-variety says so rather than claiming the win.

Four costs, each found by measuring rather than by reading:

  * every pixel evaluated every instance's field — Swarm at 59ms/frame against a
    60ms ceiling. Bounding-sphere reject first, now 12.3ms.
  * instances overlap several deep at the top of the size range, and marching
    all of them made Floating Geometry's own gate run for minutes. First-wins
    instead of last-wins, which was arbitrary either way.
  * a normal inside the march loop multiplies four copies of the SDF by the step
    count, because GLSL unrolls a fixed bound. Hoisted out.
  * the helpers in the shared preamble made all 68 scenes compile what 3 of them
    call. FORM_PREAMBLE is appended per scene instead.

The lint's backtick check was green through two of my own breakages: quoting a
name in a doc comment adds backticks in PAIRS, so parity survives and the
pair-scanner just re-partitions the file. It now finds where a shader literal
opens and requires the next backtick to be a real terminator — which
immediately found a second stray pair.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:15:49 +02:00
Dejvino
518e9ea731 Epic 4: record why direction was negative, and what is left
The first measurement offered three readings for a direction of -0.14. The
third one — the arc is not reaching the image — was correct, and it is now
+0.24 against an arcless reference of -0.02, with the floor rising exactly as
§8 predicted.

Records what the two dead channels were, since neither was visible from the
source and both passed every gate that existed: the overlay path had an empty
candidate set (0/144 stacks), and the camera moved the frame by a median of
0.029 of a half-frame at a random angle so successive shots cancelled. Both
cleared their checks because every bound on them was a ceiling. A gate on a
device needs a floor.

Also ranks what remains, with the broken seed-variety instrument first: three
of the four open items are measurements and they all report through it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 06:58:27 +02:00
Dejvino
1ca9fa2f1f Let scenes layer on each other, and give the camera somewhere to look
Two devices that were built, wired up, and doing nothing.

OVERLAYS. buildStack gated layering on `surfaceOf(m) === 'composable'`, and
no scene in the library declared `surface` — so the only thing that could ever
sit on top was the one scene declaring `role: 'accent'`, and the overlay roster
excluded accents by construction. Empty intersection, every time: 0/144 stacks
carried an overlay. Every layered frame in every song was the same particle
field.

Labelled the library from the phase 12 coverage gate rather than by eye: the
37 scenes painting under 30% of the frame are composable. Particle Field loses
its privileged slot and becomes one of them, keeping only `background: false`,
which is the honest part — points in empty space cannot carry a section alone.
The reserved accent slot is gone; one roster, two passes at it. 1 distinct
overlay scene becomes 27, and the rate lands at 34% of stacks after trimming a
base chance that had been tuned while the branch was dead.

THE CAMERA. framing.shift moved the frame by a median of 0.029 of a half-frame
at a fresh random angle every shot, so successive offsets cancelled and the
median jump at a cut was 0.014. Present in every frame, visible in none.

look/Camera.js is the director's camera department: the story says tension,
order and which act a section is in, and this turns that into where the frame
looks and how it travels there. Each director names a camera. Jump distance
follows tension and act, speed follows energy, and the curve is one of four.
Cuts choose between reframing and matching, so two scenes can still read as one
place. Median offset 0.190, median reframe 0.135, and 94% of shots now move
during the shot rather than only at the cut.

Four new gates, each with a FLOOR — the device this replaces passed every
existing check while doing nothing, because the only bound was a ceiling.

Also lands the in-progress Epic 4 story layer it builds on: Story.js, phase 13,
and the direction statistic.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 06:48:13 +02:00
Dejvino
eda8ac3810 Composable vs canvas: say which scenes are meant to sit on top of others
The top of the gallery is full of black frames with a bright element on them.
That is genuinely varied and thin to watch, and the reason it ranks is that the
gallery previews every scene ALONE — which is not how anyone watches one.

Measured, the split is not marginal: 37 of 66 scenes paint under 30% of the
frame, and the sparsest are exactly the ones topping the ranking. Storm Rift and
Pendulum Trace paint 0%, Droste Feedback and Spectrum Sculpture 2%. At the other
end Block Mosh covers 98% and Scan Tear 96%.

So a scene now declares a `surface`. A canvas fills the frame and belongs
underneath; two of them stacked is two pictures fighting rather than one picture
with depth. A composable scene is mostly empty by design and reads as elements
ON something. The distinction was implicit in `role: 'accent'`, which marked the
depth passes and said nothing about the sixty scenes treated as interchangeable
backgrounds while half of them were sparse.

Verified rather than trusted, in the pattern the rest of the project uses: a
gate renders every scene and measures what it actually paints, failing a
composable that covers more than 55% — it would hide whatever it sits on — and a
canvas that covers less than 8%, which is a canvas in name only. The gate also
prints the whole coverage table, since that list is how the library gets
labelled in the first place.

Casting uses it: only composable scenes are drawn as overlays, and because what
goes on top is now guaranteed to leave the shot underneath visible, layering
happens far more often — the overlay chance went from 0.12 to 0.3 at its base.

The gallery shows coverage and surface beside each score, sorts by sparsest, and
says in the header why the two numbers explain each other.

Nothing is labelled `composable` yet. Every scene keeps the behaviour it had
through `surfaceOf`, which defaults to canvas unless the scene is an accent, so
this commit changes the mechanism and not the output. The coverage table is what
the labelling should be read off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 18:20:24 +02:00
Dejvino
c099501ead Raise the variety bar to 0.1
Deliberately a target rather than a description of where the library sits. Of
the scenes measured since the region block went in, only Droste Feedback at
0.324 is comfortably over; Plasma Bloom is 0.082, Voronoi Shatter and Apollonian
Gasket 0.062, Moiré Grid 0.047. Most of the library fails this, which is the
point — a bar set where the work already is measures nothing.

Third value in two days, and both previous moves were instrument corrections
rather than changes of mind: the motion block was reading zero for the entire
library, and adding the region block made the total a mean over six blocks
instead of five. Both raised every score, so 0.04 had quietly drifted down
relative to the thing it was judging. Documented as something to re-read off a
fresh gallery whenever the descriptor changes, rather than a number to carry
forward.

The marker now sits at the middle of each meter and the grades land where they
should: 0.047, 0.062 and 0.082 all red, 0.140 amber, 0.324 green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 18:11:33 +02:00
Dejvino
b879abe230 A region block: how differently the parts of a frame behave from each other
The descriptor could say where a frame's energy sat and not whether one part of
the picture was doing something different from another — and that second thing
is what "nothing to watch" means. A pattern spread evenly over the screen has no
subject precisely because every region is the same region, and `layout`
normalises that away: a uniform field and a field with a form standing in it can
come out at nearly the same distribution.

So each ninth of the frame is now characterised in its own right — how much
detail, which way it runs, how many elements — and expressed as its deviation
from the frame's average. Heterogeneity rather than level, so a brighter or
busier frame does not register as a more varied one.

It is immediately the strongest block in the descriptor, and it credits scenes
the old one was underrating:

    Apollonian Gasket  0.039 -> 0.062   region 0.180
    Plasma Bloom       0.053 -> 0.082   region 0.182
    Moiré Grid         0.037 -> 0.047   region 0.097
    Droste Feedback    0.219 -> 0.324   region 0.446

Apollonian Gasket is the interesting one. It was called out as looking good and
scoring badly, and the answer turns out to be partly that the instrument was
missing the axis it is good on rather than that beauty and variety are simply
different things. Both were true; only one of them was the instrument's fault.

The metric's own gates still hold, which is the condition for believing any of
this: recolour moves structure 0.019 while moving colour 0.723, a quarter turn
moves it 0.0000, and the same scene against itself is 0.0000 against 0.0992 for
two different scenes.

EVERY SCORE HAS MOVED. The total is a mean over six blocks now rather than five,
and the 0.04 bar was derived under the old one. It needs re-deriving from a fresh
gallery before it is used to judge anything.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 18:07:11 +02:00
Dejvino
8defef3ed0 Tell a dead shader apart from a boring scene in the gallery
A failed shader compile renders black, and black frames are identical to each
other, so every block scores 0.000 and the row reads as the least varied scene
in the library rather than as a broken one. That happened yesterday — the
subject helpers were declared above the ink they call, GLSL has no forward
declarations, the whole preamble failed to compile, and the gallery reported a
variety of exactly 0.000 with no indication anything was wrong. It is a
particularly bad failure mode for a tool whose entire job is ranking scenes by
that number.

A scene whose first frame has no luminance and no variance is now reported as
broken, with both figures and a pointer to the console, and the row is coloured
apart from the merely flat ones so it cannot be mistaken for a bad score. The
summary counts them separately.

Verified against both answers rather than only the happy one: a healthy Moiré
Grid comes back 0.0365 with no error, and a deliberately broken copy of the same
scene comes back with the diagnosis instead of a zero.

Recorded in HOWTO-variety.md alongside the other instrument traps, since the
general lesson outlives this instance — a broken render produces the most boring
possible numbers rather than an error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:55:16 +02:00
Dejvino
c950f8f3b1 Give a full-frame field a subject the eye can track, and let the song choose
what it does

"One pattern across the screen" scenes have nothing to watch. Warping the field
around a focal point was the previous attempt and it did not fix that — measured
on Voronoi it raised orientation variety while layout stayed at 0.004, which is
another way of saying the cells changed and the frame still had no subject.

So a subject is now a form that does something TO the field, and which thing is
the song's decision rather than the scene's: shift, warp, punch, morph or
overlay. The same form punching a hole, bending the pattern or running it at
another rate are three different videos, and across six songs the bank picks
overlay, morph, shift, morph, warp and punch — so it is a real axis rather than
a constant with five names.

subjectSDF, subjectMask, subjectEdge and subjectWarp are in the contract, so any
field scene can take a subject without knowing where the focal points are.

Moiré Grid is the pilot and implements all five impacts. 0.031 to 0.037 — up a
fifth, and the layout block moved from essentially nothing to 0.018, which is
the first time anything has moved layout on a full-frame field. That is the
number that speaks to the complaint: the frame's energy is no longer spread
evenly, so there is somewhere to look.

It is still under the 0.04 bar, and I have not seen it. The score says there is
now a subject; whether it is worth watching is the question the gallery cannot
answer.

One bug worth recording: the subject helpers were placed above the ink in the
preamble, and subjectEdge draws with inkStroke. GLSL has no forward
declarations, so the whole preamble failed to compile and every scene using it
rendered pure black — which the gallery reported as a variety of exactly 0.000
across all six blocks rather than as an error. An all-zero row means a dead
shader, not a boring scene.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:52:57 +02:00
Dejvino
10eeddcf6d A guidance document for building visualizers that vary
HOWTO-visualizers.md gets a scene working. Nothing said how to make one look
different from song to song, which is the thing the library is worst at and the
thing every measurement this epic has been about.

Written as evidence rather than advice: each claim carries the number behind it,
so a later change can contradict it. Several already contradict things believed
earlier in the same week.

It records the failures at least as carefully as the wins, because they were more
informative and each looked obviously right beforehand — effects as parameters
inflating a score without changing a picture, concentrating disturbance instead
of adding it, the roster-size theory that a direct sweep found to be nothing, and
reading a form as a metric rather than drawing it. It also documents what the
descriptor cannot see, since half of "why is my score low" is there: brightness,
colour, rotation, quality, and layout for anything that fills the frame.

And it is explicit about which numbers to trust. Direct render comparisons have a
noise floor of zero; aggregate ratios swing enough to have produced four
withdrawn conclusions in one epic.

Ongoing by design, with the open questions listed: coupling has never moved off
zero, fixed-geometry scenes have no known route to variety, and the 0.04 bar was
set against scores that were depressed by a measurement bug.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:45:38 +02:00
Dejvino
5a9dfa6b2b A scatter point in Isometric Blocks — which measures worse, twice
Asked for, built, and honest about the result: the focal point costs this scene
a quarter of its variety and I could not make it pay for itself.

    uniform scatter, no focus     0.0490
    focus, trading scatter for it 0.0374
    focus, added on top of it     0.0365

The first version spent the scatter budget near the focus and calmed the rest,
which put most of the field back on the rigid lattice and took the orientation
variety with it, 0.164 down to 0.105. That diagnosis looked right, so the second
version made the focus additive — every cell keeps its full scatter and the ones
near the point get more. It changed nothing: 0.0365, orientation still 0.104.

So the diagnosis was wrong and I do not have the real one. The remaining suspect
is the height modulation, which lifts blocks toward the focus and may be
flattening the skyline's variety between songs by dominating it — but that is a
guess, and two guesses have already been wrong here.

The feature is committed rather than reverted because the metric does not
measure what was asked for. A scatter point is a compositional idea, and the
gallery scores how much a scene changes BETWEEN songs, which is a different
question — Apollonian Gasket makes the same point from the other direction. This
may well look better and score worse. It needs eyes on it before the number
decides.

`gather` is a parameter and seed-driven like any other, so the amount varies per
song as asked; the focal points themselves come from the identity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:42:55 +02:00
Dejvino
b1e58a6603 A focus: somewhere for a full-frame field to be about, tried on Voronoi
Edge-to-edge textures have no composition to vary. Two songs of Voronoi Shatter
measured 0.004 apart in the layout block, and that was honest rather than a
metric failure — the cells changed and the arrangement did not, because there
was no arrangement.

So the identity now decides a FOCUS: one to three points, a reach, and a pull
that either draws the field in and densifies it or opens a void. The points come
off the same lattice everything else is placed on, so a scene using them is
composing in the song's terms rather than inventing a centre of its own.
focusField and focusWarp are in the contract, available to any field scene that
wants somewhere to be about.

Voronoi Shatter is the pilot. It tiles in the warped coordinate, so cells crowd
toward the focus or pull away from it, and its seams tighten where the field
gathers so the effect reads as a change in the shatter rather than a brightness
blob laid over one. 0.0463 to 0.0588, comfortably clear of the bar.

It did NOT work the way the hypothesis said. The gain is in orientation, 0.140
to 0.194, and in texture; the layout block moved from 0.004 to 0.007, which is
still nothing. Warping where the cells sit changes what they look like without
changing where the energy is, because the field still covers the frame corner to
corner. Layout stays blind to this family until a field is allowed to actually
fall away from its focus and stop being full-frame — which is a bigger change to
what these scenes are, and worth deciding rather than sliding into.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:39:35 +02:00
Dejvino
8d9b8a593a Fix a permanently-zero motion block, and let the song arrange Isometric Blocks
Investigating three pieces of feedback turned up a measurement bug underneath
all of them. The gallery built four of the descriptor's five structural blocks
and never rendered a second frame, so `motion` came back 0.000 for every scene
in the library and dragged every score down by a fifth. It read as a property of
the scenes; it was a missing render. Every number in the gallery so far,
including the ones the 0.04 bar was calibrated against, was depressed by it.

    Plasma Bloom      0.0532 -> 0.0624
    Voronoi Shatter   0.0431 -> 0.0463
    Apollonian Gasket 0.0340 -> 0.0388

The Plasma Bloom versus Voronoi Shatter oddity is real and is the metric's
limit rather than a mistake. Plasma Bloom is a centred subject whose position
moves between songs, so its layout distance is 0.096; Voronoi Shatter is
edge-to-edge cells, and a full-frame texture has the same layout however its
cells fall, so its layout distance is 0.004. The descriptor cannot see "the
cells are different" as composition, and for full-frame work the layout block
contributes almost nothing. Worth knowing before the bar is used to judge that
family.

Apollonian Gasket scoring low with good-looking frames is the gallery working:
it measures how much a scene changes between songs, not how good it looks. A
scene can be beautiful six times and identical six times.

Isometric Blocks gets the displacement it was asked for. Every block sat exactly
on its lattice slot, so the plan of the field was the same plan in every song
and only the heights moved — a layout distance of 0.005. `scatter` steps each
cell off its slot by a fixed amount of its own, bounded under half a cell so
blocks do not cross and break the isometric read, and it drives the block's
height and side wall as well as its ground position. 0.034 to 0.049, clearing
the bar, with orientation variety nearly doubling as the rigid lattice softens.

Its layout distance stayed at 0.004, for the same reason Voronoi's did: the
field fills the frame either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:32:30 +02:00
Dejvino
71ddd37711 A minimum variety bar in the gallery, drawn as a red line
A visualizer that looks the same wherever it appears does not just fail to vary
— it leaks between songs, because it is cast into many of them and the viewer
who watches two videos ends up recognising the shot rather than the song. That
makes the floor a property of the scene, so the gallery now states it and the
scene has to clear it.

0.04 to start with, and honestly a starting bar rather than a derived one: set
where the flat cluster measurably sits, to be raised as scenes are lifted to it.

Drawn twice, because the two readings are different questions. Every meter
carries a red marker at the bar, so a single row can be judged on its own. And
in the default sort the list is cut by one red line where the scores cross it,
which answers the browsing question — everything above that line repeats itself
between songs.

Also restored progressive drawing, which the caching rewrite had quietly lost:
every draw had moved to the end of the build, leaving three minutes of spinner
and nothing to look at. The first rows are the interesting ones, since the sort
puts the repetitive scenes on top.

Verified by seeding the cache with rows spanning the bar and reloading: four
below, four above, the line between them, the marker at 20% of each meter, and
the grade colours switching at the right place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:29:09 +02:00
Dejvino
e2e5104646 Take the effects off the visualizers and leave them to the grade
Forty-nine scenes carried their own `glow`, every one of the sixty-five added
its own grain, and a handful had bloom, chroma, haze, smear or trail besides —
all of which the post chain already does, with an envelope and a threshold no
scene can see. Two uncoordinated effect layers is the smaller half of the
problem. The larger half is that an effect knob sampled per song makes a scene
look varied across parameter draws while its structure never moves, so the
gallery and the variety harness were both being told a scene was original when
only its halo had changed.

Removed in three shapes. Scene grain goes entirely — the post chain owns it.
Statements that ADD a falloff term scaled by the effect are bloom by another
name and are deleted. What remains is an intensity on something already drawn,
so the uniform is pinned to its default and the knob deleted: the picture
survives, the fake variety does not. Reactive entries driving those params went
too, since an effect modulated by the audio was the most convincing fake of the
lot.

That broke the style trait, which is the interesting part. Surface treatment had
been leaving the scenes for two commits — sigGrain to post, sigEdge to
inkStroke — and removing the last of it left `style` with almost nothing to
express: a style+shape signature had two eligible scenes in the whole library.
The trait is not obsolete, it has moved, so inkStroke and inkMask now read
u_sigLine and u_sigSoft alongside the ink's own weight and edge. A scene drawing
in the song's hand honours the track's line weight by construction, and the
runtime probe confirms it rather than taking it on trust. This reverses a call
made two commits ago for a reason that only became true now.

One check needed re-aiming rather than fixing. Two independently built shows are
two WebGL contexts, and engine/hash.js says at the top that bit-exactness is a
same-context guarantee; the check had been demanding it anyway and getting away
with it because the scenes it happened to cast were bit-stable. A casting change
put smooth-gradient scenes in frame and the last bit moved. Measured before
touching the check: three levels out of 255 across 2.6% of pixels, invisible.
The cross-context comparison is now a distance with a ceiling of four, and
bit-exactness within one show is still demanded by the check below it.

89/89 checks, 11/11 tests, all static gates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:21:34 +02:00
Dejvino
e90951c277 Cache the gallery against a fingerprint of the source that built it
Three minutes of GPU work is fine once and intolerable on every page load, so
the gallery now builds once and comes back from IndexedDB after that.

The key is the part worth getting right. A cache you clear by hand is worse than
no cache: it will eventually show last week's pixels while you are judging this
morning's change, and it will look like a render, so you will believe it. Here
the key is a fingerprint of every file under src/, collected through
import.meta.glob so it covers the shaders, the identity, the look generator, the
engine and the descriptors without naming any of them — the file that
invalidates a render is exactly the one nobody remembers to list. Edit anything
and the entry stops matching and the page rebuilds without being asked.

Thumbnails are WebP blobs rather than raw pixels: sixty-five scenes at six
frames of 256x144 is 57MB raw, and measured, the compressed form is 4.1MB for
the whole library. Saving a build evicts every other one, so the store cannot
grow without bound.

The spinner is a CSS animation on purpose. Building blocks the main thread in
bursts and a JS-driven spinner would freeze mid-turn, which reads as a hang
exactly when the page most needs to look alive.

Verified rather than assumed, six behaviours: the fingerprint is stable across
calls; a build round-trips through storage and decodes to a real image (51%
non-black); a different fingerprint misses; saving a new key evicts the old; and
appending one comment to one scene file moved the fingerprint from 125-f842b05e
to 125-c056d7b9, which is the invalidation working end to end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 07:19:22 +02:00
Dejvino
c6d96c9d40 A gallery, and a debug index to reach it from
Sixty-five visualizers, six renders each, one per song — each carrying that
song's whole identity: its cast, ink, lattice, palette and a fresh parameter
draw. Side by side, a scene that cannot be changed by the song is obvious in a
way no aggregate could show, which is the complaint this answers.

It scores as well as shows. Each row carries the mean structural distance
between its own six frames, on the same descriptor the variety harness uses and
with colour excluded, so six palettes cannot disguise one image. Sorted
least-varied first, because browsing sixty-five scenes hunting for the
repetitive ones is precisely what a sort order should do for you.

The result names names. Thirteen scenes barely change across six songs, and they
fall into two groups that were already known separately. The ink-only
migrations — Analog Wow, Halftone Misprint, Pitch Shatter, Block Mosh, Scan
Tear — are the shallow tier flagged in MIGRATION.md, where the whole migration
was one posterisation. And Moiré Grid, Isometric Blocks, Quasicrystal, Truchet
Fold, Voronoi Shatter and Apollonian Gasket are the structural twin cliques the
library sweep found weeks of measurement ago, arriving here by a completely
different route: the sweep compared scenes to each other, the gallery compares a
scene to itself, and they agree on the same offenders.

debug.html collects the tools, since there are now enough of them that knowing
which to open is its own problem. It also carries the two things a newcomer
would otherwise learn the hard way: which measurements are safe to steer by
(direct render comparisons) and which are not (aggregate ratios), and what to do
when a page renders perfectly and does nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 07:13:54 +02:00
Dejvino
4217cca3d3 Rename clicktrack.js — a content blocker was eating it and killing the app
A file called `clicktrack.js` matches the click-tracking telemetry patterns that
EasyPrivacy and similar lists block by substring. In a browser with a content
blocker the request never completes, and because it is a module import that
takes the entire graph with it: main.js never runs, no handler is ever bound,
and every control in the UI sits there looking correct and doing nothing.

The failure is unusually expensive to diagnose because everything else looks
healthy. The dev server returns 200 with the right MIME type, curl fetches it
fine, node imports it fine, and the app loads perfectly in any browser without a
blocker — which is how it passed every check here. Only the console names it,
and only as one line about a module that failed to load.

Renamed to metronome.js, which is also the better name for what it does. The
button id went with it, since cosmetic filter rules can hit ids too.

The general rule, recorded at the top of the file: anything shipped to a browser
and named like tracking will be treated as tracking. Avoid click, track,
analytics, pixel, beacon and ad in filenames and URL paths, however honest the
code behind them is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 07:01:23 +02:00
Dejvino
4267c588aa Fix two checks the migration broke, and re-aim the signature gate
Phase 2 kept its own list of contract uniforms and never learned about the
identity artifacts, so Prism Bloom reading u_castSides looked like an undeclared
uniform. It now takes the list from the contract itself.

Phase 9 demanded that every cast scene honour the track's signature, which was
true when the signature was a hard filter and stopped being true when it became
a weight. The check was asserting the old contract against the new generator.

Rather than delete it, it now checks the claim that actually matters. A track may
reach outside its signature — that change was made deliberately, because the
filter was disqualifying a third of the library and funnelling eleven scenes into
half of all videos — but it must still LEAN on it. Honouring scenes have to
carry the majority of the cast against a chance baseline near 25%, and section
anchors, which open a section and return most often, have to honour it almost
always.

89/89 checks pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 06:55:28 +02:00
Dejvino
df1ce9a957 Withdraw the separation figure: it is a ratio of two small differences
Three runs of the post-migration song variety measurement give separation 0.314,
0.555 and 0.065 — mean 0.311, half-range 0.245. The 0.38 quoted from a single
run was meaningless.

The cause is structural rather than a sample-size accident. Separation is
(observed - floor) / (ceiling - floor), and here the numerator is 0.0089 while
floor and observed each carry an error bar of 0.015 to 0.019. Dividing one small
difference by another amplifies the noise in both. The metric cannot resolve the
thing it was built to report.

What survives: the ceiling now sits above the floor on every run, with zero
variance, where before the epic it landed underneath and the ratio was not
computable at all. That is a change in kind and it is solid. The magnitude is
not.

The decomposition is the measurement to steer by — noise floor of exactly zero,
effects an order of magnitude above any drift — because it compares renders
directly instead of dividing differences of aggregates. Documented as such.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 06:48:38 +02:00
Dejvino
c3b67f45bd Record the migration's end-to-end numbers
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>
2026-08-18 06:45:59 +02:00
Dejvino
572b090b83 Fix two scenes that were failing their gate before the migration touched them
The library-wide gate run left two failures after the migration was complete,
both of the shape "renders nothing at the bottom of a param range". Running the
same gate against the pre-migration source shows them failing identically, so
the migration did not cause either — it exposed them. The per-scene battery is
opt-in and nobody had ever run it across all sixty-five at once.

The defect is the RANGE, not the shader. Spectrum Sculpture at radius 0.15 is
too small to register and Circuit Bloom at grown 0.25 has no pads yet, and both
values are ones the generator is free to sample. Floors raised to where the
scene actually draws something.

All sixty-five now pass the full battery: renders, animates, deterministic,
distinct, param sweep, flash rate, every declared trait and every declared
artifact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 06:44:04 +02:00
Dejvino
2d3ed8eb02 Finish the migration: all 65 scenes on the identity artifacts
Two helpers made the bulk of it mechanical. `inkStroke` is a drop-in for
sigEdge — the same line at the identity's weight rather than the track's — and
`castForm` is a drop-in for sigForm, same signature so call sites do not change
shape. With those in place the substitution table is one-to-one:

    sigForm(  -> castForm(     sigShape( -> castMain(     sigEdge( -> inkStroke(

Forty-seven scenes went through that pass in one run: twenty-six take the cast
and the ink, twenty-one take the ink alone. Then the gate ran over all sixty-five
and found three the pass had broken, which is the entire reason it exists.

Spectrum Sculpture rendered pure black. It had been using sigShape as a RADIAL
METRIC rather than drawing it, and the cast carries notches and a hollow — an
annulus used as a radius turns a sculpture inside out. Reverted to sigShape and
dropped to ink only. The lesson generalises: a scene that reads a form as
geometry is not a scene that draws it, and the classifier cannot tell those
apart from the source.

Eclipse Field stopped honouring its `style` trait. It opts out of surface grain,
so sigEdge was its only style evidence, and the ink replaced it. The trait claim
is now dropped — and so is the lint change that had let inkMask count as style
evidence, which was wrong and was hiding exactly this. A trait is a property of
the track a scene may honour; an artifact is content it draws. Taking the ink
says nothing about whether a scene responds to u_sigLine.

Circuit Bloom went empty at the bottom of its `grown` range, where the pads were
carried by a hairline and the ink's stroke is thinner than the edge it replaced.
Now filled as well as stroked.

Also: the backtick check now covers every shader literal rather than only the
preamble, because a mechanical pass over sixty files reintroduced one
immediately. Three rounds lost to that typo is enough.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 06:39:19 +02:00
Dejvino
e5bb7d77e0 A reproducible migration, and the first eighteen scenes through it
The library is sixty-one scenes, which is too many to convert from memory or
from taste, so the migration is a queue with a gate rather than a judgement call
per file.

Three pieces. A classifier reads each shader and assigns a tier from evidence in
the source — drawn, figure, field, treatment — so two passes over the library
reach the same answer and the work has an order. MIGRATION.md is the recipe per
tier, written to be followed mechanically. And a gate makes the result
verifiable: `consumes` is now a schema field, the lint enforces it in both
directions, and the per-scene battery renders each scene under two deliberately
distant identities and requires the picture to change.

That gate is the part that matters. Without it `consumes` is a comment, and the
whole inversion becomes unverifiable at exactly the point where it stops being
checkable by eye. With it, a scene that declares the cast and ignores it fails.

Eighteen scenes migrated. Four by hand at the drawn tier — Firefly Drift,
Metaballs, Floating Geometry, Prism Bloom — and ten at the field tier by script,
which is one declaration and one wrapped return. All eighteen pass.

The field tier is honestly marginal and the gate says so: every one of the ten
moves by 37 to 39 of 255, against 173 to 255 for the drawn tier, and the
uniformity across ten unrelated scenes is the tell. That is one global
posterisation applying, not ten scenes expressing anything. Cheap, real, shallow.

The decomposition moved from identity being worth 54% of the container to 158%,
but the stage set changed underneath the measurement and part of that is
Metaballs expressing a cast better than Constellation did. What survives the
caveat is the useful finding: a migrated library scene carries the identity
better than a stage written from scratch to carry it. The four bespoke stages
were the wrong shape of effort.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:59:24 +02:00
Dejvino
00ad1d8c2b Measure where visual difference comes from, and correct the epic's framing
Two cheap measurements settled a question three expensive ones had not, and both
of them contradicted the diagnosis offered for it.

The stated diagnosis was that the identity's expressive range had become the
bottleneck. It has not. A census of the identities themselves — no GPU, seconds
to run — puts mean distance between twelve songs at 0.43 with 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 decomposition then asked whether those designs reach the picture, by holding
the container fixed and varying only the identity, then the reverse:

    identity only    0.0299
    container only   0.0557
    both             0.1101
    neither          0.0000

Identity is worth 54% of what the container is worth, against an instrument
noise floor of exactly zero, and the two compose to more than their sum. The
inversion works at the frame level. What it does not do is replace the container.

That corrects EPIC-3 §5, which proposed a song picking two to five stages on the
theory that shared content would substitute for container variety. Container
variety is the larger of the two effects and identity adds to it. Four stages
with a rich identity throws away the 0.056 the library was already providing —
which is exactly the shape of every measurement in this epic: stages have the
lowest floor of any arm and no advantage in spread.

The direction is therefore 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 rather than their own. The migration was filed as a cost; it is the
payoff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:42:04 +02:00
Dejvino
6e4106048b Staging: a shared lattice, and the element size the songs were all sharing
The cast and ink slice lowered the floor as predicted but left `scale` — feature
size — consistently WORSE than the legacy arm, 0.033 against 0.053 and well
outside the noise. Every stage still chose its own element size from its own
param range, so every song landed in the same place: the videos shared a cast
and also, accidentally, shared how big everything was.

Staging is the third artifact. A lattice — grid, radial, spiral, scatter,
strata — with jitter, spread, a size hierarchy and a bias toward the middle or
the edges, transported the same way the cast is: uniforms plus a `stageNode`
function in the preamble, no new engine plumbing. Constellation, Swarm and
Procession place on it; the stage keeps its motion and gives up its composition.
Soloist takes only the scale, since a close-up has no composition to share.

`elementScale` is the part that mattered. It is the song's answer to "how big is
this made of", spanning about a factor of six, and it is the decision that was
missing rather than mis-set. Measured, over twelve songs and three runs:

    stages spread  +0.0111 ±0.0043  →  +0.0143 ±0.0008
    scale block      0.033 ±0.004   →    0.041 ±0.007

Against the legacy arm at +0.0108 ±0.0071 the harness still says
indistinguishable, and it is right to: the gap is +0.0035 and the legacy arm's
own run-to-run range is twice that. What can be said is narrower and holds up.
Stages have the lowest floor of the three arms by a clear margin — 0.082 against
0.095 and 0.104 — so sharing content does make a video look like itself, which
was the central prediction. And the targeted fix moved the block it was aimed at
in the direction it was aimed.

Also added: a lint check that the shader preamble contains no backticks. Twice
now one has closed the template literal and produced a check page that hangs on
"starting…" with an empty console, which is an expensive way to find a typo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:36:55 +02:00
Dejvino
577ec107f6 Sweep pool size directly, and withdraw the claim that it mattered
The Epic 3 arms appeared to show that a small casting roster was the largest
available win. Swept directly across 4, 8, 16 and 32 over twelve songs with
three pool draws each, the differences are 0.003 to 0.007 against a run-to-run
noise of +/-0.003 to +/-0.005. Pool size does nothing measurable.

The arms varied two things at once — smaller pool AND the same pool for every
song — and only the stages-versus-legacy comparison inside them held everything
else constant. That one still stands at +14%.

POOL_SIZE stays at 8, now on grounds the metric cannot see: about nine distinct
scenes in a video rather than seventeen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:27:58 +02:00
Dejvino
ec38def1a9 Epic 3, first slice: the song brings its own cast
Four stages, an identity layer, and an A/B that says the idea is right and the
reason it works is not the reason I expected.

A stage has no image of its own. It owns arrangement — a procession, a
constellation, a soloist, a swarm — and what it arranges comes from the track:
`castMain` and `castChorus` for the forms, `inkMask` and `inkValue` for the hand
they are drawn in. The identity generates a protagonist and a chorus with sides,
notches and hollows, plus an ink treatment of weight, edge, fill, outline and
posterisation. All of it travels as uniforms, so it is data rather than code and
a stage consumes it without knowing any other stage exists.

The protagonist IS the signature form rather than a second opinion about it.
They were separate draws in the first version, which let a track built on
hexagons put a round protagonist on screen — the signature said one thing and
the picture said another, and the shape trait stopped meaning anything for
stages. The cast now reads its geometry from the personality live and adds the
notches and hollows that turn a shape into a character.

Measured across seven songs, three arms, same instrument:

    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 prediction in EPIC-3 §7 was that sharing a cast would drop the floor
sharply. The floor did drop sharply — by a quarter — but nearly all of that came
from casting FEWER scenes per video, not from sharing content: the legacy arm,
four ordinary scenes with no cast at all, gets most of the same benefit. Stages
add about 14% on top of that.

That is worth knowing before building the other four registers. The single
largest available win was hiding in the roster size all along, and it costs one
number to take.

One result runs the wrong way and is recorded rather than explained: coupling is
+0.29 on the legacy arm and -0.18 on the stages. At twenty-one pairs neither is
distinguishable from zero, so it is a flag for a larger sample rather than a
finding.

Also fixed: a backtick inside the shader preamble's template literal, which
closed it and made every check page hang on "starting…" with no error in the
console.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:17:56 +02:00
Dejvino
75e0f0ef01 Make the whole library reachable, and measure what that did and did not buy
Three changes, from the list the measurements pointed at.

The personality's values now centre on the audio and the seed picks within that
centre, rather than every value being drawn from the seed alone. A bright track
sits high in its frame, a dynamic one has depth, noise earns corners where tonal
material stays round. The signature's own choice is tilted by the music too — it
decides which scenes a track can cast, so leaving it to the seed meant the most
consequential decision in the generator had no relationship to the song.

The signature stopped being a hard filter. As a filter it was the single largest
cause of sameness: a scene declaring all four traits was eligible for every track
and opened half of all videos, one declaring two was eligible for one track in
fourteen, and eleven scenes out of sixty-one carried nearly everything. It is a
weight now, worth six times at full honour.

That fix alone made things worse, which is worth recording. The filter was doing
two jobs — collapsing the library, and giving each track a DIFFERENT pool to cast
from — and removing it kept the second loss. Every track drew from the same
weighted library and measured song separation fell. So each track now draws its
own pool of about a third of the library, weighted by the signature but sampled
without replacement, keeping the differentiation and dropping the bias.

Motion became a character rather than a rate. Tempo was the only lever and tempo
compresses; stillness is a separate question from speed, and it can now halve the
animation rate or raise it by a third.

What it bought, measured against the same instrument: every scene in the library
is now cast, where sixteen were never reached; identical casts across seeds went
from four pairs to none; and the raw structural distances all rose — motion by
57%, layout by 22%, scale by 23%.

What it did not buy: videos also became more varied INTERNALLY, by more than they
became different from each other. Two songs are no more distinguishable relative
to how much one video already changes over its own length than before, and the
coupling between musical distance and visual distance is still indistinguishable
from noise at this sample size. The ceiling reference now sits below real
outputs, so the separation ratio is reported as not computable rather than as a
large number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 20:12:30 +02:00
Dejvino
a946f0e105 wip: instrument guards 2026-08-17 20:10:15 +02:00
Dejvino
2bbf5bcf42 Run both variety tests against the song bank, and fix the ceiling twice more
The tests now run on real bank entries. The seed gates used to run on a
two-section synthetic whose only kinds were intro and outro, so half the scene
library was unreachable and the number was measuring that rather than the seed.

Probes are labelled by section kind and occurrence rather than by index, which
is what makes two different songs comparable at all — they have different
section counts, so probe 3 of one is not probe 3 of the other, and matching by
position would compare a drop against an outro and score the mismatch as
variety. For two seeds of one song the labels are identical and this changes
nothing, which is the point.

The song test measures one thing the seed test does not: coupling, the rank
correlation between how different two tracks sound and how different their
videos look. Separation alone can be had by a generator that ignores the audio
and hashes the file, and that would be a perfect score for a completely wrong
video. Separation without coupling is not variety, it is a different seed per
file.

The ceiling took two more attempts. Recasting every layer at random averages a
dozen scenes together and a dozen random scenes converge on the same generic
busy image, so two references came out closer to each other than two real
videos and blocks scored over 100% of achievable. Forcing one scene per
reference collapsed the other way: a video that never changes scene has almost
no internal variation, so the ceiling landed BELOW the floor, which is a
within-video quantity. A reference has to match the structure of what it bounds.
They now keep the real pipeline — rosters, shots, per-section sampling, so a
reference rotates between three or four scenes exactly as a real video does —
while drawing from disjoint slices of the library. Same complexity, nothing in
common.

The fingerprint measurement had the same shape of error: pooling every probe
mixed in how much each video varies over its own length, which is large for
everything, and washed the answer to a flat 100% while the separation score said
almost everything was collapsed. One vector per video now.

Both tests fail as committed. Seed separation 0.04, song separation 0.03,
coupling -0.03 — two different songs differ from each other by about as much as
one video differs from itself, and that difference has no relationship to the
music. Colour scores 112%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 20:00:14 +02:00
Dejvino
e0c0dd664a Give the sections shapes, and the songs an opinion about their own edges
Two complaints, one cause: stages were flat blocks and the noise bed was glued
to the pad level, so the bed was a constant hiss under a track that never went
anywhere.

Sections have contours now — rise, fall, swell, dip, surge — sampled per note
rather than per stage, so a build builds through its chords instead of stepping
between two flat halves. The noise gets its own per-stage level instead of
following the pad, which makes it an arrangement element: a riser through a
build with its filter sweeping up, a wash under a drop, nearly absent in a
breakdown. That is also the better test signal, since the segmenter classifies a
section partly on its energy slope and a build that does not build is one it has
to guess at.

Transition hardness is a per-song axis. Genres differ on this more than they
differ on tempo — an ambient record dissolves between its sections and a club
record cuts — and until now every song in the bank cut. Soft songs crossfade
across a couple of bars; the hardest get the pre-drop trick, where everything
stops for most of a beat before the loud stage lands. Five soft, three mid, nine
hard, and the builder fails if the bank ever loses either end.

The contours immediately broke the axis they were layered onto: section-to-
section contrast is dynamic range, so adding it put a floor of 0.50 under a
statistic that had reached 0.18. That is what compression IS, so contour depth
now scales with the track's dynamics — a limitered master has shallow section
contrast as well as a shallow crest. Back to 0.29, which is as low as enveloped
notes and hard cuts will go.

One regression accepted rather than fixed: the soft fades cost the beatless
entries their tempo detection, since the attack that gave a drone a pulse is
exactly what a crossfade removes. `drone` reads 170bpm for a 62bpm source. A
beatless track has no tempo and the detector is guessing either way; `ember`
exists to anchor the low end with a beat that is actually there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 07:43:59 +02:00
Dejvino
d59dd9d5dd Give the song bank notes instead of a test tone
The tonal content was a sustained sine stack at a fixed root, and the stage
multiplier scaled that root directly — so a drop arrived at roughly 1.2kHz with
ten harmonics stacked on top of it and held there for thirty seconds. Every
statistic it was built to control came out correct. It was also unlistenable,
and a bank nobody can stand to play is a bank nobody audits.

There is a scale, a chord progression, a bass on the chord roots and a seeded
motif over the loud stages. Key, mode and progression come off the song's seed
so two entries are not the same four chords at different tempos. This is the
plainest thing that qualifies as music — not trying to be good, trying to be
playable enough that a person will listen to the bank and notice what is wrong
with it.

Register is the real fix. `stage.root` used to multiply the fundamental and now
adds voices upward instead: bass and chord stay where they belong, a lead octave
arrives when the arrangement opens up. Brightness still moves the harmonic
content and the key's register, but across an octave and a half rather than a
piercing sweep. Harmonics above Nyquist are dropped rather than left to alias,
since folded energy would corrupt the centroid and flatness the bank exists to
control.

Two measured consequences. Tempo detection on the beatless entries got better,
not worse — the bass gives a pulse where the drone gave nothing, and `drone` now
reads 62bpm instead of 88. And enveloped notes put a floor of 0.27 under the
measured dynamic range, because the gaps between notes are gaps a limitered
master does not have; note decay flattens toward a hold as dynamics falls, which
takes the compressed end back to 0.18.

Coverage still passes on all six axes and all six section kinds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 07:34:51 +02:00
Dejvino
cfe77fd524 Finish the song bank: axis ranges that are real, and a collinearity check
Completes the bank whose first half went in with the scene commits. The
remaining work was all in what "covered" means.

Two axis ranges were wrong, and being wrong made the coverage report lie in
both directions. Loudness is a raw mean spectral magnitude, not a normalised
0..1, so every song read as 0.00 and the axis looked dead. Centroid is mapped
to a log frequency axis, so a track of nothing but sub-bass and a 55Hz pad
still measures 0.28 and pure hiss measures 0.93 — against a nominal [0,1] the
bank would have reported a permanent 50% gap that no synth change could close.
Both now carry the range the statistic can really take on, with the reason.

Spread on every axis is not coverage of the space, so the tool also measures
correlation between axes. It immediately found brightness and noisiness moving
together at r=0.95: noise colour had been tied to brightness, one axis wearing
two names, leaving the dark-and-noisy quadrant unreachable. Noise colour is now
its own parameter — hiss over a sub-bass pad is an ordinary record.

That got r to 0.94, and no further. Flatness is the geometric mean of the
spectrum over its arithmetic mean across the whole band, so a signal only
measures flat if it has energy everywhere, which is the same thing as measuring
bright; band-limiting the noise to hold the centroid down empties the top and
drops the flatness with it. It is a property of the analyser and real material
does the same, so it is allowlisted with its reason. Anything not on that list
still fails — the check is there to catch a bank that has collapsed, not to
relitigate physics every run.

The output is gitignored. It is 172MB of deterministic audio derived from a
table that will keep changing: rebuild it, do not carry it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 07:03:02 +02:00
Dejvino
0c5b0a8d4d Close out the twenty: what the gates taught
Three lessons worth more than the scenes: a slow axis has to move a large
low-frequency area or the camera's own drift beats it, whole-frame luminance on
the kick is the strobe the flash gate exists for and it arrives by accident, and
a style trait expressed only as grain measures as no style at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 07:00:45 +02:00
Dejvino
905c217193 Three glitch scenes: a press, a loop and a tape
Halftone Misprint is a printing fault rather than an electronic one — the image
is never damaged, only separated and reassembled out of register, and nothing
else in the library is made of dots. Droste Feedback scales the previous frame
where Time Smear translates it, so the image never clears. Analog Wow is the
continuous, wet counterpart to Scan Tear and Block Mosh: the error varies
smoothly down the frame because every line was written at a different moment.

Three things that had to be got right. The halftone ruling is a count of dots
across the frame, not a pixel pitch, or a 4K export is the same dot on a bigger
sheet. The Droste loop has to CONTRACT — expanding pushes every copy off the
edge and leaves a plume instead of a corridor. And all three expressed style
only through grain at first, which is the Side Quest 1 complaint: they now put
the track's line weight and edge softness into the dot, the ring and the band
boundary, taking the measured style response from 24/11/18 to 86/255/88.

Neither feedback scene declares a slow axis, with the numbers recorded in the
files: their own history moves the ten-second average further than any parameter
does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 06:55:43 +02:00
Dejvino
91e74ff167 A cast census, and the arrangement it needed to be true
"Twenty-three of sixty-one scenes are never cast" was an artefact of the song it
was measured on. synthesizeSectioned has one change point, so it segments into
exactly two sections and both are quiet kinds — and intro, breakdown and outro
are restricted to the restful families for every director. Half the library was
unreachable before a seed was drawn, and the measurement reported that as a
casting failure.

synthesizeArrangement builds a real one: intro, build, drop, breakdown, drop,
outro, shaped to hit the segmenter's own classifier rather than to sound like
anything. Measured against a bank of those, two scenes out of sixty-one are
never a background, not twenty-three.

The census exists because "never cast" without a reason is unactionable. A scene
can die at the signature gate, at the director's family table, or in the roster
draw, and those are three different repairs — a trait declaration, a table, and
arithmetic that neither fixes. It reports which one, and it separates being cast
as a background from appearing as a translucent overlay, because a library can
look fully used while nine scenes carry every frame.

What it finds is that eligibility is almost entirely a function of how many
traits a scene declares. Four-trait scenes are eligible for every track and open
half of all videos; two-trait scenes are eligible for one track in fourteen; the
single scene declaring one trait is eligible for none, ever.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 06:45:42 +02:00
Dejvino
656e062069 A seed variety test, and a metric that had to be gated before it was believed
Every other phase asks whether one video is correct. This asks whether two are
different — the failure the suite could not see, since a generator that ignores
its seed passes determinism, flash safety and liveness perfectly.

Frames reduce to a structural descriptor built to be blind to the cheap axes and
sensitive to the expensive ones: standardized luma kills exposure and palette, a
Laplacian pyramid gives the radial spectrum, and the gradient-angle histogram is
carried through a DFT magnitude so a rotation shifts it without moving it.
Colour is measured and never counted; its only job is to expose the case where
two seeds differ by a palette swap and nothing else.

The score means nothing on its own, so it sits between two references the same
instrument produced: a floor of how far one video travels from itself across its
own sections, and a ceiling of the same pipeline with every layer recast at
random. Two checks gate the instrument before any number from it is trusted —
recolour must move structure ~0 while moving colour a lot, and a quarter turn
must not move it at all.

Three things this got wrong first and now does not. Averaging each video's
probes into one descriptor washed out the structure being measured and put the
floor above the ceiling; probes are matched instead, which is fair because the
track is held fixed. Cosine distance on all-positive histograms scored unrelated
scenes at 0.05, too compressed to be read; chi-square replaces it. Single-link
clustering in the library sweep chained overlapping pairs into a fourteen-scene
group that did not exist; complete-link means every pair inside a group is
really a twin.

The sweep runs against all 61 visualizations, and it finds what the per-scene
distinct gate cannot, because that one compares raw pixels and structural twins
are merely differently coloured.

Both rendered gates fail as committed. That is the point of them: separation is
0.07 against a 0.35 target, two 4-cliques of scenes are one look each, and a
third of the library is never cast. The instrument passes; the generator does
not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 06:40:04 +02:00
Dejvino
875c3868dd Three geometric scenes, and two ways to strobe a whole frame
Voronoi Shatter (cells with no fixed shape, re-cut on the phrase), Apollonian
Gasket (a packing built by inversion, so zooming finds more circles rather than
finer noise) and Isometric Blocks (a lit, gridded, solid surface where Floating
Geometry is bodies adrift).

Two flash failures, both the same mistake in different clothes. Apollonian's hard
depth cutoff popped a whole generation of discs in and out as the fold crossed
it — now faded rather than cut. Isometric Blocks drove block height off the low
end, so every tower in the frame grew and shrank together: 4/s. Moving the
reaction to the edges and to a hash-chosen quarter of the tops keeps the pulse
and drops the frame-wide swing.

Voronoi Shatter declares no slow axis: its re-cut puts the ten-second average
0.127 from itself between any two windows, the highest noise floor in the
library, and no parameter competes with that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:44:50 +02:00
Dejvino
50ceb7afa1 Four structural scenes: a span, a well, an arcade and an aisle
Suspension Span is two curves and a rhythm of hangers where Girder Lattice is a
texture of members. Stairwell Descent puts the vanishing point dead centre and
turns each flight, so the spiral is in the structure rather than in the motion.
Aqueduct March is masonry — the light comes through holes cut in a solid wall —
and Data Aisle is the interior, close counterpart to Neon City's exterior.

Two bugs worth naming: the arch openings compared a cell-local centre against a
global coordinate, which put every arch out in the wings and left a blank wall;
and the stairwell's depth fade was keyed on floor index, so it only darkened the
few pixels at the centre and measured as doing nothing.

Aqueduct March declares no slow axis and records the three candidates measured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 23:25:34 +02:00
Dejvino
c8658a6d15 Four minimal scenes, and a transition control that was reading its denominator
Pendulum Trace (a harmonograph over its own fading ghost), Contour Map (a survey
of a landscape with a tide in it), Shoji Grid (backlit panels that slide) and
Balance Stack (a leaning pile under a crossing sun).

Two of the four leave the slow axis undeclared and say why in the file: in a
frame this empty the camera's own drift moves the ten-second average more than
anything in the scene can, so a declaration would be a claim Phase 11 contradicts.

Phase 4's transition control changed from max(before, after) to their sum. These
scenes are calm, so casting them into the mid-shot control windows halved the
denominator while the boundary peak stayed put — 0.124 with them, 0.130 without.
For most of the window both stacks are live, four layers with accents in overlay
and screen, and a nonlinear blend of two moving stacks moves more than either
alone; "no worse than the busier half" was never a property a dissolve had. A
real pop still clears the threshold by an order of magnitude.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 10:40:23 +02:00
Dejvino
b01e3aff6f Three organic scenes: a reaction, a mat and a surface
Turing Bloom runs activator-inhibitor in the feedback buffer, so the pattern is
formed rather than drawn — the one organic here that will not go uniform. It
declares no slow axis on purpose, and says why: its own convergence path moves
the ten-second average by 0.10, and every candidate axis measured under that.

Mycelium Web puts an organic on the ground instead of standing in front of the
camera; the colony front is legible as area, which is also what makes its axis
measurable. Scale Mosaic is a log-polar lattice with a shear, so the rows are
spirals and every scale is the track's signature form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 10:10:21 +02:00
Dejvino
10a4cff0f6 Three flow scenes with a still point in them
Rain Column, Magnet Lines and Kármán Street. The flow family was seven ways of
advecting a noise field; none of them had an upstream, an obstacle or a rule.
These do: rain falls in three parallax layers under one shear, the field lines
are exact contours of the poles' stream function so they close on themselves,
and the vortex street is shed by a body that never moves.

The slow axes took four rounds to land. A fine-grained axis — line count, drop
density — loses to the camera's own drift in the time-averaged comparison Phase
11 makes, so all three ended up on a param that moves a large low-frequency
area: streak length, pole spread, and a wake haze that dissolves the streamlines
it widens over.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 22:42:21 +02:00
Dejvino
47e6fa33f9 Twenty more scenes: the list, and why these twenty
Even spread across families is not useful spread — casting disqualifies any
scene that misses a signature trait, so space and shape are worth more than
another entry in a full family. Each scene is specced against the neighbour it
must not resemble, because that is the gate with the numeric floor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 22:19:13 +02:00
Dejvino
e6f5a2f0d5 Epic 2.5.2: make the framing actually reach the image
Revisiting the framing layer turned up that it was not reaching four of the
forty-two scenes, and that none of its gates could have told us.

Those gates check the PLAN — cue sizes, the distribution of shot sizes across
a population, headroom, seek determinism — and a plan that never reaches the
image passes every one of them. Rendered at wide, normal and close, Scan
Tear, Pylon Grid, Pitch Shatter and the 3D Particle Field came back
byte-identical at every size.

The cause was a category error in the first implementation. Framing was
applied inside sigCamera, which is gated on the `camera` personality trait —
so a scene that declined the track's drift and sway silently declined the
shot size as well. That gating is right for a TRAIT and wrong for framing,
which is not one: framing is where the camera is standing for this shot, and
no scene should be exempt from it because of an unrelated art-direction
decision.

- Framing now lives in the shader epilogue, applied to the coordinate every
  fragment scene is handed, so honouring it is not optional. uv is left
  unframed on purpose: it is screen space, and prev() and sigGrain belong to
  the output image rather than to the scene being filmed.
- Scan Tear and Pitch Shatter build their image from uv deliberately — a
  signal artefact happens to the signal, not to the world behind it. They now
  slice on raw uv and build the field they displace from a new framedUv(p),
  so the tear stays locked to the frame while the imagery behind it is filmed
  wide or close.
- Particle Field receives framing in update() and honours it as a camera
  dolly, which is what framing literally is when a layer has a real camera.
  Distance divided by scale, matching the fragment path where the coordinate
  is divided by it.

New gate renders instead of inspecting: 42 of 42 scenes now respond to
framing, weakest Ridge Terrain at 0.22 of its own brightness, against a 0.05
floor. Also drops a stale comment on Layer.setFraming that still claimed
sigCamera applied it.

105/105 checks pass including the slow set.

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