@holoscript/core 8.0.12 → 8.0.13

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 (46) hide show
  1. package/dist/{HoloCompositionParser-3XEC7IJC.cjs → HoloCompositionParser-6GW2BZID.cjs} +3 -3
  2. package/dist/{HoloCompositionParser-FLBZKFD3.js → HoloCompositionParser-P4Y3CXKQ.js} +3 -3
  3. package/dist/{HoloScriptPlusParser-55AY4MUF.js → HoloScriptPlusParser-HQV6IJAH.js} +4 -4
  4. package/dist/{HoloScriptPlusParser-RNUCHOAJ.cjs → HoloScriptPlusParser-RFNHRMPE.cjs} +7 -7
  5. package/dist/{chunk-Z4IUBW46.js → chunk-A4OOLBZD.js} +111 -54
  6. package/dist/{chunk-TYNRXRHF.js → chunk-DJ5BNRIS.js} +30 -9
  7. package/dist/{chunk-G3BM3CT4.cjs → chunk-DVHZD5HV.cjs} +5 -5
  8. package/dist/{chunk-7NPZZZD7.js → chunk-FRSKLX6I.js} +8 -4
  9. package/dist/{chunk-JBYBMBMW.js → chunk-FWKEY4LD.js} +3 -2
  10. package/dist/{chunk-USOUXQYD.js → chunk-G6EKZHXY.js} +129 -21
  11. package/dist/{chunk-QX34ELJ7.js → chunk-G72754QB.js} +3 -3
  12. package/dist/{chunk-OMNHTCS4.cjs → chunk-LHUDAJQT.cjs} +3 -2
  13. package/dist/{chunk-KEYG7HGY.js → chunk-ONPVM4IV.js} +163 -3
  14. package/dist/{chunk-RMOEL2GD.cjs → chunk-PXVAPGTR.cjs} +31 -10
  15. package/dist/{chunk-656IAS7B.cjs → chunk-Q4K2GEMW.cjs} +10 -6
  16. package/dist/{chunk-VO52AJD5.js → chunk-RVNXBF2M.js} +3 -3
  17. package/dist/{chunk-7N63JHUN.cjs → chunk-RZ2EPXO3.cjs} +3 -3
  18. package/dist/{chunk-5H5GFBMG.cjs → chunk-SQAAWO4V.cjs} +111 -54
  19. package/dist/{chunk-W2GA3EQB.cjs → chunk-TC7JEO6R.cjs} +163 -3
  20. package/dist/{chunk-TX6ZBIF4.js → chunk-U3D4SYQ5.js} +3 -3
  21. package/dist/{chunk-EI5G5JLY.cjs → chunk-XF63CZ7E.cjs} +133 -21
  22. package/dist/{chunk-3KMANSQJ.cjs → chunk-Y57AVOM2.cjs} +4 -4
  23. package/dist/cli/holoscript-runner.cjs +9 -9
  24. package/dist/cli/holoscript-runner.js +2 -2
  25. package/dist/compiler/incremental.cjs +6 -6
  26. package/dist/compiler/incremental.js +1 -1
  27. package/dist/compiler/index.cjs +84 -84
  28. package/dist/compiler/index.js +5 -5
  29. package/dist/compiler/webgpu.cjs +3 -3
  30. package/dist/compiler/webgpu.js +2 -2
  31. package/dist/constants.cjs +3 -3
  32. package/dist/constants.js +1 -1
  33. package/dist/entries/scripting.cjs +1 -1
  34. package/dist/entries/scripting.js +1 -1
  35. package/dist/evolution/index.cjs +19 -15
  36. package/dist/evolution/index.d.ts +9 -0
  37. package/dist/evolution/index.js +4 -4
  38. package/dist/index.cjs +196 -180
  39. package/dist/index.js +15 -15
  40. package/dist/parser.cjs +13 -13
  41. package/dist/parser.js +3 -3
  42. package/dist/reconstruction/index.cjs +38 -38
  43. package/dist/reconstruction/index.js +2 -2
  44. package/dist/traits/index.cjs +4 -4
  45. package/dist/traits/index.js +1 -1
  46. package/package.json +20 -40
@@ -725,7 +725,7 @@ var ImportResolver = class {
725
725
  throw new Error(`File not found: '${canonicalPath}' (imported from '${importedBy}')`);
726
726
  }
727
727
  }
728
- const { HoloScriptPlusParser } = await import('./HoloScriptPlusParser-55AY4MUF.js');
728
+ const { HoloScriptPlusParser } = await import('./HoloScriptPlusParser-HQV6IJAH.js');
729
729
  const parser = new HoloScriptPlusParser({ enableTypeScriptImports: true });
730
730
  const result = parser.parse(source);
731
731
  if (!result.success && result.errors.length > 0) {
@@ -2003,5 +2003,5 @@ var UnauthorizedIncrementalCompilerAccessError = class extends UnauthorizedCompi
2003
2003
  };
2004
2004
 
2005
2005
  export { IncrementalCompiler, TraitDependencyGraph, UnauthorizedIncrementalCompilerAccessError, createIncrementalCompiler, deserializeCache, globalTraitGraph, serializeCache };
2006
- //# sourceMappingURL=chunk-VO52AJD5.js.map
2007
- //# sourceMappingURL=chunk-VO52AJD5.js.map
2006
+ //# sourceMappingURL=chunk-RVNXBF2M.js.map
2007
+ //# sourceMappingURL=chunk-RVNXBF2M.js.map
@@ -727,7 +727,7 @@ var ImportResolver = class {
727
727
  throw new Error(`File not found: '${canonicalPath}' (imported from '${importedBy}')`);
728
728
  }
729
729
  }
730
- const { HoloScriptPlusParser } = await import('./HoloScriptPlusParser-RNUCHOAJ.cjs');
730
+ const { HoloScriptPlusParser } = await import('./HoloScriptPlusParser-RFNHRMPE.cjs');
731
731
  const parser = new HoloScriptPlusParser({ enableTypeScriptImports: true });
732
732
  const result = parser.parse(source);
