@found-in-space/skykit 0.2.0-alpha.20260528 → 0.2.0-alpha.20260530

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 CHANGED
@@ -69,7 +69,7 @@ Optional attributes keep small tweaks HTML-only:
69
69
  data-skykit-magnitude="7"
70
70
  data-skykit-speed="4"
71
71
  data-skykit-exposure="2600"
72
- data-skykit-look-at="ra=4.496h, dec=16.948"
72
+ data-skykit-look-at="05h 36m 12.81s, −01° 12′ 06.9″"
73
73
  data-skykit-mouse-mode="strafe"
74
74
  data-skykit-persistent-cache="off"
75
75
  style="width: 100%; height: 520px; background: #02040b"
@@ -77,11 +77,12 @@ Optional attributes keep small tweaks HTML-only:
77
77
  ```
78
78
 
79
79
  `data-skykit-look-at` accepts RA/Dec text such as
80
- `ra=4.496h, dec=16.948`, decimal degrees such as `67.447,16.948`, or a
81
- parsec-space `x,y,z` target for exact generated coordinates. `data-skykit-mouse-mode`
82
- defaults to `grab`; use `look` or `strafe` for the first-person mouse-look
83
- direction, or `none` to disable mouse drag controls. Persistent browser Cache API
84
- storage is enabled by default for octree ranges; set
80
+ `05h 36m 12.81s, −01° 12′ 06.9″`, decimal degrees such as
81
+ `84.053393,-1.201926`, or a parsec-space `x,y,z` target for exact generated
82
+ coordinates. `data-skykit-mouse-mode` defaults to `grab`; use `look` or
83
+ `strafe` for the first-person mouse-look direction, or `none` to disable mouse
84
+ drag controls. Persistent browser Cache API storage is enabled by default for
85
+ octree ranges; set
85
86
  `data-skykit-persistent-cache="off"` to keep caching session-only.
86
87
 
87
88
  The host dispatches `skykit-browser-ready` with `{ browser, viewer }` in
@@ -120,15 +121,17 @@ For small scripted interactions, use the browser handle:
120
121
  <script type="module">
121
122
  import {
122
123
  SKYKIT_ACTIONS,
124
+ createRaDecLookAt,
123
125
  createSkykitNavigationPlugin,
124
126
  } from 'https://esm.sh/@found-in-space/skykit';
125
127
 
126
128
  const browser = await Skykit.whenReady();
127
129
  await browser.install(createSkykitNavigationPlugin());
130
+ const alnilam = createRaDecLookAt('05h 36m 12.81s', '−01° 12′ 06.9″');
128
131
 
129
132
  document.querySelector('#orion').addEventListener('click', () => {
130
133
  browser.viewer.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
131
- view: { lookAt: 'ra=5.919h, dec=7.407' },
134
+ view: { lookAt: alnilam },
132
135
  movement: { durationSecs: 3 },
133
136
  });
134
137
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@found-in-space/skykit",
3
- "version": "0.2.0-alpha.20260528",
3
+ "version": "0.2.0-alpha.20260530",
4
4
  "description": "Slim composition and teaching layer for Found in Space packages",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -71,9 +71,9 @@
71
71
  "dependencies": {
72
72
  "@found-in-space/anchored-image": "0.2.0-alpha.0",
73
73
  "@found-in-space/hr-diagram": "0.2.0-alpha.1",
74
- "@found-in-space/meta-sidecar-provider": "0.2.0-alpha.0",
75
- "@found-in-space/spatial": "0.2.0-alpha.20260528",
76
- "@found-in-space/star-octree-provider": "0.2.0-alpha.0",
74
+ "@found-in-space/meta-sidecar-provider": "0.2.0-alpha.1",
75
+ "@found-in-space/spatial": "0.2.0-alpha.20260529",
76
+ "@found-in-space/star-octree-provider": "0.2.0-alpha.1",
77
77
  "@found-in-space/star-trees": "0.2.0-alpha.0",
78
78
  "@found-in-space/three-star-field": "0.2.0-alpha.0"
79
79
  },
@@ -191,6 +191,25 @@ test('createSkykitBrowser enables persistent provider cache by default and can o
191
191
  });
192
192
  });
193
193
 
194
+ test('createSkykitBrowser treats forbidden Cache API access as disabled', async () => {
195
+ await withFakeWindow(async () => {
196
+ await withForbiddenCaches(async () => {
197
+ const browser = await createSkykitBrowser({
198
+ host: createHost(),
199
+ status: false,
200
+ renderer: createRenderer(),
201
+ starField: createStarField(),
202
+ autoResize: false,
203
+ autoDispose: false,
204
+ autoStart: false,
205
+ });
206
+
207
+ assert.equal(browser.provider.describe().capabilities.persistentCache, false);
208
+ await browser.dispose();
209
+ });
210
+ });
211
+ });
212
+
194
213
  test('browser.install adds plugins after startup and cleans returned teardowns', async () => {
195
214
  await withFakeWindow(async () => {
196
215
  const calls = [];
@@ -330,7 +349,7 @@ async function withFakeWindow(callback) {
330
349
  }
331
350
 
332
351
  async function withFakeCaches(callback) {
333
- const previousCaches = globalThis.caches;
352
+ const previousCaches = Object.getOwnPropertyDescriptor(globalThis, 'caches');
334
353
  const previousFetch = globalThis.fetch;
335
354
  const cache = {
336
355
  async match() {
@@ -369,14 +388,7 @@ async function withFakeCaches(callback) {
369
388
  try {
370
389
  await callback();
371
390
  } finally {
372
- if (previousCaches === undefined) {
373
- delete globalThis.caches;
374
- } else {
375
- Object.defineProperty(globalThis, 'caches', {
376
- configurable: true,
377
- value: previousCaches,
378
- });
379
- }
391
+ restoreGlobalProperty('caches', previousCaches);
380
392
  if (previousFetch === undefined) {
381
393
  delete globalThis.fetch;
382
394
  } else {
@@ -388,6 +400,36 @@ async function withFakeCaches(callback) {
388
400
  }
389
401
  }
390
402
 
403
+ async function withForbiddenCaches(callback) {
404
+ const previousCaches = Object.getOwnPropertyDescriptor(globalThis, 'caches');
405
+ const previousWarn = console.warn;
406
+
407
+ Object.defineProperty(globalThis, 'caches', {
408
+ configurable: true,
409
+ get() {
410
+ const error = new Error('Cache API storage is blocked.');
411
+ error.name = 'SecurityError';
412
+ throw error;
413
+ },
414
+ });
415
+ console.warn = () => {};
416
+
417
+ try {
418
+ await callback();
419
+ } finally {
420
+ console.warn = previousWarn;
421
+ restoreGlobalProperty('caches', previousCaches);
422
+ }
423
+ }
424
+
425
+ function restoreGlobalProperty(name, descriptor) {
426
+ if (descriptor) {
427
+ Object.defineProperty(globalThis, name, descriptor);
428
+ } else {
429
+ delete globalThis[name];
430
+ }
431
+ }
432
+
391
433
  function createHost() {
392
434
  return {
393
435
  children: [],
@@ -17,6 +17,7 @@ import {
17
17
  createDesktopSkykitObserverRig,
18
18
  createObject3dLayer,
19
19
  createObject3dPlugin,
20
+ createRaDecLookAt,
20
21
  createMouseLookPlugin,
21
22
  createSkyGrabPlugin,
22
23
  createSkykitDefaultKeyboardNavigationBindings,
@@ -313,6 +314,19 @@ test('viewer derives camera orientation from lookAt targets, sky coordinates, an
313
314
  assertVectorApprox(localVectorFromView(view, { x: 0, y: 1, z: 0 }), { x: 0, y: 1, z: 0 });
314
315
  await skyViewer.dispose();
315
316
 
317
+ const alnilamViewer = await createSkykitViewer({
318
+ renderer: createRenderer(),
319
+ view: {
320
+ lookAt: createRaDecLookAt('05h 36m 12.81s', '−01° 12′ 06.9″'),
321
+ },
322
+ });
323
+ view = alnilamViewer.getViewState();
324
+ assertVectorApprox(
325
+ localVectorFromView(view, { x: 0, y: 0, z: -1 }),
326
+ directionFromRaDec(84.053375, -1.2019166666666667),
327
+ );
328
+ await alnilamViewer.dispose();
329
+
316
330
  const starViewer = await createSkykitViewer({
317
331
  renderer: createRenderer(),
318
332
  view: { lookAt: { star: 'hyades' } },
@@ -1539,6 +1553,17 @@ test('navigation transition action restores pose with independent lane durations
1539
1553
  assert.deepEqual(viewer.getViewState().observerPc, { x: 0, y: 0, z: 0 });
1540
1554
  assert.deepEqual(viewer.getViewState().orientationIcrs, orientationAfterExplicitTransition);
1541
1555
 
1556
+ await viewer.actions.invoke(SKYKIT_ACTIONS.navigation.transitionTo, {
1557
+ lookAt: '05:36:12.81, −01:12:06.9',
1558
+ orientation: { durationSecs: 1 },
1559
+ });
1560
+ viewer.update(1);
1561
+ viewer.update(0);
1562
+ assertVectorApprox(
1563
+ localVectorFromView(viewer.getViewState(), { x: 0, y: 0, z: -1 }),
1564
+ directionFromRaDec(84.053375, -1.2019166666666667),
1565
+ );
1566
+
1542
1567
  await viewer.dispose();
1543
1568
  });
1544
1569
 
@@ -1959,6 +1984,17 @@ function assertVectorApprox(actual, expected, epsilon = 1e-9) {
1959
1984
  assert.ok(Math.abs(actual.z - expected.z) < epsilon, `z ${actual.z} !== ${expected.z}`);
1960
1985
  }
1961
1986
 
1987
+ function directionFromRaDec(raDeg, decDeg) {
1988
+ const ra = raDeg * Math.PI / 180;
1989
+ const dec = decDeg * Math.PI / 180;
1990
+ const cosDec = Math.cos(dec);
1991
+ return {
1992
+ x: Math.cos(ra) * cosDec,
1993
+ y: Math.sin(ra) * cosDec,
1994
+ z: Math.sin(dec),
1995
+ };
1996
+ }
1997
+
1962
1998
  function createPointerTarget() {
1963
1999
  const target = createEventTarget();
1964
2000
  target.clientWidth = 800;
package/src/browser.js CHANGED
@@ -6,6 +6,12 @@ import {
6
6
  } from '@found-in-space/star-octree-provider';
7
7
  import { createObserverShellStrategy } from '@found-in-space/star-trees';
8
8
  import { createThreeStarField } from '@found-in-space/three-star-field';
9
+ import {
10
+ createRaDecLookAt,
11
+ parseDeclination,
12
+ parseRightAscension,
13
+ parseSpatialLookAtText,
14
+ } from '@found-in-space/spatial';
9
15
 
10
16
  import { createSkykitAnimationLoop } from './animation-loop.js';
11
17
  import { SKYKIT_ACTIONS, SKYKIT_CONTROLS } from './actions.js';
@@ -181,8 +187,12 @@ export async function createSkykitBrowser(input = {}) {
181
187
  skykit: {
182
188
  SKYKIT_ACTIONS,
183
189
  SKYKIT_CONTROLS,
190
+ createRaDecLookAt,
184
191
  createObject3dPlugin,
185
192
  createSkykitNavigationPlugin,
193
+ parseDeclination,
194
+ parseRightAscension,
195
+ parseSpatialLookAtText,
186
196
  },
187
197
  };
188
198
  }
package/src/hr-diagram.js CHANGED
@@ -393,7 +393,9 @@ function cloneViewState(view) {
393
393
  ...view,
394
394
  observerPc: clonePoint(view.observerPc),
395
395
  renderObserverPosition: clonePoint(view.renderObserverPosition),
396
- ...(view.lookAt ? { lookAt: { ...view.lookAt } } : {}),
396
+ ...(view.lookAt
397
+ ? { lookAt: typeof view.lookAt === 'object' ? { ...view.lookAt } : view.lookAt }
398
+ : {}),
397
399
  ...(view.targetPc ? { targetPc: clonePoint(view.targetPc) } : {}),
398
400
  ...(view.motion
399
401
  ? {
package/src/index.d.ts CHANGED
@@ -56,7 +56,9 @@ export interface QuaternionLike {
56
56
  w: number;
57
57
  }
58
58
 
59
- export interface SkykitLookAtInput {
59
+ export type SkykitLookAtInput = string | SkykitLookAtSpecInput;
60
+
61
+ export interface SkykitLookAtSpecInput {
60
62
  targetPc?: Vector3Like | [number, number, number];
61
63
  raDeg?: number;
62
64
  raHours?: number;
@@ -68,6 +70,13 @@ export interface SkykitLookAtInput {
68
70
  [key: string]: unknown;
69
71
  }
70
72
 
73
+ export {
74
+ createRaDecLookAt,
75
+ parseDeclination,
76
+ parseRightAscension,
77
+ parseSpatialLookAtText,
78
+ } from '@found-in-space/spatial';
79
+
71
80
  export interface SkykitObserverMotion {
72
81
  velocityPcPerSec: Vector3Like;
73
82
  speedPcPerSec: number;
package/src/index.js CHANGED
@@ -12,6 +12,12 @@ export {
12
12
  } from './actions.js';
13
13
  export { createSkykitAnimationLoop } from './animation-loop.js';
14
14
  export { createSkykitDebugBridge, installSkykitDebugGlobal } from './debug.js';
15
+ export {
16
+ createRaDecLookAt,
17
+ parseDeclination,
18
+ parseRightAscension,
19
+ parseSpatialLookAtText,
20
+ } from '@found-in-space/spatial';
15
21
  export { createSkykitHrDiagramPlugin } from './hr-diagram.js';
16
22
  export { createObject3dLayer } from './layers.js';
17
23
  export { createDesktopSkykitObserverRig } from './observer-rig.js';
package/src/utils.js CHANGED
@@ -211,6 +211,7 @@ function resolveViewLook(input, observerPc, options = {}) {
211
211
  * @returns {import('./index.d.ts').SkykitLookAtInput | null}
212
212
  */
213
213
  function cloneLookAt(lookAt) {
214
+ if (typeof lookAt === 'string') return lookAt;
214
215
  if (!lookAt || typeof lookAt !== 'object') return null;
215
216
  const source = /** @type {Record<string, unknown>} */ (lookAt);
216
217
  return {