@relu-interactives/spatial-ecs 0.2.20 → 0.2.22

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 (51) hide show
  1. package/dist/{System-BjwOBx81.d.cts → System-CYWZ_Zx5.d.cts} +9 -4
  2. package/dist/{System-BjwOBx81.d.ts → System-CYWZ_Zx5.d.ts} +9 -4
  3. package/dist/{Video-B8yVI1tF.d.cts → Video-C1IOat7L.d.ts} +38 -9
  4. package/dist/{Video-B9z5AtIf.d.ts → Video-ppAXaKt0.d.cts} +38 -9
  5. package/dist/{chunk-HUHN6IFD.js → chunk-52CHDPRQ.js} +118 -92
  6. package/dist/chunk-52CHDPRQ.js.map +1 -0
  7. package/dist/{chunk-67MFQDHX.js → chunk-GZ2NKSMV.js} +609 -90
  8. package/dist/chunk-GZ2NKSMV.js.map +1 -0
  9. package/dist/{chunk-Z6OMYEXU.js → chunk-JHCPRVBC.js} +34 -5
  10. package/dist/chunk-JHCPRVBC.js.map +1 -0
  11. package/dist/{chunk-DY4ETMP2.js → chunk-WKGCC7YL.js} +3 -3
  12. package/dist/{chunk-DY4ETMP2.js.map → chunk-WKGCC7YL.js.map} +1 -1
  13. package/dist/components/index.cjs +31 -2
  14. package/dist/components/index.cjs.map +1 -1
  15. package/dist/components/index.d.cts +3 -3
  16. package/dist/components/index.d.ts +3 -3
  17. package/dist/components/index.js +1 -1
  18. package/dist/docs/guide/authoring-a-behaviour.md +3 -40
  19. package/dist/docs/guide/authoring-a-script-object.md +102 -0
  20. package/dist/docs/guide/behaviour-lifecycle.md +4 -4
  21. package/dist/docs/guide/prefabs.md +24 -5
  22. package/dist/docs/guide/spawning/script-object.md +36 -0
  23. package/dist/docs/guide/systems/_sidebar.json +4 -0
  24. package/dist/docs/guide/systems/script-behaviour-system.md +1 -17
  25. package/dist/docs/guide/systems/script-object-system.md +44 -0
  26. package/dist/factories/index.cjs +164 -117
  27. package/dist/factories/index.cjs.map +1 -1
  28. package/dist/factories/index.d.cts +5 -3
  29. package/dist/factories/index.d.ts +5 -3
  30. package/dist/factories/index.js +3 -3
  31. package/dist/{index-LHLKpJhZ.d.cts → index-Cu2Pt7wN.d.cts} +33 -14
  32. package/dist/{index-BBI2YdCu.d.ts → index-DdiWu1b6.d.ts} +33 -14
  33. package/dist/index.cjs +1132 -302
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +219 -32
  36. package/dist/index.d.ts +219 -32
  37. package/dist/index.js +254 -11
  38. package/dist/index.js.map +1 -1
  39. package/dist/systems/index.cjs +429 -43
  40. package/dist/systems/index.cjs.map +1 -1
  41. package/dist/systems/index.d.cts +44 -3
  42. package/dist/systems/index.d.ts +44 -3
  43. package/dist/systems/index.js +3 -3
  44. package/dist/types.d.cts +1 -1
  45. package/dist/types.d.ts +1 -1
  46. package/dist/worldSnapshotUtils.d.cts +1 -1
  47. package/dist/worldSnapshotUtils.d.ts +1 -1
  48. package/package.json +1 -1
  49. package/dist/chunk-67MFQDHX.js.map +0 -1
  50. package/dist/chunk-HUHN6IFD.js.map +0 -1
  51. package/dist/chunk-Z6OMYEXU.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { resolveDracoDecoderPath, applyTransform } from './chunk-DY4ETMP2.js';
2
- import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance } from './chunk-Z6OMYEXU.js';
1
+ import { resolveDracoDecoderPath, applyTransform, SHARED_MODEL_RESOURCE_FLAG } from './chunk-WKGCC7YL.js';
2
+ import { AnimationComponent, ModelComponent, CameraComponent, Object3DRef, Environment, Postprocessing, TransformComponent, PhysicsComponent, LightComponent, MaterialComponent, DEFAULT_MESH_GEOMETRY_PARAMETERS, MeshGeometryComponent, InstancedMeshComponent, ParentId, ScriptComponent, ScriptObjectComponent, InstanceSlotComponent, Name, ImageComponent, SpriteComponent, VideoComponent, AudioComponent, ImageTarget, Selectable, PrefabNode, PrefabInstance } from './chunk-JHCPRVBC.js';
3
3
  import { __name } from './chunk-OY7ZODZP.js';
4
4
  import * as THREE7 from 'three';
5
5
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
@@ -2741,6 +2741,10 @@ function buildComponentValueMaps(snapshot) {
2741
2741
  animationValues: readByComponent(snapshot.components, "AnimationComponent"),
2742
2742
  imageTargetAnchorValues: readByComponent(snapshot.components, "ImageTargetAnchor"),
2743
2743
  scriptValues: readByComponent(snapshot.components, "ScriptComponent"),
2744
+ scriptObjectValues: readByComponent(
2745
+ snapshot.components,
2746
+ "ScriptObjectComponent"
2747
+ ),
2744
2748
  instancedMeshValues: readByComponent(snapshot.components, "InstancedMeshComponent"),
2745
2749
  instanceSlotValues: readByComponent(snapshot.components, "InstanceSlotComponent"),
2746
2750
  prefabNodeValues: readByComponent(snapshot.components, "PrefabNode"),
@@ -2858,6 +2862,14 @@ function restorePostCreateComponents(world, objectManagementSystem, targetEntity
2858
2862
  new ScriptComponent(entries)
2859
2863
  );
2860
2864
  }
2865
+ const sourceScriptObject = asRecord(values.scriptObjectValues.get(sourceEntityId));
2866
+ if (sourceScriptObject) {
2867
+ const soc = world.getComponent(targetEntityId, ScriptObjectComponent);
2868
+ const data = asRecord(sourceScriptObject.data);
2869
+ if (soc && data) {
2870
+ soc.data = remapEntityRefsInScriptData(data, idMap);
2871
+ }
2872
+ }
2861
2873
  {
2862
2874
  const imComponent = world.getComponent(targetEntityId, InstancedMeshComponent);
2863
2875
  if (imComponent && imComponent.instanceEntityIds.length > 0) {
@@ -3410,7 +3422,13 @@ function restoreComponentState(world, objectManagementSystem, targetEntityId, so
3410
3422
  ) : [];
3411
3423
  world.addComponent(
3412
3424
  targetEntityId,
3413
- new PrefabInstance(sourcePrefabInstance.url, overrides)
3425
+ new PrefabInstance(
3426
+ sourcePrefabInstance.url,
3427
+ overrides,
3428
+ // Preserve the placement identity; the constructor generates a
3429
+ // fresh one for legacy snapshots that predate instanceId.
3430
+ typeof sourcePrefabInstance.instanceId === "string" && sourcePrefabInstance.instanceId.length > 0 ? sourcePrefabInstance.instanceId : void 0
3431
+ )
3414
3432
  );
3415
3433
  }
3416
3434
  }
