The office learns where it stands, and the sky stops being a backdrop
**Aircraft actually move now, and the reason they did not is the headline.**
`HttpFlights` holds a frozen snapshot between network refreshes and is
polled at 1 Hz, so a live feed handed the layer the same position five to
fifteen times and then jumped. `span` therefore measured the poll interval
rather than the gap between the two positions that differ, the teleport
test saw an airliner covering 36 units in a "second" against a ceiling of
8, and **every live track's history was wiped on every refresh** — so no
aircraft on the deployed site could ever grow a trail, however long
TRAIL_POINTS was set. Skipping the repeat fixes the motion and the trail
at once. Trails then go to 72 points / 240 s, which is about seventy
seconds of flying.
Three more defects in the same file, found while looking: trail
truncation dropped the segments nearest the aircraft (leaving a streak
with no aeroplane attached), MAX_TRACKS was declared and never enforced,
and one missing target deleted its whole trail. The buffer now uploads
only what it wrote, rather than 46 MB/s of untouched array.
**You can get above the constellation.** Dome to 1.05 board *radii* and
the orbit to 2.0 spans. Radii, not spans: scene space is centred on the
city and the Bay Area board runs forty kilometres down the peninsula, so
the furthest corner is 0.94 spans out where the half-diagonal is 0.65 —
sized off the half-diagonal the dome sat inside its own city. The far
plane goes to 4 spans to stop clipping the sky from off-centre chapters,
and `PointsMaterial` defaults `fog: true`, which was quietly dimming the
whole constellation with the city's haze.
**An office can say where it stands.** `Office.site` — lat, lng, height
above the ground outside, and the compass bearing the pack's −Z points
along — and with one it gets the same sun the city does, a sky, and a
horizon at `-elevation`. CONTRACT §4 reserved this as "a later
refinement"; it is taken up rather than overturned, and `daylight.ts`
computes no light of its own. It does the two things a room needs that a
map does not: turn the sun into the building's frame, and move the fog
outdoors before it greys out the far wall.
Two buildings now, and they are deliberately unalike: Lumbridge HQ 188 m
up a Transbay tower facing 205°, and **Frontier Valley**, a startup in a
hangar at Alameda Point — one room, 54 x 30 m, nine metres to the
trusses, four metres above reclaimed ground.
Floor-to-floor in the reference pack is now 16.8 m: the interstitial is
ten times a real one, so the space between the slabs is somewhere things
can hang. It is frankly not architecture, `PLENUM` is the one number to
change, and the file says so.
Also fixed, all found by review rather than by looking at the screen:
- `sun.shadow.camera.updateProjectionMatrix()` was never called, so
three's default ±5 unit box has been in force this whole time and
every `shadowExtent` this repo passes — including the city's ±752 —
has been silently ignored.
- A missing aircraft was kept alive by the new grace period and *drawn*,
so it froze in mid-air at full opacity for 32 s.
- Frontier Valley's mezzanine was a `Room`, which carries no height: its
slab lay on the concrete, its chairs floated 4.4 m over it, and its
balustrade fenced off a patch of ground floor. It is a `Level`.
- Overlapping floor slabs z-fought. The format permits overlap and
resolves later-first, so `shell.ts` now lifts a slab a hair per
earlier slab it overlaps — and by nothing at all in a pack, like the
reference office, whose rooms only ever abut.
- `switchOffice` bypassed the `entering` guard (leaking a whole scene
per double-click) and tore down the old room before knowing the new
one would load, with no way back.
Known and not fixed: raising MAX_SPAN to 30 s doubles the worst-case
re-base snap when a feed's gap shortens. It is bounded, pre-existing in
kind, and the fix wants carrying the live head into the next leg.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+32
-3
@@ -66,9 +66,38 @@ going *down* is what makes the winding rule counter-intuitive:
|
||||
a rectangle, and which has a *negative* shoelace area over `(x, z)`.
|
||||
- Get it wrong and nothing breaks: `Plan` silently re-winds a reversed polygon.
|
||||
|
||||
An office has no orientation on the earth. Calling an edge "north" is a
|
||||
convenience for reading your own file. Offices get no `Atmosphere`, no sun and
|
||||
no sky (CONTRACT.md §4); interior lighting is a fixed rig owned by the scene.
|
||||
By default an office has no orientation on the earth, and calling an edge
|
||||
"north" is a convenience for reading your own file. A pack with no `site` gets
|
||||
no `Atmosphere`, no sun and no sky (CONTRACT.md §4); interior lighting is a
|
||||
fixed rig owned by the scene, and that is a supported, permanent state — you can
|
||||
author a whole building without owning a coordinate.
|
||||
|
||||
Say where the building stands and the convenience becomes a fact:
|
||||
|
||||
```ts
|
||||
site: {
|
||||
lat: 37.7756,
|
||||
lng: -122.3186,
|
||||
elevation: 4, // metres above the ground OUTSIDE, not above sea level
|
||||
heading: 0, // compass bearing, in degrees, that the pack's −Z points along
|
||||
label: "Alameda Point",
|
||||
},
|
||||
```
|
||||
|
||||
Then the pack gets the real sun for that place at the app's clock, a sky, and a
|
||||
horizon `elevation` metres below the level-0 floor. **`heading` is the field that
|
||||
matters most and the easiest to leave wrong**: it is what decides which of your
|
||||
walls the light actually comes through. `0` means your "north" really is north.
|
||||
|
||||
`elevation` is the other one worth thinking about, because it is what the horizon
|
||||
is measured from — the difference between an office on the 48th floor and a shed
|
||||
on an airfield is one number, and it is this one. Both shipped packs are worked
|
||||
examples: `lumbridge-hq.ts` is 188 m up and rotated 205°, `frontier-valley.ts` is
|
||||
4 m up and square to the compass.
|
||||
|
||||
Nothing here is geocoded and nothing can be. These are numbers you type, like
|
||||
every other number in a pack — see CONTRACT.md §8 for why a coordinate's
|
||||
provenance is a licensing question in this repo.
|
||||
|
||||
## Rooms are slabs. Walls are segments.
|
||||
|
||||
|
||||
@@ -0,0 +1,692 @@
|
||||
/**
|
||||
* Frontier Valley — a startup in a hangar at Alameda Point.
|
||||
*
|
||||
* The second office pack, and it exists to prove the format describes more than
|
||||
* one kind of building. `lumbridge-hq.ts` is the dev kit: a corridor, fifteen
|
||||
* rooms, a grid, everything a commercial floor plate has. This is the opposite
|
||||
* building in every respect that matters, and the contrast is the point.
|
||||
*
|
||||
* ### Why a hangar, and why here
|
||||
*
|
||||
* Alameda Point is the former Naval Air Station Alameda, decommissioned in 1997:
|
||||
* a mile of runway, a seaplane lagoon, and a row of enormous steel-framed
|
||||
* hangars that have spent the last quarter century being rented to distilleries,
|
||||
* film crews and companies that need a very large room cheaply. Putting a
|
||||
* startup in one is not a conceit — it is the single most characteristic thing
|
||||
* that happens on that piece of land.
|
||||
*
|
||||
* It also gives the site field something to say. `lumbridge-hq` is 188 m up a
|
||||
* tower with the horizon far below it; this is **four metres above reclaimed
|
||||
* ground on a flat island**, looking across the estuary at the city that the
|
||||
* other office is inside. Same engine, same clock, same sun — and the two feel
|
||||
* nothing like each other, which is the whole argument for `OfficeSite`.
|
||||
*
|
||||
* ### One level, one room, and a mezzanine that is furniture
|
||||
*
|
||||
* A hangar is a single volume. There is no corridor here because there is
|
||||
* nothing to connect: the meeting rooms are freestanding boxes dropped on the
|
||||
* slab, with their own low lids, and everything else is open floor. That is
|
||||
* expressible in this format without a single new feature, which is worth
|
||||
* knowing — the format was written against a cellular office and turns out to
|
||||
* describe a shed just as well.
|
||||
*
|
||||
* ### The coordinate frame
|
||||
*
|
||||
* Metres, `1 unit = 1 m`, floor on the XZ plane with +Y up. Origin at the
|
||||
* north-west corner. +X east, +Z south, so in plan view +Z runs down the page.
|
||||
* The one difference from the reference pack is that here "north" is a genuine
|
||||
* claim: `site.heading` is 0, because the hangars at Alameda Point really are
|
||||
* laid out square to the compass along the old runways.
|
||||
*/
|
||||
|
||||
import type {
|
||||
AssetId,
|
||||
DeskBank,
|
||||
Level,
|
||||
Office,
|
||||
Opening,
|
||||
Outline,
|
||||
Point2,
|
||||
Prop,
|
||||
Room,
|
||||
Seat,
|
||||
Viewpoint,
|
||||
Wall,
|
||||
Yaw,
|
||||
Zone,
|
||||
} from "../interiors/types.ts";
|
||||
|
||||
// ---- The shed -------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A hangar, in three numbers.
|
||||
*
|
||||
* 54 x 30 m is a small one by Alameda standards — the surviving hangars on the
|
||||
* seaplane lagoon run to twice this — but it is a plausible sublet, and it is
|
||||
* already three times the floor area of the reference office's desk floor with
|
||||
* nothing standing in it.
|
||||
*/
|
||||
const WIDTH = 54.0;
|
||||
const DEPTH = 30.0;
|
||||
|
||||
/**
|
||||
* Nine metres to the underside of the trusses.
|
||||
*
|
||||
* This is the number that makes it a hangar rather than a warehouse-themed
|
||||
* office. A commercial storey is 2.8 m; at 9 m the roof is somewhere you look
|
||||
* *up* at, the meeting boxes read as objects standing in a room rather than as
|
||||
* rooms carved out of one, and a mezzanine is a thing you can put under it.
|
||||
*/
|
||||
const RIDGE = 9.0;
|
||||
|
||||
/** The freestanding boxes get their own low lids. A booth in a shed is a box. */
|
||||
const BOX_CEILING = 3.0;
|
||||
|
||||
const EXT_THICKNESS = 0.35;
|
||||
const INT_THICKNESS = 0.12;
|
||||
const EXT_FACE = EXT_THICKNESS / 2;
|
||||
const INT_FACE = INT_THICKNESS / 2;
|
||||
|
||||
const DISPLAY_OFFSET = 0.07;
|
||||
const BOARD_OFFSET = 0.06;
|
||||
|
||||
/**
|
||||
* The mezzanine deck.
|
||||
*
|
||||
* It is a `Level`, and getting there took one wrong turn worth recording. A deck
|
||||
* inside a single volume reads like furniture, so it was first authored as a
|
||||
* `Room` on the ground floor with everything on it carrying `elevation: 4.4`.
|
||||
* That is not expressible: a `Room` is a floor *finish* and has no height, so
|
||||
* the deck's slab lay flat on the concrete while its chairs floated four and a
|
||||
* half metres over it, its glass balustrade fenced off a patch of ground-floor
|
||||
* slab, and anybody sitting at `mezz-01` stood on the concrete underneath their
|
||||
* own chair.
|
||||
*
|
||||
* A `Level` is the only thing in this format that carries a floor height, so the
|
||||
* deck is one — with a storey height of `RIDGE - MEZZ_HEIGHT`, because what is
|
||||
* above it is the same roof.
|
||||
*/
|
||||
const MEZZ_W = 38.0;
|
||||
const MEZZ_N = 20.4;
|
||||
const MEZZ_HEIGHT = 4.4;
|
||||
|
||||
// The three freestanding boxes along the west end.
|
||||
const BOX_1_W = 3.0;
|
||||
const BOX_1_E = 10.2;
|
||||
const BOX_2_W = 11.4;
|
||||
const BOX_2_E = 17.4;
|
||||
const BOX_N = 3.0;
|
||||
const BOX_S = 9.6;
|
||||
|
||||
// ---- Yaw ------------------------------------------------------------------
|
||||
|
||||
const NORTH: Yaw = 0;
|
||||
const EAST: Yaw = -Math.PI / 2;
|
||||
const SOUTH: Yaw = Math.PI;
|
||||
const WEST: Yaw = Math.PI / 2;
|
||||
|
||||
// ---- Assets ---------------------------------------------------------------
|
||||
|
||||
const DESK: AssetId = "tera:desk.workstation";
|
||||
const PEDESTAL: AssetId = "tera:desk.pedestal";
|
||||
const TASK_CHAIR: AssetId = "tera:seat.task-chair";
|
||||
const LOUNGE_CHAIR: AssetId = "tera:seat.lounge";
|
||||
const MEETING_TABLE: AssetId = "tera:table.meeting";
|
||||
const SIDE_TABLE: AssetId = "tera:table.side";
|
||||
const SHELF: AssetId = "tera:storage.shelf";
|
||||
const LOCKER: AssetId = "tera:storage.locker";
|
||||
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 RUG: AssetId = "tera:rug";
|
||||
const WHITEBOARD: AssetId = "tera:whiteboard";
|
||||
|
||||
const CONCRETE = "tera:concrete.polished";
|
||||
const WOOD = "tera:wood.plank";
|
||||
const CARPET_ACCENT = "tera:carpetAccent.broadloom";
|
||||
const PAINT = "tera:paint.matt";
|
||||
const ACCENT_PAINT = "tera:plasterAccent.deep";
|
||||
const GLASS = "tera:glass.curtain";
|
||||
const STEEL = "tera:steel.panel";
|
||||
const FELT = "tera:felt.acoustic";
|
||||
|
||||
// ---- Helpers --------------------------------------------------------------
|
||||
|
||||
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 },
|
||||
];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function scatter(
|
||||
prefix: string,
|
||||
kind: AssetId,
|
||||
points: Point2[],
|
||||
opts: { rotation?: Yaw; elevation?: number; colorKey?: string } = {},
|
||||
): Prop[] {
|
||||
return points.map((position, i) => ({
|
||||
id: `${prefix}-${String(i + 1).padStart(2, "0")}`,
|
||||
kind,
|
||||
position,
|
||||
rotation: opts.rotation ?? NORTH,
|
||||
elevation: opts.elevation,
|
||||
colorKey: opts.colorKey,
|
||||
}));
|
||||
}
|
||||
|
||||
function doorway(start: number, width = 0.9, head = 2.1): Opening {
|
||||
return { kind: "door", start, width, sill: 0, head };
|
||||
}
|
||||
|
||||
function pane(start: number, width: number, sill = 0.9, head = 2.2): Opening {
|
||||
return { kind: "window", start, width, sill, head };
|
||||
}
|
||||
|
||||
function archway(start: number, width: number, head = 2.4): Opening {
|
||||
return { kind: "arch", start, width, sill: 0, head };
|
||||
}
|
||||
|
||||
// ---- Rooms ----------------------------------------------------------------
|
||||
|
||||
const ROOMS: Room[] = [
|
||||
/**
|
||||
* The slab. One room, fifty-four by thirty, notched around nothing.
|
||||
*
|
||||
* Every other "room" in this pack sits on top of this one as a different floor
|
||||
* finish, and they are authored *after* it so `Plan.roomAt` resolves them
|
||||
* first. That is the one ordering rule in the format and it is doing real work
|
||||
* here: in the reference office the floor is a jigsaw of abutting slabs, and
|
||||
* here it is one slab with rugs on it.
|
||||
*/
|
||||
{
|
||||
id: "floor",
|
||||
name: "The Floor",
|
||||
outline: rect(0, 0, WIDTH, DEPTH),
|
||||
floor: CONCRETE,
|
||||
ceiling: null,
|
||||
},
|
||||
{
|
||||
id: "box-standup",
|
||||
name: "Standup",
|
||||
outline: rect(BOX_1_W, BOX_N, BOX_1_E, BOX_S),
|
||||
floor: CARPET_ACCENT,
|
||||
ceiling: { height: BOX_CEILING, surface: FELT },
|
||||
},
|
||||
{
|
||||
id: "box-quiet",
|
||||
name: "The Quiet Box",
|
||||
outline: rect(BOX_2_W, BOX_N, BOX_2_E, BOX_S),
|
||||
floor: CARPET_ACCENT,
|
||||
ceiling: { height: BOX_CEILING, surface: FELT },
|
||||
},
|
||||
{
|
||||
id: "workshop",
|
||||
name: "The Bench",
|
||||
// Against the east gable, where the big door is. A hardware startup in a
|
||||
// hangar puts the thing it is building next to the way out.
|
||||
outline: rect(42.0, 3.0, WIDTH - 0.4, 16.0),
|
||||
floor: CONCRETE,
|
||||
ceiling: null,
|
||||
},
|
||||
{
|
||||
id: "galley",
|
||||
name: "The Galley",
|
||||
outline: rect(3.0, 22.0, 16.0, DEPTH - 0.4),
|
||||
floor: WOOD,
|
||||
ceiling: null,
|
||||
},
|
||||
];
|
||||
|
||||
// ---- Walls ----------------------------------------------------------------
|
||||
|
||||
const WALLS: Wall[] = [
|
||||
// -- The envelope, nine metres of it --------------------------------------
|
||||
{
|
||||
id: "gable-west",
|
||||
from: { x: 0, z: 0 },
|
||||
to: { x: 0, z: DEPTH },
|
||||
thickness: EXT_THICKNESS,
|
||||
height: RIDGE,
|
||||
surface: STEEL,
|
||||
openings: [doorway(13.6, 1.8, 2.4), pane(4.0, 6.0, 3.6, 7.2), pane(19.0, 6.0, 3.6, 7.2)],
|
||||
},
|
||||
{
|
||||
id: "gable-east",
|
||||
from: { x: WIDTH, z: 0 },
|
||||
to: { x: WIDTH, z: DEPTH },
|
||||
thickness: EXT_THICKNESS,
|
||||
height: RIDGE,
|
||||
surface: STEEL,
|
||||
// The hangar door. Twelve metres of it, full height, because that is what a
|
||||
// hangar is — and it is an `arch` rather than a `door` so the collider
|
||||
// knows you can walk out onto the apron through it.
|
||||
openings: [archway(6.0, 12.0, 6.4)],
|
||||
},
|
||||
{
|
||||
id: "eave-north",
|
||||
from: { x: 0, z: 0 },
|
||||
to: { x: WIDTH, z: 0 },
|
||||
thickness: EXT_THICKNESS,
|
||||
height: RIDGE,
|
||||
surface: STEEL,
|
||||
// Clerestory glazing high on the north side: the light a shed actually
|
||||
// wants, and the sill is well above head height so none of it is a doorway.
|
||||
openings: [
|
||||
pane(4.0, 9.0, 5.4, 8.2),
|
||||
pane(16.0, 9.0, 5.4, 8.2),
|
||||
pane(28.0, 9.0, 5.4, 8.2),
|
||||
pane(40.0, 9.0, 5.4, 8.2),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "eave-south",
|
||||
from: { x: 0, z: DEPTH },
|
||||
to: { x: WIDTH, z: DEPTH },
|
||||
thickness: EXT_THICKNESS,
|
||||
height: RIDGE,
|
||||
surface: STEEL,
|
||||
// The lagoon side, and the only wall with glazing you can see out of at
|
||||
// standing height. This is the view the whole building is arranged around.
|
||||
openings: [
|
||||
pane(3.0, 10.0, 0.9, 3.4),
|
||||
pane(16.0, 10.0, 0.9, 3.4),
|
||||
pane(30.0, 8.0, 0.9, 3.4),
|
||||
doorway(44.0, 1.8, 2.4),
|
||||
],
|
||||
},
|
||||
|
||||
// -- The freestanding boxes -----------------------------------------------
|
||||
//
|
||||
// Three metres tall inside a nine-metre volume, which is what makes them read
|
||||
// as objects on the floor. Their fourth sides are open — a box you can see
|
||||
// into is a box people use.
|
||||
{
|
||||
id: "standup-north",
|
||||
from: { x: BOX_1_W, z: BOX_N },
|
||||
to: { x: BOX_1_E, z: BOX_N },
|
||||
height: BOX_CEILING,
|
||||
surface: ACCENT_PAINT,
|
||||
},
|
||||
{
|
||||
id: "standup-west",
|
||||
from: { x: BOX_1_W, z: BOX_N },
|
||||
to: { x: BOX_1_W, z: BOX_S },
|
||||
height: BOX_CEILING,
|
||||
surface: ACCENT_PAINT,
|
||||
},
|
||||
{
|
||||
id: "standup-south",
|
||||
from: { x: BOX_1_W, z: BOX_S },
|
||||
to: { x: BOX_1_E, z: BOX_S },
|
||||
height: BOX_CEILING,
|
||||
surface: GLASS,
|
||||
openings: [archway(2.4, 2.4, 2.4)],
|
||||
},
|
||||
{
|
||||
id: "quiet-north",
|
||||
from: { x: BOX_2_W, z: BOX_N },
|
||||
to: { x: BOX_2_E, z: BOX_N },
|
||||
height: BOX_CEILING,
|
||||
surface: ACCENT_PAINT,
|
||||
},
|
||||
{
|
||||
id: "quiet-east",
|
||||
from: { x: BOX_2_E, z: BOX_N },
|
||||
to: { x: BOX_2_E, z: BOX_S },
|
||||
height: BOX_CEILING,
|
||||
surface: ACCENT_PAINT,
|
||||
},
|
||||
{
|
||||
id: "quiet-south",
|
||||
from: { x: BOX_2_W, z: BOX_S },
|
||||
to: { x: BOX_2_E, z: BOX_S },
|
||||
height: BOX_CEILING,
|
||||
surface: GLASS,
|
||||
openings: [doorway(2.6, 0.9, 2.1)],
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
// ---- Desks ----------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Two long benches down the middle of the shed, and nothing against a wall.
|
||||
*
|
||||
* A hangar's walls are its least valuable surface — they are steel, they are
|
||||
* cold, and the good light comes from above. So the desks sit in the volume and
|
||||
* the edges are left for the things that want an edge.
|
||||
*/
|
||||
const DESK_BANKS: DeskBank[] = [
|
||||
{
|
||||
id: "fv",
|
||||
desk: DESK,
|
||||
chair: TASK_CHAIR,
|
||||
origin: { x: 22.0, z: 5.2 },
|
||||
rotation: 0,
|
||||
columns: 7,
|
||||
rows: 2,
|
||||
pitch: 1.7,
|
||||
rowPitch: 0.85,
|
||||
facingRows: true,
|
||||
},
|
||||
{
|
||||
id: "fv-b",
|
||||
desk: DESK,
|
||||
chair: TASK_CHAIR,
|
||||
origin: { x: 22.0, z: 10.4 },
|
||||
rotation: 0,
|
||||
columns: 7,
|
||||
rows: 2,
|
||||
pitch: 1.7,
|
||||
rowPitch: 0.85,
|
||||
facingRows: true,
|
||||
seatPrefix: "fv-b",
|
||||
},
|
||||
];
|
||||
|
||||
const SEATS: Seat[] = [
|
||||
{ id: "standup-01", position: { x: 5.0, z: 5.6 }, facing: EAST, pose: "stand" },
|
||||
{ id: "standup-02", position: { x: 8.2, z: 5.6 }, facing: WEST, pose: "stand" },
|
||||
{ id: "standup-03", position: { x: 6.6, z: 4.4 }, facing: SOUTH, pose: "stand" },
|
||||
|
||||
{ id: "quietbox-01", position: { x: 13.0, z: 5.4 }, facing: EAST, pose: "sit" },
|
||||
{ id: "quietbox-02", position: { x: 15.8, z: 5.4 }, facing: WEST, pose: "sit" },
|
||||
|
||||
{ id: "galley-01", position: { x: 6.0, z: 25.0 }, facing: SOUTH, pose: "stand" },
|
||||
{ id: "galley-02", position: { x: 7.6, z: 25.0 }, facing: SOUTH, pose: "stand" },
|
||||
{ id: "galley-03", position: { x: 9.2, z: 25.0 }, facing: SOUTH, pose: "stand" },
|
||||
// On the table's south long side, not on the tabletop: the table is centred on
|
||||
// z 27.0 and is 1.2 m deep, so a seat at 27.0 was inside it.
|
||||
{ id: "galley-04", position: { x: 12.4, z: 28.0 }, facing: NORTH, pose: "sit" },
|
||||
{ id: "galley-05", position: { x: 14.0, z: 28.0 }, facing: NORTH, pose: "sit" },
|
||||
|
||||
{ id: "bench-01", position: { x: 44.4, z: 6.0 }, facing: EAST, pose: "stand" },
|
||||
{ id: "bench-02", position: { x: 44.4, z: 8.4 }, facing: EAST, pose: "stand" },
|
||||
{ id: "bench-03", position: { x: 44.4, z: 10.8 }, facing: EAST, pose: "stand" },
|
||||
|
||||
];
|
||||
|
||||
// ---- Props ----------------------------------------------------------------
|
||||
|
||||
const PROPS: Prop[] = [
|
||||
// -- The desk floor -------------------------------------------------------
|
||||
...scatter("fv-ped", PEDESTAL, grid(22.0, 6.8, 7, 1, 1.7, 0)),
|
||||
...scatter("fv-ped-b", PEDESTAL, grid(22.0, 12.0, 7, 1, 1.7, 0)),
|
||||
/**
|
||||
* The lights, hung from the trusses at seven metres.
|
||||
*
|
||||
* The one place this pack really needs the ceiling-fixture exception: a
|
||||
* pendant is authored with its origin at the mounting plane and its geometry
|
||||
* below, so 7.2 is a real height in a 9 m shed and not an offset from a lid
|
||||
* that does not exist.
|
||||
*/
|
||||
...scatter("truss-light", PENDANT, grid(8.0, 5.0, 6, 4, 9.0, 7.0), { elevation: 7.2 }),
|
||||
|
||||
// -- The boxes ------------------------------------------------------------
|
||||
{ id: "standup-board", kind: WHITEBOARD, position: { x: 6.6, z: BOX_N + INT_FACE + BOARD_OFFSET }, rotation: NORTH, elevation: 1.0 },
|
||||
{ id: "standup-rug", kind: RUG, position: { x: 6.6, z: 6.3 }, rotation: NORTH, colorKey: "team" },
|
||||
{ id: "quiet-table", kind: MEETING_TABLE, position: { x: 14.4, z: 5.4 }, rotation: NORTH },
|
||||
...scatter("quiet-chair", TASK_CHAIR, [
|
||||
{ x: 13.0, z: 5.4 },
|
||||
{ x: 15.8, z: 5.4 },
|
||||
]),
|
||||
{ id: "quiet-display", kind: DISPLAY, position: { x: 14.4, z: BOX_N + INT_FACE + DISPLAY_OFFSET }, rotation: NORTH, elevation: 1.15 },
|
||||
|
||||
// -- The bench ------------------------------------------------------------
|
||||
// Four, starting at z 1.1: the gable is solid only for z 0..6, and the hangar
|
||||
// door's arch runs from 6.0 to 18.0 — a five-shelf run from 4.0 put three of
|
||||
// them standing in the open doorway.
|
||||
...scatter("bench-shelf", SHELF, grid(WIDTH - EXT_FACE - 0.2, 1.1, 1, 4, 0, 1.2), { rotation: EAST }),
|
||||
{ id: "bench-board", kind: WHITEBOARD, position: { x: 43.0, z: 3.0 + BOARD_OFFSET }, rotation: NORTH, elevation: 1.0 },
|
||||
...scatter("bench-locker", LOCKER, grid(42.4, 13.0, 3, 1, 1.1, 0), { rotation: WEST }),
|
||||
|
||||
// -- The galley -----------------------------------------------------------
|
||||
{ id: "galley-rug", kind: RUG, position: { x: 9.0, z: 26.4 }, rotation: NORTH, colorKey: "social" },
|
||||
{ id: "galley-table", kind: MEETING_TABLE, position: { x: 13.2, z: 27.0 }, rotation: NORTH },
|
||||
...scatter("galley-lounge", LOUNGE_CHAIR, [
|
||||
{ x: 6.0, z: 26.8 },
|
||||
{ x: 8.4, z: 26.8 },
|
||||
{ x: 10.8, z: 26.8 },
|
||||
]),
|
||||
{ id: "galley-side", kind: SIDE_TABLE, position: { x: 7.2, z: 25.8 }, rotation: NORTH },
|
||||
...scatter("galley-plant", TREE, [
|
||||
{ x: 4.0, z: 23.4 },
|
||||
{ x: 15.0, z: 23.4 },
|
||||
]),
|
||||
|
||||
// -- The floor, kept mostly empty -----------------------------------------
|
||||
//
|
||||
// Three trees and nothing else. The shed's argument is the volume, and a
|
||||
// fifty-four-metre room with forty objects in it is a fifty-four-metre room
|
||||
// you cannot see across.
|
||||
...scatter("floor-tree", TREE, [
|
||||
{ x: 20.0, z: 20.0 },
|
||||
{ x: 30.0, z: 24.0 },
|
||||
{ x: 36.0, z: 4.0 },
|
||||
]),
|
||||
...scatter("floor-plant", PLANT, [
|
||||
{ x: 19.6, z: 3.2 },
|
||||
{ x: 34.0, z: 14.0 },
|
||||
]),
|
||||
|
||||
];
|
||||
|
||||
// ---- Zones ----------------------------------------------------------------
|
||||
|
||||
const ZONES: Zone[] = [
|
||||
{ id: "zone-build", name: "Build", outline: rect(42.0, 3.0, WIDTH - 0.4, 16.0), colorKey: "team" },
|
||||
{ id: "zone-galley", name: "Galley", outline: rect(3.0, 22.0, 16.0, DEPTH - 0.4), colorKey: "social" },
|
||||
];
|
||||
|
||||
// ---- Viewpoints -----------------------------------------------------------
|
||||
|
||||
const VIEWPOINTS: Viewpoint[] = [
|
||||
{
|
||||
id: "hangar",
|
||||
number: "01",
|
||||
label: "The Hangar",
|
||||
shortLabel: "Hangar",
|
||||
levelId: "level-1",
|
||||
focus: { at: { x: 27, z: 15 }, distance: 52, height: 26, rotation: 0.6 },
|
||||
description:
|
||||
"Fifty-four metres by thirty, nine to the trusses, one room. Everything Frontier Valley has is on this slab or on the deck at the far end.",
|
||||
},
|
||||
{
|
||||
id: "benches",
|
||||
number: "02",
|
||||
label: "The Benches",
|
||||
shortLabel: "Benches",
|
||||
levelId: "level-1",
|
||||
focus: { at: { x: 27.4, z: 8.0 }, distance: 15, height: 4.6, rotation: 0.4 },
|
||||
description:
|
||||
"Twenty-eight seats in two runs down the middle of the floor, under the trusses. Nothing is against a wall — in a shed the walls are the worst surface in the building.",
|
||||
},
|
||||
{
|
||||
id: "big-door",
|
||||
number: "03",
|
||||
label: "The Big Door",
|
||||
shortLabel: "Big Door",
|
||||
levelId: "level-1",
|
||||
// Low and looking east, straight out through the twelve-metre opening.
|
||||
focus: { at: { x: 46, z: 9.5 }, distance: 18, height: 3.0, rotation: 1.5 },
|
||||
description:
|
||||
"Twelve metres of hangar door, open onto the apron, with the bench inside it. The reason a company rents one of these rather than a floor of an office block.",
|
||||
},
|
||||
{
|
||||
id: "mezzanine",
|
||||
number: "04",
|
||||
label: "The Mezzanine",
|
||||
shortLabel: "Mezzanine",
|
||||
levelId: "level-mezz",
|
||||
// `height` is metres above *this* level's floor, so 3.0 here is standing on
|
||||
// the deck rather than 3 m off the concrete.
|
||||
focus: { at: { x: 46, z: 25 }, distance: 13, height: 3.0, rotation: 2.4 },
|
||||
description:
|
||||
"A deck four and a half metres up with another four and a half above it. Its own level, because a level is the only thing in this format that carries a floor height — and without one its chairs floated over bare concrete.",
|
||||
},
|
||||
{
|
||||
id: "galley",
|
||||
number: "05",
|
||||
label: "The Galley",
|
||||
shortLabel: "Galley",
|
||||
levelId: "level-1",
|
||||
focus: { at: { x: 9.5, z: 26 }, distance: 12, height: 3.2, rotation: 3.4 },
|
||||
description:
|
||||
"The lagoon side, and the only glazing you can see out of standing up. The whole social end of the building is arranged around one row of windows.",
|
||||
},
|
||||
];
|
||||
|
||||
// ---- The pack -------------------------------------------------------------
|
||||
|
||||
// ---- The mezzanine, as its own level --------------------------------------
|
||||
//
|
||||
// Authored in its own floor's frame, with the deck at zero. `Plan` adds
|
||||
// `MEZZ_HEIGHT` to every coordinate here exactly once.
|
||||
|
||||
const MEZZ_ROOMS: Room[] = [
|
||||
{
|
||||
id: "mezzanine",
|
||||
name: "The Mezzanine",
|
||||
outline: rect(MEZZ_W, MEZZ_N, WIDTH - 0.4, DEPTH - 0.4),
|
||||
floor: WOOD,
|
||||
ceiling: null,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* The balustrade, and — as in the reference pack's gallery — a **wall** and not
|
||||
* a prop, because `Plan` derives the walk collider from the wall list and the
|
||||
* drop is 4.4 m onto polished concrete. No openings: that is what a balustrade
|
||||
* is.
|
||||
*/
|
||||
const MEZZ_WALLS: Wall[] = [
|
||||
{
|
||||
id: "mezz-west",
|
||||
from: { x: MEZZ_W, z: MEZZ_N },
|
||||
to: { x: MEZZ_W, z: DEPTH - 0.4 },
|
||||
height: 1.1,
|
||||
surface: GLASS,
|
||||
},
|
||||
{
|
||||
id: "mezz-north",
|
||||
from: { x: MEZZ_W, z: MEZZ_N },
|
||||
to: { x: WIDTH - 0.4, z: MEZZ_N },
|
||||
height: 1.1,
|
||||
surface: GLASS,
|
||||
},
|
||||
];
|
||||
|
||||
const MEZZ_SEATS: Seat[] = [
|
||||
{ id: "mezz-01", position: { x: 44.0, z: 23.0 }, facing: NORTH, pose: "sit" },
|
||||
{ id: "mezz-02", position: { x: 46.4, z: 23.0 }, facing: NORTH, pose: "sit" },
|
||||
{ id: "mezz-03", position: { x: 48.8, z: 23.0 }, facing: NORTH, pose: "sit" },
|
||||
];
|
||||
|
||||
const MEZZ_PROPS: Prop[] = [
|
||||
{ id: "mezz-table", kind: MEETING_TABLE, position: { x: 46.4, z: 24.2 }, rotation: NORTH },
|
||||
...scatter("mezz-chair", TASK_CHAIR, [
|
||||
{ x: 44.0, z: 23.0 },
|
||||
{ x: 46.4, z: 23.0 },
|
||||
{ x: 48.8, z: 23.0 },
|
||||
]),
|
||||
...scatter("mezz-lounge", LOUNGE_CHAIR, [
|
||||
{ x: 44.0, z: 27.4 },
|
||||
{ x: 47.0, z: 27.4 },
|
||||
], { rotation: SOUTH }),
|
||||
{ id: "mezz-rug", kind: RUG, position: { x: 45.5, z: 27.4 }, rotation: NORTH, colorKey: "social" },
|
||||
// Half-depth plus a 25 mm reveal off the wall face, which is how the reference
|
||||
// pack stands the same asset against a wall — `EXT_FACE + 0.2` is the shelf's
|
||||
// *clearance* and leaves it floating 0.42 m out in the room.
|
||||
...scatter("mezz-shelf", SHELF, grid(WIDTH - EXT_FACE - 0.2, 22.0, 1, 3, 0, 1.2), {
|
||||
rotation: EAST,
|
||||
}),
|
||||
];
|
||||
|
||||
const MEZZ_ZONES: Zone[] = [
|
||||
{ id: "zone-mezz", name: "Mezzanine", outline: rect(MEZZ_W, MEZZ_N, WIDTH - 0.4, DEPTH - 0.4), colorKey: "social" },
|
||||
];
|
||||
|
||||
const LEVEL_MEZZ: Level = {
|
||||
id: "level-mezz",
|
||||
name: "The Mezzanine",
|
||||
elevation: MEZZ_HEIGHT,
|
||||
// What is above the deck is the same roof, so its storey height is whatever is
|
||||
// left of the shed.
|
||||
wallHeight: RIDGE - MEZZ_HEIGHT,
|
||||
wallThickness: INT_THICKNESS,
|
||||
wallSurface: PAINT,
|
||||
floorplan: {
|
||||
rooms: MEZZ_ROOMS,
|
||||
walls: MEZZ_WALLS,
|
||||
props: MEZZ_PROPS,
|
||||
seats: MEZZ_SEATS,
|
||||
zones: MEZZ_ZONES,
|
||||
},
|
||||
};
|
||||
|
||||
const LEVEL_1: Level = {
|
||||
id: "level-1",
|
||||
name: "The Slab",
|
||||
elevation: 0,
|
||||
wallHeight: RIDGE,
|
||||
wallThickness: INT_THICKNESS,
|
||||
wallSurface: PAINT,
|
||||
floorplan: {
|
||||
rooms: ROOMS,
|
||||
walls: WALLS,
|
||||
props: PROPS,
|
||||
deskBanks: DESK_BANKS,
|
||||
seats: SEATS,
|
||||
zones: ZONES,
|
||||
},
|
||||
};
|
||||
|
||||
export const FRONTIER_VALLEY: Office = {
|
||||
id: "frontier-valley",
|
||||
name: "Frontier Valley",
|
||||
levels: [LEVEL_1, LEVEL_MEZZ],
|
||||
viewpoints: VIEWPOINTS,
|
||||
/**
|
||||
* Alameda Point, on the estuary side of Alameda Island.
|
||||
*
|
||||
* Typed by hand from the street grid of the former Naval Air Station, not
|
||||
* geocoded — CONTRACT.md §8. Four metres of elevation because this is
|
||||
* reclaimed flat ground and the slab is barely above it, which is the exact
|
||||
* opposite of `lumbridge-hq`'s 188 m and is the whole point of having both.
|
||||
*
|
||||
* `heading: 0` is a real claim rather than the reference pack's convenience:
|
||||
* the hangars here are laid out square to the old runways, which run close
|
||||
* enough to north–south that calling the clerestory wall "north" is true.
|
||||
* So the high glazing really does take north light and the lagoon really is
|
||||
* to the south.
|
||||
*/
|
||||
site: {
|
||||
lat: 37.7756,
|
||||
lng: -122.3186,
|
||||
elevation: 4,
|
||||
heading: 0,
|
||||
label: "Alameda Point",
|
||||
},
|
||||
meta: {
|
||||
description:
|
||||
"A startup in a hangar at Alameda Point: one room, fifty-four by thirty, nine metres to the trusses. The second pack, and the one that shows the format describes a shed as well as it describes a corridor.",
|
||||
author: "Lumbridge",
|
||||
license: "CC0-1.0",
|
||||
version: "1.0.0",
|
||||
updated: "2026-08-07",
|
||||
},
|
||||
};
|
||||
|
||||
export default FRONTIER_VALLEY;
|
||||
+54
-10
@@ -118,16 +118,36 @@ const DEPTH = 18.0;
|
||||
const CEILING = 2.8;
|
||||
|
||||
/**
|
||||
* Floor to floor. Not the same as `CEILING` and the difference is not slack:
|
||||
* 4.2 m is 2.8 m of room plus 1.4 m of structure, services and raised floor,
|
||||
* which is what a real storey costs and what puts level 2's slab at a height a
|
||||
* stair can plausibly climb to.
|
||||
* The gap between one storey's ceiling and the next one's floor.
|
||||
*
|
||||
* In a real building this is 1.4 m of structure, ducts, trays and raised floor,
|
||||
* and you never see it. Here it is **ten times that**, and the exaggeration is
|
||||
* deliberate and is the whole reason it has a name of its own.
|
||||
*
|
||||
* The brief was to be able to see the space between the floors and eventually to
|
||||
* run things through it — cable trays, fibre, the drops that land on the ceiling
|
||||
* of the floor below and the floor of the one above. At 1.4 m there is nowhere
|
||||
* to put any of that and nothing to look at; at 14 m the interstitial is a
|
||||
* storey in its own right, the two slabs read as two separate places, and the
|
||||
* commons becomes a genuine shaft rather than a tall room.
|
||||
*
|
||||
* It is frankly not architecture. A 16.8 m floor-to-floor is a hangar, the stair
|
||||
* in the east wing is no longer a stair anybody could climb, and the gallery
|
||||
* looks down from the height of a five-storey building. That is an accepted
|
||||
* cost of a deliberately unreal building, and **this is the one number to change
|
||||
* if it reads as too much** — everything else in the file is measured off it.
|
||||
*/
|
||||
const PLENUM = 14.0;
|
||||
|
||||
/**
|
||||
* Floor to floor. Not the same as `CEILING` and the difference is not slack: it
|
||||
* is the room plus the interstitial above it.
|
||||
*
|
||||
* `Plan` adds this to every coordinate on level 2 exactly once, so both levels
|
||||
* are authored in their own floor's frame and neither has to know about the
|
||||
* other.
|
||||
*/
|
||||
const STOREY = 4.2;
|
||||
const STOREY = CEILING + PLENUM;
|
||||
|
||||
/**
|
||||
* The commons is open to both storeys, so its walls are two rooms tall.
|
||||
@@ -1420,7 +1440,7 @@ const PROPS: Prop[] = [
|
||||
elevation: 1.1,
|
||||
},
|
||||
/**
|
||||
* Pendants at 6.4 m, hanging into the void from the roof rather than from a
|
||||
* Pendants at 19 m, hanging into the void from the roof rather than from a
|
||||
* ceiling at 2.8 m that is not there.
|
||||
*
|
||||
* This is the one place in the pack where the ceiling-fixture exception earns
|
||||
@@ -1498,7 +1518,7 @@ const ZONES: Zone[] = [
|
||||
*
|
||||
* **Every coordinate below is in level 2's own frame**, with the floor at zero.
|
||||
* `Plan` adds `STOREY` to all of them exactly once when it resolves the level,
|
||||
* which is what lets this section be read without holding 4.2 in your head, and
|
||||
* which is what lets this section be read without holding 16.8 in your head, and
|
||||
* what lets both levels sit in one scene group at the origin.
|
||||
*
|
||||
* It stops at `BLOCK_E`. The wing below is open to the roof, so there is no slab
|
||||
@@ -2004,9 +2024,12 @@ const VIEWPOINTS: Viewpoint[] = [
|
||||
// wing did: an establishing shot that still framed 34 m put the commons
|
||||
// half off the right-hand edge, which is a poor introduction to the largest
|
||||
// room in the building.
|
||||
focus: { at: { x: 24.0, z: 9.0 }, distance: 44, height: 19, rotation: 0.55 },
|
||||
// Higher and further back than it was, because the building got taller
|
||||
// rather than wider: at a 14 m interstitial the roof is 19.6 m up, and a
|
||||
// camera at 19 m was level with it and looking at the parapet.
|
||||
focus: { at: { x: 24.0, z: 9.0 }, distance: 58, height: 34, rotation: 0.55 },
|
||||
description:
|
||||
"Forty-eight metres by eighteen, two storeys, glazed along the north edge and open to the roof at the east end. Everything in this building is somewhere in this frame.",
|
||||
"Forty-eight metres by eighteen, two storeys a long way apart, glazed along the north edge and open to the roof at the east end. Everything in this building is somewhere in this frame.",
|
||||
},
|
||||
{
|
||||
id: "reception",
|
||||
@@ -2081,7 +2104,7 @@ const VIEWPOINTS: Viewpoint[] = [
|
||||
// there is something underneath.
|
||||
focus: { at: { x: 37.0, z: 9.0 }, distance: 9, height: 1.8, rotation: 1.4 },
|
||||
description:
|
||||
"The balcony over the commons, four metres of it, with a glass balustrade and nothing below for another four. The reason the void is a volume rather than a tall room.",
|
||||
"The balcony over the commons, four metres of it, with a glass balustrade and nothing below for another seventeen. The reason the void is a volume rather than a tall room.",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -2135,6 +2158,27 @@ export const LUMBRIDGE_HQ: Office = {
|
||||
name: "Lumbridge HQ",
|
||||
levels: [LEVEL_1, LEVEL_2],
|
||||
viewpoints: VIEWPOINTS,
|
||||
/**
|
||||
* High in a downtown tower, on the same board the city view draws.
|
||||
*
|
||||
* The coordinate is a block in the Transbay cluster, typed by hand from the
|
||||
* street grid — not geocoded, for the reason CONTRACT.md §8 gives at length.
|
||||
* `elevation` is what makes it a *tower*: 188 m is around the forty-eighth
|
||||
* floor, so the horizon sits a long way down and the fog outside the glass is
|
||||
* the fog you look across a city at rather than through.
|
||||
*
|
||||
* `heading: 205` turns the glazed façade — `ext-north`, the wall this pack has
|
||||
* always called the daylight side — to face south-south-west. That is the one
|
||||
* number that decides whether the sun ever actually enters the building, and
|
||||
* pointing the glass at the afternoon is the whole reason the field exists.
|
||||
*/
|
||||
site: {
|
||||
lat: 37.7897,
|
||||
lng: -122.3972,
|
||||
elevation: 188,
|
||||
heading: 205,
|
||||
label: "Transbay, San Francisco",
|
||||
},
|
||||
meta: {
|
||||
description:
|
||||
"The reference office: two levels around a double-height commons, twenty-six rooms, a hundred and twenty-six seats. Copy this file, change the numbers, keep the seat ids.",
|
||||
|
||||
Reference in New Issue
Block a user