@kvyverse/world-runtime 0.1.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 (96) hide show
  1. package/LICENSE.md +57 -0
  2. package/README.md +87 -0
  3. package/dist/BallCollider-8ZrxReEd.js +16 -0
  4. package/dist/CapsuleCollider-bynyf6Mc.js +20 -0
  5. package/dist/Collider-4_rCTHVA.js +82 -0
  6. package/dist/ConeCollider-DtRZmgK8.js +20 -0
  7. package/dist/ConvexMeshCollider-BSmDM4o3.js +14 -0
  8. package/dist/CuboidCollider-CJvq7--s.js +27 -0
  9. package/dist/CylinderCollider-CQPSh4tu.js +24 -0
  10. package/dist/FpsCameraController-BE2EirRj.js +55 -0
  11. package/dist/MeshInstansing-Bxij0Yc9.js +42 -0
  12. package/dist/PointerLockCameraControls-DYxxMeSw.js +104 -0
  13. package/dist/RapierDebugRenderer-sURZDLP4.js +55 -0
  14. package/dist/RapierFpsPlayerController-VsNfQl6v.js +142 -0
  15. package/dist/RapierPhysics-C_3b7QOn.js +74 -0
  16. package/dist/Rigidbody-b7QNF4ev.js +107 -0
  17. package/dist/ScalingToFro-BD-E5-uR.js +21 -0
  18. package/dist/SightInteractableArea-DrqyB41W.js +79 -0
  19. package/dist/SimpleWASDMovement-C2m53YYH.js +17 -0
  20. package/dist/Spinning-sgUhHld8.js +10 -0
  21. package/dist/StartAnimation-B9eomOS2.js +37 -0
  22. package/dist/TrimeshCollider-7dIs_xtZ.js +62 -0
  23. package/dist/World.d.ts +19 -0
  24. package/dist/WorldRuntime.d.ts +94 -0
  25. package/dist/behaviours/animation/ScalingToFro.d.ts +8 -0
  26. package/dist/behaviours/animation/Spinning.d.ts +5 -0
  27. package/dist/behaviours/animation/StartAnimation.d.ts +9 -0
  28. package/dist/behaviours/controls/FpsCameraController.d.ts +10 -0
  29. package/dist/behaviours/controls/PointerLockCameraControls.d.ts +28 -0
  30. package/dist/behaviours/controls/SimpleWASDMovement.d.ts +5 -0
  31. package/dist/behaviours/physics/BallCollider.d.ts +10 -0
  32. package/dist/behaviours/physics/CapsuleCollider.d.ts +14 -0
  33. package/dist/behaviours/physics/ConeCollider.d.ts +14 -0
  34. package/dist/behaviours/physics/ConvexMeshCollider.d.ts +6 -0
  35. package/dist/behaviours/physics/CuboidCollider.d.ts +22 -0
  36. package/dist/behaviours/physics/CylinderCollider.d.ts +18 -0
  37. package/dist/behaviours/physics/HalfspaceCollider.d.ts +7 -0
  38. package/dist/behaviours/physics/RapierFpsPlayerController.d.ts +35 -0
  39. package/dist/behaviours/physics/Rigidbody.d.ts +24 -0
  40. package/dist/behaviours/physics/TrimeshCollider.d.ts +9 -0
  41. package/dist/behaviours/physics/_required-patch.d.ts +10 -0
  42. package/dist/behaviours/physics/rapier-addon-core/Collider.d.ts +14 -0
  43. package/dist/behaviours/physics/rapier-addon-core/RapierDebugRenderer.d.ts +17 -0
  44. package/dist/behaviours/physics/rapier-addon-core/RapierPhysics.d.ts +131 -0
  45. package/dist/behaviours/physics/rapier-addon-core/get-scale-from-obj.d.ts +8 -0
  46. package/dist/behaviours/player/SightInteractableArea.d.ts +23 -0
  47. package/dist/behaviours/registry.d.ts +16 -0
  48. package/dist/behaviours/utils/MeshInstansing.d.ts +12 -0
  49. package/dist/bootstrap.d.ts +8 -0
  50. package/dist/define-world.d.ts +9 -0
  51. package/dist/get-scale-from-obj-QKv3Fig5.js +22 -0
  52. package/dist/index.d.ts +48 -0
  53. package/dist/index.js +1707 -0
  54. package/dist/modules/EnvModule.d.ts +11 -0
  55. package/dist/modules/HtmlHudModule.d.ts +14 -0
  56. package/dist/modules/InputSystemModule.d.ts +87 -0
  57. package/dist/modules/SoundsModule.d.ts +7 -0
  58. package/dist/modules/UtilsModule.d.ts +9 -0
  59. package/dist/modules/contracts/env.module-api.d.ts +7 -0
  60. package/dist/modules/contracts/html.module-api.d.ts +7 -0
  61. package/dist/modules/contracts/input.module-api.d.ts +31 -0
  62. package/dist/modules/contracts/rapier.module-api.d.ts +11 -0
  63. package/dist/modules/contracts/rapierDebug.module-api.d.ts +5 -0
  64. package/dist/modules/contracts/sounds.module-api.d.ts +5 -0
  65. package/dist/modules/contracts/utils.module-api.d.ts +7 -0
  66. package/dist/modules/index.d.ts +22 -0
  67. package/dist/modules/is-mobile-device.d.ts +1 -0
  68. package/dist/modules/registry.d.ts +31 -0
  69. package/dist/rendering/SceneBloom.d.ts +40 -0
  70. package/dist/rendering/renderer-apply.d.ts +14 -0
  71. package/dist/rendering/renderer-options.d.ts +11 -0
  72. package/dist/rendering/tsl-node.d.ts +21 -0
  73. package/dist/rendering/tsl-uniforms.d.ts +18 -0
  74. package/dist/resources/audio.d.ts +15 -0
  75. package/dist/resources/default-resources.d.ts +36 -0
  76. package/dist/resources/font.d.ts +32 -0
  77. package/dist/resources/geometry.d.ts +5 -0
  78. package/dist/resources/hdr.d.ts +6 -0
  79. package/dist/resources/loaders.d.ts +45 -0
  80. package/dist/resources/model.d.ts +24 -0
  81. package/dist/resources/texture.d.ts +8 -0
  82. package/dist/resources/video.d.ts +14 -0
  83. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  84. package/dist/settings/AddonsRuntime.d.ts +19 -0
  85. package/dist/settings/externals.d.ts +14 -0
  86. package/dist/settings/lib-loaders.d.ts +7 -0
  87. package/dist/settings/lib-registry.d.ts +24 -0
  88. package/dist/settings/module-registry.d.ts +25 -0
  89. package/dist/settings/types.d.ts +46 -0
  90. package/dist/types.d.ts +80 -0
  91. package/dist/utils/lazy.d.ts +4 -0
  92. package/dist/values.d.ts +6 -0
  93. package/dist/version.d.ts +10 -0
  94. package/dist/version.gen.d.ts +1 -0
  95. package/dist/world-events.d.ts +13 -0
  96. package/package.json +53 -0
