/**
 * Mateo Court — a courtyard block in the Arts District, Los Angeles.
 *
 * The third office pack, and the first one on the Southern California board.
 * `lumbridge-hq.ts` is a flat: twelve metres by nine, four rooms, one compact
 * live/work studio 188 m up a tower, where the circulation is the width of the
 * room you are already in. `frontier-valley.ts` is a shed: one room fifty-four
 * metres across with the meeting boxes standing in it like furniture. Both are
 * buildings whose circulation is **indoors**, and both are on the Bay Area
 * board.
 *
 * This is neither, and the difference is one hole. Take a brick block that fills
 * its lot, lift the roof off the middle of it, and the space you are left with
 * in the centre is not a room — it is outside. Every door in the building then
 * opens onto it, and the corridor is a courtyard. That is a third kind of plan
 * rather than a third decoration on the first two: there is no corridor here at
 * all, and there is no single volume either. There is a ring of rooms and a hole.
 *
 * ### Why here, and why this is not a conceit
 *
 * The courtyard block is what Los Angeles built before it had air conditioning,
 * and it kept building them afterwards because the climate never stopped paying
 * for them. A plan whose circulation is open to the sky is a plan that only
 * works where the weather lets you walk to a meeting without a coat and where
 * the rain, when it comes, comes in a season rather than on a Tuesday. Putting
 * one in downtown Los Angeles is not a theme; it is the local answer to the
 * question every other pack in this directory answers with a corridor.
 *
 * The Arts District is where that block would be a workplace rather than
 * housing. It is single- and double-storey brick warehouse east of Alameda,
 * built to be loaded off railway sidings and now full of people who need a large
 * cheap room and somewhere to put a table saw. `socal.ts` files it as
 * `arts-district`, 12 to 48 m, "industrial" palette — a district of low masonry
 * boxes, which is what this is one of.
 *
 * ### What the format has not been asked to do before, and how it went
 *
 * Three things, and only one of them needed any care.
 *
 * A **room with no ceiling that genuinely has no ceiling.** `ceiling: null` is
 * used all over the reference pack to take the lid off for an establishing
 * shot, and once — the commons — to mean a real double-height void. `court`
 * here means it in a third sense: there is nothing above it at any height,
 * because it is the sky. Nothing in the format had to change; the field already
 * said what was needed.
 *
 * An **upper walkway open on one side.** The loggia is a level, for exactly the
 * reason `frontier-valley.ts` records at length about its mezzanine: a `Room`
 * carries a floor finish and no height, so a deck authored as a room lies on the
 * ground with its furniture floating over it. Its open edge is a `Wall` at
 * 1.1 m, because `Plan` derives the walk collider from the wall list and a
 * balustrade authored as a prop is a balcony you can walk off.
 *
 * The **stair**, which is the one thing that had to be argued about. It stands
 * in the courtyard, outdoors, which is the type's signature move. The `stair`
 * room is still a floor finish the shape of the flight — the slab the dog-leg
 * stands on and the pad at the bottom of it — and the treads on top of it are
 * built by `interiors/shell.ts` from the `Transition` at the foot of this file
 * rather than authored as a prop, so the drawn flight and the walked one are
 * the same list of points.
 *
 * ### You can walk up it now, and that is what level 2 was waiting for
 *
 * This file used to carry a warning here saying that **level 2 was unreachable
 * on foot** and that modelling the stair better would not change it, because
 * `interiors/walker.ts` refused any state on another storey. That is no longer
 * true: `levelId` is walker state, `TRANSITIONS` at the bottom of this file says
 * where the way up is, and `interiors/officeWalker.ts` runs the climb as
 * presentation. The Model Loft, the Model Bay and the Materials Room are places
 * you can stand in.
 *
 * The five viewpoints that frame the upper floor are still there and still
 * worth having — a camera reaches a room faster than a pair of legs — but they
 * are no longer the only way up.
 *
 * ### Why the prop list is long, and why it is long in *kinds*
 *
 * This building carries three hundred and sixty-two non-light props over
 * twelve hundred square metres — 0.29 per m², which is past the reference
 * pack's 0.28 — and the number is only half the story. The half that matters is
 * that `court` went from six distinct kinds to thirteen, `works` from ten to
 * nineteen and `shop` from five to eleven.
 *
 * `furnish.ts` batches props by **(asset, colorKey)** and draws `ctx.rand` once
 * per batch, so every instance of a kind is identical down to its seeded
 * jitter. A room does not get fuller when it gets more of what it already has;
 * it gets the same objects again, and it passes a density gate looking exactly
 * as empty as before. That is why the fix for a hundred-and-forty-square-metre
 * courtyard with thirteen props in it was twelve new assets in
 * `src/assets/office/studio.ts` — troughs, benches, parasols, lab benches,
 * racks, carts, docks, cases, softboxes, a camera, acoustic panels and slat
 * screens — rather than forty more trees. `src/test/packs/mateoContent.test.ts`
 * asserts both numbers, and the per-kind one is the one worth defending.
 *
 * ### The coordinate frame
 *
 * Metres, `1 unit = 1 m`, floor on the XZ plane with +Y up, origin at the corner
 * this file calls north-west. +X east, +Z south, so in plan view +Z runs down
 * the page and the street façade along `z = 0` is at the top.
 *
 * As in both other packs the compass words are a reading convenience — but here
 * they are also a claim, because `site.heading` is 36 and the sun comes in
 * accordingly. See the note on `MATEO_COURT_SITE` below, which is where the
 * consequences are worked through, and `interiors/types.ts` on `OfficeSite`.
 */

import {
  CANONICAL_CAPABILITIES,
  type DeviceCapability,
  type DeviceDeclaration,
} from "../devices/types.ts";
import type {
  AssetId,
  DeskBank,
  Level,
  Office,
  Opening,
  Outline,
  Point2,
  Prop,
  Room,
  Seat,
  Transition,
  Viewpoint,
  Wall,
  Yaw,
  Zone,
} from "../interiors/types.ts";
import { MATEO_COURT_SITE } from "./sites.ts";

// ---- Where it stands ------------------------------------------------------


// ---- The block ------------------------------------------------------------

/**
 * The lot, and the hole in it. Everything else in this file is measured off
 * these six numbers, so a self-hoster with a different block starts here.
 *
 * 36 x 26 m is a small downtown lot: 936 m², which is a little more ground than
 * the reference pack's whole ground floor covers. The courtyard takes 155 of it
 * and leaves 781 m² of building, in a ring never more than 9.6 m deep. That
 * depth is the whole reason the type works: every room in this building has a
 * wall on the street or on the yard, so every room in this building has daylight
 * from one side and a door on the other. A deeper ring would need a corridor to reach the middle of it, and
 * a courtyard block with a corridor in it has stopped being a courtyard block.
 */
const WIDTH = 36.0;
const DEPTH = 26.0;

const COURT_W = 9.6;
const COURT_E = 26.4;
const COURT_N = 8.4;
const COURT_S = 17.6;

/**
 * Ground floor to underside of the timber above: 4.4 m.
 *
 * A commercial storey is 2.8 (`lumbridge-hq`) and a hangar is 9
 * (`frontier-valley`). A brick-and-timber warehouse is neither, and the number
 * is what makes it read as one: high enough that the windows are tall and the
 * room takes light a long way back from them, low enough that it is obviously a
 * floor of a building rather than a volume. The upper storey is a metre shorter
 * for the plainest possible reason — the good height went downstairs, where the
 * goods were.
 */
const GROUND = 4.4;
const UPPER = 3.4;

/**
 * The floor structure between them, and therefore floor-to-floor.
 *
 * 0.6 m of beams and deck. Unlike the reference pack's `PLENUM` this is not
 * exaggerated and is not trying to be a storey of its own: a heavy-timber floor
 * really is about this deep, and nothing is run through it. `Plan` adds `STOREY`
 * to every coordinate on level 2 exactly once, so the upper floor below is
 * authored with its own slab at zero.
 */
const FLOOR_STRUCTURE = 0.6;
const STOREY = GROUND + FLOOR_STRUCTURE;

/** The height of the loggia's open edge. A rail, and a wall — see `RAILS`. */
const RAIL = 1.1;

/**
 * Three thicknesses, and the middle one is the interesting one.
 *
 * `EXT` is the outer skin: 350 mm of load-bearing brick, which is what holds a
 * two-storey timber floor up and is nearly three times the reference pack's
 * partition. `YARD` is the courtyard skin at 250 — also an outside wall, also
 * weathering, but carrying nothing but itself. `INT` is a stud partition and is
 * the level's default, so the thirty-odd walls that do not say otherwise are it.
 */
const EXT_THICKNESS = 0.35;
const YARD_THICKNESS = 0.25;
const INT_THICKNESS = 0.12;

const EXT_FACE = EXT_THICKNESS / 2;
const YARD_FACE = YARD_THICKNESS / 2;
const INT_FACE = INT_THICKNESS / 2;

/**
 * How far the centre of a wall-hung panel sits off the wall's face.
 *
 * `tera:screen.wall-display` is 0.12 m deep and `tera:whiteboard` 0.10 m, and a
 * prop's origin is the centre of its footprint, so half the depth plus a
 * centimetre of reveal hangs one *on* a wall rather than half inside it.
 *
 * Neither gets an `elevation` anywhere in this file, and that is deliberate:
 * both assets are authored standing on the floor with their own mount height
 * built in — `mount: 0.9` for the whiteboard, `mount: 0.86` for the display
 * (`assets/office/surfaces.ts` and `screens.ts`). A pack that also supplies
 * `elevation: 1.0` has hung the board 1.9 m up.
 */
const DISPLAY_OFFSET = 0.07;
const BOARD_OFFSET = 0.06;

// The lines the ground floor is built on, west to east across the street range.
const PRESS_E = 9.6; // the press / the front office
const LOBBY_E = 16.6; // the front office / the paseo
const PASEO_E = 19.6; // the paseo / the mess
const MESS_E = 28.0; // the mess / Hewitt

// The west range is two rooms; the south range is the desk floor plus a store.
const WILLOW_S = 13.0;
const STORE_W = 31.0;

/**
 * The stair, which stands in the yard and is therefore a shape cut out of it.
 *
 * A dog-leg in two flights with a half landing at the east end: 5 m of rise in
 * a 5.4 x 2.6 m footprint, which is about fourteen risers a flight. It is
 * against the front office's yard wall because that is the one stretch of that
 * wall with nothing behind it — the reception counter is at the other end of the
 * room — and because a stair in the middle of a courtyard is a stair you walk
 * round all day.
 *
 * The second flight comes back west and arrives at the loggia's edge, which is
 * why the gap in the balustrade is where it is and why it is 1.2 m wide.
 */
const STAIR_E = 15.0;
const STAIR_S = 11.0;
const STAIR_HEAD_N = 9.8;

// -- The upper floor --------------------------------------------------------
//
// Level 2 covers the street range and the west range and stops: the east and
// south ranges are single storey with the roof straight onto them. A block like
// this grew in pieces and never had one date, and a second floor everywhere
// would have doubled the pack without adding a room worth looking at.
const LOGGIA_W = 7.2; // the upper rooms / the walkway
const LOGGIA_N = 6.0; // the loft / the walkway
const LOFT_E = COURT_E; // the two-storey part stops on the courtyard's east line
const JESSE_N = 11.8; // Palmetto / Jesse

// ---- Yaw ------------------------------------------------------------------

/**
 * The four right angles, named for the direction a prop's *front* points.
 *
 * The rule they are read with is the one the reference pack states: a prop takes
 * the yaw of the wall it backs onto, and a seat takes the yaw of the direction
 * the occupant looks. Zero faces −Z and the angle increases counter-clockwise
 * from above, which is exactly `object.rotation.y` with nothing converting it.
 */
const NORTH: Yaw = 0;
const EAST: Yaw = -Math.PI / 2;
const SOUTH: Yaw = Math.PI;
const WEST: Yaw = Math.PI / 2;

// ---- Assets ---------------------------------------------------------------

/**
 * Every asset this pack places. All thirty-eight ids are `tera:` ids registered
 * in `src/assets/office/`; an id that resolves to nothing gets a placeholder box
 * rather than an error, which is exactly why they are bound to constants here
 * where a typo is visible once instead of in forty prop literals.
 *
 * The habitat six arrived together and for two different reasons. `SOFA`,
 * `FLOOR_LIGHT`, `BED` and `WARDROBE` are the rest of the habitat kit — four
 * assets that were written, tested and then placed by nobody, in a building
 * whose every fitting was a ceiling troffer or a pendant. `MIC` and `SPEAKER`
 * are device hardware, and they are the only two ids here that a
 * `DeviceDeclaration` is allowed to name: `deviceKindOfAssetId` reads the kind
 * out of `<ns>:device.<kind>.<placement>`, so the id is load-bearing rather than
 * decorative.
 *
 * ### The studio twelve, and why this list grew instead of the prop list
 *
 * The block below — `TROUGH` through `DIVIDER` — is `src/assets/office/studio.ts`,
 * and it is the whole reason this building stopped being thin. `furnish.ts`
 * batches props by **(asset, colorKey)** and draws `ctx.rand` once per batch, so
 * every instance of a kind is geometrically identical down to its seeded jitter.
 * A room with thirteen props of four kinds does not get fuller when it has
 * twenty-six props of four kinds; it gets the same four objects twice each, and
 * it passes a density gate while looking exactly as empty as it did. **Apparent
 * density is a function of distinct kinds**, which is why the honest fix for a
 * bare courtyard was twelve new assets rather than forty more trees.
 *
 * ### The one convention you have to hold to place any of them
 *
 * Every asset in this library puts its **back at local −Z and its user at local
 * +Z** — a whiteboard's writing face, a bench's seat front, a lab bench's
 * drawers, a dock's parking bay, a softbox's diffuser. `Prop.rotation` is that
 * −Z direction, so the rule for the author is short: **`rotation` names the wall
 * the thing backs onto.** `rotation: NORTH` means "its back is to the north", so
 * a trough at `rotation: NORTH` sits under the street window and a softbox at
 * `rotation: SOUTH` lights something to the north of it. The two exceptions
 * prove it rather than break it: a task chair at `rotation: EAST` puts the
 * sitter's back west and their face east, and `tera:camera.tripod` points its
 * lens along −Z, so a camera "backs onto" whatever it is filming.
 */
const DESK: AssetId = "tera:desk.workstation";
const PEDESTAL: AssetId = "tera:desk.pedestal";
const PARTITION: AssetId = "tera:desk.partition";
const TASK_CHAIR: AssetId = "tera:seat.task-chair";
const LOUNGE_CHAIR: AssetId = "tera:seat.lounge";
const STOOL: AssetId = "tera:seat.stool";
const MEETING_TABLE: AssetId = "tera:table.meeting";
const SIDE_TABLE: AssetId = "tera:table.side";
const KITCHEN: AssetId = "tera:kitchen.run";
const ISLAND: AssetId = "tera:kitchen.island";
const SHELF: AssetId = "tera:storage.shelf";
const LOCKER: AssetId = "tera:storage.locker";
const MONITOR: AssetId = "tera:screen.monitor";
const DISPLAY: AssetId = "tera:screen.wall-display";
const PLANT: AssetId = "tera:plant.potted";
const TREE: AssetId = "tera:plant.tall";
const PENDANT: AssetId = "tera:light.pendant";
const TROFFER: AssetId = "tera:light.troffer";
const RUG: AssetId = "tera:rug";
const WHITEBOARD: AssetId = "tera:whiteboard";
const SOFA: AssetId = "tera:sofa.modular";
const FLOOR_LIGHT: AssetId = "tera:light.floor";
const BED: AssetId = "tera:bed.platform";
const WARDROBE: AssetId = "tera:storage.wardrobe";
const MIC: AssetId = "tera:device.mic.desk";
const SPEAKER: AssetId = "tera:device.speaker.desk";

