From cd7dfcd63dd36b64b5dc6c5bf1c3d88525084ed3 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Sun, 20 Sep 2026 16:54:39 +0200 Subject: [PATCH] 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. --- flow-state/debug.html | 162 +++++++++++++++++++++++++++++++ flow-state/src/debug/battery.js | 167 ++++++++++++++++++++++++++++++++ 2 files changed, 329 insertions(+) create mode 100644 flow-state/src/debug/battery.js diff --git a/flow-state/debug.html b/flow-state/debug.html index f38f6a0..ace5b60 100644 --- a/flow-state/debug.html +++ b/flow-state/debug.html @@ -33,6 +33,22 @@ 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; } @@ -43,6 +59,48 @@ many.

+

Vis testing battery

+

+ 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 (src/debug/battery.js), so a new + song, scene or venue joins the battery for free. +

+
+
+

songs — the bank, one click each

+

Each song holds a corner of the feature space (covers). + App loads the song; +venue adds the room; strip is one frame per + 30 s across the whole song.

+
loading…
+
+
+

venues — every room

+

Pinned gallery first (fastest way to review a venue change), then + the app and the filmstrip with the room on.

+
loading…
+
+
+ +

Scenes — every visualizer

+

+ Gate is the per-scene acceptance battery (renders, animates, + deterministic, distinct, sweep, flash, traits); wide is the scene on + six songs × six seeds. +

+
+
+

by family

+
loading…
+
+
+ +

Scenarios — params and combos

+
+ +

Lab pages + headless runners

+
+

Looking at output

@@ -212,5 +270,109 @@ npm test session over a file called clicktrack.js — or a dev server caught mid-restart.

+ diff --git a/flow-state/src/debug/battery.js b/flow-state/src/debug/battery.js new file mode 100644 index 0000000..e97e4f6 --- /dev/null +++ b/flow-state/src/debug/battery.js @@ -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', + }, +];