@forgeax/engine-scene 0.0.0-dev.8d955ade1c79

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 (84) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +55 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/asset-owner.integration.test.d.ts +2 -0
  5. package/dist/__tests__/asset-owner.integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/components.unit.test.d.ts +2 -0
  7. package/dist/__tests__/components.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/diagnostic-type-surface.test-d.d.ts +3 -0
  9. package/dist/__tests__/diagnostic-type-surface.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/direct-child-of-import.unit.test.d.ts +2 -0
  11. package/dist/__tests__/direct-child-of-import.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/fixtures/malformed-hierarchy-edge.d.ts +8 -0
  13. package/dist/__tests__/fixtures/malformed-hierarchy-edge.d.ts.map +1 -0
  14. package/dist/__tests__/hierarchy-projection-contract.unit.test.d.ts +2 -0
  15. package/dist/__tests__/hierarchy-projection-contract.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/hierarchy-projection.unit.test.d.ts +2 -0
  17. package/dist/__tests__/hierarchy-projection.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/plugin.integration.test.d.ts +2 -0
  19. package/dist/__tests__/plugin.integration.test.d.ts.map +1 -0
  20. package/dist/__tests__/propagation.unit.test.d.ts +2 -0
  21. package/dist/__tests__/propagation.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/transform-change-journal.unit.test.d.ts +2 -0
  23. package/dist/__tests__/transform-change-journal.unit.test.d.ts.map +1 -0
  24. package/dist/assets/scene-decoder.d.ts +8 -0
  25. package/dist/assets/scene-decoder.d.ts.map +1 -0
  26. package/dist/collect-subtree.d.ts +4 -0
  27. package/dist/collect-subtree.d.ts.map +1 -0
  28. package/dist/components/child-of.d.ts +31 -0
  29. package/dist/components/child-of.d.ts.map +1 -0
  30. package/dist/components/children.d.ts +48 -0
  31. package/dist/components/children.d.ts.map +1 -0
  32. package/dist/components/morph-weights.d.ts +5 -0
  33. package/dist/components/morph-weights.d.ts.map +1 -0
  34. package/dist/components/name.d.ts +4 -0
  35. package/dist/components/name.d.ts.map +1 -0
  36. package/dist/components/transform.d.ts +52 -0
  37. package/dist/components/transform.d.ts.map +1 -0
  38. package/dist/errors.d.ts +22 -0
  39. package/dist/errors.d.ts.map +1 -0
  40. package/dist/index.d.ts +15 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.mjs +1731 -0
  43. package/dist/index.mjs.map +1 -0
  44. package/dist/instances/collect-profile.d.ts +7 -0
  45. package/dist/instances/collect-profile.d.ts.map +1 -0
  46. package/dist/instances/externalization.d.ts +14 -0
  47. package/dist/instances/externalization.d.ts.map +1 -0
  48. package/dist/instances/scene-instances.d.ts +350 -0
  49. package/dist/instances/scene-instances.d.ts.map +1 -0
  50. package/dist/plugin.d.ts +3 -0
  51. package/dist/plugin.d.ts.map +1 -0
  52. package/dist/systems/hierarchy-projection.d.ts +16 -0
  53. package/dist/systems/hierarchy-projection.d.ts.map +1 -0
  54. package/dist/systems/index.d.ts +3 -0
  55. package/dist/systems/index.d.ts.map +1 -0
  56. package/dist/systems/propagate-transforms.d.ts +15 -0
  57. package/dist/systems/propagate-transforms.d.ts.map +1 -0
  58. package/package.json +58 -0
  59. package/src/__tests__/asset-owner.integration.test.ts +57 -0
  60. package/src/__tests__/components.unit.test.ts +26 -0
  61. package/src/__tests__/diagnostic-type-surface.test-d.ts +11 -0
  62. package/src/__tests__/direct-child-of-import.unit.test.ts +13 -0
  63. package/src/__tests__/fixtures/malformed-hierarchy-edge.ts +15 -0
  64. package/src/__tests__/hierarchy-projection-contract.unit.test.ts +45 -0
  65. package/src/__tests__/hierarchy-projection.unit.test.ts +92 -0
  66. package/src/__tests__/plugin.integration.test.ts +23 -0
  67. package/src/__tests__/propagation.unit.test.ts +163 -0
  68. package/src/__tests__/transform-change-journal.unit.test.ts +105 -0
  69. package/src/assets/scene-decoder.ts +153 -0
  70. package/src/collect-subtree.ts +30 -0
  71. package/src/components/child-of.ts +90 -0
  72. package/src/components/children.ts +113 -0
  73. package/src/components/morph-weights.ts +6 -0
  74. package/src/components/name.ts +22 -0
  75. package/src/components/transform.ts +89 -0
  76. package/src/errors.ts +34 -0
  77. package/src/index.ts +65 -0
  78. package/src/instances/collect-profile.ts +10 -0
  79. package/src/instances/externalization.ts +137 -0
  80. package/src/instances/scene-instances.ts +1564 -0
  81. package/src/plugin.ts +28 -0
  82. package/src/systems/hierarchy-projection.ts +134 -0
  83. package/src/systems/index.ts +11 -0
  84. package/src/systems/propagate-transforms.ts +376 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,1731 @@
