@rybosome/tspice 0.0.3 → 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 +145 -84
- 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 +53 -3
- 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 +44 -4
- 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 +34 -15
- 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 +52 -0
- package/backend-contract/dist/shared/mat3.js +150 -0
- 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 +136 -5
- package/backend-contract/dist/shared/types.js +1 -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 +21 -1
- package/backend-fake/dist/index.js +1112 -33
- 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 +66 -0
- 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 +58 -6
- 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 +5 -1
- 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 +273 -2
- 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 -184
- 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 +139 -6
- 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 +108 -4
- package/backend-wasm/dist/domains/time.d.ts +2 -0
- package/backend-wasm/dist/domains/time.js +235 -133
- package/backend-wasm/dist/index.d.ts +4 -2
- 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 +11 -2
- package/backend-wasm/dist/runtime/create-backend.node.js +283 -14
- package/backend-wasm/dist/runtime/create-backend.web.d.ts +5 -2
- package/backend-wasm/dist/runtime/create-backend.web.js +40 -6
- package/backend-wasm/dist/runtime/fs.d.ts +6 -0
- package/backend-wasm/dist/runtime/fs.js +29 -3
- 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 +21 -0
- package/core/dist/index.js +57 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/backend.d.ts +15 -6
- package/dist/backend.js +3 -6
- 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 +12 -7
- package/dist/index.js +5 -2
- 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/index.d.ts +4 -0
- package/dist/kit/index.js +3 -0
- package/dist/kit/math/mat3.d.ts +31 -0
- package/dist/kit/math/mat3.js +82 -0
- package/dist/kit/spice/create-kit.d.ts +12 -0
- package/dist/kit/spice/create-kit.js +23 -0
- package/dist/kit/spice/frames.d.ts +8 -0
- package/dist/kit/spice/frames.js +16 -0
- package/dist/kit/spice/kernels.d.ts +14 -0
- package/dist/kit/spice/kernels.js +39 -0
- package/dist/kit/spice/state.d.ts +7 -0
- package/dist/kit/spice/state.js +36 -0
- package/dist/kit/spice/time.d.ts +9 -0
- package/dist/kit/spice/time.js +31 -0
- package/dist/kit/types/spice-types.d.ts +51 -0
- package/dist/spice.d.ts +10 -1
- package/dist/spice.js +84 -72
- 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 -17
- 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
- package/dist/spice-types.d.ts +0 -36
- /package/dist/{spice-types.js → kit/types/spice-types.js} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertGetmsgWhich, assertSpiceInt32, brandMat3RowMajor, kxtrctJs, matchesKernelKind, normalizeBodItem, normalizeKindInput, } from "#backend-contract";
|
|
1
2
|
/**
|
|
2
3
|
* A deterministic, pure-TS "toy" backend.
|
|
3
4
|
*
|
|
@@ -128,13 +129,27 @@ function angleBetween(a, b) {
|
|
|
128
129
|
const c = clamp(vdot(a, b) / (na * nb), -1, 1);
|
|
129
130
|
return Math.acos(c);
|
|
130
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
|
+
}
|
|
131
146
|
function canonicalizeZero(n) {
|
|
132
147
|
return Object.is(n, -0) ? 0 : n;
|
|
133
148
|
}
|
|
134
149
|
function rotZRowMajor(theta) {
|
|
135
150
|
const c = Math.cos(theta);
|
|
136
151
|
const s = Math.sin(theta);
|
|
137
|
-
return [
|
|
152
|
+
return brandMat3RowMajor([
|
|
138
153
|
canonicalizeZero(c),
|
|
139
154
|
canonicalizeZero(-s),
|
|
140
155
|
0,
|
|
@@ -144,13 +159,13 @@ function rotZRowMajor(theta) {
|
|
|
144
159
|
0,
|
|
145
160
|
0,
|
|
146
161
|
1,
|
|
147
|
-
];
|
|
162
|
+
], { label: "fake.rotZRowMajor" });
|
|
148
163
|
}
|
|
149
164
|
function drotZRowMajor(theta, w) {
|
|
150
165
|
// d/dt rotZ(theta) = w * d/dtheta rotZ(theta)
|
|
151
166
|
const c = Math.cos(theta);
|
|
152
167
|
const s = Math.sin(theta);
|
|
153
|
-
return [
|
|
168
|
+
return brandMat3RowMajor([
|
|
154
169
|
canonicalizeZero(-w * s),
|
|
155
170
|
canonicalizeZero(-w * c),
|
|
156
171
|
0,
|
|
@@ -160,7 +175,7 @@ function drotZRowMajor(theta, w) {
|
|
|
160
175
|
0,
|
|
161
176
|
0,
|
|
162
177
|
0,
|
|
163
|
-
];
|
|
178
|
+
], { label: "fake.drotZRowMajor" });
|
|
164
179
|
}
|
|
165
180
|
function mmul3(a, b) {
|
|
166
181
|
// Row-major 3x3 multiply: out = a*b
|
|
@@ -174,20 +189,147 @@ function mmul3(a, b) {
|
|
|
174
189
|
out[r * 3 + c] = sum;
|
|
175
190
|
}
|
|
176
191
|
}
|
|
177
|
-
|
|
192
|
+
// Return a real tuple (and brand) instead of exposing a mutable `number[]`.
|
|
193
|
+
return brandMat3RowMajor([out[0], out[1], out[2], out[3], out[4], out[5], out[6], out[7], out[8]], {
|
|
194
|
+
label: "fake.mmul3",
|
|
195
|
+
});
|
|
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 };
|
|
178
330
|
}
|
|
179
331
|
function mtx3(m) {
|
|
180
|
-
return [
|
|
181
|
-
m[0],
|
|
182
|
-
m[3],
|
|
183
|
-
m[6],
|
|
184
|
-
m[1],
|
|
185
|
-
m[4],
|
|
186
|
-
m[7],
|
|
187
|
-
m[2],
|
|
188
|
-
m[5],
|
|
189
|
-
m[8],
|
|
190
|
-
];
|
|
332
|
+
return brandMat3RowMajor([m[0], m[3], m[6], m[1], m[4], m[7], m[2], m[5], m[8]], { label: "fake.mtx3" });
|
|
191
333
|
}
|
|
192
334
|
function mxv(m, v) {
|
|
193
335
|
return [
|
|
@@ -309,14 +451,18 @@ function formatUtcFromMs(ms, prec) {
|
|
|
309
451
|
const padded = (frac + "000000000000").slice(0, clampedPrec);
|
|
310
452
|
return `${head}.${padded}Z`;
|
|
311
453
|
}
|
|
312
|
-
function guessKernelKind(path) {
|
|
454
|
+
function guessKernelKind(path, unknownExtension) {
|
|
313
455
|
const lower = path.toLowerCase();
|
|
314
456
|
if (lower.endsWith(".bsp"))
|
|
315
457
|
return "SPK";
|
|
316
458
|
if (lower.endsWith(".bc"))
|
|
317
459
|
return "CK";
|
|
318
|
-
if (lower.endsWith(".
|
|
460
|
+
if (lower.endsWith(".bpc"))
|
|
319
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";
|
|
320
466
|
if (lower.endsWith(".tls") || lower.endsWith(".lsk"))
|
|
321
467
|
return "LSK";
|
|
322
468
|
if (lower.endsWith(".tf") || lower.endsWith(".fk"))
|
|
@@ -325,9 +471,18 @@ function guessKernelKind(path) {
|
|
|
325
471
|
return "IK";
|
|
326
472
|
if (lower.endsWith(".tsc") || lower.endsWith(".sclk"))
|
|
327
473
|
return "SCLK";
|
|
474
|
+
if (lower.endsWith(".ek"))
|
|
475
|
+
return "EK";
|
|
328
476
|
if (lower.endsWith(".tm") || lower.endsWith(".meta"))
|
|
329
477
|
return "META";
|
|
330
|
-
|
|
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);
|
|
331
486
|
}
|
|
332
487
|
function kernelFiltyp(kind) {
|
|
333
488
|
// Keep this close to NAIF-style strings, but it doesn't need to be exact.
|
|
@@ -335,33 +490,234 @@ function kernelFiltyp(kind) {
|
|
|
335
490
|
case "SPK":
|
|
336
491
|
case "CK":
|
|
337
492
|
case "PCK":
|
|
493
|
+
case "DSK":
|
|
494
|
+
case "EK":
|
|
495
|
+
case "META":
|
|
496
|
+
case "TEXT":
|
|
497
|
+
return kind;
|
|
338
498
|
case "LSK":
|
|
339
499
|
case "FK":
|
|
340
500
|
case "IK":
|
|
341
501
|
case "SCLK":
|
|
342
|
-
|
|
343
|
-
case "META":
|
|
344
|
-
return kind;
|
|
345
|
-
case "ALL":
|
|
346
|
-
default:
|
|
347
|
-
return "ALL";
|
|
502
|
+
return "TEXT";
|
|
348
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}`);
|
|
349
507
|
}
|
|
350
|
-
|
|
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`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
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 = {}) {
|
|
351
567
|
let nextHandle = 1;
|
|
568
|
+
let spiceFailed = false;
|
|
569
|
+
let spiceShort = "";
|
|
570
|
+
let spiceLong = "";
|
|
571
|
+
const traceStack = [];
|
|
352
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).";
|
|
353
615
|
const getKernelsOfKind = (kind) => {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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];
|
|
357
670
|
};
|
|
358
|
-
|
|
671
|
+
const backend = {
|
|
359
672
|
kind: "fake",
|
|
360
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
|
+
},
|
|
361
717
|
furnsh: (kernel) => {
|
|
362
718
|
const file = typeof kernel === "string" ? kernel : kernel.path;
|
|
363
719
|
const source = typeof kernel === "string" ? file : "bytes";
|
|
364
|
-
const kind = guessKernelKind(file);
|
|
720
|
+
const kind = guessKernelKind(file, unknownExtension);
|
|
365
721
|
const handle = nextHandle++;
|
|
366
722
|
kernels.push({
|
|
367
723
|
file,
|
|
@@ -372,13 +728,42 @@ export function createFakeBackend() {
|
|
|
372
728
|
});
|
|
373
729
|
},
|
|
374
730
|
unload: (path) => {
|
|
375
|
-
const
|
|
731
|
+
const needle = normalizeVirtualKernelIdOrNull(path);
|
|
732
|
+
if (needle == null) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const idx = kernels.findIndex((k) => normalizeVirtualKernelIdOrNull(k.file) === needle);
|
|
376
736
|
if (idx >= 0) {
|
|
377
737
|
kernels.splice(idx, 1);
|
|
378
738
|
}
|
|
379
739
|
},
|
|
380
740
|
kclear: () => {
|
|
381
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);
|
|
382
767
|
},
|
|
383
768
|
ktotal: (kind = "ALL") => {
|
|
384
769
|
return getKernelsOfKind(kind).length;
|
|
@@ -396,6 +781,163 @@ export function createFakeBackend() {
|
|
|
396
781
|
handle: k.handle,
|
|
397
782
|
};
|
|
398
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
|
+
},
|
|
399
941
|
tkvrsn: (item) => {
|
|
400
942
|
if (item !== "TOOLKIT") {
|
|
401
943
|
throw new Error(`Fake backend: unsupported tkvrsn item: ${String(item)}`);
|
|
@@ -421,19 +963,102 @@ export function createFakeBackend() {
|
|
|
421
963
|
const ms = J2000_UTC_MS + et * 1000;
|
|
422
964
|
return formatUtcFromMs(ms, 3);
|
|
423
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,
|
|
424
997
|
bodn2c: (name) => {
|
|
425
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 };
|
|
426
1003
|
const id = NAME_TO_ID.get(trimmed) ?? NAME_TO_ID.get(trimmed.toLowerCase());
|
|
427
1004
|
if (id === undefined)
|
|
428
1005
|
return { found: false };
|
|
429
1006
|
return { found: true, code: id };
|
|
430
1007
|
},
|
|
431
1008
|
bodc2n: (code) => {
|
|
1009
|
+
const custom = customBodyCodeToName.get(code);
|
|
1010
|
+
if (custom !== undefined)
|
|
1011
|
+
return { found: true, name: custom };
|
|
432
1012
|
const meta = ID_TO_BODY.get(code);
|
|
433
1013
|
if (!meta)
|
|
434
1014
|
return { found: false };
|
|
435
1015
|
return { found: true, name: meta.name };
|
|
436
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
|
+
},
|
|
437
1062
|
namfrm: (name) => {
|
|
438
1063
|
const trimmed = normalizeName(name);
|
|
439
1064
|
const code = FRAME_NAME_TO_CODE.get(trimmed) ?? FRAME_NAME_TO_CODE.get(trimmed.toLowerCase());
|
|
@@ -466,6 +1091,21 @@ export function createFakeBackend() {
|
|
|
466
1091
|
? { found: true, frcode: FRAME_CODES.IAU_MOON, frname: "IAU_MOON" }
|
|
467
1092
|
: { found: false });
|
|
468
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
|
+
},
|
|
469
1109
|
scs2e: (_sc, sclkch) => {
|
|
470
1110
|
// Minimal deterministic stub: treat the string as a number of seconds.
|
|
471
1111
|
const n = Number(sclkch);
|
|
@@ -475,12 +1115,41 @@ export function createFakeBackend() {
|
|
|
475
1115
|
// Minimal deterministic stub.
|
|
476
1116
|
return String(et);
|
|
477
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
|
+
},
|
|
478
1135
|
ckgp: (_inst, _sclkdp, _tol, _ref) => {
|
|
479
1136
|
return { found: false };
|
|
480
1137
|
},
|
|
481
1138
|
ckgpav: (_inst, _sclkdp, _tol, _ref) => {
|
|
482
1139
|
return { found: false };
|
|
483
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
|
+
},
|
|
484
1153
|
pxform: (from, to, et) => {
|
|
485
1154
|
const f = parseFrameName(from);
|
|
486
1155
|
const t = parseFrameName(to);
|
|
@@ -519,6 +1188,197 @@ export function createFakeBackend() {
|
|
|
519
1188
|
lt: 0,
|
|
520
1189
|
};
|
|
521
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
|
+
},
|
|
522
1382
|
subpnt: (_method, target, et, fixref, abcorr, observer) => {
|
|
523
1383
|
void abcorr;
|
|
524
1384
|
const targetId = parseBodyRef(target);
|
|
@@ -562,7 +1422,9 @@ export function createFakeBackend() {
|
|
|
562
1422
|
// Vectors from surface point.
|
|
563
1423
|
const srfToSunJ = vsub(sunPosJ, spointJ);
|
|
564
1424
|
const srfToObsJ = vsub(obsPosJ, spointJ);
|
|
565
|
-
const
|
|
1425
|
+
const targetId = parseBodyRef(target);
|
|
1426
|
+
const normalF = ellipsoidSurfaceNormal(spoint, getBodyRadiiKm(targetId));
|
|
1427
|
+
const normalJ = frame === "J2000" ? normalF : mxv(inv, normalF);
|
|
566
1428
|
const phase = angleBetween(srfToSunJ, srfToObsJ);
|
|
567
1429
|
const incdnc = angleBetween(normalJ, srfToSunJ);
|
|
568
1430
|
const emissn = angleBetween(normalJ, srfToObsJ);
|
|
@@ -581,6 +1443,211 @@ export function createFakeBackend() {
|
|
|
581
1443
|
// Deterministic stub: 0 => "no occultation".
|
|
582
1444
|
return 0;
|
|
583
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
|
+
},
|
|
584
1651
|
reclat: (rect) => {
|
|
585
1652
|
const x = rect[0];
|
|
586
1653
|
const y = rect[1];
|
|
@@ -622,6 +1689,18 @@ export function createFakeBackend() {
|
|
|
622
1689
|
vcrss: (a, b) => vcrss(a, b),
|
|
623
1690
|
mxv: (m, v) => mxv(m, v),
|
|
624
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),
|
|
625
1703
|
};
|
|
1704
|
+
return backend;
|
|
626
1705
|
}
|
|
627
1706
|
//# sourceMappingURL=index.js.map
|