@forgeax/engine-scene 0.1.21 → 0.1.24

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 (87) hide show
  1. package/README.md +82 -11
  2. package/dist/__tests__/fixtures/malformed-hierarchy-edge.d.ts +5 -3
  3. package/dist/__tests__/fixtures/malformed-hierarchy-edge.d.ts.map +1 -1
  4. package/dist/__tests__/flat-propagation.contract.test.d.ts +2 -0
  5. package/dist/__tests__/flat-propagation.contract.test.d.ts.map +1 -0
  6. package/dist/__tests__/flat-propagation.derived-writer.contract.test.d.ts +2 -0
  7. package/dist/__tests__/flat-propagation.derived-writer.contract.test.d.ts.map +1 -0
  8. package/dist/__tests__/flat-propagation.perf.test.d.ts +2 -0
  9. package/dist/__tests__/flat-propagation.perf.test.d.ts.map +1 -0
  10. package/dist/__tests__/hierarchy-propagation.perf.test.d.ts +2 -0
  11. package/dist/__tests__/hierarchy-propagation.perf.test.d.ts.map +1 -0
  12. package/dist/__tests__/scene-binding.integration.test.d.ts +2 -0
  13. package/dist/__tests__/scene-binding.integration.test.d.ts.map +1 -0
  14. package/dist/__tests__/schedule-order.contract.test.d.ts +2 -0
  15. package/dist/__tests__/schedule-order.contract.test.d.ts.map +1 -0
  16. package/dist/__tests__/structural.test.d.ts +2 -0
  17. package/dist/__tests__/structural.test.d.ts.map +1 -0
  18. package/dist/__tests__/transform-carrier-consumers.test-d.d.ts +2 -0
  19. package/dist/__tests__/transform-carrier-consumers.test-d.d.ts.map +1 -0
  20. package/dist/__tests__/transform-carrier-prototype.bench.d.ts +39 -0
  21. package/dist/__tests__/transform-carrier-prototype.bench.d.ts.map +1 -0
  22. package/dist/__tests__/transform-carrier-prototype.test-d.d.ts +2 -0
  23. package/dist/__tests__/transform-carrier-prototype.test-d.d.ts.map +1 -0
  24. package/dist/__tests__/transform-carrier-prototype.test.d.ts +2 -0
  25. package/dist/__tests__/transform-carrier-prototype.test.d.ts.map +1 -0
  26. package/dist/__tests__/transform-component-version.unit.test.d.ts +2 -0
  27. package/dist/__tests__/transform-component-version.unit.test.d.ts.map +1 -0
  28. package/dist/assets/scene-decoder.d.ts.map +1 -1
  29. package/dist/components/child-of.d.ts +23 -14
  30. package/dist/components/child-of.d.ts.map +1 -1
  31. package/dist/components/children.d.ts +14 -13
  32. package/dist/components/children.d.ts.map +1 -1
  33. package/dist/components/transform.d.ts +7 -43
  34. package/dist/components/transform.d.ts.map +1 -1
  35. package/dist/errors.d.ts +23 -2
  36. package/dist/errors.d.ts.map +1 -1
  37. package/dist/index.d.ts +3 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.mjs +1122 -358
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/instances/binding.d.ts +29 -0
  42. package/dist/instances/binding.d.ts.map +1 -0
  43. package/dist/instances/externalization.d.ts.map +1 -1
  44. package/dist/instances/scene-instances.d.ts +6 -18
  45. package/dist/instances/scene-instances.d.ts.map +1 -1
  46. package/dist/instances/state.d.ts +28 -0
  47. package/dist/instances/state.d.ts.map +1 -0
  48. package/dist/plugin.d.ts.map +1 -1
  49. package/dist/systems/hierarchy-projection.d.ts.map +1 -1
  50. package/dist/systems/index.d.ts +1 -1
  51. package/dist/systems/index.d.ts.map +1 -1
  52. package/dist/systems/propagate-transforms.d.ts +22 -2
  53. package/dist/systems/propagate-transforms.d.ts.map +1 -1
  54. package/package.json +5 -5
  55. package/src/__tests__/components.unit.test.ts +34 -2
  56. package/src/__tests__/fixtures/malformed-hierarchy-edge.ts +26 -4
  57. package/src/__tests__/flat-propagation.contract.test.ts +128 -0
  58. package/src/__tests__/flat-propagation.derived-writer.contract.test.ts +39 -0
  59. package/src/__tests__/flat-propagation.perf.test.ts +336 -0
  60. package/src/__tests__/hierarchy-propagation.perf.test.ts +149 -0
  61. package/src/__tests__/plugin.integration.test.ts +2 -2
  62. package/src/__tests__/propagation.unit.test.ts +271 -91
  63. package/src/__tests__/scene-binding.integration.test.ts +124 -0
  64. package/src/__tests__/schedule-order.contract.test.ts +43 -0
  65. package/src/__tests__/structural.test.ts +23 -0
  66. package/src/__tests__/transform-carrier-consumers.test-d.ts +156 -0
  67. package/src/__tests__/transform-carrier-prototype.bench.ts +168 -0
  68. package/src/__tests__/transform-carrier-prototype.test-d.ts +71 -0
  69. package/src/__tests__/transform-carrier-prototype.test.ts +170 -0
  70. package/src/__tests__/{transform-change-journal.unit.test.ts → transform-component-version.unit.test.ts} +32 -39
  71. package/src/assets/scene-decoder.ts +6 -1
  72. package/src/components/child-of.ts +38 -27
  73. package/src/components/children.ts +36 -37
  74. package/src/components/transform.ts +23 -81
  75. package/src/errors.ts +26 -2
  76. package/src/index.ts +10 -1
  77. package/src/instances/binding.ts +77 -0
  78. package/src/instances/externalization.ts +7 -1
  79. package/src/instances/scene-instances.ts +51 -80
  80. package/src/instances/state.ts +62 -0
  81. package/src/plugin.ts +9 -2
  82. package/src/systems/hierarchy-projection.ts +25 -8
  83. package/src/systems/index.ts +3 -0
  84. package/src/systems/propagate-transforms.ts +1142 -240
  85. package/dist/.tsbuildinfo +0 -1
  86. package/dist/__tests__/transform-change-journal.unit.test.d.ts +0 -2
  87. 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,339 +1229,866 @@ 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 {
1130
1235
  defineSystem,
1131
1236
  defineSystemSet,
1237
+ ENTITY_NULL_RAW as ENTITY_NULL_RAW2,
1132
1238
  FixedUpdate,
1133
1239
  Update
1134
1240
  } from "@forgeax/engine-ecs";
1135
1241
  import {
1136
- createWorldProjection as createWorldProjection2,
1137
- setDerivedComponent
1138
- } from "@forgeax/engine-ecs/projection";
1242
+ getDerivedWriter,
1243
+ worldInternal
1244
+ } from "@forgeax/engine-ecs/internal";
1139
1245
  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