1
+ // ../types/dist/index.mjs
2
+ var OK_PROTO = {
3
+ unwrap() {
4
+ return this.value;
5
+ },
6
+ unwrapOr(_defaultValue) {
7
+ return this.value;
8
+ }
9
+ };
10
+ var ERR_PROTO = {
11
+ unwrap() {
12
+ throw this.error;
13
+ },
14
+ unwrapOr(defaultValue) {
15
+ return defaultValue;
16
+ }
17
+ };
18
+ function ok(value) {
19
+ const r = Object.create(OK_PROTO);
20
+ r.ok = true;
21
+ r.value = value;
22
+ return r;
23
+ }
24
+ function err(error) {
25
+ const r = Object.create(ERR_PROTO);
26
+ r.ok = false;
27
+ r.error = error;
28
+ return r;
29
+ }
30
+ function toUnique(raw) {
31
+ return raw;
32
+ }
33
+ function unwrapHandle(h) {
34
+ return h;
35
+ }
36
+ var MAX_SLOT = (1 << 24) - 1;
37
+ var MATERIAL_ERROR_CODES = [
38
+ "material-parent-not-found",
39
+ "material-circular-inheritance",
40
+ "material-no-effective-pass",
41
+ "material-value-unknown",
42
+ "material-value-type-mismatch",
43
+ "material-contract-program-mismatch",
44
+ "shader-module-id-missing",
45
+ "shader-module-id-duplicate",
46
+ "shader-module-not-found",
47
+ "shader-module-namespace-reserved",
48
+ "material-reflection-binding-mismatch",
49
+ "material-specialization-not-cooked",
50
+ "material-specialization-stale-generation",
51
+ "gltf-material-uv-set-missing",
52
+ "material-derived-interface-mismatch",
53
+ "material-texture-coordinate-invalid",
54
+ "material-payload-bounds"
55
+ ];
56
+ var MATERIAL_ERROR_POLICY = {
57
+ "material-parent-not-found": {
58
+ expected: "every parent GUID resolves to a MaterialAsset",
59
+ hint: "fix the parent GUID and resolve the material again"
60
+ },
61
+ "material-circular-inheritance": {
62
+ expected: "the parent chain is acyclic",
63
+ hint: "remove the repeated GUID from the parent chain"
64
+ },
65
+ "material-no-effective-pass": {
66
+ expected: "the resolved material has at least one pass",
67
+ hint: "add a pass to the root material or an inherited parent"
68
+ },
69
+ "material-value-unknown": {
70
+ expected: "every value name is declared by the effective contract",
71
+ hint: "remove the value or declare the parameter in the root contract"
72
+ },
73
+ "material-value-type-mismatch": {
74
+ expected: "each value matches its declared parameter type",
75
+ hint: "change the value to the declared parameter type"
76
+ },
77
+ "material-contract-program-mismatch": {
78
+ expected: "the program satisfies the material contract",
79
+ hint: "align the program entries with the root contract"
80
+ },
81
+ "shader-module-id-missing": {
82
+ expected: "each WGSL source declares a module ID",
83
+ hint: "add a compiler-native module ID declaration to the WGSL source"
84
+ },
85
+ "shader-module-id-duplicate": {
86
+ expected: "each module ID has one source provenance",
87
+ hint: "rename one module or remove the duplicate source"
88
+ },
89
+ "shader-module-not-found": {
90
+ expected: "every referenced module exists in the source catalog",
91
+ hint: "add the module to the source catalog or fix the reference"
92
+ },
93
+ "shader-module-namespace-reserved": {
94
+ expected: "user modules use a non-reserved namespace",
95
+ hint: "choose a module ID outside the reserved namespace"
96
+ },
97
+ "material-reflection-binding-mismatch": {
98
+ expected: "reflection matches the material contract bindings",
99
+ hint: "update the contract or WGSL binding and cook again"
100
+ },
101
+ "material-specialization-not-cooked": {
102
+ expected: "the requested specialization has a cooked artifact",
103
+ hint: "run the build or development cook path for this selection"
104
+ },
105
+ "material-specialization-stale-generation": {
106
+ expected: "all specialization dependencies share one generation",
107
+ hint: "retry after dependent assets and sources settle"
108
+ },
109
+ "gltf-material-uv-set-missing": {
110
+ expected: "each texture slot references an available primitive UV set",
111
+ hint: "add the requested UV set to the primitive and re-import it"
112
+ },
113
+ "material-derived-interface-mismatch": {
114
+ expected: "the generated material interface matches the derived schema interface",
115
+ hint: "repair the schema or WGSL producer and recook the material"
116
+ },
117
+ "material-texture-coordinate-invalid": {
118
+ expected: "every texture coordinate record is finite and complete",
119
+ hint: "repair the texture metadata or coordinates and recook the material"
120
+ },
121
+ "material-payload-bounds": {
122
+ expected: "every material payload write stays within the derived payload",
123
+ hint: "repair the derived payload owner before submitting the draw"
124
+ }
125
+ };
126
+ var MATERIAL_ERROR_EXPECTED = Object.fromEntries(
127
+ MATERIAL_ERROR_CODES.map((code) => [code, MATERIAL_ERROR_POLICY[code].expected])
128
+ );
129
+ var MATERIAL_ERROR_HINTS = Object.fromEntries(
130
+ MATERIAL_ERROR_CODES.map((code) => [code, MATERIAL_ERROR_POLICY[code].hint])
131
+ );
132
+ var NUMERIC_TYPES = /* @__PURE__ */ new Set([
133
+ "f32",
134
+ "i32",
135
+ "u32",
136
+ "vec2",
137
+ "vec3",
138
+ "vec4",
139
+ "color"
140
+ ]);
141
+ var TEXTURE_VIEW_TYPES = /* @__PURE__ */ new Set([
142
+ "texture2d",
143
+ "texture_cube",
144
+ "texture_depth_2d",
145
+ "texture_cube_array"
146
+ ]);
147
+ var SAMPLER_TYPES = /* @__PURE__ */ new Set([
148
+ "sampler",
149
+ "sampler_comparison"
150
+ ]);
151
+ var ALL_TYPES = /* @__PURE__ */ new Set([
152
+ ...NUMERIC_TYPES,
153
+ ...TEXTURE_VIEW_TYPES,
154
+ ...SAMPLER_TYPES,
155
+ "storage_buffer"
156
+ ]);
157
+ var PACK_ERROR_HINTS = {
158
+ "pack-malformed-meta": "check guid is a valid RFC 4122 UUID; validate with: ajv validate -s schema/meta.schema.json -d <file>",
159
+ "pack-malformed-pack": "check all asset guid and refs[] fields are 36-char dash-form UUIDs; validate with pack.schema.json",
160
+ "pack-guid-malformed": "use AssetGuid.random() or a UUIDv7 generator; all GUID fields must be 36-char RFC 4122 dash-form",
161
+ "pack-orphan-meta": "remove the orphan .meta.json or add the missing source file next to it",
162
+ "pack-meta-missing": "run forgeax-engine-remote-asset scan --roots <dir> to list source files without .meta.json",
163
+ "pack-guid-collision": "run forgeax-engine-remote-asset verify to list all GUID collisions; each GUID must be globally unique",
164
+ "pack-cyclic-reference": "run forgeax-engine-remote-asset verify to print the cycle path; break the cycle by removing a refs[] entry",
165
+ "pack-subasset-index-out-of-range": "check subAssets[].sourceIndex does not exceed the actual sub-image count in the source file",
166
+ // === 1 new hint (feat-20260523-shader-template-instance-split M1-T02) ===
167
+ "payload-schema-mismatch": "material asset payload failed schema validation; check paramSchema entries all use valid types from MATERIAL_PARAM_TYPES and materialShader is a non-empty string",
168
+ // === 4 new hints (feat-20260608-scene-nesting-ecs-fication M1 / w8;
169
+ // plan-strategy D-8) ===
170
+ "pack-mount-localid-overlap": "check parent SceneAsset.mounts[].memberFirst windows do not overlap with each other or with entities[].localId; rebuild mount sidecar after the child SceneAsset reimport",
171
+ "pack-mount-count-mismatch": "mount.memberCount must equal the referenced child SceneAsset totalSlots (entities.length + sum(mounts[].memberCount) + mounts.length); rebuild mount sidecar via forgeax-engine-remote-asset verify <dir> after the child SceneAsset reimport",
172
+ "pack-mount-override-localid-out-of-range": "override.localId must be in [0, mount.memberCount); shrink the override or extend memberCount to match the child SceneAsset",
173
+ "pack-mount-override-unknown-field": "override.comp / override.field must match a defined component schema; check defineComponent registry or rebuild mount sidecar after the child SceneAsset reimport",
174
+ // === 2 new hints (feat-20260625-asset-meta-source-mount-prefix M1 / w1) ===
175
+ "pack-unknown-path": "the @name in source is not declared in package.json#forgeax.assets.paths; add it there or use a known name from the list in error.detail.knownNames",
176
+ "pack-malformed-path-ref": "source must be @<name>/<rest> where name is a key in package.json#forgeax.assets.paths; the resolved path must not escape the declared directory"
177
+ };
178
+
179
+ // src/assets/scene-decoder.ts
180
+ var sceneAssetKind = {
181
+ kind: "scene"
182
+ };
183
+ function invalidScene(guid, reason) {
184
+ return err({
185
+ code: "asset-package-invalid",
186
+ expected: "a scene payload with an entities array",
187
+ hint: "recook the SceneAsset and publish its complete envelope",
188
+ detail: { guid, reason }
189
+ });
190
+ }
191
+ var sceneWireRefs = /* @__PURE__ */ new WeakMap();
192
+ function resolveWireRef(refs, value, location) {
193
+ const guid = refs[value];
194
+ if (!Number.isInteger(value) || value < 0 || guid === void 0) {
195
+ return {
196
+ ok: false,
197
+ reason: `${location} references refs[${value}], but refs contains ${refs.length} entries`
198
+ };
199
+ }
200
+ return { ok: true, value: guid };
201
+ }
202
+ function resolveMounts(mounts, refs) {
203
+ if (mounts === void 0) return { ok: true, value: void 0 };
204
+ const resolved = [];
205
+ for (const mount of mounts) {
206
+ if (typeof mount.source !== "number" || !Number.isInteger(mount.source)) {
207
+ resolved.push(mount);
208
+ continue;
209
+ }
210
+ const ref = resolveWireRef(refs, mount.source, `mount ${mount.localId} source`);
211
+ if (!ref.ok) return ref;
212
+ resolved.push({ ...mount, source: ref.value });
213
+ }
214
+ return { ok: true, value: resolved };
215
+ }
216
+ function resolveSkinGuids(skinGuids, refs) {
217
+ if (skinGuids === void 0) return { ok: true, value: void 0 };
218
+ const resolved = [];
219
+ for (let index = 0; index < skinGuids.length; index += 1) {
220
+ const value = skinGuids[index];
221
+ if (typeof value === "string") {
222
+ resolved.push(value);
223
+ continue;
224
+ }
225
+ if (typeof value !== "number" || !Number.isInteger(value)) {
226
+ return { ok: false, reason: `skinGuids[${index}] is not a GUID or refs index` };
227
+ }
228
+ const ref = resolveWireRef(refs, value, `skinGuids[${index}]`);
229
+ if (!ref.ok) return ref;
230
+ resolved.push(ref.value);
231
+ }
232
+ return { ok: true, value: resolved };
233
+ }
234
+ function resolveSceneWireRefs(payload, refs) {
235
+ const entities = [];
236
+ for (const entity of payload.entities) {
237
+ const components = {};
238
+ for (const [componentName, rawFields] of Object.entries(entity.components)) {
239
+ components[componentName] = { ...rawFields };
240
+ }
241
+ entities.push({ localId: entity.localId, components });
242
+ }
243
+ const mounts = resolveMounts(payload.mounts, refs);
244
+ if (!mounts.ok) return mounts;
245
+ const skinGuids = resolveSkinGuids(
246
+ payload.skinGuids,
247
+ refs
248
+ );
249
+ if (!skinGuids.ok) return skinGuids;
250
+ return {
251
+ ok: true,
252
+ value: {
253
+ kind: "scene",
254
+ entities,
255
+ ...mounts.value === void 0 ? {} : { mounts: mounts.value },
256
+ ...skinGuids.value === void 0 ? {} : { skinGuids: skinGuids.value }
257
+ }
258
+ };
259
+ }
260
+ var sceneAssetDecoder = {
261
+ async decode({ envelope }) {
262
+ const payload = envelope.payload;
263
+ if (payload.kind !== "scene" || !Array.isArray(payload.entities)) {
264
+ return invalidScene(envelope.guid, "scene payload is missing entities");
265
+ }
266
+ const resolved = resolveSceneWireRefs(payload, envelope.refs);
267
+ if (!resolved.ok) return invalidScene(envelope.guid, resolved.reason);
268
+ sceneWireRefs.set(resolved.value, Object.freeze([...envelope.refs]));
269
+ return ok(resolved.value);
270
+ }
271
+ };
272
+ var sceneAssetContribution = {
273
+ kind: sceneAssetKind,
274
+ decoder: sceneAssetDecoder,
275
+ consumer: "Scene"
276
+ };
277
+
278
+ // src/components/children.ts
279
+ import { defineRelationship } from "@forgeax/engine-ecs";
280
+ var { source: ChildOf, target: Children } = defineRelationship({
281
+ sourceName: "ChildOf",
282
+ sourceField: "parent",
283
+ targetName: "Children",
284
+ targetField: "entities",
285
+ exclusive: true,
286
+ linkedSpawn: true
287
+ });
288
+
289
+ // src/collect-subtree.ts
290
+ function collectSubtree(world, spawnRoot, visited) {
291
+ if (visited === void 0) visited = /* @__PURE__ */ new Set();
292
+ if (visited.has(spawnRoot)) return visited;
293
+ const queue = [spawnRoot];
294
+ visited.add(spawnRoot);
295
+ while (queue.length > 0) {
296
+ const current = queue.shift();
297
+ const children = world.get(current, Children);
298
+ if (!children.ok) continue;
299
+ const entities = children.value.entities;
300
+ for (let index = 0; index < entities.length; index += 1) {
301
+ const child = entities[index];
302
+ if (visited.has(child)) continue;
303
+ visited.add(child);
304
+ queue.push(child);
305
+ }
306
+ }
307
+ return visited;
308
+ }
309
+
310
+ // src/components/morph-weights.ts
311
+ import { defineComponent } from "@forgeax/engine-ecs";
312
+ var MorphWeights = defineComponent("MorphWeights", {
313
+ weights: { type: "array<f32>" }
314
+ });
315
+
316
+ // src/components/name.ts
317
+ import { defineComponent as defineComponent2 } from "@forgeax/engine-ecs";
318
+ var Name = defineComponent2("Name", { value: { type: "string" } });
319
+
320
+ // src/components/transform.ts
321
+ import { defineComponent as defineComponent3 } from "@forgeax/engine-ecs";
322
+ var IDENTITY_MAT4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
323
+ var Transform = defineComponent3("Transform", {
324
+ pos: { type: "array<f32, 3>", default: new Float32Array([0, 0, 0]) },
325
+ // Component order [x, y, z, w] end to end (glTF-aligned; E6).
326
+ quat: { type: "array<f32, 4>", default: new Float32Array([0, 0, 0, 1]) },
327
+ scale: { type: "array<f32, 3>", default: new Float32Array([1, 1, 1]) },
328
+ // `world` is field-level transient (D-5): scene collect skips it. The resolved
329
+ // world mat4 is derived by the propagate kernel from the persisted local TRS
330
+ // each frame, so serializing it would store reconstructable data (SSOT: local
331
+ // TRS). Round-trip re-derives an equivalent world on the first propagate pass.
332
+ world: { type: "array<f32, 16>", default: IDENTITY_MAT4, transient: true }
333
+ });
334
+
335
+ // src/errors.ts
336
+ import { ComponentNotDefinedError } from "@forgeax/engine-ecs/projection";
337
+ var SceneError = class extends Error {
338
+ code;
339
+ expected;
340
+ hint;
341
+ detail;
342
+ constructor(args) {
343
+ super(`[SceneError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);
344
+ this.name = "SceneError";
345
+ this.code = args.code;
346
+ this.expected = args.expected;
347
+ this.hint = args.hint;
348
+ this.detail = args.detail;
349
+ }
350
+ };
351
+
352
+ // src/instances/collect-profile.ts
353
+ var SCENE_COLLECT_PROFILE = Object.freeze({
354
+ includeComponent: (_componentName, transient) => !transient,
355
+ includeField: (_componentName, _fieldName, transient) => !transient
356
+ });
357
+
358
+ // src/instances/externalization.ts
359
+ function sharedKind(type) {
360
+ if (type?.startsWith("shared<")) return "one";
361
+ if (type?.startsWith("array<shared<")) return "many";
362
+ return void 0;
363
+ }
364
+ function overrideGuids(override, resolveSchema) {
365
+ const schema = resolveSchema(override.comp);
366
+ 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) : [];
367
+ return values.flatMap(([field, value]) => {
368
+ const kind = sharedKind(schema?.[field]);
369
+ if (kind === "one" && typeof value === "string") return [{ field, guid: value }];
370
+ if (kind === "many" && Array.isArray(value)) {
371
+ return value.flatMap((item) => typeof item === "string" ? [{ field, guid: item }] : []);
372
+ }
373
+ return [];
374
+ });
375
+ }
376
+ function externalizeSceneAsset(scene, resolveSchema) {
377
+ const refs = [];
378
+ const indexByGuid = /* @__PURE__ */ new Map();
379
+ const addRef = (guid, sourceField, sceneEntityId) => {
380
+ const prior = indexByGuid.get(guid);
381
+ if (prior !== void 0) return prior;
382
+ const index = refs.length;
383
+ refs.push({ guid, sourceField, ...sceneEntityId === void 0 ? {} : { sceneEntityId } });
384
+ indexByGuid.set(guid, index);
385
+ return index;
386
+ };
387
+ const entities = scene.entities.map((entity) => {
388
+ const components = {};
389
+ for (const componentName of Object.keys(entity.components)) {
390
+ const schema = resolveSchema(componentName);
391
+ const source = entity.components[componentName];
392
+ if (source === void 0) continue;
393
+ const fields = {};
394
+ for (const fieldName of Object.keys(source)) {
395
+ const value = source[fieldName];
396
+ if (value === void 0) continue;
397
+ const kind = sharedKind(schema?.[fieldName]);
398
+ if (kind === "one" && typeof value === "string") {
399
+ fields[fieldName] = addRef(value, { componentName, fieldName }, entity.localId);
400
+ } else if (kind === "many" && Array.isArray(value)) {
401
+ fields[fieldName] = value.map(
402
+ (item, arrayIndex) => typeof item === "string" ? addRef(item, { componentName, fieldName, arrayIndex }, entity.localId) : item
403
+ );
404
+ } else {
405
+ fields[fieldName] = value;
406
+ }
407
+ }
408
+ if (Object.keys(fields).length > 0 || Object.keys(schema ?? {}).length === 0) {
409
+ components[componentName] = fields;
410
+ }
411
+ }
412
+ return { localId: entity.localId, components };
413
+ });
414
+ const mounts = scene.mounts?.map((mount) => {
415
+ const source = typeof mount.source === "string" ? addRef(
416
+ mount.source,
417
+ { componentName: "SceneInstance", fieldName: "source" },
418
+ mount.localId
419
+ ) : mount.source;
420
+ for (const { field, guid } of (mount.overrides ?? []).flatMap(
421
+ (override) => overrideGuids(override, resolveSchema)
422
+ )) {
423
+ addRef(guid, { componentName: "SceneInstance", fieldName: `overrides.${field}` });
424
+ }
425
+ return {
426
+ localId: mount.localId,
427
+ source,
428
+ memberFirst: mount.memberFirst,
429
+ memberCount: mount.memberCount,
430
+ ...mount.parent === void 0 ? {} : { parent: mount.parent },
431
+ ...mount.publicationFence === void 0 ? {} : { publicationFence: mount.publicationFence },
432
+ ...mount.overrides === void 0 ? {} : { overrides: mount.overrides.map((item) => ({ ...item })) }
433
+ };
434
+ });
435
+ for (const [arrayIndex, guid] of (scene.skinGuids ?? []).entries()) {
436
+ if (typeof guid !== "string") return err({ field: "skinGuids", value: guid });
437
+ addRef(guid, { componentName: "<scene>", fieldName: "skinGuids", arrayIndex });
438
+ }
439
+ return ok({
440
+ payload: {
441
+ entities,
442
+ ...mounts === void 0 || mounts.length === 0 ? {} : { mounts },
443
+ ...scene.skinGuids === void 0 ? {} : { skinGuids: scene.skinGuids.map((guid) => indexByGuid.get(guid)) }
444
+ },
445
+ refs
446
+ });
447
+ }
448
+
449
+ // src/instances/scene-instances.ts
450
+ import {
451
+ ENTITY_NULL_RAW
452
+ } from "@forgeax/engine-ecs";
453
+ import { classifyEntityField, remapEntityFieldValue } from "@forgeax/engine-ecs/externalization";
454
+ import { componentSchema } from "@forgeax/engine-ecs/internal";
455
+ import { fillComponentDefaults, StaleEntityError } from "@forgeax/engine-ecs/projection";
456
+ var entityIndex = (entity) => entity & 16777215;
457
+ var entityGeneration = (entity) => entity >>> 24 & 255;
458
+ var sceneWorldStates = /* @__PURE__ */ new WeakMap();
459
+ function sceneWorldState(world) {
460
+ const current = sceneWorldStates.get(world);
461
+ if (current !== void 0) return current;
462
+ const created = { resolver: null, statePayloads: /* @__PURE__ */ new Map() };
463
+ sceneWorldStates.set(world, created);
464
+ return created;
465
+ }
466
+ function worldSetSceneAssetResolver(world, resolver) {
467
+ sceneWorldState(world).resolver = resolver;
468
+ }
469
+ function worldGetSceneAssetResolver(world) {
470
+ return sceneWorldState(world).resolver;
471
+ }
472
+ function worldInstantiateScene(world, handle, parent) {
473
+ const stack = /* @__PURE__ */ new Set();
474
+ const diagnostics = [];
475
+ const r = worldInstantiateSceneRec(world, handle, parent, stack, diagnostics);
476
+ if (!r.ok) return r;
477
+ return ok({ root: r.value, diagnostics });
478
+ }
479
+ function worldInstantiateScenePayload(world, asset, parent) {
480
+ const handle = world.allocSharedRef("SceneAsset", asset);
481
+ try {
482
+ return worldInstantiateScene(world, handle, parent);
483
+ } finally {
484
+ world.sharedRefs.release(handle);
485
+ }
486
+ }
487
+ function worldInstantiateSceneFlat(world, handle) {
488
+ const stack = /* @__PURE__ */ new Set();
489
+ const diagnostics = [];
490
+ const handleKey = unwrapHandle(handle);
491
+ const resolved = worldResolveSceneAsset(world, handle);
492
+ if (!resolved.ok) return resolved;
493
+ stack.add(handleKey);
494
+ let r;
495
+ try {
496
+ r = worldInstantiateSceneAssetFlat(world, handle, resolved.value, stack, diagnostics);
497
+ } finally {
498
+ stack.delete(handleKey);
499
+ }
500
+ if (!r.ok) return r;
501
+ return ok({ ...r.value, diagnostics });
502
+ }
503
+ function worldInstantiateSceneRec(world, handle, parent, stack, diagnostics) {
504
+ const handleKey = unwrapHandle(handle);
505
+ if (stack.has(handleKey)) {
506
+ const cycleArr = [];
507
+ for (const k of stack) cycleArr.push(String(k));
508
+ cycleArr.push(String(handleKey));
509
+ const detail = {
510
+ code: "pack-cyclic-reference",
511
+ kind: "mount-asset",
512
+ cycle: cycleArr
513
+ };
514
+ return err({
515
+ code: "pack-cyclic-reference",
516
+ expected: "acyclic SceneAsset mount graph",
517
+ hint: PACK_ERROR_HINTS["pack-cyclic-reference"],
518
+ detail
519
+ });
520
+ }
521
+ const resolved = worldResolveSceneAsset(world, handle);
522
+ if (!resolved.ok) return resolved;
523
+ const asset = resolved.value;
524
+ stack.add(handleKey);
525
+ try {
526
+ return worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnostics);
527
+ } finally {
528
+ stack.delete(handleKey);
529
+ }
530
+ }
531
+ function worldResolveSceneAsset(world, handle) {
532
+ const r = world.sharedRefs.resolve(handle);
533
+ if (!r.ok) {
534
+ return err(r.error);
535
+ }
536
+ return ok(r.value);
537
+ }
538
+ function worldSpawnSceneMembers(world, handle, asset, stack, diagnostics) {
539
+ const sceneInstanceToken = world.components.resolve("SceneInstance");
540
+ if (sceneInstanceToken === void 0) {
541
+ return err(new ComponentNotDefinedError("SceneInstance"));
542
+ }
543
+ const childOfToken = world.components.resolve("ChildOf");
544
+ const ownEntities = asset.entities;
545
+ const ownMounts = asset.mounts ?? [];
546
+ const memberSum = ownMounts.reduce((s, m) => s + m.memberCount, 0);
547
+ const countBaseline = ownEntities.length + ownMounts.length + memberSum;
548
+ let maxLocalId = ownEntities.reduce((m, e) => Math.max(m, e.localId), -1);
549
+ for (const mount of ownMounts) {
550
+ maxLocalId = Math.max(maxLocalId, mount.localId);
551
+ const last = mount.memberFirst + mount.memberCount - 1;
552
+ maxLocalId = Math.max(maxLocalId, last);
553
+ }
554
+ const totalSlots = Math.max(countBaseline, maxLocalId + 1);
555
+ {
556
+ const claims = /* @__PURE__ */ new Map();
557
+ const overlapLids = /* @__PURE__ */ new Set();
558
+ const overlapSources = [];
559
+ const claim = (lid, src) => {
560
+ const prior = claims.get(lid);
561
+ if (prior !== void 0) {
562
+ if (!overlapLids.has(lid)) {
563
+ overlapLids.add(lid);
564
+ overlapSources.push(prior);
565
+ overlapSources.push(src);
566
+ } else {
567
+ overlapSources.push(src);
568
+ }
569
+ return;
570
+ }
571
+ claims.set(lid, src);
572
+ };
573
+ for (const ent of ownEntities) {
574
+ claim(ent.localId, `entities[${ent.localId}]`);
575
+ }
576
+ for (const mount of ownMounts) {
577
+ const mLid = mount.localId;
578
+ claim(mLid, `mount[${mLid}]`);
579
+ const first = mount.memberFirst;
580
+ for (let k = 0; k < mount.memberCount; k += 1) {
581
+ claim(first + k, `mount[${mLid}].member[${k}]`);
582
+ }
583
+ }
584
+ if (overlapLids.size > 0) {
585
+ const overlapping = Array.from(overlapLids).sort((a, b) => a - b);
586
+ return err({
587
+ code: "pack-mount-localid-overlap",
588
+ expected: "each LocalEntityId claimed by exactly one entity or mount slot",
589
+ hint: PACK_ERROR_HINTS["pack-mount-localid-overlap"],
590
+ detail: {
591
+ code: "pack-mount-localid-overlap",
592
+ overlapping,
593
+ sources: overlapSources
594
+ }
595
+ });
596
+ }
597
+ }
598
+ const mapping = new Uint32Array(totalSlots).fill(ENTITY_NULL_RAW);
599
+ const entityToLocalId = /* @__PURE__ */ new Map();
600
+ const rootEntities = [];
601
+ const mountEntities = [];
602
+ const mountEntitiesNeedingRootParent = [];
603
+ const mountEntitiesNeedingDeferredParent = [];
604
+ const mountInstances = [];
605
+ for (const mount of ownMounts) {
606
+ const overrideValidationRes = worldValidateMountOverrides(world, mount);
607
+ if (!overrideValidationRes.ok) {
608
+ return overrideValidationRes;
609
+ }
610
+ const mountLid = mount.localId;
611
+ const mountSpawnRes = worldSpawnMountEntity(world, mount, mapping, diagnostics);
612
+ if (!mountSpawnRes.ok) return mountSpawnRes;
613
+ const mountEntity = mountSpawnRes.value;
614
+ mountEntities.push(mountEntity);
615
+ mapping[mountLid] = mountEntity;
616
+ const childHandleRes = worldResolveMountSource(world, mount.source, handle);
617
+ if (!childHandleRes.ok) return childHandleRes;
618
+ const childHandle = childHandleRes.value;
619
+ const childRes = worldInstantiateSceneRec(world, childHandle, mountEntity, stack, diagnostics);
620
+ if (!childRes.ok) return childRes;
621
+ const childInstRes = world.get(childRes.value, sceneInstanceToken);
622
+ if (!childInstRes.ok) return childInstRes;
623
+ const childMapping = childInstRes.value.mapping;
624
+ mountInstances.push({ mount, root: childRes.value, mapping: childMapping });
625
+ if (childMapping.length !== mount.memberCount) {
626
+ return err({
627
+ code: "pack-mount-count-mismatch",
628
+ expected: "mount.memberCount === child SceneAsset totalSlots",
629
+ hint: PACK_ERROR_HINTS["pack-mount-count-mismatch"],
630
+ detail: {
631
+ code: "pack-mount-count-mismatch",
632
+ mountLocalId: mountLid,
633
+ declared: mount.memberCount,
634
+ actual: childMapping.length
635
+ }
636
+ });
637
+ }
638
+ const window = mount.memberCount;
639
+ for (let k = 0; k < window; k += 1) {
640
+ mapping[mount.memberFirst + k] = childMapping[k] ?? ENTITY_NULL_RAW;
641
+ }
642
+ if (childOfToken !== void 0) {
643
+ if (mount.parent !== void 0) {
644
+ const parentSlot = mount.parent;
645
+ const parentEntity = mapping[parentSlot];
646
+ if (parentEntity !== void 0 && parentEntity !== ENTITY_NULL_RAW) {
647
+ const r = world.addComponent(mountEntity, {
648
+ component: childOfToken,
649
+ data: { parent: parentEntity }
650
+ });
651
+ if (!r.ok) {
652
+ const set = world.set(mountEntity, childOfToken, {
653
+ parent: parentEntity
654
+ });
655
+ if (!set.ok) return set;
656
+ }
657
+ } else {
658
+ mountEntitiesNeedingDeferredParent.push([mountEntity, parentSlot]);
659
+ }
660
+ } else {
661
+ mountEntitiesNeedingRootParent.push(mountEntity);
662
+ }
663
+ }
664
+ }
665
+ const order = sceneTopoSort(ownEntities);
666
+ for (const idx of order) {
667
+ const node = ownEntities[idx];
668
+ if (node === void 0) continue;
669
+ const lid = node.localId;
670
+ const compDataRes = worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics);
671
+ if (!compDataRes.ok) return compDataRes;
672
+ const sp = world.spawn(
673
+ ...compDataRes.value
674
+ );
675
+ if (!sp.ok) return sp;
676
+ const e = sp.value;
677
+ mapping[lid] = e;
678
+ entityToLocalId.set(e, lid);
679
+ if (node.components.ChildOf === void 0) {
680
+ rootEntities.push(e);
681
+ }
682
+ }
683
+ if (childOfToken !== void 0) {
684
+ for (const [mountEntity, parentSlot] of mountEntitiesNeedingDeferredParent) {
685
+ const parentEntity = mapping[parentSlot];
686
+ if (parentEntity === void 0 || parentEntity === ENTITY_NULL_RAW) continue;
687
+ const set = world.set(mountEntity, childOfToken, { parent: parentEntity });
688
+ if (!set.ok) {
689
+ const r = world.addComponent(mountEntity, {
690
+ component: childOfToken,
691
+ data: { parent: parentEntity }
692
+ });
693
+ if (!r.ok) return r;
694
+ }
695
+ }
696
+ }
697
+ return ok({
698
+ mapping,
699
+ entityToLocalId,
700
+ rootEntities,
701
+ mountEntitiesNeedingRootParent,
702
+ mountEntities,
703
+ mountInstances,
704
+ totalSlots
705
+ });
706
+ }
707
+ function worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnostics) {
708
+ const sceneInstanceToken = world.components.resolve("SceneInstance");
709
+ if (sceneInstanceToken === void 0) {
710
+ return err(new ComponentNotDefinedError("SceneInstance"));
711
+ }
712
+ const childOfToken = world.components.resolve("ChildOf");
713
+ const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);
714
+ if (!membersRes.ok) return membersRes;
715
+ const { mapping, entityToLocalId, rootEntities, mountEntitiesNeedingRootParent, totalSlots } = membersRes.value;
716
+ const { mountInstances } = membersRes.value;
717
+ const ownMounts = asset.mounts ?? [];
718
+ let stateRef;
719
+ stateRef = world.allocUniqueRef("SceneInstanceState", null, () => {
720
+ sceneWorldState(world).statePayloads.delete(Number(stateRef));
721
+ });
722
+ const mappingPlain = Array.from(mapping);
723
+ const rootComponents = [
724
+ {
725
+ component: sceneInstanceToken,
726
+ data: {
727
+ source: handle,
728
+ mapping: mappingPlain,
729
+ state: stateRef
730
+ }
731
+ }
732
+ ];
733
+ const transformToken = world.components.resolve("Transform");
734
+ if (transformToken !== void 0) {
735
+ rootComponents.push({
736
+ component: transformToken,
737
+ data: {}
738
+ });
739
+ }
740
+ const rootSpawn = world.spawn(
741
+ ...rootComponents
742
+ );
743
+ if (!rootSpawn.ok) {
744
+ return rootSpawn;
745
+ }
746
+ const rootEntity = rootSpawn.value;
747
+ const overrides = /* @__PURE__ */ new Map();
748
+ for (const mount of ownMounts) {
749
+ for (const ov of mount.overrides ?? []) {
750
+ const lid = ov.localId;
751
+ let fieldMap = overrides.get(lid);
752
+ if (fieldMap === void 0) {
753
+ fieldMap = /* @__PURE__ */ new Map();
754
+ overrides.set(lid, fieldMap);
755
+ }
756
+ fieldMap.set(mountOverrideStateKey(ov), ov);
757
+ const memberEntityRaw = mapping[lid];
758
+ if (memberEntityRaw !== void 0 && memberEntityRaw !== ENTITY_NULL_RAW) {
759
+ const memberEntity = memberEntityRaw;
760
+ const applyRes = worldApplyMountOverride(world, memberEntity, ov);
761
+ if (!applyRes.ok) {
762
+ return applyRes;
763
+ }
764
+ }
765
+ }
766
+ }
767
+ const detached = /* @__PURE__ */ new Set();
768
+ const state = {
769
+ source: handle,
770
+ entityToLocalId,
771
+ detachedLocalIds: detached,
772
+ // Convert overrides Map<LocalEntityId, Map<string, MountOverride>>
773
+ // into Map<LocalEntityId, Map<string, SceneInstanceOverrideRecord>>
774
+ overrides: worldMountOverridesToStateMap(overrides),
775
+ rootEntities,
776
+ mountRoots: mountInstances.map(({ root }) => root),
777
+ totalSlots,
778
+ mountTimeOverrides: ownMounts.flatMap((m) => m.overrides ?? [])
779
+ };
780
+ worldSetUniqueRefPayload(world, stateRef, state);
781
+ if (childOfToken !== void 0) {
782
+ for (const rootE of rootEntities) {
783
+ const has = world.get(rootE, childOfToken);
784
+ if (!has.ok) {
785
+ const r = world.addComponent(rootE, {
786
+ component: childOfToken,
787
+ data: { parent: rootEntity }
788
+ });
789
+ if (!r.ok) return r;
790
+ }
791
+ }
792
+ for (const mountE of mountEntitiesNeedingRootParent) {
793
+ const set = world.set(mountE, childOfToken, { parent: rootEntity });
794
+ if (!set.ok) {
795
+ const r = world.addComponent(mountE, {
796
+ component: childOfToken,
797
+ data: { parent: rootEntity }
798
+ });
799
+ if (!r.ok) return r;
800
+ }
801
+ }
802
+ if (parent !== void 0) {
803
+ const r = world.addComponent(rootEntity, {
804
+ component: childOfToken,
805
+ data: { parent }
806
+ });
807
+ if (!r.ok) return r;
808
+ }
809
+ }
810
+ return ok(rootEntity);
811
+ }
812
+ function worldInstantiateSceneAssetFlat(world, handle, asset, stack, diagnostics) {
813
+ const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);
814
+ if (!membersRes.ok) return membersRes;
815
+ const { rootEntities, mountEntitiesNeedingRootParent, mountEntities, mountInstances } = membersRes.value;
816
+ const childOfToken = world.components.resolve("ChildOf");
817
+ for (const { mount, root, mapping: childMapping } of mountInstances) {
818
+ const childStateRes = worldGetSceneInstanceState(world, root);
819
+ if (!childStateRes.ok) return childStateRes;
820
+ for (const ov of mount.overrides ?? []) {
821
+ const childLocalId = ov.localId - mount.memberFirst;
822
+ const memberEntityRaw = childMapping[childLocalId];
823
+ if (memberEntityRaw === void 0 || memberEntityRaw === ENTITY_NULL_RAW) continue;
824
+ const memberEntity = memberEntityRaw;
825
+ const applyRes = worldApplyMountOverride(world, memberEntity, ov);
826
+ if (!applyRes.ok) {
827
+ return applyRes;
828
+ }
829
+ let fieldMap = childStateRes.value.overrides.get(childLocalId);
830
+ if (fieldMap === void 0) {
831
+ fieldMap = /* @__PURE__ */ new Map();
832
+ childStateRes.value.overrides.set(childLocalId, fieldMap);
833
+ }
834
+ fieldMap.set(mountOverrideStateKey(ov), {
835
+ comp: ov.comp,
836
+ ...ov.field === void 0 ? {} : { field: ov.field },
837
+ value: ov.value
838
+ });
839
+ }
840
+ }
841
+ if (childOfToken !== void 0) {
842
+ for (const mountE of mountEntitiesNeedingRootParent) {
843
+ const co = world.get(mountE, childOfToken);
844
+ if (co.ok && co.value.parent === ENTITY_NULL_RAW) {
845
+ world.removeComponent(mountE, childOfToken);
846
+ }
847
+ }
848
+ }
849
+ return ok({ roots: [...rootEntities, ...mountEntitiesNeedingRootParent], mountEntities });
850
+ }
851
+ function worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics) {
852
+ const out = [];
853
+ const nodeLocalId = node.localId;
854
+ for (const compName of Object.keys(node.components)) {
855
+ const token = world.components.resolve(compName);
856
+ if (token === void 0) {
857
+ return err(new ComponentNotDefinedError(compName));
858
+ }
859
+ const raw = node.components[compName] ?? {};
860
+ const schema = componentSchema(token);
861
+ const remappedRaw = {};
862
+ for (const fieldName of Object.keys(raw)) {
863
+ const fieldType = schema[fieldName];
864
+ if (fieldType === void 0) {
865
+ diagnostics.push({ component: compName, field: fieldName, localId: nodeLocalId });
866
+ continue;
867
+ }
868
+ const value = raw[fieldName];
869
+ const kind = classifyEntityField(token, fieldName);
870
+ if (kind !== null) {
871
+ const sceneRemap = (localId) => {
872
+ if (localId < 0 || localId >= mapping.length) return ENTITY_NULL_RAW;
873
+ const live = mapping[localId];
874
+ return live === void 0 || live === ENTITY_NULL_RAW ? ENTITY_NULL_RAW : live;
875
+ };
876
+ remappedRaw[fieldName] = remapEntityFieldValue(value, kind, sceneRemap);
877
+ } else {
878
+ remappedRaw[fieldName] = value;
879
+ }
880
+ }
881
+ const filled = fillComponentDefaults(token, remappedRaw);
882
+ out.push({ component: token, data: filled });
883
+ }
884
+ return ok(out);
885
+ }
886
+ function worldApplyMountOverride(world, member, ov) {
887
+ const ovToken = world.components.resolve(ov.comp);
888
+ if (ovToken === void 0) return ok(void 0);
889
+ if (ov.field !== void 0) {
890
+ return world.set(member, ovToken, { [ov.field]: ov.value });
891
+ }
892
+ const rawValue = ov.value ?? {};
893
+ const filled = fillComponentDefaults(ovToken, rawValue);
894
+ const has = world.get(member, ovToken);
895
+ if (has.ok) {
896
+ return world.set(member, ovToken, filled);
897
+ }
898
+ return world.addComponent(member, { component: ovToken, data: filled });
899
+ }
900
+ function worldValidateMountOverrides(world, mount) {
901
+ const overrides = mount.overrides;
902
+ if (overrides === void 0) return ok(void 0);
903
+ const memberFirst = mount.memberFirst;
904
+ const memberCount = mount.memberCount;
905
+ const memberLast = memberFirst + memberCount;
906
+ const mountLid = mount.localId;
907
+ for (const ov of overrides) {
908
+ const ovLid = ov.localId;
909
+ if (ovLid < memberFirst || ovLid >= memberLast) {
910
+ return err({
911
+ code: "pack-mount-override-localid-out-of-range",
912
+ expected: `override.localId in [${memberFirst}, ${memberLast})`,
913
+ hint: PACK_ERROR_HINTS["pack-mount-override-localid-out-of-range"],
914
+ detail: {
915
+ code: "pack-mount-override-localid-out-of-range",
916
+ overrideLocalId: ovLid,
917
+ mountLocalId: mountLid,
918
+ memberCount
919
+ }
920
+ });
921
+ }
922
+ const ovToken = world.components.resolve(ov.comp);
923
+ if (ov.field !== void 0) {
924
+ if (ovToken !== void 0) {
925
+ const schema = componentSchema(ovToken);
926
+ if (!(ov.field in schema)) {
927
+ return err({
928
+ code: "pack-mount-override-unknown-field",
929
+ expected: `override.field defined on component '${ov.comp}'`,
930
+ hint: PACK_ERROR_HINTS["pack-mount-override-unknown-field"],
931
+ detail: {
932
+ code: "pack-mount-override-unknown-field",
933
+ comp: ov.comp,
934
+ field: ov.field,
935
+ mountLocalId: mountLid
936
+ }
937
+ });
938
+ }
939
+ }
940
+ } else {
941
+ if (ovToken === void 0) {
942
+ return err(new ComponentNotDefinedError(ov.comp));
943
+ }
944
+ const schema = componentSchema(ovToken);
945
+ const valueMap = ov.value ?? {};
946
+ for (const key of Object.keys(valueMap)) {
947
+ if (!(key in schema)) {
948
+ return err({
949
+ code: "pack-mount-override-unknown-field",
950
+ expected: `override.value keys defined on component '${ov.comp}'`,
951
+ hint: PACK_ERROR_HINTS["pack-mount-override-unknown-field"],
952
+ detail: {
953
+ code: "pack-mount-override-unknown-field",
954
+ comp: ov.comp,
955
+ field: key,
956
+ mountLocalId: mountLid
957
+ }
958
+ });
959
+ }
960
+ }
961
+ }
962
+ }
963
+ return ok(void 0);
964
+ }
965
+ function worldSpawnMountEntity(world, mount, mapping, diagnostics) {
966
+ const fakeNode = {
967
+ localId: mount.localId,
968
+ components: mount.components ?? {}
969
+ };
970
+ const cdRes = worldBuildSceneEntityComponentDatas(world, fakeNode, mapping, diagnostics);
971
+ if (!cdRes.ok) return cdRes;
972
+ const transformToken = world.components.resolve("Transform");
973
+ if (transformToken !== void 0) {
974
+ const hasTransform = cdRes.value.some((c) => c.component === transformToken);
975
+ if (!hasTransform) {
976
+ cdRes.value.push({ component: transformToken, data: {} });
977
+ }
978
+ }
979
+ if (cdRes.value.length === 0) {
980
+ const childOfToken = world.components.resolve("ChildOf");
981
+ if (childOfToken === void 0) {
982
+ return err(new ComponentNotDefinedError("ChildOf"));
983
+ }
984
+ cdRes.value.push({
985
+ component: childOfToken,
986
+ data: { parent: ENTITY_NULL_RAW }
987
+ });
988
+ }
989
+ return world.spawn(...cdRes.value);
990
+ }
991
+ function worldResolveMountSource(world, source, parentHandle) {
992
+ const resolver = worldGetSceneAssetResolver(world);
993
+ if (resolver === null) {
994
+ return err({
995
+ code: "stale-entity",
996
+ expected: "wired SceneAssetResolver (auto-wired by engine.assets.instantiate)",
997
+ hint: "engine.assets.instantiate sugar wires this for you; call worldSetSceneAssetResolver before nested scene expansion.",
998
+ detail: { entity: 0, slot: 0, generation: 0 }
999
+ });
1000
+ }
1001
+ const r = resolver(source, parentHandle);
1002
+ if (!r.ok) {
1003
+ return err(r.error);
1004
+ }
1005
+ return ok(r.value);
1006
+ }
1007
+ function worldMountOverridesToStateMap(src) {
1008
+ const out = /* @__PURE__ */ new Map();
1009
+ for (const [lid, fields] of src) {
1010
+ const m = /* @__PURE__ */ new Map();
1011
+ for (const [k, v] of fields) {
1012
+ m.set(k, {
1013
+ comp: v.comp,
1014
+ value: v.value,
1015
+ ...v.field !== void 0 ? { field: v.field } : {}
1016
+ });
1017
+ }
1018
+ out.set(lid, m);
1019
+ }
1020
+ return out;
1021
+ }
1022
+ function worldSetUniqueRefPayload(world, handle, payload) {
1023
+ sceneWorldState(world).statePayloads.set(Number(handle), payload);
1024
+ }
1025
+ function worldResolveSceneInstanceStatePayload(world, root) {
1026
+ const sceneInstanceToken = world.components.resolve("SceneInstance");
1027
+ if (sceneInstanceToken === void 0) {
1028
+ return err(new ComponentNotDefinedError("SceneInstance"));
1029
+ }
1030
+ const r = world.get(root, sceneInstanceToken);
1031
+ if (!r.ok) return r;
1032
+ const stateRefRaw = r.value.state;
1033
+ const stateRefHandle = toUnique(stateRefRaw);
1034
+ const payload = sceneWorldState(world).statePayloads.get(Number(stateRefHandle));
1035
+ if (payload === void 0) {
1036
+ return err(
1037
+ new StaleEntityError(root, entityIndex(root), entityGeneration(root), {
1038
+ operation: "resolveSceneInstanceState",
1039
+ component: "SceneInstance",
1040
+ expectedGeneration: entityGeneration(root),
1041
+ actualGeneration: entityGeneration(root)
1042
+ })
1043
+ );
1044
+ }
1045
+ return ok(payload);
1046
+ }
1047
+ function worldGetSceneInstanceState(world, root) {
1048
+ return worldResolveSceneInstanceStatePayload(world, root);
1049
+ }
1050
+ function worldDespawnScene(world, root, opts) {
1051
+ const dRes = worldDespawnDescendants(world, root, opts);
1052
+ if (!dRes.ok) return dRes;
1053
+ const drop = world.despawn(root);
1054
+ if (!drop.ok) return drop;
1055
+ return ok(dRes.value + 1);
1056
+ }
1057
+ function worldDespawnDescendants(world, root, opts) {
1058
+ let detached = null;
1059
+ let entityToLocalId = null;
1060
+ if (opts?.keepDetached === true) {
1061
+ const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1062
+ if (stateRes.ok) {
1063
+ detached = stateRes.value.detachedLocalIds;
1064
+ entityToLocalId = stateRes.value.entityToLocalId;
1065
+ }
1066
+ }
1067
+ let count = 0;
1068
+ const list = [];
1069
+ const seen = /* @__PURE__ */ new Set();
1070
+ const collect = (anchor) => {
1071
+ for (const e of world.iterDescendants(anchor)) {
1072
+ const raw = e;
1073
+ if (!seen.has(raw)) {
1074
+ seen.add(raw);
1075
+ list.push(e);
1076
+ }
1077
+ }
1078
+ const stateRes = worldResolveSceneInstanceStatePayload(world, anchor);
1079
+ if (!stateRes.ok) return;
1080
+ for (const e of stateRes.value.entityToLocalId.keys()) {
1081
+ const raw = e;
1082
+ if (!seen.has(raw)) {
1083
+ seen.add(raw);
1084
+ list.push(e);
1085
+ }
1086
+ }
1087
+ for (const nestedRoot of stateRes.value.mountRoots) {
1088
+ const raw = nestedRoot;
1089
+ if (seen.has(raw)) continue;
1090
+ seen.add(raw);
1091
+ list.push(nestedRoot);
1092
+ collect(nestedRoot);
1093
+ }
1094
+ };
1095
+ collect(root);
1096
+ const childOfToken = world.components.resolve("ChildOf");
1097
+ const owned = new Set(list.map((entity) => Number(entity)));
1098
+ const ownedDepth = (entity) => {
1099
+ if (childOfToken === void 0) return 0;
1100
+ let current = entity;
1101
+ let depth = 0;
1102
+ const visited = /* @__PURE__ */ new Set();
1103
+ while (!visited.has(Number(current))) {
1104
+ visited.add(Number(current));
1105
+ const parentRes = world.get(current, childOfToken);
1106
+ if (!parentRes.ok) break;
1107
+ const parent = parentRes.value.parent;
1108
+ if (!owned.has(Number(parent))) break;
1109
+ depth += 1;
1110
+ current = parent;
1111
+ }
1112
+ return depth;
1113
+ };
1114
+ list.sort((a, b) => ownedDepth(b) - ownedDepth(a));
1115
+ for (const e of list) {
1116
+ if (detached !== null) {
1117
+ const lid = entityToLocalId?.get(e);
1118
+ if (lid !== void 0 && detached.has(lid)) {
1119
+ if (childOfToken !== void 0) {
1120
+ world.removeComponent(e, childOfToken);
1121
+ }
1122
+ continue;
1123
+ }
1124
+ }
1125
+ const r = world.despawn(e);
1126
+ if (!r.ok) {
1127
+ if (r.error.code === "stale-entity") continue;
1128
+ return r;
1129
+ }
1130
+ count += 1;
1131
+ }
1132
+ return ok(count);
1133
+ }
1134
+ function worldSetSceneOverride(world, root, member, component, field, value) {
1135
+ const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1136
+ if (!stateRes.ok) return stateRes;
1137
+ const state = stateRes.value;
1138
+ const lid = state.entityToLocalId.get(member);
1139
+ if (lid === void 0) {
1140
+ return err(
1141
+ new StaleEntityError(
1142
+ member,
1143
+ entityIndex(member),
1144
+ entityGeneration(member),
1145
+ {
1146
+ operation: "setSceneOverride",
1147
+ component: component.name,
1148
+ expectedGeneration: entityGeneration(member),
1149
+ actualGeneration: entityGeneration(member)
1150
+ }
1151
+ )
1152
+ );
1153
+ }
1154
+ const schemaType = componentSchema(component)[field];
1155
+ if (schemaType !== void 0 && isPrimitiveScalarFieldType(schemaType)) {
1156
+ const expectJsType = primitiveJsType(schemaType);
1157
+ const actualJsType = typeof value;
1158
+ if (expectJsType !== actualJsType) {
1159
+ return err({
1160
+ code: "scene-override-type-mismatch",
1161
+ expected: `value typeof === ${expectJsType}`,
1162
+ hint: `setSceneOverride(${component.name}.${field}) expected ${expectJsType}, got ${actualJsType}; coerce or pick a different override path.`,
1163
+ detail: {
1164
+ code: "scene-override-type-mismatch",
1165
+ comp: component.name,
1166
+ field,
1167
+ expectedType: schemaType,
1168
+ actualType: actualJsType
1169
+ }
1170
+ });
1171
+ }
1172
+ }
1173
+ const setRes = world.set(member, component, { [field]: value });
1174
+ if (!setRes.ok) return setRes;
1175
+ let fieldMap = state.overrides.get(lid);
1176
+ if (fieldMap === void 0) {
1177
+ fieldMap = /* @__PURE__ */ new Map();
1178
+ state.overrides.set(lid, fieldMap);
1179
+ }
1180
+ fieldMap.set(`${component.name}:${field}`, {
1181
+ comp: component.name,
1182
+ field,
1183
+ value
1184
+ });
1185
+ return ok(void 0);
1186
+ }
1187
+ function worldRemoveSceneOverride(world, root, member, component, field) {
1188
+ const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1189
+ if (!stateRes.ok) return stateRes;
1190
+ const state = stateRes.value;
1191
+ const lid = state.entityToLocalId.get(member);
1192
+ if (lid === void 0) return ok(void 0);
1193
+ const fieldMap = state.overrides.get(lid);
1194
+ if (fieldMap !== void 0) {
1195
+ fieldMap.delete(`${component.name}:${field}`);
1196
+ if (fieldMap.size === 0) state.overrides.delete(lid);
1197
+ }
1198
+ const assetRes = worldResolveSceneAsset(world, state.source);
1199
+ if (!assetRes.ok) return assetRes;
1200
+ const node = assetRes.value.entities.find(
1201
+ (n) => n.localId === lid
1202
+ );
1203
+ const layer1 = node?.components[component.name];
1204
+ if (layer1 !== void 0 && field in layer1) {
1205
+ const r = world.set(member, component, { [field]: layer1[field] });
1206
+ if (!r.ok) return r;
1207
+ }
1208
+ return ok(void 0);
1209
+ }
1210
+ function worldDetachSceneMember(world, root, member) {
1211
+ const sceneInstanceToken = world.components.resolve("SceneInstance");
1212
+ if (sceneInstanceToken === void 0) {
1213
+ return err(new ComponentNotDefinedError("SceneInstance"));
1214
+ }
1215
+ const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1216
+ if (!stateRes.ok) return stateRes;
1217
+ const state = stateRes.value;
1218
+ const lid = state.entityToLocalId.get(member);
1219
+ if (lid === void 0) return ok(void 0);
1220
+ state.detachedLocalIds.add(lid);
1221
+ return ok(void 0);
1222
+ }
1223
+ function worldReattachSceneMember(world, root, member) {
1224
+ const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1225
+ if (!stateRes.ok) return stateRes;
1226
+ const state = stateRes.value;
1227
+ const lid = state.entityToLocalId.get(member);
1228
+ if (lid === void 0) return ok(void 0);
1229
+ state.detachedLocalIds.delete(lid);
1230
+ return ok(void 0);
1231
+ }
1232
+ function worldGetSceneAssetForInstance(world, root) {
1233
+ const stateRes = worldResolveSceneInstanceStatePayload(world, root);
1234
+ if (!stateRes.ok) return stateRes;
1235
+ return ok(stateRes.value.source);
1236
+ }
1237
+ function sceneTopoSort(nodes) {
1238
+ const n = nodes.length;
1239
+ const childrenOf = Array.from({ length: n }, () => []);
1240
+ const indeg = new Uint32Array(n);
1241
+ const localIdToIdx = /* @__PURE__ */ new Map();
1242
+ for (let i = 0; i < n; i += 1) {
1243
+ const node = nodes[i];
1244
+ if (node === void 0) continue;
1245
+ localIdToIdx.set(node.localId, i);
1246
+ }
1247
+ for (let i = 0; i < n; i += 1) {
1248
+ const node = nodes[i];
1249
+ if (node === void 0) continue;
1250
+ const child = node.components.ChildOf;
1251
+ if (child === void 0) continue;
1252
+ const p = child.parent;
1253
+ if (typeof p === "number") {
1254
+ const parentIdx = localIdToIdx.get(p);
1255
+ if (parentIdx !== void 0 && parentIdx !== i) {
1256
+ childrenOf[parentIdx]?.push(i);
1257
+ indeg[i] = (indeg[i] ?? 0) + 1;
1258
+ }
1259
+ }
1260
+ }
1261
+ const order = [];
1262
+ const queue = [];
1263
+ for (let i = 0; i < n; i += 1) if ((indeg[i] ?? 0) === 0) queue.push(i);
1264
+ while (queue.length > 0) {
1265
+ const head = queue.shift();
1266
+ if (head === void 0) break;
1267
+ order.push(head);
1268
+ for (const c of childrenOf[head] ?? []) {
1269
+ indeg[c] = (indeg[c] ?? 0) - 1;
1270
+ if ((indeg[c] ?? 0) === 0) queue.push(c);
1271
+ }
1272
+ }
1273
+ for (let i = 0; i < n; i += 1) {
1274
+ if (!order.includes(i) && nodes[i] !== void 0) order.push(i);
1275
+ }
1276
+ return order;
1277
+ }
1278
+ function mountOverrideStateKey(ov) {
1279
+ return ov.field !== void 0 ? `${ov.comp}:${ov.field}` : ov.comp;
1280
+ }
1281
+ function isPrimitiveScalarFieldType(fieldType) {
1282
+ if (fieldType === "f32" || fieldType === "f64" || fieldType === "u32" || fieldType === "i32" || fieldType === "u8" || fieldType === "i8" || fieldType === "u16" || fieldType === "i16" || fieldType === "bool" || fieldType === "string") {
1283
+ return true;
1284
+ }
1285
+ if (fieldType.startsWith("enum<")) return true;
1286
+ return false;
1287
+ }
1288
+ function primitiveJsType(fieldType) {
1289
+ if (fieldType === "bool") return "boolean";
1290
+ if (fieldType === "string") return "string";
1291
+ return "number";
1292
+ }
1293
+
1294
+ // src/systems/propagate-transforms.ts
1295
+ import {
1296
+ defineSystem,
1297
+ defineSystemSet,
1298
+ FixedUpdate,
1299
+ Update
1300
+ } from "@forgeax/engine-ecs";
1301
+ import {
1302
+ createWorldProjection as createWorldProjection2,
1303
+ setDerivedComponent
1304
+ } from "@forgeax/engine-ecs/projection";
1305
+ import { mat4 } from "@forgeax/engine-math";
1306
+
1307
+ // src/systems/hierarchy-projection.ts
1308
+ import { Entity } from "@forgeax/engine-ecs";
1309
+ import { createWorldProjection } from "@forgeax/engine-ecs/projection";
1310
+ var HIERARCHY_PROJECTION_CACHE = /* @__PURE__ */ new WeakMap();
1311
+ function diagnostic(code, entity, parent) {
1312
+ if (code === "hierarchy-cycle") {
1313
+ return {
1314
+ code,
1315
+ expected: "ChildOf parent edges form an acyclic live hierarchy",
1316
+ hint: "remove one ChildOf edge from the reported cycle, then re-run the extract",
1317
+ detail: { entity, parent }
1318
+ };
1319
+ }
1320
+ return {
1321
+ code,
1322
+ expected: "ChildOf.parent references a live entity in the same World",
1323
+ hint: "remove the stale ChildOf component or restore the referenced parent in this World",
1324
+ detail: { entity, parent }
1325
+ };
1326
+ }
1327
+ function projectHierarchy(world) {
1328
+ const cached = HIERARCHY_PROJECTION_CACHE.get(world);
1329
+ const changes = cached?.changes ?? createWorldProjection(world, { components: [ChildOf] });
1330
+ if (cached !== void 0) {
1331
+ const evidence = changes.poll();
1332
+ if (evidence.status === "delta" && evidence.changes.length === 0) return cached.snapshot;
1333
+ }
1334
+ const liveEntities = /* @__PURE__ */ new Set();
1335
+ const authoredParents = /* @__PURE__ */ new Map();
1336
+ const query = world.query({ read: [Entity], optional: [ChildOf] });
1337
+ if (query.ok) {
1338
+ for (const row of query.value) {
1339
+ liveEntities.add(row.entity);
1340
+ const parent = row.get(ChildOf)?.parent;
1341
+ if (parent !== void 0 && parent !== null) authoredParents.set(row.entity, parent);
1342
+ }
1343
+ }
1344
+ const parentOf = /* @__PURE__ */ new Map();
1345
+ const diagnostics = [];
1346
+ for (const [entity, parent] of authoredParents) {
1347
+ if (liveEntities.has(parent)) {
1348
+ parentOf.set(entity, parent);
1349
+ } else {
1350
+ diagnostics.push(diagnostic("hierarchy-broken", entity, parent));
1351
+ }
1352
+ }
1353
+ const state = /* @__PURE__ */ new Map();
1354
+ const stack = [];
1355
+ const cycleMembers = /* @__PURE__ */ new Set();
1356
+ const visit = (entity) => {
1357
+ const currentState = state.get(entity) ?? 0;
1358
+ if (currentState === 2) return;
1359
+ if (currentState === 1) {
1360
+ const cycleStart = stack.indexOf(entity);
1361
+ for (let index = cycleStart; index >= 0 && index < stack.length; index++) {
1362
+ const member = stack[index];
1363
+ if (member !== void 0) cycleMembers.add(member);
1364
+ }
1365
+ return;
1366
+ }
1367
+ state.set(entity, 1);
1368
+ stack.push(entity);
1369
+ const parent = parentOf.get(entity);
1370
+ if (parent !== void 0) visit(parent);
1371
+ stack.pop();
1372
+ state.set(entity, 2);
1373
+ };
1374
+ for (const entity of liveEntities) visit(entity);
1375
+ for (const entity of cycleMembers) {
1376
+ const parent = authoredParents.get(entity);
1377
+ if (parent !== void 0) diagnostics.push(diagnostic("hierarchy-cycle", entity, parent));
1378
+ parentOf.delete(entity);
1379
+ }
1380
+ diagnostics.sort((left, right) => {
1381
+ const entityDelta = left.detail.entity - right.detail.entity;
1382
+ if (entityDelta !== 0) return entityDelta;
1383
+ return left.code.localeCompare(right.code);
1384
+ });
1385
+ const stableParentOf = new Map(parentOf);
1386
+ const stableDiagnostics = Object.freeze(diagnostics.slice());
1387
+ const snapshot = {
1388
+ parentOf: stableParentOf,
1389
+ diagnostics: stableDiagnostics,
1390
+ getParent(entity) {
1391
+ return stableParentOf.get(entity);
1392
+ }
1393
+ };
1394
+ HIERARCHY_PROJECTION_CACHE.set(world, {
1395
+ changes,
1396
+ snapshot
1397
+ });
1398
+ return snapshot;
1399
+ }
1400
+
1401
+ // src/systems/propagate-transforms.ts
1402
+ var PROPAGATE_TRANSFORMS_SYSTEM = "propagateTransforms";
1403
+ var PROPAGATE_TRANSFORMS_FIXED_SYSTEM = "propagateTransformsFixed";
1404
+ var TransformSet = defineSystemSet({ name: "transform" });
1405
+ var TransformFixedSet = defineSystemSet({ name: "transform-fixed" });
1406
+ var CACHE = /* @__PURE__ */ new WeakMap();
1407
+ var REGISTRATION_LEASES = /* @__PURE__ */ new WeakMap();
1408
+ function copyState(value) {
1409
+ return {
1410
+ pos: new Float32Array(value.pos),
1411
+ quat: new Float32Array(value.quat),
1412
+ scale: new Float32Array(value.scale)
1413
+ };
1414
+ }
1415
+ function sameArray(left, right) {
1416
+ if (left.length !== right.length) return false;
1417
+ for (let index = 0; index < left.length; index += 1) {
1418
+ if (left[index] !== right[index]) return false;
1419
+ }
1420
+ return true;
1421
+ }
1422
+ function sameState(left, right) {
1423
+ return left !== void 0 && sameArray(left.pos, right.pos) && sameArray(left.quat, right.quat) && sameArray(left.scale, right.scale);
1424
+ }
1425
+ function compose(state, out) {
1426
+ mat4.compose(out, state.pos, state.quat, state.scale);
1427
+ }
1428
+ function indexChildren(hierarchy) {
1429
+ const childrenOf = /* @__PURE__ */ new Map();
1430
+ for (const [child, parent] of hierarchy.parentOf) {
1431
+ const children = childrenOf.get(parent);
1432
+ if (children === void 0) childrenOf.set(parent, [child]);
1433
+ else children.push(child);
1434
+ }
1435
+ return childrenOf;
1436
+ }
1437
+ function descendants(childrenOf, roots) {
1438
+ const affected = new Set(roots);
1439
+ const pending = [...roots];
1440
+ for (let index = 0; index < pending.length; index += 1) {
1441
+ const parent = pending[index];
1442
+ if (parent === void 0) continue;
1443
+ for (const child of childrenOf.get(parent) ?? []) {
1444
+ if (affected.has(child)) continue;
1445
+ affected.add(child);
1446
+ pending.push(child);
1447
+ }
1448
+ }
1449
+ return affected;
1450
+ }
1451
+ function hierarchyError(hierarchy) {
1452
+ const first = hierarchy.diagnostics[0];
1453
+ if (first === void 0) return ok(void 0);
1454
+ return err(
1455
+ new SceneError({
1456
+ code: first.code,
1457
+ expected: first.expected,
1458
+ hint: first.hint,
1459
+ detail: first.detail
1460
+ })
1461
+ );
1462
+ }
1463
+ function buildCache(world, projection) {
1464
+ const hierarchy = projectHierarchy(world);
1465
+ const locals = /* @__PURE__ */ new Map();
1466
+ const entities = /* @__PURE__ */ new Set();
1467
+ const query = world.query({ read: [Transform], optional: [ChildOf] });
1468
+ if (query.ok) {
1469
+ for (const row of query.value) {
1470
+ const transform = row.get(Transform);
1471
+ if (transform === void 0) continue;
1472
+ entities.add(row.entity);
1473
+ locals.set(row.entity, copyState(transform));
1474
+ }
1475
+ }
1476
+ return {
1477
+ projection,
1478
+ hierarchy,
1479
+ parentOf: hierarchy.parentOf,
1480
+ childrenOf: indexChildren(hierarchy),
1481
+ entities,
1482
+ locals,
1483
+ derived: /* @__PURE__ */ new Map(),
1484
+ result: ok(void 0)
1485
+ };
1486
+ }
1487
+ function deriveEntity(world, entity, cache, affected, visiting, published) {
1488
+ if (!affected.has(entity) || cache.derived.has(entity)) return ok(void 0);
1489
+ if (visiting.has(entity)) return ok(void 0);
1490
+ visiting.add(entity);
1491
+ const local = cache.locals.get(entity);
1492
+ if (local === void 0) {
1493
+ visiting.delete(entity);
1494
+ return ok(void 0);
1495
+ }
1496
+ const parent = cache.parentOf.get(entity);
1497
+ if (parent !== void 0 && cache.entities.has(parent)) {
1498
+ const parentResult = deriveEntity(world, parent, cache, affected, visiting, published);
1499
+ if (!parentResult.ok) return parentResult;
1500
+ }
1501
+ const localWorld = mat4.create();
1502
+ compose(local, localWorld);
1503
+ const parentWorld = parent === void 0 ? void 0 : cache.derived.get(parent);
1504
+ const resolved = mat4.create();
1505
+ if (parentWorld === void 0) resolved.set(localWorld);
1506
+ else mat4.multiply(resolved, parentWorld, localWorld);
1507
+ const write = setDerivedComponent(world, entity, Transform, { world: resolved });
1508
+ if (!write.ok) {
1509
+ visiting.delete(entity);
1510
+ return err(
1511
+ new SceneError({
1512
+ code: "hierarchy-broken",
1513
+ expected: "the derived Transform.world write to succeed",
1514
+ hint: "inspect the ECS mutation error before retrying transform propagation",
1515
+ detail: { entity, parent: entity }
1516
+ })
1517
+ );
1518
+ }
1519
+ cache.derived.set(entity, resolved);
1520
+ published.add(entity);
1521
+ visiting.delete(entity);
1522
+ return ok(void 0);
1523
+ }
1524
+ function drainPublishedChanges(cache, published) {
1525
+ const drained = cache.projection.poll();
1526
+ if (drained.status === "rebuild") return true;
1527
+ return drained.changes.every(
1528
+ (change) => change.kind === "derived-component-changed" && change.component === Transform && published.has(change.entity)
1529
+ );
1530
+ }
1531
+ function propagateTransforms(world, _hierarchy) {
1532
+ let cache = CACHE.get(world);
1533
+ if (cache === void 0) {
1534
+ cache = buildCache(world, createWorldProjection2(world, { components: [Transform, ChildOf] }));
1535
+ CACHE.set(world, cache);
1536
+ const initial = new Set(cache.entities);
1537
+ const published2 = /* @__PURE__ */ new Set();
1538
+ for (const entity of initial) {
1539
+ const result = deriveEntity(world, entity, cache, initial, /* @__PURE__ */ new Set(), published2);
1540
+ if (!result.ok) return result;
1541
+ }
1542
+ if (!drainPublishedChanges(cache, published2)) {
1543
+ CACHE.delete(world);
1544
+ return propagateTransforms(world);
1545
+ }
1546
+ cache.result = hierarchyError(cache.hierarchy);
1547
+ return cache.result;
1548
+ }
1549
+ const evidence = cache.projection.poll();
1550
+ if (evidence.status === "rebuild") {
1551
+ cache = buildCache(world, cache.projection);
1552
+ CACHE.set(world, cache);
1553
+ const all = new Set(cache.entities);
1554
+ const published2 = /* @__PURE__ */ new Set();
1555
+ for (const entity of all) {
1556
+ const result = deriveEntity(world, entity, cache, all, /* @__PURE__ */ new Set(), published2);
1557
+ if (!result.ok) return result;
1558
+ }
1559
+ if (!drainPublishedChanges(cache, published2)) {
1560
+ CACHE.delete(world);
1561
+ return propagateTransforms(world);
1562
+ }
1563
+ cache.result = hierarchyError(cache.hierarchy);
1564
+ return cache.result;
1565
+ }
1566
+ if (evidence.changes.length === 0) return cache.result;
1567
+ const transformSeeds = /* @__PURE__ */ new Set();
1568
+ let rebuild = false;
1569
+ for (const change of evidence.changes) {
1570
+ if (change.kind === "entity-removed" || change.kind === "component-added" || change.kind === "component-removed") {
1571
+ rebuild = true;
1572
+ break;
1573
+ }
1574
+ if (change.kind !== "component-changed") continue;
1575
+ if (change.component === ChildOf) {
1576
+ rebuild = true;
1577
+ break;
1578
+ }
1579
+ if (change.component === Transform) transformSeeds.add(change.entity);
1580
+ }
1581
+ if (rebuild) {
1582
+ CACHE.delete(world);
1583
+ return propagateTransforms(world);
1584
+ }
1585
+ const changed = /* @__PURE__ */ new Set();
1586
+ for (const entity of transformSeeds) {
1587
+ const current = world.get(entity, Transform);
1588
+ if (!current.ok) {
1589
+ CACHE.delete(world);
1590
+ return propagateTransforms(world);
1591
+ }
1592
+ const next = copyState(current.value);
1593
+ if (!sameState(cache.locals.get(entity), next)) changed.add(entity);
1594
+ cache.locals.set(entity, next);
1595
+ }
1596
+ if (changed.size === 0) return cache.result;
1597
+ const affected = descendants(cache.childrenOf, changed);
1598
+ const published = /* @__PURE__ */ new Set();
1599
+ for (const entity of affected) cache.derived.delete(entity);
1600
+ for (const entity of affected) {
1601
+ const result = deriveEntity(world, entity, cache, affected, /* @__PURE__ */ new Set(), published);
1602
+ if (!result.ok) return result;
1603
+ }
1604
+ if (!drainPublishedChanges(cache, published)) {
1605
+ CACHE.delete(world);
1606
+ return propagateTransforms(world);
1607
+ }
1608
+ cache.result = hierarchyError(cache.hierarchy);
1609
+ return cache.result;
1610
+ }
1611
+ var PropagateTransforms = defineSystem({
1612
+ name: PROPAGATE_TRANSFORMS_SYSTEM,
1613
+ queries: [],
1614
+ fn: (world) => {
1615
+ const result = propagateTransforms(world);
1616
+ if (!result.ok) throw result.error;
1617
+ }
1618
+ });
1619
+ var PropagateTransformsFixed = defineSystem({
1620
+ name: PROPAGATE_TRANSFORMS_FIXED_SYSTEM,
1621
+ queries: [],
1622
+ fn: PropagateTransforms.fn
1623
+ });
1624
+ function registerPropagateTransforms(world, options = {}) {
1625
+ const existing = REGISTRATION_LEASES.get(world);
1626
+ if (existing !== void 0) {
1627
+ existing.refs += 1;
1628
+ let active2 = true;
1629
+ return () => {
1630
+ if (!active2) return;
1631
+ active2 = false;
1632
+ existing.refs -= 1;
1633
+ if (existing.refs === 0) {
1634
+ world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);
1635
+ world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);
1636
+ REGISTRATION_LEASES.delete(world);
1637
+ CACHE.delete(world);
1638
+ }
1639
+ };
1640
+ }
1641
+ if (options.beforeSystemName === void 0) {
1642
+ world.addSystems(Update, TransformSet, [PropagateTransforms]).unwrap();
1643
+ } else {
1644
+ world.addSystems(Update, TransformSet, [
1645
+ {
1646
+ name: PROPAGATE_TRANSFORMS_SYSTEM,
1647
+ queries: [],
1648
+ fn: PropagateTransforms.fn,
1649
+ before: [options.beforeSystemName]
1650
+ }
1651
+ ]).unwrap();
1652
+ }
1653
+ world.addSystems(FixedUpdate, TransformFixedSet, [PropagateTransformsFixed]).unwrap();
1654
+ const lease = { refs: 1 };
1655
+ REGISTRATION_LEASES.set(world, lease);
1656
+ let active = true;
1657
+ return () => {
1658
+ if (!active) return;
1659
+ active = false;
1660
+ lease.refs -= 1;
1661
+ if (lease.refs !== 0) return;
1662
+ world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);
1663
+ world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);
1664
+ REGISTRATION_LEASES.delete(world);
1665
+ CACHE.delete(world);
1666
+ };
1667
+ }
1668
+
1669
+ // src/plugin.ts
1670
+ var SCENE_COMPONENTS = [ChildOf, Children, MorphWeights, Name, Transform];
1671
+ function registerSceneComponents(world) {
1672
+ const leases = SCENE_COMPONENTS.map((component) => world.components.register(component).unwrap());
1673
+ return () => {
1674
+ for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();
1675
+ };
1676
+ }
1677
+ function scenePlugin() {
1678
+ return {
1679
+ name: "scene",
1680
+ inject: ["world"],
1681
+ apply(ctx) {
1682
+ ctx.effect(() => registerSceneComponents(ctx.world), "scene/components");
1683
+ ctx.effect(() => registerPropagateTransforms(ctx.world), "scene/propagate-transforms");
1684
+ }
1685
+ };
1686
+ }
1687
+ export {
1688
+ ChildOf,
1689
+ Children,
1690
+ ComponentNotDefinedError,
1691
+ MorphWeights,
1692
+ Name,
1693
+ PROPAGATE_TRANSFORMS_SYSTEM,
1694
+ SCENE_COLLECT_PROFILE,
1695
+ SceneError,
1696
+ Transform,
1697
+ TransformSet,
1698
+ collectSubtree,
1699
+ externalizeSceneAsset,
1700
+ projectHierarchy,
1701
+ propagateTransforms,
1702
+ registerPropagateTransforms,
1703
+ sceneAssetContribution,
1704
+ scenePlugin,
1705
+ worldApplyMountOverride,
1706
+ worldBuildSceneEntityComponentDatas,
1707
+ worldDespawnDescendants,
1708
+ worldDespawnScene,
1709
+ worldDetachSceneMember,
1710
+ worldGetSceneAssetForInstance,
1711
+ worldGetSceneAssetResolver,
1712
+ worldGetSceneInstanceState,
1713
+ worldInstantiateScene,
1714
+ worldInstantiateSceneAsset,
1715
+ worldInstantiateSceneAssetFlat,
1716
+ worldInstantiateSceneFlat,
1717
+ worldInstantiateScenePayload,
1718
+ worldInstantiateSceneRec,
1719
+ worldMountOverridesToStateMap,
1720
+ worldReattachSceneMember,
1721
+ worldRemoveSceneOverride,
1722
+ worldResolveMountSource,
1723
+ worldResolveSceneAsset,
1724
+ worldResolveSceneInstanceStatePayload,
1725
+ worldSetSceneAssetResolver,
1726
+ worldSetSceneOverride,
1727
+ worldSpawnMountEntity,
1728
+ worldSpawnSceneMembers,
1729
+ worldValidateMountOverrides
1730
+ };
1731
+ //# sourceMappingURL=index.mjs.map