entangled-worlds-orbiters-shared 6.0.0 → 6.0.2

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.
@@ -12,7 +12,7 @@ export interface ShortcutSheetRow extends ShortcutNamed {
12
12
  export interface ShortcutSheetColumn extends ShortcutNamed {
13
13
  readonly rows: readonly ShortcutSheetRow[];
14
14
  }
15
- /** One surface's keys (`orbiters-play`, `ew-studio`, `site`). Two actions clash only inside a section. */
15
+ /** One surface's keys (`orbiters-play`, `ew-clip-studio`, `root`). Two actions clash only inside a section. */
16
16
  export interface ShortcutSection {
17
17
  readonly id: string;
18
18
  /** The app it belongs to (`orbiters`) and that app's name. */
@@ -54,7 +54,7 @@ export declare function localName(item: {
54
54
  }, locale?: string): string;
55
55
  /** A section's app name in a language. */
56
56
  export declare function localAppName(section: Pick<ShortcutSection, 'appLabel' | 'appNames'>, locale?: string): string;
57
- /** A section's part name (`Play`, `Studio`) in a language; `undefined` when it has none. */
57
+ /** A section's part name (`Play`, `Orbiter Studio`) in a language; `undefined` when it has none. */
58
58
  export declare function localPartName(section: Pick<ShortcutSection, 'part' | 'partNames'>, locale?: string): string | undefined;
59
59
  /** The apps the catalogue has, in its order. */
60
60
  export declare function shortcutApps(catalogue: ShortcutCatalogue): ReadonlyArray<{
@@ -82,7 +82,7 @@ export function localName(item, locale) {
82
82
  export function localAppName(section, locale) {
83
83
  return localName({ label: section.appLabel, names: section.appNames }, locale);
84
84
  }
85
- /** A section's part name (`Play`, `Studio`) in a language; `undefined` when it has none. */
85
+ /** A section's part name (`Play`, `Orbiter Studio`) in a language; `undefined` when it has none. */
86
86
  export function localPartName(section, locale) {
87
87
  return section.part === undefined ? undefined : localName({ label: section.part, names: section.partNames }, locale);
88
88
  }
@@ -1,4 +1,12 @@
1
1
  import * as THREE from 'three';
2
+ /**
3
+ * The cloud texture could not be loaded (offline, gone from the CDN, or not decodable). Its own type
4
+ * so the world builders can drop just the clouds on THIS failure and still let any other error out.
5
+ * `cause` is what the loader threw.
6
+ */
7
+ export declare class CloudTextureError extends Error {
8
+ constructor(url: string, cause: unknown);
9
+ }
2
10
  /**
3
11
  * `baseGeometry` is cloned for the shell; when absent a sphere is built from `baseRadius` (0.5) and
4
12
  * the segment counts (64 × 64). Defaults: `color` 0xffffff, `opacity` 0.6, `speed` 0.015.
@@ -1,5 +1,16 @@
1
1
  import * as THREE from 'three';
2
2
  import { loadTexture } from '../loaders/bodyTextureLoader.js';
3
+ /**
4
+ * The cloud texture could not be loaded (offline, gone from the CDN, or not decodable). Its own type
5
+ * so the world builders can drop just the clouds on THIS failure and still let any other error out.
6
+ * `cause` is what the loader threw.
7
+ */
8
+ export class CloudTextureError extends Error {
9
+ constructor(url, cause) {
10
+ super(`Cloud texture could not be loaded: ${url}`, { cause });
11
+ this.name = 'CloudTextureError';
12
+ }
13
+ }
3
14
  // Texture-only clouds material (alpha mask + tint), UV-scrolled
4
15
  async function makeTextureMaterial({ color = 0xffffff, opacity = 0.6, textureUrl, speed = 0.015, renderer = null, } = {}) {
5
16
  // Diffuse-only (Lambert): the star's key light costs no specular pass.
@@ -15,7 +26,14 @@ async function makeTextureMaterial({ color = 0xffffff, opacity = 0.6, textureUrl
15
26
  if (textureUrl) {
16
27
  // Thread the owning view's renderer: KTX2 cloud textures must transcode against the caller's
17
28
  // context, not the global fallback (which shared-renderer pages don't publish at all).
18
- const tex = await loadTexture(textureUrl, { renderer });
29
+ let tex;
30
+ try {
31
+ tex = await loadTexture(textureUrl, { renderer });
32
+ }
33
+ catch (cause) {
34
+ mat.dispose();
35
+ throw new CloudTextureError(textureUrl, cause);
36
+ }
19
37
  tex.wrapS = THREE.RepeatWrapping;
20
38
  tex.wrapT = THREE.RepeatWrapping;
21
39
  // Use linear space for alpha masks if available
@@ -48,9 +66,7 @@ async function makeTextureMaterial({ color = 0xffffff, opacity = 0.6, textureUrl
48
66
  * the segment counts (64 × 64). Defaults: `color` 0xffffff, `opacity` 0.6, `speed` 0.015.
49
67
  */
50
68
  export async function createCloudShell({ baseGeometry, baseRadius = 0.5, widthSegments = 64, heightSegments = 64, textureUrl = '/assets/texture/2k_earth_clouds.jpg', color = 0xffffff, opacity = 0.6, speed = 0.015, renderer = null, } = {}) {
51
- const geom = baseGeometry
52
- ? baseGeometry.clone()
53
- : new THREE.SphereGeometry(baseRadius, widthSegments, heightSegments);
69
+ // The texture first: when it cannot be loaded this throws before any geometry exists to leak.
54
70
  const material = await makeTextureMaterial({
55
71
  color,
56
72
  opacity,
@@ -58,6 +74,9 @@ export async function createCloudShell({ baseGeometry, baseRadius = 0.5, widthSe
58
74
  speed,
59
75
  renderer,
60
76
  });
77
+ const geom = baseGeometry
78
+ ? baseGeometry.clone()
79
+ : new THREE.SphereGeometry(baseRadius, widthSegments, heightSegments);
61
80
  const mesh = new THREE.Mesh(geom, material);
62
81
  const s = 1.001; // fixed scale
63
82
  mesh.scale.set(s, s, s);
@@ -3,7 +3,7 @@ import * as THREE from 'three';
3
3
  import { EARTH_RADIUS_UNITS } from './worldTexture.js';
4
4
  import { applyFlagsToMaterial } from '../utils/materials.js';
5
5
  import { getLightFresnel } from '../core/lightFresnel.js';
6
- import { createCloudShell } from './cloudShell.js';
6
+ import { CloudTextureError, createCloudShell } from './cloudShell.js';
7
7
  import { resolvePublicUrl } from '../config/env.js';
8
8
  /**
9
9
  * Create a simple sphere using THREE.MeshNormalMaterial.
@@ -51,8 +51,17 @@ glowScale = 1.18, glowRimHex = 0x66ccff, glowFacingHex = 0x000000, glowBias = 0.
51
51
  opacity: cloudsOpacity,
52
52
  speed: cloudsSpeed,
53
53
  renderer,
54
+ }).catch((e) => {
55
+ // Clouds are an optional layer: a texture that cannot be loaded (offline, or gone from the
56
+ // CDN) costs the clouds, never the planet — the same way a failed surface texture does. Only
57
+ // that: any other error building the shell is a bug and still fails the build.
58
+ if (!(e instanceof CloudTextureError))
59
+ throw e;
60
+ console.warn('[worldNormal] Failed to load clouds, drawing the world without them:', e.cause);
61
+ return null;
54
62
  });
55
- mesh.add(cloudMesh);
63
+ if (cloudMesh)
64
+ mesh.add(cloudMesh);
56
65
  }
57
66
  return mesh;
58
67
  }
@@ -2,7 +2,7 @@ import * as THREE from 'three';
2
2
  import { loadTexture } from '../loaders/bodyTextureLoader.js';
3
3
  import { applyFlagsToMaterial } from '../utils/materials.js';
4
4
  import { getLightFresnel } from '../core/lightFresnel.js';
5
- import { createCloudShell } from './cloudShell.js';
5
+ import { CloudTextureError, createCloudShell } from './cloudShell.js';
6
6
  import { resolvePublicUrl } from '../config/env.js';
7
7
  // Debug tag so you can verify the loaded file version in console
8
8
  export const WORLD_TEXTURE_DEBUG_TAG = 'WT:vFlipGeom-r3@2025-10-28T17:22:00Z';
@@ -287,8 +287,17 @@ cloudsEnabled = false, cloudsTextureUrl = '/assets/texture/2k_earth_clouds.jpg',
287
287
  opacity: cloudsOpacity,
288
288
  speed: cloudsSpeed,
289
289
  renderer,
290
+ }).catch((e) => {
291
+ // Clouds are an optional layer: a texture that cannot be loaded (offline, or gone from the
292
+ // CDN) costs the clouds, never the planet — the same way a failed surface texture does. Only
293
+ // that: any other error building the shell is a bug and still fails the build.
294
+ if (!(e instanceof CloudTextureError))
295
+ throw e;
296
+ console.warn('[worldTexture] Failed to load clouds, drawing the world without them:', e.cause);
297
+ return null;
290
298
  });
291
- mesh.add(cloudMesh);
299
+ if (cloudMesh)
300
+ mesh.add(cloudMesh);
292
301
  }
293
302
  // Optionally add glow as a child of the mesh so it follows transforms
294
303
  if (glow) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entangled-worlds-orbiters-shared",
3
- "version": "6.0.0",
3
+ "version": "6.0.2",
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": "dist/index.js",
@@ -20,7 +20,7 @@ export interface ShortcutSheetColumn extends ShortcutNamed {
20
20
  readonly rows: readonly ShortcutSheetRow[]
21
21
  }
22
22
 
23
- /** One surface's keys (`orbiters-play`, `ew-studio`, `site`). Two actions clash only inside a section. */
23
+ /** One surface's keys (`orbiters-play`, `ew-clip-studio`, `root`). Two actions clash only inside a section. */
24
24
  export interface ShortcutSection {
25
25
  readonly id: string
26
26
  /** The app it belongs to (`orbiters`) and that app's name. */
@@ -135,7 +135,7 @@ export function localAppName(section: Pick<ShortcutSection, 'appLabel' | 'appNam
135
135
  return localName({ label: section.appLabel, names: section.appNames }, locale)
136
136
  }
137
137
 
138
- /** A section's part name (`Play`, `Studio`) in a language; `undefined` when it has none. */
138
+ /** A section's part name (`Play`, `Orbiter Studio`) in a language; `undefined` when it has none. */
139
139
  export function localPartName(section: Pick<ShortcutSection, 'part' | 'partNames'>, locale?: string): string | undefined {
140
140
  return section.part === undefined ? undefined : localName({ label: section.part, names: section.partNames }, locale)
141
141
  }
@@ -1,6 +1,18 @@
1
1
  import * as THREE from 'three';
2
2
  import { loadTexture } from '../loaders/bodyTextureLoader.js';
3
3
 
4
+ /**
5
+ * The cloud texture could not be loaded (offline, gone from the CDN, or not decodable). Its own type
6
+ * so the world builders can drop just the clouds on THIS failure and still let any other error out.
7
+ * `cause` is what the loader threw.
8
+ */
9
+ export class CloudTextureError extends Error {
10
+ constructor(url: string, cause: unknown) {
11
+ super(`Cloud texture could not be loaded: ${url}`, { cause });
12
+ this.name = 'CloudTextureError';
13
+ }
14
+ }
15
+
4
16
  // Texture-only clouds material (alpha mask + tint), UV-scrolled
5
17
  async function makeTextureMaterial({
6
18
  color = 0xffffff,
@@ -29,7 +41,13 @@ async function makeTextureMaterial({
29
41
  if (textureUrl) {
30
42
  // Thread the owning view's renderer: KTX2 cloud textures must transcode against the caller's
31
43
  // context, not the global fallback (which shared-renderer pages don't publish at all).
32
- const tex = await loadTexture(textureUrl, { renderer });
44
+ let tex: THREE.Texture;
45
+ try {
46
+ tex = await loadTexture(textureUrl, { renderer });
47
+ } catch (cause) {
48
+ mat.dispose();
49
+ throw new CloudTextureError(textureUrl, cause);
50
+ }
33
51
  tex.wrapS = THREE.RepeatWrapping;
34
52
  tex.wrapT = THREE.RepeatWrapping;
35
53
  // Use linear space for alpha masks if available
@@ -84,10 +102,7 @@ export async function createCloudShell({
84
102
  speed?: number | undefined;
85
103
  renderer?: THREE.WebGLRenderer | null | undefined;
86
104
  } = {}) {
87
- const geom = baseGeometry
88
- ? baseGeometry.clone()
89
- : new THREE.SphereGeometry(baseRadius, widthSegments, heightSegments);
90
-
105
+ // The texture first: when it cannot be loaded this throws before any geometry exists to leak.
91
106
  const material = await makeTextureMaterial({
92
107
  color,
93
108
  opacity,
@@ -95,6 +110,9 @@ export async function createCloudShell({
95
110
  speed,
96
111
  renderer,
97
112
  });
113
+ const geom = baseGeometry
114
+ ? baseGeometry.clone()
115
+ : new THREE.SphereGeometry(baseRadius, widthSegments, heightSegments);
98
116
 
99
117
  const mesh = new THREE.Mesh(geom, material);
100
118
  const s = 1.001; // fixed scale
@@ -3,7 +3,7 @@ import * as THREE from 'three';
3
3
  import { EARTH_RADIUS_UNITS } from './worldTexture.js';
4
4
  import { applyFlagsToMaterial } from '../utils/materials.js';
5
5
  import { getLightFresnel } from '../core/lightFresnel.js';
6
- import { createCloudShell } from './cloudShell.js';
6
+ import { CloudTextureError, createCloudShell } from './cloudShell.js';
7
7
  import { resolvePublicUrl } from '../config/env.js';
8
8
 
9
9
  /**
@@ -89,8 +89,15 @@ export async function makeNormalSphere({
89
89
  opacity: cloudsOpacity,
90
90
  speed: cloudsSpeed,
91
91
  renderer,
92
+ }).catch((e: unknown) => {
93
+ // Clouds are an optional layer: a texture that cannot be loaded (offline, or gone from the
94
+ // CDN) costs the clouds, never the planet — the same way a failed surface texture does. Only
95
+ // that: any other error building the shell is a bug and still fails the build.
96
+ if (!(e instanceof CloudTextureError)) throw e;
97
+ console.warn('[worldNormal] Failed to load clouds, drawing the world without them:', e.cause);
98
+ return null;
92
99
  });
93
- mesh.add(cloudMesh);
100
+ if (cloudMesh) mesh.add(cloudMesh);
94
101
  }
95
102
 
96
103
  return mesh;
@@ -2,7 +2,7 @@ import * as THREE from 'three';
2
2
  import { loadTexture } from '../loaders/bodyTextureLoader.js';
3
3
  import { applyFlagsToMaterial } from '../utils/materials.js';
4
4
  import { getLightFresnel } from '../core/lightFresnel.js';
5
- import { createCloudShell } from './cloudShell.js';
5
+ import { CloudTextureError, createCloudShell } from './cloudShell.js';
6
6
  import { resolvePublicUrl } from '../config/env.js';
7
7
 
8
8
  // Debug tag so you can verify the loaded file version in console
@@ -374,8 +374,15 @@ export async function makeSphere({
374
374
  opacity: cloudsOpacity,
375
375
  speed: cloudsSpeed,
376
376
  renderer,
377
+ }).catch((e: unknown) => {
378
+ // Clouds are an optional layer: a texture that cannot be loaded (offline, or gone from the
379
+ // CDN) costs the clouds, never the planet — the same way a failed surface texture does. Only
380
+ // that: any other error building the shell is a bug and still fails the build.
381
+ if (!(e instanceof CloudTextureError)) throw e;
382
+ console.warn('[worldTexture] Failed to load clouds, drawing the world without them:', e.cause);
383
+ return null;
377
384
  });
378
- mesh.add(cloudMesh);
385
+ if (cloudMesh) mesh.add(cloudMesh);
379
386
  }
380
387
 
381
388
  // Optionally add glow as a child of the mesh so it follows transforms