diff --git a/flow-state/EPIC-4.md b/flow-state/EPIC-4.md index 5c9481f..a2e2d82 100644 --- a/flow-state/EPIC-4.md +++ b/flow-state/EPIC-4.md @@ -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. diff --git a/flow-state/HOWTO-variety.md b/flow-state/HOWTO-variety.md index 26605e8..f12586b 100644 --- a/flow-state/HOWTO-variety.md +++ b/flow-state/HOWTO-variety.md @@ -28,16 +28,23 @@ checks.html?scene=X the per-scene gate, including `consumes:` lines ``` The gallery score is the mean structural distance between a scene's own six -frames, on the same descriptor the variety harness uses. **Minimum bar: 0.1** -(`MIN_VARIETY` in `checks/gallery.js`), drawn as a red line. Below it a scene is -the same picture wherever it appears, and because it is cast into many songs -that sameness leaks between them — the viewer recognises the shot rather than -the song. +frames, on the same descriptor the variety harness uses. High means the song +changes this scene a lot; low means it looks like itself wherever it is cast. -Most of the library does not clear it. That is deliberate: a bar set where the -work already is measures nothing. +**There is no bar.** There used to be one — 0.1, drawn across the gallery as a +red line — and it was removed when composition arrived. A section is no longer +one scene: it is a ground, a shot standing on it, and sometimes a pass over +that, so what a viewer sees is a COMBINATION. A scene that is reliably itself is +a perfectly good ingredient in one, and the old line failed it for being +consistent. -Some reference points, measured: +What replaced it is a level up. The interesting quantity is how unalike the +scenes in one stack are, and the generator now weights its choices by exactly +that — see `structuralDistance` in `scenes/surface.js`. Your scene does not have +to be varied on its own. It has to be unlike the things it will be stacked with, +and that is measured for you. + +Some reference points, measured across six songs: ``` 0.324 Droste Feedback varies a lot @@ -47,6 +54,29 @@ Some reference points, measured: 0.047 Moiré Grid after a subject; 0.031 before ``` +### The measured metadata + +`src/scenes/metadata.json` is generated, tracked in git, and holds every +measured fact about every scene: coverage, variety, per-block scores and the +structural profile the distance above is computed from. Nothing in it is +hand-written, and two decisions read it: + +* **`surface`** — canvas at 50% coverage or more, composable below. It used to + be declared per scene and it drifted; nine scenes claimed `canvas` while + painting under a third of the frame. Declaring `surface:` in a scene file is + now an error. +* **which scenes may be a GROUND** — a canvas that does not call `prev()`, fills the frame + when sampled as a bed, and does not vanish under any of the six identities. Coverage is + measured twice, once as a shot and once as a bed, because a scene paints wildly different + amounts at different parameters: Metaballs is 69% as a drop's shot and 6% as an intro's + ground. + +Refresh it from **gallery.html → refresh metadata** after changing a shader or a +metric. The file carries a fingerprint of the scenes, the shader contract, the +identities and palettes, and the descriptor definitions; when that stops +matching, phase 12 fails and tells you to re-measure. The write goes through a +dev-only endpoint in `vite.config.js`, so it only works under `npm run dev`. + --- ## What the descriptor can and cannot see diff --git a/flow-state/gallery.html b/flow-state/gallery.html index aefb33d..0912bc8 100644 --- a/flow-state/gallery.html +++ b/flow-state/gallery.html @@ -126,6 +126,7 @@ +