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>
176 lines
9.3 KiB
JavaScript
176 lines
9.3 KiB
JavaScript
// Structural family: a field of standing pylons receding to the track's
|
|
// horizon. The columns converge on a vanishing point, giving real perspective
|
|
// depth, and each column carries the song's own object on top, so a hexagonal
|
|
// track grows hexagonal pylons. The beat walks a pulse of light down the rows
|
|
// rather than flashing the whole frame.
|
|
//
|
|
// Nothing here is the scene's own invention any more. The crown is the song's
|
|
// protagonist and the LEG is a stack of its chorus: a column of bodies holding
|
|
// the subject up, rather than the thin fixed-width strut this drew for years.
|
|
// That strut was the same object in every video — a scene that has migrated its
|
|
// subject and kept its structure is still telling you what IT is, in the half of
|
|
// the frame that is structure, and next to a marched crown it read as scaffolding
|
|
// left up around a finished building.
|
|
//
|
|
// The crowns are SOLID. This scene already had the one thing the 3D cast needs
|
|
// and most scenes lack — a real depth axis, with near rows large and far rows
|
|
// small — so a flat crown was the only part of it drawn without perspective: a
|
|
// sticker facing the viewer on top of a receding structure. Marching them puts
|
|
// the object in the same space as the field it stands in, and because each
|
|
// pylon turns its own crown at its own angle, one field shows the song's form
|
|
// from a dozen sides at once. See castSolid.
|
|
//
|
|
// WHAT THE NUMBERS SAY, since two of them disagree and both are right.
|
|
//
|
|
// Crop to the bottom third of the frame — the near rows, where the columns are
|
|
// tall and the crowns are not — and measure how far apart six identities are:
|
|
// 13.4/255 with the old strut, 22.2/255 with the chorus stacked. That is the
|
|
// complaint answered. The strut was a fixed width and a fixed shape, so the only
|
|
// thing separating one video's structure from another's was the palette.
|
|
//
|
|
// The whole-frame variety score barely moves for it: 0.1322 to 0.1348 over
|
|
// 8 songs x 8 seeds. That is honest rather than disappointing — the crowns
|
|
// already dominate a frame-level descriptor, and a measurement averaged over the
|
|
// whole image cannot see a change confined to the lower third. It is the reason
|
|
// the crop above exists.
|
|
//
|
|
// The frame score is also still under the flat version's 0.141, and the cause is
|
|
// known: 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. Two
|
|
// candidate fixes were tried and both measured flat — keeping the far rows
|
|
// stamped (0.132), and running the lit surface through inkPattern (0.132 here,
|
|
// and 0.0009 on Effigy). It is the solid itself, and the trade is deliberate:
|
|
// the score does not measure a crown that ignores the perspective of the field
|
|
// it stands in, or a leg that is the same object in every video.
|
|
|
|
export const pylonGrid = {
|
|
name: 'Pylon Grid',
|
|
family: 'structural',
|
|
kind: 'fragment',
|
|
// Personality: see look/Personality.js.
|
|
// Takes the track's surface grain, but lightly — this is drawn, not filmed.
|
|
texture: 0.4,
|
|
consumes: ['form', 'ink', 'staging'],
|
|
traits: ['shape', 'space', 'style'],
|
|
|
|
params: {
|
|
columns: { type: 'float', range: [3, 16], default: 8, uniform: 'u_columns', bias: 'density' },
|
|
rows: { type: 'float', range: [2, 10], default: 6, uniform: 'u_rows', bias: 'density' },
|
|
spread: { type: 'float', range: [0.6, 1.6], default: 1.1, uniform: 'u_spread' },
|
|
height: { type: 'float', range: [0.3, 1.4], default: 0.85, uniform: 'u_height', bias: 'energy' },
|
|
pulse: { type: 'float', range: [0, 1.2], default: 0.5, uniform: 'u_pulse', bias: 'energy' },
|
|
speed: { type: 'float', range: [0.05, 0.8], default: 0.3, uniform: 'u_speed', bias: 'motion', rate: true },
|
|
palette: { type: 'palette', count: 4 },
|
|
},
|
|
|
|
reactive: {
|
|
pulse: { feature: 'beat', amount: 0.4, response: 'smooth' },
|
|
rows: { feature: 'bandLow', amount: 0.25 },
|
|
},
|
|
|
|
shader: `
|
|
vec4 scene(vec2 uv, vec2 p) {
|
|
float t = u_time * u_speed + u_seed;
|
|
float hy = sigHorizonY();
|
|
vec3 col = pal(0) * 0.05;
|
|
|
|
// Whether a crown already owns this pixel. Rows are walked near to far, so
|
|
// first-wins is the correct occlusion here rather than merely the cheap one
|
|
// — and it bounds a hundred and sixty instances to about one march.
|
|
float painted = 0.0;
|
|
|
|
for (int r = 0; r < 10; r++) {
|
|
if (float(r) >= u_rows) break;
|
|
float fr = float(r);
|
|
|
|
// Perspective: the last row sits on the horizon, the first at the bottom
|
|
// of the frame, everything between scaling by its depth fraction.
|
|
float f = fr / max(u_rows - 1.0, 1.0); // 0 near .. 1 far
|
|
float y0 = mix(-1.08, hy + 0.06, f); // row baseline
|
|
float scale = mix(1.0, 0.12, f); // perspective shrink
|
|
float depthShade = mix(1.0, 0.35, f); // far rows dim
|
|
|
|
for (int c = 0; c < 16; c++) {
|
|
if (float(c) >= u_columns) break;
|
|
float fc = float(c);
|
|
float cx = (fc / max(u_columns - 1.0, 1.0) - 0.5) * 2.0 * u_spread * scale;
|
|
|
|
float h = u_height * scale;
|
|
float yTop = y0 + h;
|
|
|
|
// The beat pulse walks a glow down the depth axis, row by row, so the
|
|
// light travels rather than strobing the whole frame.
|
|
float walk = sat(1.0 - abs(fr - mod(u_beatPhase * 6.0 + t * 0.4, u_rows)));
|
|
float inten = (0.35 + 0.65 * walk) * depthShade * (0.7 + 0.3 * u_beat);
|
|
|
|
// The crown is resolved BEFORE the leg, because the two overlap
|
|
// where the object sits on the column and the crown has to win it —
|
|
// resolved the other way round, every subject is cut off at the
|
|
// waist by the thing holding it up.
|
|
//
|
|
// The song's object, standing on the pylon.
|
|
vec2 crown = vec2(cx, yTop);
|
|
float size = (0.06 + 0.2 * scale) * (1.0 + u_beat * 0.4);
|
|
vec2 local = (p - crown) / max(size, 1e-3);
|
|
|
|
if (dot(local, local) < 1.6 && painted < 0.5) {
|
|
// Each crown at its own angle, so the field is one form seen
|
|
// from many sides rather than one silhouette repeated. The turn
|
|
// is slow and per-pylon; the row term keeps a receding line of
|
|
// them from presenting the same profile all the way back.
|
|
mat3 turn = castTurn(t * 0.6 + fc * 0.9 + fr * 0.4, 0.2 + sin(t + fr) * 0.2);
|
|
vec3 n;
|
|
float hit = castSolid(local, turn, n);
|
|
if (hit > 0.0) {
|
|
painted = 1.0;
|
|
col = mix(col, castLit(n, vec3(0.0, 0.0, 1.0)) * inten * (0.6 + u_pulse), 0.92);
|
|
}
|
|
// Its outline in the track's line weight, which is what carries
|
|
// the far rows: at twelve percent scale a crown is a few pixels
|
|
// and the lit faces are gone before the edge is.
|
|
float d = castSDF3(turn * vec3(local, 0.0)) * size;
|
|
col += pal(3) * inkStroke(d) * inten * (0.5 + u_pulse);
|
|
}
|
|
col += pal(int(mod(fr, 4.0))) * exp(-dot(p - crown, p - crown) * 60.0) * u_pulse * depthShade;
|
|
|
|
// THE LEG: the chorus, stacked. How many members make up a column
|
|
// is the song's element size deciding it — a track built of a few
|
|
// huge forms stands on three of them, a track of many small ones on
|
|
// a ladder of nine — and how WIDE the column is comes from the
|
|
// chorus's own proportions, which is why no two songs get the same
|
|
// pylon. See Identity.js: this is what the chorus solid is for.
|
|
float segs = clamp(floor(2.0 + 3.6 / max(stageScale(), 0.3)), 2.0, 9.0);
|
|
float member = max(h / (segs * 2.0), 1e-3);
|
|
|
|
// The bounding test is the column's own box, so a pixel outside it
|
|
// costs two comparisons rather than a march. With a hundred and
|
|
// sixty pylons on screen that test is most of the scene's budget.
|
|
if (abs(p.x - cx) < member * 1.3 && p.y > y0 - member * 0.3
|
|
&& p.y < yTop + member * 0.3 && painted < 0.5) {
|
|
float rung = floor((p.y - y0) / (member * 2.0));
|
|
float centreY = y0 + (rung + 0.5) * member * 2.0;
|
|
vec2 legLocal = vec2(p.x - cx, p.y - centreY) / member;
|
|
|
|
// Every member at its own angle, so a column is a stack of one
|
|
// form seen many ways rather than one form printed nine times.
|
|
mat3 legTurn = castTurn(t * 0.35 + rung * 1.1 + fc * 0.5, rung * 0.5);
|
|
vec3 ln;
|
|
float legHit = castChorusSolid(legLocal, legTurn, ln);
|
|
if (legHit > 0.0) {
|
|
painted = 1.0;
|
|
col = mix(col, castLit(ln, vec3(0.0, 0.0, 1.0))
|
|
* mix(inten, 1.0, 0.25) * pal(int(mod(fc, 4.0))) * 1.6, 0.9);
|
|
}
|
|
col += pal(int(mod(fc, 4.0)))
|
|
* inkStroke(castChorus3(legTurn * vec3(legLocal, 0.0)) * member) * inten * 0.8;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ground haze and the shared air between here and the horizon.
|
|
col = sigAir(col, p, smoothstep(0.0, 1.6, abs(p.y - hy)));
|
|
return vec4(inkValue(col), 1.0);
|
|
}
|
|
`,
|
|
}; |