@rybosome/tspice 0.0.7 → 0.0.8

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.
Files changed (214) hide show
  1. package/README.md +140 -78
  2. package/backend-contract/dist/.tsbuildinfo +1 -1
  3. package/backend-contract/dist/domains/cells-windows.d.ts +94 -0
  4. package/backend-contract/dist/domains/cells-windows.js +10 -0
  5. package/backend-contract/dist/domains/coords-vectors.d.ts +50 -0
  6. package/backend-contract/dist/domains/dsk.d.ts +49 -0
  7. package/backend-contract/dist/domains/dsk.js +2 -0
  8. package/backend-contract/dist/domains/ek.d.ts +186 -0
  9. package/backend-contract/dist/domains/ek.js +8 -0
  10. package/backend-contract/dist/domains/ephemeris.d.ts +141 -3
  11. package/backend-contract/dist/domains/error.d.ts +42 -0
  12. package/backend-contract/dist/domains/error.js +33 -0
  13. package/backend-contract/dist/domains/file-io.d.ts +114 -0
  14. package/backend-contract/dist/domains/file-io.js +8 -0
  15. package/backend-contract/dist/domains/frames.d.ts +40 -0
  16. package/backend-contract/dist/domains/geometry-gf.d.ts +44 -0
  17. package/backend-contract/dist/domains/geometry-gf.js +14 -0
  18. package/backend-contract/dist/domains/geometry.d.ts +21 -1
  19. package/backend-contract/dist/domains/ids-names-normalize.d.ts +3 -0
  20. package/backend-contract/dist/domains/ids-names-normalize.js +74 -0
  21. package/backend-contract/dist/domains/ids-names.d.ts +37 -0
  22. package/backend-contract/dist/domains/kernel-pool.d.ts +134 -0
  23. package/backend-contract/dist/domains/kernel-pool.js +2 -0
  24. package/backend-contract/dist/domains/kernels-utils.d.ts +44 -0
  25. package/backend-contract/dist/domains/kernels-utils.js +265 -0
  26. package/backend-contract/dist/domains/kernels.d.ts +39 -3
  27. package/backend-contract/dist/domains/time.d.ts +102 -0
  28. package/backend-contract/dist/index.d.ts +31 -3
  29. package/backend-contract/dist/index.js +15 -1
  30. package/backend-contract/dist/shared/errors.d.ts +6 -0
  31. package/backend-contract/dist/shared/errors.js +8 -0
  32. package/backend-contract/dist/shared/mat3.d.ts +26 -14
  33. package/backend-contract/dist/shared/mat3.js +46 -17
  34. package/backend-contract/dist/shared/mat6.d.ts +34 -0
  35. package/backend-contract/dist/shared/mat6.js +116 -0
  36. package/backend-contract/dist/shared/spice-handles.d.ts +20 -0
  37. package/backend-contract/dist/shared/spice-handles.js +82 -0
  38. package/backend-contract/dist/shared/spice-int.d.ts +32 -0
  39. package/backend-contract/dist/shared/spice-int.js +41 -0
  40. package/backend-contract/dist/shared/types.d.ts +106 -1
  41. package/backend-contract/dist/shared/vec.d.ts +54 -0
  42. package/backend-contract/dist/shared/vec.js +162 -0
  43. package/backend-fake/dist/.tsbuildinfo +1 -1
  44. package/backend-fake/dist/index.d.ts +19 -1
  45. package/backend-fake/dist/index.js +1103 -18
  46. package/backend-node/dist/.tsbuildinfo +1 -1
  47. package/backend-node/dist/codec/arrays.d.ts +9 -0
  48. package/backend-node/dist/codec/arrays.js +36 -0
  49. package/backend-node/dist/codec/errors.d.ts +6 -6
  50. package/backend-node/dist/codec/errors.js +6 -6
  51. package/backend-node/dist/domains/cells-windows.d.ts +5 -0
  52. package/backend-node/dist/domains/cells-windows.js +112 -0
  53. package/backend-node/dist/domains/coords-vectors.d.ts +1 -0
  54. package/backend-node/dist/domains/coords-vectors.js +64 -1
  55. package/backend-node/dist/domains/dsk.d.ts +6 -0
  56. package/backend-node/dist/domains/dsk.js +108 -0
  57. package/backend-node/dist/domains/ek.d.ts +10 -0
  58. package/backend-node/dist/domains/ek.js +100 -0
  59. package/backend-node/dist/domains/ephemeris.d.ts +5 -1
  60. package/backend-node/dist/domains/ephemeris.js +150 -1
  61. package/backend-node/dist/domains/error.d.ts +5 -0
  62. package/backend-node/dist/domains/error.js +34 -0
  63. package/backend-node/dist/domains/file-io.d.ts +7 -0
  64. package/backend-node/dist/domains/file-io.js +105 -0
  65. package/backend-node/dist/domains/frames.d.ts +1 -0
  66. package/backend-node/dist/domains/frames.js +52 -0
  67. package/backend-node/dist/domains/geometry-gf.d.ts +5 -0
  68. package/backend-node/dist/domains/geometry-gf.js +74 -0
  69. package/backend-node/dist/domains/geometry.d.ts +1 -0
  70. package/backend-node/dist/domains/geometry.js +62 -0
  71. package/backend-node/dist/domains/ids-names.d.ts +2 -1
  72. package/backend-node/dist/domains/ids-names.js +30 -0
  73. package/backend-node/dist/domains/kernel-pool.d.ts +5 -0
  74. package/backend-node/dist/domains/kernel-pool.js +74 -0
  75. package/backend-node/dist/domains/kernels.d.ts +1 -0
  76. package/backend-node/dist/domains/kernels.js +100 -13
  77. package/backend-node/dist/domains/time.d.ts +1 -0
  78. package/backend-node/dist/domains/time.js +75 -1
  79. package/backend-node/dist/index.d.ts +2 -0
  80. package/backend-node/dist/index.js +62 -1
  81. package/backend-node/dist/lowlevel/binding.d.ts +3 -0
  82. package/backend-node/dist/lowlevel/binding.js +115 -0
  83. package/backend-node/dist/runtime/addon.d.ts +271 -0
  84. package/backend-node/dist/runtime/addon.js +3 -0
  85. package/backend-node/dist/runtime/kernel-staging.d.ts +17 -0
  86. package/backend-node/dist/runtime/kernel-staging.js +80 -7
  87. package/backend-node/dist/runtime/spice-handles.d.ts +3 -0
  88. package/backend-node/dist/runtime/spice-handles.js +2 -0
  89. package/backend-node/dist/runtime/virtual-output-staging.d.ts +16 -0
  90. package/backend-node/dist/runtime/virtual-output-staging.js +148 -0
  91. package/backend-wasm/dist/.tsbuildinfo +1 -1
  92. package/backend-wasm/dist/codec/alloc.d.ts +19 -0
  93. package/backend-wasm/dist/codec/alloc.js +64 -0
  94. package/backend-wasm/dist/codec/calls.d.ts +2 -0
  95. package/backend-wasm/dist/codec/calls.js +13 -24
  96. package/backend-wasm/dist/codec/errors.d.ts +6 -0
  97. package/backend-wasm/dist/codec/errors.js +34 -2
  98. package/backend-wasm/dist/codec/found.d.ts +2 -0
  99. package/backend-wasm/dist/codec/found.js +20 -43
  100. package/backend-wasm/dist/codec/strings.d.ts +31 -1
  101. package/backend-wasm/dist/codec/strings.js +93 -6
  102. package/backend-wasm/dist/domains/cells-windows.d.ts +9 -0
  103. package/backend-wasm/dist/domains/cells-windows.js +392 -0
  104. package/backend-wasm/dist/domains/coords-vectors.d.ts +1 -0
  105. package/backend-wasm/dist/domains/coords-vectors.js +377 -187
  106. package/backend-wasm/dist/domains/dsk.d.ts +6 -0
  107. package/backend-wasm/dist/domains/dsk.js +179 -0
  108. package/backend-wasm/dist/domains/ek.d.ts +6 -0
  109. package/backend-wasm/dist/domains/ek.js +543 -0
  110. package/backend-wasm/dist/domains/ephemeris.d.ts +4 -1
  111. package/backend-wasm/dist/domains/ephemeris.js +405 -46
  112. package/backend-wasm/dist/domains/error.d.ts +5 -0
  113. package/backend-wasm/dist/domains/error.js +109 -0
  114. package/backend-wasm/dist/domains/file-io.d.ts +7 -0
  115. package/backend-wasm/dist/domains/file-io.js +462 -0
  116. package/backend-wasm/dist/domains/frames.d.ts +1 -0
  117. package/backend-wasm/dist/domains/frames.js +136 -4
  118. package/backend-wasm/dist/domains/geometry-gf.d.ts +5 -0
  119. package/backend-wasm/dist/domains/geometry-gf.js +178 -0
  120. package/backend-wasm/dist/domains/geometry.d.ts +1 -0
  121. package/backend-wasm/dist/domains/geometry.js +210 -0
  122. package/backend-wasm/dist/domains/ids-names.d.ts +2 -1
  123. package/backend-wasm/dist/domains/ids-names.js +89 -0
  124. package/backend-wasm/dist/domains/kernel-pool.d.ts +5 -0
  125. package/backend-wasm/dist/domains/kernel-pool.js +357 -0
  126. package/backend-wasm/dist/domains/kernels.d.ts +1 -0
  127. package/backend-wasm/dist/domains/kernels.js +91 -2
  128. package/backend-wasm/dist/domains/time.d.ts +2 -0
  129. package/backend-wasm/dist/domains/time.js +235 -133
  130. package/backend-wasm/dist/lowlevel/exports.d.ts +215 -1
  131. package/backend-wasm/dist/lowlevel/exports.js +217 -38
  132. package/backend-wasm/dist/runtime/create-backend-options.d.ts +21 -0
  133. package/backend-wasm/dist/runtime/create-backend.node.d.ts +7 -0
  134. package/backend-wasm/dist/runtime/create-backend.node.js +283 -12
  135. package/backend-wasm/dist/runtime/create-backend.web.d.ts +1 -0
  136. package/backend-wasm/dist/runtime/create-backend.web.js +40 -4
  137. package/backend-wasm/dist/runtime/fs.d.ts +5 -0
  138. package/backend-wasm/dist/runtime/fs.js +5 -0
  139. package/backend-wasm/dist/runtime/spice-handles.d.ts +3 -0
  140. package/backend-wasm/dist/runtime/spice-handles.js +2 -0
  141. package/backend-wasm/dist/runtime/virtual-outputs.d.ts +16 -0
  142. package/backend-wasm/dist/runtime/virtual-outputs.js +35 -0
  143. package/backend-wasm/dist/tspice_backend_wasm.node.js +3 -3
  144. package/backend-wasm/dist/tspice_backend_wasm.wasm +0 -0
  145. package/backend-wasm/dist/tspice_backend_wasm.web.js +1 -1
  146. package/core/dist/.tsbuildinfo +1 -1
  147. package/core/dist/index.d.ts +19 -8
  148. package/core/dist/index.js +19 -8
  149. package/dist/.tsbuildinfo +1 -1
  150. package/dist/backend.d.ts +5 -0
  151. package/dist/clients/createSpiceAsyncFromTransport.d.ts +5 -0
  152. package/dist/clients/createSpiceAsyncFromTransport.js +90 -0
  153. package/dist/clients/createSpiceSyncFromTransport.d.ts +5 -0
  154. package/dist/clients/createSpiceSyncFromTransport.js +88 -0
  155. package/dist/clients/spiceClients.d.ts +59 -0
  156. package/dist/clients/spiceClients.js +292 -0
  157. package/dist/errors.d.ts +4 -0
  158. package/dist/errors.js +4 -0
  159. package/dist/index.d.ts +10 -7
  160. package/dist/index.js +4 -3
  161. package/dist/kernels/defaultKernelPathFromUrl.d.ts +8 -0
  162. package/dist/kernels/defaultKernelPathFromUrl.js +32 -0
  163. package/dist/kernels/kernelPack.d.ts +88 -0
  164. package/dist/kernels/kernelPack.js +122 -0
  165. package/dist/kernels/kernels.d.ts +98 -0
  166. package/dist/kernels/kernels.js +217 -0
  167. package/dist/kernels/naifKernelId.d.ts +2 -0
  168. package/dist/kernels/naifKernelId.js +2 -0
  169. package/dist/kit/math/mat3.d.ts +9 -1
  170. package/dist/kit/math/mat3.js +9 -1
  171. package/dist/kit/spice/create-kit.d.ts +1 -0
  172. package/dist/kit/spice/create-kit.js +1 -0
  173. package/dist/kit/spice/frames.d.ts +1 -0
  174. package/dist/kit/spice/frames.js +1 -0
  175. package/dist/kit/spice/kernels.d.ts +1 -0
  176. package/dist/kit/spice/kernels.js +1 -0
  177. package/dist/kit/spice/state.d.ts +2 -1
  178. package/dist/kit/spice/state.js +8 -4
  179. package/dist/kit/spice/time.d.ts +1 -0
  180. package/dist/kit/spice/time.js +1 -0
  181. package/dist/kit/types/spice-types.d.ts +18 -1
  182. package/dist/spice.d.ts +10 -1
  183. package/dist/spice.js +41 -0
  184. package/dist/transport/caching/policy.d.ts +16 -0
  185. package/dist/transport/caching/policy.js +77 -0
  186. package/dist/transport/caching/withCaching.d.ts +125 -0
  187. package/dist/transport/caching/withCaching.js +335 -0
  188. package/dist/transport/caching/withCachingSync.d.ts +24 -0
  189. package/dist/transport/caching/withCachingSync.js +161 -0
  190. package/dist/transport/rpc/protocol.d.ts +35 -0
  191. package/dist/transport/rpc/protocol.js +56 -0
  192. package/dist/transport/rpc/taskScheduling.d.ts +20 -0
  193. package/dist/transport/rpc/taskScheduling.js +98 -0
  194. package/dist/transport/rpc/valueCodec.d.ts +5 -0
  195. package/dist/transport/rpc/valueCodec.js +106 -0
  196. package/dist/transport/types.d.ts +7 -0
  197. package/dist/transport/types.js +2 -0
  198. package/dist/types.d.ts +8 -5
  199. package/dist/types.js +2 -1
  200. package/dist/worker/browser/createSpiceWorker.d.ts +22 -0
  201. package/dist/worker/browser/createSpiceWorker.js +41 -0
  202. package/dist/worker/browser/createSpiceWorkerClient.d.ts +40 -0
  203. package/dist/worker/browser/createSpiceWorkerClient.js +99 -0
  204. package/dist/worker/browser/spiceWorkerEntry.d.ts +2 -0
  205. package/dist/worker/browser/spiceWorkerEntry.js +129 -0
  206. package/dist/worker/browser/spiceWorkerInlineSource.d.ts +2 -0
  207. package/dist/worker/browser/spiceWorkerInlineSource.js +4 -0
  208. package/dist/worker/index.d.ts +10 -0
  209. package/dist/worker/index.js +7 -0
  210. package/dist/worker/transport/createWorkerTransport.d.ts +69 -0
  211. package/dist/worker/transport/createWorkerTransport.js +398 -0
  212. package/dist/worker/transport/exposeTransportToWorker.d.ts +51 -0
  213. package/dist/worker/transport/exposeTransportToWorker.js +196 -0
  214. package/package.json +4 -4