// The studio twelve. Grouped the way `studio.ts` groups them — courtyard, lab,
// loft, wall fittings — because that is the order this building uses them in.
const TROUGH: AssetId = "tera:planter.trough";
const BENCH: AssetId = "tera:bench.slat";
const PARASOL: AssetId = "tera:canopy.parasol";
const LAB_BENCH: AssetId = "tera:bench.lab";
const RACK: AssetId = "tera:rack.equipment";
const TOOL_CART: AssetId = "tera:cart.tool";
const DOCK: AssetId = "tera:dock.robot";
const CASES: AssetId = "tera:case.stack";
const SOFTBOX: AssetId = "tera:light.softbox";
const TRIPOD: AssetId = "tera:camera.tripod";
const BAFFLE: AssetId = "tera:acoustic.baffle";
const DIVIDER: AssetId = "tera:divider.slat";

/**
 * Surfaces, and one of them is a compromise worth naming.
 *
 * `MaterialRegistry.resolve` throws the namespace away, reads the first
 * dot-segment as a role through a small alias table, and **falls back silently**
 * when it recognises neither. So an id is only worth writing if it lands
 * somewhere: `tera:concrete.polished` is the `polishedConcrete` role,
 * `tera:tile.paver` is `tile`, `tera:steel.panel` is `metalTrim`.
 *
 * `BRICK` is the compromise. There is no brick in the closed `SurfaceRole` union
 * in `assets/materials.ts` and a pack cannot add one — the union is a
 * three-line change *in that file* — so `tera:brick.common` would have resolved
 * to whatever the caller's fallback happened to be, which is the worst of both
 * outcomes because it renders and it renders wrong. The id below names the
 * material a reader is looking at and resolves to `plasterAccent`, whose
 * plaster texture at 0.92 roughness is the closest thing in the list to a
 * hundred-year-old common brick. `tera:tile.brick` was the other candidate — a
 * grid texture is a better match for coursing — and was rejected because the
 * `tile` role is 0.3 roughness, and glazed brick is a different building.
 */
const BRICK = "tera:plasterAccent.brick";
const CONCRETE = "tera:concrete.polished";
const WOOD = "tera:wood.plank";
const CARPET = "tera:carpet.loop";
const CARPET_ACCENT = "tera:carpetAccent.broadloom";
const TERRAZZO = "tera:tile.terrazzo";
const PAVER = "tera:tile.paver";
const PAINT = "tera:paint.matt";
const GLASS = "tera:glass.curtain";
const STEEL = "tera:steel.panel";
const CEILING_TILE = "tera:ceiling.tile";

// ---- Authoring helpers ----------------------------------------------------

/*
 * Four functions. They run at module load and return plain objects, so the
 * exported `Office` is still what `interiors/types.ts` demands — data that would
 * survive `JSON.parse(JSON.stringify(…))` unchanged — and the file is still
 * something a person can edit twice.
 */

/**
 * An axis-aligned outline, wound counter-clockwise **in plan view**: NW → SW →
 * SE → NE, which has a negative shoelace area over (x, z) because +Z runs down
 * the page. `Plan` re-winds a reversed polygon silently, so this is a courtesy
 * to the reader rather than a trap for the author.
 */
function rect(x0: number, z0: number, x1: number, z1: number): Outline {
  return [
    { x: x0, z: z0 },
    { x: x0, z: z1 },
    { x: x1, z: z1 },
    { x: x1, z: z0 },
  ];
}

/** A regular grid of points, columns first. `rows: 1` gives a line. */
function grid(
  x0: number,
  z0: number,
  columns: number,
  rows: number,
  dx: number,
  dz: number,
): Point2[] {
  const points: Point2[] = [];
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < columns; c++) points.push({ x: x0 + c * dx, z: z0 + r * dz });
  }
  return points;
}

/**
 * A point a scattered prop stands on, optionally carrying the seat it belongs
 * to.
 *
 * The seat is per point rather than per call because that is how seats work: a
 * row of four identical chairs at a table is one `scatter`, and each of the four
 * is pulled up to a *different* address. Before this field existed the helper
 * could not express that at all, so every chair in this building that somebody
 * might sit at had no seat binding and the six hand-authored addresses in the
 * commons and the review room had no furniture — which reads perfectly in a
 * screenshot and leaves an occupancy layer nothing to dim.
 */
type ScatterPoint = Point2 & { seat?: string };

/**
 * One asset at each of `points`, with ids `${prefix}-01`, `-02`, … — the same
 * two-digit scheme `DeskBank` expansion uses, so a prop id is something you can
 * work out on paper rather than by running the code.
 *
 * Every optional field is **spread rather than assigned**, and that is not a
 * style preference: `elevation: undefined` is a different object from one with
 * no `elevation` key, `JSON.stringify` drops the key, and CONTRACT.md §2 says a
 * pack in the bundle and the same pack over the wire have to be literally the
 * same thing. `src/test/packs/packRegression.test.ts` asserts the round trip,
 * which is how the two hundred and eighty-three undefined-valued keys this
 * helper used to emit were found.
 */
function scatter(
  prefix: string,
  kind: AssetId,
  points: ScatterPoint[],
  opts: { rotation?: Yaw; elevation?: number; colorKey?: string } = {},
): Prop[] {
  return points.map((point, i) => ({
    id: `${prefix}-${String(i + 1).padStart(2, "0")}`,
    kind,
    position: { x: point.x, z: point.z },
    rotation: opts.rotation ?? NORTH,
    ...(opts.elevation === undefined ? {} : { elevation: opts.elevation }),
    ...(opts.colorKey === undefined ? {} : { colorKey: opts.colorKey }),
    ...(point.seat === undefined ? {} : { seat: point.seat }),
  }));
}

/** A doorway: `sill: 0` and enough head to walk through, which is what makes it
 * a gap in the collision segments as well as a hole in the wall. */
function doorway(start: number, width = 0.9, head = 2.1): Opening {
  return { kind: "door", start, width, sill: 0, head };
}

/** A window. The sill is the whole of the difference between a window and a
 * doorway you did not mean to author. */
function pane(start: number, width: number, sill = 1.0, head = 3.4): Opening {
  return { kind: "window", start, width, sill, head };
}

/** A cased opening with no leaf in it. */
function archway(start: number, width: number, head = 2.4): Opening {
  return { kind: "arch", start, width, sill: 0, head };
}

// ---- Rooms ----------------------------------------------------------------

/**
 * Twelve slabs, and the shape of the building is which of them touch the yard.
 *
 * Ten rooms and a passage sit in the ring; the eleventh thing on this level is
 * the yard itself and the twelfth is the stair standing in it. Nine of the ten
 * rooms open onto the yard directly and the tenth — the press — opens off the
 * front office, so the longest route in this building from the street to any
 * room is three doors.
 *
 * Nothing overlaps. The yard is notched around the stair rather than passing
 * underneath it, for the reason the reference pack notches its open floor around
 * the booths: two coplanar slabs at the same height is a z-fight waiting for the
 * wrong GPU, and `Plan.roomAt` resolving the later one is not a reason to stack
 * them.
 *
 * `ceiling: null` on nearly everything is the reference pack's convenience — a
 * block you look down into cannot have lids on the rooms you are looking into —
 * with two exceptions in opposite directions. The store keeps its ceiling
 * because it is the one room here nobody is meant to see inside. The yard and
 * the stair have `ceiling: null` because they are **outside**, and that is the
 * one place in this pack where the field means what it literally says.
 */
const ROOMS: Room[] = [
  // -- The street range, west to east ---------------------------------------
  {
    id: "press",
    name: "Media Studio",
    // The messy end, on the corner, with a concrete floor and its own door out
    // of the front office wide enough to carry a sheet of ply through.
    outline: rect(0, 0, PRESS_E, COURT_N),
    floor: CONCRETE,
    ceiling: null,
  },
  {
    id: "lobby",
    name: "Front Door",
    // Reception, and the only room in the building with a door on the paseo and
    // a door on the yard. Somebody arriving is met here and then walked out
    // into the middle of the block, which is the introduction the plan wants to
    // make.
    outline: rect(PRESS_E, 0, LOBBY_E, COURT_N),
    floor: WOOD,
    ceiling: null,
  },
  {
    id: "paseo",
    name: "Entry Passage",
    // A passage cut straight through the street range: three metres wide and
    // eight and a half from the pavement to the yard, counting the brick at the
    // street end. It is the only way into this building that is not a goods
    // door, and it is the oldest device in this kind of plan — the reason the
    // front door of the building is not the front door of the office.
    outline: rect(LOBBY_E, 0, PASEO_E, COURT_N),
    floor: CONCRETE,
    ceiling: null,
  },
  {
    id: "mess",
    name: "Kitchen · Commons",
    outline: rect(PASEO_E, 0, MESS_E, COURT_N),
    floor: TERRAZZO,
    ceiling: null,
  },
  {
    id: "hewitt",
    name: "Review Room",
    // The big meeting room, in the corner where two walls have no openings at
    // all — the party wall east and the range wall south. The meeting rooms in
    // this pack take their names from the streets of the district, the way the
    // reference pack's take theirs from places in the bay.
    outline: rect(MESS_E, 0, WIDTH, COURT_N),
    floor: CARPET,
    ceiling: null,
  },

  // -- The west range -------------------------------------------------------
  {
    id: "willow",
    name: "Project Room",
    outline: rect(0, COURT_N, COURT_W, WILLOW_S),
    floor: CARPET_ACCENT,
    ceiling: null,
  },
  {
    id: "violet",
    name: "Quiet Room",
    // The quiet room. No table you can put a laptop on, on purpose.
    outline: rect(0, WILLOW_S, COURT_W, COURT_S),
    floor: CARPET_ACCENT,
    ceiling: null,
  },

  // -- The east range -------------------------------------------------------
  {
    id: "shop",
    name: "Robotics Lab",
    // Fabrication, with a four-metre roller shutter onto the yard. That shutter
    // is the reason the yard is paved rather than planted: work comes out of
    // this room and is finished in the middle of the building, which is a thing
    // a courtyard can do and a corridor cannot.
    outline: rect(COURT_E, COURT_N, WIDTH, COURT_S),
    floor: CONCRETE,
    ceiling: null,
  },

  // -- The south range ------------------------------------------------------
  {
    id: "works",
    name: "Agent Floor",
    // Thirty-one metres of desk floor along the back of the block, glazed onto
    // the yard on one side and onto the alley on the other. The longest room
    // here, and the only one that is more than one thing at once.
    outline: rect(0, COURT_S, STORE_W, DEPTH),
    floor: CARPET,
    ceiling: null,
  },
  {
    id: "store",
    name: "Equipment Storage",
    outline: rect(STORE_W, COURT_S, WIDTH, DEPTH),
    floor: CONCRETE,
    ceiling: { height: 3.0, surface: CEILING_TILE },
  },

  // -- The hole -------------------------------------------------------------
  /**
   * The yard: 16.8 by 9.2, open to the sky, and the circulation of the whole
   * building.
   *
   * Notched around the stair on its north-west corner. Wound the way `rect`
   * winds — down the west side, along the south, back up the east and west
   * along the north — with the notch taken out of the run that meets it.
   *
   * `ceiling: null` here is not the establishing-shot convenience it is
   * everywhere else in this file. There is no ceiling.
   */
  {
    id: "court",
    name: "Courtyard Yard",
    outline: [
      { x: COURT_W, z: STAIR_S },
      { x: COURT_W, z: COURT_S },
      { x: COURT_E, z: COURT_S },
      { x: COURT_E, z: COURT_N },
      { x: STAIR_E, z: COURT_N },
      { x: STAIR_E, z: STAIR_S },
    ],
    floor: PAVER,
    ceiling: null,
  },
  {
    id: "stair",
    name: "The Stair",
    // The slab the flight stands on, and the pad at the bottom of it. The treads
    // themselves are not a room and not a prop: `interiors/shell.ts` builds them
    // from the `Transition` at the foot of this file, so the flight that is
    // drawn is the flight the walk controller climbs. This is what is under it.
    outline: rect(COURT_W, COURT_N, STAIR_E, STAIR_S),
    floor: STEEL,
    ceiling: null,
  },
];

// ---- Walls ----------------------------------------------------------------

/**
 * Twenty segments, written west-to-east and north-to-south without exception,
 * because `Opening.start` is measured from a wall's `from` end and the day you
 * reverse one and forget is the day a door moves to the other end of a room.
 *
 * The east wall is the one worth reading first. It has **no openings at all**,
 * because it is a party wall: on a downtown lot the block next door is built
 * against you, and a window in that wall would look at somebody else's brick.
 * That single fact is what makes the yard necessary rather than decorative —
 * with one side blind and the ring 9.6 m deep, the middle of the block is the
 * only place the light for half the building can come from.
 */
