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:
@@ -235,6 +235,32 @@ Two changes make it work, and both are cheap now:
|
||||
SF gets one focus region covering the whole city and behaves exactly as it does
|
||||
now. LA gets six. NYC, later, gets Manhattan plus the inner boroughs.
|
||||
|
||||
### 5.1 The heightfield is built off the main thread
|
||||
|
||||
Both numbers above are seconds during which nothing rendered and nothing
|
||||
responded, because the build ran on the thread that paints. It runs in a Worker
|
||||
now (`src/engine/terrain.worker.ts`), which is why `createScene` is async and
|
||||
takes a `Stage` rather than a canvas.
|
||||
|
||||
Three consequences worth knowing before touching it:
|
||||
|
||||
- **The city pack has to stay structured-cloneable.** It is posted to the
|
||||
worker as data. A `City` that acquires a method, a class instance or a
|
||||
closure stops being sendable, and the fix is to remove it rather than to
|
||||
JSON round-trip around it — a city pack containing code is the thing §2
|
||||
exists to prevent.
|
||||
- **The sampled accessors stay synchronous.** `elevationAt`, `groundAt` and
|
||||
`isLand` are called in tight loops by `terrain.ts`, `blocks.ts` and
|
||||
`minimap.ts`; the asynchrony is confined to *becoming ready*, and sampling
|
||||
before then is a documented error rather than a silent zero.
|
||||
- **There is a main-thread fallback and it is not optional.** An environment
|
||||
without Workers — a `file://` open, a locked-down browser — still has to
|
||||
build and render, because "clone it and it works" has no exception clause.
|
||||
|
||||
The same reasoning put Spaces behind an `await import()`: the office is a large
|
||||
slice of the bundle and most visitors never open it, so it is fetched when
|
||||
somebody reaches for the door rather than by everybody at boot.
|
||||
|
||||
---
|
||||
|
||||
## 6. How Workie feeds it
|
||||
|
||||
Reference in New Issue
Block a user