@fps-games/editor 0.1.4-beta.3 → 0.1.4-beta.5

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 (161) hide show
  1. package/dist/build-info.json +45 -0
  2. package/dist/local-editor-harness.d.ts +47 -6
  3. package/dist/local-editor-harness.d.ts.map +1 -1
  4. package/dist/local-editor-harness.js +315 -21
  5. package/dist/local-editor-harness.js.map +1 -1
  6. package/dist/playable-babylon-facade.d.ts +4 -4
  7. package/dist/playable-babylon-facade.d.ts.map +1 -1
  8. package/dist/playable-babylon-facade.js +2 -2
  9. package/dist/playable-babylon-facade.js.map +1 -1
  10. package/dist/playable-babylon-rendering.d.ts +14 -2
  11. package/dist/playable-babylon-rendering.d.ts.map +1 -1
  12. package/dist/playable-babylon-rendering.js +48 -3
  13. package/dist/playable-babylon-rendering.js.map +1 -1
  14. package/dist/playable-local-editor-host.d.ts +15 -1
  15. package/dist/playable-local-editor-host.d.ts.map +1 -1
  16. package/dist/playable-local-editor-host.js.map +1 -1
  17. package/dist/playable-sdk-vite.d.ts +2 -0
  18. package/dist/playable-sdk-vite.d.ts.map +1 -0
  19. package/dist/playable-sdk-vite.js +2 -0
  20. package/dist/playable-sdk-vite.js.map +1 -0
  21. package/dist/playable-sdk.d.ts +5 -5
  22. package/dist/playable-sdk.d.ts.map +1 -1
  23. package/dist/playable-sdk.js +2 -2
  24. package/dist/playable-sdk.js.map +1 -1
  25. package/node_modules/@fps-games/babylon-renderer/dist/index.d.ts +3 -0
  26. package/node_modules/@fps-games/babylon-renderer/dist/index.d.ts.map +1 -1
  27. package/node_modules/@fps-games/babylon-renderer/dist/index.js +3 -0
  28. package/node_modules/@fps-games/babylon-renderer/dist/index.js.map +1 -1
  29. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-shaders.d.ts +5 -0
  30. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-shaders.d.ts.map +1 -0
  31. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-shaders.js +46 -0
  32. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-shaders.js.map +1 -0
  33. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-system.d.ts +5 -0
  34. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-system.d.ts.map +1 -0
  35. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-system.js +398 -0
  36. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-system.js.map +1 -0
  37. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-system.d.ts +6 -0
  38. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-system.d.ts.map +1 -0
  39. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-system.js +717 -0
  40. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-system.js.map +1 -0
  41. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts +131 -0
  42. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts.map +1 -1
  43. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js +76 -0
  44. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js.map +1 -1
  45. package/node_modules/@fps-games/babylon-renderer/package.json +1 -1
  46. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +1 -0
  47. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
  48. package/node_modules/@fps-games/editor-babylon/dist/index.js +1 -0
  49. package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
  50. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.d.ts +49 -0
  51. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.d.ts.map +1 -0
  52. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js +141 -0
  53. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js.map +1 -0
  54. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +30 -2
  55. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
  56. package/node_modules/@fps-games/editor-babylon/dist/projection.js +161 -14
  57. package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
  58. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts +14 -3
  59. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts.map +1 -1
  60. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js +223 -8
  61. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js.map +1 -1
  62. package/node_modules/@fps-games/editor-babylon/package.json +5 -5
  63. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
  64. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +33 -6
  65. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
  66. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +3 -1
  67. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
  68. package/node_modules/@fps-games/editor-browser/package.json +3 -3
  69. package/node_modules/@fps-games/editor-core/dist/editor-session.d.ts +14 -0
  70. package/node_modules/@fps-games/editor-core/dist/editor-session.d.ts.map +1 -1
  71. package/node_modules/@fps-games/editor-core/dist/editor-session.js +54 -0
  72. package/node_modules/@fps-games/editor-core/dist/editor-session.js.map +1 -1
  73. package/node_modules/@fps-games/editor-core/package.json +2 -2
  74. package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
  75. package/node_modules/@fps-games/editor-playable-sdk/dist/bridge-command/protocol.d.ts +2 -0
  76. package/node_modules/@fps-games/editor-playable-sdk/dist/bridge-command/protocol.d.ts.map +1 -1
  77. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts +2 -0
  78. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts.map +1 -1
  79. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts +1 -1
  80. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts.map +1 -1
  81. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js +1 -1
  82. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js.map +1 -1
  83. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.d.ts.map +1 -1
  84. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js +2 -1
  85. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js.map +1 -1
  86. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts +1 -0
  87. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts.map +1 -1
  88. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js +8 -0
  89. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js.map +1 -1
  90. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.d.ts +1 -0
  91. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.d.ts.map +1 -1
  92. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.js +56 -0
  93. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.js.map +1 -1
  94. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play-types.d.ts +33 -0
  95. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play-types.d.ts.map +1 -1
  96. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play.d.ts +8 -1
  97. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play.d.ts.map +1 -1
  98. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play.js +85 -4
  99. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/forge-play.js.map +1 -1
  100. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/index.d.ts +2 -2
  101. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/index.d.ts.map +1 -1
  102. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/index.js +1 -1
  103. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/index.js.map +1 -1
  104. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.d.ts.map +1 -1
  105. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js +8 -1
  106. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js.map +1 -1
  107. package/node_modules/@fps-games/editor-playable-sdk/dist/package-resolution/aliases.d.ts +1 -1
  108. package/node_modules/@fps-games/editor-playable-sdk/dist/package-resolution/aliases.d.ts.map +1 -1
  109. package/node_modules/@fps-games/editor-playable-sdk/dist/package-resolution/aliases.js +5 -0
  110. package/node_modules/@fps-games/editor-playable-sdk/dist/package-resolution/aliases.js.map +1 -1
  111. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/panel.d.ts.map +1 -1
  112. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/panel.js +412 -0
  113. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/panel.js.map +1 -1
  114. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/profile.d.ts +6 -2
  115. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/profile.d.ts.map +1 -1
  116. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/profile.js +272 -5
  117. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/profile.js.map +1 -1
  118. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/types.d.ts +108 -1
  119. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/types.d.ts.map +1 -1
  120. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.d.ts.map +1 -1
  121. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js +1 -0
  122. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js.map +1 -1
  123. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts +2 -1
  124. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts.map +1 -1
  125. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/index.d.ts +1 -1
  126. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/index.d.ts.map +1 -1
  127. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/index.js +1 -1
  128. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/index.js.map +1 -1
  129. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/inspectors.d.ts +2 -1
  130. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/inspectors.d.ts.map +1 -1
  131. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/inspectors.js +37 -0
  132. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/inspectors.js.map +1 -1
  133. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/types.d.ts +15 -0
  134. package/node_modules/@fps-games/editor-playable-sdk/dist/upgrade-doctor/types.d.ts.map +1 -1
  135. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/build-info-core.d.ts +36 -0
  136. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/build-info-core.d.ts.map +1 -0
  137. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/build-info-core.js +65 -0
  138. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/build-info-core.js.map +1 -0
  139. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/build-info-node.d.ts +4 -0
  140. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/build-info-node.d.ts.map +1 -0
  141. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/build-info-node.js +87 -0
  142. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/build-info-node.js.map +1 -0
  143. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/index.d.ts +5 -0
  144. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/index.d.ts.map +1 -0
  145. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/index.js +5 -0
  146. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/index.js.map +1 -0
  147. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/plugin.d.ts +8 -0
  148. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/plugin.d.ts.map +1 -0
  149. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/plugin.js +226 -0
  150. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/plugin.js.map +1 -0
  151. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/types.d.ts +64 -0
  152. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/types.d.ts.map +1 -0
  153. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/types.js +2 -0
  154. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/types.js.map +1 -0
  155. package/node_modules/@fps-games/editor-playable-sdk/package.json +7 -3
  156. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +2 -0
  157. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
  158. package/node_modules/@fps-games/editor-protocol/dist/index.js +2 -0
  159. package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
  160. package/node_modules/@fps-games/editor-protocol/package.json +1 -1
  161. package/package.json +12 -8
