Two devices that were built, wired up, and doing nothing. OVERLAYS. buildStack gated layering on `surfaceOf(m) === 'composable'`, and no scene in the library declared `surface` — so the only thing that could ever sit on top was the one scene declaring `role: 'accent'`, and the overlay roster excluded accents by construction. Empty intersection, every time: 0/144 stacks carried an overlay. Every layered frame in every song was the same particle field. Labelled the library from the phase 12 coverage gate rather than by eye: the 37 scenes painting under 30% of the frame are composable. Particle Field loses its privileged slot and becomes one of them, keeping only `background: false`, which is the honest part — points in empty space cannot carry a section alone. The reserved accent slot is gone; one roster, two passes at it. 1 distinct overlay scene becomes 27, and the rate lands at 34% of stacks after trimming a base chance that had been tuned while the branch was dead. THE CAMERA. framing.shift moved the frame by a median of 0.029 of a half-frame at a fresh random angle every shot, so successive offsets cancelled and the median jump at a cut was 0.014. Present in every frame, visible in none. look/Camera.js is the director's camera department: the story says tension, order and which act a section is in, and this turns that into where the frame looks and how it travels there. Each director names a camera. Jump distance follows tension and act, speed follows energy, and the curve is one of four. Cuts choose between reframing and matching, so two scenes can still read as one place. Median offset 0.190, median reframe 0.135, and 94% of shots now move during the shot rather than only at the cut. Four new gates, each with a FLOOR — the device this replaces passed every existing check while doing nothing, because the only bound was a ceiling. Also lands the in-progress Epic 4 story layer it builds on: Story.js, phase 13, and the direction statistic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
162 lines
7.0 KiB
JavaScript
162 lines
7.0 KiB
JavaScript
// Which families answer which section kind — as a per-track CHOICE rather than
|
|
// as a constant.
|
|
//
|
|
// This was a module-level table in LookGenerator, identical for every track ever
|
|
// generated: an intro was always minimal/flow/organic, a drop always
|
|
// geometric/glitch/structural, and intro and outro were literally the same list.
|
|
// Every video therefore made the same genre decisions before a single seeded
|
|
// draw happened, which is a large source of cross-track sameness hiding inside
|
|
// something that looks like configuration.
|
|
//
|
|
// Measured, the cost was concrete: across twelve tracks, twenty-nine of
|
|
// forty-two scenes were cast in none of them. The library was not too small —
|
|
// most of it was unreachable.
|
|
//
|
|
// A director is one coherent point of view about what a song looks like. Not a
|
|
// shuffle of families: each mapping is internally consistent, and the ordering
|
|
// within a kind matters because the first family is weighted three times the
|
|
// third. Two tracks with different directors disagree about what a drop IS,
|
|
// which is the level at which videos should differ.
|
|
|
|
/**
|
|
* Families a QUIET section is allowed to draw from, whatever the director
|
|
* thinks. Phase 7 has enforced this since minimal existed, and the first draft
|
|
* of this file broke it — `corrupt` opened on glitch and `geometer` opened on
|
|
* geometric, on the theory that a point of view should apply everywhere.
|
|
*
|
|
* It should not. An intro that opens on a strobing scene and a breakdown that
|
|
* answers a lull with a dense pattern are not bold, they are the two specific
|
|
* mistakes the family coupling was introduced to prevent, and a viewer meets
|
|
* them within fifteen seconds of pressing play. So intro, breakdown and outro
|
|
* are off limits to the loud families for every director.
|
|
*
|
|
* The identity lives in build, drop and sustain — half the kinds, and the half
|
|
* anyone remembers — plus which of the restful families a director leads with
|
|
* when it is being quiet.
|
|
*/
|
|
export const RESTFUL_FAMILIES = ['minimal', 'flow', 'organic'];
|
|
const QUIET_KINDS = ['intro', 'breakdown', 'outro'];
|
|
|
|
/**
|
|
* Each director maps every section kind to three families, most-preferred
|
|
* first. Every kind must be present — a missing one silently falls back to the
|
|
* whole library and the point of view is lost exactly where it matters most.
|
|
*/
|
|
export const DIRECTORS = [
|
|
{
|
|
name: 'ambient',
|
|
// Patient camera to match: long moves, mostly along one line. See
|
|
// look/Camera.js — a director's point of view now includes how it
|
|
// shoots, not only what it points at.
|
|
camera: 'contemplative',
|
|
// The original table. A drop resolves into geometry; everything quiet is
|
|
// minimal. Still the most broadly applicable, so it keeps the most weight.
|
|
weight: 3,
|
|
families: {
|
|
intro: ['minimal', 'flow', 'organic'],
|
|
build: ['structural', 'geometric', 'flow'],
|
|
drop: ['geometric', 'glitch', 'structural'],
|
|
sustain: ['organic', 'flow', 'geometric'],
|
|
breakdown: ['minimal', 'organic', 'flow'],
|
|
outro: ['minimal', 'flow', 'organic'],
|
|
},
|
|
},
|
|
{
|
|
name: 'brutalist',
|
|
// Holds, then commits to one large move. Architecture is looked AT.
|
|
camera: 'deliberate',
|
|
// Everything is architecture. Quiet means empty rather than soft, so it
|
|
// leads on minimal and reaches for organic last.
|
|
weight: 2,
|
|
families: {
|
|
intro: ['minimal', 'organic', 'flow'],
|
|
build: ['structural', 'geometric', 'glitch'],
|
|
drop: ['structural', 'glitch', 'geometric'],
|
|
sustain: ['structural', 'geometric', 'flow'],
|
|
breakdown: ['minimal', 'flow', 'organic'],
|
|
outro: ['minimal', 'organic', 'flow'],
|
|
},
|
|
},
|
|
{
|
|
name: 'organicist',
|
|
// Never settles, because nothing here is ever finished settling.
|
|
camera: 'roaming',
|
|
// Nothing is ever built; things grow and dissolve. Deliberately never
|
|
// reaches for glitch — a point of view is defined by what it refuses.
|
|
weight: 2,
|
|
families: {
|
|
intro: ['organic', 'flow', 'minimal'],
|
|
build: ['organic', 'flow', 'structural'],
|
|
drop: ['organic', 'geometric', 'flow'],
|
|
sustain: ['organic', 'flow', 'minimal'],
|
|
breakdown: ['organic', 'minimal', 'flow'],
|
|
outro: ['flow', 'organic', 'minimal'],
|
|
},
|
|
},
|
|
{
|
|
name: 'corrupt',
|
|
// Cuts with the camera already moving.
|
|
camera: 'kinetic',
|
|
// The signal is damaged and the damage is the subject — everywhere the
|
|
// damage is allowed to be. Its quiet sections lead on flow, so the calm
|
|
// reads as signal drifting rather than as rest.
|
|
weight: 2,
|
|
families: {
|
|
intro: ['flow', 'minimal', 'organic'],
|
|
build: ['glitch', 'structural', 'geometric'],
|
|
drop: ['glitch', 'geometric', 'structural'],
|
|
sustain: ['glitch', 'organic', 'flow'],
|
|
breakdown: ['minimal', 'flow', 'organic'],
|
|
outro: ['flow', 'minimal', 'organic'],
|
|
},
|
|
},
|
|
{
|
|
name: 'geometer',
|
|
// Small, exact, always arrives — the pattern is the subject and the
|
|
// camera does not editorialise about it.
|
|
camera: 'precise',
|
|
// Pattern first, everywhere, at every energy. The drop is not an
|
|
// explosion, it is the pattern at its densest.
|
|
weight: 2,
|
|
families: {
|
|
intro: ['minimal', 'flow', 'organic'],
|
|
build: ['geometric', 'structural', 'flow'],
|
|
drop: ['geometric', 'glitch', 'structural'],
|
|
sustain: ['geometric', 'organic', 'flow'],
|
|
breakdown: ['minimal', 'organic', 'flow'],
|
|
outro: ['minimal', 'flow', 'organic'],
|
|
},
|
|
},
|
|
];
|
|
|
|
export const DIRECTOR_NAMES = DIRECTORS.map((d) => d.name);
|
|
|
|
/**
|
|
* Cast the director for a track.
|
|
*
|
|
* The audio tilts the odds and never decides: a noisy, loud track is more
|
|
* likely to be filmed as corrupt or brutalist and a tonal one as organicist,
|
|
* but every director stays reachable for every track. Predictable mapping from
|
|
* measured features to look is the failure mode this whole layer exists to
|
|
* avoid — it is how a library ends up with one house style per genre.
|
|
*/
|
|
export function pickDirector(summary, rng) {
|
|
const noisy = Math.min(1, (summary.meanFlatness ?? 0.2) * 3);
|
|
const bright = summary.meanCentroid ?? 0.5;
|
|
|
|
const weights = DIRECTORS.map((d) => {
|
|
let w = d.weight;
|
|
if (d.name === 'corrupt') w *= 0.5 + noisy * 2.0;
|
|
if (d.name === 'brutalist') w *= 0.6 + noisy * 1.2;
|
|
if (d.name === 'organicist') w *= 0.6 + (1 - noisy) * 1.4;
|
|
if (d.name === 'geometer') w *= 0.7 + bright * 1.0;
|
|
return w;
|
|
});
|
|
|
|
return rng.pickWeighted(DIRECTORS, weights);
|
|
}
|
|
|
|
export function directorByName(name) {
|
|
return DIRECTORS.find((d) => d.name === name) || DIRECTORS[0];
|
|
}
|