From 8defef3ed07660c5a87f09fda5e5e5719c736011 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Tue, 18 Aug 2026 16:55:16 +0200 Subject: [PATCH] Tell a dead shader apart from a boring scene in the gallery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- flow-state/HOWTO-variety.md | 4 ++++ flow-state/gallery.html | 9 ++++++--- flow-state/src/checks/gallery.js | 20 +++++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/flow-state/HOWTO-variety.md b/flow-state/HOWTO-variety.md index 4f3a224..4a69f7f 100644 --- a/flow-state/HOWTO-variety.md +++ b/flow-state/HOWTO-variety.md @@ -213,6 +213,10 @@ sample size is not a difference. 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 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 diff --git a/flow-state/gallery.html b/flow-state/gallery.html index ec61e19..f24bec6 100644 --- a/flow-state/gallery.html +++ b/flow-state/gallery.html @@ -43,6 +43,7 @@ .row.flat { border-color: #ef4444; } .row.thin { border-color: #eab308; } .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; } .name { font-size: 14px; color: #e6eaf2; } .fam { color: #6b7280; } @@ -112,7 +113,7 @@ let contexts = []; // 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. -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() { const mode = sortSel.value; @@ -134,7 +135,7 @@ function draw() { out.appendChild(cut); } 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 || {}) .map(([k, v]) => `${k} ${v.toFixed(3)}`).join(' · '); el.innerHTML = ` @@ -169,8 +170,10 @@ function draw() { sortSel.addEventListener('change', draw); 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` + + (broken ? ` · ${broken} BROKEN` : '') + (built ? ` · built ${new Date(built).toLocaleString()}` : ''); } diff --git a/flow-state/src/checks/gallery.js b/flow-state/src/checks/gallery.js index e3ace7d..9d6e08e 100644 --- a/flow-state/src/checks/gallery.js +++ b/flow-state/src/checks/gallery.js @@ -24,6 +24,7 @@ import { songBank } from '../audio/songbank.js'; import { generateLook } from '../look/LookGenerator.js'; import { describeIdentity } from '../look/Identity.js'; import { frameDescriptor, motionDescriptor } from './variety/descriptors.js'; +import { frameLuminance, frameVariance } from '../engine/hash.js'; import { descriptorDistance, STRUCTURAL } from './variety/signature.js'; 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; - 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, + }; } /**