@holoscript/core 7.0.0 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/README.md +4 -4
  2. package/bin/holoscript.cjs +3 -0
  3. package/dist/GLTFPipeline-N6B6PJ6G.cjs +38 -0
  4. package/dist/GLTFPipeline-P3WMNYCN.js +17 -0
  5. package/dist/HoloScriptPlusParser-7FBIFUFC.js +693 -0
  6. package/dist/HoloScriptPlusParser-A2W7OCN2.cjs +698 -0
  7. package/dist/HoloScriptPlusParser-F2Z56FJR.js +10 -0
  8. package/dist/HoloScriptPlusParser-WKHRIH6T.cjs +27 -0
  9. package/dist/HoloScriptRuntime-CBYJX3LZ.js +10 -0
  10. package/dist/HoloScriptRuntime-VJ374RER.cjs +19 -0
  11. package/dist/USDZExporter-3FWJ25JI.cjs +1037 -0
  12. package/dist/USDZExporter-D2HQLQYK.js +1035 -0
  13. package/dist/agents/index.cjs +19 -0
  14. package/dist/agents/index.d.ts +100 -0
  15. package/dist/agents/index.js +17 -0
  16. package/dist/chunk-2BSMSUCE.js +1941 -0
  17. package/dist/chunk-2IWC4FP7.cjs +1189 -0
  18. package/dist/chunk-2M2DPHKB.js +762 -0
  19. package/dist/chunk-2X7NGX3M.js +2426 -0
  20. package/dist/chunk-2YSNA2F2.cjs +1274 -0
  21. package/dist/chunk-3FOKASKO.cjs +348 -0
  22. package/dist/chunk-3WZEJHUQ.cjs +116 -0
  23. package/dist/chunk-4C57PR42.js +468 -0
  24. package/dist/chunk-4GU5GCIY.js +1384 -0
  25. package/dist/chunk-4N4SXXWF.js +769 -0
  26. package/dist/chunk-4PDEPEQW.cjs +41 -0
  27. package/dist/chunk-4R6SBXDB.js +45 -0
  28. package/dist/chunk-4SDKXQ4C.js +811 -0
  29. package/dist/chunk-4V2E7U7Q.cjs +2015 -0
  30. package/dist/chunk-5FSKKMR7.cjs +2092 -0
  31. package/dist/chunk-5GLCTO44.js +161 -0
  32. package/dist/chunk-5H2G27PK.cjs +627 -0
  33. package/dist/chunk-5JEU52DW.js +382 -0
  34. package/dist/chunk-5NWINHR5.cjs +689 -0
  35. package/dist/chunk-5S7RKFGR.cjs +446 -0
  36. package/dist/chunk-5WUNN6ZN.cjs +428 -0
  37. package/dist/chunk-6CTRH7HT.cjs +1985 -0
  38. package/dist/chunk-6EX3I5P4.cjs +130 -0
  39. package/dist/chunk-6Q7RC2MK.cjs +1418 -0
  40. package/dist/chunk-7H33U5SM.cjs +413 -0
  41. package/dist/chunk-7M3PNU2M.cjs +672 -0
  42. package/dist/chunk-7T25IEZ2.js +449 -0
  43. package/dist/chunk-7V4JI5TB.cjs +6947 -0
  44. package/dist/chunk-A6KINLZJ.js +236 -0
  45. package/dist/chunk-ADADVMXY.js +404 -0
  46. package/dist/chunk-AIA52I7T.cjs +543 -0
  47. package/dist/chunk-ALC6K7GL.cjs +4402 -0
  48. package/dist/chunk-ANXZFVY2.js +113 -0
  49. package/dist/chunk-AOVGZ25K.cjs +56583 -0
  50. package/dist/chunk-AVJHHUC5.cjs +718 -0
  51. package/dist/chunk-BGY3SDG3.js +4396 -0
  52. package/dist/chunk-BOVPWH4H.js +3857 -0
  53. package/dist/chunk-BRQJKJGT.cjs +20 -0
  54. package/dist/chunk-C37BHB7N.cjs +636 -0
  55. package/dist/chunk-CBJY23VL.js +126 -0
  56. package/dist/chunk-CCATWPXM.js +778 -0
  57. package/dist/chunk-CIWWXYHZ.cjs +50 -0
  58. package/dist/chunk-CMT5NMMS.js +669 -0
  59. package/dist/chunk-CR4FMIWL.cjs +1049 -0
  60. package/dist/chunk-CR7XRXRN.cjs +639 -0
  61. package/dist/chunk-CSD27UNQ.js +2176 -0
  62. package/dist/chunk-D3STJ56J.js +714 -0
  63. package/dist/chunk-DAJXW4KX.js +725 -0
  64. package/dist/chunk-DHO2YRIM.js +756 -0
  65. package/dist/chunk-DTGBOSEM.cjs +319 -0
  66. package/dist/chunk-E6XXE66V.js +2252 -0
  67. package/dist/chunk-E7JP3BS3.cjs +61 -0
  68. package/dist/chunk-EKBKLK7P.js +1169 -0
  69. package/dist/chunk-EKYL4PE3.cjs +404 -0
  70. package/dist/chunk-EXKSFGSX.cjs +274 -0
  71. package/dist/chunk-EXUP2BUM.cjs +1173 -0
  72. package/dist/chunk-FDSGHPAK.js +130 -0
  73. package/dist/chunk-FEWUHQNM.cjs +144 -0
  74. package/dist/chunk-FFAOS5HQ.js +70 -0
  75. package/dist/chunk-FGGJQRLZ.js +270 -0
  76. package/dist/chunk-FHP6LXJS.js +415 -0
  77. package/dist/chunk-FJPADH77.cjs +666 -0
  78. package/dist/chunk-FL33V6U7.js +421 -0
  79. package/dist/chunk-FYSPZT4V.cjs +169 -0
  80. package/dist/chunk-GIOYPNTH.js +5575 -0
  81. package/dist/chunk-GJNCFRJU.cjs +125 -0
  82. package/dist/chunk-GMP623FG.cjs +1508 -0
  83. package/dist/chunk-GQHQOPXZ.cjs +5580 -0
  84. package/dist/chunk-GTU2Z2WW.cjs +262 -0
  85. package/dist/chunk-GYZE55MO.js +2007 -0
  86. package/dist/chunk-GZG4KA4E.js +529 -0
  87. package/dist/chunk-H652NHX5.js +600 -0
  88. package/dist/chunk-HCCQLAA4.cjs +74 -0
  89. package/dist/chunk-HKEXJUWS.cjs +4007 -0
  90. package/dist/chunk-HNDK4IQU.cjs +5737 -0
  91. package/dist/chunk-HR2OSYVQ.js +56165 -0
  92. package/dist/chunk-HYNTS5CP.js +422 -0
  93. package/dist/chunk-I6XZ5DZF.cjs +488 -0
  94. package/dist/chunk-ILRBGFVD.cjs +72 -0
  95. package/dist/chunk-IUDGEUDO.cjs +1409 -0
  96. package/dist/chunk-IUTY5WQ2.cjs +424 -0
  97. package/dist/chunk-IVY74O6C.js +247 -0
  98. package/dist/chunk-IWC5DM2M.cjs +611 -0
  99. package/dist/chunk-J32ZJZJH.js +554 -0
  100. package/dist/chunk-J52VSAQ7.js +6884 -0
  101. package/dist/chunk-J6M4FPK5.js +421 -0
  102. package/dist/chunk-J7XZV333.js +844 -0
  103. package/dist/chunk-JBNDTXXV.cjs +2433 -0
  104. package/dist/chunk-JC5YBPSF.cjs +984 -0
  105. package/dist/chunk-JD7CIZ33.js +1949 -0
  106. package/dist/chunk-JIBL5LRK.js +4287 -0
  107. package/dist/chunk-JIYN45TV.cjs +2525 -0
  108. package/dist/chunk-K574J76X.cjs +457 -0
  109. package/dist/chunk-KHDZJE5D.cjs +2196 -0
  110. package/dist/chunk-KKFZ3B6J.cjs +557 -0
  111. package/dist/chunk-KKP7JKN4.cjs +6002 -0
  112. package/dist/chunk-KPJL34Y7.cjs +186 -0
  113. package/dist/chunk-KQCN6AKJ.cjs +3042 -0
  114. package/dist/chunk-L5LKKENW.cjs +424 -0
  115. package/dist/chunk-LHAXPADA.js +882 -0
  116. package/dist/chunk-LM2VU56C.cjs +37 -0
  117. package/dist/chunk-M56JSP4A.js +400 -0
  118. package/dist/chunk-MBOOR7X4.js +18 -0
  119. package/dist/chunk-MQWAT42R.cjs +4310 -0
  120. package/dist/chunk-MXFUTKCO.js +56 -0
  121. package/dist/chunk-MYY4PMWX.cjs +264 -0
  122. package/dist/chunk-N75IH2HY.js +258 -0
  123. package/dist/chunk-NCWCUZNS.cjs +72 -0
  124. package/dist/chunk-NG77ATEW.cjs +778 -0
  125. package/dist/chunk-NW5G5FBQ.js +215 -0
  126. package/dist/chunk-OBIYPYYS.cjs +1993 -0
  127. package/dist/chunk-OH2FAIVU.js +608 -0
  128. package/dist/chunk-OIQYIIO3.cjs +473 -0
  129. package/dist/chunk-OL46QLBJ.js +61 -0
  130. package/dist/chunk-OMXII44L.js +5386 -0
  131. package/dist/chunk-OSYNRVOV.cjs +243 -0
  132. package/dist/chunk-PGANYCAG.cjs +220 -0
  133. package/dist/chunk-PHENW4MQ.js +65 -0
  134. package/dist/chunk-PY37LNDP.js +686 -0
  135. package/dist/chunk-Q56TT7IL.js +483 -0
  136. package/dist/chunk-QD32ADK6.js +1046 -0
  137. package/dist/chunk-QPOQ7VPI.js +1491 -0
  138. package/dist/chunk-QPSKHBJP.js +5963 -0
  139. package/dist/chunk-QXH7QDSZ.js +1989 -0
  140. package/dist/chunk-R24NNAEG.js +1399 -0
  141. package/dist/chunk-R75BSDH7.cjs +50 -0
  142. package/dist/chunk-RARB3MBR.js +207 -0
  143. package/dist/chunk-ROHLEP2W.cjs +847 -0
  144. package/dist/chunk-S3MDHNKB.cjs +490 -0
  145. package/dist/chunk-S6YWHCKV.cjs +410 -0
  146. package/dist/chunk-SIEXCILW.js +229 -0
  147. package/dist/chunk-SLU6UPJI.cjs +410 -0
  148. package/dist/chunk-SMIWS6DE.cjs +212 -0
  149. package/dist/chunk-T5J7ZKP3.cjs +4885 -0
  150. package/dist/chunk-TDCNJHWO.js +341 -0
  151. package/dist/chunk-TG7O4WBC.js +406 -0
  152. package/dist/chunk-THCK6ECW.js +2085 -0
  153. package/dist/chunk-TJHVSBP2.js +439 -0
  154. package/dist/chunk-TVHSPJ4N.cjs +606 -0
  155. package/dist/chunk-TWUXUCEW.cjs +781 -0
  156. package/dist/chunk-U44IKGG7.cjs +5390 -0
  157. package/dist/chunk-UFZZ7TSN.cjs +886 -0
  158. package/dist/chunk-UJXISJBN.js +487 -0
  159. package/dist/chunk-UOIGFQXC.cjs +9750 -0
  160. package/dist/chunk-URG2SJLK.js +2521 -0
  161. package/dist/chunk-UWJHCOH2.js +623 -0
  162. package/dist/chunk-V6MKC2FZ.js +979 -0
  163. package/dist/chunk-VJ3L7CHT.cjs +169 -0
  164. package/dist/chunk-VMLKSV26.js +1216 -0
  165. package/dist/chunk-VVKVQZI6.js +55 -0
  166. package/dist/chunk-W6RW64BB.cjs +1219 -0
  167. package/dist/chunk-WKX5AMOY.js +309 -0
  168. package/dist/chunk-WNNE3PO3.js +34 -0
  169. package/dist/chunk-WRKUDAZJ.js +3038 -0
  170. package/dist/chunk-WRLUBRKS.cjs +1943 -0
  171. package/dist/chunk-WRNEH5MO.js +3169 -0
  172. package/dist/chunk-WRWEX5ZF.js +658 -0
  173. package/dist/chunk-X3GJPRVK.cjs +3246 -0
  174. package/dist/chunk-X3UVCWNL.cjs +2257 -0
  175. package/dist/chunk-XB274MW5.cjs +814 -0
  176. package/dist/chunk-XHIV6326.cjs +793 -0
  177. package/dist/chunk-XIVIE43A.cjs +729 -0
  178. package/dist/chunk-XJAN7CZG.cjs +432 -0
  179. package/dist/chunk-XKTPFXGA.js +164 -0
  180. package/dist/chunk-XL6INWKT.js +121 -0
  181. package/dist/chunk-XSFNHP2E.js +632 -0
  182. package/dist/chunk-XT6JY5W5.js +9684 -0
  183. package/dist/chunk-XUILI4KB.cjs +430 -0
  184. package/dist/chunk-YF2I3MV3.js +46 -0
  185. package/dist/chunk-YIMN575O.js +4847 -0
  186. package/dist/chunk-YIVFJNYV.js +176 -0
  187. package/dist/chunk-YKGPCZEE.cjs +237 -0
  188. package/dist/chunk-YN2YWTXW.js +5731 -0
  189. package/dist/chunk-YN6V7R3L.cjs +327 -0
  190. package/dist/chunk-YQELFNJV.js +39 -0
  191. package/dist/chunk-YVD3CSO3.js +1183 -0
  192. package/dist/chunk-YVOAF4ML.js +623 -0
  193. package/dist/chunk-YWOBW2PV.cjs +759 -0
  194. package/dist/chunk-YYXH45E2.js +426 -0
  195. package/dist/chunk-ZDSYWBIE.cjs +69 -0
  196. package/dist/chunk-ZU7NIRT6.js +325 -0
  197. package/dist/chunk-ZZ7RO2UQ.js +1243 -0
  198. package/dist/cli/holoscript-runner.cjs +2255 -0
  199. package/dist/cli/holoscript-runner.js +2230 -0
  200. package/dist/codebase/index.cjs +336 -0
  201. package/dist/codebase/index.js +331 -0
  202. package/dist/compiler/agent-inference.cjs +31 -0
  203. package/dist/compiler/agent-inference.js +8 -0
  204. package/dist/compiler/android-xr.cjs +24 -0
  205. package/dist/compiler/android-xr.js +14 -0
  206. package/dist/compiler/android.cjs +24 -0
  207. package/dist/compiler/android.js +14 -0
  208. package/dist/compiler/ar.cjs +23 -0
  209. package/dist/compiler/ar.js +11 -0
  210. package/dist/compiler/babylon.cjs +21 -0
  211. package/dist/compiler/babylon.js +15 -0
  212. package/dist/compiler/business-quest.cjs +43 -0
  213. package/dist/compiler/business-quest.js +4 -0
  214. package/dist/compiler/coco.cjs +27 -0
  215. package/dist/compiler/coco.js +4 -0
  216. package/dist/compiler/context.cjs +25 -0
  217. package/dist/compiler/context.d.ts +28 -0
  218. package/dist/compiler/context.js +8 -0
  219. package/dist/compiler/domain-block-utils.cjs +609 -0
  220. package/dist/compiler/domain-block-utils.js +11 -0
  221. package/dist/compiler/dtdl.cjs +27 -0
  222. package/dist/compiler/dtdl.js +11 -0
  223. package/dist/compiler/gltf-pipeline.cjs +39 -0
  224. package/dist/compiler/gltf-pipeline.js +4 -0
  225. package/dist/compiler/godot.cjs +18 -0
  226. package/dist/compiler/godot.js +12 -0
  227. package/dist/compiler/incremental.cjs +34 -0
  228. package/dist/compiler/incremental.js +9 -0
  229. package/dist/compiler/index.cjs +5408 -0
  230. package/dist/compiler/index.d.ts +334 -21
  231. package/dist/compiler/index.js +5069 -0
  232. package/dist/compiler/ios.cjs +23 -0
  233. package/dist/compiler/ios.js +13 -0
  234. package/dist/compiler/llm-provider-capabilities.cjs +25 -0
  235. package/dist/compiler/llm-provider-capabilities.d.ts +43 -0
  236. package/dist/compiler/llm-provider-capabilities.js +8 -0
  237. package/dist/compiler/multi-layer.cjs +24 -0
  238. package/dist/compiler/multi-layer.js +15 -0
  239. package/dist/compiler/nodetoy.cjs +23 -0
  240. package/dist/compiler/nodetoy.d.ts +69 -0
  241. package/dist/compiler/nodetoy.js +4 -0
  242. package/dist/compiler/openxr.cjs +19 -0
  243. package/dist/compiler/openxr.js +13 -0
  244. package/dist/compiler/playcanvas.cjs +19 -0
  245. package/dist/compiler/playcanvas.js +13 -0
  246. package/dist/compiler/r3f.cjs +39 -0
  247. package/dist/compiler/r3f.d.ts +22 -0
  248. package/dist/compiler/r3f.js +17 -0
  249. package/dist/compiler/remotion.cjs +23 -0
  250. package/dist/compiler/remotion.js +4 -0
  251. package/dist/compiler/reproducibility.cjs +39 -0
  252. package/dist/compiler/reproducibility.js +4 -0
  253. package/dist/compiler/sdf.cjs +24 -0
  254. package/dist/compiler/sdf.js +12 -0
  255. package/dist/compiler/semantic-scene.cjs +31 -0
  256. package/dist/compiler/semantic-scene.js +4 -0
  257. package/dist/compiler/state.cjs +21 -0
  258. package/dist/compiler/state.js +11 -0
  259. package/dist/compiler/trait-composition.cjs +30 -0
  260. package/dist/compiler/trait-composition.js +9 -0
  261. package/dist/compiler/unity.cjs +19 -0
  262. package/dist/compiler/unity.js +13 -0
  263. package/dist/compiler/unreal.cjs +23 -0
  264. package/dist/compiler/unreal.js +13 -0
  265. package/dist/compiler/urdf.cjs +52 -0
  266. package/dist/compiler/urdf.js +12 -0
  267. package/dist/compiler/usd-physics.cjs +26 -0
  268. package/dist/compiler/usd-physics.js +9 -0
  269. package/dist/compiler/visionos.cjs +19 -0
  270. package/dist/compiler/visionos.js +13 -0
  271. package/dist/compiler/vrchat.cjs +22 -0
  272. package/dist/compiler/vrchat.js +12 -0
  273. package/dist/compiler/vrr.cjs +24 -0
  274. package/dist/compiler/vrr.js +12 -0
  275. package/dist/compiler/wasm.cjs +36 -0
  276. package/dist/compiler/wasm.js +12 -0
  277. package/dist/compiler/webgpu.cjs +18 -0
  278. package/dist/compiler/webgpu.js +12 -0
  279. package/dist/coordinators/index.cjs +1266 -0
  280. package/dist/coordinators/index.d.ts +72 -1
  281. package/dist/coordinators/index.js +1259 -0
  282. package/dist/debugger.cjs +24 -0
  283. package/dist/debugger.js +11 -0
  284. package/dist/dist-ACXG6IH5.cjs +3113 -0
  285. package/dist/dist-VD4L6V7Q.js +3076 -0
  286. package/dist/entries/interop.cjs +64 -0
  287. package/dist/entries/interop.js +7 -0
  288. package/dist/entries/scripting.cjs +55 -0
  289. package/dist/entries/scripting.js +10 -0
  290. package/dist/hololand/index.cjs +146 -0
  291. package/dist/hololand/index.d.ts +70 -0
  292. package/dist/hololand/index.js +5 -0
  293. package/dist/index.cjs +44660 -0
  294. package/dist/index.d.ts +978 -21
  295. package/dist/index.js +41422 -0
  296. package/dist/math/tropical-spmv.cjs +45 -0
  297. package/dist/math/tropical-spmv.js +4 -0
  298. package/dist/math/vec3.cjs +57 -0
  299. package/dist/math/vec3.js +4 -0
  300. package/dist/ml-dsa-FSKBTE3W.cjs +1265 -0
  301. package/dist/ml-dsa-Z7JDKW4L.js +1265 -0
  302. package/dist/paper-0c-spike/index.cjs +150 -0
  303. package/dist/paper-0c-spike/index.js +141 -0
  304. package/dist/parser.cjs +61 -0
  305. package/dist/parser.d.ts +11 -0
  306. package/dist/parser.js +12 -0
  307. package/dist/playwright-3AT3AKTV.cjs +98673 -0
  308. package/dist/playwright-VZPDVUEQ.js +98660 -0
  309. package/dist/reconstruction/index.cjs +153 -0
  310. package/dist/reconstruction/index.d.ts +29 -2
  311. package/dist/reconstruction/index.js +8 -0
  312. package/dist/runtime.cjs +316 -0
  313. package/dist/runtime.d.ts +61 -0
  314. package/dist/runtime.js +31 -0
  315. package/dist/self-improvement/index.cjs +563 -0
  316. package/dist/self-improvement/index.js +545 -0
  317. package/dist/src-4ROC227P.cjs +2094 -0
  318. package/dist/src-HCCCSYP7.js +2073 -0
  319. package/dist/storage/index.cjs +38 -0
  320. package/dist/storage/index.js +5 -0
  321. package/dist/testing.cjs +25 -0
  322. package/dist/testing.d.ts +70 -0
  323. package/dist/testing.js +4 -0
  324. package/dist/traitDocs/traitDocs.cjs +33 -0
  325. package/dist/traitDocs/traitDocs.js +7 -0
  326. package/dist/traits/botanical-lotus.cjs +177 -0
  327. package/dist/traits/botanical-lotus.d.ts +502 -0
  328. package/dist/traits/botanical-lotus.js +7 -0
  329. package/dist/traits/engines/index.cjs +520 -0
  330. package/dist/traits/engines/index.js +380 -0
  331. package/dist/traits/index.cjs +24548 -0
  332. package/dist/traits/index.d.ts +171 -35
  333. package/dist/traits/index.js +21638 -0
  334. package/dist/traits/simulation-solver-factory.cjs +13 -0
  335. package/dist/traits/simulation-solver-factory.d.ts +23 -0
  336. package/dist/traits/simulation-solver-factory.js +4 -0
  337. package/dist/traits/webcam-gaze.cjs +35 -0
  338. package/dist/traits/webcam-gaze.d.ts +17 -0
  339. package/dist/traits/webcam-gaze.js +4 -0
  340. package/dist/type-checker.cjs +19 -0
  341. package/dist/type-checker.js +6 -0
  342. package/dist/world/index.cjs +947 -0
  343. package/dist/world/index.d.ts +147 -0
  344. package/dist/world/index.js +939 -0
  345. package/dist/world-model/index.cjs +1446 -0
  346. package/dist/world-model/index.d.ts +119 -0
  347. package/dist/world-model/index.js +1414 -0
  348. package/dist/wot/index.cjs +29 -0
  349. package/dist/wot/index.js +4 -0
  350. package/package.json +93 -49