- };
1246
+ import { err as err5, ok as ok5 } from "@forgeax/engine-types";
1247
+ var PROPAGATE_TRANSFORMS_SYSTEM = "propagateTransforms";
1248
+ var PROPAGATE_TRANSFORMS_FIXED_SYSTEM = "propagateTransformsFixed";
1249
+ var TransformSet = defineSystemSet({ name: "transform" });
1250
+ var TransformFixedSet = defineSystemSet({ name: "transform-fixed" });
1251
+ var propagationTrace;
1252
+ var hierarchyRootCursorAllocationCount = 0;
1253
+ function createHierarchyRootCursor() {
1254
+ hierarchyRootCursorAllocationCount += 1;
1255
+ return { bindingIndex: -1, row: -1 };
1256
+ }
1257
+ function countPropagation(name) {
1258
+ const trace = propagationTrace;
1259
+ if (trace !== void 0) trace[name] += 1;
1260
+ }
1261
+ var SCRATCH = /* @__PURE__ */ new WeakMap();
1262
+ var REGISTRATION_LEASES = /* @__PURE__ */ new WeakMap();
1263
+ function pairError(entity, expected) {
1264
+ return err5(
1265
+ new SceneError({
1266
+ code: "hierarchy-broken",
1267
+ expected,
1268
+ hint: "attach both Transform and GlobalTransform at scene authoring or import time, then retry propagation",
1269
+ detail: { entity, parent: entity }
1270
+ })
1271
+ );
1272
+ }
1273
+ function ensureQueries(world, scratch) {
1274
+ if (scratch.flatQuery !== void 0 && scratch.hierarchyQuery !== void 0 && scratch.transformQuery !== void 0 && scratch.hierarchyWriter !== void 0 && scratch.transformWriter !== void 0 && scratch.missingGlobalQuery !== void 0 && scratch.missingTransformQuery !== void 0) {
1275
+ return ok5(void 0);
1276
+ }
1277
+ const flatOutput = world.query({
1278
+ read: [Transform],
1279
+ write: [GlobalTransform],
1280
+ without: [ChildOf],
1281
+ changed: [Transform]
1282
+ });
1283
+ const hierarchy = world.query({ read: [Transform, ChildOf], write: [GlobalTransform] });
1284
+ const transform = world.query({ read: [Transform], write: [GlobalTransform] });
1285
+ const missingGlobal = world.query({ with: [Transform], without: [GlobalTransform] });
1286
+ const missingTransform = world.query({ with: [GlobalTransform], without: [Transform] });
1287
+ if (!flatOutput.ok || !hierarchy.ok || !transform.ok || !missingGlobal.ok || !missingTransform.ok) {
1288
+ return pairError(0, "valid Transform and GlobalTransform pair queries");
1289
+ }
1290
+ const hierarchyWriter = getDerivedWriter(hierarchy.value, GlobalTransform);
1291
+ const transformWriter = getDerivedWriter(transform.value, GlobalTransform);
1292
+ if (!hierarchyWriter.ok || !transformWriter.ok) {
1293
+ return pairError(0, "dense Transform and ChildOf derived bindings");
1294
+ }
1295
+ scratch.flatQuery = flatOutput.value;
1296
+ scratch.hierarchyQuery = hierarchy.value;
1297
+ scratch.transformQuery = transform.value;
1298
+ scratch.hierarchyWriter = hierarchyWriter.value;
1299
+ scratch.transformWriter = transformWriter.value;
1300
+ scratch.missingGlobalQuery = missingGlobal.value;
1301
+ scratch.missingTransformQuery = missingTransform.value;
1302
+ return ok5(void 0);
1303
+ }
1304
+ function validateTransformPairs(world, scratch) {
1305
+ const queryResult = ensureQueries(world, scratch);
1306
+ if (!queryResult.ok) return queryResult;
1307
+ const missingGlobal = scratch.missingGlobalQuery;
1308
+ const missingTransform = scratch.missingTransformQuery;
1309
+ if (missingGlobal === void 0 || missingTransform === void 0) {
1310
+ return pairError(0, "valid Transform and GlobalTransform pair queries");
1154
1311
  }
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 }
1312
+ for (const row of missingGlobal) {
1313
+ return pairError(row.entity, "each Transform entity to carry a GlobalTransform pair");
1314
+ }
1315
+ for (const row of missingTransform) {
1316
+ return pairError(row.entity, "each GlobalTransform entity to carry a Transform pair");
1317
+ }
1318
+ return ok5(void 0);
1319
+ }
1320
+ function scratchFor(world) {
1321
+ const existing = SCRATCH.get(world);
1322
+ if (existing !== void 0) return existing;
1323
+ const created = {
1324
+ position: new Float32Array(3),
1325
+ rotation: new Float32Array(4),
1326
+ scale: new Float32Array(3),
1327
+ local: mat4.create(),
1328
+ parent: mat4.create(),
1329
+ candidate: mat4.create(),
1330
+ hierarchyStackEntities: [],
1331
+ hierarchyStackChildren: [],
1332
+ hierarchyProbeEntities: [],
1333
+ hierarchyCurrentCursor: { bindingIndex: -1, row: -1 },
1334
+ hierarchyParentCursor: { bindingIndex: -1, row: -1 },
1335
+ hierarchyChildCursor: { bindingIndex: -1, row: -1 },
1336
+ hierarchyResidualCursor: { bindingIndex: -1, row: -1 },
1337
+ hierarchyResidualParentCursor: { bindingIndex: -1, row: -1 },
1338
+ hierarchyRootCursor: createHierarchyRootCursor(),
1339
+ hierarchyStates: [],
1340
+ hierarchyChanged: [],
1341
+ hierarchyBindingTables: [],
1342
+ hierarchyBindingRows: [],
1343
+ flatChanged: [],
1344
+ flatBindingTables: [],
1345
+ flatBindingRows: [],
1346
+ flatStructureEpoch: -1
1160
1347
  };
1348
+ SCRATCH.set(world, created);
1349
+ return created;
1161
1350
  }
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;
1351
+ function composeColumns(position, rotation, scale, out, scratch, positionStart = 0, rotationStart = 0) {
1352
+ scratch.position[0] = position[positionStart] ?? 0;
1353
+ scratch.position[1] = position[positionStart + 1] ?? 0;
1354
+ scratch.position[2] = position[positionStart + 2] ?? 0;
1355
+ scratch.rotation[0] = rotation[rotationStart] ?? 0;
1356
+ scratch.rotation[1] = rotation[rotationStart + 1] ?? 0;
1357
+ scratch.rotation[2] = rotation[rotationStart + 2] ?? 0;
1358
+ scratch.rotation[3] = rotation[rotationStart + 3] ?? 1;
1359
+ scratch.scale[0] = scale[positionStart] ?? 1;
1360
+ scratch.scale[1] = scale[positionStart + 1] ?? 1;
1361
+ scratch.scale[2] = scale[positionStart + 2] ?? 1;
1362
+ mat4.compose(out, scratch.position, scratch.rotation, scratch.scale);
1363
+ }
1364
+ function composeFlatColumns(positions, rotations, scales, worlds, count) {
1365
+ for (let row = 0; row < count; row += 1) {
1366
+ const position = row * 3;
1367
+ const rotation = row * 4;
1368
+ const world = row * 16;
1369
+ const x = rotations[rotation] ?? 0;
1370
+ const y = rotations[rotation + 1] ?? 0;
1371
+ const z = rotations[rotation + 2] ?? 0;
1372
+ const w = rotations[rotation + 3] ?? 1;
1373
+ const x2 = x + x;
1374
+ const y2 = y + y;
1375
+ const z2 = z + z;
1376
+ const xx = x * x2;
1377
+ const xy = x * y2;
1378
+ const xz = x * z2;
1379
+ const yy = y * y2;
1380
+ const yz = y * z2;
1381
+ const zz = z * z2;
1382
+ const wx = w * x2;
1383
+ const wy = w * y2;
1384
+ const wz = w * z2;
1385
+ const sx = scales[position] ?? 1;
1386
+ const sy = scales[position + 1] ?? 1;
1387
+ const sz = scales[position + 2] ?? 1;
1388
+ worlds[world] = (1 - (yy + zz)) * sx;
1389
+ worlds[world + 1] = (xy + wz) * sx;
1390
+ worlds[world + 2] = (xz - wy) * sx;
1391
+ worlds[world + 3] = 0;
1392
+ worlds[world + 4] = (xy - wz) * sy;
1393
+ worlds[world + 5] = (1 - (xx + zz)) * sy;
1394
+ worlds[world + 6] = (yz + wx) * sy;
1395
+ worlds[world + 7] = 0;
1396
+ worlds[world + 8] = (xz + wy) * sz;
1397
+ worlds[world + 9] = (yz - wx) * sz;
1398
+ worlds[world + 10] = (1 - (xx + yy)) * sz;
1399
+ worlds[world + 11] = 0;
1400
+ worlds[world + 12] = positions[position] ?? 0;
1401
+ worlds[world + 13] = positions[position + 1] ?? 0;
1402
+ worlds[world + 14] = positions[position + 2] ?? 0;
1403
+ worlds[world + 15] = 1;
1168
1404
  }
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);
1405
+ }
1406
+ function propagateFlat(world, scratch) {
1407
+ const query = scratch.flatQuery;
1408
+ if (query === void 0)
1409
+ return err5(
1410
+ new SceneError({
1411
+ code: "hierarchy-broken",
1412
+ expected: "a valid changed Transform write query",
1413
+ hint: "register the scene components before running TransformPropagation"
1414
+ })
1415
+ );
1416
+ const spans = query.spans();
1417
+ if (!spans.ok) return pairError(0, "dense numeric Transform spans");
1418
+ let bindingIndex = 0;
1419
+ try {
1420
+ for (const span of spans.value) {
1421
+ const local = span.get(Transform);
1422
+ const world2 = span.mut(GlobalTransform).world;
1423
+ composeFlatColumns(local.pos, local.quat, local.scale, world2, span.length);
1424
+ bindingIndex += 1;
1425
+ }
1426
+ } catch (cause) {
1427
+ const error = cause;
1428
+ return err5(derivedWriteError(error, bindingIndex));
1429
+ }
1430
+ const transformWriter = scratch.transformWriter;
1431
+ if (transformWriter === void 0) {
1432
+ return pairError(0, "dense Transform and GlobalTransform derived bindings");
1433
+ }
1434
+ const transformBindings = transformWriter.bindings;
1435
+ const structureEpoch = world[worldInternal].getStructureEpoch();
1436
+ if (scratch.flatStructureEpoch === structureEpoch) return ok5(void 0);
1437
+ ensureFlatBuffers(scratch, transformBindings);
1438
+ resetFlatBuffers(scratch);
1439
+ for (let bindingIndex2 = 0; bindingIndex2 < transformBindings.length; bindingIndex2 += 1) {
1440
+ const binding = transformBindings[bindingIndex2];
1441
+ const changed = scratch.flatChanged[bindingIndex2];
1442
+ if (binding === void 0 || changed === void 0) continue;
1443
+ for (let row = 0; row < binding.rowCapacity; row += 1) {
1444
+ const entity = binding.entities[row] ?? 0;
1445
+ const parentRaw = world[worldInternal].getFieldValue(entity, ChildOf, "parent");
1446
+ if (parentRaw !== void 0 && parentRaw !== ENTITY_NULL_RAW2) continue;
1447
+ countPropagation("flatStructuralRootRows");
1448
+ composeBindingRow(binding, row, void 0, 0, scratch, changed);
1177
1449
  }
1178
1450
  }
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));
1451
+ for (let bindingIndex2 = 0; bindingIndex2 < transformBindings.length; bindingIndex2 += 1) {
1452
+ const changed = scratch.flatChanged[bindingIndex2];
1453
+ if (changed === void 0) continue;
1454
+ const published = transformWriter.publishChangedRows(bindingIndex2, changed);
1455
+ if (!published.ok) return err5(derivedWriteError(published.error, bindingIndex2));
1456
+ }
1457
+ scratch.flatStructureEpoch = structureEpoch;
1458
+ return ok5(void 0);
1459
+ }
1460
+ function transformColumns(binding) {
1461
+ return binding.read;
1462
+ }
1463
+ function hierarchyColumns(binding) {
1464
+ return binding.read;
1465
+ }
1466
+ function worldColumn(binding) {
1467
+ return binding.write.world;
1468
+ }
1469
+ function hierarchyError(code, entity, parent, expected, hint) {
1470
+ return new SceneError({ code, expected, hint, detail: { entity, parent } });
1471
+ }
1472
+ function writeCandidate(binding, row, candidate, changed) {
1473
+ const worlds = worldColumn(binding);
1474
+ const base = row * 16;
1475
+ for (let index = 0; index < 16; index += 1) {
1476
+ if (worlds[base + index] !== candidate[index]) {
1477
+ worlds.set(candidate, base);
1478
+ changed[row] = 1;
1479
+ return;
1186
1480
  }
1187
1481
  }
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);
1482
+ }
1483
+ function composeBindingRow(binding, row, parentBinding, parentRow, scratch, changed) {
1484
+ if ("parent" in binding.read) countPropagation("hierarchyRowsEvaluated");
1485
+ const local = transformColumns(binding);
1486
+ const offset = row * 3;
1487
+ composeColumns(local.pos, local.quat, local.scale, scratch.local, scratch, offset, row * 4);
1488
+ if (parentBinding === void 0) {
1489
+ scratch.candidate.set(scratch.local);
1490
+ } else {
1491
+ const parentWorld = worldColumn(parentBinding);
1492
+ const parentOffset = parentRow * 16;
1493
+ for (let index = 0; index < 16; index += 1) {
1494
+ scratch.parent[index] = parentWorld[parentOffset + index] ?? 0;
1495
+ }
1496
+ mat4.multiply(scratch.candidate, scratch.parent, scratch.local);
1497
+ }
1498
+ writeCandidate(binding, row, scratch.candidate, changed);
1499
+ }
1500
+ function ensureHierarchyBuffers(scratch, bindings) {
1501
+ let same = scratch.hierarchyBindingTables.length === bindings.length;
1502
+ if (same) {
1503
+ for (let index = 0; index < bindings.length; index += 1) {
1504
+ const binding = bindings[index];
1505
+ if (binding === void 0 || scratch.hierarchyBindingTables[index] !== binding.tableId || scratch.hierarchyBindingRows[index] !== binding.rowCapacity) {
1506
+ same = false;
1507
+ break;
1199
1508
  }
1200
- return;
1201
1509
  }
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
1510
  }
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);
1511
+ if (same) return;
1512
+ scratch.hierarchyBindingTables = bindings.map((binding) => binding.tableId);
1513
+ scratch.hierarchyBindingRows = bindings.map((binding) => binding.rowCapacity);
1514
+ scratch.hierarchyStates = bindings.map((binding) => new Uint8Array(binding.rowCapacity));
1515
+ scratch.hierarchyChanged = bindings.map((binding) => new Uint8Array(binding.rowCapacity));
1516
+ }
1517
+ function resetHierarchyBuffers(scratch) {
1518
+ for (let index = 0; index < scratch.hierarchyStates.length; index += 1) {
1519
+ scratch.hierarchyStates[index]?.fill(0);
1520
+ scratch.hierarchyChanged[index]?.fill(0);
1521
+ }
1522
+ }
1523
+ function ensureFlatBuffers(scratch, bindings) {
1524
+ let same = scratch.flatBindingTables.length === bindings.length;
1525
+ if (same) {
1526
+ for (let index = 0; index < bindings.length; index += 1) {
1527
+ const binding = bindings[index];
1528
+ if (binding === void 0 || scratch.flatBindingTables[index] !== binding.tableId || scratch.flatBindingRows[index] !== binding.rowCapacity) {
1529
+ same = false;
1530
+ break;
1531
+ }
1532
+ }
1533
+ }
1534
+ if (same) return;
1535
+ scratch.flatBindingTables = bindings.map((binding) => binding.tableId);
1536
+ scratch.flatBindingRows = bindings.map((binding) => binding.rowCapacity);
1537
+ scratch.flatChanged = bindings.map((binding) => new Uint8Array(binding.rowCapacity));
1538
+ }
1539
+ function resetFlatBuffers(scratch) {
1540
+ for (const changed of scratch.flatChanged) changed.fill(0);
1541
+ }
1542
+ function derivedWriteError(cause, bindingIndex) {
1543
+ return new SceneError({
1544
+ code: "hierarchy-broken",
1545
+ expected: "derived GlobalTransform range publication to succeed",
1546
+ hint: cause.hint ?? "retry propagation on a healthy World",
1547
+ detail: {
1548
+ kind: "derived-write",
1549
+ entity: 0,
1550
+ parent: 0,
1551
+ bindingIndex,
1552
+ base: 0,
1553
+ start: 0,
1554
+ count: 0,
1555
+ cause
1227
1556
  }
1228
- };
1229
- HIERARCHY_PROJECTION_CACHE.set(world, {
1230
- changes,
1231
- snapshot
1232
1557
  });
1233
- return snapshot;
1234
1558
  }
1235
-
1236
- // src/systems/propagate-transforms.ts
1237
- var PROPAGATE_TRANSFORMS_SYSTEM = "propagateTransforms";
1238
- var PROPAGATE_TRANSFORMS_FIXED_SYSTEM = "propagateTransformsFixed";
1239
- var TransformSet = defineSystemSet({ name: "transform" });
1240
- var TransformFixedSet = defineSystemSet({ name: "transform-fixed" });
1241
- var CACHE = /* @__PURE__ */ new WeakMap();
1242
- 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
- };
1559
+ function findHierarchyLocation(writer, bindings, entity, cursor) {
1560
+ countPropagation("hierarchyEntityLookups");
1561
+ if (!writer.locateEntity(entity, cursor)) return void 0;
1562
+ return bindings[cursor.bindingIndex];
1249
1563
  }
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;
1564
+ function findTransformLocation(writer, bindings, entity, cursor) {
1565
+ countPropagation("hierarchyEntityLookups");
1566
+ if (!writer.locateEntity(entity, cursor)) return void 0;
1567
+ return bindings[cursor.bindingIndex];
1568
+ }
1569
+ function countPublishedRows(changed) {
1570
+ const trace = propagationTrace;
1571
+ if (trace === void 0) return;
1572
+ let runOpen = false;
1573
+ for (let row = 0; row < changed.length; row += 1) {
1574
+ if ((changed[row] ?? 0) !== 0) {
1575
+ trace.hierarchyPublishedRows += 1;
1576
+ if (!runOpen) {
1577
+ trace.hierarchyPublishedRuns += 1;
1578
+ runOpen = true;
1579
+ }
1580
+ } else {
1581
+ runOpen = false;
1582
+ }
1254
1583
  }
1255
- return true;
1256
1584
  }
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);
1585
+ function noteHierarchyError(current, next) {
1586
+ if (current === void 0) return next;
1587
+ const currentEntity = Number(current.detail?.entity ?? Number.MAX_SAFE_INTEGER);
1588
+ const nextEntity = Number(next.detail?.entity ?? Number.MAX_SAFE_INTEGER);
1589
+ return nextEntity < currentEntity || nextEntity === currentEntity && next.code.localeCompare(current.code) < 0 ? next : current;
1259
1590
  }
