Merge remote-tracking branch 'upstream/main' into q686

# Conflicts:
#	CHANGELOG.md
#	scripts/format-scope.json
This commit is contained in:
daikaginza committed 2026-10-05 17:32:32 +07:00
commit bea76df454
401 files changed
+47310 -3614

No files matched your search

+7 -3
View File
@@ -17,6 +17,8 @@ until deliberately adopted.
Generated output, local configuration, browser evidence and bundled datasets are
excluded. The formatter validates every entry before writing any file.
New world text labels must use the world-overlay host; Cesium text labels are forbidden.
## Current component ownership
Package imports use `gods-eye-view`; `package.json` is the authoritative
@@ -668,8 +670,8 @@ An empty search array is a definitive miss. HTTP failures, malformed results
and oversized responses remain retryable. Attribution identifies OpenStreetMap /
Nominatim.
Overpass currently supplies traffic road geometry, ALPR camera records, military
installation footprints, and annotation geometry (administrative boundaries,
Vector tile sources supply traffic roads, ALPR records and keyless military
areas. Operator-configured Overpass supplies named installations and annotation geometry (administrative boundaries,
neighborhoods, streets, building/grounds outlines and monument candidates).
Nominatim forward/reverse lookup does not replace those queries. Layer source
interfaces select traffic, camera and installation ingestion separately;
@@ -711,7 +713,9 @@ query-language strings or decoding backend tags and relation members.
Points use `{lat, lon}`. Options carry `signal`. A successful array, including an
empty array, is definitive; `null` means a transient failure;
`{rateLimited: true, retryAfterMs}` retains a provider's retry delay. Candidates
`{rateLimited: true, retryAfterMs}` retains a provider's retry delay.
`{unavailable:true, code:'OVERPASS_NOT_CONFIGURED', retryable:false}` is a
capability miss that stops retries while preserving pins and offline geometry. Candidates
carry `id`, `names` (`primary`, `english`, `official`, `alternate`, `short`),
`coordinates` (objects with `lat` and `lon`), `building` and optional `heightM`,
`center` and `point`. Administrative candidates use `category: 'administrative'`
+540 -57
View File
@@ -1,10 +1,51 @@
# God's Eye View Current State
## Cyber HUD — September 23, 2026
## God's Eye View in conversations — October 2, 2026
Tool answers that can be shown in God's Eye View include a view: camera, layers,
style, map, marks and an aircraft or satellite to follow, written in the
share-link format (`gods-eye-view/view`). `show_in_gods_eye_view` shows a view as live
God's Eye View inside clients that display MCP Apps, and as a link everywhere.
The panel runs the app's panel build (`npm run build:panel`, served at
`/panel/`) and loads it, with its data, through the MCP server, so it works
in Claude Desktop and in the Codex and ChatGPT desktop apps with a local
server and no HTTPS. `?embed=1` shows the app as the globe alone and takes
new views from the page that frames it; framing is off unless
`GEV_EMBED_FRAME_ANCESTORS` allows the framing page. MCP leads with the tools that find
what to show. See [tools and the MCP server](TOOLS.md).
## Tools and local MCP server — October 1, 2026
`gods-eye-view/tools` defines queries that answer questions from the app's data,
and `gods-eye-view/tools/mcp` exposes a composed catalog over the Model Context
Protocol. `npm run mcp` serves Core's tools over stdio to a local MCP client,
reading from a running app's `/api` routes (default `http://localhost:4173`).
The development and preview servers also serve the tools over HTTP at `/mcp`,
accepting only direct local requests: a loopback host on the port reached,
an `Origin` (when sent) from that same host, no proxy forwarding headers, and
launcher sharing off.
Queries cover earthquakes, active fires, recent launches, aircraft (in an
area, by identifier, tracks, and type and route lookups), ships (in an area,
by identifier, and tracks), satellites (next
pass over a point, and those overhead now), public cameras (including a
camera's current image), license plate reader cameras, radio stations, place search, routing, bike-share
stations, transit vehicles, road traffic flow, weather, weather map images
(radar, satellite, lightning), wind, the most recent satellite image of an
area, submarine cables, datacenters and dams, the Bhote Koshi flood event pack, regional briefs, tropical cyclones, fire
perimeters, terrain height, military installations and map features, plus a
combined situation brief, military awareness around a point, the app's heads-up display caption, and a link that
opens the app over an area. Tools reuse the layers' portable source factories, take a
shared `area` argument (place name, bounding box, or point and radius) and cap
lists at 25 rows by default. Voice offers the same queries next to its app
actions: the session lists them, and the browser runs them through the same
catalog, loaded on first use. See [tools and the MCP server](TOOLS.md).
## Cyber HUD — September 30, 2026
Display > HUD > Layout includes Cyber, also available through the HUD voice
action and shared visual state. An explicit first transition into Cyber selects
FLIR with Ironbow 0.42; restored links and subsequent visual tuning remain
FLIR with Ironbow 0.42. An explicit switch back to another HUD layout restores
the visual preset used before Cyber; restored links and scenes remain
authoritative. The skin uses shared red/slate panel treatments in map and cockpit,
with compact 200px collapsed controls and wider expanded panels. Other HUD
layouts retain their existing presentation.
@@ -17,6 +58,21 @@ headers and decorative frames. The narrow-screen rail remains scrollable to reac
each panel. Radio retains the shared nested Context player and compact
header disclosure, without relocating playback controls on theme changes.
Voice help/error popups and Location/Visual Presets pins extend beyond their
Cyber frames without being clipped: the shapes decorate non-interactive
pseudo-elements. The lower-left telemetry card leaves room for attribution's
full logo row. New panels can use the [shared surface contract](panel-surfaces.md)
for theme tokens, rail input and a fixed header with a bounded scroll body.
The contract is intentionally future-facing and opt-in; its first production
adopter will land after this change. Existing owners still control disclosure,
persistence and placement, while small-screen rail popup clipping and other
integration limits remain documented in the contract.
On desktop, Cyber raises the left rail so it clears the lower coordinate card.
`layoutRightPanelRail` reads that rendered top as its measured `baseTop`, which
keeps the right rail aligned. Cockpit retains its separate rail placement for
its visor layout.
Sonar has one contact-highlighting method. Native Cesium points, billboards and
labels are treated in GPU draw commands without replacing their positions,
identities or pick commands. Model and canvas/DOM overlays retain their own
@@ -224,6 +280,22 @@ Voice and HUD snapshots reuse the existing feedState classifier. Analyst follow-
Satellite and local infrastructure layers expose on-demand analyst records through their current factory owners. Analyst counts and ranks explicitly cover only bounded examined loaded records (default 2,000 per new layer, core satellite rows before dense extras); omitted records can change nearest/count and satellite distance is ground distance. Existing tools and result fields remain available.
Keyless terrain tiles retry when Re:Earth throttles them. The browser fetches
`terrain.reearth.land/cesium-mesh/ellipsoid/{z}/{x}/{y}.terrain` directly; no
proxy in this repository sees those requests (`/api/terrain/heights` is the
separate point-height endpoint), and the upstream edge answers zoom-out bursts
with HTTP 429 under load. `src/maps/terrain.js` hands Cesium a `Resource`
whose retry policy (`src/maps/terrainRetry.js`) every derived tile fetch
inherits: 429, 502, 503 and 504 replies retry up to three times, 750 ms
doubling to a 15 s cap, with every retry waiting behind one shared cooldown
plus up to 1.5 s of jitter, and `Retry-After` extending the cooldown when the
upstream exposes it. One console warning is logged per cooldown window. Other
failures keep Cesium's handling and the flat `EllipsoidTerrainProvider`
fallback. `scripts/qa-terrain-429.mjs` reproduces the burst against a keyless
dev server, counts real throttles with their headers as evidence, synthesises
throttles with `--inject N` when the upstream is not throttling, and fails if
a throttled tile never loads.
AIS encodes speed over ground in 0.1-knot units and course over ground in
0.1-degree units, reserving the top code of each field for "not available", so
those reports arrive as 102.3 knots and 360 degrees. Both are stored as unknown,
@@ -303,7 +375,7 @@ are protected from fallback writes. Version 6 is the export format, with
zero pitch, low camera heights, scope and detection edits preserved. See [the document contract](SCENE-DOCUMENT.md);
see [Director](DIRECTOR.md) for camera, pack, action and sharing support.
Search framing and annotations request semantic map features from an explicit source. The Overpass adapter owns bounded queries, member/tag decoding and request deadlines; callers retain candidate ranking, outline caching, deferred retries and scene placement. Empty, transient and throttled outcomes remain distinct. Traffic sources return road records and installation sources return mapped records with freshness/saturation metadata, so their layers no longer decode upstream elements. ALPR already normalizes its records in the source. Default providers, footprints, road directions, exact-viewport retries and source attribution are unchanged.
Search framing and annotations request semantic map features from an explicit source. The Overpass adapter owns bounded queries, member/tag decoding and request deadlines; callers retain candidate ranking, outline caching, deferred retries and scene placement. Empty, unavailable, transient and throttled outcomes remain distinct. Traffic sources return road records and installation sources return mapped records with freshness/saturation metadata, so their layers no longer decode upstream elements. ALPR already normalizes its records in the source. Traffic uses OpenFreeMap roads with optional matched TomTom congestion; ALPR uses an hourly OpenStreetMap extract. Keyless installations use military-area tiles. Detailed feature queries require operator-configured Overpass.
Nepal media preloads survive repeated camera-flight updates, but Stop, event disable and replacement remove abandoned frames and revoke pending Facebook sessions. Fallback evidence-card clicks respect the shared drawing-tool pointer lease.
@@ -426,12 +498,30 @@ selection, route/direct-line honesty and tool schemas are preserved. Node
Google, OSRM and Nominatim adapters accept trusted construction-time configuration;
request parameters cannot choose arbitrary upstream URLs. See APPLICATION.md.
Every ALPR native marker is repositioned on map-source changes, including canvas-owned saved appearances, while retaining its entity identity.
ALPR floor preparation and clamped markers share a per-cell queue, with at most one 64-cell terrain batch active per layer. Disabling or destroying the layer cancels its terrain consumer and discards queued cells. Repeated moves inside an outstanding query retain the displayed markers until the new snapshot is ready.
The optional **ALPR Cameras** layer shows community-mapped OpenStreetMap locations,
not camera footage or plate records. City-scale queries use the existing Overpass
provider, with capped results, retry, cached-data and incomplete-coverage notices.
not camera footage or plate records. City-scale acquisition uses an hourly
OpenStreetMap US/Canada extract, with available operator/brand/bearing
attributes. The extract carries full attributes from z9 (z0-8 are geometry-only
heat tiles); each view reads the finest zoom from z12 down to z9 that fits 16
tiles, so a 30 km whole-city view loads about 1,000 Austin cameras and a
neighbourhood reads z12 (about 2 m position precision). Each camera keeps the
finest position seen, so zooming out never nudges a marker. The fetched tiles'
bounds are the accepted coverage: later views inside it (and precise enough for
it) reuse the records at once, without a request or debounce, and a still camera
sees one marker update per move. Missing verification dates remain unknown; OSM edit dates do not
stand in for verification. Outside coverage the row says "No ALPR data for this
area". Reads cap tiles at 16 per country, concurrency at four per country,
responses at 4 MiB, decoded cache at 64 entries/24 MiB per country with a one-hour
TTL, and accepted records at 6,000; at most 1,500 cameras in view render,
nearest the view centre first, and more than that reads "Coverage limited".
Partial coverage remains explicit.
Its `./layers/alpr` entry exposes a per-instance layer factory and bounded source
adapter. Sources return normalized records, stale/coverage flags and their own
attribution; rendering does not parse Overpass payloads. The standalone
attribution; rendering does not parse tile payloads. The standalone
registration supplies selection, picking, terrain and
render services. Selected cards and Data attribution identify OpenStreetMap.
Layer state and the existing voice layer tools include `alpr-cameras`.
@@ -440,8 +530,28 @@ screen. Cyan camera badges use Manjunath's camera artwork; selection switches to
larger coral badge with corner brackets and an animated tactical card. Numeric
mapped bearings draw illustrative 90 m direction wedges (not measured coverage).
The shared overlay paints gradients for at most 64 nearby cameras, promoting the
selected camera; farther cameras retain native Cesium badges and ground-clamped
wedges. Unknown bearings never acquire a wedge. Asset URLs resolve relative to
selected camera; farther cameras keep native Cesium badges without wedges.
Native ground-clamped wedges (a ground polyline plus a classification polygon per
camera) cost about 40 ms per frame for 200 downtown cameras on photoreal tiles
and were removed. Badges sit on the cached ground floor (validated mesh or DEM,
+1.5 m) rather than ground clamping, which re-sampled every badge whenever a
tile streamed in; floors resolve before new badges appear (bounded wait), and a
badge whose cell has not resolved clamps until it does, then moves in place.
Overlay anchors start on the same floor and write marker properties only on
change. Once the camera is still and tiles have settled, each overlay anchor is
replaced once by the rendered surface under it (two depth samples per paint,
accepted only within -30/+40 m of the floor cell), so badges do not float or
sink on slopes; wedge corners follow their own floor cells instead of staying
level with the apex. Unknown bearings never acquire a wedge. The row reads
`N nearby · M on screen`; when every loaded camera is outside the view it adds
"None on screen — nearby cameras are outside the view" instead of looking
unloaded. `scripts/qa-alpr-journey.mjs` drives an Austin journey with ALPR off,
then on (cold and warm), and asserts time to cameras, marker stability while
still, and frame time within 10% of ALPR off. It also checks rendered frames:
marker pixels in the recorded clip against the ALPR-off pass, and, at the end
of every hold, the screen against the same scene after forcing frames, so a
state change without a render request fails (`--self-test-drop-renders` proves
it does). Asset URLs resolve relative to
the reusable layer, and the caller supplies overlay and map-stack services.
The ALPR row uses the same header layout as other data layers. **SHOW NEAREST**
frames and selects the nearest loaded camera; it is
@@ -452,8 +562,8 @@ Coverage follows a bounded neighborhood around the screen-center ground point,
with a radius of twice the camera-to-ground range (at least 1 km), rather than
extending to the horizon. Sky, dateline-crossing and wider-than-city views do not
query. Movement within an accepted or pending query reuses it. Markers retain
their Cesium identity and ground clamping while their geometry is unchanged;
refreshes preserve selection without replaying a click event.
their Cesium identity while their geometry is unchanged; refreshes preserve
selection without replaying a click event.
Data Centers, Dams and Submarine Cables release their built Cesium data sources
and record references when disabled. Parsed datasets remain cached for the layer
@@ -470,6 +580,8 @@ Each layer owns its state and visibility listener; destruction cancels source
reads and pending initialization. Malformed health responses retain prior health.
Existing catalog fallback, camera poses, frame pacing and coverage controls remain.
Traffic owns its rendered-surface observer only while enabled, removes it on disable/destroy, and caches surface identity without allocating on unchanged frames.
Traffic and bikeshare expose factories through `./layers/traffic` and
`./layers/bikeshare`. Traffic separates road requests, ingestion, animation,
flow matching, styling, viewport lifecycle and development timing. Each source
@@ -693,6 +805,18 @@ and result presentation. Existing search providers and camera handoff policy are
preserved. Replacing or closing a POI row cancels its pending expansion frame;
destruction releases listeners, pending searches and the orbit indicator.
Contacts and mapped-site search feedback filter subject-window installation matches by surface distance from the current subject position, within the stated 100 km radius; the retained tile-fetch square is only a loading window.
Search arrivals (location bar and voice) end above the rendered surface.
Precise places without a detailed outline used to frame a 250 m landmark shot
from a sea-level target, which left Camp Mabry about 1 m above the 171 m mesh
and a Denver coordinate underground. Search now resolves the application's
ground-floor elevation (bounded wait, same ellipsoidal datum) before framing and
warms the cell; after landing, `src/cameraGroundGuard.js` samples the rendered
surface under the target and the camera once tiles stream in and lifts the eye
to 120 m clearance, holding heading and pitch. Navigation ownership changes (Director, tracking, keyboard/UI controls and direct camera flights), layer/app teardown, and pointer/wheel gestures cancel the check and remove its listeners. `scripts/qa-context-arrivals.mjs` types Camp Mabry
and a Denver coordinate and requires at least 60 m clearance.
## Layer panel ownership
Layer rows, feed feedback, counts, focus-preserving chips and toggle listeners
@@ -807,10 +931,21 @@ errors.
## Places and CCTV request bounds
With a Google key configured, nearby and text search reject missing, blank,
non-numeric and out-of-range coordinates before the opt-in limiter and upstream
non-numeric and out-of-range coordinates before the per-IP limiter and upstream
request. Text search also requires a nonblank query. Keyless requests retain
their `configured: false` response.
The cost-bearing proxies are throttled per client IP without configuration:
`/api/realtime/token` and `/api/openai/hud-summary` share 30 requests per
minute per IP, `/api/google/nearby-places` and `/api/google/text-search` share
120 — the caps the Pinokio build already sets, so the packaged app is
unaffected. `GEV_RATELIMIT_OPENAI_PER_MIN` and `GEV_RATELIMIT_GOOGLE_PER_MIN` override
those; exactly `0` disables the limiter, while a value that cannot be read as a
number falls back to the default rather than to unlimited. Over-limit requests
receive a sanitized `429` with `Retry-After: 5` and never reach the provider.
The client key is the socket peer address only — a forwarded-for header is not
trusted, so a proxied deployment shares one bucket per upstream hop.
CCTV media waits at most 15 seconds for upstream response headers and returns
504 on timeout. Its timer stops when headers arrive, so live bodies can continue
streaming; a separate 30-second idle deadline then bounds the gap between
@@ -1121,8 +1256,34 @@ fetching, trailing-24-hour filtering and partial-success caching are unchanged.
## Installations and map-source guidance
- On an uncached Overpass failure, mapped installations keep their existing
30–240 second retry backoff. The top status and Contacts row explain the
- Keyless mapped installations use OpenFreeMap `landuse` military polygons,
merge touching parcels and tile fragments into one "Military area" marker;
tiles carry no site names. The marker uses an area-weighted centroid, moved
inside a footprint when necessary. Source-feature aliases retain ids across
pans (4096-entry cap); grouping admits at most 2048 fragments per view and
reports saturation if that cap is reached. Tile-buffer geometry is clipped to each core and
artificial clipping edges never become outlines. Fills, boundary polylines
and markers clamp to the visible globe or photoreal mesh. Wide views start
at z10, local views at z12, stepping down no further than z9 to fit the
16-tile view cap: OpenMapTiles carries `landuse=military` only from z9, so a
view too wide for 16 z9 tiles is a "Zoom in" state, never an empty area.
Camera changes debounce for 180 ms, with a final moveEnd check; identical
windows reuse the active load. Disabling or returning Contacts to passive mode
clears its anchor; passive refreshes cannot reclaim it. Background repaints
refresh context records without publishing a new selection.
Installation tiles decode only the `landuse` layer. Reloads keep the Cesium
entities of sites whose geometry is unchanged, so outlines and fills do not
blink. While Contacts has a subject, the layer loads a window around it
instead of the camera view (100 km half-width at mid latitudes, shrinking so
it fits 16 z9 tiles), moves it only after the subject travels 20 km, and
ignores camera `moveEnd`; clearing the subject returns to the viewport. The
row status names what was found ("3 mapped sites within 100 km of the
contact", "No mapped sites in view") and tile failures read "Map tiles
temporarily unavailable". Google Places
search/classification stays separate and unchanged. Configured Overpass can
still supply named sites; a not-configured miss switches to tiles once.
Retryable tile/configured-upstream failures retain the existing 30–240 second
backoff, extended when Retry-After requires it. The top status and Contacts row explain the
outage and scheduled countdown; an active retry says "Retrying mapped
sites" and successful recovery clears the previous error. Known upstream
rate limits, timeouts, and query failures are distinguished without exposing
@@ -1300,14 +1461,14 @@ This is the current runtime/source-of-truth snapshot for the project.
> dependency list rather than a fix for one layer, and the dependencies reach
> it by different routes:
> - **AIS vessels is its reachable producer.** `enable()`/`update()` both
> resolve as soon as the first `/api/ais-live` poll answers, so the manager's
> resolve as soon as the first `/api/vessels` poll answers, so the manager's
> lifecycle settles to `enabled` — but until the server-side socket delivers
> a position, `firstConnectPhase` stays `'loading'` and `getStats()` reports
> `loading: true`, `lastUpdate: null`, count 0, and an UNDEFINED status.
> Without the predicate that window prints an all-clear `0`.
> - **Mapped installations never reaches that window.**
> `militaryInstallations.enable()` is synchronous and the manager awaits
> `update()`, which owns the first Overpass fetch, so the lifecycle stays
> `update()`, which owns the first mapped-data fetch, so the lifecycle stays
> `enabling` for the whole fetch and the pre-existing `enabling` branch
> covers it. Confirmed live on :4272 across a held 17 s first fetch (34
> samples, `enabling` throughout, panel non-numeric) and across a failing
@@ -1704,7 +1865,7 @@ isCurrent })` gates BOTH halves of the map-stack switch, which is its only
> the share payload; Radio restores only its allowlisted filter and volume.
> - **AIS feed watchdog (2026-08-18):** feed liveness is judged by DATA, not
> socket state — AISStream can complete the handshake and then deliver
> nothing forever. `/api/ais-live` reports `live | stale | reconnecting |
> nothing forever. `/api/vessels` reports `live | stale | reconnecting |
down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> `silentForMs`, `reconnectAttempt` and `nextAttemptAt`. Silence is REPORTED
> at 120s and ACTED ON at 300s; recovery walks a 5s/15s/60s/300s ladder and
@@ -1849,11 +2010,12 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> registered for lifecycle and restoration but is not duplicated in Data Layers.
> Inside Cockpit, the focused summary card is titled Contact in both visible
> copy and its accessible control labels.
> The top-center Cockpit vision cycle shows the inherited map preset name
> (for example, `NOIR`) followed by CRT, NVG, FLIR, and NOIR. That inherited entry
> leaves the selected map preset unchanged inside Cockpit. NONE is not offered
> in the cycle; CRT, NVG, FLIR, and NOIR temporarily override that preset, while returning to it or exiting
> Cockpit restores the captured map style and its exact shader intensities.
> The top-center Cockpit vision cycle is the fixed, duplicate-free sequence
> Normal, CRT, NVG, FLIR, Anime, Noir, and Snow. Entry selects the map preset
> already in use, so a FLIR map opens on the existing FLIR item while
> Normal remains available exactly once. NONE is not offered. Cockpit choices
> temporarily override the map preset, while Exit Cockpit and Reset both restore
> the captured map style and its exact shader intensities.
> Selecting a Cockpit vision treatment with configurable parameters opens
> Cockpit Display and reveals those parameters through the existing right-side
> accordion; an inherited parameterless Normal preset does not force it open.
@@ -2095,7 +2257,7 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> then checks fresh memory, identical in-flight work, and fresh disk entries
> before invoking its local 90/min limiter. Cache and single-flight responses
> therefore do not spend quota; upstream-bound misses retain the existing
> limiter, mirror, stale, and sanitization behavior.
> limiter, configured-upstream, stale, and sanitization behavior. An unconfigured transport serves dated stale data or a non-retryable capability error.
> - **CCTV world-click focus:** clicking an in-world CCTV icon or ambient card
> activates it and routes the camera flight through the panel FOCUS policy.
> Aircraft/satellite tracking releases outside cockpit; cockpit retains the
@@ -2760,14 +2922,14 @@ its criteria cannot be silently ignored.
| Layer | Source | File | Proxy | Update Interval |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Live Flights ✈️ | OpenSky Network; bounded adsb.lol regional fallback | `src/data/flights.js` | `/api/opensky` (OAuth + fallback) | 30s |
| Military Flights 🎖️ | adsb.lol /v2/mil | `src/data/militaryFlights.js` | `/api/adsblol/mil` | 15s |
| Live AIS Vessels 🚢 | AISStream websocket | `src/data/aisLiveVessels.js` | `/api/ais-live` | 60s (+800ms visibility pass) |
| Mapped Installations ⌖ | OpenStreetMap mapped context; on-demand Google Maps Places supplement | `src/data/militaryInstallations.js` | `/api/military-installations`, `/api/google/text-search` | viewport-driven + user search; while unavailable, auto-retry 30 s → 240 s backoff |
| Live Flights ✈️ | OpenSky Network; bounded adsb.lol regional fallback | `src/data/flights.js` | `/api/flights` (OAuth + fallback) | 30s |
| Military Flights 🎖️ | adsb.lol /v2/mil | `src/data/militaryFlights.js` | `/api/military` | 15s |
| Live AIS Vessels 🚢 | AISStream websocket | `src/data/aisLiveVessels.js` | `/api/vessels` | 60s (+800ms visibility pass) |
| Mapped Installations ⌖ | OpenFreeMap military areas; optional configured Overpass names and Google Places search | `src/data/militaryInstallations.js` | OpenFreeMap tiles, `/api/military-installations`, `/api/google/text-search` | viewport-driven; transient errors back off, missing capability switches to tiles |
| Earthquakes | USGS | `src/data/earthquakes.js` | — | 60s |
| Satellites | CelesTrak | `src/data/satellites.js` | `/api/celestrak` | 120s |
| Space Missions (30d) | Launch Library 2 + CelesTrak | `src/data/rocketLaunches.js` | `/api/launches` + `/api/celestrak/active` | 5 min |
| Traffic | OSM Overpass (+ optional TomTom live flow) | `src/data/traffic.js` | `/api/overpass` + `/api/tomtom` | viewport-driven |
| Traffic | Selectable TomTom / OpenStreetMap / Hybrid roads; optional TomTom flow (BYOK) | `src/data/traffic.js` | browser-direct OpenFreeMap tiles; `/api/tomtom` for flow | viewport-driven; capped tile cache |
| CCTV | Austin + Caltrans (CA) + TfL London + Ontario 511 + Fintraffic (FI) + DriveBC (BC) + TxDOT (TX) + Estonia (Tallinn, Tarktee) + Live Traffic NSW + Open Calgary Open Data + Street View fallback | `src/data/cctv.js` | `/api/cctv` | 10s (active) |
| Radio | Radio Browser (public-domain station directory) | `src/data/radio.js` | `/api/radio/stations`, `/api/radio/click/:uuid` | 45 min directory refresh |
| Transit 🚌 | Operator GTFS-Realtime VehiclePositions (7 keyless regions, `src/data/transitFeeds.js`) | `src/layers/transit/` via `src/app/layers/transit.js` | `/api/transit` | 15s (poll + delayed playback) |
@@ -3376,10 +3538,11 @@ silently demoting every later lookup for the session.
- Aircraft cohort membership uses every locally loaded, selectable contact inside the 250 km window, including a plane hidden only by horizon culling or because its 3D model owns the visual. Counts and navigation therefore do not change with the current camera angle or billboard/model handoff.
- Nearby examples in the context panel are focus controls: they use their owning layer's existing selection/tracking path, then frame that contact. Static-installation distances use ellipsoidal surface distance so the count matches the ground-projected context disk.
- Context selection transfers camera ownership by subject type: selecting a civilian or military flight keeps that layer's moving follow camera, while selecting an AIS vessel or mapped installation first releases any prior aircraft tracker and performs only the source layer's one-time framing. The camera therefore remains user-controlled after non-aircraft selection instead of continuing to move with the previously selected plane.
- Selected AIS vessels use their layer-owned full-detail presentation model in the shared world-overlay host; mapped installations use the tracked-readout aesthetic. Both remain crisp above post-processing without duplicate selected labels; non-selected AIS cards retain their source-owned grid/visibility selection and are host-batched with other world cards.
- Selected AIS vessels use their layer-owned full-detail presentation model in the shared world-overlay host; mapped installations promote their own entry to a selected card. Both remain crisp above post-processing without duplicate selected labels; non-selected AIS cards retain their source-owned grid/visibility selection and are host-batched with other world cards.
- Space Mission ascent replay uses the compact rocket/thrust overlay only through orbit insertion. Once the replay enters its orbit phase, that vehicle glyph is replaced by a fixed-size cyan dot following the same orbit path and callout.
- While a subject is selected, an inner keyhole compass rotates against camera heading and up to three cyan shafted bearing arrows lock to its single faint tick-marked rim, with their labels inset just inside the circle. Labels explicitly separate the geographic bearing (`BRG`) from the contact's reported course (`CRS`) so the pointer direction is not confused with aircraft heading. They point toward the nearest observed/mapped examples; each cohort displays up to ten examples while retaining the complete locally loaded in-range cohort for navigation, with three visible at a time and a ten-second page rotation shared by the panel and arrows. This distinct neutral-context color avoids implying that all context indicators are military-flight symbols. `PREVIOUS`, `FOCUS`, and `NEXT` controls navigate selection history or the next nearby cohort example through the existing tracker. NEXT uses a cycle-scoped visited set and starts a deterministic new walk after exhausting the current candidates instead of re-admitting the nearest visited contact.
- Installations are viewport-bounded OSM map features (`military=airfield|naval_base|range|barracks|base` and `landuse=military`), capped to a 10° non-dateline request and 700 upstream features. The proxy caches five minutes and serves a one-hour stale fallback. Empty, stale, unavailable, and zoom-too-wide states remain visibly distinct.
- With a selected subject, Contacts hands the installation layer the subject position every refresh. The layer loads a window around the subject (see Installations and map-source guidance), because a follow or Cockpit camera never fires `moveEnd` and often looks at the horizon: a first load from a globe view used to leave the row at "?" for the whole track. The row and the Cockpit scope read `WITHIN 100 KM` instead of `CURRENT VIEWPORT ONLY`, and an empty window reads "none mapped within 100 km; not a complete 250 km survey". `scripts/qa-context-arrivals.mjs` flies synthetic aircraft over Camp Mabry and Fort Cavazos from a wide start, presses CONTACTS, Cockpit and SEARCH NEARBY SITES, and records a clip.
- The selected-only visual is one static, unfilled blue circle. It marks the 250 km proximity context window only; it is not coverage or a radar/weapon envelope. Missing broadcasts and unmapped sites are explicitly not evidence of absence.
- The right rail's collapsed Display, CCTV, and Context controls use the same compact sizing language as the left rail's collapsed Data Layers and Scenes controls. These compact-state widths do not constrain expanded panel or child-content widths.
@@ -3491,16 +3654,100 @@ silently demoting every later lookup for the session.
A selected mission renders its orbit as four repeating tactical sectors, each containing one prominent cyan dot followed by one hundred thin translucent dashes. The bright dots act as orbit anchors while the subdued dash field remains depth-tested against the globe and is shown only for the selected mission.
Close selected-pad views add one static 500 m-radius cyan launch-zone ring with a low-opacity translucent fill over the sampled photoreal launch-site surface. The single scene primitive is created only for the visible selected site and is otherwise dormant. It appears during Focus, sufficiently close manual zoom, and the replay countdown, but is suppressed above 120 km camera altitude, beyond 180 km direct camera-to-pad range, for unselected missions, and whenever Space Missions is inactive. Focus establishes a launch-site-centered camera transform once; subsequent manual heading and pitch changes remain centered on that site without an automated per-frame correction. Surface mission markers and labels use an additional conservative globe-limb margin before the exact ellipsoid occluder boundary, preventing near-horizon visibility from alternating between frames.
- **AIS vessels**: chevron symbology (naval cyan base, type tints), world-space headings, MMSI-keyed reconciliation (selection survives refreshes; pinned 3 refreshes with STALE marker when absent), detection-overlay integration (`type: 'SEA'`), contextStore registration for voice Q&A. Empty-space clicks, id-less photorealistic-tile picks, and Escape dismiss the vessel card/HUD/context and clear its trail; picks owned by another layer (including `gev-trail:*`) and raw vessel-record picks without a live MMSI key are no-ops for vessel selection. Click and key handlers detach while the layer is disabled and reinstall on enable. Selecting another vessel replaces the selection and trail, and reconciliation clears a trail if its owning vessel is evicted.
- **Track trails**: server accumulates per-MMSI ring buffers (`/api/ais-live/track?mmsi=`, Float32+Uint32, 64 samples, 30s/25m thinning); aircraft backfill proxies `/api/opensky-track` (OAuth, own credit bucket) and `/api/adsblol/trace` (tar1090 readsb, ~24h history, ODbL — credit adsb.lol).
- **Track trails**: server accumulates per-MMSI ring buffers (`/api/vessels/track?mmsi=`, Float32+Uint32, 64 samples, 30s/25m thinning); aircraft backfill proxies `/api/flights/track` (OAuth, own credit bucket) and `/api/military/track` (tar1090 readsb, ~24h history, ODbL — credit adsb.lol).
- Shared `src/data/pickRegistry.js` stops the two flight layers' click handlers from fighting over the camera.
### Overpass proxy mirror rotation (September 2026)
### Optional Overpass configuration
- `/api/overpass` fans out across four public mirrors. `overpassPayloadIsData()` governs cache reads, writes, and stale fallback: only a 2xx that is neither rate-limited nor a body-level runtime error qualifies. Previously stored refusals are ignored on both fresh and stale reads, so upgrading does not require manually clearing the disk cache.
- HTTP refusals such as 406 now rotate alongside the existing network, rate-limit, and runtime-error cases. A refusal from one mirror no longer prevents reaching healthy alternatives or persists under the seven-day road/month-long boundary cache TTLs. Concurrent identical queries share one mirror sequence; if it fails, both the initiating and joined callers can use the same last-good data.
- A refusal every mirror agrees on is still reported with the first mirror's status and body, so a genuinely malformed query says what upstream said — but only after every mirror has had the chance to answer it. `fetchOverpassPayload` takes injectable endpoints and fetch so the rotation is tested without a live mirror (`src/overpassProxy.test.mjs`).
- Every mirror is asked with a User-Agent that names the application, its version and the project address (`OVERPASS_USER_AGENT` in `server/providers/overpass/constants.js`), which is what the OSM API usage policy asks for. Mirrors may refuse a client they cannot identify; such a refusal is never treated as data and costs the fan-out that mirror, so the header is what keeps the list at full strength.
- When the fan-out has nothing left to offer, the traffic row says which upstream declined and how: `Overpass rate-limited`, `Overpass timed out`, or `Overpass refused the road query (HTTP 406)`; the proxy's own 502 (every mirror unreachable) and 503 (local limiter busy) read `Overpass mirrors unreachable` and `Overpass temporarily unavailable`. Street Traffic draws on two upstreams — OpenStreetMap for geometry, TomTom for flow — so a row that only said "road data unavailable" sent readers to check a TomTom key that was never the problem (#661). `roadRequestError` in `src/layers/traffic/source.js` owns the mapping and tags the error with its status. A refusal whose code is unreadable — the layer takes its source by injection, so an adapter may not supply one — reports `Overpass temporarily unavailable` rather than printing `HTTP undefined`, and anything the layer did not classify at all (a dropped connection, a malformed snapshot) still reads `Road data temporarily unavailable`, because a platform exception is not a sentence to put on a panel.
Public Overpass instances are not used by default. `OVERPASS_UPSTREAMS` is a
server-only comma-separated list of HTTP(S) instances the operator runs or pays
for, resolved lazily after env loading. It replaces the empty default list;
there is no built-in fallback. Local/private instances are allowed. The stable
`gods-eye-view/…` User-Agent is unchanged. URLs never appear in response headers
or upstream errors, including old cached endpoint metadata.
The shared transport guards `/api/overpass` and `/api/military-installations`.
With no configured upstream, each route serves existing cache data (including
expired entries, marked stale with the original cache/retrieval date) without
refresh. An annotation miss returns HTTP 503, `OVERPASS_NOT_CONFIGURED`,
`retryable:false`; installations return that capability as HTTP 200 and select
the working vector-tile source without a failed-network console entry.
`GET /api/overpass/status` answers `{configured}`; the browser asks once per
page and, when nothing is configured, returns the unavailable result for
annotation and location-feature queries without sending them (so no 503 is
logged). An older server without the probe is queried as before. The probe
times out after 3 s; a failed or timed-out probe is retried after 30 s, and
queries in between go to the server. Each query stops waiting on the probe
when its own signal aborts.
Configured endpoints have per-instance cooldowns for 406/429, honoring
Retry-After; absent delays back off from 30 seconds to five minutes. Empty
`elements` is a valid answer. Existing byte, query, concurrency and cache caps
remain. Consumer capability misses never schedule automatic retries.
Operator-configured Overpass area and focus-footprint queries use body-level
`out geom` (with `center` for focus), preserving relation member outlines.
Member geometry is printed only inside a box around the point (6 km for
neighbourhoods, 3 km for street areas, 1.5 km for focus footprints); a
relation cut by the box does not close and falls back to its pin.
Street Traffic and ALPR never use Overpass. Installations switch once to vector
tiles after a capability miss. Annotation and location-feature sources retain
a distinct unavailable result and stop querying for that source lifetime.
`scripts/qa-overpass-offload.mjs <dev-url>` checks Austin road dots, ALPR,
Camp Mabry markers/outlines, at most 12 Fort Cavazos installation markers,
an area annotation with zero console errors (and zero `/api/overpass` queries
when unconfigured), and zero browser requests to external Overpass/Nominatim hosts. It waits for
visible photoreal tilesets, dismisses first launch and measures at least 150
street-view dots within -3/+25 m of sampled mesh height, for both keyed and
keyless OpenFreeMap roads (an isolated page overrides only TomTom key availability).
Keyed Austin defaults to Hybrid (`roadSource: TomTom + OpenStreetMap`) with positive flow coverage.
It records milliseconds from enabling Street Traffic to first rendered dots at
2 km on a fresh page for each mode, plus warmed street-view timings and orbit screenshots;
`src/overpassOffload.test.mjs` separately proves server-handler zero egress.
All QA modes record per-view request counts, decoded response-body bytes,
transferred bytes and browser cache hits for OpenFreeMap TileJSON/tiles, ALPR
extract metadata/tiles and local `/api/tomtom/*` responses. `--tour` measures
Austin, London, Dubai, San Diego, Tokyo and São Paulo with all three layers on,
at 450 m and -35° pitch (São Paulo: 1250 m), then revisits Austin to measure
cache reuse. The tour disables camera collision adjustment and asserts the actual
latitude, longitude, altitude and pitch so remote terrain cannot shift a revisit.
`--compare` captures TomTom, OpenStreetMap and Hybrid at identical close tilted
photoreal views: Twin Peaks, Lombard Street, Loop 360, Dubai and Shibuya. The
mode order rotates per view so each mode meets cold caches somewhere. It records
first-dot latency (null where TomTom has no roads), projected dots,
surface-aligned dots (-3/+25 m), floaters, sinkers and missing samples, saving
`qa-shots/compare-<view>-<mode>.png` and `overpass-offload-compare-result.json`.
The comparison also clicks a real row chip and verifies its stored preference.
`--pan` runs 20 settled Austin moves: ten 150–300 m steps, five 1–2 km steps,
and five exact returns. It records OFM requests/bytes, flow tile requests, all
local API calls and dots per move, rejects repeat XYZ downloads and any new
OFM request on a return, and checks small-pan tiles against admitted bounds.
Its TomTom session ceiling is 32 browser flow requests (0.53% of the default
6,000/day upstream budget); cached server responses also count toward this
conservative session bound. `--profile` captures cold/warm Austin at 450 m,
-35 degrees with settled mesh. Development `roads:*` phase measures include
status, TileJSON, per-tile fetch/decode, parsing and surface sample counts/CPU
time, slice maxima and cache hits; no obsolete zero-valued parse-time sampling
aggregate is emitted. The six-city tour records first-dot and on-mesh counts
and asserts the 4 s cold / 2.5 s cached-return deadlines.
Reports go to `qa-shots/overpass-offload-result.json` and separate
acceptance/tour/pan/profile result files, with a console summary table. These are browser
session measurements; TomTom upstream usage still depends on the server cache
and budget. MB uses decimal bytes; transfer totals include response headers.
Configured Overpass responses still use `overpassPayloadIsData()` for cache
admission and stale fallback: refusals and body-level errors are never data.
Identical requests share a configured endpoint sequence and last-good fallback;
all configured endpoints retain the stable application User-Agent. HTTP refusals
rotate only among those configured endpoints, honoring their cooldowns.
Street Traffic reports OpenFreeMap road failures separately from TomTom flow:
`OpenFreeMap tiles rate-limited`, `OpenFreeMap tiles timed out`, or
`OpenFreeMap tiles unavailable (HTTP 406)`. `RoadRequestError` retains a numeric
`status` (null when absent); malformed snapshots and network failures read
`OpenFreeMap tiles unavailable`, never `HTTP undefined`. Detailed-pass failures
retain the major roads and display the same upstream reason with reduced coverage.
The TomTom /api proxy still reports key unavailability (503), daily budget (429),
and unreachable upstream (502/504) separately from road geometry.
### Share-link v2 layer state (August 2026)
@@ -3619,13 +3866,14 @@ silently demoting every later lookup for the session.
first, activates the Contacts sub-view, and returns the settled 250 km window.
Its `aircraft` count is the exact civilian-plus-military total when both feeds
can answer, or `unknown` when either component is unavailable.
- Cockpit's top vision switch cycles five rendered looks: the inherited map
style, CRT, NVG, FLIR, and Noir. There is no empty `NONE` entry.
- Cockpit's top vision switch cycles all seven map looks: Normal, CRT, NVG,
FLIR, Anime, Noir, and Snow. Entry selects the matching map style without adding
a duplicate carousel item. There is no empty `NONE` entry.
### Live AIS Vessels (June 2026)
- Server-side `ws` websocket to `wss://stream.aisstream.io/v0/stream` maintained by Vite middleware; `AISSTREAM_API_KEY` never reaches the browser (AISStream has no browser CORS). The `ws` package is used rather than Node's built-in WebSocket specifically because only it can hard-abort a wedged socket (see the watchdog note in the delta block at the top).
- Browser polls same-origin `/api/ais-live` cache every 60s.
- Browser polls same-origin `/api/vessels` cache every 60s.
- The first enable in a session starts one 30-second client grace timer. Until
an accepted vessel position arrives, `live`/`open`/`connecting` transport reports
`LOADING`; the timer is not restarted by the 60-second poll. Expiry or a
@@ -3749,7 +3997,36 @@ are omitted rather than framing the wrong part of the globe.
- Runtime entry: `src/main.js` calls `initAnnotations({ viewer, tileset })`, exposes `window.__gevAnnotations`, and passes the engine into the voice action runner.
- Engine contract: `src/annotations/annotationEngine.js` owns annotation state, TTL/fade lifecycle, concurrent anchor resolution, duplicate detection keyed on geometry, cancellation on clear/newer generations, and a hard cap of 120 live marks. Deferred outline upgrades drain FIFO at concurrency 2; queued work retains the owning abort controller and is discarded on a generation change before it can fetch.
- Resolver contract: `src/annotations/annotationResolver.js` converts names/coords/screen pixels into world anchors and optional geometry. The resolver is type-aware: Google Geocode/Places gives a centroid + scope, OSM/Overpass supplies admin/place/footprint/street/enclosing-area geometry, route requests use `/api/route`, and ambiguous/far results are rejected or recovered near the current view instead of drawing misleading blobs. Only explicitly country/state/county-scoped asks bypass near-view recovery and proximity gating; state scope requires a leading `state of …`/`the state of …` phrase, while bare names, proper names ending in “State,” and administrative geocode result types alone remain guarded. Overpass throttles remain distinct from normal transients: `Retry-After` is honored for one retry, and a repeated throttle ends only that mark's outline upgrade.
- Resolver contract: `src/annotations/annotationResolver.js` converts names/coords/screen pixels into world anchors and optional geometry. The resolver is type-aware: Google Geocode/Places gives a centroid + scope, operator-configured OSM/Overpass supplies admin/place/footprint/street/enclosing-area geometry, route requests use `/api/route`, and ambiguous/far results are rejected or recovered near the current view instead of drawing misleading blobs. Only explicitly country/state/county-scoped asks bypass near-view recovery and proximity gating; state scope requires a leading `state of …`/`the state of …` phrase, while bare names, proper names ending in “State,” and administrative geocode result types alone remain guarded. Overpass throttles remain distinct from normal transients: `Retry-After` is honored for one retry, and a repeated throttle ends only that mark's outline upgrade.
- With no detailed-feature capability, annotations retain their pins and say
"Detailed outline unavailable"; location focus reports the same result without
retrying. Missing capability never synthesizes an outline from a radius.
Manual geometry, bundled neighborhoods, Natural Earth regions and bundled
state/province/county outlines remain.
- County queries rank eligible US counties and international admin-1 units together, retaining exact aliases and country/state qualifiers.
- Bundled administrative outlines (`src/data/adminBoundaries.js`): states and
provinces worldwide (Natural Earth admin-1,
`src/data/local_data/natural_earth/states_provinces.json`) and US counties
and county equivalents (US Census Bureau,
`src/data/local_data/us_census_counties/counties.json`), both public domain
and rebuilt by `scripts/build-admin-packs.mjs`. An area ask whose words name
a unit ("Texas", "TX", "State of Texas", "Bavaria", "Travis County, Texas",
"Orleans Parish") is drawn from the pack before any geocode: no network
request, no proximity gate, no outline pending. Same-named units go to the
state/country qualifier, then the one under or nearest the camera
("Washington County"); every qualifier must place the unit ("Travis County
Courthouse, Texas" is not the county). A bare name answers alone only for a
prominent unit (a US state, a Canadian province, Bavaria) or a city-state
(Berlin, Vienna), and never when it is also a country or a city
("Georgia", "New York", "Santa Fe", "Victoria") or a generic word
("North"); those, and smaller units ("Kent", "Long Island"), are left to
the geocoder. When the geocoder types the place as a state or county, the
unit carrying the name and containing the geocoded point is drawn from the
pack before any Overpass query (a US county first, so "New York County" is
Manhattan). Outlines keep every part and hole (`anno.polygons`: Hawaii's islands,
Berlin inside Brandenburg). Each pack loads on the first ask that needs it,
never at start-up; the analyst region lookup uses the same packs. Gate:
`scripts/qa-admin-outlines.mjs`.
- Renderer contract: `src/annotations/hybridAnnotationRenderer.js` routes draped `area`/`route` geometry to world-space Cesium rendering and reticles/pins/arrows/callouts to the screen-space SVG renderer. Area labels are screen-space callouts so all captions share one visual language; progressive outline upgrades convert the existing screen group in place when the anchor snaps to the resolved centroid.
- Tooling: voice has `annotate_map` and `clear_annotations` tools. Annotations accumulate and persist by default; clearing is explicit only. Partial failures, approximate synthesized zones, and route fallbacks are returned as structured tool results so the voice layer can be honest.
- Console/dev API: `window.__gevAnnotations.tour()`, `.demo()`, `.annotate()`, `.clear()`, `.count()`, and `.list()` are the deterministic no-mic test surface.
@@ -3869,13 +4146,13 @@ are omitted rather than framing the wrong part of the globe.
- Proxy error payloads are sanitized (no internal error details returned to clients).
- That holds for the OpenAI and CCTV media paths too: `/api/openai/hud-summary` never relays OpenAI's own `error.message`, `/api/realtime/token` passes successful ephemeral-token responses through but answers with a fixed error when minting fails or upstream rejects the request, and a failed CCTV media fetch stores a fixed camera health `message` — `GET /api/cctv/health` serializes that field and the CCTV panel renders it as a status label, so it is a client surface as much as the response body is.
- `OPENAI_API_KEY` is server-side only; the browser receives ephemeral Realtime client secrets from `/api/realtime/token`.
- `AISSTREAM_API_KEY` is server-side only; the browser reads the same-origin `/api/ais-live` cache.
- `AISSTREAM_API_KEY` is server-side only; the browser reads the same-origin `/api/vessels` cache.
- `/api/google/nearby-places` keeps the Google key out of Places requests issued for voice scene context.
- `/api/google/text-search` keeps the Google key server-side for view-biased Places recovery used by annotation resolution.
- `/api/overpass` is bounded by body/response caps, per-client/global rate limits, concurrency limits, mirror fallback, in-flight dedupe, cache bounds, and static validation that every selector is spatially bounded.
- `/api/military-installations` uses an independent limiter with the same 90-per-client/300-global one-minute bounds, so viewport installation refreshes never consume `/api/overpass` annotation/traffic capacity.
- `/api/overpass` is bounded by body/response caps, per-client/global rate limits, concurrency limits, operator-configured upstreams, in-flight dedupe, cache bounds, and static validation that every selector is spatially bounded.
- `/api/military-installations` uses an independent limiter with the same 90-per-client/300-global one-minute bounds, so viewport installation refreshes never consume `/api/overpass` annotation capacity.
- `/api/route` proxies bounded OSRM route requests for annotation routes, with profile allowlisting, distance caps, response caps, caching, and sanitized "no route found" errors.
- Track endpoints: `/api/ais-live/track?mmsi=` (server-accumulated ring buffers; sub-route handled before the rows snapshot), `/api/opensky-track?icao24=` (OAuth, 60s cache, sanitized errors, independent OpenSky credit bucket), `/api/adsblol/trace?hex=` (60s cache, 5MB cap, ODbL attribution required in UI).
- Track endpoints: `/api/vessels/track?mmsi=` (server-accumulated ring buffers; sub-route handled before the rows snapshot), `/api/flights/track?icao24=` (OAuth, 60s cache, sanitized errors, independent OpenSky credit bucket), `/api/military/track?hex=` (60s cache, 5MB cap, ODbL attribution required in UI).
- Realtime debug logs redact API keys, bearer tokens, client secrets, and image data URLs before writing to disk; request bodies are size-capped.
- `/api/realtime/debug-log` enforces an always-on 120-per-client/400-global one-minute limiter (not the opt-in `GEV_RATELIMIT_OPENAI_PER_MIN` bucket the cost-bearing OpenAI routes share), appends asynchronously through a serialized queue, and rotates `realtime-conversations.jsonl` at 32 MB keeping one prior generation, so the sink is bounded at twice that regardless of session length. A malformed record answers 400 and a failed write 500, both with a fixed message.
@@ -3958,12 +4235,12 @@ are omitted rather than framing the wrong part of the globe.
be enabled, and a civilian or military aircraft must be tracked. The visible
Cockpit actions and the `C` shortcut use the same gate, so ordinary standalone
flight-layer selection cannot enter a context-dependent cockpit.
- **Aircraft cockpit view:** selecting a commercial or military aircraft reveals a `COCKPIT` action (`C`). Cockpit mode temporarily releases Cesium's orbit-follow transform and drives a first-person camera from the tracked aircraft's existing smoothed display position and course. Its concave helmet-visor HUD shows callsign, UTC time, coordinates, curved roll/pitch guides, and a seven-division heading tape. Ambient commercial and military AIR contacts use a Cockpit near/far band selected by the shared Display 3D mode: Proximity admits at 150 km and retains to 185 km; All admits at 400 km and retains to 450 km. The shared 3D toggle now applies in Cockpit: Off keeps in-range contacts as rotating 2D aircraft silhouettes, while On lets a ready admitted glTF take over without a drawing gap. The Cockpit model cap remains 60 and can only lower the map budget; in-range contacts that are capped or still loading remain 2D silhouettes instead of degrading to out-of-range dots. Contacts outside the selected band use small rotation-free cyan-white pips for civilian aircraft and amber pips for military. The pilot's own airframe is not drawn in first person, and exiting clears the Cockpit band and restores normal map silhouettes/models. The normal left accordion remains available for Layers and Scenes and keeps the same 26vh HUD-aligned anchor used in map mode, independent of whether the bottom-left Contact card is expanded or collapsed; the right-rail CCTV and Context chooser are hidden to avoid duplicating or overlapping the cockpit presentation. When the intelligence HUD is enabled, its classification, scene summary, collection/orbit metadata, coordinates, and imaging-status text remain visible as reduced peripheral cockpit telemetry; the optical center and flight instruments stay clear, and turning the HUD off still hides it. Ground speed, exact heading, and rendered altitude form one compact lower-center instrument cluster, keeping the horizon and peripheral view open. A second live altitude tape hugs the inside-right visor rim: its rail and nine moving ticks are derived from the same responsive keyhole radius, remain 20 px inside the circular edge even on wide displays, fade deeply at both vertical ends, and move continuously behind a fixed current-altitude pointer; its interval tightens automatically near the surface and it is suppressed on narrow screens. Cockpit mode also has a weather-backed transparent volumetric-cloud pass derived from the supplied FBM/domain-warped R&D shader. It renders at no more than 520×320, uses 24 ray steps/three FBM octaves at 12 FPS, is clipped to the visor, fails clear when Open-Meteo is unavailable, and stops its animation completely on cockpit exit. It does not restore the prior CPU weather canvases, precipitation, scene fog, or any map-mode effect. When Contacts is active for the tracked aircraft, the compact `CONTACT` rail adds the 250 km subject window, four cohort counts, nearest observed/mapped example with relative bearing and distance, freshness, explicit uncertainty, and Previous and Next controls plus its own collapse control. The mirrored right rail is a three-page **cockpit briefing carousel**: source-backed live signals, location-matched regional headlines, and local place/current-weather context from OpenStreetMap Nominatim and Open-Meteo. It is manual-first: Previous, Next, and direct page controls are always available, and the visible `CYCLE OFF` / `CYCLE ON` control starts or stops the nine-second page cycle. The cycle pauses on hover or keyboard focus and stops while collapsed, hidden, or outside cockpit mode; live signal data continues refreshing either way. Empty news matches and unavailable news use compact text states rather than reserving an empty media frame; partial local data remains explicit. Article links open their original publisher, and no headline is treated as verified risk intelligence. On desktop both cockpit rails use the same width and share a bottom-aligned safe baseline in opposite corners above the peripheral MGRS/GSD/time telemetry, leaving both that text and the lower-center instrument cluster readable. They collapse independently to slim tabs without stopping live data updates. Narrow screens use separated top/bottom fallbacks. Empty-space globe clicks are inert while cockpit owns the camera; `C`, `Escape`, or `EXIT COCKPIT` explicitly exits and restores the same tracked entity and standard follow camera. Unknown feeds remain unknown, selection loss still exits safely without inventing a replacement track, and the cockpit never presents the summary as threat scoring or an all-clear. This is a desktop first-person presentation, not a WebXR session.
- **Aircraft cockpit view:** selecting a commercial or military aircraft reveals a `COCKPIT` action (`C`). Cockpit mode temporarily releases Cesium's orbit-follow transform and drives a first-person camera from the tracked aircraft's existing smoothed display position and course. Its concave helmet-visor HUD shows callsign, UTC time, coordinates, curved roll/pitch guides, and a seven-division heading tape. Ambient commercial and military AIR contacts use a Cockpit near/far band selected by the shared Display 3D mode: Proximity admits at 150 km and retains to 185 km; All admits at 400 km and retains to 450 km. The shared 3D toggle now applies in Cockpit: Off keeps in-range contacts as rotating 2D aircraft silhouettes, while On lets a ready admitted glTF take over without a drawing gap. The Cockpit model cap remains 60 and can only lower the map budget; in-range contacts that are capped or still loading remain 2D silhouettes instead of degrading to out-of-range dots. Contacts outside the selected band use small rotation-free cyan-white pips for civilian aircraft and amber pips for military. The pilot's own airframe is not drawn in first person, and exiting clears the Cockpit band and restores normal map silhouettes/models. The normal left accordion remains available for Layers and Scenes and keeps the same 26vh HUD-aligned anchor used in map mode, independent of whether the bottom-left Contact card is expanded or collapsed; the right-rail CCTV and Context chooser are hidden to avoid duplicating or overlapping the cockpit presentation. When the intelligence HUD is enabled, its classification, scene summary, collection/orbit metadata, coordinates, and imaging-status text remain visible as reduced peripheral cockpit telemetry; the optical center and flight instruments stay clear, and turning the HUD off still hides it. Ground speed, exact heading, and rendered altitude form one compact lower-center instrument cluster, keeping the horizon and peripheral view open. A second live altitude tape hugs the inside-right visor rim: its rail and nine moving ticks are derived from the same responsive keyhole radius, remain 20 px inside the circular edge even on wide displays, fade deeply at both vertical ends, and move continuously behind a fixed current-altitude pointer; its interval tightens automatically near the surface and it is suppressed on narrow screens. Cockpit mode also has a weather-backed transparent volumetric-cloud pass derived from the supplied FBM/domain-warped R&D shader. It renders at no more than 520×320, uses 24 ray steps/three FBM octaves at 12 FPS, is clipped to the visor, fails clear when Open-Meteo is unavailable, and stops its animation completely on cockpit exit. It does not restore the prior CPU weather canvases, precipitation, scene fog, or any map-mode effect. When Contacts is active for the tracked aircraft, the compact `CONTACT` rail adds the 250 km subject window, four cohort counts, nearest observed/mapped example with relative bearing and distance, freshness, explicit uncertainty, and Previous and Next controls plus its own collapse control. The mirrored right rail is a three-page **cockpit briefing carousel**: source-backed live signals, location-matched regional headlines, and physical-region/current-weather context from bundled Natural Earth polygons and Open-Meteo. It is manual-first: Previous, Next, and direct page controls are always available, and the visible `CYCLE OFF` / `CYCLE ON` control starts or stops the nine-second page cycle. The cycle pauses on hover or keyboard focus and stops while collapsed, hidden, or outside cockpit mode; live signal data continues refreshing either way. Empty news matches and unavailable news use compact text states rather than reserving an empty media frame; partial local data remains explicit. Article links open their original publisher, and no headline is treated as verified risk intelligence. On desktop both cockpit rails use the same width and share a bottom-aligned safe baseline in opposite corners above the peripheral MGRS/GSD/time telemetry, leaving both that text and the lower-center instrument cluster readable. They collapse independently to slim tabs without stopping live data updates. Narrow screens use separated top/bottom fallbacks. Empty-space globe clicks are inert while cockpit owns the camera; `C`, `Escape`, or `EXIT COCKPIT` explicitly exits and restores the same tracked entity and standard follow camera. Unknown feeds remain unknown, selection loss still exits safely without inventing a replacement track, and the cockpit never presents the summary as threat scoring or an all-clear. This is a desktop first-person presentation, not a WebXR session.
- **Cockpit left-panel clearance:** the Cockpit Contact card and peripheral HUD participate in the adaptive left accordion's live obstacle measurements, including live viewport-height changes. Expanding Layers or Scenes keeps the active panel in the available upper-left corridor with internal scrolling; it does not cover the Contact card, lower Cockpit controls, or Cesium credit line. Outside Cockpit the hidden card does not alter the normal corridor.
- **Cockpit Context scope:** the 250 km radius applies to the air/sea proximity cohorts. Installation counts come only from the currently loaded viewport and are labeled `CURRENT VIEWPORT ONLY` in the cockpit as well as the normal Context panel; neither surface presents them as a complete 250 km installation survey.
- **Cockpit camera anchor:** first-person mode does not write feed-boundary corrections directly into the camera. A cockpit-only inertial anchor advances from the selected aircraft's displayed course and speed, then converges toward the authoritative delayed track with correction capped below forward motion. The displayed kinematics are derived from the same consecutive fix segment as the rendered position, with raw feed speed/course used only as fallback; a transient zero/missing feed speed therefore cannot freeze a visibly moving aircraft after layer enable or a map/cockpit handoff. Rendered altitude continues to come from that interpolated track position. Late ADS-B fixes and short render stalls can remove drift without accelerating or reversing the view. Camera placement runs before scene update/culling at a bounded 20 Hz so a moving cockpit does not force Photoreal 3D Tiles to retraverse on every display frame; textual instruments update at 10 Hz and context/layout work at 4 Hz. Every far Cockpit contact pip shares one stable Cesium texture-atlas entry and skips unused screen-projected course calculations, while only in-range 2D aircraft silhouettes pay the screen-projected rotation cost; ambient glTF collections are hidden/retained rather than synchronously destroyed at cockpit entry, and context rails lay out only on explicit content/state changes and viewport resize. The deliberate 15/30-second layer interpolation delays and per-Cesium-frame position caches remain unchanged.
- **Cockpit route, vision, and view controls:** visible on-screen `COCKPIT`, `RESET`, and `EXIT COCKPIT` controls replace reliance on the `C` shortcut. RESET uses the same canonical globe route as the map and voice actions, exits Cockpit, and releases its camera ownership rather than exposing the hidden map-style top action. When the tracked commercial flight has a plausible ADSBDB route, the top of the right briefing rail shows a compact `FROM → TO` airport strip and the visor shows a centered estimated-destination chevron with its relative bearing; absent or implausible route data hides the strip and cue rather than guessing. The cockpit-local vision control is an interactive `PREV / CURRENT / NEXT` carousel over the inherited map preset, `CRT`, `NVG`, `FLIR`, and `NOIR`; its previous/next actions wrap, and activating the current value advances to the next style. The inherited entry is named directly, such as `NOIR`, and retains that map shader. There is no empty `NONE` entry. CRT, NVG, FLIR, and NOIR temporarily activate the existing Cesium post-process stages, while returning to the inherited entry or exiting Cockpit restores the pre-entry visual style. The regional-news page uses a free Google News RSS locality query first, with the existing GDELT query retained only as a fail-soft fallback; linked headlines remain reporting, not verified incidents or risk intelligence.
- **Cockpit weather status:** the earlier multi-canvas atmospheric compositor remains fail-closed and is not attached to the live viewer. Cockpit clouds are a separate transparent WebGL pass with a capped 520×320 framebuffer, 24 ray steps, three FBM octaves, and a 12 FPS ceiling. It defaults off and starts only when local storage explicitly contains the persisted `WX ON` opt-in (`'1'`). When opted in, observations refresh after five minutes or 25 km of aircraft movement, fail transparent when unavailable or clear, and stop on exit or disable. `WX OFF` governs atmospheric rendering only: the briefing still fetches source-backed Nominatim, headline, and Open-Meteo local-information data, aborting and replacing any in-flight request when the selected aircraft changes. No weather effect runs in map mode and no synthetic fallback is shown.
- **Cockpit route, vision, and view controls:** visible on-screen `COCKPIT`, `RESET`, and `EXIT COCKPIT` controls replace reliance on the `C` shortcut. RESET uses the same canonical globe route as the map and voice actions, exits Cockpit, and releases its camera ownership rather than exposing the hidden map-style top action. When the tracked commercial flight has a plausible ADSBDB route, the top of the right briefing rail shows a compact `FROM → TO` airport strip and the visor shows a centered estimated-destination chevron with its relative bearing; absent or implausible route data hides the strip and cue rather than guessing. The cockpit-local vision control is an interactive `PREV / CURRENT / NEXT` carousel over `NORMAL`, `CRT`, `NVG`, `FLIR`, `ANIME`, `NOIR`, and `SNOW`; its previous/next actions wrap, and activating the current value advances to the next style. Cockpit enters on the active map preset, so a Cyber FLIR map opens Cockpit on FLIR while Normal remains a distinct option instead of producing a second FLIR entry. Cockpit choices temporarily activate the existing Cesium post-process stages, while exiting Cockpit through either EXIT or RESET restores the pre-entry map style. The regional-news page uses a free Google News RSS locality query first, with the existing GDELT query retained only as a fail-soft fallback; linked headlines remain reporting, not verified incidents or risk intelligence.
- **Cockpit weather status:** the earlier multi-canvas atmospheric compositor remains fail-closed and is not attached to the live viewer. Cockpit clouds are a separate transparent WebGL pass with a capped 520×320 framebuffer, 24 ray steps, three FBM octaves, and a 12 FPS ceiling. It defaults off and starts only when local storage explicitly contains the persisted `WX ON` opt-in (`'1'`). When opted in, observations refresh after five minutes or 25 km of aircraft movement, fail transparent when unavailable or clear, and stop on exit or disable. `WX OFF` governs atmospheric rendering only: the briefing still fetches source-backed Natural Earth region, headline, and Open-Meteo local-information data, aborting and replacing any in-flight request when the selected aircraft changes. No weather effect runs in map mode and no synthetic fallback is shown.
- **Cockpit trail visibility:** entering cockpit hides the selected aircraft's trail body and head so they cannot cross the first-person view; exit restores them. This cockpit-only presentation change does not alter the normal map-mode invariant that aircraft trails render through terrain using their depth-fail material.
- **Aircraft course slew:** civilian and military 3D models retain the 60°/s course limiter, but each rendered frame can consume at most 250 ms of accumulated slew time. A long tile/render stall therefore catches up over multiple visible frames instead of turning one delayed frame into a heading snap.
- **Manual-first cockpit briefing:** the right-side Live Signals / Regional News / Local Info carousel does not advance automatically on page load. Previous, Next, and direct page controls remain available; the visible `CYCLE OFF` / `CYCLE ON` toggle explicitly starts or stops the nine-second page cycle, which still pauses on hover/focus and while collapsed, hidden, or outside cockpit mode. Live signal data continues refreshing in either state.
@@ -4110,7 +4387,7 @@ easier to meet (detection is now on more often), but does not create it.
`SIMULATED — traffic service unreachable`; a total flow-fetch failure in live
mode sets `error` (DEGRADED · `SIMULATED — <reason>`) and zeroes the stale
coverage number. `stats.loading` covers outstanding flow work as well as the
road fetch, so a failure landing after the 250 ms paint race still ends the
road fetch, so a flow failure landing after the first paint still ends the
shared loading batch as LOAD FAILED. Harnesses must gate on `!stats.error`,
never on `mode === 'live'` alone.
- Traffic runs in `sim` mode (white dots, hardcoded speeds) unless `TOMTOM_API_KEY`
@@ -4118,14 +4395,168 @@ easier to meet (detection is now on more often), but does not create it.
TomTom flow vector tiles via the budget-governed `/api/tomtom` proxy
(`.gev-cache/tomtom/`, 120 s TTL, `TOMTOM_DAILY_TILE_BUDGET` default 6k/day,
sized so a 31-day month stays inside TomTom's 200K/month free allowance),
decoded client-side (`flowTiles.js`), matched onto Overpass roads
(`flowMatch.js`), and rendered as green/amber/red dot color + speed/density
scaling (`trafficFlowStyle.js`); closures spawn no dots; unmatched roads stay
white. Road fetch bounds center on the camera look-at point (`trafficBounds.js`).
decoded client-side for green/amber/red dot color and speed/density scaling
(`trafficFlowStyle.js`).
- Road source (`src/layers/traffic/roadModes.js`): the Street Traffic row has
TomTom / OSM / Hybrid chips (`roadMode` param, stored in the existing layer
state `lo` field, no new share token); `?trafficRoads=tomtom|osm|hybrid`
overrides restored state until the user picks a chip. With no stored choice
the default is Hybrid with a TomTom key and OSM without one; share links
without a road-source choice (including older ones) likewise use the
installation's default (Hybrid when keyed, OpenStreetMap when keyless). Without a key
every choice draws OSM roads and the status says the choice needs a key.
TomTom draws only flow-tile lines with their own flow; roads without flow
are not drawn and the status says so. A `full`-coverage flow line carries
both travel directions, a `one_side` line the direction it is drawn in.
OSM draws OpenFreeMap roads with flow matched onto them (below). Hybrid
draws eligible TomTom motor-road lines and adds OpenFreeMap roads only where no eligible TomTom line
runs within 35 m in the same travel direction (30 degrees), tested every
10 m; only uncovered stretches of at least 40 m are kept, and they are
simulated. When exactly one side is a motorway/trunk mainline (TomTom
`Motorway`/`International road`; OSM ramps count as ordinary roads) the
radius is 15 m, so public frontage roads beside a freeway stay. Flow values are ignored for this test, so an uncertain overlap
drops the OpenFreeMap copy, never the TomTom line; opposite carriageways
stay. OpenFreeMap tiles stream first and are drawn plain until flow
arrives, then one replacing snapshot applies the TomTom lines and dedupe.
Status examples: `LIVE · Roads: TomTom · Roads without flow hidden`,
`LIVE · Roads: TomTom + OpenStreetMap · Flow 81%`; Hybrid with no TomTom
line in view names OpenStreetMap alone. TomTom mode skips the z14 detail
pass and never requests OpenFreeMap unless it falls back without a key.
Matching reuses `flowMatch.js`: seven road samples, a 35 m nearest-segment
radius, 30 degree travel-bearing tolerance, and a bounded
3x3 probe in a 100 m spatial grid. At least half the samples must match;
the median flow level supplies speed/color and any matched closure stops
dots in that travel direction. Two-way roads have independent forward/reverse
records; one-way direction is enforced. Equal-distance candidates with
contradictory flow/closure are left simulated. Matching runs only on load/refresh, never per frame.
Flow stays at z12 with the same 16-tile cap, 120-second cache and server
daily budget. Lines are clipped to tile cores before caching and to the
look-at viewport fetch box before road conversion or matching. Unmatched
roads retain white simulated dots at free-flow speeds (unless the explicit
uncovered-roads hide option is enabled). Cached OSM road snapshots are
rematched on every load; TomTom and Hybrid views are recomposed from the
bounded tile caches each load because their roads carry flow. A snapshot
is cached only when the load drew plain OpenStreetMap roads, and the cache
is not read before the TomTom status probe settles (except for an explicit
OSM choice), so Hybrid fill is never replayed as OSM roads. Failed flow
refreshes clear old matches and report simulation. `flowCoveragePct` is the
percentage of shown dots on roads with flow, including all unmatched dots in
the denominator and excluding dots hidden by closures. The OSM keyed status
reads `LIVE · Roads: OpenStreetMap · Flow: TomTom · N% cov`. Road fetch
coverage follows a 5×5 ellipsoid sample of the central screen square.
Horizon rays are capped at 10 km from nadir (reduced if necessary to fit
16 z12 tiles); polygon-intersecting tiles load nearest first. Up to 16 z14
tiles provide near detail, with z12 roads beyond them. Cached tiles serve
revisits without requests, and settled coverage replaces the box-overlap gate.
- OSM geometry (OSM mode and Hybrid fill) comes from OpenFreeMap's immutable versioned tiles:
successful TileJSON metadata prefetched at enable and cached for the source lifetime, z12 wide pass and
z14 detail below 4.5 km. The nearest 16 intersecting detail tiles cover the
near field; coarse roads remain outside successfully loaded detail cores.
Partial coverage retains coarse roads under missing tiles and retries at most
three times with bounded backoff, reusing successful tiles. A failed detail pass retains major roads and separately
reports "Detailed roads unavailable". OpenMapTiles admits motorway, trunk,
primary, secondary, tertiary and minor roads, excluding access restrictions,
path subclasses, indoor ways and tunnels. Bridges and ramps inherit the motor
road's eligibility; the ramp flag alone never admits a path. Service ways,
including untyped ones, parking aisles, driveways, drive-throughs and alleys,
are excluded: their tiles do not establish public through traffic. TomTom
admits only its eight named motor-road types, rejecting Non public road,
Parking road, Walkway road and unknown categories for geometry, flow matching
and Hybrid deduplication. Eligibility follows provider classification; omitted
or incorrect upstream restrictions remain a source limitation.
Only eligible road geometry receives dots;
reverse one-way lines are reversed. The shared tile source caps each view at
16 tiles per pass (32 OpenFreeMap tiles total), four concurrent requests and 4 MiB per response. OpenFreeMap retains
up to 192 decoded tiles/64 MiB (four times body bytes as a storage estimate);
other tile sources retain the 64-tile/24 MiB defaults. Identical source-version/XYZ
requests share one download/decode, with independent caller cancellation. Parsed road views have a separate 24 MiB/64-entry cap; incomplete
views are not retained as complete snapshots. Buffer geometry is clipped and sub-12 m line slivers dropped; tile
road fragments are not stitched. Roads preserve bends and insert
waypoints at most 150 m apart, splitting paths at 80 vertices. A cancellable
preparation pass paints decoded tiles independently. Explicit enable starts
acquisition immediately; camera gestures retain the 320 ms settle debounce,
but moveEnd starts the final view immediately. Detail acquisition runs alongside
the major preview, using the same bounded tile cache. Once dots exist they stay
alive until the detail snapshot replaces them, avoiding repeated preview picks.
Status/flow and roads start
concurrently; neither TileJSON repeat reads nor flow impose a paint barrier.
Only dot-budget-admitted roads crossing the canvas (100 px margin), within
the bounded reticle range, receive surface work. Near and upper-screen roads
share the height-station budget, with fewer dots on distant roads.
Roads split into deterministic chunks no longer than 600 m, preserving every
bend while keeping dot budgets on nearby street sections. Height stations
are at most 150 m apart along the road, with heights
interpolated over the preserved intervening bends. On photoreal maps, surface
preparation first reuses rendered depth. Up to four height/projection iterations
must converge within 2 m of the road coordinate; foreground hits, invalid heights,
off-canvas points and translucent-depth picking fall back to the existing mesh
sampler. Depth and settled-height certification require a completed render of
the current camera and surface. This avoids redrawing a pick frustum for every visible station. Preparation yields after a 12 ms slice (an individual synchronous Cesium pick
cannot be preempted). Validated coordinate heights use a per-scene 40,000-point
LRU across loads. The LRU belongs to the surface it sampled (the terrain
provider, or the set of visible 3D tilesets), so a map-provider switch
drops it. Each height records its detail band (log2 of the camera-to-point
distance in 100 m steps); a point seen from a closer band is re-sampled
against the finer mesh, and a failed finer sample keeps the coarser one.
Revisits at the same or a coarser band sample nothing. Provisional shared-floor estimates remain hidden until a
local rendered-mesh/terrain sample resolves; a globally loading tileset does
not block streets whose mesh is already present. Non-finite/out-of-band
(+/-9000 m) samples defer their road and show a local-surface status, never
an OpenFreeMap failure. Samples floor on cached ground/visible terrain.
Samples acquired during refinement are revalidated when the visible mesh settles.
Shared floor cells are read only; raw mesh samples never enter their cache.
Roads retain their dot populations by source, geometry and travel direction.
Partial tiles add roads without clearing the point collection; full source
snapshots replace the accumulated graph in every road mode; completed
snapshots retire only roads that disappeared. Density changes apply at most
768 additions/removals per animation frame, with 180 ms growth/fades. The
total cap stays 6,000 at street level and scales by (1,000 / altitude)^2
above 1 km (about 667 at 3 km, minimum 400). Surface admission shares that
budget, plus a deterministic 1,000-unique-height-station ceiling, nearest roads
first (`surfaceBudgetOmitted` reports roads outside that ceiling). Below 4.5 km the wide pass prepares a 32-road preview before the
complete detail pass; previews never wait/retry missing mesh or retire existing
roads. Full coarse coverage remains available above that band.
Flow matching is memoized for admitted roads within each camera load. Surviving
dots keep their identity, road, direction and progress. Flow updates change
color, speed and closures in place. Dots use normal depth testing, including
jammed traffic, so buildings occlude roads behind them. Surface preparation is reused within a
camera load. A local height estimate selects the provisional viewport in
elevated cities; measured preview heights and rendered center depth refresh
it before detail selection. It never admits unmeasured dots. Spacing uses
height above that local surface, retaining street density at high elevations; later heights ease into separate retained waypoint arrays,
bounded to 12 projected pixels per waypoint per frame near the canvas with reused
scratch objects. Visible waypoints keep that limit even when a trial step leaves
the canvas; unsafe near-plane steps shrink before publication. Off-screen corrections use bounded world-space easing and do not
hold loading open. Road acquisition and body reads have cancellable deadlines. Queued tile paints
and terrain callbacks share pass cancellation; expired passes cannot publish,
while completed coarse roads remain available. The traffic status chip polls
busy work every 100 ms and completes without a split-flap delay; idle and global
status reads keep their 500 ms cadence. Refinement is one bounded pass, never a
publish loop. That pass replaces early cached mesh heights even in the same
distance band: tilesets can briefly report loaded before finer destination
tiles begin streaming. Ordinary revisits still reuse validated cached heights.
At a road endpoint the departing dot fades before a new simulated vehicle
identity enters at the other end, preserving one-way travel. Detection uses
those stable identities rather than collection slots. `visitMotionDots`
exposes identity, road identity, primitive and source classification for the motion gate.
`scripts/qa-traffic-motion.mjs` records timestamped CDP frames and checks
cold/warm still-camera holds in all three road modes; `--baseline` also
measures rebuild churn in older runtimes using primitive object identities.
`--journey` adds the South Congress/downtown/Camp Mabry route, layer toggles,
Capitol, greenbelt, commercial parking and corridor holds, with a 1.5-second
steady-state limit including loading and population changes. It checks every
shown dot against the allowed source classes and records phase timings.
Positions and segment distances are precomputed, with no new animation-loop
allocation. Visible-globe roads use cached terrain without offscreen mesh picks. The road-source label names the geometry being drawn, with partial/unavailable states shown plainly.
- TileJSON caches successful metadata. Transient failures retry after a
five-second cooldown; invalid metadata/origins stay unavailable until the
source is cleared. Clear resets metadata and cancels pending requests.
Every failed read cancels its body and aborts its owned request, including
declared and streaming byte-cap failures.
- Development captures opened with `?trafficDebug=1` mint an interaction anchor
from the exact `camera.changed` event that arms each debounced load, then emit
scheduling-correlated User Timing entries for production `response.json`, road
parse, flow-race, dot construction, heat-line rebuild, and next-post-render
parse, dot construction, heat-line rebuild, and next-post-render
boundaries. Every trace is paired with the exact camera-change that scheduled
its load; mismatches are counted drops. Cesium's `moveEnd` remains a diagnostic
mark only: it arrives about 500 ms after stillness, typically after fetch has
@@ -4197,11 +4628,11 @@ executable-path lookup before testing or passing the path to Chrome. Supported N
### Traffic city navigation
Traffic checks the final camera view on arrival and retries a failed road request
while the camera is stationary, backing off from 1.5 to 30 seconds. Failed road
requests report an unavailable source. Leaving the traffic altitude range or
disabling the layer cancels pending work; superseded road and flow requests cannot
release the current request or keep its loading indicator active.
Traffic checks the final camera view on arrival. Retryable road failures get
at most three parked retries with 1.5–30 second backoff; permanent capability
misses stop both the retry timer and enable-time kick. New views can try again.
Leaving the traffic altitude range or disabling cancels outstanding work.
Superseded road/flow responses cannot publish or refill cleared caches.
### Optional frame-rate readout
@@ -4433,3 +4864,55 @@ Desktop wind now allows 7,200 baked native GPU paths (narrow viewports remain at
Temperature uses stronger fixed −40..50°C colors; the underlying 1° forecast and
numeric inspection values are unchanged. No volumetric cloud height or local rain
arrival prediction is claimed. NOAA source limits are documented in DATA_SOURCES.
Traffic source refinements: ALPR admits z9-z12 detail only when the view fits 16 tiles,
returning explicit zoom guidance before I/O and allowing the next smaller view to
load normally. Outside the US/Canada extracts the row reads “No ALPR data for this
area — US and Canada only” and suppresses the nearby count. Military viewport
membership checks every merged footprint. Different source identities require
topological contact; identity aliases are zoom-scoped so coarse associations cannot join separated
detailed parcels.
Country outlines use the lazy Natural Earth admin-0 pack. UK constituent
countries use their own map-unit geometry; parent-country qualifiers stay separate
from target aliases. The pack includes substantial
islands. Names and aliases resolve offline; ambiguous Georgia follows the
camera or an explicit country qualifier. Geocoded country boundaries require
both a matching name and containment of the geocoded point.
Installation camera-load keys belong to their scheduled view; an older aborted
request cannot clear a newer view’s pending key.
Name acquisition has a ten-second deadline and retry cooldown. Close views publish
polygons immediately and enrich only the current view when names arrive.
Military names load lazily from the separately licensed Overture/OSM pack when
installation context needs them. Typed OSM ids join polygon fragments before
merging; canonical group identity survives partial views independently of the
largest named member supplying the title. Enrichment reconciles canonical ids
to the named parent across zooms, migrating selection, context and marker ownership
through explicit aliases. Geometry-union evidence stays zoom-scoped. Parent names and other member names
remain available across same-zoom pans. Wide views use a viewport-culled, area-ranked grid of at most 512
point primitives and 24 decluttered titles. Wide-point and polygon titles share
one world-overlay source using the same ambient-card entries, paint lane and persistent
label arbitration as datacenters and dams, with a 24-title source ceiling.
Camera changes reproject existing markers and titles without source loads or
per-frame allocation. Selection promotes the record’s own entry to a selected card;
clicking empty ground restores its ambient title without a second readout. Selected
footprints receive a 22% outline-colored classification fill on Google 3D tiles or
the active terrain, removed on deselect/disable and rebound on map-source changes. Labels follow the host’s Clean-UI behavior; disable/destroy
clears the source and removes marker listeners. Named point ids carry into polygon
views; wide named points remain until their close polygons publish. Unnamed
polygons remain “Military area”.
Tile sources accept a separate `tileFetchImpl` for public vector tiles; API
requests retain `fetchImpl`. The exported Overpass ALPR source uses `/api/overpass`;
the standalone layer defaults to the hourly tile source.
OSM-derived layers share one OpenStreetMap data attribution entry. Displayed
OSM data retains a short inline OpenStreetMap credit, plus OpenMapTiles while
OpenFreeMap tiles are used; its display follows the existing map-credit rules. Routing, Warendorf cameras,
datacenters, dams and the Nepal locator retain that shared credit while displayed.
Military fragments stitch by source identity. Different mapped parcels merge only
when their footprints touch or contain one another, never across a seam tolerance gap.
+71
View File
@@ -0,0 +1,71 @@
# MCP setup
Use God's Eye View from Claude and Codex: tools, links, and the live globe
inside the conversation. Agents: follow these steps in order and check each
one before going on.
## 1. Run the app
```sh
npm install
npm run build:panel # the in-conversation globe; rerun after app changes
npm run dev # http://localhost:5173, keep it running
```
The MCP server reads from this app.
## 2. Add the server to a client
The server is `server/mcp/stdio.js` in this checkout. Use absolute paths
(clients do not load your shell profile), with each argument as its own entry.
| Client | How |
| --- | --- |
| Claude Code | `claude mcp add gods-eye-view -- node /abs/path/gods-eye-view/server/mcp/stdio.js --api-base http://localhost:5173` |
| Codex CLI | `codex mcp add gods-eye-view -- node /abs/path/gods-eye-view/server/mcp/stdio.js --api-base http://localhost:5173` |
| Codex and ChatGPT desktop | Same `~/.codex/config.toml` as the CLI (block below), or Settings → MCP servers → Add server (STDIO). |
| Claude Desktop | Settings → Developer → Edit Config, then add the JSON below. |
```toml
[mcp_servers.gods-eye-view]
command = "node"
args = ["/abs/path/gods-eye-view/server/mcp/stdio.js", "--api-base", "http://localhost:5173"]
```
```json
{
"mcpServers": {
"gods-eye-view": {
"command": "node",
"args": ["/abs/path/gods-eye-view/server/mcp/stdio.js", "--api-base", "http://localhost:5173"]
}
}
}
```
**Windows with the checkout in WSL:** use `"command": "wsl.exe"` and put
`"-e"` and the absolute Linux path to `node` first in `args`, for example
`["-e", "/home/you/.local/share/mise/installs/node/24/bin/node", "/home/you/gods-eye-view/server/mcp/stdio.js", "--api-base", "http://localhost:5173"]`.
## 3. Restart and try it
Fully quit the client (on Windows, from the tray icon) and reopen it, then
start a new chat; clients keep the server they started with. Ask: *"Show San
Diego in God's Eye View with military flights on."*
- Claude Desktop (a chat, not the Code tab), Codex and ChatGPT desktop show
the live globe; the first load takes 10–20 seconds.
- Claude Code and the Codex CLI answer with data and a link.
## Troubleshooting
- **The server does not appear:** run the command from step 2 in a terminal;
it should print `God's Eye View MCP server reading http://localhost:5173`
and wait. A path or `args` mistake fails here.
- **The panel says it could not load:** `npm run build:panel` was not run, or
the dev server is down.
- **Logs:** the server writes each call, and why one failed, to stderr. Claude
Desktop keeps it in its logs folder as `mcp-server-gods-eye-view.log`; in
Codex, right-click the panel → DevTools for the panel's console.
See [tools and the MCP server](TOOLS.md) for what the tools do.
+302
View File
@@ -0,0 +1,302 @@
# Tools and the MCP server
Tools answer questions from God's Eye View data for language-model clients.
They are defined once and exposed through adapters: the Model Context Protocol
(MCP) and function calling, which voice uses.
To use them from Claude or Codex, see [MCP setup](MCP_SETUP.md).
## Layers
| Owner | Responsibility |
| -------------------- | ------------------------------------------------------------------------------------------------- |
| `src/tools/` | Tool definitions, catalog composition, argument validation, shared `area` and result helpers |
| `src/tools/queries/` | Queries, one file per domain, reading only portable source contracts |
| `src/tools/mcp/` | MCP protocol (JSON-RPC) and a stateless HTTP transport; knows the catalog interface, not queries |
| `src/tools/functions.js` | Function-calling adapter: tool records and results for function-calling clients |
| `src/tools/services.js` | The default services: the layers' source factories and request services, given a resolving fetch |
| `server/mcp/` | Node composition: points the sources at a running app's `/api` routes; serves stdio and `/mcp` |
| `server/standalone/voiceTools.js`, `src/standalone/toolCatalog.js` | Standalone voice composition: the session's tool list and the browser catalog |
Dependencies point downward only. `gods-eye-view/tools` and
`gods-eye-view/tools/mcp` are portable exports: they reach no application,
rendering, Node, Cesium or browser-global code, which
`npm run check:boundaries` enforces. In the application, only voice reaches
them: `withToolCatalog` in `src/voice/gevRealtime.js` imports the
function-calling adapter, and the standalone entry supplies the catalog.
## Definitions and composition
`defineTool({ name, kind, title, description, inputSchema, requires, run })`
validates and freezes a tool. `kind` is `query` (answers from data, read-only)
or `action`. `inputSchema` uses a JSON Schema subset that `src/tools/schema.js`
checks completely; unsupported keywords are rejected at definition time.
`run(args, { services, signal })` resolves to `{ summary, data }`: one sentence
for people and a structured object for programs. A tool may also return `images`,
each `{ mimeType, data }` with base64 data; the MCP adapter sends them as image
content. MCP results carry the summary and the data as JSON text, plus the data
as `structuredContent`, for clients that read only one of them.
`composeCatalog({ tools, services, replace, interceptors })` builds a catalog:
- **Tools**: an application adds its own tools to `coreTools`. Reusing a name
fails unless the name is listed in `replace`.
- **Services**: each tool names the services it reads in `requires`. Tools
whose services are not supplied are left out.
- **Interceptors**: `(call, next) => next(call)` functions wrap every call,
outermost first. They can observe, reject or change a call.
- **Composite tools**: `run` receives `tools`, with `has(name)` and
`call(name, args)`, to call other tools through the same catalog, so
replaced tools and interceptors apply. Interceptors see such calls with
`parent`, the calling tool's name.
### Surfaces
`src/tools/surfaces.js` lists which tools MCP and voice offer. A tool is on
both unless `TOOL_SURFACES` turns it off; edit an entry to turn a tool on or
off for one surface. MCP leaves out place search, routing, plain
weather and wind, the regional brief, the HUD caption, radio, bike share and
transit, which assistants already cover or which add little without the
globe. `catalogForSurface(catalog, surface, overrides)` is the
view a surface exposes: it lists and calls only the tools it offers, while
composite tools still reach the whole catalog. `toolsForSurface` gives the
same selection as a list of definitions, such as for the voice session's tool
list. Voice leaves out tools that answer with images, link to the app, or
repeat what its app actions answer.
Expected failures throw `ToolError` with one of `invalid_arguments`,
`unavailable`, `unsupported`, `malformed` or `retry_later`. Other errors are
reported to clients without details.
## Services
`gods-eye-view/tools/services` builds the default set with
`createToolServices({ fetchImpl, appUrl })`. Services are the portable source factories the layers already use, such as
`createUsgsEarthquakeSource`, `createFirmsSource` and `createLaunchSource`,
plus a `places` service with `resolve(name, { signal })`. Sources request
relative `/api/...` paths through an injected `fetchImpl`, so the same tool
code runs wherever an application routes those paths.
The `weather`, `regional`, `terrain`, `summary` and `features` services are the
application request services from `gods-eye-view/application/requests`.
`situation_brief` and `military_awareness` run each section whose services are supplied and mark the
others unavailable. `app` is `{ baseUrl }`, the address links open. The `bikeshare` service is `{ systems, getStations }`: the system registry and
the GBFS source. `createGeocodePlaceService` resolves place names through `/api/geocode`;
`createPlaceSearchService` searches `/api/google/*` and reports when no search key
is configured; `createRouteService` plans routes through `/api/route`.
## Views
`gods-eye-view/view` (`src/view/index.js`) describes what the app shows,
independent of how it is shown: a camera (lat, lon, altitude, heading,
pitch), data layers, visual style, map imagery, and an aircraft, military
aircraft or satellite to follow. `createView` builds and bounds one,
`viewToParams` and `viewFromParams` write and read it in the share-link
format the app restores, and `viewUrl` gives the address that opens it. The
style names are the ones share links use. Ships cannot be followed from a
link yet. An aircraft's `follow` may ask for its cockpit view
(`cockpit: true`); links cannot carry that and open the app following it.
A view can also carry `annotations`, the marks the app's `annotate_map`
action draws (pins, highlights, areas, arrows, routes and labels at a place
name or coordinates). Links carry them in the `an` parameter, and the app
draws them once the link has been restored.
Tools take a view as `VIEW_ARGUMENTS`: an `area` to frame from above, or a
`camera`, plus `layers`, `style`, `map`, `follow` and `annotations`. A
tilted camera over an area looks at its center from behind; camera fields
given with an area override its framing. `resolveViewArguments` turns them
into a view.
### The God's Eye View panel
`show_in_gods_eye_view` names an MCP Apps view (`io.modelcontextprotocol/ui`):
`_meta.ui.resourceUri` is `ui://gods-eye-view/globe`, a `text/html;profile=mcp-app`
resource from `createGlobePanelResource({ runtime })` in
`src/tools/globePanel.js`, with the panel's script from
`src/app/globePanelRuntime.js`. Clients that display apps render it inside
the conversation. The panel completes the MCP Apps handshake
(`ui/initialize`, `ui/notifications/initialized`,
`ui/notifications/size-changed`), and for each
`ui/notifications/tool-result` carrying a view it loads the app in inline
embed mode the first time and posts later views to that same app, so the
globe changes without reloading. Its "Open in God's Eye View" button asks
the host to open the link (`ui/open-link`).
Hosts serve panels from their own sites and may refuse other addresses;
Codex, for one, refuses any address on the user's machine. So the panel
never requests the app's server itself. It loads everything from the app's
own paths through `panel_request`, a tool meant for the panel: it is
marked `_meta.ui.visibility: ["app"]`, and each call must carry the key the
MCP server puts in its panel page. Any client can read that page, so the key
keeps the tool from clients that only list it, and is not access control. The
MCP server requests the path from the app's server and returns the response,
compressed and in parts when large.
The same path works in every host. Only
map imagery, tiles and fonts load directly, from the providers the
resource's `csp` lists. `panel_request` refuses Provider Settings
(`/api/setup`), credential and model endpoints (`/api/realtime`,
`/api/openai`), `/mcp` and the development server's internal routes, in any
letter case, encoding or dot suffix.
The panel loads the app's panel build, which `npm run build:panel` writes to
`dist/panel` and the servers serve at `/panel/`: one app script, one
stylesheet, and Cesium's script, which carries its workers and starts them
from memory (`CESIUM_WORKERS`). Files those workers load themselves are
embedded in a prelude the panel runs ahead of them, since a worker's
requests reach the panel page's own site. Rebuild it after changing
the app.
Hosts differ in ways the panel works around, all inside the panel only:
images and stylesheet files arrive as `data:` URLs, since some hosts refuse
`blob:` images; code that needs an https address for the app gets
`GEV_APP_BASE_URL`, since some hosts serve the page from their own scheme;
the globe keeps drawing from a timer when the host reports the panel hidden
and stops animation frames; and 2D canvases are kept in memory
(`willReadFrequently`), since a host that treats the panel as off screen may
drop their GPU contents and show the overlays as black over the globe.
Tools declare a UI resource with `defineTool({ ui: { resourceUri } })`, and
an app-only tool with `ui: { visibility: ['app'] }`;
`createMcpServer({ resources })` serves `resources/list` and
`resources/read`.
### Embed mode
`?embed=1` shows only the globe: clean view, with the HUD, panels, welcome
and setup prompts hidden; provider attribution stays. A page that frames it
changes the view by posting `{ type: 'gev:view', id, view }` to the frame.
The app applies it through its own actions (style, map, exactly the view's
layers, annotations, then the followed entity, retried until its layer has
it, or the camera when nothing is followed or the entity is not there yet,
since a camera flight would end the follow; then cockpit view when asked) and
answers `{ type: 'gev:view-applied', id, ok, steps }` to the origin that
sent the view. It posts `{ type: 'gev:ready' }` once it can take views, and
only its parent page can send them. See `src/app/embed.js`.
No page may frame the app by default: every document keeps
`X-Frame-Options: DENY` and `frame-ancestors 'none'`. Setting
`GEV_EMBED_FRAME_ANCESTORS` lets the pages it names (CSP frame-ancestors
sources, or `*` for any page) frame embed-mode documents only. The MCP Apps
panel loads the app into its own page and needs no framing.
Answers that have something to show include `data.view`: the view that
shows them, with the matching layers on, an area framed from above, and a
single aircraft or satellite followed, plus `url` to open it (null when the
app's address is not configured). `suggestView` in `src/tools/views.js`
builds one.
## The `area` argument
Location-scoped tools take `area` as exactly one of a `place` name, a `bbox`
(`[west, south, east, north]`, crossing the antimeridian when west exceeds
east), or `lat`, `lon` and `radius_km`. Lists default to 25 rows, at most 200,
and report `total`, `returned` and `truncated`.
## MCP
`createMcpServer({ catalog, name, version, instructions, descriptions, decorate })`
implements `initialize`, `ping`, `tools/list` and `tools/call` for protocol
revisions 2025-11-25, 2025-06-18 and 2025-03-26. `descriptions` overrides a
tool's title or description for this surface; `decorate(definition, tool)`
merges extra fields into each listed definition. `createMcpHttpHandler(server)`
returns a `Request`-to-`Response` handler for stateless Streamable HTTP: one
JSON-RPC message per POST, answered with JSON. The host owns routing and any
access control in front of it.
## Voice
Voice offers the catalog's queries next to its app actions.
`toFunctionTools(tools, { exclude })` turns tools into
`{ type: 'function', name, description, parameters }` records, and
`toFunctionOutput(name, result)` turns a result into
`{ ok, tool, summary, data }`, counting images in `images_omitted` instead of
sending them.
The voice session token endpoint takes its tool list as `realtime.tools`.
`realtimeSessionTools(additional)` appends function tools to the app actions,
skipping names an action already uses, so `next_satellite_pass` stays the
action. The standalone server supplies the core queries that
`src/tools/surfaces.js` offers on voice, which leaves out tools that answer
with images, link to the app, or repeat what voice's app actions answer
(aircraft, ships, earthquakes, fires, datacenters, dams and satellites
overhead, which `analyst_query` covers).
In the browser, `initGevVoiceCommands({ toolCatalog })` takes a function that
resolves a catalog. App action names go to the action runner; other names the
catalog has go to `catalog.call` with the call's abort signal. The standalone
entry composes the catalog with `createToolServices` over the page's fetch and
loads it the first time voice calls a query.
## Running locally
Start the app (`npm run dev` or `npm run preview`), then register the stdio
server with an MCP client, for example Claude Code:
```bash
claude mcp add gods-eye-view -- npm --prefix /path/to/gods-eye-view run --silent mcp
```
`npm run mcp -- --api-base http://localhost:4173` selects another server.
The development and preview servers also serve the same tools over HTTP at
`/mcp`, for clients that connect by URL:
```bash
claude mcp add --transport http gods-eye-view http://localhost:4173/mcp
```
The route accepts only requests from this machine that name a loopback host
on the port they reached and, when a browser sends an `Origin`, come from
that same host. It refuses requests a proxy forwarded and refuses all
requests while launcher sharing is on. This is local transport safety, not
authentication. The
local server's tools request the app's `/api` routes and the public feeds
the sources already use. The panel's `panel_request` also requests the app's
own files and data routes for the panel: it requires the key in the panel's
page, and refuses Provider Settings, credential and model endpoints, `/mcp`
and the development server's internal routes. See SECURITY.md.
## Tools
| Tool | Reads | Returns |
| --------------------- | ------------- | ------------------------------------------------------------- |
| `get_earthquakes` | `earthquakes` | USGS M2.5+ events in the last 24 hours, strongest first |
| `get_active_fires` | `fires` | NASA FIRMS detections in an area, highest radiative power first |
| `get_recent_launches` | `launches` | Launch Library 2 launches in the last 30 days, newest first |
| `aircraft_in_area` | `aircraft` | Aircraft in an area, nearest first; `military: true` reads the `military` feed |
| `find_aircraft` | `aircraft` | Aircraft anywhere by callsign, ICAO address or registration |
| `get_aircraft_track` | `aircraft` | Recent positions of one aircraft, thinned to 200 points |
| `get_aircraft_info` | `aircraft` | Aircraft type and registration, and flight route, from adsbdb |
| `vessels_in_area` | `vessels` | Ships reported by AIS in an area, nearest first, optionally by type |
| `find_vessel` | `vessels` | Ships anywhere by MMSI, IMO number or name |
| `get_vessel_track` | `vessels` | Recent positions of one ship, thinned to 200 points |
| `next_satellite_pass` | `satellites` | Next pass over a place or point (default the ISS), with naked-eye visibility |
| `satellites_overhead` | `satellites` | Satellites in a CelesTrak group above a place or point now, highest first |
| `find_cctv_cameras` | `cctv` | Public cameras in an area, nearest first |
| `get_cctv_snapshot` | `cctv` | The current image from one camera, returned as image content |
| `find_alpr_cameras` | `alpr` | OpenStreetMap-mapped license plate readers in a US/Canadian area up to 3° |
| `find_radio_stations` | `radio` | Radio Browser stations by area and/or search terms, with stream URLs |
| `search_places` | `placeSearch` | Points of interest matching a query within an area (Google Places) |
| `places_nearby` | `placeSearch` | Notable places around a place or point (Google Places) |
| `plan_route` | `routing` | Walking, driving or cycling route over OpenStreetMap, with a simplified path |
| `get_bike_share` | `bikeshare` | Live GBFS stations in an area, with bikes and docks available |
| `get_transit_vehicles` | `transit` | Live GTFS-Realtime vehicle positions in an area, optionally one route |
| `get_traffic_flow` | `traffic` | TomTom flow in a city-sized area: speed vs free flow, congested and closed road |
| `get_weather` | `weather` | Current conditions at a place or point |
| `get_weather_map` | `weatherMaps` | The latest NOAA radar, satellite or lightning map image over an area |
| `get_wind` | `wind` | GFS or IFS model wind 10 m above ground at a location |
| `get_recent_imagery` | `imagery` | The most recent clear Landsat/Sentinel-2 image of an area (VIIRS fallback) |
| `find_submarine_cables` | `cables` | TeleGeography cables and landing points by area or name (CC BY-NC-SA 3.0) |
| `find_infrastructure` | `infrastructure` | OpenStreetMap datacenters or dams in an area, nearest first (ODbL) |
| `get_bhote_koshi_flood` | `events` | The 2026 Bhote Koshi flood: evidence trail in story order, flood path and imagery dates (CC BY-NC 4.0) |
| `get_regional_brief` | `regional` | What and where a location is, its weather and recent headlines |
| `get_cyclones` | `cyclones` | Active NHC/CPHC tropical cyclones, optionally in an area |
| `get_fire_perimeters` | `perimeters` | Mapped WFIGS wildfire perimeters in an area, largest first |
| `get_terrain_height` | `terrain` | Ground, geoid and ellipsoid heights at up to 20 points |
| `find_military_installations` | `installations` | OpenStreetMap military sites in an area of at most 10° per side |
| `get_map_features` | `features` | Administrative areas, named places or monuments at a location (needs Overpass) |
| `situation_brief` | `weather` | Weather, earthquakes, fires, aircraft, ships and cyclones for an area, by section |
| `military_awareness` | `military` | Military and other aircraft, ships and military installations within 250 km of a point, by section |
| `get_hud_caption` | `weather`, `summary` | The app's heads-up display caption for an area |
| `show_in_gods_eye_view` | `app` | A view in God's Eye View: the live panel in clients with MCP Apps, and a link everywhere; takes another answer's view or an area or camera, layers, style, map, marks and something to follow |
| `panel_request` | `app` | Panel only: loads a path from the app's server for the God's Eye View panel |
+2 -2
View File
@@ -1,6 +1,6 @@
# OpenSky Auth Setup
God's Eye View uses explicit auth modes for `/api/opensky`:
God's Eye View uses explicit auth modes for `/api/flights`:
- `OPENSKY_AUTH_MODE=oauth` (default, recommended)
- `OPENSKY_AUTH_MODE=auto` (OAuth first, then Basic fallback)
@@ -54,7 +54,7 @@ OPENSKY_AUTH_MODE=basic ./scripts/dev-fresh.sh
Inspect proxy headers:
```bash
curl -si http://localhost:4173/api/opensky | grep -iE 'HTTP/|X-OpenSky-Auth'
curl -si http://localhost:4173/api/flights | grep -iE 'HTTP/|X-OpenSky-Auth'
```
Useful reasons:
+150
View File
@@ -0,0 +1,150 @@
# Panel surface contract
`src/ui/styles/panel-surfaces.css` supplies an opt-in visual surface for new
panels. It uses the application's default color, font, border and radius tokens;
Cyber supplies its existing red/slate palette, monospace typography and shaped
frame. This is CSS shared by the existing panel system, not a panel registry or
a controller. A panel ID does not need a new theme selector.
## Markup
A rail panel uses an outer owner with its existing stable `id`, matching
`data-panel-id`, and `panel-collapsible` class. Its first non-glow child is the
surface. Keep the header and any outward popup outside the scrolling body:
```html
<section
id="example-panel"
data-panel-id="example-panel"
class="panel-collapsible"
>
<div data-panel-surface>
<header data-panel-header>
<h2 class="panel-title panel-surface-title">Example</h2>
<button
class="panel-collapse-btn panel-surface-control"
data-collapse-target="example-panel"
aria-expanded="true"
aria-label="Collapse Example"
>
−
</button>
</header>
<div data-panel-body data-rail-scroller>
<label>Filter <input class="panel-surface-control" type="text" /></label>
<button class="panel-surface-control" type="button">Apply</button>
<p class="panel-surface-error" role="status" hidden></p>
</div>
<aside data-panel-popup hidden aria-label="Example help">
<!-- The panel owns this popup's placement and interactions. -->
</aside>
</div>
</section>
```
Use native controls and accessible names. `.panel-surface-control` is an
optional skin for buttons, text fields, selects and textareas; it provides
hover, focus, disabled and invalid-state styling. Native `disabled` owns actual
disablement; `aria-disabled` alone only describes a state and must be enforced
by the controller. `.panel-surface-error` supplies error color, not announcement
or validation behavior. Keep checkbox/radio semantics and labels intact.
`data-panel-header` stays outside `data-panel-body`, so it remains reachable
when content scrolls. Give the body a bounded height through its containing
surface. The rail's allocation supplies that bound; a dock or dialog owner must
supply its own `max-height` or height. Avoid a flex body whose contents shrink to
fit when those contents should instead scroll.
## Placement and ownership
| Mount | Shared contract | Owner responsibilities |
| ------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Direct child of `#left-panel-stack` | An owner with a direct `[data-panel-surface]` child receives relative positioning and allocated height; inner surface/body skin | Add the wrapper shown above, use the existing layout/disclosure owners, and retain left-rail focus/auto-collapse rules. |
| Direct child of `#right-context-rail` | The same hooks with right-rail allocation and intrinsic measurement | Keep `data-rail-scroller` on the body so measurement preserves its scroll position. Respect Cyber's one-expanded-panel policy. |
| Surface inside `#command-dock` | Surface, controls, popup decoration and inherited dock concealment | Supply geometry, stacking, disclosure, pin behavior if wanted, safe viewport bounds and dismissal. This CSS does not extend the existing Location/Visual Presets tray stack manager to arbitrary panels. |
| `dialog[data-panel-surface]` | The same surface/body skin; a closed dialog remains hidden | Set width/max-height, use `showModal()` or `show()` and `close()`, and own labels, initial/return focus and dismissal. Do not put `panel-collapsible` on a dialog. |
For rails, `--panel-expanded-width` and `--panel-collapsed-width` customize
the desired widths within the rail; otherwise Cyber uses its existing left,
right and collapsed panel widths. `--left-panel-allocated-height` and
`--right-panel-allocated-height` belong
to the existing layout owner, not to individual panels. The surface must be
the wrapper's first non-glow child because left-rail intrinsic measurement
inspects that child. Do not put an absolutely positioned popup before the
surface or inside its body. Position a popup beside or above its surface using
component CSS; the shared rules deliberately do not choose a direction that
could run beyond the viewport.
Rail measurement still considers visible child extents. A popup extending
below the surface can therefore increase its measured height. Prefer a popup
beside or above the surface, or integrate an external overlay host with the
feature's layout owner; this CSS does not change measurement algorithms.
At viewport widths of 720px or less, both rails are intentional scroll
containers. Their overflow can clip outward popups even when the panel's own
surface is unclipped. Use a popup contained within the rail's visible bounds,
or an explicitly owned overlay host outside the rail; the surface contract
does not provide automatic popup escape from these small-screen scroll regions.
For dock disclosure, use a semantic button with `.panel-surface-control` and
the feature's own accessible name, `aria-expanded` state and event binding.
Do not copy the rail example's `.panel-collapse-btn` class into the dock:
existing dock rules intentionally hide that class. The generic surface skin
does not replace the dock's disclosure policy.
The existing `PanelChrome` binds `.panel-collapse-btn[data-collapse-target]`
at initialization and routes state through `setPanelCollapsed`. For additions
present at startup, keep those hooks and the existing chrome lifecycle. A
dynamically mounted panel must bind and dispose its own disclosure using
`bindPanelDisclosure` / `collapsePanelOnEscape` from `panelDisclosure.js`, with
the application owning state changes. Do not rerun the whole application chrome
initializer just to add one panel. `layoutLeftPanelRail` and
`layoutRightPanelRail` accept arbitrary direct `data-panel-id` children; their
host owns layout scheduling, observations and collapse callbacks. Dynamic
content that changes size may require an owner-scheduled layout pass or an
owned `ResizeObserver`; adding CSS attributes does not register new observers.
Persisted/share-link state, pinning, provider loading, keyboard shortcuts and
Cockpit-specific mounting still require deliberate integration with their
current owners. This contract does not add entries to named persistence, dock,
Cockpit or share-state policies. Dispose listeners, observers and timers,
dismiss open popups/dialogs and restore focus before removing a dynamic panel.
## Decoration, popups and visibility
The shared surface reserves `::before` for its non-interactive background. The
Cyber frame clips this pseudo-element only; the surface stays unclipped with
visible overflow. This follows the existing Display and Global Context pattern.
Putting the shape on an interactive ancestor clips all descendant popups even
if their `z-index` is high. A scrolling body also clips descendants by design,
so outward popups must be siblings of the body, or use an explicitly owned
overlay host outside every clipping ancestor.
`data-panel-popup` gives a positioned popup the same decorative and control
tokens. It is not a popover API, focus trap, collision solver or top-layer
manager. The owner must keep it inside the viewport, above relevant siblings,
and reachable by pointer and keyboard. A native modal dialog participates in
the browser top layer; a surface in a rail does not. Transform, containment,
mask, clipping or stacking rules on an additional ancestor can still obstruct
either an ordinary panel or its popup. Arbitrary future markup is not covered.
`hidden`, a closed native dialog and collapsed bodies/popups remain hidden.
Clean UI and recording conceal conforming surfaces, including outward popups;
owners must close modal dialogs when entering these modes so an invisible
modal cannot leave the application inert. Existing Cockpit rail and dock
exclusions remain in force. New left-rail/Cockpit functionality needs an
explicit product integration; the surface attribute grants no mode exception.
Never use theme styling to alter stored preferences or force hidden content
visible.
## Check an addition
Use the real application with the default and Cyber themes. Check open,
collapsed, hidden, disabled/error, theme round trips, clean UI/recording and
the intended Cockpit behavior. Exercise keyboard focus, Escape, pointer input,
the last scrolling item, resize without reload and any pin/unpin controls.
For an outward popup, test visible pixels and pointer hit-testing outside the
surface; a positive bounding box alone does not prove it escaped clipping.
Check each mount the addition actually supports. The CSS contract cannot
establish provider correctness, disclosure lifecycle or responsive placement
without those feature-specific checks.