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:
@@ -19,7 +19,6 @@ export const particleField = {
|
||||
// top — every layered stack in every song was these particles — while the
|
||||
// general overlay path sat dead because nothing else was labelled. It is one
|
||||
// composable scene among many now.
|
||||
surface: 'composable',
|
||||
// Points in empty space with no ground of their own — the one scene in the
|
||||
// library that genuinely cannot carry a section alone. See schema.canBackground.
|
||||
background: false,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,8 +20,6 @@ export const aqueductMarch = {
|
||||
name: 'Aqueduct March',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 27% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
texture: 0.9,
|
||||
consumes: ['cast', 'ink'],
|
||||
traits: ['shape', 'camera', 'space', 'style'],
|
||||
|
||||
@@ -13,8 +13,6 @@ export const auroraVeil = {
|
||||
name: 'Aurora Veil',
|
||||
family: 'flow',
|
||||
kind: 'fragment',
|
||||
// Paints 13% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['ink'],
|
||||
traits: ['camera', 'space', 'style'],
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ export const cargoBelt = {
|
||||
name: 'Cargo Belt',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 12% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Takes the track's surface grain, but lightly — this is drawn, not filmed.
|
||||
texture: 0.4,
|
||||
consumes: ['cast', 'ink'],
|
||||
|
||||
@@ -13,8 +13,6 @@ export const circuitBloom = {
|
||||
name: 'Circuit Bloom',
|
||||
family: 'geometric',
|
||||
kind: 'fragment',
|
||||
// Paints 1% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Takes the track's surface grain, but lightly — this is drawn, not filmed.
|
||||
texture: 0.4,
|
||||
consumes: ['cast', 'ink'],
|
||||
|
||||
@@ -8,8 +8,6 @@ export const curlFlow = {
|
||||
name: 'Curl Flow',
|
||||
family: 'flow',
|
||||
kind: 'fragment',
|
||||
// Paints 21% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
consumes: ['ink'],
|
||||
traits: ['camera', 'space', 'style'],
|
||||
|
||||
@@ -11,8 +11,6 @@ export const dataAisle = {
|
||||
name: 'Data Aisle',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 21% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
texture: 0.6,
|
||||
consumes: ['ink'],
|
||||
traits: ['camera', 'space', 'style'],
|
||||
|
||||
@@ -21,8 +21,6 @@ export const drosteFeedback = {
|
||||
name: 'Droste Feedback',
|
||||
family: 'glitch',
|
||||
kind: 'fragment',
|
||||
// Paints 2% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
texture: 0.5,
|
||||
consumes: ['ink'],
|
||||
traits: ['camera', 'style'],
|
||||
|
||||
@@ -13,8 +13,6 @@ export const dustChamber = {
|
||||
name: 'Dust Chamber',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 2% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['cast', 'ink'],
|
||||
traits: ['shape', 'camera', 'space', 'style'],
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ export const eclipseField = {
|
||||
name: 'Eclipse Field',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 17% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Crisp line work: the track's surface grain would only fur the edges.
|
||||
texture: 0,
|
||||
consumes: ['cast', 'ink'],
|
||||
|
||||
@@ -9,8 +9,6 @@ export const fireflyDrift = {
|
||||
name: 'Firefly Drift',
|
||||
family: 'flow',
|
||||
kind: 'fragment',
|
||||
// Paints 2% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
consumes: ['cast', 'ink', 'staging'],
|
||||
traits: ['camera', 'style'],
|
||||
|
||||
@@ -11,8 +11,6 @@ export const flora = {
|
||||
name: 'Flora',
|
||||
family: 'organic',
|
||||
kind: 'fragment',
|
||||
// Paints 15% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
consumes: ['cast', 'ink'],
|
||||
traits: ['shape', 'camera', 'style'],
|
||||
|
||||
@@ -14,8 +14,6 @@ export const gateCorridor = {
|
||||
name: 'Gate Corridor',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 29% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Takes the track's surface grain, but lightly — this is drawn, not filmed.
|
||||
texture: 0.4,
|
||||
consumes: ['cast', 'ink'],
|
||||
|
||||
@@ -6,8 +6,6 @@ export const horizonLines = {
|
||||
name: 'Horizon Lines',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 16% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
// Crisp line work: the track's surface grain would only fur the edges.
|
||||
texture: 0,
|
||||
|
||||
@@ -13,8 +13,6 @@ export const inkBleed = {
|
||||
name: 'Ink Bleed',
|
||||
family: 'organic',
|
||||
kind: 'fragment',
|
||||
// Paints 1% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['ink'],
|
||||
traits: ['camera', 'space', 'style'],
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ export const magnetLines = {
|
||||
name: 'Magnet Lines',
|
||||
family: 'flow',
|
||||
kind: 'fragment',
|
||||
// Paints 18% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Line work. Grain furs it up, so take only a dusting.
|
||||
texture: 0.5,
|
||||
consumes: ['cast', 'ink'],
|
||||
|
||||
@@ -7,8 +7,6 @@ export const metaballs = {
|
||||
name: 'Metaballs',
|
||||
family: 'organic',
|
||||
kind: 'fragment',
|
||||
// Paints 26% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
consumes: ['cast', 'ink', 'staging'],
|
||||
traits: ['shape', 'camera', 'style'],
|
||||
|
||||
@@ -12,8 +12,6 @@ export const myceliumWeb = {
|
||||
name: 'Mycelium Web',
|
||||
family: 'organic',
|
||||
kind: 'fragment',
|
||||
// Paints 22% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['ink'],
|
||||
traits: ['camera', 'space', 'style'],
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ export const neonCity = {
|
||||
name: 'Neon City',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 26% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
consumes: ['ink'],
|
||||
traits: ['space', 'camera', 'style'],
|
||||
|
||||
@@ -13,8 +13,6 @@ export const pendulumTrace = {
|
||||
name: 'Pendulum Trace',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 0% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Fine line work; grain only furs it up.
|
||||
texture: 0.25,
|
||||
consumes: ['ink'],
|
||||
|
||||
@@ -7,8 +7,6 @@ export const prismBloom = {
|
||||
name: 'Prism Bloom',
|
||||
family: 'geometric',
|
||||
kind: 'fragment',
|
||||
// Paints 1% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
// Crisp line work: the track's surface grain would only fur the edges.
|
||||
texture: 0,
|
||||
|
||||
@@ -9,8 +9,6 @@ export const psychedelicDrift = {
|
||||
name: 'Psychedelic Drift',
|
||||
family: 'glitch',
|
||||
kind: 'fragment',
|
||||
// Paints 6% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
consumes: ['cast', 'ink'],
|
||||
traits: ['shape', 'camera', 'style'],
|
||||
|
||||
@@ -48,8 +48,6 @@ export const pylonGrid = {
|
||||
name: 'Pylon Grid',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 25% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
// Takes the track's surface grain, but lightly — this is drawn, not filmed.
|
||||
texture: 0.4,
|
||||
|
||||
@@ -7,8 +7,6 @@ export const ridgeTerrain = {
|
||||
name: 'Ridge Terrain',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 13% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
consumes: ['ink'],
|
||||
traits: ['space', 'camera', 'style'],
|
||||
|
||||
@@ -12,8 +12,6 @@ export const shojiGrid = {
|
||||
name: 'Shoji Grid',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 23% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Paper has a tooth, and this is the one scene that wants it.
|
||||
texture: 1.0,
|
||||
consumes: ['cast', 'ink'],
|
||||
|
||||
@@ -10,8 +10,6 @@ export const signalDecay = {
|
||||
name: 'Signal Decay',
|
||||
family: 'glitch',
|
||||
kind: 'fragment',
|
||||
// Paints 5% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['ink'],
|
||||
traits: ['camera', 'style'],
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ export const silkRibbon = {
|
||||
name: 'Silk Ribbon',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 3% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
// A soft drape of light. Grain furs the one thing it is made of.
|
||||
texture: 0,
|
||||
|
||||
@@ -9,8 +9,6 @@ export const slowOrb = {
|
||||
name: 'Slow Orb',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 11% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
// One soft body in an empty frame — the emptiest scene in the library, and
|
||||
// the one where speckle is most visible for being least justified. It keeps
|
||||
|
||||
@@ -12,8 +12,6 @@ export const smokeColumn = {
|
||||
name: 'Smoke Column',
|
||||
family: 'flow',
|
||||
kind: 'fragment',
|
||||
// Paints 16% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['ink'],
|
||||
traits: ['camera', 'space', 'style'],
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ export const spectrumSculpture = {
|
||||
name: 'Spectrum Sculpture',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 2% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
// Takes the track's surface grain, but lightly — this is drawn, not filmed.
|
||||
texture: 0.4,
|
||||
|
||||
@@ -13,8 +13,6 @@ export const stormRift = {
|
||||
name: 'Storm Rift',
|
||||
family: 'glitch',
|
||||
kind: 'fragment',
|
||||
// Paints 0% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['ink'],
|
||||
traits: ['camera', 'space', 'style'],
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ export const suspensionSpan = {
|
||||
name: 'Suspension Span',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 21% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
texture: 0.6,
|
||||
consumes: ['cast', 'ink'],
|
||||
traits: ['shape', 'camera', 'space', 'style'],
|
||||
|
||||
@@ -10,8 +10,6 @@ export const synthwaveRun = {
|
||||
name: 'Synthwave Run',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 5% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
// Personality: see look/Personality.js.
|
||||
consumes: ['ink'],
|
||||
traits: ['space', 'camera', 'style'],
|
||||
|
||||
@@ -13,8 +13,6 @@ export const vortexDrift = {
|
||||
name: 'Vortex Drift',
|
||||
family: 'flow',
|
||||
kind: 'fragment',
|
||||
// Paints 21% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['cast', 'ink'],
|
||||
traits: ['shape', 'camera', 'style'],
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ export const constellation = {
|
||||
name: 'Constellation',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 1% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['cast', 'ink', 'staging'],
|
||||
texture: 0.6,
|
||||
traits: ['shape', 'camera', 'space', 'style'],
|
||||
|
||||
@@ -15,7 +15,6 @@ export const effigy = {
|
||||
family: 'geometric',
|
||||
// One object against a dark wash — most of the frame is legitimately empty,
|
||||
// and it reads as something standing in a space rather than as the space.
|
||||
surface: 'composable',
|
||||
kind: 'fragment',
|
||||
consumes: ['form', 'ink', 'staging'],
|
||||
texture: 0.6,
|
||||
|
||||
@@ -9,8 +9,6 @@ export const procession = {
|
||||
name: 'Procession',
|
||||
family: 'structural',
|
||||
kind: 'fragment',
|
||||
// Paints 18% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['cast', 'ink', 'staging'],
|
||||
texture: 0.4,
|
||||
traits: ['shape', 'camera', 'style'],
|
||||
|
||||
@@ -8,8 +8,6 @@ export const soloist = {
|
||||
name: 'Soloist',
|
||||
family: 'minimal',
|
||||
kind: 'fragment',
|
||||
// Paints 20% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['cast', 'ink', 'staging'],
|
||||
texture: 0.5,
|
||||
traits: ['shape', 'camera', 'style'],
|
||||
|
||||
@@ -16,8 +16,6 @@ export const swarm = {
|
||||
name: 'Swarm',
|
||||
family: 'organic',
|
||||
kind: 'fragment',
|
||||
// Paints 2% of the frame — see checks/phase12 coverage.
|
||||
surface: 'composable',
|
||||
consumes: ['form', 'ink', 'staging'],
|
||||
texture: 0.5,
|
||||
traits: ['shape', 'camera', 'style'],
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
// What the measurements MEAN — the thresholds and the rules, kept apart from
|
||||
// the numbers they read.
|
||||
//
|
||||
// scenes/metadata.json is generated and holds no opinions: it says a scene
|
||||
// paints 32% of the frame across six songs and nothing about whether that is
|
||||
// enough. This file is where that becomes a decision, and it is hand-written
|
||||
// precisely because a threshold is a judgement about the videos rather than a
|
||||
// fact about the library.
|
||||
//
|
||||
// `surface` used to be declared in each scene file, and it drifted: nine scenes
|
||||
// said `canvas` while painting under a third of the frame, which is how a
|
||||
// section ended up standing on a few bright things and black. It is derived
|
||||
// now. A scene cannot lie about what it paints, because nobody writes it down.
|
||||
|
||||
import metadata from './metadata.json';
|
||||
import { readsHistory } from '../params/schema.js';
|
||||
|
||||
/**
|
||||
* The line between a picture and elements on one.
|
||||
*
|
||||
* Half the frame. Under it a scene is not somewhere a shot can happen — it is
|
||||
* the shot, or something in it, and what would be behind it is black.
|
||||
*
|
||||
* The same number does both jobs on purpose: `canvas` and "may be a ground" are
|
||||
* one idea, and having a scene that is a canvas but not ground-capable would be
|
||||
* a distinction with nothing behind it.
|
||||
*/
|
||||
export const GROUND_MIN = 0.5;
|
||||
|
||||
/**
|
||||
* The bias a GROUND is sampled with, wherever it appears.
|
||||
*
|
||||
* A bed is not sampled like a shot. It keeps a density floor whatever the
|
||||
* section wants — an intro biased sparse would otherwise draw the ground at
|
||||
* density zero and be thin again for a new reason — and it sits back through
|
||||
* energy and motion instead.
|
||||
*
|
||||
* Defined here rather than in the generator because the measurement has to use
|
||||
* it too. Coverage depends enormously on parameters: Metaballs paints 69% of
|
||||
* the frame sampled as a drop's shot and 27% sampled as an intro's bed, and a
|
||||
* label taken at the first bias tells you nothing about the second. So the
|
||||
* number that decides whether a scene may be a ground is measured AS a ground.
|
||||
* One definition, both places, or the two silently disagree.
|
||||
*/
|
||||
export const GROUND_BIAS = { energy: 0.5, density: 0.45, motion: 0.3 };
|
||||
|
||||
/**
|
||||
* The hand a GROUND is drawn with.
|
||||
*
|
||||
* `extremity` is how far toward the ends of its own ranges a track pushes every
|
||||
* parameter, and it is the right dial for a shot: the ends are where a scene is
|
||||
* most itself. It is the wrong dial for a bed. Half of what decides how much a
|
||||
* scene paints is a parameter with no `bias` key at all — Metaballs' threshold,
|
||||
* for one — so extremity is the only thing moving it, and one end of that range
|
||||
* is an empty frame. Measured: a ground drawn at the track's own temperament
|
||||
* landed on threshold 1.85 of 2.0 and painted 6% of the frame, while the same
|
||||
* scene measures 58% drawn moderately.
|
||||
*
|
||||
* So a bed is drawn near the middle and the extremes are left to the shot,
|
||||
* which is also the right instinct visually.
|
||||
*/
|
||||
export function groundTemperamentFrom(temperament) {
|
||||
if (!temperament) return temperament;
|
||||
return { ...temperament, extremity: temperament.extremity * 0.2, intensity: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* The song's hand, as it applies to a GROUND.
|
||||
*
|
||||
* The ink treatment — hatch, stipple, halftone, hollow — is how the song draws.
|
||||
* `hollow` draws outlines and no fill at all, which is a strong, legible
|
||||
* identity for the thing the section is ABOUT and a disaster for the thing it
|
||||
* stands on: a scene that paints 61% of the frame averaged over six identities
|
||||
* paints 2% in the song that asked for outlines, and the section renders black
|
||||
* with a ground under it. Measured, holding grounds to their worst identity
|
||||
* instead left four castable beds in the whole library.
|
||||
*
|
||||
* So the world is drawn solid and the hand is kept for the subject. Everything
|
||||
* else about the identity — the cast, the lattice, the line weight, the
|
||||
* palette — reaches the ground untouched, so it is still unmistakably this
|
||||
* song's world.
|
||||
*/
|
||||
export function groundPersonalityFrom(personality) {
|
||||
if (!personality || !personality.identity || !personality.identity.ink) return personality;
|
||||
const { identity } = personality;
|
||||
if (identity.ink.fill !== 'hollow') return personality;
|
||||
return {
|
||||
...personality,
|
||||
identity: { ...identity, ink: { ...identity.ink, fill: 'flat' } },
|
||||
};
|
||||
}
|
||||
|
||||
/** The bias `ground` would be sampled with inside `section`. */
|
||||
export function groundBiasFrom(bias) {
|
||||
return {
|
||||
...bias,
|
||||
// FLOORS, not reductions, on both of the axes that fill a frame.
|
||||
//
|
||||
// The first version of this lowered energy to make the bed sit back,
|
||||
// which turned out to be precisely how to empty it: scenes bias the
|
||||
// parameters that decide how much they paint against `energy` as often
|
||||
// as against `density` — Metaballs sizes its blobs on energy, so an
|
||||
// intro's ground drew radius 0.14 out of a 0.1–0.6 range and painted 4%
|
||||
// of the frame. A bed sits back through MOTION, which costs it nothing
|
||||
// in coverage, and through being behind everything else.
|
||||
density: Math.max(GROUND_BIAS.density, bias.density),
|
||||
energy: Math.max(GROUND_BIAS.energy, bias.energy),
|
||||
motion: Math.max(0, (bias.motion ?? 0.5) - 0.2),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* What a scene is, compositionally.
|
||||
*
|
||||
* canvas fills the frame. Belongs underneath, and two of them stacked is
|
||||
* two pictures fighting rather than one picture with depth.
|
||||
* composable mostly empty by design. Reads as elements ON something, and
|
||||
* shown alone it is a few bright things on black — which scores
|
||||
* well for variety and is thin to watch.
|
||||
*/
|
||||
export const SURFACES = ['canvas', 'composable'];
|
||||
|
||||
const ROW = (module) => (metadata.scenes && metadata.scenes[module.name]) || null;
|
||||
|
||||
/**
|
||||
* Coverage for a scene nobody has measured.
|
||||
*
|
||||
* Two ways to land here: a `layer3d`, which the measuring pass does not render,
|
||||
* and a scene added since the last refresh. Deliberately pessimistic — an
|
||||
* unmeasured scene must not be able to talk its way into the ground slot, which
|
||||
* `canGround` enforces by requiring a real row rather than by trusting this.
|
||||
*/
|
||||
const ASSUMED_COVERAGE = 0.15;
|
||||
|
||||
export function coverageOf(module) {
|
||||
const row = ROW(module);
|
||||
return row ? row.coverage : ASSUMED_COVERAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* How much this scene paints when it is used as a BED — measured at
|
||||
* GROUND_BIAS, which is the only figure that answers the question `canGround`
|
||||
* asks. See GROUND_BIAS for why the two numbers differ so much.
|
||||
*/
|
||||
export function groundCoverageOf(module) {
|
||||
const row = ROW(module);
|
||||
if (!row) return ASSUMED_COVERAGE;
|
||||
if (row.bedCoverageMean !== undefined) return row.bedCoverageMean;
|
||||
return row.bedCoverage === undefined ? row.coverage : row.bedCoverage;
|
||||
}
|
||||
|
||||
/**
|
||||
* What this scene paints as a bed in the IDENTITY THAT SUITS IT LEAST.
|
||||
*
|
||||
* A song's identity can gut a scene's fill — `hollow` ink draws outlines and no
|
||||
* fill, a dark palette drops it under the threshold — and the spread is wide:
|
||||
* Metaballs paints 61% averaged over six identities and 14% in the worst of
|
||||
* them, which is a black frame with a ground under it.
|
||||
*
|
||||
* Held as a SEPARATE, lower bar rather than as the eligibility number. Demanding
|
||||
* the worst case clear 50% leaves four castable grounds in the whole library —
|
||||
* every video in the world standing on one of four beds is a worse failure than
|
||||
* an occasional dim intro. So: fills the frame on average, and never vanishes.
|
||||
*/
|
||||
export function groundFloorCoverageOf(module) {
|
||||
const row = ROW(module);
|
||||
if (!row) return ASSUMED_COVERAGE;
|
||||
return row.bedCoverage === undefined ? groundCoverageOf(module) : row.bedCoverage;
|
||||
}
|
||||
|
||||
/** A ground may dim under a hostile identity. It may not disappear. */
|
||||
export const GROUND_FLOOR_MIN = 0.25;
|
||||
|
||||
export function isMeasured(module) {
|
||||
return ROW(module) !== null;
|
||||
}
|
||||
|
||||
/** Derived, never declared. See the header. */
|
||||
export function surfaceOf(module) {
|
||||
return coverageOf(module) >= GROUND_MIN ? 'canvas' : 'composable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a scene can be the ground under a section.
|
||||
*
|
||||
* Measured to fill the frame, and self-contained. The second half rules out
|
||||
* `prev()`: that returns the whole composited previous frame, INCLUDING the
|
||||
* layers above this one, so a datamosh under a shot is not grounding it, it is
|
||||
* eating it. Measured when one first became a bed — the render stopped
|
||||
* reproducing from a seek, and two WebGL contexts diverged by 91/255 against a
|
||||
* tolerance of 4.
|
||||
*/
|
||||
export function canGround(module) {
|
||||
return isMeasured(module)
|
||||
&& surfaceOf(module) === 'canvas'
|
||||
&& groundCoverageOf(module) >= GROUND_MIN
|
||||
&& groundFloorCoverageOf(module) >= GROUND_FLOOR_MIN
|
||||
&& !readsHistory(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* How unalike two scenes are, structurally — 0 is the same picture.
|
||||
*
|
||||
* Both profiles are means over the same six songs, so this is a fair comparison
|
||||
* in a way that two arbitrary renders would not be. Colour is excluded: six
|
||||
* palettes would otherwise make any two scenes look unalike, and the whole
|
||||
* point is to find pairs that differ in STRUCTURE.
|
||||
*
|
||||
* This is the quantity that makes a stack interesting rather than merely full.
|
||||
* Two scenes at 0.02 layered over each other are one picture at double density;
|
||||
* the same two at 0.3 are a picture with something happening in it.
|
||||
*/
|
||||
export function structuralDistance(a, b) {
|
||||
const rowA = ROW(a), rowB = ROW(b);
|
||||
if (!rowA || !rowB || !rowA.profile || !rowB.profile) return null;
|
||||
|
||||
let total = 0, blocks = 0;
|
||||
for (const block of ['scale', 'orient', 'layout', 'region', 'texture']) {
|
||||
const x = rowA.profile[block], y = rowB.profile[block];
|
||||
if (!x || !y || x.length !== y.length) continue;
|
||||
// Chi-square, matching what variety/signature.js compares descriptors
|
||||
// with. Duplicated rather than imported because this runs inside the
|
||||
// generator and the checks must not be a dependency of it.
|
||||
let d = 0;
|
||||
for (let i = 0; i < x.length; i++) {
|
||||
const sum = x[i] + y[i];
|
||||
if (sum > 1e-9) d += ((x[i] - y[i]) ** 2) / sum;
|
||||
}
|
||||
total += Math.min(1, d / 2);
|
||||
blocks++;
|
||||
}
|
||||
return blocks ? total / blocks : null;
|
||||
}
|
||||
|
||||
/** The variety score from the gallery: how much a scene changes between songs. */
|
||||
export function varietyOf(module) {
|
||||
const row = ROW(module);
|
||||
return row ? row.variety : 0;
|
||||
}
|
||||
|
||||
export const METADATA = metadata;
|
||||
Reference in New Issue
Block a user