1260
- function compose(state, out) {
1261
- mat4.compose(out, state.pos, state.quat, state.scale);
1591
+ function composeLocalHierarchyEntity(writer, bindings, entity, scratch, changed) {
1592
+ const cursor = scratch.hierarchyCurrentCursor;
1593
+ const binding = findHierarchyLocation(writer, bindings, entity, cursor);
1594
+ if (binding === void 0) return false;
1595
+ composeBindingRow(
1596
+ binding,
1597
+ cursor.row,
1598
+ void 0,
1599
+ 0,
1600
+ scratch,
1601
+ changed[cursor.bindingIndex]
1602
+ );
1603
+ const states = scratch.hierarchyStates[cursor.bindingIndex];
1604
+ if (states !== void 0) states[cursor.row] = 3;
1605
+ return true;
1262
1606
  }
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);
1607
+ function handleActiveCycle(repeated, stackEntities, stackChildren, writer, bindings, scratch, changed, report) {
1608
+ let cycleStart = -1;
1609
+ for (let index = 0; index < stackEntities.length; index += 1) {
1610
+ if (stackEntities[index] === repeated) {
1611
+ cycleStart = index;
1612
+ break;
1613
+ }
1614
+ }
1615
+ if (cycleStart < 0) return;
1616
+ const repeatedCursor = { bindingIndex: -1, row: -1 };
1617
+ const repeatedBinding = findHierarchyLocation(writer, bindings, repeated, repeatedCursor);
1618
+ const repeatedParent = repeatedBinding === void 0 ? repeated : hierarchyColumns(repeatedBinding).parent[repeatedCursor.row] ?? ENTITY_NULL_RAW2;
1619
+ report(
1620
+ hierarchyError(
1621
+ "hierarchy-cycle",
1622
+ repeated,
1623
+ repeatedParent,
1624
+ "a parent-before-child Transform hierarchy",
1625
+ "repair the ChildOf cycle and retry TransformPropagation"
1626
+ )
1627
+ );
1628
+ for (let index = cycleStart; index < stackEntities.length; index += 1) {
1629
+ const cycleEntity = stackEntities[index];
1630
+ if (cycleEntity === void 0) continue;
1631
+ composeLocalHierarchyEntity(writer, bindings, cycleEntity, scratch, changed);
1632
+ stackChildren[index] = 0;
1269
1633
  }
1270
- return childrenOf;
1271
1634
  }
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);
1635
+ function walkChildren(world, root, writer, bindings, transformWriter, transformBindings, scratch, report, allowCompletedRoot) {
1636
+ countPropagation("hierarchyRootInvocations");
1637
+ const stackEntities = scratch.hierarchyStackEntities;
1638
+ const stackChildren = scratch.hierarchyStackChildren;
1639
+ stackEntities.length = 0;
1640
+ stackChildren.length = 0;
1641
+ if (allowCompletedRoot) {
1642
+ const rootCursor = scratch.hierarchyRootCursor;
1643
+ const rootBinding = findHierarchyLocation(writer, bindings, root, rootCursor);
1644
+ if (rootBinding !== void 0) {
1645
+ const state = scratch.hierarchyStates[rootCursor.bindingIndex]?.[rootCursor.row] ?? 0;
1646
+ if (state === 4) return;
1647
+ }
1648
+ } else {
1649
+ const rootCursor = scratch.hierarchyRootCursor;
1650
+ countPropagation("hierarchyRootCursorReuses");
1651
+ const rootBinding = findHierarchyLocation(writer, bindings, root, rootCursor);
1652
+ if (rootBinding !== void 0) {
1653
+ const state = scratch.hierarchyStates[rootCursor.bindingIndex]?.[rootCursor.row] ?? 0;
1654
+ if (state !== 0) return;
1655
+ }
1656
+ }
1657
+ stackEntities.push(root);
1658
+ stackChildren.push(-1);
1659
+ const currentCursor = scratch.hierarchyCurrentCursor;
1660
+ const parentCursor = scratch.hierarchyParentCursor;
1661
+ const childCursor = scratch.hierarchyChildCursor;
1662
+ while (stackEntities.length > 0) {
1663
+ const top = stackEntities.length - 1;
1664
+ const current = stackEntities[top];
1665
+ if (current === void 0) {
1666
+ stackEntities.pop();
1667
+ stackChildren.pop();
1668
+ continue;
1669
+ }
1670
+ const hierarchyBinding = findHierarchyLocation(writer, bindings, current, currentCursor);
1671
+ const nextChild = stackChildren[top] ?? -1;
1672
+ if (nextChild < 0) {
1673
+ if (hierarchyBinding !== void 0) {
1674
+ const state = scratch.hierarchyStates[currentCursor.bindingIndex]?.[currentCursor.row] ?? 0;
1675
+ if (state === 0) {
1676
+ const states = scratch.hierarchyStates[currentCursor.bindingIndex];
1677
+ if (states !== void 0) states[currentCursor.row] = 1;
1678
+ const parentRaw = hierarchyColumns(hierarchyBinding).parent[currentCursor.row] ?? ENTITY_NULL_RAW2;
1679
+ let completed = false;
1680
+ if (parentRaw === ENTITY_NULL_RAW2) {
1681
+ composeBindingRow(
1682
+ hierarchyBinding,
1683
+ currentCursor.row,
1684
+ void 0,
1685
+ 0,
1686
+ scratch,
1687
+ scratch.hierarchyChanged[currentCursor.bindingIndex]
1688
+ );
1689
+ completed = true;
1690
+ } else {
1691
+ const parent = parentRaw;
1692
+ const parentBinding = findTransformLocation(
1693
+ transformWriter,
1694
+ transformBindings,
1695
+ parent,
1696
+ parentCursor
1697
+ );
1698
+ const parentHierarchy = findHierarchyLocation(writer, bindings, parent, childCursor);
1699
+ if (parentBinding === void 0) {
1700
+ report(
1701
+ hierarchyError(
1702
+ "hierarchy-broken",
1703
+ current,
1704
+ parent,
1705
+ "each ChildOf parent to carry Transform and GlobalTransform",
1706
+ "repair the missing parent pair before retrying propagation"
1707
+ )
1708
+ );
1709
+ composeBindingRow(
1710
+ hierarchyBinding,
1711
+ currentCursor.row,
1712
+ void 0,
1713
+ 0,
1714
+ scratch,
1715
+ scratch.hierarchyChanged[currentCursor.bindingIndex]
1716
+ );
1717
+ completed = true;
1718
+ } else if (parentHierarchy !== void 0) {
1719
+ const parentState = scratch.hierarchyStates[childCursor.bindingIndex]?.[childCursor.row] ?? 0;
1720
+ if (parentState === 1) {
1721
+ handleActiveCycle(
1722
+ parent,
1723
+ stackEntities,
1724
+ stackChildren,
1725
+ writer,
1726
+ bindings,
1727
+ scratch,
1728
+ scratch.hierarchyChanged,
1729
+ report
1730
+ );
1731
+ completed = true;
1732
+ } else if (parentState === 0) {
1733
+ report(
1734
+ hierarchyError(
1735
+ "hierarchy-broken",
1736
+ current,
1737
+ parent,
1738
+ "Children to enumerate every parent-before-child edge",
1739
+ "repair the Children mirror and retry TransformPropagation"
1740
+ )
1741
+ );
1742
+ composeBindingRow(
1743
+ hierarchyBinding,
1744
+ currentCursor.row,
1745
+ void 0,
1746
+ 0,
1747
+ scratch,
1748
+ scratch.hierarchyChanged[currentCursor.bindingIndex]
1749
+ );
1750
+ completed = true;
1751
+ } else {
1752
+ composeBindingRow(
1753
+ hierarchyBinding,
1754
+ currentCursor.row,
1755
+ parentBinding,
1756
+ parentCursor.row,
1757
+ scratch,
1758
+ scratch.hierarchyChanged[currentCursor.bindingIndex]
1759
+ );
1760
+ completed = true;
1761
+ }
1762
+ } else {
1763
+ composeBindingRow(
1764
+ hierarchyBinding,
1765
+ currentCursor.row,
1766
+ parentBinding,
1767
+ parentCursor.row,
1768
+ scratch,
1769
+ scratch.hierarchyChanged[currentCursor.bindingIndex]
1770
+ );
1771
+ completed = true;
1772
+ }
1773
+ }
1774
+ if (completed && states !== void 0 && states[currentCursor.row] === 1) {
1775
+ states[currentCursor.row] = 2;
1776
+ }
1777
+ }
1778
+ }
1779
+ stackChildren[top] = 0;
1780
+ continue;
1781
+ }
1782
+ const childrenLength = world[worldInternal].getArrayLength(current, Children, "entities") ?? 0;
1783
+ if (nextChild >= childrenLength) {
1784
+ stackEntities.pop();
1785
+ stackChildren.pop();
1786
+ if (allowCompletedRoot) {
1787
+ const completedCursor = scratch.hierarchyResidualCursor;
1788
+ const completedBinding = findHierarchyLocation(writer, bindings, current, completedCursor);
1789
+ const completedStates = completedBinding === void 0 ? void 0 : scratch.hierarchyStates[completedCursor.bindingIndex];
1790
+ if (completedStates !== void 0 && completedStates[completedCursor.row] === 3) {
1791
+ completedStates[completedCursor.row] = 4;
1792
+ }
1793
+ }
1794
+ continue;
1795
+ }
1796
+ stackChildren[top] = nextChild + 1;
1797
+ countPropagation("hierarchyEdgesVisited");
1798
+ const childRaw = world[worldInternal].getArrayElement(current, Children, "entities", nextChild);
1799
+ if (childRaw === void 0 || childRaw === ENTITY_NULL_RAW2) {
1800
+ report(
1801
+ hierarchyError(
1802
+ "hierarchy-broken",
1803
+ current,
1804
+ current,
1805
+ "Children.entities to contain live child handles",
1806
+ "repair the Children mirror and retry TransformPropagation"
1807
+ )
1808
+ );
1809
+ continue;
1810
+ }
1811
+ const child = childRaw;
1812
+ const childBinding = findHierarchyLocation(writer, bindings, child, childCursor);
1813
+ if (childBinding === void 0) {
1814
+ report(
1815
+ hierarchyError(
1816
+ "hierarchy-broken",
1817
+ child,
1818
+ current,
1819
+ "each Children entry to carry Transform, GlobalTransform, and ChildOf",
1820
+ "repair the child component pair before retrying propagation"
1821
+ )
1822
+ );
1823
+ continue;
1824
+ }
1825
+ const childParent = hierarchyColumns(childBinding).parent[childCursor.row] ?? ENTITY_NULL_RAW2;
1826
+ const childState = scratch.hierarchyStates[childCursor.bindingIndex]?.[childCursor.row] ?? 0;
1827
+ if (childParent !== current) {
1828
+ if (childState === 0) continue;
1829
+ report(
1830
+ hierarchyError(
1831
+ "hierarchy-broken",
1832
+ child,
1833
+ current,
1834
+ "Children and ChildOf to describe the same parent",
1835
+ "repair the relationship mirror and retry TransformPropagation"
1836
+ )
1837
+ );
1838
+ continue;
1839
+ }
1840
+ if (childState === 1) {
1841
+ handleActiveCycle(
1842
+ child,
1843
+ stackEntities,
1844
+ stackChildren,
1845
+ writer,
1846
+ bindings,
1847
+ scratch,
1848
+ scratch.hierarchyChanged,
1849
+ report
1850
+ );
1851
+ } else if (childState === 0) {
1852
+ stackEntities.push(child);
1853
+ stackChildren.push(-1);
1282
1854
  }
1283
1855
  }
1284
- return affected;
1285
1856
  }
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
- );
1857
+ function fallbackResidualPath(world, path, writer, bindings, transformWriter, transformBindings, scratch, report) {
1858
+ for (const entity of path) {
1859
+ composeLocalHierarchyEntity(writer, bindings, entity, scratch, scratch.hierarchyChanged);
1860
+ }
1861
+ for (const entity of path) {
1862
+ walkChildren(
1863
+ world,
1864
+ entity,
1865
+ writer,
1866
+ bindings,
1867
+ transformWriter,
1868
+ transformBindings,
1869
+ scratch,
1870
+ report,
1871
+ true
1872
+ );
1873
+ }
1297
1874
  }
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));
1875
+ function resolveResidualPath(world, entity, writer, bindings, transformWriter, transformBindings, scratch, report) {
1876
+ const path = scratch.hierarchyProbeEntities;
1877
+ path.length = 0;
1878
+ const cursor = scratch.hierarchyResidualCursor;
1879
+ const parentTransformCursor = scratch.hierarchyParentCursor;
1880
+ const parentHierarchyCursor = scratch.hierarchyResidualParentCursor;
1881
+ let current = entity;
1882
+ while (true) {
1883
+ countPropagation("hierarchyResidualParentProbes");
1884
+ const binding = findHierarchyLocation(writer, bindings, current, cursor);
1885
+ if (binding === void 0) break;
1886
+ const states = scratch.hierarchyStates[cursor.bindingIndex];
1887
+ const state = states?.[cursor.row] ?? 0;
1888
+ if (state !== 0) {
1889
+ const columns = hierarchyColumns(binding);
1890
+ const parentRaw2 = columns.parent[cursor.row] ?? ENTITY_NULL_RAW2;
1891
+ report(
1892
+ hierarchyError(
1893
+ "hierarchy-cycle",
1894
+ current,
1895
+ parentRaw2 === ENTITY_NULL_RAW2 ? current : parentRaw2,
1896
+ "a parent-before-child Transform hierarchy",
1897
+ "repair the ChildOf cycle and retry TransformPropagation"
1898
+ )
1899
+ );
1900
+ fallbackResidualPath(
1901
+ world,
1902
+ path,
1903
+ writer,
1904
+ bindings,
1905
+ transformWriter,
1906
+ transformBindings,
1907
+ scratch,
1908
+ report
1909
+ );
1910
+ return;
1911
+ }
1912
+ if (states !== void 0) states[cursor.row] = 1;
1913
+ path.push(current);
1914
+ const parentRaw = hierarchyColumns(binding).parent[cursor.row] ?? ENTITY_NULL_RAW2;
1915
+ if (parentRaw === ENTITY_NULL_RAW2) {
1916
+ report(
1917
+ hierarchyError(
1918
+ "hierarchy-broken",
1919
+ current,
1920
+ current,
1921
+ "Children to enumerate every parent-before-child edge",
1922
+ "repair the Children mirror and retry TransformPropagation"
1923
+ )
1924
+ );
1925
+ fallbackResidualPath(
1926
+ world,
1927
+ path,
1928
+ writer,
1929
+ bindings,
1930
+ transformWriter,
1931
+ transformBindings,
1932
+ scratch,
1933
+ report
1934
+ );
1935
+ return;
1936
+ }
1937
+ const parent = parentRaw;
1938
+ const parentTransform = findTransformLocation(
1939
+ transformWriter,
1940
+ transformBindings,
1941
+ parent,
1942
+ parentTransformCursor
1943
+ );
1944
+ const parentHierarchy = findHierarchyLocation(writer, bindings, parent, parentHierarchyCursor);
1945
+ if (parentHierarchy === void 0) {
1946
+ report(
1947
+ hierarchyError(
1948
+ "hierarchy-broken",
1949
+ current,
1950
+ parent,
1951
+ parentTransform === void 0 ? "each ChildOf parent to carry Transform and GlobalTransform" : "Children to enumerate every parent-before-child edge",
1952
+ parentTransform === void 0 ? "repair the missing parent pair before retrying propagation" : "repair the Children mirror and retry TransformPropagation"
1953
+ )
1954
+ );
1955
+ fallbackResidualPath(
1956
+ world,
1957
+ path,
1958
+ writer,
1959
+ bindings,
1960
+ transformWriter,
1961
+ transformBindings,
1962
+ scratch,
1963
+ report
1964
+ );
1965
+ return;
1309
1966
  }
1967
+ const parentState = scratch.hierarchyStates[parentHierarchyCursor.bindingIndex]?.[parentHierarchyCursor.row] ?? 0;
1968
+ if (parentState === 0) {
1969
+ current = parent;
1970
+ continue;
1971
+ }
1972
+ if (parentState === 1) {
1973
+ report(
1974
+ hierarchyError(
1975
+ "hierarchy-cycle",
1976
+ parent,
1977
+ current,
1978
+ "a parent-before-child Transform hierarchy",
1979
+ "repair the ChildOf cycle and retry TransformPropagation"
1980
+ )
1981
+ );
1982
+ } else {
1983
+ report(
1984
+ hierarchyError(
1985
+ "hierarchy-broken",
1986
+ current,
1987
+ parent,
1988
+ "Children to enumerate every parent-before-child edge",
1989
+ "repair the Children mirror and retry TransformPropagation"
1990
+ )
1991
+ );
1992
+ }
1993
+ fallbackResidualPath(
1994
+ world,
1995
+ path,
1996
+ writer,
1997
+ bindings,
1998
+ transformWriter,
1999
+ transformBindings,
2000
+ scratch,
2001
+ report
2002
+ );
2003
+ return;
1310
2004
  }
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
- };
1321
2005
  }
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(
2006
+ function propagateHierarchy(world, scratch) {
2007
+ const hierarchyWriter = scratch.hierarchyWriter;
2008
+ const transformWriter = scratch.transformWriter;
2009
+ if (hierarchyWriter === void 0 || transformWriter === void 0) {
2010
+ return err5(
1346
2011
  new SceneError({
1347
2012
  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 }
2013
+ expected: "paired Transform and GlobalTransform derived bindings",
2014
+ hint: "register the scene components before running TransformPropagation"
1351
2015
  })
1352
2016
  );
1353
2017
  }
1354
- cache.derived.set(entity, resolved);
1355
- published.add(entity);
1356
- visiting.delete(entity);
1357
- return ok4(void 0);
1358
- }
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
- );
1365
- }
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);
1397
- }
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;
2018
+ const hierarchyBindings = hierarchyWriter.bindings;
2019
+ const transformBindings = transformWriter.bindings;
2020
+ if (hierarchyBindings.length === 0) {
2021
+ scratch.hierarchyBindingTables.length = 0;
2022
+ scratch.hierarchyBindingRows.length = 0;
2023
+ scratch.hierarchyStates.length = 0;
2024
+ scratch.hierarchyChanged.length = 0;
2025
+ return ok5(void 0);
2026
+ }
2027
+ ensureHierarchyBuffers(scratch, hierarchyBindings);
2028
+ resetHierarchyBuffers(scratch);
2029
+ let firstError;
2030
+ const report = (error) => {
2031
+ firstError = noteHierarchyError(firstError, error);
2032
+ };
2033
+ for (const binding of transformBindings) {
2034
+ const entities = binding.entities;
2035
+ for (let row = 0; row < binding.rowCapacity; row += 1) {
2036
+ const entity = entities[row] ?? 0;
2037
+ const parentRaw = world[worldInternal].getFieldValue(entity, ChildOf, "parent");
2038
+ if (parentRaw === void 0 || parentRaw === ENTITY_NULL_RAW2) {
2039
+ walkChildren(
2040
+ world,
2041
+ entity,
2042
+ hierarchyWriter,
2043
+ hierarchyBindings,
2044
+ transformWriter,
2045
+ transformBindings,
2046
+ scratch,
2047
+ report,
2048
+ false
2049
+ );
2050
+ }
1413
2051
  }
1414
- if (change.component === Transform) transformSeeds.add(change.entity);
1415
2052
  }
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);
2053
+ for (let bindingIndex = 0; bindingIndex < hierarchyBindings.length; bindingIndex += 1) {
2054
+ const binding = hierarchyBindings[bindingIndex];
2055
+ if (binding === void 0) continue;
2056
+ const entities = binding.entities;
2057
+ const states = scratch.hierarchyStates[bindingIndex];
2058
+ for (let row = 0; row < binding.rowCapacity; row += 1) {
2059
+ if ((states?.[row] ?? 0) !== 0) continue;
2060
+ const entity = entities[row] ?? 0;
2061
+ resolveResidualPath(
2062
+ world,
2063
+ entity,
2064
+ hierarchyWriter,
2065
+ hierarchyBindings,
2066
+ transformWriter,
2067
+ transformBindings,
2068
+ scratch,
2069
+ report
2070
+ );
1426
2071
  }
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
2072
  }
