@forgeax/engine-runtime 0.1.19 → 0.1.20

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 (54) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/__tests__/helpers/tilemap-assets.d.ts.map +1 -1
  3. package/dist/__tests__/material-texture-dimension-mismatch.unit.test.d.ts +2 -0
  4. package/dist/__tests__/material-texture-dimension-mismatch.unit.test.d.ts.map +1 -0
  5. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
  6. package/dist/__tests__/texture-dimensions.browser.test.d.ts +2 -0
  7. package/dist/__tests__/texture-dimensions.browser.test.d.ts.map +1 -0
  8. package/dist/__tests__/volumetric-fog-record-recovery.integration.test.d.ts +2 -0
  9. package/dist/__tests__/volumetric-fog-record-recovery.integration.test.d.ts.map +1 -0
  10. package/dist/createRenderer.d.ts.map +1 -1
  11. package/dist/index.mjs +1 -0
  12. package/dist/index.mjs.map +1 -1
  13. package/package.json +30 -30
  14. package/src/__tests__/asset-registry.recursive.spec.ts +12 -18
  15. package/src/__tests__/asset.unit.test.ts +28 -25
  16. package/src/__tests__/basis-catalog-dispatch.integration.test.ts +10 -9
  17. package/src/__tests__/compressed-texture-physical-extent.integration.test.ts +12 -10
  18. package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +2 -3
  19. package/src/__tests__/dawn/material-alpha.dawn.test.ts +2 -3
  20. package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +2 -3
  21. package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +2 -3
  22. package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +2 -3
  23. package/src/__tests__/dawn/upload-texture.dawn.test.ts +4 -6
  24. package/src/__tests__/derive-bgl-integration.test.ts +11 -0
  25. package/src/__tests__/equirect-bc6h.integration.test.ts +6 -4
  26. package/src/__tests__/errors.unit.test.ts +20 -0
  27. package/src/__tests__/four-path-compression.integration.test.ts +2 -2
  28. package/src/__tests__/gpu-resource-cow-survivor.test.ts +2 -3
  29. package/src/__tests__/helpers/tilemap-assets.ts +3 -4
  30. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +4 -2
  31. package/src/__tests__/lights.unit.test.ts +38 -17
  32. package/src/__tests__/loadbyguid-texture-intern.unit.test.ts +3 -4
  33. package/src/__tests__/loader-name-survival.test.ts +2 -3
  34. package/src/__tests__/material-texture-dimension-mismatch.unit.test.ts +18 -0
  35. package/src/__tests__/material-texture-uv-scale.unit.test.ts +10 -2
  36. package/src/__tests__/mip-gate.unit.test.ts +11 -8
  37. package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +2 -3
  38. package/src/__tests__/new-kind-refs-contract.test.ts +2 -3
  39. package/src/__tests__/pbr-pipeline.unit.test.ts +23 -4
  40. package/src/__tests__/pipeline.unit.test.ts +18 -19
  41. package/src/__tests__/point-light-shadow.unit.test.ts +6 -5
  42. package/src/__tests__/render-error-exhaustive.test-d.ts +29 -0
  43. package/src/__tests__/render-system-extract.test.ts +4 -6
  44. package/src/__tests__/render-system-record-multi-material-textureview.test.ts +4 -6
  45. package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +2 -3
  46. package/src/__tests__/shadow-fields-observable.dawn.test.ts +56 -39
  47. package/src/__tests__/skinned-shadow-mixed.dawn.test.ts +3 -1
  48. package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +4 -6
  49. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +4 -2
  50. package/src/__tests__/systems.unit.test.ts +15 -2
  51. package/src/__tests__/texture-dimensions.browser.test.ts +127 -0
  52. package/src/__tests__/transcode-fallback.unit.test.ts +5 -1
  53. package/src/__tests__/volumetric-fog-record-recovery.integration.test.ts +180 -0
  54. package/src/createRenderer.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/engine-runtime",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -26,37 +26,37 @@
26
26
  "LICENSE"
27
27
  ],
