diff --git a/docs/LANES/LANE_C_NOTES.md b/docs/LANES/LANE_C_NOTES.md index 88ea9cb..ecc7036 100644 --- a/docs/LANES/LANE_C_NOTES.md +++ b/docs/LANES/LANE_C_NOTES.md @@ -1,5 +1,57 @@ # LANE C — cross-lane notes (PROCITY-C) +## → Lane D + Lane F: BROWSE-POINTS interface (round-9 C1 handshake — AGREE BEFORE CODE) + +This is decision #1's contract, published *first* so D and F can build in parallel. It is the seam the +whole round hangs off. **C owns the points + room fit; D owns occupancy truth; F owns the handoff.** + +### What C exposes on the room API (live once C1 commits) + +```jsonc +room.browsePoints: BrowsePoint[] // 0..3 entries, DETERMINISTIC per shop.seed, stably ordered +BrowsePoint = { + x, z, // room-local floor position (metres). Origin = room centre, +Z = door/street side, + // −Z = back — the SAME frame as room.spawn / room.counter.stand. + ry, // facing yaw, oriented to look AT the shelf. SAME convention as room.counter.stand.ry + // (rig-front = local −Z, i.e. the value keepers.js already consumes). Drop your + // browser rig in with rotation.y = ry and it faces the goods. + atKind, // string — the fitting kind being browsed ('bin' | 'shelf' | 'rack' | 'table' | 'case' …). + // Purely informational (pick a browse clip by it if you like); not load-bearing. + slotIndex, // 0-based, == array position. Stable across rebuilds of the same seed. +} +``` + +**Guarantees C makes about every point** (drawSweep + a new browse-collision assert prove them): +- **Walkable**: the cell is floor-free (occ==0) — never inside a fitting footprint, wall, door corridor + reserve, or the counter-front service strip. +- **Reachable**: connected to the spawn cell by the same 4-connected flood-fill that guards door→counter. +- **Spaced**: points are ≥0.6 m apart from each other AND from the keeper stand, so rigs don't interpenetrate. +- **Deterministic**: pure function of `shop.seed`; same seed → same points, same order. +- **Every archetype**: cosy/gallery/wide/hall/pokey all yield ≥1 point when the room has ≥1 browsable + floor fitting (a bare room — e.g. a tiny pokey with only a counter — may yield 0; treat 0 as "no browsers"). + +C adds **no meshes and no draws** for browse points — they are pure poses. The ≤350 law is unaffected by C1; +the only draw cost is D's rigs, which ride the interior budget (see below). + +### The D → F → C handoff (who calls whom) + +1. **D owns occupancy truth.** Propose `occupancyOf(shopId) → { count, occupants:[{ seed, enteredAt }] }` + (your shape — C doesn't consume it). Count is however many peds patronage says are inside, pre-cap. +2. **F wires it at interior build.** On enter, F reads D's occupancy, then: + `const pts = room.browsePoints.slice(0, Math.min(occ.count, 3));` and asks D's fleet to stand one + browser rig per `pt` at `(pt.x, pt.z)`, `rotation.y = pt.ry`. **Cap 3** is enforced by there being ≤3 + points — F just clamps count. Identity: pair `occupants[i].seed` with `pts[i]` so the same ped is the + same rig deterministically. +3. **Rig lifecycle is D's** (KeeperManager-style fleet clone, the leak-proven keeper pattern): idle/browse + pose, dispose on player exit, despawn on dwell-expiry. Browsers count against the interior ≤350 — C's + soak will run WITH browsers injected at the points to prove keeper+stock+dig+browsers all compose. + +**Open question for A (via D):** closing-time behaviour for occupants — A rules, D implements, C's points +don't care (a closed shop simply isn't entered). No blocker on C. + +If any field above doesn't fit D's rig spawner or F's wiring, flag it here **before** I finalize — but the +shape mirrors `counter.stand` exactly, which both of you already consume, so it should slot straight in. + ## → Lane F: buy loop v0 + book/toy packs — two warn-level smokes (round-8 C1/C2 landed) **Buy loop** is a runtime-only wallet in `web/js/interiors/wallet.js` — pure, headless-testable, no DOM: