@graphty/webgpu-graph-algorithms 0.5.1 → 0.6.1

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 (243) hide show
  1. package/README.md +459 -58
  2. package/dist/browser.js +1 -1
  3. package/dist/chunks/{context-BR7fx3vR.js → context-BXqgCifx.js} +190 -40
  4. package/dist/chunks/context-BXqgCifx.js.map +1 -0
  5. package/dist/node.js +1 -1
  6. package/dist/src/algorithms/components.d.ts.map +1 -1
  7. package/dist/src/algorithms/components.js +12 -13
  8. package/dist/src/algorithms/components.js.map +1 -1
  9. package/dist/src/algorithms/degree.d.ts +6 -8
  10. package/dist/src/algorithms/degree.d.ts.map +1 -1
  11. package/dist/src/algorithms/degree.js +58 -35
  12. package/dist/src/algorithms/degree.js.map +1 -1
  13. package/dist/src/algorithms/pagerank.d.ts.map +1 -1
  14. package/dist/src/algorithms/pagerank.js +16 -14
  15. package/dist/src/algorithms/pagerank.js.map +1 -1
  16. package/dist/src/algorithms/power-iteration.d.ts +2 -2
  17. package/dist/src/algorithms/power-iteration.d.ts.map +1 -1
  18. package/dist/src/algorithms/power-iteration.js +17 -14
  19. package/dist/src/algorithms/power-iteration.js.map +1 -1
  20. package/dist/src/constants.d.ts +38 -8
  21. package/dist/src/constants.d.ts.map +1 -1
  22. package/dist/src/constants.js +38 -8
  23. package/dist/src/constants.js.map +1 -1
  24. package/dist/src/errors.d.ts +3 -2
  25. package/dist/src/errors.d.ts.map +1 -1
  26. package/dist/src/errors.js +2 -1
  27. package/dist/src/errors.js.map +1 -1
  28. package/dist/src/index.d.ts +6 -4
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +8 -3
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/kernel/dispatch.d.ts +8 -3
  33. package/dist/src/kernel/dispatch.d.ts.map +1 -1
  34. package/dist/src/kernel/dispatch.js +18 -7
  35. package/dist/src/kernel/dispatch.js.map +1 -1
  36. package/dist/src/kernel/kernel.d.ts +30 -1
  37. package/dist/src/kernel/kernel.d.ts.map +1 -1
  38. package/dist/src/kernel/kernel.js +49 -5
  39. package/dist/src/kernel/kernel.js.map +1 -1
  40. package/dist/src/kernel/prelude.d.ts.map +1 -1
  41. package/dist/src/kernel/prelude.js +6 -1
  42. package/dist/src/kernel/prelude.js.map +1 -1
  43. package/dist/src/kernel/profiler.d.ts +15 -3
  44. package/dist/src/kernel/profiler.d.ts.map +1 -1
  45. package/dist/src/kernel/profiler.js +27 -4
  46. package/dist/src/kernel/profiler.js.map +1 -1
  47. package/dist/src/kernels.d.ts +17 -7
  48. package/dist/src/kernels.d.ts.map +1 -1
  49. package/dist/src/kernels.js +323 -16
  50. package/dist/src/kernels.js.map +1 -1
  51. package/dist/src/layouts/calibrate.d.ts +51 -0
  52. package/dist/src/layouts/calibrate.d.ts.map +1 -0
  53. package/dist/src/layouts/calibrate.js +172 -0
  54. package/dist/src/layouts/calibrate.js.map +1 -0
  55. package/dist/src/layouts/force-simulation.d.ts +39 -4
  56. package/dist/src/layouts/force-simulation.d.ts.map +1 -1
  57. package/dist/src/layouts/force-simulation.js +71 -19
  58. package/dist/src/layouts/force-simulation.js.map +1 -1
  59. package/dist/src/layouts/forceatlas2.d.ts +107 -36
  60. package/dist/src/layouts/forceatlas2.d.ts.map +1 -1
  61. package/dist/src/layouts/forceatlas2.js +296 -100
  62. package/dist/src/layouts/forceatlas2.js.map +1 -1
  63. package/dist/src/layouts/fruchterman-reingold.d.ts +73 -27
  64. package/dist/src/layouts/fruchterman-reingold.d.ts.map +1 -1
  65. package/dist/src/layouts/fruchterman-reingold.js +230 -70
  66. package/dist/src/layouts/fruchterman-reingold.js.map +1 -1
  67. package/dist/src/layouts/model-common.d.ts +41 -3
  68. package/dist/src/layouts/model-common.d.ts.map +1 -1
  69. package/dist/src/layouts/model-common.js +74 -3
  70. package/dist/src/layouts/model-common.js.map +1 -1
  71. package/dist/src/layouts/repulsion-grid.d.ts +152 -0
  72. package/dist/src/layouts/repulsion-grid.d.ts.map +1 -0
  73. package/dist/src/layouts/repulsion-grid.js +318 -0
  74. package/dist/src/layouts/repulsion-grid.js.map +1 -0
  75. package/dist/src/layouts/spring-electrical.d.ts +75 -30
  76. package/dist/src/layouts/spring-electrical.d.ts.map +1 -1
  77. package/dist/src/layouts/spring-electrical.js +231 -74
  78. package/dist/src/layouts/spring-electrical.js.map +1 -1
  79. package/dist/src/memory/residency.d.ts +6 -2
  80. package/dist/src/memory/residency.d.ts.map +1 -1
  81. package/dist/src/memory/residency.js +84 -14
  82. package/dist/src/memory/residency.js.map +1 -1
  83. package/dist/src/primitives/core-shape.d.ts +38 -2
  84. package/dist/src/primitives/core-shape.d.ts.map +1 -1
  85. package/dist/src/primitives/core-shape.js +71 -3
  86. package/dist/src/primitives/core-shape.js.map +1 -1
  87. package/dist/src/primitives/grid-pyramid.d.ts +71 -0
  88. package/dist/src/primitives/grid-pyramid.d.ts.map +1 -0
  89. package/dist/src/primitives/grid-pyramid.js +143 -0
  90. package/dist/src/primitives/grid-pyramid.js.map +1 -0
  91. package/dist/src/primitives/grid.d.ts +118 -0
  92. package/dist/src/primitives/grid.d.ts.map +1 -0
  93. package/dist/src/primitives/grid.js +225 -0
  94. package/dist/src/primitives/grid.js.map +1 -0
  95. package/dist/src/primitives/histogram.d.ts +67 -0
  96. package/dist/src/primitives/histogram.d.ts.map +1 -0
  97. package/dist/src/primitives/histogram.js +190 -0
  98. package/dist/src/primitives/histogram.js.map +1 -0
  99. package/dist/src/primitives/radix-sort.d.ts +75 -0
  100. package/dist/src/primitives/radix-sort.d.ts.map +1 -0
  101. package/dist/src/primitives/radix-sort.js +168 -0
  102. package/dist/src/primitives/radix-sort.js.map +1 -0
  103. package/dist/src/primitives/scan.d.ts +44 -0
  104. package/dist/src/primitives/scan.d.ts.map +1 -0
  105. package/dist/src/primitives/scan.js +151 -0
  106. package/dist/src/primitives/scan.js.map +1 -0
  107. package/dist/src/primitives/segmented-reduce.d.ts +25 -17
  108. package/dist/src/primitives/segmented-reduce.d.ts.map +1 -1
  109. package/dist/src/primitives/segmented-reduce.js +166 -47
  110. package/dist/src/primitives/segmented-reduce.js.map +1 -1
  111. package/dist/src/primitives/spmv.d.ts +18 -14
  112. package/dist/src/primitives/spmv.d.ts.map +1 -1
  113. package/dist/src/primitives/spmv.js +94 -58
  114. package/dist/src/primitives/spmv.js.map +1 -1
  115. package/dist/src/primitives/verify.d.ts +49 -0
  116. package/dist/src/primitives/verify.d.ts.map +1 -0
  117. package/dist/src/primitives/verify.js +229 -0
  118. package/dist/src/primitives/verify.js.map +1 -0
  119. package/dist/src/types/context.d.ts +53 -0
  120. package/dist/src/types/context.d.ts.map +1 -1
  121. package/dist/src/types/layout.d.ts +20 -0
  122. package/dist/src/types/layout.d.ts.map +1 -1
  123. package/dist/src/wgsl/counting-scatter.wgsl.d.ts +8 -0
  124. package/dist/src/wgsl/counting-scatter.wgsl.d.ts.map +1 -0
  125. package/dist/src/wgsl/counting-scatter.wgsl.js +17 -0
  126. package/dist/src/wgsl/counting-scatter.wgsl.js.map +1 -0
  127. package/dist/src/wgsl/fa2-attraction.wgsl.d.ts +23 -11
  128. package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -1
  129. package/dist/src/wgsl/fa2-attraction.wgsl.js +98 -20
  130. package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -1
  131. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +6 -2
  132. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -1
  133. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +22 -1
  134. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -1
  135. package/dist/src/wgsl/grid-cell-key.wgsl.d.ts +8 -0
  136. package/dist/src/wgsl/grid-cell-key.wgsl.d.ts.map +1 -0
  137. package/dist/src/wgsl/grid-cell-key.wgsl.js +30 -0
  138. package/dist/src/wgsl/grid-cell-key.wgsl.js.map +1 -0
  139. package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts +8 -0
  140. package/dist/src/wgsl/grid-centroid-hub.wgsl.d.ts.map +1 -0
  141. package/dist/src/wgsl/grid-centroid-hub.wgsl.js +29 -0
  142. package/dist/src/wgsl/grid-centroid-hub.wgsl.js.map +1 -0
  143. package/dist/src/wgsl/grid-centroid.wgsl.d.ts +8 -0
  144. package/dist/src/wgsl/grid-centroid.wgsl.d.ts.map +1 -0
  145. package/dist/src/wgsl/grid-centroid.wgsl.js +29 -0
  146. package/dist/src/wgsl/grid-centroid.wgsl.js.map +1 -0
  147. package/dist/src/wgsl/grid-downsample.wgsl.d.ts +7 -0
  148. package/dist/src/wgsl/grid-downsample.wgsl.d.ts.map +1 -0
  149. package/dist/src/wgsl/grid-downsample.wgsl.js +28 -0
  150. package/dist/src/wgsl/grid-downsample.wgsl.js.map +1 -0
  151. package/dist/src/wgsl/grid-far-field.wgsl.d.ts +13 -0
  152. package/dist/src/wgsl/grid-far-field.wgsl.d.ts.map +1 -0
  153. package/dist/src/wgsl/grid-far-field.wgsl.js +98 -0
  154. package/dist/src/wgsl/grid-far-field.wgsl.js.map +1 -0
  155. package/dist/src/wgsl/grid-near-field.wgsl.d.ts +19 -0
  156. package/dist/src/wgsl/grid-near-field.wgsl.d.ts.map +1 -0
  157. package/dist/src/wgsl/grid-near-field.wgsl.js +129 -0
  158. package/dist/src/wgsl/grid-near-field.wgsl.js.map +1 -0
  159. package/dist/src/wgsl/histogram.wgsl.d.ts +7 -0
  160. package/dist/src/wgsl/histogram.wgsl.d.ts.map +1 -0
  161. package/dist/src/wgsl/histogram.wgsl.js +15 -0
  162. package/dist/src/wgsl/histogram.wgsl.js.map +1 -0
  163. package/dist/src/wgsl/indirect-finalize.wgsl.d.ts +8 -0
  164. package/dist/src/wgsl/indirect-finalize.wgsl.d.ts.map +1 -0
  165. package/dist/src/wgsl/indirect-finalize.wgsl.js +26 -0
  166. package/dist/src/wgsl/indirect-finalize.wgsl.js.map +1 -0
  167. package/dist/src/wgsl/radix-hist.wgsl.d.ts +9 -0
  168. package/dist/src/wgsl/radix-hist.wgsl.d.ts.map +1 -0
  169. package/dist/src/wgsl/radix-hist.wgsl.js +31 -0
  170. package/dist/src/wgsl/radix-hist.wgsl.js.map +1 -0
  171. package/dist/src/wgsl/radix-scatter.wgsl.d.ts +9 -0
  172. package/dist/src/wgsl/radix-scatter.wgsl.d.ts.map +1 -0
  173. package/dist/src/wgsl/radix-scatter.wgsl.js +40 -0
  174. package/dist/src/wgsl/radix-scatter.wgsl.js.map +1 -0
  175. package/dist/src/wgsl/scan-add.wgsl.d.ts +6 -0
  176. package/dist/src/wgsl/scan-add.wgsl.d.ts.map +1 -0
  177. package/dist/src/wgsl/scan-add.wgsl.js +14 -0
  178. package/dist/src/wgsl/scan-add.wgsl.js.map +1 -0
  179. package/dist/src/wgsl/scan-block.wgsl.d.ts +8 -0
  180. package/dist/src/wgsl/scan-block.wgsl.d.ts.map +1 -0
  181. package/dist/src/wgsl/scan-block.wgsl.js +30 -0
  182. package/dist/src/wgsl/scan-block.wgsl.js.map +1 -0
  183. package/dist/src/wgsl/segmented-reduce.wgsl.d.ts +22 -8
  184. package/dist/src/wgsl/segmented-reduce.wgsl.d.ts.map +1 -1
  185. package/dist/src/wgsl/segmented-reduce.wgsl.js +84 -15
  186. package/dist/src/wgsl/segmented-reduce.wgsl.js.map +1 -1
  187. package/dist/src/wgsl/spmv-pull.wgsl.d.ts +22 -11
  188. package/dist/src/wgsl/spmv-pull.wgsl.d.ts.map +1 -1
  189. package/dist/src/wgsl/spmv-pull.wgsl.js +110 -36
  190. package/dist/src/wgsl/spmv-pull.wgsl.js.map +1 -1
  191. package/dist/tsconfig.build.tsbuildinfo +1 -1
  192. package/dist/webgpu-graph-algorithms.js +3815 -1003
  193. package/dist/webgpu-graph-algorithms.js.map +1 -1
  194. package/package.json +9 -8
  195. package/src/algorithms/components.ts +12 -16
  196. package/src/algorithms/degree.ts +58 -43
  197. package/src/algorithms/pagerank.ts +20 -18
  198. package/src/algorithms/power-iteration.ts +19 -18
  199. package/src/constants.ts +38 -8
  200. package/src/errors.ts +3 -1
  201. package/src/index.ts +14 -4
  202. package/src/kernel/dispatch.ts +18 -7
  203. package/src/kernel/kernel.ts +59 -5
  204. package/src/kernel/prelude.ts +9 -0
  205. package/src/kernel/profiler.ts +28 -4
  206. package/src/kernels.ts +356 -18
  207. package/src/layouts/calibrate.ts +187 -0
  208. package/src/layouts/force-simulation.ts +91 -23
  209. package/src/layouts/forceatlas2.ts +331 -106
  210. package/src/layouts/fruchterman-reingold.ts +255 -74
  211. package/src/layouts/model-common.ts +98 -3
  212. package/src/layouts/repulsion-grid.ts +451 -0
  213. package/src/layouts/spring-electrical.ts +257 -78
  214. package/src/memory/residency.ts +126 -20
  215. package/src/primitives/core-shape.ts +91 -4
  216. package/src/primitives/grid-pyramid.ts +221 -0
  217. package/src/primitives/grid.ts +349 -0
  218. package/src/primitives/histogram.ts +273 -0
  219. package/src/primitives/radix-sort.ts +246 -0
  220. package/src/primitives/scan.ts +197 -0
  221. package/src/primitives/segmented-reduce.ts +214 -56
  222. package/src/primitives/spmv.ts +125 -65
  223. package/src/primitives/verify.ts +249 -0
  224. package/src/types/context.ts +56 -0
  225. package/src/types/layout.ts +22 -0
  226. package/src/wgsl/counting-scatter.wgsl.ts +16 -0
  227. package/src/wgsl/fa2-attraction.wgsl.ts +98 -20
  228. package/src/wgsl/fa2-stats-finalize.wgsl.ts +22 -1
  229. package/src/wgsl/grid-cell-key.wgsl.ts +29 -0
  230. package/src/wgsl/grid-centroid-hub.wgsl.ts +28 -0
  231. package/src/wgsl/grid-centroid.wgsl.ts +28 -0
  232. package/src/wgsl/grid-downsample.wgsl.ts +27 -0
  233. package/src/wgsl/grid-far-field.wgsl.ts +97 -0
  234. package/src/wgsl/grid-near-field.wgsl.ts +128 -0
  235. package/src/wgsl/histogram.wgsl.ts +14 -0
  236. package/src/wgsl/indirect-finalize.wgsl.ts +25 -0
  237. package/src/wgsl/radix-hist.wgsl.ts +30 -0
  238. package/src/wgsl/radix-scatter.wgsl.ts +39 -0
  239. package/src/wgsl/scan-add.wgsl.ts +13 -0
  240. package/src/wgsl/scan-block.wgsl.ts +29 -0
  241. package/src/wgsl/segmented-reduce.wgsl.ts +84 -15
  242. package/src/wgsl/spmv-pull.wgsl.ts +110 -36
  243. package/dist/chunks/context-BR7fx3vR.js.map +0 -1
