@forgeax/engine-runtime 0.1.19 → 0.1.21

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.
Files changed (91) hide show
  1. package/README.md +102 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/create-renderer-gpu-pass-timing.unit.test.d.ts +2 -0
  4. package/dist/__tests__/create-renderer-gpu-pass-timing.unit.test.d.ts.map +1 -0
  5. package/dist/__tests__/helpers/tilemap-assets.d.ts.map +1 -1
  6. package/dist/__tests__/lights-preservation.fixup.test.d.ts +2 -0
  7. package/dist/__tests__/lights-preservation.fixup.test.d.ts.map +1 -0
  8. package/dist/__tests__/material-texture-dimension-mismatch.unit.test.d.ts +2 -0
  9. package/dist/__tests__/material-texture-dimension-mismatch.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
  11. package/dist/__tests__/texture-dimensions.browser.test.d.ts +2 -0
  12. package/dist/__tests__/texture-dimensions.browser.test.d.ts.map +1 -0
  13. package/dist/__tests__/volumetric-fog-record-recovery.integration.test.d.ts +2 -0
  14. package/dist/__tests__/volumetric-fog-record-recovery.integration.test.d.ts.map +1 -0
  15. package/dist/createRenderer.d.ts.map +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.mjs +3 -1
  18. package/dist/index.mjs.map +1 -1
  19. package/package.json +30 -30
  20. package/src/__tests__/active-camera.unit.test.ts +6 -8
  21. package/src/__tests__/asset-registry.recursive.spec.ts +13 -19
  22. package/src/__tests__/asset.unit.test.ts +28 -25
  23. package/src/__tests__/basis-catalog-dispatch.integration.test.ts +10 -9
  24. package/src/__tests__/clamp-to-last.e2e.browser.test.ts +2 -2
  25. package/src/__tests__/components.test-d.ts +10 -3
  26. package/src/__tests__/components.unit.test.ts +13 -7
  27. package/src/__tests__/compressed-texture-physical-extent.integration.test.ts +12 -10
  28. package/src/__tests__/create-renderer-assembly.integration.test.ts +24 -0
  29. package/src/__tests__/create-renderer-gpu-pass-timing.unit.test.ts +32 -0
  30. package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +66 -0
  31. package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +11 -4
  32. package/src/__tests__/dawn/instances-shadow.dawn.test.ts +15 -2
  33. package/src/__tests__/dawn/material-alpha.dawn.test.ts +2 -3
  34. package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +2 -3
  35. package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +2 -3
  36. package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +2 -3
  37. package/src/__tests__/dawn/upload-texture.dawn.test.ts +4 -6
  38. package/src/__tests__/derive-bgl-integration.test.ts +11 -0
  39. package/src/__tests__/equirect-bc6h.integration.test.ts +6 -4
  40. package/src/__tests__/errors.unit.test.ts +71 -20
  41. package/src/__tests__/extract-frames-merge.test.ts +48 -40
  42. package/src/__tests__/extract-skin-mismatch.unit.test.ts +6 -2
  43. package/src/__tests__/four-path-compression.integration.test.ts +2 -2
  44. package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +13 -0
  45. package/src/__tests__/geometry.unit.test.ts +7 -10
  46. package/src/__tests__/gpu-resource-cow-survivor.test.ts +2 -3
  47. package/src/__tests__/helpers/tilemap-assets.ts +3 -4
  48. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +6 -3
  49. package/src/__tests__/light-snapshot.test-d.ts +8 -0
  50. package/src/__tests__/lights-preservation.fixup.test.ts +127 -0
  51. package/src/__tests__/lights.unit.test.ts +75 -2214
  52. package/src/__tests__/loadbyguid-texture-intern.unit.test.ts +3 -4
  53. package/src/__tests__/loader-name-survival.test.ts +2 -3
  54. package/src/__tests__/material-texture-dimension-mismatch.unit.test.ts +18 -0
  55. package/src/__tests__/material-texture-uv-scale.unit.test.ts +10 -2
  56. package/src/__tests__/mip-gate.unit.test.ts +11 -8
  57. package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +2 -3
  58. package/src/__tests__/new-kind-refs-contract.test.ts +2 -3
  59. package/src/__tests__/pbr-pipeline.unit.test.ts +29 -5
  60. package/src/__tests__/pipeline.unit.test.ts +66 -221
  61. package/src/__tests__/point-light-shadow.browser.test.ts +208 -83
  62. package/src/__tests__/point-light-shadow.unit.test.ts +67 -9
  63. package/src/__tests__/render-error-exhaustive.test-d.ts +92 -9
  64. package/src/__tests__/render-system-extract.test.ts +4 -6
  65. package/src/__tests__/render-system-mega.test.ts +30 -41
  66. package/src/__tests__/render-system-record-multi-material-textureview.test.ts +8 -7
  67. package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +6 -4
  68. package/src/__tests__/render-system-skin-bg.unit.test.ts +14 -9
  69. package/src/__tests__/renderer-recover.unit.test.ts +8 -0
  70. package/src/__tests__/renderer-surface.unit.test.ts +229 -3
  71. package/src/__tests__/shadow-csm-atlas.test.ts +41 -0
  72. package/src/__tests__/shadow-csm-component.test.ts +31 -24
  73. package/src/__tests__/shadow-csm-extract.test.ts +156 -47
  74. package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +72 -0
  75. package/src/__tests__/shadow-csm-shader.dawn.test.ts +19 -0
  76. package/src/__tests__/shadow-csm-ubo.test.ts +41 -45
  77. package/src/__tests__/shadow-csm-viewport.browser.test.ts +29 -0
  78. package/src/__tests__/shadow-fields-observable.dawn.test.ts +140 -58
  79. package/src/__tests__/skinned-shadow-mixed.dawn.test.ts +6 -2
  80. package/src/__tests__/skylight-lazy-projection.test.ts +8 -5
  81. package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +4 -6
  82. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +4 -2
  83. package/src/__tests__/ssao-bgl.test.ts +10 -16
  84. package/src/__tests__/ssao-passes.test.ts +19 -2
  85. package/src/__tests__/systems.unit.test.ts +59 -33
  86. package/src/__tests__/texture-dimensions.browser.test.ts +127 -0
  87. package/src/__tests__/transcode-fallback.unit.test.ts +5 -1
  88. package/src/__tests__/view-ubo-layout.browser.test.ts +20 -10
  89. package/src/__tests__/volumetric-fog-record-recovery.integration.test.ts +180 -0
  90. package/src/createRenderer.ts +4 -0
  91. package/src/index.ts +1 -2
