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:
+72
-3
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user