Files
gods-eye-view/src/data/renderAltitude.js
T
Bilawal Sidhu b6da93b8ac release: v0.1.0 — one-click install, keyless boot, in-app Provider Settings (#142)
The first formal release.

- One-click install via Pinokio; keyless boot lands on a live Esri World
  Imagery satellite globe with keyless terrain, with automatic OSM fallback
  and graceful degradation when terrain is unavailable.
- Provider Settings (the POWER UP panel): add, replace, or remove API keys
  inside the app; credential files made owner-only before any secret is
  written; external keys shown read-only; the panel refuses shared or proxied
  servers.
- Keyless capability responses for the optional HUD summary and place search.
- Aircraft-identity voice answers cover operator, type, and route, and say so
  plainly when enrichment is unavailable.
- README rewritten keyless-first; concise 0.1.0 changelog section; CI
  workflow included.

Gates: 2,672 unit tests pass / 0 fail, build clean, tracking regression
108/108, map-source tray QA pass, setup doctor ready.
2026-08-31 17:49:45 -05:00

97 lines
5.1 KiB
JavaScript

// src/data/renderAltitude.js — pure priority-chain helper for aircraft render
// altitude (docs/plans/2026-07-05-entity-height-datum-fix.md Task 6).
//
// The globe needs ELLIPSOIDAL height (h = H + N). OpenSky's `geo_altitude`
// (state-vector index 13) is already WGS84 geometric/ellipsoidal — the
// correct value to hand `Cartesian3.fromDegrees(lon, lat, h)` verbatim.
// `baro_altitude` (index 7) is barometric/MSL-referenced (and, unlike a true
// GPS-derived geo_altitude, also carries non-standard-pressure QNH error) —
// it is only a VISUAL FALLBACK when geo_altitude is missing, corrected by
// adding the local geoid undulation N (src/data/geoid.js) so it at least
// lands close to the ellipsoid instead of sinking into high-elevation
// terrain. This module makes NO claim that baro+N is geometrically exact.
//
// Kept as a small standalone module (rather than inlined in flights.js) so
// it can be unit-tested in isolation and reused verbatim by the military
// flights layer (Task 7).
/**
* @param {object} params
* @param {number|null|undefined} params.geoAltM - OpenSky geo_altitude (m, WGS84 ellipsoidal), if reported.
* @param {number|null|undefined} params.baroAltM - Barometric/MSL altitude (m), if reported.
* @param {boolean} params.onGround - OpenSky on_ground flag.
* @param {number|null|undefined} params.surfaceM - Cached ellipsoidal ground height at this lat/lon (Task 3's cachedEllipsoidalGround), if warm.
* @param {number|null|undefined} [params.geoidN] - Geoid undulation N (m) at this lat/lon; treated as 0 when absent.
* @returns {number|null} Ellipsoidal render height in metres, or `null` (sentinel) when
* none of the above are usable — the caller applies its OWN existing sticky/default
* fallback (this helper never invents one, so it can't drift from the caller's policy).
*/
export function pickRenderAltitudeM({ geoAltM, baroAltM, onGround, surfaceM, geoidN }) {
if (onGround && Number.isFinite(surfaceM)) {
return surfaceM;
}
if (Number.isFinite(geoAltM)) {
return geoAltM;
}
if (Number.isFinite(baroAltM)) {
return baroAltM + (Number.isFinite(geoidN) ? geoidN : 0);
}
return null;
}
/**
* Ground-surface reuse for a TAXIING on-ground aircraft.
*
* The ellipsoidal-ground cache (Task 3) keys by exact 5-decimal-rounded
* lat/lon (~1.1 m). The warm-up batch resolves the CURRENT poll's fix for a
* LATER poll, but an aircraft that moves more than ~1 m between polls (any
* taxiing traffic — polls are tens of seconds apart) rounds to a fresh key
* every poll, so `cachedEllipsoidalGround(currentLat, currentLon)` misses
* indefinitely and a no-baro/no-geo aircraft would stick at the 0 m sentinel
* while it moves. LAST poll's fix, however, WAS resolved by last poll's warm
* batch, so its cached ground is available now. Airport aprons/taxiways are
* ~flat, so the previous fix's ellipsoidal ground is a good stand-in for the
* current one — far better than sinking to 0 m at an elevated field.
*
* @param {number|null|undefined} currentM - Ground at the CURRENT poll fix (may be a cache miss → null).
* @param {number|null|undefined} previousM - Ground at the PREVIOUS poll fix (warmed last poll), if any.
* @returns {number|null} The first finite of [current, previous], else null (sentinel).
*/
export function reuseGroundedSurfaceM(currentM, previousM) {
if (Number.isFinite(currentM)) return currentM;
if (Number.isFinite(previousM)) return previousM;
return null;
}
/**
* Last-resort ground prior for a grounded contact whose floor cells are all
* cold and which reports NO altitude at all.
*
* The geoid is a crude stand-in: at a sea-level field it is the local ground to
* within metres, at an inland field it is tens of metres BELOW the rendered
* mesh. It earns its place only for a contact nothing else can describe — a
* first sighting, before any cell has warmed (the documented "born-grounded
* first poll" residual).
*
* It must NOT outrank a render height the contact already had. That was the
* 2026-08-21 defect: four Re:Earth refreshes in a row timed out, a parked
* contact's cells went cold, and this guess overwrote a height that had been
* sitting correctly on the mesh — dropping it through the ground until the
* proxy recovered. With `priorRenderM` present the caller's existing sticky
* fallback holds that height instead, which is what the owner asked for:
* "hold the last known altitude until a fresh one comes in."
*
* @param {object} params
* @param {number|null|undefined} params.geoAltM - Reported geo_altitude.
* @param {number|null|undefined} params.baroAltM - Reported baro_altitude.
* @param {number|null|undefined} params.priorRenderM - Render height this
* contact already had, if any.
* @param {number|null|undefined} params.geoidN - Geoid undulation N here.
* @returns {number|null} The geoid guess, or null when it must not be used.
*/
export function geoidSurfaceLastResortM({ geoAltM, baroAltM, priorRenderM, geoidN }) {
if (Number.isFinite(geoAltM) || Number.isFinite(baroAltM)) return null;
if (Number.isFinite(priorRenderM)) return null;
return Number.isFinite(geoidN) ? geoidN : null;
}