The same bug the camera had, still live in the default light rig
gates / clean-clone (push) Successful in 16s
gates / zero-config-boot (push) Successful in 9s
gates / no-binary-art (push) Successful in 4s

Three loose ends turned into four, because one of them was not a stale comment.

`cityDaylight()` closed its fog at 210/460 — numbers tuned when San Francisco's
230-unit board was the only one. Every caller who supplies an `Atmosphere`
overrides them, which is why this survived: the demo does, so nothing looked
wrong. A self-hoster who renders a city with no clock and no weather gets the
default path, and on the Bay Area's 1003 units that fog closes well inside the
city. It now scales to the board exactly as the camera limits do, with the old
constants preserved as the default for a caller holding a palette but no world.

The three that really were comments: socal.ts justified its `latScale` by
citing the 340-unit orbit cap and the 460-unit fog as things the engine
insisted on, and the engine stopped insisting when both became board-relative —
285 is now a choice the pack makes because its lot sizes and hill radii were
authored against it. CONTRACT.md and ARCHITECTURE.md still measured the
retention argument against a 336,864-point San Francisco that has been the
whole Bay Area for some time. And server/package.json listed five routes where
there are six.

None of these changed behaviour except the first. All of them would have gone
on quietly disagreeing with the pages that now cite them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Karti Tripathi
2026-08-05 04:34:00 -07:00
parent 924be42f19
commit fe43077ff8
6 changed files with 40 additions and 18 deletions
+4 -2
View File
@@ -214,7 +214,8 @@ receiver as the eventual real answer.
## 5. LA is not "SF but more data" ## 5. LA is not "SF but more data"
SF is 0.20° × 0.36°. At the 45 m cells the SF heightfield uses, that is 336k SF is 0.20° × 0.36°. At the 45 m cells the SF heightfield uses, that is 336k
lattice points and a 1.0 s build. lattice points and a 1.0 s build. (The pack has since grown to the whole Bay
Area: 0.53M points and about 2.3 s.)
LA + Orange County + Riverside is roughly 0.5° × 1.9° — about **14× the area**. LA + Orange County + Riverside is roughly 0.5° × 1.9° — about **14× the area**.
The same approach would be 4.6M lattice points and a mesh nobody can load. This The same approach would be 4.6M lattice points and a mesh nobody can load. This
@@ -293,7 +294,8 @@ resolutions:
puts one scene unit at ~94 m with 3.6× vertical exaggeration; an office renders puts one scene unit at ~94 m with 3.6× vertical exaggeration; an office renders
at 1 unit = 1 m. There are two scenes over one renderer, and the city scene is at 1 unit = 1 m. There are two scenes over one renderer, and the city scene is
*retained and paused* when you step inside rather than disposed — rebuilding *retained and paused* when you step inside rather than disposed — rebuilding
its 336,864-point heightfield costs about a second on the way back out. its 0.53M-point heightfield costs about two and a half seconds on the way back
out.
- **A person at a desk is not a `Marker`.** A marker is geographic — it has a - **A person at a desk is not a `Marker`.** A marker is geographic — it has a
lat/lng. Presence binds to a **seat id** and never to a coordinate, which is lat/lng. Presence binds to a **seat id** and never to a coordinate, which is
what lets the office geometry be public and open-source while who is sitting what lets the office geometry be public and open-source while who is sitting
+3 -2
View File
@@ -37,8 +37,9 @@ asserting `GET /api/v1/health` → 200.
## 1. Stage and scene lifecycle ## 1. Stage and scene lifecycle
Two designs both created `src/engine/stage.ts` with opposite lifecycles. The Two designs both created `src/engine/stage.ts` with opposite lifecycles. The
retention argument wins on measured cost: SF's heightfield is 484 × 696 = retention argument wins on measured cost: the Bay Area's heightfield is 0.53M
336,864 lattice points and `world.ts` records a ~1.0 s build. Disposing the city lattice points and about 2.3 s to build — it was 336,864 and ~1.0 s when the
pack was San Francisco alone, and it grew, which is the point. Disposing the city
every time someone steps into an office and paying a second of rebuild on the every time someone steps into an office and paying a second of rebuild on the
way out is not acceptable. way out is not acceptable.
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "@lumbridge/tera-api", "name": "@lumbridge/tera-api",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"description": "The one Tera API. Health, flights, weather, markers and office packs on 127.0.0.1:8431.", "description": "The one Tera API. Health, flights, weather, markers, office packs and session on 127.0.0.1:8431.",
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
+12 -6
View File
@@ -3088,12 +3088,18 @@ export const SOCAL_CITY: City = {
/** /**
* 391 m to the scene unit, against San Francisco's 94. * 391 m to the scene unit, against San Francisco's 94.
* *
* Not a stylistic choice. `scene.ts` caps the orbit at 340 units from the * The board this produces is 393 by 308 units, against the Bay Area's 1003 by
* target and `cityDaylight` closes its fog at 460, so however large a city is * 830 — a metro four times the area rendered on a third of the board, which is
* on the ground it has to end up roughly the same size in scene units or it * why one instance here is a city block rather than a building.
* cannot be looked at. The board this produces is 393 by 308 units — a little *
* wider and deeper than San Francisco's 331 by 230, which is about as far as * This used to be forced rather than chosen: `scene.ts` capped the orbit at
* the camera will stretch. * 340 units and `cityDaylight` closed its fog at 460, both tuned when San
* Francisco was the only city, so however large a place was on the ground it
* had to come out roughly San Francisco's size in scene units or it could not
* be looked at. Both now derive from the board, so a pack is free to pick its
* own scale. This one keeps 285 because the numbers below — lot size, hill
* radii, bridge spans — were all authored against it, not because the engine
* still insists.
*/ */
latScale: 285, latScale: 285,
+17 -5
View File
@@ -13,8 +13,8 @@
* that ought to be here — is the city itself: which layers go in the scene, * that ought to be here — is the city itself: which layers go in the scene,
* where a chapter puts the camera, and what a pick means. An office builds the * where a chapter puts the camera, and what a pick means. An office builds the
* same two pieces with its own answers and swaps in on the same `Stage`, which * same two pieces with its own answers and swaps in on the same `Stage`, which
* keeps this city alive and paused rather than rebuilding its ~1.0 s * keeps this city alive and paused rather than rebuilding its heightfield — for
* heightfield on the way back. See CONTRACT.md §1. * the Bay Area, about 2.3 s — on the way back. See CONTRACT.md §1.
*/ */
import * as THREE from "three"; import * as THREE from "three";
@@ -113,7 +113,7 @@ export function createScene(canvas: HTMLCanvasElement, options: SceneOptions): S
shadowExtent: boardSpan * 0.75, shadowExtent: boardSpan * 0.75,
shadowFar: boardSpan * 2.2, shadowFar: boardSpan * 2.2,
}); });
kit.applyLighting(options.lighting ?? cityDaylight(pal)); kit.applyLighting(options.lighting ?? cityDaylight(pal, boardSpan));
scene.add(createWater(world)); scene.add(createWater(world));
scene.add(createShorePlates(world)); scene.add(createShorePlates(world));
@@ -254,12 +254,24 @@ export function createScene(canvas: HTMLCanvasElement, options: SceneOptions): S
* It exists so the engine renders with no server, no clock and no config, which * It exists so the engine renders with no server, no clock and no config, which
* is the acceptance test the whole repo is held to. * is the acceptance test the whole repo is held to.
*/ */
export function cityDaylight(palette: ScenePalette): LightingState { export function cityDaylight(palette: ScenePalette, boardSpan = 230): LightingState {
return { return {
sun: { direction: [-0.632, 0.717, 0.295], color: 0xfff3e0, intensity: 2.1 }, sun: { direction: [-0.632, 0.717, 0.295], color: 0xfff3e0, intensity: 2.1 },
hemisphere: { sky: 0xdcecf7, ground: 0x6b6f5e, intensity: 1.05 }, hemisphere: { sky: 0xdcecf7, ground: 0x6b6f5e, intensity: 1.05 },
ambient: { color: 0xffffff, intensity: 0.32 }, ambient: { color: 0xffffff, intensity: 0.32 },
sky: { top: palette.skyTop, horizon: palette.skyHorizon }, sky: { top: palette.skyTop, horizon: palette.skyHorizon },
fog: { color: palette.skyHorizon, near: 210, far: 460 }, // Scaled to the board for the same reason the camera is, and it was the same
// bug: 210/460 were tuned when San Francisco's 230-unit board was the only
// one, and on the Bay Area's 1003 units they close the fog well inside the
// city. The camera sits about 0.6 spans out on a whole-board view, so a fog
// that starts nearer than that is behind the viewer's own shoulder.
//
// The default keeps the original numbers exactly, for a caller that has a
// palette but no world.
fog: {
color: palette.skyHorizon,
near: boardSpan * 0.91,
far: boardSpan * 2,
},
}; };
} }
+3 -2
View File
@@ -6,8 +6,9 @@
* The renderer and the loop live in `Stage`; the camera, controls, flights and * The renderer and the loop live in `Stage`; the camera, controls, flights and
* picking live in a `SceneKit`; what is left here is the office itself. Swap it * picking live in a `SceneKit`; what is left here is the office itself. Swap it
* in with `stage.setScene(office)` and the city is *paused, not disposed* — * in with `stage.setScene(office)` and the city is *paused, not disposed* —
* rebuilding SF's 336,864-point heightfield on the way back out costs about a * rebuilding the Bay Area's 0.53M-point heightfield on the way back out costs
* second, which is the measurement CONTRACT.md §1 is built on. * about two and a half seconds, which is the measurement CONTRACT.md §1 is
* built on.
* *
* Whoever builds one of these disposes it. `Stage` disposes nothing it did not * Whoever builds one of these disposes it. `Stage` disposes nothing it did not
* create, and the city handle's `dispose()` does not reach in here. * create, and the city handle's `dispose()` does not reach in here.