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