@forgeax/engine-rhi 0.0.0-dev.8d955ade1c79

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 (77) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +234 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/async-form.test-d.d.ts +2 -0
  5. package/dist/__tests__/async-form.test-d.d.ts.map +1 -0
  6. package/dist/__tests__/binding-resource.test-d.d.ts +2 -0
  7. package/dist/__tests__/binding-resource.test-d.d.ts.map +1 -0
  8. package/dist/__tests__/buffer-mapping.test-d.d.ts +2 -0
  9. package/dist/__tests__/buffer-mapping.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/canvasContext.test-d.d.ts +2 -0
  11. package/dist/__tests__/canvasContext.test-d.d.ts.map +1 -0
  12. package/dist/__tests__/caps-three-way.unit.test.d.ts +2 -0
  13. package/dist/__tests__/caps-three-way.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/caps.test-d.d.ts +2 -0
  15. package/dist/__tests__/caps.test-d.d.ts.map +1 -0
  16. package/dist/__tests__/command-encoder-spec-alignment.test-d.d.ts +2 -0
  17. package/dist/__tests__/command-encoder-spec-alignment.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/createComputePipeline.test-d.d.ts +2 -0
  19. package/dist/__tests__/createComputePipeline.test-d.d.ts.map +1 -0
  20. package/dist/__tests__/createQuerySet.test-d.d.ts +2 -0
  21. package/dist/__tests__/createQuerySet.test-d.d.ts.map +1 -0
  22. package/dist/__tests__/createTextureView.test-d.d.ts +2 -0
  23. package/dist/__tests__/createTextureView.test-d.d.ts.map +1 -0
  24. package/dist/__tests__/descriptor-mirror.test-d.d.ts +2 -0
  25. package/dist/__tests__/descriptor-mirror.test-d.d.ts.map +1 -0
  26. package/dist/__tests__/draw-validation-errors.test.d.ts +2 -0
  27. package/dist/__tests__/draw-validation-errors.test.d.ts.map +1 -0
  28. package/dist/__tests__/errors-instancing.unit.test.d.ts +2 -0
  29. package/dist/__tests__/errors-instancing.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/errors.test-d.d.ts +2 -0
  31. package/dist/__tests__/errors.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/limit-exceeded-detail.test-d.d.ts +2 -0
  33. package/dist/__tests__/limit-exceeded-detail.test-d.d.ts.map +1 -0
  34. package/dist/__tests__/mapped-buffer.test-d.d.ts +2 -0
  35. package/dist/__tests__/mapped-buffer.test-d.d.ts.map +1 -0
  36. package/dist/__tests__/opaque-handle.test-d.d.ts +2 -0
  37. package/dist/__tests__/opaque-handle.test-d.d.ts.map +1 -0
  38. package/dist/__tests__/owner-out-of-range-role.test.d.ts +2 -0
  39. package/dist/__tests__/owner-out-of-range-role.test.d.ts.map +1 -0
  40. package/dist/__tests__/queue-writes.test-d.d.ts +2 -0
  41. package/dist/__tests__/queue-writes.test-d.d.ts.map +1 -0
  42. package/dist/__tests__/render-pass-encoder-spec-alignment.test-d.d.ts +2 -0
  43. package/dist/__tests__/render-pass-encoder-spec-alignment.test-d.d.ts.map +1 -0
  44. package/dist/__tests__/requestAdapter.test-d.d.ts +2 -0
  45. package/dist/__tests__/requestAdapter.test-d.d.ts.map +1 -0
  46. package/dist/errors.d.ts +315 -0
  47. package/dist/errors.d.ts.map +1 -0
  48. package/dist/errors.mjs +56 -0
  49. package/dist/errors.mjs.map +1 -0
  50. package/dist/index.d.ts +1758 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.mjs +87 -0
  53. package/dist/index.mjs.map +1 -0
  54. package/package.json +61 -0
  55. package/src/__tests__/async-form.test-d.ts +40 -0
  56. package/src/__tests__/binding-resource.test-d.ts +66 -0
  57. package/src/__tests__/buffer-mapping.test-d.ts +84 -0
  58. package/src/__tests__/canvasContext.test-d.ts +106 -0
  59. package/src/__tests__/caps-three-way.unit.test.ts +144 -0
  60. package/src/__tests__/caps.test-d.ts +83 -0
  61. package/src/__tests__/command-encoder-spec-alignment.test-d.ts +117 -0
  62. package/src/__tests__/createComputePipeline.test-d.ts +149 -0
  63. package/src/__tests__/createQuerySet.test-d.ts +81 -0
  64. package/src/__tests__/createTextureView.test-d.ts +127 -0
  65. package/src/__tests__/descriptor-mirror.test-d.ts +284 -0
  66. package/src/__tests__/draw-validation-errors.test.ts +155 -0
  67. package/src/__tests__/errors-instancing.unit.test.ts +167 -0
  68. package/src/__tests__/errors.test-d.ts +199 -0
  69. package/src/__tests__/limit-exceeded-detail.test-d.ts +33 -0
  70. package/src/__tests__/mapped-buffer.test-d.ts +65 -0
  71. package/src/__tests__/opaque-handle.test-d.ts +121 -0
  72. package/src/__tests__/owner-out-of-range-role.test.ts +84 -0
  73. package/src/__tests__/queue-writes.test-d.ts +108 -0
  74. package/src/__tests__/render-pass-encoder-spec-alignment.test-d.ts +202 -0
  75. package/src/__tests__/requestAdapter.test-d.ts +123 -0
  76. package/src/errors.ts +504 -0
  77. package/src/index.ts +2276 -0
