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:
+61
-8
@@ -19,6 +19,7 @@
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { parseScryptHash, type ScryptHash } from "./auth/password.ts";
|
||||
import { loadRegions, type RegionSet } from "./regions.ts";
|
||||
import type {
|
||||
AuthMode,
|
||||
FlightsSourceId,
|
||||
@@ -114,8 +115,17 @@ export interface Config {
|
||||
port: number;
|
||||
logLevel: string;
|
||||
version: string;
|
||||
/** The city this box is serving, in degrees. Used by weather and by flights. */
|
||||
/**
|
||||
* The default place this box serves, in degrees.
|
||||
*
|
||||
* Kept because it is what a self-hoster already wrote in their env file, and
|
||||
* because `regions[0]` is derived from it. Nothing per-request reads it any
|
||||
* more: weather and flights answer for a *resolved region*, since one origin
|
||||
* cannot describe two cities six hundred kilometres apart. See `regions.ts`.
|
||||
*/
|
||||
origin: { lat: number; lng: number };
|
||||
/** Every place this box will answer for. The first one is the default. */
|
||||
regions: RegionSet;
|
||||
/** Allowed CORS origins. Empty means same-origin only, which is the default. */
|
||||
corsOrigins: string[];
|
||||
/** `max-age` for routes that opt in to public caching. */
|
||||
@@ -136,18 +146,29 @@ export function loadConfig(env: Env = process.env): Config {
|
||||
|
||||
const weather = loadWeather(env, degraded);
|
||||
const flights = loadFlights(env, degraded);
|
||||
const markers = loadMarkers(env, degraded);
|
||||
const auth = loadAuth(env, degraded);
|
||||
// After auth, because a marker feed with nobody able to sign in is worth a
|
||||
// sentence and the sentence is only true once `mode` has finished demoting.
|
||||
const markers = loadMarkers(env, auth.mode, degraded);
|
||||
|
||||
const origin = {
|
||||
lat: num(env, "TERA_ORIGIN_LAT", 37.7749, degraded),
|
||||
lng: num(env, "TERA_ORIGIN_LNG", -122.4194, degraded),
|
||||
};
|
||||
|
||||
return {
|
||||
host: str(env, "TERA_HOST", "127.0.0.1"),
|
||||
port: num(env, "TERA_PORT", 8431, degraded),
|
||||
logLevel: str(env, "TERA_LOG_LEVEL", "info"),
|
||||
version: readVersion(),
|
||||
origin: {
|
||||
lat: num(env, "TERA_ORIGIN_LAT", 37.7749, degraded),
|
||||
lng: num(env, "TERA_ORIGIN_LNG", -122.4194, degraded),
|
||||
},
|
||||
origin,
|
||||
regions: loadRegions({
|
||||
spec: str(env, "TERA_REGIONS", ""),
|
||||
origin,
|
||||
originConfigured:
|
||||
str(env, "TERA_ORIGIN_LAT", "") !== "" || str(env, "TERA_ORIGIN_LNG", "") !== "",
|
||||
degraded,
|
||||
}),
|
||||
corsOrigins: list(env, "TERA_CORS_ORIGIN"),
|
||||
publicMaxAge: num(env, "TERA_PUBLIC_MAX_AGE", 60, degraded),
|
||||
weather,
|
||||
@@ -236,7 +257,7 @@ function loadFlights(env: Env, degraded: string[]): FlightsConfig {
|
||||
return {
|
||||
source,
|
||||
endpoint: str(env, "TERA_ADSB_ENDPOINT", "https://api.adsb.lol"),
|
||||
radiusNm: num(env, "TERA_ADSB_RADIUS_NM", 40, degraded),
|
||||
radiusNm: radius(num(env, "TERA_ADSB_RADIUS_NM", 40, degraded), degraded),
|
||||
dump1090Path,
|
||||
epochMs: num(env, "TERA_FLIGHTS_EPOCH_MS", PLAN_EPOCH_MS, degraded),
|
||||
seed: num(env, "TERA_FLIGHTS_SEED", 4711, degraded),
|
||||
@@ -244,12 +265,31 @@ function loadFlights(env: Env, degraded: string[]): FlightsConfig {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The largest circle the hosted feeds will answer for: adsb.lol and
|
||||
* airplanes.live both cap `/v2/point/:lat/:lng/:radius` at 250 nautical miles
|
||||
* and reject anything larger outright. Clamping here rather than letting the
|
||||
* request fail keeps the failure legible — an operator who typed 2500 gets a map
|
||||
* with aircraft on it and a sentence in `degraded`, not a silently empty sky.
|
||||
*/
|
||||
const MAX_ADSB_RADIUS_NM = 250;
|
||||
|
||||
function radius(asked: number, degraded: string[]): number {
|
||||
if (asked >= 1 && asked <= MAX_ADSB_RADIUS_NM) return asked;
|
||||
const clamped = Math.min(MAX_ADSB_RADIUS_NM, Math.max(1, asked));
|
||||
degraded.push(
|
||||
`TERA_ADSB_RADIUS_NM=${asked} is outside the 1–${MAX_ADSB_RADIUS_NM} nautical ` +
|
||||
`miles the hosted feeds answer for; using ${clamped}.`,
|
||||
);
|
||||
return clamped;
|
||||
}
|
||||
|
||||
const MARKER_SOURCES: MarkersSourceId[] = ["none", "file"];
|
||||
|
||||
/** CONTRACT.md §8. Adding to this list is a licence decision, not a config tweak. */
|
||||
export const DEFAULT_PROVENANCE_ALLOWLIST = ["us-census", "hand-placed", "synthetic"];
|
||||
|
||||
function loadMarkers(env: Env, degraded: string[]): MarkersConfig {
|
||||
function loadMarkers(env: Env, authMode: AuthMode, degraded: string[]): MarkersConfig {
|
||||
const asked = str(env, "TERA_MARKERS_SOURCE", "none");
|
||||
let source = oneOf(asked, MARKER_SOURCES);
|
||||
if (source === null) {
|
||||
@@ -268,6 +308,19 @@ function loadMarkers(env: Env, degraded: string[]): MarkersConfig {
|
||||
source = "none";
|
||||
}
|
||||
|
||||
// Not a demotion — the feed stays configured and the route keeps refusing
|
||||
// correctly — but an operator who mounted a marker file on a box where nobody
|
||||
// can sign in has built something no browser will ever see, and one sentence
|
||||
// now is cheaper than an afternoon. `routes/markers.ts` has the reasoning for
|
||||
// why the feed is members-only with no public escape hatch.
|
||||
if (source === "file" && authMode === "none") {
|
||||
degraded.push(
|
||||
"TERA_MARKERS_SOURCE=file needs an authentication mode: the marker feed is " +
|
||||
"refused to anonymous callers, and with TERA_AUTH_MODE=none nobody is ever " +
|
||||
"anything else, so it will answer 401 to everybody. Set TERA_AUTH_MODE.",
|
||||
);
|
||||
}
|
||||
|
||||
const allowlist = list(env, "TERA_MARKERS_PROVENANCE_ALLOWLIST");
|
||||
return {
|
||||
source,
|
||||
|
||||
Reference in New Issue
Block a user