@@ -3438,7 +3456,8 @@ var HYDRATABLE_ENTITY_KINDS = /* @__PURE__ */ new Set([
3438
3456
  "spotLight",
3439
3457
  "areaLight",
3440
3458
  "instancedMesh",
3441
- "instanceSlot"
3459
+ "instanceSlot",
3460
+ "scriptObject"
3442
3461
  ]);
3443
3462
 
3444
3463
  // core/hydration/hydrateWorld.ts
@@ -3540,6 +3559,16 @@ function buildEntityCreationPayload(sourceEntityId, kind, componentValues) {
3540
3559
  };
3541
3560
  }
3542
3561
  }
3562
+ if (kind === "scriptObject") {
3563
+ const so = asRecord(componentValues.scriptObjectValues.get(sourceEntityId));
3564
+ if (so) {
3565
+ payload.options = {
3566
+ scriptUrl: typeof so.scriptUrl === "string" ? so.scriptUrl : null,
3567
+ className: typeof so.className === "string" ? so.className : null,
3568
+ enabled: typeof so.enabled === "boolean" ? so.enabled : true
3569
+ };
3570
+ }
3571
+ }
3543
3572
  return payload;
3544
3573
  }
3545
3574
  __name(buildEntityCreationPayload, "buildEntityCreationPayload");
@@ -3657,6 +3686,148 @@ function hydrateWorldFromSnapshot(snapshot, world, objectManagementSystem) {
3657
3686
  }
3658
3687
  __name(hydrateWorldFromSnapshot, "hydrateWorldFromSnapshot");
3659
3688
 
3689
+ // core/prefab/nodeKeys.ts
3690
+ var NODE_KEY_SEPARATOR = "/";
3691
+ var ADDED_NODE_MARKER_COMPONENT = "+node";
3692
+ var asRecord2 = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null, "asRecord");
3693
+ var readBlock = /* @__PURE__ */ __name((snapshot, component) => {
3694
+ const map = /* @__PURE__ */ new Map();
3695
+ const block = snapshot.components.find((b) => b.component === component);
3696
+ for (const entry of block?.entries ?? []) {
3697
+ const record = asRecord2(entry.value);
3698
+ if (record) {
3699
+ map.set(entry.entityId, record);
3700
+ }
3701
+ }
3702
+ return map;
3703
+ }, "readBlock");
3704
+ var fallbackInstanceId = /* @__PURE__ */ __name((entityId) => `e${entityId}`, "fallbackInstanceId");
3705
+ function buildNodeKeyMapsFromSnapshot(snapshot, options) {
3706
+ const parentValues = readBlock(snapshot, "ParentId");
3707
+ const nodeValues = readBlock(snapshot, "PrefabNode");
3708
+ const instanceValues = readBlock(snapshot, "PrefabInstance");
3709
+ const inDocument = new Set(snapshot.entities);
3710
+ const childrenByParent = /* @__PURE__ */ new Map();
3711
+ const rootIds = [];
3712
+ for (const entityId of snapshot.entities) {
3713
+ const parent = parentValues.get(entityId)?.value;
3714
+ if (typeof parent === "number" && inDocument.has(parent)) {
3715
+ const siblings = childrenByParent.get(parent);
3716
+ if (siblings) siblings.push(entityId);
3717
+ else childrenByParent.set(parent, [entityId]);
3718
+ } else {
3719
+ rootIds.push(entityId);
3720
+ }
3721
+ }
3722
+ const scopeRoots = options?.scopeRootEntityId !== void 0 ? [options.scopeRootEntityId] : rootIds;
3723
+ const idToKey = /* @__PURE__ */ new Map();
3724
+ const keyToId = /* @__PURE__ */ new Map();
3725
+ const boundaryIds = /* @__PURE__ */ new Map();
3726
+ const scopeRootSet = new Set(scopeRoots);
3727
+ const queue = scopeRoots.map(
3728
+ (entityId) => ({ entityId, prefix: "" })
3729
+ );
3730
+ while (queue.length > 0) {
3731
+ const { entityId, prefix } = queue.shift();
3732
+ const instance = instanceValues.get(entityId);
3733
+ const isBoundary = Boolean(
3734
+ instance && typeof instance.url === "string" && !scopeRootSet.has(entityId)
3735
+ );
3736
+ let ownPrefix = prefix;
3737
+ let childPrefix = prefix;
3738
+ if (isBoundary && instance) {
3739
+ const instanceId = typeof instance.instanceId === "string" && instance.instanceId.length > 0 ? instance.instanceId : fallbackInstanceId(entityId);
3740
+ childPrefix = `${prefix}${instanceId}${NODE_KEY_SEPARATOR}`;
3741
+ ownPrefix = childPrefix;
3742
+ boundaryIds.set(entityId, {
3743
+ url: instance.url,
3744
+ instanceId
3745
+ });
3746
+ }
3747
+ const nodeId = nodeValues.get(entityId)?.nodeId;
3748
+ if (typeof nodeId === "string" && nodeId.length > 0) {
3749
+ const key = `${ownPrefix}${nodeId}`;
3750
+ idToKey.set(entityId, key);
3751
+ keyToId.set(key, entityId);
3752
+ }
3753
+ for (const child of childrenByParent.get(entityId) ?? []) {
3754
+ queue.push({ entityId: child, prefix: childPrefix });
3755
+ }
3756
+ }
3757
+ const rootEntityId = scopeRoots[0];
3758
+ const rootNodeId = rootEntityId !== void 0 ? nodeValues.get(rootEntityId)?.nodeId : void 0;
3759
+ return {
3760
+ idToKey,
3761
+ keyToId,
3762
+ rootKey: typeof rootNodeId === "string" && rootNodeId.length > 0 ? rootNodeId : null,
3763
+ boundaryIds
3764
+ };
3765
+ }
3766
+ __name(buildNodeKeyMapsFromSnapshot, "buildNodeKeyMapsFromSnapshot");
3767
+ function collectSnapshotSubtreeIds(snapshot, rootEntityId) {
3768
+ const parentValues = readBlock(snapshot, "ParentId");
3769
+ const childrenByParent = /* @__PURE__ */ new Map();
3770
+ for (const entityId of snapshot.entities) {
3771
+ const parent = parentValues.get(entityId)?.value;
3772
+ if (typeof parent === "number") {
3773
+ const siblings = childrenByParent.get(parent);
3774
+ if (siblings) siblings.push(entityId);
3775
+ else childrenByParent.set(parent, [entityId]);
3776
+ }
3777
+ }
3778
+ const ordered = [];
3779
+ const seen = /* @__PURE__ */ new Set();
3780
+ const queue = [rootEntityId];
3781
+ while (queue.length > 0) {
3782
+ const entityId = queue.shift();
3783
+ if (seen.has(entityId)) continue;
3784
+ seen.add(entityId);
3785
+ ordered.push(entityId);
3786
+ queue.push(...childrenByParent.get(entityId) ?? []);
3787
+ }
3788
+ return ordered;
3789
+ }
3790
+ __name(collectSnapshotSubtreeIds, "collectSnapshotSubtreeIds");
3791
+ function findTopLevelNestedRegions(snapshot) {
3792
+ const parentValues = readBlock(snapshot, "ParentId");
3793
+ const instanceValues = readBlock(snapshot, "PrefabInstance");
3794
+ const inDocument = new Set(snapshot.entities);
3795
+ const roots = new Set(
3796
+ snapshot.entities.filter((entityId) => {
3797
+ const parent = parentValues.get(entityId)?.value;
3798
+ return typeof parent !== "number" || !inDocument.has(parent);
3799
+ })
3800
+ );
3801
+ const regions = [];
3802
+ for (const [entityId, instance] of instanceValues) {
3803
+ if (roots.has(entityId)) continue;
3804
+ if (typeof instance.url !== "string" || instance.url.length === 0) continue;
3805
+ let ancestor = parentValues.get(entityId)?.value;
3806
+ let nestedDeeper = false;
3807
+ let hops = 0;
3808
+ while (typeof ancestor === "number" && hops < 512) {
3809
+ if (instanceValues.has(ancestor) && !roots.has(ancestor)) {
3810
+ nestedDeeper = true;
3811
+ break;
3812
+ }
3813
+ ancestor = parentValues.get(ancestor)?.value;
3814
+ hops += 1;
3815
+ }
3816
+ if (nestedDeeper) continue;
3817
+ regions.push({
3818
+ rootEntityId: entityId,
3819
+ url: instance.url,
3820
+ instanceId: typeof instance.instanceId === "string" && instance.instanceId.length > 0 ? instance.instanceId : fallbackInstanceId(entityId),
3821
+ overrides: Array.isArray(instance.overrides) ? instance.overrides.filter(
3822
+ (entry) => typeof entry === "string"
3823
+ ) : []
3824
+ });
3825
+ }
3826
+ regions.sort((a, b) => a.rootEntityId - b.rootEntityId);
3827
+ return regions;
3828
+ }
3829
+ __name(findTopLevelNestedRegions, "findTopLevelNestedRegions");
3830
+
3660
3831
  // core/prefab/overrides.ts