@@ -0,0 +1,84 @@
1
+ // RhiBuffer mapAsync / mapState surface + MappedBuffer brand method form.
2
+ //
3
+ // K-1 decision (plan-strategy §2): mapAsync mode parameter is the raw
4
+ // GPUMapModeFlags bitmask (NOT a closed union 'read' | 'write'). spec mirror
5
+ // stance (research §4.2 + W3C WebGPU §buffer-mapping spec anchor).
6
+ //
7
+ // D-P2 #6 (feat-20260511-rhi-spec-realign-aggressive M1): mapAsync resolves
8
+ // to `Promise<Result<MappedBuffer, RhiError>>`; `getMappedRange` and `unmap`
9
+ // migrated from `Buffer` -> `MappedBuffer` method form (this: MappedBuffer
10
+ // implicit guard). The previous top-level Buffer.getMappedRange / Buffer.unmap
11
+ // are removed.
12
+ //
13
+ // Charter: proposition 1 (progressive disclosure - one read shows the buffer
14
+ // surface) + proposition 3 (machine-readable union > prose) + proposition 4
15
+ // (explicit failure - state mismatch surfaces TS2339 at the call site, not
16
+ // runtime panic).
17
+ //
18
+ // Anchors: requirements AC-11 (MappedBuffer brand) + research §4.1 / §4.2 /
19
+ // §4.4; plan-strategy §7.1 + D-P2 #6; plan-decisions OQ-5 (unmap +
20
+ // re-mapAsync re-attach path).
21
+
22
+ /// <reference types="@webgpu/types" />
23
+
24
+ import { describe, expectTypeOf, it } from 'vitest';
25
+ import type { Buffer, MappedBuffer, Result, RhiError } from '../index';
26
+
27
+ /** Strict structural equality helper. */
28
+ type Equals<X, Y> =
29
+ (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
30
+
31
+ describe('Buffer mapAsync surface + MappedBuffer brand methods', () => {
32
+ it('mapAsync(mode: GPUMapModeFlags, offset?, size?) returns Promise<Result<MappedBuffer, RhiError>>', () => {
33
+ type Method = Buffer['mapAsync'];
34
+ expectTypeOf<Method>().toBeFunction();
35
+ type Expected = (
36
+ mode: GPUMapModeFlags,
37
+ offset?: number | undefined,
38
+ size?: number | undefined,
39
+ ) => Promise<Result<MappedBuffer, RhiError>>;
40
+ expectTypeOf<Expected>().toMatchTypeOf<Method>();
41
+ });
42
+
43
+ it('mapAsync mode parameter is the raw GPUMapModeFlags bitmask (K-1 decision)', () => {
44
+ type ModeArg = Parameters<Buffer['mapAsync']>[0];
45
+ expectTypeOf<ModeArg>().toEqualTypeOf<GPUMapModeFlags>();
46
+ type ClosedUnion = 'read' | 'write';
47
+ type IsClosedUnion = ModeArg extends ClosedUnion ? true : false;
48
+ expectTypeOf<IsClosedUnion>().toEqualTypeOf<false>();
49
+ });
50
+
51
+ it('MappedBuffer.getMappedRange returns Result<ArrayBuffer, RhiError>', () => {
52
+ type Method = MappedBuffer['getMappedRange'];
53
+ expectTypeOf<Method>().toBeFunction();
54
+ type Expected = (
55
+ offset?: number | undefined,
56
+ size?: number | undefined,
57
+ ) => Result<ArrayBuffer, RhiError>;
58
+ expectTypeOf<Expected>().toMatchTypeOf<Method>();
59
+ });
60
+
61
+ it('MappedBuffer.unmap returns void (spec silent no-op)', () => {
62
+ type Method = MappedBuffer['unmap'];
63
+ expectTypeOf<Method>().toBeFunction();
64
+ type Expected = () => void;
65
+ expectTypeOf<Expected>().toMatchTypeOf<Method>();
66
+ type IsResultShape =
67
+ ReturnType<MappedBuffer['unmap']> extends Result<unknown, unknown> ? true : false;
68
+ expectTypeOf<IsResultShape>().toEqualTypeOf<false>();
69
+ });
70
+
71
+ it("mapState getter exposes the closed union 'unmapped' | 'pending' | 'mapped'", () => {
72
+ expectTypeOf<Buffer['mapState']>().toEqualTypeOf<'unmapped' | 'pending' | 'mapped'>();
73
+ });
74
+
75
+ it('mapState writes are rejected at compile time (readonly)', () => {
76
+ type Writable<T> = { -readonly [K in keyof T]: T[K] };
77
+ type IsReadonlyMapState = Equals<Buffer, Writable<Buffer>>;
78
+ expectTypeOf<IsReadonlyMapState>().toEqualTypeOf<false>();
79
+ });
80
+
81
+ it('MappedBuffer is a structural superset of Buffer (D-P2 #6 brand)', () => {
82
+ expectTypeOf<MappedBuffer>().toMatchTypeOf<Buffer>();
83
+ });
84
+ });
@@ -0,0 +1,106 @@
1
+ // w19 - RhiSurface + RhiCanvasContext type-level contract test (TDD red).
2
+ //
3
+ // Locks M3 CanvasContext surface (plan-tasks w19 / requirements IN-4 / AC-04;
4
+ // plan-strategy K-4):
5
+ // RhiSurface — abstraction wrapping HTMLCanvasElement / OffscreenCanvas
6
+ // RhiCanvasContext.configure(desc): Result<void, RhiError>
7
+ // RhiCanvasContext.unconfigure(): void (spec literal void return)
8
+ // RhiCanvasContext.getConfiguration(): CanvasConfiguration | undefined
9
+ // RhiCanvasContext.getCurrentTexture(): Result<Texture, RhiError> (K-4)
10
+ //
11
+ // CanvasConfiguration = Pick<GPUCanvasConfiguration,
12
+ // 'device' | 'format' | 'usage' | 'viewFormats' | 'colorSpace' |
13
+ // 'toneMapping' | 'alphaMode'> (7 fields per spec §3.2)
14
+ //
15
+ // Red expected: tsc -b fails with TS2305 / TS2339 (missing RhiSurface /
16
+ // RhiCanvasContext / CanvasConfiguration). Turns green after w21 ships.
17
+ //
18
+ // K-4 decision: getCurrentTexture returns Result<Texture, RhiError> (NOT
19
+ // TextureView) — spec literal alignment; AI users go two-step:
20
+ // const tex = (canvasContext.getCurrentTexture()).unwrap();
21
+ // const view = device.createTextureView(tex, {}).unwrap();
22
+ //
23
+ // Anchors: requirements §IN-4 / §AC-04; research §3.1 4 methods + §3.2 7
24
+ // fields; plan-strategy §2 K-4 + §6 M3 + K-10.
25
+
26
+ import { describe, expectTypeOf, it } from 'vitest';
27
+ import type {
28
+ CanvasConfiguration,
29
+ Result,
30
+ RhiCanvasContext,
31
+ RhiError,
32
+ RhiSurface,
33
+ Texture,
34
+ } from '../index';
35
+
36
+ describe('w19 type-level - RhiSurface abstraction', () => {
37
+ it('RhiSurface is an abstraction over canvas surface (brand-only opaque)', () => {
38
+ // RhiSurface wraps HTMLCanvasElement / OffscreenCanvas; the abstraction
39
+ // is opaque from the AI-user perspective (spec couples context to canvas).
40
+ const s = {} as RhiSurface;
41
+ // @ts-expect-error MVP: RhiSurface is opaque; raw fields not exposed.
42
+ s.getContext;
43
+ });
44
+ });
45
+
46
+ describe('w19 type-level - CanvasConfiguration field set === Pick<GPUCanvasConfiguration, ...> (research §3.2 7 fields)', () => {
47
+ it('has exactly the 7 spec keys (device / format / usage / viewFormats / colorSpace / toneMapping / alphaMode)', () => {
48
+ type ExpectedKeys =
49
+ | 'device'
50
+ | 'format'
51
+ | 'usage'
52
+ | 'viewFormats'
53
+ | 'colorSpace'
54
+ | 'toneMapping'
55
+ | 'alphaMode';
56
+ expectTypeOf<keyof CanvasConfiguration>().toEqualTypeOf<ExpectedKeys>();
57
+ });
58
+
59
+ it('format is a GPUTextureFormat (forgeax form preserves spec field type)', () => {
60
+ type FormatField = NonNullable<CanvasConfiguration['format']>;
61
+ type SpecFormat = NonNullable<GPUCanvasConfiguration['format']>;
62
+ expectTypeOf<FormatField>().toEqualTypeOf<SpecFormat>();
63
+ });
64
+ });
65
+
66
+ describe('w19 type-level - RhiCanvasContext.configure signature', () => {
67
+ it('takes CanvasConfiguration and returns Result<void, RhiError>', () => {
68
+ type Sig = RhiCanvasContext['configure'];
69
+ type Ret = ReturnType<Sig>;
70
+ expectTypeOf<Ret>().toEqualTypeOf<Result<void, RhiError>>();
71
+ type Params = Parameters<Sig>;
72
+ expectTypeOf<Params>().toEqualTypeOf<[desc: CanvasConfiguration]>();
73
+ });
74
+ });
75
+
76
+ describe('w19 type-level - RhiCanvasContext.unconfigure signature (spec void return)', () => {
77
+ it('takes no arguments and returns void (spec literal alignment)', () => {
78
+ type Sig = RhiCanvasContext['unconfigure'];
79
+ type Ret = ReturnType<Sig>;
80
+ expectTypeOf<Ret>().toEqualTypeOf<void>();
81
+ type Params = Parameters<Sig>;
82
+ expectTypeOf<Params>().toEqualTypeOf<[]>();
83
+ });
84
+ });
85
+
86
+ describe('w19 type-level - RhiCanvasContext.getConfiguration signature', () => {
87
+ it('returns CanvasConfiguration | undefined (feature-detection entry)', () => {
88
+ type Sig = RhiCanvasContext['getConfiguration'];
89
+ type Ret = ReturnType<Sig>;
90
+ expectTypeOf<Ret>().toEqualTypeOf<CanvasConfiguration | undefined>();
91
+ });
92
+ });
93
+
94
+ describe('w19 type-level - RhiCanvasContext.getCurrentTexture signature (K-4)', () => {
95
+ it('returns Result<Texture, RhiError> (K-4: Texture brand, NOT TextureView)', () => {
96
+ type Sig = RhiCanvasContext['getCurrentTexture'];
97
+ type Ret = ReturnType<Sig>;
98
+ expectTypeOf<Ret>().toEqualTypeOf<Result<Texture, RhiError>>();
99
+ });
100
+
101
+ it('takes no arguments', () => {
102
+ type Sig = RhiCanvasContext['getCurrentTexture'];
103
+ type Params = Parameters<Sig>;
104
+ expectTypeOf<Params>().toEqualTypeOf<[]>();
105
+ });
106
+ });
@@ -0,0 +1,144 @@
1
+ // caps-three-way.unit.test.ts — M4 w23 (green after w25).
2
+ //
3
+ // Verifies the three-way texture compression cap contract on RhiCaps.
4
+ // D-8: caps fields are derived from GPUAdapter.features Set via has(...).
5
+ //
6
+ // Scenarios:
7
+ // (a) bc-only → textureCompressionBc=true, etc2/astc=false
8
+ // (b) etc2+astc → etc2/astc true, bc false
9
+ // (c) no compression → three-way all false
10
+ // (d) rhi-null → three-way all false (AC-06)
11
+
12
+ import { describe, expect, expectTypeOf, it } from 'vitest';
13
+ import type { RhiCaps } from '../index';
14
+
15
+ // ============================================================================
16
+ // Type-level: RhiCaps has the three fields as readonly boolean
17
+ // ============================================================================
18
+
19
+ describe('RhiCaps — three-way texture compression (w25)', () => {
20
+ it('textureCompressionBc is readonly boolean', () => {
21
+ expectTypeOf<RhiCaps['textureCompressionBc']>().toEqualTypeOf<boolean>();
22
+ });
23
+
24
+ it('textureCompressionEtc2 is readonly boolean', () => {
25
+ expectTypeOf<RhiCaps['textureCompressionEtc2']>().toEqualTypeOf<boolean>();
26
+ });
27
+
28
+ it('textureCompressionAstc is readonly boolean', () => {
29
+ expectTypeOf<RhiCaps['textureCompressionAstc']>().toEqualTypeOf<boolean>();
30
+ });
31
+
32
+ it('textureCompressionBc/Etc2/Astc are readonly', () => {
33
+ type Check = {
34
+ readonly textureCompressionBc: boolean;
35
+ readonly textureCompressionEtc2: boolean;
36
+ readonly textureCompressionAstc: boolean;
37
+ };
38
+ expectTypeOf<
39
+ Pick<RhiCaps, 'textureCompressionBc' | 'textureCompressionEtc2' | 'textureCompressionAstc'>
40
+ >().toEqualTypeOf<Check>();
41
+ });
42
+
43
+ it('three fields are camelCase per D-8', () => {
44
+ const keys = [
45
+ 'textureCompressionBc',
46
+ 'textureCompressionEtc2',
47
+ 'textureCompressionAstc',
48
+ ] as const;
49
+ for (const k of keys) {
50
+ expect(k).toMatch(/^textureCompression[A-Z]/);
51
+ }
52
+ });
53
+ });
54
+
55
+ // ============================================================================
56
+ // Scenario tests: mock the Derive-from-features contract
57
+ // ============================================================================
58
+
59
+ /** Simulates Derive: given a Set of GPUFeatureName strings, produce the three caps. */
60
+ function deriveThreeWayCaps(features: ReadonlySet<string>): {
61
+ textureCompressionBc: boolean;
62
+ textureCompressionEtc2: boolean;
63
+ textureCompressionAstc: boolean;
64
+ } {
65
+ return {
66
+ textureCompressionBc: features.has('texture-compression-bc'),
67
+ textureCompressionEtc2: features.has('texture-compression-etc2'),
68
+ textureCompressionAstc: features.has('texture-compression-astc'),
69
+ };
70
+ }
71
+
72
+ describe('RhiCaps three-way — derive from features Set (Derive principle, D-8)', () => {
73
+ it('(a) bc-only: textureCompressionBc=true, etc2/astc=false', () => {
74
+ const caps = deriveThreeWayCaps(new Set(['texture-compression-bc']));
75
+ expect(caps.textureCompressionBc).toBe(true);
76
+ expect(caps.textureCompressionEtc2).toBe(false);
77
+ expect(caps.textureCompressionAstc).toBe(false);
78
+ });
79
+
80
+ it('(b) etc2-only: textureCompressionEtc2=true, bc/astc=false', () => {
81
+ const caps = deriveThreeWayCaps(new Set(['texture-compression-etc2']));
82
+ expect(caps.textureCompressionBc).toBe(false);
83
+ expect(caps.textureCompressionEtc2).toBe(true);
84
+ expect(caps.textureCompressionAstc).toBe(false);
85
+ });
86
+
87
+ it('(b-2) astc-only: textureCompressionAstc=true, bc/etc2=false', () => {
88
+ const caps = deriveThreeWayCaps(new Set(['texture-compression-astc']));
89
+ expect(caps.textureCompressionBc).toBe(false);
90
+ expect(caps.textureCompressionEtc2).toBe(false);
91
+ expect(caps.textureCompressionAstc).toBe(true);
92
+ });
93
+
94
+ it('(c) no compression features: three-way all false', () => {
95
+ const caps = deriveThreeWayCaps(new Set(['timestamp-query']));
96
+ expect(caps.textureCompressionBc).toBe(false);
97
+ expect(caps.textureCompressionEtc2).toBe(false);
98
+ expect(caps.textureCompressionAstc).toBe(false);
99
+ // Also works with empty set
100
+ const capsEmpty = deriveThreeWayCaps(new Set());
101
+ expect(capsEmpty.textureCompressionBc).toBe(false);
102
+ expect(capsEmpty.textureCompressionEtc2).toBe(false);
103
+ expect(capsEmpty.textureCompressionAstc).toBe(false);
104
+ });
105
+
106
+ it('(d) rhi-null contract: three-way all false (AC-06)', () => {
107
+ // rhi-null backend has no real GPU; its caps must truthfully report false.
108
+ // Verified here as the contract; actual null device verification is in w26.
109
+ const caps = deriveThreeWayCaps(new Set());
110
+ expect(caps.textureCompressionBc).toBe(false);
111
+ expect(caps.textureCompressionEtc2).toBe(false);
112
+ expect(caps.textureCompressionAstc).toBe(false);
113
+ });
114
+
115
+ it('bc+etc2+astc co-exist: all three true (e.g. Apple Silicon dual HW)', () => {
116
+ const caps = deriveThreeWayCaps(
117
+ new Set(['texture-compression-bc', 'texture-compression-etc2', 'texture-compression-astc']),
118
+ );
119
+ expect(caps.textureCompressionBc).toBe(true);
120
+ expect(caps.textureCompressionEtc2).toBe(true);
121
+ expect(caps.textureCompressionAstc).toBe(true);
122
+ });
123
+ });
124
+
125
+ // ============================================================================
126
+ // Breaking: old textureCompression must be absent from RhiCaps after w25
127
+ // ============================================================================
128
+
129
+ describe('RhiCaps — old field removed (breaking, no compat alias)', () => {
130
+ it('old textureCompression single boolean is no longer a key of RhiCaps', () => {
131
+ // Type-level: 'textureCompression' extends keyof RhiCaps? must be false.
132
+ type HasOldTextureCompression = 'textureCompression' extends keyof RhiCaps ? true : false;
133
+ expectTypeOf<HasOldTextureCompression>().toEqualTypeOf<false>();
134
+ });
135
+
136
+ it('old name is not among the three-way keys', () => {
137
+ const threeWayKeys = [
138
+ 'textureCompressionBc',
139
+ 'textureCompressionEtc2',
140
+ 'textureCompressionAstc',
141
+ ] as const;
142
+ expect(threeWayKeys).not.toContain('textureCompression');
143
+ });
144
+ });
@@ -0,0 +1,83 @@
1
+ // Type-level — RhiCaps capability layer field-set + readonly boolean shape.
2
+ //
3
+ // Baseline fields (round 1 + rhi-resource-creation + M4 three-way caps):
4
+ // 1) compute / 2) timestampQuery / 3) indirectDrawing
5
+ // 4-6) textureCompressionBc / textureCompressionEtc2 / textureCompressionAstc
6
+ // (M4 breaking change: single textureCompression boolean replaced with
7
+ // three-way per-compression-format caps, derived from adapter.features)
8
+ // 7) multiDrawIndirect (reserved) / 8) pushConstants (reserved) /
9
+ // 9) textureBindingArray (reserved)
10
+ //
11
+ // Extended in feat-20260511-rhi-spec-realign-aggressive w2 (red) ->
12
+ // w7 (green) per plan-strategy D-P3 + requirements AC-05 + research R-03 §3.1
13
+ // 4-field mapping matrix:
14
+ // 10) samplerAliasing — spec mandatory; both backends true
15
+ // 11) firstInstanceIndirect — gated by 'indirect-first-instance' feature
16
+ // 12) storageBuffer — limits.maxStorageBuffersPerShaderStage > 0
17
+ // 13) storageTexture — limits.maxStorageTexturesPerShaderStage > 0
18
+ //
19
+ // 3 reserved fields (#7/#8/#9) JSDoc wording amended in w8 from
20
+ // `@reserved-for-wgpu-wasm` to `@reserved-for-wgpu-native-only` (AC-06): on
21
+ // the browser path both rhi-webgpu and rhi-wgpu wasm always return false; only
22
+ // wgpu native (Tauri / native runtime) flips them.
23
+ //
24
+ // charter mapping: proposition 4 (explicit failure — caps.X = false is a
25
+ // signal, not an exception) + proposition 5 (consistent abstraction —
26
+ // browsers + native share one cap surface, differences are discoverable not
27
+ // hidden).
28
+
29
+ import { describe, expectTypeOf, it } from 'vitest';
30
+ import type { RhiCaps } from '../index';
31
+
32
+ describe('RhiCaps — field shape', () => {
33
+ it('contains baseline boolean fields', () => {
34
+ expectTypeOf<RhiCaps['compute']>().toEqualTypeOf<boolean>();
35
+ expectTypeOf<RhiCaps['timestampQuery']>().toEqualTypeOf<boolean>();
36
+ expectTypeOf<RhiCaps['indirectDrawing']>().toEqualTypeOf<boolean>();
37
+ expectTypeOf<RhiCaps['textureCompressionBc']>().toEqualTypeOf<boolean>();
38
+ expectTypeOf<RhiCaps['textureCompressionEtc2']>().toEqualTypeOf<boolean>();
39
+ expectTypeOf<RhiCaps['textureCompressionAstc']>().toEqualTypeOf<boolean>();
40
+ expectTypeOf<RhiCaps['multiDrawIndirect']>().toEqualTypeOf<boolean>();
41
+ expectTypeOf<RhiCaps['pushConstants']>().toEqualTypeOf<boolean>();
42
+ expectTypeOf<RhiCaps['textureBindingArray']>().toEqualTypeOf<boolean>();
43
+ });
44
+
45
+ it('contains D-P3 boolean fields (samplerAliasing / firstInstanceIndirect / storageBuffer / storageTexture)', () => {
46
+ expectTypeOf<RhiCaps['samplerAliasing']>().toEqualTypeOf<boolean>();
47
+ expectTypeOf<RhiCaps['firstInstanceIndirect']>().toEqualTypeOf<boolean>();
48
+ expectTypeOf<RhiCaps['storageBuffer']>().toEqualTypeOf<boolean>();
49
+ expectTypeOf<RhiCaps['storageTexture']>().toEqualTypeOf<boolean>();
50
+ });
51
+
52
+ // feat-20260612-hdrp-deferred-shading-learn-render-5-8 M1 / w2:
53
+ // RhiCaps.maxColorAttachments field existence + type = number.
54
+ it('contains maxColorAttachments: number field', () => {
55
+ expectTypeOf<RhiCaps['maxColorAttachments']>().toEqualTypeOf<number>();
56
+ });
57
+
58
+ it('maxColorAttachments is readonly', () => {
59
+ type Check = { readonly maxColorAttachments: number };
60
+ expectTypeOf<Pick<RhiCaps, 'maxColorAttachments'>>().toEqualTypeOf<Check>();
61
+ });
62
+
63
+ it('all fields are readonly', () => {
64
+ type ReadonlyKeys = {
65
+ readonly compute: boolean;
66
+ readonly timestampQuery: boolean;
67
+ readonly timestampPeriodNanoseconds: number | null;
68
+ readonly indirectDrawing: boolean;
69
+ readonly textureCompressionBc: boolean;
70
+ readonly textureCompressionEtc2: boolean;
71
+ readonly textureCompressionAstc: boolean;
72
+ readonly multiDrawIndirect: boolean;
73
+ readonly pushConstants: boolean;
74
+ readonly textureBindingArray: boolean;
75
+ readonly samplerAliasing: boolean;
76
+ readonly firstInstanceIndirect: boolean;
77
+ readonly storageBuffer: boolean;
78
+ readonly storageTexture: boolean;
79
+ readonly maxColorAttachments: number;
80
+ };
81
+ expectTypeOf<RhiCaps>().toMatchTypeOf<ReadonlyKeys>();
82
+ });
83
+ });
@@ -0,0 +1,117 @@
1
+ // w2 type-level - RhiCommandEncoder spec method-set alignment with
2
+ // `@webgpu/types` GPUCommandEncoder (research F-1 / plan-strategy D-S4).
3
+ //
4
+ // Asserts (will be RED until w3 lands the impl):
5
+ // - 12 spec methods (9 direct + 3 GPUDebugCommandsMixin) exist on RhiCommandEncoder
6
+ // - `copyBufferToBuffer` exposes both spec overloads (3-arg shorthand + 5-arg full form)
7
+ // - `finish()` returns Result<CommandBuffer, RhiError>
8
+ // - method NAMES align byte-for-byte with `@webgpu/types` GPUCommandEncoder
9
+ //
10
+ // Charter mapping: proposition 1 (progressive disclosure) + proposition 3
11
+ // (machine-readable union > prose) + proposition 5 (consistent abstraction).
12
+
13
+ import { describe, expectTypeOf, it } from 'vitest';
14
+ import type {
15
+ Buffer,
16
+ CommandBuffer,
17
+ QuerySet,
18
+ Result,
19
+ RhiCommandEncoder,
20
+ RhiError,
21
+ } from '../index';
22
+
23
+ describe('w2 - RhiCommandEncoder spec method set (12 methods)', () => {
24
+ it('exposes beginRenderPass(desc): RhiRenderPassEncoder', () => {
25
+ expectTypeOf<RhiCommandEncoder['beginRenderPass']>().toBeFunction();
26
+ });
27
+
28
+ it('exposes beginComputePass(desc?): RhiComputePassEncoder', () => {
29
+ expectTypeOf<RhiCommandEncoder['beginComputePass']>().toBeFunction();
30
+ });
31
+
32
+ it('exposes copyBufferToBuffer (5-arg full form per spec)', () => {
33
+ // 5-arg overload must be callable (research F-1 finding):
34
+ // copyBufferToBuffer(src, srcOffset, dst, dstOffset, size): void
35
+ type FullForm = (
36
+ source: Buffer,
37
+ sourceOffset: number,
38
+ destination: Buffer,
39
+ destinationOffset: number,
40
+ size: number,
41
+ ) => void;
42
+ type CallSignature5 = RhiCommandEncoder['copyBufferToBuffer'] extends FullForm ? true : false;
43
+ expectTypeOf<CallSignature5>().toEqualTypeOf<true>();
44
+ });
45
+
46
+ it('exposes copyBufferToBuffer (3-arg shorthand overload per spec)', () => {
47
+ // 3-arg overload (size optional):
48
+ // copyBufferToBuffer(src, dst, size?): void
49
+ type Shorthand = (source: Buffer, destination: Buffer, size?: number | undefined) => void;
50
+ type CallSignature3 = RhiCommandEncoder['copyBufferToBuffer'] extends Shorthand ? true : false;
51
+ expectTypeOf<CallSignature3>().toEqualTypeOf<true>();
52
+ });
53
+
54
+ it('exposes copyBufferToTexture(source, destination, copySize)', () => {
55
+ expectTypeOf<RhiCommandEncoder['copyBufferToTexture']>().toBeFunction();
56
+ });
57
+
58
+ it('exposes copyTextureToBuffer(source, destination, copySize)', () => {
59
+ expectTypeOf<RhiCommandEncoder['copyTextureToBuffer']>().toBeFunction();
60
+ });
61
+
62
+ it('exposes copyTextureToTexture(source, destination, copySize)', () => {
63
+ expectTypeOf<RhiCommandEncoder['copyTextureToTexture']>().toBeFunction();
64
+ });
65
+
66
+ it('exposes clearBuffer(buffer, offset?, size?)', () => {
67
+ type Method = RhiCommandEncoder['clearBuffer'];
68
+ expectTypeOf<Method>().toBeFunction();
69
+ type Expected = (
70
+ buffer: Buffer,
71
+ offset?: number | undefined,
72
+ size?: number | undefined,
73
+ ) => void;
74
+ expectTypeOf<Expected>().toMatchTypeOf<Method>();
75
+ });
76
+
77
+ it('exposes resolveQuerySet (capability-gated placeholder per D-S4)', () => {
78
+ type Method = RhiCommandEncoder['resolveQuerySet'];
79
+ expectTypeOf<Method>().toBeFunction();
80
+ type Expected = (
81
+ querySet: QuerySet,
82
+ firstQuery: number,
83
+ queryCount: number,
84
+ destination: Buffer,
85
+ destinationOffset: number,
86
+ ) => Result<void, RhiError>;
87
+ expectTypeOf<Expected>().toMatchTypeOf<Method>();
88
+ });
89
+
90
+ it('exposes pushDebugGroup(label) (GPUDebugCommandsMixin)', () => {
91
+ expectTypeOf<RhiCommandEncoder['pushDebugGroup']>().toBeFunction();
92
+ });
93
+
94
+ it('exposes popDebugGroup() (GPUDebugCommandsMixin)', () => {
95
+ expectTypeOf<RhiCommandEncoder['popDebugGroup']>().toBeFunction();
96
+ });
97
+
98
+ it('exposes insertDebugMarker(label) (GPUDebugCommandsMixin)', () => {
99
+ expectTypeOf<RhiCommandEncoder['insertDebugMarker']>().toBeFunction();
100
+ });
101
+
102
+ it('finish() returns Result<CommandBuffer, RhiError>', () => {
103
+ expectTypeOf<RhiCommandEncoder['finish']>().returns.toEqualTypeOf<
104
+ Result<CommandBuffer, RhiError>
105
+ >();
106
+ });
107
+
108
+ // w38 (M5 / K-3): writeTimestamp on the CommandEncoder entry only.
109
+ // CPE/RPE inside-pass timestamp writes are deferred to
110
+ // feat-future-rhi-perf-timestamp-pass per K-3 decision.
111
+ it('exposes writeTimestamp(querySet, queryIndex): void (K-3 CommandEncoder-only)', () => {
112
+ type Method = RhiCommandEncoder['writeTimestamp'];
113
+ expectTypeOf<Method>().toBeFunction();
114
+ type Expected = (querySet: QuerySet, queryIndex: number) => void;
115
+ expectTypeOf<Expected>().toMatchTypeOf<Method>();
116
+ });
117
+ });