1439
- if (!drainPublishedChanges(cache, published)) {
1440
- CACHE.delete(world);
1441
- return propagateTransforms(world);
2073
+ for (let bindingIndex = 0; bindingIndex < hierarchyBindings.length; bindingIndex += 1) {
2074
+ const changed = scratch.hierarchyChanged[bindingIndex];
2075
+ if (changed === void 0) continue;
2076
+ countPublishedRows(changed);
2077
+ const published = hierarchyWriter.publishChangedRows(bindingIndex, changed);
2078
+ if (!published.ok) {
2079
+ const cause = published.error;
2080
+ report(derivedWriteError(cause, bindingIndex));
2081
+ }
1442
2082
  }
1443
- cache.result = hierarchyError(cache.hierarchy);
1444
- return cache.result;
2083
+ return firstError === void 0 ? ok5(void 0) : err5(firstError);
2084
+ }
2085
+ function propagateTransforms(world) {
2086
+ const scratch = scratchFor(world);
2087
+ const pairs = validateTransformPairs(world, scratch);
2088
+ if (!pairs.ok) return pairs;
2089
+ const flat = propagateFlat(world, scratch);
2090
+ if (!flat.ok) return flat;
2091
+ return propagateHierarchy(world, scratch);
1445
2092
  }
1446
2093
  var PropagateTransforms = defineSystem({
1447
2094
  name: PROPAGATE_TRANSFORMS_SYSTEM,
@@ -1469,7 +2116,7 @@ function registerPropagateTransforms(world, options = {}) {
1469
2116
  world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);
1470
2117
  world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);
1471
2118
  REGISTRATION_LEASES.delete(world);
1472
- CACHE.delete(world);
2119
+ SCRATCH.delete(world);
1473
2120
  }
