feat: v0.22.0 — ISS-022/025/027 hover route trace on job list, on-board rows and relocation panel

This commit is contained in:
2026-09-19 21:47:00 +00:00
parent 096a6bfe61
commit f2dbad5e03
8 changed files with 285 additions and 11 deletions
+37 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import type { StateSnapshot } from './game/state';
import { planesGeoJson, routesGeoJson, greatCircleArc, ROUTE_ARC_SEGMENTS, type AirportCoord } from './mapData';
import { planesGeoJson, routesGeoJson, hoverRouteGeoJson, greatCircleArc, ROUTE_ARC_SEGMENTS, type AirportCoord } from './mapData';
import { haversineKm } from './geo';
const AIRPORTS = new Map<string, AirportCoord>([
@@ -140,6 +140,42 @@ describe('routesGeoJson (v0.12.0 — one line per flying plane\'s current leg)',
});
});
describe('hoverRouteGeoJson (ISS-022/025/027 v0.22.0 — hover route trace)', () => {
it('returns an empty collection for the no-hover "" sentinel', () => {
expect(hoverRouteGeoJson('', '', AIRPORTS)).toEqual({ type: 'FeatureCollection', features: [] });
});
it('draws one great-circle line between two known airports with exact endpoints', () => {
const fc = hoverRouteGeoJson('EDDF', 'EHAM', AIRPORTS);
expect(fc.features).toHaveLength(1);
expect(fc.features[0].properties).toEqual({ planeId: '' }); // pure overlay — no owning plane
const coords = fc.features[0].geometry.coordinates;
expect(coords.length).toBe(ROUTE_ARC_SEGMENTS + 1); // sampled arc, not a 2-point chord
expect(coords[0]).toEqual([8.56, 50.03]); // EDDF [lon, lat] — exact endpoint
expect(coords[coords.length - 1]).toEqual([4.76, 52.31]); // EHAM — exact endpoint
});
it('draws the same trace reversed (endpoints swapped, arc mirrored)', () => {
const ab = hoverRouteGeoJson('EDDF', 'EHAM', AIRPORTS).features[0].geometry.coordinates;
const ba = hoverRouteGeoJson('EHAM', 'EDDF', AIRPORTS).features[0].geometry.coordinates;
expect(ba.length).toBe(ab.length);
// The slerp is symmetric up to floating-point rounding (1 − t ≠ (N−t)/N bit-exactly), so
// compare with a tolerance: each reversed point sits on the forward arc within ~1e-9 degrees.
for (let i = 0; i < ab.length; i++) {
expect(Math.abs(ba[i][0] - ab[ab.length - 1 - i][0])).toBeLessThan(1e-9);
expect(Math.abs(ba[i][1] - ab[ab.length - 1 - i][1])).toBeLessThan(1e-9);
}
});
it('returns an empty collection when the from airport is missing from the dataset', () => {
expect(hoverRouteGeoJson('ZZZZ', 'EDDV', AIRPORTS)).toEqual({ type: 'FeatureCollection', features: [] });
});
it('returns an empty collection when the to airport is missing from the dataset', () => {
expect(hoverRouteGeoJson('EDDF', 'ZZZZ', AIRPORTS)).toEqual({ type: 'FeatureCollection', features: [] });
});
});
describe('greatCircleArc (v0.12.2 — curved route traces)', () => {
it('samples >2 points with the endpoints exactly at the airport coords', () => {
const arc = greatCircleArc(AIRPORTS.get('EDDF')!, AIRPORTS.get('EHAM')!);