# LANE C — cross-lane notes (PROCITY-C) ## → Lane F: `?stock=real` smoke for the strict harness (round-7 C1 landed) `?stock=real` is wired (record shops; book/toy fail-soft if E ships those packs). Smoke recipe: - Preload once: `import { preloadStockPack } from interiors.js; await preloadStockPack('record')`. - Boot `?stock=real`, enter a record shop, **assert real sleeves present**: a real sleeve is a `Mesh` with `userData.isStock`, geometry `PlaneGeometry`/`BufferGeometry` (batched), on a shared **atlas** material (`material.map` = the pack atlas, not a per-item canvas). Simplest assert: after build, `let n=0; room.group.traverse(o=>{ if(o.userData?.isStock && o.material?.map) n++ }); assert n>0` — with the pack loaded the record bins' sleeves batch to **one** atlas mesh (parody canvas would be ~6 card meshes). Or open the dig (`?dig=1&stock=real`) and assert a pulled sleeve's panel title is a pack title. - **Fail-soft**: pack missing → parody canvas + one `console.warn` (no throw). `?noassets` unaffected. - **drawSweep still ≤350** with `?stock=real` (record 59 draws — the atlas batches all sleeves). ## → Lane E: stock-pack contract CONFIRMED (round-7 C1) — build the real pack to this, unchanged Your proposed index schema is good — **confirmed as-is, no field changes**. Building `?stock=real` against your staged 24-sleeve sample now (`web/assets/models/stock_record_{index.json,atlas_00.webp}`); the real covers drop in unchanged. The contract C consumes: ```jsonc // stock__index.json (type ∈ record | book | toy ; record is the acceptance bar) { "version": 1, "atlas_px": 2048, "cell": 256, // informational; C reads uv, not cell "atlases": ["stock_record_atlas_00.webp", ...], // 1+; may be depot: or assets/models/ resolved "items": [ { "id": "rec_0000", "title": "Neon Suburbs", // parody-transformed (no-real-trademarks law applies to metadata) "artist": "Trev Wollemi", "price": 39, // integer dollars — C shows "$39" "price_band": "collector", // bargain|standard|collector|grail — C may tint the sticker "atlas": "stock_record_atlas_00.webp", // which entry in `atlases` "uv": [0.0, 0.0, 0.125, 0.125] } // [u0,v0,u1,v1] cover rect, ORIGIN TOP-LEFT ] } ``` Agreements / who-does-what: - **UV origin is top-left (image-natural) — C flips V for WebGL** (`v_gl = 1 - v`). Keep your index image-natural; the consumer handles the flip. (Sample verified against this.) - **Resolution**: C loads `stock__index.json` from a base (default `assets/models/`, i.e. your staged path — same-origin, works offline). Atlas filenames in the index resolve against the same base; if you publish atlases to the depot, give me a `"depot": true` flag or depot-prefixed names and I'll route through the `depot:` loader. For now the sample loads local. - **Seeded pick per bin is mine** (same `shop.seed` → same crate contents). I only need *enough* items — 24 repeats across bins (fine for testing); the 300 pack fixes variety. - **Batching**: because every sleeve samples one shared atlas material (per-item UV baked into geometry), all real sleeves in a room merge to **one draw** — the ≤350 law holds. Please keep it **one atlas per pack** where you can (or few); each extra atlas = one more draw. - **Fail-soft**: index/atlas missing or `?stock` off → parody canvas (warn once). `?noassets` untouched. - **Per-type packs**: name them `stock_book_*`, `stock_toy_*` if you build them; I key by shop type and fail-soft per type, so record-only is fine — book/toy are a bonus. You flagged needing a Postgres DSN (from John/Fable) to swap synthetic covers for real Discogs images — that's external and **does not block my consumer**; ping me only if the schema needs to change. ## → Lane F: interior ≤350-draw assertion for the harness (round-6 C1) C1 batching landed — worst room is now **313 draws GLB-on** / 152 GLB-off (was 1,245). The test page exposes the assertion for your harness: - `window.PROCITY_C.drawSweep({ glb: true|false })` → `{ worst, worstAt, pass, law: 350, perType }`. Builds every shop type × archetype at seed 1990, renders each, returns the worst `renderer.info.render.calls`. It's already folded into the interior soak's PASS/FAIL. For your smoke/soak gate, call it with `glb:true` (the tighter case) and assert `pass` (`worst <= 350`). Headless-safe, ~1s. - The draw law is a Lane-C constant (`DRAW_LAW = 350` in interior_test.html) so it's one number to bump if the budget ever changes. Ping me if you'd rather it read from a shared config. ## → Fable / Lane F: v1.1 is ready (round-5 C1 done) **C1 committed.** Lane E's re-exports are mapped + validated in `web/js/interiors/glb.js`: - `record_crate` (fixed) → record bins/crates upgrade to plastic crates with the procedural sleeves. - `counter → counter_till` → the real ~1.6 m counter-with-till drops in; footprint-fit + floor-plant unchanged; **keeper-stand pose still valid** (it's a room-local pose, not tied to the counter geometry). - **No double till**: my separate `cash_register` counter-top attach is dropped whenever `counter_till` is live (that GLB has its own till). `milkshake_mixer` still on the milk-bar bench. - GLB soak: `0 throws / 0 path / 0 determinism / leak-free / worst 10.4 ms`, **0 rooms with a visible primitive till**. Primitive path byte-unchanged (C1 touched only `glb.js`). `qa.sh --strict` green. **→ You can tag v1.1** per ROUND5 §Lane C acceptance ("bins + counter GLB-live, no double till, soak green → F tags v1.1"). Evidence: [`docs/shots/laneC/glb_v1_1_r5.jpg`](../shots/laneC/glb_v1_1_r5.jpg), report in [`LANE_C_GLB_VALIDATION.md`](LANE_C_GLB_VALIDATION.md). ## → Lane F: input/mode hook I need for `?dig=1` (round-5 C2) The crate-riffle (dig) is interior-mode-only and needs the shell to tell an interior when the player interacts with a fitting. Proposed seam (you own the shell/`interior_mode.js`): - On the player pressing the interact key while aimed at a fitting whose `userData.kind === 'bin'` (record bins expose `places:[group]` with `userData = { kind:'bin', interactable:true }`), call a hook the interior optionally provides: **`room.onInteract?.(hitObject)`** — where `room` is the object returned by `buildInterior(...)`. If it returns truthy, the shell should suppress normal movement / pointer-lock until the interior calls back that the dig closed (I'll expose `room.digActive` + fire a `procity:digClosed` event, or a callback you pass in — your call on the exact contract). - Gate the whole thing on `?dig=1` (I read the flag inside the interiors lib; when off, `onInteract` is absent and nothing changes — flag-off boot stays byte-identical). I'm building the dig against my own test page (`web/interior_test.html`) with a direct click handler in the meantime, so it's testable without the shell. Tell me if you'd prefer a different hook shape and I'll match it. — PROCITY-C