package/src/kernels.ts CHANGED
@@ -7,8 +7,8 @@
7
7
  * and fa2-speed-finalize (K4) together with every generated block of contract 3.10.2; P2-T2 lands
8
8
  * segmented-reduce; P3-T2 adds fa2-stats-finalize (K1), fa2-attraction (K2), fa2-integrate (K5) and
9
9
  * fa2-to-scene; M8b-T3 adds the seven P7 entries: spmv-pull, pr-scale, pr-finalize, wcc-link-sample,
10
- * wcc-link-edges, wcc-compress and wcc-sample. This file is the only importer of src/wgsl/** (spec 3.2;
11
- * test/layers.test.ts).
10
+ * wcc-link-edges, wcc-compress and wcc-sample. P4-T1 adds indirect-finalize; the other P4 entries follow, one task
11
+ * each (the P4 plan, PD-1). This file is the only importer of src/wgsl/** (spec 3.2; test/layers.test.ts).
12
12
  */
13
13
 
14
14
  import { STATE_HEADER_BYTES } from "./constants.js";
@@ -17,6 +17,7 @@ import { UniformBlock } from "./kernel/struct-block.js";
17
17
  import { type BindingDecl, type OverrideDecl, type WgslModuleSpec } from "./kernel/wgsl.js";
