07cc5321d1
CI / Lint, typecheck, test, build (pull_request) Failing after 9s
Calculators (apps/live-map/src/calculators/): - eclipse.ts: findEclipseWindows(bodies, opts) — coarse scan with threshold-crossing detection, bisection to refine start/end, ternary search to find peak. Handles eclipse already in progress at scan-start. Uses sun = parentId===null body. - overpass.ts: findOverpasses(opts) — coarse scan for local distance minima, ternary refinement. Targets: vessel, body, ground station (lat/lon/alt → heliocentric). UI: - panels/CalculatorsPanel.tsx: collapsible bottom-center panel with two tabs. Eclipse form: observer, eclipser, from UT → 3 windows. Overpass form: observer vessel, target kind+id, max dist → 5 passes. - timeFormat.ts: shared KSP-time formatters. Live-map camera polish (apps/live-map/src/scene/): - camera.ts: CameraController — log-scale distance (z→exp(z)*1e8 m, range -3..12), spherical orbit around target, smooth lerp to selected body/vessel. Mouse wheel zooms, drag rotates, click raycasts for track toggle. Pointer-move-distance gate to distinguish click from drag. - glow.ts: additive shader-based atmospheric halo (rim-falloff fragment shader, BackSide) attached as child of body mesh. - layout.ts: bodyPositionAt now returns true heliocentric (walks parent chain); previous version returned parent-relative for non-root children which broke the eclipse calculator. Bug fix: - packages/orbital-math/src/occultation.ts: sign of projection check was inverted. `proj <= 0` correctly returns 0 (occluder behind observer), `proj > 0` triggers eclipse computation. Tests: 28 live-map tests (10 scene + 12 calculator + 6 camera), 45 total across the workspace, all passing.
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { inverseLogScale, logScale } from '../src/scene/layout.js';
|
|
|
|
describe('camera log-scale', () => {
|
|
it('inverseLogScale undoes logScale', () => {
|
|
for (const t of [-3, 0, 4, 8, 12]) {
|
|
expect(inverseLogScale(logScale(t))).toBeCloseTo(t, 6);
|
|
}
|
|
});
|
|
|
|
it('produces distances spanning the KSP system', () => {
|
|
// t = 0: 1e8 m = 100 Mm (close zoom)
|
|
expect(logScale(0)).toBeCloseTo(1e8, -3);
|
|
// t = 4: ~5.5e9 m (Kerbin at 13.6 Gm is just outside)
|
|
expect(logScale(4)).toBeGreaterThan(1e9);
|
|
// t = 10: ~22e12 m (Eeloo at 90 Gm is well inside)
|
|
expect(logScale(10)).toBeGreaterThan(1e10);
|
|
// t = 12: ~1.6e13 m (max zoom)
|
|
expect(logScale(12)).toBeGreaterThan(1e12);
|
|
});
|
|
});
|
|
|
|
describe('spherical math (used by camera)', () => {
|
|
// The camera controller uses spherical coords. Test the
|
|
// cartesian conversion (extracted for testability).
|
|
function sphericalToCartesian(distance: number, az: number, el: number) {
|
|
const sinE = Math.sin(el);
|
|
const cosE = Math.cos(el);
|
|
const sinA = Math.sin(az);
|
|
const cosA = Math.cos(az);
|
|
return {
|
|
x: distance * cosE * sinA,
|
|
y: distance * sinE,
|
|
z: distance * cosE * cosA,
|
|
};
|
|
}
|
|
|
|
it('produces a point on the sphere of given radius', () => {
|
|
for (const az of [0, 1, 2.5, 4.7]) {
|
|
for (const el of [-0.5, 0, 0.7]) {
|
|
const p = sphericalToCartesian(1e10, az, el);
|
|
const d = Math.hypot(p.x, p.y, p.z);
|
|
expect(d).toBeCloseTo(1e10, 4);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('azimuth=0, elevation=0 produces +Z vector', () => {
|
|
const p = sphericalToCartesian(100, 0, 0);
|
|
expect(p.z).toBeCloseTo(100, 6);
|
|
expect(p.x).toBeCloseTo(0, 6);
|
|
expect(p.y).toBeCloseTo(0, 6);
|
|
});
|
|
|
|
it('azimuth=π/2, elevation=0 produces +X vector', () => {
|
|
const p = sphericalToCartesian(100, Math.PI / 2, 0);
|
|
expect(p.x).toBeCloseTo(100, 6);
|
|
expect(p.z).toBeCloseTo(0, 6);
|
|
expect(p.y).toBeCloseTo(0, 6);
|
|
});
|
|
|
|
it('elevation=π/2 produces +Y vector', () => {
|
|
const p = sphericalToCartesian(100, 0, Math.PI / 2);
|
|
expect(p.y).toBeCloseTo(100, 6);
|
|
expect(p.x).toBeCloseTo(0, 6);
|
|
expect(p.z).toBeCloseTo(0, 6);
|
|
});
|
|
});
|