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, + }; } /**