@forgeax/engine-runtime 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/dist/collect-scene-asset.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +480 -15
- package/dist/index.mjs.map +1 -1
- package/dist/scene-utils/mount-override-fold.d.ts +3 -1
- package/dist/scene-utils/mount-override-fold.d.ts.map +1 -1
- package/package.json +37 -30
- package/src/__tests__/animation-graph-collect-roundtrip.test.ts +4 -2
- package/src/__tests__/asset-registry-aabb.cases.unit.test.ts +5 -4
- package/src/__tests__/asset-registry-guid-reverse.test.ts +22 -27
- package/src/__tests__/asset-registry-mounts.test.ts +174 -734
- package/src/__tests__/asset-registry-scene.cases.unit.test.ts +9 -18
- package/src/__tests__/asset-registry.recursive.spec.ts +23 -29
- package/src/__tests__/camera-ortho.cases.unit.test.ts +12 -3
- package/src/__tests__/collect-scene-asset.test.ts +96 -113
- package/src/__tests__/collect-scene-mount-collapse.test.ts +64 -66
- package/src/__tests__/collect-transient-children-absent.test.ts +11 -17
- package/src/__tests__/collect-transient-roundtrip.test.ts +12 -13
- package/src/__tests__/collect-transient-skip.test.ts +20 -27
- package/src/__tests__/components.test-d.ts +17 -1
- package/src/__tests__/components.unit.test.ts +75 -27
- package/src/__tests__/composite-skybox-cross-world.browser.test.ts +4 -2
- package/src/__tests__/dawn/instances-per-instance-pbr.dawn.test.ts +8 -4
- package/src/__tests__/dawn/instances-shadow.dawn.test.ts +4 -1
- package/src/__tests__/dawn/instances-uniform-fallback.dawn.test.ts +66 -92
- package/src/__tests__/dawn/material-cooked-fixture.dawn.test.ts +200 -4
- package/src/__tests__/errors.unit.test.ts +13 -0
- package/src/__tests__/feature-depth-material.dawn.test.ts +23 -0
- package/src/__tests__/feature-depth-material.fixture.ts +473 -0
- package/src/__tests__/fullscreen-post-process-pass.dawn.test.ts +14 -14
- package/src/__tests__/geometry.unit.test.ts +42 -43
- package/src/__tests__/gpu-resource-release.cases.unit.test.ts +16 -9
- package/src/__tests__/handle-migration.test.ts +0 -1
- package/src/__tests__/instances-renderer-lifecycle.integration.test.ts +20 -14
- package/src/__tests__/instances.test-d.ts +13 -52
- package/src/__tests__/instantiate-mount-deferred-wiring.test.ts +42 -80
- package/src/__tests__/lazy-catalog.cases.unit.test.ts +17 -3
- package/src/__tests__/loader-registry.cases.unit.test.ts +4 -4
- package/src/__tests__/material-publication.browser.test.ts +29 -0
- package/src/__tests__/material-publication.commands.ts +23 -0
- package/src/__tests__/material-publication.dawn.test.ts +24 -0
- package/src/__tests__/material-publication.fixture.ts +349 -0
- package/src/__tests__/material-publication.server.ts +109 -0
- package/src/__tests__/parse-scene-payload-refs.cases.unit.test.ts +73 -96
- package/src/__tests__/pbr-pipeline.unit.test.ts +25 -11
- package/src/__tests__/point-light-shadow.browser.test.ts +8 -2
- package/src/__tests__/point-light-shadow.unit.test.ts +37 -8
- package/src/__tests__/record-all-topology.cases.unit.test.ts +52 -0
- package/src/__tests__/record-draw-branch.cases.unit.test.ts +1 -0
- package/src/__tests__/record-worldid-isolation.test.ts +47 -28
- package/src/__tests__/render-error-exhaustive.test-d.ts +24 -0
- package/src/__tests__/render-system-extract-dispatch-ordering.test.ts +15 -4
- package/src/__tests__/render-system-extract.test.ts +61 -0
- package/src/__tests__/render-system-extract.unit.test.ts +21 -7
- package/src/__tests__/render-system-fold-bucket.unit.test.ts +33 -1
- package/src/__tests__/render-system-fold-material.unit.test.ts +32 -6
- package/src/__tests__/render-system-fold-modegate.unit.test.ts +26 -4
- package/src/__tests__/render-system-fold.dawn.test.ts +4 -1
- package/src/__tests__/render-system-mega.test.ts +50 -62
- package/src/__tests__/renderer-device-loss-provider-feasibility.dawn.test.ts +0 -1
- package/src/__tests__/renderer-device-loss-recovery.dawn.test.ts +0 -3
- package/src/__tests__/renderer-draw-world.cases.unit.test.ts +16 -513
- package/src/__tests__/resolve-scene-guids.cases.unit.test.ts +48 -43
- package/src/__tests__/rhi-null-command-flow.unit.test.ts +9 -7
- package/src/__tests__/roots-to-scene-asset.test.ts +27 -28
- package/src/__tests__/roundtrip-primitive.test.ts +24 -23
- package/src/__tests__/scene-equirect-roundtrip.test.ts +5 -16
- package/src/__tests__/scene-instantiate-cycle-runtime.test.ts +13 -29
- package/src/__tests__/scene-mount-fixed-point.test.ts +104 -227
- package/src/__tests__/scene-mount-roundtrip.test.ts +95 -961
- package/src/__tests__/scene-mount-wrapper-roundtrip.test.ts +36 -182
- package/src/__tests__/scene-sugar-instantiate.test.ts +20 -15
- package/src/__tests__/serialize-mounts-roundtrip.test.ts +108 -492
- package/src/__tests__/serialize-scene-asset.test.ts +36 -50
- package/src/__tests__/shadow-caster-empty-schema.test.ts +3 -1
- package/src/__tests__/shadow-fields-observable.dawn.test.ts +34 -19
- package/src/__tests__/shadow-mesh-ssbo-order.test.ts +5 -12
- package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +6 -4
- package/src/__tests__/skin-m2-extract-happy.cases.unit.test.ts +50 -2
- package/src/__tests__/skin-m2-extract-joint-error.cases.unit.test.ts +50 -2
- package/src/__tests__/skin-m2-extract-null-assets.cases.unit.test.ts +50 -2
- package/src/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.ts +50 -2
- package/src/__tests__/skin-palette-extract.cases.unit.test.ts +2 -0
- package/src/__tests__/skinned-shadow-mixed.dawn.test.ts +93 -48
- package/src/__tests__/skylight-pipeline-layout.cases.unit.test.ts +6 -2
- package/src/__tests__/sparse-tag-scene-roundtrip.test.ts +1 -1
- package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +11 -3
- package/src/__tests__/template-game-default-api-contract.test.ts +21 -21
- package/src/__tests__/tonemap-hdr-target.cases.unit.test.ts +8 -5
- package/src/__tests__/tonemap-pipeline-split.cases.unit.test.ts +3 -0
- package/src/__tests__/transient-fields-collect.test.ts +4 -4
- package/src/__tests__/transient-runtime-behavior.test.ts +9 -9
- package/src/__tests__/vfx-depth-material.dawn.test.ts +42 -0
- package/src/__tests__/vfx-mesh-lighting.browser.test.ts +16 -0
- package/src/__tests__/vfx-mesh-lighting.commands.ts +14 -0
- package/src/__tests__/vfx-mesh-lighting.dawn.test.ts +16 -0
- package/src/__tests__/vfx-mesh-lighting.fixture.ts +883 -0
- package/src/__tests__/wave1-dynamic-geometry.browser.test.ts +326 -0
- package/src/__tests__/wave1-rendering-materials.browser.test.ts +617 -0
- package/src/__tests__/wave1-rendering-p0.browser.test.ts +751 -0
- package/src/__tests__/wave1-rendering-recovery.browser.test.ts +666 -0
- package/src/__tests__/wave1-shadow-diagnostic.browser.test.ts +582 -0
- package/src/__tests__/world-scene-despawn.test.ts +8 -8
- package/src/__tests__/world-scene-instantiate.test.ts +23 -29
- package/src/__tests__/world-scene-nested.test.ts +90 -163
- package/src/__tests__/world-scene-override.test.ts +17 -16
- package/src/__tests__/zero-compression-zero-load.integration.test.ts +1 -0
- package/src/collect-scene-asset.ts +637 -19
- package/src/components/__tests__/sprite-animation.test-d.ts +2 -2
- package/src/index.ts +1 -0
- package/src/scene-utils/mount-override-fold.ts +5 -14
- package/src/__tests__/node-shims.d.ts +0 -31
|
@@ -53,12 +53,7 @@ describe('world.instantiateScene basic (w17)', () => {
|
|
|
53
53
|
const world = new World();
|
|
54
54
|
const asset: SceneAsset = {
|
|
55
55
|
kind: 'scene',
|
|
56
|
-
entities: [
|
|
57
|
-
{
|
|
58
|
-
localId: 0 as never,
|
|
59
|
-
components: { Transform: { pos: [1, 2, 3] } },
|
|
60
|
-
},
|
|
61
|
-
],
|
|
56
|
+
entities: { 'entity-0': { components: { Transform: { pos: [1, 2, 3] } } } },
|
|
62
57
|
};
|
|
63
58
|
const handle = registerSceneAsset(world, asset);
|
|
64
59
|
const r = instantiateScene(world, handle);
|
|
@@ -72,11 +67,11 @@ describe('world.instantiateScene basic (w17)', () => {
|
|
|
72
67
|
const world = new World();
|
|
73
68
|
const asset: SceneAsset = {
|
|
74
69
|
kind: 'scene',
|
|
75
|
-
entities:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
entities: {
|
|
71
|
+
'entity-0': { components: { Transform: {} } },
|
|
72
|
+
'entity-1': { components: { Transform: {} } },
|
|
73
|
+
'entity-2': { components: { Transform: {} } },
|
|
74
|
+
},
|
|
80
75
|
};
|
|
81
76
|
const handle = registerSceneAsset(world, asset);
|
|
82
77
|
const r = instantiateScene(world, handle);
|
|
@@ -93,12 +88,7 @@ describe('world.instantiateScene basic (w17)', () => {
|
|
|
93
88
|
const world = new World();
|
|
94
89
|
const asset: SceneAsset = {
|
|
95
90
|
kind: 'scene',
|
|
96
|
-
entities: [
|
|
97
|
-
{
|
|
98
|
-
localId: 0 as never,
|
|
99
|
-
components: { Transform: { pos: [7, 8, 9] } },
|
|
100
|
-
},
|
|
101
|
-
],
|
|
91
|
+
entities: { 'entity-0': { components: { Transform: { pos: [7, 8, 9] } } } },
|
|
102
92
|
};
|
|
103
93
|
const handle = registerSceneAsset(world, asset);
|
|
104
94
|
const r = instantiateScene(world, handle);
|
|
@@ -124,7 +114,7 @@ describe('world.instantiateScene basic (w17)', () => {
|
|
|
124
114
|
|
|
125
115
|
const asset: SceneAsset = {
|
|
126
116
|
kind: 'scene',
|
|
127
|
-
entities:
|
|
117
|
+
entities: { 'entity-0': { components: { Transform: {} } } },
|
|
128
118
|
};
|
|
129
119
|
const handle = registerSceneAsset(world, asset);
|
|
130
120
|
const r = instantiateScene(world, handle, parent);
|
|
@@ -141,7 +131,7 @@ describe('world.getSceneAssetForInstance (w22)', () => {
|
|
|
141
131
|
const world = new World();
|
|
142
132
|
const asset: SceneAsset = {
|
|
143
133
|
kind: 'scene',
|
|
144
|
-
entities:
|
|
134
|
+
entities: { 'entity-0': { components: { Transform: {} } } },
|
|
145
135
|
};
|
|
146
136
|
const handle = registerSceneAsset(world, asset);
|
|
147
137
|
const r = instantiateScene(world, handle);
|
|
@@ -173,16 +163,20 @@ describe('world.instantiateScene perf (w23) — 100 entities x 5 components', ()
|
|
|
173
163
|
void C3;
|
|
174
164
|
void C4;
|
|
175
165
|
const N = 100;
|
|
176
|
-
const entities =
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
166
|
+
const entities = Object.fromEntries(
|
|
167
|
+
Array.from({ length: N }, (_, i) => [
|
|
168
|
+
`entity-${i}`,
|
|
169
|
+
{
|
|
170
|
+
components: {
|
|
171
|
+
Transform: {},
|
|
172
|
+
SceneNestPerfC1: { a: i },
|
|
173
|
+
SceneNestPerfC2: { a: i },
|
|
174
|
+
SceneNestPerfC3: { a: i },
|
|
175
|
+
SceneNestPerfC4: { a: i },
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
]),
|
|
179
|
+
);
|
|
186
180
|
const asset: SceneAsset = { kind: 'scene', entities };
|
|
187
181
|
const handle = registerSceneAsset(world, asset, [C1, C2, C3, C4]);
|
|
188
182
|
const t0 = performance.now();
|
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
// feat-20260608-scene-nesting-ecs-fication M2 / w18 (red phase) — nested /
|
|
3
|
-
// cross-boundary / deep / multi-mount cases.
|
|
4
|
-
//
|
|
5
|
-
// Test file location rationale: same as world-scene-instantiate.test.ts —
|
|
6
|
-
// uses SceneInstance / Transform tokens (defined in @forgeax/engine-runtime),
|
|
7
|
-
// so the test physically lives in runtime; the World methods themselves
|
|
8
|
-
// resolve to @forgeax/engine-ecs.
|
|
9
|
-
//
|
|
10
|
-
// AC anchors:
|
|
11
|
-
// - AC-23 (double-nested A->B->C: query<SceneInstance>() returns >=3 roots)
|
|
12
|
-
// - AC-24 (cross-boundary: outer entities[].ChildOf.parent points into a
|
|
13
|
-
// mount sub-region)
|
|
14
|
-
// - AC-25 (deep nesting >=5 layers does not stack-overflow)
|
|
15
|
-
// - AC-26 (same prefab mounted multiple times: mappings disjoint, overrides
|
|
16
|
-
// do not leak across mount instances)
|
|
17
|
-
|
|
18
|
-
import { defineComponent, World } from '@forgeax/engine-ecs';
|
|
1
|
+
import { World } from '@forgeax/engine-ecs';
|
|
19
2
|
import { SceneInstance } from '@forgeax/engine-render';
|
|
3
|
+
import * as SceneOwner from '@forgeax/engine-scene';
|
|
20
4
|
import { ChildOf } from '@forgeax/engine-scene';
|
|
21
5
|
import type { Handle, SceneAsset } from '@forgeax/engine-types';
|
|
22
6
|
import { err, ok } from '@forgeax/engine-types';
|
|
@@ -28,203 +12,146 @@ function registerSceneAsset(world: World, asset: SceneAsset): Handle<'SceneAsset
|
|
|
28
12
|
return world.allocSharedRef('SceneAsset', asset);
|
|
29
13
|
}
|
|
30
14
|
|
|
31
|
-
describe('
|
|
32
|
-
it('
|
|
15
|
+
describe('keyed nested SceneAsset instances', () => {
|
|
16
|
+
it('double-nested A -> B -> C materialises three SceneInstance roots', () => {
|
|
33
17
|
const world = new World();
|
|
34
|
-
// Bottom asset C: 1 entity
|
|
35
18
|
const assetC: SceneAsset = {
|
|
36
19
|
kind: 'scene',
|
|
37
|
-
entities:
|
|
20
|
+
entities: { 'entity-0': { components: { Transform: {} } } },
|
|
38
21
|
};
|
|
39
22
|
const handleC = registerSceneAsset(world, assetC);
|
|
40
|
-
// Middle asset B: 1 entity + 1 mount of C
|
|
41
23
|
const assetB: SceneAsset = {
|
|
42
24
|
kind: 'scene',
|
|
43
|
-
entities:
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
source: 0,
|
|
48
|
-
memberFirst: 2 as never,
|
|
49
|
-
memberCount: 1,
|
|
50
|
-
},
|
|
51
|
-
],
|
|
25
|
+
entities: {
|
|
26
|
+
'entity-0': { components: { Transform: {} } },
|
|
27
|
+
child: { components: {}, instance: { source: 'C' } },
|
|
28
|
+
},
|
|
52
29
|
};
|
|
53
30
|
const handleB = registerSceneAsset(world, assetB);
|
|
54
|
-
// Outer asset A: 1 entity + 1 mount of B
|
|
55
31
|
const assetA: SceneAsset = {
|
|
56
32
|
kind: 'scene',
|
|
57
|
-
entities:
|
|
58
|
-
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
source: 0,
|
|
62
|
-
memberFirst: 2 as never,
|
|
63
|
-
memberCount: 3,
|
|
64
|
-
},
|
|
65
|
-
],
|
|
33
|
+
entities: {
|
|
34
|
+
'entity-0': { components: { Transform: {} } },
|
|
35
|
+
child: { components: {}, instance: { source: 'B' } },
|
|
36
|
+
},
|
|
66
37
|
};
|
|
67
38
|
const handleA = registerSceneAsset(world, assetA);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// resolver directly by stuffing handles into a Map keyed by parent.
|
|
74
|
-
// The test relies on World using the same registry the SceneAsset was
|
|
75
|
-
// alloc'd into; mount.source -> child handle resolution is done by
|
|
76
|
-
// looking up `parent.refs[source]` in the implementation. Since this
|
|
77
|
-
// unit test creates raw POD assets in-memory without `refs`, the
|
|
78
|
-
// implementation must accept an explicit resolver arm — see
|
|
79
|
-
// _setSceneAssetResolver below.
|
|
80
|
-
SceneOwner.worldSetSceneAssetResolver(world, (sourceIdx, parentHandle) => {
|
|
81
|
-
void sourceIdx;
|
|
82
|
-
const parentManagedRaw = parentHandle as unknown as number;
|
|
83
|
-
// Parent A handle u32 -> handle B; parent B handle u32 -> handle C.
|
|
84
|
-
if (parentManagedRaw === (handleA as unknown as number)) return ok(handleB);
|
|
85
|
-
if (parentManagedRaw === (handleB as unknown as number)) return ok(handleC);
|
|
39
|
+
SceneOwner.worldSetSceneAssetResolver(world, (source, parentHandle) => {
|
|
40
|
+
if (typeof source === 'number') return ok(source as Handle<'SceneAsset', 'shared'>);
|
|
41
|
+
const parent = Number(parentHandle);
|
|
42
|
+
if (parent === Number(handleA) && source === 'B') return ok(handleB);
|
|
43
|
+
if (parent === Number(handleB) && source === 'C') return ok(handleC);
|
|
86
44
|
return err({ code: 'asset-not-found' });
|
|
87
45
|
});
|
|
88
46
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
if (!
|
|
92
|
-
|
|
47
|
+
const result = SceneOwner.worldInstantiateScene(world, handleA);
|
|
48
|
+
if (!result.ok) throw new Error(JSON.stringify(result.error));
|
|
49
|
+
if (!result.ok) return;
|
|
93
50
|
let count = 0;
|
|
94
51
|
const query = world.query({ with: [SceneInstance] }).unwrap();
|
|
95
52
|
for (const _row of query) count += 1;
|
|
96
53
|
expect(count).toBeGreaterThanOrEqual(3);
|
|
97
54
|
});
|
|
98
|
-
});
|
|
99
55
|
|
|
100
|
-
|
|
101
|
-
it('5-layer chain instantiates successfully', () => {
|
|
56
|
+
it('five keyed instance layers recurse without stack overflow', () => {
|
|
102
57
|
const world = new World();
|
|
103
|
-
// Build chain L0 ... L5 where Lk mounts L(k+1).
|
|
104
|
-
// R2/B-2: mount.memberCount must equal child.totalSlots; recompute
|
|
105
|
-
// bottom-up. L5 has no mounts -> totalSlots=1; each k<5 has
|
|
106
|
-
// 1 entity + 1 mount.localId + memberCount(=child.totalSlots) slots.
|
|
107
58
|
const handles: Handle<'SceneAsset', 'shared'>[] = [];
|
|
108
|
-
let
|
|
109
|
-
for (let k = 5; k >= 0; k -= 1) {
|
|
59
|
+
for (let layer = 5; layer >= 0; layer -= 1) {
|
|
110
60
|
const asset: SceneAsset = {
|
|
111
61
|
kind: 'scene',
|
|
112
|
-
entities:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
?
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
source: 0,
|
|
119
|
-
memberFirst: 2 as never,
|
|
120
|
-
memberCount: childTotalSlots,
|
|
121
|
-
},
|
|
122
|
-
]
|
|
123
|
-
: [],
|
|
62
|
+
entities: {
|
|
63
|
+
'entity-0': { components: { Transform: {} } },
|
|
64
|
+
...(layer < 5
|
|
65
|
+
? { child: { components: {}, instance: { source: `layer-${layer + 1}` } } }
|
|
66
|
+
: {}),
|
|
67
|
+
},
|
|
124
68
|
};
|
|
125
69
|
handles.unshift(registerSceneAsset(world, asset));
|
|
126
|
-
// Update for the parent layer above.
|
|
127
|
-
// totalSlots = 1 entity + 1 mount.localId + memberCount slots
|
|
128
|
-
childTotalSlots = k < 5 ? 1 + 1 + childTotalSlots : 1;
|
|
129
70
|
}
|
|
130
|
-
SceneOwner.worldSetSceneAssetResolver(world, (
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
if (raw === (handles[k] as unknown as number)) {
|
|
135
|
-
const next = handles[k + 1];
|
|
136
|
-
if (next === undefined) break;
|
|
137
|
-
return ok(next);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return err({ code: 'asset-not-found' });
|
|
71
|
+
SceneOwner.worldSetSceneAssetResolver(world, (_source, parentHandle) => {
|
|
72
|
+
const parentIndex = handles.findIndex((handle) => Number(handle) === Number(parentHandle));
|
|
73
|
+
const next = handles[parentIndex + 1];
|
|
74
|
+
return next === undefined ? err({ code: 'asset-not-found' }) : ok(next);
|
|
141
75
|
});
|
|
142
|
-
const
|
|
143
|
-
|
|
76
|
+
const result = SceneOwner.worldInstantiateScene(
|
|
77
|
+
world,
|
|
78
|
+
handles[0] as Handle<'SceneAsset', 'shared'>,
|
|
79
|
+
);
|
|
80
|
+
if (!result.ok) throw new Error(JSON.stringify(result.error));
|
|
144
81
|
});
|
|
145
|
-
});
|
|
146
82
|
|
|
147
|
-
|
|
148
|
-
it('two mounts of the same child each get their own member entities', () => {
|
|
83
|
+
it('same child instance twice gets disjoint child mappings', () => {
|
|
149
84
|
const world = new World();
|
|
150
85
|
const child: SceneAsset = {
|
|
151
86
|
kind: 'scene',
|
|
152
|
-
entities:
|
|
87
|
+
entities: { 'entity-0': { components: { Transform: { pos: [99, 0, 0] } } } },
|
|
153
88
|
};
|
|
154
89
|
const childHandle = registerSceneAsset(world, child);
|
|
155
|
-
// Parent: 1 entity + 2 mounts of `child`
|
|
156
90
|
const parent: SceneAsset = {
|
|
157
91
|
kind: 'scene',
|
|
158
|
-
entities:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
],
|
|
92
|
+
entities: {
|
|
93
|
+
first: { components: {}, instance: { source: 'child' } },
|
|
94
|
+
second: { components: {}, instance: { source: 'child' } },
|
|
95
|
+
},
|
|
163
96
|
};
|
|
164
97
|
const parentHandle = registerSceneAsset(world, parent);
|
|
165
|
-
SceneOwner.worldSetSceneAssetResolver(world, (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
98
|
+
SceneOwner.worldSetSceneAssetResolver(world, (source) => {
|
|
99
|
+
return typeof source === 'number'
|
|
100
|
+
? ok(source as Handle<'SceneAsset', 'shared'>)
|
|
101
|
+
: source === 'child'
|
|
102
|
+
? ok(childHandle)
|
|
103
|
+
: err({ code: 'asset-not-found' });
|
|
169
104
|
});
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
if (!
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const m3 = inst.value.mapping[3];
|
|
180
|
-
const m4 = inst.value.mapping[4];
|
|
181
|
-
expect(m3).toBeDefined();
|
|
182
|
-
expect(m4).toBeDefined();
|
|
183
|
-
expect(m3).not.toBe(m4);
|
|
105
|
+
const result = SceneOwner.worldInstantiateScene(world, parentHandle);
|
|
106
|
+
if (!result.ok) throw new Error(JSON.stringify(result.error));
|
|
107
|
+
if (!result.ok) return;
|
|
108
|
+
const instance = world.get(result.value.root, SceneInstance);
|
|
109
|
+
expect(instance.ok).toBe(true);
|
|
110
|
+
if (!instance.ok) return;
|
|
111
|
+
expect(instance.value.mapping[2]).toBeDefined();
|
|
112
|
+
expect(instance.value.mapping[3]).toBeDefined();
|
|
113
|
+
expect(instance.value.mapping[2]).not.toBe(instance.value.mapping[3]);
|
|
184
114
|
});
|
|
185
|
-
});
|
|
186
115
|
|
|
187
|
-
|
|
188
|
-
it('outer entity carries ChildOf with parent=mapping[memberFirst+k]', () => {
|
|
116
|
+
it('parent components can address a nested child by a key path', () => {
|
|
189
117
|
const world = new World();
|
|
190
118
|
const child: SceneAsset = {
|
|
191
119
|
kind: 'scene',
|
|
192
|
-
entities:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
120
|
+
entities: {
|
|
121
|
+
'entity-0': { components: { Transform: {} } },
|
|
122
|
+
'entity-1': { components: { Transform: {} } },
|
|
123
|
+
},
|
|
196
124
|
};
|
|
197
125
|
const childHandle = registerSceneAsset(world, child);
|
|
198
|
-
// Outer scene: entity[0] is independent root; entity[1] declares
|
|
199
|
-
// ChildOf {parent: 3} — referencing the FIRST member of the mount at
|
|
200
|
-
// localId=2 (memberFirst=3). The runtime must remap localId=3 to the
|
|
201
|
-
// live spawned member entity.
|
|
202
126
|
const parent: SceneAsset = {
|
|
203
127
|
kind: 'scene',
|
|
204
|
-
entities:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
128
|
+
entities: {
|
|
129
|
+
'entity-0': { components: { Transform: {} } },
|
|
130
|
+
'entity-1': {
|
|
131
|
+
components: { Transform: {}, ChildOf: { parent: ['child', 'entity-0'] } },
|
|
132
|
+
},
|
|
133
|
+
child: { components: {}, instance: { source: 'child' } },
|
|
134
|
+
},
|
|
209
135
|
};
|
|
210
136
|
const parentHandle = registerSceneAsset(world, parent);
|
|
211
|
-
SceneOwner.worldSetSceneAssetResolver(world, () =>
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
expect(
|
|
223
|
-
if (!
|
|
224
|
-
|
|
137
|
+
SceneOwner.worldSetSceneAssetResolver(world, (source) =>
|
|
138
|
+
typeof source === 'number'
|
|
139
|
+
? ok(source as Handle<'SceneAsset', 'shared'>)
|
|
140
|
+
: source === 'child'
|
|
141
|
+
? ok(childHandle)
|
|
142
|
+
: err({ code: 'asset-not-found' }),
|
|
143
|
+
);
|
|
144
|
+
const result = SceneOwner.worldInstantiateScene(world, parentHandle);
|
|
145
|
+
expect(result.ok).toBe(true);
|
|
146
|
+
if (!result.ok) return;
|
|
147
|
+
const instance = world.get(result.value.root, SceneInstance);
|
|
148
|
+
expect(instance.ok).toBe(true);
|
|
149
|
+
if (!instance.ok) return;
|
|
150
|
+
const outer = instance.value.mapping[1] as number;
|
|
151
|
+
const nested = instance.value.mapping[3] as number;
|
|
152
|
+
const childOf = world.get(outer as never, ChildOf);
|
|
153
|
+
expect(childOf.ok).toBe(true);
|
|
154
|
+
if (!childOf.ok) return;
|
|
155
|
+
expect(childOf.value.parent as unknown as number).toBe(nested);
|
|
225
156
|
});
|
|
226
157
|
});
|
|
227
|
-
|
|
228
|
-
// Force-reference defineComponent so a future TS pruning pass keeps the
|
|
229
|
-
// import alive even if all explicit calls are inlined into a deeper helper.
|
|
230
|
-
void defineComponent;
|
|
@@ -10,7 +10,7 @@ import * as SceneOwner from '@forgeax/engine-scene';
|
|
|
10
10
|
// AND the state map population);
|
|
11
11
|
// - AC-20 setSceneOverride writes the override + readback equals value;
|
|
12
12
|
// removeSceneOverride rolls back to the source SceneAsset value.
|
|
13
|
-
// - detach/reattach mark the
|
|
13
|
+
// - detach/reattach mark the runtime slot in state.detachedLocalIds; member
|
|
14
14
|
// entity remains alive.
|
|
15
15
|
// - setSceneOverride type mismatch -> Err 'scene-override-type-mismatch'
|
|
16
16
|
// with detail.{ comp, field, expectedType, actualType }.
|
|
@@ -33,24 +33,25 @@ describe('AC-19 mount-time override apply (w20)', () => {
|
|
|
33
33
|
const world = new World();
|
|
34
34
|
const child: SceneAsset = {
|
|
35
35
|
kind: 'scene',
|
|
36
|
-
entities:
|
|
36
|
+
entities: { 'entity-0': { components: { Transform: { pos: [1, 0, 0] } } } },
|
|
37
37
|
};
|
|
38
38
|
const childHandle = registerSceneAsset(world, child);
|
|
39
39
|
const parent: SceneAsset = {
|
|
40
40
|
kind: 'scene',
|
|
41
|
-
entities:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
overrides: [{ localId: 1 as never, comp: 'Transform', field: 'pos', value: [42, 0, 0] }],
|
|
41
|
+
entities: {
|
|
42
|
+
instance: {
|
|
43
|
+
components: {},
|
|
44
|
+
instance: {
|
|
45
|
+
source: 'child',
|
|
46
|
+
overrides: [{ target: ['entity-0'], components: { Transform: { pos: [42, 0, 0] } } }],
|
|
47
|
+
},
|
|
49
48
|
},
|
|
50
|
-
|
|
49
|
+
},
|
|
51
50
|
};
|
|
52
51
|
const parentHandle = registerSceneAsset(world, parent);
|
|
53
|
-
SceneOwner.worldSetSceneAssetResolver(world, () =>
|
|
52
|
+
SceneOwner.worldSetSceneAssetResolver(world, (source) =>
|
|
53
|
+
source === 'child' ? ok(childHandle) : ok(childHandle),
|
|
54
|
+
);
|
|
54
55
|
const r = SceneOwner.worldInstantiateScene(world, parentHandle);
|
|
55
56
|
if (!r.ok) throw new Error('instantiate failed');
|
|
56
57
|
const inst = world.get(r.value.root, SceneInstance);
|
|
@@ -79,7 +80,7 @@ describe('world.setSceneOverride (w20)', () => {
|
|
|
79
80
|
const world = new World();
|
|
80
81
|
const asset: SceneAsset = {
|
|
81
82
|
kind: 'scene',
|
|
82
|
-
entities:
|
|
83
|
+
entities: { 'entity-0': { components: { Transform: { pos: [1, 0, 0] } } } },
|
|
83
84
|
};
|
|
84
85
|
const handle = registerSceneAsset(world, asset);
|
|
85
86
|
const r = SceneOwner.worldInstantiateScene(world, handle);
|
|
@@ -112,7 +113,7 @@ describe('world.setSceneOverride (w20)', () => {
|
|
|
112
113
|
registerSceneComponents(world, [Hp]);
|
|
113
114
|
const asset: SceneAsset = {
|
|
114
115
|
kind: 'scene',
|
|
115
|
-
entities:
|
|
116
|
+
entities: { 'entity-0': { components: { SceneOverrideHp: {} } } },
|
|
116
117
|
};
|
|
117
118
|
const handle = registerSceneAsset(world, asset);
|
|
118
119
|
const r = SceneOwner.worldInstantiateScene(world, handle);
|
|
@@ -141,7 +142,7 @@ describe('world.removeSceneOverride (w20)', () => {
|
|
|
141
142
|
const world = new World();
|
|
142
143
|
const asset: SceneAsset = {
|
|
143
144
|
kind: 'scene',
|
|
144
|
-
entities:
|
|
145
|
+
entities: { 'entity-0': { components: { Transform: { pos: [5, 0, 0] } } } },
|
|
145
146
|
};
|
|
146
147
|
const handle = registerSceneAsset(world, asset);
|
|
147
148
|
const r = SceneOwner.worldInstantiateScene(world, handle);
|
|
@@ -176,7 +177,7 @@ describe('world.detachSceneMember + reattachSceneMember (w20)', () => {
|
|
|
176
177
|
const world = new World();
|
|
177
178
|
const asset: SceneAsset = {
|
|
178
179
|
kind: 'scene',
|
|
179
|
-
entities:
|
|
180
|
+
entities: { 'entity-0': { components: { Transform: {} } } },
|
|
180
181
|
};
|
|
181
182
|
const handle = registerSceneAsset(world, asset);
|
|
182
183
|
const r = SceneOwner.worldInstantiateScene(world, handle);
|