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:
@@ -16,8 +16,9 @@
|
||||
// looking for the repetitive ones is exactly the job a sort order should do.
|
||||
|
||||
import { Engine } from '../engine/Engine.js';
|
||||
import { blit } from './blit.js';
|
||||
import { scenes } from '../scenes/registry.js';
|
||||
import { sampleValues, surfaceOf } from '../params/schema.js';
|
||||
import { sampleValues } from '../params/schema.js';
|
||||
import { Rng, hashString } from '../engine/rng.js';
|
||||
import { featureProviderFor } from '../audio/FeatureTrack.js';
|
||||
import { songBank } from '../audio/songbank.js';
|
||||
@@ -29,28 +30,23 @@ import { descriptorDistance, STRUCTURAL } from './variety/signature.js';
|
||||
|
||||
const THUMB = { width: 256, height: 144 };
|
||||
|
||||
/**
|
||||
* The floor a visualizer has to clear: how different its own six frames must be
|
||||
* from each other, across six songs' identities.
|
||||
/*
|
||||
* There used to be a MIN_VARIETY floor here — 0.1, drawn as a red line across
|
||||
* the gallery — on the theory that a scene which looks the same in every song
|
||||
* leaks that sameness between videos.
|
||||
*
|
||||
* Below this a scene is the same picture wherever it appears, and because it is
|
||||
* cast into many songs that repetition leaks between them — the viewer who
|
||||
* watches two videos recognises the shot rather than the song. It is a property
|
||||
* of the scene, not of the generator, so it is the scene that has to be lifted.
|
||||
* That was true when a section was ONE scene. It is not any more: a section is
|
||||
* a ground, a shot over it and sometimes a pass over that, so what a viewer
|
||||
* sees is a combination, and a scene that is reliably itself is a perfectly
|
||||
* good ingredient in one. Held against a floor, such scenes were failing for
|
||||
* being consistent.
|
||||
*
|
||||
* 0.1, and deliberately a target rather than a description of where the library
|
||||
* currently sits. Most of it does not clear this — of the scenes measured after
|
||||
* the region block went in, only Droste Feedback at 0.324 is comfortably over,
|
||||
* with Plasma Bloom at 0.082 and Voronoi Shatter at 0.062 still short. A bar set
|
||||
* where the work already is measures nothing.
|
||||
*
|
||||
* It has moved twice for instrument reasons rather than taste — the motion block
|
||||
* was reading zero for the whole library, and adding the region block made the
|
||||
* total a mean over six rather than five — and both raised every score. Expect
|
||||
* to re-read it off a fresh gallery whenever the descriptor changes, rather than
|
||||
* carrying the old number forward.
|
||||
* The score is still measured, still reported, and still what the gallery sorts
|
||||
* by — it is genuinely the right question to ask about a scene you are working
|
||||
* on. It is no longer a bar anything has to clear, and the interesting quantity
|
||||
* moved up a level: how unalike the scenes in one stack are. That lives in
|
||||
* scenes/metadata.json and is read by the look generator.
|
||||
*/
|
||||
export const MIN_VARIETY = 0.1;
|
||||
|
||||
/**
|
||||
* Six contexts, one per song: everything a scene is handed when it is cast.
|
||||
@@ -78,20 +74,6 @@ export function galleryContexts(count = 6) {
|
||||
});
|
||||
}
|
||||
|
||||
/** Bottom-up WebGL pixels into a canvas the right way up. */
|
||||
function blit(canvas, pixels, width, height) {
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const image = ctx.createImageData(width, height);
|
||||
const row = width * 4;
|
||||
for (let y = 0; y < height; y++) {
|
||||
const src = (height - 1 - y) * row;
|
||||
image.data.set(pixels.subarray(src, src + row), y * row);
|
||||
}
|
||||
ctx.putImageData(image, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render one scene across every context.
|
||||
*
|
||||
@@ -175,9 +157,14 @@ export function renderScene(engine, module, contexts) {
|
||||
|
||||
return {
|
||||
thumbs,
|
||||
// The six per-context descriptors, so a caller can average them into a
|
||||
// profile rather than re-render the library to get one. See
|
||||
// checks/metadata.js — this function is the only place the scenes are
|
||||
// rendered under real identities, and everything measured about a scene
|
||||
// comes out of here.
|
||||
descriptors,
|
||||
variety: pairs ? total / pairs : 0,
|
||||
coverage: covered,
|
||||
surface: surfaceOf(module),
|
||||
byBlock,
|
||||
error: dead
|
||||
? `renders nothing — luminance ${lum.toFixed(4)}, variance ${variance.toFixed(4)}. ` +
|
||||
|
||||
Reference in New Issue
Block a user