const WALLS: Wall[] = [
  // -- The envelope ---------------------------------------------------------
  {
    id: "ext-north",
    // The street. Tall windows on brick piers, and the paseo arch punched
    // straight through the middle of them — 3 m wide and 3.6 m to the head,
    // which is a lorry-sized hole and is what the passage was cut for before it
    // was a way for people to walk in.
    from: { x: 0, z: 0 },
    to: { x: WIDTH, z: 0 },
    thickness: EXT_THICKNESS,
    surface: BRICK,
    openings: [
      pane(1.2, 3.2),
      pane(5.6, 3.2),
      pane(10.6, 4.4),
      archway(LOBBY_E, 3.0, 3.6),
      pane(20.6, 3.2),
      pane(24.4, 3.0),
      pane(29.0, 5.6),
    ],
  },
  {
    id: "ext-east",
    // The party wall. Solid for its whole 26 m, at both storeys.
    from: { x: WIDTH, z: 0 },
    to: { x: WIDTH, z: DEPTH },
    thickness: EXT_THICKNESS,
    surface: BRICK,
  },
  {
    id: "ext-south",
    // The alley, and the goods side. The shutter is a `door` rather than a
    // window for the reason that matters to the collider: `sill: 0` and 4 m of
    // head is a hole a walker can use, which is what an escape route is.
    from: { x: 0, z: DEPTH },
    to: { x: WIDTH, z: DEPTH },
    thickness: EXT_THICKNESS,
    surface: BRICK,
    openings: [
      pane(1.6, 5.2),
      pane(8.4, 5.2),
      pane(15.2, 5.2),
      { kind: "door", start: 22.4, width: 3.6, sill: 0, head: 4.0 },
      doorway(32.4),
    ],
  },
  {
    id: "ext-west",
    from: { x: 0, z: 0 },
    to: { x: 0, z: DEPTH },
    thickness: EXT_THICKNESS,
    surface: BRICK,
    openings: [pane(1.4, 3.4), pane(9.4, 2.6, 1.0, 2.8), pane(13.8, 2.6, 1.0, 2.8), pane(18.6, 5.2)],
  },

  // -- The courtyard skin ---------------------------------------------------
  //
  // Six walls and a gap. The gap is the paseo's mouth between x 16.6 and 19.6,
  // where the passage simply arrives in the yard: a wall belongs to no room, so
  // an opening in the ring is authored by not writing a wall rather than by
  // punching a hole in one.
  //
  // **Every door in this skin is 1.2 m and not 0.9**, and that is arithmetic
  // rather than taste. `Plan.blocked` inflates each solid run by
  // `radius + thickness / 2`, so a leaf in one of these 0.25 m walls loses
  // 2 x (0.3 + 0.125) = 0.85 m of its width to a 0.3 m walker: a 0.9 m door
  // leaves 0.05 m of clear width, which no flood fill will thread and which
  // seals the room behind it while looking completely fine in a screenshot.
  // Willow and Violet were sealed exactly that way on the first pass. At 1.2 m
  // the same sum leaves 0.35 m, and 1.2 m is a pair of leaves, which is what a
  // door onto a courtyard is anyway. The 0.9 m leaves that survive are mostly in
  // the 0.12 m partitions, where the loss is 0.72 and the clear width is 0.18 —
  // the same margin every door in the reference pack runs at. The exception is
  // the alley door into the store, which is 0.9 m through 0.35 m of brick and
  // was measured rather than assumed: `blocked` is false through it at a 0.3 m
  // radius, because it is the *sum* of the inflations that has to fit and this
  // one still does.
  {
    id: "yard-lobby",
    // The front office's yard wall, with the stair standing against it. The
    // window is high — sill 2.6 — because the bottom two and a half metres of
    // this wall have a flight of stairs in front of them, and the door is at the
    // far end of it, which puts reception's way out into the yard exactly at the
    // foot of the stair.
    from: { x: COURT_W, z: COURT_N },
    to: { x: LOBBY_E, z: COURT_N },
    thickness: YARD_THICKNESS,
    surface: BRICK,
    openings: [pane(1.0, 3.6, 2.6, 3.8), doorway(5.4, 1.2)],
  },
  {
    id: "yard-mess",
    from: { x: PASEO_E, z: COURT_N },
    to: { x: COURT_E, z: COURT_N },
    thickness: YARD_THICKNESS,
    surface: GLASS,
    // Wide enough to carry lunch through without turning sideways, which is the
    // only specification a kitchen door onto a courtyard has.
    openings: [archway(1.4, 2.4), pane(4.6, 1.8, 1.0, 2.6)],
  },
  {
    id: "front-willow",
    from: { x: COURT_W, z: COURT_N },
    to: { x: COURT_W, z: WILLOW_S },
    thickness: YARD_THICKNESS,
    surface: GLASS,
    // The door starts 2.6 m along, which is where the stair's footprint ends. A
    // metre further north and it would have opened into the second flight.
    openings: [doorway(2.6, 1.2), pane(3.9, 0.6, 1.0, 2.6)],
  },
  {
    id: "front-violet",
    from: { x: COURT_W, z: WILLOW_S },
    to: { x: COURT_W, z: COURT_S },
    thickness: YARD_THICKNESS,
    surface: BRICK,
    openings: [doorway(1.2, 1.2), pane(3.0, 1.4, 1.0, 2.6)],
  },
  {
    id: "yard-shop",
    from: { x: COURT_E, z: COURT_N },
    to: { x: COURT_E, z: COURT_S },
    thickness: YARD_THICKNESS,
    surface: STEEL,
    // The shutter, and a personnel door beside it so the shop is usable with the
    // shutter down. Both are ways through; the shutter is four metres of one.
    openings: [{ kind: "door", start: 2.6, width: 4.0, sill: 0, head: 4.0 }, doorway(7.4, 1.2)],
  },
  {
    id: "yard-works",
    from: { x: COURT_W, z: COURT_S },
    to: { x: COURT_E, z: COURT_S },
    thickness: YARD_THICKNESS,
    surface: GLASS,
    // A pair of leaves rather than a single: this is the door forty people use,
    // and the reference pack's entrance makes the same argument. Full-height
    // glazing either side of it, as a *wall with a glass surface* — an opening
    // here would hand the collider sixteen metres of gap and put the desk floor
    // in the yard.
    openings: [doorway(3.4, 1.8, 2.4), pane(7.4, 3.6, 1.0, 3.0), pane(12.4, 3.0, 1.0, 3.0)],
  },

  // -- Inside the ring ------------------------------------------------------
  {
    id: "paseo-west",
    from: { x: LOBBY_E, z: 0 },
    to: { x: LOBBY_E, z: COURT_N },
    openings: [doorway(4.2, 1.0, 2.4)],
  },
  {
    id: "paseo-east",
    from: { x: PASEO_E, z: 0 },
    to: { x: PASEO_E, z: COURT_N },
    openings: [doorway(4.6)],
  },
  {
    id: "press-east",
    from: { x: PRESS_E, z: 0 },
    to: { x: PRESS_E, z: COURT_N },
    // 1.6 m, because the things this room makes leave through it.
    openings: [doorway(3.4, 1.6, 2.4)],
  },
  {
    id: "press-south",
    from: { x: 0, z: COURT_N },
    to: { x: PRESS_E, z: COURT_N },
  },
  {
    id: "willow-violet",
    from: { x: 0, z: WILLOW_S },
    to: { x: COURT_W, z: WILLOW_S },
  },
  {
    id: "violet-works",
    from: { x: 0, z: COURT_S },
    to: { x: COURT_W, z: COURT_S },
  },
  {
    id: "mess-hewitt",
    from: { x: MESS_E, z: 0 },
    to: { x: MESS_E, z: COURT_N },
    openings: [doorway(4.4)],
  },
  {
    id: "range-north-east",
    // Between the street range and the east range, and deliberately solid: the
    // way from the mess to the shop is across the yard. In a building organised
    // round a courtyard, a shortcut that avoids the courtyard is a hole in the
    // idea rather than a convenience.
    from: { x: COURT_E, z: COURT_N },
    to: { x: WIDTH, z: COURT_N },
  },
  {
    id: "range-south-east",
    from: { x: COURT_E, z: COURT_S },
    to: { x: WIDTH, z: COURT_S },
    // The one internal door on the far side of the yard, because material goes
    // from the shop to the desks and 1.6 m of it goes on a trolley.
    openings: [doorway(1.6, 1.6, 2.4)],
  },
  {
    id: "store-west",
    from: { x: STORE_W, z: COURT_S },
    to: { x: STORE_W, z: DEPTH },
    openings: [doorway(3.0, 1.2, 2.4)],
  },
];

// ---- Desk banks -----------------------------------------------------------

/**
 * Sixteen desks in two declarations on the agent floor.
 *
 * `Plan` expands each into a desk prop, a chair prop and a seat per station,
 * with ids that are contractual: `works-a-01` … `works-a-12` counting along each
 * row and then down the rows, and `works-a-desk-01`, `works-a-chair-01` for the
 * furniture. A `Presence` binds to `works-a-04` and has no way to notice that
 * the bench moved 200 mm, which is the property the whole scheme exists for.
 *
 * The rest of the long south range stays open for robots, rolling prototypes
 * and review. Capacity is a plan decision here, not every square metre filled
 * with a generated workstation — which is why the second bench is four stations
 * and not another twelve.
 */
const DESK_BANKS: DeskBank[] = [
  {
    id: "works-a",
    desk: DESK,
    chair: TASK_CHAIR,
    origin: { x: 3.4, z: 19.2 },
    rotation: NORTH,
    columns: 6,
    rows: 2,
    pitch: 1.7, // 1.6 m desks with a 100 mm gap
    rowPitch: 0.85,
    facingRows: true,
    seatOffset: 0.6,
  },
  /**
   * A second, smaller bench at the east end, past the slat screens.
   *
   * Four stations rather than another twelve, and the difference is the point: a
   * two-hundred-and-sixty-square-metre floor with one bench down one end reads
   * as a room somebody stopped furnishing, and the same floor with sixty desks
   * in it reads as a call centre. Two benches with a screened gap between them
   * is a floor that has *bays*, which is what an open plan of this length
   * actually has.
   *
   * `works-b-01` … `works-b-04` are generated addresses, not hand-authored ones,
   * so they arrive with their desk and chair already bound — that is the whole
   * reason `DeskBank` exists and why the seat-binding assertion only has to
   * police the ten seats written by hand.
   */
  {
    id: "works-b",
    desk: DESK,
    chair: TASK_CHAIR,
    origin: { x: 18.0, z: 22.4 },
    rotation: NORTH,
    columns: 2,
    rows: 2,
    pitch: 1.7,
    rowPitch: 0.85,
    facingRows: true,
    seatOffset: 0.6,
  },
];

// ---- Seats ----------------------------------------------------------------

/**
 * Nine deliberate ground-level addresses outside the generated agent bench.
 * They describe useful activity points, not a fabricated headcount.
 *
 * A seat is an address and not a chair. The prefixes here (`front`, `mess`,
 * `hewitt`, `willow`, `violet`, `shop`, `press`, `yard`) are disjoint from both
 * shipped packs' on purpose: ids are unique per building by the format's rules,
 * but a deployment serving three buildings from one presence API would find out
 * the hard way, and staying disjoint costs nothing.
 */
const SEATS: Seat[] = [
  // One host position inside the front door.
  { id: "front-01", position: { x: 13.6, z: 5.0 }, facing: EAST, pose: "sit" },

  // Two standing capture/review marks in the media studio.
  { id: "media-01", position: { x: 3.8, z: 4.4 }, facing: NORTH, pose: "stand" },
  { id: "media-02", position: { x: 5.4, z: 4.4 }, facing: NORTH, pose: "stand" },

  // Two stools at the kitchen island.
  { id: "commons-01", position: { x: 22.6, z: 4.2 }, facing: NORTH, pose: "sit" },
  { id: "commons-02", position: { x: 23.5, z: 4.2 }, facing: NORTH, pose: "sit" },

  // Four places in the review room, all with a clean line to the stable screen.
  { id: "review-01", position: { x: 29.8, z: 3.2 }, facing: SOUTH, pose: "sit" },
  { id: "review-02", position: { x: 32.6, z: 3.2 }, facing: SOUTH, pose: "sit" },
  { id: "review-03", position: { x: 29.8, z: 5.2 }, facing: NORTH, pose: "sit" },
  { id: "review-04", position: { x: 32.6, z: 5.2 }, facing: NORTH, pose: "sit" },
];

// ---- Props ----------------------------------------------------------------

/**
 * Everything that is not a wall, a floor or a desk bank, grouped in the order
 * you walk through the building: in off the street, into reception, round the
 * yard clockwise, and out into the works.
 *
 * No monitors on the desks, for the reason the reference pack gives at length:
 * twenty-eight monitor props kept in step by hand with a bank somebody will move
 * next week is what `overrides:` exists to prevent. The one monitor in the
 * building belongs to a place rather than to a desk.
 *
 * ### Two sets of ids in here are addresses and must not be renamed
 *
 * **Media screens.** `server/src/media/bindings.ts` treats every
 * `tera:screen.monitor` and `tera:screen.wall-display` in a pack as a shareable
 * surface, and a hosted screen grant names one by `screenId`. So these seven
 * ids are pinned exactly as `lobby-monitor` is pinned in `lumbridge-hq.ts`:
 * `front-monitor`, `paseo-directory`, `hewitt-display`, `willow-display`,
 * `palmetto-display`, `works-display`, `loft-display`. Move the prop, rename the
 * room, re-light the building — but rename one of those and a grant that was
 * working yesterday silently stops resolving, with nothing on either side saying
 * why. A content pass adds screens; it does not renumber them.
 *
 * **Robot stations.** `operations/mateo-court.ts` anchors eight stations to
 * props by id — `store-shelf-02`, `works-locker-02`, `loft-locker-02`,
 * `jesse-shelf-01`, `jesse-board-02` and three of the screens above. Those
 * resolve through `Plan.prop()` and `resolveRobotOperations` **throws** on a
 * miss, unlike almost everything else here, so a renumbered `scatter` is a
 * loud failure rather than a quiet one. It is still a failure.
 */
