1
0

Real weather, real aircraft, a heightfield off the main thread, and instruments

Three things that were built and never connected, connected.

**The weather was already there.** `observe()` has always taken a
`WeatherObservation` and `main.ts` has always passed null, so the cloud,
precipitation, visibility and marine-layer paths in atmosphere.ts had never run
outside a test. The server already shipped NWS, met.no and Open-Meteo, all
configured off. What was actually missing was that a single TERA_ORIGIN_LAT/LNG
served one metro and lied to the other — so weather and traffic are per-region
now, derived from the city's own bounds, and the Bay Area gets its fog while
Long Beach gets its own sky. The route takes ?city= or a validated ?lat=&lng=
and refuses to become an open geocoding proxy for the planet.

**The heightfield moved to a Worker.** 2.3 s of blocked main thread at boot, and
another ~950 ms of point-in-polygon on top of it: the park mask is filled in the
worker now, and block placement samples four corners and only runs the exact
test on a cell that straddles an edge — 8 buildings differ out of 185,036.
createScene is async and takes a Stage as a consequence, and there is a
main-thread fallback because "clone it and it works" has no exception clause.

**Spaces is a chunk you fetch when you reach for the door**, not one everybody
downloads. Same for the godmode tools. The entry chunk is 722 kB rather than
772; three.js is most of what is left and splitting it is a different job.

**Godmode is an instrument panel now** rather than one slider: the date and the
season, not just the hour, so the Meeus moon and the sun's seasonal arc become
visible instead of merely correct; a weather override that says on screen when
it is lying; a frame-time and draw-call readout; and a pose editor that emits a
paste-ready Chapter block, which is the thing that makes adding New York cheap.

Two blockers the review caught:

  - Every city switch leaked 8 GPU textures — one of them a 2048x2048 shadow map
    — and ~10.5 shader programs, and deleteTexture had never been called once in
    the app's lifetime. The renderer was being built per scene; it belongs to the
    canvas, for the life of the page.
  - An upstream fetch that threw rather than returning null skipped the cache
    stamp, so the TTL — the only rate limit on outbound calls — collapsed to one
    upstream request per inbound request, and the caller got a 500.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-06 03:25:31 -07:00
parent a6f6a91813
commit e41c90fe8d
39 changed files with 8482 additions and 503 deletions
+35
View File
@@ -0,0 +1,35 @@
/**
* `src/tools/` — the instruments. Everything in here is for the person building
* this map, not for the person looking at it.
*
* There is one rule and this file exists to hold it: **nothing under
* `src/tools/` may be reached by a static import from the app.** `main.ts` loads
* it like this, and only like this:
*
* ```ts
* if (access.can.debug) {
* const { createGodmode } = await import("./tools/index.ts");
* godmode = createGodmode({ ... });
* }
* ```
*
* A static `import` would put the panel in the entry chunk, and the entry chunk
* is 772 kB before anybody has done anything — the whole thing arrives, parses
* and runs for every anonymous visitor who will never see a single control in
* it. Behind a dynamic import Vite gives the tools a chunk of their own, and a
* visitor who is not god does not download the code at all. That is also the
* strongest available reading of "nothing here may run for a non-god visitor":
* not a hidden panel, not a disabled panel, no panel.
*
* The corollary is that the arrow points one way. A tool may import from
* `engine/`, `access.ts` or `adapters/`; nothing outside `src/tools/` may import
* from inside it except through an `await import()`, or the split silently stops
* happening and nobody notices until the bundle is measured again.
*
* Type-only imports are the exception and are free: `import type { Godmode }` is
* erased at build time and creates no chunk edge. `main.ts` needs one to hold
* the handle in a nullable field.
*/
export { createGodmode } from "./godmode.ts";
export type { Godmode, GodmodeOptions, GodmodePlace } from "./godmode.ts";