@363045841yyt/klinechart-core 0.8.11-alpha.0 → 0.8.12

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 (578) hide show
  1. package/dist/alerts/createAlertController.d.ts +30 -0
  2. package/dist/alerts/createAlertController.d.ts.map +1 -0
  3. package/dist/alerts/createAlertController.js +261 -0
  4. package/dist/alerts/createAlertController.js.map +1 -0
  5. package/dist/alerts/index.d.ts +5 -0
  6. package/dist/alerts/index.d.ts.map +1 -0
  7. package/dist/alerts/index.js +4 -0
  8. package/dist/alerts/index.js.map +1 -0
  9. package/dist/alerts/predicates.d.ts +28 -0
  10. package/dist/alerts/predicates.d.ts.map +1 -0
  11. package/dist/alerts/predicates.js +211 -0
  12. package/dist/alerts/predicates.js.map +1 -0
  13. package/dist/alerts/rollingVolume.d.ts +15 -0
  14. package/dist/alerts/rollingVolume.d.ts.map +1 -0
  15. package/dist/alerts/rollingVolume.js +42 -0
  16. package/dist/alerts/rollingVolume.js.map +1 -0
  17. package/dist/alerts/ruleSchema.d.ts +42 -0
  18. package/dist/alerts/ruleSchema.d.ts.map +1 -0
  19. package/dist/alerts/ruleSchema.js +240 -0
  20. package/dist/alerts/ruleSchema.js.map +1 -0
  21. package/dist/alerts/types.d.ts +169 -0
  22. package/dist/alerts/types.d.ts.map +1 -0
  23. package/dist/alerts/types.js +24 -0
  24. package/dist/alerts/types.js.map +1 -0
  25. package/dist/chartTypes/heikinAshi.d.ts +49 -0
  26. package/dist/chartTypes/heikinAshi.d.ts.map +1 -0
  27. package/dist/chartTypes/heikinAshi.js +94 -0
  28. package/dist/chartTypes/heikinAshi.js.map +1 -0
  29. package/dist/chartTypes/index.d.ts +6 -0
  30. package/dist/chartTypes/index.d.ts.map +1 -0
  31. package/dist/chartTypes/index.js +5 -0
  32. package/dist/chartTypes/index.js.map +1 -0
  33. package/dist/chartTypes/pointAndFigure.d.ts +83 -0
  34. package/dist/chartTypes/pointAndFigure.d.ts.map +1 -0
  35. package/dist/chartTypes/pointAndFigure.js +159 -0
  36. package/dist/chartTypes/pointAndFigure.js.map +1 -0
  37. package/dist/chartTypes/rangeBars.d.ts +61 -0
  38. package/dist/chartTypes/rangeBars.d.ts.map +1 -0
  39. package/dist/chartTypes/rangeBars.js +181 -0
  40. package/dist/chartTypes/rangeBars.js.map +1 -0
  41. package/dist/chartTypes/renko.d.ts +94 -0
  42. package/dist/chartTypes/renko.d.ts.map +1 -0
  43. package/dist/chartTypes/renko.js +207 -0
  44. package/dist/chartTypes/renko.js.map +1 -0
  45. package/dist/chartTypes/types.d.ts +97 -0
  46. package/dist/chartTypes/types.d.ts.map +1 -0
  47. package/dist/chartTypes/types.js +39 -0
  48. package/dist/chartTypes/types.js.map +1 -0
  49. package/dist/components/anchoredVwap/computeAnchoredVwap.d.ts +76 -0
  50. package/dist/components/anchoredVwap/computeAnchoredVwap.d.ts.map +1 -0
  51. package/dist/components/anchoredVwap/computeAnchoredVwap.js +155 -0
  52. package/dist/components/anchoredVwap/computeAnchoredVwap.js.map +1 -0
  53. package/dist/components/anchoredVwap/createAnchoredVwapController.d.ts +28 -0
  54. package/dist/components/anchoredVwap/createAnchoredVwapController.d.ts.map +1 -0
  55. package/dist/components/anchoredVwap/createAnchoredVwapController.js +282 -0
  56. package/dist/components/anchoredVwap/createAnchoredVwapController.js.map +1 -0
  57. package/dist/components/anchoredVwap/index.d.ts +11 -0
  58. package/dist/components/anchoredVwap/index.d.ts.map +1 -0
  59. package/dist/components/anchoredVwap/index.js +10 -0
  60. package/dist/components/anchoredVwap/index.js.map +1 -0
  61. package/dist/components/anchoredVwap/types.d.ts +171 -0
  62. package/dist/components/anchoredVwap/types.d.ts.map +1 -0
  63. package/dist/components/anchoredVwap/types.js +25 -0
  64. package/dist/components/anchoredVwap/types.js.map +1 -0
  65. package/dist/components/crosshairSync/createCrosshairSync.d.ts +94 -0
  66. package/dist/components/crosshairSync/createCrosshairSync.d.ts.map +1 -0
  67. package/dist/components/crosshairSync/createCrosshairSync.js +118 -0
  68. package/dist/components/crosshairSync/createCrosshairSync.js.map +1 -0
  69. package/dist/components/crosshairSync/index.d.ts +2 -0
  70. package/dist/components/crosshairSync/index.d.ts.map +1 -0
  71. package/dist/components/crosshairSync/index.js +2 -0
  72. package/dist/components/crosshairSync/index.js.map +1 -0
  73. package/dist/components/footprint/aggressor.d.ts +89 -0
  74. package/dist/components/footprint/aggressor.d.ts.map +1 -0
  75. package/dist/components/footprint/aggressor.js +106 -0
  76. package/dist/components/footprint/aggressor.js.map +1 -0
  77. package/dist/components/footprint/createFootprintController.d.ts +29 -0
  78. package/dist/components/footprint/createFootprintController.d.ts.map +1 -0
  79. package/dist/components/footprint/createFootprintController.js +264 -0
  80. package/dist/components/footprint/createFootprintController.js.map +1 -0
  81. package/dist/components/footprint/index.d.ts +7 -0
  82. package/dist/components/footprint/index.d.ts.map +1 -0
  83. package/dist/components/footprint/index.js +4 -0
  84. package/dist/components/footprint/index.js.map +1 -0
  85. package/dist/components/footprint/perBarStats.d.ts +63 -0
  86. package/dist/components/footprint/perBarStats.d.ts.map +1 -0
  87. package/dist/components/footprint/perBarStats.js +123 -0
  88. package/dist/components/footprint/perBarStats.js.map +1 -0
  89. package/dist/components/footprint/types.d.ts +197 -0
  90. package/dist/components/footprint/types.d.ts.map +1 -0
  91. package/dist/components/footprint/types.js +23 -0
  92. package/dist/components/footprint/types.js.map +1 -0
  93. package/dist/components/mtfOverlay/alignToBaseIndex.d.ts +45 -0
  94. package/dist/components/mtfOverlay/alignToBaseIndex.d.ts.map +1 -0
  95. package/dist/components/mtfOverlay/alignToBaseIndex.js +92 -0
  96. package/dist/components/mtfOverlay/alignToBaseIndex.js.map +1 -0
  97. package/dist/components/mtfOverlay/createMtfController.d.ts +7 -0
  98. package/dist/components/mtfOverlay/createMtfController.d.ts.map +1 -0
  99. package/dist/components/mtfOverlay/createMtfController.js +134 -0
  100. package/dist/components/mtfOverlay/createMtfController.js.map +1 -0
  101. package/dist/components/mtfOverlay/index.d.ts +19 -0
  102. package/dist/components/mtfOverlay/index.d.ts.map +1 -0
  103. package/dist/components/mtfOverlay/index.js +18 -0
  104. package/dist/components/mtfOverlay/index.js.map +1 -0
  105. package/dist/components/mtfOverlay/resampleBars.d.ts +37 -0
  106. package/dist/components/mtfOverlay/resampleBars.d.ts.map +1 -0
  107. package/dist/components/mtfOverlay/resampleBars.js +93 -0
  108. package/dist/components/mtfOverlay/resampleBars.js.map +1 -0
  109. package/dist/components/mtfOverlay/types.d.ts +132 -0
  110. package/dist/components/mtfOverlay/types.d.ts.map +1 -0
  111. package/dist/components/mtfOverlay/types.js +22 -0
  112. package/dist/components/mtfOverlay/types.js.map +1 -0
  113. package/dist/components/orderBookHeatmap/createHeatmapController.d.ts +21 -0
  114. package/dist/components/orderBookHeatmap/createHeatmapController.d.ts.map +1 -0
  115. package/dist/components/orderBookHeatmap/createHeatmapController.js +234 -0
  116. package/dist/components/orderBookHeatmap/createHeatmapController.js.map +1 -0
  117. package/dist/components/orderBookHeatmap/createOrderBookState.d.ts +18 -0
  118. package/dist/components/orderBookHeatmap/createOrderBookState.d.ts.map +1 -0
  119. package/dist/components/orderBookHeatmap/createOrderBookState.js +102 -0
  120. package/dist/components/orderBookHeatmap/createOrderBookState.js.map +1 -0
  121. package/dist/components/orderBookHeatmap/deltaArchive.d.ts +25 -0
  122. package/dist/components/orderBookHeatmap/deltaArchive.d.ts.map +1 -0
  123. package/dist/components/orderBookHeatmap/deltaArchive.js +106 -0
  124. package/dist/components/orderBookHeatmap/deltaArchive.js.map +1 -0
  125. package/dist/components/orderBookHeatmap/index.d.ts +13 -0
  126. package/dist/components/orderBookHeatmap/index.d.ts.map +1 -0
  127. package/dist/components/orderBookHeatmap/index.js +12 -0
  128. package/dist/components/orderBookHeatmap/index.js.map +1 -0
  129. package/dist/components/orderBookHeatmap/logColorScale.d.ts +20 -0
  130. package/dist/components/orderBookHeatmap/logColorScale.d.ts.map +1 -0
  131. package/dist/components/orderBookHeatmap/logColorScale.js +71 -0
  132. package/dist/components/orderBookHeatmap/logColorScale.js.map +1 -0
  133. package/dist/components/orderBookHeatmap/snapshotRing.d.ts +16 -0
  134. package/dist/components/orderBookHeatmap/snapshotRing.d.ts.map +1 -0
  135. package/dist/components/orderBookHeatmap/snapshotRing.js +61 -0
  136. package/dist/components/orderBookHeatmap/snapshotRing.js.map +1 -0
  137. package/dist/components/orderBookHeatmap/types.d.ts +128 -0
  138. package/dist/components/orderBookHeatmap/types.d.ts.map +1 -0
  139. package/dist/components/orderBookHeatmap/types.js +16 -0
  140. package/dist/components/orderBookHeatmap/types.js.map +1 -0
  141. package/dist/components/volumeProfile/binning.d.ts +54 -0
  142. package/dist/components/volumeProfile/binning.d.ts.map +1 -0
  143. package/dist/components/volumeProfile/binning.js +123 -0
  144. package/dist/components/volumeProfile/binning.js.map +1 -0
  145. package/dist/components/volumeProfile/createVolumeProfileController.d.ts +31 -0
  146. package/dist/components/volumeProfile/createVolumeProfileController.d.ts.map +1 -0
  147. package/dist/components/volumeProfile/createVolumeProfileController.js +179 -0
  148. package/dist/components/volumeProfile/createVolumeProfileController.js.map +1 -0
  149. package/dist/components/volumeProfile/index.d.ts +6 -0
  150. package/dist/components/volumeProfile/index.d.ts.map +1 -0
  151. package/dist/components/volumeProfile/index.js +5 -0
  152. package/dist/components/volumeProfile/index.js.map +1 -0
  153. package/dist/components/volumeProfile/poc.d.ts +35 -0
  154. package/dist/components/volumeProfile/poc.d.ts.map +1 -0
  155. package/dist/components/volumeProfile/poc.js +51 -0
  156. package/dist/components/volumeProfile/poc.js.map +1 -0
  157. package/dist/components/volumeProfile/types.d.ts +138 -0
  158. package/dist/components/volumeProfile/types.d.ts.map +1 -0
  159. package/dist/components/volumeProfile/types.js +19 -0
  160. package/dist/components/volumeProfile/types.js.map +1 -0
  161. package/dist/components/volumeProfile/valueArea.d.ts +58 -0
  162. package/dist/components/volumeProfile/valueArea.d.ts.map +1 -0
  163. package/dist/components/volumeProfile/valueArea.js +172 -0
  164. package/dist/components/volumeProfile/valueArea.js.map +1 -0
  165. package/dist/controllers/createChartController.d.ts.map +1 -1
  166. package/dist/controllers/createChartController.js +22 -11
  167. package/dist/controllers/createChartController.js.map +1 -1
  168. package/dist/controllers/types.d.ts +3 -1
  169. package/dist/controllers/types.d.ts.map +1 -1
  170. package/dist/data-fetchers/baostock.d.ts.map +1 -1
  171. package/dist/data-fetchers/baostock.js +2 -1
  172. package/dist/data-fetchers/baostock.js.map +1 -1
  173. package/dist/data-fetchers/dataBuffer.effects.d.ts.map +1 -1
  174. package/dist/data-fetchers/dataBuffer.effects.js +2 -1
  175. package/dist/data-fetchers/dataBuffer.effects.js.map +1 -1
  176. package/dist/data-fetchers/fetcherDefinitionRegistry.d.ts.map +1 -1
  177. package/dist/data-fetchers/fetcherDefinitionRegistry.js +2 -1
  178. package/dist/data-fetchers/fetcherDefinitionRegistry.js.map +1 -1
  179. package/dist/data-fetchers/gotdx.js +4 -3
  180. package/dist/data-fetchers/gotdx.js.map +1 -1
  181. package/dist/data-fetchers/router.d.ts.map +1 -1
  182. package/dist/data-fetchers/router.js +4 -3
  183. package/dist/data-fetchers/router.js.map +1 -1
  184. package/dist/data-fetchers/tradingview.js +3 -2
  185. package/dist/data-fetchers/tradingview.js.map +1 -1
  186. package/dist/engine/chart.d.ts +13 -0
  187. package/dist/engine/chart.d.ts.map +1 -1
  188. package/dist/engine/chart.js +109 -0
  189. package/dist/engine/chart.js.map +1 -1
  190. package/dist/engine/data/chartDataManager.d.ts +1 -0
  191. package/dist/engine/data/chartDataManager.d.ts.map +1 -1
  192. package/dist/engine/data/chartDataManager.js +27 -4
  193. package/dist/engine/data/chartDataManager.js.map +1 -1
  194. package/dist/engine/drawing/toolConfig.d.ts.map +1 -1
  195. package/dist/engine/drawing/toolConfig.js +2 -1
  196. package/dist/engine/drawing/toolConfig.js.map +1 -1
  197. package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
  198. package/dist/engine/indicators/chartIndicatorManager.js +2 -1
  199. package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
  200. package/dist/engine/indicators/indicatorDefinitionRegistry.d.ts.map +1 -1
  201. package/dist/engine/indicators/indicatorDefinitionRegistry.js +2 -1
  202. package/dist/engine/indicators/indicatorDefinitionRegistry.js.map +1 -1
  203. package/dist/engine/indicators/indicatorMetadata.d.ts.map +1 -1
  204. package/dist/engine/indicators/indicatorMetadata.js +2 -1
  205. package/dist/engine/indicators/indicatorMetadata.js.map +1 -1
  206. package/dist/engine/indicators/indicatorRegistry.d.ts.map +1 -1
  207. package/dist/engine/indicators/indicatorRegistry.js +6 -5
  208. package/dist/engine/indicators/indicatorRegistry.js.map +1 -1
  209. package/dist/engine/indicators/registerBuiltins.d.ts.map +1 -1
  210. package/dist/engine/indicators/registerBuiltins.js +2 -1
  211. package/dist/engine/indicators/registerBuiltins.js.map +1 -1
  212. package/dist/engine/indicators/scheduler.d.ts +6 -0
  213. package/dist/engine/indicators/scheduler.d.ts.map +1 -1
  214. package/dist/engine/indicators/scheduler.js +12 -0
  215. package/dist/engine/indicators/scheduler.js.map +1 -1
  216. package/dist/engine/indicators/soa.d.ts.map +1 -1
  217. package/dist/engine/indicators/soa.js +2 -1
  218. package/dist/engine/indicators/soa.js.map +1 -1
  219. package/dist/engine/indicators/stateComposer.d.ts.map +1 -1
  220. package/dist/engine/indicators/stateComposer.js +2 -1
  221. package/dist/engine/indicators/stateComposer.js.map +1 -1
  222. package/dist/engine/subPaneManager.d.ts.map +1 -1
  223. package/dist/engine/subPaneManager.js +2 -1
  224. package/dist/engine/subPaneManager.js.map +1 -1
  225. package/dist/engine/utils/chartZoomController.d.ts +2 -0
  226. package/dist/engine/utils/chartZoomController.d.ts.map +1 -1
  227. package/dist/engine/utils/chartZoomController.js +5 -1
  228. package/dist/engine/utils/chartZoomController.js.map +1 -1
  229. package/dist/errors-help.d.ts +72 -0
  230. package/dist/errors-help.d.ts.map +1 -0
  231. package/dist/errors-help.js +149 -0
  232. package/dist/errors-help.js.map +1 -0
  233. package/dist/errors.d.ts +64 -0
  234. package/dist/errors.d.ts.map +1 -0
  235. package/dist/errors.js +61 -0
  236. package/dist/errors.js.map +1 -0
  237. package/dist/index.d.ts +18 -0
  238. package/dist/index.d.ts.map +1 -1
  239. package/dist/index.js +22 -0
  240. package/dist/index.js.map +1 -1
  241. package/dist/indicators/alma.d.ts +27 -0
  242. package/dist/indicators/alma.d.ts.map +1 -0
  243. package/dist/indicators/alma.js +36 -0
  244. package/dist/indicators/alma.js.map +1 -0
  245. package/dist/indicators/awesomeOscillator.d.ts +19 -0
  246. package/dist/indicators/awesomeOscillator.d.ts.map +1 -0
  247. package/dist/indicators/awesomeOscillator.js +36 -0
  248. package/dist/indicators/awesomeOscillator.js.map +1 -0
  249. package/dist/indicators/dpo.d.ts +17 -0
  250. package/dist/indicators/dpo.d.ts.map +1 -0
  251. package/dist/indicators/dpo.js +24 -0
  252. package/dist/indicators/dpo.js.map +1 -0
  253. package/dist/indicators/fisherTransform.d.ts +26 -0
  254. package/dist/indicators/fisherTransform.d.ts.map +1 -0
  255. package/dist/indicators/fisherTransform.js +44 -0
  256. package/dist/indicators/fisherTransform.js.map +1 -0
  257. package/dist/indicators/frama.d.ts +25 -0
  258. package/dist/indicators/frama.d.ts.map +1 -0
  259. package/dist/indicators/frama.js +61 -0
  260. package/dist/indicators/frama.js.map +1 -0
  261. package/dist/indicators/index.d.ts +24 -0
  262. package/dist/indicators/index.d.ts.map +1 -0
  263. package/dist/indicators/index.js +26 -0
  264. package/dist/indicators/index.js.map +1 -0
  265. package/dist/indicators/lsma.d.ts +21 -0
  266. package/dist/indicators/lsma.d.ts.map +1 -0
  267. package/dist/indicators/lsma.js +37 -0
  268. package/dist/indicators/lsma.js.map +1 -0
  269. package/dist/indicators/schaffTrendCycle.d.ts +24 -0
  270. package/dist/indicators/schaffTrendCycle.d.ts.map +1 -0
  271. package/dist/indicators/schaffTrendCycle.js +70 -0
  272. package/dist/indicators/schaffTrendCycle.js.map +1 -0
  273. package/dist/indicators/stochRSI.d.ts +24 -0
  274. package/dist/indicators/stochRSI.d.ts.map +1 -0
  275. package/dist/indicators/stochRSI.js +86 -0
  276. package/dist/indicators/stochRSI.js.map +1 -0
  277. package/dist/indicators/t3.d.ts +31 -0
  278. package/dist/indicators/t3.d.ts.map +1 -0
  279. package/dist/indicators/t3.js +63 -0
  280. package/dist/indicators/t3.js.map +1 -0
  281. package/dist/indicators/ultimateOscillator.d.ts +26 -0
  282. package/dist/indicators/ultimateOscillator.d.ts.map +1 -0
  283. package/dist/indicators/ultimateOscillator.js +59 -0
  284. package/dist/indicators/ultimateOscillator.js.map +1 -0
  285. package/dist/indicators/vidya.d.ts +24 -0
  286. package/dist/indicators/vidya.d.ts.map +1 -0
  287. package/dist/indicators/vidya.js +54 -0
  288. package/dist/indicators/vidya.js.map +1 -0
  289. package/dist/indicators/zlema.d.ts +16 -0
  290. package/dist/indicators/zlema.d.ts.map +1 -0
  291. package/dist/indicators/zlema.js +26 -0
  292. package/dist/indicators/zlema.js.map +1 -0
  293. package/dist/input/gesture.d.ts +125 -0
  294. package/dist/input/gesture.d.ts.map +1 -0
  295. package/dist/input/gesture.js +249 -0
  296. package/dist/input/gesture.js.map +1 -0
  297. package/dist/input/index.d.ts +9 -0
  298. package/dist/input/index.d.ts.map +1 -0
  299. package/dist/input/index.js +9 -0
  300. package/dist/input/index.js.map +1 -0
  301. package/dist/input/keyboard.d.ts +140 -0
  302. package/dist/input/keyboard.d.ts.map +1 -0
  303. package/dist/input/keyboard.js +260 -0
  304. package/dist/input/keyboard.js.map +1 -0
  305. package/dist/mcp/chartBridge.d.ts +2 -2
  306. package/dist/mcp/chartBridge.d.ts.map +1 -1
  307. package/dist/mcp/chartBridge.js +27 -21
  308. package/dist/mcp/chartBridge.js.map +1 -1
  309. package/dist/plugin/PluginHost.d.ts +0 -3
  310. package/dist/plugin/PluginHost.d.ts.map +1 -1
  311. package/dist/plugin/PluginHost.js +7 -3
  312. package/dist/plugin/PluginHost.js.map +1 -1
  313. package/dist/plugin/PluginRegistry.d.ts +0 -3
  314. package/dist/plugin/PluginRegistry.d.ts.map +1 -1
  315. package/dist/plugin/PluginRegistry.js +5 -1
  316. package/dist/plugin/PluginRegistry.js.map +1 -1
  317. package/dist/render/Renderer.d.ts +116 -0
  318. package/dist/render/Renderer.d.ts.map +1 -0
  319. package/dist/render/Renderer.js +31 -0
  320. package/dist/render/Renderer.js.map +1 -0
  321. package/dist/render/SurfaceBackend.d.ts +83 -0
  322. package/dist/render/SurfaceBackend.d.ts.map +1 -0
  323. package/dist/render/SurfaceBackend.js +27 -0
  324. package/dist/render/SurfaceBackend.js.map +1 -0
  325. package/dist/render/index.d.ts +12 -0
  326. package/dist/render/index.d.ts.map +1 -0
  327. package/dist/render/index.js +11 -0
  328. package/dist/render/index.js.map +1 -0
  329. package/dist/renderer-tier/detectRendererTier.d.ts +57 -0
  330. package/dist/renderer-tier/detectRendererTier.d.ts.map +1 -0
  331. package/dist/renderer-tier/detectRendererTier.js +143 -0
  332. package/dist/renderer-tier/detectRendererTier.js.map +1 -0
  333. package/dist/renderer-tier/index.d.ts +12 -0
  334. package/dist/renderer-tier/index.d.ts.map +1 -0
  335. package/dist/renderer-tier/index.js +12 -0
  336. package/dist/renderer-tier/index.js.map +1 -0
  337. package/dist/renderer-tier/selectBackend.d.ts +106 -0
  338. package/dist/renderer-tier/selectBackend.d.ts.map +1 -0
  339. package/dist/renderer-tier/selectBackend.js +113 -0
  340. package/dist/renderer-tier/selectBackend.js.map +1 -0
  341. package/dist/renderer-tier/types.d.ts +50 -0
  342. package/dist/renderer-tier/types.d.ts.map +1 -0
  343. package/dist/renderer-tier/types.js +24 -0
  344. package/dist/renderer-tier/types.js.map +1 -0
  345. package/dist/replay/createReplayController.d.ts +3 -0
  346. package/dist/replay/createReplayController.d.ts.map +1 -0
  347. package/dist/replay/createReplayController.js +251 -0
  348. package/dist/replay/createReplayController.js.map +1 -0
  349. package/dist/replay/index.d.ts +5 -0
  350. package/dist/replay/index.d.ts.map +1 -0
  351. package/dist/replay/index.js +3 -0
  352. package/dist/replay/index.js.map +1 -0
  353. package/dist/replay/timeline.d.ts +50 -0
  354. package/dist/replay/timeline.d.ts.map +1 -0
  355. package/dist/replay/timeline.js +104 -0
  356. package/dist/replay/timeline.js.map +1 -0
  357. package/dist/replay/types.d.ts +94 -0
  358. package/dist/replay/types.d.ts.map +1 -0
  359. package/dist/replay/types.js +21 -0
  360. package/dist/replay/types.js.map +1 -0
  361. package/dist/scale/anchoredZoom.d.ts +64 -0
  362. package/dist/scale/anchoredZoom.d.ts.map +1 -0
  363. package/dist/scale/anchoredZoom.js +67 -0
  364. package/dist/scale/anchoredZoom.js.map +1 -0
  365. package/dist/scale/createPriceScale.d.ts +49 -0
  366. package/dist/scale/createPriceScale.d.ts.map +1 -0
  367. package/dist/scale/createPriceScale.js +175 -0
  368. package/dist/scale/createPriceScale.js.map +1 -0
  369. package/dist/scale/createTimeScale.d.ts +27 -0
  370. package/dist/scale/createTimeScale.d.ts.map +1 -0
  371. package/dist/scale/createTimeScale.js +139 -0
  372. package/dist/scale/createTimeScale.js.map +1 -0
  373. package/dist/scale/index.d.ts +6 -0
  374. package/dist/scale/index.d.ts.map +1 -0
  375. package/dist/scale/index.js +5 -0
  376. package/dist/scale/index.js.map +1 -0
  377. package/dist/scale/originShift.d.ts +69 -0
  378. package/dist/scale/originShift.d.ts.map +1 -0
  379. package/dist/scale/originShift.js +41 -0
  380. package/dist/scale/originShift.js.map +1 -0
  381. package/dist/scale/types.d.ts +90 -0
  382. package/dist/scale/types.d.ts.map +1 -0
  383. package/dist/scale/types.js +17 -0
  384. package/dist/scale/types.js.map +1 -0
  385. package/dist/scene/createScene.d.ts +22 -0
  386. package/dist/scene/createScene.d.ts.map +1 -0
  387. package/dist/scene/createScene.js +114 -0
  388. package/dist/scene/createScene.js.map +1 -0
  389. package/dist/scene/index.d.ts +13 -0
  390. package/dist/scene/index.d.ts.map +1 -0
  391. package/dist/scene/index.js +11 -0
  392. package/dist/scene/index.js.map +1 -0
  393. package/dist/scene/layerRegistry.d.ts +83 -0
  394. package/dist/scene/layerRegistry.d.ts.map +1 -0
  395. package/dist/scene/layerRegistry.js +43 -0
  396. package/dist/scene/layerRegistry.js.map +1 -0
  397. package/dist/scene/types.d.ts +128 -0
  398. package/dist/scene/types.d.ts.map +1 -0
  399. package/dist/scene/types.js +21 -0
  400. package/dist/scene/types.js.map +1 -0
  401. package/dist/scheduler/createFrameBudget.d.ts +91 -0
  402. package/dist/scheduler/createFrameBudget.d.ts.map +1 -0
  403. package/dist/scheduler/createFrameBudget.js +232 -0
  404. package/dist/scheduler/createFrameBudget.js.map +1 -0
  405. package/dist/scheduler/index.d.ts +2 -0
  406. package/dist/scheduler/index.d.ts.map +1 -0
  407. package/dist/scheduler/index.js +2 -0
  408. package/dist/scheduler/index.js.map +1 -0
  409. package/dist/tokens/themeToCssVars.d.ts.map +1 -1
  410. package/dist/tokens/themeToCssVars.js +2 -28
  411. package/dist/tokens/themeToCssVars.js.map +1 -1
  412. package/dist/version.d.ts +1 -1
  413. package/dist/version.d.ts.map +1 -1
  414. package/dist/version.js +1 -1
  415. package/dist/version.js.map +1 -1
  416. package/package.json +1 -1
  417. package/src/__bench__/indicators.bench.ts +215 -0
  418. package/src/__bench__/orderBookHeatmap.bench.ts +68 -0
  419. package/src/__bench__/scale.bench.ts +80 -0
  420. package/src/__bench__/signal.bench.ts +41 -0
  421. package/src/__bench__/volumeProfile.bench.ts +66 -0
  422. package/src/__tests__/errors-help.test.ts +184 -0
  423. package/src/__tests__/errors.test.ts +187 -0
  424. package/src/alerts/__tests__/controller.test.ts +231 -0
  425. package/src/alerts/__tests__/predicates.test.ts +374 -0
  426. package/src/alerts/__tests__/ruleSchema.test.ts +180 -0
  427. package/src/alerts/createAlertController.ts +312 -0
  428. package/src/alerts/index.ts +18 -0
  429. package/src/alerts/predicates.ts +231 -0
  430. package/src/alerts/rollingVolume.ts +51 -0
  431. package/src/alerts/ruleSchema.ts +278 -0
  432. package/src/alerts/types.ts +177 -0
  433. package/src/chartTypes/__tests__/heikinAshi.test.ts +122 -0
  434. package/src/chartTypes/__tests__/pointAndFigure.test.ts +167 -0
  435. package/src/chartTypes/__tests__/rangeBars.test.ts +146 -0
  436. package/src/chartTypes/__tests__/renko.test.ts +160 -0
  437. package/src/chartTypes/heikinAshi.ts +116 -0
  438. package/src/chartTypes/index.ts +5 -0
  439. package/src/chartTypes/pointAndFigure.ts +278 -0
  440. package/src/chartTypes/rangeBars.ts +303 -0
  441. package/src/chartTypes/renko.ts +311 -0
  442. package/src/chartTypes/types.ts +99 -0
  443. package/src/components/anchoredVwap/__tests__/computeAnchoredVwap.test.ts +331 -0
  444. package/src/components/anchoredVwap/__tests__/controller.test.ts +430 -0
  445. package/src/components/anchoredVwap/computeAnchoredVwap.ts +174 -0
  446. package/src/components/anchoredVwap/createAnchoredVwapController.ts +358 -0
  447. package/src/components/anchoredVwap/index.ts +17 -0
  448. package/src/components/anchoredVwap/types.ts +187 -0
  449. package/src/components/crosshairSync/__tests__/crosshairSync.test.ts +261 -0
  450. package/src/components/crosshairSync/createCrosshairSync.ts +187 -0
  451. package/src/components/crosshairSync/index.ts +5 -0
  452. package/src/components/footprint/__tests__/aggressor.test.ts +127 -0
  453. package/src/components/footprint/__tests__/controller.test.ts +130 -0
  454. package/src/components/footprint/__tests__/perBarStats.test.ts +114 -0
  455. package/src/components/footprint/aggressor.ts +165 -0
  456. package/src/components/footprint/createFootprintController.ts +338 -0
  457. package/src/components/footprint/index.ts +21 -0
  458. package/src/components/footprint/perBarStats.ts +137 -0
  459. package/src/components/footprint/types.ts +232 -0
  460. package/src/components/mtfOverlay/__tests__/alignToBaseIndex.test.ts +103 -0
  461. package/src/components/mtfOverlay/__tests__/controller.test.ts +172 -0
  462. package/src/components/mtfOverlay/__tests__/resampleBars.test.ts +106 -0
  463. package/src/components/mtfOverlay/alignToBaseIndex.ts +108 -0
  464. package/src/components/mtfOverlay/createMtfController.ts +180 -0
  465. package/src/components/mtfOverlay/index.ts +26 -0
  466. package/src/components/mtfOverlay/resampleBars.ts +134 -0
  467. package/src/components/mtfOverlay/types.ts +148 -0
  468. package/src/components/orderBookHeatmap/__tests__/controller.test.ts +237 -0
  469. package/src/components/orderBookHeatmap/__tests__/deltaArchive.test.ts +88 -0
  470. package/src/components/orderBookHeatmap/__tests__/logColorScale.test.ts +69 -0
  471. package/src/components/orderBookHeatmap/__tests__/orderBookState.test.ts +113 -0
  472. package/src/components/orderBookHeatmap/__tests__/snapshotRing.test.ts +53 -0
  473. package/src/components/orderBookHeatmap/computeShader.wgsl.md +130 -0
  474. package/src/components/orderBookHeatmap/createHeatmapController.ts +273 -0
  475. package/src/components/orderBookHeatmap/createOrderBookState.ts +116 -0
  476. package/src/components/orderBookHeatmap/deltaArchive.ts +113 -0
  477. package/src/components/orderBookHeatmap/index.ts +25 -0
  478. package/src/components/orderBookHeatmap/logColorScale.ts +77 -0
  479. package/src/components/orderBookHeatmap/snapshotRing.ts +65 -0
  480. package/src/components/orderBookHeatmap/types.ts +168 -0
  481. package/src/components/volumeProfile/__tests__/binning.test.ts +133 -0
  482. package/src/components/volumeProfile/__tests__/poc.test.ts +44 -0
  483. package/src/components/volumeProfile/__tests__/valueArea.test.ts +177 -0
  484. package/src/components/volumeProfile/binning.ts +133 -0
  485. package/src/components/volumeProfile/createVolumeProfileController.ts +213 -0
  486. package/src/components/volumeProfile/index.ts +12 -0
  487. package/src/components/volumeProfile/poc.ts +53 -0
  488. package/src/components/volumeProfile/types.ts +148 -0
  489. package/src/components/volumeProfile/valueArea.ts +187 -0
  490. package/src/controllers/createChartController.ts +23 -10
  491. package/src/controllers/types.ts +5 -1
  492. package/src/data-fetchers/baostock.ts +2 -1
  493. package/src/data-fetchers/dataBuffer.effects.ts +3 -1
  494. package/src/data-fetchers/fetcherDefinitionRegistry.ts +2 -1
  495. package/src/data-fetchers/gotdx.ts +4 -3
  496. package/src/data-fetchers/router.ts +6 -3
  497. package/src/data-fetchers/tradingview.ts +3 -2
  498. package/src/engine/chart.ts +126 -3
  499. package/src/engine/data/chartDataManager.ts +26 -4
  500. package/src/engine/drawing/toolConfig.ts +2 -1
  501. package/src/engine/indicators/chartIndicatorManager.ts +2 -1
  502. package/src/engine/indicators/indicatorDefinitionRegistry.ts +2 -1
  503. package/src/engine/indicators/indicatorMetadata.ts +2 -1
  504. package/src/engine/indicators/indicatorRegistry.ts +6 -5
  505. package/src/engine/indicators/registerBuiltins.ts +2 -1
  506. package/src/engine/indicators/scheduler.ts +15 -0
  507. package/src/engine/indicators/soa.ts +2 -1
  508. package/src/engine/indicators/stateComposer.ts +2 -1
  509. package/src/engine/subPaneManager.ts +2 -1
  510. package/src/engine/utils/chartZoomController.ts +7 -1
  511. package/src/errors-help.ts +218 -0
  512. package/src/errors.ts +128 -0
  513. package/src/index.ts +37 -0
  514. package/src/indicators/__tests__/maFamily.test.ts +159 -0
  515. package/src/indicators/__tests__/oscillators.test.ts +150 -0
  516. package/src/indicators/alma.ts +61 -0
  517. package/src/indicators/awesomeOscillator.ts +54 -0
  518. package/src/indicators/dpo.ts +37 -0
  519. package/src/indicators/fisherTransform.ts +63 -0
  520. package/src/indicators/frama.ts +87 -0
  521. package/src/indicators/index.ts +39 -0
  522. package/src/indicators/lsma.ts +58 -0
  523. package/src/indicators/schaffTrendCycle.ts +92 -0
  524. package/src/indicators/stochRSI.ts +108 -0
  525. package/src/indicators/t3.ts +96 -0
  526. package/src/indicators/ultimateOscillator.ts +83 -0
  527. package/src/indicators/vidya.ts +80 -0
  528. package/src/indicators/zlema.ts +41 -0
  529. package/src/input/__tests__/gesture.test.ts +271 -0
  530. package/src/input/__tests__/keyboard.test.ts +317 -0
  531. package/src/input/gesture.ts +357 -0
  532. package/src/input/index.ts +26 -0
  533. package/src/input/keyboard.ts +373 -0
  534. package/src/mcp/chartBridge.ts +98 -72
  535. package/src/plugin/PluginHost.ts +4 -3
  536. package/src/plugin/PluginRegistry.ts +2 -1
  537. package/src/render/Renderer.ts +127 -0
  538. package/src/render/SurfaceBackend.ts +94 -0
  539. package/src/render/__tests__/contract.test.ts +176 -0
  540. package/src/render/index.ts +27 -0
  541. package/src/renderer-tier/__tests__/detectRendererTier.test.ts +180 -0
  542. package/src/renderer-tier/__tests__/selectBackend.test.ts +253 -0
  543. package/src/renderer-tier/detectRendererTier.ts +168 -0
  544. package/src/renderer-tier/index.ts +30 -0
  545. package/src/renderer-tier/selectBackend.ts +201 -0
  546. package/src/renderer-tier/types.ts +59 -0
  547. package/src/replay/__tests__/controller.test.ts +309 -0
  548. package/src/replay/__tests__/timeline.test.ts +99 -0
  549. package/src/replay/createReplayController.ts +302 -0
  550. package/src/replay/index.ts +15 -0
  551. package/src/replay/timeline.ts +106 -0
  552. package/src/replay/types.ts +107 -0
  553. package/src/scale/__tests__/anchoredZoom.test.ts +125 -0
  554. package/src/scale/__tests__/originShift.test.ts +97 -0
  555. package/src/scale/__tests__/priceScale.test.ts +219 -0
  556. package/src/scale/__tests__/timeScale.test.ts +119 -0
  557. package/src/scale/anchoredZoom.ts +104 -0
  558. package/src/scale/createPriceScale.ts +226 -0
  559. package/src/scale/createTimeScale.ts +150 -0
  560. package/src/scale/index.ts +9 -0
  561. package/src/scale/originShift.ts +111 -0
  562. package/src/scale/types.ts +100 -0
  563. package/src/scene/__tests__/layerRegistry.test.ts +207 -0
  564. package/src/scene/__tests__/scene.test.ts +411 -0
  565. package/src/scene/createScene.ts +117 -0
  566. package/src/scene/index.ts +29 -0
  567. package/src/scene/layerRegistry.ts +117 -0
  568. package/src/scene/types.ts +143 -0
  569. package/src/scheduler/__tests__/createFrameBudget.test.ts +315 -0
  570. package/src/scheduler/createFrameBudget.ts +316 -0
  571. package/src/scheduler/index.ts +7 -0
  572. package/src/tokens/themeToCssVars.ts +3 -1
  573. package/src/version.ts +1 -1
  574. package/dist/data-fetchers/gotdx/gotdx.d.ts +0 -7
  575. package/dist/data-fetchers/gotdx/gotdx.d.ts.map +0 -1
  576. package/dist/data-fetchers/gotdx/gotdx.js +0 -7
  577. package/dist/data-fetchers/gotdx/gotdx.js.map +0 -1
  578. package/src/data-fetchers/gotdx/gotdx.ts +0 -6
