Fix two checks the migration broke, and re-aim the signature gate

Phase 2 kept its own list of contract uniforms and never learned about the
identity artifacts, so Prism Bloom reading u_castSides looked like an undeclared
uniform. It now takes the list from the contract itself.

Phase 9 demanded that every cast scene honour the track's signature, which was
true when the signature was a hard filter and stopped being true when it became
a weight. The check was asserting the old contract against the new generator.

Rather than delete it, it now checks the claim that actually matters. A track may
reach outside its signature — that change was made deliberately, because the
filter was disqualifying a third of the library and funnelling eleven scenes into
half of all videos — but it must still LEAN on it. Honouring scenes have to
carry the majority of the cast against a chance baseline near 25%, and section
anchors, which open a section and return most often, have to honour it almost
always.

89/89 checks pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dejvino 2026-08-18 06:55:28 +02:00
parent df1ce9a957
commit 4267c588aa
2 changed files with 32 additions and 10 deletions

View File

@ -13,7 +13,9 @@ import { defaultValues, sweepValues, validateModule } from '../params/schema.js'
import { serializeParams, deserializeParams } from '../params/serialize.js'; import { serializeParams, deserializeParams } from '../params/serialize.js';
import { ParamPanel } from '../ui/ParamPanel.js'; import { ParamPanel } from '../ui/ParamPanel.js';
import { frameLuminance, frameVariance } from '../engine/hash.js'; import { frameLuminance, frameVariance } from '../engine/hash.js';
import { AUDIO_UNIFORMS, SIGNATURE_UNIFORMS } from '../engine/shader-contract.js'; import {
AUDIO_UNIFORMS, SIGNATURE_UNIFORMS, IDENTITY_UNIFORMS,
} from '../engine/shader-contract.js';
import { featureProviderFor } from '../audio/FeatureTrack.js'; import { featureProviderFor } from '../audio/FeatureTrack.js';
import { testTrack } from './phase1.js'; import { testTrack } from './phase1.js';
@ -52,6 +54,7 @@ check(2, 'declared uniforms and shader sources agree both ways', () => {
'u_seed', 'u_opacity', 'u_colors', 'u_colorCount', 'u_prev', 'u_hasPrev', 'u_seed', 'u_opacity', 'u_colors', 'u_colorCount', 'u_prev', 'u_hasPrev',
...AUDIO_UNIFORMS, ...AUDIO_UNIFORMS,
...Object.keys(SIGNATURE_UNIFORMS), ...Object.keys(SIGNATURE_UNIFORMS),
...Object.keys(IDENTITY_UNIFORMS),
]); ]);
const problems = []; const problems = [];
for (const module of scenes) { for (const module of scenes) {

View File

@ -119,20 +119,39 @@ check(9, 'every trait has enough scenes to build a track from', () => {
(thin.length ? ` — too thin: ${thin.map(([t]) => t).join(', ')}` : '')); (thin.length ? ` — too thin: ${thin.map(([t]) => t).join(', ')}` : ''));
}); });
check(9, 'no scene is cast in a track it cannot express', () => { check(9, 'casting leans hard on the track\'s signature', () => {
// The whole point. A scene that ignores the trait a track is built on is the // This used to demand that EVERY cast scene honour the signature, because
// shot that was obviously filmed somewhere else. // the signature was a hard filter. It is a weight now — measured, the filter
const problems = []; // was the largest single cause of sameness in the generator, disqualifying
// eleven scenes into half of all videos and leaving a third of the library
// uncastable. See look/Personality.js signatureWeight.
//
// So the claim being checked changes shape. A track may reach outside its
// signature; what it must not do is stop leaning on it. Honouring scenes
// should dominate the cast by a wide margin, and the anchor of each section
// — the scene that opens it and returns most often — should almost always
// honour it.
let honoured = 0, total = 0, anchorsHonoured = 0, anchors = 0;
for (const look of looks()) { for (const look of looks()) {
const signature = look.personality.signature; const signature = look.personality.signature;
for (const module of castOf(look)) { for (const module of castOf(look)) {
if (!sceneHonours(module, signature)) { total++;
problems.push(`${module.name} cast in a ${signature.join('+')} track`); if (sceneHonours(module, signature)) honoured++;
} }
for (const section of look.sections) {
anchors++;
if (sceneHonours(section.layers[0].module, signature)) anchorsHonoured++;
} }
} }
return expect(problems.length === 0, const share = total ? honoured / total : 0;
[...new Set(problems)].slice(0, 4).join(' · ') || 'every scene honours its track'); const anchorShare = anchors ? anchorsHonoured / anchors : 0;
// A signature of two traits leaves roughly a quarter of the library eligible,
// so chance alone would land near 0.25. Anything close to that means the
// weighting has stopped meaning anything.
return expect(share >= 0.55 && anchorShare >= 0.6,
`${(share * 100).toFixed(0)}% of cast scenes honour the signature ` +
`(chance ~25%), ${(anchorShare * 100).toFixed(0)}% of section anchors do`);
}); });
check(9, 'the signature still leaves a track enough scenes to cut between', () => { check(9, 'the signature still leaves a track enough scenes to cut between', () => {