1474
2121
  };
1475
2122
  }
@@ -1486,23 +2133,31 @@ function registerPropagateTransforms(world, options = {}) {
1486
2133
  ]).unwrap();
1487
2134
  }
1488
2135
  world.addSystems(FixedUpdate, TransformFixedSet, [PropagateTransformsFixed]).unwrap();
1489
- const lease = { refs: 1 };
1490
- REGISTRATION_LEASES.set(world, lease);
2136
+ REGISTRATION_LEASES.set(world, { refs: 1 });
1491
2137
  let active = true;
1492
2138
  return () => {
1493
2139
  if (!active) return;
1494
2140
  active = false;
2141
+ const lease = REGISTRATION_LEASES.get(world);
2142
+ if (lease === void 0) return;
1495
2143
  lease.refs -= 1;
1496
2144
  if (lease.refs !== 0) return;
1497
2145
  world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);
1498
2146
  world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);
1499
2147
  REGISTRATION_LEASES.delete(world);
1500
- CACHE.delete(world);
2148
+ SCRATCH.delete(world);
1501
2149
  };
1502
2150
  }
1503
2151
 
1504
2152
  // src/plugin.ts
1505
- var SCENE_COMPONENTS = [ChildOf, Children, MorphWeights, Name, Transform];
2153
+ var SCENE_COMPONENTS = [
2154
+ ChildOf,
2155
+ Children,
2156
+ MorphWeights,
2157
+ Name,
2158
+ Transform,
2159
+ GlobalTransform
2160
+ ];
1506
2161
  function registerSceneComponents(world) {
1507
2162
  const leases = SCENE_COMPONENTS.map((component) => world.components.register(component).unwrap());
1508
2163
  return () => {
@@ -1519,10 +2174,115 @@ function scenePlugin() {
1519
2174
  }
1520
2175
  };
1521
2176
  }
