@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
|
@@ -1,202 +1,56 @@
|
|
|
1
|
-
// scene-mount-wrapper-roundtrip.test.ts — regression for the editor "Add to
|
|
2
|
-
// Scene" ghost-node accretion bug.
|
|
3
|
-
//
|
|
4
|
-
// The editor's whole-GLB Add-to-Scene (spawnGlbSceneAsMount) produces a scene
|
|
5
|
-
// pack shaped like:
|
|
6
|
-
// entities: [ ..., { localId: W, Name:"bed.glb", Transform, Children:[M] } ]
|
|
7
|
-
// mounts: [ { localId: M, source: <bedGUID>, memberCount: N, parent: W } ]
|
|
8
|
-
// i.e. an OWNED wrapper entity whose child is a nested mount. This is the exact
|
|
9
|
-
// on-disk shape after the first add+save.
|
|
10
|
-
//
|
|
11
|
-
// On RELOAD, world.instantiateScene re-materialises the mount as a plain "mount
|
|
12
|
-
// entity" (no Name, no SceneInstance — just Transform/ChildOf/Children) sitting
|
|
13
|
-
// BETWEEN the wrapper and the child SceneInstance root. On the NEXT collect that
|
|
14
|
-
// plain mount entity carries no SceneInstance, so rootsToSceneAsset emitted it
|
|
15
|
-
// as an OWNED (nameless) entity + a fresh mount — one extra ghost node per
|
|
16
|
-
// save→reload cycle. This test asserts collect is a FIXED POINT across two
|
|
17
|
-
// reload cycles.
|
|
18
|
-
|
|
19
1
|
import type { Asset } from '@forgeax/engine-assets-runtime';
|
|
20
2
|
import { AssetRegistry } from '@forgeax/engine-assets-runtime';
|
|
21
3
|
import { World } from '@forgeax/engine-ecs';
|
|
22
4
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
23
|
-
import {
|
|
5
|
+
import { SceneInstance } from '@forgeax/engine-render';
|
|
24
6
|
import type { Handle, SceneAsset } from '@forgeax/engine-types';
|
|
25
7
|
import { describe, expect, it } from 'vitest';
|
|
26
|
-
import { rootsToSceneAsset
|
|
8
|
+
import { rootsToSceneAsset } from '../collect-scene-asset';
|
|
27
9
|
import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
28
10
|
import { registerSceneComponents } from './helpers/register-scene-components';
|
|
29
11
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
function pg(s: string): AssetGuid {
|
|
37
|
-
const r = AssetGuid.parse(s);
|
|
38
|
-
if (!r.ok) throw new Error(`bad GUID: ${s}`);
|
|
39
|
-
return r.value;
|
|
40
|
-
}
|
|
41
|
-
function cat(reg: AssetRegistry, g: string, p: SceneAsset): void {
|
|
42
|
-
reg.catalog(pg(g), p as Asset);
|
|
43
|
-
}
|
|
44
|
-
function rs(w: World, a: SceneAsset): Handle<'SceneAsset', 'shared'> {
|
|
45
|
-
registerSceneComponents(w, [Name]);
|
|
46
|
-
return w.allocSharedRef('SceneAsset', a);
|
|
47
|
-
}
|
|
48
|
-
function accessParse(reg: AssetRegistry) {
|
|
49
|
-
const internal = reg as unknown as {
|
|
50
|
-
parseAssetPayload(kind: string, payload: Record<string, unknown>, refs?: string[]): unknown;
|
|
51
|
-
};
|
|
52
|
-
return (payload: Record<string, unknown>, refs?: readonly string[]): SceneAsset =>
|
|
53
|
-
internal.parseAssetPayload('scene', payload, refs as string[] | undefined) as SceneAsset;
|
|
12
|
+
const CHILD = '11111111-1111-4111-8111-111111111111';
|
|
13
|
+
function guid(value: string): AssetGuid {
|
|
14
|
+
const result = AssetGuid.parse(value);
|
|
15
|
+
if (!result.ok) throw new Error(`bad GUID: ${value}`);
|
|
16
|
+
return result.value;
|
|
54
17
|
}
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
} {
|
|
59
|
-
const a = (pack.assets as Record<string, unknown>[])[0] as Record<string, unknown>;
|
|
60
|
-
return { payload: a.payload as Record<string, unknown>, refs: a.refs as string[] | undefined };
|
|
18
|
+
function makeHandle(world: World, asset: SceneAsset): Handle<'SceneAsset', 'shared'> {
|
|
19
|
+
registerSceneComponents(world);
|
|
20
|
+
return world.allocSharedRef('SceneAsset', asset);
|
|
61
21
|
}
|
|
62
22
|
|
|
63
|
-
describe('
|
|
64
|
-
it('collect
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
// Child scene = the "bed": one owned mesh entity.
|
|
23
|
+
describe('keyed instance wrapper collect', () => {
|
|
24
|
+
it('collect preserves the wrapper key, source and explicit parent declaration', () => {
|
|
25
|
+
const registry = new AssetRegistry(makeMockShaderRegistry());
|
|
26
|
+
const world = new World();
|
|
69
27
|
const child: SceneAsset = {
|
|
70
28
|
kind: 'scene',
|
|
71
|
-
entities:
|
|
72
|
-
{
|
|
73
|
-
localId: 0 as never,
|
|
74
|
-
components: { Name: { value: 'bed-mesh' }, Transform: { pos: [0, 0, 0] } },
|
|
75
|
-
},
|
|
76
|
-
],
|
|
29
|
+
entities: { leaf: { components: { Transform: {} } } },
|
|
77
30
|
};
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// Plain top scene with ONE owned entity (like spin-cube's Ground). The
|
|
81
|
-
// editor adds the mount live below via a wrapper (no mounts[] on disk yet).
|
|
82
|
-
const top: SceneAsset = {
|
|
31
|
+
expect(registry.catalog(guid(CHILD), child as Asset).ok).toBe(true);
|
|
32
|
+
const parent: SceneAsset = {
|
|
83
33
|
kind: 'scene',
|
|
84
|
-
entities:
|
|
85
|
-
{
|
|
86
|
-
|
|
87
|
-
components: {
|
|
34
|
+
entities: {
|
|
35
|
+
root: { components: { Transform: {} } },
|
|
36
|
+
wrapper: {
|
|
37
|
+
components: { ChildOf: { parent: 'root' } },
|
|
38
|
+
instance: { source: CHILD },
|
|
88
39
|
},
|
|
89
|
-
|
|
40
|
+
},
|
|
90
41
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// The editor's owned top-level roots after a reload = the named entities the
|
|
106
|
-
// session tracks (store.ts _e2h owned-set: the scene asset's entities[], not
|
|
107
|
-
// mount internals). Mirror that here by taking the NAMED entities reachable
|
|
108
|
-
// from the scene (Ground + the bed.glb wrapper), excluding the nameless
|
|
109
|
-
// mount carrier — exactly what the editor collects via entRootHandles.
|
|
110
|
-
const namedTopEntities = (syntheticRoot: number): number[] => {
|
|
111
|
-
const wr = w as unknown as {
|
|
112
|
-
get(
|
|
113
|
-
e: number,
|
|
114
|
-
c: unknown,
|
|
115
|
-
): {
|
|
116
|
-
ok: boolean;
|
|
117
|
-
value?: { entities?: ArrayLike<number>; value?: string; parent?: number };
|
|
118
|
-
};
|
|
119
|
-
};
|
|
120
|
-
// BFS from the synthetic root over Children; keep NAMED entities whose
|
|
121
|
-
// parent is not itself a named-tracked entity (i.e. top-level owned roots).
|
|
122
|
-
const named: number[] = [];
|
|
123
|
-
const queue = [syntheticRoot];
|
|
124
|
-
const seen = new Set<number>([syntheticRoot]);
|
|
125
|
-
while (queue.length) {
|
|
126
|
-
const cur = queue.shift() as number;
|
|
127
|
-
const ch = wr.get(cur, Children);
|
|
128
|
-
if (ch.ok && ch.value?.entities) {
|
|
129
|
-
for (const k of Array.from(ch.value.entities)) {
|
|
130
|
-
if (seen.has(k)) continue;
|
|
131
|
-
seen.add(k);
|
|
132
|
-
queue.push(k);
|
|
133
|
-
if (wr.get(k, Name).ok) named.push(k);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return named;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// Instantiate the top scene.
|
|
141
|
-
const i0 = reg.instantiate(rs(w, top), w);
|
|
142
|
-
expect(i0.ok).toBe(true);
|
|
143
|
-
if (!i0.ok) return;
|
|
144
|
-
|
|
145
|
-
// ── Editor "Add to Scene": spawn a NAMED wrapper parented under the top
|
|
146
|
-
// scene, then mount the child scene UNDER the wrapper (instantiate with
|
|
147
|
-
// parent=wrapper). Mirrors spawnGlbSceneAsMount +
|
|
148
|
-
// instantiateSceneRefUnderWorld. ──
|
|
149
|
-
const wrapperRes = w.spawn(
|
|
150
|
-
{ component: Name, data: { value: 'bed.glb' } },
|
|
151
|
-
{ component: Transform, data: { pos: [0, 0, 0] } },
|
|
152
|
-
);
|
|
153
|
-
expect(wrapperRes.ok).toBe(true);
|
|
154
|
-
if (!wrapperRes.ok) return;
|
|
155
|
-
const mountRes = reg.instantiate(rs(w, child), w, wrapperRes.value);
|
|
156
|
-
expect(mountRes.ok).toBe(true);
|
|
157
|
-
if (!mountRes.ok) return;
|
|
158
|
-
|
|
159
|
-
// Cycle 1: collect from the owned roots (Ground + wrapper).
|
|
160
|
-
// Ground is a child of the synthetic root; the wrapper is a free root we
|
|
161
|
-
// just spawned. Collect from both (the editor's entRootHandles set).
|
|
162
|
-
const groundRoots = namedTopEntities(i0.value as unknown as number);
|
|
163
|
-
const c1 = runCycle([...groundRoots, wrapperRes.value as unknown as number]);
|
|
164
|
-
|
|
165
|
-
// Cycle 2: serialize → parse → reload → collect.
|
|
166
|
-
const s1 = serializeSceneAssetToPack(c1.pack, w.components.entries(), G_TOP);
|
|
167
|
-
expect(s1.ok).toBe(true);
|
|
168
|
-
if (!s1.ok) return;
|
|
169
|
-
const u1 = unpack(s1.value);
|
|
170
|
-
const p1 = parse(u1.payload, u1.refs);
|
|
171
|
-
cat(reg, G_TOP, p1);
|
|
172
|
-
const i1 = reg.instantiate(rs(w, p1), w);
|
|
173
|
-
expect(i1.ok).toBe(true);
|
|
174
|
-
if (!i1.ok) return;
|
|
175
|
-
const c2 = runCycle(namedTopEntities(i1.value as unknown as number));
|
|
176
|
-
|
|
177
|
-
// Cycle 3: reload + collect once more (the ghost-accretion point).
|
|
178
|
-
const s2 = serializeSceneAssetToPack(c2.pack, w.components.entries(), G_TOP);
|
|
179
|
-
expect(s2.ok).toBe(true);
|
|
180
|
-
if (!s2.ok) return;
|
|
181
|
-
const u2 = unpack(s2.value);
|
|
182
|
-
const p2 = parse(u2.payload, u2.refs);
|
|
183
|
-
cat(reg, G_TOP, p2);
|
|
184
|
-
const i2 = reg.instantiate(rs(w, p2), w);
|
|
185
|
-
expect(i2.ok).toBe(true);
|
|
186
|
-
if (!i2.ok) return;
|
|
187
|
-
const c3 = runCycle(namedTopEntities(i2.value as unknown as number));
|
|
188
|
-
|
|
189
|
-
// ACCEPTANCE: owned + mount counts are a FIXED POINT across reloads (before
|
|
190
|
-
// the fix this grew unboundedly: owned 2 → 3 → 5, one nameless ghost per
|
|
191
|
-
// cycle). Ground + bed.glb wrapper = 2 owned; the bed = 1 mount.
|
|
192
|
-
expect({ owned: c1.owned, mounts: c1.mounts }).toEqual({ owned: 2, mounts: 1 });
|
|
193
|
-
expect({ owned: c2.owned, mounts: c2.mounts }).toEqual({ owned: 2, mounts: 1 });
|
|
194
|
-
expect({ owned: c3.owned, mounts: c3.mounts }).toEqual({ owned: 2, mounts: 1 });
|
|
195
|
-
|
|
196
|
-
// No owned entity is nameless (the ghost signature).
|
|
197
|
-
for (const e of c3.pack.entities) {
|
|
198
|
-
const comps = e.components as Record<string, Record<string, unknown>>;
|
|
199
|
-
expect(comps.Name, `owned entity localId ${e.localId} must have a Name`).toBeDefined();
|
|
200
|
-
}
|
|
42
|
+
const result = registry.instantiate(makeHandle(world, parent), world);
|
|
43
|
+
expect(result.ok).toBe(true);
|
|
44
|
+
if (!result.ok) return;
|
|
45
|
+
expect(
|
|
46
|
+
[...world.iterDescendants(result.value)].filter(
|
|
47
|
+
(entity) => world.get(entity, SceneInstance).ok,
|
|
48
|
+
),
|
|
49
|
+
).toHaveLength(1);
|
|
50
|
+
const collected = rootsToSceneAsset(registry, world, [result.value]);
|
|
51
|
+
expect(collected.ok).toBe(true);
|
|
52
|
+
if (!collected.ok) return;
|
|
53
|
+
expect(collected.value.entities.wrapper?.instance?.source).toBe(CHILD);
|
|
54
|
+
expect(collected.value.entities.wrapper?.components.ChildOf).toEqual({ parent: 'root' });
|
|
201
55
|
});
|
|
202
56
|
});
|
|
@@ -21,24 +21,23 @@ import { AssetRegistry } from '@forgeax/engine-assets-runtime';
|
|
|
21
21
|
import { defineComponent, type EntityHandle, World } from '@forgeax/engine-ecs';
|
|
22
22
|
import { SceneInstance } from '@forgeax/engine-render';
|
|
23
23
|
import { ChildOf } from '@forgeax/engine-scene';
|
|
24
|
-
import type { Handle,
|
|
24
|
+
import type { Handle, SceneAsset, SceneEntity } from '@forgeax/engine-types';
|
|
25
25
|
import { toShared } from '@forgeax/engine-types';
|
|
26
26
|
import { describe, expect, it } from 'vitest';
|
|
27
27
|
import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
28
28
|
import { registerRuntimeComponents } from './helpers/register-runtime-components';
|
|
29
29
|
|
|
30
|
-
function localId(n: number): LocalEntityId {
|
|
31
|
-
return n as LocalEntityId;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
30
|
function buildScene(): SceneAsset {
|
|
35
31
|
const Transform = defineComponent('Transform', { pos: 'array<f32, 3>' });
|
|
36
32
|
void Transform;
|
|
37
33
|
const nodes: SceneEntity[] = [
|
|
38
|
-
{
|
|
39
|
-
{
|
|
34
|
+
{ key: 'entity-0', components: { Transform: { pos: [1, 0, 0] } } },
|
|
35
|
+
{ key: 'entity-1', components: { Transform: { pos: [2, 0, 0] } } },
|
|
40
36
|
];
|
|
41
|
-
return {
|
|
37
|
+
return {
|
|
38
|
+
kind: 'scene',
|
|
39
|
+
entities: Object.fromEntries(nodes.map(({ key, components }) => [key, { components }])),
|
|
40
|
+
};
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
function registerSceneAsset(world: World, asset: SceneAsset): Handle<'SceneAsset', 'shared'> {
|
|
@@ -142,11 +141,17 @@ describe('w30 - engine.assets.instantiate sugar wrapper equivalence (AC-03)', ()
|
|
|
142
141
|
// array is a snapshot at call time, not a live view (SceneInstance contract).
|
|
143
142
|
|
|
144
143
|
function buildPostScene(): SceneAsset {
|
|
145
|
-
const nodes: SceneEntity
|
|
146
|
-
{
|
|
147
|
-
{
|
|
144
|
+
const nodes: Array<SceneEntity & { readonly key: string }> = [
|
|
145
|
+
{ key: 'entity-0', components: { Transform: { pos: [0, 0, 0] } } },
|
|
146
|
+
{
|
|
147
|
+
key: 'entity-1',
|
|
148
|
+
components: { Transform: { pos: [1, 0, 0] }, ChildOf: { parent: 'entity-0' } },
|
|
149
|
+
},
|
|
148
150
|
];
|
|
149
|
-
return {
|
|
151
|
+
return {
|
|
152
|
+
kind: 'scene',
|
|
153
|
+
entities: Object.fromEntries(nodes.map(({ key, components }) => [key, { components }])),
|
|
154
|
+
};
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
// A lightweight component for post-instantiate injection testing.
|
|
@@ -227,8 +232,8 @@ describe('M5 post-instantiate component injection (w13 / AC-09 + AC-10)', () =>
|
|
|
227
232
|
// Two root nodes, each gets a different component via query-after.
|
|
228
233
|
defineComponent('Transform', { pos: 'array<f32, 3>' });
|
|
229
234
|
const multiRoot: SceneEntity[] = [
|
|
230
|
-
{
|
|
231
|
-
{
|
|
235
|
+
{ key: 'entity-0', components: { Transform: { pos: [0, 0, 0] } } },
|
|
236
|
+
{ key: 'entity-1', components: { Transform: { pos: [10, 0, 0] } } },
|
|
232
237
|
];
|
|
233
238
|
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
234
239
|
const world = new World();
|
|
@@ -267,7 +272,7 @@ describe('M5 post-instantiate component injection (w13 / AC-09 + AC-10)', () =>
|
|
|
267
272
|
const world = new World();
|
|
268
273
|
registerRuntimeComponents(world, [Enemy, Waypoint]);
|
|
269
274
|
const singleRoot: SceneEntity[] = [
|
|
270
|
-
{
|
|
275
|
+
{ key: 'entity-0', components: { Transform: { pos: [0, 0, 0] } } },
|
|
271
276
|
];
|
|
272
277
|
const asset: SceneAsset = { kind: 'scene', entities: singleRoot };
|
|
273
278
|
const handle = registerSceneAsset(world, asset);
|