@vulfram/engine 0.14.8-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 (63) hide show
  1. package/README.md +106 -0
  2. package/package.json +60 -4
  3. package/src/core.ts +14 -0
  4. package/src/ecs.ts +1 -0
  5. package/src/engine/api.ts +222 -24
  6. package/src/engine/bridge/dispatch.ts +260 -40
  7. package/src/engine/bridge/guards.ts +4 -1
  8. package/src/engine/bridge/protocol.ts +69 -52
  9. package/src/engine/ecs/components.ts +340 -0
  10. package/src/engine/ecs/index.ts +3 -518
  11. package/src/engine/ecs/intents.ts +184 -0
  12. package/src/engine/ecs/systems.ts +26 -0
  13. package/src/engine/intents/store.ts +72 -0
  14. package/src/engine/state.ts +136 -5
  15. package/src/engine/systems/command-intent.ts +159 -14
  16. package/src/engine/systems/constraint-solve.ts +167 -0
  17. package/src/engine/systems/core-command-builder.ts +9 -268
  18. package/src/engine/systems/diagnostics.ts +20 -29
  19. package/src/engine/systems/index.ts +3 -1
  20. package/src/engine/systems/input-mirror.ts +257 -21
  21. package/src/engine/systems/resource-upload.ts +108 -58
  22. package/src/engine/systems/response-decode.ts +86 -15
  23. package/src/engine/systems/scene-sync.ts +305 -0
  24. package/src/engine/systems/ui-bridge.ts +381 -0
  25. package/src/engine/systems/utils.ts +86 -1
  26. package/src/engine/systems/world-lifecycle.ts +43 -114
  27. package/src/engine/window/manager.ts +168 -0
  28. package/src/engine/world/entities.ts +998 -91
  29. package/src/engine/world/mount.ts +195 -0
  30. package/src/engine/world/types.ts +71 -0
  31. package/src/engine/world/world-ui.ts +313 -0
  32. package/src/engine/world/world3d.ts +529 -0
  33. package/src/helpers/collision.ts +487 -0
  34. package/src/helpers/index.ts +2 -0
  35. package/src/helpers/raycast.ts +442 -0
  36. package/src/index.ts +30 -1
  37. package/src/mount.ts +2 -0
  38. package/src/types/cmds/audio.ts +73 -48
  39. package/src/types/cmds/camera.ts +12 -8
  40. package/src/types/cmds/environment.ts +9 -3
  41. package/src/types/cmds/geometry.ts +15 -16
  42. package/src/types/cmds/index.ts +234 -162
  43. package/src/types/cmds/input.ts +39 -0
  44. package/src/types/cmds/light.ts +12 -11
  45. package/src/types/cmds/material.ts +19 -21
  46. package/src/types/cmds/model.ts +17 -15
  47. package/src/types/cmds/realm.ts +23 -0
  48. package/src/types/cmds/system.ts +29 -0
  49. package/src/types/cmds/target.ts +96 -0
  50. package/src/types/cmds/texture.ts +13 -3
  51. package/src/types/cmds/ui.ts +220 -0
  52. package/src/types/cmds/window.ts +41 -204
  53. package/src/types/events/index.ts +4 -1
  54. package/src/types/events/keyboard.ts +2 -2
  55. package/src/types/events/pointer.ts +85 -13
  56. package/src/types/events/system.ts +188 -30
  57. package/src/types/events/ui.ts +21 -0
  58. package/src/types/index.ts +1 -0
  59. package/src/types/json.ts +15 -0
  60. package/src/window.ts +8 -0
  61. package/src/world-ui.ts +2 -0
  62. package/src/world3d.ts +10 -0
  63. package/tsconfig.json +0 -29
