entangled-worlds-orbiters-shared 3.3.0 → 4.0.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.
Files changed (118) hide show
  1. package/README.md +3 -2
  2. package/dist/config/env.d.ts +8 -0
  3. package/dist/config/env.js +36 -0
  4. package/dist/config/index.d.ts +3 -0
  5. package/dist/config/index.js +4 -0
  6. package/dist/config/textureConfig.d.ts +140 -0
  7. package/dist/config/textureConfig.js +318 -0
  8. package/dist/core/index.d.ts +2 -0
  9. package/dist/core/index.js +3 -0
  10. package/dist/core/lightFresnel.d.ts +6 -0
  11. package/dist/core/lightFresnel.js +63 -0
  12. package/dist/core/lights.d.ts +48 -0
  13. package/dist/core/lights.js +79 -0
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +7 -0
  16. package/dist/loaders/bodyTextureLoader.d.ts +10 -0
  17. package/dist/loaders/bodyTextureLoader.js +28 -0
  18. package/dist/loaders/cubeMapLoader.d.ts +58 -0
  19. package/dist/loaders/cubeMapLoader.js +291 -0
  20. package/dist/loaders/glbLoader.d.ts +24 -0
  21. package/dist/loaders/glbLoader.js +72 -0
  22. package/dist/loaders/index.d.ts +5 -0
  23. package/{src → dist}/loaders/index.js +0 -2
  24. package/dist/{types/loaders → loaders}/ktx2.d.ts +4 -6
  25. package/dist/loaders/ktx2.js +48 -0
  26. package/dist/moons/index.d.ts +3 -0
  27. package/dist/moons/index.js +3 -0
  28. package/dist/moons/moonsGeneration.d.ts +64 -0
  29. package/dist/moons/moonsGeneration.js +238 -0
  30. package/dist/moons/moonsManager.d.ts +43 -0
  31. package/dist/moons/moonsManager.js +93 -0
  32. package/dist/{types/render → render}/frameBudget.d.ts +18 -17
  33. package/dist/render/frameBudget.js +42 -0
  34. package/dist/render/index.d.ts +4 -0
  35. package/dist/render/viewportCompositor.d.ts +124 -0
  36. package/dist/render/viewportCompositor.js +643 -0
  37. package/dist/studio/headerBroadcast.d.ts +10 -1
  38. package/dist/studio/hostTransport.d.ts +2 -2
  39. package/dist/studio/index.d.ts +1 -1
  40. package/dist/utils/dispose.d.ts +5 -0
  41. package/dist/utils/dispose.js +50 -0
  42. package/dist/utils/index.d.ts +3 -0
  43. package/{src → dist}/utils/index.js +1 -1
  44. package/dist/utils/materials.d.ts +10 -0
  45. package/dist/utils/materials.js +36 -0
  46. package/dist/world/cloudShell.d.ts +16 -0
  47. package/dist/world/cloudShell.js +73 -0
  48. package/dist/world/index.d.ts +6 -0
  49. package/{src → dist}/world/index.js +0 -2
  50. package/dist/world/worldGLB.d.ts +17 -0
  51. package/dist/world/worldGLB.js +22 -0
  52. package/dist/world/worldManager.d.ts +57 -0
  53. package/dist/world/worldManager.js +125 -0
  54. package/dist/world/worldNormal.d.ts +30 -0
  55. package/dist/world/worldNormal.js +58 -0
  56. package/dist/world/worldTexture.d.ts +56 -0
  57. package/dist/world/worldTexture.js +299 -0
  58. package/package.json +29 -39
  59. package/src/config/env.ts +48 -0
  60. package/src/config/{index.js → index.ts} +7 -0
  61. package/src/config/{textureConfig.js → textureConfig.ts} +126 -60
  62. package/src/core/{lightFresnel.js → lightFresnel.ts} +1 -1
  63. package/src/core/{lights.js → lights.ts} +21 -21
  64. package/src/loaders/{bodyTextureLoader.js → bodyTextureLoader.ts} +10 -7
  65. package/src/loaders/{cubeMapLoader.js → cubeMapLoader.ts} +83 -49
  66. package/src/loaders/{glbLoader.js → glbLoader.ts} +41 -18
  67. package/src/loaders/index.ts +6 -0
  68. package/src/loaders/{ktx2.js → ktx2.ts} +5 -7
  69. package/src/moons/{index.js → index.ts} +1 -0
  70. package/src/moons/{moonsGeneration.js → moonsGeneration.ts} +45 -32
  71. package/src/moons/{moonsManager.js → moonsManager.ts} +37 -11
  72. package/src/render/{frameBudget.js → frameBudget.ts} +15 -8
  73. package/src/render/index.ts +14 -0
  74. package/src/render/{viewportCompositor.js → viewportCompositor.ts} +121 -59
  75. package/src/studio/headerBroadcast.ts +14 -1
  76. package/src/studio/hostTransport.ts +2 -1
  77. package/src/studio/index.ts +1 -1
  78. package/src/utils/dispose.ts +47 -0
  79. package/src/utils/index.ts +4 -0
  80. package/src/utils/materials.ts +44 -0
  81. package/src/world/{cloudShell.js → cloudShell.ts} +19 -12
  82. package/src/world/index.ts +7 -0
  83. package/src/world/worldGLB.ts +41 -0
  84. package/src/world/{worldManager.js → worldManager.ts} +67 -32
  85. package/src/world/{worldNormal.js → worldNormal.ts} +25 -20
  86. package/src/world/{worldTexture.js → worldTexture.ts} +70 -51
  87. package/dist/types/config/env.d.ts +0 -12
  88. package/dist/types/config/index.d.ts +0 -2
  89. package/dist/types/config/textureConfig.d.ts +0 -121
  90. package/dist/types/core/index.d.ts +0 -2
  91. package/dist/types/core/lightFresnel.d.ts +0 -5
  92. package/dist/types/core/lights.d.ts +0 -58
  93. package/dist/types/index.d.ts +0 -6
  94. package/dist/types/loaders/bodyTextureLoader.d.ts +0 -12
  95. package/dist/types/loaders/cubeMapLoader.d.ts +0 -70
  96. package/dist/types/loaders/glbLoader.d.ts +0 -24
  97. package/dist/types/loaders/index.d.ts +0 -5
  98. package/dist/types/moons/index.d.ts +0 -2
  99. package/dist/types/moons/moonsGeneration.d.ts +0 -72
  100. package/dist/types/moons/moonsManager.d.ts +0 -27
  101. package/dist/types/render/index.d.ts +0 -2
  102. package/dist/types/render/viewportCompositor.d.ts +0 -58
  103. package/dist/types/utils/dispose.d.ts +0 -2
  104. package/dist/types/utils/index.d.ts +0 -2
  105. package/dist/types/utils/materials.d.ts +0 -2
  106. package/dist/types/world/cloudShell.d.ts +0 -25
  107. package/dist/types/world/index.d.ts +0 -6
  108. package/dist/types/world/worldGLB.d.ts +0 -20
  109. package/dist/types/world/worldManager.d.ts +0 -63
  110. package/dist/types/world/worldNormal.d.ts +0 -43
  111. package/dist/types/world/worldTexture.d.ts +0 -80
  112. package/src/config/env.js +0 -38
  113. package/src/utils/dispose.js +0 -39
  114. package/src/utils/materials.js +0 -31
  115. package/src/world/worldGLB.js +0 -19
  116. /package/{src → dist}/render/index.js +0 -0
  117. /package/src/core/{index.js → index.ts} +0 -0
  118. /package/src/{index.js → index.ts} +0 -0
