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>
This commit is contained in:
@@ -394,3 +394,191 @@ do it once the numbers were understood, and they now are.
|
||||
written — direction clears the arcless reference by 0.26, which is hard to explain without
|
||||
a story — but it remains the only thing that would attribute the floor rise (0.1697 →
|
||||
0.2130) to this work rather than to everything else that changed alongside it.
|
||||
|
||||
---
|
||||
|
||||
## 14. Composition — the frame has a floor and a ceiling
|
||||
|
||||
Not part of the original plan. It comes out of the same place §12 did: the layer stack was
|
||||
being asked to carry the video, and half of what it was stacking was nothing.
|
||||
|
||||
**The floor.** Two thirds of the library is `composable` — sparse by design, elements ON
|
||||
something. `canBackground` let almost all of it anchor a section anyway, so a section's
|
||||
bottom layer was regularly a scene painting 2% of the frame with black behind it. Measured
|
||||
across six videos, sampling the middle of every section: **9 of 40 frames were under 20%
|
||||
painted, the darkest at 0.3%**. That is a minute and a half of a few bright things on black,
|
||||
and no gate could see it, because every gate on the stack was a limit rather than a floor.
|
||||
|
||||
Every section now stands on a GROUND: a canvas painting at least half the frame, cast per
|
||||
section kind so a section's shot cuts change the shot and not the world. When the shot is
|
||||
itself a full canvas it *is* the ground — two canvases stacked is two pictures fighting. The
|
||||
same measurement now reads **mean 92% painted, darkest 63%, nothing under 30%**, and phase 12
|
||||
gates it in the image, not in the spec.
|
||||
|
||||
**The ceiling.** Layering had a rate but no budget, so how full a frame got was a die roll
|
||||
that knew nothing about the song. A stack now has a coverage budget — director appetite
|
||||
(`crowd`, 1.05 brutalist to 1.7 corrupt), times the section's energy and density, times where
|
||||
the story is — capped at **200%**, two frames' worth of material. The ground and the shot are
|
||||
paid for first; the budget governs what may be stacked on top, and the odds of an overlay
|
||||
fall off as the headroom does rather than only at the wall.
|
||||
|
||||
**What it cost, and what it bought.** Same instrument, same six seeds:
|
||||
|
||||
```
|
||||
baseline with grounds
|
||||
separation 0.10 0.44
|
||||
direction 0.06 0.21 (arcless reference 0.02 → 0.08)
|
||||
library 80% 97% of what the library can express
|
||||
scale 42% 56%
|
||||
motion 68% 96%
|
||||
texture 74% 90%
|
||||
```
|
||||
|
||||
The ceiling *fell* (0.2334 → 0.2031) while observed rose, so part of that separation gain is
|
||||
the reference arm coming down: videos that share a ground vocabulary are less unalike even
|
||||
when they share no shots. The honest reading is that the floor rose 0.009, direction tripled,
|
||||
and the ceiling moved toward the floor — the numbers are up, and not all of the rise is
|
||||
signal.
|
||||
|
||||
**A scene that reads the previous frame cannot be a ground.** `prev()` returns the whole
|
||||
composited frame, *including whatever is layered on top of this scene*, so a datamosh under a
|
||||
shot is not grounding it, it is eating it. Measured the moment one became a bed: the render
|
||||
stopped reproducing from a seek, and two WebGL contexts diverged by 91/255 against a ceiling
|
||||
of 4 — phase 4 caught both. `readsHistory` is derived from the shader source rather than
|
||||
declared, and costs the ground pool six of twenty scenes, five of them glitch.
|
||||
|
||||
`scenes/coverage.js` is the measured coverage of every scene, pasted back out of the phase 12
|
||||
gate, which re-measures and fails on drift over a tenth of a frame. A new scene has no entry
|
||||
and therefore cannot ground anything until someone renders it.
|
||||
|
||||
### 14.1 The measurements moved into the repo
|
||||
|
||||
Everything in §14 depends on a number — how much of the frame a scene paints — and the
|
||||
first version of it was a table pasted into a source file by hand. That is the same mistake
|
||||
`surface` already was: a measured fact written down by a person, correct on the day and
|
||||
wrong after the next shader edit.
|
||||
|
||||
**`src/scenes/metadata.json`** is now generated, tracked in git, and holds every measured
|
||||
fact about every visualizer: coverage as a shot, coverage as a bed, the variety score, the
|
||||
per-block scores, and the mean structural profile. Nothing in it is typed by a human.
|
||||
`surface` is derived from it (canvas at 50%, composable below) and declaring `surface:` in a
|
||||
scene file is now a lint error.
|
||||
|
||||
It is refreshed from **gallery.html → refresh metadata**, which re-renders the library and
|
||||
writes the file through a dev-only endpoint in `vite.config.js`. The file carries a
|
||||
fingerprint of everything that can move a number in it — the scenes, the shader contract,
|
||||
the identities and palettes they are handed, the descriptor definitions — and phase 12 fails
|
||||
when that stops matching. A stale metadata file does not produce a stale report, it produces
|
||||
wrong videos, so it has to be detectable.
|
||||
|
||||
**Measuring at the wrong bias measures nothing.** Coverage is mostly a function of a scene's
|
||||
parameters, and the first measurement took one number per scene at the busiest section of
|
||||
six songs. Metaballs measures 69% there and painted **6%** as an intro's ground. Three
|
||||
things came out of chasing that, in order of how much each was worth:
|
||||
|
||||
* A bed is drawn with a **moderate hand**. `extremity` pushes parameters toward the ends of
|
||||
their ranges, and half of what decides coverage is a parameter with no `bias` key at all —
|
||||
Metaballs' `threshold` — so extremity is the only thing moving it and one end is an empty
|
||||
frame. Grounds sample at `extremity * 0.2`. The extremes belong to the shot.
|
||||
* Energy and density are **floors** for a ground, not reductions. Calming the bed by
|
||||
lowering energy is how to empty it: scenes bias their fill against `energy` as often as
|
||||
against `density`.
|
||||
* Coverage is measured **twice**, once as a shot and once as a bed, at the same bias and
|
||||
temperament the generator will use. `GROUND_BIAS` and `groundTemperamentFrom` live in
|
||||
`scenes/surface.js` and are imported by both the generator and the measuring pass, so the
|
||||
two cannot drift apart.
|
||||
|
||||
Rendered result across six videos, sampling the middle of every section: **mean 87% painted,
|
||||
darkest 30%**, against 51%/0.3% before any of this.
|
||||
|
||||
**Eleven scenes can ground a section** — five organic, five geometric, one structural. There
|
||||
is no `minimal`, `flow` or `glitch` canvas in the library that paints half the frame without
|
||||
reading `prev()`, so every quiet section in every video stands on one of five organic beds.
|
||||
That is the largest remaining hole and it is library work, not generator work: the fix is
|
||||
writing dense, self-contained canvases in the three thin families.
|
||||
|
||||
### 14.2 The red line is gone
|
||||
|
||||
`MIN_VARIETY` — 0.1, drawn across the gallery, "below this a scene is the same picture
|
||||
wherever it appears" — was right when a section was one scene and is wrong now. A section is
|
||||
a ground, a shot on it and sometimes a pass over that, so what a viewer sees is a
|
||||
combination, and a scene that is reliably itself is a good ingredient in one. Nineteen
|
||||
scenes were failing a bar for being consistent.
|
||||
|
||||
What replaced it is a level up, and it is the reason the profiles are in the metadata: the
|
||||
generator weights every layering choice by **structural distance** — how unalike two scenes
|
||||
measured, on the same descriptor the variety harness compares videos with. Family labels and
|
||||
the render disagree often enough to matter; two `geometric` scenes can be 0.31 apart and a
|
||||
`flow` and an `organic` scene 0.04, and stacking the second pair is one picture at double
|
||||
density. Measured over 436 stacked pairs: **mean distance 0.268, zero near-twins**.
|
||||
|
||||
### 14.3 The other end of the frame
|
||||
|
||||
The floor had a ceiling missing. Measured across twelve videos, sampling the middle of every
|
||||
section: **a median 24% of every frame was clipped to pure white, and whole sections
|
||||
rendered at 100%.** The videos were washed out, and no gate could see it because every guard
|
||||
on the frame asked whether there was enough in it.
|
||||
|
||||
Three causes, all introduced or exposed by §14, in the order they were found:
|
||||
|
||||
**The shot was screened over its ground.** Screen is a lightening operator — correct for a
|
||||
few bright elements over a bed, and over a filled canvas it drives everything toward white.
|
||||
Replaced with a **lumakey** blend (`passes.js`): the shot's own brightness is its alpha, so
|
||||
it replaces the ground where it paints and leaves it where it does not, keeping its own
|
||||
colour instead of adding it to the bed's. Median clipping 24% → 11%.
|
||||
|
||||
**The feedback loop was an accumulator.** `cur + hist * decay * amount` settles a still
|
||||
image at `1/(1 - decay*amount)` times its drawn brightness — **2.3x** at the settings the
|
||||
generator hands out. Survivable when a frame was a few bright things on black; fatal the
|
||||
moment every section stood on a filled ground. Turning feedback off took a blown frame from
|
||||
100% to 34% mean luminance, which is the whole diagnosis in one number. It now divides by
|
||||
the same gain, at 0.6 rather than 1.0: full normalisation took the lift out along with the
|
||||
blowout — the median frame fell from 95% painted to 71% and fourteen sections dropped
|
||||
through the black-frame floor.
|
||||
|
||||
**Nothing rolled off.** The grade clipped. A **highlight shoulder** now compresses everything
|
||||
above 0.75 toward but never to 1. Clipped white is not brightness, it is missing information:
|
||||
every difference inside it has been deleted.
|
||||
|
||||
Rendered, after all three — 74 sections, twelve videos:
|
||||
|
||||
```
|
||||
before after
|
||||
painted 51% mean 88% mean, darkest 43% (was darkest 0.3%)
|
||||
clipped white 24% median 0% median, worst 21% (was worst 100%)
|
||||
luminance 77% median 42% median, worst 80%
|
||||
```
|
||||
|
||||
The gate is now two-ended: `composition · a rendered section is neither black nor blown out`.
|
||||
|
||||
**A ground has to survive the song's identity.** Chasing the dark end turned up the same
|
||||
class of error as §14.1: a ground measured at 61% painted **2%** in one particular video,
|
||||
because that song's ink treatment is `hollow` — outlines, no fill. Two changes came out of
|
||||
it. The world is now drawn solid and the song's hand is kept for the subject
|
||||
(`groundPersonalityFrom`), and eligibility takes the **worst** identity into account as well
|
||||
as the mean: a ground fills the frame on average and never vanishes. Holding the worst case
|
||||
to the full 50% would leave *four* castable beds in the library, which is a worse video than
|
||||
an occasionally dim intro.
|
||||
|
||||
**Seven scenes can ground a section** — five geometric, two organic. Every quiet section of
|
||||
every video stands on one of two beds. Restated from §14.1 because it got worse, not better:
|
||||
this is the library's largest hole.
|
||||
|
||||
### 14.4 Blazing on purpose
|
||||
|
||||
Screening the shot over its ground and letting the two brightnesses sum is a real effect — a
|
||||
drop that goes to paper for eight bars reads as the song peaking. The mistake in §14.3 was
|
||||
not the effect, it was that the effect was the **default**: every section did it, so a median
|
||||
quarter of every frame in every video was clipped, and nothing about that said "peak".
|
||||
|
||||
It is a decision now. Each director declares a `blaze` appetite — brutalist 0.05 (mass does
|
||||
not glow), corrupt 0.5 (overload is the subject) — and a section still has to earn one: loud,
|
||||
and late in the story. Quiet kinds never blaze, because a breakdown that goes white is not a
|
||||
choice, it is a bug with a rationale. The default composite is the lumakey, which keeps the
|
||||
shot's own colour.
|
||||
|
||||
The ceiling gate matches the intent rather than banning brightness. Per section, only a hard
|
||||
cap — past about half the frame at full white there is no picture left to read. Across the
|
||||
population, **no more than a fifth of sections may be hot at all**, which is the number that
|
||||
actually distinguishes a director choosing to peak from a pipeline with no headroom.
|
||||
Measured now: **1 of 74 sections blazing**, worst 30% clipped.
|
||||
|
||||
Reference in New Issue
Block a user