entangled-worlds-orbiters-shared 3.1.0 → 3.3.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 (53) hide show
  1. package/README.md +1 -1
  2. package/dist/star/framing.d.ts +17 -6
  3. package/dist/star/framing.js +16 -52
  4. package/dist/star/index.d.ts +1 -1
  5. package/dist/star/index.js +1 -1
  6. package/dist/star/layout.js +1 -2
  7. package/dist/star/params.js +3 -2
  8. package/dist/types/config/env.d.ts +12 -0
  9. package/dist/types/config/index.d.ts +2 -0
  10. package/dist/types/config/textureConfig.d.ts +121 -0
  11. package/dist/types/core/index.d.ts +2 -0
  12. package/dist/types/core/lightFresnel.d.ts +5 -0
  13. package/dist/types/core/lights.d.ts +58 -0
  14. package/dist/types/index.d.ts +6 -0
  15. package/dist/types/loaders/bodyTextureLoader.d.ts +12 -0
  16. package/dist/types/loaders/cubeMapLoader.d.ts +70 -0
  17. package/dist/types/loaders/glbLoader.d.ts +24 -0
  18. package/dist/types/loaders/index.d.ts +5 -0
  19. package/dist/types/loaders/ktx2.d.ts +16 -0
  20. package/dist/types/moons/index.d.ts +2 -0
  21. package/dist/types/moons/moonsGeneration.d.ts +72 -0
  22. package/dist/types/moons/moonsManager.d.ts +27 -0
  23. package/dist/types/render/frameBudget.d.ts +29 -0
  24. package/dist/types/render/index.d.ts +2 -0
  25. package/dist/types/render/viewportCompositor.d.ts +58 -0
  26. package/dist/types/utils/dispose.d.ts +2 -0
  27. package/dist/types/utils/index.d.ts +2 -0
  28. package/dist/types/utils/materials.d.ts +2 -0
  29. package/dist/types/world/cloudShell.d.ts +25 -0
  30. package/dist/types/world/index.d.ts +6 -0
  31. package/dist/types/world/worldGLB.d.ts +20 -0
  32. package/dist/types/world/worldManager.d.ts +63 -0
  33. package/dist/types/world/worldNormal.d.ts +43 -0
  34. package/dist/types/world/worldTexture.d.ts +80 -0
  35. package/package.json +35 -10
  36. package/src/config/env.js +8 -0
  37. package/src/core/lights.js +12 -0
  38. package/src/loaders/bodyTextureLoader.js +7 -0
  39. package/src/loaders/cubeMapLoader.js +27 -0
  40. package/src/loaders/glbLoader.js +7 -1
  41. package/src/loaders/index.js +3 -1
  42. package/src/moons/moonsGeneration.js +32 -2
  43. package/src/moons/moonsManager.js +1 -1
  44. package/src/star/framing.ts +17 -55
  45. package/src/star/index.ts +1 -1
  46. package/src/star/layout.ts +1 -2
  47. package/src/star/params.ts +3 -2
  48. package/src/world/cloudShell.js +13 -0
  49. package/src/world/index.js +3 -1
  50. package/src/world/worldGLB.js +12 -1
  51. package/src/world/worldManager.js +32 -1
  52. package/src/world/worldNormal.js +19 -0
  53. package/src/world/worldTexture.js +41 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * A frame gate. Not a clock and not a scheduler: it answers one question and remembers the last time
