destroyulator/game/scripts/Levels.gd
Monster Robot Party e588508436 LANE10: the whole shop — four rooms, stitched by its own portal graph
The hip-hop room has stock (7 racks, 47 bins, 2,865 records); the entry hall and
back room are empty. Import all four anyway: the empty rooms are the connective
tissue, and they are the only floor in the building big enough to get an office
chair up to speed.

A portal pair is one doorway seen from both sides, so the importer walks the graph
and places each room by making its portal land exactly on its neighbour's. Every
pair here sits on parallel walls with opposite facings, so each join is a pure
translation — and when one isn't, it says so rather than quietly getting it wrong.
It then recentres the union, emits the interior walls with a gap at each doorway,
and puts the spawn in the entry hall, because that is the way in.

  hiphop-room <-> entry-hall <-> shop-floor <-> back-room
  12.96 x 15.41 m - 36 racks - 325 bins - 302 real genre labels - 24,024 records

Levels.record_store() now takes the walls and rooms from the import, hangs the
street glazing on the entry hall's outer wall, and lays a light grid across
whatever footprint the rooms came out as.

probe_store.gd also checks the new facts: five doorways exist, and the spawn has
nothing in it (excluding the player, who is supposed to be standing there — that
false positive was the probe's bug, not the level's).
All gates green: smoke clean, 7/7 sites 0.00 m/s, overlap CLEAN, all probes pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 02:48:39 +10:00

831 lines
39 KiB
GDScript

