entangled-worlds-orbiters-shared 2.8.0 → 2.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entangled-worlds-orbiters-shared",
3
- "version": "2.8.0",
3
+ "version": "2.9.1",
4
4
  "description": "Shared library for entangled worlds orbiters - contains core world rendering, moons system, and UI components",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -11,6 +11,7 @@
11
11
  {
12
12
  "id": "water-drops",
13
13
  "filename": "18-water-drops.ktx2",
14
+ "still": "thumbs/18-water-drops.jpg",
14
15
  "label": "Water Drops",
15
16
  "description": "Atmospheric water droplets",
16
17
  "tags": ["atmospheric", "water", "natural"]
@@ -18,6 +19,7 @@
18
19
  {
19
20
  "id": "scream-1",
20
21
  "filename": "17-scream.ktx2",
22
+ "still": "thumbs/17-scream.jpg",
21
23
  "label": "Scream",
22
24
  "description": "Abstract screaming patterns",
23
25
  "tags": ["abstract", "artistic"]
@@ -25,6 +27,7 @@
25
27
  {
26
28
  "id": "scream-2",
27
29
  "filename": "16-scream.ktx2",
30
+ "still": "thumbs/16-scream.jpg",
28
31
  "label": "Scream Alt",
29
32
  "description": "Alternative screaming patterns",
30
33
  "tags": ["abstract", "artistic"]
@@ -32,6 +35,7 @@
32
35
  {
33
36
  "id": "neon-lights-1",
34
37
  "filename": "15-neon-lights.ktx2",
38
+ "still": "thumbs/15-neon-lights.jpg",
35
39
  "label": "Neon Lights",
36
40
  "description": "Electric neon lighting effects",
37
41
  "tags": ["electric", "urban", "artificial"]
@@ -39,6 +43,7 @@
39
43
  {
40
44
  "id": "neon-lights-2",
41
45
  "filename": "14-neon-lights.ktx2",
46
+ "still": "thumbs/14-neon-lights.jpg",
42
47
  "label": "Neon Lights Alt",
43
48
  "description": "Alternative neon lighting",
44
49
  "tags": ["electric", "urban", "artificial"]
@@ -46,6 +51,7 @@
46
51
  {
47
52
  "id": "neon-lights-3",
48
53
  "filename": "13-neon-lights.ktx2",
54
+ "still": "thumbs/13-neon-lights.jpg",
49
55
  "label": "Neon Lights Pro",
50
56
  "description": "Professional neon effects",
51
57
  "tags": ["electric", "urban", "artificial"]
@@ -53,6 +59,7 @@
53
59
  {
54
60
  "id": "marble-1",
55
61
  "filename": "12-marble.ktx2",
62
+ "still": "thumbs/12-marble.jpg",
56
63
  "label": "Marble",
57
64
  "description": "Natural marble patterns",
58
65
  "tags": ["stone", "natural", "elegant"]
@@ -60,6 +67,7 @@
60
67
  {
61
68
  "id": "marble-2",
62
69
  "filename": "11-marble.ktx2",
70
+ "still": "thumbs/11-marble.jpg",
63
71
  "label": "Marble Alt",
64
72
  "description": "Alternative marble texture",
65
73
  "tags": ["stone", "natural", "elegant"]
@@ -67,6 +75,7 @@
67
75
  {
68
76
  "id": "isolines-1",
69
77
  "filename": "10-isolines.ktx2",
78
+ "still": "thumbs/10-isolines.jpg",
70
79
  "label": "Isolines",
71
80
  "description": "Topographic isolines",
72
81
  "tags": ["geometric", "lines", "technical"]
@@ -74,6 +83,7 @@
74
83
  {
75
84
  "id": "isolines-2",
76
85
  "filename": "09-isolines.ktx2",
86
+ "still": "thumbs/09-isolines.jpg",
77
87
  "label": "Isolines Alt",
78
88
  "description": "Alternative isolines pattern",
79
89
  "tags": ["geometric", "lines", "technical"]
@@ -81,6 +91,7 @@
81
91
  {
82
92
  "id": "fordite-1",
83
93
  "filename": "08-fordite.ktx2",
94
+ "still": "thumbs/08-fordite.jpg",
84
95
  "label": "Fordite",
85
96
  "description": "Industrial fordite patterns",
86
97
  "tags": ["industrial", "layered", "colorful"]
@@ -88,6 +99,7 @@
88
99
  {
89
100
  "id": "fordite-2",
90
101
  "filename": "07-fordite.ktx2",
102
+ "still": "thumbs/07-fordite.jpg",
91
103
  "label": "Fordite Alt",
92
104
  "description": "Alternative fordite texture",
93
105
  "tags": ["industrial", "layered", "colorful"]
@@ -95,6 +107,7 @@
95
107
  {
96
108
  "id": "entangled-1",
97
109
  "filename": "06-entangled.ktx2",
110
+ "still": "thumbs/06-entangled.jpg",
98
111
  "label": "Entangled",
99
112
  "description": "Quantum entangled patterns",
100
113
  "tags": ["quantum", "abstract", "complex"]
@@ -102,6 +115,7 @@
102
115
  {
103
116
  "id": "entangled-2",
104
117
  "filename": "05-entangled.ktx2",
118
+ "still": "thumbs/05-entangled.jpg",
105
119
  "label": "Entangled Alt",
106
120
  "description": "Alternative entangled patterns",
107
121
  "tags": ["quantum", "abstract", "complex"]
@@ -109,6 +123,7 @@
109
123
  {
110
124
  "id": "clouds-1",
111
125
  "filename": "04-clouds.ktx2",
126
+ "still": "thumbs/04-clouds.jpg",
112
127
  "label": "Clouds",
113
128
  "description": "Natural cloud formations",
114
129
  "tags": ["natural", "atmospheric", "default"]
@@ -116,6 +131,7 @@
116
131
  {
117
132
  "id": "clouds-2",
118
133
  "filename": "03-clouds.ktx2",
134
+ "still": "thumbs/03-clouds.jpg",
119
135
  "label": "Clouds Alt",
120
136
  "description": "Alternative cloud patterns",
121
137
  "tags": ["natural", "atmospheric"]
@@ -123,6 +139,7 @@
123
139
  {
124
140
  "id": "cave-art-1",
125
141
  "filename": "02-cave-art.ktx2",
142
+ "still": "thumbs/02-cave-art.jpg",
126
143
  "label": "Cave Art",
127
144
  "description": "Ancient cave paintings",
128
145
  "tags": ["ancient", "artistic", "historic"]
@@ -130,6 +147,7 @@
130
147
  {
131
148
  "id": "cave-art-2",
132
149
  "filename": "01-cave-art.ktx2",
150
+ "still": "thumbs/01-cave-art.jpg",
133
151
  "label": "Cave Art Alt",
134
152
  "description": "Alternative cave art patterns",
135
153
  "tags": ["ancient", "artistic", "historic"]
@@ -144,14 +162,16 @@
144
162
  {
145
163
  "id": "electric-moon",
146
164
  "filename": "moon-equirectangular.ktx2",
165
+ "still": "thumbs/moon-equirectangular.jpg",
147
166
  "label": "Electric Moon",
148
167
  "description": "Standard lunar surface texture",
149
168
  "tags": ["lunar", "realistic", "default"],
150
- "fallback": "moon-equirectangular.jpg"
169
+ "fallback": "moon-equirectangular.png"
151
170
  },
152
171
  {
153
172
  "id": "earth-moon",
154
173
  "filename": "2k_moon.ktx2",
174
+ "still": "thumbs/2k_moon.jpg",
155
175
  "label": "Earth Moon",
156
176
  "description": "Realistic Earth satellite texture",
157
177
  "tags": ["lunar", "earth", "satellite"],
@@ -160,6 +180,7 @@
160
180
  {
161
181
  "id": "crater-lines",
162
182
  "filename": "crater-lines.ktx2",
183
+ "still": "thumbs/crater-lines.jpg",
163
184
  "label": "Crater Lines",
164
185
  "description": "Linear crater formations across the lunar surface",
165
186
  "tags": ["craters", "lunar", "pattern"],
@@ -168,6 +189,7 @@
168
189
  {
169
190
  "id": "moon-dust",
170
191
  "filename": "moon-dust.ktx2",
192
+ "still": "thumbs/moon-dust.jpg",
171
193
  "label": "Moon Dust",
172
194
  "description": "Fine dust texture inspired by lunar regolith",
173
195
  "tags": ["dust", "surface", "regolith"],
@@ -176,6 +198,7 @@
176
198
  {
177
199
  "id": "lunar-scepter",
178
200
  "filename": "lunar-scepter.ktx2",
201
+ "still": "thumbs/lunar-scepter.jpg",
179
202
  "label": "Lunar Scepter",
180
203
  "description": "Sharp, vertical ridge texture reminiscent of lunar highlands",
181
204
  "tags": ["ridges", "highlands", "lunar"],
@@ -184,6 +207,7 @@
184
207
  {
185
208
  "id": "satin-moon",
186
209
  "filename": "satin-moon.ktx2",
210
+ "still": "thumbs/satin-moon.jpg",
187
211
  "label": "Satin Moon",
188
212
  "description": "Smooth satin-like lunar surface",
189
213
  "tags": ["smooth", "lunar", "texture"],
@@ -192,6 +216,7 @@
192
216
  {
193
217
  "id": "impact-craters",
194
218
  "filename": "impact-craters.ktx2",
219
+ "still": "thumbs/impact-craters.jpg",
195
220
  "label": "Impact Craters",
196
221
  "description": "Dense field of lunar craters",
197
222
  "tags": ["craters", "impact", "texture"],
@@ -200,6 +225,7 @@
200
225
  {
201
226
  "id": "eclipse-dots",
202
227
  "filename": "eclipse-dots.ktx2",
228
+ "still": "thumbs/eclipse-dots.jpg",
203
229
  "label": "Eclipse Dots",
204
230
  "description": "Pattern of scattered dots inspired by lunar eclipses",
205
231
  "tags": ["dots", "eclipse", "pattern"],
@@ -208,6 +234,7 @@
208
234
  {
209
235
  "id": "shadow-dots",
210
236
  "filename": "shadow-dots.ktx2",
237
+ "still": "thumbs/shadow-dots.jpg",
211
238
  "label": "Shadow Dots",
212
239
  "description": "Dot textures evoking shadows over the lunar surface",
213
240
  "tags": ["dots", "shadows", "surface"],
@@ -216,6 +243,7 @@
216
243
  {
217
244
  "id": "moonrise",
218
245
  "filename": "moonrise.ktx2",
246
+ "still": "thumbs/moonrise.jpg",
219
247
  "label": "Moonrise",
220
248
  "description": "Atmospheric gradient evoking moonrise",
221
249
  "tags": ["moonrise", "gradient", "lunar"],
@@ -224,6 +252,7 @@
224
252
  {
225
253
  "id": "midnight-moon",
226
254
  "filename": "midnight-moon.ktx2",
255
+ "still": "thumbs/midnight-moon.jpg",
227
256
  "label": "Midnight Moon",
228
257
  "description": "Dark-toned lunar surface texture",
229
258
  "tags": ["midnight", "dark", "lunar"],
@@ -232,6 +261,7 @@
232
261
  {
233
262
  "id": "pale-orbit",
234
263
  "filename": "pale-orbit.ktx2",
264
+ "still": "thumbs/pale-orbit.jpg",
235
265
  "label": "Pale Orbit",
236
266
  "description": "Light lunar surface with subtle orbital tones",
237
267
  "tags": ["orbit", "pale", "surface"],
@@ -240,6 +270,7 @@
240
270
  {
241
271
  "id": "tidal-glow",
242
272
  "filename": "tidal-glow.ktx2",
273
+ "still": "thumbs/tidal-glow.jpg",
243
274
  "label": "Tidal Glow",
244
275
  "description": "Soft glowing lunar texture reflecting tidal pull",
245
276
  "tags": ["glow", "tidal", "lunar"],
@@ -248,6 +279,7 @@
248
279
  {
249
280
  "id": "silver-glow",
250
281
  "filename": "silver-glow.ktx2",
282
+ "still": "thumbs/silver-glow.jpg",
251
283
  "label": "Silver Glow",
252
284
  "description": "Bright silver surface with reflective highlights",
253
285
  "tags": ["silver", "glow", "lunar"],
@@ -1,8 +1,9 @@
1
1
  // Configuration exports
2
2
  export {
3
- loadTextureConfig,
4
- getTextureOptions,
5
- getDefaultTexture,
3
+ loadTextureConfig,
4
+ getTextureOptions,
5
+ getTextureChoices,
6
+ getDefaultTexture,
6
7
  getSkyboxOptions,
7
8
  getDefaultSkybox,
8
9
  getTextureFallbackUrl
@@ -91,33 +91,59 @@ export async function loadTextureConfig(configUrl = null, useCache = true) {
91
91
  }
92
92
 
93
93
  /**
94
- * Get texture options for a specific category (clouds, moons, etc.)
95
- * @param {string} category - Texture category (clouds, moons, skyboxes)
94
+ * @typedef {Object} TextureChoice
95
+ * @property {string} id Catalogue id.
96
+ * @property {string} label Human-readable name.
97
+ * @property {string} url Resolved texture URL (the .ktx2 the world renders).
98
+ * @property {string|null} still Resolved URL of the item's picture — a small thumbnail suitable
99
+ * for showing the texture in a picker. `null` when the catalogue names none.
100
+ *
101
+ * Every catalogue item IS a picture, so a chooser should show it rather than its name. `still` is
102
+ * read straight from the catalogue and never derived from `filename`: the stills do not share one
103
+ * extension, and a wrong guess fails silently (a broken `<img>` just shows nothing).
104
+ */
105
+
106
+ /**
107
+ * Get the full choice records for a category (clouds, moons, etc.) — label, texture URL, and the
108
+ * picture to show for it. The single resolution point for a category's items; `getTextureOptions`
109
+ * is the flat `{ label: url }` projection of this.
110
+ * @param {string} category - Texture category (clouds, moons)
96
111
  * @param {Object} config - Optional pre-loaded config object
97
- * @returns {Promise<Object>} Options object suitable for dropdown option lists
112
+ * @returns {Promise<TextureChoice[]>} Choice records in catalogue order
98
113
  */
99
- export async function getTextureOptions(category, config = null) {
114
+ export async function getTextureChoices(category, config = null) {
100
115
  const textureConfig = config || await loadTextureConfig();
101
116
  const categoryConfig = textureConfig.textures[category];
102
-
117
+
103
118
  if (!categoryConfig) {
104
119
  console.warn(`[TextureConfig] Category '${category}' not found`);
105
- return {};
120
+ return [];
106
121
  }
107
122
 
108
- const options = {};
109
123
  const baseUrl = textureConfig.baseUrl || '/default';
110
124
  const categoryPath = categoryConfig.basePath || `/${category}`;
111
125
 
112
- categoryConfig.items.forEach(item => {
113
- const key = item.label || item.id;
114
- const resolved = buildTextureUrl(baseUrl, categoryPath, item.filename || item.id);
115
- if (resolved) {
116
- options[key] = resolved;
117
- }
126
+ return (categoryConfig.items || []).flatMap(item => {
127
+ const url = buildTextureUrl(baseUrl, categoryPath, item.filename || item.id);
128
+ if (!url) return [];
129
+ return [{
130
+ id: item.id,
131
+ label: item.label || item.id,
132
+ url,
133
+ still: item.still ? buildTextureUrl(baseUrl, categoryPath, item.still) : null,
134
+ }];
118
135
  });
136
+ }
119
137
 
120
- return options;
138
+ /**
139
+ * Get texture options for a specific category (clouds, moons, etc.)
140
+ * @param {string} category - Texture category (clouds, moons, skyboxes)
141
+ * @param {Object} config - Optional pre-loaded config object
142
+ * @returns {Promise<Object>} Options object suitable for dropdown option lists
143
+ */
144
+ export async function getTextureOptions(category, config = null) {
145
+ const choices = await getTextureChoices(category, config);
146
+ return Object.fromEntries(choices.map(c => [c.label, c.url]));
121
147
  }
122
148
 
123
149
  /**
@@ -56,25 +56,47 @@ function detectExtension(faceKeys) {
56
56
  return 'ktx2'; // default assumption
57
57
  }
58
58
 
59
+ // How many faces may be in flight at once. Not six: the KTX2 worker pool three.js gives us holds
60
+ // four transcoder workers, each carrying its own copy of the basis wasm, and that same pool also
61
+ // serves body textures and GLBs. Asking for all six together spawned every worker and queued the
62
+ // other loaders behind the whole sky; measured on cold fetches it was no faster than three.
63
+ const FACES_IN_FLIGHT = 3;
64
+
59
65
  // Load KTX2 faces and create skybox mesh
60
66
  async function loadFacesKTX2(faceKeys, renderer) {
61
67
  const loader = getKTX2LoaderForRenderer(renderer);
68
+ const anisotropy = Math.min(8, renderer.capabilities.getMaxAnisotropy());
69
+
70
+ const loadFace = async face => {
71
+ const tex = await loader.loadAsync(faceKeys[face]);
72
+ tex.flipY = false;
73
+ tex.wrapS = THREE.ClampToEdgeWrapping;
74
+ tex.wrapT = THREE.ClampToEdgeWrapping;
75
+ tex.anisotropy = anisotropy;
76
+ return { face, tex };
77
+ };
78
+
79
+ // Faces overlap instead of chaining. Awaiting each one in turn made every face wait on the
80
+ // previous face's download *and* its transcode, so the sky arrived six round trips deep.
81
+ const settled = [];
82
+ for (let i = 0; i < FACE_ORDER.length; i += FACES_IN_FLIGHT) {
83
+ const wave = FACE_ORDER.slice(i, i + FACES_IN_FLIGHT).map(face => loadFace(face));
84
+ settled.push(...await Promise.allSettled(wave));
85
+ }
86
+
87
+ // allSettled, not all: a bare Promise.all rejects on the first failure and leaks the faces that
88
+ // land after it. Settle every face first, then dispose everything that arrived before rethrowing.
62
89
  const loaded = [];
63
-
64
- for (const face of FACE_ORDER) {
65
- const url = faceKeys[face];
66
- try {
67
- const tex = await loader.loadAsync(url);
68
- tex.flipY = false;
69
- tex.wrapS = THREE.ClampToEdgeWrapping;
70
- tex.wrapT = THREE.ClampToEdgeWrapping;
71
- tex.anisotropy = Math.min(8, renderer.capabilities.getMaxAnisotropy());
72
- loaded.push({ face, tex });
73
- } catch (e) {
74
- // Dispose already loaded textures on failure
75
- loaded.forEach(f => f.tex.dispose?.());
76
- throw e;
77
- }
90
+ let failure = null;
91
+ for (const result of settled) {
92
+ if (result.status === 'fulfilled') loaded.push(result.value);
93
+ else if (!failure) failure = result.reason;
94
+ }
95
+
96
+ if (failure) {
97
+ // Dispose already loaded textures on failure
98
+ loaded.forEach(f => f.tex.dispose?.());
99
+ throw failure;
78
100
  }
79
101
 
80
102
  const size = 500;