Run both variety tests against the song bank, and fix the ceiling twice more

The tests now run on real bank entries. The seed gates used to run on a
two-section synthetic whose only kinds were intro and outro, so half the scene
library was unreachable and the number was measuring that rather than the seed.

Probes are labelled by section kind and occurrence rather than by index, which
is what makes two different songs comparable at all — they have different
section counts, so probe 3 of one is not probe 3 of the other, and matching by
position would compare a drop against an outro and score the mismatch as
variety. For two seeds of one song the labels are identical and this changes
nothing, which is the point.

The song test measures one thing the seed test does not: coupling, the rank
correlation between how different two tracks sound and how different their
videos look. Separation alone can be had by a generator that ignores the audio
and hashes the file, and that would be a perfect score for a completely wrong
video. Separation without coupling is not variety, it is a different seed per
file.

The ceiling took two more attempts. Recasting every layer at random averages a
dozen scenes together and a dozen random scenes converge on the same generic
busy image, so two references came out closer to each other than two real
videos and blocks scored over 100% of achievable. Forcing one scene per
reference collapsed the other way: a video that never changes scene has almost
no internal variation, so the ceiling landed BELOW the floor, which is a
within-video quantity. A reference has to match the structure of what it bounds.
They now keep the real pipeline — rosters, shots, per-section sampling, so a
reference rotates between three or four scenes exactly as a real video does —
while drawing from disjoint slices of the library. Same complexity, nothing in
common.

The fingerprint measurement had the same shape of error: pooling every probe
mixed in how much each video varies over its own length, which is large for
everything, and washed the answer to a flat 100% while the separation score said
almost everything was collapsed. One vector per video now.

Both tests fail as committed. Seed separation 0.04, song separation 0.03,
coupling -0.03 — two different songs differ from each other by about as much as
one video differs from itself, and that difference has no relationship to the
music. Colour scores 112%.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dejvino
2026-08-17 20:00:14 +02:00
co-authored by Claude Opus 5
parent e0c0dd664a
commit 2bbf5bcf42
6 changed files with 418 additions and 58 deletions
+7 -6
View File
@@ -82,8 +82,9 @@ const DURATION = 120;
const cache = new Map();
/** One song from the bank, analysed. */
export function song(name, { fps = 60 } = {}) {
if (cache.has(name)) return cache.get(name);
export function song(name, { fps = 60, duration = DURATION } = {}) {
const cacheKey = `${name}:${fps}:${duration}`;
if (cache.has(cacheKey)) return cache.get(cacheKey);
const spec = SONGS.find((s) => s.name === name);
if (!spec) throw new Error(`no song named "${name}" — have: ${SONGS.map((s) => s.name).join(', ')}`);
@@ -91,7 +92,7 @@ export function song(name, { fps = 60 } = {}) {
...spec,
track: FeatureTrack.fromAudioBuffer(synthesizeSong({
bpm: spec.bpm,
duration: DURATION,
duration,
arrangement: spec.arrangement,
brightness: spec.brightness,
noise: spec.noise,
@@ -101,7 +102,7 @@ export function song(name, { fps = 60 } = {}) {
seed: 1 + SONGS.indexOf(spec),
}), { fps }),
};
cache.set(name, entry);
cache.set(cacheKey, entry);
return entry;
}
@@ -111,11 +112,11 @@ export function song(name, { fps = 60 } = {}) {
* `count` takes an evenly spaced subset rather than the first n, so a cheap run
* still spans the space instead of testing five slow ambient tracks.
*/
export function songBank({ count = null, fps = 60 } = {}) {
export function songBank({ count = null, fps = 60, duration = DURATION } = {}) {
const specs = count && count < SONGS.length
? Array.from({ length: count }, (_, i) => SONGS[Math.round(i * (SONGS.length - 1) / (count - 1))])
: SONGS;
return specs.map((s) => song(s.name, { fps }));
return specs.map((s) => song(s.name, { fps, duration }));
}
/** Feature axes the bank claims to span, and where each is read. */