From 8e61640c38e39ae9abc19bc00552cbcfd8b1f01f Mon Sep 17 00:00:00 2001 From: Monster Robot Party Date: Tue, 4 Aug 2026 00:49:49 +1000 Subject: [PATCH] =?UTF-8?q?LANE10:=20SITE=207=20=E2=80=94=20the=20real=20s?= =?UTF-8?q?hop,=20imported=20from=20its=20own=20database?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The level the whole game started as, and the only one that isn't authored. robotmonster.party/store/ is assembled procedurally from Postgres (the wowplatter virtual_* schema, served by recordgod), and this game's sites are already data — so tools/import_store.py reads the live shop and emits assets/store/shop_floor.json: the actual 6.66 x 9.51 m floor, 35 racks across 21 archetypes, 278 bins carrying their real genre labels (HARD TRANCE, $12-$15 EURO/ITALO HOUSE), 21,159 records of stock. Levels.record_store() loads it; 'store' joins ORDER as the seventh site. The shop's schema IS the cascade. virtual_crate.rack_id already records which rack holds which bin, so bins arrive frozen and supported_by their rack and none of the structure is authored. To carry that, smashables gained two optional generic keys — 'id' and 'supported_by' — so any level can express support as data. Every existing level is unaffected. Two reconciliations, because a storefront is a renderer: - Mesh sizes are a lie: 21 archetypes share a few stand-in GLBs, so each piece carries 'fit' (the archetype's real dims from the shop's table) and _glb_piece scales the visual to it and builds the collider from it. Also fixed racks being flattened to the floor — pos_y is a piece's BOTTOM, which is what sit_on means, so the ceiling beams, aircon and wall shelves now stay up where they belong. - Nothing collides in a renderer: racks that sat inside each other for years get separated (weighted, so racks barely move and aisles keep their shape — 3 m of correction across 35), rack footprints inset 1.5 cm a side for the few genuinely wedged units, and the importer AUDITS ITS OWN OUTPUT with the same test probe_overlap runs. Bins are deliberately not separated from their rack: cargo inside a container is what the shop means, they spawn frozen, and the audit skips frozen bodies because a frozen body cannot be depenetrated across the room. dev/probe_store.gd: 22 racks hold bins, 255 labels survived, and smashing the rack carrying 22 bins releases exactly those 22 while the other 256 stay frozen. All gates green: smoke clean, 7/7 sites 0.00 m/s, overlap CLEAN, LANE9 probes pass. Co-Authored-By: Claude Opus 5 --- LANES/LANE10-record-store.md | 63 + README.md | 7 +- game/README.md | 41 +- game/assets/store/shop_floor.json | 5577 +++++++++++++++++++++++++++++ game/dev/probe_store.gd | 82 + game/scripts/Levels.gd | 82 +- game/scripts/Main.gd | 35 +- tools/import_store.py | 318 ++ 8 files changed, 6197 insertions(+), 8 deletions(-) create mode 100644 LANES/LANE10-record-store.md create mode 100644 game/assets/store/shop_floor.json create mode 100644 game/dev/probe_store.gd create mode 100644 tools/import_store.py diff --git a/LANES/LANE10-record-store.md b/LANES/LANE10-record-store.md new file mode 100644 index 0000000..7ea3faa --- /dev/null +++ b/LANES/LANE10-record-store.md @@ -0,0 +1,63 @@ +# LANE 10 — SITE 7: MONSTER ROBOT PARTY + +*The level the whole game started as. Spec'd and executed by Fable, 2026-08-04.* + +## The idea + +Every other site is authored. This one is **imported from the real shop**. The live +storefront at `robotmonster.party/store/` is procedurally assembled from Postgres rows +(`virtual_rack`, `virtual_crate`, `virtual_rack_type*`, `virtual_crate_type` — the +wowplatter schema, now served by recordgod/storegod), and the game's sites are already +data. So Site 7 is not hand-placed: a converter reads the shop and emits a level. + +`GET https://robotmonster.party/virtual/scene` returns the shop floor: + +| | | +|---|---| +| room | 6.66 m × 9.51 m, 2.89 m ceiling | +| racks | 35 placed, across 21 archetypes | +| crates | 302, every one on a rack | +| labels | real: `HARD TRANCE`, `$12-$15 EURO/ITALO HOUSE`, `SOUND TRACKS, SPOKEN WORD & COMEDY` | +| stock | **22,049 records** across the bins (`n_items`) | + +The rack archetype names (`wooden-rack-type-long`, `white-tub-3x3`, `chiu-long`) are the +same names as the GLBs in `3D-STORE/`, because both were generated from the same table. + +## Why this is the best-fitting level in the game + +The shop's schema **is** the cascade. `virtual_crate.rack_id` already says which rack +holds which bin, so `rack.supports = [its crates]` falls out of the data — knock a rack +and the real bins that really sit on it really go over. Nothing to author. + +## Deliverables + +### 1. Generic data-driven cascade *(`Main.gd`, small)* +Extend the `smashables` spec entry with two optional keys so a level can express support +without code: `"id"` (a name other entries can refer to) and `"supported_by"` (the id of +the piece holding this one up). Main builds in order, keeps `id -> Smashable`, and wires +`holder.supports.append(piece)`. Every existing level is unaffected. + +### 2. `tools/import_store.py` — the converter +Fetches the live scene (`--url`, or `--offline `), filters to the shop-floor space, +and emits `game/assets/store/shop_floor.json`: +- world-space rack positions, **re-centred** (the shop's origin is a corner; the game + centres its bounds on 0) +- each crate placed by `rack_pos + rotate_y(crate_local, rack.rotation_y)`, carrying its + real `label` and `n_items` +- archetype → game GLB + material mapping (tubs are `plastic`, timber racks `wood`, + columns and aircon `steel`) +- crates spawn **frozen** and supported by their rack: they don't jitter at spawn (the + stillness gate stays green) and they go live exactly when the rack does +- a **measured spawn point** — a grid scan for open floor, because a 6.66 × 9.51 room + holding 35 racks has very little of it, and spawning inside a rack is not a level + +Re-runnable: when the real shop is rearranged, re-run it and the level changes. + +### 3. `Levels.record_store()` — the site +Loads the JSON and returns a normal Floorplan spec. Timber floor, white walls, the +brand's pink as accent. Added to `Levels.ORDER` as the seventh site. + +## Gates +Headless smoke clean · `probe_levels` **7/7** at 0.00 m/s · `probe_overlap` CLEAN · +the LANE9 probes still green. Plus `dev/probe_store.gd`: the site builds, the rack→crate +cascade is wired, and smashing one rack releases exactly its own bins. diff --git a/README.md b/README.md index 34462be..0c8c8a0 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,11 @@ rest of your day. Your score is your payslip: **wages earned minus damages cause during a meltdown you want to wreck a lot of cheap rubbish rather than one expensive filing cabinet. Falling Down, scored by payroll. -**Six sites** (Scranton branch, Pawnee city hall, a hacker house, a windowless IT -basement, a greengrocer where everything is soft except the bottles, and the Backrooms — where the walls are procedurally generated and everything +**Seven sites** (Scranton branch, Pawnee city hall, a hacker house, a windowless IT +basement, a greengrocer where everything is soft except the bottles, the Backrooms — where the walls are procedurally generated and everything you're there to destroy is invisible until you coat it in extinguisher powder, some of -which you breathe) and **four modes** (Cubicle Hell, the Gauntlet, Total Destruction, Against the +which you breathe, and the real Monster Robot Party shop — imported live from the +storefront's own database, bins, genre labels and all) and **four modes** (Cubicle Hell, the Gauntlet, Total Destruction, Against the Clock). Sites are data, not code — see `game/scripts/Levels.gd`. Design, built and planned, lives in [`LANES/LANE7-cubicle-hell.md`](LANES/LANE7-cubicle-hell.md). diff --git a/game/README.md b/game/README.md index 7eba639..22b840d 100644 --- a/game/README.md +++ b/game/README.md @@ -21,7 +21,7 @@ Mac-first (Apple Silicon / Metal), **Godot 4.7**, **Jolt** physics. | **1–6 · wheel · Q** | pick a weapon · cycle · swap to previous | | **H** | cycle HUD style (Arcade / Minimal / Work Order / Dev) | | **M** | cycle game mode (Cubicle Hell / Gauntlet / Total Destruction / Against the Clock) | -| **L** | cycle site (Scranton / Pawnee / Incubator / Sub-level 4 / Greengrocer / Level 0) | +| **L** | cycle site (Scranton / Pawnee / Incubator / Sub-level 4 / Greengrocer / Level 0 / Monster Robot Party) | | **G** | restart a Gauntlet run | | **F** | report the disc in hand (Find-the-Misfiled-Disc) | | **E** | work at the station you're standing at (locks you in place); else hop on / off an office chair under the crosshair; else pick up whatever's there | @@ -46,6 +46,7 @@ generator can later emit the same structure. | **SUB-LEVEL 4** | concrete, green strip lights | **no windows**, exposed services, a wall of servers | | **THE GREENGROCER** | bright, pale, warm strips | everything is **soft** except the bottles — see below | | **LEVEL 0** | one colour, everywhere | the Backrooms. Walls are **generated**, and everything you're here to destroy is **invisible** | +| **MONSTER ROBOT PARTY** | timber, white, shop pink | the real shop, **imported from the live storefront** — see below | `dev/probe_levels.gd` builds every one and reports residual motion after a full second. All six must read `0.00 m/s`. @@ -259,6 +260,44 @@ tick on the meter — it's what makes the room hostile rather than merely beige. per object but each object bills at its own value, so the meltdown decision is *smash a lot of cheap things fast* versus *one expensive swing*. +### MONSTER ROBOT PARTY — the only site that isn't authored + +The shop the whole game started as, and the only one **imported** rather than written. +The live storefront at `robotmonster.party/store/` is assembled procedurally from Postgres +(the wowplatter `virtual_*` schema, served by recordgod), and this game's sites are already +data — so `tools/import_store.py` reads the real shop and emits +`assets/store/shop_floor.json`: + +| | | +|---|---| +| room | 6.66 m × 9.51 m, 2.89 m ceiling — the actual floor | +| racks | 35, across 21 archetypes | +| bins | 278, each carrying its **real genre label** (`HARD TRANCE`, `$12-$15 EURO/ITALO HOUSE`) | +| stock | **21,159 records** | + +**The shop's schema is the cascade.** `virtual_crate.rack_id` already records which rack +holds which bin, so every bin arrives frozen and `supported_by` its rack — knock a rack +over and the actual bins that actually sit on it actually go. None of it is authored; it +comes off the shop floor. `dev/probe_store.gd` proves it: smashing the rack that carries +22 bins releases exactly those 22 and leaves the other 256 frozen. + +Two things the importer has to reconcile, because a web storefront is a renderer: + +- **The mesh sizes are a lie.** 21 archetypes share a handful of stand-in GLBs, so each + piece carries a `fit` — the archetype's real width/height/depth from the shop's own + table — and the game scales the visual to it and builds the collider from it. +- **Nothing collides in a renderer.** A rack can sit a few centimetres inside its + neighbour and look fine for years; Jolt disagrees violently. The importer separates + them (weighted, so racks barely move and the aisles keep their shape — 3 m of total + correction across 35 racks), insets rack footprints by 1.5 cm a side, and then **audits + its own output** with the same test `dev/probe_overlap.gd` runs. + +Re-run it when the real shop is rearranged and the level follows: + +```sh +python3 tools/import_store.py +``` + ## The fun layer (LANE9) Three antagonists to the *tidy* impulse, all riding systems that already exist: diff --git a/game/assets/store/shop_floor.json b/game/assets/store/shop_floor.json new file mode 100644 index 0000000..7f2dd91 --- /dev/null +++ b/game/assets/store/shop_floor.json @@ -0,0 +1,5577 @@ +{ + "_source": "/tmp/scene_probe.json", + "_note": "GENERATED by tools/import_store.py from the live shop. Do not hand-edit.", + "room": { + "w": 6.66, + "d": 9.51, + "h": 2.89 + }, + "counts": { + "racks": 35, + "crates": 278, + "stock_records": 21159, + "slabs": 6 + }, + "spawn": { + "at": [ + -0.23, + 2.84 + ] + }, + "slabs": [ + { + "size": [ + 0.48, + 0.04, + 1.95 + ], + "at": [ + -3.33, + 0.87, + -4.705 + ], + "mat": "wood" + }, + { + "size": [ + 2.6, + 1.22, + 0.05 + ], + "at": [ + 0.51, + 1.61, + -4.755 + ], + "mat": "wood" + }, + { + "size": [ + 6.5, + 0.36, + 0.32 + ], + "at": [ + -3.33, + 2.58, + -1.135 + ], + "mat": "trim" + }, + { + "size": [ + 6.5, + 0.36, + 0.32 + ], + "at": [ + -3.33, + 2.58, + 1.785 + ], + "mat": "trim" + }, + { + "size": [ + 4.6, + 0.04, + 0.48 + ], + "at": [ + -1.43, + 0.87, + -4.755 + ], + "mat": "wood" + }, + { + "size": [ + 4.85, + 1.6, + 0.05 + ], + "at": [ + -1.68, + 2.1, + -4.755 + ], + "mat": "wood" + } + ], + "smashables": [ + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + 1.07, + 0.301 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.77, + 1.07, + 1.99 + ], + "id": "r7", + "name": "wooden-rack-long-trance-2" + }, + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + -0.43, + 0.165 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.77, + 1.07, + 1.99 + ], + "id": "r8", + "name": "wooden-rack-long-breaks-1" + }, + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + 1.07, + -1.717 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.77, + 1.07, + 1.99 + ], + "id": "r6", + "name": "wooden-rack-long-trance-1" + }, + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + -1.93, + -1.855 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.77, + 1.07, + 1.99 + ], + "id": "r10", + "name": "wooden-rack-long-dnb-1" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + -3.087, + -4.255 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.5, + 0.44, + 1.53 + ], + "id": "r15", + "name": "black-tubs-4+4-booth" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + -3.29, + 0.485 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.5, + 0.5, + 1.27 + ], + "id": "r16", + "name": "white-tub-3x3-long-01" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + -3.159, + -0.815 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.5, + 0.5, + 1.27 + ], + "id": "r17", + "name": "white-tub-3x3-long-02" + }, + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + -1.97, + 0.165 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.77, + 1.07, + 1.99 + ], + "id": "r11", + "name": "wooden-rack-long-dnb-2" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + 3.004, + -4.255 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.52, + 0.53, + 1.53 + ], + "id": "r23", + "name": "3-top-4-low-rack-wide-01" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + -3.29, + -2.555 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.52, + 0.53, + 1.53 + ], + "id": "r24", + "name": "3-top-4-low-rack-wide-02" + }, + { + "glb": "store-column-white", + "kind": "steel", + "at": [ + 0.93, + 4.285 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.33, + 2.6, + 0.43 + ], + "id": "r28", + "name": "WHITE COLUMN 1" + }, + { + "glb": "store-column-white", + "kind": "steel", + "at": [ + -1.27, + 4.285 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.33, + 2.6, + 0.43 + ], + "id": "r29", + "name": "WHITE COLUMN 2" + }, + { + "glb": "store-station", + "kind": "wood", + "at": [ + -2.15, + 4.245 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.85, + 0.84, + 0.47 + ], + "id": "r31", + "name": "STATION 1" + }, + { + "glb": "store-station", + "kind": "wood", + "at": [ + -3.33, + 2.965 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.47, + 0.84, + 0.85 + ], + "id": "r32", + "name": "STATION 2" + }, + { + "glb": "store-trolley", + "kind": "steel", + "at": [ + -3.33, + 3.885 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.38, + 0.7, + 0.83 + ], + "id": "r34", + "name": "library trolley" + }, + { + "glb": "store-column-white", + "kind": "steel", + "at": [ + -3.31, + 1.785 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.05, + 2.6, + 0.29 + ], + "id": "r35", + "name": "GREY COLUMN 1" + }, + { + "glb": "store-column-white", + "kind": "steel", + "at": [ + -3.461, + -1.135 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.05, + 2.6, + 0.29 + ], + "id": "r36", + "name": "GREY COLUMN 2" + }, + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + -0.43, + -1.855 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.77, + 1.07, + 1.99 + ], + "id": "r9", + "name": "wooden-rack-long-breaks-2" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + 1.937, + -4.225 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 1.53, + 0.44, + 0.5 + ], + "id": "r14", + "name": "white-tubs-4+4-window" + }, + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + 2.884, + -2.712 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.56, + 1.22, + 1.47 + ], + "id": "r20", + "name": "big-4x4x4-01" + }, + { + "glb": "store-aircon", + "kind": "steel", + "at": [ + 0.62, + 1.545 + ], + "sit_on": 2.25, + "yaw": 0.0, + "fit": [ + 1.12, + 0.35, + 0.22 + ], + "id": "r43", + "name": "AIRCON" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + 2.62, + 1.816 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.52, + 0.53, + 1.53 + ], + "id": "r25", + "name": "3-top-4-low-rack-wide-03" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + -1.73, + -3.655 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 1.99, + 1.07, + 0.77 + ], + "id": "r4", + "name": "wooden-rack-hiphop-wide-1" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + 1.567, + -3.139 + ], + "sit_on": 0.02, + "yaw": 3.1416, + "fit": [ + 1.99, + 1.07, + 0.77 + ], + "id": "r5", + "name": "wooden-rack-hiphop-wide-2" + }, + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + 2.58, + 0.295 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.56, + 1.22, + 1.47 + ], + "id": "r22", + "name": "big-4x4x4-03" + }, + { + "glb": "store-rack-wide", + "kind": "wood", + "at": [ + 3.135, + 3.355 + ], + "sit_on": 0.0, + "yaw": 1.5708, + "fit": [ + 1.35, + 0.75, + 0.4 + ], + "id": "r27", + "name": "chiu-short" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -3.534, + -4.755 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.31, + 0.05, + 0.31 + ], + "id": "r45", + "name": "floor crate" + }, + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + 2.51, + 4.285 + ], + "sit_on": 0.0, + "yaw": 3.1416, + "fit": [ + 1.85, + 0.75, + 0.4 + ], + "id": "r26", + "name": "chiu-long" + }, + { + "glb": "store-rack-long", + "kind": "wood", + "at": [ + 2.58, + -1.2 + ], + "sit_on": 0.0, + "yaw": 0.0, + "fit": [ + 0.56, + 1.22, + 1.47 + ], + "id": "r21", + "name": "big-4x4x4-02" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.07, + 0.301 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.07, + 0.301 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + -0.419 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + -0.419 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + -0.059 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7", + "label": "HARD TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + -0.059 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7", + "label": "CENTRAL CUTS" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + 0.301 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7", + "label": "HARD TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + 0.301 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7", + "label": "$12-$15 EURO/ITALO HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + 0.661 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7", + "label": "HARD TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + 0.661 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7", + "label": "$12-$15 EURO/ITALO HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + 1.021 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7", + "label": "$5 - $10 EURO/ITALO HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + 1.021 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r7", + "label": "$5 - $10 EURO/ITALO HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + -0.419 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "TRANCE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + -0.419 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "TRANCE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + -0.059 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "TRANCE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + -0.059 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "TRANCE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + 0.301 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "TRANCE LABELS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + 0.301 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "ACID & ACID HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + 0.661 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "EURO & ITALO HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + 0.661 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "EURO & ITALO HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + 1.021 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "EURO & ITALO HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + 1.021 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r7", + "label": "EURO & ITALO HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + -0.555 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + -0.555 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8", + "label": "$15 BREAKS & BROKEN" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + -0.195 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8", + "label": "$15 BREAKS & BROKEN" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + -0.195 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8", + "label": "$15 BREAKS / BROKEN" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + 0.165 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8", + "label": "TIDY TRAX" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + 0.165 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + 0.525 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8", + "label": "DOWNTEMPO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + 0.525 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8", + "label": "HARD HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + 0.885 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8", + "label": "HARD HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + 0.885 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r8", + "label": "HARD HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + -0.555 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + -0.555 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + -0.195 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + -0.195 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + 0.165 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + 0.165 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "DOWNTEMPO & AMBIENT" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + 0.525 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "HARD CORE & HAPPY HARD CORE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + 0.525 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "HARD HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + 0.885 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "HARD HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + 0.885 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r8", + "label": "HARD HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + -2.437 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + -2.437 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "TECH HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + -2.077 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + -2.077 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "TECH HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + -1.717 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + -1.717 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "PSY & GOA TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + -1.357 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "HARD TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + -1.357 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "PSY & GOA TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.875, + -0.997 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "HARD TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.265, + -0.997 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r6", + "label": "ACID & ACID HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + -2.437 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "TRANCE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + -2.437 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "TECH HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + -2.077 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "TRANCE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + -2.077 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "TECH HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + -1.717 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "TRANCE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + -1.717 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "HOOJ CHOONS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + -1.357 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "TRANCE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + -1.357 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "STRICTLY RHYTHM & POSITIVA" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.875, + -0.997 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "LATIN HOUSE, ACID/ACID HOUSE" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.265, + -0.997 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r6", + "label": "TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.125, + -2.575 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10", + "label": "2010 PLUS DANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.735, + -2.575 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10", + "label": "SAMPOLOGY" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.125, + -2.215 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10", + "label": "TECHNO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.735, + -2.215 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.125, + -1.855 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10", + "label": "TECHNO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.735, + -1.855 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10", + "label": "DRUM & BASS" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.125, + -1.495 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10", + "label": "TECHNO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.735, + -1.495 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10", + "label": "DRUM & BASS" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.125, + -1.135 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10", + "label": "TECHNO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.735, + -1.135 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r10", + "label": "DRUM & BASS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.125, + -2.575 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.735, + -2.575 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "DRUM & BASS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.125, + -2.215 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.735, + -2.215 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "DRUM & BASS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.125, + -1.855 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.735, + -1.855 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "DRUM & BASS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.125, + -1.495 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.735, + -1.495 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "DRUM & BASS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.125, + -1.135 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.735, + -1.135 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r10", + "label": "DRUM & BASS" + }, + { + "glb": "store-tub-black", + "kind": "plastic", + "at": [ + -3.087, + -4.84 + ], + "sit_on": 0.44, + "yaw": 0.0, + "fit": [ + 0.36, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r15", + "label": "$5-10 BREAKS" + }, + { + "glb": "store-tub-black", + "kind": "plastic", + "at": [ + -3.087, + -4.45 + ], + "sit_on": 0.44, + "yaw": 0.0, + "fit": [ + 0.36, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r15", + "label": "$5-10 BREAKS" + }, + { + "glb": "store-tub-black", + "kind": "plastic", + "at": [ + -3.087, + -4.06 + ], + "sit_on": 0.44, + "yaw": 0.0, + "fit": [ + 0.36, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r15", + "label": "$5-10 TRANCE" + }, + { + "glb": "store-tub-black", + "kind": "plastic", + "at": [ + -3.087, + -3.67 + ], + "sit_on": 0.44, + "yaw": 0.0, + "fit": [ + 0.36, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r15", + "label": "$5-10 TRANCE" + }, + { + "glb": "store-tub-black", + "kind": "plastic", + "at": [ + -3.087, + -4.84 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.36, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r15", + "label": "$5-10 DRUM & BASS" + }, + { + "glb": "store-tub-black", + "kind": "plastic", + "at": [ + -3.087, + -4.45 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.36, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r15", + "label": "$5-10 DRUM & BASS" + }, + { + "glb": "store-tub-black", + "kind": "plastic", + "at": [ + -3.087, + -4.06 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.36, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r15", + "label": "$5-10 HARD TRANCE" + }, + { + "glb": "store-tub-black", + "kind": "plastic", + "at": [ + -3.087, + -3.67 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.36, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r15", + "label": "$5-10 HARD TRANCE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + 0.052 + ], + "sit_on": 0.53, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r16", + "label": "$5-10 HARD HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + 0.485 + ], + "sit_on": 0.53, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r16", + "label": "$5-10 HARD HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + 0.918 + ], + "sit_on": 0.53, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r16", + "label": "$5-10 TRANCE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + 0.052 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r16", + "label": "$5-10 HARD HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + 0.485 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r16" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + 0.918 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r16", + "label": "$5-10 TECHNO" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.159, + -1.248 + ], + "sit_on": 0.53, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r17", + "label": "$5-10 DEEP HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.159, + -0.815 + ], + "sit_on": 0.53, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r17", + "label": "$5-10 GARAGE HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.159, + -0.382 + ], + "sit_on": 0.53, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r17", + "label": "$5-10 PROG HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.159, + -1.248 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r17", + "label": "$5-10 LATIN/TRIBAL" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.159, + -0.815 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r17" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.159, + -0.382 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r17", + "label": "$5-10 PROG HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.165, + -0.555 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.775, + -0.555 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.165, + -0.195 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11", + "label": "TECHNO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.775, + -0.195 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11", + "label": "DRUM N BASS" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.165, + 0.165 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11", + "label": "TECHNO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.775, + 0.165 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11", + "label": "DRUM N BASS" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.165, + 0.525 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11", + "label": "$12-$15 TECHNO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.775, + 0.525 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11", + "label": "DRUM N BASS" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.165, + 0.885 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.775, + 0.885 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r11", + "label": "BASS MUSIC" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.165, + -0.555 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.775, + -0.555 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.165, + -0.195 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.775, + -0.195 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.165, + 0.165 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "TECHNO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.775, + 0.165 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "DRUM N BASS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.165, + 0.525 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "DRUM N BASS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.775, + 0.525 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "DRUM N BASS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.165, + 0.885 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "DUBSTEP/GRIME" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.775, + 0.885 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r11", + "label": "BASS MUSIC" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 3.004, + -4.775 + ], + "sit_on": 0.13, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r23", + "label": "$12-$15 GARAGE HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 3.004, + -4.255 + ], + "sit_on": 0.13, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r23", + "label": "$12-$15 ELECTRO" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 3.004, + -3.735 + ], + "sit_on": 0.13, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r23", + "label": "$5-$10 ELECTRO" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 3.004, + -4.84 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r23", + "label": "GARAGE HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 3.004, + -4.45 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r23", + "label": "GARAGE HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 3.004, + -4.06 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r23", + "label": "ELECTRO" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 3.004, + -3.67 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r23", + "label": "ELECTRO" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + -3.14 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r24", + "label": "$5-10 HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + -2.75 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r24", + "label": "$5-10 HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + -2.36 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r24", + "label": "$5-10 HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + -1.97 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r24", + "label": "$5-10 HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + -3.075 + ], + "sit_on": 0.13, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r24", + "label": "$5-10 HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + -2.555 + ], + "sit_on": 0.13, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r24", + "label": "$5-10 HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.29, + -2.035 + ], + "sit_on": 0.13, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r24", + "label": "$5-10 HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + -2.575 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9", + "label": "LEFTFIELD" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + -2.575 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9", + "label": "BREAKS & BROKEN" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + -2.215 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9", + "label": "BREAKS & BROKEN" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + -2.215 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9", + "label": "BREAKS & BROKEN" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + -1.855 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + -1.855 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9", + "label": "FUTURE JAZZ" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + -1.495 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9", + "label": "MORE ABSTRACT / IDM / INDUSTRIAL" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + -1.495 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9", + "label": "ACID JAZZ, FUTURE JAZZ & NU JAZZ" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.625, + -1.135 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9", + "label": "TRIP HOP / DOWNTEMPO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -0.235, + -1.135 + ], + "sit_on": 0.25, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r9", + "label": "DOWNTEMPO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + -2.575 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + -2.575 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + -2.215 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + -2.215 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + -1.855 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "BREAKS & BROKEN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + -1.855 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "AMBIENT & DOWNTEMPO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + -1.495 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "ABSTRACT / IDM & EXPERIMENTAL" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + -1.495 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "TRIP HOP" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.625, + -1.135 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "ACID JAZZ FUTURE JAZZ NU-JAZZ" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -0.235, + -1.135 + ], + "sit_on": 0.78, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r9", + "label": "ABSTRACT/IDM & EXPERIMENTAL" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 1.352, + -4.225 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r14", + "label": "$5-$15 SYNTH POP" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.522, + -4.225 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r14", + "label": "AUSSIE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 1.742, + -4.225 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r14", + "label": "$5-$15 SYNTH POP" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.131, + -4.225 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r14", + "label": "POP ROCK" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 1.352, + -4.225 + ], + "sit_on": 0.44, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r14", + "label": "SYNTH POP" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 1.742, + -4.225 + ], + "sit_on": 0.44, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r14", + "label": "SYNTH POP" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.131, + -4.225 + ], + "sit_on": 0.44, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r14", + "label": "POP ROCK" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.522, + -4.225 + ], + "sit_on": 0.44, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r14", + "label": "AUSSIE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -3.274 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "PROG HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -2.899 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "PROG HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -2.524 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "PROG HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -2.149 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "PROG HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -3.274 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "PROG HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -2.899 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "$12 PROG HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -2.524 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "TRIBAL HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -2.149 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "LATIN HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -3.274 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "DEEP HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -2.899 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "DEEP HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -2.524 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "DEEP HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.884, + -2.149 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r20", + "label": "80s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 0.62, + 1.545 + ], + "sit_on": 2.25, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r43", + "label": "VIP SECTION" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 0.62, + 1.545 + ], + "sit_on": 2.25, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r43", + "label": "VIP SECTION" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 0.62, + 1.545 + ], + "sit_on": 2.25, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r43", + "label": "VIP SECTION" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 0.62, + 1.545 + ], + "sit_on": 2.25, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r43", + "label": "VIP SECTION" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 0.62, + 1.545 + ], + "sit_on": 2.25, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r43", + "label": "VIP SECTION" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 0.62, + 1.545 + ], + "sit_on": 2.25, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r43", + "label": "NEW ARRIVALS" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 0.62, + 1.545 + ], + "sit_on": 2.25, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r43", + "label": "NEWER ARRIVALS" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.62, + 1.231 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r25", + "label": "$12-$15 TECHNO" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.62, + 1.621 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r25", + "label": "$12-$15 TECHNO" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.62, + 2.011 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r25", + "label": "$12-$15 TRANCE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.62, + 2.401 + ], + "sit_on": 0.54, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r25", + "label": "$12-$15 TRANCE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.62, + 1.296 + ], + "sit_on": 0.13, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r25", + "label": "$12-$15 TECHNO" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.62, + 1.816 + ], + "sit_on": 0.13, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r25", + "label": "$12-$15 HARD TRANCE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.62, + 2.336 + ], + "sit_on": 0.13, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r25", + "label": "$12-$15 TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.73, + -3.655 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.45, + -3.85 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4", + "label": "$5-$10 DISCO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.73, + -3.655 + ], + "sit_on": 0.02, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.09, + -3.85 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4", + "label": "$12-$15 DISCO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.73, + -3.85 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4", + "label": "$12-$15 DISCO" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.37, + -3.85 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.01, + -3.85 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.45, + -3.46 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4", + "label": "$5-$10 FUNK & SOUL" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -2.09, + -3.46 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4", + "label": "$12-$15 FUNK & SOUL" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.73, + -3.46 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4", + "label": "$12-$15 FUNK & SOUL" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.37, + -3.46 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + -1.01, + -3.46 + ], + "sit_on": 0.17, + "yaw": 0.0, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r4" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.45, + -3.85 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4", + "label": "GOSPEL" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.09, + -3.85 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4", + "label": "DISCO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.73, + -3.85 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4", + "label": "DISCO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.37, + -3.85 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4", + "label": "DISCO" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.01, + -3.85 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.45, + -3.46 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4", + "label": "FUNK & SOUL" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -2.09, + -3.46 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4", + "label": "FUNK & SOUL" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.73, + -3.46 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4", + "label": "FUNK & SOUL" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.37, + -3.46 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4", + "label": "FUNK & SOUL" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + -1.01, + -3.46 + ], + "sit_on": 0.9, + "yaw": 0.0, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r4", + "label": "FUNK & SOUL" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.207, + -3.334 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5", + "label": "JAZZ" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.847, + -3.334 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5", + "label": "LATIN, AFRO & CUBAN" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 2.287, + -2.944 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "HOUSE ACCAPELLAS & LIBRARY RECORDS" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.926, + -2.944 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "SOUND TRACKS, SPOKEN WORD & COMEDY" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.567, + -2.944 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "SOUND TRACKS, SPOKEN WORD & COMEDY" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.207, + -2.944 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "SOUND TRACKS, SPOKEN WORD & COMEDY" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.847, + -2.944 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "SOUND TRACKS, SPOKEN WORD & COMEDY" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 2.287, + -3.334 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "JAZZ LP\\'S" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.926, + -3.334 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "JAZZ LP\\'S" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 2.287, + -2.944 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.926, + -2.944 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5", + "label": "HOT TRACKS" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.567, + -2.944 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5", + "label": "ANIME SOUNDTRACKS" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.207, + -2.944 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5", + "label": "SOUNDTRACKS, SPOKEN WORD & COMEDY" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 0.847, + -2.944 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5", + "label": "EXOTICA, LOUNGE AND EZ LISTENING" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 2.287, + -3.334 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5", + "label": "JAPANESE CITY POP" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.926, + -3.334 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5", + "label": "JAPANESE JAZZ/ASSORTED" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.567, + -3.334 + ], + "sit_on": 0.17, + "yaw": 3.1416, + "fit": [ + 0.33, + 0.29, + 0.3 + ], + "frozen": true, + "supported_by": "r5", + "label": "JAPANESE JAZZ" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.567, + -3.334 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "JAZZ LP\\'S" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 1.207, + -3.334 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "JAZZ LP\\'S" + }, + { + "glb": "crate", + "kind": "wood", + "at": [ + 0.847, + -3.334 + ], + "sit_on": 0.9, + "yaw": 3.1416, + "fit": [ + 0.34, + 0.31, + 0.4 + ], + "frozen": true, + "supported_by": "r5", + "label": "JAZZ LP\\'S" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + 0.483 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "00s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + 0.858 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "00s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -0.267 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "00s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + 0.108 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "00s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -0.267 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "HOUSE COMPILATIONS" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + 0.108 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + 0.483 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "$12 00s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + 0.858 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "$12 00s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -0.267 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "00s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + 0.108 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "00s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + 0.483 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "00s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + 0.858 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r22", + "label": "00S HOUSE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.135, + 3.872 + ], + "sit_on": 0.05, + "yaw": 1.5708, + "fit": [ + 0.34, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r27", + "label": "$5-$15 ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.135, + 3.527 + ], + "sit_on": 0.05, + "yaw": 1.5708, + "fit": [ + 0.34, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r27", + "label": "REMOVE ME" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.135, + 3.182 + ], + "sit_on": 0.05, + "yaw": 1.5708, + "fit": [ + 0.34, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r27", + "label": "$5-$15 ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.135, + 2.837 + ], + "sit_on": 0.05, + "yaw": 1.5708, + "fit": [ + 0.34, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r27", + "label": "REMOVE ME" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.135, + 3.872 + ], + "sit_on": 0.76, + "yaw": 1.5708, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r27", + "label": "ART, ALT AND INDIE ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.135, + 3.527 + ], + "sit_on": 0.76, + "yaw": 1.5708, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r27", + "label": "PROG AND PSYCH ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.135, + 3.182 + ], + "sit_on": 0.76, + "yaw": 1.5708, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r27", + "label": "METAL, HARDCORE, THRASH AND PUNK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.135, + 2.837 + ], + "sit_on": 0.76, + "yaw": 1.5708, + "fit": [ + 0.24, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r27", + "label": "CLASSICAL, HARD AND GARAGE ROCK" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.661, + -4.755 + ], + "sit_on": 0.001, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r45", + "label": "POP REMIXES" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.576, + -4.755 + ], + "sit_on": 0.001, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r45", + "label": "10\"s" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.491, + -4.755 + ], + "sit_on": 0.001, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r45", + "label": "7\"s" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + -3.406, + -4.755 + ], + "sit_on": 0.001, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r45", + "label": "NEW HARD TRANCE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 2.51, + 4.285 + ], + "sit_on": 0.76, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.262, + 4.285 + ], + "sit_on": 0.76, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "GOTH, SYNTH AND NEW WAVE" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 2.886, + 4.285 + ], + "sit_on": 0.76, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 2.134, + 4.285 + ], + "sit_on": 0.76, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.758, + 4.285 + ], + "sit_on": 0.76, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 3.262, + 4.285 + ], + "sit_on": 0.05, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "BLUES & BLUES ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 2.886, + 4.285 + ], + "sit_on": 0.05, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "BLUES & BLUES ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 2.51, + 4.285 + ], + "sit_on": 0.05, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "FOLK, COUNTRY & COUNTRY ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 2.134, + 4.285 + ], + "sit_on": 0.05, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "JAZZ & JAZZ ROCK" + }, + { + "glb": "store-crate-blue", + "kind": "plastic", + "at": [ + 1.758, + 4.285 + ], + "sit_on": 0.05, + "yaw": 3.1416, + "fit": [ + 0.37, + 0.35, + 0.43 + ], + "frozen": true, + "supported_by": "r26", + "label": "REMOVE ME" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -1.762 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -1.387 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -1.012 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "$12 90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -0.637 + ], + "sit_on": 0.16, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "$12 90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -1.762 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -1.387 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -1.012 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -0.637 + ], + "sit_on": 0.7, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -1.762 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -1.387 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -1.012 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "90s HOUSE" + }, + { + "glb": "store-tub-white", + "kind": "plastic", + "at": [ + 2.58, + -0.637 + ], + "sit_on": 1.22, + "yaw": 0.0, + "fit": [ + 0.34, + 0.2, + 0.53 + ], + "frozen": true, + "supported_by": "r21", + "label": "90s HOUSE" + } + ] +} \ No newline at end of file diff --git a/game/dev/probe_store.gd b/game/dev/probe_store.gd new file mode 100644 index 0000000..bba7746 --- /dev/null +++ b/game/dev/probe_store.gd @@ -0,0 +1,82 @@ +extends SceneTree + +## SITE 7 contract — the imported shop: +## - it builds, with racks AND bins +## - the cascade came off the shop's own data: racks hold bins via `supported_by` +## - smashing one rack releases exactly ITS bins, and no others +## - the real genre labels survived the import +## +## Godot --headless --path game --script dev/probe_store.gd + +func _initialize() -> void: + var main: Node = (load("res://main.tscn") as PackedScene).instantiate() + get_root().add_child(main) + await process_frame + main._load_level("store") + for i in 70: + await physics_frame + + var racks: Array = [] + var bins: Array = [] + var labelled := 0 + for n in get_nodes_in_group("smashable"): + var s := n as Smashable + if s == null: + continue + if s.supports.size() > 0: + racks.append(s) + if s.freeze: + bins.append(s) + if s.has_meta("label"): + labelled += 1 + print("racks holding bins: %d frozen bins: %d labelled bins: %d" % [ + racks.size(), bins.size(), labelled]) + if racks.is_empty() or bins.is_empty(): + print("FAIL: the shop did not import (no racks holding bins)") + quit(1) + return + if labelled < 20: + print("FAIL: the real genre labels did not survive the import") + quit(1) + return + + # find the rack carrying the most bins and knock it over + var best: Smashable = racks[0] + for r in racks: + if r.supports.size() > best.supports.size(): + best = r + var mine: Array = [] + for b in best.supports: + if is_instance_valid(b): + mine.append(b) + var others: Array = [] + for b in bins: + if is_instance_valid(b) and not mine.has(b): + others.append(b) + var others_frozen_before := 0 + for b in others: + if (b as RigidBody3D).freeze: + others_frozen_before += 1 + + print("smashing '%s' — it carries %d bins" % [best.name, mine.size()]) + best.smash(Vector3(0, 1, 0), 999.0) + await physics_frame + await physics_frame + + var released := 0 + for b in mine: + if is_instance_valid(b) and not (b as RigidBody3D).freeze: + released += 1 + var others_still := 0 + for b in others: + if is_instance_valid(b) and (b as RigidBody3D).freeze: + others_still += 1 + print("its bins released: %d/%d other bins still frozen: %d/%d" % [ + released, mine.size(), others_still, others_frozen_before]) + + if released == mine.size() and others_still == others_frozen_before: + print("STORE OK") + quit(0) + else: + print("FAIL: cascade released the wrong set of bins") + quit(1) diff --git a/game/scripts/Levels.gd b/game/scripts/Levels.gd index d3ea228..0a78bc9 100644 --- a/game/scripts/Levels.gd +++ b/game/scripts/Levels.gd @@ -20,7 +20,7 @@ class_name Levels ## ## Godot 4.7 GDScript 2.0. -const ORDER := ["scranton", "pawnee", "house", "basement", "grocer", "backrooms"] +const ORDER := ["scranton", "pawnee", "house", "basement", "grocer", "backrooms", "store"] static func get_level(id: String) -> Dictionary: match id: @@ -29,6 +29,7 @@ static func get_level(id: String) -> Dictionary: "basement": return basement() "backrooms": return backrooms() "grocer": return grocer() + "store": return record_store() return scranton() static func next_id(id: String) -> String: @@ -717,3 +718,82 @@ static func grocer() -> Dictionary: "stand": Vector3(-8.6, 0.0, -4.0), "shredder": Vector3(-6.4, 0.0, -5.4)}, "spawn": {"at": Vector3(0.0, 0.0, 6.4), "yaw": PI}, } + +# ================================================================ SITE 7 +## THE MONSTER ROBOT PARTY — the shop the whole game started as. +## +## The only site that is IMPORTED rather than authored. The live storefront at +## robotmonster.party/store/ is assembled procedurally from Postgres (the wowplatter +## virtual_* schema, served by recordgod), and this game's sites are data, so +## tools/import_store.py reads the real shop and emits assets/store/shop_floor.json: +## 35 racks, 278 bins carrying their real genre labels, and 21,159 records of stock. +## +## The shop's schema IS the cascade. virtual_crate.rack_id already records which rack +## holds which bin, so every crate arrives frozen and `supported_by` its rack — knock a +## rack over and the actual bins that actually sit on it actually go. None of that is +## authored; it comes off the shop floor. +## +## Re-run the importer when the real shop is rearranged and this level follows. +const STORE_JSON := "res://assets/store/shop_floor.json" + +static func record_store() -> Dictionary: + var raw := FileAccess.get_file_as_string(STORE_JSON) + var data = JSON.parse_string(raw) if raw != "" else null + if data == null: + push_warning("[store] %s missing — run tools/import_store.py" % STORE_JSON) + data = {"room": {"w": 6.66, "d": 9.51, "h": 2.89}, "smashables": [], "slabs": [], + "spawn": {"at": [0.0, 3.0]}} + + var room: Dictionary = data.get("room", {}) + var w: float = float(room.get("w", 6.66)) + var dp: float = float(room.get("d", 9.51)) + var h: float = float(room.get("h", 2.89)) + + # JSON has no Vector types — rehydrate into what Floorplan/Main expect. + var smash: Array = [] + for e in data.get("smashables", []): + var s: Dictionary = e.duplicate() + var a: Array = s["at"] + s["at"] = Vector2(float(a[0]), float(a[1])) + smash.append(s) + var slabs: Array = [] + for e in data.get("slabs", []): + var sz: Array = e["size"] + var at: Array = e["at"] + slabs.append({"size": Vector3(float(sz[0]), float(sz[1]), float(sz[2])), + "at": Vector3(float(at[0]), float(at[1]), float(at[2])), + "mat": String(e.get("mat", "wood"))}) + var sp: Array = data.get("spawn", {}).get("at", [0.0, 3.0]) + + return { + "name": "MONSTER ROBOT PARTY", + "subtitle": "your own shop. you know exactly what everything cost.", + "bounds": {"x0": -w * 0.5, "x1": w * 0.5, "z0": -dp * 0.5, "z1": dp * 0.5, "h": h}, + "palette": { + "floor": Color(0.42, 0.31, 0.22), "wall": Color(0.93, 0.92, 0.90), + "ceiling": Color(0.17, 0.16, 0.19), "trim": Color(0.19, 0.18, 0.21), + "partition": Color(0.35, 0.20, 0.34), "wood": Color(0.52, 0.36, 0.20), + "accent": Color(1.0, 0.36, 0.62), # the shop's own pink + }, + "floor_style": "timber", + "ceiling": "flat", + "walls": [], + # the shopfront: glazing down the street side, where the window display is + "windows": [{"a": "z", "at": dp * 0.5, "from": -w * 0.5, "to": w * 0.5, + "y0": 0.55, "y1": 2.45, "blinds": false}], + "lights": {"xs": [-1.9, 1.9], "zs": [-3.4, -1.1, 1.2, 3.5], + "color": Color(1.0, 0.93, 0.86), "energy": 2.1, "style": "strip", + "key_energy": 1.5}, + "sun": {"color": Color(1.0, 0.95, 0.88), "energy": 0.9, "rot": Vector3(-38, 14, 0)}, + "env": {"bg": Color(0.72, 0.76, 0.82), "ambient": Color(0.58, 0.55, 0.56), + "ambient_energy": 0.62, "exposure": 1.02}, + "clusters": [], + "chairs": [], + "props": [], + "slabs": slabs, + "smashables": smash, + "stacks": [], + # a few playable records loose on the counter side, for the ritual + "records": [], + "spawn": {"at": Vector3(float(sp[0]), 0.0, float(sp[1])), "yaw": PI}, + } diff --git a/game/scripts/Main.gd b/game/scripts/Main.gd index b6e7a35..a719050 100644 --- a/game/scripts/Main.gd +++ b/game/scripts/Main.gd @@ -246,11 +246,33 @@ func _populate() -> void: (cdesk["piece"] as Smashable).supports.append(printer["piece"]) # --- everything else this site contains, straight from its spec --- + # Two optional keys let a level express STRUCTURE as data instead of code: "id" names + # a piece, "supported_by" points at the id of whatever holds it up. Wiring happens + # after the loop so an entry may refer to a holder declared later. This is what makes + # the record store importable — the shop's own DB already knows which rack holds which + # bin, so the cascade comes straight off `virtual_crate.rack_id`. See LANE10. + var by_id: Dictionary = {} + var links: Array = [] for e in _plan.smashables(): var d: Dictionary = e - _glb_piece(P + String(d["glb"]) + ".glb", String(d.get("kind", "wood")), + var fit := Vector3.ZERO + if d.has("fit"): + var f: Array = d["fit"] + fit = Vector3(float(f[0]), float(f[1]), float(f[2])) + var res := _glb_piece(P + String(d["glb"]) + ".glb", String(d.get("kind", "wood")), d["at"], bool(d.get("frozen", false)), float(d.get("sit_on", 0.0)), - float(d.get("yaw", 0.0))) + float(d.get("yaw", 0.0)), false, fit) + var piece := res["piece"] as Smashable + if d.has("id"): + by_id[String(d["id"])] = piece + if d.has("supported_by"): + links.append([String(d["supported_by"]), piece]) + if d.has("label"): + piece.set_meta("label", String(d["label"])) + for link in links: + var holder := by_id.get(link[0]) as Smashable + if holder != null and is_instance_valid(holder): + holder.supports.append(link[1]) # --- crates of records, where the site has any. Records stand side by side ON the # --- crate lid, offset by more than a sleeve is thick: they used to be stacked 3 cm @@ -491,7 +513,7 @@ func _glb_radius(path: String) -> float: ## the mesh AABB, and drop it so its bottom rests at height `sit_on`. Returns ## { piece: Smashable, top: float } so a caller can stack the next thing on top. func _glb_piece(path: String, kind: String, xz: Vector2, frozen: bool, sit_on := 0.0, - yaw := 0.0, centred := false) -> Dictionary: + yaw := 0.0, centred := false, fit := Vector3.ZERO) -> Dictionary: var s := Smashable.new() s.kind = kind s.start_frozen = frozen @@ -512,6 +534,13 @@ func _glb_piece(path: String, kind: String, xz: Vector2, frozen: bool, sit_on := var ab := _body_local_aabb(s, vis) if ab.size == Vector3.ZERO: ab = AABB(Vector3(-0.2, 0.0, -0.2), Vector3(0.4, 0.4, 0.4)) # fallback + # `fit`: this piece's REAL dimensions, when the caller knows them better than the mesh + # does. The record store maps 21 shop archetypes onto a handful of stand-in meshes, so + # the mesh's own size is a lie — scale the visual to the truth and measure again, and + # both the render and the collider match the shop that was imported. + if fit != Vector3.ZERO and ab.size.x > 0.001 and ab.size.y > 0.001 and ab.size.z > 0.001: + vis.scale = Vector3(fit.x / ab.size.x, fit.y / ab.size.y, fit.z / ab.size.z) + ab = _body_local_aabb(s, vis) var col := CollisionShape3D.new() # Round produce gets a SPHERE. A box collider on an apple is why a pyramid needed # hand-tuned lattice spacing to nest, and why nothing ever rolled: the corners hit diff --git a/tools/import_store.py b/tools/import_store.py new file mode 100644 index 0000000..292c773 --- /dev/null +++ b/tools/import_store.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +"""Turn the REAL Monster Robot Party shop into a Destroyulator level. + +The live storefront (robotmonster.party/store/) is assembled procedurally from Postgres +rows — the wowplatter virtual_* schema, served by recordgod as GET /virtual/scene. The +game's sites are data too, so Site 7 is not hand-placed: this reads the shop and emits +game/assets/store/shop_floor.json, which Levels.record_store() loads. + +Re-run it whenever the real shop is rearranged and the level follows. + + python3 tools/import_store.py # fetch live + python3 tools/import_store.py --offline scene.json # from a saved dump + +Why the shop maps onto the game so cleanly: virtual_crate.rack_id already records which +rack holds which bin, so the game's support cascade (smash the rack, its bins go over) +falls straight out of the data. Nothing about it is authored. +""" + +import argparse +import json +import math +import pathlib +import sys +import urllib.request + +URL = "https://robotmonster.party/virtual/scene" +OUT = pathlib.Path(__file__).resolve().parent.parent / "game/assets/store/shop_floor.json" + +# --- archetype -> what the game builds ------------------------------------------------- +# The shop has 21 rack archetypes; the game has a curated store-* GLB set. Map by shape +# and material, not one-to-one: two timber rack sizes, tubs, columns, and the fittings. +RACK_GLB = { + "wooden-rack-type-long": ("store-rack-long", "wood"), + "wooden-rack-type-wide": ("store-rack-wide", "wood"), + "big-4x4x4-01": ("store-rack-long", "wood"), + "white-tub-3x3": ("store-rack-wide", "wood"), + "white-tub-3x3-long": ("store-rack-long", "wood"), + "tubs-4x2-vertical": ("store-rack-wide", "wood"), + "tubs-4x2-wide": ("store-rack-wide", "wood"), + "3-top-4-low-tubs-on-shelf-wide": ("store-rack-wide", "wood"), + "3-top-4-low-tubs-on-shelf-long": ("store-rack-long", "wood"), + "chiu-short": ("store-rack-wide", "wood"), + "chiu-long": ("store-rack-long", "wood"), + "mos-japhop": ("store-rack-wide", "wood"), + "library trolley": ("store-trolley", "steel"), + "WHITE COLUMN": ("store-column-white", "steel"), + "GREY COLUMN": ("store-column-white", "steel"), + "AIR CON": ("store-aircon", "steel"), + "STATION 1": ("store-station", "wood"), + "STATION 2": ("store-station", "wood"), + "floor crate": ("store-crate-blue", "plastic"), +} +# Bins. Tubs and crates are moulded plastic; the timber bin is wood. +CRATE_GLB = { + "White Tub": ("store-tub-white", "plastic"), + "White Tub long": ("store-bin-long", "plastic"), + "Black Tub": ("store-tub-black", "plastic"), + "Black Tub long": ("store-bin-long", "plastic"), + "Blue Crate": ("store-crate-blue", "plastic"), + "Wooden Rack Bin": ("crate", "wood"), +} +CRATE_DEFAULT = ("store-crate-blue", "plastic") +# Archetypes that are not objects: flat benches and zero-depth wall shelves become slabs, +# and the roof beam is structure. Building them as smashable props would be wrong. +AS_SLAB = {"long guy", "short guy", "wall long", "wall big", "ROOF COLUMN", "Shelf Level", + "Shelf Level long"} +SKIP_CRATE_TYPES = {"Shelf Level", "Shelf Level long"} # shelves, not bins + + +def fetch(url, offline): + if offline: + return json.loads(pathlib.Path(offline).read_text()) + with urllib.request.urlopen(url, timeout=30) as r: + return json.loads(r.read()) + + +def rot_y(x, z, deg): + a = math.radians(deg or 0.0) + return x * math.cos(a) + z * math.sin(a), -x * math.sin(a) + z * math.cos(a) + + +def resolve_overlaps(pieces, iters=60, slop=0.012): + """Push interpenetrating pieces apart in X/Z until nothing overlaps. + + The web storefront is a RENDERER — nothing collides there, so a rack can sit a few + centimetres inside its neighbour and look perfectly fine for years. Godot's solver + disagrees violently (that's what the game's spawn guard exists to catch). This is the + seam where a physics-free layout becomes a physically valid one. + + Each piece has a `w` weight: racks are heavy and barely move, bins are light and give + way, so aisles keep their shape and the furniture stays where the shop put it. + Heights are never touched — a ceiling beam must stay on the ceiling. + """ + moved = 0.0 + for _ in range(iters): + worst = 0.0 + for i in range(len(pieces)): + a = pieces[i] + for j in range(i + 1, len(pieces)): + b = pieces[j] + # vertical separation is real separation: a bin on a shelf above another + # rack is not an overlap + ay0, ay1 = a["y"], a["y"] + a["fit"][1] + by0, by1 = b["y"], b["y"] + b["fit"][1] + if ay1 <= by0 + slop or by1 <= ay0 + slop: + continue + dx = (a["x"] - b["x"]) + dz = (a["z"] - b["z"]) + ox = (a["fit"][0] + b["fit"][0]) * 0.5 - abs(dx) + oz = (a["fit"][2] + b["fit"][2]) * 0.5 - abs(dz) + if ox <= slop or oz <= slop: + continue + worst = max(worst, min(ox, oz)) + # separate along the axis of LEAST penetration — the smaller correction + wa, wb = a["w"], b["w"] + tot = wa + wb + if ox < oz: + push = (ox + slop) * (1.0 if dx >= 0 else -1.0) + a["x"] += push * (wb / tot) + b["x"] -= push * (wa / tot) + else: + push = (oz + slop) * (1.0 if dz >= 0 else -1.0) + a["z"] += push * (wb / tot) + b["z"] -= push * (wa / tot) + moved += abs(push) + if worst <= slop: + break + return moved + + +def audit(pieces, slop=0.004): + """What still interpenetrates after resolution — the same test the game's + dev/probe_overlap.gd runs, so the importer can't hand off a level it knows is bad.""" + bad = [] + for i in range(len(pieces)): + a = pieces[i] + for j in range(i + 1, len(pieces)): + b = pieces[j] + ay1, by1 = a["y"] + a["fit"][1], b["y"] + b["fit"][1] + if ay1 <= b["y"] + slop or by1 <= a["y"] + slop: + continue + ox = (a["fit"][0] + b["fit"][0]) * 0.5 - abs(a["x"] - b["x"]) + oz = (a["fit"][2] + b["fit"][2]) * 0.5 - abs(a["z"] - b["z"]) + if ox > slop and oz > slop: + bad.append((min(ox, oz), a.get("name", a["glb"]), b.get("name", b["glb"]))) + return sorted(bad, reverse=True) + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--url", default=URL) + ap.add_argument("--offline") + ap.add_argument("--out", default=str(OUT)) + args = ap.parse_args() + + d = fetch(args.url, args.offline) + space = d["space"] + W, D, H = space["room_width"], space["room_depth"], space["ceiling_height"] + # The shop's origin is a room CORNER; the game centres bounds on 0. + cx, cz = W / 2.0, D / 2.0 + + rack_types = {t["id"]: t for t in d["rack_types"]} + crate_types = {c["id"]: c for c in d["crate_types"]} + levels = {} + for l in d["rack_type_levels"]: + levels.setdefault(l["rack_type_id"], {})[l.get("level_index")] = l + + racks = [r for r in d["racks"] + if r.get("visible") == "y" and r.get("space_id") == space["id"]] + crates_by_rack = {} + for c in d["crates"]: + if c.get("visible") == "y" and c.get("rack_id"): + crates_by_rack.setdefault(c["rack_id"], []).append(c) + + pieces, slabs, footprints = [], [], [] + n_crates = 0 + stock = 0 + + for r in racks: + t = rack_types.get(r["rack_type_id"]) + if not t: + continue + name = t.get("name", "") + x, z = r["pos_x"] - cx, r["pos_z"] - cz + yaw = math.radians(r.get("rotation_y") or 0.0) + tw, th, td = t.get("width", 0.5), t.get("height", 0.5), t.get("depth", 0.5) + + if name in AS_SLAB: + # benches, wall shelves and the roof beam: structure, not props + slabs.append({"size": [max(tw, 0.05), max(th, 0.04), max(td, 0.05)], + "at": [round(x, 3), round(max(th, 0.04) / 2.0 + (r.get("pos_y") or 0.0), 3), + round(z, 3)], + "mat": "wood" if "guy" in name or "wall" in name else "trim"}) + continue + + glb, kind = RACK_GLB.get(name, ("store-rack-wide", "wood")) + rid = f"r{r['id']}" + # "fit": the archetype's REAL dimensions. 21 archetypes share a handful of + # stand-in meshes, so the mesh's own size is a lie — the shop's table is the truth. + # pos_y is the piece's BOTTOM in the shop's data — the same thing sit_on means to + # the game. Without it the ceiling beams, the aircon and the wall shelves all get + # dropped onto the floor, where they stand inside the floor racks. + base = r.get("pos_y") or 0.0 + pieces.append({"kind_of": "rack", "glb": glb, "kind": kind, "id": rid, + "x": x, "z": z, "y": base, "yaw": yaw, "w": 8.0, + "fit": [max(tw, 0.05), max(th, 0.05), max(td, 0.05)], + "name": r.get("name") or name, + "rack": r, "rot": r.get("rotation_y") or 0.0}) + + # Separate the RACKS only, before their bins are placed, so a rack and everything it + # carries move together. Bins are deliberately NOT separated from their own rack: a + # bin sitting in a rack is cargo inside a container, which is what the shop means and + # what the game wants. They spawn frozen, and the overlap audit skips frozen bodies + # precisely because a frozen body cannot be depenetrated across the room. + moved = resolve_overlaps(pieces, iters=400) + # A few racks in the real shop are genuinely wedged — three big units in a row that + # cannot all fit without shoving the aisle around. An archetype's width is a NOMINAL + # envelope, so take a centimetre and a half off each side of every rack's footprint: + # invisible at any distance, and it buys the solver the clearance it needs. Heights + # are untouched, so nothing sinks into the floor or leaves the ceiling. + INSET = 0.03 + for p in pieces: + if p["kind_of"] == "rack": + p["fit"][0] = max(p["fit"][0] - INSET, 0.05) + p["fit"][2] = max(p["fit"][2] - INSET, 0.05) + left = audit(pieces) + for depth, na, nb in left[:6]: + print(f"[import_store] STILL OVERLAPPING {depth * 100:.0f} cm: {na} / {nb}") + + for p in [p for p in pieces if p["kind_of"] == "rack"]: + r = p["rack"] + for c in crates_by_rack.get(r["id"], []): + ct = crate_types.get(c["crate_type_id"], {}) + cname = ct.get("name", "") + if cname in SKIP_CRATE_TYPES: + continue + # crate pos is LOCAL to its rack (pos_y already carries the shelf height) + lx, lz = rot_y(c.get("pos_x") or 0.0, c.get("pos_z") or 0.0, p["rot"]) + cglb, ckind = CRATE_GLB.get(cname, CRATE_DEFAULT) + e = {"kind_of": "crate", "glb": cglb, "kind": ckind, + "x": p["x"] + lx, "z": p["z"] + lz, + "y": (r.get("pos_y") or 0.0) + (c.get("pos_y") or 0.0), + "yaw": p["yaw"], "w": 1.0, + "fit": [max(ct.get("width") or 0.3, 0.05), + max(ct.get("height") or 0.3, 0.05), + max(ct.get("depth") or 0.3, 0.05)], + "supported_by": p["id"]} + label = (c.get("label_text") or "").strip() + if label and label != "---": + e["label"] = label + pieces.append(e) + n_crates += 1 + stock += c.get("n_items") or 0 + + smashables = [] + for p in pieces: + e = {"glb": p["glb"], "kind": p["kind"], + "at": [round(p["x"], 3), round(p["z"], 3)], + "sit_on": round(p["y"], 3), "yaw": round(p["yaw"], 4), + "fit": [round(v, 3) for v in p["fit"]]} + if p["kind_of"] == "rack": + e["id"] = p["id"] + e["name"] = p["name"] + if p["y"] < 1.2: + footprints.append((p["x"], p["z"], + max(p["fit"][0], p["fit"][2]) * 0.5 + 0.35)) + else: + # frozen + supported: bins don't jitter at spawn (the stillness gate stays + # green) and they go live exactly when their rack does + e["frozen"] = True + e["supported_by"] = p["supported_by"] + if p.get("label"): + e["label"] = p["label"] + smashables.append(e) + + # --- a measured spawn point --------------------------------------------------------- + # 35 racks in a 6.66 x 9.51 room leaves very little open floor, and spawning inside a + # rack is not a level. Scan a grid for the clearest spot in the front half of the shop. + best, best_clear = (0.0, D / 2.0 - 0.9), -1.0 + step = 0.25 + x0, x1 = -cx + 0.6, cx - 0.6 + z0, z1 = -cz + 0.6, cz - 0.6 + gx = x0 + while gx <= x1: + gz = z0 + while gz <= z1: + clear = min(((gx - fx) ** 2 + (gz - fz) ** 2) ** 0.5 - fr + for fx, fz, fr in footprints) if footprints else 9.9 + # prefer the front of the shop (larger z), where the door is + score = clear + (gz + cz) * 0.05 + if clear > 0.55 and score > best_clear: + best_clear, best = score, (gx, gz) + gz += step + gx += step + + out = { + "_source": args.url if not args.offline else args.offline, + "_note": "GENERATED by tools/import_store.py from the live shop. Do not hand-edit.", + "room": {"w": W, "d": D, "h": H}, + "counts": {"racks": len(racks), "crates": n_crates, "stock_records": stock, + "slabs": len(slabs)}, + "spawn": {"at": [round(best[0], 2), round(best[1], 2)]}, + "slabs": slabs, + "smashables": smashables, + } + p = pathlib.Path(args.out) + p.write_text(json.dumps(out, indent=1)) + print(f"[import_store] room {W} x {D} x {H}") + print(f"[import_store] separation pass moved {moved:.2f} m total") + print(f"[import_store] {len(racks)} racks, {n_crates} crates, {len(slabs)} slabs, " + f"{stock:,} records of stock") + print(f"[import_store] spawn at ({best[0]:.2f}, {best[1]:.2f}), clearance {best_clear:.2f} m") + print(f"[import_store] -> {p} ({p.stat().st_size / 1024:.0f} KB)") + return 0 + + +if __name__ == "__main__": + sys.exit(main())