@@ -7,6 +7,8 @@ import { createPlayableBabylonOutlineInspectorSection, createPlayableBabylonOutl
7
7
  import { createLocalEditorViewportRenderCoordinator, } from './local-editor-viewport-render-coordinator.js';
8
8
  import { createLocalEditorSceneViewInteractionRuntime, } from './local-editor-scene-view-interaction-runtime.js';
9
9
  const BATCH_EDIT_REQUIRES_MULTI_PATCH_REASON = 'Batch edit requires multi-property patch support.';
10
+ const EDITOR_PROJECTION_IDLE_BEFORE_RUN_GAME_TIMEOUT_MS = 1500;
11
+ const EDITOR_WORLD_DISPOSE_BOUNDARY_DELAY_MS = 500;
10
12
  export function createLocalEditorHarness(options) {
11
13
  const root = options.root ?? document.body;
12
14
  const state = {
@@ -87,12 +89,10 @@ export function createLocalEditorHarness(options) {
87
89
  void runExclusive(state, harness.render, () => harness.discardAndRunGame());
88
90
  },
89
91
  onUndo: () => {
90
- if (undoSessionChange(state, options))
91
- harness.render();
92
+ void harness.undo({ origin: 'user', waitForProjection: 'none' });
92
93
  },
93
94
  onRedo: () => {
94
- if (redoSessionChange(state, options))
95
- harness.render();
95
+ void harness.redo({ origin: 'user', waitForProjection: 'none' });
96
96
  },
97
97
  onCreateFromAsset: (assetId) => {
98
98
  if (createAssetFromBrowserIntent(state, options, assetId))
@@ -300,7 +300,9 @@ export function createLocalEditorHarness(options) {
300
300
  });
301
301
  const runGameFromEditor = async (reason) => {
302
302
  cancelActiveOperation(state);
303
+ await waitForEditorProjectionIdleBeforeRunGame(state);
303
304
  disposeEditorWorld(state);
305
+ await waitForEditorWorldDisposeBoundary();
304
306
  state.mode = 'game';
305
307
  state.session = null;
306
308
  state.source = null;
@@ -370,6 +372,18 @@ export function createLocalEditorHarness(options) {
370
372
  harness.render();
371
373
  return result;
372
374
  },
375
+ async undo(historyOptions) {
376
+ const result = applyHistoryOperation(state, options, 'undo', historyOptions);
377
+ if (result.ok)
378
+ harness.render();
379
+ return waitForHistoryOperationProjection(state, result);
380
+ },
381
+ async redo(historyOptions) {
382
+ const result = applyHistoryOperation(state, options, 'redo', historyOptions);
383
+ if (result.ok)
384
+ harness.render();
385
+ return waitForHistoryOperationProjection(state, result);
386
+ },
373
387
  async enterEditor() {
374
388
  const loadedSource = options.persistenceAdapter.loadAuthoringSource
375
389
  ? await options.persistenceAdapter.loadAuthoringSource()
@@ -835,6 +849,7 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
835
849
  scene: world.scene,
836
850
  importModel: options.worldAdapter.importProjectionModel,
837
851
  resolveTextureUrl: options.worldAdapter.resolveMaterialTextureUrl,
852
+ selectionEdgesPrewarm: options.world?.selectionEdgesPrewarm,
838
853
  logger: console,
839
854
  onProjectionReady(event) {
840
855
  syncCurrentSelectionToSceneArtifacts(state, `projection-${event.nodeId}-ready-selection`);
@@ -1156,6 +1171,63 @@ function disposeEditorWorld(state) {
1156
1171
  state.world = null;
1157
1172
  state.engine = null;
1158
1173
  }
1174
+ async function waitForEditorProjectionIdleBeforeRunGame(state) {
1175
+ const projection = state.projection;
1176
+ if (!projection)
1177
+ return;
1178
+ const timeout = createTimeoutPromise(EDITOR_PROJECTION_IDLE_BEFORE_RUN_GAME_TIMEOUT_MS);
1179
+ try {
1180
+ await Promise.race([
1181
+ projection.waitForIdle().catch(() => null),
1182
+ timeout.promise,
1183
+ ]);
1184
+ }
1185
+ finally {
1186
+ timeout.cancel();
1187
+ }
1188
+ }
1189
+ async function waitForEditorWorldDisposeBoundary() {
1190
+ await waitForEditorAnimationFrame();
1191
+ await waitForEditorAnimationFrame();
1192
+ await waitForEditorTimeout(EDITOR_WORLD_DISPOSE_BOUNDARY_DELAY_MS);
1193
+ }
1194
+ function waitForEditorAnimationFrame() {
1195
+ if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
1196
+ return Promise.resolve();
1197
+ }
1198
+ return new Promise(resolve => {
1199
+ window.requestAnimationFrame(() => resolve());
1200
+ });
1201
+ }
1202
+ function waitForEditorTimeout(milliseconds) {
1203
+ if (typeof window === 'undefined' || typeof window.setTimeout !== 'function') {
1204
+ return Promise.resolve();
1205
+ }
1206
+ return new Promise(resolve => {
1207
+ window.setTimeout(() => resolve(), milliseconds);
1208
+ });
1209
+ }
1210
+ function createTimeoutPromise(milliseconds) {
1211
+ if (typeof window === 'undefined' || typeof window.setTimeout !== 'function') {
1212
+ return { promise: new Promise(() => undefined), cancel: () => undefined };
1213
+ }
1214
+ let timeoutId = null;
1215
+ const promise = new Promise(resolve => {
1216
+ timeoutId = window.setTimeout(() => {
1217
+ timeoutId = null;
1218
+ resolve();
1219
+ }, milliseconds);
1220
+ });
1221
+ return {
1222
+ promise,
1223
+ cancel() {
1224
+ if (timeoutId === null)
1225
+ return;
1226
+ window.clearTimeout(timeoutId);
1227
+ timeoutId = null;
1228
+ },
1229
+ };
1230
+ }
1159
1231
  function requestEditorSceneFrame(state, reason) {
1160
1232
  state.viewportRenderCoordinator?.requestFrame(reason);
1161
1233
  }
@@ -1409,6 +1481,7 @@ function renameSceneGraphNode(state, options, intent) {
1409
1481
  label: patch.label ?? `Rename ${intent.id}`,
1410
1482
  patch: patch.patch,
1411
1483
  targetId: intent.id,
1484
+ ...withProjectionImpact(patch.projectionImpact),
1412
1485
  });
1413
1486
  if (!result.documentChanged) {
1414
1487
  state.status = `Rename unchanged: ${intent.id}`;
@@ -1434,6 +1507,7 @@ function createSceneGraphGroup(state, options, intent) {
1434
1507
  label: patch.label ?? 'Create Empty',
1435
1508
  patch: patch.patch,
1436
1509
  targetId: patch.createdId ?? undefined,
1510
+ ...withProjectionImpact(resolveProjectionImpact(patch, createCreatedNodeProjectionImpact([patch.createdId]))),
1437
1511
  });
1438
1512
  if (!result.documentChanged) {
1439
1513
  state.status = 'Create empty unchanged';
@@ -1469,6 +1543,10 @@ function createSceneGraphPrimitive(state, options, intent) {
1469
1543
  label: patch.label ?? `Create ${intent.shape}`,
1470
1544
  patch: patch.patch,
1471
1545
  targetId: patch.createdId ?? undefined,
1546
+ ...withProjectionImpact(resolveProjectionImpact(patch, createKnownCreatedReprojectProjectionImpact({
1547
+ createdNodeIds: [patch.createdId],
1548
+ reprojectIds: patch.reprojectIds,
1549
+ }))),
1472
1550
  });
1473
1551
  if (!result.documentChanged) {
1474
1552
  state.status = `Create ${intent.shape} unchanged`;
@@ -1509,6 +1587,9 @@ function deleteSceneGraphNodes(state, options, intent) {
1509
1587
  label: patch.label ?? `Delete ${intent.ids.length} node(s)`,
1510
1588
  patch: patch.patch,
1511
1589
  targetId: intent.activeId ?? undefined,
1590
+ ...withProjectionImpact(resolveProjectionImpact(patch, patch.deletedIds
1591
+ ? createDeletedNodeProjectionImpact(patch.deletedIds)
1592
+ : undefined)),
1512
1593
  });
1513
1594
  if (!result.documentChanged) {
1514
1595
  state.status = 'Delete unchanged';
@@ -1557,6 +1638,10 @@ function duplicateSceneGraphNodes(state, options, intent) {
1557
1638
  label: patch.label ?? `Duplicate ${targetIds.length} object(s)`,
1558
1639
  patch: patch.patch,
1559
1640
  targetId: patch.activeId ?? patch.createdIds[patch.createdIds.length - 1] ?? undefined,
1641
+ ...withProjectionImpact(resolveProjectionImpact(patch, createCreatedReprojectProjectionImpact({
1642
+ createdNodeIds: patch.createdIds,
1643
+ reprojectIds: patch.reprojectIds,
1644
+ }))),
1560
1645
  });
1561
1646
  if (!result.documentChanged) {
1562
1647
  state.status = 'Duplicate unchanged';
@@ -1613,6 +1698,7 @@ function dropSceneGraphNode(state, options, intent) {
1613
1698
  label: patch.label ?? `Reparent ${intent.draggedId}`,
1614
1699
  patch: patch.patch,
1615
1700
  targetId: intent.draggedId,
1701
+ ...withProjectionImpact(patch.projectionImpact),
1616
1702
  });
1617
1703
  if (!result.documentChanged) {
1618
1704
  state.status = `Reparent unchanged: ${intent.draggedId}`;
@@ -1657,6 +1743,7 @@ function moveSceneGraphNodes(state, options, intent) {
1657
1743
  label: patch.label ?? `Move ${intent.ids.length} node(s)`,
1658
1744
  patch: patch.patch,
1659
1745
  targetId: intent.ids[0] ?? undefined,
1746
+ ...withProjectionImpact(patch.projectionImpact),
1660
1747
  });
1661
1748
  if (!result.documentChanged) {
1662
1749
  state.status = 'Move unchanged';
@@ -1694,6 +1781,7 @@ function groupSceneGraphSelection(state, options, intent) {
1694
1781
  label: patch.label ?? 'Create Parent',
1695
1782
  patch: patch.patch,
1696
1783
  targetId: patch.createdId,
1784
+ ...withProjectionImpact(patch.projectionImpact),
1697
1785
  });
1698
1786
  if (!result.documentChanged) {
1699
1787
  state.status = 'Create Parent unchanged';
@@ -1914,6 +2002,9 @@ function executeTransformAction(state, options, action) {
1914
2002
  label: patch.label ?? formatTransformActionStatus(action, changedTargets.length),
1915
2003
  patch: patch.patch,
1916
2004
  targetId: selection.activeId ?? changedTargets[0]?.id,
2005
+ ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
2006
+ ? createReprojectProjectionImpact(patch.reprojectIds)
2007
+ : createTransformProjectionImpact(changedTargets.map(target => target.id)))),
1917
2008
  });
1918
2009
  if (!result.documentChanged) {
1919
2010
  state.status = `Transform action unchanged: ${action}`;
@@ -2010,6 +2101,11 @@ function patchBrowserAssetAction(state, options, input) {
2010
2101
  type: 'document.patch',
2011
2102
  label: patch.label ?? `Asset action ${input.actionId}`,
2012
2103
  patch: patch.patch,
2104
+ ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
2105
+ ? createReprojectProjectionImpact(patch.reprojectIds)
2106
+ : patch.changedIds?.length
2107
+ ? createReprojectProjectionImpact(patch.changedIds)
2108
+ : undefined)),
2013
2109
  });
