@forgeax/engine-scene 0.1.20 → 0.1.23

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 (80) hide show
  1. package/README.md +37 -9
  2. package/dist/__tests__/flat-propagation.contract.test.d.ts +2 -0
  3. package/dist/__tests__/flat-propagation.contract.test.d.ts.map +1 -0
  4. package/dist/__tests__/flat-propagation.derived-writer.contract.test.d.ts +2 -0
  5. package/dist/__tests__/flat-propagation.derived-writer.contract.test.d.ts.map +1 -0
  6. package/dist/__tests__/flat-propagation.perf.test.d.ts +2 -0
  7. package/dist/__tests__/flat-propagation.perf.test.d.ts.map +1 -0
  8. package/dist/__tests__/scene-binding.integration.test.d.ts +2 -0
  9. package/dist/__tests__/scene-binding.integration.test.d.ts.map +1 -0
  10. package/dist/__tests__/schedule-order.contract.test.d.ts +2 -0
  11. package/dist/__tests__/schedule-order.contract.test.d.ts.map +1 -0
  12. package/dist/__tests__/structural.test.d.ts +2 -0
  13. package/dist/__tests__/structural.test.d.ts.map +1 -0
  14. package/dist/__tests__/transform-carrier-consumers.test-d.d.ts +2 -0
  15. package/dist/__tests__/transform-carrier-consumers.test-d.d.ts.map +1 -0
  16. package/dist/__tests__/transform-carrier-prototype.bench.d.ts +39 -0
  17. package/dist/__tests__/transform-carrier-prototype.bench.d.ts.map +1 -0
  18. package/dist/__tests__/transform-carrier-prototype.test-d.d.ts +2 -0
  19. package/dist/__tests__/transform-carrier-prototype.test-d.d.ts.map +1 -0
  20. package/dist/__tests__/transform-carrier-prototype.test.d.ts +2 -0
  21. package/dist/__tests__/transform-carrier-prototype.test.d.ts.map +1 -0
  22. package/dist/__tests__/transform-component-version.unit.test.d.ts +2 -0
  23. package/dist/__tests__/transform-component-version.unit.test.d.ts.map +1 -0
  24. package/dist/assets/scene-decoder.d.ts.map +1 -1
  25. package/dist/components/child-of.d.ts +15 -8
  26. package/dist/components/child-of.d.ts.map +1 -1
  27. package/dist/components/children.d.ts.map +1 -1
  28. package/dist/components/transform.d.ts +7 -43
  29. package/dist/components/transform.d.ts.map +1 -1
  30. package/dist/errors.d.ts +23 -2
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/index.d.ts +3 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.mjs +616 -372
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/instances/binding.d.ts +29 -0
  37. package/dist/instances/binding.d.ts.map +1 -0
  38. package/dist/instances/externalization.d.ts.map +1 -1
  39. package/dist/instances/scene-instances.d.ts +5 -17
  40. package/dist/instances/scene-instances.d.ts.map +1 -1
  41. package/dist/instances/state.d.ts +28 -0
  42. package/dist/instances/state.d.ts.map +1 -0
  43. package/dist/plugin.d.ts.map +1 -1
  44. package/dist/systems/hierarchy-projection.d.ts.map +1 -1
  45. package/dist/systems/index.d.ts +1 -1
  46. package/dist/systems/index.d.ts.map +1 -1
  47. package/dist/systems/propagate-transforms.d.ts +1 -2
  48. package/dist/systems/propagate-transforms.d.ts.map +1 -1
  49. package/package.json +5 -5
  50. package/src/__tests__/components.unit.test.ts +34 -2
  51. package/src/__tests__/flat-propagation.contract.test.ts +128 -0
  52. package/src/__tests__/flat-propagation.derived-writer.contract.test.ts +39 -0
  53. package/src/__tests__/flat-propagation.perf.test.ts +336 -0
  54. package/src/__tests__/plugin.integration.test.ts +2 -2
  55. package/src/__tests__/propagation.unit.test.ts +207 -91
  56. package/src/__tests__/scene-binding.integration.test.ts +124 -0
  57. package/src/__tests__/schedule-order.contract.test.ts +43 -0
  58. package/src/__tests__/structural.test.ts +23 -0
  59. package/src/__tests__/transform-carrier-consumers.test-d.ts +156 -0
  60. package/src/__tests__/transform-carrier-prototype.bench.ts +168 -0
  61. package/src/__tests__/transform-carrier-prototype.test-d.ts +71 -0
  62. package/src/__tests__/transform-carrier-prototype.test.ts +170 -0
  63. package/src/__tests__/{transform-change-journal.unit.test.ts → transform-component-version.unit.test.ts} +32 -39
  64. package/src/assets/scene-decoder.ts +6 -1
  65. package/src/components/child-of.ts +15 -8
  66. package/src/components/children.ts +6 -0
  67. package/src/components/transform.ts +23 -81
  68. package/src/errors.ts +26 -2
  69. package/src/index.ts +10 -1
  70. package/src/instances/binding.ts +77 -0
  71. package/src/instances/externalization.ts +7 -1
  72. package/src/instances/scene-instances.ts +47 -76
  73. package/src/instances/state.ts +62 -0
  74. package/src/plugin.ts +9 -2
  75. package/src/systems/hierarchy-projection.ts +25 -8
  76. package/src/systems/index.ts +3 -0
  77. package/src/systems/propagate-transforms.ts +376 -245
  78. package/dist/.tsbuildinfo +0 -1
  79. package/dist/__tests__/transform-change-journal.unit.test.d.ts +0 -2
  80. package/dist/__tests__/transform-change-journal.unit.test.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -64,7 +64,11 @@ function resolveSceneWireRefs(payload, refs) {
64
64
  for (const [componentName, rawFields] of Object.entries(entity.components)) {
65
65
  components[componentName] = { ...rawFields };
66
66
  }
67
- entities.push({ localId: entity.localId, components });
67
+ entities.push({
68
+ localId: entity.localId,
69
+ ...entity.bindingKey === void 0 ? {} : { bindingKey: entity.bindingKey },
70
+ components
71
+ });
68
72
  }
69
73
  const mounts = resolveMounts(payload.mounts, refs);
70
74
  if (!mounts.ok) return mounts;