const PROPS: Prop[] = [
  // -- The paseo ------------------------------------------------------------
  // Nearly empty, and it has to be: three metres wide is a passage, and a
  // passage with furniture in it is a corridor with an obstruction in it.
  {
    id: "paseo-directory",
    kind: DISPLAY,
    position: { x: LOBBY_E + INT_FACE + DISPLAY_OFFSET, z: 2.4 },
    rotation: WEST,
  },
  ...scatter("paseo-tree", TREE, [
    { x: 17.2, z: 1.2 },
    { x: 19.0, z: 1.2 },
  ]),
  // Two planted troughs against the flanks of the passage, and they are here for
  // the arrival shot rather than for the passage. `viewpoints[0]` looks down
  // this tunnel from the pavement; before these there was brick, brick, and a
  // lit rectangle at the end. 0.44 m deep each still leaves 2.1 m of clear walk
  // in a 3 m passage, which is the number that decides whether planting a
  // corridor is generous or obstructive.
  { id: "paseo-trough-w", kind: TROUGH, position: { x: 16.95, z: 6.6 }, rotation: WEST },
  { id: "paseo-trough-e", kind: TROUGH, position: { x: 19.25, z: 6.6 }, rotation: EAST },
  ...scatter("paseo-light", TROFFER, grid(18.1, 2.4, 1, 2, 0, 3.6), { elevation: GROUND }),

  // -- The Front ------------------------------------------------------------
  // The desk is turned to face the paseo door, so the receptionist is on its far
  // flank looking back at whoever just came in off the street.
  { id: "front-desk", kind: DESK, position: { x: 14.2, z: 5.0 }, rotation: EAST },
  // The host chair, bound to the address somebody standing at it occupies.
  // `front-01` had no furniture at all before this pass, which is the state the
  // seat-binding assertion in `src/test/packs/mateoContent.test.ts` exists to
  // keep the building out of: a seat with no prop bound to it gives an occupancy
  // layer nothing to dim and reads as fine in every screenshot.
  {
    id: "front-chair",
    kind: TASK_CHAIR,
    position: { x: 13.6, z: 5.0 },
    rotation: EAST,
    seat: "front-01",
  },
  {
    id: "front-monitor",
    kind: MONITOR,
    position: { x: 13.9, z: 5.0 },
    rotation: EAST,
    // Standing on the desktop. `tera:desk.workstation` is 0.73 m to the deck and
    // a monitor is authored on the floor like everything else, so the pack
    // supplies the height rather than the asset assuming one.
    elevation: 0.73,
  },
  // The mic on the desk and the speaker on the computer, either side of the
  // screen on the 0.73 m desktop. These two props are hardware: `DEVICES` at the
  // foot of this file points at these ids and takes its coordinates from them,
  // so a device has no position of its own to disagree with the furniture.
  { id: "front-mic", kind: MIC, position: { x: 14.0, z: 4.5 }, rotation: EAST, elevation: 0.73 },
  {
    id: "front-speaker",
    kind: SPEAKER,
    position: { x: 14.0, z: 5.5 },
    rotation: EAST,
    elevation: 0.73,
  },
  { id: "front-pedestal", kind: PEDESTAL, position: { x: 14.2, z: 3.9 }, rotation: EAST },
  { id: "front-rug", kind: RUG, position: { x: 12.4, z: 6.6 }, rotation: NORTH },
  { id: "front-sofa-w", kind: LOUNGE_CHAIR, position: { x: 11.6, z: 6.6 }, rotation: EAST },
  { id: "front-sofa-e", kind: LOUNGE_CHAIR, position: { x: 13.2, z: 6.6 }, rotation: WEST },
  { id: "front-table", kind: SIDE_TABLE, position: { x: 12.4, z: 6.6 }, rotation: NORTH },
  {
    id: "front-shelf",
    kind: SHELF,
    position: { x: PRESS_E + INT_FACE + 0.18, z: 2.0 },
    rotation: WEST,
  },
  { id: "front-tree", kind: TREE, position: { x: 15.8, z: 1.2 }, rotation: NORTH },
  // A standing lamp beside the lounge chairs. Reception is the first room a
  // visitor is in and it was lit exclusively by a nine-fitting ceiling grid.
  { id: "front-floor-light", kind: FLOOR_LIGHT, position: { x: 10.5, z: 7.3 }, rotation: EAST },
  // Planting at the street window and under the high courtyard light, and a
  // bench between them. Reception is the first room a visitor stands in and it
  // had a desk, two chairs and a rug in it.
  { id: "front-trough-n", kind: TROUGH, position: { x: 10.6, z: 0.55 }, rotation: NORTH },
  { id: "front-trough-s", kind: TROUGH, position: { x: 13.0, z: 7.95 }, rotation: SOUTH },
  { id: "front-bench", kind: BENCH, position: { x: 10.8, z: 7.9 }, rotation: SOUTH },
  // On the paseo partition, clear of the door at z 4.2–5.2. The wall a visitor
  // faces while they wait is the one wall in this building worth panelling.
  {
    id: "front-baffle",
    kind: BAFFLE,
    position: { x: LOBBY_E - INT_FACE - BOARD_OFFSET, z: 6.8 },
    rotation: EAST,
  },
  ...scatter("front-light", TROFFER, grid(11.6, 2.2, 2, 2, 3.0, 3.4), { elevation: GROUND }),

  // -- The Press ------------------------------------------------------------
  // Two 2.4 m tables, because a `Prop` is an id and a placement and carries no
  // parameters: the long bench this room wants is two of the table the kit has.
  { id: "press-table-n", kind: MEETING_TABLE, position: { x: 4.6, z: 3.4 }, rotation: NORTH },
  { id: "press-table-s", kind: MEETING_TABLE, position: { x: 4.6, z: 6.6 }, rotation: NORTH },
  ...scatter("press-locker", LOCKER, grid(EXT_FACE + 0.25, 1.4, 1, 2, 0, 1.3), { rotation: WEST }),
  ...scatter("press-shelf", SHELF, grid(6.6, COURT_N - INT_FACE - 0.18, 2, 1, 1.0, 0), {
    rotation: SOUTH,
  }),
  {
    id: "press-board",
    kind: WHITEBOARD,
    position: { x: 2.4, z: COURT_N - INT_FACE - BOARD_OFFSET },
    rotation: SOUTH,
  },
  { id: "press-plant", kind: PLANT, position: { x: 8.6, z: 0.8 }, rotation: NORTH },
  // A wardrobe, which in a media studio is not domestic furniture: it is where
  // the clothes people are filmed in live. 0.58 m deep against 0.175 m of brick
  // face, so its centre is 0.465 m off the wall line and its 1.8 m of width runs
  // along the wall.
  {
    id: "press-wardrobe",
    kind: WARDROBE,
    position: { x: EXT_FACE + 0.29, z: 5.4 },
    rotation: WEST,
  },
  // Two capture marks, two pieces of hardware. `media-01` and `media-02` are
  // standing addresses at the north bench; the mic and the monitor speaker sit
  // on the bench in front of each, bound to the address each serves.
  {
    id: "press-mic",
    kind: MIC,
    position: { x: 3.8, z: 3.8 },
    rotation: SOUTH,
    elevation: 0.74,
    seat: "media-01",
  },
  {
    id: "press-speaker",
    kind: SPEAKER,
    position: { x: 5.4, z: 3.8 },
    rotation: SOUTH,
    elevation: 0.74,
    seat: "media-02",
  },
  /**
   * The studio kit, in the room the studio kit was drawn for.
   *
   * A media studio furnished out of the office catalogue is two meeting tables
   * and some lockers, which is what this room was. What makes it read as a
   * studio is the stuff that only ever lives in one: a camera on sticks, two
   * softboxes aimed at the capture marks, a wall of acoustic panels, and cases
   * stacked in a corner because nothing here gets put away between shoots.
   *
   * The two softboxes are `rotation: SOUTH` and they light **north**. That is
   * the library's back-at-−Z rule doing exactly what it says — a softbox's
   * diffuser is its user face, so it points opposite the rotation, the same way
   * `press-board` at `rotation: SOUTH` writes toward the north. `media-01` and
   * `media-02` stand at z 4.4; the boxes stand at z 5.6 and throw onto them.
   */
  { id: "press-tripod", kind: TRIPOD, position: { x: 4.6, z: 5.6 }, rotation: NORTH },
  ...scatter("press-softbox", SOFTBOX, [
    { x: 2.6, z: 5.6 },
    { x: 6.6, z: 5.6 },
  ], { rotation: SOUTH }),
  // The tool wall goes on the east partition, which is the only wall in this
  // room with neither a window nor a board on it.
  { id: "press-lab-bench", kind: LAB_BENCH, position: { x: 9.15, z: 6.4 }, rotation: EAST },
  { id: "press-rack", kind: RACK, position: { x: 9.09, z: 7.9 }, rotation: EAST },
  { id: "press-cart", kind: TOOL_CART, position: { x: 7.5, z: 4.4 }, rotation: NORTH },
  ...scatter("press-case", CASES, [
    { x: 8.8, z: 2.2 },
    { x: 8.8, z: 3.1 },
    { x: 7.7, z: 2.2 },
  ]),
  // Acoustic panels on the range wall, in the 2.4 m of it between the board and
  // the shelves. This is the direct answer to "blank white wall planes
  // everywhere": 45 mm of relief, a fabric that is not plaster, and a shadow
  // under every panel.
  {
    id: "press-baffle",
    kind: BAFFLE,
    position: { x: 5.0, z: COURT_N - INT_FACE - BOARD_OFFSET },
    rotation: SOUTH,
  },
  // A slat screen across the middle of the room, which is how a studio splits a
  // set from the gear without building a wall — and the one prop in the library
  // that looks different depending on where you stand.
  { id: "press-divider", kind: DIVIDER, position: { x: 7.0, z: 6.8 }, rotation: NORTH },
  { id: "press-bench", kind: BENCH, position: { x: 2.2, z: 8.1 }, rotation: SOUTH },
  ...scatter("press-light", TROFFER, grid(1.8, 2.0, 3, 2, 3.2, 3.2), { elevation: GROUND }),

  // -- The Mess -------------------------------------------------------------
  // A complete but restrained commons: one authored run, one island, two stools.
  { id: "mess-kitchen-run", kind: KITCHEN, position: { x: 25.7, z: 1.15 }, rotation: NORTH },
  { id: "mess-island", kind: ISLAND, position: { x: 23.0, z: 3.0 }, rotation: NORTH },
  {
    id: "mess-island-plant",
    kind: PLANT,
    position: { x: 23.0, z: 3.0 },
    rotation: NORTH,
    elevation: 0.74,
  },
  // The two stools at the island, bound to the two addresses at the island. The
  // seats and the stools were authored at the same two coordinates and knew
  // nothing about each other until this pass.
  ...scatter(
    "mess-stool",
    STOOL,
    [
      { x: 22.6, z: 4.2, seat: "commons-01" },
      { x: 23.5, z: 4.2, seat: "commons-02" },
    ],
    { rotation: NORTH },
  ),
  {
    id: "mess-shelf",
    kind: SHELF,
    position: { x: MESS_E - INT_FACE - 0.18, z: 6.0 },
    rotation: EAST,
  },
  { id: "mess-table", kind: MEETING_TABLE, position: { x: 21.6, z: 6.6 }, rotation: NORTH },
  { id: "mess-seat-w", kind: LOUNGE_CHAIR, position: { x: 20.2, z: 6.6 }, rotation: EAST },
  { id: "mess-seat-e", kind: LOUNGE_CHAIR, position: { x: 23.0, z: 6.6 }, rotation: WEST },
  { id: "mess-tree", kind: TREE, position: { x: 26.0, z: 1.4 }, rotation: NORTH },
  // A real sofa against the courtyard glazing, its back under the sill of the
  // 1.0 m window and clear of the archway at x 21.0–23.4. A commons with nothing
  // soft in it is a canteen.
  { id: "mess-sofa", kind: SOFA, position: { x: 25.6, z: 7.82 }, rotation: SOUTH },
  { id: "mess-sofa-table", kind: SIDE_TABLE, position: { x: 25.6, z: 6.7 }, rotation: NORTH },
  { id: "mess-floor-light", kind: FLOOR_LIGHT, position: { x: 27.1, z: 7.4 }, rotation: EAST },
  // A planted trough under the street window, a second one standing free beside
  // the review-room wall, and a slat screen between the kitchen and the sitting
  // end — the commons equivalent of a wall nobody wanted to build.
  { id: "mess-trough-n", kind: TROUGH, position: { x: 21.4, z: 0.55 }, rotation: NORTH },
  { id: "mess-trough-e", kind: TROUGH, position: { x: 26.9, z: 4.4 }, rotation: EAST },
  { id: "mess-divider", kind: DIVIDER, position: { x: 24.7, z: 5.2 }, rotation: NORTH },
  { id: "mess-bench", kind: BENCH, position: { x: 19.9, z: 2.4 }, rotation: WEST },
  // Panels on the review-room wall, clear of the door at z 4.4–5.3. A terrazzo
  // floor, a glazed wall and a hard ceiling is a room you cannot hear anybody
  // in, and this is the only wall here that can take absorption.
  {
    id: "mess-baffle",
    kind: BAFFLE,
    position: { x: MESS_E - INT_FACE - BOARD_OFFSET, z: 2.4 },
    rotation: EAST,
  },
  { id: "mess-plant", kind: PLANT, position: { x: 27.3, z: 7.9 }, rotation: NORTH },
  ...scatter("mess-pendant", PENDANT, grid(22.4, 3.0, 2, 1, 1.2, 0), { elevation: GROUND }),
  ...scatter("mess-light", TROFFER, grid(21.2, 2.2, 3, 1, 2.8, 0), { elevation: GROUND }),

  // -- Hewitt ---------------------------------------------------------------
  { id: "hewitt-table", kind: MEETING_TABLE, position: { x: 31.2, z: 4.2 }, rotation: NORTH },
  // Four chairs at four addresses. `review-01` … `review-04` are the seats the
  // pack has always declared here and the chairs have always stood on; the
  // binding is what lets something outside this repo say "review-03" and have it
  // mean a chair rather than a coordinate.
  ...scatter(
    "hewitt-chair-n",
    TASK_CHAIR,
    [
      { x: 29.8, z: 3.2, seat: "review-01" },
      { x: 32.6, z: 3.2, seat: "review-02" },
    ],
    { rotation: SOUTH },
  ),
  ...scatter(
    "hewitt-chair-s",
    TASK_CHAIR,
    [
      { x: 29.8, z: 5.2, seat: "review-03" },
      { x: 32.6, z: 5.2, seat: "review-04" },
    ],
    { rotation: NORTH },
  ),
  {
    id: "hewitt-display",
    kind: DISPLAY,
    // On the party wall, which is the one wall in this room that will never have
    // a window in it. Rooms get laid out around where a screen can go more often
    // than anybody admits, and here the wall was decided by the neighbours.
    position: { x: WIDTH - EXT_FACE - DISPLAY_OFFSET, z: 4.2 },
    rotation: EAST,
  },
  {
    id: "hewitt-board",
    kind: WHITEBOARD,
    position: { x: MESS_E + INT_FACE + BOARD_OFFSET, z: 4.2 },
    rotation: WEST,
  },
  { id: "hewitt-tree", kind: TREE, position: { x: 29.2, z: 7.4 }, rotation: NORTH },
  // The review room is where people sit for an hour, and it was four task chairs
  // on bare carpet under six troffers. A rug under the table, a corner to retreat
  // to, and a lamp that is not in the ceiling.
  {
    id: "hewitt-rug",
    kind: RUG,
    position: { x: 31.2, z: 4.2 },
    rotation: NORTH,
    scale: [1.6, 1, 1.35],
    colorKey: "team-a",
  },
  { id: "hewitt-lounge", kind: LOUNGE_CHAIR, position: { x: 33.0, z: 7.2 }, rotation: SOUTH },
  { id: "hewitt-side", kind: SIDE_TABLE, position: { x: 34.1, z: 7.2 }, rotation: NORTH },
  { id: "hewitt-floor-light", kind: FLOOR_LIGHT, position: { x: 35.2, z: 7.3 }, rotation: EAST },
  // Absorption either side of the screen on the party wall, planting at the
  // street window, a bench against the range wall and a slat screen closing off
  // the lounge corner. A room four people sit in for an hour is the room where
  // bare plaster and a ceiling grid are most obviously not enough.
  ...scatter("hewitt-baffle", BAFFLE, [
    { x: WIDTH - EXT_FACE - BOARD_OFFSET, z: 1.6 },
    { x: WIDTH - EXT_FACE - BOARD_OFFSET, z: 6.9 },
  ], { rotation: EAST }),
  ...scatter("hewitt-trough", TROUGH, [
    { x: 30.0, z: 0.55 },
    { x: 33.0, z: 0.55 },
  ], { rotation: NORTH }),
  { id: "hewitt-divider", kind: DIVIDER, position: { x: 33.6, z: 6.2 }, rotation: NORTH },
  { id: "hewitt-bench", kind: BENCH, position: { x: 31.6, z: 8.0 }, rotation: SOUTH },
  { id: "hewitt-plant", kind: PLANT, position: { x: 34.8, z: 2.4 }, rotation: NORTH },
  ...scatter("hewitt-light", TROFFER, grid(29.6, 2.6, 3, 1, 2.8, 0), { elevation: GROUND }),

  // -- Willow ---------------------------------------------------------------
  { id: "willow-table", kind: MEETING_TABLE, position: { x: 4.4, z: 10.7 }, rotation: NORTH },
  ...scatter("willow-chair-n", TASK_CHAIR, grid(3.4, 9.7, 2, 1, 2.0, 0), { rotation: SOUTH }),
  ...scatter("willow-chair-s", TASK_CHAIR, grid(3.4, 11.7, 2, 1, 2.0, 0), { rotation: NORTH }),
  {
    id: "willow-display",
    kind: DISPLAY,
    position: { x: EXT_FACE + DISPLAY_OFFSET, z: 10.7 },
    rotation: WEST,
  },
  { id: "willow-plant", kind: PLANT, position: { x: 8.4, z: 9.2 }, rotation: NORTH },
  {
    id: "willow-shelf",
    kind: SHELF,
    position: { x: 7.4, z: COURT_N + INT_FACE + 0.18 },
    rotation: NORTH,
  },
  { id: "willow-floor-light", kind: FLOOR_LIGHT, position: { x: 1.0, z: 12.2 }, rotation: WEST },
  // The south partition is the one blind wall in this room and it faces the
  // table, so it takes the panels; the trough goes on the courtyard glazing
  // north of the door, under a 1.0 m sill it comfortably clears at 0.46.
  ...scatter("willow-baffle", BAFFLE, [
    { x: 2.4, z: WILLOW_S - INT_FACE - BOARD_OFFSET },
    { x: 5.4, z: WILLOW_S - INT_FACE - BOARD_OFFSET },
  ], { rotation: SOUTH }),
  { id: "willow-trough", kind: TROUGH, position: { x: 9.2, z: 9.6 }, rotation: EAST },
  { id: "willow-case", kind: CASES, position: { x: 8.4, z: 12.4 }, rotation: NORTH },
  ...scatter("willow-light", TROFFER, grid(3.0, 10.7, 2, 1, 3.0, 0), { elevation: GROUND }),

  // -- Violet ---------------------------------------------------------------
  { id: "violet-rug", kind: RUG, position: { x: 4.4, z: 15.3 }, rotation: NORTH, colorKey: "focus" },
  { id: "violet-chair-n", kind: LOUNGE_CHAIR, position: { x: 4.4, z: 14.4 }, rotation: SOUTH },
  { id: "violet-chair-s", kind: LOUNGE_CHAIR, position: { x: 4.4, z: 16.2 }, rotation: NORTH },
  { id: "violet-table", kind: SIDE_TABLE, position: { x: 4.4, z: 15.3 }, rotation: NORTH },
  {
    id: "violet-shelf",
    kind: SHELF,
    position: { x: 7.0, z: WILLOW_S + INT_FACE + 0.18 },
    rotation: NORTH,
  },
  { id: "violet-tree", kind: TREE, position: { x: 8.2, z: 16.6 }, rotation: NORTH },
  /**
   * The daybed, and the only bed in a building nobody sleeps in.
   *
   * A quiet room in a studio that runs late is a room with somewhere to lie
   * down, and `tera:bed.platform` is the asset that says so — 1.52 by 2.03, laid
   * head-to-the-wall so its length runs east off the brick rather than across
   * the room. It is also the fourth of seven habitat assets this building had
   * placed nowhere at all, in a pack whose hundred and eight fittings were
   * entirely ceiling troffers and pendants.
   *
   * `violet` is the room that says the least in this pack and it is deliberate:
   * no table you can put a laptop on, and now nothing overhead either. The
   * troffer grid that used to be here is gone; the pendant and the two standing
   * lamps are the whole of the light.
   */
  { id: "violet-daybed", kind: BED, position: { x: 1.2, z: 15.3 }, rotation: WEST },
  { id: "violet-floor-light", kind: FLOOR_LIGHT, position: { x: 2.6, z: 13.7 }, rotation: NORTH },
  { id: "violet-floor-light-s", kind: FLOOR_LIGHT, position: { x: 2.6, z: 16.9 }, rotation: SOUTH },
  { id: "violet-pendant", kind: PENDANT, position: { x: 4.4, z: 15.3 }, rotation: NORTH, elevation: GROUND },
  // The quiet room earns its name with absorption rather than with emptiness.
  // Two panelled arrays on the blind south partition, a bench under one of them,
  // a slat screen between the daybed and the courtyard door, and planting on the
  // glazing. Nothing here is a work surface, which is still the rule.
  ...scatter("violet-baffle", BAFFLE, [
    { x: 2.4, z: COURT_S - INT_FACE - BOARD_OFFSET },
    { x: 6.0, z: COURT_S - INT_FACE - BOARD_OFFSET },
  ], { rotation: SOUTH }),
  { id: "violet-bench", kind: BENCH, position: { x: 2.0, z: 17.1 }, rotation: SOUTH },
  { id: "violet-divider", kind: DIVIDER, position: { x: 7.2, z: 15.4 }, rotation: EAST },
  { id: "violet-trough", kind: TROUGH, position: { x: 9.2, z: 15.8 }, rotation: EAST },

  // -- The Shop -------------------------------------------------------------
  /**
   * The robotics lab, which used to be furnished with two meeting tables.
   *
   * That was the clearest case in the building of the office catalogue being
   * used past its brief: a fabrication room whose work surfaces were the same
   * asset as the review room's is a room that has never been looked at. Both are
   * `tera:bench.lab` now — a boxed frame, a drawer bank, a concrete deck and a
   * pegboard of hanging tools — and the ids are unchanged, so nothing that
   * pointed at them has moved.
   *
   * The rest is the shape a lab actually takes: benches on three sides, racks
   * and carts in the middle, two charge docks so the humanoid has somewhere to
   * *be*, and cases stacked where things wait to go out. Nothing stands in the
   * four metres of shutter at z 11.0–15.0 or in front of the personnel door,
   * because that is the route the work leaves by and it is the reason the yard
   * outside is paved rather than planted.
   */
  { id: "shop-bench-n", kind: LAB_BENCH, position: { x: 31.0, z: 10.0 }, rotation: NORTH },
  { id: "shop-bench-s", kind: LAB_BENCH, position: { x: 31.0, z: 12.6 }, rotation: NORTH },
  ...scatter("shop-bench-alley", LAB_BENCH, [
    { x: 31.2, z: 17.05 },
    { x: 33.4, z: 17.05 },
  ], { rotation: SOUTH }),
  { id: "shop-bench-island", kind: LAB_BENCH, position: { x: 28.9, z: 13.0 }, rotation: NORTH },
  ...scatter("shop-rack-eq", RACK, [
    { x: 35.35, z: 14.4 },
    { x: 35.35, z: 15.3 },
    { x: 35.35, z: 16.2 },
  ], { rotation: EAST }),
  ...scatter("shop-cart", TOOL_CART, [
    { x: 29.9, z: 11.0 },
    { x: 32.8, z: 11.2 },
    { x: 30.0, z: 15.0 },
  ]),
  // Two docks: one on the party-wall end of the blind north wall, one in the
  // corner by the yard door. `operations/mateo-court.ts` parks a humanoid in
  // this building and a humanoid standing on bare concrete reads as a mistake.
  { id: "shop-dock-n", kind: DOCK, position: { x: 35.0, z: 8.95 }, rotation: NORTH },
  { id: "shop-dock-s", kind: DOCK, position: { x: 27.2, z: 17.05 }, rotation: SOUTH },
  ...scatter("shop-case", CASES, [
    { x: 28.6, z: 15.6 },
    { x: 29.5, z: 15.6 },
    { x: 28.6, z: 16.5 },
    { x: 29.5, z: 16.5 },
  ]),
  { id: "shop-divider", kind: DIVIDER, position: { x: 33.0, z: 15.2 }, rotation: NORTH },
  { id: "shop-plant", kind: PLANT, position: { x: 27.4, z: 10.2 }, rotation: NORTH },
  ...scatter("shop-locker", LOCKER, grid(28.0, COURT_N + INT_FACE + 0.25, 3, 1, 1.3, 0)),
  // Racking beside the shutter, against the courtyard wall — which is the one
  // place in this pack a prop backs onto the 0.25 m skin rather than the 0.12 m
  // partition or the 0.35 m brick, so it is the one place `YARD_FACE` is the
  // right half-thickness to add to a locker's own 0.25.
  ...scatter("shop-rack", LOCKER, grid(COURT_E + YARD_FACE + 0.25, 9.1, 1, 2, 0, 1.3), {
    rotation: WEST,
  }),
  // Against the party wall, which in this room is 9.2 m of unbroken brick and
  // the best storage wall in the building.
  ...scatter("shop-shelf", SHELF, grid(WIDTH - EXT_FACE - 0.18, 9.6, 1, 4, 0, 1.2), {
    rotation: EAST,
  }),
  {
    id: "shop-board",
    kind: WHITEBOARD,
    position: { x: 33.4, z: COURT_N + INT_FACE + BOARD_OFFSET },
    rotation: NORTH,
  },
  { id: "shop-tree", kind: TREE, position: { x: 27.4, z: 16.4 }, rotation: NORTH },
  ...scatter("shop-light", TROFFER, grid(28.6, 9.8, 3, 2, 2.8, 4.2), { elevation: GROUND }),

  // -- The Works ------------------------------------------------------------
  // A screen down the spine of each bench: 1.4 m against a 1.7 m pitch leaves a
  // gap you can talk through, which is the entire argument for a 0.45 m screen.
  ...scatter("works-a-screen", PARTITION, grid(3.4, 19.625, 6, 1, 1.7, 0)),
  // Pedestals park at the ends of the benches rather than under the desks, which
  // is where they end up in a building where people move seats.
  ...scatter("works-pedestal", PEDESTAL, [
    { x: 2.0, z: 19.6 },
    { x: 13.2, z: 19.6 },
  ]),
  ...scatter("works-locker", LOCKER, grid(1.4, COURT_S + INT_FACE + 0.25, 3, 1, 1.3, 0)),
  {
    id: "works-display",
    kind: DISPLAY,
    position: { x: EXT_FACE + DISPLAY_OFFSET, z: 21.0 },
    rotation: WEST,
  },
  // The east end of the works is left as somewhere to sit rather than as more
  // desks. None of these four chairs has a seat id, and that is the format
  // working as intended: a chair with nobody's name on it needs no address.
  { id: "works-rug", kind: RUG, position: { x: 25.0, z: 21.8 }, rotation: NORTH, colorKey: "social" },
  ...scatter("works-lounge", LOUNGE_CHAIR, [
    { x: 23.8, z: 21.0 },
    { x: 26.2, z: 21.0 },
    { x: 23.8, z: 22.6 },
    { x: 26.2, z: 22.6 },
  ]),
  { id: "works-side", kind: SIDE_TABLE, position: { x: 25.0, z: 21.8 }, rotation: NORTH },
  ...scatter("works-tree", TREE, [
    { x: 17.6, z: 18.6 },
    { x: 29.6, z: 18.6 },
    { x: 20.0, z: 25.0 },
  ]),
  { id: "works-floor-light", kind: FLOOR_LIGHT, position: { x: 27.4, z: 22.6 }, rotation: EAST },
  /**
   * Ten fittings over 260 m², where there were twenty-four.
   *
   * An 8 x 3 grid was a false economy in both directions: it is the single
   * biggest block of props in this pack, it is the one thing a viewer never
   * looks at, and the room it hangs in is declared `ceiling: null`. Twenty-four
   * troffers over an open sky is a lighting plan for a building that is not
   * this one. Five by two on a 6 m pitch reads as an industrial bay, which is
   * what a timber-framed warehouse floor lit on beam centres actually looks
   * like, and the fourteen fittings it gives back are spent on furniture.
   */
  /**
   * Two hundred and sixty square metres, and what it was missing was not desks.
   *
   * The long south range is the biggest room in the building by a factor of
   * three and it held forty-five props of ten kinds, most of them one bench of
   * workstations. Everything below is about the *rest* of the floor: a second
   * four-station bench at the east end, two slat screens dividing the run into
   * bays, planting along the courtyard glazing, three robot docks in the piers
   * between the alley windows, racks and carts and cases at the storage end, and
   * two benches to sit on that are not task chairs.
   *
   * The docks are placed on the **piers**, not under the windows — 6.8–8.4,
   * 13.6–15.2 and 20.4–22.4 are the three stretches of solid brick in
   * `ext-south`, and a 1.9 m backboard in front of a window is a prop that
   * deletes a window. Nothing stands in the 22.4–26.0 goods shutter either.
   */
  ...scatter("works-b-screen", PARTITION, grid(18.0, 22.825, 2, 1, 1.7, 0)),
  ...scatter("works-divider", DIVIDER, [
    { x: 16.4, z: 19.0 },
    { x: 16.4, z: 23.4 },
  ], { rotation: EAST }),
  ...scatter("works-trough", TROUGH, [
    { x: 7.0, z: 17.95 },
    { x: 9.0, z: 17.95 },
    { x: 11.0, z: 17.95 },
    { x: 20.8, z: 17.95 },
    { x: 23.2, z: 17.95 },
    { x: 25.4, z: 17.95 },
  ], { rotation: NORTH }),
  ...scatter("works-dock", DOCK, [
    { x: 7.6, z: 25.4 },
    { x: 14.4, z: 25.4 },
    { x: 21.4, z: 25.4 },
  ], { rotation: SOUTH }),
  ...scatter("works-bench", BENCH, [
    { x: 11.0, z: 25.5 },
    { x: 18.0, z: 25.5 },
  ], { rotation: SOUTH }),
  ...scatter("works-rack", RACK, [
    { x: 30.45, z: 18.6 },
    { x: 30.45, z: 19.5 },
    { x: 30.45, z: 23.0 },
  ], { rotation: EAST }),
  ...scatter("works-cart", TOOL_CART, [
    { x: 13.0, z: 22.4 },
    { x: 28.0, z: 22.0 },
  ]),
  ...scatter("works-case", CASES, [
    { x: 28.4, z: 24.6 },
    { x: 29.3, z: 24.6 },
    { x: 28.4, z: 23.6 },
  ]),
  ...scatter("works-plant-b", PLANT, [
    { x: 12.6, z: 18.4 },
    { x: 15.0, z: 24.2 },
    { x: 22.6, z: 19.4 },
  ]),
  // A camera on the floor, pointed west down the run. This building films its
  // own environment work; the media studio is where that is edited, not the only
  // place it happens.
  { id: "works-tripod", kind: TRIPOD, position: { x: 24.0, z: 20.0 }, rotation: WEST },
  ...scatter("works-light", TROFFER, grid(3.0, 19.4, 5, 2, 6.0, 4.4), { elevation: GROUND }),

  // -- Facilities -----------------------------------------------------------
  // The lockers skip the middle of the wall because the door is there.
  ...scatter("store-locker", LOCKER, [
    { x: STORE_W + INT_FACE + 0.25, z: 18.4 },
    { x: STORE_W + INT_FACE + 0.25, z: 19.7 },
    { x: STORE_W + INT_FACE + 0.25, z: 23.0 },
    { x: STORE_W + INT_FACE + 0.25, z: 24.3 },
  ], { rotation: WEST }),
  ...scatter("store-shelf", SHELF, grid(WIDTH - EXT_FACE - 0.18, 18.6, 1, 3, 0, 1.2), {
    rotation: EAST,
  }),
  // Racking in the middle of the floor rather than more shelves on the walls —
  // the walls are full, and this is the one room in the building with a ceiling
  // to rack up to.
  ...scatter("store-rack", RACK, [
    { x: 33.4, z: 19.0 },
    { x: 33.4, z: 20.2 },
    { x: 33.4, z: 21.4 },
  ]),
  ...scatter("store-case", CASES, [
    { x: 33.2, z: 24.4 },
    { x: 34.2, z: 24.4 },
  ]),
  // A bench to unpack on, a cart, and a spare charge bay. A store furnished out
  // of two kinds is a room somebody drew a grid of lockers in; four walls of
  // shelving is not what the inside of a working store looks like.
  { id: "store-bench", kind: LAB_BENCH, position: { x: 34.4, z: 25.4 }, rotation: SOUTH },
  { id: "store-cart", kind: TOOL_CART, position: { x: 32.2, z: 22.0 }, rotation: NORTH },
  { id: "store-dock", kind: DOCK, position: { x: 35.3, z: 22.6 }, rotation: EAST },
  ...scatter("store-light", TROFFER, grid(32.6, 19.6, 1, 2, 0, 3.0), { elevation: 3.0 }),

  // -- The Yard -------------------------------------------------------------
  /**
   * A long table and four trees, and no light fittings at all.
   *
   * The table is two 2.4 m tables end to end with eight chairs at it, and the
   * chairs are `tera:seat.task-chair` because there is no bench anywhere in the
   * catalogue. That is the one place in this pack where the library is being
   * used past its brief and it shows: the honest fix is `defineAsset` with a
   * bench of your own, not a task chair left out in the weather.
   *
   * The absence of lighting is not an oversight. `tera:light.pendant` is
   * authored with its origin at a **mounting plane** and all its geometry below
   * it, and there is nothing over the yard to mount one on — the fittings that
   * light this space in the evening are on the loggia above, hung from a soffit
   * that exists.
   */
  { id: "yard-table", kind: MEETING_TABLE, position: { x: 19.2, z: 13.4 }, rotation: NORTH },
  ...scatter("yard-chair-n", TASK_CHAIR, grid(18.0, 12.4, 2, 1, 2.4, 0), { rotation: SOUTH }),
  ...scatter("yard-chair-s", TASK_CHAIR, grid(18.0, 14.4, 2, 1, 2.4, 0), { rotation: NORTH }),
  ...scatter("yard-tree", TREE, [
    { x: 11.4, z: 12.6 },
    { x: 11.4, z: 16.2 },
    { x: 24.6, z: 10.4 },
    { x: 24.6, z: 16.2 },
  ]),
  ...scatter("yard-plant", PLANT, [
    { x: 16.0, z: 9.4 },
    { x: 22.4, z: 9.4 },
    { x: 15.0, z: 16.6 },
    { x: 24.6, z: 13.2 },
  ]),
  // A second, looser group at the south-west corner of the yard, away from the
  // long table: two chairs and something to put a cup on. A courtyard that is
  // only ever a route is a corridor with weather in it.
  { id: "yard-lounge-w", kind: LOUNGE_CHAIR, position: { x: 12.6, z: 15.4 }, rotation: EAST },
  { id: "yard-lounge-e", kind: LOUNGE_CHAIR, position: { x: 14.6, z: 15.4 }, rotation: WEST },
  { id: "yard-side", kind: SIDE_TABLE, position: { x: 13.6, z: 15.4 }, rotation: NORTH },

  /**
   * The courtyard, properly.
   *
   * A hundred and forty square metres with sixteen props in it, four of which
   * were the same tree, is the single thinnest thing in this pack — and it is
   * also the room the whole plan is organised around and the one every viewpoint
   * looks across. What it needed was not more trees. It needed the kinds a
   * courtyard is *made* of and this library did not have until now: planted
   * troughs to give the paving an edge, slat benches to sit on that are not task
   * chairs left out in the weather, and parasols, which are the only thing in
   * the catalogue that occupies the volume between head height and the sky.
   *
   * Three routes are kept clear on purpose and every position below is measured
   * against them: the four-metre shutter into the robotics lab (x 26.4,
   * z 11.0–15.0), the personnel door beside it (z 15.8–17.0), and the pair of
   * leaves into the agent floor (z 17.6, x 13.0–14.8). A courtyard that a
   * pallet cannot cross is a garden.
   */
  ...scatter("court-trough", TROUGH, [
    // The south edge, under the agent floor's glazing, skipping the doors.
    { x: 16.4, z: 17.2 },
    { x: 18.4, z: 17.2 },
    { x: 20.4, z: 17.2 },
    { x: 22.4, z: 17.2 },
    { x: 24.4, z: 17.2 },
  ], { rotation: SOUTH }),
  ...scatter("court-trough-stair", TROUGH, [
    { x: 10.8, z: 11.4 },
    { x: 12.8, z: 11.4 },
  ], { rotation: NORTH }),
  { id: "court-trough-e", kind: TROUGH, position: { x: 26.05, z: 9.5 }, rotation: EAST },
  // Six benches: two facing north out of the planted south edge, two facing the
  // long table from the north, and one each at the west and north-east corners.
  ...scatter("court-bench-s", BENCH, [
    { x: 17.4, z: 16.5 },
    { x: 21.4, z: 16.5 },
  ], { rotation: SOUTH }),
  ...scatter("court-bench-n", BENCH, [
    { x: 17.4, z: 10.0 },
    { x: 21.4, z: 10.0 },
  ], { rotation: NORTH }),
  { id: "court-bench-w", kind: BENCH, position: { x: 10.6, z: 15.4 }, rotation: WEST },
  { id: "court-bench-e", kind: BENCH, position: { x: 25.4, z: 9.6 }, rotation: EAST },
  /**
   * Three parasols, and they are the reason this yard finally has a middle.
   *
   * Everything else in an office catalogue lives below 2 m or hangs from a
   * ceiling, and this room has no ceiling. Two flank the long table so their
   * canopies overlap it at 2.42 m without a mast standing in anybody's way, and
   * the third covers the loose chairs at the south-west corner. In a courtyard
   * in Los Angeles they are also the only honest answer to the question of what
   * anybody is doing out there at two in the afternoon.
   */
  ...scatter("court-parasol", PARASOL, [
    { x: 16.6, z: 13.4 },
    { x: 21.8, z: 13.4 },
    { x: 13.6, z: 16.6 },
  ]),
  /**
   * The charge dock, against the west wall between the quiet room's door and its
   * window.
   *
   * `operations/mateo-court.ts` used to park `la-l1-dock` at (18.4, 13.2), which
   * is underneath the long table. That was invisible for as long as nothing was
   * drawn there; the moment a robot stands on it, it stands in the lunch. The
   * station moved with this prop and the two now name the same place — the dock
   * is the furniture and the station is the address, which is the same
   * reference-not-restatement rule `Prop.seat` follows.
   */
  { id: "court-dock", kind: DOCK, position: { x: 10.15, z: 16.9 }, rotation: WEST },
  ...scatter("court-case", CASES, [
    { x: 24.0, z: 9.2 },
    { x: 24.9, z: 9.2 },
    { x: 12.0, z: 16.9 },
  ]),
  { id: "court-cart", kind: TOOL_CART, position: { x: 23.4, z: 16.6 }, rotation: NORTH },
  { id: "court-tripod", kind: TRIPOD, position: { x: 16.4, z: 10.4 }, rotation: SOUTH },
  ...scatter("court-plant", PLANT, [
    { x: 12.4, z: 12.0 },
    { x: 12.4, z: 14.2 },
    { x: 16.4, z: 16.6 },
    { x: 22.6, z: 15.6 },
    { x: 19.4, z: 9.6 },
  ]),

  // -- The Stair ------------------------------------------------------------
  // One pot under the flight, which is the only thing that fits there and the
  // only thing this room needs: a steel-floored triangle with a staircase in it
  // is not a room you furnish.
  { id: "stair-plant", kind: PLANT, position: { x: 9.95, z: 10.6 }, rotation: NORTH },
];

