@forgeax/engine-runtime 0.1.28 → 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 -32
- 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-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
|
@@ -66,14 +66,16 @@ describe('m2-t1 — Form 2: root itself is SceneInstance', () => {
|
|
|
66
66
|
const w = new World();
|
|
67
67
|
const child: SceneAsset = {
|
|
68
68
|
kind: 'scene',
|
|
69
|
-
entities:
|
|
69
|
+
entities: { 'entity-0': { components: { Transform: {} } } },
|
|
70
70
|
};
|
|
71
71
|
reg.catalog(pg(G1), child as Asset);
|
|
72
72
|
|
|
73
73
|
const parent: SceneAsset = {
|
|
74
74
|
kind: 'scene',
|
|
75
|
-
entities:
|
|
76
|
-
|
|
75
|
+
entities: {
|
|
76
|
+
'entity-0': { components: { Transform: { pos: [10, 0, 0] } } },
|
|
77
|
+
child: { components: {}, instance: { source: G1 } },
|
|
78
|
+
},
|
|
77
79
|
};
|
|
78
80
|
const ch = rs(w, child);
|
|
79
81
|
const ph = rs(w, parent);
|
|
@@ -91,16 +93,15 @@ describe('m2-t1 — Form 2: root itself is SceneInstance', () => {
|
|
|
91
93
|
// Root is SceneInstance → root's own entities are owned.
|
|
92
94
|
// Child's member entities are excluded.
|
|
93
95
|
const s = res.value;
|
|
94
|
-
expect(s.entities.length).toBeGreaterThanOrEqual(1); // parent's own entity
|
|
95
|
-
for (const e of s.entities) {
|
|
96
|
+
expect(Object.keys(s.entities).length).toBeGreaterThanOrEqual(1); // parent's own entity
|
|
97
|
+
for (const e of Object.values(s.entities)) {
|
|
96
98
|
expect(
|
|
97
99
|
(e.components as Record<string, Record<string, unknown>>).SceneInstance,
|
|
98
100
|
).toBeUndefined();
|
|
99
101
|
}
|
|
100
102
|
// Child anchor produces a mount.
|
|
101
|
-
|
|
102
|
-
expect(
|
|
103
|
-
expect(s.mounts?.[0]?.source).toBe(G1);
|
|
103
|
+
const childInstance = Object.values(s.entities).find((entity) => entity.instance);
|
|
104
|
+
expect(childInstance?.instance?.source).toBe(G1);
|
|
104
105
|
});
|
|
105
106
|
});
|
|
106
107
|
|
|
@@ -111,10 +112,10 @@ describe('m2-t2 — Form 1: deep anchor with ChildOf parent', () => {
|
|
|
111
112
|
const w = new World();
|
|
112
113
|
const child: SceneAsset = {
|
|
113
114
|
kind: 'scene',
|
|
114
|
-
entities:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
entities: {
|
|
116
|
+
'entity-0': { components: { Transform: { pos: [1, 0, 0] } } },
|
|
117
|
+
'entity-1': { components: { Transform: { pos: [2, 0, 0] } } },
|
|
118
|
+
},
|
|
118
119
|
};
|
|
119
120
|
reg.catalog(pg(G1), child as Asset);
|
|
120
121
|
const ch = rs(w, child);
|
|
@@ -144,10 +145,9 @@ describe('m2-t2 — Form 1: deep anchor with ChildOf parent', () => {
|
|
|
144
145
|
|
|
145
146
|
// Outer root is NOT SceneInstance → no mount for it.
|
|
146
147
|
// Child anchor produces a mount; its member entities excluded from owned.
|
|
147
|
-
|
|
148
|
-
expect(
|
|
149
|
-
|
|
150
|
-
for (const e of res.value.entities) {
|
|
148
|
+
const childInstance = Object.values(res.value.entities).find((entity) => entity.instance);
|
|
149
|
+
expect(childInstance?.instance?.source).toBe(G1);
|
|
150
|
+
for (const e of Object.values(res.value.entities)) {
|
|
151
151
|
expect(
|
|
152
152
|
(e.components as Record<string, Record<string, unknown>>).SceneInstance,
|
|
153
153
|
).toBeUndefined();
|
|
@@ -162,15 +162,17 @@ describe('m2-t3 — AC-03 window invariants', () => {
|
|
|
162
162
|
const w = new World();
|
|
163
163
|
const ca: SceneAsset = {
|
|
164
164
|
kind: 'scene',
|
|
165
|
-
entities:
|
|
165
|
+
entities: { 'entity-0': { components: { Transform: {} } } },
|
|
166
166
|
};
|
|
167
167
|
reg.catalog(pg(G1), ca as Asset);
|
|
168
168
|
const ch = rs(w, ca);
|
|
169
169
|
|
|
170
170
|
const pa: SceneAsset = {
|
|
171
171
|
kind: 'scene',
|
|
172
|
-
entities:
|
|
173
|
-
|
|
172
|
+
entities: {
|
|
173
|
+
'entity-0': { components: { Transform: {} } },
|
|
174
|
+
child: { components: {}, instance: { source: G1 } },
|
|
175
|
+
},
|
|
174
176
|
};
|
|
175
177
|
const ph = rs(w, pa);
|
|
176
178
|
wr(w, ph, ch);
|
|
@@ -182,7 +184,7 @@ describe('m2-t3 — AC-03 window invariants', () => {
|
|
|
182
184
|
const res = rootsToSceneAsset(reg, w, [inst.value.root]);
|
|
183
185
|
expect(res.ok).toBe(true);
|
|
184
186
|
if (!res.ok) return;
|
|
185
|
-
expect(res.value.
|
|
187
|
+
expect(Object.values(res.value.entities).filter((entity) => entity.instance)).toHaveLength(1);
|
|
186
188
|
});
|
|
187
189
|
|
|
188
190
|
it('sparse localId does not shrink window', () => {
|
|
@@ -190,18 +192,20 @@ describe('m2-t3 — AC-03 window invariants', () => {
|
|
|
190
192
|
const w = new World();
|
|
191
193
|
const ca: SceneAsset = {
|
|
192
194
|
kind: 'scene',
|
|
193
|
-
entities:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
entities: {
|
|
196
|
+
'entity-0': { components: { Transform: { pos: [1, 0, 0] } } },
|
|
197
|
+
'entity-1': { components: { Transform: { pos: [2, 0, 0] } } },
|
|
198
|
+
},
|
|
197
199
|
};
|
|
198
200
|
reg.catalog(pg(G1), ca as Asset);
|
|
199
201
|
const ch = rs(w, ca);
|
|
200
202
|
|
|
201
203
|
const pa: SceneAsset = {
|
|
202
204
|
kind: 'scene',
|
|
203
|
-
entities:
|
|
204
|
-
|
|
205
|
+
entities: {
|
|
206
|
+
'entity-0': { components: { Transform: {} } },
|
|
207
|
+
child: { components: {}, instance: { source: G1 } },
|
|
208
|
+
},
|
|
205
209
|
};
|
|
206
210
|
const ph = rs(w, pa);
|
|
207
211
|
wr(w, ph, ch);
|
|
@@ -220,7 +224,7 @@ describe('m2-t3 — AC-03 window invariants', () => {
|
|
|
220
224
|
const res = rootsToSceneAsset(reg, w, [inst.value.root]);
|
|
221
225
|
expect(res.ok).toBe(true);
|
|
222
226
|
if (!res.ok) return;
|
|
223
|
-
expect(res.value.
|
|
227
|
+
expect(Object.values(res.value.entities).filter((entity) => entity.instance)).toHaveLength(1); // keyed instance survives sparse members
|
|
224
228
|
});
|
|
225
229
|
|
|
226
230
|
it('multiple child mounts have non-overlapping windows', () => {
|
|
@@ -228,14 +232,14 @@ describe('m2-t3 — AC-03 window invariants', () => {
|
|
|
228
232
|
const w = new World();
|
|
229
233
|
const cA: SceneAsset = {
|
|
230
234
|
kind: 'scene',
|
|
231
|
-
entities:
|
|
235
|
+
entities: { 'entity-0': { components: { Transform: {} } } },
|
|
232
236
|
};
|
|
233
237
|
const cB: SceneAsset = {
|
|
234
238
|
kind: 'scene',
|
|
235
|
-
entities:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
entities: {
|
|
240
|
+
'entity-0': { components: { Transform: {} } },
|
|
241
|
+
'entity-1': { components: { Transform: {} } },
|
|
242
|
+
},
|
|
239
243
|
};
|
|
240
244
|
reg.catalog(pg(G1), cA as Asset);
|
|
241
245
|
reg.catalog(pg(G2), cB as Asset);
|
|
@@ -244,20 +248,19 @@ describe('m2-t3 — AC-03 window invariants', () => {
|
|
|
244
248
|
|
|
245
249
|
const pa: SceneAsset = {
|
|
246
250
|
kind: 'scene',
|
|
247
|
-
entities:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
251
|
+
entities: {
|
|
252
|
+
'entity-0': { components: { Transform: {} } },
|
|
253
|
+
childA: { components: {}, instance: { source: G1 } },
|
|
254
|
+
childB: { components: {}, instance: { source: G2 } },
|
|
255
|
+
},
|
|
252
256
|
};
|
|
253
257
|
const ph = rs(w, pa);
|
|
254
|
-
SceneOwner.worldSetSceneAssetResolver(w, (sIdx, pH) =>
|
|
255
|
-
(pH as unknown as number) !== (ph as unknown as number)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
);
|
|
258
|
+
SceneOwner.worldSetSceneAssetResolver(w, (sIdx, pH) => {
|
|
259
|
+
if ((pH as unknown as number) !== (ph as unknown as number))
|
|
260
|
+
return err({ code: 'asset-not-found' });
|
|
261
|
+
if (typeof sIdx === 'number') return ok(sIdx as unknown as Handle<'SceneAsset', 'shared'>);
|
|
262
|
+
return String(sIdx).toLowerCase() === G1 ? ok(hA) : ok(hB);
|
|
263
|
+
});
|
|
261
264
|
|
|
262
265
|
const inst = SceneOwner.worldInstantiateScene(w, ph);
|
|
263
266
|
expect(inst.ok).toBe(true);
|
|
@@ -268,27 +271,18 @@ describe('m2-t3 — AC-03 window invariants', () => {
|
|
|
268
271
|
if (!s.ok) continue;
|
|
269
272
|
const c = resolveAssetHandle<SceneAsset>(w, s.value as unknown as Handle<string, 'shared'>);
|
|
270
273
|
if (!c.ok) continue;
|
|
271
|
-
soi(reg, c.value as SceneAsset, c.value.entities.length === 1 ? G1 : G2);
|
|
274
|
+
soi(reg, c.value as SceneAsset, Object.keys(c.value.entities).length === 1 ? G1 : G2);
|
|
272
275
|
}
|
|
273
276
|
|
|
274
277
|
const res = rootsToSceneAsset(reg, w, [inst.value.root]);
|
|
275
278
|
expect(res.ok).toBe(true);
|
|
276
279
|
if (!res.ok) return;
|
|
277
280
|
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
for (let i = 0; i < W.length - 1; i++) {
|
|
284
|
-
const cur = W[i];
|
|
285
|
-
const next = W[i + 1];
|
|
286
|
-
if (!cur || !next) throw new Error('window index out of range');
|
|
287
|
-
expect(cur.f + cur.c).toBeLessThanOrEqual(next.f);
|
|
288
|
-
}
|
|
289
|
-
const owned = new Set(res.value.entities.map((e) => e.localId as unknown as number));
|
|
290
|
-
for (const w of W)
|
|
291
|
-
for (let lid = w.f; lid < w.f + w.c; lid++) expect(owned.has(lid)).toBe(false);
|
|
281
|
+
const instances = Object.values(res.value.entities).flatMap((entity) =>
|
|
282
|
+
entity.instance ? [entity.instance] : [],
|
|
283
|
+
);
|
|
284
|
+
expect(instances).toHaveLength(2);
|
|
285
|
+
expect(new Set(instances.map((instance) => instance.source))).toEqual(new Set([G1, G2]));
|
|
292
286
|
});
|
|
293
287
|
});
|
|
294
288
|
|
|
@@ -299,15 +293,17 @@ describe('m2-t4 — graft preservation', () => {
|
|
|
299
293
|
const w = new World();
|
|
300
294
|
const ca: SceneAsset = {
|
|
301
295
|
kind: 'scene',
|
|
302
|
-
entities:
|
|
296
|
+
entities: { 'entity-0': { components: { Transform: {} } } },
|
|
303
297
|
};
|
|
304
298
|
reg.catalog(pg(G1), ca as Asset);
|
|
305
299
|
const ch = rs(w, ca);
|
|
306
300
|
|
|
307
301
|
const pa: SceneAsset = {
|
|
308
302
|
kind: 'scene',
|
|
309
|
-
entities:
|
|
310
|
-
|
|
303
|
+
entities: {
|
|
304
|
+
'entity-0': { components: { Transform: {} } },
|
|
305
|
+
child: { components: {}, instance: { source: G1 } },
|
|
306
|
+
},
|
|
311
307
|
};
|
|
312
308
|
const ph = rs(w, pa);
|
|
313
309
|
wr(w, ph, ch);
|
|
@@ -337,7 +333,7 @@ describe('m2-t4 — graft preservation', () => {
|
|
|
337
333
|
|
|
338
334
|
// Prop should survive as owned entity (not eaten by mount).
|
|
339
335
|
let propFound = false;
|
|
340
|
-
for (const e of res.value.entities) {
|
|
336
|
+
for (const e of Object.values(res.value.entities)) {
|
|
341
337
|
if ((e.components as Record<string, Record<string, unknown>>).ChildOf) {
|
|
342
338
|
propFound = true;
|
|
343
339
|
break;
|
|
@@ -351,15 +347,17 @@ describe('m2-t4 — graft preservation', () => {
|
|
|
351
347
|
const w = new World();
|
|
352
348
|
const ca: SceneAsset = {
|
|
353
349
|
kind: 'scene',
|
|
354
|
-
entities:
|
|
350
|
+
entities: { 'entity-0': { components: { Transform: {} } } },
|
|
355
351
|
};
|
|
356
352
|
reg.catalog(pg(G1), ca as Asset);
|
|
357
353
|
const ch = rs(w, ca);
|
|
358
354
|
|
|
359
355
|
const pa: SceneAsset = {
|
|
360
356
|
kind: 'scene',
|
|
361
|
-
entities:
|
|
362
|
-
|
|
357
|
+
entities: {
|
|
358
|
+
'entity-0': { components: { Transform: {} } },
|
|
359
|
+
child: { components: {}, instance: { source: G1 } },
|
|
360
|
+
},
|
|
363
361
|
};
|
|
364
362
|
const ph = rs(w, pa);
|
|
365
363
|
wr(w, ph, ch);
|
|
@@ -25,7 +25,7 @@ function makeRegistry(): AssetRegistry {
|
|
|
25
25
|
return new AssetRegistry(makeMockShaderRegistry());
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function
|
|
28
|
+
function _localId(n: number): LocalEntityId {
|
|
29
29
|
return n as LocalEntityId;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -37,10 +37,7 @@ describe('m1t3 — Children absent from collect (AC-04)', () => {
|
|
|
37
37
|
it('2-level hierarchy: no entity has Children in collect output', () => {
|
|
38
38
|
const asset: SceneAsset = {
|
|
39
39
|
kind: 'scene',
|
|
40
|
-
entities:
|
|
41
|
-
{ localId: localId(0), components: {} },
|
|
42
|
-
{ localId: localId(1), components: {} },
|
|
43
|
-
],
|
|
40
|
+
entities: { 'entity-0': { components: {} }, 'entity-1': { components: {} } },
|
|
44
41
|
};
|
|
45
42
|
|
|
46
43
|
const world = new World();
|
|
@@ -59,12 +56,12 @@ describe('m1t3 — Children absent from collect (AC-04)', () => {
|
|
|
59
56
|
if (!collected.ok) return;
|
|
60
57
|
|
|
61
58
|
// Verify: no entity in the collected scene has 'Children' component.
|
|
62
|
-
for (const ent of collected.value.entities) {
|
|
59
|
+
for (const ent of Object.values(collected.value.entities)) {
|
|
63
60
|
expect(hasComp(ent, 'Children')).toBe(false);
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
// Also verify no entity has 'SceneInstance'.
|
|
67
|
-
for (const ent of collected.value.entities) {
|
|
64
|
+
for (const ent of Object.values(collected.value.entities)) {
|
|
68
65
|
expect(hasComp(ent, 'SceneInstance')).toBe(false);
|
|
69
66
|
}
|
|
70
67
|
});
|
|
@@ -76,18 +73,15 @@ describe('m1t3 — Children absent from collect (AC-04)', () => {
|
|
|
76
73
|
// Level 3: leaf scene instantiated under mid scene entity.
|
|
77
74
|
const leafAsset: SceneAsset = {
|
|
78
75
|
kind: 'scene',
|
|
79
|
-
entities:
|
|
76
|
+
entities: { 'entity-0': { components: {} } },
|
|
80
77
|
};
|
|
81
78
|
const midAsset: SceneAsset = {
|
|
82
79
|
kind: 'scene',
|
|
83
|
-
entities:
|
|
80
|
+
entities: { 'entity-0': { components: {} } },
|
|
84
81
|
};
|
|
85
82
|
const outerAsset: SceneAsset = {
|
|
86
83
|
kind: 'scene',
|
|
87
|
-
entities:
|
|
88
|
-
{ localId: localId(0), components: {} },
|
|
89
|
-
{ localId: localId(1), components: {} },
|
|
90
|
-
],
|
|
84
|
+
entities: { 'entity-0': { components: {} }, 'entity-1': { components: {} } },
|
|
91
85
|
};
|
|
92
86
|
|
|
93
87
|
const world = new World();
|
|
@@ -131,10 +125,10 @@ describe('m1t3 — Children absent from collect (AC-04)', () => {
|
|
|
131
125
|
|
|
132
126
|
// Hierarchy: outer root + (2 outer children) + mid root + (1 mid child) + leaf root + (1 leaf child) >= 7 entities.
|
|
133
127
|
// But the exact count depends on mount-collapse logic — focus on the Children assertion.
|
|
134
|
-
expect(collected.value.entities.length).toBeGreaterThan(0);
|
|
128
|
+
expect(Object.keys(collected.value.entities).length).toBeGreaterThan(0);
|
|
135
129
|
|
|
136
130
|
// No entity has Children or SceneInstance.
|
|
137
|
-
for (const ent of collected.value.entities) {
|
|
131
|
+
for (const ent of Object.values(collected.value.entities)) {
|
|
138
132
|
expect(hasComp(ent, 'Children')).toBe(false);
|
|
139
133
|
expect(hasComp(ent, 'SceneInstance')).toBe(false);
|
|
140
134
|
}
|
|
@@ -143,7 +137,7 @@ describe('m1t3 — Children absent from collect (AC-04)', () => {
|
|
|
143
137
|
it('degenerate: zero-hierarchy scene has no Children', () => {
|
|
144
138
|
const asset: SceneAsset = {
|
|
145
139
|
kind: 'scene',
|
|
146
|
-
entities:
|
|
140
|
+
entities: { 'entity-0': { components: {} } },
|
|
147
141
|
};
|
|
148
142
|
|
|
149
143
|
const world = new World();
|
|
@@ -161,7 +155,7 @@ describe('m1t3 — Children absent from collect (AC-04)', () => {
|
|
|
161
155
|
expect(collected.ok).toBe(true);
|
|
162
156
|
if (!collected.ok) return;
|
|
163
157
|
|
|
164
|
-
for (const ent of collected.value.entities) {
|
|
158
|
+
for (const ent of Object.values(collected.value.entities)) {
|
|
165
159
|
expect(hasComp(ent, 'Children')).toBe(false);
|
|
166
160
|
}
|
|
167
161
|
});
|
|
@@ -39,7 +39,7 @@ function makeRegistry(): AssetRegistry {
|
|
|
39
39
|
return new AssetRegistry(makeMockShaderRegistry());
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function
|
|
42
|
+
function _localId(n: number): LocalEntityId {
|
|
43
43
|
return n as LocalEntityId;
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -55,11 +55,11 @@ describe('m1t4 — AC-05 roundtrip regression (red-first)', () => {
|
|
|
55
55
|
// After instantiateScene, the synthetic root has Children populated by mirror hook.
|
|
56
56
|
const asset: SceneAsset = {
|
|
57
57
|
kind: 'scene',
|
|
58
|
-
entities:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
entities: {
|
|
59
|
+
'entity-0': { components: {} },
|
|
60
|
+
'entity-1': { components: {} },
|
|
61
|
+
'entity-2': { components: {} },
|
|
62
|
+
},
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
const world = new World();
|
|
@@ -128,7 +128,7 @@ describe('m1t4 — AC-05 roundtrip regression (red-first)', () => {
|
|
|
128
128
|
it('(c) degenerate: zero parent-child scene round-trips', () => {
|
|
129
129
|
const asset: SceneAsset = {
|
|
130
130
|
kind: 'scene',
|
|
131
|
-
entities:
|
|
131
|
+
entities: { 'entity-0': { components: {} } },
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
const world = new World();
|
|
@@ -172,7 +172,7 @@ describe('m1t4 — AC-05 roundtrip regression (red-first)', () => {
|
|
|
172
172
|
// world produced. The transient world column is reconstructed by propagate from
|
|
173
173
|
// the persisted local TRS -- proving skipping it loses nothing.
|
|
174
174
|
describe('w1 — AC-04 transient world roundtrip numeric equivalence', () => {
|
|
175
|
-
function
|
|
175
|
+
function _localId2(n: number): LocalEntityId {
|
|
176
176
|
return n as LocalEntityId;
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -180,9 +180,8 @@ describe('w1 — AC-04 transient world roundtrip numeric equivalence', () => {
|
|
|
180
180
|
// Non-identity local TRS so the world mat4 is a real, distinguishable value.
|
|
181
181
|
const asset: SceneAsset = {
|
|
182
182
|
kind: 'scene',
|
|
183
|
-
entities:
|
|
184
|
-
{
|
|
185
|
-
localId: localId2(0),
|
|
183
|
+
entities: {
|
|
184
|
+
'entity-0': {
|
|
186
185
|
components: {
|
|
187
186
|
Transform: {
|
|
188
187
|
pos: [3, 4, 5],
|
|
@@ -191,7 +190,7 @@ describe('w1 — AC-04 transient world roundtrip numeric equivalence', () => {
|
|
|
191
190
|
},
|
|
192
191
|
},
|
|
193
192
|
},
|
|
194
|
-
|
|
193
|
+
},
|
|
195
194
|
};
|
|
196
195
|
|
|
197
196
|
const world = new World();
|
|
@@ -213,7 +212,7 @@ describe('w1 — AC-04 transient world roundtrip numeric equivalence', () => {
|
|
|
213
212
|
if (!collected.ok) return;
|
|
214
213
|
|
|
215
214
|
// Serialized output must not carry world (transient).
|
|
216
|
-
for (const ent of collected.value.entities) {
|
|
215
|
+
for (const ent of Object.values(collected.value.entities)) {
|
|
217
216
|
const t = (ent.components as Record<string, Record<string, unknown>>).Transform;
|
|
218
217
|
if (t !== undefined) expect('world' in t).toBe(false);
|
|
219
218
|
expect((ent.components as Record<string, unknown>).GlobalTransform).toBeUndefined();
|
|
@@ -24,7 +24,7 @@ function makeRegistry(): AssetRegistry {
|
|
|
24
24
|
return new AssetRegistry(makeMockShaderRegistry());
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function
|
|
27
|
+
function _localId(n: number): LocalEntityId {
|
|
28
28
|
return n as LocalEntityId;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -41,7 +41,7 @@ describe('m1t2 — collect transient skip (AC-02 + AC-03)', () => {
|
|
|
41
41
|
it('(a) SceneInstance absent from collect output', () => {
|
|
42
42
|
const asset: SceneAsset = {
|
|
43
43
|
kind: 'scene',
|
|
44
|
-
entities:
|
|
44
|
+
entities: { 'entity-0': { components: {} } },
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const world = new World();
|
|
@@ -58,7 +58,7 @@ describe('m1t2 — collect transient skip (AC-02 + AC-03)', () => {
|
|
|
58
58
|
expect(collected.ok).toBe(true);
|
|
59
59
|
if (!collected.ok) return;
|
|
60
60
|
|
|
61
|
-
for (const ent of collected.value.entities) {
|
|
61
|
+
for (const ent of Object.values(collected.value.entities)) {
|
|
62
62
|
expect(hasComp(ent, 'SceneInstance')).toBe(false);
|
|
63
63
|
}
|
|
64
64
|
});
|
|
@@ -68,10 +68,7 @@ describe('m1t2 — collect transient skip (AC-02 + AC-03)', () => {
|
|
|
68
68
|
// (instance-level isRoot && ChildOf skip is independent of transient).
|
|
69
69
|
const asset: SceneAsset = {
|
|
70
70
|
kind: 'scene',
|
|
71
|
-
entities:
|
|
72
|
-
{ localId: localId(0), components: {} },
|
|
73
|
-
{ localId: localId(1), components: {} },
|
|
74
|
-
],
|
|
71
|
+
entities: { 'entity-0': { components: {} }, 'entity-1': { components: {} } },
|
|
75
72
|
};
|
|
76
73
|
|
|
77
74
|
const world = new World();
|
|
@@ -88,34 +85,27 @@ describe('m1t2 — collect transient skip (AC-02 + AC-03)', () => {
|
|
|
88
85
|
expect(collected.ok).toBe(true);
|
|
89
86
|
if (!collected.ok) return;
|
|
90
87
|
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
const rootWithChildOf = false;
|
|
88
|
+
// Both entities are authored roots. Their runtime ChildOf edges point to
|
|
89
|
+
// the transient synthetic root and are therefore omitted on collect.
|
|
94
90
|
let childrenWithChildOf = 0;
|
|
95
91
|
|
|
96
|
-
for (const ent of collected.value.entities) {
|
|
92
|
+
for (const ent of Object.values(collected.value.entities)) {
|
|
97
93
|
if (hasComp(ent, 'ChildOf')) {
|
|
98
94
|
childrenWithChildOf++;
|
|
99
95
|
}
|
|
100
96
|
}
|
|
101
97
|
|
|
102
|
-
|
|
103
|
-
// Root ChildOf is skipped by isRoot && ChildOf instance-level check.
|
|
104
|
-
expect(childrenWithChildOf).toBeGreaterThan(0);
|
|
105
|
-
// No root entity should have ChildOf (the only root is the synthetic scene root).
|
|
106
|
-
// We verify by counting: all entities with ChildOf are children, root has none.
|
|
107
|
-
// The sum indicates root coverage is correct.
|
|
108
|
-
expect(rootWithChildOf).toBe(false);
|
|
98
|
+
expect(childrenWithChildOf).toBe(0);
|
|
109
99
|
});
|
|
110
100
|
|
|
111
101
|
it('(c) non-root entities retain ChildOf', () => {
|
|
112
102
|
// Non-root entities (instantiated from SceneAsset localIds) must carry ChildOf.
|
|
113
103
|
const asset: SceneAsset = {
|
|
114
104
|
kind: 'scene',
|
|
115
|
-
entities:
|
|
116
|
-
|
|
117
|
-
{
|
|
118
|
-
|
|
105
|
+
entities: {
|
|
106
|
+
'entity-0': { components: {} },
|
|
107
|
+
'entity-1': { components: { ChildOf: { parent: 'entity-0' } } },
|
|
108
|
+
},
|
|
119
109
|
};
|
|
120
110
|
|
|
121
111
|
const world = new World();
|
|
@@ -132,9 +122,12 @@ describe('m1t2 — collect transient skip (AC-02 + AC-03)', () => {
|
|
|
132
122
|
expect(collected.ok).toBe(true);
|
|
133
123
|
if (!collected.ok) return;
|
|
134
124
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
125
|
+
// The explicit authored edge survives; the implicit synthetic-root edges
|
|
126
|
+
// are omitted by the collector.
|
|
127
|
+
const childOfEntities = Object.values(collected.value.entities).filter((e) =>
|
|
128
|
+
hasComp(e, 'ChildOf'),
|
|
129
|
+
);
|
|
130
|
+
expect(childOfEntities.length).toBe(1);
|
|
138
131
|
|
|
139
132
|
// Each child ChildOf.parent should point to root handle.
|
|
140
133
|
for (const c of childOfEntities) {
|
|
@@ -149,7 +142,7 @@ describe('m1t2 — collect transient skip (AC-02 + AC-03)', () => {
|
|
|
149
142
|
|
|
150
143
|
const asset: SceneAsset = {
|
|
151
144
|
kind: 'scene',
|
|
152
|
-
entities:
|
|
145
|
+
entities: { 'entity-0': { components: { M1T2_Ctrl: { val: 42, label: 'hello' } } } },
|
|
153
146
|
};
|
|
154
147
|
|
|
155
148
|
const world = new World();
|
|
@@ -166,7 +159,7 @@ describe('m1t2 — collect transient skip (AC-02 + AC-03)', () => {
|
|
|
166
159
|
expect(collected.ok).toBe(true);
|
|
167
160
|
if (!collected.ok) return;
|
|
168
161
|
|
|
169
|
-
const found = collected.value.entities.find((e) => hasComp(e, 'M1T2_Ctrl'));
|
|
162
|
+
const found = Object.values(collected.value.entities).find((e) => hasComp(e, 'M1T2_Ctrl'));
|
|
170
163
|
expect(found).toBeDefined();
|
|
171
164
|
const c = (found?.components as Record<string, Record<string, unknown>>).M1T2_Ctrl;
|
|
172
165
|
expect(c).toBeDefined();
|
|
@@ -69,7 +69,7 @@ describe('w7 type-level - 5 component schemas yield exact data shapes via ShapeO
|
|
|
69
69
|
expectTypeOf<{ materials: readonly [0] }>().not.toMatchTypeOf<SpawnData>();
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
it('Camera data shape
|
|
72
|
+
it('Camera data shape includes output exposure and color-grading fields', () => {
|
|
73
73
|
type Data = ShapeOf<SchemaOf<typeof Camera>>;
|
|
74
74
|
expectTypeOf<keyof Data>().toEqualTypeOf<
|
|
75
75
|
| 'fov'
|
|
@@ -83,7 +83,15 @@ describe('w7 type-level - 5 component schemas yield exact data shapes via ShapeO
|
|
|
83
83
|
| 'top'
|
|
84
84
|
| 'tonemap'
|
|
85
85
|
| 'exposure'
|
|
86
|
+
| 'exposureMode'
|
|
87
|
+
| 'compensationEv'
|
|
88
|
+
| 'rangeEv'
|
|
89
|
+
| 'rates'
|
|
86
90
|
| 'whitePoint'
|
|
91
|
+
| 'temperature'
|
|
92
|
+
| 'tint'
|
|
93
|
+
| 'colorLut'
|
|
94
|
+
| 'colorLutStrength'
|
|
87
95
|
| 'antialias'
|
|
88
96
|
| 'historyVersion'
|
|
89
97
|
| 'bloom'
|
|
@@ -102,7 +110,15 @@ describe('w7 type-level - 5 component schemas yield exact data shapes via ShapeO
|
|
|
102
110
|
// plan-strategy section 2.3 D-1 (f32 enum encoding).
|
|
103
111
|
expectTypeOf<Data['tonemap']>().toEqualTypeOf<number>();
|
|
104
112
|
expectTypeOf<Data['exposure']>().toEqualTypeOf<number>();
|
|
113
|
+
expectTypeOf<Data['exposureMode']>().toEqualTypeOf<number>();
|
|
114
|
+
expectTypeOf<Data['compensationEv']>().toEqualTypeOf<number>();
|
|
115
|
+
expectTypeOf<Data['rangeEv']>().toEqualTypeOf<Float32Array>();
|
|
116
|
+
expectTypeOf<Data['rates']>().toEqualTypeOf<Float32Array>();
|
|
105
117
|
expectTypeOf<Data['whitePoint']>().toEqualTypeOf<number>();
|
|
118
|
+
expectTypeOf<Data['temperature']>().toEqualTypeOf<number>();
|
|
119
|
+
expectTypeOf<Data['tint']>().toEqualTypeOf<number>();
|
|
120
|
+
expectTypeOf<Data['colorLut']>().toEqualTypeOf<Handle<'TextureAsset', 'shared'>>();
|
|
121
|
+
expectTypeOf<Data['colorLutStrength']>().toEqualTypeOf<number>();
|
|
106
122
|
expectTypeOf<Data['historyVersion']>().toEqualTypeOf<number>();
|
|
107
123
|
// feat-20260709 M3: clear-color quartet collapsed into one inline
|
|
108
124
|
// array<f32,4> column; read side resolves to Float32Array (mirrors the
|