@@ -144,12 +144,11 @@ function configuredStore(probe: CowProbe): GpuResidencyCache {
144
144
  function texturePod(w = 2, h = 2): TextureAsset {
145
145
  return {
146
146
  kind: 'texture',
147
- width: w,
148
- height: h,
147
+ shape: { viewDimension: '2d', extent: { width: w, height: h } },
149
148
  format: 'rgba8unorm-srgb',
150
149
  data: new Uint8Array(w * h * 4).fill(188),
151
150
  colorSpace: 'srgb',
152
- mipmap: false,
151
+ mips: { kind: 'none' },
153
152
  };
154
153
  }
155
154
 
@@ -6,12 +6,11 @@ export const TEST_ATLAS_GUID = 'test/atlas';
6
6
  export function makeTestTexture(seed = 0): TextureAsset {
7
7
  return {
8
8
  kind: 'texture',
9
- width: 32,
10
- height: 32,
11
- format: 'rgba8unorm',
9
+ shape: { viewDimension: '2d', extent: { width: 32, height: 32 } },
10
+ format: 'rgba8unorm-srgb',
12
11
  data: new Uint8Array(32 * 32 * 4).fill(seed),
13
12
  colorSpace: 'srgb',
14
- mipmap: false,
13
+ mips: { kind: 'none' },
15
14
  };
16
15
  }
17
16
 
@@ -57,13 +57,14 @@ const emptyLights: ExtractedLights = {
57
57
  directionalCount: 0,
58
58
  point: [],
59
59
  spot: [],
60
+ directionalShadowError: undefined,
60
61
  lightViewProj: undefined,
61
62
  splitPlanes: undefined,
62
63
  cascadeCount: undefined,
63
64
  cascadeBlend: undefined,
64
65
  depthBias: undefined,
65
66
  normalBias: undefined,
66
- pcfKernelSize: undefined,
67
+ directionalShadowQuality: undefined,
67
68
  } as unknown as ExtractedLights;
68
69
 
69
70
  /** Capture the single writeViewUbo payload as a Float32Array. */
@@ -117,7 +118,7 @@ describe('w7 -- light GPU pack byte-neutral (AC-11)', () => {
117
118
  expect(out[6]).toBeCloseTo(0.6, 6);
118
119
  });
119
120
 
120
- it('packSpotLight writes color slots 4..6 + direction slots 8..10 byte-for-byte (64 B)', () => {
121
+ it('packSpotLight writes color slots 4..6 + direction slots 8..10 byte-for-byte (80 B)', () => {
121
122
  const snap: SpotLightSnapshot = {
122
123
  kind: 'spot',
123
124
  position: vec3.create(0, 5, 0),
@@ -135,7 +136,9 @@ describe('w7 -- light GPU pack byte-neutral (AC-11)', () => {
135
136
  shadowAtlasTile: -1,
136
137
  };
137
138
  const out = packSpotLight(snap);
138
- expect(out.byteLength).toBe(64);
139
+ // SpotLight carries projector receiver controls and the shadow intensity
140
+ // tail in the current std430 payload.
141
+ expect(out.byteLength).toBe(80);
139
142
  expect(out[4]).toBeCloseTo(0.3, 6);
140
143
  expect(out[5]).toBeCloseTo(0.4, 6);
141
144
  expect(out[6]).toBeCloseTo(0.5, 6);
@@ -21,6 +21,8 @@
21
21
  // covers any .test-d.ts file in any package).
22
22
 
23
23
  import { expectTypeOf, test } from 'vitest';
24
+ import type { DirectionalShadowQuality } from '../../../render/src/components/directional-shadow-filter';
25
+ import type { LightValidationError } from '../../../render/src/components/light-helpers';
24
26
  import type {
25
27
  DirectionalLightSnapshot,
26
28
  ExtractedLights,
@@ -81,4 +83,10 @@ test('ExtractedLights three-bucket shape', () => {
81
83
  expectTypeOf<ExtractedLights['directionalCount']>().toEqualTypeOf<number>();
82
84
  expectTypeOf<ExtractedLights['point']>().toEqualTypeOf<readonly PointLightSnapshot[]>();
83
85
  expectTypeOf<ExtractedLights['spot']>().toEqualTypeOf<readonly SpotLightSnapshot[]>();
86
+ expectTypeOf<ExtractedLights['directionalShadowQuality']>().toEqualTypeOf<
87
+ DirectionalShadowQuality | undefined
88
+ >();
89
+ expectTypeOf<ExtractedLights['directionalShadowError']>().toEqualTypeOf<
90
+ LightValidationError | undefined
91
+ >();
84
92
  });
@@ -0,0 +1,127 @@
1
+ import { World } from '@forgeax/engine-ecs';
2
+ import { vec3 } from '@forgeax/engine-math';
3
+ import { Camera, PointLight, SpotLight } from '@forgeax/engine-render';
4
+ import { Transform } from '@forgeax/engine-scene';
5
+ import { describe, expect, it } from 'vitest';
6
+ import {
7
+ computeInvRangeSquared,
8
+ degToCos,
9
+ validatePointLightShadowData,
10
+ validateSpotLightData,
11
+ } from '../../../render/src/components/light-helpers';
12
+ import {
13
+ packLightSlot,
14
+ packPointLight,
15
+ packSpotLight,
16
+ } from '../../../render/src/light-buffer-layout';
17
+ import { buildPbrViewBglEntries } from '../../../render/src/pbr-pipeline';
18
+ import type {
19
+ PointLightSnapshot,
20
+ SpotLightSnapshot,
21
+ } from '../../../render/src/render-system-extract';
22
+ import { extractFrame, prepareExtractContext } from '../../../render/src/render-system-extract';
23
+
24
+ function pointSnapshot(): PointLightSnapshot {
25
+ return {
26
+ kind: 'point',
27
+ position: vec3.create(1, 2, 3),
28
+ color: vec3.create(2, 1, 0.5),
29
+ intensity: 2,
30
+ invRangeSquared: 0.25,
31
+ };
32
+ }
33
+
34
+ function spotSnapshot(): SpotLightSnapshot {
35
+ return {
36
+ kind: 'spot',
37
+ position: vec3.create(1, 2, 3),
38
+ direction: vec3.create(0, -1, 0),
39
+ color: vec3.create(2, 1, 0.5),
40
+ intensity: 2,
41
+ invRangeSquared: 0.25,
42
+ cosInner: degToCos(15),
43
+ cosOuter: degToCos(30),
44
+ castShadow: false,
45
+ lightViewProj: undefined,
46
+ mapSize: 512,
47
+ nearPlane: 0.1,
48
+ farPlane: 25,
49
+ shadowAtlasTile: -1,
50
+ };
51
+ }
52
+
53
+ describe('Point/Spot and shared-light regression guard', () => {
54
+ it('keeps extract facts for point range and spot cone in the shared light path', () => {
55
+ const world = new World();
56
+ const pointSpawn = world.spawn(
57
+ { component: Transform, data: { pos: [1, 2, 3] } },
58
+ { component: PointLight, data: { range: 2 } },
59
+ );
60
+ const spotSpawn = world.spawn(
61
+ { component: Transform, data: { pos: [4, 5, 6] } },
62
+ {
63
+ component: SpotLight,
64
+ data: { direction: [0, -1, 0], range: 4, innerConeDeg: 15, outerConeDeg: 30 },
65
+ },
66
+ );
67
+ world.spawn(
68
+ { component: Transform, data: {} },
69
+ { component: Camera, data: { fov: Math.PI / 4, aspect: 1, near: 0.1, far: 100 } },
70
+ );
71
+ expect(pointSpawn.ok, JSON.stringify(pointSpawn)).toBe(true);
72
+ expect(spotSpawn.ok, JSON.stringify(spotSpawn)).toBe(true);
73
+
74
+ const lights = extractFrame(world, prepareExtractContext(world)).lights;
75
+ expect(lights.point).toHaveLength(1);
76
+ expect(lights.point[0]?.invRangeSquared).toBeCloseTo(0.25, 6);
77
+ expect(lights.spot).toHaveLength(1);
78
+ expect(lights.spot[0]?.cosInner).toBeCloseTo(degToCos(15), 6);
79
+ expect(lights.spot[0]?.cosOuter).toBeCloseTo(degToCos(30), 6);
80
+ });
81
+
82
+ it('keeps attenuation and cone helpers as finite, shared semantic owners', () => {
83
+ expect(computeInvRangeSquared(2)).toBeCloseTo(0.25, 8);
84
+ expect(computeInvRangeSquared(0)).toBe(1e8);
85
+ expect(computeInvRangeSquared(Number.POSITIVE_INFINITY)).toBe(0);
86
+ expect(degToCos(0)).toBeCloseTo(1, 8);
87
+ expect(degToCos(60)).toBeCloseTo(0.5, 8);
88
+ });
89
+
90
+ it('keeps Point/Spot packing and the shared LightSlot discriminant intact', () => {
91
+ const point = pointSnapshot();
92
+ const spot = spotSnapshot();
93
+ const pointPacked = packPointLight(point);
94
+ const spotPacked = packSpotLight(spot);
95
+ const pointSlot = packLightSlot(point, { shadowAtlasLayer: 2, near: 0.1, far: 20 });
96
+ const spotSlot = packLightSlot(spot);
97
+
98
+ expect(pointPacked.byteLength).toBe(32);
99
+ expect(spotPacked.byteLength).toBe(80);
100
+ expect(new Uint32Array(pointSlot.buffer)[12]).toBe(0);
101
+ expect(new Int32Array(pointSlot.buffer)[13]).toBe(2);
102
+ expect(new Uint32Array(spotSlot.buffer)[12]).toBe(1);
103
+ expect(new Int32Array(spotSlot.buffer)[13]).toBe(-1);
104
+ });
105
+
106
+ it('keeps shared PBR view bindings for both storage and uniform light routes', () => {
107
+ const storageBindings = buildPbrViewBglEntries({ storageBuffer: true });
108
+ const uniformBindings = buildPbrViewBglEntries({ storageBuffer: false });
109
+ expect(storageBindings.map((entry) => entry.binding)).toEqual(
110
+ uniformBindings.map((entry) => entry.binding),
111
+ );
112
+ expect(storageBindings[1]?.buffer?.type).toBe('read-only-storage');
113
+ expect(storageBindings[2]?.buffer?.type).toBe('read-only-storage');
114
+ expect(uniformBindings[1]?.buffer?.type).toBe('uniform');
115
+ expect(uniformBindings[2]?.buffer?.type).toBe('uniform');
116
+ });
117
+
118
+ it('keeps Point/Spot pcfKernelSize validation independent of Directional quality', () => {
119
+ expect(
120
+ validatePointLightShadowData({ mapSize: 512, nearPlane: 0.1, farPlane: 20, pcfKernelSize: 5 })
121
+ .ok,
122
+ ).toBe(true);
123
+ expect(validateSpotLightData({ direction: [0, -1, 0], pcfKernelSize: 3 }).ok).toBe(true);
124
+ expect(validatePointLightShadowData({ pcfKernelSize: 2 }).ok).toBe(false);
125
+ expect(validateSpotLightData({ direction: [0, -1, 0], pcfKernelSize: 2 }).ok).toBe(false);
126
+ });
127
+ });