@@ -1,268 +1,9 @@
1
- import { mat4, vec3 } from 'gl-matrix';
2
- import type { CameraKind, LightKind } from '../../types/kinds';
3
- import { enqueueCommand } from '../bridge/dispatch';
4
- import type {
5
- CameraComponent,
6
- Component,
7
- LightComponent,
8
- ModelComponent,
9
- System,
10
- TransformComponent,
11
- } from '../ecs';
12
- import { getEntityTransformMatrix, toQuat, toVec2, toVec3, toVec4 } from './utils';
13
-
14
- export const CoreCommandBuilderSystem: System = (world, context) => {
15
- const intentsToRemove: number[] = [];
16
-
17
- for (let i = 0; i < world.pendingIntents.length; i++) {
18
- const intent = world.pendingIntents[i];
19
- if (!intent) continue;
20
-
21
- if (intent.type === 'attach-model') {
22
- const modelId = world.nextCoreId++;
23
- const transform = getEntityTransformMatrix(world, intent.entityId);
24
- const castShadow = intent.props.castShadow ?? true;
25
- const receiveShadow = intent.props.receiveShadow ?? true;
26
- const castOutline = intent.props.castOutline ?? false;
27
- const outlineColor = intent.props.outlineColor ?? ([0, 0, 0, 0] as const);
28
-
29
- enqueueCommand(context.worldId, 'cmd-model-create', {
30
- windowId: context.worldId,
31
- modelId,
32
- geometryId: intent.props.geometryId,
33
- materialId: intent.props.materialId,
34
- transform: Array.from(transform),
35
- layerMask: 0x7fffffff,
36
- castShadow,
37
- receiveShadow,
38
- castOutline,
39
- outlineColor: [...outlineColor] as [number, number, number, number],
40
- });
41
-
42
- let store = world.components.get(intent.entityId);
43
- if (!store) {
44
- store = new Map();
45
- world.components.set(intent.entityId, store);
46
- }
47
- store.set('Model', {
48
- type: 'Model',
49
- id: modelId,
50
- geometryId: intent.props.geometryId,
51
- materialId: intent.props.materialId,
52
- castShadow: intent.props.castShadow ?? true,
53
- receiveShadow: intent.props.receiveShadow ?? true,
54
- castOutline,
55
- outlineColor: [...outlineColor] as [number, number, number, number],
56
- skipUpdate: true,
57
- });
58
-
59
- intentsToRemove.push(i);
60
- } else if (intent.type === 'attach-camera') {
61
- const cameraId = world.nextCoreId++;
62
- const transform = getEntityTransformMatrix(world, intent.entityId);
63
-
64
- enqueueCommand(context.worldId, 'cmd-camera-create', {
65
- cameraId,
66
- label: `Cam ${cameraId}`,
67
- kind: intent.props.kind ?? ('perspective' as CameraKind),
68
- flags: 0,
69
- nearFar: [intent.props.near ?? 0.1, intent.props.far ?? 1000],
70
- order: intent.props.order ?? 0,
71
- transform: Array.from(transform),
72
- layerMask: 0x7fffffff,
73
- orthoScale: 1.0,
74
- });
75
-
76
- let store = world.components.get(intent.entityId);
77
- if (!store) {
78
- store = new Map();
79
- world.components.set(intent.entityId, store);
80
- }
81
- store.set('Camera', {
82
- type: 'Camera',
83
- id: cameraId,
84
- kind: intent.props.kind ?? ('perspective' as CameraKind),
85
- near: intent.props.near ?? 0.1,
86
- far: intent.props.far ?? 1000,
87
- order: intent.props.order ?? 0,
88
- orthoScale: 1.0,
89
- skipUpdate: true,
90
- });
91
-
92
- intentsToRemove.push(i);
93
- } else if (intent.type === 'attach-light') {
94
- const lightId = world.nextCoreId++;
95
- const transform = getEntityTransformMatrix(world, intent.entityId);
96
-
97
- const pos = vec3.create();
98
- mat4.getTranslation(pos, transform);
99
- const direction = intent.props.direction
100
- ? toVec3(intent.props.direction)
101
- : ([0, 0, -1] as [number, number, number]);
102
- const color = intent.props.color
103
- ? toVec3(intent.props.color)
104
- : ([1, 1, 1] as [number, number, number]);
105
-
106
- const lightCmd: {
107
- windowId: number;
108
- lightId: number;
109
- kind: LightKind;
110
- color: [number, number, number, number];
111
- intensity: number;
112
- range: number;
113
- castShadow: boolean;
114
- position: [number, number, number, number];
115
- layerMask: number;
116
- direction?: [number, number, number, number];
117
- spotInnerOuter?: [number, number];
118
- } = {
119
- windowId: context.worldId,
120
- lightId,
121
- kind: intent.props.kind ?? ('directional' as LightKind),
122
- color: [...color, 1] as [number, number, number, number],
123
- intensity: intent.props.intensity ?? 1.0,
124
- range: intent.props.range ?? 10.0,
125
- castShadow: intent.props.castShadow ?? true,
126
- position: [pos[0], pos[1], pos[2], 1],
127
- layerMask: 0x7fffffff,
128
- };
129
- if (direction) {
130
- const [dirX, dirY, dirZ] = direction;
131
- lightCmd.direction = [dirX, dirY, dirZ, 0];
132
- }
133
- if (intent.props.spotInnerOuter) {
134
- lightCmd.spotInnerOuter = toVec2(intent.props.spotInnerOuter);
135
- }
136
- enqueueCommand(context.worldId, 'cmd-light-create', lightCmd);
137
-
138
- let store = world.components.get(intent.entityId);
139
- if (!store) {
140
- store = new Map();
141
- world.components.set(intent.entityId, store);
142
- }
143
- store.set('Light', {
144
- type: 'Light',
145
- id: lightId,
146
- kind: intent.props.kind ?? ('directional' as LightKind),
147
- color,
148
- intensity: intent.props.intensity ?? 1.0,
149
- range: intent.props.range ?? 10.0,
150
- castShadow: intent.props.castShadow ?? true,
151
- direction,
152
- spotInnerOuter: intent.props.spotInnerOuter
153
- ? toVec2(intent.props.spotInnerOuter)
154
- : [0.2, 0.6],
155
- skipUpdate: true,
156
- });
157
-
158
- intentsToRemove.push(i);
159
- } else if (intent.type === 'update-transform') {
160
- const store = world.components.get(intent.entityId);
161
- if (store) {
162
- const transform = store.get('Transform') as
163
- | TransformComponent
164
- | undefined;
165
- if (transform) {
166
- const nextProps = { ...intent.props };
167
- if (nextProps.position) {
168
- nextProps.position = toVec3(nextProps.position);
169
- }
170
- if (nextProps.rotation) {
171
- nextProps.rotation = toQuat(nextProps.rotation);
172
- }
173
- if (nextProps.scale) {
174
- nextProps.scale = toVec3(nextProps.scale);
175
- }
176
- Object.assign(transform, nextProps);
177
- const matrix = getEntityTransformMatrix(world, intent.entityId);
178
- const matrixArray = Array.from(matrix);
179
-
180
- const model = store.get('Model') as ModelComponent | undefined;
181
- if (model) {
182
- if (model.skipUpdate) {
183
- model.skipUpdate = false;
184
- } else {
185
- enqueueCommand(context.worldId, 'cmd-model-update', {
186
- windowId: context.worldId,
187
- modelId: model.id,
188
- transform: matrixArray,
189
- });
190
- }
191
- }
192
- const camera = store.get('Camera') as CameraComponent | undefined;
193
- if (camera) {
194
- if (camera.skipUpdate) {
195
- camera.skipUpdate = false;
196
- } else {
197
- enqueueCommand(context.worldId, 'cmd-camera-update', {
198
- cameraId: camera.id,
199
- transform: matrixArray,
200
- });
201
- }
202
- }
203
- const light = store.get('Light') as LightComponent | undefined;
204
- if (light) {
205
- if (light.skipUpdate) {
206
- light.skipUpdate = false;
207
- } else {
208
- const pos = vec3.create();
209
- mat4.getTranslation(pos, matrix);
210
- enqueueCommand(context.worldId, 'cmd-light-update', {
211
- windowId: context.worldId,
212
- lightId: light.id,
213
- position: [pos[0], pos[1], pos[2], 1],
214
- });
215
- }
216
- }
217
- }
218
- }
219
- intentsToRemove.push(i);
220
- } else if (intent.type === 'detach-component') {
221
- const store = world.components.get(intent.entityId);
222
- if (store) {
223
- const comp = store.get(intent.componentType) as Component | undefined;
224
- if (comp && 'id' in comp) {
225
- if (intent.componentType === 'Model') {
226
- const modelComp = comp as ModelComponent;
227
- enqueueCommand(context.worldId, 'cmd-model-dispose', {
228
- windowId: context.worldId,
229
- modelId: modelComp.id,
230
- });
231
- } else if (intent.componentType === 'Camera') {
232
- const cameraComp = comp as CameraComponent;
233
- enqueueCommand(context.worldId, 'cmd-camera-dispose', {
234
- cameraId: cameraComp.id,
235
- });
236
- } else if (intent.componentType === 'Light') {
237
- const lightComp = comp as LightComponent;
238
- enqueueCommand(context.worldId, 'cmd-light-dispose', {
239
- windowId: context.worldId,
240
- lightId: lightComp.id,
241
- });
242
- }
243
- }
244
- store.delete(intent.componentType);
245
- }
246
- intentsToRemove.push(i);
247
- } else if (intent.type === 'gizmo-draw-line') {
248
- enqueueCommand(context.worldId, 'cmd-gizmo-draw-line', {
249
- start: toVec3(intent.start),
250
- end: toVec3(intent.end),
251
- color: toVec4(intent.color),
252
- });
253
- intentsToRemove.push(i);
254
- } else if (intent.type === 'gizmo-draw-aabb') {
255
- enqueueCommand(context.worldId, 'cmd-gizmo-draw-aabb', {
256
- min: toVec3(intent.min),
257
- max: toVec3(intent.max),
258
- color: toVec4(intent.color),
259
- });
260
- intentsToRemove.push(i);
261
- }
262
- }
263
-
264
- for (let i = intentsToRemove.length - 1; i >= 0; i--) {
265
- const idx = intentsToRemove[i];
266
- if (idx !== undefined) world.pendingIntents.splice(idx, 1);
267
- }
268
- };
1
+ /**
2
+ * Backward-compatible module shim.
3
+ *
4
+ * The canonical implementation now lives in `scene-sync.ts`.
5
+ */
6
+ export {
7
+ CoreCommandBuilderSystem,
8
+ SceneSyncSystem,
9
+ } from './scene-sync';
@@ -2,41 +2,32 @@ import type { EngineCmd } from '../../types/cmds';
2
2
  import { enqueueCommand } from '../bridge/dispatch';
