Merge branch 'main' into codex/shared-geodesy

This commit is contained in:
Sameh Khamis committed 2026-10-17 13:37:28 -07:00
commit aa52ab2eca
17 files changed
+419 -152

No files matched your search

+4 -4
View File
@@ -219,8 +219,8 @@ export const COURSE_SLEW_DT_MAX_SEC = 0.25;
// adsbdb enrichment (best-effort, fail-silent). Bounded fan-out: max 4
// concurrent requests, dispatches dripped ≥ENRICH_DISPATCH_GAP_MS apart
// (≤5/s — adsbdb is a free community API; the dev-server proxy additionally
// caches per-key on disk forever, negative results included, so repeat
// sessions never re-hit adsbdb). Each key is requested at most once per
// caches successful and 404 responses per key for 24 hours, so later sessions
// may refresh them). Each key is requested at most once per
// session. Priority jobs (tracked plane, model-eligible planes) jump the
// queue; the ambient fleet sweep (below) fills the back at poll cadence.
// ---------------------------------------------------------------------------
@@ -243,7 +243,7 @@ export const ENRICH_DISPATCH_GAP_MS = 200;
// of drip, so the backlog can't outgrow a poll and re-sorts fresh), and
// - a ROLLING token-bucket budget (below) bounding the sustained ambient
// request rate (repeat sessions resolve instantly from the proxy's
// permanent disk cache).
// 24-hour disk cache).
// Fail-silent by contract: the sweep never throws into the poll loop, never
// blocks rendering, and never touches tracking state. When a type answer
// lands, _requestTypeEnrichment's callback swaps the billboard glyph + scale
@@ -257,7 +257,7 @@ export const ENRICH_DISPATCH_GAP_MS = 200;
// ENRICH_AMBIENT_REFILL_TOKENS every ENRICH_AMBIENT_REFILL_WINDOW_MS, clamped
// at the ceiling (no banking beyond one bucket). Numbers: 150 / 5 min sustains
// 0.5 req/s worst case — an order of magnitude under the 5/s drip that (with
// the 4-concurrent limit + the proxy's permanent disk cache) is the REAL
// the 4-concurrent limit + the proxy's 24-hour disk cache) is the REAL
// politeness bound on adsbdb; the 300 ceiling preserves the old first-look
// burst so a fresh region still classifies quickly.
/** Bucket ceiling: max ambient tokens held at once (= the initial burst). */
+2 -1
View File
@@ -1,5 +1,6 @@
import * as Cesium from 'cesium';
import { twoline2satrec } from 'satellite.js';
import { noradIdFromSatnum } from '../../sources/tle.js';
import {
DENSE_REFRESH_FRAMES,
DENSE_GROUP_PATH,
@@ -113,7 +114,7 @@ export function createCatalog({ state: layerState, services, parts, source }) {
const entry = entries[i];
const satrec = twoline2satrec(entry.line1, entry.line2);
if (!satrec || satrec.error !== 0) continue;
const noradId = Number(satrec.satnum);
const noradId = noradIdFromSatnum(satrec.satnum);
if (layerState._catalog.has(noradId)) continue; // core catalog keeps priority
const pos = parts.orbits.propagatePosition(satrec, now);
if (!pos) continue;
+2 -1
View File
@@ -1,6 +1,7 @@
import { twoline2satrec } from 'satellite.js';
import * as Cesium from 'cesium';
import { msg } from '../../i18n/index.js';
import { noradIdFromSatnum } from '../../sources/tle.js';
import { CATALOG_GROUPS, ISS_NORAD, POINT_STYLES } from './policy.js';
export function createIngestion({
@@ -111,7 +112,7 @@ export function createIngestion({
const satrec = twoline2satrec(entry.line1, entry.line2);
if (!satrec || satrec.error !== 0) continue;
const noradId = Number(satrec.satnum);
const noradId = noradIdFromSatnum(satrec.satnum);
if (seen.has(noradId)) continue;
seen.add(noradId);
+80
View File
@@ -0,0 +1,80 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { parseTleText } from '../../sources/tle.js';
import { createIngestion } from './ingestion.js';
// The ISS set (1 January 2024) with its catalog field swapped per satellite.
const ISS_LINE1 =
'1 25544U 98067A 24001.50000000 .00016717 00000-0 30370-3 0 9993';
const ISS_LINE2 =
'2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.50377579432323';
function tleSet(name, field) {
return [
name,
ISS_LINE1.replace('25544', field),
ISS_LINE2.replace('25544', field),
].join('\n');
}
function ingestionFor(catalogText) {
const state = {
_trackingRefreshEpoch: 0,
_activeUpdateControllers: new Set(),
_pointCollection: {
removeAll() {},
add: (point) => point,
},
_points: new Map(),
_orbitPaths: new Map(),
_catalog: new Map(),
_detectionObjects: new Map(),
_denseLoadToken: 0,
_catalogRevision: 0,
_params: { catalog: 'core', showOrbits: false },
};
const parts = {
orbits: {
parseTLE: parseTleText,
propagatePosition: () => ({ longitude: 0, latitude: 0, altitude: 4e5 }),
},
labels: { _syncIssOverlay() {} },
controls: { _pointStyleFor: () => ({}) },
rendering: { _showOrbitPath() {} },
tracking: {
async _reconcileTrackedSubjectContext() {},
_applyPendingTrackingRestore() {},
},
catalog: {},
};
const source = {
async readGroup() {
return { ok: true, text: catalogText };
},
};
const { methods } = createIngestion({ state, services: {}, parts, source });
return { state, update: () => methods.update({ scene: {} }) };
}
test('every Alpha-5 satellite keeps its own catalog entry through dedupe', async (t) => {
t.mock.method(console, 'log', () => {});
t.mock.method(console, 'warn', () => {});
const { state, update } = ingestionFor(
[
tleSet('FIRST', 'A0000'),
tleSet('SECOND', 'B0001'),
tleSet('THIRD', 'Z9999'),
].join('\n'),
);
await update();
assert.deepEqual(
[...state._catalog].map(([id, entry]) => [id, entry.name]),
[
[100000, 'FIRST'],
[110001, 'SECOND'],
[339999, 'THIRD'],
],
);
assert.deepEqual([...state._points.keys()], [100000, 110001, 339999]);
assert.equal(state._count, 3);
});
+2 -2
View File
@@ -8,7 +8,7 @@ import {
twoline2satrec,
} from 'satellite.js';
import { findNextSatellitePass } from '../../data/satellitePass.js';
import { parseTleText } from '../../sources/tle.js';
import { noradIdFromSatnum, parseTleText } from '../../sources/tle.js';
import { ORBIT_PATH_STEPS, ISS_NORAD } from './policy.js';
export function createOrbits({ state: layerState, services, parts, source }) {
@@ -284,7 +284,7 @@ export function createOrbits({ state: layerState, services, parts, source }) {
const current = propagatePosition(satrec, referenceDate);
if (!current) return null;
return {
noradId: Number(satrec.satnum),
noradId: noradIdFromSatnum(satrec.satnum),
name: String(name || '').trim(),
current,
periodSec: orbitalPeriodSeconds(satrec),