diff --git a/docs/LANES/LANE_B_NOTES.md b/docs/LANES/LANE_B_NOTES.md index 80fc822..414ac09 100644 --- a/docs/LANES/LANE_B_NOTES.md +++ b/docs/LANES/LANE_B_NOTES.md @@ -1,5 +1,76 @@ # LANE B — NOTES (measured) +## Round 14 (Fable's ROUND14 → Lane B) — v3.0 RELEASE: alias retirement + the tour audit + +Release round — no new systems. Three B files touched (`audio.js`, `venue.js`, `dbg.js`); no world +geometry, no rng, goldens frozen (`0x3fa36874` synthetic, `0x1f636349` gig — neither moved). Verified +fresh Chromium, seed 20261990, `?gigs=1`. + +### 1. Alpha-alias readers dropped (debt #1 — B first; **F unblocked**) +The R12 `window.PROCITY.gigs` scalar alias (`.state/.on/.venueShopId/.cover/.paid/…`) is retired this +round. B's readers removed: +- **`audio.js`** `venueGigState()` — dropped the alias hop (`g.venueShopId === shopId → g.state`). + Now byVenue-only + the clock fallback: `g.byVenue[id]` → else `plan.gigs` night-0 vs the segment. +- **`venue.js`** `stateGetter()` — dropped the bare-string arm; reads `gigStates.byVenue || gigStates` + (F passes `gigState.byVenue`, confirmed at `index.html:379`). Seed call is now `update(null)`. + +Verified live: F's `gigState.byVenue` reads `{116:'on', 404:'on', 490:'quiet'}` at NIGHT (pub + RSL +playing, band_room dark); passing it to `venue.update()` lights the marquee with **no string arm**, and +`audio.js` resolves each venue's `gig-` with **no alias hop**. Audio engine loads `live`. + +**→ Lane F (you can now delete the alias):** an alias-surface sweep (all of `web/js/**`, `index.html`, +`tools/**`) confirms **CROSS-LANE CLEAN** — no A/C/D/E file reads the scalar alias (interior_mode.js +uses `onOf()/gigOf()`; sim.js uses shell-driven `setGig`). The only remaining alias readers are **your +own 20 lines** in `tools/flags_check.py` + `tools/qa/gig_shot.py` (`.venueShopId`×12 → `venueShopIds[0]` +or an explicit id; `.state`×5 → `stateOf(id)`; `.cover`×2 → `coverOf(id)`; `.bandName`×3 → +`bandNameOf(id)`; `gig_shot.py:81` already prefers `stateOf(id)`). Delete the alias getters at +`gig_state.js:141–151` + migrate those tool lines in the same commit; `!!P.gigs` existence checks keep +working (the per-venue machine still exists). CITY_SPEC §v3 runtime block loses the alias line (with A). + +### 2. Bookmark / tour audit under `?gigs=1` — **all clean, no pose fixes** +All 19 bookmarks (16 v2 + `venue_night` + `district_posters` + new `queue_night`) resolve, frame +**un-letterboxed**, **0 page errors**, ≤300 draws. The district's in-place venue conversions hijacked +**no** bookmark: `night_neon`→"Video Regal" (openLate is never converted), `dig_real`→"Retro Groove" +(a real record shop survives — multiple per type). Framing eyeballed clean on the tour spread: +`street_noon`, `venue_night`, `queue_night`, `tram_stop`, `district_posters`, `night_neon`. **No dbg.js +pose changes were needed** beyond adding `queue_night`. +- **Measurement gotcha (re-confirmed):** `DBG.shot` teleports, so shooting many bookmarks in sequence + leaves orphan chunks that inflate tris on later shots (saw 238k on `patronage_door`; **42k** on a + fresh load / shoot-twice-to-settle). Shoot from a clean load, or twice, for the true per-frame count. +- **Observation → Lane D / Fable (not a Lane B fix):** `tram_stop` is a stable **~293k tris** — real, + from close-range full-detail GLB ped rigs on the footpath (framing is fine). It's not a venue block + (F's tri gate measures at the venue block, ~64k), so it likely doesn't trip the gate, but it exceeds + the 200k reference for a tour shot; worth a look if a close ped shot lands in the release set. + +### 3. `queue_night` — John's release hero (`dbg.js`) +New bookmark `queue_night` (seg 5 / NIGHT / 'on'): a close 3/4 on the pub door, biased to the venue's +`+right` (the queue side). New `poseForVenueQueue()` recomputes venue.js's frontage geometry from the +pub lot: `DIST 7 m` out on the street, `SIDE = mw/2 + 2.2 m` to the queue side, eye 2.2 m, looking at +the door lifted to y2.7 (catches the marquee). Verified: frames marquee + window glow + streetlamp +pools + the queue. **The queue is spawned by the shell's live rAF loop** (`index.html:383` builds D's +`VenueQueue` on `openOf(id)`), so F's `gig_shot.py` must drive it **live** (the automation rAF is +throttled — I confirmed the pose + D's line by manually spawning `VenueQueue` at the pub queueZone: 2 +punters seat at the door, facing it). +- **→ Lane A (blocks the hero's poster):** `queue_night` frames door + marquee + queue, but **no poster + lands in the close frame** — the pub's own-frontage poster is still on A's local **−Z** (the BACK + wall, `gigs.js buildPosters` item 1 `-sin,-cos`; measured 15.9 m from the +Z street facade), and the + nearest spine poster is 53 m away. Per Fable's "fix poses, don't touch world code," B did **not** + work around it. **Flip item 1 to `+sin,+cos`** and the frontage poster seats by the door — in the + existing `queue_night` frame with no pose change. (Re-flagged from R13; now it gates John's hero shot.) + +### 4. Poster-repetition call (debt #4) — **FINE, no ask to E** +Tonight's 14 posters carry **2 distinct designs** (Screaming Utes / screenprint ×7, The Dead Ringers / +xerox ×7 — different skins, different bands). Variety = number of venues playing tonight (one seeded +skin per gigId), so a spine run reads as two real band bills, not a repeat. Only a town where a single +gig plays everywhere would look monotonous (rare, and reads as "a residency"). No new templates needed. + +### 5. Town-wide streetlamp pools — v3.1 candidate (Fable ruling, no code) +Fable ruled the night pools **stay venue-scoped for v3.0** (town-wide needs always-on `furniture.js` +changes that fight flags-off parity in release week; venue blocks read fine — `venue_night` proves it). +Parked here as a **v3.1 candidate** alongside wind sway and bench-sit loiter. No R14 code. + +--- + ## Round 13 (Fable's ROUND13 → Lane B) — the district reads by night (`?gigs=1`, v3.0-beta) Four B pieces, all gig-gated (flags-off byte-identical, verified). Files: `venue.js` (rewrite), diff --git a/web/js/world/audio.js b/web/js/world/audio.js index 28f9488..ffcadc4 100644 --- a/web/js/world/audio.js +++ b/web/js/world/audio.js @@ -205,15 +205,13 @@ export function createAudioEngine(PROCITY, { noassets = false, mute = false } = const lot = pub && (PROCITY.plan?.lots || []).find((l) => l.id === pub.lot); if (lot) venues.push({ shop: pub, lot, genreKey: pub.genreKey || 'pubrock' }); } - // Per-venue gig state for the muffled spill, robust across Lane F's alpha→district transition: - // 1. F R13 per-venue map window.PROCITY.gigs.byVenue[id].state - // 2. alpha alias window.PROCITY.gigs is the single machine → its state for its venueShopId - // 3. clock fallback plan.gigs (night 0) vs the current segment (works before/without F) + // Per-venue gig state for the muffled spill (R14: the R12 alpha alias hop is retired): + // 1. F's per-venue map window.PROCITY.gigs.byVenue[id] (the canonical surface since R13) + // 2. clock fallback plan.gigs (night 0) vs the current segment (works before F wires / headless) // Returns null only when there is no gig layer at all (flags-off ⇒ byte-identical, no spill). function venueGigState(shopId) { const g = PROCITY.gigs; if (g && g.byVenue) { const e = g.byVenue[shopId]; if (e) return typeof e === 'string' ? e : (e.state || 'quiet'); } - if (g && g.venueShopId === shopId && typeof g.state === 'string') return g.state; const gigs = PROCITY.plan?.gigs; if (!gigs || !gigs.length) return null; const ton = gigs.find((x) => x.night === 0 && x.venueShopId === shopId); if (!ton) return 'quiet'; // dark tonight — reads true diff --git a/web/js/world/dbg.js b/web/js/world/dbg.js index 68f260e..6ae83e7 100644 --- a/web/js/world/dbg.js +++ b/web/js/world/dbg.js @@ -137,6 +137,25 @@ export function installDBG(deps) { || (plan.shops || []).find((s) => s.type === 'pub'); return shop ? poseForShop(shop, dist) : null; }; + // John's release hero: a CLOSE 3/4 on the pub door during doors/on, biased to the venue's +right + // (the queue side) so punters' faces read and a verge poster enters frame. The queue + posters are + // spawned by the live shell loop when the venue opens (seg 5 = NIGHT / 'on'), so F drives this shot + // live; this just frames the door + marquee + queue zone. Geometry mirrors venue.js's frontage. + const poseForVenueQueue = () => { + const vs = venueShops(); + const shop = vs.find((s) => (s.venueKind || s.type) === 'pub') || vs[0] + || (plan.shops || []).find((s) => s.type === 'pub'); + const lot = shop && lotOf(shop); if (!lot) return null; + const ry = lot.ry || 0, fx = Math.sin(ry), fz = Math.cos(ry), rx = Math.cos(ry), rz = -Math.sin(ry); + const w = lot.w || 8, d = lot.d || 10; + const cx = lot.x + fx * (d / 2), cz = lot.z + fz * (d / 2); // door centre on the street (venue.js frontage) + const mw = Math.min(w * 0.92, 6.5); // marquee width (queue head ~ +right of it) + const DIST = 7.0, SIDE = mw * 0.5 + 2.2, EYE = 2.2; // close standoff, biased to the queue side + return { + pos: [cx + fx * DIST + rx * SIDE, EYE, cz + fz * DIST + rz * SIDE], + look: [cx + rx * (mw * 0.30), 2.7, cz + rz * (mw * 0.30)], // door, lifted to catch the marquee + }; + }; // Named bookmarks (LANE_F_NOTES §4). seg = day segment index (0 DAWN … 5 NIGHT). // Predicates cover both Lane A's taxonomy (dept/stall/record/…) and the fixture's; each falls back @@ -164,6 +183,7 @@ export function installDBG(deps) { // ── v3 gig district (round 13, ?gigs=1): the street money shots F scripts via tools/qa/gig_shot.py ── venue_night: { seg: 5, resolve: () => poseForVenue('pub', 13) || streetViewPose(['main']) }, // ?gigs=1 — lit pub frontage + streetlamp pools + queue + posters at night district_posters: { seg: 5, resolve: () => streetViewPose(['main']) }, // ?gigs=1 — the spine at night: poster poles + lit venue frontages receding + queue_night: { seg: 5, resolve: () => poseForVenueQueue() || poseForVenue('pub', 11) || streetViewPose(['main']) }, // ?gigs=1 — John's hero: close on the pub door at 'on', queue + marquee + poster, punters facing the door }; const DBG = { diff --git a/web/js/world/venue.js b/web/js/world/venue.js index 56b4e45..0abda2a 100644 --- a/web/js/world/venue.js +++ b/web/js/world/venue.js @@ -185,13 +185,13 @@ export function createVenuePresentation(plan, skins, scene, { assetBase = 'asset scene.add(group); - // ── per-venue state → frontage glow. Accepts F's per-venue map { [venueShopId]: state }, a Map, an - // { byVenue } wrapper, OR a bare state string (alpha compat → the primary pub). Idempotent; the shell + // ── per-venue state → frontage glow. F passes its per-venue map `gigState.byVenue` + // ({ [venueShopId]: 'quiet'|'doors'|'on'|'done' }); a Map or a { byVenue } wrapper are also accepted. + // (R14: the R12 alpha bare-string arm is retired — F wires the map since R13.) Idempotent; the shell // calls it every street frame, so pool/glow ramps and the night baseline lerp here. ──────────────── function stateGetter(gigStates) { if (gigStates == null) return () => 'quiet'; - if (typeof gigStates === 'string') return (id) => (id === primaryId ? gigStates : 'quiet'); - const map = gigStates.byVenue || gigStates; + const map = gigStates.byVenue || gigStates; // F passes gigState.byVenue (per-venue map) return (id) => { const v = map instanceof Map ? map.get(id) : map[id]; if (v == null) return 'quiet'; @@ -225,7 +225,7 @@ export function createVenuePresentation(plan, skins, scene, { assetBase = 'asset if (poolMesh) poolMesh.visible = poolMat.opacity > 0.01; } } - update('quiet'); + update(null); // seed: no gig states yet → every venue quiet (frontage dark, pools follow night) function dispose() { for (const d of disposables) d.dispose && d.dispose();