2014
2110
  state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
2015
2111
  state.status = patch.label ?? `Asset action ${input.actionId}`;
@@ -2091,6 +2187,10 @@ function commitArmedPlacement(state, options, event) {
2091
2187
  label: patch.label ?? `Place ${armed.assetId}`,
2092
2188
  patch: patch.patch,
2093
2189
  targetId: patch.createdId ?? undefined,
2190
+ ...withProjectionImpact(resolveProjectionImpact(patch, createKnownCreatedReprojectProjectionImpact({
2191
+ createdNodeIds: [patch.createdId],
2192
+ reprojectIds: patch.reprojectIds,
2193
+ }))),
2094
2194
  });
2095
2195
  if (!result.documentChanged) {
2096
2196
  state.status = 'Placement unchanged';
@@ -2154,8 +2254,14 @@ function addAssetToDocument(state, options, assetId, createOptions = {}) {
2154
2254
  type: 'document.patch',
2155
2255
  label: patch.label ?? 'Create Object From Asset',
2156
2256
  patch: patch.patch,
2257
+ ...withProjectionImpact(resolveProjectionImpact(patch, createKnownCreatedReprojectProjectionImpact({
2258
+ createdNodeIds: [patch.createdId],
2259
+ reprojectIds: patch.reprojectIds,
2260
+ }))),
2157
2261
  });