package/dist/index.js ADDED
@@ -0,0 +1,1707 @@
1
+ import { t as RapierPhysics } from "./RapierPhysics-C_3b7QOn.js";
2
+ import { n as getRadiusScale, t as getHalfheightRadiusScale } from "./get-scale-from-obj-QKv3Fig5.js";
3
+ import * as THREE from "three/webgpu";
4
+ import { BufferGeometryLoader, LoadingManager, TextureLoader } from "three/webgpu";
5
+ import * as ThreeStartNS from "three-start";
6
+ import { ContextModule, ThreeContextEvents, ThreeStart } from "three-start";
7
+ import * as TSL from "three/tsl";
8
+ import { emissive, mrt, output } from "three/tsl";
9
+ import van from "vanjs-core";
10
+ import EventEmitter from "eventemitter3";
11
+ import { Howl, Howler } from "howler";
12
+ import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
13
+ import { clone } from "three/examples/jsm/utils/SkeletonUtils.js";
14
+ //#region packages/world-runtime/src/world-events.ts
15
+ /**
16
+ * World lifecycle events, emitted on `game.emitter` (`world.events` in scripts)
17
+ * during classic-load. The entry script runs BEFORE them (step 9) — subscriptions
18
+ * from `main.ts` catch all three. See `docs/scripting/game-runtime.md`.
19
+ */
20
+ var WorldEvent = /* @__PURE__ */ function(WorldEvent) {
21
+ /** Scene parsed, values applied; objects are NOT yet added to `ctx.scene`. */
22
+ WorldEvent["BeforeSceneReady"] = "before-scene-ready";
23
+ /** Hierarchy added to `ctx.scene` — scene is ready; lifecycle not started yet. */
24
+ WorldEvent["SceneReady"] = "scene-ready";
25
+ /** `start()` called: behaviours woke up, the render loop is running. */
26
+ WorldEvent["Started"] = "started";
27
+ return WorldEvent;
28
+ }({});
29
+ //#endregion
30
+ //#region packages/world-runtime/src/World.ts
31
+ /**
32
+ * A loaded world is a three-start app with the scene already assembled.
33
+ * Mounting and starting are manual: `mount(container)` attaches the canvas and
34
+ * runs the render loop, `start()` activates behaviours (idempotent — call it
35
+ * after a user gesture if the world plays audio or captures the pointer).
36
+ */
37
+ var World = class extends ThreeStart {
38
+ _runtime;
39
+ constructor(options, _runtime) {
40
+ super(options);
41
+ this._runtime = _runtime;
42
+ }
43
+ /** Asset URL by project path — same resolving as `getUrl` in scripts. */
44
+ getUrl = (path) => this._runtime.getUrl(path);
45
+ /** World lifecycle event bus — the same one as `world.events` in scripts. */
46
+ get events() {
47
+ return this._runtime.emitter;
48
+ }
49
+ start() {
50
+ const wasStarted = this.isStarted;
51
+ super.start();
52
+ if (!wasStarted) this._runtime.emitter.emit(WorldEvent.Started);
53
+ return this;
54
+ }
55
+ dispose() {
56
+ this._runtime.dispose();
57
+ super.dispose();
58
+ }
59
+ };
60
+ //#endregion
61
+ //#region packages/world-runtime/src/utils/lazy.ts
62
+ /** Lazy cached value: the factory is called once on the first `get()`. */
63
+ function lazy(factory) {
64
+ let has = false;
65
+ let value;
66
+ return { get: () => {
67
+ if (!has) {
68
+ value = factory();
69
+ has = true;
70
+ }
71
+ return value;
72
+ } };
73
+ }
74
+ //#endregion
75
+ //#region packages/world-runtime/src/resources/loaders.ts
76
+ var LOADING_MANAGER = new LoadingManager();
77
+ LOADING_MANAGER.onError = (url) => {
78
+ console.error(`Loading asset failed: ${url}`);
79
+ };
80
+ var options = {
81
+ /**
82
+ * Draco decoder folder (file names inside are fixed, the folder is
83
+ * versioned). Default — the public Google decoder: always available and
84
+ * served with CORS, so an exported world works without our infrastructure.
85
+ * The host may override it: the app serves its own copy from
86
+ * `public/draco/<hash>/`, a self-hosted world — via `loadWorld({ dracoPath })`.
87
+ */
88
+ dracoPath: "https://www.gstatic.com/draco/v1/decoders/" };
89
+ /** Loader configuration. Called by the host before the first asset load. */
90
+ function configureAssetLoaders(next) {
91
+ Object.assign(options, next);
92
+ }
93
+ var m = LOADING_MANAGER;
94
+ /** Lazy three loaders: the chunk is pulled only on first use of the format. */
95
+ var AssetLoaders = {
96
+ texture: lazy(() => {
97
+ const loader = new TextureLoader(m);
98
+ loader.setCrossOrigin("anonymous");
99
+ return loader;
100
+ }),
101
+ gltf: lazy(async () => {
102
+ const [{ DRACOLoader }, { GLTFLoader }] = await Promise.all([import("three/examples/jsm/loaders/DRACOLoader.js"), import("three/examples/jsm/loaders/GLTFLoader.js")]);
103
+ const gltfLoader = new GLTFLoader(m);
104
+ const dracoLoader = new DRACOLoader(m);
105
+ dracoLoader.setDecoderPath(options.dracoPath);
106
+ dracoLoader.setCrossOrigin("anonymous");
107
+ gltfLoader.setDRACOLoader(dracoLoader);
108
+ gltfLoader.setCrossOrigin("anonymous");
109
+ return gltfLoader;
110
+ }),
111
+ fbx: lazy(async () => {
112
+ const { FBXLoader } = await import("three/examples/jsm/loaders/FBXLoader.js");
113
+ const loader = new FBXLoader(m);
114
+ loader.setCrossOrigin("anonymous");
115
+ return loader;
116
+ }),
117
+ obj: lazy(async () => {
118
+ const { OBJLoader } = await import("three/examples/jsm/loaders/OBJLoader.js");
119
+ const loader = new OBJLoader(m);
120
+ loader.setCrossOrigin("anonymous");
121
+ return loader;
122
+ }),
123
+ rgbe: lazy(async () => {
124
+ const { RGBELoader } = await import("three/examples/jsm/loaders/RGBELoader.js");
125
+ const loader = new RGBELoader(m);
126
+ loader.setCrossOrigin("anonymous");
127
+ return loader;
128
+ }),
129
+ exr: lazy(async () => {
130
+ const { EXRLoader } = await import("three/examples/jsm/loaders/EXRLoader.js");
131
+ const loader = new EXRLoader(m);
132
+ loader.setCrossOrigin("anonymous");
133
+ return loader;
134
+ }),
135
+ bufferGeometry: lazy(() => {
136
+ const loader = new BufferGeometryLoader(m);
137
+ loader.setCrossOrigin("anonymous");
138
+ return loader;
139
+ }),
140
+ font: lazy(async () => {
141
+ const [{ TTFLoader }, { Font }] = await Promise.all([import("three/examples/jsm/loaders/TTFLoader.js"), import("three/examples/jsm/loaders/FontLoader.js")]);
142
+ return {
143
+ ttfLoader: new TTFLoader(m),
144
+ Font
145
+ };
146
+ })
147
+ };
148
+ //#endregion
149
+ //#region packages/world-runtime/src/rendering/renderer-apply.ts
150
+ var SHADOW_MAP_TYPES$1 = {
151
+ Basic: THREE.BasicShadowMap,
152
+ PCF: THREE.PCFShadowMap,
153
+ PCFSoft: THREE.PCFSoftShadowMap,
154
+ VSM: THREE.VSMShadowMap
155
+ };
156
+ var TONE_MAPPINGS = {
157
+ None: THREE.NoToneMapping,
158
+ Linear: THREE.LinearToneMapping,
159
+ Reinhard: THREE.ReinhardToneMapping,
160
+ Cineon: THREE.CineonToneMapping,
161
+ ACESFilmic: THREE.ACESFilmicToneMapping,
162
+ AgX: THREE.AgXToneMapping,
163
+ Neutral: THREE.NeutralToneMapping
164
+ };
165
+ /**
166
+ * Applies the runtime part of `RendererConfig` to an already created WebGPURenderer
167
+ * (shadowMap, toneMapping, pixelRatio). Used by game (once on load) and editor
168
+ * (live, see `editor/rendering`). Defaults: PCFSoft shadows, Neutral tonemap,
169
+ * pixelResolution=1. Construction params (`cfg.renderer`) are applied only in
170
+ * the renderer constructor (see `createGameCtx`).
171
+ *
172
+ * The config is NOT schema-validated at runtime (raw JSON.parse), so bad values
173
+ * (enum typo, non-number) fall back to defaults instead of producing an
174
+ * `undefined` constant / `NaN` (silently broken rendering).
175
+ */
176
+ function applyRendererConfig(renderer, cfg) {
177
+ renderer.shadowMap.enabled = cfg.shadowMap?.enabled ?? true;
178
+ renderer.shadowMap.type = SHADOW_MAP_TYPES$1[cfg.shadowMap?.type ?? "PCFSoft"] ?? THREE.PCFSoftShadowMap;
179
+ renderer.toneMapping = TONE_MAPPINGS[cfg.toneMapping ?? "Neutral"] ?? THREE.NeutralToneMapping;
180
+ const pr = cfg.pixelResolution;
181
+ const pixelResolution = typeof pr === "number" && Number.isFinite(pr) && pr > 0 ? pr : 1;
182
+ renderer.setPixelRatio(window.devicePixelRatio * pixelResolution);
183
+ }
184
+ //#endregion
185
+ //#region packages/world-runtime/src/rendering/SceneBloom.ts
186
+ /** BloomNode loads as a chunk only when bloom is enabled. */
187
+ var lazyBloomModule = lazy(() => import("three/addons/tsl/display/BloomNode.js"));
188
+ /**
189
+ * Enables MRT `{ output, emissive }` on the scenePass. Idempotent. IMPORTANT:
190
+ * MRT is part of the WebGPU pipelines' attachment state; enable it either before
191
+ * the first frame (game), or sticky + material invalidation (editor). It must
192
+ * NEVER be disabled at runtime — see `./README.md` → "MRT and attachment state".
193
+ */
194
+ function enableSceneEmissiveMrt(scenePass) {
195
+ if (scenePass.getMRT()) return;
196
+ scenePass.setMRT(mrt({
197
+ output,
198
+ emissive
199
+ }));
200
+ }
201
+ /**
202
+ * Bloom post-effect over the scenePass (editor + game). `mode=threshold` —
203
+ * full-frame by brightness; `mode=emissive` — selective, via the emissive MRT
204
+ * target (MRT must be enabled by the caller via `enableSceneEmissiveMrt` BEFORE
205
+ * creation). SceneBloom does not own the MRT topology. Pipeline contracts — `./README.md`.
206
+ */
207
+ var SceneBloom = class SceneBloom {
208
+ mode;
209
+ /** Bloom contribution (RGB) — for custom outputNode composition (editor). */
210
+ node;
211
+ /** `scenePassColor + bloom` — ready outputNode for the default pipeline (game). */
212
+ sceneWithBloom;
213
+ constructor(props) {
214
+ this.mode = props.mode;
215
+ this.node = props.node;
216
+ this.sceneWithBloom = props.scenePass.getTextureNode().add(props.node);
217
+ }
218
+ static async createAsync(props) {
219
+ const { bloom } = await lazyBloomModule.get();
220
+ const { scenePass, config } = props;
221
+ const mode = config.mode ?? "threshold";
222
+ let node;
223
+ if (mode === "emissive") {
224
+ if (!scenePass.getMRT()) throw new Error("SceneBloom: emissive mode requires MRT on scenePass — call enableSceneEmissiveMrt() first");
225
+ node = bloom(scenePass.getTextureNode("emissive"));
226
+ } else node = bloom(scenePass.getTextureNode());
227
+ const sceneBloom = new SceneBloom({
228
+ scenePass,
229
+ mode,
230
+ node
231
+ });
232
+ sceneBloom.updateParams(config);
233
+ return sceneBloom;
234
+ }
235
+ /** strength/radius/threshold are uniforms — applied on the fly without a rebuild. */
236
+ updateParams(cfg) {
237
+ this.strength = finiteOr(cfg.strength, 1);
238
+ this.radius = finiteOr(cfg.radius, 0);
239
+ this.threshold = this.mode === "emissive" ? 0 : finiteOr(cfg.threshold, 0);
240
+ }
241
+ get strength() {
242
+ return this.node.strength.value;
243
+ }
244
+ set strength(value) {
245
+ this.node.strength.value = value;
246
+ }
247
+ get radius() {
248
+ return this.node.radius.value;
249
+ }
250
+ set radius(value) {
251
+ this.node.radius.value = value;
252
+ }
253
+ get threshold() {
254
+ return this.node.threshold.value;
255
+ }
256
+ set threshold(value) {
257
+ this.node.threshold.value = value;
258
+ }
259
+ /** true — a mode change requires recreation (dispose + createAsync + new outputNode). */
260
+ needsRebuild(cfg) {
261
+ return (cfg.mode ?? "threshold") !== this.mode;
262
+ }
263
+ dispose() {
264
+ this.node.dispose();
265
+ }
266
+ };
267
+ function finiteOr(value, fallback) {
268
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
269
+ }
270
+ //#endregion
271
+ //#region packages/world-runtime/src/modules/is-mobile-device.ts
272
+ function isMobileDevice() {
273
+ if (typeof navigator === "undefined" || typeof window === "undefined") return false;
274
+ const ua = navigator.userAgent || navigator.vendor || window.opera;
275
+ if (/android/i.test(ua)) return true;
276
+ if (/iPhone|iPad|iPod/i.test(ua)) return true;
277
+ if (/Mobile|Opera Mini|IEMobile|WPDesktop/i.test(ua)) return true;
278
+ if (/Mobile|Mobi/i.test(ua)) return true;
279
+ return "ontouchstart" in window || navigator.maxTouchPoints && navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
280
+ }
281
+ //#endregion
282
+ //#region packages/world-runtime/src/modules/EnvModule.ts
283
+ var EnvModule = class extends ContextModule {
284
+ dev = true;
285
+ prod = true;
286
+ mode;
287
+ isMobile;
288
+ constructor(mode) {
289
+ super();
290
+ this.mode = mode;
291
+ this.dev = mode === "dev";
292
+ this.prod = mode === "prod";
293
+ this.isMobile = isMobileDevice();
294
+ }
295
+ };
296
+ //#endregion
297
+ //#region packages/world-runtime/src/modules/HtmlHudModule.ts
298
+ var { div } = van.tags;
299
+ var HtmlHudModule = class extends ContextModule {
300
+ root;
301
+ crosshair;
302
+ constructor() {
303
+ super();
304
+ this.crosshair = div({
305
+ id: "kvyverse-game-gui-crosshair",
306
+ style: "position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);"
307
+ });
308
+ this.root = div({
309
+ id: "kvyverse-game-gui-root",
310
+ style: "position:fixed;inset:0;pointer-events:none;"
311
+ }, this.crosshair);
312
+ }
313
+ onAwake() {
314
+ const ctx = this.ctx;
315
+ if (ctx.canvasContainer) this.onMount(ctx.canvasContainer);
316
+ ctx.on(ThreeContextEvents.Mount, this.onMount);
317
+ ctx.on(ThreeContextEvents.Unmount, this.onUnmount);
318
+ }
319
+ append(...nodes) {
320
+ this.root.append(...nodes);
321
+ }
322
+ setCrosshair(innerHtml) {
323
+ this.crosshair.innerHTML = innerHtml;
324
+ }
325
+ /** three-start ContextModule has no onDestroy; teardown is explicit. */
326
+ dispose() {
327
+ this.ctx.off(ThreeContextEvents.Mount, this.onMount);
328
+ this.ctx.off(ThreeContextEvents.Unmount, this.onUnmount);
329
+ }
330
+ onMount = (container) => {
331
+ container.append(this.root);
332
+ };
333
+ onUnmount = () => {
334
+ this.root.remove();
335
+ };
336
+ };
337
+ //#endregion
338
+ //#region packages/world-runtime/src/modules/InputSystemModule.ts
339
+ var PlayerInput = class extends EventEmitter {
340
+ _forward = 0;
341
+ _lateral = 0;
342
+ _force = 1;
343
+ /**
344
+ * @default 0
345
+ */
346
+ get forward() {
347
+ return this._forward;
348
+ }
349
+ /**
350
+ * @default 0
351
+ */
352
+ get lateral() {
353
+ return this._lateral;
354
+ }
355
+ /**
356
+ * @default 1
357
+ */
358
+ get force() {
359
+ return this._force;
360
+ }
361
+ setDir(forward, lateral) {
362
+ this._forward = forward;
363
+ this._lateral = lateral;
364
+ this.emit("dirchanged", forward, lateral);
365
+ }
366
+ setForce(force) {
367
+ this._force = force;
368
+ this.emit("forcechanged", force);
369
+ }
370
+ jump() {
371
+ this.emit("jump");
372
+ }
373
+ /**
374
+ * 0 is keyboard, 1 is joystick
375
+ * @default 0
376
+ */
377
+ mode = 0;
378
+ };
379
+ var InputSystemModule = class extends ContextModule {
380
+ _dom;
381
+ _keys = /* @__PURE__ */ new Set();
382
+ _shift = false;
383
+ _alt = false;
384
+ _ctrl = false;
385
+ _meta = false;
386
+ _isDragging = false;
387
+ _pointerNdc = new THREE.Vector2();
388
+ _averageActivePointersCoords = {
389
+ x: 0,
390
+ y: 0
391
+ };
392
+ _pointerXStack = [0, 0];
393
+ _pointerYStack = [0, 0];
394
+ _domBoundingClientRect = null;
395
+ activePointers = /* @__PURE__ */ new Map();
396
+ get pointerDeltaX() {
397
+ return this._pointerXStack[1] - this._pointerXStack[0];
398
+ }
399
+ get pointerDeltaY() {
400
+ return this._pointerYStack[1] - this._pointerYStack[0];
401
+ }
402
+ get isDragging() {
403
+ return this._isDragging;
404
+ }
405
+ /** Pointer position {x,y} in normalized device coordinates (-1 to +1) */
406
+ get pointerNdc() {
407
+ return this._pointerNdc;
408
+ }
409
+ get shiftKey() {
410
+ return this._shift;
411
+ }
412
+ get altKey() {
413
+ return this._alt;
414
+ }
415
+ get ctrlKey() {
416
+ return this._ctrl;
417
+ }
418
+ get metaKey() {
419
+ return this._meta;
420
+ }
421
+ isKeyDown = (key) => {
422
+ return this._keys.has(key);
423
+ };
424
+ player = new PlayerInput();
425
+ onAwake() {
426
+ const ctx = this.ctx;
427
+ this._dom = ctx.renderer.domElement;
428
+ window.addEventListener("resize", this.onWindowResize);
429
+ window.addEventListener("scroll", this.onWindowScroll);
430
+ ctx.on(ThreeContextEvents.Resized, this.onDomResize);
431
+ const on = this._dom.addEventListener.bind(this._dom);
432
+ on("focus", this.onFocus);
433
+ on("blur", this.onBlur);
434
+ on("pointerenter", this.onPointerEnter);
435
+ on("pointerdown", this.onPointerDown);
436
+ on("pointermove", this.onPointerMove);
437
+ on("pointerup", this.onPointerUp);
438
+ on("pointerleave", this.onPointerLeave);
439
+ on("keydown", this.onKeyDown);
440
+ on("keyup", this.onKeyUp);
441
+ }
442
+ /** three-start `ContextModule` has no `onDestroy`; teardown is explicit. */
443
+ dispose() {
444
+ window.removeEventListener("resize", this.onWindowResize);
445
+ window.removeEventListener("scroll", this.onWindowScroll);
446
+ this.ctx.off(ThreeContextEvents.Resized, this.onDomResize);
447
+ const off = this._dom.removeEventListener.bind(this._dom);
448
+ off("focus", this.onFocus);
449
+ off("blur", this.onBlur);
450
+ off("pointerenter", this.onPointerEnter);
451
+ off("pointerdown", this.onPointerDown);
452
+ off("pointermove", this.onPointerMove);
453
+ off("pointerup", this.onPointerUp);
454
+ off("pointerleave", this.onPointerLeave);
455
+ off("keydown", this.onKeyDown);
456
+ off("keyup", this.onKeyUp);
457
+ }
458
+ onWindowResize = () => {
459
+ this.updateDomBoundingClientRect();
460
+ };
461
+ onWindowScroll = () => {
462
+ this.updateDomBoundingClientRect();
463
+ };
464
+ onDomResize = () => {
465
+ this.updateDomBoundingClientRect();
466
+ };
467
+ onFocus = () => {
468
+ this.resetKeys();
469
+ this.resetPointers();
470
+ };
471
+ onBlur = () => {
472
+ this.resetKeys();
473
+ this.resetPointers();
474
+ };
475
+ onKeyDown = (event) => {
476
+ this.updFnKeys(event);
477
+ this._keys.add(event.code);
478
+ this.emit("keyschanged");
479
+ };
480
+ onKeyUp = (event) => {
481
+ this.updFnKeys(event);
482
+ this._keys.delete(event.code);
483
+ this.emit("keyschanged");
484
+ };
485
+ onPointerEnter = (_) => {};
486
+ onPointerDown = (event) => {
487
+ this.activePointers.set(event.pointerId, event);
488
+ this.updateStackAndNdcByActivePointers(true);
489
+ this._isDragging = true;
490
+ };
491
+ onPointerMove = (event) => {
492
+ if (this.activePointers.size > 0) {
493
+ this.activePointers.has(event.pointerId) && this.activePointers.set(event.pointerId, event);
494
+ this.updateStackAndNdcByActivePointers();
495
+ } else this.updateStackAndNdcByPointer(event.clientX, event.clientY);
496
+ };
497
+ onPointerUp = (event) => {
498
+ this.activePointers.delete(event.pointerId);
499
+ this._isDragging = this.activePointers.size > 0;
500
+ };
501
+ onPointerLeave = () => {
502
+ this.resetKeys();
503
+ this.resetPointers();
504
+ };
505
+ resetKeys() {
506
+ this._shift = false;
507
+ this._alt = false;
508
+ this._ctrl = false;
509
+ this._meta = false;
510
+ this._keys.clear();
511
+ }
512
+ updFnKeys(event) {
513
+ this._shift = event.shiftKey;
514
+ this._alt = event.altKey;
515
+ this._ctrl = event.ctrlKey;
516
+ this._meta = event.metaKey;
517
+ }
518
+ updateAverageActivePointersCoords() {
519
+ let x = 0;
520
+ let y = 0;
521
+ const size = this.activePointers.size;
522
+ if (size === 1) {
523
+ const pointerEvent = this.activePointers.values().next().value;
524
+ if (pointerEvent) {
525
+ x = pointerEvent.clientX;
526
+ y = pointerEvent.clientY;
527
+ }
528
+ } else if (size > 1) {
529
+ let sumX = 0;
530
+ let sumY = 0;
531
+ this.activePointers.forEach((pointer) => {
532
+ sumX += pointer.clientX;
533
+ sumY += pointer.clientY;
534
+ });
535
+ x = sumX / size;
536
+ y = sumY / size;
537
+ }
538
+ this._averageActivePointersCoords.x = x;
539
+ this._averageActivePointersCoords.y = y;
540
+ return this._averageActivePointersCoords;
541
+ }
542
+ updateStackAndNdcByActivePointers = (fillStack) => {
543
+ const { x, y } = this.updateAverageActivePointersCoords();
544
+ this.updateStackAndNdcByPointer(x, y, fillStack);
545
+ };
546
+ updateStackAndNdcByPointer(x, y, fillStack) {
547
+ if (fillStack) this.fillPointerStack(x, y);
548
+ else this.pushPointerStack(x, y);
549
+ this.recalculatePointerNdc(x, y);
550
+ }
551
+ pushPointerStack(x, y) {
552
+ this._pointerXStack.shift();
553
+ this._pointerXStack.push(x);
554
+ this._pointerYStack.shift();
555
+ this._pointerYStack.push(y);
556
+ }
557
+ fillPointerStack(x, y) {
558
+ this._pointerXStack = [x, x];
559
+ this._pointerYStack = [y, y];
560
+ }
561
+ resetPointers() {
562
+ this._pointerXStack = [0, 0];
563
+ this._pointerYStack = [0, 0];
564
+ this.activePointers.clear();
565
+ }
566
+ recalculatePointerNdc(clientX, clientY) {
567
+ this._pointerNdc.x = clientX / this._dom.clientWidth * 2 - 1;
568
+ this._pointerNdc.y = -(clientY / this._dom.clientHeight) * 2 + 1;
569
+ const bounds = this._domBoundingClientRect ?? this.updateDomBoundingClientRect();
570
+ const fixedClientX = (clientX - bounds.left) / bounds.width;
571
+ const fixedClientY = (clientY - bounds.top) / bounds.height;
572
+ this._pointerNdc.x = fixedClientX * 2 - 1;
573
+ this._pointerNdc.y = -(fixedClientY * 2 - 1);
574
+ }
575
+ updateDomBoundingClientRect() {
576
+ this._domBoundingClientRect = this._dom.getBoundingClientRect();
577
+ return this._domBoundingClientRect;
578
+ }
579
+ };
580
+ //#endregion
581
+ //#region packages/world-runtime/src/modules/SoundsModule.ts
582
+ var SoundsModule = class extends ContextModule {
583
+ global = Howler;
584
+ /** three-start ContextModule has no onDestroy; teardown is explicit. */
585
+ dispose() {
586
+ Howler.stop();
587
+ }
588
+ };
589
+ //#endregion
590
+ //#region packages/world-runtime/src/modules/UtilsModule.ts
591
+ var UtilsModule = class extends ContextModule {
592
+ cloneObject3D;
593
+ mergeGeometries;
594
+ constructor() {
595
+ super();
596
+ this.cloneObject3D = clone;
597
+ this.mergeGeometries = mergeGeometries;
598
+ }
599
+ };
600
+ //#endregion
601
+ //#region packages/world-runtime/src/modules/index.ts
602
+ /** World ctx modules. Registered before `start()` — three-start forbids it later. */
603
+ function createWorldModules(mode = "prod") {
604
+ return {
605
+ env: new EnvModule(mode),
606
+ html: new HtmlHudModule(),
607
+ input: new InputSystemModule(),
608
+ sounds: new SoundsModule(),
609
+ utils: new UtilsModule()
610
+ };
611
+ }
612
+ //#endregion
613
+ //#region packages/world-runtime/src/behaviours/physics/_required-patch.ts
614
+ /** rapier loads lazily: the wasm is pulled only in worlds with physics. */
615
+ var lazyRapier = lazy(async () => {
616
+ const rapier = await import("@dimforge/rapier3d-compat");
617
+ await rapier.init();
618
+ return rapier;
619
+ });
620
+ var lazyRapierPhysicsModule = lazy(async () => (await import("./RapierPhysics-C_3b7QOn.js").then((n) => n.n)).default);
621
+ var lazyRapierDebugModule = lazy(async () => (await import("./RapierDebugRenderer-sURZDLP4.js")).default);
622
+ /**
623
+ * Registers rapier modules before bootstrap: three-start forbids `addModules`
624
+ * after `start()`. Idempotent — a repeated call sees the module in ctx and exits.
625
+ */
626
+ async function applyRapierPhysicsPatch(starter) {
627
+ const RAPIER = await lazyRapier.get();
628
+ const RapierPhysicsModule = await lazyRapierPhysicsModule.get();
629
+ const RapierDebugRenderer = await lazyRapierDebugModule.get();
630
+ if (RapierPhysics.findInCtx(starter.ctx)) return;
631
+ starter.addModules({
632
+ rapier: new RapierPhysicsModule(RAPIER),
633
+ rapierDebug: new RapierDebugRenderer()
634
+ });
635
+ }
636
+ //#endregion
637
+ //#region packages/world-runtime/src/behaviours/registry.ts
638
+ var entries = [
639
+ {
640
+ uuid: "spinning",
641
+ name: "Spinning",
642
+ load: () => loadDefault(import("./Spinning-sgUhHld8.js"))
643
+ },
644
+ {
645
+ uuid: "scalingtofro",
646
+ name: "ScalingToFro",
647
+ load: () => loadDefault(import("./ScalingToFro-BD-E5-uR.js"))
648
+ },
649
+ {
650
+ uuid: "startanim",
651
+ name: "StartAnimation",
652
+ load: () => loadDefault(import("./StartAnimation-B9eomOS2.js"))
653
+ },
654
+ {
655
+ uuid: "pointerlockcameracontrols",
656
+ name: "PointerLockCameraController",
657
+ load: () => loadDefault(import("./PointerLockCameraControls-DYxxMeSw.js"))
658
+ },
659
+ {
660
+ uuid: "simplewasdmovement",
661
+ name: "SimpleWASDMovement",
662
+ load: () => loadDefault(import("./SimpleWASDMovement-C2m53YYH.js"))
663
+ },
664
+ {
665
+ uuid: "fpscameracontroller",
666
+ name: "FpsCameraController",
667
+ load: () => loadDefault(import("./FpsCameraController-BE2EirRj.js"))
668
+ },
669
+ {
670
+ uuid: "sightinteractablearea",
671
+ name: "SightInteractableArea",
672
+ load: () => loadDefault(import("./SightInteractableArea-DrqyB41W.js"))
673
+ },
674
+ {
675
+ uuid: "meshinstansing",
676
+ name: "MeshInstansing",
677
+ load: () => loadDefault(import("./MeshInstansing-Bxij0Yc9.js"))
678
+ },
679
+ {
680
+ uuid: "rrb",
681
+ name: "Rigidbody",
682
+ load: () => loadDefault(import("./Rigidbody-b7QNF4ev.js").then((n) => n.n)),
683
+ requiredPatch: applyRapierPhysicsPatch
684
+ },
685
+ {
686
+ uuid: "rapierballcol",
687
+ name: "BallCollider",
688
+ load: () => loadDefault(import("./BallCollider-8ZrxReEd.js")),
689
+ requiredPatch: applyRapierPhysicsPatch
690
+ },
691
+ {
692
+ uuid: "rapiercapscol",
693
+ name: "CapsuleCollider",
694
+ load: () => loadDefault(import("./CapsuleCollider-bynyf6Mc.js")),
695
+ requiredPatch: applyRapierPhysicsPatch
696
+ },
697
+ {
698
+ uuid: "rapierconecol",
699
+ name: "ConeCollider",
700
+ load: () => loadDefault(import("./ConeCollider-DtRZmgK8.js")),
701
+ requiredPatch: applyRapierPhysicsPatch
702
+ },
703
+ {
704
+ uuid: "rapierconvxcol",
705
+ name: "ConvexMeshCollider",
706
+ load: () => loadDefault(import("./ConvexMeshCollider-BSmDM4o3.js")),
707
+ requiredPatch: applyRapierPhysicsPatch
708
+ },
709
+ {
710
+ uuid: "rapiercubcol",
711
+ name: "CuboidCollider",
712
+ load: () => loadDefault(import("./CuboidCollider-CJvq7--s.js")),
713
+ requiredPatch: applyRapierPhysicsPatch
714
+ },
715
+ {
716
+ uuid: "rapiercylcol",
717
+ name: "CylinderCollider",
718
+ load: () => loadDefault(import("./CylinderCollider-CQPSh4tu.js")),
719
+ requiredPatch: applyRapierPhysicsPatch
720
+ },
721
+ {
722
+ uuid: "rapiertricol",
723
+ name: "TrimeshCollider",
724
+ load: () => loadDefault(import("./TrimeshCollider-7dIs_xtZ.js")),
725
+ requiredPatch: applyRapierPhysicsPatch
726
+ },
727
+ {
728
+ uuid: "rapierfpsplayercontroller",
729
+ name: "RapierFpsPlayerController",
730
+ load: () => loadDefault(import("./RapierFpsPlayerController-VsNfQl6v.js")),
731
+ requiredPatch: applyRapierPhysicsPatch
732
+ }
733
+ ];
734
+ /** Registry of built-in behaviours by uuid — source of truth for both game and export. */
735
+ var BUILTIN_BEHAVIOURS = Object.fromEntries(entries.map((entry) => [entry.uuid, entry]));
736
+ var BUILTIN_BEHAVIOUR_LIST = entries;
737
+ function getBuiltinBehaviour(uuid) {
738
+ return BUILTIN_BEHAVIOURS[uuid];
739
+ }
740
+ async function loadDefault(module) {
741
+ return (await module).default;
742
+ }
743
+ //#endregion
744
+ //#region packages/world-runtime/src/resources/font.ts
745
+ var FORMAT_BY_MIME = {
746
+ "font/ttf": "truetype",
747
+ "font/otf": "opentype",
748
+ "font/woff": "woff",
749
+ "font/woff2": "woff2"
750
+ };
751
+ /** CSS font format by mime (for `@font-face … format(...)`). */
752
+ function fontFormatFromMime(mime) {
753
+ return FORMAT_BY_MIME[mime] ?? "truetype";
754
+ }
755
+ /**
756
+ * Font source: the CSS side (`family` + `toFontFaceCss`) and the three side
757
+ * (`getThreeFontAsync`, lazy TTFLoader/opentype chunk).
758
+ */
759
+ var FontSource = class {
760
+ url;
761
+ format;
762
+ name = "";
763
+ uuid = crypto.randomUUID();
764
+ /** CSS font-family. Edited in the inspector, persisted in `values.src.family`. */
765
+ family;
766
+ _threeFontPromise = null;
767
+ constructor(url, family, format) {
768
+ this.url = url;
769
+ this.format = format;
770
+ this.family = family;
771
+ }
772
+ /** Ready-to-use `@font-face` rule for the game document. */
773
+ toFontFaceCss() {
774
+ return `@font-face { font-family: ${JSON.stringify(this.family)}; src: url("${this.url}") format("${this.format}"); }`;
775
+ }
776
+ /**
777
+ * three.js `Font` (for TextGeometry etc.). Lazy TTFLoader import
778
+ * (pulls opentype ~100kB gzip), the result is cached. woff2 is not
779
+ * supported by opentype — throws a clear error.
780
+ */
781
+ getThreeFontAsync() {
782
+ if (this._threeFontPromise) return this._threeFontPromise;
783
+ this._threeFontPromise = this._parseThreeFontAsync();
784
+ this._threeFontPromise.catch(() => this._threeFontPromise = null);
785
+ return this._threeFontPromise;
786
+ }
787
+ dispose() {
788
+ this._threeFontPromise = null;
789
+ if (this.url.startsWith("blob:")) URL.revokeObjectURL(this.url);
790
+ }
791
+ async _parseThreeFontAsync() {
792
+ if (this.format === "woff2") throw new Error(`FontSource "${this.name}": woff2 is not supported by three TTFLoader (opentype.js). Use ttf/otf/woff for 3D text, or use this font via CSS font-family ${JSON.stringify(this.family)}.`);
793
+ const response = await fetch(this.url);
794
+ if (!response.ok) throw new Error(`Failed to load font "${this.url}": ${response.status}`);
795
+ const { ttfLoader, Font } = await AssetLoaders.font.get();
796
+ return new Font(ttfLoader.parse(await response.arrayBuffer()));
797
+ }
798
+ };
799
+ /** Font source from a URL. `mime` determines the CSS format of the `@font-face` rule. */
800
+ function createFont(url, family, mime, name) {
801
+ const source = new FontSource(url, family, fontFormatFromMime(mime));
802
+ if (name !== void 0) source.name = name;
803
+ return source;
804
+ }
805
+ //#endregion
806
+ //#region packages/world-runtime/src/settings/lib-registry.ts
807
+ /**
808
+ * Registry of built-in libraries enabled via `settings.addons.json → libs`.
809
+ *
810
+ * Separates two keys: `id` (short config key under `libs`) and `requireKey`
811
+ * (what the user writes in `require(...)` in scripts). Single source for:
812
+ * - game: dynamic loading (`lib-loaders`),
813
+ * - editor: JSON schema (`libs` props) + require typings (`AddonsRequireTypings`).
814
+ *
815
+ * Adding a lib = add an entry here (+ loader in game, + d.ts loader in editor).
816
+ */
817
+ var LibId = /* @__PURE__ */ function(LibId) {
818
+ LibId["Tween"] = "tween";
819
+ LibId["Anime"] = "anime";
820
+ return LibId;
821
+ }({});
822
+ var LIBS = {
823
+ ["tween"]: {
824
+ id: "tween",
825
+ requireKey: "@tweenjs/tween.js",
826
+ label: "Tween.js"
827
+ },
828
+ ["anime"]: {
829
+ id: "anime",
830
+ requireKey: "animejs/animation",
831
+ label: "Anime.js (animate)"
832
+ }
833
+ };
834
+ var LIB_IDS = Object.values(LibId);
835
+ var LIB_DESCRIPTORS = LIB_IDS.map((id) => LIBS[id]);
836
+ function libByRequireKey(requireKey) {
837
+ return LIB_DESCRIPTORS.find((l) => l.requireKey === requireKey);
838
+ }
839
+ //#endregion
840
+ //#region packages/world-runtime/src/settings/lib-loaders.ts
841
+ /**
842
+ * Dynamic loaders of built-in libs (game-only). Key — `LibId` from the registry;
843
+ * the value resolves for the user via `require("<requireKey>")`. Imports are lazy —
844
+ * bundled only when the lib is enabled in `settings.addons.json`.
845
+ */
846
+ var LIB_LOADERS = {
847
+ [LibId.Tween]: () => import("@tweenjs/tween.js"),
848
+ [LibId.Anime]: () => import("animejs/animation")
849
+ };
850
+ //#endregion
851
+ //#region packages/world-runtime/src/settings/AddonsRuntime.ts
852
+ /**
853
+ * Runtime loading of addons (game-only): enabled built-in libs + external
854
+ * scripts (externals). Exposes them to user scripts via `resolve(key)`
855
+ * (used in `ScriptsRuntime.require`). Replaces the old
856
+ * `GameLoader.loadExternalsAsync` / `getExternalLib`.
857
+ */
858
+ var AddonsRuntime = class {
859
+ /** libs — by requireKey (`@tweenjs/tween.js`), externals — by accessor (`Tone`). */
860
+ _byKey = /* @__PURE__ */ new Map();
861
+ _scriptTags = [];
862
+ async load(addons) {
863
+ await this._loadLibs(addons.libs);
864
+ await this._loadExternals(addons.externals);
865
+ }
866
+ /** Resolves a require key: enabled lib or external accessor; undefined otherwise. */
867
+ resolve(key) {
868
+ return this._byKey.get(key);
869
+ }
870
+ async _loadLibs(libs) {
871
+ if (!libs) return;
872
+ for (const id of LIB_IDS) {
873
+ if (!libs[id]) continue;
874
+ try {
875
+ this._byKey.set(LIBS[id].requireKey, await LIB_LOADERS[id]());
876
+ } catch (e) {
877
+ console.error(`[addons] failed to load lib "${id}"`, e);
878
+ }
879
+ }
880
+ }
881
+ async _loadExternals(externals) {
882
+ if (!externals) return;
883
+ for (const ext of externals) try {
884
+ if (ext.type === "module") this._byKey.set(ext.accessor, await import(
885
+ /* @vite-ignore */
886
+ ext.src
887
+ ));
888
+ else {
889
+ await this._mountScript(ext.src);
890
+ this._byKey.set(ext.accessor, window[ext.accessor]);
891
+ }
892
+ } catch (e) {
893
+ console.error(`[addons] failed to load external "${ext.accessor}"`, e);
894
+ }
895
+ }
896
+ _mountScript(src) {
897
+ return new Promise((resolve, reject) => {
898
+ const script = document.createElement("script");
899
+ script.src = src;
900
+ script.onload = () => resolve();
901
+ script.onerror = () => reject(/* @__PURE__ */ new Error(`external script failed: ${src}`));
902
+ document.head.appendChild(script);
903
+ this._scriptTags.push(script);
904
+ });
905
+ }
906
+ dispose() {
907
+ for (const tag of this._scriptTags) tag.remove();
908
+ this._scriptTags.length = 0;
909
+ this._byKey.clear();
910
+ }
911
+ };
912
+ //#endregion
913
+ //#region packages/world-runtime/src/settings/module-registry.ts
914
+ /**
915
+ * Registry of pluggable engine modules (`settings.addons.json → modules`).
916
+ * These are three-start modules that must be registered before `start()`.
917
+ * Feature toggles (chat/joystick/debugPerf) are NOT here — they live in
918
+ * `settings.json` (see `../features/feature-registry`).
919
+ *
920
+ * The only module so far is physics.
921
+ * Installation lives in game (`game/settings/module-installers`).
922
+ */
923
+ var ModuleId = /* @__PURE__ */ function(ModuleId) {
924
+ ModuleId["Physics"] = "physics";
925
+ return ModuleId;
926
+ }({});
927
+ var PHYSICS_ENGINES = ["rapier"];
928
+ var MODULE_IDS = Object.values(ModuleId);
929
+ /** Normalizes the physics config to an object (or null if disabled). */
930
+ function normalizePhysics(value) {
931
+ if (!value) return null;
932
+ if (typeof value === "string") return {
933
+ engine: value,
934
+ debug: false
935
+ };
936
+ return {
937
+ engine: value.engine,
938
+ debug: value.debug ?? false
939
+ };
940
+ }
941
+ //#endregion
942
+ //#region packages/world-runtime/src/WorldRuntime.ts
943
+ /**
944
+ * Owner of the world's runtime state: assets, script execution, ctx access.
945
+ * The only object the generated code receives.
946
+ */
947
+ var WorldRuntime = class {
948
+ definition;
949
+ options;
950
+ /** World lifecycle event bus (`world.events` in scripts). */
951
+ emitter = new EventEmitter();
952
+ _addons = new AddonsRuntime();
953
+ _executedScripts = /* @__PURE__ */ new Map();
954
+ _builtins = /* @__PURE__ */ new Map();
955
+ _styleElements = [];
956
+ _resources = {};
957
+ _ctx = null;
958
+ _bloom = null;
959
+ _scriptContext = null;
960
+ constructor(definition, options) {
961
+ this.definition = definition;
962
+ this.options = options;
963
+ }
964
+ /** ctx appears at the 3d-context creation step; unavailable to scripts before that. */
965
+ get ctx() {
966
+ if (!this._ctx) throw new Error("World runtime: 3d context is not created yet");
967
+ return this._ctx;
968
+ }
969
+ /** @internal Set by bootstrap. */
970
+ setContext(ctx) {
971
+ this._ctx = ctx;
972
+ }
973
+ /** @internal World bloom, if enabled in the renderer config. */
974
+ setBloom(bloom) {
975
+ this._bloom = bloom;
976
+ }
977
+ /** @internal Built-in libs and external scripts from `settings.addons.json`. */
978
+ async loadAddons() {
979
+ const addons = this.definition.addons;
980
+ if (addons) await this._addons.load(addons);
981
+ }
982
+ /** @internal World resources by asset path — result of `loadAssets`. */
983
+ setResources(resources) {
984
+ this._resources = resources;
985
+ }
986
+ reportProgress(stage, details) {
987
+ const progress = {
988
+ stage,
989
+ details
990
+ };
991
+ this.options.onProgress?.(progress);
992
+ }
993
+ getUrl = (path) => {
994
+ const url = this.definition.assets[path];
995
+ if (url === void 0) throw new Error(`World runtime: unknown asset "${path}"`);
996
+ if (isAbsoluteUrl(url)) return url;
997
+ return `${this.options.assetsBaseUrl ?? ""}${url}`;
998
+ };
999
+ /**
1000
+ * Built-in behaviour class by uuid. Classes are preloaded by bootstrap,
1001
+ * so attaching in `buildScene` is synchronous.
1002
+ */
1003
+ builtin = (uuid) => {
1004
+ const Component = this._builtins.get(uuid);
1005
+ if (!Component) throw new Error(`World runtime: built-in behaviour "${uuid}" is not loaded`);
1006
+ return Component;
1007
+ };
1008
+ /** @internal Loads classes of the used built-in behaviours (before scene assembly). */
1009
+ async loadBuiltins() {
1010
+ const uuids = this.definition.usedBuiltins;
1011
+ if (!uuids || uuids.length === 0) return;
1012
+ await Promise.all(uuids.map(async (uuid) => {
1013
+ const entry = getBuiltinBehaviour(uuid);
1014
+ if (!entry) {
1015
+ console.error(`World runtime: unknown built-in behaviour "${uuid}"`);
1016
+ return;
1017
+ }
1018
+ this._builtins.set(uuid, await entry.load());
1019
+ }));
1020
+ }
1021
+ /**
1022
+ * @internal Registers ctx modules required by built-in behaviours (physics).
1023
+ * Strictly before `start()` — three-start forbids `addModules` afterwards.
1024
+ */
1025
+ async applyRequiredPatches(starter) {
1026
+ for (const uuid of this.definition.usedBuiltins ?? []) {
1027
+ const patch = getBuiltinBehaviour(uuid)?.requiredPatch;
1028
+ if (!patch) continue;
1029
+ try {
1030
+ await patch(starter);
1031
+ } catch (error) {
1032
+ console.error(`Required patch of "${uuid}" failed`, error);
1033
+ }
1034
+ }
1035
+ if (normalizePhysics(this.definition.addons?.modules?.physics)) try {
1036
+ await applyRapierPhysicsPatch(starter);
1037
+ } catch (error) {
1038
+ console.error("Physics module patch failed", error);
1039
+ }
1040
+ }
1041
+ /** Addon (built-in lib / external) → script by path → asset resource. Same
1042
+ * resolve order as in play mode. */
1043
+ require = (key) => {
1044
+ const addon = this._addons.resolve(key);
1045
+ if (addon !== void 0) return addon;
1046
+ if (this.definition.scripts?.[key]) return this.executeScript(key);
1047
+ const resource = this._resources[key];
1048
+ if (resource !== void 0) return resource;
1049
+ throw new Error(`World runtime: cannot resolve require("${key}")`);
1050
+ };
1051
+ /**
1052
+ * Runtime spawn: a prefab (build-time factory) is assembled anew, a regular
1053
+ * object is cloned. Returns a detached object — the caller adds it.
1054
+ */
1055
+ instantiate = (source, options) => {
1056
+ const object = typeof source === "function" ? source() : clone(source);
1057
+ if (options?.position) setVec3(object.position, options.position);
1058
+ if (options?.rotation) setVec3(object.rotation, options.rotation);
1059
+ if (options?.scale !== void 0) if (typeof options.scale === "number") object.scale.setScalar(options.scale);
1060
+ else setVec3(object.scale, options.scale);
1061
+ return object;
1062
+ };
1063
+ /** Runs a script once and caches its `output`. The cache entry is set before
1064
+ * execution — this breaks cyclic `require`. */
1065
+ executeScript(path) {
1066
+ if (this._executedScripts.has(path)) return this._executedScripts.get(path);
1067
+ const script = this.definition.scripts?.[path];
1068
+ if (!script) throw new Error(`World runtime: unknown script "${path}"`);
1069
+ this._executedScripts.set(path, void 0);
1070
+ try {
1071
+ const output = script(this.scriptContext);
1072
+ this._executedScripts.set(path, output);
1073
+ return output;
1074
+ } catch (error) {
1075
+ console.error(`Script "${path}" failed`, error);
1076
+ return;
1077
+ }
1078
+ }
1079
+ /** World entry script (`main.ts`), once. */
1080
+ executeEntryScript() {
1081
+ const entry = this.definition.entryScript;
1082
+ if (entry) this.executeScript(entry);
1083
+ }
1084
+ /** Mounts world css scripts into the document where the canvas actually renders. */
1085
+ mountStyles() {
1086
+ const styles = this.definition.styles;
1087
+ if (!styles || styles.length === 0) return;
1088
+ for (const css of styles) this.appendStyle(css);
1089
+ }
1090
+ /** `@font-face` of all world fonts in a single tag — before styles and entry script. */
1091
+ mountFonts() {
1092
+ const paths = this.definition.fontPaths;
1093
+ if (!paths || paths.length === 0) return;
1094
+ const rules = [];
1095
+ for (const path of paths) {
1096
+ const source = this._resources[path];
1097
+ if (source instanceof FontSource) rules.push(source.toFontFaceCss());
1098
+ }
1099
+ if (rules.length === 0) return;
1100
+ this.appendStyle(rules.join("\n")).setAttribute("data-kvy-fonts", "");
1101
+ }
1102
+ dispose() {
1103
+ this._addons.dispose();
1104
+ for (const element of this._styleElements) element.remove();
1105
+ this._styleElements.length = 0;
1106
+ this._executedScripts.clear();
1107
+ this.emitter.removeAllListeners();
1108
+ }
1109
+ /** Narrow bloom facade for scripts: mode is read-only, params are live
1110
+ * (uniforms, can be animated per frame). `null` — bloom is disabled. */
1111
+ get bloomApi() {
1112
+ const bloom = this._bloom;
1113
+ if (!bloom) return null;
1114
+ return {
1115
+ mode: bloom.mode,
1116
+ get strength() {
1117
+ return bloom.strength;
1118
+ },
1119
+ set strength(value) {
1120
+ bloom.strength = value;
1121
+ },
1122
+ get radius() {
1123
+ return bloom.radius;
1124
+ },
1125
+ set radius(value) {
1126
+ bloom.radius = value;
1127
+ },
1128
+ get threshold() {
1129
+ return bloom.threshold;
1130
+ },
1131
+ set threshold(value) {
1132
+ bloom.threshold = value;
1133
+ }
1134
+ };
1135
+ }
1136
+ /** Script-scope globals: all of three-start + `world`, `require`, libs.
1137
+ * `ctx` is a getter: script components run before the 3d context is created. */
1138
+ get scriptContext() {
1139
+ if (this._scriptContext) return this._scriptContext;
1140
+ const runtime = this;
1141
+ this._scriptContext = {
1142
+ ...ThreeStartNS,
1143
+ world: {
1144
+ get ctx() {
1145
+ return runtime.ctx;
1146
+ },
1147
+ events: this.emitter,
1148
+ emitter: this.emitter,
1149
+ rendering: { get bloom() {
1150
+ return runtime.bloomApi;
1151
+ } }
1152
+ },
1153
+ require: this.require,
1154
+ instantiate: this.instantiate,
1155
+ getUrl: this.getUrl,
1156
+ THREE,
1157
+ TSL,
1158
+ van
1159
+ };
1160
+ return this._scriptContext;
1161
+ }
1162
+ appendStyle(css) {
1163
+ const doc = this.ctx.renderer.domElement.ownerDocument;
1164
+ const element = doc.createElement("style");
1165
+ element.innerHTML = css;
1166
+ doc.head.appendChild(element);
1167
+ this._styleElements.push(element);
1168
+ return element;
1169
+ }
1170
+ };
1171
+ function setVec3(target, value) {
1172
+ if (Array.isArray(value)) target.set(value[0], value[1], value[2]);
1173
+ else target.set(value.x, value.y, value.z);
1174
+ }
1175
+ function isAbsoluteUrl(url) {
1176
+ return /^(?:[a-z]+:)?\/\//i.test(url) || url.startsWith("data:");
1177
+ }
1178
+ //#endregion
1179
+ //#region packages/world-runtime/src/version.gen.ts
1180
+ var RUNTIME_VERSION = "0.1.0";
1181
+ //#endregion
1182
+ //#region packages/world-runtime/src/version.ts
1183
+ /**
1184
+ * Format version of the generated world. The codegen stamps it into the artifact,
1185
+ * the runtime checks it on load. A breaking format change = package major bump.
1186
+ * Not to be confused with {@link RUNTIME_VERSION} — the package's own version
1187
+ * (from package.json, generated into version.gen.ts by gen-runtime-version).
1188
+ */
1189
+ var WORLD_FORMAT_VERSION = 1;
1190
+ /** Throws if the artifact was built by an incompatible generator version. */
1191
+ function assertFormatVersion(version) {
1192
+ if (version === 1) return;
1193
+ throw new Error(`@kvyverse/world-runtime: world format v${version} is not supported by runtime (expected v1). Re-export the world or pin a matching runtime version.`);
1194
+ }
1195
+ //#endregion
1196
+ //#region packages/world-runtime/src/bootstrap.ts
1197
+ /**
1198
+ * World assembly order. The only place where it is fixed: the artifact supplies
1199
+ * the data and steps, the runtime owns the sequence. Mirrors the game's
1200
+ * classic-load (`/docs/scripting/game-runtime.md`).
1201
+ */
1202
+ async function bootstrapWorld(definition, options) {
1203
+ assertFormatVersion(definition.formatVersion);
1204
+ const runtime = new WorldRuntime(definition, options);
1205
+ if (options.dracoPath) configureAssetLoaders({ dracoPath: options.dracoPath });
1206
+ runtime.reportProgress("Loading addons");
1207
+ await runtime.loadAddons();
1208
+ runtime.reportProgress("Loading assets");
1209
+ const assets = await definition.loadAssets(runtime);
1210
+ runtime.setResources(assets);
1211
+ runtime.reportProgress("Loading built-in behaviours");
1212
+ await runtime.loadBuiltins();
1213
+ runtime.reportProgress("Building scene");
1214
+ const built = definition.buildScene(runtime, assets);
1215
+ runtime.reportProgress("Initializing 3D context");
1216
+ const world = await createWorld(definition, runtime);
1217
+ runtime.setContext(world.ctx);
1218
+ await runtime.applyRequiredPatches(world);
1219
+ runtime.reportProgress("Applying scene settings");
1220
+ definition.applySceneSettings?.(runtime, world.ctx.scene);
1221
+ if (built.camera) world.ctx.camera = built.camera;
1222
+ runtime.reportProgress("Running scripts");
1223
+ runtime.mountFonts();
1224
+ runtime.mountStyles();
1225
+ runtime.executeEntryScript();
1226
+ runtime.emitter.emit(WorldEvent.BeforeSceneReady);
1227
+ world.ctx.scene.add(...built.children);
1228
+ runtime.emitter.emit(WorldEvent.SceneReady);
1229
+ runtime.reportProgress("Ready");
1230
+ return world;
1231
+ }
1232
+ /** Renderer + the world's three-start app. */
1233
+ async function createWorld(definition, runtime) {
1234
+ const config = definition.renderer ?? {};
1235
+ const renderer = new THREE.WebGPURenderer({
1236
+ antialias: true,
1237
+ ...config.renderer
1238
+ });
1239
+ await renderer.init();
1240
+ const world = new World({
1241
+ renderer,
1242
+ camera: new THREE.PerspectiveCamera(),
1243
+ scene: new THREE.Scene(),
1244
+ autoInitRenderer: false
1245
+ }, runtime);
1246
+ world.addModules(createWorldModules("prod"));
1247
+ applyRendererConfig(renderer, config);
1248
+ if (config.bloom?.enabled) {
1249
+ if (config.bloom.mode === "emissive") enableSceneEmissiveMrt(world.ctx.scenePass);
1250
+ const bloom = await SceneBloom.createAsync({
1251
+ scenePass: world.ctx.scenePass,
1252
+ config: config.bloom
1253
+ });
1254
+ world.ctx.renderPipeline.outputNode = bloom.sceneWithBloom;
1255
+ world.ctx.renderPipeline.needsUpdate = true;
1256
+ runtime.setBloom(bloom);
1257
+ }
1258
+ return world;
1259
+ }
1260
+ //#endregion
1261
+ //#region packages/world-runtime/src/define-world.ts
1262
+ /**
1263
+ * Entry point of the generated artifact: world definition → public `loadWorld`.
1264
+ * The codegen prints `export const { loadWorld } = defineWorld({ ... })`.
1265
+ */
1266
+ function defineWorld(definition) {
1267
+ return { loadWorld: (options = {}) => bootstrapWorld(definition, options) };
1268
+ }
1269
+ //#endregion
1270
+ //#region packages/world-runtime/src/values.ts
1271
+ /**
1272
+ * Writes a color into an object field, creating a `Color` if the slot is empty.
1273
+ * Mirrors the `$C` branch of `applyValues` — needed by generated code for fields
1274
+ * like `scene.background` that may be `null`.
1275
+ */
1276
+ function setColor(target, key, hex) {
1277
+ const holder = target;
1278
+ const current = holder[key];
1279
+ if (current?.isColor) {
1280
+ current.set(hex);
1281
+ return;
1282
+ }
1283
+ if (current !== void 0 && current !== null) throw new Error(`setColor: "${key}" is not a Color`);
1284
+ holder[key] = new THREE.Color(hex);
1285
+ }
1286
+ //#endregion
1287
+ //#region packages/world-runtime/src/resources/default-resources.ts
1288
+ /**
1289
+ * Built-in engine resources with fixed uuids: world values reference them via
1290
+ * a regular ref (`{$A:"6"}`), so the identifiers must never change.
1291
+ */
1292
+ var DefaultResourceId = {
1293
+ EmptyGeometry: "0",
1294
+ BoxGeometry: "1",
1295
+ SphereGeometry: "2",
1296
+ PlaneGeometry: "3",
1297
+ CylinderGeometry: "4",
1298
+ PyramidGeometry: "5",
1299
+ Material: "6",
1300
+ SpriteMaterial: "7",
1301
+ Texture: "8"
1302
+ };
1303
+ var FACTORIES = {
1304
+ [DefaultResourceId.EmptyGeometry]: () => {
1305
+ const geometry = new THREE.BufferGeometry();
1306
+ geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(0), 3));
1307
+ geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(0), 1));
1308
+ return geometry;
1309
+ },
1310
+ [DefaultResourceId.BoxGeometry]: () => new THREE.BoxGeometry(),
1311
+ [DefaultResourceId.SphereGeometry]: () => new THREE.SphereGeometry(),
1312
+ [DefaultResourceId.PlaneGeometry]: () => new THREE.PlaneGeometry(),
1313
+ [DefaultResourceId.CylinderGeometry]: () => new THREE.CylinderGeometry(),
1314
+ [DefaultResourceId.PyramidGeometry]: () => new THREE.ConeGeometry(1, 1, 4, 1),
1315
+ [DefaultResourceId.Material]: () => new THREE.MeshStandardNodeMaterial(),
1316
+ [DefaultResourceId.SpriteMaterial]: () => new THREE.SpriteNodeMaterial(),
1317
+ [DefaultResourceId.Texture]: () => {
1318
+ const texture = new THREE.DataTexture(new Uint8Array([
1319
+ 255,
1320
+ 255,
1321
+ 255,
1322
+ 255
1323
+ ]), 1, 1);
1324
+ texture.needsUpdate = true;
1325
+ return texture;
1326
+ }
1327
+ };
1328
+ var NAMES = {
1329
+ [DefaultResourceId.EmptyGeometry]: "Empty",
1330
+ [DefaultResourceId.BoxGeometry]: "Box",
1331
+ [DefaultResourceId.SphereGeometry]: "Sphere",
1332
+ [DefaultResourceId.PlaneGeometry]: "Plane",
1333
+ [DefaultResourceId.CylinderGeometry]: "Cylinder",
1334
+ [DefaultResourceId.PyramidGeometry]: "Pyramid",
1335
+ [DefaultResourceId.Material]: "Default Material",
1336
+ [DefaultResourceId.SpriteMaterial]: "Sprite Material",
1337
+ [DefaultResourceId.Texture]: "Default Texture"
1338
+ };
1339
+ var CACHE = /* @__PURE__ */ new Map();
1340
+ /**
1341
+ * Resource by its uuid. Lazy and cached: an exported world creates only
1342
+ * what it actually references.
1343
+ */
1344
+ function getDefaultResource(id) {
1345
+ const cached = CACHE.get(id);
1346
+ if (cached) return cached;
1347
+ const resource = FACTORIES[id]();
1348
+ resource.name = NAMES[id];
1349
+ resource.uuid = id;
1350
+ CACHE.set(id, resource);
1351
+ return resource;
1352
+ }
1353
+ var DEFAULT_RESOURCE_IDS = Object.values(DefaultResourceId);
1354
+ function isDefaultResourceId(uuid) {
1355
+ return DEFAULT_RESOURCE_IDS.includes(uuid);
1356
+ }
1357
+ //#endregion
1358
+ //#region packages/world-runtime/src/resources/texture.ts
1359
+ /**
1360
+ * World texture defaults: clamp wrapping switches to repeat, color space
1361
+ * is sRGB. Identical in the editor, the game and the exported world.
1362
+ */
1363
+ function applyTextureDefaults(texture, name) {
1364
+ if (texture.wrapS === THREE.ClampToEdgeWrapping) texture.wrapS = THREE.RepeatWrapping;
1365
+ if (texture.wrapT === THREE.ClampToEdgeWrapping) texture.wrapT = THREE.RepeatWrapping;
1366
+ texture.colorSpace = THREE.SRGBColorSpace;
1367
+ if (name !== void 0) texture.name = name;
1368
+ }
1369
+ /** Texture from a URL with defaults applied. */
1370
+ async function loadTexture(url, name) {
1371
+ const texture = await AssetLoaders.texture.get().loadAsync(url);
1372
+ applyTextureDefaults(texture, name);
1373
+ return texture;
1374
+ }
1375
+ //#endregion
1376
+ //#region packages/world-runtime/src/resources/model.ts
1377
+ /** Parses model bytes into a hierarchy. `data`: ArrayBuffer, for obj — text. */
1378
+ async function parseModel(data, format, name) {
1379
+ let root;
1380
+ switch (format) {
1381
+ case "gltf":
1382
+ case "glb": {
1383
+ const gltf = await (await AssetLoaders.gltf.get()).parseAsync(data, "");
1384
+ root = gltf.scene;
1385
+ root.animations = gltf.animations;
1386
+ break;
1387
+ }
1388
+ case "fbx":
1389
+ root = (await AssetLoaders.fbx.get()).parse(data, "");
1390
+ break;
1391
+ case "obj":
1392
+ root = (await AssetLoaders.obj.get()).parse(data);
1393
+ break;
1394
+ }
1395
+ if (name !== void 0) root.name = name;
1396
+ return root;
1397
+ }
1398
+ /** Loads a model from a URL and parses it. */
1399
+ async function loadModel(url, format, name) {
1400
+ const response = await fetch(url);
1401
+ if (!response.ok) throw new Error(`Failed to load model "${url}": ${response.status}`);
1402
+ return parseModel(format === "obj" ? await response.text() : await response.arrayBuffer(), format, name);
1403
+ }
1404
+ /**
1405
+ * Collects model sub-resources in a single traversal. The only implementation:
1406
+ * used by `Model3DAsset` in the editor and by the export generator — otherwise
1407
+ * refs to the model's materials and geometries drift apart by index.
1408
+ */
1409
+ function collectModelSubResources(root) {
1410
+ const geometries = /* @__PURE__ */ new Set();
1411
+ const materials = /* @__PURE__ */ new Set();
1412
+ const textures = /* @__PURE__ */ new Set();
1413
+ const animations = /* @__PURE__ */ new Set();
1414
+ const skeletons = /* @__PURE__ */ new Set();
1415
+ root.traverse((node) => {
1416
+ for (const animation of node.animations) animations.add(animation);
1417
+ const mesh = node;
1418
+ if (mesh.isMesh && mesh.geometry) {
1419
+ geometries.add(mesh.geometry);
1420
+ if (mesh.geometry.name.length === 0) mesh.geometry.name = node.name;
1421
+ const material = mesh.material;
1422
+ if (Array.isArray(material)) for (const item of material) materials.add(item);
1423
+ else if (material) materials.add(material);
1424
+ }
1425
+ const skinned = node;
1426
+ if (skinned.isSkinnedMesh && skinned.skeleton) skeletons.add(skinned.skeleton);
1427
+ });
1428
+ for (const material of materials) {
1429
+ const record = material;
1430
+ for (const key in record) {
1431
+ if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
1432
+ const value = record[key];
1433
+ if (value?.isTexture) textures.add(value);
1434
+ }
1435
+ }
1436
+ return {
1437
+ geometries: Array.from(geometries),
1438
+ materials: Array.from(materials),
1439
+ textures: Array.from(textures),
1440
+ animations: Array.from(animations),
1441
+ skeletons: Array.from(skeletons)
1442
+ };
1443
+ }
1444
+ //#endregion
1445
+ //#region packages/world-runtime/src/resources/hdr.ts
1446
+ /** Parses hdr/exr into a `DataTexture` with environment defaults (equirect, no mipmaps). */
1447
+ async function parseHdr(data, format, name) {
1448
+ let texture;
1449
+ if (format === "hdr") {
1450
+ const result = await AssetLoaders.rgbe.get().then((loader) => loader.parse(data));
1451
+ texture = new THREE.DataTexture(result.data, result.width, result.height, THREE.RGBAFormat, THREE.HalfFloatType);
1452
+ } else {
1453
+ const result = await AssetLoaders.exr.get().then((loader) => loader.parse(data));
1454
+ texture = new THREE.DataTexture(result.data, result.width, result.height, result.format, result.type);
1455
+ texture.colorSpace = result.colorSpace;
1456
+ }
1457
+ texture.minFilter = THREE.LinearFilter;
1458
+ texture.magFilter = THREE.LinearFilter;
1459
+ texture.flipY = true;
1460
+ texture.generateMipmaps = false;
1461
+ texture.mapping = THREE.EquirectangularReflectionMapping;
1462
+ texture.needsUpdate = true;
1463
+ if (name !== void 0) texture.name = name;
1464
+ return texture;
1465
+ }
1466
+ /** Loads hdr/exr from a URL and parses it. */
1467
+ async function loadHdr(url, format, name) {
1468
+ const response = await fetch(url);
1469
+ if (!response.ok) throw new Error(`Failed to load hdr "${url}": ${response.status}`);
1470
+ return parseHdr(await response.arrayBuffer(), format, name);
1471
+ }
1472
+ //#endregion
1473
+ //#region packages/world-runtime/src/resources/geometry.ts
1474
+ /** Geometry from our json format (`BufferGeometry.toJSON`). */
1475
+ function parseGeometryJson(json, name) {
1476
+ const geometry = AssetLoaders.bufferGeometry.get().parse(json);
1477
+ if (name !== void 0) geometry.name = name;
1478
+ return geometry;
1479
+ }
1480
+ /** Loads geometry from a URL and parses it. */
1481
+ async function loadGeometry(url, name) {
1482
+ const response = await fetch(url);
1483
+ if (!response.ok) throw new Error(`Failed to load geometry "${url}": ${response.status}`);
1484
+ return parseGeometryJson(await response.json(), name);
1485
+ }
1486
+ //#endregion
1487
+ //#region packages/world-runtime/src/resources/audio.ts
1488
+ /**
1489
+ * Audio asset source: a `Howl` with identity — this is what scripts get
1490
+ * from `require("/Sounds/...")`.
1491
+ */
1492
+ var AudioSource = class extends Howl {
1493
+ _url;
1494
+ name = "";
1495
+ uuid = crypto.randomUUID();
1496
+ constructor(_url, ...args) {
1497
+ super(...args);
1498
+ this._url = _url;
1499
+ }
1500
+ /** Revoke blob URLs (the editor keeps the file in memory); external URLs stay. */
1501
+ dispose() {
1502
+ this.unload();
1503
+ if (this._url.startsWith("blob:")) URL.revokeObjectURL(this._url);
1504
+ }
1505
+ };
1506
+ /** Loaded audio from a URL. `format` — extension hint when it's absent from the URL. */
1507
+ function createAudio(url, format, name) {
1508
+ const source = new AudioSource(url, {
1509
+ src: url,
1510
+ format
1511
+ });
1512
+ if (name !== void 0) source.name = name;
1513
+ return new Promise((resolve, reject) => {
1514
+ source.once("load", () => resolve(source));
1515
+ source.once("loaderror", (_id, error) => reject(/* @__PURE__ */ new Error(`Failed to load audio "${url}": ${String(error)}`)));
1516
+ source.load();
1517
+ });
1518
+ }
1519
+ //#endregion
1520
+ //#region packages/world-runtime/src/resources/video.ts
1521
+ /**
1522
+ * Video asset source: a `<video>` with identity — this is what scripts get
1523
+ * from `require("/Videos/...")` (and `THREE.VideoTexture` from `element`).
1524
+ */
1525
+ var VideoSource = class {
1526
+ element;
1527
+ _url;
1528
+ name = "";
1529
+ uuid = crypto.randomUUID();
1530
+ constructor(element, _url) {
1531
+ this.element = element;
1532
+ this._url = _url;
1533
+ }
1534
+ dispose() {
1535
+ const video = this.element;
1536
+ try {
1537
+ video.pause();
1538
+ video.removeAttribute("src");
1539
+ video.load();
1540
+ } catch (error) {
1541
+ console.warn("Failed to stop video", error);
1542
+ }
1543
+ if (this._url?.startsWith("blob:")) URL.revokeObjectURL(this._url);
1544
+ this._url = null;
1545
+ }
1546
+ };
1547
+ /** Playback-ready `<video>` from a URL (waits for `loadeddata`). */
1548
+ async function createVideo(url, name) {
1549
+ const video = document.createElement("video");
1550
+ video.src = url;
1551
+ video.crossOrigin = "anonymous";
1552
+ video.loop = true;
1553
+ video.playsInline = true;
1554
+ video.muted = true;
1555
+ video.load();
1556
+ await new Promise((resolve, reject) => {
1557
+ const cleanup = () => {
1558
+ video.removeEventListener("loadeddata", onReady);
1559
+ video.removeEventListener("error", onError);
1560
+ };
1561
+ const onReady = () => {
1562
+ cleanup();
1563
+ resolve();
1564
+ };
1565
+ const onError = (event) => {
1566
+ cleanup();
1567
+ reject(/* @__PURE__ */ new Error(`Failed to load video "${url}": ${event.message}`));
1568
+ };
1569
+ video.addEventListener("loadeddata", onReady);
1570
+ video.addEventListener("error", onError);
1571
+ });
1572
+ const source = new VideoSource(video, url);
1573
+ if (name !== void 0) source.name = name;
1574
+ return source;
1575
+ }
1576
+ //#endregion
1577
+ //#region packages/world-runtime/src/rendering/tsl-node.ts
1578
+ /**
1579
+ * World TSL node: the built graph node + its uniform nodes. This is what
1580
+ * `require("/Shaders/....tsl")` returns, so the method set matches the editor's
1581
+ * `TslNodeSource` (`setUniformValue` / `getUniformValue` / `uniformValues`).
1582
+ */
1583
+ var TslNode = class {
1584
+ uniforms;
1585
+ node;
1586
+ _uniformValuesView;
1587
+ constructor(uniforms, build, name = "TslNode") {
1588
+ this.uniforms = uniforms;
1589
+ let node;
1590
+ try {
1591
+ node = build(TSL, uniforms);
1592
+ } catch (error) {
1593
+ console.warn(`TSL node "${name}" build failed`, error);
1594
+ }
1595
+ this.node = node ?? TSL.float(1);
1596
+ }
1597
+ /** Flat view of uniform values: reads and writes go straight to `node.value`. */
1598
+ get uniformValues() {
1599
+ return this._uniformValuesView ??= new Proxy({}, {
1600
+ get: (_target, key) => typeof key === "string" ? this.getUniformValue(key) : void 0,
1601
+ set: (_target, key, value) => {
1602
+ if (typeof key === "string") this.setUniformValue(key, value);
1603
+ return true;
1604
+ }
1605
+ });
1606
+ }
1607
+ setUniformValue(key, value) {
1608
+ const node = this.uniforms[key];
1609
+ if (node) node.value = value;
1610
+ }
1611
+ getUniformValue(key) {
1612
+ return this.uniforms[key]?.value;
1613
+ }
1614
+ };
1615
+ /** Factory for the generated code: builds the node and wraps it. */
1616
+ function createTslNode(uniforms, build, name) {
1617
+ return new TslNode(uniforms, build, name);
1618
+ }
1619
+ //#endregion
1620
+ //#region packages/world-runtime/src/rendering/tsl-uniforms.ts
1621
+ var UNIFORM_TYPES = [
1622
+ "float",
1623
+ "int",
1624
+ "vec2",
1625
+ "vec3",
1626
+ "vec4",
1627
+ "color",
1628
+ "bool",
1629
+ "texture"
1630
+ ];
1631
+ function isUniformType(x) {
1632
+ return UNIFORM_TYPES.includes(x);
1633
+ }
1634
+ /** Uniform node type. Texture is a separate class; the rest carry an explicit `nodeType`. */
1635
+ function uniformType(node) {
1636
+ if (node.isTextureNode) return "texture";
1637
+ const t = node.nodeType;
1638
+ return t && isUniformType(t) ? t : "float";
1639
+ }
1640
+ var makeUniform = TSL.uniform;
1641
+ /** Creates a uniform node with the type's baked-in default. The type is set
1642
+ * explicitly — otherwise `int`/`float` and `color`/`vec3` are indistinguishable
1643
+ * from the value. */
1644
+ function createUniformNode(type) {
1645
+ switch (type) {
1646
+ case "float": return makeUniform(1, "float");
1647
+ case "int": return makeUniform(1, "int");
1648
+ case "vec2": return makeUniform(new THREE.Vector2(1, 1), "vec2");
1649
+ case "vec3": return makeUniform(new THREE.Vector3(1, 1, 1), "vec3");
1650
+ case "vec4": return makeUniform(new THREE.Vector4(1, 1, 1, 1), "vec4");
1651
+ case "color": return makeUniform(new THREE.Color(1, 1, 1), "color");
1652
+ case "bool": return makeUniform(false, "bool");
1653
+ case "texture": return TSL.texture(getDefaultResource(DefaultResourceId.Texture));
1654
+ }
1655
+ }
1656
+ /** Default value per type — for `Property.setDefault` (value==default is not
1657
+ * serialized, restored on load). Factories, since Color/Vec are mutable. */
1658
+ var UNIFORM_DEFAULTS = {
1659
+ float: () => 1,
1660
+ int: () => 1,
1661
+ vec2: () => ({
1662
+ x: 1,
1663
+ y: 1
1664
+ }),
1665
+ vec3: () => ({
1666
+ x: 1,
1667
+ y: 1,
1668
+ z: 1
1669
+ }),
1670
+ vec4: () => ({
1671
+ x: 1,
1672
+ y: 1,
1673
+ z: 1,
1674
+ w: 1
1675
+ }),
1676
+ color: () => new THREE.Color(1, 1, 1),
1677
+ bool: () => false,
1678
+ texture: () => getDefaultResource(DefaultResourceId.Texture)
1679
+ };
1680
+ //#endregion
1681
+ //#region packages/world-runtime/src/settings/externals.ts
1682
+ var EXTERNAL_TYPES = ["umd", "module"];
1683
+ //#endregion
1684
+ //#region packages/world-runtime/src/rendering/renderer-options.ts
1685
+ /**
1686
+ * String names for renderer options set as words in the config and mapped to
1687
+ * THREE constants at runtime (game). Single source for the JSON schema
1688
+ * (`renderer.schema`) and the runtime mapping (`game`).
1689
+ */
1690
+ var SHADOW_MAP_TYPES = [
1691
+ "Basic",
1692
+ "PCF",
1693
+ "PCFSoft",
1694
+ "VSM"
1695
+ ];
1696
+ var BLOOM_MODES = ["threshold", "emissive"];
1697
+ var TONE_MAPPING_NAMES = [
1698
+ "None",
1699
+ "Linear",
1700
+ "Reinhard",
1701
+ "Cineon",
1702
+ "ACESFilmic",
1703
+ "AgX",
1704
+ "Neutral"
1705
+ ];
1706
+ //#endregion
1707
+ export { AddonsRuntime, AssetLoaders, AudioSource, BLOOM_MODES, BUILTIN_BEHAVIOURS, BUILTIN_BEHAVIOUR_LIST, DEFAULT_RESOURCE_IDS, DefaultResourceId, EXTERNAL_TYPES, EnvModule, FontSource, HtmlHudModule, InputSystemModule, LIBS, LIB_DESCRIPTORS, LIB_IDS, LOADING_MANAGER, LibId, MODULE_IDS, ModuleId, PHYSICS_ENGINES, RUNTIME_VERSION, SHADOW_MAP_TYPES, SceneBloom, SoundsModule, TONE_MAPPING_NAMES, TslNode, UNIFORM_DEFAULTS, UNIFORM_TYPES, UtilsModule, VideoSource, WORLD_FORMAT_VERSION, World, WorldEvent, applyRapierPhysicsPatch, applyRendererConfig, applyTextureDefaults, collectModelSubResources, configureAssetLoaders, createAudio, createFont, createTslNode, createUniformNode, createVideo, createWorldModules, defineWorld, enableSceneEmissiveMrt, fontFormatFromMime, getBuiltinBehaviour, getDefaultResource, getHalfheightRadiusScale, getRadiusScale, isDefaultResourceId, isMobileDevice, isUniformType, lazyRapier, libByRequireKey, loadGeometry, loadHdr, loadModel, loadTexture, normalizePhysics, parseGeometryJson, parseHdr, parseModel, setColor, uniformType };