@forgeax/engine-runtime 0.1.28 → 0.1.29

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 (113) hide show
  1. package/README.md +98 -0
  2. package/dist/collect-scene-asset.d.ts.map +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.mjs +480 -15
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/scene-utils/mount-override-fold.d.ts +3 -1
  8. package/dist/scene-utils/mount-override-fold.d.ts.map +1 -1
  9. package/package.json +37 -32
  10. package/src/__tests__/animation-graph-collect-roundtrip.test.ts +4 -2
  11. package/src/__tests__/asset-registry-aabb.cases.unit.test.ts +5 -4
  12. package/src/__tests__/asset-registry-guid-reverse.test.ts +22 -27
  13. package/src/__tests__/asset-registry-mounts.test.ts +174 -734
  14. package/src/__tests__/asset-registry-scene.cases.unit.test.ts +9 -18
  15. package/src/__tests__/asset-registry.recursive.spec.ts +23 -29
  16. package/src/__tests__/camera-ortho.cases.unit.test.ts +12 -3
  17. package/src/__tests__/collect-scene-asset.test.ts +96 -113
  18. package/src/__tests__/collect-scene-mount-collapse.test.ts +64 -66
  19. package/src/__tests__/collect-transient-children-absent.test.ts +11 -17
  20. package/src/__tests__/collect-transient-roundtrip.test.ts +12 -13
  21. package/src/__tests__/collect-transient-skip.test.ts +20 -27
  22. package/src/__tests__/components.test-d.ts +17 -1
  23. package/src/__tests__/components.unit.test.ts +75 -27
  24. package/src/__tests__/composite-skybox-cross-world.browser.test.ts +4 -2
  25. package/src/__tests__/dawn/instances-per-instance-pbr.dawn.test.ts +8 -4
  26. package/src/__tests__/dawn/instances-shadow.dawn.test.ts +4 -1
  27. package/src/__tests__/dawn/instances-uniform-fallback.dawn.test.ts +66 -92
  28. package/src/__tests__/dawn/material-cooked-fixture.dawn.test.ts +200 -4
  29. package/src/__tests__/errors.unit.test.ts +13 -0
  30. package/src/__tests__/feature-depth-material.dawn.test.ts +23 -0
  31. package/src/__tests__/feature-depth-material.fixture.ts +473 -0
  32. package/src/__tests__/fullscreen-post-process-pass.dawn.test.ts +14 -14
  33. package/src/__tests__/geometry.unit.test.ts +42 -43
  34. package/src/__tests__/gpu-resource-release.cases.unit.test.ts +16 -9
  35. package/src/__tests__/handle-migration.test.ts +0 -1
  36. package/src/__tests__/instances-renderer-lifecycle.integration.test.ts +20 -14
  37. package/src/__tests__/instances.test-d.ts +13 -52
  38. package/src/__tests__/instantiate-mount-deferred-wiring.test.ts +42 -80
  39. package/src/__tests__/lazy-catalog.cases.unit.test.ts +17 -3
  40. package/src/__tests__/loader-registry.cases.unit.test.ts +4 -4
  41. package/src/__tests__/material-publication.browser.test.ts +29 -0
  42. package/src/__tests__/material-publication.commands.ts +23 -0
  43. package/src/__tests__/material-publication.dawn.test.ts +24 -0
  44. package/src/__tests__/material-publication.fixture.ts +349 -0
  45. package/src/__tests__/material-publication.server.ts +109 -0
  46. package/src/__tests__/parse-scene-payload-refs.cases.unit.test.ts +73 -96
  47. package/src/__tests__/pbr-pipeline.unit.test.ts +25 -11
  48. package/src/__tests__/point-light-shadow.browser.test.ts +8 -2
  49. package/src/__tests__/point-light-shadow.unit.test.ts +37 -8
  50. package/src/__tests__/record-all-topology.cases.unit.test.ts +52 -0
  51. package/src/__tests__/record-draw-branch.cases.unit.test.ts +1 -0
  52. package/src/__tests__/record-worldid-isolation.test.ts +47 -28
  53. package/src/__tests__/render-error-exhaustive.test-d.ts +24 -0
  54. package/src/__tests__/render-system-extract-dispatch-ordering.test.ts +15 -4
  55. package/src/__tests__/render-system-extract.test.ts +61 -0
  56. package/src/__tests__/render-system-extract.unit.test.ts +21 -7
  57. package/src/__tests__/render-system-fold-bucket.unit.test.ts +33 -1
  58. package/src/__tests__/render-system-fold-material.unit.test.ts +32 -6
  59. package/src/__tests__/render-system-fold-modegate.unit.test.ts +26 -4
  60. package/src/__tests__/render-system-fold.dawn.test.ts +4 -1
  61. package/src/__tests__/render-system-mega.test.ts +50 -62
  62. package/src/__tests__/renderer-device-loss-provider-feasibility.dawn.test.ts +0 -1
  63. package/src/__tests__/renderer-device-loss-recovery.dawn.test.ts +0 -3
  64. package/src/__tests__/renderer-draw-world.cases.unit.test.ts +16 -513
  65. package/src/__tests__/resolve-scene-guids.cases.unit.test.ts +48 -43
  66. package/src/__tests__/rhi-null-command-flow.unit.test.ts +9 -7
  67. package/src/__tests__/roots-to-scene-asset.test.ts +27 -28
  68. package/src/__tests__/roundtrip-primitive.test.ts +24 -23
  69. package/src/__tests__/scene-equirect-roundtrip.test.ts +5 -16
  70. package/src/__tests__/scene-instantiate-cycle-runtime.test.ts +13 -29
  71. package/src/__tests__/scene-mount-fixed-point.test.ts +104 -227
  72. package/src/__tests__/scene-mount-roundtrip.test.ts +95 -961
  73. package/src/__tests__/scene-mount-wrapper-roundtrip.test.ts +36 -182
  74. package/src/__tests__/scene-sugar-instantiate.test.ts +20 -15
  75. package/src/__tests__/serialize-mounts-roundtrip.test.ts +108 -492
  76. package/src/__tests__/serialize-scene-asset.test.ts +36 -50
  77. package/src/__tests__/shadow-caster-empty-schema.test.ts +3 -1
  78. package/src/__tests__/shadow-fields-observable.dawn.test.ts +34 -19
  79. package/src/__tests__/shadow-mesh-ssbo-order.test.ts +5 -12
  80. package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +6 -4
  81. package/src/__tests__/skin-m2-extract-happy.cases.unit.test.ts +50 -2
  82. package/src/__tests__/skin-m2-extract-joint-error.cases.unit.test.ts +50 -2
  83. package/src/__tests__/skin-m2-extract-null-assets.cases.unit.test.ts +50 -2
  84. package/src/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.ts +50 -2
  85. package/src/__tests__/skin-palette-extract.cases.unit.test.ts +2 -0
  86. package/src/__tests__/skinned-shadow-mixed.dawn.test.ts +93 -48
  87. package/src/__tests__/skylight-pipeline-layout.cases.unit.test.ts +6 -2
  88. package/src/__tests__/sparse-tag-scene-roundtrip.test.ts +1 -1
  89. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +11 -3
  90. package/src/__tests__/template-game-default-api-contract.test.ts +21 -21
  91. package/src/__tests__/tonemap-hdr-target.cases.unit.test.ts +8 -5
  92. package/src/__tests__/tonemap-pipeline-split.cases.unit.test.ts +3 -0
  93. package/src/__tests__/transient-fields-collect.test.ts +4 -4
  94. package/src/__tests__/transient-runtime-behavior.test.ts +9 -9
  95. package/src/__tests__/vfx-depth-material.dawn.test.ts +42 -0
  96. package/src/__tests__/vfx-mesh-lighting.browser.test.ts +16 -0
  97. package/src/__tests__/vfx-mesh-lighting.commands.ts +14 -0
  98. package/src/__tests__/vfx-mesh-lighting.dawn.test.ts +16 -0
  99. package/src/__tests__/vfx-mesh-lighting.fixture.ts +883 -0
  100. package/src/__tests__/wave1-rendering-materials.browser.test.ts +617 -0
  101. package/src/__tests__/wave1-rendering-p0.browser.test.ts +751 -0
  102. package/src/__tests__/wave1-rendering-recovery.browser.test.ts +666 -0
  103. package/src/__tests__/wave1-shadow-diagnostic.browser.test.ts +582 -0
  104. package/src/__tests__/world-scene-despawn.test.ts +8 -8
  105. package/src/__tests__/world-scene-instantiate.test.ts +23 -29
  106. package/src/__tests__/world-scene-nested.test.ts +90 -163
  107. package/src/__tests__/world-scene-override.test.ts +17 -16
  108. package/src/__tests__/zero-compression-zero-load.integration.test.ts +1 -0
  109. package/src/collect-scene-asset.ts +637 -19
  110. package/src/components/__tests__/sprite-animation.test-d.ts +2 -2
  111. package/src/index.ts +1 -0
  112. package/src/scene-utils/mount-override-fold.ts +5 -14
  113. package/src/__tests__/node-shims.d.ts +0 -31
