@vulfram/engine 0.17.1-alpha → 0.19.2-alpha

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 (36) hide show
  1. package/README.md +3 -3
  2. package/package.json +9 -4
  3. package/src/engine/api.ts +12 -25
  4. package/src/engine/bridge/dispatch.ts +3 -8
  5. package/src/engine/ecs/components.ts +340 -0
  6. package/src/engine/ecs/index.ts +3 -661
  7. package/src/engine/ecs/intents.ts +184 -0
  8. package/src/engine/ecs/systems.ts +26 -0
  9. package/src/engine/intents/store.ts +72 -0
  10. package/src/engine/state.ts +3 -3
  11. package/src/engine/systems/command-intent.ts +11 -16
  12. package/src/engine/systems/diagnostics.ts +3 -13
  13. package/src/engine/systems/input-mirror.ts +156 -18
  14. package/src/engine/systems/resource-upload.ts +12 -14
  15. package/src/engine/systems/response-decode.ts +17 -0
  16. package/src/engine/systems/scene-sync.ts +12 -13
  17. package/src/engine/systems/ui-bridge.ts +31 -10
  18. package/src/engine/systems/utils.ts +46 -3
  19. package/src/engine/systems/world-lifecycle.ts +9 -15
  20. package/src/engine/world/entities.ts +201 -37
  21. package/src/engine/world/mount.ts +27 -6
  22. package/src/engine/world/world-ui.ts +77 -30
  23. package/src/engine/world/world3d.ts +282 -33
  24. package/src/helpers/collision.ts +487 -0
  25. package/src/helpers/index.ts +2 -0
  26. package/src/helpers/raycast.ts +442 -0
  27. package/src/types/cmds/geometry.ts +2 -2
  28. package/src/types/cmds/index.ts +42 -0
  29. package/src/types/cmds/input.ts +39 -0
  30. package/src/types/cmds/material.ts +10 -10
  31. package/src/types/cmds/realm.ts +0 -2
  32. package/src/types/cmds/system.ts +10 -0
  33. package/src/types/cmds/target.ts +14 -0
  34. package/src/types/events/keyboard.ts +2 -2
  35. package/src/types/events/pointer.ts +43 -0
  36. package/src/types/events/system.ts +44 -0
