@kvyverse/world-runtime 0.1.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +78 -60
  2. package/dist/{BallCollider-8ZrxReEd.js → BallCollider-3L5vOrcL.js} +2 -2
  3. package/dist/{CapsuleCollider-bynyf6Mc.js → CapsuleCollider-CBTsR-Ys.js} +2 -2
  4. package/dist/ChatModule-2tiWS_Yr.js +411 -0
  5. package/dist/{Collider-4_rCTHVA.js → Collider-CPUCG3RA.js} +2 -2
  6. package/dist/{ConeCollider-DtRZmgK8.js → ConeCollider-C3XU3Y9X.js} +2 -2
  7. package/dist/{ConvexMeshCollider-BSmDM4o3.js → ConvexMeshCollider-DzNV4HTO.js} +1 -1
  8. package/dist/{CuboidCollider-CJvq7--s.js → CuboidCollider-Dp1DZoFM.js} +1 -1
  9. package/dist/{CylinderCollider-CQPSh4tu.js → CylinderCollider-C0h_nhB5.js} +2 -2
  10. package/dist/{FpsCameraController-BE2EirRj.js → FpsCameraController-2GNWJM5D.js} +1 -1
  11. package/dist/RapierDebugRenderer-BQy_m-3C.js +73 -0
  12. package/dist/{RapierFpsPlayerController-VsNfQl6v.js → RapierFpsPlayerController-DthD8dw9.js} +5 -22
  13. package/dist/{RapierPhysics-C_3b7QOn.js → RapierPhysics-zLwVZpwF.js} +33 -15
  14. package/dist/{Rigidbody-b7QNF4ev.js → Rigidbody-DLhN8ddv.js} +12 -1
  15. package/dist/World.d.ts +2 -2
  16. package/dist/WorldRuntime.d.ts +13 -4
  17. package/dist/addons/AddonsRuntime.d.ts +35 -0
  18. package/dist/addons/lib-registry.d.ts +34 -0
  19. package/dist/addons/types.d.ts +28 -0
  20. package/dist/behaviours/physics/BallCollider.d.ts +1 -1
  21. package/dist/behaviours/physics/CapsuleCollider.d.ts +1 -1
  22. package/dist/behaviours/physics/ConeCollider.d.ts +1 -1
  23. package/dist/behaviours/physics/ConvexMeshCollider.d.ts +1 -1
  24. package/dist/behaviours/physics/CuboidCollider.d.ts +1 -1
  25. package/dist/behaviours/physics/CylinderCollider.d.ts +1 -1
  26. package/dist/behaviours/physics/HalfspaceCollider.d.ts +1 -1
  27. package/dist/behaviours/physics/_required-patch.d.ts +10 -6
  28. package/dist/chat.module-descriptor-BTsep-Kq.js +107 -0
  29. package/dist/{get-scale-from-obj-QKv3Fig5.js → get-scale-from-obj-B7JlVPni.js} +1 -1
  30. package/dist/index.d.ts +21 -13
  31. package/dist/index.js +441 -155
  32. package/dist/modules/InputSystemModule.d.ts +14 -32
  33. package/dist/modules/chat/ChatDom.d.ts +100 -0
  34. package/dist/modules/chat/ChatModule.d.ts +36 -0
  35. package/dist/modules/chat/chat.module-descriptor.d.ts +43 -0
  36. package/dist/modules/contracts/addons.module-api.d.ts +15 -0
  37. package/dist/modules/contracts/chat.module-api.d.ts +52 -0
  38. package/dist/modules/contracts/controls.module-api.d.ts +58 -0
  39. package/dist/modules/contracts/html.module-api.d.ts +9 -1
  40. package/dist/modules/contracts/input.module-api.d.ts +11 -11
  41. package/dist/modules/contracts/module-events.d.ts +17 -0
  42. package/dist/modules/contracts/rapier.module-api.d.ts +13 -1
  43. package/dist/modules/controls/ControlsModule.d.ts +45 -0
  44. package/dist/modules/controls/KeyboardControlsDriver.d.ts +12 -0
  45. package/dist/modules/index.d.ts +30 -3
  46. package/dist/modules/optional-module-descriptor.d.ts +36 -0
  47. package/dist/modules/optional-modules.d.ts +11 -0
  48. package/dist/modules/physics/RapierDebugRenderer.d.ts +19 -0
  49. package/dist/{behaviours/physics/rapier-addon-core → modules/physics}/RapierPhysics.d.ts +13 -3
  50. package/dist/modules/physics/physics.module-descriptor.d.ts +16 -0
  51. package/dist/modules/registry.d.ts +7 -3
  52. package/dist/rendering/SceneBloom.d.ts +1 -1
  53. package/dist/rendering/renderer-apply.d.ts +1 -1
  54. package/dist/{settings → rendering}/types.d.ts +1 -12
  55. package/dist/types.d.ts +10 -3
  56. package/dist/version.gen.d.ts +1 -1
  57. package/package.json +9 -1
  58. package/dist/RapierDebugRenderer-sURZDLP4.js +0 -55
  59. package/dist/behaviours/physics/rapier-addon-core/RapierDebugRenderer.d.ts +0 -17
  60. package/dist/modules/contracts/rapierDebug.module-api.d.ts +0 -5
  61. package/dist/rolldown-runtime-D7D4PA-g.js +0 -13
  62. package/dist/settings/AddonsRuntime.d.ts +0 -19
  63. package/dist/settings/lib-loaders.d.ts +0 -7
  64. package/dist/settings/lib-registry.d.ts +0 -24
  65. package/dist/settings/module-registry.d.ts +0 -25
  66. /package/dist/{settings → addons}/externals.d.ts +0 -0
  67. /package/dist/behaviours/physics/{rapier-addon-core/Collider.d.ts → Collider.d.ts} +0 -0
  68. /package/dist/behaviours/physics/{rapier-addon-core/get-scale-from-obj.d.ts → get-scale-from-obj.d.ts} +0 -0
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
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";
1
+ import { a as OPTIONAL_MODULE_IDS, i as normalizeChat, n as CHAT_INPUT_MODES, o as OptionalModuleId, r as chatModuleDescriptor } from "./chat.module-descriptor-BTsep-Kq.js";
2
+ import { n as getRadiusScale, t as getHalfheightRadiusScale } from "./get-scale-from-obj-B7JlVPni.js";
3
3
  import * as THREE from "three/webgpu";