// ---- Devices --------------------------------------------------------------

/**
 * One sentence, shown to a viewer beside every reading this building publishes.
 *
 * Mandatory and checked: `validateDeviceDeclaration` refuses a `simulated`
 * declaration whose disclosure does not contain the word, the same way
 * `resolveRobotOperations` refuses a robot definition that does not say its
 * activity is simulated. A level meter that moves, with nothing beside it
 * saying where the number came from, is a claim about a real room — and a
 * building that makes that claim by omission is exactly what this field exists
 * to stop.
 */
const DISCLOSURE =
  "Simulated studio hardware. These readings are demonstration data, never live " +
  "presence data.";

/**
 * The two studio instruments are real, and everything about how they are
 * declared follows from what that costs.
 *
 * `press-mic` and `press-speaker` are still the props they always were, in a
 * press room this pack invented. What has changed is where their *readings* come
 * from: `TERA_DEVICES_SOURCE=first-party` points `server/src/devices/firstParty.ts`
 * at a real desk in the LA Studio, and these two ids are the two the bridge
 * publishes. So the placement is authored and the reading is measured, and the
 * disclosure below says exactly that rather than letting a viewer assume both
 * halves are one or the other. `src/tools/geo.ts` labels MEASURED, PLACEMENT and
 * DRAWN separately for the same reason; this is that distinction at building
 * scale.
 *
 * ### The ids are the upstream's, not names we chose
 *
 * `mic-yeti` and `speaker` are the keys `GET /api/la-studio/state` publishes,
 * and the bridge binds a reading to a declaration **by id**. A prettier id here
 * would be a device that declares itself live and is permanently unreachable,
 * which is worse than a simulated one. They read oddly in a floorplan; that is
 * the price of the binding being one lookup rather than a mapping table nobody
 * remembers to update.
 *
 * ### No `level`, and it is the most important line in this file
 *
 * `CANONICAL_CAPABILITIES.mic` is `power, mute, gain, level`, and this mic
 * declares the first three. There is no passive level upstream: obtaining one
 * means `POST /levels`, which records one and a half to three seconds of audio
 * per microphone. A level meter that breathes is the single most convincing
 * thing a twin could show and it would be a continuously-open microphone in a
 * room with people in it, looking like a feature the entire time it did it. The
 * meter row hides itself on a missing reading, which is what `undefined` has
 * always meant on `DeviceState`.
 *
 * ### The gain range is a percent because the gain *is* a percent
 *
 * Upstream reports `gainPct`, normalised over four different native scales — a
 * Yeti Nano's ALSA travel is 0–50, an Anker's is 0–100, a ThinkPad's is 0–63.
 * The global default range is decibels, and 68 % of a Yeti's travel is not any
 * number of decibels. Declaring the range here is a pack author saying what the
 * number means, and it is the only condition under which the bridge will emit a
 * gain reading at all — without it, fail-closed, there is no gain row.
 *
 * ### And a second sentence for the path that is not live
 *
 * An anonymous visitor cannot read the device route — it is a room somebody is
 * standing in — so they get the local simulator running this declaration.
 * `simulatedDisclosure` is what the panel prints instead the moment a reading is
 * synthetic, so "live, LA Studio" never appears under a number invented one
 * millisecond earlier in somebody's browser.
 */