@@ -0,0 +1,237 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import { createHeatmapController } from '../createHeatmapController'
4
+ import { createOrderBookState } from '../createOrderBookState'
5
+ import type { BookSnapshot, OrderBookDelta } from '../types'
6
+
7
+ function bid(ts: number, price: number, size: number): OrderBookDelta {
8
+ return { side: 'bid', price, size, timestamp: ts }
9
+ }
10
+ function ask(ts: number, price: number, size: number): OrderBookDelta {
11
+ return { side: 'ask', price, size, timestamp: ts }
12
+ }
13
+
14
+ function findBidSize(snap: BookSnapshot, price: number): number {
15
+ for (const [p, s] of snap.bids) if (p === price) return s
16
+ return 0
17
+ }
18
+
19
+ describe('createHeatmapController', () => {
20
+ it('auto-generates snapshots at the configured interval driven by delta timestamps', () => {
21
+ const ctrl = createHeatmapController({
22
+ tickSize: 0.01,
23
+ snapshotIntervalMs: 100,
24
+ snapshotRingCapacity: 10,
25
+ deltaArchiveMaxSize: 1000,
26
+ logColorRange: { sizeMin: 1, sizeMax: 1000 },
27
+ })
28
+ // Send 5 deltas spanning 0 → 450ms — 4 interval crossings.
29
+ ctrl.ingestDelta(bid(0, 100, 1))
30
+ ctrl.ingestDelta(bid(150, 100, 2)) // crosses 100ms
31
+ ctrl.ingestDelta(bid(220, 100, 3)) // crosses 200ms
32
+ ctrl.ingestDelta(bid(330, 100, 4)) // crosses 300ms
33
+ ctrl.ingestDelta(bid(450, 100, 5)) // crosses 400ms
34
+ const st = ctrl.state.peek()
35
+ // Each crossing produces one snapshot.
36
+ expect(st.snapshotCount).toBe(4)
37
+ expect(st.deltaCount).toBe(5)
38
+ ctrl.dispose()
39
+ })
40
+
41
+ it('records flash orders in the archive even when they vanish before the next snapshot', () => {
42
+ const ctrl = createHeatmapController({
43
+ tickSize: 0.01,
44
+ snapshotIntervalMs: 100,
45
+ snapshotRingCapacity: 10,
46
+ deltaArchiveMaxSize: 1000,
47
+ logColorRange: { sizeMin: 1, sizeMax: 1000 },
48
+ })
49
+ // First delta anchors snapshot clock at t=0.
50
+ ctrl.ingestDelta(bid(0, 100, 1))
51
+ // Place + cancel inside the [0, 100) window.
52
+ ctrl.ingestDelta(bid(10, 99.5, 500)) // flash place
53
+ ctrl.ingestDelta(bid(20, 99.5, 0)) // flash cancel
54
+ // Now cross the interval to fire a snapshot.
55
+ ctrl.ingestDelta(bid(150, 100, 2))
56
+ const st = ctrl.state.peek()
57
+ expect(st.snapshotCount).toBe(1)
58
+ // The forced snapshot should NOT show the flash level (it was cancelled).
59
+ ctrl.forceSnapshot()
60
+ const latest = ctrl.state.peek().latestSnapshot
61
+ expect(latest).not.toBeNull()
62
+ expect(findBidSize(latest as BookSnapshot, 99.5)).toBe(0)
63
+ // But replay across the flash window MUST show it.
64
+ const series = ctrl.replay(5, 25, 5)
65
+ const flashOn = series.find((s) => findBidSize(s, 99.5) === 500)
66
+ const flashOff = series.find(
67
+ (s) => s.timestamp >= 20 && findBidSize(s, 99.5) === 0,
68
+ )
69
+ expect(flashOn).toBeDefined()
70
+ expect(flashOff).toBeDefined()
71
+ ctrl.dispose()
72
+ })
73
+
74
+ it('forceSnapshot() pushes the current book state immediately', () => {
75
+ const ctrl = createHeatmapController({
76
+ tickSize: 0.01,
77
+ snapshotIntervalMs: 1000,
78
+ snapshotRingCapacity: 10,
79
+ deltaArchiveMaxSize: 1000,
80
+ logColorRange: { sizeMin: 1, sizeMax: 1000 },
81
+ })
82
+ ctrl.ingestDelta(bid(0, 100, 5))
83
+ ctrl.ingestDelta(ask(0, 101, 7))
84
+ expect(ctrl.state.peek().snapshotCount).toBe(0)
85
+ ctrl.forceSnapshot()
86
+ const st = ctrl.state.peek()
87
+ expect(st.snapshotCount).toBe(1)
88
+ expect(st.latestSnapshot?.bids).toEqual([[100, 5]])
89
+ expect(st.latestSnapshot?.asks).toEqual([[101, 7]])
90
+ ctrl.dispose()
91
+ })
92
+
93
+ it('replay(t1,t2) reconstructs the book equivalent to fold-left of deltas', () => {
94
+ // Property-test equivalent: build a fixed delta stream, replay through
95
+ // the controller, and assert the replayed snapshot at every grid
96
+ // timestamp equals the snapshot computed by applying deltas
97
+ // up-to-and-including that timestamp to a fresh order book.
98
+ const ctrl = createHeatmapController({
99
+ tickSize: 0.01,
100
+ snapshotIntervalMs: 50,
101
+ snapshotRingCapacity: 100,
102
+ deltaArchiveMaxSize: 10_000,
103
+ logColorRange: { sizeMin: 1, sizeMax: 1000 },
104
+ })
105
+ const deltas: OrderBookDelta[] = [
106
+ bid(0, 100, 5),
107
+ ask(0, 101, 5),
108
+ bid(40, 99.5, 10),
109
+ ask(60, 101.5, 3),
110
+ bid(75, 100, 0), // remove
111
+ ask(110, 102, 4),
112
+ bid(140, 99.5, 25),
113
+ ask(170, 101, 0),
114
+ bid(190, 99, 1),
115
+ ]
116
+ for (const d of deltas) ctrl.ingestDelta(d)
117
+
118
+ const replayed = ctrl.replay(0, 200, 25)
119
+
120
+ for (const snap of replayed) {
121
+ const oracle = createOrderBookState({ tickSize: 0.01 })
122
+ for (const d of deltas) {
123
+ if (d.timestamp <= snap.timestamp) oracle.applyDelta(d)
124
+ }
125
+ const expected = oracle.snapshot()
126
+ expect(snap.bids).toEqual(expected.bids)
127
+ expect(snap.asks).toEqual(expected.asks)
128
+ }
129
+ ctrl.dispose()
130
+ })
131
+
132
+ it('replay across midpoint matches live-state snapshot at that point', () => {
133
+ // Hand-coded equivalence: a known midpoint timestamp must yield the
134
+ // same book state whether reached via live ingest or via replay.
135
+ const cfg = {
136
+ tickSize: 0.01,
137
+ snapshotIntervalMs: 100,
138
+ snapshotRingCapacity: 64,
139
+ deltaArchiveMaxSize: 10_000,
140
+ logColorRange: { sizeMin: 1, sizeMax: 1000 },
141
+ }
142
+ const live = createHeatmapController(cfg)
143
+ const deltas: OrderBookDelta[] = [
144
+ bid(0, 100, 1),
145
+ ask(0, 101, 1),
146
+ bid(50, 99.5, 2),
147
+ bid(120, 100, 0),
148
+ ask(160, 101.5, 4),
149
+ ]
150
+ for (const d of deltas) live.ingestDelta(d)
151
+
152
+ // Live midpoint reference: replay BOOK alone, no controller, up to t=80.
153
+ const reference = createOrderBookState({ tickSize: 0.01 })
154
+ for (const d of deltas) if (d.timestamp <= 80) reference.applyDelta(d)
155
+ const refSnap = reference.snapshot()
156
+
157
+ // Controller-produced replay snapshot at exactly t=80.
158
+ const series = live.replay(80, 80, 1)
159
+ expect(series).toHaveLength(1)
160
+ expect(series[0].bids).toEqual(refSnap.bids)
161
+ expect(series[0].asks).toEqual(refSnap.asks)
162
+ live.dispose()
163
+ })
164
+
165
+ it('dispose() silences subsequent mutator calls', () => {
166
+ const ctrl = createHeatmapController({
167
+ tickSize: 0.01,
168
+ snapshotIntervalMs: 100,
169
+ snapshotRingCapacity: 10,
170
+ deltaArchiveMaxSize: 1000,
171
+ logColorRange: { sizeMin: 1, sizeMax: 1000 },
172
+ })
173
+ ctrl.ingestDelta(bid(0, 100, 1))
174
+ const before = ctrl.state.peek().deltaCount
175
+ ctrl.dispose()
176
+ ctrl.ingestDelta(bid(100, 100, 2))
177
+ ctrl.forceSnapshot()
178
+ ctrl.setConfig({ snapshotIntervalMs: 50 })
179
+ expect(ctrl.state.peek().deltaCount).toBe(before)
180
+ // replay() on disposed controller returns empty.
181
+ expect(ctrl.replay(0, 100, 10)).toEqual([])
182
+ // dispose() is idempotent.
183
+ ctrl.dispose()
184
+ })
185
+
186
+ it('setConfig() rebuilds book + ring + archive cap when tick/capacity/maxSize change', () => {
187
+ const ctrl = createHeatmapController({
188
+ tickSize: 0.01,
189
+ snapshotIntervalMs: 100,
190
+ snapshotRingCapacity: 4,
191
+ deltaArchiveMaxSize: 1000,
192
+ logColorRange: { sizeMin: 1, sizeMax: 1000 },
193
+ })
194
+ // Three distinct fine prices that all fall inside a single coarse
195
+ // bucket once we re-quantize.
196
+ ctrl.ingestDelta(bid(0, 100.10, 1))
197
+ ctrl.ingestDelta(bid(150, 100.20, 2))
198
+ ctrl.ingestDelta(bid(300, 100.30, 3))
199
+ // Coarser tick: 0.10/0.20/0.30 → ticks 1/2/3 at 0.01, but at 1.0
200
+ // they all round to 100.0.
201
+ ctrl.setConfig({ tickSize: 1.0 })
202
+ ctrl.forceSnapshot()
203
+ const snap = ctrl.state.peek().latestSnapshot as BookSnapshot
204
+ const buckets = new Set(snap.bids.map((b) => b[0]))
205
+ expect(buckets.size).toBe(1)
206
+ expect(snap.bids[0][0]).toBe(100)
207
+ ctrl.dispose()
208
+ })
209
+
210
+ it('rejects invalid replay configuration', () => {
211
+ const ctrl = createHeatmapController()
212
+ expect(() => ctrl.replay(0, 100, 0)).toThrow()
213
+ expect(() => ctrl.replay(0, 100, -1)).toThrow()
214
+ // from > to returns empty (not throw).
215
+ expect(ctrl.replay(100, 0, 10)).toEqual([])
216
+ ctrl.dispose()
217
+ })
218
+
219
+ it('emits a state notification on every ingestDelta', () => {
220
+ const ctrl = createHeatmapController({
221
+ tickSize: 0.01,
222
+ snapshotIntervalMs: 1_000,
223
+ snapshotRingCapacity: 10,
224
+ deltaArchiveMaxSize: 1000,
225
+ logColorRange: { sizeMin: 1, sizeMax: 1000 },
226
+ })
227
+ let calls = 0
228
+ const off = ctrl.state.subscribe(() => calls++)
229
+ ctrl.ingestDelta(bid(0, 100, 1))
230
+ ctrl.ingestDelta(bid(1, 100, 2))
231
+ ctrl.ingestDelta(bid(2, 100, 3))
232
+ // 3 deltas → 3 notifications.
233
+ expect(calls).toBe(3)
234
+ off()
235
+ ctrl.dispose()
236
+ })
237
+ })
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import { createDeltaArchive } from '../deltaArchive'
4
+ import type { OrderBookDelta } from '../types'
5
+
6
+ function delta(ts: number, price = 100, size = 1): OrderBookDelta {
7
+ return { side: 'bid', price, size, timestamp: ts }
8
+ }
9
+
10
+ describe('createDeltaArchive', () => {
11
+ it('appends + ranges across the inserted window', () => {
12
+ const a = createDeltaArchive()
13
+ for (let i = 0; i < 10; i++) a.append(delta(i))
14
+ const r = a.range(2, 5)
15
+ expect(r.map((d) => d.timestamp)).toEqual([2, 3, 4, 5])
16
+ })
17
+
18
+ it('range bounds are inclusive on both ends', () => {
19
+ const a = createDeltaArchive()
20
+ a.append(delta(10))
21
+ a.append(delta(20))
22
+ a.append(delta(30))
23
+ expect(a.range(10, 30).map((d) => d.timestamp)).toEqual([10, 20, 30])
24
+ expect(a.range(11, 29).map((d) => d.timestamp)).toEqual([20])
25
+ })
26
+
27
+ it('trim drops the oldest entries until size ≤ target', () => {
28
+ const a = createDeltaArchive()
29
+ for (let i = 0; i < 100; i++) a.append(delta(i))
30
+ a.trim(30)
31
+ expect(a.size()).toBe(30)
32
+ // The 30 newest should remain.
33
+ const all = a.range(0, 999)
34
+ expect(all[0].timestamp).toBe(70)
35
+ expect(all[all.length - 1].timestamp).toBe(99)
36
+ })
37
+
38
+ it('reports accurate size after append, trim, and clear', () => {
39
+ const a = createDeltaArchive()
40
+ expect(a.size()).toBe(0)
41
+ for (let i = 0; i < 5; i++) a.append(delta(i))
42
+ expect(a.size()).toBe(5)
43
+ a.trim(2)
44
+ expect(a.size()).toBe(2)
45
+ a.clear()
46
+ expect(a.size()).toBe(0)
47
+ })
48
+
49
+ it('chunked storage does not break range queries across chunks', () => {
50
+ // CHUNK_SIZE is 10_000 in the impl. Push more than that to force at
51
+ // least one chunk boundary and ensure ordering still holds.
52
+ const a = createDeltaArchive()
53
+ const N = 25_000
54
+ for (let i = 0; i < N; i++) a.append(delta(i))
55
+ // Window straddles two chunk boundaries.
56
+ const r = a.range(9_998, 10_002)
57
+ expect(r.map((d) => d.timestamp)).toEqual([9_998, 9_999, 10_000, 10_001, 10_002])
58
+ // Full range returns every delta exactly once.
59
+ const all = a.range(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY)
60
+ expect(all.length).toBe(N)
61
+ expect(all[0].timestamp).toBe(0)
62
+ expect(all[N - 1].timestamp).toBe(N - 1)
63
+ })
64
+
65
+ it('respects maxSize at construction by auto-trimming on append', () => {
66
+ const a = createDeltaArchive({ maxSize: 5 })
67
+ for (let i = 0; i < 12; i++) a.append(delta(i))
68
+ expect(a.size()).toBe(5)
69
+ const all = a.range(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY)
70
+ expect(all.map((d) => d.timestamp)).toEqual([7, 8, 9, 10, 11])
71
+ })
72
+
73
+ it('returns empty array when from > to', () => {
74
+ const a = createDeltaArchive()
75
+ for (let i = 0; i < 5; i++) a.append(delta(i))
76
+ expect(a.range(3, 1)).toEqual([])
77
+ })
78
+
79
+ it('clear() resets the archive without breaking subsequent appends', () => {
80
+ const a = createDeltaArchive()
81
+ for (let i = 0; i < 5; i++) a.append(delta(i))
82
+ a.clear()
83
+ expect(a.size()).toBe(0)
84
+ a.append(delta(99))
85
+ expect(a.size()).toBe(1)
86
+ expect(a.range(0, 1000).map((d) => d.timestamp)).toEqual([99])
87
+ })
88
+ })
@@ -0,0 +1,69 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import { createLogColorScale } from '../logColorScale'
4
+
5
+ describe('createLogColorScale', () => {
6
+ it('maps size = 0 to intensity 0', () => {
7
+ const scale = createLogColorScale(1, 1000)
8
+ expect(scale.intensity(0)).toBe(0)
9
+ expect(scale.intensity(-5)).toBe(0)
10
+ })
11
+
12
+ it('maps size below sizeMin to 0', () => {
13
+ const scale = createLogColorScale(10, 1000)
14
+ expect(scale.intensity(1)).toBe(0)
15
+ expect(scale.intensity(10)).toBe(0)
16
+ })
17
+
18
+ it('maps size above sizeMax to 1', () => {
19
+ const scale = createLogColorScale(1, 100)
20
+ expect(scale.intensity(100)).toBe(1)
21
+ expect(scale.intensity(1_000_000)).toBe(1)
22
+ })
23
+
24
+ it('mid-range is monotonically increasing in log size', () => {
25
+ const scale = createLogColorScale(1, 10_000)
26
+ const a = scale.intensity(2)
27
+ const b = scale.intensity(20)
28
+ const c = scale.intensity(200)
29
+ const d = scale.intensity(2_000)
30
+ expect(a).toBeGreaterThan(0)
31
+ expect(a).toBeLessThan(b)
32
+ expect(b).toBeLessThan(c)
33
+ expect(c).toBeLessThan(d)
34
+ expect(d).toBeLessThan(1)
35
+ })
36
+
37
+ it('matches the exact log-ratio formula for a known interior point', () => {
38
+ // sizeMin = 1, sizeMax = 100 → log range = ln(100). At size = 10
39
+ // intensity should be ln(10) / ln(100) = 0.5.
40
+ const scale = createLogColorScale(1, 100)
41
+ expect(scale.intensity(10)).toBeCloseTo(0.5, 10)
42
+ })
43
+
44
+ it('degenerate range (sizeMin === sizeMax) returns 0.5 for any positive size', () => {
45
+ const scale = createLogColorScale(5, 5)
46
+ expect(scale.intensity(1)).toBe(0.5)
47
+ expect(scale.intensity(5)).toBe(0.5)
48
+ expect(scale.intensity(1_000_000)).toBe(0.5)
49
+ // Non-positive sizes still map to 0.
50
+ expect(scale.intensity(0)).toBe(0)
51
+ })
52
+
53
+ it('setRange swaps the range without rebuilding the scale', () => {
54
+ const scale = createLogColorScale(1, 100)
55
+ expect(scale.intensity(10)).toBeCloseTo(0.5, 10)
56
+ scale.setRange(1, 1_000_000)
57
+ // Now ln(10) / ln(1e6) = 1 / 6.
58
+ expect(scale.intensity(10)).toBeCloseTo(1 / 6, 6)
59
+ expect(scale.range()).toEqual({ sizeMin: 1, sizeMax: 1_000_000 })
60
+ })
61
+
62
+ it('rejects invalid ranges at construction', () => {
63
+ expect(() => createLogColorScale(0, 1)).toThrow()
64
+ expect(() => createLogColorScale(1, 0)).toThrow()
65
+ expect(() => createLogColorScale(-1, 1)).toThrow()
66
+ expect(() => createLogColorScale(10, 1)).toThrow()
67
+ expect(() => createLogColorScale(Infinity, 1)).toThrow()
68
+ })
69
+ })
@@ -0,0 +1,113 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import { createOrderBookState } from '../createOrderBookState'
4
+
5
+ describe('createOrderBookState', () => {
6
+ it('applies a bid delta and reflects it in the next snapshot', () => {
7
+ const book = createOrderBookState({ tickSize: 0.01 })
8
+ book.applyDelta({ side: 'bid', price: 100.0, size: 5, timestamp: 1 })
9
+ const snap = book.snapshot()
10
+ expect(snap.bids).toEqual([[100.0, 5]])
11
+ expect(snap.asks).toEqual([])
12
+ expect(snap.timestamp).toBe(1)
13
+ })
14
+
15
+ it('treats size=0 as a removal of the level', () => {
16
+ const book = createOrderBookState({ tickSize: 0.01 })
17
+ book.applyDelta({ side: 'bid', price: 100.0, size: 5, timestamp: 1 })
18
+ book.applyDelta({ side: 'bid', price: 100.0, size: 0, timestamp: 2 })
19
+ const snap = book.snapshot()
20
+ expect(snap.bids).toEqual([])
21
+ expect(snap.timestamp).toBe(2)
22
+ })
23
+
24
+ it('quantizes input prices to ticks (67000.123 with tick 0.01 → 67000.12)', () => {
25
+ const book = createOrderBookState({ tickSize: 0.01 })
26
+ book.applyDelta({ side: 'ask', price: 67000.123, size: 1, timestamp: 1 })
27
+ const snap = book.snapshot()
28
+ expect(snap.asks).toHaveLength(1)
29
+ expect(snap.asks[0][0]).toBeCloseTo(67000.12, 6)
30
+ expect(snap.asks[0][1]).toBe(1)
31
+ })
32
+
33
+ it('returns dequantized prices (consumer never sees the tick index)', () => {
34
+ const book = createOrderBookState({ tickSize: 0.5 })
35
+ // 100.7 → tick 201 → 100.5
36
+ book.applyDelta({ side: 'bid', price: 100.7, size: 3, timestamp: 1 })
37
+ // 101.3 → tick 203 → 101.5
38
+ book.applyDelta({ side: 'ask', price: 101.3, size: 4, timestamp: 1 })
39
+ const snap = book.snapshot()
40
+ expect(snap.bids).toEqual([[100.5, 3]])
41
+ expect(snap.asks).toEqual([[101.5, 4]])
42
+ })
43
+
44
+ it('clear() resets both sides and the last timestamp', () => {
45
+ const book = createOrderBookState({ tickSize: 0.01 })
46
+ book.applyDelta({ side: 'bid', price: 100, size: 1, timestamp: 5 })
47
+ book.applyDelta({ side: 'ask', price: 101, size: 2, timestamp: 6 })
48
+ book.clear()
49
+ const snap = book.snapshot()
50
+ expect(snap.bids).toEqual([])
51
+ expect(snap.asks).toEqual([])
52
+ expect(snap.timestamp).toBe(0)
53
+ expect(book.lastTimestamp()).toBe(0)
54
+ })
55
+
56
+ it('last update wins for the same price (multiple updates collapse)', () => {
57
+ const book = createOrderBookState({ tickSize: 0.01 })
58
+ book.applyDelta({ side: 'bid', price: 100, size: 1, timestamp: 1 })
59
+ book.applyDelta({ side: 'bid', price: 100, size: 2, timestamp: 2 })
60
+ book.applyDelta({ side: 'bid', price: 100, size: 9, timestamp: 3 })
61
+ const snap = book.snapshot()
62
+ expect(snap.bids).toEqual([[100.0, 9]])
63
+ expect(snap.timestamp).toBe(3)
64
+ })
65
+
66
+ it('maintains bid and ask side independence', () => {
67
+ const book = createOrderBookState({ tickSize: 0.01 })
68
+ book.applyDelta({ side: 'bid', price: 100, size: 5, timestamp: 1 })
69
+ book.applyDelta({ side: 'ask', price: 100, size: 7, timestamp: 2 })
70
+ // Removing bid at 100 must not remove ask at 100.
71
+ book.applyDelta({ side: 'bid', price: 100, size: 0, timestamp: 3 })
72
+ const snap = book.snapshot()
73
+ expect(snap.bids).toEqual([])
74
+ expect(snap.asks).toEqual([[100, 7]])
75
+ })
76
+
77
+ it('sorts bids descending and asks ascending', () => {
78
+ const book = createOrderBookState({ tickSize: 0.01 })
79
+ book.applyDelta({ side: 'bid', price: 99.5, size: 1, timestamp: 1 })
80
+ book.applyDelta({ side: 'bid', price: 100.0, size: 1, timestamp: 1 })
81
+ book.applyDelta({ side: 'bid', price: 99.8, size: 1, timestamp: 1 })
82
+ book.applyDelta({ side: 'ask', price: 101.0, size: 1, timestamp: 1 })
83
+ book.applyDelta({ side: 'ask', price: 100.2, size: 1, timestamp: 1 })
84
+ book.applyDelta({ side: 'ask', price: 100.5, size: 1, timestamp: 1 })
85
+ const snap = book.snapshot()
86
+ expect(snap.bids.map((b) => b[0])).toEqual([100.0, 99.8, 99.5])
87
+ expect(snap.asks.map((a) => a[0])).toEqual([100.2, 100.5, 101.0])
88
+ })
89
+
90
+ it('respects maxLevels by truncating to top-of-book first', () => {
91
+ const book = createOrderBookState({ tickSize: 0.01, maxLevels: 2 })
92
+ book.applyDelta({ side: 'bid', price: 99.5, size: 1, timestamp: 1 })
93
+ book.applyDelta({ side: 'bid', price: 100.0, size: 1, timestamp: 1 })
94
+ book.applyDelta({ side: 'bid', price: 99.8, size: 1, timestamp: 1 })
95
+ const snap = book.snapshot()
96
+ expect(snap.bids.map((b) => b[0])).toEqual([100.0, 99.8])
97
+ })
98
+
99
+ it('rejects non-positive tickSize at construction', () => {
100
+ expect(() => createOrderBookState({ tickSize: 0 })).toThrow()
101
+ expect(() => createOrderBookState({ tickSize: -1 })).toThrow()
102
+ expect(() => createOrderBookState({ tickSize: Infinity })).toThrow()
103
+ })
104
+
105
+ it('ignores NaN / Infinity deltas without corrupting state', () => {
106
+ const book = createOrderBookState({ tickSize: 0.01 })
107
+ book.applyDelta({ side: 'bid', price: NaN, size: 1, timestamp: 1 })
108
+ book.applyDelta({ side: 'bid', price: 100, size: NaN, timestamp: 2 })
109
+ book.applyDelta({ side: 'bid', price: 100, size: 1, timestamp: 3 })
110
+ const snap = book.snapshot()
111
+ expect(snap.bids).toEqual([[100, 1]])
112
+ })
113
+ })
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import { createSnapshotRing } from '../snapshotRing'
4
+ import type { BookSnapshot } from '../types'
5
+
6
+ function makeSnap(ts: number): BookSnapshot {
7
+ return { bids: [], asks: [], timestamp: ts }
8
+ }
9
+
10
+ describe('createSnapshotRing', () => {
11
+ it('respects the declared capacity and reports size accurately', () => {
12
+ const ring = createSnapshotRing(3)
13
+ expect(ring.capacity).toBe(3)
14
+ expect(ring.size()).toBe(0)
15
+ ring.push(makeSnap(1))
16
+ ring.push(makeSnap(2))
17
+ expect(ring.size()).toBe(2)
18
+ })
19
+
20
+ it('drops the oldest entry when pushed past capacity', () => {
21
+ const ring = createSnapshotRing(3)
22
+ ring.push(makeSnap(1))
23
+ ring.push(makeSnap(2))
24
+ ring.push(makeSnap(3))
25
+ ring.push(makeSnap(4))
26
+ const out = ring.toArray()
27
+ expect(out.map((s) => s.timestamp)).toEqual([2, 3, 4])
28
+ })
29
+
30
+ it('toArray returns snapshots oldest → newest, even after wrap-around', () => {
31
+ const ring = createSnapshotRing(4)
32
+ for (let i = 1; i <= 10; i++) ring.push(makeSnap(i))
33
+ const out = ring.toArray()
34
+ expect(out.map((s) => s.timestamp)).toEqual([7, 8, 9, 10])
35
+ })
36
+
37
+ it('clear() empties the buffer without affecting capacity', () => {
38
+ const ring = createSnapshotRing(2)
39
+ ring.push(makeSnap(1))
40
+ ring.push(makeSnap(2))
41
+ ring.clear()
42
+ expect(ring.size()).toBe(0)
43
+ expect(ring.toArray()).toEqual([])
44
+ ring.push(makeSnap(3))
45
+ expect(ring.toArray().map((s) => s.timestamp)).toEqual([3])
46
+ })
47
+
48
+ it('rejects non-positive capacity', () => {
49
+ expect(() => createSnapshotRing(0)).toThrow()
50
+ expect(() => createSnapshotRing(-5)).toThrow()
51
+ expect(() => createSnapshotRing(1.5)).toThrow()
52
+ })
53
+ })
@@ -0,0 +1,130 @@
1
+ # Order Book Heatmap — WGSL Render Path Spec
2
+
3
+ This document specifies the GPU side of the heatmap. **No `.wgsl` source ships
4
+ with this commit** — the JS controller in this module produces the snapshot
5
+ ring; the renderer (P1 deliverable) will translate the spec below into a real
6
+ pipeline once the WebGPU surface lands. The spec is authoritative for that
7
+ work.
8
+
9
+ ## Inputs
10
+
11
+ ### `snapshotRing` — storage buffer (read-only in shaders)
12
+
13
+ Logically a 2-D grid `intensity[col][row]` where:
14
+
15
+ - `col` ∈ `[0, snapshotRingCapacity)` — time axis (oldest → newest)
16
+ - `row` ∈ `[0, priceRowCount)` — price axis (low → high)
17
+
18
+ CPU-side layout (one upload per render frame for simplicity in v0):
19
+
20
+ ```
21
+ struct SnapshotCell {
22
+ intensity: f32, // log-mapped, already in [0, 1]
23
+ };
24
+ @group(0) @binding(0) var<storage, read> ring: array<SnapshotCell>;
25
+ ```
26
+
27
+ Index conversion: `cellIdx = col * priceRowCount + row`.
28
+
29
+ Empty levels (no resting size at that price/time) are written as `0.0`. The
30
+ fragment shader treats `0.0` as fully transparent.
31
+
32
+ ### `viridisLUT` — 256-entry uniform color table
33
+
34
+ ```
35
+ @group(0) @binding(1) var<uniform> viridisLUT: array<vec4<f32>, 256>;
36
+ ```
37
+
38
+ `viridis` is the perceptually-uniform colormap from `d3-scale-chromatic`. Each
39
+ entry is `vec4<f32>(r, g, b, 1.0)` with channels in `[0, 1]` and gamma already
40
+ applied. The CPU sends it once at controller init and on theme change.
41
+
42
+ ### `params` — uniform block
43
+
44
+ ```
45
+ struct HeatmapParams {
46
+ priceRowCount: u32,
47
+ snapshotColumnCount: u32,
48
+ // Scroll offset: when the ring buffer wraps, this is the col where the
49
+ // oldest sample lives. The fragment shader rotates around this.
50
+ ringHead: u32,
51
+ // Range used by the CPU when log-mapping; replicated here so the shader
52
+ // can re-map should we move log mapping onto the GPU later (see §4).
53
+ sizeMinLog: f32,
54
+ sizeMaxLog: f32,
55
+ _pad: f32,
56
+ };
57
+ @group(0) @binding(2) var<uniform> params: HeatmapParams;
58
+ ```
59
+
60
+ ## Fragment shader
61
+
62
+ ```
63
+ @fragment
64
+ fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
65
+ // uv.x: 0 (left, oldest) → 1 (right, newest)
66
+ // uv.y: 0 (bottom, lowest price) → 1 (top, highest price)
67
+ let col = u32(floor(uv.x * f32(params.snapshotColumnCount)));
68
+ let row = u32(floor(uv.y * f32(params.priceRowCount)));
69
+ let rotated = (col + params.ringHead) % params.snapshotColumnCount;
70
+ let idx = rotated * params.priceRowCount + row;
71
+ let i = ring[idx].intensity;
72
+ if (i <= 0.0) {
73
+ return vec4<f32>(0.0, 0.0, 0.0, 0.0);
74
+ }
75
+ let lutIdx = u32(clamp(i * 255.0, 0.0, 255.0));
76
+ return viridisLUT[lutIdx];
77
+ }
78
+ ```
79
+
80
+ ## Trade-off — full re-upload vs scrolling write
81
+
82
+ There are two reasonable upload strategies. The CPU controller currently
83
+ produces the data shape for **either**; the renderer picks at integration time.
84
+
85
+ ### Option A — full re-upload every frame
86
+
87
+ Each render frame the CPU calls `snapshotRing.toArray()` and writes the whole
88
+ buffer. Simple, no GPU-side state, fast on small rings.
89
+
90
+ - Cost: `O(N · R · sizeof(SnapshotCell))` per frame
91
+ (N = ring capacity, R = price rows). For N = 600, R = 400, 4 B per cell
92
+ that's ~960 KB per frame — fine at 60 fps on any GPU we target.
93
+ - Pros: stateless, robust to seeking/replay, code is short.
94
+ - Cons: redundant uploads for the columns that didn't change.
95
+
96
+ ### Option B — scrolling buffer with single-column write
97
+
98
+ Treat the storage buffer as a true ring. On each new snapshot:
99
+
100
+ 1. CPU writes one column at offset `ringHead * R` (only the new data).
101
+ 2. CPU bumps `params.ringHead = (ringHead + 1) % N`.
102
+
103
+ - Cost: `O(R · sizeof(SnapshotCell))` per snapshot.
104
+ - Pros: minimal bandwidth, scales to N >> 600.
105
+ - Cons: needs careful synchronization with frame timing; the fragment shader
106
+ already accounts for `ringHead` rotation (see snippet above).
107
+
108
+ **Recommendation**: start with A in v0 (correct + easy to validate); migrate
109
+ to B once the renderer is on WebGPU and we want to drive much wider time
110
+ windows.
111
+
112
+ ## Why CPU-side log mapping (today) and not GPU?
113
+
114
+ We do the log transform on CPU inside `logColorScale` because:
115
+
116
+ - The mapping is cheap (one `log` per cell, ~tens of µs per snapshot).
117
+ - Having the intensity already in `[0, 1]` keeps the fragment shader trivial
118
+ and avoids passing raw size ranges through every uniform.
119
+
120
+ Once we move to Option B and want to scrub a long history with dynamic range
121
+ recalibration, we can move log mapping onto the GPU using `sizeMinLog` /
122
+ `sizeMaxLog` from `HeatmapParams`. The shader code path is one extra `log`
123
+ call and a divide — no change to the buffer shape is required.
124
+
125
+ ## Composition with depth chart / order book ladder
126
+
127
+ The same `ring` storage buffer is consumed by §3.4's depth chart, which only
128
+ needs the *current* column (`ringHead - 1 mod N`) and a different fragment
129
+ shader that does a cumulative-sum sweep. We deliberately avoid duplicating
130
+ state for the two visualizations.