package/dist/index.mjs CHANGED
@@ -359,10 +359,6 @@ function foldMountOverrides(world, state) {
359
359
  );
360
360
  if (!sourceRes.ok) return [];
361
361
  const source = sourceRes.value;
362
- const sourceByLid = /* @__PURE__ */ new Map();
363
- for (const node of source.entities) {
364
- sourceByLid.set(node.localId, node);
365
- }
366
362
  const excluded = excludedComponentNames();
367
363
  const registered = world.components.entries();
368
364
  const overrides = [];
@@ -372,7 +368,8 @@ function foldMountOverrides(world, state) {
372
368
  }
373
369
  members.sort((a, b) => a[1] - b[1]);
374
370
  for (const [entity, lid] of members) {
375
- const sourceNode = sourceByLid.get(lid);
371
+ const sourceKey = state.keyByLocalId.get(lid);
372
+ const sourceNode = sourceKey === void 0 ? void 0 : source.entities[sourceKey];
376
373
  const sourceComps = sourceNode?.components ?? {};
377
374
  for (const [compName, compToken] of registered) {
378
375
  if (excluded.has(compName)) continue;
@@ -481,6 +478,436 @@ function _serializeOverrideValueHandles(world, registry, ov) {
481
478
  }
482
479
  return ok$1(out);
483
480
  }
481
+ function isSceneEntityAddress(value) {
482
+ return typeof value === "string" || value.length > 0;
483
+ }
484
+ function collectKeyedSceneAsset(world, registry, _roots, visited, ownedEntities, legacyEntities, mounts, anchors, memberOrigin) {
485
+ const sceneInstanceToken = world.components.resolve("SceneInstance");
486
+ const childOfToken = world.components.resolve("ChildOf");
487
+ const stateInfos = [];
488
+ if (sceneInstanceToken !== void 0) {
489
+ const queryResult = world.query({ read: [SceneInstance] });
490
+ if (queryResult.ok) {
491
+ for (const row of queryResult.value) {
492
+ const stateResult = worldGetSceneInstanceState(world, row.entity);
493
+ if (!stateResult.ok) continue;
494
+ const rowValue = row.get(SceneInstance);
495
+ stateInfos.push({
496
+ root: row.entity,
497
+ state: stateResult.value,
498
+ mapping: Uint32Array.from(rowValue.mapping)
499
+ });
500
+ }
501
+ }
502
+ }
503
+ const stateByRoot = /* @__PURE__ */ new Map();
504
+ for (const info of stateInfos) stateByRoot.set(info.root, info);
505
+ const parentByChildRoot = /* @__PURE__ */ new Map();
506
+ const relationCandidates = [];
507
+ for (const child of stateInfos) {
508
+ if (child.state.instanceKey === void 0 || childOfToken === void 0) continue;
509
+ const parentResult = world.get(
510
+ child.root,
511
+ childOfToken
512
+ );
513
+ if (!parentResult.ok) continue;
514
+ const carrier = parentResult.value.parent;
515
+ if (carrier === void 0) continue;
516
+ for (const parent of stateInfos) {
517
+ const bound = parent.state.bindings.get(child.state.instanceKey);
518
+ if (bound === carrier) {
519
+ parentByChildRoot.set(child.root, parent);
520
+ relationCandidates.push({ child, parent, key: child.state.instanceKey });
521
+ break;
522
+ }
523
+ }
524
+ }
525
+ const findSequence = (haystack, needle, start) => {
526
+ if (needle.length === 0) return start;
527
+ for (let index = Math.max(0, start); index + needle.length <= haystack.length; index += 1) {
528
+ let match = true;
529
+ for (let offset = 0; offset < needle.length; offset += 1) {
530
+ if (haystack[index + offset] !== needle[offset]) {
531
+ match = false;
532
+ break;
533
+ }
534
+ }
535
+ if (match) return index;
536
+ }
537
+ return void 0;
538
+ };
539
+ const relations = [];
540
+ for (const candidate of relationCandidates) {
541
+ const slot = [...candidate.parent.state.keyByLocalId.entries()].find(
542
+ ([, value]) => value === candidate.key
543
+ )?.[0];
544
+ if (slot === void 0) continue;
545
+ const memberFirst = findSequence(candidate.parent.mapping, candidate.child.mapping, slot + 1);
546
+ if (memberFirst === void 0) continue;
547
+ relations.push({ ...candidate, memberFirst });
548
+ }
549
+ const relationsByParent = /* @__PURE__ */ new Map();
550
+ for (const relation of relations) {
551
+ const list = relationsByParent.get(relation.parent.root);
552
+ if (list === void 0) relationsByParent.set(relation.parent.root, [relation]);
553
+ else list.push(relation);
554
+ }
555
+ const statePrefixMemo = /* @__PURE__ */ new Map();
556
+ const statePrefix = (info, stack = /* @__PURE__ */ new Set()) => {
557
+ const prior = statePrefixMemo.get(info.root);
558
+ if (prior !== void 0) return prior;
559
+ if (stack.has(info.root)) return [];
560
+ if (info.state.instanceKey === void 0) {
561
+ statePrefixMemo.set(info.root, []);
562
+ return [];
563
+ }
564
+ const next = new Set(stack);
565
+ next.add(info.root);
566
+ const parent = parentByChildRoot.get(info.root);
567
+ const prefix = parent === void 0 ? [info.state.instanceKey] : [...statePrefix(parent, next), info.state.instanceKey];
568
+ statePrefixMemo.set(info.root, prefix);
569
+ return prefix;
570
+ };
571
+ const stateSlotAddress = (info, slot, prefix, stack = /* @__PURE__ */ new Set()) => {
572
+ if (slot < 0 || stack.has(info.root)) return void 0;
573
+ const key = info.state.keyByLocalId.get(slot);
574
+ if (key !== void 0) return [...prefix, key];
575
+ const next = new Set(stack);
576
+ next.add(info.root);
577
+ for (const relation of relationsByParent.get(info.root) ?? []) {
578
+ const end = relation.memberFirst + relation.child.mapping.length;
579
+ if (slot >= relation.memberFirst && slot < end) {
580
+ return stateSlotAddress(
581
+ relation.child,
582
+ slot - relation.memberFirst,
583
+ [...prefix, relation.key],
584
+ next
585
+ );
586
+ }
587
+ }
588
+ return void 0;
589
+ };
590
+ const rawToAddress = /* @__PURE__ */ new Map();
591
+ const orderedStates = [...stateInfos].sort(
592
+ (a, b) => statePrefix(a).length - statePrefix(b).length
593
+ );
594
+ for (const info of orderedStates) {
595
+ const prefix = statePrefix(info);
596
+ for (let slot = 0; slot < info.mapping.length; slot += 1) {
597
+ const raw = info.mapping[slot];
598
+ if (raw === void 0 || raw === 4294967295 || !visited.has(raw)) continue;
599
+ const address = stateSlotAddress(info, slot, prefix);
600
+ if (address !== void 0 && !rawToAddress.has(raw)) rawToAddress.set(raw, address);
601
+ }
602
+ }
603
+ for (const [raw, origin] of memberOrigin) {
604
+ if (!visited.has(raw) || rawToAddress.has(raw)) continue;
605
+ const child = stateByRoot.get(origin.anchorRaw);
606
+ if (child === void 0) continue;
607
+ const address = stateSlotAddress(child, origin.memberLocalId, statePrefix(child));
608
+ if (address !== void 0) rawToAddress.set(raw, address);
609
+ }
610
+ const generatedKeys = /* @__PURE__ */ new Set();
611
+ const keyForOwnedRaw = /* @__PURE__ */ new Map();
612
+ for (let index = 0; index < ownedEntities.length; index += 1) {
613
+ const raw = ownedEntities[index];
614
+ const known = rawToAddress.get(raw);
615
+ let key = known?.length === 1 ? known[0] : void 0;
616
+ if (key === void 0 || key.length === 0 || generatedKeys.has(key)) {
617
+ const base = `entity-${index}`;
618
+ key = base;
619
+ let suffix = 1;
620
+ while (generatedKeys.has(key)) key = `${base}-${suffix++}`;
621
+ }
622
+ generatedKeys.add(key);
623
+ keyForOwnedRaw.set(raw, key);
624
+ rawToAddress.set(raw, [key]);
625
+ }
626
+ const slotAddress = (slot) => {
627
+ if (slot >= 0 && slot < ownedEntities.length) {
628
+ const raw = ownedEntities[slot];
629
+ const key = raw === void 0 ? void 0 : keyForOwnedRaw.get(raw);
630
+ return key;
631
+ }
632
+ const mountIndex = mounts.findIndex((mount) => mount.localId === slot);
633
+ if (mountIndex >= 0) {
634
+ const anchor = anchors[mountIndex];
635
+ const child = anchor === void 0 ? void 0 : stateByRoot.get(anchor.entityRaw);
636
+ const key = child?.state.instanceKey;
637
+ if (key !== void 0) return key;
638
+ }
639
+ for (let index = 0; index < mounts.length; index += 1) {
640
+ const mount = mounts[index];
641
+ if (mount === void 0) continue;
642
+ const first = mount.memberFirst;
643
+ if (slot < first || slot >= first + mount.memberCount) continue;
644
+ const anchor = anchors[index];
645
+ const child = anchor === void 0 ? void 0 : stateByRoot.get(anchor.entityRaw);
646
+ const mountKey = child?.state.instanceKey;
647
+ if (child === void 0 || mountKey === void 0) return void 0;
648
+ const address = stateSlotAddress(child, slot - first, []);
649
+ if (address === void 0) return void 0;
650
+ return [mountKey, ...address];
651
+ }
652
+ const top = orderedStates.find((info) => statePrefix(info).length === 0);
653
+ if (top !== void 0) {
654
+ const address = stateSlotAddress(top, slot, []);
655
+ if (address !== void 0) {
656
+ return address.length === 1 ? address[0] : address;
657
+ }
658
+ }
659
+ return void 0;
660
+ };
661
+ const convertComponents = (entityRaw, source) => {
662
+ const out = {};
663
+ for (const [componentName, rawFields] of Object.entries(source)) {
664
+ const token = world.components.resolve(componentName);
665
+ if (token === void 0) {
666
+ out[componentName] = { ...rawFields };
667
+ continue;
668
+ }
669
+ const converted = {};
670
+ for (const [fieldName, value] of Object.entries(rawFields)) {
671
+ const kind = classifyEntityField(token, fieldName);
672
+ if (kind === null) {
673
+ converted[fieldName] = value;
674
+ continue;
675
+ }
676
+ const convertSlot = (localSlot) => {
677
+ if (typeof localSlot !== "number") return void 0;
678
+ return slotAddress(localSlot);
679
+ };
680
+ if (kind.isArray) {
681
+ if (!Array.isArray(value)) {
682
+ return err$1(
683
+ new SceneCollectEntityRefOutOfClosureError(
684
+ entityRaw,
685
+ `${componentName}.${fieldName}`,
686
+ -1
687
+ )
688
+ );
689
+ }
690
+ const addresses = [];
691
+ for (const item of value) {
692
+ const address = convertSlot(item);
693
+ if (address === void 0 || !isSceneEntityAddress(address)) {
694
+ return err$1(
695
+ new SceneCollectEntityRefOutOfClosureError(
696
+ entityRaw,
697
+ `${componentName}.${fieldName}`,
698
+ Number(item)
699
+ )
700
+ );
701
+ }
702
+ addresses.push(address);
703
+ }
704
+ converted[fieldName] = addresses;
705
+ } else if (value === null) {
706
+ converted[fieldName] = null;
707
+ } else {
708
+ const address = convertSlot(value);
709
+ if (address === void 0 || !isSceneEntityAddress(address)) {
710
+ return err$1(
711
+ new SceneCollectEntityRefOutOfClosureError(
712
+ entityRaw,
713
+ `${componentName}.${fieldName}`,
714
+ Number(value)
715
+ )
716
+ );
717
+ }
718
+ converted[fieldName] = address;
719
+ }
720
+ }
721
+ out[componentName] = converted;
722
+ }
723
+ return ok$1(out);
724
+ };
725
+ const entities = {};
726
+ for (const legacy of legacyEntities) {
727
+ const raw = ownedEntities[legacy.localId];
728
+ if (raw === void 0) continue;
729
+ const key = keyForOwnedRaw.get(raw);
730
+ const converted = convertComponents(raw, legacy.components);
731
+ if (!converted.ok) return converted;
732
+ entities[key] = { components: converted.value };
733
+ }
734
+ const resolveOverrideTarget = (child, memberFirst, localId) => {
735
+ if (child === void 0) return void 0;
736
+ const target = stateSlotAddress(child, localId - memberFirst, []);
737
+ if (target === void 0 || target.length === 0) return void 0;
738
+ return target;
739
+ };
740
+ const convertOverrideValue = (entityRaw, componentName, fieldName, value) => {
741
+ const token = world.components.resolve(componentName);
742
+ if (token === void 0) return ok$1(value);
743
+ const schema = componentSchema(token);
744
+ const convertFields = (field, fieldValue) => {
745
+ const kind = classifyEntityField(token, field);
746
+ if (kind === null) {
747
+ const sharedType = schema[field];
748
+ const shared = sharedType?.startsWith("shared<") ? { scalar: true } : sharedType?.startsWith("array<shared<") ? { scalar: false } : void 0;
749
+ if (shared === void 0) return ok$1(fieldValue);
750
+ return _serializeSharedFieldValue(world, registry, shared, fieldValue, field);
751
+ }
752
+ const resolveLiveOrSlot = (item) => {
753
+ if (typeof item !== "number") return void 0;
754
+ const live = rawToAddress.get(item);
755
+ if (live !== void 0) {
756
+ return live.length === 1 ? live[0] : live;
757
+ }
758
+ return slotAddress(item);
759
+ };
760
+ if (kind.isArray) {
761
+ if (!Array.isArray(fieldValue)) return ok$1(fieldValue);
762
+ const mapped = [];
763
+ for (const item of fieldValue) {
764
+ const address2 = resolveLiveOrSlot(item);
765
+ if (address2 === void 0 || !isSceneEntityAddress(address2)) {
766
+ return err$1(
767
+ new SceneCollectEntityRefOutOfClosureError(
768
+ entityRaw,
769
+ `${componentName}.${field}`,
770
+ Number(item)
771
+ )
772
+ );
773
+ }
774
+ mapped.push(address2);
775
+ }
776
+ return ok$1(mapped);
777
+ }
778
+ if (fieldValue === null) return ok$1(null);
779
+ const address = resolveLiveOrSlot(fieldValue);
780
+ if (address === void 0 || !isSceneEntityAddress(address)) {
781
+ return err$1(
782
+ new SceneCollectEntityRefOutOfClosureError(
783
+ entityRaw,
784
+ `${componentName}.${field}`,
785
+ Number(fieldValue)
786
+ )
787
+ );
788
+ }
789
+ return ok$1(address);
790
+ };
791
+ if (fieldName !== void 0) return convertFields(fieldName, value);
792
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return ok$1(value);
793
+ const result = {};
794
+ for (const [field, fieldValue] of Object.entries(value)) {
795
+ const converted = convertFields(field, fieldValue);
796
+ if (!converted.ok) return converted;
797
+ result[field] = converted.value;
798
+ }
799
+ return ok$1(result);
800
+ };
801
+ const declarationOverrides = (parent, child, mount, anchor) => {
802
+ const memberFirst = mount.memberFirst;
803
+ const candidates = [];
804
+ if (parent !== void 0) {
805
+ for (const override of parent.state.mountTimeOverrides) {
806
+ const localId = override.localId;
807
+ if (localId >= memberFirst && localId < memberFirst + mount.memberCount)
808
+ candidates.push(override);
809
+ }
810
+ for (const [localId, records] of parent.state.overrides) {
811
+ const numeric = localId;
812
+ if (numeric < memberFirst || numeric >= memberFirst + mount.memberCount) continue;
813
+ for (const record of records.values()) {
814
+ candidates.push({
815
+ localId,
816
+ comp: record.comp,
817
+ ...record.field === void 0 ? {} : { field: record.field },
818
+ value: record.value
819
+ });
820
+ }
821
+ }
822
+ }
823
+ if (candidates.length === 0) candidates.push(...mount.overrides ?? []);
824
+ if (candidates.length === 0 || child === void 0) return ok$1(void 0);
825
+ const byTarget = /* @__PURE__ */ new Map();
826
+ for (const override of candidates) {
827
+ const localId = override.localId;
828
+ const target = resolveOverrideTarget(child, memberFirst, localId);
829
+ if (target === void 0) {
830
+ return err$1(
831
+ new SceneCollectEntityRefOutOfClosureError(
832
+ anchor.entityRaw,
833
+ "instance.override.target",
834
+ localId
835
+ )
836
+ );
837
+ }
838
+ const converted = convertOverrideValue(
839
+ anchor.entityRaw,
840
+ override.comp,
841
+ override.field,
842
+ override.value
843
+ );
844
+ if (!converted.ok) return converted;
845
+ const targetKey = JSON.stringify(target);
846
+ const prior = byTarget.get(targetKey);
847
+ const components = {};
848
+ if (prior !== void 0) {
849
+ for (const [name, fields] of Object.entries(
850
+ prior.components
851
+ )) {
852
+ components[name] = { ...fields };
853
+ }
854
+ }
855
+ const priorFields = components[override.comp];
856
+ const existing = { ...priorFields ?? {} };
857
+ if (override.field === void 0 && typeof converted.value === "object" && converted.value !== null) {
858
+ Object.assign(existing, converted.value);
859
+ } else if (override.field !== void 0) {
860
+ existing[override.field] = converted.value;
861
+ }
862
+ components[override.comp] = existing;
863
+ byTarget.set(targetKey, { target, components });
864
+ }
865
+ return ok$1([...byTarget.values()]);
866
+ };
867
+ for (let index = 0; index < mounts.length; index += 1) {
868
+ const mount = mounts[index];
869
+ const anchor = anchors[index];
870
+ if (mount === void 0 || anchor === void 0) continue;
871
+ const child = stateByRoot.get(anchor.entityRaw);
872
+ const parent = child === void 0 ? void 0 : parentByChildRoot.get(child.root);
873
+ const instanceKey = child?.state.instanceKey ?? `instance-${index}`;
874
+ const carrierRaw = childOfToken === void 0 ? void 0 : (() => {
875
+ const parentResult = world.get(
876
+ anchor.entityRaw,
877
+ childOfToken
878
+ );
879
+ return parentResult.ok ? parentResult.value.parent : void 0;
880
+ })();
881
+ const converted = convertComponents(
882
+ carrierRaw ?? anchor.entityRaw,
883
+ mount.components ?? {}
884
+ );
885
+ if (!converted.ok) return converted;
886
+ if (mount.parent !== void 0) {
887
+ const parentAddress = slotAddress(mount.parent);
888
+ if (parentAddress === void 0) {
889
+ return err$1(
890
+ new SceneCollectEntityRefOutOfClosureError(
891
+ carrierRaw ?? anchor.entityRaw,
892
+ "ChildOf.parent",
893
+ mount.parent
894
+ )
895
+ );
896
+ }
897
+ converted.value.ChildOf = { parent: parentAddress };
898
+ }
899
+ const overrides = declarationOverrides(parent, child, mount, anchor);
900
+ if (!overrides.ok) return overrides;
901
+ entities[instanceKey] = {
902
+ components: converted.value,
903
+ instance: {
904
+ source: String(mount.source),
905
+ ...overrides.value === void 0 ? {} : { overrides: overrides.value }
906
+ }
907
+ };
908
+ }
909
+ return ok$1({ kind: "scene", entities });
910
+ }
484
911
  function serializeSceneAssetToPack(sceneAsset, components, guid) {
485
912
  const externalized = externalizeSceneAsset(sceneAsset, (componentName) => {
486
913
  const component = components.get(componentName);
@@ -513,7 +940,7 @@ function rootsToSceneAsset(registry, world, roots) {
513
940
  const collectProfile = SCENE_COLLECT_PROFILE;
514
941
  const visited = /* @__PURE__ */ new Set();
515
942
  for (const root of roots) collectSubtree(world, root, visited);
516
- if (visited.size === 0) return ok$1({ kind: "scene", entities: [] });
943
+ if (visited.size === 0) return ok$1({ kind: "scene", entities: {} });
517
944
  const rootRawSet = /* @__PURE__ */ new Set();
518
945
  for (const r of roots) rootRawSet.add(r);
519
946
  const anchorEntities = /* @__PURE__ */ new Set();
@@ -527,13 +954,41 @@ function rootsToSceneAsset(registry, world, roots) {
527
954
  if (rootRawSet.has(er)) continue;
528
955
  const sr = worldGetSceneInstanceState(world, er);
529
956
  if (!sr.ok) continue;
530
- for (const [me, lid] of sr.value.entityToLocalId) {
957
+ const retainedState = sr.value;
958
+ for (const [me, lid] of retainedState.entityToLocalId) {
531
959
  const mr = me;
532
960
  if (visited.has(mr) && !anchorEntities.has(mr) && !memberEntities.has(mr)) {
533
961
  memberEntities.add(mr);
534
962
  memberOrigin.set(mr, { anchorRaw: er, memberLocalId: lid });
535
963
  }
536
964
  }
965
+ for (const [key, member] of retainedState.bindings) {
966
+ const mr = member;
967
+ const localId = [...retainedState.keyByLocalId.entries()].find(
968
+ ([, value]) => value === key
969
+ )?.[0];
970
+ if (localId !== void 0 && visited.has(mr) && !anchorEntities.has(mr) && !memberEntities.has(mr)) {
971
+ memberEntities.add(mr);
972
+ memberOrigin.set(mr, { anchorRaw: er, memberLocalId: localId });
973
+ }
974
+ }
975
+ }
976
+ const childOfForAnchor = world.components.resolve("ChildOf");
977
+ if (childOfForAnchor !== void 0) {
978
+ for (const anchorRaw of [...anchorEntities]) {
979
+ if (rootRawSet.has(anchorRaw) || memberEntities.has(anchorRaw)) continue;
980
+ const parentRes = world.get(
981
+ anchorRaw,
982
+ childOfForAnchor
983
+ );
984
+ if (!parentRes.ok) continue;
985
+ const carrierRaw = parentRes.value.parent;
986
+ const origin = carrierRaw === void 0 ? void 0 : memberOrigin.get(carrierRaw);
987
+ if (origin !== void 0) {
988
+ memberEntities.add(anchorRaw);
989
+ memberOrigin.set(anchorRaw, origin);
990
+ }
991
+ }
537
992
  }
538
993
  for (const er of anchorEntities) {
539
994
  if (memberEntities.has(er) && !rootRawSet.has(er)) anchorEntities.delete(er);
@@ -588,7 +1043,8 @@ function rootsToSceneAsset(registry, world, roots) {
588
1043
  const ownedEntities = [];
589
1044
  for (const er of orderedEntities) {
590
1045
  if (carrierToAnchor.has(er)) continue;
591
- if ((!anchorEntities.has(er) || rootRawSet.has(er)) && !memberEntities.has(er)) {
1046
+ if (rootRawSet.has(er) && anchorEntities.has(er)) continue;
1047
+ if (!anchorEntities.has(er) && !memberEntities.has(er)) {
592
1048
  ownedEntities.push(er);
593
1049
  }
594
1050
  }
@@ -727,7 +1183,7 @@ function rootsToSceneAsset(registry, world, roots) {
727
1183
  return void 0;
728
1184
  }
729
1185
  const registeredComps = world.components.entries();
730
- const entities = [];
1186
+ const legacyEntities = [];
731
1187
  for (let lid = 0; lid < ownedEntities.length; lid++) {
732
1188
  const entityRaw = ownedEntities[lid];
733
1189
  if (entityRaw === void 0) continue;
@@ -774,6 +1230,9 @@ function rootsToSceneAsset(registry, world, roots) {
774
1230
  continue;
775
1231
  }
776
1232
  if (entityKind !== null) {
1233
+ if (!entityKind.isArray && typeof rawValue === "number" && rootRawSet.has(rawValue) && anchorEntities.has(rawValue)) {
1234
+ continue;
1235
+ }
777
1236
  if (entityKind.isArray) {
778
1237
  const arr = _isArrayLike(rawValue) ? _normalizeArray(rawValue) : rawValue;
779
1238
  const mapped = [];
@@ -821,13 +1280,19 @@ function rootsToSceneAsset(registry, world, roots) {
821
1280
  }
822
1281
  if (Object.keys(fieldValues).length > 0) components[compName] = fieldValues;
823
1282
  }
824
- entities.push({ localId: lid, components });
1283
+ legacyEntities.push({ localId: lid, components });
825
1284
  }
826
- return ok$1({
827
- kind: "scene",
828
- entities,
829
- ...outMounts.length > 0 ? { mounts: outMounts } : {}
830
- });
1285
+ return collectKeyedSceneAsset(
1286
+ world,
1287
+ registry,
1288
+ roots,
1289
+ visited,
1290
+ ownedEntities,
1291
+ legacyEntities,
1292
+ outMounts,
1293
+ nonRootAnchors,
1294
+ memberOrigin
1295
+ );
831
1296
  }
832
1297
 
833
1298
  export { createDevImportTransport, createRenderer, defaultAssetDecoderContributions, rootsToSceneAsset, serializeSceneAssetToPack, spriteAnimationTickSystem };