Tell a dead shader apart from a boring scene in the gallery

A failed shader compile renders black, and black frames are identical to each
other, so every block scores 0.000 and the row reads as the least varied scene
in the library rather than as a broken one. That happened yesterday — the
subject helpers were declared above the ink they call, GLSL has no forward
declarations, the whole preamble failed to compile, and the gallery reported a
variety of exactly 0.000 with no indication anything was wrong. It is a
particularly bad failure mode for a tool whose entire job is ranking scenes by
that number.

A scene whose first frame has no luminance and no variance is now reported as
broken, with both figures and a pointer to the console, and the row is coloured
apart from the merely flat ones so it cannot be mistaken for a bad score. The
summary counts them separately.

Verified against both answers rather than only the happy one: a healthy Moiré
Grid comes back 0.0365 with no error, and a deliberately broken copy of the same
scene comes back with the diagnosis instead of a zero.

Recorded in HOWTO-variety.md alongside the other instrument traps, since the
general lesson outlives this instance — a broken render produces the most boring
possible numbers rather than an error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dejvino 2026-08-18 16:55:16 +02:00
parent c950f8f3b1
commit 8defef3ed0
3 changed files with 29 additions and 4 deletions

View File

@ -213,6 +213,10 @@ sample size is not a difference.
Two more traps, both of which have bitten: Two more traps, both of which have bitten:
- **A score of exactly 0.000 is a dead shader, not a boring scene.** The gallery
now says so outright — the row goes purple and carries the luminance and
variance — but the underlying trap is general: a broken render produces the
most boring possible numbers rather than an error.
- **A permanently-zero block looks like a property of your scene.** The gallery - **A permanently-zero block looks like a property of your scene.** The gallery
built four of the descriptor's five blocks for weeks; `motion` came back 0.000 built four of the descriptor's five blocks for weeks; `motion` came back 0.000
for every scene and depressed every score by a fifth. If a block is identical for every scene and depressed every score by a fifth. If a block is identical

View File

@ -43,6 +43,7 @@
.row.flat { border-color: #ef4444; } .row.flat { border-color: #ef4444; }
.row.thin { border-color: #eab308; } .row.thin { border-color: #eab308; }
.row.good { border-color: #22c55e; } .row.good { border-color: #22c55e; }
.row.broken { border-color: #a855f7; background: #150e1c; }
.head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 6px; flex-wrap: wrap; } .head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 6px; flex-wrap: wrap; }
.name { font-size: 14px; color: #e6eaf2; } .name { font-size: 14px; color: #e6eaf2; }
.fam { color: #6b7280; } .fam { color: #6b7280; }
@ -112,7 +113,7 @@ let contexts = [];
// Graded against the bar rather than against taste: below it is a failure, and // Graded against the bar rather than against taste: below it is a failure, and
// the band just above it is where a scene is passing by too little to trust. // the band just above it is where a scene is passing by too little to trust.
const grade = (v) => (v < MIN_VARIETY ? 'flat' : v < MIN_VARIETY * 2 ? 'thin' : 'good'); const grade = (v, err) => (err ? 'broken' : v < MIN_VARIETY ? 'flat' : v < MIN_VARIETY * 2 ? 'thin' : 'good');
function draw() { function draw() {
const mode = sortSel.value; const mode = sortSel.value;
@ -134,7 +135,7 @@ function draw() {
out.appendChild(cut); out.appendChild(cut);
} }
const el = document.createElement('div'); const el = document.createElement('div');
el.className = `row ${grade(row.variety)}`; el.className = `row ${grade(row.variety, row.error)}`;
const blocks = Object.entries(row.byBlock || {}) const blocks = Object.entries(row.byBlock || {})
.map(([k, v]) => `${k} ${v.toFixed(3)}`).join(' · '); .map(([k, v]) => `${k} ${v.toFixed(3)}`).join(' · ');
el.innerHTML = ` el.innerHTML = `
@ -169,8 +170,10 @@ function draw() {
sortSel.addEventListener('change', draw); sortSel.addEventListener('change', draw);
function summarise(built) { function summarise(built) {
const flat = rows.filter((r) => r.variety < MIN_VARIETY).length; const flat = rows.filter((r) => r.variety < MIN_VARIETY && !r.error).length;
const broken = rows.filter((r) => r.error).length;
return `${rows.length} scenes · ${flat} below the ${MIN_VARIETY} bar` + return `${rows.length} scenes · ${flat} below the ${MIN_VARIETY} bar` +
(broken ? ` · ${broken} BROKEN` : '') +
(built ? ` · built ${new Date(built).toLocaleString()}` : ''); (built ? ` · built ${new Date(built).toLocaleString()}` : '');
} }

View File

@ -24,6 +24,7 @@ import { songBank } from '../audio/songbank.js';
import { generateLook } from '../look/LookGenerator.js'; import { generateLook } from '../look/LookGenerator.js';
import { describeIdentity } from '../look/Identity.js'; import { describeIdentity } from '../look/Identity.js';
import { frameDescriptor, motionDescriptor } from './variety/descriptors.js'; import { frameDescriptor, motionDescriptor } from './variety/descriptors.js';
import { frameLuminance, frameVariance } from '../engine/hash.js';
import { descriptorDistance, STRUCTURAL } from './variety/signature.js'; import { descriptorDistance, STRUCTURAL } from './variety/signature.js';
const THUMB = { width: 256, height: 144 }; const THUMB = { width: 256, height: 144 };
@ -140,7 +141,24 @@ export function renderScene(engine, module, contexts) {
} }
for (const b of Object.keys(byBlock)) byBlock[b] /= pairs || 1; for (const b of Object.keys(byBlock)) byBlock[b] /= pairs || 1;
return { thumbs, variety: pairs ? total / pairs : 0, byBlock }; // A dead shader scores zero on every block, which is indistinguishable from
// a very boring scene if you only read the number — and it happened: the
// subject helpers were declared above the ink they call, the whole preamble
// failed to compile, and every scene rendered black while the gallery
// reported a variety of exactly 0.000. Say which it is.
const lum = frameLuminance(thumbs[0]);
const variance = frameVariance(thumbs[0]);
const dead = lum < 0.002 || variance < 0.001;
return {
thumbs,
variety: pairs ? total / pairs : 0,
byBlock,
error: dead
? `renders nothing — luminance ${lum.toFixed(4)}, variance ${variance.toFixed(4)}. ` +
'A failed shader compile scores 0.000 on every block; check the console for GLSL errors.'
: undefined,
};
} }
/** /**