@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
@@ -1,38 +1,10 @@
1
1
  import { ContextModule } from "three-start";
2
- import EventEmitter from "eventemitter3";
3
2
  import * as THREE from "three/webgpu";
4
3
  import type { InputModuleApi } from "./contracts/input.module-api";
5
- declare class PlayerInput extends EventEmitter<{
6
- dirchanged: [forward: number, lateral: number];
7
- forcechanged: [force: number];
8
- jump: [];
9
- }> {
10
- private _forward;
11
- private _lateral;
12
- private _force;
13
- /**
14
- * @default 0
15
- */
16
- get forward(): number;
17
- /**
18
- * @default 0
19
- */
20
- get lateral(): number;
21
- /**
22
- * @default 1
23
- */
24
- get force(): number;
25
- setDir(forward: number, lateral: number): void;
26
- setForce(force: number): void;
27
- jump(): void;
28
- /**
29
- * 0 is keyboard, 1 is joystick
30
- * @default 0
31
- */
32
- mode: 0 | 1;
33
- }
34
4
  export declare class InputSystemModule extends ContextModule<{
35
5
  keyschanged: [];
6
+ keydown: [event: KeyboardEvent];
7
+ keyup: [event: KeyboardEvent];
36
8
  }> implements InputModuleApi {
37
9
  private _dom;
38
10
  private readonly _keys;
@@ -57,7 +29,15 @@ export declare class InputSystemModule extends ContextModule<{
57
29
  get ctrlKey(): boolean;
58
30
  get metaKey(): boolean;
59
31
  isKeyDown: (key: string) => boolean;
60
- readonly player: PlayerInput;
32
+ private _captureKeyboard;
33
+ /**
34
+ * The game listens to the keyboard on the whole document, not just the canvas
35
+ * (otherwise any HTML over the world would swallow input). `false` hands the
36
+ * keyboard entirely to the UI: the game sees no keys and held ones are reset.
37
+ * @default true
38
+ */
39
+ get captureKeyboard(): boolean;
40
+ set captureKeyboard(value: boolean);
61
41
  onAwake(): void;
62
42
  /** three-start `ContextModule` has no `onDestroy`; teardown is explicit. */
63
43
  dispose(): void;
@@ -66,6 +46,9 @@ export declare class InputSystemModule extends ContextModule<{
66
46
  private onDomResize;
67
47
  private onFocus;
68
48
  private onBlur;
49
+ private onDocumentFocusIn;
50
+ /** The key belongs to the game, not to the UI. */
51
+ private acceptsKey;
69
52
  private onKeyDown;
70
53
  private onKeyUp;
71
54
  private onPointerEnter;
@@ -84,4 +67,3 @@ export declare class InputSystemModule extends ContextModule<{
84
67
  private recalculatePointerNdc;
85
68
  private updateDomBoundingClientRect;
86
69
  }
87
- export {};
@@ -0,0 +1,100 @@
1
+ import type { ChatMessage } from "../contracts/chat.module-api";
2
+ export interface ChatDomOptions {
3
+ /** The sent text (already non-empty and trimmed). */
4
+ onSubmit?: (text: string) => void;
5
+ onFocusChange?: (focused: boolean) => void;
6
+ placeholder?: string;
7
+ maxLength?: number;
8
+ /**
9
+ * Keep focus in the input after sending. On mobile — yes (otherwise the
10
+ * keyboard closes after every message), on desktop usually not: control goes
11
+ * back to the game.
12
+ */
13
+ keepFocusAfterSubmit?: boolean;
14
+ /** Where focus goes after sending or Escape (usually the canvas). */
15
+ returnFocusTo?: HTMLElement | null;
16
+ /**
17
+ * Hide the input while it is not focused (`toggle` mode): the chat stays out of
18
+ * the way and opens on Enter. `false` keeps the input always visible.
19
+ */
20
+ hideWhenIdle?: boolean;
21
+ /** How many recent lines stay visible while the chat is closed. */
22
+ visibleLines?: number;
23
+ /** How many lines are visible while the input is focused. */
24
+ openedLines?: number;
25
+ /** Ms before a line fades out; `0` keeps lines lit. */
26
+ fadeAfter?: number;
27
+ }
28
+ /**
29
+ * Chat DOM: the message feed plus the input field.
30
+ *
31
+ * Built to survive real devices: submit through a `<form>` (Enter/Go/Send on any
32
+ * mobile keyboard), an IME composition guard, and lifting above the on-screen
33
+ * keyboard via `visualViewport`. Details —
34
+ * `./README.md`.
35
+ */
36
+ export declare class ChatDom {
37
+ readonly root: HTMLDivElement;
38
+ readonly feed: HTMLDivElement;
39
+ readonly form: HTMLFormElement;
40
+ readonly input: HTMLInputElement;
41
+ private readonly _options;
42
+ private _lines;
43
+ private _isComposing;
44
+ private _viewportRaf;
45
+ private _mounted;
46
+ constructor(options?: ChatDomOptions);
47
+ /** The focus policy is only known at runtime (mobile/desktop, canvas). */
48
+ setFocusPolicy(policy: Pick<ChatDomOptions, "keepFocusAfterSubmit" | "returnFocusTo">): void;
49
+ get isMounted(): boolean;
50
+ /** Whether to hide the input while unfocused (`toggle` mode). */
51
+ setHideWhenIdle(hide: boolean): void;
52
+ get isFocused(): boolean;
53
+ get value(): string;
54
+ set value(next: string);
55
+ /**
56
+ * Syncs the feed with the history: new lines are appended at the bottom, lines
57
+ * gone from the history are removed, extra ones are cut from the top. There is
58
+ * no scrolling — what does not fit is not shown.
59
+ */
60
+ renderMessages(messages: readonly ChatMessage[]): void;
61
+ mount(container: HTMLElement): void;
62
+ unmount(): void;
63
+ focus(): void;
64
+ blur(): void;
65
+ clear(): void;
66
+ dispose(): void;
67
+ /** Keep in the DOM as much as an opened chat could need. */
68
+ private get _maxLines();
69
+ private _createLine;
70
+ private _removeLine;
71
+ /**
72
+ * While the input is focused more lines are shown, including expired ones (an
73
+ * opened chat is meant to be read). Once closed, every line lives by its own
74
+ * deadline again: expired ones fade at once, the rest sit out their remainder.
75
+ */
76
+ private _syncLines;
77
+ private _setFaded;
78
+ private _bind;
79
+ private _unbind;
80
+ private readonly _onSubmit;
81
+ private readonly _onCompositionStart;
82
+ private readonly _onCompositionEnd;
83
+ /** Some Android keyboards send no Enter keydown, only `insertLineBreak`. */
84
+ private readonly _onBeforeInput;
85
+ private readonly _onKeyDown;
86
+ private readonly _stopPropagation;
87
+ private readonly _onFocus;
88
+ private readonly _onBlur;
89
+ /** In `toggle` mode a hidden input must catch neither clicks nor tab focus. */
90
+ private _syncVisibility;
91
+ private readonly _onViewportChange;
92
+ /**
93
+ * The keyboard changes the layout viewport neither on iOS nor (by default) on
94
+ * Android — anything fixed to the bottom ends up underneath it. We measure the
95
+ * overlap through `visualViewport` and lift the chat by that much.
96
+ */
97
+ private _syncKeyboardInset;
98
+ /** Styles are inlined once per document: the package ships no separate css file. */
99
+ private static _ensureStyles;
100
+ }
@@ -0,0 +1,36 @@
1
+ import { ContextModule } from "three-start";
2
+ import { ChatDom } from "./ChatDom";
3
+ import type { NormalizedChat } from "./chat.module-descriptor";
4
+ import type { ChatEvents, ChatMessage, ChatModuleApi } from "../contracts/chat.module-api";
5
+ /**
6
+ * The world chat as a pluggable ctx module: message history plus its own DOM
7
+ * input (`dom`), no React. Enabled from `settings.addons.json → modules`; it
8
+ * mounts its DOM itself. Details and mobile quirks — `./README.md`.
9
+ */
10
+ export declare class ChatModule extends ContextModule<ChatEvents> implements ChatModuleApi {
11
+ readonly dom: ChatDom;
12
+ private readonly _maxMessages;
13
+ private readonly _openKeys;
14
+ private _messages;
15
+ private _seq;
16
+ constructor(config: NormalizedChat);
17
+ get messages(): readonly ChatMessage[];
18
+ onAwake(): void;
19
+ /** Print a line into the feed as is. */
20
+ print(text: string): void;
21
+ /**
22
+ * Send a line the way a player does: commands are handled here, everything else
23
+ * goes out as `sent`. It prints nothing by itself — what to show is up to the world.
24
+ */
25
+ send(text: string): void;
26
+ /** Redraw the whole feed — animation frames, tables, ASCII art. */
27
+ setMessages(texts: readonly string[]): void;
28
+ clear(): void;
29
+ private _emitChanged;
30
+ private _createMessage;
31
+ /** three-start ContextModule has no onDestroy; teardown is explicit. */
32
+ dispose(): void;
33
+ private readonly _onKeyDown;
34
+ private readonly _onMount;
35
+ private readonly _onUnmount;
36
+ }
@@ -0,0 +1,43 @@
1
+ import type { OptionalModuleDescriptor } from "../optional-module-descriptor";
2
+ /**
3
+ * How the input behaves:
4
+ * - `always` — always visible (the classic game chat at the bottom);
5
+ * - `toggle` — hidden, opens on Enter and hides again on Escape or after sending.
6
+ */
7
+ export type ChatInputMode = "always" | "toggle";
8
+ /** `modules.chat`: off | input mode | an object with options. */
9
+ export type ChatConfig = boolean | ChatInputMode | {
10
+ input?: ChatInputMode;
11
+ /** Max message length. */
12
+ maxLength?: number;
13
+ placeholder?: string;
14
+ /** How many messages the history keeps. */
15
+ maxMessages?: number;
16
+ /**
17
+ * Keys that open the chat — `KeyboardEvent.code`, i.e. physical keys:
18
+ * the player's layout does not matter.
19
+ */
20
+ openKeys?: string[];
21
+ /** How many recent lines stay visible while the chat is closed. */
22
+ visibleLines?: number;
23
+ /** How many lines are visible while the input is focused. */
24
+ openedLines?: number;
25
+ /** Ms before a line fades out; `0` keeps lines lit. */
26
+ fadeAfter?: number;
27
+ };
28
+ export interface NormalizedChat {
29
+ input: ChatInputMode;
30
+ maxLength: number;
31
+ placeholder: string;
32
+ maxMessages: number;
33
+ openKeys: readonly string[];
34
+ visibleLines: number;
35
+ openedLines: number;
36
+ fadeAfter: number;
37
+ }
38
+ /** `Slash` opens the chat with a "/" already typed — the Minecraft command gesture. */
39
+ export declare const CHAT_COMMAND_KEY = "Slash";
40
+ export declare const CHAT_INPUT_MODES: readonly ChatInputMode[];
41
+ /** Chat defaults live here: `true` = an always visible input with a limit of 80. */
42
+ export declare function normalizeChat(value: ChatConfig | undefined): NormalizedChat | null;
43
+ export declare const chatModuleDescriptor: OptionalModuleDescriptor<NormalizedChat>;
@@ -0,0 +1,15 @@
1
+ import type RAPIER from "@dimforge/rapier3d-compat";
2
+ /**
3
+ * Public API of the `addons` module — external dependencies of the world:
4
+ * built-in libs and external scripts enabled in `settings.addons.json`, loaded
5
+ * before the world starts.
6
+ *
7
+ * Scripts normally reach a lib by `require("<key>")`; this module is the same
8
+ * bag behind that call, plus the physics engine.
9
+ */
10
+ export interface AddonsModuleApi {
11
+ /** Value by require key (a lib) or by accessor (an external); null if absent. */
12
+ resolve: (key: string) => unknown;
13
+ /** The physics engine — loaded and initialized; null in worlds without physics. */
14
+ readonly rapier: typeof RAPIER | null;
15
+ }
@@ -0,0 +1,52 @@
1
+ import type { ModuleEvents } from "./module-events";
2
+ /** A message in the chat history. */
3
+ export interface ChatMessage {
4
+ id: string;
5
+ text: string;
6
+ /** `Date.now()` when the message was added. */
7
+ timestamp: number;
8
+ }
9
+ export type ChatEvents = {
10
+ /** The feed changed — printed, replaced or cleared. Render your own view here. */
11
+ changed: [messages: ChatMessage[]];
12
+ /** A line was sent (by the player or `send()`); commands never reach here. */
13
+ sent: [text: string];
14
+ /** The input gained or lost focus. */
15
+ inputfocuschanged: [focused: boolean];
16
+ };
17
+ /** Chat DOM — for advanced cases: place, focus or read the input yourself. */
18
+ export interface ChatDomApi {
19
+ readonly root: HTMLDivElement;
20
+ /** Feed element — style it or place your own nodes next to the lines. */
21
+ readonly feed: HTMLDivElement;
22
+ readonly input: HTMLInputElement;
23
+ readonly isMounted: boolean;
24
+ readonly isFocused: boolean;
25
+ value: string;
26
+ mount: (container: HTMLElement) => void;
27
+ unmount: () => void;
28
+ focus: () => void;
29
+ blur: () => void;
30
+ clear: () => void;
31
+ }
32
+ /**
33
+ * Public API of the `chat` module — world chat: the feed + its own input.
34
+ *
35
+ * Sending and printing are separate on purpose: a sent line only fires `sent`,
36
+ * and nothing shows up until the world prints it.
37
+ *
38
+ * ```ts
39
+ * chat.on("sent", (text) => chat.print(`Player: ${text}`));
40
+ * ```
41
+ */
42
+ export interface ChatModuleApi extends ModuleEvents<ChatEvents> {
43
+ readonly messages: readonly ChatMessage[];
44
+ /** Print a line into the feed as is. */
45
+ print: (text: string) => void;
46
+ /** Send a line as the player would: runs commands, otherwise fires `sent`. */
47
+ send: (text: string) => void;
48
+ /** Replace the whole feed at once — for redraws (frames, tables, ASCII art). */
49
+ setMessages: (texts: readonly string[]) => void;
50
+ clear: () => void;
51
+ readonly dom: ChatDomApi;
52
+ }
@@ -0,0 +1,58 @@
1
+ import type { ModuleEvents } from "./module-events";
2
+ import type * as THREE from "three/webgpu";
3
+ /** Device kind a driver reports; any string works, these are the built-in ones. */
4
+ export type ControlsSource = "keyboard" | "touch" | "gamepad" | (string & {});
5
+ /** Frame state a driver fills in. Reused across frames — never store it. */
6
+ export interface ControlsState {
7
+ /** x — strafe, y — forward, -1..1. Write in place: `move.set(x, y)`. */
8
+ readonly move: THREE.Vector2;
9
+ /** Deflection: 1 is a normal walk, higher means a stronger stick tilt. */
10
+ force: number;
11
+ /** Run modifier — shift, a stick pushed past its ring, a UI button. */
12
+ run: boolean;
13
+ /** Held this frame; `controls` fires `jump` on the rising edge. */
14
+ jump: boolean;
15
+ }
16
+ /**
17
+ * An input source for `controls`: keyboard, an on-screen stick, a gamepad, the
18
+ * network, a cutscene. Register with `controls.addDriver(...)` — `read` is then
19
+ * called once per frame, before behaviours update.
20
+ *
21
+ * ```ts
22
+ * controls.addDriver({ kind: "touch", read: (s) => { s.move.set(x, y); s.force = f; } });
23
+ * ```
24
+ */
25
+ export interface ControlsDriver {
26
+ /** Reported as `controls.source` while this driver is the one driving. */
27
+ readonly kind: ControlsSource;
28
+ read: (state: ControlsState) => void;
29
+ }
30
+ export type ControlsEvents = {
31
+ /** Movement vector changed (the same reused `move` instance). */
32
+ movechanged: [move: THREE.Vector2];
33
+ forcechanged: [force: number];
34
+ /** The driving device changed — the player switched keys ↔ stick. */
35
+ sourcechanged: [source: ControlsSource];
36
+ jump: [];
37
+ };
38
+ /**
39
+ * Public API of the `controls` module — the player's movement intent, whatever
40
+ * the device is. Input sources register as drivers, character controllers read
41
+ * `move`/`force`/`run`; raw device state lives in the `input` module.
42
+ */
43
+ export interface ControlsModuleApi extends ModuleEvents<ControlsEvents> {
44
+ /** x — strafe, y — forward. Reused vector: copy it before storing. */
45
+ readonly move: THREE.Vector2;
46
+ readonly force: number;
47
+ readonly run: boolean;
48
+ /** Kind of the driver currently driving. */
49
+ readonly source: ControlsSource;
50
+ readonly drivers: readonly ControlsDriver[];
51
+ /** Built-in WASD/arrows driver, registered on start; remove it to take over. */
52
+ readonly keyboard: ControlsDriver;
53
+ /** Registers a driver and returns the unsubscribe. */
54
+ addDriver: (driver: ControlsDriver) => () => void;
55
+ removeDriver: (driver: ControlsDriver) => void;
56
+ /** Fire a jump by hand — a UI button, a script. */
57
+ jump: () => void;
58
+ }
@@ -1,7 +1,15 @@
1
- /** Public API of the `html` module — fullscreen HTML HUD on top of the canvas. */
1
+ /**
2
+ * Public API of the `html` module — fullscreen HTML HUD on top of the canvas.
3
+ *
4
+ * The root is `pointer-events: none` on purpose: the HUD covers the whole screen
5
+ * and must not swallow clicks meant for the world. Anything interactive you
6
+ * append has to set `pointer-events: auto` on itself.
7
+ */
2
8
  export interface HtmlModuleApi {
9
+ /** HUD container over the canvas; click-through (`pointer-events: none`). */
3
10
  readonly root: HTMLDivElement;
4
11
  readonly crosshair: HTMLDivElement;
12
+ /** Appends nodes into the click-through root — see the note above. */
5
13
  append: (...nodes: Array<Node | string>) => void;
6
14
  setCrosshair: (innerHtml: string) => void;
7
15
  }
@@ -1,3 +1,4 @@
1
+ import type { ModuleEvents } from "./module-events";
1
2
  import type * as THREE from "three/webgpu";
2
3
  /**
3
4
  * Public API of the input module, available in user scripts
@@ -7,7 +8,13 @@ import type * as THREE from "three/webgpu";
7
8
  * if the class stops matching the contract, the project won't compile.
8
9
  * This same file is fed into Monaco (see `monaco/monaco-lib-types.ts`).
9
10
  */
10
- export interface InputModuleApi {
11
+ export type InputEvents = {
12
+ /** Set of pressed keys changed. */
13
+ keyschanged: [];
14
+ keydown: [event: KeyboardEvent];
15
+ keyup: [event: KeyboardEvent];
16
+ };
17
+ export interface InputModuleApi extends ModuleEvents<InputEvents> {
11
18
  readonly pointerDeltaX: number;
12
19
  readonly pointerDeltaY: number;
13
20
  readonly isDragging: boolean;
@@ -18,14 +25,7 @@ export interface InputModuleApi {
18
25
  readonly ctrlKey: boolean;
19
26
  readonly metaKey: boolean;
20
27
  isKeyDown: (key: string) => boolean;
21
- readonly player: PlayerInputApi;
22
- }
23
- /** Player's virtual gamepad stick (`input.player`). */
24
- export interface PlayerInputApi {
25
- readonly forward: number;
26
- readonly lateral: number;
27
- readonly force: number;
28
- setDir: (forward: number, lateral: number) => void;
29
- setForce: (force: number) => void;
30
- jump: () => void;
28
+ /** Game reads the keyboard; `false` hands it over to the world's own UI.
29
+ * @default true */
30
+ captureKeyboard: boolean;
31
31
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Typed events every module inherits from the engine.
3
+ *
4
+ * ```ts
5
+ * world.ctx.modules.chat.on("sent", (text) => { ... });
6
+ * ```
7
+ *
8
+ * Not a module contract — a shared piece the `*.module-api.ts` files extend.
9
+ */
10
+ export interface ModuleEvents<TEvents extends Record<string, unknown[]>> {
11
+ /** Subscribe. */
12
+ on: <TKey extends keyof TEvents & string>(event: TKey, fn: (...args: TEvents[TKey]) => void) => this;
13
+ /** Subscribe until the first call. */
14
+ once: <TKey extends keyof TEvents & string>(event: TKey, fn: (...args: TEvents[TKey]) => void) => this;
15
+ /** Unsubscribe; without `fn` removes every listener of the event. */
16
+ off: <TKey extends keyof TEvents & string>(event: TKey, fn?: (...args: TEvents[TKey]) => void) => this;
17
+ }
@@ -1,11 +1,23 @@
1
+ import type { ModuleEvents } from "./module-events";
1
2
  import type RAPIER from "@dimforge/rapier3d-compat";
2
3
  export type RapierJsModule = typeof RAPIER;
3
4
  /** Public API of the `rapier` module — Rapier physics world, event queue, simulation step. */
4
- export interface RapierModuleApi {
5
+ export type RapierEvents = {
6
+ /** Right before the physics world steps. */
7
+ stepbefore: [];
8
+ /** Right after the step — transforms are already updated. */
9
+ stepafter: [];
10
+ };
11
+ export interface RapierModuleApi extends ModuleEvents<RapierEvents> {
5
12
  readonly isRapierPhysics: true;
6
13
  readonly api: RapierJsModule;
7
14
  readonly world: RAPIER.World;
8
15
  readonly eventQueue: RAPIER.EventQueue;
9
16
  /** @default "vary" */
10
17
  timeStep: number | "vary";
18
+ /** Collider debug rendering is on right now. */
19
+ readonly isDebugEnabled: boolean;
20
+ /** Turn collider debug rendering on; the renderer is fetched on demand. */
21
+ enableDebug: () => Promise<void>;
22
+ disableDebug: () => void;
11
23
  }
@@ -0,0 +1,45 @@
1
+ import { ContextModule } from "three-start";
2
+ import * as THREE from "three/webgpu";
3
+ import type { ControlsDriver, ControlsEvents, ControlsModuleApi, ControlsSource } from "../contracts/controls.module-api";
4
+ /**
5
+ * The player's movement intent — what they want to do, whatever the hardware is.
6
+ * Input sources register as drivers (`addDriver`); the module polls them once a
7
+ * frame and decides which one is driving. Details — `./README.md`.
8
+ */
9
+ export declare class ControlsModule extends ContextModule<ControlsEvents> implements ControlsModuleApi {
10
+ /** x — strafe, y — forward. Reused vector: copy it before storing. */
11
+ readonly move: THREE.Vector2;
12
+ private readonly _drivers;
13
+ private readonly _state;
14
+ private _keyboard;
15
+ private _active;
16
+ private _source;
17
+ private _force;
18
+ private _run;
19
+ private _jump;
20
+ onAwake(): void;
21
+ /** Speed multiplier: 1 is a normal walk, higher means a stronger stick tilt. */
22
+ get force(): number;
23
+ /** Run modifier: shift, a stick pushed past its ring, a UI button. */
24
+ get run(): boolean;
25
+ /** Kind of the device driving right now. */
26
+ get source(): ControlsSource;
27
+ get drivers(): readonly ControlsDriver[];
28
+ /** The built-in keyboard driver — detach it with `removeDriver`. */
29
+ get keyboard(): ControlsDriver;
30
+ addDriver(driver: ControlsDriver): () => void;
31
+ removeDriver(driver: ControlsDriver): void;
32
+ jump(): void;
33
+ onUpdate(): void;
34
+ /**
35
+ * The active driver keeps the channel while it produces input — otherwise the
36
+ * keyboard and a stick would steal control from each other every frame. Once it
37
+ * goes quiet, the first driver with input takes over. The state is left filled
38
+ * by the winner, so nobody is read twice.
39
+ */
40
+ private _readActiveDriver;
41
+ private _readDriver;
42
+ private _resetState;
43
+ private _apply;
44
+ }
45
+ export default ControlsModule;
@@ -0,0 +1,12 @@
1
+ import type { InputSystemModule } from "../InputSystemModule";
2
+ import type { ControlsDriver, ControlsState } from "../contracts/controls.module-api";
3
+ /**
4
+ * Keyboard driver for `controls`: WASD/arrows, shift to run, Space to jump.
5
+ * Reads key state from the `input` module and binds no listeners of its own.
6
+ */
7
+ export declare class KeyboardControlsDriver implements ControlsDriver {
8
+ private readonly _input;
9
+ readonly kind = "keyboard";
10
+ constructor(_input: InputSystemModule);
11
+ read(state: ControlsState): void;
12
+ }
@@ -1,9 +1,16 @@
1
+ import { ControlsModule } from "./controls/ControlsModule";
1
2
  import { EnvModule } from "./EnvModule";
2
3
  import { HtmlHudModule } from "./HtmlHudModule";
3
4
  import { InputSystemModule } from "./InputSystemModule";
4
5
  import { SoundsModule } from "./SoundsModule";
5
6
  import { UtilsModule } from "./UtilsModule";
7
+ import type { AddonsRuntime } from "../addons/AddonsRuntime";
8
+ import type { AddonsConfig } from "../addons/types";
6
9
  import type { EnvMode } from "./EnvModule";
10
+ import type { KvyverseModules } from "./registry";
11
+ export type { ChatModule } from "./chat/ChatModule";
12
+ export type { ChatDom, ChatDomOptions } from "./chat/ChatDom";
13
+ export { ControlsModule } from "./controls/ControlsModule";
7
14
  export { EnvModule } from "./EnvModule";
8
15
  export { HtmlHudModule } from "./HtmlHudModule";
9
16
  export { InputSystemModule } from "./InputSystemModule";
@@ -11,12 +18,32 @@ export { SoundsModule } from "./SoundsModule";
11
18
  export { UtilsModule } from "./UtilsModule";
12
19
  export { isMobileDevice } from "./is-mobile-device";
13
20
  export type { EnvMode } from "./EnvModule";
14
- export type WorldModules = {
21
+ /** Modules every world has — no toggle for them. */
22
+ export type CoreWorldModules = {
23
+ addons: AddonsRuntime;
24
+ controls: ControlsModule;
15
25
  env: EnvModule;
16
26
  html: HtmlHudModule;
17
27
  input: InputSystemModule;
18
28
  sounds: SoundsModule;
19
29
  utils: UtilsModule;
20
30
  };
21
- /** World ctx modules. Registered before `start()` — three-start forbids it later. */
22
- export declare function createWorldModules(mode?: EnvMode): WorldModules;
31
+ /** @deprecated kept for compatibility; see `CoreWorldModules`. */
32
+ export type WorldModules = CoreWorldModules;
33
+ export interface CreateWorldModulesOptions {
34
+ /** The world's external dependencies, already loaded (libs, externals, rapier). */
35
+ addons: AddonsRuntime;
36
+ /** `settings.addons.json → modules`: which pluggable modules are on, and how. */
37
+ modules?: AddonsConfig["modules"];
38
+ }
39
+ /**
40
+ * Assembles the world's ctx modules: core ones always, pluggable ones per config.
41
+ * Pluggable classes are pulled dynamically, so a disabled module never reaches
42
+ * the bundle. Call strictly before `start()` and with addons already loaded: the
43
+ * physics module takes rapier from `ctx.modules.addons`.
44
+ *
45
+ * A module the scene needs without a line in the config (physics when a physics
46
+ * behaviour is present) is added separately — through the behaviour's
47
+ * `meta.requiredPatch`, with the same descriptor and idempotently.
48
+ */
49
+ export declare function createWorldModules(mode: EnvMode | undefined, options: CreateWorldModulesOptions): Promise<CoreWorldModules & Partial<KvyverseModules>>;
@@ -0,0 +1,36 @@
1
+ import type { ContextModule } from "three-start";
2
+ /**
3
+ * Keys of the pluggable modules — the same keys as in
4
+ * `settings.addons.json → modules`. Core modules (`addons`/`env`/`html`/`input`/
5
+ * `controls`/`sounds`/`utils`) are not here: every world has them, with no toggle.
6
+ */
7
+ export declare enum OptionalModuleId {
8
+ Physics = "physics",
9
+ Chat = "chat"
10
+ }
11
+ export declare const OPTIONAL_MODULE_IDS: OptionalModuleId[];
12
+ /** A JSON Schema (draft-07) fragment; structurally the platform's `JSONSchema`. */
13
+ export type ModuleJsonSchema = Record<string, unknown>;
14
+ /**
15
+ * Describes a pluggable module: how to read its value from the world config, how
16
+ * to create it and how to validate the config in the code editor. Defaults (is
17
+ * it on, in which mode) live in `normalize` — the registry knows nothing of them.
18
+ *
19
+ * Rule: `create` pulls the module class through a **dynamic** import, otherwise a
20
+ * disabled module still ends up in the bundle.
21
+ */
22
+ export interface OptionalModuleDescriptor<TConfig = unknown> {
23
+ id: OptionalModuleId;
24
+ /** Config value → normalized config; `null` means the module is off. */
25
+ normalize: (raw: unknown) => TConfig | null;
26
+ /** Config for an on-demand enable (a behaviour pulled the module in without config). */
27
+ defaultConfig: () => TConfig;
28
+ /** The module class is pulled here — a disabled module never reaches the bundle. */
29
+ create: (config: TConfig) => Promise<ContextModule>;
30
+ /**
31
+ * JSON Schema fragment for `modules.<id>`: the platform assembles the
32
+ * `settings.addons.json` schema from these, so module keys never drift from the
33
+ * registry. The type is deliberately wide — the package knows nothing of Monaco.
34
+ */
35
+ schema: ModuleJsonSchema;
36
+ }
@@ -0,0 +1,11 @@
1
+ import type { OptionalModuleDescriptor, OptionalModuleId } from "./optional-module-descriptor";
2
+ /**
3
+ * Table of contents for the world's pluggable modules: what can be enabled from
4
+ * `settings.addons.json → modules`. The descriptors themselves live next to
5
+ * their module code; this is only the list.
6
+ *
7
+ * Add a descriptor here and the module shows up both in the runtime and in the
8
+ * config's JSON schema (the platform builds it from `descriptor.schema`).
9
+ */
10
+ export declare const OPTIONAL_MODULES: ReadonlyArray<OptionalModuleDescriptor<any>>;
11
+ export declare function getOptionalModuleDescriptor(id: OptionalModuleId): OptionalModuleDescriptor<any>;
@@ -0,0 +1,19 @@
1
+ import type { RapierPhysics } from "./RapierPhysics";
2
+ import type { ThreeContext } from "three-start";
3
+ /**
4
+ * Draws rapier colliders as lines. Not a ctx module: it lives inside
5
+ * `RapierPhysics` (`enableDebug`/`disableDebug`) and is pulled dynamically —
6
+ * worlds without debug never load this code.
7
+ *
8
+ * @see {@link https://github.com/vladkrutenyuk/three-kvy-core/blob/main/src/addons/RapierDebugRenderer.ts | Source}
9
+ */
10
+ export declare class RapierDebugRenderer {
11
+ private readonly _ctx;
12
+ private readonly _rapier;
13
+ private _clear;
14
+ constructor(ctx: ThreeContext, rapier: RapierPhysics);
15
+ get enabled(): boolean;
16
+ start(): void;
17
+ stop(): void;
18
+ }
19
+ export default RapierDebugRenderer;