From fe43077ff89f3a9e9418666b960a4cc52e1984df Mon Sep 17 00:00:00 2001 From: Karti Tripathi Date: Wed, 5 Aug 2026 04:34:00 -0700 Subject: [PATCH] The same bug the camera had, still live in the default light rig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ARCHITECTURE.md | 6 ++++-- CONTRACT.md | 5 +++-- server/package.json | 2 +- src/cities/socal.ts | 18 ++++++++++++------ src/engine/scene.ts | 22 +++++++++++++++++----- src/interiors/officeScene.ts | 5 +++-- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 0d6b1ce..dd772ed 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -214,7 +214,8 @@ receiver as the eventual real answer. ## 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 -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**. 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 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 - 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 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 diff --git a/CONTRACT.md b/CONTRACT.md index be9c380..68a9537 100644 --- a/CONTRACT.md +++ b/CONTRACT.md @@ -37,8 +37,9 @@ asserting `GET /api/v1/health` → 200. ## 1. Stage and scene lifecycle Two designs both created `src/engine/stage.ts` with opposite lifecycles. The -retention argument wins on measured cost: SF's heightfield is 484 × 696 = -336,864 lattice points and `world.ts` records a ~1.0 s build. Disposing the city +retention argument wins on measured cost: the Bay Area's heightfield is 0.53M +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 way out is not acceptable. diff --git a/server/package.json b/server/package.json index 3db1ed9..d43ca93 100644 --- a/server/package.json +++ b/server/package.json @@ -2,7 +2,7 @@ "name": "@lumbridge/tera-api", "version": "0.1.0", "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", "type": "module", "main": "src/index.ts", diff --git a/src/cities/socal.ts b/src/cities/socal.ts index 6302c5e..bd143fb 100644 --- a/src/cities/socal.ts +++ b/src/cities/socal.ts @@ -3088,12 +3088,18 @@ export const SOCAL_CITY: City = { /** * 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 - * target and `cityDaylight` closes its fog at 460, so however large a city is - * on the ground it has to end up roughly the same size in scene units or it - * 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 - * the camera will stretch. + * The board this produces is 393 by 308 units, against the Bay Area's 1003 by + * 830 — a metro four times the area rendered on a third of the board, which is + * why one instance here is a city block rather than a building. + * + * This used to be forced rather than chosen: `scene.ts` capped the orbit at + * 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, diff --git a/src/engine/scene.ts b/src/engine/scene.ts index 0821d17..11ca34c 100644 --- a/src/engine/scene.ts +++ b/src/engine/scene.ts @@ -13,8 +13,8 @@ * 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 * 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 - * heightfield on the way back. See CONTRACT.md §1. + * keeps this city alive and paused rather than rebuilding its heightfield — for + * the Bay Area, about 2.3 s — on the way back. See CONTRACT.md §1. */ import * as THREE from "three"; @@ -113,7 +113,7 @@ export function createScene(canvas: HTMLCanvasElement, options: SceneOptions): S shadowExtent: boardSpan * 0.75, shadowFar: boardSpan * 2.2, }); - kit.applyLighting(options.lighting ?? cityDaylight(pal)); + kit.applyLighting(options.lighting ?? cityDaylight(pal, boardSpan)); scene.add(createWater(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 * 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 { sun: { direction: [-0.632, 0.717, 0.295], color: 0xfff3e0, intensity: 2.1 }, hemisphere: { sky: 0xdcecf7, ground: 0x6b6f5e, intensity: 1.05 }, ambient: { color: 0xffffff, intensity: 0.32 }, 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, + }, }; } diff --git a/src/interiors/officeScene.ts b/src/interiors/officeScene.ts index 41bde52..56da1f7 100644 --- a/src/interiors/officeScene.ts +++ b/src/interiors/officeScene.ts @@ -6,8 +6,9 @@ * 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 * 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 - * second, which is the measurement CONTRACT.md §1 is built on. + * rebuilding the Bay Area's 0.53M-point heightfield on the way back out costs + * 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 * create, and the city handle's `dispose()` does not reach in here.