@holoscript/core 8.0.14 → 8.0.16

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 (187) hide show
  1. package/README.md +6 -13
  2. package/bin/holo-decision.mjs +0 -0
  3. package/bin/holoscript.cjs +0 -0
  4. package/dist/{GLTFPipeline-6LS3UDYX.js → GLTFPipeline-FRBZIATS.js} +6 -6
  5. package/dist/{GLTFPipeline-VTI6PRRQ.cjs → GLTFPipeline-MTPP444P.cjs} +10 -10
  6. package/dist/{HoloCompositionParser-GILABNN3.js → HoloCompositionParser-2GVQAZ2X.js} +7 -7
  7. package/dist/HoloCompositionParser-3NDF6BMX.cjs +45 -0
  8. package/dist/HoloScriptPlusParser-URSIBEAW.cjs +30 -0
  9. package/dist/HoloScriptPlusParser-YCV6L7GI.js +13 -0
  10. package/dist/{HoloScriptRuntime-YBYXJHJN.cjs → HoloScriptRuntime-CRWI6C3P.cjs} +5 -5
  11. package/dist/{HoloScriptRuntime-UTRLJI5L.js → HoloScriptRuntime-IAZGHPFP.js} +4 -4
  12. package/dist/{SceneIRCompiler-VX75RB6H.js → SceneIRCompiler-BJQMKLWL.js} +6 -6
  13. package/dist/{SceneIRCompiler-7KQ5NKAU.cjs → SceneIRCompiler-RTPPDQAV.cjs} +11 -11
  14. package/dist/{chunk-Y2OCKXVD.js → chunk-22AULOAL.js} +5 -5
  15. package/dist/{chunk-D44CEFY4.cjs → chunk-2DJIS7TH.cjs} +5 -5
  16. package/dist/{chunk-LHUDAJQT.cjs → chunk-2PVEXEOL.cjs} +5 -5
  17. package/dist/{chunk-M7XR3F53.js → chunk-2UP2J3OK.js} +4 -4
  18. package/dist/{chunk-3KHRZTNW.js → chunk-3AUYBQ5I.js} +3 -3
  19. package/dist/{chunk-UKEK2NZJ.js → chunk-3MQAOMKD.js} +3 -3
  20. package/dist/{chunk-FWKEY4LD.js → chunk-4AGNARX6.js} +3 -3
  21. package/dist/{chunk-7ITJP33Q.cjs → chunk-4CQ7ULXZ.cjs} +3 -3
  22. package/dist/{chunk-47XZ62KM.js → chunk-4UA5OBWR.js} +5 -5
  23. package/dist/{chunk-ORBVCOTC.js → chunk-5LCJDX5A.js} +3 -3
  24. package/dist/{chunk-IK6Z2HNC.js → chunk-6OQHLNTT.js} +2 -2
  25. package/dist/{chunk-EI3J6LTW.cjs → chunk-6RDHWSZM.cjs} +217 -217
  26. package/dist/{chunk-73EDVY5D.js → chunk-7AY66PZP.js} +3 -3
  27. package/dist/{chunk-6FCNMTL5.js → chunk-7HDRN5BW.js} +4 -4
  28. package/dist/{chunk-N7MIIDLI.cjs → chunk-7IBXTJP7.cjs} +8 -8
  29. package/dist/{chunk-R5VKLUMR.js → chunk-AAUQTR7I.js} +4 -4
  30. package/dist/{chunk-HCONR4J6.cjs → chunk-AN3ZH7M7.cjs} +38 -38
  31. package/dist/{chunk-YW3I2FQL.js → chunk-ANMME7XT.js} +4 -2
  32. package/dist/{chunk-W4AJMMQR.js → chunk-AUF7HFOO.js} +60 -22
  33. package/dist/{chunk-SKVBUGMC.cjs → chunk-AUMTQV64.cjs} +7 -7
  34. package/dist/{chunk-7ZKQ32XK.js → chunk-B5K3WHJ4.js} +4 -4
  35. package/dist/{chunk-QQFB4RT2.cjs → chunk-BTVHY3UB.cjs} +2 -2
  36. package/dist/{chunk-SE2Y5BSL.cjs → chunk-CMEINO5I.cjs} +186 -70
  37. package/dist/{chunk-YMI3ED3B.cjs → chunk-CRUIGXF7.cjs} +5 -5
  38. package/dist/chunk-DDBYJJZK.js +1571 -0
  39. package/dist/{chunk-2E6PP5UE.cjs → chunk-E2SNPMBO.cjs} +11 -11
  40. package/dist/{chunk-GI5YYZH6.js → chunk-E4YS5SIZ.js} +90 -25
  41. package/dist/{chunk-62OYZT6Q.js → chunk-EKNWUSGW.js} +3 -3
  42. package/dist/{chunk-RVN7OPCP.js → chunk-ETIJOUIO.js} +4 -4
  43. package/dist/chunk-EUNELE7Y.cjs +1605 -0
  44. package/dist/{chunk-C73GVAYC.js → chunk-F33KLYOS.js} +5 -5
  45. package/dist/{chunk-4CRMVEON.cjs → chunk-F47FEV4X.cjs} +40 -40
  46. package/dist/{chunk-FGXJPJ3W.cjs → chunk-FCGTI7FO.cjs} +4 -4
  47. package/dist/{chunk-OSKGCXAQ.cjs → chunk-FGTXAVKG.cjs} +5 -5
  48. package/dist/{chunk-XZC75AJ4.cjs → chunk-FPGYWNNA.cjs} +5 -5
  49. package/dist/{chunk-5B2AJKA5.cjs → chunk-FQ4MYZYU.cjs} +20 -20
  50. package/dist/{chunk-KOGPVAP6.js → chunk-FT4FGTR6.js} +3 -3
  51. package/dist/{chunk-N7IYRAFG.cjs → chunk-FVNV4WHQ.cjs} +14 -14
  52. package/dist/{chunk-SJIRCTIS.cjs → chunk-HSGJFCVJ.cjs} +2 -2
  53. package/dist/{chunk-TKDDIE3T.cjs → chunk-ILO2KMER.cjs} +4 -4
  54. package/dist/{chunk-GGH3ZYDB.cjs → chunk-IPBSNGAN.cjs} +92 -27
  55. package/dist/{chunk-JQCXLKAU.js → chunk-IX2U3RQT.js} +3 -3
  56. package/dist/{chunk-P65OIMJT.cjs → chunk-IYK6LMQ7.cjs} +7 -7
  57. package/dist/{chunk-NWKARJM2.js → chunk-J3VVM6GV.js} +4 -4
  58. package/dist/{chunk-FOM36K5I.cjs → chunk-J74S4ZMF.cjs} +18 -18
  59. package/dist/{chunk-36NPLOI4.cjs → chunk-JERWCCEJ.cjs} +4 -2
  60. package/dist/{chunk-VXHB6ZHY.js → chunk-JTDDLCSP.js} +4 -4
  61. package/dist/{chunk-GYJLP4BO.cjs → chunk-K53L47NH.cjs} +30 -30
  62. package/dist/{chunk-FHR2AVMA.cjs → chunk-LJTM5ONX.cjs} +23 -23
  63. package/dist/{chunk-JCVSCKUH.js → chunk-LJVVOVA6.js} +4 -4
  64. package/dist/{chunk-LQNL3IYV.cjs → chunk-LNJKBQ2B.cjs} +38 -38
  65. package/dist/{chunk-2GKF7JQ6.js → chunk-M2GDE3NV.js} +3 -3
  66. package/dist/{chunk-Z7NUL73U.js → chunk-MSZNGI5P.js} +3 -3
  67. package/dist/{chunk-B6JHTTMR.cjs → chunk-N2BOTBPG.cjs} +15 -15
  68. package/dist/{chunk-L4DBUX3N.cjs → chunk-NFALJJCH.cjs} +12 -12
  69. package/dist/{chunk-YBM34FUJ.cjs → chunk-NRZRBK25.cjs} +6 -6
  70. package/dist/{chunk-SNMQUWIO.cjs → chunk-NTSLNIY7.cjs} +10 -10
  71. package/dist/{chunk-645EWDZW.cjs → chunk-NZJOAWJM.cjs} +4 -4
  72. package/dist/{chunk-LGRKBTPM.js → chunk-OG7RKWWX.js} +3 -3
  73. package/dist/{chunk-OOP4S3GU.cjs → chunk-OQVHCYY4.cjs} +5 -5
  74. package/dist/{chunk-TMNAKNV2.cjs → chunk-PEBNX3VM.cjs} +5 -5
  75. package/dist/{chunk-5T6BWIB7.js → chunk-PFZ5WLIP.js} +4 -4
  76. package/dist/{chunk-CESMPUEZ.cjs → chunk-PHLNABJD.cjs} +159 -145
  77. package/dist/{chunk-CHIZSMWD.js → chunk-PLK3DLTF.js} +3 -3
  78. package/dist/{chunk-4NEPHK2K.js → chunk-PMACVMHL.js} +3 -3
  79. package/dist/{chunk-EYHPTJ3F.js → chunk-PV2OXNP5.js} +40 -26
  80. package/dist/{chunk-KLM6QD6D.cjs → chunk-Q6VW7MGX.cjs} +14 -14
  81. package/dist/{chunk-YDSNDCV3.js → chunk-QC3CAVFN.js} +186 -69
  82. package/dist/{chunk-REN3LHO6.cjs → chunk-QKLEDRHN.cjs} +19 -19
  83. package/dist/{chunk-2EGGAZT3.js → chunk-QS27OYBU.js} +3 -3
  84. package/dist/{chunk-4376VHNJ.js → chunk-RUB7WL6C.js} +3 -3
  85. package/dist/chunk-S2BSXG3B.js +3308 -0
  86. package/dist/{chunk-TZQKJOPC.js → chunk-TW2AH2ZS.js} +3 -3
  87. package/dist/{chunk-Q4J2K7FI.js → chunk-TZM6DKSJ.js} +6 -6
  88. package/dist/{chunk-ESSEF6U2.js → chunk-U227RWXB.js} +3 -3
  89. package/dist/{chunk-ERYKKPHW.cjs → chunk-U6CQOM5Q.cjs} +13 -13
  90. package/dist/{chunk-VMCR5QVI.js → chunk-ULT2QAV5.js} +4 -4
  91. package/dist/chunk-VQOTDNFL.cjs +3337 -0
  92. package/dist/{chunk-VZI7S7P6.cjs → chunk-VSO6HPDZ.cjs} +7 -7
  93. package/dist/{chunk-QLPRI3QW.js → chunk-WQJMSSJT.js} +4 -4
  94. package/dist/{chunk-GILYQ5VH.cjs → chunk-WXSDBWGM.cjs} +4 -4
  95. package/dist/{chunk-42Z3SVHQ.js → chunk-X5PMMK7H.js} +3 -3
  96. package/dist/{chunk-ML4V5PVU.cjs → chunk-X5ZLSPU2.cjs} +74 -32
  97. package/dist/{chunk-PSWCJQS4.js → chunk-X727CQ7X.js} +5 -5
  98. package/dist/{chunk-YM7UUYR5.cjs → chunk-XHZYS4QO.cjs} +5 -5
  99. package/dist/{chunk-PZ46WRHX.cjs → chunk-XRQCTTDC.cjs} +8 -8
  100. package/dist/{chunk-IXFNA5TT.js → chunk-YBIHSN55.js} +4 -4
  101. package/dist/{chunk-BLCWQ225.js → chunk-Z574GVI3.js} +4 -4
  102. package/dist/{chunk-ZVQMKUGJ.js → chunk-Z5V3KA7L.js} +2 -2
  103. package/dist/{chunk-VTFLKSJL.cjs → chunk-ZHW6TDZ6.cjs} +14 -14
  104. package/dist/{chunk-KYJD2UMV.js → chunk-ZPHCTPR6.js} +4 -4
  105. package/dist/{chunk-VA7THINF.cjs → chunk-ZXXC2GXB.cjs} +38 -38
  106. package/dist/cli/holoscript-runner.cjs +168 -19
  107. package/dist/cli/holoscript-runner.js +162 -13
  108. package/dist/compiler/agent-inference.cjs +7 -7
  109. package/dist/compiler/agent-inference.js +3 -3
  110. package/dist/compiler/android-xr.cjs +6 -6
  111. package/dist/compiler/android-xr.js +4 -4
  112. package/dist/compiler/android.cjs +5 -5
  113. package/dist/compiler/android.js +3 -3
  114. package/dist/compiler/context.cjs +5 -5
  115. package/dist/compiler/context.js +2 -2
  116. package/dist/compiler/daimon-seed.cjs +8 -8
  117. package/dist/compiler/daimon-seed.js +3 -3
  118. package/dist/compiler/domain-block-utils.cjs +164 -164
  119. package/dist/compiler/domain-block-utils.js +3 -3
  120. package/dist/compiler/dtdl.cjs +6 -6
  121. package/dist/compiler/dtdl.js +3 -3
  122. package/dist/compiler/gltf-pipeline.cjs +8 -8
  123. package/dist/compiler/gltf-pipeline.js +1 -1
  124. package/dist/compiler/godot.cjs +5 -5
  125. package/dist/compiler/godot.js +4 -4
  126. package/dist/compiler/incremental.cjs +7 -7
  127. package/dist/compiler/incremental.js +2 -2
  128. package/dist/compiler/index.cjs +1603 -204
  129. package/dist/compiler/index.d.ts +8 -0
  130. package/dist/compiler/index.js +1431 -55
  131. package/dist/compiler/ios.cjs +5 -5
  132. package/dist/compiler/ios.js +3 -3
  133. package/dist/compiler/llm-provider-capabilities.cjs +5 -5
  134. package/dist/compiler/llm-provider-capabilities.js +2 -2
  135. package/dist/compiler/openxr.cjs +5 -5
  136. package/dist/compiler/openxr.js +4 -4
  137. package/dist/compiler/sdf.cjs +6 -6
  138. package/dist/compiler/sdf.js +4 -4
  139. package/dist/compiler/state.cjs +5 -5
  140. package/dist/compiler/state.js +3 -3
  141. package/dist/compiler/trait-composition.cjs +6 -6
  142. package/dist/compiler/trait-composition.js +2 -2
  143. package/dist/compiler/unity.cjs +5 -5
  144. package/dist/compiler/unity.js +4 -4
  145. package/dist/compiler/unreal.cjs +6 -6
  146. package/dist/compiler/unreal.js +4 -4
  147. package/dist/compiler/urdf.cjs +13 -13
  148. package/dist/compiler/urdf.js +4 -4
  149. package/dist/compiler/usd-physics.cjs +6 -6
  150. package/dist/compiler/usd-physics.js +3 -3
  151. package/dist/compiler/visionos.cjs +5 -5
  152. package/dist/compiler/visionos.js +4 -4
  153. package/dist/compiler/vrchat.cjs +6 -6
  154. package/dist/compiler/vrchat.js +4 -4
  155. package/dist/compiler/wasm.cjs +9 -9
  156. package/dist/compiler/wasm.js +4 -4
  157. package/dist/compiler/webgpu.cjs +5 -5
  158. package/dist/compiler/webgpu.js +4 -4
  159. package/dist/constants.cjs +99 -99
  160. package/dist/constants.js +3 -3
  161. package/dist/debugger.cjs +5 -5
  162. package/dist/debugger.js +3 -3
  163. package/dist/{dist-GRMOP2UL.cjs → dist-YGATNURH.cjs} +705 -9
  164. package/dist/{dist-ZBUKU2JG.js → dist-ZEZXRCBG.js} +701 -10
  165. package/dist/entries/scripting.cjs +8 -8
  166. package/dist/entries/scripting.js +4 -4
  167. package/dist/evolution/index.cjs +20 -20
  168. package/dist/evolution/index.js +8 -8
  169. package/dist/index.cjs +871 -809
  170. package/dist/index.d.ts +8 -0
  171. package/dist/index.js +132 -70
  172. package/dist/parser/HoloCompositionTypes.d.ts +1 -1
  173. package/dist/parser.cjs +21 -21
  174. package/dist/parser.js +7 -7
  175. package/dist/reconstruction/index.cjs +63 -63
  176. package/dist/reconstruction/index.js +7 -7
  177. package/dist/runtime.cjs +9 -9
  178. package/dist/runtime.js +3 -3
  179. package/dist/traits/botanical-lotus.d.ts +61 -0
  180. package/dist/traits/index.cjs +664 -671
  181. package/dist/traits/index.d.ts +65 -0
  182. package/dist/traits/index.js +14 -21
  183. package/dist/types/HoloScriptPlus.d.ts +1 -1
  184. package/package.json +65 -63
  185. package/dist/HoloCompositionParser-ZVRYVEA5.cjs +0 -45
  186. package/dist/HoloScriptPlusParser-WTPRPRPR.cjs +0 -30
  187. package/dist/HoloScriptPlusParser-ZP53ECSL.js +0 -13
