diff --git a/.env.example b/.env.example
index e16e85a27..8cb85ecf4 100644
--- a/.env.example
+++ b/.env.example
@@ -34,16 +34,19 @@ GOOGLE_MAPS_API_KEY=
# single shared key keeps working — splitting is opt-in.
# SERVER-SIDE ONLY.
GOOGLE_MAPS_SERVER_API_KEY=
-# Optional: opt-in per-IP rate limit for the Google Places cost endpoint
-# (/api/google/nearby-places), in requests per minute.
-# DEFAULT IS UNLIMITED — unset (or 0) means no throttling, unchanged behavior.
-# Set a positive integer to cap per-IP Places quota spend when self-hosting.
-# Recommended whenever HOST is not localhost. NOTE: this is a per-IP,
-# process-local guard (in-memory, resets on restart; over-limit requests get a
-# sanitized 429) — it is NOT a billing cap. For hard spend protection set
-# provider-side budget alerts: Google Cloud Console -> Billing -> Budgets & alerts
-# (plus per-API quotas under APIs & Services -> Quotas).
-# GEV_RATELIMIT_GOOGLE_PER_MIN=60
+# Per-IP rate limit for the Google Places cost endpoints
+# (/api/google/nearby-places + /api/google/text-search), in requests per minute.
+# DEFAULTS TO 120, the value the Pinokio build already ships. Raise or lower it
+# with a positive integer; 60 is a sensible tightening whenever HOST is not
+# localhost. Set it to exactly 0 to run unthrottled. An unreadable value (a
+# typo, a stray unit) falls back to the default rather than to unlimited, so a
+# slip cannot quietly disarm the guard. A positive value below 1 counts as 1.
+# NOTE: this is a per-IP, process-local guard (in-memory, resets on restart;
+# over-limit requests get a sanitized 429) — it is NOT a billing cap. For hard
+# spend protection set provider-side budget alerts: Google Cloud Console ->
+# Billing -> Budgets & alerts (plus per-API quotas under APIs & Services ->
+# Quotas).
+# GEV_RATELIMIT_GOOGLE_PER_MIN=60 # uncomment to tighten from the default 120
# Optional: Google Photorealistic 3D Tiles through Cesium ion, Bing world
# imagery, and Cesium World Terrain. The free Community plan is for eligible
@@ -65,15 +68,19 @@ OPENAI_REALTIME_REASONING_EFFORT=low
OPENAI_REALTIME_CONTEXT_TOKENS=3000
OPENAI_REALTIME_CONTEXT_RETENTION=0.5
OPENAI_HUD_SUMMARY_MODEL=gpt-5-nano
-# Optional: opt-in per-IP rate limit for the OpenAI cost endpoints
+# Per-IP rate limit for the OpenAI cost endpoints
# (/api/realtime/token + /api/openai/hud-summary), in requests per minute.
-# DEFAULT IS UNLIMITED — unset (or 0) means no throttling, unchanged behavior.
-# Set a positive integer to cap per-IP spend when self-hosting on a network.
-# Recommended whenever HOST is not localhost. NOTE: this is a per-IP,
-# process-local guard (in-memory, resets on restart; over-limit requests get a
-# sanitized 429) — it is NOT a billing cap. For hard spend protection set a
-# provider-side budget: OpenAI platform -> Settings -> Limits (usage limits).
-# GEV_RATELIMIT_OPENAI_PER_MIN=30
+# DEFAULTS TO 30, the value the Pinokio build also ships. That clears ordinary
+# use several times over: an open tab asks for a HUD summary every 15s (4/min)
+# and a voice session mints one token. Raise or lower it with a positive
+# integer; set it to exactly 0 to run unthrottled. An unreadable value (a typo,
+# a stray unit) falls back to the default rather than to unlimited, so a slip
+# cannot quietly disarm the guard. A positive value below 1 counts as 1.
+# NOTE: this is a per-IP, process-local guard (in-memory, resets on restart;
+# over-limit requests get a sanitized 429) — it is NOT a billing cap. For hard
+# spend protection set a provider-side budget: OpenAI platform -> Settings ->
+# Limits (usage limits).
+# GEV_RATELIMIT_OPENAI_PER_MIN=30 # the default; uncomment only to change it
# Optional: OpenSky auth mode (oauth | basic | auto | anon)
# Default: oauth — requires client credentials below
@@ -101,6 +108,11 @@ PORT=4173
# networks you trust, and consider the GEV_RATELIMIT_* throttles above.
# HOST=localhost
# HOST=0.0.0.0
+# Optional: comma-separated LAN hostnames to accept, in addition to localhost
+# and IP addresses, for example GEV_ALLOWED_HOSTS=globe.lan,globe.internal.
+# Binding HOST=0.0.0.0 alone does NOT trust every Host header; suffix and
+# wildcard entries are ignored.
+# GEV_ALLOWED_HOSTS=
# Optional: NASA FIRMS live active fires. SERVER-SIDE ONLY (never reaches the
# browser — the client polls the /api/firms proxy). Free key:
@@ -110,7 +122,7 @@ PORT=4173
# Optional: Live AIS vessels layer
# Browser fetch path. Keep same-origin so the private stream key stays server-side.
-VITE_AIS_LIVE_API_URL=/api/ais-live
+VITE_AIS_LIVE_API_URL=/api/vessels
# Client render cap. The server can keep a larger AISStream cache, but drawing
# every global vessel at once can overwhelm WebGL on laptops.
VITE_AIS_LIVE_MAX_ROWS=12000
@@ -183,3 +195,11 @@ AISSTREAM_API_KEY=
# CCTV_MAX_SOURCES=4000 # catalog-wide ceiling, shared round-robin across packs
# CCTV_PREFER_AUSTIN=1 # prioritize Austin cameras in the registry
# CCTV_FORCE_AUSTIN= # force live default sources when a source-pack JSON is configured
+
+# Optional: comma-separated Overpass URLs; use only instances you run or pay for.
+OVERPASS_UPSTREAMS=
+
+# Optional: who may frame the app in embed mode (?embed=1), as CSP
+# frame-ancestors sources such as "https://example.com", or * for any page.
+# Unset, no page may frame it. Other pages of the app can never be framed.
+# GEV_EMBED_FRAME_ANCESTORS=
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b886cbf74..3175d10b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
# Changelog
+## [Unreleased]
+
- Bound how often the GBFS proxy will relay. `/api/gbfs` already restricted
where it could go — https, an allowlisted host, a station feed path, no
redirects, a 12-second deadline and a 5 MB body cap — but nothing restricted
@@ -9,6 +11,182 @@
`Retry-After` before any outbound fetch. The bikeshare layer needs about 15 a
minute with every catalogued system in range, so ordinary use is unchanged.
+## [0.2.1] — 2026-10-02 — God's Eye View through Your Agent (with security fixes and other features)
+
+- Ship each bundled data pack once. The region, marine, admin-boundary,
+ county, military-name and neighborhood packs were emitted twice by the
+ production build, as the JSON the browser fetches and as an unused
+ JavaScript copy; `dist/` drops from 54 MB to 42 MB. Under Node the loader
+ reads the JSON file directly (reporting that Node 24.14 or newer is needed
+ on a runtime too old to do so), and a test keeps app code from importing a
+ pack as a module.
+
+- Apply the Host check before the app's own routes. Vite installs its Host
+ check after the middleware that plugins add, so the provider and `/api`
+ routes used to answer any Host, including a DNS-rebinding name, in every
+ mode. A first-running middleware now applies the same allowed hosts on the
+ dev and preview servers.
+
+- Refuse cross-site browser requests to the cost-bearing and log endpoints
+ (`/api/realtime/token`, `/api/openai/hud-summary`,
+ `/api/google/nearby-places`, `/api/google/text-search`,
+ `/api/realtime/debug-log`): a foreign or opaque
+ Origin, a cross-site `Sec-Fetch-Site`, or proxy forwarding headers get 403,
+ while loopback tools and LAN use keep working. The dev and preview servers
+ send one shared Content-Security-Policy (James Sumpter, #242).
+
+- The voice debug log records the server's own time: a record can no longer
+ supply its `loggedAt`. The dev and preview servers also send
+ `X-Content-Type-Options: nosniff` (findings by Sunil, #710).
+
+- Keep the Host-header check when binding to all interfaces. `HOST=0.0.0.0`
+ used to accept any Host; the dev and preview servers now accept IP
+ addresses, `localhost` and the LAN hostnames listed in `GEV_ALLOWED_HOSTS`
+ (suffix and wildcard entries are ignored), which also keeps DNS-rebinding
+ names out in LAN mode. The `.local` suffix is no longer accepted by default;
+ list such a name explicitly (Puspo Aditya, #97, fixes #21).
+
+- Throttle the cost-bearing proxies by default instead of on request. The
+ OpenAI endpoints (`/api/realtime/token`, `/api/openai/hud-summary`) now allow
+ 30 requests per minute per client IP and the Google Places endpoints
+ (`/api/google/nearby-places`, `/api/google/text-search`) 120 — the caps the
+ Pinokio build already ships, so the packaged app is unaffected and only an
+ unconfigured server changes, from unlimited to what the product already runs
+ with. `GEV_RATELIMIT_OPENAI_PER_MIN` and `GEV_RATELIMIT_GOOGLE_PER_MIN` still
+ override the caps, and exactly `0` disables them; a value that cannot be read
+ as a number now falls back to the default rather than to unlimited, so a typo
+ cannot silently disarm the guard (daikaginza, #683).
+
+- Tighten the local MCP server and embed mode. The panel's `panel_request`
+ needs the key in its MCP server's panel page, and refuses Provider
+ Settings, credential and model endpoints, `/mcp` and development server
+ routes in any letter case, encoding or dot suffix; panels on one server share six
+ requests in flight, and further requests wait their turn. `/mcp` applies Provider Settings' proxy and sharing checks,
+ requires a Host on the port it reached and an Origin from that host, and
+ times out request bodies after 30 seconds; stdio honors
+ `notifications/cancelled`. Embed mode is no longer framable unless
+ `GEV_EMBED_FRAME_ANCESTORS` allows the framing page, and view answers go
+ only to the origin that sent the view. SECURITY.md describes the MCP surface
+ and the panel's browser keys.
+
+- Cockpit now enters on the existing matching map style while keeping one fixed,
+ duplicate-free carousel over `Normal / CRT / NVG / FLIR / Anime / Noir / Snow`.
+ Normal remains a real unfiltered option; Cockpit-only choices still restore
+ the captured map style through both Exit Cockpit and Reset.
+- Cyber's compact right-rail and Cockpit utility buttons now center their glyphs
+ vertically and share the same inset and edge alignment.
+- Keep Cyber Voice help/error popups and Location/Visual Presets pins clear of
+ their decorative frames. Leave space above attribution for its full logo row.
+- Add an intentionally future-facing, opt-in panel surface contract for new
+ panels to inherit compatible Normal, Cyber and Cockpit styling. Its first
+ production adopter will land separately after this change.
+- Restore the user's previous visual preset when they explicitly switch from
+ Cyber to another HUD layout, without overriding scene or shared-link state.
+- Move the aligned Cyber side-panel rails upward on desktop so the left stack
+ clears the lower coordinate card; keep Cockpit's independent visor layout.
+
+## [0.2.0] — 2026-10-02 — God's Eye View through Your Agent
+
+- Show God's Eye View inside AI conversations. Answers that can be shown
+ include a view (camera, layers, style, map, marks, something to follow),
+ and `show_in_gods_eye_view` displays it as the live globe in clients that
+ support MCP Apps, such as Claude Desktop and the Codex and ChatGPT desktop
+ apps, and as a link everywhere else. The panel loads the app's panel build
+ (`npm run build:panel`) and its data through the MCP server, so a local
+ server needs no HTTPS or tunnel. `?embed=1` shows the globe alone for pages
+ that frame the app.
+
+- Add tools for language-model clients and a local MCP server, served over
+ stdio by `npm run mcp` and over HTTP at `/mcp` on the development and preview
+ servers for local requests only. `npm run mcp`
+ serves earthquake, active-fire, launch, aircraft, ship, satellite, camera, radio,
+ place, routing, bike-share, transit, traffic, weather, cyclone, fire-perimeter,
+ terrain, installation and map-feature queries, plus a situation brief, over
+ stdio to clients such as Claude Code, reading
+ from a running app. Tools are defined
+ once in `gods-eye-view/tools`, reuse the layers' source factories, and are
+ exposed through the protocol adapter in `gods-eye-view/tools/mcp`. The
+ catalog also covers weather maps, wind, recent imagery, submarine cables,
+ license plate reader cameras, datacenters and dams, the Bhote Koshi flood,
+ military awareness and links that open the app over an area with layers on.
+ Voice offers the same queries next to its app actions.
+
+- Public Overpass instances are no longer used by default. Street Traffic
+ roads come from TomTom flow tiles, OpenFreeMap vector tiles, or both, chosen
+ on the layer row (TomTom / OSM / Hybrid) or with `?trafficRoads=`. With a
+ TomTom key the default is Hybrid: TomTom roads with live flow, plus
+ OpenFreeMap roads TomTom does not cover, simulated. Without a key every
+ choice draws OpenFreeMap roads. TomTom mode shows only roads with flow; OSM
+ mode matches TomTom flow onto OpenFreeMap roads for congestion colors,
+ speeds and closures, and unmatched roads stay simulated. Road failures name
+ OpenFreeMap and retain HTTP status or timeout reasons separately from TomTom.
+ Flow coverage counts
+ rendered dots on roads with flow; mapped military areas come from OpenFreeMap and
+ follow settled camera changes with cached tiles and retained site entities.
+ Superseded requests preserve the next view’s pending load.
+ Tile and API transports can be injected independently; the optional Overpass
+ ALPR adapter remains available.
+ OSM-derived layers share one attribution entry and a persistent inline map credit,
+ including routing, Warendorf cameras, datacenters, dams and the Nepal locator.
+ Military areas use bundled Overture/OSM names; wide views show bounded,
+ decluttered named points that follow camera motion while Contacts holds a subject,
+ using the shared world-overlay host for wide-point and polygon titles. Polygons appear while names load; cancelled views
+ cannot publish late names, and stalled name downloads can retry. Merged groups
+ keep their identity and parent name when a pan hides the largest member. Late
+ names reconcile ids across zooms while retaining selection and marker ownership. Wide
+ named points remain visible until matching close polygons take over. Installation
+ titles share datacenter/dam card arbitration and keep incumbents while panning.
+ Selecting an installation replaces its label with one card; deselection restores it.
+ Selected footprints receive a translucent fill draped on the active map surface.
+ ALPR cameras come from an hourly OpenStreetMap extract (US and Canada). Features
+ without a replacement say they are unavailable; area and footprint
+ annotations check once whether an Overpass instance is configured and skip
+ the query when none is. Area annotations outline countries, states and provinces
+ (Natural Earth, including UK constituent countries) and US counties (US Census Bureau) from bundled data, with no
+ lookup. County names are disambiguated across countries by aliases, qualifiers
+ and geography. `OVERPASS_UPSTREAMS` sets an
+ Overpass instance you run or pay for. The cockpit regional brief resolves
+ regions from bundled Natural Earth data instead of Nominatim. Traffic starts on enable and paints
+ locally grounded roads incrementally, shares concurrent tile requests and
+ reuses validated heights across pans. Camera moves and arriving tiles retain
+ existing traffic dots on their roads; budget changes fade in/out incrementally,
+ height refinements ease vertically, and live flow changes color and speed in
+ place. The traffic footprint follows the reticle square at oblique angles, with
+ cached detailed near tiles and coarse distant roads. Failed detail tiles retain
+ coarse roads and retry at most three times. Road-pass deadlines also cancel
+ queued tile paints and terrain callbacks;
+ off-screen height corrections cannot hold loading open, and visible corrections
+ stay pixel-limited at canvas and camera-plane boundaries. Completed road snapshots replace
+ streamed previews without duplicating roads. Unresolved previews no longer delay or suppress detailed street roads.
+ Road admission excludes private, parking, walking and unknown traffic categories;
+ OpenStreetMap service ways and tunnels no longer receive surface vehicles.
+ Detail tiles load alongside previews, arrival bypasses the movement debounce,
+ and geographically validated rendered depth avoids repeated mesh redraws.
+ Traffic releases surface-frame listeners on disable/destroy and reuses unchanged
+ surface identities without frame-by-frame allocations.
+ Grounded dots respect building occlusion instead of drawing through roofs,
+ and the traffic status chip reports completion without a delayed text animation.
+ Short road chunks and local surface elevation keep the budget on visible
+ streets in elevated cities. Congestion and closures respect travel
+ direction. ALPR distinguishes unsupported coverage from empty results and
+ asks for zoom-in before exceeding its tile budget. Installation footprints
+ retain visible fragments without joining separate parcels across zooms or
+ across quantization gaps between different mapped identities. Keyless
+ terrain tiles retry HTTP 429 and transient gateway failures with bounded,
+ shared backoff. Operator-configured Overpass area and footprint queries
+ retain relation member geometry so their outlines remain available.
+ Contacts filters mapped installations to a true 100 km surface radius around
+ the current tracked subject,
+ including in Cockpit, instead of "?" or nothing. Search arrivals frame against the resolved
+ ground and lift the eye above the rendered surface, so Camp Mabry and Denver
+ no longer land underground (Milan Khanal, #118). Arrival corrections yield to
+ new camera owners and are cancelled on layer/app teardown. ALPR floor preparation
+ and marker placement share terrain requests, cancelled when the layer is disabled.
+ Map-source changes reposition every ALPR marker without replacing entities. ALPR loads
+ whole-city views from z9-z12 tiles, drops native ground wedges and clamping,
+ keeps frame time with ALPR on within a few percent of ALPR off, seats nearby
+ badges on the rendered surface and says when no loaded camera is on screen.
+
- CCTV cameras whose bearing is a guess now say so. Packs mark bearings derived
from a hash of the camera id as `headingConfidence: 'low'`, but nothing read the
flag, so roughly 70% of a default catalog rendered like surveyed facings. The HUD
@@ -115,7 +293,7 @@
(`src/ui/imagerySplit.js`, `src/maps/imageryComparison.js`), and
`MapSourceController.subscribe()` reports every settled map switch.
-## Unreleased — local receiver feeds
+### Local receiver feeds
- The Local ADS-B layer also reads local 1090 MHz and 978 MHz UAT decoder
feeds: the `aircraft.json` that dump1090-fa, readsb, tar1090 or skyaware978
@@ -144,7 +322,7 @@
- Records carry `band` (`1090`/`978`) and `source` (`webusb`/`feed`).
- See `docs/LOCAL-RECEIVERS.md`.
-## Unreleased — local RTL-SDR and Local ADS-B
+### Local RTL-SDR and Local ADS-B
- Add a Local RTL-SDR card to the Radio panel. It connects a USB RTL-SDR in
desktop Chrome or Edge through WebUSB and receives broadcast FM (tune, seek,
@@ -169,7 +347,7 @@
- Add `@jtarrio/webrtlsdr` and `@jtarrio/signals` (Apache-2.0); see
`THIRD_PARTY_NOTICES.md`.
-## Unreleased — weather review
+### Weather review
- On 3D Tiles, draw a 4096×2048 detail window around the view on each
observed-weather shell except global infrared, sampled by the shell's own
@@ -435,26 +613,26 @@ Analyst records for loaded satellites, datacenters and dams, with explicit bound
- Split application scene, controls, catalog, tools and HTML into reusable components; configure application request services and sources without changing global fetch. Preserve standalone markup and voice behavior. Explicit annotation navigation may resolve a distant named target.
-## Satellite pass prediction
+### Satellite pass prediction
- Bisect pass rise/set to ~0.2 s and fit peak elevation with a parabola.
- Mark passes visible from Earth-shadow and civil-twilight checks.
- Add `getNextSatellitePass(noradId, options)` for any loaded catalog satellite.
- `next_iss_pass` retains the next geometric pass and adds visibility metadata. `next_satellite_pass` adds bounded loaded-catalog name/NORAD lookup and optional visible-only filtering (Rehaan Delmotra, #451; maintainer adaptation).
-## Voice component boundaries
+### Voice component boundaries
- Separate voice controls, Realtime connection requests and the action runner.
- Allow compatible endpoints and server-selected models through construction options.
- Cancel pending token/SDP requests on Stop or teardown and reject expired secrets.
-## Configurable geospatial services
+### Configurable geospatial services
- Compose geocoding, place context and routes through independent providers.
- Allow compatible endpoint configuration without changing voice tools or annotation behavior.
- Isolate configured source caches and reject results after cancellation.
-## ALPR camera locations
+### ALPR camera locations
- Port Manjunath's (@manjunath22466) cyan camera badges, coral selection brackets,
gradient direction wedges and animated tactical labels into the reusable ALPR
@@ -474,32 +652,32 @@ Analyst records for loaded satellites, datacenters and dams, with explicit bound
- Separate the request adapter, camera model, presentation, and instance lifecycle.
Source cancellation also guards late response bodies and rejects invalid query bounds.
-## Release disabled infrastructure rendering
+### Release disabled infrastructure rendering
- Remove built Data Center, Dam and Submarine Cable entities when their layers
are disabled, avoiding retained visualizer work and entity memory.
- Keep parsed datasets cached for re-enable; rebuild entities without refetching.
-## Camera layer components
+### Camera layer components
- Separate camera source requests, placement, frames, projection, cards and calibration.
- Own visibility listeners and pending initialization within each layer lifetime.
- Preserve existing camera catalogs, URL families, geometry and playback behavior.
-## Traffic and bikeshare components
+### Traffic and bikeshare components
- Separate traffic loading, animation, styling and lifecycle into factory-owned components.
- Give each flow source its own bounded decode cache and cancellation checks.
- Separate bikeshare registry, station requests, rendering, selection and proximity handling.
-## Installation and context components
+### Installation and context components
- Separate mapped-site requests, records, placement, selection and viewport lifecycle.
- Separate proximity queries, subject tracking, navigation/history, panel and direction rendering.
- Retain source and ground-floor ownership in standalone composition; reject malformed
installation snapshots and ignore failures from cancelled requests.
-## Satellite and mission layer components
+### Satellite and mission layer components
- Separate catalog loading, orbit calculations, display, tracking and interaction
into instance-owned satellite components.
@@ -507,20 +685,20 @@ Analyst records for loaded satellites, datacenters and dams, with explicit bound
operations, retaining existing layer controls and satellite coordination.
- Cancel late mission source work and reject malformed launch snapshots.
-## Fire layer components
+### Fire layer components
- Split fire source loading, state, rendering, cards, selection and viewport work
into reusable components with application-owned scene services.
- Cancel late refreshes, retain good data after malformed responses, and preserve
selection identity without repeating a user-selection notification on refresh.
-## Earthquake components
+### Earthquake components
- Separate earthquake snapshot loading, record validation, and display ownership.
- Cancel pending earthquake refreshes on disable or destruction, retaining the
last good snapshot after malformed or failed refreshes.
-## September 8, 2026
+### September 8, 2026
Earthquake refreshes validate the complete feed and construct replacement entities before clearing the previous snapshot. Malformed rows and duplicate rendered IDs retain the last good entities, overlays, count and timestamp and report a malformed response; unknown magnitude is excluded from M2.5+ rendering.
@@ -531,7 +709,8 @@ Launch payloads with missing records now say PAYLOAD DATA UNAVAILABLE. Missing n
This changelog records public product changes. For the authoritative description
of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md).
-## [Unreleased]
+### Other changes
+
- Add ECMWF IFS model selection to Wind (#464, thanks @beneduzi), with model-scoped forecast-step caches, cancellation of replaced requests, and separate issue/valid timestamps.
@@ -551,7 +730,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
- Separate submarine cable sources and rendering components, and export bundled geography lookup modules.
-### Added
+#### Added
- DISPLAY ▸ Draw: draw on the world by hand. Pick Area, Line or Pin, click the
vertices, double-click or press Enter to finish, label and colour it; Backspace
@@ -561,7 +740,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
together. While you are drawing, the draw tool owns the pointer and no layer
selects what you click through (#235 — thanks @cora-fresh-labs).
-### Fixed
+#### Fixed
- Keep traffic-road bounds crossing the antimeridian monotonic and inside the
longitude range accepted by the Overpass request path, preserving the small
@@ -628,7 +807,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
toggle. Camera departure cancels pending work, arrival checks the final view,
and superseded requests cannot keep a newer view loading.
-### Added
+#### Added
- Add MODIS NRT (Terra+Aqua, ~1 km) active fires to the FIRMS layer, sharing the
existing `FIRMS_MAP_KEY` and 30-minute cache.
@@ -756,9 +935,9 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
- Extract shared surface keyboard handling for the welcome launcher and Provider
Settings, preserving Tab/Escape behavior and releasing the listener on teardown.
-### Added
+#### Added
-### Security
+#### Security
- The CCTV media route no longer forwards a client `Range` header to the upstream
camera host as it arrived. A single `bytes=` range is canonicalized and
@@ -789,7 +968,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
- Cancel the active location lookup when its controls are disposed.
-### Fixed
+#### Fixed
- `DATA_SOURCES.md` states what the project does with camera frame content: a
successful upstream response is relayed as the provider served it, nothing in
@@ -868,7 +1047,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
fetching, AIS records/tracks and shared request helpers; preserve existing
routes, local setup, fallback behavior and rendering.
-### Added
+#### Added
- **Directions layer** — keyless A→B directions without a geocoder or a
microphone (thanks @spcpza). The row's chips arm a globe click for A and B
@@ -896,7 +1075,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
voice routes since launch, previously uncredited), with the OpenStreetMap
credit and the "fix the map" link the service's usage policy asks for.
-### Changed
+#### Changed
- The interface asks Google Fonts for only the icon glyphs it draws, instead of
the whole variable icon font, and no longer requests a second icon family that
@@ -911,7 +1090,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
- Rename standalone browser startup to `src/standalone/` and add a Node-only
`gods-eye-view/build/vite` export with checked package ownership.
-### Development
+#### Development
- The CCTV launcher and preview-server tests resolve their temporary fixture
root through `fs.realpath`, so they pass on macOS, where the system temp
@@ -945,7 +1124,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
infrastructure modules and their consumer tests. Package boundary checks keep
those exports separate from app startup and local Node services.
-### Fixed
+#### Fixed
- Reduce terrain-height timeouts when Re:Earth slows down. Batches are
sized against measured response latency on both browser and server to reduce
@@ -1036,7 +1215,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
and upstream error bodies; response statuses and cache fallback remain intact.
Includes the security fixes contributed by Tom-Neverwinter in PR #171.
-### Fixed
+#### Fixed
- Map Source keyboard opening retries focus until the selected tile is visible.
Leaving the disclosure, pointer interaction, or closing the tray cancels the
@@ -1078,7 +1257,7 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
that had since recovered. A miss is now cached only when every source
consulted actually returned a verdict.
-### Added
+#### Added
- Keyless place search. The LOCATION search box and the `fly_to_location` voice
tool now resolve place names through Photon (komoot, over OpenStreetMap) when
@@ -1098,6 +1277,13 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
to Puppeteer 25.10.0 and Sharp 0.35.4. Cesium remains on 1.138.0.
Browser QA awaits the new asynchronous executable-path lookup.
+### Live CCTV video
+
+- Live HLS video shares one decoder between the camera panel and projection,
+ with a DelDOT HTTPS source pack. Credit: Daniel Slay (@Danielslay86), PR #489.
+- Maintainer adjustments bound sessions and downloads, remove disk/subprocess
+ remuxing, reject redirects, and clean up playback on switching or disabling.
+
## [0.1.1] — 2026-09-01 — Installation and live-data fixes
### Changed
@@ -1335,9 +1521,3 @@ represent previously published GitHub Releases.
- Initial project version.
-### Live CCTV integration candidate
-
-- Live HLS video shares one decoder between the camera panel and projection,
- with a DelDOT HTTPS source pack. Credit: Daniel Slay (@Danielslay86), PR #489.
-- Maintainer adjustments bound sessions and downloads, remove disk/subprocess
- remuxing, reject redirects, and clean up playback on switching or disabling.
diff --git a/DATA_SOURCES.md b/DATA_SOURCES.md
index b1439230e..ab059cebb 100644
--- a/DATA_SOURCES.md
+++ b/DATA_SOURCES.md
@@ -20,7 +20,7 @@ How to read this:
| **NOAA nowCOAST observed weather** (`nowcoast.noaa.gov/geoserver/observations/{weather_radar,satellite}/ows`, WMS 1.1.1) | Rain radar (MRMS base reflectivity, contiguous US, about 4-minute updates) and Satellite clouds (GOES-19/18 Band 14 regional infrared, about 5-minute updates; global infrared mosaic, hourly). Keyless; layer metadata refreshed every 2 minutes, exact-time images cached for up to 24 hours | [NOAA disclaimer](https://oceanservice.noaa.gov/disclaimer.html) | "NOAA nowCOAST · NWS/OAR MRMS radar; NESDIS GOES and global satellite partners" |
| **NOAA nowCOAST lightning density** (`nowcoast.noaa.gov/geoserver/observations/lightning_detection/ows`) | Lightning density: 15-minute accumulated strike density on an approximately 8 km grid, Americas and Pacific. Keyless; layer metadata refreshed every 10 minutes, exact-time images cached for up to 24 hours | NOAA Level-5 derived product from Vaisala NLDN/GLD360; the [product description](https://ocean.weather.gov/lightning/lightning_pdd.php) permits public distribution of this density product, not of raw Vaisala detections | "NOAA/NWS nowCOAST; derived from Vaisala NLDN/GLD360" |
| **NOAA NHC / CPHC advisories** (`www.nhc.noaa.gov/CurrentStorms.json` + the `mapservices.weather.noaa.gov` tropical weather summary MapServer) | Cyclone advisories: storm positions, forecast tracks, points and cones for the Atlantic and eastern/central North Pacific. Keyless through `/api/cyclones`; cached for 5 minutes | [NWS public-data terms](https://www.weather.gov/disclaimer); no endorsement implied | "NOAA/NWS NHC / CPHC" |
-| **OpenStreetMap ALPR camera locations** (including DeFlock community mapping) | Optional mapped automatic license-plate-reader camera layer | [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/); commercial use permitted with applicable attribution and database share-alike obligations | [© OpenStreetMap contributors](https://www.openstreetmap.org/copyright); [DeFlock](https://deflock.org) community mapping |
+| **OpenStreetMap** | Map and place data (roads, military areas and names, ALPR, place search, outlines); via vector tiles, hourly ALPR extract, bundled data, Nominatim and optional self-configured Overpass | [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/) | © OpenStreetMap contributors |
| **Google Map Tiles API** (Photorealistic 3D Tiles) + Places/Geocoding | The 3D globe, voice scene context, and on-demand nearby installation search | Google Maps Platform ToS (proprietary, your own key + billing) | "Google" / "Google Maps" logo — **shown in-app**, required |
| **OpenSky Network** | Primary worldwide live-flight snapshot | Non-commercial research/education license | Schäfer et al., _"Bringing Up OpenSky"_, IPSN 2014 + opensky-network.org |
| **adsb.lol point API** | Bounded live-flight fallback when OpenSky has no usable snapshot | ODbL 1.0 | adsb.lol contributors; `api.adsb.lol/v2/lat/{lat}/lon/{lon}/dist/{radius}` |
@@ -34,11 +34,10 @@ How to read this:
| **NASA GIBS** (WMTS tiles + Worldview Snapshots) and **NASA CMR** (granule catalog) | Recent Imagery layer: HLS S30/L30 (Sentinel-2, Landsat 8/9) and VIIRS true-colour tiles draped on the map, per-day thumbnails and PNG exports; the HLS granule catalog (dates, cloud, footprints) for a selected box | U.S. public domain (NASA data); keyless, browser-direct (CORS `*`); no bulk use — one 30-day catalog query per box, tiles bounded to the box, six tile requests in flight | "We acknowledge the use of imagery provided by services from NASA's Global Imagery Browse Services (GIBS), part of NASA's Earth Science Data and Information System (ESDIS)." — [gibs.earthdata.nasa.gov](https://gibs.earthdata.nasa.gov), [HLS](https://lpdaac.usgs.gov/products/hlss30v002/) |
| **NIFC WFIGS** (Wildland Fire Interagency Geospatial Services, ArcGIS feature service) | Fire Perimeters layer: current interagency wildfire incident perimeters, containment, and incident facts | U.S. public domain (interagency wildland-fire data published through the [NIFC Open Data portal](https://data-nifc.opendata.arcgis.com/)); fetched keyless through `/api/fire-perimeters` (5-minute server cache) with `exceededTransferLimit` paging | "Wildfire perimeters: National Interagency Fire Center (WFIGS)" |
| **InciWeb** (inciweb.wildfire.gov) | Per-incident "InciWeb ↗" card links to official incident pages (narratives, evacuation/closure notices) | U.S. government public incident information. Links are matched best-effort by incident name and state, with incident page origin and update times checked on selection. Same-origin `/api/fire-perimeters/inciweb` routes cache the catalog for 1 hour and publication metadata for 30 minutes; linked pages remain InciWeb content | "Incident information: InciWeb (inciweb.wildfire.gov)" |
-| **OpenStreetMap (Overpass API)** | Road geometry for traffic | ODbL 1.0 | "© OpenStreetMap contributors" |
-| **TomTom Traffic API** (flow vector tiles) | Live congestion coloring for the traffic layer (optional, BYOK) | [TomTom for Developers terms](https://developer.tomtom.com) (proprietary, your own key; free tier currently 200K tile requests/month — see [current pricing](https://docs.tomtom.com/pricing/)) | "Traffic flow data © TomTom" — registered when live mode activates |
-| **OpenStreetMap (Overpass API)** | Viewport-bounded mapped installation context for Global Context | ODbL 1.0 | "© OpenStreetMap contributors" (incomplete mapped context) |
-| **Photon** (komoot) | Keyless place search — the fallback when no Google Maps key is configured, or when Google declines the Geocoding request | Service: free public instance, [fair use](https://photon.komoot.io) (no bulk/heavy use); underlying data: **ODbL 1.0** | "Photon (komoot)" + "© OpenStreetMap contributors" |
-| **OpenStreetMap (Nominatim)** | Reverse-geocoded place label in the cockpit Local Info page, and last-resort forward place search via `/api/geocode` when Google and Photon do not answer | ODbL 1.0 + [Nominatim usage policy](https://operations.osmfoundation.org/policies/nominatim/) (maximum 1 request/second, results cached, identifying User-Agent) | "© OpenStreetMap contributors" |
+| **OpenFreeMap / OpenMapTiles** | Vector tiles | OpenStreetMap data under ODbL 1.0 | OpenFreeMap © OpenMapTiles |
+| **Overture Maps Foundation** | Military area names pack | [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/) | Overture Maps Foundation |
+| **TomTom Traffic API** (flow vector tiles) | Traffic roads and live congestion (optional, BYOK) | [TomTom for Developers terms](https://developer.tomtom.com) (proprietary, your own key; free tier currently 200K tile requests/month — see [current pricing](https://docs.tomtom.com/pricing/)) | "Traffic flow data © TomTom" — registered when live mode activates |
+| **Photon** (komoot) | Keyless place search — the fallback when no Google Maps key is configured, or when Google declines the Geocoding request | Service: free public instance, [fair use](https://photon.komoot.io) (no bulk/heavy use); underlying data: **ODbL 1.0** | "Photon (komoot)" |
| **Open-Meteo** | Current weather in the cockpit Local Info page and cockpit-local dynamic atmospheric effects | [CC BY 4.0 data licence and adjacent-link attribution requirement](https://open-meteo.com/en/licence) | Linked "Weather data by Open-Meteo.com" beside the displayed local data |
| **Google News RSS** | Primary locality-matched headlines in the cockpit Regional News page | [Google News Terms of Service](https://www.google.com/intl/en_us/terms_google_news.html) restrict use to personal, noncommercial use; linked articles remain third-party publisher content and retain publisher terms | "Google News RSS" plus each article's linked publisher/domain |
| **GDELT Project DOC 2.0** | Fail-soft fallback for location-matched cockpit headlines | [GDELT Terms of Use](https://www.gdeltproject.org/about.html#termsofuse): unrestricted academic/commercial/governmental dataset use, with citation and link required; linked articles retain publisher terms | "GDELT Project" plus each article's linked publisher/domain |
@@ -46,7 +45,7 @@ How to read this:
| **TxDOT ITS (its.txdot.gov)** | CCTV camera catalogs + frames, Texas districts | Public TxDOT traffic camera data | "Texas Department of Transportation" (courtesy) |
| **City of Tallinn (ristmikud.tallinn.ee)** | CCTV camera catalog + frames, Tallinn intersections | Public City of Tallinn traffic camera data | "City of Tallinn — ristmikud.tallinn.ee" (courtesy) |
| **Transpordiamet / Tarktee** | CCTV camera catalog + frames, Estonia road-weather cameras | Public Transpordiamet / Tarktee road camera data | "Transpordiamet / Tarktee — tarktee.transpordiamet.ee" (courtesy) |
-| **Stadt Warendorf webcam** | Webcam frames, Marktplatz / Historisches Rathaus (`config/cctv_sources.warendorf.json`) | Public municipal webcam data | "Stadt Warendorf — webcam.warendorf.de" (courtesy); mount and heading derived from OSM geometry, "© OpenStreetMap contributors" (ODbL) |
+| **Stadt Warendorf webcam** | Webcam frames, Marktplatz / Historisches Rathaus (`config/cctv_sources.warendorf.json`) | Public municipal webcam data | "Stadt Warendorf — webcam.warendorf.de" (courtesy) |
| **Live Traffic NSW (Transport for NSW)** | CCTV camera catalog + frames, New South Wales | [CC BY 4.0](https://opendata.transport.nsw.gov.au/) — attribution REQUIRED | "Live Traffic NSW — Transport for NSW" |
| **Caltrans (cwwp2.dot.ca.gov)** | CCTV camera catalogs + frames, California districts | Public Caltrans traffic camera data | "Caltrans — cwwp2.dot.ca.gov" (courtesy) |
| **DelDOT (video.deldot.gov)** | CCTV camera catalog (`tmc.deldot.gov/json/videocamera.json`, cached for 15 minutes) + live HTTPS HLS video, Delaware. Keyless | Public DelDOT traffic camera streams via the [DelDOT traffic map](https://deldot.gov/map/) | "DelDOT — Delaware Department of Transportation" (courtesy) |
@@ -65,7 +64,7 @@ How to read this:
| **HSL** (realtime.hsl.fi) | Transit layer live vehicles, Helsinki | HSL (Helsinki Region Transport) open realtime vehicle data, used for the live view; published for developer use and not against their use. | Attribution to HSL (Helsinki Region Transport) (courtesy text) |
| **Radio Browser** | Geolocated internet-radio station directory and station-level tags | Public-domain directory data under PDDL 1.0; individual broadcaster stream terms apply | "Radio Browser" plus a link to the selected broadcaster |
| **Re:Earth Terrain** (Mapterhorn) | Terrain (keyless globe stacks — OSM etc. — + `/api/terrain/heights` ellipsoidal-height lookups) | Terrain mesh: CC BY 4.0; geoid: EGM2008 (NGA, public domain) | "Terrain (keyless globe stacks): Re:Earth Terrain / Mapterhorn (CC BY 4.0) / EGM2008 (NGA)" |
-| **OSRM on the FOSSGIS routing servers** (`routing.openstreetmap.de`) | Street-following routes for the Directions layer and voice route annotations, via `/api/route` | [FOSSGIS routing usage policy](https://routing.openstreetmap.de/about.html): "Display the required attribution and display a link to 'fix the map'", "Use a valid user agent and, if applicable, a correct referrer", "One request per second max", "No scraping, no heavy usage". The full policy is the German [FOSSGIS Nutzungsbedingungen](https://www.fossgis.de/arbeitsgruppen/osm-server/nutzungsbedingungen/); FOSSGIS also states that the server may be embedded in your own pages but "eine gewerbliche Nutzung ist nur mit Einschränkungen erlaubt" (commercial use only with restrictions). Route data derives from OpenStreetMap (ODbL 1.0) | "Routing: OSRM on the FOSSGIS servers" + "© OpenStreetMap contributors" + a "fix the map" link — shown in the Data attribution popover |
+| **OSRM on the FOSSGIS routing servers** (`routing.openstreetmap.de`) | Street-following routes for the Directions layer and voice route annotations, via `/api/route` | [FOSSGIS routing usage policy](https://routing.openstreetmap.de/about.html): "Display the required attribution and display a link to 'fix the map'", "Use a valid user agent and, if applicable, a correct referrer", "One request per second max", "No scraping, no heavy usage". The full policy is the German [FOSSGIS Nutzungsbedingungen](https://www.fossgis.de/arbeitsgruppen/osm-server/nutzungsbedingungen/); FOSSGIS also states that the server may be embedded in your own pages but "eine gewerbliche Nutzung ist nur mit Einschränkungen erlaubt" (commercial use only with restrictions). Route data derives from OpenStreetMap (ODbL 1.0) | "Routing: OSRM on the FOSSGIS servers" + a "fix the map" link — shown in the Data attribution popover |
### Local receiver input (not fetched from a service)
@@ -96,9 +95,9 @@ The optional Local ADS-B layer and the broadcast-FM receiver use radio you recei
- **Routing (OSRM / FOSSGIS).** `/api/route` proxies the public OSRM instances FOSSGIS e.V. runs for the OpenStreetMap community (car, bike, foot profiles). They are keyless and intended for fair, interactive use — the proxy rate-limits clients (60/min, 200/min total), caps route span and response size, caches each route for 10 minutes, and identifies itself in the User-Agent. Heavy or automated use must run its own OSRM. Routes are computed from OpenStreetMap data (ODbL); the Directions layer and voice routes never redistribute them. A route the router cannot find is reported as such; the Directions layer draws nothing in that case, and voice routes fall back to a clearly labeled straight line.
- **Re:Earth Terrain.** Keyless (no API key). Used two ways: (1) `src/mapStackController.js` swaps in a `Cesium.CesiumTerrainProvider` pointed at Re:Earth's `cesium-mesh/ellipsoid` quantized-mesh endpoint for globe stacks without a Cesium ion token (e.g. OSM), replacing a flat `EllipsoidTerrainProvider`; falls back to the flat provider if the endpoint can't be reached. (2) The server-side `/api/terrain/heights` proxy (disk-cached, serve-stale) resolves per-point ellipsoidal ground height for entity placement. Both are best-effort with a keyless-safe fallback (bundled EGM96 geoid math) if Re:Earth is unreachable.
- **OSRM on the FOSSGIS routing servers.** Keyless (no API key). Every route is requested by a person: clicking A and B in the Directions layer, or a voice "route from A to B". The server-side `/api/route` proxy sends an identifying `User-Agent`, pins the upstream host, accepts 2-12 coordinates, rejects any leg over 600 km or a total over 2,500 km, caps the buffered response at 8 MB, times the upstream out at 12 s, rate-limits per client, caches each route for 10 minutes, and coalesces identical requests that are in flight at the same time. Turn-by-turn maneuvers are asked for only when a caller wants them (`steps=1`), so the callers that do not read them send the smaller request. The "fix the map" link the policy asks for is in the Data attribution popover beside the OpenStreetMap credit.
-- **Global Context installation context.** `/api/military-installations` queries only an allow-listed subset of OSM `military=*` and `landuse=military` features inside a maximum 10° non-dateline viewport. It caches and may serve stale mapped context, but it is neither a global installation database nor evidence of capability, activity, or absence. User-requested Google Places results remain separately sourced candidates unless their returned types explicitly establish military classification; generic offices, museums, and similarly ambiguous matches are excluded from military proximity counts.
-- **Place search.** Named-place fly-to uses Google Geocoding when a Maps key is configured, then Photon's public instance, then `/api/geocode` (Nominatim search) when neither answers. `/api/geocode` shares the cockpit's Nominatim queue at no more than one request per second, sends an identifying `User-Agent` and `Referer`, caches answers for five minutes, shares one upstream call between identical searches already in flight, bounds the queue so a burst is refused rather than held, and drops a queued search whose caller has given up. It asks for one result per search and is not used for systematic or bulk queries.
-- **Cockpit regional briefing.** `/api/regional-brief` rounds aircraft coordinates into 0.1° cache cells, caches results for five minutes, and serializes Nominatim calls at no more than one request per second. Google News RSS is queried with the resolved locality/region first; GDELT is used only when that RSS query fails or is empty. Google's published Google News terms restrict that source to personal, noncommercial use, so commercial deployments must disable/replace it or obtain separate permission; GDELT permits commercial dataset use with citation. The Data attribution popover identifies the active headline sources; article links retain publisher attribution. Headlines are location-query matches, not verified incidents, risk rankings, or evidence that a location is safe. Empty, partial, stale, and unavailable source states remain distinct. Open-Meteo supplies current conditions independently of the news source. `WX OFF` disables cockpit weather rendering only; the Local Info briefing still fetches its source-backed weather values and displays the required linked Open-Meteo credit.
+- **Mapped installations.** OpenFreeMap military polygons use a bundled Overture/OSM name index; wide views show named points. Optional operator-configured Overpass and explicit Google Places searches keep their own source paths. Mapped data does not establish current activity or complete coverage.
+- **Place search.** Named-place fly-to uses Google Geocoding when a Maps key is configured, then Photon's public instance, then `/api/geocode` (Nominatim search) when neither answers. `/api/geocode` uses the shared Nominatim forward-search queue at no more than one request per second, sends an identifying `User-Agent` and `Referer`, caches answers for five minutes, shares one upstream call between identical searches already in flight, bounds the queue so a burst is refused rather than held, and drops a queued search whose caller has given up. It asks for one result per search and is not used for systematic or bulk queries.
+- **Cockpit regional briefing.** `/api/regional-brief` rounds aircraft coordinates into 0.1° cache cells, caches results for five minutes, and resolves physical regions from bundled public-domain Natural Earth polygons without reverse-geocoding requests. Google News RSS is queried with the resolved locality/region first; GDELT is used only when that RSS query fails or is empty. Google's published Google News terms restrict that source to personal, noncommercial use, so commercial deployments must disable/replace it or obtain separate permission; GDELT permits commercial dataset use with citation. The Data attribution popover identifies the active headline sources; article links retain publisher attribution. Headlines are location-query matches, not verified incidents, risk rankings, or evidence that a location is safe. Empty, partial, stale, and unavailable source states remain distinct. Open-Meteo supplies current conditions independently of the news source. `WX OFF` disables cockpit weather rendering only; the Local Info briefing still fetches its source-backed weather values and displays the required linked Open-Meteo credit.
- **Dynamic weather presentation.** While cockpit mode is active, `/api/weather-effects` requests current Open-Meteo observations for the aircraft/camera location, rounds coordinates into 0.1° cache cells, caches results for five minutes, and may retain a stale observation for up to 30 minutes during a transient outage. WMO condition code selects the visual family; observed cloud cover, precipitation, visibility, wind speed, and wind direction bound its strength and motion. Missing or expired weather renders no synthetic atmospheric effect, and normal globe view never renders the weather overlay.
---
@@ -111,11 +110,14 @@ The [Bhote Koshi event pack](public/events/bhote-koshi-2026/README.md), under `p
| Dataset | Folder | License | Commercial use? | Attribution |
| --------------------------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------- |
-| **Datacenters** (~4.3K) | `datacenters/` | **ODbL 1.0** (OpenStreetMap extract) | ✅ (attribution + share-alike on data) | "© OpenStreetMap contributors" |
-| **Dams** (704) | `dams/` | **ODbL 1.0** (OpenInfraMap / OSM extract) | ✅ (attribution + share-alike on data) | "© OpenStreetMap contributors" (+ Open Infrastructure Map) |
+| **Datacenters** (4,351) | `datacenters/` | **ODbL 1.0** (OpenStreetMap extract) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) |
+| **Dams** (704) | `dams/` | **ODbL 1.0** (OpenInfraMap / OSM extract) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) + Open Infrastructure Map |
+| **Military area names** (36,466) | `osm_military_names/` | **ODbL 1.0** (OpenStreetMap, via Overture Maps) | ✅ (attribution + share-alike on data) | © OpenStreetMap contributors (shared credit) + Overture Maps Foundation |
| **TeleGeography Submarine Cable Map** (712 cables + 1,917 landing points) | `telegeography_submarine_cables/` | **CC BY-NC-SA 3.0** | ❌ **NonCommercial — remove for commercial use** | "© TeleGeography — submarinecablemap.com" |
| **Natural Earth physical regions** (1,046 land + 292 marine named polygons) | `natural_earth/` | **Public domain** | ✅ (no restrictions) | "Made with Natural Earth" (courtesy credit — not legally required) |
| **DataSF Analysis Neighborhoods** (41 SF neighborhood polygons) | `neighborhoods/` | **PDDL 1.0** (public domain) | ✅ (no restrictions) | "City & County of San Francisco — DataSF" (courtesy — not legally required) |
+| **Natural Earth countries, states and provinces** (260 admin-0/map units + 4,587 admin-1 polygons) | `natural_earth/` | **Public domain** | ✅ (no restrictions) | "Made with Natural Earth" (courtesy credit — not legally required) |
+| **US Census Bureau counties** (3,235 county polygons) | `us_census_counties/` | **Public domain** | ✅ (no restrictions) | "U.S. Census Bureau" (courtesy — not legally required) |
| **CCTV ground heights** (3,445 cameras) | `cctv_ground_heights/` | Precomputed camera placement heights, aligned to work with Google Photorealistic 3D Tiles (folder README) | — | — |
### ⚠️ TeleGeography is bundled but NonCommercial
@@ -128,33 +130,23 @@ The richer structured dataset is licensed separately/commercially by TeleGeograp
### ALPR camera mapping
-The optional ALPR layer queries OpenStreetMap nodes tagged `man_made=surveillance`
-and `surveillance:type=ALPR` (including case-insensitive semicolon multi-values)
-through the shared Overpass proxy. Source/tag reference:
-[OSM ALPR tagging](https://wiki.openstreetmap.org/wiki/Tag:surveillance:type%3DALPR).
-No DeFlock software, branded assets or separate database is bundled. The layer
-shows contributor-supplied locations and tags, not plate records, camera video,
-current operating status, or exhaustive coverage. All manufacturers share the
-ALPR category and marker color. Manufacturer, operator and camera type are shown
-only when tagged, not inferred or independently verified. Every selected card
-names OpenStreetMap in a smaller, muted source line beneath those details.
+The optional ALPR layer reads OpenStreetMap `surveillance:type=ALPR` nodes
+from a community-hosted hourly US/Canada extract, using detail vector tiles.
+It shows mapped locations and available tags, not plate records, camera video,
+current operating status or exhaustive coverage. OSM edit timestamps are not
+presented as field-verification dates. Other regions show no ALPR data.
-Queries are limited to a city-scale viewport (at most 3° per axis), at most
-1,500 returned nodes, with outward-snapped query reuse and explicit stale/limited
-coverage notices. A linked © OpenStreetMap credit appears for five
-seconds when camera locations first display after enabling, then collapses;
-Data attribution retains the full source/license entry, following the
-[OSMF interactive-map guidance](https://osmfoundation.org/wiki/Licence/Attribution_Guidelines).
-OSM data remains separately attributed under ODbL; querying
-at runtime does not replace the license obligations. ODbL permits commercial
-use and does not license the application code. If publicly using a derivative
-database, follow its share-alike and access requirements; keep independent
-datasets separate rather than treating the whole collection as MIT data.
-Overpass hosting capacity and usage policies are separate from the data license.
+Reads are viewport-bounded, with at most 16 tiles per country, four concurrent
+reads per country, 1,500 displayed records and an hour-long decoded-tile cache.
+Displayed OSM-derived layers retain a short inline OpenStreetMap credit, plus
+OpenMapTiles while OpenFreeMap tiles are used; Data attribution retains the full credits. ODbL permits
+commercial use with attribution and applicable database share-alike/access
+obligations; it does not license the application code. Trimmed test-only tile
+fixtures retain their provenance in `src/data/fixtures/README.md`.
-### ODbL share-alike (datacenters, dams)
+### Bundled OpenStreetMap data
-The OSM-derived datasets are under the **Open Database License**. ODbL's share-alike applies to the **data / derived database, not this MIT-licensed code** — the two coexist (exactly how Open Infrastructure Map ships: MIT software + ODbL data). If you publicly distribute a _modified_ version of these databases, you must offer it under ODbL. Keep the "© OpenStreetMap contributors" notice (link: https://www.openstreetmap.org/copyright).
+Bundled OSM-derived data is distributed under [ODbL 1.0](https://opendatacommons.org/licenses/odbl/1-0/), separately from the MIT code: [datacenters](src/data/local_data/datacenters/), [dams](src/data/local_data/dams/) (via Open Infrastructure Map), [military names](src/data/local_data/osm_military_names/), and [OSM test fixtures](src/data/fixtures/). Keep © OpenStreetMap contributors and offer modified databases under ODbL.
### NASA FIRMS acknowledgement
diff --git a/README.md b/README.md
index 88690d52f..85e66938d 100644
--- a/README.md
+++ b/README.md
@@ -99,6 +99,10 @@ non-commercial use, or a **Google Maps key** for the direct, metered route and
Google place search. Provider terms and quotas apply. Add keys through the
app's **POWER UP** panel; [Keys & Costs](#-api-keys) explains the options.
+> **Already installed?** Update to the latest version. Older versions query
+> public OpenStreetMap Overpass servers, which now refuse them, so Traffic,
+> Mapped Installations and ALPR stay empty until you update.
+
### Path 1 — One click, no terminal
1. Install or update [Pinokio](https://desktop.pinokio.co/) to **8.2 or later**.
@@ -275,6 +279,8 @@ _Ask for radio near anywhere and the globe starts broadcasting — every station
---
+**💬 Or ask from Claude and Codex.** God's Eye View is also an MCP server: ask Claude Desktop, Codex or ChatGPT desktop to show a place, and the live globe opens right in the conversation. Setup takes a minute — see [docs/MCP_SETUP.md](docs/MCP_SETUP.md).
+
## 🛰️ What's on the Globe
Nineteen layers and map sources. **Seventeen have a keyless path.** Some offer additional capabilities with a provider key. (🟢 no key · 🟡 free key · 🔴 metered.)
@@ -506,7 +512,7 @@ Everything above is the deliberately cheap baseline — enough to get a real tas
### 🔒 Sharing an instance
-By default nobody else can reach your server — it binds to localhost. To share on your LAN, opt in explicitly (`npm run dev -- --host 0.0.0.0 --port 4173`, or `HOST=0.0.0.0 ./scripts/dev-fresh.sh` on macOS/Linux) — but know that ⚠️ **a LAN-visible server brokers your configured API keys to anyone who can reach it.** Set the per-IP throttles (`GEV_RATELIMIT_OPENAI_PER_MIN`, `GEV_RATELIMIT_GOOGLE_PER_MIN` — see `.env.example`) and, before anything else, **configure provider quotas, usage limits, and billing alerts**: app-level throttles are not billing caps, and a budget alert alone does not stop spending. Full threat model in [SECURITY.md](SECURITY.md).
+By default nobody else can reach your server — it binds to localhost. To share on your LAN, opt in explicitly (`npm run dev -- --host 0.0.0.0 --port 4173`, or `HOST=0.0.0.0 ./scripts/dev-fresh.sh` on macOS/Linux). IP addresses work as they are; if you use a LAN hostname, add it with `GEV_ALLOWED_HOSTS=globe.lan`, since binding to `0.0.0.0` does not trust arbitrary Host headers. But know that ⚠️ **a LAN-visible server brokers your configured API keys to anyone who can reach it.** Per-IP throttles on the cost-bearing endpoints are on by default (`GEV_RATELIMIT_OPENAI_PER_MIN`, `GEV_RATELIMIT_GOOGLE_PER_MIN` — see `.env.example` to tune them), but before anything else, **configure provider quotas, usage limits, and billing alerts**: app-level throttles are not billing caps, and a budget alert alone does not stop spending. Full threat model in [SECURITY.md](SECURITY.md).
Provider Settings is disabled when the server is shared, so remote users cannot
access the key-entry panel.
diff --git a/SECURITY.md b/SECURITY.md
index 718c43778..8a4256523 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -18,8 +18,8 @@ The golden rule: **secret-bearing API keys stay on the server side.** The dev/pr
| Key | Where it lives | How the browser uses it |
|-----|----------------|--------------------------|
| `OPENAI_API_KEY` | Server only | Browser fetches a short-lived **ephemeral** Realtime session token from `/api/realtime/token`; the real key never ships |
-| `AISSTREAM_API_KEY` | Server only | Server holds the AISStream websocket; browser polls the same-origin `/api/ais-live` cache |
-| OpenSky OAuth (`OPENSKY_CLIENT_ID/SECRET`) | Server only | Server mints + refreshes the token behind `/api/opensky` |
+| `AISSTREAM_API_KEY` | Server only | Server holds the AISStream websocket; browser polls the same-origin `/api/vessels` cache |
+| OpenSky OAuth (`OPENSKY_CLIENT_ID/SECRET`) | Server only | Server mints + refreshes the token behind `/api/flights` |
| `GOOGLE_MAPS_SERVER_API_KEY` (optional, #33) | Server only | Server calls Places (`/api/google/nearby-places`, `/api/google/text-search`) and the Street View fallback with this key; falls back to `GOOGLE_MAPS_API_KEY` when unset |
### Two deliberately client-side keys — restrict them
@@ -71,14 +71,16 @@ The data proxies under `server/providers/` are written so the browser cannot tur
- **Sanitized errors** — internal error details are not echoed back to clients.
- **Coalesced OAuth refresh** and cached successful responses only (OpenSky).
- **Redacted debug logging.** The voice debug log (`.gev-logs/`, gitignored) strips API keys, bearer tokens, client secrets, and image data URLs before writing.
+- **Cross-site gate on cost/log endpoints.** The cost-bearing (`/api/realtime/token`, `/api/openai/hud-summary`, `/api/google/nearby-places`, `/api/google/text-search`) and log (`/api/realtime/debug-log`) endpoints refuse cross-site browser requests: a foreign or opaque `Origin`, a `Sec-Fetch-Site` other than `same-origin`/`none` (this is what blocks an `
`/navigation that carries no `Origin`), or any reverse-proxy/CDN forwarding header returns `403`. Non-browser loopback tools (the repo QA harnesses POST the token endpoint with no `Origin`) and the explicit `HOST=0.0.0.0` LAN opt-in keep working — the gate deliberately does not require a loopback remote address; that stricter requirement belongs only to the credential panel (`admitKeySetupRequest`). The Host check (`allowedHosts`, applied by `hostCheckPlugin` ahead of every route, since Vite's own check runs only after plugin middleware) rejects a rebinding `Host` before any handler runs, in every mode, and the gate is defense in depth behind it: the LAN opt-in keeps that check and accepts only IP addresses, `localhost` and names listed in `GEV_ALLOWED_HOSTS`. **Limit:** a name you list there is trusted like the app's own address, so list only names you control.
+- **Full Content-Security-Policy.** The dev and preview servers send a real CSP (`script-src 'self' 'unsafe-eval' blob:` plus the YouTube, Facebook and X scripts the Nepal event's embedded media loads, with a matching `frame-src` for their players and posts: no inline script and no other script origin; `blob:` covers the object URLs Cesium's bundled workers bootstrap through in the built app), alongside the existing `X-Frame-Options: DENY` and `frame-ancestors 'none'`. `'unsafe-eval'` stays because Knockout, bundled inside Cesium's widgets package, resolves the global object with `eval` at load time and the globe does not initialize without it; styles permit `'unsafe-inline'` and Google Fonts because Vite's dev client injects `