Close out the twenty: what the gates taught

Three lessons worth more than the scenes: a slow axis has to move a large
low-frequency area or the camera's own drift beats it, whole-frame luminance on
the kick is the strobe the flash gate exists for and it arrives by accident, and
a style trait expressed only as grain measures as no style at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dejvino
2026-08-17 07:00:45 +02:00
co-authored by Claude Opus 5
parent 905c217193
commit 0c5b0a8d4d
4 changed files with 160 additions and 42 deletions
+34
View File
@@ -87,6 +87,7 @@ for (const spec of SONGS) {
arrangement: spec.arrangement,
brightness: spec.brightness,
noise: spec.noise,
noiseColour: spec.noiseColour ?? null,
dynamics: spec.dynamics,
seed: 1 + SONGS.indexOf(spec),
});
@@ -146,6 +147,39 @@ for (const axis of AXES) {
if (bins.size < 3) problems.push(`${axis.label} occupies only ${bins.size}/5 of its range`);
}
// --- what the bank CANNOT decouple ----------------------------------------
// Spread on every axis is not the same as covering the space. Two axes that
// move together mean whole combinations are unrepresentable, and a downstream
// test would then attribute to the generator what is really a hole in the bank.
//
// The big one here is physical rather than fixable: spectral flatness measures
// how noise-like a spectrum is, and a dark signal is one whose spectrum is
// tilted, so "dark and very noisy" is only weakly reachable. Real material has
// the same correlation. It is reported rather than engineered away.
console.log('\n AXIS CORRELATION — |r| near 1 means the bank cannot vary these independently\n');
const corr = (a, b) => {
const ma = a.reduce((x, y) => x + y, 0) / a.length;
const mb = b.reduce((x, y) => x + y, 0) / b.length;
let num = 0, da = 0, db = 0;
for (let i = 0; i < a.length; i++) {
num += (a[i] - ma) * (b[i] - mb);
da += (a[i] - ma) ** 2;
db += (b[i] - mb) ** 2;
}
return num / (Math.sqrt(da * db) || 1e-9);
};
const series = Object.fromEntries(AXES.map(
(a) => [a.key, built.map((b) => b.measured[a.key])]));
for (let i = 0; i < AXES.length; i++) {
for (let j = i + 1; j < AXES.length; j++) {
const r = corr(series[AXES[i].key], series[AXES[j].key]);
if (Math.abs(r) < 0.6) continue;
console.log(` ${AXES[i].label}${AXES[j].label}: r = ${r.toFixed(2)}` +
(Math.abs(r) > 0.85 ? ' ← effectively one axis' : ''));
if (Math.abs(r) > 0.85) problems.push(`${AXES[i].label} and ${AXES[j].label} are collinear (r ${r.toFixed(2)})`);
}
}
// Section kinds are the casting gate, so coverage of them is not optional: a
// bank with no drop cannot reach the geometric, glitch or structural families
// no matter how widely its tempo spreads.