4
4
  import { BufferGeometryLoader, LoadingManager, TextureLoader } from "three/webgpu";
5
5
  import * as ThreeStartNS from "three-start";
@@ -7,10 +7,10 @@ import { ContextModule, ThreeContextEvents, ThreeStart } from "three-start";
7
7
  import * as TSL from "three/tsl";
8
8
  import { emissive, mrt, output } from "three/tsl";
9
9
  import van from "vanjs-core";
10
- import EventEmitter from "eventemitter3";
11
10
  import { Howl, Howler } from "howler";
12
11
  import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
13
12
  import { clone } from "three/examples/jsm/utils/SkeletonUtils.js";
13
+ import EventEmitter from "eventemitter3";
14
14
  //#region packages/world-runtime/src/world-events.ts
15
15
  /**
16
16
  * World lifecycle events, emitted on `game.emitter` (`world.events` in scripts)
@@ -40,8 +40,8 @@ var World = class extends ThreeStart {
40
40
  super(options);
41
41
  this._runtime = _runtime;
42
42
  }
43
- /** Asset URL by project path — same resolving as `getUrl` in scripts. */
44
- getUrl = (path) => this._runtime.getUrl(path);
43
+ /** Asset URL by project path or resource — same resolving as `getUrl` in scripts. */
44
+ getUrl = (source) => this._runtime.getUrl(source);
45
45
  /** World lifecycle event bus — the same one as `world.events` in scripts. */