3
+ * the answer was yes.
4
+ *
5
+ * @param {{ fps?: number, toleranceMs?: number }} [options]
6
+ * @returns {{ shouldRender(nowMs: number): boolean, reset(): void }}
7
+ */
8
+ export function createFrameBudget({ fps, toleranceMs }?: {
9
+ fps?: number;
10
+ toleranceMs?: number;
11
+ }): {
12
+ shouldRender(nowMs: number): boolean;
13
+ reset(): void;
14
+ };
15
+ /**
16
+ * @file src/render/frameBudget.js
17
+ * @description "Has enough time passed to draw again?" — the whole of a frame cap, kept apart from
18
+ * whatever is being capped so it can be reasoned about and tested on its own.
19
+ *
20
+ * The tolerance is the part that is easy to get wrong. rAF does not deliver frames at arbitrary
21
+ * times; it delivers them on the display's ticks. On a 60Hz display those arrive every ~16.7ms, so a
22
+ * strict `elapsed >= 33.3` test rejects the frame at 33.3ms by a hair and waits for the next one at
23
+ * 50ms — 20fps, not the 30 that was asked for. A few milliseconds of slack lets the frame that is
24
+ * near enough through, and the cap lands where it was aimed.
25
+ */
26
+ /** Frames a capped loop is allowed per second. */
27
+ export const CAPPED_FPS: 30;
28
+ /** Slack for rAF landing on display ticks — see the file note. */
29
+ export const FRAME_TOLERANCE_MS: 4;
@@ -0,0 +1,2 @@
1
+ export { createViewportCompositor } from "./viewportCompositor.js";
2
+ export { createFrameBudget, CAPPED_FPS, FRAME_TOLERANCE_MS } from "./frameBudget.js";
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @param {object} [opts]
3
+ * @param {{ acquireCell: (index:number, total:number) => HTMLElement, releaseCell?: (index:number) => void } | null} [opts.cellSource]
4
+ * external owner of the cell DOM. When present, `createCell` delegates to it and the compositor
5
+ * builds no grid of its own; when absent, the default `⌈√n⌉` grid is used.
6
+ * @param {'fixed-overlay'|'per-cell'} [opts.presentation='fixed-overlay'] how pixels reach the
7
+ * screen — the displayed fixed canvas (full-screen stage layouts) or a per-cell blit canvas the
8
+ * DOM positions (cells inside scrolling content). See the module doc.
9
+ * @param {Partial<typeof DEFAULT_RENDERER_PROFILE>} [opts.rendererProfile] renderer construction +
10
+ * pixel-ratio cap. Omit for the historical defaults.
11
+ * @param {boolean} [opts.performanceMode=false] start with the realm's renderer savings on — pixel
12
+ * ratio clamped to 1, shadow map off, this loop capped at 30fps. Off by default, and off means the
13
+ * renderer and the cadence are exactly what they were before this option existed. Toggle it live
14
+ * with `setPerformanceMode`.
15
+ * @param {string|null} [opts.devHandleKey=null] when set, publish the returned api on
16
+ * `window[devHandleKey]` so tooling can force a composited frame while rAF is throttled (a hidden
17
+ * tab during automated checks). Off by default — a library does not name globals for its host.
18
+ * @returns {{
19
+ * renderer: THREE.WebGLRenderer,
20
+ * canvas: HTMLCanvasElement,
21
+ * createCell: (index: number, total: number) => HTMLElement,
22
+ * addVoice: (voice: { voiceId: string, cell: HTMLElement, controller: object }) => void,
23
+ * removeVoice: (voiceId: string) => void,
24
+ * renderOnce: () => void,
25
+ * setPerformanceMode: (enabled: boolean) => boolean,
26
+ * dispose: () => void,
27
+ * }}
28
+ */
29
+ export function createViewportCompositor({ cellSource, presentation, rendererProfile, devHandleKey, performanceMode, }?: {
30
+ cellSource?: {
31
+ acquireCell: (index: number, total: number) => HTMLElement;
32
+ releaseCell?: (index: number) => void;
33
+ } | null | undefined;
34
+ presentation?: "fixed-overlay" | "per-cell" | undefined;
35
+ rendererProfile?: Partial<{
36
+ antialias: boolean;
37
+ alpha: boolean;
38
+ preserveDrawingBuffer: boolean;
39
+ powerPreference: string;
40
+ maxDevicePixelRatio: number;
41
+ }> | undefined;
42
+ performanceMode?: boolean | undefined;
43
+ devHandleKey?: string | null | undefined;
44
+ }): {
45
+ renderer: THREE.WebGLRenderer;
46
+ canvas: HTMLCanvasElement;
47
+ createCell: (index: number, total: number) => HTMLElement;
48
+ addVoice: (voice: {
49
+ voiceId: string;
50
+ cell: HTMLElement;
51
+ controller: object;
52
+ }) => void;
53
+ removeVoice: (voiceId: string) => void;
54
+ renderOnce: () => void;
55
+ setPerformanceMode: (enabled: boolean) => boolean;
56
+ dispose: () => void;
57
+ };
58
+ import * as THREE from 'three';
@@ -0,0 +1,2 @@
1
+ export function disposeMaterial(material: any): void;
2
+ export function disposeObject3D(obj: any): void;
@@ -0,0 +1,2 @@
1
+ export { disposeObject3D } from "./dispose.js";
2
+ export { ensureStandardMaterial, applyFlagsToMaterial } from "./materials.js";
@@ -0,0 +1,2 @@
1
+ export function ensureStandardMaterial(mesh: any): void;
2
+ export function applyFlagsToMaterial(material: any, flags?: {}): void;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @param {object} [opts]
3
+ * @param {import('three').BufferGeometry} [opts.baseGeometry] cloned for the shell; when absent a
4
+ * sphere is built from `baseRadius` and the segment counts.
5
+ * @param {number} [opts.baseRadius=0.5]
6
+ * @param {number} [opts.widthSegments=64]
7
+ * @param {number} [opts.heightSegments=64]
8
+ * @param {string} [opts.textureUrl]
9
+ * @param {number} [opts.color=0xffffff]
10
+ * @param {number} [opts.opacity=0.6]
11
+ * @param {number} [opts.speed=0.015]
12
+ * @param {import('three').WebGLRenderer | null} [opts.renderer]
13
+ */
14
+ export function createCloudShell({ baseGeometry, baseRadius, widthSegments, heightSegments, textureUrl, color, opacity, speed, renderer, }?: {
15
+ baseGeometry?: THREE.BufferGeometry<THREE.NormalBufferAttributes> | undefined;
16
+ baseRadius?: number | undefined;
17
+ widthSegments?: number | undefined;
18
+ heightSegments?: number | undefined;
19
+ textureUrl?: string | undefined;
20
+ color?: number | undefined;
21
+ opacity?: number | undefined;
22
+ speed?: number | undefined;
23
+ renderer?: THREE.WebGLRenderer | null | undefined;
24
+ }): Promise<THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes>, THREE.MeshLambertMaterial, THREE.Object3DEventMap>>;
25
+ import * as THREE from 'three';
@@ -0,0 +1,6 @@
1
+ export { WorldManager } from "./worldManager.js";
2
+ export { makeNormalSphere } from "./worldNormal.js";
3
+ export { makeGLB } from "./worldGLB.js";
4
+ export { createCloudShell } from "./cloudShell.js";
5
+ export type WorldCreator = import("./worldManager.js").WorldCreator;
6
+ export { makeSphere, EARTH_RADIUS_UNITS } from "./worldTexture.js";
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `emissiveIntensity`: textured self-glow (1 = old flat-lit look; ≈0.15 under a star light).
3
+ *
4
+ * @param {object} [opts]
5
+ * @param {string} opts.modelUrl REQUIRED; throws without it.
6
+ * @param {object} [opts.flags] material flags (`forceOpaque`, `wireframe`).
7
+ * @param {number} [opts.retries=3]
8
+ * @param {boolean} [opts.withCredentials=false]
9
+ * @param {import('three').WebGLRenderer | null} [opts.renderer] REQUIRED at runtime (`loadGLB` throws
10
+ * without one); nullable here because `WorldManager` threads its own, which may be null.
11
+ * @param {number} [opts.emissiveIntensity]
12
+ */
13
+ export function makeGLB({ modelUrl, flags, retries, withCredentials, renderer, emissiveIntensity }?: {
14
+ modelUrl: string;
15
+ flags?: object | undefined;
16
+ retries?: number | undefined;
17
+ withCredentials?: boolean | undefined;
18
+ renderer?: import("three").WebGLRenderer | null | undefined;
19
+ emissiveIntensity?: number | undefined;
20
+ }): Promise<any>;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Builds a world object for a mode registered with `registerWorldCreator`. It receives the manager's
3
+ * flags, the `setMode` args and the renderer, and may return the object or a promise of it (the
4
+ * manager awaits either).
5
+ * @typedef {(args: { flags: object, renderer: import('three').WebGLRenderer | null, [key: string]: unknown }) => import('three').Object3D | Promise<import('three').Object3D>} WorldCreator
6
+ */
7
+ export class WorldManager {
8
+ /**
9
+ * @param {import('three').Scene} scene
10
+ * @param {object} [opts]
11
+ * @param {() => void} [opts.onLoadStart]
12
+ * @param {() => void} [opts.onLoadEnd]
13
+ * @param {import('three').WebGLRenderer | null} [opts.renderer]
14
+ * @param {boolean} [opts.externalTick=false]
15
+ */
16
+ constructor(scene: import("three").Scene, { onLoadStart, onLoadEnd, renderer, externalTick }?: {
17
+ onLoadStart?: (() => void) | undefined;
18
+ onLoadEnd?: (() => void) | undefined;
19
+ renderer?: import("three").WebGLRenderer | null | undefined;
20
+ externalTick?: boolean | undefined;
21
+ });
22
+ scene: import("three").Scene;
23
+ renderer: import("three").WebGLRenderer | null;
24
+ externalTick: boolean;
25
+ current: any;
26
+ /** @type {Record<string, any>} */
27
+ flags: Record<string, any>;
28
+ onLoadStart: (() => void) | undefined;
29
+ onLoadEnd: (() => void) | undefined;
30
+ token: number;
31
+ customCreators: Map<any, any>;
32
+ /**
33
+ * Run the current world's per-frame updater once (externalTick hosts only; no-op otherwise).
34
+ * @param {number} timeSec
35
+ */
36
+ tick(timeSec: number): void;
37
+ /** @param {object} flags merged over the current flags */
38
+ setFlags(flags: object): void;
39
+ disposeCurrent(): void;
40
+ /**
41
+ * @param {number} mode
42
+ * @param {WorldCreator} creatorFunction
43
+ */
44
+ registerWorldCreator(mode: number, creatorFunction: WorldCreator): void;
45
+ /**
46
+ * Build and show the world for `mode` (a registered creator, 2 = GLB, 3 = normal sphere, else the
47
+ * textured sphere), replacing the current one. A newer call supersedes one still loading.
48
+ * @param {number} mode
49
+ * @param {Record<string, any>} [args]
50
+ * @returns {Promise<void>}
51
+ */
52
+ setMode(mode: number, args?: Record<string, any>): Promise<void>;
53
+ }
54
+ /**
55
+ * Builds a world object for a mode registered with `registerWorldCreator`. It receives the manager's
56
+ * flags, the `setMode` args and the renderer, and may return the object or a promise of it (the
57
+ * manager awaits either).
58
+ */
59
+ export type WorldCreator = (args: {
60
+ flags: object;
61
+ renderer: import("three").WebGLRenderer | null;
62
+ [key: string]: unknown;
63
+ }) => import("three").Object3D | Promise<import("three").Object3D>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Create a simple sphere using THREE.MeshNormalMaterial.
3
+ * Accepts the same basic geometry params as textured sphere so the manager API is consistent.
4
+ *
5
+ * @param {object} [opts]
6
+ * @param {number} [opts.sphereRadius]
7
+ * @param {number} [opts.sphereWidthSegments]
8
+ * @param {number} [opts.sphereHeightSegments]
9
+ * @param {object} [opts.flags] material flags (`forceOpaque`, `wireframe`).
10
+ * @param {boolean} [opts.glow] add the fresnel halo.
11
+ * @param {number} [opts.glowScale=1.18]
12
+ * @param {number} [opts.glowRimHex=0x66ccff]
13
+ * @param {number} [opts.glowFacingHex=0x000000]
14
+ * @param {number} [opts.glowBias=0.02]
15
+ * @param {number} [opts.glowFresnelScale=1]
16
+ * @param {number} [opts.glowPower=3.5]
17
+ * @param {boolean} [opts.cloudsEnabled]
18
+ * @param {string} [opts.cloudsTextureUrl]
19
+ * @param {number} [opts.cloudsColor=0xffffff]
20
+ * @param {number} [opts.cloudsOpacity=0.6]
21
+ * @param {number} [opts.cloudsSpeed=0.015]
22
+ * @param {import('three').WebGLRenderer | null} [opts.renderer]
23
+ */
24
+ export function makeNormalSphere({ sphereRadius, sphereWidthSegments, sphereHeightSegments, flags, glow, glowScale, glowRimHex, glowFacingHex, glowBias, glowFresnelScale, glowPower, cloudsEnabled, cloudsTextureUrl, cloudsColor, cloudsOpacity, cloudsSpeed, renderer, }?: {
25
+ sphereRadius?: number | undefined;
26
+ sphereWidthSegments?: number | undefined;
27
+ sphereHeightSegments?: number | undefined;
28
+ flags?: object | undefined;
29
+ glow?: boolean | undefined;
30
+ glowScale?: number | undefined;
31
+ glowRimHex?: number | undefined;
32
+ glowFacingHex?: number | undefined;
33
+ glowBias?: number | undefined;
34
+ glowFresnelScale?: number | undefined;
35
+ glowPower?: number | undefined;
36
+ cloudsEnabled?: boolean | undefined;
37
+ cloudsTextureUrl?: string | undefined;
38
+ cloudsColor?: number | undefined;
39
+ cloudsOpacity?: number | undefined;
40
+ cloudsSpeed?: number | undefined;
41
+ renderer?: THREE.WebGLRenderer | null | undefined;
42
+ }): Promise<THREE.Mesh<THREE.SphereGeometry, THREE.MeshNormalMaterial, THREE.Object3DEventMap>>;
43
+ import * as THREE from 'three';
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The textured world sphere. Every option is optional. The offset, repeat, rotation, flip and sphere
3
+ * geometry options are read only when they have their type; the rest are used as given (`glow`,
4
+ * `cloudsEnabled` and `mirrorX` by truthiness).
5
+ *
6
+ * @param {object} [opts]
7
+ * @param {string} [opts.textureUrl]
8
+ * @param {import('three').WebGLRenderer | null} [opts.renderer] REQUIRED for a KTX2 texture.
9
+ * @param {object} [opts.flags] material flags (`forceOpaque`, `wireframe`).
10
+ * @param {number} [opts.uOffset] horizontal texture offset, 0..1 (wins over `textureOffsetDeg`).
11
+ * @param {number} [opts.textureOffsetDeg] horizontal texture offset in degrees.
12
+ * @param {boolean} [opts.mirrorX]
13
+ * @param {boolean} [opts.mirrorDuplicateX] explicit override of the equirectangular detection.
14
+ * @param {boolean} [opts.autoDetectEquirectangular=true]
15
+ * @param {boolean} [opts.forceEquirectangular=false]
16
+ * @param {boolean} [opts.forceRectangular=false]
17
+ * @param {string} [opts.wrapS='repeat'] `'clamp'`, `'repeat'` or `'mirror'`.
18
+ * @param {string} [opts.wrapT='clamp'] `'clamp'`, `'repeat'` or `'mirror'`.
19
+ * @param {number} [opts.repeatX]
20
+ * @param {number} [opts.repeatY]
21
+ * @param {number} [opts.rotationDeg]
22
+ * @param {number[]} [opts.center=[0.5, 0.5]] texture rotation centre, `[u, v]`.
23
+ * @param {boolean} [opts.flipY]
24
+ * @param {number} [opts.sphereRadius]
25
+ * @param {number} [opts.sphereWidthSegments]
26
+ * @param {number} [opts.sphereHeightSegments]
27
+ * @param {number} [opts.materialRoughness] accepted from saved worlds; no longer changes the look.
28
+ * @param {number} [opts.materialMetalness] accepted from saved worlds; no longer changes the look.
29
+ * @param {boolean} [opts.glow=true]
30
+ * @param {number} [opts.glowRimHex=0x66ccff]
31
+ * @param {number} [opts.glowFacingHex=0x000000]
32
+ * @param {number} [opts.glowScale=1.18]
33
+ * @param {number} [opts.glowBias=0.02]
34
+ * @param {number} [opts.glowFresnelScale=1]
35
+ * @param {number} [opts.glowPower=3.5]
36
+ * @param {boolean} [opts.cloudsEnabled=false]
37
+ * @param {string} [opts.cloudsTextureUrl]
38
+ * @param {number} [opts.cloudsColor=0xffffff]
39
+ * @param {number} [opts.cloudsOpacity=0.6]
40
+ * @param {number} [opts.cloudsSpeed=0.015]
41
+ */
42
+ export function makeSphere({ textureUrl, renderer, flags, uOffset, textureOffsetDeg, mirrorX, mirrorDuplicateX, autoDetectEquirectangular, forceEquirectangular, forceRectangular, wrapS, wrapT, repeatX, repeatY, rotationDeg, center, flipY, sphereRadius, sphereWidthSegments, sphereHeightSegments, materialRoughness, materialMetalness, glow, glowRimHex, glowFacingHex, glowScale, glowBias, glowFresnelScale, glowPower, cloudsEnabled, cloudsTextureUrl, cloudsColor, cloudsOpacity, cloudsSpeed, }?: {
43
+ textureUrl?: string | undefined;
44
+ renderer?: THREE.WebGLRenderer | null | undefined;
45
+ flags?: object | undefined;
46
+ uOffset?: number | undefined;
47
+ textureOffsetDeg?: number | undefined;
48
+ mirrorX?: boolean | undefined;
49
+ mirrorDuplicateX?: boolean | undefined;
50
+ autoDetectEquirectangular?: boolean | undefined;
51
+ forceEquirectangular?: boolean | undefined;
52
+ forceRectangular?: boolean | undefined;
53
+ wrapS?: string | undefined;
54
+ wrapT?: string | undefined;
55
+ repeatX?: number | undefined;
56
+ repeatY?: number | undefined;
57
+ rotationDeg?: number | undefined;
58
+ center?: number[] | undefined;
59
+ flipY?: boolean | undefined;
60
+ sphereRadius?: number | undefined;
61
+ sphereWidthSegments?: number | undefined;
62
+ sphereHeightSegments?: number | undefined;
63
+ materialRoughness?: number | undefined;
64
+ materialMetalness?: number | undefined;
65
+ glow?: boolean | undefined;
66
+ glowRimHex?: number | undefined;
67
+ glowFacingHex?: number | undefined;
68
+ glowScale?: number | undefined;
69
+ glowBias?: number | undefined;
70
+ glowFresnelScale?: number | undefined;
71
+ glowPower?: number | undefined;
72
+ cloudsEnabled?: boolean | undefined;
73
+ cloudsTextureUrl?: string | undefined;
74
+ cloudsColor?: number | undefined;
75
+ cloudsOpacity?: number | undefined;
76
+ cloudsSpeed?: number | undefined;
77
+ }): Promise<THREE.Mesh<THREE.SphereGeometry, THREE.MeshLambertMaterial, THREE.Object3DEventMap>>;
78
+ export const WORLD_TEXTURE_DEBUG_TAG: "WT:vFlipGeom-r3@2025-10-28T17:22:00Z";
79
+ export const EARTH_RADIUS_UNITS: 0.5;
80
+ import * as THREE from 'three';
package/package.json CHANGED
@@ -1,19 +1,44 @@
1
1
  {
2
2
  "name": "entangled-worlds-orbiters-shared",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
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",
7
+ "types": "./dist/types/index.d.ts",
7
8
  "sideEffects": false,
8
9
  "exports": {
9
- ".": "./src/index.js",
10
- "./world": "./src/world/index.js",
11
- "./moons": "./src/moons/index.js",
12
- "./loaders": "./src/loaders/index.js",
13
- "./render": "./src/render/index.js",
14
- "./core": "./src/core/index.js",
15
- "./config": "./src/config/index.js",
16
- "./utils": "./src/utils/index.js",
10
+ ".": {
11
+ "types": "./dist/types/index.d.ts",
12
+ "default": "./src/index.js"
13
+ },
14
+ "./world": {
15
+ "types": "./dist/types/world/index.d.ts",
16
+ "default": "./src/world/index.js"
17
+ },
18
+ "./moons": {
19
+ "types": "./dist/types/moons/index.d.ts",
20
+ "default": "./src/moons/index.js"
21
+ },
22
+ "./loaders": {
23
+ "types": "./dist/types/loaders/index.d.ts",
24
+ "default": "./src/loaders/index.js"
25
+ },
26
+ "./render": {
27
+ "types": "./dist/types/render/index.d.ts",
28
+ "default": "./src/render/index.js"
29
+ },
30
+ "./core": {
31
+ "types": "./dist/types/core/index.d.ts",
32
+ "default": "./src/core/index.js"
33
+ },
34
+ "./config": {
35
+ "types": "./dist/types/config/index.d.ts",
36
+ "default": "./src/config/index.js"
37
+ },
38
+ "./utils": {
39
+ "types": "./dist/types/utils/index.d.ts",
40
+ "default": "./src/utils/index.js"
41
+ },
17
42
  "./clock": {
18
43
  "types": "./dist/clock/index.d.ts",
19
44
  "import": "./dist/clock/index.js"
@@ -120,7 +145,7 @@
120
145
  "lint": "eslint .",
121
146
  "lint:strict": "eslint . --max-warnings=0",
122
147
  "lint:fallback": "node scripts/lint.js",
123
- "build:ts": "tsc -p tsconfig.build.json",
148
+ "build:ts": "tsc -p tsconfig.build.json && tsc -p tsconfig.types.json",
124
149
  "build:clock": "npm run build:ts",
125
150
  "prepare": "npm run build:ts",
126
151
  "typecheck": "tsc -p tsconfig.json --noEmit",
package/src/config/env.js CHANGED
@@ -1,15 +1,18 @@
1
1
  // Runtime public base URL helper for building absolute asset URLs at runtime
2
2
 
3
3
  // Mutable module-scoped variable to allow apps to override at startup
4
+ /** @type {string | null} */
4
5
  export let __PUBLIC_BASE = null;
5
6
 
6
7
  // Set the public base (strips trailing slash) and mirror to window for convenience
8
+ /** @param {string | null | undefined} url */
7
9
  export function setPublicBase(url) {
8
10
  __PUBLIC_BASE = (url || '').replace(/\/$/, '');
9
11
  if (typeof window !== 'undefined') window.VITE_PUBLIC_HERBARIUM_BASE = __PUBLIC_BASE;
10
12
  }
11
13
 
12
14
  // Get the current public base from (in order): explicit set value, envs, or default
15
+ /** @returns {string} */
13
16
  export function getPublicBase() {
14
17
  const envVal =
15
18
  (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_PUBLIC_HERBARIUM_BASE) ||
@@ -20,6 +23,11 @@ export function getPublicBase() {
20
23
  }
21
24
 
22
25
  // Resolve a possibly-relative path against the current public base
26
+ /**
27
+ * @param {string} path root-relative, relative, or an absolute http(s) URL (returned unchanged).
28
+ * An empty string comes back as is.
29
+ * @returns {string}
30
+ */
23
31
  export function resolvePublicUrl(path) {
24
32
  if (!path) return path;
25
33
  if (/^https?:\/\//i.test(path)) return path;
@@ -8,6 +8,9 @@ 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 }}
11
14
  */
12
15
  export function ensureDefaultLights(scene) {
13
16
  let hemi = scene.getObjectByName('ew_HemisphereLight');
@@ -33,7 +36,15 @@ export function ensureDefaultLights(scene) {
33
36
  * was ensured last, so on a page rendering several scenes every lighting update landed on one of
34
37
  * them — a shared-renderer page (N feed worlds, N orbiters) lit the wrong world.
35
38
  *
39
+ * Every field is optional; an omitted one leaves that part of the lighting as it is.
40
+ *
36
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
37
48
  */
38
49
  export function updateDefaultLights(scene, { ambientIntensity, diffuseIntensity, lightColor, lightDirection, hemisphere } = {}) {
39
50
  if (!scene?.isObject3D) {
@@ -75,6 +86,7 @@ export function setHemisphereEnabled(scene, on) {
75
86
  }
76
87
 
77
88
  // Back-compat helper
89
+ /** @param {import('three').Scene} scene */
78
90
  export function addDefaultLights(scene) {
79
91
  return ensureDefaultLights(scene);
80
92
  }
@@ -3,6 +3,13 @@ import { getKTX2LoaderForRenderer } from './ktx2.js';
3
3
 
4
4
  const textureLoader = new THREE.TextureLoader();
5
5
 
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.
12
+ */
6
13
  export function loadTexture(url, { signal, renderer } = {}) {
7
14
  return new Promise((resolve, reject) => {
8
15
  if (url && url.endsWith('.ktx2')) {
@@ -167,6 +167,15 @@ function resolveUrls(faceKeys) {
167
167
  * `renderer` binds the KTX2 transcoder to the right WebGL context. REQUIRED (3.0) when the KTX2
168
168
  * path is taken — the old `window.__EW_RENDERER` fallback is gone; thread the owning view's
169
169
  * renderer. (The PNG/JPG path is renderer-agnostic, so its absence only bites on KTX2.)
170
+ *
171
+ * @param {object} [opts]
172
+ * @param {{ right: string, left: string, top: string, bottom: string, front: string, back: string }} [opts.faceKeys]
173
+ * explicit face URLs; when absent they are built from `skycubeName`.
174
+ * @param {string} [opts.skycubeName] skybox id (e.g. `'001'`), used when `faceKeys` is absent.
175
+ * @param {boolean} [opts.preferKTX2=true]
176
+ * @param {boolean} [opts.forceLow] force (or forbid) the low-resolution faces; omitted = detect the device.
177
+ * @param {import('three').WebGLRenderer | null} [opts.renderer]
178
+ * @returns {Promise<SkyboxResource>} pass it to `setBackground`.
170
179
  */
171
180
  export async function loadCubeMap({ faceKeys, skycubeName, preferKTX2 = true, forceLow, renderer } = {}) {
172
181
 
@@ -235,6 +244,20 @@ function disposeSkyboxMesh(mesh) {
235
244
  }
236
245
  }
237
246
 
247
+ /**
248
+ * What `loadCubeMap` resolves to: a skybox mesh wrapper (the KTX2 path) or a cube texture (the
249
+ * PNG/JPG path). Read `mesh` to tell them apart.
250
+ * @typedef {{ __ewType: 'skybox-mesh', mesh: import('three').Mesh } | (import('three').CubeTexture & { mesh?: undefined })} SkyboxResource
251
+ */
252
+
253
+ /**
254
+ * Show a loaded skybox as the scene's background. A previous skybox mesh or cube-texture background is
255
+ * disposed (a plain-texture background is not). Takes what `loadCubeMap` resolved to (or any texture);
256
+ * anything else, `null` included, clears it.
257
+ *
258
+ * @param {import('three').Scene} scene
259
+ * @param {unknown} resource
260
+ */
238
261
  export function setBackground(scene, resource) {
239
262
  // Clean up previous skybox mesh
240
263
  if (scene.__EW_SKYBOX_MESH && scene.__EW_SKYBOX_MESH.parent === scene) {
@@ -274,6 +297,10 @@ export function setBackground(scene, resource) {
274
297
  }
275
298
  }
276
299
 
300
+ /**
301
+ * @param {import('three').Scene} scene
302
+ * @param {{ renderer?: import('three').WebGLRenderer | null }} [opts]
303
+ */
277
304
  export async function initSpaceScene(scene, { renderer } = {}) {
278
305
  const faceKeys = buildSkycubefaceKeys('001');
279
306
  const cube = await loadCubeMap({ faceKeys, preferKTX2: true, renderer });
@@ -4,7 +4,13 @@ import { ensureStandardMaterial, applyFlagsToMaterial } from '../utils/materials
4
4
  import { getKTX2LoaderForRenderer } from './ktx2.js';
5
5
 
6
6
  /**
7
- * @param {object} [opts]
7
+ * @param {string} url
8
+ * @param {object} [opts] `renderer` is REQUIRED at runtime (throws without one).
9
+ * @param {number} [opts.retries=3] load attempts before giving up.
10
+ * @param {AbortSignal} [opts.signal] rejects the load on abort.
11
+ * @param {(event: ProgressEvent) => void} [opts.onProgress]
12
+ * @param {object} [opts.applyFlags] material flags (`forceOpaque`, `wireframe`) applied to every mesh.
13
+ * @param {boolean} [opts.withCredentials=false]
8
14
  * @param {number} [opts.emissiveIntensity=1] self-glow of textured materials (their map doubles as the
9
15
  * emissive map). 1 keeps the old flat-lit look; with a directional star light pass ≈0.1–0.15 so the
10
16
  * night side reads.
@@ -2,4 +2,6 @@
2
2
  export { loadCubeMap, setBackground, normalizeSkycubeName, buildSkycubefaceKeys } from './cubeMapLoader.js';
3
3
  export { loadGLB } from './glbLoader.js';
4
4
  export { loadTexture } from './bodyTextureLoader.js';
5
- export { getKTX2LoaderForRenderer, disposeKTX2LoaderForRenderer } from './ktx2.js';
5
+ export { getKTX2LoaderForRenderer, disposeKTX2LoaderForRenderer } from './ktx2.js';
6
+
7
+ /** @typedef {import('./cubeMapLoader.js').SkyboxResource} SkyboxResource */
@@ -21,6 +21,36 @@ export const MOON_ORBIT_SCATTER = 0.22;
21
21
  /** Each moon sits up to ± half of this above or below the orbit plane. */
22
22
  export const MOON_HEIGHT_SCATTER = 0.12;
23
23
 
24
+ /**
25
+ * @param {object} [opts]
26
+ * @param {number} [opts.count=0] 0 or less resolves to null.
27
+ * @param {number} [opts.orbitRadius=1.5]
28
+ * @param {string} [opts.textureKTX2]
29
+ * @param {string} [opts.texturePNG]
30
+ * @param {string | null} [opts.textureFallback] used instead of `texturePNG` when set.
31
+ * @param {boolean} [opts.useTexture=true]
32
+ * @param {number | string | null} [opts.seed] deterministic positions when set.
33
+ * @param {string | null} [opts.textureKey] replaces `textureKTX2` when set (the fallback stays
34
+ * `textureFallback` or `texturePNG`).
35
+ * @param {string} [opts.moonColor='#ffffff']
36
+ * @param {number} [opts.brightness=1.15]
37
+ * @param {number} [opts.emissiveColor=0x333333]
38
+ * @param {number} [opts.emissiveIntensity=0.18]
39
+ * @param {number} [opts.emissiveLift] night-side self-glow 0..1; omitted = `emissiveIntensity`.
40
+ * @param {number} [opts.roughness=0.85]
41
+ * @param {number} [opts.metalness=0]
42
+ * @param {boolean} [opts.debug=true]
43
+ * @param {boolean} [opts.smooth=true]
44
+ * @param {number} [opts.geometryWidthSegments=16]
45
+ * @param {number} [opts.geometryHeightSegments=12]
46
+ * @param {boolean} [opts.useBumpFromColor=true]
47
+ * @param {number} [opts.bumpScale=0.15]
48
+ * @param {boolean} [opts.debugShowUvGrid=false]
49
+ * @param {boolean} [opts.debugShowReferenceMoon=false]
50
+ * @param {number} [opts.referenceMoonScale=0.25]
51
+ * @param {boolean} [opts.useEmissiveMapFromTexture=true]
52
+ * @param {import('three').WebGLRenderer | null} [opts.renderer] binds the KTX2 moon texture.
53
+ */
24
54
  export async function generateMoons({
25
55
  count = 0,
26
56
  orbitRadius = 1.5,
@@ -55,8 +85,8 @@ export async function generateMoons({
55
85
  referenceMoonScale = 0.25,
56
86
  // behavior flags
57
87
  useEmissiveMapFromTexture = true, // reuse color texture as emissiveMap to lift dark sides
58
- // The renderer whose KTX2 transcoder decodes the moon texture. Required on any page with more than
59
- // one renderer; falls back to the standalone app's global inside the loader.
88
+ // The renderer whose KTX2 transcoder decodes the moon texture. REQUIRED for the KTX2 texture (3.0:
89
+ // the loader has no global fallback).
60
90
  renderer,
61
91
  } = {}) {
62
92
  if (count <= 0) return null;
@@ -6,7 +6,7 @@ export class MoonsManager {
6
6
  /**
7
7
  * @param {import('three').Scene} scene
8
8
  * @param {object} [opts]
9
- * @param {import('three').WebGLRenderer} [opts.renderer] renderer the moon texture binds to (KTX2).
9
+ * @param {import('three').WebGLRenderer | null} [opts.renderer] renderer the moon texture binds to (KTX2).
10
10
  */
11
11
  constructor(scene, { renderer } = {}) {
12
12
  this.scene = scene;