@carbonenginejs/runtime-resource 0.11.1 → 0.12.0

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 (72) hide show
  1. package/dist/formats/bnk/CjsBnkFormat.js +12 -2
  2. package/dist/formats/bnk/CjsBnkFormat.js.map +1 -1
  3. package/dist/formats/bnk/core/graph.js +3 -6
  4. package/dist/formats/bnk/core/graph.js.map +1 -1
  5. package/dist/formats/bnk/core/helpers.js +53 -11
  6. package/dist/formats/bnk/core/helpers.js.map +1 -1
  7. package/dist/formats/bnk/core/sfxNodes.js +532 -0
  8. package/dist/formats/bnk/core/sfxNodes.js.map +1 -0
  9. package/dist/formats/bnk/core/soundbanksInfo.js +59 -51
  10. package/dist/formats/bnk/core/soundbanksInfo.js.map +1 -1
  11. package/dist/formats/gr2/CjsGr2Format.js +256 -13
  12. package/dist/formats/gr2/CjsGr2Format.js.map +1 -1
  13. package/dist/formats/gr2/core/helpers.js +15 -15
  14. package/dist/formats/gr2/core/helpers.js.map +1 -1
  15. package/dist/formats/gr2/core/json.js +1 -1
  16. package/dist/formats/gr2/core/json.js.map +1 -1
  17. package/dist/formats/gr2/core/targets.js +1 -1
  18. package/dist/formats/gr2/core/targets.js.map +1 -1
  19. package/dist/formats/gr2/index.js +0 -1
  20. package/dist/formats/gr2/index.js.map +1 -1
  21. package/dist/formats/hlsl/core/analysis.js +1 -1
  22. package/dist/formats/hlsl/index.js +1 -0
  23. package/dist/formats/hlsl/index.js.map +1 -1
  24. package/dist/formats/index.js +3 -6
  25. package/dist/formats/index.js.map +1 -1
  26. package/dist/formats/webgl/core/cewg/binary.js +3 -3
  27. package/dist/formats/webgl/core/cewg/binary.js.map +1 -1
  28. package/dist/formats/webgl/core/effectPackage.js +4 -1
  29. package/dist/formats/webgl/core/effectPackage.js.map +1 -1
  30. package/dist/formats/webgl/core/errors.js +3 -3
  31. package/dist/formats/webgl/core/errors.js.map +1 -1
  32. package/docs/formats/dxbc/README.md +68 -0
  33. package/docs/formats/dxbc/architecture.md +80 -0
  34. package/docs/formats/dxbc/reference/api.md +77 -0
  35. package/docs/formats/dxbc/reference/classes/README.md +76 -0
  36. package/docs/formats/dxbc/reference/decoded-output.md +122 -0
  37. package/docs/formats/gr2.md +3 -4
  38. package/docs/formats/hlsl/README.md +54 -0
  39. package/docs/formats/hlsl/architecture.md +67 -0
  40. package/docs/formats/hlsl/guides/hydrating-json-output.md +62 -0
  41. package/docs/formats/hlsl/guides/reading-effects.md +64 -0
  42. package/docs/formats/hlsl/reference/advanced-analysis.md +66 -0
  43. package/docs/formats/hlsl/reference/api.md +97 -0
  44. package/docs/formats/hlsl/reference/classes/README.md +16 -0
  45. package/docs/formats/hlsl/reference/classes/carbon-compatibility.md +66 -0
  46. package/docs/formats/hlsl/reference/classes/public-api.md +26 -0
  47. package/docs/formats/hlsl/reference/classes/tr2-effect-model.md +150 -0
  48. package/docs/formats/hlsl/reference/json-graph.md +100 -0
  49. package/docs/formats/hlsl/reference/portable-reflection.md +141 -0
  50. package/docs/formats/provenance.md +9 -4
  51. package/docs/formats/webgl/README.md +57 -0
  52. package/docs/formats/webgl/architecture.md +70 -0
  53. package/docs/formats/webgl/carbon-constant-layouts.md +326 -0
  54. package/docs/formats/webgl/decl-io.md +1234 -0
  55. package/docs/formats/webgl/effect-reflection.md +127 -0
  56. package/docs/formats/webgl/memory-structured.md +871 -0
  57. package/docs/formats/webgl/reference/classes/README.md +96 -0
  58. package/docs/formats/webgl/texture-sample.md +964 -0
  59. package/docs/formats/webgpu/README.md +81 -0
  60. package/docs/formats/webgpu/architecture.md +93 -0
  61. package/docs/formats/webgpu/formats/cewgpu.md +479 -0
  62. package/docs/formats/webgpu/guides/effect-packaging.md +199 -0
  63. package/docs/formats/webgpu/reference/api.md +197 -0
  64. package/docs/formats/webgpu/reference/classes/README.md +66 -0
  65. package/docs/formats/webgpu/reference/wgsl-compatibility.md +1442 -0
  66. package/docs/formats/wwise.md +16 -1
  67. package/docs/reference/classes/formats.md +10 -10
  68. package/format-notices/bnk/NOTICE +5 -4
  69. package/format-notices/webgl/NOTICE +1 -1
  70. package/package.json +1 -1
  71. package/dist/formats/gr2/core/CjsFormatGr2.js +0 -273
  72. package/dist/formats/gr2/core/CjsFormatGr2.js.map +0 -1