@@ -0,0 +1,3337 @@
1
+ 'use strict';
2
+
3
+ var chunkLM2VU56C_cjs = require('./chunk-LM2VU56C.cjs');
4
+ var chunkN2ILK7BF_cjs = require('./chunk-N2ILK7BF.cjs');
5
+
6
+ // src/version.ts
7
+ var HOLOSCRIPT_VERSION = "8.0.16" ;
8
+ var GIT_COMMIT_SHA = "c55048b35" ;
9
+ var BUILD_TIMESTAMP = "2026-07-17T05:25:25.840Z" ;
10
+ function getVersionString() {
11
+ const sha = `+${GIT_COMMIT_SHA}` ;
12
+ return `${HOLOSCRIPT_VERSION}${sha}`;
13
+ }
14
+ function getVersionInfo() {
15
+ return {
16
+ version: HOLOSCRIPT_VERSION,
17
+ gitCommitSha: GIT_COMMIT_SHA,
18
+ buildTimestamp: BUILD_TIMESTAMP,
19
+ versionString: getVersionString()
20
+ };
21
+ }
22
+
23
+ // src/reconstruction/imagePatchEmbedKernel.ts
24
+ var WGSL_IMAGE_PATCH_EMBED = `
25
+ struct Params {
26
+ imgH: u32,
27
+ imgW: u32,
28
+ patchH: u32,
29
+ patchW: u32,
30
+ numChannels:u32,
31
+ embedDim: u32,
32
+ patchLen: u32, // = patchH * patchW * numChannels
33
+ numPatchesX:u32, // = imgW / patchW
34
+ }
35
+
36
+ @group(0) @binding(0) var<storage, read> img: array<f32>; // [imgH, imgW, numChannels]
37
+ @group(0) @binding(1) var<storage, read> proj: array<f32>; // [embedDim, patchLen]
38
+ @group(0) @binding(2) var<storage, read_write> out: array<f32>; // [numPatches, embedDim]
39
+ @group(0) @binding(3) var<uniform> p: Params;
40
+
41
+ @compute @workgroup_size(64, 1, 1)
42
+ fn main(
43
+ @builtin(workgroup_id) wg: vec3<u32>,
44
+ @builtin(local_invocation_id) lid: vec3<u32>,
45
+ ) {
46
+ let patchIdx = wg.x;
47
+ let patchRow = patchIdx / p.numPatchesX;
48
+ let patchCol = patchIdx % p.numPatchesX;
49
+
50
+ // Top-left corner of patch in the image
51
+ let startRow = patchRow * p.patchH;
52
+ let startCol = patchCol * p.patchW;
53
+
54
+ // Fan out over embedDim
55
+ var d = lid.x;
56
+ while (d < p.embedDim) {
57
+ let projRow = d * p.patchLen;
58
+ var dot: f32 = 0.0;
59
+
60
+ for (var pr = 0u; pr < p.patchH; pr++) {
61
+ for (var pc2 = 0u; pc2 < p.patchW; pc2++) {
62
+ for (var ch = 0u; ch < p.numChannels; ch++) {
63
+ let imgRow = startRow + pr;
64
+ let imgCol = startCol + pc2;
65
+ let imgIdx = (imgRow * p.imgW + imgCol) * p.numChannels + ch;
66
+ let projIdx = projRow + (pr * p.patchW + pc2) * p.numChannels + ch;
67
+ dot = dot + img[imgIdx] * proj[projIdx];
68
+ }
69
+ }
70
+ }
71
+ out[patchIdx * p.embedDim + d] = dot;
72
+ d = d + 64u;
73
+ }
74
+ }
75
+ `;
76
+ function storageBuffer(device, data) {
77
+ const buf = device.createBuffer({
78
+ size: data.byteLength,
79
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
80
+ });
81
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
82
+ return buf;
83
+ }
84
+ function createImagePatchEmbedKernel(device) {
85
+ const shader = device.createShaderModule({ code: WGSL_IMAGE_PATCH_EMBED });
86
+ const pipeline = device.createComputePipeline({
87
+ layout: "auto",
88
+ compute: { module: shader, entryPoint: "main" }
89
+ });
90
+ return {
91
+ async run(image, proj, params) {
92
+ const { imgH, imgW, patchH, patchW, numChannels, embedDim } = params;
93
+ if (imgH % patchH !== 0)
94
+ throw new Error(`imagePatchEmbed: imgH=${imgH} not divisible by patchH=${patchH}`);
95
+ if (imgW % patchW !== 0)
96
+ throw new Error(`imagePatchEmbed: imgW=${imgW} not divisible by patchW=${patchW}`);
97
+ const patchLen = patchH * patchW * numChannels;
98
+ const numPatchesY = imgH / patchH;
99
+ const numPatchesX = imgW / patchW;
100
+ const numPatches = numPatchesY * numPatchesX;
101
+ if (image.length !== imgH * imgW * numChannels)
102
+ throw new Error(`imagePatchEmbed: image size mismatch`);
103
+ if (proj.length !== embedDim * patchLen)
104
+ throw new Error(`imagePatchEmbed: proj size mismatch`);
105
+ const outBytes = numPatches * embedDim * 4;
106
+ const imgBuf = storageBuffer(device, image);
107
+ const projBuf = storageBuffer(device, proj);
108
+ const outBuf = device.createBuffer({
109
+ size: outBytes,
110
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
111
+ });
112
+ const paramAB = new ArrayBuffer(32);
113
+ const pu = new Uint32Array(paramAB);
114
+ pu[0] = imgH;
115
+ pu[1] = imgW;
116
+ pu[2] = patchH;
117
+ pu[3] = patchW;
118
+ pu[4] = numChannels;
119
+ pu[5] = embedDim;
120
+ pu[6] = patchLen;
121
+ pu[7] = numPatchesX;
122
+ const paramsBuf = device.createBuffer({
123
+ size: paramAB.byteLength,
124
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
125
+ });
126
+ device.queue.writeBuffer(paramsBuf, 0, paramAB);
127
+ const bg = device.createBindGroup({
128
+ layout: pipeline.getBindGroupLayout(0),
129
+ entries: [
130
+ { binding: 0, resource: { buffer: imgBuf } },
131
+ { binding: 1, resource: { buffer: projBuf } },
132
+ { binding: 2, resource: { buffer: outBuf } },
133
+ { binding: 3, resource: { buffer: paramsBuf } }
134
+ ]
135
+ });
136
+ const enc = device.createCommandEncoder();
137
+ const pass = enc.beginComputePass();
138
+ pass.setPipeline(pipeline);
139
+ pass.setBindGroup(0, bg);
140
+ pass.dispatchWorkgroups(numPatches);
141
+ pass.end();
142
+ const staging = device.createBuffer({
143
+ size: outBytes,
144
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
145
+ });
146
+ enc.copyBufferToBuffer(outBuf, 0, staging, 0, outBytes);
147
+ device.queue.submit([enc.finish()]);
148
+ await staging.mapAsync(GPUMapMode.READ);
149
+ const result = new Float32Array(staging.getMappedRange().slice(0));
150
+ staging.unmap();
151
+ imgBuf.destroy();
152
+ projBuf.destroy();
153
+ outBuf.destroy();
154
+ paramsBuf.destroy();
155
+ staging.destroy();
156
+ return result;
157
+ }
158
+ };
159
+ }
160
+
161
+ // src/reconstruction/layerNormKernel.ts
162
+ var WGSL_LAYER_NORM_SOURCE = `
163
+ struct Params {
164
+ rows: u32,
165
+ dModel: u32,
166
+ eps: f32,
167
+ }
168
+ @group(0) @binding(0) var<storage, read> input: array<f32>;
169
+ @group(0) @binding(1) var<storage, read> gamma: array<f32>;
170
+ @group(0) @binding(2) var<storage, read> beta: array<f32>;
171
+ @group(0) @binding(3) var<storage, read_write> output: array<f32>;
172
+ @group(0) @binding(4) var<uniform> params: Params;
173
+ var<workgroup> reduceScratch: array<f32, 64>;
174
+ fn inputIndex(row: u32, col: u32) -> u32 {
175
+ return row * params.dModel + col;
176
+ }
177
+ @compute @workgroup_size(64, 1, 1)
178
+ fn main(
179
+ @builtin(workgroup_id) workgroupId: vec3<u32>,
180
+ @builtin(local_invocation_id) localId: vec3<u32>,
181
+ ) {
182
+ let row = workgroupId.x;
183
+ let lane = localId.x;
184
+ if (row >= params.rows || params.dModel == 0u) {
185
+ return;
186
+ }
187
+ var sum = 0.0;
188
+ for (var c: u32 = lane; c < params.dModel; c = c + 64u) {
189
+ sum = sum + input[inputIndex(row, c)];
190
+ }
191
+ reduceScratch[lane] = sum;
192
+ workgroupBarrier();
193
+ var stride: u32 = 32u;
194
+ loop {
195
+ if (lane < stride) {
196
+ reduceScratch[lane] = reduceScratch[lane] + reduceScratch[lane + stride];
197
+ }
198
+ workgroupBarrier();
199
+ if (stride == 1u) {
200
+ break;
201
+ }
202
+ stride = stride >> 1u;
203
+ }
204
+ let mean = reduceScratch[0] / f32(params.dModel);
205
+ var sq = 0.0;
206
+ for (var c: u32 = lane; c < params.dModel; c = c + 64u) {
207
+ let d = input[inputIndex(row, c)] - mean;
208
+ sq = sq + d * d;
209
+ }
210
+ reduceScratch[lane] = sq;
211
+ workgroupBarrier();
212
+ stride = 32u;
213
+ loop {
214
+ if (lane < stride) {
215
+ reduceScratch[lane] = reduceScratch[lane] + reduceScratch[lane + stride];
216
+ }
217
+ workgroupBarrier();
218
+ if (stride == 1u) {
219
+ break;
220
+ }
221
+ stride = stride >> 1u;
222
+ }
223
+ let variance = reduceScratch[0] / f32(params.dModel);
224
+ let invStd = inverseSqrt(variance + params.eps);
225
+ for (var c: u32 = lane; c < params.dModel; c = c + 64u) {
226
+ let idx = inputIndex(row, c);
227
+ let normalized = (input[idx] - mean) * invStd;
228
+ output[idx] = normalized * gamma[c] + beta[c];
229
+ }
230
+ }
231
+ `;
232
+ function createStorageBuffer(device, data) {
233
+ const buf = device.createBuffer({
234
+ size: data.byteLength,
235
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
236
+ });
237
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
238
+ return buf;
239
+ }
240
+ function createParamsBuffer(device, rows, dModel, eps) {
241
+ const paramsBytes = new ArrayBuffer(16);
242
+ const view = new DataView(paramsBytes);
243
+ view.setUint32(0, rows, true);
244
+ view.setUint32(4, dModel, true);
245
+ view.setFloat32(8, eps, true);
246
+ const params = device.createBuffer({
247
+ size: paramsBytes.byteLength,
248
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
249
+ });
250
+ device.queue.writeBuffer(params, 0, paramsBytes);
251
+ return params;
252
+ }
253
+ function createLayerNormKernel(device) {
254
+ const shader = device.createShaderModule({ code: WGSL_LAYER_NORM_SOURCE });
255
+ const pipeline = device.createComputePipeline({
256
+ layout: "auto",
257
+ compute: {
258
+ module: shader,
259
+ entryPoint: "main"
260
+ }
261
+ });
262
+ return {
263
+ async run(input, gamma, beta) {
264
+ if (gamma.length === 0) {
265
+ throw new Error("LayerNorm: gamma must be non-empty");
266
+ }
267
+ if (beta.length !== gamma.length) {
268
+ throw new Error("LayerNorm: beta length must match gamma length");
269
+ }
270
+ if (input.length % gamma.length !== 0) {
271
+ throw new Error("LayerNorm: input length must be a multiple of gamma length (dModel)");
272
+ }
273
+ const dModel = gamma.length;
274
+ const rows = input.length / dModel;
275
+ const eps = 1e-5;
276
+ const inputBuf = createStorageBuffer(device, input);
277
+ const gammaBuf = createStorageBuffer(device, gamma);
278
+ const betaBuf = createStorageBuffer(device, beta);
279
+ const outputBuf = device.createBuffer({
280
+ size: input.byteLength,
281
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
282
+ });
283
+ const paramsBuf = createParamsBuffer(device, rows, dModel, eps);
284
+ const readback2 = device.createBuffer({
285
+ size: input.byteLength,
286
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
287
+ });
288
+ const bindGroup = device.createBindGroup({
289
+ layout: pipeline.getBindGroupLayout(0),
290
+ entries: [
291
+ { binding: 0, resource: { buffer: inputBuf } },
292
+ { binding: 1, resource: { buffer: gammaBuf } },
293
+ { binding: 2, resource: { buffer: betaBuf } },
294
+ { binding: 3, resource: { buffer: outputBuf } },
295
+ { binding: 4, resource: { buffer: paramsBuf } }
296
+ ]
297
+ });
298
+ const encoder = device.createCommandEncoder();
299
+ {
300
+ const pass = encoder.beginComputePass();
301
+ pass.setPipeline(pipeline);
302
+ pass.setBindGroup(0, bindGroup);
303
+ pass.dispatchWorkgroups(rows, 1, 1);
304
+ pass.end();
305
+ }
306
+ encoder.copyBufferToBuffer(outputBuf, 0, readback2, 0, input.byteLength);
307
+ device.queue.submit([encoder.finish()]);
308
+ await readback2.mapAsync(GPUMapMode.READ);
309
+ const bytes = readback2.getMappedRange();
310
+ const out = new Float32Array(bytes.slice(0));
311
+ readback2.unmap();
312
+ inputBuf.destroy();
313
+ gammaBuf.destroy();
314
+ betaBuf.destroy();
315
+ outputBuf.destroy();
316
+ paramsBuf.destroy();
317
+ readback2.destroy();
318
+ return out;
319
+ }
320
+ };
321
+ }
322
+
323
+ // src/reconstruction/gemmKernel.ts
324
+ var TILE = 8;
325
+ var WGSL_GEMM_SOURCE = `
326
+ const TILE: u32 = 8u;
327
+ struct Params {
328
+ M: u32,
329
+ N: u32,
330
+ K: u32,
331
+ }
332
+ @group(0) @binding(0) var<storage, read> A: array<f32>; // [M * K]
333
+ @group(0) @binding(1) var<storage, read> B: array<f32>; // [K * N]
334
+ @group(0) @binding(2) var<storage, read_write> C: array<f32>; // [M * N]
335
+ @group(0) @binding(3) var<uniform> p: Params;
336
+ var<workgroup> tileA: array<f32, 64>; // TILE * TILE = 8*8
337
+ var<workgroup> tileB: array<f32, 64>;
338
+ @compute @workgroup_size(8, 8, 1)
339
+ fn main(
340
+ @builtin(workgroup_id) wg: vec3<u32>,
341
+ @builtin(local_invocation_id) lid: vec3<u32>,
342
+ ) {
343
+ let row = wg.y * TILE + lid.y;
344
+ let col = wg.x * TILE + lid.x;
345
+ var acc: f32 = 0.0;
346
+ let numTiles = (p.K + TILE - 1u) / TILE;
347
+ for (var t = 0u; t < numTiles; t++) {
348
+ // Load tile of A (row of A) into shared
349
+ let aCol = t * TILE + lid.x;
350
+ if (row < p.M && aCol < p.K) {
351
+ tileA[lid.y * TILE + lid.x] = A[row * p.K + aCol];
352
+ } else {
353
+ tileA[lid.y * TILE + lid.x] = 0.0;
354
+ }
355
+ // Load tile of B (col of B) into shared
356
+ let bRow = t * TILE + lid.y;
357
+ if (bRow < p.K && col < p.N) {
358
+ tileB[lid.y * TILE + lid.x] = B[bRow * p.N + col];
359
+ } else {
360
+ tileB[lid.y * TILE + lid.x] = 0.0;
361
+ }
362
+ workgroupBarrier();
363
+ for (var k = 0u; k < TILE; k++) {
364
+ acc = acc + tileA[lid.y * TILE + k] * tileB[k * TILE + lid.x];
365
+ }
366
+ workgroupBarrier();
367
+ }
368
+ if (row < p.M && col < p.N) {
369
+ C[row * p.N + col] = acc;
370
+ }
371
+ }
372
+ `;
373
+ function storageBuffer2(device, data) {
374
+ const buf = device.createBuffer({
375
+ size: data.byteLength,
376
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
377
+ });
378
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
379
+ return buf;
380
+ }
381
+ function createGemmKernel(device) {
382
+ const shader = device.createShaderModule({ code: WGSL_GEMM_SOURCE });
383
+ const pipeline = device.createComputePipeline({
384
+ layout: "auto",
385
+ compute: { module: shader, entryPoint: "main" }
386
+ });
387
+ return {
388
+ async run(A, B, M, N, K) {
389
+ if (A.length !== M * K) throw new Error(`GEMM: A.length=${A.length} != M*K=${M * K}`);
390
+ if (B.length !== K * N) throw new Error(`GEMM: B.length=${B.length} != K*N=${K * N}`);
391
+ const aBuf = storageBuffer2(device, A);
392
+ const bBuf = storageBuffer2(device, B);
393
+ const cBuf = device.createBuffer({
394
+ size: M * N * 4,
395
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
396
+ });
397
+ const paramAB = new ArrayBuffer(16);
398
+ const pu = new Uint32Array(paramAB);
399
+ pu[0] = M;
400
+ pu[1] = N;
401
+ pu[2] = K;
402
+ const paramsBuf = device.createBuffer({
403
+ size: paramAB.byteLength,
404
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
405
+ });
406
+ device.queue.writeBuffer(paramsBuf, 0, paramAB);
407
+ const bg = device.createBindGroup({
408
+ layout: pipeline.getBindGroupLayout(0),
409
+ entries: [
410
+ { binding: 0, resource: { buffer: aBuf } },
411
+ { binding: 1, resource: { buffer: bBuf } },
412
+ { binding: 2, resource: { buffer: cBuf } },
413
+ { binding: 3, resource: { buffer: paramsBuf } }
414
+ ]
415
+ });
416
+ const wgX = Math.ceil(N / TILE);
417
+ const wgY = Math.ceil(M / TILE);
418
+ const enc = device.createCommandEncoder();
419
+ const pass = enc.beginComputePass();
420
+ pass.setPipeline(pipeline);
421
+ pass.setBindGroup(0, bg);
422
+ pass.dispatchWorkgroups(wgX, wgY, 1);
423
+ pass.end();
424
+ const staging = device.createBuffer({
425
+ size: M * N * 4,
426
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
427
+ });
428
+ enc.copyBufferToBuffer(cBuf, 0, staging, 0, M * N * 4);
429
+ device.queue.submit([enc.finish()]);
430
+ await staging.mapAsync(GPUMapMode.READ);
431
+ const result = new Float32Array(staging.getMappedRange().slice(0));
432
+ staging.unmap();
433
+ aBuf.destroy();
434
+ bBuf.destroy();
435
+ cBuf.destroy();
436
+ paramsBuf.destroy();
437
+ staging.destroy();
438
+ return result;
439
+ }
440
+ };
441
+ }
442
+
443
+ // src/reconstruction/ropeKernel.ts
444
+ var WGSL_ROPE_SOURCE = `
445
+ struct Params {
446
+ seqLen: u32,
447
+ numHeads: u32,
448
+ headDim: u32,
449
+ base: f32,
450
+ posOffset: u32,
451
+ }
452
+ @group(0) @binding(0) var<storage, read> qIn: array<f32>;
453
+ @group(0) @binding(1) var<storage, read_write> qOut: array<f32>;
454
+ @group(0) @binding(2) var<uniform> p: Params;
455
+ fn flatIdx(token: u32, head: u32, dim: u32) -> u32 {
456
+ return token * p.numHeads * p.headDim + head * p.headDim + dim;
457
+ }
458
+ @compute @workgroup_size(32, 1, 1)
459
+ fn main(
460
+ @builtin(workgroup_id) wgid: vec3<u32>,
461
+ @builtin(local_invocation_id) lid: vec3<u32>,
462
+ ) {
463
+ let wg = wgid.x;
464
+ let token = wg / p.numHeads;
465
+ let head = wg % p.numHeads;
466
+ if (token >= p.seqLen) { return; }
467
+ let pairs = p.headDim / 2u;
468
+ let pos = token + p.posOffset;
469
+ var pairIdx = lid.x;
470
+ while (pairIdx < pairs) {
471
+ let d = pairIdx;
472
+ let exponent = -2.0 * f32(d) / f32(p.headDim);
473
+ let theta = f32(pos) * pow(p.base, exponent);
474
+ let c = cos(theta);
475
+ let s = sin(theta);
476
+ let i0 = flatIdx(token, head, 2u * d);
477
+ let i1 = flatIdx(token, head, 2u * d + 1u);
478
+ let x0 = qIn[i0];
479
+ let x1 = qIn[i1];
480
+ qOut[i0] = x0 * c - x1 * s;
481
+ qOut[i1] = x0 * s + x1 * c;
482
+ pairIdx = pairIdx + 32u;
483
+ }
484
+ }
485
+ `;
486
+ function createStorageBuffer2(device, data) {
487
+ const buf = device.createBuffer({
488
+ size: data.byteLength,
489
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
490
+ });
491
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
492
+ return buf;
493
+ }
494
+ function createOutputBuffer(device, byteLength) {
495
+ return device.createBuffer({
496
+ size: byteLength,
497
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
498
+ });
499
+ }
500
+ function createParamsBuffer2(device, rp) {
501
+ const buf = new ArrayBuffer(32);
502
+ const u = new Uint32Array(buf);
503
+ const f = new Float32Array(buf);
504
+ u[0] = rp.seqLen;
505
+ u[1] = rp.numHeads;
506
+ u[2] = rp.headDim;
507
+ f[3] = rp.base;
508
+ u[4] = rp.posOffset;
509
+ const gpuBuf = device.createBuffer({
510
+ size: buf.byteLength,
511
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
512
+ });
513
+ device.queue.writeBuffer(gpuBuf, 0, buf);
514
+ return gpuBuf;
515
+ }
516
+ function createRopeKernel(device) {
517
+ const shader = device.createShaderModule({ code: WGSL_ROPE_SOURCE });
518
+ const pipeline = device.createComputePipeline({
519
+ layout: "auto",
520
+ compute: { module: shader, entryPoint: "main" }
521
+ });
522
+ return {
523
+ async run(input, params) {
524
+ const { seqLen, numHeads, headDim } = params;
525
+ const base = params.base ?? 1e4;
526
+ const posOffset = params.posOffset ?? 0;
527
+ if (headDim % 2 !== 0) {
528
+ throw new Error("RoPE: headDim must be even");
529
+ }
530
+ const expected = seqLen * numHeads * headDim;
531
+ if (input.length !== expected) {
532
+ throw new Error(
533
+ `RoPE: input length ${input.length} does not match seqLen*numHeads*headDim=${expected}`
534
+ );
535
+ }
536
+ const rp = { seqLen, numHeads, headDim, base, posOffset };
537
+ const inBuf = createStorageBuffer2(device, input);
538
+ const outBuf = createOutputBuffer(device, input.byteLength);
539
+ const paramsBuf = createParamsBuffer2(device, rp);
540
+ const stagingBuf = device.createBuffer({
541
+ size: input.byteLength,
542
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
543
+ });
544
+ const bg = device.createBindGroup({
545
+ layout: pipeline.getBindGroupLayout(0),
546
+ entries: [
547
+ { binding: 0, resource: { buffer: inBuf } },
548
+ { binding: 1, resource: { buffer: outBuf } },
549
+ { binding: 2, resource: { buffer: paramsBuf } }
550
+ ]
551
+ });
552
+ const enc = device.createCommandEncoder();
553
+ const pass = enc.beginComputePass();
554
+ pass.setPipeline(pipeline);
555
+ pass.setBindGroup(0, bg);
556
+ pass.dispatchWorkgroups(seqLen * numHeads);
557
+ pass.end();
558
+ enc.copyBufferToBuffer(outBuf, 0, stagingBuf, 0, input.byteLength);
559
+ device.queue.submit([enc.finish()]);
560
+ await stagingBuf.mapAsync(GPUMapMode.READ);
561
+ const result = new Float32Array(stagingBuf.getMappedRange().slice(0));
562
+ stagingBuf.unmap();
563
+ inBuf.destroy();
564
+ outBuf.destroy();
565
+ paramsBuf.destroy();
566
+ stagingBuf.destroy();
567
+ return result;
568
+ }
569
+ };
570
+ }
571
+
572
+ // src/reconstruction/fusedMHAKernel.ts
573
+ var WGSL_FUSED_MHA = `
574
+ struct Params {
575
+ numHeads: u32,
576
+ qLen: u32,
577
+ kLen: u32,
578
+ dHead: u32, // head dimension (= sqrt-scale base)
579
+ vHead: u32, // output feature dim per head (usually == dHead)
580
+ }
581
+
582
+ @group(0) @binding(0) var<storage, read> Q: array<f32>; // [h, qLen, dHead]
583
+ @group(0) @binding(1) var<storage, read> K: array<f32>; // [h, kLen, dHead]
584
+ @group(0) @binding(2) var<storage, read> V: array<f32>; // [h, kLen, vHead]
585
+ @group(0) @binding(3) var<storage, read_write> O: array<f32>; // [h, qLen, vHead]
586
+ @group(0) @binding(4) var<uniform> p: Params;
587
+
588
+ // Shared memory: reuse for scores then for v-accumulation
589
+ var<workgroup> scores: array<f32, 1024>; // max supported kLen per workgroup
590
+
591
+ @compute @workgroup_size(64, 1, 1)
592
+ fn main(
593
+ @builtin(workgroup_id) wg: vec3<u32>,
594
+ @builtin(local_invocation_id) lid: vec3<u32>,
595
+ ) {
596
+ // workgroup index encodes (head, qRow)
597
+ let head = wg.x / p.qLen;
598
+ let qRow = wg.x % p.qLen;
599
+
600
+ if (head >= p.numHeads || qRow >= p.qLen) { return; }
601
+
602
+ let scale: f32 = 1.0 / sqrt(f32(p.dHead));
603
+
604
+ // ---- Phase 1: compute dot products Q[qRow] \xB7 K[k] for all k ----
605
+ // Thread 0 does sequential dot products and stores in shared scores[]
606
+ // (for kLen \u2264 1024; if larger, falls back to partial accumulation)
607
+ if (lid.x == 0u) {
608
+ let qBase = (head * p.qLen + qRow) * p.dHead;
609
+ for (var ki = 0u; ki < p.kLen; ki++) {
610
+ let kBase = (head * p.kLen + ki) * p.dHead;
611
+ var dot: f32 = 0.0;
612
+ for (var d = 0u; d < p.dHead; d++) {
613
+ dot = dot + Q[qBase + d] * K[kBase + d];
614
+ }
615
+ scores[ki] = dot * scale;
616
+ }
617
+ }
618
+ workgroupBarrier();
619
+
620
+ // ---- Phase 2: stable softmax in-place ----
621
+ if (lid.x == 0u) {
622
+ var maxS: f32 = scores[0];
623
+ for (var ki = 1u; ki < p.kLen; ki++) {
624
+ maxS = max(maxS, scores[ki]);
625
+ }
626
+ var sumExp: f32 = 0.0;
627
+ for (var ki = 0u; ki < p.kLen; ki++) {
628
+ scores[ki] = exp(scores[ki] - maxS);
629
+ sumExp = sumExp + scores[ki];
630
+ }
631
+ for (var ki = 0u; ki < p.kLen; ki++) {
632
+ scores[ki] = scores[ki] / sumExp;
633
+ }
634
+ }
635
+ workgroupBarrier();
636
+
637
+ // ---- Phase 3: weighted sum over V ----
638
+ // Threads fan out over vHead dimension
639
+ let outBase = (head * p.qLen + qRow) * p.vHead;
640
+ var vi = lid.x;
641
+ while (vi < p.vHead) {
642
+ var acc: f32 = 0.0;
643
+ for (var ki = 0u; ki < p.kLen; ki++) {
644
+ acc = acc + scores[ki] * V[(head * p.kLen + ki) * p.vHead + vi];
645
+ }
646
+ O[outBase + vi] = acc;
647
+ vi = vi + 64u;
648
+ }
649
+ }
650
+ `;
651
+ function storageBuffer3(device, data) {
652
+ const buf = device.createBuffer({
653
+ size: data.byteLength,
654
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
655
+ });
656
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
657
+ return buf;
658
+ }
659
+ function createFusedMHAKernel(device) {
660
+ const shader = device.createShaderModule({ code: WGSL_FUSED_MHA });
661
+ const pipeline = device.createComputePipeline({
662
+ layout: "auto",
663
+ compute: { module: shader, entryPoint: "main" }
664
+ });
665
+ return {
666
+ async run(Q, K, V, params) {
667
+ const { numHeads, qLen, kLen, dHead } = params;
668
+ const vHead = params.vHead ?? dHead;
669
+ if (kLen > 1024) throw new Error(`fusedMHA: kLen=${kLen} exceeds shared memory limit 1024`);
670
+ if (Q.length !== numHeads * qLen * dHead) throw new Error(`fusedMHA: Q size mismatch`);
671
+ if (K.length !== numHeads * kLen * dHead) throw new Error(`fusedMHA: K size mismatch`);
672
+ if (V.length !== numHeads * kLen * vHead) throw new Error(`fusedMHA: V size mismatch`);
673
+ const outBytes = numHeads * qLen * vHead * 4;
674
+ const qBuf = storageBuffer3(device, Q);
675
+ const kBuf = storageBuffer3(device, K);
676
+ const vBuf = storageBuffer3(device, V);
677
+ const oBuf = device.createBuffer({
678
+ size: outBytes,
679
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
680
+ });
681
+ const paramAB = new ArrayBuffer(24);
682
+ const pu = new Uint32Array(paramAB);
683
+ pu[0] = numHeads;
684
+ pu[1] = qLen;
685
+ pu[2] = kLen;
686
+ pu[3] = dHead;
687
+ pu[4] = vHead;
688
+ const paramsBuf = device.createBuffer({
689
+ size: paramAB.byteLength,
690
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
691
+ });
692
+ device.queue.writeBuffer(paramsBuf, 0, paramAB);
693
+ const bg = device.createBindGroup({
694
+ layout: pipeline.getBindGroupLayout(0),
695
+ entries: [
696
+ { binding: 0, resource: { buffer: qBuf } },
697
+ { binding: 1, resource: { buffer: kBuf } },
698
+ { binding: 2, resource: { buffer: vBuf } },
699
+ { binding: 3, resource: { buffer: oBuf } },
700
+ { binding: 4, resource: { buffer: paramsBuf } }
701
+ ]
702
+ });
703
+ const workgroups = numHeads * qLen;
704
+ const enc = device.createCommandEncoder();
705
+ const pass = enc.beginComputePass();
706
+ pass.setPipeline(pipeline);
707
+ pass.setBindGroup(0, bg);
708
+ pass.dispatchWorkgroups(workgroups);
709
+ pass.end();
710
+ const staging = device.createBuffer({
711
+ size: outBytes,
712
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
713
+ });
714
+ enc.copyBufferToBuffer(oBuf, 0, staging, 0, outBytes);
715
+ device.queue.submit([enc.finish()]);
716
+ await staging.mapAsync(GPUMapMode.READ);
717
+ const result = new Float32Array(staging.getMappedRange().slice(0));
718
+ staging.unmap();
719
+ qBuf.destroy();
720
+ kBuf.destroy();
721
+ vBuf.destroy();
722
+ oBuf.destroy();
723
+ paramsBuf.destroy();
724
+ staging.destroy();
725
+ return result;
726
+ }
727
+ };
728
+ }
729
+
730
+ // src/reconstruction/geluKernel.ts
731
+ var WGSL_GELU_SOURCE = `
732
+ struct Params {
733
+ elements: u32,
734
+ }
735
+ @group(0) @binding(0) var<storage, read> input: array<f32>;
736
+ @group(0) @binding(1) var<storage, read_write> output: array<f32>;
737
+ @group(0) @binding(2) var<uniform> params: Params;
738
+ fn gelu(x: f32) -> f32 {
739
+ let k0 = 0.7978845608;
740
+ let k1 = 0.044715;
741
+ let x3 = x * x * x;
742
+ let inner = k0 * (x + k1 * x3);
743
+ return 0.5 * x * (1.0 + tanh(inner));
744
+ }
745
+ @compute @workgroup_size(64, 1, 1)
746
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
747
+ var idx = gid.x;
748
+ let stride = 64u;
749
+ while (idx < params.elements) {
750
+ output[idx] = gelu(input[idx]);
751
+ idx = idx + stride;
752
+ }
753
+ }
754
+ `;
755
+ function createInputBuffer(device, data) {
756
+ const buf = device.createBuffer({
757
+ size: data.byteLength,
758
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
759
+ });
760
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
761
+ return buf;
762
+ }
763
+ function createParamsBuffer3(device, elements) {
764
+ const paramsBytes = new ArrayBuffer(16);
765
+ const view = new DataView(paramsBytes);
766
+ view.setUint32(0, elements, true);
767
+ const params = device.createBuffer({
768
+ size: paramsBytes.byteLength,
769
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
770
+ });
771
+ device.queue.writeBuffer(params, 0, paramsBytes);
772
+ return params;
773
+ }
774
+ function createGeluKernel(device) {
775
+ const shader = device.createShaderModule({ code: WGSL_GELU_SOURCE });
776
+ const pipeline = device.createComputePipeline({
777
+ layout: "auto",
778
+ compute: {
779
+ module: shader,
780
+ entryPoint: "main"
781
+ }
782
+ });
783
+ return {
784
+ async run(input) {
785
+ if (input.length === 0) {
786
+ throw new Error("GELU: input must be non-empty");
787
+ }
788
+ const inputBuf = createInputBuffer(device, input);
789
+ const outputBuf = device.createBuffer({
790
+ size: input.byteLength,
791
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
792
+ });
793
+ const paramsBuf = createParamsBuffer3(device, input.length);
794
+ const readback2 = device.createBuffer({
795
+ size: input.byteLength,
796
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
797
+ });
798
+ const bindGroup = device.createBindGroup({
799
+ layout: pipeline.getBindGroupLayout(0),
800
+ entries: [
801
+ { binding: 0, resource: { buffer: inputBuf } },
802
+ { binding: 1, resource: { buffer: outputBuf } },
803
+ { binding: 2, resource: { buffer: paramsBuf } }
804
+ ]
805
+ });
806
+ const encoder = device.createCommandEncoder();
807
+ {
808
+ const pass = encoder.beginComputePass();
809
+ pass.setPipeline(pipeline);
810
+ pass.setBindGroup(0, bindGroup);
811
+ pass.dispatchWorkgroups(1, 1, 1);
812
+ pass.end();
813
+ }
814
+ encoder.copyBufferToBuffer(outputBuf, 0, readback2, 0, input.byteLength);
815
+ device.queue.submit([encoder.finish()]);
816
+ await readback2.mapAsync(GPUMapMode.READ);
817
+ const bytes = readback2.getMappedRange();
818
+ const out = new Float32Array(bytes.slice(0));
819
+ readback2.unmap();
820
+ inputBuf.destroy();
821
+ outputBuf.destroy();
822
+ paramsBuf.destroy();
823
+ readback2.destroy();
824
+ return out;
825
+ }
826
+ };
827
+ }
828
+
829
+ // src/reconstruction/holoMapMicroEncoder.ts
830
+ var MICRO = 14;
831
+ var EMBED_DIM = 32;
832
+ var NUM_HEADS = 4;
833
+ var HEAD_DIM = 8;
834
+ var PATCH_LEN = MICRO * MICRO * 3;
835
+ var weightCache = /* @__PURE__ */ new Map();
836
+ var weightBlobKeyCache = /* @__PURE__ */ new WeakMap();
837
+ function hashConfigSeed(config) {
838
+ let h = config.seed >>> 0;
839
+ const s = config.modelHash || "";
840
+ for (let i = 0; i < s.length; i += 1) {
841
+ h = Math.imul(h ^ s.charCodeAt(i), 2654435769);
842
+ }
843
+ return h >>> 0;
844
+ }
845
+ function mulberry32(seed) {
846
+ let t = seed >>> 0;
847
+ return () => {
848
+ t += 1831565813;
849
+ let r = Math.imul(t ^ t >>> 15, t | 1);
850
+ r ^= r + Math.imul(r ^ r >>> 7, r | 61);
851
+ return ((r ^ r >>> 14) >>> 0) / 4294967296;
852
+ };
853
+ }
854
+ function fillGaussian2D(out, rng, scale) {
855
+ for (let i = 0; i < out.length; i += 1) out[i] = (rng() * 2 - 1) * scale;
856
+ }
857
+ function microWeightKey(config) {
858
+ return `${config.seed}\0${config.modelHash || ""}`;
859
+ }
860
+ var MICRO_WEIGHT_MAGIC = [72, 77, 87, 49];
861
+ var MICRO_WEIGHT_VERSION = 1;
862
+ var MICRO_WEIGHT_FLOATS = EMBED_DIM * PATCH_LEN + // proj
863
+ 3 * EMBED_DIM * EMBED_DIM + // Wq, Wk, Wv
864
+ EMBED_DIM * 3 + // Wxyz
865
+ 4 * EMBED_DIM;
866
+ var MICRO_WEIGHT_BYTES = 8 + MICRO_WEIGHT_FLOATS * 4;
867
+ function toUint8(bytes) {
868
+ return bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
869
+ }
870
+ function fnv1a(bytes) {
871
+ let h = 2166136261;
872
+ for (let i = 0; i < bytes.length; i += 1) {
873
+ h ^= bytes[i];
874
+ h = Math.imul(h, 16777619);
875
+ }
876
+ return (h >>> 0).toString(16);
877
+ }
878
+ function cachedWeightBlobKey(input) {
879
+ const bytes = toUint8(input);
880
+ const cached = weightBlobKeyCache.get(input);
881
+ if (cached) return { bytes, key: cached };
882
+ const key = `${bytes.byteLength}:${fnv1a(bytes)}`;
883
+ weightBlobKeyCache.set(input, key);
884
+ return { bytes, key };
885
+ }
886
+ function serializeMicroWeights(w) {
887
+ const bytes = new Uint8Array(MICRO_WEIGHT_BYTES);
888
+ bytes.set(MICRO_WEIGHT_MAGIC, 0);
889
+ const view = new DataView(bytes.buffer);
890
+ view.setUint32(4, MICRO_WEIGHT_VERSION, true);
891
+ let off = 8;
892
+ const put = (arr) => {
893
+ for (let i = 0; i < arr.length; i += 1) {
894
+ view.setFloat32(off, arr[i], true);
895
+ off += 4;
896
+ }
897
+ };
898
+ put(w.proj);
899
+ put(w.Wq);
900
+ put(w.Wk);
901
+ put(w.Wv);
902
+ put(w.Wxyz);
903
+ put(w.gamma1);
904
+ put(w.beta1);
905
+ put(w.gamma2);
906
+ put(w.beta2);
907
+ return bytes;
908
+ }
909
+ function deserializeMicroWeights(input) {
910
+ const bytes = toUint8(input);
911
+ if (bytes.byteLength !== MICRO_WEIGHT_BYTES) return null;
912
+ for (let i = 0; i < 4; i += 1) {
913
+ if (bytes[i] !== MICRO_WEIGHT_MAGIC[i]) return null;
914
+ }
915
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
916
+ if (view.getUint32(4, true) !== MICRO_WEIGHT_VERSION) return null;
917
+ let off = 8;
918
+ const take = (count) => {
919
+ const out = new Float32Array(count);
920
+ for (let i = 0; i < count; i += 1) {
921
+ out[i] = view.getFloat32(off, true);
922
+ off += 4;
923
+ }
924
+ return out;
925
+ };
926
+ return {
927
+ proj: take(EMBED_DIM * PATCH_LEN),
928
+ Wq: take(EMBED_DIM * EMBED_DIM),
929
+ Wk: take(EMBED_DIM * EMBED_DIM),
930
+ Wv: take(EMBED_DIM * EMBED_DIM),
931
+ Wxyz: take(EMBED_DIM * 3),
932
+ gamma1: take(EMBED_DIM),
933
+ beta1: take(EMBED_DIM),
934
+ gamma2: take(EMBED_DIM),
935
+ beta2: take(EMBED_DIM)
936
+ };
937
+ }
938
+ function getMicroWeights(config) {
939
+ if (config.weightBytes) {
940
+ const { bytes: wb, key: blobKey } = cachedWeightBlobKey(config.weightBytes);
941
+ const ckptKey = `${microWeightKey(config)}\0ckpt:${blobKey}`;
942
+ const cachedReal = weightCache.get(ckptKey);
943
+ if (cachedReal) return cachedReal;
944
+ const real = deserializeMicroWeights(wb);
945
+ if (real) {
946
+ weightCache.set(ckptKey, real);
947
+ return real;
948
+ }
949
+ console.warn(
950
+ `[holoMapMicroEncoder] weightBytes present (${wb.byteLength} B) but not a valid HMW1 checkpoint (expected ${MICRO_WEIGHT_BYTES} B + magic) \u2014 falling back to PRNG-synthesized weights.`
951
+ );
952
+ }
953
+ const key = microWeightKey(config);
954
+ const cached = weightCache.get(key);
955
+ if (cached) return cached;
956
+ const rng = mulberry32(hashConfigSeed(config));
957
+ const weights = {
958
+ proj: new Float32Array(EMBED_DIM * PATCH_LEN),
959
+ Wq: new Float32Array(EMBED_DIM * EMBED_DIM),
960
+ Wk: new Float32Array(EMBED_DIM * EMBED_DIM),
961
+ Wv: new Float32Array(EMBED_DIM * EMBED_DIM),
962
+ Wxyz: new Float32Array(EMBED_DIM * 3),
963
+ gamma1: new Float32Array(EMBED_DIM).fill(1),
964
+ beta1: new Float32Array(EMBED_DIM).fill(0),
965
+ gamma2: new Float32Array(EMBED_DIM).fill(1),
966
+ beta2: new Float32Array(EMBED_DIM).fill(0)
967
+ };
968
+ fillGaussian2D(weights.proj, rng, 0.02);
969
+ fillGaussian2D(weights.Wq, rng, 0.02);
970
+ fillGaussian2D(weights.Wk, rng, 0.02);
971
+ fillGaussian2D(weights.Wv, rng, 0.02);
972
+ fillGaussian2D(weights.Wxyz, rng, 0.05);
973
+ weightCache.set(key, weights);
974
+ return weights;
975
+ }
976
+ function frameToMicroImage(frame) {
977
+ const out = new Float32Array(MICRO * MICRO * 3);
978
+ for (let y = 0; y < MICRO; y += 1) {
979
+ const sy = Math.min(frame.height - 1, Math.floor(y * frame.height / MICRO));
980
+ for (let x = 0; x < MICRO; x += 1) {
981
+ const sx = Math.min(frame.width - 1, Math.floor(x * frame.width / MICRO));
982
+ const src = (sy * frame.width + sx) * frame.stride;
983
+ const dst = (y * MICRO + x) * 3;
984
+ out[dst] = frame.rgb[src] / 255;
985
+ out[dst + 1] = frame.rgb[src + 1] / 255;
986
+ out[dst + 2] = frame.rgb[src + 2] / 255;
987
+ }
988
+ }
989
+ return out;
990
+ }
991
+ function patchEmbedCpu(image, proj, embedDim) {
992
+ const out = new Float32Array(embedDim);
993
+ for (let d = 0; d < embedDim; d += 1) {
994
+ let dot = 0;
995
+ const row = d * PATCH_LEN;
996
+ for (let i = 0; i < PATCH_LEN; i += 1) dot += image[i] * proj[row + i];
997
+ out[d] = dot;
998
+ }
999
+ return out;
1000
+ }
1001
+ function gemmCpu(a, b, M, K, N) {
1002
+ const c = new Float32Array(M * N);
1003
+ for (let m = 0; m < M; m += 1) {
1004
+ for (let n = 0; n < N; n += 1) {
1005
+ let acc = 0;
1006
+ for (let k = 0; k < K; k += 1) acc += a[m * K + k] * b[k * N + n];
1007
+ c[m * N + n] = acc;
1008
+ }
1009
+ }
1010
+ return c;
1011
+ }
1012
+ function layerNormCpu(x, gamma, beta, rows, d) {
1013
+ const y = new Float32Array(x.length);
1014
+ const eps = 1e-5;
1015
+ for (let r = 0; r < rows; r += 1) {
1016
+ let sum = 0;
1017
+ for (let j = 0; j < d; j += 1) sum += x[r * d + j];
1018
+ const mean = sum / d;
1019
+ let sq = 0;
1020
+ for (let j = 0; j < d; j += 1) {
1021
+ const t = x[r * d + j] - mean;
1022
+ sq += t * t;
1023
+ }
1024
+ const inv = 1 / Math.sqrt(sq / d + eps);
1025
+ for (let j = 0; j < d; j += 1) {
1026
+ const norm = (x[r * d + j] - mean) * inv;
1027
+ y[r * d + j] = norm * gamma[j] + beta[j];
1028
+ }
1029
+ }
1030
+ return y;
1031
+ }
1032
+ function geluCpu(x) {
1033
+ const y = new Float32Array(x.length);
1034
+ const k0 = 0.7978845608;
1035
+ const k1 = 0.044715;
1036
+ for (let i = 0; i < x.length; i += 1) {
1037
+ const v = x[i];
1038
+ const x3 = v * v * v;
1039
+ const inner = k0 * (v + k1 * x3);
1040
+ y[i] = 0.5 * v * (1 + Math.tanh(inner));
1041
+ }
1042
+ return y;
1043
+ }
1044
+ function mhaCpu(Q, K, V, numHeads, qLen, kLen, dHead, vHead) {
1045
+ const out = new Float32Array(numHeads * qLen * vHead);
1046
+ const scale = 1 / Math.sqrt(Math.max(dHead, 1));
1047
+ for (let h = 0; h < numHeads; h += 1) {
1048
+ for (let qi = 0; qi < qLen; qi += 1) {
1049
+ const scores = new Float32Array(kLen);
1050
+ let maxS = -Infinity;
1051
+ const qBase = (h * qLen + qi) * dHead;
1052
+ for (let ki = 0; ki < kLen; ki += 1) {
1053
+ const kBase = (h * kLen + ki) * dHead;
1054
+ let dot = 0;
1055
+ for (let d = 0; d < dHead; d += 1) dot += Q[qBase + d] * K[kBase + d];
1056
+ const s = dot * scale;
1057
+ scores[ki] = s;
1058
+ maxS = Math.max(maxS, s);
1059
+ }
1060
+ let denom = 0;
1061
+ for (let ki = 0; ki < kLen; ki += 1) {
1062
+ scores[ki] = Math.exp(scores[ki] - maxS);
1063
+ denom += scores[ki];
1064
+ }
1065
+ const oBase = (h * qLen + qi) * vHead;
1066
+ for (let vi = 0; vi < vHead; vi += 1) {
1067
+ let acc = 0;
1068
+ for (let ki = 0; ki < kLen; ki += 1) {
1069
+ acc += scores[ki] / Math.max(denom, 1e-9) * V[(h * kLen + ki) * vHead + vi];
1070
+ }
1071
+ out[oBase + vi] = acc;
1072
+ }
1073
+ }
1074
+ }
1075
+ return out;
1076
+ }
1077
+ function ropeCpuSync(q, seqLen, numHeads, headDim, posOffset) {
1078
+ const out = q.slice();
1079
+ const base = 1e4;
1080
+ for (let t = 0; t < seqLen; t += 1) {
1081
+ const pos = t + posOffset;
1082
+ for (let h = 0; h < numHeads; h += 1) {
1083
+ for (let p = 0; p < headDim / 2; p += 1) {
1084
+ const exp = -2 * (p / headDim);
1085
+ const theta = pos * base ** exp;
1086
+ const c = Math.cos(theta);
1087
+ const s = Math.sin(theta);
1088
+ const i0 = (t * numHeads + h) * headDim + 2 * p;
1089
+ const i1 = i0 + 1;
1090
+ const x0 = q[i0];
1091
+ const x1 = q[i1];
1092
+ out[i0] = x0 * c - x1 * s;
1093
+ out[i1] = x0 * s + x1 * c;
1094
+ }
1095
+ }
1096
+ }
1097
+ return out;
1098
+ }
1099
+ async function tryCreateHoloMapEncoderDevice() {
1100
+ try {
1101
+ const nav = globalThis.navigator;
1102
+ if (!nav?.gpu) return null;
1103
+ const adapter = await nav.gpu.requestAdapter();
1104
+ if (!adapter) return null;
1105
+ return await adapter.requestDevice();
1106
+ } catch {
1107
+ return null;
1108
+ }
1109
+ }
1110
+ function createHoloMapMicroEncoder(device) {
1111
+ const patchEmbed = createImagePatchEmbedKernel(device);
1112
+ const layerNorm = createLayerNormKernel(device);
1113
+ const gemm = createGemmKernel(device);
1114
+ const rope = createRopeKernel(device);
1115
+ const fusedMha = createFusedMHAKernel(device);
1116
+ const gelu = createGeluKernel(device);
1117
+ return {
1118
+ async run(frame, config) {
1119
+ const weights = getMicroWeights(config);
1120
+ const image = frameToMicroImage(frame);
1121
+ let tokens = await patchEmbed.run(image, weights.proj, {
1122
+ imgH: MICRO,
1123
+ imgW: MICRO,
1124
+ patchH: MICRO,
1125
+ patchW: MICRO,
1126
+ numChannels: 3,
1127
+ embedDim: EMBED_DIM
1128
+ });
1129
+ tokens = await layerNorm.run(tokens, weights.gamma1, weights.beta1);
1130
+ const qFlat = await gemm.run(tokens, weights.Wq, 1, EMBED_DIM, EMBED_DIM);
1131
+ const kFlat = await gemm.run(tokens, weights.Wk, 1, EMBED_DIM, EMBED_DIM);
1132
+ const vFlat = await gemm.run(tokens, weights.Wv, 1, EMBED_DIM, EMBED_DIM);
1133
+ const q3 = await rope.run(qFlat, {
1134
+ seqLen: 1,
1135
+ numHeads: NUM_HEADS,
1136
+ headDim: HEAD_DIM,
1137
+ posOffset: frame.index
1138
+ });
1139
+ const k3 = await rope.run(kFlat, {
1140
+ seqLen: 1,
1141
+ numHeads: NUM_HEADS,
1142
+ headDim: HEAD_DIM,
1143
+ posOffset: frame.index
1144
+ });
1145
+ let attn = await fusedMha.run(q3, k3, vFlat, {
1146
+ numHeads: NUM_HEADS,
1147
+ qLen: 1,
1148
+ kLen: 1,
1149
+ dHead: HEAD_DIM,
1150
+ vHead: HEAD_DIM
1151
+ });
1152
+ attn = await layerNorm.run(attn, weights.gamma2, weights.beta2);
1153
+ attn = await gelu.run(attn);
1154
+ return gemm.run(attn, weights.Wxyz, 1, EMBED_DIM, 3);
1155
+ }
1156
+ };
1157
+ }
1158
+ async function runHoloMapMicroEncoderCpu(frame, config) {
1159
+ const weights = getMicroWeights(config);
1160
+ const image = frameToMicroImage(frame);
1161
+ let tokens = patchEmbedCpu(image, weights.proj, EMBED_DIM);
1162
+ tokens = layerNormCpu(tokens, weights.gamma1, weights.beta1, 1, EMBED_DIM);
1163
+ const qFlat = gemmCpu(tokens, weights.Wq, 1, EMBED_DIM, EMBED_DIM);
1164
+ const kFlat = gemmCpu(tokens, weights.Wk, 1, EMBED_DIM, EMBED_DIM);
1165
+ const vFlat = gemmCpu(tokens, weights.Wv, 1, EMBED_DIM, EMBED_DIM);
1166
+ const qR = ropeCpuSync(qFlat, 1, NUM_HEADS, HEAD_DIM, frame.index);
1167
+ const kR = ropeCpuSync(kFlat, 1, NUM_HEADS, HEAD_DIM, frame.index);
1168
+ let attn = mhaCpu(qR, kR, vFlat, NUM_HEADS, 1, 1, HEAD_DIM, HEAD_DIM);
1169
+ attn = layerNormCpu(attn, weights.gamma2, weights.beta2, 1, EMBED_DIM);
1170
+ attn = geluCpu(attn);
1171
+ return gemmCpu(attn, weights.Wxyz, 1, EMBED_DIM, 3);
1172
+ }
1173
+
1174
+ // src/reconstruction/holoMapTelemetry.ts
1175
+ var highVolumeEvents = /* @__PURE__ */ new Set(["step", "step_throttled", "step_evict"]);
1176
+ var pendingStepSummaries = /* @__PURE__ */ new Map();
1177
+ function loggingDisabled() {
1178
+ try {
1179
+ return typeof process !== "undefined" && process.env?.HOLOMAP_LOG === "0";
1180
+ } catch {
1181
+ return false;
1182
+ }
1183
+ }
1184
+ function verboseStepLoggingEnabled() {
1185
+ try {
1186
+ if (typeof process === "undefined") {
1187
+ return false;
1188
+ }
1189
+ const value = process.env?.HOLOMAP_LOG?.toLowerCase();
1190
+ return value === "verbose" || value === "debug" || process.env?.HOLOMAP_LOG_STEPS === "1";
1191
+ } catch {
1192
+ return false;
1193
+ }
1194
+ }
1195
+ function numericDetail(detail, key) {
1196
+ const value = detail?.[key];
1197
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1198
+ }
1199
+ function getOrCreateStepSummary(runId) {
1200
+ const existing = pendingStepSummaries.get(runId);
1201
+ if (existing) {
1202
+ return existing;
1203
+ }
1204
+ const summary = {
1205
+ stepCount: 0,
1206
+ firstFrameIndex: null,
1207
+ lastFrameIndex: null,
1208
+ pointCountTotal: 0,
1209
+ totalStepMs: 0,
1210
+ maxStepMs: 0,
1211
+ minStepMs: Infinity,
1212
+ throttledCount: 0,
1213
+ evictionCount: 0,
1214
+ evictedPointTotal: 0,
1215
+ lastThrottledFrameIndex: null,
1216
+ lastEvictedFrameIndex: null,
1217
+ lastRetainedSteps: null,
1218
+ latestAvgStepMs: null,
1219
+ latestThrottledSoFar: null
1220
+ };
1221
+ pendingStepSummaries.set(runId, summary);
1222
+ return summary;
1223
+ }
1224
+ function accumulateStepSummary(runId, event, detail) {
1225
+ const summary = getOrCreateStepSummary(runId);
1226
+ const frameIndex = numericDetail(detail, "frameIndex");
1227
+ if (event === "step") {
1228
+ summary.stepCount += 1;
1229
+ if (frameIndex !== null) {
1230
+ summary.firstFrameIndex = summary.firstFrameIndex ?? frameIndex;
1231
+ summary.lastFrameIndex = frameIndex;
1232
+ }
1233
+ summary.pointCountTotal += numericDetail(detail, "pointCount") ?? 0;
1234
+ const stepMs = numericDetail(detail, "stepMs");
1235
+ if (stepMs !== null) {
1236
+ summary.totalStepMs += stepMs;
1237
+ summary.maxStepMs = Math.max(summary.maxStepMs, stepMs);
1238
+ summary.minStepMs = Math.min(summary.minStepMs, stepMs);
1239
+ }
1240
+ summary.latestAvgStepMs = numericDetail(detail, "avgStepMs") ?? summary.latestAvgStepMs;
1241
+ summary.latestThrottledSoFar = numericDetail(detail, "throttledSoFar") ?? summary.latestThrottledSoFar;
1242
+ return;
1243
+ }
1244
+ if (event === "step_throttled") {
1245
+ summary.throttledCount += 1;
1246
+ summary.lastThrottledFrameIndex = frameIndex ?? summary.lastThrottledFrameIndex;
1247
+ return;
1248
+ }
1249
+ summary.evictionCount += 1;
1250
+ summary.lastEvictedFrameIndex = frameIndex ?? summary.lastEvictedFrameIndex;
1251
+ summary.evictedPointTotal += numericDetail(detail, "evictedPoints") ?? 0;
1252
+ summary.lastRetainedSteps = numericDetail(detail, "retainedSteps") ?? summary.lastRetainedSteps;
1253
+ }
1254
+ function emitLogPayload(runId, event, detail) {
1255
+ const payload = {
1256
+ t: (/* @__PURE__ */ new Date()).toISOString(),
1257
+ runId,
1258
+ event,
1259
+ ...detail
1260
+ };
1261
+ console.log(`[HoloMap] ${JSON.stringify(payload)}`);
1262
+ }
1263
+ function flushStepSummary(runId, reason) {
1264
+ const summary = pendingStepSummaries.get(runId);
1265
+ if (!summary) {
1266
+ return;
1267
+ }
1268
+ pendingStepSummaries.delete(runId);
1269
+ const throttledCount = Math.max(summary.throttledCount, summary.latestThrottledSoFar ?? 0);
1270
+ emitLogPayload(runId, "step_summary", {
1271
+ reason,
1272
+ stepCount: summary.stepCount,
1273
+ firstFrameIndex: summary.firstFrameIndex,
1274
+ lastFrameIndex: summary.lastFrameIndex,
1275
+ pointCountTotal: summary.pointCountTotal,
1276
+ avgStepMs: summary.stepCount > 0 ? Math.round(summary.totalStepMs / summary.stepCount) : summary.latestAvgStepMs ?? 0,
1277
+ maxStepMs: summary.stepCount > 0 ? Math.round(summary.maxStepMs) : 0,
1278
+ minStepMs: summary.stepCount > 0 ? Math.round(summary.minStepMs) : 0,
1279
+ throttledCount,
1280
+ evictionCount: summary.evictionCount,
1281
+ evictedPointTotal: summary.evictedPointTotal,
1282
+ lastThrottledFrameIndex: summary.lastThrottledFrameIndex,
1283
+ lastEvictedFrameIndex: summary.lastEvictedFrameIndex,
1284
+ lastRetainedSteps: summary.lastRetainedSteps
1285
+ });
1286
+ }
1287
+ function createHoloMapRunId() {
1288
+ try {
1289
+ const c = globalThis.crypto;
1290
+ if (c && "randomUUID" in c) {
1291
+ return `hm_${c.randomUUID().slice(0, 8)}`;
1292
+ }
1293
+ } catch {
1294
+ }
1295
+ return `hm_${Math.random().toString(36).slice(2, 10)}`;
1296
+ }
1297
+ function logHoloMapEvent(runId, event, detail) {
1298
+ if (loggingDisabled()) {
1299
+ return;
1300
+ }
1301
+ if (highVolumeEvents.has(event) && !verboseStepLoggingEnabled()) {
1302
+ accumulateStepSummary(runId, event, detail);
1303
+ return;
1304
+ }
1305
+ if (event === "finalize" || event === "dispose" || event === "error") {
1306
+ flushStepSummary(runId, event);
1307
+ }
1308
+ emitLogPayload(runId, event, detail);
1309
+ }
1310
+ function resetHoloMapTelemetryForTests() {
1311
+ pendingStepSummaries.clear();
1312
+ }
1313
+
1314
+ // src/reconstruction/holoMapWeightLoader.ts
1315
+ async function sha256Digest(bytes) {
1316
+ const c = globalThis.crypto;
1317
+ if (c?.subtle) {
1318
+ const buf = await c.subtle.digest(
1319
+ "SHA-256",
1320
+ bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)
1321
+ );
1322
+ return bufferToHex(new Uint8Array(buf));
1323
+ }
1324
+ const { createHash } = await import('crypto');
1325
+ return createHash("sha256").update(bytes).digest("hex");
1326
+ }
1327
+ function bufferToHex(bytes) {
1328
+ let s = "";
1329
+ for (let i = 0; i < bytes.length; i += 1) s += bytes[i].toString(16).padStart(2, "0");
1330
+ return s;
1331
+ }
1332
+ function normalizeWeightCidDigest(cid) {
1333
+ const t = cid.trim().toLowerCase();
1334
+ if (t.startsWith("sha256:")) return t.slice("sha256:".length);
1335
+ return t;
1336
+ }
1337
+ async function assertWeightCid(bytes, weightCid) {
1338
+ const expect = normalizeWeightCidDigest(weightCid);
1339
+ if (!/^[0-9a-f]{64}$/.test(expect)) {
1340
+ throw new Error(
1341
+ `HoloMap weights: weightCid must be sha256:hex64 or 64-char hex (got "${weightCid.slice(0, 24)}...")`
1342
+ );
1343
+ }
1344
+ const actual = await sha256Digest(bytes);
1345
+ if (actual !== expect) {
1346
+ throw new Error(
1347
+ `HoloMap weights: digest mismatch \u2014 expected ${expect.slice(0, 12)}\u2026 got ${actual.slice(0, 12)}\u2026`
1348
+ );
1349
+ }
1350
+ }
1351
+ function isFileUrl(url) {
1352
+ return url.startsWith("file://");
1353
+ }
1354
+ async function readFileUrl(url) {
1355
+ const { readFile } = await import('fs/promises');
1356
+ const path = url.slice("file://".length);
1357
+ const buf = await readFile(path);
1358
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
1359
+ }
1360
+ async function sleep(ms) {
1361
+ return new Promise((r) => setTimeout(r, ms));
1362
+ }
1363
+ async function loadHoloMapWeightBlob(options) {
1364
+ const { weightUrl, weightUrls = [], weightCid, fetchImpl, skipCache, localResolver } = options;
1365
+ const f = fetchImpl ?? globalThis.fetch;
1366
+ if (localResolver && weightCid) {
1367
+ try {
1368
+ const local = await localResolver(weightCid);
1369
+ if (local) {
1370
+ await assertWeightCid(new Uint8Array(local), weightCid);
1371
+ await putCachedWeightBlob(weightCid, local);
1372
+ return { bytes: local, verified: true, source: "file" };
1373
+ }
1374
+ } catch {
1375
+ }
1376
+ }
1377
+ if (weightCid && !skipCache) {
1378
+ const cached = await getCachedWeightBlob(weightCid);
1379
+ if (cached) {
1380
+ return { bytes: cached, verified: true, source: "cache" };
1381
+ }
1382
+ }
1383
+ const candidates = [weightUrl, ...weightUrls];
1384
+ for (const url of candidates) {
1385
+ if (isFileUrl(url)) {
1386
+ try {
1387
+ const buf = await readFileUrl(url);
1388
+ if (weightCid) {
1389
+ await assertWeightCid(new Uint8Array(buf), weightCid);
1390
+ }
1391
+ if (weightCid) {
1392
+ await putCachedWeightBlob(weightCid, buf);
1393
+ }
1394
+ return { bytes: buf, verified: !!weightCid, source: "file" };
1395
+ } catch {
1396
+ }
1397
+ continue;
1398
+ }
1399
+ if (typeof f !== "function") {
1400
+ throw new Error("HoloMap weights: fetch is not available (provide fetchImpl)");
1401
+ }
1402
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1403
+ try {
1404
+ const res = await f(url);
1405
+ if (!res.ok) {
1406
+ throw new Error(`GET ${url} failed (${res.status} ${res.statusText})`);
1407
+ }
1408
+ const buf = await res.arrayBuffer();
1409
+ if (weightCid) {
1410
+ await assertWeightCid(new Uint8Array(buf), weightCid);
1411
+ }
1412
+ if (weightCid) {
1413
+ await putCachedWeightBlob(weightCid, buf);
1414
+ }
1415
+ return { bytes: buf, verified: !!weightCid, source: "network" };
1416
+ } catch (err) {
1417
+ if (attempt < 2) {
1418
+ await sleep(250 * (attempt + 1));
1419
+ } else {
1420
+ break;
1421
+ }
1422
+ }
1423
+ }
1424
+ }
1425
+ throw new Error(`HoloMap weights: all sources exhausted (tried ${candidates.length} URL(s))`);
1426
+ }
1427
+
1428
+ // src/reconstruction/holoMapWeightCache.ts
1429
+ var DB_NAME = "HoloMapWeightCache";
1430
+ var STORE_NAME = "weights";
1431
+ var DB_VERSION = 1;
1432
+ async function openIndexedDB() {
1433
+ return new Promise((resolve, reject) => {
1434
+ const req = indexedDB.open(DB_NAME, DB_VERSION);
1435
+ req.onerror = () => reject(req.error);
1436
+ req.onsuccess = () => resolve(req.result);
1437
+ req.onupgradeneeded = () => {
1438
+ req.result.createObjectStore(STORE_NAME);
1439
+ };
1440
+ });
1441
+ }
1442
+ async function idbGet(db, key) {
1443
+ return new Promise((resolve, reject) => {
1444
+ const tx = db.transaction(STORE_NAME, "readonly");
1445
+ const store = tx.objectStore(STORE_NAME);
1446
+ const req = store.get(key);
1447
+ req.onsuccess = () => {
1448
+ const result = req.result;
1449
+ if (result instanceof ArrayBuffer) resolve(result);
1450
+ else if (result && typeof result.byteLength === "number") resolve(result);
1451
+ else resolve(void 0);
1452
+ };
1453
+ req.onerror = () => reject(req.error);
1454
+ });
1455
+ }
1456
+ async function idbPut(db, key, value) {
1457
+ return new Promise((resolve, reject) => {
1458
+ const tx = db.transaction(STORE_NAME, "readwrite");
1459
+ const store = tx.objectStore(STORE_NAME);
1460
+ const req = store.put(value, key);
1461
+ req.onsuccess = () => resolve();
1462
+ req.onerror = () => reject(req.error);
1463
+ });
1464
+ }
1465
+ var nodeCacheDeps = null;
1466
+ async function getNodeCacheDeps() {
1467
+ if (nodeCacheDeps) return nodeCacheDeps;
1468
+ try {
1469
+ const fs = await import('fs/promises');
1470
+ const os = await import('os');
1471
+ const path = await import('path');
1472
+ nodeCacheDeps = {
1473
+ readFile: (p) => fs.readFile(p),
1474
+ writeFile: (p, d) => fs.writeFile(p, d),
1475
+ mkdir: (p, o) => fs.mkdir(p, o),
1476
+ stat: (p) => fs.stat(p),
1477
+ tmpdir: () => os.tmpdir(),
1478
+ homedir: () => os.homedir()
1479
+ };
1480
+ return nodeCacheDeps;
1481
+ } catch {
1482
+ return null;
1483
+ }
1484
+ }
1485
+ async function nodeCachePath(deps, cid) {
1486
+ const path = await import('path');
1487
+ const base = process.env.HOLOMAP_WEIGHT_CACHE_DIR ?? path.join(deps.homedir(), ".cache", "holoscript", "holomap-weights");
1488
+ return path.join(base, `${cid}.bin`);
1489
+ }
1490
+ async function nodeCacheGet(deps, cid) {
1491
+ const p = await nodeCachePath(deps, cid);
1492
+ try {
1493
+ const buf = await deps.readFile(p);
1494
+ const copy = new ArrayBuffer(buf.byteLength);
1495
+ new Uint8Array(copy).set(buf);
1496
+ return copy;
1497
+ } catch {
1498
+ return void 0;
1499
+ }
1500
+ }
1501
+ async function nodeCachePut(deps, cid, value) {
1502
+ const p = await nodeCachePath(deps, cid);
1503
+ const path = await import('path');
1504
+ const dir = path.dirname(p);
1505
+ await deps.mkdir(dir, { recursive: true });
1506
+ await deps.writeFile(p, new Uint8Array(value));
1507
+ }
1508
+ function isIndexedDBAvailable() {
1509
+ return typeof indexedDB !== "undefined";
1510
+ }
1511
+ async function getCachedWeightBlob(weightCid) {
1512
+ const cid = normalizeWeightCidDigest(weightCid);
1513
+ if (isIndexedDBAvailable()) {
1514
+ try {
1515
+ const db = await openIndexedDB();
1516
+ const cached = await idbGet(db, cid);
1517
+ db.close();
1518
+ return cached;
1519
+ } catch {
1520
+ }
1521
+ }
1522
+ const deps = await getNodeCacheDeps();
1523
+ if (deps) {
1524
+ return nodeCacheGet(deps, cid);
1525
+ }
1526
+ return void 0;
1527
+ }
1528
+ async function putCachedWeightBlob(weightCid, value) {
1529
+ const cid = normalizeWeightCidDigest(weightCid);
1530
+ if (isIndexedDBAvailable()) {
1531
+ try {
1532
+ const db = await openIndexedDB();
1533
+ await idbPut(db, cid, value);
1534
+ db.close();
1535
+ return;
1536
+ } catch {
1537
+ }
1538
+ }
1539
+ const deps = await getNodeCacheDeps();
1540
+ if (deps) {
1541
+ await nodeCachePut(deps, cid, value);
1542
+ }
1543
+ }
1544
+
1545
+ // src/reconstruction/holoMapAnchoredManifest.ts
1546
+ function mergeAnchoredProvenance(manifest, urls) {
1547
+ return {
1548
+ ...manifest,
1549
+ provenance: {
1550
+ ...manifest.provenance,
1551
+ anchorHash: urls.anchorHash,
1552
+ opentimestampsProof: urls.opentimestampsProof,
1553
+ baseCalldataTx: urls.baseCalldataTx,
1554
+ capturedAtIso: manifest.provenance.capturedAtIso
1555
+ }
1556
+ };
1557
+ }
1558
+ function stripAnchoredProvenance(manifest) {
1559
+ return {
1560
+ ...manifest,
1561
+ provenance: {
1562
+ capturedAtIso: manifest.provenance.capturedAtIso
1563
+ }
1564
+ };
1565
+ }
1566
+ function selfAttestReconstructionManifest(manifest) {
1567
+ const stripped = stripAnchoredProvenance(manifest);
1568
+ return {
1569
+ ...stripped,
1570
+ provenance: {
1571
+ ...stripped.provenance,
1572
+ anchorHash: `self-attested:${stripped.replayHash}`
1573
+ }
1574
+ };
1575
+ }
1576
+ async function computeReconstructionManifestDigest(manifest) {
1577
+ const canonical = stableStringify(stripAnchoredProvenance(manifest));
1578
+ return `sha256:${await sha256Hex(canonical)}`;
1579
+ }
1580
+ async function anchorReconstructionManifest(manifest, provider) {
1581
+ const stripped = stripAnchoredProvenance(manifest);
1582
+ if (!provider) return selfAttestReconstructionManifest(stripped);
1583
+ const manifestDigest = await computeReconstructionManifestDigest(stripped);
1584
+ const urls = await provider.anchorManifest({
1585
+ manifestDigest,
1586
+ replayHash: stripped.replayHash,
1587
+ replayFingerprint: stripped.simulationContract.replayFingerprint,
1588
+ capturedAtIso: stripped.provenance.capturedAtIso,
1589
+ manifest: stripped
1590
+ });
1591
+ if (!urls) return selfAttestReconstructionManifest(stripped);
1592
+ return mergeAnchoredProvenance(stripped, urls);
1593
+ }
1594
+ function stableStringify(value) {
1595
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
1596
+ if (Array.isArray(value)) return `[${value.map((item) => stableStringify(item)).join(",")}]`;
1597
+ const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([a], [b]) => a.localeCompare(b));
1598
+ return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`).join(",")}}`;
1599
+ }
1600
+ async function sha256Hex(input) {
1601
+ const bytes = new TextEncoder().encode(input);
1602
+ const subtle = globalThis.crypto?.subtle;
1603
+ if (subtle) {
1604
+ const digest = await subtle.digest("SHA-256", bytes);
1605
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1606
+ }
1607
+ const { createHash } = await import('crypto');
1608
+ return createHash("sha256").update(bytes).digest("hex");
1609
+ }
1610
+
1611
+ // src/reconstruction/pagedKVKernels.ts
1612
+ var WGSL_PAGED_KV_APPEND = `
1613
+ struct AppendParams {
1614
+ numVecs: u32, // number of NEW vectors to append
1615
+ headDim: u32, // dimension of each vector
1616
+ pageSize: u32, // vectors per page
1617
+ }
1618
+ // kv_pages: flat storage for all pages. page p, slot s, dim d:
1619
+ // index = pageTable[p] * pageSize * headDim + s * headDim + d
1620
+ @group(0) @binding(0) var<storage, read> newVecs: array<f32>; // [numVecs * headDim]
1621
+ @group(0) @binding(1) var<storage, read> pageTable: array<u32>; // logical page \u2192 base offset in kv_pages (in elements)
1622
+ @group(0) @binding(2) var<storage, read> slotMap: array<u32>; // per-vector: (logicalPage << 16 | slotInPage)
1623
+ @group(0) @binding(3) var<storage, read_write> kvPages: array<f32>; // output KV store
1624
+ @group(0) @binding(4) var<uniform> p: AppendParams;
1625
+ @compute @workgroup_size(64, 1, 1)
1626
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
1627
+ // gid.x spans numVecs * headDim
1628
+ let total = p.numVecs * p.headDim;
1629
+ if (gid.x >= total) { return; }
1630
+ let vecIdx = gid.x / p.headDim;
1631
+ let dimIdx = gid.x % p.headDim;
1632
+ let slot = slotMap[vecIdx];
1633
+ let logPage = slot >> 16u;
1634
+ let inPage = slot & 0xFFFFu;
1635
+ let base = pageTable[logPage] * p.pageSize * p.headDim;
1636
+ kvPages[base + inPage * p.headDim + dimIdx] = newVecs[vecIdx * p.headDim + dimIdx];
1637
+ }
1638
+ `;
1639
+ var WGSL_PAGED_KV_LOOKUP = `
1640
+ struct LookupParams {
1641
+ numVecs: u32, // number of vectors to read
1642
+ headDim: u32,
1643
+ pageSize: u32,
1644
+ startSlot: u32, // logical first slot (token offset)
1645
+ }
1646
+ @group(0) @binding(0) var<storage, read> kvPages: array<f32>;
1647
+ @group(0) @binding(1) var<storage, read> pageTable: array<u32>; // logical page \u2192 base offset (in elements)
1648
+ @group(0) @binding(2) var<storage, read_write> out: array<f32>; // [numVecs * headDim]
1649
+ @group(0) @binding(3) var<uniform> p: LookupParams;
1650
+ @compute @workgroup_size(64, 1, 1)
1651
+ fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
1652
+ let total = p.numVecs * p.headDim;
1653
+ if (gid.x >= total) { return; }
1654
+ let vecIdx = gid.x / p.headDim;
1655
+ let dimIdx = gid.x % p.headDim;
1656
+ let logSlot = (vecIdx + p.startSlot);
1657
+ let logPage = logSlot / p.pageSize;
1658
+ let inPage = logSlot % p.pageSize;
1659
+ let base = pageTable[logPage] * p.pageSize * p.headDim;
1660
+ out[gid.x] = kvPages[base + inPage * p.headDim + dimIdx];
1661
+ }
1662
+ `;
1663
+ function storageBuffer4(device, data) {
1664
+ const buf = device.createBuffer({
1665
+ size: data.byteLength,
1666
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
1667
+ });
1668
+ device.queue.writeBuffer(buf, 0, data.buffer, data.byteOffset, data.byteLength);
1669
+ return buf;
1670
+ }
1671
+ function outputBuffer(device, bytes) {
1672
+ return device.createBuffer({
1673
+ size: bytes,
1674
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
1675
+ });
1676
+ }
1677
+ function uniformBuffer(device, data) {
1678
+ const buf = device.createBuffer({
1679
+ size: data.byteLength,
1680
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
1681
+ });
1682
+ device.queue.writeBuffer(buf, 0, data);
1683
+ return buf;
1684
+ }
1685
+ async function readback(device, src, bytes) {
1686
+ const staging = device.createBuffer({
1687
+ size: bytes,
1688
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
1689
+ });
1690
+ const enc = device.createCommandEncoder();
1691
+ enc.copyBufferToBuffer(src, 0, staging, 0, bytes);
1692
+ device.queue.submit([enc.finish()]);
1693
+ await staging.mapAsync(GPUMapMode.READ);
1694
+ const result = new Float32Array(staging.getMappedRange().slice(0));
1695
+ staging.unmap();
1696
+ staging.destroy();
1697
+ return result;
1698
+ }
1699
+ function createPagedKVAppendKernel(device) {
1700
+ const shader = device.createShaderModule({ code: WGSL_PAGED_KV_APPEND });
1701
+ const pipeline = device.createComputePipeline({
1702
+ layout: "auto",
1703
+ compute: { module: shader, entryPoint: "main" }
1704
+ });
1705
+ return {
1706
+ async run(kvPagesData, pageTableData, slotMapData, newVectors, pageSize, headDim) {
1707
+ const numVecs = newVectors.length / headDim;
1708
+ const kvBuf = device.createBuffer({
1709
+ size: kvPagesData.byteLength,
1710
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC
1711
+ });
1712
+ device.queue.writeBuffer(
1713
+ kvBuf,
1714
+ 0,
1715
+ kvPagesData.buffer,
1716
+ kvPagesData.byteOffset,
1717
+ kvPagesData.byteLength
1718
+ );
1719
+ const newVecsBuf = storageBuffer4(device, newVectors);
1720
+ const pageTableBuf = storageBuffer4(device, pageTableData);
1721
+ const slotMapBuf = storageBuffer4(device, slotMapData);
1722
+ const paramAB = new ArrayBuffer(16);
1723
+ new Uint32Array(paramAB)[0] = numVecs;
1724
+ new Uint32Array(paramAB)[1] = headDim;
1725
+ new Uint32Array(paramAB)[2] = pageSize;
1726
+ const paramsBuf = uniformBuffer(device, paramAB);
1727
+ const bg = device.createBindGroup({
1728
+ layout: pipeline.getBindGroupLayout(0),
1729
+ entries: [
1730
+ { binding: 0, resource: { buffer: newVecsBuf } },
1731
+ { binding: 1, resource: { buffer: pageTableBuf } },
1732
+ { binding: 2, resource: { buffer: slotMapBuf } },
1733
+ { binding: 3, resource: { buffer: kvBuf } },
1734
+ { binding: 4, resource: { buffer: paramsBuf } }
1735
+ ]
1736
+ });
1737
+ const total = numVecs * headDim;
1738
+ const workgroups = Math.ceil(total / 64);
1739
+ const enc = device.createCommandEncoder();
1740
+ const pass = enc.beginComputePass();
1741
+ pass.setPipeline(pipeline);
1742
+ pass.setBindGroup(0, bg);
1743
+ pass.dispatchWorkgroups(workgroups);
1744
+ pass.end();
1745
+ device.queue.submit([enc.finish()]);
1746
+ const result = await readback(device, kvBuf, kvPagesData.byteLength);
1747
+ newVecsBuf.destroy();
1748
+ pageTableBuf.destroy();
1749
+ slotMapBuf.destroy();
1750
+ kvBuf.destroy();
1751
+ paramsBuf.destroy();
1752
+ return result;
1753
+ }
1754
+ };
1755
+ }
1756
+ function createPagedKVLookupKernel(device) {
1757
+ const shader = device.createShaderModule({ code: WGSL_PAGED_KV_LOOKUP });
1758
+ const pipeline = device.createComputePipeline({
1759
+ layout: "auto",
1760
+ compute: { module: shader, entryPoint: "main" }
1761
+ });
1762
+ return {
1763
+ async run(kvPagesData, pageTableData, numVecs, startSlot, pageSize, headDim) {
1764
+ const outBytes = numVecs * headDim * 4;
1765
+ const kvBuf = storageBuffer4(device, kvPagesData);
1766
+ const pageTableBuf = storageBuffer4(device, pageTableData);
1767
+ const outBuf = outputBuffer(device, outBytes);
1768
+ const paramAB = new ArrayBuffer(16);
1769
+ const pu = new Uint32Array(paramAB);
1770
+ pu[0] = numVecs;
1771
+ pu[1] = headDim;
1772
+ pu[2] = pageSize;
1773
+ pu[3] = startSlot;
1774
+ const paramsBuf = uniformBuffer(device, paramAB);
1775
+ const bg = device.createBindGroup({
1776
+ layout: pipeline.getBindGroupLayout(0),
1777
+ entries: [
1778
+ { binding: 0, resource: { buffer: kvBuf } },
1779
+ { binding: 1, resource: { buffer: pageTableBuf } },
1780
+ { binding: 2, resource: { buffer: outBuf } },
1781
+ { binding: 3, resource: { buffer: paramsBuf } }
1782
+ ]
1783
+ });
1784
+ const total = numVecs * headDim;
1785
+ const enc = device.createCommandEncoder();
1786
+ const pass = enc.beginComputePass();
1787
+ pass.setPipeline(pipeline);
1788
+ pass.setBindGroup(0, bg);
1789
+ pass.dispatchWorkgroups(Math.ceil(total / 64));
1790
+ pass.end();
1791
+ device.queue.submit([enc.finish()]);
1792
+ const result = await readback(device, outBuf, outBytes);
1793
+ kvBuf.destroy();
1794
+ pageTableBuf.destroy();
1795
+ outBuf.destroy();
1796
+ paramsBuf.destroy();
1797
+ return result;
1798
+ }
1799
+ };
1800
+ }
1801
+
1802
+ // src/reconstruction/PagedKVCache.ts
1803
+ var PAGE_TABLE_EMPTY = 4294967295;
1804
+ function createPagedKVCache(config) {
1805
+ validateConfig(config);
1806
+ const layers = Array.from({ length: config.numLayers }, () => ({
1807
+ pages: /* @__PURE__ */ new Map(),
1808
+ nextToken: 0
1809
+ }));
1810
+ const pageBytes = config.pageSize * config.hiddenDim * Float32Array.BYTES_PER_ELEMENT;
1811
+ const keyPages = new Float32Array(config.maxResidentPages * config.pageSize * config.hiddenDim);
1812
+ const valuePages = new Float32Array(config.maxResidentPages * config.pageSize * config.hiddenDim);
1813
+ const freePhysicalPages = Array.from({ length: config.maxResidentPages }, (_, i) => i).reverse();
1814
+ const appendKernel = config.device ? createPagedKVAppendKernel(config.device) : null;
1815
+ const lookupKernel = config.device ? createPagedKVLookupKernel(config.device) : null;
1816
+ const residentPageStates = /* @__PURE__ */ new Set();
1817
+ let disposed = false;
1818
+ let nextPageId = 0;
1819
+ let accessClock = 0;
1820
+ let devicePageCount = 0;
1821
+ let hostPageCount = 0;
1822
+ function assertLive() {
1823
+ if (disposed) throw new Error("PagedKVCache has been disposed.");
1824
+ }
1825
+ function validateLayer(layer) {
1826
+ if (!Number.isInteger(layer) || layer < 0 || layer >= config.numLayers) {
1827
+ throw new Error(`PagedKVCache layer ${layer} out of range 0..${config.numLayers - 1}`);
1828
+ }
1829
+ }
1830
+ function pageSnapshot(page) {
1831
+ return {
1832
+ pageId: page.pageId,
1833
+ layer: page.layer,
1834
+ residency: page.residency,
1835
+ firstToken: page.firstToken,
1836
+ lastToken: page.lastToken
1837
+ };
1838
+ }
1839
+ function pageOffset(physicalPage) {
1840
+ return physicalPage * config.pageSize * config.hiddenDim;
1841
+ }
1842
+ function copyResidentPage(page, keys, values) {
1843
+ if (page.physicalPage === null) return;
1844
+ const offset = pageOffset(page.physicalPage);
1845
+ const end = offset + config.pageSize * config.hiddenDim;
1846
+ keys.set(keyPages.subarray(offset, end));
1847
+ values.set(valuePages.subarray(offset, end));
1848
+ }
1849
+ function zeroPhysicalPage(physicalPage) {
1850
+ const offset = pageOffset(physicalPage);
1851
+ const end = offset + config.pageSize * config.hiddenDim;
1852
+ keyPages.fill(0, offset, end);
1853
+ valuePages.fill(0, offset, end);
1854
+ }
1855
+ function residentPages() {
1856
+ return Array.from(residentPageStates);
1857
+ }
1858
+ function evictPage(page) {
1859
+ if (page.physicalPage === null) return pageSnapshot(page);
1860
+ const hostKeys = new Float32Array(config.pageSize * config.hiddenDim);
1861
+ const hostValues = new Float32Array(config.pageSize * config.hiddenDim);
1862
+ copyResidentPage(page, hostKeys, hostValues);
1863
+ zeroPhysicalPage(page.physicalPage);
1864
+ freePhysicalPages.push(page.physicalPage);
1865
+ page.physicalPage = null;
1866
+ page.hostKeys = hostKeys;
1867
+ page.hostValues = hostValues;
1868
+ page.residency = "host";
1869
+ residentPageStates.delete(page);
1870
+ devicePageCount -= 1;
1871
+ hostPageCount += 1;
1872
+ return pageSnapshot(page);
1873
+ }
1874
+ function evictOldestInternal(count, protectedPage) {
1875
+ if (count <= 0) return [];
1876
+ const victims = residentPages().filter((page) => page !== protectedPage).sort((a, b) => a.lastAccess - b.lastAccess).slice(0, count);
1877
+ return victims.map((page) => evictPage(page));
1878
+ }
1879
+ function allocatePhysicalPage(protectedPage) {
1880
+ if (freePhysicalPages.length === 0) {
1881
+ evictOldestInternal(1, protectedPage);
1882
+ }
1883
+ const physicalPage = freePhysicalPages.pop();
1884
+ if (physicalPage === void 0) {
1885
+ throw new Error("PagedKVCache could not allocate a resident page.");
1886
+ }
1887
+ zeroPhysicalPage(physicalPage);
1888
+ return physicalPage;
1889
+ }
1890
+ function ensurePageResident(page) {
1891
+ if (page.residency === "device" && page.physicalPage !== null) {
1892
+ page.lastAccess = ++accessClock;
1893
+ return;
1894
+ }
1895
+ const physicalPage = allocatePhysicalPage(page);
1896
+ page.physicalPage = physicalPage;
1897
+ if (page.residency === "host") hostPageCount -= 1;
1898
+ page.residency = "device";
1899
+ residentPageStates.add(page);
1900
+ devicePageCount += 1;
1901
+ page.lastAccess = ++accessClock;
1902
+ if (page.hostKeys) {
1903
+ keyPages.set(page.hostKeys, pageOffset(physicalPage));
1904
+ page.hostKeys = void 0;
1905
+ }
1906
+ if (page.hostValues) {
1907
+ valuePages.set(page.hostValues, pageOffset(physicalPage));
1908
+ page.hostValues = void 0;
1909
+ }
1910
+ }
1911
+ function allocatePageInternal(layer, logicalPage) {
1912
+ const layerState = layers[layer];
1913
+ const existing = layerState.pages.get(logicalPage);
1914
+ if (existing) {
1915
+ ensurePageResident(existing);
1916
+ return existing;
1917
+ }
1918
+ const physicalPage = allocatePhysicalPage();
1919
+ const page = {
1920
+ pageId: nextPageId++,
1921
+ logicalPage,
1922
+ layer,
1923
+ residency: "device",
1924
+ firstToken: logicalPage * config.pageSize,
1925
+ lastToken: logicalPage * config.pageSize + config.pageSize - 1,
1926
+ physicalPage,
1927
+ lastAccess: ++accessClock
1928
+ };
1929
+ layerState.pages.set(logicalPage, page);
1930
+ residentPageStates.add(page);
1931
+ devicePageCount += 1;
1932
+ return page;
1933
+ }
1934
+ function buildPageTable(layer, logicalPages) {
1935
+ const requiredPages = Array.from(new Set(logicalPages)).sort((a, b) => a - b);
1936
+ const maxLogicalPage = requiredPages[requiredPages.length - 1] ?? 0;
1937
+ const pageTable = new Uint32Array(maxLogicalPage + 1);
1938
+ pageTable.fill(PAGE_TABLE_EMPTY);
1939
+ const layerState = layers[layer];
1940
+ for (const logicalPage of requiredPages) {
1941
+ const page = layerState.pages.get(logicalPage);
1942
+ if (page) {
1943
+ ensurePageResident(page);
1944
+ pageTable[logicalPage] = page.physicalPage ?? PAGE_TABLE_EMPTY;
1945
+ }
1946
+ }
1947
+ return pageTable;
1948
+ }
1949
+ function appendCpuInPlace(pages, touchedPages, vectors, startToken) {
1950
+ const numVecs = vectors.length / config.hiddenDim;
1951
+ for (let v = 0; v < numVecs; v += 1) {
1952
+ const token = startToken + v;
1953
+ const logicalPage = Math.floor(token / config.pageSize);
1954
+ const inPage = token % config.pageSize;
1955
+ const physicalPage = touchedPages.get(logicalPage)?.physicalPage;
1956
+ if (physicalPage === void 0 || physicalPage === null) {
1957
+ throw new Error(`PagedKVCache append misses resident logical page ${logicalPage}.`);
1958
+ }
1959
+ const base = physicalPage * config.pageSize * config.hiddenDim;
1960
+ for (let d = 0; d < config.hiddenDim; d += 1) {
1961
+ pages[base + inPage * config.hiddenDim + d] = vectors[v * config.hiddenDim + d];
1962
+ }
1963
+ }
1964
+ }
1965
+ function lookupCpuDirect(pages, layerState, tokenCount, startToken) {
1966
+ const out = new Float32Array(tokenCount * config.hiddenDim);
1967
+ for (let v = 0; v < tokenCount; v += 1) {
1968
+ const logicalSlot = startToken + v;
1969
+ const logicalPage = Math.floor(logicalSlot / config.pageSize);
1970
+ const inPage = logicalSlot % config.pageSize;
1971
+ const physicalPage = layerState.pages.get(logicalPage)?.physicalPage;
1972
+ if (physicalPage === void 0 || physicalPage === null) {
1973
+ throw new Error(`PagedKVCache lookup misses resident logical page ${logicalPage}.`);
1974
+ }
1975
+ const base = physicalPage * config.pageSize * config.hiddenDim;
1976
+ for (let d = 0; d < config.hiddenDim; d += 1) {
1977
+ out[v * config.hiddenDim + d] = pages[base + inPage * config.hiddenDim + d];
1978
+ }
1979
+ }
1980
+ return out;
1981
+ }
1982
+ return {
1983
+ config,
1984
+ allocatePage(layer, firstToken) {
1985
+ assertLive();
1986
+ validateLayer(layer);
1987
+ if (!Number.isInteger(firstToken) || firstToken < 0) {
1988
+ throw new Error(`PagedKVCache firstToken must be a non-negative integer, got ${firstToken}`);
1989
+ }
1990
+ const logicalPage = Math.floor(firstToken / config.pageSize);
1991
+ return pageSnapshot(allocatePageInternal(layer, logicalPage));
1992
+ },
1993
+ residentPagesForLayer(layer) {
1994
+ assertLive();
1995
+ validateLayer(layer);
1996
+ return Array.from(residentPageStates).filter((page) => page.layer === layer).sort((a, b) => a.firstToken - b.firstToken).map((page) => pageSnapshot(page));
1997
+ },
1998
+ evictOldest(count) {
1999
+ assertLive();
2000
+ if (!Number.isInteger(count) || count < 0) {
2001
+ throw new Error(`PagedKVCache evict count must be a non-negative integer, got ${count}`);
2002
+ }
2003
+ return evictOldestInternal(count);
2004
+ },
2005
+ memoryUsage() {
2006
+ assertLive();
2007
+ return {
2008
+ device: devicePageCount * pageBytes * 2,
2009
+ host: hostPageCount * pageBytes * 2
2010
+ };
2011
+ },
2012
+ async dispose() {
2013
+ disposed = true;
2014
+ for (const layer of layers) layer.pages.clear();
2015
+ residentPageStates.clear();
2016
+ freePhysicalPages.length = 0;
2017
+ devicePageCount = 0;
2018
+ hostPageCount = 0;
2019
+ },
2020
+ async append(layer, keyVectors, valueVectors = keyVectors, firstToken) {
2021
+ assertLive();
2022
+ validateLayer(layer);
2023
+ if (keyVectors.length !== valueVectors.length) {
2024
+ throw new Error("PagedKVCache append requires key/value vectors with equal length.");
2025
+ }
2026
+ if (keyVectors.length % config.hiddenDim !== 0) {
2027
+ throw new Error(
2028
+ `PagedKVCache append vector length ${keyVectors.length} is not divisible by hiddenDim=${config.hiddenDim}`
2029
+ );
2030
+ }
2031
+ const tokenCount = keyVectors.length / config.hiddenDim;
2032
+ if (tokenCount === 0) return [];
2033
+ const startToken = firstToken ?? layers[layer].nextToken;
2034
+ if (!Number.isInteger(startToken) || startToken < 0) {
2035
+ throw new Error(`PagedKVCache append firstToken must be non-negative, got ${startToken}`);
2036
+ }
2037
+ const touchedPages = /* @__PURE__ */ new Map();
2038
+ const requiredPages = /* @__PURE__ */ new Set();
2039
+ for (let i = 0; i < tokenCount; i += 1) {
2040
+ const token = startToken + i;
2041
+ const logicalPage = Math.floor(token / config.pageSize);
2042
+ requiredPages.add(logicalPage);
2043
+ }
2044
+ if (requiredPages.size > config.maxResidentPages) {
2045
+ throw new Error(
2046
+ `PagedKVCache append touches ${requiredPages.size} pages but only ${config.maxResidentPages} can be resident.`
2047
+ );
2048
+ }
2049
+ for (const logicalPage of requiredPages) {
2050
+ const page = allocatePageInternal(layer, logicalPage);
2051
+ touchedPages.set(logicalPage, page);
2052
+ }
2053
+ if (appendKernel) {
2054
+ const slotMap = new Uint32Array(tokenCount);
2055
+ for (let i = 0; i < tokenCount; i += 1) {
2056
+ const token = startToken + i;
2057
+ const logicalPage = Math.floor(token / config.pageSize);
2058
+ const slot = token % config.pageSize;
2059
+ slotMap[i] = logicalPage << 16 | slot;
2060
+ }
2061
+ const pageTable = buildPageTable(layer, touchedPages.keys());
2062
+ keyPages.set(
2063
+ await appendKernel.run(
2064
+ keyPages,
2065
+ pageTable,
2066
+ slotMap,
2067
+ keyVectors,
2068
+ config.pageSize,
2069
+ config.hiddenDim
2070
+ )
2071
+ );
2072
+ valuePages.set(
2073
+ await appendKernel.run(
2074
+ valuePages,
2075
+ pageTable,
2076
+ slotMap,
2077
+ valueVectors,
2078
+ config.pageSize,
2079
+ config.hiddenDim
2080
+ )
2081
+ );
2082
+ } else {
2083
+ appendCpuInPlace(keyPages, touchedPages, keyVectors, startToken);
2084
+ appendCpuInPlace(valuePages, touchedPages, valueVectors, startToken);
2085
+ }
2086
+ for (const page of touchedPages.values()) page.lastAccess = ++accessClock;
2087
+ layers[layer].nextToken = Math.max(layers[layer].nextToken, startToken + tokenCount);
2088
+ return Array.from(touchedPages.values()).sort((a, b) => a.firstToken - b.firstToken).map((page) => pageSnapshot(page));
2089
+ },
2090
+ async lookup(layer, startToken, tokenCount) {
2091
+ assertLive();
2092
+ validateLayer(layer);
2093
+ if (!Number.isInteger(startToken) || startToken < 0) {
2094
+ throw new Error(`PagedKVCache lookup startToken must be non-negative, got ${startToken}`);
2095
+ }
2096
+ if (!Number.isInteger(tokenCount) || tokenCount < 0) {
2097
+ throw new Error(`PagedKVCache lookup tokenCount must be non-negative, got ${tokenCount}`);
2098
+ }
2099
+ if (tokenCount === 0) {
2100
+ return { keys: new Float32Array(), values: new Float32Array() };
2101
+ }
2102
+ const endToken = startToken + tokenCount - 1;
2103
+ const firstLogicalPage = Math.floor(startToken / config.pageSize);
2104
+ const maxLogicalPage = Math.floor(endToken / config.pageSize);
2105
+ const requiredPages = [];
2106
+ for (let logicalPage = firstLogicalPage; logicalPage <= maxLogicalPage; logicalPage += 1) {
2107
+ const page = layers[layer].pages.get(logicalPage);
2108
+ if (!page) {
2109
+ throw new Error(`PagedKVCache lookup misses layer ${layer} logical page ${logicalPage}.`);
2110
+ }
2111
+ requiredPages.push(logicalPage);
2112
+ }
2113
+ if (requiredPages.length > config.maxResidentPages) {
2114
+ throw new Error(
2115
+ `PagedKVCache lookup needs ${requiredPages.length} pages but only ${config.maxResidentPages} can be resident.`
2116
+ );
2117
+ }
2118
+ for (const logicalPage of requiredPages) {
2119
+ ensurePageResident(layers[layer].pages.get(logicalPage));
2120
+ }
2121
+ if (lookupKernel) {
2122
+ const pageTable = buildPageTable(layer, requiredPages);
2123
+ return {
2124
+ keys: await lookupKernel.run(
2125
+ keyPages,
2126
+ pageTable,
2127
+ tokenCount,
2128
+ startToken,
2129
+ config.pageSize,
2130
+ config.hiddenDim
2131
+ ),
2132
+ values: await lookupKernel.run(
2133
+ valuePages,
2134
+ pageTable,
2135
+ tokenCount,
2136
+ startToken,
2137
+ config.pageSize,
2138
+ config.hiddenDim
2139
+ )
2140
+ };
2141
+ }
2142
+ return {
2143
+ keys: lookupCpuDirect(keyPages, layers[layer], tokenCount, startToken),
2144
+ values: lookupCpuDirect(valuePages, layers[layer], tokenCount, startToken)
2145
+ };
2146
+ }
2147
+ };
2148
+ }
2149
+ function validateConfig(config) {
2150
+ const fields = [
2151
+ ["pageSize", config.pageSize],
2152
+ ["maxResidentPages", config.maxResidentPages],
2153
+ ["hiddenDim", config.hiddenDim],
2154
+ ["numHeads", config.numHeads],
2155
+ ["numLayers", config.numLayers]
2156
+ ];
2157
+ for (const [field, value] of fields) {
2158
+ if (!Number.isInteger(value) || value <= 0) {
2159
+ throw new Error(`PagedKVCache config ${String(field)} must be a positive integer.`);
2160
+ }
2161
+ }
2162
+ if (config.pageSize > 65535) {
2163
+ throw new Error("PagedKVCache pageSize must fit the 16-bit slot packing used by WGSL kernels.");
2164
+ }
2165
+ }
2166
+
2167
+ // src/reconstruction/HoloMapRuntime.ts
2168
+ var HOLOMAP_DEFAULTS = {
2169
+ inputResolution: { width: 518, height: 378 },
2170
+ targetFPS: 15,
2171
+ maxSequenceLength: 1e4,
2172
+ seed: 0,
2173
+ modelHash: "unset",
2174
+ tileGrid: 4,
2175
+ cpuOffload: false,
2176
+ weightStrategy: "distill",
2177
+ reconstructionMode: "preview",
2178
+ allowCpuFallback: true
2179
+ };
2180
+ var HOLOMAP_DENSE_PROOF_DEFAULTS = {
2181
+ ...HOLOMAP_DEFAULTS,
2182
+ tileGrid: 16,
2183
+ reconstructionMode: "dense-proof"
2184
+ };
2185
+ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
2186
+ constructor() {
2187
+ this.config = { ...HOLOMAP_DEFAULTS };
2188
+ this.initialized = false;
2189
+ this.steps = [];
2190
+ this.replayKey = "unset";
2191
+ this.runId = createHoloMapRunId();
2192
+ this.encoderDevice = null;
2193
+ this.microEncoder = null;
2194
+ /** Loaded weight blob (optional; GPU upload wiring follows R3+). */
2195
+ this.weightBytes = null;
2196
+ this.weightVerified = false;
2197
+ this.weightSource = null;
2198
+ this.streamingKVCache = null;
2199
+ this.streamingKVNextToken = 0;
2200
+ // ── Sprint-3 performance / determinism state ──
2201
+ /** Running bounding box (updated incrementally per step). */
2202
+ this.boundsMin = [0, 0, 0];
2203
+ this.boundsMax = [0, 0, 0];
2204
+ this.boundsValid = false;
2205
+ /** Running total point count (avoids O(n) re-scan in finalize). */
2206
+ this.totalPointCount = 0;
2207
+ // ── Scan-derived trajectory / drift state ──
2208
+ // Anchor pose, trajectory keyframes, and drift are derived from the scan
2209
+ // geometry rather than emitted as constants. Drift is an uncertainty-weighted
2210
+ // inter-frame pose delta accumulated over the capture.
2211
+ /** Accumulated drift estimate (meters) over the capture. */
2212
+ this.cumulativeDriftMeters = 0;
2213
+ /**
2214
+ * Running sum of every observed point position + confidence (eviction-adjusted).
2215
+ * The anchor pose is the centroid (sum/count) of all observed points and the
2216
+ * descriptor carries the global mean confidence, so a single tampered point
2217
+ * moves the anchor — bounds center/extent alone are insensitive to a one-point
2218
+ * nudge that does not touch an extremum. (E4 acceptance bar.)
2219
+ */
2220
+ this.globalPosSum = [0, 0, 0];
2221
+ this.globalConfSum = 0;
2222
+ this.globalPrincipalMomentXX = 0;
2223
+ this.globalPrincipalMomentZZ = 0;
2224
+ this.globalPrincipalMomentXZ = 0;
2225
+ /** Previous frame camera pose (centroid of that frame's points). */
2226
+ this.prevPose = null;
2227
+ /** Previous full camera pose, including scan-derived principal-axis rotation. */
2228
+ this.prevCameraPose = null;
2229
+ /** Previous inter-frame velocity, for constant-velocity drift prediction. */
2230
+ this.prevVelocity = null;
2231
+ /** Frame index of the last detected loop closure (−1 if none). */
2232
+ this.lastLoopClosureFrameIdx = -1;
2233
+ /** Compact camera-pose history for loop-closure (revisit) detection. */
2234
+ this.poseHistory = [];
2235
+ /** Compact trajectory snapshot emitted with every accepted step. */
2236
+ this.trajectoryKeyframes = [];
2237
+ /** Last accepted capture timestamp (ms) for deterministic FPS throttling. */
2238
+ this.lastAcceptedFrameTimestampMs = null;
2239
+ /** Deterministic session start timestamp. */
2240
+ this.sessionStartMs = 0;
2241
+ /** Performance metrics accumulated across steps. */
2242
+ this.perfMetrics = { stepCount: 0, throttledCount: 0, totalStepMs: 0, maxStepMs: 0, minStepMs: Infinity };
2243
+ }
2244
+ /**
2245
+ * Nearest-neighbour sample of a row-major depth map at a normalized UV.
2246
+ * Used to read MEASURED sensor depth at a tile centre (frame.depth). Nearest
2247
+ * (not bilinear) keeps it deterministic and cheap; tiles are small.
2248
+ */
2249
+ static sampleDepthNearestUv(depth, width, height, uv) {
2250
+ const px = Math.min(width - 1, Math.max(0, Math.round(uv[0] * (width - 1))));
2251
+ const py = Math.min(height - 1, Math.max(0, Math.round(uv[1] * (height - 1))));
2252
+ const v = depth[py * width + px];
2253
+ return Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0.5;
2254
+ }
2255
+ static sampleMetricDepthNearestUv(depthMeters, width, height, uv) {
2256
+ const px = Math.min(width - 1, Math.max(0, Math.round(uv[0] * (width - 1))));
2257
+ const py = Math.min(height - 1, Math.max(0, Math.round(uv[1] * (height - 1))));
2258
+ const v = depthMeters[py * width + px];
2259
+ return Number.isFinite(v) && v > 0 ? v : void 0;
2260
+ }
2261
+ static hasUsableIntrinsics(intrinsics) {
2262
+ return !!intrinsics && Number.isFinite(intrinsics.width) && Number.isFinite(intrinsics.height) && Number.isFinite(intrinsics.fx) && Number.isFinite(intrinsics.fy) && Number.isFinite(intrinsics.cx) && Number.isFinite(intrinsics.cy) && intrinsics.width > 0 && intrinsics.height > 0 && intrinsics.fx > 0 && intrinsics.fy > 0;
2263
+ }
2264
+ static normalizeQuaternion(q) {
2265
+ const len = Math.hypot(q[0], q[1], q[2], q[3]);
2266
+ if (len <= 1e-12) return [0, 0, 0, 1];
2267
+ return [q[0] / len, q[1] / len, q[2] / len, q[3] / len];
2268
+ }
2269
+ static rotateByQuaternion(v, q) {
2270
+ const [x, y, z, w] = _HoloMapRuntimeImpl.normalizeQuaternion(q);
2271
+ const [vx, vy, vz] = v;
2272
+ const tx = 2 * (y * vz - z * vy);
2273
+ const ty = 2 * (z * vx - x * vz);
2274
+ const tz = 2 * (x * vy - y * vx);
2275
+ return [
2276
+ vx + w * tx + (y * tz - z * ty),
2277
+ vy + w * ty + (z * tx - x * tz),
2278
+ vz + w * tz + (x * ty - y * tx)
2279
+ ];
2280
+ }
2281
+ static projectMetricDepthToWorld(uv, depthMeters, intrinsics, pose) {
2282
+ const pixelX = uv[0] * intrinsics.width;
2283
+ const pixelY = uv[1] * intrinsics.height;
2284
+ const local = [
2285
+ (pixelX - intrinsics.cx) * depthMeters / intrinsics.fx,
2286
+ (intrinsics.cy - pixelY) * depthMeters / intrinsics.fy,
2287
+ -depthMeters
2288
+ ];
2289
+ if (!pose) return local;
2290
+ const rotated = _HoloMapRuntimeImpl.rotateByQuaternion(local, pose.rotation);
2291
+ return [
2292
+ pose.position[0] + rotated[0],
2293
+ pose.position[1] + rotated[1],
2294
+ pose.position[2] + rotated[2]
2295
+ ];
2296
+ }
2297
+ static fitShiftScaleDepth(samples) {
2298
+ const paired = samples.filter((sample) => sample.metricDepthMeters !== void 0).map((sample) => ({
2299
+ x: sample.rawDepthSignal,
2300
+ y: sample.metricDepthMeters
2301
+ }));
2302
+ if (paired.length === 0) return void 0;
2303
+ let meanX = 0;
2304
+ let meanY = 0;
2305
+ for (const sample of paired) {
2306
+ meanX += sample.x;
2307
+ meanY += sample.y;
2308
+ }
2309
+ meanX /= paired.length;
2310
+ meanY /= paired.length;
2311
+ let varianceX = 0;
2312
+ let covariance = 0;
2313
+ for (const sample of paired) {
2314
+ const dx = sample.x - meanX;
2315
+ varianceX += dx * dx;
2316
+ covariance += dx * (sample.y - meanY);
2317
+ }
2318
+ const scale = varianceX > 1e-12 ? covariance / varianceX : 0;
2319
+ const shift = meanY - scale * meanX;
2320
+ let squared = 0;
2321
+ for (const sample of paired) {
2322
+ const err = scale * sample.x + shift - sample.y;
2323
+ squared += err * err;
2324
+ }
2325
+ return {
2326
+ kind: "shift-scale",
2327
+ sampleCount: paired.length,
2328
+ scale,
2329
+ shift,
2330
+ rmseMeters: Math.sqrt(squared / paired.length)
2331
+ };
2332
+ }
2333
+ static alignedDepthMeters(sample, fit) {
2334
+ if (fit) {
2335
+ const aligned = fit.scale * sample.rawDepthSignal + fit.shift;
2336
+ if (Number.isFinite(aligned) && aligned > 0) return aligned;
2337
+ }
2338
+ return sample.metricDepthMeters;
2339
+ }
2340
+ static buildLoopClosureDescriptor(samples) {
2341
+ if (samples.length === 0) return new Float32Array([0, 0, 0, 0, 0, 0]);
2342
+ let r = 0;
2343
+ let g = 0;
2344
+ let b = 0;
2345
+ let luma = 0;
2346
+ let texture = 0;
2347
+ let depth = 0;
2348
+ let depthCount = 0;
2349
+ for (const sample of samples) {
2350
+ r += sample.meanColor[0] / 255;
2351
+ g += sample.meanColor[1] / 255;
2352
+ b += sample.meanColor[2] / 255;
2353
+ luma += sample.luminance;
2354
+ texture += sample.texture;
2355
+ if (sample.metricDepthMeters !== void 0) {
2356
+ depth += Math.min(1, sample.metricDepthMeters / 10);
2357
+ depthCount += 1;
2358
+ }
2359
+ }
2360
+ const inv = 1 / samples.length;
2361
+ return new Float32Array([
2362
+ r * inv,
2363
+ g * inv,
2364
+ b * inv,
2365
+ luma * inv,
2366
+ texture * inv,
2367
+ depthCount > 0 ? depth / depthCount : 0
2368
+ ]);
2369
+ }
2370
+ static descriptorDistance(a, b) {
2371
+ const n = Math.min(a.length, b.length);
2372
+ if (n === 0) return Number.POSITIVE_INFINITY;
2373
+ let squared = 0;
2374
+ for (let i = 0; i < n; i += 1) {
2375
+ const d = (a[i] ?? 0) - (b[i] ?? 0);
2376
+ squared += d * d;
2377
+ }
2378
+ return Math.sqrt(squared / n);
2379
+ }
2380
+ static computeBounds(steps) {
2381
+ if (steps.length === 0) {
2382
+ return {
2383
+ min: [0, 0, 0],
2384
+ max: [0, 0, 0]
2385
+ };
2386
+ }
2387
+ let minX = Number.POSITIVE_INFINITY;
2388
+ let minY = Number.POSITIVE_INFINITY;
2389
+ let minZ = Number.POSITIVE_INFINITY;
2390
+ let maxX = Number.NEGATIVE_INFINITY;
2391
+ let maxY = Number.NEGATIVE_INFINITY;
2392
+ let maxZ = Number.NEGATIVE_INFINITY;
2393
+ for (const step of steps) {
2394
+ const { positions } = step.points;
2395
+ for (let i = 0; i < positions.length; i += 3) {
2396
+ const x = positions[i] ?? 0;
2397
+ const y = positions[i + 1] ?? 0;
2398
+ const z = positions[i + 2] ?? 0;
2399
+ if (x < minX) minX = x;
2400
+ if (y < minY) minY = y;
2401
+ if (z < minZ) minZ = z;
2402
+ if (x > maxX) maxX = x;
2403
+ if (y > maxY) maxY = y;
2404
+ if (z > maxZ) maxZ = z;
2405
+ }
2406
+ }
2407
+ if (!Number.isFinite(minX)) {
2408
+ return {
2409
+ min: [0, 0, 0],
2410
+ max: [0, 0, 0]
2411
+ };
2412
+ }
2413
+ return {
2414
+ min: [minX, minY, minZ],
2415
+ max: [maxX, maxY, maxZ]
2416
+ };
2417
+ }
2418
+ /** Update running bounds with new point positions. */
2419
+ updateBounds(positions) {
2420
+ if (positions.length === 0) return;
2421
+ if (!this.boundsValid) {
2422
+ this.boundsMin = [
2423
+ Number.POSITIVE_INFINITY,
2424
+ Number.POSITIVE_INFINITY,
2425
+ Number.POSITIVE_INFINITY
2426
+ ];
2427
+ this.boundsMax = [
2428
+ Number.NEGATIVE_INFINITY,
2429
+ Number.NEGATIVE_INFINITY,
2430
+ Number.NEGATIVE_INFINITY
2431
+ ];
2432
+ }
2433
+ for (let i = 0; i < positions.length; i += 3) {
2434
+ const x = positions[i] ?? 0;
2435
+ const y = positions[i + 1] ?? 0;
2436
+ const z = positions[i + 2] ?? 0;
2437
+ if (x < this.boundsMin[0]) this.boundsMin[0] = x;
2438
+ if (y < this.boundsMin[1]) this.boundsMin[1] = y;
2439
+ if (z < this.boundsMin[2]) this.boundsMin[2] = z;
2440
+ if (x > this.boundsMax[0]) this.boundsMax[0] = x;
2441
+ if (y > this.boundsMax[1]) this.boundsMax[1] = y;
2442
+ if (z > this.boundsMax[2]) this.boundsMax[2] = z;
2443
+ }
2444
+ this.boundsValid = true;
2445
+ }
2446
+ /** Get current bounds (running or computed). */
2447
+ getBounds() {
2448
+ if (this.boundsValid) {
2449
+ return { min: [...this.boundsMin], max: [...this.boundsMax] };
2450
+ }
2451
+ return _HoloMapRuntimeImpl.computeBounds(this.steps);
2452
+ }
2453
+ static *retainedPositionChunks(steps, current) {
2454
+ for (const step of steps) yield step.points.positions;
2455
+ yield current;
2456
+ }
2457
+ static estimatePrincipalAxisRotation(chunks, centroid) {
2458
+ let xx = 0;
2459
+ let zz = 0;
2460
+ let xz = 0;
2461
+ let n = 0;
2462
+ for (const positions of chunks) {
2463
+ for (let i = 0; i < positions.length; i += 3) {
2464
+ const dx = (positions[i] ?? 0) - centroid[0];
2465
+ const dz = (positions[i + 2] ?? 0) - centroid[2];
2466
+ xx += dx * dx;
2467
+ zz += dz * dz;
2468
+ xz += dx * dz;
2469
+ n += 1;
2470
+ }
2471
+ }
2472
+ if (n < 2 || xx + zz < 1e-12) return [0, 0, 0, 1];
2473
+ const yaw = 0.5 * Math.atan2(2 * xz, xx - zz);
2474
+ return _HoloMapRuntimeImpl.yawToQuaternion(yaw);
2475
+ }
2476
+ static estimatePrincipalAxisRotationFromMoments(momentXX, momentZZ, momentXZ, pointCount, centroid) {
2477
+ if (pointCount < 2) return [0, 0, 0, 1];
2478
+ const xx = momentXX - pointCount * centroid[0] * centroid[0];
2479
+ const zz = momentZZ - pointCount * centroid[2] * centroid[2];
2480
+ const xz = momentXZ - pointCount * centroid[0] * centroid[2];
2481
+ if (xx + zz < 1e-12) return [0, 0, 0, 1];
2482
+ const yaw = 0.5 * Math.atan2(2 * xz, xx - zz);
2483
+ return _HoloMapRuntimeImpl.yawToQuaternion(yaw);
2484
+ }
2485
+ static yawToQuaternion(yaw) {
2486
+ const half = yaw / 2;
2487
+ return [0, Math.sin(half), 0, Math.cos(half)];
2488
+ }
2489
+ static poseDistanceMeters(a, b) {
2490
+ const dx = a.position[0] - b.position[0];
2491
+ const dy = a.position[1] - b.position[1];
2492
+ const dz = a.position[2] - b.position[2];
2493
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
2494
+ }
2495
+ static poseRotationDeltaRadians(a, b) {
2496
+ const dot = Math.abs(
2497
+ a.rotation[0] * b.rotation[0] + a.rotation[1] * b.rotation[1] + a.rotation[2] * b.rotation[2] + a.rotation[3] * b.rotation[3]
2498
+ );
2499
+ const clamped = Math.max(-1, Math.min(1, dot));
2500
+ return 2 * Math.acos(clamped);
2501
+ }
2502
+ static buildTrajectoryEmbedding(pose, anchorDescriptor, interFrameDeltaMeters, interFrameRotationRadians) {
2503
+ return new Float32Array([
2504
+ pose.position[0],
2505
+ pose.position[1],
2506
+ pose.position[2],
2507
+ pose.rotation[0],
2508
+ pose.rotation[1],
2509
+ pose.rotation[2],
2510
+ pose.rotation[3],
2511
+ pose.confidence,
2512
+ interFrameDeltaMeters,
2513
+ interFrameRotationRadians,
2514
+ anchorDescriptor[0] ?? 0,
2515
+ anchorDescriptor[1] ?? 0,
2516
+ anchorDescriptor[2] ?? 0,
2517
+ anchorDescriptor[3] ?? 0
2518
+ ]);
2519
+ }
2520
+ static cloneTrajectoryKeyframes(keyframes, maxCount = keyframes.length) {
2521
+ const start = Math.max(0, keyframes.length - Math.max(0, maxCount));
2522
+ const out = [];
2523
+ for (let i = start; i < keyframes.length; i += 1) {
2524
+ const keyframe = keyframes[i];
2525
+ out.push({
2526
+ frameIndex: keyframe.frameIndex,
2527
+ timestampMs: keyframe.timestampMs,
2528
+ pose: {
2529
+ position: [...keyframe.pose.position],
2530
+ rotation: [...keyframe.pose.rotation],
2531
+ confidence: keyframe.pose.confidence
2532
+ },
2533
+ embedding: new Float32Array(keyframe.embedding)
2534
+ });
2535
+ }
2536
+ return out;
2537
+ }
2538
+ pushTrajectoryKeyframe(keyframe) {
2539
+ this.trajectoryKeyframes.push(keyframe);
2540
+ if (this.trajectoryKeyframes.length > _HoloMapRuntimeImpl.TRAJECTORY_KEYFRAME_CAP) {
2541
+ this.trajectoryKeyframes.shift();
2542
+ }
2543
+ }
2544
+ async init(config) {
2545
+ this.config = {
2546
+ ...config,
2547
+ reconstructionMode: config.reconstructionMode ?? "preview"
2548
+ };
2549
+ if (this.config.reconstructionMode === "dense-proof") {
2550
+ this.config.tileGrid = Math.max(
2551
+ this.config.tileGrid ?? _HoloMapRuntimeImpl.GRID_N,
2552
+ _HoloMapRuntimeImpl.DENSE_PROOF_GRID_N
2553
+ );
2554
+ }
2555
+ this.config.tileGrid = _HoloMapRuntimeImpl.normalizeTileGrid(this.config.tileGrid);
2556
+ if (this.config.reconstructionMode === "dense-proof") {
2557
+ _HoloMapRuntimeImpl.assertDenseProofConfig(this.config);
2558
+ }
2559
+ const allowCpu = this.config.allowCpuFallback !== false;
2560
+ if (!allowCpu && !chunkN2ILK7BF_cjs.isWebGpuEnvironmentPresent()) {
2561
+ 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.";
2562
+ logHoloMapEvent(this.runId, "error", { message: err });
2563
+ throw new Error(err);
2564
+ }
2565
+ this.steps.length = 0;
2566
+ this.totalPointCount = 0;
2567
+ this.boundsValid = false;
2568
+ this.boundsMin = [0, 0, 0];
2569
+ this.boundsMax = [0, 0, 0];
2570
+ this.cumulativeDriftMeters = 0;
2571
+ this.prevPose = null;
2572
+ this.prevCameraPose = null;
2573
+ this.prevVelocity = null;
2574
+ this.lastLoopClosureFrameIdx = -1;
2575
+ this.poseHistory.length = 0;
2576
+ this.trajectoryKeyframes.length = 0;
2577
+ this.globalPosSum = [0, 0, 0];
2578
+ this.globalConfSum = 0;
2579
+ this.globalPrincipalMomentXX = 0;
2580
+ this.globalPrincipalMomentZZ = 0;
2581
+ this.globalPrincipalMomentXZ = 0;
2582
+ this.lastAcceptedFrameTimestampMs = null;
2583
+ this.sessionStartMs = performance.now();
2584
+ this.perfMetrics = {
2585
+ stepCount: 0,
2586
+ throttledCount: 0,
2587
+ totalStepMs: 0,
2588
+ maxStepMs: 0,
2589
+ minStepMs: Infinity
2590
+ };
2591
+ this.replayKey = chunkLM2VU56C_cjs.computeHoloMapReplayFingerprint({
2592
+ modelHash: this.config.modelHash,
2593
+ seed: this.config.seed,
2594
+ weightStrategy: this.config.weightStrategy ?? "distill",
2595
+ videoHash: this.config.videoHash,
2596
+ tileGrid: this.config.tileGrid,
2597
+ weightCid: this.config.weightCid,
2598
+ verticalProfile: this.config.verticalProfile
2599
+ });
2600
+ this.weightBytes = null;
2601
+ this.weightVerified = false;
2602
+ this.weightSource = null;
2603
+ const weightUrl = this.config.weightUrl ?? (this.config.weightCid && this.config.localResolver ? `holomap-local://${this.config.weightCid}` : void 0);
2604
+ if (weightUrl) {
2605
+ const result = await loadHoloMapWeightBlob({
2606
+ weightUrl,
2607
+ weightUrls: this.config.weightUrls,
2608
+ weightCid: this.config.weightCid,
2609
+ localResolver: this.config.localResolver
2610
+ });
2611
+ this.weightBytes = result.bytes;
2612
+ this.weightVerified = result.verified;
2613
+ this.weightSource = result.source;
2614
+ }
2615
+ if (this.config.reconstructionMode === "dense-proof" && (!this.weightBytes || !this.weightVerified)) {
2616
+ throw new Error(
2617
+ "HoloMap dense-proof mode requires verified checkpoint bytes; preview mode is the only mode allowed to fall back to PRNG weights."
2618
+ );
2619
+ }
2620
+ this.encoderDevice = await tryCreateHoloMapEncoderDevice();
2621
+ this.microEncoder = this.encoderDevice ? createHoloMapMicroEncoder(this.encoderDevice) : null;
2622
+ const streamingTokens = Math.max(
2623
+ 1,
2624
+ this.config.maxSequenceLength * (this.config.tileGrid ?? _HoloMapRuntimeImpl.GRID_N) ** 2
2625
+ );
2626
+ const streamingPageSize = Math.max(
2627
+ 1,
2628
+ Math.min(64, this.config.tileGrid ?? _HoloMapRuntimeImpl.GRID_N)
2629
+ );
2630
+ this.streamingKVCache = createPagedKVCache({
2631
+ pageSize: streamingPageSize,
2632
+ maxResidentPages: Math.max(1, Math.min(256, Math.ceil(streamingTokens / streamingPageSize))),
2633
+ hiddenDim: 3,
2634
+ numHeads: 1,
2635
+ numLayers: 1,
2636
+ device: this.encoderDevice
2637
+ });
2638
+ this.streamingKVNextToken = 0;
2639
+ this.initialized = true;
2640
+ logHoloMapEvent(this.runId, "init", {
2641
+ modelHash: this.config.modelHash,
2642
+ seed: this.config.seed,
2643
+ allowCpuFallback: allowCpu,
2644
+ webgpu: chunkN2ILK7BF_cjs.isWebGpuEnvironmentPresent(),
2645
+ microEncoder: this.microEncoder ? "webgpu" : "cpu",
2646
+ reconstructionMode: this.config.reconstructionMode,
2647
+ tileGrid: this.config.tileGrid,
2648
+ weightLoadedBytes: this.weightBytes?.byteLength ?? 0,
2649
+ weightVerified: this.weightVerified,
2650
+ weightSource: this.weightSource,
2651
+ replayFingerprint: this.replayKey
2652
+ });
2653
+ }
2654
+ static normalizeTileGrid(value) {
2655
+ const raw = value ?? _HoloMapRuntimeImpl.GRID_N;
2656
+ if (!Number.isFinite(raw)) {
2657
+ throw new Error(`HoloMapRuntime.init invalid tileGrid: ${String(value)}`);
2658
+ }
2659
+ const tileGrid = Math.floor(raw);
2660
+ if (tileGrid < 1 || tileGrid > _HoloMapRuntimeImpl.MAX_GRID_N) {
2661
+ throw new Error(
2662
+ `HoloMapRuntime.init tileGrid must be an integer from 1 to ${_HoloMapRuntimeImpl.MAX_GRID_N}`
2663
+ );
2664
+ }
2665
+ return tileGrid;
2666
+ }
2667
+ static assertDenseProofConfig(config) {
2668
+ const missing = [];
2669
+ if (!config.modelHash || config.modelHash === "unset") missing.push("modelHash");
2670
+ if (!config.weightCid) missing.push("weightCid");
2671
+ if (!config.weightUrl && !config.localResolver) missing.push("weightUrl or localResolver");
2672
+ if (missing.length > 0) {
2673
+ throw new Error(
2674
+ `HoloMap dense-proof mode requires ${missing.join(
2675
+ ", "
2676
+ )}; preview mode is the only mode allowed to use PRNG/default weights.`
2677
+ );
2678
+ }
2679
+ }
2680
+ /**
2681
+ * Carve `frame` into gridN×gridN tiles. Each tile carries its own
2682
+ * (rgb, width, height, stride, index) and the mean RGB color over its
2683
+ * pixels (used to color the corresponding output point).
2684
+ *
2685
+ * Tiles inherit `frame.index` shifted by tile id so micro-encoder
2686
+ * per-frame seeds remain deterministic and distinct across tiles.
2687
+ */
2688
+ static tileFrame(frame, gridN) {
2689
+ const out = [];
2690
+ const tileW = Math.max(1, Math.floor(frame.width / gridN));
2691
+ const tileH = Math.max(1, Math.floor(frame.height / gridN));
2692
+ const stride = frame.stride;
2693
+ for (let ty = 0; ty < gridN; ty += 1) {
2694
+ for (let tx = 0; tx < gridN; tx += 1) {
2695
+ const x0 = tx * tileW;
2696
+ const y0 = ty * tileH;
2697
+ const x1 = tx === gridN - 1 ? frame.width : x0 + tileW;
2698
+ const y1 = ty === gridN - 1 ? frame.height : y0 + tileH;
2699
+ const w = x1 - x0;
2700
+ const h = y1 - y0;
2701
+ const tileBytes = new Uint8Array(w * h * stride);
2702
+ let rSum = 0;
2703
+ let gSum = 0;
2704
+ let bSum = 0;
2705
+ let lumaSum = 0;
2706
+ let lumaSqSum = 0;
2707
+ let count = 0;
2708
+ for (let y = 0; y < h; y += 1) {
2709
+ const srcRow = (y0 + y) * frame.width * stride;
2710
+ const dstRow = y * w * stride;
2711
+ for (let x = 0; x < w; x += 1) {
2712
+ const sIdx = srcRow + (x0 + x) * stride;
2713
+ const dIdx = dstRow + x * stride;
2714
+ const r = frame.rgb[sIdx] ?? 0;
2715
+ const g = frame.rgb[sIdx + 1] ?? 0;
2716
+ const b = frame.rgb[sIdx + 2] ?? 0;
2717
+ tileBytes[dIdx] = r;
2718
+ tileBytes[dIdx + 1] = g;
2719
+ tileBytes[dIdx + 2] = b;
2720
+ if (stride === 4) tileBytes[dIdx + 3] = frame.rgb[sIdx + 3] ?? 255;
2721
+ rSum += r;
2722
+ gSum += g;
2723
+ bSum += b;
2724
+ const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
2725
+ lumaSum += luma;
2726
+ lumaSqSum += luma * luma;
2727
+ count += 1;
2728
+ }
2729
+ }
2730
+ const denom = Math.max(1, count);
2731
+ const meanLuma = lumaSum / denom;
2732
+ const lumaVariance = Math.max(0, lumaSqSum / denom - meanLuma * meanLuma);
2733
+ const tileId = ty * gridN + tx;
2734
+ out.push({
2735
+ tile: {
2736
+ // Encode (frameIndex, tileId) into the per-tile micro index so
2737
+ // each tile gets a distinct deterministic micro-encoder seed.
2738
+ index: frame.index * gridN * gridN + tileId,
2739
+ rgb: tileBytes,
2740
+ width: w,
2741
+ height: h,
2742
+ stride
2743
+ },
2744
+ centerUv: [
2745
+ (x0 + x1) * 0.5 / Math.max(1, frame.width),
2746
+ (y0 + y1) * 0.5 / Math.max(1, frame.height)
2747
+ ],
2748
+ luminance: meanLuma / 255,
2749
+ texture: Math.sqrt(lumaVariance) / 255,
2750
+ meanColor: [Math.round(rSum / denom), Math.round(gSum / denom), Math.round(bSum / denom)]
2751
+ });
2752
+ }
2753
+ }
2754
+ return out;
2755
+ }
2756
+ async encodeTile(tile, microCfg) {
2757
+ if (!this.microEncoder) {
2758
+ return runHoloMapMicroEncoderCpu(tile, microCfg);
2759
+ }
2760
+ try {
2761
+ return await this.microEncoder.run(tile, microCfg);
2762
+ } catch (error) {
2763
+ const message = error instanceof Error ? error.message : String(error);
2764
+ if (this.config.allowCpuFallback === false) {
2765
+ throw new Error(`HoloMap WebGPU micro-encoder failed: ${message}`);
2766
+ }
2767
+ logHoloMapEvent(this.runId, "micro_encoder_fallback", {
2768
+ frameIndex: tile.index,
2769
+ reason: message
2770
+ });
2771
+ this.microEncoder = null;
2772
+ return runHoloMapMicroEncoderCpu(tile, microCfg);
2773
+ }
2774
+ }
2775
+ async updateStreamingKVCache(encodedTiles) {
2776
+ if (!this.streamingKVCache || encodedTiles.length === 0) return void 0;
2777
+ const keys = new Float32Array(encodedTiles.length * 3);
2778
+ const values = new Float32Array(encodedTiles.length * 3);
2779
+ for (let i = 0; i < encodedTiles.length; i += 1) {
2780
+ const sample = encodedTiles[i];
2781
+ keys.set(sample.latent, i * 3);
2782
+ values[i * 3] = sample.rawDepthSignal;
2783
+ values[i * 3 + 1] = sample.texture;
2784
+ values[i * 3 + 2] = sample.luminance;
2785
+ }
2786
+ const firstToken = this.streamingKVNextToken;
2787
+ await this.streamingKVCache.append(0, keys, values, firstToken);
2788
+ this.streamingKVNextToken += encodedTiles.length;
2789
+ const retainedTokenCapacity = this.streamingKVCache.config.pageSize * this.streamingKVCache.config.maxResidentPages;
2790
+ const kLen = Math.min(this.streamingKVNextToken, 128, retainedTokenCapacity);
2791
+ const lookupStart = this.streamingKVNextToken - kLen;
2792
+ const lookup = await this.streamingKVCache.lookup(0, lookupStart, kLen);
2793
+ let checksum = 0;
2794
+ for (let i = 0; i < lookup.keys.length; i += 1) checksum += lookup.keys[i] ?? 0;
2795
+ for (let i = 0; i < lookup.values.length; i += 1) checksum += lookup.values[i] ?? 0;
2796
+ return {
2797
+ layer: 0,
2798
+ kLen,
2799
+ cachedPages: this.streamingKVCache.residentPagesForLayer(0).length,
2800
+ lookupChecksum: checksum,
2801
+ memory: this.streamingKVCache.memoryUsage()
2802
+ };
2803
+ }
2804
+ async step(frame) {
2805
+ if (!this.initialized) {
2806
+ throw new Error("HoloMapRuntime not initialized. Call init(config) before step(frame).");
2807
+ }
2808
+ if (!Number.isFinite(frame.timestampMs)) {
2809
+ throw new Error(`HoloMapRuntime.step invalid frame timestamp: ${frame.timestampMs}`);
2810
+ }
2811
+ const expectedBytes = frame.width * frame.height * frame.stride;
2812
+ if (frame.rgb.byteLength !== expectedBytes) {
2813
+ throw new Error(
2814
+ `HoloMapRuntime.step invalid frame byte length: got ${frame.rgb.byteLength}, expected ${expectedBytes} (w=${frame.width}, h=${frame.height}, stride=${frame.stride})`
2815
+ );
2816
+ }
2817
+ if (frame.depth && frame.depth.length !== frame.width * frame.height) {
2818
+ throw new Error(
2819
+ `HoloMapRuntime.step invalid measured depth length: got ${frame.depth.length}, expected ${frame.width * frame.height} (w=${frame.width}, h=${frame.height})`
2820
+ );
2821
+ }
2822
+ if (frame.depthMeters && frame.depthMeters.length !== frame.width * frame.height) {
2823
+ throw new Error(
2824
+ `HoloMapRuntime.step invalid metric depth length: got ${frame.depthMeters.length}, expected ${frame.width * frame.height} (w=${frame.width}, h=${frame.height})`
2825
+ );
2826
+ }
2827
+ if (frame.depthConfidence && frame.depthConfidence.length !== frame.width * frame.height) {
2828
+ throw new Error(
2829
+ `HoloMapRuntime.step invalid measured depth confidence length: got ${frame.depthConfidence.length}, expected ${frame.width * frame.height} (w=${frame.width}, h=${frame.height})`
2830
+ );
2831
+ }
2832
+ const minIntervalMs = 1e3 / Math.max(1, this.config.targetFPS);
2833
+ if (this.lastAcceptedFrameTimestampMs !== null && frame.timestampMs - this.lastAcceptedFrameTimestampMs < minIntervalMs) {
2834
+ this.perfMetrics.throttledCount += 1;
2835
+ logHoloMapEvent(this.runId, "step_throttled", {
2836
+ frameIndex: frame.index,
2837
+ elapsedFrameMs: Math.round(frame.timestampMs - this.lastAcceptedFrameTimestampMs),
2838
+ targetIntervalMs: Math.round(minIntervalMs)
2839
+ });
2840
+ return null;
2841
+ }
2842
+ const stepStartMs = performance.now();
2843
+ this.lastAcceptedFrameTimestampMs = frame.timestampMs;
2844
+ if (this.steps.length >= this.config.maxSequenceLength) {
2845
+ const evicted = this.steps.shift();
2846
+ const evictedPoints = evicted.points.positions.length / 3;
2847
+ this.totalPointCount -= evictedPoints;
2848
+ const ep = evicted.points.positions;
2849
+ for (let i = 0; i < ep.length; i += 3) {
2850
+ const x = ep[i];
2851
+ const y = ep[i + 1];
2852
+ const z = ep[i + 2];
2853
+ this.globalPosSum[0] -= x;
2854
+ this.globalPosSum[1] -= y;
2855
+ this.globalPosSum[2] -= z;
2856
+ this.globalPrincipalMomentXX -= x * x;
2857
+ this.globalPrincipalMomentZZ -= z * z;
2858
+ this.globalPrincipalMomentXZ -= x * z;
2859
+ }
2860
+ const ec = evicted.points.confidence;
2861
+ for (let i = 0; i < ec.length; i += 1) this.globalConfSum -= ec[i];
2862
+ this.boundsValid = false;
2863
+ for (const s of this.steps) {
2864
+ this.updateBounds(s.points.positions);
2865
+ }
2866
+ logHoloMapEvent(this.runId, "step_evict", {
2867
+ frameIndex: evicted.frameIndex,
2868
+ evictedPoints,
2869
+ retainedSteps: this.steps.length
2870
+ });
2871
+ }
2872
+ const microCfg = {
2873
+ seed: this.config.seed,
2874
+ modelHash: this.config.modelHash,
2875
+ // Wire the loaded checkpoint through to the encoder. Previously weightBytes
2876
+ // was fetched + verified in init() then never passed here, so the encoder
2877
+ // always ran on PRNG weights even when a real checkpoint was provided.
2878
+ weightBytes: this.weightBytes
2879
+ };
2880
+ const configuredGrid = this.config.tileGrid ?? _HoloMapRuntimeImpl.GRID_N;
2881
+ const gridN = Math.max(1, Math.min(configuredGrid, frame.width, frame.height));
2882
+ const tiles = _HoloMapRuntimeImpl.tileFrame(frame, gridN);
2883
+ const numPoints = tiles.length;
2884
+ const positions = new Float32Array(numPoints * 3);
2885
+ const colors = new Uint8Array(numPoints * 3);
2886
+ const confidence = new Float32Array(numPoints);
2887
+ const aspect = frame.width / Math.max(1, frame.height);
2888
+ const metricIntrinsics = _HoloMapRuntimeImpl.hasUsableIntrinsics(frame.cameraIntrinsics) ? frame.cameraIntrinsics : void 0;
2889
+ const encodedTiles = [];
2890
+ for (let t = 0; t < numPoints; t += 1) {
2891
+ const sample = tiles[t];
2892
+ const xyz = await this.encodeTile(sample.tile, microCfg);
2893
+ const latentX = xyz[0] ?? 0;
2894
+ const latentY = xyz[1] ?? 0;
2895
+ const latentZ = xyz[2] ?? 0;
2896
+ const depthHint = (sample.luminance - 0.5) * 0.34 + sample.texture * 0.24;
2897
+ encodedTiles.push({
2898
+ ...sample,
2899
+ latent: [latentX, latentY, latentZ],
2900
+ rawDepthSignal: depthHint + latentZ * 0.12,
2901
+ metricDepthMeters: frame.depthMeters ? _HoloMapRuntimeImpl.sampleMetricDepthNearestUv(
2902
+ frame.depthMeters,
2903
+ frame.width,
2904
+ frame.height,
2905
+ sample.centerUv
2906
+ ) : void 0
2907
+ });
2908
+ }
2909
+ const streamingKV = await this.updateStreamingKVCache(encodedTiles);
2910
+ const depthAlignment = _HoloMapRuntimeImpl.fitShiftScaleDepth(encodedTiles);
2911
+ const loopDescriptor = _HoloMapRuntimeImpl.buildLoopClosureDescriptor(encodedTiles);
2912
+ const rawDevicePose = frame.devicePose ? {
2913
+ position: [...frame.devicePose.position],
2914
+ rotation: [...frame.devicePose.rotation],
2915
+ confidence: frame.devicePose.confidence
2916
+ } : void 0;
2917
+ let loopCorrectedPose;
2918
+ if (frame.depthMeters && metricIntrinsics) {
2919
+ for (const kf of this.poseHistory) {
2920
+ if (frame.index - kf.frameIndex < _HoloMapRuntimeImpl.LOOP_CLOSURE_MIN_GAP) continue;
2921
+ const descriptorDistance = _HoloMapRuntimeImpl.descriptorDistance(
2922
+ loopDescriptor,
2923
+ kf.descriptor
2924
+ );
2925
+ if (descriptorDistance <= _HoloMapRuntimeImpl.LOOP_CLOSURE_DESCRIPTOR_DISTANCE) {
2926
+ this.lastLoopClosureFrameIdx = frame.index;
2927
+ if (rawDevicePose) {
2928
+ loopCorrectedPose = {
2929
+ ...rawDevicePose,
2930
+ position: [...kf.position],
2931
+ confidence: Math.min(1, Math.max(rawDevicePose.confidence, 0.9))
2932
+ };
2933
+ }
2934
+ break;
2935
+ }
2936
+ }
2937
+ }
2938
+ const metricProjectionPose = loopCorrectedPose ?? rawDevicePose;
2939
+ let centroidX = 0;
2940
+ let centroidY = 0;
2941
+ let centroidZ = 0;
2942
+ let confidenceSum = 0;
2943
+ let framePrincipalMomentXX = 0;
2944
+ let framePrincipalMomentZZ = 0;
2945
+ let framePrincipalMomentXZ = 0;
2946
+ for (let t = 0; t < numPoints; t += 1) {
2947
+ const { meanColor, centerUv, texture, latent, rawDepthSignal } = encodedTiles[t];
2948
+ const [latentX, latentY, latentZ] = latent;
2949
+ const planeX = (centerUv[0] - 0.5) * 2 * aspect;
2950
+ const planeY = (0.5 - centerUv[1]) * 2;
2951
+ let px = planeX + latentX * 0.08;
2952
+ let py = planeY + latentY * 0.08;
2953
+ let pz;
2954
+ const metricDepth = _HoloMapRuntimeImpl.alignedDepthMeters(encodedTiles[t], depthAlignment);
2955
+ if (metricDepth !== void 0 && metricIntrinsics) {
2956
+ const worldPoint = _HoloMapRuntimeImpl.projectMetricDepthToWorld(
2957
+ centerUv,
2958
+ metricDepth,
2959
+ metricIntrinsics,
2960
+ metricProjectionPose
2961
+ );
2962
+ px = worldPoint[0];
2963
+ py = worldPoint[1];
2964
+ pz = worldPoint[2];
2965
+ } else if (metricDepth !== void 0) {
2966
+ pz = -metricDepth;
2967
+ } else if (frame.depth) {
2968
+ const measured = _HoloMapRuntimeImpl.sampleDepthNearestUv(
2969
+ frame.depth,
2970
+ frame.width,
2971
+ frame.height,
2972
+ centerUv
2973
+ );
2974
+ pz = (0.5 - measured) * _HoloMapRuntimeImpl.DEPTH_Z_SCALE;
2975
+ } else {
2976
+ pz = rawDepthSignal;
2977
+ }
2978
+ positions[t * 3] = px;
2979
+ positions[t * 3 + 1] = py;
2980
+ positions[t * 3 + 2] = pz;
2981
+ colors[t * 3] = meanColor[0];
2982
+ colors[t * 3 + 1] = meanColor[1];
2983
+ colors[t * 3 + 2] = meanColor[2];
2984
+ const latentMag = Math.sqrt(latentX * latentX + latentY * latentY + latentZ * latentZ);
2985
+ const baseConfidence = Math.min(
2986
+ 1,
2987
+ 0.45 + 0.35 / (1 + latentMag) + Math.min(0.2, texture * 1.5)
2988
+ );
2989
+ if (frame.depthConfidence) {
2990
+ const sensorConfidence = _HoloMapRuntimeImpl.sampleDepthNearestUv(
2991
+ frame.depthConfidence,
2992
+ frame.width,
2993
+ frame.height,
2994
+ centerUv
2995
+ );
2996
+ confidence[t] = baseConfidence * sensorConfidence;
2997
+ } else {
2998
+ confidence[t] = baseConfidence;
2999
+ }
3000
+ confidenceSum += confidence[t];
3001
+ centroidX += px;
3002
+ centroidY += py;
3003
+ centroidZ += pz;
3004
+ framePrincipalMomentXX += px * px;
3005
+ framePrincipalMomentZZ += pz * pz;
3006
+ framePrincipalMomentXZ += px * pz;
3007
+ }
3008
+ const inv = 1 / Math.max(1, numPoints);
3009
+ const poseX = centroidX * inv;
3010
+ const poseY = centroidY * inv;
3011
+ const poseZ = centroidZ * inv;
3012
+ const meanConfidence = confidenceSum * inv;
3013
+ const derivedPosition = [poseX, poseY, poseZ];
3014
+ const poseConfidence = Math.max(0, Math.min(1, meanConfidence));
3015
+ const cameraPose = metricProjectionPose ? {
3016
+ position: [...metricProjectionPose.position],
3017
+ rotation: [...metricProjectionPose.rotation],
3018
+ confidence: metricProjectionPose.confidence
3019
+ } : {
3020
+ position: derivedPosition,
3021
+ rotation: _HoloMapRuntimeImpl.estimatePrincipalAxisRotation([positions], derivedPosition),
3022
+ confidence: poseConfidence
3023
+ };
3024
+ const curPose = [
3025
+ cameraPose.position[0],
3026
+ cameraPose.position[1],
3027
+ cameraPose.position[2]
3028
+ ];
3029
+ let interFrameDeltaMeters = 0;
3030
+ let interFrameRotationRadians = 0;
3031
+ if (this.prevCameraPose) {
3032
+ interFrameDeltaMeters = _HoloMapRuntimeImpl.poseDistanceMeters(
3033
+ cameraPose,
3034
+ this.prevCameraPose
3035
+ );
3036
+ interFrameRotationRadians = _HoloMapRuntimeImpl.poseRotationDeltaRadians(
3037
+ cameraPose,
3038
+ this.prevCameraPose
3039
+ );
3040
+ const uncertaintyWeight = Math.max(
3041
+ 0.05,
3042
+ 1 - Math.min(cameraPose.confidence, this.prevCameraPose.confidence)
3043
+ );
3044
+ this.cumulativeDriftMeters += interFrameDeltaMeters * uncertaintyWeight + interFrameRotationRadians * 0.05;
3045
+ }
3046
+ if (this.prevPose) {
3047
+ const velocity = [
3048
+ curPose[0] - this.prevPose[0],
3049
+ curPose[1] - this.prevPose[1],
3050
+ curPose[2] - this.prevPose[2]
3051
+ ];
3052
+ if (this.prevVelocity) {
3053
+ const predX = this.prevPose[0] + this.prevVelocity[0];
3054
+ const predY = this.prevPose[1] + this.prevVelocity[1];
3055
+ const predZ = this.prevPose[2] + this.prevVelocity[2];
3056
+ const rx = curPose[0] - predX;
3057
+ const ry = curPose[1] - predY;
3058
+ const rz = curPose[2] - predZ;
3059
+ this.cumulativeDriftMeters += Math.sqrt(rx * rx + ry * ry + rz * rz);
3060
+ }
3061
+ this.prevVelocity = velocity;
3062
+ }
3063
+ this.prevPose = curPose;
3064
+ this.prevCameraPose = cameraPose;
3065
+ if (this.lastLoopClosureFrameIdx !== frame.index) {
3066
+ for (const kf of this.poseHistory) {
3067
+ if (frame.index - kf.frameIndex < _HoloMapRuntimeImpl.LOOP_CLOSURE_MIN_GAP) continue;
3068
+ const dx = curPose[0] - kf.position[0];
3069
+ const dy = curPose[1] - kf.position[1];
3070
+ const dz = curPose[2] - kf.position[2];
3071
+ if (Math.sqrt(dx * dx + dy * dy + dz * dz) < _HoloMapRuntimeImpl.LOOP_CLOSURE_RADIUS) {
3072
+ this.lastLoopClosureFrameIdx = frame.index;
3073
+ break;
3074
+ }
3075
+ }
3076
+ }
3077
+ this.poseHistory.push({
3078
+ frameIndex: frame.index,
3079
+ position: curPose,
3080
+ descriptor: loopDescriptor
3081
+ });
3082
+ if (this.poseHistory.length > _HoloMapRuntimeImpl.POSE_HISTORY_CAP) this.poseHistory.shift();
3083
+ this.updateBounds(positions);
3084
+ this.globalPosSum[0] += centroidX;
3085
+ this.globalPosSum[1] += centroidY;
3086
+ this.globalPosSum[2] += centroidZ;
3087
+ this.globalConfSum += confidenceSum;
3088
+ this.globalPrincipalMomentXX += framePrincipalMomentXX;
3089
+ this.globalPrincipalMomentZZ += framePrincipalMomentZZ;
3090
+ this.globalPrincipalMomentXZ += framePrincipalMomentXZ;
3091
+ const totalPoints = this.totalPointCount + numPoints;
3092
+ const ginv = 1 / Math.max(1, totalPoints);
3093
+ const anchorCenter = [
3094
+ this.globalPosSum[0] * ginv,
3095
+ this.globalPosSum[1] * ginv,
3096
+ this.globalPosSum[2] * ginv
3097
+ ];
3098
+ const globalMeanConfidence = this.globalConfSum * ginv;
3099
+ const b = this.getBounds();
3100
+ const extentX = b.max[0] - b.min[0];
3101
+ const extentY = b.max[1] - b.min[1];
3102
+ const extentZ = b.max[2] - b.min[2];
3103
+ const anchorRotation = _HoloMapRuntimeImpl.estimatePrincipalAxisRotationFromMoments(
3104
+ this.globalPrincipalMomentXX,
3105
+ this.globalPrincipalMomentZZ,
3106
+ this.globalPrincipalMomentXZ,
3107
+ totalPoints,
3108
+ anchorCenter
3109
+ );
3110
+ const anchorDescriptor = new Float32Array([extentX, extentY, extentZ, globalMeanConfidence]);
3111
+ this.pushTrajectoryKeyframe({
3112
+ frameIndex: frame.index,
3113
+ timestampMs: frame.timestampMs,
3114
+ pose: cameraPose,
3115
+ embedding: _HoloMapRuntimeImpl.buildTrajectoryEmbedding(
3116
+ cameraPose,
3117
+ anchorDescriptor,
3118
+ interFrameDeltaMeters,
3119
+ interFrameRotationRadians
3120
+ )
3121
+ });
3122
+ const trajectoryKeyframes = _HoloMapRuntimeImpl.cloneTrajectoryKeyframes(
3123
+ this.trajectoryKeyframes,
3124
+ _HoloMapRuntimeImpl.TRAJECTORY_STEP_SNAPSHOT_CAP
3125
+ );
3126
+ const step = {
3127
+ frame,
3128
+ pose: cameraPose,
3129
+ points: {
3130
+ positions,
3131
+ colors,
3132
+ confidence
3133
+ },
3134
+ ...streamingKV ? { streamingKV } : {},
3135
+ trajectory: {
3136
+ keyframes: trajectoryKeyframes,
3137
+ estimatedDriftMeters: this.cumulativeDriftMeters,
3138
+ lastLoopClosureFrame: this.lastLoopClosureFrameIdx,
3139
+ revision: frame.index + 1
3140
+ },
3141
+ anchor: {
3142
+ anchorFrameIndex: this.trajectoryKeyframes[0]?.frameIndex ?? frame.index,
3143
+ anchorPose: {
3144
+ position: anchorCenter,
3145
+ rotation: anchorRotation,
3146
+ confidence: Math.max(0, Math.min(1, globalMeanConfidence))
3147
+ },
3148
+ anchorDescriptor,
3149
+ revision: frame.index + 1
3150
+ },
3151
+ ...depthAlignment ? { depthAlignment } : {}
3152
+ };
3153
+ this.steps.push({
3154
+ frameIndex: frame.index,
3155
+ points: {
3156
+ positions,
3157
+ confidence
3158
+ }
3159
+ });
3160
+ this.totalPointCount += numPoints;
3161
+ const stepMs = performance.now() - stepStartMs;
3162
+ this.perfMetrics.stepCount += 1;
3163
+ this.perfMetrics.totalStepMs += stepMs;
3164
+ if (stepMs > this.perfMetrics.maxStepMs) this.perfMetrics.maxStepMs = stepMs;
3165
+ if (stepMs < this.perfMetrics.minStepMs) this.perfMetrics.minStepMs = stepMs;
3166
+ logHoloMapEvent(this.runId, "step", {
3167
+ frameIndex: frame.index,
3168
+ pointCount: numPoints,
3169
+ stepMs: Math.round(stepMs),
3170
+ avgStepMs: Math.round(this.perfMetrics.totalStepMs / this.perfMetrics.stepCount),
3171
+ throttledSoFar: this.perfMetrics.throttledCount
3172
+ });
3173
+ return step;
3174
+ }
3175
+ async finalize() {
3176
+ if (!this.initialized) {
3177
+ throw new Error("HoloMapRuntime not initialized. Call init(config) before finalize().");
3178
+ }
3179
+ const frameCount = this.steps.length;
3180
+ const pointCount = this.totalPointCount;
3181
+ const bounds = this.getBounds();
3182
+ const avgStepMs = this.perfMetrics.stepCount > 0 ? this.perfMetrics.totalStepMs / this.perfMetrics.stepCount : 0;
3183
+ logHoloMapEvent(this.runId, "finalize", {
3184
+ frameCount,
3185
+ pointCount,
3186
+ avgStepMs: Math.round(avgStepMs),
3187
+ maxStepMs: Math.round(this.perfMetrics.maxStepMs),
3188
+ minStepMs: Math.round(this.perfMetrics.minStepMs),
3189
+ throttledCount: this.perfMetrics.throttledCount,
3190
+ sessionDurationMs: Math.round(performance.now() - this.sessionStartMs)
3191
+ });
3192
+ const reconstruction = {
3193
+ mode: this.config.reconstructionMode ?? "preview",
3194
+ modelHash: this.config.modelHash,
3195
+ tileGrid: this.config.tileGrid ?? _HoloMapRuntimeImpl.GRID_N,
3196
+ weightLoadedBytes: this.weightBytes?.byteLength ?? 0,
3197
+ weightVerified: this.weightVerified,
3198
+ ...this.config.weightCid ? { weightCid: this.config.weightCid } : {},
3199
+ ...this.weightSource ? { weightSource: this.weightSource } : {}
3200
+ };
3201
+ const manifest = {
3202
+ version: "1.0.0",
3203
+ worldId: `holomap-${this.replayKey}`,
3204
+ displayName: "HoloMap Reconstruction",
3205
+ pointCount,
3206
+ frameCount,
3207
+ bounds,
3208
+ replayHash: this.replayKey,
3209
+ simulationContract: {
3210
+ kind: chunkN2ILK7BF_cjs.HOLOMAP_SIMULATION_CONTRACT_KIND,
3211
+ replayFingerprint: this.replayKey,
3212
+ holoScriptBuild: getVersionString()
3213
+ },
3214
+ provenance: {
3215
+ anchorHash: `self-attested:${this.replayKey}`,
3216
+ capturedAtIso: (/* @__PURE__ */ new Date()).toISOString()
3217
+ },
3218
+ assets: {
3219
+ points: "reconstruction.points.bin",
3220
+ trajectory: "reconstruction.trajectory.json",
3221
+ anchors: "reconstruction.anchors.json"
3222
+ },
3223
+ weightStrategy: this.config.weightStrategy ?? "distill",
3224
+ reconstruction
3225
+ };
3226
+ try {
3227
+ return await anchorReconstructionManifest(manifest, this.config.provenanceAnchorProvider);
3228
+ } catch (error) {
3229
+ logHoloMapEvent(this.runId, "anchor_failed", {
3230
+ reason: error instanceof Error ? error.message : String(error)
3231
+ });
3232
+ return selfAttestReconstructionManifest(manifest);
3233
+ }
3234
+ }
3235
+ replayHash() {
3236
+ return this.replayKey;
3237
+ }
3238
+ async dispose() {
3239
+ logHoloMapEvent(this.runId, "dispose", {
3240
+ stepsRetained: this.steps.length,
3241
+ totalPointCount: this.totalPointCount,
3242
+ throttledCount: this.perfMetrics.throttledCount
3243
+ });
3244
+ this.initialized = false;
3245
+ this.steps.length = 0;
3246
+ this.microEncoder = null;
3247
+ this.encoderDevice = null;
3248
+ await this.streamingKVCache?.dispose();
3249
+ this.streamingKVCache = null;
3250
+ this.streamingKVNextToken = 0;
3251
+ this.weightBytes = null;
3252
+ this.weightVerified = false;
3253
+ this.weightSource = null;
3254
+ this.totalPointCount = 0;
3255
+ this.cumulativeDriftMeters = 0;
3256
+ this.prevPose = null;
3257
+ this.prevCameraPose = null;
3258
+ this.prevVelocity = null;
3259
+ this.lastLoopClosureFrameIdx = -1;
3260
+ this.poseHistory.length = 0;
3261
+ this.trajectoryKeyframes.length = 0;
3262
+ this.globalPosSum = [0, 0, 0];
3263
+ this.globalConfSum = 0;
3264
+ this.globalPrincipalMomentXX = 0;
3265
+ this.globalPrincipalMomentZZ = 0;
3266
+ this.globalPrincipalMomentXZ = 0;
3267
+ this.boundsValid = false;
3268
+ this.lastAcceptedFrameTimestampMs = null;
3269
+ this.perfMetrics = {
3270
+ stepCount: 0,
3271
+ throttledCount: 0,
3272
+ totalStepMs: 0,
3273
+ maxStepMs: 0,
3274
+ minStepMs: Infinity
3275
+ };
3276
+ }
3277
+ };
3278
+ _HoloMapRuntimeImpl.LOOP_CLOSURE_MIN_GAP = 3;
3279
+ _HoloMapRuntimeImpl.LOOP_CLOSURE_RADIUS = 0.05;
3280
+ _HoloMapRuntimeImpl.POSE_HISTORY_CAP = 512;
3281
+ _HoloMapRuntimeImpl.TRAJECTORY_KEYFRAME_CAP = 512;
3282
+ _HoloMapRuntimeImpl.TRAJECTORY_STEP_SNAPSHOT_CAP = 64;
3283
+ /** Maps normalized measured depth [0=near,1=far] to a point Z range matching
3284
+ * the monocular estimate's spread (±~0.17), so sensor and estimate paths
3285
+ * produce geometry in the same coordinate scale. */
3286
+ _HoloMapRuntimeImpl.DEPTH_Z_SCALE = 0.34;
3287
+ _HoloMapRuntimeImpl.LOOP_CLOSURE_DESCRIPTOR_DISTANCE = 0.015;
3288
+ /**
3289
+ * Default number of tiles per axis used to fan the encoder across the frame.
3290
+ * Total points emitted = tileGrid * tileGrid (one per tile).
3291
+ * Each tile runs the full 8-kernel transformer chain via the micro encoder
3292
+ * (imagePatchEmbed → layerNorm → gemm Q/K/V → rope → fusedMHA →
3293
+ * layerNorm → gelu → gemm xyz). pagedKV append/lookup remains available
3294
+ * for future streaming kLen>1 paths.
3295
+ */
3296
+ _HoloMapRuntimeImpl.GRID_N = 4;
3297
+ _HoloMapRuntimeImpl.DENSE_PROOF_GRID_N = 16;
3298
+ _HoloMapRuntimeImpl.MAX_GRID_N = 32;
3299
+ var HoloMapRuntimeImpl = _HoloMapRuntimeImpl;
3300
+ function createHoloMapRuntime(_config) {
3301
+ const runtime = new HoloMapRuntimeImpl();
3302
+ if (_config) {
3303
+ void runtime.init({ ...HOLOMAP_DEFAULTS, ..._config });
3304
+ }
3305
+ return runtime;
3306
+ }
3307
+
3308
+ exports.BUILD_TIMESTAMP = BUILD_TIMESTAMP;
3309
+ exports.GIT_COMMIT_SHA = GIT_COMMIT_SHA;
3310
+ exports.HOLOMAP_DEFAULTS = HOLOMAP_DEFAULTS;
3311
+ exports.HOLOMAP_DENSE_PROOF_DEFAULTS = HOLOMAP_DENSE_PROOF_DEFAULTS;
3312
+ exports.HOLOSCRIPT_VERSION = HOLOSCRIPT_VERSION;
3313
+ exports.anchorReconstructionManifest = anchorReconstructionManifest;
3314
+ exports.assertWeightCid = assertWeightCid;
3315
+ exports.computeReconstructionManifestDigest = computeReconstructionManifestDigest;
3316
+ exports.createHoloMapMicroEncoder = createHoloMapMicroEncoder;
3317
+ exports.createHoloMapRunId = createHoloMapRunId;
3318
+ exports.createHoloMapRuntime = createHoloMapRuntime;
3319
+ exports.createPagedKVCache = createPagedKVCache;
3320
+ exports.deserializeMicroWeights = deserializeMicroWeights;
3321
+ exports.frameToMicroImage = frameToMicroImage;
3322
+ exports.getCachedWeightBlob = getCachedWeightBlob;
3323
+ exports.getVersionInfo = getVersionInfo;
3324
+ exports.getVersionString = getVersionString;
3325
+ exports.loadHoloMapWeightBlob = loadHoloMapWeightBlob;
3326
+ exports.logHoloMapEvent = logHoloMapEvent;
3327
+ exports.mergeAnchoredProvenance = mergeAnchoredProvenance;
3328
+ exports.normalizeWeightCidDigest = normalizeWeightCidDigest;
3329
+ exports.putCachedWeightBlob = putCachedWeightBlob;
3330
+ exports.resetHoloMapTelemetryForTests = resetHoloMapTelemetryForTests;
3331
+ exports.runHoloMapMicroEncoderCpu = runHoloMapMicroEncoderCpu;
3332
+ exports.selfAttestReconstructionManifest = selfAttestReconstructionManifest;
3333
+ exports.serializeMicroWeights = serializeMicroWeights;
3334
+ exports.stripAnchoredProvenance = stripAnchoredProvenance;
3335
+ exports.tryCreateHoloMapEncoderDevice = tryCreateHoloMapEncoderDevice;
3336
+ //# sourceMappingURL=chunk-VQOTDNFL.cjs.map
3337
+ //# sourceMappingURL=chunk-VQOTDNFL.cjs.map