@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
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-browser
9
+ ```
10
+
11
+ Use the transport that fits your environment (`transport-browser`, `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-browser';
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,73 @@
1
1
  {
2
2
  "name": "@vulfram/engine",
3
- "version": "0.14.8-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",
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
+ "./helpers": {
51
+ "default": "./src/helpers/index.ts",
52
+ "import": "./src/helpers/index.ts",
53
+ "types": "./src/helpers/index.ts"
54
+ },
55
+ "./package.json": "./package.json"
56
+ },
57
+ "files": [
58
+ "src",
59
+ "package.json"
60
+ ],
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
8
64
  "dependencies": {
9
- "@vulfram/transport-types": "^0.2.3",
65
+ "@vulfram/transport-types": "^0.2.6",
10
66
  "gl-matrix": "^3.4.4",
11
- "glob": "^13.0.0",
12
67
  "msgpackr": "^1.11.8"
13
68
  },
14
69
  "devDependencies": {
15
- "@types/bun": "^1.3.8"
70
+ "@types/bun": "^1.3.10",
71
+ "glob": "^13.0.6"
16
72
  }
17
73
  }
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';
@@ -13,10 +14,45 @@ import {
13
14
  import type { ComponentSchema, System, SystemContext, SystemStep } from './ecs';
14
15
  import { EngineError } from './errors';
15
16
  import { engineState, REQUIRED_SYSTEMS, type WorldState } from './state';
17
+ import { createIntentStore } from './intents/store';
16
18
  import * as CoreSystems from './systems';
17
19
  import type { UploadType } from '../types/kinds';
20
+ import type { RealmKind } from '../types/cmds/realm';
21
+ import { asWorldId, type WorldId } from './world/types';
18
22
 
19
- export * from './world/entities';
23
+ export * from './window/manager';
24
+
25
+ function recalculateWorldWindowBindings(world: WorldState): void {
26
+ world.boundWindowIds.clear();
27
+ for (const windowId of world.targetWindowBindings.values()) {
28
+ world.boundWindowIds.add(windowId);
29
+ }
30
+
31
+ if (world.boundWindowIds.size === 0) {
32
+ world.primaryWindowId = undefined;
33
+ return;
34
+ }
35
+
36
+ let primary = Number.POSITIVE_INFINITY;
37
+ for (const windowId of world.boundWindowIds) {
38
+ if (windowId < primary) {
39
+ primary = windowId;
40
+ }
41
+ }
42
+ world.primaryWindowId = primary;
43
+ }
44
+
45
+ /**
46
+ * Shared realm creation options used by `createWorld3D` and `createWorldUI`.
47
+ */
48
+ export type CreateWorldOptions = {
49
+ /** Core scheduling hint for realm priority. */
50
+ importance?: number;
51
+ /** Core-side cache policy value. */
52
+ cachePolicy?: number;
53
+ /** Bit flags forwarded to realm creation in core. */
54
+ flags?: number;
55
+ };
20
56
 
21
57
  /**
22
58
  * Initializes the engine runtime and registers core systems.
@@ -35,9 +71,24 @@ export function initEngine(config: {
35
71
  // Reset Engine State for fresh start (important if re-initializing after dispose)
36
72
  engineState.worlds.clear();
37
73
  engineState.commandBatch = [];
74
+ engineState.usedWindowIds.clear();
75
+ engineState.confirmedWindowIds.clear();
76
+ engineState.pendingWindowCreateByCommandId.clear();
77
+ engineState.pendingWindowCloseByCommandId.clear();
78
+ engineState.globalPendingCommands = [];
79
+ engineState.globalPendingCommandsHead = 0;
38
80
  engineState.commandTracker.clear();
81
+ engineState.globalCommandTracker.clear();
82
+ engineState.globalInboundResponses = [];
83
+ engineState.nextWorldId = 1;
84
+ engineState.nextWindowId = 1;
39
85
  engineState.nextEntityId = 1;
40
86
  engineState.nextCommandId = 1;
87
+ engineState.nextGlobalId = 100;
88
+ engineState.routingIndex.byWindowId.clear();
89
+ engineState.routingIndex.byRealmId.clear();
90
+ engineState.routingIndex.byTargetId.clear();
91
+ engineState.routingIndex.dirty = true;
41
92
  engineState.registry.systems.input = [];
42
93
  engineState.registry.systems.update = [];
43
94
  engineState.registry.systems.preRender = [];
@@ -60,9 +111,11 @@ export function initEngine(config: {
60
111
  // Register Core Systems
61
112
  registerSystem('input', CoreSystems.InputMirrorSystem);
62
113
  registerSystem('update', CoreSystems.CommandIntentSystem);
114
+ registerSystem('update', CoreSystems.UiBridgeSystem);
63
115
  registerSystem('update', CoreSystems.WorldLifecycleSystem);
64
116
  registerSystem('update', CoreSystems.ResourceUploadSystem);
65
- registerSystem('preRender', CoreSystems.CoreCommandBuilderSystem);
117
+ registerSystem('preRender', CoreSystems.ConstraintSolveSystem);
118
+ registerSystem('preRender', CoreSystems.SceneSyncSystem);
66
119
  registerSystem('postRender', CoreSystems.ResponseDecodeSystem);
67
120
  registerSystem('postRender', CoreSystems.DiagnosticsSystem);
68
121
  }
@@ -80,7 +133,19 @@ export function disposeEngine(): void {
80
133
  engineState.transport = null;
81
134
  engineState.worlds.clear();
82
135
  engineState.commandBatch = [];
136
+ engineState.usedWindowIds.clear();
137
+ engineState.confirmedWindowIds.clear();
138
+ engineState.pendingWindowCreateByCommandId.clear();
139
+ engineState.pendingWindowCloseByCommandId.clear();
140
+ engineState.globalPendingCommands = [];
141
+ engineState.globalPendingCommandsHead = 0;
83
142
  engineState.commandTracker.clear();
143
+ engineState.globalCommandTracker.clear();
144
+ engineState.globalInboundResponses = [];
145
+ engineState.routingIndex.byWindowId.clear();
146
+ engineState.routingIndex.byRealmId.clear();
147
+ engineState.routingIndex.byTargetId.clear();
148
+ engineState.routingIndex.dirty = true;
84
149
  engineState.status = 'disposed';
85
150
  }
86
151
 
@@ -93,7 +158,13 @@ export function registerComponent(name: string, schema: ComponentSchema): void {
93
158
  }
94
159
 
95
160
  /**
96
- * Registers a system to run at a specific pipeline step.
161
+ * Registers a custom system into the engine pipeline.
162
+ *
163
+ * Stage semantics:
164
+ * - `input`: consume mirrored inbound events/state for the current frame.
165
+ * - `update`: mutate ECS state and emit intents.
166
+ * - `preRender`: resolve constraints and emit core commands.
167
+ * - `postRender`: consume command responses and diagnostics.
97
168
  */
98
169
  export function registerSystem(step: SystemStep, system: System): void {
99
170
  requireInitialized();
@@ -119,12 +190,6 @@ function uploadTypeToId(type: UploadType): number {
119
190
  }
120
191
  }
121
192
 
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
193
  /**
129
194
  * Uploads a raw buffer to the core for later use (textures, geometry, etc.).
130
195
  */
@@ -148,36 +213,111 @@ export function uploadBuffer(
148
213
  }
149
214
  }