46
46
  get events() {
47
47
  return this._runtime.emitter;
@@ -268,6 +268,241 @@ function finiteOr(value, fallback) {
268
268
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
269
269
  }
270
270
  //#endregion
271
+ //#region packages/world-runtime/src/modules/controls/KeyboardControlsDriver.ts
272
+ /** A diagonal must not be faster than a straight line. */
273
+ var DIAGONAL = Math.SQRT1_2;
274
+ /**
275
+ * Keyboard driver for `controls`: WASD/arrows, shift to run, Space to jump.
276
+ * Reads key state from the `input` module and binds no listeners of its own.
277
+ */
278
+ var KeyboardControlsDriver = class {
279
+ _input;
280
+ kind = "keyboard";
281
+ constructor(_input) {
282
+ this._input = _input;
283
+ }
284
+ read(state) {
285
+ const key = this._input.isKeyDown;
286
+ let x = 0;
287
+ let y = 0;
288
+ if (key("KeyW") || key("ArrowUp")) y += 1;
289
+ if (key("KeyS") || key("ArrowDown")) y -= 1;
290
+ if (key("KeyD") || key("ArrowRight")) x += 1;
291
+ if (key("KeyA") || key("ArrowLeft")) x -= 1;
292
+ if (x !== 0 && y !== 0) {
293
+ x *= DIAGONAL;
294
+ y *= DIAGONAL;
295
+ }
296
+ state.move.set(x, y);
297
+ state.force = x !== 0 || y !== 0 ? 1 : 0;
298
+ state.run = this._input.shiftKey;
299
+ state.jump = key("Space");
300
+ }
301
+ };
302
+ //#endregion
303
+ //#region packages/world-runtime/src/modules/controls/ControlsModule.ts
304
+ /**
305
+ * The player's movement intent — what they want to do, whatever the hardware is.
306
+ * Input sources register as drivers (`addDriver`); the module polls them once a
307
+ * frame and decides which one is driving. Details — `./README.md`.
308
+ */
309
+ var ControlsModule = class extends ContextModule {
310
+ /** x — strafe, y — forward. Reused vector: copy it before storing. */
311
+ move = new THREE.Vector2();
312
+ _drivers = [];
313
+ _state = {
314
+ move: new THREE.Vector2(),
315
+ force: 0,
316
+ run: false,
317
+ jump: false
318
+ };
319
+ _keyboard = null;
320
+ _active = null;
321
+ _source = "keyboard";
322
+ _force = 0;
323
+ _run = false;
324
+ _jump = false;
325
+ onAwake() {
326
+ this._keyboard = new KeyboardControlsDriver(this.modules.input);
327
+ this.addDriver(this._keyboard);
328
+ }
329
+ /** Speed multiplier: 1 is a normal walk, higher means a stronger stick tilt. */
330
+ get force() {
331
+ return this._force;
332
+ }
333
+ /** Run modifier: shift, a stick pushed past its ring, a UI button. */
334
+ get run() {
335
+ return this._run;
336
+ }
337
+ /** Kind of the device driving right now. */
338
+ get source() {
339
+ return this._source;
340
+ }
341
+ get drivers() {
342
+ return this._drivers;
343
+ }
344
+ /** The built-in keyboard driver — detach it with `removeDriver`. */
345
+ get keyboard() {
346
+ if (!this._keyboard) throw new Error("controls: keyboard driver is not ready yet");
347
+ return this._keyboard;
348
+ }
349
+ addDriver(driver) {
350
+ if (!this._drivers.includes(driver)) this._drivers.push(driver);
351
+ return () => this.removeDriver(driver);
352
+ }
353
+ removeDriver(driver) {
354
+ const index = this._drivers.indexOf(driver);
355
+ if (index < 0) return;
356
+ this._drivers.splice(index, 1);
357
+ if (this._active === driver) this._active = null;
358
+ }
359
+ jump() {
360
+ this.emit("jump");
361
+ }
362
+ onUpdate() {
363
+ const driver = this._readActiveDriver();
364
+ this._apply(driver);
365
+ }
366
+ /**
367
+ * The active driver keeps the channel while it produces input — otherwise the
368
+ * keyboard and a stick would steal control from each other every frame. Once it
369
+ * goes quiet, the first driver with input takes over. The state is left filled
370
+ * by the winner, so nobody is read twice.
371
+ */
372
+ _readActiveDriver() {
373
+ if (this._active && this._readDriver(this._active)) return this._active;
374
+ for (const driver of this._drivers) {
375
+ if (driver === this._active) continue;
376
+ if (this._readDriver(driver)) return driver;
377
+ }
378
+ this._resetState();
379
+ return null;
380
+ }
381
+ _readDriver(driver) {
382
+ this._resetState();
383
+ driver.read(this._state);
384
+ const { move, force, jump } = this._state;
385
+ return move.x !== 0 || move.y !== 0 || force > 0 || jump;
386
+ }
387
+ _resetState() {
388
+ const state = this._state;
389
+ state.move.set(0, 0);
390
+ state.force = 0;
391
+ state.run = false;
392
+ state.jump = false;
393
+ }
394
+ _apply(driver) {
395
+ const state = this._state;
396
+ this._active = driver ?? this._active;
397
+ const source = this._active?.kind ?? this._source;
398
+ if (source !== this._source) {
399
+ this._source = source;
400
+ this.emit("sourcechanged", source);
401
+ }
402
+ if (!this.move.equals(state.move)) {
403
+ this.move.copy(state.move);
404
+ this.emit("movechanged", this.move);
405
+ }
406
+ if (this._force !== state.force) {
407
+ this._force = state.force;
408
+ this.emit("forcechanged", state.force);
409
+ }
410
+ this._run = state.run;
411
+ if (state.jump && !this._jump) this.jump();
412
+ this._jump = state.jump;
413
+ }
414
+ };
415
+ //#endregion
416
+ //#region packages/world-runtime/src/behaviours/physics/_required-patch.ts
417
+ var lazyRapierPhysicsModule = lazy(async () => (await import("./RapierPhysics-zLwVZpwF.js")).default);
418
+ /** The only place the physics module is created — called by the registry
419
+ * descriptor. The module itself takes rapier from `ctx.modules.addons` on awake. */
420
+ async function createRapierPhysicsModule(config) {
421
+ return new (await (lazyRapierPhysicsModule.get()))({ debug: config.debug });
422
+ }
423
+ /**
424
+ * Registers physics on demand: a built-in physics behaviour declares it via
425
+ * `meta.requiredPatch`, so a world with a collider on the scene gets
426
+ * `ctx.modules.rapier` even without a line in `settings.addons.json`.
427
+ * Idempotent, and strictly before `start()` — three-start forbids `addModules`
428
+ * afterwards.
429
+ */
430
+ async function applyRapierPhysicsPatch(starter) {
431
+ if (starter.ctx.modules.rapier) return;
432
+ const descriptor = physicsModuleDescriptor;
433
+ const module = await descriptor.create(descriptor.defaultConfig());
434
+ starter.addModules({ rapier: module });
435
+ }
436
+ //#endregion
437
+ //#region packages/world-runtime/src/modules/physics/physics.module-descriptor.ts
438
+ var PHYSICS_ENGINES = ["rapier"];
439
+ /** Physics defaults: the rapier engine, debug off. */
440
+ function normalizePhysics(value) {
441
+ if (!value) return null;
442
+ if (typeof value === "string") return {
443
+ engine: value,
444
+ debug: false
445
+ };
446
+ return {
447
+ engine: value.engine,
448
+ debug: value.debug ?? false
449
+ };
450
+ }
451
+ var physicsModuleDescriptor = {
452
+ id: OptionalModuleId.Physics,
453
+ normalize: (raw) => normalizePhysics(raw),
454
+ defaultConfig: () => ({
455
+ engine: "rapier",
456
+ debug: false
457
+ }),
458
+ create: (config) => createRapierPhysicsModule(config),
459
+ schema: {
460
+ description: "Physics engine module. false | \"rapier\" | { engine, debug }.",
461
+ oneOf: [
462
+ {
463
+ type: "boolean",
464
+ enum: [false]
465
+ },
466
+ {
467
+ type: "string",
468
+ enum: [...PHYSICS_ENGINES]
469
+ },
470
+ {
471
+ type: "object",
472
+ additionalProperties: false,
473
+ required: ["engine"],
474
+ properties: {
475
+ engine: {
476
+ type: "string",
477
+ enum: [...PHYSICS_ENGINES]
478
+ },
479
+ debug: {
480
+ type: "boolean",
481
+ description: "Draw collider debug lines from the start (the renderer is loaded on demand)."
482
+ }
483
+ }
484
+ }
485
+ ]
486
+ }
487
+ };
488
+ //#endregion
489
+ //#region packages/world-runtime/src/modules/optional-modules.ts
490
+ /**
491
+ * Table of contents for the world's pluggable modules: what can be enabled from
492
+ * `settings.addons.json → modules`. The descriptors themselves live next to
493
+ * their module code; this is only the list.
494
+ *
495
+ * Add a descriptor here and the module shows up both in the runtime and in the
496
+ * config's JSON schema (the platform builds it from `descriptor.schema`).
497
+ */
498
+ var OPTIONAL_MODULES = [physicsModuleDescriptor, chatModuleDescriptor];
499
+ var DESCRIPTOR_BY_ID = new Map(OPTIONAL_MODULES.map((m) => [m.id, m]));
500
+ function getOptionalModuleDescriptor(id) {
501
+ const descriptor = DESCRIPTOR_BY_ID.get(id);
502
+ if (!descriptor) throw new Error(`Unknown world module "${id}"`);
503
+ return descriptor;
504
+ }
505
+ //#endregion
271
506
  //#region packages/world-runtime/src/modules/is-mobile-device.ts
272
507
  function isMobileDevice() {
273
508
  if (typeof navigator === "undefined" || typeof window === "undefined") return false;
@@ -336,46 +571,6 @@ var HtmlHudModule = class extends ContextModule {
336
571
  };
337
572
  //#endregion
338
573
  //#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
574
  var InputSystemModule = class extends ContextModule {
380
575
  _dom;
381
576
  _keys = /* @__PURE__ */ new Set();
@@ -421,12 +616,30 @@ var InputSystemModule = class extends ContextModule {
421
616
  isKeyDown = (key) => {
422
617
  return this._keys.has(key);
423
618
  };
424
- player = new PlayerInput();
619
+ _captureKeyboard = true;
620
+ /**
621
+ * The game listens to the keyboard on the whole document, not just the canvas
622
+ * (otherwise any HTML over the world would swallow input). `false` hands the
623
+ * keyboard entirely to the UI: the game sees no keys and held ones are reset.
624
+ * @default true
625
+ */
626
+ get captureKeyboard() {
627
+ return this._captureKeyboard;
628
+ }
629
+ set captureKeyboard(value) {
630
+ if (this._captureKeyboard === value) return;
631
+ this._captureKeyboard = value;
632
+ if (!value) {
633
+ this.resetKeys();
634
+ this.emit("keyschanged");
635
+ }
636
+ }
425
637
  onAwake() {
426
638
  const ctx = this.ctx;
427
639
  this._dom = ctx.renderer.domElement;
428
640
  window.addEventListener("resize", this.onWindowResize);
429
641
  window.addEventListener("scroll", this.onWindowScroll);
642
+ window.addEventListener("blur", this.onBlur);
430
643
  ctx.on(ThreeContextEvents.Resized, this.onDomResize);
431
644
  const on = this._dom.addEventListener.bind(this._dom);
432
645
  on("focus", this.onFocus);
@@ -436,13 +649,15 @@ var InputSystemModule = class extends ContextModule {
436
649
  on("pointermove", this.onPointerMove);
437
650
  on("pointerup", this.onPointerUp);
438
651
  on("pointerleave", this.onPointerLeave);
439
- on("keydown", this.onKeyDown);
440
- on("keyup", this.onKeyUp);
652
+ document.addEventListener("keydown", this.onKeyDown);
653
+ document.addEventListener("keyup", this.onKeyUp);
654
+ document.addEventListener("focusin", this.onDocumentFocusIn);
441
655
  }
442
656
  /** three-start `ContextModule` has no `onDestroy`; teardown is explicit. */
443
657
  dispose() {
444
658
  window.removeEventListener("resize", this.onWindowResize);
445
659
  window.removeEventListener("scroll", this.onWindowScroll);
660
+ window.removeEventListener("blur", this.onBlur);
446
661
  this.ctx.off(ThreeContextEvents.Resized, this.onDomResize);
447
662
  const off = this._dom.removeEventListener.bind(this._dom);
448
663
  off("focus", this.onFocus);
@@ -452,8 +667,9 @@ var InputSystemModule = class extends ContextModule {
452
667
  off("pointermove", this.onPointerMove);
453
668
  off("pointerup", this.onPointerUp);
454
669
  off("pointerleave", this.onPointerLeave);
455
- off("keydown", this.onKeyDown);
456
- off("keyup", this.onKeyUp);
670
+ document.removeEventListener("keydown", this.onKeyDown);
671
+ document.removeEventListener("keyup", this.onKeyUp);
672
+ document.removeEventListener("focusin", this.onDocumentFocusIn);
457
673
  }
458
674
  onWindowResize = () => {
459
675
  this.updateDomBoundingClientRect();
@@ -472,15 +688,28 @@ var InputSystemModule = class extends ContextModule {
472
688
  this.resetKeys();
473
689
  this.resetPointers();
474
690
  };
691
+ onDocumentFocusIn = (event) => {
692
+ if (!isUiKeyboardTarget(event.target)) return;
693
+ this.resetKeys();
694
+ this.emit("keyschanged");
695
+ };
696
+ /** The key belongs to the game, not to the UI. */
697
+ acceptsKey(event) {
698
+ return this._captureKeyboard && !isUiKeyboardTarget(event.target);
699
+ }
475
700
  onKeyDown = (event) => {
701
+ if (!this.acceptsKey(event)) return;
476
702
  this.updFnKeys(event);
477
703
  this._keys.add(event.code);
478
704
  this.emit("keyschanged");
705
+ this.emit("keydown", event);
479
706
  };
480
707
  onKeyUp = (event) => {
708
+ if (!this.acceptsKey(event)) return;
481
709
  this.updFnKeys(event);
482
710
  this._keys.delete(event.code);
483
711
  this.emit("keyschanged");
712
+ this.emit("keyup", event);
484
713
  };
485
714
  onPointerEnter = (_) => {};
486
715
  onPointerDown = (event) => {
@@ -577,6 +806,21 @@ var InputSystemModule = class extends ContextModule {
577
806
  return this._domBoundingClientRect;
578
807
  }
579
808
  };
809
+ /**
810
+ * Focus is in a text field (or contenteditable) — the keyboard belongs to the UI
811
+ * and the game does not see it. Engine plumbing: nobody has to think about it,
812
+ * `isKeyDown` simply stays quiet while the player types.
813
+ */
814
+ function isUiKeyboardTarget(target) {
815
+ if (!(target instanceof HTMLElement)) return false;
816
+ if (target.isContentEditable) return true;
817
+ switch (target.tagName) {
818
+ case "INPUT":
819
+ case "TEXTAREA":
820
+ case "SELECT": return true;
821
+ default: return false;
822
+ }
823
+ }
580
824
  //#endregion
581
825
  //#region packages/world-runtime/src/modules/SoundsModule.ts
582
826
  var SoundsModule = class extends ContextModule {
@@ -599,39 +843,36 @@ var UtilsModule = class extends ContextModule {
599
843
  };
600
844
  //#endregion
601
845
  //#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 {
846
+ /**
847
+ * Assembles the world's ctx modules: core ones always, pluggable ones per config.
848
+ * Pluggable classes are pulled dynamically, so a disabled module never reaches
849
+ * the bundle. Call strictly before `start()` and with addons already loaded: the
850
+ * physics module takes rapier from `ctx.modules.addons`.
851
+ *
852
+ * A module the scene needs without a line in the config (physics when a physics
853
+ * behaviour is present) is added separately — through the behaviour's
854
+ * `meta.requiredPatch`, with the same descriptor and idempotently.
855
+ */
856
+ async function createWorldModules(mode = "prod", options) {
857
+ const modules = {
858
+ addons: options.addons,
859
+ controls: new ControlsModule(),
605
860
  env: new EnvModule(mode),
606
861
  html: new HtmlHudModule(),
607
862
  input: new InputSystemModule(),
608
863
  sounds: new SoundsModule(),
609
864
  utils: new UtilsModule()
610
865
  };
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
- });
866
+ for (const descriptor of OPTIONAL_MODULES) {
867
+ const config = descriptor.normalize(options.modules?.[descriptor.id]);
868
+ if (!config) continue;
869
+ try {
870
+ modules[descriptor.id] = await descriptor.create(config);
871
+ } catch (error) {
872
+ console.error(`[modules] failed to create "${descriptor.id}"`, error);
873
+ }
874
+ }
875
+ return modules;
635
876
  }
636
877
  //#endregion
637
878
  //#region packages/world-runtime/src/behaviours/registry.ts
@@ -664,7 +905,7 @@ var entries = [
664
905
  {
665
906
  uuid: "fpscameracontroller",
666
907
  name: "FpsCameraController",
667
- load: () => loadDefault(import("./FpsCameraController-BE2EirRj.js"))
908
+ load: () => loadDefault(import("./FpsCameraController-2GNWJM5D.js"))
668
909
  },
669
910
  {
670
911
  uuid: "sightinteractablearea",
@@ -679,43 +920,43 @@ var entries = [
679
920
  {
680
921
  uuid: "rrb",
681
922
  name: "Rigidbody",
682
- load: () => loadDefault(import("./Rigidbody-b7QNF4ev.js").then((n) => n.n)),
923
+ load: () => loadDefault(import("./Rigidbody-DLhN8ddv.js").then((n) => n.n)),
683
924
  requiredPatch: applyRapierPhysicsPatch
684
925
  },
685
926
  {
686
927
  uuid: "rapierballcol",
687
928
  name: "BallCollider",
688
- load: () => loadDefault(import("./BallCollider-8ZrxReEd.js")),
929
+ load: () => loadDefault(import("./BallCollider-3L5vOrcL.js")),
689
930
  requiredPatch: applyRapierPhysicsPatch
690
931
  },
691
932
  {
692
933
  uuid: "rapiercapscol",
693
934
  name: "CapsuleCollider",
694
- load: () => loadDefault(import("./CapsuleCollider-bynyf6Mc.js")),
935
+ load: () => loadDefault(import("./CapsuleCollider-CBTsR-Ys.js")),
695
936
  requiredPatch: applyRapierPhysicsPatch
696
937
  },
697
938
  {
698
939
  uuid: "rapierconecol",
699
940
  name: "ConeCollider",
700
- load: () => loadDefault(import("./ConeCollider-DtRZmgK8.js")),
941
+ load: () => loadDefault(import("./ConeCollider-C3XU3Y9X.js")),
701
942
  requiredPatch: applyRapierPhysicsPatch
702
943
  },
703
944
  {
704
945
  uuid: "rapierconvxcol",
705
946
  name: "ConvexMeshCollider",
706
- load: () => loadDefault(import("./ConvexMeshCollider-BSmDM4o3.js")),
947
+ load: () => loadDefault(import("./ConvexMeshCollider-DzNV4HTO.js")),
707
948
  requiredPatch: applyRapierPhysicsPatch
708
949
  },
709
950
  {
710
951
  uuid: "rapiercubcol",
711
952
  name: "CuboidCollider",
712
- load: () => loadDefault(import("./CuboidCollider-CJvq7--s.js")),
953
+ load: () => loadDefault(import("./CuboidCollider-Dp1DZoFM.js")),
713
954
  requiredPatch: applyRapierPhysicsPatch
714
955
  },
715
956
  {
716
957
  uuid: "rapiercylcol",
717
958
  name: "CylinderCollider",
718
- load: () => loadDefault(import("./CylinderCollider-CQPSh4tu.js")),
959
+ load: () => loadDefault(import("./CylinderCollider-C0h_nhB5.js")),
719
960
  requiredPatch: applyRapierPhysicsPatch
720
961
  },
721
962
  {
@@ -727,7 +968,7 @@ var entries = [
727
968
  {
728
969
  uuid: "rapierfpsplayercontroller",
729
970
  name: "RapierFpsPlayerController",
730
- load: () => loadDefault(import("./RapierFpsPlayerController-VsNfQl6v.js")),
971
+ load: () => loadDefault(import("./RapierFpsPlayerController-DthD8dw9.js")),
731
972
  requiredPatch: applyRapierPhysicsPatch
732
973
  }
733
974
  ];
@@ -803,20 +1044,25 @@ function createFont(url, family, mime, name) {
803
1044
  return source;
804
1045
  }
805
1046
  //#endregion
806
- //#region packages/world-runtime/src/settings/lib-registry.ts
1047
+ //#region packages/world-runtime/src/addons/lib-registry.ts
807
1048
  /**
808
1049
  * Registry of built-in libraries enabled via `settings.addons.json → libs`.
809
1050
  *
810
1051
  * 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`),
1052
+ * (what the user writes in `require(...)` in scripts — and the import specifier
1053
+ * importers are built from). Single source for:
1054
+ * - game: the app's importer map (`core/game/settings/optional-imports`),
1055
+ * - export: the `imports.libs` block the build emits into the world module,
813
1056
  * - editor: JSON schema (`libs` props) + require typings (`AddonsRequireTypings`).
814
1057
  *
815
- * Adding a lib = add an entry here (+ loader in game, + d.ts loader in editor).
1058
+ * Adding a lib = add an entry here (+ importer in the app, + d.ts loader in editor).
1059
+ * The package itself never imports these — see `./optional-imports.ts`.
816
1060
  */
817
1061
  var LibId = /* @__PURE__ */ function(LibId) {
818
1062
  LibId["Tween"] = "tween";
819
1063
  LibId["Anime"] = "anime";
1064
+ LibId["Nipplejs"] = "nipplejs";
1065
+ LibId["Stats"] = "stats";
820
1066
  return LibId;
821
1067
  }({});
822
1068
  var LIBS = {
@@ -828,7 +1074,20 @@ var LIBS = {
828
1074
  ["anime"]: {
829
1075
  id: "anime",
830
1076
  requireKey: "animejs/animation",
1077
+ packageName: "animejs",
831
1078
  label: "Anime.js (animate)"
1079
+ },
1080
+ ["nipplejs"]: {
1081
+ id: "nipplejs",
1082
+ requireKey: "nipplejs",
1083
+ label: "nipplejs (on-screen joystick)",
1084
+ defaultExport: true
1085
+ },
1086
+ ["stats"]: {
1087
+ id: "stats",
1088
+ requireKey: "stats.js",
1089
+ label: "Stats.js (fps overlay)",
1090
+ defaultExport: true
832
1091
  }
833
1092
  };
834
1093
  var LIB_IDS = Object.values(LibId);
@@ -837,47 +1096,71 @@ function libByRequireKey(requireKey) {
837
1096
  return LIB_DESCRIPTORS.find((l) => l.requireKey === requireKey);
838
1097
  }
839
1098
  //#endregion
840
- //#region packages/world-runtime/src/settings/lib-loaders.ts
1099
+ //#region packages/world-runtime/src/addons/AddonsRuntime.ts
841
1100
  /**
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`.
1101
+ * External dependencies of the world: built-in libs, externals and the physics
1102
+ * engine. Loaded before the world starts; scripts reach them through
1103
+ * `resolve(key)` (`require`), the physics module through `rapier`.
1104
+ *
1105
+ * This is the `addons` ctx module, but it also works without a ctx: launcher
1106
+ * worlds have no 3d context while `require` still has to work, so there the
1107
+ * instance is used directly. How to import packages is up to the runtime owner
1108
+ * (see `WorldImports`).
857
1109
  */
858
- var AddonsRuntime = class {
1110
+ var AddonsRuntime = class extends ContextModule {
1111
+ _imports;
859
1112
  /** libs — by requireKey (`@tweenjs/tween.js`), externals — by accessor (`Tone`). */
860
1113
  _byKey = /* @__PURE__ */ new Map();
861
1114
  _scriptTags = [];
862
- async load(addons) {
1115
+ _rapier = null;
1116
+ constructor(_imports = {}) {
1117
+ super();
1118
+ this._imports = _imports;
1119
+ }
1120
+ /** `physics` — whether the world needs rapier (config or a physics behaviour). */
1121
+ async load(addons, options = {}) {
863
1122
  await this._loadLibs(addons.libs);
864
1123
  await this._loadExternals(addons.externals);
1124
+ if (options.physics) await this._loadRapier();
865
1125
  }
866
1126
  /** Resolves a require key: enabled lib or external accessor; undefined otherwise. */
867
1127
  resolve(key) {
868
1128
  return this._byKey.get(key);
869
1129
  }
1130
+ /** The physics engine, loaded and initialized; `null` in worlds without physics. */
1131
+ get rapier() {
1132
+ return this._rapier;
1133
+ }
870
1134
  async _loadLibs(libs) {
871
1135
  if (!libs) return;
872
1136
  for (const id of LIB_IDS) {
873
1137
  if (!libs[id]) continue;
1138
+ const load = this._imports.libs?.[id];
1139
+ if (!load) {
1140
+ console.error(`[addons] lib "${id}" is enabled but has no importer`);
1141
+ continue;
1142
+ }
874
1143
  try {
875
- this._byKey.set(LIBS[id].requireKey, await LIB_LOADERS[id]());
1144
+ this._byKey.set(LIBS[id].requireKey, unwrapLib(id, await load()));
876
1145
  } catch (e) {
877
1146
  console.error(`[addons] failed to load lib "${id}"`, e);
878
1147
  }
879
1148
  }
880
1149
  }
1150
+ async _loadRapier() {
1151
+ const load = this._imports.rapier;
1152
+ if (!load) {
1153
+ console.error("[addons] the world needs physics but has no importer for \"@dimforge/rapier3d-compat\"");
1154
+ return;
1155
+ }
1156
+ try {
1157
+ const rapier = await load();
1158
+ await rapier.init();
1159
+ this._rapier = rapier;
1160
+ } catch (e) {
1161
+ console.error("[addons] failed to load rapier", e);
1162
+ }
1163
+ }
881
1164
  async _loadExternals(externals) {
882
1165
  if (!externals) return;
883
1166
  for (const ext of externals) try {
@@ -907,36 +1190,14 @@ var AddonsRuntime = class {
907
1190
  for (const tag of this._scriptTags) tag.remove();
908
1191
  this._scriptTags.length = 0;
909
1192
  this._byKey.clear();
1193
+ this._rapier = null;
910
1194
  }
911
1195
  };
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
- };
1196
+ /** A package like nipplejs exposes its API as the default export — that is what
1197
+ * `require` should hand back. */
1198
+ function unwrapLib(id, module) {
1199
+ if (!LIBS[id].defaultExport) return module;
1200
+ return module.default ?? module;
940
1201
  }
941
1202
  //#endregion
942
1203
  //#region packages/world-runtime/src/WorldRuntime.ts
@@ -949,17 +1210,24 @@ var WorldRuntime = class {
949
1210
  options;
950
1211
  /** World lifecycle event bus (`world.events` in scripts). */
951
1212
  emitter = new EventEmitter();
952
- _addons = new AddonsRuntime();
1213
+ _addons;
953
1214
  _executedScripts = /* @__PURE__ */ new Map();
954
1215
  _builtins = /* @__PURE__ */ new Map();
955
1216
  _styleElements = [];
956
1217
  _resources = {};
1218
+ /** Reverse index for `getUrl(resource)` — play mode resolves objects too. */
1219
+ _pathByResource = /* @__PURE__ */ new Map();
957
1220
  _ctx = null;
958
1221
  _bloom = null;
959
1222
  _scriptContext = null;
960
1223
  constructor(definition, options) {
961
1224
  this.definition = definition;
962
1225
  this.options = options;
1226
+ this._addons = new AddonsRuntime(definition.imports);
1227
+ }
1228
+ /** The world's external dependencies; also the `addons` ctx module. */
1229
+ get addons() {
1230
+ return this._addons;
963
1231
  }
964
1232
  /** ctx appears at the 3d-context creation step; unavailable to scripts before that. */
965
1233
  get ctx() {
@@ -974,14 +1242,23 @@ var WorldRuntime = class {
974
1242
  setBloom(bloom) {
975
1243
  this._bloom = bloom;
976
1244
  }
977
- /** @internal Built-in libs and external scripts from `settings.addons.json`. */
1245
+ /** @internal Built-in libs, externals and the physics engine, if the world needs one. */
978
1246
  async loadAddons() {
979
- const addons = this.definition.addons;
980
- if (addons) await this._addons.load(addons);
1247
+ await this._addons.load(this.definition.addons ?? {}, { physics: this._usesPhysics() });
1248
+ }
1249
+ /** Physics is on in the config, or a built-in behaviour on the scene needs it. */
1250
+ _usesPhysics() {
1251
+ if (normalizePhysics(this.definition.addons?.modules?.physics)) return true;
1252
+ return (this.definition.usedBuiltins ?? []).some((uuid) => getBuiltinBehaviour(uuid)?.requiredPatch);
981
1253
  }
982
1254
  /** @internal World resources by asset path — result of `loadAssets`. */
983
1255
  setResources(resources) {
984
1256
  this._resources = resources;
1257
+ this._pathByResource.clear();
1258
+ for (const path in resources) {
1259
+ const resource = resources[path];
1260
+ if (resource !== null && typeof resource === "object") this._pathByResource.set(resource, path);
1261
+ }
985
1262
  }
986
1263
  reportProgress(stage, details) {
987
1264
  const progress = {
@@ -990,9 +1267,11 @@ var WorldRuntime = class {
990
1267
  };
991
1268
  this.options.onProgress?.(progress);
992
1269
  }
993
- getUrl = (path) => {
994
- const url = this.definition.assets[path];
995
- if (url === void 0) throw new Error(`World runtime: unknown asset "${path}"`);
1270
+ /** Accepts an asset path or a resource returned by `require` — like play mode. */
1271
+ getUrl = (source) => {
1272
+ const path = typeof source === "string" ? source : this._pathByResource.get(source);
1273
+ const url = path === void 0 ? void 0 : this.definition.assets[path];
1274
+ if (url === void 0) throw new Error(`World runtime: unknown asset "${typeof source === "string" ? source : path ?? "<unknown resource>"}"`);
996
1275
  if (isAbsoluteUrl(url)) return url;
997
1276
  return `${this.options.assetsBaseUrl ?? ""}${url}`;
998
1277
  };
@@ -1032,11 +1311,6 @@ var WorldRuntime = class {
1032
1311
  console.error(`Required patch of "${uuid}" failed`, error);
1033
1312
  }
1034
1313
  }
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
1314
  }
1041
1315
  /** Addon (built-in lib / external) → script by path → asset resource. Same
1042
1316
  * resolve order as in play mode. */
@@ -1050,7 +1324,8 @@ var WorldRuntime = class {
1050
1324
  };
1051
1325
  /**
1052
1326
  * Runtime spawn: a prefab (build-time factory) is assembled anew, a regular
1053
- * object is cloned. Returns a detached object — the caller adds it.
1327
+ * object is cloned. The instance is only created and returned — adding it to
1328
+ * the scene hierarchy is up to the caller (`ctx.scene.add(obj)`).
1054
1329
  */
1055
1330
  instantiate = (source, options) => {
1056
1331
  const object = typeof source === "function" ? source() : clone(source);
@@ -1144,6 +1419,10 @@ var WorldRuntime = class {
1144
1419
  get ctx() {
1145
1420
  return runtime.ctx;
1146
1421
  },
1422
+ /** Short path to the chat module, same as in play mode. */
1423
+ get chat() {
1424
+ return runtime.ctx.modules.chat;
1425
+ },
1147
1426
  events: this.emitter,
1148
1427
  emitter: this.emitter,
1149
1428
  rendering: { get bloom() {
@@ -1155,7 +1434,11 @@ var WorldRuntime = class {
1155
1434
  getUrl: this.getUrl,
1156
1435
  THREE,
1157
1436
  TSL,
1158
- van
1437
+ van,
1438
+ /** Physics engine global; null in worlds without physics. */
1439
+ get RAPIER() {
1440
+ return runtime.addons.rapier;
1441
+ }
1159
1442
  };
1160
1443
  return this._scriptContext;
1161
1444
  }
@@ -1177,7 +1460,7 @@ function isAbsoluteUrl(url) {
1177
1460
  }
1178
1461
  //#endregion
1179
1462
  //#region packages/world-runtime/src/version.gen.ts
1180
- var RUNTIME_VERSION = "0.1.0";
1463
+ var RUNTIME_VERSION = "0.3.2";
1181
1464
  //#endregion
1182
1465
  //#region packages/world-runtime/src/version.ts
1183
1466
  /**
@@ -1243,7 +1526,10 @@ async function createWorld(definition, runtime) {
1243
1526
  scene: new THREE.Scene(),
1244
1527
  autoInitRenderer: false
1245
1528
  }, runtime);
1246
- world.addModules(createWorldModules("prod"));
1529
+ world.addModules(await createWorldModules("prod", {
1530
+ addons: runtime.addons,
1531
+ modules: runtime.definition.addons?.modules
1532
+ }));
1247
1533
  applyRendererConfig(renderer, config);
1248
1534
  if (config.bloom?.enabled) {
1249
1535
  if (config.bloom.mode === "emissive") enableSceneEmissiveMrt(world.ctx.scenePass);
@@ -1678,7 +1964,7 @@ var UNIFORM_DEFAULTS = {
1678
1964
  texture: () => getDefaultResource(DefaultResourceId.Texture)
1679
1965
  };
1680
1966
  //#endregion
1681
- //#region packages/world-runtime/src/settings/externals.ts
1967
+ //#region packages/world-runtime/src/addons/externals.ts
1682
1968
  var EXTERNAL_TYPES = ["umd", "module"];
1683
1969
  //#endregion
1684
1970
  //#region packages/world-runtime/src/rendering/renderer-options.ts
@@ -1704,4 +1990,4 @@ var TONE_MAPPING_NAMES = [
1704
1990
  "Neutral"
1705
1991
  ];
1706
1992
  //#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 };
1993
+ export { AddonsRuntime, AssetLoaders, AudioSource, BLOOM_MODES, BUILTIN_BEHAVIOURS, BUILTIN_BEHAVIOUR_LIST, CHAT_INPUT_MODES, DEFAULT_RESOURCE_IDS, DefaultResourceId, EXTERNAL_TYPES, EnvModule, FontSource, HtmlHudModule, InputSystemModule, LIBS, LIB_DESCRIPTORS, LIB_IDS, LOADING_MANAGER, LibId, OPTIONAL_MODULES, OPTIONAL_MODULE_IDS, OptionalModuleId, 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, getOptionalModuleDescriptor, getRadiusScale, isDefaultResourceId, isMobileDevice, isUniformType, libByRequireKey, loadGeometry, loadHdr, loadModel, loadTexture, normalizeChat, normalizePhysics, parseGeometryJson, parseHdr, parseModel, setColor, uniformType };