@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.
- package/README.md +140 -78
- package/backend-contract/dist/.tsbuildinfo +1 -1
- package/backend-contract/dist/domains/cells-windows.d.ts +94 -0
- package/backend-contract/dist/domains/cells-windows.js +10 -0
- package/backend-contract/dist/domains/coords-vectors.d.ts +50 -0
- package/backend-contract/dist/domains/dsk.d.ts +49 -0
- package/backend-contract/dist/domains/dsk.js +2 -0
- package/backend-contract/dist/domains/ek.d.ts +186 -0
- package/backend-contract/dist/domains/ek.js +8 -0
- package/backend-contract/dist/domains/ephemeris.d.ts +141 -3
- package/backend-contract/dist/domains/error.d.ts +42 -0
- package/backend-contract/dist/domains/error.js +33 -0
- package/backend-contract/dist/domains/file-io.d.ts +114 -0
- package/backend-contract/dist/domains/file-io.js +8 -0
- package/backend-contract/dist/domains/frames.d.ts +40 -0
- package/backend-contract/dist/domains/geometry-gf.d.ts +44 -0
- package/backend-contract/dist/domains/geometry-gf.js +14 -0
- package/backend-contract/dist/domains/geometry.d.ts +21 -1
- package/backend-contract/dist/domains/ids-names-normalize.d.ts +3 -0
- package/backend-contract/dist/domains/ids-names-normalize.js +74 -0
- package/backend-contract/dist/domains/ids-names.d.ts +37 -0
- package/backend-contract/dist/domains/kernel-pool.d.ts +134 -0
- package/backend-contract/dist/domains/kernel-pool.js +2 -0
- package/backend-contract/dist/domains/kernels-utils.d.ts +44 -0
- package/backend-contract/dist/domains/kernels-utils.js +265 -0
- package/backend-contract/dist/domains/kernels.d.ts +39 -3
- package/backend-contract/dist/domains/time.d.ts +102 -0
- package/backend-contract/dist/index.d.ts +31 -3
- package/backend-contract/dist/index.js +15 -1
- package/backend-contract/dist/shared/errors.d.ts +6 -0
- package/backend-contract/dist/shared/errors.js +8 -0
- package/backend-contract/dist/shared/mat3.d.ts +26 -14
- package/backend-contract/dist/shared/mat3.js +46 -17
- package/backend-contract/dist/shared/mat6.d.ts +34 -0
- package/backend-contract/dist/shared/mat6.js +116 -0
- package/backend-contract/dist/shared/spice-handles.d.ts +20 -0
- package/backend-contract/dist/shared/spice-handles.js +82 -0
- package/backend-contract/dist/shared/spice-int.d.ts +32 -0
- package/backend-contract/dist/shared/spice-int.js +41 -0
- package/backend-contract/dist/shared/types.d.ts +106 -1
- package/backend-contract/dist/shared/vec.d.ts +54 -0
- package/backend-contract/dist/shared/vec.js +162 -0
- package/backend-fake/dist/.tsbuildinfo +1 -1
- package/backend-fake/dist/index.d.ts +19 -1
- package/backend-fake/dist/index.js +1103 -18
- package/backend-node/dist/.tsbuildinfo +1 -1
- package/backend-node/dist/codec/arrays.d.ts +9 -0
- package/backend-node/dist/codec/arrays.js +36 -0
- package/backend-node/dist/codec/errors.d.ts +6 -6
- package/backend-node/dist/codec/errors.js +6 -6
- package/backend-node/dist/domains/cells-windows.d.ts +5 -0
- package/backend-node/dist/domains/cells-windows.js +112 -0
- package/backend-node/dist/domains/coords-vectors.d.ts +1 -0
- package/backend-node/dist/domains/coords-vectors.js +64 -1
- package/backend-node/dist/domains/dsk.d.ts +6 -0
- package/backend-node/dist/domains/dsk.js +108 -0
- package/backend-node/dist/domains/ek.d.ts +10 -0
- package/backend-node/dist/domains/ek.js +100 -0
- package/backend-node/dist/domains/ephemeris.d.ts +5 -1
- package/backend-node/dist/domains/ephemeris.js +150 -1
- package/backend-node/dist/domains/error.d.ts +5 -0
- package/backend-node/dist/domains/error.js +34 -0
- package/backend-node/dist/domains/file-io.d.ts +7 -0
- package/backend-node/dist/domains/file-io.js +105 -0
- package/backend-node/dist/domains/frames.d.ts +1 -0
- package/backend-node/dist/domains/frames.js +52 -0
- package/backend-node/dist/domains/geometry-gf.d.ts +5 -0
- package/backend-node/dist/domains/geometry-gf.js +74 -0
- package/backend-node/dist/domains/geometry.d.ts +1 -0
- package/backend-node/dist/domains/geometry.js +62 -0
- package/backend-node/dist/domains/ids-names.d.ts +2 -1
- package/backend-node/dist/domains/ids-names.js +30 -0
- package/backend-node/dist/domains/kernel-pool.d.ts +5 -0
- package/backend-node/dist/domains/kernel-pool.js +74 -0
- package/backend-node/dist/domains/kernels.d.ts +1 -0
- package/backend-node/dist/domains/kernels.js +100 -13
- package/backend-node/dist/domains/time.d.ts +1 -0
- package/backend-node/dist/domains/time.js +75 -1
- package/backend-node/dist/index.d.ts +2 -0
- package/backend-node/dist/index.js +62 -1
- package/backend-node/dist/lowlevel/binding.d.ts +3 -0
- package/backend-node/dist/lowlevel/binding.js +115 -0
- package/backend-node/dist/runtime/addon.d.ts +271 -0
- package/backend-node/dist/runtime/addon.js +3 -0
- package/backend-node/dist/runtime/kernel-staging.d.ts +17 -0
- package/backend-node/dist/runtime/kernel-staging.js +80 -7
- package/backend-node/dist/runtime/spice-handles.d.ts +3 -0
- package/backend-node/dist/runtime/spice-handles.js +2 -0
- package/backend-node/dist/runtime/virtual-output-staging.d.ts +16 -0
- package/backend-node/dist/runtime/virtual-output-staging.js +148 -0
- package/backend-wasm/dist/.tsbuildinfo +1 -1
- package/backend-wasm/dist/codec/alloc.d.ts +19 -0
- package/backend-wasm/dist/codec/alloc.js +64 -0
- package/backend-wasm/dist/codec/calls.d.ts +2 -0
- package/backend-wasm/dist/codec/calls.js +13 -24
- package/backend-wasm/dist/codec/errors.d.ts +6 -0
- package/backend-wasm/dist/codec/errors.js +34 -2
- package/backend-wasm/dist/codec/found.d.ts +2 -0
- package/backend-wasm/dist/codec/found.js +20 -43
- package/backend-wasm/dist/codec/strings.d.ts +31 -1
- package/backend-wasm/dist/codec/strings.js +93 -6
- package/backend-wasm/dist/domains/cells-windows.d.ts +9 -0
- package/backend-wasm/dist/domains/cells-windows.js +392 -0
- package/backend-wasm/dist/domains/coords-vectors.d.ts +1 -0
- package/backend-wasm/dist/domains/coords-vectors.js +377 -187
- package/backend-wasm/dist/domains/dsk.d.ts +6 -0
- package/backend-wasm/dist/domains/dsk.js +179 -0
- package/backend-wasm/dist/domains/ek.d.ts +6 -0
- package/backend-wasm/dist/domains/ek.js +543 -0
- package/backend-wasm/dist/domains/ephemeris.d.ts +4 -1
- package/backend-wasm/dist/domains/ephemeris.js +405 -46
- package/backend-wasm/dist/domains/error.d.ts +5 -0
- package/backend-wasm/dist/domains/error.js +109 -0
- package/backend-wasm/dist/domains/file-io.d.ts +7 -0
- package/backend-wasm/dist/domains/file-io.js +462 -0
- package/backend-wasm/dist/domains/frames.d.ts +1 -0
- package/backend-wasm/dist/domains/frames.js +136 -4
- package/backend-wasm/dist/domains/geometry-gf.d.ts +5 -0
- package/backend-wasm/dist/domains/geometry-gf.js +178 -0
- package/backend-wasm/dist/domains/geometry.d.ts +1 -0
- package/backend-wasm/dist/domains/geometry.js +210 -0
- package/backend-wasm/dist/domains/ids-names.d.ts +2 -1
- package/backend-wasm/dist/domains/ids-names.js +89 -0
- package/backend-wasm/dist/domains/kernel-pool.d.ts +5 -0
- package/backend-wasm/dist/domains/kernel-pool.js +357 -0
- package/backend-wasm/dist/domains/kernels.d.ts +1 -0
- package/backend-wasm/dist/domains/kernels.js +91 -2
- package/backend-wasm/dist/domains/time.d.ts +2 -0
- package/backend-wasm/dist/domains/time.js +235 -133
- package/backend-wasm/dist/lowlevel/exports.d.ts +215 -1
- package/backend-wasm/dist/lowlevel/exports.js +217 -38
- package/backend-wasm/dist/runtime/create-backend-options.d.ts +21 -0
- package/backend-wasm/dist/runtime/create-backend.node.d.ts +7 -0
- package/backend-wasm/dist/runtime/create-backend.node.js +283 -12
- package/backend-wasm/dist/runtime/create-backend.web.d.ts +1 -0
- package/backend-wasm/dist/runtime/create-backend.web.js +40 -4
- package/backend-wasm/dist/runtime/fs.d.ts +5 -0
- package/backend-wasm/dist/runtime/fs.js +5 -0
- package/backend-wasm/dist/runtime/spice-handles.d.ts +3 -0
- package/backend-wasm/dist/runtime/spice-handles.js +2 -0
- package/backend-wasm/dist/runtime/virtual-outputs.d.ts +16 -0
- package/backend-wasm/dist/runtime/virtual-outputs.js +35 -0
- package/backend-wasm/dist/tspice_backend_wasm.node.js +3 -3
- package/backend-wasm/dist/tspice_backend_wasm.wasm +0 -0
- package/backend-wasm/dist/tspice_backend_wasm.web.js +1 -1
- package/core/dist/.tsbuildinfo +1 -1
- package/core/dist/index.d.ts +19 -8
- package/core/dist/index.js +19 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/backend.d.ts +5 -0
- package/dist/clients/createSpiceAsyncFromTransport.d.ts +5 -0
- package/dist/clients/createSpiceAsyncFromTransport.js +90 -0
- package/dist/clients/createSpiceSyncFromTransport.d.ts +5 -0
- package/dist/clients/createSpiceSyncFromTransport.js +88 -0
- package/dist/clients/spiceClients.d.ts +59 -0
- package/dist/clients/spiceClients.js +292 -0
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +4 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.js +4 -3
- package/dist/kernels/defaultKernelPathFromUrl.d.ts +8 -0
- package/dist/kernels/defaultKernelPathFromUrl.js +32 -0
- package/dist/kernels/kernelPack.d.ts +88 -0
- package/dist/kernels/kernelPack.js +122 -0
- package/dist/kernels/kernels.d.ts +98 -0
- package/dist/kernels/kernels.js +217 -0
- package/dist/kernels/naifKernelId.d.ts +2 -0
- package/dist/kernels/naifKernelId.js +2 -0
- package/dist/kit/math/mat3.d.ts +9 -1
- package/dist/kit/math/mat3.js +9 -1
- package/dist/kit/spice/create-kit.d.ts +1 -0
- package/dist/kit/spice/create-kit.js +1 -0
- package/dist/kit/spice/frames.d.ts +1 -0
- package/dist/kit/spice/frames.js +1 -0
- package/dist/kit/spice/kernels.d.ts +1 -0
- package/dist/kit/spice/kernels.js +1 -0
- package/dist/kit/spice/state.d.ts +2 -1
- package/dist/kit/spice/state.js +8 -4
- package/dist/kit/spice/time.d.ts +1 -0
- package/dist/kit/spice/time.js +1 -0
- package/dist/kit/types/spice-types.d.ts +18 -1
- package/dist/spice.d.ts +10 -1
- package/dist/spice.js +41 -0
- package/dist/transport/caching/policy.d.ts +16 -0
- package/dist/transport/caching/policy.js +77 -0
- package/dist/transport/caching/withCaching.d.ts +125 -0
- package/dist/transport/caching/withCaching.js +335 -0
- package/dist/transport/caching/withCachingSync.d.ts +24 -0
- package/dist/transport/caching/withCachingSync.js +161 -0
- package/dist/transport/rpc/protocol.d.ts +35 -0
- package/dist/transport/rpc/protocol.js +56 -0
- package/dist/transport/rpc/taskScheduling.d.ts +20 -0
- package/dist/transport/rpc/taskScheduling.js +98 -0
- package/dist/transport/rpc/valueCodec.d.ts +5 -0
- package/dist/transport/rpc/valueCodec.js +106 -0
- package/dist/transport/types.d.ts +7 -0
- package/dist/transport/types.js +2 -0
- package/dist/types.d.ts +8 -5
- package/dist/types.js +2 -1
- package/dist/worker/browser/createSpiceWorker.d.ts +22 -0
- package/dist/worker/browser/createSpiceWorker.js +41 -0
- package/dist/worker/browser/createSpiceWorkerClient.d.ts +40 -0
- package/dist/worker/browser/createSpiceWorkerClient.js +99 -0
- package/dist/worker/browser/spiceWorkerEntry.d.ts +2 -0
- package/dist/worker/browser/spiceWorkerEntry.js +129 -0
- package/dist/worker/browser/spiceWorkerInlineSource.d.ts +2 -0
- package/dist/worker/browser/spiceWorkerInlineSource.js +4 -0
- package/dist/worker/index.d.ts +10 -0
- package/dist/worker/index.js +7 -0
- package/dist/worker/transport/createWorkerTransport.d.ts +69 -0
- package/dist/worker/transport/createWorkerTransport.js +398 -0
- package/dist/worker/transport/exposeTransportToWorker.d.ts +51 -0
- package/dist/worker/transport/exposeTransportToWorker.js +196 -0
- 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(".
|
|
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
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|