1
0

feat: add active SF and LA HQ environments

This commit is contained in:
2026-08-19 02:51:17 -07:00
parent afa285bd5c
commit 0557a26e6b
14 changed files with 1096 additions and 2312 deletions
+200 -2029
View File
File diff suppressed because it is too large Load Diff
+85 -180
View File
@@ -241,8 +241,11 @@ 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";
@@ -392,7 +395,7 @@ const ROOMS: Room[] = [
// -- The street range, west to east ---------------------------------------
{
id: "press",
name: "The 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),
@@ -401,7 +404,7 @@ const ROOMS: Room[] = [
},
{
id: "lobby",
name: "The Front",
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
@@ -412,7 +415,7 @@ const ROOMS: Room[] = [
},
{
id: "paseo",
name: "The 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
@@ -424,14 +427,14 @@ const ROOMS: Room[] = [
},
{
id: "mess",
name: "The Mess",
name: "Kitchen · Commons",
outline: rect(PASEO_E, 0, MESS_E, COURT_N),
floor: TERRAZZO,
ceiling: null,
},
{
id: "hewitt",
name: "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
@@ -444,14 +447,14 @@ const ROOMS: Room[] = [
// -- The west range -------------------------------------------------------
{
id: "willow",
name: "Willow",
name: "Project Room",
outline: rect(0, COURT_N, COURT_W, WILLOW_S),
floor: CARPET_ACCENT,
ceiling: null,
},
{
id: "violet",
name: "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,
@@ -461,7 +464,7 @@ const ROOMS: Room[] = [
// -- The east range -------------------------------------------------------
{
id: "shop",
name: "The 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
@@ -474,7 +477,7 @@ const ROOMS: Room[] = [
// -- The south range ------------------------------------------------------
{
id: "works",
name: "The 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.
@@ -484,7 +487,7 @@ const ROOMS: Room[] = [
},
{
id: "store",
name: "Facilities",
name: "Equipment Storage",
outline: rect(STORE_W, COURT_S, WIDTH, DEPTH),
floor: CONCRETE,
ceiling: { height: 3.0, surface: CEILING_TILE },
@@ -504,7 +507,7 @@ const ROOMS: Room[] = [
*/
{
id: "court",
name: "The Yard",
name: "Courtyard Yard",
outline: [
{ x: COURT_W, z: STAIR_S },
{ x: COURT_W, z: COURT_S },
@@ -755,7 +758,7 @@ const WALLS: Wall[] = [
// ---- Desk banks -----------------------------------------------------------
/**
* Twenty-eight desks in two declarations, both in the works.
* Twelve desks in one declaration 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-14` counting along each
@@ -763,9 +766,9 @@ const WALLS: Wall[] = [
* 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.
*
* A second bench is a second bank, for the reason both other packs record: one
* `rowPitch` cannot be both the 0.85 m that makes two rows meet back to back and
* the 1.95 m of chair, aisle and chair between one bench and the next.
* 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.
*/
const DESK_BANKS: DeskBank[] = [
{
@@ -774,35 +777,20 @@ const DESK_BANKS: DeskBank[] = [
chair: TASK_CHAIR,
origin: { x: 3.4, z: 19.2 },
rotation: NORTH,
columns: 7,
columns: 6,
rows: 2,
pitch: 1.7, // 1.6 m desks with a 100 mm gap
rowPitch: 0.85,
facingRows: true,
seatOffset: 0.6,
},
{
id: "works-b",
desk: DESK,
chair: TASK_CHAIR,
origin: { x: 3.4, z: 23.2 },
rotation: NORTH,
columns: 7,
rows: 2,
pitch: 1.7,
rowPitch: 0.85,
facingRows: true,
seatOffset: 0.6,
},
];
// ---- Seats ----------------------------------------------------------------
/**
* The forty-one seats on this level that a `DeskBank` cannot generate: chairs
* round tables, two people waiting in reception, four standing at a pair of
* worktables — and eight at a table in the open air, which is the entry the
* other two packs have nothing like.
* 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
@@ -811,70 +799,22 @@ const DESK_BANKS: DeskBank[] = [
* the hard way, and staying disjoint costs nothing.
*/
const SEATS: Seat[] = [
// Reception, and the pair waiting for somebody to come and get them.
// One host position inside the front door.
{ id: "front-01", position: { x: 13.6, z: 5.0 }, facing: EAST, pose: "sit" },
{ id: "front-02", position: { x: 11.6, z: 6.6 }, facing: EAST, pose: "sit" },
{ id: "front-03", position: { x: 13.2, z: 6.6 }, facing: WEST, pose: "sit" },
// The press. Four standing at two worktables, because nobody sits down to cut
// a mount board — `pose` is the only thing that tells a consumer how tall to
// draw an occupant.
{ id: "press-01", position: { x: 3.8, z: 4.4 }, facing: NORTH, pose: "stand" },
{ id: "press-02", position: { x: 5.4, z: 4.4 }, facing: NORTH, pose: "stand" },
{ id: "press-03", position: { x: 3.8, z: 5.4 }, facing: SOUTH, pose: "stand" },
{ id: "press-04", position: { x: 5.4, z: 5.4 }, facing: SOUTH, pose: "stand" },
// 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" },
// The mess: three perched at the island, two at the table by the glass.
{ id: "mess-01", position: { x: 22.2, z: 4.2 }, facing: NORTH, pose: "stand" },
{ id: "mess-02", position: { x: 23.0, z: 4.2 }, facing: NORTH, pose: "stand" },
{ id: "mess-03", position: { x: 23.8, z: 4.2 }, facing: NORTH, pose: "stand" },
{ id: "mess-04", position: { x: 20.2, z: 6.6 }, facing: EAST, pose: "sit" },
{ id: "mess-05", position: { x: 23.0, z: 6.6 }, facing: WEST, pose: "sit" },
// 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" },
// Hewitt — ten round a 4.8 m table, five a side, both ends left clear so the
// people at them can see the display on the party wall.
{ id: "hewitt-01", position: { x: 29.4, z: 3.2 }, facing: SOUTH, pose: "sit" },
{ id: "hewitt-02", position: { x: 30.4, z: 3.2 }, facing: SOUTH, pose: "sit" },
{ id: "hewitt-03", position: { x: 31.4, z: 3.2 }, facing: SOUTH, pose: "sit" },
{ id: "hewitt-04", position: { x: 32.4, z: 3.2 }, facing: SOUTH, pose: "sit" },
{ id: "hewitt-05", position: { x: 33.4, z: 3.2 }, facing: SOUTH, pose: "sit" },
{ id: "hewitt-06", position: { x: 29.4, z: 5.2 }, facing: NORTH, pose: "sit" },
{ id: "hewitt-07", position: { x: 30.4, z: 5.2 }, facing: NORTH, pose: "sit" },
{ id: "hewitt-08", position: { x: 31.4, z: 5.2 }, facing: NORTH, pose: "sit" },
{ id: "hewitt-09", position: { x: 32.4, z: 5.2 }, facing: NORTH, pose: "sit" },
{ id: "hewitt-10", position: { x: 33.4, z: 5.2 }, facing: NORTH, pose: "sit" },
// Willow, six.
{ id: "willow-01", position: { x: 3.4, z: 9.7 }, facing: SOUTH, pose: "sit" },
{ id: "willow-02", position: { x: 4.4, z: 9.7 }, facing: SOUTH, pose: "sit" },
{ id: "willow-03", position: { x: 5.4, z: 9.7 }, facing: SOUTH, pose: "sit" },
{ id: "willow-04", position: { x: 3.4, z: 11.7 }, facing: NORTH, pose: "sit" },
{ id: "willow-05", position: { x: 4.4, z: 11.7 }, facing: NORTH, pose: "sit" },
{ id: "willow-06", position: { x: 5.4, z: 11.7 }, facing: NORTH, pose: "sit" },
// Violet, the quiet room: two chairs and nothing else.
{ id: "violet-01", position: { x: 4.4, z: 14.4 }, facing: SOUTH, pose: "sit" },
{ id: "violet-02", position: { x: 4.4, z: 16.2 }, facing: NORTH, pose: "sit" },
// The shop, standing at the benches.
{ id: "shop-01", position: { x: 30.2, z: 11.4 }, facing: NORTH, pose: "stand" },
{ id: "shop-02", position: { x: 31.8, z: 11.4 }, facing: NORTH, pose: "stand" },
{ id: "shop-03", position: { x: 31.0, z: 14.0 }, facing: NORTH, pose: "stand" },
/**
* The yard table. Eight seats outdoors, which is the pack's whole argument in
* one declaration: a seat is a spot on the floor with a name, the format never
* asked whether the floor had a roof over it, and a building in this climate
* puts a third of its meeting capacity in the middle of the block.
*/
{ id: "yard-01", position: { x: 17.2, z: 12.4 }, facing: SOUTH, pose: "sit" },
{ id: "yard-02", position: { x: 18.4, z: 12.4 }, facing: SOUTH, pose: "sit" },
{ id: "yard-03", position: { x: 19.6, z: 12.4 }, facing: SOUTH, pose: "sit" },
{ id: "yard-04", position: { x: 20.8, z: 12.4 }, facing: SOUTH, pose: "sit" },
{ id: "yard-05", position: { x: 17.2, z: 14.4 }, facing: NORTH, pose: "sit" },
{ id: "yard-06", position: { x: 18.4, z: 14.4 }, facing: NORTH, pose: "sit" },
{ id: "yard-07", position: { x: 19.6, z: 14.4 }, facing: NORTH, pose: "sit" },
{ id: "yard-08", position: { x: 20.8, z: 14.4 }, 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 ----------------------------------------------------------------
@@ -953,10 +893,9 @@ const PROPS: Prop[] = [
...scatter("press-light", TROFFER, grid(1.6, 1.6, 3, 3, 3.2, 2.6), { elevation: GROUND }),
// -- The Mess -------------------------------------------------------------
// A meeting table doing duty as an island, exactly as the reference pack's
// kitchen does. There is no `tera:kitchen.island` and there should not be; the
// honest way to get one is to register it in your own namespace.
{ id: "mess-island", kind: MEETING_TABLE, position: { x: 23.0, z: 3.0 }, rotation: NORTH },
// 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,
@@ -964,13 +903,7 @@ const PROPS: Prop[] = [
rotation: NORTH,
elevation: 0.74,
},
...scatter("mess-stool", TASK_CHAIR, grid(22.2, 4.2, 3, 1, 0.8, 0), { rotation: NORTH }),
// The counters run down the wall the building's only internal door on this
// range is in, not under the street windows: a 1.8 m locker in front of a
// window with a 1 m sill is a wall with a window nobody can see out of.
...scatter("mess-counter", LOCKER, grid(MESS_E - INT_FACE - 0.25, 1.4, 1, 3, 0, 1.3), {
rotation: EAST,
}),
...scatter("mess-stool", STOOL, grid(22.6, 4.2, 2, 1, 0.9, 0), { rotation: NORTH }),
{
id: "mess-shelf",
kind: SHELF,
@@ -985,10 +918,9 @@ const PROPS: Prop[] = [
...scatter("mess-light", TROFFER, grid(20.6, 2.0, 3, 2, 2.6, 3.4), { elevation: GROUND }),
// -- Hewitt ---------------------------------------------------------------
{ id: "hewitt-table-w", kind: MEETING_TABLE, position: { x: 30.4, z: 4.2 }, rotation: NORTH },
{ id: "hewitt-table-e", kind: MEETING_TABLE, position: { x: 32.8, z: 4.2 }, rotation: NORTH },
...scatter("hewitt-chair-n", TASK_CHAIR, grid(29.4, 3.2, 5, 1, 1.0, 0), { rotation: SOUTH }),
...scatter("hewitt-chair-s", TASK_CHAIR, grid(29.4, 5.2, 5, 1, 1.0, 0), { rotation: NORTH }),
{ id: "hewitt-table", kind: MEETING_TABLE, position: { x: 31.2, z: 4.2 }, rotation: NORTH },
...scatter("hewitt-chair-n", TASK_CHAIR, grid(29.8, 3.2, 2, 1, 2.8, 0), { rotation: SOUTH }),
...scatter("hewitt-chair-s", TASK_CHAIR, grid(29.8, 5.2, 2, 1, 2.8, 0), { rotation: NORTH }),
{
id: "hewitt-display",
kind: DISPLAY,
@@ -1009,8 +941,8 @@ const PROPS: Prop[] = [
// -- 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, 3, 1, 1.0, 0), { rotation: SOUTH }),
...scatter("willow-chair-s", TASK_CHAIR, grid(3.4, 11.7, 3, 1, 1.0, 0), { 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,
@@ -1063,15 +995,12 @@ const PROPS: Prop[] = [
// -- 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, 7, 1, 1.7, 0)),
...scatter("works-b-screen", PARTITION, grid(3.4, 23.625, 7, 1, 1.7, 0)),
...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: 2.0, z: 23.6 },
{ x: 14.8, z: 19.6 },
{ x: 14.8, z: 23.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)),
{
@@ -1127,10 +1056,9 @@ const PROPS: Prop[] = [
* light this space in the evening are on the loggia above, hung from a soffit
* that exists.
*/
{ id: "yard-table-w", kind: MEETING_TABLE, position: { x: 18.0, z: 13.4 }, rotation: NORTH },
{ id: "yard-table-e", kind: MEETING_TABLE, position: { x: 20.4, z: 13.4 }, rotation: NORTH },
...scatter("yard-chair-n", TASK_CHAIR, grid(17.2, 12.4, 4, 1, 1.2, 0), { rotation: SOUTH }),
...scatter("yard-chair-s", TASK_CHAIR, grid(17.2, 14.4, 4, 1, 1.2, 0), { rotation: NORTH }),
{ 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 },
@@ -1158,19 +1086,18 @@ const PROPS: Prop[] = [
* twice.
*/
const ZONES: Zone[] = [
{ id: "zone-yard", name: "The Yard", outline: rect(COURT_W, COURT_N, COURT_E, COURT_S), colorKey: "social" },
{ id: "zone-yard", name: "Courtyard Yard", outline: rect(COURT_W, COURT_N, COURT_E, COURT_S), colorKey: "social" },
{
id: "zone-fabrication",
name: "Fabrication",
name: "Robotics Lab",
outline: rect(COURT_E, COURT_N, WIDTH, COURT_S),
colorKey: "focus",
},
{
id: "zone-crew",
name: "Crew",
name: "Agent Floor",
outline: rect(2.0, 18.4, 15.6, 24.8),
colorKey: "team-a",
audience: "private",
},
];
@@ -1195,7 +1122,7 @@ const ZONES: Zone[] = [
const L2_ROOMS: Room[] = [
{
id: "loft",
name: "The 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
@@ -1206,14 +1133,14 @@ const L2_ROOMS: Room[] = [
},
{
id: "palmetto",
name: "Palmetto",
name: "Project Review",
outline: rect(0, LOGGIA_N, LOGGIA_W, JESSE_N),
floor: CARPET_ACCENT,
ceiling: null,
},
{
id: "jesse",
name: "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.
@@ -1237,7 +1164,7 @@ const L2_ROOMS: Room[] = [
*/
{
id: "loggia",
name: "The Loggia",
name: "Model Walk",
outline: [
{ x: LOGGIA_W, z: LOGGIA_N },
{ x: LOGGIA_W, z: COURT_S },
@@ -1367,19 +1294,17 @@ const L2_WALLS: Wall[] = [
];
/**
* Twelve desks upstairs, in one bank. Seat ids are `loft-a-01` … `loft-a-12`,
* restarting at 01 as every bank's do and unique building-wide because the
* prefix is.
* 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: 1.9 },
origin: { x: 8.4, z: 2.3 },
rotation: NORTH,
columns: 6,
rows: 2,
columns: 2,
rows: 1,
pitch: 1.7,
rowPitch: 0.85,
facingRows: true,
@@ -1388,34 +1313,16 @@ const L2_DESK_BANKS: DeskBank[] = [
];
const L2_SEATS: Seat[] = [
// Palmetto, six round a 2.4 m table.
{ id: "palmetto-01", position: { x: 2.6, z: 7.9 }, facing: SOUTH, pose: "sit" },
{ id: "palmetto-02", position: { x: 3.6, z: 7.9 }, facing: SOUTH, pose: "sit" },
{ id: "palmetto-03", position: { x: 4.6, z: 7.9 }, facing: SOUTH, pose: "sit" },
{ id: "palmetto-04", position: { x: 2.6, z: 9.9 }, facing: NORTH, pose: "sit" },
{ id: "palmetto-05", position: { x: 3.6, z: 9.9 }, facing: NORTH, pose: "sit" },
{ id: "palmetto-06", position: { x: 4.6, z: 9.9 }, facing: NORTH, pose: "sit" },
// Jesse. Standing at the boards, like the reference pack's project room.
{ id: "jesse-01", position: { x: 1.4, z: 16.4 }, facing: SOUTH, pose: "stand" },
{ id: "jesse-02", position: { x: 3.4, z: 16.4 }, facing: SOUTH, pose: "stand" },
{ id: "jesse-03", position: { x: 5.4, z: 16.4 }, facing: SOUTH, pose: "stand" },
// The loggia, looking down into the yard. Set back from the rail, because a
// chair against a balustrade is a chair nobody chooses.
// One optional review perch, set back from the open edge.
{ id: "loggia-01", position: { x: 8.2, z: 12.0 }, facing: EAST, pose: "sit" },
{ id: "loggia-02", position: { x: 8.2, z: 13.6 }, facing: EAST, pose: "sit" },
{ id: "loggia-03", position: { x: 12.0, z: 6.9 }, facing: SOUTH, pose: "sit" },
];
const L2_PROPS: Prop[] = [
// -- The Loft -------------------------------------------------------------
...scatter("loft-screen", PARTITION, grid(8.4, 2.325, 6, 1, 1.7, 0)),
...scatter("loft-screen", PARTITION, grid(8.4, 2.325, 2, 1, 1.7, 0)),
...scatter("loft-pedestal", PEDESTAL, [
{ x: 7.0, z: 1.4 },
{ x: 7.0, z: 3.2 },
{ x: 18.4, z: 1.4 },
{ x: 18.4, z: 3.2 },
]),
...scatter("loft-locker", LOCKER, grid(LOFT_E - EXT_FACE - 0.25, 1.4, 1, 3, 0, 1.3), {
rotation: EAST,
@@ -1442,8 +1349,8 @@ const L2_PROPS: Prop[] = [
// -- 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, 3, 1, 1.0, 0), { rotation: SOUTH }),
...scatter("palmetto-chair-s", TASK_CHAIR, grid(2.6, 9.9, 3, 1, 1.0, 0), { 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,
@@ -1469,9 +1376,7 @@ const L2_PROPS: Prop[] = [
// courtyard for one to hang from.
...scatter("loggia-chair", LOUNGE_CHAIR, [
{ x: 8.2, z: 12.0 },
{ x: 8.2, z: 13.6 },
], { rotation: EAST }),
{ id: "loggia-chair-n", kind: LOUNGE_CHAIR, position: { x: 12.0, z: 6.9 }, rotation: SOUTH },
{ id: "loggia-side", kind: SIDE_TABLE, position: { x: 8.2, z: 12.8 }, rotation: NORTH },
...scatter("loggia-plant", PLANT, [
{ x: 8.9, z: 9.0 },
@@ -1486,7 +1391,7 @@ const L2_PROPS: Prop[] = [
const L2_ZONES: Zone[] = [
{
id: "zone-loggia",
name: "The Loggia",
name: "Model Walk",
outline: rect(LOGGIA_W, LOGGIA_N, COURT_W, COURT_S),
colorKey: "social",
},
@@ -1515,68 +1420,68 @@ const VIEWPOINTS: Viewpoint[] = [
{
id: "paseo",
number: "01",
label: "The Paseo",
shortLabel: "Paseo",
label: "The Front Door",
shortLabel: "Front Door",
levelId: "level-1",
focus: { at: { x: 18.1, z: 9.0 }, distance: 8, height: 1.7, rotation: Math.PI },
description:
"Standing under the street arch looking down the passage into the yard. Three metres wide and eight and a half from the pavement to the yard, and the only way into this building anybody arrives by — everything else here is on the other side of it.",
"Arrival under the street arch, looking down the entry passage into the courtyard. The route stays clear from the front door to every ground-floor workspace.",
},
{
id: "block",
number: "02",
label: "The Whole Block",
shortLabel: "The Block",
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:
"Thirty-six metres by twenty-six with a hole in the middle of it. A ring of rooms never more than nine and a half metres deep on the long sides, two storeys on two sides and one on the other two, and the courtyard doing the job a corridor does in every other pack in this directory.",
"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: "yard",
number: "03",
label: "The Yard",
shortLabel: "Yard",
label: "Courtyard Review",
shortLabel: "Courtyard",
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 walkway, looking across the middle of the block. Sixteen metres of open air with a table in it, the shop's shutter on the far side, and nothing overhead — the one room in this office that is not a room.",
"From the model walk, across the open-air review yard toward the robotics lab. Four loose chairs keep the courtyard useful without turning circulation into an auditorium.",
},
{
id: "works",
number: "04",
label: "The Works",
shortLabel: "Works",
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:
"Twenty-eight seats in two benches along the back of the block, glazed onto the yard on one side and the alley on the other. The bench nearer the yard is works-a-01 through works-a-14.",
"Twelve agent workstations in one focused bench, leaving the rest of the long floor clear for rolling prototypes, activity robots and changing environment work.",
},
{
id: "shop",
number: "05",
label: "The Shop",
shortLabel: "Shop",
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:
"Four metres of roller shutter onto the courtyard, with the benches inside it and the party wall behind them. Work comes out of this room and gets finished in the middle of the building.",
"The robotics lab opens through a four-metre shutter onto the yard, keeping benches, storage and test circulation connected without inventing a separate facility.",
},
{
id: "loft",
number: "06",
label: "The Loft",
shortLabel: "Loft",
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:
"Twenty-six metres of unbroken floor over the street range, with the paseo tunnelling under the far end of it. Twelve seats, north light on one side and the loggia on the other.",
"Two model-review workstations on the north-lit upper floor, beside boards, display surfaces and the open model walk above the yard.",
},
];
@@ -1626,7 +1531,7 @@ const LEVEL_2: Level = {
export const MATEO_COURT: Office = {
id: "mateo-court",
name: "Mateo Court",
name: "LA HQ · Office",
levels: [LEVEL_1, LEVEL_2],
viewpoints: VIEWPOINTS,
/**
@@ -1639,11 +1544,11 @@ export const MATEO_COURT: Office = {
site: MATEO_COURT_SITE,
meta: {
description:
"A courtyard block in the Arts District: a ring of rooms round a yard with the roof off, an outdoor stair and an open walkway over it. The third pack, and the one where the circulation is outside.",
"Lumbridge's LA HQ office: an authored courtyard workplace with a front door, twelve-seat agent floor, robotics lab, media studio, four-seat review room, kitchen commons, yard and two-seat model loft.",
author: "Lumbridge",
license: "CC0-1.0",
version: "1.0.0",
updated: "2026-08-07",
version: "2.0.0",
updated: "2026-08-19",
},
};
+52
View File
@@ -0,0 +1,52 @@
/**
* Small, deterministic runtime facts for the shipped office environments.
*
* Activity robots are authored here rather than inferred from storey count. A
* floor is geometry, not a staffing instruction: the SF studio should have one
* useful machine, LA has two distinct work areas, and a building environment
* still under construction should not wake a synthetic workforce merely
* because it has levels. The extra job metadata is ignored by today's wander
* controller but is deliberately carried beside `RobotSpec`, ready for the
* robot-jobs runtime without changing stable robot ids.
*/
import type { RobotSpec } from "../interiors/robots.ts";
import type { Office } from "../interiors/types.ts";
import type { ShippedOfficeId } from "./sites.ts";
export type OfficeRobotJobSet = "studio-support" | "office-operations" | "model-review";
export interface OfficeActivityRobotSpec extends RobotSpec {
id: string;
/** Stable per-robot seed for a future job scheduler or replay harness. */
seed: number;
jobSet: OfficeRobotJobSet;
}
const OFFICE_ACTIVITY_ROBOTS: Readonly<Record<ShippedOfficeId, readonly OfficeActivityRobotSpec[]>> = {
"lumbridge-hq": [
{ id: "sf-studio-activity-01", levelId: "level-1", seed: 0x53464851, jobSet: "studio-support" },
],
"frontier-valley": [],
"mateo-court": [
{ id: "la-office-activity-01", levelId: "level-1", seed: 0x4c414851, jobSet: "office-operations" },
{ id: "la-office-activity-02", levelId: "level-2", seed: 0x4c414852, jobSet: "model-review" },
],
};
/**
* Resolve fresh specs and discard any whose authored level is absent.
*
* The level check keeps downloaded or locally modified packs safe, while fresh
* objects stop one consumer's scheduler state from mutating the next scene or
* determinism test.
*/
export function activityRobotsForOffice(
office: Pick<Office, "id" | "levels">,
): OfficeActivityRobotSpec[] {
const specs = OFFICE_ACTIVITY_ROBOTS[office.id as ShippedOfficeId] ?? [];
const levels = new Set(office.levels.map((level) => level.id));
return specs
.filter((spec) => levels.has(spec.levelId))
.map((spec) => ({ ...spec }));
}
+47 -4
View File
@@ -100,8 +100,51 @@ export const MATEO_COURT_SITE: OfficeSite = {
* them and by `office.test.ts` asserting that each pack's site is the very
* object named here — identity, not equality, so a restated coordinate fails.
*/
export const OFFICE_SITES: { id: string; name: string; site: OfficeSite }[] = [
{ id: "lumbridge-hq", name: "Lumbridge HQ", site: LUMBRIDGE_HQ_SITE },
{ id: "frontier-valley", name: "Frontier Valley", site: FRONTIER_VALLEY_SITE },
{ id: "mateo-court", name: "Mateo Court", site: MATEO_COURT_SITE },
export const SHIPPED_OFFICE_IDS = ["lumbridge-hq", "frontier-valley", "mateo-court"] as const;
export type ShippedOfficeId = (typeof SHIPPED_OFFICE_IDS)[number];
export type OfficeStatus = "active" | "building";
/**
* The public, eager metadata for one shipped environment.
*
* `status` is about the authored runtime, not a lease or a headcount. `active`
* means the environment is ready to enter; it never implies that people are in
* the building. `entryCopy` is intentionally operational and contains no
* invented street address or staffing claim.
*/
export interface OfficeDirectoryEntry {
id: ShippedOfficeId;
name: string;
status: OfficeStatus;
entryCopy: string;
site: OfficeSite;
}
export const OFFICE_SITES: readonly OfficeDirectoryEntry[] = [
{
id: "lumbridge-hq",
name: "SF HQ · Studio",
status: "active",
entryCopy: "Enter the compact live/work studio",
site: LUMBRIDGE_HQ_SITE,
},
{
id: "frontier-valley",
name: "Frontier Valley",
status: "building",
entryCopy: "Preview the hangar environment in progress",
site: FRONTIER_VALLEY_SITE,
},
{
id: "mateo-court",
name: "LA HQ · Office",
status: "active",
entryCopy: "Enter the courtyard office and robotics lab",
site: MATEO_COURT_SITE,
},
];
export function officeDirectoryEntry(id: string): OfficeDirectoryEntry | undefined {
return OFFICE_SITES.find((entry) => entry.id === id);
}