150
215
 
151
- /**
152
- * Creates a world bound to a window ID. Returns the world ID.
153
- */
154
- export function createWorld(windowId: number): number {
216
+ function createRealmWorld(
217
+ kind: RealmKind,
218
+ config: CreateWorldOptions = {},
219
+ ): WorldId {
155
220
  requireInitialized();
156
- if (engineState.worlds.has(windowId)) {
157
- throw new EngineError('WorldExists', `World ${windowId} already exists.`);
158
- }
221
+
222
+ const worldId = engineState.nextWorldId++;
159
223
 
160
224
  const world: WorldState = {
161
- windowId,
225
+ worldId,
226
+ realmKind: kind,
227
+ primaryWindowId: undefined,
228
+ boundWindowIds: new Set(),
229
+ targetLayerBindings: new Map(),
230
+ targetWindowBindings: new Map(),
231
+ coreRealmId: undefined,
232
+ realmCreateArgs: {
233
+ kind,
234
+ importance: config.importance,
235
+ cachePolicy: config.cachePolicy,
236
+ flags: config.flags,
237
+ },
238
+ coreSurfaceId: undefined,
239
+ corePresentId: undefined,
240
+ resolvedEntityTransforms: new Map(),
241
+ constraintDirtyEntities: new Set(),
242
+ constraintScratchResolved: new Map(),
243
+ constraintScratchVisiting: new Set(),
244
+ constraintChangedEntities: new Set(),
245
+ constraintChildrenByParent: new Map(),
246
+ constraintParentByChild: new Map(),
247
+ sceneSyncMatrixScratch: new Map(),
162
248
  entities: new Set(),
163
249
  components: new Map(),
164
250
  nextCoreId: 100,
165
251
  systems: [...REQUIRED_SYSTEMS],
166
- pendingIntents: [],
252
+ intentStore: createIntentStore(),
167
253
  internalEvents: [],
168
254
  pendingCommands: [],
255
+ pendingCommandsHead: 0,
169
256
  inboundEvents: [],
170
257
  inboundResponses: [],
258
+ realmCreateRetryCount: 0,
259
+ nextRealmCreateRetryAtMs: 0,
171
260
  };
172
261
 
173
- engineState.worlds.set(windowId, world);
174
- return windowId;
262
+ engineState.worlds.set(worldId, world);
263
+ markRoutingIndexDirty();
264
+ world.pendingCommands.push({
265
+ id: engineState.nextCommandId++,
266
+ type: 'cmd-realm-create',
267
+ content: world.realmCreateArgs,
268
+ });
269
+ return asWorldId(worldId);
270
+ }
271
+
272
+ /**
273
+ * Creates a `three-d` realm world and queues `cmd-realm-create`.
274
+ *
275
+ * This function only allocates local runtime state and enqueues the create command.
276
+ * The core realm is resolved asynchronously after at least one `tick`.
277
+ *
278
+ * Preconditions:
279
+ * - `initEngine` must have been called.
280
+ *
281
+ * Side effects:
282
+ * - Allocates a new world ID.
283
+ * - Registers the world in engine state.
284
+ * - Enqueues `cmd-realm-create` for the new world.
285
+ *
286
+ * @param config Optional create options.
287
+ * @returns Numeric world ID associated with a core `three-d` realm.
288
+ */
289
+ export function createWorld3D(config?: CreateWorldOptions): WorldId {
290
+ return createRealmWorld('three-d', config);
291
+ }
292
+
293
+ /**
294
+ * Creates a `two-d` realm world for UI-centric pipelines and queues `cmd-realm-create`.
295
+ *
296
+ * This world type is intended for the dedicated WorldUI functional APIs.
297
+ * The core realm is resolved asynchronously after at least one `tick`.
298
+ *
299
+ * Preconditions:
300
+ * - `initEngine` must have been called.
301
+ *
302
+ * Side effects:
303
+ * - Allocates a new world ID.
304
+ * - Registers the world in engine state.
305
+ * - Enqueues `cmd-realm-create` for the new world.
306
+ *
307
+ * @param config Optional create options.
308
+ * @returns Numeric world ID associated with a core `two-d` realm.
309
+ */
310
+ export function createWorldUI(config?: CreateWorldOptions): WorldId {
311
+ return createRealmWorld('two-d', config);
175
312
  }
176
313
 
177
314
  /**
178
- * Main engine tick.
179
- * Orchestrates event routing, world updates, command collection, and core synchronization.
315
+ * Creates a default `three-d` world.
180
316
  */
317
+ export function createWorld(config?: CreateWorldOptions): WorldId {
318
+ return createWorld3D(config);
319
+ }
320
+
181
321
  /**
182
322
  * Advances the engine by one frame.
183
323
  * Call once per frame with monotonic time and delta in milliseconds.
@@ -185,6 +325,8 @@ export function createWorld(windowId: number): number {
185
325
  export function tick(timeMs: number, deltaMs: number): void {
186
326
  requireInitialized();
187
327
  const transport = engineState.transport!;
328
+ const coreTimeMs = Math.max(0, Math.floor(timeMs));
329
+ const coreDeltaMs = Math.max(0, Math.floor(deltaMs));
188
330
 
189
331
  // 1. Engine Phase: Receive from Core (Input Pipeline)
190
332
  // We process events and responses received since last frame
@@ -199,9 +341,10 @@ export function tick(timeMs: number, deltaMs: number): void {
199
341
  const responses = deserializeResponses(responsesResult.buffer);
200
342
  routeResponses(responses);
201
343
  }
344
+ processGlobalResponses();
202
345
 
203
346
  // 2. Engine Phase: Clock & Context Preparation
204
- const cappedDelta = Math.min(deltaMs, 100);
347
+ const cappedDelta = Math.min(coreDeltaMs, 100);
205
348
  engineState.clock.lastTime = timeMs;
206
349
  engineState.clock.lastDelta = cappedDelta;
207
350
  engineState.clock.frameCount++;
@@ -238,6 +381,14 @@ export function tick(timeMs: number, deltaMs: number): void {
238
381
  );
239
382
  for (const cmd of engineState.commandBatch) {
240
383
  engineState.commandTracker.delete(cmd.id);
384
+ engineState.globalCommandTracker.delete(cmd.id);
385
+ const pendingCreateWindowId =
386
+ engineState.pendingWindowCreateByCommandId.get(cmd.id);
387
+ if (pendingCreateWindowId !== undefined) {
388
+ engineState.usedWindowIds.delete(pendingCreateWindowId);
389
+ engineState.pendingWindowCreateByCommandId.delete(cmd.id);
390
+ }
391
+ engineState.pendingWindowCloseByCommandId.delete(cmd.id);
241
392
  }
242
393
  if (engineState.flags.debugEnabled) {
243
394
  console.group('[Vulfram Debug] Failed Batch');
@@ -254,7 +405,54 @@ export function tick(timeMs: number, deltaMs: number): void {
254
405
  }
255
406
 
256
407
  // 5. Core Phase: Execute Tick
257
- transport.vulframTick(timeMs, deltaMs);
408
+ transport.vulframTick(coreTimeMs, coreDeltaMs);
409
+ }
410
+
411
+ function processGlobalResponses(): void {
412
+ for (let i = 0; i < engineState.globalInboundResponses.length; i++) {
413
+ const res = engineState.globalInboundResponses[i]!;
414
+ const content = res.content as { success?: boolean; message?: string };
415
+ if (res.type === 'window-create') {
416
+ const pendingWindowId = engineState.pendingWindowCreateByCommandId.get(
417
+ res.id,
418
+ );
419
+ if (pendingWindowId !== undefined) {
420
+ if (content.success) {
421
+ engineState.confirmedWindowIds.add(pendingWindowId);
422
+ } else {
423
+ engineState.usedWindowIds.delete(pendingWindowId);
424
+ }
425
+ engineState.pendingWindowCreateByCommandId.delete(res.id);
426
+ }
427
+ } else if (res.type === 'window-close') {
428
+ const pendingWindowId = engineState.pendingWindowCloseByCommandId.get(
429
+ res.id,
430
+ );
431
+ if (pendingWindowId !== undefined) {
432
+ if (content.success) {
433
+ engineState.usedWindowIds.delete(pendingWindowId);
434
+ engineState.confirmedWindowIds.delete(pendingWindowId);
435
+ for (const world of engineState.worlds.values()) {
436
+ for (const [targetId, windowId] of world.targetWindowBindings) {
437
+ if (windowId === pendingWindowId) {
438
+ world.targetWindowBindings.delete(targetId);
439
+ world.targetLayerBindings.delete(targetId);
440
+ }
441
+ }
442
+ recalculateWorldWindowBindings(world);
443
+ }
444
+ markRoutingIndexDirty();
445
+ }
446
+ engineState.pendingWindowCloseByCommandId.delete(res.id);
447
+ }
448
+ }
449
+ if (content && typeof content.success === 'boolean' && !content.success) {
450
+ console.error(
451
+ `[Global] Command ${res.type} (ID: ${res.id}) failed: ${content.message}`,
452
+ );
453
+ }
454
+ }
455
+ engineState.globalInboundResponses.length = 0;
258
456
  }
259
457
 
260
458
  /**