18
18
  import { type CoreBinding } from "./memory/residency.js";
19
19
  import { type Binding } from "./types/memory.js";
20
+ import { countingScatterWgsl } from "./wgsl/counting-scatter.wgsl.js";
20
21
  import { degreeWgsl } from "./wgsl/degree.wgsl.js";
21
22
  import { fa2AttractionWgsl } from "./wgsl/fa2-attraction.wgsl.js";
22
23
  import { fa2IntegrateWgsl } from "./wgsl/fa2-integrate.wgsl.js";
@@ -25,9 +26,21 @@ import { fa2SpeedFinalizeWgsl } from "./wgsl/fa2-speed-finalize.wgsl.js";
25
26
  import { fa2StatsFinalizeWgsl } from "./wgsl/fa2-stats-finalize.wgsl.js";
26
27
  import { fa2ToSceneWgsl } from "./wgsl/fa2-to-scene.wgsl.js";
27
28
  import { fillWgsl } from "./wgsl/fill.wgsl.js";
29
+ import { gridCellKeyWgsl } from "./wgsl/grid-cell-key.wgsl.js";
30
+ import { gridCentroidWgsl } from "./wgsl/grid-centroid.wgsl.js";
31
+ import { gridCentroidHubWgsl } from "./wgsl/grid-centroid-hub.wgsl.js";
32
+ import { gridDownsampleWgsl } from "./wgsl/grid-downsample.wgsl.js";
33
+ import { gridFarFieldWgsl } from "./wgsl/grid-far-field.wgsl.js";
34
+ import { gridNearFieldWgsl } from "./wgsl/grid-near-field.wgsl.js";
35
+ import { histogramWgsl } from "./wgsl/histogram.wgsl.js";
36
+ import { indirectFinalizeWgsl } from "./wgsl/indirect-finalize.wgsl.js";
28
37
  import { prFinalizeWgsl } from "./wgsl/pr-finalize.wgsl.js";
29
38
  import { prScaleWgsl } from "./wgsl/pr-scale.wgsl.js";
39
+ import { radixHistWgsl } from "./wgsl/radix-hist.wgsl.js";
40
+ import { radixScatterWgsl } from "./wgsl/radix-scatter.wgsl.js";
30
41
  import { reduceWgsl } from "./wgsl/reduce.wgsl.js";
42
+ import { scanAddWgsl } from "./wgsl/scan-add.wgsl.js";
43
+ import { scanBlockWgsl } from "./wgsl/scan-block.wgsl.js";
31
44
  import { segmentedReduceWgsl } from "./wgsl/segmented-reduce.wgsl.js";
32
45
  import { spmvPullWgsl } from "./wgsl/spmv-pull.wgsl.js";
33
46
  import { wccCompressWgsl } from "./wgsl/wcc-compress.wgsl.js";