3
3
  import type { System } from '../ecs';
4
4
 
5
+ /**
6
+ * Handles diagnostic intents that query core-side resource lists.
7
+ *
8
+ * This system is intentionally narrow: it transforms
9
+ * `request-resource-list` intents into typed `cmd-*-list` commands.
10
+ */
5
11
  export const DiagnosticsSystem: System = (world, context) => {
6
- const intentsToRemove: number[] = [];
7
-
8
- for (let i = 0; i < world.pendingIntents.length; i++) {
9
- const intent = world.pendingIntents[i];
12
+ const intents = world.intentStore.take('request-resource-list');
13
+ for (let i = 0; i < intents.length; i++) {
14
+ const intent = intents[i];
10
15
  if (intent?.type === 'request-resource-list') {
16
+ let windowId = world.primaryWindowId;
17
+ if (windowId === undefined) {
18
+ for (const boundWindowId of world.targetWindowBindings.values()) {
19
+ windowId = boundWindowId;
20
+ break;
21
+ }
22
+ }
23
+ if (windowId === undefined) {
24
+ continue;
25
+ }
11
26
  const type = intent.resourceType;
12
27
  const cmdType = `cmd-${type}-list` as EngineCmd['type'];
13
28
  enqueueCommand(context.worldId, cmdType, {
14
- windowId: context.worldId,
29
+ windowId,
15
30
  });
16
- intentsToRemove.push(i);
17
- }
18
- }
19
-
20
- for (let i = intentsToRemove.length - 1; i >= 0; i--) {
21
- const idx = intentsToRemove[i];
22
- if (idx !== undefined) world.pendingIntents.splice(idx, 1);
23
- }
24
-
25
- // Simple diagnostic log every 600 frames
26
- if (world.entities.size > 0 && world.nextCoreId > 2) {
27
- // We only log if there is something interesting
28
- const globalCounters = globalThis as unknown as Record<string, number>;
29
- const frame = globalCounters.vulframFrameCount || 0;
30
- globalCounters.vulframFrameCount = frame + 1;
31
-
32
- if (frame % 600 === 0) {
33
- console.debug(
34
- `[Diagnostics] World ${context.worldId} | Entities: ${
35
- world.entities.size
36
- } | Intents: ${world.pendingIntents.length} | Core objects: ${
37
- world.nextCoreId - 2
38
- }`,
39
- );
40
31
  }
41
32
  }
42
33
  };
@@ -1,7 +1,9 @@
1
1
  export { InputMirrorSystem } from './input-mirror';
2
2
  export { CommandIntentSystem } from './command-intent';
3
- export { CoreCommandBuilderSystem } from './core-command-builder';
3
+ export { ConstraintSolveSystem } from './constraint-solve';
4
+ export { SceneSyncSystem, CoreCommandBuilderSystem } from './scene-sync';
4
5
  export { ResourceUploadSystem } from './resource-upload';
6
+ export { UiBridgeSystem } from './ui-bridge';
5
7
  export { ResponseDecodeSystem } from './response-decode';
6
8
  export { WorldLifecycleSystem } from './world-lifecycle';
7
9
  export { DiagnosticsSystem } from './diagnostics';