@vulfram/engine 0.5.8-alpha → 0.17.1-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 (58) hide show
  1. package/README.md +106 -0
  2. package/package.json +55 -4
  3. package/src/core.ts +14 -0
  4. package/src/ecs.ts +1 -0
  5. package/src/engine/api.ts +234 -23
  6. package/src/engine/bridge/dispatch.ts +265 -40
  7. package/src/engine/bridge/guards.ts +4 -1
  8. package/src/engine/bridge/protocol.ts +72 -54
  9. package/src/engine/ecs/index.ts +187 -42
  10. package/src/engine/state.ts +133 -2
  11. package/src/engine/systems/command-intent.ts +153 -3
  12. package/src/engine/systems/constraint-solve.ts +167 -0
  13. package/src/engine/systems/core-command-builder.ts +9 -265
  14. package/src/engine/systems/diagnostics.ts +20 -19
  15. package/src/engine/systems/index.ts +3 -1
  16. package/src/engine/systems/input-mirror.ts +101 -3
  17. package/src/engine/systems/resource-upload.ts +96 -44
  18. package/src/engine/systems/response-decode.ts +69 -15
  19. package/src/engine/systems/scene-sync.ts +306 -0
  20. package/src/engine/systems/ui-bridge.ts +360 -0
  21. package/src/engine/systems/utils.ts +43 -1
  22. package/src/engine/systems/world-lifecycle.ts +72 -103
  23. package/src/engine/window/manager.ts +168 -0
  24. package/src/engine/world/entities.ts +931 -33
  25. package/src/engine/world/mount.ts +174 -0
  26. package/src/engine/world/types.ts +71 -0
  27. package/src/engine/world/world-ui.ts +266 -0
  28. package/src/engine/world/world3d.ts +280 -0
  29. package/src/index.ts +30 -1
  30. package/src/mount.ts +2 -0
  31. package/src/types/cmds/audio.ts +189 -0
  32. package/src/types/cmds/camera.ts +18 -13
  33. package/src/types/cmds/environment.ts +47 -4
  34. package/src/types/cmds/geometry.ts +18 -16
  35. package/src/types/cmds/index.ts +203 -132
  36. package/src/types/cmds/light.ts +17 -13
  37. package/src/types/cmds/material.ts +14 -13
  38. package/src/types/cmds/model.ts +40 -16
  39. package/src/types/cmds/realm.ts +25 -0
  40. package/src/types/cmds/render-graph.ts +49 -0
  41. package/src/types/cmds/resources.ts +4 -0
  42. package/src/types/cmds/shadow.ts +7 -7
  43. package/src/types/cmds/system.ts +29 -0
  44. package/src/types/cmds/target.ts +82 -0
  45. package/src/types/cmds/texture.ts +19 -5
  46. package/src/types/cmds/ui.ts +220 -0
  47. package/src/types/cmds/window.ts +41 -204
  48. package/src/types/events/index.ts +4 -1
  49. package/src/types/events/pointer.ts +42 -13
  50. package/src/types/events/system.ts +150 -7
  51. package/src/types/events/ui.ts +21 -0
  52. package/src/types/index.ts +1 -0
  53. package/src/types/json.ts +15 -0
  54. package/src/types/kinds.ts +3 -0
  55. package/src/window.ts +8 -0
  56. package/src/world-ui.ts +2 -0
  57. package/src/world3d.ts +10 -0
  58. package/tsconfig.json +0 -29
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @vulfram/engine
2
+
3
+ Functional engine for the Vulfram core, focused on a simple API for creating worlds, entities, and mounting to targets.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @vulfram/engine @vulfram/transport-wasm
9
+ ```
10
+
11
+ Use the transport that fits your environment (`transport-wasm`, `transport-bun`, `transport-napi`).
12
+
13
+ ## Simple example
14
+
15
+ ```ts
16
+ import { initEngine, tick } from '@vulfram/engine/core';
17
+ import { createWindow } from '@vulfram/engine/window';
18
+ import { mountWorldToWindow } from '@vulfram/engine/mount';
19
+ import * as World3D from '@vulfram/engine/world3d';
20
+ import { transportWasm } from '@vulfram/transport-wasm';
21
+
22
+ initEngine({ transport: transportWasm });
23
+
24
+ const world = World3D.create3DWorld();
25
+ const { windowId } = createWindow({
26
+ title: 'Vulfram Engine - Simple Demo',
27
+ size: [1280, 720],
28
+ position: [100, 100],
29
+ initialState: 'windowed',
30
+ });
31
+
32
+ mountWorldToWindow(world, windowId);
33
+
34
+ const camera = World3D.create3DEntity(world);
35
+ World3D.create3DCamera(world, camera, {
36
+ kind: 'perspective',
37
+ near: 0.1,
38
+ far: 100,
39
+ order: 0,
40
+ });
41
+ World3D.update3DTransform(world, camera, {
42
+ position: [0, 1.2, 4],
43
+ rotation: [0, 0, 0, 1],
44
+ scale: [1, 1, 1],
45
+ });
46
+
47
+ const light = World3D.create3DEntity(world);
48
+ World3D.create3DLight(world, light, {
49
+ kind: 'directional',
50
+ color: [1, 1, 1],
51
+ intensity: 2,
52
+ });
53
+ World3D.update3DTransform(world, light, {
54
+ position: [3, 6, 2],
55
+ rotation: [0, 0, 0, 1],
56
+ scale: [1, 1, 1],
57
+ });
58
+
59
+ const geom = World3D.create3DGeometry(world, { type: 'primitive', shape: 'cube' });
60
+ const mat = World3D.create3DMaterial(world, {
61
+ kind: 'standard',
62
+ options: {
63
+ type: 'standard',
64
+ content: {
65
+ baseColor: [0.9, 0.2, 0.2, 1],
66
+ surfaceType: 'opaque',
67
+ flags: 0,
68
+ },
69
+ },
70
+ });
71
+
72
+ const cube = World3D.create3DEntity(world);
73
+ World3D.create3DModel(world, cube, { geometryId: geom, materialId: mat });
74
+ World3D.update3DTransform(world, cube, {
75
+ position: [0, 0, 0],
76
+ rotation: [0, 0, 0, 1],
77
+ scale: [1, 1, 1],
78
+ });
79
+
80
+ let last = performance.now();
81
+ function frame(now: number) {
82
+ const dt = now - last;
83
+ last = now;
84
+ tick(now, dt);
85
+ requestAnimationFrame(frame);
86
+ }
87
+ requestAnimationFrame(frame);
88
+ ```
89
+
90
+ ## Public module structure
91
+
92
+ - `@vulfram/engine/core`: init, tick, dispose, system/component registration
93
+ - `@vulfram/engine/window`: window APIs
94
+ - `@vulfram/engine/world3d`: 3D world APIs
95
+ - `@vulfram/engine/world-ui`: UI world APIs
96
+ - `@vulfram/engine/mount`: world binding to targets/windows
97
+ - `@vulfram/engine/ecs`: ECS types
98
+ - `@vulfram/engine/types`: command/event types
99
+
100
+ ## Documentation
101
+
102
+ Temporary documentation URL:
103
+
104
+ - https://vulppi.dev/vulfram/docs
105
+
106
+ When the final engine documentation URL is available, this README will be updated.
package/package.json CHANGED
@@ -1,17 +1,68 @@
1
1
  {
2
2
  "name": "@vulfram/engine",
3
- "version": "0.5.8-alpha",
3
+ "version": "0.17.1-alpha",
4
4
  "module": "src/index.ts",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
7
7
  "type": "module",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "default": "./src/index.ts",
12
+ "import": "./src/index.ts",
13
+ "types": "./src/index.ts"
14
+ },
15
+ "./core": {
16
+ "default": "./src/core.ts",
17
+ "import": "./src/core.ts",
18
+ "types": "./src/core.ts"
19
+ },
20
+ "./window": {
21
+ "default": "./src/window.ts",
22
+ "import": "./src/window.ts",
23
+ "types": "./src/window.ts"
24
+ },
25
+ "./mount": {
26
+ "default": "./src/mount.ts",
27
+ "import": "./src/mount.ts",
28
+ "types": "./src/mount.ts"
29
+ },
30
+ "./world3d": {
31
+ "default": "./src/world3d.ts",
32
+ "import": "./src/world3d.ts",
33
+ "types": "./src/world3d.ts"
34
+ },
35
+ "./world-ui": {
36
+ "default": "./src/world-ui.ts",
37
+ "import": "./src/world-ui.ts",
38
+ "types": "./src/world-ui.ts"
39
+ },
40
+ "./ecs": {
41
+ "default": "./src/ecs.ts",
42
+ "import": "./src/ecs.ts",
43
+ "types": "./src/ecs.ts"
44
+ },
45
+ "./types": {
46
+ "default": "./src/types/index.ts",
47
+ "import": "./src/types/index.ts",
48
+ "types": "./src/types/index.ts"
49
+ },
50
+ "./package.json": "./package.json"
51
+ },
52
+ "files": [
53
+ "src",
54
+ "package.json"
55
+ ],
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
8
59
  "dependencies": {
9
- "@vulfram/transport-types": "^0.2.2",
60
+ "@vulfram/transport-types": "^0.2.5",
10
61
  "gl-matrix": "^3.4.4",
11
- "glob": "^13.0.0",
62
+ "glob": "^13.0.6",
12
63
  "msgpackr": "^1.11.8"
13
64
  },
14
65
  "devDependencies": {
15
- "@types/bun": "^1.3.8"
66
+ "@types/bun": "^1.3.10"
16
67
  }
17
68
  }
package/src/core.ts ADDED
@@ -0,0 +1,14 @@
1
+ export {
2
+ disposeEngine,
3
+ initEngine,
4
+ registerComponent,
5
+ registerSystem,
6
+ tick,
7
+ uploadBuffer,
8
+ } from './engine/api';
9
+ export { EngineError } from './engine/errors';
10
+ export type {
11
+ BufferResult,
12
+ EngineTransport,
13
+ EngineTransportFactory,
14
+ } from '@vulfram/transport-types';
package/src/ecs.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './engine/ecs';
package/src/engine/api.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { EngineTransportFactory } from '@vulfram/transport-types';
2
2
  import {
3
3
  collectCommands,
4
+ markRoutingIndexDirty,
4
5
  routeEvents,
5
6
  routeResponses,
6
7
  } from './bridge/dispatch';
@@ -15,8 +16,57 @@ import { EngineError } from './errors';
15
16
  import { engineState, REQUIRED_SYSTEMS, type WorldState } from './state';
16
17
  import * as CoreSystems from './systems';
17
18
  import type { UploadType } from '../types/kinds';
19
+ import type { RealmKind } from '../types/cmds/realm';
20
+ import { asWorldId, type WorldId } from './world/types';
18
21
 
19
- export * from './world/entities';
22
+ export * from './window/manager';
23
+
24
+ function recalculateWorldWindowBindings(world: WorldState): void {
25
+ world.boundWindowIds.clear();
26
+ for (const windowId of world.targetWindowBindings.values()) {
27
+ world.boundWindowIds.add(windowId);
28
+ }
29
+
30
+ if (world.boundWindowIds.size === 0) {
31
+ world.primaryWindowId = undefined;
32
+ return;
33
+ }
34
+
35
+ let primary = Number.POSITIVE_INFINITY;
36
+ for (const windowId of world.boundWindowIds) {
37
+ if (windowId < primary) {
38
+ primary = windowId;
39
+ }
40
+ }
41
+ world.primaryWindowId = primary;
42
+ }
43
+
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
+ /**
58
+ * Shared realm creation options used by `createWorld3D` and `createWorldUI`.
59
+ */
60
+ export type CreateWorldOptions = {
61
+ /** Optional output surface id when provided by host integration. */
62
+ outputSurfaceId?: number;
63
+ /** Core scheduling hint for realm priority. */
64
+ importance?: number;
65
+ /** Core-side cache policy value. */
66
+ cachePolicy?: number;
67
+ /** Bit flags forwarded to realm creation in core. */
68
+ flags?: number;
69
+ };
20
70
 
21
71
  /**
22
72
  * Initializes the engine runtime and registers core systems.
@@ -35,9 +85,24 @@ export function initEngine(config: {
35
85
  // Reset Engine State for fresh start (important if re-initializing after dispose)
36
86
  engineState.worlds.clear();
37
87
  engineState.commandBatch = [];
88
+ engineState.usedWindowIds.clear();
89
+ engineState.confirmedWindowIds.clear();
90
+ engineState.pendingWindowCreateByCommandId.clear();
91
+ engineState.pendingWindowCloseByCommandId.clear();
92
+ engineState.globalPendingCommands = [];
93
+ engineState.globalPendingCommandsHead = 0;
38
94
  engineState.commandTracker.clear();
95
+ engineState.globalCommandTracker.clear();
96
+ engineState.globalInboundResponses = [];
97
+ engineState.nextWorldId = 1;
98
+ engineState.nextWindowId = 1;
39
99
  engineState.nextEntityId = 1;
40
100
  engineState.nextCommandId = 1;
101
+ engineState.nextGlobalId = 100;
102
+ engineState.routingIndex.byWindowId.clear();
103
+ engineState.routingIndex.byRealmId.clear();
104
+ engineState.routingIndex.byTargetId.clear();
105
+ engineState.routingIndex.dirty = true;
41
106
  engineState.registry.systems.input = [];
42
107
  engineState.registry.systems.update = [];
43
108
  engineState.registry.systems.preRender = [];
@@ -60,9 +125,11 @@ export function initEngine(config: {
60
125
  // Register Core Systems
61
126
  registerSystem('input', CoreSystems.InputMirrorSystem);
62
127
  registerSystem('update', CoreSystems.CommandIntentSystem);
128
+ registerSystem('update', CoreSystems.UiBridgeSystem);
63
129
  registerSystem('update', CoreSystems.WorldLifecycleSystem);
64
130
  registerSystem('update', CoreSystems.ResourceUploadSystem);
65
- registerSystem('preRender', CoreSystems.CoreCommandBuilderSystem);
131
+ registerSystem('preRender', CoreSystems.ConstraintSolveSystem);
132
+ registerSystem('preRender', CoreSystems.SceneSyncSystem);
66
133
  registerSystem('postRender', CoreSystems.ResponseDecodeSystem);
67
134
  registerSystem('postRender', CoreSystems.DiagnosticsSystem);
68
135
  }
@@ -80,7 +147,19 @@ export function disposeEngine(): void {
80
147
  engineState.transport = null;
81
148
  engineState.worlds.clear();
82
149
  engineState.commandBatch = [];
150
+ engineState.usedWindowIds.clear();
151
+ engineState.confirmedWindowIds.clear();
152
+ engineState.pendingWindowCreateByCommandId.clear();
153
+ engineState.pendingWindowCloseByCommandId.clear();
154
+ engineState.globalPendingCommands = [];
155
+ engineState.globalPendingCommandsHead = 0;
83
156
  engineState.commandTracker.clear();
157
+ engineState.globalCommandTracker.clear();
158
+ engineState.globalInboundResponses = [];
159
+ engineState.routingIndex.byWindowId.clear();
160
+ engineState.routingIndex.byRealmId.clear();
161
+ engineState.routingIndex.byTargetId.clear();
162
+ engineState.routingIndex.dirty = true;
84
163
  engineState.status = 'disposed';
85
164
  }
86
165
 
@@ -93,7 +172,13 @@ export function registerComponent(name: string, schema: ComponentSchema): void {
93
172
  }
94
173
 
95
174
  /**
96
- * Registers a system to run at a specific pipeline step.
175
+ * Registers a custom system into the engine pipeline.
176
+ *
177
+ * Stage semantics:
178
+ * - `input`: consume mirrored inbound events/state for the current frame.
179
+ * - `update`: mutate ECS state and emit intents.
180
+ * - `preRender`: resolve constraints and emit core commands.
181
+ * - `postRender`: consume command responses and diagnostics.
97
182
  */
98
183
  export function registerSystem(step: SystemStep, system: System): void {
99
184
  requireInitialized();
@@ -119,12 +204,6 @@ function uploadTypeToId(type: UploadType): number {
119
204
  }
120
205
  }
121
206
 
122
- /**
123
- * Uploads a raw buffer to the engine's GPU memory or internal storage.
124
- * @param bufferId Unique ID for this buffer.
125
- * @param type Type of data in the buffer (ImageData, VertexData, etc).
126
- * @param data The raw binary data.
127
- */
128
207
  /**
129
208
  * Uploads a raw buffer to the core for later use (textures, geometry, etc.).
130
209
  */
@@ -148,17 +227,41 @@ export function uploadBuffer(
148
227
  }
149
228
  }
150
229
 
151
- /**
152
- * Creates a world bound to a window ID. Returns the world ID.
153
- */
154
- export function createWorld(windowId: number): number {
230
+ function createRealmWorld(kind: RealmKind, config: CreateWorldOptions = {}): WorldId {
155
231
  requireInitialized();
156
- if (engineState.worlds.has(windowId)) {
157
- throw new EngineError('WorldExists', `World ${windowId} already exists.`);
158
- }
232
+
233
+ 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();
159
238
 
160
239
  const world: WorldState = {
161
- windowId,
240
+ worldId,
241
+ realmKind: kind,
242
+ primaryWindowId: undefined,
243
+ boundWindowIds: new Set(),
244
+ targetLayerBindings: new Map(),
245
+ targetWindowBindings: new Map(),
246
+ coreRealmId: undefined,
247
+ realmCreateArgs: {
248
+ kind,
249
+ hostWindowId: inferredHostWindowId,
250
+ outputSurfaceId: config.outputSurfaceId,
251
+ importance: config.importance,
252
+ cachePolicy: config.cachePolicy,
253
+ flags: config.flags,
254
+ },
255
+ coreSurfaceId: undefined,
256
+ corePresentId: undefined,
257
+ resolvedEntityTransforms: new Map(),
258
+ constraintDirtyEntities: new Set(),
259
+ constraintScratchResolved: new Map(),
260
+ constraintScratchVisiting: new Set(),
261
+ constraintChangedEntities: new Set(),
262
+ constraintChildrenByParent: new Map(),
263
+ constraintParentByChild: new Map(),
264
+ sceneSyncMatrixScratch: new Map(),
162
265
  entities: new Set(),
163
266
  components: new Map(),
164
267
  nextCoreId: 100,
@@ -166,18 +269,72 @@ export function createWorld(windowId: number): number {
166
269
  pendingIntents: [],
167
270
  internalEvents: [],
168
271
  pendingCommands: [],
272
+ pendingCommandsHead: 0,
169
273
  inboundEvents: [],
170
274
  inboundResponses: [],
275
+ realmCreateRetryCount: 0,
276
+ nextRealmCreateRetryAtMs: 0,
171
277
  };
172
278
 
173
- engineState.worlds.set(windowId, world);
174
- return windowId;
279
+ engineState.worlds.set(worldId, world);
280
+ markRoutingIndexDirty();
281
+ world.pendingCommands.push({
282
+ id: engineState.nextCommandId++,
283
+ type: 'cmd-realm-create',
284
+ content: world.realmCreateArgs,
285
+ });
286
+ return asWorldId(worldId);
287
+ }
288
+
289
+ /**
290
+ * Creates a `three-d` realm world and queues `cmd-realm-create`.
291
+ *
292
+ * This function only allocates local runtime state and enqueues the create command.
293
+ * The core realm is resolved asynchronously after at least one `tick`.
294
+ *
295
+ * Preconditions:
296
+ * - `initEngine` must have been called.
297
+ *
298
+ * Side effects:
299
+ * - Allocates a new world ID.
300
+ * - Registers the world in engine state.
301
+ * - Enqueues `cmd-realm-create` for the new world.
302
+ *
303
+ * @param config Optional create options.
304
+ * @returns Numeric world ID associated with a core `three-d` realm.
305
+ */
306
+ export function createWorld3D(config?: CreateWorldOptions): WorldId {
307
+ return createRealmWorld('three-d', config);
308
+ }
309
+
310
+ /**
311
+ * Creates a `two-d` realm world for UI-centric pipelines and queues `cmd-realm-create`.
312
+ *
313
+ * This world type is intended for the dedicated WorldUI functional APIs.
314
+ * The core realm is resolved asynchronously after at least one `tick`.
315
+ *
316
+ * Preconditions:
317
+ * - `initEngine` must have been called.
318
+ *
319
+ * Side effects:
320
+ * - Allocates a new world ID.
321
+ * - Registers the world in engine state.
322
+ * - Enqueues `cmd-realm-create` for the new world.
323
+ *
324
+ * @param config Optional create options.
325
+ * @returns Numeric world ID associated with a core `two-d` realm.
326
+ */
327
+ export function createWorldUI(config?: CreateWorldOptions): WorldId {
328
+ return createRealmWorld('two-d', config);
175
329
  }
176
330
 
177
331
  /**
178
- * Main engine tick.
179
- * Orchestrates event routing, world updates, command collection, and core synchronization.
332
+ * Creates a default `three-d` world.
180
333
  */
334
+ export function createWorld(config?: CreateWorldOptions): WorldId {
335
+ return createWorld3D(config);
336
+ }
337
+
181
338
  /**
182
339
  * Advances the engine by one frame.
183
340
  * Call once per frame with monotonic time and delta in milliseconds.
@@ -185,6 +342,8 @@ export function createWorld(windowId: number): number {
185
342
  export function tick(timeMs: number, deltaMs: number): void {
186
343
  requireInitialized();
187
344
  const transport = engineState.transport!;
345
+ const coreTimeMs = Math.max(0, Math.floor(timeMs));
346
+ const coreDeltaMs = Math.max(0, Math.floor(deltaMs));
188
347
 
189
348
  // 1. Engine Phase: Receive from Core (Input Pipeline)
190
349
  // We process events and responses received since last frame
@@ -199,9 +358,10 @@ export function tick(timeMs: number, deltaMs: number): void {
199
358
  const responses = deserializeResponses(responsesResult.buffer);
200
359
  routeResponses(responses);
201
360
  }
361
+ processGlobalResponses();
202
362
 
203
363
  // 2. Engine Phase: Clock & Context Preparation
204
- const cappedDelta = Math.min(deltaMs, 100);
364
+ const cappedDelta = Math.min(coreDeltaMs, 100);
205
365
  engineState.clock.lastTime = timeMs;
206
366
  engineState.clock.lastDelta = cappedDelta;
207
367
  engineState.clock.frameCount++;
@@ -238,6 +398,14 @@ export function tick(timeMs: number, deltaMs: number): void {
238
398
  );
239
399
  for (const cmd of engineState.commandBatch) {
240
400
  engineState.commandTracker.delete(cmd.id);
401
+ engineState.globalCommandTracker.delete(cmd.id);
402
+ const pendingCreateWindowId =
403
+ engineState.pendingWindowCreateByCommandId.get(cmd.id);
404
+ if (pendingCreateWindowId !== undefined) {
405
+ engineState.usedWindowIds.delete(pendingCreateWindowId);
406
+ engineState.pendingWindowCreateByCommandId.delete(cmd.id);
407
+ }
408
+ engineState.pendingWindowCloseByCommandId.delete(cmd.id);
241
409
  }
242
410
  if (engineState.flags.debugEnabled) {
243
411
  console.group('[Vulfram Debug] Failed Batch');
@@ -254,7 +422,50 @@ export function tick(timeMs: number, deltaMs: number): void {
254
422
  }
255
423
 
256
424
  // 5. Core Phase: Execute Tick
257
- transport.vulframTick(timeMs, deltaMs);
425
+ transport.vulframTick(coreTimeMs, coreDeltaMs);
426
+ }
427
+
428
+ function processGlobalResponses(): void {
429
+ for (let i = 0; i < engineState.globalInboundResponses.length; i++) {
430
+ const res = engineState.globalInboundResponses[i]!;
431
+ const content = res.content as { success?: boolean; message?: string };
432
+ if (res.type === 'window-create') {
433
+ const pendingWindowId = engineState.pendingWindowCreateByCommandId.get(res.id);
434
+ if (pendingWindowId !== undefined) {
435
+ if (content.success) {
436
+ engineState.confirmedWindowIds.add(pendingWindowId);
437
+ } else {
438
+ engineState.usedWindowIds.delete(pendingWindowId);
439
+ }
440
+ engineState.pendingWindowCreateByCommandId.delete(res.id);
441
+ }
442
+ } else if (res.type === 'window-close') {
443
+ const pendingWindowId = engineState.pendingWindowCloseByCommandId.get(res.id);
444
+ if (pendingWindowId !== undefined) {
445
+ if (content.success) {
446
+ engineState.usedWindowIds.delete(pendingWindowId);
447
+ engineState.confirmedWindowIds.delete(pendingWindowId);
448
+ for (const world of engineState.worlds.values()) {
449
+ for (const [targetId, windowId] of world.targetWindowBindings) {
450
+ if (windowId === pendingWindowId) {
451
+ world.targetWindowBindings.delete(targetId);
452
+ world.targetLayerBindings.delete(targetId);
453
+ }
454
+ }
455
+ recalculateWorldWindowBindings(world);
456
+ }
457
+ markRoutingIndexDirty();
458
+ }
459
+ engineState.pendingWindowCloseByCommandId.delete(res.id);
460
+ }
461
+ }
462
+ if (content && typeof content.success === 'boolean' && !content.success) {
463
+ console.error(
464
+ `[Global] Command ${res.type} (ID: ${res.id}) failed: ${content.message}`,
465
+ );
466
+ }
467
+ }
468
+ engineState.globalInboundResponses.length = 0;
258
469
  }
259
470
 
260
471
  /**