@graphty/webgpu-graph-algorithms 0.0.0 → 0.2.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 (269) hide show
  1. package/README.md +344 -23
  2. package/dist/browser.d.ts +1 -0
  3. package/dist/browser.js +32 -0
  4. package/dist/browser.js.map +1 -0
  5. package/dist/chunks/context-E6iKaeuJ.js +3136 -0
  6. package/dist/chunks/context-E6iKaeuJ.js.map +1 -0
  7. package/dist/node.d.ts +1 -0
  8. package/dist/node.js +131 -0
  9. package/dist/node.js.map +1 -0
  10. package/dist/src/accelerator.d.ts +26 -0
  11. package/dist/src/accelerator.d.ts.map +1 -0
  12. package/dist/src/accelerator.js +101 -0
  13. package/dist/src/accelerator.js.map +1 -0
  14. package/dist/src/algorithms/degree.d.ts +35 -0
  15. package/dist/src/algorithms/degree.d.ts.map +1 -0
  16. package/dist/src/algorithms/degree.js +119 -0
  17. package/dist/src/algorithms/degree.js.map +1 -0
  18. package/dist/src/browser/index.d.ts +23 -0
  19. package/dist/src/browser/index.d.ts.map +1 -0
  20. package/dist/src/browser/index.js +48 -0
  21. package/dist/src/browser/index.js.map +1 -0
  22. package/dist/src/constants.d.ts +92 -0
  23. package/dist/src/constants.d.ts.map +1 -0
  24. package/dist/src/constants.js +92 -0
  25. package/dist/src/constants.js.map +1 -0
  26. package/dist/src/context.d.ts +84 -0
  27. package/dist/src/context.d.ts.map +1 -0
  28. package/dist/src/context.js +304 -0
  29. package/dist/src/context.js.map +1 -0
  30. package/dist/src/device/acquire.d.ts +57 -0
  31. package/dist/src/device/acquire.d.ts.map +1 -0
  32. package/dist/src/device/acquire.js +232 -0
  33. package/dist/src/device/acquire.js.map +1 -0
  34. package/dist/src/device/caps.d.ts +43 -0
  35. package/dist/src/device/caps.d.ts.map +1 -0
  36. package/dist/src/device/caps.js +104 -0
  37. package/dist/src/device/caps.js.map +1 -0
  38. package/dist/src/device/error-scope.d.ts +75 -0
  39. package/dist/src/device/error-scope.d.ts.map +1 -0
  40. package/dist/src/device/error-scope.js +152 -0
  41. package/dist/src/device/error-scope.js.map +1 -0
  42. package/dist/src/device/lost.d.ts +51 -0
  43. package/dist/src/device/lost.d.ts.map +1 -0
  44. package/dist/src/device/lost.js +130 -0
  45. package/dist/src/device/lost.js.map +1 -0
  46. package/dist/src/device/webgpu-constants.d.ts +31 -0
  47. package/dist/src/device/webgpu-constants.d.ts.map +1 -0
  48. package/dist/src/device/webgpu-constants.js +31 -0
  49. package/dist/src/device/webgpu-constants.js.map +1 -0
  50. package/dist/src/errors.d.ts +56 -0
  51. package/dist/src/errors.d.ts.map +1 -0
  52. package/dist/src/errors.js +57 -0
  53. package/dist/src/errors.js.map +1 -0
  54. package/dist/src/index.d.ts +29 -0
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/index.js +27 -0
  57. package/dist/src/index.js.map +1 -0
  58. package/dist/src/kernel/batch.d.ts +116 -0
  59. package/dist/src/kernel/batch.d.ts.map +1 -0
  60. package/dist/src/kernel/batch.js +335 -0
  61. package/dist/src/kernel/batch.js.map +1 -0
  62. package/dist/src/kernel/dispatch.d.ts +59 -0
  63. package/dist/src/kernel/dispatch.d.ts.map +1 -0
  64. package/dist/src/kernel/dispatch.js +139 -0
  65. package/dist/src/kernel/dispatch.js.map +1 -0
  66. package/dist/src/kernel/kernel.d.ts +84 -0
  67. package/dist/src/kernel/kernel.d.ts.map +1 -0
  68. package/dist/src/kernel/kernel.js +239 -0
  69. package/dist/src/kernel/kernel.js.map +1 -0
  70. package/dist/src/kernel/pipeline-cache.d.ts +90 -0
  71. package/dist/src/kernel/pipeline-cache.d.ts.map +1 -0
  72. package/dist/src/kernel/pipeline-cache.js +251 -0
  73. package/dist/src/kernel/pipeline-cache.js.map +1 -0
  74. package/dist/src/kernel/prelude.d.ts +35 -0
  75. package/dist/src/kernel/prelude.d.ts.map +1 -0
  76. package/dist/src/kernel/prelude.js +211 -0
  77. package/dist/src/kernel/prelude.js.map +1 -0
  78. package/dist/src/kernel/profiler.d.ts +64 -0
  79. package/dist/src/kernel/profiler.d.ts.map +1 -0
  80. package/dist/src/kernel/profiler.js +120 -0
  81. package/dist/src/kernel/profiler.js.map +1 -0
  82. package/dist/src/kernel/struct-block.d.ts +122 -0
  83. package/dist/src/kernel/struct-block.d.ts.map +1 -0
  84. package/dist/src/kernel/struct-block.js +353 -0
  85. package/dist/src/kernel/struct-block.js.map +1 -0
  86. package/dist/src/kernel/uniform-ring.d.ts +70 -0
  87. package/dist/src/kernel/uniform-ring.d.ts.map +1 -0
  88. package/dist/src/kernel/uniform-ring.js +146 -0
  89. package/dist/src/kernel/uniform-ring.js.map +1 -0
  90. package/dist/src/kernel/wgsl.d.ts +88 -0
  91. package/dist/src/kernel/wgsl.d.ts.map +1 -0
  92. package/dist/src/kernel/wgsl.js +390 -0
  93. package/dist/src/kernel/wgsl.js.map +1 -0
  94. package/dist/src/kernels.d.ts +81 -0
  95. package/dist/src/kernels.d.ts.map +1 -0
  96. package/dist/src/kernels.js +417 -0
  97. package/dist/src/kernels.js.map +1 -0
  98. package/dist/src/layouts/force-simulation.d.ts +498 -0
  99. package/dist/src/layouts/force-simulation.d.ts.map +1 -0
  100. package/dist/src/layouts/force-simulation.js +1650 -0
  101. package/dist/src/layouts/force-simulation.js.map +1 -0
  102. package/dist/src/layouts/forceatlas2.d.ts +210 -0
  103. package/dist/src/layouts/forceatlas2.d.ts.map +1 -0
  104. package/dist/src/layouts/forceatlas2.js +759 -0
  105. package/dist/src/layouts/forceatlas2.js.map +1 -0
  106. package/dist/src/layouts/inputs.d.ts +40 -0
  107. package/dist/src/layouts/inputs.d.ts.map +1 -0
  108. package/dist/src/layouts/inputs.js +185 -0
  109. package/dist/src/layouts/inputs.js.map +1 -0
  110. package/dist/src/layouts/repulsion-exact.d.ts +85 -0
  111. package/dist/src/layouts/repulsion-exact.d.ts.map +1 -0
  112. package/dist/src/layouts/repulsion-exact.js +134 -0
  113. package/dist/src/layouts/repulsion-exact.js.map +1 -0
  114. package/dist/src/layouts/seed.d.ts +56 -0
  115. package/dist/src/layouts/seed.d.ts.map +1 -0
  116. package/dist/src/layouts/seed.js +173 -0
  117. package/dist/src/layouts/seed.js.map +1 -0
  118. package/dist/src/memory/buffer-pool.d.ts +73 -0
  119. package/dist/src/memory/buffer-pool.d.ts.map +1 -0
  120. package/dist/src/memory/buffer-pool.js +170 -0
  121. package/dist/src/memory/buffer-pool.js.map +1 -0
  122. package/dist/src/memory/lease.d.ts +53 -0
  123. package/dist/src/memory/lease.d.ts.map +1 -0
  124. package/dist/src/memory/lease.js +85 -0
  125. package/dist/src/memory/lease.js.map +1 -0
  126. package/dist/src/memory/readback.d.ts +143 -0
  127. package/dist/src/memory/readback.d.ts.map +1 -0
  128. package/dist/src/memory/readback.js +375 -0
  129. package/dist/src/memory/readback.js.map +1 -0
  130. package/dist/src/memory/residency.d.ts +83 -0
  131. package/dist/src/memory/residency.d.ts.map +1 -0
  132. package/dist/src/memory/residency.js +573 -0
  133. package/dist/src/memory/residency.js.map +1 -0
  134. package/dist/src/memory/upload-plan.d.ts +101 -0
  135. package/dist/src/memory/upload-plan.d.ts.map +1 -0
  136. package/dist/src/memory/upload-plan.js +265 -0
  137. package/dist/src/memory/upload-plan.js.map +1 -0
  138. package/dist/src/node/index.d.ts +64 -0
  139. package/dist/src/node/index.d.ts.map +1 -0
  140. package/dist/src/node/index.js +183 -0
  141. package/dist/src/node/index.js.map +1 -0
  142. package/dist/src/primitives/reduce.d.ts +57 -0
  143. package/dist/src/primitives/reduce.d.ts.map +1 -0
  144. package/dist/src/primitives/reduce.js +161 -0
  145. package/dist/src/primitives/reduce.js.map +1 -0
  146. package/dist/src/primitives/segmented-reduce.d.ts +38 -0
  147. package/dist/src/primitives/segmented-reduce.d.ts.map +1 -0
  148. package/dist/src/primitives/segmented-reduce.js +211 -0
  149. package/dist/src/primitives/segmented-reduce.js.map +1 -0
  150. package/dist/src/types/accelerator.d.ts +209 -0
  151. package/dist/src/types/accelerator.d.ts.map +1 -0
  152. package/dist/src/types/accelerator.js +8 -0
  153. package/dist/src/types/accelerator.js.map +1 -0
  154. package/dist/src/types/context.d.ts +114 -0
  155. package/dist/src/types/context.d.ts.map +1 -0
  156. package/dist/src/types/context.js +7 -0
  157. package/dist/src/types/context.js.map +1 -0
  158. package/dist/src/types/layout.d.ts +95 -0
  159. package/dist/src/types/layout.d.ts.map +1 -0
  160. package/dist/src/types/layout.js +6 -0
  161. package/dist/src/types/layout.js.map +1 -0
  162. package/dist/src/types/memory.d.ts +22 -0
  163. package/dist/src/types/memory.d.ts.map +1 -0
  164. package/dist/src/types/memory.js +7 -0
  165. package/dist/src/types/memory.js.map +1 -0
  166. package/dist/src/types/options.d.ts +78 -0
  167. package/dist/src/types/options.d.ts.map +1 -0
  168. package/dist/src/types/options.js +7 -0
  169. package/dist/src/types/options.js.map +1 -0
  170. package/dist/src/types/run.d.ts +13 -0
  171. package/dist/src/types/run.d.ts.map +1 -0
  172. package/dist/src/types/run.js +6 -0
  173. package/dist/src/types/run.js.map +1 -0
  174. package/dist/src/wgsl/degree.wgsl.d.ts +10 -0
  175. package/dist/src/wgsl/degree.wgsl.d.ts.map +1 -0
  176. package/dist/src/wgsl/degree.wgsl.js +25 -0
  177. package/dist/src/wgsl/degree.wgsl.js.map +1 -0
  178. package/dist/src/wgsl/fa2-attraction.wgsl.d.ts +12 -0
  179. package/dist/src/wgsl/fa2-attraction.wgsl.d.ts.map +1 -0
  180. package/dist/src/wgsl/fa2-attraction.wgsl.js +37 -0
  181. package/dist/src/wgsl/fa2-attraction.wgsl.js.map +1 -0
  182. package/dist/src/wgsl/fa2-integrate.wgsl.d.ts +13 -0
  183. package/dist/src/wgsl/fa2-integrate.wgsl.d.ts.map +1 -0
  184. package/dist/src/wgsl/fa2-integrate.wgsl.js +69 -0
  185. package/dist/src/wgsl/fa2-integrate.wgsl.js.map +1 -0
  186. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts +12 -0
  187. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.d.ts.map +1 -0
  188. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js +79 -0
  189. package/dist/src/wgsl/fa2-repulsion-exact.wgsl.js.map +1 -0
  190. package/dist/src/wgsl/fa2-speed-finalize.wgsl.d.ts +15 -0
  191. package/dist/src/wgsl/fa2-speed-finalize.wgsl.d.ts.map +1 -0
  192. package/dist/src/wgsl/fa2-speed-finalize.wgsl.js +54 -0
  193. package/dist/src/wgsl/fa2-speed-finalize.wgsl.js.map +1 -0
  194. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts +14 -0
  195. package/dist/src/wgsl/fa2-stats-finalize.wgsl.d.ts.map +1 -0
  196. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js +57 -0
  197. package/dist/src/wgsl/fa2-stats-finalize.wgsl.js.map +1 -0
  198. package/dist/src/wgsl/fa2-to-scene.wgsl.d.ts +11 -0
  199. package/dist/src/wgsl/fa2-to-scene.wgsl.d.ts.map +1 -0
  200. package/dist/src/wgsl/fa2-to-scene.wgsl.js +19 -0
  201. package/dist/src/wgsl/fa2-to-scene.wgsl.js.map +1 -0
  202. package/dist/src/wgsl/fill.wgsl.d.ts +7 -0
  203. package/dist/src/wgsl/fill.wgsl.d.ts.map +1 -0
  204. package/dist/src/wgsl/fill.wgsl.js +14 -0
  205. package/dist/src/wgsl/fill.wgsl.js.map +1 -0
  206. package/dist/src/wgsl/reduce.wgsl.d.ts +10 -0
  207. package/dist/src/wgsl/reduce.wgsl.d.ts.map +1 -0
  208. package/dist/src/wgsl/reduce.wgsl.js +63 -0
  209. package/dist/src/wgsl/reduce.wgsl.js.map +1 -0
  210. package/dist/src/wgsl/segmented-reduce.wgsl.d.ts +13 -0
  211. package/dist/src/wgsl/segmented-reduce.wgsl.d.ts.map +1 -0
  212. package/dist/src/wgsl/segmented-reduce.wgsl.js +35 -0
  213. package/dist/src/wgsl/segmented-reduce.wgsl.js.map +1 -0
  214. package/dist/tsconfig.build.tsbuildinfo +1 -0
  215. package/dist/webgpu-graph-algorithms.d.ts +1 -0
  216. package/dist/webgpu-graph-algorithms.js +4454 -0
  217. package/dist/webgpu-graph-algorithms.js.map +1 -0
  218. package/package.json +108 -17
  219. package/src/accelerator.ts +117 -0
  220. package/src/algorithms/degree.ts +142 -0
  221. package/src/browser/index.ts +57 -0
  222. package/src/constants.ts +116 -0
  223. package/src/context.ts +399 -0
  224. package/src/device/acquire.ts +256 -0
  225. package/src/device/caps.ts +122 -0
  226. package/src/device/error-scope.ts +171 -0
  227. package/src/device/lost.ts +142 -0
  228. package/src/device/webgpu-constants.ts +44 -0
  229. package/src/errors.ts +94 -0
  230. package/src/index.ts +102 -0
  231. package/src/kernel/batch.ts +427 -0
  232. package/src/kernel/dispatch.ts +162 -0
  233. package/src/kernel/kernel.ts +311 -0
  234. package/src/kernel/pipeline-cache.ts +288 -0
  235. package/src/kernel/prelude.ts +229 -0
  236. package/src/kernel/profiler.ts +148 -0
  237. package/src/kernel/struct-block.ts +439 -0
  238. package/src/kernel/uniform-ring.ts +184 -0
  239. package/src/kernel/wgsl.ts +490 -0
  240. package/src/kernels.ts +511 -0
  241. package/src/layouts/force-simulation.ts +2111 -0
  242. package/src/layouts/forceatlas2.ts +942 -0
  243. package/src/layouts/inputs.ts +252 -0
  244. package/src/layouts/repulsion-exact.ts +183 -0
  245. package/src/layouts/seed.ts +198 -0
  246. package/src/memory/buffer-pool.ts +204 -0
  247. package/src/memory/lease.ts +93 -0
  248. package/src/memory/readback.ts +429 -0
  249. package/src/memory/residency.ts +753 -0
  250. package/src/memory/upload-plan.ts +350 -0
  251. package/src/node/index.ts +230 -0
  252. package/src/primitives/reduce.ts +233 -0
  253. package/src/primitives/segmented-reduce.ts +270 -0
  254. package/src/types/accelerator.ts +236 -0
  255. package/src/types/context.ts +135 -0
  256. package/src/types/layout.ts +103 -0
  257. package/src/types/memory.ts +23 -0
  258. package/src/types/options.ts +84 -0
  259. package/src/types/run.ts +13 -0
  260. package/src/wgsl/degree.wgsl.ts +24 -0
  261. package/src/wgsl/fa2-attraction.wgsl.ts +37 -0
  262. package/src/wgsl/fa2-integrate.wgsl.ts +69 -0
  263. package/src/wgsl/fa2-repulsion-exact.wgsl.ts +78 -0
  264. package/src/wgsl/fa2-speed-finalize.wgsl.ts +53 -0
  265. package/src/wgsl/fa2-stats-finalize.wgsl.ts +57 -0
  266. package/src/wgsl/fa2-to-scene.wgsl.ts +19 -0
  267. package/src/wgsl/fill.wgsl.ts +13 -0
  268. package/src/wgsl/reduce.wgsl.ts +62 -0
  269. package/src/wgsl/segmented-reduce.wgsl.ts +35 -0
