mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
Extract military-flight components and shared aircraft mechanics
This commit is contained in:
1 parent
b5e546daf1
commit
cb790ddea1
25 files changed
+4849
-3930
No files matched your search
+32
-3805
File diff suppressed because it is too large.
Load diff
@@ -1,119 +1,10 @@
|
||||
/**
|
||||
* Shared military-aircraft ICAO24 registry (2026-06-10 playtest fix).
|
||||
*
|
||||
* adsb.lol /v2/mil tags aircraft via its database's military flag; OpenSky
|
||||
* carries no such tag, so military aircraft (e.g. ADAPT91/92) appeared in
|
||||
* BOTH layers as duplicate icons and tracks. This registry reconciles them:
|
||||
*
|
||||
* - The military layer, while enabled, feeds every poll's ICAO set here and
|
||||
* marks itself active — the commercial flights layer then SUPPRESSES its
|
||||
* duplicates (military layer wins icon, track, and click).
|
||||
* - While the military layer is OFF, the flights layer keeps a low-rate
|
||||
* poll (60s against the dev proxy's cached /api/adsblol/mil) so known
|
||||
* military aircraft are still classified and styled amber.
|
||||
*/
|
||||
import { createMilitaryRegistry } from '../layers/aircraft/classification.js';
|
||||
import { createAdsbLolSource } from '../sources/live/standalone.js';
|
||||
|
||||
const MIL_POLL_INTERVAL_MS = 60000;
|
||||
|
||||
/** @type {Set<string>} Lowercase ICAO24 hexes known to be military. */
|
||||
const _milIcaos = new Set();
|
||||
/** @type {boolean} True while the dedicated military layer is enabled. */
|
||||
let _militaryLayerActive = false;
|
||||
/** @type {Set<(active: boolean) => void>} Fired on active-state TRANSITIONS. */
|
||||
const _activeChangeListeners = new Set();
|
||||
/** @type {number} Epoch ms of the last registry refresh (any source). */
|
||||
let _lastRefreshMs = 0;
|
||||
/** @type {boolean} A self-poll fetch is in flight. */
|
||||
let _polling = false;
|
||||
|
||||
/**
|
||||
* True when the dedicated military layer currently renders these aircraft
|
||||
* (the flights layer should suppress duplicates rather than restyle them).
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isMilitaryLayerActive() {
|
||||
return _militaryLayerActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the dedicated military layer enabled/disabled. On a TRANSITION
|
||||
* (value actually changed) the registered change listeners fire so the
|
||||
* flights layer can reconcile its duplicates immediately instead of waiting
|
||||
* out its 30 s poll (pre-ship audit M2).
|
||||
* @param {boolean} active - Whether the military layer renders.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setMilitaryLayerActive(active) {
|
||||
const next = !!active;
|
||||
if (next === _militaryLayerActive) return;
|
||||
_militaryLayerActive = next;
|
||||
for (const listener of _activeChangeListeners) {
|
||||
try {
|
||||
listener(next);
|
||||
} catch {
|
||||
// a broken listener must never break the layer toggle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to military-layer active-state transitions (fired only when the
|
||||
* value changes, AFTER the new state is committed).
|
||||
* @param {(active: boolean) => void} listener - Change callback.
|
||||
* @returns {() => void} Unsubscribe function.
|
||||
*/
|
||||
export function onMilitaryLayerActiveChange(listener) {
|
||||
if (typeof listener !== 'function') return () => {};
|
||||
_activeChangeListeners.add(listener);
|
||||
return () => _activeChangeListeners.delete(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces/extends the known-military set from a fresh poll.
|
||||
* Adds only — transient dropouts from one poll must not declassify an
|
||||
* aircraft mid-session (the set stays small: a few hundred hexes).
|
||||
* @param {Iterable<string>} icaos - ICAO24 hexes from a /v2/mil response.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function registerMilitaryIcaos(icaos) {
|
||||
for (const icao of icaos || []) {
|
||||
const hex = String(icao || '').trim().toLowerCase();
|
||||
if (hex) _milIcaos.add(hex);
|
||||
}
|
||||
_lastRefreshMs = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an aircraft is known military.
|
||||
* @param {string} icao24 - ICAO24 hex (any case).
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isMilitaryIcao(icao24) {
|
||||
return _milIcaos.has(String(icao24 || '').toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the registry from /api/adsblol/mil when stale — used by the
|
||||
* flights layer so classification works while the military layer is off
|
||||
* (the military layer's own polls keep it fresh otherwise). The dev proxy
|
||||
* caches upstream responses, so this is nearly free.
|
||||
* @returns {void} Fire-and-forget; failures leave the current set intact.
|
||||
*/
|
||||
export function refreshMilitaryRegistryIfStale() {
|
||||
if (_militaryLayerActive) return; // military layer's polls keep us fresh
|
||||
if (_polling || (Date.now() - _lastRefreshMs) < MIL_POLL_INTERVAL_MS) return;
|
||||
_polling = true;
|
||||
(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/adsblol/mil', { signal: AbortSignal.timeout(10000) });
|
||||
if (!response.ok) return;
|
||||
const data = await response.json();
|
||||
const aircraft = Array.isArray(data?.ac) ? data.ac : [];
|
||||
registerMilitaryIcaos(aircraft.map((entry) => entry?.hex));
|
||||
} catch {
|
||||
// keep the existing set on any failure
|
||||
} finally {
|
||||
_polling = false;
|
||||
}
|
||||
})();
|
||||
}
|
||||
const registry = createMilitaryRegistry({ source: createAdsbLolSource() });
|
||||
export const isMilitaryLayerActive = registry.isMilitaryLayerActive;
|
||||
export const setMilitaryLayerActive = registry.setMilitaryLayerActive;
|
||||
export const onMilitaryLayerActiveChange = registry.onMilitaryLayerActiveChange;
|
||||
export const registerMilitaryIcaos = registry.registerMilitaryIcaos;
|
||||
export const isMilitaryIcao = registry.isMilitaryIcao;
|
||||
export const refreshMilitaryRegistryIfStale = registry.refreshMilitaryRegistryIfStale;
|
||||
@@ -392,7 +392,7 @@ const LAYERS = [
|
||||
name: 'military',
|
||||
source: 'src/data/militaryFlights.js',
|
||||
asset: (() => {
|
||||
const src = fs.readFileSync(path.join(ROOT, 'src/data/militaryFlights.js'), 'utf8');
|
||||
const src = readLayerSource(path.join(ROOT, 'src/data/militaryFlights.js'));
|
||||
const m = src.match(/\bconst JET_MODEL_URL = '([^']+)';/);
|
||||
assert.ok(m, 'militaryFlights.js: JET_MODEL_URL not found');
|
||||
return m[1];
|
||||
@@ -492,7 +492,7 @@ for (const [klass, spec] of Object.entries(CLASS_MODEL_REAL)) {
|
||||
// PLANE_* constants — pin them to the measured meter-scale GLB + flights'
|
||||
// calibration so the copies cannot drift.
|
||||
test('military layer airplane.glb constants match the measured GLB and flights calibration', () => {
|
||||
const src = fs.readFileSync(path.join(ROOT, 'src/data/militaryFlights.js'), 'utf8');
|
||||
const src = readLayerSource(path.join(ROOT, 'src/data/militaryFlights.js'));
|
||||
const grab = (name) => {
|
||||
const m = src.match(new RegExp(`\\bconst ${name} = ([\\d.]+);`));
|
||||
assert.ok(m, `militaryFlights.js: ${name} not found`);
|
||||
|
||||
@@ -252,7 +252,7 @@ test('tracking layers write gevLabelModel and expose only their cached display p
|
||||
assert.ok(source.includes('.gevDisplayPosition ='), `${name} exposes a display-position cache`);
|
||||
}
|
||||
assert.match(sources['flights.js'], /gevDisplayPosition\s*=\s*parts\.motion\._trackedDisplayCached/);
|
||||
assert.ok(sources['militaryFlights.js'].includes('gevDisplayPosition = _trackedDisplayCached'));
|
||||
assert.match(sources['militaryFlights.js'], /gevDisplayPosition\s*=\s*parts\.motion\._trackedDisplayCached/);
|
||||
assert.ok(sources['satellites.js'].includes('gevDisplayPosition = _trackedDisplayCached'));
|
||||
assert.equal(sources['flights.js'].includes('_trackedEntity.label.text'), false);
|
||||
assert.equal(sources['militaryFlights.js'].includes('_trackedEntity.label.text'), false);
|
||||
|
||||
Reference in new issue
Block a user