const STUDIO_DISCLOSURE =
  "Live reading from the LA Studio's own hardware, mirrored read-only over the " +
  "operator's network. The room it stands in here is authored; the number is not.";

const STUDIO_SIMULATED_DISCLOSURE =
  "Simulated: this viewer is not reading the LA Studio's hardware, so these " +
  "numbers are demonstration data generated in your own browser.";

/** Mute and gain, and deliberately not level. See the note above. */
const STUDIO_MIC_CAPABILITIES: readonly DeviceCapability[] = ["power", "mute", "gain"];

/**
 * The canonical speaker list plus `mute`, which the canonical list omits and the
 * upstream actually reports. `playback` stays because a speaker has a transport
 * whether or not this deployment reads one.
 */
const STUDIO_SPEAKER_CAPABILITIES: readonly DeviceCapability[] = [
  "power",
  "mute",
  "volume",
  "playback",
];

/**
 * Four devices in two places: the desk in reception, and the two capture marks
 * in the media studio.
 *
 * **None of them carries a coordinate.** Every one names a prop, and the prop is
 * the hardware — `Plan` derives the world position from that prop's resolved
 * transform, so moving `press-mic` 200 mm moves the device and there is no
 * second number to forget. That is the rule `Prop.seat` follows for chairs and
 * the one CONTRACT.md follows for sites, one level further in.
 *
 * The two in reception are **simulated** and say so. The two in the media studio
 * are **first-party** and say something different. Keeping both in one pack, with
 * the disclosure under each instrument rather than in a header over all four, is
 * the whole point: the sentence has to travel with the reading it is about.
 *
 * `seatId` is an address and not a position. It says which standing or sitting
 * place each unit serves, which is what lets a consumer ask whether anybody is
 * where the mic is pointed without ever being handed a coordinate.
 */
const DEVICES: DeviceDeclaration[] = [
  {
    id: "la-front-mic",
    kind: "mic",
    label: "Front desk mic",
    assetId: MIC,
    anchor: { levelId: "level-1", propId: "front-mic", roomId: "lobby", seatId: "front-01" },
    capabilities: CANONICAL_CAPABILITIES.mic,
    provenance: "simulated",
    disclosure: DISCLOSURE,
  },
  {
    id: "la-front-speaker",
    kind: "speaker",
    label: "Front desk speaker",
    assetId: SPEAKER,
    anchor: { levelId: "level-1", propId: "front-speaker", roomId: "lobby", seatId: "front-01" },
    capabilities: CANONICAL_CAPABILITIES.speaker,
    provenance: "simulated",
    disclosure: DISCLOSURE,
  },
  {
    // Upstream's own id for the Blue Yeti Nano on the studio desk.
    id: "mic-yeti",
    kind: "mic",
    label: "Studio mic",
    assetId: MIC,
    anchor: { levelId: "level-1", propId: "press-mic", roomId: "press", seatId: "media-01" },
    capabilities: STUDIO_MIC_CAPABILITIES,
    // 0–100 of the mixer's own travel. `initial` is where an unread simulator
    // starts, not a reading: a live one is whatever the desk is set to.
    ranges: { gain: { min: 0, max: 100, initial: 60, unit: "%" } },
    provenance: "first-party-sensor",
    disclosure: STUDIO_DISCLOSURE,
    simulatedDisclosure: STUDIO_SIMULATED_DISCLOSURE,
  },
  {
    // Upstream publishes its chosen sink under the literal key `speaker` as well
    // as under the sink's own name. `speaker` is the one that is always present,
    // so it is the one that cannot silently stop binding when a sink changes.
    id: "speaker",
    kind: "speaker",
    label: "Studio monitor",
    assetId: SPEAKER,
    anchor: { levelId: "level-1", propId: "press-speaker", roomId: "press", seatId: "media-02" },
    capabilities: STUDIO_SPEAKER_CAPABILITIES,
    provenance: "first-party-sensor",
    disclosure: STUDIO_DISCLOSURE,
    simulatedDisclosure: STUDIO_SIMULATED_DISCLOSURE,
  },
];

// ---- Zones ----------------------------------------------------------------

/**
 * Three labelled regions of floor and no behaviour anywhere.
 *
 * Two of them are spatial — anybody standing in the yard can see that it is the
 * yard — and are built at both depths. The third names who sits somewhere rather
 * than what the floor is for, which is org chart drawn on a plan, and is not
 * built for an anonymous visitor. That is the line `audience` exists to draw and
 * the reference pack's `ZONES` argues it at length; there is no point arguing it
 * twice.
 */
const ZONES: Zone[] = [
  { id: "zone-yard", name: "Courtyard Yard", outline: rect(COURT_W, COURT_N, COURT_E, COURT_S), colorKey: "social" },
  {
    id: "zone-fabrication",
    name: "Robotics Lab",
    outline: rect(COURT_E, COURT_N, WIDTH, COURT_S),
    colorKey: "focus",
  },
  {
    id: "zone-crew",
    name: "Agent Floor",
    outline: rect(2.0, 18.4, 15.6, 24.8),
    colorKey: "team-a",
  },
];

// ---- The upper floor ------------------------------------------------------

/**
 * Level 2: a long room over the street, two rooms over the west range, and the
 * walkway that connects them.
 *
 * **Every coordinate below is in this level's own frame**, with its floor at
 * zero; `Plan` adds `STOREY` to all of them exactly once. It stops at the
 * courtyard's east line and at the courtyard's south line, because the east and
 * south ranges are single storey — so the upper floor is an L, and the inside
 * corner of that L is the loggia.
 *
 * The upper partitions do not line up with the ground floor's, and that is not
 * an error to be tidied: the frame here is columns and beams, the brick is on
 * the outside, and a warehouse that has been three businesses has its walls
 * wherever the last one wanted them.
 */

const L2_ROOMS: Room[] = [
  {
    id: "loft",
    name: "Model Loft",
    // Twenty-six metres of north-lit floor over the street range, with the
    // paseo tunnelling underneath it. The one room in this building that could
    // not exist on the ground floor, because on the ground floor the way in
    // goes through it.
    outline: rect(0, 0, LOFT_E, LOGGIA_N),
    floor: CARPET,
    ceiling: null,
  },
  {
    id: "palmetto",
    name: "Project Review",
    outline: rect(0, LOGGIA_N, LOGGIA_W, JESSE_N),
    floor: CARPET_ACCENT,
    ceiling: null,
  },
  {
    id: "jesse",
    name: "Materials Room",
    // The project room: concrete, three boards along the end wall, and nowhere
    // comfortable to sit down. Its equivalent downstairs is the press, and both
    // rooms read the same way for the same reason.
    outline: rect(0, JESSE_N, LOGGIA_W, COURT_S),
    floor: CONCRETE,
    ceiling: null,
  },
  /**
   * The loggia: 2.4 m of covered walkway wrapping the yard's north and west
   * sides, and the circulation of the whole upper floor.
   *
   * An L, wound the way `rect` winds — down the west side, east along the
   * bottom of the west arm, north up the courtyard edge, east along the
   * courtyard's north edge, and back. It is not a corridor with a view; it is
   * the same idea as the yard one storey up, which is why the two upper rooms
   * open onto it and the loft opens onto it twice.
   *
   * 2.4 m is the width that matters. Under about two a covered walkway is a
   * balcony you edge along; much over three and the rooms behind it stop getting
   * the light the courtyard was cut for in the first place.
   */
  {
    id: "loggia",
    name: "Model Walk",
    outline: [
      { x: LOGGIA_W, z: LOGGIA_N },
      { x: LOGGIA_W, z: COURT_S },
      { x: COURT_W, z: COURT_S },
      { x: COURT_W, z: COURT_N },
      { x: LOFT_E, z: COURT_N },
      { x: LOFT_E, z: LOGGIA_N },
    ],
    floor: WOOD,
    ceiling: null,
  },
];

const L2_WALLS: Wall[] = [
  // -- The envelope, one storey up ------------------------------------------
  //
  // The same lines as the ground floor and not the same openings, which is the
  // point of restating the walls rather than sharing them: this floor wants
  // glass where the floor below wants a pier.
  {
    id: "l2-ext-north",
    from: { x: 0, z: 0 },
    to: { x: LOFT_E, z: 0 },
    thickness: EXT_THICKNESS,
    surface: BRICK,
    openings: [
      pane(1.6, 4.0, 0.9, 2.8),
      pane(7.2, 4.0, 0.9, 2.8),
      pane(12.8, 4.0, 0.9, 2.8),
      pane(18.4, 4.0, 0.9, 2.8),
    ],
  },
  {
    id: "l2-ext-west",
    from: { x: 0, z: 0 },
    to: { x: 0, z: COURT_S },
    thickness: EXT_THICKNESS,
    surface: BRICK,
    openings: [pane(1.6, 3.4, 0.9, 2.8), pane(7.0, 2.6, 0.9, 2.8), pane(12.6, 2.6, 0.9, 2.8)],
  },
  {
    id: "l2-ext-east",
    // The party wall again, and blind again. It is what the loft's lockers and
    // its board hang on, which is a real consequence of a real constraint.
    from: { x: LOFT_E, z: 0 },
    to: { x: LOFT_E, z: COURT_N },
    thickness: EXT_THICKNESS,
    surface: BRICK,
  },
  {
    id: "l2-ext-south",
    // Where the two-storey part of the block stops. Beyond it is the roof of the
    // south range, so this is an outside wall standing on an inside line.
    from: { x: 0, z: COURT_S },
    to: { x: COURT_W, z: COURT_S },
    thickness: EXT_THICKNESS,
    surface: BRICK,
    openings: [pane(1.6, 3.0, 0.9, 2.8)],
  },

  // -- Internal --------------------------------------------------------------
  {
    id: "l2-palmetto-north",
    from: { x: 0, z: LOGGIA_N },
    to: { x: LOGGIA_W, z: LOGGIA_N },
    openings: [doorway(3.0)],
  },
  {
    id: "l2-loft-south",
    from: { x: LOGGIA_W, z: LOGGIA_N },
    to: { x: LOFT_E, z: LOGGIA_N },
    // Two arches rather than one door, because this is the wall between a
    // twenty-six-metre room and the only way out of it.
    openings: [archway(2.0, 2.4), archway(12.0, 2.4)],
  },
  {
    id: "l2-loggia-west",
    from: { x: LOGGIA_W, z: LOGGIA_N },
    to: { x: LOGGIA_W, z: COURT_S },
    surface: GLASS,
    openings: [doorway(3.6), doorway(7.6), pane(9.2, 1.6, 1.0, 2.4)],
  },
  {
    id: "l2-palmetto-jesse",
    from: { x: 0, z: JESSE_N },
    to: { x: LOGGIA_W, z: JESSE_N },
  },

  /**
   * The balustrade, in three pieces, and the gap between two of them is the way
   * up.
   *
   * A **wall** and not a prop, for the reason `office.test.ts` already asserts
   * about the reference pack's gallery: `Plan` derives the walk collider from
   * the wall list, so a rail authored as furniture is an edge you walk off. The
   * drop here is 5 m onto pavers.
   *
   * There are no openings anywhere in these three, and there is no `sill: 0,
   * head: 1.1` trick either. An opening is a way through iff `sill <= 0` and
   * `head >= 1.1`, and threading a doorway through a 1.1 m wall means authoring
   * a hole whose head is exactly the wall's height — which works, and which is a
   * clever thing that reads as a mistake. Two walls with 1.2 m of nothing
   * between them says the same and cannot be misread. The stair arrives in that
   * 1.2 m.
   */
  {
    id: "rail-north",
    from: { x: COURT_W, z: COURT_N },
    to: { x: LOFT_E, z: COURT_N },
    height: RAIL,
    surface: STEEL,
  },
  {
    id: "rail-west-n",
    from: { x: COURT_W, z: COURT_N },
    to: { x: COURT_W, z: STAIR_HEAD_N },
    height: RAIL,
    surface: STEEL,
  },
  {
    id: "rail-west-s",
    from: { x: COURT_W, z: STAIR_S },
    to: { x: COURT_W, z: COURT_S },
    height: RAIL,
    surface: STEEL,
  },
];

