diff --git a/docs/LANES/LANE_B_NOTES.md b/docs/LANES/LANE_B_NOTES.md index 917f173..573253b 100644 --- a/docs/LANES/LANE_B_NOTES.md +++ b/docs/LANES/LANE_B_NOTES.md @@ -1,5 +1,55 @@ # LANE B — NOTES (measured) +## Round 16 (Fable's ROUND16 → Lane B) — v3.1 THE FLIP: town-wide pools + default-boot re-baseline + +Two deliverables. Verified fresh Chromium, seed 20261990. (Ran before F's flip landed — my pools are +gig-gated so they work under `?gigs=1` now, and the re-baseline uses **explicit** all-four-flags URLs = +the post-flip default-boot content; F re-confirms against the real default at close.) + +### 1. Town-wide streetlamp pools (ledger #4) — `venue.js` +Extended the R13 instanced-pool pass from venue frontages to **every streetlight + bus shelter**. New +`townPoolSpecs(plan)` replicates furniture.js's exact lamp cadence off the plan's street graph (a lamp +every 18 m from `s=8`, alternating sides, at `off = halfRoad+0.7`) and reuses the exported +`busShelterStops(plan)` — **read-only, `furniture.js` untouched** (imported the one function). All pools +(venue + town) ride the **one existing InstancedMesh**, so town-wide is still **~1 draw**: +- **381 pool instances / 1 draw / ~760 tris** for Boolarra Heads (22 edges, 27 nodes). Night-driven via + the existing opacity ramp; `fog: true` now (was false) so distant pools fade into the night fog + instead of floating as dots over un-built chunks (pools aren't chunk-streamed). A/B'd `fog:false` — + no dramatic difference and a dot-risk on bigger towns, so `fog:true` is the keeper. +- **Gig-gated → classic parity by construction:** venue.js only exists under the gig layer, so + `?classic=1` (gigs off) is pool-free = v2 parity. No always-on `furniture.js` change (the v3.0 blocker). +- Pure function of the plan (deterministic cadence, no rng) → **no new seeded draws** (release law). + +### 2. Default-boot budget re-baseline (ledger #4) — the number the budget law now means +Measured on the **default boot content** (gigs + `weather=rain` worst-case + winmap + tram all live), +settled (shoot-twice / clean-load to defeat the orphan-chunk teleport inflation): + +| view (default boot, all flags) | draws (composer total) | tris | gate | +|---|---|---|---| +| **busiest venue block** (pub, NIGHT, rain, 3/4) | **~157 scene / ~170 total** | **66k** | ≤300 / ≤200k ✅ | +| `venue_night` | 122 | 53k | ✅ | +| `queue_night` | 121 | 61k | ✅ | +| `district_posters` / `street_noon` (wide main spine) | **185** | 131k | ✅ (worst tour shot) | +| `market_square` (densest daytime, fresh) | 97 | 52k | ✅ | + +**No budget bust.** The flip adds ~3 draws (rain Points 1 + tram 2; winmap 0 — it's a shader on +existing windows), and the **town-wide pools add ~0** (1 instanced mesh). Worst default-boot street +view measured is **185 total draws / 131k tris** — ~115 draw / ~70k tri margin. (Pools are night-only, +so the pre-existing daytime dense-citizen ceiling — Lane D territory, ~275 total per R13's live-walk — +is unchanged by this round; winmap/rain add ≤3 there. F's live week-soak confirms the full-density number.) + +### 3. Bookmark spot-check on the default boot +All **19 bookmarks resolve, un-letterboxed, 0 page errors** with all four flags on. Rain particles + +tram + winmap now appear in the old opt-in frames and compose cleanly — `street_noon` rainy (177 draws +/ 114k tris) frames the strip with rain + storm sky + wet road, nothing blocked. No pose fixes needed. + +**→ Lane F (B→F handshake — the default worst-case numbers):** default boot, busiest venue block = +**~170 total draws / 66k tris**; worst street view (wide main spine) = **185 / 131k**; densest daytime += 97 / 52k. All ≤300 / ≤200k. Town-wide pools are ~1 draw. Re-confirm against the real default once +your flip lands; the pool sprite is procedural (no asset, E has nothing to do). + +--- + ## Round 15 (Fable's ROUND15 → Lane B) — verify the hero after A's poster flip (v3.0 release) Task: re-verify `queue_night` after A's ledger-#1 flip (`35da83c`: `buildPosters` item 1 → `+sin,+cos` diff --git a/web/js/world/venue.js b/web/js/world/venue.js index 978b8f0..4ea37ce 100644 --- a/web/js/world/venue.js +++ b/web/js/world/venue.js @@ -19,6 +19,7 @@ // ry under A's poster convention (front = local −Z); posters are rendered at A's (x,z,ry) verbatim. import * as THREE from 'three'; +import { busShelterStops } from './furniture.js'; // read-only: shelter (tram-stop) positions for pools const POSTER_W = 1.05, POSTER_H = 1.5; // ~A2 bill on a pole/wall const POSTER_SKINS = ['grunge', 'retro', 'screenprint', 'xerox']; // E's poster pack (assets/gen/poster-*.jpg) @@ -169,11 +170,18 @@ export function createVenuePresentation(plan, skins, scene, { assetBase = 'asset }); } - // ── one instanced streetlamp-pool mesh for the whole district (night-driven) ────────────────────── + // ── town-wide streetlamp pools (R16 ledger #4): a warm pool under every streetlight + bus shelter, + // not just venue frontages. Positions are replicated from the plan's street graph using furniture.js's + // exact lamp cadence (READ-ONLY — we don't touch furniture.js), so a pool lands under each real lamp. + // Gig-gated by construction (venue.js only exists under the gig layer ⇒ ?classic stays pool-free); one + // instanced mesh ⇒ still ~1 draw town-wide. Night-driven via the same opacity ramp as the venue pools. + for (const s of townPoolSpecs(plan)) poolSpecs.push(s); + + // ── one instanced streetlamp-pool mesh for the whole town (night-driven) ────────────────────────── let poolMesh = null, poolMat = null; if (poolSpecs.length) { const poolTex = poolTexture(); disposables.push(poolTex); - poolMat = new THREE.MeshBasicMaterial({ map: poolTex, transparent: true, opacity: 0, depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide, fog: false, toneMapped: false }); + poolMat = new THREE.MeshBasicMaterial({ map: poolTex, transparent: true, opacity: 0, depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide, fog: true, toneMapped: false }); const pg = new THREE.PlaneGeometry(1, 1); pg.rotateX(-Math.PI / 2); // lies flat, normal +Y disposables.push(poolMat, pg); poolMesh = new THREE.InstancedMesh(pg, poolMat, poolSpecs.length); @@ -265,6 +273,30 @@ export function createVenuePresentation(plan, skins, scene, { assetBase = 'asset const r2 = (v) => Math.round(v * 100) / 100; const r4 = (v) => Math.round(v * 10000) / 10000; +// ── town-wide pool positions: one under every streetlight + bus shelter, derived from the plan's street +// graph. This MIRRORS furniture.js's streetlight rule (a lamp every 18 m from s=8, alternating sides, at +// off = halfRoad+0.7) so pools sit under the real lamps without importing furniture internals. Pure +// function of the plan (no rng — the cadence is deterministic), so it adds no seeded draws (release law). +function townPoolSpecs(plan) { + const specs = []; + const nodeById = new Map((plan.streets?.nodes || []).map((n) => [n.id, n])); + for (const e of (plan.streets?.edges || [])) { + const a = nodeById.get(e.a), b = nodeById.get(e.b); + if (!a || !b) continue; + const dx = b.x - a.x, dz = b.z - a.z, len = Math.hypot(dx, dz) || 1; + const ux = dx / len, uz = dz / len, px = -uz, pz = ux; + const halfRoad = (e.width || 8) / 2; + let side = 1; + for (let s = 8; s < len - 4; s += 18) { + const off = (halfRoad + 0.7) * side; + specs.push({ x: a.x + ux * s + px * off, z: a.z + uz * s + pz * off, diam: 3.4 }); + side = -side; + } + } + for (const st of busShelterStops(plan)) specs.push({ x: st.x, z: st.z, diam: 3.6 }); + return specs; +} + // ── a warm radial streetlamp-pool sprite (procedural, no fetch): bright warm centre → transparent edge. function poolTexture() { const S = 128;