28
28
  "dependencies": {
29
- "@forgeax/engine-animation": "0.1.19",
30
- "@forgeax/engine-assets-runtime": "0.1.19",
31
- "@forgeax/engine-audio-webaudio": "0.1.19",
32
- "@forgeax/engine-codec": "0.1.19",
33
- "@forgeax/engine-debug-draw": "0.1.19",
34
- "@forgeax/engine-ecs": "0.1.19",
35
- "@forgeax/engine-font": "0.1.19",
36
- "@forgeax/engine-geometry": "0.1.19",
37
- "@forgeax/engine-graphics-extras": "0.1.19",
38
- "@forgeax/engine-image": "0.1.19",
39
- "@forgeax/engine-input": "0.1.19",
40
- "@forgeax/engine-math": "0.1.19",
41
- "@forgeax/engine-pack": "0.1.19",
42
- "@forgeax/engine-profiler": "0.1.19",
43
- "@forgeax/engine-render": "0.1.19",
44
- "@forgeax/engine-render-graph": "0.1.19",
45
- "@forgeax/engine-rhi": "0.1.19",
46
- "@forgeax/engine-rhi-null": "0.1.19",
47
- "@forgeax/engine-rhi-webgpu": "0.1.19",
48
- "@forgeax/engine-rhi-wgpu": "0.1.19",
49
- "@forgeax/engine-scene": "0.1.19",
50
- "@forgeax/engine-shader": "0.1.19",
51
- "@forgeax/engine-skinning": "0.1.19",
52
- "@forgeax/engine-types": "0.1.19",
53
- "@forgeax/engine-vfx": "0.1.19"
29
+ "@forgeax/engine-animation": "0.1.20",
30
+ "@forgeax/engine-assets-runtime": "0.1.20",
31
+ "@forgeax/engine-audio-webaudio": "0.1.20",
32
+ "@forgeax/engine-codec": "0.1.20",
33
+ "@forgeax/engine-debug-draw": "0.1.20",
34
+ "@forgeax/engine-ecs": "0.1.20",
35
+ "@forgeax/engine-font": "0.1.20",
36
+ "@forgeax/engine-geometry": "0.1.20",
37
+ "@forgeax/engine-graphics-extras": "0.1.20",
38
+ "@forgeax/engine-image": "0.1.20",
39
+ "@forgeax/engine-input": "0.1.20",
40
+ "@forgeax/engine-math": "0.1.20",
41
+ "@forgeax/engine-pack": "0.1.20",
42
+ "@forgeax/engine-profiler": "0.1.20",
43
+ "@forgeax/engine-render": "0.1.20",
44
+ "@forgeax/engine-render-graph": "0.1.20",
45
+ "@forgeax/engine-rhi": "0.1.20",
46
+ "@forgeax/engine-rhi-null": "0.1.20",
47
+ "@forgeax/engine-rhi-webgpu": "0.1.20",
48
+ "@forgeax/engine-rhi-wgpu": "0.1.20",
49
+ "@forgeax/engine-scene": "0.1.20",
50
+ "@forgeax/engine-shader": "0.1.20",
51
+ "@forgeax/engine-skinning": "0.1.20",
52
+ "@forgeax/engine-types": "0.1.20",
53
+ "@forgeax/engine-vfx": "0.1.20"
54
54
  },
55
55
  "devDependencies": {
56
- "@forgeax/engine-import": "0.1.19",
57
- "@forgeax/engine-naga": "0.1.19",
58
- "@forgeax/engine-state": "0.1.19",
59
- "@forgeax/engine-vite-plugin-shader": "0.1.19",
56
+ "@forgeax/engine-import": "0.1.20",
57
+ "@forgeax/engine-naga": "0.1.20",
58
+ "@forgeax/engine-state": "0.1.20",
59
+ "@forgeax/engine-vite-plugin-shader": "0.1.20",
60
60
  "@types/node": "^20.14.0"
61
61
  },
