Files
Dejvino cd7dfcd63d Vis testing battery: library + debug page sections
Collects every manual testing entry point into one library (src/debug/battery.js) rendered from the live registries: all 17 bank songs with app/+venue/strip links, all 70 scenes grouped by family with gate/wide links, all 3 venues, 9 curated param and scenario combos, and the graduated lab pages with their headless runners. debug.html renders it; new songs/scenes/venues join free.
2026-09-20 16:54:39 +02:00

379 lines
17 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>flow-state · debug</title>
<style>
:root { color-scheme: dark; }
body {
margin: 0; padding: 24px 24px 80px;
background: #0b0d12; color: #d6dae3;
font: 13px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
}
h1 { font-size: 15px; letter-spacing: .12em; text-transform: uppercase; color: #7d8698; margin: 0 0 6px; }
h2 { font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: #6b7280; margin: 26px 0 10px; font-weight: 600; }
p.lede { color: #8b94a7; max-width: 80ch; margin: 0 0 8px; }
a { color: #7dd3fc; text-decoration: none; }
a:hover { text-decoration: underline; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 10px; }
.card {
background: #11141b; border: 1px solid #1c2030; border-radius: 4px;
padding: 12px 14px; border-left: 3px solid #2a3040;
}
.card.slow { border-left-color: #eab308; }
.card.fast { border-left-color: #22c55e; }
.card.cli { border-left-color: #6366f1; }
.card h3 { margin: 0 0 4px; font-size: 13px; }
.card p { margin: 0 0 8px; color: #8b94a7; font-size: 12px; }
.card .meta { color: #565f70; font-size: 11px; }
code, pre {
background: #080a0f; border: 1px solid #1a1e28; border-radius: 3px;
padding: 1px 5px; color: #b8c0d0; font-size: 12px;
}
pre { padding: 8px 10px; overflow-x: auto; margin: 6px 0 0; }
.note { color: #6b7280; font-size: 12px; max-width: 80ch; }
.warn { color: #eab308; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 2px; }
.chips a {
background: #080a0f; border: 1px solid #1a1e28; border-radius: 3px;
padding: 1px 7px; font-size: 12px; white-space: nowrap;
}
.chips a:hover { border-color: #7dd3fc; text-decoration: none; }
.chips .fam { width: 100%; color: #565f70; font-size: 11px; letter-spacing: .08em;
text-transform: uppercase; margin-top: 6px; }
.chips .fam:first-child { margin-top: 0; }
.links { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.links a {
background: #080a0f; border: 1px solid #1a1e28; border-radius: 3px;
padding: 1px 7px; font-size: 12px;
}
.links a:hover { border-color: #7dd3fc; text-decoration: none; }
.links .lbl { color: #565f70; font-size: 12px; align-self: center; }
</style>
</head>
<body>
<h1>flow-state · debug</h1>
<p class="lede">
Everything here answers a question the app itself cannot. The app shows you
one video; these show you whether the generator is doing its job across
many.
</p>
<h2>Vis testing battery</h2>
<p class="lede">
The testing library: every bank song, every scene, every venue, the
param/scenario combos worth re-running, and the lab pages. Rendered
from the live registries (<code>src/debug/battery.js</code>), so a new
song, scene or venue joins the battery for free.
</p>
<div class="grid">
<div class="card fast">
<h3>songs — the bank, one click each</h3>
<p>Each song holds a corner of the feature space (<code>covers</code>).
App loads the song; +venue adds the room; strip is one frame per
30 s across the whole song.</p>
<div id="battery-songs" class="chips"><span class="note">loading…</span></div>
</div>
<div class="card fast">
<h3>venues — every room</h3>
<p>Pinned gallery first (fastest way to review a venue change), then
the app and the filmstrip with the room on.</p>
<div id="battery-venues"><span class="note">loading…</span></div>
</div>
</div>
<h2>Scenes — every visualizer</h2>
<p class="note">
Gate is the per-scene acceptance battery (renders, animates,
deterministic, distinct, sweep, flash, traits); wide is the scene on
six songs × six seeds.
</p>
<div class="grid">
<div class="card fast">
<h3>by family</h3>
<div id="battery-scenes" class="chips"><span class="note">loading…</span></div>
</div>
</div>
<h2>Scenarios — params and combos</h2>
<div class="grid" id="battery-scenarios"></div>
<h2>Lab pages + headless runners</h2>
<div class="grid" id="battery-labs"></div>
<h2>Looking at output</h2>
<div class="grid">
<div class="card fast">
<h3><a href="/gallery.html">gallery</a></h3>
<p>Every visualizer, six times, on six different songs' content — cast,
ink, lattice, palette and parameters all varying. Sorted least-varied
first, so the scenes that always look the same come to the top.</p>
<div class="meta">~3 min · the one to open when a scene feels familiar ·
<code>HOWTO-variety.md</code> for how to fix what it finds</div>
</div>
<div class="card fast">
<h3><a href="/gallery.html?scene=Effigy&amp;songs=6&amp;seeds=6">one scene, wide</a></h3>
<p>The gallery for a single visualizer, on a grid of songs (rows) against
seeds (columns) — up to every song in the bank by ten draws. Six
thumbnails ranks a library; it is far too few to study one scene.
Scores the two axes separately, which is the thing the library
gallery cannot tell you: <strong>across seeds</strong> low means the
scene ignores the identity, <strong>across songs</strong> low means it
ignores the music. The closest pair in the grid is outlined.</p>
<div class="meta">seconds, after the songs are analysed once ·
click any scene name in the gallery to open it here ·
the buttons change grid size and walk onto fresh seeds</div>
</div>
<div class="card">
<h3><a href="/filmstrip.html">filmstrip</a></h3>
<p>Every song in the bank, one frame every 30 seconds, left to right.
The gallery asks whether a scene looks the same in every song; this
asks whether a song looks the same as itself four minutes later.
Scored on <code>direction</code> — whether the distance between two
frames grows with the time between them — so the videos that are busy
and going nowhere sort to the top.</p>
<div class="meta">~6 min, then cached · <a href="/filmstrip.html?every=15">?every=15</a> tighter ·
<a href="/filmstrip.html?song=centre,ember">?song=</a> just these ·
<code>EPIC-4.md</code> for the arc it is checking</div>
</div>
<div class="card fast">
<h3><a href="/venue-gallery.html">venue gallery</a></h3>
<p>Venue &times; viz explorer — venue (higher-level than viz, picked first via census; hybrid
projection per archetype) against viz behind it. Try a sad-slow chamber of memories vs. a
pumping stage field from different angles and with different viz.</p>
<div class="meta">rows = songs · try <code>?venue=1</code> in gallery/filmstrip/app</div>
</div>
<div class="card fast">
<h3><a href="/">the app</a></h3>
<p>Drop a track and watch it. <code>test/songs/*.wav</code> holds the
synthetic bank if you want something with known properties.</p>
<div class="meta">build the bank with <code>npm run build:songs</code></div>
</div>
</div>
<h2>Is it correct?</h2>
<div class="grid">
<div class="card fast">
<h3><a href="/checks.html">phase gates</a></h3>
<p>Every acceptance gate in <code>PLAN.md</code>, run for real. This is
the one to check before committing.</p>
<div class="meta">
<a href="/checks.html?slow=1">?slow=1</a> full suite ·
<a href="/checks.html?phase=12">?phase=12</a> one phase
</div>
</div>
<div class="card fast">
<h3>single scene</h3>
<p>The whole per-scene battery for one visualizer: renders, animates,
deterministic, distinct, param sweep, flash rate, and every declared
trait and artifact.</p>
<div class="meta"><code>checks.html?scene=Metaballs</code> — the loop you
are in while writing or migrating one</div>
</div>
</div>
<h2>Is it varied?</h2>
<p class="note">
These measure whether two videos differ, which no other gate can see — a
generator that ignores its input passes determinism and liveness perfectly.
<span class="warn">Read the caution below before trusting a single run.</span>
</p>
<div class="grid">
<div class="card slow">
<h3><a href="/checks.html?decompose=1">decompose</a></h3>
<p>Identity against container, measured apart: hold the scene fixed and
vary the song's content, then the reverse. <strong>The most
trustworthy number here</strong> — it compares renders directly, and
its noise floor is exactly zero.</p>
<div class="meta">~1 min</div>
</div>
<div class="card slow">
<h3><a href="/checks.html?songs=1&amp;count=12">song variety</a></h3>
<p>Twelve songs from the bank, each with its own audio-derived seed. Also
reports coupling — whether songs that <em>sound</em> different come
out <em>looking</em> different, which is still unsolved.</p>
<div class="meta">~2 min · <a href="/checks.html?variety=1">?variety=1</a>
for the seed version, one song and many seeds</div>
</div>
<div class="card slow">
<h3><a href="/checks.html?experiment=1&amp;count=12&amp;repeats=3">arms, with error bars</a></h3>
<p>Three pools compared over repeated runs. The template for any A/B
here: it prints the difference against its own noise band and says
outright when the two are indistinguishable.</p>
<div class="meta">~5 min</div>
</div>
<div class="card slow">
<h3><a href="/checks.html?sweep=1&amp;count=12&amp;sizes=4,8,16,32&amp;repeats=3">pool sweep</a></h3>
<p>How many scenes a track should draw on. Swept rather than guessed —
and the answer turned out to be that it does not matter between 4 and
32.</p>
<div class="meta">~5 min</div>
</div>
</div>
<h2>Command line</h2>
<div class="grid">
<div class="card cli">
<h3>migration status</h3>
<p>Which scenes draw the song's content, which still draw their own, and
what each remaining one needs. Recipe in <code>MIGRATION.md</code>.</p>
<pre>node tools/migration-status.js</pre>
</div>
<div class="card cli">
<h3>cast census</h3>
<p>Which visualizers actually get cast, across songs and seeds — and for
the ones that do not, which of the four gates killed them.</p>
<pre>node tools/cast-census.js 12 40</pre>
</div>
<div class="card cli">
<h3>identity census</h3>
<p>How far apart the songs' identities are before anything is rendered.
No GPU. Run this before blaming the visualizers.</p>
<pre>node tools/identity-census.js 12</pre>
</div>
<div class="card cli">
<h3>song bank</h3>
<p>Rebuild the test songs and verify they still span every feature the
generator reads. Fails loudly if an axis has collapsed.</p>
<pre>npm run build:songs
npm run check:songs</pre>
</div>
<div class="card cli">
<h3>static gates</h3>
<p>Determinism grep, schema/shader agreement both ways, and the backtick
check that guards the shader template literals.</p>
<pre>npm run lint:scenes
npm test</pre>
</div>
</div>
<h2>Reading the variety numbers</h2>
<p class="note">
This harness has two classes of measurement and only one of them is safe to
steer by. <strong>Direct render comparisons</strong> — the gallery score,
<code>decompose</code>, the per-scene gate — have a noise floor of zero and
mean what they say. <strong>Aggregate ratios</strong> — chiefly
<code>separation</code>, which divides one small difference by another —
swing wildly between runs: three runs of the same measurement gave 0.31,
0.56 and 0.07. Four conclusions were drawn and withdrawn during this epic
for exactly that reason. Anything under about 0.01 of spread needs
<code>&amp;repeats=3</code> before it is believed, and a difference that
changes sign with the sample is not a difference.
</p>
<h2>If a page is dead</h2>
<p class="note">
A module that fails to load takes the whole graph with it: the page renders,
every control is present, and nothing is wired to anything. The app carries a
boot guard that says so. The usual causes are a content blocker — a file
named like tracking will be treated as tracking, which cost this project a
session over a file called <code>clicktrack.js</code> — or a dev server
caught mid-restart.
</p>
<script type="module">
import {
songNames, songCovers, families, scenesIn,
venueNames, appLinks, sceneLinks, venueLinks,
SCENARIOS, LABS,
} from '/src/debug/battery.js';
const el = (tag, cls, text) => {
const e = document.createElement(tag);
if (cls) e.className = cls;
if (text !== undefined) e.textContent = text;
return e;
};
const link = (href, text) => {
const a = document.createElement('a');
a.href = href;
a.textContent = text;
return a;
};
// Songs: one row per bank song — name, corner it holds, app / +venue / strip.
{
const host = document.getElementById('battery-songs');
host.replaceChildren();
for (const name of songNames()) {
const covers = songCovers(name);
const L = appLinks(name);
const row = el('span', 'fam', `${name} — ${covers} `);
host.appendChild(row);
host.appendChild(link(L.app, 'app'));
host.appendChild(link(L.venue, '+venue'));
host.appendChild(link(`/filmstrip.html?song=${encodeURIComponent(name)}`, 'strip'));
}
}
// Venues: pinned gallery + app + filmstrip per room.
{
const host = document.getElementById('battery-venues');
host.replaceChildren();
for (const name of venueNames()) {
const L = venueLinks(name);
const card = el('div', null);
card.appendChild(el('p', null, name));
const row = el('div', 'links');
row.appendChild(link(L.gallery, 'gallery'));
row.appendChild(link(L.app, 'app +venue'));
row.appendChild(link(L.filmstrip, 'strip'));
card.appendChild(row);
host.appendChild(card);
}
}
// Scenes: one chip per visualizer (→ gate), ◈ per scene (→ wide gallery).
{
const host = document.getElementById('battery-scenes');
host.replaceChildren();
for (const fam of families()) {
host.appendChild(el('span', 'fam', fam));
for (const name of scenesIn(fam)) {
const L = sceneLinks(name);
host.appendChild(link(L.gate, name));
const w = link(L.wide, '◈');
w.title = `${name} — wide gallery (6 songs × 6 seeds)`;
host.appendChild(w);
}
}
}
// Scenarios: curated cards from the library.
{
const host = document.getElementById('battery-scenarios');
host.replaceChildren();
for (const s of SCENARIOS) {
const card = el('div', 'card');
card.appendChild(el('h3', null, s.title));
card.appendChild(el('p', null, s.desc));
const row = el('div', 'links');
for (const [label, href] of s.links) row.appendChild(link(href, label));
card.appendChild(row);
host.appendChild(card);
}
}
// Labs: pages + the CLI that drives them.
{
const host = document.getElementById('battery-labs');
host.replaceChildren();
for (const lab of LABS) {
const card = el('div', 'card cli');
card.appendChild(el('h3', null, lab.title));
card.appendChild(el('p', null, lab.desc));
if (lab.href) {
const row = el('div', 'links');
row.appendChild(link(lab.href, 'open'));
card.appendChild(row);
}
if (lab.cli) {
const pre = el('pre', null, lab.cli);
card.appendChild(pre);
}
host.appendChild(card);
}
}
</script>
</body>
</html>