# LANE E — cross-lane notes ## Round 13 — two genres, the gigKey rename (debt #1), lighter instruments **v3.0-beta THE DISTRICT.** All local work landed + verified; the one gated step (depot publish) waits on John. Verification: core gates GREEN (scaffold · consistency 0-warn · citygen selfcheck 3273/3273 incl. v3 gig-layer + assets-on-disk), manifest validator `--depot` GREEN, and the real app boots clean flags-off / `?gigs=1` (A's 3-venue district, 14 gigs/posters — all three gig beds resolve by canonical key, no stale key, 0 console errors) / `?noassets=1&gigs=1` (0 audio + 0 GLB fetches, silent-and-fine). ### 1. gigKey rename — DONE (debt #1). **→ Lane B + Lane F: switch your readers.** The manifest music key IS the gigKey, **always `gig-`** — one key, zero mapping. Renamed `pubrock-live` → **`gig-pubrock`**; added `gig-grunge`, `gig-covers`. Files are now `audio/music-gig-{pubrock,grunge,covers}.{ogg,m4a}`; the old `music-pubrock-live.*` are **deleted**. Manifest regen'd + validates 0/0; no stale `pubrock-live` anywhere. - **→ Lane F (interior_mode.js):** DELETE the `GIG_BED` bridge (the `{'gig-pubrock':'pubrock-live'}` map + `gigBedKey()`); read `ra.gigKey` (`gig-`) straight against `AUDIO.music[...]`. The bridge currently maps to the now-gone `pubrock-live` → **fails soft to silence** (I verified: no crash, just a silent band) until you delete it. This is your R13 task #4. - **→ Lane B (audio.js ~L274/276):** you hardcode `'pubrock-live'` for the street spill — read each venue's `gigKey` bed instead (`AUDIO.music[venue.gigKey]`, i.e. `'gig-'+genreKey`). Same fail-soft-silent until you switch. Your R13 task #2 (per-genre spill) covers this. - **CITY_SPEC §v3 contract line (coordinate wording w/ A):** *"The audio manifest key for a gig genre IS the gigKey, always `gig-` (`gig-pubrock`, `gig-grunge`, `gig-covers`). No mapping table: C emits `room.audio.gigKey`, E names the bed, B/F read the same key."* ### 2. Two new gig beds — grunge (band_room) + covers (RSL). Procedural, seeded, loopable, OGG+M4A, loudnorm, $0 on-device (same discipline as R11/12). All three are audibly + spectrally distinct (spectral centroids pubrock 2336 / grunge 2271 / covers 1115 Hz; contact sheet `docs/shots/laneE/gig_beds_spectrograms.png`): - **`gig-grunge`** (0.6 gain, 17s): down-tuned sludge, quiet-loud dynamic (clean-ish verse → full-fuzz chorus — visible in the spectrogram), loose heavy kit. Sits at −14.6 LUFS (genre-dense; a 1.02 *inter-sample* peak, not real clipping — plays at gain 0.6 so absolute output ≈0.6). - **`gig-covers`** (0.5 gain, 19s): mellow RSL covers band — new drawbar-`organ()` voice, walking bass, soft kit, sparse organ answer. Warm, dark highs (that low continuous band + horizontal harmonic striations in the spectrogram = the organ). New synth helpers: `organ()`, `grungechord()`. - **John may re-flavour genres (names only) — I synthesize to order.** Note it's now a coordinated A+E change: the genreKey lives in A's `registry.js` (drives `gigKeyFor` → the bed name), so a re-flavour = A renames the genreKey + E renames/re-synths the bed. Defaults grunge/covers are live. ### 3. Instrument decimation — DONE. 5 GLBs → 14k tris each (≤15k target). **→ Lane D.** Collapse-decimated (Blender, `pipeline/decimate_props.py`) — **not** voxel-remesh, so thin features survive: mic pole + hanging cable, drum cymbals/stands, guitar necks/headstocks all read (thumbs re-rendered + eyeballed). Dims/orientation/base-origin/WebP preserved; GLB-law clean (no Draco, tex ≤1024). Fleet **236k → 70k tris (−70%)** across the district's multiple stages. | instrument | tris (norm→14k) | footprint (m) | height | |---|---|---|---| | electric_guitar | 43.5k → 14k | 0.38×0.08 | 1.00 | | bass_guitar | 36.0k → 14k | 0.34×0.13 | 1.15 | | mic_stand | 34.1k → 14k | 0.57×0.57 | 1.70 | | drum_kit | 26.2k → 14k | 1.50×1.24 | 1.30 | | guitar_amp | 96.6k → 14k | 0.52×0.29 | 0.61 | - **→ Lane D:** wire against the SAME filenames (`procity_fit_{electric_guitar,bass_guitar,mic_stand, drum_kit,guitar_amp}_01.glb`) — footprints/heights in the manifest are **unchanged**, so your `instrumentFor(role)` fittings are unaffected; they're just lighter now. (debt #2) - High-poly originals preserved as `_hi` variants in `_normalized/` (git-ignored) for the depot. ### 4. Depot publish — DONE (John OK'd direct publish). Gate 3 flag CLEARED. Published via the passwordless tailnet ingress (`GOD3_DEPOT=http://100.94.195.115:8788` — this box is allowlisted, so no secret handling). The 5 base names now serve the **14k bytes** and the 5 `_hi` hi-poly originals are live — verified on the public `digalot.fyi` depot (amp base 5.9MB→1.3MB, `_hi` 5.9MB live, byte-identical to local). `_published.json` 33→38 (the 5 `_hi` recorded). `validate_manifest --depot` GREEN. **The −70% decimation win is now live in production**, not just ?localdepot. (The `vintage-cash-register.glb` drift note from publish.py is pre-existing + unrelated — a depot-reuse asset, not a Lane E file.) ## Round 9 — VRAM audit verdict + one rec for C **VERDICT: fine to ship v2.0.** Texture VRAM ~240 MB default boot / ~420 MB worst-case (all flags + all shop types visited) — comfortable on desktop, within a low-end ~512 MB–1 GB budget. Full numbers in `AUDIT.md`. **Cheap win taken:** book + toy stock atlases half-res'd (they're quick pull-cards, not the deep record dig — covers verified legible), stock VRAM **323 → 181 MB**, no index/UV change, atlases re-published. Depot/manifest hygiene all green (`validate --depot` 0 err, pack-QA 3/3, 0 orphans). **→ Lane C (optional, NOT blocking v2.0 — v3 follow-up):** `stockpack.js` caches atlas textures with no dispose, so stock VRAM accretes across shop types (record+book+toy → 181 MB resident). A small **cache-cap** (dispose non-current shop-type atlases on room-type change) would bound it to the current type (≤134 MB). Not needed for the tag; flagging for when you next touch stockpack. **→ v3 (bigger, flagged): KTX2/Basis GPU-compressed atlases** — 4–8× VRAM cut, no quality loss. **→ Lane B (tram, on-call):** if you want a tram asset, a **box primitive with the right silhouette** (long low body + roof + windows strip) beats a heavy model and is budget-free — do that first; ping me only if it truly reads wrong and you want a flux_local/gen pass. ## Round 8 — REAL packs live (update) **→ Lane C: all 3 stock packs are now REAL** (GODVERSE data, John unblocked DB access). Same schema you wired — just richer data, no code change needed: - **record** — 350 real vinyl (John's store stock, real Discogs covers), 6 atlases, cell 256×256. - **book** — 311 real book covers (`dealgod.products` books), 4 atlases, cell **176×256 (portrait face-out cover**, not thin spine — I only have cover art; render books face-out). - **toy** — 273 real toy/collectible photos, 5 atlases, cell 256×256. Metadata is parody-transformed (no real titles/artists/brands); covers are real art; book prices are seeded (source is info-only). Indexes committed to `pipeline/packs/` + staged (`assets/models/`, your `?localdepot` path); atlases published to the depot. All pass the pack-QA gate. If your book shelf wants thin spines, tell me — I'd need spine art (don't have it; covers only). ## Round 8 (synthetic samples — superseded by REAL above) **→ Lane C (E1 — book + toy packs staged for you):** `build_stock_pack.py` is generalized over kinds; book/toy **sample packs are staged** (`web/assets/models/stock_{book,toy}_{index.json,atlas_00.webp}`, `?localdepot`) + indexes committed to `pipeline/packs/`. Same schema you proved for records — wire book/toy shelves through the stockAdapter, fail-soft per kind. **Cell geometry:** record 256×256 (square), **book 96×256 (tall-thin spine)**, toy 256×256 (box). You read `uv` (I confirmed from your notes), so render each kind on its own shelf geometry; the index also carries `cell_w`/`cell_h` if useful. `artist` carries author (book) / brand (toy). These are **synthetic placeholders** (real index format) until the DSN lands — your fail-soft + wiring are unaffected when real covers swap in. **E2 — pack-QA gate is live** (`validate_pack.py` in qa gate 3 via validate_manifest): a bad bake (bad UV/price-band/empty title/dangling atlas) fails qa. If you add index fields, tell me and I'll extend `REQUIRED`. **→ Fable (blocker, flagged early per the slip rule):** the **real** book/toy/record packs need **`GODVERSE_DSN`** (dealgod Postgres — up on :5432; builder reads it from env, I don't hunt creds). One command per kind once set (`--kind book --from-db --count 200`). Pipeline + parody + atlas + QA gate all proven on samples; not blocking C (samples staged). No fal spend. ## Round 7 **→ Lane D (D1 — the flip gate): merged peds ready to validate.** `web/models/peds/*.glb` (19 rigs) are merged to **ONE draw each** (1 primitive + 1 atlased material) — fleet **92→19 draws**, at the 24-rig near cap **116→24**. Method: Blender join + UV-pack + EMIT-bake all materials into one 1024 atlas (`pipeline/merge_ped.py`). **Skinning + skeleton preserved** — 65 joints intact on all 19, `walk`/`idle` untouched, so clips retarget unchanged. I verified in the vendored GLTFLoader: all 19 load as SkinnedMesh + bind walk; `man_suit` poses to byte-identical dims vs the original (skin deforms unchanged); `man_athletic` (3-mat) atlas eyeballed correct. Please do the full pass (silhouettes/materials/impostor bake/identity variety) + the **full-density draws/tris numbers** for F's flip. Pre-merge peds backed up (gitignored) at `pipeline/.peds_r6/` if you need a diff; per-ped in `pipeline/_peds_merge.json`. **If any ped's atlas/silhouette reads wrong, name it — I re-run that one** (per-ped `atlas` size is tunable; a couple with dense detail could go 2048). **→ Lane C (E2 — stock pack): pipeline built + PROVEN, sample staged for you now.** `pipeline/build_stock_pack.py` produces the pack (parody-transform → atlas WebPs + JSON index). A **24-sleeve sample pack is staged at `web/assets/models/stock_record_{index.json,atlas_00.webp}`** — wire `?stock=real` against it NOW (real index format, synthetic covers), and the real covers drop in unchanged. **Proposed index schema** (I built to this — confirm/adjust in LANE_C_NOTES and I'll match `INDEX_FIELDS`): ```json { "version":1, "atlas_px":2048, "cell":256, "atlases":["stock_record_atlas_00.webp"], "items":[ {"id":"rec_0000","title":"Neon Suburbs","artist":"Trev Wollemi","price":39, "price_band":"collector","atlas":"stock_record_atlas_00.webp","uv":[0,0,0.125,0.125]} ] } ``` `uv` = [u0,v0,u1,v1] into the named atlas (origin top-left); price_band ∈ bargain/standard/collector/grail. Seeded pick per bin is yours (same shop.seed → same crate). Fail-soft: pack missing → parody canvas. **→ Fable (E2 blockers — flagging mid-round per your schedule-harness note):** the real 200-400 pack needs two things I can't self-serve: (1) **`GODVERSE_DSN`** — the dealgod Postgres dsn/creds (Postgres is up on :5432; I read it from env, don't hunt creds) → `--from-db --count 300`; (2) **C's schema** confirmation. Both external. The pipeline + parody law + atlas are done and proven on `--sample`; the real pack builds in one command once the DSN lands. Not blocking C's consumer wiring (sample staged). ## Round 6 **E1 done** — `publish.py` now merges into `_published.json` (no more R5 clobber); `--verify` + post-publish drift-check vs the live depot; `validate_manifest.py` fails if a manifest GLB isn't in `_published.json`. Record verified 27==depot. All 27 procity GLBs stay live + recorded. **→ Lane B / Lane C — I'm on-call for the props I shipped:** - **B:** if `bus_shelter` / `bin` need a scale, origin, or facing tweak once placed on the street graph, tell me the exact issue (current: bus_shelter 2.33×1.41 m / 2.4 m, bin 0.57×0.68 m / 1.0 m, both base-origin minY=0). I can re-normalize + re-publish same-name (idempotent, merge-safe now). - **C:** if `glass_case` / `magazine_rack` / `crate_stack` read wrong in-room (scale, the fal glass_case's interior clutter, the magazine_rack density), flag it. A *broken*-prop fal re-gen is pre-authorized ≤2 attempts (decision #5) — I'll log any spend in AUDIT. Footprints in the manifest; all base-origin. Mapping guidance for the 3 orphans is in the Round-5 → Lane C section below. ## Round 5 ### → Lane C: hero props BAKED to ≤8k tris — re-measure the record interior The 6 heavy TRELLIS props are re-baked and **live on the depot** (same filenames, footprints unchanged, so **no re-map needed** — they upgrade in place): | prop | tris before → after | |---|---| | drinks_fridge | 77k → 8.0k | | milkshake_mixer | 73k → 8.0k | | listening_booth | 54k → 8.0k | | counter_till | 32k → 8.0k | | novelty_record | 26k → 8.0k | | arcade_cabinet | 25k → 8.0k | **Fleet 288k → 48k tris.** Method: voxel-remesh → decimate → Cycles base-colour bake (see `pipeline/bake_lowpoly.py`, AUDIT R5). Verified loading in the vendored GLTFLoader; base colour baked (no separate metallic/normal maps → also dodges the record_crate dangling-texture class). → **Please re-measure the record interior** (was ~273k tris in your R4 note) and record the new number in LANE_C_GLB_VALIDATION.md — it should drop by ~most of the prop weight. ### → Lane C: 3 new fittings LIVE (fal Hunyuan) — glass_case + magazine_rack The two remaining primitive-only fittings are now real GLBs on the depot (≤8k, base-colour baked): - **`glass_case`** (`procity_fit_glass_case_01.glb`, 1.08×1.42 m, 1.0 m tall) — timber base + glass panels + items inside. Registry ids `glass_case` (toy/dept) + `glass_cabinets` (pawn) both alias to it. → add `glass_case`/`glassCase` (+ your pawn cabinet kind) → `'glass_case'` in `KIND_TO_GLB`. - **`magazine_rack`** (`procity_fit_magazine_rack_01.glb`, 0.54×0.44 m, 1.2 m tall) — tiered timber rack w/ magazines (milkbar/pawn). Registry `magazine_rack` aliases to it. → map your kind → `'magazine_rack'`. - Both base-origin, verified loading in the vendored GLTFLoader. `manifest.fittingAliases` also maps `fridge`→`drinks_fridge` and `listening_corner`→`listening_booth` if you iterate registry ids. ### → Lane B: new street props — `bus_shelter` + `bin` - `procity_street_bus_shelter_01.glb` (`furniture.bus_shelter`, 2.33×1.41 m, 2.4 m, ≤8k, base-origin). Solid painted-steel shelter (back wall + roof + bench) — place at bus-stop spots along the footpath. - `procity_street_bin_01.glb` (`furniture.bin`, 0.57×0.68 m, 1.0 m, 3k) — red council wheelie bin, an obvious town-furniture gap. Map `bin` in your furniture GLB-upgrade path if you want it on footpaths. - **B3 resolved:** `novelty_record` is now **8k** (was 26.5k) via the E1 bake — re-measure the busy view. All furniture GLBs base-origin + footprint-accurate; fail-soft as usual. ### → Lane C also: `crate_stack` for floor piles / dig `procity_fit_crate_stack_01.glb` (`fittings.crate_stack`, 0.71×0.72 m, 0.9 m, 3k) — a stack of 3 plastic milk crates, good for record-store floor dressing or the dig scene. Optional; map a kind to `'crate_stack'` if useful. ### → Fable: E1 (bake) + E3 (fal) status - **E1 bake DONE + committed** — 6 props ≤8k, published. Feeds C's re-measure + your v1.1 tag. - **E3 fal DONE** — glass_case + bus_shelter + magazine_rack generated on fal Hunyuan v2, **~$0.64 total** (log in AUDIT R5), ≤2 attempts each, all baked ≤8k + published. Guardrail held (nothing else on fal). `validate --depot` 0 err. Last two primitive-only fittings retired. - **E2 depot growth** next (v2-serving library props). --- ## Round 4 ## → Lane D (task D2): decimated peds ready to validate The 19 ped rigs in `web/models/peds/` are decimated and staged (committed `518678d`, ref `lane-e/round4-peds`). Please validate rig binding + animation: - **≤3k tris each** (range 1564–2805; per-ped before/after in `pipeline/_peds_decim.json`). Fleet total **924k → 46.5k tris** (48.6k → 2.45k avg). At the 24-rig near cap ≈ **≤67k**. - **Skinning preserved**: `JOINTS_0`/`WEIGHTS_0` intact; **skeleton unchanged** (nodes + 65 joints identical to source on every ped I checked), so the shared `walk.glb`/`idle.glb` clips retarget unchanged. **`walk.glb`/`idle.glb` were NOT touched.** - Method: `gltf-transform weld` → `simplify --ratio 0.05 --error 0.12` (meshoptimizer — works because these are manifold skinned meshes, not R3 TRELLIS shell-soup). Contact sheet: `docs/shots/laneE/peds_decimated.png` (silhouettes/textures clean at street distance; fingers merge + faces flatten as expected at 3k, no melting). - Originals backed up (gitignored) at `pipeline/.peds_orig/` if you need a before/after diff. If any rig fails to bind/animate or a silhouette reads badly, tell me the file(s) and I'll re-run that one with a gentler error (0.08 keeps ~50% more tris but still <5k). Then F re-measures gate 3 after your sign-off. ## → Lane C: E2 asset re-exports 1. **`procity_fit_record_crate_01.glb` — FIXED.** Root cause of your `undefined 'uri'` crash: the material's `metallicRoughnessTexture` + `occlusionTexture` both pointed at a **sourceless texture** (the packed AO/roughness map failed to encode to WebP during normalize; base-colour + normal were fine). Fix: `pipeline/fix_glb_textures.py` strips material slots that reference a sourceless texture (orphaning it — three.js loads textures lazily, so no crash). **Verified it loads in the real vendored `GLTFLoader`** (meshes=1, tris=5000, textures ok) alongside a bookshelf control. Re-published to the depot — re-map `record_crate`/`recordBin` in `glb.js` (they should already be mapped) and it'll upgrade instead of falling back. 2. **counter-with-till — LIVE.** New fitting `counter_till` (`procity_fit_counter_till_01.glb`): **1.61 × 0.64 m footprint, 1.15 m tall**, timber counter + cabinet doors + period beige till on top (flux_local concept → trellis_mac, on-device). Fixes the R3 4 m till-less counter that squashed. **Verified it loads in the vendored `GLTFLoader`** (32k tris — TRELLIS shell-soup floor, within decision-#2's 25–77k for focal props) and **live on the depot** (`validate --depot` 0 err). → **In `glb.js` re-add `counter` to `KIND_TO_GLB` mapping to `'counter_till'`** (NOT the old `counter` = balcao, which is a 4 m long bar counter kept for shops that want it). The manifest `fittingAliases.counter` → `counter_till` too. It's base-origin (minY=0), so your footprint-fit + floor-plant works unchanged; at a 1.4–2.0 m counter slot it scales to a believable ~1.0–1.4 m tall counter. ## → Fable: status - **E1 (gate-3 critical path): DONE.** Peds ≤3k, committed + pinned. Unblocks D2 → F3. - **E2a record_crate: DONE + verified** (loads in vendored GLTFLoader). Re-published. - **E2b counter: in progress** (local, on-device — no fal spend per decision #3). - Not doing: fal.ai spend (glass-case/bus-shelter stay parked), hero-prop bake-to-lowpoly.