e41c90fe8d
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>
226 lines
8.5 KiB
TypeScript
226 lines
8.5 KiB
TypeScript
/**
|
||
* The simulated sky, as a plan rather than as positions.
|
||
*
|
||
* The engine's `SimulatedFlights` already evaluates a `SimRoute[]` in closed
|
||
* form against wall-clock time, so the server has nothing to simulate — it hands
|
||
* over the routes, a fixed phase origin and a seed, and every browser arrives at
|
||
* the same answer. One cacheable request replaces a poll per second, and two
|
||
* people on different machines see the same aircraft in the same places, which
|
||
* a per-client simulation cannot promise and which is the point of doing it this
|
||
* way.
|
||
*
|
||
* ### Where the coordinates came from
|
||
*
|
||
* The seven airport positions — SFO, OAK and SJC in the north, LAX, BUR, LGB and
|
||
* SNA in the south — are published FAA airport reference points, typed in by
|
||
* hand. They are US government facts in the public domain, and — as with every
|
||
* other coordinate in this repo — emphatically not derived from OpenStreetMap.
|
||
* Everything else here is a waypoint someone made up so the legs go the right
|
||
* way. See ARCHITECTURE.md §3.2 and CONTRACT.md §8.
|
||
*
|
||
* The callsigns are invented, with operator prefixes that belong to nobody. A
|
||
* repo that refuses to ship other people's logos should not ship their flight
|
||
* numbers either.
|
||
*/
|
||
|
||
import type { WireSimRoute } from "../../../src/server/wire.ts";
|
||
|
||
const SFO: [number, number] = [37.6188, -122.375];
|
||
const OAK: [number, number] = [37.7213, -122.2207];
|
||
const SJC: [number, number] = [37.3639, -121.9289];
|
||
|
||
/**
|
||
* Departures climb, arrivals descend, and a third of the traffic is just passing
|
||
* through at cruise. That mix is what makes the sky read as a working airspace
|
||
* rather than a carousel.
|
||
*/
|
||
const BAY_AREA: WireSimRoute[] = [
|
||
// Departures — out over the Pacific, north-east over the Central Valley, and
|
||
// south down the peninsula.
|
||
{ callsign: "LMB231", from: SFO, to: [37.3, -123.2], fromAlt: 20, toAlt: 10500, duration: 420 },
|
||
{ callsign: "LMB778", from: SFO, to: [38.4, -121.3], fromAlt: 20, toAlt: 11000, duration: 480 },
|
||
{ callsign: "PAC1082", from: SFO, to: [38.6, -122.9], fromAlt: 20, toAlt: 10000, duration: 460 },
|
||
{ callsign: "BAY412", from: OAK, to: [37.0, -121.2], fromAlt: 20, toAlt: 9500, duration: 400 },
|
||
{ callsign: "SIE1440", from: SJC, to: [36.6, -121.6], fromAlt: 20, toAlt: 9800, duration: 430 },
|
||
|
||
// Arrivals — the long descent over Point Reyes, the south-east downwind, and
|
||
// the two east-bay finals.
|
||
{ callsign: "GLD566", from: [38.3, -123.1], to: SFO, fromAlt: 6000, toAlt: 20, duration: 500 },
|
||
{ callsign: "LMB1889", from: [37.1, -121.6], to: SFO, fromAlt: 5500, toAlt: 20, duration: 520 },
|
||
{ callsign: "RDW915", from: [37.9, -121.3], to: OAK, fromAlt: 5200, toAlt: 20, duration: 470 },
|
||
{ callsign: "PAC331", from: [38.1, -122.3], to: SJC, fromAlt: 5800, toAlt: 20, duration: 540 },
|
||
|
||
// Overflights, level the whole way.
|
||
{
|
||
callsign: "GLD55",
|
||
from: [38.8, -122.6],
|
||
to: [36.6, -121.4],
|
||
fromAlt: 11000,
|
||
toAlt: 11000,
|
||
duration: 620,
|
||
},
|
||
{
|
||
callsign: "PAC21",
|
||
from: [37.9, -121.4],
|
||
to: [37.2, -123.4],
|
||
fromAlt: 10500,
|
||
toAlt: 10500,
|
||
duration: 700,
|
||
},
|
||
{
|
||
callsign: "RDW1205",
|
||
from: [36.5, -122.4],
|
||
to: [38.9, -122.0],
|
||
fromAlt: 10800,
|
||
toAlt: 10800,
|
||
duration: 660,
|
||
},
|
||
|
||
// Low and slow across the bay: general aviation is most of what is actually
|
||
// visible from the ground, and it is the only traffic that reads as *near*.
|
||
{
|
||
callsign: "BAY7789",
|
||
from: [37.45, -122.1],
|
||
to: [38.05, -122.45],
|
||
fromAlt: 900,
|
||
toAlt: 900,
|
||
duration: 520,
|
||
},
|
||
{
|
||
callsign: "SIE4402",
|
||
from: [37.95, -122.55],
|
||
to: [37.35, -121.85],
|
||
fromAlt: 1400,
|
||
toAlt: 1400,
|
||
duration: 560,
|
||
},
|
||
];
|
||
|
||
const LAX: [number, number] = [33.9425, -118.4081];
|
||
const BUR: [number, number] = [34.2007, -118.3585];
|
||
const LGB: [number, number] = [33.8177, -118.1516];
|
||
const SNA: [number, number] = [33.6757, -117.8682];
|
||
|
||
/**
|
||
* The Southland, which is a different shape of airspace and not a translated
|
||
* copy of the Bay Area.
|
||
*
|
||
* Almost everything here runs east–west, because the terrain does: the basin is
|
||
* walled by the San Gabriels to the north, so departures go out over the water
|
||
* and turn, and arrivals come down the length of the valley. Four fields instead
|
||
* of three, and the two small ones matter more than they do in the north — a
|
||
* board four times the area at a third of the scale reads as empty very fast if
|
||
* everything is at cruise.
|
||
*/
|
||
const SOCAL: WireSimRoute[] = [
|
||
// Departures — the standard west-over-the-ocean climb, the north-east haul
|
||
// over the Cajon Pass, and the two valley fields going their own way.
|
||
{ callsign: "LMB604", from: LAX, to: [33.6, -119.3], fromAlt: 20, toAlt: 10500, duration: 440 },
|
||
{ callsign: "PAC712", from: LAX, to: [34.6, -117.1], fromAlt: 20, toAlt: 11500, duration: 520 },
|
||
{ callsign: "SIE288", from: BUR, to: [34.9, -118.9], fromAlt: 20, toAlt: 9800, duration: 460 },
|
||
{ callsign: "BAY1516", from: SNA, to: [33.1, -118.6], fromAlt: 20, toAlt: 9600, duration: 420 },
|
||
{ callsign: "GLD843", from: LGB, to: [34.5, -116.9], fromAlt: 20, toAlt: 10200, duration: 500 },
|
||
|
||
// Arrivals — the long straight-in from the east, the coastal descent from the
|
||
// north-west, and one down the back of the mountains into Burbank.
|
||
{ callsign: "LMB1170", from: [34.1, -116.8], to: LAX, fromAlt: 6200, toAlt: 20, duration: 560 },
|
||
{ callsign: "RDW425", from: [34.7, -119.4], to: LAX, fromAlt: 6800, toAlt: 20, duration: 600 },
|
||
{ callsign: "PAC96", from: [34.8, -117.6], to: BUR, fromAlt: 5400, toAlt: 20, duration: 480 },
|
||
{ callsign: "SIE1901", from: [33.2, -117.2], to: SNA, fromAlt: 5000, toAlt: 20, duration: 450 },
|
||
|
||
// Overflights, level the whole way: the coastal corridor and the desert one.
|
||
{
|
||
callsign: "GLD1330",
|
||
from: [34.9, -119.2],
|
||
to: [32.9, -117.0],
|
||
fromAlt: 11200,
|
||
toAlt: 11200,
|
||
duration: 690,
|
||
},
|
||
{
|
||
callsign: "RDW78",
|
||
from: [33.0, -119.1],
|
||
to: [34.9, -116.8],
|
||
fromAlt: 10600,
|
||
toAlt: 10600,
|
||
duration: 720,
|
||
},
|
||
|
||
// Low across the basin. General aviation is most of what anybody standing in
|
||
// Culver City actually sees, and it is the only traffic that reads as *near*.
|
||
{
|
||
callsign: "BAY3312",
|
||
from: [33.78, -118.42],
|
||
to: [34.16, -117.75],
|
||
fromAlt: 850,
|
||
toAlt: 850,
|
||
duration: 540,
|
||
},
|
||
{
|
||
callsign: "LMB2044",
|
||
from: [34.22, -118.72],
|
||
to: [33.72, -117.98],
|
||
fromAlt: 1300,
|
||
toAlt: 1300,
|
||
duration: 570,
|
||
},
|
||
];
|
||
|
||
/**
|
||
* The airspaces this file has actually laid out, and how close a region's centre
|
||
* has to be to get one.
|
||
*
|
||
* Degrees rather than kilometres because the comparison is a box, not a circle,
|
||
* and the radii are generous: the Bay Area's is roughly downtown to the far end
|
||
* of the bay, and the Southland's spans a basin that runs from Ventura to
|
||
* Riverside. A region centre that lands inside one of these is close enough that
|
||
* the real airports are the right answer; anything else gets spokes.
|
||
*/
|
||
const AIRSPACES: { centre: [number, number]; radius: number; routes: WireSimRoute[] }[] = [
|
||
{ centre: [37.7749, -122.4194], radius: 0.75, routes: BAY_AREA },
|
||
{ centre: [33.82, -118.05], radius: 1.0, routes: SOCAL },
|
||
];
|
||
|
||
/**
|
||
* A plan for a city this file has never heard of.
|
||
*
|
||
* A self-hoster pointing `TERA_REGIONS` (or `TERA_ORIGIN_LAT/LNG`) at somewhere
|
||
* that is neither of the two boards should get moving aircraft rather than an
|
||
* empty sky, so eight legs are laid out on evenly-spaced bearings through the
|
||
* region centre. It is not their city's real airspace and does not pretend to be
|
||
* — it is motion in the right kind of place, which is all the map ever wanted
|
||
* from this.
|
||
*/
|
||
function genericPlan(lat: number, lng: number): WireSimRoute[] {
|
||
const routes: WireSimRoute[] = [];
|
||
const span = 0.55;
|
||
for (let i = 0; i < 8; i++) {
|
||
const bearing = (i / 8) * Math.PI * 2;
|
||
const dLat = Math.cos(bearing) * span;
|
||
const dLng = (Math.sin(bearing) * span) / Math.max(0.2, Math.cos((lat * Math.PI) / 180));
|
||
const cruise = 8000 + (i % 4) * 900;
|
||
routes.push({
|
||
callsign: `LMB${100 + i * 37}`,
|
||
from: [lat - dLat, lng - dLng],
|
||
to: [lat + dLat, lng + dLng],
|
||
fromAlt: i % 3 === 0 ? 600 : cruise,
|
||
toAlt: cruise,
|
||
duration: 380 + i * 45,
|
||
});
|
||
}
|
||
return routes;
|
||
}
|
||
|
||
export function planFor(centre: { lat: number; lng: number }): WireSimRoute[] {
|
||
for (const airspace of AIRSPACES) {
|
||
const [lat, lng] = airspace.centre;
|
||
if (
|
||
Math.abs(centre.lat - lat) < airspace.radius &&
|
||
Math.abs(centre.lng - lng) < airspace.radius
|
||
) {
|
||
return airspace.routes;
|
||
}
|
||
}
|
||
return genericPlan(centre.lat, centre.lng);
|
||
}
|