2158
- const createdId = options.documentAdapter.findCreatedId?.(beforeDocument, result.workingDocument);
2262
+ const createdId = patch.createdId
2263
+ ?? options.documentAdapter.findCreatedId?.(beforeDocument, result.workingDocument)
2264
+ ?? null;
2159
2265
  let selectionResult = null;
2160
2266
  if (createdId) {
2161
2267
  selectionResult = session.dispatch({
@@ -2217,15 +2323,22 @@ function patchSerializedProperty(state, options, input) {
2217
2323
  });
2218
2324
  if (!patch)
2219
2325
  return false;
2326
+ const changedIds = patch.changedIds ?? targetIds;
2220
2327
  const result = state.session.dispatch({
2221
2328
  type: 'document.patch',
2222
2329
  label: patch.label ?? `Patch ${payload.path} on ${targetIds.length} objects`,
2223
2330
  patch: patch.patch,
2224
2331
  targetId: state.session.getState().selection.activeId ?? undefined,
2332
+ ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
2333
+ ? createReprojectProjectionImpact(patch.reprojectIds)
2334
+ : shouldReprojectInspectorEdit
2335
+ ? createReprojectProjectionImpact(changedIds)
2336
+ : isSerializedTransformPath(payload.path)
2337
+ ? createTransformProjectionImpact(targetIds)
2338
+ : undefined)),
2225
2339
  });
