@forgeax/engine-scene 0.1.4 → 0.1.6

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.
package/dist/index.mjs CHANGED
@@ -30,9 +30,6 @@ function err(error) {
30
30
  function toUnique(raw) {
31
31
  return raw;
32
32
  }
33
- function toShared(raw) {
34
- return raw;
35
- }
36
33
  function unwrapHandle(h) {
37
34
  return h;
38
35
  }
@@ -192,9 +189,6 @@ function invalidScene(guid, reason) {
192
189
  });
193
190
  }
194
191
  var sceneWireRefs = /* @__PURE__ */ new WeakMap();
195
- function sceneAssetWireRefs(asset) {
196
- return sceneWireRefs.get(asset);
197
- }
198
192
  function resolveWireRef(refs, value, location) {
199
193
  const guid = refs[value];
200
194
  if (!Number.isInteger(value) || value < 0 || guid === void 0) {
@@ -281,17 +275,62 @@ var sceneAssetContribution = {
281
275
  consumer: "Scene"
282
276
  };
283
277
 
284
- // src/assets/scene-projection.ts
285
- import { componentSchema as componentSchema2 } from "@forgeax/engine-ecs/internal";
286
- import { AssetGuid } from "@forgeax/engine-pack/guid";
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
+ });
287
288
 
288
- // src/instances/scene-instances.ts
289
- import {
290
- ENTITY_NULL_RAW
291
- } from "@forgeax/engine-ecs";
292
- import { classifyEntityField, remapEntityFieldValue } from "@forgeax/engine-ecs/externalization";
293
- import { componentSchema } from "@forgeax/engine-ecs/internal";
294
- import { fillComponentDefaults, StaleEntityError } from "@forgeax/engine-ecs/projection";
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
+ });
295
334
 
296
335
  // src/errors.ts
297
336
  import { ComponentNotDefinedError } from "@forgeax/engine-ecs/projection";
@@ -310,18 +349,117 @@ var SceneError = class extends Error {
310
349
  }
311
350
  };
312
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
+
313
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";
314
456
  var entityIndex = (entity) => entity & 16777215;
315
457
  var entityGeneration = (entity) => entity >>> 24 & 255;
316
458
  var sceneWorldStates = /* @__PURE__ */ new WeakMap();
317
459
  function sceneWorldState(world) {
318
460
  const current = sceneWorldStates.get(world);
319
461
  if (current !== void 0) return current;
320
- const created = {
321
- resolver: null,
322
- statePayloads: /* @__PURE__ */ new Map(),
323
- instantiateHook: null
324
- };
462
+ const created = { resolver: null, statePayloads: /* @__PURE__ */ new Map() };
325
463
  sceneWorldStates.set(world, created);
326
464
  return created;
327
465
  }