@@ -53,7 +66,20 @@ export type KernelId =
53
66
  | "wcc-link-sample"
54
67
  | "wcc-link-edges"
55
68
  | "wcc-compress"
56
- | "wcc-sample";
69
+ | "wcc-sample"
70
+ | "indirect-finalize"
71
+ | "scan-block"
72
+ | "scan-add"
73
+ | "histogram"
74
+ | "counting-scatter"
75
+ | "radix-hist"
76
+ | "radix-scatter"
77
+ | "grid-cell-key"
78
+ | "grid-centroid"
79
+ | "grid-centroid-hub"
80
+ | "grid-downsample"
81
+ | "grid-far-field"
82
+ | "grid-near-field";
57
83
 
58
84
  /** One registry entry: everything of a WgslModuleSpec except the per-variant overrides and snippets. */
59
85
  export interface KernelEntry {
@@ -68,7 +94,7 @@ export interface KernelEntry {
68
94
  /** The snippet marker names the body carries (segmented-reduce: ["VALUE"]). */
69
95
  readonly snippetSlots: readonly string[];
70
96
  /** The phase the entry landed in (documentation and the compile-matrix filter). */
71
- readonly phase: "P1" | "P2" | "P3" | "P7";
97
+ readonly phase: "P1" | "P2" | "P3" | "P4" | "P7";
72
98
  }
73
99
 
74
100
  // ---- the generated blocks (spec 5.3; contract 3.10.2): field order = byte order, offsets in the JSDoc
@@ -101,7 +127,7 @@ export const FILL_PARAMS: UniformBlock = UniformBlock.define("FillParams", [
101
127
  ["pad0", "u32"],
102
128
  ]);
103
129
 
104
- /** `Fa2Params` (uniform, 128 B; spec 7.3): the per-iteration ForceAtlas2 parameters -- `n` @0, `dim` @4, `flags` @8 (bit 0 = FA2_FLAG_FIRST), `tierStart` @12, `tierEnd` @16, `iterationIndex` @20, `seed` @24, `nearMax` @28, `scalingRatio` @32, `gravity` @36, `jitterTolerance` @40, `scale` @44, `center` @48 (xyz, w 0), `settleThreshold` @64, `extentFactor` @68, `gridMax` @72, `levels` @76, `pad` @80 (reserved for the P4 GridSpec); the P5 model fields (PD-3): `frK` @96 (the FR optimal distance), `temperature` @100 (the FR temperature of this iteration), `springLength` @104, `springCoefficient` @108, `coulomb` @112 (ngraph's `gravity`, negative repels), `dragCoefficient` @116, `timeStep` @120, `pad1` @124; 128 B. */
130
+ /** `Fa2Params` (uniform, 128 B; spec 7.3): the per-iteration ForceAtlas2 parameters -- `n` @0, `dim` @4, `flags` @8 (bit 0 = FA2_FLAG_FIRST), `tierStart` @12, `tierEnd` @16, `iterationIndex` @20, `seed` @24, `nearMax` @28, `scalingRatio` @32, `gravity` @36, `jitterTolerance` @40, `scale` @44, `center` @48 (xyz, w 0), `settleThreshold` @64, `extentFactor` @68, `gridMax` @72, `levels` @76, `arcBase` @80 / `arcEnd` @84 (the bound arc window of K2, 0 and arcCount in the layout), `accumulate` @88 (1 combines into `force`: the windowed pattern), `hiEnd` @92 / `midEnd` @124 (the degreeOrder tier boundaries, PD-7; both 0 without a permutation); the P5 model fields (PD-3): `frK` @96 (the FR optimal distance), `temperature` @100 (the FR temperature of this iteration), `springLength` @104, `springCoefficient` @108, `coulomb` @112 (ngraph's `gravity`, negative repels), `dragCoefficient` @116, `timeStep` @120; 128 B. */
105
131
  export const FA2_PARAMS: UniformBlock = UniformBlock.define("Fa2Params", [
106
132
  ["n", "u32"],
107
133
  ["dim", "u32"],
@@ -120,7 +146,10 @@ export const FA2_PARAMS: UniformBlock = UniformBlock.define("Fa2Params", [
120
146
  ["extentFactor", "f32"],
121
147
  ["gridMax", "u32"],
122
148
  ["levels", "u32"],
123
- ["pad", "vec4f"],
149
+ ["arcBase", "u32"],
150
+ ["arcEnd", "u32"],
151
+ ["accumulate", "u32"],
152
+ ["hiEnd", "u32"],
124
153
  ["frK", "f32"],
125
154
  ["temperature", "f32"],
126
155
  ["springLength", "f32"],
@@ -128,10 +157,10 @@ export const FA2_PARAMS: UniformBlock = UniformBlock.define("Fa2Params", [
128
157
  ["coulomb", "f32"],
129
158
  ["dragCoefficient", "f32"],
130
159
  ["timeStep", "f32"],
131
- ["pad1", "f32"],
160
+ ["midEnd", "u32"],
132
161
  ]);
133
162
 
134
- /** `Fa2State` (storage, padded to STATE_HEADER_BYTES = 256; spec 7.3): the device-resident controller state the finalize kernels write and the host reads back for stats -- `speed` @0, `speedEfficiency` @4, `swing` @8, `traction` @12, `centroid` @16, `rmsRadius` @32, `radius` @36, `meanDisplacement` @40, `iteration` @44, `min` @48, `max` @64, `gridMin` @80 (P4), `eps` @96 (P4), `settledCount` @100, `outsideGrid` @104 (P4), `maxCellOccupancy` @108 (P4), `temperature` @112 (FR, written by K1 under STATS_MODE 1), `kineticEnergy` @116 (the preset, K1 under STATS_MODE 2), `reserved0` @120 (vec2f), `reserved1` .. `reserved8` @128 .. @240. */
163
+ /** `Fa2State` (storage, padded to STATE_HEADER_BYTES = 256; spec 7.3): the device-resident controller state the finalize kernels write and the host reads back for stats -- `speed` @0, `speedEfficiency` @4, `swing` @8, `traction` @12, `centroid` @16, `rmsRadius` @32, `radius` @36, `meanDisplacement` @40, `iteration` @44, `min` @48, `max` @64, `gridMin` @80 (P4), `eps` @96 (P4), `settledCount` @100, `outsideGrid` @104 (P4), `maxCellOccupancy` @108 (P4), `temperature` @112 (FR, written by K1 under STATS_MODE 1), `kineticEnergy` @116 (the preset, K1 under STATS_MODE 2), `frEnergy` @120 / `frProgress` @124 (the FR adaptive cooling), `invCellSize` @128 (P4, PD-10: `1 / cellSize`, written by K1 beside `cellSize` in `gridMin.w`; G1 multiplies by it so every key is bitwise reproducible), `reserved0` @132 (f32), `reserved1` @136 (vec2f), `reserved2` .. `reserved8` @144 .. @240. */
135
164
  export const FA2_STATE: UniformBlock = UniformBlock.define(
136
165
  "Fa2State",
137
166
  [
@@ -155,7 +184,9 @@ export const FA2_STATE: UniformBlock = UniformBlock.define(
155
184
  ["kineticEnergy", "f32"],
156
185
  ["frEnergy", "f32"],
157
186
  ["frProgress", "u32"],
158
- ["reserved1", "vec4f"],
187
+ ["invCellSize", "f32"],
188
+ ["reserved0", "f32"],
189
+ ["reserved1", "vec2f"],
159
190
  ["reserved2", "vec4f"],
160
191
  ["reserved3", "vec4f"],
161
192
  ["reserved4", "vec4f"],
@@ -196,7 +227,7 @@ export const FA2_PARTIAL: UniformBlock = UniformBlock.define(
196
227
  { layout: "storage" },
197
228
  );
198
229
 
199
- /** `SpmvParams` (uniform, 32 B; spec 8.2): `n` @0 rows of the pull, the bound arc window `[arcBase, arcEnd)` @4 / @8 (0 and arcCount when not windowed), the grid-stride step `stride` @12, `alpha` @16, `beta` @20 (the `1 - alpha` term), `uniformP` @24 (the uniform personalization mass `1 / n`, 0 for a pure SpMV), `pad0` @28. */
230
+ /** `SpmvParams` (uniform, 32 B; spec 8.2): `n` @0 rows of the pull, the bound arc window `[arcBase, arcEnd)` @4 / @8 (0 and arcCount when not windowed), the grid-stride step `stride` @12, `alpha` @16, `beta` @20 (the `1 - alpha` term), `uniformP` @24 (the uniform personalization mass `1 / n`, 0 for a pure SpMV), `start` @28 (the first row of the dispatch; TIER 0 strides from it, the tiers index from it). */
200
231
  export const SPMV_PARAMS: UniformBlock = UniformBlock.define("SpmvParams", [
201
232
  ["n", "u32"],
202
233
  ["arcBase", "u32"],
@@ -205,7 +236,7 @@ export const SPMV_PARAMS: UniformBlock = UniformBlock.define("SpmvParams", [
205
236
  ["alpha", "f32"],
206
237
  ["beta", "f32"],
207
238
  ["uniformP", "f32"],
208
- ["pad0", "u32"],
239
+ ["start", "u32"],
209
240
  ]);
210
241
 
211
242
  /** `PrParams` (uniform, 32 B; spec 8.2): `n` @0, `groups` @4 (the per-workgroup partial count the finalize folds), `iteration` @8 (1-based), `trackConvergence` @12 (1 records firstConverged), `convergeThreshold` @16 (`tolerance * n`, the design's `delta < tol * n`), `pad0` @20, `pad1` @24, `pad2` @28. */
@@ -254,6 +285,50 @@ export const WCC_PARAMS: UniformBlock = UniformBlock.define("WccParams", [
254
285
  ["pad0", "u32"],
255
286
  ]);
256
287
 
288
+ /** `IndirectParams` (uniform, 16 B; spec 5.4): `countIndex` @0 (the word of `counters` holding the count), `wg` @4 (the consumer's workgroup size), `slot` @8 (the 16-byte args slot to write), `pad0` @12. */
289
+ export const INDIRECT_PARAMS: UniformBlock = UniformBlock.define("IndirectParams", [
290
+ ["countIndex", "u32"],
291
+ ["wg", "u32"],
292
+ ["slot", "u32"],
293
+ ["pad0", "u32"],
294
+ ]);
295
+
296
+ /** `ScanParams` (uniform, 16 B; spec 6 row 2): `count` @0 (the u32 words of the level), `pad0` @4, `pad1` @8, `pad2` @12. */
297
+ export const SCAN_PARAMS: UniformBlock = UniformBlock.define("ScanParams", [
298
+ ["count", "u32"],
299
+ ["pad0", "u32"],
300
+ ["pad1", "u32"],
301
+ ["pad2", "u32"],
302
+ ]);
303
+
304
+ /** `GridLevelParams` (uniform, 32 B; spec 7.7 G5, P4-T9): `childBase` @0 and `parentBase` @4 (the first cell of the child / parent level inside the pyramid), `parentSide` @8, `parentCells` @12 (`parentSide^dim`), `depth` @16 (1 in 2D, 2 in 3D), `pad0` @20, `pad1` @24, `pad2` @28. */
305
+ export const GRID_LEVEL_PARAMS: UniformBlock = UniformBlock.define("GridLevelParams", [
306
+ ["childBase", "u32"],
307
+ ["parentBase", "u32"],
308
+ ["parentSide", "u32"],
309
+ ["parentCells", "u32"],
310
+ ["depth", "u32"],
311
+ ["pad0", "u32"],
312
+ ["pad1", "u32"],
313
+ ["pad2", "u32"],
314
+ ]);
315
+
316
+ /** `HistParams` (uniform, 16 B; spec 6 row 5): `count` @0 (the keys), `bins` @4 (a key >= bins is not counted), `pad0` @8, `pad1` @12. */
317
+ export const HIST_PARAMS: UniformBlock = UniformBlock.define("HistParams", [
318
+ ["count", "u32"],
319
+ ["bins", "u32"],
320
+ ["pad0", "u32"],
321
+ ["pad1", "u32"],
322
+ ]);
323
+
324
+ /** `RadixParams` (uniform, 16 B; spec 6 row 6): `count` @0 (the pairs), `shift` @4 (the pass's digit shift, 8 x pass), `groups` @8 (ceil(count / WG), the stride of the digit-major table), `pad0` @12. */
325
+ export const RADIX_PARAMS: UniformBlock = UniformBlock.define("RadixParams", [
326
+ ["count", "u32"],
327
+ ["shift", "u32"],
328
+ ["groups", "u32"],
329
+ ["pad0", "u32"],
330
+ ]);
331
+
257
332
  // ---- the entries (contract 3.10.1; group 0 = graph, 1 = state, 2 = params, 3 = cold)
258
333
 
259
334
  /**
@@ -330,7 +405,7 @@ const FILL: KernelEntry = {
330
405
  phase: "P1",
331
406
  };
332
407
 
333
- /** `segmented-reduce` (3.10.1): the per-row fold of the VALUE snippet over the CSR rows; OP 0 sum / 1 min / 2 max, TIER 0 (thread-per-row; the degreeOrder tiers land at P4) plus the standard USE_PERM / HAS_WEIGHTS; 5 storage bindings; one snippet slot. */
408
+ /** `segmented-reduce` (3.10.1): the per-row fold of the VALUE snippet over the CSR rows; OP 0 sum / 1 min / 2 max, TIER 0 thread-per-row / 1 32-lanes-per-row / 2 workgroup-per-row (the degreeOrder tiers, P4 PD-6) plus the standard USE_PERM / HAS_WEIGHTS; 5 storage bindings; one snippet slot; calls the reduction helpers (TIER 2). */
334
409
  const SEGMENTED_REDUCE: KernelEntry = {
335
410
  id: "segmented-reduce",
336
411
  body: segmentedReduceWgsl,
@@ -341,12 +416,12 @@ const SEGMENTED_REDUCE: KernelEntry = {
341
416
  { name: "TIER", type: "u32", default: 0 },
342
417
  ],
343
418
  uniforms: [RANGE_PARAMS],
344
- needs: [],
419
+ needs: ["subgroups"],
345
420
  snippetSlots: ["VALUE"],
346
421
  phase: "P2",
347
422
  };
348
423
 
349
- /** `fa2-stats-finalize` (K1, 3.10.1): the one-workgroup fold of the previous integrate's partials into the state block and the K1 half of the trace record; STATS_MODE 0 FA2 / 1 FR temperature / 2 kinetic energy (P5); 3 storage bindings; calls the reduction helpers. */
424
+ /** `fa2-stats-finalize` (K1, 3.10.1): the one-workgroup fold of the previous integrate's partials into the state block and the K1 half of the trace record; STATS_MODE 0 FA2 / 1 FR temperature / 2 kinetic energy (P5); the grid frame, counts and hub-counter reset under `P.gridMax > 0` (P4-T10, PD-14: `cellHist` and `hubCounters` are bound to dummies on the exact tier); 5 storage bindings; calls the reduction helpers. */
350
425
  const FA2_STATS_FINALIZE: KernelEntry = {
351
426
  id: "fa2-stats-finalize",
352
427
  body: fa2StatsFinalizeWgsl,
@@ -355,6 +430,8 @@ const FA2_STATS_FINALIZE: KernelEntry = {
355
430
  decl(1, 0, "partials", "storage-ro", "array<Fa2Partial>"),
356
431
  decl(1, 1, "S", "storage", "Fa2State"),
357
432
  decl(1, 2, "T", "storage", "array<Fa2Trace>"),
433
+ decl(1, 3, "cellHist", "storage-ro", "array<u32>"),
434
+ decl(1, 4, "hubCounters", "storage", "array<atomic<u32>>"),
358
435
  decl(2, 0, "P", "uniform", "Fa2Params"),
359
436
  ],
360
437
  overrideDecls: [{ name: "STATS_MODE", type: "u32", default: 0 }],
@@ -364,7 +441,7 @@ const FA2_STATS_FINALIZE: KernelEntry = {
364
441
  phase: "P3",
365
442
  };
366
443
 
367
- /** `fa2-attraction` (K2, 3.10.1): the thread-per-row attraction gather over the CSR rows (the first writer of `force` each iteration); LINLOG / DISTRIBUTED / TIER 0 plus the standard USE_PERM / HAS_WEIGHTS; LAW 0 FA2 / 1 FR / 2 spring (P5); 6 storage bindings. */
444
+ /** `fa2-attraction` (K2, 3.10.1): the attraction gather over the CSR rows (the first writer of `force` each iteration); LINLOG / DISTRIBUTED / TIER 0 thread-per-row / 1 32-lanes-per-row / 2 workgroup-per-row (P4 PD-6) plus the standard USE_PERM / HAS_WEIGHTS; LAW 0 FA2 / 1 FR / 2 spring (P5); 6 storage bindings; calls the reduction helpers (TIER 2). */
368
445
  const FA2_ATTRACTION: KernelEntry = {
369
446
  id: "fa2-attraction",
370
447
  body: fa2AttractionWgsl,
@@ -381,7 +458,7 @@ const FA2_ATTRACTION: KernelEntry = {
381
458
  { name: "LAW", type: "u32", default: 0 },
382
459
  ],
383
460
  uniforms: [FA2_PARAMS],
384
- needs: [],
461
+ needs: ["subgroups"],
385
462
  snippetSlots: [],
386
463
  phase: "P3",
387
464
  };
@@ -471,7 +548,7 @@ const FA2_TO_SCENE: KernelEntry = {
471
548
  phase: "P3",
472
549
  };
473
550
 
474
- /** `spmv-pull` (spec 8.10): the grid-stride pull SpMV over the reverse adjacency; HAS_PERSONALIZATION / USE_DANGLING plus the standard USE_PERM / HAS_WEIGHTS; 8 storage bindings (the design's count). */
551
+ /** `spmv-pull` (spec 8.10): the pull SpMV over the reverse adjacency; HAS_PERSONALIZATION / USE_DANGLING / TIER 0 grid-stride / 1 32-lanes-per-row / 2 workgroup-per-row (the reverseDegreeOrder tiers, P4 PD-6) plus the standard USE_PERM / HAS_WEIGHTS; 8 storage bindings (the design's count); calls the reduction helpers (TIER 2). */
475
552
  const SPMV_PULL: KernelEntry = {
476
553
  id: "spmv-pull",
477
554
  body: spmvPullWgsl,
@@ -486,9 +563,10 @@ const SPMV_PULL: KernelEntry = {
486
563
  overrideDecls: [
487
564
  { name: "HAS_PERSONALIZATION", type: "bool", default: false },
488
565
  { name: "USE_DANGLING", type: "bool", default: false },
566
+ { name: "TIER", type: "u32", default: 0 },
489
567
  ],
490
568
  uniforms: [SPMV_PARAMS, PR_PARTIAL],
491
- needs: [],
569
+ needs: ["subgroups"],
492
570
  snippetSlots: [],
493
571
  phase: "P7",
494
572
  };
@@ -590,6 +668,253 @@ const WCC_SAMPLE: KernelEntry = {
590
668
  phase: "P7",
591
669
  };
592
670
 
671
+ /** `indirect-finalize` (spec 5.4; P4-T1): the one-lane count -> (x, y, 1, count) finalize; 2 storage bindings. */
672
+ const INDIRECT_FINALIZE: KernelEntry = {
673
+ id: "indirect-finalize",
674
+ body: indirectFinalizeWgsl,
675
+ entryPoint: "indirect_finalize",
676
+ bindings: [
677
+ decl(1, 0, "counters", "storage-ro", "array<u32>"),
678
+ decl(1, 1, "args", "storage", "array<u32>"),
679
+ decl(2, 0, "P", "uniform", "IndirectParams"),
680
+ ],
681
+ overrideDecls: [],
682
+ uniforms: [INDIRECT_PARAMS],
683
+ needs: [],
684
+ snippetSlots: [],
685
+ phase: "P4",
686
+ };
687
+
688
+ /** `scan-block` (spec 6 row 2 steps (a) / (b); P4-T2, PD-3): the Hillis-Steele exclusive scan of one WG-wide block into `out` and its total into `blockSums[group]`; 3 storage bindings; no subgroup variant. */
689
+ const SCAN_BLOCK: KernelEntry = {
690
+ id: "scan-block",
691
+ body: scanBlockWgsl,
692
+ entryPoint: "scan_block",
693
+ bindings: [
694
+ decl(1, 0, "src", "storage-ro", "array<u32>"),
695
+ decl(1, 1, "out", "storage", "array<u32>"),
696
+ decl(1, 2, "blockSums", "storage", "array<u32>"),
697
+ decl(2, 0, "P", "uniform", "ScanParams"),
698
+ ],
699
+ overrideDecls: [],
700
+ uniforms: [SCAN_PARAMS],
701
+ needs: [],
702
+ snippetSlots: [],
703
+ phase: "P4",
704
+ };
705
+
706
+ /** `scan-add` (spec 6 row 2 step (c); P4-T2): adds `blockOffsets[group]` to every element of the block; 2 storage bindings. */
707
+ const SCAN_ADD: KernelEntry = {
708
+ id: "scan-add",
709
+ body: scanAddWgsl,
710
+ entryPoint: "scan_add",
711
+ bindings: [
712
+ decl(1, 0, "out", "storage", "array<u32>"),
713
+ decl(1, 1, "blockOffsets", "storage-ro", "array<u32>"),
714
+ decl(2, 0, "P", "uniform", "ScanParams"),
715
+ ],
716
+ overrideDecls: [],
717
+ uniforms: [SCAN_PARAMS],
718
+ needs: [],
719
+ snippetSlots: [],
720
+ phase: "P4",
721
+ };
722
+
723
+ /** `histogram` (spec 6 row 5; P4-T3, PD-4): one global atomicAdd per key into `hist` (zeroed by a `fill` dispatch earlier in the pass); 2 storage bindings; one path, no PRIVATE override. */
724
+ const HISTOGRAM: KernelEntry = {
725
+ id: "histogram",
726
+ body: histogramWgsl,
727
+ entryPoint: "histogram",
728
+ bindings: [
729
+ decl(1, 0, "keys", "storage-ro", "array<u32>"),
730
+ decl(1, 1, "hist", "storage", "array<atomic<u32>>"),
731
+ decl(2, 0, "P", "uniform", "HistParams"),
732
+ ],
733
+ overrideDecls: [],
734
+ uniforms: [HIST_PARAMS],
735
+ needs: [],
736
+ snippetSlots: [],
737
+ phase: "P4",
738
+ };
739
+
740
+ /** `counting-scatter` (spec 6 row 5; P4-T3): the scatter of a counting sort, `outIndex[start[k] + atomicAdd(&cursor[k], 1u)] = i`; 4 storage bindings; set-deterministic (the order inside a bin follows the schedule). */
741
+ const COUNTING_SCATTER: KernelEntry = {
742
+ id: "counting-scatter",
743
+ body: countingScatterWgsl,
744
+ entryPoint: "counting_scatter",
745
+ bindings: [
746
+ decl(1, 0, "keys", "storage-ro", "array<u32>"),
747
+ decl(1, 1, "start", "storage-ro", "array<u32>"),
748
+ decl(1, 2, "cursor", "storage", "array<atomic<u32>>"),
749
+ decl(1, 3, "outIndex", "storage", "array<u32>"),
750
+ decl(2, 0, "P", "uniform", "HistParams"),
751
+ ],
752
+ overrideDecls: [],
753
+ uniforms: [HIST_PARAMS],
754
+ needs: [],
755
+ snippetSlots: [],
756
+ phase: "P4",
757
+ };
758
+
759
+ /** `radix-hist` (spec 6 row 6; P4-T4, PD-5): the per-workgroup 256-bin digit histogram, privatised in workgroup memory and stored digit-major `hist[digit * groups + group]`; 2 storage bindings. */
760
+ const RADIX_HIST: KernelEntry = {
761
+ id: "radix-hist",
762
+ body: radixHistWgsl,
763
+ entryPoint: "radix_hist",
764
+ bindings: [
765
+ decl(1, 0, "keys", "storage-ro", "array<u32>"),
766
+ decl(1, 1, "hist", "storage", "array<u32>"),
767
+ decl(2, 0, "P", "uniform", "RadixParams"),
768
+ ],
769
+ overrideDecls: [],
770
+ uniforms: [RADIX_PARAMS],
771
+ needs: [],
772
+ snippetSlots: [],
773
+ phase: "P4",
774
+ };
775
+
776
+ /** `radix-scatter` (spec 6 row 6; P4-T4, PD-5): the stable scatter of one LSD pass, ranked serially by lane 0, written at `offsets[digit * groups + group] + rank`; 5 storage bindings. */
777
+ const RADIX_SCATTER: KernelEntry = {
778
+ id: "radix-scatter",
779
+ body: radixScatterWgsl,
780
+ entryPoint: "radix_scatter",
781
+ bindings: [
782
+ decl(1, 0, "keys", "storage-ro", "array<u32>"),
783
+ decl(1, 1, "vals", "storage-ro", "array<u32>"),
784
+ decl(1, 2, "offsets", "storage-ro", "array<u32>"),
785
+ decl(1, 3, "keysOut", "storage", "array<u32>"),
786
+ decl(1, 4, "valsOut", "storage", "array<u32>"),
787
+ decl(2, 0, "P", "uniform", "RadixParams"),
788
+ ],
789
+ overrideDecls: [],
790
+ uniforms: [RADIX_PARAMS],
791
+ needs: [],
792
+ snippetSlots: [],
793
+ phase: "P4",
794
+ };
795
+
796
+ /** `grid-cell-key` (G1, spec 7.7; P4-T8, PD-10): the finest cell key of every node, `floor((p - gridMin) * invCellSize)` linearised, or the outside pseudo-cell `G^dim`; `cellVal[i] = i`; 4 storage bindings (the state read-only: K1 writes it). */
797
+ const GRID_CELL_KEY: KernelEntry = {
798
+ id: "grid-cell-key",
799
+ body: gridCellKeyWgsl,
800
+ entryPoint: "grid_cell_key",
801
+ bindings: [
802
+ decl(1, 0, "pos", "storage-ro", "array<vec4f>"),
803
+ decl(1, 1, "S", "storage-ro", "Fa2State"),
804
+ decl(1, 2, "cellKey", "storage", "array<u32>"),
805
+ decl(1, 3, "cellVal", "storage", "array<u32>"),
806
+ decl(2, 0, "P", "uniform", "Fa2Params"),
807
+ ],
808
+ overrideDecls: [],
809
+ uniforms: [FA2_PARAMS, FA2_STATE],
810
+ needs: [],
811
+ snippetSlots: [],
812
+ phase: "P4",
813
+ };
814
+
815
+ /** `grid-centroid` (G4, spec 7.7; P4-T9, PD-13): thread per finest cell (the pseudo-cell included), the serial mass-weighted sum in sorted order into level 0, the occupancy max into `hubCounters[1]`, hub cells (> GRID_HUB_CELL) appended to `hubList`; 6 storage bindings. */
816
+ const GRID_CENTROID: KernelEntry = {
817
+ id: "grid-centroid",
818
+ body: gridCentroidWgsl,
819
+ entryPoint: "grid_centroid",
820
+ bindings: [
821
+ decl(1, 0, "sortedIdx", "storage-ro", "array<u32>"),
822
+ decl(1, 1, "cellStart", "storage-ro", "array<u32>"),
823
+ decl(1, 2, "pos", "storage-ro", "array<vec4f>"),
824
+ decl(1, 3, "pyramid", "storage", "array<vec4f>"),
825
+ decl(1, 4, "hubList", "storage", "array<u32>"),
826
+ decl(1, 5, "hubCounters", "storage", "array<atomic<u32>>"),
827
+ decl(2, 0, "P", "uniform", "Fa2Params"),
828
+ ],
829
+ overrideDecls: [],
830
+ uniforms: [FA2_PARAMS],
831
+ needs: [],
832
+ snippetSlots: [],
833
+ phase: "P4",
834
+ };
835
+
836
+ /** `grid-centroid-hub` (G4b, spec 7.7; P4-T9, PD-13, DEP-P4-L): one workgroup per hub cell, dispatched indirectly, a WG-strided sum through `wg_reduce_vec4` guarded by `h < hubCount[0]`; 6 storage bindings (`hubCount` is a read-only view of `hubCounters`). */
837
+ const GRID_CENTROID_HUB: KernelEntry = {
838
+ id: "grid-centroid-hub",
839
+ body: gridCentroidHubWgsl,
840
+ entryPoint: "grid_centroid_hub",
841
+ bindings: [
842
+ decl(1, 0, "sortedIdx", "storage-ro", "array<u32>"),
843
+ decl(1, 1, "cellStart", "storage-ro", "array<u32>"),
844
+ decl(1, 2, "pos", "storage-ro", "array<vec4f>"),
845
+ decl(1, 3, "pyramid", "storage", "array<vec4f>"),
846
+ decl(1, 4, "hubList", "storage-ro", "array<u32>"),
847
+ decl(1, 5, "hubCount", "storage-ro", "array<u32>"),
848
+ decl(2, 0, "P", "uniform", "Fa2Params"),
849
+ ],
850
+ overrideDecls: [],
851
+ uniforms: [FA2_PARAMS],
852
+ needs: ["subgroups"],
853
+ snippetSlots: [],
854
+ phase: "P4",
855
+ };
856
+
857
+ /** `grid-downsample` (G5, spec 7.7; P4-T9): one dispatch per coarser level, every parent the sum of its 4 / 8 children at `P.childBase`, written at `P.parentBase`; 1 storage binding. */
858
+ const GRID_DOWNSAMPLE: KernelEntry = {
859
+ id: "grid-downsample",
860
+ body: gridDownsampleWgsl,
861
+ entryPoint: "grid_downsample",
862
+ bindings: [decl(1, 0, "pyramid", "storage", "array<vec4f>"), decl(2, 0, "P", "uniform", "GridLevelParams")],
863
+ overrideDecls: [],
864
+ uniforms: [GRID_LEVEL_PARAMS],
865
+ needs: [],
866
+ snippetSlots: [],
867
+ phase: "P4",
868
+ };
869
+
870
+ /** `grid-far-field` (G6, spec 7.7; P4-T10, PD-16, DEP-P4-G): per node in sorted order, the coarsest level minus its 3x3 (3x3x3) and, per finer level, the parent's 3x3 refined minus the level's own 3x3, plus the pseudo-cell; the loop bounds are `P.levels` / `P.gridMax`; LAW 0 FA2 / 1 FR / 2 coulomb per cell (P4-T13, PD-22); 5 storage bindings. */
871
+ const GRID_FAR_FIELD: KernelEntry = {
872
+ id: "grid-far-field",
873
+ body: gridFarFieldWgsl,
874
+ entryPoint: "grid_far_field",
875
+ bindings: [
876
+ decl(1, 0, "pos", "storage-ro", "array<vec4f>"),
877
+ decl(1, 1, "sortedIdx", "storage-ro", "array<u32>"),
878
+ decl(1, 2, "pyramid", "storage-ro", "array<vec4f>"),
879
+ decl(1, 3, "S", "storage-ro", "Fa2State"),
880
+ decl(1, 4, "force", "storage", "array<f32>"),
881
+ decl(2, 0, "P", "uniform", "Fa2Params"),
882
+ ],
883
+ overrideDecls: [{ name: "LAW", type: "u32", default: 0 }],
884
+ uniforms: [FA2_PARAMS, FA2_STATE],
885
+ needs: [],
886
+ snippetSlots: [],
887
+ phase: "P4",
888
+ };
889
+
890
+ /** `grid-near-field` (G7, spec 7.7; P4-T10, PD-15): per node in sorted order, K3's exact pair law over the 9 (27) finest cells, a cell above `nearMax` sampled through the hashed window and scaled by `others / sampled`, then K3's fused epilogue (gravity, `force +=`, the swing / traction reduction); SWING_MODE / STRONG_GRAVITY / GRAVITY_CENTER / LAW as K3 (LAW 0 FA2 / 1 FR / 2 coulomb, P4-T13, PD-22); 8 storage bindings; calls the reduction helpers. */
891
+ const GRID_NEAR_FIELD: KernelEntry = {
892
+ id: "grid-near-field",
893
+ body: gridNearFieldWgsl,
894
+ entryPoint: "grid_near_field",
895
+ bindings: [
896
+ decl(1, 0, "pos", "storage-ro", "array<vec4f>"),
897
+ decl(1, 1, "sortedIdx", "storage-ro", "array<u32>"),
898
+ decl(1, 2, "cellStart", "storage-ro", "array<u32>"),
899
+ decl(1, 3, "S", "storage", "Fa2State"),
900
+ decl(1, 4, "force", "storage", "array<f32>"),
901
+ decl(1, 5, "oldForce", "storage-ro", "array<f32>"),
902
+ decl(1, 6, "fixedMask", "storage-ro", "array<u32>"),
903
+ decl(1, 7, "partials", "storage", "array<Fa2Partial>"),
904
+ decl(2, 0, "P", "uniform", "Fa2Params"),
905
+ ],
906
+ overrideDecls: [
907
+ { name: "SWING_MODE", type: "u32", default: 0 },
908
+ { name: "STRONG_GRAVITY", type: "bool", default: false },
909
+ { name: "GRAVITY_CENTER", type: "u32", default: 0 },
910
+ { name: "LAW", type: "u32", default: 0 },
911
+ ],
912
+ uniforms: [FA2_PARAMS, FA2_STATE, FA2_PARTIAL],
913
+ needs: ["subgroups"],
914
+ snippetSlots: [],
915
+ phase: "P4",
916
+ };
917
+
593
918
  /**
594
919
  * The entries by id, in dispatch order. PLAN DECISION: `KernelId` is declared in full (contract 3.10) while the
595
920
  * entries landed phase by phase, so the table is built as a Partial record and exported below through the
@@ -617,6 +942,19 @@ const REGISTRY: Readonly<Partial<Record<KernelId, KernelEntry>>> = Object.freeze
617
942
  "wcc-link-edges": WCC_LINK_EDGES,
618
943
  "wcc-compress": WCC_COMPRESS,
619
944
  "wcc-sample": WCC_SAMPLE,
945
+ "indirect-finalize": INDIRECT_FINALIZE,
946
+ "scan-block": SCAN_BLOCK,
947
+ "scan-add": SCAN_ADD,
948
+ histogram: HISTOGRAM,
949
+ "counting-scatter": COUNTING_SCATTER,
950
+ "radix-hist": RADIX_HIST,
951
+ "radix-scatter": RADIX_SCATTER,
952
+ "grid-cell-key": GRID_CELL_KEY,
953
+ "grid-centroid": GRID_CENTROID,
954
+ "grid-centroid-hub": GRID_CENTROID_HUB,
955
+ "grid-downsample": GRID_DOWNSAMPLE,
956
+ "grid-far-field": GRID_FAR_FIELD,
957
+ "grid-near-field": GRID_NEAR_FIELD,
620
958
  });
621
959
 
622
960
  /** THE registry (spec 3.5): every entry, keyed by id. */