3661
3832
  var OVERRIDE_DIFF_EXCLUDED_COMPONENTS = /* @__PURE__ */ new Set([
3662
3833
  "PrefabNode",
@@ -3670,10 +3841,10 @@ var OVERRIDE_ROOT_EXCLUDED_COMPONENTS = /* @__PURE__ */ new Set([
3670
3841
  "Name",
3671
3842
  "ParentId"
3672
3843
  ]);
3673
- var asRecord2 = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null, "asRecord");
3844
+ var asRecord3 = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null, "asRecord");
3674
3845
  var clone = /* @__PURE__ */ __name((value) => value === void 0 ? value : structuredClone(value), "clone");
3675
3846
  var isNodeRef = /* @__PURE__ */ __name((value) => {
3676
- const record = asRecord2(value);
3847
+ const record = asRecord3(value);
3677
3848
  return Boolean(
3678
3849
  record && typeof record.$node === "string" && Object.keys(record).length === 1
3679
3850
  );
@@ -3685,7 +3856,7 @@ function findSnapshotRootEntityIds(snapshot) {
3685
3856
  );
3686
3857
  const parentByEntity = /* @__PURE__ */ new Map();
3687
3858
  for (const entry of parentBlock?.entries ?? []) {
3688
- parentByEntity.set(entry.entityId, asRecord2(entry.value)?.value ?? null);
3859
+ parentByEntity.set(entry.entityId, asRecord3(entry.value)?.value ?? null);
3689
3860
  }
3690
3861
  return snapshot.entities.filter(
3691
3862
  (entityId) => typeof parentByEntity.get(entityId) !== "number"
@@ -3699,7 +3870,7 @@ function buildNodeMapsFromSnapshot(snapshot) {
3699
3870
  (block) => block.component === "PrefabNode"
3700
3871
  );
3701
3872
  for (const entry of nodeBlock?.entries ?? []) {
3702
- const nodeId = asRecord2(entry.value)?.nodeId;
3873
+ const nodeId = asRecord3(entry.value)?.nodeId;
3703
3874
  if (typeof nodeId === "string" && nodeId.length > 0) {
3704
3875
  idToNode.set(entry.entityId, nodeId);
3705
3876
  nodeToId.set(nodeId, entry.entityId);
@@ -3729,9 +3900,36 @@ var toRefOrRaw = /* @__PURE__ */ __name((id, idToNode) => {
3729
3900
  const nodeId = idToNode.get(id);
3730
3901
  return nodeId !== void 0 ? makeNodeRef(nodeId) : id;
3731
3902
  }, "toRefOrRaw");
3903
+ function canonicalizeScriptDataFields(data, idToNode) {
3904
+ const canonicalData = {};
3905
+ for (const [key, fieldValue] of Object.entries(data)) {
3906
+ const field = asRecord3(fieldValue);
3907
+ if (field?.type === "entity" && typeof field.value === "number") {
3908
+ canonicalData[key] = {
3909
+ ...field,
3910
+ value: toRefOrRaw(field.value, idToNode)
3911
+ };
3912
+ continue;
3913
+ }
3914
+ const componentRef = asRecord3(field?.value);
3915
+ if (field?.type === "component" && componentRef && typeof componentRef.entityId === "number") {
3916
+ canonicalData[key] = {
3917
+ ...field,
3918
+ value: {
3919
+ ...componentRef,
3920
+ entityId: toRefOrRaw(componentRef.entityId, idToNode)
3921
+ }
3922
+ };
3923
+ continue;
3924
+ }
3925
+ canonicalData[key] = fieldValue;
3926
+ }
3927
+ return canonicalData;
3928
+ }
3929
+ __name(canonicalizeScriptDataFields, "canonicalizeScriptDataFields");
3732
3930
  function canonicalizeComponentValue(componentName, value, idToNode) {
3733
3931
  const cloned = clone(value);
3734
- const record = asRecord2(cloned);
3932
+ const record = asRecord3(cloned);
3735
3933
  if (!record) {
3736
3934
  return cloned;
3737
3935
  }
@@ -3750,43 +3948,27 @@ function canonicalizeComponentValue(componentName, value, idToNode) {
3750
3948
  const entries = Array.isArray(record.entries) ? record.entries : [];
3751
3949
  return {
3752
3950
  entries: entries.map((rawEntry) => {
3753
- const entry = asRecord2(rawEntry);
3951
+ const entry = asRecord3(rawEntry);
3754
3952
  if (!entry) {
3755
3953
  return rawEntry;
3756
3954
  }
3757
- const data = asRecord2(entry.data) ?? {};
3758
- const canonicalData = {};
3759
- for (const [key, fieldValue] of Object.entries(data)) {
3760
- const field = asRecord2(fieldValue);
3761
- if (field?.type === "entity" && typeof field.value === "number") {
3762
- canonicalData[key] = {
3763
- ...field,
3764
- value: toRefOrRaw(field.value, idToNode)
3765
- };
3766
- continue;
3767
- }
3768
- const componentRef = asRecord2(field?.value);
3769
- if (field?.type === "component" && componentRef && typeof componentRef.entityId === "number") {
3770
- canonicalData[key] = {
3771
- ...field,
3772
- value: {
3773
- ...componentRef,
3774
- entityId: toRefOrRaw(componentRef.entityId, idToNode)
3775
- }
3776
- };
3777
- continue;
3778
- }
3779
- canonicalData[key] = fieldValue;
3780
- }
3781
3955
  return {
3782
3956
  className: typeof entry.className === "string" ? entry.className : "",
3783
3957
  scriptUrl: typeof entry.scriptUrl === "string" ? entry.scriptUrl : "",
3784
3958
  enabled: typeof entry.enabled === "boolean" ? entry.enabled : true,
3785
- data: canonicalData
3959
+ data: canonicalizeScriptDataFields(asRecord3(entry.data) ?? {}, idToNode)
3786
3960
  };
3787
3961
  })
3788
3962
  };
3789
3963
  }
3964
+ if (componentName === "ScriptObjectComponent") {
3965
+ return {
3966
+ scriptUrl: typeof record.scriptUrl === "string" ? record.scriptUrl : null,
3967
+ className: typeof record.className === "string" ? record.className : null,
3968
+ enabled: typeof record.enabled === "boolean" ? record.enabled : true,
3969
+ data: canonicalizeScriptDataFields(asRecord3(record.data) ?? {}, idToNode)
3970
+ };
3971
+ }
3790
3972
  if (componentName === "InstancedMeshComponent") {
3791
3973
  if (Array.isArray(record.instanceEntityIds)) {
3792
3974
  record.instanceEntityIds = record.instanceEntityIds.map(
@@ -3811,7 +3993,7 @@ function decanonicalizeComponentValue(value, nodeToId) {
3811
3993
  if (Array.isArray(value)) {
3812
3994
  return value.map((item) => decanonicalizeComponentValue(item, nodeToId));
3813
3995
  }
3814
- const record = asRecord2(value);
3996
+ const record = asRecord3(value);
3815
3997
  if (record) {
3816
3998
  const result = {};
3817
3999
  for (const [key, item] of Object.entries(record)) {
@@ -3914,15 +4096,20 @@ function collectDiffPaths(base, current, prefix = "", out = []) {
3914
4096
  return out;
3915
4097
  }
3916
4098
  __name(collectDiffPaths, "collectDiffPaths");
3917
- function computeInstanceOverrides(baseSnapshot, instanceSnapshot) {
3918
- const baseMaps = buildNodeMapsFromSnapshot(baseSnapshot);
3919
- const instanceMaps = buildNodeMapsFromSnapshot(instanceSnapshot);
4099
+ function computeInstanceOverrides(baseSnapshot, instanceSnapshot, options) {
4100
+ const baseMaps = buildNodeKeyMapsFromSnapshot(baseSnapshot);
4101
+ const instanceMaps = buildNodeKeyMapsFromSnapshot(instanceSnapshot);
3920
4102
  const baseByEntity = indexSnapshotByEntity(baseSnapshot);
3921
4103
  const instanceByEntity = indexSnapshotByEntity(instanceSnapshot);
3922
4104
  const overrides = [];
3923
- for (const [nodeId, instanceEntityId] of instanceMaps.nodeToId) {
3924
- const baseEntityId = baseMaps.nodeToId.get(nodeId);
4105
+ for (const [nodeKey, instanceEntityId] of instanceMaps.keyToId) {
4106
+ const baseEntityId = baseMaps.keyToId.get(nodeKey);
3925
4107
  if (baseEntityId === void 0) {
4108
+ if (options?.emitAdditionMarkers) {
4109
+ overrides.push(
4110
+ encodeOverride(nodeKey, ADDED_NODE_MARKER_COMPONENT, "")
4111
+ );
4112
+ }
3926
4113
  continue;
3927
4114
  }
3928
4115
  const baseComponents = baseByEntity.get(baseEntityId);
@@ -3930,7 +4117,7 @@ function computeInstanceOverrides(baseSnapshot, instanceSnapshot) {
3930
4117
  if (!baseComponents) {
3931
4118
  continue;
3932
4119
  }
3933
- const isRoot = nodeId === baseMaps.rootNodeId;
4120
+ const isRoot = nodeKey === baseMaps.rootKey;
3934
4121
  for (const [componentName, baseRaw] of baseComponents) {
3935
4122
  if (OVERRIDE_DIFF_EXCLUDED_COMPONENTS.has(componentName)) {
3936
4123
  continue;
@@ -3945,15 +4132,15 @@ function computeInstanceOverrides(baseSnapshot, instanceSnapshot) {
3945
4132
  const baseCanonical = canonicalizeComponentValue(
3946
4133
  componentName,
3947
4134
  baseRaw,
3948
- baseMaps.idToNode
4135
+ baseMaps.idToKey
3949
4136
  );
3950
4137
  const instanceCanonical = canonicalizeComponentValue(
3951
4138
  componentName,
3952
4139
  instanceRaw,
3953
- instanceMaps.idToNode
4140
+ instanceMaps.idToKey
3954
4141
  );
3955
4142
  for (const path of collectDiffPaths(baseCanonical, instanceCanonical)) {
3956
- overrides.push(encodeOverride(nodeId, componentName, path));
4143
+ overrides.push(encodeOverride(nodeKey, componentName, path));
3957
4144
  }
3958
4145
  }
3959
4146
  }
@@ -3967,7 +4154,7 @@ function getAtPath(value, tokens) {
3967
4154
  if (Array.isArray(cursor)) {
3968
4155
  cursor = cursor[Number(token)];
3969
4156
  } else {
3970
- const record = asRecord2(cursor);
4157
+ const record = asRecord3(cursor);
3971
4158
  if (!record) {
3972
4159
  return void 0;
3973
4160
  }
@@ -3987,7 +4174,7 @@ function setAtPath(target, tokens, value) {
3987
4174
  if (Array.isArray(cursor)) {
3988
4175
  cursor = cursor[Number(token)];
3989
4176
  } else {
3990
- const record = asRecord2(cursor);
4177
+ const record = asRecord3(cursor);
3991
4178
  if (!record) {
3992
4179
  return;
3993
4180
  }
@@ -4001,7 +4188,7 @@ function setAtPath(target, tokens, value) {
4001
4188
  if (Array.isArray(cursor)) {
4002
4189
  cursor[Number(last)] = value;
4003
4190
  } else {
4004
- const record = asRecord2(cursor);
4191
+ const record = asRecord3(cursor);
4005
4192
  if (record) {
4006
4193
  if (value === void 0) {
4007
4194
  delete record[last];
@@ -4033,15 +4220,20 @@ __name(mergeComponentValue, "mergeComponentValue");
4033
4220
  var INSTANTIATE_EXCLUDED_COMPONENTS = /* @__PURE__ */ new Set([
4034
4221
  "Environment",
4035
4222
  "Postprocessing",
4036
- "SelectionTransformState",
4037
- "PrefabInstance"
4223
+ "SelectionTransformState"
4038
4224
  ]);
4039
4225
  function instantiateSubtree(world, objectManagementSystem, snapshot) {
4226
+ const snapshotRootIds = new Set(findSnapshotRootEntityIds(snapshot));
4040
4227
  const safeSnapshot = {
4041
4228
  entities: snapshot.entities,
4042
- components: snapshot.components.filter(
4043
- (block) => !INSTANTIATE_EXCLUDED_COMPONENTS.has(block.component)
4044
- )
4229
+ components: snapshot.components.filter((block) => !INSTANTIATE_EXCLUDED_COMPONENTS.has(block.component)).map(
4230
+ (block) => block.component === "PrefabInstance" ? {
4231
+ component: block.component,
4232
+ entries: block.entries.filter(
4233
+ (entry) => !snapshotRootIds.has(entry.entityId)
4234
+ )
4235
+ } : block
4236
+ ).filter((block) => block.entries.length > 0)
4045
4237
  };
4046
4238
  const result = hydrateWorldFromSnapshotDetailed(
4047
4239
  safeSnapshot,
@@ -4061,6 +4253,11 @@ __name(instantiateSubtree, "instantiateSubtree");
4061
4253
 
4062
4254
  // core/prefab/types.ts
4063
4255
  var PREFAB_FILE_VERSION = 1;
4256
+ var PREFAB_FILE_VERSION_NESTED = 2;
4257
+ function prefabFileVersionFor(snapshot) {
4258
+ return findTopLevelNestedRegions(snapshot).length > 0 ? PREFAB_FILE_VERSION_NESTED : PREFAB_FILE_VERSION;
4259
+ }
4260
+ __name(prefabFileVersionFor, "prefabFileVersionFor");
4064
4261
  var isRecord = /* @__PURE__ */ __name((value) => value !== null && typeof value === "object" && !Array.isArray(value), "isRecord");
4065
4262
  function parsePrefabAsset(json) {
4066
4263
  if (!isRecord(json)) {
@@ -4474,22 +4671,14 @@ var _Behaviour = class _Behaviour {
4474
4671
  /**
4475
4672
  * Called once before the first {@link Behaviour.onUpdate}. Override to
4476
4673
  * default and validate {@link Behaviour.data}, cache references, or set
4477
- * initial state.
4478
- *
4479
- * @remarks
4480
- * Also called in editor mode when `executeInEditor` is `true` on the script
4481
- * entry.
4674
+ * initial state. Runs at preview/runtime only — never in the editor.
4482
4675
  */
4483
4676
  init() {
4484
4677
  }
4485
4678
  /**
4486
4679
  * Called every frame while the script is enabled. Override with the
4487
- * script's main per-frame logic.
4488
- *
4489
- * @remarks
4490
- * Also called every frame in editor mode when `executeInEditor` is `true`.
4491
- * Access the Three.js scene with `this.world.getScene()` for editor-only
4492
- * rendering work such as custom shaders or procedural geometry.
4680
+ * script's main per-frame logic. Runs at preview/runtime only — never in the
4681
+ * editor (use {@link ScriptObject} for editor-visible visuals).
4493
4682
  */
4494
4683
  onUpdate() {
4495
4684
  }
@@ -4528,7 +4717,14 @@ __name(_Behaviour, "Behaviour");
4528
4717
  _Behaviour.__reluBehaviourClass = true;
4529
4718
  var Behaviour = _Behaviour;
4530
4719
 
4531
- // core/systems/ScriptBehaviourSystem.ts
4720
+ // core/systems/scriptShared.ts
4721
+ async function defaultModuleLoader(scriptUrl) {
4722
+ return await import(
4723
+ /* webpackIgnore: true */
4724
+ scriptUrl
4725
+ );
4726
+ }
4727
+ __name(defaultModuleLoader, "defaultModuleLoader");
4532
4728
  function toErrorMessage(error) {
4533
4729
  const sanitize = /* @__PURE__ */ __name((value) => value.replace(/localasset:\/\/[^/]+\//g, "localasset://").replace(/\/Users\/[^\s)]+/g, "[local path]"), "sanitize");
4534
4730
  if (error instanceof Error && error.message) {
@@ -4540,26 +4736,22 @@ function toErrorMessage(error) {
4540
4736
  return "Unknown script error.";
4541
4737
  }
4542
4738
  __name(toErrorMessage, "toErrorMessage");
4543
- function buildInstanceKey(entityId, scriptUrl, className) {
4544
- return `${entityId}:${scriptUrl}:${className}`;
4545
- }
4546
- __name(buildInstanceKey, "buildInstanceKey");
4547
- function resolveBehaviourCtor(moduleValue, className) {
4739
+ function resolveScriptCtor(moduleValue, className, options) {
4548
4740
  const named = moduleValue[className];
4549
4741
  const fallback = moduleValue.default;
4550
4742
  const candidate = typeof named === "function" ? named : typeof fallback === "function" ? fallback : null;
4551
4743
  if (!candidate) {
4552
4744
  return null;
4553
4745
  }
4554
- if (!(candidate.prototype instanceof Behaviour)) {
4555
- const isBehaviourSubclass = !!candidate.__reluBehaviourClass;
4556
- if (!isBehaviourSubclass) {
4746
+ if (!(candidate.prototype instanceof options.baseClass)) {
4747
+ const marker = candidate[options.markerKey];
4748
+ if (!marker) {
4557
4749
  return null;
4558
4750
  }
4559
4751
  }
4560
4752
  return candidate;
4561
4753
  }
4562
- __name(resolveBehaviourCtor, "resolveBehaviourCtor");
4754
+ __name(resolveScriptCtor, "resolveScriptCtor");
4563
4755
  function normalizeScriptData(value) {
4564
4756
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4565
4757
  }
@@ -4641,8 +4833,8 @@ function resolveInputData(world, rawData) {
4641
4833
  return resolved;
4642
4834
  }
4643
4835
  __name(resolveInputData, "resolveInputData");
4644
- function serializeDataForStorage(rawData, behaviourData) {
4645
- const result = { ...behaviourData };
4836
+ function serializeDataForStorage(rawData, instanceData) {
4837
+ const result = { ...instanceData };
4646
4838
  for (const [key, rawValue] of Object.entries(rawData)) {
4647
4839
  if (isRawEntityInput(rawValue) || isRawComponentInput(rawValue)) {
4648
4840
  result[key] = rawValue;
@@ -4651,13 +4843,25 @@ function serializeDataForStorage(rawData, behaviourData) {
4651
4843
  return result;
4652
4844
  }
4653
4845
  __name(serializeDataForStorage, "serializeDataForStorage");
4846
+
4847
+ // core/systems/ScriptBehaviourSystem.ts
4848
+ function buildInstanceKey(entityId, scriptUrl, className) {
4849
+ return `${entityId}:${scriptUrl}:${className}`;
4850
+ }
4851
+ __name(buildInstanceKey, "buildInstanceKey");
4852
+ function resolveBehaviourCtor(moduleValue, className) {
4853
+ return resolveScriptCtor(moduleValue, className, {
4854
+ baseClass: Behaviour,
4855
+ markerKey: "__reluBehaviourClass"
4856
+ });
4857
+ }
4858
+ __name(resolveBehaviourCtor, "resolveBehaviourCtor");
4654
4859
  function normalizeEntry(value, fallbackEntityId) {
4655
4860
  return {
4656
4861
  entityId: typeof value?.entityId === "number" ? value.entityId : fallbackEntityId,
4657
4862
  className: typeof value?.className === "string" ? value.className : "",
4658
4863
  scriptUrl: typeof value?.scriptUrl === "string" ? value.scriptUrl : "",
4659
4864
  enabled: typeof value?.enabled === "boolean" ? value.enabled : true,
4660
- executeInEditor: typeof value?.executeInEditor === "boolean" ? value.executeInEditor : false,
4661
4865
  data: normalizeScriptData(value?.data),
4662
4866
  runtimeState: value?.runtimeState === "idle" || value?.runtimeState === "loading" || value?.runtimeState === "ready" || value?.runtimeState === "error" ? value.runtimeState : void 0,
4663
4867
  runtimeMessage: typeof value?.runtimeMessage === "string" ? value.runtimeMessage : void 0
@@ -4680,13 +4884,6 @@ function writeScriptEntries(component, entries) {
4680
4884
  }
4681
4885
  }
4682
4886
  __name(writeScriptEntries, "writeScriptEntries");
4683
- async function defaultModuleLoader(scriptUrl) {
4684
- return await import(
4685
- /* webpackIgnore: true */
4686
- scriptUrl
4687
- );
4688
- }
4689
- __name(defaultModuleLoader, "defaultModuleLoader");
4690
4887
  var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
4691
4888
  constructor(moduleLoader, selectionContext) {
4692
4889
  this.instances = /* @__PURE__ */ new Map();
@@ -4802,11 +4999,11 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
4802
4999
  let hasEnabledScript = false;
4803
5000
  for (let entryIndex = 0; entryIndex < nextEntries.length; entryIndex += 1) {
4804
5001
  const entry = nextEntries[entryIndex];
4805
- const skipInEditor = world.isEditorMode() && !entry.executeInEditor;
5002
+ const skipInEditor = world.isEditorMode();
4806
5003
  if (!entry.enabled || !entry.scriptUrl || skipInEditor) {
4807
5004
  nextEntries[entryIndex] = {
4808
5005
  ...entry,
4809
- runtimeState: skipInEditor ? "idle" : "idle",
5006
+ runtimeState: "idle",
4810
5007
  runtimeMessage: void 0
4811
5008
  };
4812
5009
  continue;
@@ -4993,7 +5190,7 @@ var _ScriptBehaviourSystem = class _ScriptBehaviourSystem {
4993
5190
  const mergedData = mergeDataByCurrentDefaults(defaults, currentData);
4994
5191
  const resolvedData = resolveInputData(world, mergedData);
4995
5192
  behaviour.data = resolvedData;
4996
- if (!world.isEditorMode() || liveEntry.executeInEditor === true) {
5193
+ if (!world.isEditorMode()) {
4997
5194
  behaviour.init();
4998
5195
  }
4999
5196
  liveEntries[entryIndex] = {
@@ -5021,6 +5218,328 @@ __name(_ScriptBehaviourSystem, "ScriptBehaviourSystem");
5021
5218
  _ScriptBehaviourSystem.CLICK_THRESHOLD = 6;
5022
5219
  var ScriptBehaviourSystem = _ScriptBehaviourSystem;
5023
5220
 
5221
+ // core/ScriptObject.ts
5222
+ var _ScriptObject = class _ScriptObject {
5223
+ /**
5224
+ * Engine-managed constructor. `ScriptObjectSystem` instantiates subclasses
5225
+ * with the owning world, the entity id, and the component's `data`. User code
5226
+ * should not call this directly.
5227
+ */
5228
+ constructor(world, entityId, data) {
5229
+ /**
5230
+ * The script's data payload (the `ScriptObjectComponent.data`). Declare a
5231
+ * concrete shape on subclasses with `declare data: MyShape` for type safety.
5232
+ * Use the same typed input envelopes as Behaviours (`ColorInput`,
5233
+ * `SliderInput`, `EntityInput`, …) to get inspector fields.
5234
+ */
5235
+ this.data = {};
5236
+ /**
5237
+ * The Object3D returned by {@link ScriptObject.build}. Assigned by
5238
+ * `ScriptObjectSystem` after `build()` runs; `null` until then.
5239
+ */
5240
+ this.root = null;
5241
+ this.world = world;
5242
+ this.entityId = entityId;
5243
+ if (data) this.data = data;
5244
+ }
5245
+ /**
5246
+ * REQUIRED — build and return the visual root. Called once by
5247
+ * `ScriptObjectSystem` after construction. The returned object is attached
5248
+ * under the entity's Object3D (so it inherits the entity transform, selection
5249
+ * and visibility). Subclasses MUST override.
5250
+ */
5251
+ build() {
5252
+ throw new Error("ScriptObject subclasses must implement build()");
5253
+ }
5254
+ };
5255
+ __name(_ScriptObject, "ScriptObject");
5256
+ /**
5257
+ * Static marker used by `ScriptObjectSystem` to detect `ScriptObject`
5258
+ * subclasses even when a bundler ships a second copy of the package into the
5259
+ * script blob (breaking `instanceof`). Do NOT override.
5260
+ */
5261
+ _ScriptObject.__reluScriptObjectClass = true;
5262
+ var ScriptObject = _ScriptObject;
5263
+
5264
+ // core/systems/ScriptObjectSystem.ts
5265
+ var SCRIPT_OBJECT_ROOT_FLAG = "__scriptObjectRoot";
5266
+ function setInstanceRef(component, value) {
5267
+ Object.defineProperty(component, "instance", {
5268
+ value,
5269
+ writable: true,
5270
+ configurable: true,
5271
+ enumerable: false
5272
+ });
5273
+ }
5274
+ __name(setInstanceRef, "setInstanceRef");
5275
+ function disposeBuiltObject(root) {
5276
+ root.parent?.remove(root);
5277
+ root.traverse((node) => {
5278
+ const mesh = node;
5279
+ const geometry = mesh.geometry;
5280
+ if (geometry?.dispose && !geometry.userData?.[SHARED_MODEL_RESOURCE_FLAG]) {
5281
+ geometry.dispose();
5282
+ }
5283
+ const material = mesh.material;
5284
+ const materials = Array.isArray(material) ? material : material ? [material] : [];
5285
+ for (const mat of materials) {
5286
+ if (mat.userData?.[SHARED_MODEL_RESOURCE_FLAG]) continue;
5287
+ for (const value of Object.values(mat)) {
5288
+ if (value instanceof THREE7.Texture) value.dispose();
5289
+ }
5290
+ mat.dispose?.();
5291
+ }
5292
+ });
5293
+ }
5294
+ __name(disposeBuiltObject, "disposeBuiltObject");
5295
+ var _ScriptObjectSystem = class _ScriptObjectSystem {
5296
+ constructor(moduleLoader) {
5297
+ this.instances = /* @__PURE__ */ new Map();
5298
+ this.loading = /* @__PURE__ */ new Map();
5299
+ /** Bumped by invalidate*() so in-flight loads that predate it are discarded. */
5300
+ this.epoch = 0;
5301
+ this.moduleLoader = moduleLoader ?? defaultModuleLoader;
5302
+ }
5303
+ update(world, deltaTime) {
5304
+ const active = /* @__PURE__ */ new Map();
5305
+ for (const entityId of world.query(ScriptObjectComponent)) {
5306
+ const component = world.getComponent(entityId, ScriptObjectComponent);
5307
+ if (!component) continue;
5308
+ const wantsRun = component.enabled && typeof component.scriptUrl === "string" && typeof component.className === "string";
5309
+ if (!wantsRun) {
5310
+ const record = this.instances.get(entityId);
5311
+ if (record) this.teardownRecord(world, entityId, record, false);
5312
+ this.loading.delete(entityId);
5313
+ if (component.runtimeState !== "idle") {
5314
+ component.runtimeState = "idle";
5315
+ component.runtimeMessage = void 0;
5316
+ }
5317
+ continue;
5318
+ }
5319
+ active.set(entityId, component);
5320
+ }
5321
+ for (const [entityId, record] of this.instances) {
5322
+ const component = active.get(entityId);
5323
+ const identityChanged = !component || record.scriptUrl !== component.scriptUrl || record.className !== component.className;
5324
+ if (!world.hasEntity(entityId) || identityChanged) {
5325
+ this.teardownRecord(world, entityId, record, false);
5326
+ }
5327
+ }
5328
+ for (const [entityId, component] of active) {
5329
+ const record = this.instances.get(entityId);
5330
+ if (!record || record.status !== "ready" || !record.instance) continue;
5331
+ if (!world.isEntityVisible(entityId)) continue;
5332
+ if (component.data !== record.lastWrittenData) {
5333
+ try {
5334
+ const merged = mergeDataByCurrentDefaults(
5335
+ record.rawData,
5336
+ normalizeScriptData(component.data)
5337
+ );
5338
+ record.rawData = merged;
5339
+ record.instance.data = resolveInputData(world, merged);
5340
+ const stored = serializeDataForStorage(merged, merged);
5341
+ component.data = stored;
5342
+ record.lastWrittenData = stored;
5343
+ } catch (error) {
5344
+ this.markError(world, entityId, component, error);
5345
+ continue;
5346
+ }
5347
+ }
5348
+ try {
5349
+ record.instance.update?.(deltaTime);
5350
+ } catch (error) {
5351
+ this.markError(world, entityId, component, error);
5352
+ }
5353
+ }
5354
+ for (const [entityId, component] of active) {
5355
+ if (this.instances.has(entityId) || this.loading.has(entityId)) continue;
5356
+ const scriptUrl = component.scriptUrl;
5357
+ const className = component.className;
5358
+ if (component.runtimeState !== "loading") {
5359
+ component.runtimeState = "loading";
5360
+ component.runtimeMessage = void 0;
5361
+ }
5362
+ const epochAtStart = this.epoch;
5363
+ const key = `${epochAtStart}:${scriptUrl}:${className}`;
5364
+ const promise = this.loadScriptObject(
5365
+ world,
5366
+ entityId,
5367
+ scriptUrl,
5368
+ className,
5369
+ key,
5370
+ epochAtStart
5371
+ ).catch((error) => {
5372
+ this.markError(world, entityId, void 0, error);
5373
+ console.error(
5374
+ `[ScriptObjectSystem] Failed loading ${className} (${scriptUrl})`,
5375
+ error
5376
+ );
5377
+ }).finally(() => {
5378
+ if (this.loading.get(entityId)?.key === key) {
5379
+ this.loading.delete(entityId);
5380
+ }
5381
+ });
5382
+ this.loading.set(entityId, { key, promise });
5383
+ }
5384
+ for (const entityId of this.loading.keys()) {
5385
+ if (!active.has(entityId)) this.loading.delete(entityId);
5386
+ }
5387
+ }
5388
+ async loadScriptObject(world, entityId, scriptUrl, className, key, epochAtStart) {
5389
+ const moduleValue = await this.moduleLoader(scriptUrl);
5390
+ if (this.loading.get(entityId)?.key !== key || epochAtStart !== this.epoch || !world.hasEntity(entityId)) {
5391
+ return;
5392
+ }
5393
+ const component = world.getComponent(entityId, ScriptObjectComponent);
5394
+ if (!component || !component.enabled || component.scriptUrl !== scriptUrl || component.className !== className) {
5395
+ return;
5396
+ }
5397
+ const ctor = resolveScriptCtor(moduleValue, className, {
5398
+ baseClass: ScriptObject,
5399
+ markerKey: "__reluScriptObjectClass"
5400
+ });
5401
+ if (!ctor) {
5402
+ throw new Error(
5403
+ `Missing ScriptObject export '${className}' in ${scriptUrl}`
5404
+ );
5405
+ }
5406
+ const currentData = normalizeScriptData(component.data);
5407
+ let instance;
5408
+ try {
5409
+ instance = new ctor(world, entityId, void 0);
5410
+ } catch {
5411
+ instance = new ctor(world, entityId, currentData);
5412
+ }
5413
+ const defaults = normalizeScriptData(instance.data);
5414
+ const merged = mergeDataByCurrentDefaults(defaults, currentData);
5415
+ instance.data = resolveInputData(world, merged);
5416
+ const parent = world.getComponent(entityId, Object3DRef)?.object;
5417
+ if (!parent) {
5418
+ throw new Error("Script object entity has no Object3D to attach to.");
5419
+ }
5420
+ for (const child of [...parent.children]) {
5421
+ if (child.userData?.[SCRIPT_OBJECT_ROOT_FLAG]) {
5422
+ disposeBuiltObject(child);
5423
+ }
5424
+ }
5425
+ let built;
5426
+ try {
5427
+ built = instance.build();
5428
+ } catch (error) {
5429
+ try {
5430
+ instance.dispose?.();
5431
+ } catch {
5432
+ }
5433
+ throw error;
5434
+ }
5435
+ if (!built || built.isObject3D !== true) {
5436
+ try {
5437
+ instance.dispose?.();
5438
+ } catch {
5439
+ }
5440
+ throw new Error("build() must return a THREE.Object3D.");
5441
+ }
5442
+ built.userData[SCRIPT_OBJECT_ROOT_FLAG] = true;
5443
+ parent.add(built);
5444
+ instance.root = built;
5445
+ const stored = serializeDataForStorage(merged, merged);
5446
+ component.data = stored;
5447
+ component.runtimeState = "ready";
5448
+ component.runtimeMessage = void 0;
5449
+ setInstanceRef(component, instance);
5450
+ this.instances.set(entityId, {
5451
+ entityId,
5452
+ scriptUrl,
5453
+ className,
5454
+ status: "ready",
5455
+ instance,
5456
+ root: built,
5457
+ rawData: merged,
5458
+ lastWrittenData: stored
5459
+ });
5460
+ }
5461
+ /** Dispose a record's instance + built subtree. `keepTombstone` leaves an error marker. */
5462
+ teardownRecord(world, entityId, record, keepTombstone) {
5463
+ try {
5464
+ record.instance?.dispose?.();
5465
+ } catch {
5466
+ }
5467
+ if (record.root) disposeBuiltObject(record.root);
5468
+ const component = world.getComponent(entityId, ScriptObjectComponent);
5469
+ if (component) setInstanceRef(component, null);
5470
+ if (keepTombstone) {
5471
+ this.instances.set(entityId, {
5472
+ ...record,
5473
+ status: "error",
5474
+ instance: null,
5475
+ root: null
5476
+ });
5477
+ } else {
5478
+ this.instances.delete(entityId);
5479
+ }
5480
+ }
5481
+ markError(world, entityId, component, error) {
5482
+ const comp = component ?? world.getComponent(entityId, ScriptObjectComponent) ?? null;
5483
+ if (comp) {
5484
+ comp.runtimeState = "error";
5485
+ comp.runtimeMessage = toErrorMessage(error);
5486
+ }
5487
+ const record = this.instances.get(entityId);
5488
+ if (record) {
5489
+ this.teardownRecord(world, entityId, record, true);
5490
+ } else {
5491
+ this.instances.set(entityId, {
5492
+ entityId,
5493
+ scriptUrl: comp?.scriptUrl ?? "",
5494
+ className: comp?.className ?? "",
5495
+ status: "error",
5496
+ instance: null,
5497
+ root: null,
5498
+ rawData: {},
5499
+ lastWrittenData: null
5500
+ });
5501
+ }
5502
+ }
5503
+ /** Dispose all live instances so the next update() reloads with fresh modules. */
5504
+ invalidateAll() {
5505
+ this.epoch += 1;
5506
+ for (const [entityId, record] of [...this.instances]) {
5507
+ try {
5508
+ record.instance?.dispose?.();
5509
+ } catch {
5510
+ }
5511
+ if (record.root) disposeBuiltObject(record.root);
5512
+ this.instances.delete(entityId);
5513
+ }
5514
+ this.loading.clear();
5515
+ }
5516
+ /** Invalidate only the entities whose script matches `scriptUrl`. */
5517
+ invalidate(scriptUrl) {
5518
+ this.epoch += 1;
5519
+ for (const [entityId, record] of [...this.instances]) {
5520
+ if (record.scriptUrl !== scriptUrl) continue;
5521
+ try {
5522
+ record.instance?.dispose?.();
5523
+ } catch {
5524
+ }
5525
+ if (record.root) disposeBuiltObject(record.root);
5526
+ this.instances.delete(entityId);
5527
+ }
5528
+ for (const [entityId, entry] of [...this.loading]) {
5529
+ if (entry.key.includes(`:${scriptUrl}:`)) this.loading.delete(entityId);
5530
+ }
5531
+ }
5532
+ dispose(world) {
5533
+ for (const [entityId, record] of [...this.instances]) {
5534
+ this.teardownRecord(world, entityId, record, false);
5535
+ }
5536
+ this.instances.clear();
5537
+ this.loading.clear();
5538
+ }
5539
+ };
5540
+ __name(_ScriptObjectSystem, "ScriptObjectSystem");
5541
+ var ScriptObjectSystem = _ScriptObjectSystem;
5542
+
5024
5543
  // core/systems/TransformSyncSystem.ts
5025
5544
  var _TransformSyncSystem = class _TransformSyncSystem {
5026
5545
  update(world) {
@@ -5282,6 +5801,6 @@ var _VideoSystem = class _VideoSystem {
5282
5801
  __name(_VideoSystem, "VideoSystem");
5283
5802
  var VideoSystem = _VideoSystem;
5284
5803
 
5285
- export { AnimationSystem, AudioSystem, Behaviour, CameraSyncSystem, EnvironmentSyncSystem, HYDRATABLE_ENTITY_KINDS, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MaterialSyncSystem, MeshGeometrySystem, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PREFAB_FILE_VERSION, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, ScriptBehaviourSystem, TransformSyncSystem, VideoSystem, buildComponentValueMaps, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, computeInstanceOverrides, decanonicalizeComponentValue, despawnPrefab, encodeOverride, findSnapshotRootEntityIds, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, mergeComponentValue, parseOverride, parsePrefabAsset, restoreComponentState, restorePostCreateComponents, setPrefabAssetUrlResolver, spawnPrefab };
5286
- //# sourceMappingURL=chunk-67MFQDHX.js.map
5287
- //# sourceMappingURL=chunk-67MFQDHX.js.map
5804
+ export { ADDED_NODE_MARKER_COMPONENT, AnimationSystem, AudioSystem, Behaviour, CameraSyncSystem, EnvironmentSyncSystem, HYDRATABLE_ENTITY_KINDS, InputSystem, InstancedMeshSystem, KeyboardKeys, LightSyncSystem, MaterialSyncSystem, MeshGeometrySystem, NODE_KEY_SEPARATOR, OVERRIDE_DIFF_EXCLUDED_COMPONENTS, OVERRIDE_ROOT_EXCLUDED_COMPONENTS, ObjectVisibilitySystem, PREFAB_FILE_VERSION, PREFAB_FILE_VERSION_NESTED, PhysicsSyncSystem, PostprocessingSyncSystem, RenderSystem, SCRIPT_OBJECT_ROOT_FLAG, ScriptBehaviourSystem, ScriptObject, ScriptObjectSystem, TransformSyncSystem, VideoSystem, buildComponentValueMaps, buildNodeKeyMapsFromSnapshot, buildNodeMapsFromSnapshot, canonicalizeComponentValue, clearPrefabCache, collectDiffPaths, collectEntitySubtree, collectSnapshotSubtreeIds, computeInstanceOverrides, decanonicalizeComponentValue, despawnPrefab, encodeOverride, findSnapshotRootEntityIds, findTopLevelNestedRegions, groupOverridePaths, hydrateWorldFromSnapshot, hydrateWorldFromSnapshotDetailed, indexSnapshotByEntity, instantiateSubtree, loadPrefab, mergeComponentValue, parseOverride, parsePrefabAsset, prefabFileVersionFor, restoreComponentState, restorePostCreateComponents, setPrefabAssetUrlResolver, spawnPrefab };
5805
+ //# sourceMappingURL=chunk-GZ2NKSMV.js.map
5806
+ //# sourceMappingURL=chunk-GZ2NKSMV.js.map