mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
The far-plane comment said z = w (the code puts z just under w), the Milky Way constant is a brightness, not an opacity, and the CDS acknowledgement lives in the Data attribution popover, not DATA_SOURCES.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
133 lines
5.2 KiB
JavaScript
133 lines
5.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
||
import test from 'node:test';
|
||
import {
|
||
buildStarPack,
|
||
parseBscRecord,
|
||
} from '../../scripts/build-bright-stars.mjs';
|
||
import {
|
||
CUBE_FACES,
|
||
cubeFaceDirection,
|
||
directionToPlateCarree,
|
||
icrfToGalactic,
|
||
smoothstep,
|
||
softBlack,
|
||
} from '../../scripts/build-milky-way.mjs';
|
||
import { raDecToUnitVector } from '../sky/stars.js';
|
||
|
||
// Verbatim CDS V/50 records: HR 1, Sirius (HR 2491) and a nova with no J2000
|
||
// position. Columns matter, so the padding is part of the fixture.
|
||
const HR_1 =
|
||
' 1 BD+44 4550 3 36042 46 000001.1+444022000509.9+451345114.44-16.88 6.70 +0.07 +0.08 A1Vn -0.012-0.018 -018 195 4.2 21.6AC 3';
|
||
const SIRIUS =
|
||
'2491 9Alp CMaBD-16 1591 48915151881 257I 5423 064044.6-163444064508.9-164258227.22-08.88-1.46 0.00 -0.05 -0.03 A1Vm -0.553-1.205 +.375-008SBO 13 10.3 11.2AB 4*';
|
||
const NOVA =
|
||
' 92 NOVA 1572 B Cas *';
|
||
|
||
const close = (actual, expected, epsilon = 1e-9) =>
|
||
assert.ok(
|
||
Math.abs(actual - expected) <= epsilon,
|
||
`${actual} is not within ${epsilon} of ${expected}`,
|
||
);
|
||
|
||
test('BSC5 records parse J2000 position, V magnitude and B−V', () => {
|
||
const sirius = parseBscRecord(SIRIUS);
|
||
assert.equal(sirius.hr, 2491);
|
||
close(sirius.raDeg, (6 + 45 / 60 + 8.9 / 3600) * 15);
|
||
close(sirius.decDeg, -(16 + 42 / 60 + 58 / 3600));
|
||
assert.equal(sirius.vmag, -1.46);
|
||
assert.equal(sirius.bv, 0);
|
||
const first = parseBscRecord(HR_1);
|
||
close(first.raDeg, (5 / 60 + 9.9 / 3600) * 15);
|
||
close(first.decDeg, 45 + 13 / 60 + 45 / 3600);
|
||
assert.equal(first.vmag, 6.7);
|
||
assert.equal(first.bv, 0.07);
|
||
assert.equal(parseBscRecord(NOVA), null);
|
||
});
|
||
|
||
test('the star pack is a flat, rounded column array that counts its drops', () => {
|
||
const pack = buildStarPack([HR_1, NOVA, SIRIUS, ''].join('\n'), {
|
||
fetched: '2026-10-19',
|
||
});
|
||
assert.deepEqual(
|
||
pack.stars,
|
||
[1.291, 45.229, 6.7, 0.07, 101.287, -16.716, -1.46, 0],
|
||
);
|
||
assert.equal(pack.meta.count, 2);
|
||
assert.equal(pack.meta.dropped, 1);
|
||
assert.equal(pack.meta.fetched, '2026-10-19');
|
||
});
|
||
|
||
test('cube faces use the WebGL major-axis layout', () => {
|
||
// Each face centre looks down its own axis …
|
||
const axes = {
|
||
positiveX: [1, 0, 0],
|
||
negativeX: [-1, 0, 0],
|
||
positiveY: [0, 1, 0],
|
||
negativeY: [0, -1, 0],
|
||
positiveZ: [0, 0, 1],
|
||
negativeZ: [0, 0, -1],
|
||
};
|
||
for (const face of CUBE_FACES)
|
||
assert.deepEqual(
|
||
cubeFaceDirection(face, 0.5, 0.5).map((v) => v + 0),
|
||
axes[face],
|
||
);
|
||
// … and the first texel row and column follow OpenGL ES 3.0 §3.8.10:
|
||
// on +X, s grows towards −Z and t towards −Y.
|
||
assert.deepEqual(cubeFaceDirection('positiveX', 0, 0), [1, 1, 1]);
|
||
assert.deepEqual(cubeFaceDirection('positiveZ', 1, 1), [1, -1, 1]);
|
||
assert.deepEqual(cubeFaceDirection('negativeY', 0, 1), [-1, -1, -1]);
|
||
assert.throws(() => cubeFaceDirection('up', 0, 0), RangeError);
|
||
});
|
||
|
||
test('plate carrée is centred on longitude 0, growing to the left, north up', () => {
|
||
assert.deepEqual(directionToPlateCarree(1, 0, 0), [0.5, 0.5]);
|
||
const sixHours = directionToPlateCarree(0, 1, 0);
|
||
close(sixHours[0], 0.25);
|
||
const eighteenHours = directionToPlateCarree(0, -1, 0);
|
||
close(eighteenHours[0], 0.75);
|
||
close(directionToPlateCarree(0, 0, 1)[1], 0);
|
||
close(directionToPlateCarree(0, 0, -1)[1], 1);
|
||
const twelveHours = directionToPlateCarree(-1, 0, 0);
|
||
assert.ok(twelveHours[0] === 0 || twelveHours[0] === 1);
|
||
});
|
||
|
||
test('the galactic rotation puts the Galactic Centre and pole where the IAU does', () => {
|
||
// Galactic origin l = b = 0 at RA 266.405°, Dec −28.936° (J2000).
|
||
const centre = icrfToGalactic(raDecToUnitVector(266.40499, -28.93617));
|
||
close(centre[0], 1, 1e-6);
|
||
// North galactic pole at RA 192.85948°, Dec +27.12825°.
|
||
const pole = icrfToGalactic(raDecToUnitVector(192.85948, 27.12825));
|
||
close(pole[2], 1, 1e-6);
|
||
// ESO's panorama: the Large Magellanic Cloud (l ≈ 280.5°, b ≈ −32.9°) sits
|
||
// right of and below the centre.
|
||
const [u, v] = directionToPlateCarree(
|
||
...icrfToGalactic(raDecToUnitVector(80.894, -69.756)),
|
||
);
|
||
close(u, 0.5 + (360 - 280.47) / 360, 2e-3);
|
||
close(v, 0.5 + 32.89 / 180, 2e-3);
|
||
});
|
||
|
||
test('the black level eases in instead of cutting the faint sky into contours', () => {
|
||
assert.equal(softBlack(-0.2), 0);
|
||
assert.equal(softBlack(0), 0);
|
||
// Continuous, rising, with no step at the black level.
|
||
let previous = 0;
|
||
for (let x = 0.001; x <= 1; x += 0.001) {
|
||
const y = softBlack(x);
|
||
assert.ok(y > previous && y - previous < 0.0011, `step at ${x.toFixed(3)}`);
|
||
previous = y;
|
||
}
|
||
// Starts flat, then joins the straight line just below it.
|
||
assert.ok(softBlack(0.003) < 0.0003);
|
||
close(softBlack(1), 1 - 0.03, 1e-3);
|
||
});
|
||
|
||
test('the faint-glow blend weight eases between its edges', () => {
|
||
assert.equal(smoothstep(0.1, 0.2, 0), 0);
|
||
assert.equal(smoothstep(0.1, 0.2, 0.1), 0);
|
||
close(smoothstep(0.1, 0.2, 0.15), 0.5, 1e-9);
|
||
assert.equal(smoothstep(0.1, 0.2, 0.2), 1);
|
||
assert.equal(smoothstep(0.1, 0.2, 9), 1);
|
||
});
|