733
733
  if (!result.success && result.errors.length > 0) {
@@ -2011,5 +2011,5 @@ exports.createIncrementalCompiler = createIncrementalCompiler;
2011
2011
  exports.deserializeCache = deserializeCache;
2012
2012
  exports.globalTraitGraph = globalTraitGraph;
2013
2013
  exports.serializeCache = serializeCache;
2014
- //# sourceMappingURL=chunk-7N63JHUN.cjs.map
2015
- //# sourceMappingURL=chunk-7N63JHUN.cjs.map
2014
+ //# sourceMappingURL=chunk-RZ2EPXO3.cjs.map
2015
+ //# sourceMappingURL=chunk-RZ2EPXO3.cjs.map
@@ -5,8 +5,8 @@ var chunkN2ILK7BF_cjs = require('./chunk-N2ILK7BF.cjs');
5
5
 
6
6
  // src/version.ts
7
7
  var HOLOSCRIPT_VERSION = "8.0.12" ;
8
- var GIT_COMMIT_SHA = "530989456" ;
9
- var BUILD_TIMESTAMP = "2026-07-05T23:05:02.393Z" ;
8
+ var GIT_COMMIT_SHA = "65d9a9d46" ;
9
+ var BUILD_TIMESTAMP = "2026-07-07T08:36:54.702Z" ;
10
10
  function getVersionString() {
11
11
  const sha = `+${GIT_COMMIT_SHA}` ;
12
12
  return `${HOLOSCRIPT_VERSION}${sha}`;
@@ -1813,9 +1813,12 @@ function createPagedKVCache(config) {
1813
1813
  const freePhysicalPages = Array.from({ length: config.maxResidentPages }, (_, i) => i).reverse();
1814
1814
  const appendKernel = config.device ? createPagedKVAppendKernel(config.device) : null;
1815
1815
  const lookupKernel = config.device ? createPagedKVLookupKernel(config.device) : null;
1816
+ const residentPageStates = /* @__PURE__ */ new Set();
1816
1817
  let disposed = false;
1817
1818
  let nextPageId = 0;
1818
1819
  let accessClock = 0;
1820
+ let devicePageCount = 0;
1821
+ let hostPageCount = 0;
1819
1822
  function assertLive() {
1820
1823
  if (disposed) throw new Error("PagedKVCache has been disposed.");
1821
1824
  }
@@ -1850,9 +1853,7 @@ function createPagedKVCache(config) {
1850
1853
  valuePages.fill(0, offset, end);
1851
1854
  }
1852
1855
  function residentPages() {
1853
- return layers.flatMap(
1854
- (layer) => Array.from(layer.pages.values()).filter((page) => page.residency === "device")
1855
- );
1856
+ return Array.from(residentPageStates);
1856
1857
  }
1857
1858
  function evictPage(page) {
1858
1859
  if (page.physicalPage === null) return pageSnapshot(page);
@@ -1865,6 +1866,9 @@ function createPagedKVCache(config) {
1865
1866
  page.hostKeys = hostKeys;
1866
1867
  page.hostValues = hostValues;
1867
1868
  page.residency = "host";
1869
+ residentPageStates.delete(page);
1870
+ devicePageCount -= 1;
1871
+ hostPageCount += 1;
1868
1872
  return pageSnapshot(page);
1869
1873
  }
1870
1874
  function evictOldestInternal(count, protectedPage) {
@@ -1890,7 +1894,10 @@ function createPagedKVCache(config) {
1890
1894
  }
1891
1895
  const physicalPage = allocatePhysicalPage(page);
1892
1896
  page.physicalPage = physicalPage;
1897
+ if (page.residency === "host") hostPageCount -= 1;
1893
1898
  page.residency = "device";
1899
+ residentPageStates.add(page);
1900
+ devicePageCount += 1;
1894
1901
  page.lastAccess = ++accessClock;
1895
1902
  if (page.hostKeys) {
1896
1903
  keyPages.set(page.hostKeys, pageOffset(physicalPage));
@@ -1920,6 +1927,8 @@ function createPagedKVCache(config) {
1920
1927
  lastAccess: ++accessClock
1921
1928
  };
1922
1929
  layerState.pages.set(logicalPage, page);
1930
+ residentPageStates.add(page);
1931
+ devicePageCount += 1;
1923
1932
  return page;
1924
1933
  }
1925
1934
  function buildPageTable(layer, logicalPages) {
@@ -1937,27 +1946,32 @@ function createPagedKVCache(config) {
1937
1946
  }
1938
1947
  return pageTable;
1939
1948
  }
1940
- function appendCpu(pages, pageTable, slotMap, vectors) {
1941
- const out = new Float32Array(pages);
1949
+ function appendCpuInPlace(pages, touchedPages, vectors, startToken) {
1942
1950
  const numVecs = vectors.length / config.hiddenDim;
1943
1951
  for (let v = 0; v < numVecs; v += 1) {
1944
- const logicalPage = slotMap[v] >>> 16;
1945
- const inPage = slotMap[v] & 65535;
1946
- const physicalPage = pageTable[logicalPage];
1952
+ const token = startToken + v;
1953
+ const logicalPage = Math.floor(token / config.pageSize);
1954
+ const inPage = token % config.pageSize;
1955
+ const physicalPage = touchedPages.get(logicalPage)?.physicalPage;
1956
+ if (physicalPage === void 0 || physicalPage === null) {
1957
+ throw new Error(`PagedKVCache append misses resident logical page ${logicalPage}.`);
1958
+ }
1947
1959
  const base = physicalPage * config.pageSize * config.hiddenDim;
1948
1960
  for (let d = 0; d < config.hiddenDim; d += 1) {
1949
- out[base + inPage * config.hiddenDim + d] = vectors[v * config.hiddenDim + d];
1961
+ pages[base + inPage * config.hiddenDim + d] = vectors[v * config.hiddenDim + d];
1950
1962
  }
1951
1963
  }
1952
- return out;
1953
1964
  }
1954
- function lookupCpu(pages, pageTable, tokenCount, startToken) {
1965
+ function lookupCpuDirect(pages, layerState, tokenCount, startToken) {
1955
1966
  const out = new Float32Array(tokenCount * config.hiddenDim);
1956
1967
  for (let v = 0; v < tokenCount; v += 1) {
1957
1968
  const logicalSlot = startToken + v;
1958
1969
  const logicalPage = Math.floor(logicalSlot / config.pageSize);
1959
1970
  const inPage = logicalSlot % config.pageSize;
1960
- const physicalPage = pageTable[logicalPage];
1971
+ const physicalPage = layerState.pages.get(logicalPage)?.physicalPage;
1972
+ if (physicalPage === void 0 || physicalPage === null) {
1973
+ throw new Error(`PagedKVCache lookup misses resident logical page ${logicalPage}.`);
1974
+ }
1961
1975
  const base = physicalPage * config.pageSize * config.hiddenDim;
1962
1976
  for (let d = 0; d < config.hiddenDim; d += 1) {
1963
1977
  out[v * config.hiddenDim + d] = pages[base + inPage * config.hiddenDim + d];
@@ -1979,7 +1993,7 @@ function createPagedKVCache(config) {
1979
1993
  residentPagesForLayer(layer) {
1980
1994
  assertLive();
1981
1995
  validateLayer(layer);
1982
- return Array.from(layers[layer].pages.values()).filter((page) => page.residency === "device").sort((a, b) => a.firstToken - b.firstToken).map((page) => pageSnapshot(page));
1996
+ return Array.from(residentPageStates).filter((page) => page.layer === layer).sort((a, b) => a.firstToken - b.firstToken).map((page) => pageSnapshot(page));
1983
1997
  },
1984
1998
  evictOldest(count) {
1985
1999
  assertLive();
@@ -1990,23 +2004,18 @@ function createPagedKVCache(config) {
1990
2004
  },
1991
2005
  memoryUsage() {
1992
2006
  assertLive();
1993
- let devicePages = 0;
1994
- let hostPages = 0;
1995
- for (const layer of layers) {
1996
- for (const page of layer.pages.values()) {
1997
- if (page.residency === "device") devicePages += 1;
1998
- if (page.residency === "host") hostPages += 1;
1999
- }
2000
- }
2001
2007
  return {
2002
- device: devicePages * pageBytes * 2,
2003
- host: hostPages * pageBytes * 2
2008
+ device: devicePageCount * pageBytes * 2,
2009
+ host: hostPageCount * pageBytes * 2
2004
2010
  };
2005
2011
  },
2006
2012
  async dispose() {
2007
2013
  disposed = true;
2008
2014
  for (const layer of layers) layer.pages.clear();
2015
+ residentPageStates.clear();
2009
2016
  freePhysicalPages.length = 0;
2017
+ devicePageCount = 0;
2018
+ hostPageCount = 0;
2010
2019
  },
2011
2020
  async append(layer, keyVectors, valueVectors = keyVectors, firstToken) {
2012
2021
  assertLive();
@@ -2026,14 +2035,11 @@ function createPagedKVCache(config) {
2026
2035
  throw new Error(`PagedKVCache append firstToken must be non-negative, got ${startToken}`);
2027
2036
  }
2028
2037
  const touchedPages = /* @__PURE__ */ new Map();
2029
- const slotMap = new Uint32Array(tokenCount);
2030
2038
  const requiredPages = /* @__PURE__ */ new Set();
2031
2039
  for (let i = 0; i < tokenCount; i += 1) {
2032
2040
  const token = startToken + i;
2033
2041
  const logicalPage = Math.floor(token / config.pageSize);
2034
- const slot = token % config.pageSize;
2035
2042
  requiredPages.add(logicalPage);
2036
- slotMap[i] = logicalPage << 16 | slot;
2037
2043
  }
2038
2044
  if (requiredPages.size > config.maxResidentPages) {
2039
2045
  throw new Error(
@@ -2044,8 +2050,15 @@ function createPagedKVCache(config) {
2044
2050
  const page = allocatePageInternal(layer, logicalPage);
2045
2051
  touchedPages.set(logicalPage, page);
2046
2052
  }
2047
- const pageTable = buildPageTable(layer, touchedPages.keys());
2048
2053
  if (appendKernel) {
2054
+ const slotMap = new Uint32Array(tokenCount);
2055
+ for (let i = 0; i < tokenCount; i += 1) {
2056
+ const token = startToken + i;
2057
+ const logicalPage = Math.floor(token / config.pageSize);
2058
+ const slot = token % config.pageSize;
2059
+ slotMap[i] = logicalPage << 16 | slot;
2060
+ }
2061
+ const pageTable = buildPageTable(layer, touchedPages.keys());
2049
2062
  keyPages.set(
2050
2063
  await appendKernel.run(
2051
2064
  keyPages,
@@ -2067,8 +2080,8 @@ function createPagedKVCache(config) {
2067
2080
  )
2068
2081
  );
2069
2082
  } else {
2070
- keyPages.set(appendCpu(keyPages, pageTable, slotMap, keyVectors));
2071
- valuePages.set(appendCpu(valuePages, pageTable, slotMap, valueVectors));
2083
+ appendCpuInPlace(keyPages, touchedPages, keyVectors, startToken);
2084
+ appendCpuInPlace(valuePages, touchedPages, valueVectors, startToken);
2072
2085
  }
2073
2086
  for (const page of touchedPages.values()) page.lastAccess = ++accessClock;
2074
2087
  layers[layer].nextToken = Math.max(layers[layer].nextToken, startToken + tokenCount);
@@ -2105,8 +2118,8 @@ function createPagedKVCache(config) {
2105
2118
  for (const logicalPage of requiredPages) {
2106
2119
  ensurePageResident(layers[layer].pages.get(logicalPage));
2107
2120
  }
2108
- const pageTable = buildPageTable(layer, requiredPages);
2109
2121
  if (lookupKernel) {
2122
+ const pageTable = buildPageTable(layer, requiredPages);
2110
2123
  return {
2111
2124
  keys: await lookupKernel.run(
2112
2125
  keyPages,
@@ -2127,8 +2140,8 @@ function createPagedKVCache(config) {
2127
2140
  };
2128
2141
  }
2129
2142
  return {
2130
- keys: lookupCpu(keyPages, pageTable, tokenCount, startToken),
2131
- values: lookupCpu(valuePages, pageTable, tokenCount, startToken)
2143
+ keys: lookupCpuDirect(keyPages, layers[layer], tokenCount, startToken),
2144
+ values: lookupCpuDirect(valuePages, layers[layer], tokenCount, startToken)
2132
2145
  };
2133
2146
  }
2134
2147
  };
@@ -2206,6 +2219,9 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
2206
2219
  */
2207
2220
  this.globalPosSum = [0, 0, 0];
2208
2221
  this.globalConfSum = 0;
2222
+ this.globalPrincipalMomentXX = 0;
2223
+ this.globalPrincipalMomentZZ = 0;
2224
+ this.globalPrincipalMomentXZ = 0;
2209
2225
  /** Previous frame camera pose (centroid of that frame's points). */
2210
2226
  this.prevPose = null;
2211
2227
  /** Previous full camera pose, including scan-derived principal-axis rotation. */
@@ -2457,6 +2473,15 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
2457
2473
  const yaw = 0.5 * Math.atan2(2 * xz, xx - zz);
2458
2474
  return _HoloMapRuntimeImpl.yawToQuaternion(yaw);
2459
2475
  }
2476
+ static estimatePrincipalAxisRotationFromMoments(momentXX, momentZZ, momentXZ, pointCount, centroid) {
2477
+ if (pointCount < 2) return [0, 0, 0, 1];
2478
+ const xx = momentXX - pointCount * centroid[0] * centroid[0];
2479
+ const zz = momentZZ - pointCount * centroid[2] * centroid[2];
2480
+ const xz = momentXZ - pointCount * centroid[0] * centroid[2];
2481
+ if (xx + zz < 1e-12) return [0, 0, 0, 1];
2482
+ const yaw = 0.5 * Math.atan2(2 * xz, xx - zz);
2483
+ return _HoloMapRuntimeImpl.yawToQuaternion(yaw);
2484
+ }
2460
2485
  static yawToQuaternion(yaw) {
2461
2486
  const half = yaw / 2;
2462
2487
  return [0, Math.sin(half), 0, Math.cos(half)];
@@ -2492,17 +2517,23 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
2492
2517
  anchorDescriptor[3] ?? 0
2493
2518
  ]);
2494
2519
  }
2495
- static cloneTrajectoryKeyframes(keyframes) {
2496
- return keyframes.map((keyframe) => ({
2497
- frameIndex: keyframe.frameIndex,
2498
- timestampMs: keyframe.timestampMs,
2499
- pose: {
2500
- position: [...keyframe.pose.position],
2501
- rotation: [...keyframe.pose.rotation],
2502
- confidence: keyframe.pose.confidence
2503
- },
2504
- embedding: new Float32Array(keyframe.embedding)
2505
- }));
2520
+ static cloneTrajectoryKeyframes(keyframes, maxCount = keyframes.length) {
2521
+ const start = Math.max(0, keyframes.length - Math.max(0, maxCount));
2522
+ const out = [];
2523
+ for (let i = start; i < keyframes.length; i += 1) {
2524
+ const keyframe = keyframes[i];
2525
+ out.push({
2526
+ frameIndex: keyframe.frameIndex,
2527
+ timestampMs: keyframe.timestampMs,
2528
+ pose: {
2529
+ position: [...keyframe.pose.position],
2530
+ rotation: [...keyframe.pose.rotation],
2531
+ confidence: keyframe.pose.confidence
2532
+ },
2533
+ embedding: new Float32Array(keyframe.embedding)
2534
+ });
2535
+ }
2536
+ return out;
2506
2537
  }
2507
2538
  pushTrajectoryKeyframe(keyframe) {
2508
2539
  this.trajectoryKeyframes.push(keyframe);
@@ -2545,6 +2576,9 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
2545
2576
  this.trajectoryKeyframes.length = 0;
2546
2577
  this.globalPosSum = [0, 0, 0];
2547
2578
  this.globalConfSum = 0;
2579
+ this.globalPrincipalMomentXX = 0;
2580
+ this.globalPrincipalMomentZZ = 0;
2581
+ this.globalPrincipalMomentXZ = 0;
2548
2582
  this.lastAcceptedFrameTimestampMs = null;
2549
2583
  this.sessionStartMs = performance.now();
2550
2584
  this.perfMetrics = {
@@ -2813,9 +2847,15 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
2813
2847
  this.totalPointCount -= evictedPoints;
2814
2848
  const ep = evicted.points.positions;
2815
2849
  for (let i = 0; i < ep.length; i += 3) {
2816
- this.globalPosSum[0] -= ep[i];
2817
- this.globalPosSum[1] -= ep[i + 1];
2818
- this.globalPosSum[2] -= ep[i + 2];
2850
+ const x = ep[i];
2851
+ const y = ep[i + 1];
2852
+ const z = ep[i + 2];
2853
+ this.globalPosSum[0] -= x;
2854
+ this.globalPosSum[1] -= y;
2855
+ this.globalPosSum[2] -= z;
2856
+ this.globalPrincipalMomentXX -= x * x;
2857
+ this.globalPrincipalMomentZZ -= z * z;
2858
+ this.globalPrincipalMomentXZ -= x * z;
2819
2859
  }
2820
2860
  const ec = evicted.points.confidence;
2821
2861
  for (let i = 0; i < ec.length; i += 1) this.globalConfSum -= ec[i];
@@ -2900,6 +2940,9 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
2900
2940
  let centroidY = 0;
2901
2941
  let centroidZ = 0;
2902
2942
  let confidenceSum = 0;
2943
+ let framePrincipalMomentXX = 0;
2944
+ let framePrincipalMomentZZ = 0;
2945
+ let framePrincipalMomentXZ = 0;
2903
2946
  for (let t = 0; t < numPoints; t += 1) {
2904
2947
  const { meanColor, centerUv, texture, latent, rawDepthSignal } = encodedTiles[t];
2905
2948
  const [latentX, latentY, latentZ] = latent;
@@ -2958,6 +3001,9 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
2958
3001
  centroidX += px;
2959
3002
  centroidY += py;
2960
3003
  centroidZ += pz;
3004
+ framePrincipalMomentXX += px * px;
3005
+ framePrincipalMomentZZ += pz * pz;
3006
+ framePrincipalMomentXZ += px * pz;
2961
3007
  }
2962
3008
  const inv = 1 / Math.max(1, numPoints);
2963
3009
  const poseX = centroidX * inv;
@@ -3039,6 +3085,9 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
3039
3085
  this.globalPosSum[1] += centroidY;
3040
3086
  this.globalPosSum[2] += centroidZ;
3041
3087
  this.globalConfSum += confidenceSum;
3088
+ this.globalPrincipalMomentXX += framePrincipalMomentXX;
3089
+ this.globalPrincipalMomentZZ += framePrincipalMomentZZ;
3090
+ this.globalPrincipalMomentXZ += framePrincipalMomentXZ;
3042
3091
  const totalPoints = this.totalPointCount + numPoints;
3043
3092
  const ginv = 1 / Math.max(1, totalPoints);
3044
3093
  const anchorCenter = [
@@ -3051,8 +3100,11 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
3051
3100
  const extentX = b.max[0] - b.min[0];
3052
3101
  const extentY = b.max[1] - b.min[1];
3053
3102
  const extentZ = b.max[2] - b.min[2];
3054
- const anchorRotation = _HoloMapRuntimeImpl.estimatePrincipalAxisRotation(
3055
- _HoloMapRuntimeImpl.retainedPositionChunks(this.steps, positions),
3103
+ const anchorRotation = _HoloMapRuntimeImpl.estimatePrincipalAxisRotationFromMoments(
3104
+ this.globalPrincipalMomentXX,
3105
+ this.globalPrincipalMomentZZ,
3106
+ this.globalPrincipalMomentXZ,
3107
+ totalPoints,
3056
3108
  anchorCenter
3057
3109
  );
3058
3110
  const anchorDescriptor = new Float32Array([extentX, extentY, extentZ, globalMeanConfidence]);
@@ -3068,7 +3120,8 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
3068
3120
  )
3069
3121
  });
3070
3122
  const trajectoryKeyframes = _HoloMapRuntimeImpl.cloneTrajectoryKeyframes(
3071
- this.trajectoryKeyframes
3123
+ this.trajectoryKeyframes,
3124
+ _HoloMapRuntimeImpl.TRAJECTORY_STEP_SNAPSHOT_CAP
3072
3125
  );
3073
3126
  const step = {
3074
3127
  frame,
@@ -3086,7 +3139,7 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
3086
3139
  revision: frame.index + 1
3087
3140
  },
3088
3141
  anchor: {
3089
- anchorFrameIndex: trajectoryKeyframes[0]?.frameIndex ?? frame.index,
3142
+ anchorFrameIndex: this.trajectoryKeyframes[0]?.frameIndex ?? frame.index,
3090
3143
  anchorPose: {
3091
3144
  position: anchorCenter,
3092
3145
  rotation: anchorRotation,
@@ -3208,6 +3261,9 @@ var _HoloMapRuntimeImpl = class _HoloMapRuntimeImpl {
3208
3261
  this.trajectoryKeyframes.length = 0;
3209
3262
  this.globalPosSum = [0, 0, 0];
3210
3263
  this.globalConfSum = 0;
3264
+ this.globalPrincipalMomentXX = 0;
3265
+ this.globalPrincipalMomentZZ = 0;
3266
+ this.globalPrincipalMomentXZ = 0;
3211
3267
  this.boundsValid = false;
3212
3268
  this.lastAcceptedFrameTimestampMs = null;
3213
3269
  this.perfMetrics = {
@@ -3223,6 +3279,7 @@ _HoloMapRuntimeImpl.LOOP_CLOSURE_MIN_GAP = 3;
3223
3279
  _HoloMapRuntimeImpl.LOOP_CLOSURE_RADIUS = 0.05;
3224
3280
  _HoloMapRuntimeImpl.POSE_HISTORY_CAP = 512;
3225
3281
  _HoloMapRuntimeImpl.TRAJECTORY_KEYFRAME_CAP = 512;
3282
+ _HoloMapRuntimeImpl.TRAJECTORY_STEP_SNAPSHOT_CAP = 64;
3226
3283
  /** Maps normalized measured depth [0=near,1=far] to a point Z range matching
3227
3284
  * the monocular estimate's spread (±~0.17), so sensor and estimate paths
3228
3285
  * produce geometry in the same coordinate scale. */
@@ -3276,5 +3333,5 @@ exports.selfAttestReconstructionManifest = selfAttestReconstructionManifest;
3276
3333
  exports.serializeMicroWeights = serializeMicroWeights;
3277
3334
  exports.stripAnchoredProvenance = stripAnchoredProvenance;
3278
3335
  exports.tryCreateHoloMapEncoderDevice = tryCreateHoloMapEncoderDevice;
3279
- //# sourceMappingURL=chunk-5H5GFBMG.cjs.map
3280
- //# sourceMappingURL=chunk-5H5GFBMG.cjs.map
3336
+ //# sourceMappingURL=chunk-SQAAWO4V.cjs.map
3337
+ //# sourceMappingURL=chunk-SQAAWO4V.cjs.map
@@ -56,8 +56,10 @@ var init_WebGPUCompiler = chunkZHPMP447_cjs.__esm({
56
56
  this.emitDeviceInit();
57
57
  this.emitGeometryHelpers();
58
58
  this.emitShaderSources();
59
- if (composition.environment) this.emitEnvironment(composition.environment);
59
+ this.emitEnvironment(composition.environment ?? { type: "Environment", properties: [] });
60
60
  this.emitCamera(composition.camera);
61
+ this.emit("const holoGraphObjects = [];");
62
+ this.emit("");
61
63
  if (composition.objects) {
62
64
  for (const obj of composition.objects) this.emitObject(obj);
63
65
  }
@@ -69,6 +71,7 @@ var init_WebGPUCompiler = chunkZHPMP447_cjs.__esm({
69
71
  }
70
72
  if (this.options.enableCompute) this.emitComputeShaders(composition);
71
73
  this.emitWebGPUDomainBlocks(composition);
74
+ this.emitViewportRuntime();
72
75
  this.emitRenderLoop(composition);
73
76
  return this.lines.join("\n");
74
77
  }
@@ -497,6 +500,9 @@ var init_WebGPUCompiler = chunkZHPMP447_cjs.__esm({
497
500
  this.emit(
498
501
  `const ${v}Mat = createBuffer(device, new Float32Array([${cr},${cg},${cb},1.0, ${rough},${metal},${emissiveStrength},0, ${er},${eg},${eb},0]), GPUBufferUsage.UNIFORM);`
499
502
  );
503
+ this.emit(
504
+ `holoGraphObjects.push({ id: "${this.escapeStringValue(obj.name, "TypeScript")}", name: "${this.escapeStringValue(obj.name, "TypeScript")}", geometry: "${this.escapeStringValue(meshType, "TypeScript")}", traits: ${this.json((obj.traits || []).map((t) => t.name))}, properties: ${this.json(this.extractObjectProperties(obj))}, basePosition: [${px},${py},${pz}], position: [${px},${py},${pz}], baseScale: [${sx},${sy},${sz}], scale: [${sx},${sy},${sz}], baseColor: [${cr},${cg},${cb}], color: [${cr},${cg},${cb}], material: { roughness: ${rough}, metalness: ${metal}, emissiveStrength: ${emissiveStrength}, emissive: [${er},${eg},${eb}] }, modelBuffer: ${v}Model, matBuffer: ${v}Mat, visible: true });`
505
+ );
500
506
  this.emit(`const ${v}Pipeline = device.createRenderPipeline({`);
501
507
  this.indent();
502
508
  this.emit('layout: "auto",');
@@ -857,6 +863,145 @@ var init_WebGPUCompiler = chunkZHPMP447_cjs.__esm({
857
863
  this.emit("");
858
864
  }
859
865
  // ─── Render Loop ──────────────────────────────────────────────────────────
866
+ emitViewportRuntime() {
867
+ this.emit("// === HoloGraph Viewport Runtime ===");
868
+ this.emit("const holoGraphInitialCameraPos = new Float32Array(cameraPos);");
869
+ this.emit("const holoGraphInitialCameraTarget = new Float32Array(cameraTarget);");
870
+ this.emit(
871
+ 'const holoGraphViewportState = { actionCount: 0, lastAction: null, filter: "all", colorMode: "semantic", structureStyle: "flow", labelPolicy: "none", focusId: null };'
872
+ );
873
+ this.emit('document.body.style.position = document.body.style.position || "relative";');
874
+ this.emit('const holoGraphLabelLayer = document.createElement("div");');
875
+ this.emit('holoGraphLabelLayer.setAttribute("data-holograph-label-layer", "true");');
876
+ this.emit('Object.assign(holoGraphLabelLayer.style, { position: "absolute", inset: "0", pointerEvents: "none", overflow: "hidden", fontFamily: "ui-monospace, SFMono-Regular, Consolas, monospace", zIndex: "5" });');
877
+ this.emit('canvas.insertAdjacentElement("afterend", holoGraphLabelLayer);');
878
+ this.emit("function hsVec3(v) { return [Number(v[0] || 0), Number(v[1] || 0), Number(v[2] || 0)]; }");
879
+ this.emit("function hsSub(a, b) { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; }");
880
+ this.emit("function hsAdd(a, b) { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; }");
881
+ this.emit("function hsMul(v, s) { return [v[0] * s, v[1] * s, v[2] * s]; }");
882
+ this.emit("function hsLen(v) { return Math.hypot(v[0], v[1], v[2]) || 1; }");
883
+ this.emit("function hsNorm(v) { const l = hsLen(v); return [v[0] / l, v[1] / l, v[2] / l]; }");
884
+ this.emit(
885
+ "function hsCross(a, b) { return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; }"
886
+ );
887
+ this.emit(
888
+ 'function hsHash(text) { let h = 2166136261; const s = String(text || ""); for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; }'
889
+ );
890
+ this.emit(
891
+ "function hsHueRgb(h) { const x = (n) => { const k = (n + h * 6) % 6; return Math.max(0, Math.min(1, Math.min(k, 4 - k, 1))); }; return [x(5), x(3), x(1)]; }"
892
+ );
893
+ this.emit(
894
+ "function hsGraphCenter() { if (!holoGraphObjects.length) return [0, 0, 0]; const sum = holoGraphObjects.reduce((acc, o) => [acc[0] + o.basePosition[0], acc[1] + o.basePosition[1], acc[2] + o.basePosition[2]], [0, 0, 0]); return hsMul(sum, 1 / holoGraphObjects.length); }"
895
+ );
896
+ this.emit(
897
+ "function hsWriteModel(o) { const s = o.visible ? o.scale : [0, 0, 0]; device.queue.writeBuffer(o.modelBuffer, 0, new Float32Array([s[0],0,0,0, 0,s[1],0,0, 0,0,s[2],0, o.position[0],o.position[1],o.position[2],1])); }"
898
+ );
899
+ this.emit(
900
+ "function hsWriteMaterial(o, color) { o.color = [color[0], color[1], color[2]]; const m = o.material; device.queue.writeBuffer(o.matBuffer, 0, new Float32Array([color[0],color[1],color[2],1.0, m.roughness,m.metalness,m.emissiveStrength,0, m.emissive[0],m.emissive[1],m.emissive[2],0])); }"
901
+ );
902
+ this.emit("function hsCameraDistance() { return hsLen(hsSub(hsVec3(cameraTarget), hsVec3(cameraPos))); }");
903
+ this.emit(
904
+ "function hsSetCamera(pos, target) { cameraPos.set(pos); cameraTarget.set(target); device.queue.writeBuffer(vpUniform, 0, buildViewProjection()); document.body.dataset.holoscriptWebgpuCamera = JSON.stringify({ position: Array.from(cameraPos), target: Array.from(cameraTarget) }); }"
905
+ );
906
+ this.emit(
907
+ "function hsCameraBasis() { const forward = hsNorm(hsSub(hsVec3(cameraTarget), hsVec3(cameraPos))); const worldUp = [0, 1, 0]; const right = hsNorm(hsCross(forward, worldUp)); const up = hsNorm(hsCross(right, forward)); return { forward, right, up }; }"
908
+ );
909
+ this.emit(
910
+ 'function hsMatchesFilter(o, filter) { const rawKind = o.properties.symbolType || o.properties.symbol_type || o.properties.kind || o.properties.role || o.properties.type || ""; const symbolType = String(rawKind).toLowerCase(); const traits = (o.traits || []).map((t) => String(t).toLowerCase()); if (filter === "all") return true; if (filter === "call") return symbolType === "function" || symbolType === "method" || symbolType === "call" || traits.includes("function") || traits.includes("method") || traits.includes("call"); if (filter === "import") return symbolType === "module" || symbolType === "namespace" || symbolType === "package" || symbolType === "import" || traits.includes("module") || traits.includes("namespace") || traits.includes("package") || traits.includes("import"); return true; }'
911
+ );
912
+ this.emit(
913
+ 'function hsColorForMode(o, mode) { if (mode === "identity_hue") return hsHueRgb((hsHash(o.id) % 10000) / 10000); if (mode === "file_hue") return hsHueRgb((hsHash(o.properties.file || o.properties.path || o.id) % 10000) / 10000); if (mode === "relation_hue") return hsHueRgb((hsHash(o.properties.symbolType || o.properties.symbol_type || o.properties.kind || o.properties.role || (o.traits || []).join(",")) % 10000) / 10000); return o.baseColor; }'
914
+ );
915
+ this.emit("function hsRound(n) { return Math.round(Number(n || 0) * 1000) / 1000; }");
916
+ this.emit("function hsRoundVec(v) { return [hsRound(v?.[0]), hsRound(v?.[1]), hsRound(v?.[2])]; }");
917
+ this.emit(
918
+ 'function hsHueBucket(o, mode = holoGraphViewportState.colorMode) { if (mode === "identity_hue") return hsHash(o.id) % 10000; if (mode === "file_hue") return hsHash(o.properties.file || o.properties.path || o.id) % 10000; if (mode === "relation_hue") return hsHash(o.properties.symbolType || o.properties.symbol_type || o.properties.kind || o.properties.role || (o.traits || []).join(",")) % 10000; return hsHash(JSON.stringify(o.color || o.baseColor || [])) % 10000; }'
919
+ );
920
+ this.emit(
921
+ 'function hsObjectRole(o) { return String(o.properties.symbolType || o.properties.symbol_type || o.properties.kind || o.properties.role || o.properties.type || (o.traits || [])[0] || "object"); }'
922
+ );
923
+ this.emit(
924
+ 'function hsCounts(items, keyFn, limit = 12) { const m = new Map(); for (const item of items) { const key = String(keyFn(item) || "unknown"); m.set(key, (m.get(key) || 0) + 1); } return Array.from(m.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit).map(([key, count]) => ({ key, count })); }'
925
+ );
926
+ this.emit(
927
+ 'function hsCompactObject(o, index) { return { index, id: o.id, name: o.name, geometry: o.geometry || "mesh", shape: o.geometry || "mesh", role: hsObjectRole(o), traits: o.traits || [], file: o.properties.file || null, path: o.properties.path || null, signature: o.properties.signature || null, language: o.properties.language || null, position: hsRoundVec(o.position), scale: hsRoundVec(o.scale), color: hsRoundVec(o.color || o.baseColor), hueBucket: hsHueBucket(o), visible: o.visible, projection: hsProjectPoint(o.position) }; }'
928
+ );
929
+ this.emit(
930
+ 'function hsResolveObject(payload = {}) { const key = payload && (payload.nodeId ?? payload.id ?? payload.target ?? payload.anchorId ?? payload.name); let obj = null; if (typeof payload?.anchorIndex === "number") obj = holoGraphObjects[payload.anchorIndex] || null; if (!obj && typeof key === "number") obj = holoGraphObjects[key] || null; if (!obj && key !== undefined && key !== null) obj = holoGraphObjects.find((o) => o.id === String(key) || o.name === String(key)); if (!obj) obj = holoGraphObjects.find((o) => o.visible) || holoGraphObjects[0] || null; return obj; }'
931
+ );
932
+ this.emit(
933
+ "function hsNearestObjects(obj, limit = 8) { if (!obj) return []; return holoGraphObjects.filter((o) => o.visible && o !== obj).map((o) => ({ object: o, distance: hsLen(hsSub(o.position, obj.position)) })).sort((a, b) => a.distance - b.distance).slice(0, Math.max(0, Math.min(32, Number(limit) || 8))).map((entry) => ({ ...hsCompactObject(entry.object, holoGraphObjects.indexOf(entry.object)), distance: hsRound(entry.distance) })); }"
934
+ );
935
+ this.emit(
936
+ 'function hsDescribeScene(payload = {}) { const limit = Math.max(1, Math.min(128, Number(payload.limit || 24))); const visible = holoGraphObjects.filter((o) => o.visible); return { canonicalNames: { graphSurface: "HoloGraph", embeddingTower: "HoloEmbed", visionNavigator: "HoloLlama" }, actionContract: ["pan_left","pan_right","pan_up","pan_down","zoom_in","zoom_out","zoom_to_fit","focus_anchor","focus_node","filter_call","filter_import","filter_all","set_color_mode","set_structure_style","set_label_policy","reset_view"], projectionBridge: "renderer-native", canvas: { width: canvas.width, height: canvas.height, clientWidth: canvas.clientWidth, clientHeight: canvas.clientHeight }, camera: { position: hsRoundVec(cameraPos), target: hsRoundVec(cameraTarget), distance: hsRound(hsCameraDistance()) }, actionCount: holoGraphViewportState.actionCount, lastAction: holoGraphViewportState.lastAction, filter: holoGraphViewportState.filter, colorMode: holoGraphViewportState.colorMode, structureStyle: holoGraphViewportState.structureStyle, labelPolicy: holoGraphViewportState.labelPolicy, objectCount: holoGraphObjects.length, visibleObjects: visible.length, shapes: hsCounts(visible, (o) => o.geometry || "mesh"), roles: hsCounts(visible, hsObjectRole), hueBuckets: hsCounts(visible, (o) => `hue_${hsHueBucket(o)}`, 16), files: hsCounts(visible, (o) => o.properties.file || o.properties.path || "unknown", 16), objects: visible.slice(0, limit).map((o) => hsCompactObject(o, holoGraphObjects.indexOf(o))) }; }'
937
+ );
938
+ this.emit(
939
+ "function hsProjectObject(payload = {}) { const obj = hsResolveObject(payload); if (!obj) return null; return { ...hsCompactObject(obj, holoGraphObjects.indexOf(obj)), cameraDistance: hsRound(hsLen(hsSub(hsVec3(cameraPos), obj.position))) }; }"
940
+ );
941
+ this.emit(
942
+ "function hsInspectObject(payload = {}) { const obj = hsResolveObject(payload); if (!obj) return null; return { ...hsProjectObject(payload), properties: obj.properties, neighbors: hsNearestObjects(obj, payload.limit || 8) }; }"
943
+ );
944
+ this.emit('function hsNormalizeLabelPolicy(value) { const policy = String(value || "none").toLowerCase(); return ["none","ids","signatures","details"].includes(policy) ? policy : "none"; }');
945
+ this.emit(
946
+ 'function hsLabelText(o) { const policy = holoGraphViewportState.labelPolicy; if (policy === "none") return ""; if (policy === "ids") return o.id; if (policy === "signatures") return o.properties.signature || o.id; return `${hsObjectRole(o)}:${o.properties.signature || o.id}`; }'
947
+ );
948
+ this.emit(
949
+ 'function hsProjectPoint(p) { const m = buildViewProjection(); const x = Number(p?.[0] || 0), y = Number(p?.[1] || 0), z = Number(p?.[2] || 0); const cx = m[0]*x + m[4]*y + m[8]*z + m[12]; const cy = m[1]*x + m[5]*y + m[9]*z + m[13]; const cz = m[2]*x + m[6]*y + m[10]*z + m[14]; const cw = m[3]*x + m[7]*y + m[11]*z + m[15]; const clip = { x: hsRound(cx), y: hsRound(cy), z: hsRound(cz), w: hsRound(cw) }; if (!Number.isFinite(cw) || Math.abs(cw) < 0.00001) return { source: "renderer-native", valid: false, reason: "invalid-w", inFrame: false, clip }; const ndcX = cx / cw, ndcY = cy / cw, ndcZ = cz / cw; const normalizedX = ndcX * 0.5 + 0.5; const normalizedY = 0.5 - ndcY * 0.5; const width = canvas.clientWidth || canvas.width || 1; const height = canvas.clientHeight || canvas.height || 1; const screenX = normalizedX * width; const screenY = normalizedY * height; const centerDistance = Math.hypot(normalizedX - 0.5, normalizedY - 0.5); return { source: "renderer-native", valid: Number.isFinite(normalizedX) && Number.isFinite(normalizedY), inFrame: ndcX >= -1 && ndcX <= 1 && ndcY >= -1 && ndcY <= 1, ndc: { x: hsRound(ndcX), y: hsRound(ndcY), z: hsRound(ndcZ) }, normalizedX: hsRound(normalizedX), normalizedY: hsRound(normalizedY), screenX: hsRound(screenX), screenY: hsRound(screenY), centerDistance: hsRound(centerDistance), clip }; }'
950
+ );
951
+ this.emit(
952
+ 'function hsRenderLabels() { const policy = holoGraphViewportState.labelPolicy; document.body.dataset.holoscriptWebgpuLabels = policy; if (policy === "none") { holoGraphLabelLayer.replaceChildren(); return; } const ranked = holoGraphObjects.filter((o) => o.visible).map((o) => ({ object: o, distance: hsLen(hsSub(o.position, hsVec3(cameraTarget))) })).sort((a, b) => a.distance - b.distance).slice(0, 18); const nodes = []; for (const entry of ranked) { const o = entry.object; const text = hsLabelText(o); const point = hsProjectPoint(o.position); if (!text || !point?.valid || !point.inFrame) continue; const focus = entry.distance <= 0.001 || o.id === holoGraphViewportState.focusId; const el = document.createElement("div"); el.textContent = text.length > 72 ? `${text.slice(0, 69)}...` : text; Object.assign(el.style, { position: "absolute", left: `${point.screenX}px`, top: `${point.screenY}px`, transform: "translate(-50%, -120%)", maxWidth: "280px", padding: focus ? "4px 7px" : "3px 6px", border: focus ? "2px solid rgba(250,204,21,0.98)" : "1px solid rgba(255,255,255,0.68)", borderRadius: "3px", background: focus ? "rgba(15,23,42,0.94)" : "rgba(2,6,23,0.86)", color: focus ? "#fef9c3" : "#f8fafc", fontSize: focus ? "13px" : "12px", fontWeight: focus ? "700" : "600", lineHeight: "1.2", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", textShadow: "0 1px 2px #000" }); nodes.push(el); } const focusObj = holoGraphViewportState.focusId ? holoGraphObjects.find((o) => o.id === holoGraphViewportState.focusId && o.visible) : null; if (focusObj) { const panel = document.createElement("div"); Object.assign(panel.style, { position: "absolute", left: "12px", top: "12px", maxWidth: "560px", padding: "8px 10px", border: "2px solid rgba(250,204,21,0.98)", borderRadius: "4px", background: "rgba(15,23,42,0.96)", color: "#fef9c3", fontSize: "13px", fontWeight: "700", lineHeight: "1.35", textShadow: "0 1px 2px #000", boxShadow: "0 10px 30px rgba(0,0,0,0.34)" }); const near = holoGraphObjects.filter((o) => o.visible && o !== focusObj).map((o) => ({ object: o, distance: hsLen(hsSub(o.position, focusObj.position)) })).sort((a, b) => a.distance - b.distance).slice(0, 5); const rows = [{ kind: "target", object: focusObj }, ...near.map((entry) => ({ kind: "near", object: entry.object }))]; for (const row of rows) { const line = document.createElement("div"); const text = `${row.kind} ${hsLabelText(row.object)}`; line.textContent = text.length > 96 ? `${text.slice(0, 93)}...` : text; panel.appendChild(line); } nodes.push(panel); } holoGraphLabelLayer.replaceChildren(...nodes); }'
953
+ );
954
+ this.emit("function hsApplyLabelPolicy(policy) { holoGraphViewportState.labelPolicy = hsNormalizeLabelPolicy(policy); hsRenderLabels(); }");
955
+ this.emit("function hsApplyFilter(filter) { holoGraphViewportState.filter = filter; for (const o of holoGraphObjects) { o.visible = hsMatchesFilter(o, filter); hsWriteModel(o); } }");
956
+ this.emit("function hsApplyColorMode(mode) { holoGraphViewportState.colorMode = mode; for (const o of holoGraphObjects) hsWriteMaterial(o, hsColorForMode(o, mode)); }");
957
+ this.emit(
958
+ 'function hsApplyStructureStyle(style) { const center = hsGraphCenter(); const n = Math.max(1, holoGraphObjects.length); const radius = Math.max(20, Math.sqrt(n) * 18); holoGraphViewportState.structureStyle = style; holoGraphObjects.forEach((o, i) => { if (style === "compact") { o.position = hsAdd(center, hsMul(hsSub(o.basePosition, center), 0.45)); } else if (style === "radial") { const a = (i / n) * Math.PI * 2; o.position = [center[0] + Math.cos(a) * radius, center[1] + (o.basePosition[1] - center[1]) * 0.18, center[2] + Math.sin(a) * radius]; } else { o.position = [...o.basePosition]; } hsWriteModel(o); }); }'
959
+ );
960
+ this.emit(
961
+ "function hsFocusObject(payload) { const obj = hsResolveObject(payload); if (!obj) return false; holoGraphViewportState.focusId = obj.id; const target = [...obj.position]; const away = hsNorm(hsSub(hsVec3(cameraPos), hsVec3(cameraTarget))); const distance = Math.max(8, hsCameraDistance() * 0.45); hsSetCamera(hsAdd(target, hsMul(away, distance)), target); return true; }"
962
+ );
963
+ this.emit(
964
+ 'function hsViewportSnapshot() { const scene = hsDescribeScene({ limit: 8 }); return { camera: { position: Array.from(cameraPos), target: Array.from(cameraTarget), distance: hsCameraDistance() }, projectionBridge: "renderer-native", canvas: scene.canvas, actionCount: holoGraphViewportState.actionCount, lastAction: holoGraphViewportState.lastAction, filter: holoGraphViewportState.filter, colorMode: holoGraphViewportState.colorMode, structureStyle: holoGraphViewportState.structureStyle, labelPolicy: holoGraphViewportState.labelPolicy, focusId: holoGraphViewportState.focusId, objectCount: scene.objectCount, visibleObjects: scene.visibleObjects, sceneSummary: { shapes: scene.shapes, roles: scene.roles, hueBuckets: scene.hueBuckets, files: scene.files }, sampleObjects: scene.objects }; }'
965
+ );
966
+ this.emit("function hsApplyViewportAction(action, payload = {}) {");
967
+ this.indent();
968
+ this.emit('const type = typeof action === "string" ? action : String(action?.type || action?.action || "");');
969
+ this.emit('const data = typeof action === "object" && action !== null ? { ...action, ...payload } : payload || {};');
970
+ this.emit("const before = hsViewportSnapshot();");
971
+ this.emit("const basis = hsCameraBasis();");
972
+ this.emit("const distance = hsCameraDistance();");
973
+ this.emit("const pan = Math.max(1, distance * 0.08);");
974
+ this.emit("let ok = true;");
975
+ this.emit('if (type === "pan_left") hsSetCamera(hsAdd(hsVec3(cameraPos), hsMul(basis.right, -pan)), hsAdd(hsVec3(cameraTarget), hsMul(basis.right, -pan)));');
976
+ this.emit('else if (type === "pan_right") hsSetCamera(hsAdd(hsVec3(cameraPos), hsMul(basis.right, pan)), hsAdd(hsVec3(cameraTarget), hsMul(basis.right, pan)));');
977
+ this.emit('else if (type === "pan_up") hsSetCamera(hsAdd(hsVec3(cameraPos), hsMul(basis.up, pan)), hsAdd(hsVec3(cameraTarget), hsMul(basis.up, pan)));');
978
+ this.emit('else if (type === "pan_down") hsSetCamera(hsAdd(hsVec3(cameraPos), hsMul(basis.up, -pan)), hsAdd(hsVec3(cameraTarget), hsMul(basis.up, -pan)));');
979
+ this.emit('else if (type === "zoom_in") hsSetCamera(hsAdd(hsVec3(cameraPos), hsMul(basis.forward, distance * 0.22)), hsVec3(cameraTarget));');
980
+ this.emit('else if (type === "zoom_out") hsSetCamera(hsAdd(hsVec3(cameraPos), hsMul(basis.forward, -distance * 0.25)), hsVec3(cameraTarget));');
981
+ this.emit('else if (type === "zoom_to_fit" || type === "reset_view") { holoGraphViewportState.focusId = null; hsSetCamera(Array.from(holoGraphInitialCameraPos), Array.from(holoGraphInitialCameraTarget)); }');
982
+ this.emit('else if (type === "focus_anchor" || type === "focus_node") ok = hsFocusObject(data);');
983
+ this.emit('else if (type === "filter_call") hsApplyFilter("call");');
984
+ this.emit('else if (type === "filter_import") hsApplyFilter("import");');
985
+ this.emit('else if (type === "filter_all") hsApplyFilter("all");');
986
+ this.emit('else if (type === "set_color_mode") hsApplyColorMode(String(data.mode || data.colorMode || "semantic"));');
987
+ this.emit('else if (type === "set_structure_style") hsApplyStructureStyle(String(data.style || data.structureStyle || "flow"));');
988
+ this.emit('else if (type === "set_label_policy") hsApplyLabelPolicy(data.policy || data.labelPolicy || "none");');
989
+ this.emit("else ok = false;");
990
+ this.emit("if (!ok) return { ok: false, action: type, before, after: hsViewportSnapshot() };");
991
+ this.emit("holoGraphViewportState.actionCount++;");
992
+ this.emit("holoGraphViewportState.lastAction = type;");
993
+ this.emit("document.body.dataset.holoscriptWebgpuLastAction = type;");
994
+ this.emit("return { ok: true, action: type, before, after: hsViewportSnapshot() };");
995
+ this.dedent();
996
+ this.emit("}");
997
+ this.emit("globalThis.holoscriptWebgpuViewport = { applyAction: hsApplyViewportAction, getState: hsViewportSnapshot, describeScene: hsDescribeScene, inspectObject: hsInspectObject, projectObject: hsProjectObject, projectPoint: hsProjectPoint };");
998
+ this.emit("globalThis.HoloGraphViewport = globalThis.holoscriptWebgpuViewport;");
999
+ this.emit('document.body.dataset.holoscriptWebgpuViewport = "ready";');
1000
+ this.emit('document.body.dataset.holoscriptWebgpuInspector = "ready";');
1001
+ this.emit('document.body.dataset.holoscriptWebgpuLabels = "none";');
1002
+ this.emit("hsSetCamera(Array.from(cameraPos), Array.from(cameraTarget));");
1003
+ this.emit("");
1004
+ }
860
1005
  emitRenderLoop(composition) {
861
1006
  this.emit("// === Render Loop ===");
862
1007
  this.emit("let frameCount = 0;");
@@ -865,6 +1010,8 @@ var init_WebGPUCompiler = chunkZHPMP447_cjs.__esm({
865
1010
  this.indent();
866
1011
  this.emit("frameCount++;");
867
1012
  this.emit("const time = (performance.now() - t0) / 1000.0;");
1013
+ this.emit("device.queue.writeBuffer(vpUniform, 0, buildViewProjection());");
1014
+ this.emit("hsRenderLabels();");
868
1015
  this.emit("const enc = device.createCommandEncoder();");
869
1016
  const gpuObjs = (composition.objects || []).filter(
870
1017
  (o) => o.traits?.some(
@@ -1007,6 +1154,19 @@ var init_WebGPUCompiler = chunkZHPMP447_cjs.__esm({
1007
1154
  findObjProp(obj, key) {
1008
1155
  return obj.properties?.find((p) => p.key === key)?.value;
1009
1156
  }
1157
+ extractObjectProperties(obj) {
1158
+ const properties = {};
1159
+ for (const prop of obj.properties ?? []) {
1160
+ if (!prop.key) continue;
1161
+ const value = prop.value;
1162
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || Array.isArray(value) && value.every(
1163
+ (item) => typeof item === "string" || typeof item === "number" || typeof item === "boolean"
1164
+ )) {
1165
+ properties[prop.key] = value;
1166
+ }
1167
+ }
1168
+ return properties;
1169
+ }
1010
1170
  parseColor(value) {
1011
1171
  if (typeof value === "string" && value.startsWith("#")) {
1012
1172
  const h = value.slice(1);
@@ -1061,5 +1221,5 @@ var init_WebGPUCompiler = chunkZHPMP447_cjs.__esm({
1061
1221
 
1062
1222
  exports.WebGPUCompiler_exports = WebGPUCompiler_exports;
1063
1223
  exports.init_WebGPUCompiler = init_WebGPUCompiler;
1064
- //# sourceMappingURL=chunk-W2GA3EQB.cjs.map
1065
- //# sourceMappingURL=chunk-W2GA3EQB.cjs.map
1224
+ //# sourceMappingURL=chunk-TC7JEO6R.cjs.map
1225
+ //# sourceMappingURL=chunk-TC7JEO6R.cjs.map