1
0

Spaces: the inside of the world, and a sun that is actually where it should be

Ten agents wrote this in parallel against CONTRACT.md, which exists because the
five design agents before them collided on fifteen blocking points — four files
specified twice with incompatible contents, three separate backends for one box,
and `Environment` exported twice meaning different things.

What landed: a Stage owning only the renderer and the loop, with the city and an
office as two scenes over it. They cannot share one — San Francisco is ~94 m per
scene unit with 3.6x vertical exaggeration and an office is 1 unit = 1 m — and
the city is paused rather than disposed on the way in, because rebuilding its
336,864-point heightfield costs about a second on the way back out.

Offices are data. `src/offices/lumbridge-hq.ts` is fifteen rooms and seventy-six
seats, and it is the file a self-hoster copies. Walls are a segment list with
1-D openings, so doors and windows are holes punched in a wall rather than
placed objects, and the pass that splits a wall around its openings hands the
walk-mode collider its segments for free.

The sun is real. `solar.ts` is a NOAA/Meeus implementation with no imports at
all — not even three.js — so time of day keeps working on a laptop in a field.
Verified against known values: 75.45 degrees at the June solstice in SF, 28.79
at December, sunset at 03:15Z. The first screenshot after wiring it was a black
rectangle, which turned out to be correct: it was midnight in San Francisco.

Presence binds to a seat id and never to a coordinate. The pack knows where
`eng-04` is; who is sitting in it is private data behind an API. Same shape as
the marker rule, one level in.

Two corrections to ARCHITECTURE.md are in here. Containment does not discharge
ODbL — publishing OSM-derived coordinates is Public Use of a Derivative Database
wherever the rows live, so the rule is about the geocoder (US Census, public
domain) and not the storage. And a person at a desk is not a Marker; markers are
geographic.

