Venues: song sky, ride roadside, room camera, filmstrip venue pin

- Shared sky dome (src/venues/sky.js): gradient backdrop from palette /
  section light / style texture, wired into chamber/stage/ride with fog
  matched to the dome mid colour — replaces clear-to-black
- Ride roadside: houses, signs, trees, seeded viz billboard, all analytic
  scroll; popMul fade for crew, arrangement-gated roadside (no mid-section
  popping); parked idle creep + engine shake; analytic dash clock
- Room camera: traverse glide + progress look sweep + per-section dolly,
  single-side room visits, one re-hang max; venue framing per archetype
- Filmstrip venue selector (?venueSel=) with cache key; seed persisted to
  URL on reroll; gallery query plumbing
This commit is contained in:
Dejvino
2026-09-18 18:57:04 +02:00
parent 46fab512df
commit 9c768f13d1
10 changed files with 604 additions and 60 deletions
+19 -3
View File
@@ -88,6 +88,7 @@
<div class="bar" id="bar">
<span class="spinner"></span>
<button id="rebuild">rebuild</button>
<select id="venueSel" title="pin one venue in every row, or census"></select>
<select id="sort">
<option value="direction">sort: least direction first</option>
<option value="drift">sort: least drift first</option>
@@ -103,6 +104,7 @@
<script type="module">
import { buildFilmstrip, songNames, STRIP, DEFAULT_EVERY, DEFAULT_DURATION } from '/src/checks/filmstrip.js';
import { venues } from '/src/venues/registry.js';
import { blit } from '/src/checks/blit.js';
import { hashString } from '/src/engine/rng.js';
import {
@@ -115,6 +117,7 @@ const every = Number(params.get('every')) || DEFAULT_EVERY;
const duration = Number(params.get('duration')) || DEFAULT_DURATION;
const only = params.get('song') ? params.get('song').split(',') : null;
const venueEnabled = params.get('venue') === '1';
const venueSelParam = params.get('venueSel') || '';
document.getElementById('everyLabel').textContent = every;
@@ -122,9 +125,21 @@ const bar = document.getElementById('bar');
const out = document.getElementById('out');
const status = document.getElementById('status');
const sortSel = document.getElementById('sort');
const venueSel = document.getElementById('venueSel');
const progress = document.querySelector('#progress i');
const rebuildBtn = document.getElementById('rebuild');
venueSel.innerHTML = `<option value="">all venues (census)</option>` +
venues.map((v) => `<option value="${v.name}">${v.name} (${v.archetype})</option>`).join('');
if ([...venueSel.options].some((o) => o.value === venueSelParam)) venueSel.value = venueSelParam;
venueSel.addEventListener('change', () => {
const p = new URLSearchParams(location.search);
if (venueSel.value) p.set('venueSel', venueSel.value);
else p.delete('venueSel');
history.replaceState(null, '', `${location.pathname}?${p}`);
build();
});
let rows = [];
// Read against the arcless reference the variety report prints: around zero is
@@ -224,7 +239,8 @@ function summarise(built) {
? rows.reduce((a, r) => a + (r.direction || 0), 0) / rows.length : 0;
const meanAgit = rows.length
? rows.reduce((a, r) => a + (r.agitation ?? 0), 0) / rows.length : 0;
return `${rows.length} songs · every ${every}s of ${duration}s · ` +
const venueNote = venueSel.value ? ` · venue ${venueSel.value}` : (venueEnabled ? ' · venues from census' : '');
return `${rows.length} songs · every ${every}s of ${duration}s${venueNote} · ` +
`mean direction ${meanDir.toFixed(2)} · ${flat} with no direction` +
` · mean agit ${meanAgit.toFixed(2)} · ${agited} agitated (≥${AGIT_CEILING.toFixed(2)})${validatedNote}` +
(built ? ` · built ${new Date(built).toLocaleString()}` : '');
@@ -232,7 +248,7 @@ function summarise(built) {
// The cache key carries the sampling parameters as well as the source, so
// changing `?every=` does not silently show you the previous spacing's pixels.
const cacheKey = () => `filmstrip:${every}:${duration}:${only ? only.join(',') : 'all'}:v${venueEnabled ? 1 : 0}:${sourceFingerprint()}`;
const cacheKey = () => `filmstrip:${every}:${duration}:${only ? only.join(',') : 'all'}:v${venueEnabled ? 1 : 0}:${venueSel.value || 'auto'}:${sourceFingerprint()}`;
async function build() {
bar.classList.add('building');
@@ -242,7 +258,7 @@ async function build() {
const total = (only || songNames()).length;
const built = await buildFilmstrip({
duration, every, names: only, venueEnabled,
duration, every, names: only, venueEnabled, venueSel: venueSel.value || null,
// Painted as they land, in bank order, rather than after the whole run.
// A full build is a couple of minutes of GPU work and the interesting
// failure — a strip whose frames could be shuffled — is visible in the