diff --git a/C-progress.md b/C-progress.md index 497ffd3..bc1c784 100644 --- a/C-progress.md +++ b/C-progress.md @@ -27,7 +27,19 @@ then batched (new `web/js/interiors/batch.js`): - **Sweep now ASSERTS ≤350** (`drawSweep()` in the test page, folded into the soak; `window.PROCITY_C. drawSweep` for F's harness — see LANE_C_NOTES for the hook). -Still in flight this round: C2 map glass_case/magazine_rack/crate_stack. +**C2 — map the 3 orphaned fittings** (published R5, unconsumed). In `glb.js`: `glassCase→glass_case`, +`magazineRack→magazine_rack`, and a NEW `crateStack` primitive (stack of crates) → `crate_stack`. +Recipe placements: crateStack in record + opshop (back-room dressing), glassCase added to milkbar, +magazineRack added to pawn (+ moved milkbar's magazineRack window→wall to dodge the door-counter). +Also **fixed `placeNearCounter`** — it picked one random side and gave up, so counter-zone fittings +(the glass case) rarely landed; now it tries both sides + offsets, then falls back to a free aisle spot. +All 3 load + render planted/scaled/facing right (yaw 0). Appearance: glassCase **12/12** (toy/pawn/milkbar), +magazineRack **12/12** (milkbar/pawn), crateStack ~11/12 (record/opshop). Contact sheet: +[glb_orphans_r6.jpg](docs/shots/laneC/glb_orphans_r6.jpg). drawSweep still ≤350 with them on (worst 177 +GLB-on). **C3**: dig soak re-run after batching + new fittings — riffle works on batched bins, leak-free. + +**Round 6 complete for Lane C**: C1 (batching, `35981ad`) + C2/C3 (orphans + dig re-verify) committed. +Draw law satisfied (≤313 was the tightest; ≤177 after C2), determinism/leak/dig all green, qa strict green. --- diff --git a/docs/shots/laneC/glb_orphans_r6.jpg b/docs/shots/laneC/glb_orphans_r6.jpg new file mode 100644 index 0000000..7d1fe1b Binary files /dev/null and b/docs/shots/laneC/glb_orphans_r6.jpg differ diff --git a/web/js/interiors/fittings.js b/web/js/interiors/fittings.js index b0d9845..4e1dc6f 100644 --- a/web/js/interiors/fittings.js +++ b/web/js/interiors/fittings.js @@ -76,6 +76,22 @@ function crate(ctx, o = {}, r) { return { group: g, footprint: { w: w + 0.06, d: d + 0.06 }, height: h, slots, places: [g] }; } +// A stack of empty crates — back-room dressing for record/op shops (GLB `crate_stack` upgrades it). +function crateStack(ctx, o = {}, r) { + const g = new ctx.THREE.Group(); + const w = o.w || 0.66, d = o.d || 0.66, ch = 0.3, th = 0.02; + const n = 2 + (r() * 2 | 0); // 2–3 crates + for (let i = 0; i < n; i++) { + const m = ctx.mat(i % 2 ? '#4a5a6a' : '#8a6a45', 0.8); + const ox = (r() - 0.5) * 0.06, oz = (r() - 0.5) * 0.06, y = i * ch; + ctx.box(w, th, d, m, ox, y + th / 2, oz, g); // floor + ctx.box(th, ch, d, m, ox - w / 2, y + ch / 2, oz, g); ctx.box(th, ch, d, m, ox + w / 2, y + ch / 2, oz, g); + ctx.box(w, ch, th, m, ox, y + ch / 2, oz - d / 2, g); ctx.box(w, ch, th, m, ox, y + ch / 2, oz + d / 2, g); + } + g.userData = { kind: 'prop' }; + return { group: g, footprint: { w: w + 0.1, d: d + 0.1 }, height: n * ch, slots: [], places: [] }; +} + // A listening corner: a low bench + a boxy hi-fi + headphones on a stand. function listeningCorner(ctx, o = {}, r) { const g = new ctx.THREE.Group(); @@ -377,7 +393,7 @@ function artFrame(ctx, o = {}, r) { } export const FITTINGS = { - recordBin, crate, listeningCorner, + recordBin, crate, crateStack, listeningCorner, wallShelf, metalShelf, cubeShelf, bookshelf, vhsAisle, spinnerRack, arcadeCabinet, clothesRack, trestleTable, counter, glassCase, diff --git a/web/js/interiors/glb.js b/web/js/interiors/glb.js index 02870c2..819ae5c 100644 --- a/web/js/interiors/glb.js +++ b/web/js/interiors/glb.js @@ -29,6 +29,10 @@ const KIND_TO_GLB = { listeningCorner: 'listening_booth', // record store focal prop fridge: 'drinks_fridge', // milk bar drinks fridge arcadeCabinet: 'arcade_cabinet', // video store focal prop + // Round-6 orphans (published R5, mapped now): + glassCase: 'glass_case', // toy/pawn/dept/milkbar display case + magazineRack: 'magazine_rack', // milkbar/pawn/book magazine rack + crateStack: 'crate_stack', // record/opshop back-room dressing }; // Per-kind facing correction (radians, about +Y). The GLBs follow glb_law (metres, +Y up, base at origin) @@ -44,6 +48,7 @@ const KIND_TO_YAW = { fridge: 0, arcadeCabinet: 0, counter: 0, // R5 counter_till — tuned on-screen + glassCase: 0, magazineRack: 0, crateStack: 0, // R6 orphans — tuned on-screen }; // Counter-top GLB props: sit ON the counter's benchtop (not a floor swap). Each targets a counterTop diff --git a/web/js/interiors/layout.js b/web/js/interiors/layout.js index 22cdc9f..4e53f7c 100644 --- a/web/js/interiors/layout.js +++ b/web/js/interiors/layout.js @@ -198,7 +198,7 @@ export function layout(ctx, { recipe, dims, shell, adapter, shop }) { if (spec.zone === 'wall' || f.wallMounted) { ok = placeAtWall(ctx, grid, f, wallSlots, rPlace, placed, spec); } else if (spec.zone === 'counter') { - ok = placeNearCounter(ctx, grid, f, counter, rPlace, placed, spec); + ok = placeNearCounter(ctx, grid, f, counter, rPlace, placed, spec, W, D, doorCx); } else { ok = placeFree(ctx, grid, f, spec.zone, W, D, doorCx, rPlace, placed, spec); } @@ -296,17 +296,25 @@ function placeAtWall(ctx, grid, f, wallSlots, r, placed, spec) { return false; } -function placeNearCounter(ctx, grid, f, counter, r, placed, spec) { - // treasures / cases beside the till, on the goods side of the counter - const side = counter.ry === 0 ? { x: counter.x + (r() < 0.5 ? -1.3 : 1.3), z: counter.z } : { x: counter.x, z: counter.z + (r() < 0.5 ? -1.3 : 1.3) }; +function placeNearCounter(ctx, grid, f, counter, r, placed, spec, W, D, doorCx) { + // cases beside the till, on the goods side of the counter — try BOTH sides + a couple offsets, then + // fall back to a free-standing aisle spot so a min≥1 fitting (e.g. a glass case) reliably lands. const ry = counter.ry; + const cands = counter.ry === 0 + ? [{ x: counter.x - 1.4, z: counter.z }, { x: counter.x + 1.4, z: counter.z }, { x: counter.x - 1.4, z: counter.z - 0.7 }, { x: counter.x + 1.4, z: counter.z - 0.7 }] + : [{ x: counter.x, z: counter.z - 1.4 }, { x: counter.x, z: counter.z + 1.4 }, { x: counter.x - 0.7, z: counter.z - 1.4 }, { x: counter.x - 0.7, z: counter.z + 1.4 }]; + shuffle(r, cands); const [hw, hd] = halfExtents(f.footprint.w, f.footprint.d, ry); - const rect = rectCells(grid, side.x, side.z, hw, hd); - if (!areaFree(grid, rect)) return false; - f.group.position.set(side.x, 0, side.z); f.group.rotation.y = ry; - stampRect(grid, rect); - placed.push({ fitting: f, x: side.x, z: side.z, ry, hw, hd, rect, priority: spec.priority, kind: spec.kind }); - return true; + for (const side of cands) { + const rect = rectCells(grid, side.x, side.z, hw, hd); + if (!areaFree(grid, rect)) continue; + f.group.position.set(side.x, 0, side.z); f.group.rotation.y = ry; + stampRect(grid, rect); + placed.push({ fitting: f, x: side.x, z: side.z, ry, hw, hd, rect, priority: spec.priority, kind: spec.kind }); + return true; + } + // counter is boxed in — put it in the aisle instead (still front-of-room-ish) + return placeFree(ctx, grid, f, 'aisle', W, D, doorCx, r, placed, spec); } // ── wall decor (art + signage) ───────────────────────────────────────────────────────── diff --git a/web/js/interiors/theme.js b/web/js/interiors/theme.js index f088d7d..f63e0de 100644 --- a/web/js/interiors/theme.js +++ b/web/js/interiors/theme.js @@ -38,6 +38,7 @@ const RECIPES = { { kind: 'recordBin', zone: 'aisle', min: 2, max: 5, priority: 5 }, { kind: 'crate', zone: 'centre', min: 1, max: 3, priority: 3 }, { kind: 'wallShelf', zone: 'wall', min: 1, max: 3, priority: 3 }, + { kind: 'crateStack', zone: 'wall', min: 1, max: 2, priority: 2 }, // back-room dressing (crate_stack GLB) { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, ], stockKind: 'sleeves', @@ -57,6 +58,7 @@ const RECIPES = { { kind: 'metalShelf', zone: 'wall', min: 1, max: 3, priority: 4 }, { kind: 'bookshelf', zone: 'wall', min: 1, max: 2, priority: 3 }, { kind: 'trestleTable', zone: 'aisle', min: 1, max: 2, priority: 3 }, + { kind: 'crateStack', zone: 'wall', min: 1, max: 2, priority: 2 }, // back-room dressing (crate_stack GLB) { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, ], stockKind: 'garments', @@ -133,6 +135,7 @@ const RECIPES = { { kind: 'glassCase', zone: 'counter', min: 1, max: 3, priority: 6 }, { kind: 'pegboard', zone: 'wall', min: 1, max: 3, priority: 4 }, // wall hooks { kind: 'metalShelf', zone: 'wall', min: 1, max: 2, priority: 3 }, + { kind: 'magazineRack', zone: 'wall', min: 1, max: 1, priority: 2 }, // magazine_rack GLB { kind: 'barredWindow', zone: 'window', min: 1, max: 1, priority: 3 }, { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, ], @@ -151,7 +154,8 @@ const RECIPES = { fittings: [ { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, { kind: 'fridge', zone: 'wall', min: 1, max: 3, priority: 6 }, - { kind: 'magazineRack', zone: 'window', min: 1, max: 2, priority: 4 }, + { kind: 'magazineRack', zone: 'wall', min: 1, max: 2, priority: 4 }, // wall (was window — conflicted with the door-counter) + { kind: 'glassCase', zone: 'counter', min: 1, max: 1, priority: 3 }, // lolly/treat display case (glass_case GLB) { kind: 'metalShelf', zone: 'wall', min: 1, max: 3, priority: 4 }, { kind: 'cubeShelf', zone: 'aisle', min: 0, max: 1, priority: 2 }, ],