@@ -77,6 +81,7 @@ function resolveSceneWireRefs(payload, refs) {
77
81
  ok: true,
78
82
  value: {
79
83
  kind: "scene",
84
+ ...payload.sourceKey === void 0 ? {} : { sourceKey: payload.sourceKey },
80
85
  entities,
81
86
  ...mounts.value === void 0 ? {} : { mounts: mounts.value },
82
87
  ...skinGuids.value === void 0 ? {} : { skinGuids: skinGuids.value }
@@ -103,11 +108,39 @@ var sceneAssetContribution = {
103
108
 
104
109
  // src/components/children.ts
105
110
  import { defineRelationship } from "@forgeax/engine-ecs";
111
+
112
+ // src/components/transform.ts
113
+ import { defineComponent } from "@forgeax/engine-ecs";
114
+ var IDENTITY_MAT4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
115
+ var GlobalTransform = defineComponent(
116
+ "GlobalTransform",
117
+ {
118
+ world: { type: "array<f32, 16>", default: IDENTITY_MAT4 }
119
+ },
120
+ { transient: true }
121
+ );
122
+ var Transform = defineComponent(
123
+ "Transform",
124
+ {
125
+ pos: { type: "array<f32, 3>", default: new Float32Array([0, 0, 0]) },
126
+ // Component order [x, y, z, w] is shared with glTF.
127
+ quat: { type: "array<f32, 4>", default: new Float32Array([0, 0, 0, 1]) },
128
+ scale: { type: "array<f32, 3>", default: new Float32Array([1, 1, 1]) }
129
+ },
130
+ { requires: [GlobalTransform] }
131
+ );
132
+
133
+ // src/components/children.ts
106
134
  var { source: ChildOf, target: Children } = defineRelationship({
107
135
  sourceName: "ChildOf",
108
136
  sourceField: "parent",
109
137
  targetName: "Children",
110
138
  targetField: "entities",
139
+ // Every scene hierarchy node is spatial. Adding ChildOf therefore
140
+ // materializes the local/derived transform pair at the same structural
141
+ // boundary, so render- and scene-authored children cannot enter a frame
142
+ // with an incomplete hierarchy node.
143
+ sourceRequires: [Transform],
111
144
  exclusive: true,
112
145
  linkedSpawn: true
113
146
  });
@@ -134,29 +167,14 @@ function collectSubtree(world, spawnRoot, visited) {
134
167
  }
135
168
 
136
169
  // src/components/morph-weights.ts
137
- import { defineComponent } from "@forgeax/engine-ecs";
138
- var MorphWeights = defineComponent("MorphWeights", {
170
+ import { defineComponent as defineComponent2 } from "@forgeax/engine-ecs";
171
+ var MorphWeights = defineComponent2("MorphWeights", {
139
172
  weights: { type: "array<f32>" }
140
173
  });
141
174
 
142
175
  // src/components/name.ts
143
- import { defineComponent as defineComponent2 } from "@forgeax/engine-ecs";
144
- var Name = defineComponent2("Name", { value: { type: "string" } });
145
-
146
- // src/components/transform.ts
147
176
  import { defineComponent as defineComponent3 } from "@forgeax/engine-ecs";
148
- var IDENTITY_MAT4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
149
- var Transform = defineComponent3("Transform", {
150
- pos: { type: "array<f32, 3>", default: new Float32Array([0, 0, 0]) },
151
- // Component order [x, y, z, w] end to end (glTF-aligned; E6).
152
- quat: { type: "array<f32, 4>", default: new Float32Array([0, 0, 0, 1]) },
153
- scale: { type: "array<f32, 3>", default: new Float32Array([1, 1, 1]) },
154
- // `world` is field-level transient (D-5): scene collect skips it. The resolved
155
- // world mat4 is derived by the propagate kernel from the persisted local TRS
156
- // each frame, so serializing it would store reconstructable data (SSOT: local
157
- // TRS). Round-trip re-derives an equivalent world on the first propagate pass.
158
- world: { type: "array<f32, 16>", default: IDENTITY_MAT4, transient: true }
159
- });
177
+ var Name = defineComponent3("Name", { value: { type: "string" } });
160
178
 
161
179
  // src/errors.ts
162
180
  import { ComponentNotDefinedError } from "@forgeax/engine-ecs/projection";
@@ -175,6 +193,55 @@ var SceneError = class extends Error {
175
193
  }
176
194
  };
177
195
 
196
+ // src/instances/binding.ts
197
+ import { err as err2, ok as ok2 } from "@forgeax/engine-types";
198
+ function validateSceneBindings(sceneSourceKey, bindingKeys) {
199
+ if (sceneSourceKey.length === 0) {
200
+ return err2({
201
+ code: "scene-binding-source-missing",
202
+ expected: "a non-empty scene sourceKey",
203
+ hint: "declare the scene sourceKey in the author inventory",
204
+ detail: {}
205
+ });
206
+ }
207
+ const seen = /* @__PURE__ */ new Set();
208
+ for (const bindingKey of bindingKeys) {
209
+ if (bindingKey.length === 0 || seen.has(bindingKey)) {
210
+ return err2({
211
+ code: "scene-binding-duplicate",
212
+ expected: "unique non-empty bindingKey values within one scene",
213
+ hint: "rename the duplicate bindingKey in the scene producer",
214
+ detail: { sceneSourceKey, bindingKey }
215
+ });
216
+ }
217
+ seen.add(bindingKey);
218
+ }
219
+ return ok2([...bindingKeys]);
220
+ }
221
+ function sceneEntity(sceneSourceKey, bindingKey) {
222
+ return { sceneSourceKey, bindingKey };
223
+ }
224
+ function resolveSceneEntity(ref, instance) {
225
+ if (ref.sceneSourceKey !== instance.sceneSourceKey) {
226
+ return err2({
227
+ code: "scene-binding-wrong-instance",
228
+ expected: `scene instance ${ref.sceneSourceKey}`,
229
+ hint: "resolve the SceneEntityRef against its owning SceneInstance",
230
+ detail: { sceneSourceKey: ref.sceneSourceKey, bindingKey: ref.bindingKey }
231
+ });
232
+ }
233
+ const value = instance.bindings.get(ref.bindingKey);
234
+ if (value === void 0) {
235
+ return err2({
236
+ code: "scene-binding-missing",
237
+ expected: "bindingKey declared by the scene producer",
238
+ hint: "declare the bindingKey in the scene producer before consuming it",
239
+ detail: { sceneSourceKey: ref.sceneSourceKey, bindingKey: ref.bindingKey }
240
+ });
241
+ }
242
+ return ok2(value);
243
+ }
244
+
178
245
  // src/instances/collect-profile.ts
179
246
  var SCENE_COLLECT_PROFILE = Object.freeze({
180
247
  includeComponent: (_componentName, transient) => !transient,
@@ -182,7 +249,7 @@ var SCENE_COLLECT_PROFILE = Object.freeze({
182
249
  });
183
250
 
184
251
  // src/instances/externalization.ts
185
- import { err as err2, ok as ok2 } from "@forgeax/engine-types";
252
+ import { err as err3, ok as ok3 } from "@forgeax/engine-types";
186
253
  function sharedKind(type) {
187
254
  if (type?.startsWith("shared<")) return "one";
188
255
  if (type?.startsWith("array<shared<")) return "many";
@@ -236,7 +303,11 @@ function externalizeSceneAsset(scene, resolveSchema) {
236
303
  components[componentName] = fields;
237
304
  }
238
305
  }
239
- return { localId: entity.localId, components };
306
+ return {
307
+ localId: entity.localId,
308
+ ...entity.bindingKey === void 0 ? {} : { bindingKey: entity.bindingKey },
309
+ components
310
+ };
240
311
  });
241
312
  const mounts = scene.mounts?.map((mount) => {
242
313
  const source = typeof mount.source === "string" ? addRef(
@@ -260,11 +331,13 @@ function externalizeSceneAsset(scene, resolveSchema) {
260
331
  };
261
332
  });
262
333
  for (const [arrayIndex, guid] of (scene.skinGuids ?? []).entries()) {
263
- if (typeof guid !== "string") return err2({ field: "skinGuids", value: guid });
334
+ if (typeof guid !== "string") return err3({ field: "skinGuids", value: guid });
264
335
  addRef(guid, { componentName: "<scene>", fieldName: "skinGuids", arrayIndex });
265
336
  }
266
- return ok2({
337
+ return ok3({
267
338
  payload: {
339
+ kind: "scene",
340
+ ...scene.sourceKey === void 0 ? {} : { sourceKey: scene.sourceKey },
268
341
  entities,
269
342
  ...mounts === void 0 || mounts.length === 0 ? {} : { mounts },
270
343
  ...scene.skinGuids === void 0 ? {} : { skinGuids: scene.skinGuids.map((guid) => indexByGuid.get(guid)) }
@@ -281,14 +354,14 @@ import { classifyEntityField, remapEntityFieldValue } from "@forgeax/engine-ecs/
281
354
  import { componentSchema } from "@forgeax/engine-ecs/internal";
282
355
  import { fillComponentDefaults, StaleEntityError } from "@forgeax/engine-ecs/projection";
283
356
  import {
284
- err as err3,
285
- ok as ok3,
357
+ err as err4,
358
+ ok as ok4,
286
359
  PACK_ERROR_HINTS,
287
360
  toUnique,
288
361
  unwrapHandle
289
362
  } from "@forgeax/engine-types";
290
- var entityIndex = (entity) => entity & 16777215;
291
- var entityGeneration = (entity) => entity >>> 24 & 255;
363
+
364
+ // src/instances/state.ts
292
365
  var sceneWorldStates = /* @__PURE__ */ new WeakMap();
293
366
  function sceneWorldState(world) {
294
367
  const current = sceneWorldStates.get(world);
@@ -297,6 +370,24 @@ function sceneWorldState(world) {
297
370
  sceneWorldStates.set(world, created);
298
371
  return created;
299
372
  }
373
+ function mountOverrideStateKey(ov) {
374
+ return ov.field !== void 0 ? `${ov.comp}:${ov.field}` : ov.comp;
375
+ }
376
+ function isPrimitiveScalarFieldType(fieldType) {
377
+ if (fieldType === "f32" || fieldType === "f64" || fieldType === "u32" || fieldType === "i32" || fieldType === "u8" || fieldType === "i8" || fieldType === "u16" || fieldType === "i16" || fieldType === "bool" || fieldType === "string") {
378
+ return true;
379
+ }
380
+ return fieldType.startsWith("enum<");
381
+ }
382
+ function primitiveJsType(fieldType) {
383
+ if (fieldType === "bool") return "boolean";
384
+ if (fieldType === "string") return "string";
385
+ return "number";
386
+ }
387
+
388
+ // src/instances/scene-instances.ts
389
+ var entityIndex = (entity) => entity & 16777215;
390
+ var entityGeneration = (entity) => entity >>> 24 & 255;
300
391
  function worldSetSceneAssetResolver(world, resolver) {
301
392
  sceneWorldState(world).resolver = resolver;
302
393
  }
@@ -308,7 +399,7 @@ function worldInstantiateScene(world, handle, parent) {
308
399
  const diagnostics = [];
309
400
  const r = worldInstantiateSceneRec(world, handle, parent, stack, diagnostics);
310
401
  if (!r.ok) return r;
311
- return ok3({ root: r.value, diagnostics });
402
+ return ok4({ root: r.value, diagnostics });
312
403
  }
313
404
  function worldInstantiateScenePayload(world, asset, parent) {
314
405
  const handle = world.allocSharedRef("SceneAsset", asset);
@@ -332,7 +423,7 @@ function worldInstantiateSceneFlat(world, handle) {
332
423
  stack.delete(handleKey);
333
424
  }
334
425
  if (!r.ok) return r;
335
- return ok3({ ...r.value, diagnostics });
426
+ return ok4({ ...r.value, diagnostics });
336
427
  }
337
428
  function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
338
429
  const handleKey = unwrapHandle(handle);
@@ -345,7 +436,7 @@ function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
345
436
  kind: "mount-asset",
346
437
  cycle: cycleArr
347
438
  };
348
- return err3({
439
+ return err4({
349
440
  code: "pack-cyclic-reference",
350
441
  expected: "acyclic SceneAsset mount graph",
351
442
  hint: PACK_ERROR_HINTS["pack-cyclic-reference"],
@@ -365,18 +456,27 @@ function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
365
456
  function worldResolveSceneAsset(world, handle) {
366
457
  const r = world.sharedRefs.resolve(handle);
367
458
  if (!r.ok) {
368
- return err3(r.error);
459
+ return err4(r.error);
369
460
  }
370
- return ok3(r.value);
461
+ return ok4(r.value);
371
462
  }
372
463
  function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
373
464
  const sceneInstanceToken = world.components.resolve("SceneInstance");
374
465
  if (sceneInstanceToken === void 0) {
375
- return err3(new ComponentNotDefinedError("SceneInstance"));
466
+ return err4(new ComponentNotDefinedError("SceneInstance"));
376
467
  }
377
468
  const childOfToken = world.components.resolve("ChildOf");
378
469
  const ownEntities = asset.entities;
379
470
  const ownMounts = asset.mounts ?? [];
471
+ const bindingKeys = ownEntities.flatMap(
472
+ (entity) => entity.bindingKey === void 0 ? [] : [entity.bindingKey]
473
+ );
474
+ if (bindingKeys.length > 0) {
475
+ const bindingCheck = validateSceneBindings(asset.sourceKey ?? "", bindingKeys);
476
+ if (!bindingCheck.ok) {
477
+ return err4(bindingCheck.error);
478
+ }
479
+ }
380
480
  const memberSum = ownMounts.reduce((s, m) => s + m.memberCount, 0);
381
481
  const countBaseline = ownEntities.length + ownMounts.length + memberSum;
382
482
  let maxLocalId = ownEntities.reduce((m, e) => Math.max(m, e.localId), -1);
@@ -417,7 +517,7 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
417
517
  }
418
518
  if (overlapLids.size > 0) {
419
519
  const overlapping = Array.from(overlapLids).sort((a, b) => a - b);
420
- return err3({
520
+ return err4({
421
521
  code: "pack-mount-localid-overlap",
422
522
  expected: "each LocalEntityId claimed by exactly one entity or mount slot",
423
523
  hint: PACK_ERROR_HINTS["pack-mount-localid-overlap"],
@@ -457,7 +557,7 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
457
557
  const childMapping = childInstRes.value.mapping;
458
558
  mountInstances.push({ mount, root: childRes.value, mapping: childMapping });
459
559
  if (childMapping.length !== mount.memberCount) {
460
- return err3({
560
+ return err4({
461
561
  code: "pack-mount-count-mismatch",
462
562
  expected: "mount.memberCount === child SceneAsset totalSlots",
463
563
  hint: PACK_ERROR_HINTS["pack-mount-count-mismatch"],
@@ -528,7 +628,7 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
528
628
  }
529
629
  }
530
630
  }
531
- return ok3({
631
+ return ok4({
532
632
  mapping,
533
633
  entityToLocalId,
534
634
  rootEntities,
@@ -541,7 +641,7 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
541
641
  function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnostics) {
542
642
  const sceneInstanceToken = world.components.resolve("SceneInstance");
543
643
  if (sceneInstanceToken === void 0) {
544
- return err3(new ComponentNotDefinedError("SceneInstance"));
644
+ return err4(new ComponentNotDefinedError("SceneInstance"));
545
645
  }
546
646
  const childOfToken = world.components.resolve("ChildOf");
547
647
  const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);
@@ -599,8 +699,18 @@ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnos
599
699
  }
600
700
  }
601
701
  const detached = /* @__PURE__ */ new Set();
702
+ const bindings = /* @__PURE__ */ new Map();
703
+ for (const entity of asset.entities) {
704
+ if (entity.bindingKey === void 0) continue;
705
+ const live = mapping[entity.localId];
706
+ if (live !== void 0 && live !== ENTITY_NULL_RAW) {
707
+ bindings.set(entity.bindingKey, live);
708
+ }
709
+ }
602
710
  const state = {
603
711
  source: handle,
712
+ sceneSourceKey: asset.sourceKey,
713
+ bindings,
604
714
  entityToLocalId,
605
715
  detachedLocalIds: detached,
606
716
  // Convert overrides Map<LocalEntityId, Map<string, MountOverride>>
@@ -641,7 +751,7 @@ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnos
641
751
  if (!r.ok) return r;
642
752
  }
643
753
  }
644
- return ok3(rootEntity);
754
+ return ok4(rootEntity);
645
755
  }
646
756
  function worldInstantiateSceneAssetFlat(world, handle, asset, stack, diagnostics) {
647
757
  const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);
@@ -680,7 +790,7 @@ function worldInstantiateSceneAssetFlat(world, handle, asset, stack, diagnostics
680
790
  }
681
791
  }
682
792
  }
683
- return ok3({ roots: [...rootEntities, ...mountEntitiesNeedingRootParent], mountEntities });
793
+ return ok4({ roots: [...rootEntities, ...mountEntitiesNeedingRootParent], mountEntities });
684
794
  }
685
795
  function worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics) {
686
796
  const out = [];
@@ -688,7 +798,7 @@ function worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics)
688
798
  for (const compName of Object.keys(node.components)) {
689
799
  const token = world.components.resolve(compName);
690
800
  if (token === void 0) {
691
- return err3(new ComponentNotDefinedError(compName));
801
+ return err4(new ComponentNotDefinedError(compName));
692
802
  }
693
803
  const raw = node.components[compName] ?? {};
694
804
  const schema = componentSchema(token);
@@ -715,11 +825,11 @@ function worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics)
715
825
  const filled = fillComponentDefaults(token, remappedRaw);
716
826
  out.push({ component: token, data: filled });
717
827
  }
718
- return ok3(out);
828
+ return ok4(out);
719
829
  }
720
830
  function worldApplyMountOverride(world, member, ov) {
721
831
  const ovToken = world.components.resolve(ov.comp);
722
- if (ovToken === void 0) return ok3(void 0);
832
+ if (ovToken === void 0) return ok4(void 0);
723
833
  if (ov.field !== void 0) {
724
834
  return world.set(member, ovToken, { [ov.field]: ov.value });
725
835
  }
@@ -733,7 +843,7 @@ function worldApplyMountOverride(world, member, ov) {
733
843
  }
734
844
  function worldValidateMountOverrides(world, mount) {
735
845
  const overrides = mount.overrides;
736
- if (overrides === void 0) return ok3(void 0);
846
+ if (overrides === void 0) return ok4(void 0);
737
847
  const memberFirst = mount.memberFirst;
738
848
  const memberCount = mount.memberCount;
739
849
  const memberLast = memberFirst + memberCount;
@@ -741,7 +851,7 @@ function worldValidateMountOverrides(world, mount) {
741
851
  for (const ov of overrides) {
742
852
  const ovLid = ov.localId;
743
853
  if (ovLid < memberFirst || ovLid >= memberLast) {
744
- return err3({
854
+ return err4({
745
855
  code: "pack-mount-override-localid-out-of-range",
746
856
  expected: `override.localId in [${memberFirst}, ${memberLast})`,
747
857
  hint: PACK_ERROR_HINTS["pack-mount-override-localid-out-of-range"],
@@ -758,7 +868,7 @@ function worldValidateMountOverrides(world, mount) {
758
868
  if (ovToken !== void 0) {
759
869
  const schema = componentSchema(ovToken);
760
870
  if (!(ov.field in schema)) {
761
- return err3({
871
+ return err4({
762
872
  code: "pack-mount-override-unknown-field",
763
873
  expected: `override.field defined on component '${ov.comp}'`,
764
874
  hint: PACK_ERROR_HINTS["pack-mount-override-unknown-field"],
@@ -773,13 +883,13 @@ function worldValidateMountOverrides(world, mount) {
773
883
  }
774
884
  } else {
775
885
  if (ovToken === void 0) {
776
- return err3(new ComponentNotDefinedError(ov.comp));
886
+ return err4(new ComponentNotDefinedError(ov.comp));
777
887
  }
778
888
  const schema = componentSchema(ovToken);
779
889
  const valueMap = ov.value ?? {};
780
890
  for (const key of Object.keys(valueMap)) {
781
891
  if (!(key in schema)) {
782
- return err3({
892
+ return err4({
783
893
  code: "pack-mount-override-unknown-field",
784
894
  expected: `override.value keys defined on component '${ov.comp}'`,
785
895
  hint: PACK_ERROR_HINTS["pack-mount-override-unknown-field"],
@@ -794,7 +904,7 @@ function worldValidateMountOverrides(world, mount) {
794
904
  }
795
905
  }
796
906
  }
797
- return ok3(void 0);
907
+ return ok4(void 0);
798
908
  }
799
909
  function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
800
910
  const fakeNode = {
@@ -813,7 +923,7 @@ function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
813
923
  if (cdRes.value.length === 0) {
814
924
  const childOfToken = world.components.resolve("ChildOf");
815
925
  if (childOfToken === void 0) {
816
- return err3(new ComponentNotDefinedError("ChildOf"));
926
+ return err4(new ComponentNotDefinedError("ChildOf"));
817
927
  }
818
928
  cdRes.value.push({
819
929
  component: childOfToken,
@@ -825,7 +935,7 @@ function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
825
935
  function worldResolveMountSource(world, source, parentHandle) {
826
936
  const resolver = worldGetSceneAssetResolver(world);
827
937
  if (resolver === null) {
828
- return err3({
938
+ return err4({
829
939
  code: "stale-entity",
830
940
  expected: "wired SceneAssetResolver (auto-wired by engine.assets.instantiate)",
831
941
  hint: "engine.assets.instantiate sugar wires this for you; call worldSetSceneAssetResolver before nested scene expansion.",
@@ -834,9 +944,9 @@ function worldResolveMountSource(world, source, parentHandle) {
834
944
  }
835
945
  const r = resolver(source, parentHandle);
836
946
  if (!r.ok) {
837
- return err3(r.error);
947
+ return err4(r.error);
838
948
  }
839
- return ok3(r.value);
949
+ return ok4(r.value);
840
950
  }
841
951
  function worldMountOverridesToStateMap(src) {
842
952
  const out = /* @__PURE__ */ new Map();
@@ -859,7 +969,7 @@ function worldSetUniqueRefPayload(world, handle, payload) {
859
969
  function worldResolveSceneInstanceStatePayload(world, root) {
860
970
  const sceneInstanceToken = world.components.resolve("SceneInstance");
861
971
  if (sceneInstanceToken === void 0) {
862
- return err3(new ComponentNotDefinedError("SceneInstance"));
972
+ return err4(new ComponentNotDefinedError("SceneInstance"));
863
973
  }
864
974
  const r = world.get(root, sceneInstanceToken);
865
975
  if (!r.ok) return r;
@@ -867,7 +977,7 @@ function worldResolveSceneInstanceStatePayload(world, root) {
867
977
  const stateRefHandle = toUnique(stateRefRaw);
868
978
  const payload = sceneWorldState(world).statePayloads.get(Number(stateRefHandle));
869
979
  if (payload === void 0) {
870
- return err3(
980
+ return err4(
871
981
  new StaleEntityError(root, entityIndex(root), entityGeneration(root), {
872
982
  operation: "resolveSceneInstanceState",
873
983
  component: "SceneInstance",
@@ -876,17 +986,27 @@ function worldResolveSceneInstanceStatePayload(world, root) {
876
986
  })
877
987
  );
878
988
  }
879
- return ok3(payload);
989
+ return ok4(payload);
880
990
  }
881
991
  function worldGetSceneInstanceState(world, root) {
882
992
  return worldResolveSceneInstanceStatePayload(world, root);
883
993
  }
994
+ function worldResolveSceneEntity(world, root, ref) {
995
+ const state = worldResolveSceneInstanceStatePayload(world, root);
996
+ if (!state.ok) return state;
997
+ const resolved = resolveSceneEntity(ref, {
998
+ sceneSourceKey: state.value.sceneSourceKey ?? ref.sceneSourceKey,
999
+ bindings: state.value.bindings
1000
+ });
1001
+ if (!resolved.ok) return err4(resolved.error);
1002
+ return ok4(resolved.value);
1003
+ }
884
1004
  function worldDespawnScene(world, root, opts) {
885
1005
  const dRes = worldDespawnDescendants(world, root, opts);
886
1006
  if (!dRes.ok) return dRes;
887
1007
  const drop = world.despawn(root);
888
1008
  if (!drop.ok) return drop;
889
- return ok3(dRes.value + 1);
1009
+ return ok4(dRes.value + 1);
890
1010
  }
891
1011
  function worldDespawnDescendants(world, root, opts) {
892
1012
  let detached = null;
@@ -963,7 +1083,7 @@ function worldDespawnDescendants(world, root, opts) {
963
1083
  }
964
1084
  count += 1;
965
1085
  }
966
- return ok3(count);
1086
+ return ok4(count);
967
1087
  }
968
1088
  function worldSetSceneOverride(world, root, member, component, field, value) {
969
1089
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
@@ -971,7 +1091,7 @@ function worldSetSceneOverride(world, root, member, component, field, value) {
971
1091
  const state = stateRes.value;
972
1092
  const lid = state.entityToLocalId.get(member);
973
1093
  if (lid === void 0) {
974
- return err3(
1094
+ return err4(
975
1095
  new StaleEntityError(
976
1096
  member,
977
1097
  entityIndex(member),
@@ -990,7 +1110,7 @@ function worldSetSceneOverride(world, root, member, component, field, value) {
990
1110
  const expectJsType = primitiveJsType(schemaType);
991
1111
  const actualJsType = typeof value;
992
1112
  if (expectJsType !== actualJsType) {
993
- return err3({
1113
+ return err4({
994
1114
  code: "scene-override-type-mismatch",
995
1115
  expected: `value typeof === ${expectJsType}`,
996
1116
  hint: `setSceneOverride(${component.name}.${field}) expected ${expectJsType}, got ${actualJsType}; coerce or pick a different override path.`,
@@ -1016,14 +1136,14 @@ function worldSetSceneOverride(world, root, member, component, field, value) {
1016
1136
  field,
1017
1137
  value
1018
1138
  });
1019
- return ok3(void 0);
1139
+ return ok4(void 0);
1020
1140
  }
1021
1141
  function worldRemoveSceneOverride(world, root, member, component, field) {
1022
1142
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1023
1143
  if (!stateRes.ok) return stateRes;
1024
1144
  const state = stateRes.value;
1025
1145
  const lid = state.entityToLocalId.get(member);
1026
- if (lid === void 0) return ok3(void 0);
1146
+ if (lid === void 0) return ok4(void 0);
1027
1147
  const fieldMap = state.overrides.get(lid);
1028
1148
  if (fieldMap !== void 0) {
1029
1149
  fieldMap.delete(`${component.name}:${field}`);
@@ -1039,34 +1159,34 @@ function worldRemoveSceneOverride(world, root, member, component, field) {
1039
1159
  const r = world.set(member, component, { [field]: layer1[field] });
1040
1160
  if (!r.ok) return r;
1041
1161
  }
1042
- return ok3(void 0);
1162
+ return ok4(void 0);
1043
1163
  }
1044
1164
  function worldDetachSceneMember(world, root, member) {
1045
1165
  const sceneInstanceToken = world.components.resolve("SceneInstance");
1046
1166
  if (sceneInstanceToken === void 0) {
1047
- return err3(new ComponentNotDefinedError("SceneInstance"));
1167
+ return err4(new ComponentNotDefinedError("SceneInstance"));
1048
1168
  }
1049
1169
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1050
1170
  if (!stateRes.ok) return stateRes;
1051
1171
  const state = stateRes.value;
1052
1172
  const lid = state.entityToLocalId.get(member);
1053
- if (lid === void 0) return ok3(void 0);
1173
+ if (lid === void 0) return ok4(void 0);
1054
1174
  state.detachedLocalIds.add(lid);
1055
- return ok3(void 0);
1175
+ return ok4(void 0);
1056
1176
  }
1057
1177
  function worldReattachSceneMember(world, root, member) {
1058
1178
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1059
1179
  if (!stateRes.ok) return stateRes;
1060
1180
  const state = stateRes.value;
1061
1181
  const lid = state.entityToLocalId.get(member);
1062
- if (lid === void 0) return ok3(void 0);
1182
+ if (lid === void 0) return ok4(void 0);
1063
1183
  state.detachedLocalIds.delete(lid);
1064
- return ok3(void 0);
1184
+ return ok4(void 0);
1065
1185
  }
1066
1186
  function worldGetSceneAssetForInstance(world, root) {
1067
1187
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1068
1188
  if (!stateRes.ok) return stateRes;
1069
- return ok3(stateRes.value.source);
1189
+ return ok4(stateRes.value.source);
1070
1190
  }
1071
1191
  function sceneTopoSort(nodes) {
1072
1192
  const n = nodes.length;
@@ -1109,21 +1229,6 @@ function sceneTopoSort(nodes) {
1109
1229
  }
1110
1230
  return order;
1111
1231
  }
1112
- function mountOverrideStateKey(ov) {
1113
- return ov.field !== void 0 ? `${ov.comp}:${ov.field}` : ov.comp;
1114
- }
1115
- function isPrimitiveScalarFieldType(fieldType) {
1116
- if (fieldType === "f32" || fieldType === "f64" || fieldType === "u32" || fieldType === "i32" || fieldType === "u8" || fieldType === "i8" || fieldType === "u16" || fieldType === "i16" || fieldType === "bool" || fieldType === "string") {
1117
- return true;
1118
- }
1119
- if (fieldType.startsWith("enum<")) return true;
1120
- return false;
1121
- }
1122
- function primitiveJsType(fieldType) {
1123
- if (fieldType === "bool") return "boolean";
1124
- if (fieldType === "string") return "string";
1125
- return "number";
1126
- }
1127
1232
 
1128
1233
  // src/systems/propagate-transforms.ts
1129
1234
  import {
@@ -1132,316 +1237,338 @@ import {
1132
1237
  FixedUpdate,
1133
1238
  Update
1134
1239
  } from "@forgeax/engine-ecs";
1135
- import {
1136
- createWorldProjection as createWorldProjection2,
1137
- setDerivedComponent
1138
- } from "@forgeax/engine-ecs/projection";
1139
1240
  import { mat4 } from "@forgeax/engine-math";
1140
- import { err as err4, ok as ok4 } from "@forgeax/engine-types";
1141
-
1142
- // src/systems/hierarchy-projection.ts
1143
- import { Entity } from "@forgeax/engine-ecs";
1144
- import { createWorldProjection } from "@forgeax/engine-ecs/projection";
1145
- var HIERARCHY_PROJECTION_CACHE = /* @__PURE__ */ new WeakMap();
1146
- function diagnostic(code, entity, parent) {
1147
- if (code === "hierarchy-cycle") {
1148
- return {
1149
- code,
1150
- expected: "ChildOf parent edges form an acyclic live hierarchy",
1151
- hint: "remove one ChildOf edge from the reported cycle, then re-run the extract",
1152
- detail: { entity, parent }
1153
- };
1154
- }
1155
- return {
1156
- code,
1157
- expected: "ChildOf.parent references a live entity in the same World",
1158
- hint: "remove the stale ChildOf component or restore the referenced parent in this World",
1159
- detail: { entity, parent }
1160
- };
1161
- }
1162
- function projectHierarchy(world) {
1163
- const cached = HIERARCHY_PROJECTION_CACHE.get(world);
1164
- const changes = cached?.changes ?? createWorldProjection(world, { components: [ChildOf] });
1165
- if (cached !== void 0) {
1166
- const evidence = changes.poll();
1167
- if (evidence.status === "delta" && evidence.changes.length === 0) return cached.snapshot;
1168
- }
1169
- const liveEntities = /* @__PURE__ */ new Set();
1170
- const authoredParents = /* @__PURE__ */ new Map();
1171
- const query = world.query({ read: [Entity], optional: [ChildOf] });
1172
- if (query.ok) {
1173
- for (const row of query.value) {
1174
- liveEntities.add(row.entity);
1175
- const parent = row.get(ChildOf)?.parent;
1176
- if (parent !== void 0 && parent !== null) authoredParents.set(row.entity, parent);
1177
- }
1178
- }
1179
- const parentOf = /* @__PURE__ */ new Map();
1180
- const diagnostics = [];
1181
- for (const [entity, parent] of authoredParents) {
1182
- if (liveEntities.has(parent)) {
1183
- parentOf.set(entity, parent);
1184
- } else {
1185
- diagnostics.push(diagnostic("hierarchy-broken", entity, parent));
1186
- }
1187
- }
1188
- const state = /* @__PURE__ */ new Map();
1189
- const stack = [];
1190
- const cycleMembers = /* @__PURE__ */ new Set();
1191
- const visit = (entity) => {
1192
- const currentState = state.get(entity) ?? 0;
1193
- if (currentState === 2) return;
1194
- if (currentState === 1) {
1195
- const cycleStart = stack.indexOf(entity);
1196
- for (let index = cycleStart; index >= 0 && index < stack.length; index++) {
1197
- const member = stack[index];
1198
- if (member !== void 0) cycleMembers.add(member);
1199
- }
1200
- return;
1201
- }
1202
- state.set(entity, 1);
1203
- stack.push(entity);
1204
- const parent = parentOf.get(entity);
1205
- if (parent !== void 0) visit(parent);
1206
- stack.pop();
1207
- state.set(entity, 2);
1208
- };
1209
- for (const entity of liveEntities) visit(entity);
1210
- for (const entity of cycleMembers) {
1211
- const parent = authoredParents.get(entity);
1212
- if (parent !== void 0) diagnostics.push(diagnostic("hierarchy-cycle", entity, parent));
1213
- parentOf.delete(entity);
1214
- }
1215
- diagnostics.sort((left, right) => {
1216
- const entityDelta = left.detail.entity - right.detail.entity;
1217
- if (entityDelta !== 0) return entityDelta;
1218
- return left.code.localeCompare(right.code);
1219
- });
1220
- const stableParentOf = new Map(parentOf);
1221
- const stableDiagnostics = Object.freeze(diagnostics.slice());
1222
- const snapshot = {
1223
- parentOf: stableParentOf,
1224
- diagnostics: stableDiagnostics,
1225
- getParent(entity) {
1226
- return stableParentOf.get(entity);
1227
- }
1228
- };
1229
- HIERARCHY_PROJECTION_CACHE.set(world, {
1230
- changes,
1231
- snapshot
1232
- });
1233
- return snapshot;
1234
- }
1235
-
1236
- // src/systems/propagate-transforms.ts
1241
+ import { err as err5, ok as ok5 } from "@forgeax/engine-types";
1237
1242
  var PROPAGATE_TRANSFORMS_SYSTEM = "propagateTransforms";
1238
1243
  var PROPAGATE_TRANSFORMS_FIXED_SYSTEM = "propagateTransformsFixed";
1239
1244
  var TransformSet = defineSystemSet({ name: "transform" });
1240
1245
  var TransformFixedSet = defineSystemSet({ name: "transform-fixed" });
1241
- var CACHE = /* @__PURE__ */ new WeakMap();
1246
+ var SCRATCH = /* @__PURE__ */ new WeakMap();
1242
1247
  var REGISTRATION_LEASES = /* @__PURE__ */ new WeakMap();
1243
- function copyState(value) {
1244
- return {
1245
- pos: new Float32Array(value.pos),
1246
- quat: new Float32Array(value.quat),
1247
- scale: new Float32Array(value.scale)
1248
- };
1248
+ function pairError(entity, expected) {
1249
+ return err5(
1250
+ new SceneError({
1251
+ code: "hierarchy-broken",
1252
+ expected,
1253
+ hint: "attach both Transform and GlobalTransform at scene authoring or import time, then retry propagation",
1254
+ detail: { entity, parent: entity }
1255
+ })
1256
+ );
1249
1257
  }
1250
- function sameArray(left, right) {
1251
- if (left.length !== right.length) return false;
1252
- for (let index = 0; index < left.length; index += 1) {
1253
- if (left[index] !== right[index]) return false;
1254
- }
1255
- return true;
1258
+ function ensureQueries(world, scratch) {
1259
+ if (scratch.flatQuery !== void 0 && scratch.outputQuery !== void 0 && scratch.hierarchyQuery !== void 0 && scratch.missingGlobalQuery !== void 0 && scratch.missingTransformQuery !== void 0) {
1260
+ return ok5(void 0);
1261
+ }
1262
+ const flatOutput = world.query({
1263
+ read: [Transform],
1264
+ write: [GlobalTransform],
1265
+ without: [ChildOf],
1266
+ changed: [Transform]
1267
+ });
1268
+ const output = world.query({ write: [GlobalTransform] });
1269
+ const hierarchy = world.query({ read: [Transform, ChildOf] });
1270
+ const missingGlobal = world.query({ with: [Transform], without: [GlobalTransform] });
1271
+ const missingTransform = world.query({ with: [GlobalTransform], without: [Transform] });
1272
+ if (!flatOutput.ok || !output.ok || !hierarchy.ok || !missingGlobal.ok || !missingTransform.ok) {
1273
+ return pairError(0, "valid Transform and GlobalTransform pair queries");
1274
+ }
1275
+ scratch.flatQuery = flatOutput.value;
1276
+ scratch.outputQuery = output.value;
1277
+ scratch.hierarchyQuery = hierarchy.value;
1278
+ scratch.missingGlobalQuery = missingGlobal.value;
1279
+ scratch.missingTransformQuery = missingTransform.value;
1280
+ return ok5(void 0);
1256
1281
  }
1257
- function sameState(left, right) {
1258
- return left !== void 0 && sameArray(left.pos, right.pos) && sameArray(left.quat, right.quat) && sameArray(left.scale, right.scale);
1282
+ function validateTransformPairs(world, scratch) {
1283
+ const queryResult = ensureQueries(world, scratch);
1284
+ if (!queryResult.ok) return queryResult;
1285
+ const missingGlobal = scratch.missingGlobalQuery;
1286
+ const missingTransform = scratch.missingTransformQuery;
1287
+ if (missingGlobal === void 0 || missingTransform === void 0) {
1288
+ return pairError(0, "valid Transform and GlobalTransform pair queries");
1289
+ }
1290
+ for (const row of missingGlobal) {
1291
+ return pairError(row.entity, "each Transform entity to carry a GlobalTransform pair");
1292
+ }
1293
+ for (const row of missingTransform) {
1294
+ return pairError(row.entity, "each GlobalTransform entity to carry a Transform pair");
1295
+ }
1296
+ return ok5(void 0);
1259
1297
  }
1260
- function compose(state, out) {
1261
- mat4.compose(out, state.pos, state.quat, state.scale);
1298
+ function scratchFor(world) {
1299
+ const existing = SCRATCH.get(world);
1300
+ if (existing !== void 0) return existing;
1301
+ const created = {
1302
+ position: new Float32Array(3),
1303
+ rotation: new Float32Array(4),
1304
+ scale: new Float32Array(3),
1305
+ local: mat4.create(),
1306
+ hierarchyPath: []
1307
+ };
1308
+ SCRATCH.set(world, created);
1309
+ return created;
1262
1310
  }
1263
- function indexChildren(hierarchy) {
1264
- const childrenOf = /* @__PURE__ */ new Map();
1265
- for (const [child, parent] of hierarchy.parentOf) {
1266
- const children = childrenOf.get(parent);
1267
- if (children === void 0) childrenOf.set(parent, [child]);
1268
- else children.push(child);
1269
- }
1270
- return childrenOf;
1311
+ function composeColumns(position, rotation, scale, out, scratch, positionStart = 0, rotationStart = 0) {
1312
+ scratch.position[0] = position[positionStart] ?? 0;
1313
+ scratch.position[1] = position[positionStart + 1] ?? 0;
1314
+ scratch.position[2] = position[positionStart + 2] ?? 0;
1315
+ scratch.rotation[0] = rotation[rotationStart] ?? 0;
1316
+ scratch.rotation[1] = rotation[rotationStart + 1] ?? 0;
1317
+ scratch.rotation[2] = rotation[rotationStart + 2] ?? 0;
1318
+ scratch.rotation[3] = rotation[rotationStart + 3] ?? 1;
1319
+ scratch.scale[0] = scale[positionStart] ?? 1;
1320
+ scratch.scale[1] = scale[positionStart + 1] ?? 1;
1321
+ scratch.scale[2] = scale[positionStart + 2] ?? 1;
1322
+ mat4.compose(out, scratch.position, scratch.rotation, scratch.scale);
1271
1323
  }
1272
- function descendants(childrenOf, roots) {
1273
- const affected = new Set(roots);
1274
- const pending = [...roots];
1275
- for (let index = 0; index < pending.length; index += 1) {
1276
- const parent = pending[index];
1277
- if (parent === void 0) continue;
1278
- for (const child of childrenOf.get(parent) ?? []) {
1279
- if (affected.has(child)) continue;
1280
- affected.add(child);
1281
- pending.push(child);
1282
- }
1324
+ function composeFlatColumns(positions, rotations, scales, worlds, count) {
1325
+ for (let row = 0; row < count; row += 1) {
1326
+ const position = row * 3;
1327
+ const rotation = row * 4;
1328
+ const world = row * 16;
1329
+ const x = rotations[rotation] ?? 0;
1330
+ const y = rotations[rotation + 1] ?? 0;
1331
+ const z = rotations[rotation + 2] ?? 0;
1332
+ const w = rotations[rotation + 3] ?? 1;
1333
+ const x2 = x + x;
1334
+ const y2 = y + y;
1335
+ const z2 = z + z;
1336
+ const xx = x * x2;
1337
+ const xy = x * y2;
1338
+ const xz = x * z2;
1339
+ const yy = y * y2;
1340
+ const yz = y * z2;
1341
+ const zz = z * z2;
1342
+ const wx = w * x2;
1343
+ const wy = w * y2;
1344
+ const wz = w * z2;
1345
+ const sx = scales[position] ?? 1;
1346
+ const sy = scales[position + 1] ?? 1;
1347
+ const sz = scales[position + 2] ?? 1;
1348
+ worlds[world] = (1 - (yy + zz)) * sx;
1349
+ worlds[world + 1] = (xy + wz) * sx;
1350
+ worlds[world + 2] = (xz - wy) * sx;
1351
+ worlds[world + 3] = 0;
1352
+ worlds[world + 4] = (xy - wz) * sy;
1353
+ worlds[world + 5] = (1 - (xx + zz)) * sy;
1354
+ worlds[world + 6] = (yz + wx) * sy;
1355
+ worlds[world + 7] = 0;
1356
+ worlds[world + 8] = (xz + wy) * sz;
1357
+ worlds[world + 9] = (yz - wx) * sz;
1358
+ worlds[world + 10] = (1 - (xx + yy)) * sz;
1359
+ worlds[world + 11] = 0;
1360
+ worlds[world + 12] = positions[position] ?? 0;
1361
+ worlds[world + 13] = positions[position + 1] ?? 0;
1362
+ worlds[world + 14] = positions[position + 2] ?? 0;
1363
+ worlds[world + 15] = 1;
1283
1364
  }
1284
- return affected;
1285
- }
1286
- function hierarchyError(hierarchy) {
1287
- const first = hierarchy.diagnostics[0];
1288
- if (first === void 0) return ok4(void 0);
1289
- return err4(
1290
- new SceneError({
1291
- code: first.code,
1292
- expected: first.expected,
1293
- hint: first.hint,
1294
- detail: first.detail
1295
- })
1296
- );
1297
1365
  }
1298
- function buildCache(world, projection) {
1299
- const hierarchy = projectHierarchy(world);
1300
- const locals = /* @__PURE__ */ new Map();
1301
- const entities = /* @__PURE__ */ new Set();
1302
- const query = world.query({ read: [Transform], optional: [ChildOf] });
1303
- if (query.ok) {
1304
- for (const row of query.value) {
1305
- const transform = row.get(Transform);
1306
- if (transform === void 0) continue;
1307
- entities.add(row.entity);
1308
- locals.set(row.entity, copyState(transform));
1366
+ function writeWorld(world, output, entity, value) {
1367
+ const row = output.at(entity);
1368
+ if (row === void 0) {
1369
+ return err5(
1370
+ new SceneError({
1371
+ code: "hierarchy-broken",
1372
+ expected: "each Transform entity to carry a GlobalTransform pair",
1373
+ hint: "attach GlobalTransform at scene authoring or import time, then retry propagation",
1374
+ detail: { entity, parent: entity }
1375
+ })
1376
+ );
1377
+ }
1378
+ const current = world.get(entity, GlobalTransform);
1379
+ if (!current.ok)
1380
+ return err5(
1381
+ new SceneError({
1382
+ code: "hierarchy-broken",
1383
+ expected: "each Transform entity to carry a GlobalTransform pair",
1384
+ hint: "attach GlobalTransform at scene authoring or import time, then retry propagation",
1385
+ detail: { entity, parent: entity }
1386
+ })
1387
+ );
1388
+ let changed = false;
1389
+ for (let index = 0; index < value.length; index += 1) {
1390
+ if (current.value.world[index] !== value[index]) {
1391
+ changed = true;
1392
+ break;
1309
1393
  }
1310
1394
  }
1311
- return {
1312
- projection,
1313
- hierarchy,
1314
- parentOf: hierarchy.parentOf,
1315
- childrenOf: indexChildren(hierarchy),
1316
- entities,
1317
- locals,
1318
- derived: /* @__PURE__ */ new Map(),
1319
- result: ok4(void 0)
1320
- };
1395
+ if (!changed) return ok5(void 0);
1396
+ row.mut(GlobalTransform).world.set(value);
1397
+ return ok5(void 0);
1321
1398
  }
1322
- function deriveEntity(world, entity, cache, affected, visiting, published) {
1323
- if (!affected.has(entity) || cache.derived.has(entity)) return ok4(void 0);
1324
- if (visiting.has(entity)) return ok4(void 0);
1325
- visiting.add(entity);
1326
- const local = cache.locals.get(entity);
1327
- if (local === void 0) {
1328
- visiting.delete(entity);
1329
- return ok4(void 0);
1330
- }
1331
- const parent = cache.parentOf.get(entity);
1332
- if (parent !== void 0 && cache.entities.has(parent)) {
1333
- const parentResult = deriveEntity(world, parent, cache, affected, visiting, published);
1334
- if (!parentResult.ok) return parentResult;
1335
- }
1336
- const localWorld = mat4.create();
1337
- compose(local, localWorld);
1338
- const parentWorld = parent === void 0 ? void 0 : cache.derived.get(parent);
1339
- const resolved = mat4.create();
1340
- if (parentWorld === void 0) resolved.set(localWorld);
1341
- else mat4.multiply(resolved, parentWorld, localWorld);
1342
- const write = setDerivedComponent(world, entity, Transform, { world: resolved });
1343
- if (!write.ok) {
1344
- visiting.delete(entity);
1345
- return err4(
1399
+ function propagateFlat(scratch) {
1400
+ const query = scratch.flatQuery;
1401
+ if (query === void 0)
1402
+ return err5(
1346
1403
  new SceneError({
1347
1404
  code: "hierarchy-broken",
1348
- expected: "the derived Transform.world write to succeed",
1349
- hint: "inspect the ECS mutation error before retrying transform propagation",
1350
- detail: { entity, parent: entity }
1405
+ expected: "a valid changed Transform write query",
1406
+ hint: "register the scene components before running TransformPropagation"
1407
+ })
1408
+ );
1409
+ const spans = query.spans();
1410
+ if (!spans.ok) return pairError(0, "dense numeric Transform spans");
1411
+ let bindingIndex = 0;
1412
+ try {
1413
+ for (const span of spans.value) {
1414
+ const local = span.get(Transform);
1415
+ const world = span.mut(GlobalTransform).world;
1416
+ composeFlatColumns(local.pos, local.quat, local.scale, world, span.length);
1417
+ bindingIndex += 1;
1418
+ }
1419
+ } catch (cause) {
1420
+ const error = cause;
1421
+ return err5(
1422
+ new SceneError({
1423
+ code: "hierarchy-broken",
1424
+ expected: "derived GlobalTransform range publication to succeed",
1425
+ hint: error.hint,
1426
+ detail: {
1427
+ kind: "derived-write",
1428
+ entity: 0,
1429
+ parent: 0,
1430
+ bindingIndex,
1431
+ base: 0,
1432
+ start: 0,
1433
+ count: 0,
1434
+ cause: error
1435
+ }
1351
1436
  })
1352
1437
  );
1353
1438
  }
1354
- cache.derived.set(entity, resolved);
1355
- published.add(entity);
1356
- visiting.delete(entity);
1357
- return ok4(void 0);
1439
+ return ok5(void 0);
1358
1440
  }
1359
- function drainPublishedChanges(cache, published) {
1360
- const drained = cache.projection.poll();
1361
- if (drained.status === "rebuild") return true;
1362
- return drained.changes.every(
1363
- (change) => change.kind === "derived-component-changed" && change.component === Transform && published.has(change.entity)
1364
- );
1441
+ function findEntry(entries, entity) {
1442
+ for (let index = 0; index < entries.length; index += 1) {
1443
+ if (entries[index]?.entity === entity) return index;
1444
+ }
1445
+ return -1;
1365
1446
  }
1366
- function propagateTransforms(world, _hierarchy) {
1367
- let cache = CACHE.get(world);
1368
- if (cache === void 0) {
1369
- cache = buildCache(world, createWorldProjection2(world, { components: [Transform, ChildOf] }));
1370
- CACHE.set(world, cache);
1371
- const initial = new Set(cache.entities);
1372
- const published2 = /* @__PURE__ */ new Set();
1373
- for (const entity of initial) {
1374
- const result = deriveEntity(world, entity, cache, initial, /* @__PURE__ */ new Set(), published2);
1375
- if (!result.ok) return result;
1376
- }
1377
- if (!drainPublishedChanges(cache, published2)) {
1378
- CACHE.delete(world);
1379
- return propagateTransforms(world);
1380
- }
1381
- cache.result = hierarchyError(cache.hierarchy);
1382
- return cache.result;
1383
- }
1384
- const evidence = cache.projection.poll();
1385
- if (evidence.status === "rebuild") {
1386
- cache = buildCache(world, cache.projection);
1387
- CACHE.set(world, cache);
1388
- const all = new Set(cache.entities);
1389
- const published2 = /* @__PURE__ */ new Set();
1390
- for (const entity of all) {
1391
- const result = deriveEntity(world, entity, cache, all, /* @__PURE__ */ new Set(), published2);
1392
- if (!result.ok) return result;
1393
- }
1394
- if (!drainPublishedChanges(cache, published2)) {
1395
- CACHE.delete(world);
1396
- return propagateTransforms(world);
1447
+ function resolveEntry(index, entries, world, output, scratch) {
1448
+ const entry = entries[index];
1449
+ if (entry === void 0 || entry.state === 2) return ok5(void 0);
1450
+ if (entry.state === 1) {
1451
+ const cycleParent = entry.parent ?? entry.entity;
1452
+ const cycleStart = scratch.hierarchyPath.indexOf(index);
1453
+ const start = cycleStart < 0 ? 0 : cycleStart;
1454
+ for (let pathIndex = start; pathIndex < scratch.hierarchyPath.length; pathIndex += 1) {
1455
+ const cycleEntry = entries[scratch.hierarchyPath[pathIndex] ?? -1];
1456
+ if (cycleEntry !== void 0) cycleEntry.parent = void 0;
1397
1457
  }
1398
- cache.result = hierarchyError(cache.hierarchy);
1399
- return cache.result;
1400
- }
1401
- if (evidence.changes.length === 0) return cache.result;
1402
- const transformSeeds = /* @__PURE__ */ new Set();
1403
- let rebuild = false;
1404
- for (const change of evidence.changes) {
1405
- if (change.kind === "entity-removed" || change.kind === "component-added" || change.kind === "component-removed") {
1406
- rebuild = true;
1407
- break;
1408
- }
1409
- if (change.kind !== "component-changed") continue;
1410
- if (change.component === ChildOf) {
1411
- rebuild = true;
1412
- break;
1458
+ return err5(
1459
+ new SceneError({
1460
+ code: "hierarchy-cycle",
1461
+ expected: "a parent-before-child Transform hierarchy",
1462
+ hint: "repair the ChildOf cycle and retry TransformPropagation",
1463
+ detail: { entity: entry.entity, parent: cycleParent }
1464
+ })
1465
+ );
1466
+ }
1467
+ entry.state = 1;
1468
+ scratch.hierarchyPath.push(index);
1469
+ let parentIndex = entry.parent === void 0 ? -1 : findEntry(entries, entry.parent);
1470
+ let failure;
1471
+ if (entry.parent !== void 0 && parentIndex < 0) {
1472
+ const local = world.get(entry.parent, Transform);
1473
+ const global = world.get(entry.parent, GlobalTransform);
1474
+ if (!local.ok || !global.ok) {
1475
+ failure = new SceneError({
1476
+ code: "hierarchy-broken",
1477
+ expected: "each ChildOf parent to carry Transform and GlobalTransform",
1478
+ hint: "repair the missing parent pair before retrying propagation",
1479
+ detail: { entity: entry.entity, parent: entry.parent }
1480
+ });
1481
+ entry.parent = void 0;
1482
+ parentIndex = -1;
1483
+ } else {
1484
+ const relation = world.get(entry.parent, ChildOf);
1485
+ entries.push({
1486
+ entity: entry.parent,
1487
+ parent: relation.ok && relation.value.parent !== null ? relation.value.parent : void 0,
1488
+ position: new Float32Array(local.value.pos),
1489
+ rotation: new Float32Array(local.value.quat),
1490
+ scale: new Float32Array(local.value.scale),
1491
+ world: mat4.create(),
1492
+ state: 0
1493
+ });
1494
+ parentIndex = entries.length - 1;
1413
1495
  }
1414
- if (change.component === Transform) transformSeeds.add(change.entity);
1415
- }
1416
- if (rebuild) {
1417
- CACHE.delete(world);
1418
- return propagateTransforms(world);
1419
- }
1420
- const changed = /* @__PURE__ */ new Set();
1421
- for (const entity of transformSeeds) {
1422
- const current = world.get(entity, Transform);
1423
- if (!current.ok) {
1424
- CACHE.delete(world);
1425
- return propagateTransforms(world);
1496
+ }
1497
+ if (parentIndex >= 0) {
1498
+ const parentResult = resolveEntry(parentIndex, entries, world, output, scratch);
1499
+ if (!parentResult.ok) failure = parentResult.error;
1500
+ parentIndex = entry.parent === void 0 ? -1 : findEntry(entries, entry.parent);
1501
+ if (parentIndex >= 0 && entries[parentIndex]?.state !== 2) parentIndex = -1;
1502
+ }
1503
+ composeColumns(entry.position, entry.rotation, entry.scale, scratch.local, scratch);
1504
+ if (parentIndex >= 0) {
1505
+ const parent = entries[parentIndex];
1506
+ if (parent !== void 0) mat4.multiply(entry.world, parent.world, scratch.local);
1507
+ } else {
1508
+ entry.world.set(scratch.local);
1509
+ }
1510
+ const write = writeWorld(world, output, entry.entity, entry.world);
1511
+ if (!write.ok) {
1512
+ scratch.hierarchyPath.pop();
1513
+ return write;
1514
+ }
1515
+ entry.state = 2;
1516
+ scratch.hierarchyPath.pop();
1517
+ return failure === void 0 ? ok5(void 0) : err5(failure);
1518
+ }
1519
+ function propagateHierarchy(world, scratch) {
1520
+ const query = scratch.hierarchyQuery;
1521
+ const output = scratch.outputQuery;
1522
+ if (query === void 0 || output === void 0) {
1523
+ return err5(
1524
+ new SceneError({
1525
+ code: "hierarchy-broken",
1526
+ expected: "paired Transform and GlobalTransform query access",
1527
+ hint: "register the scene components before running TransformPropagation"
1528
+ })
1529
+ );
1530
+ }
1531
+ const entries = [];
1532
+ for (const row of query) {
1533
+ if (findEntry(entries, row.entity) >= 0) continue;
1534
+ const local = row.get(Transform);
1535
+ const parentValue = row.get(ChildOf).parent;
1536
+ const parent = parentValue === null ? void 0 : parentValue;
1537
+ entries.push({
1538
+ entity: row.entity,
1539
+ parent,
1540
+ position: new Float32Array(local.pos),
1541
+ rotation: new Float32Array(local.quat),
1542
+ scale: new Float32Array(local.scale),
1543
+ world: mat4.create(),
1544
+ state: 0
1545
+ });
1546
+ }
1547
+ scratch.hierarchyPath.length = 0;
1548
+ let firstError;
1549
+ for (let index = 0; index < entries.length; index += 1) {
1550
+ const result = resolveEntry(index, entries, world, output, scratch);
1551
+ if (!result.ok) {
1552
+ const currentEntity = Number(result.error.detail?.entity ?? Number.MAX_SAFE_INTEGER);
1553
+ if (firstError === void 0) {
1554
+ firstError = result.error;
1555
+ } else {
1556
+ const firstEntity = Number(firstError.detail?.entity ?? Number.MAX_SAFE_INTEGER);
1557
+ if (currentEntity < firstEntity || currentEntity === firstEntity && result.error.code.localeCompare(firstError.code) < 0) {
1558
+ firstError = result.error;
1559
+ }
1560
+ }
1426
1561
  }
1427
- const next = copyState(current.value);
1428
- if (!sameState(cache.locals.get(entity), next)) changed.add(entity);
1429
- cache.locals.set(entity, next);
1430
- }
1431
- if (changed.size === 0) return cache.result;
1432
- const affected = descendants(cache.childrenOf, changed);
1433
- const published = /* @__PURE__ */ new Set();
1434
- for (const entity of affected) cache.derived.delete(entity);
1435
- for (const entity of affected) {
1436
- const result = deriveEntity(world, entity, cache, affected, /* @__PURE__ */ new Set(), published);
1437
- if (!result.ok) return result;
1438
- }
1439
- if (!drainPublishedChanges(cache, published)) {
1440
- CACHE.delete(world);
1441
- return propagateTransforms(world);
1442
- }
1443
- cache.result = hierarchyError(cache.hierarchy);
1444
- return cache.result;
1562
+ }
1563
+ return firstError === void 0 ? ok5(void 0) : err5(firstError);
1564
+ }
1565
+ function propagateTransforms(world) {
1566
+ const scratch = scratchFor(world);
1567
+ const pairs = validateTransformPairs(world, scratch);
1568
+ if (!pairs.ok) return pairs;
1569
+ const flat = propagateFlat(scratch);
1570
+ if (!flat.ok) return flat;
1571
+ return propagateHierarchy(world, scratch);
1445
1572
  }
1446
1573
  var PropagateTransforms = defineSystem({
1447
1574
  name: PROPAGATE_TRANSFORMS_SYSTEM,
@@ -1469,7 +1596,7 @@ function registerPropagateTransforms(world, options = {}) {
1469
1596
  world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);
1470
1597
  world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);
1471
1598
  REGISTRATION_LEASES.delete(world);
1472
- CACHE.delete(world);
1599
+ SCRATCH.delete(world);
1473
1600
  }
1474
1601
  };
1475
1602
  }
@@ -1486,23 +1613,31 @@ function registerPropagateTransforms(world, options = {}) {
1486
1613
  ]).unwrap();
1487
1614
  }
1488
1615
  world.addSystems(FixedUpdate, TransformFixedSet, [PropagateTransformsFixed]).unwrap();
1489
- const lease = { refs: 1 };
1490
- REGISTRATION_LEASES.set(world, lease);
1616
+ REGISTRATION_LEASES.set(world, { refs: 1 });
1491
1617
  let active = true;
1492
1618
  return () => {
1493
1619
  if (!active) return;
1494
1620
  active = false;
1621
+ const lease = REGISTRATION_LEASES.get(world);
1622
+ if (lease === void 0) return;
1495
1623
  lease.refs -= 1;
1496
1624
  if (lease.refs !== 0) return;
1497
1625
  world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);
1498
1626
  world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);
1499
1627
  REGISTRATION_LEASES.delete(world);
1500
- CACHE.delete(world);
1628
+ SCRATCH.delete(world);
1501
1629
  };
1502
1630
  }
1503
1631
 
1504
1632
  // src/plugin.ts
1505
- var SCENE_COMPONENTS = [ChildOf, Children, MorphWeights, Name, Transform];
1633
+ var SCENE_COMPONENTS = [
1634
+ ChildOf,
1635
+ Children,
1636
+ MorphWeights,
1637
+ Name,
1638
+ Transform,
1639
+ GlobalTransform
1640
+ ];
1506
1641
  function registerSceneComponents(world) {
1507
1642
  const leases = SCENE_COMPONENTS.map((component) => world.components.register(component).unwrap());
1508
1643
  return () => {
@@ -1519,10 +1654,115 @@ function scenePlugin() {
1519
1654
  }
1520
1655
  };
1521
1656
  }
1657
+
1658
+ // src/systems/hierarchy-projection.ts
1659
+ import { Entity } from "@forgeax/engine-ecs";
1660
+ var HIERARCHY_PROJECTION_CACHE = /* @__PURE__ */ new WeakMap();
1661
+ function createChildOfChangeQuery(world) {
1662
+ const result = world.query({ changed: [ChildOf] });
1663
+ if (!result.ok) throw result.error;
1664
+ return result.value;
1665
+ }
1666
+ function drainChanges(query) {
1667
+ let changed = false;
1668
+ for (const span of query.spans().unwrap()) changed ||= span.length > 0;
1669
+ return changed;
1670
+ }
1671
+ function diagnostic(code, entity, parent) {
1672
+ if (code === "hierarchy-cycle") {
1673
+ return {
1674
+ code,
1675
+ expected: "ChildOf parent edges form an acyclic live hierarchy",
1676
+ hint: "remove one ChildOf edge from the reported cycle, then re-run the extract",
1677
+ detail: { entity, parent }
1678
+ };
1679
+ }
1680
+ return {
1681
+ code,
1682
+ expected: "ChildOf.parent references a live entity in the same World",
1683
+ hint: "remove the stale ChildOf component or restore the referenced parent in this World",
1684
+ detail: { entity, parent }
1685
+ };
1686
+ }
1687
+ function projectHierarchy(world) {
1688
+ const cached = HIERARCHY_PROJECTION_CACHE.get(world);
1689
+ if (cached !== void 0 && cached.structureEpoch === world.getStructureEpoch() && !drainChanges(cached.childOfChanges)) {
1690
+ return cached.snapshot;
1691
+ }
1692
+ const liveEntities = /* @__PURE__ */ new Set();
1693
+ const authoredParents = /* @__PURE__ */ new Map();
1694
+ const query = world.query({ read: [Entity], optional: [ChildOf] });
1695
+ if (query.ok) {
1696
+ for (const row of query.value) {
1697
+ liveEntities.add(row.entity);
1698
+ const parent = row.get(ChildOf)?.parent;
1699
+ if (parent !== void 0 && parent !== null) authoredParents.set(row.entity, parent);
1700
+ }
1701
+ }
1702
+ const parentOf = /* @__PURE__ */ new Map();
1703
+ const diagnostics = [];
1704
+ for (const [entity, parent] of authoredParents) {
1705
+ if (liveEntities.has(parent)) {
1706
+ parentOf.set(entity, parent);
1707
+ } else {
1708
+ diagnostics.push(diagnostic("hierarchy-broken", entity, parent));
1709
+ }
1710
+ }
1711
+ const state = /* @__PURE__ */ new Map();
1712
+ const stack = [];
1713
+ const cycleMembers = /* @__PURE__ */ new Set();
1714
+ const visit = (entity) => {
1715
+ const currentState = state.get(entity) ?? 0;
1716
+ if (currentState === 2) return;
1717
+ if (currentState === 1) {
1718
+ const cycleStart = stack.indexOf(entity);
1719
+ for (let index = cycleStart; index >= 0 && index < stack.length; index++) {
1720
+ const member = stack[index];
1721
+ if (member !== void 0) cycleMembers.add(member);
1722
+ }
1723
+ return;
1724
+ }
1725
+ state.set(entity, 1);
1726
+ stack.push(entity);
1727
+ const parent = parentOf.get(entity);
1728
+ if (parent !== void 0) visit(parent);
1729
+ stack.pop();
1730
+ state.set(entity, 2);
1731
+ };
1732
+ for (const entity of liveEntities) visit(entity);
1733
+ for (const entity of cycleMembers) {
1734
+ const parent = authoredParents.get(entity);
1735
+ if (parent !== void 0) diagnostics.push(diagnostic("hierarchy-cycle", entity, parent));
1736
+ parentOf.delete(entity);
1737
+ }
1738
+ diagnostics.sort((left, right) => {
1739
+ const entityDelta = left.detail.entity - right.detail.entity;
1740
+ if (entityDelta !== 0) return entityDelta;
1741
+ return left.code.localeCompare(right.code);
1742
+ });
1743
+ const stableParentOf = new Map(parentOf);
1744
+ const stableDiagnostics = Object.freeze(diagnostics.slice());
1745
+ const snapshot = {
1746
+ parentOf: stableParentOf,
1747
+ diagnostics: stableDiagnostics,
1748
+ getParent(entity) {
1749
+ return stableParentOf.get(entity);
1750
+ }
1751
+ };
1752
+ const childOfChanges = createChildOfChangeQuery(world);
1753
+ drainChanges(childOfChanges);
1754
+ HIERARCHY_PROJECTION_CACHE.set(world, {
1755
+ structureEpoch: world.getStructureEpoch(),
1756
+ childOfChanges,
1757
+ snapshot
1758
+ });
1759
+ return snapshot;
1760
+ }
1522
1761
  export {
1523
1762
  ChildOf,
1524
1763
  Children,
1525
1764
  ComponentNotDefinedError,
1765
+ GlobalTransform,
1526
1766
  MorphWeights,
1527
1767
  Name,
1528
1768
  PROPAGATE_TRANSFORMS_SYSTEM,
@@ -1535,8 +1775,11 @@ export {
1535
1775
  projectHierarchy,
1536
1776
  propagateTransforms,
1537
1777
  registerPropagateTransforms,
1778
+ resolveSceneEntity,
1538
1779
  sceneAssetContribution,
1780
+ sceneEntity,
1539
1781
  scenePlugin,
1782
+ validateSceneBindings,
1540
1783
  worldApplyMountOverride,
1541
1784
  worldBuildSceneEntityComponentDatas,
1542
1785
  worldDespawnDescendants,
@@ -1556,6 +1799,7 @@ export {
1556
1799
  worldRemoveSceneOverride,
1557
1800
  worldResolveMountSource,
1558
1801
  worldResolveSceneAsset,
1802
+ worldResolveSceneEntity,
1559
1803
  worldResolveSceneInstanceStatePayload,
1560
1804
  worldSetSceneAssetResolver,
1561
1805
  worldSetSceneOverride,