2226
2340
  if (!result.documentChanged)
2227
2341
  return false;
2228
- const changedIds = patch.changedIds ?? targetIds;
2229
2342
  const projectionSyncIds = isSerializedTransformPath(payload.path) ? targetIds : changedIds;
2230
2343
  const workingDocument = result.workingDocument;
2231
2344
  if (patch.reprojectIds?.length)
@@ -2256,6 +2369,13 @@ function patchSerializedProperty(state, options, input) {
2256
2369
  type: 'document.patch',
2257
2370
  label: patch.label ?? `Patch ${payload.path}`,
2258
2371
  patch: patch.patch,
2372
+ ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
2373
+ ? createReprojectProjectionImpact(patch.reprojectIds)
2374
+ : shouldReprojectInspectorEdit
2375
+ ? createReprojectProjectionImpact(patch.changedIds ?? [patch.changedId ?? payload.targetId])
2376
+ : isSerializedTransformPath(payload.path)
2377
+ ? createTransformProjectionImpact([patch.changedId ?? payload.targetId])
2378
+ : undefined)),
2259
2379
  });
2260
2380
  if (patch.reprojectIds?.length)
2261
2381
  reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.reprojectIds);
@@ -2480,6 +2600,10 @@ function beginDuplicateDrag(state, options, input) {
2480
2600
  label: patch.label ?? `Duplicate ${input.targetIds.length} object(s)`,
2481
2601
  patch: patch.patch,
2482
2602
  targetId: patch.activeId ?? patch.createdIds[patch.createdIds.length - 1] ?? undefined,
2603
+ ...withProjectionImpact(resolveProjectionImpact(patch, createCreatedReprojectProjectionImpact({
2604
+ createdNodeIds: patch.createdIds,
2605
+ reprojectIds: patch.reprojectIds,
2606
+ }))),
2483
2607
  });
