mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
merge main 43a67f9 into feat/voice-outlines
This commit is contained in:
commit
3ac3499e4a
175 files changed
+4153
-522
No files matched your search
+100
-21
@@ -131,6 +131,26 @@ provider registration rejects compositions that cannot fit rather than truncatin
|
||||
identities. A malformed or duplicate entry invalidates the complete custom-switch
|
||||
field. Existing whole-link size limits still apply.
|
||||
|
||||
## Utilities panel — October 29, 2026
|
||||
|
||||
UTILITIES is its own left-side panel, between Data Layers and Scenes, with
|
||||
its own rail item (`handyman`). It is a `RailPanel` (`#utilities-panel`) in
|
||||
`src/ui/views/LayerPanelsView.vue` and holds the Directions layer row, which
|
||||
`LayerPanel` renders into `#utilities-toggles` instead of the Data Layers
|
||||
list, then Draw (`DrawControls.vue`) and Import (`GeoImport.vue`), which left
|
||||
DISPLAY with their ids unchanged. Without `#utilities-toggles` (a host that
|
||||
omits the panel) Directions stays in Data Layers under its Utilities heading.
|
||||
Radio, formerly the other row under that heading, stays in Data Layers under
|
||||
"Audio". Like the other side panels it follows the one-open-panel rule,
|
||||
remembers its state, and is not part of share links. It hides in clean view
|
||||
and recording. In Cockpit it stays with Directions alone, as Data Layers does;
|
||||
Draw and Import hide there, as DISPLAY always did, and opening Utilities folds
|
||||
the Cockpit context rail away as Data Layers does (and Contact expanding
|
||||
closes it). Voice `show_data_layers_menu` for Directions opens Utilities, the
|
||||
panel that holds its row, and `set_panel_open` reaches it as
|
||||
`utilities-panel` or by "utilities", "draw", "drawing" or "import". On a phone
|
||||
it sits collapsed in the Layers sheet under Data Layers.
|
||||
|
||||
## God's Eye View in conversations — October 2, 2026
|
||||
|
||||
Tool answers that can be shown in God's Eye View include a view: camera, layers,
|
||||
@@ -197,6 +217,9 @@ catalog, loaded on first use. See [tools and the MCP server](TOOLS.md).
|
||||
Aircraft altitude-band queries require a reported barometric or geometric altitude
|
||||
for each match, so a grounded contact that reports none is left out of a band too.
|
||||
Queries without an altitude band retain unknown-altitude contacts.
|
||||
`find_submarine_cables` lists and counts each cable once: TeleGeography splits
|
||||
a few cables (2Africa, Echo) into several features under one id, and such a
|
||||
cable matches when any of its parts crosses the area.
|
||||
|
||||
Catalog calls check their cancellation signal immediately before executing a
|
||||
tool. Already-cancelled queries do not start; cancellation after execution starts
|
||||
@@ -293,7 +316,7 @@ settings from active map/contact effects. Sonar settings are session-only and
|
||||
return to their defaults on reload. Sliders accept integer values matching the
|
||||
action's ranges.
|
||||
|
||||
Wind appears in the Weather group before Utilities. The surface-weather prototype
|
||||
Wind appears in the Weather group before Audio. The surface-weather prototype
|
||||
uses keyless NOAA GFS or ECMWF IFS forecasts on an approximately 1° display grid.
|
||||
It defaults to 10 m wind trails over the existing basemap. Speed shading is an explicit choice; earlier v2 links retain their original speed-shading meaning.
|
||||
Weather rows contain only the toggle and source/meta line. Configuration lives
|
||||
@@ -1135,7 +1158,8 @@ The tracked-aircraft descriptor both flight layers hand to the cockpit carries
|
||||
`verticalRate` and military records as `verticalRateMps`; the query seam maps both to
|
||||
one name, so `resolveTrackedAircraftInfo` returns a single shape for either layer. The
|
||||
rate is reported, never derived: a blank poll keeps the last transmitted value and an
|
||||
absent rate stays null rather than reading as level flight.
|
||||
absent rate stays null rather than reading as level flight. A military readsb row
|
||||
without `baro_rate` reports its GNSS rate as `geom_rate`, which is used instead.
|
||||
|
||||
## Transponder emergency highlighting
|
||||
|
||||
@@ -1582,6 +1606,10 @@ Imagery (while shown), Street Level and Context on the right. Selecting an item
|
||||
opens its panel through `setPanelCollapsed(id, false, {explicit: true})`;
|
||||
selecting the open panel's item closes it. The item of the open panel is
|
||||
pressed (`aria-pressed`), and its tooltip is suppressed while the panel is open.
|
||||
While no panel on its side is open, the rail shows each item as a labelled pill
|
||||
(icon and title, `UiRail`'s `collapsible`); opening a panel folds the labels away
|
||||
to the icon rail. The rail is placed by its screen-side edge, so the pills grow
|
||||
toward the middle of the screen, over the closed side's empty corridor.
|
||||
|
||||
One panel is open per desktop side, in every HUD layout. Opening a side panel
|
||||
by any route (rail, header, keyboard, voice) collapses its peers; restored or
|
||||
@@ -1890,6 +1918,8 @@ limit, as well as when a tile decodes or the source resets.
|
||||
GBFS null or empty availability counts retain an unknown value through station
|
||||
parsing and rendering; they use the neutral station color rather than reporting
|
||||
an empty dock. Explicit zero and caller-supplied numeric fallbacks remain valid.
|
||||
A station whose `station_information` publishes no capacity takes bikes plus
|
||||
docks from each status poll (15 until its status arrives), not from the first.
|
||||
|
||||
## Terrain height cache bound
|
||||
|
||||
@@ -2786,6 +2816,20 @@ isCurrent })` gates BOTH halves of the map-stack switch, which is its only
|
||||
> name the panel (`#location-bar` / `#control-panel`) to reach (2,5,0). Adding
|
||||
> a new tray rule means checking it against the pinned variants, not just
|
||||
> against the base rule.
|
||||
> - **Dock wings are icon-only from 900px down to 721px (2026-10-28):** at
|
||||
> ≤900px `--dock-side-compact` shrinks the Location and Visual Presets wings
|
||||
> to 4.25rem, open or closed (an open tray pops out above the dock), which is
|
||||
> too narrow for their text labels. The labels used to spill over the voice
|
||||
> control, and the LOCATION label captured taps on the left half of the mic
|
||||
> button. `src/ui/styles/command-dock-trays.css` now sets the
|
||||
> labels to `font-size: 0` and drops their `›`/`‹` chevrons in that band, so
|
||||
> each wing shows only its icon. `CommandDockView.vue` still gives the toggles
|
||||
> their "Expand/Collapse …" `aria-label` and `title` (`trayWords`), so the
|
||||
> browser tooltip is the only visible name on hover.
|
||||
> The Cyber HUD layout is excluded: it sets `--dock-side-compact` to
|
||||
> 9–9.75rem, which fits its labels. The former ≤620px ellipsis clip is gone:
|
||||
> the phone shell takes over at 720px, so it never applied to a visible
|
||||
> dock.
|
||||
> - **LOCATION mini-status is data-only today (2026-08-20):** the collapsed
|
||||
> readout now follows a free-text geocode search as well as preset pills
|
||||
> (`src/locationStatus.js` owns the copy for both), and every other camera
|
||||
@@ -3425,7 +3469,8 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
|
||||
> style-switch persistence state. Browser/GPU performance comparison remains
|
||||
> operator-side; the final accounting records only comparable population
|
||||
> reductions and deterministic Node allocation/solve measurements. Those
|
||||
> allocation gates are calibrated on Node.js 24.14.x; `package.json` permits
|
||||
> allocation gates were calibrated on Node.js 24.14.x and run in CI on the
|
||||
> pinned Node.js 24.22.2 with the same ceilings; `package.json` permits
|
||||
> supported product runtimes on Node 24 or 26, while the allocation runner
|
||||
> separately enforces Node 24 for these two calibrated probes. Each probe
|
||||
> compiles synchronously and discards explicit-GC
|
||||
@@ -4317,7 +4362,9 @@ animate). Disable clears all state.
|
||||
|
||||
At the metre/kilometre boundary, route distance labels use the rounded
|
||||
ten-metre value to select the unit, switching to kilometres at 995 metres;
|
||||
a 996-metre leg displays as `1.0 km` rather than `1000 m`.
|
||||
a 996-metre leg displays as `1.0 km` rather than `1000 m`. Whole kilometres
|
||||
start where the label would round to 10.0 km, so a 9.96 km route displays as
|
||||
`10 km` rather than `10.0 km`.
|
||||
|
||||
**Keys needed: none.** Directions works on a keyless boot, and the layer is off
|
||||
until the operator turns it on.
|
||||
@@ -4610,8 +4657,21 @@ silent for five minutes are removed. Credits appear when the feed is used.
|
||||
coordinator, but it is not a user-visible Data Layers entry. Its visible entry
|
||||
point is the right-side `CONTEXT` chooser's `CONTACTS` mode.
|
||||
|
||||
Contacts distance labels select metres or kilometres after rounding to the
|
||||
nearest metre, so a 999.5-metre contact displays as `1.0 km`, not `1000 m`.
|
||||
Contacts distance labels, in the panel and on the globe's contact markers,
|
||||
select metres or kilometres after rounding to the nearest metre, so a
|
||||
999.5-metre contact displays as `1.0 km`, not `1000 m`, and drop the
|
||||
kilometre decimal from 9.95 km, so 9,960 m displays as `10 km`.
|
||||
|
||||
Distance labels that switch between metres and kilometres share
|
||||
`distanceParts` / `formatDistanceText` in `src/data/distanceFormat.js`: the
|
||||
Contacts panel and markers, Directions rows and step cards, placed annotation
|
||||
labels, the drawn-line measure and `formatRouteDistance`. Each cut is taken on
|
||||
the value as it prints, and kilometres come back already rounded, so the
|
||||
plain and localized labels agree. Callers choose the metre step (whole metres;
|
||||
tens, as annotation labels use; or tens with whole metres under 100, as
|
||||
Directions uses) and keep their own rendering; the Cockpit's kilometre-only
|
||||
distance uses `kilometreDigits`. The HUD altitude tag and the spoken view
|
||||
height still choose their own unit.
|
||||
|
||||
At global scale, ambient Radio cluster badges are hard-opacity shared-host
|
||||
entries: count/category updates and identity replacement do not run keyhole or
|
||||
@@ -5038,7 +5098,7 @@ silently demoting every later lookup for the session.
|
||||
protected-card path. Disable tears the tier down completely.
|
||||
Coverage/viewshed semantics unchanged.
|
||||
- **Satellites**: orbit rings rotate about Earth's Z by ΔGMST every ~1s (exact inertial→ECEF compensation; no SGP4 re-runs); the tracked satellite propagates per frame with one shared epoch for dot/label/camera. Verified: ISS holds <1km perpendicular to its ring while tracked. Pass prediction (`src/data/satellitePass.js`; `getNextSatellitePass`, `getNextIssPass`) scans at 20s, bisects rise/set to ~0.2s, fits the peak with a parabola, and marks a pass visible when the satellite is outside a cylindrical Earth shadow while the observer's Sun is at or below −6°. `next_iss_pass` preserves its geometric-pass ordering and appends visibility metadata. General `next_satellite_pass` accepts a loaded NORAD ID or unambiguous name, searches 24 hours and optionally requires estimated visibility. These estimates do not account for weather or satellite brightness.
|
||||
- **Space Missions (30d)**: recent launches render as bounded shared-host, horizon-occluded mission markers using Launch Library 2 v2.3 detailed records. Enabling the layer selects the unified right-side Context panel's Space Missions mode and enables the required satellite layer. Before applying its temporary dense/hidden Satellite mode, Space Missions snapshots the complete standalone Satellite parameter set and exact enabled-layer set. Disabling Space Missions from either Context or the left Data Layers rail restores those parameters and the exact prior enabled state, so a Satellite layer that was already on stays on while a mode-owned dependency returns off; enabling Satellites by itself remains independent. Selecting a mission isolates its launch-to-orbit transfer and dashed satellite orbit, fills that same panel with navigation/details, and animates a small phase-colored marker along the exact displayed Cartesian samples. Marker hit testing drill-picks through photorealistic tiles so the depth-test-free tactical dot remains reliably selectable; its text is non-interactive shared-host presentation. The selected pad is the camera's zoom pivot: the overview remains centered on its launch site, wheel zoom approaches that site instead of drifting elsewhere, and camera pitch progressively changes from global nadir to an oblique close 3D view. Its protected shared-host callout remains visible and expands to include both mission and launch-site names. `FOCUS` flies directly to a 12 km oblique frame around the selected launch site and retains the same anchored zoom/orbit behavior. `REPLAY ASCENT` resets the selected marker at the pad, frames it from an oblique third-person angle, and follows it through the mission-specific compressed ascent directly into one orbital lap at the default `1×` rate. A live `0.25×`–`4×` slider changes ascent and orbit playback speed; adjusting it mid-replay preserves the current path position and historical mission timestamp. Re-entry/recovery cannot be inserted into replay. At orbit insertion the camera smoothly pulls back over the first fifth of the orbital replay and pitches to a globe-scale nadir view while continuing to target the moving replay point. Replay Cancel, mission navigation, deselection, layer disable, and data refresh all release camera ownership. Reconstructed paths are one continuous 128-sample geodetic curve: horizontal departure begins near zero while altitude rises quickly, then the climb progressively bends toward insertion without the former hard 120 km corner. Unmatched orbit fallbacks are smooth planar inclined rings rather than longitude/latitude ground-track curves. The panel lists disclosed payload names, types, operators/manufacturers, mass, multiplicity, and destination when supplied; an empty LL2 payload collection is shown as `CLASSIFIED / MULTI-PAYLOAD`. Launcher, spacecraft, and recoverable payload stages appear in a compact stage table with serial/flight/reuse details, recovery outcome/type, destination, and final coordinates when those records exist; an empty recovery collection omits the section. Stage recoveries with confirmed coordinates use those coordinates; return-to-launch-site records use the pad; downrange-only records receive an explicitly labeled estimated endpoint along the ascent azimuth. Available endpoints render as static 2 px dashed descent/recovery paths with a fixed final-position dot and an estimated atmospheric-interface segment when applicable. The ascent is geodetically densified above the ellipsoid toward the orbit's nearest insertion point, then rendered with `ArcType.NONE`, so it neither cuts through Earth nor separates from the marker. Because LL2 does not normally supply continuous ascent telemetry, pad-to-insertion paths without upstream trajectory samples are labeled `RECONSTRUCTED ESTIMATE` / `ASCENT ESTIMATE`; only supplied trajectory samples receive the replay wording. Selected-orbit framing fits the complete ring, rear-side linework uses normal scene depth occlusion, and current distance, speed, and callout data come only from a launch-year-validated satellite match. Speed is the magnitude of the SGP4 inertial velocity vector at the same propagation epoch as position, displayed in km/s with km/h available as hover detail. Unavailable operator, site, launch-time, orbit, current-altitude, and speed values omit their detail rows instead of reserving panel space with placeholders. Newly launched payloads absent from the core operational groups use CelesTrak's cached active TLE feed as a lookup-only fallback; weak constellation-name matches are rejected. The replay callout maps compressed animation progress onto Launch Library's mission-relative timeline, showing the historical UTC date/time at the marker's current path position; unavailable timelines remain explicit. Matched live satellite positions propagate at one-second cadence and use a distinct green dot/callout with the current UTC date/time.
|
||||
- **Space Missions (30d)**: recent launches render as bounded shared-host, horizon-occluded mission markers using Launch Library 2 v2.3 detailed records. Enabling the layer selects the unified right-side Context panel's Space Missions mode and enables the required satellite layer. Before applying its temporary dense/hidden Satellite mode, Space Missions snapshots the complete standalone Satellite parameter set and exact enabled-layer set. Disabling Space Missions from either Context or the left Data Layers rail restores those parameters and the exact prior enabled state, so a Satellite layer that was already on stays on while a mode-owned dependency returns off; enabling Satellites by itself remains independent. Selecting a mission isolates its launch-to-orbit transfer and dashed satellite orbit, fills that same panel with navigation/details, and animates a small phase-colored marker along the exact displayed Cartesian samples. Marker hit testing drill-picks through photorealistic tiles so the depth-test-free tactical dot remains reliably selectable; its text is non-interactive shared-host presentation. The selected pad is the camera's zoom pivot: the overview remains centered on its launch site, wheel zoom approaches that site instead of drifting elsewhere, and camera pitch progressively changes from global nadir to an oblique close 3D view. Its protected shared-host callout remains visible and expands to include both mission and launch-site names. `FOCUS` flies directly to a 12 km oblique frame around the selected launch site and retains the same anchored zoom/orbit behavior. `REPLAY ASCENT` resets the selected marker at the pad, frames it from an oblique third-person angle, and follows it through the mission-specific compressed ascent directly into one orbital lap at the default `1×` rate. A live `0.25×`–`4×` slider changes ascent and orbit playback speed; adjusting it mid-replay preserves the current path position and historical mission timestamp. Re-entry/recovery cannot be inserted into replay. At orbit insertion the camera smoothly pulls back over the first fifth of the orbital replay and pitches to a globe-scale nadir view while continuing to target the moving replay point. Replay Cancel, mission navigation, deselection, layer disable, and data refresh all release camera ownership. Reconstructed paths are one continuous 128-sample geodetic curve: horizontal departure begins near zero while altitude rises quickly, then the climb progressively bends toward insertion without the former hard 120 km corner. Unmatched orbit fallbacks are smooth planar inclined rings rather than longitude/latitude ground-track curves. The panel lists disclosed payload names, types, operators/manufacturers, mass, multiplicity, and destination when supplied; an empty LL2 payload collection is shown as `CLASSIFIED / MULTI-PAYLOAD`. Launcher, spacecraft, and recoverable payload stages appear in a compact stage table with serial/flight/reuse details, recovery outcome/type, destination, and final coordinates when those records exist; an empty recovery collection omits the section. Stage recoveries with confirmed coordinates use those coordinates; return-to-launch-site records use the pad; downrange-only records receive an explicitly labeled estimated endpoint along the ascent azimuth. Available endpoints render as static 2 px dashed descent/recovery paths with a fixed final-position dot and an estimated atmospheric-interface segment when applicable. The ascent is geodetically densified above the ellipsoid toward the orbit's nearest insertion point, then rendered with `ArcType.NONE`, so it neither cuts through Earth nor separates from the marker. Because LL2 does not normally supply continuous ascent telemetry, pad-to-insertion paths without upstream trajectory samples are labeled `RECONSTRUCTED ESTIMATE` / `ASCENT ESTIMATE`; only supplied trajectory samples receive the replay wording. Selected-orbit framing fits the complete ring, rear-side linework uses normal scene depth occlusion, and current distance, speed, and callout data come only from a launch-year-validated satellite match. Both the loaded-catalog and active-TLE lookups read the launch year from the TLE international designator through one parser (`src/sources/tle.js`); a blank or malformed designator is an unknown year, which skips the year check rather than rejecting the match. Speed is the magnitude of the SGP4 inertial velocity vector at the same propagation epoch as position, displayed in km/s with km/h available as hover detail. Unavailable operator, site, launch-time, orbit, current-altitude, and speed values omit their detail rows instead of reserving panel space with placeholders. Newly launched payloads absent from the core operational groups use CelesTrak's cached active TLE feed as a lookup-only fallback; weak constellation-name matches are rejected. The replay callout maps compressed animation progress onto Launch Library's mission-relative timeline,Line truncated
|
||||
When no mission is selected, the Context panel presents a scrollable newest-first roster of every launch in the rolling window, including the smaller 5 px operator-colored marker, provider, and launch date. Hovering or keyboard-focusing a row shows four compact cyan corner brackets on both that roster row and its corresponding globe dot, rotates the globe at the current zoom to center it, and gives its label declutter priority without selecting it; the globe label remains unbracketed. Selecting a roster row invokes the same mission isolation and full-orbit framing as clicking its globe marker. The replay vehicle is one screen-space SVG/CSS HUD overlay rather than separate Cesium billboard, label, and reticle graphics. It is hidden during ordinary Focus and manual close views, where the standard selected launch-site label remains visible, and exists only while ascent replay is active. Its fixed pixel scale is shared by ascent and insertion, so camera range never resizes the rocket on screen before the phase boundary. Generic Launch Library pad names are reduced to their identifying suffix, and replay timestamps use a cyan state title over unprefixed white UTC date/time values. `REPLAY ASCENT` holds the unframed cyan/white rocket at the pad for a real-time `T−10` countdown, transitions through `LIFTOFF`, and attaches six tapered cyan/white ellipse waves directly below it from liftoff through insertion to convey thrust without adding scene geometry. While replay is active, the single start button is replaced by compact Play, Pause, and Cancel icon controls. Pause freezes countdown or mission time, vehicle/stage positions, camera target, labels, and thrust-wave animation; Play resumes from that exact frame, and Cancel releases replay camera ownership. The rocket and thrust group rotates from the path's live screen-space tangent, so its nose follows the visible ascent curve while the adjacent text remains upright. The camera begins as a close oblique launch chase, then smoothly widens between roughly 120 and 420 km vehicle altitude into a higher oblique context view that keeps the moving rocket targeted while exposing the ascent bend and orbit connection. At insertion the rocket/thrust glyph is replaced by the fixed-size cyan orbit dot, which the camera follows through the existing globe-scale orbit pullback. The chase camera limits per-frame yaw changes across heading wraps so it cannot abruptly cross in front of the vehicle and make ascent read in reverse; the replay-speed slider affects mission playback but not countdown duration.
|
||||
Mission world text has no native `LabelGraphics`: overview launch markers publish at most 48 ambient candidates for a 24-winner budget, while selecting a mission clears that overview source and publishes its launch-site callout, stage re-entry annotations, live/estimated payload-position readout, and orbit annotation as protected selected-lane entries. The source retains the exact former strings and colors. Static anchors reuse the Cartesian values used to build their mission geometry; the moving payload entry reads the layer's per-frame live-position cache; catalog-backed orbit annotation positions are cached in the same one-second matrix update that realigns the orbit primitive. Keyhole edge fade, horizon culling, final collision placement, and UI exclusion are owned by the shared host. Deselect restores the bounded overview, and refresh, disable, and destroy replace or clear both mission sources.
|
||||
A selected mission renders its orbit as four repeating tactical sectors, each containing one prominent cyan dot followed by one hundred thin translucent dashes. The bright dots act as orbit anchors while the subdued dash field remains depth-tested against the globe and is shown only for the selected mission.
|
||||
@@ -5061,6 +5121,8 @@ repointing `OVERPASS_UPSTREAMS` refetches instead of serving another instance's
|
||||
answers; untagged entries stop serving once an instance is configured.
|
||||
|
||||
The shared transport guards `/api/overpass` and `/api/military-installations`.
|
||||
Both query handlers refuse cross-site and forwarded browser requests through
|
||||
the shared local-request gate, before any rate-limit, cache or upstream work.
|
||||
With no configured upstream, each route serves existing cache data (including
|
||||
expired entries, marked stale with the original cache/retrieval date) without
|
||||
refresh. An annotation miss returns HTTP 503, `OVERPASS_NOT_CONFIGURED`,
|
||||
@@ -5261,6 +5323,8 @@ and unreachable upstream (502/504) separately from road geometry.
|
||||
cannot race the nearest-aircraft query ahead of layer enablement. A healthy fallback feed is
|
||||
queried normally and its source is returned with the selection; fallback with
|
||||
no airborne records remains an honest no-data result, not an enable failure.
|
||||
The spoken reply and the card leave out an altitude the feed did not report
|
||||
rather than reading it as 0 feet.
|
||||
- Voice treats the parent Context panel and Contacts as separate intents. An
|
||||
explicit request to open Context expands only `global-context-panel`; it does
|
||||
not choose a mode. An explicit request to open Contacts expands that parent
|
||||
@@ -5308,7 +5372,7 @@ and unreachable upstream (502/504) separately from road geometry.
|
||||
- Pins: `src/routeCinematics.test.mjs` (26 tests), with `scripts/qa-flyroute-mutations.mjs` reverting each fix individually to prove they are load-bearing (19 named defects); rendered proof `scripts/qa-flyroute-cinema.mjs` (drives the real voice runner, measures the real Cesium camera every frame, writes a labelled contact sheet).
|
||||
- **Public control facade** on StyleManager (`setHudVisible/setHudLayout/setDetection/setMapStack/setBloom/setSharpen/setOrbit/setCleanView/getControlState` plus `runImmediateNavigation`): every setter syncs DOM sliders + share links + scene snapshots and returns `{ok, ...state}` — voice confirms only what actually happened. Validated `move_camera`, `fly_route`, `frame_overhead`, strongest-fire focus, and entity tracking use the shared navigation transaction, which refuses Cockpit before mutation, advances authority, releases follow owners, cancels stale work, and only then starts the requested action. All four entity layers expose `findByQuery/getNearby/getAllPositions/trackById|selectById/stopTracking/getTrackedInfo`.
|
||||
- **Scene context** (`get_entity_context`): selected entity from the context store, or visible entities ranked by distance to the view target (≤100km altitude); plus basemap context — view-target picking, 7-point viewport sampling, view-scale classification (global/continental/regional/metro/city/local), reverse geocoding (center ≤750km, viewport samples ≤3,000km), Google Places Nearby via `/api/google/nearby-places` (≤25km), known-landmark matching against `CITY_POIS`, coarse country inference fallback. Context assembly is capped at 1.5s with cache fallbacks; caches are deduped in-flight.
|
||||
- **Every selectable layer writes that one slot** (2026-08-21). The tracking layers — flights, military, satellites — publish selection on their own awareness lane (`gev:awareness-subject-selected`, consumed by the readout card and the Contacts panel) and for the life of the voice tools never wrote the shared slot, so `scope:'selected'` silently answered `in_view` with a contact plainly selected on screen. They now call `selectTrackedSubjectContext` / `refreshTrackedSubjectContext` / `clearTrackedSubjectContext` (`src/data/contextStore.js`) on select / poll / deselect. **Invariants: (a)** that write path must NOT dispatch `gev:entity-selected` — tracking layers already own an event lane and a second one makes two surfaces fight over one subject; **(b)** exactly one record per tracking layer, because a frozen snapshot of a moved contact must never reach the visible-entity scan; **(c)** precedence is recency, not layer — one slot, last selection wins, so clicking an overlay entity supersedes a tracked plane as the voice subject while the plane stays tracked. Satellites refresh on the 1 s propagation beat, not per frame.
|
||||
- **Every selectable layer writes that one slot** (2026-08-21). The tracking layers — flights, military, satellites — publish selection on their own awareness lane (`gev:awareness-subject-selected`, consumed by the readout card and the Contacts panel) and for the life of the voice tools never wrote the shared slot, so `scope:'selected'` silently answered `in_view` with a contact plainly selected on screen. They now call `selectTrackedSubjectContext` / `refreshTrackedSubjectContext` / `clearTrackedSubjectContext` (`src/data/contextStore.js`) on select / poll / deselect. **Invariants: (a)** that write path must NOT dispatch `gev:entity-selected` — tracking layers already own an event lane and a second one makes two surfaces fight over one subject; **(b)** exactly one record per tracking layer, because a frozen snapshot of a moved contact must never reach the visible-entity scan; **(c)** precedence is recency, not layer — one slot, last selection wins, so clicking an overlay entity supersedes a tracked plane as the voice subject while the plane stays tracked. Satellites refresh on the 1 s propagation beat, not per frame. The flights and military context records wrap the track after rounding to whole degrees, so a contact on 359.5° or more reads `0°` rather than `360°`.
|
||||
- **Context-mode vocabulary is symmetric** (2026-08-21). `set_context_mode` accepts `contacts`; the internal id is `flights`. Every model-readable field (`mode`, `entering`, `priorMode`, nested `context`/`contextRollback`, and the transition diagnostic text) is reported in the accepted vocabulary, with the internal id preserved as `<field>Internal`; an absent secondary mode stays `null` rather than claiming to be `off`. Mapping lives in `src/contextModePolicy.js` so UI text and voice payloads cannot drift. Reporting the internal id made the model read `mode:'flights'` as "Contacts is off" and refuse to answer from the `contactsWindow` counts in the same payload.
|
||||
- **Analyst → track handoff carries a key, not just a label** (2026-08-21). `analyst_query` items include `icao24`/`mmsi` alongside the display `id`, and contact lookup uses the shared tiered ranking in `src/data/contactMatch.js`: hex exact → callsign exact → registration exact → callsign prefix → registration prefix → callsign substring → registration substring. The tiers keep an exact callsign ahead of a colliding registration regardless of feed order, and registrations compare separator-insensitively (`G-ABCD`/`GABCD`, `05-8152`/`058152`, `N123AB`/`N-123AB`).
|
||||
- **A typed command supersedes the turn it interrupts** (2026-08-21; hardened 2026-10-17). `sendTextCommand` defers its `response.create` behind an active response instead of colliding with it, marks that response superseded so a late function call from it is refused rather than dispatched, and drops the old turn's queued follow-up. A refused call is still ANSWERED — a terminal `{ok:false, superseded:true}` `function_call_output` — because an unanswered `function_call` strands a pending call and deadlocks the model; the refusal creates no response of its own. A burst of typed commands coalesces into one response while keeping both conversation items. If the local channel rejects the user item, that command requests no response and adds no deferred answer; earlier accepted deferred input remains queued. Interruption and pointer preparation still happen before the send attempt.
|
||||
@@ -5533,20 +5597,21 @@ request viewbox and continues searching without that geographic preference.
|
||||
|
||||
### Manual Whiteboard Drawing (September 2026)
|
||||
|
||||
- DISPLAY ▸ **Draw** turns the globe into a whiteboard drawn by hand. Pick a shape (Area, Line, Pin), click the vertices on the real world, type an optional label and pick a colour, and finish. **Keyboard contract, live only while Draw is on and never while you are typing in another field:** Enter finishes the shape (as does a double-click, and the two cannot both submit it — the session is replaced before the annotate call is awaited); Backspace removes the last vertex; Esc cancels the shape in progress, and a second Esc — Esc on an empty session — leaves draw mode. Typing in the label field keeps Backspace; Enter there also finishes. **Clear** wipes the board (the shape in progress and every placed mark), so removing marks does not require the console or the microphone.
|
||||
- Runtime entry: `createApplicationTools` (`src/app/tools.js`, behind the `src/standalone/tools.js` shim) calls `initDrawTool({ viewer, annotations })` after `initAnnotations` and defers its `destroy()`, so disposing the shell returns every DOM listener, the Cesium handler, the preview data source, the borrowed click actions and the `window.__gevDrawTool` handle. Its markup is in the DISPLAY-rail view (`src/ui/views/DisplayControlsView.vue`), rendered into its `index.html` host before the page is wired. That handle (`setActive`, `setShape`, `addVertex(lon, lat)`, `finish`, `cancel`, `clearAll`, `diagnostics`) is the no-mouse test seam.
|
||||
- Contract: a finished shape is submitted through the SAME `annotationEngine.annotate()` the voice agent uses, with geometry supplied and `manual: true` (`type: area` + `ring`, `type: route` + `path`, `type: pin` + coordinates). `resolveSpec` short-circuits manual specs (`resolveManualSpec`) so no geocoder, Places or Overpass call is made. A drawn area is a real (not synthesized) outline and drapes solid; a drawn line is a `route` with `mode: 'manual'`, labelled with its length and never a travel time; a pin is a point. Drawn marks therefore render with the whiteboard look, persist, de-dup, appear in `.list()`, and clear with `clear_annotations` / `.clear()`.
|
||||
- UTILITIES ▸ **Draw** turns the globe into a whiteboard drawn by hand. Pick a shape (icon buttons for Area, Line and Pin) and a colour swatch for the next shape, click the vertices on the real world, and finish. **Keyboard contract, live only while Draw is on and never while you are typing in another field:** Enter finishes the shape (as does a double-click, and the two cannot both submit it — the session is replaced before the annotate call is awaited); Backspace removes the last vertex; Esc cancels the shape in progress, and a second Esc — Esc on an empty session — leaves draw mode. **Clear** wipes the board (the shape in progress and every placed mark), so removing marks does not require the console or the microphone.
|
||||
- **Drawn shapes are listed under Draw like Import's files** (`src/ui/views/panels/DrawMarkList.vue`, rows worded by `presentDrawMarks` in `src/annotations/drawMarks.js`): one row per hand-drawn mark (`manual: true`; voice marks are not listed) with its colour swatch, its name, and show/hide, fly-to and remove. The draw tool publishes the rows with the rest of its controls and answers the row actions (`renameDrawMark`, `recolorDrawMark`, `toggleDrawMark`, `flyToDrawMark`, `removeDrawMark`) through the display store; it republishes on the engine's `onChange`, so a voice clear or an expiry updates the list. Rows are keyed and updated in place: a board change never takes a name being typed (the field keeps its draft while focused) or the button under a click. The name is saved on Enter or on leaving the field, once; Esc restores it, before anything else and with the row's colours open too. Both keep focus in the field, so neither drops it to the page or leaves Draw. The swatch opens the five colours right after it (shown at the end of the row), with the current colour focused; the arrows, Home and End try another, and a click or Esc closes them and returns focus to the swatch. Both colour groups (the next shape's and a row's) are one `DrawSwatches` radio group with a single tab stop, coloured from `DRAW_COLOR_CSS`; on touch screens each dot answers a 24 px target. An unnamed shape shows its kind, and an unnamed line its length; a renamed line keeps its length (`Ridge walk — 950 m`). **Hidden** keeps a mark on the board at alpha 0, and the world renderer takes every one of its entities out (`show = false`), so no material that keeps its own colour (the line's flowing stroke) still paints it; the screen renderer does not project a hidden callout, so it cannot push a visible label aside; `fly_route` skips a hidden route; and with every mark hidden the annotations render hold is released. **Fly to** is an immediate navigation (`runImmediateNavigation('drawing', …)`), like Import's: Cockpit refuses it and a running orbit, follow or track lets go first. A line is framed by its whole path, across the antimeridian too.
|
||||
- Runtime entry: `createApplicationTools` (`src/app/tools.js`, behind the `src/standalone/tools.js` shim) calls `initDrawTool({ viewer, annotations, navigation, publish, connect })` after `initAnnotations` and defers its `destroy()`, so disposing the shell returns every DOM listener, the Cesium handler, the preview data source, the borrowed click actions and the `window.__gevDrawTool` handle. Its controls are Vue views in the UTILITIES panel (`DrawControls.vue`, `DrawMarkList.vue`) that render what the tool publishes to the display store and call its actions. That handle (`setActive`, `setShape`, `addVertex(lon, lat)`, `finish`, `cancel`, `clearAll`, `marks`, `setColor`, `diagnostics`) is the no-mouse test seam.
|
||||
- Contract: a finished shape is submitted through the SAME `annotationEngine.annotate()` the voice agent uses, with geometry supplied and `manual: true` (`type: area` + `ring`, `type: route` + `path`, `type: pin` + coordinates). `resolveSpec` short-circuits manual specs (`resolveManualSpec`) so no geocoder, Places or Overpass call is made. A drawn area is a real (not synthesized) outline and drapes solid; a drawn line is a `route` with `mode: 'manual'`, labelled with its length and never a travel time; a pin is a point. Drawn marks therefore render with the whiteboard look, persist, appear in `.list()`, and clear with `clear_annotations` / `.clear()`. A drawn shape is never merged with another as a duplicate: every one is drawn on purpose, and every one is unnamed until its row names it, so two pins dropped close together are two pins.
|
||||
- **A vertex is a lon/lat, and the mark is DRAPED.** Clicks are picked through `pickWorldFromScreen` — one of two scene helpers the annotation resolver factory (`src/annotations/resolver.js`) exposes alongside `sampleGroundHeight`, so the app has ONE depth-aware pick cascade rather than a second written beside it — which returns the height the pick landed on as well as the coordinate, so the live preview hangs on the roof or hillside under the pointer instead of sinking to sea level. The world renderer draws areas and routes with `clampToGround`, so the height is dropped at finish in one place (`finishSpec`) rather than being carried downstream and discarded silently: what you get is the outline you drew, lying on the surface beneath it.
|
||||
- **Draped marks classify onto `ClassificationType.BOTH`** — terrain AND 3D tiles — not onto 3D tiles alone. With only `CESIUM_3D_TILE`, every area fill, area outline, route and arrow rendered to nothing on a keyless boot, where Cesium's own globe carries Esri/OSM imagery and there are no tiles to classify against; the labels appeared and the geometry did not. That hit spoken annotations exactly as hard as hand-drawn ones. `scripts/qa-draw-tool.mjs` counts the mark's own colour in the rendered frame before and after a shape is finished, on a keyed AND a keyless server, so "the board says there is a mark" can no longer pass for "the mark is on screen".
|
||||
- **A finished area is a CLOSED ring.** The fill polygon closes itself but the outline beside it is a polyline, so an open ring drew every edge except the one back to the first vertex — a triangle with two sides. `finishSpec` repeats the first position at the end. A line is never closed: joining its ends would invent a segment nobody drew.
|
||||
- Drawn-line distance labels select metres or kilometres after rounding to the nearest metre, so a 999.5-metre line displays as `1.0 km` rather than `1000 m`.
|
||||
- Drawn-line distance labels select metres or kilometres after rounding to the nearest metre, so a 999.5-metre line displays as `1.0 km` rather than `1000 m`, and drop the kilometre decimal from 9.95 km. The placed mark's label (shared with spoken routes and arrows) rounds before each unit cut, so a 996-metre line is labelled `1.0 km`, not `1000 m`, and a 9,960-metre one `10 km`, not `10.0 km`.
|
||||
- **Measurements and centres are computed on unwrapped longitudes** (`unwrapLongitudes` / `wrapLongitude` in `drawMode.js`, used by `ringAreaM2`, `ringCentroid` and the engine's manual-area anchor). A ~50,000 m² rectangle straddling the antimeridian reads its 0.002° width as 359.998° under raw arithmetic: it measured thousands of square kilometres and anchored on the Greenwich meridian, half a world from the shape. Path length was already safe — haversine is periodic in the longitude delta — and a test pins that so a "fix" cannot break it.
|
||||
- **Bounds.** One shape holds at most `MAX_VERTICES` = 512 points; two clicks within 0.5 m are one vertex (that is what makes a double-click finish without leaving a stray point); a vertex that is not a finite on-globe coordinate is refused. A shape that encloses nothing is refused rather than placed: three collinear points are not an area and a line under a metre long is not a line, and the hint says which.
|
||||
- Drawn-area measures round before choosing m², ha or km², so 9,999.6 m² displays as `1.0 ha`, not `10000 m²`, and 999,960 m² as `1.00 km²`, not `100.0 ha`.
|
||||
- Pointer ownership: while a session is open the tool holds the single pointer claim (`claimPointer('draw')`, `src/data/inputOwnership.js`) and **every** ambient scene selection handler — the shared tracking click gesture behind flights, military and CCTV, plus vessels, satellites, launches, fires, installations, bikeshare, radio, submarine cables, the bundled GeoJSON layers and the CCTV calibration gizmo — returns before picking. A vertex over an aircraft is a vertex, not a selection.
|
||||
- **The claim is a LEASE.** `claimPointer(owner)` returns an opaque token or `null`; `releasePointer(lease)` takes that token. A second claim fails even under the same owner name, because two live instances of one tool — an old one mid-teardown and its replacement — are two owners; releasing by name let the old instance free the claim its successor was holding, and every layer started selecting through the live session's vertices. A superseded lease now releases nothing.
|
||||
- **Cesium's own click actions are borrowed too.** The Viewer binds a selecting LEFT_CLICK and a tracking LEFT_DOUBLE_CLICK on its own handler, outside every layer and therefore outside the shared claim — guarding the layers alone still left a drawn vertex selecting the entity under it. Both are removed for the session and restored, as the same function objects, when it ends or the tool is destroyed.
|
||||
- **Enter belongs to whatever has the keyboard.** The finish key is taken only from the canvas, the page body and the tool's own label field. With a control focused — Clear, a shape button — Enter activates that control, as a keyboard user expects; it does not quietly finish the shape.
|
||||
- **Enter belongs to whatever has the keyboard.** The finish key is taken only from the canvas and the page body. With a control focused — Clear, a shape button — Enter activates that control, as a keyboard user expects; it does not quietly finish the shape.
|
||||
- Pure half: `src/annotations/drawMode.js` (session, minimum vertices, double-click de-dup at 0.5 m, vertex ceiling, degenerate-geometry refusal, ring closing, dateline-safe length/area/centroid, hint text). Regression surface: `src/annotations/drawMode.test.mjs` and `src/data/inputOwnership.test.mjs` (pure), `src/annotations/drawToolBehaviour.test.mjs` (the tool driven against DOM and viewer doubles — leases, borrowed input actions, teardown, the Enter key, preview heights), `src/annotations/drawTool.test.mjs` (structural: where files live, which ids the markup carries, what the docs may claim), and the rendered harness `scripts/qa-draw-tool.mjs`.
|
||||
- The annotation tree is a declared ownership group: `./annotations` → `src/annotations/index.js` in `package.json`, with its module graph listed in `scripts/package-boundaries.json`, so an annotation module reaching outside that graph is a gate failure rather than a surprise in a layer's bundle. `drawMode.js`, `drawTool.js` and their tests are in `scripts/format-scope.json`.
|
||||
|
||||
@@ -5574,7 +5639,7 @@ request viewbox and continues searching without that geographic preference.
|
||||
|
||||
### Import Your Own Files (October 2026)
|
||||
|
||||
- DISPLAY ▸ **Import** reads GeoJSON (`.geojson`/`.json`), KML, KMZ and GPX from the picker or a file drop anywhere in the app. Each file gets show/hide, Fly-to and Remove controls. GeoJSON and GPX use the row's color; KML/KMZ retain their styles and show a hatched swatch. The file bytes remain on the device for the page session. Rendered overlays can appear in map screenshots sent through voice or other existing screenshot tools.
|
||||
- UTILITIES ▸ **Import** reads GeoJSON (`.geojson`/`.json`), KML, KMZ and GPX from the picker or a file drop anywhere in the app. Each file gets show/hide, Fly-to and Remove controls. GeoJSON and GPX use the row's color; KML/KMZ retain their styles and show a hatched swatch. The file bytes remain on the device for the page session. Rendered overlays can appear in map screenshots sent through voice or other existing screenshot tools.
|
||||
- **Ground and height.** GeoJSON XYZ coordinates become ground footprints. KML ground lines/rings are tessellated; static recorded tracks preserve their original vertices, segment gaps and explicit altitude modes. KML with absolute/relative altitude keeps that meaning. Ground pins resolve in batches of 256 against loaded terrain and, in the nearby camera footprint on photoreal maps, `clampToHeightMostDetailed`, then draw in one owned native BillboardCollection per file, with static positions and no per-frame point Entity visualizer or clamping callbacks. Point metadata remains in `dataSource.importedEntities` for framing/style inspection; non-point geometry stays in the normal source collection. Pin collections mount only after source admission, follow file show/hide, and are destroyed on removal. Settled camera/map-source changes and completed terrain loads refresh visible files through the same serial queue; distant points retain their latest available height. No detailed worldwide terrain sweep is started. Nearby mesh sampling can add requests/time; configured photoreal providers require separate validation. The depth test is disabled for ground pins, matching Draw's marker visibility policy.
|
||||
- **Navigation.** Row Fly-to uses `runImmediateNavigation('file', …)`; Cockpit can refuse it and existing follow/track ownership is released first. Automatic Fly-to is stamped when a batch is submitted and reasserted before flying. Pointer/wheel gestures and newer navigation supersede it, including time spent in the queue. Framing uses every finite visible point/path/polygon-hole/wall vertex plus rotated overlay bounds, with viewport-aware range and a nadir view. The target uses available terrain/cached ground height and the ground guard on arrival. Empty footprints never fall back to deferred `viewer.flyTo`.
|
||||
- **Clock.** Imports expose no source clock and no entity availability. KML times/view metadata are removed before loading; recorded tracks become static lines. GPX segments remain separate and GPX times are ignored. The shared clock stays unchanged.
|
||||
@@ -5799,6 +5864,7 @@ apply to `public/` assets or built-preview serving.
|
||||
- **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`, 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°`.
|
||||
- The transit, ALPR, vessel, CCTV, Local ADS-B, flight and military context, awareness, Cockpit and Street Level heading readouts round through `roundHeadingDegrees` in `src/data/headingDegrees.js` (whole degrees 0–359, `null` when not finite, so each caller keeps its own placeholder); `normalizeHeading` lives there too and `cockpitMath.js` re-exports it. The Cockpit's `TRACK ACQUIRED` signal uses `formatHeadingReadout`, so a 359.6° track reads `COURSE 000°`.
|
||||
- **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.
|
||||
- **Cockpit trail visibility:** entering cockpit hides the selected aircraft's trail body and head so they cannot cross the first-person view; exit restores them. This cockpit-only presentation change does not alter the normal map-mode invariant that aircraft trails render through terrain using their depth-fail material.
|
||||
@@ -6007,9 +6073,11 @@ easier to meet (detection is now on more often), but does not create it.
|
||||
OSM choice), so Hybrid fill is never replayed as OSM roads. Failed flow
|
||||
refreshes clear old matches and report simulation. `flowCoveragePct` is the
|
||||
percentage of shown dots on roads with flow, including all unmatched dots in
|
||||
the denominator and excluding dots hidden by closures. The OSM keyed status
|
||||
reads `LIVE · Roads: OpenStreetMap · Flow: TomTom · N% cov`. Road fetch
|
||||
coverage follows a 5×5 ellipsoid sample of the central screen square.
|
||||
the denominator and excluding dots hidden by closures. It reads 1–99 whenever
|
||||
both matched and simulated dots are shown, so rounding never turns a few
|
||||
matches into `(no matches)` or a few simulated dots into full coverage. The
|
||||
OSM keyed status reads `LIVE · Roads: OpenStreetMap · Flow: TomTom · N% cov`.
|
||||
Road fetch coverage follows a 5×5 ellipsoid sample of the central screen square.
|
||||
Horizon rays are capped at 10 km from nadir (reduced if necessary to fit
|
||||
16 z12 tiles); polygon-intersecting tiles load nearest first. Up to 16 z14
|
||||
tiles provide near detail, with z12 roads beyond them. Cached tiles serve
|
||||
@@ -6200,7 +6268,10 @@ The lockfile uses DOMPurify 3.4.17, protobufjs 8.8.0, PostCSS 8.5.28,
|
||||
source-map-js 1.2.2 and nanoid 3.3.19. Cesium remains on 1.138.0. Browser QA
|
||||
uses Puppeteer 25.10.0; image-processing tools use Sharp 0.35.5. QA scripts await Puppeteer's asynchronous
|
||||
executable-path lookup before testing or passing the path to Chrome. Supported Node versions remain
|
||||
24.14.x and 26.x. Use `npm ci` to reproduce the checked-in dependency tree.
|
||||
Node 24 (24.14.0 or later) and 26.x; the Docker image and CI pin 24.22.2,
|
||||
the current 24 LTS patch, which carries the October 21, 2026 Node.js security
|
||||
fixes first released in 24.22.1 (including CVE-2026-111497). Use
|
||||
`npm ci` to reproduce the checked-in dependency tree.
|
||||
|
||||
### Traffic city navigation
|
||||
|
||||
@@ -6342,7 +6413,9 @@ Receivers publish one record shape from `src/sources/adsbRecords.js`
|
||||
`lastMessageAt`, `messageCount`, `rssiDbfs`, `band`, `source`). `band` is `1090` or `978`; `source` is
|
||||
`webusb` (browser SDR) or `feed` (decoder feed).
|
||||
`normalizeDump1090Aircraft(json, nowMs, { band })` maps a dump1090/readsb/
|
||||
skyaware978 `aircraft.json` document into the same records. Voice
|
||||
skyaware978 `aircraft.json` document into the same records; a row without
|
||||
`baro_rate` reads its vertical rate from `geom_rate`, where a GNSS-sourced rate
|
||||
is sent. Voice
|
||||
`set_layer_visibility` accepts `local-adsb` ("local ADS-B", "my receiver",
|
||||
"my antenna").
|
||||
|
||||
@@ -6389,6 +6462,12 @@ three Cesium data sources; enabling rebuilds from the accepted parsed cache,
|
||||
and destroying clears it. Load ownership prevents cancelled work from adding
|
||||
entities after teardown.
|
||||
|
||||
Every Cesium entity from multipart cable or landing-point geometry stays tied
|
||||
to its source feature, including generated IDs and IDs that collide with
|
||||
Cesium's multipart suffixes. Each segment gets its own feature's styling, click
|
||||
destination and horizon-culling reference, and each source feature has one
|
||||
reference stem. The source collections are not modified.
|
||||
|
||||
Natural Earth regions and neighborhood polygon lookup are package exports.
|
||||
Their existing lazy loaders, retry behavior, bundled datasets and attribution
|
||||
are unchanged. The cable dataset remains CC BY-NC-SA 3.0 and is not covered by
|
||||
@@ -6529,8 +6608,8 @@ when their footprints touch or contain one another, never across a seam toleranc
|
||||
|
||||
### Shared native dropdowns and numeric fields
|
||||
|
||||
- Scene and CCTV selects share the default `gev-field` surface. HUD layout and
|
||||
draw colour retain compact density. Radio filter, tuner gain and FM frequency
|
||||
- Scene and CCTV selects share the default `gev-field` surface. HUD layout
|
||||
retains compact density; Draw's colour is a row of swatches, not a select. Radio filter, tuner gain and FM frequency
|
||||
use the same base while retaining their feature-specific skins and layout.
|
||||
- The CCTV inline number editor is rendered by its pose chip component with
|
||||
its existing precision and inline density. Enter/blur commit, Escape cancellation, frozen
|
||||
|
||||
Reference in new issue
Block a user