@@ -4,7 +4,57 @@
4
4
  */
5
5
  import { resolvePublicUrl } from './env.js';
6
6
 
7
- function buildTextureUrl(baseUrl, categoryPath, value) {
7
+ /** One catalogue item. */
8
+ export interface TextureItem {
9
+ id: string;
10
+ filename?: string;
11
+ label?: string;
12
+ /** Picture of the item (a thumbnail), relative like `filename`. */
13
+ still?: string;
14
+ /** Texture to use when this one fails to load, relative like `filename`. */
15
+ fallback?: string;
16
+ tags?: string[];
17
+ }
18
+
19
+ /** One catalogue category (clouds, moons, skyboxes, …). */
20
+ export interface TextureCategory {
21
+ category?: string;
22
+ basePath?: string;
23
+ defaultTexture?: string;
24
+ defaultSkybox?: string;
25
+ items?: TextureItem[];
26
+ }
27
+
28
+ /** The texture catalogue as loaded from the config URL. */
29
+ export interface TextureConfig {
30
+ version?: string;
31
+ lastUpdated?: string;
32
+ baseUrl?: string;
33
+ /** By category name; a category the catalogue does not list is missing. */
34
+ textures: Partial<Record<string, TextureCategory>>;
35
+ }
36
+
37
+ /** A catalogue item found by its resolved URL, with the base and path it resolved against. */
38
+ interface FoundTextureItem {
39
+ item: TextureItem;
40
+ baseUrl: string;
41
+ categoryPath: string;
42
+ }
43
+
44
+ /** What `getConfigMetadata` reports about a catalogue. */
45
+ export interface TextureConfigMetadata {
46
+ version: string | undefined;
47
+ lastUpdated: string | undefined;
48
+ categories: string[];
49
+ totalTextures: number;
50
+ }
51
+
52
+ /** The catalogue a public function was handed, or the loaded one. */
53
+ async function resolveConfig(config: TextureConfig | null | undefined): Promise<TextureConfig> {
54
+ return config || (await loadTextureConfig());
55
+ }
56
+
57
+ function buildTextureUrl(baseUrl: string, categoryPath: string, value: string | undefined): string | null {
8
58
  if (!value) return null;
9
59
  if (/^https?:\/\//i.test(value)) return value;
10
60
  if (value.startsWith('/')) return resolvePublicUrl(value);
@@ -12,7 +62,7 @@ function buildTextureUrl(baseUrl, categoryPath, value) {
12
62
  return resolvePublicUrl(`${prefix}/${value}`);
13
63
  }
14
64
 
15
- function findTextureItem(textureConfig, category, textureUrl) {
65
+ function findTextureItem(textureConfig: TextureConfig, category: string, textureUrl: string): FoundTextureItem | null {
16
66
  if (!textureUrl || !textureConfig) return null;
17
67
  const categoryConfig = textureConfig.textures?.[category];
18
68
  if (!categoryConfig || !categoryConfig.items) return null;
@@ -30,17 +80,17 @@ function findTextureItem(textureConfig, category, textureUrl) {
30
80
  return null;
31
81
  }
32
82
 
33
- let cachedConfig = null;
34
- let configPromise = null;
35
- let lastConfigUrl = null;
83
+ let cachedConfig: TextureConfig | null = null;
84
+ let configPromise: Promise<TextureConfig> | null = null;
85
+ let lastConfigUrl: string | null = null;
36
86
 
37
87
  /**
38
88
  * Load texture configuration from remote endpoint or use passed config
39
89
  * @param {string} configUrl - Override config URL
40
90
  * @param {boolean} useCache - Whether to use cached config if available
41
- * @returns {Promise<Object>} Texture configuration object
91
+ * @returns {Promise<TextureConfig>} Texture configuration object
42
92
  */
43
- export async function loadTextureConfig(configUrl = null, useCache = true) {
93
+ export async function loadTextureConfig(configUrl?: string, useCache = true): Promise<TextureConfig> {
44
94
  if (useCache && cachedConfig) {
45
95
  return cachedConfig;
46
96
  }
@@ -67,7 +117,7 @@ export async function loadTextureConfig(configUrl = null, useCache = true) {
67
117
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
68
118
  }
69
119
 
70
- const config = await response.json();
120
+ const config = (await response.json()) as TextureConfig;
71
121
 
72
122
  // Validate configuration structure
73
123
  if (!config.textures || typeof config.textures !== 'object') {
@@ -80,8 +130,9 @@ export async function loadTextureConfig(configUrl = null, useCache = true) {
80
130
  console.error('[TextureConfig] Failed to load config:', error);
81
131
  // Return minimal fallback configuration when loading fails
82
132
  console.warn('[TextureConfig] Using hardcoded fallback config');
83
- cachedConfig = getHardcodedFallback();
84
- return cachedConfig;
133
+ const fallback = getHardcodedFallback();
134
+ cachedConfig = fallback;
135
+ return fallback;
85
136
  } finally {
86
137
  configPromise = null;
87
138
  }
@@ -91,28 +142,36 @@ export async function loadTextureConfig(configUrl = null, useCache = true) {
91
142
  }
92
143
 
93
144
  /**
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.
145
+ * A choice record for one catalogue item.
100
146
  *
101
147
  * Every catalogue item IS a picture, so a chooser should show it rather than its name. `still` is
102
148
  * read straight from the catalogue and never derived from `filename`: the stills do not share one
103
149
  * extension, and a wrong guess fails silently (a broken `<img>` just shows nothing).
104
150
  */
151
+ export type TextureChoice = {
152
+ /** Catalogue id. */
153
+ id: string;
154
+ /** Human-readable name. */
155
+ label: string;
156
+ /** Resolved texture URL (the .ktx2 the world renders). */
157
+ url: string;
158
+ /**
159
+ * Resolved URL of the item's picture — a small thumbnail suitable for showing the texture in a
160
+ * picker. `null` when the catalogue names none.
161
+ */
162
+ still: string | null;
163
+ };
105
164
 
106
165
  /**
107
166
  * Get the full choice records for a category (clouds, moons, etc.) — label, texture URL, and the
108
167
  * picture to show for it. The single resolution point for a category's items; `getTextureOptions`
109
168
  * is the flat `{ label: url }` projection of this.
110
169
  * @param {string} category - Texture category (clouds, moons)
111
- * @param {Object} config - Optional pre-loaded config object
170
+ * @param {TextureConfig} config - Optional pre-loaded config object
112
171
  * @returns {Promise<TextureChoice[]>} Choice records in catalogue order
113
172
  */
114
- export async function getTextureChoices(category, config = null) {
115
- const textureConfig = config || await loadTextureConfig();
173
+ export async function getTextureChoices(category: string, config?: TextureConfig | null): Promise<TextureChoice[]> {
174
+ const textureConfig = await resolveConfig(config);
116
175
  const categoryConfig = textureConfig.textures[category];
117
176
 
118
177
  if (!categoryConfig) {
@@ -123,7 +182,7 @@ export async function getTextureChoices(category, config = null) {
123
182
  const baseUrl = textureConfig.baseUrl || '/default';
124
183
  const categoryPath = categoryConfig.basePath || `/${category}`;
125
184
 
126
- return (categoryConfig.items || []).flatMap(item => {
185
+ return (categoryConfig.items || []).flatMap((item): TextureChoice[] => {
127
186
  const url = buildTextureUrl(baseUrl, categoryPath, item.filename || item.id);
128
187
  if (!url) return [];
129
188
  return [{
@@ -138,10 +197,10 @@ export async function getTextureChoices(category, config = null) {
138
197
  /**
139
198
  * Get texture options for a specific category (clouds, moons, etc.)
140
199
  * @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
200
+ * @param {TextureConfig} config - Optional pre-loaded config object
201
+ * @returns {Promise<Record<string, string>>} `{ label: url }`, suitable for dropdown option lists
143
202
  */
144
- export async function getTextureOptions(category, config = null) {
203
+ export async function getTextureOptions(category: string, config?: TextureConfig | null): Promise<Record<string, string>> {
145
204
  const choices = await getTextureChoices(category, config);
146
205
  return Object.fromEntries(choices.map(c => [c.label, c.url]));
147
206
  }
@@ -149,11 +208,11 @@ export async function getTextureOptions(category, config = null) {
149
208
  /**
150
209
  * Get default texture for a category
151
210
  * @param {string} category - Texture category
152
- * @param {Object} config - Optional pre-loaded config object
153
- * @returns {Promise<string>} Default texture path
211
+ * @param {TextureConfig} config - Optional pre-loaded config object
212
+ * @returns {Promise<string|null>} Default texture path, or null when the category has no texture
154
213
  */
155
- export async function getDefaultTexture(category, config = null) {
156
- const textureConfig = config || await loadTextureConfig();
214
+ export async function getDefaultTexture(category: string, config?: TextureConfig | null): Promise<string | null> {
215
+ const textureConfig = await resolveConfig(config);
157
216
  const categoryConfig = textureConfig.textures[category];
158
217
 
159
218
  if (!categoryConfig) {
@@ -181,10 +240,10 @@ export async function getDefaultTexture(category, config = null) {
181
240
  * Return the configured fallback URL for a given texture if defined.
182
241
  * @param {string} category - Texture category (clouds, moons, etc.)
183
242
  * @param {string} textureUrl - Resolved texture URL
184
- * @param {Object|null} config - Optional texture config override
243
+ * @param {TextureConfig|null} config - Optional texture config override
185
244
  * @returns {string|null} Resolved fallback texture URL or null
186
245
  */
187
- export function getTextureFallbackUrl(category, textureUrl, config = null) {
246
+ export function getTextureFallbackUrl(category: string, textureUrl: string, config: TextureConfig | null = null): string | null {
188
247
  const textureConfig = config || cachedConfig;
189
248
  if (!textureConfig) return null;
190
249
  const found = findTextureItem(textureConfig, category, textureUrl);
@@ -196,11 +255,11 @@ export function getTextureFallbackUrl(category, textureUrl, config = null) {
196
255
  * Get texture by ID within a category
197
256
  * @param {string} category - Texture category
198
257
  * @param {string} textureId - Texture ID
199
- * @param {Object} config - Optional pre-loaded config object
200
- * @returns {Promise<Object|null>} Texture definition object
258
+ * @param {TextureConfig} config - Optional pre-loaded config object
259
+ * @returns {Promise<TextureItem|null>} Texture definition object
201
260
  */
202
- export async function getTextureById(category, textureId, config = null) {
203
- const textureConfig = config || await loadTextureConfig();
261
+ export async function getTextureById(category: string, textureId: string, config?: TextureConfig | null): Promise<TextureItem | null> {
262
+ const textureConfig = await resolveConfig(config);
204
263
  const categoryConfig = textureConfig.textures[category];
205
264
 
206
265
  if (!categoryConfig || !categoryConfig.items) {
@@ -214,15 +273,17 @@ export async function getTextureById(category, textureId, config = null) {
214
273
  * Get full URL for a texture
215
274
  * @param {string} category - Texture category
216
275
  * @param {string} textureId - Texture ID
217
- * @param {Object} config - Optional pre-loaded config object
276
+ * @param {TextureConfig} config - Optional pre-loaded config object
218
277
  * @returns {Promise<string|null>} Full texture URL
219
278
  */
220
- export async function getTextureUrl(category, textureId, config = null) {
279
+ export async function getTextureUrl(category: string, textureId: string, config?: TextureConfig | null): Promise<string | null> {
221
280
  const texture = await getTextureById(category, textureId, config);
222
281
  if (!texture) return null;
223
282
 
224
- const textureConfig = config || await loadTextureConfig();
225
- const categoryConfig = textureConfig.textures[category];
283
+ const textureConfig = await resolveConfig(config);
284
+ // `getTextureById` just found the item in this category, so it is there (unless the catalogue
285
+ // was reloaded in between, which throws here, as it always did).
286
+ const categoryConfig = textureConfig.textures[category]!;
226
287
  const baseUrl = textureConfig.baseUrl || '/default';
227
288
  const categoryPath = categoryConfig.basePath || `/${category}`;
228
289
  return resolvePublicUrl(`${baseUrl}${categoryPath}/${texture.filename || texture.id}`);
@@ -232,11 +293,11 @@ export async function getTextureUrl(category, textureId, config = null) {
232
293
  * Search textures by tags
233
294
  * @param {string} category - Texture category
234
295
  * @param {string[]} tags - Tags to search for
235
- * @param {Object} config - Optional pre-loaded config object
236
- * @returns {Promise<Object[]>} Array of matching texture definitions
296
+ * @param {TextureConfig} config - Optional pre-loaded config object
297
+ * @returns {Promise<TextureItem[]>} Array of matching texture definitions
237
298
  */
238
- export async function searchTexturesByTags(category, tags, config = null) {
239
- const textureConfig = config || await loadTextureConfig();
299
+ export async function searchTexturesByTags(category: string, tags: string[], config?: TextureConfig | null): Promise<TextureItem[]> {
300
+ const textureConfig = await resolveConfig(config);
240
301
  const categoryConfig = textureConfig.textures[category];
241
302
 
242
303
  if (!categoryConfig || !categoryConfig.items) {
@@ -244,17 +305,17 @@ export async function searchTexturesByTags(category, tags, config = null) {
244
305
  }
245
306
 
246
307
  return categoryConfig.items.filter(item =>
247
- item.tags && tags.some(tag => item.tags.includes(tag))
308
+ item.tags && tags.some(tag => item.tags!.includes(tag))
248
309
  );
249
310
  }
250
311
 
251
312
  /**
252
313
  * Get skybox options formatted for GUI dropdowns
253
- * @param {Object} config - Optional pre-loaded config object
254
- * @returns {Promise<Object>} Options object with labels and IDs
314
+ * @param {TextureConfig} config - Optional pre-loaded config object
315
+ * @returns {Promise<Record<string, string>>} `{ label: id }`
255
316
  */
256
- export async function getSkyboxOptions(config = null) {
257
- const textureConfig = config || await loadTextureConfig();
317
+ export async function getSkyboxOptions(config?: TextureConfig | null): Promise<Record<string, string>> {
318
+ const textureConfig = await resolveConfig(config);
258
319
  const skyboxConfig = textureConfig.textures.skyboxes;
259
320
 
260
321
  if (!skyboxConfig) {
@@ -262,9 +323,10 @@ export async function getSkyboxOptions(config = null) {
262
323
  return {};
263
324
  }
264
325
 
265
- const options = {};
326
+ const options: Record<string, string> = {};
266
327
 
267
- skyboxConfig.items.forEach(item => {
328
+ // Reads `items` unguarded, unlike the other readers: a skyboxes category without items throws here.
329
+ skyboxConfig.items!.forEach(item => {
268
330
  const label = item.label || item.id;
269
331
  const id = item.id;
270
332
  options[label] = id;
@@ -275,11 +337,11 @@ export async function getSkyboxOptions(config = null) {
275
337
 
276
338
  /**
277
339
  * Get default skybox ID
278
- * @param {Object} config - Optional pre-loaded config object
340
+ * @param {TextureConfig} config - Optional pre-loaded config object
279
341
  * @returns {Promise<string>} Default skybox ID
280
342
  */
281
- export async function getDefaultSkybox(config = null) {
282
- const textureConfig = config || await loadTextureConfig();
343
+ export async function getDefaultSkybox(config?: TextureConfig | null): Promise<string> {
344
+ const textureConfig = await resolveConfig(config);
283
345
  const skyboxConfig = textureConfig.textures.skyboxes;
284
346
 
285
347
  if (!skyboxConfig) {
@@ -293,7 +355,7 @@ export async function getDefaultSkybox(config = null) {
293
355
  * Hardcoded fallback configuration (minimal)
294
356
  * Used when remote config loading fails
295
357
  */
296
- function getHardcodedFallback() {
358
+ function getHardcodedFallback(): TextureConfig {
297
359
  return {
298
360
  version: "1.0-fallback",
299
361
  lastUpdated: new Date().toISOString(),
@@ -331,27 +393,31 @@ function getHardcodedFallback() {
331
393
  /**
332
394
  * Reload configuration (force refresh)
333
395
  * @param {string} configUrl - Override config URL
334
- * @returns {Promise<Object>} Fresh configuration object
396
+ * @returns {Promise<TextureConfig>} Fresh configuration object
335
397
  */
336
- export async function reloadTextureConfig(configUrl = null) {
398
+ export async function reloadTextureConfig(configUrl?: string): Promise<TextureConfig> {
337
399
  cachedConfig = null;
338
400
  configPromise = null;
339
- return loadTextureConfig(configUrl || lastConfigUrl, false);
401
+ return loadTextureConfig(configUrl || lastConfigUrl || undefined, false);
340
402
  }
341
403
 
342
404
  /**
343
405
  * Get configuration metadata
344
- * @param {Object} config - Optional pre-loaded config object
345
- * @returns {Promise<Object>} Configuration metadata
406
+ * @param {TextureConfig} config - Optional pre-loaded config object
407
+ * @returns {Promise<TextureConfigMetadata>} Configuration metadata
346
408
  */
347
- export async function getConfigMetadata(config = null) {
348
- const textureConfig = config || await loadTextureConfig();
409
+ export async function getConfigMetadata(config?: TextureConfig | null): Promise<TextureConfigMetadata> {
410
+ const textureConfig = await resolveConfig(config);
349
411
  return {
350
412
  version: textureConfig.version,
351
413
  lastUpdated: textureConfig.lastUpdated,
352
414
  categories: Object.keys(textureConfig.textures),
353
415
  totalTextures: Object.values(textureConfig.textures).reduce(
354
- (sum, cat) => sum + (cat.items ? cat.items.length : 0), 0
416
+ // Only listed categories are values; a missing one is a key that is not there.
417
+ (sum, cat) => {
418
+ const items = (cat as TextureCategory).items;
419
+ return sum + (items ? items.length : 0);
420
+ }, 0
355
421
  )
356
422
  };
357
423
  }
@@ -1,6 +1,6 @@
1
1
  import * as THREE from "three";
2
2
 
3
- function getLightFresnel({rimHex = 0x0088ff, facingHex = 0x000000} = {}) {
3
+ function getLightFresnel({ rimHex = 0x0088ff, facingHex = 0x000000 }: { rimHex?: number; facingHex?: number } = {}): THREE.ShaderMaterial {
4
4
  const uniforms = {
5
5
  color1: { value: new THREE.Color(rimHex) },
6
6
  color2: { value: new THREE.Color(facingHex) },
@@ -8,19 +8,17 @@ import * as THREE from 'three';
8
8
  * live in just one scene (adding it to a second removes it from the first), so scenes 2..N rendered
9
9
  * DARK ("only one entangled world"). So create + add the lights PER scene, keyed by name so a
10
10
  * re-call on the same scene is a no-op. Single-orbiter (one scene) is unchanged.
11
- *
12
- * @param {import('three').Scene} scene
13
- * @returns {{ hemi: import('three').HemisphereLight, ambient: import('three').AmbientLight }}
14
11
  */
15
- export function ensureDefaultLights(scene) {
16
- let hemi = scene.getObjectByName('ew_HemisphereLight');
12
+ export function ensureDefaultLights(scene: THREE.Scene): { hemi: THREE.HemisphereLight; ambient: THREE.AmbientLight } {
13
+ // Found by name: the objects under these names are the lights this function added.
14
+ let hemi = scene.getObjectByName('ew_HemisphereLight') as THREE.HemisphereLight | undefined;
17
15
  if (!hemi) {
18
16
  hemi = new THREE.HemisphereLight(0xffffff, 0xffffff, 1.0);
19
17
  hemi.position.set(1, 1, 1);
20
18
  hemi.name = 'ew_HemisphereLight';
21
19
  scene.add(hemi);
22
20
  }
23
- let ambient = scene.getObjectByName('ew_AmbientLight');
21
+ let ambient = scene.getObjectByName('ew_AmbientLight') as THREE.AmbientLight | undefined;
24
22
  if (!ambient) {
25
23
  ambient = new THREE.AmbientLight(0xffffff, 1.0);
26
24
  ambient.name = 'ew_AmbientLight';
@@ -37,16 +35,22 @@ export function ensureDefaultLights(scene) {
37
35
  * them — a shared-renderer page (N feed worlds, N orbiters) lit the wrong world.
38
36
  *
39
37
  * Every field is optional; an omitted one leaves that part of the lighting as it is.
40
- *
41
- * @param {import('three').Scene} scene
42
- * @param {object} [patch]
43
- * @param {number} [patch.ambientIntensity] ambient light intensity (clamped at 0)
44
- * @param {number} [patch.diffuseIntensity] hemisphere light intensity (clamped at 0)
45
- * @param {import('three').Color | number | string} [patch.lightColor] hemisphere light colour
46
- * @param {{ x: number, y?: number, z?: number }} [patch.lightDirection] hemisphere light direction
47
- * @param {boolean} [patch.hemisphere] switch the hemisphere light on or off
48
38
  */
49
- export function updateDefaultLights(scene, { ambientIntensity, diffuseIntensity, lightColor, lightDirection, hemisphere } = {}) {
39
+ export function updateDefaultLights(
40
+ scene: THREE.Scene,
41
+ { ambientIntensity, diffuseIntensity, lightColor, lightDirection, hemisphere }: {
42
+ /** ambient light intensity (clamped at 0) */
43
+ ambientIntensity?: number;
44
+ /** hemisphere light intensity (clamped at 0) */
45
+ diffuseIntensity?: number;
46
+ /** hemisphere light colour */
47
+ lightColor?: THREE.Color | number | string;
48
+ /** hemisphere light direction */
49
+ lightDirection?: { x: number; y?: number; z?: number };
50
+ /** switch the hemisphere light on or off */
51
+ hemisphere?: boolean;
52
+ } = {},
53
+ ): void {
50
54
  if (!scene?.isObject3D) {
51
55
  console.error('[lights] updateDefaultLights(scene, patch) requires the scene to update; ignoring patch.');
52
56
  return;
@@ -75,18 +79,14 @@ export function updateDefaultLights(scene, { ambientIntensity, diffuseIntensity,
75
79
  /**
76
80
  * Switch a scene's default hemisphere light on or off (the ambient light is untouched). Hosts turn it
77
81
  * off when a directional light — the star — lights the scene, so night sides can read.
78
- *
79
- * @param {import('three').Scene} scene
80
- * @param {boolean} on
81
82
  */
82
- export function setHemisphereEnabled(scene, on) {
83
+ export function setHemisphereEnabled(scene: THREE.Scene, on: boolean): void {
83
84
  if (!scene?.isObject3D) return;
84
85
  const { hemi } = ensureDefaultLights(scene);
85
86
  hemi.visible = Boolean(on);
86
87
  }
87
88
 
88
89
  // Back-compat helper
89
- /** @param {import('three').Scene} scene */
90
- export function addDefaultLights(scene) {
90
+ export function addDefaultLights(scene: THREE.Scene): { hemi: THREE.HemisphereLight; ambient: THREE.AmbientLight } {
91
91
  return ensureDefaultLights(scene);
92
92
  }
@@ -4,16 +4,19 @@ import { getKTX2LoaderForRenderer } from './ktx2.js';
4
4
  const textureLoader = new THREE.TextureLoader();
5
5
 
6
6
  /**
7
- * @param {string} url `.ktx2` goes through the renderer's KTX2 transcoder; anything else through
8
- * THREE.TextureLoader.
9
- * @param {object} [opts]
10
- * @param {AbortSignal} [opts.signal] an abort during the load rejects the promise.
11
- * @param {import('three').WebGLRenderer | null} [opts.renderer] REQUIRED for a `.ktx2` url.
7
+ * `url`: `.ktx2` goes through the renderer's KTX2 transcoder; anything else through
8
+ * THREE.TextureLoader. `signal`: an abort during the load rejects the promise. `renderer`: REQUIRED
9
+ * for a `.ktx2` url.
12
10
  */
13
- export function loadTexture(url, { signal, renderer } = {}) {
11
+ export function loadTexture(
12
+ url: string,
13
+ { signal, renderer }: { signal?: AbortSignal | undefined; renderer?: THREE.WebGLRenderer | null | undefined } = {},
14
+ ): Promise<THREE.Texture> {
14
15
  return new Promise((resolve, reject) => {
15
16
  if (url && url.endsWith('.ktx2')) {
16
- const ktx2 = getKTX2LoaderForRenderer(renderer);
17
+ // A missing renderer throws inside (as before): the type allows `null` only because the
18
+ // non-KTX2 path never needs one.
19
+ const ktx2 = getKTX2LoaderForRenderer(renderer as THREE.WebGLRenderer);
17
20
  const onAbort = () => reject(new Error('aborted'));
18
21
  if (signal) signal.addEventListener('abort', onAbort, { once: true });
19
22
  ktx2.load(