1
0

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:
2026-08-06 23:30:45 -07:00
parent d8afc42d15
commit 06455f7424
15 changed files with 2061 additions and 75 deletions
+72 -3
View File
@@ -90,6 +90,38 @@ export interface Shell {
dispose(): void;
}
/**
* How far a slab is lifted per earlier slab it overlaps. See `liftOf`.
*
* 4 mm. Big enough to beat the depth buffer's resolution at office range — the
* near plane is 0.2 m and the camera orbits within about a hundred metres, so a
* 24-bit buffer resolves far finer than this — and small enough that a step
* between two floor finishes is not a step anybody can see or trip over.
*/
const SLAB_LIFT = 0.004;
/** Do two outlines' axis-aligned bounding boxes intersect? See `liftOf`. */
function boxesOverlap(a: readonly Point2[], b: readonly Point2[]): boolean {
const box = (points: readonly Point2[]) => {
let minX = Infinity;
let maxX = -Infinity;
let minZ = Infinity;
let maxZ = -Infinity;
for (const p of points) {
minX = Math.min(minX, p.x);
maxX = Math.max(maxX, p.x);
minZ = Math.min(minZ, p.z);
maxZ = Math.max(maxZ, p.z);
}
return { minX, maxX, minZ, maxZ };
};
const one = box(a);
const two = box(b);
// Touching edge-to-edge is not overlapping: the reference office's rooms abut
// along shared lines everywhere and must not all be lifted for it.
return one.minX < two.maxX && two.minX < one.maxX && one.minZ < two.maxZ && two.minZ < one.maxZ;
}
export function createShell(plan: Plan, options: ShellOptions): Shell {
const { materials } = options;
const parts = options.parts ?? sharedParts;
@@ -129,7 +161,7 @@ export function createShell(plan: Plan, options: ShellOptions): Shell {
buildWall(level.id, wallId, runs, holesByWall.get(wallId) ?? []);
}
for (const room of level.rooms) {
buildFloor(room);
buildFloor(room, liftOf(room, level.rooms));
buildCeiling(room);
}
if (drawOpenings) {
@@ -206,8 +238,45 @@ export function createShell(plan: Plan, options: ShellOptions): Shell {
}
}
function buildFloor(room: ResolvedRoom): void {
const geometry = slabGeometry(room.outline, room.y, true);
/**
* How far to lift a slab so it does not fight the ones it overlaps.
*
* The format explicitly permits overlapping rooms and resolves *later* ones
* first (`types.ts` on `Room.outline`, `Plan.roomAt`), so "a slab on top of
* another slab" is legal and is the natural way to author a hangar: one
* concrete floor with a carpeted meeting box and a timber galley laid on it.
* The reference office avoids it by notching every room around its neighbours,
* which works when the rooms tile the plate and cannot work at all when they
* are islands in the middle of it — a rectangle with holes in it is not a
* simple polygon.
*
* Two coplanar slabs at the same `y` is a z-fight, and which one wins is the
* GPU's business. So a room that overlaps earlier rooms is lifted by a hair
* per earlier room it overlaps, which makes the depth test agree with the
* ordering the format already documents.
*
* **A pack whose rooms do not overlap is lifted by nothing**, which is why
* this counts overlaps rather than simply using the room's index: indexing
* would raise the reference office's fifteenth room by a centimetre and a half
* for no reason at all.
*
* Bounding boxes rather than true polygon intersection, deliberately. It is
* conservative in the safe direction — two rooms whose boxes touch but whose
* outlines do not get a lift they did not need, which is invisible — and it is
* a handful of comparisons rather than a clipping library.
*/
function liftOf(room: ResolvedRoom, rooms: readonly ResolvedRoom[]): number {
let overlaps = 0;
for (const other of rooms) {
if (other === room) break;
if (Math.abs(other.y - room.y) > 1e-6) continue;
if (boxesOverlap(other.outline, room.outline)) overlaps += 1;
}
return overlaps * SLAB_LIFT;
}
function buildFloor(room: ResolvedRoom, lift: number): void {
const geometry = slabGeometry(room.outline, room.y + lift, true);
if (!geometry) return;
owned.push(geometry);
const mesh = new THREE.Mesh(geometry, materials.forSurface(room.floor, "carpet"));