mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
feat(flights): publish verticalRateMps on the tracked-aircraft descriptor
`mapAnalystRecord` has always published `verticalRateMps`, but the tracked seam — `getTrackedInfo()` / `getTrackedSubject()`, built by `_describeFlight` — did not, so anything needing climb/descent had to reach past the public descriptor into the raw poll record. Both layers now map it, which matters because they store it under different keys: the civil records write `verticalRate` (records.js), the military ones write `verticalRateMps`. Mapping only one would leave `resolveTrackedAircraftInfo()` handing back two different shapes, and a consumer reading the field off a civil contact would silently read every military contact as level flight. Reported, never derived. `stickyNumber` seeds the field with null, so an aircraft that has not transmitted a rate publishes null rather than a level-flight 0 — absent is not level, and a flight-phase classifier that cannot tell them apart calls a climbing aircraft cruise. Tests: both layers, both halves (a reported rate arrives with its sign; an absent one stays null). Mutation-verified — removing either field or defaulting it to 0 turns them red. `docs/CURRENT-STATE.md` records the descriptor field, the two record keys it reconciles, and the reported-never-derived rule.
This commit is contained in:
1 parent
b210ab0fe4
commit
936d75b69e
6 files changed
+125
-5
No files matched your search
@@ -77,6 +77,11 @@
|
||||
Native point, billboard and label highlighting uses GPU draw commands; there
|
||||
is no scene-dimming effect selector. Unsupported shaders retain native contact
|
||||
rendering, and leaving Cyber restores the standard shell and contact treatment.
|
||||
- The tracked-aircraft descriptor now publishes `verticalRateMps`, on both the
|
||||
civil and military layers, so a consumer reading `getTrackedInfo()` no longer
|
||||
has to reach past it into the raw poll record for climb/descent. Reported,
|
||||
never derived: an aircraft that has not transmitted a rate reads as `null`
|
||||
rather than a level-flight `0`.
|
||||
|
||||
- Add a **Recent Imagery** data layer (NASA GIBS · HLS + VIIRS, keyless).
|
||||
Select a box (drag, the current view, or around a pin; up to 1,000 km a
|
||||
|
||||
@@ -567,6 +567,13 @@ use the supplied asset resolver, including the preload path. Enrichment receives
|
||||
an abort signal and cannot update a later lifecycle after destruction. Existing
|
||||
camera, terrain floor, trail, selection and measured model-size policies remain.
|
||||
|
||||
The tracked-aircraft descriptor both flight layers hand to the cockpit carries
|
||||
`verticalRateMps` beside `velocityMps` and `track`. Civil records store the rate as
|
||||
`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.
|
||||
|
||||
## Browser live-source observations
|
||||
|
||||
Flights, Military Flights and AIS Vessels obtain snapshots and optional history
|
||||
|
||||
@@ -487,7 +487,15 @@ const LABEL_ICAO = 'ae1fa4';
|
||||
const LABEL_POSITION = Cesium.Cartesian3.fromDegrees(-97.71, 30.21, 10_668);
|
||||
|
||||
/** Seed one civil contact through the production refresh seam. */
|
||||
function seedLabelContact({ callsign, registration, tracked = false }) {
|
||||
function seedLabelContact({
|
||||
callsign,
|
||||
registration,
|
||||
tracked = false,
|
||||
// Per-test metadata overrides, spread last so a test can set a field to
|
||||
// `undefined` to model "the feed never sent it" — a default parameter
|
||||
// could not express that, it would just re-supply the default.
|
||||
meta: metaOverrides = {},
|
||||
}) {
|
||||
_setTrackedFlightRefreshStateForTest({
|
||||
icao24: LABEL_ICAO,
|
||||
entity: null,
|
||||
@@ -509,6 +517,7 @@ function seedLabelContact({ callsign, registration, tracked = false }) {
|
||||
turnRateDps: 0,
|
||||
rawLat: 30.21,
|
||||
rawLon: -97.71,
|
||||
...metaOverrides,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -574,6 +583,44 @@ test('civil label chain: the cockpit descriptor exposes a trimmed registration',
|
||||
assert.equal(flightsLayer.getTrackedInfo()?.registration, null);
|
||||
});
|
||||
|
||||
test('the tracked descriptor publishes the reported vertical rate', () => {
|
||||
// `mapAnalystRecord` has always published `verticalRateMps`; the tracked
|
||||
// seam did not, so a consumer that needs climb/descent (a flight-phase
|
||||
// classifier, the cockpit) had to reach past getTrackedInfo() into the
|
||||
// raw record. Both seams now answer with the same field name and unit.
|
||||
seedLabelContact({
|
||||
callsign: 'SWA696',
|
||||
registration: 'N123AB',
|
||||
tracked: true,
|
||||
meta: { verticalRate: -6.4 },
|
||||
});
|
||||
assert.equal(flightsLayer.getTrackedInfo()?.verticalRateMps, -6.4);
|
||||
assert.equal(
|
||||
mapAnalystRecord(LABEL_ICAO, { verticalRate: -6.4 }).verticalRateMps,
|
||||
-6.4,
|
||||
'the analyst seam and the tracked seam must agree',
|
||||
);
|
||||
});
|
||||
|
||||
test('an unreported vertical rate stays null, never a level-flight zero', () => {
|
||||
// Absent is not level. `stickyNumber` seeds this field with null, so a
|
||||
// contact that has never transmitted a rate must not read as 0 m/s — a
|
||||
// phase classifier would call that cruise while the aircraft climbs.
|
||||
for (const missing of [undefined, null, Number.NaN]) {
|
||||
seedLabelContact({
|
||||
callsign: 'SWA696',
|
||||
registration: 'N123AB',
|
||||
tracked: true,
|
||||
meta: { verticalRate: missing },
|
||||
});
|
||||
assert.equal(
|
||||
flightsLayer.getTrackedInfo()?.verticalRateMps,
|
||||
null,
|
||||
`vertical rate ${String(missing)} must publish as null`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('civil label chain: identity stays icao24 while the label moves', () => {
|
||||
for (const fixture of [
|
||||
{ callsign: 'SWA696', registration: 'N123AB' },
|
||||
|
||||
@@ -603,3 +603,48 @@ test('two contacts matching at the same strength resolve deterministically', ()
|
||||
assert.equal(first, 'aef001', 'the stable key (lowest hex) wins, not the feed order');
|
||||
assert.equal(second, first, 'and the same query resolves the same way every time');
|
||||
});
|
||||
|
||||
test('the military tracked descriptor publishes the reported vertical rate', () => {
|
||||
// Shape parity with the civil layer's getTrackedInfo(). The two layers store
|
||||
// the rate under different record keys (`verticalRateMps` here,
|
||||
// `verticalRate` there), so `resolveTrackedAircraftInfo` only hands back one
|
||||
// shape if BOTH descriptors map it — a consumer that reads the field off the
|
||||
// civil seam and finds it missing on the military one would silently treat
|
||||
// every military contact as level flight.
|
||||
const seedRate = (verticalRateMps) =>
|
||||
_setTrackedMilitaryRefreshStateForTest({
|
||||
icao24: 'ae7f01',
|
||||
entity: { gevLabelModel: { title: 'TEST', details: [] } },
|
||||
billboard: {
|
||||
position: Cesium.Cartesian3.fromDegrees(-97.0, 31.0, 8_000),
|
||||
color: Cesium.Color.WHITE,
|
||||
show: true,
|
||||
},
|
||||
billboardCollection: { show: true, remove() {} },
|
||||
viewer: { camera: { positionCartographic: null }, scene: {} },
|
||||
meta: {
|
||||
callsign: 'RCH512',
|
||||
rawLat: 31,
|
||||
rawLon: -97,
|
||||
altitudeFt: 26_000,
|
||||
speedMps: 220,
|
||||
track: 95,
|
||||
onGround: false,
|
||||
verticalRateMps,
|
||||
},
|
||||
});
|
||||
|
||||
seedRate(12.7);
|
||||
assert.equal(militaryFlightsLayer.getTrackedInfo()?.verticalRateMps, 12.7);
|
||||
|
||||
// Absent is not level: a contact that has never transmitted a rate must read
|
||||
// as null, not as a 0 m/s a phase classifier would call cruise.
|
||||
for (const missing of [undefined, null, Number.NaN]) {
|
||||
seedRate(missing);
|
||||
assert.equal(
|
||||
militaryFlightsLayer.getTrackedInfo()?.verticalRateMps,
|
||||
null,
|
||||
`vertical rate ${String(missing)} must publish as null`,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -113,7 +113,7 @@ export function createQueries({
|
||||
* Build a public descriptor for one aircraft using its best current position
|
||||
* (dead-reckoned when history exists, billboard position otherwise).
|
||||
* @param {string} icao24 - ICAO 24-bit transponder address.
|
||||
* @returns {{icao24: string, callsign: string|null, position: Cesium.Cartesian3, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null}|null}
|
||||
* @returns {{icao24: string, callsign: string|null, position: Cesium.Cartesian3, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null, verticalRateMps: number|null}|null}
|
||||
* Descriptor with a cloned position, or null if the aircraft is unknown.
|
||||
*/
|
||||
|
||||
@@ -163,6 +163,15 @@ export function createQueries({
|
||||
onGround: info?.onGround === true,
|
||||
velocityMps: displayed.speedMps,
|
||||
track: displayed.trackDeg,
|
||||
// Climb/descent rate, the one kinematic field `mapAnalystRecord` already
|
||||
// published (as `verticalRateMps`) but this descriptor did not, so a
|
||||
// consumer reading the tracked seam had to reach back into `_flightData`
|
||||
// for it. Reported, never derived: `stickyNumber` in records.js keeps the
|
||||
// last value across a blank poll, and an absent rate stays null rather
|
||||
// than becoming a level-flight 0 the aircraft never transmitted.
|
||||
verticalRateMps: Number.isFinite(info?.verticalRate)
|
||||
? info.verticalRate
|
||||
: null,
|
||||
stale: Boolean(
|
||||
flightState.records.missingPolls.get(icao24) ||
|
||||
flightState.feed._backoff,
|
||||
@@ -834,7 +843,7 @@ export function createQueries({
|
||||
|
||||
/**
|
||||
* Describe the currently tracked aircraft at its dead-reckoned position.
|
||||
* @returns {{icao24: string, callsign: string|null, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null}|null}
|
||||
* @returns {{icao24: string, callsign: string|null, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null, verticalRateMps: number|null}|null}
|
||||
* Tracked aircraft info, or null when nothing is tracked.
|
||||
*/
|
||||
getTrackedInfo() {
|
||||
|
||||
@@ -86,7 +86,7 @@ export function createQueries({
|
||||
* Build a public descriptor for one aircraft using its best current position
|
||||
* (dead-reckoned when history exists, billboard position otherwise).
|
||||
* @param {string} icao24 - ICAO hex identifier of the aircraft.
|
||||
* @returns {{icao24: string, callsign: string|null, position: Cesium.Cartesian3, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null}|null}
|
||||
* @returns {{icao24: string, callsign: string|null, position: Cesium.Cartesian3, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null, verticalRateMps: number|null}|null}
|
||||
* Descriptor with a cloned position, or null if the aircraft is unknown.
|
||||
*/
|
||||
|
||||
@@ -129,6 +129,13 @@ export function createQueries({
|
||||
onGround: info?.onGround === true,
|
||||
velocityMps: displayed.speedMps,
|
||||
track: displayed.trackDeg,
|
||||
// Same field, same name, same null-when-absent rule as the civil
|
||||
// descriptor — the two layers store the record under different keys
|
||||
// (`verticalRateMps` here, `verticalRate` there), so only the mapping
|
||||
// makes `resolveTrackedAircraftInfo` hand back one shape for both.
|
||||
verticalRateMps: Number.isFinite(info?.verticalRateMps)
|
||||
? info.verticalRateMps
|
||||
: null,
|
||||
stale: Boolean(
|
||||
flightState.records.missingPolls.get(icao24) ||
|
||||
flightState.feed._backoff,
|
||||
@@ -755,7 +762,7 @@ export function createQueries({
|
||||
|
||||
/**
|
||||
* Describe the currently tracked aircraft at its dead-reckoned position.
|
||||
* @returns {{icao24: string, callsign: string|null, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null}|null}
|
||||
* @returns {{icao24: string, callsign: string|null, latitude: number, longitude: number, altitudeM: number, velocityMps: number|null, track: number|null, verticalRateMps: number|null}|null}
|
||||
* Tracked aircraft info, or null when nothing is tracked.
|
||||
*/
|
||||
getTrackedInfo() {
|
||||
|
||||
Reference in new issue
Block a user