@@ -1,4 +1,4 @@
1
- import { brandMat3RowMajor } from "#backend-contract";
1
+ import { assertGetmsgWhich, assertSpiceInt32, brandMat3RowMajor, kxtrctJs, matchesKernelKind, normalizeBodItem, normalizeKindInput, } from "#backend-contract";
2
2
  /**
3
3
  * A deterministic, pure-TS "toy" backend.
4
4
  *
@@ -129,6 +129,20 @@ function angleBetween(a, b) {
129
129
  const c = clamp(vdot(a, b) / (na * nb), -1, 1);
130
130
  return Math.acos(c);
131
131
  }
132
+ function ellipsoidSurfaceNormal(spoint, radii) {
133
+ const [x, y, z] = spoint;
134
+ const [a, b, c] = radii;
135
+ // Gradient of x^2/a^2 + y^2/b^2 + z^2/c^2 = 1 is [x/a^2, y/b^2, z/c^2]
136
+ if (!Number.isFinite(a) || !Number.isFinite(b) || !Number.isFinite(c) || a === 0 || b === 0 || c === 0) {
137
+ // If we don't have radii, fall back to the sphere assumption.
138
+ return vhat(spoint);
139
+ }
140
+ return vhat([
141
+ x / (a * a),
142
+ y / (b * b),
143
+ z / (c * c),
144
+ ]);
145
+ }
132
146
  function canonicalizeZero(n) {
133
147
  return Object.is(n, -0) ? 0 : n;
134
148
  }
@@ -180,6 +194,140 @@ function mmul3(a, b) {
180
194
  label: "fake.mmul3",
181
195
  });
182
196
  }
197
+ function rotateRowMajor(angle, axis) {
198
+ // CSPICE reduces `iaxis` mod 3 (treating 0 as 3). Mirror that for parity.
199
+ if (!Number.isFinite(axis) || !Number.isInteger(axis)) {
200
+ throw new Error(`Fake backend: rotate(): invalid axis: ${axis} (expected a finite integer)`);
201
+ }
202
+ const iaxis = axis;
203
+ const reduced = ((iaxis % 3) + 3) % 3;
204
+ const spiceAxis = (reduced === 0 ? 3 : reduced);
205
+ const c = Math.cos(angle);
206
+ const s = Math.sin(angle);
207
+ switch (spiceAxis) {
208
+ case 1:
209
+ return brandMat3RowMajor([
210
+ 1,
211
+ 0,
212
+ 0,
213
+ 0,
214
+ canonicalizeZero(c),
215
+ canonicalizeZero(-s),
216
+ 0,
217
+ canonicalizeZero(s),
218
+ canonicalizeZero(c),
219
+ ], { label: "fake.rotate" });
220
+ case 2:
221
+ return brandMat3RowMajor([
222
+ canonicalizeZero(c),
223
+ 0,
224
+ canonicalizeZero(s),
225
+ 0,
226
+ 1,
227
+ 0,
228
+ canonicalizeZero(-s),
229
+ 0,
230
+ canonicalizeZero(c),
231
+ ], { label: "fake.rotate" });
232
+ case 3:
233
+ return brandMat3RowMajor([
234
+ canonicalizeZero(c),
235
+ canonicalizeZero(-s),
236
+ 0,
237
+ canonicalizeZero(s),
238
+ canonicalizeZero(c),
239
+ 0,
240
+ 0,
241
+ 0,
242
+ 1,
243
+ ], { label: "fake.rotate" });
244
+ default: {
245
+ const _exhaustive = spiceAxis;
246
+ throw new Error(`Fake backend: rotate(): unreachable axis: ${String(_exhaustive)}`);
247
+ }
248
+ }
249
+ }
250
+ function axisAngleToRotationRowMajor(axis, angle) {
251
+ const u = vhat(axis);
252
+ const ux = u[0];
253
+ const uy = u[1];
254
+ const uz = u[2];
255
+ // Parity with CSPICE axisar_c: if axis is zero, return identity rotation.
256
+ if (ux === 0 && uy === 0 && uz === 0) {
257
+ return brandMat3RowMajor([1, 0, 0, 0, 1, 0, 0, 0, 1], { label: "fake.axisar" });
258
+ }
259
+ const c = Math.cos(angle);
260
+ const s = Math.sin(angle);
261
+ const t = 1 - c;
262
+ return brandMat3RowMajor([
263
+ canonicalizeZero(t * ux * ux + c),
264
+ canonicalizeZero(t * ux * uy - s * uz),
265
+ canonicalizeZero(t * ux * uz + s * uy),
266
+ canonicalizeZero(t * ux * uy + s * uz),
267
+ canonicalizeZero(t * uy * uy + c),
268
+ canonicalizeZero(t * uy * uz - s * ux),
269
+ canonicalizeZero(t * ux * uz - s * uy),
270
+ canonicalizeZero(t * uy * uz + s * ux),
271
+ canonicalizeZero(t * uz * uz + c),
272
+ ], { label: "fake.axisar" });
273
+ }
274
+ function georec(lon, lat, alt, re, f) {
275
+ // Match CSPICE validation semantics where feasible.
276
+ if (!Number.isFinite(re) || re <= 0) {
277
+ throw new Error(`Fake backend: georec(): invalid re: ${re} (expected > 0)`);
278
+ }
279
+ if (!Number.isFinite(f) || f >= 1) {
280
+ throw new Error(`Fake backend: georec(): invalid f: ${f} (expected < 1)`);
281
+ }
282
+ // Standard geodetic-to-rectangular conversion.
283
+ const rp = re * (1 - f);
284
+ const e2 = 1 - (rp * rp) / (re * re);
285
+ const slat = Math.sin(lat);
286
+ const clat = Math.cos(lat);
287
+ const slon = Math.sin(lon);
288
+ const clon = Math.cos(lon);
289
+ const n = re / Math.sqrt(1 - e2 * slat * slat);
290
+ const x = (n + alt) * clat * clon;
291
+ const y = (n + alt) * clat * slon;
292
+ const z = (n * (1 - e2) + alt) * slat;
293
+ return [x, y, z];
294
+ }
295
+ function recgeo(rect, re, f) {
296
+ // Match CSPICE validation semantics where feasible.
297
+ if (!Number.isFinite(re) || re <= 0) {
298
+ throw new Error(`Fake backend: recgeo(): invalid re: ${re} (expected > 0)`);
299
+ }
300
+ if (!Number.isFinite(f) || f >= 1) {
301
+ throw new Error(`Fake backend: recgeo(): invalid f: ${f} (expected < 1)`);
302
+ }
303
+ // Bowring's method (non-iterative) for rectangular to geodetic.
304
+ const x = rect[0];
305
+ const y = rect[1];
306
+ const z = rect[2];
307
+ const rp = re * (1 - f);
308
+ const e2 = 1 - (rp * rp) / (re * re);
309
+ const ep2 = (re * re - rp * rp) / (rp * rp);
310
+ const lon = Math.atan2(y, x);
311
+ const p = Math.sqrt(x * x + y * y);
312
+ // Handle poles / near-pole numerical stability.
313
+ // Avoid the general-case `alt = p / cos(lat) - n` path when `p` is tiny.
314
+ // (Near-pole cases are extremely sensitive as `cos(lat) -> 0`.)
315
+ const poleTol = 1e-14 * re;
316
+ if (p <= poleTol) {
317
+ const poleLon = 0;
318
+ const poleLat = z >= 0 ? Math.PI / 2 : -Math.PI / 2;
319
+ const poleAlt = Math.abs(z) - rp;
320
+ return { lon: poleLon, lat: poleLat, alt: poleAlt };
321
+ }
322
+ const theta = Math.atan2(z * re, p * rp);
323
+ const st = Math.sin(theta);
324
+ const ct = Math.cos(theta);
325
+ const lat = Math.atan2(z + ep2 * rp * st * st * st, p - e2 * re * ct * ct * ct);
326
+ const slat = Math.sin(lat);
327
+ const n = re / Math.sqrt(1 - e2 * slat * slat);
328
+ const alt = p / Math.cos(lat) - n;
329
+ return { lon, lat, alt };
330
+ }
183
331
  function mtx3(m) {
184
332
  return brandMat3RowMajor([m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8]], { label: "fake.mtx3" });
185
333
  }
@@ -303,14 +451,18 @@ function formatUtcFromMs(ms, prec) {
303
451
  const padded = (frac + "000000000000").slice(0, clampedPrec);
304
452
  return `${head}.${padded}Z`;
305
453
  }
306
- function guessKernelKind(path) {
454
+ function guessKernelKind(path, unknownExtension) {
307
455
  const lower = path.toLowerCase();
308
456
  if (lower.endsWith(".bsp"))
309
457
  return "SPK";
310
458
  if (lower.endsWith(".bc"))
311
459
  return "CK";
312
- if (lower.endsWith(".tpc") || lower.endsWith(".pck"))
460
+ if (lower.endsWith(".bpc"))
313
461
  return "PCK";
462
+ if (lower.endsWith(".bds") || lower.endsWith(".dsk"))
463
+ return "DSK";
464
+ if (lower.endsWith(".tpc") || lower.endsWith(".pck"))
465
+ return "TEXT";
314
466
  if (lower.endsWith(".tls") || lower.endsWith(".lsk"))
315
467
  return "LSK";
316
468
  if (lower.endsWith(".tf") || lower.endsWith(".fk"))
@@ -319,9 +471,18 @@ function guessKernelKind(path) {
319
471
  return "IK";
320
472
  if (lower.endsWith(".tsc") || lower.endsWith(".sclk"))
321
473
  return "SCLK";
474
+ if (lower.endsWith(".ek"))
475
+ return "EK";
322
476
  if (lower.endsWith(".tm") || lower.endsWith(".meta"))
323
477
  return "META";
324
- return "ALL";
478
+ // "ALL" is a query token, not a per-kernel kind.
479
+ if (unknownExtension === "assume-text") {
480
+ return "TEXT";
481
+ }
482
+ throw new RangeError(`Unsupported kernel extension (fake backend): ${path}`);
483
+ }
484
+ function assertNever(x, msg) {
485
+ throw new Error(msg);
325
486
  }
326
487
  function kernelFiltyp(kind) {
327
488
  // Keep this close to NAIF-style strings, but it doesn't need to be exact.
@@ -329,33 +490,234 @@ function kernelFiltyp(kind) {
329
490
  case "SPK":
330
491
  case "CK":
331
492
  case "PCK":
493
+ case "DSK":
494
+ case "EK":
495
+ case "META":
496
+ case "TEXT":
497
+ return kind;
332
498
  case "LSK":
333
499
  case "FK":
334
500
  case "IK":
335
501
  case "SCLK":
336
- case "EK":
337
- case "META":
338
- return kind;
339
- case "ALL":
340
- default:
341
- return "ALL";
502
+ return "TEXT";
503
+ }
504
+ // Compile-time exhaustiveness check: if a new KernelKind is added, TypeScript
505
+ // forces us to intentionally map it.
506
+ return assertNever(kind, `Unmapped KernelKind: ${kind}`);
507
+ }
508
+ function normalizeVirtualKernelIdOrNull(input) {
509
+ // Keep lookup semantics aligned with the WASM backend's virtual-id
510
+ // normalization (see core's `normalizeVirtualKernelPath`).
511
+ //
512
+ // Unlike the core helper, this is non-throwing: invalid inputs just don't
513
+ // match.
514
+ const raw = input.replace(/\\/g, "/").trim();
515
+ if (!raw) {
516
+ return null;
517
+ }
518
+ // Strip leading slashes so `/kernels/foo.tls` behaves like `kernels/foo.tls`.
519
+ let rel = raw.replace(/^\/+/, "");
520
+ // Strip leading `./` segments.
521
+ while (rel.startsWith("./")) {
522
+ rel = rel.slice(2);
523
+ }
524
+ // Strip a leading `kernels/` directory to keep user input flexible.
525
+ // Treat a bare `kernels` segment as equivalent to `kernels/`.
526
+ if (rel === "kernels") {
527
+ rel = "";
528
+ }
529
+ while (rel.startsWith("kernels/")) {
530
+ rel = rel.replace(/^kernels\/+/, "");
531
+ }
532
+ const segments = rel.split("/");
533
+ const out = [];
534
+ for (const seg of segments) {
535
+ if (!seg || seg === ".") {
536
+ continue;
537
+ }
538
+ if (seg === "..") {
539
+ return null;
540
+ }
541
+ out.push(seg);
542
+ }
543
+ if (out.length === 0) {
544
+ return null;
545
+ }
546
+ return out.join("/");
547
+ }
548
+ function assertPoolRange(fn, start, room) {
549
+ if (!Number.isFinite(start) || !Number.isInteger(start) || start < 0) {
550
+ throw new RangeError(`${fn}(): start must be an integer >= 0`);
551
+ }
552
+ if (!Number.isFinite(room) || !Number.isInteger(room) || room <= 0) {
553
+ throw new RangeError(`${fn}(): room must be an integer > 0`);
554
+ }
555
+ }
556
+ function assertNonEmptyString(fn, field, value) {
557
+ if (value.trim().length === 0) {
558
+ throw new RangeError(`${fn}(): ${field} must be a non-empty string`);
342
559
  }
343
560
  }
344
- export function createFakeBackend() {
561
+ /**
562
+ * Create an in-memory fake backend implementing the {@link SpiceBackend} contract.
563
+ *
564
+ * Intended for tests and UI development without native/WASM backends.
565
+ */
566
+ export function createFakeBackend(options = {}) {
345
567
  let nextHandle = 1;
568
+ let spiceFailed = false;
569
+ let spiceShort = "";
570
+ let spiceLong = "";
571
+ const traceStack = [];
346
572
  const kernels = [];
573
+ const unknownExtension = options.unknownExtension ?? "throw";
574
+ const kernelPool = new Map();
575
+ // boddef() mappings (CSPICE uses process-global state; keep it per-backend here).
576
+ const customNameToBodyCode = new Map();
577
+ const customBodyCodeToName = new Map();
578
+ // swpool/cvpool "agent" state.
579
+ const kernelPoolWatches = new Map();
580
+ const kernelPoolWatchesByName = new Map();
581
+ const unindexKernelPoolWatch = (agent, names) => {
582
+ for (const name of names) {
583
+ const agents = kernelPoolWatchesByName.get(name);
584
+ if (!agents)
585
+ continue;
586
+ agents.delete(agent);
587
+ if (agents.size === 0) {
588
+ kernelPoolWatchesByName.delete(name);
589
+ }
590
+ }
591
+ };
592
+ const indexKernelPoolWatch = (agent, names) => {
593
+ for (const name of names) {
594
+ let agents = kernelPoolWatchesByName.get(name);
595
+ if (!agents) {
596
+ agents = new Set();
597
+ kernelPoolWatchesByName.set(name, agents);
598
+ }
599
+ agents.add(agent);
600
+ }
601
+ };
602
+ const markKernelPoolUpdated = (name) => {
603
+ // Avoid an O(watches * names) scan by maintaining a reverse index.
604
+ const agents = kernelPoolWatchesByName.get(name);
605
+ if (!agents)
606
+ return;
607
+ for (const agent of agents) {
608
+ const watch = kernelPoolWatches.get(agent);
609
+ if (watch) {
610
+ watch.dirty = true;
611
+ }
612
+ }
613
+ };
614
+ const spiceCellUnsupported = "Fake backend does not support SpiceCell/SpiceWindow APIs (use wasm/node backend).";
347
615
  const getKernelsOfKind = (kind) => {
348
- if (kind === "ALL")
349
- return kernels;
350
- return kernels.filter((k) => k.kind === kind);
616
+ const requested = new Set(normalizeKindInput(kind));
617
+ return kernels.filter((k) => matchesKernelKind(requested, k));
618
+ };
619
+ // Minimal state for timdef() GET/SET.
620
+ //
621
+ // The fake backend treats time systems deterministically and does not model
622
+ // leap seconds, so these defaults are mostly for parity with the contract
623
+ // and to support callers that expect timdef() to exist.
624
+ const DEFAULT_TIME_DEFAULTS = [
625
+ ["SYSTEM", "UTC"],
626
+ ["CALENDAR", "GREGORIAN"],
627
+ ["ZONE", "UTC"],
628
+ ];
629
+ const timeDefaults = new Map(DEFAULT_TIME_DEFAULTS);
630
+ function resetTimeDefaults() {
631
+ timeDefaults.clear();
632
+ for (const [k, v] of DEFAULT_TIME_DEFAULTS) {
633
+ timeDefaults.set(k, v);
634
+ }
635
+ }
636
+ function timdef(action, item, value) {
637
+ assertNonEmptyString("timdef", "item", item);
638
+ if (action === "GET") {
639
+ return timeDefaults.get(item) ?? "";
640
+ }
641
+ if (typeof value !== "string") {
642
+ throw new TypeError("timdef(SET) requires a string value");
643
+ }
644
+ // Match CSPICE `timdef_c`: empty (length 0) strings are invalid, but
645
+ // whitespace-only strings are allowed (e.g. ZONE can be "blank").
646
+ if (value.length === 0) {
647
+ throw new RangeError("timdef(SET): value must be a non-empty string");
648
+ }
649
+ timeDefaults.set(item, value);
650
+ }
651
+ const getBodyRadiiKm = (bodyId) => {
652
+ const key = `BODY${bodyId}_RADII`;
653
+ const entry = kernelPool.get(key);
654
+ if (entry?.type === "N" && entry.values.length >= 3) {
655
+ const a = entry.values[0];
656
+ const b = entry.values[1];
657
+ const c = entry.values[2];
658
+ if (typeof a === "number" &&
659
+ typeof b === "number" &&
660
+ typeof c === "number" &&
661
+ Number.isFinite(a) &&
662
+ Number.isFinite(b) &&
663
+ Number.isFinite(c)) {
664
+ return [a, b, c];
665
+ }
666
+ }
667
+ // Fall back to a spherical body when RADII isn't available.
668
+ const r = getBodyRadiusKm(bodyId);
669
+ return [r, r, r];
351
670
  };
352
- return {
671
+ const backend = {
353
672
  kind: "fake",
354
673
  spiceVersion: () => FAKE_SPICE_VERSION,
674
+ failed: () => spiceFailed,
675
+ reset: () => {
676
+ spiceFailed = false;
677
+ spiceShort = "";
678
+ spiceLong = "";
679
+ traceStack.length = 0;
680
+ resetTimeDefaults();
681
+ },
682
+ getmsg: (which) => {
683
+ assertGetmsgWhich(which);
684
+ if (which === "SHORT")
685
+ return spiceShort;
686
+ if (which === "LONG")
687
+ return spiceLong;
688
+ // EXPLAIN
689
+ // CSPICE convention is typically:
690
+ // setmsg(long)
691
+ // sigerr(short)
692
+ // so we avoid overwriting the long message when signaling.
693
+ const trace = traceStack.length > 0 ? traceStack.join(" -> ") : "";
694
+ if (!spiceLong && !trace)
695
+ return "";
696
+ if (spiceLong && !trace)
697
+ return spiceLong;
698
+ if (!spiceLong && trace)
699
+ return `Trace: ${trace}`;
700
+ return `${spiceLong}\n\nTrace: ${trace}`;
701
+ },
702
+ setmsg: (message) => {
703
+ spiceLong = message;
704
+ },
705
+ sigerr: (short) => {
706
+ spiceFailed = true;
707
+ spiceShort = short;
708
+ },
709
+ chkin: (name) => {
710
+ traceStack.push(name);
711
+ },
712
+ chkout: (name) => {
713
+ const idx = traceStack.lastIndexOf(name);
714
+ if (idx >= 0)
715
+ traceStack.splice(idx, 1);
716
+ },
355
717
  furnsh: (kernel) => {
356
718
  const file = typeof kernel === "string" ? kernel : kernel.path;
357
719
  const source = typeof kernel === "string" ? file : "bytes";
358
- const kind = guessKernelKind(file);
720
+ const kind = guessKernelKind(file, unknownExtension);
359
721
  const handle = nextHandle++;
360
722
  kernels.push({
361
723
  file,
@@ -366,13 +728,42 @@ export function createFakeBackend() {
366
728
  });
367
729
  },
368
730
  unload: (path) => {
369
- const idx = kernels.findIndex((k) => k.file === path);
731
+ const needle = normalizeVirtualKernelIdOrNull(path);
732
+ if (needle == null) {
733
+ return;
734
+ }
735
+ const idx = kernels.findIndex((k) => normalizeVirtualKernelIdOrNull(k.file) === needle);
370
736
  if (idx >= 0) {
371
737
  kernels.splice(idx, 1);
372
738
  }
373
739
  },
374
740
  kclear: () => {
375
741
  kernels.length = 0;
742
+ kernelPool.clear();
743
+ kernelPoolWatches.clear();
744
+ kernelPoolWatchesByName.clear();
745
+ },
746
+ kinfo: (path) => {
747
+ const needle = normalizeVirtualKernelIdOrNull(path);
748
+ if (needle == null) {
749
+ return { found: false };
750
+ }
751
+ const k = kernels.find((k) => normalizeVirtualKernelIdOrNull(k.file) === needle);
752
+ if (!k) {
753
+ return { found: false };
754
+ }
755
+ return {
756
+ found: true,
757
+ filtyp: k.filtyp,
758
+ source: k.source,
759
+ handle: k.handle,
760
+ };
761
+ },
762
+ kxtrct: (keywd, terms, wordsq) => {
763
+ return kxtrctJs(keywd, terms, wordsq);
764
+ },
765
+ kplfrm: (_frmcls, _idset) => {
766
+ throw new Error(spiceCellUnsupported);
376
767
  },
377
768
  ktotal: (kind = "ALL") => {
378
769
  return getKernelsOfKind(kind).length;
@@ -390,6 +781,163 @@ export function createFakeBackend() {
390
781
  handle: k.handle,
391
782
  };
392
783
  },
784
+ gdpool: (name, start, room) => {
785
+ assertNonEmptyString("gdpool", "name", name);
786
+ assertPoolRange("gdpool", start, room);
787
+ const start0 = start;
788
+ const room0 = room;
789
+ const entry = kernelPool.get(name);
790
+ if (!entry)
791
+ return { found: false };
792
+ if (entry.type !== "N") {
793
+ throw new Error(`Fake backend: gdpool only supports numeric variables (got ${entry.type})`);
794
+ }
795
+ return {
796
+ found: true,
797
+ values: entry.values.slice(start0, start0 + room0),
798
+ };
799
+ },
800
+ gipool: (name, start, room) => {
801
+ assertNonEmptyString("gipool", "name", name);
802
+ assertPoolRange("gipool", start, room);
803
+ const start0 = start;
804
+ const room0 = room;
805
+ const entry = kernelPool.get(name);
806
+ if (!entry)
807
+ return { found: false };
808
+ if (entry.type !== "N") {
809
+ throw new Error(`Fake backend: gipool only supports numeric variables (got ${entry.type})`);
810
+ }
811
+ return {
812
+ found: true,
813
+ values: entry.values.slice(start0, start0 + room0).map((v, i) => {
814
+ assertSpiceInt32(v, `gipool(): values[${start0 + i}]`);
815
+ return v;
816
+ }),
817
+ };
818
+ },
819
+ gcpool: (name, start, room) => {
820
+ assertNonEmptyString("gcpool", "name", name);
821
+ assertPoolRange("gcpool", start, room);
822
+ const start0 = start;
823
+ const room0 = room;
824
+ const entry = kernelPool.get(name);
825
+ if (!entry)
826
+ return { found: false };
827
+ if (entry.type !== "C") {
828
+ throw new Error(`Fake backend: gcpool only supports character variables (got ${entry.type})`);
829
+ }
830
+ return {
831
+ found: true,
832
+ values: entry.values.slice(start0, start0 + room0),
833
+ };
834
+ },
835
+ gnpool: (template, start, room) => {
836
+ assertNonEmptyString("gnpool", "template", template);
837
+ assertPoolRange("gnpool", start, room);
838
+ const start0 = start;
839
+ const room0 = room;
840
+ const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\[\]\\]/g, "\\$&");
841
+ // Support escaping wildcard characters via backslash:
842
+ // - `\*` matches a literal `*`
843
+ // - `\%` matches a literal `%`
844
+ let reSrc = "^";
845
+ for (let i = 0; i < template.length; i++) {
846
+ const ch = template[i];
847
+ if (ch === "\\") {
848
+ const next = template[i + 1];
849
+ if (next !== undefined) {
850
+ reSrc += escapeRegex(next);
851
+ i++;
852
+ }
853
+ else {
854
+ reSrc += "\\\\";
855
+ }
856
+ continue;
857
+ }
858
+ if (ch === "*") {
859
+ reSrc += ".*";
860
+ continue;
861
+ }
862
+ if (ch === "%") {
863
+ reSrc += ".";
864
+ continue;
865
+ }
866
+ reSrc += escapeRegex(ch);
867
+ }
868
+ reSrc += "$";
869
+ const re = new RegExp(reSrc);
870
+ const matches = Array.from(kernelPool.keys()).filter((k) => re.test(k)).sort();
871
+ if (matches.length === 0) {
872
+ return { found: false };
873
+ }
874
+ return {
875
+ found: true,
876
+ values: matches.slice(start0, start0 + room0),
877
+ };
878
+ },
879
+ dtpool: (name) => {
880
+ assertNonEmptyString("dtpool", "name", name);
881
+ const entry = kernelPool.get(name);
882
+ if (!entry)
883
+ return { found: false };
884
+ return {
885
+ found: true,
886
+ n: entry.values.length,
887
+ type: entry.type,
888
+ };
889
+ },
890
+ pdpool: (name, values) => {
891
+ assertNonEmptyString("pdpool", "name", name);
892
+ for (let i = 0; i < values.length; i++) {
893
+ const v = values[i];
894
+ if (!Number.isFinite(v)) {
895
+ throw new RangeError(`pdpool(): values[${i}] must be a finite number (got ${String(v)})`);
896
+ }
897
+ }
898
+ kernelPool.set(name, { type: "N", values: [...values] });
899
+ markKernelPoolUpdated(name);
900
+ },
901
+ pipool: (name, values) => {
902
+ assertNonEmptyString("pipool", "name", name);
903
+ for (let i = 0; i < values.length; i++) {
904
+ assertSpiceInt32(values[i], `pipool(): values[${i}]`);
905
+ }
906
+ kernelPool.set(name, { type: "N", values: [...values] });
907
+ markKernelPoolUpdated(name);
908
+ },
909
+ pcpool: (name, values) => {
910
+ assertNonEmptyString("pcpool", "name", name);
911
+ kernelPool.set(name, { type: "C", values: [...values] });
912
+ markKernelPoolUpdated(name);
913
+ },
914
+ swpool: (agent, names) => {
915
+ assertNonEmptyString("swpool", "agent", agent);
916
+ for (let i = 0; i < names.length; i++) {
917
+ assertNonEmptyString("swpool", `names[${i}]`, names[i]);
918
+ }
919
+ // CSPICE guarantees the next cvpool(agent) returns true.
920
+ const prev = kernelPoolWatches.get(agent);
921
+ if (prev) {
922
+ unindexKernelPoolWatch(agent, prev.names);
923
+ }
924
+ kernelPoolWatches.set(agent, { names: [...names], dirty: true });
925
+ indexKernelPoolWatch(agent, names);
926
+ },
927
+ cvpool: (agent) => {
928
+ assertNonEmptyString("cvpool", "agent", agent);
929
+ const watch = kernelPoolWatches.get(agent);
930
+ if (!watch)
931
+ return false;
932
+ const dirty = watch.dirty;
933
+ watch.dirty = false;
934
+ return dirty;
935
+ },
936
+ expool: (name) => {
937
+ assertNonEmptyString("expool", "name", name);
938
+ const entry = kernelPool.get(name);
939
+ return entry?.type === "N";
940
+ },
393
941
  tkvrsn: (item) => {
394
942
  if (item !== "TOOLKIT") {
395
943
  throw new Error(`Fake backend: unsupported tkvrsn item: ${String(item)}`);
@@ -415,19 +963,102 @@ export function createFakeBackend() {
415
963
  const ms = J2000_UTC_MS + et * 1000;
416
964
  return formatUtcFromMs(ms, 3);
417
965
  },
966
+ deltet: (_epoch, _eptype) => {
967
+ // Deterministic stub: the fake backend ignores leap seconds, so ET and
968
+ // UTC are treated as identical.
969
+ return 0;
970
+ },
971
+ unitim: (epoch, _insys, _outsys) => {
972
+ // Deterministic stub: treat all input/output systems as identical.
973
+ return epoch;
974
+ },
975
+ tparse: (timstr) => {
976
+ // Deterministic stub: match `str2et` semantics in the fake backend.
977
+ if (!isIso8601OrRfc3339Utcish(timstr)) {
978
+ throw new Error(`Fake backend: tparse() only supports ISO-8601/RFC3339 timestamps (got ${JSON.stringify(timstr)})`);
979
+ }
980
+ const ms = Date.parse(timstr);
981
+ if (!Number.isFinite(ms)) {
982
+ throw new Error(`Fake backend: failed to parse time: ${JSON.stringify(timstr)}`);
983
+ }
984
+ return (ms - J2000_UTC_MS) / 1000;
985
+ },
986
+ tpictr: (sample, pictur) => {
987
+ if (sample.length === 0) {
988
+ throw new RangeError("tpictr(): sample must be a non-empty string");
989
+ }
990
+ if (pictur.length === 0) {
991
+ throw new RangeError("tpictr(): pictur must be a non-empty string");
992
+ }
993
+ // Picture transformation is out of scope for the fake backend.
994
+ return pictur;
995
+ },
996
+ timdef,
418
997
  bodn2c: (name) => {
419
998
  const trimmed = normalizeName(name);
999
+ const custom = customNameToBodyCode.get(trimmed) ??
1000
+ customNameToBodyCode.get(trimmed.toLowerCase());
1001
+ if (custom !== undefined)
1002
+ return { found: true, code: custom };
420
1003
  const id = NAME_TO_ID.get(trimmed) ?? NAME_TO_ID.get(trimmed.toLowerCase());
421
1004
  if (id === undefined)
422
1005
  return { found: false };
423
1006
  return { found: true, code: id };
424
1007
  },
425
1008
  bodc2n: (code) => {
1009
+ const custom = customBodyCodeToName.get(code);
1010
+ if (custom !== undefined)
1011
+ return { found: true, name: custom };
426
1012
  const meta = ID_TO_BODY.get(code);
427
1013
  if (!meta)
428
1014
  return { found: false };
429
1015
  return { found: true, name: meta.name };
430
1016
  },
1017
+ bodc2s: (code) => {
1018
+ const custom = customBodyCodeToName.get(code);
1019
+ if (custom !== undefined)
1020
+ return custom;
1021
+ const meta = ID_TO_BODY.get(code);
1022
+ if (meta)
1023
+ return meta.name;
1024
+ return String(code);
1025
+ },
1026
+ bods2c: (name) => {
1027
+ const trimmed = normalizeName(name);
1028
+ // Accept numeric IDs as strings.
1029
+ if (/^-?\d+$/.test(trimmed)) {
1030
+ return { found: true, code: Number(trimmed) };
1031
+ }
1032
+ const custom = customNameToBodyCode.get(trimmed) ??
1033
+ customNameToBodyCode.get(trimmed.toLowerCase());
1034
+ if (custom !== undefined)
1035
+ return { found: true, code: custom };
1036
+ const id = NAME_TO_ID.get(trimmed) ?? NAME_TO_ID.get(trimmed.toLowerCase());
1037
+ if (id === undefined)
1038
+ return { found: false };
1039
+ return { found: true, code: id };
1040
+ },
1041
+ boddef: (name, code) => {
1042
+ const trimmed = normalizeName(name);
1043
+ customNameToBodyCode.set(trimmed, code);
1044
+ customNameToBodyCode.set(trimmed.toLowerCase(), code);
1045
+ customBodyCodeToName.set(code, trimmed);
1046
+ },
1047
+ bodfnd: (body, item) => {
1048
+ const key = `BODY${body}_${normalizeBodItem(item)}`;
1049
+ const entry = kernelPool.get(key);
1050
+ return entry !== undefined;
1051
+ },
1052
+ bodvar: (body, item) => {
1053
+ const key = `BODY${body}_${normalizeBodItem(item)}`;
1054
+ const entry = kernelPool.get(key);
1055
+ if (!entry || entry.type !== "N") {
1056
+ // Align with the backend contract: missing / non-numeric pool vars are a normal miss.
1057
+ // Callers that need strict presence checks can use `bodfnd()`.
1058
+ return [];
1059
+ }
1060
+ return [...entry.values];
1061
+ },
431
1062
  namfrm: (name) => {
432
1063
  const trimmed = normalizeName(name);
433
1064
  const code = FRAME_NAME_TO_CODE.get(trimmed) ?? FRAME_NAME_TO_CODE.get(trimmed.toLowerCase());
@@ -460,6 +1091,21 @@ export function createFakeBackend() {
460
1091
  ? { found: true, frcode: FRAME_CODES.IAU_MOON, frname: "IAU_MOON" }
461
1092
  : { found: false });
462
1093
  },
1094
+ frinfo: (frameId) => {
1095
+ if (frameId === FRAME_CODES.J2000) {
1096
+ return { found: true, center: 0, frameClass: 1, classId: frameId };
1097
+ }
1098
+ return { found: false };
1099
+ },
1100
+ ccifrm: (frameClass, classId) => {
1101
+ if (frameClass === 1) {
1102
+ const frname = FRAME_CODE_TO_NAME.get(classId);
1103
+ if (!frname)
1104
+ return { found: false };
1105
+ return { found: true, frcode: classId, frname, center: 0 };
1106
+ }
1107
+ return { found: false };
1108
+ },
463
1109
  scs2e: (_sc, sclkch) => {
464
1110
  // Minimal deterministic stub: treat the string as a number of seconds.
465
1111
  const n = Number(sclkch);
@@ -469,12 +1115,41 @@ export function createFakeBackend() {
469
1115
  // Minimal deterministic stub.
470
1116
  return String(et);
471
1117
  },
1118
+ scencd: (_sc, sclkch) => {
1119
+ // Minimal deterministic stub: treat the string as a number of ticks.
1120
+ const n = Number(sclkch);
1121
+ return Number.isFinite(n) ? n : 0;
1122
+ },
1123
+ scdecd: (_sc, sclkdp) => {
1124
+ // Minimal deterministic stub.
1125
+ return String(sclkdp);
1126
+ },
1127
+ sct2e: (_sc, sclkdp) => {
1128
+ // Minimal deterministic stub.
1129
+ return sclkdp;
1130
+ },
1131
+ sce2c: (_sc, et) => {
1132
+ // Minimal deterministic stub.
1133
+ return et;
1134
+ },
472
1135
  ckgp: (_inst, _sclkdp, _tol, _ref) => {
473
1136
  return { found: false };
474
1137
  },
475
1138
  ckgpav: (_inst, _sclkdp, _tol, _ref) => {
476
1139
  return { found: false };
477
1140
  },
1141
+ cklpf: (_ck) => {
1142
+ throw new Error("Fake backend: cklpf() is not implemented");
1143
+ },
1144
+ ckupf: (_handle) => {
1145
+ throw new Error("Fake backend: ckupf() is not implemented");
1146
+ },
1147
+ ckobj: (_ck, _ids) => {
1148
+ throw new Error("Fake backend: ckobj() is not implemented");
1149
+ },
1150
+ ckcov: (_ck, _idcode, _needav, _level, _tol, _timsys, _cover) => {
1151
+ throw new Error("Fake backend: ckcov() is not implemented");
1152
+ },
478
1153
  pxform: (from, to, et) => {
479
1154
  const f = parseFrameName(from);
480
1155
  const t = parseFrameName(to);
@@ -513,6 +1188,197 @@ export function createFakeBackend() {
513
1188
  lt: 0,
514
1189
  };
515
1190
  },
1191
+ spkez: (target, et, ref, abcorr, observer) => {
1192
+ void abcorr;
1193
+ assertSpiceInt32(target, "spkez(target)");
1194
+ assertSpiceInt32(observer, "spkez(observer)");
1195
+ const stateJ2000 = getRelativeStateInJ2000(String(target), String(observer), et);
1196
+ const outFrame = parseFrameName(ref);
1197
+ const state = outFrame === "J2000" ? stateJ2000 : applyStateTransform("J2000", outFrame, et, stateJ2000);
1198
+ return { state, lt: 0 };
1199
+ },
1200
+ spkezp: (target, et, ref, abcorr, observer) => {
1201
+ void abcorr;
1202
+ assertSpiceInt32(target, "spkezp(target)");
1203
+ assertSpiceInt32(observer, "spkezp(observer)");
1204
+ const stateJ2000 = getRelativeStateInJ2000(String(target), String(observer), et);
1205
+ const outFrame = parseFrameName(ref);
1206
+ const state = outFrame === "J2000" ? stateJ2000 : applyStateTransform("J2000", outFrame, et, stateJ2000);
1207
+ return {
1208
+ pos: [state[0], state[1], state[2]],
1209
+ lt: 0,
1210
+ };
1211
+ },
1212
+ spkgeo: (target, et, ref, observer) => {
1213
+ assertSpiceInt32(target, "spkgeo(target)");
1214
+ assertSpiceInt32(observer, "spkgeo(observer)");
1215
+ const stateJ2000 = getRelativeStateInJ2000(String(target), String(observer), et);
1216
+ const outFrame = parseFrameName(ref);
1217
+ const state = outFrame === "J2000" ? stateJ2000 : applyStateTransform("J2000", outFrame, et, stateJ2000);
1218
+ return { state, lt: 0 };
1219
+ },
1220
+ spkgps: (target, et, ref, observer) => {
1221
+ assertSpiceInt32(target, "spkgps(target)");
1222
+ assertSpiceInt32(observer, "spkgps(observer)");
1223
+ const stateJ2000 = getRelativeStateInJ2000(String(target), String(observer), et);
1224
+ const outFrame = parseFrameName(ref);
1225
+ const state = outFrame === "J2000" ? stateJ2000 : applyStateTransform("J2000", outFrame, et, stateJ2000);
1226
+ return {
1227
+ pos: [state[0], state[1], state[2]],
1228
+ lt: 0,
1229
+ };
1230
+ },
1231
+ spkssb: (target, et, ref) => {
1232
+ assertSpiceInt32(target, "spkssb(target)");
1233
+ const abs = getAbsoluteStateInJ2000(target, et);
1234
+ const stateJ2000 = [
1235
+ abs.posKm[0],
1236
+ abs.posKm[1],
1237
+ abs.posKm[2],
1238
+ abs.velKmPerSec[0],
1239
+ abs.velKmPerSec[1],
1240
+ abs.velKmPerSec[2],
1241
+ ];
1242
+ const outFrame = parseFrameName(ref);
1243
+ return outFrame === "J2000" ? stateJ2000 : applyStateTransform("J2000", outFrame, et, stateJ2000);
1244
+ },
1245
+ spkcov: (_spk, idcode, _cover) => {
1246
+ assertSpiceInt32(idcode, "spkcov(idcode)");
1247
+ throw new Error(spiceCellUnsupported);
1248
+ },
1249
+ spkobj: (_spk, _ids) => {
1250
+ throw new Error(spiceCellUnsupported);
1251
+ },
1252
+ illumg: (_method, target, ilusrc, et, fixref, abcorr, observer, spoint) => {
1253
+ void abcorr;
1254
+ const frame = parseFrameName(fixref);
1255
+ const inv = rotZRowMajor(FRAME_SPIN_RATE_RAD_PER_SEC[frame] * et);
1256
+ const spointJ = frame === "J2000" ? spoint : mxv(inv, spoint);
1257
+ const srcState = getRelativeStateInJ2000(ilusrc, target, et);
1258
+ const srcPosJ = [srcState[0], srcState[1], srcState[2]];
1259
+ const obsState = getRelativeStateInJ2000(observer, target, et);
1260
+ const obsPosJ = [obsState[0], obsState[1], obsState[2]];
1261
+ // Vectors from surface point.
1262
+ const srfToSrcJ = vsub(srcPosJ, spointJ);
1263
+ const srfToObsJ = vsub(obsPosJ, spointJ);
1264
+ const targetId = parseBodyRef(target);
1265
+ const normalF = ellipsoidSurfaceNormal(spoint, getBodyRadiiKm(targetId));
1266
+ const normalJ = frame === "J2000" ? normalF : mxv(inv, normalF);
1267
+ const phase = angleBetween(srfToSrcJ, srfToObsJ);
1268
+ const incdnc = angleBetween(normalJ, srfToSrcJ);
1269
+ const emissn = angleBetween(normalJ, srfToObsJ);
1270
+ const srfvecJ = vsub(spointJ, obsPosJ);
1271
+ const srfvec = frame === "J2000" ? srfvecJ : mxv(rotZRowMajor(-FRAME_SPIN_RATE_RAD_PER_SEC[frame] * et), srfvecJ);
1272
+ return {
1273
+ trgepc: et,
1274
+ srfvec,
1275
+ phase,
1276
+ incdnc,
1277
+ emissn,
1278
+ };
1279
+ },
1280
+ illumf: (_method, target, ilusrc, et, fixref, abcorr, observer, spoint) => {
1281
+ void abcorr;
1282
+ const frame = parseFrameName(fixref);
1283
+ const inv = rotZRowMajor(FRAME_SPIN_RATE_RAD_PER_SEC[frame] * et);
1284
+ const spointJ = frame === "J2000" ? spoint : mxv(inv, spoint);
1285
+ const srcState = getRelativeStateInJ2000(ilusrc, target, et);
1286
+ const srcPosJ = [srcState[0], srcState[1], srcState[2]];
1287
+ const obsState = getRelativeStateInJ2000(observer, target, et);
1288
+ const obsPosJ = [obsState[0], obsState[1], obsState[2]];
1289
+ // Vectors from surface point.
1290
+ const srfToSrcJ = vsub(srcPosJ, spointJ);
1291
+ const srfToObsJ = vsub(obsPosJ, spointJ);
1292
+ const targetId = parseBodyRef(target);
1293
+ const normalF = ellipsoidSurfaceNormal(spoint, getBodyRadiiKm(targetId));
1294
+ const normalJ = frame === "J2000" ? normalF : mxv(inv, normalF);
1295
+ const phase = angleBetween(srfToSrcJ, srfToObsJ);
1296
+ const incdnc = angleBetween(normalJ, srfToSrcJ);
1297
+ const emissn = angleBetween(normalJ, srfToObsJ);
1298
+ const srfvecJ = vsub(spointJ, obsPosJ);
1299
+ const srfvec = frame === "J2000" ? srfvecJ : mxv(rotZRowMajor(-FRAME_SPIN_RATE_RAD_PER_SEC[frame] * et), srfvecJ);
1300
+ const out = {
1301
+ trgepc: et,
1302
+ srfvec,
1303
+ phase,
1304
+ incdnc,
1305
+ emissn,
1306
+ };
1307
+ // NOTE: This fake backend defines:
1308
+ // - visibl: point is visible if emission angle is <= 90deg
1309
+ // - lit: point is lit if incidence angle is <= 90deg
1310
+ const visibl = out.emissn <= Math.PI / 2;
1311
+ const lit = out.incdnc <= Math.PI / 2;
1312
+ return { ...out, visibl, lit };
1313
+ },
1314
+ spksfs: (body, et) => {
1315
+ assertSpiceInt32(body, "spksfs(body)");
1316
+ void et;
1317
+ return { found: false };
1318
+ },
1319
+ spkpds: (_body, _center, _frame, _type, _first, _last) => {
1320
+ throw new Error("Fake backend: spkpds() is not implemented");
1321
+ },
1322
+ spkuds: (_descr) => {
1323
+ throw new Error("Fake backend: spkuds() is not implemented");
1324
+ },
1325
+ nvc2pl: (normal, konst) => {
1326
+ if (!Array.isArray(normal) || normal.length !== 3) {
1327
+ throw new TypeError("nvc2pl(normal, konst): normal must be a length-3 number[]");
1328
+ }
1329
+ for (let i = 0; i < 3; i++) {
1330
+ const v = normal[i];
1331
+ if (typeof v !== "number") {
1332
+ throw new TypeError(`nvc2pl(normal, konst): normal[${i}] must be a number`);
1333
+ }
1334
+ if (!Number.isFinite(v)) {
1335
+ throw new RangeError(`nvc2pl(normal, konst): normal[${i}] must be a finite number`);
1336
+ }
1337
+ }
1338
+ if (typeof konst !== "number") {
1339
+ throw new TypeError("nvc2pl(normal, konst): konst must be a number");
1340
+ }
1341
+ if (!Number.isFinite(konst)) {
1342
+ throw new RangeError("nvc2pl(normal, konst): konst must be a finite number");
1343
+ }
1344
+ return [normal[0], normal[1], normal[2], konst];
1345
+ },
1346
+ pl2nvc: (plane) => {
1347
+ if (!Array.isArray(plane) || plane.length !== 4) {
1348
+ throw new TypeError("pl2nvc(plane): plane must be a length-4 number[]");
1349
+ }
1350
+ for (let i = 0; i < 4; i++) {
1351
+ const v = plane[i];
1352
+ if (typeof v !== "number") {
1353
+ throw new TypeError(`pl2nvc(plane): plane[${i}] must be a number`);
1354
+ }
1355
+ if (!Number.isFinite(v)) {
1356
+ throw new RangeError(`pl2nvc(plane): plane[${i}] must be a finite number`);
1357
+ }
1358
+ }
1359
+ const n = [plane[0], plane[1], plane[2]];
1360
+ const mag = vnorm(n);
1361
+ if (mag === 0) {
1362
+ throw new RangeError("pl2nvc(plane): plane is degenerate (normal must be non-zero)");
1363
+ }
1364
+ return {
1365
+ normal: vscale(1 / mag, n),
1366
+ konst: plane[3] / mag,
1367
+ };
1368
+ },
1369
+ // --- SPK writers (not implemented in fake backend) ---
1370
+ spkopn: (_file, _ifname, _ncomch) => {
1371
+ throw new Error("Fake backend: spkopn() is not implemented");
1372
+ },
1373
+ spkopa: (_file) => {
1374
+ throw new Error("Fake backend: spkopa() is not implemented");
1375
+ },
1376
+ spkcls: (_handle) => {
1377
+ throw new Error("Fake backend: spkcls() is not implemented");
1378
+ },
1379
+ spkw08: (_handle, _body, _center, _frame, _first, _last, _segid, _degree, _states, _epoch1, _step) => {
1380
+ throw new Error("Fake backend: spkw08() is not implemented");
1381
+ },
516
1382
  subpnt: (_method, target, et, fixref, abcorr, observer) => {
517
1383
  void abcorr;
518
1384
  const targetId = parseBodyRef(target);
@@ -556,7 +1422,9 @@ export function createFakeBackend() {
556
1422
  // Vectors from surface point.
557
1423
  const srfToSunJ = vsub(sunPosJ, spointJ);
558
1424
  const srfToObsJ = vsub(obsPosJ, spointJ);
559
- const normalJ = vhat(spointJ);
1425
+ const targetId = parseBodyRef(target);
1426
+ const normalF = ellipsoidSurfaceNormal(spoint, getBodyRadiiKm(targetId));
1427
+ const normalJ = frame === "J2000" ? normalF : mxv(inv, normalF);
560
1428
  const phase = angleBetween(srfToSunJ, srfToObsJ);
561
1429
  const incdnc = angleBetween(normalJ, srfToSunJ);
562
1430
  const emissn = angleBetween(normalJ, srfToObsJ);
@@ -575,6 +1443,211 @@ export function createFakeBackend() {
575
1443
  // Deterministic stub: 0 => "no occultation".
576
1444
  return 0;
577
1445
  },
1446
+ // --- GF (Geometry Finder) (not implemented in fake backend) ---
1447
+ gfsstp: (_step) => {
1448
+ throw new Error("Fake backend: gfsstp() is not implemented");
1449
+ },
1450
+ gfstep: (_time) => {
1451
+ throw new Error("Fake backend: gfstep() is not implemented");
1452
+ },
1453
+ gfstol: (_value) => {
1454
+ throw new Error("Fake backend: gfstol() is not implemented");
1455
+ },
1456
+ gfrefn: (_t1, _t2, _s1, _s2) => {
1457
+ throw new Error("Fake backend: gfrefn() is not implemented");
1458
+ },
1459
+ gfrepi: (_window, _begmss, _endmss) => {
1460
+ throw new Error("Fake backend: gfrepi() is not implemented");
1461
+ },
1462
+ gfrepf: () => {
1463
+ throw new Error("Fake backend: gfrepf() is not implemented");
1464
+ },
1465
+ gfsep: (_targ1, _shape1, _frame1, _targ2, _shape2, _frame2, _abcorr, _obsrvr, _relate, _refval, _adjust, _step, _nintvls, _cnfine, _result) => {
1466
+ throw new Error("Fake backend: gfsep() is not implemented");
1467
+ },
1468
+ gfdist: (_target, _abcorr, _obsrvr, _relate, _refval, _adjust, _step, _nintvls, _cnfine, _result) => {
1469
+ throw new Error("Fake backend: gfdist() is not implemented");
1470
+ },
1471
+ // --- file i/o primitives (not implemented in fake backend) ---
1472
+ exists: (_path) => {
1473
+ throw new Error("Fake backend: exists() is not implemented");
1474
+ },
1475
+ getfat: (_path) => {
1476
+ throw new Error("Fake backend: getfat() is not implemented");
1477
+ },
1478
+ readVirtualOutput: (_output) => {
1479
+ throw new Error("Fake backend: readVirtualOutput() is not implemented");
1480
+ },
1481
+ dafopr: (_path) => {
1482
+ throw new Error("Fake backend: dafopr() is not implemented");
1483
+ },
1484
+ dafcls: (_handle) => {
1485
+ throw new Error("Fake backend: dafcls() is not implemented");
1486
+ },
1487
+ dafbfs: (_handle) => {
1488
+ throw new Error("Fake backend: dafbfs() is not implemented");
1489
+ },
1490
+ daffna: (_handle) => {
1491
+ throw new Error("Fake backend: daffna() is not implemented");
1492
+ },
1493
+ dasopr: (_path) => {
1494
+ throw new Error("Fake backend: dasopr() is not implemented");
1495
+ },
1496
+ dascls: (_handle) => {
1497
+ throw new Error("Fake backend: dascls() is not implemented");
1498
+ },
1499
+ dlaopn: (_path, _ftype, _ifname, _ncomch) => {
1500
+ throw new Error("Fake backend: dlaopn() is not implemented");
1501
+ },
1502
+ dlabfs: (_handle) => {
1503
+ throw new Error("Fake backend: dlabfs() is not implemented");
1504
+ },
1505
+ dlafns: (_handle, _descr) => {
1506
+ throw new Error("Fake backend: dlafns() is not implemented");
1507
+ },
1508
+ dlacls: (_handle) => {
1509
+ throw new Error("Fake backend: dlacls() is not implemented");
1510
+ },
1511
+ // --- EK (not implemented in fake backend) ---
1512
+ ekopr: (_path) => {
1513
+ throw new Error("Fake backend: ekopr() is not implemented");
1514
+ },
1515
+ ekopw: (_path) => {
1516
+ throw new Error("Fake backend: ekopw() is not implemented");
1517
+ },
1518
+ ekopn: (_path, _ifname, _ncomch) => {
1519
+ throw new Error("Fake backend: ekopn() is not implemented");
1520
+ },
1521
+ ekcls: (_handle) => {
1522
+ throw new Error("Fake backend: ekcls() is not implemented");
1523
+ },
1524
+ ekntab: () => {
1525
+ throw new Error("Fake backend: ekntab() is not implemented");
1526
+ },
1527
+ ektnam: (_n) => {
1528
+ throw new Error("Fake backend: ektnam() is not implemented");
1529
+ },
1530
+ eknseg: (_handle) => {
1531
+ throw new Error("Fake backend: eknseg() is not implemented");
1532
+ },
1533
+ // --- EK query/data ops (not implemented in fake backend) ---
1534
+ ekfind: (_query) => {
1535
+ throw new Error("Fake backend: ekfind() is not implemented");
1536
+ },
1537
+ ekgc: (_selidx, _row, _elment) => {
1538
+ throw new Error("Fake backend: ekgc() is not implemented");
1539
+ },
1540
+ ekgd: (_selidx, _row, _elment) => {
1541
+ throw new Error("Fake backend: ekgd() is not implemented");
1542
+ },
1543
+ ekgi: (_selidx, _row, _elment) => {
1544
+ throw new Error("Fake backend: ekgi() is not implemented");
1545
+ },
1546
+ ekifld: (_handle, _tabnam, _nrows, _cnames, _decls) => {
1547
+ throw new Error("Fake backend: ekifld() is not implemented");
1548
+ },
1549
+ ekacli: (_handle, _segno, _column, _ivals, _entszs, _nlflgs, _rcptrs) => {
1550
+ throw new Error("Fake backend: ekacli() is not implemented");
1551
+ },
1552
+ ekacld: (_handle, _segno, _column, _dvals, _entszs, _nlflgs, _rcptrs) => {
1553
+ throw new Error("Fake backend: ekacld() is not implemented");
1554
+ },
1555
+ ekaclc: (_handle, _segno, _column, _cvals, _entszs, _nlflgs, _rcptrs) => {
1556
+ throw new Error("Fake backend: ekaclc() is not implemented");
1557
+ },
1558
+ ekffld: (_handle, _segno, _rcptrs) => {
1559
+ throw new Error("Fake backend: ekffld() is not implemented");
1560
+ },
1561
+ // --- DSK writer (not implemented in fake backend) ---
1562
+ dskopn: (_path, _ifname, _ncomch) => {
1563
+ throw new Error("Fake backend: dskopn() is not implemented");
1564
+ },
1565
+ dskmi2: (_nv, _vrtces, _np, _plates, _finscl, _corscl, _worksz, _voxpsz, _voxlsz, _makvtl, _spxisz) => {
1566
+ throw new Error("Fake backend: dskmi2() is not implemented");
1567
+ },
1568
+ dskw02: (_handle, _center, _surfid, _dclass, _frame, _corsys, _corpar, _mncor1, _mxcor1, _mncor2, _mxcor2, _mncor3, _mxcor3, _first, _last, _nv, _vrtces, _np, _plates, _spaixd, _spaixi) => {
1569
+ throw new Error("Fake backend: dskw02() is not implemented");
1570
+ },
1571
+ // -- DSK ----------------------------------------------------------------
1572
+ dskobj: (_dsk, _bodids) => {
1573
+ throw new Error("Fake backend: dskobj() is not implemented");
1574
+ },
1575
+ dsksrf: (_dsk, _bodyid, _srfids) => {
1576
+ throw new Error("Fake backend: dsksrf() is not implemented");
1577
+ },
1578
+ dskgd: (_handle, _dladsc) => {
1579
+ throw new Error("Fake backend: dskgd() is not implemented");
1580
+ },
1581
+ dskb02: (_handle, _dladsc) => {
1582
+ throw new Error("Fake backend: dskb02() is not implemented");
1583
+ },
1584
+ // -- Cells + windows -----------------------------------------------------
1585
+ //
1586
+ // The fake backend is intentionally minimal and does not attempt to
1587
+ // simulate CSPICE cell/window semantics.
1588
+ newIntCell: (_size) => {
1589
+ throw new Error(spiceCellUnsupported);
1590
+ },
1591
+ newDoubleCell: (_size) => {
1592
+ throw new Error(spiceCellUnsupported);
1593
+ },
1594
+ newCharCell: (_size, _length) => {
1595
+ throw new Error(spiceCellUnsupported);
1596
+ },
1597
+ newWindow: (_maxIntervals) => {
1598
+ throw new Error(spiceCellUnsupported);
1599
+ },
1600
+ freeCell: (_cell) => {
1601
+ throw new Error(spiceCellUnsupported);
1602
+ },
1603
+ freeWindow: (_window) => {
1604
+ throw new Error(spiceCellUnsupported);
1605
+ },
1606
+ ssize: (_size, _cell) => {
1607
+ throw new Error(spiceCellUnsupported);
1608
+ },
1609
+ scard: (_card, _cell) => {
1610
+ throw new Error(spiceCellUnsupported);
1611
+ },
1612
+ card: (_cell) => {
1613
+ throw new Error(spiceCellUnsupported);
1614
+ },
1615
+ size: (_cell) => {
1616
+ throw new Error(spiceCellUnsupported);
1617
+ },
1618
+ valid: (_size, _n, _cell) => {
1619
+ throw new Error(spiceCellUnsupported);
1620
+ },
1621
+ insrti: (_item, _cell) => {
1622
+ throw new Error(spiceCellUnsupported);
1623
+ },
1624
+ insrtd: (_item, _cell) => {
1625
+ throw new Error(spiceCellUnsupported);
1626
+ },
1627
+ insrtc: (_item, _cell) => {
1628
+ throw new Error(spiceCellUnsupported);
1629
+ },
1630
+ cellGeti: (_cell, _index) => {
1631
+ throw new Error(spiceCellUnsupported);
1632
+ },
1633
+ cellGetd: (_cell, _index) => {
1634
+ throw new Error(spiceCellUnsupported);
1635
+ },
1636
+ cellGetc: (_cell, _index) => {
1637
+ throw new Error(spiceCellUnsupported);
1638
+ },
1639
+ wninsd: (_left, _right, _window) => {
1640
+ throw new Error(spiceCellUnsupported);
1641
+ },
1642
+ wncard: (_window) => {
1643
+ throw new Error(spiceCellUnsupported);
1644
+ },
1645
+ wnfetd: (_window, _index) => {
1646
+ throw new Error(spiceCellUnsupported);
1647
+ },
1648
+ wnvald: (_size, _n, _window) => {
1649
+ throw new Error(spiceCellUnsupported);
1650
+ },
578
1651
  reclat: (rect) => {
579
1652
  const x = rect[0];
580
1653
  const y = rect[1];
@@ -616,6 +1689,18 @@ export function createFakeBackend() {
616
1689
  vcrss: (a, b) => vcrss(a, b),
617
1690
  mxv: (m, v) => mxv(m, v),
618
1691
  mtxv: (m, v) => mtxv(m, v),
1692
+ vadd: (a, b) => vadd(a, b),
1693
+ vsub: (a, b) => vsub(a, b),
1694
+ vminus: (v) => vscale(-1, v),
1695
+ vscl: (s, v) => vscale(s, v),
1696
+ mxm: (a, b) => mmul3(a, b),
1697
+ rotate: (angle, axis) => rotateRowMajor(angle, axis),
1698
+ // CSPICE rotmat_c left-multiplies: mout = rotate(angle, axis) * m
1699
+ rotmat: (m, angle, axis) => mmul3(rotateRowMajor(angle, axis), m),
1700
+ axisar: (axis, angle) => axisAngleToRotationRowMajor(axis, angle),
1701
+ georec: (lon, lat, alt, re, f) => georec(lon, lat, alt, re, f),
1702
+ recgeo: (rect, re, f) => recgeo(rect, re, f),
619
1703
  };
1704
+ return backend;
620
1705
  }
621
1706
  //# sourceMappingURL=index.js.map