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.
This commit is contained in:
Dejvino
2026-09-20 16:54:39 +02:00
parent a99a7cbe49
commit cd7dfcd63d
2 changed files with 329 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
// The vis testing battery as a library.
//
// Every manual testing entry point this project has — bank songs, scenes by
// family, venues, param/scenario combos, the throwaway lab pages and the
// headless CLI runners — collected in one place so debug.html (and anything
// headless later) renders them from the live registries instead of a stale
// hardcoded list. Adding a song, scene or venue updates the battery for free.
//
// Pure URL builders + data: no DOM, no WebGL, no wall-clock. Safe to import
// anywhere, including node (nothing here touches metadata.json, which needs
// Vite's JSON handling — so scene *coverage* is deliberately not imported).
import { SONGS } from '../audio/songbank.js';
import { scenes } from '../scenes/registry.js';
import { venues } from '../venues/registry.js';
const q = (params) => {
const p = new URLSearchParams();
for (const [k, v] of Object.entries(params)) {
if (v === null || v === undefined || v === '') continue;
p.set(k, String(v));
}
const s = p.toString();
return s ? `?${s}` : '';
};
export const songNames = () => SONGS.map((s) => s.name);
export const songCovers = (name) =>
(SONGS.find((s) => s.name === name) || {}).covers || '';
export const sceneNames = () => scenes.map((s) => s.name);
export const venueNames = () => venues.map((v) => v.name);
export const families = () => {
const out = [];
for (const s of scenes) if (!out.includes(s.family)) out.push(s.family);
return out;
};
export const scenesIn = (family) =>
scenes.filter((s) => s.family === family).map((s) => s.name);
/** App links for a bank song: plain, with venue, pinned seeds. */
export const appLinks = (song) => ({
app: `/${q({ song })}`,
venue: `/${q({ song, venue: 1 })}`,
seed1: `/${q({ song, seed: 1 })}`,
seed2: `/${q({ song, seed: 2 })}`,
});
/** Per-scene links: acceptance battery + wide single-scene gallery. */
export const sceneLinks = (name) => ({
gate: `/checks.html${q({ scene: name })}`,
wide: `/gallery.html${q({ scene: name, songs: 6, seeds: 6 })}`,
});
/** Per-venue links: gallery pinned to the venue, app + filmstrip with venue. */
export const venueLinks = (name) => ({
gallery: `/venue-gallery.html${q({ venueSel: name })}`,
app: `/${q({ song: 'centre', venue: 1 })}`,
filmstrip: `/filmstrip.html${q({ song: 'centre,ember', venue: 1, venueSel: name })}`,
});
/**
* Curated param/scenario combos: each is one question with the links that
* answer it. Songs are corners of the feature space (see songbank.js
* `covers`); pairs differ in exactly one control where possible.
*/
export const SCENARIOS = [
{
title: 'venue A/B, same song',
desc: 'Does the room earn its place? Same seed both sides — only the venue differs.',
links: [
['no venue', `/${q({ song: 'centre', seed: 7 })}`],
['venue', `/${q({ song: 'centre', seed: 7, venue: 1 })}`],
],
},
{
title: 'seed reroll, same song',
desc: 'Same music, two draws. Should read as two videos, not two grades of one.',
links: [
['seed 1', `/${q({ song: 'ember', seed: 1 })}`],
['seed 2', `/${q({ song: 'ember', seed: 2 })}`],
],
},
{
title: 'tempo ends',
desc: '62bpm beatless vs 174bpm broken. Motion should follow the song, not the scene.',
links: [
['drone 62', `/${q({ song: 'drone' })}`],
['runner 174', `/${q({ song: 'runner' })}`],
],
},
{
title: 'degenerate case',
desc: 'Two sections. Must not break — still is the regression test for it.',
links: [['still', `/${q({ song: 'still' })}`]],
},
{
title: 'hostile cases',
desc: 'Limitered, noisiest, brightest: the corners that collapse palettes and directors.',
links: [
['slab (flat)', `/${q({ song: 'slab' })}`],
['grit (noise)', `/${q({ song: 'grit' })}`],
['flare (bright)', `/${q({ song: 'flare' })}`],
],
},
{
title: 'most sections',
desc: 'Lattice rotates rosters the hardest. Watch for A/B ping-ponging and repeats.',
links: [['lattice', `/${q({ song: 'lattice' })}`]],
},
{
title: 'venue × viz pins',
desc: 'One room, chosen viz behind it. The fastest way to review a venue change.',
links: [
['Ride + corridor', `/venue-gallery.html${q({ venueSel: 'Ride', viz: 'Synthwave Corridor', rows: 2, cols: 3 })}`],
['Chamber + swarm', `/venue-gallery.html${q({ venueSel: 'Chamber', viz: 'Swarm', rows: 2, cols: 3 })}`],
['Stage + pylons', `/venue-gallery.html${q({ venueSel: 'Stage', viz: 'Pylon Field 3D', rows: 2, cols: 3 })}`],
],
},
{
title: 'filmstrip with venue',
desc: 'Does the room hold for two minutes, or does it drift, flash, or empty out?',
links: [
['centre + ember', `/filmstrip.html${q({ song: 'centre,ember', venue: 1 })}`],
],
},
{
title: 'fresh scene gates',
desc: 'The per-scene battery for the scenes most recently touched.',
links: [
['Swarm', `/checks.html${q({ scene: 'Swarm' })}`],
['Assembly', `/checks.html${q({ scene: 'Assembly' })}`],
['Pylon Field 3D', `/checks.html${q({ scene: 'Pylon Field 3D' })}`],
],
},
];
/**
* The graduated throwaways: single-purpose lab pages plus the headless
* runners that drive them. Linked, not duplicated — the pages stay where
* the runners expect them.
*/
export const LABS = [
{
title: 'ride lab',
href: '/ride-lab.html',
desc: 'One Ride, four inspection cameras. Settles which way the car faces.',
cli: 'node ride-lab-run.mjs',
},
{
title: 'passerby lab',
href: '/passerby-lab.html',
desc: 'Hunts a seed with a passerby cue on elegy, renders the shot head/mid/tail.',
cli: null,
},
{
title: 'ride closeup',
href: '/venue-gallery.html?rows=1&cols=1&venueSel=Ride',
desc: 'Wheels + beams at 2×. Same framing the closeup runner screenshots.',
cli: 'node ride-closeup.mjs',
},
{
title: 'venue gate loop',
href: null,
desc: 'Headless Chromium against vite dev: phases, filmstrip, gallery, variety. Screenshots to logs/venue/.',
cli: 'node tools/venue-check.js --quick',
},
];