@forgeax/engine-scene 0.1.27 → 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 (38) hide show
  1. package/README.md +50 -0
  2. package/dist/__tests__/keyed-scene.integration.test.d.ts +2 -0
  3. package/dist/__tests__/keyed-scene.integration.test.d.ts.map +1 -0
  4. package/dist/assets/scene-decoder.d.ts.map +1 -1
  5. package/dist/index.d.ts +3 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.mjs +848 -229
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/instances/binding.d.ts +7 -5
  10. package/dist/instances/binding.d.ts.map +1 -1
  11. package/dist/instances/externalization.d.ts +1 -1
  12. package/dist/instances/externalization.d.ts.map +1 -1
  13. package/dist/instances/keyed.d.ts +38 -0
  14. package/dist/instances/keyed.d.ts.map +1 -0
  15. package/dist/instances/legacy.d.ts +20 -0
  16. package/dist/instances/legacy.d.ts.map +1 -0
  17. package/dist/instances/runtime-types.d.ts +20 -0
  18. package/dist/instances/runtime-types.d.ts.map +1 -0
  19. package/dist/instances/scene-instances.d.ts +27 -37
  20. package/dist/instances/scene-instances.d.ts.map +1 -1
  21. package/dist/instances/state.d.ts +6 -1
  22. package/dist/instances/state.d.ts.map +1 -1
  23. package/package.json +5 -5
  24. package/src/__tests__/asset-owner.integration.test.ts +4 -7
  25. package/src/__tests__/flat-propagation.perf.test.ts +18 -17
  26. package/src/__tests__/keyed-scene.integration.test.ts +269 -0
  27. package/src/__tests__/scene-binding.integration.test.ts +141 -32
  28. package/src/__tests__/structural.test.ts +4 -1
  29. package/src/assets/scene-decoder.ts +123 -33
  30. package/src/collect-subtree.ts +2 -2
  31. package/src/index.ts +10 -1
  32. package/src/instances/binding.ts +26 -19
  33. package/src/instances/externalization.ts +115 -97
  34. package/src/instances/keyed.ts +505 -0
  35. package/src/instances/legacy.ts +108 -0
  36. package/src/instances/runtime-types.ts +21 -0
  37. package/src/instances/scene-instances.ts +220 -83
  38. package/src/instances/state.ts +6 -1
package/dist/index.mjs CHANGED
@@ -3,13 +3,81 @@ import {
3
3
  err,
4
4
  ok
5
5
  } from "@forgeax/engine-types";
6
+
7
+ // src/instances/legacy.ts
8
+ function migrateLegacySceneComponentFields(componentName, source, addressByLocalId) {
9
+ const fields = { ...source };
10
+ const address = (value) => Number.isSafeInteger(value) ? addressByLocalId?.get(value) ?? String(value) : value;
11
+ if (componentName === "DirectionalLight" && Object.hasOwn(fields, "pcfKernelSize")) {
12
+ const kernel = fields.pcfKernelSize;
13
+ const shadowFilter = kernel === 1 ? 1 : kernel === 3 ? 2 : kernel === 5 ? 3 : void 0;
14
+ if (shadowFilter !== void 0 && !Object.hasOwn(fields, "shadowFilter")) {
15
+ delete fields.pcfKernelSize;
16
+ fields.shadowFilter = shadowFilter;
17
+ }
18
+ }
19
+ if (componentName === "ChildOf" && Number.isSafeInteger(fields.parent)) {
20
+ fields.parent = address(fields.parent);
21
+ }
22
+ if (componentName === "Children" && Array.isArray(fields.entities)) {
23
+ fields.entities = fields.entities.map(address);
24
+ }
25
+ return fields;
26
+ }
27
+ function normalizeLegacySceneAsset(scene) {
28
+ if (scene === null || typeof scene !== "object") return scene;
29
+ const candidate = scene;
30
+ if (!Array.isArray(candidate.entities))
31
+ return scene;
32
+ const rows = candidate.entities;
33
+ const addressByLocalId = /* @__PURE__ */ new Map();
34
+ const rowKeys = [];
35
+ const used = /* @__PURE__ */ new Set();
36
+ for (const [index, row] of rows.entries()) {
37
+ const localId = Number.isSafeInteger(row?.localId) ? row.localId : index;
38
+ const bindingKey = typeof row?.bindingKey === "string" && row.bindingKey.length > 0 ? row.bindingKey : String(localId);
39
+ const key = used.has(bindingKey) ? String(localId) : bindingKey;
40
+ used.add(key);
41
+ addressByLocalId.set(localId, key);
42
+ rowKeys.push(key);
43
+ }
44
+ const entities = {};
45
+ for (const [index, row] of rows.entries()) {
46
+ const key = rowKeys[index];
47
+ const rawComponents = row?.components;
48
+ const components = {};
49
+ if (rawComponents !== null && typeof rawComponents === "object" && !Array.isArray(rawComponents)) {
50
+ for (const [componentName, rawFields] of Object.entries(
51
+ rawComponents
52
+ )) {
53
+ if (rawFields === null || typeof rawFields !== "object" || Array.isArray(rawFields))
54
+ continue;
55
+ components[componentName] = migrateLegacySceneComponentFields(
56
+ componentName,
57
+ rawFields,
58
+ addressByLocalId
59
+ );
60
+ }
61
+ }
62
+ entities[key] = {
63
+ components,
64
+ ...row?.instance === void 0 ? {} : { instance: row.instance }
65
+ };
66
+ }
67
+ return {
68
+ ...scene,
69
+ entities
70
+ };
71
+ }
72
+
73
+ // src/assets/scene-decoder.ts
6
74
  var sceneAssetKind = {
7
75
  kind: "scene"
8
76
  };
9
77
  function invalidScene(guid, reason) {
10
78
  return err({
11
79
  code: "asset-package-invalid",
12
- expected: "a scene payload with an entities array",
80
+ expected: "a scene payload with keyed entities",
13
81
  hint: "recook the SceneAsset and publish its complete envelope",
14
82
  detail: { guid, reason }
15
83
  });
@@ -25,19 +93,12 @@ function resolveWireRef(refs, value, location) {
25
93
  }
26
94
  return { ok: true, value: guid };
27
95
  }
