mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix: address GNSS integrity and aircraft emergency review findings
This commit is contained in:
1 parent
4240d04642
commit
01a8ad7aa7
44 files changed
+1282
-388
No files matched your search
+45
-33
@@ -817,31 +817,26 @@ camera, terrain floor, trail, selection and measured model-size policies remain.
|
||||
## Transponder emergency highlighting
|
||||
|
||||
`src/data/aircraftEmergency.js` derives one status per poll from the Mode A
|
||||
squawk (7500 unlawful interference, 7600 radio failure, 7700 general emergency;
|
||||
ICAO-standard worldwide) and, where the feed carries it, the readsb ADS-B
|
||||
`emergency` field (adsb.lol; OpenSky state vectors carry the squawk only). An
|
||||
explicit ADS-B status wins over the squawk. `general`, `unlawful`, `nordo` and
|
||||
`downed` are emergencies; `minfuel` and `lifeguard` are priorities.
|
||||
squawk (7500 unlawful interference, 7600 radio failure, 7700 general emergency)
|
||||
and the readsb ADS-B emergency/priority field where present. Higher severity
|
||||
wins, with ADS-B taking a tie. GEV treats `minfuel` and `lifeguard` as priorities;
|
||||
`general`, `unlawful`, `nordo` and `downed` are emergency statuses.
|
||||
|
||||
Both aircraft layers keep `squawk` and `emergency` on each record and replace
|
||||
them on every poll, so the status is never sticky. Civil and military fleet
|
||||
billboards and Cockpit far-contact dots are tinted `EMERGENCY_TINT_CSS` red
|
||||
only for emergencies; priorities keep the normal tint. The military layer uses
|
||||
the same tint when a billboard is first created and when a tracked contact is
|
||||
released. While a broadcasting aircraft is tracked, the Cockpit aircraft-meta
|
||||
line reads `SQUAWK 7700 · GENERAL EMERGENCY` when the code names the status,
|
||||
else `ADS-B EMERGENCY · …` or `ADS-B PRIORITY · …`, in red. Analyst records and
|
||||
the voice query fields expose `squawk`, `emergency` (flag), `emergencyKind` and
|
||||
`emergencySource` (`squawk` when the Mode A code names the status, `ads-b` for
|
||||
the readsb field), plus `emergencyBroadcast` (`broadcasting squawk 7700 (general
|
||||
emergency)` vs `ADS-B reports general emergency`) and `lastContactAtMs`, the latest known general aircraft-message contact in epoch
|
||||
ms. Missing contact times retain the preceding known contact, or stay null if
|
||||
none is known. This does not timestamp the current emergency broadcast. Civil
|
||||
adsb.lol fallback preserves the readsb status in `emergencyByIcao` alongside
|
||||
unchanged OpenSky-compatible vectors, including clearing on subsequent polls.
|
||||
Compact voice results retain the emergency flag, source, phrase and named
|
||||
last-contact time; they do not label that time as the status receipt.
|
||||
`scripts/qa-emergency-squawk.mjs` proves the tint, HUD line and clearing.
|
||||
Civil and military fleet billboards, 3D models and Cockpit dots turn red for
|
||||
emergencies and repaint when the broadcast changes. Priorities retain normal
|
||||
colors; tracked map colors keep their existing selection treatment. The Cockpit
|
||||
meta line names the squawk or ADS-B status (only emergencies are red), replacing
|
||||
COURSE ALIGNED while a status is present.
|
||||
|
||||
Analyst and tracked voice results describe broadcasts, not confirmed incidents.
|
||||
Fields include `squawk`, `emergency`, `emergencyKind`, `emergencySource` and
|
||||
`emergencyBroadcast`. `lastContactAtMs` is the last known transponder contact,
|
||||
not the arrival time of this status; missing contact times retain known contact.
|
||||
Civil fallback preserves status alongside unchanged OpenSky vectors. One poll
|
||||
omitting ADS-B status may retain the previous status when the squawk is unchanged;
|
||||
explicit clears and changed squawks take effect immediately. The next omitted
|
||||
poll expires that retention. `scripts/qa-emergency-squawk.mjs` checks billboard
|
||||
and model transitions, Cockpit HUD, clearing and tracking release.
|
||||
|
||||
## Browser live-source observations
|
||||
|
||||
@@ -3129,10 +3124,33 @@ its criteria cannot be silently ignored.
|
||||
| FIRMS Active Fires ▲ | NASA FIRMS live (VIIRS ×3 NRT + MODIS NRT, trailing 24h) | `src/data/firmsHeatmap.js` | `/api/firms` (`FIRMS_MAP_KEY`) | 10 min (proxy TTL 30 min) |
|
||||
| Street Level 📷 | Street-level imagery; Mapillary is the first provider (vector tiles, Graph API, MapillaryJS) | `src/layers/streetLevel/` via `src/app/layers/streetLevel.js` | `/api/mapillary/status`, `/api/mapillary/tiles/coverage/{z}/{x}/{y}` (`MAPILLARY_CLIENT_TOKEN`) | camera-driven (320 ms debounce, ≤9 tiles, cached 24 h) |
|
||||
| Wind 🌬 | NOAA GFS 10 m wind (keyless, 0.25°→1° grid; animated particles) | `src/data/wind.js` | `/api/wind` | 1 h (forecast cycle) |
|
||||
| GNSS Integrity 📡 (`gnss-interference`) | adsb.lol point snapshot, low navigation accuracy binned per 0.5° cell (GEV NIC/NACp threshold + gpsjam.org formula and bands, keyless, ODbL) | `src/layers/gnss/` via `src/app/layers/gnss.js` | `/api/gnss-integrity` | camera-driven (600 ms debounce, minimum 2.5 s between reads) + 60s poll (server cache 60 s; 30 min rolling window) |
|
||||
| GNSS Integrity 📡 (`gnss-integrity`) | adsb.lol point snapshot, low navigation accuracy binned per 0.5° cell (GEV NIC/NACp threshold + gpsjam.org formula and bands, keyless, ODbL) | `src/layers/gnss/` via `src/app/layers/gnss.js` | `/api/gnss-integrity` | camera-driven (600 ms debounce, minimum 2.5 s between reads) + 60s poll (server cache 60 s; 30 min rolling window) |
|
||||
| Fire Perimeters 🔥 | NIFC WFIGS current interagency perimeters (keyless, paged past the 2000-record cap); InciWeb catalog + incident page origin and update time checks for verified incident-page links | `src/layers/perimeters/` via `src/app/layers/perimeters.js` | `/api/fire-perimeters` + `/api/fire-perimeters/inciweb/*` | 5 min (server caches: catalog 1 h; publication 30 min) |
|
||||
|
||||
GNSS display activation stays enabled through transient feed failures, including the first read. The row reports unavailable/degraded source health, a specific HTTP cause and the next automatic retry time; successful recovery clears the warning. Retry-After is honored in full for provider cooldowns, with 30/60/120 s bounded backoff otherwise. Camera movement, polling and toggling cannot bypass the deadline; disable/destroy cancels retries. Genuine non-retryable requests and renderer failures remain lifecycle failures. GNSS Integrity automatically refreshes after camera pan/zoom (600 ms debounce, minimum 2.5 s between reads, shared whole-degree cache keys) through the manager refresh lane, so counts and loading/error state update without toggling. The anchor is the Earth point under the screen centre, including at globe scale where the visible rectangle can cover the entire world. It fetches a capped 250-nautical-mile-radius adsb.lol snapshot around that anchor, keeps each aircraft once per cell over 30 minutes as the camera pans, and draws low/medium/high ground rectangles only for cells with at least 3 aircraft. The row legend counts cells per band and visibly labels the regional 250 nm sampling radius. Zooming out does not turn this public point feed into worldwide coverage; blank areas can lie outside the sampled region or have fewer than three qualifying aircraft. The method is a GEV threshold plus gpsjam.org aggregation, not the gpsjam method: the per-aircraft rule (NIC < 7 or NACp < 8, borrowed from the US 14 CFR 91.227 ADS-B Out minima, or readsb's GPS-loss flag) and the 30-minute visited-view window are GEV-defined and unvalidated; only the cell formula and 2% / 10% bands come from gpsjam.org. `layer.provenance` (also on `getStats()`) records each part, and the proxy tags its rows with `classifier: gev-nic-nacp-v1`. Cells carry `state: navigation-integrity-anomaly` (medium/high) or `nominal`; `interpretation: suspected-gnss-interference` is a secondary reading, not a detection or a located emitter. Only DO-260A/B (ADS-B versions 1 and 2) direct 1090ES positions with a known `seen_pos` of 0–60 s count. Presence and degraded evidence age separately: a degraded report keeps the aircraft degraded in its cell for 30 minutes after that report, however many healthy reports follow, and an aircraft unheard for 30 minutes starts fresh. Rows are dated by the snapshot's observation time, which the proxy keeps on cached and stale answers and reports as `ageMs` on its own clock (so browser clock skew does not move the window); a replayed snapshot never restarts the window; the proxy stops serving a stale snapshot once it is older than 30 minutes, the source refuses an undated one, and cells age out on schedule during fetch failures or while there is no view anchor.
|
||||
GNSS Integrity samples a 250 nm circle around the Earth point under the screen
|
||||
centre. Camera reads debounce for 600 ms with a monotonic 2.5 s minimum gap;
|
||||
nearby views share whole-degree cache keys, and a minute poll refreshes data.
|
||||
Counts and bands cover visited views over 30 minutes, including regions now off
|
||||
screen. Non-pickable ground primitives preserve Street Level and perimeter
|
||||
selection. The legend describes a low-accuracy score, not the raw aircraft share;
|
||||
`getStats().levels` exposes nominal, medium and high counts separately.
|
||||
|
||||
Transient source failures keep activation enabled and show unavailable/degraded
|
||||
health with the next automatic retry. Upstream 429 deadlines are capped at one
|
||||
hour, with the longest concurrent deadline retained; 5xx, timeout and malformed
|
||||
responses impose a 15 s server cooldown. Cache hits and shared reads do not spend
|
||||
upstream admission. Cache age and cooldown use a monotonic clock; observation
|
||||
age comes from the upstream snapshot and is preserved in the row and voice stats.
|
||||
Snapshots older than 30 minutes or without a source timestamp are refused.
|
||||
Disable/destroy cancels pending requests, timers, subscriptions and primitives.
|
||||
|
||||
The GEV classifier remains unvalidated; anomalies do not confirm interference.
|
||||
See [method, provenance and limits](../DATA_SOURCES.md#gnss-integrity), including
|
||||
MLAT-positioned GPS loss, derived-field exclusions and the NIC 0 exception.
|
||||
`scripts/qa-gnss.mjs` covers live freshness, bands, built primitives, camera/globe
|
||||
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.
|
||||
|
||||
@@ -4482,7 +4500,7 @@ are omitted rather than framing the wrong part of the globe.
|
||||
|
||||
- **The TRACKED contact's 2D↔3D handoff is DEFAULT behaviour (2026-08-19), driven by camera distance alone.** It does NOT consult the DISPLAY-rail `3D` toggle, which continues to own the FLEET (the un-instanced draw-call budget stays the operator's decision). Policy lives in `src/data/trackedModelRegime.js` and is shared by both layers: enter below `TRACKED_MODEL_ENTER_ALT_M` = 150,000 m and hand back to the billboard only above `TRACKED_MODEL_EXIT_ALT_M` = 172,500 m. **The swap distance was set by playtesting on 2026-08-20:** a first pass at 1,000,000 m switched too early; 2D reads correctly at ~600 km and the handoff belongs at ~150 km. **Consequence, recorded on purpose:** the tracked contact now enters 3D NEARER than the FLEET does (`MODEL_ALT_CEIL_M` = 800,000 m, unchanged), so with the DISPLAY-rail `3D` toggle on, 150–800 km draws surrounding contacts as models while the selected one is still a glyph. Nothing double-draws (the fleet pass skips the tracked icao) and aligning the two is a fleet-side decision, deliberately out of scope. **The two thresholds are asymmetric on purpose:** a single threshold makes a tracked orbit sitting ON the boundary strobe billboard↔model as the camera's altitude wobbles across it. Do not collapse them. The latch is scoped per selection, so a new target re-evaluates against the ENTER ceiling rather than inheriting the previous target's exit band. Exactly ONE model is involved; it loads on demand when the regime opens, is HIDDEN (not released) on regime exit so re-entry has no load gap, and is released by the existing teardown on deselect/re-track/destroy. Cockpit and TR-3B suppression are unchanged. **Two invariants around it:** (a) the hysteresis latch AND the load-failure latch are per-selection state cleared by `_resetTrackedSelectionState()` in the tracking lifecycle (deselect / re-track / cross-layer / init / destroy) — the predicate's icao-change guard is defence only, since it needs a drawn frame while nothing is selected and the render governor's idle mode does not promise one; (b) on-demand loading is bounded at 3 attempts per selection with a 1.5 s backoff and one console warning naming the asset — the driver runs every `scene.preUpdate`, so an unbounded catch means a missing GLB spins load→reject at frame rate. The billboard stays the visual throughout a failed load.
|
||||
- **Grounded 3D handoff is terrain-validity gated (2026-08-23).** A ready civilian or military glTF does not own the visual until `groundSnap` (`src/data/groundSnap.js`) can answer with a MEASURED photoreal-surface height. On success the layer writes `height + the model's measured belly offset` before revealing the model. Cache movement is measured on the WGS84 surface, not across altitude, so a stationary contact keeps its snap through poll-time vertical-datum changes. Model existence or GPU readiness alone never suppresses the billboard floor, and ordinary zoom/style/deselect transitions preserve a valid snap cache. **Two states, and the difference is the whole design.** COLD — nothing has ever resolved for this icao (tiles still streaming on first sight, sample failure, backoff after a first miss): there is no evidence of where the ground is, `heightFor` returns null, the model stays hidden and the depth-test-free 2D billboard remains opaque and floored. WARM — a snap resolved and then a >`MOVE_INVALIDATE_M` (50 m) taxi move stopped it answering directly: the measurement is DEMOTED to a bounded last-known rather than deleted, and it keeps answering while the resample is outstanding, so a taxiing aircraft does not pop 3D→2D→3D across a 2–30 s retry backoff. The bound is `HELD_SNAP_MAX_DRIFT_M` = 250 m from the spot the value was measured at, past which the hold is dropped rather than stretched and the contact is COLD again. It is spatial with no timer beside it (ground under a contact that has not moved does not change; what invalidates the value is the contact MOVING) and deliberately a quarter of the billboard chain's `HELD_FLOOR_MAX_DRIFT_KM` — that hold only ever RAISES a sprite, while a held snap IS the model's placement, so its error shows in both directions. A fresh sample releases the hold, and so does a ground flip (which already calls `forget`). **A loading model is HIDDEN, never zero-scaled:** admission sets `show = false` (Cesium's default is `true`, and an unplaced primitive would claim the visual at the identity matrix), and ownership is `ready && show` — Cesium 1.138's `Model.update` has no `show` guard, so hiding a primitive costs its load nothing.
|
||||
- Commercial and military aircraft use the same high-level FLEET model regime: 2D billboards when zoomed out, optional glTF models when closer, controlled by the DISPLAY rail `3D` toggle and `Proximity` / `All` modes. Since 2026-08-16 (Hangar fleet) models are PER-CLASS: real CC-BY GLBs for light/bizjet/turboprop/widebody/helicopter/uav (`CLASS_MODEL_REAL` in `src/data/aircraftClass.js` — meters-baked, scale 1, per-model belly/radius; provenance in `public/models/README.md`), the shared `airplane.glb` for the remaining civilian classes, and the military layer maps weight classes (real GLBs / 747 heavies / `jet.glb` fastjets, per-model heading offsets, always flat amber). Textured civilians carry a HEAVY tint, not a light one: `MODEL_COLOR_BLEND_AMOUNT` is `0.94` in BOTH layers under Cesium's `ColorBlendMode.MIX`, so the class colour supplies 94% of the surface and the asset's own texture ~6%. The visual direction is clean light silhouettes with only a weak diffuse contribution from the approved textures, so liveries deliberately do NOT read. IR boost raises the blend to a full `1.0`. Under NVG/FLIR (map preset or Cockpit vision) models render unlit flat-white at full alpha and scene fog is disabled (fog otherwise blacks out distant models with the globe hidden); state restores on exit.
|
||||
- Commercial and military aircraft use the same high-level FLEET model regime: 2D billboards when zoomed out, optional glTF models when closer, controlled by the DISPLAY rail `3D` toggle and `Proximity` / `All` modes. Since 2026-08-16 (Hangar fleet) models are PER-CLASS: real CC-BY GLBs for light/bizjet/turboprop/widebody/helicopter/uav (`CLASS_MODEL_REAL` in `src/data/aircraftClass.js` — meters-baked, scale 1, per-model belly/radius; provenance in `public/models/README.md`), the shared `airplane.glb` for the remaining civilian classes, and the military layer maps weight classes (real GLBs / 747 heavies / `jet.glb` fastjets, per-model heading offsets, flat amber or emergency red). Textured civilians carry a HEAVY tint, not a light one: `MODEL_COLOR_BLEND_AMOUNT` is `0.94` in BOTH layers under Cesium's `ColorBlendMode.MIX`, so the class colour supplies 94% of the surface and the asset's own texture ~6%. The visual direction is clean light silhouettes with only a weak diffuse contribution from the approved textures, so liveries deliberately do NOT read. IR boost raises the blend to a full `1.0`. Under NVG/FLIR (map preset or Cockpit vision) models render unlit flat-white at full alpha and scene fog is disabled (fog otherwise blacks out distant models with the globe hidden); state restores on exit.
|
||||
- The DISPLAY-rail `3D` toggle is the user-facing activation path for both aircraft layers. Their small approved GLBs build their render resources without Cesium's frame-spread job queue, preventing continuous Photorealistic 3D Tiles streaming from starving model readiness; model caps, tracking, camera, and fallback billboards are unchanged.
|
||||
- **The `3D` toggle DEFAULTS ON in `proximity` on a first run.** Proximity is itself the budget — models appear only below `MODEL_ALT_CEIL_M` and only for the nearest `MODEL_MAX` in view — so the default costs nothing at globe scale, and `all` remains a deliberate opt-in. A fresh boot runs NO layer-state restoration (`LayerStateCoordinator.start()` returns early with neither a share payload nor stored state), so four independent initializers decide what a first-run operator sees and must agree: `booleanOption('models3d', 'e', true)` in `src/data/layerState.js`, `_models3dEnabled = true` in BOTH flight layers, `this._models3dEnabled = true` in `src/ui.js`, and the `active` / `visible` classes on `#models3d-toggle` / `#models3d-mode-row` in `index.html`. All four are pinned together in `src/data/layerState.test.mjs`. Explicit state still wins: because the codec omits default-valued options, `models3d: false` is now what travels in a link (`lo=…f.e.0`) and restores OFF at both aircraft layers. **Consequence for returning users:** a stored `gev:layer-state:v2` blob is a FULL options snapshot, so a session that wrote one before this change carries `models3d:false` and keeps 3D off until the operator flips it (or clears the key) — the durable snapshot is treated as the recipient's own state, by design.
|
||||
- **Consequence of the flip on the recorded tracked/fleet inversion:** the 150–800 km band where surrounding contacts draw as models while the SELECTED one is still a glyph is now what an operator sees WITHOUT arming anything. The inversion itself is unchanged and still deliberate (see `src/data/trackedModelRegime.js`); only its reachability changed.
|
||||
@@ -5336,9 +5354,3 @@ datacenters, dams and the Nepal locator retain that shared credit while displaye
|
||||
|
||||
Military fragments stitch by source identity. Different mapped parcels merge only
|
||||
when their footprints touch or contain one another, never across a seam tolerance gap.
|
||||
|
||||
GNSS integration reserves share token `3`, retaining published Street Level `0`.
|
||||
It uses the catalog source contract and voice layer manifest. Upstream 429
|
||||
Retry-After seconds/HTTP dates retain their full deadlines; overlapping requests
|
||||
keep the longest cooldown. Live GNSS QA requires a successful dated response
|
||||
even when there are no qualifying cells.
|
||||
Reference in new issue
Block a user