feat(layers): highlight aircraft broadcasting a transponder emergency

Flights and Military Flights tint aircraft red while the latest poll
carries squawk 7500/7600/7700 or an adsb.lol ADS-B emergency status
(general/unlawful/nordo/downed). Minimum-fuel and lifeguard priorities
are reported but not tinted. The Cockpit HUD aircraft-meta line names
the broadcast code in red. The status is not sticky: a cleared code
stops highlighting on the next refresh.

The analyst/voice query engine gains squawk, emergency and
emergencyKind fields. No new source or request: OpenSky state vectors
already carry the squawk and adsb.lol/readsb carries both fields.

Built by the Hex project (Crew Leo Agile dev team).

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
Leo PechnickiandClaude authored and Mazeyar Moeini Feizabadi committed 2026-10-13 17:22:29 +02:00
1 parent 9beadbda63
commit eb7f059750
26 files changed
+1174 -107

No files matched your search

+6
View File
@@ -7,6 +7,7 @@ export {
ERROR_BACKOFF_INTERVAL,
} from './recordPolicy.js';
import * as Cesium from 'cesium';
import { EMERGENCY_TINT_CSS } from '../../data/aircraftEmergency.js';
export const FOCUS_EVIDENCE_DEV = import.meta.env?.DEV === true;
@@ -14,6 +15,11 @@ export const FOCUS_EVIDENCE_DEV = import.meta.env?.DEV === true;
export const MIL_TINT = Cesium.Color.fromCssColorString('#FFB800');
/** Red tint for aircraft broadcasting an emergency (7500/7600/7700 or ADS-B status). */
export const EMERGENCY_TINT =
Cesium.Color.fromCssColorString(EMERGENCY_TINT_CSS);
// --- Ground traffic (owner reversal 2026-07-03: "absolutely we should see planes
// taxiing and landing") -----------------------------------------------------------
// Present-but-grounded planes are RENDERED instead of being skipped: same class
+12 -1
View File
@@ -167,6 +167,9 @@ export function createQueries({
flightState.records.missingPolls.get(icao24) ||
flightState.feed._backoff,
),
// This poll's transponder broadcast (not sticky; see records.js).
squawk: info?.squawk ?? null,
emergency: info?.emergency ? { ...info.emergency } : null,
airline: info?.airline ?? null,
// CLASS label follows the TR-3B conversion so every downstream card
// (cockpit, Contacts, analyst) agrees with the triangle on screen.
@@ -219,7 +222,9 @@ export function createQueries({
* lon: number|null, altitudeM: number|null, speedMps: number|null,
* heading: number|null, verticalRateMps: number|null, onGround: boolean,
* military: boolean, aircraftClass: string|null, originCountry: string|null,
* operator: string|null, routeOrigin: string|null, routeDestination: string|null}}
* operator: string|null, squawk: string|null, emergency: boolean,
* emergencyKind: string|null, routeOrigin: string|null,
* routeDestination: string|null}}
*/
function mapAnalystRecord(
@@ -253,6 +258,12 @@ export function createQueries({
aircraftClass: tr3bAircraftClass(icao24, text(info?.klass)),
originCountry: text(info?.originCountry),
operator: text(info?.airline),
squawk: text(info?.squawk),
// True while this poll broadcast an emergency (7500/7600/7700 or the
// ADS-B status); priority statuses (lifeguard, minfuel) report the kind
// without the flag.
emergency: info?.emergency?.severity === 'emergency',
emergencyKind: text(info?.emergency?.kind),
routeOrigin: routeOk ? text(info?.route?.origin?.code) : null,
routeDestination: routeOk ? text(info?.route?.destination?.code) : null,
};
+5
View File
@@ -4,6 +4,7 @@ import {
pickRenderAltitudeM,
} from '../../data/renderAltitude.js';
import { classifyAircraft } from '../../data/aircraftClass.js';
import { aircraftEmergency } from '../../data/aircraftEmergency.js';
import {
approxDistanceKm,
GROUND_FLOOR_CLAMP_RADIUS_KM,
@@ -226,6 +227,10 @@ export class FlightRecords {
// like callsign so a transient blank row doesn't blank the field.
originCountry:
stickyText(origin_country, prevMeta?.originCountry) || null,
// Per poll, NOT sticky: an emergency code clears when the crew resets
// it, so a blank squawk must not hold a stale 7700 on screen.
squawk: observation.squawk ?? null,
emergency: aircraftEmergency(observation),
// OpenSky distinguishes the last position epoch from the last
// transponder message. The fleet coast horizon uses this actual
// contact time so a temporarily old position does not hard-freeze
+10
View File
@@ -106,3 +106,13 @@ test('ground transitions preserve last render height while a surface cell is col
assert.equal(next.meta.renderAltitudeM, 1040);
assert.equal(next.meta.wasAirborne, true);
});
test('civil emergency status follows the latest poll instead of sticking', () => {
const store = records();
const first = store.receive(observation({ squawk: '7700' }), view());
assert.equal(first.meta.squawk, '7700');
assert.equal(first.meta.emergency.kind, 'general');
const next = store.receive(observation({ squawk: '2000' }), view());
assert.equal(next.meta.squawk, '2000');
assert.equal(next.meta.emergency, null);
});
+21 -8
View File
@@ -3,6 +3,7 @@ import { cyberSonarBaseAlpha } from '../../cyberSonar.js';
import { selectModelEligible } from '../../data/modelEligibility.js';
import { civilAircraftModelSpec } from './modelSpec.js';
import { CLASS_SCALE_2D } from '../../data/aircraftClass.js';
import { isEmergency } from '../../data/aircraftEmergency.js';
import { cockpitContactDotImage } from '../../data/cockpitContactDot.js';
import { aircraftIcon, TRACKED_ICON_PX } from '../../data/aircraftIcons.js';
import {
@@ -13,6 +14,7 @@ import {
import { limitCourseStep, courseSlewCapDps } from '../../data/motionModel.js';
import {
MIL_TINT,
EMERGENCY_TINT,
GROUND_SCALE,
COCKPIT_CONTACT_SIZE_PX,
COCKPIT_CIVILIAN_COLOR,
@@ -54,13 +56,28 @@ export function createRendering({
const { applyAircraftBillboardTreatment, applyAircraftModelTreatment } =
services.recession;
/** Fleet (untracked) billboard tint: amber for known-military, white otherwise.
/** True while this contact's latest poll broadcast an emergency code. */
function _isEmergency(icao24) {
return isEmergency(flightState.records.data.get(icao24)?.emergency);
}
/** Fleet (untracked) billboard tint: red while broadcasting an emergency,
* amber for known-military, white otherwise.
* Ground traffic gets NO special tint (owner verdict 2026-07-03 field test). */
function _fleetBillboardColor(icao24) {
if (_isEmergency(icao24)) return EMERGENCY_TINT;
return isMilitaryIcao(icao24) ? MIL_TINT : Cesium.Color.WHITE;
}
/** Cockpit far-contact dot tint: the emergency red outranks the category tint. */
function _cockpitContactColor(icao24) {
if (_isEmergency(icao24)) return EMERGENCY_TINT;
return isMilitaryIcao(icao24) ? MIL_TINT : COCKPIT_CIVILIAN_COLOR;
}
/** Fleet billboard scale: per-class scale, ×GROUND_SCALE while grounded. */
function _fleetBillboardScale(icao24, klass) {
@@ -129,9 +146,7 @@ export function createRendering({
bb.height = COCKPIT_CONTACT_SIZE_PX;
bb.scale = limbScale;
bb.scaleByDistance = _cockpitBillboardScaleByDistance();
bb.color = (
isMilitaryIcao(icao24) ? MIL_TINT : COCKPIT_CIVILIAN_COLOR
).withAlpha(freshnessAlpha);
bb.color = _cockpitContactColor(icao24).withAlpha(freshnessAlpha);
bb.rotation = 0;
return;
}
@@ -162,7 +177,7 @@ export function createRendering({
function _modelColor(icao24) {
if (icao24 === flightState._trackedIcao) return Cesium.Color.CYAN;
return isMilitaryIcao(icao24) ? MIL_TINT : Cesium.Color.WHITE;
return _fleetBillboardColor(icao24);
}
/** The FLEET's 3D-model regime: models3d enabled AND the camera zoomed in past the altitude
@@ -1009,9 +1024,7 @@ export function createRendering({
flightState._cockpitNearContacts.has(icao24);
const layerBaseColor =
flightState._cockpitContactMode && !isCockpitNear
? isMilitaryIcao(icao24)
? MIL_TINT
: COCKPIT_CIVILIAN_COLOR
? _cockpitContactColor(icao24)
: _fleetBillboardColor(icao24);
const baseColor = layerBaseColor;
const treatment = applyAircraftBillboardTreatment({
+6
View File
@@ -6,6 +6,7 @@ export {
ERROR_BACKOFF_INTERVAL,
} from './recordPolicy.js';
import * as Cesium from 'cesium';
import { EMERGENCY_TINT_CSS } from '../../data/aircraftEmergency.js';
/**
* @module militaryFlights
@@ -40,6 +41,11 @@ export const MIL_ICON_COLOR = Cesium.Color.fromCssColorString('#FFB800');
export const TRACKED_ICON_COLOR = Cesium.Color.fromCssColorString('#FFD166');
/** @constant {Cesium.Color} Red tint for aircraft broadcasting an emergency (mirror of flights) */
export const EMERGENCY_ICON_COLOR =
Cesium.Color.fromCssColorString(EMERGENCY_TINT_CSS);
// --- Ground traffic (owner reversal 2026-07-03; mirror of flights.js) ---------------
// adsb.lol/readsb flags ground traffic with alt_baro === "ground" (no separate
// boolean). Such aircraft are RENDERED instead of floating at the 3 km altitude
+11 -1
View File
@@ -133,6 +133,9 @@ export function createQueries({
flightState.records.missingPolls.get(icao24) ||
flightState.feed._backoff,
),
// This poll's transponder broadcast (not sticky; see records.js).
squawk: info?.squawk ?? null,
emergency: info?.emergency ? { ...info.emergency } : null,
};
}
@@ -156,7 +159,8 @@ export function createQueries({
* lon: number|null, altitudeM: number|null, speedMps: number|null,
* heading: number|null, verticalRateMps: number|null, onGround: boolean,
* military: boolean, aircraftClass: string|null, originCountry: null,
* operator: string|null, routeOrigin: null, routeDestination: null}}
* operator: string|null, squawk: string|null, emergency: boolean,
* emergencyKind: string|null, routeOrigin: null, routeDestination: null}}
*/
function mapAnalystRecord(icao24, info) {
@@ -187,6 +191,12 @@ export function createQueries({
aircraftClass: tr3bAircraftClass(icao24, text(info?.klass)),
originCountry: null,
operator: text(info?.operator),
squawk: text(info?.squawk),
// True while this poll broadcast an emergency (7500/7600/7700 or the
// ADS-B status); priority statuses (lifeguard, minfuel) report the kind
// without the flag.
emergency: info?.emergency?.severity === 'emergency',
emergencyKind: text(info?.emergency?.kind),
routeOrigin: null,
routeDestination: null,
};
+4
View File
@@ -1,6 +1,7 @@
import { pickRenderAltitudeM } from '../../data/renderAltitude.js';
import { stickyText, stickyNumber } from '../../data/aircraftMeta.js';
import { classifyAircraft } from '../../data/aircraftClass.js';
import { aircraftEmergency } from '../../data/aircraftEmergency.js';
import {
GROUND_FLOOR_WARM_MAX_ALT_M,
LANDED_MISSING_POLL_LIMIT,
@@ -161,6 +162,9 @@ export class MilitaryFlightRecords {
null,
),
turnRateDps: prevMeta?.turnRateDps || 0,
// Per poll, NOT sticky (mirror of flights): a cleared code clears here.
squawk: aircraft.squawk ?? null,
emergency: aircraftEmergency(aircraft),
onGround,
// Round 7: sticky airborne history (see _likelyLanded).
wasAirborne: prevMeta?.wasAirborne === true || !onGround,
+16
View File
@@ -98,3 +98,19 @@ test('military records keep bounded partial retention and release metadata only
'remove',
);
});
test('military emergency status follows the latest poll instead of sticking', () => {
const records = store();
const first = records.receive(
observation({ squawk: '1234', emergency: 'minfuel' }),
context(),
);
assert.equal(first.meta.squawk, '1234');
assert.equal(first.meta.emergency.kind, 'minfuel');
assert.equal(first.meta.emergency.severity, 'priority');
const next = records.receive(
observation({ squawk: '1234', emergency: 'none' }),
context(),
);
assert.equal(next.meta.emergency, null);
});
+16 -4
View File
@@ -18,6 +18,7 @@ import {
screenProjectedRotation,
} from '../../data/iconOrientation.js';
import { limitCourseStep, courseSlewCapDps } from '../../data/motionModel.js';
import { isEmergency } from '../../data/aircraftEmergency.js';
import {
PLANE_MODEL_SCALE,
PLANE_MODEL_URL,
@@ -33,6 +34,7 @@ import {
COCKPIT_CONTACT_SIZE_PX,
MIL_ICON_COLOR,
TRACKED_ICON_COLOR,
EMERGENCY_ICON_COLOR,
MODEL_ALT_CEIL_M,
MODEL_MAX_ALL,
MODEL_MAX,
@@ -159,7 +161,7 @@ export function createRendering({
bb.height = COCKPIT_CONTACT_SIZE_PX;
bb.scale = limbScale;
bb.scaleByDistance = _cockpitBillboardScaleByDistance();
bb.color = MIL_ICON_COLOR.withAlpha(freshnessAlpha);
bb.color = _fleetBillboardColor(icao24).withAlpha(freshnessAlpha);
bb.rotation = 0;
return;
}
@@ -173,7 +175,7 @@ export function createRendering({
bb.height = icao24 === flightState._trackedIcao ? 24 : 20;
bb.scale = _militaryBillboardScale(icao24) * limbScale;
bb.scaleByDistance = _normalBillboardScaleByDistance();
bb.color = MIL_ICON_COLOR.withAlpha(cyberSonarBaseAlpha(bb));
bb.color = _fleetBillboardColor(icao24).withAlpha(cyberSonarBaseAlpha(bb));
}
/** Sprite kind for one contact's billboard. Identity for every aircraft except
@@ -189,7 +191,16 @@ export function createRendering({
function _modelColor(icao24) {
if (icao24 === flightState._trackedIcao) return TRACKED_ICON_COLOR;
return MIL_ICON_COLOR;
return _fleetBillboardColor(icao24);
}
/** Fleet (untracked) tint: red while the latest poll broadcast an emergency,
* amber otherwise. Cockpit far-contact dots use it too. */
function _fleetBillboardColor(icao24) {
return isEmergency(flightState.records.data.get(icao24)?.emergency)
? EMERGENCY_ICON_COLOR
: MIL_ICON_COLOR;
}
/** The FLEET's 3D-model regime: models3d enabled AND the camera zoomed in past the altitude
@@ -965,7 +976,7 @@ export function createRendering({
const isCockpitNear =
flightState._cockpitContactMode &&
flightState._cockpitNearContacts.has(icao24);
const baseColor = MIL_ICON_COLOR;
const baseColor = _fleetBillboardColor(icao24);
const treatment = applyAircraftBillboardTreatment({
billboard: bb,
baseScale:
@@ -1082,6 +1093,7 @@ export function createRendering({
_applyFleetBillboardPresentation,
_iconKind,
_modelColor,
_fleetBillboardColor,
_modelRegimeActive,
_modelCap,
_modelAddDistM,