// PROCITY Lane C — type theming. Every CITY_SPEC shop type gets a distinct interior recipe so a // `record` shop instantly reads different from a `toy` shop the moment the door opens. // // LANE_C says "import SHOP_TYPES from core/registry.js" — Lane A hasn't landed registry.js yet, so // this lane stays FULLY STANDALONE: the canonical recipes live here. When registry.js exists, Lane F // can call `mergeRegistry(SHOP_TYPES)` to let authored registry data override any field. No hard // dependency, so the test page runs with zero other lanes. // Fitting `kind`s referenced below must exist in fittings.js FITTINGS registry. // Zones the layout placer understands: 'window' (front, by glazing), 'aisle' (centre rows), // 'wall' (perimeter, uses wall-slots), 'centre' (free-standing island), 'counter' (till corner). // // Recipe fields: // archetypeBias : weighted room-shape preference (keys of shell.ARCHETYPES) — 'auto' picks from here // wallpaperBias : biased pick from web/assets/gen/wall-*.jpg (names without prefix/suffix) // floorBias : biased pick from carpet/lino/board floor names // clutter : 0..1 density multiplier (some shops crammed, some sparse) // counterPos : 'door' | 'forward' | 'corner' | 'back' — where the till lives // fittings : the mix. Each {kind, zone, min, max, priority, o?}. Counts scale with clutter+area. // priority: higher = more essential; flood-fill removes low-priority first if the // door→counter path is ever blocked. Counter is always highest. // o (R39, optional): opts handed to the fitting builder. Pure data, reads no stream. // A per-build override lives at buildInterior opts.fittingOpts[kind]. // fallbackZones / anywhere (R39, optional): opt-in placement fallbacks for a fitting // the recipe PROMISES will be there. Absent ⇒ pre-R39 behaviour (drop it). // stockKind : what stock.js renders into this shop's carriers // signFlavour : seeded inside signage strings, themed // [R41 §41.4] The kit pools live in kit41.js; recipes name a pool, never an asset id, so a manifest change // is one edit in one file. Every kit spec below is APPENDED LAST inside its recipe on purpose: layout.js // draws counts from `lay-count` and placements from `lay-place` in array order, so a spec added at the END // leaves every pre-existing fitting in that recipe the SAME draws, and therefore the same placement. (The // R39 lesson, applied at ten times the size.) import { POOLS } from './kit41.js'; export const ARCH = { cosy: 'cosy', gallery: 'gallery', wide: 'wide', hall: 'hall', pokey: 'pokey' }; const RECIPES = { // ── RECORD ───────────────────────────────────────────────────────────────────── record: { label: 'Record Store', archetypeBias: [['cosy', 3], ['gallery', 2], ['pokey', 1]], wallpaperBias: ['woodchip-white', 'stripe-sage', 'geo-orange'], floorBias: ['boards-polished', 'carpet-greygreen', 'lino-cork'], clutter: 0.85, counterPos: 'corner', fittings: [ { kind: 'listeningCorner', zone: 'window', min: 1, max: 1, priority: 6 }, // focal hero prop — placed first + high priority { 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 }, // ── R41: the 3dstore shop floor. Real record-shop fittings, not a scatter: bins to flick through in // the aisle, tiered racks behind them, boards on the wall, a window ledge, sleeves pinned up. The // record shop already digs (R39), so its bins mint `kind:'bin'` — this is the one type where more // bins is unambiguously more game. { kind: 'kitBin', zone: 'aisle', min: 2, max: 4, priority: 4, fallbackZones: ['centre'], o: { pool: POOLS.record_bins, dig: true, stock: 'sleeve', items: 12 } }, { kind: 'kitRack', zone: 'aisle', min: 1, max: 3, priority: 4, fallbackZones: ['centre'], o: { pool: POOLS.shop_racks, dig: true, stock: 'sleeve', items: 9 } }, { kind: 'kitWall', zone: 'wall', min: 1, max: 2, priority: 3, o: { pool: POOLS.wall_boards, wall: true, mountY: 1.5, stock: 'box', items: 5, maxRun: 3.0 } }, { kind: 'kitWindow', zone: 'window', min: 0, max: 1, priority: 2, o: { pool: POOLS.window_dress } }, { kind: 'kitSleeve', zone: 'wall', min: 1, max: 2, priority: 1, o: { pool: POOLS.sleeve_wall, wall: true, mountY: 1.72 } }, // TRAP 2: vinyl_sleeve, wall only // THE HERO: the rigged 1200 + a rigged TTM-54i behind the counter. 8 draws, one room, once. { kind: 'djBooth', zone: 'counter', min: 1, max: 1, priority: 6, gap: 2.35, fallbackZones: ['aisle', 'centre'], anywhere: true, o: { booth: 'record_hero' } }, ], stockKind: 'sleeves', signFlavour: ['NEW ARRIVALS', 'VINYL $5 EA', 'SORRY NO REFUNDS', '2ND HAND CDs', 'JAZZ · SOUL · FUNK'], }, // ── OP SHOP ──────────────────────────────────────────────────────────────────── opshop: { label: 'Op Shop', archetypeBias: [['cosy', 3], ['wide', 2], ['hall', 1]], wallpaperBias: ['floral-cream', 'damask-mauve', 'trellis-blue', 'floral-gold'], floorBias: ['carpet-swirl', 'carpet-mustard', 'lino-check'], clutter: 1.0, counterPos: 'corner', fittings: [ { kind: 'clothesRack', zone: 'centre', min: 2, max: 5, priority: 5 }, { 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 }, // [R39 — THE OP SHOP CAN'T BE DUG] the op shop's own floor bin. APPENDED LAST on purpose: the // recipe loop draws counts from `lay-count` and placements from `lay-place` in array order, so a // spec added at the end leaves every existing op-shop fitting's draws — and therefore its // placement — byte-identical, and only adds the tub. `dig:false` is the CONTENTS gate: off, this // is furniture (kind 'prop'); `{ rummageBin:{dig:true} }` in opts.fittingOpts makes every op shop // diggable. WHAT IT YIELDS IS JOHN'S CALL — LANE_C_PUB §10. Do not flip the default in passing. { kind: 'rummageBin', zone: 'aisle', min: 1, max: 1, priority: 4, fallbackZones: ['centre', 'window'], anywhere: true, o: { dig: false } }, // ── R41: bargain tubs + a wooden rack + a wall board. `dig` STAYS OFF — R39 measured the whole // question ("op shops now dig RECORDS") and held the contents for John; nothing here reopens it. { kind: 'kitBin', zone: 'aisle', min: 1, max: 2, priority: 3, fallbackZones: ['centre', 'window'], o: { pool: POOLS.bargain_tubs, stock: 'box', items: 10 } }, { kind: 'kitRack', zone: 'wall', min: 1, max: 2, priority: 3, fallbackZones: ['aisle'], o: { pool: POOLS.shop_racks, stock: 'box', items: 8 } }, { kind: 'kitWall', zone: 'wall', min: 0, max: 1, priority: 2, o: { pool: POOLS.wall_boards, wall: true, mountY: 1.5, stock: 'box', items: 5, maxRun: 3.0 } }, ], stockKind: 'garments', signFlavour: ['ALL CLOTHES $2', 'BAG SALE SAT', 'BRIC-A-BRAC', 'PROCEEDS TO CHARITY', 'HALF PRICE'], }, // ── TOY ──────────────────────────────────────────────────────────────────────── toy: { label: 'Toy Shop', archetypeBias: [['wide', 3], ['cosy', 2], ['hall', 1]], wallpaperBias: ['geo-orange', 'diamond-green', 'trellis-blue'], floorBias: ['lino-check', 'carpet-greygreen', 'lino-cork'], clutter: 0.75, counterPos: 'corner', fittings: [ { kind: 'cubeShelf', zone: 'wall', min: 2, max: 4, priority: 5 }, { kind: 'trestleTable', zone: 'aisle', min: 1, max: 3, priority: 3 }, { kind: 'glassCase', zone: 'counter', min: 1, max: 1, priority: 4 }, { kind: 'metalShelf', zone: 'aisle', min: 1, max: 2, priority: 3 }, { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, ], stockKind: 'boxes', signFlavour: ['NEW! ', 'AS SEEN ON TV', 'PLAY & SAVE', 'BATTERIES EXTRA', 'LAYBY WELCOME'], }, // ── BOOK ─────────────────────────────────────────────────────────────────────── book: { label: 'Book Barn', archetypeBias: [['hall', 3], ['gallery', 2], ['cosy', 1]], wallpaperBias: ['damask-mauve', 'woodchip-white', 'floral-gold'], floorBias: ['carpet-greygreen', 'boards-polished', 'carpet-swirl'], clutter: 0.9, counterPos: 'corner', fittings: [ { kind: 'bookshelf', zone: 'aisle', min: 3, max: 5, priority: 5 }, { kind: 'bookshelf', zone: 'wall', min: 2, max: 3, priority: 4 }, { kind: 'spinnerRack', zone: 'centre', min: 1, max: 2, priority: 3 }, { kind: 'armchair', zone: 'window', min: 1, max: 1, priority: 2 }, { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, // ── R41: the returns trolley (3dstore `library_trolley`) + a rack of overflow + wall boards. { kind: 'kitTrolley', zone: 'aisle', min: 1, max: 1, priority: 3, fallbackZones: ['centre'], o: { pool: POOLS.library, stock: 'spine', items: 9 } }, { kind: 'kitRack', zone: 'wall', min: 1, max: 2, priority: 2, fallbackZones: ['aisle'], o: { pool: POOLS.shop_racks, stock: 'spine', items: 9 } }, { kind: 'kitWall', zone: 'wall', min: 1, max: 2, priority: 2, o: { pool: POOLS.wall_boards, wall: true, mountY: 1.55, stock: 'spine', items: 8, maxRun: 3.0 } }, ], stockKind: 'spines', signFlavour: ['ALL BOOKS $2', 'FICTION A–M', 'RARE & COLLECTABLE', 'NO HAGGLING (haggle at till)', 'POETRY ↑'], }, // ── VIDEO ────────────────────────────────────────────────────────────────────── video: { label: 'Video Rental', archetypeBias: [['gallery', 3], ['cosy', 2], ['wide', 1]], wallpaperBias: ['diamond-green', 'geo-orange', 'stripe-sage'], floorBias: ['carpet-swirl', 'carpet-greygreen', 'lino-cork'], clutter: 0.85, counterPos: 'door', fittings: [ { kind: 'arcadeCabinet', zone: 'aisle', min: 1, max: 1, priority: 6 }, // focal hero prop — placed first + high priority so the door-counter path loop won't pull it { kind: 'vhsAisle', zone: 'aisle', min: 2, max: 4, priority: 5 }, { kind: 'vhsAisle', zone: 'wall', min: 1, max: 2, priority: 4 }, { kind: 'returnsSlot', zone: 'counter', min: 1, max: 1, priority: 4 }, { kind: 'pegboard', zone: 'wall', min: 1, max: 2, priority: 2 }, { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, // ── R41: the ex-rental bargain tub by the door + wall boards + the window display. { kind: 'kitBin', zone: 'aisle', min: 1, max: 2, priority: 3, fallbackZones: ['centre', 'window'], o: { pool: POOLS.bargain_tubs, stock: 'box', items: 10 } }, { kind: 'kitWall', zone: 'wall', min: 1, max: 2, priority: 2, o: { pool: POOLS.wall_boards, wall: true, mountY: 1.5, stock: 'box', items: 6, maxRun: 3.0 } }, { kind: 'kitWindow', zone: 'window', min: 0, max: 1, priority: 2, o: { pool: POOLS.window_dress } }, ], stockKind: 'spines', signFlavour: ['NEW RELEASES', 'OVERNIGHT $3', 'BE KIND REWIND', 'WEEKLY $2', 'DROP RETURNS HERE'], }, // ── PAWN ─────────────────────────────────────────────────────────────────────── pawn: { label: 'Pawnbroker', archetypeBias: [['pokey', 3], ['cosy', 2], ['gallery', 1]], wallpaperBias: ['woodchip-white', 'stripe-sage', 'damask-mauve'], floorBias: ['lino-cork', 'boards-polished', 'lino-check'], clutter: 0.7, counterPos: 'forward', // counter is right up front; goods behind it fittings: [ { 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 }, // ── R41: unredeemed pledges. Flight cases and a sub on the floor; an 808 and an SP-1200 on the // bench behind the grille — statics, 2 draws, because a pawnbroker's gear is not a working booth. { kind: 'kitGear', zone: 'aisle', min: 1, max: 2, priority: 3, fallbackZones: ['centre'], o: { pool: POOLS.pawn_gear } }, { kind: 'djBooth', zone: 'counter', min: 1, max: 1, priority: 4, gap: 2.1, fallbackZones: ['aisle', 'centre'], anywhere: true, o: { booth: 'pawn' } }, { kind: 'kitWall', zone: 'wall', min: 0, max: 1, priority: 2, o: { pool: POOLS.wall_boards, wall: true, mountY: 1.5, stock: 'box', items: 5, maxRun: 3.0 } }, ], stockKind: 'mixed', signFlavour: ['WE BUY ANYTHING', 'CASH LOANS', 'GOLD · TOOLS · GEAR', 'ID REQUIRED', 'UNREDEEMED PLEDGES'], }, // ── MILK BAR ───────────────────────────────────────────────────────────────────── milkbar: { label: 'Milk Bar', archetypeBias: [['pokey', 3], ['cosy', 2]], wallpaperBias: ['diamond-green', 'geo-orange', 'stripe-sage'], floorBias: ['lino-check', 'lino-cork', 'carpet-mustard'], clutter: 0.8, counterPos: 'door', // counter faces the door — you're served on the way in fittings: [ { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, { kind: 'fridge', zone: 'wall', min: 1, max: 3, priority: 6 }, { 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 }, // ── R41: the hot box. `bain_marie` + `pie_warmer` are the two props that make a milk bar a milk bar // (this is also PROCITY's cafe — there is no separate `cafe` type in SHOP_TYPE_IDS). { kind: 'kitHot', zone: 'wall', min: 1, max: 2, priority: 5, fallbackZones: ['aisle'], o: { pool: POOLS.milkbar_hot } }, { kind: 'kitFloor', zone: 'aisle', min: 1, max: 3, priority: 2, fallbackZones: ['window'], o: { pool: POOLS.milkbar_floor } }, { kind: 'kitWall', zone: 'wall', min: 0, max: 1, priority: 1, o: { pool: POOLS.wall_service, wall: true, mountY: 2.35 } }, // the rattling air-con ], stockKind: 'snacks', signFlavour: ['MILK · BREAD · SMOKES', 'PIES HOT', 'PAPERS IN', 'PHONECARDS', 'NO SCHOOL KIDS AT LUNCH'], }, // ── DEPT (anchor) ────────────────────────────────────────────────────────────── dept: { label: 'Department Store', archetypeBias: [['hall', 4], ['wide', 1]], wallpaperBias: ['floral-gold', 'damask-mauve', 'trellis-blue'], floorBias: ['carpet-greygreen', 'carpet-swirl', 'boards-polished'], clutter: 0.7, counterPos: 'corner', fittings: [ { kind: 'cubeShelf', zone: 'aisle', min: 2, max: 4, priority: 4 }, { kind: 'clothesRack', zone: 'centre', min: 2, max: 4, priority: 4 }, { kind: 'glassCase', zone: 'aisle', min: 1, max: 2, priority: 3 }, { kind: 'metalShelf', zone: 'wall', min: 2, max: 4, priority: 3 }, { kind: 'escalator', zone: 'centre', min: 1, max: 1, priority: 5 }, // the grand-hall centrepiece { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, // ── R41: SPEND CAREFULLY HERE. dept is the worst room in the game (GLB-on 188 pre-R41, i.e. it owns // the whole 162-draw margin), so it gets the fewest additions of any type: columns for the hall and // one rack row. { kind: 'kitColumn', zone: 'centre', min: 0, max: 2, priority: 3, o: { pool: POOLS.columns } }, { kind: 'kitRack', zone: 'aisle', min: 1, max: 2, priority: 3, o: { pool: POOLS.shop_racks, stock: 'box', items: 8 } }, { kind: 'kitWall', zone: 'wall', min: 0, max: 1, priority: 2, o: { pool: POOLS.wall_boards, wall: true, mountY: 1.6, stock: 'box', items: 6, maxRun: 3.0 } }, ], stockKind: 'mixed', signFlavour: ['MANCHESTER ↑', 'MENSWEAR', 'HABERDASHERY', 'GROUND FLOOR', 'STAFF ONLY'], }, // ── STALL (market) ─────────────────────────────────────────────────────────────── stall: { label: 'Market Stall', archetypeBias: [['wide', 3], ['hall', 1]], wallpaperBias: ['woodchip-white', 'stripe-sage'], // barely walled; open feel floorBias: ['lino-cork', 'boards-polished'], clutter: 0.95, counterPos: 'forward', fittings: [ { kind: 'trestleTable', zone: 'aisle', min: 3, max: 6, priority: 5 }, { kind: 'crate', zone: 'centre', min: 2, max: 5, priority: 3 }, { kind: 'clothesRack', zone: 'wall', min: 0, max: 2, priority: 2 }, { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 8 }, // ── R41: this closes a MEASURED HOLE, not just a dressing gap. R39 counted 48 stall rooms and found // 21 of them (44%) with no crate at all — the recipe's `crate` is zone 'centre' and the 3–6 trestles // take the aisle first, so RED TICKET DAY could name a stall you cannot dig in. These bins carry the // full fallback chain (aisle → centre → window → anywhere), so a market stall always has something // to riffle. Re-measured after the change; the number is in C-progress.md §41.4. { kind: 'kitBin', zone: 'aisle', min: 2, max: 4, priority: 4, fallbackZones: ['centre', 'window'], anywhere: true, o: { pool: POOLS.record_bins, dig: true, stock: 'sleeve', items: 12 } }, { kind: 'kitRack', zone: 'aisle', min: 1, max: 2, priority: 3, fallbackZones: ['centre'], o: { pool: POOLS.shop_racks, stock: 'box', items: 8 } }, ], stockKind: 'boxes', signFlavour: ['$1 EACH', 'CASH ONLY', 'FIRST IN BEST DRESSED', 'MAKE ME AN OFFER', 'FRESH TODAY'], }, // ── PUB (venue, v3.0-alpha · round 12, behind ?gigs=1) ─────────────────────────────── // The band room. `venue: true` makes layout.js place a stage (+ PA/amps) at the back wall and emit // room.watchPoints[] for Lane D's crowd + room.stage for the band. Bar counter = a normal counter, so // the barkeep is just a keeper. Sticky-carpet floor; the blocked back doorway doubles as the green room. pub: { label: 'Pub', archetypeBias: [['wide', 3], ['hall', 2]], // big open room for a crowd wallpaperBias: ['damask-mauve', 'woodchip-white', 'stripe-sage'], floorBias: ['carpet-swirl', 'carpet-mustard'], // sticky carpet clutter: 0.45, // keep the floor open — the crowd fills it counterPos: 'corner', // the bar, along the east wall (keeper = barkeep) venue: true, // → stage + PA + watchPoints (layout.js) // venueSpec (round 13): per-kind stage/crowd tuning read by layout.js placeStage/placeWatchPoints. // watchMax = crowd cap (== max watchPoints); deckH = deck height; deckDepth/deckWidthFrac size the // deck. pub values == the R12 constants, so the pub stage is byte-identical (bar the new drums pose). venueSpec: { watchMax: 8, deckH: 0.32, deckDepth: 2.0, deckWidthFrac: 0.58 }, // [R41] the bar is Lane E's `plywood_bar`, not the shop `counter_till`. Same fitting, same keeper // stand, same service strip — only the model changes. counterGlb: 'plywood_bar', fittings: [ { kind: 'trestleTable', zone: 'wall', min: 1, max: 3, priority: 2 }, // pub tables against the walls { kind: 'metalShelf', zone: 'wall', min: 0, max: 1, priority: 2 }, // bar back-shelf (bottles) { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, // ── R41 — THE PUB FINALLY GETS FURNISHED. Before this round the whole room was a bar, a stage, two // PA cabs and up to three trestles: the gig venue with almost nothing in it. Seating hugs the WALLS // and the floor props sit at priority 2 so the door→bar path loop pulls them FIRST — the crowd's // standing room (watchPoints) is computed after placement and must survive furnishing. Both numbers // (draws AND watchPoints per venue) are re-measured in C-progress.md §41.4. // ORDER MATTERS INSIDE THIS BLOCK (and only inside it — every pre-R41 spec is above and untouched): // the venue kit goes FIRST because the stage flanks and the side-wall slots beside the stage are // the same 0.4 m cells. Seating placed first took them, and the gear landed 5 times in 15 rooms; // gear first, and it lands every time while the seating simply takes the next wall slot along. { kind: 'kitVenue', zone: 'stage', min: 2, max: 3, priority: 2, fallbackZones: ['wall', 'aisle'], o: { pool: POOLS.venue_kit } }, { kind: 'kitSeat', zone: 'wall', min: 3, max: 6, priority: 3, fallbackZones: ['aisle', 'centre'], o: { pool: POOLS.pub_seating } }, { kind: 'kitFloor', zone: 'wall', min: 3, max: 6, priority: 2, fallbackZones: ['aisle'], o: { pool: POOLS.pub_floor } }, { kind: 'kitWall', zone: 'wall', min: 0, max: 1, priority: 1, o: { pool: POOLS.wall_service, wall: true, mountY: 2.4 } }, // the DJ rig beside the bar — STATICS (E: "a static copy is 1 draw and looks the same standing still") { kind: 'djBooth', zone: 'counter', min: 1, max: 1, priority: 4, gap: 2.2, fallbackZones: ['stage', 'wall', 'aisle', 'centre'], anywhere: true, o: { booth: 'venue' } }, ], stockKind: 'snacks', signFlavour: ['LIVE MUSIC FRI', 'HAPPY HOUR 5–6', 'NO THONGS AFTER 7', 'COLD BEER ON TAP', 'BANDS OUT BACK'], }, // ── BAND ROOM (venue, v3.0-beta · round 13, behind ?gigs=1) ────────────────────────── // A tin shed on the warehouse fringe: small, scuzzy, low ceiling, the PA louder than the room and the // stage barely a riser. No tables — you stand and you sweat. `genre: 'grunge'` ⇒ gigKey 'gig-grunge'. // Reuses the pub venue machinery; only the dressing + venueSpec differ. band_room: { label: 'Band Room', archetypeBias: [['cosy', 3], ['pokey', 2], ['wide', 1]], // small rooms; low ceilings come from storeys=1 wallpaperBias: ['woodchip-white', 'stripe-sage', 'damask-mauve'], // dim + dingy floorBias: ['lino-cork', 'carpet-swirl', 'boards-polished'], // bare/sticky clutter: 0.35, // near-empty standing room — the crowd IS the furniture counterPos: 'corner', // a plank bar in the corner (keeper = barkeep) venue: true, venueSpec: { watchMax: 8, deckH: 0.2, deckDepth: 1.6, deckWidthFrac: 0.62 }, // barely a riser, wide + shallow counterGlb: 'plywood_bar', // a plank bar is literally what this recipe says it is fittings: [ { kind: 'metalShelf', zone: 'wall', min: 1, max: 2, priority: 2 }, // bar back-shelf (tinnies) { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, // ── R41: PA, sub, flight cases, a smoke machine and a distro box shoved against the wall — the tin // shed's furniture is the gear. No seating: you stand and you sweat (the recipe's own words). { kind: 'kitVenue', zone: 'stage', min: 2, max: 4, priority: 3, fallbackZones: ['wall', 'aisle'], o: { pool: POOLS.venue_kit } }, { kind: 'kitFloor', zone: 'wall', min: 2, max: 3, priority: 2, fallbackZones: ['aisle'], o: { pool: POOLS.pub_floor } }, { kind: 'djBooth', zone: 'counter', min: 1, max: 1, priority: 4, gap: 2.2, fallbackZones: ['stage', 'wall', 'aisle'], o: { booth: 'venue' } }, ], stockKind: 'snacks', signFlavour: ['ALL AGES SUN', 'PAY THE BAND', 'NO STAGE DIVING', 'MERCH OUT BACK', '$5 ON THE DOOR'], }, // ── RSL CLUB (venue, v3.0-beta · round 13, behind ?gigs=1) ─────────────────────────── // Carpet, flock wallpaper, a bistro round the edge, a proper stage at one end, and the biggest crowd // cap in town — the round-13 crowd-cap stress case. `genre: 'covers'` ⇒ gigKey 'gig-covers'. rsl: { label: 'RSL Club', archetypeBias: [['hall', 3], ['wide', 2]], // big floor for the biggest crowd wallpaperBias: ['damask-mauve', 'floral-gold', 'trellis-blue'], // clubby flock floorBias: ['carpet-mustard', 'carpet-swirl', 'carpet-greygreen'], // patterned club carpet clutter: 0.5, // bistro tables round the edges, floor kept open for the crowd counterPos: 'corner', // the members' bar venue: true, venueSpec: { watchMax: 12, deckH: 0.4, deckDepth: 2.4, deckWidthFrac: 0.5 }, // a raised stage at one end counterGlb: 'plywood_bar', fittings: [ { kind: 'trestleTable', zone: 'wall', min: 2, max: 4, priority: 2 }, // bistro tables round the edge { kind: 'metalShelf', zone: 'wall', min: 1, max: 2, priority: 2 }, // bar back-shelf { kind: 'counter', zone: 'counter', min: 1, max: 1, priority: 9 }, // ── R41: banquettes and bistro tables round the edge, the pokies and the meat-raffle drum the // signage has been promising since R13. The 12-strong crowd cap is why everything hugs a wall. { kind: 'kitVenue', zone: 'stage', min: 1, max: 3, priority: 2, fallbackZones: ['wall', 'aisle'], o: { pool: POOLS.venue_kit } }, { kind: 'kitSeat', zone: 'wall', min: 3, max: 6, priority: 3, fallbackZones: ['aisle', 'centre'], o: { pool: POOLS.pub_seating } }, { kind: 'kitFloor', zone: 'wall', min: 3, max: 5, priority: 2, fallbackZones: ['aisle'], o: { pool: POOLS.rsl_floor } }, ], stockKind: 'snacks', signFlavour: ['MEMBERS & GUESTS', 'BISTRO OPEN', 'BINGO THURS', 'MEAT RAFFLE 7PM', 'LEST WE FORGET'], }, }; // Aliases: accept thriftgod / Overpass-style type names and map to canonical recipes. const ALIAS = { music: 'record', records: 'record', charity: 'opshop', second_hand: 'opshop', secondhand: 'opshop', antiques: 'opshop', op_shop: 'opshop', toys: 'toy', toyshop: 'toy', books: 'book', bookshop: 'book', bookbarn: 'book', video_games: 'video', games: 'video', vhs: 'video', rental: 'video', pawnbroker: 'pawn', pawnshop: 'pawn', milk_bar: 'milkbar', deli: 'milkbar', corner_store: 'milkbar', department: 'dept', anchor: 'dept', market: 'stall', stalls: 'stall', }; export const SHOP_TYPES = Object.keys(RECIPES); export function canonicalType(type) { if (!type) return 'opshop'; const t = String(type).toLowerCase(); if (RECIPES[t]) return t; if (ALIAS[t]) return ALIAS[t]; return 'opshop'; // sensible default: an op shop takes anything } export function getRecipe(type) { const key = canonicalType(type); return { key, ...RECIPES[key] }; } // Lane F hook: let an authored registry override recipe fields without editing this lane. // registry: { [typeKey]: partialRecipe }. Shallow-merges over the built-in recipe. export function mergeRegistry(registry) { if (!registry) return; for (const [k, patch] of Object.entries(registry)) { const key = canonicalType(k); if (RECIPES[key]) Object.assign(RECIPES[key], patch); } }