Finish the migration: all 65 scenes on the identity artifacts
Two helpers made the bulk of it mechanical. `inkStroke` is a drop-in for
sigEdge — the same line at the identity's weight rather than the track's — and
`castForm` is a drop-in for sigForm, same signature so call sites do not change
shape. With those in place the substitution table is one-to-one:
sigForm( -> castForm( sigShape( -> castMain( sigEdge( -> inkStroke(
Forty-seven scenes went through that pass in one run: twenty-six take the cast
and the ink, twenty-one take the ink alone. Then the gate ran over all sixty-five
and found three the pass had broken, which is the entire reason it exists.
Spectrum Sculpture rendered pure black. It had been using sigShape as a RADIAL
METRIC rather than drawing it, and the cast carries notches and a hollow — an
annulus used as a radius turns a sculpture inside out. Reverted to sigShape and
dropped to ink only. The lesson generalises: a scene that reads a form as
geometry is not a scene that draws it, and the classifier cannot tell those
apart from the source.
Eclipse Field stopped honouring its `style` trait. It opts out of surface grain,
so sigEdge was its only style evidence, and the ink replaced it. The trait claim
is now dropped — and so is the lint change that had let inkMask count as style
evidence, which was wrong and was hiding exactly this. A trait is a property of
the track a scene may honour; an artifact is content it draws. Taking the ink
says nothing about whether a scene responds to u_sigLine.
Circuit Bloom went empty at the bottom of its `grown` range, where the pads were
carried by a hairline and the ink's stroke is thinner than the edge it replaced.
Now filled as well as stroked.
Also: the backtick check now covers every shader literal rather than only the
preamble, because a mechanical pass over sixty files reintroduced one
immediately. Three rounds lost to that typo is enough.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -103,43 +103,69 @@ const CONTRACT_UNIFORMS = new Set([
|
||||
* cast in the hexagon video and be the one shot that looks filmed elsewhere.
|
||||
* So the claim is machine-checked against the source rather than trusted.
|
||||
*/
|
||||
// A stage that draws the song's CAST is expressing `shape` more completely than
|
||||
// A scene that draws the song's CAST is expressing `shape` more completely than
|
||||
// sigShape ever did — the form is the subject rather than a hint applied to one
|
||||
// — so castMain/castChorus count as evidence. Likewise inkMask/inkValue are the
|
||||
// style trait carried out in full. See scenes/stage/README.md.
|
||||
// — so castMain/castChorus count as evidence.
|
||||
//
|
||||
// The INK does not work the same way and must not be listed under `style`. A
|
||||
// trait is a property of the track that a scene may honour; an artifact is
|
||||
// content it draws. Taking the ink says nothing about whether the scene
|
||||
// responds to u_sigLine, and treating it as evidence let Eclipse Field claim a
|
||||
// trait it had stopped honouring — which the runtime gate then caught.
|
||||
// The same idea as TRAIT_EVIDENCE, for the identity artifacts: a scene that
|
||||
// declares it consumes the cast has to actually draw it. Without this,
|
||||
// `consumes` is a comment, and the migration becomes unverifiable the moment it
|
||||
// is more than a handful of files.
|
||||
const ARTIFACT_EVIDENCE = {
|
||||
cast: /\bcast(Main|Chorus|SDF)\s*\(/,
|
||||
ink: /\bink(Mask|Value|Pattern)\s*\(/,
|
||||
cast: /\bcast(Main|Chorus|SDF|Form)\s*\(/,
|
||||
ink: /\bink(Mask|Value|Pattern|Stroke)\s*\(/,
|
||||
staging: /\b(stageNode|stageScale)\s*\(/,
|
||||
};
|
||||
|
||||
const TRAIT_EVIDENCE = {
|
||||
shape: /\b(sig(Shape|Form)|cast(Main|Chorus|SDF))\s*\(/,
|
||||
shape: /\b(sig(Shape|Form)|cast(Main|Chorus|SDF|Form))\s*\(/,
|
||||
camera: /\bsigCamera\s*\(/,
|
||||
space: /\b(sigHorizonY|sigAir)\s*\(|\bu_sig(Horizon|Depth|Wash)\b/,
|
||||
style: /\b(sigEdge|sigGrain|sigFolded|inkMask|inkValue|inkPattern)\s*\(|\bu_sig(Line|Soft|Texture|Fold)\b/,
|
||||
style: /\b(sigEdge|sigGrain|sigFolded)\s*\(|\bu_sig(Line|Soft|Texture|Fold)\b/,
|
||||
};
|
||||
|
||||
// The shader preamble is a JS template literal, so a backtick anywhere inside
|
||||
// it silently closes the literal. Twice now that has produced a check page that
|
||||
// hangs on "starting…" with an empty console, which is an expensive way to find
|
||||
// a typo. One grep is cheaper.
|
||||
console.log('\nshader contract');
|
||||
// Every shader in this project lives inside a JS template literal, so a
|
||||
// backtick anywhere in one silently closes it. Three times now that has cost a
|
||||
// debugging round: twice in the preamble, where it produced a check page that
|
||||
// hung on "starting…" with an empty console, and once in a scene, where at
|
||||
// least the module failed to parse loudly. A GLSL comment is the natural place
|
||||
// to reach for backticks when quoting a param name, which is exactly why this
|
||||
// keeps happening.
|
||||
//
|
||||
// Checked across the contract AND every scene, since the scene case is the one
|
||||
// a mechanical pass over sixty files will keep reintroducing.
|
||||
console.log('\nshader literals');
|
||||
{
|
||||
const src = readFileSync(join(SRC, 'engine/shader-contract.js'), 'utf8');
|
||||
const literal = src.slice(src.indexOf('export const PREAMBLE = `') + 25);
|
||||
const body = literal.slice(0, literal.indexOf('\n`;'));
|
||||
const stray = body.split('\n').filter((l) => l.includes('`') && !l.includes('${'));
|
||||
if (stray.length) {
|
||||
fail(`shader preamble contains a backtick, which closes the template literal:\n` +
|
||||
stray.map((l) => ` ${l.trim()}`).join('\n'));
|
||||
} else {
|
||||
ok('preamble has no stray backticks');
|
||||
const targets = [join(SRC, 'engine/shader-contract.js'), ...walk(join(SRC, 'scenes'))];
|
||||
let clean = 0;
|
||||
for (const file of targets) {
|
||||
const src = readFileSync(file, 'utf8');
|
||||
const rel = relative(SRC, file).replace(/\\/g, '/');
|
||||
// Every template literal in the file, then the lines inside them that
|
||||
// carry a backtick without being an interpolation.
|
||||
const stray = [];
|
||||
const rx = /`([\s\S]*?)`/g;
|
||||
let m;
|
||||
while ((m = rx.exec(src)) !== null) {
|
||||
if (!/\bvec4 scene|precision highp|void main/.test(m[1])) continue;
|
||||
for (const line of m[1].split('\n')) {
|
||||
if (line.includes('`') && !line.includes('${')) stray.push(line);
|
||||
}
|
||||
}
|
||||
// A shader body that swallowed a closing backtick shows up as an
|
||||
// unbalanced count across the file.
|
||||
const ticks = (src.match(/`/g) || []).length;
|
||||
if (stray.length || ticks % 2 !== 0) {
|
||||
fail(`${rel}: backtick inside a shader literal — it closes the string` +
|
||||
(stray.length ? `:\n${stray.map((l) => ` ${l.trim()}`).join('\n')}` : ''));
|
||||
} else clean++;
|
||||
}
|
||||
if (clean === targets.length) ok(`${clean} shader literals balanced, no stray backticks`);
|
||||
}
|
||||
|
||||
console.log('\nscene schema lint');
|
||||
|
||||
Reference in New Issue
Block a user