@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
|
@@ -8,9 +8,8 @@ import * as SceneOwner from '@forgeax/engine-scene';
|
|
|
8
8
|
// via AssetRegistry, no external handleToGuid table needed. Those scenarios
|
|
9
9
|
// are covered by m2-t3 in roots-to-scene-asset.test.ts.
|
|
10
10
|
//
|
|
11
|
-
// NOTE: rootsToSceneAsset
|
|
12
|
-
//
|
|
13
|
-
// Entity counts here include the synthetic root from instantiateScene.
|
|
11
|
+
// NOTE: rootsToSceneAsset omits the transient synthetic SceneInstance anchor.
|
|
12
|
+
// Entity counts therefore describe authored keyed declarations only.
|
|
14
13
|
//
|
|
15
14
|
// M1T7 AUDIT (feat-20260707-engine-world-clone-transient-for-editor-ssot):
|
|
16
15
|
// Classification: (b) false-positive modification risk — all 10 tests
|
|
@@ -27,7 +26,7 @@ import { type Component, defineComponent as defineEcsComponent, World } from '@f
|
|
|
27
26
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
28
27
|
import { SceneInstance } from '@forgeax/engine-render';
|
|
29
28
|
import { ChildOf, Children, Transform } from '@forgeax/engine-scene';
|
|
30
|
-
import type {
|
|
29
|
+
import type { SceneAsset, SceneEntity } from '@forgeax/engine-types';
|
|
31
30
|
import { describe, expect, it } from 'vitest';
|
|
32
31
|
import { rootsToSceneAsset, serializeSceneAssetToPack } from '../collect-scene-asset';
|
|
33
32
|
import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
@@ -50,10 +49,6 @@ function makeRegistry(): AssetRegistry {
|
|
|
50
49
|
return new AssetRegistry(makeMockShaderRegistry());
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
function localId(n: number): LocalEntityId {
|
|
54
|
-
return n as LocalEntityId;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
52
|
// biome-ignore lint/suspicious/noExplicitAny: allocSharedRef('SceneAsset') returns branded Handle<"SceneAsset", ...> but instantiateScene expects Handle<string, ...>
|
|
58
53
|
function registerSceneAsset(world: World, asset: SceneAsset): any {
|
|
59
54
|
registerSceneComponents(world);
|
|
@@ -71,8 +66,12 @@ function def<T>(v: T | undefined | null, label = 'value'): T {
|
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
/** Find the entity in the scene that has the given component. */
|
|
74
|
-
function findEntityWith(
|
|
75
|
-
|
|
69
|
+
function findEntityWith(
|
|
70
|
+
entities: Readonly<Record<string, SceneEntity>> | readonly SceneEntity[],
|
|
71
|
+
compName: string,
|
|
72
|
+
): SceneEntity {
|
|
73
|
+
const list = Array.isArray(entities) ? entities : Object.values(entities);
|
|
74
|
+
const found = list.find(
|
|
76
75
|
(e) => (e.components as Record<string, Record<string, unknown>>)[compName] !== undefined,
|
|
77
76
|
);
|
|
78
77
|
if (!found) throw new Error(`no entity with component ${compName}`);
|
|
@@ -87,11 +86,11 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
87
86
|
|
|
88
87
|
const asset: SceneAsset = {
|
|
89
88
|
kind: 'scene',
|
|
90
|
-
entities:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
entities: {
|
|
90
|
+
'entity-0': { components: { Test_Transform: { pos: [1, 2, 3] } } },
|
|
91
|
+
'entity-1': { components: { Test_Transform: { pos: [4, 5, 6] } } },
|
|
92
|
+
'entity-2': { components: { Test_Transform: { pos: [7, 8, 9] } } },
|
|
93
|
+
},
|
|
95
94
|
};
|
|
96
95
|
|
|
97
96
|
const world = new World();
|
|
@@ -109,8 +108,8 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
109
108
|
if (!collected.ok) return;
|
|
110
109
|
const scene = collected.value;
|
|
111
110
|
expect(scene.kind).toBe('scene');
|
|
112
|
-
//
|
|
113
|
-
expect(scene.entities).toHaveLength(
|
|
111
|
+
// Three authored declarations; the transient anchor is omitted.
|
|
112
|
+
expect(Object.keys(scene.entities)).toHaveLength(3);
|
|
114
113
|
|
|
115
114
|
const entityWithTransform = findEntityWith(scene.entities, 'Test_Transform');
|
|
116
115
|
expect(entityWithTransform).toBeDefined();
|
|
@@ -125,7 +124,7 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
125
124
|
|
|
126
125
|
const asset: SceneAsset = {
|
|
127
126
|
kind: 'scene',
|
|
128
|
-
entities:
|
|
127
|
+
entities: { 'entity-0': { components: { Test_Pos3: { x: 10.5, y: 20.5, z: 30.5 } } } },
|
|
129
128
|
};
|
|
130
129
|
|
|
131
130
|
const world = new World();
|
|
@@ -142,8 +141,8 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
142
141
|
expect(collected.ok).toBe(true);
|
|
143
142
|
if (!collected.ok) return;
|
|
144
143
|
const scene = collected.value;
|
|
145
|
-
//
|
|
146
|
-
expect(scene.entities).toHaveLength(
|
|
144
|
+
// one authored entity; the transient anchor is omitted.
|
|
145
|
+
expect(Object.keys(scene.entities)).toHaveLength(1);
|
|
147
146
|
const e = findEntityWith(scene.entities, 'Test_Pos3');
|
|
148
147
|
const tp = def(comp(e, 'Test_Pos3'), 'Test_Pos3');
|
|
149
148
|
expect(Math.abs((tp.x as number) - 10.5)).toBeLessThan(0.001);
|
|
@@ -159,7 +158,7 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
159
158
|
|
|
160
159
|
const asset: SceneAsset = {
|
|
161
160
|
kind: 'scene',
|
|
162
|
-
entities:
|
|
161
|
+
entities: { 'entity-0': { components: { Test_WithDefault: { required: 7 } } } },
|
|
163
162
|
};
|
|
164
163
|
|
|
165
164
|
const world = new World();
|
|
@@ -176,7 +175,7 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
176
175
|
expect(collected.ok).toBe(true);
|
|
177
176
|
if (!collected.ok) return;
|
|
178
177
|
const scene = collected.value;
|
|
179
|
-
expect(scene.entities).toHaveLength(
|
|
178
|
+
expect(Object.keys(scene.entities)).toHaveLength(1);
|
|
180
179
|
const e = findEntityWith(scene.entities, 'Test_WithDefault');
|
|
181
180
|
const tw = def(comp(e, 'Test_WithDefault'), 'Test_WithDefault');
|
|
182
181
|
expect(tw.required).toBe(7);
|
|
@@ -184,7 +183,7 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
184
183
|
});
|
|
185
184
|
|
|
186
185
|
it('(d) empty scene (no entities) round-trips to empty entities[]', () => {
|
|
187
|
-
const asset: SceneAsset = { kind: 'scene', entities:
|
|
186
|
+
const asset: SceneAsset = { kind: 'scene', entities: {} };
|
|
188
187
|
const world = new World();
|
|
189
188
|
const reg = makeRegistry();
|
|
190
189
|
const sg = AssetGuid.parse('00000000-0000-0000-0000-000000000000');
|
|
@@ -201,8 +200,8 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
201
200
|
if (!collected.ok) return;
|
|
202
201
|
const scene = collected.value;
|
|
203
202
|
expect(scene.kind).toBe('scene');
|
|
204
|
-
// Empty scene
|
|
205
|
-
expect(scene.entities
|
|
203
|
+
// Empty scene has no authored declarations.
|
|
204
|
+
expect(Object.keys(scene.entities)).toHaveLength(0);
|
|
206
205
|
});
|
|
207
206
|
|
|
208
207
|
it('(e) string field round-trips through rootsToSceneAsset', () => {
|
|
@@ -212,7 +211,7 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
212
211
|
|
|
213
212
|
const asset: SceneAsset = {
|
|
214
213
|
kind: 'scene',
|
|
215
|
-
entities:
|
|
214
|
+
entities: { 'entity-0': { components: { Test_Name: { label: 'hello' } } } },
|
|
216
215
|
};
|
|
217
216
|
|
|
218
217
|
const world = new World();
|
|
@@ -229,7 +228,7 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
229
228
|
expect(collected.ok).toBe(true);
|
|
230
229
|
if (!collected.ok) return;
|
|
231
230
|
const scene = collected.value;
|
|
232
|
-
expect(scene.entities).toHaveLength(
|
|
231
|
+
expect(Object.keys(scene.entities)).toHaveLength(1);
|
|
233
232
|
const e = findEntityWith(scene.entities, 'Test_Name');
|
|
234
233
|
const tn = def(comp(e, 'Test_Name'), 'Test_Name');
|
|
235
234
|
expect(tn.label).toBe('hello');
|
|
@@ -242,16 +241,15 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
242
241
|
|
|
243
242
|
const asset: SceneAsset = {
|
|
244
243
|
kind: 'scene',
|
|
245
|
-
entities:
|
|
246
|
-
{
|
|
247
|
-
localId: localId(0),
|
|
244
|
+
entities: {
|
|
245
|
+
'entity-0': {
|
|
248
246
|
components: {
|
|
249
247
|
Test_A: { val: 1 },
|
|
250
248
|
Test_B: { flag: true },
|
|
251
249
|
Test_C: { name: 'multi' },
|
|
252
250
|
},
|
|
253
251
|
},
|
|
254
|
-
|
|
252
|
+
},
|
|
255
253
|
};
|
|
256
254
|
|
|
257
255
|
const world = new World();
|
|
@@ -268,7 +266,7 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
268
266
|
expect(collected.ok).toBe(true);
|
|
269
267
|
if (!collected.ok) return;
|
|
270
268
|
const scene = collected.value;
|
|
271
|
-
expect(scene.entities).toHaveLength(
|
|
269
|
+
expect(Object.keys(scene.entities)).toHaveLength(1);
|
|
272
270
|
const e = findEntityWith(scene.entities, 'Test_A');
|
|
273
271
|
expect(def(comp(e, 'Test_A'), 'Test_A').val).toBe(1);
|
|
274
272
|
expect(def(comp(e, 'Test_B'), 'Test_B').flag).toBe(true);
|
|
@@ -280,10 +278,10 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
280
278
|
|
|
281
279
|
const asset: SceneAsset = {
|
|
282
280
|
kind: 'scene',
|
|
283
|
-
entities:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
281
|
+
entities: {
|
|
282
|
+
'entity-0': { components: { Test_Flag: { enabled: true } } },
|
|
283
|
+
'entity-1': { components: { Test_Flag: { enabled: false } } },
|
|
284
|
+
},
|
|
287
285
|
};
|
|
288
286
|
|
|
289
287
|
const world = new World();
|
|
@@ -300,11 +298,11 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
300
298
|
expect(collected.ok).toBe(true);
|
|
301
299
|
if (!collected.ok) return;
|
|
302
300
|
const scene = collected.value;
|
|
303
|
-
//
|
|
304
|
-
expect(scene.entities).toHaveLength(
|
|
301
|
+
// two authored children; the transient anchor is omitted.
|
|
302
|
+
expect(Object.keys(scene.entities)).toHaveLength(2);
|
|
305
303
|
|
|
306
304
|
// Find the two entities with Test_Flag.
|
|
307
|
-
const flagged = scene.entities.filter(
|
|
305
|
+
const flagged = Object.values(scene.entities).filter(
|
|
308
306
|
(e) => (e.components as Record<string, Record<string, unknown>>).Test_Flag !== undefined,
|
|
309
307
|
);
|
|
310
308
|
expect(flagged).toHaveLength(2);
|
|
@@ -322,7 +320,7 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
322
320
|
|
|
323
321
|
const asset: SceneAsset = {
|
|
324
322
|
kind: 'scene',
|
|
325
|
-
entities:
|
|
323
|
+
entities: { 'entity-0': { components: { Test_Pack: { a: 99 } } } },
|
|
326
324
|
};
|
|
327
325
|
|
|
328
326
|
const packResult = serializeSceneAssetToPack(
|
|
@@ -344,12 +342,16 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
344
342
|
expect(assetEntry.guid).toBe('aaaaaaaa-bbbb-4ccc-dddd-eeeeeeeeeeee');
|
|
345
343
|
expect(assetEntry.kind).toBe('scene');
|
|
346
344
|
const payload = assetEntry.payload as Record<string, unknown>;
|
|
347
|
-
expect(
|
|
348
|
-
|
|
345
|
+
expect(
|
|
346
|
+
payload.entities !== null &&
|
|
347
|
+
typeof payload.entities === 'object' &&
|
|
348
|
+
!Array.isArray(payload.entities),
|
|
349
|
+
).toBe(true);
|
|
350
|
+
expect(Object.keys(payload.entities as Record<string, unknown>)).toHaveLength(1);
|
|
349
351
|
});
|
|
350
352
|
|
|
351
353
|
it('(i) serializeSceneAssetToPack without guid uses a generated guid', () => {
|
|
352
|
-
const asset: SceneAsset = { kind: 'scene', entities:
|
|
354
|
+
const asset: SceneAsset = { kind: 'scene', entities: {} };
|
|
353
355
|
|
|
354
356
|
const packResult = serializeSceneAssetToPack(asset, new Map());
|
|
355
357
|
expect(packResult.ok).toBe(true);
|
|
@@ -375,10 +377,10 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
375
377
|
|
|
376
378
|
const asset: SceneAsset = {
|
|
377
379
|
kind: 'scene',
|
|
378
|
-
entities:
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
380
|
+
entities: {
|
|
381
|
+
'entity-0': { components: { Test_Full: { posX: 1.5, posY: 2.5, name: 'e0' } } },
|
|
382
|
+
'entity-1': { components: { Test_Full: { posX: 3.5, posY: 4.5, name: 'e1' } } },
|
|
383
|
+
},
|
|
382
384
|
};
|
|
383
385
|
|
|
384
386
|
const world = new World();
|
|
@@ -394,8 +396,8 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
394
396
|
const collected = rootsToSceneAsset(reg, world, [root]);
|
|
395
397
|
expect(collected.ok).toBe(true);
|
|
396
398
|
if (!collected.ok) return;
|
|
397
|
-
//
|
|
398
|
-
expect(collected.value.entities).toHaveLength(
|
|
399
|
+
// Two authored declarations; the transient anchor is omitted.
|
|
400
|
+
expect(Object.keys(collected.value.entities)).toHaveLength(2);
|
|
399
401
|
|
|
400
402
|
const packResult = serializeSceneAssetToPack(
|
|
401
403
|
collected.value,
|
|
@@ -408,8 +410,8 @@ describe('w9 — round-trip semantic equivalence', () => {
|
|
|
408
410
|
expect(pack.kind).toBe('internal-text-package');
|
|
409
411
|
const assets = pack.assets as Array<Record<string, unknown>>;
|
|
410
412
|
const payload = def(assets[0], 'assets[0]').payload as Record<string, unknown>;
|
|
411
|
-
const entities = payload.entities as
|
|
412
|
-
expect(entities).toHaveLength(
|
|
413
|
+
const entities = Object.values(payload.entities as Record<string, Record<string, unknown>>);
|
|
414
|
+
expect(entities).toHaveLength(2);
|
|
413
415
|
|
|
414
416
|
// Find entities with Test_Full in serialized output.
|
|
415
417
|
const tfEntities = entities.filter(
|
|
@@ -444,7 +446,7 @@ describe('w1 — field-level transient collect skip (AC-02 + AC-03)', () => {
|
|
|
444
446
|
it('(AC-02) Transform entity serializes without a world key', () => {
|
|
445
447
|
const asset: SceneAsset = {
|
|
446
448
|
kind: 'scene',
|
|
447
|
-
entities:
|
|
449
|
+
entities: { 'entity-0': { components: { Transform: { pos: [1, 2, 3] } } } },
|
|
448
450
|
};
|
|
449
451
|
|
|
450
452
|
const world = new World();
|
|
@@ -462,7 +464,7 @@ describe('w1 — field-level transient collect skip (AC-02 + AC-03)', () => {
|
|
|
462
464
|
|
|
463
465
|
// Every Transform-carrying entity (synthetic identity root + the authored
|
|
464
466
|
// child) must omit the transient world field.
|
|
465
|
-
const transformEntities = collected.value.entities.filter(
|
|
467
|
+
const transformEntities = Object.values(collected.value.entities).filter(
|
|
466
468
|
(e) => (e.components as Record<string, Record<string, unknown>>).Transform !== undefined,
|
|
467
469
|
);
|
|
468
470
|
expect(transformEntities.length).toBeGreaterThan(0);
|
|
@@ -472,11 +474,12 @@ describe('w1 — field-level transient collect skip (AC-02 + AC-03)', () => {
|
|
|
472
474
|
expect('world' in t).toBe(false);
|
|
473
475
|
}
|
|
474
476
|
|
|
475
|
-
// The authored
|
|
476
|
-
//
|
|
477
|
-
const authored = transformEntities.find(
|
|
478
|
-
|
|
479
|
-
|
|
477
|
+
// The authored entity retains its persisted local TRS. Its implicit
|
|
478
|
+
// ChildOf edge to the transient anchor is omitted, so identify it by data.
|
|
479
|
+
const authored = transformEntities.find((e) => {
|
|
480
|
+
const pos = (e.components as Record<string, Record<string, unknown>>).Transform?.pos;
|
|
481
|
+
return Array.isArray(pos) && pos[0] === 1 && pos[1] === 2 && pos[2] === 3;
|
|
482
|
+
});
|
|
480
483
|
const t = def(comp(def(authored, 'authored'), 'Transform'), 'Transform');
|
|
481
484
|
expect(t.pos).toEqual([1, 2, 3]);
|
|
482
485
|
// Reference the imported Transform token so the schema is registered.
|
|
@@ -493,12 +496,9 @@ describe('w1 — field-level transient collect skip (AC-02 + AC-03)', () => {
|
|
|
493
496
|
|
|
494
497
|
const asset: SceneAsset = {
|
|
495
498
|
kind: 'scene',
|
|
496
|
-
entities:
|
|
497
|
-
{
|
|
498
|
-
|
|
499
|
-
components: { W1_GenericTransient: { persisted: 7, cache: [9, 9, 9, 9] } },
|
|
500
|
-
},
|
|
501
|
-
],
|
|
499
|
+
entities: {
|
|
500
|
+
'entity-0': { components: { W1_GenericTransient: { persisted: 7, cache: [9, 9, 9, 9] } } },
|
|
501
|
+
},
|
|
502
502
|
};
|
|
503
503
|
|
|
504
504
|
const world = new World();
|
|
@@ -530,7 +530,7 @@ describe('w1 — field-level transient collect skip (AC-02 + AC-03)', () => {
|
|
|
530
530
|
|
|
531
531
|
const asset: SceneAsset = {
|
|
532
532
|
kind: 'scene',
|
|
533
|
-
entities:
|
|
533
|
+
entities: { 'entity-0': { components: { W1_NoTransient: { a: 3, b: 4 } } } },
|
|
534
534
|
};
|
|
535
535
|
|
|
536
536
|
const world = new World();
|
|
@@ -561,7 +561,7 @@ describe('marker component scene-pack round-trip', () => {
|
|
|
561
561
|
defineComponent('Test_EmptySchemaMarker', {});
|
|
562
562
|
const asset: SceneAsset = {
|
|
563
563
|
kind: 'scene',
|
|
564
|
-
entities:
|
|
564
|
+
entities: { 'entity-0': { components: { Test_EmptySchemaMarker: {} } } },
|
|
565
565
|
};
|
|
566
566
|
|
|
567
567
|
const world = new World();
|
|
@@ -589,7 +589,7 @@ describe('marker component scene-pack round-trip', () => {
|
|
|
589
589
|
const pack = serialized.value;
|
|
590
590
|
const payload = def((pack.assets as Array<Record<string, unknown>>)[0], 'assets[0]')
|
|
591
591
|
.payload as Record<string, unknown>;
|
|
592
|
-
const entities = payload.entities as
|
|
592
|
+
const entities = Object.values(payload.entities as Record<string, Record<string, unknown>>);
|
|
593
593
|
const markerEntity = entities.find((entity) => {
|
|
594
594
|
const components = entity.components as Record<string, unknown>;
|
|
595
595
|
return components.Test_EmptySchemaMarker !== undefined;
|
|
@@ -632,22 +632,20 @@ describe('w4 -- Transform vec serialization shape (AC-05)', () => {
|
|
|
632
632
|
});
|
|
633
633
|
});
|
|
634
634
|
|
|
635
|
-
describe('w4 -- old-shape scene JSON
|
|
636
|
-
it('unknown per-axis keys
|
|
635
|
+
describe('w4 -- old-shape scene JSON validation (research Finding 3)', () => {
|
|
636
|
+
it('rejects unknown per-axis keys before spawning a partial scene', () => {
|
|
637
637
|
// Old 10-scalar shape scene JSON: every per-axis key is unknown after the
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
// entity lands the default identity transform (Finding 3 + 4 downgrade).
|
|
638
|
+
// keyed SceneAsset schema cut. The producer must repair the source rather
|
|
639
|
+
// than silently dropping authored data.
|
|
641
640
|
const asset: SceneAsset = {
|
|
642
641
|
kind: 'scene',
|
|
643
|
-
entities:
|
|
644
|
-
{
|
|
645
|
-
localId: localId(0),
|
|
642
|
+
entities: {
|
|
643
|
+
'entity-0': {
|
|
646
644
|
components: {
|
|
647
645
|
Transform: { posX: 5, posY: 6, posZ: 7, quatW: 1, scaleX: 2 },
|
|
648
646
|
},
|
|
649
647
|
},
|
|
650
|
-
|
|
648
|
+
},
|
|
651
649
|
};
|
|
652
650
|
|
|
653
651
|
const world = new World();
|
|
@@ -656,29 +654,17 @@ describe('w4 -- old-shape scene JSON downgrade regression (research Finding 3)',
|
|
|
656
654
|
if (sg.ok) reg.catalog(sg.value, asset);
|
|
657
655
|
const handle = registerSceneAsset(world, asset);
|
|
658
656
|
const res = SceneOwner.worldInstantiateScene(world, handle);
|
|
659
|
-
expect(res.ok).toBe(
|
|
657
|
+
expect(res.ok).toBe(false);
|
|
660
658
|
if (!res.ok) return;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const tfEntity = findEntityWith(
|
|
671
|
-
(() => {
|
|
672
|
-
const collected = rootsToSceneAsset(reg, world, [res.value.root]);
|
|
673
|
-
if (!collected.ok) throw new Error('collect failed');
|
|
674
|
-
return collected.value.entities;
|
|
675
|
-
})(),
|
|
676
|
-
'Transform',
|
|
677
|
-
);
|
|
678
|
-
const tf = def(comp(tfEntity, 'Transform'), 'Transform');
|
|
679
|
-
expect(tf.pos).toEqual([0, 0, 0]);
|
|
680
|
-
expect(tf.quat).toEqual([0, 0, 0, 1]);
|
|
681
|
-
expect(tf.scale).toEqual([1, 1, 1]);
|
|
659
|
+
expect(res.error).toMatchObject({
|
|
660
|
+
code: 'asset-package-invalid',
|
|
661
|
+
detail: {
|
|
662
|
+
reason: 'unknown component field',
|
|
663
|
+
component: 'Transform',
|
|
664
|
+
field: 'posX',
|
|
665
|
+
entity: 'entity-0',
|
|
666
|
+
},
|
|
667
|
+
});
|
|
682
668
|
});
|
|
683
669
|
});
|
|
684
670
|
|
|
@@ -701,7 +687,7 @@ describe('m3 — runtime collector kernel parity', () => {
|
|
|
701
687
|
const world = new World();
|
|
702
688
|
const asset: SceneAsset = {
|
|
703
689
|
kind: 'scene',
|
|
704
|
-
entities:
|
|
690
|
+
entities: { 'entity-0': { components: { CollectorParity: { count: 42 } } } },
|
|
705
691
|
};
|
|
706
692
|
const handle = registerSceneAsset(world, asset);
|
|
707
693
|
const r = SceneOwner.worldInstantiateScene(world, handle);
|
|
@@ -723,10 +709,12 @@ describe('m3 — runtime collector kernel parity', () => {
|
|
|
723
709
|
const world = new World();
|
|
724
710
|
const asset: SceneAsset = {
|
|
725
711
|
kind: 'scene',
|
|
726
|
-
entities:
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
712
|
+
entities: {
|
|
713
|
+
'entity-0': {
|
|
714
|
+
components: { FixedEnt_CollectorParity: { refs: ['entity-0', 'entity-1'] } },
|
|
715
|
+
},
|
|
716
|
+
'entity-1': { components: {} },
|
|
717
|
+
},
|
|
730
718
|
};
|
|
731
719
|
const handle = registerSceneAsset(world, asset);
|
|
732
720
|
const r = SceneOwner.worldInstantiateScene(world, handle);
|
|
@@ -767,12 +755,9 @@ describe('m3 — runtime collector kernel parity', () => {
|
|
|
767
755
|
const world = new World();
|
|
768
756
|
const asset: SceneAsset = {
|
|
769
757
|
kind: 'scene',
|
|
770
|
-
entities:
|
|
771
|
-
{
|
|
772
|
-
|
|
773
|
-
components: { TransientField_CollectorParity: { keep: 5, derived: 99 } },
|
|
774
|
-
},
|
|
775
|
-
],
|
|
758
|
+
entities: {
|
|
759
|
+
'entity-0': { components: { TransientField_CollectorParity: { keep: 5, derived: 99 } } },
|
|
760
|
+
},
|
|
776
761
|
};
|
|
777
762
|
const handle = registerSceneAsset(world, asset);
|
|
778
763
|
const r = SceneOwner.worldInstantiateScene(world, handle);
|
|
@@ -829,7 +814,7 @@ describe('m3 — runtime collector kernel parity supplemental', () => {
|
|
|
829
814
|
const world = new World();
|
|
830
815
|
const asset: SceneAsset = {
|
|
831
816
|
kind: 'scene',
|
|
832
|
-
entities:
|
|
817
|
+
entities: { 'entity-0': { components: { RoundTripComp_Sup: { val: 99 } } } },
|
|
833
818
|
};
|
|
834
819
|
const handle = registerSceneAsset(world, asset);
|
|
835
820
|
const r = SceneOwner.worldInstantiateScene(world, handle);
|
|
@@ -850,9 +835,7 @@ describe('m3 — runtime collector kernel parity supplemental', () => {
|
|
|
850
835
|
const world = new World();
|
|
851
836
|
const asset: SceneAsset = {
|
|
852
837
|
kind: 'scene',
|
|
853
|
-
entities:
|
|
854
|
-
{ localId: localId(0), components: { TransientFieldCol_Sup: { keep: 5, derived: 99 } } },
|
|
855
|
-
],
|
|
838
|
+
entities: { 'entity-0': { components: { TransientFieldCol_Sup: { keep: 5, derived: 99 } } } },
|
|
856
839
|
};
|
|
857
840
|
const handle = registerSceneAsset(world, asset);
|
|
858
841
|
const r = SceneOwner.worldInstantiateScene(world, handle);
|