spacemap 0.1.11 → 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,10 +3216,31 @@ 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
  }
3227
3235
 
3236
+ //#endregion
3237
+ //#region src/lib/credits/tile-credit.ts
3238
+ function tileCredit(map) {
3239
+ const tiles = map.tiles;
3240
+ if (!tiles || tilesBase() === "" || !textureAllowed(tiles.distribution)) return void 0;
3241
+ return tiles.source === map.source && tiles.attribution === map.attribution ? void 0 : tiles;
3242
+ }
3243
+
3228
3244
  //#endregion
3229
3245
  //#region src/lib/scene/state/credits.svelte.ts
3230
3246
  var CreditsStore = class {
@@ -3273,9 +3289,24 @@ var CreditsStore = class {
3273
3289
  set orbitSourceParents(value) {
3274
3290
  set(this.#orbitSourceParents, value, true);
3275
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
+ }
3276
3299
  registerImagery(layer, bodyId, systemId, meta) {
3277
- const sep = "\0";
3278
- const key = layer === "rings" ? `${layer}${sep}${bodyId}${sep}${meta.source}` : `${layer}${sep}${bodyId}`;
3300
+ this.addImagery(layer, bodyId, systemId, meta, layer === "rings");
3301
+ const tiles = layer === "surface" ? tileCredit(meta) : void 0;
3302
+ if (tiles) this.addImagery(layer, bodyId, systemId, tiles, true);
3303
+ }
3304
+ addImagery(layer, bodyId, systemId, meta, bySource) {
3305
+ const key = [
3306
+ layer,
3307
+ bodyId,
3308
+ ...bySource ? [meta.source] : []
3309
+ ].join("\0");
3279
3310
  if (this.imagery.has(key)) return;
3280
3311
  const { source, organisation, license, attribution, description } = meta;
3281
3312
  this.imagery.set(key, {
@@ -3306,6 +3337,7 @@ var CreditsStore = class {
3306
3337
  recordOrbitSources(bodies) {
3307
3338
  let added = false;
3308
3339
  let scoped = false;
3340
+ let archived = false;
3309
3341
  for (const b of bodies) {
3310
3342
  const src = b.data.orbitalSource;
3311
3343
  if (src === OrbitalSource.UNKNOWN) continue;
@@ -3313,6 +3345,11 @@ var CreditsStore = class {
3313
3345
  this.orbitSources.add(src);
3314
3346
  added = true;
3315
3347
  }
3348
+ const archive = b.data.orbitArchive;
3349
+ if (archive && !this.probeArchives.has(archive)) {
3350
+ this.probeArchives.add(archive);
3351
+ archived = true;
3352
+ }
3316
3353
  if (!orbitSourceInfo(src)?.scoped) continue;
3317
3354
  let parents = this.orbitSourceParents.get(src);
3318
3355
  if (!parents) {
@@ -3325,6 +3362,7 @@ var CreditsStore = class {
3325
3362
  }
3326
3363
  if (added) this.orbitSources = new Set(this.orbitSources);
3327
3364
  if (scoped) this.orbitSourceParents = new Map(this.orbitSourceParents);
3365
+ if (archived) this.probeArchives = new Set(this.probeArchives);
3328
3366
  }
3329
3367
  recordOrbitSource(src) {
3330
3368
  if (src === OrbitalSource.UNKNOWN || this.orbitSources.has(src)) return;
@@ -5817,6 +5855,10 @@ var TYPE_COLOR_PROBE = "#d5cfe7";
5817
5855
  var SUN_ID = "naif-10";
5818
5856
  var EARTH_ID = "naif-399";
5819
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
+ }
5820
5862
  var MINOR_PROMOTED_IDS = new Set([
5821
5863
  "naif-0",
5822
5864
  "naif-9",
@@ -6192,10 +6234,6 @@ var MinorBucket = class {
6192
6234
  function isTopLevelParent(parentId) {
6193
6235
  return parentId === "naif-0" || parentId === "naif-10";
6194
6236
  }
6195
- function dominantPlanetId(id) {
6196
- const m = /^naif-([1-9])$/.exec(id);
6197
- return m ? `naif-${m[1]}99` : null;
6198
- }
6199
6237
  function barycenterPrimaryId(id) {
6200
6238
  if (id === "naif-0") return SUN_ID;
6201
6239
  return dominantPlanetId(id);
@@ -6491,38 +6529,76 @@ var loadProgress = new LoadProgress();
6491
6529
  //#endregion
6492
6530
  //#region src/lib/fetch/fetch-timeout.ts
6493
6531
  var BOOT_FETCH_TIMEOUT_MS = 3e4;
6494
- function countBootBytes(res, background) {
6495
- if (background || !loadProgress.active || !res.body) return res;
6532
+ function bootByteCounter(res, background) {
6533
+ if (background || !loadProgress.active) return null;
6496
6534
  const total = Number(res.headers.get("content-length"));
6497
- if (!Number.isFinite(total) || total <= 0) return res;
6535
+ if (!Number.isFinite(total) || total <= 0) return null;
6498
6536
  loadProgress.announce(total);
6499
- const counter = new TransformStream({ transform(chunk, controller) {
6500
- loadProgress.addBytes(chunk.byteLength);
6501
- controller.enqueue(chunk);
6502
- } });
6503
- return new Response(res.body.pipeThrough(counter), {
6504
- status: res.status,
6505
- statusText: res.statusText,
6506
- headers: res.headers
6507
- });
6537
+ return (bytes) => loadProgress.addBytes(bytes);
6508
6538
  }
6509
6539
  async function fetchWithTimeout(input, init = {}, timeoutMs = BOOT_FETCH_TIMEOUT_MS) {
6510
6540
  const controller = new AbortController();
6511
- 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
+ };
6512
6550
  const onCallerAbort = () => controller.abort();
6513
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;
6514
6559
  try {
6515
- return countBootBytes(await fetch(input, {
6560
+ res = await fetch(input, {
6516
6561
  ...init,
6517
6562
  signal: controller.signal
6518
- }), init.priority === "low");
6563
+ });
6519
6564
  } catch (e) {
6520
- if (controller.signal.aborted && !init.signal?.aborted) throw new Error(`Request timed out after ${timeoutMs} ms: ${input}`, { cause: e });
6521
- throw e;
6522
- } finally {
6523
- clearTimeout(timer);
6524
- init.signal?.removeEventListener("abort", onCallerAbort);
6525
- }
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
+ });
6526
6602
  }
6527
6603
 
6528
6604
  //#endregion
@@ -7131,7 +7207,7 @@ function parseLabels(text) {
7131
7207
  return out;
7132
7208
  }
7133
7209
  var labelsByLang = /* @__PURE__ */ new Map();
7134
- async function fetchLabels$1(lang = getLocale()) {
7210
+ async function fetchLabels(lang = getLocale()) {
7135
7211
  let p = labelsByLang.get(lang);
7136
7212
  if (!p) {
7137
7213
  p = (async () => {
@@ -7356,6 +7432,7 @@ function parseProbesPayload(buffer, startJd, endJd, float64Coeffs) {
7356
7432
  const fitCenterIdValue = view.getInt32(offset + 12, true);
7357
7433
  const fitCenterIdType = view.getUint8(offset + 16);
7358
7434
  const nSystemIntervals = view.getUint8(offset + 17);
7435
+ const archive = PROBE_ARCHIVES[view.getUint8(offset + 18)];
7359
7436
  offset += 20;
7360
7437
  const id = buildObjectId(idTypeOrdinal, objIdValue) ?? "";
7361
7438
  if (!id) console.warn(`probes payload: probe[${p}] (value=${objIdValue}) has unknown id-type ${idTypeOrdinal}; skipping`);
@@ -7408,6 +7485,7 @@ function parseProbesPayload(buffer, startJd, endJd, float64Coeffs) {
7408
7485
  probeId: objIdValue,
7409
7486
  hasLocalized,
7410
7487
  objectType,
7488
+ archive,
7411
7489
  fitCenter,
7412
7490
  subStartEt,
7413
7491
  subEndEt,
@@ -8281,7 +8359,7 @@ async function fetchElements(zone, zoom, part, time, priority) {
8281
8359
  }
8282
8360
  var ChunkLoader = class {
8283
8361
  static prefetch(zone, zoom, part, time = null) {
8284
- fetch(elementsUrl(zone, zoom, part, time));
8362
+ fetch(elementsUrl(zone, zoom, part, time)).catch(() => {});
8285
8363
  }
8286
8364
  barycenters = /* @__PURE__ */ new Map();
8287
8365
  probeBuffers = /* @__PURE__ */ new Map();
@@ -8420,7 +8498,8 @@ var ChunkLoader = class {
8420
8498
  equatorial: false,
8421
8499
  validityStart: startJd,
8422
8500
  validityEnd: endJd,
8423
- orbitalSource: OrbitalSource.SPICE_PROBE
8501
+ orbitalSource: OrbitalSource.SPICE_PROBE,
8502
+ orbitArchive: probe.archive
8424
8503
  };
8425
8504
  const elements = probeOsculatingElements(probe, jd, primaryMu);
8426
8505
  const ownId = id;
@@ -8468,7 +8547,7 @@ var ChunkLoader = class {
8468
8547
  const firstPass = this.barycenters.size === 0;
8469
8548
  const bodies = [];
8470
8549
  let degenerate = 0;
8471
- 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()]);
8472
8551
  const isParabolic = cols.kind === "parabolic";
8473
8552
  const isSGP4 = cols.kind === "sgp4";
8474
8553
  let sliceStart = performance.now();
@@ -8910,6 +8989,41 @@ async function fetchChebyshev(zone, zoom, chunk, priority) {
8910
8989
  };
8911
8990
  }
8912
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
+
8913
9027
  //#endregion
8914
9028
  //#region src/lib/fetch/position/chunk-window.ts
8915
9029
  var NEIGHBOR_WINDOW = 1;
@@ -8920,6 +9034,11 @@ function chunkKey(zone, chunkIdx) {
8920
9034
  var ChunkWindow = class {
8921
9035
  chunks = /* @__PURE__ */ new Map();
8922
9036
  inflight = /* @__PURE__ */ new Map();
9037
+ retries = new RetryBackoff(() => {
9038
+ this.retryDue = true;
9039
+ this._version++;
9040
+ });
9041
+ retryDue = false;
8923
9042
  lastEnsuredJd = NaN;
8924
9043
  _version = 0;
8925
9044
  constructor(zoneParams) {
@@ -8932,19 +9051,21 @@ var ChunkWindow = class {
8932
9051
  return Array.from(this.zoneParams.keys());
8933
9052
  }
8934
9053
  ensure(jd) {
8935
- if (jd === this.lastEnsuredJd) return {
8936
- ready: this.allCurrentChunksLoaded(jd),
8937
- done: this.pendingCurrent(jd)
8938
- };
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) {
8939
9061
  this.lastEnsuredJd = jd;
9062
+ this.retryDue = false;
8940
9063
  const jobs = [];
8941
9064
  const neighbors = [];
8942
- let ready = true;
8943
9065
  for (const [zone, params] of this.zoneParams) {
8944
9066
  const center = chunkIndexForJd(params, jd);
8945
- if (this.isLoadable(params, center) && !this.isResident(zone, center)) {
8946
- ready = false;
8947
- const job = this.loadChunk(zone, params, center, "high");
9067
+ if (this.isLoadable(params, center)) {
9068
+ const job = this.loadChunk(zone, params, center, "high", now);
8948
9069
  if (job) jobs.push(job);
8949
9070
  }
8950
9071
  for (let d = -NEIGHBOR_WINDOW; d <= NEIGHBOR_WINDOW; d++) {
@@ -8962,10 +9083,7 @@ var ChunkWindow = class {
8962
9083
  done.catch(() => {}).then(() => {
8963
9084
  for (const [zone, params, idx] of neighbors) this.loadChunk(zone, params, idx, "low")?.catch(() => {});
8964
9085
  });
8965
- return {
8966
- ready,
8967
- done
8968
- };
9086
+ return done;
8969
9087
  }
8970
9088
  request(jd) {
8971
9089
  for (const [zone, params] of this.zoneParams) {
@@ -8981,23 +9099,6 @@ var ChunkWindow = class {
8981
9099
  }
8982
9100
  return false;
8983
9101
  }
8984
- pendingCurrent(jd) {
8985
- if (this.inflight.size === 0) return RESOLVED;
8986
- let pending = null;
8987
- for (const [zone, params] of this.zoneParams) {
8988
- const job = this.inflight.get(chunkKey(zone, chunkIndexForJd(params, jd)));
8989
- if (job) (pending ??= []).push(job);
8990
- }
8991
- return pending ? Promise.all(pending).then(() => void 0) : RESOLVED;
8992
- }
8993
- allCurrentChunksLoaded(jd) {
8994
- for (const [zone, params] of this.zoneParams) {
8995
- const center = chunkIndexForJd(params, jd);
8996
- if (!this.isLoadable(params, center)) continue;
8997
- if (!this.isResident(zone, center)) return false;
8998
- }
8999
- return true;
9000
- }
9001
9102
  isResident(zone, chunkIdx) {
9002
9103
  return this.chunks.get(zone)?.has(chunkIdx) ?? false;
9003
9104
  }
@@ -9006,14 +9107,22 @@ var ChunkWindow = class {
9006
9107
  if (!zoneMap) return;
9007
9108
  for (const idx of zoneMap.keys()) if (idx < center - NEIGHBOR_WINDOW || idx > center + NEIGHBOR_WINDOW) zoneMap.delete(idx);
9008
9109
  }
9009
- loadChunk(zone, params, chunkIdx, priority) {
9110
+ loadChunk(zone, params, chunkIdx, priority, now = false) {
9010
9111
  if (this.isResident(zone, chunkIdx)) return null;
9011
9112
  const key = chunkKey(zone, chunkIdx);
9012
9113
  const existing = this.inflight.get(key);
9013
9114
  if (existing) return existing;
9014
- 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) {
9015
9119
  this.inflight.set(key, job);
9016
- 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));
9017
9126
  return job;
9018
9127
  }
9019
9128
  async fetchAndStore(zone, params, chunkIdx, priority) {
@@ -9144,15 +9253,7 @@ var ProbeStore = class extends ChunkWindow {
9144
9253
  if (this.isResident(zone, idx)) continue;
9145
9254
  const key = chunkKey(zone, idx);
9146
9255
  if (this.warmed.has(key)) continue;
9147
- const existing = this.inflight.get(key);
9148
- if (existing) {
9149
- jobs.push(existing);
9150
- continue;
9151
- }
9152
- const job = this.fetchAndWarm(key, zone, idx, params);
9153
- this.inflight.set(key, job);
9154
- job.finally(() => this.inflight.delete(key));
9155
- jobs.push(job);
9256
+ jobs.push(this.inflight.get(key) ?? this.track(key, this.fetchAndWarm(key, zone, idx, params)));
9156
9257
  }
9157
9258
  await Promise.all(jobs);
9158
9259
  }
@@ -9374,9 +9475,11 @@ function rowsFor(chunk, id) {
9374
9475
 
9375
9476
  //#endregion
9376
9477
  //#region src/lib/fetch/gz.ts
9478
+ function gunzip(res) {
9479
+ return new Response(res.body.pipeThrough(new DecompressionStream("gzip")));
9480
+ }
9377
9481
  async function gunzipJson(res) {
9378
- const ds = new DecompressionStream("gzip");
9379
- return await new Response(res.body.pipeThrough(ds)).json();
9482
+ return await gunzip(res).json();
9380
9483
  }
9381
9484
  function memoizedGzJson(url, opts = {}) {
9382
9485
  const { onMissing, fetcher = (u) => fetch(u), error = (res, u) => `memoizedGzJson: ${u} returned ${res.status} ${res.statusText}` } = opts;
@@ -9638,13 +9741,7 @@ async function createPlaceholderBody(targetId, ctx, visited = /* @__PURE__ */ ne
9638
9741
  return [];
9639
9742
  }
9640
9743
  visited.add(targetId);
9641
- let detail;
9642
- try {
9643
- detail = await fetchObjectDetail(targetId);
9644
- } catch {
9645
- console.warn(`createPlaceholderBody: failed to fetch global data for ${targetId} — hiding`);
9646
- return [];
9647
- }
9744
+ const detail = await fetchObjectDetail(targetId);
9648
9745
  const global = detail.global;
9649
9746
  if (!global) {
9650
9747
  console.warn(`createPlaceholderBody: no catalogue record for ${targetId} — hiding`);
@@ -9694,7 +9791,7 @@ async function ensureTargetStreamed(ctx, targetId) {
9694
9791
  if (ctx.getBody(targetId)) return;
9695
9792
  const placeholders = await createPlaceholderBody(targetId, ctx);
9696
9793
  if (placeholders.length === 0) return;
9697
- const added = routePlaceholders(ctx, placeholders, await fetchLabels$1());
9794
+ const added = routePlaceholders(ctx, placeholders, await fetchLabels());
9698
9795
  ctx.bodies.flushMinor(added > 0);
9699
9796
  }
9700
9797
 
@@ -9755,11 +9852,17 @@ function setSceneSettings(settings) {
9755
9852
  //#region src/lib/scene/zone-refresher.ts
9756
9853
  var MIN_LOAD_INTERVAL_MS = 2e3;
9757
9854
  var STALE_FAILURE_STREAK = 3;
9855
+ function loadKey(z, target) {
9856
+ return `${z.zone}:${z.zoom ?? ""}:${target}`;
9857
+ }
9758
9858
  var ZoneRefresher = class {
9759
9859
  zones = [];
9760
9860
  latestDate;
9761
9861
  failureStreak = 0;
9762
9862
  staleNotified = false;
9863
+ retries = new RetryBackoff(() => this.wakes++);
9864
+ wakes = 0;
9865
+ wakeTimer;
9763
9866
  constructor(ctx, metadata, loader, initialDate) {
9764
9867
  this.ctx = ctx;
9765
9868
  this.loader = loader;
@@ -9805,16 +9908,30 @@ var ZoneRefresher = class {
9805
9908
  }
9806
9909
  }
9807
9910
  }
9808
- recordSuccess() {
9911
+ recordSuccess(key) {
9912
+ this.retries.clear(key);
9809
9913
  this.failureStreak = 0;
9810
9914
  }
9811
- recordFailure() {
9915
+ recordFailure(key) {
9916
+ this.retries.fail(key);
9812
9917
  this.failureStreak++;
9813
9918
  if (this.failureStreak >= STALE_FAILURE_STREAK && !this.staleNotified) {
9814
9919
  this.staleNotified = true;
9815
9920
  this.ctx.onDataStale?.();
9816
9921
  }
9817
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
+ }
9818
9935
  invalidateZone(zoneName) {
9819
9936
  for (const z of this.zones) {
9820
9937
  if (z.zone !== zoneName || z.kind !== "time") continue;
@@ -9835,12 +9952,15 @@ var ZoneRefresher = class {
9835
9952
  if (state.inFlight) continue;
9836
9953
  const target = snapshotDate(state.zoomData, date);
9837
9954
  if (target === state.currentTime) continue;
9955
+ if (this.retries.waiting(loadKey(state, target))) continue;
9838
9956
  const nowMs = performance.now();
9839
- 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
+ }
9840
9962
  state.lastLoadStartMs = nowMs;
9841
- state.inFlight = this.loadTime(state, target, date).finally(() => {
9842
- state.inFlight = null;
9843
- });
9963
+ this.start(state, this.loadTime(state, target, date));
9844
9964
  } else {
9845
9965
  while (true) {
9846
9966
  const target = chunkIndexForJd(state.zoomData, jd);
@@ -9855,18 +9975,18 @@ var ZoneRefresher = class {
9855
9975
  if (state.inFlight) continue;
9856
9976
  const target = chunkIndexForJd(state.zoomData, jd);
9857
9977
  if (target === state.currentIdx) continue;
9858
- state.inFlight = this.loadChunk(state, target).finally(() => {
9859
- state.inFlight = null;
9860
- });
9978
+ if (this.retries.waiting(loadKey(state, target))) continue;
9979
+ this.start(state, this.loadChunk(state, target));
9861
9980
  }
9862
9981
  }
9863
9982
  async ready(date = this.latestDate) {
9864
9983
  this.latestDate = date;
9865
9984
  const jd = dateToJD(date);
9866
9985
  const waits = this.zones.map((z) => this.zoneReady(z, date, jd));
9867
- if (this.ctx.chebStore) waits.push(this.ctx.chebStore.ensure(jd).done);
9868
- if (this.ctx.probeStore) waits.push(this.ctx.probeStore.ensure(jd).done);
9869
- 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;
9870
9990
  }
9871
9991
  async zoneReady(z, date, jd) {
9872
9992
  while (z.inFlight) await z.inFlight;
@@ -9874,17 +9994,12 @@ var ZoneRefresher = class {
9874
9994
  const target = snapshotDate(z.zoomData, date);
9875
9995
  if (target === z.currentTime) return;
9876
9996
  z.lastLoadStartMs = performance.now();
9877
- z.inFlight = this.loadTime(z, target, date).finally(() => {
9878
- z.inFlight = null;
9879
- });
9997
+ await this.start(z, this.loadTime(z, target, date));
9880
9998
  } else {
9881
9999
  const target = chunkIndexForJd(z.zoomData, jd);
9882
10000
  if (target === z.currentIdx) return;
9883
- z.inFlight = this.loadChunk(z, target).finally(() => {
9884
- z.inFlight = null;
9885
- });
10001
+ await this.start(z, this.loadChunk(z, target));
9886
10002
  }
9887
- await z.inFlight;
9888
10003
  }
9889
10004
  wanted = /* @__PURE__ */ new Set();
9890
10005
  want(id) {
@@ -9897,7 +10012,7 @@ var ZoneRefresher = class {
9897
10012
  this.want(targetId);
9898
10013
  if (this.ctx.earthSatFilter || this.ctx.earthTypeFilter) {
9899
10014
  this.invalidateZone("earth");
9900
- await this.ready(date);
10015
+ await this.ready(date).catch(() => {});
9901
10016
  if (this.ctx.getBody(targetId)) return;
9902
10017
  }
9903
10018
  }
@@ -9908,13 +10023,13 @@ var ZoneRefresher = class {
9908
10023
  if (!store) return;
9909
10024
  const jd = dateToJD(date);
9910
10025
  const passengerPromise = passengerFor(targetId);
9911
- await store.ensure(jd).done;
10026
+ await store.load(jd).catch(() => {});
9912
10027
  const passenger = await passengerPromise;
9913
10028
  if (passenger) store.registerCarried(passenger);
9914
10029
  const fcId = store.stampedFitCenterAt(targetId, jd);
9915
10030
  if (fcId) await ensureTargetStreamed(this.ctx, fcId);
9916
10031
  if (this.ctx.getBody(targetId)) return;
9917
- const labels = await fetchLabels$1();
10032
+ const labels = await fetchLabels();
9918
10033
  const target = this.loader.processProbes(store, date, labels).find((b) => b.data.id === targetId);
9919
10034
  if (!target) return;
9920
10035
  this.ctx.bodies.addBodies([target]);
@@ -9975,10 +10090,11 @@ var ZoneRefresher = class {
9975
10090
  if (z.zone === "earth") this.ctx.notifyEarthSatRollover();
9976
10091
  console.log(`zone-refresher: ${z.zone}@${fromTime} → ${time} (+${added} ~${updated} -${removed})`);
9977
10092
  this.prefetchTimeNeighbors(z, date);
9978
- this.recordSuccess();
10093
+ this.recordSuccess(loadKey(z, time));
9979
10094
  } catch (e) {
9980
10095
  console.warn(`zone-refresher: failed to refresh ${z.zone}@${time}:`, e);
9981
- this.recordFailure();
10096
+ this.recordFailure(loadKey(z, time));
10097
+ throw e;
9982
10098
  }
9983
10099
  }
9984
10100
  async loadChunk(z, target) {
@@ -9991,11 +10107,12 @@ var ZoneRefresher = class {
9991
10107
  else chunks = await Promise.all(Array.from({ length: z.parts }, (_, part) => this.loader.process(z.zone, z.zoom, part, String(target), z.parentIdType)));
9992
10108
  this.applyChunkSwap(z, previous, target, chunks);
9993
10109
  this.preloadChunkNeighbors(z);
9994
- this.recordSuccess();
10110
+ this.recordSuccess(loadKey(z, target));
9995
10111
  } catch (e) {
9996
10112
  console.warn(`zone-refresher: ${z.zone} chunk reload failed (${previous} → ${target}):`, e);
9997
10113
  z.currentIdx = previous;
9998
- this.recordFailure();
10114
+ this.recordFailure(loadKey(z, target));
10115
+ throw e;
9999
10116
  }
10000
10117
  }
10001
10118
  applyChunkSwap(z, previous, target, chunks) {
@@ -10344,18 +10461,18 @@ async function buildProbeStore(metadata, jd, layers) {
10344
10461
  const missingCenter = Array.from(params).filter(([, p]) => p.fit_center_naif_id === void 0);
10345
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(", ")}`);
10346
10463
  const store = new ProbeStore(params);
10347
- await store.ensure(jd).done;
10464
+ await store.load(jd);
10348
10465
  return store;
10349
10466
  }
10350
10467
  async function loadMajorBodies(ctx, loader, metadata, date, jd, passenger) {
10351
10468
  const major = [];
10352
10469
  let cachedLabels = null;
10353
10470
  if (ctx.chebStore) {
10354
- cachedLabels = await fetchLabels$1();
10471
+ cachedLabels = await fetchLabels();
10355
10472
  major.push(...loader.processChebyshev(date, cachedLabels));
10356
10473
  }
10357
10474
  if (ctx.probeStore) {
10358
- cachedLabels ??= await fetchLabels$1();
10475
+ cachedLabels ??= await fetchLabels();
10359
10476
  if (passenger) ctx.probeStore.registerCarried(passenger);
10360
10477
  major.push(...loader.processProbes(ctx.probeStore, date, cachedLabels));
10361
10478
  }
@@ -10388,7 +10505,7 @@ async function loadScene(ctx, date, targetId) {
10388
10505
  const atmospheresPromise = loadAtmospheres().catch((e) => console.warn("scene-load: atmospheres failed to load — rendering without shells:", e));
10389
10506
  const jd = dateToJD(date);
10390
10507
  const metadataPromise = fetchMetadata();
10391
- fetchLabels$1().catch(() => {});
10508
+ fetchLabels().catch(() => {});
10392
10509
  metadataPromise.then((metadata) => {
10393
10510
  const moons = metadata.position.zones.moons;
10394
10511
  if (!moons || ctx.layers.skipsZone("moons")) return;
@@ -10404,7 +10521,7 @@ async function loadScene(ctx, date, targetId) {
10404
10521
  for (const zone of params.keys()) if (ctx.layers.skipsZone(zone)) params.delete(zone);
10405
10522
  if (params.size === 0) return null;
10406
10523
  const store = new ChebyshevStore(params);
10407
- await store.ensure(jd).done;
10524
+ await store.load(jd);
10408
10525
  return store;
10409
10526
  });
10410
10527
  const probePromise = metadataPromise.then((metadata) => buildProbeStore(metadata, jd, ctx.layers));
@@ -10422,7 +10539,7 @@ async function loadScene(ctx, date, targetId) {
10422
10539
  await atmospheresPromise;
10423
10540
  ctx.bodies.addBodies(major);
10424
10541
  ctx.credits.recordOrbitSources(major);
10425
- const labels = await fetchLabels$1();
10542
+ const labels = await fetchLabels();
10426
10543
  loadProgress.reach("labels");
10427
10544
  let addedSinceFlush = false;
10428
10545
  const noteAdded = () => {
@@ -10473,8 +10590,8 @@ async function loadScene(ctx, date, targetId) {
10473
10590
  console.warn(`scene-load: ${zone} part ${part} failed (skipped):`, e);
10474
10591
  };
10475
10592
  try {
10476
- 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(() => {
10477
- 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);
10478
10595
  })]);
10479
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))));
10480
10597
  } finally {
@@ -10505,6 +10622,7 @@ var fetchEarthMembership = memoizedGzJson(async () => {
10505
10622
  await fetchMetadata();
10506
10623
  return versionedUrl("/v1/membership/earth.json.gz", "membership");
10507
10624
  }, {
10625
+ fetcher: fetchWithTimeout,
10508
10626
  onMissing: () => ({}),
10509
10627
  error: (res) => `fetchEarthMembership: ${res.status} ${res.statusText}`
10510
10628
  });
@@ -10557,6 +10675,7 @@ var ContextManager = class {
10557
10675
  smallBodyFilter = null;
10558
10676
  missionMemberIds = null;
10559
10677
  currentGroupSlug = null;
10678
+ groupFilterJob = Promise.resolve();
10560
10679
  groupFilterListeners = /* @__PURE__ */ new Set();
10561
10680
  smallBodyFilterListeners = /* @__PURE__ */ new Set();
10562
10681
  earthSatRolloverListeners = /* @__PURE__ */ new Set();
@@ -10597,12 +10716,15 @@ var ContextManager = class {
10597
10716
  try {
10598
10717
  await loadScene(this, date, targetId);
10599
10718
  } catch (e) {
10600
- this.loading = false;
10601
- this.error = e instanceof Error ? e.message : String(e);
10602
- console.error("[scene-load]", e);
10719
+ this.fail(e);
10603
10720
  throw e;
10604
10721
  }
10605
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
+ }
10606
10728
  refreshTick(date) {
10607
10729
  this.refresher?.tick(date);
10608
10730
  }
@@ -10612,15 +10734,23 @@ var ContextManager = class {
10612
10734
  async ensureBody(targetId, date) {
10613
10735
  await this.refresher?.ensureBody(targetId, date);
10614
10736
  }
10615
- async applyGroupFilter(slug) {
10616
- if (slug === this.currentGroupSlug) return;
10737
+ applyGroupFilter(slug) {
10738
+ if (slug === this.currentGroupSlug) return this.groupFilterJob;
10617
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) {
10618
10748
  if (slug?.startsWith("mission-")) {
10619
10749
  const detail = await fetchGroupDetail(slug);
10620
10750
  if (slug !== this.currentGroupSlug) return;
10621
10751
  this.missionMemberIds = new Set((detail.global?.notable_members ?? []).map((mm) => mm.id).filter((id) => !!id));
10622
10752
  } else this.missionMemberIds = null;
10623
- const entry = slug ? await this.resolveIndexEntry(slug) : null;
10753
+ const entry = slug ? (await fetchGroupIndex())[slug] ?? null : null;
10624
10754
  if (slug !== this.currentGroupSlug) return;
10625
10755
  const nextSmallBody = this.resolveSmallBodyFilter(slug, entry?.applies_to, entry?.n);
10626
10756
  const nextEarthSlug = entry?.applies_to === "earth_sat" ? slug : null;
@@ -10669,13 +10799,6 @@ var ContextManager = class {
10669
10799
  }
10670
10800
  return null;
10671
10801
  }
10672
- async resolveIndexEntry(slug) {
10673
- try {
10674
- return (await fetchGroupIndex())[slug] ?? null;
10675
- } catch {
10676
- return null;
10677
- }
10678
- }
10679
10802
  };
10680
10803
 
10681
10804
  //#endregion
@@ -11291,6 +11414,51 @@ function makeEnvMap(renderer) {
11291
11414
  return tex;
11292
11415
  }
11293
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
+
11294
11462
  //#endregion
11295
11463
  //#region src/lib/scene/setup/three-boot.ts
11296
11464
  var CAMERA_FAR_DEFAULT = 1e5;
@@ -11357,6 +11525,7 @@ function bootThree(canvas, labelContainer, ctx) {
11357
11525
  composer.setSize(canvas.clientWidth, canvas.clientHeight);
11358
11526
  composer.addPass(new RenderPass(scene, camera));
11359
11527
  const bloomPass = new UnrealBloomPass(new Vector2(canvas.clientWidth * tier.bloomScale, canvas.clientHeight * tier.bloomScale), .3, .5, 1);
11528
+ installBloomHighPass(bloomPass);
11360
11529
  composer.addPass(bloomPass);
11361
11530
  composer.addPass(new OutputPass());
11362
11531
  return {
@@ -11431,7 +11600,7 @@ var AttitudeTrack = class {
11431
11600
  files;
11432
11601
  chunks = /* @__PURE__ */ new Map();
11433
11602
  inflight = /* @__PURE__ */ new Set();
11434
- failed = /* @__PURE__ */ new Set();
11603
+ retries = new RetryBackoff();
11435
11604
  constructor(probeId, manifest) {
11436
11605
  this.probeId = probeId;
11437
11606
  this.startJd = manifest.files.length ? manifest.files[0].start_jd : 0;
@@ -11511,7 +11680,7 @@ var AttitudeTrack = class {
11511
11680
  if (i < 0 || i >= this.files.length) return void 0;
11512
11681
  const have = this.chunks.get(i);
11513
11682
  if (have) return have;
11514
- 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;
11515
11684
  this.inflight.add(i);
11516
11685
  this.load(i);
11517
11686
  }
@@ -11523,8 +11692,9 @@ var AttitudeTrack = class {
11523
11692
  const ds = new DecompressionStream("gzip");
11524
11693
  const buf = await new Response(res.body.pipeThrough(ds)).arrayBuffer();
11525
11694
  this.chunks.set(i, parseAttitudeChunk(buf));
11695
+ this.retries.clear(i);
11526
11696
  } catch (e) {
11527
- this.failed.add(i);
11697
+ this.retries.fail(i);
11528
11698
  console.warn(`attitude: chunk ${this.probeId}/${file.name} failed to load:`, e);
11529
11699
  } finally {
11530
11700
  this.inflight.delete(i);
@@ -11959,6 +12129,23 @@ function cullOccludersFor(dst, center, radius, sunDir) {
11959
12129
  }
11960
12130
  return n;
11961
12131
  }
12132
+ function sunInView(aSun, aOc, sep) {
12133
+ if (aOc > 10 * aSun) {
12134
+ const t = (aOc - sep) / aSun;
12135
+ if (t >= 1) return 0;
12136
+ if (t <= -1) return 1;
12137
+ return 1 - (Math.acos(-t) + t * Math.sqrt(Math.max(1 - t * t, 0))) / Math.PI;
12138
+ }
12139
+ if (sep >= aSun + aOc) return 1;
12140
+ if (sep + aSun <= aOc) return 0;
12141
+ if (sep + aOc <= aSun) return 1 - aOc * aOc / (aSun * aSun);
12142
+ const a = aSun;
12143
+ const b = aOc;
12144
+ const c = sep;
12145
+ const x = (c * c + a * a - b * b) / (2 * c);
12146
+ const y = Math.sqrt(Math.max(a * a - x * x, 0));
12147
+ return 1 - (a * a * Math.acos(Math.max(-1, Math.min(1, x / a))) + b * b * Math.acos(Math.max(-1, Math.min(1, (c - x) / b))) - c * y) / (Math.PI * a * a);
12148
+ }
11962
12149
  function evaluateEclipseFactor(receiverPos, selfPos) {
11963
12150
  const aSun = SHARED.uSunAngularRadius.value;
11964
12151
  if (aSun <= 0) return 1;
@@ -11981,28 +12168,8 @@ function evaluateEclipseFactor(receiverPos, selfPos) {
11981
12168
  if (dOc < 1e-5) continue;
11982
12169
  const aOc = Math.asin(Math.min(r / dOc, 1));
11983
12170
  const cosSep = Math.max(-1, Math.min(1, (sunDir.x * tx + sunDir.y * ty + sunDir.z * tz) / dOc));
11984
- const sep = Math.acos(cosSep);
11985
- if (aOc > 10 * aSun) {
11986
- const t = (aOc - sep) / aSun;
11987
- if (t >= 1) return 0;
11988
- if (t <= -1) continue;
11989
- const covered = (Math.acos(-t) + t * Math.sqrt(Math.max(1 - t * t, 0))) / Math.PI;
11990
- result *= 1 - covered;
11991
- continue;
11992
- }
11993
- if (sep >= aSun + aOc) continue;
11994
- if (sep + aSun <= aOc) return 0;
11995
- if (sep + aOc <= aSun) {
11996
- result *= 1 - aOc * aOc / (aSun * aSun);
11997
- continue;
11998
- }
11999
- const a = aSun;
12000
- const b = aOc;
12001
- const c = sep;
12002
- const x = (c * c + a * a - b * b) / (2 * c);
12003
- const y = Math.sqrt(Math.max(a * a - x * x, 0));
12004
- const A = a * a * Math.acos(Math.max(-1, Math.min(1, x / a))) + b * b * Math.acos(Math.max(-1, Math.min(1, (c - x) / b))) - c * y;
12005
- result *= 1 - A / (Math.PI * a * a);
12171
+ result *= sunInView(aSun, aOc, Math.acos(cosSep));
12172
+ if (result === 0) return 0;
12006
12173
  }
12007
12174
  return result;
12008
12175
  }
@@ -12272,12 +12439,20 @@ function fetchBundleMeta(slug) {
12272
12439
  if (!p) {
12273
12440
  p = fetch(versionedUrl(`/v1/models/${slug}/metadata.json`, "models")).then((r) => r.json());
12274
12441
  _bundleMetaCache.set(slug, p);
12442
+ p.catch(() => _bundleMetaCache.delete(slug));
12275
12443
  }
12276
12444
  return p;
12277
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
+ }
12278
12450
  function shapeModelCredit(meta) {
12279
12451
  return meta.credit ?? meta.exports.high.credit;
12280
12452
  }
12453
+ function modelTierCredit(meta, tier) {
12454
+ return meta.exports[tier]?.credit ?? meta.exports.high.credit;
12455
+ }
12281
12456
  function modelUnitScene(bo) {
12282
12457
  return bo.modelUnitOverride ?? bo.radiusScene;
12283
12458
  }
@@ -12332,22 +12507,21 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
12332
12507
  if ((bo.modelLoadEpoch ?? 0) !== epoch) return;
12333
12508
  if (bo.mesh) bo.mesh.visible = false;
12334
12509
  setHaloLoading(bo, true);
12335
- const metaPromise = fetchBundleMeta(slug);
12336
- const gltf = await _loader.loadAsync(versionedUrl(`/v1/models/${slug}/high.glb`, "models"));
12337
- let meta = null;
12338
- try {
12339
- meta = await metaPromise;
12340
- } catch (e) {
12341
- 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;
12342
12515
  }
12516
+ const gltf = await _loader.loadAsync(versionedUrl(`/v1/models/${slug}/${tier}.glb`, "models"));
12343
12517
  if ((bo.modelLoadEpoch ?? 0) !== epoch || !bo.mesh) {
12344
12518
  disposeGltf(gltf.scene);
12345
12519
  return;
12346
12520
  }
12347
- const baseFrame = meta?.frame_map ? frameMapQuaternion(meta.frame_map) : null;
12521
+ const baseFrame = meta.frame_map ? frameMapQuaternion(meta.frame_map) : null;
12348
12522
  bo.body.modelBaseFrame = baseFrame ?? void 0;
12349
12523
  const root = withBaseFrame(gltf.scene, baseFrame);
12350
- const anchor = meta?.model_anchor ? new Vector3(...meta.model_anchor) : null;
12524
+ const anchor = meta.model_anchor ? new Vector3(...meta.model_anchor) : null;
12351
12525
  fitToUnitRadius(root, anchor && baseFrame ? anchor.applyQuaternion(baseFrame) : anchor);
12352
12526
  enableShadows(root);
12353
12527
  modelScene.add(root);
@@ -12355,20 +12529,19 @@ async function loadSpacecraftModel(bo, modelScene, ctx) {
12355
12529
  bo.model = root;
12356
12530
  bo.modelName = slug;
12357
12531
  setHaloLoading(bo, false);
12358
- if (meta) {
12359
- if (meta.scale_meters) {
12360
- const spanKm = meta.scale_meters / 2e3;
12361
- bo.body.data.radiusKm = spanKm * (meta.body_span_ratio ?? 1);
12362
- bo.radiusScene = kmToScene(effectiveRadiusKm(bo.body.data));
12363
- bo.modelUnitOverride = meta.body_span_ratio ? kmToScene(spanKm) : void 0;
12364
- }
12365
- ctx?.credits.registerModel({
12366
- bodyId: bo.body.data.id,
12367
- source: meta.exports.high.credit.url,
12368
- organisation: meta.exports.high.credit.name,
12369
- license: meta.exports.high.credit.license
12370
- });
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;
12371
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
+ });
12372
12545
  } finally {
12373
12546
  if (!bo.model) setHaloLoading(bo, false);
12374
12547
  }
@@ -15162,8 +15335,11 @@ function sunPathTransmittance(params, camRelPos, sunDir, planetRadiusScene, plan
15162
15335
  var SUN_HDR_MULTIPLIER = 6;
15163
15336
  var LIMB_DISC_AVG = .8;
15164
15337
  var STAR_POINT_SIZE_PX = 4;
15165
- var STAR_POINT_TEXEL_ALPHA = .3;
15166
- 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;
15167
15343
  function makeGlowTexture(color, size = 256) {
15168
15344
  const canvas = document.createElement("canvas");
15169
15345
  canvas.width = size;
@@ -15244,7 +15420,7 @@ function makeStarGlow(radius, color) {
15244
15420
  map: makeGlowTexture(color.startsWith("#") ? hexToRgb(color) : color),
15245
15421
  blending: AdditiveBlending,
15246
15422
  transparent: true,
15247
- opacity: .6,
15423
+ opacity: CORONA_OPACITY,
15248
15424
  depthWrite: false,
15249
15425
  depthTest: true
15250
15426
  }));
@@ -15252,23 +15428,44 @@ function makeStarGlow(radius, color) {
15252
15428
  corona.scale.set(glowSize, glowSize, 1);
15253
15429
  return corona;
15254
15430
  }
15255
- 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) {
15256
15438
  const geometry = new BufferGeometry();
15257
15439
  geometry.setAttribute("position", new Float32BufferAttribute(new Float32Array(3), 3));
15258
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
+ };
15259
15453
  const points = new Points(geometry, new ShaderMaterial({
15260
- uniforms: {
15261
- uColor: { value: new Vector3(c.r, c.g, c.b) },
15262
- uIntensity: { value: STAR_POINT_HANDOFF_INTENSITY },
15263
- uTint: { value: new Vector3(1, 1, 1) },
15264
- uMap: { value: circleTexture }
15265
- },
15454
+ uniforms,
15266
15455
  vertexShader: `
15267
15456
  #include <common>
15268
15457
  #include <logdepthbuf_pars_vertex>
15458
+ uniform float uPixelRatio;
15459
+ uniform vec4 uViewport;
15460
+ varying vec2 vSeat;
15269
15461
  void main() {
15270
15462
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
15271
- 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;
15272
15469
  #include <logdepthbuf_vertex>
15273
15470
  }
15274
15471
  `,
@@ -15276,14 +15473,35 @@ function makeStarPoint(color, circleTexture) {
15276
15473
  #include <common>
15277
15474
  #include <logdepthbuf_pars_fragment>
15278
15475
  uniform vec3 uColor;
15279
- uniform float uIntensity;
15476
+ uniform vec3 uDiscColor;
15477
+ uniform float uFlux;
15478
+ uniform float uPixelRatio;
15479
+ uniform float uPixels;
15280
15480
  uniform vec3 uTint;
15281
- uniform sampler2D uMap;
15481
+ varying vec2 vSeat;
15282
15482
  void main() {
15283
15483
  #include <logdepthbuf_fragment>
15284
- vec4 texel = texture2D(uMap, gl_PointCoord);
15285
- if (texel.a < 0.01) discard;
15286
- 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);
15287
15505
  }
15288
15506
  `,
15289
15507
  transparent: true,
@@ -15291,14 +15509,22 @@ function makeStarPoint(color, circleTexture) {
15291
15509
  depthWrite: false
15292
15510
  }));
15293
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
+ };
15294
15520
  return points;
15295
15521
  }
15296
- function buildStarExtras(root, radius, color, circleTexture) {
15522
+ function buildStarExtras(root, radius, color) {
15297
15523
  const light = new PointLight(16777215, SUN_LIGHT_INTENSITY, 0, 0);
15298
15524
  root.add(light);
15299
15525
  const corona = makeStarGlow(radius, color);
15300
15526
  root.add(corona);
15301
- const starPoint = makeStarPoint(color, circleTexture);
15527
+ const starPoint = makeStarPoint(color);
15302
15528
  root.add(starPoint);
15303
15529
  return {
15304
15530
  light,
@@ -15310,6 +15536,12 @@ function applyStarTint(corona, starPoint, tint) {
15310
15536
  if (corona) corona.material.color.setRGB(tint.x, tint.y, tint.z);
15311
15537
  if (starPoint) starPoint.material.uniforms.uTint.value.copy(tint);
15312
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
+ }
15313
15545
  function starViewTintUniforms(material) {
15314
15546
  const uniforms = material?.uniforms;
15315
15547
  return uniforms?.uAtmoTEnable ? uniforms : null;
@@ -15729,7 +15961,8 @@ function createStore(tileSize, layers, renderer) {
15729
15961
  texture.type = UnsignedByteType;
15730
15962
  texture.colorSpace = SRGBColorSpace;
15731
15963
  texture.wrapS = texture.wrapT = RepeatWrapping;
15732
- texture.minFilter = texture.magFilter = LinearFilter;
15964
+ texture.magFilter = LinearFilter;
15965
+ texture.minFilter = LinearMipmapLinearFilter;
15733
15966
  texture.generateMipmaps = false;
15734
15967
  texture.anisotropy = Math.min(MAX_ANISOTROPY, renderer.capabilities.getMaxAnisotropy());
15735
15968
  texture.needsUpdate = true;
@@ -15881,7 +16114,7 @@ function disposeTileOverlay(bo) {
15881
16114
  }
15882
16115
  function tilesServe(bo) {
15883
16116
  const tiles = bo.surfaceTiles;
15884
- return !!tiles && tiles.max_level >= 3 && tilesBase() !== "" && maxTileWindows() > 0 && !bo.tileOverlay?.dead && textureAllowed(tiles.distribution);
16117
+ return !!tiles && !bo.model && tiles.max_level >= 3 && tilesBase() !== "" && maxTileWindows() > 0 && !bo.tileOverlay?.dead && textureAllowed(tiles.distribution);
15885
16118
  }
15886
16119
  var inverse = new Matrix4();
15887
16120
  var over = new Vector3();
@@ -15965,7 +16198,7 @@ function disposeMaterial(mat) {
15965
16198
  }
15966
16199
  var STAR_SPHERE_SEGMENTS = 96;
15967
16200
  var BODY_SPHERE_SEGMENTS = 24;
15968
- function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, circleTexture, rendererElement, handleFocus, onHoverChange, halfPromoteIds) {
16201
+ function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, rendererElement, handleFocus, onHoverChange, halfPromoteIds) {
15969
16202
  for (const body of bodies) {
15970
16203
  const id = body.data.id;
15971
16204
  const isHalfPromoted = halfPromoteIds?.has(id) ?? false;
@@ -15987,7 +16220,7 @@ function buildMajorBodies(bodies, scene, clickables, meshToBody, bodyObjects, ci
15987
16220
  let atmosphere = null;
15988
16221
  let sunTint;
15989
16222
  if (!isVirtual && !noPhysical) {
15990
- if (isStar) starExtras = buildStarExtras(root, radius, color, circleTexture);
16223
+ if (isStar) starExtras = buildStarExtras(root, radius, color);
15991
16224
  const segments = isStar ? STAR_SPHERE_SEGMENTS : BODY_SPHERE_SEGMENTS;
15992
16225
  const geometry = new SphereGeometry(radius, segments, segments);
15993
16226
  const material = isStar ? makeStarSurfaceMaterial() : new MeshStandardMaterial({ color: bodyMeshColor(body.data) });
@@ -17032,30 +17265,31 @@ async function fetchPositions(bodyId) {
17032
17265
  if (res.status === 404) return [];
17033
17266
  throw new Error(`fetchPositions: ${url} returned ${res.status} ${res.statusText}`);
17034
17267
  }
17035
- const ds = new DecompressionStream("gzip");
17036
- return parseNomenclature(await new Response(res.body.pipeThrough(ds)).arrayBuffer());
17268
+ return parseNomenclature(await gunzip(res).arrayBuffer());
17037
17269
  }
17038
- async function fetchLabels(bodyId, lang) {
17270
+ async function fetchLabelText(bodyId, lang) {
17039
17271
  const url = versionedUrl(`/v1/nomenclature/labels/${lang}/${bodyId}.txt.gz`, "nomenclature");
17040
17272
  const res = await fetch(url);
17041
17273
  if (!res.ok) {
17042
- if (res.status === 404) return [];
17274
+ if (res.status === 404) return "";
17043
17275
  throw new Error(`fetchNomenclatureLabels: ${url} returned ${res.status} ${res.statusText}`);
17044
17276
  }
17045
- const ds = new DecompressionStream("gzip");
17046
- const text = await new Response(res.body.pipeThrough(ds)).text();
17047
- 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
+ }));
17048
17285
  }
17049
17286
  function fetchBodyNomenclature(bodyId, lang = getLocale()) {
17050
17287
  const key = `${bodyId}:${lang}`;
17051
17288
  let p = cache$1.get(key);
17052
17289
  if (!p) {
17053
17290
  p = (async () => {
17054
- const [records, labels] = await Promise.all([fetchPositions(bodyId), fetchLabels(bodyId, lang)]);
17055
- return records.map((rec, i) => ({
17056
- ...rec,
17057
- name: labels[i] ?? `Feature ${rec.featureId}`
17058
- }));
17291
+ const [records, labels] = await Promise.all([fetchPositions(bodyId), fetchLabelText(bodyId, lang)]);
17292
+ return nameFeatures(records, labels);
17059
17293
  })();
17060
17294
  cache$1.set(key, p);
17061
17295
  p.catch(() => {
@@ -20464,18 +20698,15 @@ function updateBodyVisibility(bodyObjects, camera, ctx, focusTruePos, focusedBod
20464
20698
  isClose = false;
20465
20699
  }
20466
20700
  if (trail && (!drawn || body.orbitCenter === null)) trail.visible = false;
20701
+ let starIsPoint = false;
20467
20702
  if (bo.starPoint && drawn) {
20468
- const screenR = bo.radiusScene / dist * projScale;
20469
- const subPixel = screenR < STAR_POINT_HANDOFF_R;
20470
- bo.starPoint.visible = subPixel;
20471
- if (subPixel) {
20472
- const material = bo.starPoint.material;
20473
- const t = screenR / STAR_POINT_HANDOFF_R;
20474
- const intensity = Math.max(STAR_POINT_HANDOFF_INTENSITY * t * t, 3);
20475
- material.uniforms.uIntensity.value = intensity;
20476
- }
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);
20477
20708
  group.visible = true;
20478
- isClose = !subPixel;
20709
+ isClose = !starIsPoint;
20479
20710
  showLabel = !isClose;
20480
20711
  }
20481
20712
  if (bo.isMinor && !isFocused && trail && !ctx.visibility.isPinned(body.data.id)) trail.visible = false;
@@ -20491,7 +20722,7 @@ function updateBodyVisibility(bodyObjects, camera, ctx, focusTruePos, focusedBod
20491
20722
  label.element.remove();
20492
20723
  } else if (group.visible && label && label.parent !== group) group.add(label);
20493
20724
  if (bo.mesh && bo.radiusScene > 0) {
20494
- const subPixel = !isFocused && !isSystemRoot && bo.radiusScene / dist * projScale < .5;
20725
+ const subPixel = starIsPoint || !isFocused && !isSystemRoot && bo.radiusScene / dist * projScale < .5;
20495
20726
  if (subPixel !== bo.meshSubPixel) {
20496
20727
  bo.meshSubPixel = subPixel;
20497
20728
  if (subPixel) bo.mesh.layers.disable(0);
@@ -22831,7 +23062,7 @@ function updateTextureLOD(bodyObjects, camera, renderer, ctx, textureLoader, foc
22831
23062
  const target = wantsUpgrade ? highestAvailableTier(desiredRank, bo.availableTiers) : bo.textureTier;
22832
23063
  if (target) loadBodyTextureTier(bo, target, desiredFrame, textureLoader);
22833
23064
  }
22834
- updateTileOverlay(bo, camera, renderer, projScale * renderer.getPixelRatio(), settings.showSurfaceTexture && !bo.appearance?.surfaceOwned && !bo.model, invalidate);
23065
+ updateTileOverlay(bo, camera, renderer, projScale * renderer.getPixelRatio(), settings.showSurfaceTexture && !bo.appearance?.surfaceOwned, invalidate);
22835
23066
  const dispTiers = bo.displacementMeta?.tiers;
22836
23067
  if (bo.displacementMap && dispTiers?.length && !bo.displacementLoading) {
22837
23068
  const maxTex = renderer.capabilities.maxTextureSize;
@@ -23184,7 +23415,7 @@ var PromotionRegistry = class {
23184
23415
  this.applyGroupFilter(deps.ctx.earthSatFilter);
23185
23416
  deps.ctx.onSmallBodyFilterChange((f) => this.applySmallBodyFilter(f));
23186
23417
  this.applySmallBodyFilter(deps.ctx.smallBodyFilter);
23187
- fetchLabels$1().then((labels) => {
23418
+ fetchLabels().then((labels) => {
23188
23419
  const alreadyLoaded = [];
23189
23420
  const add = (id) => {
23190
23421
  if (!this.defaults.add(id)) return;
@@ -23250,14 +23481,14 @@ var PromotionRegistry = class {
23250
23481
  this.finalizeBuilds();
23251
23482
  }
23252
23483
  buildBodyInstance(body, halfPromoteIds, auto = false) {
23253
- const { bodyObjects, ctx, clock, scene, clickables, meshToBody, circleTexture, renderer } = this.deps;
23484
+ const { bodyObjects, ctx, clock, scene, clickables, meshToBody, renderer } = this.deps;
23254
23485
  if (bodyObjects.has(body.data.id)) return false;
23255
23486
  if (!body.orbitElements && !body.data.pageOnly && body.data.orbitalSource !== OrbitalSource.SPICE_PROBE) {
23256
23487
  body.orbitElements = body.data;
23257
23488
  body.orbitCenterId = body.data.parentId;
23258
23489
  }
23259
23490
  ctx.place(body, clock.jd);
23260
- 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);
23261
23492
  const bo = bodyObjects.get(body.data.id);
23262
23493
  if (bo && !body.data.name) loadBodyLabel(bo);
23263
23494
  if (body.data.objectType === ObjectType.SPACECRAFT) ctx.bodies.dirtySpacecraftGroups.add(body.data.parentId);
@@ -23462,7 +23693,7 @@ var FocusController = class {
23462
23693
  _tmpV3 = new Vector3();
23463
23694
  pendingInitialView = null;
23464
23695
  upgradeTargetIds = /* @__PURE__ */ new Set();
23465
- constructor(deps, initialFocus, hoveredBodyIds, circleTexture, renderer) {
23696
+ constructor(deps, initialFocus, hoveredBodyIds, renderer) {
23466
23697
  this.deps = deps;
23467
23698
  this.focusedBody = initialFocus;
23468
23699
  this.promotion = new PromotionRegistry({
@@ -23471,7 +23702,6 @@ var FocusController = class {
23471
23702
  ctx: deps.ctx,
23472
23703
  clock: deps.clock,
23473
23704
  renderer,
23474
- circleTexture,
23475
23705
  clickables: deps.clickables,
23476
23706
  meshToBody: deps.meshToBody,
23477
23707
  hoveredBodyIds,
@@ -24332,7 +24562,7 @@ var SceneRenderer = class SceneRenderer {
24332
24562
  loadTexture: (b) => this.maybeLoadTexture(b),
24333
24563
  repositionAll: () => this.repositionAll(),
24334
24564
  assignMapLayerToTrails: () => this.assignMapLayerToTrails()
24335
- }, focusBody, this.hoveredBodyIds, this.circleTexture, this.renderer);
24565
+ }, focusBody, this.hoveredBodyIds, this.renderer);
24336
24566
  this.coverageWatch = new ProbeCoverageWatch(clock, callbacks.notices);
24337
24567
  const framing = focusBody?.data.id === initialView.id ? initialView : {
24338
24568
  latitude: 45,
@@ -24366,7 +24596,7 @@ var SceneRenderer = class SceneRenderer {
24366
24596
  this.tick();
24367
24597
  }
24368
24598
  buildScene() {
24369
- 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) => {
24370
24600
  if (hovered) this.hoveredBodyIds.add(id);
24371
24601
  else this.hoveredBodyIds.delete(id);
24372
24602
  this.invalidate();
@@ -25213,7 +25443,7 @@ var SceneRenderer = class SceneRenderer {
25213
25443
  const systemId = this.ctx.visibility.focusedSystemId;
25214
25444
  const seatKey = this.focusedSeatConfigKey();
25215
25445
  const probeVersion = this.ctx.probeStore?.version ?? 0;
25216
- 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);
25217
25447
  if (this.clock.jd === this.lastUpdatedJd && systemId === this.lastUpdatedSystemId && seatKey === this.lastSeatConfigKey && probeVersion === this.lastProbeVersion && dataVersion === this.lastDataVersion) {
25218
25448
  this.clock.seeked = false;
25219
25449
  return;
@@ -27829,6 +28059,43 @@ function subsolarPoint$1(id, ctx, jd) {
27829
28059
  const sunCentre = bodyCentre(sun, ctx, jd);
27830
28060
  return centre && sunCentre && subsolarAt(body, centre, sunCentre, jd);
27831
28061
  }
28062
+ var SUNLIGHT_SAMPLES = 1024;
28063
+ var GOLDEN_ANGLE = Math.PI * (3 - Math.sqrt(5));
28064
+ function sunlightAt(radiusKm, sun, occluders) {
28065
+ const sunKm = Math.hypot(...sun.centre);
28066
+ const aSun = Math.asin(Math.min(sun.radiusKm / sunKm, 1));
28067
+ if (!(aSun > 0)) return 1;
28068
+ const toSun = sun.centre.map((km) => km / sunKm);
28069
+ const along = (v) => v[0] * toSun[0] + v[1] * toSun[1] + v[2] * toSun[2];
28070
+ const near = occluders.filter(({ centre, radiusKm: r }) => {
28071
+ const ahead = along(centre);
28072
+ const aside = Math.sqrt(Math.max(Math.hypot(...centre) ** 2 - ahead ** 2, 0));
28073
+ return ahead > -(radiusKm + r) && aside < radiusKm + r + 2 * aSun * (ahead + radiusKm);
28074
+ });
28075
+ if (near.length === 0) return 1;
28076
+ const [u, v] = planeBasis(sun.centre);
28077
+ let sum = 0;
28078
+ for (let i = 0; i < SUNLIGHT_SAMPLES; i++) {
28079
+ const rho = Math.sqrt((i + .5) / SUNLIGHT_SAMPLES);
28080
+ const x = radiusKm * rho * Math.cos(i * GOLDEN_ANGLE);
28081
+ const y = radiusKm * rho * Math.sin(i * GOLDEN_ANGLE);
28082
+ const z = radiusKm * Math.sqrt(1 - rho * rho);
28083
+ let lit = 1;
28084
+ for (const { centre, radiusKm: r } of near) {
28085
+ const to = [
28086
+ centre[0] - x * u[0] - y * v[0] - z * toSun[0],
28087
+ centre[1] - x * u[1] - y * v[1] - z * toSun[1],
28088
+ centre[2] - x * u[2] - y * v[2] - z * toSun[2]
28089
+ ];
28090
+ const km = Math.hypot(...to);
28091
+ const sep = Math.acos(Math.max(-1, Math.min(1, along(to) / km)));
28092
+ lit *= sunInView(aSun, Math.asin(Math.min(r / km, 1)), sep);
28093
+ if (lit === 0) break;
28094
+ }
28095
+ sum += lit;
28096
+ }
28097
+ return sum / SUNLIGHT_SAMPLES;
28098
+ }
27832
28099
 
27833
28100
  //#endregion
27834
28101
  //#region src/lib/interaction/gesture.ts
@@ -28422,6 +28689,7 @@ var SpaceMap = class {
28422
28689
  longitude: target.lon
28423
28690
  })
28424
28691
  });
28692
+ if (arrival.outcome === "failed") throw arrival.error;
28425
28693
  return this.settled(arrival.flightMs);
28426
28694
  }
28427
28695
  async goTo(id, options = {}) {
@@ -28454,11 +28722,24 @@ var SpaceMap = class {
28454
28722
  }
28455
28723
  }
28456
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
+ };
28457
28738
  try {
28458
28739
  await this.open(id).catch(() => {});
28459
28740
  const date = jdToDate(this.clock.jd);
28460
- await this.ctx.ready(date);
28461
- await this.ctx.ensureBody(id, date);
28741
+ await this.ctx.ready(date).catch(note);
28742
+ await this.ctx.ensureBody(id, date).catch(note);
28462
28743
  renderer = await this.rendering();
28463
28744
  } finally {
28464
28745
  release();
@@ -28466,6 +28747,7 @@ var SpaceMap = class {
28466
28747
  if (superseded()) return gone;
28467
28748
  const body = this.ctx.getBody(id);
28468
28749
  if (!body || !renderer) {
28750
+ if (loadError !== void 0) return failed();
28469
28751
  console.warn(`[map] goTo ${id}: not an object of this map`);
28470
28752
  return {
28471
28753
  outcome: "missing",
@@ -28473,8 +28755,14 @@ var SpaceMap = class {
28473
28755
  flightMs: 0
28474
28756
  };
28475
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
+ }
28476
28764
  options.commit?.(body);
28477
- if (renderer.placeOf(body)) {
28765
+ if (placed) {
28478
28766
  const view = options.view?.(body) ?? {};
28479
28767
  const distance = view.distance ?? (body.data.radiusKm > 0 ? this.framing({}, id) : distanceBefore);
28480
28768
  let flightMs = 0;
@@ -32578,7 +32866,6 @@ var SystemMap = class {
32578
32866
 
32579
32867
  //#endregion
32580
32868
  //#region src/lib/scene/state/attribution.ts
32581
- var DEFAULT_ORBIT_LABELS = Object.fromEntries(ORBIT_SOURCE_ORDER.map((s) => [s, ORBIT_SOURCES[s].label]));
32582
32869
  function feedsFocusedSystem(ctx, source) {
32583
32870
  const parents = ctx.credits.orbitSourceParents.get(source);
32584
32871
  if (!parents) return true;
@@ -32586,22 +32873,28 @@ function feedsFocusedSystem(ctx, source) {
32586
32873
  for (const parent of parents) if (ctx.bodies.isInSystem(parent, sysId)) return true;
32587
32874
  return false;
32588
32875
  }
32589
- function creditedOrbitSources(ctx) {
32590
- return ORBIT_SOURCE_ORDER.filter((s) => ctx.credits.orbitSources.has(s)).filter((s) => !ORBIT_SOURCES[s].scoped || feedsFocusedSystem(ctx, s)).map((source) => ({
32591
- source,
32592
- info: ORBIT_SOURCES[source]
32593
- }));
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];
32594
32887
  }
32595
32888
  function scopedCredits(ctx, all) {
32596
32889
  const systemId = ctx.visibility.focusedSystemId;
32597
32890
  const bodyId = ctx.visibility.focusedBodyId;
32598
32891
  return [...all].filter((c) => c.bodyId === bodyId || systemId && c.systemId === systemId);
32599
32892
  }
32600
- function attributionChips(ctx, labels = DEFAULT_ORBIT_LABELS) {
32893
+ function attributionChips(ctx, labels = ORBIT_ARCHIVE_ORGANISATIONS) {
32601
32894
  const credits = ctx.credits;
32602
32895
  credits.imageryVersion;
32603
32896
  credits.modelVersion;
32604
- const orbits = [...new Set(creditedOrbitSources(ctx).map(({ source }) => labels[source]))];
32897
+ const orbits = [...new Set(creditedOrbitArchives(ctx).map((archive) => labels[archive]))];
32605
32898
  const organisations = /* @__PURE__ */ new Set();
32606
32899
  for (const credit of scopedCredits(ctx, credits.imagery.values())) organisations.add(credit.organisation);
32607
32900
  const bodyId = ctx.visibility.focusedBodyId;
@@ -32850,12 +33143,7 @@ async function open(date) {
32850
33143
  const params = chebyshevZoneParams(metadata);
32851
33144
  if (params.size > 0) chebyshev = new ChebyshevStore(params);
32852
33145
  }
32853
- try {
32854
- await chebyshev?.ensure(jd).done;
32855
- } catch (error) {
32856
- chebyshev = null;
32857
- throw error;
32858
- }
33146
+ await chebyshev?.load(jd);
32859
33147
  return {
32860
33148
  jd,
32861
33149
  metadata,
@@ -32919,7 +33207,8 @@ async function place(id, at, above = /* @__PURE__ */ new Set()) {
32919
33207
  if (!own) return null;
32920
33208
  if (own.parentId === "naif-0") return {
32921
33209
  data: own.data,
32922
- centre: own.offset
33210
+ centre: own.offset,
33211
+ parents: []
32923
33212
  };
32924
33213
  const parent = await place(own.parentId, at, new Set(above).add(id));
32925
33214
  if (!parent) return null;
@@ -32930,9 +33219,16 @@ async function place(id, at, above = /* @__PURE__ */ new Set()) {
32930
33219
  x + own.offset[0],
32931
33220
  y + own.offset[1],
32932
33221
  z + own.offset[2]
32933
- ]
33222
+ ],
33223
+ parents: [own.parentId, ...parent.parents]
32934
33224
  };
32935
33225
  }
33226
+ function neighbours(id, { parents }, at) {
33227
+ const ids = new Set(parents);
33228
+ for (const { body } of at.chebyshev?.bodiesAt(at.jd) ?? []) if (parents.includes(`naif-${body.parentId}`)) ids.add(body.id);
33229
+ ids.delete(id);
33230
+ return [...ids];
33231
+ }
32936
33232
  async function spinOf(id) {
32937
33233
  const [detail] = await Promise.all([fetchObjectDetail(id, false), loadSystemsGlobal()]);
32938
33234
  const orientation = detail.global?.orientation;
@@ -32985,6 +33281,29 @@ function subsolarPoint(id, date) {
32985
33281
  }, body.centre, sun.centre, at.jd);
32986
33282
  });
32987
33283
  }
33284
+ function sunlight(id, date) {
33285
+ return inTurn(async () => {
33286
+ if (id === "naif-10") return null;
33287
+ const at = await open(date);
33288
+ const body = await place(id, at);
33289
+ const sun = body && await place("naif-10", at);
33290
+ if (!body || !sun) return null;
33291
+ const ball = ({ data, centre }) => ({
33292
+ centre: sceneToEcliptic([
33293
+ centre[0] - body.centre[0],
33294
+ centre[1] - body.centre[1],
33295
+ centre[2] - body.centre[2]
33296
+ ]),
33297
+ radiusKm: data.radiusKm
33298
+ });
33299
+ const occluders = [];
33300
+ for (const other of neighbours(id, body, at)) {
33301
+ const placed = await place(other, at);
33302
+ if (placed && placed.data.radiusKm > 0) occluders.push(ball(placed));
33303
+ }
33304
+ return sunlightAt(body.data.radiusKm > 0 ? body.data.radiusKm : 0, ball(sun), occluders);
33305
+ });
33306
+ }
32988
33307
 
32989
33308
  //#endregion
32990
33309
  //#region src/lib/fetch/panoramas.ts
@@ -33006,11 +33325,13 @@ async function fetchPanoramas(bodyId) {
33006
33325
  //#endregion
33007
33326
  //#region src/sdk/index.ts
33008
33327
  async function createMap(options) {
33009
- const { container, dataUrl, imagesUrl, locale, messages, includeNonCommercial, controls, events, ...rest } = options;
33328
+ const { container, dataUrl, imagesUrl, liveUrl, tilesUrl, locale, messages, includeNonCommercial, controls, events, ...rest } = options;
33010
33329
  const element = resolveContainer(container);
33011
33330
  applyHost({
33012
33331
  dataUrl,
33013
33332
  imagesUrl,
33333
+ liveUrl,
33334
+ tilesUrl,
33014
33335
  locale,
33015
33336
  messages,
33016
33337
  includeNonCommercial
@@ -33031,11 +33352,13 @@ async function createMap(options) {
33031
33352
  return map;
33032
33353
  }
33033
33354
  async function createFlatMap(options) {
33034
- const { container, dataUrl, imagesUrl, locale, messages, includeNonCommercial, controls, events, ...rest } = options;
33355
+ const { container, dataUrl, imagesUrl, liveUrl, tilesUrl, locale, messages, includeNonCommercial, controls, events, ...rest } = options;
33035
33356
  const element = resolveContainer(container);
33036
33357
  applyHost({
33037
33358
  dataUrl,
33038
33359
  imagesUrl,
33360
+ liveUrl,
33361
+ tilesUrl,
33039
33362
  locale,
33040
33363
  messages,
33041
33364
  includeNonCommercial
@@ -33050,11 +33373,13 @@ async function createFlatMap(options) {
33050
33373
  return map;
33051
33374
  }
33052
33375
  async function createPanorama(options) {
33053
- const { container, dataUrl, imagesUrl, locale, messages, includeNonCommercial, controls, events, ...rest } = options;
33376
+ const { container, dataUrl, imagesUrl, liveUrl, tilesUrl, locale, messages, includeNonCommercial, controls, events, ...rest } = options;
33054
33377
  const element = resolveContainer(container);
33055
33378
  applyHost({
33056
33379
  dataUrl,
33057
33380
  imagesUrl,
33381
+ liveUrl,
33382
+ tilesUrl,
33058
33383
  locale,
33059
33384
  messages,
33060
33385
  includeNonCommercial
@@ -33069,11 +33394,13 @@ async function createPanorama(options) {
33069
33394
  return view;
33070
33395
  }
33071
33396
  async function createSystemMap(options) {
33072
- const { container, dataUrl, imagesUrl, locale, messages, includeNonCommercial, events, ...rest } = options;
33397
+ const { container, dataUrl, imagesUrl, liveUrl, tilesUrl, locale, messages, includeNonCommercial, events, ...rest } = options;
33073
33398
  const element = resolveContainer(container);
33074
33399
  applyHost({
33075
33400
  dataUrl,
33076
33401
  imagesUrl,
33402
+ liveUrl,
33403
+ tilesUrl,
33077
33404
  locale,
33078
33405
  messages,
33079
33406
  includeNonCommercial
@@ -33099,6 +33426,8 @@ function applyHost(options) {
33099
33426
  const overrides = {};
33100
33427
  if (options.dataUrl !== void 0) overrides.dataUrl = options.dataUrl;
33101
33428
  if (options.imagesUrl !== void 0) overrides.imagesUrl = options.imagesUrl;
33429
+ if (options.liveUrl !== void 0) overrides.liveUrl = options.liveUrl;
33430
+ if (options.tilesUrl !== void 0) overrides.tilesUrl = options.tilesUrl;
33102
33431
  if (options.locale !== void 0) overrides.locale = () => options.locale;
33103
33432
  if (options.messages !== void 0) overrides.messages = options.messages;
33104
33433
  if (options.includeNonCommercial) overrides.textures = "non-commercial";
@@ -33106,5 +33435,5 @@ function applyHost(options) {
33106
33435
  }
33107
33436
 
33108
33437
  //#endregion
33109
- export { AttributionControl, FlatAttributionControl, FlatMap, GestureHandler, MAP_LAYERS, PROJECTION_IDS, PanoramaAttributionControl, PanoramaView, SpaceMap, SystemMap, Viewport, bearingDeg, boxRing, circlePoints, configureHost, createFlatMap, createMap, createPanorama, createProjection, createSystemMap, dateToJD, defaultSceneSettings, distanceKm, elements, fetchPanoramaIndex, fetchPanoramas, findPanorama, fixed, graticule, groundDistanceM, isViewable, jdToDate, kmToAu, neighboursOf, offsetKm, panoramaAt, pathFor, planeBasis, projectionAspect, samples, smallCircle, subsolarPoint, tle };
33438
+ export { AttributionControl, FlatAttributionControl, FlatMap, GestureHandler, MAP_LAYERS, PROJECTION_IDS, PanoramaAttributionControl, PanoramaView, SpaceMap, SystemMap, Viewport, bearingDeg, boxRing, circlePoints, configureHost, createFlatMap, createMap, createPanorama, createProjection, createSystemMap, dateToJD, defaultSceneSettings, distanceKm, elements, fetchPanoramaIndex, fetchPanoramas, findPanorama, fixed, graticule, groundDistanceM, isViewable, jdToDate, kmToAu, neighboursOf, offsetKm, panoramaAt, pathFor, planeBasis, projectionAspect, samples, smallCircle, subsolarPoint, sunlight, tle };
33110
33439
  (function(){if(typeof document==='undefined')return;var s=document.createElement('style');s.textContent=".scene-label{pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;color:inherit;touch-action:none;z-index:1;width:32px;height:32px;text-decoration:none;display:block;position:relative;left:0}.scene-label__halo{border-radius:50%;width:100%;height:100%;transition:transform .1s}.scene-label__halo--spacecraft,.scene-label__halo--debris{overflow:visible}.scene-label__loader{box-sizing:border-box;pointer-events:none;border:2px solid #fff;border-color:#fff #fff #fff3 #fff3;border-radius:50%;width:32px;height:32px;margin:auto;animation:.9s linear infinite scene-label-loader-spin;position:fixed;inset:0}@keyframes scene-label-loader-spin{to{transform:rotate(360deg)}}.reduce-motion .scene-label__loader{animation-duration:.9s!important;animation-iteration-count:infinite!important}.scene-label__name{white-space:nowrap;pointer-events:auto;cursor:pointer;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;padding-left:8px;font-size:14px;font-weight:700;text-decoration:none;position:absolute;top:50%;left:32px;transform:translateY(-50%)}.scene-label__name--large{font-size:16px}.scene-label__annotations{top:28px;pointer-events:none;flex-direction:column;padding-inline-start:8px;display:flex;position:absolute;inset-inline-start:32px}.scene-label__annotation{opacity:0;white-space:nowrap;color:#fff9;text-shadow:0 0 3px #000,0 0 3px #000;transition:opacity .15s, display .15s allow-discrete;font-size:11px;line-height:1.3;display:none}.scene-label__annotation--visible{opacity:1;display:block}@starting-style{.scene-label__annotation--visible{opacity:0}}.scene-feature-label{color:#ffffffb3;white-space:nowrap;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;text-shadow:0 0 2px #000,0 0 2px #000;z-index:0;font-size:12px;transition:color .1s;left:0}.scene-feature-label:hover{color:#fff;text-shadow:0 0 3px #000,0 0 3px #000}.scene-feature-label--active{color:#fff;font-size:14px;font-weight:600}.scene-path-label{color:#fff;font:inherit;white-space:nowrap;text-align:start;text-shadow:0 0 4px #000,0 0 4px #000;pointer-events:auto;cursor:pointer;-webkit-user-select:none;user-select:none;touch-action:none;z-index:2;background:0 0;border:0;align-items:center;gap:8px;padding:0;line-height:1.15;display:flex;left:0}.scene-path-label--static{pointer-events:none;cursor:default}.scene-path-label__halo{border-radius:50%;flex-shrink:0;width:32px;height:32px;margin-inline-start:-16px;transition:transform .1s}.scene-path-label:hover .scene-path-label__halo{transform:scale(1.15)}.scene-path-label__text{flex-direction:column;gap:1px;display:flex}.scene-path-label__name{font-size:14px;font-weight:700}.scene-path-label__when{opacity:.8;font-variant-numeric:tabular-nums;font-size:12px}.scene-path-label--dim .scene-path-label__text{display:none}.scene-path-label--dim .scene-path-label__halo{transform:scale(.55)}.scene-path-label--dim:hover .scene-path-label__halo{transform:scale(.75)}.scene-path-label--step .scene-path-label__halo{background:0 0;border:0}.scene-path-label--step:hover .scene-path-label__halo{background:#ffffff1f}.scene-path-label--step .scene-path-label__name{font-size:12px;font-weight:600}.scene-path-label--step .scene-path-label__when{font-size:11px}.scene-path-label__hazard{font-size:11px;font-weight:700}.scene-path-label--lit{color:#fff}.scene-path-label--lit .scene-path-label__halo{transform:scale(1.15)}.scene-path-label--lit.scene-path-label--dim .scene-path-label__halo{transform:scale(.75)}.scene-path-label--faint .scene-path-label__text{display:none}.scene-path-label--faint .scene-path-label__halo{opacity:.45;transform:scale(.5)}.scene-path-label--faint:hover .scene-path-label__halo{opacity:1;transform:scale(.75)}.scene-overlay--no-names .scene-label__name,.scene-overlay--no-names .scene-label__annotations{display:none}.scene-overlay--no-halos .scene-label__halo{visibility:hidden}.scene-overlay--no-halos .scene-label{pointer-events:none}.scene-hazard-label{white-space:nowrap;pointer-events:none;-webkit-user-select:none;user-select:none;text-shadow:0 0 4px #000,0 0 4px #000;margin-inline-start:16px;font-size:12px;font-weight:700;position:relative;left:0}.user-location-marker{pointer-events:none;background:#1a73e8;border:2px solid #fff;border-radius:50%;width:16px;height:16px;box-shadow:0 0 6px #0006}.sm-drawing-label{white-space:nowrap;color:#fff;text-shadow:0 0 4px #000,0 0 4px #000;-webkit-user-select:none;user-select:none;pointer-events:none;font:700 14px/1.2 system-ui,sans-serif}.sm-drawing-icon{-webkit-user-select:none;user-select:none;pointer-events:none;display:block}.sm-drawing--interactive{pointer-events:auto;cursor:pointer;touch-action:none}.sm-cooperative-hint{z-index:4;color:#fff;text-align:center;pointer-events:none;opacity:0;background:#0000008c;place-content:center;padding:16px;font:15px/1.4 system-ui,sans-serif;transition:opacity .2s;display:grid;position:absolute;inset:0}.sm-cooperative-hint--shown{opacity:1}@media (prefers-reduced-motion:reduce){.sm-cooperative-hint{transition:none}}.sm-ctrl{z-index:2;box-sizing:border-box;pointer-events:none;flex-direction:column;gap:6px;max-width:100%;padding:6px;display:flex;position:absolute}.sm-ctrl>*{pointer-events:auto}.sm-ctrl--top-left{align-items:flex-start;top:0;left:0}.sm-ctrl--top-right{align-items:flex-end;top:0;right:0}.sm-ctrl--bottom-left{align-items:flex-start;bottom:0;left:0}.sm-ctrl--bottom-right{align-items:flex-end;bottom:0;right:0}.sm-map{-webkit-user-select:none;user-select:none}:where(.sm-map){width:100%;height:100%;position:relative}.sm-map__canvas{pointer-events:auto;touch-action:none;width:100%;height:100%;display:block}.sm-map__canvas:focus-visible{outline:2px solid var(--ring,Highlight)}.sm-map .scene-overlay{pointer-events:none;z-index:0;position:absolute;inset:0}.sm-flat{touch-action:none;background:#05070e;width:100%;height:100%;position:relative;overflow:hidden}.sm-flat__raster,.sm-flat__vector,.sm-flat__markers{width:100%;height:100%;position:absolute;inset:0}.sm-flat__raster{display:block}.sm-flat__vector,.sm-flat__markers{pointer-events:none}.sm-flat--interactive{cursor:grab}.sm-flat--cooperative{touch-action:pan-x pan-y}.sm-flat--dragging{cursor:grabbing}.sm-flat__graticule{fill:none;stroke:#ffffff47;stroke-width:1px}.sm-flat__frame{fill:none;stroke:#ffffff73;stroke-width:1px}.sm-flat__feature-dot{fill:#fffc}.sm-flat__feature-label{fill:#ffffffd9;paint-order:stroke;stroke:#000000a6;stroke-width:2.5px;font:10px/1 system-ui,sans-serif}.sm-flat__marker-dot{background:#4ea3ff;border:2px solid #fff;border-radius:50%;width:10px;height:10px;box-shadow:0 0 0 1px #00000073}.sm-system-map{border-radius:var(--sm-system-map-radius,6px);background:var(--sm-system-map-background,#ffffff0d);width:100%;color:var(--sm-system-map-ink,#ffffff9e);position:relative;overflow:hidden}.sm-system-map--background{pointer-events:none;background:0 0;border-radius:0;height:100%}.sm-system-map__svg{touch-action:pan-y;width:100%;height:auto;display:block}.sm-system-map--background .sm-system-map__svg{height:100%}.sm-system-map__svg--near :is(a,.sm-system-map__button):not([data-near]){pointer-events:none}.sm-system-map__button{cursor:pointer}.sm-system-map__button:focus{outline:none}.sm-system-map__reference{opacity:.6}.sm-system-map__band--muted{color:var(--sm-system-map-ink,#ffffff9e)}.sm-system-map__band--sky{color:var(--sm-system-map-sky,oklch(74.6% .16 232.661/.7))}.sm-system-map__band--amber{color:var(--sm-system-map-amber,oklch(92.4% .12 95.746/.8))}.sm-system-map__band-label{font-weight:600}.sm-system-map__band-label--muted{fill:var(--sm-system-map-ink,#ffffff9e)}.sm-system-map__band-label--sky{fill:var(--sm-system-map-sky-label,oklch(74.6% .16 232.661))}.sm-system-map__band-label--amber{fill:var(--sm-system-map-amber-label,oklch(92.4% .12 95.746/.9))}.sm-system-map__tip{z-index:10;background:var(--sm-system-map-tip-background,#0a0c14e6);width:max-content;color:var(--sm-system-map-tip-ink,#fffffff2);white-space:nowrap;pointer-events:none;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-radius:.25rem;padding:.25rem .5rem;font-size:.75rem;line-height:1.33333;position:absolute;transform:translate(-50%);box-shadow:0 1px 3px #0000001a,0 1px 2px -1px #0000001a}.sm-system-map__tip[hidden]{display:none}.sm-system-map__tip-title{font-weight:500}.sm-system-map__tip-sub{color:var(--sm-system-map-ink,#ffffff9e)}.sm-ctrl:has(>.sm-attribution:only-child){padding:0}.sm-ctrl--top-left:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 0 var(--sm-attribution-radius) 0}.sm-ctrl--top-right:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 0 0 var(--sm-attribution-radius)}.sm-ctrl--bottom-left:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:0 var(--sm-attribution-radius) 0 0}.sm-ctrl--bottom-right:has(>.sm-attribution:only-child)>.sm-attribution{border-radius:var(--sm-attribution-radius) 0 0 0}.sm-attribution{--sm-attribution-radius:3px;box-sizing:border-box;border-radius:var(--sm-attribution-radius);color:#ffffffbf;white-space:nowrap;background:#0006;align-items:center;gap:.5em;max-width:100%;padding:1px 6px;font:11px/1.45 system-ui,sans-serif;display:flex}.sm-attribution a{color:inherit;text-decoration:none}.sm-attribution a:hover{color:#fff;text-decoration:underline}.sm-attribution__home{font-weight:600}.sm-attribution__group{text-overflow:ellipsis;overflow:hidden}.sm-attribution__label{color:#ffffff80}\n/*$vite$:1*/";document.head.appendChild(s);})();