Simulate · Spaces

Spaces.

The inside of the world. Tera is a city from above; a Space is a room you are in. An Office is one building's interior and a Space is a room within it, and both are described by a data pack the engine renders without being told what any of it means.

An office is data

An Office is strictly JSON-serialisable — no functions, no classes, no THREE types, no Date — so a pack hand-written as a .ts module and a pack arriving as a .json body over HTTP are literally the same object. Offices are authored in metres, 1 unit = 1 m. src/offices/lumbridge-hq.ts is the reference pack and the file a self-hoster copies: one level, 15 rooms, 28 walls, about 300 props and 76 seats on a 34 x 18 m plate, with 2.8 m ceilings, 0.12 m partitions against 0.25 m exterior walls, 0.9 m doors and 1.8 m corridors. It carries CC0-1.0 in its own metadata, so the plan and the seat ids can be taken whole. The plan resolver drops rather than throws: a self-crossing polygon or a duplicate id is reported through plan.problems and the rest of the pack still opens.

Walls are an explicit list with 1-D openings

A room is a floor slab and implies no walls; walls are a separate explicit list, and the two are never derived from each other. Deriving walls from shared room edges needs float-equality to decide whether two rooms touch. Doors and windows are openings punched in a wall — an interval along the wall, metres from its own end and a width — never placed objects, because shipping both a door prop and a door-shaped hole leaves the collider with no gap where the door is. One pass splits every wall around its openings and emits both the solid runs the renderer draws and the collision segments a walker would use. A hole counts as a way through only if its sill is on the floor and its head clears 1.1 m, so a window at a 0.9 m sill is drawn and still blocks.

Presence binds to a seat id, never a coordinate

A seat is an address: a stable name for a spot on the floor, with a facing and a pose. It is not a chair and not a person. A Presence carries a seatId and no position, and never appears in an office pack at all. That split is what lets the geometry be public and the occupancy not — the pack knows where eng-04 is, a private API behind authentication knows who is sitting in it, and publishing the building and publishing the people stay two separate acts. A presence whose seat is not in the plan is dropped rather than placed.

Two scenes, one renderer

The city and an office cannot share a THREE.Scene: San Francisco puts one scene unit at about 94 m with 3.6x vertical exaggeration and clips at 900, while an office is 1 unit = 1 m with a 0.05 near plane. So there are two scenes and one renderer, and stepping inside pauses the city rather than disposing it, because rebuilding the Bay's heightfield on the way back out costs about a second. An office gets no atmosphere and runs a fixed interior light rig.

Packs are served by the optional API at GET /api/v1/offices/:id, one file per office in TERA_OFFICES_DIR, with no database and no build step. A pack that does not declare its visibility is read as private, and a private office answers 404 rather than 403, identically to an office that does not exist — a 403 is an existence oracle. Not built: walk mode, which has its collision data and not its controller, so you look into a Space from outside it; LiveKit presence, which has a type and no occupancy source; and daylight through the windows.

Read about Lumbridge Simulate