/**
 * Two workstations upstairs for model and environment review.
 */
const L2_DESK_BANKS: DeskBank[] = [
  {
    id: "loft-a",
    desk: DESK,
    chair: TASK_CHAIR,
    origin: { x: 8.4, z: 2.3 },
    rotation: NORTH,
    columns: 2,
    rows: 1,
    pitch: 1.7,
    rowPitch: 0.85,
    facingRows: true,
    seatOffset: 0.6,
  },
  /**
   * Six more, in the middle of the north light.
   *
   * Two workstations in a hundred and fifty-eight square metres was a room
   * labelled "Model Loft" with nobody in it. Three columns facing rows puts six
   * people under the four street windows this floor was cut for, and leaves the
   * east third — the model bay, the racks and the camera — clear.
   */
  {
    id: "loft-b",
    desk: DESK,
    chair: TASK_CHAIR,
    origin: { x: 13.4, z: 2.3 },
    rotation: NORTH,
    columns: 3,
    rows: 2,
    pitch: 1.7,
    rowPitch: 0.85,
    facingRows: true,
    seatOffset: 0.6,
  },
];

const L2_SEATS: Seat[] = [
  // One optional review perch, set back from the open edge.
  { id: "loggia-01", position: { x: 8.2, z: 12.0 }, facing: EAST, pose: "sit" },
];

const L2_PROPS: Prop[] = [
  // -- The Loft -------------------------------------------------------------
  ...scatter("loft-screen", PARTITION, grid(8.4, 2.75, 2, 1, 1.7, 0)),
  ...scatter("loft-pedestal", PEDESTAL, [
    { x: 7.0, z: 1.4 },
    { x: 7.0, z: 3.2 },
  ]),
  ...scatter("loft-locker", LOCKER, grid(LOFT_E - EXT_FACE - 0.25, 1.4, 1, 3, 0, 1.3), {
    rotation: EAST,
  }),
  {
    id: "loft-board",
    kind: WHITEBOARD,
    position: { x: LOFT_E - EXT_FACE - BOARD_OFFSET, z: 5.0 },
    rotation: EAST,
  },
  {
    id: "loft-display",
    kind: DISPLAY,
    // On the loggia wall, clear of both arches.
    position: { x: 23.0, z: LOGGIA_N - INT_FACE - DISPLAY_OFFSET },
    rotation: SOUTH,
  },
  ...scatter("loft-tree", TREE, [
    { x: 2.0, z: 4.4 },
    { x: 24.8, z: 5.4 },
  ]),
  ...scatter("loft-plant", PLANT, grid(9.2, 0.7, 3, 1, 3.4, 0)),
  // Somewhere to sit and look at a model that is not a task chair. Backed onto
  // the loggia wall and stopped 100 mm short of the eastern arch, because the
  // two arches are the only way out of a twenty-six-metre room.
  { id: "loft-sofa", kind: SOFA, position: { x: 18.0, z: LOGGIA_N - INT_FACE - 0.45 }, rotation: SOUTH },
  {
    id: "loft-rug",
    kind: RUG,
    position: { x: 18.0, z: 4.3 },
    rotation: NORTH,
    scale: [1.15, 1, 1.1],
    colorKey: "social",
  },
  { id: "loft-floor-light", kind: FLOOR_LIGHT, position: { x: 16.3, z: 5.2 }, rotation: SOUTH },
  // Six fittings where there were fourteen, on the 8 m column grid the room is
  // actually framed on rather than a 3.6 m office pitch. This is the north-lit
  // room in the building; the windows do most of the work in daylight and the
  // sofa's lamp does the rest after it.
  /**
   * The model bay, at the east end where the wall is blind.
   *
   * A "Model Loft" whose whole contents were two desks, three lockers and a rug
   * is a label rather than a room. What a model loft has is somewhere to *shoot*
   * a model — a camera on sticks, a softbox, a table to put the thing on — and
   * somewhere to keep what it is made of, which is racks and cases and a cart.
   * The softbox is `rotation: SOUTH` and lights north onto the table, the same
   * back-at-−Z rule the media studio's pair follow.
   *
   * The two racks stand against the 22.4–26.4 stretch of the street wall, which
   * is the only run of it on this floor without a window in it.
   */
  { id: "loft-table", kind: SIDE_TABLE, position: { x: 23.6, z: 2.4 }, rotation: NORTH },
  { id: "loft-tripod", kind: TRIPOD, position: { x: 23.6, z: 4.2 }, rotation: NORTH },
  { id: "loft-softbox", kind: SOFTBOX, position: { x: 22.2, z: 4.4 }, rotation: SOUTH },
  ...scatter("loft-rack", RACK, [
    { x: 23.4, z: 0.65 },
    { x: 24.6, z: 0.65 },
  ], { rotation: NORTH }),
  // The upper charge dock, beside the racks. `operations/mateo-court.ts` parks
  // `la-l2-dock` on this pad; before it existed the station was a bare point in
  // the middle of what is now the `loft-b` bench.
  { id: "loft-dock", kind: DOCK, position: { x: 22.6, z: 0.65 }, rotation: NORTH },
  ...scatter("loft-cart", TOOL_CART, [
    { x: 20.0, z: 2.4 },
    { x: 21.6, z: 3.8 },
  ]),
  ...scatter("loft-case", CASES, [
    { x: 5.6, z: 5.3 },
    { x: 6.5, z: 5.3 },
    { x: 7.4, z: 5.3 },
  ]),
  // Panels on the loggia wall, in the two stretches of it that are not an arch.
  ...scatter("loft-baffle", BAFFLE, [
    { x: 8.0, z: LOGGIA_N - INT_FACE - BOARD_OFFSET },
    { x: 21.0, z: LOGGIA_N - INT_FACE - BOARD_OFFSET },
  ], { rotation: SOUTH }),
  // Two slat screens across the run, so twenty-six metres of open floor has
  // bays rather than a horizon.
  ...scatter("loft-divider", DIVIDER, [
    { x: 12.2, z: 3.6 },
    { x: 19.0, z: 2.6 },
  ], { rotation: EAST }),
  { id: "loft-bench", kind: BENCH, position: { x: 17.6, z: 0.5 }, rotation: NORTH },
  ...scatter("loft-plant-b", PLANT, [
    { x: 20.5, z: 0.8 },
    { x: 25.0, z: 3.4 },
  ]),
  ...scatter("loft-light", TROFFER, grid(4.0, 1.8, 3, 2, 8.0, 2.8), { elevation: UPPER }),

  // -- Palmetto -------------------------------------------------------------
  { id: "palmetto-table", kind: MEETING_TABLE, position: { x: 3.6, z: 8.9 }, rotation: NORTH },
  ...scatter("palmetto-chair-n", TASK_CHAIR, grid(2.6, 7.9, 2, 1, 2.0, 0), { rotation: SOUTH }),
  ...scatter("palmetto-chair-s", TASK_CHAIR, grid(2.6, 9.9, 2, 1, 2.0, 0), { rotation: NORTH }),
  {
    id: "palmetto-display",
    kind: DISPLAY,
    position: { x: EXT_FACE + DISPLAY_OFFSET, z: 8.9 },
    rotation: WEST,
  },
  { id: "palmetto-plant", kind: PLANT, position: { x: 6.4, z: 7.0 }, rotation: NORTH },
  // Panels either side of the door onto the walk, planting on the glazed wall
  // north of its door, and a case stack in the corner. Upstairs project review
  // is the room a model gets argued about in and it was a table and a screen.
  ...scatter("palmetto-baffle", BAFFLE, [
    { x: 1.4, z: LOGGIA_N + INT_FACE + BOARD_OFFSET },
    { x: 5.6, z: LOGGIA_N + INT_FACE + BOARD_OFFSET },
  ], { rotation: NORTH }),
  { id: "palmetto-trough", kind: TROUGH, position: { x: 6.9, z: 7.4 }, rotation: EAST },
  { id: "palmetto-case", kind: CASES, position: { x: 6.4, z: 11.2 }, rotation: NORTH },
  ...scatter("palmetto-light", TROFFER, grid(2.4, 8.9, 2, 1, 3.0, 0), { elevation: UPPER }),

  // -- Jesse ----------------------------------------------------------------
  ...scatter("jesse-board", WHITEBOARD, grid(1.4, COURT_S - EXT_FACE - BOARD_OFFSET, 3, 1, 2.0, 0), {
    rotation: SOUTH,
  }),
  { id: "jesse-table", kind: MEETING_TABLE, position: { x: 3.6, z: 14.4 }, rotation: NORTH },
  ...scatter("jesse-shelf", SHELF, grid(5.0, JESSE_N + INT_FACE + 0.18, 2, 1, 1.0, 0)),
  { id: "jesse-plant", kind: PLANT, position: { x: 6.4, z: 16.8 }, rotation: NORTH },
  // The materials room gets the three kinds a materials room is: cases stacked
  // against the partition, a tool cart, and a rack on the blind stretch of the
  // west wall between its two windows. Still nowhere comfortable to sit down.
  ...scatter("jesse-case", CASES, [
    { x: 1.0, z: 12.6 },
    { x: 1.9, z: 12.6 },
  ]),
  { id: "jesse-cart", kind: TOOL_CART, position: { x: 1.2, z: 14.6 }, rotation: NORTH },
  { id: "jesse-rack", kind: RACK, position: { x: 0.6, z: 16.4 }, rotation: WEST },
  ...scatter("jesse-light", TROFFER, grid(2.4, 14.4, 2, 1, 3.0, 0), { elevation: UPPER }),

  // -- The Loggia -----------------------------------------------------------
  // Furnished as somewhere to sit and not as a route, and everything set back
  // 1.4 m from the rail. The pendants are the fittings that light the yard: they
  // hang from the soffit of this walkway, which is the only thing over the
  // courtyard for one to hang from.
  // The review perch, bound to the address it stands on. `loggia-01` is the one
  // seat on this floor that is not part of a desk bank.
  ...scatter("loggia-chair", LOUNGE_CHAIR, [
    { x: 8.2, z: 12.0, seat: "loggia-01" },
  ], { rotation: EAST }),
  { id: "loggia-side", kind: SIDE_TABLE, position: { x: 8.2, z: 12.8 }, rotation: NORTH },
  // A sofa in the corner where the walkway turns, its back to the glazed wall
  // and 1.4 m of the 2.4 m walkway still clear past it. The lamp beside it is
  // what makes the loggia somewhere to be after dark rather than a route to the
  // stair — the pendants over the rail are lighting the yard, not this.
  {
    id: "loggia-sofa",
    kind: SOFA,
    position: { x: LOGGIA_W + INT_FACE + 0.45, z: 7.8 },
    rotation: WEST,
  },
  { id: "loggia-floor-light", kind: FLOOR_LIGHT, position: { x: 8.4, z: 9.8 }, rotation: WEST },
  ...scatter("loggia-plant", PLANT, [
    { x: 8.9, z: 9.0 },
    { x: 8.9, z: 16.6 },
    { x: 16.0, z: 7.15 },
    { x: 22.0, z: 7.15 },
  ]),
  /**
   * A planted rail and three benches, and both are set out against the one
   * number that governs this walkway: it is 2.4 m wide.
   *
   * The troughs stand against the balustrade — 0.44 m deep, leaving 1.9 m of
   * clear walk — and they do two jobs at once, because the loggia is also the
   * ceiling of the courtyard: planting on this rail is what somebody standing on
   * the pavers five metres below looks up at. The benches back onto walls rather
   * than onto the rail, so nobody is sitting with a five-metre drop behind them.
   *
   * The slat screen closes the far end of the north arm. A covered walk that
   * simply stops at a wall has nothing at the end of it to walk toward, and a
   * screen you can half see through is the cheapest depth there is.
   */
  ...scatter("loggia-trough", TROUGH, [
    { x: 11.0, z: 8.1 },
    { x: 13.5, z: 8.1 },
    { x: 16.0, z: 8.1 },
    { x: 18.5, z: 8.1 },
    { x: 21.0, z: 8.1 },
    { x: 23.5, z: 8.1 },
  ], { rotation: SOUTH }),
  { id: "loggia-bench-w", kind: BENCH, position: { x: 7.6, z: 12.2 }, rotation: WEST },
  ...scatter("loggia-bench-n", BENCH, [
    { x: 17.0, z: 6.5 },
    { x: 22.4, z: 6.5 },
  ], { rotation: NORTH }),
  { id: "loggia-divider", kind: DIVIDER, position: { x: 25.6, z: 7.2 }, rotation: EAST },
  ...scatter("loggia-plant-b", PLANT, [
    { x: 8.9, z: 6.6 },
    { x: 8.9, z: 14.4 },
  ]),
  ...scatter("loggia-pendant", PENDANT, grid(8.4, 8.0, 1, 3, 0, 3.2), { elevation: UPPER }),
  ...scatter("loggia-light", PENDANT, grid(12.0, 7.2, 4, 1, 3.6, 0), { elevation: UPPER }),
];

const L2_ZONES: Zone[] = [
  {
    id: "zone-loggia",
    name: "Model Walk",
    outline: rect(LOGGIA_W, LOGGIA_N, COURT_W, COURT_S),
    colorKey: "social",
  },
];

// ---- Viewpoints -----------------------------------------------------------

/**
 * Nineteen poses, and the first one is the passage rather than the plan.
 *
 * `viewpoints[0]` is the arrival pose. `interiors/types.ts` suggests reception;
 * the reference pack argues for the establishing shot instead, on the grounds
 * that arriving inside a room before you have seen the shape of a building is
 * disorienting. This pack takes the third option and arrives **under the arch**,
 * because the one shot that shows what kind of building this is is the one taken
 * from inside the way in: a dark three-metre passage with a lit courtyard at the
 * end of it. You have seen the plan by the time you have walked eight metres,
 * which answers the reference pack's objection rather than ignoring it.
 *
 * `distance` is metres from the target to the camera and `height` is metres
 * above *this level's* floor. The camera sits at `at + (sin r, cos r) * distance`
 * and looks back at the target, so `rotation: Math.PI` is a camera to the north
 * looking south — which is how the paseo shot points down the passage.
 *
 * ### Why there are nineteen and not six
 *
 * Sixteen rooms and six poses meant twelve rooms nobody had framed, and an
 * unframed room is one nobody has looked at since they authored it. Every room
 * over 20 m² now has at least one viewpoint whose `focus.at` lands inside its
 * outline, which is a property `src/test/packs/mateoContent.test.ts` asserts
 * rather than a habit — the failure it catches is a room that quietly stops
 * being furnished because no shot in the legend would have shown it.
 *
 * The last two — `shop-floor` and `loft-bay` — are the exception to that rule
 * and are worth the duplication. Their rooms were already framed, from the yard
 * and from the far end of the loft respectively, and both of those shots are of
 * a *doorway*. The robotics lab and the model bay are where most of the studio
 * kit went, and a room you only ever see through its own opening is a room whose
 * furniture nobody has to justify.
 *
 * The order is a walk and not a ranking: in off the street, round the ground
 * floor clockwise from reception, up to the loggia, and round the upper floor.
 * `number` is a legend label, so renumbering costs nothing; the **ids** are what
 * `flyTo` is keyed on and they do not change.
 */