28
- function resolveMounts(mounts, refs) {
29
- if (mounts === void 0) return { ok: true, value: void 0 };
30
- const resolved = [];
31
- for (const mount of mounts) {
32
- if (typeof mount.source !== "number" || !Number.isInteger(mount.source)) {
33
- resolved.push(mount);
34
- continue;
35
- }
36
- const ref = resolveWireRef(refs, mount.source, `mount ${mount.localId} source`);
37
- if (!ref.ok) return ref;
38
- resolved.push({ ...mount, source: ref.value });
96
+ function resolveInstanceSource(source, refs, location) {
97
+ if (typeof source === "string" && source.length > 0) return { ok: true, value: source };
98
+ if (typeof source !== "number" || !Number.isInteger(source)) {
99
+ return { ok: false, reason: `${location} must be a GUID or refs index` };
39
100
  }
40
- return { ok: true, value: resolved };
101
+ return resolveWireRef(refs, source, location);
41
102
  }
42
103
  function resolveSkinGuids(skinGuids, refs) {
43
104
  if (skinGuids === void 0) return { ok: true, value: void 0 };
@@ -58,22 +119,88 @@ function resolveSkinGuids(skinGuids, refs) {
58
119
  return { ok: true, value: resolved };
59
120
  }
60
121
  function resolveSceneWireRefs(payload, refs) {
61
- const entities = [];
62
- for (const entity of payload.entities) {
122
+ const normalized = normalizeLegacySceneAsset({ kind: "scene", entities: payload.entities });
123
+ const rawEntities = normalized.entities;
124
+ if (rawEntities === null || typeof rawEntities !== "object" || Array.isArray(rawEntities)) {
125
+ return { ok: false, reason: "entities must be a keyed object" };
126
+ }
127
+ const entities = {};
128
+ for (const [key, rawEntity] of Object.entries(rawEntities)) {
129
+ const entity = rawEntity;
130
+ if (key.length === 0 || entity === void 0 || typeof entity !== "object") {
131
+ return { ok: false, reason: `entities[${JSON.stringify(key)}] is malformed` };
132
+ }
133
+ if (entity.components === null || typeof entity.components !== "object" || Array.isArray(entity.components)) {
134
+ return { ok: false, reason: `entities.${key}.components must be an object` };
135
+ }
63
136
  const components = {};
64
- for (const [componentName, rawFields] of Object.entries(entity.components)) {
137
+ for (const [componentName, rawFields] of Object.entries(
138
+ entity.components
139
+ )) {
140
+ if (rawFields === null || typeof rawFields !== "object" || Array.isArray(rawFields)) {
141
+ return {
142
+ ok: false,
143
+ reason: `entities.${key}.components.${componentName} must be an object`
144
+ };
145
+ }
65
146
  components[componentName] = { ...rawFields };
66
147
  }
67
- entities.push({
68
- localId: entity.localId,
69
- ...entity.bindingKey === void 0 ? {} : { bindingKey: entity.bindingKey },
70
- components
71
- });
148
+ const instance = entity.instance;
149
+ let resolvedInstance;
150
+ if (instance !== void 0) {
151
+ if (instance === null || typeof instance !== "object") {
152
+ return { ok: false, reason: `entities.${key}.instance must be an object` };
153
+ }
154
+ const source = resolveInstanceSource(
155
+ instance.source,
156
+ refs,
157
+ `entities.${key}.instance.source`
158
+ );
159
+ if (!source.ok) return source;
160
+ if (instance.overrides !== void 0 && !Array.isArray(instance.overrides)) {
161
+ return { ok: false, reason: `entities.${key}.instance.overrides must be an array` };
162
+ }
163
+ let overrides;
164
+ if (instance.overrides === void 0) {
165
+ overrides = void 0;
166
+ } else {
167
+ const resolvedOverrides = [];
168
+ for (const [index, rawOverride] of instance.overrides.entries()) {
169
+ if (rawOverride === null || typeof rawOverride !== "object" || Array.isArray(rawOverride) || !Array.isArray(rawOverride.target) || rawOverride.target?.some(
170
+ (part) => typeof part !== "string" || part.length === 0
171
+ )) {
172
+ return {
173
+ ok: false,
174
+ reason: `entities.${key}.instance.overrides[${index}] is malformed`
175
+ };
176
+ }
177
+ const target = rawOverride.target;
178
+ const rawComponents = rawOverride.components;
179
+ if (rawComponents === null || typeof rawComponents !== "object" || Array.isArray(rawComponents)) {
180
+ return {
181
+ ok: false,
182
+ reason: `entities.${key}.instance.overrides[${index}].components is malformed`
183
+ };
184
+ }
185
+ resolvedOverrides.push({
186
+ target: [...target],
187
+ components: rawComponents
188
+ });
189
+ }
190
+ overrides = resolvedOverrides;
191
+ }
192
+ resolvedInstance = {
193
+ source: source.value,
194
+ ...overrides === void 0 ? {} : { overrides }
195
+ };
196
+ }
197
+ entities[key] = {
198
+ components,
199
+ ...resolvedInstance === void 0 ? {} : { instance: resolvedInstance }
200
+ };
72
201
  }
73
- const mounts = resolveMounts(payload.mounts, refs);
74
- if (!mounts.ok) return mounts;
75
202
  const skinGuids = resolveSkinGuids(
76
- payload.skinGuids,
203
+ Array.isArray(payload.skinGuids) ? payload.skinGuids : payload.skinGuids === void 0 ? void 0 : [],
77
204
  refs
78
205
  );
79
206
  if (!skinGuids.ok) return skinGuids;
@@ -81,9 +208,7 @@ function resolveSceneWireRefs(payload, refs) {
81
208
  ok: true,
82
209
  value: {
83
210
  kind: "scene",
84
- ...payload.sourceKey === void 0 ? {} : { sourceKey: payload.sourceKey },
85
211
  entities,
86
- ...mounts.value === void 0 ? {} : { mounts: mounts.value },
87
212
  ...skinGuids.value === void 0 ? {} : { skinGuids: skinGuids.value }
88
213
  }
89
214
  };
@@ -91,8 +216,8 @@ function resolveSceneWireRefs(payload, refs) {
91
216
  var sceneAssetDecoder = {
92
217
  async decode({ envelope }) {
93
218
  const payload = envelope.payload;
94
- if (payload.kind !== "scene" || !Array.isArray(payload.entities)) {
95
- return invalidScene(envelope.guid, "scene payload is missing entities");
219
+ if (payload.kind !== "scene" || payload.entities === null || typeof payload.entities !== "object") {
220
+ return invalidScene(envelope.guid, "scene payload is missing keyed entities");
96
221
  }
97
222
  const resolved = resolveSceneWireRefs(payload, envelope.refs);
98
223
  if (!resolved.ok) return invalidScene(envelope.guid, resolved.reason);
@@ -151,8 +276,8 @@ function collectSubtree(world, spawnRoot, visited) {
151
276
  if (visited.has(spawnRoot)) return visited;
152
277
  const queue = [spawnRoot];
153
278
  visited.add(spawnRoot);
154
- while (queue.length > 0) {
155
- const current = queue.shift();
279
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
280
+ const current = queue[cursor];
156
281
  const children = world.get(current, Children);
157
282
  if (!children.ok) continue;
158
283
  const entities = children.value.entities;
@@ -195,7 +320,7 @@ var SceneError = class extends Error {
195
320
 
196
321
  // src/instances/binding.ts
197
322
  import { err as err2, ok as ok2 } from "@forgeax/engine-types";
198
- function validateSceneBindings(sceneSourceKey, bindingKeys) {
323
+ function validateSceneEntityKeys(sceneSourceKey, entityKeys) {
199
324
  if (sceneSourceKey.length === 0) {
200
325
  return err2({
201
326
  code: "scene-binding-source-missing",
@@ -205,21 +330,26 @@ function validateSceneBindings(sceneSourceKey, bindingKeys) {
205
330
  });
206
331
  }
207
332
  const seen = /* @__PURE__ */ new Set();
208
- for (const bindingKey of bindingKeys) {
209
- if (bindingKey.length === 0 || seen.has(bindingKey)) {
333
+ for (const entityKey of entityKeys) {
334
+ if (entityKey.length === 0 || seen.has(entityKey)) {
210
335
  return err2({
211
336
  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 }
337
+ expected: "unique non-empty entity keys within one scene",
338
+ hint: "rename the duplicate entity key in the scene producer",
339
+ detail: { sceneSourceKey, address: entityKey }
215
340
  });
216
341
  }
217
- seen.add(bindingKey);
342
+ seen.add(entityKey);
218
343
  }
219
- return ok2([...bindingKeys]);
344
+ return ok2([...entityKeys]);
220
345
  }
221
- function sceneEntity(sceneSourceKey, bindingKey) {
222
- return { sceneSourceKey, bindingKey };
346
+ function sceneEntity(sceneSourceKey, address) {
347
+ return { sceneSourceKey, address };
348
+ }
349
+ function sceneEntityAddressKey(address) {
350
+ if (typeof address === "string") return `s:${JSON.stringify(address)}`;
351
+ if (address.length === 1) return `s:${JSON.stringify(address[0] ?? "")}`;
352
+ return `a:${JSON.stringify(address)}`;
223
353
  }
224
354
  function resolveSceneEntity(ref, instance) {
225
355
  if (ref.sceneSourceKey !== instance.sceneSourceKey) {
@@ -227,16 +357,16 @@ function resolveSceneEntity(ref, instance) {
227
357
  code: "scene-binding-wrong-instance",
228
358
  expected: `scene instance ${ref.sceneSourceKey}`,
229
359
  hint: "resolve the SceneEntityRef against its owning SceneInstance",
230
- detail: { sceneSourceKey: ref.sceneSourceKey, bindingKey: ref.bindingKey }
360
+ detail: { sceneSourceKey: ref.sceneSourceKey, address: ref.address }
231
361
  });
232
362
  }
233
- const value = instance.bindings.get(ref.bindingKey);
363
+ const value = instance.bindings.get(sceneEntityAddressKey(ref.address));
234
364
  if (value === void 0) {
235
365
  return err2({
236
366
  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 }
367
+ expected: "entity key declared by the scene producer",
368
+ hint: "declare the entity key in the scene producer before consuming it",
369
+ detail: { sceneSourceKey: ref.sceneSourceKey, address: ref.address }
240
370
  });
241
371
  }
242
372
  return ok2(value);
@@ -255,108 +385,488 @@ function sharedKind(type) {
255
385
  if (type?.startsWith("array<shared<")) return "many";
256
386
  return void 0;
257
387
  }
258
- function overrideGuids(override, resolveSchema) {
259
- const schema = resolveSchema(override.comp);
260
- const values = override.field !== void 0 ? [[override.field, override.value]] : override.value !== null && typeof override.value === "object" && !Array.isArray(override.value) ? Object.entries(override.value) : [];
261
- return values.flatMap(([field, value]) => {
262
- const kind = sharedKind(schema?.[field]);
263
- if (kind === "one" && typeof value === "string") return [{ field, guid: value }];
264
- if (kind === "many" && Array.isArray(value)) {
265
- return value.flatMap((item) => typeof item === "string" ? [{ field, guid: item }] : []);
266
- }
267
- return [];
388
+ function addRef(context, guid, sourceField, sceneEntityKey) {
389
+ const prior = context.indexByGuid.get(guid);
390
+ if (prior !== void 0) return prior;
391
+ const index = context.refs.length;
392
+ context.refs.push({
393
+ guid,
394
+ sourceField,
395
+ ...sceneEntityKey === void 0 ? {} : { sceneEntityKey }
268
396
  });
397
+ context.indexByGuid.set(guid, index);
398
+ return index;
399
+ }
400
+ function externalizeFields(componentName, source, resolveSchema, context, sceneEntityKey) {
401
+ const schema = resolveSchema(componentName);
402
+ const fields = {};
403
+ for (const [fieldName, value] of Object.entries(
404
+ migrateLegacySceneComponentFields(componentName, source)
405
+ )) {
406
+ if (value === void 0) continue;
407
+ const kind = sharedKind(schema?.[fieldName]);
408
+ if (kind === "one" && typeof value === "string") {
409
+ fields[fieldName] = addRef(context, value, { componentName, fieldName }, sceneEntityKey);
410
+ } else if (kind === "many" && Array.isArray(value)) {
411
+ fields[fieldName] = value.map(
412
+ (item, arrayIndex) => typeof item === "string" ? addRef(context, item, { componentName, fieldName, arrayIndex }, sceneEntityKey) : item
413
+ );
414
+ } else {
415
+ fields[fieldName] = value;
416
+ }
417
+ }
418
+ return fields;
269
419
  }
270
- function externalizeSceneAsset(scene, resolveSchema) {
271
- const refs = [];
272
- const indexByGuid = /* @__PURE__ */ new Map();
273
- const addRef = (guid, sourceField, sceneEntityId) => {
274
- const prior = indexByGuid.get(guid);
275
- if (prior !== void 0) return prior;
276
- const index = refs.length;
277
- refs.push({ guid, sourceField, ...sceneEntityId === void 0 ? {} : { sceneEntityId } });
278
- indexByGuid.set(guid, index);
279
- return index;
420
+ function externalizeOverride(override, resolveSchema, context, sceneEntityKey) {
421
+ const components = {};
422
+ for (const [componentName, rawFields] of Object.entries(override.components)) {
423
+ components[componentName] = externalizeFields(
424
+ componentName,
425
+ { ...rawFields },
426
+ resolveSchema,
427
+ context,
428
+ sceneEntityKey
429
+ );
430
+ }
431
+ return {
432
+ target: [...override.target],
433
+ components
280
434
  };
281
- const entities = scene.entities.map((entity) => {
435
+ }
436
+ function externalizeSceneAsset(scene, resolveSchema) {
437
+ const normalized = normalizeLegacySceneAsset(scene);
438
+ const context = { refs: [], indexByGuid: /* @__PURE__ */ new Map() };
439
+ const entities = {};
440
+ for (const [key, entity] of Object.entries(normalized.entities)) {
282
441
  const components = {};
283
- for (const componentName of Object.keys(entity.components)) {
284
- const schema = resolveSchema(componentName);
285
- const source = entity.components[componentName];
442
+ for (const [componentName, raw] of Object.entries(entity.components)) {
443
+ const source = raw;
286
444
  if (source === void 0) continue;
287
- const fields = {};
288
- for (const fieldName of Object.keys(source)) {
289
- const value = source[fieldName];
290
- if (value === void 0) continue;
291
- const kind = sharedKind(schema?.[fieldName]);
292
- if (kind === "one" && typeof value === "string") {
293
- fields[fieldName] = addRef(value, { componentName, fieldName }, entity.localId);
294
- } else if (kind === "many" && Array.isArray(value)) {
295
- fields[fieldName] = value.map(
296
- (item, arrayIndex) => typeof item === "string" ? addRef(item, { componentName, fieldName, arrayIndex }, entity.localId) : item
297
- );
298
- } else {
299
- fields[fieldName] = value;
445
+ components[componentName] = externalizeFields(
446
+ componentName,
447
+ source,
448
+ resolveSchema,
449
+ context,
450
+ key
451
+ );
452
+ }
453
+ const instance = entity.instance;
454
+ entities[key] = {
455
+ components,
456
+ ...instance === void 0 ? {} : {
457
+ instance: {
458
+ source: addRef(
459
+ context,
460
+ instance.source,
461
+ { componentName: "SceneInstance", fieldName: "source" },
462
+ key
463
+ ),
464
+ ...instance.overrides === void 0 ? {} : {
465
+ overrides: instance.overrides.map(
466
+ (override) => externalizeOverride(override, resolveSchema, context, key)
467
+ )
468
+ }
300
469
  }
301
470
  }
302
- if (Object.keys(fields).length > 0 || Object.keys(schema ?? {}).length === 0) {
303
- components[componentName] = fields;
304
- }
305
- }
306
- return {
307
- localId: entity.localId,
308
- ...entity.bindingKey === void 0 ? {} : { bindingKey: entity.bindingKey },
309
- components
310
471
  };
311
- });
312
- const mounts = scene.mounts?.map((mount) => {
313
- const source = typeof mount.source === "string" ? addRef(
314
- mount.source,
315
- { componentName: "SceneInstance", fieldName: "source" },
316
- mount.localId
317
- ) : mount.source;
318
- for (const { field, guid } of (mount.overrides ?? []).flatMap(
319
- (override) => overrideGuids(override, resolveSchema)
320
- )) {
321
- addRef(guid, { componentName: "SceneInstance", fieldName: `overrides.${field}` });
322
- }
323
- return {
324
- localId: mount.localId,
325
- source,
326
- memberFirst: mount.memberFirst,
327
- memberCount: mount.memberCount,
328
- ...mount.parent === void 0 ? {} : { parent: mount.parent },
329
- ...mount.publicationFence === void 0 ? {} : { publicationFence: mount.publicationFence },
330
- ...mount.overrides === void 0 ? {} : { overrides: mount.overrides.map((item) => ({ ...item })) }
331
- };
332
- });
333
- for (const [arrayIndex, guid] of (scene.skinGuids ?? []).entries()) {
472
+ }
473
+ for (const [arrayIndex, guid] of (normalized.skinGuids ?? []).entries()) {
334
474
  if (typeof guid !== "string") return err3({ field: "skinGuids", value: guid });
335
- addRef(guid, { componentName: "<scene>", fieldName: "skinGuids", arrayIndex });
475
+ addRef(context, guid, { componentName: "<scene>", fieldName: "skinGuids", arrayIndex });
336
476
  }
337
477
  return ok3({
338
478
  payload: {
339
479
  kind: "scene",
340
- ...scene.sourceKey === void 0 ? {} : { sourceKey: scene.sourceKey },
341
480
  entities,
342
- ...mounts === void 0 || mounts.length === 0 ? {} : { mounts },
343
- ...scene.skinGuids === void 0 ? {} : { skinGuids: scene.skinGuids.map((guid) => indexByGuid.get(guid)) }
481
+ ...normalized.skinGuids === void 0 ? {} : {
482
+ skinGuids: normalized.skinGuids.map((guid) => context.indexByGuid.get(guid))
483
+ }
344
484
  },
345
- refs
485
+ refs: context.refs
486
+ });
487
+ }
488
+
489
+ // src/instances/keyed.ts
490
+ import { classifyEntityField, remapEntityFieldValue } from "@forgeax/engine-ecs/externalization";
491
+ import { componentSchema } from "@forgeax/engine-ecs/internal";
492
+ import {
493
+ err as err4,
494
+ ok as ok4,
495
+ PACK_ERROR_HINTS
496
+ } from "@forgeax/engine-types";
497
+ function fail(reason, detail = {}) {
498
+ return err4({
499
+ code: "asset-package-invalid",
500
+ expected: "a keyed SceneAsset with valid entity and instance addresses",
501
+ hint: "repair the SceneAsset source and recook the asset",
502
+ detail: { reason, ...detail }
503
+ });
504
+ }
505
+ function keyList(entities) {
506
+ return Object.keys(entities).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
507
+ }
508
+ function addressParts(value) {
509
+ if (typeof value === "string" && value.length > 0) return [value];
510
+ if (Number.isSafeInteger(value)) return [String(value)];
511
+ if (!Array.isArray(value) || value.length === 0) return void 0;
512
+ if (!value.every((part) => typeof part === "string" && part.length > 0)) return void 0;
513
+ return value;
514
+ }
515
+ function fieldRemap(world, componentName, fields, resolveAddress, entityKey) {
516
+ const token = world.components.resolve(componentName);
517
+ if (token === void 0) return fail("unknown component", { component: componentName });
518
+ const schema = componentSchema(token);
519
+ const out = {};
520
+ for (const [fieldName, value] of Object.entries(
521
+ migrateLegacySceneComponentFields(componentName, fields)
522
+ )) {
523
+ const fieldType = schema[fieldName];
524
+ if (fieldType === void 0) {
525
+ return fail("unknown component field", {
526
+ component: componentName,
527
+ field: fieldName,
528
+ ...entityKey === void 0 ? {} : { entity: entityKey }
529
+ });
530
+ }
531
+ const kind = classifyEntityField(token, fieldName);
532
+ if (kind === null) {
533
+ out[fieldName] = value;
534
+ continue;
535
+ }
536
+ const remap = (address) => resolveAddress(address, `${componentName}.${fieldName}`) ?? address;
537
+ if (kind.isArray) {
538
+ if (!Array.isArray(value))
539
+ return fail("array entity field is not an array", {
540
+ component: componentName,
541
+ field: fieldName
542
+ });
543
+ const numeric = [];
544
+ for (const item of value) {
545
+ const parts2 = addressParts(item);
546
+ if (parts2 === void 0)
547
+ return fail("invalid entity address", {
548
+ component: componentName,
549
+ field: fieldName,
550
+ address: item
551
+ });
552
+ const slot2 = resolveAddress(parts2, `${componentName}.${fieldName}`);
553
+ if (slot2 === void 0)
554
+ return fail("missing entity address target", {
555
+ component: componentName,
556
+ field: fieldName,
557
+ address: parts2
558
+ });
559
+ numeric.push(slot2);
560
+ }
561
+ out[fieldName] = remapEntityFieldValue(numeric, kind, remap);
562
+ continue;
563
+ }
564
+ if (value === null) {
565
+ out[fieldName] = null;
566
+ continue;
567
+ }
568
+ const parts = addressParts(value);
569
+ if (parts === void 0)
570
+ return fail("invalid entity address", {
571
+ component: componentName,
572
+ field: fieldName,
573
+ address: value
574
+ });
575
+ const slot = resolveAddress(parts, `${componentName}.${fieldName}`);
576
+ if (slot === void 0)
577
+ return fail("missing entity address target", {
578
+ component: componentName,
579
+ field: fieldName,
580
+ address: parts
581
+ });
582
+ out[fieldName] = remapEntityFieldValue(slot, kind, remap);
583
+ }
584
+ return ok4(out);
585
+ }
586
+ function compileKeyedSceneAsset(world, handle, asset, context) {
587
+ asset = normalizeLegacySceneAsset(asset);
588
+ if (asset.kind !== "scene" || asset.entities === null || typeof asset.entities !== "object" || Array.isArray(asset.entities)) {
589
+ return fail("entities must be a keyed object");
590
+ }
591
+ const currentRaw = Number(handle);
592
+ const activeStack = context.stack.has(currentRaw) ? context.stack : /* @__PURE__ */ new Set([...context.stack, currentRaw]);
593
+ const keys = keyList(asset.entities);
594
+ if (keys.some((key) => key.length === 0)) return fail("entity keys must be non-empty");
595
+ const ownKeys = keys.filter((key) => asset.entities[key]?.instance === void 0);
596
+ const instanceKeys = keys.filter((key) => asset.entities[key]?.instance !== void 0);
597
+ const ownSlotByKey = /* @__PURE__ */ new Map();
598
+ const instanceSlotByKey = /* @__PURE__ */ new Map();
599
+ const keyByLocalId = /* @__PURE__ */ new Map();
600
+ for (let index = 0; index < ownKeys.length; index += 1) {
601
+ const key = ownKeys[index];
602
+ ownSlotByKey.set(key, index);
603
+ keyByLocalId.set(index, key);
604
+ }
605
+ for (let index = 0; index < instanceKeys.length; index += 1) {
606
+ const key = instanceKeys[index];
607
+ const slot = ownKeys.length + index;
608
+ instanceSlotByKey.set(key, slot);
609
+ keyByLocalId.set(slot, key);
610
+ }
611
+ const childCompiled = /* @__PURE__ */ new Map();
612
+ for (const key of instanceKeys) {
613
+ const declaration = asset.entities[key]?.instance;
614
+ if (declaration === void 0 || typeof declaration.source !== "string" || declaration.source.length === 0) {
615
+ return fail("instance source must be a non-empty GUID", { entity: key });
616
+ }
617
+ const childHandle = context.resolveSource(declaration.source, handle);
618
+ if (!childHandle.ok) return childHandle;
619
+ const childRaw = Number(childHandle.value);
620
+ if (activeStack.has(childRaw)) {
621
+ return err4({
622
+ code: "pack-cyclic-reference",
623
+ expected: "acyclic SceneAsset instance graph",
624
+ hint: PACK_ERROR_HINTS["pack-cyclic-reference"],
625
+ detail: {
626
+ code: "pack-cyclic-reference",
627
+ kind: "mount-asset",
628
+ cycle: [...activeStack, childRaw].map(String)
629
+ }
630
+ });
631
+ }
632
+ const childAsset = context.resolveAsset(childHandle.value);
633
+ if (!childAsset.ok) return childAsset;
634
+ const childContext = {
635
+ ...context,
636
+ stack: activeStack
637
+ };
638
+ const compiled = compileKeyedSceneAsset(
639
+ world,
640
+ childHandle.value,
641
+ childAsset.value,
642
+ childContext
643
+ );
644
+ if (!compiled.ok) return compiled;
645
+ childCompiled.set(key, { handle: childHandle.value, compiled: compiled.value });
646
+ }
647
+ const mountKeyByLocalId = /* @__PURE__ */ new Map();
648
+ const mounts = [];
649
+ let nextMemberFirst = ownKeys.length + instanceKeys.length;
650
+ for (let index = 0; index < instanceKeys.length; index += 1) {
651
+ const key = instanceKeys[index];
652
+ const slot = instanceSlotByKey.get(key);
653
+ const child = childCompiled.get(key);
654
+ const node = asset.entities[key];
655
+ mountKeyByLocalId.set(slot, key);
656
+ mounts.push({
657
+ localId: slot,
658
+ source: Number(child.handle),
659
+ memberFirst: nextMemberFirst,
660
+ memberCount: child.compiled.asset.entities.length + (child.compiled.asset.mounts?.length ?? 0) + (child.compiled.asset.mounts ?? []).reduce((sum, mount) => sum + mount.memberCount, 0),
661
+ ...Object.keys(node.components).length > 0 ? { components: node.components } : {}
662
+ });
663
+ nextMemberFirst += mounts[index]?.memberCount ?? 0;
664
+ }
665
+ const mountByKey = /* @__PURE__ */ new Map();
666
+ for (const mount of mounts)
667
+ mountByKey.set(mountKeyByLocalId.get(Number(mount.localId)), mount);
668
+ const resolveInChild = (childResult, value, _field) => {
669
+ const parts = addressParts(value);
670
+ if (parts === void 0) return void 0;
671
+ return childResult.resolveAddress(parts);
672
+ };
673
+ const resolveAddress = (value, field) => {
674
+ const parts = addressParts(value);
675
+ if (parts === void 0) return void 0;
676
+ const first = parts[0];
677
+ if (first === void 0) return void 0;
678
+ const own = ownSlotByKey.get(first) ?? instanceSlotByKey.get(first);
679
+ if (own !== void 0 && parts.length === 1) return own;
680
+ const mount = mountByKey.get(first);
681
+ if (mount === void 0) return void 0;
682
+ const child = childCompiled.get(first);
683
+ if (child === void 0) return void 0;
684
+ const childSlot = resolveInChild(child.compiled, parts.slice(1), field);
685
+ return childSlot === void 0 ? void 0 : mount.memberFirst + childSlot;
686
+ };
687
+ for (const key of instanceKeys) {
688
+ const node = asset.entities[key];
689
+ const mount = mountByKey.get(key);
690
+ const convertedFields = Object.fromEntries(
691
+ Object.entries(node.components).map(([componentName, raw]) => [
692
+ componentName,
693
+ fieldRemap(
694
+ world,
695
+ componentName,
696
+ { ...raw },
697
+ resolveAddress,
698
+ key
699
+ )
700
+ ])
701
+ );
702
+ const bad = Object.values(convertedFields).find((result) => !result.ok);
703
+ if (bad !== void 0 && !bad.ok) return bad;
704
+ const components = {};
705
+ for (const [componentName, result] of Object.entries(convertedFields)) {
706
+ if (!result.ok) return result;
707
+ components[componentName] = result.value;
708
+ }
709
+ const index = mounts.findIndex((item) => item.localId === mount.localId);
710
+ if (index >= 0) {
711
+ const childOf = components.ChildOf?.parent;
712
+ if (typeof childOf === "number") {
713
+ const { ChildOf: _ignored, ...mountComponents } = components;
714
+ void _ignored;
715
+ mounts[index] = { ...mount, components: mountComponents, parent: childOf };
716
+ } else {
717
+ mounts[index] = { ...mount, components };
718
+ }
719
+ }
720
+ }
721
+ const converted = [];
722
+ for (const key of ownKeys) {
723
+ const node = asset.entities[key];
724
+ const components = {};
725
+ for (const [componentName, raw] of Object.entries(node.components)) {
726
+ const convertedFields = fieldRemap(
727
+ world,
728
+ componentName,
729
+ { ...raw },
730
+ resolveAddress,
731
+ key
732
+ );
733
+ if (!convertedFields.ok) return convertedFields;
734
+ components[componentName] = convertedFields.value;
735
+ }
736
+ converted.push({ localId: ownSlotByKey.get(key), components });
737
+ }
738
+ const childHasComponent = (result, target, componentName) => {
739
+ const own = result.asset.entities.find((entity) => Number(entity.localId) === target);
740
+ if (own !== void 0 && own.components[componentName] !== void 0) return true;
741
+ const mount = result.asset.mounts?.find((entry) => Number(entry.localId) === target);
742
+ return mount?.components?.[componentName] !== void 0;
743
+ };
744
+ for (const key of instanceKeys) {
745
+ const node = asset.entities[key];
746
+ const declaration = node.instance;
747
+ const mount = mountByKey.get(key);
748
+ const child = childCompiled.get(key);
749
+ const childSlot = (target) => resolveInChild(child.compiled, target, `${key}.instance`);
750
+ const overrides = [];
751
+ for (const override of declaration.overrides ?? []) {
752
+ const target = childSlot(override.target);
753
+ if (target === void 0)
754
+ return fail("instance override target does not exist", {
755
+ entity: key,
756
+ target: override.target
757
+ });
758
+ for (const [componentName, fields] of Object.entries(override.components)) {
759
+ const convertedFields = fieldRemap(
760
+ world,
761
+ componentName,
762
+ { ...fields },
763
+ resolveAddress,
764
+ `${key}.instance.${override.target.join(".")}`
765
+ );
766
+ if (!convertedFields.ok) return convertedFields;
767
+ if (!childHasComponent(child.compiled, target, componentName)) {
768
+ overrides.push({
769
+ localId: mount.memberFirst + target,
770
+ comp: componentName,
771
+ value: convertedFields.value
772
+ });
773
+ } else {
774
+ overrides.push(
775
+ ...Object.entries(convertedFields.value).map(([field, value]) => ({
776
+ localId: mount.memberFirst + target,
777
+ comp: componentName,
778
+ field,
779
+ value
780
+ }))
781
+ );
782
+ }
783
+ }
784
+ }
785
+ if (overrides.length > 0) {
786
+ const index = mounts.findIndex((item) => item.localId === mount.localId);
787
+ const existing = mounts[index];
788
+ if (index >= 0 && existing !== void 0) mounts[index] = { ...existing, overrides };
789
+ }
790
+ }
791
+ const rootLocalIds = [
792
+ ...convertedRootLocalIds(converted),
793
+ ...mounts.filter((mount) => mount.parent === void 0).map((mount) => Number(mount.localId))
794
+ ];
795
+ const hierarchyParentByLocalId = /* @__PURE__ */ new Map();
796
+ for (const node of converted) {
797
+ const parent = node.components.ChildOf?.parent;
798
+ if (typeof parent === "number" && parent >= 0) {
799
+ hierarchyParentByLocalId.set(Number(node.localId), parent);
800
+ }
801
+ }
802
+ for (const mount of mounts) {
803
+ if (mount.parent !== void 0) {
804
+ hierarchyParentByLocalId.set(Number(mount.localId), mount.parent);
805
+ }
806
+ const key = mountKeyByLocalId.get(Number(mount.localId));
807
+ const child = key === void 0 ? void 0 : childCompiled.get(key);
808
+ if (child !== void 0) {
809
+ for (const [childLocalId, childParent] of child.compiled.hierarchyParentByLocalId) {
810
+ hierarchyParentByLocalId.set(
811
+ Number(mount.memberFirst) + childLocalId,
812
+ Number(mount.memberFirst) + childParent
813
+ );
814
+ }
815
+ for (const childRoot of child.compiled.rootLocalIds) {
816
+ hierarchyParentByLocalId.set(Number(mount.memberFirst) + childRoot, Number(mount.localId));
817
+ }
818
+ }
819
+ for (const override of mount.overrides ?? []) {
820
+ if (override.comp !== "ChildOf") continue;
821
+ if (override.field === "parent" && typeof override.value === "number") {
822
+ hierarchyParentByLocalId.set(Number(override.localId), override.value);
823
+ } else if (override.field === void 0 && typeof override.value === "object" && override.value !== null) {
824
+ const parentValue = override.value.parent;
825
+ if (typeof parentValue === "number") {
826
+ hierarchyParentByLocalId.set(Number(override.localId), parentValue);
827
+ }
828
+ }
829
+ }
830
+ }
831
+ for (const start of hierarchyParentByLocalId.keys()) {
832
+ const seen = /* @__PURE__ */ new Set();
833
+ let current = start;
834
+ while (current !== void 0 && hierarchyParentByLocalId.has(current)) {
835
+ if (seen.has(current))
836
+ return fail("hierarchy cycle", { entity: keyByLocalId.get(start), address: [...seen] });
837
+ seen.add(current);
838
+ current = hierarchyParentByLocalId.get(current);
839
+ }
840
+ }
841
+ return ok4({
842
+ asset: {
843
+ kind: "scene",
844
+ entities: converted,
845
+ ...mounts.length > 0 ? { mounts } : {},
846
+ ...asset.skinGuids === void 0 ? {} : { skinGuids: asset.skinGuids }
847
+ },
848
+ keyByLocalId,
849
+ mountKeyByLocalId,
850
+ rootLocalIds,
851
+ hierarchyParentByLocalId,
852
+ resolveAddress
346
853
  });
347
854
  }
855
+ function convertedRootLocalIds(nodes) {
856
+ return nodes.filter((node) => node.components.ChildOf === void 0).map((node) => Number(node.localId));
857
+ }
348
858
 
349
859
  // src/instances/scene-instances.ts
350
860
  import {
351
861
  ENTITY_NULL_RAW
352
862
  } from "@forgeax/engine-ecs";
353
- import { classifyEntityField, remapEntityFieldValue } from "@forgeax/engine-ecs/externalization";
354
- import { componentSchema } from "@forgeax/engine-ecs/internal";
863
+ import { classifyEntityField as classifyEntityField2, remapEntityFieldValue as remapEntityFieldValue2 } from "@forgeax/engine-ecs/externalization";
864
+ import { componentSchema as componentSchema2 } from "@forgeax/engine-ecs/internal";
355
865
  import { fillComponentDefaults, StaleEntityError } from "@forgeax/engine-ecs/projection";
356
866
  import {
357
- err as err4,
358
- ok as ok4,
359
- PACK_ERROR_HINTS,
867
+ err as err5,
868
+ ok as ok5,
869
+ PACK_ERROR_HINTS as PACK_ERROR_HINTS2,
360
870
  toUnique,
361
871
  unwrapHandle
362
872
  } from "@forgeax/engine-types";
@@ -388,18 +898,50 @@ function primitiveJsType(fieldType) {
388
898
  // src/instances/scene-instances.ts
389
899
  var entityIndex = (entity) => entity & 16777215;
390
900
  var entityGeneration = (entity) => entity >>> 24 & 255;
901
+ function collectSceneEntityBindings(world, root, prefix, bindings, visited = /* @__PURE__ */ new Set()) {
902
+ const rootRaw = root;
903
+ if (visited.has(rootRaw)) return;
904
+ visited.add(rootRaw);
905
+ const state = worldResolveSceneInstanceStatePayload(world, root);
906
+ if (!state.ok) return;
907
+ const sceneInstance = world.components.resolve("SceneInstance");
908
+ if (sceneInstance === void 0) return;
909
+ const component = world.get(root, sceneInstance);
910
+ if (!component.ok) return;
911
+ const mapping = component.value.mapping;
912
+ for (const [slot, key] of state.value.keyByLocalId) {
913
+ const raw = mapping[slot];
914
+ if (raw === void 0 || raw === ENTITY_NULL_RAW) continue;
915
+ const address = prefix.length === 0 ? key : [...prefix, key];
916
+ bindings.set(sceneEntityAddressKey(address), raw);
917
+ }
918
+ for (const childRoot of state.value.mountRoots) {
919
+ const childState = worldResolveSceneInstanceStatePayload(world, childRoot);
920
+ const childKey = childState.ok ? childState.value.instanceKey : void 0;
921
+ if (childKey === void 0) continue;
922
+ collectSceneEntityBindings(world, childRoot, [...prefix, childKey], bindings, visited);
923
+ }
924
+ }
391
925
  function worldSetSceneAssetResolver(world, resolver) {
392
926
  sceneWorldState(world).resolver = resolver;
393
927
  }
394
928
  function worldGetSceneAssetResolver(world) {
395
929
  return sceneWorldState(world).resolver;
396
930
  }
397
- function worldInstantiateScene(world, handle, parent) {
931
+ function worldInstantiateScene(world, handle, parent, sceneSourceKey) {
398
932
  const stack = /* @__PURE__ */ new Set();
399
933
  const diagnostics = [];
400
- const r = worldInstantiateSceneRec(world, handle, parent, stack, diagnostics);
934
+ const r = worldInstantiateSceneRec(
935
+ world,
936
+ handle,
937
+ parent,
938
+ stack,
939
+ diagnostics,
940
+ void 0,
941
+ sceneSourceKey
942
+ );
401
943
  if (!r.ok) return r;
402
- return ok4({ root: r.value, diagnostics });
944
+ return ok5({ root: r.value, diagnostics });
403
945
  }
404
946
  function worldInstantiateScenePayload(world, asset, parent) {
405
947
  const handle = world.allocSharedRef("SceneAsset", asset);
@@ -423,9 +965,9 @@ function worldInstantiateSceneFlat(world, handle) {
423
965
  stack.delete(handleKey);
424
966
  }
425
967
  if (!r.ok) return r;
426
- return ok4({ ...r.value, diagnostics });
968
+ return ok5({ ...r.value, diagnostics });
427
969
  }
428
- function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
970
+ function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics, instanceKey, sceneSourceKey) {
429
971
  const handleKey = unwrapHandle(handle);
430
972
  if (stack.has(handleKey)) {
431
973
  const cycleArr = [];
@@ -436,10 +978,10 @@ function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
436
978
  kind: "mount-asset",
437
979
  cycle: cycleArr
438
980
  };
439
- return err4({
981
+ return err5({
440
982
  code: "pack-cyclic-reference",
441
983
  expected: "acyclic SceneAsset mount graph",
442
- hint: PACK_ERROR_HINTS["pack-cyclic-reference"],
984
+ hint: PACK_ERROR_HINTS2["pack-cyclic-reference"],
443
985
  detail
444
986
  });
445
987
  }
@@ -448,7 +990,16 @@ function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
448
990
  const asset = resolved.value;
449
991
  stack.add(handleKey);
450
992
  try {
451
- return worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnostics);
993
+ return worldInstantiateSceneAsset(
994
+ world,
995
+ handle,
996
+ asset,
997
+ parent,
998
+ stack,
999
+ diagnostics,
1000
+ instanceKey,
1001
+ sceneSourceKey
1002
+ );
452
1003
  } finally {
453
1004
  stack.delete(handleKey);
454
1005
  }
@@ -456,27 +1007,18 @@ function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
456
1007
  function worldResolveSceneAsset(world, handle) {
457
1008
  const r = world.sharedRefs.resolve(handle);
458
1009
  if (!r.ok) {
459
- return err4(r.error);
1010
+ return err5(r.error);
460
1011
  }
461
- return ok4(r.value);
1012
+ return ok5(r.value);
462
1013
  }
463
- function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
1014
+ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics, mountKeys) {
464
1015
  const sceneInstanceToken = world.components.resolve("SceneInstance");
465
1016
  if (sceneInstanceToken === void 0) {
466
- return err4(new ComponentNotDefinedError("SceneInstance"));
1017
+ return err5(new ComponentNotDefinedError("SceneInstance"));
467
1018
  }
468
1019
  const childOfToken = world.components.resolve("ChildOf");
469
1020
  const ownEntities = asset.entities;
470
1021
  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
- }
480
1022
  const memberSum = ownMounts.reduce((s, m) => s + m.memberCount, 0);
481
1023
  const countBaseline = ownEntities.length + ownMounts.length + memberSum;
482
1024
  let maxLocalId = ownEntities.reduce((m, e) => Math.max(m, e.localId), -1);
@@ -517,10 +1059,10 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
517
1059
  }
518
1060
  if (overlapLids.size > 0) {
519
1061
  const overlapping = Array.from(overlapLids).sort((a, b) => a - b);
520
- return err4({
1062
+ return err5({
521
1063
  code: "pack-mount-localid-overlap",
522
1064
  expected: "each LocalEntityId claimed by exactly one entity or mount slot",
523
- hint: PACK_ERROR_HINTS["pack-mount-localid-overlap"],
1065
+ hint: PACK_ERROR_HINTS2["pack-mount-localid-overlap"],
524
1066
  detail: {
525
1067
  code: "pack-mount-localid-overlap",
526
1068
  overlapping,
@@ -550,17 +1092,29 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
550
1092
  const childHandleRes = worldResolveMountSource(world, mount.source, handle);
551
1093
  if (!childHandleRes.ok) return childHandleRes;
552
1094
  const childHandle = childHandleRes.value;
553
- const childRes = worldInstantiateSceneRec(world, childHandle, mountEntity, stack, diagnostics);
1095
+ const childRes = worldInstantiateSceneRec(
1096
+ world,
1097
+ childHandle,
1098
+ mountEntity,
1099
+ stack,
1100
+ diagnostics,
1101
+ mountKeys?.get(mountLid)
1102
+ );
554
1103
  if (!childRes.ok) return childRes;
555
1104
  const childInstRes = world.get(childRes.value, sceneInstanceToken);
556
1105
  if (!childInstRes.ok) return childInstRes;
557
1106
  const childMapping = childInstRes.value.mapping;
558
- mountInstances.push({ mount, root: childRes.value, mapping: childMapping });
1107
+ mountInstances.push({
1108
+ mount,
1109
+ root: childRes.value,
1110
+ mapping: childMapping,
1111
+ ...mountKeys?.get(mountLid) === void 0 ? {} : { key: mountKeys.get(mountLid) }
1112
+ });
559
1113
  if (childMapping.length !== mount.memberCount) {
560
- return err4({
1114
+ return err5({
561
1115
  code: "pack-mount-count-mismatch",
562
1116
  expected: "mount.memberCount === child SceneAsset totalSlots",
563
- hint: PACK_ERROR_HINTS["pack-mount-count-mismatch"],
1117
+ hint: PACK_ERROR_HINTS2["pack-mount-count-mismatch"],
564
1118
  detail: {
565
1119
  code: "pack-mount-count-mismatch",
566
1120
  mountLocalId: mountLid,
@@ -628,7 +1182,7 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
628
1182
  }
629
1183
  }
630
1184
  }
631
- return ok4({
1185
+ return ok5({
632
1186
  mapping,
633
1187
  entityToLocalId,
634
1188
  rootEntities,
@@ -638,17 +1192,31 @@ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
638
1192
  totalSlots
639
1193
  });
640
1194
  }
641
- function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnostics) {
1195
+ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnostics, instanceKey, sceneSourceKey) {
642
1196
  const sceneInstanceToken = world.components.resolve("SceneInstance");
643
1197
  if (sceneInstanceToken === void 0) {
644
- return err4(new ComponentNotDefinedError("SceneInstance"));
1198
+ return err5(new ComponentNotDefinedError("SceneInstance"));
645
1199
  }
646
1200
  const childOfToken = world.components.resolve("ChildOf");
647
- const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);
1201
+ const compiled = compileKeyedSceneAsset(world, handle, asset, {
1202
+ resolveSource: (source, parentHandle) => worldResolveMountSource(world, source, parentHandle),
1203
+ resolveAsset: (childHandle) => worldResolveSceneAsset(world, childHandle),
1204
+ stack
1205
+ });
1206
+ if (!compiled.ok) return err5(compiled.error);
1207
+ const compiledAsset = compiled.value.asset;
1208
+ const membersRes = worldSpawnSceneMembers(
1209
+ world,
1210
+ handle,
1211
+ compiledAsset,
1212
+ stack,
1213
+ diagnostics,
1214
+ compiled.value.mountKeyByLocalId
1215
+ );
648
1216
  if (!membersRes.ok) return membersRes;
649
1217
  const { mapping, entityToLocalId, rootEntities, mountEntitiesNeedingRootParent, totalSlots } = membersRes.value;
650
1218
  const { mountInstances } = membersRes.value;
651
- const ownMounts = asset.mounts ?? [];
1219
+ const ownMounts = compiledAsset.mounts ?? [];
652
1220
  let stateRef;
653
1221
  stateRef = world.allocUniqueRef("SceneInstanceState", null, () => {
654
1222
  sceneWorldState(world).statePayloads.delete(Number(stateRef));
@@ -691,7 +1259,11 @@ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnos
691
1259
  const memberEntityRaw = mapping[lid];
692
1260
  if (memberEntityRaw !== void 0 && memberEntityRaw !== ENTITY_NULL_RAW) {
693
1261
  const memberEntity = memberEntityRaw;
694
- const applyRes = worldApplyMountOverride(world, memberEntity, ov);
1262
+ const applyRes = worldApplyMountOverride(
1263
+ world,
1264
+ memberEntity,
1265
+ worldRemapMountOverride(world, ov, mapping)
1266
+ );
695
1267
  if (!applyRes.ok) {
696
1268
  return applyRes;
697
1269
  }
@@ -700,16 +1272,11 @@ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnos
700
1272
  }
701
1273
  const detached = /* @__PURE__ */ new Set();
702
1274
  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
- }
710
1275
  const state = {
711
1276
  source: handle,
712
- sceneSourceKey: asset.sourceKey,
1277
+ ...sceneSourceKey === void 0 ? {} : { sceneSourceKey },
1278
+ keyByLocalId: new Map(compiled.value.keyByLocalId),
1279
+ ...instanceKey === void 0 ? {} : { instanceKey },
713
1280
  bindings,
714
1281
  entityToLocalId,
715
1282
  detachedLocalIds: detached,
@@ -722,6 +1289,7 @@ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnos
722
1289
  mountTimeOverrides: ownMounts.flatMap((m) => m.overrides ?? [])
723
1290
  };
724
1291
  worldSetUniqueRefPayload(world, stateRef, state);
1292
+ collectSceneEntityBindings(world, rootEntity, [], bindings);
725
1293
  if (childOfToken !== void 0) {
726
1294
  for (const rootE of rootEntities) {
727
1295
  const has = world.get(rootE, childOfToken);
@@ -751,10 +1319,23 @@ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnos
751
1319
  if (!r.ok) return r;
752
1320
  }
753
1321
  }
754
- return ok4(rootEntity);
1322
+ return ok5(rootEntity);
755
1323
  }
756
1324
  function worldInstantiateSceneAssetFlat(world, handle, asset, stack, diagnostics) {
757
- const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);
1325
+ const compiled = compileKeyedSceneAsset(world, handle, asset, {
1326
+ resolveSource: (source, parentHandle) => worldResolveMountSource(world, source, parentHandle),
1327
+ resolveAsset: (childHandle) => worldResolveSceneAsset(world, childHandle),
1328
+ stack
1329
+ });
1330
+ if (!compiled.ok) return err5(compiled.error);
1331
+ const membersRes = worldSpawnSceneMembers(
1332
+ world,
1333
+ handle,
1334
+ compiled.value.asset,
1335
+ stack,
1336
+ diagnostics,
1337
+ compiled.value.mountKeyByLocalId
1338
+ );
758
1339
  if (!membersRes.ok) return membersRes;
759
1340
  const { rootEntities, mountEntitiesNeedingRootParent, mountEntities, mountInstances } = membersRes.value;
760
1341
  const childOfToken = world.components.resolve("ChildOf");
@@ -766,7 +1347,11 @@ function worldInstantiateSceneAssetFlat(world, handle, asset, stack, diagnostics
766
1347
  const memberEntityRaw = childMapping[childLocalId];
767
1348
  if (memberEntityRaw === void 0 || memberEntityRaw === ENTITY_NULL_RAW) continue;
768
1349
  const memberEntity = memberEntityRaw;
769
- const applyRes = worldApplyMountOverride(world, memberEntity, ov);
1350
+ const applyRes = worldApplyMountOverride(
1351
+ world,
1352
+ memberEntity,
1353
+ worldRemapMountOverride(world, ov, childMapping)
1354
+ );
770
1355
  if (!applyRes.ok) {
771
1356
  return applyRes;
772
1357
  }
@@ -790,34 +1375,43 @@ function worldInstantiateSceneAssetFlat(world, handle, asset, stack, diagnostics
790
1375
  }
791
1376
  }
792
1377
  }
793
- return ok4({ roots: [...rootEntities, ...mountEntitiesNeedingRootParent], mountEntities });
1378
+ return ok5({ roots: [...rootEntities, ...mountEntitiesNeedingRootParent], mountEntities });
794
1379
  }
795
- function worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics) {
1380
+ function worldBuildSceneEntityComponentDatas(world, node, mapping, _diagnostics) {
796
1381
  const out = [];
797
1382
  const nodeLocalId = node.localId;
798
1383
  for (const compName of Object.keys(node.components)) {
799
1384
  const token = world.components.resolve(compName);
800
1385
  if (token === void 0) {
801
- return err4(new ComponentNotDefinedError(compName));
1386
+ return err5(new ComponentNotDefinedError(compName));
802
1387
  }
803
1388
  const raw = node.components[compName] ?? {};
804
- const schema = componentSchema(token);
1389
+ const schema = componentSchema2(token);
805
1390
  const remappedRaw = {};
806
1391
  for (const fieldName of Object.keys(raw)) {
807
1392
  const fieldType = schema[fieldName];
808
1393
  if (fieldType === void 0) {
809
- diagnostics.push({ component: compName, field: fieldName, localId: nodeLocalId });
810
- continue;
1394
+ return err5({
1395
+ code: "spawn-data-unknown-field",
1396
+ expected: `field name in {${Object.keys(schema).sort().join(", ")}}`,
1397
+ hint: `unknown field '${fieldName}' on component '${compName}' at scene localId ${nodeLocalId}`,
1398
+ detail: {
1399
+ component: compName,
1400
+ field: fieldName,
1401
+ entity: nodeLocalId,
1402
+ knownFields: Object.keys(schema).sort()
1403
+ }
1404
+ });
811
1405
  }
812
1406
  const value = raw[fieldName];
813
- const kind = classifyEntityField(token, fieldName);
1407
+ const kind = classifyEntityField2(token, fieldName);
814
1408
  if (kind !== null) {
815
1409
  const sceneRemap = (localId) => {
816
1410
  if (localId < 0 || localId >= mapping.length) return ENTITY_NULL_RAW;
817
1411
  const live = mapping[localId];
818
1412
  return live === void 0 || live === ENTITY_NULL_RAW ? ENTITY_NULL_RAW : live;
819
1413
  };
820
- remappedRaw[fieldName] = remapEntityFieldValue(value, kind, sceneRemap);
1414
+ remappedRaw[fieldName] = remapEntityFieldValue2(value, kind, sceneRemap);
821
1415
  } else {
822
1416
  remappedRaw[fieldName] = value;
823
1417
  }
@@ -825,11 +1419,35 @@ function worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics)
825
1419
  const filled = fillComponentDefaults(token, remappedRaw);
826
1420
  out.push({ component: token, data: filled });
827
1421
  }
828
- return ok4(out);
1422
+ return ok5(out);
1423
+ }
1424
+ function worldRemapMountOverride(world, override, mapping) {
1425
+ const token = world.components.resolve(override.comp);
1426
+ if (token === void 0) return override;
1427
+ const remapField = (field, value2) => {
1428
+ const kind = classifyEntityField2(token, field);
1429
+ if (kind === null) return value2;
1430
+ const toLive = (slot) => {
1431
+ if (slot < 0 || slot >= mapping.length) return ENTITY_NULL_RAW;
1432
+ return mapping[slot] ?? ENTITY_NULL_RAW;
1433
+ };
1434
+ return remapEntityFieldValue2(value2, kind, toLive);
1435
+ };
1436
+ if (override.field !== void 0) {
1437
+ return { ...override, value: remapField(override.field, override.value) };
1438
+ }
1439
+ if (typeof override.value !== "object" || override.value === null || Array.isArray(override.value)) {
1440
+ return override;
1441
+ }
1442
+ const value = {};
1443
+ for (const [field, fieldValue] of Object.entries(override.value)) {
1444
+ value[field] = remapField(field, fieldValue);
1445
+ }
1446
+ return { ...override, value };
829
1447
  }
830
1448
  function worldApplyMountOverride(world, member, ov) {
831
1449
  const ovToken = world.components.resolve(ov.comp);
832
- if (ovToken === void 0) return ok4(void 0);
1450
+ if (ovToken === void 0) return ok5(void 0);
833
1451
  if (ov.field !== void 0) {
834
1452
  return world.set(member, ovToken, { [ov.field]: ov.value });
835
1453
  }
@@ -843,7 +1461,7 @@ function worldApplyMountOverride(world, member, ov) {
843
1461
  }
844
1462
  function worldValidateMountOverrides(world, mount) {
845
1463
  const overrides = mount.overrides;
846
- if (overrides === void 0) return ok4(void 0);
1464
+ if (overrides === void 0) return ok5(void 0);
847
1465
  const memberFirst = mount.memberFirst;
848
1466
  const memberCount = mount.memberCount;
849
1467
  const memberLast = memberFirst + memberCount;
@@ -851,10 +1469,10 @@ function worldValidateMountOverrides(world, mount) {
851
1469
  for (const ov of overrides) {
852
1470
  const ovLid = ov.localId;
853
1471
  if (ovLid < memberFirst || ovLid >= memberLast) {
854
- return err4({
1472
+ return err5({
855
1473
  code: "pack-mount-override-localid-out-of-range",
856
1474
  expected: `override.localId in [${memberFirst}, ${memberLast})`,
857
- hint: PACK_ERROR_HINTS["pack-mount-override-localid-out-of-range"],
1475
+ hint: PACK_ERROR_HINTS2["pack-mount-override-localid-out-of-range"],
858
1476
  detail: {
859
1477
  code: "pack-mount-override-localid-out-of-range",
860
1478
  overrideLocalId: ovLid,
@@ -866,12 +1484,12 @@ function worldValidateMountOverrides(world, mount) {
866
1484
  const ovToken = world.components.resolve(ov.comp);
867
1485
  if (ov.field !== void 0) {
868
1486
  if (ovToken !== void 0) {
869
- const schema = componentSchema(ovToken);
1487
+ const schema = componentSchema2(ovToken);
870
1488
  if (!(ov.field in schema)) {
871
- return err4({
1489
+ return err5({
872
1490
  code: "pack-mount-override-unknown-field",
873
1491
  expected: `override.field defined on component '${ov.comp}'`,
874
- hint: PACK_ERROR_HINTS["pack-mount-override-unknown-field"],
1492
+ hint: PACK_ERROR_HINTS2["pack-mount-override-unknown-field"],
875
1493
  detail: {
876
1494
  code: "pack-mount-override-unknown-field",
877
1495
  comp: ov.comp,
@@ -883,16 +1501,16 @@ function worldValidateMountOverrides(world, mount) {
883
1501
  }
884
1502
  } else {
885
1503
  if (ovToken === void 0) {
886
- return err4(new ComponentNotDefinedError(ov.comp));
1504
+ return err5(new ComponentNotDefinedError(ov.comp));
887
1505
  }
888
- const schema = componentSchema(ovToken);
1506
+ const schema = componentSchema2(ovToken);
889
1507
  const valueMap = ov.value ?? {};
890
1508
  for (const key of Object.keys(valueMap)) {
891
1509
  if (!(key in schema)) {
892
- return err4({
1510
+ return err5({
893
1511
  code: "pack-mount-override-unknown-field",
894
1512
  expected: `override.value keys defined on component '${ov.comp}'`,
895
- hint: PACK_ERROR_HINTS["pack-mount-override-unknown-field"],
1513
+ hint: PACK_ERROR_HINTS2["pack-mount-override-unknown-field"],
896
1514
  detail: {
897
1515
  code: "pack-mount-override-unknown-field",
898
1516
  comp: ov.comp,
@@ -904,7 +1522,7 @@ function worldValidateMountOverrides(world, mount) {
904
1522
  }
905
1523
  }
906
1524
  }
907
- return ok4(void 0);
1525
+ return ok5(void 0);
908
1526
  }
909
1527
  function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
910
1528
  const fakeNode = {
@@ -923,7 +1541,7 @@ function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
923
1541
  if (cdRes.value.length === 0) {
924
1542
  const childOfToken = world.components.resolve("ChildOf");
925
1543
  if (childOfToken === void 0) {
926
- return err4(new ComponentNotDefinedError("ChildOf"));
1544
+ return err5(new ComponentNotDefinedError("ChildOf"));
927
1545
  }
928
1546
  cdRes.value.push({
929
1547
  component: childOfToken,
@@ -935,7 +1553,7 @@ function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
935
1553
  function worldResolveMountSource(world, source, parentHandle) {
936
1554
  const resolver = worldGetSceneAssetResolver(world);
937
1555
  if (resolver === null) {
938
- return err4({
1556
+ return err5({
939
1557
  code: "stale-entity",
940
1558
  expected: "wired SceneAssetResolver (auto-wired by engine.assets.instantiate)",
941
1559
  hint: "engine.assets.instantiate sugar wires this for you; call worldSetSceneAssetResolver before nested scene expansion.",
@@ -944,9 +1562,9 @@ function worldResolveMountSource(world, source, parentHandle) {
944
1562
  }
945
1563
  const r = resolver(source, parentHandle);
946
1564
  if (!r.ok) {
947
- return err4(r.error);
1565
+ return err5(r.error);
948
1566
  }
949
- return ok4(r.value);
1567
+ return ok5(r.value);
950
1568
  }
951
1569
  function worldMountOverridesToStateMap(src) {
952
1570
  const out = /* @__PURE__ */ new Map();
@@ -969,7 +1587,7 @@ function worldSetUniqueRefPayload(world, handle, payload) {
969
1587
  function worldResolveSceneInstanceStatePayload(world, root) {
970
1588
  const sceneInstanceToken = world.components.resolve("SceneInstance");
971
1589
  if (sceneInstanceToken === void 0) {
972
- return err4(new ComponentNotDefinedError("SceneInstance"));
1590
+ return err5(new ComponentNotDefinedError("SceneInstance"));
973
1591
  }
974
1592
  const r = world.get(root, sceneInstanceToken);
975
1593
  if (!r.ok) return r;
@@ -977,7 +1595,7 @@ function worldResolveSceneInstanceStatePayload(world, root) {
977
1595
  const stateRefHandle = toUnique(stateRefRaw);
978
1596
  const payload = sceneWorldState(world).statePayloads.get(Number(stateRefHandle));
979
1597
  if (payload === void 0) {
980
- return err4(
1598
+ return err5(
981
1599
  new StaleEntityError(root, entityIndex(root), entityGeneration(root), {
982
1600
  operation: "resolveSceneInstanceState",
983
1601
  component: "SceneInstance",
@@ -986,7 +1604,7 @@ function worldResolveSceneInstanceStatePayload(world, root) {
986
1604
  })
987
1605
  );
988
1606
  }
989
- return ok4(payload);
1607
+ return ok5(payload);
990
1608
  }
991
1609
  function worldGetSceneInstanceState(world, root) {
992
1610
  return worldResolveSceneInstanceStatePayload(world, root);
@@ -995,18 +1613,18 @@ function worldResolveSceneEntity(world, root, ref) {
995
1613
  const state = worldResolveSceneInstanceStatePayload(world, root);
996
1614
  if (!state.ok) return state;
997
1615
  const resolved = resolveSceneEntity(ref, {
998
- sceneSourceKey: state.value.sceneSourceKey ?? ref.sceneSourceKey,
1616
+ sceneSourceKey: state.value.sceneSourceKey ?? "",
999
1617
  bindings: state.value.bindings
1000
1618
  });
1001
- if (!resolved.ok) return err4(resolved.error);
1002
- return ok4(resolved.value);
1619
+ if (!resolved.ok) return err5(resolved.error);
1620
+ return ok5(resolved.value);
1003
1621
  }
1004
1622
  function worldDespawnScene(world, root, opts) {
1005
1623
  const dRes = worldDespawnDescendants(world, root, opts);
1006
1624
  if (!dRes.ok) return dRes;
1007
1625
  const drop = world.despawn(root);
1008
1626
  if (!drop.ok) return drop;
1009
- return ok4(dRes.value + 1);
1627
+ return ok5(dRes.value + 1);
1010
1628
  }
1011
1629
  function worldDespawnDescendants(world, root, opts) {
1012
1630
  let detached = null;
@@ -1083,7 +1701,7 @@ function worldDespawnDescendants(world, root, opts) {
1083
1701
  }
1084
1702
  count += 1;
1085
1703
  }
1086
- return ok4(count);
1704
+ return ok5(count);
1087
1705
  }
1088
1706
  function worldSetSceneOverride(world, root, member, component, field, value) {
1089
1707
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
@@ -1091,7 +1709,7 @@ function worldSetSceneOverride(world, root, member, component, field, value) {
1091
1709
  const state = stateRes.value;
1092
1710
  const lid = state.entityToLocalId.get(member);
1093
1711
  if (lid === void 0) {
1094
- return err4(
1712
+ return err5(
1095
1713
  new StaleEntityError(
1096
1714
  member,
1097
1715
  entityIndex(member),
@@ -1105,12 +1723,12 @@ function worldSetSceneOverride(world, root, member, component, field, value) {
1105
1723
  )
1106
1724
  );
1107
1725
  }
1108
- const schemaType = componentSchema(component)[field];
1726
+ const schemaType = componentSchema2(component)[field];
1109
1727
  if (schemaType !== void 0 && isPrimitiveScalarFieldType(schemaType)) {
1110
1728
  const expectJsType = primitiveJsType(schemaType);
1111
1729
  const actualJsType = typeof value;
1112
1730
  if (expectJsType !== actualJsType) {
1113
- return err4({
1731
+ return err5({
1114
1732
  code: "scene-override-type-mismatch",
1115
1733
  expected: `value typeof === ${expectJsType}`,
1116
1734
  hint: `setSceneOverride(${component.name}.${field}) expected ${expectJsType}, got ${actualJsType}; coerce or pick a different override path.`,
@@ -1136,14 +1754,14 @@ function worldSetSceneOverride(world, root, member, component, field, value) {
1136
1754
  field,
1137
1755
  value
1138
1756
  });
1139
- return ok4(void 0);
1757
+ return ok5(void 0);
1140
1758
  }
1141
1759
  function worldRemoveSceneOverride(world, root, member, component, field) {
1142
1760
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1143
1761
  if (!stateRes.ok) return stateRes;
1144
1762
  const state = stateRes.value;
1145
1763
  const lid = state.entityToLocalId.get(member);
1146
- if (lid === void 0) return ok4(void 0);
1764
+ if (lid === void 0) return ok5(void 0);
1147
1765
  const fieldMap = state.overrides.get(lid);
1148
1766
  if (fieldMap !== void 0) {
1149
1767
  fieldMap.delete(`${component.name}:${field}`);
@@ -1151,42 +1769,41 @@ function worldRemoveSceneOverride(world, root, member, component, field) {
1151
1769
  }
1152
1770
  const assetRes = worldResolveSceneAsset(world, state.source);
1153
1771
  if (!assetRes.ok) return assetRes;
1154
- const node = assetRes.value.entities.find(
1155
- (n) => n.localId === lid
1156
- );
1772
+ const key = state.keyByLocalId.get(lid);
1773
+ const node = key === void 0 ? void 0 : assetRes.value.entities[key];
1157
1774
  const layer1 = node?.components[component.name];
1158
1775
  if (layer1 !== void 0 && field in layer1) {
1159
1776
  const r = world.set(member, component, { [field]: layer1[field] });
1160
1777
  if (!r.ok) return r;
1161
1778
  }
1162
- return ok4(void 0);
1779
+ return ok5(void 0);
1163
1780
  }
1164
1781
  function worldDetachSceneMember(world, root, member) {
1165
1782
  const sceneInstanceToken = world.components.resolve("SceneInstance");
1166
1783
  if (sceneInstanceToken === void 0) {
1167
- return err4(new ComponentNotDefinedError("SceneInstance"));
1784
+ return err5(new ComponentNotDefinedError("SceneInstance"));
1168
1785
  }
1169
1786
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1170
1787
  if (!stateRes.ok) return stateRes;
1171
1788
  const state = stateRes.value;
1172
1789
  const lid = state.entityToLocalId.get(member);
1173
- if (lid === void 0) return ok4(void 0);
1790
+ if (lid === void 0) return ok5(void 0);
1174
1791
  state.detachedLocalIds.add(lid);
1175
- return ok4(void 0);
1792
+ return ok5(void 0);
1176
1793
  }
1177
1794
  function worldReattachSceneMember(world, root, member) {
1178
1795
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1179
1796
  if (!stateRes.ok) return stateRes;
1180
1797
  const state = stateRes.value;
1181
1798
  const lid = state.entityToLocalId.get(member);
1182
- if (lid === void 0) return ok4(void 0);
1799
+ if (lid === void 0) return ok5(void 0);
1183
1800
  state.detachedLocalIds.delete(lid);
1184
- return ok4(void 0);
1801
+ return ok5(void 0);
1185
1802
  }
1186
1803
  function worldGetSceneAssetForInstance(world, root) {
1187
1804
  const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1188
1805
  if (!stateRes.ok) return stateRes;
1189
- return ok4(stateRes.value.source);
1806
+ return ok5(stateRes.value.source);
1190
1807
  }
1191
1808
  function sceneTopoSort(nodes) {
1192
1809
  const n = nodes.length;
@@ -1243,7 +1860,7 @@ import {
1243
1860
  } from "@forgeax/engine-ecs/internal";
1244
1861
  import { worldRead } from "@forgeax/engine-ecs/world-read";
1245
1862
  import { mat4 } from "@forgeax/engine-math";
1246
- import { err as err5, ok as ok5 } from "@forgeax/engine-types";
1863
+ import { err as err6, ok as ok6 } from "@forgeax/engine-types";
1247
1864
  var PROPAGATE_TRANSFORMS_SYSTEM = "propagateTransforms";
1248
1865
  var PROPAGATE_TRANSFORMS_FIXED_SYSTEM = "propagateTransformsFixed";
1249
1866
  var TransformSet = defineSystemSet({ name: "transform" });
@@ -1261,7 +1878,7 @@ function countPropagation(name) {
1261
1878
  var SCRATCH = /* @__PURE__ */ new WeakMap();
1262
1879
  var REGISTRATION_LEASES = /* @__PURE__ */ new WeakMap();
1263
1880
  function pairError(entity, expected) {
1264
- return err5(
1881
+ return err6(
1265
1882
  new SceneError({
1266
1883
  code: "hierarchy-broken",
1267
1884
  expected,
@@ -1272,7 +1889,7 @@ function pairError(entity, expected) {
1272
1889
  }
1273
1890
  function ensureQueries(world, scratch) {
1274
1891
  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);
1892
+ return ok6(void 0);
1276
1893
  }
1277
1894
  const flatOutput = world.query({
1278
1895
  read: [Transform],
@@ -1299,7 +1916,7 @@ function ensureQueries(world, scratch) {
1299
1916
  scratch.transformWriter = transformWriter.value;
1300
1917
  scratch.missingGlobalQuery = missingGlobal.value;
1301
1918
  scratch.missingTransformQuery = missingTransform.value;
1302
- return ok5(void 0);
1919
+ return ok6(void 0);
1303
1920
  }
1304
1921
  function validateTransformPairs(world, scratch) {
1305
1922
  const queryResult = ensureQueries(world, scratch);
@@ -1315,7 +1932,7 @@ function validateTransformPairs(world, scratch) {
1315
1932
  for (const row of missingTransform) {
1316
1933
  return pairError(row.entity, "each GlobalTransform entity to carry a Transform pair");
1317
1934
  }
1318
- return ok5(void 0);
1935
+ return ok6(void 0);
1319
1936
  }
1320
1937
  function scratchFor(world) {
1321
1938
  const existing = SCRATCH.get(world);
@@ -1406,7 +2023,7 @@ function composeFlatColumns(positions, rotations, scales, worlds, count) {
1406
2023
  function propagateFlat(world, scratch) {
1407
2024
  const query = scratch.flatQuery;
1408
2025
  if (query === void 0)
1409
- return err5(
2026
+ return err6(
1410
2027
  new SceneError({
1411
2028
  code: "hierarchy-broken",
1412
2029
  expected: "a valid changed Transform write query",
@@ -1425,7 +2042,7 @@ function propagateFlat(world, scratch) {
1425
2042
  }
1426
2043
  } catch (cause) {
1427
2044
  const error = cause;
1428
- return err5(derivedWriteError(error, bindingIndex));
2045
+ return err6(derivedWriteError(error, bindingIndex));
1429
2046
  }
1430
2047
  const transformWriter = scratch.transformWriter;
1431
2048
  if (transformWriter === void 0) {
@@ -1433,7 +2050,7 @@ function propagateFlat(world, scratch) {
1433
2050
  }
1434
2051
  const transformBindings = transformWriter.bindings;
1435
2052
  const structureEpoch = world.getStructureEpoch();
1436
- if (scratch.flatStructureEpoch === structureEpoch) return ok5(void 0);
2053
+ if (scratch.flatStructureEpoch === structureEpoch) return ok6(void 0);
1437
2054
  ensureFlatBuffers(scratch, transformBindings);
1438
2055
  resetFlatBuffers(scratch);
1439
2056
  for (let bindingIndex2 = 0; bindingIndex2 < transformBindings.length; bindingIndex2 += 1) {
@@ -1452,10 +2069,10 @@ function propagateFlat(world, scratch) {
1452
2069
  const changed = scratch.flatChanged[bindingIndex2];
1453
2070
  if (changed === void 0) continue;
1454
2071
  const published = transformWriter.publishChangedRows(bindingIndex2, changed);
1455
- if (!published.ok) return err5(derivedWriteError(published.error, bindingIndex2));
2072
+ if (!published.ok) return err6(derivedWriteError(published.error, bindingIndex2));
1456
2073
  }
1457
2074
  scratch.flatStructureEpoch = structureEpoch;
1458
- return ok5(void 0);
2075
+ return ok6(void 0);
1459
2076
  }
1460
2077
  function transformColumns(binding) {
1461
2078
  return binding.read;
@@ -2007,7 +2624,7 @@ function propagateHierarchy(world, scratch) {
2007
2624
  const hierarchyWriter = scratch.hierarchyWriter;
2008
2625
  const transformWriter = scratch.transformWriter;
2009
2626
  if (hierarchyWriter === void 0 || transformWriter === void 0) {
2010
- return err5(
2627
+ return err6(
2011
2628
  new SceneError({
2012
2629
  code: "hierarchy-broken",
2013
2630
  expected: "paired Transform and GlobalTransform derived bindings",
@@ -2022,7 +2639,7 @@ function propagateHierarchy(world, scratch) {
2022
2639
  scratch.hierarchyBindingRows.length = 0;
2023
2640
  scratch.hierarchyStates.length = 0;
2024
2641
  scratch.hierarchyChanged.length = 0;
2025
- return ok5(void 0);
2642
+ return ok6(void 0);
2026
2643
  }
2027
2644
  ensureHierarchyBuffers(scratch, hierarchyBindings);
2028
2645
  resetHierarchyBuffers(scratch);
@@ -2080,7 +2697,7 @@ function propagateHierarchy(world, scratch) {
2080
2697
  report(derivedWriteError(cause, bindingIndex));
2081
2698
  }
2082
2699
  }
2083
- return firstError === void 0 ? ok5(void 0) : err5(firstError);
2700
+ return firstError === void 0 ? ok6(void 0) : err6(firstError);
2084
2701
  }
2085
2702
  function propagateTransforms(world) {
2086
2703
  const scratch = scratchFor(world);
@@ -2291,6 +2908,7 @@ export {
2291
2908
  Transform,
2292
2909
  TransformSet,
2293
2910
  collectSubtree,
2911
+ compileKeyedSceneAsset,
2294
2912
  externalizeSceneAsset,
2295
2913
  projectHierarchy,
2296
2914
  propagateTransforms,
@@ -2298,8 +2916,9 @@ export {
2298
2916
  resolveSceneEntity,
2299
2917
  sceneAssetContribution,
2300
2918
  sceneEntity,
2919
+ sceneEntityAddressKey,
2301
2920
  scenePlugin,
2302
- validateSceneBindings,
2921
+ validateSceneEntityKeys,
2303
2922
  worldApplyMountOverride,
2304
2923
  worldBuildSceneEntityComponentDatas,
2305
2924
  worldDespawnDescendants,