@found-in-space/skykit 0.2.0-alpha.1 → 0.2.0-dev.20260527.0
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 +143 -6
- package/examples/custom-object-layer/custom-object-layer.js +1 -24
- package/examples/xr-free-roam/index.html +62 -4
- package/examples/xr-free-roam/xr-free-roam.css +249 -18
- package/examples/xr-free-roam/xr-free-roam.js +644 -217
- package/package.json +31 -5
- package/src/__tests__/skykit-anchored-images.test.js +32 -4
- package/src/__tests__/skykit-browser.test.js +217 -0
- package/src/__tests__/skykit-data.test.js +131 -0
- package/src/__tests__/skykit-parallax.test.js +4 -4
- package/src/__tests__/skykit-touch-os.test.js +71 -0
- package/src/__tests__/skykit-xr.test.js +123 -2
- package/src/__tests__/skykit.test.js +138 -1
- package/src/anchored-images.js +14 -15
- package/src/browser-addons.d.ts +16 -0
- package/src/browser-addons.js +155 -0
- package/src/browser-constellations.d.ts +13 -0
- package/src/browser-constellations.js +387 -0
- package/src/browser-journey.d.ts +8 -0
- package/src/browser-journey.js +240 -0
- package/src/browser.d.ts +98 -0
- package/src/browser.js +215 -13
- package/src/data.d.ts +133 -0
- package/src/data.js +447 -0
- package/src/embed.d.ts +5 -0
- package/src/embed.js +52 -2
- package/src/hr-diagram.js +23 -5
- package/src/index.d.ts +32 -7
- package/src/plugins.js +87 -43
- package/src/story.d.ts +57 -0
- package/src/story.js +396 -0
- package/src/three-shim.d.ts +32 -0
- package/src/touch-os.d.ts +70 -0
- package/src/touch-os.js +275 -0
- package/src/utils.js +96 -6
- package/src/viewer-entry.d.ts +10 -0
- package/src/viewer-entry.js +4 -0
- package/src/viewer.js +110 -12
- package/src/xr/plugins.js +224 -13
- package/src/xr/session.js +60 -14
- package/src/xr.d.ts +22 -0
- package/src/xr.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@found-in-space/skykit",
|
|
3
|
-
"version": "0.2.0-
|
|
3
|
+
"version": "0.2.0-dev.20260527.0",
|
|
4
4
|
"description": "Slim composition and teaching layer for Found in Space packages",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,34 @@
|
|
|
23
23
|
"types": "./src/browser.d.ts",
|
|
24
24
|
"default": "./src/browser.js"
|
|
25
25
|
},
|
|
26
|
+
"./browser-addons": {
|
|
27
|
+
"types": "./src/browser-addons.d.ts",
|
|
28
|
+
"default": "./src/browser-addons.js"
|
|
29
|
+
},
|
|
30
|
+
"./browser-constellations": {
|
|
31
|
+
"types": "./src/browser-constellations.d.ts",
|
|
32
|
+
"default": "./src/browser-constellations.js"
|
|
33
|
+
},
|
|
34
|
+
"./browser-journey": {
|
|
35
|
+
"types": "./src/browser-journey.d.ts",
|
|
36
|
+
"default": "./src/browser-journey.js"
|
|
37
|
+
},
|
|
26
38
|
"./embed": {
|
|
27
39
|
"types": "./src/embed.d.ts",
|
|
28
40
|
"default": "./src/embed.js"
|
|
29
41
|
},
|
|
42
|
+
"./viewer": {
|
|
43
|
+
"types": "./src/viewer-entry.d.ts",
|
|
44
|
+
"default": "./src/viewer-entry.js"
|
|
45
|
+
},
|
|
46
|
+
"./data": {
|
|
47
|
+
"types": "./src/data.d.ts",
|
|
48
|
+
"default": "./src/data.js"
|
|
49
|
+
},
|
|
50
|
+
"./story": {
|
|
51
|
+
"types": "./src/story.d.ts",
|
|
52
|
+
"default": "./src/story.js"
|
|
53
|
+
},
|
|
30
54
|
"./parallax": {
|
|
31
55
|
"types": "./src/parallax.d.ts",
|
|
32
56
|
"default": "./src/parallax.js"
|
|
@@ -46,7 +70,8 @@
|
|
|
46
70
|
"README.md"
|
|
47
71
|
],
|
|
48
72
|
"sideEffects": [
|
|
49
|
-
"./src/embed.js"
|
|
73
|
+
"./src/embed.js",
|
|
74
|
+
"./src/story.js"
|
|
50
75
|
],
|
|
51
76
|
"scripts": {
|
|
52
77
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
@@ -56,16 +81,17 @@
|
|
|
56
81
|
"@found-in-space/anchored-image": "0.2.0-alpha.0",
|
|
57
82
|
"@found-in-space/hr-diagram": "0.2.0-alpha.0",
|
|
58
83
|
"@found-in-space/journey": "0.2.0-alpha.0",
|
|
59
|
-
"@found-in-space/
|
|
84
|
+
"@found-in-space/meta-sidecar-provider": "0.2.0-alpha.0",
|
|
85
|
+
"@found-in-space/spatial": "0.2.0-dev.20260527.0",
|
|
60
86
|
"@found-in-space/star-octree-provider": "0.2.0-alpha.0",
|
|
61
87
|
"@found-in-space/star-trees": "0.2.0-alpha.0",
|
|
62
88
|
"@found-in-space/three-star-field": "0.2.0-alpha.0"
|
|
63
89
|
},
|
|
64
90
|
"devDependencies": {
|
|
65
|
-
"@found-in-space/touch-os": "0.2.0-dev.
|
|
91
|
+
"@found-in-space/touch-os": "0.2.0-dev.3"
|
|
66
92
|
},
|
|
67
93
|
"peerDependencies": {
|
|
68
|
-
"@found-in-space/touch-os": ">=0.2.0-dev.
|
|
94
|
+
"@found-in-space/touch-os": ">=0.2.0-dev.3 <1",
|
|
69
95
|
"three": "^0.170.0"
|
|
70
96
|
},
|
|
71
97
|
"peerDependenciesMeta": {
|
|
@@ -147,7 +147,7 @@ test('anchored image sky plugin preloads controller selection and mounts fixed-a
|
|
|
147
147
|
});
|
|
148
148
|
const viewer = await createSkykitViewer({
|
|
149
149
|
renderer: createRenderer(),
|
|
150
|
-
view: {
|
|
150
|
+
view: { lookAt: { raDeg: 0, decDeg: 0 } },
|
|
151
151
|
plugins: [plugin],
|
|
152
152
|
});
|
|
153
153
|
|
|
@@ -161,6 +161,34 @@ test('anchored image sky plugin preloads controller selection and mounts fixed-a
|
|
|
161
161
|
await viewer.dispose();
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
+
test('anchored image sky plugin can mount art into a named scale band', async () => {
|
|
165
|
+
const catalog = await createAnchoredImageCatalog({ manifest: MANIFEST });
|
|
166
|
+
const requests = [];
|
|
167
|
+
const scaleRoot = new THREE.Group();
|
|
168
|
+
const plugin = createAnchoredImageSkyPlugin({
|
|
169
|
+
id: 'banded-art',
|
|
170
|
+
catalog,
|
|
171
|
+
controller: createManualAnchoredImageController({ selection: 'alpha' }),
|
|
172
|
+
loading: 'preload',
|
|
173
|
+
textureLoader: createTextureLoader(requests),
|
|
174
|
+
anchorMode: 'scale-banded',
|
|
175
|
+
scaleBandId: 'constellation-art',
|
|
176
|
+
});
|
|
177
|
+
const viewer = await createSkykitViewer({
|
|
178
|
+
renderer: createRenderer(),
|
|
179
|
+
roots: {
|
|
180
|
+
scaleBandedContentRoots: new Map([['constellation-art', scaleRoot]]),
|
|
181
|
+
},
|
|
182
|
+
view: { lookAt: { raDeg: 0, decDeg: 0 } },
|
|
183
|
+
plugins: [plugin],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
assert.deepEqual(requests, ['alpha.png']);
|
|
187
|
+
assert.equal(scaleRoot.children.some((child) => child.name === 'banded-art'), true);
|
|
188
|
+
|
|
189
|
+
await viewer.dispose();
|
|
190
|
+
});
|
|
191
|
+
|
|
164
192
|
test('anchored image sky plugin lazy-loads active controller entries and caches them', async () => {
|
|
165
193
|
const catalog = await createAnchoredImageCatalog({ manifest: MANIFEST });
|
|
166
194
|
const requests = [];
|
|
@@ -174,13 +202,13 @@ test('anchored image sky plugin lazy-loads active controller entries and caches
|
|
|
174
202
|
});
|
|
175
203
|
const viewer = await createSkykitViewer({
|
|
176
204
|
renderer: createRenderer(),
|
|
177
|
-
view: {
|
|
205
|
+
view: { lookAt: { raDeg: 0, decDeg: 0 } },
|
|
178
206
|
plugins: [plugin],
|
|
179
207
|
});
|
|
180
208
|
await flushPromises();
|
|
181
209
|
|
|
182
210
|
assert.deepEqual(requests, ['alpha.png']);
|
|
183
|
-
viewer.requestViewState({
|
|
211
|
+
viewer.requestViewState({ lookAt: { raDeg: 90, decDeg: 0 } }, 'test-active-change');
|
|
184
212
|
viewer.update(0);
|
|
185
213
|
await flushPromises();
|
|
186
214
|
assert.deepEqual(requests, ['alpha.png', 'beta.png']);
|
|
@@ -208,7 +236,7 @@ test('anchored image sky plugin fades opacity by seconds and keeps fading object
|
|
|
208
236
|
});
|
|
209
237
|
const viewer = await createSkykitViewer({
|
|
210
238
|
renderer: createRenderer(),
|
|
211
|
-
view: {
|
|
239
|
+
view: { lookAt: { raDeg: 0, decDeg: 0 } },
|
|
212
240
|
plugins: [plugin],
|
|
213
241
|
});
|
|
214
242
|
await flushPromises();
|
|
@@ -2,6 +2,10 @@ import assert from 'node:assert/strict';
|
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
installSkykitBrowserGlobal,
|
|
7
|
+
registerBrowserInstance,
|
|
8
|
+
} from '../browser-addons.js';
|
|
5
9
|
import { createSkykitBrowser } from '../browser.js';
|
|
6
10
|
|
|
7
11
|
test('createSkykitBrowser wires the starter viewer and extra plugins', async () => {
|
|
@@ -44,6 +48,20 @@ test('createSkykitBrowser wires the starter viewer and extra plugins', async ()
|
|
|
44
48
|
assert.deepEqual(extraPartCalls, ['attach', 'start']);
|
|
45
49
|
assert.match(status.textContent, /"starsLoaded": 0/);
|
|
46
50
|
|
|
51
|
+
const marker = new THREE.Object3D();
|
|
52
|
+
const markerHandle = browser.addObject(marker, {
|
|
53
|
+
id: 'hyades-marker',
|
|
54
|
+
positionPc: { x: 17.574, y: 42.316, z: 13.963 },
|
|
55
|
+
});
|
|
56
|
+
await Promise.resolve();
|
|
57
|
+
assert.ok(Math.abs(marker.position.x - 0.017574) < 1e-12);
|
|
58
|
+
assert.ok(Math.abs(marker.position.y - 0.042316) < 1e-12);
|
|
59
|
+
assert.ok(Math.abs(marker.position.z - 0.013963) < 1e-12);
|
|
60
|
+
assert.equal(browser.viewer.roots.originContentRoot.children.includes(marker), true);
|
|
61
|
+
markerHandle.remove();
|
|
62
|
+
await Promise.resolve();
|
|
63
|
+
assert.equal(browser.viewer.roots.originContentRoot.children.includes(marker), false);
|
|
64
|
+
|
|
47
65
|
browser.resize();
|
|
48
66
|
assert.equal(fakeWindow.addedEvents.length, 0);
|
|
49
67
|
|
|
@@ -88,6 +106,205 @@ test('createSkykitBrowser registers resize and page-lifecycle cleanup by default
|
|
|
88
106
|
});
|
|
89
107
|
});
|
|
90
108
|
|
|
109
|
+
test('createSkykitBrowser accepts startup lookAt and mouse look mode', async () => {
|
|
110
|
+
await withFakeWindow(async () => {
|
|
111
|
+
const browser = await createSkykitBrowser({
|
|
112
|
+
host: createHost(),
|
|
113
|
+
status: false,
|
|
114
|
+
renderer: createRenderer(),
|
|
115
|
+
provider: createProvider(),
|
|
116
|
+
starField: createStarField(),
|
|
117
|
+
autoResize: false,
|
|
118
|
+
autoDispose: false,
|
|
119
|
+
autoStart: false,
|
|
120
|
+
mouseMode: 'strafe',
|
|
121
|
+
lookAt: { targetPc: { x: 10, y: 0, z: 0 } },
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const view = browser.viewer.getViewState();
|
|
125
|
+
assert.deepEqual(view.targetPc, { x: 10, y: 0, z: 0 });
|
|
126
|
+
assert.ok(view.orientationIcrs);
|
|
127
|
+
assert.equal(
|
|
128
|
+
browser.viewer.getSnapshot().parts.some((part) => part.id === 'mouse-look'),
|
|
129
|
+
true,
|
|
130
|
+
);
|
|
131
|
+
assert.equal(
|
|
132
|
+
browser.viewer.getSnapshot().parts.some((part) => part.id === 'sky-grab'),
|
|
133
|
+
false,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
await browser.dispose();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('createSkykitBrowser can disable mouse drag controls', async () => {
|
|
141
|
+
await withFakeWindow(async () => {
|
|
142
|
+
const browser = await createSkykitBrowser({
|
|
143
|
+
host: createHost(),
|
|
144
|
+
status: false,
|
|
145
|
+
renderer: createRenderer(),
|
|
146
|
+
provider: createProvider(),
|
|
147
|
+
starField: createStarField(),
|
|
148
|
+
autoResize: false,
|
|
149
|
+
autoDispose: false,
|
|
150
|
+
autoStart: false,
|
|
151
|
+
mouseMode: 'none',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
assert.equal(
|
|
155
|
+
browser.viewer.getSnapshot().parts.some((part) => part.id === 'mouse-look' || part.id === 'sky-grab'),
|
|
156
|
+
false,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
await browser.dispose();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('browser.install adds plugins after startup and cleans returned teardowns', async () => {
|
|
164
|
+
await withFakeWindow(async () => {
|
|
165
|
+
const calls = [];
|
|
166
|
+
const browser = await createSkykitBrowser({
|
|
167
|
+
host: createHost(),
|
|
168
|
+
status: false,
|
|
169
|
+
renderer: createRenderer(),
|
|
170
|
+
provider: createProvider(),
|
|
171
|
+
starField: createStarField(),
|
|
172
|
+
autoResize: false,
|
|
173
|
+
autoDispose: false,
|
|
174
|
+
autoStart: false,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const uninstall = await browser.install((context) => {
|
|
178
|
+
context.addPart({
|
|
179
|
+
id: 'late-part',
|
|
180
|
+
attach() { calls.push('attach'); },
|
|
181
|
+
start() { calls.push('start'); },
|
|
182
|
+
});
|
|
183
|
+
return () => calls.push('teardown');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await Promise.resolve();
|
|
187
|
+
assert.deepEqual(calls, ['attach', 'start']);
|
|
188
|
+
assert.equal(browser.viewer.getSnapshot().parts.some((part) => part.id === 'late-part'), true);
|
|
189
|
+
uninstall();
|
|
190
|
+
assert.deepEqual(calls, ['attach', 'start', 'teardown']);
|
|
191
|
+
|
|
192
|
+
await browser.dispose();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('browser journey capability transitions through navigation actions and loads instances', async () => {
|
|
197
|
+
await withFakeWindow(async () => {
|
|
198
|
+
const browser = await createSkykitBrowser({
|
|
199
|
+
host: createHost(),
|
|
200
|
+
status: false,
|
|
201
|
+
renderer: createRenderer(),
|
|
202
|
+
provider: createProvider(),
|
|
203
|
+
starField: createStarField(),
|
|
204
|
+
autoResize: false,
|
|
205
|
+
autoDispose: false,
|
|
206
|
+
autoStart: false,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await browser.journey.transitionTo({
|
|
210
|
+
lookAt: { targetPc: { x: 10, y: 0, z: 0 } },
|
|
211
|
+
durationSecs: 1,
|
|
212
|
+
});
|
|
213
|
+
browser.viewer.update(1);
|
|
214
|
+
browser.viewer.update(0);
|
|
215
|
+
assert.ok(browser.viewer.getViewState().orientationIcrs);
|
|
216
|
+
assert.equal(browser.capabilities.has('skykit:navigation'), true);
|
|
217
|
+
|
|
218
|
+
const journey = await browser.journey.load({
|
|
219
|
+
initial: 'home',
|
|
220
|
+
scenes: {
|
|
221
|
+
home: { view: { observerPc: { x: 0, y: 0, z: 0 } } },
|
|
222
|
+
away: { view: { observerPc: { x: 1, y: 2, z: 3 } } },
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
await journey.goTo('away');
|
|
226
|
+
browser.viewer.update(0);
|
|
227
|
+
assert.deepEqual(browser.viewer.getViewState().observerPc, { x: 1, y: 2, z: 3 });
|
|
228
|
+
assert.equal(journey.getSnapshot().disposed, false);
|
|
229
|
+
journey.dispose();
|
|
230
|
+
assert.equal(journey.getSnapshot().disposed, true);
|
|
231
|
+
|
|
232
|
+
await browser.dispose();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('browser constellations capability loads manifest boundaries without art', async () => {
|
|
237
|
+
await withFakeWindow(async () => {
|
|
238
|
+
const browser = await createSkykitBrowser({
|
|
239
|
+
host: createHost(),
|
|
240
|
+
status: false,
|
|
241
|
+
renderer: createRenderer(),
|
|
242
|
+
provider: createProvider(),
|
|
243
|
+
starField: createStarField(),
|
|
244
|
+
autoResize: false,
|
|
245
|
+
autoDispose: false,
|
|
246
|
+
autoStart: false,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const constellations = await browser.constellations.load({
|
|
250
|
+
art: 'off',
|
|
251
|
+
manifest: {
|
|
252
|
+
id: 'test-skyculture',
|
|
253
|
+
boundaries: {
|
|
254
|
+
edges: ['001:002 M+ 00:00:00 +00:00:00 01:00:00 +00:00:00 AAA BBB'],
|
|
255
|
+
},
|
|
256
|
+
constellations: [],
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
assert.equal(constellations.getSnapshot().lineCount, 1);
|
|
261
|
+
assert.equal(browser.capabilities.has('skykit:browser.constellations'), true);
|
|
262
|
+
assert.equal(browser.viewer.roots.observerContentRoot.children.some((child) => child.name === 'constellation-boundaries'), true);
|
|
263
|
+
assert.equal(constellations.hide(), false);
|
|
264
|
+
assert.equal(constellations.show(), true);
|
|
265
|
+
|
|
266
|
+
await browser.dispose();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('Skykit browser global resolves existing and future browsers and installs add-ons once', async () => {
|
|
271
|
+
await withFakeWindow(async () => {
|
|
272
|
+
const service = installSkykitBrowserGlobal(/** @type {typeof globalThis} */ ({}));
|
|
273
|
+
const host = createHost();
|
|
274
|
+
const browser = await createSkykitBrowser({
|
|
275
|
+
host,
|
|
276
|
+
status: false,
|
|
277
|
+
renderer: createRenderer(),
|
|
278
|
+
provider: createProvider(),
|
|
279
|
+
starField: createStarField(),
|
|
280
|
+
autoResize: false,
|
|
281
|
+
autoDispose: false,
|
|
282
|
+
autoStart: false,
|
|
283
|
+
});
|
|
284
|
+
let installs = 0;
|
|
285
|
+
service.registerBrowserAddon({
|
|
286
|
+
id: 'test-addon',
|
|
287
|
+
install({ browser: installedBrowser }) {
|
|
288
|
+
installs += 1;
|
|
289
|
+
assert.equal(installedBrowser, browser);
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const unregister = registerBrowserInstance(service, host, browser);
|
|
294
|
+
assert.equal(await service.whenReady(), browser);
|
|
295
|
+
assert.equal(installs, 1);
|
|
296
|
+
service.registerBrowserAddon({
|
|
297
|
+
id: 'test-addon',
|
|
298
|
+
install() {
|
|
299
|
+
installs += 1;
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
assert.equal(installs, 1);
|
|
303
|
+
unregister();
|
|
304
|
+
await browser.dispose();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
91
308
|
async function withFakeWindow(callback) {
|
|
92
309
|
const previousWindow = globalThis.window;
|
|
93
310
|
const fakeWindow = {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
formatStarLabel,
|
|
6
|
+
loadStarLabels,
|
|
7
|
+
loadStarRows,
|
|
8
|
+
rowsFromStarCells,
|
|
9
|
+
streamStarRows,
|
|
10
|
+
} from '../data.js';
|
|
11
|
+
|
|
12
|
+
test('rowsFromStarCells converts cells to plain app rows', () => {
|
|
13
|
+
const rows = rowsFromStarCells([createCell()], {
|
|
14
|
+
observerPc: { x: 0, y: 0, z: 0 },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
assert.equal(rows.length, 2);
|
|
18
|
+
assert.deepEqual(rows[0].positionPc, { x: 1, y: 2, z: 2 });
|
|
19
|
+
assert.equal(rows[0].distancePc, 3);
|
|
20
|
+
assert.equal(rows[0].apparentMagnitude, 1 + 5 * (Math.log10(3) - 1));
|
|
21
|
+
assert.equal(rows[0].temperatureK, 5800);
|
|
22
|
+
assert.deepEqual(rows[0].ref, {
|
|
23
|
+
datasetId: 'dataset-a',
|
|
24
|
+
level: 1,
|
|
25
|
+
mortonCode: '2',
|
|
26
|
+
ordinal: 0,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('loadStarRows streams provider cells, filters visible rows, and respects maxStars', async () => {
|
|
31
|
+
const provider = createProvider();
|
|
32
|
+
const rows = await loadStarRows({
|
|
33
|
+
provider,
|
|
34
|
+
limitingMagnitude: 4,
|
|
35
|
+
maxStars: 1,
|
|
36
|
+
sortBy: 'distancePc',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
assert.equal(provider.disposed, false);
|
|
40
|
+
assert.equal(provider.streamOptions.length, 1);
|
|
41
|
+
assert.deepEqual(provider.streamOptions[0].view.observerPc, { x: 0, y: 0, z: 0 });
|
|
42
|
+
assert.deepEqual(provider.streamOptions[0].attributes, ['position', 'magAbs', 'teffLog8', 'objectRef']);
|
|
43
|
+
assert.equal(rows.length, 1);
|
|
44
|
+
assert.equal(rows[0].ordinal, 0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('streamStarRows yields row batches without exposing cell deltas', async () => {
|
|
48
|
+
const batches = [];
|
|
49
|
+
for await (const rows of streamStarRows({
|
|
50
|
+
provider: createProvider(),
|
|
51
|
+
limitingMagnitude: 99,
|
|
52
|
+
})) {
|
|
53
|
+
batches.push(rows);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
assert.equal(batches.length, 1);
|
|
57
|
+
assert.equal(batches[0].length, 2);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('loadStarLabels formats metadata labels for rows', async () => {
|
|
61
|
+
const [row] = rowsFromStarCells([createCell()]);
|
|
62
|
+
const labels = await loadStarLabels([row], {
|
|
63
|
+
metaProvider: {
|
|
64
|
+
async getMeta(ref) {
|
|
65
|
+
assert.equal(ref.ordinal, 0);
|
|
66
|
+
return { proper_name: 'Sol', hip_id: 0 };
|
|
67
|
+
},
|
|
68
|
+
dispose() {
|
|
69
|
+
throw new Error('caller-owned meta providers are not disposed');
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
assert.equal(labels.length, 1);
|
|
75
|
+
assert.equal(labels[0].label, 'Sol');
|
|
76
|
+
assert.equal(formatStarLabel(null, 'Fallback'), 'Fallback');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function createCell() {
|
|
80
|
+
return {
|
|
81
|
+
cellKey: '1:2',
|
|
82
|
+
cell: { level: 1, mortonCode: '2' },
|
|
83
|
+
bounds: {
|
|
84
|
+
centerPc: { x: 0, y: 0, z: 0 },
|
|
85
|
+
halfSizePc: 1,
|
|
86
|
+
gridX: 0,
|
|
87
|
+
gridY: 0,
|
|
88
|
+
gridZ: 0,
|
|
89
|
+
},
|
|
90
|
+
count: 2,
|
|
91
|
+
coordinates: {
|
|
92
|
+
name: 'position',
|
|
93
|
+
frame: 'icrs',
|
|
94
|
+
units: ['pc', 'pc', 'pc'],
|
|
95
|
+
components: new Float32Array([
|
|
96
|
+
1, 2, 2,
|
|
97
|
+
20, 0, 0,
|
|
98
|
+
]),
|
|
99
|
+
},
|
|
100
|
+
attributes: {
|
|
101
|
+
magAbs: new Float32Array([1, 9]),
|
|
102
|
+
teffLog8: new Uint8Array([255, 0]),
|
|
103
|
+
},
|
|
104
|
+
refs: [
|
|
105
|
+
{ datasetId: 'dataset-a', level: 1, mortonCode: '2', ordinal: 0 },
|
|
106
|
+
{ datasetId: 'dataset-a', level: 1, mortonCode: '2', ordinal: 1 },
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function createProvider() {
|
|
112
|
+
return {
|
|
113
|
+
disposed: false,
|
|
114
|
+
streamOptions: [],
|
|
115
|
+
async *streamCells(options) {
|
|
116
|
+
this.streamOptions.push(options);
|
|
117
|
+
yield {
|
|
118
|
+
type: 'stars/cells-upsert',
|
|
119
|
+
cells: [createCell()],
|
|
120
|
+
};
|
|
121
|
+
yield {
|
|
122
|
+
type: 'stars/current',
|
|
123
|
+
cellKeys: ['1:2'],
|
|
124
|
+
starCount: 2,
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
dispose() {
|
|
128
|
+
this.disposed = true;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -185,7 +185,7 @@ test('parallax observer moves in the target-relative plane without accumulating
|
|
|
185
185
|
const viewer = await createSkykitViewer({
|
|
186
186
|
view: {
|
|
187
187
|
observerPc: { x: 0, y: 0, z: 0 },
|
|
188
|
-
targetPc: { x: 0, y: 0, z: -10 },
|
|
188
|
+
lookAt: { targetPc: { x: 0, y: 0, z: -10 } },
|
|
189
189
|
},
|
|
190
190
|
plugins: [
|
|
191
191
|
createParallaxObserverPlugin({
|
|
@@ -223,7 +223,7 @@ test('parallax observer smoothing approaches the requested offset', async () =>
|
|
|
223
223
|
const viewer = await createSkykitViewer({
|
|
224
224
|
view: {
|
|
225
225
|
observerPc: { x: 0, y: 0, z: 0 },
|
|
226
|
-
targetPc: { x: 0, y: 0, z: -10 },
|
|
226
|
+
lookAt: { targetPc: { x: 0, y: 0, z: -10 } },
|
|
227
227
|
},
|
|
228
228
|
plugins: [
|
|
229
229
|
createParallaxObserverPlugin({
|
|
@@ -253,7 +253,7 @@ test('parallax observer static upIcrs controls the target-relative up plane', as
|
|
|
253
253
|
const viewer = await createSkykitViewer({
|
|
254
254
|
view: {
|
|
255
255
|
observerPc: { x: 0, y: 0, z: 0 },
|
|
256
|
-
targetPc: { x: 0, y: -10, z: 0 },
|
|
256
|
+
lookAt: { targetPc: { x: 0, y: -10, z: 0 } },
|
|
257
257
|
},
|
|
258
258
|
plugins: [
|
|
259
259
|
createParallaxObserverPlugin({
|
|
@@ -285,7 +285,7 @@ test('parallax observer resolveUpIcrs overrides static up and can change at runt
|
|
|
285
285
|
const viewer = await createSkykitViewer({
|
|
286
286
|
view: {
|
|
287
287
|
observerPc: { x: 0, y: 0, z: 0 },
|
|
288
|
-
targetPc: { x: 0, y: -10, z: 0 },
|
|
288
|
+
lookAt: { targetPc: { x: 0, y: -10, z: 0 } },
|
|
289
289
|
},
|
|
290
290
|
plugins: [
|
|
291
291
|
createParallaxObserverPlugin({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import * as THREE from 'three';
|
|
4
|
+
import { createRuntime } from '@found-in-space/touch-os';
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
SKYKIT_ACTIONS,
|
|
@@ -8,6 +9,8 @@ import {
|
|
|
8
9
|
} from '../index.js';
|
|
9
10
|
import {
|
|
10
11
|
createSkykitShipControlsRoot,
|
|
12
|
+
createSkykitSurfaceApp,
|
|
13
|
+
createSkykitTabletRoot,
|
|
11
14
|
createTouchOsHudPlugin,
|
|
12
15
|
createTouchOsPanelPlugin,
|
|
13
16
|
dispatchTouchOsActionOutputs,
|
|
@@ -72,6 +75,74 @@ test('createSkykitShipControlsRoot builds reusable pseudo-key controls and statu
|
|
|
72
75
|
);
|
|
73
76
|
});
|
|
74
77
|
|
|
78
|
+
test('createSkykitTabletRoot builds a tablet app shell from touch apps', () => {
|
|
79
|
+
const app = createSkykitSurfaceApp({
|
|
80
|
+
id: 'app.surface',
|
|
81
|
+
name: 'Surface',
|
|
82
|
+
node: createSkykitShipControlsRoot({ id: 'surface-child', movePad: false, verticalControls: false }),
|
|
83
|
+
});
|
|
84
|
+
const root = createSkykitTabletRoot({
|
|
85
|
+
id: 'test-tablet',
|
|
86
|
+
apps: [app],
|
|
87
|
+
appStates: { 'app.surface': { ready: true } },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
assert.equal(root.id, 'test-tablet');
|
|
91
|
+
assert.equal(root.component.kind, 'app-shell');
|
|
92
|
+
assert.equal(root.props.presentation.kind, 'tablet-home');
|
|
93
|
+
assert.equal(root.props.appHostMode, 'same-runtime');
|
|
94
|
+
assert.equal(root.props.homeKey, true);
|
|
95
|
+
assert.deepEqual(root.props.registry.list().map((manifest) => manifest.id), ['app.surface']);
|
|
96
|
+
|
|
97
|
+
const runtime = createRuntime({
|
|
98
|
+
root,
|
|
99
|
+
surface: { width: 320, height: 240 },
|
|
100
|
+
});
|
|
101
|
+
const snapshot = runtime.render();
|
|
102
|
+
assert.equal(snapshot.commands.some((command) => command.role === 'tablet-home-button'), true);
|
|
103
|
+
assert.equal(snapshot.commands.some((command) => command.role === 'tablet-home-bar'), false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('createSkykitSurfaceApp wraps display nodes and emits app events', () => {
|
|
107
|
+
const emitted = [];
|
|
108
|
+
const app = createSkykitSurfaceApp({
|
|
109
|
+
id: 'app.hr',
|
|
110
|
+
name: 'HR',
|
|
111
|
+
node: () => createSkykitShipControlsRoot({ id: 'hr-child', movePad: false, verticalControls: false }),
|
|
112
|
+
});
|
|
113
|
+
const instance = app.createApp({
|
|
114
|
+
appId: 'app.hr',
|
|
115
|
+
instanceId: 'app-1',
|
|
116
|
+
windowId: 'app-1-window',
|
|
117
|
+
surface: { width: 420, height: 300, pixelDensity: 1, safeArea: { top: 0, right: 0, bottom: 0, left: 0 } },
|
|
118
|
+
theme: { getTokens() { return {}; } },
|
|
119
|
+
actions: { emit(event) { emitted.push(event); } },
|
|
120
|
+
windows: {
|
|
121
|
+
setTitle() {},
|
|
122
|
+
requestClose() {},
|
|
123
|
+
requestResize() {},
|
|
124
|
+
openApp() {},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const root = instance.render({});
|
|
129
|
+
assert.equal(app.manifest.id, 'app.hr');
|
|
130
|
+
assert.deepEqual(app.manifest.capabilities, ['surfaces']);
|
|
131
|
+
assert.equal(root.component.kind, 'skykit-surface-app-frame');
|
|
132
|
+
assert.equal(root.props.child.id, 'hr-child');
|
|
133
|
+
|
|
134
|
+
instance.handleOutput({ type: 'action', actionId: 'app.fly', componentId: 'fly', payload: { target: 'sun' } });
|
|
135
|
+
assert.deepEqual(emitted, [{
|
|
136
|
+
type: 'app-action',
|
|
137
|
+
appId: 'app.hr',
|
|
138
|
+
instanceId: 'app-1',
|
|
139
|
+
windowId: 'app-1-window',
|
|
140
|
+
name: 'app.fly',
|
|
141
|
+
payload: { target: 'sun' },
|
|
142
|
+
componentId: 'fly',
|
|
143
|
+
}]);
|
|
144
|
+
});
|
|
145
|
+
|
|
75
146
|
test('touch-os pointer helpers resolve screen input and surface metrics', () => {
|
|
76
147
|
const target = createTarget({ width: 640, height: 360, pixelRatio: 3 });
|
|
77
148
|
const metrics = resolveTouchOsSurfaceMetrics(target, { pixelDensity: 1.5 });
|