Browser ephemeris (WASM + Web Worker)
This recipe shows a browser-first setup that:
- runs the WASM backend inside a Web Worker (
spiceClients…toWebWorker()), - fetches kernel bytes over HTTP,
- computes a concrete ephemeris result via
kit.getState().
If you want a full reference implementation, see the Orrery app:
apps/orrery/src/spice/createSpiceClient.ts
Kernels required (and why)
For basic planet-to-planet state vectors you typically need:
- LSK (
naif0012.tls): leap seconds; required forkit.utcToEt()/kit.etToUtc(). - SPK (
de432s.bspor similar): ephemerides; required forkit.getState()/raw.spkezr(). - PCK (
pck00011.tpc): body radii + orientation models; required once you start working in body-fixed frames (and used by many geometry/lighting routines).
These three kernels are a common “starter set”. For quickstarts, kernels.tspice() is a zero-config way to load them. For production, self-host kernels and use kernels.naif({ ... }) / kernels.custom(...).
Create a worker-backed client (recommended)
Put the kernel files at:
public/kernels/naif/lsk/naif0012.tlspublic/kernels/naif/pck/pck00011.tpcpublic/kernels/naif/spk/planets/de432s.bsp
Then you can load them with kernels.naif + spiceClients.withKernels(packOrPacks):
import { kernels, spiceClients } from '@rybosome/tspice'
const pack = kernels
.naif({
origin: 'kernels/naif/',
// Important for apps deployed under a subpath (GitHub Pages, etc).
baseUrl: import.meta.env.BASE_URL,
pathBase: 'naif/',
})
.pick(
'lsk/naif0012.tls',
'pck/pck00011.tpc',
'spk/planets/de432s.bsp',
)
const { spice, dispose } = await spiceClients
.caching({
maxEntries: 10_000,
ttlMs: null,
})
.withKernels(pack)
.toWebWorker()
try {
// …use `spice` (see below)…
} finally {
await dispose()
}Alternative (no worker)
If you don’t want a worker, you can run WASM in-process:
import { spiceClients } from '@rybosome/tspice'
const { spice, dispose } = await spiceClients.toAsync({ backend: 'wasm' })
try {
// …use `spice`…
} finally {
await dispose()
}Explicit kernel loading as bytes ({ path, bytes })
Whether you’re using a worker-backed client or an in-process WASM client, the browser-side kernel loading primitive is:
await spice.kit.loadKernel({ path, bytes })Here’s the explicit fetch + load flow (equivalent to what withKernels(packOrPacks) does internally):
Note on root-relative URLs (
"/..."):/...kernel URLs ignorepack.baseUrl(so/kernels/a.tlsstays/kernels/a.tls). This can surprise subpath deployments (/myapp/), where you likely want relative kernel URLs (kernels/...) sopack.baseUrlcan be applied.
import { kernels } from '@rybosome/tspice'
const pack = kernels
.naif({
// Root kernel URLs at your app base to make them fetchable without
// additional resolution logic.
origin: `${import.meta.env.BASE_URL}kernels/naif/`,
pathBase: 'naif/',
})
.pick(
'lsk/naif0012.tls',
'pck/pck00011.tpc',
'spk/planets/de432s.bsp',
)
for (const kernel of pack.kernels) {
const res = await fetch(kernel.url)
if (!res.ok) {
throw new Error(`Failed to fetch kernel: ${kernel.url} (${res.status} ${res.statusText})`)
}
const bytes = new Uint8Array(await res.arrayBuffer())
await spice.kit.loadKernel({ path: kernel.path, bytes })
}Example: Mars state relative to Earth at a UTC
const at = await spice.kit.utcToEt('2024-01-01T00:00:00Z')
const state = await spice.kit.getState({
target: 'MARS',
observer: 'EARTH',
at,
frame: 'J2000',
aberration: 'NONE',
})
console.log({
positionKm: state.position,
velocityKmPerSec: state.velocity,
lightTimeSec: state.lightTime,
})Interpreting the result
- Frame:
J2000is the canonical inertial frame. - Units:
positionis km andvelocityis km/s (this matches CSPICEspkezr). - Time: the
atargument and the returnedstate.etare ET seconds past J2000. Usekit.utcToEt()andkit.etToUtc()to convert to/from UTC.