merge main f13c60c into feat/voice-outlines

reconcile voice outlines with main f13c60c, retaining German/Japanese,
Operator HUD retirement and Context/Radio cleanup, named-building
containment, courtyard-hole, non-Latin identity, attribution, cancellation
and antimeridian repairs

keep outline callouts above the open voice preview when an above-panel
slot fits without colliding with another callout,
and clip the compact dock location label on narrow screens
This commit is contained in:
manjunath committed 2026-10-30 19:02:47 +05:30
commit 578ebdc030
449 files changed
+30400 -3429

No files matched your search

+123 -46
View File
@@ -4,6 +4,24 @@ Docker Compose mounts the checkout with the shared SELinux `:z` label so
containers can read it on enforcing SELinux hosts. The separate image-owned
`node_modules` volume and loopback-only port publishing remain unchanged.
The HUD's AI summary line looks up place names for the view only when the
summary can use them. The page asks `GET /api/openai/hud-summary/status` once;
a server without an OpenAI key answers `{ configured: false }`, and the HUD then
makes no reverse-geocode or Nearby Places request until the page reloads. A
server without the route is asked once, and an unconfigured summary reply
stops the lookups the same way. Each lookup reverse-geocodes at most two
points: the view center alone below 10 km, the center and one viewport point
up to 750 km, and two viewport points up to 3000 km; Nearby Places is still
added below 25 km. A lookup is reused until the view center moves a quarter of
the visible width, the camera's distance to the center doubles or halves, or
the camera crosses one of those heights. The width is measured from the
camera's distance to the view center, so terrain and tilt scale it. A reused
lookup asks again only for what it did not get (a timeout, a failure, an empty
answer); everything else is a cache hit. A small pan over a reused lookup
repaints the last AI reply instead of asking for a new one. A host passing
`cachedOnly` keeps reading every cached sample. Voice context lookups are
unchanged.
The HUD's LAT/LON degrees-minutes-seconds readout rounds the coordinate once, to
hundredths of a second, and then splits it. A value within 0.005" of the next
whole minute carries into the minutes and degrees, so `-122.35` reads
@@ -203,7 +221,7 @@ from 0–200%. OFF disables the slider while retaining its value. HUD layout
changes preserve both settings; share links and scenes restore them. The
historical missing-`scf` fallback remains 35%; explicit 0% keeps the hard edge.
With Scope OFF, Tactical, Operator and Minimal add text-sized black backplates
With Scope OFF, Tactical and Minimal add text-sized black backplates
at 70% opacity behind telemetry, vertical metadata and scene chrome. Original
HUD text color, glow and opacity are preserved. Scope OFF brightens the tagline
and Active Style label with the shared secondary text color. The title plate
@@ -213,6 +231,15 @@ the original HUD presentation; Cyber retains its existing skin.
`node scripts/qa-scope-hud.mjs` checks native input, range, layout persistence,
restoration and both Scope presentations. `QA_SCREENSHOT_DIR` saves screenshots.
## HUD layouts — October 29, 2026
Display > HUD > Layout offers Tactical, Minimal and Cyber; the `set_hud` voice
action accepts the same three. The Operator layout is retired. Share links
keep its enum code so older links still parse, and a link, scene or saved state
that names Operator restores the default Tactical layout. Operator was the
only layout that showed the HUD's top banner bar, so its markup and styles are
removed too.
## Cyber HUD — September 30, 2026
Display > HUD > Layout includes Cyber, also available through the HUD voice
@@ -228,8 +255,8 @@ red hover border in both map and cockpit, so clipping does not hide the highligh
Cyber's map-side right rail opens one panel at a time while keeping collapsed
launchers available. Display, CCTV and Context scroll their contents inside fixed
headers and decorative frames. The narrow-screen rail remains scrollable to reach
each panel. Radio retains the shared nested Context player and compact
header disclosure, without relocating playback controls on theme changes.
each panel. Radio retains the shared nested Context player 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
@@ -560,6 +587,11 @@ dateline while preserving both authored endpoint longitudes, and turns heading
and roll the short way for any authored angles.
A seek to a shot's start, as a shot action makes, selects that shot rather than
the end of the previous one.
Same-shot seeking restores declared layer visibility after a manual toggle,
including one still in flight, by reconciling only the changed layers through
the cancellable layer admission path. Refused restoration reports a failed
seek and keeps the last settled position. Undeclared operator layers remain
untouched, and the shot is not rebuilt.
See [camera directions](DIRECTOR-CAMERA.md).
Realtime voice composes separate connection, response/tool, Radio handoff, input
@@ -793,6 +825,11 @@ rendered detection set. The legacy manager retains panel helper compatibility;
registration sealing, restoration order and failed-transition rollback remain
unchanged.
Polled layer refreshes and the HUD's camera readout pause while the page is
hidden. An app embedded inline in a panel keeps both running, because the
panel's host can report the page as hidden while it is on screen. This is the
same rule the render loop follows (`keepPanelRendering` in `app/embed.js`).
Application scene setup owns request services, terrain/floor caches and annotation
lookup state. Controls, layers and voice share those owners, and cancellation
clears caches and removes map-stack listeners. Standalone setup supplies the
@@ -1144,6 +1181,10 @@ Shot selection updates the highlight without replacing the row, preserving
native double-click rename. Replacing rows revokes their old listeners. Disposal stops controls immediately;
late file and failed-action completions cannot update removed presentation.
Director clock listeners, interaction observers and scene release failures no
longer leave the director inconsistent, and a phase error is kept when cleanup
also fails.
## Cockpit component ownership
Cockpit presentation is separated from its camera/controller behavior. Existing
@@ -1360,8 +1401,10 @@ and sharpen. Display supplies actions and renders settings; effect execution has
no DOM dependency. Existing presets and the 500 ms transition remain unchanged.
Stopping the controller revokes animation before asynchronous UI teardown; final
destruction removes its stages and restores the borrowed bloom configuration.
Snow's sideways sway stays within a fixed range at any session age; Wind scales
its amplitude.
Snow is no longer a visual preset. A share link that names it, in either the
long or compact form, opens on Normal and drops the Snow shader parameters. A
view passed to embed mode or `show_in_gods_eye_view`, and a saved scene shot,
that names Snow also open on Normal.
Sharpen runs only when enabled with intensity above 0.001. At slider zero its
stage is disabled; raising the slider resumes it when sharpen is enabled.
@@ -1476,10 +1519,6 @@ Automatic collapse remains presentation only, prioritizes the latest explicit
panel, and honors keyboard focus on the right. Mobile layout still releases
desktop allocation styles. Stable Display allocation avoids unnecessary style
writes. This extraction does not change panel positions or layout defaults.
Context expansion through the panel owner or adaptive right-rail restoration
closes an open compact Radio disclosure before Context/Space Missions becomes
visible. The mini remains available while Context is collapsed. This fixes the
Radio-overlap path reported in #804 without changing z-index or layout policy.
The right rail skips panels hidden by other CSS (computed `display: none`), but
keeps the collapsed panels its own exclusive presentation hides, so automatic
collapse can still be undone.
@@ -1968,7 +2007,7 @@ they do not reduce the materialized entity count or establish an FPS gain.
rotation and mission-marker highlight as pointer hover. Focus alone does not
select the mission; Enter or Space performs selection. Keyboard and pointer
preview ownership remain independent when the pointer is parked over the list.
- Radio power controls in the full, compact, and Cockpit surfaces, Search Nearby
- Radio power controls in the full and Cockpit surfaces, Search Nearby
Sites, and Clear Selected Layers remain focusable while lifecycle work is in
progress. They announce busy/disabled semantics and ignore repeat activation
until the operation settles, so async work cannot drop the keyboard ring.
@@ -2214,8 +2253,9 @@ fetching, trailing-24-hour filtering and partial-success caching are unchanged.
> to yield to: whoever handles ESC first calls `preventDefault()` **and**
> `stopImmediatePropagation()`, and the launcher skips `defaultPrevented` events.
> `stopPropagation()` alone does **not** stop later listeners on the same
> `document` — that is exactly how the compact Radio disclosure made one key
> close the disclosure _and_ dismiss the launcher.
> `document` — that is exactly how a disclosure that called it alone made one
> key close the disclosure _and_ dismiss the launcher. Cockpit's utility
> disclosures (Display, Radio) claim the key the immediate way.
>
> **Accepted:** a surface class that never clears means no launcher for that page
> load, with no timeout. None of the four classes is restored at startup, so an
@@ -2254,6 +2294,16 @@ fetching, trailing-24-hour filtering and partial-success caching are unchanged.
> SCOPE toggle + FEATHER slider, hash keys `sc`/`scf`) — it is NOT the
> six zero-intensity style stages anymore (those are disabled; see the
> history note in `_initStages`). Hidden tabs stop the render loop.
> AIS vessels no longer hold the loop while enabled (2026-09-30): vessels move
> only when a poll lands, so the layer requests a frame for every forced pass
> (new data, selection), leaves one trailing frame when the camera moved inside
> the 800 ms visibility cadence, and holds `ais-vessels` only while focus
> emphasis animates or any vessel stays dimmed under a live focus target. The
> trailing frame goes through the layer's injectable runtime clock and is
> cancelled on disable, destroy and reset (`renderGovernor.test.mjs` pins the
> no-self-wake invariant). The 80 ms focus pass also skips hidden sprites whose
> focus state is at rest (`spriteFocusAtRest`), since advancing them changes
> nothing; with a 12k worldwide feed that is most of the fleet.
> The scope's OUTSIDE terminus is **altitude-adaptive** (2026-08-17): 0.94
> at/above **10 Mm**, so
> faint stars survive in the corners of a TRUE full-globe view, fading quickly
@@ -2789,7 +2839,7 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> failed searches leave the current camera owner untouched, and Context Focus
> can return to the preserved Contact. Focus also restores the selected
> aircraft's canonical follow frame after a manual zoom-away.
> Visual presets retain the order Normal, CRT, NVG, FLIR, Anime, Noir, Snow.
> Visual presets retain the order Normal, CRT, NVG, FLIR, Anime, Noir.
> Configurable preset selection—including same-style reselection—opens the
> shared Parameters surface directly below Detection and scrolls it into
> view; share-link restoration
@@ -2841,7 +2891,7 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> Inside Cockpit, the focused summary card is titled Contact in both visible
> copy and its accessible control labels.
> 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
> Normal, CRT, NVG, FLIR, Anime, and Noir. 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
@@ -2885,7 +2935,7 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> corridor). Its minimal Display popover exposes Intel HUD,
> Detection, Parameters, and 3D aircraft. During Cockpit, those existing
> standard Display controls move into the Cockpit popover and retain their normal
> nested interaction: HUD plus Tactical/Operator/Minimal layout, Detection plus
> nested interaction: HUD plus Tactical/Minimal/Cyber layout, Detection plus
> Density/Allocation/Fade/Outside tuning, and 3D plus Proximity/All mode. The
> same nodes and state return to the map Display on exit, so Cockpit does not
> maintain a second control state. **Detection is owned by the CONTACTS
@@ -2947,15 +2997,10 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> stacking. Narrow mobile Cockpit
> viewports suppress the legacy layer stack, peripheral HUD, and secondary
> Context/briefing panes so the flight instruments and primary controls remain
> unobstructed. Outside Cockpit, Radio starts collapsed while off. When Context
> is collapsed, its Radio header icon opens compact controls whose explicit
> Enable/Disable action owns power; stable close and full-panel buttons own
> compact dismissal and one-way detailed expansion independently. When Context
> is already expanded, that same icon skips the floating compact card, expands
> the embedded Radio section, scrolls it into view, and moves keyboard focus to
> its disclosure control. The detailed panel's normal accordion control owns
> its collapse. Compact/detailed state and playback continuity remain shared.
> Radio volume in the full, compact, and Cockpit surfaces, plus Space Mission
> unobstructed. Outside Cockpit, Radio starts collapsed while off and lives
> only in its section inside Context; the detailed panel's normal accordion
> control owns its disclosure and its Enable/Disable action owns power.
> Radio volume in the full and Cockpit surfaces, plus Space Mission
> replay speed, use Display / Sharpen's muted 3px rail, circular cyan thumb,
> glow, and mono value treatment. Their larger transparent hit areas, keyboard
> focus indication, ranges, disabled states, and mission speed scale remain
@@ -3078,7 +3123,14 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
> usable per point, while an uncached absent height still returns 502 rather
> than becoming a fabricated ground value. A position the upstream answers
> with a null ellipsoid is reported as an absent height rather than a refresh
> failure, and is left uncached so a later poll re-asks it. Client geoid
> failure, and is left uncached so a later poll re-asks it. A `Retry-After`
> longer than the 10 s retry budget ends the retries at once. After a 429 that
> outlasts its retries, the proxy pauses upstream calls for the `Retry-After`
> (60 seconds when none, at most 10 minutes). A request whose points are all
> cached, fresh or stale, is still served; a request with any uncached point
> gets 502 and the page uses its geoid fallback for that chunk. Each request
> that was waiting on the upstream when the 429 came back logs one warning;
> requests refused during the pause do not log. Client geoid
> fallbacks wait 60 seconds before retrying and self-heal to Re:Earth on the
> first later successful fetch. Smaller chunks reduce timeout risk; complete
> camera batches still wait for their sequential requests, and unresolved
@@ -3733,7 +3785,7 @@ test:track` 43 tracking invariants · headless QA harnesses under
### Interface localization (October 2026)
Shipped locales: **en** (source and fallback), **es**, **ru** and **zh-CN**. The code lives in
Shipped locales: **en** (source and fallback), **de**, **es**, **fr**, **it**, **ja**, **pt** (Brazilian Portuguese), **ru**, **uk**, **zh-CN** and **zh-Hant**. The code lives in
`src/i18n/`. How to mark UI text: [`docs/I18N.md`](./I18N.md); translator workflow:
[`docs/TRANSLATORS.md`](./TRANSLATORS.md).
@@ -3744,8 +3796,8 @@ Shipped locales: **en** (source and fallback), **es**, **ru** and **zh-CN**. The
value). English has no catalog; every other locale is
`src/i18n/locales/<code>.json`, and a missing entry falls back to English.
- **Locale choice.** `?lang=` → stored choice (`gev:locale:v1`) →
`navigator.languages` → English. Chinese tags map to `zh-CN`; tags containing
`Hant`, `TW`, `HK` or `MO` are skipped. `?lang=` is not stored, and share links drop
`navigator.languages` → English. Any `pt` tag (`pt-BR`, `pt-PT`) selects `pt`. Chinese tags default to `zh-CN`; Traditional Chinese tags
select `zh-Hant`. Explicit `Hans` selects `zh-CN` regardless of region. `?lang=` is not stored, and share links drop
it, so a recipient keeps their own language. The globe picker in the
bottom-right corner (`src/ui/localePicker.js`) stores the choice, drops
`?lang=` (keeps it only when storage is blocked) and reloads. It asks first
@@ -3850,7 +3902,7 @@ MLAT-positioned GPS loss, derived-field exclusions and the NIC 0 exception.
anchors, recovery, keyboard and mobile states; emergency repainting is covered
by `scripts/qa-emergency-squawk.mjs`. Both accept `--timeout-ms` for slow boot.
Fire Perimeters uses capped, timed server reads with stale-on-error caching and a per-client limit. Unchanged snapshots retain geometry; link checks cancel on disable or selection change, and the row legend shows reported containment.
Fire Perimeters uses capped, timed server reads with stale-on-error caching and a per-client limit. A stale snapshot is served for at most 24 hours; the layer row then reads STALE, dated by that snapshot's fetch, and `get_fire_perimeters` returns `stale` and `retrieved_at` with "(data may be stale)" in its summary. Unchanged snapshots retain geometry; link checks cancel on disable or selection change, and the row legend shows reported containment.
Street Level is a docked, collapsible right-rail panel (`#street-level-panel`,
layer token `0`, option owner `street-level`, panel `ui` token `t`) that starts
@@ -3905,6 +3957,8 @@ reports it (`keyAlternatives`), and the panel's KEY REQUIRED, its coverage
line and the layer row's tooltip say to light it. The
viewer sits under the header with EXPAND, FIT/FILL and close above the
image. SINCE is a stepped slider whose readout names the cut-off date.
The viewer caption's bearing wraps after rounding to whole degrees, so a view
facing 359.5° or more reads `0°` rather than `360°`.
MapillaryJS loads on the first photo. Opening one frames it with a single
camera flight through the application's deferred navigation (`begin` /
@@ -4720,13 +4774,6 @@ synchronous playback-control observers run, so reentrant voice cleanup cannot
overwrite a released stream with a stale paused state. Layer
destruction likewise retains an enabled manager entry when module disable or
destroy fails semantically, preventing an active orphan and allowing retry.
When Context is collapsed, explicitly activating its Radio header icon reveals
the compact transport; hover and focus alone do not open it. When Context is
expanded, the icon instead expands and scrolls to the embedded Radio section.
Its accessible label, controlled region, and expanded state keep describing the
current route throughout enabling, enabled, disabling, and uncertain lifecycle
renders. Both routes preserve Radio power, playback, station, filter, and volume
state.
Bundled datasets live in `src/data/local_data/` with per-folder provenance READMEs; they are lazily loaded via `src/data/localGeojsonCore.js` (Vite `?url` assets) when toggled on.
@@ -4747,6 +4794,8 @@ silently demoting every later lookup for the session.
- The expanded `CONTEXT` view offers mutually exclusive `CONTACTS` and `SPACE MISSIONS` modes. Selecting `CONTACTS` enables the context-owned Flights, Military Flights, AIS Vessels, and Mapped Installations dependencies only when they are not already user-enabled; selecting `SPACE MISSIONS` enables the recent-launch layer and its Satellite dependency. `CONTACTS` cycles the nearest supported contact of whatever type is selected. Satellites are deliberately excluded from those Awareness cohorts and keep their own tracking UX. Selecting the active mode again returns to the neutral chooser and releases only mode-owned dependencies.
- If a civilian or military aircraft is already tracked when `CONTACTS` becomes operational, that source-owned track is adopted as the Context subject before nearest-contact autofocus. Context rechecks the tracker after its dependencies settle, so a newer selection wins, while an explicit clear during activation prevents fallback from silently selecting a replacement. Cockpit entry remains unavailable until that Context transaction has settled, so its camera takeover cannot clear Cesium tracking before adoption. Adoption does not recreate tracking or transfer camera ownership; it initializes the normal 250 km ring, history, proximity results, and Cockpit Previous/Next state for the original aircraft.
- `SEARCH NEARBY SITES` retains the bounded OSM results and makes one user-initiated, view-biased Google Maps Places text search for “military installation.” Google results are source-stamped, deduplicated against OSM by rounded location/name, and remain mapped context rather than operational claims. If Places is unavailable or the API is not enabled for the supplied key, OSM context remains available. A view across the antimeridian is searched around its own centre, with a radius from its real width.
- The Context header holds only its title and collapse control. It no longer carries a Radio shortcut or a compact Radio player: Radio is the `RADIO` section inside the panel, with its own disclosure and the single `ENABLE` power control. Cockpit keeps its own compact Radio utility.
- Context is set for reading in every theme: mode, standby and action labels are 10 px, and the panel draws no glow halo. Outside Cyber, the two modes form one segmented control, the standby guidance is plain left-aligned text rather than a dashed placeholder, and the expanded panel's section titles use `--text-secondary`. Cyber keeps its separately framed mode buttons, its red panel titles, and in layout-focus a framed, centred standby card over its radar art.
- The expanded desktop header omits the redundant `ON` label; the active mode button carries state. Expanded Contact results also omit the duplicate `GLOBAL CONTEXT` / `CONTEXT ONLY` status row and begin with the selected subject and its 250 km scope. Global Context does not fabricate a selected-entity model preview: the provisional hand-authored aircraft wireframe was removed because it was not geometry extracted from the selected entity's actual asset.
- Dependency ownership is reversible: disabling Global Context releases only dependencies it enabled, while user-enabled layers remain on. This also removes the Military-layer suppression handoff when Global Context owned Military, allowing an already-enabled civilian Flights layer to resume its normal mixed rendering. If OpenSky is unavailable and has no last-good cache, Flights requests a capped 250 nm adsb.lol point snapshot around the current view anchor and labels that provenance explicitly; it never relabels military-feed rows as civilian data. If both inputs fail, Flights remains `UNKNOWN`.
- Space Missions is replay-isolated: Rocket Launches and Satellites are the only Data Layers permitted while the mode is active. Direct UI and voice entry capture the same pre-entry snapshot; internal dependency and restoration enables do not create a user-owned Context session. Entry waits for incompatible layers to shut down, direct incompatible enables are blocked before lifecycle work, and the entry gate remains active through the complete Rocket Launches enable. A newer same-target ON request takes ownership of the pending entry without releasing its isolation snapshot, including when it arrives while the prior request is awaiting the adoption guard. A caller abort, resource cancellation, newer OFF, or layer teardown waits for exact manager settlement and restores that snapshot without resurrecting Rocket Launches. If an abort lands after only part of a restore settles, Context completes the same exact target without the stale caller signal and then replays newer explicit layer intent. Dedicated Voice Context cancellation reports a stable cancelled result plus the current Context state; generic layer visibility additionally exposes manager phase, reason, successor, and lifecycle details. Once an exact voice visibility intent commits, a newer voice turn cannot relabel it as cancelled while Context settlement completes; pre-commit aborts remain cancellable and final lifecycle mismatches remain failures. Clear Selected Layers reserves its complete captured OFF set before sequential teardown; a newer absolute request of any origin supersedes only its layer reservation and remains authoritative. A rejected layer teardown retains the truthful enabled state, rolls already-stopped siblings back to the captured pre-entry set, and aborts replay; rapid exit/re-entry serializes the full Satellite enabled-state and parameter restore before a new snapshot is taken. Contacts remains additive and restores user-enabled layers normally.
@@ -5119,8 +5168,8 @@ and unreachable upstream (502/504) separately from road geometry.
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 all seven map looks: Normal, CRT, NVG,
FLIR, Anime, Noir, and Snow. Entry selects the matching map style without adding
- Cockpit's top vision switch cycles all six map looks: Normal, CRT, NVG,
FLIR, Anime, and Noir. Entry selects the matching map style without adding
a duplicate carousel item. There is no empty `NONE` entry.
### Live AIS Vessels (June 2026)
@@ -5223,10 +5272,23 @@ nearby-landmark recovery stands aside for it — otherwise a typed coordinate
could be replaced by whatever Places finds near the current view. The portable `./search` export uses
Google first when configured, Photon/OpenStreetMap as fallback (including Google
transport failures or declined requests), and the local `/api/geocode` route
over Nominatim as a last resort when neither answers. That route keeps to the
public instance's usage policy: an identifying `User-Agent` and `Referer`, at
over Nominatim as a last resort when neither answers and an operator explicitly
configures `NOMINATIM_URL`. Unset, empty and invalid values disable Nominatim.
Explicit public-service configuration retains an identifying `User-Agent` and `Referer`, at
least 1.1 seconds between public search and outline starts, a shared durable
daily allowance and pause state that fail closed when unusable, answers cached
per-install daily allowance and pause state that fail closed when unusable.
The shared state lock covers actual fetch invocation and recording the next
start. An unfinished dispatch marker disables further public requests until
operator recovery; it is never automatically reclaimed. These controls do not
establish an application-wide public-service allowance or authorize its use.
For manual recovery, stop every process sharing the state file and account for
the interrupted dispatch before removing only its confirmed orphan
`state.json.dispatch.*` marker. Preserve the state file's count, pause and next
start, and wait at least the public spacing interval after the last possible
invocation. `stats().persistence.dispatchPending` exposes the marker; an unlocked
status read can also briefly see a live dispatch. Do not clear markers while an
owner is active or reset usage state to restore access.
Answers are cached
for 30 days (search), 90 days (outline) or 24 hours (genuine no result),
one upstream call shared between identical searches
already in flight, a queue bounded at four waiting searches (a burst past it is
@@ -5265,7 +5327,13 @@ HTTP refusals and outages remain retryable. Searches share a 12-second total
deadline, with Photon requests capped at six seconds each. Caller/application
cancellation stops retries and late cache writes. Replacing a location search
cancels the previous lookup; disposing its controls cancels the active lookup. Photon uses a soft proximity bias, up to five
candidates, and an unbiased retry for name mismatches. Invalid or wrapped bounds returned by providers
candidates, and an unbiased retry for name mismatches. A "place, region" query
prefers a candidate named exactly for the place whose city, county, state,
country or country code (or USA/UK) contains every word of the region, and a
settlement or administrative area over a POI when both fit. Nearby results that
fail that check are retried unbiased. After the region checks of both passes, a
nearby place whose whole name is the query (one with a comma in it) comes next,
then the unbiased name match. Invalid or wrapped bounds returned by providers
are omitted rather than framing the wrong part of the globe.
A location search that finds nothing, or fails, leaves the field open with the
@@ -5293,7 +5361,7 @@ request viewbox and continues searching without that geographic preference.
- 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/resolver.js` converts names, coordinates and screen pixels into world anchors and optional geometry. Google Geocode/Places supplies a centroid and scope; route requests use `/api/route`; ambiguous or distant results are rejected or recovered near the current view instead of drawing misleading blobs. Outline resolution keeps this order: bundled admin/place/neighborhood/Natural Earth geometry; OpenFreeMap street or pointed-building geometry where applicable; the configured OSM/Overpass admin/place/footprint/street/enclosing-area ladder; guarded Nominatim for eligible named cities, admin units, landmarks, buildings and grounds; then an OpenFreeMap building fallback. If no rung can supply honest geometry, the mark remains a point or reports the capability unavailable. A real polygon, definitive miss, configured-ladder approximation, rate limit or transient result keeps its existing finality; Nominatim is only tried after higher-priority configured sources cannot answer. Multi-part outlines and holes are retained in `anno.polygons`, with `ring` anchored to the part whose filled area contains the resolved point, outside its holes, when possible. Missing capability never invents a radius outline. 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/resolver.js` converts names, coordinates and screen pixels into world anchors and optional geometry. Google Geocode/Places supplies a centroid and scope; route requests use `/api/route`; ambiguous or distant results are rejected or recovered near the current view instead of drawing misleading blobs. Outline resolution keeps this order: bundled admin/place/neighborhood/Natural Earth geometry; OpenFreeMap street or pointed-building geometry where applicable; the configured OSM/Overpass admin/place/footprint/street/enclosing-area ladder; guarded Nominatim for eligible named cities, admin units, landmarks, buildings and grounds; then an OpenFreeMap building fallback that accepts a named building only when its resolved anchor lies in the selected filled footprint. Pointed-at building requests retain nearby selection within 40 m. If no rung can supply honest geometry, the mark remains a point or reports the capability unavailable. A real polygon, definitive miss, configured-ladder approximation, rate limit or transient result keeps its existing finality; Nominatim is only tried after higher-priority configured sources cannot answer. Building tile selection returns the selected component and its holes; a shared vector feature ID does not establish that unrelated components are one building. Other trusted multi-part outlines and holes are retained in `anno.polygons`, with `ring` anchored to the part whose filled area contains the resolved point, outside its holes, when possible. Missing capability never invents a radius outline. 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.
@@ -5554,7 +5622,7 @@ apply to `public/` assets or built-preview serving.
- The desktop right rail (`#right-context-rail`) owns `DISPLAY`, `CCTV`, its active parameter controls, and `GLOBAL CONTEXT` as one fixed responsive stack in that order. Its compact buttons use the same 176 px width as the left accordion and one consistent 50 px height, share the left stack's 52 px edge inset and measured top baseline across HUD variants, then constrain themselves against visible HUD/chrome rectangles and the remaining vertical corridor. `DISPLAY` is no longer draggable and legacy saved coordinates are ignored.
- The right rail is labeled **DISPLAY** (formerly "MOVE") and groups, in order, HUD, DETECT, Bloom, Sharpen, 3D, Clean-UI (HUD + DETECT promoted to the top). Its expanded controls retain the same compact 176 px width as the right-side tabs instead of growing to the wider Context detail-card width. It starts expanded on first run and respects the user's later `v6` collapse choice. Collapses/expands with directional chevrons (`◀` collapsed, `▶` expanded).
- Display and Context use matching 330 px expanded widths and matching compact tab dimensions. The parameter panel is part of Display's expanded content. DISPLAY may remain open beside one contextual panel; CCTV and Context are mutually exclusive. In Tactical HUD, expanding CCTV or Context hides the other contextual launcher while DISPLAY remains independently available. The most recently opened right-rail panel owns the constrained lane even when it appears later in DOM order; passive restoration and automatic disclosure do not replace that explicit owner. Minimal and other HUD layouts retain the collapsed launchers; when their active panel exceeds the measured corridor, the rail reserves sibling heights and gaps and scrolls the active panel internally.
- At 720px and below both panel stacks (`#left-panel-stack`, `#right-context-rail`) go edge-to-edge and scroll vertically. Every hosted panel's decorative `.panel-glow` is pinned to its panel box there (`inset: 0` in `src/ui/styles/responsive.css`): on desktop the glow overhangs the panel by 18–20 px as harmless paint, but inside a scroll container that overhang is scrollable overflow on both axes and drew a horizontal scrollbar band plus a vertical scrollbar that scrolled nothing but glow whenever CCTV, Context, Data Layers or Scenes was expanded. The glow's blur is ink overflow and never scrolls, so the look is unchanged. Do not fix this by clipping the stacks: `overflow-x: clip` would also clip the Context radio popover and would not remove the vertical scrollbar. Pinned by `src/ui/panelRails.test.mjs` against every `(max-width: 720px)` block in the flattened stylesheet.
- At 720px and below both panel stacks (`#left-panel-stack`, `#right-context-rail`) go edge-to-edge and scroll vertically. Every hosted panel's decorative `.panel-glow` is pinned to its panel box there (`inset: 0` in `src/ui/styles/responsive.css`): on desktop the glow overhangs the panel by 18–20 px as harmless paint, but inside a scroll container that overhang is scrollable overflow on both axes and drew a horizontal scrollbar band plus a vertical scrollbar that scrolled nothing but glow whenever CCTV, Context, Data Layers or Scenes was expanded. The glow's blur is ink overflow and never scrolls, so the look is unchanged. Do not fix this by clipping the stacks: `overflow-x: clip` would not remove the vertical scrollbar. Context draws no glow at all (`display: none`), so it is not part of that pin. Pinned by `src/ui/panelRails.test.mjs` against every `(max-width: 720px)` block in the flattened stylesheet.
- `STYLE PRESETS` and `LOCATIONS` start collapsed, expand on intentional hover/click, and auto-collapse after hover leave delay.
- Collapsed mini-status indicators show active style and active location/landmark.
- Detection mode is user-controlled and should persist when switching styles. Since 2026-08-22 it also STARTS on — Dense @ 75% for every style on a first run, Normal included — as a `GLOBAL_POST_DEFAULTS` baseline that does NOT set `_detectionUserOverridden`. Exception (unchanged): selecting a military style (CRT/NVG/FLIR) auto-enables the same Dense preset, but only until the user manually changes detection this session (`_detectionUserOverridden` gate), after which style switches never touch it.
@@ -5623,7 +5691,7 @@ apply to `public/` assets or built-preview serving.
- **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; its destination label rounds to whole degrees in 000–359, wrapping rounded north to 000° while the chevron continues to use the relative bearing independently of label rounding; 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 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; its destination label rounds to whole degrees in 000–359, wrapping rounded north to 000° while the chevron continues to use the relative bearing independently of label rounding; 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`, and `NOIR`; 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 wind-direction labels wrap after rounding to whole degrees, so a direction of 359.5° displays as `N · 0°` rather than `N · 360°`.
- **Cockpit missing observations:** missing local weather observations remain unknown rather than being converted to zero or clear skies, in the briefing and in the cloud effects; a missing temperature drops the whole local weather record, including its wind and condition.
- **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.
@@ -5777,7 +5845,11 @@ easier to meet (detection is now on more often), but does not create it.
coverage number. `stats.loading` covers outstanding flow work as well as 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.
never on `mode === 'live'` alone. When the drawn flow came from the proxy's
cached STALE-BUDGET / STALE-ERROR tiles, `getStats()` reports `stale: true`
and `oldestTileAt`, the row reads STALE with the oldest tile's age, and the
sync chip reads
`STALE · Roads: <source> · Flow: cached TomTom tiles` instead of LIVE.
- Traffic runs in `sim` mode (white dots, hardcoded speeds) unless `TOMTOM_API_KEY`
is configured (env or Keychain `tomtom-api`/`api-key`), which enables `live` mode:
TomTom flow vector tiles via the budget-governed `/api/tomtom` proxy
@@ -6080,6 +6152,11 @@ reception does not interrupt internet radio.
Changes apply to the open receiver without reconnecting.
- **Receiver stats (ADS-B).** CRC-valid messages per second, aircraft heard,
aircraft with a fresh position and the IQ level.
- **Error text.** A receiver error sets two fields (`errorState()` in
`src/sdr/controller.js`): `message` is the UI language for the Radio card,
`errorText` is the English text the Local ADS-B layer reports as its stats
`error` (`localAdsbStatus()`, falling back to `message` when a receiver sets
none); the Layers panel translates it where it shows it.
- **Devices.** An already-authorized receiver opens without the WebUSB
picker. ADS-B prefers a device whose product name matches ADS-B or 1090 (the
1090 MHz channel of a dual-channel board); FM avoids ADS-B/UAT channels.