const VIEWPOINTS: Viewpoint[] = [
  {
    id: "paseo",
    number: "01",
    label: "The Front Door",
    shortLabel: "Front Door",
    levelId: "level-1",
    // The target is inside the passage and the camera is out on the pavement,
    // which is the whole of the change from the first version of this shot. It
    // used to stand the camera *in* the three-metre passage, which framed two
    // brick walls and the edge of a third. `focus.at` is also the walk spawn
    // (`main.ts` reads `viewpoints[0].focus.at`), so the target has to stay on a
    // floor somebody can stand on — moving the camera and leaving the target
    // where it is is the only way to have both.
    focus: { at: { x: 18.1, z: 6.8 }, distance: 14, height: 5.6, rotation: Math.PI },
    description:
      "Arrival on the street under the entry arch: a brick warehouse façade, a three-metre passage cut straight through it between planted troughs, and the lit courtyard at the far end.",
  },
  {
    id: "block",
    number: "02",
    label: "The LA Office",
    shortLabel: "LA Office",
    levelId: "level-1",
    // Aimed at the middle of the yard rather than the middle of the plate,
    // because the middle of the plate is the thing that is not there.
    focus: { at: { x: 18.0, z: 13.0 }, distance: 44, height: 26, rotation: 0.5 },
    description:
      "The complete LA HQ: a low brick ring around an open courtyard, with the agent floor, robotics lab, media studio, review room and commons visible as one working environment.",
  },
  {
    id: "lobby",
    number: "03",
    label: "The Front Desk",
    shortLabel: "Front Desk",
    levelId: "level-1",
    focus: { at: { x: 14.2, z: 5.0 }, distance: 3.6, height: 2.4, rotation: 5.6 },
    description:
      "Reception, one turn off the passage: a host position with its own screen, a mic and a speaker on the desk, low chairs and a bench, and planting at the street and courtyard windows.",
  },
  {
    id: "press",
    number: "04",
    label: "The Media Studio",
    shortLabel: "Media Studio",
    levelId: "level-1",
    focus: { at: { x: 4.6, z: 3.4 }, distance: 5.0, height: 2.4, rotation: 0.9 },
    description:
      "The media studio on the street corner: a camera on sticks between two softboxes, a lab bench and pegboard, cases stacked in the corner, and a wall of acoustic panels behind it all.",
  },
  {
    id: "mess",
    number: "05",
    label: "Kitchen and Commons",
    shortLabel: "Commons",
    levelId: "level-1",
    focus: { at: { x: 23.0, z: 3.0 }, distance: 4.6, height: 2.3, rotation: 1.0 },
    description:
      "The commons: a full kitchen run, an island with two stools, a sofa under the courtyard glazing and a slat screen dividing the cooking end from the sitting end. Where this building eats.",
  },
  {
    id: "hewitt",
    number: "06",
    label: "The Review Room",
    shortLabel: "Review Room",
    levelId: "level-1",
    focus: { at: { x: 31.2, z: 4.2 }, distance: 3.0, height: 2.1, rotation: -1.2 },
    description:
      "The review room, in the corner where two walls have no openings at all: four bound seats on a rug, a screen on the party wall flanked by acoustic panels, and a board facing it.",
  },
  {
    id: "court",
    number: "07",
    label: "In the Courtyard",
    shortLabel: "Courtyard",
    levelId: "level-1",
    // Eye level, standing on the pavers. The `yard` view further down looks at
    // the same space from the loggia, and the two are not redundant: this one is
    // what a person in the building sees, that one is what the building is.
    focus: { at: { x: 17.6, z: 13.2 }, distance: 7.0, height: 1.7, rotation: 5.0 },
    description:
      "Standing on the pavers at eye level: nine metres of open sky, parasols over the long table, planted troughs along the edges and benches under them. This is the circulation of the whole block.",
  },
  {
    id: "shop",
    number: "08",
    label: "Robotics Lab",
    shortLabel: "Robotics Lab",
    levelId: "level-1",
    // Standing in the yard looking east through the open shutter.
    focus: { at: { x: 31.0, z: 13.0 }, distance: 9, height: 2.6, rotation: 4.9 },
    description:
      "The robotics lab opens through a four-metre shutter onto the yard: five pegboard workbenches, racks, tool carts and two charge docks, with the shutter route kept clear straight through it.",
  },
  {
    id: "shop-floor",
    number: "09",
    label: "Inside the Lab",
    shortLabel: "Lab Floor",
    levelId: "level-1",
    // Standing on the concrete between the two pegboard benches, looking
    // south-east down the room. The `shop` view above is what the lab looks like
    // from the yard; this is what it looks like from inside it, and the two are
    // not redundant for the same reason the courtyard has two.
    focus: { at: { x: 31.0, z: 12.6 }, distance: 4.5, height: 2.0, rotation: -2.2 },
    description:
      "Inside the robotics lab: pegboard workbenches, equipment racks against the party wall, tool carts on the floor and a charge dock in the corner, on concrete that takes a scuff.",
  },
  {
    id: "works",
    number: "10",
    label: "Agent Floor",
    shortLabel: "Agent Floor",
    levelId: "level-1",
    focus: { at: { x: 8.6, z: 21.6 }, distance: 12, height: 4.0, rotation: 1.2 },
    description:
      "Sixteen agent workstations in two benches, split by slat screens into bays, with planting along the courtyard glazing and three robot charge docks in the piers of the alley wall.",
  },
  {
    id: "store",
    number: "11",
    label: "Equipment Storage",
    shortLabel: "Storage",
    levelId: "level-1",
    focus: { at: { x: 33.5, z: 21.8 }, distance: 3.2, height: 2.1, rotation: 0.6 },
    description:
      "Storage off the east end of the agent floor: the one room in this building with a ceiling on it and a door that shuts, which is why the equipment racks and the cases live here.",
  },
  {
    id: "willow",
    number: "12",
    label: "The Project Room",
    shortLabel: "Project Room",
    levelId: "level-1",
    focus: { at: { x: 4.4, z: 10.7 }, distance: 3.8, height: 2.0, rotation: 1.35 },
    description:
      "The project room in the west range: a table for four, a wall screen, panelled absorption on the blind wall and daylight off the alley. Where a piece of work goes for an afternoon.",
  },
  {
    id: "violet",
    number: "13",
    label: "The Quiet Room",
    shortLabel: "Quiet Room",
    levelId: "level-1",
    focus: { at: { x: 4.4, z: 15.3 }, distance: 3.8, height: 1.9, rotation: 1.35 },
    description:
      "The quiet room, and the only room here with a bed in it: two lamps, a pendant, no ceiling grid at all, a slat screen off the courtyard door, and deliberately no surface for a laptop.",
  },
  {
    id: "loggia",
    number: "14",
    label: "The Model Walk",
    shortLabel: "Model Walk",
    levelId: "level-2",
    focus: { at: { x: 8.4, z: 12.4 }, distance: 6.0, height: 1.7, rotation: Math.PI },
    description:
      "South along the covered walkway, which does upstairs what the yard does down: benches backed onto the walls, a planted rail overhanging the courtyard, and the soffit its lights hang from.",
  },
  {
    id: "yard",
    number: "15",
    label: "Courtyard Review",
    shortLabel: "From Above",
    levelId: "level-2",
    // From the loggia's west arm, looking east across the void at the far range.
    // On level 2, so `height` is measured from the walkway rather than the
    // pavers five metres below it.
    focus: { at: { x: 18.0, z: 12.0 }, distance: 9.6, height: 1.7, rotation: -Math.PI / 2 },
    description:
      "From the model walk, over the planted rail and across the open-air yard to the robotics lab shutter. Three parasols and six benches keep the circulation useful without making it an auditorium.",
  },
  {
    id: "loft",
    number: "16",
    label: "Model Loft",
    shortLabel: "Model Loft",
    levelId: "level-2",
    focus: { at: { x: 12.0, z: 3.0 }, distance: 13, height: 4.0, rotation: 1.4 },
    description:
      "Eight model-review workstations on the north-lit upper floor, with slat screens across the run and a model bay at the east end: a camera, a softbox, equipment racks and a charge dock.",
  },
  {
    id: "loft-bay",
    number: "17",
    label: "The Model Bay",
    shortLabel: "Model Bay",
    levelId: "level-2",
    // The east end of the loft, past the slat screens. West-facing camera, so
    // the north windows are down the right-hand side of frame.
    focus: { at: { x: 23.6, z: 2.4 }, distance: 4.0, height: 1.8, rotation: -Math.PI / 2 },
    description:
      "The model bay at the east end of the loft: a camera on sticks, a softbox lighting the table it points at, two equipment racks on the blind wall and a charge dock beside them.",
  },
  {
    id: "palmetto",
    number: "18",
    label: "Project Review",
    shortLabel: "Palmetto",
    levelId: "level-2",
    focus: { at: { x: 3.6, z: 8.9 }, distance: 3.2, height: 2.0, rotation: 1.3 },
    description:
      "Upstairs project review: a table for four against the west windows, a wall screen, absorption either side of the door, and the model walk two steps behind the camera.",
  },
  {
    id: "jesse",
    number: "19",
    label: "The Materials Room",
    shortLabel: "Materials",
    levelId: "level-2",
    focus: { at: { x: 3.6, z: 14.4 }, distance: 3.2, height: 2.0, rotation: 1.3 },
    description:
      "Three boards along the end wall, a bare table, cases and a tool cart on a concrete floor, and nowhere comfortable to sit down. The upstairs equivalent of the press, and it reads the same.",
  },
];

// ---- The pack -------------------------------------------------------------

const LEVEL_1: Level = {
  id: "level-1",
  name: "Ground",
  elevation: 0,
  wallHeight: GROUND,
  wallThickness: INT_THICKNESS,
  wallSurface: PAINT,
  floorplan: {
    rooms: ROOMS,
    walls: WALLS,
    props: PROPS,
    deskBanks: DESK_BANKS,
    seats: SEATS,
    zones: ZONES,
    devices: DEVICES,
  },
};

/**
 * The upper storey, at floor-to-*floor* above the ground.
 *
 * `elevation` is `STOREY` and not `GROUND`: 4.4 would bury this slab in the
 * timber it is supposed to be sitting on. `Plan` adds it to every coordinate on
 * the level exactly once, which is what lets the `L2_*` section above be read
 * without holding 5 m in your head.
 */
const LEVEL_2: Level = {
  id: "level-2",
  name: "Upper",
  elevation: STOREY,
  wallHeight: UPPER,
  wallThickness: INT_THICKNESS,
  wallSurface: PAINT,
  floorplan: {
    rooms: L2_ROOMS,
    walls: L2_WALLS,
    props: L2_PROPS,
    deskBanks: L2_DESK_BANKS,
    seats: L2_SEATS,
    zones: L2_ZONES,
  },
};

/**
 * The way up, and the record that finally makes the upper floor a place.
 *
 * Level 2 of this building — the Model Loft, the Model Bay, the Materials Room,
 * two desk banks and twenty-four props — was authored and then unreachable on
 * foot, because `interiors/walker.ts` refused any state on a storey other than
 * the one it spawned on. That is fixed, and this is the record that uses it.
 *
 * ### The dog-leg is the one the balustrade already implied
 *
 * The shape was never free. The gap in the rail is 1.2 m of nothing between
 * `rail-west-n` and `rail-west-s`, at z 9.8 to 11.0 on the yard's west line, and
 * a flight arriving there is arriving **westbound**. So the second flight runs
 * east to west along the south half of the stair's footprint, the half landing
 * is at its east end, and the first flight runs west to east along the north
 * half — which is exactly what the constants at the top of this file describe
 * and what `STAIR_HEAD_N` was put there for. Nothing about the building moved to
 * accommodate this; the transition states what was already drawn.
 *
 * Two and a half metres of rise per flight comes out at fourteen risers each at
 * the shell's 178 mm, which is the number this file's own comment claimed before
 * there were any treads to count.
 *
 * ### The footprints are small on purpose
 *
 * Standing on one *is* the input — there is no key to press — so a footprint the
 * size of the whole stair would mean walking under the flight took you up it.
 * The lower one is the bottom two metres, reachable by stepping north out of the
 * yard; the upper one is the gap in the balustrade and nothing else, which is
 * also what stops a walker strolling off a 5 m drop onto pavers. That gap has
 * been an unguarded edge since the loggia was authored and nobody could ever
 * reach it to find out.
 *
 * ### What still does not collide
 *
 * The treads are drawn geometry and, like every prop in every pack in this repo,
 * they are not in the collider — `Plan` derives collision from the wall list
 * alone. A walker crossing the north half of the stair's footprint passes
 * through the low end of the first flight in the same way it passes through a
 * desk. That is the engine's existing physics rather than something this record
 * introduces, and the fix for it is a collider that knows about furniture.
 */
const TRANSITIONS: Transition[] = [
  {
    id: "stair",
    kind: "stair",
    label: "The Stair",
    // Steel, like the floor finish it stands on, and like the balustrade it
    // arrives through.
    surface: STEEL,
    // 1.2 m, which is the width of the gap in the rail at the head of it.
    width: 1.2,
    lower: {
      levelId: "level-1",
      // The bottom of the flight, open to the yard on its south side. It stops
      // short of the wall on the west and the wall on the north because a
      // footprint is a place to stand, not a place to be inside masonry.
      footprint: [
        { x: 9.95, z: 8.75 },
        { x: 11.6, z: 8.75 },
        { x: 11.6, z: 10.5 },
        { x: 9.95, z: 10.5 },
      ],
      landing: { x: 10.2, z: 9.2 },
    },
    upper: {
      levelId: "level-2",
      // The gap in the balustrade, and only the gap: 9.15 to 9.95 in x covers
      // both sides of the rail line at 9.6, and 9.88 to 10.92 sits inside the
      // 9.8-to-11.0 opening without touching either rail's end.
      footprint: [
        { x: 9.15, z: 9.88 },
        { x: 9.95, z: 9.88 },
        { x: 9.95, z: 10.92 },
        { x: 9.15, z: 10.92 },
      ],
      landing: { x: 9.25, z: 10.4 },
    },
    legs: [
      // Up the north half, west to east: 3.9 m of run for 2.5 m of rise.
      { to: { x: 14.1, z: 9.2 }, rise: 1 },
      // The half landing, at the east end, against the yard-shop side of the
      // footprint. Flat, so its share of the rise is zero.
      { to: { x: 14.1, z: 10.4 }, rise: 0 },
      // Back west along the south half, out through the rail gap onto the
      // loggia. The last tread is the loggia's own deck.
      { to: { x: 9.25, z: 10.4 }, rise: 1 },
    ],
  },
];

export const MATEO_COURT: Office = {
  id: "mateo-court",
  name: "LA HQ · Office",
  levels: [LEVEL_1, LEVEL_2],
  viewpoints: VIEWPOINTS,
  transitions: TRANSITIONS,
  /**
   * The Arts District, on the board `cities/socal.ts` draws. See
   * `MATEO_COURT_SITE` above for where the three numbers come from and which of
   * them decides what: the short version is that 1.2 m is a loading dock, 36° is
   * the 1781 pueblo grid, and the second of those is why the loggia is on the
   * two sides of the yard it is on.
   */
  site: MATEO_COURT_SITE,
  meta: {
    description:
      "Lumbridge's LA HQ office: an authored courtyard workplace with a front door, sixteen-seat agent floor, robotics lab, media studio, review room, kitchen commons, planted yard and model loft.",
    author: "Lumbridge",
    license: "CC0-1.0",
    version: "2.2.0",
    updated: "2026-08-21",
  },
};

export default MATEO_COURT;