62
62
  "forgeax": {
@@ -374,12 +374,11 @@ describe('mesh default material ready boundary', () => {
374
374
  if (refs.length > 0) {
375
375
  reg.catalog(parseGuid(WRONG_DEFAULT_KIND_GUID), {
376
376
  kind: 'texture' as const,
377
- width: 1,
378
- height: 1,
377
+ shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
379
378
  format: 'rgba8unorm' as const,
380
379
  data: new Uint8Array([255, 255, 255, 255]),
381
380
  colorSpace: 'srgb' as const,
382
- mipmap: false,
381
+ mips: { kind: 'none' as const },
383
382
  });
384
383
  }
385
384
  const omittedDefaultGuid = 'b0000000-0000-4000-b000-000000000099';
@@ -445,12 +444,11 @@ describe('mesh default material ready boundary', () => {
445
444
  const reg = makeRegistry();
446
445
  reg.catalog(parseGuid(WRONG_DEFAULT_KIND_GUID), {
447
446
  kind: 'texture' as const,
448
- width: 1,
449
- height: 1,
447
+ shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
450
448
  format: 'rgba8unorm' as const,
451
449
  data: new Uint8Array([255, 255, 255, 255]),
452
450
  colorSpace: 'srgb' as const,
453
- mipmap: false,
451
+ mips: { kind: 'none' as const },
454
452
  });
455
453
 
456
454
  const packIndex = [
@@ -740,21 +738,19 @@ describe('AC-02 — material recursive loadByGuid', () => {
740
738
  // Pre-register textures in dev mode (fast-path compatible)
741
739
  reg.catalog(parseGuid(TEXTURE_A_GUID), {
742
740
  kind: 'texture' as const,
743
- width: 4,
744
- height: 4,
741
+ shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
745
742
  format: 'rgba8unorm' as const,
746
743
  data: new Uint8Array(64),
747
744
  colorSpace: 'srgb' as const,
748
- mipmap: false,
745
+ mips: { kind: 'none' as const },
749
746
  });
750
747
  reg.catalog(parseGuid(TEXTURE_B_GUID), {
751
748
  kind: 'texture' as const,
752
- width: 4,
753
- height: 4,
749
+ shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
754
750
  format: 'rgba8unorm' as const,
755
751
  data: new Uint8Array(64),
756
752
  colorSpace: 'srgb' as const,
757
- mipmap: false,
753
+ mips: { kind: 'none' as const },
758
754
  });
759
755
 
760
756
  const packIndex = [
@@ -900,12 +896,11 @@ describe('AC-03 — gltf-shaped scene composite (via SceneAsset, D-9)', () => {
900
896
  // Pre-register texture in dev mode.
901
897
  reg.catalog(parseGuid(TEXTURE_C_GUID), {
902
898
  kind: 'texture' as const,
903
- width: 4,
904
- height: 4,
899
+ shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
905
900
  format: 'rgba8unorm' as const,
906
901
  data: new Uint8Array(64),
907
902
  colorSpace: 'srgb' as const,
908
- mipmap: false,
903
+ mips: { kind: 'none' as const },
909
904
  });
910
905
 
911
906
  const packIndex = [
@@ -1103,12 +1098,11 @@ const LEAF_FIXTURES: readonly LeafFixture[] = [
1103
1098
  kind: 'texture',
1104
1099
  makeAsset: () => ({
1105
1100
  kind: 'texture' as const,
1106
- width: 4,
1107
- height: 4,
1101
+ shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
1108
1102
  format: 'rgba8unorm' as const,
1109
1103
  data: new Uint8Array(64),
1110
1104
  colorSpace: 'srgb' as const,
1111
- mipmap: false,
1105
+ mips: { kind: 'none' as const },
1112
1106
  }),
1113
1107
  },
1114
1108
  {
@@ -689,11 +689,10 @@ function _makeStubGPU(): unknown {
689
689
  kind: 'texture',
690
690
  payload: {
691
691
  kind: 'texture',
692
- width: 2,
693
- height: 2,
692
+ shape: { viewDimension: '2d', extent: { width: 2, height: 2 } },
694
693
  format: 'rgba8unorm',
695
694
  colorSpace: 'srgb',
696
- mipmap: false,
695
+ mips: { kind: 'none' },
697
696
  },
698
697
  refs: [],
699
698
  artifacts: {
@@ -706,7 +705,11 @@ function _makeStubGPU(): unknown {
706
705
  const out = await textureLoader.loadPack?.(input, mockCtx());
707
706
  expect(out.ok).toBe(true);
708
707
  if (out.ok) {
709
- expect(out.value).toMatchObject({ kind: 'texture', width: 2, height: 2 });
708
+ expect(out.value).toMatchObject({
709
+ kind: 'texture',
710
+ shape: { viewDimension: '2d', extent: { width: 2, height: 2 } },
711
+ mips: { kind: 'none' },
712
+ });
710
713
  }
711
714
  });
712
715
 
@@ -3414,11 +3417,10 @@ function _makeStubGPU(): unknown {
3414
3417
 
3415
3418
  const TEXTURE_METADATA = {
3416
3419
  kind: 'texture',
3417
- width: 2,
3418
- height: 2,
3420
+ shape: { viewDimension: '2d', extent: { width: 2, height: 2 } },
3419
3421
  format: 'rgba8unorm',
3420
3422
  colorSpace: 'srgb',
3421
- mipmap: false,
3423
+ mips: { kind: 'none' },
3422
3424
  } as const;
3423
3425
 
3424
3426
  function unimportedTextureRow() {
@@ -3731,10 +3733,13 @@ function _makeStubGPU(): unknown {
3731
3733
  guid: GUID_IMPORTED,
3732
3734
  kind: 'texture',
3733
3735
  payload: {
3734
- width: binWidth,
3735
- height: binHeight,
3736
+ shape: {
3737
+ viewDimension: '2d',
3738
+ extent: { width: binWidth, height: binHeight },
3739
+ },
3736
3740
  format: 'rgba16float',
3737
3741
  colorSpace: 'linear',
3742
+ mips: { kind: 'none' },
3738
3743
  },
3739
3744
  refs: [],
3740
3745
  artifacts: {
@@ -3771,8 +3776,10 @@ function _makeStubGPU(): unknown {
3771
3776
  const tex: TextureAsset = result.value;
3772
3777
  expect(tex.format).toBe('rgba16float');
3773
3778
  expect(tex.colorSpace).toBe('linear');
3774
- expect(tex.width).toBe(binWidth);
3775
- expect(tex.height).toBe(binHeight);
3779
+ expect(tex.shape).toEqual({
3780
+ viewDimension: '2d',
3781
+ extent: { width: binWidth, height: binHeight },
3782
+ });
3776
3783
  expect(tex.data.length).toBe(binWidth * binHeight * 4 * 2);
3777
3784
  });
3778
3785
  });
@@ -5544,11 +5551,10 @@ function _makeStubGPU(): unknown {
5544
5551
  kind: 'texture',
5545
5552
  payload: {
5546
5553
  kind: 'texture',
5547
- width: 4,
5548
- height: 4,
5554
+ shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
5549
5555
  format: 'rgba8unorm',
5550
5556
  colorSpace: 'linear',
5551
- mipmap: false,
5557
+ mips: { kind: 'none' },
5552
5558
  },
5553
5559
  refs: [],
5554
5560
  artifacts: {
@@ -5577,11 +5583,10 @@ function _makeStubGPU(): unknown {
5577
5583
  // loadByGuid returns the payload directly.
5578
5584
  const asset = result.value;
5579
5585
  expect(asset.kind).toBe('texture');
5580
- expect(asset.width).toBe(4);
5581
- expect(asset.height).toBe(4);
5586
+ expect(asset.shape).toEqual({ viewDimension: '2d', extent: { width: 4, height: 4 } });
5582
5587
  expect(asset.format).toBe('rgba8unorm');
5583
5588
  expect(asset.colorSpace).toBe('linear');
5584
- expect(asset.mipmap).toBe(false);
5589
+ expect(asset.mips).toEqual({ kind: 'none' });
5585
5590
  expect(asset.data.byteLength).toBe(4 * 4 * 4);
5586
5591
  expect(asset.data[0]).toBe(0);
5587
5592
  expect(asset.data[63]).toBe(63);
@@ -6040,12 +6045,11 @@ function _makeStubGPU(): unknown {
6040
6045
 
6041
6046
  return {
6042
6047
  kind: 'texture' as const,
6043
- width: 1,
6044
- height: 1,
6048
+ shape: { viewDimension: '2d' as const, extent: { width: 1, height: 1 } },
6045
6049
  format: 'rgba16float',
6046
6050
  data,
6047
6051
  colorSpace: 'linear' as const,
6048
- mipmap: false,
6052
+ mips: { kind: 'none' as const },
6049
6053
  };
6050
6054
  }
6051
6055
 
@@ -6065,8 +6069,8 @@ function _makeStubGPU(): unknown {
6065
6069
  if (got === undefined) return;
6066
6070
  expect(got.format).toBe('rgba16float');
6067
6071
  expect(got.colorSpace).toBe('linear');
6068
- expect(got.width).toBe(1);
6069
- expect(got.height).toBe(1);
6072
+ expect(got.shape).toEqual({ viewDimension: '2d', extent: { width: 1, height: 1 } });
6073
+ expect(got.mips).toEqual({ kind: 'none' });
6070
6074
  expect(got.data.length).toBe(8);
6071
6075
  });
6072
6076
 
@@ -6815,12 +6819,11 @@ function _makeStubGPU(): unknown {
6815
6819
  function makeTexture(format: GPUTextureFormat, colorSpace: 'srgb' | 'linear'): TextureAsset {
6816
6820
  return {
6817
6821
  kind: 'texture',
6818
- width: 1,
6819
- height: 1,
6822
+ shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
6820
6823
  format,
6821
6824
  data: new Uint8Array([255, 255, 255, 255]),
6822
6825
  colorSpace,
6823
- mipmap: false,
6826
+ mips: { kind: 'none' },
6824
6827
  };
6825
6828
  }
6826
6829
 
@@ -166,10 +166,10 @@ function wireFetch(
166
166
  guid: GUID_TEX,
167
167
  kind: 'texture',
168
168
  payload: {
169
- width: W,
170
- height: H,
169
+ shape: { viewDimension: '2d', extent: { width: W, height: H } },
171
170
  format: fixture.colorSpace === 'srgb' ? 'rgba8unorm-srgb' : 'rgba16float',
172
171
  colorSpace: fixture.colorSpace,
172
+ mips: { kind: 'none' },
173
173
  },
174
174
  refs: [],
175
175
  artifacts: {
@@ -238,10 +238,13 @@ describe.skipIf(!pkgBuilt)('Basis catalog dispatch round-trip (M6 fix)', () => {
238
238
  const result = await loadWith(true);
239
239
  expect(result.ok).toBe(true);
240
240
  if (!result.ok) throw new Error(`load failed: ${result.error.code}`);
241
- const tex = result.value as { kind: string; width: number; height: number; format: string };
241
+ const tex = result.value as {
242
+ kind: string;
243
+ shape: { viewDimension: string; extent: { width: number; height: number } };
244
+ format: string;
245
+ };
242
246
  expect(tex.kind).toBe('texture');
243
- expect(tex.width).toBe(W);
244
- expect(tex.height).toBe(H);
247
+ expect(tex.shape).toEqual({ viewDimension: '2d', extent: { width: W, height: H } });
245
248
  // NO_CAPS -> the transcode arm degrades to the uncompressed sRGB fallback
246
249
  // (section 8 P3), never a scheme=1 reject.
247
250
  expect(tex.format).toBe('rgba8unorm-srgb');
@@ -290,15 +293,13 @@ describe.skipIf(!pkgBuilt)('Basis catalog dispatch round-trip (M6 fix)', () => {
290
293
  if (!result.ok) continue;
291
294
  const texture = result.value as {
292
295
  kind: string;
293
- width: number;
294
- height: number;
296
+ shape: { viewDimension: string; extent: { width: number; height: number } };
295
297
  format: string;
296
298
  data: Uint8Array;
297
299
  colorSpace: string;
298
300
  };
299
301
  expect(texture.kind).toBe('texture');
300
- expect(texture.width).toBe(W);
301
- expect(texture.height).toBe(H);
302
+ expect(texture.shape).toEqual({ viewDimension: '2d', extent: { width: W, height: H } });
302
303
  expect(texture.data.byteLength).toBeGreaterThan(0);
303
304
  expect(texture.colorSpace).toBe(fixture.colorSpace);
304
305
  const expected =
@@ -79,13 +79,11 @@ function compressedPod(): TextureAsset {
79
79
  bytes.fill(level.level + 1, level.byteOffset, level.byteOffset + level.byteLength);
80
80
  return {
81
81
  kind: 'texture',
82
- width: 2085,
83
- height: 1573,
82
+ shape: { viewDimension: '2d', extent: { width: 2085, height: 1573 } },
84
83
  format: 'bc7-rgba-unorm',
85
84
  data: bytes,
86
85
  colorSpace: 'linear',
87
- mipmap: true,
88
- mipLevelCount: layout.length,
86
+ mips: { kind: 'packed', levelCount: layout.length },
89
87
  };
90
88
  }
91
89
 
@@ -93,13 +91,18 @@ describe('compressed texture physical extent recorded-RHI witness [w36]', () =>
93
91
  it('allocates and uploads BC7 physical extents while preserving logical asset metadata', () => {
94
92
  const calls = { create: [] as TextureCall[], writes: [] as WriteCall[] };
95
93
  const pod = compressedPod();
96
- const expected = deriveMipUploadLayout(pod.format, pod.width, pod.height, numMipLevels(pod));
94
+ const expected = deriveMipUploadLayout(
95
+ pod.format,
96
+ pod.shape.extent.width,
97
+ pod.shape.extent.height,
98
+ pod.mips.kind === 'packed' ? pod.mips.levelCount : 1,
99
+ );
97
100
 
98
101
  const result = makeStore(calls).ensureResident(toShared<'TextureAsset'>(36), pod);
99
102
  if (!result.ok) throw result.error;
100
103
 
101
- expect(pod.width).toBe(2085);
102
- expect(pod.height).toBe(1573);
104
+ expect(pod.shape.extent.width).toBe(2085);
105
+ expect(pod.shape.extent.height).toBe(1573);
103
106
  expect(calls.create).toHaveLength(1);
104
107
  expect(calls.create[0]?.size).toMatchObject({ width: 2088, height: 1576 });
105
108
  expect(calls.writes).toHaveLength(expected.length);
@@ -122,12 +125,11 @@ describe('compressed texture physical extent recorded-RHI witness [w36]', () =>
122
125
  const calls = { create: [] as TextureCall[], writes: [] as WriteCall[] };
123
126
  const pod: TextureAsset = {
124
127
  kind: 'texture',
125
- width: 17,
126
- height: 9,
128
+ shape: { viewDimension: '2d', extent: { width: 17, height: 9 } },
127
129
  format: 'rgba8unorm',
128
130
  data: new Uint8Array(17 * 9 * 4),
129
131
  colorSpace: 'linear',
130
- mipmap: false,
132
+ mips: { kind: 'none' },
131
133
  };
132
134
 
133
135
  expect(makeStore(calls).ensureResident(toShared<'TextureAsset'>(37), pod).ok).toBe(true);
@@ -89,11 +89,10 @@ function makeCornerCheckerTexture(): TextureAsset {
89
89
  }
90
90
  return {
91
91
  kind: 'texture',
92
- width: side,
93
- height: side,
92
+ shape: { viewDimension: '2d', extent: { width: side, height: side } },
94
93
  format: 'rgba8unorm-srgb',
95
94
  colorSpace: 'srgb',
96
- mipmap: false,
95
+ mips: { kind: 'none' },
97
96
  data: bytes,
98
97
  };
99
98
  }
@@ -99,12 +99,11 @@ function textureBytes(color: readonly [number, number, number, number]): Uint8Ar
99
99
  function makeTexture(color: readonly [number, number, number, number]): TextureAsset {
100
100
  return {
101
101
  kind: 'texture',
102
- width: 1,
103
- height: 1,
102
+ shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
104
103
  format: 'rgba8unorm-srgb',
105
104
  data: textureBytes(color),
106
105
  colorSpace: 'srgb',
107
- mipmap: false,
106
+ mips: { kind: 'none' },
108
107
  };
109
108
  }
110
109
 
@@ -42,12 +42,11 @@ function makeTexture(
42
42
  ): TextureAsset {
43
43
  return {
44
44
  kind: 'texture',
45
- width,
46
- height,
45
+ shape: { viewDimension: '2d', extent: { width, height } },
47
46
  format,
48
47
  data: new Uint8Array(width * height * 4),
49
48
  colorSpace,
50
- mipmap,
49
+ mips: mipmap ? { kind: 'generate' } : { kind: 'none' },
51
50
  };
52
51
  }
53
52
 
@@ -60,12 +60,11 @@ function makeChequerTexture(primaryR: number, primaryG: number, primaryB: number
60
60
  }
61
61
  return {
62
62
  kind: 'texture',
63
- width: side,
64
- height: side,
63
+ shape: { viewDimension: '2d', extent: { width: side, height: side } },
65
64
  format: 'rgba8unorm',
66
65
  data: bytes,
67
66
  colorSpace: 'srgb',
68
- mipmap: false,
67
+ mips: { kind: 'none' },
69
68
  };
70
69
  }
71
70
 
@@ -126,11 +126,10 @@ describe('feat-20260527-sprite-nineslice w13 dawn smoke (HANDLE_NINESLICE_QUAD b
126
126
  }
127
127
  const texAsset: TextureAsset = {
128
128
  kind: 'texture',
129
- width: 4,
130
- height: 4,
129
+ shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
131
130
  format: 'rgba8unorm-srgb',
132
131
  colorSpace: 'srgb',
133
- mipmap: false,
132
+ mips: { kind: 'none' },
134
133
  data: pixels,
135
134
  };
136
135
  const texHandle = world.allocSharedRef<'TextureAsset', TextureAsset>('TextureAsset', texAsset);
@@ -72,12 +72,11 @@ function makeTexture(
72
72
  ): TextureAsset {
73
73
  return {
74
74
  kind: 'texture',
75
- width,
76
- height,
75
+ shape: { viewDimension: '2d', extent: { width, height } },
77
76
  format,
78
77
  data: new Uint8Array(width * height * 4),
79
78
  colorSpace,
80
- mipmap,
79
+ mips: mipmap ? { kind: 'generate' } : { kind: 'none' },
81
80
  };
82
81
  }
83
82
 
@@ -182,12 +181,11 @@ describe('AC-04: uploadTexture non-256-aligned width (real GPU path)', () => {
182
181
 
183
182
  const tex: TextureAsset = {
184
183
  kind: 'texture',
185
- width,
186
- height,
184
+ shape: { viewDimension: '2d', extent: { width, height } },
187
185
  format: 'rgba8unorm',
188
186
  data: buf,
189
187
  colorSpace: 'linear',
190
- mipmap: false,
188
+ mips: { kind: 'none' },
191
189
  };
192
190
 
193
191
  const handle = world.allocSharedRef('TextureAsset', tex);
@@ -87,6 +87,17 @@ const builtinSchemas: ReadonlyArray<{ id: string; schema: readonly ParamSchemaEn
87
87
  ];
88
88
 
89
89
  describe('derive(schema) integration over 5 built-in shader families (M3 w11)', () => {
90
+ it('derives array and volume texture schemas without a second binding path', () => {
91
+ for (const schema of [
92
+ [{ name: 'layers', type: 'texture2d_array' as const }],
93
+ [{ name: 'volume', type: 'texture3d' as const }],
94
+ ]) {
95
+ const out = derive(schema);
96
+ expect(out.bglEntries.map((entry) => entry.binding)).toEqual([0, 1, 2]);
97
+ expect(out.resourceBindings[1]?.name).toBe(schema[0]?.name);
98
+ expect(out.textureFieldNames).toContain(schema[0]?.name);
99
+ }
100
+ });
90
101
  it('(a) every built-in shader derives a well-formed BGL with consistent userRegionBindingEnd', () => {
91
102
  for (const { id, schema } of builtinSchemas) {
92
103
  const out = derive(schema);
@@ -145,12 +145,14 @@ describe.skipIf(!hasGpu)('equirect renderable-source integration (feat-20260707
145
145
  // POD mirrors that 2D surface.
146
146
  const source = {
147
147
  kind: 'texture' as const,
148
- width: asset.width,
149
- height: asset.height,
148
+ shape: { viewDimension: '2d' as const, extent: { width: asset.width, height: asset.height } },
150
149
  format: asset.format,
151
- data: asset.data,
150
+ data:
151
+ asset.data instanceof Uint8ClampedArray
152
+ ? new Uint8Array(asset.data.buffer, asset.data.byteOffset, asset.data.byteLength)
153
+ : asset.data,
152
154
  colorSpace: asset.colorSpace,
153
- mipmap: false,
155
+ mips: { kind: 'none' as const },
154
156
  };
155
157
  const res = deriveRenderDataCubemap(source);
156
158
  expect(res.ok).toBe(true);
@@ -530,6 +530,16 @@ type RuntimeLayerError = RenderError | AssetRuntimeError | SkinError;
530
530
  return 'points lines prepare failed';
531
531
  case 'transmission-capability-missing':
532
532
  return 'transmission capability missing';
533
+ case 'projector-binding-failed':
534
+ return 'projector binding failed';
535
+ case 'volume-owner-conflict':
536
+ return 'volume owner conflict';
537
+ case 'volume-density-shape-mismatch':
538
+ return 'volume density shape mismatch';
539
+ case 'volume-invalid-bounds':
540
+ return 'volume invalid bounds';
541
+ case 'volume-invalid-parameters':
542
+ return 'volume invalid parameters';
533
543
  }
534
544
  }
535
545
  expect(exhaustive('equirect-projection-failed')).toBe('equirect projection failed');
@@ -680,6 +690,16 @@ type RuntimeLayerError = RenderError | AssetRuntimeError | SkinError;
680
690
  return 'ok';
681
691
  case 'transmission-capability-missing':
682
692
  return 'ok';
693
+ case 'projector-binding-failed':
694
+ return 'ok';
695
+ case 'volume-owner-conflict':
696
+ return 'ok';
697
+ case 'volume-density-shape-mismatch':
698
+ return 'ok';
699
+ case 'volume-invalid-bounds':
700
+ return 'ok';
701
+ case 'volume-invalid-parameters':
702
+ return 'ok';
683
703
  }
684
704
  }
685
705
  expect(exhaustive('hdrp-deferred-caps-insufficient')).toBe('ok');
@@ -216,10 +216,10 @@ describe('w16: 4-path compressed-fixture loading', () => {
216
216
  guid: GUID_TEX,
217
217
  kind: 'texture',
218
218
  payload: {
219
- width: 4,
220
- height: 4,
219
+ shape: { viewDimension: '2d', extent: { width: 4, height: 4 } },
221
220
  format: 'rgba8unorm',
222
221
  colorSpace: 'srgb',
222
+ mips: { kind: 'none' },
223
223
  },
224
224
  refs: [],
225
225
  artifacts: {
@@ -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
 
@@ -117,7 +117,7 @@ describe('w7 -- light GPU pack byte-neutral (AC-11)', () => {
117
117
  expect(out[6]).toBeCloseTo(0.6, 6);
118
118
  });
119
119
 
120
- it('packSpotLight writes color slots 4..6 + direction slots 8..10 byte-for-byte (64 B)', () => {
120
+ it('packSpotLight writes color slots 4..6 + direction slots 8..10 byte-for-byte (80 B)', () => {
121
121
  const snap: SpotLightSnapshot = {
122
122
  kind: 'spot',
123
123
  position: vec3.create(0, 5, 0),
@@ -135,7 +135,9 @@ describe('w7 -- light GPU pack byte-neutral (AC-11)', () => {
135
135
  shadowAtlasTile: -1,
136
136
  };
137
137
  const out = packSpotLight(snap);
138
- expect(out.byteLength).toBe(64);
138
+ // SpotLight carries projector receiver controls and the shadow intensity
139
+ // tail in the current std430 payload.
140
+ expect(out.byteLength).toBe(80);
139
141
  expect(out[4]).toBeCloseTo(0.3, 6);
140
142
  expect(out[5]).toBeCloseTo(0.4, 6);
141
143
  expect(out[6]).toBeCloseTo(0.5, 6);