@@ -331,24 +469,21 @@ function worldSetSceneAssetResolver(world, resolver) {
331
469
  function worldGetSceneAssetResolver(world) {
332
470
  return sceneWorldState(world).resolver;
333
471
  }
334
- function worldSetSceneInstantiateHook(world, hook) {
335
- sceneWorldState(world).instantiateHook = hook;
336
- }
337
472
  function worldInstantiateScene(world, handle, parent) {
338
473
  const stack = /* @__PURE__ */ new Set();
339
474
  const diagnostics = [];
340
475
  const r = worldInstantiateSceneRec(world, handle, parent, stack, diagnostics);
341
476
  if (!r.ok) return r;
342
- const hook = sceneWorldState(world).instantiateHook;
343
- if (hook !== null) {
344
- const hooked = hook(world, r.value);
345
- if (!hooked.ok) {
346
- worldDespawnScene(world, r.value);
347
- return err(hooked.error);
348
- }
349
- }
350
477
  return ok({ root: r.value, diagnostics });
351
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
+ }
352
487
  function worldInstantiateSceneFlat(world, handle) {
353
488
  const stack = /* @__PURE__ */ new Set();
354
489
  const diagnostics = [];
@@ -1156,573 +1291,6 @@ function primitiveJsType(fieldType) {
1156
1291
  return "number";
1157
1292
  }
1158
1293
 
1159
- // src/assets/scene-projection.ts
1160
- var SCENE_ASSET_SKIN_RESOLVER_RESOURCE_KEY = "SceneAssetSkinResolver";
1161
- var SCENE_ASSET_MESH_DEFAULT_RESOLVER_RESOURCE_KEY = "SceneAssetMeshDefaultResolver";
1162
- var ASSET_KIND_BY_TARGET = Object.freeze({
1163
- MeshAsset: "mesh",
1164
- TextureAsset: "texture",
1165
- EquirectAsset: "equirect",
1166
- SamplerAsset: "sampler",
1167
- MaterialAsset: "material",
1168
- SceneAsset: "scene",
1169
- AudioClipAsset: "audio",
1170
- SkinAsset: "skin",
1171
- SkeletonAsset: "skeleton",
1172
- AnimationClip: "animation-clip",
1173
- AnimationGraph: "animation-graph",
1174
- FontAsset: "font",
1175
- RenderPipelineAsset: "render-pipeline",
1176
- TilesetAsset: "tileset",
1177
- VideoAsset: "video",
1178
- ParticleEffectAsset: "particle-effect"
1179
- });
1180
- var projectionStores = /* @__PURE__ */ new WeakMap();
1181
- function projectionStoreFor(world) {
1182
- const current = projectionStores.get(world);
1183
- if (current !== void 0) return current;
1184
- const skeletonGuidByHandle = /* @__PURE__ */ new Map();
1185
- const skinByGuid = /* @__PURE__ */ new Map();
1186
- const skinBySkeletonGuid = /* @__PURE__ */ new Map();
1187
- const materialHandleByGuid = /* @__PURE__ */ new Map();
1188
- const resolver = {
1189
- resolveSkinAsset(skeletonHandle) {
1190
- const skeletonGuid = skeletonGuidByHandle.get(skeletonHandle);
1191
- return skeletonGuid === void 0 ? void 0 : skinBySkeletonGuid.get(skeletonGuid);
1192
- }
1193
- };
1194
- const meshDefaultResolver = {
1195
- resolveMeshDefaultMaterial(guid) {
1196
- return materialHandleByGuid.get(guid.toLowerCase());
1197
- }
1198
- };
1199
- const store = {
1200
- skeletonGuidByHandle,
1201
- skinByGuid,
1202
- skinBySkeletonGuid,
1203
- resolver,
1204
- materialHandleByGuid,
1205
- meshDefaultResolver
1206
- };
1207
- projectionStores.set(world, store);
1208
- world.insertResource(SCENE_ASSET_SKIN_RESOLVER_RESOURCE_KEY, resolver);
1209
- world.insertResource(SCENE_ASSET_MESH_DEFAULT_RESOLVER_RESOURCE_KEY, meshDefaultResolver);
1210
- return store;
1211
- }
1212
- function projectionError(code, detail) {
1213
- switch (code) {
1214
- case "scene-reference-target-unsupported":
1215
- return {
1216
- code,
1217
- expected: "every shared SceneAsset field target maps to a built-in Asset kind",
1218
- hint: "register the target in the AssetTagMap before projecting the scene",
1219
- detail
1220
- };
1221
- case "scene-reference-kind-mismatch":
1222
- return {
1223
- code,
1224
- expected: "the loaded payload kind matches the component shared-handle target",
1225
- hint: "repair the scene refs[] edge or republish the referenced asset",
1226
- detail
1227
- };
1228
- case "scene-reference-cycle":
1229
- return {
1230
- code,
1231
- expected: "an acyclic SceneAsset mount graph",
1232
- hint: "remove the circular mount.source reference and republish the scenes",
1233
- detail
1234
- };
1235
- case "scene-reference-unresolved":
1236
- return {
1237
- code,
1238
- expected: "a mount.source GUID that is loadable by the current AssetRegistry",
1239
- hint: "publish the child SceneAsset before projecting its parent",
1240
- detail
1241
- };
1242
- }
1243
- }
1244
- function isPayloadOfKind(value, kind) {
1245
- return typeof value === "object" && value !== null && value.kind === kind;
1246
- }
1247
- function sharedTarget(fieldType) {
1248
- if (typeof fieldType !== "string") return void 0;
1249
- const match = /^shared<([^>]+)>$/.exec(fieldType);
1250
- return match?.[1];
1251
- }
1252
- function schemaFieldType(field) {
1253
- if (typeof field === "object" && field !== null && "type" in field) {
1254
- return field.type;
1255
- }
1256
- return field;
1257
- }
1258
- function sharedArrayTarget(fieldType) {
1259
- if (typeof fieldType !== "string") return void 0;
1260
- const match = /^array<shared<([^>]+)>(?:,\s*\d+)? *>$/.exec(fieldType);
1261
- return match?.[1];
1262
- }
1263
- function resolveWireValue(value, refs, location) {
1264
- if (refs === void 0 || typeof value !== "number") return ok(value);
1265
- const guid = refs[value];
1266
- if (!Number.isInteger(value) || guid === void 0) {
1267
- return err(
1268
- projectionError("scene-reference-unresolved", {
1269
- source: `${location} refs[${value}]`
1270
- })
1271
- );
1272
- }
1273
- return ok(guid);
1274
- }
1275
- async function projectSharedValue(state, value, target, field) {
1276
- if (typeof value !== "string") {
1277
- if (target === "MeshAsset" && typeof value === "number") {
1278
- const resolved = state.world.sharedRefs.resolve(toShared(value));
1279
- if (resolved.ok && isPayloadOfKind(resolved.value, "mesh")) {
1280
- const meshDefaults = await projectMeshDefaults(state, resolved.value, field);
1281
- if (!meshDefaults.ok) return meshDefaults;
1282
- }
1283
- }
1284
- return ok(value);
1285
- }
1286
- const kind = ASSET_KIND_BY_TARGET[target];
1287
- if (kind === void 0) {
1288
- return err(
1289
- projectionError("scene-reference-target-unsupported", {
1290
- guid: value,
1291
- target,
1292
- field
1293
- })
1294
- );
1295
- }
1296
- const cacheKey = `${target}:${value.toLowerCase()}`;
1297
- const cached = state.assetHandles.get(cacheKey);
1298
- if (cached !== void 0) {
1299
- if (target === "SkeletonAsset") {
1300
- state.skinStore.skeletonGuidByHandle.set(Number(cached), value.toLowerCase());
1301
- }
1302
- return ok(cached);
1303
- }
1304
- const loaded = await state.load(value, kind);
1305
- if (!loaded.ok) return loaded;
1306
- if (!isPayloadOfKind(loaded.value, kind)) {
1307
- return err(
1308
- projectionError("scene-reference-kind-mismatch", {
1309
- guid: value,
1310
- target,
1311
- expectedKind: kind,
1312
- actualKind: typeof loaded.value === "object" && loaded.value !== null && "kind" in loaded.value ? String(loaded.value.kind) : typeof loaded.value,
1313
- field
1314
- })
1315
- );
1316
- }
1317
- if (target === "MeshAsset") {
1318
- const meshDefaults = await projectMeshDefaults(state, loaded.value, field);
1319
- if (!meshDefaults.ok) return meshDefaults;
1320
- }
1321
- const handle = state.world.allocSharedRef(target, loaded.value);
1322
- state.assetHandles.set(cacheKey, handle);
1323
- if (target === "SkeletonAsset") {
1324
- state.skinStore.skeletonGuidByHandle.set(Number(handle), value.toLowerCase());
1325
- }
1326
- return ok(handle);
1327
- }
1328
- async function projectMeshDefaults(state, mesh, field) {
1329
- if (!Array.isArray(mesh.materialSlots)) return ok(void 0);
1330
- for (let slotIndex = 0; slotIndex < mesh.materialSlots.length; slotIndex += 1) {
1331
- const defaultMaterial = mesh.materialSlots[slotIndex]?.defaultMaterial;
1332
- if (defaultMaterial === void 0) continue;
1333
- const guid = AssetGuid.format(defaultMaterial);
1334
- const projected = await projectSharedValue(
1335
- state,
1336
- guid,
1337
- "MaterialAsset",
1338
- `${field}.materialSlots[${slotIndex}].defaultMaterial`
1339
- );
1340
- if (!projected.ok) return projected;
1341
- state.skinStore.materialHandleByGuid.set(guid, Number(projected.value));
1342
- }
1343
- return ok(void 0);
1344
- }
1345
- async function projectSkinDependencies(state, asset) {
1346
- for (let index = 0; index < (asset.skinGuids?.length ?? 0); index += 1) {
1347
- const guid = asset.skinGuids?.[index];
1348
- if (guid === void 0) continue;
1349
- const guidKey = guid.toLowerCase();
1350
- if (state.skinStore.skinByGuid.has(guidKey)) continue;
1351
- const loaded = await state.load(guid, "skin");
1352
- if (!loaded.ok) return loaded;
1353
- if (!isPayloadOfKind(loaded.value, "skin")) {
1354
- return err(
1355
- projectionError("scene-reference-kind-mismatch", {
1356
- guid,
1357
- target: "SkinAsset",
1358
- expectedKind: "skin",
1359
- actualKind: typeof loaded.value === "object" && loaded.value !== null && "kind" in loaded.value ? String(loaded.value.kind) : typeof loaded.value,
1360
- field: `scene.skinGuids[${index}]`
1361
- })
1362
- );
1363
- }
1364
- const skin = loaded.value;
1365
- state.skinStore.skinByGuid.set(guidKey, skin);
1366
- state.skinStore.skinBySkeletonGuid.set(skin.skeletonGuid.toLowerCase(), skin);
1367
- }
1368
- return ok(void 0);
1369
- }
1370
- async function projectFields(state, componentName, rawFields, location, wireRefs) {
1371
- const component = state.world.components.resolve(componentName);
1372
- if (component === void 0) return ok({ ...rawFields });
1373
- const fields = { ...rawFields };
1374
- for (const [fieldName, value] of Object.entries(rawFields)) {
1375
- const fieldType = schemaFieldType(componentSchema2(component)[fieldName]);
1376
- const target = sharedTarget(fieldType);
1377
- if (target !== void 0) {
1378
- const wireValue = await resolveWireValue(
1379
- value,
1380
- wireRefs,
1381
- `${location}.${componentName}.${fieldName}`
1382
- );
1383
- if (!wireValue.ok) return wireValue;
1384
- const projected = await projectSharedValue(
1385
- state,
1386
- wireValue.value,
1387
- target,
1388
- `${location}.${componentName}.${fieldName}`
1389
- );
1390
- if (!projected.ok) return projected;
1391
- fields[fieldName] = projected.value;
1392
- continue;
1393
- }
1394
- const arrayTarget = sharedArrayTarget(fieldType);
1395
- if (arrayTarget === void 0 || !Array.isArray(value)) continue;
1396
- const projectedValues = [];
1397
- for (let index = 0; index < value.length; index += 1) {
1398
- const wireValue = await resolveWireValue(
1399
- value[index],
1400
- wireRefs,
1401
- `${location}.${componentName}.${fieldName}[${index}]`
1402
- );
1403
- if (!wireValue.ok) return wireValue;
1404
- const projected = await projectSharedValue(
1405
- state,
1406
- wireValue.value,
1407
- arrayTarget,
1408
- `${location}.${componentName}.${fieldName}[${index}]`
1409
- );
1410
- if (!projected.ok) return projected;
1411
- projectedValues.push(projected.value);
1412
- }
1413
- fields[fieldName] = projectedValues;
1414
- }
1415
- return ok(fields);
1416
- }
1417
- async function projectComponents(state, components, location, wireRefs) {
1418
- const projected = {};
1419
- for (const [componentName, rawFields] of Object.entries(components)) {
1420
- if (typeof rawFields !== "object" || rawFields === null || Array.isArray(rawFields)) continue;
1421
- const fields = await projectFields(
1422
- state,
1423
- componentName,
1424
- rawFields,
1425
- location,
1426
- wireRefs
1427
- );
1428
- if (!fields.ok) return fields;
1429
- projected[componentName] = fields.value;
1430
- }
1431
- return ok(projected);
1432
- }
1433
- async function projectOverride(state, override, index, wireRefs) {
1434
- const component = state.world.components.resolve(override.comp);
1435
- if (component === void 0) return ok(override);
1436
- if (override.field !== void 0) {
1437
- const fieldType = schemaFieldType(componentSchema2(component)[override.field]);
1438
- const target = sharedTarget(fieldType);
1439
- if (target !== void 0) {
1440
- const wireValue = await resolveWireValue(
1441
- override.value,
1442
- wireRefs,
1443
- `mount override ${index}.${override.comp}.${override.field}`
1444
- );
1445
- if (!wireValue.ok) return wireValue;
1446
- const value2 = await projectSharedValue(
1447
- state,
1448
- wireValue.value,
1449
- target,
1450
- `mount override ${index}.${override.comp}.${override.field}`
1451
- );
1452
- if (!value2.ok) return value2;
1453
- return ok({ ...override, value: value2.value });
1454
- }
1455
- const arrayTarget = sharedArrayTarget(fieldType);
1456
- if (arrayTarget !== void 0 && Array.isArray(override.value)) {
1457
- const values = [];
1458
- for (let element = 0; element < override.value.length; element += 1) {
1459
- const wireValue = await resolveWireValue(
1460
- override.value[element],
1461
- wireRefs,
1462
- `mount override ${index}.${override.comp}.${override.field}[${element}]`
1463
- );
1464
- if (!wireValue.ok) return wireValue;
1465
- const value2 = await projectSharedValue(
1466
- state,
1467
- wireValue.value,
1468
- arrayTarget,
1469
- `mount override ${index}.${override.comp}.${override.field}[${element}]`
1470
- );
1471
- if (!value2.ok) return value2;
1472
- values.push(value2.value);
1473
- }
1474
- return ok({ ...override, value: values });
1475
- }
1476
- return ok(override);
1477
- }
1478
- if (typeof override.value !== "object" || override.value === null || Array.isArray(override.value)) {
1479
- return ok(override);
1480
- }
1481
- const value = await projectFields(
1482
- state,
1483
- override.comp,
1484
- override.value,
1485
- `mount override ${index}`,
1486
- wireRefs
1487
- );
1488
- if (!value.ok) return value;
1489
- return ok({ ...override, value: value.value });
1490
- }
1491
- async function projectMount(state, mount, index, visiting, wireRefs) {
1492
- const components = mount.components === void 0 ? void 0 : await projectComponents(state, mount.components, `mount ${index}`, wireRefs);
1493
- if (components !== void 0 && !components.ok) return components;
1494
- const overrides = [];
1495
- for (let overrideIndex = 0; overrideIndex < (mount.overrides?.length ?? 0); overrideIndex += 1) {
1496
- const override = mount.overrides?.[overrideIndex];
1497
- if (override === void 0) continue;
1498
- const projected = await projectOverride(state, override, overrideIndex, wireRefs);
1499
- if (!projected.ok) return projected;
1500
- overrides.push(projected.value);
1501
- }
1502
- if (typeof mount.source !== "string") {
1503
- return ok({
1504
- ...mount,
1505
- ...components === void 0 ? {} : { components: components.value },
1506
- ...mount.overrides === void 0 ? {} : { overrides }
1507
- });
1508
- }
1509
- const sourceKey = mount.source.toLowerCase();
1510
- const cached = state.sceneHandles.get(sourceKey);
1511
- if (cached !== void 0) {
1512
- return ok({
1513
- ...mount,
1514
- source: cached,
1515
- ...components === void 0 ? {} : { components: components.value },
1516
- ...mount.overrides === void 0 ? {} : { overrides }
1517
- });
1518
- }
1519
- if (visiting.has(sourceKey)) {
1520
- return err(
1521
- projectionError("scene-reference-cycle", {
1522
- cycle: [...visiting, sourceKey]
1523
- })
1524
- );
1525
- }
1526
- const loaded = await state.load(mount.source, "scene");
1527
- if (!loaded.ok) return loaded;
1528
- if (!isPayloadOfKind(loaded.value, "scene")) {
1529
- return err(projectionError("scene-reference-unresolved", { source: mount.source }));
1530
- }
1531
- visiting.add(sourceKey);
1532
- const projectedChild = await projectScene(state, loaded.value, visiting);
1533
- visiting.delete(sourceKey);
1534
- if (!projectedChild.ok) return projectedChild;
1535
- const childHandle = state.world.allocSharedRef("SceneAsset", projectedChild.value);
1536
- state.sceneHandles.set(sourceKey, childHandle);
1537
- return ok({
1538
- ...mount,
1539
- source: childHandle,
1540
- ...components === void 0 ? {} : { components: components.value },
1541
- ...mount.overrides === void 0 ? {} : { overrides }
1542
- });
1543
- }
1544
- async function projectScene(state, asset, visiting) {
1545
- const skins = await projectSkinDependencies(state, asset);
1546
- if (!skins.ok) return skins;
1547
- const entities = [];
1548
- const wireRefs = sceneAssetWireRefs(asset);
1549
- for (const entity of asset.entities) {
1550
- const components = await projectComponents(
1551
- state,
1552
- entity.components,
1553
- `entity ${entity.localId}`,
1554
- wireRefs
1555
- );
1556
- if (!components.ok) return components;
1557
- entities.push({ localId: entity.localId, components: components.value });
1558
- }
1559
- const mounts = [];
1560
- for (let index = 0; index < (asset.mounts?.length ?? 0); index += 1) {
1561
- const mount = asset.mounts?.[index];
1562
- if (mount === void 0) continue;
1563
- const projected = await projectMount(state, mount, index, visiting, wireRefs);
1564
- if (!projected.ok) return projected;
1565
- mounts.push(projected.value);
1566
- }
1567
- return ok({
1568
- kind: "scene",
1569
- entities,
1570
- ...asset.mounts === void 0 ? {} : { mounts },
1571
- ...asset.skinGuids === void 0 ? {} : { skinGuids: [...asset.skinGuids] }
1572
- });
1573
- }
1574
- async function projectSceneAsset(world, asset, load) {
1575
- const skinStore = projectionStoreFor(world);
1576
- worldSetSceneAssetResolver(world, (source) => {
1577
- if (typeof source === "number") return ok(toShared(source));
1578
- return err(projectionError("scene-reference-unresolved", { source }));
1579
- });
1580
- return projectScene(
1581
- {
1582
- world,
1583
- load,
1584
- assetHandles: /* @__PURE__ */ new Map(),
1585
- sceneHandles: /* @__PURE__ */ new Map(),
1586
- skinStore
1587
- },
1588
- asset,
1589
- /* @__PURE__ */ new Set()
1590
- );
1591
- }
1592
-
1593
- // src/components/children.ts
1594
- import { defineRelationship } from "@forgeax/engine-ecs";
1595
- var { source: ChildOf, target: Children } = defineRelationship({
1596
- sourceName: "ChildOf",
1597
- sourceField: "parent",
1598
- targetName: "Children",
1599
- targetField: "entities",
1600
- exclusive: true,
1601
- linkedSpawn: true
1602
- });
1603
-
1604
- // src/components/morph-weights.ts
1605
- import { defineComponent } from "@forgeax/engine-ecs";
1606
- var MorphWeights = defineComponent("MorphWeights", {
1607
- weights: { type: "array<f32>" }
1608
- });
1609
-
1610
- // src/components/name.ts
1611
- import { defineComponent as defineComponent2 } from "@forgeax/engine-ecs";
1612
- var Name = defineComponent2("Name", { value: { type: "string" } });
1613
-
1614
- // src/components/transform.ts
1615
- import { defineComponent as defineComponent3 } from "@forgeax/engine-ecs";
1616
- var IDENTITY_MAT4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
1617
- var Transform = defineComponent3("Transform", {
1618
- pos: { type: "array<f32, 3>", default: new Float32Array([0, 0, 0]) },
1619
- // Component order [x, y, z, w] end to end (glTF-aligned; E6).
1620
- quat: { type: "array<f32, 4>", default: new Float32Array([0, 0, 0, 1]) },
1621
- scale: { type: "array<f32, 3>", default: new Float32Array([1, 1, 1]) },
1622
- // `world` is field-level transient (D-5): scene collect skips it. The resolved
1623
- // world mat4 is derived by the propagate kernel from the persisted local TRS
1624
- // each frame, so serializing it would store reconstructable data (SSOT: local
1625
- // TRS). Round-trip re-derives an equivalent world on the first propagate pass.
1626
- world: { type: "array<f32, 16>", default: IDENTITY_MAT4, transient: true }
1627
- });
1628
-
1629
- // src/instances/collect-profile.ts
1630
- var SCENE_COLLECT_PROFILE = Object.freeze({
1631
- includeComponent: (_componentName, transient) => !transient,
1632
- includeField: (_componentName, _fieldName, transient) => !transient
1633
- });
1634
-
1635
- // src/instances/externalization.ts
1636
- function sharedKind(type) {
1637
- if (type?.startsWith("shared<")) return "one";
1638
- if (type?.startsWith("array<shared<")) return "many";
1639
- return void 0;
1640
- }
1641
- function overrideGuids(override, resolveSchema) {
1642
- const schema = resolveSchema(override.comp);
1643
- 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) : [];
1644
- return values.flatMap(([field, value]) => {
1645
- const kind = sharedKind(schema?.[field]);
1646
- if (kind === "one" && typeof value === "string") return [{ field, guid: value }];
1647
- if (kind === "many" && Array.isArray(value)) {
1648
- return value.flatMap((item) => typeof item === "string" ? [{ field, guid: item }] : []);
1649
- }
1650
- return [];
1651
- });
1652
- }
1653
- function externalizeSceneAsset(scene, resolveSchema) {
1654
- const refs = [];
1655
- const indexByGuid = /* @__PURE__ */ new Map();
1656
- const addRef = (guid, sourceField, sceneEntityId) => {
1657
- const prior = indexByGuid.get(guid);
1658
- if (prior !== void 0) return prior;
1659
- const index = refs.length;
1660
- refs.push({ guid, sourceField, ...sceneEntityId === void 0 ? {} : { sceneEntityId } });
1661
- indexByGuid.set(guid, index);
1662
- return index;
1663
- };
1664
- const entities = scene.entities.map((entity) => {
1665
- const components = {};
1666
- for (const componentName of Object.keys(entity.components)) {
1667
- const schema = resolveSchema(componentName);
1668
- const source = entity.components[componentName];
1669
- if (source === void 0) continue;
1670
- const fields = {};
1671
- for (const fieldName of Object.keys(source)) {
1672
- const value = source[fieldName];
1673
- if (value === void 0) continue;
1674
- const kind = sharedKind(schema?.[fieldName]);
1675
- if (kind === "one" && typeof value === "string") {
1676
- fields[fieldName] = addRef(value, { componentName, fieldName }, entity.localId);
1677
- } else if (kind === "many" && Array.isArray(value)) {
1678
- fields[fieldName] = value.map(
1679
- (item, arrayIndex) => typeof item === "string" ? addRef(item, { componentName, fieldName, arrayIndex }, entity.localId) : item
1680
- );
1681
- } else {
1682
- fields[fieldName] = value;
1683
- }
1684
- }
1685
- if (Object.keys(fields).length > 0 || Object.keys(schema ?? {}).length === 0) {
1686
- components[componentName] = fields;
1687
- }
1688
- }
1689
- return { localId: entity.localId, components };
1690
- });
1691
- const mounts = scene.mounts?.map((mount) => {
1692
- const source = typeof mount.source === "string" ? addRef(
1693
- mount.source,
1694
- { componentName: "SceneInstance", fieldName: "source" },
1695
- mount.localId
1696
- ) : mount.source;
1697
- for (const { field, guid } of (mount.overrides ?? []).flatMap(
1698
- (override) => overrideGuids(override, resolveSchema)
1699
- )) {
1700
- addRef(guid, { componentName: "SceneInstance", fieldName: `overrides.${field}` });
1701
- }
1702
- return {
1703
- localId: mount.localId,
1704
- source,
1705
- memberFirst: mount.memberFirst,
1706
- memberCount: mount.memberCount,
1707
- ...mount.parent === void 0 ? {} : { parent: mount.parent },
1708
- ...mount.publicationFence === void 0 ? {} : { publicationFence: mount.publicationFence },
1709
- ...mount.overrides === void 0 ? {} : { overrides: mount.overrides.map((item) => ({ ...item })) }
1710
- };
1711
- });
1712
- for (const [arrayIndex, guid] of (scene.skinGuids ?? []).entries()) {
1713
- if (typeof guid !== "string") return err({ field: "skinGuids", value: guid });
1714
- addRef(guid, { componentName: "<scene>", fieldName: "skinGuids", arrayIndex });
1715
- }
1716
- return ok({
1717
- payload: {
1718
- entities,
1719
- ...mounts === void 0 || mounts.length === 0 ? {} : { mounts },
1720
- ...scene.skinGuids === void 0 ? {} : { skinGuids: scene.skinGuids.map((guid) => indexByGuid.get(guid)) }
1721
- },
1722
- refs
1723
- });
1724
- }
1725
-
1726
1294
  // src/systems/propagate-transforms.ts
1727
1295
  import {
1728
1296
  defineSystem,
@@ -2123,20 +1691,16 @@ export {
2123
1691
  MorphWeights,
2124
1692
  Name,
2125
1693
  PROPAGATE_TRANSFORMS_SYSTEM,
2126
- SCENE_ASSET_MESH_DEFAULT_RESOLVER_RESOURCE_KEY,
2127
- SCENE_ASSET_SKIN_RESOLVER_RESOURCE_KEY,
2128
1694
  SCENE_COLLECT_PROFILE,
2129
1695
  SceneError,
2130
1696
  Transform,
2131
1697
  TransformSet,
1698
+ collectSubtree,
2132
1699
  externalizeSceneAsset,
2133
1700
  projectHierarchy,
2134
- projectSceneAsset,
2135
1701
  propagateTransforms,
2136
1702
  registerPropagateTransforms,
2137
1703
  sceneAssetContribution,
2138
- sceneAssetDecoder,
2139
- sceneAssetKind,
2140
1704
  scenePlugin,
2141
1705
  worldApplyMountOverride,
2142
1706
  worldBuildSceneEntityComponentDatas,
@@ -2150,6 +1714,7 @@ export {
2150
1714
  worldInstantiateSceneAsset,
2151
1715
  worldInstantiateSceneAssetFlat,
2152
1716
  worldInstantiateSceneFlat,
1717
+ worldInstantiateScenePayload,
2153
1718
  worldInstantiateSceneRec,
2154
1719
  worldMountOverridesToStateMap,
2155
1720
  worldReattachSceneMember,
@@ -2158,7 +1723,6 @@ export {
2158
1723
  worldResolveSceneAsset,
2159
1724
  worldResolveSceneInstanceStatePayload,
2160
1725
  worldSetSceneAssetResolver,
2161
- worldSetSceneInstantiateHook,
2162
1726
  worldSetSceneOverride,
2163
1727
  worldSpawnMountEntity,
2164
1728
  worldSpawnSceneMembers,