package/src/kernels.ts ADDED
@@ -0,0 +1,511 @@
1
+ /**
2
+ * THE kernel registry (spec 3.5; contract 3.10): every WGSL module the package compiles, keyed by id, with the
3
+ * binding table, the override declarations, the generated uniform / storage blocks and the feature needs from
4
+ * which src/kernel/wgsl.ts emits the bind declarations and src/kernel/pipeline-cache.ts derives the explicit
5
+ * bind-group layouts. A variant cannot exist without an entry here: the compile tests, the bind-group-budget
6
+ * test and PipelineCache.warm() iterate this table. P1-T4 lands degree, reduce, fill, fa2-repulsion-exact (K3)
7
+ * and fa2-speed-finalize (K4) together with every generated block of contract 3.10.2; P2-T2 lands
8
+ * segmented-reduce; P3-T2 adds fa2-stats-finalize (K1), fa2-attraction (K2), fa2-integrate (K5) and
9
+ * fa2-to-scene. This file is the only importer of src/wgsl/** (spec 3.2; test/layers.test.ts).
10
+ */
11
+
12
+ import { STATE_HEADER_BYTES } from "./constants.js";
13
+ import { WebGpuGraphError } from "./errors.js";
14
+ import { UniformBlock } from "./kernel/struct-block.js";
15
+ import { type BindingDecl, type OverrideDecl, type WgslModuleSpec } from "./kernel/wgsl.js";
16
+ import { type CoreBinding } from "./memory/residency.js";
17
+ import { type Binding } from "./types/memory.js";
18
+ import { degreeWgsl } from "./wgsl/degree.wgsl.js";
19
+ import { fa2AttractionWgsl } from "./wgsl/fa2-attraction.wgsl.js";
20
+ import { fa2IntegrateWgsl } from "./wgsl/fa2-integrate.wgsl.js";
21
+ import { fa2RepulsionExactWgsl } from "./wgsl/fa2-repulsion-exact.wgsl.js";
22
+ import { fa2SpeedFinalizeWgsl } from "./wgsl/fa2-speed-finalize.wgsl.js";
23
+ import { fa2StatsFinalizeWgsl } from "./wgsl/fa2-stats-finalize.wgsl.js";
24
+ import { fa2ToSceneWgsl } from "./wgsl/fa2-to-scene.wgsl.js";
25
+ import { fillWgsl } from "./wgsl/fill.wgsl.js";
26
+ import { reduceWgsl } from "./wgsl/reduce.wgsl.js";
27
+ import { segmentedReduceWgsl } from "./wgsl/segmented-reduce.wgsl.js";
28
+
29
+ /** Every module id of P1-P3 (P4+ ids are appended, never renamed). */
30
+ export type KernelId =
31
+ | "degree"
32
+ | "reduce"
33
+ | "fill"
34
+ | "segmented-reduce"
35
+ | "fa2-stats-finalize"
36
+ | "fa2-attraction"
37
+ | "fa2-repulsion-exact"
38
+ | "fa2-speed-finalize"
39
+ | "fa2-integrate"
40
+ | "fa2-to-scene";
41
+
42
+ /** One registry entry: everything of a WgslModuleSpec except the per-variant overrides and snippets. */
43
+ export interface KernelEntry {
44
+ readonly id: KernelId;
45
+ readonly body: string;
46
+ readonly entryPoint: string;
47
+ readonly bindings: readonly BindingDecl[];
48
+ readonly overrideDecls: readonly OverrideDecl[];
49
+ readonly uniforms: readonly UniformBlock[];
50
+ /** ["subgroups"] when the body calls a reduction helper (the twin axis), else []. */
51
+ readonly needs: readonly "subgroups"[];
52
+ /** The snippet marker names the body carries (segmented-reduce: ["VALUE"]). */
53
+ readonly snippetSlots: readonly string[];
54
+ /** The phase the entry landed in (documentation and the compile-matrix filter). */
55
+ readonly phase: "P1" | "P2" | "P3";
56
+ }
57
+
58
+ // ---- the generated blocks (spec 5.3; contract 3.10.2): field order = byte order, offsets in the JSDoc
59
+
60
+ /** `RangeParams` (uniform, 32 B): rows `[start, end)` @0 / @4 of the dispatch, the bound arc window `[arcBase, arcEnd)` @8 / @12 (0 and arcCount when not windowed), `accumulate` @16 (1 combines into `out[i]` instead of overwriting: the P4 windowed loop), `n` @20 (the node count that bounds neighbour indices), `pad0` @24, `pad1` @28. */
61
+ export const RANGE_PARAMS: UniformBlock = UniformBlock.define("RangeParams", [
62
+ ["start", "u32"],
63
+ ["end", "u32"],
64
+ ["arcBase", "u32"],
65
+ ["arcEnd", "u32"],
66
+ ["accumulate", "u32"],
67
+ ["n", "u32"],
68
+ ["pad0", "u32"],
69
+ ["pad1", "u32"],
70
+ ]);
71
+
72
+ /** `ReduceParams` (uniform, 16 B): `count` @0 elements of the level, `outOffset` @4 (the element index the level writes at), `level` @8, `pad0` @12. */
73
+ export const REDUCE_PARAMS: UniformBlock = UniformBlock.define("ReduceParams", [
74
+ ["count", "u32"],
75
+ ["outOffset", "u32"],
76
+ ["level", "u32"],
77
+ ["pad0", "u32"],
78
+ ]);
79
+
80
+ /** `FillParams` (uniform, 16 B): `count` @0 words, `value` @4, `mode` @8 (0 = the constant `value`, 1 = iota `i + value`), `pad0` @12. */
81
+ export const FILL_PARAMS: UniformBlock = UniformBlock.define("FillParams", [
82
+ ["count", "u32"],
83
+ ["value", "u32"],
84
+ ["mode", "u32"],
85
+ ["pad0", "u32"],
86
+ ]);
87
+
88
+ /** `Fa2Params` (uniform, 96 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). */
89
+ export const FA2_PARAMS: UniformBlock = UniformBlock.define("Fa2Params", [
90
+ ["n", "u32"],
91
+ ["dim", "u32"],
92
+ ["flags", "u32"],
93
+ ["tierStart", "u32"],
94
+ ["tierEnd", "u32"],
95
+ ["iterationIndex", "u32"],
96
+ ["seed", "u32"],
97
+ ["nearMax", "u32"],
98
+ ["scalingRatio", "f32"],
99
+ ["gravity", "f32"],
100
+ ["jitterTolerance", "f32"],
101
+ ["scale", "f32"],
102
+ ["center", "vec4f"],
103
+ ["settleThreshold", "f32"],
104
+ ["extentFactor", "f32"],
105
+ ["gridMax", "u32"],
106
+ ["levels", "u32"],
107
+ ["pad", "vec4f"],
108
+ ]);
109
+
110
+ /** `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), `reserved0` .. `reserved8` @112 .. @240. */
111
+ export const FA2_STATE: UniformBlock = UniformBlock.define(
112
+ "Fa2State",
113
+ [
114
+ ["speed", "f32"],
115
+ ["speedEfficiency", "f32"],
116
+ ["swing", "f32"],
117
+ ["traction", "f32"],
118
+ ["centroid", "vec4f"],
119
+ ["rmsRadius", "f32"],
120
+ ["radius", "f32"],
121
+ ["meanDisplacement", "f32"],
122
+ ["iteration", "u32"],
123
+ ["min", "vec4f"],
124
+ ["max", "vec4f"],
125
+ ["gridMin", "vec4f"],
126
+ ["eps", "f32"],
127
+ ["settledCount", "u32"],
128
+ ["outsideGrid", "u32"],
129
+ ["maxCellOccupancy", "u32"],
130
+ ["reserved0", "vec4f"],
131
+ ["reserved1", "vec4f"],
132
+ ["reserved2", "vec4f"],
133
+ ["reserved3", "vec4f"],
134
+ ["reserved4", "vec4f"],
135
+ ["reserved5", "vec4f"],
136
+ ["reserved6", "vec4f"],
137
+ ["reserved7", "vec4f"],
138
+ ["reserved8", "vec4f"],
139
+ ],
140
+ { layout: "storage", padTo: STATE_HEADER_BYTES },
141
+ );
142
+
143
+ /** `Fa2Trace` (storage record, 32 B; spec 7.3): one per-iteration trace record -- `swing` @0, `traction` @4, `speed` @8, `speedEfficiency` @12 (written by K4), `meanDisplacement` @16, `settledCount` @20, `iteration` @24 (written by K1), `pad0` @28; the trace region is `array<Fa2Trace>` at byte offset STATE_HEADER_BYTES of the state buffer. */
144
+ export const FA2_TRACE: UniformBlock = UniformBlock.define(
145
+ "Fa2Trace",
146
+ [
147
+ ["swing", "f32"],
148
+ ["traction", "f32"],
149
+ ["speed", "f32"],
150
+ ["speedEfficiency", "f32"],
151
+ ["meanDisplacement", "f32"],
152
+ ["settledCount", "u32"],
153
+ ["iteration", "u32"],
154
+ ["pad0", "u32"],
155
+ ],
156
+ { layout: "storage" },
157
+ );
158
+
159
+ /** `Fa2Partial` (storage record, 64 B; spec 7.3): one per-workgroup partial -- `sum` @0 (xyz = sum of positions, w = sum of |p - centroid|^2), `min` @16, `max` @32 (w = max of |p - centroid|^2, the exact layoutRadius source), written by K5; `swingTraction` @48 (written by K3's epilogue); `dispFree` @56 (x = sum |dp| over free rows, y = the free count as an f32 <= 256, written by K5). */
160
+ export const FA2_PARTIAL: UniformBlock = UniformBlock.define(
161
+ "Fa2Partial",
162
+ [
163
+ ["sum", "vec4f"],
164
+ ["min", "vec4f"],
165
+ ["max", "vec4f"],
166
+ ["swingTraction", "vec2f"],
167
+ ["dispFree", "vec2f"],
168
+ ],
169
+ { layout: "storage" },
170
+ );
171
+
172
+ // ---- the entries (contract 3.10.1; group 0 = graph, 1 = state, 2 = params, 3 = cold)
173
+
174
+ /**
175
+ * One binding declaration of contract 3.9, so the tables below read like the rows of 3.10.1.
176
+ * @param group - the bind group (0 graph, 1 state, 2 params, 3 cold)
177
+ * @param binding - the slot inside the group
178
+ * @param name - the WGSL variable name (also the KernelBindings key)
179
+ * @param kind - "storage" (read_write), "storage-ro" (read) or "uniform"
180
+ * @param wgslType - the element / struct type text
181
+ * @returns the declaration
182
+ */
183
+ function decl(
184
+ group: 0 | 1 | 2 | 3,
185
+ binding: number,
186
+ name: string,
187
+ kind: BindingDecl["kind"],
188
+ wgslType: string,
189
+ ): BindingDecl {
190
+ return { group, binding, name, kind, wgslType };
191
+ }
192
+
193
+ /** The four group-0 graph slots of every row-walking kernel (spec 3.5): rowPtr, colIdx, weights | dummy, perm | dummy -- all read-only, so the dummies never alias a writable slot (3.10.1). */
194
+ const GRAPH_SLOTS: readonly BindingDecl[] = [
195
+ decl(0, 0, "rowPtr", "storage-ro", "array<u32>"),
196
+ decl(0, 1, "colIdx", "storage-ro", "array<u32>"),
197
+ decl(0, 2, "weights", "storage-ro", "array<f32>"),
198
+ decl(0, 3, "perm", "storage-ro", "array<u32>"),
199
+ ];
200
+
201
+ /** `degree` (3.10.1): 5 storage bindings; only the standard USE_PERM / HAS_WEIGHTS overrides. */
202
+ const DEGREE: KernelEntry = {
203
+ id: "degree",
204
+ body: degreeWgsl,
205
+ entryPoint: "degree",
206
+ bindings: GRAPH_SLOTS.concat(decl(1, 0, "out", "storage", "array<u32>"), decl(2, 0, "P", "uniform", "RangeParams")),
207
+ overrideDecls: [],
208
+ uniforms: [RANGE_PARAMS],
209
+ needs: [],
210
+ snippetSlots: [],
211
+ phase: "P1",
212
+ };
213
+
214
+ /** `reduce` (3.10.1): the multi-level reduction; OP 0 sum / 1 min / 2 max, DTYPE 0 f32 / 1 u32 / 2 vec4f, FINAL for the one-workgroup level; 2 storage bindings; calls the reduction helpers. */
215
+ const REDUCE: KernelEntry = {
216
+ id: "reduce",
217
+ body: reduceWgsl,
218
+ entryPoint: "reduce",
219
+ bindings: [
220
+ decl(1, 0, "src", "storage-ro", "array<u32>"),
221
+ decl(1, 1, "out", "storage", "array<u32>"),
222
+ decl(2, 0, "P", "uniform", "ReduceParams"),
223
+ ],
224
+ overrideDecls: [
225
+ { name: "OP", type: "u32", default: 0 },
226
+ { name: "DTYPE", type: "u32", default: 0 },
227
+ { name: "FINAL", type: "bool", default: false },
228
+ ],
229
+ uniforms: [REDUCE_PARAMS],
230
+ needs: ["subgroups"],
231
+ snippetSlots: [],
232
+ phase: "P1",
233
+ };
234
+
235
+ /** `fill` (3.10.1): dst[i] = value (mode 0) or i + value (mode 1) over `count` words; 1 storage binding. */
236
+ const FILL: KernelEntry = {
237
+ id: "fill",
238
+ body: fillWgsl,
239
+ entryPoint: "fill",
240
+ bindings: [decl(1, 0, "dst", "storage", "array<u32>"), decl(2, 0, "P", "uniform", "FillParams")],
241
+ overrideDecls: [],
242
+ uniforms: [FILL_PARAMS],
243
+ needs: [],
244
+ snippetSlots: [],
245
+ phase: "P1",
246
+ };
247
+
248
+ /** `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. */
249
+ const SEGMENTED_REDUCE: KernelEntry = {
250
+ id: "segmented-reduce",
251
+ body: segmentedReduceWgsl,
252
+ entryPoint: "segmented_reduce",
253
+ bindings: GRAPH_SLOTS.concat(decl(1, 0, "out", "storage", "array<f32>"), decl(2, 0, "P", "uniform", "RangeParams")),
254
+ overrideDecls: [
255
+ { name: "OP", type: "u32", default: 0 },
256
+ { name: "TIER", type: "u32", default: 0 },
257
+ ],
258
+ uniforms: [RANGE_PARAMS],
259
+ needs: [],
260
+ snippetSlots: ["VALUE"],
261
+ phase: "P2",
262
+ };
263
+
264
+ /** `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; 3 storage bindings; calls the reduction helpers. */
265
+ const FA2_STATS_FINALIZE: KernelEntry = {
266
+ id: "fa2-stats-finalize",
267
+ body: fa2StatsFinalizeWgsl,
268
+ entryPoint: "stats_finalize",
269
+ bindings: [
270
+ decl(1, 0, "partials", "storage-ro", "array<Fa2Partial>"),
271
+ decl(1, 1, "S", "storage", "Fa2State"),
272
+ decl(1, 2, "T", "storage", "array<Fa2Trace>"),
273
+ decl(2, 0, "P", "uniform", "Fa2Params"),
274
+ ],
275
+ overrideDecls: [],
276
+ uniforms: [FA2_PARAMS, FA2_STATE, FA2_TRACE, FA2_PARTIAL],
277
+ needs: ["subgroups"],
278
+ snippetSlots: [],
279
+ phase: "P3",
280
+ };
281
+
282
+ /** `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; 6 storage bindings. */
283
+ const FA2_ATTRACTION: KernelEntry = {
284
+ id: "fa2-attraction",
285
+ body: fa2AttractionWgsl,
286
+ entryPoint: "attraction",
287
+ bindings: GRAPH_SLOTS.concat(
288
+ decl(1, 0, "pos", "storage-ro", "array<vec4f>"),
289
+ decl(1, 1, "force", "storage", "array<f32>"),
290
+ decl(2, 0, "P", "uniform", "Fa2Params"),
291
+ ),
292
+ overrideDecls: [
293
+ { name: "LINLOG", type: "bool", default: false },
294
+ { name: "DISTRIBUTED", type: "bool", default: false },
295
+ { name: "TIER", type: "u32", default: 0 },
296
+ ],
297
+ uniforms: [FA2_PARAMS],
298
+ needs: [],
299
+ snippetSlots: [],
300
+ phase: "P3",
301
+ };
302
+
303
+ /** `fa2-repulsion-exact` (K3, 3.10.1): the tiled all-pairs repulsion with the gravity and swing / traction epilogue; 6 storage bindings (`oldForce` read-only: it only calls load_old); calls the reduction helpers. */
304
+ const FA2_REPULSION_EXACT: KernelEntry = {
305
+ id: "fa2-repulsion-exact",
306
+ body: fa2RepulsionExactWgsl,
307
+ entryPoint: "repulsion",
308
+ bindings: [
309
+ decl(1, 0, "pos", "storage-ro", "array<vec4f>"),
310
+ decl(1, 1, "S", "storage", "Fa2State"),
311
+ decl(1, 2, "force", "storage", "array<f32>"),
312
+ decl(1, 3, "oldForce", "storage-ro", "array<f32>"),
313
+ decl(1, 4, "fixedMask", "storage-ro", "array<u32>"),
314
+ decl(1, 5, "partials", "storage", "array<Fa2Partial>"),
315
+ decl(2, 0, "P", "uniform", "Fa2Params"),
316
+ ],
317
+ overrideDecls: [
318
+ { name: "SWING_MODE", type: "u32", default: 0 },
319
+ { name: "STRONG_GRAVITY", type: "bool", default: false },
320
+ { name: "GRAVITY_CENTER", type: "u32", default: 0 },
321
+ ],
322
+ uniforms: [FA2_PARAMS, FA2_STATE, FA2_PARTIAL],
323
+ needs: ["subgroups"],
324
+ snippetSlots: [],
325
+ phase: "P1",
326
+ };
327
+
328
+ /** `fa2-speed-finalize` (K4, 3.10.1): the one-workgroup partials fold and estimateFactor; 3 storage bindings; calls the reduction helpers. */
329
+ const FA2_SPEED_FINALIZE: KernelEntry = {
330
+ id: "fa2-speed-finalize",
331
+ body: fa2SpeedFinalizeWgsl,
332
+ entryPoint: "speed_finalize",
333
+ bindings: [
334
+ decl(1, 0, "partials", "storage-ro", "array<Fa2Partial>"),
335
+ decl(1, 1, "S", "storage", "Fa2State"),
336
+ decl(1, 2, "T", "storage", "array<Fa2Trace>"),
337
+ decl(2, 0, "P", "uniform", "Fa2Params"),
338
+ ],
339
+ overrideDecls: [{ name: "SWING_MODE", type: "u32", default: 0 }],
340
+ uniforms: [FA2_PARAMS, FA2_STATE, FA2_TRACE, FA2_PARTIAL],
341
+ needs: ["subgroups"],
342
+ snippetSlots: [],
343
+ phase: "P1",
344
+ };
345
+
346
+ /** `fa2-integrate` (K5, 3.10.1): the per-node speed factor and position update (no clamp, D25), `oldForce` stored in SWING_MODE 0, and the partials A / C of the next K1; 6 storage bindings; calls the reduction helpers. */
347
+ const FA2_INTEGRATE: KernelEntry = {
348
+ id: "fa2-integrate",
349
+ body: fa2IntegrateWgsl,
350
+ entryPoint: "integrate",
351
+ bindings: [
352
+ decl(1, 0, "force", "storage-ro", "array<f32>"),
353
+ decl(1, 1, "oldForce", "storage", "array<f32>"),
354
+ decl(1, 2, "fixedMask", "storage-ro", "array<u32>"),
355
+ decl(1, 3, "S", "storage", "Fa2State"),
356
+ decl(1, 4, "pos", "storage", "array<vec4f>"),
357
+ decl(1, 5, "partials", "storage", "array<Fa2Partial>"),
358
+ decl(2, 0, "P", "uniform", "Fa2Params"),
359
+ ],
360
+ overrideDecls: [{ name: "SWING_MODE", type: "u32", default: 0 }],
361
+ uniforms: [FA2_PARAMS, FA2_STATE, FA2_PARTIAL],
362
+ needs: ["subgroups"],
363
+ snippetSlots: [],
364
+ phase: "P3",
365
+ };
366
+
367
+ /** `fa2-to-scene` (3.10.1): the per-batch unpack of the vec4f layout positions into the stride-3 scene array with `scale` / `center` applied (z = center.z in 2D); 2 storage bindings. */
368
+ const FA2_TO_SCENE: KernelEntry = {
369
+ id: "fa2-to-scene",
370
+ body: fa2ToSceneWgsl,
371
+ entryPoint: "to_scene",
372
+ bindings: [
373
+ decl(1, 0, "pos", "storage-ro", "array<vec4f>"),
374
+ decl(1, 1, "scene", "storage", "array<f32>"),
375
+ decl(2, 0, "P", "uniform", "Fa2Params"),
376
+ ],
377
+ overrideDecls: [],
378
+ uniforms: [FA2_PARAMS],
379
+ needs: [],
380
+ snippetSlots: [],
381
+ phase: "P3",
382
+ };
383
+
384
+ /**
385
+ * The entries by id, in dispatch order. PLAN DECISION: `KernelId` is declared in full (contract 3.10) while the
386
+ * entries landed phase by phase, so the table is built as a Partial record and exported below through the
387
+ * contract's `Readonly<Record<KernelId, KernelEntry>>` type by one assertion; the runtime membership check of
388
+ * `entryOf` is the E_INVALID_ARGUMENT the contract documents for a JS caller's unknown id. P1-T4 landed the five
389
+ * P1 entries, P2-T2 `"segmented-reduce"`, and P3-T2 `"fa2-stats-finalize"`, `"fa2-attraction"`, `"fa2-integrate"`
390
+ * and `"fa2-to-scene"`, so every member of `KernelId` is present and the assertion is exact.
391
+ */
392
+ const REGISTRY: Readonly<Partial<Record<KernelId, KernelEntry>>> = Object.freeze({
393
+ degree: DEGREE,
394
+ reduce: REDUCE,
395
+ fill: FILL,
396
+ "segmented-reduce": SEGMENTED_REDUCE,
397
+ "fa2-stats-finalize": FA2_STATS_FINALIZE,
398
+ "fa2-attraction": FA2_ATTRACTION,
399
+ "fa2-repulsion-exact": FA2_REPULSION_EXACT,
400
+ "fa2-speed-finalize": FA2_SPEED_FINALIZE,
401
+ "fa2-integrate": FA2_INTEGRATE,
402
+ "fa2-to-scene": FA2_TO_SCENE,
403
+ });
404
+
405
+ /** THE registry (spec 3.5): every entry, keyed by id. */
406
+ export const KERNELS: Readonly<Record<KernelId, KernelEntry>> = REGISTRY as Readonly<Record<KernelId, KernelEntry>>;
407
+
408
+ /** The bodies installed by setKernelBodyOverride (the sabotage seam of spec 11.9 item 1), by id. */
409
+ const bodyOverrides = new Map<KernelId, string>();
410
+
411
+ /**
412
+ * The entry of an id, or E_INVALID_ARGUMENT for an id that is not (yet) registered.
413
+ * @param id - the module id
414
+ * @returns the entry
415
+ */
416
+ function entryOf(id: KernelId): KernelEntry {
417
+ const entry = REGISTRY[id];
418
+ if (entry === undefined) {
419
+ throw new WebGpuGraphError("E_INVALID_ARGUMENT", `unknown kernel id "${id}"`, {
420
+ argument: "id",
421
+ value: id,
422
+ expected: Object.keys(REGISTRY),
423
+ });
424
+ }
425
+ return entry;
426
+ }
427
+
428
+ /**
429
+ * A WgslModuleSpec for a variant: the entry plus the overrides / snippets given; unknown override names are
430
+ * rejected at compose time. A body override installed by setKernelBodyOverride is used instead of the entry's
431
+ * body.
432
+ * @param id - the module id
433
+ * @param overrides - the override values of this variant (the five standard names and the entry's overrideDecls)
434
+ * @param snippets - the texts substituted at the body's `//@@NAME@@` markers
435
+ * @returns the spec the PipelineCache compiles
436
+ */
437
+ export function kernelSpec(
438
+ id: KernelId,
439
+ overrides?: Readonly<Record<string, number | boolean>>,
440
+ snippets?: Readonly<Record<string, string>>,
441
+ ): WgslModuleSpec {
442
+ const entry = entryOf(id);
443
+ return {
444
+ id: entry.id,
445
+ body: bodyOverrides.get(id) ?? entry.body,
446
+ bindings: entry.bindings,
447
+ overrideDecls: entry.overrideDecls,
448
+ overrides: overrides ?? {},
449
+ needs: entry.needs,
450
+ uniforms: entry.uniforms,
451
+ snippets,
452
+ };
453
+ }
454
+
455
+ /**
456
+ * The sabotage seam (spec 11.9 item 1): replaces an entry's body for specs created afterwards (null restores).
457
+ * Tests use a FRESH context per mutation because the pipeline key does not include the body.
458
+ * @internal
459
+ * @param id - the module id
460
+ * @param body - the replacement body, or null to restore the normative one
461
+ */
462
+ export function setKernelBodyOverride(id: KernelId, body: string | null): void {
463
+ entryOf(id);
464
+ if (body === null) {
465
+ bodyOverrides.delete(id);
466
+ } else {
467
+ bodyOverrides.set(id, body);
468
+ }
469
+ }
470
+
471
+ /**
472
+ * The group-0 graph bindings of a core with the dummy rules applied (spec 3.5, 4.1): colIdx <- rowPtr when null,
473
+ * weights <- colIdx ?? rowPtr when null, perm <- rowPtr when null. `weights` is the binding to use in the weights
474
+ * slot: omitted -> core.weights (degree, segmented-reduce); a layout passes its RESOLVED weights
475
+ * (ModelResources.weights, 3.13), null meaning "attract with 1.0" even on a weighted snapshot.
476
+ * @param core - the resident core arrays of a snapshot
477
+ * @param perm - the degreeOrder row permutation, or null (rowPtr is bound as the dummy)
478
+ * @param weights - the weights binding to use; undefined takes the core's, null binds the colIdx dummy
479
+ * @returns the four bindings in slot order
480
+ */
481
+ export function graphBindings(
482
+ core: CoreBinding,
483
+ perm: Binding | null,
484
+ weights?: Binding | null,
485
+ ): Readonly<Record<"rowPtr" | "colIdx" | "weights" | "perm", Binding>> {
486
+ const colIdx = core.colIdx ?? core.rowPtr;
487
+ const resolved = weights === undefined ? core.weights : weights;
488
+ return {
489
+ rowPtr: core.rowPtr,
490
+ colIdx,
491
+ weights: resolved ?? colIdx,
492
+ perm: perm ?? core.rowPtr,
493
+ };
494
+ }
495
+
496
+ /**
497
+ * The override values graphBindings implies: USE_PERM = perm !== null, HAS_WEIGHTS = (weights === undefined ?
498
+ * core.weights : weights) !== null.
499
+ * @param core - the resident core arrays of a snapshot
500
+ * @param perm - the row permutation binding, or null
501
+ * @param weights - the weights binding to use; undefined takes the core's, null means "unweighted"
502
+ * @returns the two standard override values of the variant
503
+ */
504
+ export function graphOverrides(
505
+ core: CoreBinding,
506
+ perm: Binding | null,
507
+ weights?: Binding | null,
508
+ ): Readonly<{ USE_PERM: boolean; HAS_WEIGHTS: boolean }> {
509
+ const resolved = weights === undefined ? core.weights : weights;
510
+ return { USE_PERM: perm !== null, HAS_WEIGHTS: resolved !== null };
511
+ }