2177
+
2178
+ // src/systems/hierarchy-projection.ts
2179
+ import { Entity } from "@forgeax/engine-ecs";
2180
+ var HIERARCHY_PROJECTION_CACHE = /* @__PURE__ */ new WeakMap();
2181
+ function createChildOfChangeQuery(world) {
2182
+ const result = world.query({ changed: [ChildOf] });
2183
+ if (!result.ok) throw result.error;
2184
+ return result.value;
2185
+ }
2186
+ function drainChanges(query) {
2187
+ let changed = false;
2188
+ for (const span of query.spans().unwrap()) changed ||= span.length > 0;
2189
+ return changed;
2190
+ }
2191
+ function diagnostic(code, entity, parent) {
2192
+ if (code === "hierarchy-cycle") {
2193
+ return {
2194
+ code,
2195
+ expected: "ChildOf parent edges form an acyclic live hierarchy",
2196
+ hint: "remove one ChildOf edge from the reported cycle, then re-run the extract",
2197
+ detail: { entity, parent }
2198
+ };
2199
+ }
2200
+ return {
2201
+ code,
2202
+ expected: "ChildOf.parent references a live entity in the same World",
2203
+ hint: "remove the stale ChildOf component or restore the referenced parent in this World",
2204
+ detail: { entity, parent }
2205
+ };
2206
+ }
2207
+ function projectHierarchy(world) {
2208
+ const cached = HIERARCHY_PROJECTION_CACHE.get(world);
2209
+ if (cached !== void 0 && cached.structureEpoch === world.getStructureEpoch() && !drainChanges(cached.childOfChanges)) {
2210
+ return cached.snapshot;
2211
+ }
2212
+ const liveEntities = /* @__PURE__ */ new Set();
2213
+ const authoredParents = /* @__PURE__ */ new Map();
2214
+ const query = world.query({ read: [Entity], optional: [ChildOf] });
2215
+ if (query.ok) {
2216
+ for (const row of query.value) {
2217
+ liveEntities.add(row.entity);
2218
+ const parent = row.get(ChildOf)?.parent;
2219
+ if (parent !== void 0 && parent !== null) authoredParents.set(row.entity, parent);
2220
+ }
2221
+ }
2222
+ const parentOf = /* @__PURE__ */ new Map();
2223
+ const diagnostics = [];
2224
+ for (const [entity, parent] of authoredParents) {
2225
+ if (liveEntities.has(parent)) {
2226
+ parentOf.set(entity, parent);
2227
+ } else {
2228
+ diagnostics.push(diagnostic("hierarchy-broken", entity, parent));
2229
+ }
2230
+ }
2231
+ const state = /* @__PURE__ */ new Map();
2232
+ const stack = [];
2233
+ const cycleMembers = /* @__PURE__ */ new Set();
2234
+ const visit = (entity) => {
2235
+ const currentState = state.get(entity) ?? 0;
2236
+ if (currentState === 2) return;
2237
+ if (currentState === 1) {
2238
+ const cycleStart = stack.indexOf(entity);
2239
+ for (let index = cycleStart; index >= 0 && index < stack.length; index++) {
2240
+ const member = stack[index];
2241
+ if (member !== void 0) cycleMembers.add(member);
2242
+ }
2243
+ return;
2244
+ }
2245
+ state.set(entity, 1);
2246
+ stack.push(entity);
2247
+ const parent = parentOf.get(entity);
2248
+ if (parent !== void 0) visit(parent);
2249
+ stack.pop();
2250
+ state.set(entity, 2);
2251
+ };
2252
+ for (const entity of liveEntities) visit(entity);
2253
+ for (const entity of cycleMembers) {
2254
+ const parent = authoredParents.get(entity);
2255
+ if (parent !== void 0) diagnostics.push(diagnostic("hierarchy-cycle", entity, parent));
2256
+ parentOf.delete(entity);
2257
+ }
2258
+ diagnostics.sort((left, right) => {
2259
+ const entityDelta = left.detail.entity - right.detail.entity;
2260
+ if (entityDelta !== 0) return entityDelta;
2261
+ return left.code.localeCompare(right.code);
2262
+ });
2263
+ const stableParentOf = new Map(parentOf);
2264
+ const stableDiagnostics = Object.freeze(diagnostics.slice());
2265
+ const snapshot = {
2266
+ parentOf: stableParentOf,
2267
+ diagnostics: stableDiagnostics,
2268
+ getParent(entity) {
2269
+ return stableParentOf.get(entity);
2270
+ }
2271
+ };
2272
+ const childOfChanges = createChildOfChangeQuery(world);
2273
+ drainChanges(childOfChanges);
2274
+ HIERARCHY_PROJECTION_CACHE.set(world, {
2275
+ structureEpoch: world.getStructureEpoch(),
2276
+ childOfChanges,
2277
+ snapshot
2278
+ });
2279
+ return snapshot;
2280
+ }
1522
2281
  export {
1523
2282
  ChildOf,
1524
2283
  Children,
1525
2284
  ComponentNotDefinedError,
2285
+ GlobalTransform,
1526
2286
  MorphWeights,
1527
2287
  Name,
1528
2288
  PROPAGATE_TRANSFORMS_SYSTEM,
@@ -1535,8 +2295,11 @@ export {
1535
2295
  projectHierarchy,
1536
2296
  propagateTransforms,
1537
2297
  registerPropagateTransforms,
2298
+ resolveSceneEntity,
1538
2299
  sceneAssetContribution,
2300
+ sceneEntity,
1539
2301
  scenePlugin,
2302
+ validateSceneBindings,
1540
2303
  worldApplyMountOverride,
1541
2304
  worldBuildSceneEntityComponentDatas,
1542
2305
  worldDespawnDescendants,
@@ -1556,6 +2319,7 @@ export {
1556
2319
  worldRemoveSceneOverride,
1557
2320
  worldResolveMountSource,
1558
2321
  worldResolveSceneAsset,
2322
+ worldResolveSceneEntity,
1559
2323
  worldResolveSceneInstanceStatePayload,
1560
2324
  worldSetSceneAssetResolver,
1561
2325
  worldSetSceneOverride,