@@ -0,0 +1,1442 @@
1
+ # WGSL compatibility
2
+
3
+ Status: Evolving
4
+ Scope: `@carbonenginejs/runtime-resource/formats/webgpu` DXBC-to-WGSL lowering
5
+ Audience: Shader-tool authors, engine integrators, and maintainers
6
+ Summary: Records deliberate semantic adaptations, unsupported inputs, and bounded compiler behavior.
7
+
8
+ ## Purpose
9
+
10
+ This page records every deliberate divergence between the DXBC contract and
11
+ emitted WGSL, every fail-closed boundary, and every bounded support decision.
12
+ Consult and update it whenever compiler behavior changes;
13
+ each entry says why it exists and what revisiting it would take. Categories:
14
+
15
+ - **Adapted** — accepted input whose WGSL semantics deliberately differ from
16
+ the exact D3D contract. These are the entries to re-read first when hunting
17
+ a rendering difference against the native client.
18
+ - **Not supported (fail closed)** — inputs the compiler rejects with an
19
+ explicit diagnostic rather than guessing.
20
+ - **Bounded / temporary** — supported within stated limits; the limits are the
21
+ first thing to widen when a shader trips them.
22
+
23
+ ## Adapted
24
+
25
+ ### `precise` floating-point operations → ordinary math + `@invariant` position
26
+
27
+ Current package policy adapts DXBC `precise`, which forbids
28
+ reassociation/fusion so multi-pass position math is bit-identical.
29
+ WGSL has no general no-contraction control, so instead of rejecting these
30
+ operations (a previous compiler boundary for higher-quality shader profiles):
31
+
32
+ - `precise`-marked operations lower as ordinary IEEE float math;
33
+ - every vertex `SV_Position` output is emitted `@invariant @builtin(position)`
34
+ (unconditionally, all vertex shaders), which guarantees identical position
35
+ results across pipelines built from the same emitted WGSL — the multi-pass
36
+ crack/z-fight artifact `precise` protects against;
37
+ - precise-mask metadata is still validated structurally (well-formed mask,
38
+ lanes covered by a destination write) in
39
+ `src/core/wgsl/precisionControls.js`.
40
+
41
+ NOT promised: bit-exact arithmetic parity with native D3D11; differential
42
+ tests against native output may differ in final ulps. Globally-non-refactorable
43
+ shaders (missing `REFACTORING_ALLOWED`) remain rejected — that contract is
44
+ stronger and genuinely unrepresentable. Revisit: if WGSL ever gains a
45
+ no-contraction control, restore exact lowering and drop this entry.
46
+
47
+ ### DXBC comparison masks → `select` masks
48
+
49
+ DXBC comparisons produce 0xFFFFFFFF/0 integer masks; WGSL comparisons produce
50
+ `bool`. All comparison opcodes (`lt/ge/eq/ne`, `ilt/ige/ieq/ine`, `ult/uge`)
51
+ lower as `select(0u, 0xffffffffu, a OP b)` so downstream mask arithmetic
52
+ (`and`/`movc` chains) stays bit-faithful.
53
+
54
+ ### Typeless registers → per-lane storage types with explicit bitcasts
55
+
56
+ DXBC registers are typeless 32-bit lanes. The type-inference union assigns one
57
+ storage type per SSA lane (conflicts become `bitpattern32` = `u32`) and every
58
+ crossing emits an explicit `bitcast`. Mixed-component writes (one instruction
59
+ writing lanes of different resolved types) split into per-component `let`s
60
+ (`valueN_x`, …) for immediate movs, structured loads, packed intrinsic
61
+ projections, and per-lane `movc` selects — in BOTH stages.
62
+
63
+ ### SSA-legal cross-scope reads → hoisted function-top `var`s
64
+
65
+ SSA may resolve a post-join read to one arm's definition (the other path
66
+ diverges via return/discard); structured WGSL scoping cannot express that
67
+ directly. `src/core/wgsl/hoistEscapingValues.js` hoists escaping declarations
68
+ to uninitialized function-top `var`s (WGSL zero-initializes) plus in-place
69
+ assignments. The zero is unobservable — SSA proves the value is only read on
70
+ assigning paths.
71
+
72
+ ### Switch clauses without a `default` → empty WGSL `default`
73
+
74
+ WGSL requires a `default` clause; DXBC switches without one fall through to
75
+ `endswitch`. The emitter appends an empty `default: {}`. Switches carrying
76
+ live merges DO require a real DXBC default (fail closed otherwise).
77
+
78
+ ### `SV_IsFrontFace` → `front_facing` mask projection
79
+
80
+ DXBC reads the front-face flag as a 0xFFFFFFFF/0 mask; WGSL's builtin is
81
+ `bool`. Consumers receive `select(0u, 0xffffffffu, front_facing)` (or the
82
+ signed variant).
83
+
84
+ ### Selection arms may write outputs alongside a live merge
85
+
86
+ A vertex `if`/`else` whose arms write shader outputs (typically `SV_Position`
87
+ in a Picking/stretch pass — one arm computes the real transform, the other
88
+ writes a constant/off-screen position) AND also carry a scalar phi merge
89
+ (e.g. a `TEXCOORD` lane read after the join) used to be rejected outright.
90
+ Output writes inside a branch compose correctly with the merge machinery: the
91
+ merge `var` is pre-declared before the `if` and assigned at each arm's end,
92
+ while output completeness is enforced by the post-branch component
93
+ intersection and the reachable-`ret` coverage check. The guard is therefore
94
+ removed for selections; the genuine "terminates before merge assignments"
95
+ (return inside an arm ahead of the appended merge write) guard stays.
96
+ This shape is covered by browser validation across vertex selections and live
97
+ merges.
98
+
99
+ The fragment stage kept this guard longer than the vertex stage even though its
100
+ surrounding machinery (per-arm written-component cloning, post-branch
101
+ intersection, merge-var appends) is identical; the fragment guard is now
102
+ removed too, browser-validated across fragment selections with live merges.
103
+
104
+ ### Scalar merge inputs inherited through an arm tail
105
+
106
+ A two-armed selection merge's inputs are matched to arms by
107
+ `incoming.blockId === trueBlockId/falseBlockId`; canonical IR records the CFG
108
+ predecessor there even when its value ref resolves to an upstream definition.
109
+ For accepted prebuilt IR where exactly one edge identity is unavailable, the
110
+ remaining input is assigned to the remaining arm by elimination — a two-armed
111
+ join has exactly two edges and the phi exactly two inputs. The referenced input
112
+ may still be inherited through an arm tail, which requires the scope handling
113
+ described below.
114
+
115
+ The inherited input frequently does not lexically dominate its arm-tail merge
116
+ assignment. That is safe for the two arms whose assignment is emitted *inside* a
117
+ branch body (the true arm, and the else arm of an if/else): a selection region
118
+ is acyclic, so on the path reaching the arm tail the value was already assigned
119
+ before the merge write, and `hoistEscapingValues` lifts its declaration to a
120
+ function-top `var` (the zero initializer is unobservable on paths that skip it).
121
+ The **no-else false input is excluded** from this relaxation — it pre-initializes
122
+ the merge `var` *before* the `if`, so it must genuinely dominate the header;
123
+ hoisting cannot rescue a value that may be unassigned on a path reaching the
124
+ pre-init. Inputs that neither dominate nor are hoistable (and undefined-register
125
+ inputs on the true edge) still fail closed. Browser-validated on avatar tattoo
126
+ picking selections whose merges inherit a true-arm value through an inner join.
127
+
128
+ ### Source modifiers (`neg`/`abs`/`absneg`) → per-consumer-type lowering
129
+
130
+ DXBC source-modifier semantics depend on the consuming instruction's type, and
131
+ the supported cases lower according to that consumer:
132
+
133
+ - float consumers: IEEE negate/abs (`-(x)`, `abs(x)`, `-(abs(x))`);
134
+ - signed-integer consumers: `neg` is two's-complement negation (`-(x)` on
135
+ `i32`);
136
+ - unsigned-integer consumers: `neg` is two's-complement negation, emitted as
137
+ the wrapping `(0u - x)` (WGSL has no unary minus on `u32`);
138
+ - integer consumers: `abs`/`absneg` fail closed because the absolute modifier
139
+ is defined only for floating-point instructions;
140
+ - bit-preserving movers (`mov`/`movc` with unknown or conflicting lane types):
141
+ the modifier applies FLOAT semantics to the raw lane bits, and IEEE
142
+ negate/abs/absneg are pure sign-bit operations, so they lower to
143
+ `^ 0x80000000u` / `& 0x7fffffffu` / `| 0x80000000u` on the `u32` storage
144
+ (with `bitcast` in/out for `i32`-stored lanes).
145
+
146
+ The ordinary WGSL float operators match finite non-zero inputs; signed-zero
147
+ and non-finite behavior inherits WGSL's floating-point latitude. The
148
+ bit-preserving mover path uses explicit sign-bit arithmetic.
149
+
150
+ Previously the modifier was applied as a type-blind `-(x)`/`abs(x)`, which was
151
+ invalid WGSL on `u32` lanes (caught by the browser gate) and a silent
152
+ miscompile on integer-stored mover lanes (two's-complement where the contract
153
+ is a sign-bit flip). The corpus-wide rebuild confirmed every previously
154
+ qualified package is byte-identical under the typed lowering: no already
155
+ qualified shader used the changed paths. Both stages; per-lane (mixed-type
156
+ `movc`) reads share the same storage-typed rules.
157
+
158
+ *Confirmed against vkd3d-shader:* `vsir_program_lower_modifiers` (ir.c) lowers
159
+ `NEG` as `data_type_is_integer(src) ? INEG : NEG` — integer vs float negate
160
+ dispatched on the operand's data type, the same per-consumer typing — with
161
+ `ABS` as float abs and `ABSNEG` as abs-then-neg. (vkd3d resolves the type before
162
+ lowering, so it has no separate bit-mover case; our sign-bit-on-raw-bits path is
163
+ the WGSL-specific equivalent for lanes whose type is still `bitpattern32`.)
164
+
165
+ When a `movc` writes lanes whose inferred storage types differ, both stages
166
+ emit one scalar `select` per lane instead of an unrepresentable mixed-type WGSL
167
+ vector. Each condition and value source is selected with that destination
168
+ lane's original swizzle, modifier, and storage reinterpretation. This path is
169
+ bounded to unsaturated temporary results and register, immediate, or constant-
170
+ buffer lane sources; other mixed mover shapes remain fail-closed. Condition
171
+ modifiers follow the `u32` consumer rules (two's-complement `neg`, with
172
+ `abs`/`absneg` rejected), while the two value operands retain the raw float-
173
+ data mover rules above.
174
+
175
+ ### `continue`/`continuec` in loops → WGSL `continuing {}` latch
176
+
177
+ Loop phi-latch updates are emitted in a WGSL `continuing {}` block (which runs
178
+ on both fall-through and `continue` paths) instead of being appended to the loop
179
+ body. `continue` lowers to `continue;` and `continuec` to `if (cond) { continue;
180
+ }`. Behavior is unchanged for loops without `continue` (the continuing block
181
+ still runs the latch each iteration); it simply makes body `continue` correct
182
+ rather than skipping the latch. Both stages.
183
+
184
+ ### Declared-but-unwritten location outputs → zero-filled (vertex only)
185
+
186
+ A **vertex** output signature may declare a `location` varying (COLOR/TEXCOORD)
187
+ that a given permutation never writes. D3D leaves such
188
+ lanes undefined; WGSL zero-initializes `var output`, so the unwritten lanes read
189
+ as 0 — a safe, valid choice. Completeness is still enforced for **builtin**
190
+ outputs (`SV_Position` must be fully written; zero is not a meaningful position).
191
+
192
+ The **fragment** stage does NOT relax this: an unwritten `SV_Target` lane still
193
+ fails closed (an undefined render-target lane is not a safe zero — it feeds
194
+ blending). Only the vertex `ret` completeness check was relaxed.
195
+
196
+ ### Dead untyped temp writes → skipped
197
+
198
+ Compiler-emitted dead stores whose values nothing reads (and whose types are
199
+ therefore unresolvable) are dropped instead of failing the module.
200
+
201
+ ### Terminal control flow → dead tail dropped
202
+
203
+ An `if`/`else` whose both arms return, or a `switch` with a default whose every
204
+ clause returns, terminates all paths; instructions after it (a trailing
205
+ unreachable `ret`, common after fully-branched Picking/depth outputs) are dead
206
+ and not lowered. Output-completeness is validated only on reachable `ret`s.
207
+
208
+ ### `immediate_constant_buffer` (DXBC icb) → module `const` array
209
+
210
+ DXBC's inline constant table (`customdata`, dataClass 3) is emitted as a
211
+ module-scope `const icb = array<vec4<f32>, N>(vec4<f32>(...), ...)`. Finite
212
+ non-zero lanes use a shortest f32 decimal; non-finite lanes and negative zero
213
+ use `bitcast<f32>(0x..u)` so the exceptional or sign bit pattern is preserved.
214
+ WGSL does not fix the rounding direction for an inexact decimal-to-f32
215
+ conversion, so readable finite decimal emission is not a normative raw-bit
216
+ guarantee; guaranteed preservation would require raw-bit literals for every
217
+ lane. The same literal emitter is used for immutable indexable-temp tables.
218
+ `immediate_constant_buffer` operands lower as
219
+ `icb[<index>].<comp>` reusing the dynamic constant-buffer index machinery
220
+ (pure-relative and base+relative indices both supported), with int/uint
221
+ consumers bitcast exactly like uniform cbuffers. Out-of-bounds dynamic indices
222
+ are an adaptation: D3D constant/ICB reads return zero, while the emitted
223
+ unchecked WGSL array access has implementation-chosen out-of-bounds behavior;
224
+ qualified corpus shaders stay in range.
225
+
226
+ ### Fixed-slot indexable temps → scalarized SSA locals
227
+
228
+ A declared width-four `indexable_temp` addressed as an exact
229
+ `x#[immediate-slot]` is treated as a bounded family of ordinary register
230
+ locals. The declaration must be unique, both index dimensions and the register
231
+ identity must be exact, the slot must be in range, and source selectors,
232
+ destination masks, and direct-IR SSA metadata must agree with the referenced
233
+ lanes. Each slot/lane then participates independently in the existing SSA,
234
+ masked-write, and structured merge machinery in both vertex and fragment
235
+ stages. No mutable WGSL array or dynamic array write is emitted.
236
+
237
+ Relative/dynamic addressing and narrower mutable declarations remain
238
+ fail-closed. Fixed reads of a register recognized as the immutable table shape
239
+ below are routed through that table rather than through mutable SSA. The
240
+ `cloud` and `cloudsimple` browser gates cover fixed-slot writes and reads with
241
+ zero WGSL warnings. The full corpus transition moved from 502 qualified / 35
242
+ unsupported / 0 failed to 504 / 33 / 0: exactly those two packages became
243
+ qualified, and SHA-256 comparison confirmed all 502 previously qualified
244
+ package bytes remained identical.
245
+
246
+ ### Relative indexable temps → module `const` tables (immutable shape only)
247
+
248
+ An indexable temp (`x#`) accessed with relative addressing is recognized when
249
+ it is an immutable constant table: every write is a straight-line
250
+ pre-control-flow `mov x#[slot].mask, l(...)` immediate, all initializers precede
251
+ every read, one write mask is shared, every declared slot is fully written for
252
+ that mask, and reads select only written lanes. Operand roles (including
253
+ dual-destination instructions), register identities, index widths, modifiers,
254
+ and precision metadata are validated exactly. Initializer source
255
+ selection/swizzles are applied before storage, and scalar immediates replicate
256
+ across every written lane. Such registers lower exactly like the icb — a
257
+ module-scope
258
+ `const xt# = array<vec4<f32>, N>(...)` with reads through the shared dynamic
259
+ index machinery (`xt#[base + i32(index)].comp`) — so the dynamic read needs no
260
+ mutable-register SSA and inherits index-driven uniformity. This is the
261
+ compiler-generated shape for small lookup tables (e.g. the six quad-corner
262
+ UVs in `particles/gpu/quads`). Any other relative indexable-temp use —
263
+ mutable writes, non-immediate initializers, initializers under control flow,
264
+ partial slots — fails closed with a per-reason diagnostic. Out-of-bounds
265
+ dynamic indices retain WGSL's implementation-chosen array-access behavior
266
+ rather than D3D's out-of-bounds register semantics; no qualified corpus shader
267
+ indexes out of bounds.
268
+
269
+ ### Component-packed varyings → one merged interface field per register
270
+
271
+ DXBC signature tables can emit several rows for a single interpolant register
272
+ when distinct semantics occupy different lanes (e.g. three `TEXCOORD`s packed
273
+ into `x`/`y`/`z` of output register 2, as in `starsprites`). Each row carries a
274
+ non-prefix mask (`y`-only, `z`-only) that would individually be rejected as a
275
+ gap in the WGSL location layout. Both stages now group signature rows by
276
+ `registerIndex`, union their masks, and emit ONE interface field per register
277
+ (validated prefix, single component type across the group). This is a
278
+ faithfulness fix, not a divergence — the merged field reproduces the register's
279
+ true lane occupancy.
280
+
281
+ ### `linear_noperspective` varyings → `@interpolate(linear)` on both stages
282
+
283
+ DXBC `linear` interpolation is perspective-correct — the WGSL default — and
284
+ needs no attribute. DXBC `linear_noperspective` maps exactly to WGSL
285
+ `@interpolate(linear)` (center sampling on both sides). Because WebGPU
286
+ requires the vertex output and fragment input attributes at one location to
287
+ MATCH at pipeline creation, and DXBC declares interpolation only on the
288
+ fragment side (`dcl_input_ps`), the pass-global binding plan records the
289
+ non-default modes (`varyingInterpolation`) and the vertex module mirrors them
290
+ onto its paired outputs. Mixed modes on one packed register, centroid and
291
+ sample variants, and `constant` fail closed.
292
+
293
+ ### Non-float `saturate` on movers → float clamp on the raw bits
294
+
295
+ D3D `saturate` assumes float data (like source modifiers). When a
296
+ bit-preserving `mov`/`movc` result's lanes resolve to integer storage, the
297
+ saturate lowers as `bitcast<T>(clamp(bitcast<f32-vec>(bits), 0.0, 1.0))` —
298
+ the direct WGSL float clamp on the raw lanes, keeping the storage type. Finite
299
+ values match the D3D clamp; non-finite inputs do not have portable
300
+ D3D-equivalent results in WGSL. Saturate on
301
+ genuinely integer arithmetic results still fails closed.
302
+
303
+ *Confirmed against vkd3d-shader:* `spirv_compiler_emit_sat` (spirv.c) is
304
+ `nclamp(x, 0.0, 1.0)` with float constants for floating-point data and a
305
+ `FIXME("Unhandled data type")` for non-float — saturate is a float clamp and
306
+ integer saturate is unhandled, matching "assumes float". Our bitcast-clamp on
307
+ `bitpattern32` mover lanes is the WGSL-specific handling for the float-data-in-
308
+ integer-storage case vkd3d left as a FIXME.
309
+
310
+ ### `rcp` (both stages) → ordinary f32 division
311
+
312
+ DXBC `rcp` is a reduced-precision component-wise reciprocal; its maximum
313
+ relative error is 2^-21. It lowers to `1.0 / x`. For a finite, normal,
314
+ non-zero f32 with `abs(x)` in `[2^-126, 2^126]`, WGSL gives f32 division a
315
+ maximum error of 2.5 ULP, which satisfies that DXBC accuracy allowance.
316
+
317
+ The special-value contract is adapted. D3D specifies signed infinities for
318
+ signed-zero and subnormal inputs, signed zero for infinities, and NaN for NaN.
319
+ WGSL permits zero signs to be ignored and makes a runtime result that is
320
+ infinite or NaN indeterminate under its finite-math assumption. Exact behavior
321
+ for those inputs is therefore not portable. Finite normal denominators outside
322
+ the stated magnitude range can produce a subnormal reciprocal that D3D flushes
323
+ to signed zero but WGSL may preserve, so only the stated range has the claimed
324
+ accuracy match. Immediate operands are a fail-closed portability boundary:
325
+ each consumed lane whose raw f32 exponent is zero (signed zero or subnormal) or
326
+ 255 (infinity or NaN) is rejected before modifiers and result saturation.
327
+ Unused immediate lanes are ignored, one-word immediates replicate normally,
328
+ and finite normal lanes remain accepted. Dynamic operands remain supported
329
+ with the signed-zero, subnormal, and non-finite caveats above. The same
330
+ signed-zero and non-finite caveats apply to the supported `div` opcode in both
331
+ stages.
332
+
333
+ *Confirmed against vkd3d-shader within the finite-normal scope above:* its
334
+ IR applies float source modifiers before `rcp` and destination saturation
335
+ afterward, while its SPIR-V backend emits floating division with a `1.0`
336
+ numerator using the active destination-mask/source-swizzle lanes. Scalar
337
+ immediates replicate across active lanes. Its tests also record D3D's
338
+ sign-sensitive zero/infinity results. This confirmation does not widen the
339
+ portable WGSL claim: signed zero, subnormal, infinity, NaN, overflow, and a
340
+ subnormal reciprocal retain the caveats above, and the GLSL/MSL backends do
341
+ not independently corroborate `rcp`.
342
+
343
+ ### Vertex-stage texture sampling → explicit LOD/gradient only
344
+
345
+ The vertex binding restriction now admits texture and sampler bindings, and the
346
+ vertex stage lowers `sample_l` (`textureSampleLevel`) and `sample_d`
347
+ (`textureSampleGrad`). Implicit-LOD `sample`/`sample_b` stay fragment-only —
348
+ WGSL forbids implicit derivatives in a vertex entry point.
349
+
350
+ ### Typed uint buffer UAVs + atomic operations → guarded storage atomics
351
+
352
+ A `dcl_unordered_access_view_typed` buffer with a uniform uint return type
353
+ lowers to `var<storage, read_write> uN: array<atomic<u32>>`. In fragment
354
+ programs, `atomic_iadd` becomes a bounds-guarded statement:
355
+ `if (i < arrayLength(&uN)) { atomicAdd(&uN[i], v); }`. The guard reproduces
356
+ D3D's defined behavior — out-of-bounds typed-UAV atomics are dropped — where
357
+ an unguarded WGSL access could target a live element or otherwise raise a
358
+ dynamic error. The result-returning form (`imm_atomic_iadd`), other atomic
359
+ opcodes, and non-uint or non-buffer UAV shapes fail closed. Vertex writable
360
+ storage remains outside the current portability contract. The bounded compute
361
+ profile below uses the same representation and `atomicStore` for ordinary
362
+ typed stores because WGSL requires every access to an atomic-typed element to
363
+ use an atomic builtin. The engine must bind either form as storage containing
364
+ raw 4-byte u32 words (`minBindingSize: 4`); the admitted typed-atomic contract
365
+ is one scalar word per element. No DXGI view-format conversion is reproduced.
366
+ *Confirmed against vkd3d-shader for operation shape and the
367
+ robustness-dependent OOB mechanism:* its backend emits the corresponding
368
+ atomic through a directly computed buffer/image pointer and inserts no
369
+ explicit bounds guard. A zero/drop result therefore depends on the applicable
370
+ target and runtime robustness guarantees; vkd3d-shader alone does not prove
371
+ that result for every target. This compiler independently implements D3D's
372
+ dropped-write result for the supported non-result atomic with an explicit
373
+ statement-level guard. A future result-returning OOB atomic must additionally
374
+ synthesize a zero old-value result.
375
+
376
+ ### Bounded 1×1×1 compute programs → native WebGPU compute pipelines
377
+
378
+ Compute lowering is admitted only for an exact, whole-program-validated SM5.0
379
+ structural profile currently exercised by `particles/gpu/setdrawparameters` and
380
+ `particles/gpu/setsortargs`. It requires canonical global/SRV/UAV/temp/thread-
381
+ group declarations, one temporary register, `dcl_thread_group 1,1,1`, one
382
+ reachable straight-line block ending in `ret`, and exactly one typed scalar
383
+ sint buffer SRV plus one typed scalar uint buffer UAV. The supported body
384
+ opcodes are `ld`, low-half `imul`, `umax`, `iadd`, `ushr`,
385
+ `store_uav_typed`, and `ret`; every operand, selector, immediate, binding,
386
+ type-flow fact, and SSA edge is revalidated before emission.
387
+
388
+ The SRV is exposed as `var<storage, read> tN: array<i32>` and an out-of-bounds
389
+ `ld` returns zero through a clamped load plus `select`. The UAV is
390
+ `var<storage, read_write> uN: array<atomic<u32>>`; an in-range typed store
391
+ uses `atomicStore`, while an out-of-bounds store is dropped by an explicit
392
+ branch. These scalar-word layouts deliberately do not reproduce DXGI typed-view
393
+ conversion, so the engine binding contract is one raw 4-byte word per element.
394
+ Restricting the profile to scalar `x` loads and replicated full-mask stores
395
+ also avoids guessing the width of a general DXBC typed-buffer view.
396
+
397
+ The package carries the declared thread-group size as `[1, 1, 1]`. Trinity
398
+ effect metadata identifies compute as stage type `2`, while the decoded DXBC
399
+ program type remains `5`; these two enums are intentionally kept separate.
400
+ The browser gate creates and validates native shader modules, compute bind-group
401
+ layouts, pipeline layouts, and compute pipelines. It does not dispatch work or
402
+ expand the public render-only device API.
403
+
404
+ The full corpus transition moved from 504 qualified / 33 unsupported / 0
405
+ failed to 506 / 31 / 0: exactly the two programs named above became qualified,
406
+ and SHA-256 comparison confirmed all 504 previously qualified package bytes
407
+ remained identical.
408
+
409
+ ### Bounded 64×1×1 structured skinning compute
410
+
411
+ A second, separate SM5.0 compute profile is currently exercised by
412
+ `system/raytracing/skinvertices`. Profile selection is structural rather than
413
+ path- or byte-hash-based, and malformed members of the selected declaration
414
+ family fail there instead of falling through to the scalar-word profile. The
415
+ declaration envelope is exactly one immediate `cb3` with three vec4 rows,
416
+ structured SRVs `t0`/`t1` with 48-/4-byte strides, one non-coherent structured
417
+ UAV `u0` with a 4-byte stride, `input_thread_id.x`, ten temporary registers,
418
+ and `dcl_thread_group 64,1,1`. Its bounded body has two nested selections and
419
+ no loops, barriers, atomics, textures, or samplers. CFG, SSA, scalar types,
420
+ bitcasts, live merges, operands, and resource identities are replayed and
421
+ compared before emission.
422
+
423
+ `input_thread_id.x` maps to
424
+ `@builtin(global_invocation_id) dispatch_thread_id: vec3<u32>` and uses only
425
+ the x component; the workgroup size is not multiplied into that already-global
426
+ identifier. The supported packed-index path treats `ubfe` as unsigned and
427
+ extracts only its observed eight-bit fields. Matrix rows and input words use
428
+ flat `array<u32>` storage so typeless f32/u32 bits survive unchanged.
429
+ Structured loads compute `arrayLength / strideWords`, clamp the eagerly
430
+ evaluated physical word access, and select zero for an out-of-range structure
431
+ index. Each scalar structured store has its own
432
+ `address < arrayLength(&u0)` branch and is dropped when out of range.
433
+ Offset-plus-swizzle accesses beyond a declared stride fail closed.
434
+
435
+ The DX12 SM5.1 comparison shader remains comparison-only: its unbounded
436
+ space1/space2 descriptor ranges do not provide the fixed WebGPU binding
437
+ contract used by the DX11 profile. The native browser gate validates the
438
+ 64×1×1 module, four-entry compute bind-group layout, pipeline layout, and
439
+ compute pipeline without widening the public render-only device API or
440
+ dispatching work.
441
+
442
+ *Checked against vkd3d-shader:* its compute builtin maps to the global
443
+ invocation identifier, its raw/structured buffers flatten to scalar words, and
444
+ its structured loads/stores use direct backend accesses. Its Vulkan path
445
+ relies on runtime robustness for physical out-of-bounds behavior; the explicit
446
+ WGSL load-zero/store-drop guards above independently preserve the D3D result.
447
+
448
+ The full corpus transition moved from 506 qualified / 31 unsupported / 0
449
+ failed to 507 / 30 / 0: only `system/raytracing/skinvertices` became
450
+ qualified, and direct byte comparison confirmed all 506 previously qualified
451
+ packages remained identical.
452
+
453
+ ### Bounded 256×1×1 two-word particle sort step
454
+
455
+ An isolated SM5.0/SM5.1 compute profile covers
456
+ `particles/gpu/sortstep`. Both backends declare the same finite space-zero
457
+ resources: immediate `cb3` with one vec4 row, typed uint buffer `t0`,
458
+ non-coherent structured UAV `u0` with an 8-byte stride,
459
+ `input_thread_group_id.x`, `input_thread_id_in_group.x`, two temporary
460
+ registers, and `dcl_thread_group 256,1,1`. The SM5.1 form additionally requires
461
+ canonical finite range-zero encodings; range-relative `cb0[3]` is normalized
462
+ back to physical `cb3`, not treated as a different binding. The exact 20-opcode
463
+ body has two nested selections and no loops, barriers, atomics, workgroup
464
+ memory, textures, samplers, or live register merges. Declaration, operand,
465
+ modifier, extension, binding-range, CFG, SSA, and type metadata are replayed
466
+ and compared before emission.
467
+
468
+ The two compute builtins map, in fixed order, to
469
+ `@builtin(workgroup_id) workgroup_id` and
470
+ `@builtin(local_invocation_id) local_invocation_id`. Their x components form
471
+ the global scalar lane used by the source; neither y nor z is read. Integer
472
+ arithmetic remains raw wrapping u32, including the profile's sole source
473
+ modifier: integer `NEG` is emitted as two's-complement negation rather than
474
+ floating negation. The pair comparison bitcasts the second word of each record
475
+ to f32, so ordinary WGSL `<` also preserves the source's false result for NaN.
476
+
477
+ The fixed `t0[3]` typed-buffer read uses a clamped physical word access and
478
+ selects zero when index 3 is out of range. The scalar-word view is not inferred
479
+ from the DXBC return tuple alone: the already-qualified `setsortargs` producer
480
+ publishes the same `SortParameters` binding as a typed scalar uint UAV and
481
+ writes its four words individually. The consumer therefore uses
482
+ `var<storage, read> t0: array<u32>` with `minBindingSize: 4`.
483
+
484
+ The UAV is `var<storage, read_write> u0: array<u32>` with
485
+ `minBindingSize: 8`. Structured loads divide `arrayLength` by two to obtain the
486
+ complete-record count, clamp both eagerly evaluated physical word accesses,
487
+ and select two zero words for an out-of-range record. Each two-word structured
488
+ store has its own complete-record bounds branch and is dropped as one source
489
+ instruction when out of range.
490
+
491
+ DX11 and DX12 are both substantive comparison inputs for this profile and emit
492
+ the same WGSL and portable binding layout after finite-range normalization.
493
+ The native browser gate validates the two-builtin 256×1×1 module, its compute
494
+ bind-group and pipeline layouts, and the compute pipeline with zero WGSL
495
+ warnings.
496
+
497
+ The full corpus transition moved from 507 qualified / 30 unsupported / 0
498
+ failed to 508 / 29 / 0: only `particles/gpu/sortstep` became qualified, and
499
+ direct byte comparison confirmed all 507 previously qualified packages
500
+ remained identical.
501
+
502
+ ### Bounded 256×1×1 shared-memory particle bitonic merge
503
+
504
+ An isolated SM5.0/finite-SM5.1 profile covers
505
+ `particles/gpu/sortinner`. Both forms declare a typed uint Buffer `t0`, a
506
+ non-coherent structured UAV `u0` with an 8-byte stride, flattened and vector
507
+ local-thread identifiers, `workgroup_id.x`, three temporary registers,
508
+ structured thread-group memory `g0` with 512 8-byte records, and
509
+ `dcl_thread_group 256,1,1`. The flattened local identifier and
510
+ `input_thread_id_in_group.x` both map to `local_invocation_id.x` because the
511
+ admitted group shape is exactly 256×1×1.
512
+
513
+ The exact 61-opcode body has eight no-else selections and one uniform
514
+ nine-iteration loop. Its two `sync` instructions must carry exactly
515
+ `threads_in_group | thread_group_shared_memory`; the second executes once per
516
+ loop iteration, so each invocation dynamically reaches ten workgroup
517
+ barriers. The profile replays CFG, SSA, and scalar types, requires the exact
518
+ loop-carried signed stride merge and both integer `NEG` source modifiers, and
519
+ rejects declaration or executable tail words. A dedicated uniform WGSL
520
+ `stride` variable and immutable loop-exit condition avoid carrying the earlier
521
+ varying `r0.w` value into barrier control flow.
522
+
523
+ `g0` lowers to `var<workgroup> g0: array<u32, 1024>`, exactly 4 KiB. `t0`
524
+ and `u0` reuse the scalar-word and complete two-word-record contracts proven
525
+ for SetSortArgs and SortStep. External structured loads return a complete zero
526
+ record when physically out of range, and external stores drop the complete
527
+ record. The logical active count is not clamped to the physical `u0` length:
528
+ an out-of-range zero record can participate in the network and move into a
529
+ physically present slot, so pre-clamping would change defined robust-buffer
530
+ behavior.
531
+
532
+ The loop is a nine-stage compare/exchange (bitonic-merge) network, not a
533
+ standalone general sort of arbitrary input. For each stride it partitions the
534
+ 512 workgroup records into disjoint pairs, compares the f32 key in word one,
535
+ and swaps both words when the high key is less than the low key. NaN therefore
536
+ keeps the source's false comparison result. The surrounding particle-sort
537
+ schedule supplies the bitonic input relationship; replacing this body with a
538
+ library sort would implement a stronger and different operation.
539
+
540
+ Shared-memory initialization has an explicit runtime orchestration premise.
541
+ SetSortArgs must complete first, the same `SortParameters` buffer range must be
542
+ usable as storage and indirect-dispatch data, its first three words must
543
+ dispatch exactly `D = ceil(max(N, 1) / 512)` groups, and SortInner must read
544
+ the fourth word, `t0[3]`, as the same `N`. A valid D3D/WebGPU x-dispatch
545
+ dimension has `D <= 65535`, hence `N <= 33,553,920 < 2^31`; for every
546
+ dispatched group and every logical record below the clamped remainder, the
547
+ source's signed outer guard then proves that record was initialized before the
548
+ network reads it.
549
+ The current WebGPU runtime prepares compute pipelines but does not dispatch
550
+ them, so same-buffer usage, ordering, and indirect dispatch remain integration
551
+ obligations rather than locally enforced runtime facts. Outside this premise
552
+ the original DXBC can read uninitialized thread-group memory (for example,
553
+ `N = 0x80000000`, group 1), and this profile makes no equivalence claim for
554
+ that source-undefined path.
555
+
556
+ The native browser gate validates one shared-memory module and both paired
557
+ compute pipelines with zero WGSL warnings. The full corpus transition moved
558
+ from 508 qualified / 29 unsupported / 0 failed to 509 / 28 / 0: only
559
+ `particles/gpu/sortinner` became qualified, and the hardened direct-byte
560
+ comparison confirmed all 508 previously qualified package outputs remained
561
+ identical.
562
+
563
+ ### Exact 256×1×1 shared-memory particle chunk sort
564
+
565
+ An isolated SM5.0/finite-SM5.1 profile covers the full per-workgroup particle
566
+ sort that precedes the later merge passes. It has the same typed uint Buffer
567
+ `t0`, non-coherent stride-8 structured UAV `u0`, three thread identifiers,
568
+ 512 two-word `g0` records, and 256×1×1 group shape as SortInner, but requires
569
+ exactly four temporary registers and its own exact 69-opcode body. Routing
570
+ checks this longer family before SortInner; near siblings still fail closed.
571
+ Declaration tails, body tails, operand selectors, the three integer `NEG`
572
+ sites, load extensions, synchronization flags, finite SM5.1 ranges and
573
+ references, CFG, SSA, scalar types, and both loop-carried merges are all
574
+ replayed or compared before emission.
575
+
576
+ The source begins with a storage-data-dependent `if (N == 0) return`
577
+ immediately before group barriers, which WGSL uniformity analysis cannot prove
578
+ uniform. That return is observably redundant: when `N` is zero the clamped
579
+ logical count is zero, so no external load, shared-memory read, or external
580
+ store executes. The profile validates but omits that three-opcode selection.
581
+ Every invocation can therefore reach the barriers uniformly with the same
582
+ result as the source's all-invocation return.
583
+
584
+ The two source loops are emitted with dedicated uniform state:
585
+ `merge_width`, `merge_done`, `half_width`, `stride`, and `stride_done`.
586
+ `merge_width` visits powers of two from 2 through 512; for each width, `stride`
587
+ visits descending powers of two from half the width through 1. This produces
588
+ 45 compare/exchange stages. The two static `sync` sites require exactly
589
+ `threads_in_group | thread_group_shared_memory`: one follows initialization
590
+ and the other executes after every stage, for 46 dynamic barriers in a
591
+ complete invocation, including the normalized zero-count case.
592
+
593
+ For each stage, every local lane selects one disjoint pair. The first stride
594
+ of a merge width mirrors the high index; later strides use the ordinary merge
595
+ partner. Both words move together, and the second word is bitcast to f32 for
596
+ the `<` comparison. Finite keys are consequently sorted ascending within each
597
+ logical chunk of at most 512 records. Equal keys and signed zero preserve the
598
+ source's false comparison result, as does any comparison involving NaN; no
599
+ stronger total ordering is introduced.
600
+
601
+ `g0` remains the exact 4 KiB `array<u32, 1024>`. The signed difference between
602
+ `N` and the wrapping `workgroup_id.x * 512` base is clamped to `[0, 512]`.
603
+ Every shared record below that count is initialized before the first barrier,
604
+ and the guarded high member of every admitted pair implies that both pair
605
+ members are below the count. All physical shared indices remain in
606
+ `[0, 511]`, and each stage's pairs are disjoint. Shared-memory safety therefore
607
+ does not depend on a dispatch premise.
608
+
609
+ External accesses retain D3D robust-buffer behavior independently. `t0[3]`
610
+ returns zero when absent. A structured `u0` load returns one complete zero
611
+ record when either physical word is absent, while a structured store writes
612
+ both words only when the complete record exists. The logical count is not
613
+ clamped to physical `u0` length, because a robust zero record is allowed to
614
+ participate in the sort and move into a present slot.
615
+
616
+ Runtime orchestration is still responsible for the intended global result:
617
+ the producer must publish the same `N` in `t0[3]`, and the application must
618
+ dispatch the chunk groups that cover that logical range before scheduling
619
+ the subsequent merge profiles. This is a result-coverage obligation, not a
620
+ shared-memory-safety precondition of this profile.
621
+
622
+ The native browser gate validates the shared-memory module and both paired
623
+ compute pipelines with zero WGSL warnings.
624
+
625
+ The full corpus transition moved from 509 qualified / 28 unsupported / 0
626
+ failed to 510 / 27 / 0: only `particles/gpu/sort` became qualified. The
627
+ hardened direct-byte comparator cached and compared all 509 previously
628
+ qualified package outputs, with zero byte changes and zero regressions.
629
+
630
+ ### `float_16` minimum precision → full-precision f32
631
+
632
+ D3D minimum precision is a floor, not a format: an implementation that computes
633
+ `min16float` operands at full 32-bit precision is conforming, and the registers
634
+ are 32-bit regardless of the hint. Numeric/value operands tagged `float_16`
635
+ therefore lower as ordinary f32 lanes — the hint is dropped, which changes
636
+ nothing observable versus a conforming D3D driver running at full precision.
637
+ Resource, sampler, and UAV handles are not value lanes and require default
638
+ precision. The other operand minimum-precision kinds (`float_2_8`, `sint_16`,
639
+ `uint_16`) stay fail-closed until a shader needs them.
640
+
641
+ *Confirmed against vkd3d-shader:* its SPIR-V backend (`spirv.c`) never reads the
642
+ decoded `min_precision` field — arithmetic lowers at full 32-bit width, the same
643
+ promotion. I/O-signature precision is a separate field. This compiler ignores
644
+ it and emits the signature's base 32-bit component type, so valid 10/16-bit
645
+ float or integer minima are conformingly widened; reserved or unknown
646
+ signature-precision values are not yet rejected.
647
+
648
+ ### Exact 256×1×1 atomic histogram merge
649
+
650
+ An isolated SM5.0/finite-SM5.1 profile covers
651
+ `postprocess/mergehistograms`. Both forms declare immediate one-row `cb0`,
652
+ typed uint Buffer `t0`, a non-coherent typed uint UAV `u0`,
653
+ `input_thread_id_in_group.x`, `input_thread_id.x`, four temporary registers,
654
+ 64 stride-4 thread-group records in `g0`, and
655
+ `dcl_thread_group 256,1,1`. The SM5.1 declarations and body references must
656
+ use canonical finite singleton range-zero metadata. The exact 35-opcode body,
657
+ operand selectors and immediates, SM5.0 typed-load extensions, both barrier
658
+ payloads, CFG, SSA, scalar types, and the loop-carried bin index are replayed
659
+ or compared before emission. Selection occurs before the generic compute
660
+ lowerer, so malformed near siblings fail closed.
661
+
662
+ `g0` lowers to
663
+ `var<workgroup> g0: array<atomic<u32>, 64>`. Local lanes 0 through 63
664
+ atomically initialize one bin each, all 256 invocations execute an
665
+ unconditional `workgroupBarrier`, active global invocations accumulate 16
666
+ `uint4` input records with four `atomicAdd` operations per loop iteration,
667
+ and every invocation executes the second unconditional barrier. Local lanes
668
+ 0 through 63 then atomically add the shared totals to `u0`. The source
669
+ barriers must carry exactly
670
+ `threads_in_group | thread_group_shared_memory`; moving either barrier under
671
+ one of the surrounding varying conditions is rejected.
672
+
673
+ The typed Buffer is represented as `var<storage, read> t0: array<u32>`.
674
+ The source record address is preserved as the wrapping sequence
675
+ `(global_invocation_id.x << 6) >> 2`, and each logical `uint4` load is admitted
676
+ only when all four physical words exist. A missing or partial record therefore
677
+ produces four zero lanes, matching typed-buffer OOB load semantics without an
678
+ eager WGSL access outside the scalar backing array. The typed UAV is
679
+ `var<storage, read_write> u0: array<atomic<u32>>`; its final atomic executes
680
+ only when the local bin index is below `arrayLength(&u0)`, so an OOB source
681
+ atomic is dropped.
682
+
683
+ The intended histogram result has explicit runtime premises. `cb0.x` and
684
+ `cb0.y` must be finite non-negative integer-valued f32 dimensions representable
685
+ as u32, because the profile's `ftou` adaptation claims equivalence only on
686
+ that domain. Their low-u32 product is the active global-invocation count.
687
+ Dispatch must cover that count, `t0` must provide the intended 64 bins for
688
+ each active invocation, and `u0` must provide at least 64 elements (normally
689
+ initialized to zero for a fresh result). The explicit robust guards remain
690
+ memory-safe outside the physical `t0`/`u0` size premises, but undersized
691
+ bindings intentionally produce the source's zero-load/drop-atomic behavior
692
+ rather than the complete intended histogram.
693
+
694
+ The substantive DX11 and DX12 shader pair passes the real comparison matrix
695
+ and emits byte-identical WGSL after finite-range normalization. The native
696
+ browser gate validates both paired compute pipelines with zero WGSL warnings.
697
+
698
+ The full corpus transition moved from 510 qualified / 27 unsupported / 0
699
+ failed to 511 / 26 / 0: only `postprocess/mergehistograms` became qualified.
700
+ The hardened direct-byte comparator cached and compared all 510 previously
701
+ qualified package outputs, with zero byte changes and zero regressions.
702
+
703
+ ### Exact 16×16×1 atomic histogram creation
704
+
705
+ An isolated dual-validator profile covers `postprocess/createhistograms`.
706
+ Both backends declare immediate one-row `cb0`, float `texture2d` `t0`, a
707
+ non-coherent typed uint Buffer UAV `u0`, two-component workgroup, local, and
708
+ global invocation identifiers, 64 stride-4 thread-group records in `g0`, and
709
+ `dcl_thread_group 16,16,1`. The SM5.0 form has three temporary registers and
710
+ converts `cb0.z` inside its final selection. The finite-range SM5.1 form has
711
+ four temps and hoists that conversion ahead of the first selection. Each
712
+ literal 49-instruction schedule has its own exact opcode, operand, modifier,
713
+ extension, range/reference, CFG, SSA, and type validator; both feed one
714
+ canonical emitter only after validation.
715
+
716
+ Canonical emission hoists the pure uniform `ftou(cb0.z)` conversion. This is
717
+ equivalent on the admitted runtime domain: `ScreenTilesX` must be finite,
718
+ non-negative, integer-valued, and representable as u32. The conversion has no
719
+ side effect, changes no branch or barrier participation, and its value is used
720
+ only by the final output address. Evaluating it for all 256 lanes instead of
721
+ the 16 output lanes is therefore unobservable on that domain. NaN and
722
+ out-of-range conversion remain outside this claim.
723
+
724
+ The entry point preserves the ordered source identities as
725
+ `workgroup_id`, `local_invocation_id`, and `global_invocation_id`.
726
+ `g0` is `var<workgroup> array<atomic<u32>, 64>`. The first 64 flattened
727
+ local lanes initialize it with `atomicStore`, all lanes execute the first
728
+ unconditional `workgroupBarrier`, and in-range pixels atomically increment one
729
+ bin. All lanes then execute the second unconditional barrier before the first
730
+ 16 lanes atomically load four bins each. Both source barriers must carry
731
+ exactly `threads_in_group | thread_group_shared_memory`; moved, conditional,
732
+ or differently flagged barriers fail closed.
733
+
734
+ Mip-zero `resinfo_uint` becomes `textureDimensions(t0, 0)`. The explicit
735
+ global-coordinate test surrounds the source pixel path, while the load itself
736
+ retains the texture contract's safe coordinate and zero-result selection so
737
+ no eager out-of-range `textureLoad` can be formed. The RGB transfer curve,
738
+ luminance dot product, base-two logarithms/exponent, natural-log conversion,
739
+ and source `div_sat` remain in their original f32 order and bit-exact
740
+ constants. The resulting signed bin is additionally guarded by
741
+ `0 <= bin && bin < 64` before `atomicAdd`. That guard is redundant for the
742
+ admitted finite path but contains WGSL memory access on adapted numeric edges;
743
+ D3D otherwise makes all TGSM undefined for an out-of-range shared atomic.
744
+
745
+ Each workgroup emits 16 typed `uint4` records. The wrapping address is
746
+ `((workgroup_id.y * ScreenTilesX + workgroup_id.x) << 4) + local_index`.
747
+ The atomic-word `u0` representation tests the complete typed element against
748
+ `arrayLength(&u0) / 4` before issuing all four `atomicStore` calls. An
749
+ out-of-range or physically partial element therefore writes nothing, never a
750
+ partial record. This is the exact 64-bin layout consumed by
751
+ `postprocess/mergehistograms`.
752
+
753
+ The intended numeric result additionally requires finite `MinLuminance` and
754
+ `MaxLuminance`, `MaxLuminance > MinLuminance`, and finite intermediate
755
+ normalization arithmetic. In particular, every executed pixel path admitted
756
+ by this claim must produce a finite positive `luminance`, a finite
757
+ `log_luminance`, and a finite `normalized_luminance` before the multiply and
758
+ `ftoi`; the value presented to `ftoi` is consequently in `[0, 64]` and within
759
+ the i32 conversion range. This explicitly excludes zero/negative luminance,
760
+ NaN, infinity, and any overflow or invalid intermediate from the equivalence
761
+ claim. Ordinary finite `_sat` maps exactly to WGSL
762
+ `clamp(..., 0.0, 1.0)`; D3D's special NaN-to-zero saturation result remains
763
+ the existing documented non-finite adaptation.
764
+
765
+ For every non-empty dispatch, `ScreenTilesX` must equal the dispatched
766
+ x workgroup count as well as being the output row stride, and the y workgroup
767
+ count must cover the intended source texture rows. The equality prevents an
768
+ x workgroup beyond the declared row width from aliasing a later row's output
769
+ records; a dispatch/stride mismatch and its resulting output collision are
770
+ outside the equivalence claim. `u0` must contain the intended complete output
771
+ records. Physical texture and UAV undersizing remains memory-safe through the
772
+ explicit guards, but cannot produce the complete intended histogram.
773
+
774
+ The substantive DX11 and DX12 pair passes the real comparison matrix and
775
+ emits byte-identical WGSL after schedule and finite-range normalization. The
776
+ native browser gate validates the shared module and both paired compute
777
+ pipelines with zero WGSL warnings.
778
+
779
+ The full corpus transition moved from 511 qualified / 26 unsupported / 0
780
+ failed to 512 / 25 / 0: only `postprocess/createhistograms` became qualified.
781
+ The hardened direct-byte comparator confirmed all 511 common previously
782
+ qualified package outputs remained byte-identical.
783
+
784
+ ### Exact two-pass particle clear with effect-proven signed counter
785
+
786
+ An effect-level profile covers `managed/space/specialfx/particles/gpu/clear`.
787
+ It requires exactly `Main.pass0.compute` and `Main.pass1.compute`, each as the
788
+ only active stage in its pass. Reflection must identify pass 0 `u0` and pass 1
789
+ `u1` as the same one-element `ParticleCounters` UAV with Carbon type 10,
790
+ alongside pass 1 stride-4 `DeadBuffer` `u0` and stride-32 `ParticleBuffer`
791
+ `u2`. Both companion IR programs are validated before an opaque,
792
+ program-identity-bound proof is minted. A standalone pass-0 shader can never
793
+ select this profile: its signed typed-store declaration alone does not prove
794
+ the bound view is `R32_SINT`, and a missing, forged, or differently decoded
795
+ program proof fails closed. Selecting only pass 0 for package output remains
796
+ safe because preflight still examines the complete resolved effect.
797
+
798
+ Pass 0 has exact SM5.0 and finite-range SM5.1 validators for its signed typed
799
+ `u0`, `1x1x1` group, and two-instruction body. Under the effect proof it emits
800
+ `array<atomic<i32>>` with a four-byte minimum binding and
801
+ `atomicStore(&u0[0u], 0i)`. The same opaque policy gates binding-plan
802
+ construction and final lowering, so signed typed UAV layout is not admitted
803
+ as a general store feature.
804
+
805
+ Pass 1 independently proves the signed 32-bit view through its exact returned
806
+ `imm_atomic_iadd` on signed typed `u1[0]`. Its separate SM5.0 and finite-range
807
+ SM5.1 validators require the literal 26-opcode schedule, immediate `cb3`,
808
+ three UAV identities and strides, scalar flattened local index, two temps,
809
+ `16x16x1` group, both structured loops, lane-zero tail selection, operand
810
+ selectors and immediates, ranges/references, CFG, SSA, scalar types, and the
811
+ two loop-carried merges. `cb3[0].x` is read as raw bits with `bitcast<u32>`.
812
+ The complete-block index stays source-shaped as
813
+ `insertBits(local_invocation_index, block_index, 8u, 24u)`.
814
+
815
+ Together the 256 lanes visit every index in `[0, count)` exactly once: all
816
+ complete 256-record blocks run in the first loop, then lane zero visits the
817
+ remainder. Each visit first attempts both source-ordered `ParticleBuffer`
818
+ stores under a complete stride-32 record guard, then executes
819
+ `atomicAdd(&u1[0u], 1i)`, bitcasts the returned old signed value to the
820
+ dead-list `u32` index, and independently guards the stride-4 `DeadBuffer`
821
+ store. A short particle buffer therefore does not suppress the counter
822
+ increment or dead-list attempt, and a short dead-list buffer drops only its
823
+ own store. No barrier is introduced.
824
+
825
+ The intended dispatch uses one `1x1x1` pass-0 workgroup, requires its reset to
826
+ complete and become visible before pass 1, then uses exactly one `16x16x1`
827
+ pass-1 workgroup with no concurrent counter users and at least `count`
828
+ complete records in both structured buffers. Extra pass-0 workgroups only
829
+ repeat the same zero store, but extra pass-1 workgroups repeat the entire
830
+ traversal and append duplicates. External consumers must wait for pass 1 to
831
+ complete. Practical counts must also fit the application's watchdog budget.
832
+ Explicit store guards remain memory-safe for undersized buffers, but the final
833
+ counter still reaches `count`, matching the source's per-operation
834
+ dropped-write behavior rather than claiming a complete result.
835
+
836
+ The substantive DX11 and DX12 effect pair passes the real effect-level matrix:
837
+ both passes are ready and emit byte-identical WGSL after range normalization.
838
+ The intentionally standalone matrix view keeps pass 0 unsupported while pass
839
+ 1 is independently emitted. The engine's fail-closed matrix validator
840
+ reconciles the exact two-pass body, stage digests, occurrence counts, per-key
841
+ coverage, reset WGSL, and signed atomic layout before admitting that contextual
842
+ pass. Its required native WebGPU gate compiled one unique independently
843
+ emitted module and prepared four compute pipelines for the paired backends
844
+ with zero WGSL warnings.
845
+
846
+ The full corpus transition moved from 512 qualified / 25 unsupported / 0
847
+ failed to 513 / 24 / 0: only
848
+ `managed/space/specialfx/particles/gpu/clear` became qualified. The hardened
849
+ direct-byte comparator confirmed all 512 common previously qualified package
850
+ outputs remained byte-identical, with zero regressions.
851
+
852
+ ### Exact 16×16×1 shared-memory particle emit
853
+
854
+ A dedicated SM5.0 profile covers
855
+ `managed/space/specialfx/particles/gpu/emit`. Admission is two-layered:
856
+ the exact declaration family (immediate `cb3` of 4096 vec4s, stride-32
857
+ `ParticleBuffer` UAV, signed typed counter buffer, stride-4 `DeadBuffer`,
858
+ flattened/vector local identifiers, `workgroup_id`, 112-byte raw TGSM, and a
859
+ `16x16x1` group) plus a browser-safe SHA-256 semantic digest of the complete
860
+ normalized program. The digest implementation is proven against `node:crypto`
861
+ on known and varied vectors and hardened against property aliasing, sparse
862
+ arrays, accessors, prototypes, and unknown semantic fields. SM5.1 is
863
+ recognized by the same digest family and then deliberately rejected as
864
+ comparison-only; only the literal SM5.0 schedule may emit WGSL.
865
+
866
+ The lowered body keeps the audited execution shape: lane-zero TGSM
867
+ initialization is followed by a uniform `workgroupBarrier()`; the returned
868
+ signed `atomicAdd(u1[0u], -1i)` stays ordered before its `old - 1` signed
869
+ success test; DeadBuffer reads use a clamped physical load with an explicit
870
+ zero fallback; cbuffer rows use clamped physical access with zero selection;
871
+ raw TGSM accesses stay within the initialized words; and the final
872
+ `ParticleBuffer` write is guarded as one complete eight-word record. Source
873
+ mapping covers the executable schedule, omitting only structural closure
874
+ instructions.
875
+
876
+ The signed typed-UAV layout is owned by one self-proving policy
877
+ (`particleEmitSignedAtomicLayoutPolicy`): candidacy itself — the declaration
878
+ family and semantic digest — is the whole proof, and binding-plan
879
+ construction and the lowerer consume the same function, so no identity list
880
+ is duplicated or admitted generally. Trusted admission constants may only be
881
+ regenerated by the fixture generator from the audited effect bytes; it
882
+ verifies the effect and internal DXBC SHA-256 hashes and fails closed on any
883
+ other input.
884
+
885
+ The genuine paired effects are exercised through the optional
886
+ `CJS_PARTICLE_EMIT_DX11_EFFECT` / `CJS_PARTICLE_EMIT_DX12_EFFECT` test
887
+ inputs. With both fixtures present the suite runs with zero skips: the DX11
888
+ package qualifies with its `Main.pass0` ready, the DX12 counterpart reports
889
+ the comparison-only boundary, and the required native WebGPU gate compiled
890
+ the one unique emitted module and prepared its compute pipeline with zero
891
+ WGSL warnings.
892
+
893
+ The full corpus transition moved from 506 qualified / 31 unsupported / 0
894
+ failed to 507 / 30 / 0: only
895
+ `managed/space/specialfx/particles/gpu/emit` became qualified, and direct
896
+ comparison confirmed all 506 common previously qualified package outputs
897
+ remained byte-identical.
898
+
899
+ ### The exact compute-profile set is complete and frozen
900
+
901
+ The emit profile completes the audited GPU particle pipeline
902
+ (`setdrawparameters`, `setsortargs`, `sortstep`, `sort`, `sortinner`,
903
+ `createhistograms`, `mergehistograms`, `clear`, `emit`) alongside structured
904
+ skinning. No further package-specific exact compute profiles should be added:
905
+ remaining or future compute coverage (for example `particles/gpu/update` or
906
+ `computelightlists`) must instead extend the general typed IR path with
907
+ reusable thread-group memory, barrier, atomic, and loop lowering, retiring
908
+ profile-by-profile growth. Exact profiles remain pinned to their audited
909
+ bytecode, so any game-build shader recompile demotes the affected package to
910
+ unsupported until it is re-audited — a corpus rebuild after a build bump is
911
+ the standing re-qualification gate.
912
+
913
+ ### Fail-closed intentional nontermination: `system/crash`
914
+
915
+ `system/crash` is not a bounded compute candidate. Without the required
916
+ sentinel its loop deliberately traverses all `2^32` indices and never
917
+ terminates; adding an iteration cap would change observable semantics.
918
+ Multiple workgroups also race rather than preserving the intended
919
+ exactly-one-group behavior. Because the package proves neither the sentinel
920
+ nor the exact-one-group runtime contract, the shader remains permanently
921
+ fail-closed.
922
+
923
+ ### Resource handles → fixed, unmodified identities
924
+
925
+ Every supported resource, sampler, or UAV role requires the declared handle
926
+ type, default minimum precision, no source modifier, and a fixed descriptor
927
+ identity within the admitted singleton binding range. Relative identities fail
928
+ closed before binding lookup; a present fixed absolute identity is checked
929
+ against the resolved singleton binding. Legal resource-result swizzles remain
930
+ supported. *Confirmed against vkd3d-shader:* its register and descriptor
931
+ validation likewise restricts modifier types and verifies descriptor indices
932
+ against their declared ranges. This compiler is stricter about relative member
933
+ indices because its binding layout deliberately supports singleton ranges only.
934
+
935
+ ### Typed `Buffer` SRVs require bound-view metadata
936
+
937
+ A render-stage `dcl_resource` with dimension `buffer` declares the component
938
+ class returned by `ld`, but it does not encode the width or conversion rules of
939
+ the bound DXGI view. The same uniform uint declaration can be paired with an
940
+ `R32_UINT` view or an `R32G32B32A32_UINT` view; lowering both to one WGSL
941
+ storage-array element type changes indexing for at least one valid binding.
942
+
943
+ Render typed-buffer SRVs therefore fail closed until trusted bound-view format
944
+ metadata is part of the binding policy, manifest, and compatibility
945
+ fingerprint. A future lowering must derive the physical WGSL element type,
946
+ element stride, D3D missing-channel values, format conversion, and
947
+ `minBindingSize` from that metadata rather than from the declaration return
948
+ token. The bounded compute profiles described above use separately validated
949
+ scalar-word contracts and are not widened by this restriction.
950
+
951
+ The corrective corpus transition moved from 513 qualified / 24 unsupported /
952
+ 0 failed to 506 / 31 / 0. Exactly `exposuredebug`, `highpassfilter`, `taa`,
953
+ `taacopy`, `tonemapping`, `lensflare`, and `lensgrime` were retracted; direct
954
+ comparison confirmed all 506 remaining qualified packages are byte-identical.
955
+ The paired DX11/DX12 matrices retained matching axes and active topology with
956
+ zero front-end failures while moving the affected 111 stage occurrences (72
957
+ DX11 and 39 DX12) from emitted to unsupported.
958
+
959
+ ## Not supported (fail closed)
960
+
961
+ - **Globally non-refactorable shaders** (`dcl_global_flags` without
962
+ `REFACTORING_ALLOWED`) — every operation would be precise; see the Adapted
963
+ entry for why per-op precise is representable but this is not.
964
+ - **DX12 bindless sampled-resource ranges** (`space1` arrays/unbounded
965
+ ranges) — comparison-only limitation under the current DX11 translation target
966
+ (DX11 is the target; DX12 exists to confirm equal results). Needs its own
967
+ audited design if it ever becomes target work.
968
+ - **`imul`/`umul` high-half results** — WGSL has no 32×32→64 multiply
969
+ builtin; only the low-half destination is supported.
970
+ - **Dynamic constant-buffer register selection** (`cbX[dynamic][…]` selecting
971
+ the *buffer*) — only the vector index may be dynamic.
972
+ - **Non-immediate mip levels in `resinfo`**; texture `ld` accepts a dynamic
973
+ address/mip but remains bounded to the resource shapes listed below.
974
+ - **Unknown texture dimensions** (`texturecubearray`, MSAA kinds, …) in
975
+ sampled layouts.
976
+ - **Render typed `Buffer` SRVs without explicit bound-view format metadata** —
977
+ the DXBC declaration identifies a return component class but not whether the
978
+ runtime view is scalar, vector, normalized, integer, or floating point.
979
+ - **Immediate texture offsets** (`sample_controls` / `_aoffimmi`) outside the
980
+ bounded 2D sample family below. In particular, offset texture `ld` and
981
+ non-2D sampling fail closed.
982
+ - **Mutable relative `indexable_temp` registers** (fixed, declared, bounded
983
+ width-four slots are scalarized as described above; any relative shape outside
984
+ the immutable constant-table form still fails closed), and subroutine control flow
985
+ (`call`/`callc`/`label`/`interface_call`) — front-end rejections.
986
+ - **Geometry, hull, and domain stage kinds, plus compute programs outside the
987
+ bounded profile above** — WGSL has no geometry/hull/domain stage. General
988
+ compute resource shapes, thread-group sizes, builtins, control flow, and
989
+ instruction families are not yet lowered. These fail closed per stage kind
990
+ or bounded-profile reason instead of being misreported as malformed records.
991
+ - **Sampler modes other than `default`**, fragment input interpolation modes
992
+ other than `linear` and `linear_noperspective`, minimum-precision operand
993
+ kinds other than `float_16` (which promotes; see Adapted), and vertex system semantics
994
+ outside `SV_Position`/`SV_VertexID`/`SV_InstanceID` (fragment:
995
+ `SV_Position`/`SV_IsFrontFace`, output `SV_Target`).
996
+
997
+ ## Supported mappings
998
+
999
+ ### `sample_d` gradient sampling and integer/rounding opcodes
1000
+
1001
+ `sample_d` lowers to `textureSampleGrad(t, s, coord, ddx, ddy)` (2/3-component
1002
+ spatial gradients by dimension). A 2D-array address consumes three source
1003
+ lanes (xy coordinate plus array layer) but its gradients consume only xy;
1004
+ 3D/cube addresses and gradients consume xyz. Added `imax/imin/umax/umin` (WGSL overloaded
1005
+ `max`/`min`), `ishl`/`ishr` (`<< u32(...)` / `>> u32(...)` — DXBC shift counts
1006
+ cast to the WGSL-required u32), `ineg` (signed negation), `round_ne`
1007
+ (`round`, ties to even), `round_pi` (`ceil`), and the previously handler-only
1008
+ `ult`/`uge` to the applicable stage support sets.
1009
+
1010
+ ### 2D-array sample layers → round-to-nearest-even
1011
+
1012
+ DXBC sampling rounds a floating Texture2DArray layer coordinate to the nearest
1013
+ integer with ties to even, then clamps it to the available layer range. The
1014
+ layer argument therefore lowers as `i32(round(layer))`; WGSL `round` has the
1015
+ same tie rule and WGSL sampling clamps the resulting array index. The spatial
1016
+ xy coordinate stays separate from that layer argument in every supported
1017
+ sample form and in both stages.
1018
+
1019
+ ## Bounded / temporary
1020
+
1021
+ - **Carbon Detail maps → one physical 2D-array texture (fragment stage).**
1022
+ The late resource-transform planner recognizes exactly two logical
1023
+ `Detail1Map`/`Detail2Map` inputs or exactly three consecutive
1024
+ `Detail1Map`/`Detail2Map`/`Detail3Map` inputs. Each must be a scalar,
1025
+ non-sRGB, float4 Texture2D in the same register space, and every shader use
1026
+ must be an unmodified, fixed-register `sample_b` using the same sampler and
1027
+ bias operand. Relative/non-uniform handles, sample offsets, other opcodes,
1028
+ incompatible metadata, incomplete names, missing samples, or ambiguous
1029
+ bindings reject the transform.
1030
+
1031
+ The source IR and semantic parameter names remain unchanged. Physical
1032
+ lowering replaces the inputs with one `texture_2d_array<f32>`, reuses the
1033
+ first input identity, removes the later bindings only from the owning pass,
1034
+ and emits fixed layers 0/1 or 0/1/2. The WGSL set becomes version 3 and
1035
+ carries the complete realization recipe. Every named layer is required; the
1036
+ runtime may use a compatible native array representation or decode all
1037
+ layers to RGBA8, but it may not silently supply a missing layer.
1038
+
1039
+ Exact high (`.sm_depth`) and medium (`.sm_hi`) exhaustive matrices qualify
1040
+ all 160 DX11 bodies of `unpackedskinned_quaddetailv5` and all 32 DX11 bodies
1041
+ of `unpackedskinned_quadheatdetailv5`, with zero failed bodies. Their DX12
1042
+ matrices have the same axes/topology and zero failures; bindless bodies
1043
+ retain the pre-existing comparison-only unbounded-range boundary. The
1044
+ representative non-bindless, PPT-on, unclipped, opaque, debug-off body is
1045
+ body 4 (overlay blend for Detail): Detail falls from 17 source textures to
1046
+ 15 physical textures through a three-layer recipe, while HeatDetail falls
1047
+ from 17 to 16 through a two-layer recipe. All four DX11 and four DX12
1048
+ representative fragment modules across high and medium compile in the
1049
+ browser with zero WGSL warnings.
1050
+
1051
+ The exact-build corpus remains 507 qualified, 30 unsupported, and 0 failed.
1052
+ Twelve package hashes change, all within the static/skinned,
1053
+ packed/unpacked Quad Detail, HeatDetail, and Environment families; the other
1054
+ 495 qualified packages are byte-identical. Every changed package carries
1055
+ exactly one two- or three-layer Detail transform.
1056
+
1057
+ This closes the compiler-side sampled-texture binding limit only. WGSL-set
1058
+ version 3 is not yet accepted by the committed engine reader, and raw module
1059
+ compilation does not prove resource realization or rendering. Runtime
1060
+ support must consume the recipe explicitly before these packages are
1061
+ render-ready.
1062
+ - **Immediate 2D sample offsets** — `sample`, `sample_b`, `sample_d`, and
1063
+ `sample_l` lower their signed `_aoffimmi(u,v,w)` record to WGSL's final
1064
+ constant `vec2<i32>(u, v)` sampling argument. Both APIs apply that
1065
+ texel-space offset before sampler address modes, and both require components
1066
+ in `[-8, 7]`. D3D ignores `w` for a Texture2D, so only `u` and `v` are
1067
+ emitted. Fragment supports all four opcodes; vertex supports the
1068
+ explicit-gradient/LOD pair already legal there. Duplicate or malformed
1069
+ records, offsets on other opcodes, and non-2D resource shapes fail closed.
1070
+ The completed corpus transition kept 497 shaders qualified and intentionally
1071
+ changed exactly seven prior packages: `downsample`, `taa`, the tactical
1072
+ overlay `anchor`, `connector`, `ubershader`, and `velocity` shaders, and
1073
+ `ui/glowtransform`.
1074
+ *Confirmed against vkd3d-shader:* its IR preserves the signed immediate
1075
+ offset on sample instructions, and its SPIR-V, GLSL, and MSL backends pass
1076
+ those constants through as the target sampling operation's constant offset.
1077
+ - **`resinfo` (fragment stage)** — 2D and 3D textures, scalar immediate mip, components x/y
1078
+ (dimensions), z (depth, 3D only), and w (`textureNumLevels`); z rejected
1079
+ for 2D. A non-zero mip is queried through an in-range clamped level and its
1080
+ dimensions are selected to zero when the requested level is out of range,
1081
+ reproducing D3D instead of exposing WGSL's indeterminate out-of-range
1082
+ `textureDimensions` result. `_rcpFloat` reciprocates only dimensions, never
1083
+ the mip count; its specified infinity for zero dimensions shares the
1084
+ non-finite WGSL limitation documented for `rcp` above. Unknown return-type
1085
+ encodings fail closed. Ordinary float saturation is valid in D3D but
1086
+ currently unsupported here; saturation on the uint return mode is invalid
1087
+ because saturation requires a floating-point destination. D3D's zero result
1088
+ for an unbound resource is outside
1089
+ this shader mapping: WebGPU requires every declared binding, and the engine
1090
+ rejects a missing caller resource. A fallback texture cannot reproduce the
1091
+ exact result because WebGPU textures cannot have zero dimensions (and
1092
+ `_rcpFloat` requires infinity for applicable zero dimensions); exact
1093
+ emulation would need explicit bound-state metadata and a selected result.
1094
+ Widen per dimension when a shader needs it.
1095
+ *Confirmed against vkd3d-shader:* `spirv_compiler_emit_resinfo` (spirv.c)
1096
+ emits image-size and mip-level-count queries, pads missing dimension
1097
+ components with zero, applies the resource swizzle, and converts the uint
1098
+ vector to float for the ordinary float form. It also accepts ordinary float
1099
+ saturation after forming that result. It explicitly rejects
1100
+ `VKD3DSI_RESINFO_RCP_FLOAT`; that form here follows the D3D contract
1101
+ independently. vkd3d also issues the size query directly, so our explicit
1102
+ clamped-query/zero-select is the WGSL-specific guard needed to preserve
1103
+ D3D's defined out-of-range result.
1104
+ - **`ld`** — 2D textures (fragment only; original address lanes xy=texel and
1105
+ w=mip, packed into a three-lane u32 WGSL address). Typed-buffer loads are
1106
+ admitted only by bounded compute profiles with a separately validated
1107
+ scalar-word contract.
1108
+ Texture coordinates and mip are clamped to a valid texel for the eagerly
1109
+ evaluated `textureLoad`, then the result is selected to zero unless the
1110
+ original address was fully in range. This excludes WGSL's otherwise
1111
+ permitted live in-bounds texel result for an invalid logical texel address.
1112
+ The zero vector is exact under the current engine contract that these
1113
+ bindings use four-component views (`rgba8unorm` or `rgba8unorm-srgb` today).
1114
+ A future one- or two-component view would require view-channel metadata so
1115
+ the explicit out-of-bounds replacement can reproduce D3D's missing-component
1116
+ defaults (normally alpha one).
1117
+ - **`ld_structured`** — fixed immediate DWORD byte offsets, one scalar
1118
+ address, fixed (non-relative) resource operands. Every word fetch is clamped
1119
+ to valid storage-buffer memory and selected to zero when the structure index
1120
+ is outside `arrayLength / stride`. Offset-plus-swizzle accesses beyond the
1121
+ declared stride fail closed, so D3D's undefined byte-offset-overrun case is
1122
+ never emitted.
1123
+ *Confirmed against vkd3d-shader for address formation and the
1124
+ robustness-dependent OOB mechanism:* its texture `ld` takes coordinates from
1125
+ the resource-dimensional coordinate mask and LOD separately from source lane
1126
+ `w`; texture and raw/structured buffer loads then use direct backend accesses
1127
+ with no compiler-inserted bounds guard. A zero result therefore depends on
1128
+ the applicable target and runtime robustness guarantees (buffer robustness
1129
+ must not be generalized to every image access), and vkd3d-shader alone does
1130
+ not prove exact zero on every target. The explicit WGSL clamps and logical
1131
+ in-range selects above independently implement D3D's zero result without
1132
+ executing an invalid logical access.
1133
+ - **`f16tof32`/`f32tof16`** — per-lane `unpack2x16float`/`pack2x16float`.
1134
+ `f16tof32` is exact for finite normal inputs, but WGSL may flush binary16
1135
+ subnormals and ignore zero sign. `f32tof16` keeps only the low 16 result bits
1136
+ and is exact for finite non-zero inputs representable as normal binary16.
1137
+ Subnormal and zero-sign behavior shares the preceding caveat. For other
1138
+ finite normal-range values D3D requires round-toward-zero while WGSL does not
1139
+ fix a rounding direction; on finite overflow D3D yields signed max-f16 while
1140
+ WGSL permits an indeterminate result. Those inputs are an adapted boundary.
1141
+ - **`udiv` (both stages)** — quotient and remainder lower to WGSL `u32`
1142
+ division and remainder. Immediate divisors whose lanes are all non-zero keep
1143
+ the direct byte-stable `/` or `%` form. Dynamic or possibly-zero divisors use
1144
+ `select(0xffffffffu, a / max(b, 1u), b != 0u)` (and the corresponding `%`
1145
+ form); clamping the eagerly evaluated operation is necessary because WGSL
1146
+ evaluates both `select` alternatives. Both destinations may be written by
1147
+ one instruction when their masks match; mismatched live masks fail closed.
1148
+ A `null` destination does not contribute active source lanes. That shared
1149
+ multi-destination rule also corrects partial-mask `sincos` source lanes:
1150
+ the full-corpus rebuild intentionally changes only the affected WGSL lines
1151
+ in `beaconfx`, `raymarcher`, and `scannerbackground`; the other 494
1152
+ previously qualified packages remain byte-identical.
1153
+ *Confirmed against vkd3d-shader:* its
1154
+ `vsir_program_lower_udiv` comments that "division by zero is well-defined for
1155
+ … UDIV, and returns UINT_MAX", and it emits a `MOVC` selecting `0xffffffff`
1156
+ for both quotient and remainder when the divisor is zero — the same semantic
1157
+ reproduced by the eager-safe WGSL guard.
1158
+ - **Loop merges** — scalar header phis with exactly one entry and one
1159
+ backedge incoming. The entry and backedge use their actual reaching
1160
+ references, including an inherited preheader value. Multi-exit loops resolve
1161
+ and validate one assignment for every live scalar exit phi at every reachable
1162
+ `break` edge.
1163
+ - **Loop-exit (break-join) and header-backedge merges — cross-plan reaching
1164
+ values.** A loop exited only through `break` edges yields phis at the after-
1165
+ `endloop` join; a header phi likewise takes a value back along the latch edge.
1166
+ In both cases the per-edge value is resolved by `reachingRef` — a walk up the
1167
+ dominator chain from the edge's predecessor to the nearest block whose
1168
+ `outputValues` actually define the register. This is necessary because a break
1169
+ predecessor (or latch block) commonly only *inherits* the register: it has no
1170
+ matching entry in its own `outputValues`, while the canonical phi incoming
1171
+ retains the predecessor `blockId` but may reference an upstream definition.
1172
+ The resolved value is accepted when it is (a) an instruction result / program
1173
+ input that dominates the edge; (b) this loop's own header phi (a `var` before
1174
+ the loop / a no-op
1175
+ self-latch); or (c) any other **live** merge phi — an enclosing selection/
1176
+ switch/loop plan declares it as a `var` and `hoistEscapingValues` lifts that
1177
+ declaration to function scope, so the cross-plan read resolves. A non-live phi
1178
+ is never declared and fails closed.
1179
+ - **Switch merges** — break-terminated clauses; at most ONE pass-through
1180
+ incoming (a clause that keeps the prior value); a shared-join planner exists
1181
+ for `if { switch } endif` joins.
1182
+ - **Selection merges** — scalar phis; two-armed regions identify arm tails by
1183
+ edge kind; guaranteed-output tracking intersects arms.
1184
+ - **Observable undefined merge paths** — validation follows the exact
1185
+ references emitted by ordinary selections, switch clauses, shared
1186
+ `if { switch }` joins, loop header entry/backedge assignments, and loop-exit
1187
+ break assignments. Correlation keys include both SSA value identity and
1188
+ component, so two lanes written by one vector comparison are not conflated.
1189
+ Conditions are preserved through acyclic selection paths but cleared across
1190
+ loop backedges/exits, where they may change between iterations. Switch
1191
+ selector correlations are not modeled. Direct instruction uses fail closed
1192
+ except for one lane-exact rule: an undefined carrier consumed by raw bitwise
1193
+ `and` is safe when the sibling lane is the exact SSA condition proven zero on
1194
+ that path (`0 & unknown` is deterministically zero). The proof is repeated
1195
+ independently for every use and lane, requires the canonical unmodified
1196
+ default-precision `and` shape, and is cleared across loop boundaries; other
1197
+ operations, sibling identities, components, modifiers, index reads, and
1198
+ additional uses remain unsupported.
1199
+ - **`gather4`** — front-end lanes reserved, WGSL emission not yet built.
1200
+
1201
+ Unless a mapping states otherwise, ordinary WGSL floating-point operations
1202
+ inherit WGSL's permitted rounding, denormal, and zero-sign behavior plus its
1203
+ finite-math assumption. D3D's prescribed NaN/infinity tables are therefore not
1204
+ portable on those edge inputs.
1205
+
1206
+ ## Adapted — numeric conversion edges
1207
+
1208
+ `ftoi`/`ftou` lower to WGSL `i32(x)`/`u32(x)`. Finite inputs within the target
1209
+ integer range match D3D's truncation toward zero. NaN and positive overflow do
1210
+ not: D3D specifies zero for NaN and the full integer maximum for overflow,
1211
+ whereas WGSL makes the NaN conversion indeterminate and clamps positive
1212
+ overflow to the largest target integer exactly representable by f32
1213
+ (`2147483520` for i32 and `4294967040` for u32). These inputs are an adapted
1214
+ boundary.
1215
+
1216
+ ## Adapted — uniformity
1217
+
1218
+ ### Derivatives / implicit-LOD samples in non-uniform control flow → `diagnostic(off, derivative_uniformity)`
1219
+
1220
+ WGSL forbids screen-space derivatives — the `dpdx*`/`dpdy*` family and the
1221
+ implicit-LOD samples that derive internally (`textureSample` /
1222
+ `textureSampleBias`) — inside **non-uniform** control flow (a branch whose
1223
+ condition can differ between the pixels of a 2x2 quad), because the derivative
1224
+ compares neighbor pixels that may not all be present. `src/core/wgsl/
1225
+ uniformity.js` tags each SSA value uniform or varying; when the fragment lowerer
1226
+ finds one of these operations under a varying-conditioned branch it records
1227
+ `requiresDerivativeUniformityOptOut` on the program, and `emitWgsl` prepends the
1228
+ module-level filter `diagnostic(off, derivative_uniformity);` (a standard WGSL
1229
+ opt-out that Dawn/Tint and Naga both honor — browser-gate confirmed) rather than
1230
+ rejecting the shader.
1231
+
1232
+ Why the directive and not gradient hoisting: the DXBC came from HLSL that relied
1233
+ on **D3D11's permissive divergent-derivative behavior** (non-participating quad
1234
+ lanes yield undefined derivatives). The directive keeps the operation at its
1235
+ original source-level control-flow point; both APIs leave the divergent result
1236
+ nonportable or undefined, and WGSL does not guarantee a particular hardware
1237
+ evaluation strategy. Converting to `textureSampleGrad` with a gradient computed in
1238
+ uniform control flow (hoisting) would substitute a *different* gradient than the
1239
+ one D3D11 used, i.e. be less faithful. The directive is emitted only when the
1240
+ analysis actually detects a non-uniform derivative/sample, and it is visible in
1241
+ the WGSL (with an explanatory comment) plus flagged on the typed program, so the
1242
+ reliance on the opt-out is never silent.
1243
+
1244
+ Soundness of the trigger: constant-buffer and immediate operands are not SSA
1245
+ values. Varying seeds are interpolated fragment inputs (`input[N]`, including
1246
+ `SV_Position`) and, conservatively, all texture sampling/loading and derivative
1247
+ results. This avoids known false negatives but may add the opt-out for a branch
1248
+ whose producer happens to be dynamically uniform; that only broadens where the
1249
+ diagnostic is disabled.
1250
+
1251
+ Loop-exit uniformity **is** modelled: `loopHasNonUniformExit` flags a loop whose
1252
+ exit is non-uniform — a `breakc`/`continuec` with a varying condition, or an
1253
+ unconditional `break`/`continue` guarded by a varying `if`/`switch` (nested loops
1254
+ skipped, as their breaks belong to the inner loop). Per the WGSL uniformity rules
1255
+ such a break taints both the loop body **and every statement after the loop** (the
1256
+ break edges carry non-uniformity to the merge), so the lowerer folds it into a
1257
+ running per-range flow flag: a requires-uniform op inside or below such a loop
1258
+ picks up the opt-out directive. This is what qualifies `system/shadowdepth`,
1259
+ whose top-level `textureSample` follows a loop with a varying-guarded `break` —
1260
+ top-level in the emitted WGSL, but non-uniform per the spec, and rejected by Dawn
1261
+ without the directive.
1262
+
1263
+ Representative implicit-LOD and derivative cases are browser-gated with the
1264
+ directive enabled, while uniform control-flow cases verify that the directive
1265
+ is not emitted unnecessarily.
1266
+
1267
+ ## Selected-effect package provenance
1268
+
1269
+ `BuildEffect` emits selected-effect INFO schema version 2 while the binary
1270
+ CEWGPU container remains version 1. INFO v2 records the WebGPU target,
1271
+ backend-package and translator semantic versions, and a lower-case SHA-256
1272
+ digest computed over the exact compiled-effect input byte view. A conflicting
1273
+ caller digest fails closed. The reader retains legacy selected-effect INFO v1
1274
+ support and rejects unknown INFO schema versions.
1275
+
1276
+ The 0.4.2 strong-provenance checkpoint was metadata/container-only. Its
1277
+ exact-build corpus retained 507 qualified and 30 unsupported packages with no
1278
+ failures; all 537 status/error results were unchanged. Every qualified package
1279
+ changed only in INFO, while all 507 `META`, `ANLS`, and `WGSL` payloads remained
1280
+ byte-identical.
1281
+
1282
+ The 0.4.3 PGRF checkpoint additionally records the complete builder-derived
1283
+ source permutation topology and identity-only unique-body table. Exact build
1284
+ 3444265 again retains 507 qualified and 30 unsupported packages with no
1285
+ failures and all 537 status/error results unchanged. Across the 507 qualified
1286
+ packages:
1287
+
1288
+ - all 1,521 `META`, `ANLS`, and `WGSL` chunks are byte-identical to 0.4.2;
1289
+ - all INFO documents equal 0.4.2 after removing only the PGRF pointer and
1290
+ normalizing the producer/translator versions;
1291
+ - an independent source-byte comparison matches all 507 PGRF documents,
1292
+ covering 8,257 permutation variants and 3,331 unique body identities; and
1293
+ - a full 537-source header audit finds no anomalies across 8,722 permutations,
1294
+ 3,567 unique body records, and 5,155 exact aliases (maximum 972
1295
+ permutations in one source).
1296
+
1297
+ The final DX11/DX12 quads matrix remains qualified with matching active
1298
+ topology and both stages emitted. A new browser gate is not required for this
1299
+ checkpoint because emitted WGSL, analysis, metadata, layouts, and transforms
1300
+ are unchanged; old readers tolerate the additive chunk.
1301
+
1302
+ The 0.4.4 selected-reflection checkpoint adds complete version-15 reflection
1303
+ for the selected body in `RFLX`, with every exact byte vector externalized into
1304
+ canonical deduplicated `RBLB` records. The upstream
1305
+ `@carbonenginejs/runtime-resource/formats/hlsl/portable` v1 validator is rerun after
1306
+ reconstruction; package validation also reconciles INFO source identity,
1307
+ META/PGRF body identity, and every ANLS pass/stage source record. Earlier
1308
+ effect versions retain the legacy package surface. All-body reflection remains
1309
+ unsupported, so source/backend/runtime completeness remain false.
1310
+
1311
+ The exact-build 3444265 corpus remains 507 qualified, 30 unsupported, and zero
1312
+ failed/unqualified. All 537 statuses and errors match 0.4.3. Across the 507
1313
+ qualified packages:
1314
+
1315
+ - all `META`, `PGRF`, `ANLS`, and `WGSL` payloads are byte-identical;
1316
+ - INFO differs only by the 0.4.4 producer/translator versions and its RFLX/RBLB
1317
+ pointer;
1318
+ - all 507 packages contain deterministic, structurally valid RFLX/RBLB;
1319
+ - reconstructing every selected portable document from RFLX/RBLB deep-equals
1320
+ a fresh source reflection, including 16 non-default/non-first bodies; and
1321
+ - the reflected corpus covers 885 techniques, 937 passes, 1,863 stage
1322
+ programs, 4,366 constants, 2,281 resources, 1,027 samplers, 16 UAVs, 1,589
1323
+ render states, and 74,912 exact constant-default bytes.
1324
+
1325
+ A packed-package Chrome smoke builds and reads a real version-15 effect in the
1326
+ browser, resolves all reflected byte references, structured-clones the JSON
1327
+ view, and fails closed after raw blob corruption with no console errors. The
1328
+ engine prepares a newly generated real `quads.sm_hi` CEWGPU package with 10
1329
+ canonical bindings and zero WGSL warnings. The high-tier
1330
+ `unpacked_quadv5.sm_depth` DX11 self-pair remains exhaustively qualified across
1331
+ 480 bodies, 4,480 emitted stage occurrences, and 2,240 ready pass occurrences;
1332
+ the envelope slice changes none of its WGSL.
1333
+
1334
+ The 0.5.0 all-source-reflection checkpoint replaces selected-only RFLX v1
1335
+ output with INFO v3 plus all-unique RFLX v2/RBLB. It records complete portable
1336
+ reflection for every unique version-15 source body while ANLS/WGSL remain
1337
+ selected-backend data. `GetPortableEffectReflection(permutationIndex)` exposes
1338
+ any package permutation as a fresh, format-hlsl-validated document with owned
1339
+ byte payloads. `runtime-resource` owns `Tr2EffectRes` selection, canonical
1340
+ `Tr2Shader` hydration, and the per-index cache; renderer-owned handles remain
1341
+ an engine concern.
1342
+
1343
+ An exhaustive build-3444265 oracle retains 507 qualified, 30 unsupported, and
1344
+ zero failed/unqualified results. For all 507 emitted packages, `META`, `PGRF`,
1345
+ `ANLS`, and `WGSL` are byte-identical to 0.4.4 after the expected INFO/RFLX
1346
+ schema change. Reconstructing all 8,257 permutations deep-equals fresh source
1347
+ reflection across 3,331 unique bodies, 23,949 source programs, 11,549
1348
+ techniques, 11,963 passes, 68,367 constants, 38,335 resources, 10,858
1349
+ samplers, 16 UAVs, and 1,241,456 exact constant-default bytes. The shared
1350
+ RBLB stores 5,901 deduplicated payloads / 20,625,492 bytes. Candidate packages
1351
+ total 132,688,092 bytes versus 39,643,003 bytes at 0.4.4; the median package
1352
+ ratio is 1.008 and p95 is 8.51. A second fresh build using format-hlsl 0.1.8
1353
+ produces all 507 packages byte-for-byte identically.
1354
+
1355
+ The format-hlsl 0.1.8 prerequisite preserves Carbon's authored zero-count
1356
+ bindless descriptor ranges and requires every resource/UAV map entry to match
1357
+ exactly one signature record. The real DX12 High unpacked-Quad source validates
1358
+ all 288 unique bodies, including its zero/unbounded SRV, UAV, and sampler
1359
+ ranges. The exact DX11/DX12
1360
+ `managed/space/spaceobject/v5/quad/unpacked_quadv5.sm_depth` matrix remains
1361
+ qualified across 480 bodies per backend; DX11 emits all 4,480 stage
1362
+ occurrences and 2,240 passes, while DX12's expected unbounded-range backend
1363
+ boundary remains comparison-only for 480 pass occurrences.
1364
+
1365
+ Explicit `SPACE_OBJECT_PPT_ENABLED=SOPPT_ENABLED` body-4 packages were then
1366
+ prepared through the unchanged engine-webgpu reader on a real WebGPU adapter.
1367
+ The DX11 package contains 144 reflected bodies and the DX12 package 288; each
1368
+ selected Main pass exposes two modules and 25 canonical bindings. Both browser
1369
+ gates compile with zero WGSL warnings.
1370
+
1371
+ ## All-body backend packaging
1372
+
1373
+ `mode: "all"` translates every unique source body and stores the result in a
1374
+ `WGSB` `CJS_WGSL_BODY_SET` chunk. Selected mode remains the default and is
1375
+ unchanged.
1376
+
1377
+ The translation unit is deliberately one pass of one body rather than one
1378
+ stage. A pass owns its binding plan and resource-transform plan, so identical
1379
+ stage bytecode can legitimately translate differently when its pass-mates
1380
+ differ; sharing at stage granularity would be unsound. Bodies whose pass
1381
+ carries byte-identical stage bytecode, semantic bindings, and render states
1382
+ therefore share exactly one stored unit. On real uber-shader ship families this
1383
+ is the difference between storing every body's programs and storing a small
1384
+ fraction of them, because most permutation axes change only one stage of one
1385
+ pass.
1386
+
1387
+ A body the compiler cannot lower is retained as an explicitly unsupported
1388
+ record carrying its reason, and coverage degrades to `partial`. Its complete
1389
+ source reflection is untouched, so a partial backend never silently reduces
1390
+ source truth. This is the fail-closed alternative to dropping a body or
1391
+ shrinking the reported shader count.
1392
+
1393
+ Translating every body is deliberately **not** treated as backend completeness.
1394
+ `backendComplete` and `runtimeComplete` remain false, matching the sibling
1395
+ WebGL package, because neither the engine's realization of these layouts and
1396
+ resource transforms nor an exact draw of them has been gated. `INFO` carries
1397
+ the honest scope in `backendBodyCoverage` instead.
1398
+
1399
+ Evidence for the introducing change: the every-permutation reader join resolves
1400
+ every permutation of a real Quad family package to translated programs and
1401
+ reaches every unique body; the selected body's shared units are byte-identical
1402
+ to that package's own `WGSL` chunk; a full corpus rebuild leaves every
1403
+ selected-mode package byte-identical with unchanged statuses; every effect in
1404
+ the corpus builds in all-body mode with no build failures, the small number of
1405
+ partial results losing bodies only to already-documented compiler boundaries;
1406
+ and 202 translated modules from bodies that selected-mode packaging never
1407
+ emitted were compiled on a real WebGPU adapter with zero warnings, including a
1408
+ High `.sm_depth` explicit PPT-on Detail-family package with version-3
1409
+ resource-transform units and the exact bounded particle-clear compute profile.
1410
+ That is compiler and module evidence for the newly packaged bodies. It is not a
1411
+ prepared-pipeline or rendered claim for them.
1412
+
1413
+ Two defects in the first implementation were found only by building the whole
1414
+ corpus in all-body mode, not by the package suite: stage selection outside the
1415
+ per-body guard turned one geometry-stage body into a whole-package failure, and
1416
+ the all-body path initially omitted the particle-clear effect-profile
1417
+ preflight. The synthetic fixtures cannot express either condition, so an
1418
+ all-body corpus sweep is part of this feature's verification, not an optional
1419
+ extra.
1420
+
1421
+ ## Verification contract
1422
+
1423
+ Every shader-emission, layout, or transform compatibility change requires the
1424
+ package suite and a representative `engine-webgpu` browser gate on a real
1425
+ WebGPU device with zero WGSL warnings. Format-level qualification cannot detect
1426
+ every WGSL scoping or validator failure, so browser validation remains part of
1427
+ the compiler contract. A reviewed envelope-only schema change may omit a new
1428
+ browser run when corpus comparison proves unchanged status/errors and
1429
+ byte-identical runtime-consumed shader chunks, and downstream reader tolerance
1430
+ is separately confirmed.
1431
+
1432
+ The browser gate proves the emitted WGSL is *valid and runs*; it does not by
1433
+ itself prove the translation is *semantically equivalent to D3D*. Semantic
1434
+ decisions (out-of-bounds behavior, source-modifier typing, minimum-precision,
1435
+ division-by-zero, atomics) are therefore taken from the Direct3D 11 functional
1436
+ specification and independently cross-referenced against
1437
+ [vkd3d-shader](https://gitlab.winehq.org/wine/vkd3d), Wine's DXBC→SPIR-V/GLSL
1438
+ translator, which is the closest independent implementation of the same
1439
+ input. vkd3d is used strictly as a **behavioral reference for verification** —
1440
+ no code is derived from it; this compiler is implemented independently from the
1441
+ D3D specification. (The reference checkout is kept quarantined outside every
1442
+ package, never bundled or published.)