extends RefCounted
class_name Levels
## The workplaces, as data. Each is a Floorplan spec (see Floorplan.gd for the schema).
##
## The point of these being data and not four subclasses: a new site is authorable in
## minutes, they can be diffed, and a generator can later emit the same structure.
##
## They are deliberately NOT reskins. Each differs in the three things a player actually
## reads — the palette, the light, and what the walls are made of:
##
## SCRANTON ..... magnolia, grey carpet, drop ceiling, fluorescent troffers, daylight
## down one glazed wall. The baseline office.
## PAWNEE ....... civic beige and blue-grey, low partitions everywhere, a public
## counter, pinboards. Municipal, over-partitioned, no privacy.
## THE HOUSE .... timber floor, white walls, 3 m ceiling, pendant lights, a glass wall
## onto a pool. Nobody here has an office; they work at a dining table.
## THE BASEMENT . concrete, no windows at all, exposed services, bare strip lights, a
## wall of server racks. The light is green and everything is junk.
##
## Godot 4.7 GDScript 2.0.
const ORDER := ["scranton", "pawnee", "house", "basement", "grocer", "backrooms", "store"]
static func get_level(id: String) -> Dictionary:
match id:
"pawnee": return pawnee()
"house": return house()
"basement": return basement()
"backrooms": return backrooms()
"grocer": return grocer()
"store": return record_store()
return scranton()
static func next_id(id: String) -> String:
var i := ORDER.find(id)
return ORDER[(i + 1) % ORDER.size()] if i >= 0 else ORDER[0]
# ================================================================ LEVEL 01
static func scranton() -> Dictionary:
return {
"name": "SCRANTON BRANCH",
"subtitle": "paper company · 2nd floor",
"bounds": {"x0": -11.0, "x1": 11.0, "z0": -8.0, "z1": 8.0, "h": 2.75},
"palette": {
"floor": Color(0.30, 0.30, 0.32), "wall": Color(0.80, 0.78, 0.72),
"ceiling": Color(0.88, 0.88, 0.86), "trim": Color(0.13, 0.12, 0.13),
"partition": Color(0.44, 0.44, 0.40), "wood": Color(0.42, 0.29, 0.17),
"accent": Color(0.36, 0.42, 0.50),
},
"floor_style": "carpet",
"ceiling": "tile",
"walls": [
# manager's office — half solid, half glass, so you can be watched through it
{"a": "x", "at": -4.4, "from": -11.0, "to": -6.6, "door": -9.2},
{"a": "z", "at": -6.6, "from": -8.0, "to": -4.4, "style": "glass_top"},
# conference room
{"a": "x", "at": -4.4, "from": -6.6, "to": -0.4, "door": -3.4},
{"a": "z", "at": -0.4, "from": -8.0, "to": -4.4},
# break room
{"a": "z", "at": 5.4, "from": -1.2, "to": 8.0, "door": 2.2},
{"a": "x", "at": -1.2, "from": 5.4, "to": 11.0},
# copier alcove
{"a": "z", "at": 3.6, "from": -1.6, "to": 1.0},
# reception screen
{"a": "z", "at": -6.9, "from": 4.6, "to": 8.0, "door": 6.8},
],
"windows": [{"a": "z", "at": 11.0, "from": -8.0, "to": 8.0,
"y0": 0.85, "y1": 2.35, "blinds": true}],
"lights": {"xs": [-8.5, -4.6, -0.7, 3.2, 7.1], "zs": [-6.2, -2.6, 1.0, 4.6],
"color": Color(1.0, 0.98, 0.93), "energy": 1.9, "style": "troffer"},
"sun": {"color": Color(0.82, 0.88, 1.0), "energy": 0.7,
"rot": Vector3(-30, -104, 0)},
"env": {"bg": Color(0.55, 0.62, 0.72), "ambient": Color(0.50, 0.50, 0.50),
"ambient_energy": 0.5},
"clusters": [Vector2(-4.6, -2.0), Vector2(-4.6, 2.8),
Vector2(-0.4, -2.0), Vector2(-0.4, 2.8)],
"cluster_gap": 0.78,
"slabs": [
# reception counter
{"size": Vector3(0.70, 1.10, 3.0), "at": Vector3(-9.9, 0.55, 5.6), "mat": "wood"},
{"size": Vector3(0.92, 0.08, 3.2), "at": Vector3(-9.9, 1.14, 5.6), "mat": "trim"},
{"size": Vector3(1.8, 1.10, 0.70), "at": Vector3(-9.6, 0.55, 4.45), "mat": "wood"},
# conference table + whiteboard
{"size": Vector3(3.9, 0.09, 1.35), "at": Vector3(-3.5, 0.74, -6.2), "mat": "wood"},
{"size": Vector3(0.16, 0.72, 0.90), "at": Vector3(-5.1, 0.36, -6.2), "mat": "trim"},
{"size": Vector3(0.16, 0.72, 0.90), "at": Vector3(-1.9, 0.36, -6.2), "mat": "trim"},
{"size": Vector3(2.5, 1.25, 0.03), "at": Vector3(-3.5, 1.6, -7.93), "mat": "trim"},
{"size": Vector3(2.4, 1.15, 0.05), "at": Vector3(-3.5, 1.6, -7.90), "mat": "ceiling"},
# break room kitchen + tables
{"size": Vector3(0.62, 0.88, 3.4), "at": Vector3(10.58, 0.44, 4.0), "mat": "wood"},
{"size": Vector3(0.70, 0.06, 3.5), "at": Vector3(10.58, 0.90, 4.0), "mat": "trim"},
{"size": Vector3(1.05, 0.07, 1.05), "at": Vector3(7.4, 0.73, 0.4), "mat": "ceiling"},
{"size": Vector3(0.12, 0.70, 0.12), "at": Vector3(7.4, 0.36, 0.4), "mat": "trim"},
{"size": Vector3(1.05, 0.07, 1.05), "at": Vector3(7.4, 0.73, 3.0), "mat": "ceiling"},
{"size": Vector3(0.12, 0.70, 0.12), "at": Vector3(7.4, 0.36, 3.0), "mat": "trim"},
# warehouse roller door
{"size": Vector3(2.6, 2.4, 0.12), "at": Vector3(7.8, 1.2, -7.94), "mat": "trim"},
],
"props": [
{"glb": "store-column-white", "at": Vector3(-2.2, 0.0, -3.4)},
{"glb": "store-column-white", "at": Vector3(2.2, 0.0, -3.4)},
{"glb": "store-aircon", "at": Vector3(2.6, 2.35, -7.9)},
{"glb": "office-plant", "at": Vector3(-6.4, 0.0, 6.6)},
{"glb": "office-plant", "at": Vector3(2.9, 0.0, 6.4)},
{"glb": "vending-machine", "at": Vector3(6.3, 0.0, 7.4), "yaw": PI},
{"glb": "vending-machine", "at": Vector3(7.3, 0.0, 7.4), "yaw": PI},
{"glb": "microwave", "at": Vector3(10.55, 0.93, 5.1), "yaw": -PI / 2},
],
"chairs": [
{"at": Vector3(-7.6, 0.0, 5.5), "yaw": -PI / 2},
{"at": Vector3(-7.6, 0.0, 6.35), "yaw": -PI / 2},
{"at": Vector3(-7.6, 0.0, 7.2), "yaw": -PI / 2},
{"at": Vector3(-4.85, 0.0, -7.35), "yaw": 0.0},
{"at": Vector3(-3.95, 0.0, -7.35), "yaw": 0.0},
{"at": Vector3(-3.05, 0.0, -7.35), "yaw": 0.0},
{"at": Vector3(-2.15, 0.0, -7.35), "yaw": 0.0},
{"at": Vector3(-4.85, 0.0, -5.05), "yaw": PI},
{"at": Vector3(-3.95, 0.0, -5.05), "yaw": PI},
{"at": Vector3(-3.05, 0.0, -5.05), "yaw": PI},
{"at": Vector3(-2.15, 0.0, -5.05), "yaw": PI},
{"at": Vector3(-8.8, 0.0, -7.1), "yaw": 0.0},
{"at": Vector3(-9.6, 0.0, -5.1), "yaw": PI},
{"at": Vector3(8.0, 0.0, 1.13), "yaw": 2.0},
{"at": Vector3(7.7, 0.0, -0.49), "yaw": 4.1},
{"at": Vector3(6.5, 0.0, 0.56), "yaw": 0.2},
{"at": Vector3(8.0, 0.0, 3.73), "yaw": 2.0},
{"at": Vector3(7.7, 0.0, 2.11), "yaw": 4.1},
{"at": Vector3(6.5, 0.0, 3.16), "yaw": 0.2},
],
"copier": {"at": Vector3(3.0, 0.0, -0.3), "yaw": -PI / 2},
"smashables": [
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(4.5, -1.6)},
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(4.5, -0.9)},
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(4.5, -0.2)},
{"glb": "water-cooler", "kind": "steel", "at": Vector2(-6.2, 3.4)},
{"glb": "water-cooler", "kind": "steel", "at": Vector2(5.9, -0.6)},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-2.3, 5.2)},
# an archive box is 1.35 m across, so a "couple of boxes dumped together" is a
# PILE, not two of them side by side inside each other
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(1.6, 5.4)},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(1.74, 5.26),
"sit_on": 0.40, "yaw": 0.10},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(6.4, -3.3)},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(6.52, -3.44),
"sit_on": 0.40, "yaw": -0.08},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-9.7, 1.2)},
{"glb": "wastebin", "kind": "plastic", "at": Vector2(-6.0, -1.0)},
{"glb": "wastebin", "kind": "plastic", "at": Vector2(1.0, 1.0)},
{"glb": "desk-phone", "kind": "plastic", "at": Vector2(-9.8, 5.0), "sit_on": 1.18},
{"glb": "rack", "kind": "wood", "at": Vector2(9.4, -6.6), "frozen": true},
{"glb": "rack", "kind": "wood", "at": Vector2(9.4, -4.3), "frozen": true},
{"glb": "turntable", "kind": "wood", "at": Vector2(6.9, -5.9)},
{"glb": "guillotine", "kind": "steel", "at": Vector2(4.5, 1.2)},
{"glb": "stapler", "kind": "steel", "at": Vector2(-5.25, -2.78), "sit_on": 0.75},
],
"records": [Vector2(6.2, -6.6), Vector2(7.0, -6.9), Vector2(7.8, -6.6)],
"gauntlet": {"at": Vector3(0.0, 0.0, -3.9), "yaw": PI,
"stand": Vector3(0.0, 0.0, -1.7), "shredder": Vector3(2.7, 0.0, -2.4)},
"spawn": {"at": Vector3(-8.6, 0.0, 7.2), "yaw": deg_to_rad(196.0)},
}
# ================================================================ LEVEL 02
static func pawnee() -> Dictionary:
return {
"name": "PAWNEE CITY HALL",
"subtitle": "parks & recreation · 4th floor",
"bounds": {"x0": -10.0, "x1": 10.0, "z0": -7.0, "z1": 7.5, "h": 2.70},
"palette": {
"floor": Color(0.34, 0.36, 0.40), "wall": Color(0.84, 0.80, 0.68),
"ceiling": Color(0.90, 0.89, 0.85), "trim": Color(0.30, 0.22, 0.14),
"partition": Color(0.52, 0.50, 0.42), "wood": Color(0.50, 0.34, 0.19),
"accent": Color(0.16, 0.36, 0.28),
},
"floor_style": "carpet",
"ceiling": "tile",
"walls": [
# the department head's office, and the meeting room beside it
{"a": "x", "at": -3.4, "from": -10.0, "to": -5.6, "door": -7.0},
{"a": "z", "at": -5.6, "from": -7.0, "to": -3.4, "style": "glass_top"},
{"a": "x", "at": -3.4, "from": -5.6, "to": 0.4, "door": -1.6},
{"a": "z", "at": 0.4, "from": -7.0, "to": -3.4},
# records room, and the corridor wall that makes this place a warren
{"a": "z", "at": 4.6, "from": -7.0, "to": -2.0, "door": -4.4},
{"a": "x", "at": -2.0, "from": 4.6, "to": 10.0},
# the public counter divides the front third from everything else
{"a": "x", "at": 3.6, "from": -10.0, "to": 3.0, "door": -8.0, "style": "half"},
],
"windows": [{"a": "x", "at": 7.5, "from": -10.0, "to": 10.0,
"y0": 0.95, "y1": 2.30, "blinds": true}],
"lights": {"xs": [-7.5, -3.4, 0.7, 4.8, 8.4], "zs": [-5.2, -1.6, 2.0, 5.6],
"color": Color(1.0, 0.96, 0.86), "energy": 1.8, "style": "troffer",
"key_energy": 1.4},
"sun": {"color": Color(0.90, 0.88, 0.78), "energy": 0.55,
"rot": Vector3(-34, 20, 0)},
"env": {"bg": Color(0.62, 0.66, 0.62), "ambient": Color(0.50, 0.48, 0.44),
"ambient_energy": 0.52, "exposure": 1.02},
"clusters": [Vector2(-6.0, 0.4), Vector2(-1.4, 0.4), Vector2(2.6, 0.4)],
"cluster_gap": 0.78,
"slabs": [
# the public counter itself — the thing between you and the citizens
{"size": Vector3(6.6, 1.06, 0.66), "at": Vector3(-1.2, 0.53, 5.4), "mat": "wood"},
{"size": Vector3(6.9, 0.07, 0.86), "at": Vector3(-1.2, 1.10, 5.4), "mat": "trim"},
{"size": Vector3(0.66, 1.06, 2.2), "at": Vector3(2.1, 0.53, 6.4), "mat": "wood"},
# meeting room table
{"size": Vector3(2.9, 0.09, 1.25), "at": Vector3(-2.6, 0.74, -5.2), "mat": "wood"},
{"size": Vector3(0.14, 0.72, 0.84), "at": Vector3(-3.7, 0.36, -5.2), "mat": "trim"},
{"size": Vector3(0.14, 0.72, 0.84), "at": Vector3(-1.5, 0.36, -5.2), "mat": "trim"},
# pinboards — municipal offices are 40% pinboard by surface area
{"size": Vector3(2.2, 1.20, 0.05), "at": Vector3(5.0, 1.65, -6.93), "mat": "accent"},
{"size": Vector3(2.3, 1.30, 0.03), "at": Vector3(5.0, 1.65, -6.90), "mat": "trim"},
{"size": Vector3(0.05, 1.20, 2.6), "at": Vector3(-9.93, 1.62, 2.0), "mat": "accent"},
{"size": Vector3(0.03, 1.30, 2.7), "at": Vector3(-9.90, 1.62, 2.0), "mat": "trim"},
# department head's desk
{"size": Vector3(1.7, 0.08, 0.85), "at": Vector3(-7.4, 0.74, -5.4), "mat": "wood"},
{"size": Vector3(1.5, 0.66, 0.72), "at": Vector3(-7.4, 0.35, -5.4), "mat": "wood"},
],
"props": [
{"glb": "office-plant", "at": Vector3(-9.3, 0.0, 6.6)},
{"glb": "office-plant", "at": Vector3(3.2, 0.0, -2.6)},
{"glb": "office-plant", "at": Vector3(-9.3, 0.0, -1.0)},
{"glb": "sofa", "at": Vector3(-6.6, 0.0, 6.5), "yaw": PI},
{"glb": "vending-machine", "at": Vector3(9.2, 0.0, 6.6), "yaw": -PI / 2},
{"glb": "microwave", "at": Vector3(9.3, 0.90, 1.2), "yaw": -PI / 2},
{"glb": "store-aircon", "at": Vector3(-6.0, 2.30, -6.9)},
],
"chairs": [
{"at": Vector3(-7.4, 0.0, -4.2), "yaw": PI},
{"at": Vector3(-8.6, 0.0, -6.3), "yaw": 0.0},
{"at": Vector3(-3.5, 0.0, -6.3), "yaw": 0.0},
{"at": Vector3(-2.6, 0.0, -6.3), "yaw": 0.0},
{"at": Vector3(-1.7, 0.0, -6.3), "yaw": 0.0},
{"at": Vector3(-3.5, 0.0, -4.1), "yaw": PI},
{"at": Vector3(-2.6, 0.0, -4.1), "yaw": PI},
{"at": Vector3(-1.7, 0.0, -4.1), "yaw": PI},
{"at": Vector3(-4.0, 0.0, 6.4), "yaw": PI},
{"at": Vector3(0.4, 0.0, 6.5), "yaw": PI},
{"at": Vector3(1.2, 0.0, 6.5), "yaw": PI},
],
"copier": {"at": Vector3(6.4, 0.0, 2.4), "yaw": PI},
"smashables": [
# a cabinet is 0.80 m wide; a row of them 0.70 m apart is one cabinet
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(5.4, -3.2)},
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(6.26, -3.2)},
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(7.12, -3.2)},
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(7.98, -3.2)},
{"glb": "water-cooler", "kind": "steel", "at": Vector2(3.9, 4.2)},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(8.6, -5.4)},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(8.74, -5.26),
"sit_on": 0.40, "yaw": 0.12},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-4.8, 3.0)},
{"glb": "wastebin", "kind": "plastic", "at": Vector2(-8.8, -3.9)},
{"glb": "wastebin", "kind": "plastic", "at": Vector2(4.4, 0.9)},
{"glb": "guillotine", "kind": "steel", "at": Vector2(6.4, 3.6), "sit_on": 0.0},
{"glb": "desk-phone", "kind": "plastic", "at": Vector2(-6.6, -5.4), "sit_on": 0.78},
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-7.6, -5.2), "sit_on": 0.78},
],
"records": [],
"gauntlet": {"at": Vector3(7.2, 0.0, -5.8), "yaw": PI,
"stand": Vector3(7.2, 0.0, -3.9), "shredder": Vector3(5.2, 0.0, -6.2)},
"spawn": {"at": Vector3(-8.0, 0.0, 6.6), "yaw": deg_to_rad(178.0)},
}
# ================================================================ LEVEL 03
static func house() -> Dictionary:
return {
"name": "THE INCUBATOR",
"subtitle": "seed round · someone's house",
"bounds": {"x0": -10.0, "x1": 10.0, "z0": -8.0, "z1": 8.0, "h": 3.05},
"palette": {
"floor": Color(0.46, 0.33, 0.20), "wall": Color(0.92, 0.91, 0.88),
"ceiling": Color(0.95, 0.95, 0.94), "trim": Color(0.18, 0.17, 0.17),
"partition": Color(0.72, 0.72, 0.70), "wood": Color(0.40, 0.27, 0.16),
"accent": Color(0.22, 0.44, 0.62),
},
"floor_style": "timber",
"ceiling": "flat",
"walls": [
# only two real walls: a bedroom-turned-office and the bathroom nobody cleans
{"a": "z", "at": -4.6, "from": -8.0, "to": -2.4, "door": -4.0},
{"a": "x", "at": -2.4, "from": -10.0, "to": -4.6, "door": -7.4},
{"a": "z", "at": 4.0, "from": -8.0, "to": -5.2, "door": -6.6},
# the kitchen island's back wall
{"a": "x", "at": -1.0, "from": 4.0, "to": 10.0, "style": "half"},
],
"windows": [{"a": "x", "at": 8.0, "from": -10.0, "to": 10.0,
"y0": 0.35, "y1": 2.70, "blinds": false}],
"lights": {"xs": [-6.5, -1.5, 3.5, 7.5], "zs": [-5.0, 0.0, 5.0],
"color": Color(1.0, 0.90, 0.76), "energy": 2.4, "style": "pendant",
"key_energy": 1.7},
"sun": {"color": Color(1.0, 0.94, 0.80), "energy": 1.5,
"rot": Vector3(-46, -96, 0)},
"env": {"bg": Color(0.70, 0.80, 0.90), "ambient": Color(0.56, 0.55, 0.52),
"ambient_energy": 0.62, "exposure": 1.05},
# nobody has a desk; they work at the big table
"clusters": [Vector2(1.6, -1.4), Vector2(1.6, 2.6)],
"cluster_gap": 0.80,
"slabs": [
# kitchen island + counter
{"size": Vector3(2.6, 0.92, 1.05), "at": Vector3(4.2, 0.46, 5.6), "mat": "wood"},
{"size": Vector3(2.8, 0.08, 1.20), "at": Vector3(4.2, 0.95, 5.6), "mat": "ceiling"},
{"size": Vector3(6.0, 0.90, 0.65), "at": Vector3(5.0, 0.45, 7.5), "mat": "ceiling"},
{"size": Vector3(6.2, 0.06, 0.75), "at": Vector3(5.0, 0.92, 7.5), "mat": "trim"},
# the long table everyone actually works at
{"size": Vector3(1.5, 0.09, 4.6), "at": Vector3(-4.4, 0.75, 0.6), "mat": "wood"},
{"size": Vector3(0.12, 0.72, 0.12), "at": Vector3(-4.9, 0.36, -1.4), "mat": "trim"},
{"size": Vector3(0.12, 0.72, 0.12), "at": Vector3(-3.9, 0.36, 2.6), "mat": "trim"},
# whiteboard, obviously
{"size": Vector3(0.05, 1.40, 3.0), "at": Vector3(-9.93, 1.75, 1.0), "mat": "ceiling"},
{"size": Vector3(0.03, 1.50, 3.1), "at": Vector3(-9.90, 1.75, 1.0), "mat": "trim"},
# a low bookshelf run
{"size": Vector3(0.42, 0.90, 3.2), "at": Vector3(-9.7, 0.45, -5.0), "mat": "wood"},
# pool decking visible through the glass
{"size": Vector3(7.0, 0.10, 16.0), "at": Vector3(12.6, -0.05, 0.0),
"mat": "ceiling", "collide": false},
],
"props": [
{"glb": "sofa", "at": Vector3(-6.6, 0.0, 5.4), "yaw": -PI / 2},
{"glb": "sofa", "at": Vector3(-2.0, 0.0, 6.6), "yaw": PI},
{"glb": "office-plant", "at": Vector3(-0.6, 0.0, 3.2)},
{"glb": "office-plant", "at": Vector3(7.2, 0.0, -6.4)},
{"glb": "office-plant", "at": Vector3(-9.2, 0.0, 7.2)},
{"glb": "microwave", "at": Vector3(5.4, 0.95, 7.4), "yaw": PI},
{"glb": "server-rack", "at": Vector3(9.0, 0.0, -6.8), "yaw": -PI / 2},
],
"chairs": [
{"at": Vector3(-6.0, 0.0, -1.4), "yaw": -PI / 2},
{"at": Vector3(-6.0, 0.0, -0.2), "yaw": -PI / 2},
{"at": Vector3(-6.0, 0.0, 1.4), "yaw": -PI / 2},
{"at": Vector3(-6.0, 0.0, 2.6), "yaw": -PI / 2},
{"at": Vector3(-2.8, 0.0, -1.0), "yaw": PI / 2},
{"at": Vector3(-2.8, 0.0, 1.8), "yaw": PI / 2},
{"at": Vector3(4.2, 0.0, 4.2), "yaw": 0.0},
{"at": Vector3(5.2, 0.0, 4.2), "yaw": 0.0},
{"at": Vector3(-7.6, 0.0, -6.4), "yaw": 0.2},
],
"copier": {"at": Vector3(-8.6, 0.0, -3.4), "yaw": PI / 2},
"smashables": [
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-4.4, -2.2)},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(8.4, 2.2)},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(8.54, 2.06),
"sit_on": 0.40, "yaw": -0.14},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-8.2, 6.4)},
{"glb": "wastebin", "kind": "plastic", "at": Vector2(-2.6, -3.2)},
{"glb": "wastebin", "kind": "plastic", "at": Vector2(6.2, 3.0)},
{"glb": "water-cooler", "kind": "steel", "at": Vector2(1.0, 6.8)},
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(-9.3, -1.6)},
{"glb": "turntable", "kind": "wood", "at": Vector2(-9.4, -6.6)},
{"glb": "desk-phone", "kind": "plastic", "at": Vector2(4.0, 5.6), "sit_on": 0.99},
{"glb": "guillotine", "kind": "steel", "at": Vector2(-9.4, -3.4)},
],
"records": [Vector2(-8.4, -7.0), Vector2(-7.6, -7.0)],
"gauntlet": {"at": Vector3(-7.2, 0.0, -7.2), "yaw": PI,
"stand": Vector3(-7.2, 0.0, -5.2), "shredder": Vector3(-5.0, 0.0, -6.8)},
"spawn": {"at": Vector3(-1.0, 0.0, 7.0), "yaw": deg_to_rad(186.0)},
}
# ================================================================ LEVEL 04
static func basement() -> Dictionary:
return {
"name": "SUB-LEVEL 4",
"subtitle": "i.t. department · no windows",
"bounds": {"x0": -12.0, "x1": 12.0, "z0": -6.0, "z1": 6.0, "h": 2.90},
"palette": {
"floor": Color(0.22, 0.22, 0.23), "wall": Color(0.50, 0.53, 0.46),
"ceiling": Color(0.20, 0.20, 0.21), "trim": Color(0.10, 0.10, 0.10),
"partition": Color(0.34, 0.36, 0.32), "wood": Color(0.30, 0.20, 0.12),
"accent": Color(0.52, 0.10, 0.09),
},
"floor_style": "concrete",
"ceiling": "flat",
"walls": [
# the boss's glass box, bolted into a room that never wanted one
{"a": "z", "at": 4.6, "from": -6.0, "to": 1.2, "door": -1.0,
"style": "glass_top"},
{"a": "x", "at": 1.2, "from": 4.6, "to": 12.0},
# server room behind a security door
{"a": "z", "at": -6.4, "from": -6.0, "to": 6.0, "door": 1.0},
# a stub wall someone put up and nobody took down
{"a": "x", "at": -1.4, "from": -4.0, "to": -1.6},
],
"windows": [], # it is a basement
"lights": {"xs": [-9.0, -4.5, 0.0, 4.5, 9.0], "zs": [-3.6, 0.4, 4.0],
"color": Color(0.86, 0.96, 0.84), "energy": 2.0, "style": "strip",
"key_energy": 1.1},
"env": {"bg": Color(0.06, 0.07, 0.06), "ambient": Color(0.32, 0.36, 0.32),
"ambient_energy": 0.40, "exposure": 1.08},
"clusters": [Vector2(3.6, 0.0)],
"cluster_gap": 0.82,
"slabs": [
# exposed services, because there's no ceiling to hide them
{"size": Vector3(24.0, 0.22, 0.22), "at": Vector3(0.0, 2.70, -3.0),
"mat": "trim", "collide": false},
{"size": Vector3(24.0, 0.16, 0.16), "at": Vector3(0.0, 2.62, 1.4),
"mat": "trim", "collide": false},
{"size": Vector3(24.0, 0.30, 0.10), "at": Vector3(0.0, 2.78, 4.2),
"mat": "trim", "collide": false},
# boss's desk in the glass box
{"size": Vector3(1.8, 0.08, 0.86), "at": Vector3(8.0, 0.74, 5.4), "mat": "wood"},
{"size": Vector3(1.6, 0.66, 0.74), "at": Vector3(8.0, 0.35, 5.4), "mat": "wood"},
# workbench along the back
{"size": Vector3(5.0, 0.08, 0.80), "at": Vector3(-6.0, 0.90, -5.5), "mat": "trim"},
{"size": Vector3(0.10, 0.88, 0.70), "at": Vector3(-8.4, 0.44, -5.5), "mat": "trim"},
{"size": Vector3(0.10, 0.88, 0.70), "at": Vector3(-3.6, 0.44, -5.5), "mat": "trim"},
# the famous red door
{"size": Vector3(1.10, 2.10, 0.10), "at": Vector3(1.0, 1.05, -6.35),
"mat": "accent"},
],
"props": [
{"glb": "server-rack", "at": Vector3(-10.6, 0.0, -4.4), "yaw": PI / 2},
{"glb": "server-rack", "at": Vector3(-10.6, 0.0, -2.4), "yaw": PI / 2},
{"glb": "server-rack", "at": Vector3(-10.6, 0.0, -0.4), "yaw": PI / 2},
{"glb": "server-rack", "at": Vector3(-10.6, 0.0, 1.6), "yaw": PI / 2},
{"glb": "sofa", "at": Vector3(-4.0, 0.0, 3.4), "yaw": 0.15},
{"glb": "office-plant", "at": Vector3(11.2, 0.0, 5.4)},
{"glb": "vending-machine", "at": Vector3(-0.4, 0.0, 5.4), "yaw": PI},
{"glb": "microwave", "at": Vector3(-5.0, 0.95, -5.5), "yaw": PI},
],
"chairs": [
{"at": Vector3(8.0, 0.0, 4.1), "yaw": PI},
{"at": Vector3(6.6, 0.0, 5.9), "yaw": 0.3},
{"at": Vector3(-6.6, 0.0, -4.2), "yaw": PI},
{"at": Vector3(-1.0, 0.0, 2.2), "yaw": 1.2},
],
"copier": {"at": Vector3(0.4, 0.0, -4.4), "yaw": 0.0},
"smashables": [
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-7.0, -5.5), "sit_on": 0.94},
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-5.0, -4.6)},
{"glb": "crt-monitor", "kind": "glass", "at": Vector2(-8.4, -3.2)},
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(10.8, 3.0)},
{"glb": "filing-cabinet", "kind": "steel", "at": Vector2(10.8, 2.2)},
# nobody in an IT basement puts a box down on its own
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-2.6, -4.8)},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-2.46, -4.92),
"sit_on": 0.40, "yaw": 0.09},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(-2.58, -4.70),
"sit_on": 0.80, "yaw": -0.16},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(5.6, -4.8)},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(5.72, -4.94),
"sit_on": 0.40, "yaw": -0.11},
{"glb": "cardboard-box", "kind": "cardboard", "at": Vector2(5.58, -4.68),
"sit_on": 0.80, "yaw": 0.20},
{"glb": "wastebin", "kind": "plastic", "at": Vector2(2.0, 2.6)},
{"glb": "wastebin", "kind": "plastic", "at": Vector2(9.0, 4.4)},
{"glb": "water-cooler", "kind": "steel", "at": Vector2(0.6, 2.2)},
{"glb": "shredder", "kind": "plastic", "at": Vector2(-2.2, -2.0)},
{"glb": "desk-phone", "kind": "plastic", "at": Vector2(8.3, 5.4), "sit_on": 0.78},
{"glb": "turntable", "kind": "wood", "at": Vector2(-9.0, 4.4)},
{"glb": "guillotine", "kind": "steel", "at": Vector2(-6.0, -4.2)},
],
"records": [Vector2(-8.2, 5.0), Vector2(-7.5, 5.0)],
"gauntlet": {"at": Vector3(-3.4, 0.0, -3.4), "yaw": PI,
"stand": Vector3(-3.4, 0.0, -1.5), "shredder": Vector3(-2.2, 0.0, -2.0)},
"spawn": {"at": Vector3(1.0, 0.0, -5.2), "yaw": deg_to_rad(0.0)},
}
# ================================================================ LEVEL 00
## THE BACKROOMS. The only site whose walls are GENERATED rather than authored, which is
## both the honest test of the Floorplan spec and the right call for the fiction: this
## place is supposed to feel endless and samey, and a seeded RNG produces that better
## than a human placing slabs ever would.
##
## Layout follows the reference photographs rather than a maze algorithm — the Backrooms
## aren't corridors, they're big offset SLABS sitting in an open floor with gaps between
## them, so that's what this emits: partial wall runs along a coarse grid, plus pillars.
## A proper maze would be legible; this is deliberately not.
##
## Everything smashable here is INVISIBLE (see Dust.gd). You find it by walking into it.
const BR_SEED := 0xBACC
const BR_HALF := 19.0
const BR_H := 2.55 # low. lower than any real office.
static func backrooms() -> Dictionary:
var rng := RandomNumberGenerator.new()
rng.seed = BR_SEED
var walls: Array = []
# Wall runs on a 4.6 m lattice. Each line gets a few segments with gaps between, so
# you can always get through somewhere but never see far.
# Density matters more than it looks. The first pass put a run on every lattice line
# in both axes and the result had NO sightline longer than a few metres — which kills
# the entity outright, because a stalker you can never see can never be stared at. The
# reference photographs are mostly open floor with occasional slabs, so: skip roughly
# a third of the lines, and leave big gaps.
var lines := [-13.8, -9.2, -4.6, 0.0, 4.6, 9.2, 13.8]
for axis in ["x", "z"]:
for at in lines:
if rng.randf() < 0.34:
continue # this line is simply open floor
var cursor := -BR_HALF
while cursor < BR_HALF - 2.0:
var gap := rng.randf_range(4.5, 12.0)
cursor += gap
if cursor >= BR_HALF - 2.0:
break
var run := rng.randf_range(3.0, 7.5)
var end: float = minf(cursor + run, BR_HALF)
if end - cursor > 1.4:
walls.append({"a": axis, "at": at, "from": cursor, "to": end})
cursor = end
# freestanding pillars, because every Backrooms photo has one in the way
var slabs: Array = []
for i in 16:
var px := rng.randf_range(-BR_HALF + 2.0, BR_HALF - 2.0)
var pz := rng.randf_range(-BR_HALF + 2.0, BR_HALF - 2.0)
slabs.append({"size": Vector3(0.62, BR_H, 0.62),
"at": Vector3(px, BR_H * 0.5, pz), "mat": "wall"})
# skirting-height trim run around the outer wall, the one detail the photos all share
for s in [-1.0, 1.0]:
slabs.append({"size": Vector3(BR_HALF * 2.0, 0.12, 0.06),
"at": Vector3(0.0, 0.06, s * (BR_HALF - 0.03)), "mat": "trim",
"collide": false})
slabs.append({"size": Vector3(0.06, 0.12, BR_HALF * 2.0),
"at": Vector3(s * (BR_HALF - 0.03), 0.06, 0.0), "mat": "trim",
"collide": false})
# a dense grid of buzzing troffers — the light IS the horror
var lx: Array = []
var lz: Array = []
var v := -BR_HALF + 2.3
while v < BR_HALF:
lx.append(v)
lz.append(v)
v += 3.05
# sparse, wrong office objects. All invisible until you dust them.
var smash: Array = []
var kinds := [["office-chair", "plastic"], ["wastebin", "plastic"],
["cardboard-box", "cardboard"], ["filing-cabinet", "steel"],
["crt-monitor", "glass"], ["desk-phone", "plastic"],
["water-cooler", "steel"], ["office-plant", "wood"]]
# dense enough that a single cloud finds you two or three things — the level is
# a search, and a search with nothing in it is just a walk
# Rejection sampling: the widest thing on this list is a 1.35 m cardboard box, so
# anything landing inside 1.6 m of a spot already taken gets redrawn. Two props
# spawned inside each other are a filing cabinet launched across the maze on frame
# one, which in a level built on silence is not the surprise you wanted.
var taken: Array = []
var tries := 0
while smash.size() < 78 and tries < 4000:
tries += 1
var spot := Vector2(rng.randf_range(-BR_HALF + 2.5, BR_HALF - 2.5),
rng.randf_range(-BR_HALF + 2.5, BR_HALF - 2.5))
var clear := true
for t in taken:
if (spot - (t as Vector2)).length() < 1.6:
clear = false
break
if not clear:
continue
taken.append(spot)
var k: Array = kinds[rng.randi() % kinds.size()]
smash.append({
"glb": String(k[0]), "kind": String(k[1]), "at": spot,
"yaw": rng.randf_range(0.0, TAU),
})
return {
"name": "LEVEL 0",
"subtitle": "you noclipped out of reality",
"invisible": true, # Dust.gd hides every smashable here
"bounds": {"x0": -BR_HALF, "x1": BR_HALF, "z0": -BR_HALF, "z1": BR_HALF,
"h": BR_H},
"palette": {
# the whole point is that there is only one colour
"floor": Color(0.52, 0.47, 0.30), "wall": Color(0.76, 0.72, 0.42),
"ceiling": Color(0.80, 0.77, 0.55), "trim": Color(0.62, 0.58, 0.34),
"partition": Color(0.74, 0.70, 0.40), "wood": Color(0.62, 0.55, 0.32),
"accent": Color(0.70, 0.66, 0.38),
},
"floor_style": "carpet",
"ceiling": "tile",
"walls": walls,
"windows": [], # there is no outside
"slabs": slabs,
"lights": {"xs": lx, "zs": lz, "color": Color(1.0, 0.97, 0.80),
"energy": 1.15, "style": "troffer", "key_energy": 0.9},
"env": {"bg": Color(0.30, 0.28, 0.16), "ambient": Color(0.62, 0.58, 0.36),
"ambient_energy": 0.70, "exposure": 1.12},
"clusters": [],
"props": [],
"chairs": [],
"smashables": smash,
"records": [],
"gauntlet": {"at": Vector3(0.0, 0.0, -2.2), "yaw": PI,
"stand": Vector3(0.0, 0.0, 0.2), "shredder": Vector3(3.0, 0.0, -1.0)},
"spawn": {"at": Vector3(0.0, 0.0, 16.5), "yaw": PI},
}
# ================================================================ THE GROCER
## A greengrocer, and the only site whose contents are mostly SOFT.
##
## Everything else in the game shatters; here the stock bursts. That one material change
## rewrites the level: produce squashes instead of breaking, a pyramid crushes its own
## bottom layer as it collapses, and the floor fills with juice you then slip in. The
## glass bottles behind the juice bar are the exception, and the contrast is the point —
## one shelf in the room still rewards a proper swing.
##
## Bright, clean, warm track lighting on a pale floor, because the whole level is about
## making a mess and a mess needs somewhere clean to happen.
static func grocer() -> Dictionary:
var stacks: Array = []
# Angled island displays. `at` is the TOP surface a pyramid piles onto; Main builds
# the pyramid itself so the physics settles honestly rather than being placed.
var islands := [
[Vector3(-5.4, 0.95, -2.2), "apple", 5],
[Vector3(-5.4, 0.95, 1.4), "orange", 5],
[Vector3(-2.0, 0.95, -2.2), "tomato", 5],
[Vector3(-2.0, 0.95, 1.4), "apple", 4],
[Vector3(1.4, 0.95, -2.2), "cabbage", 3],
[Vector3(1.4, 0.95, 1.4), "orange", 4],
[Vector3(4.8, 0.80, -0.4), "melon", 3],
]
for i in islands:
stacks.append({"at": i[0], "item": String(i[1]), "rows": int(i[2])})
var slabs: Array = []
# the island tables themselves: a plinth, a timber top, and a raised back edge so a
# pyramid has something to fail against
for i in islands:
var p: Vector3 = i[0]
var big: bool = String(i[1]) == "melon"
var w := 2.4 if big else 1.5
var d := 1.9 if big else 1.5
var h: float = p.y
slabs.append({"size": Vector3(w, h - 0.10, d),
"at": Vector3(p.x, (h - 0.10) * 0.5, p.z), "mat": "trim"})
slabs.append({"size": Vector3(w + 0.14, 0.10, d + 0.14),
"at": Vector3(p.x, h - 0.05, p.z), "mat": "wood"})
for s in [-1.0, 1.0]:
slabs.append({"size": Vector3(w + 0.14, 0.10, 0.06),
"at": Vector3(p.x, h + 0.05, p.z + s * (d * 0.5 + 0.04)), "mat": "wood"})
slabs.append({"size": Vector3(0.06, 0.10, d + 0.14),
"at": Vector3(p.x + s * (w * 0.5 + 0.04), h + 0.05, p.z), "mat": "wood"})
# juice bar along the back, with shelving above it for the bottles
slabs.append({"size": Vector3(7.0, 1.05, 0.75), "at": Vector3(-3.0, 0.525, -7.4),
"mat": "trim"})
slabs.append({"size": Vector3(7.2, 0.09, 0.92), "at": Vector3(-3.0, 1.10, -7.4),
"mat": "wood"})
for k in range(3):
slabs.append({"size": Vector3(6.6, 0.06, 0.34),
"at": Vector3(-3.0, 1.62 + k * 0.46, -7.72), "mat": "wood"})
# chiller run down the right wall
slabs.append({"size": Vector3(0.80, 2.10, 8.0), "at": Vector3(10.4, 1.05, 1.0),
"mat": "trim"})
for k in range(4):
slabs.append({"size": Vector3(0.86, 0.05, 8.0),
"at": Vector3(10.3, 0.55 + k * 0.46, 1.0), "mat": "ceiling"})
var smash: Array = []
# bottles on the bar shelves — the one thing in here that still wants a proper swing
for k in range(3):
for b in range(11):
smash.append({"glb": "juice-bottle", "kind": "glass",
"at": Vector2(-6.0 + b * 0.60, -7.72), "sit_on": 1.65 + k * 0.46})
for b in range(9):
smash.append({"glb": "juice-bottle", "kind": "glass",
"at": Vector2(-5.6 + b * 0.62, -7.4), "sit_on": 1.15})
# loose crates of stock on the floor
for c in [Vector2(-8.6, 3.4), Vector2(-8.6, 4.4), Vector2(-8.0, 5.4),
Vector2(7.4, -4.6), Vector2(8.2, -4.0), Vector2(7.8, 5.2)]:
smash.append({"glb": "produce-crate", "kind": "wood", "at": c,
"yaw": randf_range(0.0, TAU)})
# a few bananas and cabbages just lying about
for b in [Vector2(-7.2, 0.4), Vector2(3.2, 4.2), Vector2(6.0, 2.0)]:
smash.append({"glb": "produce-banana", "kind": "produce", "at": b})
for c in [Vector2(-9.4, -2.0), Vector2(2.4, -5.6)]:
smash.append({"glb": "produce-cabbage", "kind": "produce", "at": c})
return {
"name": "THE GREENGROCER",
"subtitle": "everything here is soft except the bottles",
"bounds": {"x0": -11.0, "x1": 11.0, "z0": -8.0, "z1": 8.0, "h": 3.30},
"palette": {
"floor": Color(0.70, 0.68, 0.64), "wall": Color(0.90, 0.89, 0.85),
"ceiling": Color(0.20, 0.20, 0.21), "trim": Color(0.15, 0.15, 0.16),
"partition": Color(0.30, 0.44, 0.40), "wood": Color(0.52, 0.36, 0.20),
"accent": Color(0.20, 0.48, 0.42),
},
"floor_style": "lino",
"ceiling": "flat",
"walls": [],
"windows": [{"a": "z", "at": 8.0, "from": -11.0, "to": 11.0,
"y0": 0.35, "y1": 2.85, "blinds": false}],
"lights": {"xs": [-8.0, -4.0, 0.0, 4.0, 8.0], "zs": [-6.0, -2.0, 2.0, 6.0],
"color": Color(1.0, 0.94, 0.84), "energy": 2.4, "style": "strip",
"key_energy": 1.8},
"sun": {"color": Color(1.0, 0.96, 0.88), "energy": 1.1,
"rot": Vector3(-40, 8, 0)},
"env": {"bg": Color(0.76, 0.82, 0.88), "ambient": Color(0.60, 0.58, 0.55),
"ambient_energy": 0.66, "exposure": 1.04},
"clusters": [],
"chairs": [],
"props": [
{"glb": "office-plant", "at": Vector3(-10.2, 0.0, 7.0)},
{"glb": "office-plant", "at": Vector3(9.6, 0.0, 7.0)},
# the bag roll. Its post stands on the floor; the station is the roll itself.
{"glb": "bag-roll", "at": Vector3(-0.3, 0.0, 3.6), "yaw": PI},
],
# Brass shop scales hung down the aisles, dial at eye level and the dish at chest
# height — where a real one hangs, where you look INTO the pan rather than at its
# unlit underside, and where you keep walking into it.
"scales": [
Vector3(-3.7, 1.58, -0.4), Vector3(-0.3, 1.58, -0.4),
# the counter one hangs higher, so you can still see the bottle shelf
Vector3(3.1, 1.58, -0.4), Vector3(-3.0, 1.88, -6.5),
],
"stations": [
{"kind": "bags", "at": Vector3(-0.3, 1.1, 3.6), "label": "bag up the order"},
],
"slabs": slabs,
"smashables": smash,
"stacks": stacks,
"records": [],
"gauntlet": {"at": Vector3(-8.6, 0.0, -6.0), "yaw": PI,
"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])
# The interior walls between the four rooms, with a gap wherever a doorway pierces
# one — the importer derives them from the shop's own portal graph.
var walls: Array = []
for e in data.get("walls", []):
var wl: Dictionary = {"a": String(e.get("a", "x")), "at": float(e.get("at", 0.0)),
"from": float(e.get("from", 0.0)), "to": float(e.get("to", 0.0))}
if e.has("door"):
wl["door"] = float(e["door"])
walls.append(wl)
# The street glazing goes on the entry hall's outer wall, because that is the way in.
var windows: Array = []
for r in data.get("rooms", []):
if not String(r.get("name", "")).begins_with("entry"):
continue
var at: Array = r["at"]
var size: Array = r["size"]
var rx0: float = float(at[0])
var rz0: float = float(at[1])
if absf(rz0 - (-dp * 0.5)) < 0.2:
windows.append({"a": "z", "at": -dp * 0.5, "from": rx0 + 0.4,
"to": rx0 + float(size[0]) - 0.4, "y0": 0.6, "y1": 2.3, "blinds": false})
# a light every ~3 m across whatever footprint the four rooms came out as
var lx: Array = []
var lz: Array = []
var gx: float = -w * 0.5 + 1.6
while gx < w * 0.5 - 0.6:
lx.append(gx)
gx += 3.0
var gz: float = -dp * 0.5 + 1.6
while gz < dp * 0.5 - 0.6:
lz.append(gz)
gz += 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": walls,
"windows": windows,
"lights": {"xs": lx, "zs": lz,
"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},
}