package/README.md CHANGED
@@ -5,10 +5,10 @@ Functional engine for the Vulfram core, focused on a simple API for creating wor
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- bun add @vulfram/engine @vulfram/transport-wasm
8
+ bun add @vulfram/engine @vulfram/transport-browser
9
9
  ```
10
10
 
11
- Use the transport that fits your environment (`transport-wasm`, `transport-bun`, `transport-napi`).
11
+ Use the transport that fits your environment (`transport-browser`, `transport-bun`, `transport-napi`).
12
12
 
13
13
  ## Simple example
14
14
 
@@ -17,7 +17,7 @@ import { initEngine, tick } from '@vulfram/engine/core';
17
17
  import { createWindow } from '@vulfram/engine/window';
18
18
  import { mountWorldToWindow } from '@vulfram/engine/mount';
19
19
  import * as World3D from '@vulfram/engine/world3d';
20
- import { transportWasm } from '@vulfram/transport-wasm';
20
+ import { transportWasm } from '@vulfram/transport-browser';
21
21
 
22
22
  initEngine({ transport: transportWasm });
23
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vulfram/engine",
3
- "version": "0.17.1-alpha",
3
+ "version": "0.19.2-alpha",
4
4
  "module": "src/index.ts",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -47,6 +47,11 @@
47
47
  "import": "./src/types/index.ts",
48
48
  "types": "./src/types/index.ts"
49
49
  },
50
+ "./helpers": {
51
+ "default": "./src/helpers/index.ts",
52
+ "import": "./src/helpers/index.ts",
53
+ "types": "./src/helpers/index.ts"
54
+ },
50
55
  "./package.json": "./package.json"
51
56
  },
52
57
  "files": [
@@ -57,12 +62,12 @@
57
62
  "access": "public"
58
63
  },
59
64
  "dependencies": {
60
- "@vulfram/transport-types": "^0.2.5",
65
+ "@vulfram/transport-types": "^0.2.6",
61
66
  "gl-matrix": "^3.4.4",
62
- "glob": "^13.0.6",
63
67
  "msgpackr": "^1.11.8"
64
68
  },
65
69
  "devDependencies": {
66
- "@types/bun": "^1.3.10"
70
+ "@types/bun": "^1.3.10",
71
+ "glob": "^13.0.6"
67
72
  }
68
73
  }
package/src/engine/api.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  import type { ComponentSchema, System, SystemContext, SystemStep } from './ecs';
15
15
  import { EngineError } from './errors';
16
16
  import { engineState, REQUIRED_SYSTEMS, type WorldState } from './state';
17
+ import { createIntentStore } from './intents/store';
17
18
  import * as CoreSystems from './systems';
18
19
  import type { UploadType } from '../types/kinds';
19
20
  import type { RealmKind } from '../types/cmds/realm';
@@ -41,25 +42,10 @@ function recalculateWorldWindowBindings(world: WorldState): void {
41
42
  world.primaryWindowId = primary;
42
43
  }
43
44
 
44
- function findLowestConfirmedWindowId(): number | undefined {
45
- if (engineState.confirmedWindowIds.size === 0) {
46
- return undefined;
47
- }
48
- let minWindowId = Number.POSITIVE_INFINITY;
49
- for (const windowId of engineState.confirmedWindowIds) {
50
- if (windowId < minWindowId) {
51
- minWindowId = windowId;
52
- }
53
- }
54
- return Number.isFinite(minWindowId) ? minWindowId : undefined;
55
- }
56
-
57
45
  /**
58
46
  * Shared realm creation options used by `createWorld3D` and `createWorldUI`.
59
47
  */
60
48
  export type CreateWorldOptions = {
61
- /** Optional output surface id when provided by host integration. */
62
- outputSurfaceId?: number;
63
49
  /** Core scheduling hint for realm priority. */
64
50
  importance?: number;
65
51
  /** Core-side cache policy value. */
@@ -227,14 +213,13 @@ export function uploadBuffer(
227
213
  }
228
214
  }
229
215
 
230
- function createRealmWorld(kind: RealmKind, config: CreateWorldOptions = {}): WorldId {
216
+ function createRealmWorld(
217
+ kind: RealmKind,
218
+ config: CreateWorldOptions = {},
219
+ ): WorldId {
231
220
  requireInitialized();
232
221
 
233
222
  const worldId = engineState.nextWorldId++;
234
- // Core compatibility: when at least one window exists, pick the lowest ID as host window
235
- // if the caller did not provide an explicit surface. This keeps world API window-agnostic
236
- // while satisfying render graph bootstrap paths that still rely on host-window mapping.
237
- const inferredHostWindowId = findLowestConfirmedWindowId();
238
223
 
239
224
  const world: WorldState = {
240
225
  worldId,
@@ -246,8 +231,6 @@ function createRealmWorld(kind: RealmKind, config: CreateWorldOptions = {}): Wor
246
231
  coreRealmId: undefined,
247
232
  realmCreateArgs: {
248
233
  kind,
249
- hostWindowId: inferredHostWindowId,
250
- outputSurfaceId: config.outputSurfaceId,
251
234
  importance: config.importance,
252
235
  cachePolicy: config.cachePolicy,
253
236
  flags: config.flags,
@@ -266,7 +249,7 @@ function createRealmWorld(kind: RealmKind, config: CreateWorldOptions = {}): Wor
266
249
  components: new Map(),
267
250
  nextCoreId: 100,
268
251
  systems: [...REQUIRED_SYSTEMS],
269
- pendingIntents: [],
252
+ intentStore: createIntentStore(),
270
253
  internalEvents: [],
271
254
  pendingCommands: [],
272
255
  pendingCommandsHead: 0,
@@ -430,7 +413,9 @@ function processGlobalResponses(): void {
430
413
  const res = engineState.globalInboundResponses[i]!;
431
414
  const content = res.content as { success?: boolean; message?: string };
432
415
  if (res.type === 'window-create') {
433
- const pendingWindowId = engineState.pendingWindowCreateByCommandId.get(res.id);
416
+ const pendingWindowId = engineState.pendingWindowCreateByCommandId.get(
417
+ res.id,
418
+ );
434
419
  if (pendingWindowId !== undefined) {
435
420
  if (content.success) {
436
421
  engineState.confirmedWindowIds.add(pendingWindowId);
@@ -440,7 +425,9 @@ function processGlobalResponses(): void {
440
425
  engineState.pendingWindowCreateByCommandId.delete(res.id);
441
426
  }
442
427
  } else if (res.type === 'window-close') {
443
- const pendingWindowId = engineState.pendingWindowCloseByCommandId.get(res.id);
428
+ const pendingWindowId = engineState.pendingWindowCloseByCommandId.get(
429
+ res.id,
430
+ );
444
431
  if (pendingWindowId !== undefined) {
445
432
  if (content.success) {
446
433
  engineState.usedWindowIds.delete(pendingWindowId);
@@ -1,6 +1,5 @@
1
1
  import type { CommandResponseEnvelope, EngineCmd } from '../../types/cmds';
2
2
  import type { EngineEvent } from '../../types/events';
3
- import { EngineError } from '../errors';
4
3
  import { engineState } from '../state';
5
4
  import { getWorldOrThrow, requireInitialized } from './guards';
6
5
 
@@ -88,13 +87,9 @@ export function enqueueCommand<T extends EngineCmd['type']>(
88
87
  typedContent.windowId = world.primaryWindowId;
89
88
  }
90
89
  if ('realmId' in typedContent && typedContent.realmId == null) {
91
- if (world.coreRealmId === undefined) {
92
- throw new EngineError(
93
- 'WorldNotReady',
94
- `World ${worldId} realm is not ready yet. Wait for at least one tick after createWorld.`,
95
- );
96
- }
97
- typedContent.realmId = world.coreRealmId;
90
+ // Avoid host-side readiness guards: keep logical-id flow stable.
91
+ // Core validates realm existence and may apply internal fallbacks.
92
+ typedContent.realmId = world.coreRealmId ?? world.worldId;
98
93
  }
99
94
 
100
95
  world.pendingCommands.push({
@@ -0,0 +1,340 @@
1
+ import type { ViewPosition } from '../../types/cmds/camera';
2
+ import type { GeometryPrimitiveEntry } from '../../types/cmds/geometry';
3
+ import type { MaterialOptions } from '../../types/cmds/material';
4
+ import type { ForwardAtlasOptions } from '../../types/cmds/texture';
5
+ import type { GamepadEvent, SystemEvent, UiEvent } from '../../types/events';
6
+ import type {
7
+ CameraKind,
8
+ LightKind,
9
+ MaterialKind,
10
+ TextureCreateMode,
11
+ } from '../../types/kinds';
12
+ import type { JsonObject, JsonValue } from '../../types/json';
13
+
14
+ /** Transform component data used to position entities. */
15
+ export interface TransformProps {
16
+ position?: [number, number, number];
17
+ rotation?: [number, number, number, number];
18
+ scale?: [number, number, number];
19
+ layerMask?: number;
20
+ visible?: boolean;
21
+ }
22
+
23
+ /** Fully-resolved transform component stored in the ECS. */
24
+ export interface TransformComponent extends Required<Omit<TransformProps, never>> {
25
+ type: 'Transform';
26
+ }
27
+
28
+ /** Parent link data used for hierarchical transforms. */
29
+ export interface ParentProps {
30
+ parentId: number;
31
+ }
32
+
33
+ /** Parent component stored in the ECS. */
34
+ export interface ParentComponent extends ParentProps {
35
+ type: 'Parent';
36
+ }
37
+
38
+ /** Camera component configuration. */
39
+ export interface CameraProps {
40
+ kind?: CameraKind;
41
+ near?: number;
42
+ far?: number;
43
+ order?: number;
44
+ viewPosition?: ViewPosition;
45
+ orthoScale?: number;
46
+ }
47
+
48
+ /** Camera component stored in the ECS. */
49
+ export interface CameraComponent extends Required<Omit<CameraProps, 'viewPosition'>> {
50
+ type: 'Camera';
51
+ id: number;
52
+ viewPosition?: ViewPosition;
53
+ skipUpdate?: boolean;
54
+ }
55
+
56
+ /** Light component configuration. */
57
+ export interface LightProps {
58
+ kind?: LightKind;
59
+ color?: [number, number, number];
60
+ intensity?: number;
61
+ range?: number;
62
+ castShadow?: boolean;
63
+ direction?: [number, number, number];
64
+ spotInnerOuter?: [number, number];
65
+ }
66
+
67
+ /** Light component stored in the ECS. */
68
+ export interface LightComponent extends Required<Omit<LightProps, never>> {
69
+ type: 'Light';
70
+ id: number;
71
+ skipUpdate?: boolean;
72
+ }
73
+
74
+ /** Model component configuration. */
75
+ export interface ModelProps {
76
+ geometryId: number;
77
+ materialId?: number;
78
+ castShadow?: boolean;
79
+ receiveShadow?: boolean;
80
+ castOutline?: boolean;
81
+ outlineColor?: [number, number, number, number];
82
+ }
83
+
84
+ /** Model component stored in the ECS. */
85
+ export interface ModelComponent extends Required<Omit<ModelProps, 'materialId'>> {
86
+ type: 'Model';
87
+ id: number;
88
+ materialId?: number;
89
+ skipUpdate?: boolean;
90
+ }
91
+
92
+ /** Tag component configuration. */
93
+ export interface TagProps {
94
+ name?: string;
95
+ labels?: string[];
96
+ }
97
+
98
+ /** Tag component stored in the ECS. */
99
+ export interface TagComponent {
100
+ type: 'Tag';
101
+ name: string;
102
+ labels: Set<string>;
103
+ }
104
+
105
+ /** InputState: Stores the current input state for a world. */
106
+ export interface InputStateComponent {
107
+ type: 'InputState';
108
+ keysPressed: Set<number>;
109
+ keysJustPressed: Set<number>;
110
+ keysJustReleased: Set<number>;
111
+ pointerButtons: Set<number>;
112
+ pointerPosition: [number, number];
113
+ pointerDelta: [number, number];
114
+ pointerJustPressed: Set<number>;
115
+ pointerJustReleased: Set<number>;
116
+ pointerWindowId?: number;
117
+ pointerWindowSize?: [number, number];
118
+ pointerTargetId?: number;
119
+ pointerPositionTarget?: [number, number];
120
+ pointerTargetSize?: [number, number];
121
+ pointerTargetUv?: [number, number];
122
+ pointerTargetDelta?: [number, number];
123
+ scrollDelta: [number, number];
124
+ imeEnabled: boolean;
125
+ imePreeditText?: string;
126
+ imeCursorRange?: [number, number];
127
+ imeCommitText?: string;
128
+ }
129
+
130
+ /** WindowState: Stores window events and state. */
131
+ export interface WindowStateComponent {
132
+ type: 'WindowState';
133
+ focused: boolean;
134
+ size: [number, number];
135
+ position: [number, number];
136
+ scaleFactor: number;
137
+ closeRequested: boolean;
138
+ resizedThisFrame: boolean;
139
+ movedThisFrame: boolean;
140
+ focusChangedThisFrame: boolean;
141
+ }
142
+
143
+ /** Gamepad state mirrored from core gamepad events. */
144
+ export interface GamepadStateComponent {
145
+ type: 'GamepadState';
146
+ connected: Map<number, { name: string }>;
147
+ buttons: Map<number, Map<number, { pressed: boolean; value: number }>>;
148
+ axes: Map<number, Map<number, number>>;
149
+ eventsThisFrame: GamepadEvent[];
150
+ }
151
+
152
+ /** System events mirrored for host-level integrations. */
153
+ export interface SystemEventStateComponent {
154
+ type: 'SystemEventState';
155
+ eventsThisFrame: SystemEvent[];
156
+ lastError?: {
157
+ scope: string;
158
+ message: string;
159
+ commandId?: number;
160
+ commandType?: string;
161
+ };
162
+ }
163
+
164
+ /** UI events mirrored from core UI event channel. */
165
+ export interface UiEventStateComponent {
166
+ type: 'UiEventState';
167
+ eventsThisFrame: UiEvent[];
168
+ }
169
+
170
+ /** Focus traversal behavior when reaching first/last input. */
171
+ export type UiFocusCycleMode = 'wrap' | 'clamp';
172
+
173
+ /** Form scope state used to control focus traversal. */
174
+ export interface UiFormScope {
175
+ formId: string;
176
+ windowId: number;
177
+ realmId: number;
178
+ documentId: number;
179
+ disabled: boolean;
180
+ cycleMode: UiFocusCycleMode;
181
+ activeFieldsetId?: string;
182
+ activeNodeId?: number;
183
+ }
184
+
185
+ /** Fieldset scope metadata. */
186
+ export interface UiFieldsetScope {
187
+ formId: string;
188
+ fieldsetId: string;
189
+ disabled: boolean;
190
+ legendNodeId?: number;
191
+ }
192
+
193
+ /** Focusable node metadata for tab ordering. */
194
+ export interface UiFocusableNode {
195
+ formId: string;
196
+ nodeId: number;
197
+ tabIndex: number;
198
+ fieldsetId?: string;
199
+ disabled: boolean;
200
+ orderHint: number;
201
+ }
202
+
203
+ /** UI runtime state mirrored in the ECS world. */
204
+ export interface UiStateComponent {
205
+ type: 'UiState';
206
+ forms: Map<string, UiFormScope>;
207
+ fieldsets: Map<string, UiFieldsetScope>;
208
+ nodes: Map<number, UiFocusableNode>;
209
+ focusByWindow: Map<number, { formId: string; nodeId: number }>;
210
+ }
211
+
212
+ /** Base properties shared by resources. */
213
+ export interface BaseResourceProps {
214
+ label?: string;
215
+ }
216
+
217
+ /** Material resource configuration. */
218
+ export interface MaterialProps extends BaseResourceProps {
219
+ kind: MaterialKind;
220
+ options: MaterialOptions;
221
+ }
222
+
223
+ /** Options for cube primitive geometry. */
224
+ export interface CubeOptions {
225
+ size?: [number, number, number];
226
+ }
227
+
228
+ /** Options for plane primitive geometry. */
229
+ export interface PlaneOptions {
230
+ size?: [number, number, number];
231
+ subdivisions?: number;
232
+ }
233
+
234
+ /** Options for sphere primitive geometry. */
235
+ export interface SphereOptions {
236
+ radius?: number;
237
+ sectors?: number;
238
+ stacks?: number;
239
+ }
240
+
241
+ /** Options for cylinder primitive geometry. */
242
+ export interface CylinderOptions {
243
+ radius?: number;
244
+ height?: number;
245
+ sectors?: number;
246
+ segments?: number;
247
+ }
248
+
249
+ /** Options for torus primitive geometry. */
250
+ export interface TorusOptions {
251
+ majorRadius?: number;
252
+ minorRadius?: number;
253
+ majorSegments?: number;
254
+ minorSegments?: number;
255
+ radialSegments?: number;
256
+ tubularSegments?: number;
257
+ }
258
+
259
+ /** Options for pyramid primitive geometry. */
260
+ export interface PyramidOptions {
261
+ size?: [number, number, number];
262
+ subdivisions?: number;
263
+ }
264
+
265
+ /** Geometry resource configuration (primitive or custom). */
266
+ export type GeometryProps = BaseResourceProps & (
267
+ | {
268
+ type: 'custom';
269
+ entries: GeometryPrimitiveEntry[];
270
+ }
271
+ | ({ type: 'primitive' } & (
272
+ | { shape: 'cube'; options?: CubeOptions }
273
+ | { shape: 'plane'; options?: PlaneOptions }
274
+ | { shape: 'sphere'; options?: SphereOptions }
275
+ | { shape: 'cylinder'; options?: CylinderOptions }
276
+ | { shape: 'torus'; options?: TorusOptions }
277
+ | { shape: 'pyramid'; options?: PyramidOptions }
278
+ ))
279
+ );
280
+
281
+ /** Texture resource configuration. */
282
+ export interface TextureProps extends BaseResourceProps {
283
+ srgb?: boolean;
284
+ mode?: TextureCreateMode;
285
+ atlasOptions?: ForwardAtlasOptions;
286
+ source:
287
+ | { type: 'buffer'; bufferId: number }
288
+ | { type: 'color'; color: [number, number, number, number] };
289
+ }
290
+
291
+ /** Custom component payload stored in the ECS. */
292
+ export interface CustomComponent {
293
+ type: string;
294
+ data: JsonObject;
295
+ }
296
+
297
+ /** Union of all built-in component types. */
298
+ export type Component =
299
+ | TransformComponent
300
+ | ParentComponent
301
+ | CameraComponent
302
+ | LightComponent
303
+ | ModelComponent
304
+ | TagComponent
305
+ | InputStateComponent
306
+ | WindowStateComponent
307
+ | GamepadStateComponent
308
+ | SystemEventStateComponent
309
+ | UiEventStateComponent
310
+ | UiStateComponent
311
+ | CustomComponent;
312
+
313
+ /** String literal type for component identifiers. */
314
+ export type ComponentType = Component['type'];
315
+
316
+ /** Supported property types for custom schemas. */
317
+ export type PropertyType =
318
+ | 'number'
319
+ | 'string'
320
+ | 'boolean'
321
+ | 'vec2'
322
+ | 'vec3'
323
+ | 'vec4'
324
+ | 'quat'
325
+ | 'entity'
326
+ | 'resource'
327
+ | 'array'
328
+ | 'object';
329
+
330
+ /** Schema entry describing a custom component field. */
331
+ export interface SchemaProperty {
332
+ type: PropertyType;
333
+ default?: JsonValue;
334
+ optional?: boolean;
335
+ }
336
+
337
+ /** Schema definition for a custom component. */
338
+ export interface ComponentSchema {
339
+ [key: string]: SchemaProperty;
340
+ }