import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import test from "node:test"; import ts from "typescript"; const source = await readFile(new URL("../apps/catalog/src/mapSpiralMath.ts", import.meta.url), "utf8"); const { outputText } = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022, }, fileName: "mapSpiralMath.ts", }); const spiralMath = await import(`data:text/javascript;base64,${Buffer.from(outputText).toString("base64")}`); const { MAX_SPIRAL_RADIUS_METERS, directGeodesicDestination, normalizeRadians, spiralAngleAtArcLength, spiralArcLengthAtAngle, spiralRadiusAtAngle, spiralSurfaceFrame, } = spiralMath; const twoPi = Math.PI * 2; test("one complete spiral turn increases radius by exactly one pitch", () => { assert.equal(spiralRadiusAtAngle(twoPi, 10_000), 10_000); }); test("arc-length inversion keeps configured surface distance across the route", () => { for (const distance of [0, 1, 20, 1_000, 25_000, 1_000_000]) { const angle = spiralAngleAtArcLength(distance, 10_000); const restored = spiralArcLengthAtAngle(angle, 10_000); assert.ok(Math.abs(restored - distance) <= Math.max(1e-6, distance * 1e-10), `${distance} → ${restored}`); } }); test("equal elapsed time produces equal traveled surface distance", () => { const speed = 250; const pitch = 10_000; const atOneSecond = spiralAngleAtArcLength(speed, pitch); const atTenSeconds = spiralAngleAtArcLength(speed * 10, pitch); assert.ok(atTenSeconds > atOneSecond); assert.ok(spiralRadiusAtAngle(atTenSeconds, pitch) > spiralRadiusAtAngle(atOneSecond, pitch)); assert.ok(Math.abs(spiralArcLengthAtAngle(atTenSeconds, pitch) - speed * 10) < 1e-6); }); test("WGS84 direct destination remains finite across dateline and high latitude", () => { const destination = directGeodesicDestination({ longitude: 179.9 * Math.PI / 180, latitude: 84 * Math.PI / 180, }, Math.PI / 3, 500_000); assert.ok(Number.isFinite(destination.longitude)); assert.ok(Number.isFinite(destination.latitude)); assert.ok(destination.longitude >= -Math.PI && destination.longitude <= Math.PI); assert.ok(destination.latitude >= -Math.PI / 2 && destination.latitude <= Math.PI / 2); }); test("eastbound WGS84 destination at equator matches the ellipsoid circumference", () => { const destination = directGeodesicDestination({ longitude: 0, latitude: 0 }, Math.PI / 2, 100_000); assert.ok(Math.abs(destination.latitude) < 1e-12); assert.ok(Math.abs(destination.longitude - 100_000 / 6_378_137) < 1e-10); }); test("spiral frame starts at the current point and follows the current heading", () => { const origin = { longitude: 37.6173 * Math.PI / 180, latitude: 55.7558 * Math.PI / 180 }; const heading = 0.7; const frame = spiralSurfaceFrame(origin, heading, 0, 10_000); assert.ok(Math.abs(normalizeRadians(frame.longitude - origin.longitude)) < 1e-12); assert.ok(Math.abs(frame.latitude - origin.latitude) < 1e-12); assert.equal(frame.radiusMeters, 0); assert.ok(Math.abs(normalizeRadians(frame.tangentHeading - heading)) < 1e-12); }); test("route stops at the documented regional WGS84 safety boundary", () => { const pitch = 100_000; const angle = (MAX_SPIRAL_RADIUS_METERS + pitch) * twoPi / pitch; const distance = spiralArcLengthAtAngle(angle, pitch); assert.throws( () => spiralSurfaceFrame({ longitude: 0, latitude: 0 }, 0, distance, pitch), /spiral_extent_limit/, ); }); test("invalid animation inputs fail closed", () => { assert.throws(() => spiralAngleAtArcLength(100, 0), /spiral_pitch_invalid/); assert.throws(() => directGeodesicDestination({ longitude: 0, latitude: Number.NaN }, 0, 10), /spiral_origin_invalid/); });