mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Directions, the Contacts marker and panel, placed annotation labels, the drawn-line measure, the Cockpit and formatRouteDistance each chose the unit and precision on their own, and most chose before rounding: #1450, #1433 and formatDistanceText or kilometreDigits from src/data/distanceFormat.js, which cut after rounding. Each keeps its own metre step and rendering. That also fixes the 10 km cut the Contacts marker and panel, the drawn-line measure and the Cockpit still had (9,960 m read 10.0 km). The helper returns kilometres already rounded, so the plain marker and the localized panel no longer disagree on half-tenths such as 1,450 m. A negative annotation distance now leaves the label without a distance, and a drawn line with a coordinate that is not a number shows no measure, rather than printing 0 m or NaN km. The HUD altitude tag and the spoken view height keep their own unit choice.
126 lines
5.5 KiB
JavaScript
126 lines
5.5 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { setLocale, translateKnown } from '../i18n/index.js';
|
|
import {
|
|
AWARENESS_RELATIONSHIP,
|
|
findByDoublingRadius,
|
|
getAwarenessNavigationTargets,
|
|
formatAwarenessDistance,
|
|
formatAwarenessLabel,
|
|
summarizeAwarenessCohort,
|
|
} from './militaryAwarenessEngine.js';
|
|
|
|
test('awareness formatters preserve valid zeroes and replace missing or invalid values', () => {
|
|
assert.equal(formatAwarenessDistance(0), '0 m');
|
|
assert.equal(formatAwarenessDistance(950), '950 m');
|
|
assert.equal(formatAwarenessDistance(Number.NaN), '—');
|
|
assert.equal(formatAwarenessDistance(-1), '—');
|
|
assert.equal(formatAwarenessLabel(' UAL649 '), 'UAL649');
|
|
assert.equal(formatAwarenessLabel(0), '0');
|
|
assert.equal(formatAwarenessLabel({ callsign: ' UAL649 ', id: 'fallback' }), 'UAL649');
|
|
assert.equal(formatAwarenessLabel({ name: '', label: 'Mapped site' }), 'Mapped site');
|
|
assert.equal(formatAwarenessLabel({ id: 0 }), '0');
|
|
assert.equal(formatAwarenessLabel(''), '—');
|
|
assert.equal(formatAwarenessLabel({}), '—');
|
|
assert.equal(formatAwarenessLabel(undefined), '—');
|
|
});
|
|
|
|
test('a contact marker moves a distance that rounds to 1,000 m into kilometres', () => {
|
|
assert.equal(formatAwarenessDistance(999.4), '999 m');
|
|
assert.equal(formatAwarenessDistance(999.6), '1.0 km');
|
|
assert.equal(formatAwarenessDistance(4600), '4.6 km');
|
|
assert.equal(formatAwarenessDistance(9949), '9.9 km');
|
|
assert.equal(formatAwarenessDistance(9950), '10 km');
|
|
assert.equal(formatAwarenessDistance(9960), '10 km');
|
|
});
|
|
|
|
test('awareness cohorts sort nearest-first and do not turn empty data into out of range', () => {
|
|
const result = summarizeAwarenessCohort([{ id: 'far', distance: 9000 }, { id: 'near', distanceM: 1000 }]);
|
|
assert.equal(result.relationship, 'NEARBY');
|
|
assert.deepEqual(result.nearest.map((item) => item.id), ['near', 'far']);
|
|
assert.equal(summarizeAwarenessCohort([]).relationship, 'UNKNOWN');
|
|
});
|
|
|
|
test('awareness cohorts reject negative and non-finite distances as unavailable data', () => {
|
|
const result = summarizeAwarenessCohort([
|
|
{ id: 'negative', distanceM: -1 },
|
|
{ id: 'nan', distanceM: Number.NaN },
|
|
{ id: 'valid-zero', distanceM: 0 },
|
|
]);
|
|
assert.equal(result.relationship, 'NEARBY');
|
|
assert.deepEqual(result.nearest.map((item) => item.id), ['valid-zero']);
|
|
});
|
|
|
|
test('unavailable and stale feeds remain unknown', () => {
|
|
assert.equal(AWARENESS_RELATIONSHIP.OUTSIDE_RANGE, 'OUTSIDE_RANGE');
|
|
assert.equal(summarizeAwarenessCohort([], { available: false }).reason, 'feed unavailable');
|
|
assert.equal(summarizeAwarenessCohort([], { stale: true }).reason, 'feed stale');
|
|
});
|
|
|
|
test('cohort reasons stay English in data and translate where the panel shows them', (t) => {
|
|
t.after(() => setLocale('en'));
|
|
setLocale('ru');
|
|
const reasons = [
|
|
summarizeAwarenessCohort([], { available: false }).reason,
|
|
summarizeAwarenessCohort([], { stale: true }).reason,
|
|
summarizeAwarenessCohort([{ id: 'near', distanceM: 10 }]).reason,
|
|
summarizeAwarenessCohort([]).reason,
|
|
];
|
|
assert.deepEqual(reasons, [
|
|
'feed unavailable',
|
|
'feed stale',
|
|
'observed or mapped nearby context',
|
|
'no observed or mapped objects in current feeds',
|
|
]);
|
|
// The awareness panel shows a reason through translateKnown().
|
|
for (const reason of reasons) assert.notEqual(translateKnown(reason), reason);
|
|
});
|
|
|
|
test('navigation prioritizes nearby targets from the selected cohort', () => {
|
|
const cohorts = [
|
|
{ id: 'flights', summary: { nearest: [{ icao24: 'a1', distanceM: 1000 }, { icao24: 'a2', distanceM: 2000 }] } },
|
|
{ id: 'military', summary: { nearest: [{ icao24: 'm1', distanceM: 500 }] } },
|
|
];
|
|
const targets = getAwarenessNavigationTargets(cohorts, { layerId: 'flights', id: 'subject' }, ['flights:a1']);
|
|
// Cohort affinity still wins among UNVISITED targets: a2 leads at 2000 m even
|
|
// though military m1 sits at 500 m.
|
|
//
|
|
// Visited state now outranks that affinity, which is the corrected half of
|
|
// this ordering. Ranking the subject's own layer first unconditionally put
|
|
// the already-visited a1 ahead of the unvisited m1, so NEXT cycled inside the
|
|
// subject's layer and never reached military, vessels, or installations.
|
|
assert.deepEqual(targets.map((target) => `${target.layerId}:${target.id}`), ['flights:a2', 'military:m1', 'flights:a1']);
|
|
});
|
|
|
|
test('awareness cycling excludes satellites, which retain independent tracking', () => {
|
|
const cohorts = [
|
|
{ id: 'satellites', summary: { nearest: [{ id: 'iss', distanceM: 100 }] } },
|
|
{ id: 'ais-live-vessels', summary: { nearest: [{ mmsi: 'v1', distanceM: 1000 }] } },
|
|
];
|
|
const targets = getAwarenessNavigationTargets(cohorts);
|
|
assert.deepEqual(targets.map((target) => `${target.layerId}:${target.id}`), ['ais-live-vessels:v1']);
|
|
});
|
|
|
|
test('empty proximity searches double their radius until an aircraft is found', () => {
|
|
const searched = [];
|
|
const result = findByDoublingRadius((radiusM) => {
|
|
searched.push(radiusM);
|
|
return radiusM >= 1000000 ? { id: 'closest-flight' } : null;
|
|
});
|
|
assert.deepEqual(searched, [250000, 500000, 1000000]);
|
|
assert.deepEqual(result, {
|
|
candidate: { id: 'closest-flight' },
|
|
radiusM: 1000000,
|
|
});
|
|
});
|
|
|
|
test('expanding proximity search stops at its global bound when feeds are empty', () => {
|
|
const searched = [];
|
|
const result = findByDoublingRadius((radiusM) => {
|
|
searched.push(radiusM);
|
|
return null;
|
|
}, { initialRadiusM: 250000, maxRadiusM: 1000000 });
|
|
assert.equal(result, null);
|
|
assert.deepEqual(searched, [250000, 500000, 1000000]);
|
|
});
|