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:
@@ -1,18 +1,48 @@
|
||||
/**
|
||||
* `GET /api/v1/flights`.
|
||||
* `GET /api/v1/flights` — for a city, not for the box.
|
||||
*
|
||||
* `?city=socal`, or `?lat=&lng=` resolved against the same allowlist the weather
|
||||
* route uses, or neither for the default region. `regions.ts` owns the
|
||||
* validation, the refusal, and the reasoning behind refusing at all: a live
|
||||
* traffic endpoint that will fetch any coordinate on demand is an amplifier
|
||||
* pointed at a volunteer-funded feed.
|
||||
*
|
||||
* Publicly cacheable, because the whole design of the plan is that one response
|
||||
* serves every viewer for its whole TTL. Aircraft are not personal data and this
|
||||
* body never varies by who asked.
|
||||
* serves every viewer of a region for its whole TTL. Aircraft are not personal
|
||||
* data and this body never varies by who asked — only by where.
|
||||
*
|
||||
* ### `radiusNm` on the query is ignored, deliberately
|
||||
*
|
||||
* The browser sends one. It is dropped, and the size of the circle stays
|
||||
* `TERA_ADSB_RADIUS_NM`, for a reason that is not stubbornness: the cache key
|
||||
* would have to include it, and a caller who can choose the key can make this
|
||||
* box hold an unbounded number of entries and issue an unbounded number of
|
||||
* distinct upstream requests — each one more expensive than the last, since a
|
||||
* wider circle is more work for the feed to answer. Every bound in
|
||||
* `flights/index.ts` and `regions.ts` rests on the key space being the operator's
|
||||
* region list, and a query parameter that widens it dissolves all of them.
|
||||
*
|
||||
* An operator whose board is bigger than the circle raises
|
||||
* `TERA_ADSB_RADIUS_NM`; 60 nm covers both shipped cities. The client already
|
||||
* discards aircraft outside the region it drew, so a circle that is too large
|
||||
* costs a little bandwidth and nothing else.
|
||||
*/
|
||||
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { publicCache } from "../cache.ts";
|
||||
import { resolveRegion, type RegionQuery } from "../regions.ts";
|
||||
import type { ErrorBody } from "../../../src/server/wire.ts";
|
||||
import type { Services } from "../services.ts";
|
||||
|
||||
export function registerFlights(app: FastifyInstance, services: Services): void {
|
||||
app.get("/api/v1/flights", async (req, reply) => {
|
||||
const body = await services.flights.current();
|
||||
app.get<{ Querystring: RegionQuery }>("/api/v1/flights", async (req, reply) => {
|
||||
const resolved = resolveRegion(services.config.regions, req.query);
|
||||
if (!resolved.ok) {
|
||||
const error: ErrorBody = { error: "bad_request", message: resolved.message };
|
||||
return reply.code(400).send(error);
|
||||
}
|
||||
|
||||
const body = await services.flights.current(resolved.region);
|
||||
publicCache(req, reply, body.ttlSeconds);
|
||||
return body;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user