2484
2608
  if (!result.documentChanged) {
2485
2609
  state.status = 'Duplicate drag unchanged';
@@ -2559,6 +2683,9 @@ function commitGizmoTransform(state, options, event) {
2559
2683
  label: patch.label ?? `${event.tool} ${event.targetIds.length} objects`,
2560
2684
  patch: patch.patch,
2561
2685
  targetId: event.activeId ?? undefined,
2686
+ ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
2687
+ ? createReprojectProjectionImpact(patch.reprojectIds)
2688
+ : createTransformProjectionImpact(event.targetIds))),
2562
2689
  }, {
2563
2690
  mergeWithPrevious: event.targetIds.some(id => state.duplicateDrag?.createdIds.includes(id)) === true,
2564
2691
  });
@@ -2594,6 +2721,9 @@ function commitGizmoTransform(state, options, event) {
2594
2721
  label: patch.label ?? `${event.tool} ${targetId}`,
2595
2722
  patch: patch.patch,
2596
2723
  targetId,
2724
+ ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
2725
+ ? createReprojectProjectionImpact(patch.reprojectIds)
2726
+ : createTransformProjectionImpact([patch.changedId ?? targetId]))),
2597
2727
  }, {
2598
2728
  mergeWithPrevious: state.duplicateDrag?.createdIds.includes(targetId) === true,
2599
2729
  });
@@ -2699,29 +2829,193 @@ function formatAssetLabel(asset, fallback) {
2699
2829
  const label = record?.label ?? record?.displayName ?? record?.name;
2700
2830
  return typeof label === 'string' && label.trim() ? label : fallback;
2701
2831
  }
