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
+14 -2
View File
@@ -22,6 +22,7 @@
import { Show, FADE_SECONDS } from '../Show.js';
import { generateLook } from '../look/LookGenerator.js';
import { venues } from '../venues/registry.js';
import { song, SONGS } from '../audio/songbank.js';
import { hashString } from '../engine/rng.js';
import { frameDescriptor, motionDescriptor } from './variety/descriptors.js';
@@ -69,12 +70,22 @@ function blockMean(byBlock) {
*
* @returns {{name, frames: Array, drift: number, direction: number, look}}
*/
export function renderSong(show, name, { duration, every, venueEnabled = null }) {
export function renderSong(show, name, { duration, every, venueEnabled = null, venueSel = null }) {
const { track } = song(name, { duration });
// Seeded off the song's name, so a strip is stable between rebuilds and two
// songs are not accidentally handed the same look.
const venueFlag = venueEnabled !== null ? !!venueEnabled : (typeof location !== 'undefined' && new URLSearchParams(location.search).get('venue') === '1');
const look = generateLook(track, { seed: hashString(name), venueEnabled: venueFlag });
// Pin one venue in every row (same shell-swap as the venue gallery): the
// VenueSpec population stays the census one, only the shell is forced.
const sel = venueSel || (typeof location !== 'undefined' && new URLSearchParams(location.search).get('venueSel')) || null;
if (sel && look.venue) {
const picked = venues.find((v) => v.name === sel);
if (!picked) throw new Error(`unknown venue "${sel}"`);
look.venue.module = picked;
look.venue.name = picked.name;
look.venue.archetype = picked.archetype;
}
show.useTrack(track, look);
const frames = [];
@@ -208,6 +219,7 @@ function momentAt(story, index) {
*/
export async function buildFilmstrip({
duration = DEFAULT_DURATION, every = DEFAULT_EVERY, names = null, venueEnabled = null,
venueSel = null,
onSong = null, onSongStart = null,
} = {}) {
const list = names && names.length ? names : songNames();
@@ -225,7 +237,7 @@ export async function buildFilmstrip({
}
let row;
try {
row = renderSong(show, list[i], { duration, every, venueEnabled });
row = renderSong(show, list[i], { duration, every, venueEnabled, venueSel });
} catch (err) {
row = { name: list[i], frames: [], shots: [], drift: 0, direction: 0, error: err.message };
}