Rooms
How the interior of every building is built from mod data, and how to change or add a venue's rooms.
Every interior in Underlives is assembled at runtime from mod files: the shell (walls, floor, ceiling, doors, windows), the furniture, the lights. Nothing is a hand-built Unreal level. Change the files and every café in every city changes with them.
Which furniture a bar gets, where the counter goes, how many ceiling lights hang in a gym: all of it is a grammar or a recipe in Rooms/. The engine only knows how to solve and render them.
The four kinds of file
| File | Where | Says |
|---|---|---|
.ulroom (a recipe) |
Rooms/<name>.ulroom |
For one venue tag: the shell kit, which furniture sets to use, and how (preset or grammar) |
.ulgrammar (a grammar) |
Rooms/Grammars/<name>.ulgrammar |
The shape of a kind of room: which zones exist, what goes in each, where |
| A set | Rooms/Sets/<Set>/ |
A furniture set: one .glb per piece, plus their catalog and the groups built from them |
.ulroomlayout (a layout) |
Rooms/Layouts/ or saved games |
An exact arrangement of furniture, from the room editor |
When the player enters a building, the game finds the recipe for its venue tag, picks a dominant style from the building's seed, and either reposes a whole authored set or composes the room from the grammar. The same building always gets the same room.
Recipes: .ulroom
lua-- Rooms/arcade.ulroom
local KIT_FILE = "GenericKit/kit_worn.glb#"
local function M(node) return KIT_FILE .. node end
return {
AMENITY = "arcade",
PERSONALITY = { CLEANLINESS = 0.4, CLUTTER = 0.8, DECORATION = 0.4 },
-- A building either reposes one authored set as-is…
LAYOUT = {
SETS = { "Sets/UndergroundGame" },
USE_SET_WALLS = false,
},
PRESET_CHANCE = 0.5,
-- …or is composed by a grammar from groups mined out of these sets.
GENERATE = {
GRAMMAR = "games_hall",
SETS = { "Sets/UndergroundGame" },
EXTRA = { "Sets/CasinoHall", "Sets/Bowling", "Sets/SportsBar" },
},
KIT = {
FLOOR = M("Floor_Tile"), CEILING = M("Ceiling_Tile"), WALL = M("Wall"),
WALL_WINDOW = M("Wall_Window"), WALL_DOOR = M("Wall_Door"), DOOR = M("Door_Leaf"),
PILLAR = M("Pillar"), CEILING_LIGHT = M("Ceiling_Light"),
TILE = 200, WALL_HEIGHT = 250, WINDOW_EVERY = 2, FACING = 0,
},
POOLS = {},
}
| Field | Meaning |
|---|---|
AMENITY |
The venue tag this recipe is for. A later mod's recipe for the same tag replaces it. |
PERSONALITY |
CLEANLINESS, CLUTTER, DECORATION from 0 to 1: how tidy, full and decorated the place feels |
LAYOUT.SETS |
Authored sets a building may repose whole, one picked per building |
LAYOUT.USE_SET_WALLS |
Keep the set's own walls instead of the generated shell |
GENERATE.GRAMMAR |
The grammar that composes the room |
GENERATE.SETS |
Sets that can be the building's dominant style |
GENERATE.EXTRA |
Sets that only lend groups the dominant one lacks |
GENERATE.ROOMS |
For multi-room venues: { { "living", "home_living" }, { "bedroom", "home_bedroom" }, … }, each room with its own grammar. The first is where the player enters; each other room gets a door. |
PRESET_CHANCE |
With both LAYOUT and GENERATE: the share of buildings that get an authored set; the rest are composed |
CROWD |
Optional { MIN = 2, MAX = 6, AREA = 8 }: strangers in the room, one per AREA m² of floor the furniture leaves free, kept between MIN and MAX. Each field left out uses the venue size's default (max 2 / 4 / 7 / 8 from small to institutional, min 1 for small rooms and 2 otherwise, 8 m²). No room ever holds more than 10. How many people a venue holds in the town simulation, and who walks in and out while the player is there, is Map/venue_capacity.json and Animations/ambient.json (people who come and go) |
KIT |
The modular shell: node names in a .glb kit ("file.glb#Node"), the tile size and wall height in cm, a window every N wall tiles. By default the ceiling grid hangs a light borrowed from the furniture sets, for variety; OWN_CEILING_LIGHT = true hangs the kit's own CEILING_LIGHT instead (the hospital keeps its ceiling panels this way) |
The shipped kits live in ul_Base/Rooms/GenericKit/ (kit_apartment.glb, kit_bar.glb, kit_worn.glb…) and can be referenced from any mod's recipe.
Open-air sites
A park, a beach or a forest is a room without walls; a back alley is walls without a roof (ROOFLESS, below). OPEN = true in the KIT turns the shell into an open site:
- the kit's floor becomes the ground, painted by the look's
floor_texture, and it runs on for kilometres past the plot; - no walls, ceiling, windows, doors, pillars or ceiling lights are built, and lighting zones are skipped;
- the sun and the sky light the site, with a brighter sky fill than indoors;
- the camera stands back off the plot.
A street can be a site too: bus_stop.ulroom and taxi.ulroom are a stretch of pavement with the shelter, the rank sign, bins and bollards along the far edge, and street lamps, plane trees and house fronts scattered beyond it.
luaKIT = {
OPEN = true, SEA = "back", SHORE = 450,
FLOOR = M("Floor_Tile"), WALL = M("Wall"),
TILE = 200, WALL_HEIGHT = 250, WINDOW_EVERY = 0,
SCATTER = {
{ LEAD = "Nature/Tree", MATCH = { "Palm" }, COUNT = 25, NEAR = 150, FAR = 2500 },
{ LEAD = "Nature/Backdrop", MATCH = { "Mountain" }, COUNT = 6, NEAR = 3500, FAR = 9000 },
},
},
| Field | Meaning |
|---|---|
OPEN |
true for an open-air site |
ROOFLESS |
true for walls without a ceiling: a back alley, a yard, a light well. The shell is built as a closed room (its door on the street side, windows only if WINDOW_EVERY asks), but no ceiling or ceiling lights are hung and the sky lights it like an open site. alley.ulroom uses it with WALL_HEIGHT = 650 |
SEA |
"back", "front", "left" or "right": a sea on that side, with waves, surf and wet sand, out to the horizon. The ground stops at the waterline and the camera faces the water |
SHORE |
Distance in cm from the plot's edge to the waterline (default 450) |
RIVER |
Width in cm: the water on the SEA side becomes a calm river that wide, below its banks, with the far bank beyond |
SCATTER |
Scenery strewn in a ring around the plot, picked from the recipe's sets. Nothing is placed between the camera and the plot, or in the water. Each entry takes: • LEAD and MATCH, as a grammar zone;• COUNT;• NEAR and FAR, the ring in cm measured from the plot's edge (points crowd toward NEAR);• SCALE = { min, max }.Entries with NEAR of 2000 or more are far scenery: they are placed only on the side the camera looks at, measured to their own edge. Use them for Nature/Backdrop mountains, hills and tree lines |
The grammar's wall and corner bands become the site's edges. It should have no ceiling zone.
Multi-room venues
luaGENERATE = {
GRAMMAR = "home",
ROOMS = { { "living", "home_living" }, { "bedroom", "home_bedroom" }, { "kitchen", "home_kitchen" }, { "bathroom", "home_bathroom" } },
SETS = { … },
},
"home:bedroom" addresses one room of a multi-room venue directly.
The rooms need not share a grammar family: brothel.ulroom opens on a brothel_lounge (reception, sofas, price board) with a hotel_room off it, and newsroom.ulroom puts a news_studio (anchor desk, cameras, green-screen cove) next to the newsroom floor. Each extra room's own set list, the third entry of its pair, is where its special pieces come from.
Grammars: .ulgrammar
A grammar describes a kind of room as zones. The solver fills each zone with furniture groups from the recipe's sets, scoring every candidate spot.
lua-- Rooms/Grammars/bar.ulgrammar (abridged)
return {
NAME = "bar",
ZONES = {
{ NAME = "lighting", LEAD = { "Lighting/CeilingLight", "Lighting/Chandelier" }, PLACE = "ceiling",
COUNT = { 2, 4 }, DUPES = true, ANY = true, HUNG = true },
{ NAME = "bar", LEAD = { "Counter/Bar", "Counter" }, PLACE = "wall", REQUIRED = true, ANY = true, MAX_WIDTH = 900,
PREFER = { FAR_FROM_DOOR = 1.0, AWAY_FROM_WINDOW = 0.8 } },
{ NAME = "back_bar", LEAD = { "Storage/Shelf", "Display/Rack", "Appliance/Fridge" }, PLACE = "wall",
COUNT = { 0, 3 }, DUPES = true, CHANCE = 0.9, HUNG = true, ANY = true, PREFER = { NEAR = "bar" } },
{ NAME = "stools", LEAD = { "Seating/Stool" }, PLACE = "free", COUNT = { 0, 4 }, DUPES = true,
CHANCE = 0.7, STANDALONE = true, PREFER = { NEAR = "bar" } },
},
}
Zone fields
| Field | Meaning |
|---|---|
NAME |
The zone's name (other zones refer to it in PREFER) |
LEAD |
What the group is built around: "Category" or "Category/Type" of its main piece (see Naming and kinds). A Type is one kind of object, so "Fixture/BondageCross" is only crosses; list several kinds for a zone that takes several |
ROLES |
Only groups mined from these room roles ("bedroom", "kitchen"…) |
PLACE |
"wall" (default), "corner", "free" (in the open floor), "ceiling" or "aisle" (back-to-back rows down the room's long axis, the way a warehouse or a supermarket stands its racking and gondolas; the only open-floor place that takes pieces 2.2 m tall and 1.8 m long) |
AISLE |
With PLACE = "aisle": walkway width in cm between rows and at the row ends (default 200, minimum 80) |
REQUIRED |
The room is not valid without it |
COUNT |
{ min, max } groups (default one) |
CHANCE |
Probability the zone is filled at all (default 1) |
DUPES |
May place a kind of furniture the room already has (a wall of shelves) |
REPEAT |
Every placement after the first takes the same model when it fits (one pendant model across a ceiling) |
LEAD_ORDER |
LEAD is a preference order: an assembly matching an earlier entry always wins over a later one (the cafe takes a bar counter or a whole counter wall, Counter/Bar, before any other Counter) |
MATCH |
Of the pieces LEAD allows, only those whose name contains one of these words: LEAD = { "Fixture/Sling" }, MATCH = { "Sling_Frame" } keeps the framed slings and leaves out the ones hung from a hook |
NOT_MATCH |
Leaves out the pieces whose name contains one of these words, after MATCH: MATCH = { "Appliance_Washer" }, NOT_MATCH = { "Commercial" } keeps the home washers only |
STANDALONE |
Wants single pieces rather than composed groups |
ANY |
Single pieces and groups are equally welcome |
HUNG |
Accepts wall-hung groups (high shelves, mirrors) |
MAX_WIDTH, MAX_HEIGHT |
Size caps in cm (keeps a 7 m diner counter out of a home kitchen) |
MIN_AREA |
Only in rooms of at least this many m² |
PREFER |
Soft preferences with weights: FAR_FROM_DOOR, NEAR_DOOR, NEAR_WINDOW, AWAY_FROM_WINDOW, CORNER; and NEAR, FACES, OPPOSITE naming another zone |
Grammar fields
| Field | Meaning |
|---|---|
NAME |
The grammar's name (recipes refer to it) |
EXTENT |
{ x, y }: half-size of the room in cm, instead of the building's size tier |
KIT_FILE |
Another kit .glb for this room (a tiled bathroom inside an apartment) |
FLOOR_TILE |
The floor must be tile or stone (wet rooms) |
WINDOW_EVERY |
Override the kit's window spacing (0 = no windows) |
SCORE |
Weights for the whole layout: OPEN_CENTRE, WALL_COVERAGE, CAMERA |
Always give a grammar a lighting zone with PLACE = "ceiling". It is how a composed room gets real light fixtures of its own; without one, it is lit only by a generic ceiling grid.
Sets: furniture
A set is a folder under Rooms/Sets/. The base game's sets are all generated (Gen*): GenCore holds
every piece, and the use sets (GenHome, GenFood, GenNight…) and their style variants
(GenHome_WoodLight…) hold the pieces for one kind of venue.
textRooms/Sets/GenFood/
├── Pieces/GenFood/<Piece>.glb -- one model per piece, named after it
├── Pieces/GenFood/<Piece>.ulmat -- its surfaces, moving parts and restraint points
├── catalog.ulset -- per piece: category, type, size, height off the floor, wall-hung or not
└── assemblies.ulset -- the groups the solver places (single pieces and composed groups)
Naming and kinds
A piece's name is Category_Type_Descriptors, and its kind is Category/Type:
- Category is one of
Seating,Table,Desk,Bed,Storage,Counter,Display,Appliance,Lighting,Fixture,Arch,Decor,WallDecor,Textile,Rug,Plant,Nature,Product,Wear. - Type is the object itself, never the venue:
Seating_BarStool_Saddle_Reception,Fixture_Turnstile_Tripod,Appliance_Dishwasher. A specialised object has its own Type, so a dining room asking forSeating/Chairnever gets a dentist's chair (Seating/DentistChair) or a wheelchair (Seating/Wheelchair). - Descriptors tell pieces of one kind apart: model, material, size, venue, state (
_Open,_Lit). British spelling, no real brand names.
Lights: Lighting/CeilingLight, Pendant and Chandelier hang from the ceiling; FloorLamp,
TableLamp, WallLight, Neon, StreetLight and Floodlight do not.
catalog.ulsetlists every piece with its category, type, size (cm), centre, height off the floor and whether it hangs on a wall. Generated pieces also carrysurface(the top's height in cm) andlevels, every flat surface a small object can stand on:[[z, x0, y0, x1, y1, clear], ...]in cm from the piece's origin, lowest first, withzthe height,x0..x1/y0..y1the usable rectangle (x to the piece's right, y to its back, the front is at -y) andclearthe free height above it (200 when nothing is above). A bookcase has one level per shelf; a coffee table its top and the shelf under it. Room dressing puts objects on them. The levels of a cupboard, fridge or wardrobe that lie below its top are its inside shelves: home storage stands the things you keep on them.- A moving part listed under
partscan have its ownlevels, in the same space and measured with the part closed. These are a drawer's floor, a freezer basket, the racks on the inside of a fridge door. For an open drawer, add its slide (axis×hicm). For a door, turn the level about the part'spivot_cmandaxis. railslists the bars that clothes hang on, in wardrobes and on clothes rails:[[z, x0, y0, x1, y1, drop], ...]in cm, in the same axes aslevels.zis the top of the bar, where a hanger's hook rests.(x0, y0)–(x1, y1)are its two ends.dropis the free height under it, down to the wardrobe floor, a shelf or the floor. A wardrobe with a shirt rail over a trouser rail has two entries. The samerailsare written in the piece's.ulmat.
- A moving part listed under
assemblies.ulsetlists the groups: an id (G_<Piece>for a single piece,C_…for a composed group), a kind (a single piece's kind, or its lead piece's kind plus what the group adds:Table_DiningTable+4Seat,Counter_KitchenUnit+3), a room role, the lead piece, the nodes it contains, its footprint and facing, and interaction slots.surfaceis the top of the piece's main horizontal surface in cm above the floor (the seat of a chair, the mattress of a bed, the top of a table), measured from the mesh as seen from above. Scenes use it to find a seat or a couch of the height a position was made for (Rooms/SceneHeights.json), sincesizeincludes the backrest."ceiling": truemarks a group that hangs from the ceiling: the room lifts it under its own ceiling and does not count its footprint. Without the field, aLightinglead of typeCeilingLight,PendantorChandelierhangs.- Groups are looked up by the set's folder, and referenced from layouts as
set+id.
Any recipe in any mod can use the base game's sets.
Surfaces of the generated furniture: .ulmat
The Gen* sets ship one .glb per piece under Pieces/, with no textures inside. Beside each .glb, a .ulmat file (JSON) gives every material zone of the piece its surface. The scan textures live in Textures/Scans/<id>/ (diffuse.jpg, normal.jpg, rough.jpg) in any mod, and are projected onto the piece, so the mesh needs no UVs. Edit a .ulmat to retint or refinish a piece without touching its mesh.
json{ "zones": {
"Wood": { "scan": "walnut_veneer", "tint": [0.62, 0.45, 0.30], "scale": 1.0,
"rough": 1.0, "metal": 0.0, "coat": 1.0, "coat_rough": 0.16 } } }
| Field | Meaning |
|---|---|
scan |
Scan folder id, or null for a plain colour |
grey |
true uses the scan's colourless twin (diffuse_grey.jpg), so tint alone gives the colour |
tint |
RGB multiplied over the scan, or the colour itself when there is no scan |
scale |
Tiles per metre |
rough |
Multiplies the scan's roughness (1 = as scanned), or the roughness itself for a plain zone |
metal |
Metallic, 0 to 1 |
coat, coat_rough |
A varnish, lacquer or glaze film over the surface: its weight (0 to 1) and its own roughness. A veneer table is 1.0 / 0.16; leather 0.4 / 0.38. Leave them out for bare fabric, plaster or raw wood |
emissive |
Glow strength, in the tint's colour (lamp shades, screens) |
alpha |
Below 1 keeps the .glb's own see-through material (glass) |
rails |
Top level, beside zones. The piece's hanging rails, as in catalog.ulset: [[z, x0, y0, x1, y1, drop]] in cm, where z is the top of the bar, (x0, y0)–(x1, y1) are its ends and drop is the free height under it. Add an entry to let the game hang clothes on a bar of your own furniture |
grips |
Top level, beside zones. Where a hand takes the piece when a person picks it up: [{"name": "Main", "kind": "power", "hand": "any", "pos_cm": [3.8, 0, 10], "normal": [1, 0, 0], "fingers": [0, -1, 0]}]. pos_cm is the point the palm rests on, normal the surface normal out of the object, fingers where the straight fingers point, kind how they close (power, pinch, tripod, key, hook, platform, spherical, trigger, two_hand, from the .ulgrips files). Without one, the game guesses the grip from the piece's size. Details under Touches, Grips |
rig |
Top level, beside zones. A bendable piece's skeletal twin: {"file": "<piece>__rig", "bones": 14, "clips": {"Hang": 1.0, "Crack": 1.0}}. file is a .glb beside the static (a skin plus one glTF animation per clip name); the game plays Hang on pick-up and the rest through Actor:heldClip. Details under Touches, Rigged things |
points |
Top level, beside zones, not per zone. Named restraint points where a person's wrist, ankle or neck is tied to the piece: [{"name": "WristL", "kind": "ring", "pos_cm": [48.8, 5.5, 178.6]}]. kind is ring (tie-off ring), post (bed post or leg to tie round), hook (overhead hook) or bar (a rail or board a cuff clips onto). pos_cm is in centimetres from the piece's origin (floor centre), x right, y back, z up, front toward −y. Left and right are the tied person's own. Add or move entries to make your own furniture restrainable |
At the top level, beside zones, "ao": true says the .glb carries contact shading in its vertex colour (COLOR_0, grey: 1 = open, darker = in a joint or under a seat). The game multiplies it into every zone of the piece. Leave it out when your mesh has no vertex colour, or the piece renders black.
Packing a new set (writing its catalog.ulset and mining assemblies.ulset from a .glb) is done with tools that are not distributed yet. You can already reuse and recombine every shipped set in your own recipes and grammars; new furniture packs will follow once the packing tool is released.
Printed surfaces: menus, posters, labels
Picture frames, posters, menu boards, signs, cans, bottles, books and boxes have a surface the game prints an image on (the piece's material zone is named Canvas). Each piece placed in a room gets its own image, picked from Rooms/Canvas/:
textRooms/Canvas/
├── canvas.ulcanvas -- which kind of image each piece gets
├── menu/cafe/*.jpg -- <kind>/<venue>/: images for one venue type
├── menu/any/*.jpg -- <kind>/any/: images for every venue
├── poster/any/ …
└── art/home/ …
canvas.ulcanvas is JSON:
json{
"rules": [
{ "match": "Board_Menu|Chalkboard|A_Board", "kind": "menu" },
{ "match": "Picture_Frame|Picture_Canvas", "kind": "art" }
],
"venues": { "coffee": "cafe", "pub": "bar" }
}
rules: the first rule whosematchis found in the piece's name gives the kind. Alternatives are separated by|, and*matches anything. A mod's rules are tried before the base game's.- Venue: the room's tag (
cafe,bar,home…), renamed throughvenuesso several tags can share one folder. - Choice: the venue's own images count twice as much as those in
any/. Among a few candidates, the image whose width / height is closest to the surface's wins, so make wide images for signs and wide labels for cans (about 2.4 : 1). - Images are
.jpgor.png. To add yours, drop them into a folder in your mod with the same layout; there is nothing to register.
The kinds the base game uses: menu, poster, sign, art, graffiti (the painted wall pieces, WallDecor/Graffiti; the base images are spray tags, drop your murals in graffiti/any/ or graffiti/<venue>/), certificate, print (books, magazines, newspapers) and one label/<product> per kind of product: label/tin, label/drink_can, label/beer, label/wine, label/spirit, label/soft, label/sauce, label/cosmetic, label/pharma, label/cleaning, label/cup, label/takeaway, label/snack, label/grocery, label/tobacco, label/shopbag, label/money, label/card, label/casino, label/shop, label/street, label/machine (payphone and token-machine panels), label/audio (amplifier and stereo badges) and label/misc; and calendar (wall and desk calendar pages). A kind can be a path, so its images go in label/tin/any/ and so on. Labels on cans, tins, jars and bottles are unrolled: the image goes all the way round, so make them wide (2.4 : 1 for a can, 3 : 1 for a tin).
In Underlives' world, the brands on labels and ads belong to Underlives and Broken Human Studio. Keep real brands out of your images.
Layouts and presets
The room editor saves an exact arrangement of furniture as a .ulroomlayout (JSON):
json{
"tag": "clothes",
"grammar": "shop_fashion",
"name": "Clothes Shop",
"seed": 7,
"placements": [
{ "set": "ClothingBoutique", "id": "A013", "x": -271, "y": 167.8, "yaw": 0, "zone": "racks" },
{ "set": "ClothingBoutique", "id": "A016", "x": 272.5, "y": 224.5, "yaw": 0, "zone": "counter" }
]
}
- A layout in a mod, under
Rooms/Layouts/, is a shared preset for venues with thattag. It only needs the sets it references to be installed. seed(optional) pins the room itself: when the preset is forced (-RoomLayout=,cine.room layout=,Game:roomLayout) and no seed is given, the walls, windows, doors and style sets are rolled from this seed, so the room is the same every time. Without it only the furniture is pinned and the shell is rolled again at every visit.cine.saveroomwrites the seed of the room it saw.- Layouts the player edits in their own game (build mode) are saved per building in the save slot, so two lives do not share one furnished home. Games saved before October 2026 copy the old
Saved/RoomLayouts/files into the slot the first time they are loaded. Outside a game (the room editor) layouts are still files underSaved/RoomLayouts/. - Layouts downloaded from the content hub land in
Mods/lp_hub_downloads/Rooms/Layouts/. - A venue the player has changed (a thing taken, put down, moved or bought, or its shop's stock sold) keeps its furniture from then on. A building's room comes out of its seed, but the furniture also avoids what the rest of the city showed lately, so a building nobody touched can look different on the next visit. At the first change the save slot stores that venue's layout (every room of it, plus its style set), and later visits replay it instead of solving again; the dressing and the player's changes then go on top as usual. Untouched venues keep their variety and cost nothing in the save. If a mod you removed supplied a piece of a stored layout, that piece is left out and the rest of the room stays; if a room's grammar changed, that room is generated again.
Build mode and bought furniture
In a game, the player furnishes their home in build mode. Its catalog has three tabs: PIECES and SETS (the room's whole library) and OWNED, the furniture the player bought at a furniture shop, with how many of each they have.
- Placing an owned piece uses one up; taking it out of the room again (delete, undo, or cancelling the placement) puts it back in the inventory.
- A piece placed from OWNED is marked in the saved layout (its
zoneis"owned"), so the game knows which pieces were bought: the furniture that came with the home can be moved but never turns into something to sell. - OWNED also lists the small things the player carries that have a room piece (a
.ulitemwithPiece:, such as a table lamp or a radio bought off a shop's shelf), grouped by the piece's category and type, with how many they have. Picking one makes it follow the cursor: it stands on the top or a shelf of the furniture under it (the measured levels the dressing uses) or on the floor, and a click puts it there and takes one out of the inventory. These are not layout pieces: they are saved with the room like anything put down, in the save slot. A click on one the player put down picks it back up into the inventory and puts it on the cursor again; Escape or a right click keeps it in the inventory. - The standalone room editor has no player and no OWNED tab.
Today a shared preset is applied to every building of its tag, not to a share of them. Ship a preset only for a tag you mean to fix to that one arrangement.
Checklist for a new venue type
- Add the tag to
Map/amenities.json(Map and venues). - Write
Rooms/<tag>.ulroompointing at existing sets and a grammar (reuse one of the 47 shipped grammars (hospital_reception,ward,deliveryandermake up the generated hospital), or write your own inRooms/Grammars/). - Give the venue something to do: actions with the tag in
WHERE(Actions). - Optionally a look profile for its lighting and colours.
- Optionally a crowd profile so the strangers in it are the venue's own people, not anyone.