2702
- function undoSessionChange(state, options) {
2703
- cancelActiveOperation(state);
2704
- const result = state.session?.undo();
2705
- if (!result)
2706
- return false;
2707
- rebuildProjection(state, options, result);
2708
- state.summary = summarizeDocument(options, result.workingDocument, state.session?.getSource());
2709
- state.status = `Undo ${result.transaction.label}`;
2710
- return true;
2832
+ function withProjectionImpact(impact) {
2833
+ return impact ? { projectionImpact: impact } : {};
2834
+ }
2835
+ function resolveProjectionImpact(source, fallback) {
2836
+ return source.projectionImpact ?? fallback;
2837
+ }
2838
+ function createTransformProjectionImpact(ids) {
2839
+ return createIncrementalProjectionImpact({ syncTransformIds: ids });
2840
+ }
2841
+ function createReprojectProjectionImpact(ids) {
2842
+ return createIncrementalProjectionImpact({ reprojectIds: ids });
2843
+ }
2844
+ function createCreatedNodeProjectionImpact(ids) {
2845
+ return createIncrementalProjectionImpact({ createdNodeIds: ids });
2846
+ }
2847
+ function createDeletedNodeProjectionImpact(ids) {
2848
+ return createIncrementalProjectionImpact({ deletedNodeIds: ids });
2849
+ }
2850
+ function createCreatedReprojectProjectionImpact(input) {
2851
+ return createIncrementalProjectionImpact({
2852
+ createdNodeIds: input.createdNodeIds,
2853
+ reprojectIds: input.reprojectIds,
2854
+ });
2711
2855
  }
2712
- function redoSessionChange(state, options) {
2856
+ function createKnownCreatedReprojectProjectionImpact(input) {
2857
+ const createdNodeIds = uniqueProjectionIds(input.createdNodeIds);
2858
+ if (createdNodeIds.length === 0)
2859
+ return undefined;
2860
+ return createCreatedReprojectProjectionImpact({
2861
+ createdNodeIds,
2862
+ reprojectIds: input.reprojectIds,
2863
+ });
2864
+ }
2865
+ function createIncrementalProjectionImpact(input) {
2866
+ const syncTransformIds = uniqueProjectionIds(input.syncTransformIds);
2867
+ const reprojectIds = uniqueProjectionIds(input.reprojectIds);
2868
+ const createdNodeIds = uniqueProjectionIds(input.createdNodeIds);
2869
+ const deletedNodeIds = uniqueProjectionIds(input.deletedNodeIds);
2870
+ if (syncTransformIds.length === 0
2871
+ && reprojectIds.length === 0
2872
+ && createdNodeIds.length === 0
2873
+ && deletedNodeIds.length === 0) {
2874
+ return undefined;
2875
+ }
2876
+ return {
2877
+ type: 'incremental',
2878
+ ...(syncTransformIds.length ? { syncTransformIds } : {}),
2879
+ ...(reprojectIds.length ? { reprojectIds } : {}),
2880
+ ...(createdNodeIds.length ? { createdNodeIds } : {}),
2881
+ ...(deletedNodeIds.length ? { deletedNodeIds } : {}),
2882
+ };
2883
+ }
2884
+ function uniqueProjectionIds(ids) {
2885
+ const result = [];
2886
+ const seen = new Set();
2887
+ for (const id of ids ?? []) {
2888
+ if (!id || seen.has(id))
2889
+ continue;
2890
+ seen.add(id);
2891
+ result.push(id);
2892
+ }
2893
+ return result;
2894
+ }
2895
+ function applyHistoryOperation(state, options, operation, input = {}) {
2896
+ const origin = input.origin ?? 'user';
2897
+ const waitForProjection = input.waitForProjection ?? 'none';
2713
2898
  cancelActiveOperation(state);
2714
- const result = state.session?.redo();
2899
+ const result = operation === 'undo'
2900
+ ? state.session?.undo()
2901
+ : state.session?.redo();
2715
2902
  if (!result)
2716
- return false;
2717
- rebuildProjection(state, options, result);
2903
+ return createHistoryOperationResult(state, {
2904
+ operation,
2905
+ origin,
2906
+ waitForProjection,
2907
+ result: null,
2908
+ status: operation === 'undo' ? 'Undo unavailable' : 'Redo unavailable',
2909
+ });
2910
+ applyHistoryProjectionImpact(state, options, result, operation);
2718
2911
  state.summary = summarizeDocument(options, result.workingDocument, state.session?.getSource());
2719
- state.status = `Redo ${result.transaction.label}`;
2720
- return true;
2912
+ state.status = `${operation === 'undo' ? 'Undo' : 'Redo'} ${result.transaction.label}`;
2913
+ return createHistoryOperationResult(state, {
2914
+ operation,
2915
+ origin,
2916
+ waitForProjection,
2917
+ result,
2918
+ status: state.status,
2919
+ });
2920
+ }
2921
+ async function waitForHistoryOperationProjection(state, result) {
2922
+ if (!result.ok || result.waitForProjection !== 'settled')
2923
+ return result;
2924
+ const idleState = await state.projection?.waitForIdle() ?? null;
2925
+ return {
2926
+ ...result,
2927
+ projection: createHistoryProjectionResult(result.waitForProjection, idleState, state),
2928
+ };
2929
+ }
2930
+ function createHistoryOperationResult(state, input) {
2931
+ const sessionState = state.session?.getState() ?? null;
2932
+ return {
2933
+ ok: !!input.result,
2934
+ operation: input.operation,
2935
+ origin: input.origin,
2936
+ waitForProjection: input.waitForProjection,
2937
+ documentChanged: input.result?.documentChanged ?? false,
2938
+ transactionLabel: input.result?.transaction.label ?? null,
2939
+ workingDocument: input.result?.workingDocument ?? sessionState?.workingDocument ?? null,
2940
+ dirty: input.result?.dirty ?? sessionState?.dirty ?? false,
2941
+ history: {
2942
+ canUndo: sessionState?.canUndo ?? false,
2943
+ canRedo: sessionState?.canRedo ?? false,
2944
+ },
2945
+ status: input.status,
2946
+ projection: createHistoryProjectionResult(input.waitForProjection, null, state),
2947
+ };
2948
+ }
2949
+ function createHistoryProjectionResult(waitPolicy, idleState, state) {
2950
+ const resolved = idleState ?? state.projection?.getIdleState() ?? null;
2951
+ return {
2952
+ waitPolicy,
2953
+ idle: resolved?.idle ?? true,
2954
+ activeBatchIds: resolved?.activeBatchIds ?? [],
2955
+ lastBatch: resolved?.lastBatch ?? null,
2956
+ };
2957
+ }
2958
+ function applyHistoryProjectionImpact(state, options, result, operation) {
2959
+ const impact = result.transaction.projectionImpact;
2960
+ if (!impact || impact.type === 'rebuild') {
2961
+ rebuildProjection(state, options, result);
2962
+ return;
2963
+ }
2964
+ const applied = applyIncrementalHistoryProjectionImpact(state, options, result, operation, impact);
2965
+ if (!applied)
2966
+ rebuildProjection(state, options, result);
2721
2967
  }
2722
2968
  function rebuildProjection(state, options, result) {
2723
2969
  rebuildProjectionFromDocument(state, options, result.workingDocument, result.selection);
2724
2970
  }
2971
+ function applyIncrementalHistoryProjectionImpact(state, options, result, operation, impact) {
2972
+ if (!state.projection)
2973
+ return false;
2974
+ const syncTransformIds = uniqueProjectionIds(impact.syncTransformIds);
2975
+ const reprojectIds = uniqueProjectionIds(impact.reprojectIds);
2976
+ const projectNodeIds = operation === 'redo'
2977
+ ? uniqueProjectionIds(impact.createdNodeIds)
2978
+ : uniqueProjectionIds(impact.deletedNodeIds);
2979
+ const removeNodeIds = operation === 'redo'
2980
+ ? uniqueProjectionIds(impact.deletedNodeIds)
2981
+ : uniqueProjectionIds(impact.createdNodeIds);
2982
+ const touched = syncTransformIds.length > 0
2983
+ || reprojectIds.length > 0
2984
+ || projectNodeIds.length > 0
2985
+ || removeNodeIds.length > 0;
2986
+ if (!touched)
2987
+ return false;
2988
+ for (const nodeId of removeNodeIds) {
2989
+ state.projection.removeNode(nodeId);
2990
+ }
2991
+ for (const nodeId of projectNodeIds) {
2992
+ const projectedNode = options.documentAdapter.getProjectionNode(result.workingDocument, nodeId);
2993
+ if (!projectedNode)
2994
+ return false;
2995
+ state.projection.projectNode(projectedNode);
2996
+ }
2997
+ for (const nodeId of reprojectIds) {
2998
+ const projectedNode = options.documentAdapter.getProjectionNode(result.workingDocument, nodeId);
2999
+ if (!projectedNode)
3000
+ return false;
3001
+ state.projection.projectNode(projectedNode);
3002
+ }
3003
+ for (const nodeId of syncTransformIds) {
3004
+ const projectedNode = options.documentAdapter.getProjectionNode(result.workingDocument, nodeId);
3005
+ if (!projectedNode)
3006
+ return false;
3007
+ state.projection.syncNodeTransform(projectedNode);
3008
+ }
3009
+ syncProjectionAfterHistoryIncrementalImpact(state, options, result.workingDocument, result.selection);
3010
+ return true;
3011
+ }
3012
+ function syncProjectionAfterHistoryIncrementalImpact(state, options, document, selection) {
3013
+ const sanitized = sanitizeSelection(state, options, document, selection);
3014
+ syncSelectionToProjection(state, sanitized ?? selection);
3015
+ syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-history-impact');
3016
+ syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-history-impact', true);
3017
+ invalidateEditorScene(state, 'projection-history-impact', options);
3018
+ }
2725
3019
  function rebuildProjectionFromDocument(state, options, document, selection) {
2726
3020
  state.projection?.rebuild(options.documentAdapter.getProjectionNodes(document));
2727
3021
  const sanitized = sanitizeSelection(state, options, document, selection);