Files
gods-eye-view/src/layers/wind/edgeCases.test.mjs
T
German Khokhlov b8305de14b Run the Navara paths without the Cesium SDK, and load it on demand for the features that still build on it
Every Cesium read that a Navara page made was surveyed with the strict acceptance (GEV_QA_CESIUM_TRAP=record). Math on those paths now uses src/geo: screen annotations, weather and wind center readings, cyclone picks, scene data pack polygons and images (new navaraGeometry.js), the awareness subject, the orbit offset of camera verbs, voice frame_overhead, the agent world packet and Directions colours and ground picks. The celestial ring's Sun and Moon come from a bit-identical port of Cesium's Simon1994 model, JulianDate and TEME rotation (src/geo/simon1994.js, julianDate.js, transforms.js); a Cesium globe still applies its ICRF rotation once loaded.

Traffic, Street Level, the Nepal event and locator and geo import build on Cesium's math or entity model on either globe; their enable (or the import) now loads the SDK first through src/app/loadCesium.js, synchronously when it is already there. A production page guards the global so a missed read is reported and starts the load. No module reads Cesium while it loads (scripts/check-cesium-load-time.mjs, run by npm test). Cesium.js is still loaded by index.html here.
2026-11-04 17:15:21 +04:00

91 lines
3.5 KiB
JavaScript

// Edge cases from docs/migration/planet-separation/layers.md (wind): on a fixed
// grid direction, speed and legend agree whichever way the centre is picked,
// and an empty field is never presented as calm.
import test from 'node:test';
import assert from 'node:assert/strict';
import { inspectWindAtCenter } from './inspection.js';
import { windReadingResult } from './presentation.js';
import { sampleWind, windColor, windSpeed } from './model.js';
import { windStats } from './index.js';
import * as geo from '../../geo/index.js';
const at = (lonDeg, latDeg) => geo.cartesian3.fromDegrees(lonDeg, latDeg);
const grid = { nx: 4, ny: 3, lo1: 0, la1: 90, dx: 90, dy: 90 };
const field = (u, v) => ({
grid,
u: Float32Array.from({ length: 12 }, () => u),
v: Float32Array.from({ length: 12 }, () => v),
});
const options = {
units: 'km/h',
overlay: 'speed',
model: 'GFS',
validTime: '2026-09-14 18:00 UTC',
status: 'Model forecast',
};
/** The Cesium path picks the centre itself; Navara hands in the position. */
const viaViewer = (snapshot, point) =>
inspectWindAtCenter(
snapshot,
{
camera: { pickEllipsoid: () => point },
scene: { canvas: { clientWidth: 800, clientHeight: 600 } },
},
options,
);
const viaPosition = (snapshot, point) =>
inspectWindAtCenter(snapshot, null, { ...options, position: point });
const windLine = (reading) =>
windReadingResult(reading).lines.find(({ id }) => id === 'wind').text;
test('edge:wind: a fixed grid gives the same direction, speed and legend colour on both pick paths', () => {
// 5 m/s blowing toward the west: the wind comes FROM the east.
const snapshot = field(-5, 0);
for (const point of [at(10, 20), at(180, 0), at(-180, 0), at(-45.5, -60)]) {
const cesiumReading = viaViewer(snapshot, point);
const navaraReading = viaPosition(snapshot, point);
for (const reading of [cesiumReading, navaraReading]) {
assert.equal(reading.from, 'E');
assert.equal(reading.speed, 5);
assert.equal(reading.wind, '18.0 km/h from E');
assert.equal(reading.scalarValue, '18.0 km/h');
}
assert.deepEqual(navaraReading, { ...cesiumReading });
assert.equal(windColor(navaraReading.speed), windColor(5));
}
// The seam is one place, not two.
const east = sampleWind({ ...grid, ...snapshot }, 180, 0);
const west = sampleWind({ ...grid, ...snapshot }, -180, 0);
assert.deepEqual(east, west);
assert.equal(windSpeed(east.u, east.v), 5);
});
test('edge:wind: an empty or unavailable field is not reported as calm, a real 0 m/s field is', () => {
const point = at(10, 20);
const calm = viaPosition(field(0, 0), point);
assert.equal(calm.speed, 0);
assert.equal(calm.from, 'Calm');
assert.equal(calm.wind, '0.0 km/h · calm');
const empty = [
null,
{ unavailable: true, reason: 'Wind upstream unavailable' },
{ grid, u: null, v: null },
// A field whose arrays hold no cells at all.
{ grid, u: new Float32Array(0), v: new Float32Array(0) },
];
for (const snapshot of empty) {
for (const reading of [
viaViewer(snapshot, point),
viaPosition(snapshot, point),
]) {
assert.equal(reading.speed, undefined, 'no speed is invented');
assert.equal(reading.from, undefined, 'no direction is invented');
assert.equal(reading.coordinates, 'No surface reading');
assert.doesNotMatch(windLine(reading), /calm|NaN|undefined/i);
}
}
assert.equal(windStats(null).count, 0);
assert.equal(windStats({ unavailable: true }).error, 'Wind unavailable');
});