spacemap 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -3025,6 +3025,17 @@ var OrbitalSource = /* @__PURE__ */ function(OrbitalSource) {
3025
3025
  OrbitalSource[OrbitalSource["UNKNOWN"] = 255] = "UNKNOWN";
3026
3026
  return OrbitalSource;
3027
3027
  }({});
3028
+ var PROBE_ARCHIVES = [
3029
+ void 0,
3030
+ "naif",
3031
+ "esa",
3032
+ "naif-pds3",
3033
+ "naif-pds4",
3034
+ "jaxa-darts",
3035
+ "horizons",
3036
+ "gcat-deepcat",
3037
+ "spacetrack"
3038
+ ];
3028
3039
  var IdType = /* @__PURE__ */ function(IdType) {
3029
3040
  IdType[IdType["NAIF"] = 0] = "NAIF";
3030
3041
  IdType[IdType["SPKID"] = 1] = "SPKID";
@@ -3174,40 +3185,24 @@ function isNaturalBody(type) {
3174
3185
  //#endregion
3175
3186
  //#region src/lib/scene/state/orbit-sources.ts
3176
3187
  var ORBIT_SOURCES = {
3177
- [OrbitalSource.HORIZONS]: {
3178
- archive: "horizons",
3179
- label: "NASA"
3180
- },
3181
- [OrbitalSource.SBDB]: {
3182
- archive: "sbdb",
3183
- label: "NASA"
3184
- },
3185
- [OrbitalSource.SPICE]: {
3186
- archive: "naif",
3187
- label: "NASA"
3188
- },
3188
+ [OrbitalSource.HORIZONS]: { archive: "horizons" },
3189
+ [OrbitalSource.SBDB]: { archive: "sbdb" },
3190
+ [OrbitalSource.SPICE]: { archive: "naif" },
3189
3191
  [OrbitalSource.SBDB_MOON]: {
3190
3192
  archive: "sbdb",
3191
- label: "NASA",
3192
3193
  scoped: true
3193
3194
  },
3194
3195
  [OrbitalSource.ASTERSAT]: {
3195
3196
  archive: "nsdb",
3196
- label: "Natural Satellites Data Base",
3197
3197
  scoped: true
3198
3198
  },
3199
- [OrbitalSource.SPICE_PROBE]: {
3200
- archive: "naif",
3201
- label: "NASA"
3202
- },
3199
+ [OrbitalSource.SPICE_PROBE]: {},
3203
3200
  [OrbitalSource.CELESTRAK]: {
3204
3201
  archive: "celestrak",
3205
- label: "CelesTrak",
3206
3202
  scoped: true
3207
3203
  },
3208
3204
  [OrbitalSource.SPACETRACK]: {
3209
3205
  archive: "spacetrack",
3210
- label: "Space-Track.org",
3211
3206
  scoped: true
3212
3207
  }
3213
3208
  };
@@ -3221,6 +3216,19 @@ var ORBIT_SOURCE_ORDER = [
3221
3216
  OrbitalSource.CELESTRAK,
3222
3217
  OrbitalSource.SPACETRACK
3223
3218
  ];
3219
+ var ORBIT_ARCHIVE_ORGANISATIONS = {
3220
+ horizons: "NASA",
3221
+ sbdb: "NASA",
3222
+ naif: "NASA",
3223
+ "naif-pds3": "NASA",
3224
+ "naif-pds4": "NASA",
3225
+ esa: "ESA",
3226
+ "jaxa-darts": "JAXA",
3227
+ "gcat-deepcat": "McDowell Deep Space Catalog",
3228
+ nsdb: "Natural Satellites Data Base",
3229
+ celestrak: "CelesTrak",
3230
+ spacetrack: "Space-Track.org"
3231
+ };
3224
3232
  function orbitSourceInfo(source) {
3225
3233
  return ORBIT_SOURCES[source];
3226
3234
  }
@@ -3281,6 +3289,13 @@ var CreditsStore = class {
3281
3289
  set orbitSourceParents(value) {
3282
3290
  set(this.#orbitSourceParents, value, true);
3283
3291
  }
3292
+ #probeArchives = state(proxy(/* @__PURE__ */ new Set()));
3293
+ get probeArchives() {
3294
+ return get(this.#probeArchives);
3295
+ }
3296
+ set probeArchives(value) {
3297
+ set(this.#probeArchives, value, true);
3298
+ }
3284
3299
  registerImagery(layer, bodyId, systemId, meta) {
3285
3300
  this.addImagery(layer, bodyId, systemId, meta, layer === "rings");
3286
3301
  const tiles = layer === "surface" ? tileCredit(meta) : void 0;
@@ -3322,6 +3337,7 @@ var CreditsStore = class {
3322
3337
  recordOrbitSources(bodies) {
3323
3338
  let added = false;
3324
3339
  let scoped = false;
3340
+ let archived = false;
3325
3341
  for (const b of bodies) {
3326
3342
  const src = b.data.orbitalSource;
3327
3343
  if (src === OrbitalSource.UNKNOWN) continue;
@@ -3329,6 +3345,11 @@ var CreditsStore = class {
3329
3345
  this.orbitSources.add(src);
3330
3346
  added = true;
3331
3347
  }
3348
+ const archive = b.data.orbitArchive;
3349
+ if (archive && !this.probeArchives.has(archive)) {
3350
+ this.probeArchives.add(archive);
3351
+ archived = true;
3352
+ }
3332
3353
  if (!orbitSourceInfo(src)?.scoped) continue;
3333
3354
  let parents = this.orbitSourceParents.get(src);
3334
3355
  if (!parents) {
@@ -3341,6 +3362,7 @@ var CreditsStore = class {
3341
3362
  }
3342
3363
  if (added) this.orbitSources = new Set(this.orbitSources);
3343
3364
  if (scoped) this.orbitSourceParents = new Map(this.orbitSourceParents);
3365
+ if (archived) this.probeArchives = new Set(this.probeArchives);
3344
3366
  }
3345
3367
  recordOrbitSource(src) {
3346
3368
  if (src === OrbitalSource.UNKNOWN || this.orbitSources.has(src)) return;
@@ -5833,6 +5855,10 @@ var TYPE_COLOR_PROBE = "#d5cfe7";
5833
5855
  var SUN_ID = "naif-10";
5834
5856
  var EARTH_ID = "naif-399";
5835
5857
  var SSB_ID = "naif-0";
5858
+ function dominantPlanetId(id) {
5859
+ const m = /^naif-([1-9])$/.exec(id);
5860
+ return m ? `naif-${m[1]}99` : null;
5861
+ }
5836
5862
  var MINOR_PROMOTED_IDS = new Set([
5837
5863
  "naif-0",
5838
5864
  "naif-9",
@@ -6208,10 +6234,6 @@ var MinorBucket = class {
6208
6234
  function isTopLevelParent(parentId) {
6209
6235
  return parentId === "naif-0" || parentId === "naif-10";
6210
6236
  }
6211
- function dominantPlanetId(id) {
6212
- const m = /^naif-([1-9])$/.exec(id);
6213
- return m ? `naif-${m[1]}99` : null;
6214
- }
6215
6237
  function barycenterPrimaryId(id) {
6216
6238
  if (id === "naif-0") return SUN_ID;
6217
6239
  return dominantPlanetId(id);
@@ -6507,38 +6529,76 @@ var loadProgress = new LoadProgress();
6507
6529
  //#endregion
6508
6530
  //#region src/lib/fetch/fetch-timeout.ts
6509
6531
  var BOOT_FETCH_TIMEOUT_MS = 3e4;
6510
- function countBootBytes(res, background) {
6511
- if (background || !loadProgress.active || !res.body) return res;
6532
+ function bootByteCounter(res, background) {
6533
+ if (background || !loadProgress.active) return null;
6512
6534
  const total = Number(res.headers.get("content-length"));
6513
- if (!Number.isFinite(total) || total <= 0) return res;
6535
+ if (!Number.isFinite(total) || total <= 0) return null;
6514
6536
  loadProgress.announce(total);
6515
- const counter = new TransformStream({ transform(chunk, controller) {
6516
- loadProgress.addBytes(chunk.byteLength);
6517
- controller.enqueue(chunk);
6518
- } });
6519
- return new Response(res.body.pipeThrough(counter), {
6520
- status: res.status,
6521
- statusText: res.statusText,
6522
- headers: res.headers
6523
- });
6537
+ return (bytes) => loadProgress.addBytes(bytes);
6524
6538
  }
6525
6539
  async function fetchWithTimeout(input, init = {}, timeoutMs = BOOT_FETCH_TIMEOUT_MS) {
6526
6540
  const controller = new AbortController();
6527
- const timer = setTimeout(() => controller.abort(), timeoutMs);
6541
+ let timer;
6542
+ let stalled = false;
6543
+ const arm = () => {
6544
+ clearTimeout(timer);
6545
+ timer = setTimeout(() => {
6546
+ stalled = true;
6547
+ controller.abort();
6548
+ }, timeoutMs);
6549
+ };
6528
6550
  const onCallerAbort = () => controller.abort();
6529
6551
  init.signal?.addEventListener("abort", onCallerAbort, { once: true });
6552
+ const release = () => {
6553
+ clearTimeout(timer);
6554
+ init.signal?.removeEventListener("abort", onCallerAbort);
6555
+ };
6556
+ const failure = (e) => stalled ? new Error(`Request stalled for ${timeoutMs} ms: ${input}`, { cause: e }) : e;
6557
+ arm();
6558
+ let res;
6530
6559
  try {
6531
- return countBootBytes(await fetch(input, {
6560
+ res = await fetch(input, {
6532
6561
  ...init,
6533
6562
  signal: controller.signal
6534
- }), init.priority === "low");
6563
+ });
6535
6564
  } catch (e) {
6536
- if (controller.signal.aborted && !init.signal?.aborted) throw new Error(`Request timed out after ${timeoutMs} ms: ${input}`, { cause: e });
6537
- throw e;
6538
- } finally {
6539
- clearTimeout(timer);
6540
- init.signal?.removeEventListener("abort", onCallerAbort);
6541
- }
6565
+ release();
6566
+ throw failure(e);
6567
+ }
6568
+ if (!res.body) {
6569
+ release();
6570
+ return res;
6571
+ }
6572
+ const count = bootByteCounter(res, init.priority === "low");
6573
+ const reader = res.body.getReader();
6574
+ const body = new ReadableStream({
6575
+ async pull(out) {
6576
+ arm();
6577
+ try {
6578
+ const { done, value } = await reader.read();
6579
+ clearTimeout(timer);
6580
+ if (done) {
6581
+ release();
6582
+ out.close();
6583
+ return;
6584
+ }
6585
+ count?.(value.byteLength);
6586
+ out.enqueue(value);
6587
+ } catch (e) {
6588
+ release();
6589
+ out.error(failure(e));
6590
+ }
6591
+ },
6592
+ cancel(reason) {
6593
+ release();
6594
+ return reader.cancel(reason);
6595
+ }
6596
+ });
6597
+ return new Response(body, {
6598
+ status: res.status,
6599
+ statusText: res.statusText,
6600
+ headers: res.headers
6601
+ });
6542
6602
  }
6543
6603
 
6544
6604
  //#endregion
@@ -7147,7 +7207,7 @@ function parseLabels(text) {
7147
7207
  return out;
7148
7208
  }
7149
7209
  var labelsByLang = /* @__PURE__ */ new Map();
7150
- async function fetchLabels$1(lang = getLocale()) {
7210
+ async function fetchLabels(lang = getLocale()) {
7151
7211
  let p = labelsByLang.get(lang);
7152
7212
  if (!p) {
7153
7213
  p = (async () => {
@@ -7372,6 +7432,7 @@ function parseProbesPayload(buffer, startJd, endJd, float64Coeffs) {
7372
7432
  const fitCenterIdValue = view.getInt32(offset + 12, true);
7373
7433
  const fitCenterIdType = view.getUint8(offset + 16);
7374
7434
  const nSystemIntervals = view.getUint8(offset + 17);
7435
+ const archive = PROBE_ARCHIVES[view.getUint8(offset + 18)];
7375
7436
  offset += 20;
7376
7437
  const id = buildObjectId(idTypeOrdinal, objIdValue) ?? "";
7377
7438
  if (!id) console.warn(`probes payload: probe[${p}] (value=${objIdValue}) has unknown id-type ${idTypeOrdinal}; skipping`);
@@ -7424,6 +7485,7 @@ function parseProbesPayload(buffer, startJd, endJd, float64Coeffs) {
7424
7485
  probeId: objIdValue,
7425
7486
  hasLocalized,
7426
7487
  objectType,
7488
+ archive,
7427
7489
  fitCenter,
7428
7490
  subStartEt,
7429
7491
  subEndEt,
@@ -8297,7 +8359,7 @@ async function fetchElements(zone, zoom, part, time, priority) {
8297
8359
  }
8298
8360
  var ChunkLoader = class {
8299
8361
  static prefetch(zone, zoom, part, time = null) {
8300
- fetch(elementsUrl(zone, zoom, part, time));
8362
+ fetch(elementsUrl(zone, zoom, part, time)).catch(() => {});
8301
8363
  }
8302
8364
  barycenters = /* @__PURE__ */ new Map();
8303
8365
  probeBuffers = /* @__PURE__ */ new Map();
@@ -8436,7 +8498,8 @@ var ChunkLoader = class {
8436
8498
  equatorial: false,
8437
8499
  validityStart: startJd,
8438
8500
  validityEnd: endJd,
8439
- orbitalSource: OrbitalSource.SPICE_PROBE
8501
+ orbitalSource: OrbitalSource.SPICE_PROBE,
8502
+ orbitArchive: probe.archive
8440
8503
  };
8441
8504
  const elements = probeOsculatingElements(probe, jd, primaryMu);
8442
8505
  const ownId = id;
@@ -8484,7 +8547,7 @@ var ChunkLoader = class {
8484
8547
  const firstPass = this.barycenters.size === 0;
8485
8548
  const bodies = [];
8486
8549
  let degenerate = 0;
8487
- const [cols, labels] = await Promise.all([fetchElements(zone, zoom, part, time, priority), fetchLabels$1()]);
8550
+ const [cols, labels] = await Promise.all([fetchElements(zone, zoom, part, time, priority), fetchLabels()]);
8488
8551
  const isParabolic = cols.kind === "parabolic";
8489
8552
  const isSGP4 = cols.kind === "sgp4";
8490
8553
  let sliceStart = performance.now();
@@ -8926,6 +8989,41 @@ async function fetchChebyshev(zone, zoom, chunk, priority) {
8926
8989
  };
8927
8990
  }
8928
8991
 
8992
+ //#endregion
8993
+ //#region src/lib/fetch/retry-backoff.ts
8994
+ var FIRST_DELAY_MS = 2e3;
8995
+ var MAX_DELAY_MS = 6e4;
8996
+ var JITTER = .25;
8997
+ var RetryBackoff = class {
8998
+ failures = /* @__PURE__ */ new Map();
8999
+ constructor(onDue = () => {}, random = Math.random) {
9000
+ this.onDue = onDue;
9001
+ this.random = random;
9002
+ }
9003
+ waiting(key) {
9004
+ return this.failures.get(key)?.timer !== void 0;
9005
+ }
9006
+ fail(key) {
9007
+ const last = this.failures.get(key);
9008
+ clearTimeout(last?.timer);
9009
+ const failure = {
9010
+ count: (last?.count ?? 0) + 1,
9011
+ timer: void 0
9012
+ };
9013
+ const delay = Math.min(FIRST_DELAY_MS * 2 ** (failure.count - 1), MAX_DELAY_MS);
9014
+ const spread = 1 + JITTER * (2 * this.random() - 1);
9015
+ failure.timer = setTimeout(() => {
9016
+ failure.timer = void 0;
9017
+ this.onDue(key);
9018
+ }, delay * spread);
9019
+ this.failures.set(key, failure);
9020
+ }
9021
+ clear(key) {
9022
+ clearTimeout(this.failures.get(key)?.timer);
9023
+ this.failures.delete(key);
9024
+ }
9025
+ };
9026
+
8929
9027
  //#endregion
8930
9028
  //#region src/lib/fetch/position/chunk-window.ts
8931
9029
  var NEIGHBOR_WINDOW = 1;
@@ -8936,6 +9034,11 @@ function chunkKey(zone, chunkIdx) {
8936
9034
  var ChunkWindow = class {
8937
9035
  chunks = /* @__PURE__ */ new Map();
8938
9036
  inflight = /* @__PURE__ */ new Map();
9037
+ retries = new RetryBackoff(() => {
9038
+ this.retryDue = true;
9039
+ this._version++;
9040
+ });
9041
+ retryDue = false;
8939
9042
  lastEnsuredJd = NaN;
8940
9043
  _version = 0;
8941
9044
  constructor(zoneParams) {
@@ -8948,19 +9051,21 @@ var ChunkWindow = class {
8948
9051
  return Array.from(this.zoneParams.keys());
8949
9052
  }
8950
9053
  ensure(jd) {
8951
- if (jd === this.lastEnsuredJd) return {
8952
- ready: this.allCurrentChunksLoaded(jd),
8953
- done: this.pendingCurrent(jd)
8954
- };
9054
+ if (jd === this.lastEnsuredJd && !this.retryDue) return;
9055
+ this.pass(jd, false);
9056
+ }
9057
+ load(jd) {
9058
+ return this.pass(jd, true);
9059
+ }
9060
+ pass(jd, now) {
8955
9061
  this.lastEnsuredJd = jd;
9062
+ this.retryDue = false;
8956
9063
  const jobs = [];
8957
9064
  const neighbors = [];
8958
- let ready = true;
8959
9065
  for (const [zone, params] of this.zoneParams) {
8960
9066
  const center = chunkIndexForJd(params, jd);
8961
- if (this.isLoadable(params, center) && !this.isResident(zone, center)) {
8962
- ready = false;
8963
- const job = this.loadChunk(zone, params, center, "high");
9067
+ if (this.isLoadable(params, center)) {
9068
+ const job = this.loadChunk(zone, params, center, "high", now);
8964
9069
  if (job) jobs.push(job);
8965
9070
  }
8966
9071
  for (let d = -NEIGHBOR_WINDOW; d <= NEIGHBOR_WINDOW; d++) {
@@ -8978,10 +9083,7 @@ var ChunkWindow = class {
8978
9083
  done.catch(() => {}).then(() => {
8979
9084
  for (const [zone, params, idx] of neighbors) this.loadChunk(zone, params, idx, "low")?.catch(() => {});
8980
9085
  });
8981
- return {
8982
- ready,
8983
- done
8984
- };
9086
+ return done;
8985
9087
  }
8986
9088
  request(jd) {
8987
9089
  for (const [zone, params] of this.zoneParams) {
@@ -8997,23 +9099,6 @@ var ChunkWindow = class {
8997
9099
  }
8998
9100
  return false;
8999
9101
  }
9000
- pendingCurrent(jd) {
9001
- if (this.inflight.size === 0) return RESOLVED;
9002
- let pending = null;
9003
- for (const [zone, params] of this.zoneParams) {
9004
- const job = this.inflight.get(chunkKey(zone, chunkIndexForJd(params, jd)));
9005
- if (job) (pending ??= []).push(job);
9006
- }
9007
- return pending ? Promise.all(pending).then(() => void 0) : RESOLVED;
9008
- }
9009
- allCurrentChunksLoaded(jd) {
9010
- for (const [zone, params] of this.zoneParams) {
9011
- const center = chunkIndexForJd(params, jd);
9012
- if (!this.isLoadable(params, center)) continue;
9013
- if (!this.isResident(zone, center)) return false;
9014
- }
9015
- return true;
9016
- }
9017
9102
  isResident(zone, chunkIdx) {
9018
9103
  return this.chunks.get(zone)?.has(chunkIdx) ?? false;
9019
9104
  }
@@ -9022,14 +9107,22 @@ var ChunkWindow = class {
9022
9107
  if (!zoneMap) return;
9023
9108
  for (const idx of zoneMap.keys()) if (idx < center - NEIGHBOR_WINDOW || idx > center + NEIGHBOR_WINDOW) zoneMap.delete(idx);
9024
9109
  }
9025
- loadChunk(zone, params, chunkIdx, priority) {
9110
+ loadChunk(zone, params, chunkIdx, priority, now = false) {
9026
9111
  if (this.isResident(zone, chunkIdx)) return null;
9027
9112
  const key = chunkKey(zone, chunkIdx);
9028
9113
  const existing = this.inflight.get(key);
9029
9114
  if (existing) return existing;
9030
- const job = this.fetchAndStore(zone, params, chunkIdx, priority);
9115
+ if (!now && this.retries.waiting(key)) return null;
9116
+ return this.track(key, this.fetchAndStore(zone, params, chunkIdx, priority));
9117
+ }
9118
+ track(key, job) {
9031
9119
  this.inflight.set(key, job);
9032
- job.finally(() => this.inflight.delete(key));
9120
+ const settle = (ok) => {
9121
+ if (this.inflight.get(key) === job) this.inflight.delete(key);
9122
+ if (ok) this.retries.clear(key);
9123
+ else this.retries.fail(key);
9124
+ };
9125
+ job.then(() => settle(true), () => settle(false));
9033
9126
  return job;
9034
9127
  }
9035
9128
  async fetchAndStore(zone, params, chunkIdx, priority) {
@@ -9160,15 +9253,7 @@ var ProbeStore = class extends ChunkWindow {
9160
9253
  if (this.isResident(zone, idx)) continue;
9161
9254
  const key = chunkKey(zone, idx);
9162
9255
  if (this.warmed.has(key)) continue;
9163
- const existing = this.inflight.get(key);
9164
- if (existing) {
9165
- jobs.push(existing);
9166
- continue;
9167
- }
9168
- const job = this.fetchAndWarm(key, zone, idx, params);
9169
- this.inflight.set(key, job);
9170
- job.finally(() => this.inflight.delete(key));
9171
- jobs.push(job);
9256
+ jobs.push(this.inflight.get(key) ?? this.track(key, this.fetchAndWarm(key, zone, idx, params)));
9172
9257
  }
9173
9258
  await Promise.all(jobs);
9174
9259
  }
@@ -9390,9 +9475,11 @@ function rowsFor(chunk, id) {
9390
9475
 
9391
9476
  //#endregion
9392
9477
  //#region src/lib/fetch/gz.ts
9478
+ function gunzip(res) {
9479
+ return new Response(res.body.pipeThrough(new DecompressionStream("gzip")));
9480
+ }
9393
9481
  async function gunzipJson(res) {
9394
- const ds = new DecompressionStream("gzip");
9395
- return await new Response(res.body.pipeThrough(ds)).json();
9482
+ return await gunzip(res).json();
9396
9483
  }
9397
9484
  function memoizedGzJson(url, opts = {}) {
9398
9485
  const { onMissing, fetcher = (u) => fetch(u), error = (res, u) => `memoizedGzJson: ${u} returned ${res.status} ${res.statusText}` } = opts;
@@ -9654,13 +9741,7 @@ async function createPlaceholderBody(targetId, ctx, visited = /* @__PURE__ */ ne
9654
9741
  return [];
9655
9742
  }
9656
9743
  visited.add(targetId);
9657
- let detail;
9658
- try {
9659
- detail = await fetchObjectDetail(targetId);
9660
- } catch {
9661
- console.warn(`createPlaceholderBody: failed to fetch global data for ${targetId} — hiding`);
9662
- return [];
9663
- }
9744
+ const detail = await fetchObjectDetail(targetId);
9664
9745
  const global = detail.global;
9665
9746
  if (!global) {
9666
9747
  console.warn(`createPlaceholderBody: no catalogue record for ${targetId} — hiding`);
@@ -9710,7 +9791,7 @@ async function ensureTargetStreamed(ctx, targetId) {
9710
9791
  if (ctx.getBody(targetId)) return;
9711
9792
  const placeholders = await createPlaceholderBody(targetId, ctx);
9712
9793
  if (placeholders.length === 0) return;
9713
- const added = routePlaceholders(ctx, placeholders, await fetchLabels$1());
9794
+ const added = routePlaceholders(ctx, placeholders, await fetchLabels());
9714
9795
  ctx.bodies.flushMinor(added > 0);
9715
9796
  }
9716
9797
 
@@ -9771,11 +9852,17 @@ function setSceneSettings(settings) {
9771
9852
  //#region src/lib/scene/zone-refresher.ts
9772
9853
  var MIN_LOAD_INTERVAL_MS = 2e3;
9773
9854
  var STALE_FAILURE_STREAK = 3;
9855
+ function loadKey(z, target) {
9856
+ return `${z.zone}:${z.zoom ?? ""}:${target}`;
9857
+ }
9774
9858
  var ZoneRefresher = class {
9775
9859
  zones = [];
9776
9860
  latestDate;
9777
9861
  failureStreak = 0;
9778
9862
  staleNotified = false;
9863
+ retries = new RetryBackoff(() => this.wakes++);
9864
+ wakes = 0;
9865
+ wakeTimer;
9779
9866
  constructor(ctx, metadata, loader, initialDate) {
9780
9867
  this.ctx = ctx;
9781
9868
  this.loader = loader;
@@ -9821,16 +9908,30 @@ var ZoneRefresher = class {
9821
9908
  }
9822
9909
  }
9823
9910
  }
9824
- recordSuccess() {
9911
+ recordSuccess(key) {
9912
+ this.retries.clear(key);
9825
9913
  this.failureStreak = 0;
9826
9914
  }
9827
- recordFailure() {
9915
+ recordFailure(key) {
9916
+ this.retries.fail(key);
9828
9917
  this.failureStreak++;
9829
9918
  if (this.failureStreak >= STALE_FAILURE_STREAK && !this.staleNotified) {
9830
9919
  this.staleNotified = true;
9831
9920
  this.ctx.onDataStale?.();
9832
9921
  }
9833
9922
  }
9923
+ wakeIn(ms) {
9924
+ this.wakeTimer ??= setTimeout(() => {
9925
+ this.wakeTimer = void 0;
9926
+ this.wakes++;
9927
+ }, ms);
9928
+ }
9929
+ start(z, load) {
9930
+ z.inFlight = load.catch(() => {}).finally(() => {
9931
+ z.inFlight = null;
9932
+ });
9933
+ return load;
9934
+ }
9834
9935
  invalidateZone(zoneName) {
9835
9936
  for (const z of this.zones) {
9836
9937
  if (z.zone !== zoneName || z.kind !== "time") continue;
@@ -9851,12 +9952,15 @@ var ZoneRefresher = class {
9851
9952
  if (state.inFlight) continue;
9852
9953
  const target = snapshotDate(state.zoomData, date);
9853
9954
  if (target === state.currentTime) continue;
9955
+ if (this.retries.waiting(loadKey(state, target))) continue;
9854
9956
  const nowMs = performance.now();
9855
- if (nowMs - state.lastLoadStartMs < MIN_LOAD_INTERVAL_MS) continue;
9957
+ const held = state.lastLoadStartMs + MIN_LOAD_INTERVAL_MS - nowMs;
9958
+ if (held > 0) {
9959
+ this.wakeIn(held);
9960
+ continue;
9961
+ }
9856
9962
  state.lastLoadStartMs = nowMs;
9857
- state.inFlight = this.loadTime(state, target, date).finally(() => {
9858
- state.inFlight = null;
9859
- });
9963
+ this.start(state, this.loadTime(state, target, date));
9860
9964
  } else {
9861
9965
  while (true) {
9862
9966
  const target = chunkIndexForJd(state.zoomData, jd);
@@ -9871,18 +9975,18 @@ var ZoneRefresher = class {
9871
9975
  if (state.inFlight) continue;
9872
9976
  const target = chunkIndexForJd(state.zoomData, jd);
9873
9977
  if (target === state.currentIdx) continue;
9874
- state.inFlight = this.loadChunk(state, target).finally(() => {
9875
- state.inFlight = null;
9876
- });
9978
+ if (this.retries.waiting(loadKey(state, target))) continue;
9979
+ this.start(state, this.loadChunk(state, target));
9877
9980
  }
9878
9981
  }
9879
9982
  async ready(date = this.latestDate) {
9880
9983
  this.latestDate = date;
9881
9984
  const jd = dateToJD(date);
9882
9985
  const waits = this.zones.map((z) => this.zoneReady(z, date, jd));
9883
- if (this.ctx.chebStore) waits.push(this.ctx.chebStore.ensure(jd).done);
9884
- if (this.ctx.probeStore) waits.push(this.ctx.probeStore.ensure(jd).done);
9885
- await Promise.all(waits);
9986
+ if (this.ctx.chebStore) waits.push(this.ctx.chebStore.load(jd));
9987
+ if (this.ctx.probeStore) waits.push(this.ctx.probeStore.load(jd));
9988
+ const failure = (await Promise.allSettled(waits)).find((w) => w.status === "rejected");
9989
+ if (failure) throw failure.reason;
9886
9990
  }
9887
9991
  async zoneReady(z, date, jd) {
9888
9992
  while (z.inFlight) await z.inFlight;
@@ -9890,17 +9994,12 @@ var ZoneRefresher = class {
9890
9994
  const target = snapshotDate(z.zoomData, date);
9891
9995
  if (target === z.currentTime) return;
9892
9996
  z.lastLoadStartMs = performance.now();
9893
- z.inFlight = this.loadTime(z, target, date).finally(() => {
9894
- z.inFlight = null;
9895
- });
9997
+ await this.start(z, this.loadTime(z, target, date));
9896
9998
  } else {
9897
9999
  const target = chunkIndexForJd(z.zoomData, jd);
9898
10000
  if (target === z.currentIdx) return;
9899
- z.inFlight = this.loadChunk(z, target).finally(() => {
9900
- z.inFlight = null;
9901
- });
10001
+ await this.start(z, this.loadChunk(z, target));
9902
10002
  }
9903
- await z.inFlight;
9904
10003
  }
9905
10004
  wanted = /* @__PURE__ */ new Set();
9906
10005
  want(id) {
@@ -9913,7 +10012,7 @@ var ZoneRefresher = class {
9913
10012
  this.want(targetId);
9914
10013
  if (this.ctx.earthSatFilter || this.ctx.earthTypeFilter) {
9915
10014
  this.invalidateZone("earth");
9916
- await this.ready(date);
10015
+ await this.ready(date).catch(() => {});
9917
10016
  if (this.ctx.getBody(targetId)) return;
9918
10017
  }
9919
10018
  }
@@ -9924,13 +10023,13 @@ var ZoneRefresher = class {
9924
10023
  if (!store) return;
9925
10024
  const jd = dateToJD(date);
9926
10025
  const passengerPromise = passengerFor(targetId);
9927
- await store.ensure(jd).done;
10026
+ await store.load(jd).catch(() => {});
9928
10027
  const passenger = await passengerPromise;
9929
10028
  if (passenger) store.registerCarried(passenger);
9930
10029
  const fcId = store.stampedFitCenterAt(targetId, jd);
9931
10030
  if (fcId) await ensureTargetStreamed(this.ctx, fcId);
9932
10031
  if (this.ctx.getBody(targetId)) return;
9933
- const labels = await fetchLabels$1();
10032
+ const labels = await fetchLabels();
9934
10033
  const target = this.loader.processProbes(store, date, labels).find((b) => b.data.id === targetId);
9935
10034
  if (!target) return;
9936
10035
  this.ctx.bodies.addBodies([target]);
@@ -9991,10 +10090,11 @@ var ZoneRefresher = class {
9991
10090
  if (z.zone === "earth") this.ctx.notifyEarthSatRollover();
9992
10091
  console.log(`zone-refresher: ${z.zone}@${fromTime} → ${time} (+${added} ~${updated} -${removed})`);
9993
10092
  this.prefetchTimeNeighbors(z, date);
9994
- this.recordSuccess();
10093
+ this.recordSuccess(loadKey(z, time));
9995
10094
  } catch (e) {
9996
10095
  console.warn(`zone-refresher: failed to refresh ${z.zone}@${time}:`, e);
9997
- this.recordFailure();
10096
+ this.recordFailure(loadKey(z, time));
10097
+ throw e;
9998
10098
  }
9999
10099
  }
10000
10100
  async loadChunk(z, target) {
@@ -10007,11 +10107,12 @@ var ZoneRefresher = class {
10007
10107
  else chunks = await Promise.all(Array.from({ length: z.parts }, (_, part) => this.loader.process(z.zone, z.zoom, part, String(target), z.parentIdType)));
10008
10108
  this.applyChunkSwap(z, previous, target, chunks);
10009
10109
  this.preloadChunkNeighbors(z);
10010
- this.recordSuccess();
10110
+ this.recordSuccess(loadKey(z, target));
10011
10111
  } catch (e) {
10012
10112
  console.warn(`zone-refresher: ${z.zone} chunk reload failed (${previous} → ${target}):`, e);
10013
10113
  z.currentIdx = previous;
10014
- this.recordFailure();
10114
+ this.recordFailure(loadKey(z, target));
10115
+ throw e;
10015
10116
  }
10016
10117
  }
10017
10118
  applyChunkSwap(z, previous, target, chunks) {
@@ -10360,18 +10461,18 @@ async function buildProbeStore(metadata, jd, layers) {
10360
10461
  const missingCenter = Array.from(params).filter(([, p]) => p.fit_center_naif_id === void 0);
10361
10462
  if (missingCenter.length > 0) console.warn(`ProbeStore: ${missingCenter.length} zone(s) missing fit_center_naif_id in metadata — re-export to refresh: ${missingCenter.map(([z]) => z).join(", ")}`);
10362
10463
  const store = new ProbeStore(params);
10363
- await store.ensure(jd).done;
10464
+ await store.load(jd);
10364
10465
  return store;
10365
10466
  }
10366
10467
  async function loadMajorBodies(ctx, loader, metadata, date, jd, passenger) {
10367
10468
  const major = [];
10368
10469
  let cachedLabels = null;
10369
10470
  if (ctx.chebStore) {
10370
- cachedLabels = await fetchLabels$1();
10471
+ cachedLabels = await fetchLabels();
10371
10472
  major.push(...loader.processChebyshev(date, cachedLabels));
10372
10473
  }
10373
10474
  if (ctx.probeStore) {
10374
- cachedLabels ??= await fetchLabels$1();
10475
+ cachedLabels ??= await fetchLabels();
10375
10476
  if (passenger) ctx.probeStore.registerCarried(passenger);
10376
10477
  major.push(...loader.processProbes(ctx.probeStore, date, cachedLabels));
10377
10478
  }
@@ -10404,7 +10505,7 @@ async function loadScene(ctx, date, targetId) {
10404
10505
  const atmospheresPromise = loadAtmospheres().catch((e) => console.warn("scene-load: atmospheres failed to load — rendering without shells:", e));
10405
10506
  const jd = dateToJD(date);
10406
10507
  const metadataPromise = fetchMetadata();
10407
- fetchLabels$1().catch(() => {});
10508
+ fetchLabels().catch(() => {});
10408
10509
  metadataPromise.then((metadata) => {
10409
10510
  const moons = metadata.position.zones.moons;
10410
10511
  if (!moons || ctx.layers.skipsZone("moons")) return;
@@ -10420,7 +10521,7 @@ async function loadScene(ctx, date, targetId) {
10420
10521
  for (const zone of params.keys()) if (ctx.layers.skipsZone(zone)) params.delete(zone);
10421
10522
  if (params.size === 0) return null;
10422
10523
  const store = new ChebyshevStore(params);
10423
- await store.ensure(jd).done;
10524
+ await store.load(jd);
10424
10525
  return store;
10425
10526
  });
10426
10527
  const probePromise = metadataPromise.then((metadata) => buildProbeStore(metadata, jd, ctx.layers));
@@ -10438,7 +10539,7 @@ async function loadScene(ctx, date, targetId) {
10438
10539
  await atmospheresPromise;
10439
10540
  ctx.bodies.addBodies(major);
10440
10541
  ctx.credits.recordOrbitSources(major);
10441
- const labels = await fetchLabels$1();
10542
+ const labels = await fetchLabels();
10442
10543
  loadProgress.reach("labels");
10443
10544
  let addedSinceFlush = false;
10444
10545
  const noteAdded = () => {
@@ -10489,8 +10590,8 @@ async function loadScene(ctx, date, targetId) {
10489
10590
  console.warn(`scene-load: ${zone} part ${part} failed (skipped):`, e);
10490
10591
  };
10491
10592
  try {
10492
- await Promise.all([...asteroidOtherArgs.map(({ zone, zoom, part, time, parentIdType }) => loader.fetchMinorColumns(zone, zoom, part, time).then((cols) => handleColumnChunk(zone, cols, parentIdType)).catch(onChunkFail(zone, part))), ctx.refresher?.ready().then(() => {
10493
- noteAdded();
10593
+ await Promise.all([...asteroidOtherArgs.map(({ zone, zoom, part, time, parentIdType }) => loader.fetchMinorColumns(zone, zoom, part, time).then((cols) => handleColumnChunk(zone, cols, parentIdType)).catch(onChunkFail(zone, part))), ctx.refresher?.ready().then(noteAdded, (e) => {
10594
+ console.warn("scene-load: the data of the opening date is not all in:", e);
10494
10595
  })]);
10495
10596
  await Promise.all(moonArgs.map(({ zone, zoom, part, time, parentIdType }) => loader.process(zone, zoom, part, time, parentIdType).then((chunk) => handleMoonChunk(zone, chunk)).catch(onChunkFail(zone, part))));
10496
10597
  } finally {
@@ -10521,6 +10622,7 @@ var fetchEarthMembership = memoizedGzJson(async () => {
10521
10622
  await fetchMetadata();
10522
10623
  return versionedUrl("/v1/membership/earth.json.gz", "membership");
10523
10624
  }, {
10625
+ fetcher: fetchWithTimeout,
10524
10626
  onMissing: () => ({}),
10525
10627
  error: (res) => `fetchEarthMembership: ${res.status} ${res.statusText}`
10526
10628
  });
@@ -10573,6 +10675,7 @@ var ContextManager = class {
10573
10675
  smallBodyFilter = null;
10574
10676
  missionMemberIds = null;
10575
10677
  currentGroupSlug = null;
10678
+ groupFilterJob = Promise.resolve();
10576
10679
  groupFilterListeners = /* @__PURE__ */ new Set();
10577
10680
  smallBodyFilterListeners = /* @__PURE__ */ new Set();
10578
10681
  earthSatRolloverListeners = /* @__PURE__ */ new Set();
@@ -10613,12 +10716,15 @@ var ContextManager = class {
10613
10716
  try {
10614
10717
  await loadScene(this, date, targetId);
10615
10718
  } catch (e) {
10616
- this.loading = false;
10617
- this.error = e instanceof Error ? e.message : String(e);
10618
- console.error("[scene-load]", e);
10719
+ this.fail(e);
10619
10720
  throw e;
10620
10721
  }
10621
10722
  }
10723
+ fail(e) {
10724
+ this.loading = false;
10725
+ this.error = e instanceof Error ? e.message : String(e);
10726
+ console.error("[scene-load]", e);
10727
+ }
10622
10728
  refreshTick(date) {
10623
10729
  this.refresher?.tick(date);
10624
10730
  }
@@ -10628,15 +10734,23 @@ var ContextManager = class {
10628
10734
  async ensureBody(targetId, date) {
10629
10735
  await this.refresher?.ensureBody(targetId, date);
10630
10736
  }
10631
- async applyGroupFilter(slug) {
10632
- if (slug === this.currentGroupSlug) return;
10737
+ applyGroupFilter(slug) {
10738
+ if (slug === this.currentGroupSlug) return this.groupFilterJob;
10633
10739
  this.currentGroupSlug = slug;
10740
+ const job = this.installGroupFilter(slug);
10741
+ this.groupFilterJob = job;
10742
+ job.catch(() => {
10743
+ if (this.groupFilterJob === job) this.currentGroupSlug = void 0;
10744
+ });
10745
+ return job;
10746
+ }
10747
+ async installGroupFilter(slug) {
10634
10748
  if (slug?.startsWith("mission-")) {
10635
10749
  const detail = await fetchGroupDetail(slug);
10636
10750
  if (slug !== this.currentGroupSlug) return;
10637
10751
  this.missionMemberIds = new Set((detail.global?.notable_members ?? []).map((mm) => mm.id).filter((id) => !!id));
10638
10752
  } else this.missionMemberIds = null;
10639
- const entry = slug ? await this.resolveIndexEntry(slug) : null;
10753
+ const entry = slug ? (await fetchGroupIndex())[slug] ?? null : null;
10640
10754
  if (slug !== this.currentGroupSlug) return;
10641
10755
  const nextSmallBody = this.resolveSmallBodyFilter(slug, entry?.applies_to, entry?.n);
10642
10756
  const nextEarthSlug = entry?.applies_to === "earth_sat" ? slug : null;
@@ -10685,13 +10799,6 @@ var ContextManager = class {
10685
10799
  }
10686
10800
  return null;
10687
10801
  }
10688
- async resolveIndexEntry(slug) {
10689
- try {
10690
- return (await fetchGroupIndex())[slug] ?? null;
10691
- } catch {
10692
- return null;
10693
- }
10694
- }
10695
10802
  };
10696
10803
 
10697
10804
  //#endregion
@@ -11307,6 +11414,51 @@ function makeEnvMap(renderer) {
11307
11414
  return tex;
11308
11415
  }
11309
11416
 
11417
+ //#endregion
11418
+ //#region src/lib/scene/setup/bloom-high-pass.ts
11419
+ var MAX_SPAN = 16;
11420
+ function installBloomHighPass(pass) {
11421
+ pass.materialHighPassFilter.dispose();
11422
+ pass.materialHighPassFilter = new ShaderMaterial({
11423
+ uniforms: pass.highPassUniforms,
11424
+ vertexShader: `
11425
+ varying vec2 vUv;
11426
+ void main() {
11427
+ vUv = uv;
11428
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
11429
+ }
11430
+ `,
11431
+ fragmentShader: `
11432
+ uniform sampler2D tDiffuse;
11433
+ uniform float luminosityThreshold;
11434
+ uniform float smoothWidth;
11435
+ varying vec2 vUv;
11436
+ void main() {
11437
+ vec2 size = vec2(textureSize(tDiffuse, 0));
11438
+ vec2 span = size * abs(vec2(dFdx(vUv.x), dFdy(vUv.y)));
11439
+ vec2 lo = vUv * size - 0.5 * span;
11440
+ vec2 hi = lo + span;
11441
+ // A sliver of a pixel is rounding, not light.
11442
+ ivec2 first = max(ivec2(floor(lo + 0.001)), ivec2(0));
11443
+ ivec2 last = min(ivec2(ceil(hi - 0.001)) - 1, ivec2(size) - 1);
11444
+ last = min(last, first + ${MAX_SPAN - 1});
11445
+ vec3 sum = vec3(0.0);
11446
+ for (int y = first.y; y <= last.y; y++) {
11447
+ float wy = min(float(y) + 1.0, hi.y) - max(float(y), lo.y);
11448
+ for (int x = first.x; x <= last.x; x++) {
11449
+ float wx = min(float(x) + 1.0, hi.x) - max(float(x), lo.x);
11450
+ vec3 texel = texelFetch(tDiffuse, ivec2(x, y), 0).rgb;
11451
+ float taken = smoothstep(
11452
+ luminosityThreshold, luminosityThreshold + smoothWidth, luminance(texel));
11453
+ sum += texel * (taken * wx * wy);
11454
+ }
11455
+ }
11456
+ gl_FragColor = vec4(sum / (span.x * span.y), 1.0);
11457
+ }
11458
+ `
11459
+ });
11460
+ }
11461
+
11310
11462
  //#endregion
11311
11463
  //#region src/lib/scene/setup/three-boot.ts
11312
11464
  var CAMERA_FAR_DEFAULT = 1e5;
@@ -11373,6 +11525,7 @@ function bootThree(canvas, labelContainer, ctx) {
11373
11525
  composer.setSize(canvas.clientWidth, canvas.clientHeight);
11374
11526
  composer.addPass(new RenderPass(scene, camera));
11375
11527
  const bloomPass = new UnrealBloomPass(new Vector2(canvas.clientWidth * tier.bloomScale, canvas.clientHeight * tier.bloomScale), .3, .5, 1);
11528
+ installBloomHighPass(bloomPass);
11376
11529
  composer.addPass(bloomPass);
11377
11530
  composer.addPass(new OutputPass());
11378
11531
  return {
@@ -11447,7 +11600,7 @@ var AttitudeTrack = class {
11447
11600
  files;
11448
11601
  chunks = /* @__PURE__ */ new Map();
11449
11602
  inflight = /* @__PURE__ */ new Set();
11450
- failed = /* @__PURE__ */ new Set();
11603
+ retries = new RetryBackoff();
11451
11604
  constructor(probeId, manifest) {
11452
11605
  this.probeId = probeId;
11453
11606
  this.startJd = manifest.files.length ? manifest.files[0].start_jd : 0;
@@ -11527,7 +11680,7 @@ var AttitudeTrack = class {
11527
11680
  if (i < 0 || i >= this.files.length) return void 0;
11528
11681
  const have = this.chunks.get(i);
11529
11682
  if (have) return have;
11530
- if (this.inflight.has(i) || this.failed.has(i)) return void 0;
11683
+ if (this.inflight.has(i) || this.retries.waiting(i)) return void 0;
11531
11684
  this.inflight.add(i);
11532
11685
  this.load(i);
11533
11686
  }
@@ -11539,8 +11692,9 @@ var AttitudeTrack = class {
11539
11692
  const ds = new DecompressionStream("gzip");
11540
11693
  const buf = await new Response(res.body.pipeThrough(ds)).arrayBuffer();
11541
11694
  this.chunks.set(i, parseAttitudeChunk(buf));
11695
+ this.retries.clear(i);
11542
11696
  } catch (e) {
11543
- this.failed.add(i);
11697
+ this.retries.fail(i);
11544
11698
  console.warn(`attitude: chunk ${this.probeId}/${file.name} failed to load:`, e);
11545
11699
  } finally {
11546
11700
  this.inflight.delete(i);
@@ -12285,12 +12439,20 @@ function fetchBundleMeta(slug) {
12285
12439
  if (!p) {
12286
12440
  p = fetch(versionedUrl(`/v1/models/${slug}/metadata.json`, "models")).then((r) => r.json());
12287
12441
  _bundleMetaCache.set(slug, p);
12442
+ p.catch(() => _bundleMetaCache.delete(slug));
12288
12443
  }
12289
12444
  return p;
12290
12445
  }
12446
+ function allowedTier(meta) {
12447
+ if (textureAllowed(meta.exports.high.distribution)) return "high";
12448
+ if (meta.exports.low && textureAllowed(meta.exports.low.distribution)) return "low";
12449
+ }
12291
12450
  function shapeModelCredit(meta) {
12292
12451
  return meta.credit ?? meta.exports.high.credit;
12293
12452
  }
12453
+ function modelTierCredit(meta, tier) {
12454
+ return meta.exports[tier]?.credit ?? meta.exports.high.credit;
12455
+ }
12294
12456
  function modelUnitScene(bo) {
12295
12457
  return bo.modelUnitOverride ?? bo.radiusScene;
12296
12458
  }
@@ -12345,22 +12507,21 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
12345
12507
  if ((bo.modelLoadEpoch ?? 0) !== epoch) return;
12346
12508
  if (bo.mesh) bo.mesh.visible = false;
12347
12509
  setHaloLoading(bo, true);
12348
- const metaPromise = fetchBundleMeta(slug);
12349
- const gltf = await _loader.loadAsync(versionedUrl(`/v1/models/${slug}/high.glb`, "models"));
12350
- let meta = null;
12351
- try {
12352
- meta = await metaPromise;
12353
- } catch (e) {
12354
- console.warn(`Failed to load model metadata for ${slug}:`, e);
12510
+ const meta = await fetchBundleMeta(slug);
12511
+ const tier = allowedTier(meta);
12512
+ if (!tier) {
12513
+ bo.noPhysical = "model";
12514
+ return;
12355
12515
  }
12516
+ const gltf = await _loader.loadAsync(versionedUrl(`/v1/models/${slug}/${tier}.glb`, "models"));
12356
12517
  if ((bo.modelLoadEpoch ?? 0) !== epoch || !bo.mesh) {
12357
12518
  disposeGltf(gltf.scene);
12358
12519
  return;
12359
12520
  }
12360
- const baseFrame = meta?.frame_map ? frameMapQuaternion(meta.frame_map) : null;
12521
+ const baseFrame = meta.frame_map ? frameMapQuaternion(meta.frame_map) : null;
12361
12522
  bo.body.modelBaseFrame = baseFrame ?? void 0;
12362
12523
  const root = withBaseFrame(gltf.scene, baseFrame);
12363
- const anchor = meta?.model_anchor ? new Vector3(...meta.model_anchor) : null;
12524
+ const anchor = meta.model_anchor ? new Vector3(...meta.model_anchor) : null;
12364
12525
  fitToUnitRadius(root, anchor && baseFrame ? anchor.applyQuaternion(baseFrame) : anchor);
12365
12526
  enableShadows(root);
12366
12527
  modelScene.add(root);
@@ -12368,20 +12529,19 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
12368
12529
  bo.model = root;
12369
12530
  bo.modelName = slug;
12370
12531
  setHaloLoading(bo, false);
12371
- if (meta) {
12372
- if (meta.scale_meters) {
12373
- const spanKm = meta.scale_meters / 2e3;
12374
- bo.body.data.radiusKm = spanKm * (meta.body_span_ratio ?? 1);
12375
- bo.radiusScene = kmToScene(effectiveRadiusKm(bo.body.data));
12376
- bo.modelUnitOverride = meta.body_span_ratio ? kmToScene(spanKm) : void 0;
12377
- }
12378
- ctx?.credits.registerModel({
12379
- bodyId: bo.body.data.id,
12380
- source: meta.exports.high.credit.url,
12381
- organisation: meta.exports.high.credit.name,
12382
- license: meta.exports.high.credit.license
12383
- });
12532
+ if (meta.scale_meters) {
12533
+ const spanKm = meta.scale_meters / 2e3;
12534
+ bo.body.data.radiusKm = spanKm * (meta.body_span_ratio ?? 1);
12535
+ bo.radiusScene = kmToScene(effectiveRadiusKm(bo.body.data));
12536
+ bo.modelUnitOverride = meta.body_span_ratio ? kmToScene(spanKm) : void 0;
12384
12537
  }
12538
+ const credit = modelTierCredit(meta, tier);
12539
+ ctx?.credits.registerModel({
12540
+ bodyId: bo.body.data.id,
12541
+ source: credit.url,
12542
+ organisation: credit.name,
12543
+ license: credit.license
12544
+ });
12385
12545
  } finally {
12386
12546
  if (!bo.model) setHaloLoading(bo, false);
12387
12547
  }
@@ -15175,8 +15335,11 @@ function sunPathTransmittance(params, camRelPos, sunDir, planetRadiusScene, plan
15175
15335
  var SUN_HDR_MULTIPLIER = 6;
15176
15336
  var LIMB_DISC_AVG = .8;
15177
15337
  var STAR_POINT_SIZE_PX = 4;
15178
- var STAR_POINT_TEXEL_ALPHA = .3;
15179
- var STAR_POINT_HANDOFF_INTENSITY = 6 * LIMB_DISC_AVG / STAR_POINT_TEXEL_ALPHA;
15338
+ var STAR_POINT_LIGHT = 6 * LIMB_DISC_AVG * Math.PI * (4 / 2) ** 2;
15339
+ var STAR_POINT_FLOOR = .9;
15340
+ var STAR_POINT_SEAT_BIAS = .125;
15341
+ var CORONA_OPACITY = .6;
15342
+ var CORONA_WHOLE = 1.5;
15180
15343
  function makeGlowTexture(color, size = 256) {
15181
15344
  const canvas = document.createElement("canvas");
15182
15345
  canvas.width = size;
@@ -15257,7 +15420,7 @@ function makeStarGlow(radius, color) {
15257
15420
  map: makeGlowTexture(color.startsWith("#") ? hexToRgb(color) : color),
15258
15421
  blending: AdditiveBlending,
15259
15422
  transparent: true,
15260
- opacity: .6,
15423
+ opacity: CORONA_OPACITY,
15261
15424
  depthWrite: false,
15262
15425
  depthTest: true
15263
15426
  }));
@@ -15265,23 +15428,44 @@ function makeStarGlow(radius, color) {
15265
15428
  corona.scale.set(glowSize, glowSize, 1);
15266
15429
  return corona;
15267
15430
  }
15268
- function makeStarPoint(color, circleTexture) {
15431
+ function dotPixels(size) {
15432
+ const reach = Math.ceil(size / 2);
15433
+ let count = 0;
15434
+ for (let y = -reach; y < reach; y++) for (let x = -reach; x < reach; x++) if ((x + .5) ** 2 + (y + .5) ** 2 <= size * size / 4) count++;
15435
+ return count;
15436
+ }
15437
+ function makeStarPoint(color) {
15269
15438
  const geometry = new BufferGeometry();
15270
15439
  geometry.setAttribute("position", new Float32BufferAttribute(new Float32Array(3), 3));
15271
15440
  const c = new Color(color);
15441
+ const disc = new Color(PHOTOSPHERE_CENTRE_COLOR);
15442
+ const viewport = new Vector4(0, 0, 1, 1);
15443
+ const size = 4 .toFixed(1);
15444
+ const uniforms = {
15445
+ uColor: { value: new Vector3(c.r, c.g, c.b) },
15446
+ uDiscColor: { value: new Vector3(disc.r, disc.g, disc.b) },
15447
+ uFlux: { value: 1 },
15448
+ uPixelRatio: { value: 1 },
15449
+ uPixels: { value: dotPixels(4) },
15450
+ uViewport: { value: viewport },
15451
+ uTint: { value: new Vector3(1, 1, 1) }
15452
+ };
15272
15453
  const points = new Points(geometry, new ShaderMaterial({
15273
- uniforms: {
15274
- uColor: { value: new Vector3(c.r, c.g, c.b) },
15275
- uIntensity: { value: STAR_POINT_HANDOFF_INTENSITY },
15276
- uTint: { value: new Vector3(1, 1, 1) },
15277
- uMap: { value: circleTexture }
15278
- },
15454
+ uniforms,
15279
15455
  vertexShader: `
15280
15456
  #include <common>
15281
15457
  #include <logdepthbuf_pars_vertex>
15458
+ uniform float uPixelRatio;
15459
+ uniform vec4 uViewport;
15460
+ varying vec2 vSeat;
15282
15461
  void main() {
15283
15462
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
15284
- gl_PointSize = ${4 .toFixed(1)};
15463
+ vec2 halfView = 0.5 * uViewport.zw;
15464
+ vec2 seat = floor((gl_Position.xy / gl_Position.w + 1.0) * halfView + ${(.5 + STAR_POINT_SEAT_BIAS).toFixed(3)});
15465
+ gl_Position.xy = (seat / halfView - 1.0) * gl_Position.w;
15466
+ vSeat = uViewport.xy + seat;
15467
+ // A margin: the square of the point must not cut the dot.
15468
+ gl_PointSize = ${size} * uPixelRatio + 2.0;
15285
15469
  #include <logdepthbuf_vertex>
15286
15470
  }
15287
15471
  `,
@@ -15289,14 +15473,35 @@ function makeStarPoint(color, circleTexture) {
15289
15473
  #include <common>
15290
15474
  #include <logdepthbuf_pars_fragment>
15291
15475
  uniform vec3 uColor;
15292
- uniform float uIntensity;
15476
+ uniform vec3 uDiscColor;
15477
+ uniform float uFlux;
15478
+ uniform float uPixelRatio;
15479
+ uniform float uPixels;
15293
15480
  uniform vec3 uTint;
15294
- uniform sampler2D uMap;
15481
+ varying vec2 vSeat;
15295
15482
  void main() {
15296
15483
  #include <logdepthbuf_fragment>
15297
- vec4 texel = texture2D(uMap, gl_PointCoord);
15298
- if (texel.a < 0.01) discard;
15299
- gl_FragColor = vec4(uTint * uColor * uIntensity, texel.a);
15484
+ vec2 p = gl_FragCoord.xy - vSeat;
15485
+ float radius = 0.5 * ${size} * uPixelRatio;
15486
+ if (dot(p, p) > radius * radius) discard;
15487
+ // The disc's colour at the handoff, so the glow keeps it there.
15488
+ vec3 colour = uTint * mix(uColor, uDiscColor, uFlux);
15489
+ float luma = max(luminance(colour), 0.0001);
15490
+ // What the bloom takes for sure, and what it leaves for sure.
15491
+ float over = ${(1 * 1.05).toFixed(2)} / luma;
15492
+ float under = ${(1 * .95).toFixed(2)} / luma;
15493
+ // The disc's light at the handoff, on all the pixels. With too little
15494
+ // for all, the four at the centre have it: the glow goes on for a
15495
+ // fainter star.
15496
+ float all = ${STAR_POINT_LIGHT.toFixed(3)} * uFlux * uPixelRatio * uPixelRatio / uPixels;
15497
+ float four = all * uPixels / 4.0;
15498
+ float value = clamp(all, ${STAR_POINT_FLOOR.toFixed(1)}, under);
15499
+ if (all >= over) {
15500
+ value = all;
15501
+ } else if (four >= over && max(abs(p.x), abs(p.y)) < 1.0) {
15502
+ value = four;
15503
+ }
15504
+ gl_FragColor = vec4(colour * value, 1.0);
15300
15505
  }
15301
15506
  `,
15302
15507
  transparent: true,
@@ -15304,14 +15509,22 @@ function makeStarPoint(color, circleTexture) {
15304
15509
  depthWrite: false
15305
15510
  }));
15306
15511
  points.frustumCulled = false;
15512
+ points.onBeforeRender = (renderer) => {
15513
+ renderer.getCurrentViewport(viewport).floor();
15514
+ const pixelRatio = renderer.getPixelRatio();
15515
+ if (pixelRatio !== uniforms.uPixelRatio.value) {
15516
+ uniforms.uPixelRatio.value = pixelRatio;
15517
+ uniforms.uPixels.value = dotPixels(4 * pixelRatio);
15518
+ }
15519
+ };
15307
15520
  return points;
15308
15521
  }
15309
- function buildStarExtras(root, radius, color, circleTexture) {
15522
+ function buildStarExtras(root, radius, color) {
15310
15523
  const light = new PointLight(16777215, SUN_LIGHT_INTENSITY, 0, 0);
15311
15524
  root.add(light);
15312
15525
  const corona = makeStarGlow(radius, color);
15313
15526
  root.add(corona);
15314
- const starPoint = makeStarPoint(color, circleTexture);
15527
+ const starPoint = makeStarPoint(color);
15315
15528
  root.add(starPoint);
15316
15529
  return {
15317
15530
  light,
@@ -15323,6 +15536,12 @@ function applyStarTint(corona, starPoint, tint) {
15323
15536
  if (corona) corona.material.color.setRGB(tint.x, tint.y, tint.z);
15324
15537
  if (starPoint) starPoint.material.uniforms.uTint.value.copy(tint);
15325
15538
  }
15539
+ function fadeStarCorona(corona, t) {
15540
+ if (!corona) return;
15541
+ const part = Math.min(Math.max((t - 1) / (CORONA_WHOLE - 1), 0), 1);
15542
+ corona.visible = part > 0;
15543
+ corona.material.opacity = CORONA_OPACITY * part;
15544
+ }
15326
15545
  function starViewTintUniforms(material) {
15327
15546
  const uniforms = material?.uniforms;
15328
15547
  return uniforms?.uAtmoTEnable ? uniforms : null;
@@ -15979,7 +16198,7 @@ function disposeMaterial(mat) {
15979
16198
  }
15980
16199
  var STAR_SPHERE_SEGMENTS = 96;
15981
16200
  var BODY_SPHERE_SEGMENTS = 24;
15982
- function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, circleTexture, rendererElement, handleFocus, onHoverChange, halfPromoteIds) {
16201
+ function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, rendererElement, handleFocus, onHoverChange, halfPromoteIds) {
15983
16202
  for (const body of bodies) {
15984
16203
  const id = body.data.id;
15985
16204
  const isHalfPromoted = halfPromoteIds?.has(id) ?? false;
@@ -16001,7 +16220,7 @@ function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, ci
16001
16220
  let atmosphere = null;
16002
16221
  let sunTint;
16003
16222
  if (!isVirtual && !noPhysical) {
16004
- if (isStar) starExtras = buildStarExtras(root, radius, color, circleTexture);
16223
+ if (isStar) starExtras = buildStarExtras(root, radius, color);
16005
16224
  const segments = isStar ? STAR_SPHERE_SEGMENTS : BODY_SPHERE_SEGMENTS;
16006
16225
  const geometry = new SphereGeometry(radius, segments, segments);
16007
16226
  const material = isStar ? makeStarSurfaceMaterial() : new MeshStandardMaterial({ color: bodyMeshColor(body.data) });
@@ -17046,30 +17265,31 @@ async function fetchPositions(bodyId) {
17046
17265
  if (res.status === 404) return [];
17047
17266
  throw new Error(`fetchPositions: ${url} returned ${res.status} ${res.statusText}`);
17048
17267
  }
17049
- const ds = new DecompressionStream("gzip");
17050
- return parseNomenclature(await new Response(res.body.pipeThrough(ds)).arrayBuffer());
17268
+ return parseNomenclature(await gunzip(res).arrayBuffer());
17051
17269
  }
17052
- async function fetchLabels(bodyId, lang) {
17270
+ async function fetchLabelText(bodyId, lang) {
17053
17271
  const url = versionedUrl(`/v1/nomenclature/labels/${lang}/${bodyId}.txt.gz`, "nomenclature");
17054
17272
  const res = await fetch(url);
17055
17273
  if (!res.ok) {
17056
- if (res.status === 404) return [];
17274
+ if (res.status === 404) return "";
17057
17275
  throw new Error(`fetchNomenclatureLabels: ${url} returned ${res.status} ${res.statusText}`);
17058
17276
  }
17059
- const ds = new DecompressionStream("gzip");
17060
- const text = await new Response(res.body.pipeThrough(ds)).text();
17061
- return text === "" ? [] : text.split("\n");
17277
+ return gunzip(res).text();
17278
+ }
17279
+ function nameFeatures(records, labelText) {
17280
+ const labels = labelText === "" ? [] : labelText.split("\n");
17281
+ return records.map((rec, i) => ({
17282
+ ...rec,
17283
+ name: labels[i] ?? `Feature ${rec.featureId}`
17284
+ }));
17062
17285
  }
17063
17286
  function fetchBodyNomenclature(bodyId, lang = getLocale()) {
17064
17287
  const key = `${bodyId}:${lang}`;
17065
17288
  let p = cache$1.get(key);
17066
17289
  if (!p) {
17067
17290
  p = (async () => {
17068
- const [records, labels] = await Promise.all([fetchPositions(bodyId), fetchLabels(bodyId, lang)]);
17069
- return records.map((rec, i) => ({
17070
- ...rec,
17071
- name: labels[i] ?? `Feature ${rec.featureId}`
17072
- }));
17291
+ const [records, labels] = await Promise.all([fetchPositions(bodyId), fetchLabelText(bodyId, lang)]);
17292
+ return nameFeatures(records, labels);
17073
17293
  })();
17074
17294
  cache$1.set(key, p);
17075
17295
  p.catch(() => {
@@ -20478,18 +20698,15 @@ function updateBodyVisibility(bodyObjects, camera, ctx, focusTruePos, focusedBod
20478
20698
  isClose = false;
20479
20699
  }
20480
20700
  if (trail && (!drawn || body.orbitCenter === null)) trail.visible = false;
20701
+ let starIsPoint = false;
20481
20702
  if (bo.starPoint && drawn) {
20482
- const screenR = bo.radiusScene / dist * projScale;
20483
- const subPixel = screenR < STAR_POINT_HANDOFF_R;
20484
- bo.starPoint.visible = subPixel;
20485
- if (subPixel) {
20486
- const material = bo.starPoint.material;
20487
- const t = screenR / STAR_POINT_HANDOFF_R;
20488
- const intensity = Math.max(STAR_POINT_HANDOFF_INTENSITY * t * t, 3);
20489
- material.uniforms.uIntensity.value = intensity;
20490
- }
20703
+ const t = bo.radiusScene / dist * projScale / STAR_POINT_HANDOFF_R;
20704
+ starIsPoint = t < 1;
20705
+ bo.starPoint.visible = starIsPoint;
20706
+ if (starIsPoint) bo.starPoint.material.uniforms.uFlux.value = t * t;
20707
+ fadeStarCorona(bo.corona, t);
20491
20708
  group.visible = true;
20492
- isClose = !subPixel;
20709
+ isClose = !starIsPoint;
20493
20710
  showLabel = !isClose;
20494
20711
  }
20495
20712
  if (bo.isMinor && !isFocused && trail && !ctx.visibility.isPinned(body.data.id)) trail.visible = false;
@@ -20505,7 +20722,7 @@ function updateBodyVisibility(bodyObjects, camera, ctx, focusTruePos, focusedBod
20505
20722
  label.element.remove();
20506
20723
  } else if (group.visible && label && label.parent !== group) group.add(label);
20507
20724
  if (bo.mesh && bo.radiusScene > 0) {
20508
- const subPixel = !isFocused && !isSystemRoot && bo.radiusScene / dist * projScale < .5;
20725
+ const subPixel = starIsPoint || !isFocused && !isSystemRoot && bo.radiusScene / dist * projScale < .5;
20509
20726
  if (subPixel !== bo.meshSubPixel) {
20510
20727
  bo.meshSubPixel = subPixel;
20511
20728
  if (subPixel) bo.mesh.layers.disable(0);
@@ -23198,7 +23415,7 @@ var PromotionRegistry = class {
23198
23415
  this.applyGroupFilter(deps.ctx.earthSatFilter);
23199
23416
  deps.ctx.onSmallBodyFilterChange((f) => this.applySmallBodyFilter(f));
23200
23417
  this.applySmallBodyFilter(deps.ctx.smallBodyFilter);
23201
- fetchLabels$1().then((labels) => {
23418
+ fetchLabels().then((labels) => {
23202
23419
  const alreadyLoaded = [];
23203
23420
  const add = (id) => {
23204
23421
  if (!this.defaults.add(id)) return;
@@ -23264,14 +23481,14 @@ var PromotionRegistry = class {
23264
23481
  this.finalizeBuilds();
23265
23482
  }
23266
23483
  buildBodyInstance(body, halfPromoteIds, auto = false) {
23267
- const { bodyObjects, ctx, clock, scene, clickables, meshToBody, circleTexture, renderer } = this.deps;
23484
+ const { bodyObjects, ctx, clock, scene, clickables, meshToBody, renderer } = this.deps;
23268
23485
  if (bodyObjects.has(body.data.id)) return false;
23269
23486
  if (!body.orbitElements && !body.data.pageOnly && body.data.orbitalSource !== OrbitalSource.SPICE_PROBE) {
23270
23487
  body.orbitElements = body.data;
23271
23488
  body.orbitCenterId = body.data.parentId;
23272
23489
  }
23273
23490
  ctx.place(body, clock.jd);
23274
- buildMajorBodies([body], scene, clickables, meshToBody, bodyObjects, circleTexture, renderer.domElement, this.deps.onBodyClick, (id, hovered) => hovered ? this.deps.hoveredBodyIds.add(id) : this.deps.hoveredBodyIds.delete(id), halfPromoteIds);
23491
+ buildMajorBodies([body], scene, clickables, meshToBody, bodyObjects, renderer.domElement, this.deps.onBodyClick, (id, hovered) => hovered ? this.deps.hoveredBodyIds.add(id) : this.deps.hoveredBodyIds.delete(id), halfPromoteIds);
23275
23492
  const bo = bodyObjects.get(body.data.id);
23276
23493
  if (bo && !body.data.name) loadBodyLabel(bo);
23277
23494
  if (body.data.objectType === ObjectType.SPACECRAFT) ctx.bodies.dirtySpacecraftGroups.add(body.data.parentId);
@@ -23476,7 +23693,7 @@ var FocusController = class {
23476
23693
  _tmpV3 = new Vector3();
23477
23694
  pendingInitialView = null;
23478
23695
  upgradeTargetIds = /* @__PURE__ */ new Set();
23479
- constructor(deps, initialFocus, hoveredBodyIds, circleTexture, renderer) {
23696
+ constructor(deps, initialFocus, hoveredBodyIds, renderer) {
23480
23697
  this.deps = deps;
23481
23698
  this.focusedBody = initialFocus;
23482
23699
  this.promotion = new PromotionRegistry({
@@ -23485,7 +23702,6 @@ var FocusController = class {
23485
23702
  ctx: deps.ctx,
23486
23703
  clock: deps.clock,
23487
23704
  renderer,
23488
- circleTexture,
23489
23705
  clickables: deps.clickables,
23490
23706
  meshToBody: deps.meshToBody,
23491
23707
  hoveredBodyIds,
@@ -24346,7 +24562,7 @@ var SceneRenderer = class SceneRenderer {
24346
24562
  loadTexture: (b) => this.maybeLoadTexture(b),
24347
24563
  repositionAll: () => this.repositionAll(),
24348
24564
  assignMapLayerToTrails: () => this.assignMapLayerToTrails()
24349
- }, focusBody, this.hoveredBodyIds, this.circleTexture, this.renderer);
24565
+ }, focusBody, this.hoveredBodyIds, this.renderer);
24350
24566
  this.coverageWatch = new ProbeCoverageWatch(clock, callbacks.notices);
24351
24567
  const framing = focusBody?.data.id === initialView.id ? initialView : {
24352
24568
  latitude: 45,
@@ -24380,7 +24596,7 @@ var SceneRenderer = class SceneRenderer {
24380
24596
  this.tick();
24381
24597
  }
24382
24598
  buildScene() {
24383
- buildMajorBodies(this.ctx.bodies.majorBodies, this.scene, this.clickables, this.meshToBody, this.bodyObjects, this.circleTexture, this.renderer.domElement, (body) => this.focusController.handleFocus(body), (id, hovered) => {
24599
+ buildMajorBodies(this.ctx.bodies.majorBodies, this.scene, this.clickables, this.meshToBody, this.bodyObjects, this.renderer.domElement, (body) => this.focusController.handleFocus(body), (id, hovered) => {
24384
24600
  if (hovered) this.hoveredBodyIds.add(id);
24385
24601
  else this.hoveredBodyIds.delete(id);
24386
24602
  this.invalidate();
@@ -25227,7 +25443,7 @@ var SceneRenderer = class SceneRenderer {
25227
25443
  const systemId = this.ctx.visibility.focusedSystemId;
25228
25444
  const seatKey = this.focusedSeatConfigKey();
25229
25445
  const probeVersion = this.ctx.probeStore?.version ?? 0;
25230
- const dataVersion = this.ctx.bodies.dataVersion + (this.ctx.chebStore?.version ?? 0);
25446
+ const dataVersion = this.ctx.bodies.dataVersion + (this.ctx.chebStore?.version ?? 0) + (this.ctx.refresher?.wakes ?? 0);
25231
25447
  if (this.clock.jd === this.lastUpdatedJd && systemId === this.lastUpdatedSystemId && seatKey === this.lastSeatConfigKey && probeVersion === this.lastProbeVersion && dataVersion === this.lastDataVersion) {
25232
25448
  this.clock.seeked = false;
25233
25449
  return;
@@ -28473,6 +28689,7 @@ var SpaceMap = class {
28473
28689
  longitude: target.lon
28474
28690
  })
28475
28691
  });
28692
+ if (arrival.outcome === "failed") throw arrival.error;
28476
28693
  return this.settled(arrival.flightMs);
28477
28694
  }
28478
28695
  async goTo(id, options = {}) {
@@ -28505,11 +28722,24 @@ var SpaceMap = class {
28505
28722
  }
28506
28723
  }
28507
28724
  let renderer;
28725
+ let loadError;
28726
+ const note = (error) => {
28727
+ loadError ??= error;
28728
+ };
28729
+ const failed = () => {
28730
+ console.warn(`[map] goTo ${id}: its data did not load:`, loadError);
28731
+ return {
28732
+ outcome: "failed",
28733
+ error: loadError,
28734
+ clockMoved,
28735
+ flightMs: 0
28736
+ };
28737
+ };
28508
28738
  try {
28509
28739
  await this.open(id).catch(() => {});
28510
28740
  const date = jdToDate(this.clock.jd);
28511
- await this.ctx.ready(date);
28512
- await this.ctx.ensureBody(id, date);
28741
+ await this.ctx.ready(date).catch(note);
28742
+ await this.ctx.ensureBody(id, date).catch(note);
28513
28743
  renderer = await this.rendering();
28514
28744
  } finally {
28515
28745
  release();
@@ -28517,6 +28747,7 @@ var SpaceMap = class {
28517
28747
  if (superseded()) return gone;
28518
28748
  const body = this.ctx.getBody(id);
28519
28749
  if (!body || !renderer) {
28750
+ if (loadError !== void 0) return failed();
28520
28751
  console.warn(`[map] goTo ${id}: not an object of this map`);
28521
28752
  return {
28522
28753
  outcome: "missing",
@@ -28524,8 +28755,14 @@ var SpaceMap = class {
28524
28755
  flightMs: 0
28525
28756
  };
28526
28757
  }
28758
+ const placed = renderer.placeOf(body) !== null;
28759
+ if (!placed && loadError !== void 0) {
28760
+ windows ??= await coverage();
28761
+ if (superseded()) return gone;
28762
+ if (nearestCoveredJd(windows, this.clock.jd) === this.clock.jd) return failed();
28763
+ }
28527
28764
  options.commit?.(body);
28528
- if (renderer.placeOf(body)) {
28765
+ if (placed) {
28529
28766
  const view = options.view?.(body) ?? {};
28530
28767
  const distance = view.distance ?? (body.data.radiusKm > 0 ? this.framing({}, id) : distanceBefore);
28531
28768
  let flightMs = 0;
@@ -32629,7 +32866,6 @@ var SystemMap = class {
32629
32866
 
32630
32867
  //#endregion
32631
32868
  //#region src/lib/scene/state/attribution.ts
32632
- var DEFAULT_ORBIT_LABELS = Object.fromEntries(ORBIT_SOURCE_ORDER.map((s) => [s, ORBIT_SOURCES[s].label]));
32633
32869
  function feedsFocusedSystem(ctx, source) {
32634
32870
  const parents = ctx.credits.orbitSourceParents.get(source);
32635
32871
  if (!parents) return true;
@@ -32637,22 +32873,28 @@ function feedsFocusedSystem(ctx, source) {
32637
32873
  for (const parent of parents) if (ctx.bodies.isInSystem(parent, sysId)) return true;
32638
32874
  return false;
32639
32875
  }
32640
- function creditedOrbitSources(ctx) {
32641
- return ORBIT_SOURCE_ORDER.filter((s) => ctx.credits.orbitSources.has(s)).filter((s) => !ORBIT_SOURCES[s].scoped || feedsFocusedSystem(ctx, s)).map((source) => ({
32642
- source,
32643
- info: ORBIT_SOURCES[source]
32644
- }));
32876
+ function creditedOrbitArchives(ctx) {
32877
+ const credits = ctx.credits;
32878
+ const archives = /* @__PURE__ */ new Set();
32879
+ for (const source of ORBIT_SOURCE_ORDER) {
32880
+ if (!credits.orbitSources.has(source)) continue;
32881
+ const { archive, scoped } = ORBIT_SOURCES[source];
32882
+ if (scoped && !feedsFocusedSystem(ctx, source)) continue;
32883
+ if (archive) archives.add(archive);
32884
+ else for (const a of PROBE_ARCHIVES) if (a && credits.probeArchives.has(a)) archives.add(a);
32885
+ }
32886
+ return [...archives];
32645
32887
  }
32646
32888
  function scopedCredits(ctx, all) {
32647
32889
  const systemId = ctx.visibility.focusedSystemId;
32648
32890
  const bodyId = ctx.visibility.focusedBodyId;
32649
32891
  return [...all].filter((c) => c.bodyId === bodyId || systemId && c.systemId === systemId);
32650
32892
  }
32651
- function attributionChips(ctx, labels = DEFAULT_ORBIT_LABELS) {
32893
+ function attributionChips(ctx, labels = ORBIT_ARCHIVE_ORGANISATIONS) {
32652
32894
  const credits = ctx.credits;
32653
32895
  credits.imageryVersion;
32654
32896
  credits.modelVersion;
32655
- const orbits = [...new Set(creditedOrbitSources(ctx).map(({ source }) => labels[source]))];
32897
+ const orbits = [...new Set(creditedOrbitArchives(ctx).map((archive) => labels[archive]))];
32656
32898
  const organisations = /* @__PURE__ */ new Set();
32657
32899
  for (const credit of scopedCredits(ctx, credits.imagery.values())) organisations.add(credit.organisation);
32658
32900
  const bodyId = ctx.visibility.focusedBodyId;
@@ -32901,12 +33143,7 @@ async function open(date) {
32901
33143
  const params = chebyshevZoneParams(metadata);
32902
33144
  if (params.size > 0) chebyshev = new ChebyshevStore(params);
32903
33145
  }
32904
- try {
32905
- await chebyshev?.ensure(jd).done;
32906
- } catch (error) {
32907
- chebyshev = null;
32908
- throw error;
32909
- }
33146
+ await chebyshev?.load(jd);
32910
33147
  return {
32911
33148
  jd,
32912
33149
  metadata,