@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
|
@@ -12,6 +12,7 @@ import type { Renderer as RendererType } from '@forgeax/engine-render';
|
|
|
12
12
|
import {
|
|
13
13
|
Camera,
|
|
14
14
|
DirectionalLight,
|
|
15
|
+
InstanceCollectionStore,
|
|
15
16
|
Instances,
|
|
16
17
|
MeshFilter,
|
|
17
18
|
MeshRenderer,
|
|
@@ -213,6 +214,7 @@ function drawPublished(renderer: RendererType, world: WorldType) {
|
|
|
213
214
|
},
|
|
214
215
|
setVertexBuffer: () => undefined,
|
|
215
216
|
setIndexBuffer: () => undefined,
|
|
217
|
+
setViewport: () => undefined,
|
|
216
218
|
setBindGroup: (groupIndex: number, _bg: unknown, dynamicOffsets?: number[]) => {
|
|
217
219
|
log.setBindGroupCount++;
|
|
218
220
|
// w8-A: capture mesh uniform buffer offset at group=2.
|
|
@@ -297,6 +299,7 @@ function drawPublished(renderer: RendererType, world: WorldType) {
|
|
|
297
299
|
materialShaders: [
|
|
298
300
|
materialShaderStub('forgeax::default-standard-pbr'),
|
|
299
301
|
materialShaderStub('forgeax::default-unlit'),
|
|
302
|
+
materialShaderStub('forgeax::default-shadow-caster'),
|
|
300
303
|
],
|
|
301
304
|
};
|
|
302
305
|
return `data:application/json,${encodeURIComponent(JSON.stringify(manifest))}`;
|
|
@@ -435,7 +438,10 @@ function drawPublished(renderer: RendererType, world: WorldType) {
|
|
|
435
438
|
drawPublished(renderer, world as WorldType);
|
|
436
439
|
|
|
437
440
|
// DirectionalLight with merged shadow fields.
|
|
438
|
-
expect(
|
|
441
|
+
expect(
|
|
442
|
+
errors,
|
|
443
|
+
JSON.stringify(errors, (_key, value) => (value instanceof Error ? value.stack : value)),
|
|
444
|
+
).toHaveLength(0);
|
|
439
445
|
// Standard Cluster owns the shadow, G-buffer, lighting, forward, and
|
|
440
446
|
// output topology stages. Keep this structural rather than freezing a
|
|
441
447
|
// pass-count literal that changes when a named stage is split.
|
|
@@ -1523,33 +1529,16 @@ describe('RenderSystem Skylight extract + record phase contract (plan-strategy D
|
|
|
1523
1529
|
});
|
|
1524
1530
|
});
|
|
1525
1531
|
|
|
1526
|
-
// ─── from render-system-
|
|
1532
|
+
// ─── from render-system-instance-collection.test.ts ───
|
|
1527
1533
|
{
|
|
1528
|
-
//
|
|
1529
|
-
//
|
|
1530
|
-
// Locks the AC-06 invariant: when an entity carries an `Instances` component
|
|
1531
|
-
// whose `transforms` array<f32> snapshot has a length that is NOT a multiple
|
|
1532
|
-
// of 16, the RenderSystem extract stage MUST emit a structured `EcsError`
|
|
1533
|
-
// with `code: 'instance-transforms-stride-mismatch'` BEFORE the GPU upload
|
|
1534
|
-
// path is reached. The error carries a discriminated `.detail` per
|
|
1535
|
-
// `EcsErrorDetail`:
|
|
1536
|
-
//
|
|
1537
|
-
// detail = { actualLength: number, expectedStride: 16 }
|
|
1534
|
+
// Renderer-owned collection authoring and extract boundary coverage.
|
|
1538
1535
|
//
|
|
1539
|
-
//
|
|
1540
|
-
//
|
|
1541
|
-
//
|
|
1542
|
-
// (c) length === 15 -> fail; structured error fires with detail; no
|
|
1543
|
-
// renderable lands.
|
|
1544
|
-
// (d) length === 17 -> fail; structured error fires with detail; no
|
|
1545
|
-
// renderable lands.
|
|
1536
|
+
// The ECS component carries only a collection id. The renderer validates the
|
|
1537
|
+
// canonical packed matrix payload before publication and reports a typed
|
|
1538
|
+
// InstanceCollectionError; malformed input never reaches GPU upload.
|
|
1546
1539
|
//
|
|
1547
|
-
//
|
|
1548
|
-
//
|
|
1549
|
-
// `world.get(entity, Instances)` snapshot is extracted but BEFORE any
|
|
1550
|
-
// per-element copy / GPU upload. Violation routes through Layer-3
|
|
1551
|
-
// `World._errorHandler` (set via `world.setErrorHandler`); the renderable
|
|
1552
|
-
// MUST NOT be pushed (fail-fast halts the per-entity branch).
|
|
1540
|
+
// Coverage keeps legal collection association visible and rejects malformed
|
|
1541
|
+
// stride/range input with the Render-owned error facts.
|
|
1553
1542
|
|
|
1554
1543
|
interface CollectedError {
|
|
1555
1544
|
readonly code: string;
|
|
@@ -1564,7 +1553,11 @@ describe('RenderSystem Skylight extract + record phase contract (plan-strategy D
|
|
|
1564
1553
|
return { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] };
|
|
1565
1554
|
}
|
|
1566
1555
|
|
|
1567
|
-
function makeWorld(): {
|
|
1556
|
+
function makeWorld(): {
|
|
1557
|
+
world: World;
|
|
1558
|
+
collected: CollectedError[];
|
|
1559
|
+
instanceCollections: InstanceCollectionStore;
|
|
1560
|
+
} {
|
|
1568
1561
|
const collected: CollectedError[] = [];
|
|
1569
1562
|
const world = new World();
|
|
1570
1563
|
vi.spyOn(console, 'error').mockImplementation((...args: unknown[]) => {
|
|
@@ -1599,10 +1592,15 @@ describe('RenderSystem Skylight extract + record phase contract (plan-strategy D
|
|
|
1599
1592
|
},
|
|
1600
1593
|
})
|
|
1601
1594
|
.unwrap();
|
|
1602
|
-
return { world, collected };
|
|
1595
|
+
return { world, collected, instanceCollections: new InstanceCollectionStore() };
|
|
1603
1596
|
}
|
|
1604
1597
|
|
|
1605
|
-
function spawnInstancedEntity(
|
|
1598
|
+
function spawnInstancedEntity(
|
|
1599
|
+
world: World,
|
|
1600
|
+
instanceCollections: InstanceCollectionStore,
|
|
1601
|
+
transforms: Float32Array,
|
|
1602
|
+
): void {
|
|
1603
|
+
const collection = instanceCollections.create({ transforms }).unwrap();
|
|
1606
1604
|
world
|
|
1607
1605
|
.spawn(
|
|
1608
1606
|
{ component: Transform, data: makeIdentityTransform() },
|
|
@@ -1614,58 +1612,48 @@ describe('RenderSystem Skylight extract + record phase contract (plan-strategy D
|
|
|
1614
1612
|
component: MeshRenderer,
|
|
1615
1613
|
data: {} as never,
|
|
1616
1614
|
},
|
|
1617
|
-
{ component: Instances, data: {
|
|
1615
|
+
{ component: Instances, data: { collectionId: collection.collectionId } },
|
|
1618
1616
|
)
|
|
1619
1617
|
.unwrap();
|
|
1620
1618
|
}
|
|
1621
1619
|
|
|
1622
|
-
describe('
|
|
1620
|
+
describe('renderer-owned instance collection validation', () => {
|
|
1623
1621
|
it('(a) length === 16: no error; renderable lands', () => {
|
|
1624
|
-
const { world, collected } = makeWorld();
|
|
1622
|
+
const { world, collected, instanceCollections } = makeWorld();
|
|
1625
1623
|
const transforms = new Float32Array(16);
|
|
1626
|
-
spawnInstancedEntity(world, transforms);
|
|
1627
|
-
const frame = extractFrame(world, prepareExtractContext(world));
|
|
1628
|
-
expect(collected.
|
|
1629
|
-
0,
|
|
1630
|
-
);
|
|
1624
|
+
spawnInstancedEntity(world, instanceCollections, transforms);
|
|
1625
|
+
const frame = extractFrame(world, prepareExtractContext(world, { instanceCollections }));
|
|
1626
|
+
expect(collected).toHaveLength(0);
|
|
1631
1627
|
expect(frame.renderables.length).toBe(1);
|
|
1632
1628
|
});
|
|
1633
1629
|
|
|
1634
1630
|
it('(b) length === 32: no error; renderable lands', () => {
|
|
1635
|
-
const { world, collected } = makeWorld();
|
|
1631
|
+
const { world, collected, instanceCollections } = makeWorld();
|
|
1636
1632
|
const transforms = new Float32Array(32);
|
|
1637
|
-
spawnInstancedEntity(world, transforms);
|
|
1638
|
-
const frame = extractFrame(world, prepareExtractContext(world));
|
|
1639
|
-
expect(collected.
|
|
1640
|
-
0,
|
|
1641
|
-
);
|
|
1633
|
+
spawnInstancedEntity(world, instanceCollections, transforms);
|
|
1634
|
+
const frame = extractFrame(world, prepareExtractContext(world, { instanceCollections }));
|
|
1635
|
+
expect(collected).toHaveLength(0);
|
|
1642
1636
|
expect(frame.renderables.length).toBe(1);
|
|
1643
1637
|
});
|
|
1644
1638
|
|
|
1645
|
-
it('(c) length === 15: structured error
|
|
1646
|
-
const {
|
|
1639
|
+
it('(c) length === 15: structured authoring error carries typed facts', () => {
|
|
1640
|
+
const { instanceCollections } = makeWorld();
|
|
1647
1641
|
const transforms = new Float32Array(15);
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
expect(
|
|
1652
|
-
|
|
1653
|
-
expect(detail.actualLength).toBe(15);
|
|
1654
|
-
expect(detail.expectedStride).toBe(16);
|
|
1655
|
-
expect(frame.renderables.length).toBe(0);
|
|
1642
|
+
const result = instanceCollections.create({ transforms });
|
|
1643
|
+
expect(result.ok).toBe(false);
|
|
1644
|
+
if (result.ok) return;
|
|
1645
|
+
expect(result.error.code).toBe('instance-collection-invalid');
|
|
1646
|
+
expect(result.error.detail?.actual).toBe(15);
|
|
1656
1647
|
});
|
|
1657
1648
|
|
|
1658
|
-
it('(d) length === 17: structured error
|
|
1659
|
-
const {
|
|
1649
|
+
it('(d) length === 17: structured authoring error carries typed facts', () => {
|
|
1650
|
+
const { instanceCollections } = makeWorld();
|
|
1660
1651
|
const transforms = new Float32Array(17);
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
expect(
|
|
1665
|
-
|
|
1666
|
-
expect(detail.actualLength).toBe(17);
|
|
1667
|
-
expect(detail.expectedStride).toBe(16);
|
|
1668
|
-
expect(frame.renderables.length).toBe(0);
|
|
1652
|
+
const result = instanceCollections.create({ transforms });
|
|
1653
|
+
expect(result.ok).toBe(false);
|
|
1654
|
+
if (result.ok) return;
|
|
1655
|
+
expect(result.error.code).toBe('instance-collection-invalid');
|
|
1656
|
+
expect(result.error.detail?.actual).toBe(17);
|
|
1669
1657
|
});
|
|
1670
1658
|
});
|
|
1671
1659
|
}
|
|
@@ -112,7 +112,6 @@ describe('real Dawn device-loss provider feasibility', () => {
|
|
|
112
112
|
'No test-visible non-destroyed loss injection method is exposed by the real Dawn GPU, adapter, or device objects',
|
|
113
113
|
providerSurface,
|
|
114
114
|
};
|
|
115
|
-
console.log(`[dawn-loss-feasibility] ${JSON.stringify(evidence)}`);
|
|
116
115
|
expect(evidence.submittedWork).toBe(true);
|
|
117
116
|
expect(['available', 'insufficient-evidence']).toContain(evidence.status);
|
|
118
117
|
if (evidence.status === 'available') {
|
|
@@ -39,9 +39,6 @@ describe('real Dawn renderer device-loss recovery', () => {
|
|
|
39
39
|
'A non-destroyed raw loss was observed, but this fixture does not exercise public Renderer recovery',
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
|
-
console.log(
|
|
43
|
-
`[dawn-loss-recovery] ${JSON.stringify({ ...evidence, rawLoss: rawLoss ?? 'provider-missing' })}`,
|
|
44
|
-
);
|
|
45
42
|
expect(evidence.submittedWork).toBe(true);
|
|
46
43
|
expect(evidence.worlds).toHaveLength(2);
|
|
47
44
|
expect(evidence.status).toBe('insufficient-evidence');
|