@@ -0,0 +1,2176 @@
1
+ import { computeHoloMapReplayFingerprint } from './chunk-WNNE3PO3.js';
2
+ import { isWebGpuEnvironmentPresent, HOLOMAP_SIMULATION_CONTRACT_KIND } from './chunk-YF2I3MV3.js';
3
+
4
+ // src/version.ts
5
+ var HOLOSCRIPT_VERSION = "8.0.1" ;
6
+ var GIT_COMMIT_SHA = "efb467dce" ;
7
+ var BUILD_TIMESTAMP = "2026-06-04T22:03:08.151Z" ;
8
+ function getVersionString() {
9
+ const sha = `+${GIT_COMMIT_SHA}` ;
10
+ return `${HOLOSCRIPT_VERSION}${sha}`;
11
+ }
12
+ function getVersionInfo() {
13
+ return {
14
+ version: HOLOSCRIPT_VERSION,
15
+ gitCommitSha: GIT_COMMIT_SHA,
16
+ buildTimestamp: BUILD_TIMESTAMP,
17
+ versionString: getVersionString()
18
+ };
19
+ }
20
+
21
+ // src/reconstruction/imagePatchEmbedKernel.ts
22
+ var WGSL_IMAGE_PATCH_EMBED = `
23
+ struct Params {
24
+ imgH: u32,
25
+ imgW: u32,
26
+ patchH: u32,
27
+ patchW: u32,
28
+ numChannels:u32,
29
+ embedDim: u32,
30
+ patchLen: u32, // = patchH * patchW * numChannels
31
+ numPatchesX:u32, // = imgW / patchW
32
+ }
33
+
34
+ @group(0) @binding(0) var<storage, read> img: array<f32>; // [imgH, imgW, numChannels]
35
+ @group(0) @binding(1) var<storage, read> proj: array<f32>; // [embedDim, patchLen]
36
+ @group(0) @binding(2) var<storage, read_write> out: array<f32>; // [numPatches, embedDim]
37
+ @group(0) @binding(3) var<uniform> p: Params;
38
+
39
+ @compute @workgroup_size(64, 1, 1)
40
+ fn main(
41
+ @builtin(workgroup_id) wg: vec3<u32>,
42
+ @builtin(local_invocation_id) lid: vec3<u32>,
43
+ ) {
44
+ let patchIdx = wg.x;
45
+ let patchRow = patchIdx / p.numPatchesX;
46
+ let patchCol = patchIdx % p.numPatchesX;
47
+
48
+ // Top-left corner of patch in the image
49
+ let startRow = patchRow * p.patchH;
50
+ let startCol = patchCol * p.patchW;
51
+
52
+ // Fan out over embedDim
53
+ var d = lid.x;
54
+ while (d < p.embedDim) {
55
+ let projRow = d * p.patchLen;
56
+ var dot: f32 = 0.0;
57
+
58
+ for (var pr = 0u; pr < p.patchH; pr++) {
59
+ for (var pc2 = 0u; pc2 < p.patchW; pc2++) {
60
+ for (var ch = 0u; ch < p.numChannels; ch++) {
61
+ let imgRow = startRow + pr;
62
+ let imgCol = startCol + pc2;
63
+ let imgIdx = (imgRow * p.imgW + imgCol) * p.numChannels + ch;
64
+ let projIdx = projRow + (pr * p.patchW + pc2) * p.numChannels + ch;
65
+ dot = dot + img[imgIdx] * proj[projIdx];
66
+ }
67
+ }
68
+ }
69
+ out[patchIdx * p.embedDim + d] = dot;
70
+ d = d + 64u;
71
+ }
72
+ }
73
+ `;
74
+ function storageBuffer(device, data) {
75
+ const buf = device.createBuffer({
76
+ size: data.byteLength,
77
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
78
+ });
79
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
80
+ return buf;
81
+ }
82
+ function createImagePatchEmbedKernel(device) {
83
+ const shader = device.createShaderModule({ code: WGSL_IMAGE_PATCH_EMBED });
84
+ const pipeline = device.createComputePipeline({
85
+ layout: "auto",
86
+ compute: { module: shader, entryPoint: "main" }
87
+ });
88
+ return {
89
+ async run(image, proj, params) {
90
+ const { imgH, imgW, patchH, patchW, numChannels, embedDim } = params;
91
+ if (imgH % patchH !== 0) throw new Error(`imagePatchEmbed: imgH=${imgH} not divisible by patchH=${patchH}`);
92
+ if (imgW % patchW !== 0) throw new Error(`imagePatchEmbed: imgW=${imgW} not divisible by patchW=${patchW}`);
93
+ const patchLen = patchH * patchW * numChannels;
94
+ const numPatchesY = imgH / patchH;
95
+ const numPatchesX = imgW / patchW;
96
+ const numPatches = numPatchesY * numPatchesX;
97
+ if (image.length !== imgH * imgW * numChannels) throw new Error(`imagePatchEmbed: image size mismatch`);
98
+ if (proj.length !== embedDim * patchLen) throw new Error(`imagePatchEmbed: proj size mismatch`);
99
+ const outBytes = numPatches * embedDim * 4;
100
+ const imgBuf = storageBuffer(device, image);
101
+ const projBuf = storageBuffer(device, proj);
102
+ const outBuf = device.createBuffer({
103
+ size: outBytes,
104
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
105
+ });
106
+ const paramAB = new ArrayBuffer(32);
107
+ const pu = new Uint32Array(paramAB);
108
+ pu[0] = imgH;
109
+ pu[1] = imgW;
110
+ pu[2] = patchH;
111
+ pu[3] = patchW;
112
+ pu[4] = numChannels;
113
+ pu[5] = embedDim;
114
+ pu[6] = patchLen;
115
+ pu[7] = numPatchesX;
116
+ const paramsBuf = device.createBuffer({
117
+ size: paramAB.byteLength,
118
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
119
+ });
120
+ device.queue.writeBuffer(paramsBuf, 0, paramAB);
121
+ const bg = device.createBindGroup({
122
+ layout: pipeline.getBindGroupLayout(0),
123
+ entries: [
124
+ { binding: 0, resource: { buffer: imgBuf } },
125
+ { binding: 1, resource: { buffer: projBuf } },
126
+ { binding: 2, resource: { buffer: outBuf } },
127
+ { binding: 3, resource: { buffer: paramsBuf } }
128
+ ]
129
+ });
130
+ const enc = device.createCommandEncoder();
131
+ const pass = enc.beginComputePass();
132
+ pass.setPipeline(pipeline);
133
+ pass.setBindGroup(0, bg);
134
+ pass.dispatchWorkgroups(numPatches);
135
+ pass.end();
136
+ const staging = device.createBuffer({
137
+ size: outBytes,
138
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
139
+ });
140
+ enc.copyBufferToBuffer(outBuf, 0, staging, 0, outBytes);
141
+ device.queue.submit([enc.finish()]);
142
+ await staging.mapAsync(GPUMapMode.READ);
143
+ const result = new Float32Array(staging.getMappedRange().slice(0));
144
+ staging.unmap();
145
+ imgBuf.destroy();
146
+ projBuf.destroy();
147
+ outBuf.destroy();
148
+ paramsBuf.destroy();
149
+ staging.destroy();
150
+ return result;
151
+ }
152
+ };
153
+ }
154
+
155
+ // src/reconstruction/layerNormKernel.ts
156
+ var WGSL_LAYER_NORM_SOURCE = `
157
+ struct Params {
158
+ rows: u32,
159
+ dModel: u32,
160
+ eps: f32,
161
+ }
162
+ @group(0) @binding(0) var<storage, read> input: array<f32>;
163
+ @group(0) @binding(1) var<storage, read> gamma: array<f32>;
164
+ @group(0) @binding(2) var<storage, read> beta: array<f32>;
165
+ @group(0) @binding(3) var<storage, read_write> output: array<f32>;
166
+ @group(0) @binding(4) var<uniform> params: Params;
167
+ var<workgroup> reduceScratch: array<f32, 64>;
168
+ fn inputIndex(row: u32, col: u32) -> u32 {
169
+ return row * params.dModel + col;
170
+ }
171
+ @compute @workgroup_size(64, 1, 1)
172
+ fn main(
173
+ @builtin(workgroup_id) workgroupId: vec3<u32>,
174
+ @builtin(local_invocation_id) localId: vec3<u32>,
175
+ ) {
176
+ let row = workgroupId.x;
177
+ let lane = localId.x;
178
+ if (row >= params.rows || params.dModel == 0u) {
179
+ return;
180
+ }
181
+ var sum = 0.0;
182
+ for (var c: u32 = lane; c < params.dModel; c = c + 64u) {
183
+ sum = sum + input[inputIndex(row, c)];
184
+ }
185
+ reduceScratch[lane] = sum;
186
+ workgroupBarrier();
187
+ var stride: u32 = 32u;
188
+ loop {
189
+ if (lane < stride) {
190
+ reduceScratch[lane] = reduceScratch[lane] + reduceScratch[lane + stride];
191
+ }
192
+ workgroupBarrier();
193
+ if (stride == 1u) {
194
+ break;
195
+ }
196
+ stride = stride >> 1u;
197
+ }
198
+ let mean = reduceScratch[0] / f32(params.dModel);
199
+ var sq = 0.0;
200
+ for (var c: u32 = lane; c < params.dModel; c = c + 64u) {
201
+ let d = input[inputIndex(row, c)] - mean;
202
+ sq = sq + d * d;
203
+ }
204
+ reduceScratch[lane] = sq;
205
+ workgroupBarrier();
206
+ stride = 32u;
207
+ loop {
208
+ if (lane < stride) {
209
+ reduceScratch[lane] = reduceScratch[lane] + reduceScratch[lane + stride];
210
+ }
211
+ workgroupBarrier();
212
+ if (stride == 1u) {
213
+ break;
214
+ }
215
+ stride = stride >> 1u;
216
+ }
217
+ let variance = reduceScratch[0] / f32(params.dModel);
218
+ let invStd = inverseSqrt(variance + params.eps);
219
+ for (var c: u32 = lane; c < params.dModel; c = c + 64u) {
220
+ let idx = inputIndex(row, c);
221
+ let normalized = (input[idx] - mean) * invStd;
222
+ output[idx] = normalized * gamma[c] + beta[c];
223
+ }
224
+ }
225
+ `;
226
+ function createStorageBuffer(device, data) {
227
+ const buf = device.createBuffer({
228
+ size: data.byteLength,
229
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
230
+ });
231
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
232
+ return buf;
233
+ }
234
+ function createParamsBuffer(device, rows, dModel, eps) {
235
+ const paramsBytes = new ArrayBuffer(16);
236
+ const view = new DataView(paramsBytes);
237
+ view.setUint32(0, rows, true);
238
+ view.setUint32(4, dModel, true);
239
+ view.setFloat32(8, eps, true);
240
+ const params = device.createBuffer({
241
+ size: paramsBytes.byteLength,
242
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
243
+ });
244
+ device.queue.writeBuffer(params, 0, paramsBytes);
245
+ return params;
246
+ }
247
+ function createLayerNormKernel(device) {
248
+ const shader = device.createShaderModule({ code: WGSL_LAYER_NORM_SOURCE });
249
+ const pipeline = device.createComputePipeline({
250
+ layout: "auto",
251
+ compute: {
252
+ module: shader,
253
+ entryPoint: "main"
254
+ }
255
+ });
256
+ return {
257
+ async run(input, gamma, beta) {
258
+ if (gamma.length === 0) {
259
+ throw new Error("LayerNorm: gamma must be non-empty");
260
+ }
261
+ if (beta.length !== gamma.length) {
262
+ throw new Error("LayerNorm: beta length must match gamma length");
263
+ }
264
+ if (input.length % gamma.length !== 0) {
265
+ throw new Error("LayerNorm: input length must be a multiple of gamma length (dModel)");
266
+ }
267
+ const dModel = gamma.length;
268
+ const rows = input.length / dModel;
269
+ const eps = 1e-5;
270
+ const inputBuf = createStorageBuffer(device, input);
271
+ const gammaBuf = createStorageBuffer(device, gamma);
272
+ const betaBuf = createStorageBuffer(device, beta);
273
+ const outputBuf = device.createBuffer({
274
+ size: input.byteLength,
275
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
276
+ });
277
+ const paramsBuf = createParamsBuffer(device, rows, dModel, eps);
278
+ const readback = device.createBuffer({
279
+ size: input.byteLength,
280
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
281
+ });
282
+ const bindGroup = device.createBindGroup({
283
+ layout: pipeline.getBindGroupLayout(0),
284
+ entries: [
285
+ { binding: 0, resource: { buffer: inputBuf } },
286
+ { binding: 1, resource: { buffer: gammaBuf } },
287
+ { binding: 2, resource: { buffer: betaBuf } },
288
+ { binding: 3, resource: { buffer: outputBuf } },
289
+ { binding: 4, resource: { buffer: paramsBuf } }
290
+ ]
291
+ });
292
+ const encoder = device.createCommandEncoder();
293
+ {
294
+ const pass = encoder.beginComputePass();
295
+ pass.setPipeline(pipeline);
296
+ pass.setBindGroup(0, bindGroup);
297
+ pass.dispatchWorkgroups(rows, 1, 1);
298
+ pass.end();
299
+ }
300
+ encoder.copyBufferToBuffer(outputBuf, 0, readback, 0, input.byteLength);
301
+ device.queue.submit([encoder.finish()]);
302
+ await readback.mapAsync(GPUMapMode.READ);
303
+ const bytes = readback.getMappedRange();
304
+ const out = new Float32Array(bytes.slice(0));
305
+ readback.unmap();
306
+ inputBuf.destroy();
307
+ gammaBuf.destroy();
308
+ betaBuf.destroy();
309
+ outputBuf.destroy();
310
+ paramsBuf.destroy();
311
+ readback.destroy();
312
+ return out;
313
+ }
314
+ };
315
+ }
316
+
317
+ // src/reconstruction/gemmKernel.ts
318
+ var TILE = 8;
319
+ var WGSL_GEMM_SOURCE = `
320
+ const TILE: u32 = 8u;
321
+ struct Params {
322
+ M: u32,
323
+ N: u32,
324
+ K: u32,
325
+ }
326
+ @group(0) @binding(0) var<storage, read> A: array<f32>; // [M * K]
327
+ @group(0) @binding(1) var<storage, read> B: array<f32>; // [K * N]
328
+ @group(0) @binding(2) var<storage, read_write> C: array<f32>; // [M * N]
329
+ @group(0) @binding(3) var<uniform> p: Params;
330
+ var<workgroup> tileA: array<f32, 64>; // TILE * TILE = 8*8
331
+ var<workgroup> tileB: array<f32, 64>;
332
+ @compute @workgroup_size(8, 8, 1)
333
+ fn main(
334
+ @builtin(workgroup_id) wg: vec3<u32>,
335
+ @builtin(local_invocation_id) lid: vec3<u32>,
336
+ ) {
337
+ let row = wg.y * TILE + lid.y;
338
+ let col = wg.x * TILE + lid.x;
339
+ var acc: f32 = 0.0;
340
+ let numTiles = (p.K + TILE - 1u) / TILE;
341
+ for (var t = 0u; t < numTiles; t++) {
342
+ // Load tile of A (row of A) into shared
343
+ let aCol = t * TILE + lid.x;
344
+ if (row < p.M && aCol < p.K) {
345
+ tileA[lid.y * TILE + lid.x] = A[row * p.K + aCol];
346
+ } else {
347
+ tileA[lid.y * TILE + lid.x] = 0.0;
348
+ }
349
+ // Load tile of B (col of B) into shared
350
+ let bRow = t * TILE + lid.y;
351
+ if (bRow < p.K && col < p.N) {
352
+ tileB[lid.y * TILE + lid.x] = B[bRow * p.N + col];
353
+ } else {
354
+ tileB[lid.y * TILE + lid.x] = 0.0;
355
+ }
356
+ workgroupBarrier();
357
+ for (var k = 0u; k < TILE; k++) {
358
+ acc = acc + tileA[lid.y * TILE + k] * tileB[k * TILE + lid.x];
359
+ }
360
+ workgroupBarrier();
361
+ }
362
+ if (row < p.M && col < p.N) {
363
+ C[row * p.N + col] = acc;
364
+ }
365
+ }
366
+ `;
367
+ function storageBuffer2(device, data) {
368
+ const buf = device.createBuffer({
369
+ size: data.byteLength,
370
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
371
+ });
372
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
373
+ return buf;
374
+ }
375
+ function createGemmKernel(device) {
376
+ const shader = device.createShaderModule({ code: WGSL_GEMM_SOURCE });
377
+ const pipeline = device.createComputePipeline({
378
+ layout: "auto",
379
+ compute: { module: shader, entryPoint: "main" }
380
+ });
381
+ return {
382
+ async run(A, B, M, N, K) {
383
+ if (A.length !== M * K) throw new Error(`GEMM: A.length=${A.length} != M*K=${M * K}`);
384
+ if (B.length !== K * N) throw new Error(`GEMM: B.length=${B.length} != K*N=${K * N}`);
385
+ const aBuf = storageBuffer2(device, A);
386
+ const bBuf = storageBuffer2(device, B);
387
+ const cBuf = device.createBuffer({
388
+ size: M * N * 4,
389
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
390
+ });
391
+ const paramAB = new ArrayBuffer(16);
392
+ const pu = new Uint32Array(paramAB);
393
+ pu[0] = M;
394
+ pu[1] = N;
395
+ pu[2] = K;
396
+ const paramsBuf = device.createBuffer({
397
+ size: paramAB.byteLength,
398
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
399
+ });
400
+ device.queue.writeBuffer(paramsBuf, 0, paramAB);
401
+ const bg = device.createBindGroup({
402
+ layout: pipeline.getBindGroupLayout(0),
403
+ entries: [
404
+ { binding: 0, resource: { buffer: aBuf } },
405
+ { binding: 1, resource: { buffer: bBuf } },
406
+ { binding: 2, resource: { buffer: cBuf } },
407
+ { binding: 3, resource: { buffer: paramsBuf } }
408
+ ]
409
+ });
410
+ const wgX = Math.ceil(N / TILE);
411
+ const wgY = Math.ceil(M / TILE);
412
+ const enc = device.createCommandEncoder();
413
+ const pass = enc.beginComputePass();
414
+ pass.setPipeline(pipeline);
415
+ pass.setBindGroup(0, bg);
416
+ pass.dispatchWorkgroups(wgX, wgY, 1);
417
+ pass.end();
418
+ const staging = device.createBuffer({
419
+ size: M * N * 4,
420
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
421
+ });
422
+ enc.copyBufferToBuffer(cBuf, 0, staging, 0, M * N * 4);
423
+ device.queue.submit([enc.finish()]);
424
+ await staging.mapAsync(GPUMapMode.READ);
425
+ const result = new Float32Array(staging.getMappedRange().slice(0));
426
+ staging.unmap();
427
+ aBuf.destroy();
428
+ bBuf.destroy();
429
+ cBuf.destroy();
430
+ paramsBuf.destroy();
431
+ staging.destroy();
432
+ return result;
433
+ }
434
+ };
435
+ }
436
+
437
+ // src/reconstruction/ropeKernel.ts
438
+ var WGSL_ROPE_SOURCE = `
439
+ struct Params {
440
+ seqLen: u32,
441
+ numHeads: u32,
442
+ headDim: u32,
443
+ base: f32,
444
+ posOffset: u32,
445
+ }
446
+ @group(0) @binding(0) var<storage, read> qIn: array<f32>;
447
+ @group(0) @binding(1) var<storage, read_write> qOut: array<f32>;
448
+ @group(0) @binding(2) var<uniform> p: Params;
449
+ fn flatIdx(token: u32, head: u32, dim: u32) -> u32 {
450
+ return token * p.numHeads * p.headDim + head * p.headDim + dim;
451
+ }
452
+ @compute @workgroup_size(32, 1, 1)
453
+ fn main(
454
+ @builtin(workgroup_id) wgid: vec3<u32>,
455
+ @builtin(local_invocation_id) lid: vec3<u32>,
456
+ ) {
457
+ let wg = wgid.x;
458
+ let token = wg / p.numHeads;
459
+ let head = wg % p.numHeads;
460
+ if (token >= p.seqLen) { return; }
461
+ let pairs = p.headDim / 2u;
462
+ let pos = token + p.posOffset;
463
+ var pairIdx = lid.x;
464
+ while (pairIdx < pairs) {
465
+ let d = pairIdx;
466
+ let exponent = -2.0 * f32(d) / f32(p.headDim);
467
+ let theta = f32(pos) * pow(p.base, exponent);
468
+ let c = cos(theta);
469
+ let s = sin(theta);
470
+ let i0 = flatIdx(token, head, 2u * d);
471
+ let i1 = flatIdx(token, head, 2u * d + 1u);
472
+ let x0 = qIn[i0];
473
+ let x1 = qIn[i1];
474
+ qOut[i0] = x0 * c - x1 * s;
475
+ qOut[i1] = x0 * s + x1 * c;
476
+ pairIdx = pairIdx + 32u;
477
+ }
478
+ }
479
+ `;
480
+ function createStorageBuffer2(device, data) {
481
+ const buf = device.createBuffer({
482
+ size: data.byteLength,
483
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
484
+ });
485
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
486
+ return buf;
487
+ }
488
+ function createOutputBuffer(device, byteLength) {
489
+ return device.createBuffer({
490
+ size: byteLength,
491
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
492
+ });
493
+ }
494
+ function createParamsBuffer2(device, rp) {
495
+ const buf = new ArrayBuffer(32);
496
+ const u = new Uint32Array(buf);
497
+ const f = new Float32Array(buf);
498
+ u[0] = rp.seqLen;
499
+ u[1] = rp.numHeads;
500
+ u[2] = rp.headDim;
501
+ f[3] = rp.base;
502
+ u[4] = rp.posOffset;
503
+ const gpuBuf = device.createBuffer({
504
+ size: buf.byteLength,
505
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
506
+ });
507
+ device.queue.writeBuffer(gpuBuf, 0, buf);
508
+ return gpuBuf;
509
+ }
510
+ function createRopeKernel(device) {
511
+ const shader = device.createShaderModule({ code: WGSL_ROPE_SOURCE });
512
+ const pipeline = device.createComputePipeline({
513
+ layout: "auto",
514
+ compute: { module: shader, entryPoint: "main" }
515
+ });
516
+ return {
517
+ async run(input, params) {
518
+ const { seqLen, numHeads, headDim } = params;
519
+ const base = params.base ?? 1e4;
520
+ const posOffset = params.posOffset ?? 0;
521
+ if (headDim % 2 !== 0) {
522
+ throw new Error("RoPE: headDim must be even");
523
+ }
524
+ const expected = seqLen * numHeads * headDim;
525
+ if (input.length !== expected) {
526
+ throw new Error(
527
+ `RoPE: input length ${input.length} does not match seqLen*numHeads*headDim=${expected}`
528
+ );
529
+ }
530
+ const rp = { seqLen, numHeads, headDim, base, posOffset };
531
+ const inBuf = createStorageBuffer2(device, input);
532
+ const outBuf = createOutputBuffer(device, input.byteLength);
533
+ const paramsBuf = createParamsBuffer2(device, rp);
534
+ const stagingBuf = device.createBuffer({
535
+ size: input.byteLength,
536
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
537
+ });
538
+ const bg = device.createBindGroup({
539
+ layout: pipeline.getBindGroupLayout(0),
540
+ entries: [
541
+ { binding: 0, resource: { buffer: inBuf } },
542
+ { binding: 1, resource: { buffer: outBuf } },
543
+ { binding: 2, resource: { buffer: paramsBuf } }
544
+ ]
545
+ });
546
+ const enc = device.createCommandEncoder();
547
+ const pass = enc.beginComputePass();
548
+ pass.setPipeline(pipeline);
549
+ pass.setBindGroup(0, bg);
550
+ pass.dispatchWorkgroups(seqLen * numHeads);
551
+ pass.end();
552
+ enc.copyBufferToBuffer(outBuf, 0, stagingBuf, 0, input.byteLength);
553
+ device.queue.submit([enc.finish()]);
554
+ await stagingBuf.mapAsync(GPUMapMode.READ);
555
+ const result = new Float32Array(stagingBuf.getMappedRange().slice(0));
556
+ stagingBuf.unmap();
557
+ inBuf.destroy();
558
+ outBuf.destroy();
559
+ paramsBuf.destroy();
560
+ stagingBuf.destroy();
561
+ return result;
562
+ }
563
+ };
564
+ }
565
+
566
+ // src/reconstruction/fusedMHAKernel.ts
567
+ var WGSL_FUSED_MHA = `
568
+ struct Params {
569
+ numHeads: u32,
570
+ qLen: u32,
571
+ kLen: u32,
572
+ dHead: u32, // head dimension (= sqrt-scale base)
573
+ vHead: u32, // output feature dim per head (usually == dHead)
574
+ }
575
+
576
+ @group(0) @binding(0) var<storage, read> Q: array<f32>; // [h, qLen, dHead]
577
+ @group(0) @binding(1) var<storage, read> K: array<f32>; // [h, kLen, dHead]
578
+ @group(0) @binding(2) var<storage, read> V: array<f32>; // [h, kLen, vHead]
579
+ @group(0) @binding(3) var<storage, read_write> O: array<f32>; // [h, qLen, vHead]
580
+ @group(0) @binding(4) var<uniform> p: Params;
581
+
582
+ // Shared memory: reuse for scores then for v-accumulation
583
+ var<workgroup> scores: array<f32, 1024>; // max supported kLen per workgroup
584
+
585
+ @compute @workgroup_size(64, 1, 1)
586
+ fn main(
587
+ @builtin(workgroup_id) wg: vec3<u32>,
588
+ @builtin(local_invocation_id) lid: vec3<u32>,
589
+ ) {
590
+ // workgroup index encodes (head, qRow)
591
+ let head = wg.x / p.qLen;
592
+ let qRow = wg.x % p.qLen;
593
+
594
+ if (head >= p.numHeads || qRow >= p.qLen) { return; }
595
+
596
+ let scale: f32 = 1.0 / sqrt(f32(p.dHead));
597
+
598
+ // ---- Phase 1: compute dot products Q[qRow] \xB7 K[k] for all k ----
599
+ // Thread 0 does sequential dot products and stores in shared scores[]
600
+ // (for kLen \u2264 1024; if larger, falls back to partial accumulation)
601
+ if (lid.x == 0u) {
602
+ let qBase = (head * p.qLen + qRow) * p.dHead;
603
+ for (var ki = 0u; ki < p.kLen; ki++) {
604
+ let kBase = (head * p.kLen + ki) * p.dHead;
605
+ var dot: f32 = 0.0;
606
+ for (var d = 0u; d < p.dHead; d++) {
607
+ dot = dot + Q[qBase + d] * K[kBase + d];
608
+ }
609
+ scores[ki] = dot * scale;
610
+ }
611
+ }
612
+ workgroupBarrier();
613
+
614
+ // ---- Phase 2: stable softmax in-place ----
615
+ if (lid.x == 0u) {
616
+ var maxS: f32 = scores[0];
617
+ for (var ki = 1u; ki < p.kLen; ki++) {
618
+ maxS = max(maxS, scores[ki]);
619
+ }
620
+ var sumExp: f32 = 0.0;
621
+ for (var ki = 0u; ki < p.kLen; ki++) {
622
+ scores[ki] = exp(scores[ki] - maxS);
623
+ sumExp = sumExp + scores[ki];
624
+ }
625
+ for (var ki = 0u; ki < p.kLen; ki++) {
626
+ scores[ki] = scores[ki] / sumExp;
627
+ }
628
+ }
629
+ workgroupBarrier();
630
+
631
+ // ---- Phase 3: weighted sum over V ----
632
+ // Threads fan out over vHead dimension
633
+ let outBase = (head * p.qLen + qRow) * p.vHead;
634
+ var vi = lid.x;
635
+ while (vi < p.vHead) {
636
+ var acc: f32 = 0.0;
637
+ for (var ki = 0u; ki < p.kLen; ki++) {
638
+ acc = acc + scores[ki] * V[(head * p.kLen + ki) * p.vHead + vi];
639
+ }
640
+ O[outBase + vi] = acc;
641
+ vi = vi + 64u;
642
+ }
643
+ }
644
+ `;
645
+ function storageBuffer3(device, data) {
646
+ const buf = device.createBuffer({
647
+ size: data.byteLength,
648
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
649
+ });
650
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
651
+ return buf;
652
+ }
653
+ function createFusedMHAKernel(device) {
654
+ const shader = device.createShaderModule({ code: WGSL_FUSED_MHA });
655
+ const pipeline = device.createComputePipeline({
656
+ layout: "auto",
657
+ compute: { module: shader, entryPoint: "main" }
658
+ });
659
+ return {
660
+ async run(Q, K, V, params) {
661
+ const { numHeads, qLen, kLen, dHead } = params;
662
+ const vHead = params.vHead ?? dHead;
663
+ if (kLen > 1024) throw new Error(`fusedMHA: kLen=${kLen} exceeds shared memory limit 1024`);
664
+ if (Q.length !== numHeads * qLen * dHead) throw new Error(`fusedMHA: Q size mismatch`);
665
+ if (K.length !== numHeads * kLen * dHead) throw new Error(`fusedMHA: K size mismatch`);
666
+ if (V.length !== numHeads * kLen * vHead) throw new Error(`fusedMHA: V size mismatch`);
667
+ const outBytes = numHeads * qLen * vHead * 4;
668
+ const qBuf = storageBuffer3(device, Q);
669
+ const kBuf = storageBuffer3(device, K);
670
+ const vBuf = storageBuffer3(device, V);
671
+ const oBuf = device.createBuffer({
672
+ size: outBytes,
673
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
674
+ });
675
+ const paramAB = new ArrayBuffer(24);
676
+ const pu = new Uint32Array(paramAB);
677
+ pu[0] = numHeads;
678
+ pu[1] = qLen;
679
+ pu[2] = kLen;
680
+ pu[3] = dHead;
681
+ pu[4] = vHead;
682
+ const paramsBuf = device.createBuffer({
683
+ size: paramAB.byteLength,
684
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
685
+ });
686
+ device.queue.writeBuffer(paramsBuf, 0, paramAB);
687
+ const bg = device.createBindGroup({
688
+ layout: pipeline.getBindGroupLayout(0),
689
+ entries: [
690
+ { binding: 0, resource: { buffer: qBuf } },
691
+ { binding: 1, resource: { buffer: kBuf } },
692
+ { binding: 2, resource: { buffer: vBuf } },
693
+ { binding: 3, resource: { buffer: oBuf } },
694
+ { binding: 4, resource: { buffer: paramsBuf } }
695
+ ]
696
+ });
697
+ const workgroups = numHeads * qLen;
698
+ const enc = device.createCommandEncoder();
699
+ const pass = enc.beginComputePass();
700
+ pass.setPipeline(pipeline);
701
+ pass.setBindGroup(0, bg);
702
+ pass.dispatchWorkgroups(workgroups);
703
+ pass.end();
704
+ const staging = device.createBuffer({
705
+ size: outBytes,
706
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
707
+ });
708
+ enc.copyBufferToBuffer(oBuf, 0, staging, 0, outBytes);
709
+ device.queue.submit([enc.finish()]);
710
+ await staging.mapAsync(GPUMapMode.READ);
711
+ const result = new Float32Array(staging.getMappedRange().slice(0));
712
+ staging.unmap();
713
+ qBuf.destroy();
714
+ kBuf.destroy();
715
+ vBuf.destroy();
716
+ oBuf.destroy();
717
+ paramsBuf.destroy();
718
+ staging.destroy();
719
+ return result;
720
+ }
721
+ };
722
+ }
723
+
724
+ // src/reconstruction/geluKernel.ts
725
+ var WGSL_GELU_SOURCE = `
726
+ struct Params {
727
+ elements: u32,
728
+ }
729
+ @group(0) @binding(0) var<storage, read> input: array<f32>;
730
+ @group(0) @binding(1) var<storage, read_write> output: array<f32>;
731
+ @group(0) @binding(2) var<uniform> params: Params;
732
+ fn gelu(x: f32) -> f32 {
733
+ let k0 = 0.7978845608;
734
+ let k1 = 0.044715;
735
+ let x3 = x * x * x;
736
+ let inner = k0 * (x + k1 * x3);
737
+ return 0.5 * x * (1.0 + tanh(inner));
738
+ }
739
+ @compute @workgroup_size(64, 1, 1)
740
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
741
+ var idx = gid.x;
742
+ let stride = 64u;
743
+ while (idx < params.elements) {
744
+ output[idx] = gelu(input[idx]);
745
+ idx = idx + stride;
746
+ }
747
+ }
748
+ `;
749
+ function createInputBuffer(device, data) {
750
+ const buf = device.createBuffer({
751
+ size: data.byteLength,
752
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
753
+ });
754
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
755
+ return buf;
756
+ }
757
+ function createParamsBuffer3(device, elements) {
758
+ const paramsBytes = new ArrayBuffer(16);
759
+ const view = new DataView(paramsBytes);
760
+ view.setUint32(0, elements, true);
761
+ const params = device.createBuffer({
762
+ size: paramsBytes.byteLength,
763
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
764
+ });
765
+ device.queue.writeBuffer(params, 0, paramsBytes);
766
+ return params;
767
+ }
768
+ function createGeluKernel(device) {
769
+ const shader = device.createShaderModule({ code: WGSL_GELU_SOURCE });
770
+ const pipeline = device.createComputePipeline({
771
+ layout: "auto",
772
+ compute: {
773
+ module: shader,
774
+ entryPoint: "main"
775
+ }
776
+ });
777
+ return {
778
+ async run(input) {
779
+ if (input.length === 0) {
780
+ throw new Error("GELU: input must be non-empty");
781
+ }
782
+ const inputBuf = createInputBuffer(device, input);
783
+ const outputBuf = device.createBuffer({
784
+ size: input.byteLength,
785
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
786
+ });
787
+ const paramsBuf = createParamsBuffer3(device, input.length);
788
+ const readback = device.createBuffer({
789
+ size: input.byteLength,
790
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
791
+ });
792
+ const bindGroup = device.createBindGroup({
793
+ layout: pipeline.getBindGroupLayout(0),
794
+ entries: [
795
+ { binding: 0, resource: { buffer: inputBuf } },
796
+ { binding: 1, resource: { buffer: outputBuf } },
797
+ { binding: 2, resource: { buffer: paramsBuf } }
798
+ ]
799
+ });
800
+ const encoder = device.createCommandEncoder();
801
+ {
802
+ const pass = encoder.beginComputePass();
803
+ pass.setPipeline(pipeline);
804
+ pass.setBindGroup(0, bindGroup);
805
+ pass.dispatchWorkgroups(1, 1, 1);
806
+ pass.end();
807
+ }
808
+ encoder.copyBufferToBuffer(outputBuf, 0, readback, 0, input.byteLength);
809
+ device.queue.submit([encoder.finish()]);
810
+ await readback.mapAsync(GPUMapMode.READ);
811
+ const bytes = readback.getMappedRange();
812
+ const out = new Float32Array(bytes.slice(0));
813
+ readback.unmap();
814
+ inputBuf.destroy();
815
+ outputBuf.destroy();
816
+ paramsBuf.destroy();
817
+ readback.destroy();
818
+ return out;
819
+ }
820
+ };
821
+ }
822
+
823
+ // src/reconstruction/holoMapMicroEncoder.ts
824
+ var MICRO = 14;
825
+ var EMBED_DIM = 32;
826
+ var NUM_HEADS = 4;
827
+ var HEAD_DIM = 8;
828
+ var PATCH_LEN = MICRO * MICRO * 3;
829
+ var weightCache = /* @__PURE__ */ new Map();
830
+ function hashConfigSeed(config) {
831
+ let h = config.seed >>> 0;
832
+ const s = config.modelHash || "";
833
+ for (let i = 0; i < s.length; i += 1) {
834
+ h = Math.imul(h ^ s.charCodeAt(i), 2654435769);
835
+ }
836
+ return h >>> 0;
837
+ }
838
+ function mulberry32(seed) {
839
+ let t = seed >>> 0;
840
+ return () => {
841
+ t += 1831565813;
842
+ let r = Math.imul(t ^ t >>> 15, t | 1);
843
+ r ^= r + Math.imul(r ^ r >>> 7, r | 61);
844
+ return ((r ^ r >>> 14) >>> 0) / 4294967296;
845
+ };
846
+ }
847
+ function fillGaussian2D(out, rng, scale) {
848
+ for (let i = 0; i < out.length; i += 1) out[i] = (rng() * 2 - 1) * scale;
849
+ }
850
+ function microWeightKey(config) {
851
+ return `${config.seed}\0${config.modelHash || ""}`;
852
+ }
853
+ function getMicroWeights(config) {
854
+ const key = microWeightKey(config);
855
+ const cached = weightCache.get(key);
856
+ if (cached) return cached;
857
+ const rng = mulberry32(hashConfigSeed(config));
858
+ const weights = {
859
+ proj: new Float32Array(EMBED_DIM * PATCH_LEN),
860
+ Wq: new Float32Array(EMBED_DIM * EMBED_DIM),
861
+ Wk: new Float32Array(EMBED_DIM * EMBED_DIM),
862
+ Wv: new Float32Array(EMBED_DIM * EMBED_DIM),
863
+ Wxyz: new Float32Array(EMBED_DIM * 3),
864
+ gamma1: new Float32Array(EMBED_DIM).fill(1),
865
+ beta1: new Float32Array(EMBED_DIM).fill(0),
866
+ gamma2: new Float32Array(EMBED_DIM).fill(1),
867
+ beta2: new Float32Array(EMBED_DIM).fill(0)
868
+ };
869
+ fillGaussian2D(weights.proj, rng, 0.02);
870
+ fillGaussian2D(weights.Wq, rng, 0.02);
871
+ fillGaussian2D(weights.Wk, rng, 0.02);
872
+ fillGaussian2D(weights.Wv, rng, 0.02);
873
+ fillGaussian2D(weights.Wxyz, rng, 0.05);
874
+ weightCache.set(key, weights);
875
+ return weights;
876
+ }
877
+ function frameToMicroImage(frame) {
878
+ const out = new Float32Array(MICRO * MICRO * 3);
879
+ for (let y = 0; y < MICRO; y += 1) {
880
+ const sy = Math.min(frame.height - 1, Math.floor(y * frame.height / MICRO));
881
+ for (let x = 0; x < MICRO; x += 1) {
882
+ const sx = Math.min(frame.width - 1, Math.floor(x * frame.width / MICRO));
883
+ const src = (sy * frame.width + sx) * frame.stride;
884
+ const dst = (y * MICRO + x) * 3;
885
+ out[dst] = frame.rgb[src] / 255;
886
+ out[dst + 1] = frame.rgb[src + 1] / 255;
887
+ out[dst + 2] = frame.rgb[src + 2] / 255;
888
+ }
889
+ }
890
+ return out;
891
+ }
892
+ function patchEmbedCpu(image, proj, embedDim) {
893
+ const out = new Float32Array(embedDim);
894
+ for (let d = 0; d < embedDim; d += 1) {
895
+ let dot = 0;
896
+ const row = d * PATCH_LEN;
897
+ for (let i = 0; i < PATCH_LEN; i += 1) dot += image[i] * proj[row + i];
898
+ out[d] = dot;
899
+ }
900
+ return out;
901
+ }
902
+ function gemmCpu(a, b, M, K, N) {
903
+ const c = new Float32Array(M * N);
904
+ for (let m = 0; m < M; m += 1) {
905
+ for (let n = 0; n < N; n += 1) {
906
+ let acc = 0;
907
+ for (let k = 0; k < K; k += 1) acc += a[m * K + k] * b[k * N + n];
908
+ c[m * N + n] = acc;
909
+ }
910
+ }
911
+ return c;
912
+ }
913
+ function layerNormCpu(x, gamma, beta, rows, d) {
914
+ const y = new Float32Array(x.length);
915
+ const eps = 1e-5;
916
+ for (let r = 0; r < rows; r += 1) {
917
+ let sum = 0;
918
+ for (let j = 0; j < d; j += 1) sum += x[r * d + j];
919
+ const mean = sum / d;
920
+ let sq = 0;
921
+ for (let j = 0; j < d; j += 1) {
922
+ const t = x[r * d + j] - mean;
923
+ sq += t * t;
924
+ }
925
+ const inv = 1 / Math.sqrt(sq / d + eps);
926
+ for (let j = 0; j < d; j += 1) {
927
+ const norm = (x[r * d + j] - mean) * inv;
928
+ y[r * d + j] = norm * gamma[j] + beta[j];
929
+ }
930
+ }
931
+ return y;
932
+ }
933
+ function geluCpu(x) {
934
+ const y = new Float32Array(x.length);
935
+ const k0 = 0.7978845608;
936
+ const k1 = 0.044715;
937
+ for (let i = 0; i < x.length; i += 1) {
938
+ const v = x[i];
939
+ const x3 = v * v * v;
940
+ const inner = k0 * (v + k1 * x3);
941
+ y[i] = 0.5 * v * (1 + Math.tanh(inner));
942
+ }
943
+ return y;
944
+ }
945
+ function mhaCpu(Q, K, V, numHeads, qLen, kLen, dHead, vHead) {
946
+ const out = new Float32Array(numHeads * qLen * vHead);
947
+ const scale = 1 / Math.sqrt(Math.max(dHead, 1));
948
+ for (let h = 0; h < numHeads; h += 1) {
949
+ for (let qi = 0; qi < qLen; qi += 1) {
950
+ const scores = new Float32Array(kLen);
951
+ let maxS = -Infinity;
952
+ const qBase = (h * qLen + qi) * dHead;
953
+ for (let ki = 0; ki < kLen; ki += 1) {
954
+ const kBase = (h * kLen + ki) * dHead;
955
+ let dot = 0;
956
+ for (let d = 0; d < dHead; d += 1) dot += Q[qBase + d] * K[kBase + d];
957
+ const s = dot * scale;
958
+ scores[ki] = s;
959
+ maxS = Math.max(maxS, s);
960
+ }
961
+ let denom = 0;
962
+ for (let ki = 0; ki < kLen; ki += 1) {
963
+ scores[ki] = Math.exp(scores[ki] - maxS);
964
+ denom += scores[ki];
965
+ }
966
+ const oBase = (h * qLen + qi) * vHead;
967
+ for (let vi = 0; vi < vHead; vi += 1) {
968
+ let acc = 0;
969
+ for (let ki = 0; ki < kLen; ki += 1) {
970
+ acc += scores[ki] / Math.max(denom, 1e-9) * V[(h * kLen + ki) * vHead + vi];
971
+ }
972
+ out[oBase + vi] = acc;
973
+ }
974
+ }
975
+ }
976
+ return out;
977
+ }
978
+ function ropeCpuSync(q, seqLen, numHeads, headDim, posOffset) {
979
+ const out = q.slice();
980
+ const base = 1e4;
981
+ for (let t = 0; t < seqLen; t += 1) {
982
+ const pos = t + posOffset;
983
+ for (let h = 0; h < numHeads; h += 1) {
984
+ for (let p = 0; p < headDim / 2; p += 1) {
985
+ const exp = -2 * (p / headDim);
986
+ const theta = pos * base ** exp;
987
+ const c = Math.cos(theta);
988
+ const s = Math.sin(theta);
989
+ const i0 = (t * numHeads + h) * headDim + 2 * p;
990
+ const i1 = i0 + 1;
991
+ const x0 = q[i0];
992
+ const x1 = q[i1];
993
+ out[i0] = x0 * c - x1 * s;
994
+ out[i1] = x0 * s + x1 * c;
995
+ }
996
+ }
997
+ }
998
+ return out;
999
+ }
1000
+ async function tryCreateHoloMapEncoderDevice() {
1001
+ try {
1002
+ const nav = globalThis.navigator;
1003
+ if (!nav?.gpu) return null;
1004
+ const adapter = await nav.gpu.requestAdapter();
1005
+ if (!adapter) return null;
1006
+ return await adapter.requestDevice();
1007
+ } catch {
1008
+ return null;
1009
+ }
1010
+ }
1011
+ function createHoloMapMicroEncoder(device) {
1012
+ const patchEmbed = createImagePatchEmbedKernel(device);
1013
+ const layerNorm = createLayerNormKernel(device);
1014
+ const gemm = createGemmKernel(device);
1015
+ const rope = createRopeKernel(device);
1016
+ const fusedMha = createFusedMHAKernel(device);
1017
+ const gelu = createGeluKernel(device);
1018
+ return {
1019
+ async run(frame, config) {
1020
+ const weights = getMicroWeights(config);
1021
+ const image = frameToMicroImage(frame);
1022
+ let tokens = await patchEmbed.run(image, weights.proj, {
1023
+ imgH: MICRO,
1024
+ imgW: MICRO,
1025
+ patchH: MICRO,
1026
+ patchW: MICRO,
1027
+ numChannels: 3,
1028
+ embedDim: EMBED_DIM
1029
+ });
1030
+ tokens = await layerNorm.run(tokens, weights.gamma1, weights.beta1);
1031
+ const qFlat = await gemm.run(tokens, weights.Wq, 1, EMBED_DIM, EMBED_DIM);
1032
+ const kFlat = await gemm.run(tokens, weights.Wk, 1, EMBED_DIM, EMBED_DIM);
1033
+ const vFlat = await gemm.run(tokens, weights.Wv, 1, EMBED_DIM, EMBED_DIM);
1034
+ const q3 = await rope.run(qFlat, { seqLen: 1, numHeads: NUM_HEADS, headDim: HEAD_DIM, posOffset: frame.index });
1035
+ const k3 = await rope.run(kFlat, { seqLen: 1, numHeads: NUM_HEADS, headDim: HEAD_DIM, posOffset: frame.index });
1036
+ let attn = await fusedMha.run(q3, k3, vFlat, {
1037
+ numHeads: NUM_HEADS,
1038
+ qLen: 1,
1039
+ kLen: 1,
1040
+ dHead: HEAD_DIM,
1041
+ vHead: HEAD_DIM
1042
+ });
1043
+ attn = await layerNorm.run(attn, weights.gamma2, weights.beta2);
1044
+ attn = await gelu.run(attn);
1045
+ return gemm.run(attn, weights.Wxyz, 1, EMBED_DIM, 3);
1046
+ }
1047
+ };
1048
+ }
1049
+ async function runHoloMapMicroEncoderCpu(frame, config) {
1050
+ const weights = getMicroWeights(config);
1051
+ const image = frameToMicroImage(frame);
1052
+ let tokens = patchEmbedCpu(image, weights.proj, EMBED_DIM);
1053
+ tokens = layerNormCpu(tokens, weights.gamma1, weights.beta1, 1, EMBED_DIM);
1054
+ const qFlat = gemmCpu(tokens, weights.Wq, 1, EMBED_DIM, EMBED_DIM);
1055
+ const kFlat = gemmCpu(tokens, weights.Wk, 1, EMBED_DIM, EMBED_DIM);
1056
+ const vFlat = gemmCpu(tokens, weights.Wv, 1, EMBED_DIM, EMBED_DIM);
1057
+ const qR = ropeCpuSync(qFlat, 1, NUM_HEADS, HEAD_DIM, frame.index);
1058
+ const kR = ropeCpuSync(kFlat, 1, NUM_HEADS, HEAD_DIM, frame.index);
1059
+ let attn = mhaCpu(qR, kR, vFlat, NUM_HEADS, 1, 1, HEAD_DIM, HEAD_DIM);
1060
+ attn = layerNormCpu(attn, weights.gamma2, weights.beta2, 1, EMBED_DIM);
1061
+ attn = geluCpu(attn);
1062
+ return gemmCpu(attn, weights.Wxyz, 1, EMBED_DIM, 3);
1063
+ }
1064
+
1065
+ // src/reconstruction/holoMapTelemetry.ts
1066
+ var highVolumeEvents = /* @__PURE__ */ new Set(["step", "step_throttled", "step_evict"]);
1067
+ var pendingStepSummaries = /* @__PURE__ */ new Map();
1068
+ function loggingDisabled() {
1069
+ try {
1070
+ return typeof process !== "undefined" && process.env?.HOLOMAP_LOG === "0";
1071
+ } catch {
1072
+ return false;
1073
+ }
1074
+ }
1075
+ function verboseStepLoggingEnabled() {
1076
+ try {
1077
+ if (typeof process === "undefined") {
1078
+ return false;
1079
+ }
1080
+ const value = process.env?.HOLOMAP_LOG?.toLowerCase();
1081
+ return value === "verbose" || value === "debug" || process.env?.HOLOMAP_LOG_STEPS === "1";
1082
+ } catch {
1083
+ return false;
1084
+ }
1085
+ }
1086
+ function numericDetail(detail, key) {
1087
+ const value = detail?.[key];
1088
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1089
+ }
1090
+ function getOrCreateStepSummary(runId) {
1091
+ const existing = pendingStepSummaries.get(runId);
1092
+ if (existing) {
1093
+ return existing;
1094
+ }
1095
+ const summary = {
1096
+ stepCount: 0,
1097
+ firstFrameIndex: null,
1098
+ lastFrameIndex: null,
1099
+ pointCountTotal: 0,
1100
+ totalStepMs: 0,
1101
+ maxStepMs: 0,
1102
+ minStepMs: Infinity,
1103
+ throttledCount: 0,
1104
+ evictionCount: 0,
1105
+ evictedPointTotal: 0,
1106
+ lastThrottledFrameIndex: null,
1107
+ lastEvictedFrameIndex: null,
1108
+ lastRetainedSteps: null,
1109
+ latestAvgStepMs: null,
1110
+ latestThrottledSoFar: null
1111
+ };
1112
+ pendingStepSummaries.set(runId, summary);
1113
+ return summary;
1114
+ }
1115
+ function accumulateStepSummary(runId, event, detail) {
1116
+ const summary = getOrCreateStepSummary(runId);
1117
+ const frameIndex = numericDetail(detail, "frameIndex");
1118
+ if (event === "step") {
1119
+ summary.stepCount += 1;
1120
+ if (frameIndex !== null) {
1121
+ summary.firstFrameIndex = summary.firstFrameIndex ?? frameIndex;
1122
+ summary.lastFrameIndex = frameIndex;
1123
+ }
1124
+ summary.pointCountTotal += numericDetail(detail, "pointCount") ?? 0;
1125
+ const stepMs = numericDetail(detail, "stepMs");
1126
+ if (stepMs !== null) {
1127
+ summary.totalStepMs += stepMs;
1128
+ summary.maxStepMs = Math.max(summary.maxStepMs, stepMs);
1129
+ summary.minStepMs = Math.min(summary.minStepMs, stepMs);
1130
+ }
1131
+ summary.latestAvgStepMs = numericDetail(detail, "avgStepMs") ?? summary.latestAvgStepMs;
1132
+ summary.latestThrottledSoFar = numericDetail(detail, "throttledSoFar") ?? summary.latestThrottledSoFar;
1133
+ return;
1134
+ }
1135
+ if (event === "step_throttled") {
1136
+ summary.throttledCount += 1;
1137
+ summary.lastThrottledFrameIndex = frameIndex ?? summary.lastThrottledFrameIndex;
1138
+ return;
1139
+ }
1140
+ summary.evictionCount += 1;
1141
+ summary.lastEvictedFrameIndex = frameIndex ?? summary.lastEvictedFrameIndex;
1142
+ summary.evictedPointTotal += numericDetail(detail, "evictedPoints") ?? 0;
1143
+ summary.lastRetainedSteps = numericDetail(detail, "retainedSteps") ?? summary.lastRetainedSteps;
1144
+ }
1145
+ function emitLogPayload(runId, event, detail) {
1146
+ const payload = {
1147
+ t: (/* @__PURE__ */ new Date()).toISOString(),
1148
+ runId,
1149
+ event,
1150
+ ...detail
1151
+ };
1152
+ console.log(`[HoloMap] ${JSON.stringify(payload)}`);
1153
+ }
1154
+ function flushStepSummary(runId, reason) {
1155
+ const summary = pendingStepSummaries.get(runId);
1156
+ if (!summary) {
1157
+ return;
1158
+ }
1159
+ pendingStepSummaries.delete(runId);
1160
+ const throttledCount = Math.max(summary.throttledCount, summary.latestThrottledSoFar ?? 0);
1161
+ emitLogPayload(runId, "step_summary", {
1162
+ reason,
1163
+ stepCount: summary.stepCount,
1164
+ firstFrameIndex: summary.firstFrameIndex,
1165
+ lastFrameIndex: summary.lastFrameIndex,
1166
+ pointCountTotal: summary.pointCountTotal,
1167
+ avgStepMs: summary.stepCount > 0 ? Math.round(summary.totalStepMs / summary.stepCount) : summary.latestAvgStepMs ?? 0,
1168
+ maxStepMs: summary.stepCount > 0 ? Math.round(summary.maxStepMs) : 0,
1169
+ minStepMs: summary.stepCount > 0 ? Math.round(summary.minStepMs) : 0,
1170
+ throttledCount,
1171
+ evictionCount: summary.evictionCount,
1172
+ evictedPointTotal: summary.evictedPointTotal,
1173
+ lastThrottledFrameIndex: summary.lastThrottledFrameIndex,
1174
+ lastEvictedFrameIndex: summary.lastEvictedFrameIndex,
1175
+ lastRetainedSteps: summary.lastRetainedSteps
1176
+ });
1177
+ }
1178
+ function createHoloMapRunId() {
1179
+ try {
1180
+ const c = globalThis.crypto;
1181
+ if (c && "randomUUID" in c) {
1182
+ return `hm_${c.randomUUID().slice(0, 8)}`;
1183
+ }
1184
+ } catch {
1185
+ }
1186
+ return `hm_${Math.random().toString(36).slice(2, 10)}`;
1187
+ }
1188
+ function logHoloMapEvent(runId, event, detail) {
1189
+ if (loggingDisabled()) {
1190
+ return;
1191
+ }
1192
+ if (highVolumeEvents.has(event) && !verboseStepLoggingEnabled()) {
1193
+ accumulateStepSummary(runId, event, detail);
1194
+ return;
1195
+ }
1196
+ if (event === "finalize" || event === "dispose" || event === "error") {
1197
+ flushStepSummary(runId, event);
1198
+ }
1199
+ emitLogPayload(runId, event, detail);
1200
+ }
1201
+ function resetHoloMapTelemetryForTests() {
1202
+ pendingStepSummaries.clear();
1203
+ }
1204
+
1205
+ // src/reconstruction/holoMapWeightLoader.ts
1206
+ async function sha256Digest(bytes) {
1207
+ const c = globalThis.crypto;
1208
+ if (c?.subtle) {
1209
+ const buf = await c.subtle.digest("SHA-256", bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
1210
+ return bufferToHex(new Uint8Array(buf));
1211
+ }
1212
+ const { createHash } = await import('crypto');
1213
+ return createHash("sha256").update(bytes).digest("hex");
1214
+ }
1215
+ function bufferToHex(bytes) {
1216
+ let s = "";
1217
+ for (let i = 0; i < bytes.length; i += 1) s += bytes[i].toString(16).padStart(2, "0");
1218
+ return s;
1219
+ }
1220
+ function normalizeWeightCidDigest(cid) {
1221
+ const t = cid.trim().toLowerCase();
1222
+ if (t.startsWith("sha256:")) return t.slice("sha256:".length);
1223
+ return t;
1224
+ }
1225
+ async function assertWeightCid(bytes, weightCid) {
1226
+ const expect = normalizeWeightCidDigest(weightCid);
1227
+ if (!/^[0-9a-f]{64}$/.test(expect)) {
1228
+ throw new Error(
1229
+ `HoloMap weights: weightCid must be sha256:hex64 or 64-char hex (got "${weightCid.slice(0, 24)}...")`
1230
+ );
1231
+ }
1232
+ const actual = await sha256Digest(bytes);
1233
+ if (actual !== expect) {
1234
+ throw new Error(
1235
+ `HoloMap weights: digest mismatch \u2014 expected ${expect.slice(0, 12)}\u2026 got ${actual.slice(0, 12)}\u2026`
1236
+ );
1237
+ }
1238
+ }
1239
+ function isFileUrl(url) {
1240
+ return url.startsWith("file://");
1241
+ }
1242
+ async function readFileUrl(url) {
1243
+ const { readFile } = await import('fs/promises');
1244
+ const path = url.slice("file://".length);
1245
+ const buf = await readFile(path);
1246
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1247
+ }
1248
+ async function sleep(ms) {
1249
+ return new Promise((r) => setTimeout(r, ms));
1250
+ }
1251
+ async function loadHoloMapWeightBlob(options) {
1252
+ const { weightUrl, weightUrls = [], weightCid, fetchImpl, skipCache, localResolver } = options;
1253
+ const f = fetchImpl ?? globalThis.fetch;
1254
+ if (localResolver && weightCid) {
1255
+ try {
1256
+ const local = await localResolver(weightCid);
1257
+ if (local) {
1258
+ await assertWeightCid(new Uint8Array(local), weightCid);
1259
+ await putCachedWeightBlob(weightCid, local);
1260
+ return { bytes: local, verified: true, source: "file" };
1261
+ }
1262
+ } catch {
1263
+ }
1264
+ }
1265
+ if (weightCid && !skipCache) {
1266
+ const cached = await getCachedWeightBlob(weightCid);
1267
+ if (cached) {
1268
+ return { bytes: cached, verified: true, source: "cache" };
1269
+ }
1270
+ }
1271
+ const candidates = [weightUrl, ...weightUrls];
1272
+ for (const url of candidates) {
1273
+ if (isFileUrl(url)) {
1274
+ try {
1275
+ const buf = await readFileUrl(url);
1276
+ if (weightCid) {
1277
+ await assertWeightCid(new Uint8Array(buf), weightCid);
1278
+ }
1279
+ if (weightCid) {
1280
+ await putCachedWeightBlob(weightCid, buf);
1281
+ }
1282
+ return { bytes: buf, verified: !!weightCid, source: "file" };
1283
+ } catch {
1284
+ }
1285
+ continue;
1286
+ }
1287
+ if (typeof f !== "function") {
1288
+ throw new Error("HoloMap weights: fetch is not available (provide fetchImpl)");
1289
+ }
1290
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1291
+ try {
1292
+ const res = await f(url);
1293
+ if (!res.ok) {
1294
+ throw new Error(`GET ${url} failed (${res.status} ${res.statusText})`);
1295
+ }
1296
+ const buf = await res.arrayBuffer();
1297
+ if (weightCid) {
1298
+ await assertWeightCid(new Uint8Array(buf), weightCid);
1299
+ }
1300
+ if (weightCid) {
1301
+ await putCachedWeightBlob(weightCid, buf);
1302
+ }
1303
+ return { bytes: buf, verified: !!weightCid, source: "network" };
1304
+ } catch (err) {
1305
+ if (attempt < 2) {
1306
+ await sleep(250 * (attempt + 1));
1307
+ } else {
1308
+ break;
1309
+ }
1310
+ }
1311
+ }
1312
+ }
1313
+ throw new Error(`HoloMap weights: all sources exhausted (tried ${candidates.length} URL(s))`);
1314
+ }
1315
+
1316
+ // src/reconstruction/holoMapWeightCache.ts
1317
+ var DB_NAME = "HoloMapWeightCache";
1318
+ var STORE_NAME = "weights";
1319
+ var DB_VERSION = 1;
1320
+ async function openIndexedDB() {
1321
+ return new Promise((resolve, reject) => {
1322
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
1323
+ req.onerror = () => reject(req.error);
1324
+ req.onsuccess = () => resolve(req.result);
1325
+ req.onupgradeneeded = () => {
1326
+ req.result.createObjectStore(STORE_NAME);
1327
+ };
1328
+ });
1329
+ }
1330
+ async function idbGet(db, key) {
1331
+ return new Promise((resolve, reject) => {
1332
+ const tx = db.transaction(STORE_NAME, "readonly");
1333
+ const store = tx.objectStore(STORE_NAME);
1334
+ const req = store.get(key);
1335
+ req.onsuccess = () => {
1336
+ const result = req.result;
1337
+ if (result instanceof ArrayBuffer) resolve(result);
1338
+ else if (result && typeof result.byteLength === "number") resolve(result);
1339
+ else resolve(void 0);
1340
+ };
1341
+ req.onerror = () => reject(req.error);
1342
+ });
1343
+ }
1344
+ async function idbPut(db, key, value) {
1345
+ return new Promise((resolve, reject) => {
1346
+ const tx = db.transaction(STORE_NAME, "readwrite");
1347
+ const store = tx.objectStore(STORE_NAME);
1348
+ const req = store.put(value, key);
1349
+ req.onsuccess = () => resolve();
1350
+ req.onerror = () => reject(req.error);
1351
+ });
1352
+ }
1353
+ var nodeCacheDeps = null;
1354
+ async function getNodeCacheDeps() {
1355
+ if (nodeCacheDeps) return nodeCacheDeps;
1356
+ try {
1357
+ const fs = await import('fs/promises');
1358
+ const os = await import('os');
1359
+ const path = await import('path');
1360
+ nodeCacheDeps = {
1361
+ readFile: (p) => fs.readFile(p),
1362
+ writeFile: (p, d) => fs.writeFile(p, d),
1363
+ mkdir: (p, o) => fs.mkdir(p, o),
1364
+ stat: (p) => fs.stat(p),
1365
+ tmpdir: () => os.tmpdir(),
1366
+ homedir: () => os.homedir()
1367
+ };
1368
+ return nodeCacheDeps;
1369
+ } catch {
1370
+ return null;
1371
+ }
1372
+ }
1373
+ async function nodeCachePath(deps, cid) {
1374
+ const path = await import('path');
1375
+ const base = process.env.HOLOMAP_WEIGHT_CACHE_DIR ?? path.join(deps.homedir(), ".cache", "holoscript", "holomap-weights");
1376
+ return path.join(base, `${cid}.bin`);
1377
+ }
1378
+ async function nodeCacheGet(deps, cid) {
1379
+ const p = await nodeCachePath(deps, cid);
1380
+ try {
1381
+ const buf = await deps.readFile(p);
1382
+ const copy = new ArrayBuffer(buf.byteLength);
1383
+ new Uint8Array(copy).set(buf);
1384
+ return copy;
1385
+ } catch {
1386
+ return void 0;
1387
+ }
1388
+ }
1389
+ async function nodeCachePut(deps, cid, value) {
1390
+ const p = await nodeCachePath(deps, cid);
1391
+ const path = await import('path');
1392
+ const dir = path.dirname(p);
1393
+ await deps.mkdir(dir, { recursive: true });
1394
+ await deps.writeFile(p, new Uint8Array(value));
1395
+ }
1396
+ function isIndexedDBAvailable() {
1397
+ return typeof indexedDB !== "undefined";
1398
+ }
1399
+ async function getCachedWeightBlob(weightCid) {
1400
+ const cid = normalizeWeightCidDigest(weightCid);
1401
+ if (isIndexedDBAvailable()) {
1402
+ try {
1403
+ const db = await openIndexedDB();
1404
+ const cached = await idbGet(db, cid);
1405
+ db.close();
1406
+ return cached;
1407
+ } catch {
1408
+ }
1409
+ }
1410
+ const deps = await getNodeCacheDeps();
1411
+ if (deps) {
1412
+ return nodeCacheGet(deps, cid);
1413
+ }
1414
+ return void 0;
1415
+ }
1416
+ async function putCachedWeightBlob(weightCid, value) {
1417
+ const cid = normalizeWeightCidDigest(weightCid);
1418
+ if (isIndexedDBAvailable()) {
1419
+ try {
1420
+ const db = await openIndexedDB();
1421
+ await idbPut(db, cid, value);
1422
+ db.close();
1423
+ return;
1424
+ } catch {
1425
+ }
1426
+ }
1427
+ const deps = await getNodeCacheDeps();
1428
+ if (deps) {
1429
+ await nodeCachePut(deps, cid, value);
1430
+ }
1431
+ }
1432
+
1433
+ // src/reconstruction/HoloMapRuntime.ts
1434
+ var HOLOMAP_DEFAULTS = {
1435
+ inputResolution: { width: 518, height: 378 },
1436
+ targetFPS: 15,
1437
+ maxSequenceLength: 1e4,
1438
+ seed: 0,
1439
+ modelHash: "unset",
1440
+ tileGrid: 4,
1441
+ cpuOffload: false,
1442
+ weightStrategy: "distill",
1443
+ allowCpuFallback: true
1444
+ };
1445
+ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
1446
+ constructor() {
1447
+ this.config = { ...HOLOMAP_DEFAULTS };
1448
+ this.initialized = false;
1449
+ this.steps = [];
1450
+ this.replayKey = "unset";
1451
+ this.runId = createHoloMapRunId();
1452
+ this.encoderDevice = null;
1453
+ this.microEncoder = null;
1454
+ /** Loaded weight blob (optional; GPU upload wiring follows R3+). */
1455
+ this.weightBytes = null;
1456
+ // ── Sprint-3 performance / determinism state ──
1457
+ /** Running bounding box (updated incrementally per step). */
1458
+ this.boundsMin = [0, 0, 0];
1459
+ this.boundsMax = [0, 0, 0];
1460
+ this.boundsValid = false;
1461
+ /** Running total point count (avoids O(n) re-scan in finalize). */
1462
+ this.totalPointCount = 0;
1463
+ // ── Scan-derived trajectory / drift state ──
1464
+ // Anchor pose, trajectory keyframes, and drift are derived from the scan
1465
+ // geometry rather than emitted as constants. Drift is an uncertainty-weighted
1466
+ // inter-frame pose delta accumulated over the capture.
1467
+ /** Accumulated drift estimate (meters) over the capture. */
1468
+ this.cumulativeDriftMeters = 0;
1469
+ /**
1470
+ * Running sum of every observed point position + confidence (eviction-adjusted).
1471
+ * The anchor pose is the centroid (sum/count) of all observed points and the
1472
+ * descriptor carries the global mean confidence, so a single tampered point
1473
+ * moves the anchor — bounds center/extent alone are insensitive to a one-point
1474
+ * nudge that does not touch an extremum. (E4 acceptance bar.)
1475
+ */
1476
+ this.globalPosSum = [0, 0, 0];
1477
+ this.globalConfSum = 0;
1478
+ /** Previous frame camera pose (centroid of that frame's points). */
1479
+ this.prevPose = null;
1480
+ /** Previous full camera pose, including scan-derived principal-axis rotation. */
1481
+ this.prevCameraPose = null;
1482
+ /** Previous inter-frame velocity, for constant-velocity drift prediction. */
1483
+ this.prevVelocity = null;
1484
+ /** Frame index of the last detected loop closure (−1 if none). */
1485
+ this.lastLoopClosureFrameIdx = -1;
1486
+ /** Compact camera-pose history for loop-closure (revisit) detection. */
1487
+ this.poseHistory = [];
1488
+ /** Compact trajectory snapshot emitted with every accepted step. */
1489
+ this.trajectoryKeyframes = [];
1490
+ /** Last accepted capture timestamp (ms) for deterministic FPS throttling. */
1491
+ this.lastAcceptedFrameTimestampMs = null;
1492
+ /** Deterministic session start timestamp. */
1493
+ this.sessionStartMs = 0;
1494
+ /** Performance metrics accumulated across steps. */
1495
+ this.perfMetrics = { stepCount: 0, throttledCount: 0, totalStepMs: 0, maxStepMs: 0, minStepMs: Infinity };
1496
+ }
1497
+ /**
1498
+ * Nearest-neighbour sample of a row-major depth map at a normalized UV.
1499
+ * Used to read MEASURED sensor depth at a tile centre (frame.depth). Nearest
1500
+ * (not bilinear) keeps it deterministic and cheap; tiles are small.
1501
+ */
1502
+ static sampleDepthNearestUv(depth, width, height, uv) {
1503
+ const px = Math.min(width - 1, Math.max(0, Math.round(uv[0] * (width - 1))));
1504
+ const py = Math.min(height - 1, Math.max(0, Math.round(uv[1] * (height - 1))));
1505
+ const v = depth[py * width + px];
1506
+ return Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0.5;
1507
+ }
1508
+ static computeBounds(steps) {
1509
+ if (steps.length === 0) {
1510
+ return {
1511
+ min: [0, 0, 0],
1512
+ max: [0, 0, 0]
1513
+ };
1514
+ }
1515
+ let minX = Number.POSITIVE_INFINITY;
1516
+ let minY = Number.POSITIVE_INFINITY;
1517
+ let minZ = Number.POSITIVE_INFINITY;
1518
+ let maxX = Number.NEGATIVE_INFINITY;
1519
+ let maxY = Number.NEGATIVE_INFINITY;
1520
+ let maxZ = Number.NEGATIVE_INFINITY;
1521
+ for (const step of steps) {
1522
+ const { positions } = step.points;
1523
+ for (let i = 0; i < positions.length; i += 3) {
1524
+ const x = positions[i] ?? 0;
1525
+ const y = positions[i + 1] ?? 0;
1526
+ const z = positions[i + 2] ?? 0;
1527
+ if (x < minX) minX = x;
1528
+ if (y < minY) minY = y;
1529
+ if (z < minZ) minZ = z;
1530
+ if (x > maxX) maxX = x;
1531
+ if (y > maxY) maxY = y;
1532
+ if (z > maxZ) maxZ = z;
1533
+ }
1534
+ }
1535
+ if (!Number.isFinite(minX)) {
1536
+ return {
1537
+ min: [0, 0, 0],
1538
+ max: [0, 0, 0]
1539
+ };
1540
+ }
1541
+ return {
1542
+ min: [minX, minY, minZ],
1543
+ max: [maxX, maxY, maxZ]
1544
+ };
1545
+ }
1546
+ /** Update running bounds with new point positions. */
1547
+ updateBounds(positions) {
1548
+ if (positions.length === 0) return;
1549
+ if (!this.boundsValid) {
1550
+ this.boundsMin = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY];
1551
+ this.boundsMax = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY];
1552
+ }
1553
+ for (let i = 0; i < positions.length; i += 3) {
1554
+ const x = positions[i] ?? 0;
1555
+ const y = positions[i + 1] ?? 0;
1556
+ const z = positions[i + 2] ?? 0;
1557
+ if (x < this.boundsMin[0]) this.boundsMin[0] = x;
1558
+ if (y < this.boundsMin[1]) this.boundsMin[1] = y;
1559
+ if (z < this.boundsMin[2]) this.boundsMin[2] = z;
1560
+ if (x > this.boundsMax[0]) this.boundsMax[0] = x;
1561
+ if (y > this.boundsMax[1]) this.boundsMax[1] = y;
1562
+ if (z > this.boundsMax[2]) this.boundsMax[2] = z;
1563
+ }
1564
+ this.boundsValid = true;
1565
+ }
1566
+ /** Get current bounds (running or computed). */
1567
+ getBounds() {
1568
+ if (this.boundsValid) {
1569
+ return { min: [...this.boundsMin], max: [...this.boundsMax] };
1570
+ }
1571
+ return _HoloMapRuntimeImpl.computeBounds(this.steps);
1572
+ }
1573
+ static estimatePrincipalAxisRotation(chunks, centroid) {
1574
+ let xx = 0;
1575
+ let zz = 0;
1576
+ let xz = 0;
1577
+ let n = 0;
1578
+ for (const positions of chunks) {
1579
+ for (let i = 0; i < positions.length; i += 3) {
1580
+ const dx = (positions[i] ?? 0) - centroid[0];
1581
+ const dz = (positions[i + 2] ?? 0) - centroid[2];
1582
+ xx += dx * dx;
1583
+ zz += dz * dz;
1584
+ xz += dx * dz;
1585
+ n += 1;
1586
+ }
1587
+ }
1588
+ if (n < 2 || xx + zz < 1e-12) return [0, 0, 0, 1];
1589
+ const yaw = 0.5 * Math.atan2(2 * xz, xx - zz);
1590
+ return _HoloMapRuntimeImpl.yawToQuaternion(yaw);
1591
+ }
1592
+ static yawToQuaternion(yaw) {
1593
+ const half = yaw / 2;
1594
+ return [0, Math.sin(half), 0, Math.cos(half)];
1595
+ }
1596
+ static poseDistanceMeters(a, b) {
1597
+ const dx = a.position[0] - b.position[0];
1598
+ const dy = a.position[1] - b.position[1];
1599
+ const dz = a.position[2] - b.position[2];
1600
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
1601
+ }
1602
+ static poseRotationDeltaRadians(a, b) {
1603
+ const dot = Math.abs(
1604
+ a.rotation[0] * b.rotation[0] + a.rotation[1] * b.rotation[1] + a.rotation[2] * b.rotation[2] + a.rotation[3] * b.rotation[3]
1605
+ );
1606
+ const clamped = Math.max(-1, Math.min(1, dot));
1607
+ return 2 * Math.acos(clamped);
1608
+ }
1609
+ static buildTrajectoryEmbedding(pose, anchorDescriptor, interFrameDeltaMeters, interFrameRotationRadians) {
1610
+ return new Float32Array([
1611
+ pose.position[0],
1612
+ pose.position[1],
1613
+ pose.position[2],
1614
+ pose.rotation[0],
1615
+ pose.rotation[1],
1616
+ pose.rotation[2],
1617
+ pose.rotation[3],
1618
+ pose.confidence,
1619
+ interFrameDeltaMeters,
1620
+ interFrameRotationRadians,
1621
+ anchorDescriptor[0] ?? 0,
1622
+ anchorDescriptor[1] ?? 0,
1623
+ anchorDescriptor[2] ?? 0,
1624
+ anchorDescriptor[3] ?? 0
1625
+ ]);
1626
+ }
1627
+ static cloneTrajectoryKeyframes(keyframes) {
1628
+ return keyframes.map((keyframe) => ({
1629
+ frameIndex: keyframe.frameIndex,
1630
+ timestampMs: keyframe.timestampMs,
1631
+ pose: {
1632
+ position: [...keyframe.pose.position],
1633
+ rotation: [...keyframe.pose.rotation],
1634
+ confidence: keyframe.pose.confidence
1635
+ },
1636
+ embedding: new Float32Array(keyframe.embedding)
1637
+ }));
1638
+ }
1639
+ pushTrajectoryKeyframe(keyframe) {
1640
+ this.trajectoryKeyframes.push(keyframe);
1641
+ if (this.trajectoryKeyframes.length > _HoloMapRuntimeImpl.TRAJECTORY_KEYFRAME_CAP) {
1642
+ this.trajectoryKeyframes.shift();
1643
+ }
1644
+ }
1645
+ async init(config) {
1646
+ this.config = { ...config };
1647
+ this.config.tileGrid = _HoloMapRuntimeImpl.normalizeTileGrid(this.config.tileGrid);
1648
+ const allowCpu = this.config.allowCpuFallback !== false;
1649
+ if (!allowCpu && !isWebGpuEnvironmentPresent()) {
1650
+ const err = "HoloMap: native reconstruction requires WebGPU (allowCpuFallback=false). Run in a WebGPU-capable browser, or set allowCpuFallback=true for CPU fallback, or use compatibility scene ingest (Marble) for headless benchmarks.";
1651
+ logHoloMapEvent(this.runId, "error", { message: err });
1652
+ throw new Error(err);
1653
+ }
1654
+ this.steps.length = 0;
1655
+ this.totalPointCount = 0;
1656
+ this.boundsValid = false;
1657
+ this.boundsMin = [0, 0, 0];
1658
+ this.boundsMax = [0, 0, 0];
1659
+ this.cumulativeDriftMeters = 0;
1660
+ this.prevPose = null;
1661
+ this.prevCameraPose = null;
1662
+ this.prevVelocity = null;
1663
+ this.lastLoopClosureFrameIdx = -1;
1664
+ this.poseHistory.length = 0;
1665
+ this.trajectoryKeyframes.length = 0;
1666
+ this.globalPosSum = [0, 0, 0];
1667
+ this.globalConfSum = 0;
1668
+ this.lastAcceptedFrameTimestampMs = null;
1669
+ this.sessionStartMs = performance.now();
1670
+ this.perfMetrics = { stepCount: 0, throttledCount: 0, totalStepMs: 0, maxStepMs: 0, minStepMs: Infinity };
1671
+ this.replayKey = computeHoloMapReplayFingerprint({
1672
+ modelHash: this.config.modelHash,
1673
+ seed: this.config.seed,
1674
+ weightStrategy: this.config.weightStrategy ?? "distill",
1675
+ videoHash: this.config.videoHash,
1676
+ tileGrid: this.config.tileGrid,
1677
+ weightCid: this.config.weightCid,
1678
+ verticalProfile: this.config.verticalProfile
1679
+ });
1680
+ this.weightBytes = null;
1681
+ if (this.config.weightUrl) {
1682
+ const result = await loadHoloMapWeightBlob({
1683
+ weightUrl: this.config.weightUrl,
1684
+ weightUrls: this.config.weightUrls,
1685
+ weightCid: this.config.weightCid
1686
+ });
1687
+ this.weightBytes = result.bytes;
1688
+ }
1689
+ this.encoderDevice = await tryCreateHoloMapEncoderDevice();
1690
+ this.microEncoder = this.encoderDevice ? createHoloMapMicroEncoder(this.encoderDevice) : null;
1691
+ this.initialized = true;
1692
+ logHoloMapEvent(this.runId, "init", {
1693
+ modelHash: this.config.modelHash,
1694
+ seed: this.config.seed,
1695
+ allowCpuFallback: allowCpu,
1696
+ webgpu: isWebGpuEnvironmentPresent(),
1697
+ microEncoder: this.microEncoder ? "webgpu" : "cpu",
1698
+ tileGrid: this.config.tileGrid,
1699
+ weightLoadedBytes: this.weightBytes?.byteLength ?? 0,
1700
+ replayFingerprint: this.replayKey
1701
+ });
1702
+ }
1703
+ static normalizeTileGrid(value) {
1704
+ const raw = value ?? _HoloMapRuntimeImpl.GRID_N;
1705
+ if (!Number.isFinite(raw)) {
1706
+ throw new Error(`HoloMapRuntime.init invalid tileGrid: ${String(value)}`);
1707
+ }
1708
+ const tileGrid = Math.floor(raw);
1709
+ if (tileGrid < 1 || tileGrid > _HoloMapRuntimeImpl.MAX_GRID_N) {
1710
+ throw new Error(
1711
+ `HoloMapRuntime.init tileGrid must be an integer from 1 to ${_HoloMapRuntimeImpl.MAX_GRID_N}`
1712
+ );
1713
+ }
1714
+ return tileGrid;
1715
+ }
1716
+ /**
1717
+ * Carve `frame` into gridN×gridN tiles. Each tile carries its own
1718
+ * (rgb, width, height, stride, index) and the mean RGB color over its
1719
+ * pixels (used to color the corresponding output point).
1720
+ *
1721
+ * Tiles inherit `frame.index` shifted by tile id so micro-encoder
1722
+ * per-frame seeds remain deterministic and distinct across tiles.
1723
+ */
1724
+ static tileFrame(frame, gridN) {
1725
+ const out = [];
1726
+ const tileW = Math.max(1, Math.floor(frame.width / gridN));
1727
+ const tileH = Math.max(1, Math.floor(frame.height / gridN));
1728
+ const stride = frame.stride;
1729
+ for (let ty = 0; ty < gridN; ty += 1) {
1730
+ for (let tx = 0; tx < gridN; tx += 1) {
1731
+ const x0 = tx * tileW;
1732
+ const y0 = ty * tileH;
1733
+ const x1 = tx === gridN - 1 ? frame.width : x0 + tileW;
1734
+ const y1 = ty === gridN - 1 ? frame.height : y0 + tileH;
1735
+ const w = x1 - x0;
1736
+ const h = y1 - y0;
1737
+ const tileBytes = new Uint8Array(w * h * stride);
1738
+ let rSum = 0;
1739
+ let gSum = 0;
1740
+ let bSum = 0;
1741
+ let lumaSum = 0;
1742
+ let lumaSqSum = 0;
1743
+ let count = 0;
1744
+ for (let y = 0; y < h; y += 1) {
1745
+ const srcRow = (y0 + y) * frame.width * stride;
1746
+ const dstRow = y * w * stride;
1747
+ for (let x = 0; x < w; x += 1) {
1748
+ const sIdx = srcRow + (x0 + x) * stride;
1749
+ const dIdx = dstRow + x * stride;
1750
+ const r = frame.rgb[sIdx] ?? 0;
1751
+ const g = frame.rgb[sIdx + 1] ?? 0;
1752
+ const b = frame.rgb[sIdx + 2] ?? 0;
1753
+ tileBytes[dIdx] = r;
1754
+ tileBytes[dIdx + 1] = g;
1755
+ tileBytes[dIdx + 2] = b;
1756
+ if (stride === 4) tileBytes[dIdx + 3] = frame.rgb[sIdx + 3] ?? 255;
1757
+ rSum += r;
1758
+ gSum += g;
1759
+ bSum += b;
1760
+ const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
1761
+ lumaSum += luma;
1762
+ lumaSqSum += luma * luma;
1763
+ count += 1;
1764
+ }
1765
+ }
1766
+ const denom = Math.max(1, count);
1767
+ const meanLuma = lumaSum / denom;
1768
+ const lumaVariance = Math.max(0, lumaSqSum / denom - meanLuma * meanLuma);
1769
+ const tileId = ty * gridN + tx;
1770
+ out.push({
1771
+ tile: {
1772
+ // Encode (frameIndex, tileId) into the per-tile micro index so
1773
+ // each tile gets a distinct deterministic micro-encoder seed.
1774
+ index: frame.index * gridN * gridN + tileId,
1775
+ rgb: tileBytes,
1776
+ width: w,
1777
+ height: h,
1778
+ stride
1779
+ },
1780
+ centerUv: [
1781
+ (x0 + x1) * 0.5 / Math.max(1, frame.width),
1782
+ (y0 + y1) * 0.5 / Math.max(1, frame.height)
1783
+ ],
1784
+ luminance: meanLuma / 255,
1785
+ texture: Math.sqrt(lumaVariance) / 255,
1786
+ meanColor: [
1787
+ Math.round(rSum / denom),
1788
+ Math.round(gSum / denom),
1789
+ Math.round(bSum / denom)
1790
+ ]
1791
+ });
1792
+ }
1793
+ }
1794
+ return out;
1795
+ }
1796
+ async encodeTile(tile, microCfg) {
1797
+ if (!this.microEncoder) {
1798
+ return runHoloMapMicroEncoderCpu(tile, microCfg);
1799
+ }
1800
+ try {
1801
+ return await this.microEncoder.run(tile, microCfg);
1802
+ } catch (error) {
1803
+ const message = error instanceof Error ? error.message : String(error);
1804
+ if (this.config.allowCpuFallback === false) {
1805
+ throw new Error(`HoloMap WebGPU micro-encoder failed: ${message}`);
1806
+ }
1807
+ logHoloMapEvent(this.runId, "micro_encoder_fallback", {
1808
+ frameIndex: tile.index,
1809
+ reason: message
1810
+ });
1811
+ this.microEncoder = null;
1812
+ return runHoloMapMicroEncoderCpu(tile, microCfg);
1813
+ }
1814
+ }
1815
+ async step(frame) {
1816
+ if (!this.initialized) {
1817
+ throw new Error("HoloMapRuntime not initialized. Call init(config) before step(frame).");
1818
+ }
1819
+ if (!Number.isFinite(frame.timestampMs)) {
1820
+ throw new Error(`HoloMapRuntime.step invalid frame timestamp: ${frame.timestampMs}`);
1821
+ }
1822
+ const expectedBytes = frame.width * frame.height * frame.stride;
1823
+ if (frame.rgb.byteLength !== expectedBytes) {
1824
+ throw new Error(
1825
+ `HoloMapRuntime.step invalid frame byte length: got ${frame.rgb.byteLength}, expected ${expectedBytes} (w=${frame.width}, h=${frame.height}, stride=${frame.stride})`
1826
+ );
1827
+ }
1828
+ if (frame.depth && frame.depth.length !== frame.width * frame.height) {
1829
+ throw new Error(
1830
+ `HoloMapRuntime.step invalid measured depth length: got ${frame.depth.length}, expected ${frame.width * frame.height} (w=${frame.width}, h=${frame.height})`
1831
+ );
1832
+ }
1833
+ if (frame.depthConfidence && frame.depthConfidence.length !== frame.width * frame.height) {
1834
+ throw new Error(
1835
+ `HoloMapRuntime.step invalid measured depth confidence length: got ${frame.depthConfidence.length}, expected ${frame.width * frame.height} (w=${frame.width}, h=${frame.height})`
1836
+ );
1837
+ }
1838
+ const minIntervalMs = 1e3 / Math.max(1, this.config.targetFPS);
1839
+ if (this.lastAcceptedFrameTimestampMs !== null && frame.timestampMs - this.lastAcceptedFrameTimestampMs < minIntervalMs) {
1840
+ this.perfMetrics.throttledCount += 1;
1841
+ logHoloMapEvent(this.runId, "step_throttled", {
1842
+ frameIndex: frame.index,
1843
+ elapsedFrameMs: Math.round(frame.timestampMs - this.lastAcceptedFrameTimestampMs),
1844
+ targetIntervalMs: Math.round(minIntervalMs)
1845
+ });
1846
+ return null;
1847
+ }
1848
+ const stepStartMs = performance.now();
1849
+ this.lastAcceptedFrameTimestampMs = frame.timestampMs;
1850
+ if (this.steps.length >= this.config.maxSequenceLength) {
1851
+ const evicted = this.steps.shift();
1852
+ const evictedPoints = evicted.points.positions.length / 3;
1853
+ this.totalPointCount -= evictedPoints;
1854
+ const ep = evicted.points.positions;
1855
+ for (let i = 0; i < ep.length; i += 3) {
1856
+ this.globalPosSum[0] -= ep[i];
1857
+ this.globalPosSum[1] -= ep[i + 1];
1858
+ this.globalPosSum[2] -= ep[i + 2];
1859
+ }
1860
+ const ec = evicted.points.confidence;
1861
+ for (let i = 0; i < ec.length; i += 1) this.globalConfSum -= ec[i];
1862
+ this.boundsValid = false;
1863
+ for (const s of this.steps) {
1864
+ this.updateBounds(s.points.positions);
1865
+ }
1866
+ logHoloMapEvent(this.runId, "step_evict", {
1867
+ frameIndex: evicted.frame.index,
1868
+ evictedPoints,
1869
+ retainedSteps: this.steps.length
1870
+ });
1871
+ }
1872
+ const microCfg = { seed: this.config.seed, modelHash: this.config.modelHash };
1873
+ const configuredGrid = this.config.tileGrid ?? _HoloMapRuntimeImpl.GRID_N;
1874
+ const gridN = Math.max(1, Math.min(configuredGrid, frame.width, frame.height));
1875
+ const tiles = _HoloMapRuntimeImpl.tileFrame(frame, gridN);
1876
+ const numPoints = tiles.length;
1877
+ const positions = new Float32Array(numPoints * 3);
1878
+ const colors = new Uint8Array(numPoints * 3);
1879
+ const confidence = new Float32Array(numPoints);
1880
+ const aspect = frame.width / Math.max(1, frame.height);
1881
+ let centroidX = 0;
1882
+ let centroidY = 0;
1883
+ let centroidZ = 0;
1884
+ let confidenceSum = 0;
1885
+ for (let t = 0; t < numPoints; t += 1) {
1886
+ const { tile, meanColor, centerUv, luminance, texture } = tiles[t];
1887
+ const xyz = await this.encodeTile(tile, microCfg);
1888
+ const latentX = xyz[0] ?? 0;
1889
+ const latentY = xyz[1] ?? 0;
1890
+ const latentZ = xyz[2] ?? 0;
1891
+ const planeX = (centerUv[0] - 0.5) * 2 * aspect;
1892
+ const planeY = (0.5 - centerUv[1]) * 2;
1893
+ const px = planeX + latentX * 0.08;
1894
+ const py = planeY + latentY * 0.08;
1895
+ let pz;
1896
+ if (frame.depth) {
1897
+ const measured = _HoloMapRuntimeImpl.sampleDepthNearestUv(
1898
+ frame.depth,
1899
+ frame.width,
1900
+ frame.height,
1901
+ centerUv
1902
+ );
1903
+ pz = (0.5 - measured) * _HoloMapRuntimeImpl.DEPTH_Z_SCALE;
1904
+ } else {
1905
+ const depthHint = (luminance - 0.5) * 0.34 + texture * 0.24;
1906
+ pz = depthHint + latentZ * 0.12;
1907
+ }
1908
+ positions[t * 3] = px;
1909
+ positions[t * 3 + 1] = py;
1910
+ positions[t * 3 + 2] = pz;
1911
+ colors[t * 3] = meanColor[0];
1912
+ colors[t * 3 + 1] = meanColor[1];
1913
+ colors[t * 3 + 2] = meanColor[2];
1914
+ const latentMag = Math.sqrt(latentX * latentX + latentY * latentY + latentZ * latentZ);
1915
+ const baseConfidence = Math.min(
1916
+ 1,
1917
+ 0.45 + 0.35 / (1 + latentMag) + Math.min(0.2, texture * 1.5)
1918
+ );
1919
+ if (frame.depthConfidence) {
1920
+ const sensorConfidence = _HoloMapRuntimeImpl.sampleDepthNearestUv(
1921
+ frame.depthConfidence,
1922
+ frame.width,
1923
+ frame.height,
1924
+ centerUv
1925
+ );
1926
+ confidence[t] = baseConfidence * sensorConfidence;
1927
+ } else {
1928
+ confidence[t] = baseConfidence;
1929
+ }
1930
+ confidenceSum += confidence[t];
1931
+ centroidX += px;
1932
+ centroidY += py;
1933
+ centroidZ += pz;
1934
+ }
1935
+ const inv = 1 / Math.max(1, numPoints);
1936
+ const poseX = centroidX * inv;
1937
+ const poseY = centroidY * inv;
1938
+ const poseZ = centroidZ * inv;
1939
+ const meanConfidence = confidenceSum * inv;
1940
+ const derivedPosition = [poseX, poseY, poseZ];
1941
+ const poseConfidence = Math.max(0, Math.min(1, meanConfidence));
1942
+ const cameraPose = frame.devicePose ? {
1943
+ position: [...frame.devicePose.position],
1944
+ rotation: [...frame.devicePose.rotation],
1945
+ confidence: frame.devicePose.confidence
1946
+ } : {
1947
+ position: derivedPosition,
1948
+ rotation: _HoloMapRuntimeImpl.estimatePrincipalAxisRotation([positions], derivedPosition),
1949
+ confidence: poseConfidence
1950
+ };
1951
+ const curPose = [
1952
+ cameraPose.position[0],
1953
+ cameraPose.position[1],
1954
+ cameraPose.position[2]
1955
+ ];
1956
+ let interFrameDeltaMeters = 0;
1957
+ let interFrameRotationRadians = 0;
1958
+ if (this.prevCameraPose) {
1959
+ interFrameDeltaMeters = _HoloMapRuntimeImpl.poseDistanceMeters(cameraPose, this.prevCameraPose);
1960
+ interFrameRotationRadians = _HoloMapRuntimeImpl.poseRotationDeltaRadians(
1961
+ cameraPose,
1962
+ this.prevCameraPose
1963
+ );
1964
+ const uncertaintyWeight = Math.max(
1965
+ 0.05,
1966
+ 1 - Math.min(cameraPose.confidence, this.prevCameraPose.confidence)
1967
+ );
1968
+ this.cumulativeDriftMeters += interFrameDeltaMeters * uncertaintyWeight + interFrameRotationRadians * 0.05;
1969
+ }
1970
+ if (this.prevPose) {
1971
+ const velocity = [
1972
+ curPose[0] - this.prevPose[0],
1973
+ curPose[1] - this.prevPose[1],
1974
+ curPose[2] - this.prevPose[2]
1975
+ ];
1976
+ if (this.prevVelocity) {
1977
+ const predX = this.prevPose[0] + this.prevVelocity[0];
1978
+ const predY = this.prevPose[1] + this.prevVelocity[1];
1979
+ const predZ = this.prevPose[2] + this.prevVelocity[2];
1980
+ const rx = curPose[0] - predX;
1981
+ const ry = curPose[1] - predY;
1982
+ const rz = curPose[2] - predZ;
1983
+ this.cumulativeDriftMeters += Math.sqrt(rx * rx + ry * ry + rz * rz);
1984
+ }
1985
+ this.prevVelocity = velocity;
1986
+ }
1987
+ this.prevPose = curPose;
1988
+ this.prevCameraPose = cameraPose;
1989
+ for (const kf of this.poseHistory) {
1990
+ if (frame.index - kf.frameIndex < _HoloMapRuntimeImpl.LOOP_CLOSURE_MIN_GAP) continue;
1991
+ const dx = curPose[0] - kf.position[0];
1992
+ const dy = curPose[1] - kf.position[1];
1993
+ const dz = curPose[2] - kf.position[2];
1994
+ if (Math.sqrt(dx * dx + dy * dy + dz * dz) < _HoloMapRuntimeImpl.LOOP_CLOSURE_RADIUS) {
1995
+ this.lastLoopClosureFrameIdx = frame.index;
1996
+ break;
1997
+ }
1998
+ }
1999
+ this.poseHistory.push({ frameIndex: frame.index, position: curPose });
2000
+ if (this.poseHistory.length > _HoloMapRuntimeImpl.POSE_HISTORY_CAP) this.poseHistory.shift();
2001
+ this.updateBounds(positions);
2002
+ this.globalPosSum[0] += centroidX;
2003
+ this.globalPosSum[1] += centroidY;
2004
+ this.globalPosSum[2] += centroidZ;
2005
+ this.globalConfSum += confidenceSum;
2006
+ const totalPoints = this.totalPointCount + numPoints;
2007
+ const ginv = 1 / Math.max(1, totalPoints);
2008
+ const anchorCenter = [
2009
+ this.globalPosSum[0] * ginv,
2010
+ this.globalPosSum[1] * ginv,
2011
+ this.globalPosSum[2] * ginv
2012
+ ];
2013
+ const globalMeanConfidence = this.globalConfSum * ginv;
2014
+ const b = this.getBounds();
2015
+ const extentX = b.max[0] - b.min[0];
2016
+ const extentY = b.max[1] - b.min[1];
2017
+ const extentZ = b.max[2] - b.min[2];
2018
+ const anchorRotation = _HoloMapRuntimeImpl.estimatePrincipalAxisRotation(
2019
+ [...this.steps.map((s) => s.points.positions), positions],
2020
+ anchorCenter
2021
+ );
2022
+ const anchorDescriptor = new Float32Array([extentX, extentY, extentZ, globalMeanConfidence]);
2023
+ this.pushTrajectoryKeyframe({
2024
+ frameIndex: frame.index,
2025
+ timestampMs: frame.timestampMs,
2026
+ pose: cameraPose,
2027
+ embedding: _HoloMapRuntimeImpl.buildTrajectoryEmbedding(
2028
+ cameraPose,
2029
+ anchorDescriptor,
2030
+ interFrameDeltaMeters,
2031
+ interFrameRotationRadians
2032
+ )
2033
+ });
2034
+ const trajectoryKeyframes = _HoloMapRuntimeImpl.cloneTrajectoryKeyframes(this.trajectoryKeyframes);
2035
+ const step = {
2036
+ frame,
2037
+ pose: cameraPose,
2038
+ points: {
2039
+ positions,
2040
+ colors,
2041
+ confidence
2042
+ },
2043
+ trajectory: {
2044
+ keyframes: trajectoryKeyframes,
2045
+ estimatedDriftMeters: this.cumulativeDriftMeters,
2046
+ lastLoopClosureFrame: this.lastLoopClosureFrameIdx,
2047
+ revision: frame.index + 1
2048
+ },
2049
+ anchor: {
2050
+ anchorFrameIndex: trajectoryKeyframes[0]?.frameIndex ?? frame.index,
2051
+ anchorPose: {
2052
+ position: anchorCenter,
2053
+ rotation: anchorRotation,
2054
+ confidence: Math.max(0, Math.min(1, globalMeanConfidence))
2055
+ },
2056
+ anchorDescriptor,
2057
+ revision: frame.index + 1
2058
+ }
2059
+ };
2060
+ this.steps.push(step);
2061
+ this.totalPointCount += numPoints;
2062
+ const stepMs = performance.now() - stepStartMs;
2063
+ this.perfMetrics.stepCount += 1;
2064
+ this.perfMetrics.totalStepMs += stepMs;
2065
+ if (stepMs > this.perfMetrics.maxStepMs) this.perfMetrics.maxStepMs = stepMs;
2066
+ if (stepMs < this.perfMetrics.minStepMs) this.perfMetrics.minStepMs = stepMs;
2067
+ logHoloMapEvent(this.runId, "step", {
2068
+ frameIndex: frame.index,
2069
+ pointCount: numPoints,
2070
+ stepMs: Math.round(stepMs),
2071
+ avgStepMs: Math.round(this.perfMetrics.totalStepMs / this.perfMetrics.stepCount),
2072
+ throttledSoFar: this.perfMetrics.throttledCount
2073
+ });
2074
+ return step;
2075
+ }
2076
+ async finalize() {
2077
+ if (!this.initialized) {
2078
+ throw new Error("HoloMapRuntime not initialized. Call init(config) before finalize().");
2079
+ }
2080
+ const frameCount = this.steps.length;
2081
+ const pointCount = this.totalPointCount;
2082
+ const bounds = this.getBounds();
2083
+ const avgStepMs = this.perfMetrics.stepCount > 0 ? this.perfMetrics.totalStepMs / this.perfMetrics.stepCount : 0;
2084
+ logHoloMapEvent(this.runId, "finalize", {
2085
+ frameCount,
2086
+ pointCount,
2087
+ avgStepMs: Math.round(avgStepMs),
2088
+ maxStepMs: Math.round(this.perfMetrics.maxStepMs),
2089
+ minStepMs: Math.round(this.perfMetrics.minStepMs),
2090
+ throttledCount: this.perfMetrics.throttledCount,
2091
+ sessionDurationMs: Math.round(performance.now() - this.sessionStartMs)
2092
+ });
2093
+ return {
2094
+ version: "1.0.0",
2095
+ worldId: `holomap-${this.replayKey}`,
2096
+ displayName: "HoloMap Reconstruction",
2097
+ pointCount,
2098
+ frameCount,
2099
+ bounds,
2100
+ replayHash: this.replayKey,
2101
+ simulationContract: {
2102
+ kind: HOLOMAP_SIMULATION_CONTRACT_KIND,
2103
+ replayFingerprint: this.replayKey,
2104
+ holoScriptBuild: getVersionString()
2105
+ },
2106
+ provenance: {
2107
+ anchorHash: `self-attested:${this.replayKey}`,
2108
+ capturedAtIso: (/* @__PURE__ */ new Date()).toISOString()
2109
+ },
2110
+ assets: {
2111
+ points: "reconstruction.points.bin",
2112
+ trajectory: "reconstruction.trajectory.json",
2113
+ anchors: "reconstruction.anchors.json"
2114
+ },
2115
+ weightStrategy: this.config.weightStrategy ?? "distill"
2116
+ };
2117
+ }
2118
+ replayHash() {
2119
+ return this.replayKey;
2120
+ }
2121
+ async dispose() {
2122
+ logHoloMapEvent(this.runId, "dispose", {
2123
+ stepsRetained: this.steps.length,
2124
+ totalPointCount: this.totalPointCount,
2125
+ throttledCount: this.perfMetrics.throttledCount
2126
+ });
2127
+ this.initialized = false;
2128
+ this.steps.length = 0;
2129
+ this.microEncoder = null;
2130
+ this.encoderDevice = null;
2131
+ this.weightBytes = null;
2132
+ this.totalPointCount = 0;
2133
+ this.cumulativeDriftMeters = 0;
2134
+ this.prevPose = null;
2135
+ this.prevCameraPose = null;
2136
+ this.prevVelocity = null;
2137
+ this.lastLoopClosureFrameIdx = -1;
2138
+ this.poseHistory.length = 0;
2139
+ this.trajectoryKeyframes.length = 0;
2140
+ this.globalPosSum = [0, 0, 0];
2141
+ this.globalConfSum = 0;
2142
+ this.boundsValid = false;
2143
+ this.lastAcceptedFrameTimestampMs = null;
2144
+ this.perfMetrics = { stepCount: 0, throttledCount: 0, totalStepMs: 0, maxStepMs: 0, minStepMs: Infinity };
2145
+ }
2146
+ };
2147
+ _HoloMapRuntimeImpl.LOOP_CLOSURE_MIN_GAP = 3;
2148
+ _HoloMapRuntimeImpl.LOOP_CLOSURE_RADIUS = 0.05;
2149
+ _HoloMapRuntimeImpl.POSE_HISTORY_CAP = 512;
2150
+ _HoloMapRuntimeImpl.TRAJECTORY_KEYFRAME_CAP = 512;
2151
+ /** Maps normalized measured depth [0=near,1=far] to a point Z range matching
2152
+ * the monocular estimate's spread (±~0.17), so sensor and estimate paths
2153
+ * produce geometry in the same coordinate scale. */
2154
+ _HoloMapRuntimeImpl.DEPTH_Z_SCALE = 0.34;
2155
+ /**
2156
+ * Default number of tiles per axis used to fan the encoder across the frame.
2157
+ * Total points emitted = tileGrid * tileGrid (one per tile).
2158
+ * Each tile runs the full 8-kernel transformer chain via the micro encoder
2159
+ * (imagePatchEmbed → layerNorm → gemm Q/K/V → rope → fusedMHA →
2160
+ * layerNorm → gelu → gemm xyz). pagedKV append/lookup remains available
2161
+ * for future streaming kLen>1 paths.
2162
+ */
2163
+ _HoloMapRuntimeImpl.GRID_N = 4;
2164
+ _HoloMapRuntimeImpl.MAX_GRID_N = 32;
2165
+ var HoloMapRuntimeImpl = _HoloMapRuntimeImpl;
2166
+ function createHoloMapRuntime(_config) {
2167
+ const runtime = new HoloMapRuntimeImpl();
2168
+ if (_config) {
2169
+ void runtime.init({ ...HOLOMAP_DEFAULTS, ..._config });
2170
+ }
2171
+ return runtime;
2172
+ }
2173
+
2174
+ export { BUILD_TIMESTAMP, GIT_COMMIT_SHA, HOLOMAP_DEFAULTS, HOLOSCRIPT_VERSION, assertWeightCid, createHoloMapMicroEncoder, createHoloMapRunId, createHoloMapRuntime, frameToMicroImage, getCachedWeightBlob, getVersionInfo, getVersionString, loadHoloMapWeightBlob, logHoloMapEvent, normalizeWeightCidDigest, putCachedWeightBlob, resetHoloMapTelemetryForTests, runHoloMapMicroEncoderCpu, tryCreateHoloMapEncoderDevice };
2175
+ //# sourceMappingURL=chunk-CSD27UNQ.js.map
2176
+ //# sourceMappingURL=chunk-CSD27UNQ.js.map