@forgeax/engine-runtime 0.1.20 → 0.1.23
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 +105 -3
- package/dist/__tests__/create-renderer-gpu-pass-timing.unit.test.d.ts +2 -0
- package/dist/__tests__/create-renderer-gpu-pass-timing.unit.test.d.ts.map +1 -0
- package/dist/__tests__/helpers/register-runtime-components.d.ts.map +1 -1
- package/dist/__tests__/helpers/register-scene-components.d.ts.map +1 -1
- package/dist/__tests__/lights-preservation.fixup.test.d.ts +2 -0
- package/dist/__tests__/lights-preservation.fixup.test.d.ts.map +1 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
- package/dist/createRenderer.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +4 -2
- package/dist/index.mjs.map +1 -1
- package/dist/sprite-param-values.d.ts +1 -1
- package/package.json +30 -30
- package/src/__tests__/active-camera.unit.test.ts +6 -8
- package/src/__tests__/animation-graph-asset-union.test-d.ts +2 -0
- package/src/__tests__/asset-brand-and-name-retirement.test.ts +4 -3
- package/src/__tests__/asset-registry-scene.test-d.ts +1 -0
- package/src/__tests__/asset-registry.recursive.spec.ts +1 -1
- package/src/__tests__/asset.unit.test.ts +5 -2
- package/src/__tests__/clamp-to-last.e2e.browser.test.ts +2 -2
- package/src/__tests__/collect-transient-roundtrip.test.ts +14 -3
- package/src/__tests__/components.test-d.ts +19 -9
- package/src/__tests__/components.unit.test.ts +36 -26
- package/src/__tests__/create-renderer-assembly.integration.test.ts +24 -0
- package/src/__tests__/create-renderer-gpu-pass-timing.unit.test.ts +32 -0
- package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +66 -0
- package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +9 -1
- package/src/__tests__/dawn/instances-shadow.dawn.test.ts +15 -2
- package/src/__tests__/errors.unit.test.ts +54 -20
- package/src/__tests__/extract-frames-merge.test.ts +51 -40
- package/src/__tests__/extract-skin-mismatch.unit.test.ts +6 -2
- package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +13 -0
- package/src/__tests__/geometry.unit.test.ts +7 -10
- package/src/__tests__/helpers/register-runtime-components.ts +9 -1
- package/src/__tests__/helpers/register-scene-components.ts +9 -2
- package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +10 -11
- package/src/__tests__/light-snapshot.test-d.ts +35 -1
- package/src/__tests__/lights-preservation.fixup.test.ts +125 -0
- package/src/__tests__/lights.unit.test.ts +223 -308
- package/src/__tests__/loader-name-survival.test.ts +8 -9
- package/src/__tests__/package-promotion.test.ts +7 -11
- package/src/__tests__/pbr-pipeline.unit.test.ts +22 -17
- package/src/__tests__/pipeline.unit.test.ts +68 -226
- package/src/__tests__/point-light-shadow.browser.test.ts +209 -83
- package/src/__tests__/point-light-shadow.unit.test.ts +24 -47
- package/src/__tests__/propagate-transforms.dawn.test.ts +15 -16
- package/src/__tests__/record-worldid-isolation.test.ts +16 -32
- package/src/__tests__/render-error-exhaustive.test-d.ts +73 -9
- package/src/__tests__/render-system-extract.test.ts +5 -5
- package/src/__tests__/render-system-mega.test.ts +30 -41
- package/src/__tests__/render-system-record-multi-material-textureview.test.ts +5 -1
- package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +5 -1
- package/src/__tests__/render-system-skin-bg.unit.test.ts +14 -9
- package/src/__tests__/renderer-recover.unit.test.ts +8 -0
- package/src/__tests__/renderer-surface.unit.test.ts +229 -5
- package/src/__tests__/resolveName-unified-entry.test.ts +6 -6
- package/src/__tests__/resolveName.test.ts +14 -2
- package/src/__tests__/shadow-csm-atlas.test.ts +41 -0
- package/src/__tests__/shadow-csm-cascade-loadop.test.ts +1 -0
- package/src/__tests__/shadow-csm-component.test.ts +31 -24
- package/src/__tests__/shadow-csm-extract.test.ts +156 -47
- package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +72 -0
- package/src/__tests__/shadow-csm-shader.dawn.test.ts +19 -0
- package/src/__tests__/shadow-csm-tile-consistency.test.ts +1 -0
- package/src/__tests__/shadow-csm-ubo.test.ts +43 -45
- package/src/__tests__/shadow-csm-viewport.browser.test.ts +29 -0
- package/src/__tests__/shadow-fields-observable.dawn.test.ts +100 -24
- package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +1 -1
- package/src/__tests__/skin-parented-double-transform.test.ts +5 -5
- package/src/__tests__/skinned-shadow-mixed.dawn.test.ts +4 -7
- package/src/__tests__/skylight-lazy-projection.test.ts +8 -5
- package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +11 -13
- package/src/__tests__/ssao-bgl.test.ts +12 -17
- package/src/__tests__/ssao-passes.test.ts +19 -2
- package/src/__tests__/systems.unit.test.ts +69 -205
- package/src/__tests__/template-game-default-api-contract.test.ts +45 -23
- package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +10 -4
- package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +2 -2
- package/src/__tests__/view-ubo-layout.browser.test.ts +20 -10
- package/src/collect-scene-asset.ts +1 -1
- package/src/components/__tests__/transform-vec-schema.test.ts +2 -2
- package/src/createRenderer.ts +2 -0
- package/src/index.ts +1 -2
- package/src/sprite-param-values.ts +1 -1
- package/dist/.tsbuildinfo +0 -1
|
@@ -80,7 +80,13 @@ import {
|
|
|
80
80
|
} from '@forgeax/engine-render';
|
|
81
81
|
import type { BindGroupEntry, Buffer, Sampler, Texture, TextureView } from '@forgeax/engine-rhi';
|
|
82
82
|
import { ok as rhiOk } from '@forgeax/engine-rhi';
|
|
83
|
-
import {
|
|
83
|
+
import {
|
|
84
|
+
ChildOf,
|
|
85
|
+
GlobalTransform,
|
|
86
|
+
Name,
|
|
87
|
+
propagateTransforms,
|
|
88
|
+
Transform,
|
|
89
|
+
} from '@forgeax/engine-scene';
|
|
84
90
|
import { TONEMAP_LUMINANCE_EPSILON } from '@forgeax/engine-shader';
|
|
85
91
|
import type { SkinErrorCode } from '@forgeax/engine-skinning';
|
|
86
92
|
import {
|
|
@@ -206,11 +212,7 @@ import {
|
|
|
206
212
|
} from '../../../render/src/instance-buffer-cache';
|
|
207
213
|
import { standardPipeline as urpPipeline } from '../../../render/src/pipeline/standard-pipeline';
|
|
208
214
|
import { ZERO_CAMERA_CLEAR_FALLBACK } from '../../../render/src/record/frame-snapshot';
|
|
209
|
-
import {
|
|
210
|
-
warnMultiLightDirectional,
|
|
211
|
-
warnMultiLightPoint,
|
|
212
|
-
warnMultiLightSpot,
|
|
213
|
-
} from '../../../render/src/record/helpers';
|
|
215
|
+
import { warnMultiLightDirectional } from '../../../render/src/record/helpers';
|
|
214
216
|
import type { CameraSnapshot } from '../../../render/src/render-contract';
|
|
215
217
|
import {
|
|
216
218
|
type ExtractedLights,
|
|
@@ -448,9 +450,9 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
448
450
|
];
|
|
449
451
|
}
|
|
450
452
|
|
|
451
|
-
describe('Camera schema (
|
|
452
|
-
it('Camera.schema has
|
|
453
|
-
expect(Object.keys(Camera.fields).length).toBe(
|
|
453
|
+
describe('Camera schema (21 fields: 17 f32 + clearColor array + autoAspect bool + target after render-target extension)', () => {
|
|
454
|
+
it('Camera.schema has 21 fields (17 f32 + clearColor array + autoAspect bool + target: perspective quartet + projection + ortho quartet + tonemap trio + antialias + bloom quartet + clearColor + autoAspect)', () => {
|
|
455
|
+
expect(Object.keys(Camera.fields).length).toBe(21);
|
|
454
456
|
expect(componentFieldType(Camera, 'fov')).toBe('f32');
|
|
455
457
|
expect(componentFieldType(Camera, 'aspect')).toBe('f32');
|
|
456
458
|
expect(componentFieldType(Camera, 'near')).toBe('f32');
|
|
@@ -476,6 +478,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
476
478
|
// feat-20260617-host-engine-contract-and-video-cutscene / M3: aspect-sync
|
|
477
479
|
// opt-out flag (bool column tier).
|
|
478
480
|
expect(componentFieldType(Camera, 'autoAspect')).toBe('bool');
|
|
481
|
+
expect(componentFieldType(Camera, 'target')).toBe('shared<RenderTarget>');
|
|
479
482
|
});
|
|
480
483
|
});
|
|
481
484
|
|
|
@@ -574,6 +577,8 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
574
577
|
directionalCount: 0,
|
|
575
578
|
point: [],
|
|
576
579
|
spot: [],
|
|
580
|
+
rect: [],
|
|
581
|
+
directionalShadowError: undefined,
|
|
577
582
|
lightSpaceMatrix: undefined,
|
|
578
583
|
shadowMapSize: undefined,
|
|
579
584
|
pointShadow: [],
|
|
@@ -688,8 +693,6 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
688
693
|
unlitPipelineHdr: null,
|
|
689
694
|
shadowFallbackTextureView: { __label: 'shadow-fallback-view' },
|
|
690
695
|
skylightFallback: null,
|
|
691
|
-
pointLightsBuffer: { __label: 'point-lights-buf' },
|
|
692
|
-
spotLightsBuffer: { __label: 'spot-lights-buf' },
|
|
693
696
|
pbrPipelineLayout: { __label: 'pbr-pipeline-layout' },
|
|
694
697
|
defaultNormalTextureView: { __label: 'default-normal-view' },
|
|
695
698
|
perPassResources: {
|
|
@@ -767,14 +770,13 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
767
770
|
temporalFrameInput: undefined,
|
|
768
771
|
compiledFrameGraph: null,
|
|
769
772
|
compiledFrameGraphTopologyKey: null,
|
|
773
|
+
compiledFrameGraphGeneration: 0,
|
|
770
774
|
retiredCompiledFrameGraphs: new Set(),
|
|
771
775
|
instanceBuffers: new Map(),
|
|
772
776
|
transientInstanceBuffers: [],
|
|
773
777
|
warnedZeroLightStandard: false,
|
|
774
778
|
warnedShadowDisabled: false,
|
|
775
779
|
warnedMultiLightDirectional: false,
|
|
776
|
-
warnedMultiLightPoint: false,
|
|
777
|
-
warnedMultiLightSpot: false,
|
|
778
780
|
warnedSkyboxTonemapNone: false,
|
|
779
781
|
warnedMissingBaseColorTextureHandles: new Set<number>(),
|
|
780
782
|
warnedNineSliceScaleEntities: new Set<number>(),
|
|
@@ -787,8 +789,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
787
789
|
installedPipelineHandle: 0,
|
|
788
790
|
activePipeline: urpPipeline,
|
|
789
791
|
installedPipelineConfig: undefined,
|
|
790
|
-
|
|
791
|
-
hdrpOncePerFrameFired: new Set(),
|
|
792
|
+
standardOncePerFrameFired: new Set(),
|
|
792
793
|
},
|
|
793
794
|
{ unlit: 0 },
|
|
794
795
|
{ createBindGroup: 0, keys: [] },
|
|
@@ -824,14 +825,13 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
824
825
|
temporalFrameInput: undefined,
|
|
825
826
|
compiledFrameGraph: null,
|
|
826
827
|
compiledFrameGraphTopologyKey: null,
|
|
828
|
+
compiledFrameGraphGeneration: 0,
|
|
827
829
|
retiredCompiledFrameGraphs: new Set(),
|
|
828
830
|
instanceBuffers: new Map(),
|
|
829
831
|
transientInstanceBuffers: [],
|
|
830
832
|
warnedZeroLightStandard: false,
|
|
831
833
|
warnedShadowDisabled: false,
|
|
832
834
|
warnedMultiLightDirectional: false,
|
|
833
|
-
warnedMultiLightPoint: false,
|
|
834
|
-
warnedMultiLightSpot: false,
|
|
835
835
|
warnedSkyboxTonemapNone: false,
|
|
836
836
|
warnedMissingBaseColorTextureHandles: new Set<number>(),
|
|
837
837
|
warnedNineSliceScaleEntities: new Set<number>(),
|
|
@@ -844,8 +844,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
844
844
|
installedPipelineHandle: 0,
|
|
845
845
|
activePipeline: urpPipeline,
|
|
846
846
|
installedPipelineConfig: undefined,
|
|
847
|
-
|
|
848
|
-
hdrpOncePerFrameFired: new Set(),
|
|
847
|
+
standardOncePerFrameFired: new Set(),
|
|
849
848
|
},
|
|
850
849
|
{ unlit: 0 },
|
|
851
850
|
{ createBindGroup: 0, keys: [] },
|
|
@@ -914,8 +913,9 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
914
913
|
// --- from fxaa-pipeline.test.ts ---
|
|
915
914
|
|
|
916
915
|
describe('feat-20260528-fxaa-post-processing M2 w8: FXAA pipeline construction (success path)', () => {
|
|
917
|
-
it('D-2: FXAA BGL has exactly
|
|
918
|
-
//
|
|
916
|
+
it('D-2: FXAA BGL has exactly 3 entries: texture + sampler + dither policy UBO', () => {
|
|
917
|
+
// FXAA owns a per-pass output-policy UBO so pipeline asset config can
|
|
918
|
+
// disable dither without rebuilding the shader or graph topology.
|
|
919
919
|
// The sampler entry uses { type: 'filtering' } (linear sampling).
|
|
920
920
|
const bglEntries = [
|
|
921
921
|
{
|
|
@@ -928,15 +928,21 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
928
928
|
visibility: 2, // GPU_SHADER_STAGE_FRAGMENT = 0x2
|
|
929
929
|
sampler: { type: 'filtering' },
|
|
930
930
|
},
|
|
931
|
+
{
|
|
932
|
+
binding: 2,
|
|
933
|
+
visibility: 2,
|
|
934
|
+
buffer: { type: 'uniform' },
|
|
935
|
+
},
|
|
931
936
|
];
|
|
932
|
-
expect(bglEntries).toHaveLength(
|
|
937
|
+
expect(bglEntries).toHaveLength(3);
|
|
933
938
|
expect(bglEntries[0]?.binding).toBe(0);
|
|
934
939
|
expect(bglEntries[0]?.texture).toBeDefined();
|
|
935
940
|
expect(bglEntries[1]?.binding).toBe(1);
|
|
936
941
|
expect(bglEntries[1]?.sampler).toBeDefined();
|
|
937
|
-
|
|
942
|
+
expect(bglEntries[2]?.binding).toBe(2);
|
|
943
|
+
expect(bglEntries[2]?.buffer).toEqual({ type: 'uniform' });
|
|
938
944
|
const hasBuffer = bglEntries.some((e) => 'buffer' in (e as Record<string, unknown>));
|
|
939
|
-
expect(hasBuffer).toBe(
|
|
945
|
+
expect(hasBuffer).toBe(true);
|
|
940
946
|
});
|
|
941
947
|
|
|
942
948
|
it('D-3: FXAA pipeline target format = swap-chain storage format (non-srgb, helper Channel 2 truth)', () => {
|
|
@@ -1309,8 +1315,6 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
1309
1315
|
function makeFrameState() {
|
|
1310
1316
|
return {
|
|
1311
1317
|
warnedMultiLightDirectional: false,
|
|
1312
|
-
warnedMultiLightPoint: false,
|
|
1313
|
-
warnedMultiLightSpot: false,
|
|
1314
1318
|
};
|
|
1315
1319
|
}
|
|
1316
1320
|
|
|
@@ -1379,151 +1383,6 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
1379
1383
|
}
|
|
1380
1384
|
});
|
|
1381
1385
|
});
|
|
1382
|
-
|
|
1383
|
-
describe('warnMultiLightPoint (M3 / AC-04 / AC-05 (c))', () => {
|
|
1384
|
-
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
1385
|
-
|
|
1386
|
-
beforeEach(() => {
|
|
1387
|
-
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1388
|
-
});
|
|
1389
|
-
|
|
1390
|
-
afterEach(() => {
|
|
1391
|
-
warnSpy.mockRestore();
|
|
1392
|
-
});
|
|
1393
|
-
|
|
1394
|
-
it('first hit fires console.warn (N>4)', () => {
|
|
1395
|
-
const fs = makeFrameState();
|
|
1396
|
-
warnMultiLightPoint(fs, 5);
|
|
1397
|
-
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
1398
|
-
expect(fs.warnedMultiLightPoint).toBe(true);
|
|
1399
|
-
const call = warnSpy.mock.calls[0];
|
|
1400
|
-
expect(call[1]).toMatchObject({
|
|
1401
|
-
code: 'render-system-multi-light',
|
|
1402
|
-
expected: 'at most 4 point',
|
|
1403
|
-
detail: { type: 'point', got: 5 },
|
|
1404
|
-
});
|
|
1405
|
-
});
|
|
1406
|
-
|
|
1407
|
-
it('no-op when pointCount <= 4', () => {
|
|
1408
|
-
const fs = makeFrameState();
|
|
1409
|
-
warnMultiLightPoint(fs, 4);
|
|
1410
|
-
warnMultiLightPoint(fs, 0);
|
|
1411
|
-
expect(warnSpy).toHaveBeenCalledTimes(0);
|
|
1412
|
-
expect(fs.warnedMultiLightPoint).toBe(false);
|
|
1413
|
-
});
|
|
1414
|
-
|
|
1415
|
-
it('warn-once across 6 frames', () => {
|
|
1416
|
-
const fs = makeFrameState();
|
|
1417
|
-
warnMultiLightPoint(fs, 5);
|
|
1418
|
-
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
1419
|
-
for (let i = 0; i < 5; i++) {
|
|
1420
|
-
warnMultiLightPoint(fs, 5);
|
|
1421
|
-
}
|
|
1422
|
-
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
1423
|
-
});
|
|
1424
|
-
|
|
1425
|
-
it('production gate suppresses output but flips latch', () => {
|
|
1426
|
-
const fs = makeFrameState();
|
|
1427
|
-
warnMultiLightPoint(fs, 5, { env: { NODE_ENV: 'production' } });
|
|
1428
|
-
expect(warnSpy).toHaveBeenCalledTimes(0);
|
|
1429
|
-
expect(fs.warnedMultiLightPoint).toBe(true);
|
|
1430
|
-
});
|
|
1431
|
-
});
|
|
1432
|
-
|
|
1433
|
-
describe('warnMultiLightSpot (M3 / AC-04 / AC-05 (c))', () => {
|
|
1434
|
-
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
1435
|
-
|
|
1436
|
-
beforeEach(() => {
|
|
1437
|
-
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1438
|
-
});
|
|
1439
|
-
|
|
1440
|
-
afterEach(() => {
|
|
1441
|
-
warnSpy.mockRestore();
|
|
1442
|
-
});
|
|
1443
|
-
|
|
1444
|
-
it('first hit fires console.warn (N>4)', () => {
|
|
1445
|
-
const fs = makeFrameState();
|
|
1446
|
-
warnMultiLightSpot(fs, 5);
|
|
1447
|
-
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
1448
|
-
expect(fs.warnedMultiLightSpot).toBe(true);
|
|
1449
|
-
const call = warnSpy.mock.calls[0];
|
|
1450
|
-
expect(call[1]).toMatchObject({
|
|
1451
|
-
code: 'render-system-multi-light',
|
|
1452
|
-
expected: 'at most 4 spot',
|
|
1453
|
-
detail: { type: 'spot', got: 5 },
|
|
1454
|
-
});
|
|
1455
|
-
});
|
|
1456
|
-
|
|
1457
|
-
it('no-op when spotCount <= 4', () => {
|
|
1458
|
-
const fs = makeFrameState();
|
|
1459
|
-
warnMultiLightSpot(fs, 4);
|
|
1460
|
-
warnMultiLightSpot(fs, 0);
|
|
1461
|
-
expect(warnSpy).toHaveBeenCalledTimes(0);
|
|
1462
|
-
expect(fs.warnedMultiLightSpot).toBe(false);
|
|
1463
|
-
});
|
|
1464
|
-
|
|
1465
|
-
it('warn-once across 6 frames', () => {
|
|
1466
|
-
const fs = makeFrameState();
|
|
1467
|
-
warnMultiLightSpot(fs, 5);
|
|
1468
|
-
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
1469
|
-
for (let i = 0; i < 5; i++) {
|
|
1470
|
-
warnMultiLightSpot(fs, 5);
|
|
1471
|
-
}
|
|
1472
|
-
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
1473
|
-
});
|
|
1474
|
-
|
|
1475
|
-
it('production gate suppresses output but flips latch', () => {
|
|
1476
|
-
const fs = makeFrameState();
|
|
1477
|
-
warnMultiLightSpot(fs, 5, { env: { NODE_ENV: 'production' } });
|
|
1478
|
-
expect(warnSpy).toHaveBeenCalledTimes(0);
|
|
1479
|
-
expect(fs.warnedMultiLightSpot).toBe(true);
|
|
1480
|
-
});
|
|
1481
|
-
});
|
|
1482
|
-
|
|
1483
|
-
describe('independent latch isolation (M3 / AC-04 / user Step 5.a)', () => {
|
|
1484
|
-
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
1485
|
-
|
|
1486
|
-
beforeEach(() => {
|
|
1487
|
-
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1488
|
-
});
|
|
1489
|
-
|
|
1490
|
-
afterEach(() => {
|
|
1491
|
-
warnSpy.mockRestore();
|
|
1492
|
-
});
|
|
1493
|
-
|
|
1494
|
-
it('firing directional does not silence point', () => {
|
|
1495
|
-
const fs = makeFrameState();
|
|
1496
|
-
warnMultiLightDirectional(fs, 2);
|
|
1497
|
-
expect(fs.warnedMultiLightDirectional).toBe(true);
|
|
1498
|
-
expect(fs.warnedMultiLightPoint).toBe(false);
|
|
1499
|
-
expect(fs.warnedMultiLightSpot).toBe(false);
|
|
1500
|
-
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
1501
|
-
warnMultiLightPoint(fs, 5);
|
|
1502
|
-
expect(warnSpy).toHaveBeenCalledTimes(2);
|
|
1503
|
-
expect(fs.warnedMultiLightPoint).toBe(true);
|
|
1504
|
-
});
|
|
1505
|
-
|
|
1506
|
-
it('firing point does not silence spot', () => {
|
|
1507
|
-
const fs = makeFrameState();
|
|
1508
|
-
warnMultiLightPoint(fs, 5);
|
|
1509
|
-
expect(fs.warnedMultiLightPoint).toBe(true);
|
|
1510
|
-
expect(fs.warnedMultiLightSpot).toBe(false);
|
|
1511
|
-
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
1512
|
-
warnMultiLightSpot(fs, 5);
|
|
1513
|
-
expect(warnSpy).toHaveBeenCalledTimes(2);
|
|
1514
|
-
expect(fs.warnedMultiLightSpot).toBe(true);
|
|
1515
|
-
});
|
|
1516
|
-
|
|
1517
|
-
it('pre-set directional latch does not prevent point/spot first fire', () => {
|
|
1518
|
-
const fs = makeFrameState();
|
|
1519
|
-
fs.warnedMultiLightDirectional = true;
|
|
1520
|
-
warnMultiLightPoint(fs, 5);
|
|
1521
|
-
warnMultiLightSpot(fs, 5);
|
|
1522
|
-
expect(warnSpy).toHaveBeenCalledTimes(2);
|
|
1523
|
-
expect(fs.warnedMultiLightPoint).toBe(true);
|
|
1524
|
-
expect(fs.warnedMultiLightSpot).toBe(true);
|
|
1525
|
-
});
|
|
1526
|
-
});
|
|
1527
1386
|
}
|
|
1528
1387
|
|
|
1529
1388
|
{
|
|
@@ -1694,6 +1553,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
1694
1553
|
|
|
1695
1554
|
async function importComponents(): Promise<{
|
|
1696
1555
|
Transform: unknown;
|
|
1556
|
+
GlobalTransform: unknown;
|
|
1697
1557
|
MeshFilter: unknown;
|
|
1698
1558
|
MeshRenderer: unknown;
|
|
1699
1559
|
Camera: unknown;
|
|
@@ -1920,10 +1780,10 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
1920
1780
|
return 'mesh-ssbo-capacity';
|
|
1921
1781
|
case 'mesh-ssbo-ceiling-reached':
|
|
1922
1782
|
return 'mesh-ssbo-ceiling';
|
|
1923
|
-
case '
|
|
1924
|
-
return '
|
|
1925
|
-
case '
|
|
1926
|
-
return '
|
|
1783
|
+
case 'standard-light-budget-exceeded':
|
|
1784
|
+
return 'standard-budget';
|
|
1785
|
+
case 'standard-cluster-index-overflow':
|
|
1786
|
+
return 'standard-overflow';
|
|
1927
1787
|
}
|
|
1928
1788
|
}
|
|
1929
1789
|
expect(exhaustive('skin-joint-count-exceeded')).toBe('joint-count');
|
|
@@ -2761,8 +2621,10 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
2761
2621
|
const result = buildPbrPipelineLayouts(device as any, STORAGE_CAPS);
|
|
2762
2622
|
expect(result.bindGroupLayouts).toHaveLength(4);
|
|
2763
2623
|
// And the captured pipeline layout descriptor matches.
|
|
2764
|
-
expect(device.capturedPipelineLayouts).toHaveLength(
|
|
2765
|
-
const layout = device.capturedPipelineLayouts
|
|
2624
|
+
expect(device.capturedPipelineLayouts).toHaveLength(2);
|
|
2625
|
+
const layout = device.capturedPipelineLayouts.find(
|
|
2626
|
+
(candidate) => candidate.label === 'pbr-pl',
|
|
2627
|
+
);
|
|
2766
2628
|
expect(layout?.bindGroupLayouts).toHaveLength(4);
|
|
2767
2629
|
expect(layout?.label).toBe('pbr-pl');
|
|
2768
2630
|
});
|
|
@@ -3186,6 +3048,8 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
3186
3048
|
directionalCount: 0,
|
|
3187
3049
|
point: [],
|
|
3188
3050
|
spot: [],
|
|
3051
|
+
rect: [],
|
|
3052
|
+
directionalShadowError: undefined,
|
|
3189
3053
|
lightSpaceMatrix: undefined,
|
|
3190
3054
|
shadowMapSize: undefined,
|
|
3191
3055
|
pointShadow: [],
|
|
@@ -3368,11 +3232,12 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
3368
3232
|
}
|
|
3369
3233
|
|
|
3370
3234
|
function makePipelineState(
|
|
3371
|
-
internals: { _fakes: Record<string, unknown
|
|
3235
|
+
internals: { _fakes: Record<string, unknown>; device?: unknown },
|
|
3372
3236
|
hdrAlreadyAllocated: boolean,
|
|
3373
3237
|
): unknown {
|
|
3374
3238
|
const f = internals._fakes;
|
|
3375
3239
|
return {
|
|
3240
|
+
device: internals.device,
|
|
3376
3241
|
meshes: new Map(),
|
|
3377
3242
|
format: 'bgra8unorm',
|
|
3378
3243
|
colorAttachmentFormat: 'bgra8unorm-srgb',
|
|
@@ -3461,14 +3326,13 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
3461
3326
|
temporalFrameInput: undefined,
|
|
3462
3327
|
compiledFrameGraph: null,
|
|
3463
3328
|
compiledFrameGraphTopologyKey: null,
|
|
3329
|
+
compiledFrameGraphGeneration: 0,
|
|
3464
3330
|
retiredCompiledFrameGraphs: new Set(),
|
|
3465
3331
|
instanceBuffers: new Map(),
|
|
3466
3332
|
transientInstanceBuffers: [],
|
|
3467
3333
|
warnedZeroLightStandard: false,
|
|
3468
3334
|
warnedShadowDisabled: false,
|
|
3469
3335
|
warnedMultiLightDirectional: false,
|
|
3470
|
-
warnedMultiLightPoint: false,
|
|
3471
|
-
warnedMultiLightSpot: false,
|
|
3472
3336
|
warnedSkyboxTonemapNone: false,
|
|
3473
3337
|
warnedMissingBaseColorTextureHandles: new Set<number>(),
|
|
3474
3338
|
warnedNineSliceScaleEntities: new Set<number>(),
|
|
@@ -3481,8 +3345,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
3481
3345
|
installedPipelineHandle: 0,
|
|
3482
3346
|
activePipeline: urpPipeline,
|
|
3483
3347
|
installedPipelineConfig: undefined,
|
|
3484
|
-
|
|
3485
|
-
hdrpOncePerFrameFired: new Set(),
|
|
3348
|
+
standardOncePerFrameFired: new Set(),
|
|
3486
3349
|
},
|
|
3487
3350
|
{ unlit: 0 },
|
|
3488
3351
|
{ createBindGroup: 0, keys: [] },
|
|
@@ -3582,13 +3445,12 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
3582
3445
|
temporalFrameInput: undefined,
|
|
3583
3446
|
compiledFrameGraph: null,
|
|
3584
3447
|
compiledFrameGraphTopologyKey: null,
|
|
3448
|
+
compiledFrameGraphGeneration: 0,
|
|
3585
3449
|
retiredCompiledFrameGraphs: new Set(),
|
|
3586
3450
|
instanceBuffers,
|
|
3587
3451
|
warnedZeroLightStandard: false,
|
|
3588
3452
|
warnedShadowDisabled: false,
|
|
3589
3453
|
warnedMultiLightDirectional: false,
|
|
3590
|
-
warnedMultiLightPoint: false,
|
|
3591
|
-
warnedMultiLightSpot: false,
|
|
3592
3454
|
warnedSkyboxTonemapNone: false,
|
|
3593
3455
|
warnedMissingBaseColorTextureHandles: new Set<number>(),
|
|
3594
3456
|
warnedNineSliceScaleEntities: new Set<number>(),
|
|
@@ -3601,8 +3463,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
3601
3463
|
installedPipelineHandle: 0,
|
|
3602
3464
|
activePipeline: urpPipeline,
|
|
3603
3465
|
installedPipelineConfig: undefined,
|
|
3604
|
-
|
|
3605
|
-
hdrpOncePerFrameFired: new Set(),
|
|
3466
|
+
standardOncePerFrameFired: new Set(),
|
|
3606
3467
|
},
|
|
3607
3468
|
{ unlit: 0 },
|
|
3608
3469
|
{ createBindGroup: 0, keys: [] },
|
|
@@ -3657,13 +3518,12 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
3657
3518
|
temporalFrameInput: undefined,
|
|
3658
3519
|
compiledFrameGraph: null,
|
|
3659
3520
|
compiledFrameGraphTopologyKey: null,
|
|
3521
|
+
compiledFrameGraphGeneration: 0,
|
|
3660
3522
|
retiredCompiledFrameGraphs: new Set(),
|
|
3661
3523
|
instanceBuffers,
|
|
3662
3524
|
warnedZeroLightStandard: false,
|
|
3663
3525
|
warnedShadowDisabled: false,
|
|
3664
3526
|
warnedMultiLightDirectional: false,
|
|
3665
|
-
warnedMultiLightPoint: false,
|
|
3666
|
-
warnedMultiLightSpot: false,
|
|
3667
3527
|
warnedSkyboxTonemapNone: false,
|
|
3668
3528
|
warnedMissingBaseColorTextureHandles: new Set<number>(),
|
|
3669
3529
|
warnedNineSliceScaleEntities: new Set<number>(),
|
|
@@ -3676,8 +3536,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
3676
3536
|
installedPipelineHandle: 0,
|
|
3677
3537
|
activePipeline: urpPipeline,
|
|
3678
3538
|
installedPipelineConfig: undefined,
|
|
3679
|
-
|
|
3680
|
-
hdrpOncePerFrameFired: new Set(),
|
|
3539
|
+
standardOncePerFrameFired: new Set(),
|
|
3681
3540
|
},
|
|
3682
3541
|
{ unlit: 0 },
|
|
3683
3542
|
{ createBindGroup: 0, keys: [] },
|
|
@@ -5137,7 +4996,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
5137
4996
|
}
|
|
5138
4997
|
|
|
5139
4998
|
function readWorld(world: World, entity: EntityHandle): Float32Array {
|
|
5140
|
-
return world.get(entity,
|
|
4999
|
+
return world.get(entity, GlobalTransform).unwrap().world as Float32Array;
|
|
5141
5000
|
}
|
|
5142
5001
|
|
|
5143
5002
|
function expectMatClose(actual: Float32Array, expected: Float32Array): void {
|
|
@@ -5146,7 +5005,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
5146
5005
|
}
|
|
5147
5006
|
}
|
|
5148
5007
|
|
|
5149
|
-
describe('w7 - propagate writes
|
|
5008
|
+
describe('w7 - propagate writes GlobalTransform.world mat4 (AC-05 / AC-06)', () => {
|
|
5150
5009
|
it('root: world == compose(local.TRS) element-wise epsilon<=1e-5 (AC-06)', () => {
|
|
5151
5010
|
const world = new World();
|
|
5152
5011
|
const local = trs({ pos: [3, 4, 5], scale: [2, 2, 2] });
|
|
@@ -5539,17 +5398,17 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
5539
5398
|
});
|
|
5540
5399
|
|
|
5541
5400
|
// feat-20260601 w12/w13: skin joint world matrices now flow from the resolved
|
|
5542
|
-
// `
|
|
5401
|
+
// `GlobalTransform.world` column array view (a raw 16-float `Float32Array`, written
|
|
5543
5402
|
// by propagateTransforms) straight into the allocator's `readonly Mat4[]`
|
|
5544
5403
|
// jointWorlds parameter -- zero recompose from decomposed TRS. The allocator
|
|
5545
|
-
// contract is unchanged; this guards that a bare
|
|
5404
|
+
// contract is unchanged; this guards that a bare GlobalTransform.world-shaped
|
|
5546
5405
|
// Float32Array premultiplies against the IBM exactly like a `mat4.create()`.
|
|
5547
|
-
it('accepts a
|
|
5406
|
+
it('accepts a GlobalTransform.world-shaped Float32Array as a joint world matrix', () => {
|
|
5548
5407
|
const { device, written } = mockDevice();
|
|
5549
5408
|
const alloc = createSkinPaletteAllocator(device, MAX_BINDING);
|
|
5550
|
-
// A
|
|
5409
|
+
// A GlobalTransform.world view is a plain 16-float column-major Float32Array.
|
|
5551
5410
|
// Here: translation (0, 3, 0) -- the shape a `world._getArrayView(joint,
|
|
5552
|
-
//
|
|
5411
|
+
// GlobalTransform, 'world')` read returns after propagate.
|
|
5553
5412
|
const jointWorldView = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 3, 0, 1]);
|
|
5554
5413
|
const ibm = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
|
|
5555
5414
|
const slice = alloc.allocateSlice(1);
|
|
@@ -7370,6 +7229,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
7370
7229
|
|
|
7371
7230
|
async function importComponents(): Promise<{
|
|
7372
7231
|
Transform: unknown;
|
|
7232
|
+
GlobalTransform: unknown;
|
|
7373
7233
|
MeshFilter: unknown;
|
|
7374
7234
|
MeshRenderer: unknown;
|
|
7375
7235
|
Camera: unknown;
|
|
@@ -7687,6 +7547,7 @@ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
|
7687
7547
|
|
|
7688
7548
|
async function importComponents(): Promise<{
|
|
7689
7549
|
Transform: unknown;
|
|
7550
|
+
GlobalTransform: unknown;
|
|
7690
7551
|
MeshFilter: unknown;
|
|
7691
7552
|
MeshRenderer: unknown;
|
|
7692
7553
|
Camera: unknown;
|
|
@@ -8336,10 +8197,10 @@ type ExtractFramesWithPipeline = (
|
|
|
8336
8197
|
}
|
|
8337
8198
|
|
|
8338
8199
|
// ── feat-20260612-skin-palette-per-frame-upload M3 / m3-1: record-stage ──
|
|
8339
|
-
// dynOffset[1] truth-value + skin BG cache miss=1 + hit=N-1 stats counter.
|
|
8200
|
+
// dynOffset[1]/[2] truth-value + skin BG cache miss=1 + hit=N-1 stats counter.
|
|
8340
8201
|
//
|
|
8341
8202
|
// Why this block exists:
|
|
8342
|
-
// M3 lifts the record-stage `group2DynamicOffsets`
|
|
8203
|
+
// M3 lifts the record-stage `group2DynamicOffsets` palette slots from a
|
|
8343
8204
|
// hard-coded `0` (PR #353 stub) to the per-entity `entry.source.skin
|
|
8344
8205
|
// .byteOffset` cursor that M2 m2-6 wired into the extract output. The
|
|
8345
8206
|
// acceptanceCheck calls for the literal text `group2DynamicOffsets[1]
|
|
@@ -8351,8 +8212,8 @@ type ExtractFramesWithPipeline = (
|
|
|
8351
8212
|
// What the assertions cover:
|
|
8352
8213
|
// (a) The test-local offset oracle mirrors the production main-pass
|
|
8353
8214
|
// geometry contract. With skinByteOffset !== undefined the returned
|
|
8354
|
-
// tuple's [1]
|
|
8355
|
-
//
|
|
8215
|
+
// tuple's [1] and [2] slots equal the supplied byteOffset (the
|
|
8216
|
+
// temporal contract); with skinByteOffset ===
|
|
8356
8217
|
// undefined it returns a length-1 tuple (non-skin paths preserved).
|
|
8357
8218
|
// (b) N=3 sequential lookups
|
|
8358
8219
|
// against the same `(meshStorageBuffer, skinPaletteAllocator
|
|
@@ -8379,7 +8240,9 @@ type ExtractFramesWithPipeline = (
|
|
|
8379
8240
|
skinByteOffset: number | undefined,
|
|
8380
8241
|
): readonly number[] => {
|
|
8381
8242
|
const meshOffset = meshSlotIdx * MESH_PER_ENTITY_STRIDE;
|
|
8382
|
-
return skinByteOffset === undefined
|
|
8243
|
+
return skinByteOffset === undefined
|
|
8244
|
+
? [meshOffset]
|
|
8245
|
+
: [meshOffset, skinByteOffset, skinByteOffset];
|
|
8383
8246
|
};
|
|
8384
8247
|
|
|
8385
8248
|
// Skin path: second slot must equal the per-entity byteOffset cursor
|
|
@@ -8388,12 +8251,12 @@ type ExtractFramesWithPipeline = (
|
|
|
8388
8251
|
// (first allocateSlice), cursor=256 (second 1-joint slice), cursor
|
|
8389
8252
|
// =16320 (the worst-case 255-joint slice end).
|
|
8390
8253
|
const skinAt0 = computeSkinGroup2DynOffsets(0, 0);
|
|
8391
|
-
expect(skinAt0).toHaveLength(
|
|
8254
|
+
expect(skinAt0).toHaveLength(3);
|
|
8392
8255
|
// group2DynamicOffsets[1] === byteOffset (literal assertion text per acceptanceCheck)
|
|
8393
8256
|
expect(skinAt0[1]).toBe(0);
|
|
8394
8257
|
|
|
8395
8258
|
const skinAt256 = computeSkinGroup2DynOffsets(1, 256);
|
|
8396
|
-
expect(skinAt256).toHaveLength(
|
|
8259
|
+
expect(skinAt256).toHaveLength(3);
|
|
8397
8260
|
// group2DynamicOffsets[1] === byteOffset
|
|
8398
8261
|
expect(skinAt256[1]).toBe(256);
|
|
8399
8262
|
// mesh slot continues to follow the 256-byte stride: meshSlotIdx * 256.
|
|
@@ -8401,7 +8264,7 @@ type ExtractFramesWithPipeline = (
|
|
|
8401
8264
|
|
|
8402
8265
|
const byteOffsetWorst = 254 * 256;
|
|
8403
8266
|
const skinAtEnd = computeSkinGroup2DynOffsets(2, byteOffsetWorst);
|
|
8404
|
-
expect(skinAtEnd).toHaveLength(
|
|
8267
|
+
expect(skinAtEnd).toHaveLength(3);
|
|
8405
8268
|
// group2DynamicOffsets[1] === byteOffset
|
|
8406
8269
|
expect(skinAtEnd[1]).toBe(byteOffsetWorst);
|
|
8407
8270
|
expect(skinAtEnd[0]).toBe(2 * 256);
|
|
@@ -8764,6 +8627,7 @@ type ExtractFramesWithPipeline = (
|
|
|
8764
8627
|
|
|
8765
8628
|
interface IntegrationComponents {
|
|
8766
8629
|
Transform: unknown;
|
|
8630
|
+
GlobalTransform: unknown;
|
|
8767
8631
|
MeshFilter: unknown;
|
|
8768
8632
|
MeshRenderer: unknown;
|
|
8769
8633
|
Camera: unknown;
|