One contract gap surfaced only in a screenshot: two agents read `height` on a
viewpoint differently, so the establishing shot aimed at empty air fourteen
metres above the roof. It now means what the same field means for a city.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Karti Tripathi
2026-08-05 00:11:01 -07:00
parent 36471bbad7
commit d464459838
77 changed files with 14266 additions and 216 deletions
+151 -38
View File
@@ -1,17 +1,25 @@
/**
* The standalone demo: San Francisco, simulated traffic, chapter legend.
* The standalone demo: San Francisco under a real sun, and one office you can
* step into.
*
* Deliberately ships **no company data**. Markers are demonstrated using the
* city's own landmarks — buildings, not businesses — because company positions
* are geocoded (ODbL) and company pipeline status is private, and neither
* belongs in this repo. Real markers arrive at runtime from an adapter; see
* `src/adapters/` and ARCHITECTURE.md §3.
* are geocoded and company pipeline status is private, and neither belongs in
* this repo. Real markers arrive at runtime from an adapter; see `src/adapters/`
* and ARCHITECTURE.md §3.
*
* It also makes no network calls. The sun is computed locally, the traffic is
* simulated and the office is a data file, so a clone of this repo runs.
*/
import { createScene } from "./engine/scene.ts";
import { createAtmosphere, observe, PACIFIC_MARINE_LAYER } from "./engine/atmosphere.ts";
import { daylightPhase } from "./engine/solar.ts";
import { SimulatedFlights, type SimRoute } from "./engine/flights.ts";
import type { Marker, MarkerPalette } from "./engine/types.ts";
import { createScene } from "./engine/scene.ts";
import type { Marker, MarkerPalette, View } from "./engine/types.ts";
import SAN_FRANCISCO from "./cities/sf.ts";
import { createOfficeScene, type OfficeScene } from "./interiors/officeScene.ts";
import LUMBRIDGE_HQ from "./offices/lumbridge-hq.ts";
/**
* Bay Area traffic, roughly where it actually is: SFO sits south of frame and
@@ -28,31 +36,76 @@ const ROUTES: SimRoute[] = [
{ callsign: "JBU 915", from: [37.96, -122.48], to: [37.63, -122.36], fromAlt: 3100, toAlt: 600, duration: 205 },
];
const MARKER_PALETTE: MarkerPalette = {
landmark: 0xf2b134,
neutral: 0x9aa4ad,
};
const MARKER_PALETTE: MarkerPalette = { landmark: 0xf2b134, neutral: 0x9aa4ad };
const canvas = document.querySelector<HTMLCanvasElement>("#scene");
if (!canvas) throw new Error("#scene canvas missing");
const scene = createScene(canvas, {
const city = createScene(canvas, {
city: SAN_FRANCISCO,
markerPalette: MARKER_PALETTE,
flights: new SimulatedFlights(ROUTES),
onMarkerPick: (marker) => {
const card = document.querySelector<HTMLElement>("#detail");
if (!card) return;
if (!marker) {
card.hidden = true;
return;
}
card.hidden = false;
card.textContent = marker.label;
},
onMarkerPick: (marker) => showDetail(marker?.label ?? null),
});
// Demo markers: the city's own named buildings.
// ---- The sun --------------------------------------------------------------
/**
* Real solar position for San Francisco, right now, recomputed every minute.
*
* No network and no timezone database — `solar.ts` is arithmetic — so this
* keeps working on a laptop in a field. The marine layer is switched on because
* the fog is the single most recognisable atmospheric fact about this city.
*/
const atmosphere = createAtmosphere({
lng: SAN_FRANCISCO.center.lng,
metresPerUnit: city.world.metresPerUnit,
marineLayer: PACIFIC_MARINE_LAYER,
});
/**
* `null` follows the wall clock. A number is an hour-of-day override from the
* scrubber, which exists because the honest answer at 2 a.m. is a black
* rectangle — correct, and impossible to look at. Being able to drag the sun is
* also the only practical way to eyeball whether the solar maths is right.
*/
let hourOverride: number | null = null;
function currentInstant(): Date {
const now = new Date();
if (hourOverride === null) return now;
const d = new Date(now);
d.setHours(Math.floor(hourOverride), Math.round((hourOverride % 1) * 60), 0, 0);
return d;
}
function updateSun() {
const env = observe(SAN_FRANCISCO.center.lat, SAN_FRANCISCO.center.lng, currentInstant());
city.setLighting(atmosphere.apply(env));
const clock = document.querySelector<HTMLElement>("#clock");
if (!clock) return;
const el = env.sun.elevation;
const time = env.time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
const phase = daylightPhase(el);
clock.textContent = `${time} · sun ${el >= 0 ? "+" : ""}${el.toFixed(1)}° · ${phase}${hourOverride === null ? "" : " (held)"}`;
}
const scrubber = document.querySelector<HTMLInputElement>("#hour");
scrubber?.addEventListener("input", () => {
hourOverride = Number(scrubber.value);
updateSun();
});
document.querySelector<HTMLElement>("#now")?.addEventListener("click", () => {
hourOverride = null;
if (scrubber) scrubber.value = String(new Date().getHours());
updateSun();
});
updateSun();
window.setInterval(() => hourOverride === null && updateSun(), 60_000);
// ---- Demo markers ---------------------------------------------------------
const demoMarkers: Marker[] = SAN_FRANCISCO.landmarks
.filter((l) => l.label)
.map((l) => ({
@@ -63,26 +116,86 @@ const demoMarkers: Marker[] = SAN_FRANCISCO.landmarks
colorKey: "landmark",
located: true,
}));
scene.setMarkers(demoMarkers);
city.setMarkers(demoMarkers);
// ---- Chapter legend -------------------------------------------------------
// ---- The office -----------------------------------------------------------
/**
* Built on first entry and then kept, for the same reason the city is paused
* rather than disposed on the way in: rebuilding either scene costs far more
* than holding it.
*/
let office: OfficeScene | null = null;
let inside = false;
function enterOffice() {
if (!office) {
office = createOfficeScene(LUMBRIDGE_HQ, {
dom: city.stage.renderer.domElement,
background: 0x11161c,
});
office.onViewChange(() => renderLegend());
}
city.stage.setScene(office);
inside = true;
showDetail(null);
renderLegend();
}
function leaveOffice() {
city.stage.setScene(city.stageScene);
inside = false;
showDetail(null);
renderLegend();
}
// ---- Chrome ---------------------------------------------------------------
const nav = document.querySelector<HTMLElement>("#chapters");
const blurb = document.querySelector<HTMLElement>("#blurb");
const title = document.querySelector<HTMLElement>("#title");
const subtitle = document.querySelector<HTMLElement>("#subtitle");
const enterButton = document.querySelector<HTMLButtonElement>("#enter");
function renderLegend(activeId: string) {
if (!nav) return;
nav.replaceChildren();
for (const chapter of scene.chapters) {
const button = document.createElement("button");
button.className = chapter.id === activeId ? "chapter active" : "chapter";
button.innerHTML = `<span class="num">${chapter.number}</span><span>${chapter.shortLabel}</span>`;
button.addEventListener("click", () => scene.flyTo(chapter.id));
nav.append(button);
}
const active = scene.chapters.find((c) => c.id === activeId);
if (blurb && active) blurb.textContent = active.description;
function showDetail(text: string | null) {
const card = document.querySelector<HTMLElement>("#detail");
if (!card) return;
card.hidden = text === null;
card.textContent = text ?? "";
}
renderLegend(scene.current());
scene.onChapterChange(renderLegend);
/**
* One legend for both places. A city chapter and an office viewpoint are both
* `View`s, which is the whole reason that type was extracted.
*/
function renderLegend() {
if (!nav) return;
const views: View[] = inside && office ? office.views : city.chapters;
const activeId = inside && office ? office.current() : city.current();
nav.replaceChildren();
views.forEach((view, i) => {
const button = document.createElement("button");
button.className = view.id === activeId ? "chapter active" : "chapter";
const number = view.number ?? String(i + 1).padStart(2, "0");
button.innerHTML = `<span class="num">${number}</span><span>${view.shortLabel}</span>`;
button.addEventListener("click", () => {
if (inside && office) office.flyTo(view.id);
else city.flyTo(view.id);
});
nav.append(button);
});
const active = views.find((v) => v.id === activeId);
if (blurb) {
blurb.textContent = active?.description ?? "";
blurb.hidden = !active?.description;
}
if (title) title.textContent = inside ? LUMBRIDGE_HQ.name : SAN_FRANCISCO.name;
if (subtitle) subtitle.textContent = inside ? "Spaces · a Lumbridge office" : "Tera · Lumbridge Simulate";
if (enterButton) enterButton.textContent = inside ? "← Back to the city" : "Enter the office →";
}
enterButton?.addEventListener("click", () => (inside ? leaveOffice() : enterOffice()));
city.onChapterChange(() => renderLegend());
renderLegend();