@myzonerocks/gosslens 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import type { GossSession } from "./index";
1
+ import type { GossSession } from "./index.js";
2
2
  export declare class GossMicInput {
3
3
  private session;
4
4
  private context;
@@ -1 +1 @@
1
- {"version":3,"file":"mic-input.d.ts","sourceRoot":"","sources":["../../src/mic-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAiB3C,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,IAAI,CAAiC;IAC7C,OAAO,CAAC,MAAM,CAA2C;IAEzD,YAAY,OAAO,EAAE,WAAW,EAE/B;IAKK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAuB3B;IAGK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAU1B;CACF"}
1
+ {"version":3,"file":"mic-input.d.ts","sourceRoot":"","sources":["../../src/mic-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAiB9C,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,IAAI,CAAiC;IAC7C,OAAO,CAAC,MAAM,CAA2C;IAEzD,YAAY,OAAO,EAAE,WAAW,EAE/B;IAKK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAuB3B;IAGK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAU1B;CACF"}
@@ -1,4 +1,4 @@
1
- import type { GossSession } from "./index";
1
+ import type { GossSession } from "./index.js";
2
2
  export declare class GossVideoTexture {
3
3
  private session;
4
4
  private name;
@@ -1 +1 @@
1
- {"version":3,"file":"video-texture.d.ts","sourceRoot":"","sources":["../../src/video-texture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAM3C,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAU1D;IAIK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAK3B;IAKD,IAAI,IAAI,IAAI,CAYX;IAGD,IAAI,IAAI,IAAI,CAKX;CACF"}
1
+ {"version":3,"file":"video-texture.d.ts","sourceRoot":"","sources":["../../src/video-texture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM9C,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAU1D;IAIK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAK3B;IAKD,IAAI,IAAI,IAAI,CAYX;IAGD,IAAI,IAAI,IAAI,CAKX;CACF"}
@@ -1,4 +1,4 @@
1
- import type { GossSession } from "./index";
1
+ import type { GossSession } from "./index.js";
2
2
  interface XRRigidTransformLike {
3
3
  matrix: Float32Array;
4
4
  }
@@ -1 +1 @@
1
- {"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/world.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAwC,MAAM,SAAS,CAAC;AAIjF,UAAU,oBAAoB;IAC5B,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,UAAU,UAAU;IAClB,gBAAgB,EAAE,YAAY,CAAC;CAChC;AAED,UAAU,gBAAgB;IACxB,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,UAAU,WAAW;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,UAAU;IAClB,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,cAAc,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAChE,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;IACpE,cAAc,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;CACpC;AAED,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,MAAM,CAAK;IAEnB,YAA6B,OAAO,EAAE,WAAW,EAAI;IAIrD,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CA6ClF;IAED,OAAO,CAAC,KAAK;CAQd"}
1
+ {"version":3,"file":"world.d.ts","sourceRoot":"","sources":["../../src/world.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAwC,MAAM,YAAY,CAAC;AAIpF,UAAU,oBAAoB;IAC5B,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,UAAU,UAAU;IAClB,gBAAgB,EAAE,YAAY,CAAC;CAChC;AAED,UAAU,gBAAgB;IACxB,SAAS,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,UAAU,WAAW;IACnB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,UAAU;IAClB,SAAS,EAAE,oBAAoB,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,CAAC,cAAc,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAAC;IAChE,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;IACpE,cAAc,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;CACpC;AAED,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,MAAM,CAAK;IAEnB,YAA6B,OAAO,EAAE,WAAW,EAAI;IAIrD,OAAO,CAAC,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CA6ClF;IAED,OAAO,CAAC,KAAK;CAQd"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myzonerocks/gosslens",
3
- "version": "0.10.1",
3
+ "version": "0.11.1",
4
4
  "description": "Web SDK for the gosslens camera and AR engine, over the wasm core.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -1,4 +1,4 @@
1
- import type { GossSession } from "./index";
1
+ import type { GossSession } from "./index.js";
2
2
 
3
3
  /// The worklet drains chunks the page posts it, so the audio thread
4
4
  /// never touches the wasm heap and the engine's graph-thread pull
package/src/index.ts CHANGED
@@ -163,6 +163,28 @@ export interface GossPoseOut {
163
163
 
164
164
  export const GOSS_HAND_LANDMARK_COUNT = 21;
165
165
  export const GOSS_HAND_MAX = 2;
166
+
167
+ export const GOSS_CAP_TRACKING = 1 << 0;
168
+ export const GOSS_CAP_SEGMENTATION = 1 << 1;
169
+ export const GOSS_CAP_ML_INFER = 1 << 2;
170
+ export const GOSS_CAP_DIFFUSION = 1 << 3;
171
+ export const GOSS_CAP_BEAUTY = 1 << 4;
172
+ export const GOSS_CAP_PHYSICS = 1 << 5;
173
+ export const GOSS_CAP_VIDEO_TEXTURES = 1 << 6;
174
+ export const GOSS_CAP_PHOTO_CAPTURE = 1 << 7;
175
+ export const GOSS_CAP_RECORDING = 1 << 8;
176
+ export const GOSS_CAP_FILE_IO = 1 << 9;
177
+
178
+ /// A hand handed to submitHands: the tracker's presence, handedness, gesture,
179
+ /// and GOSS_HAND_LANDMARK_COUNT * 3 frame-pixel landmark floats. The shape
180
+ /// GossHandTracker.process returns plugs in directly.
181
+ export interface GossHandInput {
182
+ presence?: number;
183
+ handedness?: number;
184
+ gesture?: number;
185
+ gestureScore?: number;
186
+ landmarks: Float32Array | number[];
187
+ }
166
188
  const HAND_RESULT_BYTES = 560;
167
189
  const HAND_ONE_BYTES = 268;
168
190
 
@@ -371,6 +393,13 @@ export class Gosslens {
371
393
  return this.version;
372
394
  }
373
395
 
396
+ /// Which capabilities this build compiled real, as GOSS_CAP_* bits. A stub
397
+ /// build shares the full one's filename and abi version, so check the rail
398
+ /// you need here before feeding real bytes to an enable call.
399
+ capabilities(): number {
400
+ return this.mod.ccall("goss_capabilities", "number", [], []) as number;
401
+ }
402
+
374
403
  /// Loads gosslens_web.js and checks its ABI major version. A
375
404
  /// dynamic import, not static: bun's bundler would otherwise inline
376
405
  /// this file, breaking Emscripten's own import.meta.url-relative
@@ -498,8 +527,12 @@ export class GossEngine {
498
527
  // itself, via emscripten_webgl_create_context - passing
499
528
  // webGLContextAttributes here has no effect regardless,
500
529
  // preserveDrawingBuffer stays false. Worked around in capturePixels.
501
- const rendererStatus = await mod.ccall("goss_engine_init_renderer", "number", ["number", "number"], [this.handle, rendererDescPtr], { async: true });
502
- mod.ccall("goss_free", null, ["number", "number"], [rendererDescPtr, 12]);
530
+ let rendererStatus: number;
531
+ try {
532
+ rendererStatus = await mod.ccall("goss_engine_init_renderer", "number", ["number", "number"], [this.handle, rendererDescPtr], { async: true });
533
+ } finally {
534
+ mod.ccall("goss_free", null, ["number", "number"], [rendererDescPtr, 12]);
535
+ }
503
536
  if (rendererStatus !== GOSS_OK) throw new Error(`renderer init failed: ${rendererStatus}`);
504
537
 
505
538
  // Emscripten's C++ side just created this canvas's own rendering
@@ -1103,7 +1136,8 @@ export class GossSession {
1103
1136
  if (bytes > this.scratchCapacity) {
1104
1137
  if (this.scratchPtr !== 0) this.mod.ccall("goss_free", null, ["number", "number"], [this.scratchPtr, this.scratchCapacity]);
1105
1138
  this.scratchPtr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]) as number;
1106
- this.scratchCapacity = bytes;
1139
+ this.scratchCapacity = this.scratchPtr === 0 ? 0 : bytes;
1140
+ if (this.scratchPtr === 0) throw new Error("wasm allocation failed");
1107
1141
  }
1108
1142
  return this.scratchPtr;
1109
1143
  }
@@ -1264,7 +1298,7 @@ export class GossSession {
1264
1298
  /// muxed recording track, unused by the web preview path.
1265
1299
  submitAudio(samples: Float32Array, frameCount: number, sampleRate: number, channels: number, timestampUs: bigint = 0n): void {
1266
1300
  const bytes = samples.length * 4;
1267
- const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]) as number;
1301
+ const ptr = this.scratch(bytes);
1268
1302
  this.mod.HEAPF32.set(samples, ptr >> 2);
1269
1303
  this.mod.ccall(
1270
1304
  "goss_session_submit_audio",
@@ -1272,18 +1306,15 @@ export class GossSession {
1272
1306
  ["number", "number", "number", "number", "number", "number"],
1273
1307
  [this.handle, ptr, frameCount, sampleRate, channels, timestampUs],
1274
1308
  );
1275
- this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
1276
1309
  }
1277
1310
 
1278
1311
  /// Pulls the next block of mixed lens audio (frames interleaved s16) that
1279
1312
  /// play_sound triggers produced, for the page to feed into WebAudio.
1280
1313
  pullAudio(frames: number): Int16Array {
1281
1314
  const byteLen = frames * 2;
1282
- const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [byteLen]);
1315
+ const ptr = this.scratch(byteLen);
1283
1316
  this.mod.ccall("goss_session_pull_audio", "number", ["number", "number", "number"], [this.handle, ptr, frames]);
1284
- const out = new Int16Array(this.mod.HEAP16.buffer, ptr, frames).slice();
1285
- this.mod.ccall("goss_free", null, ["number", "number"], [ptr, byteLen]);
1286
- return out;
1317
+ return new Int16Array(this.mod.HEAP16.buffer, ptr, frames).slice();
1287
1318
  }
1288
1319
 
1289
1320
  /// Folds the active lens sound into the caller's outgoing call/live track:
@@ -1292,12 +1323,12 @@ export class GossSession {
1292
1323
  /// mixed interleaved s16. Advances the mixer once, replacing `pullAudio`.
1293
1324
  mixOutputAudio(mic: Float32Array | null, frameCount: number, sampleRate: number, channels: number): Int16Array {
1294
1325
  const outLen = frameCount * channels;
1295
- const outBytes = outLen * 2;
1296
- const outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [outBytes]);
1297
- let micPtr = 0;
1326
+ const outBytes = (outLen * 2 + 3) & ~3;
1298
1327
  const micBytes = mic ? mic.length * 4 : 0;
1328
+ const outPtr = this.scratch(outBytes + micBytes);
1329
+ let micPtr = 0;
1299
1330
  if (mic) {
1300
- micPtr = this.mod.ccall("goss_alloc", "number", ["number"], [micBytes]);
1331
+ micPtr = outPtr + outBytes;
1301
1332
  this.mod.HEAPF32.set(mic, micPtr >> 2);
1302
1333
  }
1303
1334
  this.mod.ccall(
@@ -1306,10 +1337,7 @@ export class GossSession {
1306
1337
  ["number", "number", "number", "number", "number", "number"],
1307
1338
  [this.handle, micPtr, outPtr, frameCount, sampleRate, channels],
1308
1339
  );
1309
- const out = new Int16Array(this.mod.HEAP16.buffer, outPtr, outLen).slice();
1310
- this.mod.ccall("goss_free", null, ["number", "number"], [outPtr, outBytes]);
1311
- if (mic) this.mod.ccall("goss_free", null, ["number", "number"], [micPtr, micBytes]);
1312
- return out;
1340
+ return new Int16Array(this.mod.HEAP16.buffer, outPtr, outLen).slice();
1313
1341
  }
1314
1342
 
1315
1343
  /// Stores validated camera-hardware intent; the engine normalizes it. Read it
@@ -1454,7 +1482,7 @@ export class GossSession {
1454
1482
  /// Uploads one RGBA/BGRA frame into a named source (pixelFormat 3 BGRA, 4 RGBA).
1455
1483
  submitSourceFrameRgba(name: string, rgba: Uint8Array, width: number, height: number, stride: number, pixelFormat: GossPixelFormat = GossPixelFormat.Rgba8): void {
1456
1484
  const byteLen = stride * height;
1457
- const rgbaPtr = this.mod.ccall("goss_alloc", "number", ["number"], [byteLen]) as number;
1485
+ const rgbaPtr = this.scratch(byteLen);
1458
1486
  this.mod.HEAPU8.set(rgba.subarray(0, byteLen), rgbaPtr);
1459
1487
  this.mod.setValue(this.frameDescPtr, width, "i32");
1460
1488
  this.mod.setValue(this.frameDescPtr + 4, height, "i32");
@@ -1466,7 +1494,6 @@ export class GossSession {
1466
1494
  this.mod.setValue(this.frameDescPtr + 28, 0, "i32");
1467
1495
  this.withName(name, (ptr, len) =>
1468
1496
  this.mod.ccall("goss_session_submit_source_frame_rgba_copy", "number", ["number", "number", "number", "number", "number", "number"], [this.handle, ptr, len, this.frameDescPtr, rgbaPtr, stride]));
1469
- this.mod.ccall("goss_free", null, ["number", "number"], [rgbaPtr, byteLen]);
1470
1497
  }
1471
1498
 
1472
1499
  /// Arranges the camera and named sources: 0 custom, 1 side-by-side, 2 top-bottom, 3 pip, 4 grid.
@@ -1635,7 +1662,7 @@ export class GossSession {
1635
1662
  this.mod.ccall("goss_session_brush_clear", "number", ["number"], [this.handle]);
1636
1663
  }
1637
1664
 
1638
- /// The brush preset the next stroke opens with: 0 pen, 1 highlighter, 2 marker, 3 neon.
1665
+ /// The brush preset the next stroke opens with: 0 pen, 1 highlighter, 2 marker, 3 neon, 4 stamp.
1639
1666
  setBrushMode(mode: number): void {
1640
1667
  this.mod.ccall("goss_session_brush_set_mode", "number", ["number", "number"], [this.handle, mode]);
1641
1668
  }
@@ -1769,7 +1796,7 @@ export class GossSession {
1769
1796
  }
1770
1797
  const scaleX = this.frameWidth / sourceWidth;
1771
1798
  const scaleY = this.frameHeight / sourceHeight;
1772
- const pointCount = landmarks.length / 3;
1799
+ const pointCount = Math.min(landmarks.length / 3, GOSS_FACE_LANDMARK_COUNT);
1773
1800
  const base = this.landmarksPtr >> 2;
1774
1801
  for (let at = 0; at < pointCount; at += 1) {
1775
1802
  this.mod.HEAPF32[base + at * 3] = landmarks[at * 3]! * scaleX;
@@ -1800,15 +1827,15 @@ export class GossSession {
1800
1827
  const off = i * FACE_RESULT_BYTES;
1801
1828
  const base = ptr + off;
1802
1829
  const f = faces[i]!;
1803
- const count = f.landmarks.length / 3;
1830
+ const count = Math.min(f.landmarks.length / 3, GOSS_FACE_LANDMARK_COUNT);
1804
1831
  dv.setBigUint64(off, BigInt(f.frameSerial ?? 0), true);
1805
1832
  dv.setBigInt64(off + 8, BigInt(f.timestampUs ?? 0), true);
1806
1833
  dv.setFloat32(off + 16, f.presence ?? 1, true);
1807
1834
  dv.setUint32(off + 20, count, true);
1808
- this.mod.HEAPF32.set(f.landmarks, (base + 24) >> 2);
1835
+ this.mod.HEAPF32.set(f.landmarks.subarray(0, count * 3), (base + 24) >> 2);
1809
1836
  const bsStart = (base + 24 + GOSS_FACE_LANDMARK_COUNT * 3 * 4) >> 2;
1810
1837
  this.mod.HEAPF32.fill(0, bsStart, bsStart + GOSS_FACE_BLENDSHAPE_COUNT);
1811
- if (f.blendshapes) this.mod.HEAPF32.set(f.blendshapes, bsStart);
1838
+ if (f.blendshapes) this.mod.HEAPF32.set(f.blendshapes.subarray(0, GOSS_FACE_BLENDSHAPE_COUNT), bsStart);
1812
1839
  }
1813
1840
  this.mod.ccall("goss_session_submit_faces", "number", ["number", "number", "number"], [this.handle, ptr, faces.length]);
1814
1841
  }
@@ -1865,20 +1892,48 @@ export class GossSession {
1865
1892
  const off = i * POSE_RESULT_BYTES;
1866
1893
  const base = ptr + off;
1867
1894
  const b = bodies[i]!;
1868
- const count = b.landmarks.length / 3;
1895
+ const count = Math.min(b.landmarks.length / 3, GOSS_POSE_LANDMARK_COUNT);
1869
1896
  dv.setBigUint64(off, BigInt(b.frameSerial ?? 0), true);
1870
1897
  dv.setBigInt64(off + 8, BigInt(b.timestampUs ?? 0), true);
1871
1898
  dv.setFloat32(off + 16, b.presence ?? 1, true);
1872
1899
  dv.setUint32(off + 20, count, true);
1873
- this.mod.HEAPF32.set(b.landmarks, (base + 24) >> 2);
1900
+ this.mod.HEAPF32.set(b.landmarks.subarray(0, count * 3), (base + 24) >> 2);
1874
1901
  const visStart = (base + 24 + GOSS_POSE_LANDMARK_COUNT * 3 * 4) >> 2;
1875
1902
  this.mod.HEAPF32.fill(0, visStart, visStart + GOSS_POSE_LANDMARK_COUNT * 2);
1876
- if (b.visibilities) this.mod.HEAPF32.set(b.visibilities, visStart);
1877
- if (b.presences) this.mod.HEAPF32.set(b.presences, visStart + GOSS_POSE_LANDMARK_COUNT);
1903
+ if (b.visibilities) this.mod.HEAPF32.set(b.visibilities.subarray(0, GOSS_POSE_LANDMARK_COUNT), visStart);
1904
+ if (b.presences) this.mod.HEAPF32.set(b.presences.subarray(0, GOSS_POSE_LANDMARK_COUNT), visStart + GOSS_POSE_LANDMARK_COUNT);
1878
1905
  }
1879
1906
  this.mod.ccall("goss_session_submit_bodies", "number", ["number", "number", "number"], [this.handle, ptr, bodies.length]);
1880
1907
  }
1881
1908
 
1909
+ /// Submits the hands tracked this frame from the page's own tracker (the
1910
+ /// GossHandTracker's result plugs in directly), so hands.gesture and
1911
+ /// hands.pinch lenses and handJoint work with no in-engine worker; the
1912
+ /// submitted hands win over the worker while set. Null clears the path.
1913
+ submitHands(hands: { hands: GossHandInput[]; timestampUs?: bigint } | null): void {
1914
+ if (hands === null) {
1915
+ this.mod.ccall("goss_session_submit_hands", "number", ["number", "number"], [this.handle, 0]);
1916
+ return;
1917
+ }
1918
+ const ptr = this.scratch(HAND_RESULT_BYTES);
1919
+ this.mod.HEAPU8.fill(0, ptr, ptr + HAND_RESULT_BYTES);
1920
+ const dv = new DataView(this.mod.HEAPU8.buffer, ptr, HAND_RESULT_BYTES);
1921
+ dv.setBigInt64(8, hands.timestampUs ?? 0n, true);
1922
+ const list = hands.hands.slice(0, GOSS_HAND_MAX);
1923
+ dv.setUint32(16, list.length, true);
1924
+ for (let i = 0; i < list.length; i += 1) {
1925
+ const off = 24 + i * HAND_ONE_BYTES;
1926
+ const h = list[i]!;
1927
+ dv.setFloat32(off, h.presence ?? 1, true);
1928
+ dv.setFloat32(off + 4, h.handedness ?? 0.5, true);
1929
+ dv.setUint32(off + 8, h.gesture ?? 0, true);
1930
+ dv.setFloat32(off + 12, h.gestureScore ?? 0, true);
1931
+ const floats = Math.min(h.landmarks.length, GOSS_HAND_LANDMARK_COUNT * 3);
1932
+ for (let k = 0; k < floats; k += 1) dv.setFloat32(off + 16 + k * 4, h.landmarks[k]!, true);
1933
+ }
1934
+ this.mod.ccall("goss_session_submit_hands", "number", ["number", "number"], [this.handle, ptr]);
1935
+ }
1936
+
1882
1937
  /// Submits one frame's depth map from the host AR backend (WebXR depth-
1883
1938
  /// sensing): width by height metres per pixel, row major, with the near and
1884
1939
  /// far metres that bound it. An empty array clears it. Kept for depth
@@ -2039,6 +2094,75 @@ export class GossSession {
2039
2094
  return result;
2040
2095
  }
2041
2096
 
2097
+ /// Hands the engine one bundle asset's bytes under its manifest name ahead
2098
+ /// of activateLens, so a lens's models, references, and label files load
2099
+ /// from memory - the door the web's inference nodes activate through. An
2100
+ /// empty array removes a staged name; the engine keeps its own copy.
2101
+ provideLensAsset(name: string, bytes: Uint8Array): void {
2102
+ const id = new TextEncoder().encode(name);
2103
+ const idPtr = this.mod.ccall("goss_alloc", "number", ["number"], [id.length || 1]) as number;
2104
+ this.mod.HEAPU8.set(id, idPtr);
2105
+ let bytesPtr = 0;
2106
+ if (bytes.length > 0) {
2107
+ bytesPtr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes.length]) as number;
2108
+ this.mod.HEAPU8.set(bytes, bytesPtr);
2109
+ }
2110
+ const args = ["number", "number", "number", "number", "number"];
2111
+ const status = this.mod.ccall("goss_session_provide_lens_asset", "number", args, [this.handle, idPtr, id.length, bytesPtr, bytes.length]) as number;
2112
+ if (bytesPtr !== 0) this.mod.ccall("goss_free", null, ["number", "number"], [bytesPtr, bytes.length]);
2113
+ this.mod.ccall("goss_free", null, ["number", "number"], [idPtr, id.length || 1]);
2114
+ if (status !== 0) throw new Error(`provideLensAsset failed (${status})`);
2115
+ }
2116
+
2117
+ /// One ml.infer node's whole published output tensor, by the node's id and
2118
+ /// tensor index, or null before the model's first publish or when the node
2119
+ /// or tensor does not exist. A length probe sizes the read, so detections,
2120
+ /// embeddings, and logits come back at their own length.
2121
+ mlOutput(nodeId: string, tensor = 0): Float32Array | null {
2122
+ const id = new TextEncoder().encode(nodeId);
2123
+ const idPtr = this.mod.ccall("goss_alloc", "number", ["number"], [id.length || 1]) as number;
2124
+ this.mod.HEAPU8.set(id, idPtr);
2125
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]) as number;
2126
+ const readLen = () => new DataView(this.mod.HEAPU8.buffer, lenPtr, 4).getUint32(0, true);
2127
+ const args = ["number", "number", "number", "number", "number", "number", "number"];
2128
+ this.mod.ccall("goss_session_ml_output", "number", args, [this.handle, idPtr, id.length, tensor, 0, 0, lenPtr]);
2129
+ const needed = readLen();
2130
+ let result: Float32Array | null = null;
2131
+ if (needed > 0) {
2132
+ const outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [needed * 4]) as number;
2133
+ const status = this.mod.ccall("goss_session_ml_output", "number", args, [this.handle, idPtr, id.length, tensor, outPtr, needed, lenPtr]) as number;
2134
+ if (status === 0) result = this.mod.HEAPF32.slice(outPtr >> 2, (outPtr >> 2) + needed);
2135
+ this.mod.ccall("goss_free", null, ["number", "number"], [outPtr, needed * 4]);
2136
+ }
2137
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 4]);
2138
+ this.mod.ccall("goss_free", null, ["number", "number"], [idPtr, id.length || 1]);
2139
+ return result;
2140
+ }
2141
+
2142
+ /// One ml.infer node's mask-bound output resampled to the fixed
2143
+ /// segmentation plane, or null when the node has no mask binding or the
2144
+ /// model has not published yet.
2145
+ mlMask(nodeId: string): Float32Array | null {
2146
+ const id = new TextEncoder().encode(nodeId);
2147
+ const idPtr = this.mod.ccall("goss_alloc", "number", ["number"], [id.length || 1]) as number;
2148
+ this.mod.HEAPU8.set(id, idPtr);
2149
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]) as number;
2150
+ const readLen = () => new DataView(this.mod.HEAPU8.buffer, lenPtr, 4).getUint32(0, true);
2151
+ const args = ["number", "number", "number", "number", "number", "number"];
2152
+ this.mod.ccall("goss_session_ml_mask", "number", args, [this.handle, idPtr, id.length, 0, 0, lenPtr]);
2153
+ const needed = readLen();
2154
+ let result: Float32Array | null = null;
2155
+ if (needed > 0) {
2156
+ const outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [needed * 4]) as number;
2157
+ const status = this.mod.ccall("goss_session_ml_mask", "number", args, [this.handle, idPtr, id.length, outPtr, needed, lenPtr]) as number;
2158
+ if (status === 0) result = this.mod.HEAPF32.slice(outPtr >> 2, (outPtr >> 2) + needed);
2159
+ this.mod.ccall("goss_free", null, ["number", "number"], [outPtr, needed * 4]);
2160
+ }
2161
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 4]);
2162
+ this.mod.ccall("goss_free", null, ["number", "number"], [idPtr, id.length || 1]);
2163
+ return result;
2164
+ }
2165
+
2042
2166
  /// The recent diarized caption segment at index (0 the newest), or null when
2043
2167
  /// the index is past the segments held: the times it spanned, the speaker who
2044
2168
  /// spoke it, and its text, a speaker-tagged transcript for diarized subtitles.
@@ -2089,9 +2213,9 @@ export class GossSession {
2089
2213
  /// Submits one NV12 exposure of an HDR bracket, fed to bracket-source
2090
2214
  /// temporal.fuse nodes. y and uv are the plane bytes as submitFrameCopy takes.
2091
2215
  submitFrameBracket(y: Uint8Array, yStride: number, uv: Uint8Array, uvStride: number, width: number, height: number, colorStandard = 1, colorRange = 0): void {
2092
- const yPtr = this.mod.ccall("goss_alloc", "number", ["number"], [y.length]) as number;
2216
+ const yPtr = this.scratch(y.length + uv.length);
2093
2217
  this.mod.HEAPU8.set(y, yPtr);
2094
- const uvPtr = this.mod.ccall("goss_alloc", "number", ["number"], [uv.length]) as number;
2218
+ const uvPtr = yPtr + y.length;
2095
2219
  this.mod.HEAPU8.set(uv, uvPtr);
2096
2220
  this.mod.setValue(this.frameDescPtr, width, "i32");
2097
2221
  this.mod.setValue(this.frameDescPtr + 4, height, "i32");
@@ -2102,14 +2226,12 @@ export class GossSession {
2102
2226
  this.mod.setValue(this.frameDescPtr + 24, 0, "i32");
2103
2227
  this.mod.setValue(this.frameDescPtr + 28, 0, "i32");
2104
2228
  this.mod.ccall("goss_session_submit_frame_bracket", "number", ["number", "number", "number", "number", "number", "number"], [this.handle, this.frameDescPtr, yPtr, yStride, uvPtr, uvStride]);
2105
- this.mod.ccall("goss_free", null, ["number", "number"], [yPtr, y.length]);
2106
- this.mod.ccall("goss_free", null, ["number", "number"], [uvPtr, uv.length]);
2107
2229
  }
2108
2230
 
2109
2231
  private submitPlanes(sym: string, y: Uint8Array, yStride: number, uv: Uint8Array, uvStride: number, width: number, height: number, colorStandard: number, colorRange: number): void {
2110
- const yPtr = this.mod.ccall("goss_alloc", "number", ["number"], [y.length]) as number;
2232
+ const yPtr = this.scratch(y.length + uv.length);
2111
2233
  this.mod.HEAPU8.set(y, yPtr);
2112
- const uvPtr = this.mod.ccall("goss_alloc", "number", ["number"], [uv.length]) as number;
2234
+ const uvPtr = yPtr + y.length;
2113
2235
  this.mod.HEAPU8.set(uv, uvPtr);
2114
2236
  this.mod.setValue(this.frameDescPtr, width, "i32");
2115
2237
  this.mod.setValue(this.frameDescPtr + 4, height, "i32");
@@ -2120,8 +2242,6 @@ export class GossSession {
2120
2242
  this.mod.setValue(this.frameDescPtr + 24, 0, "i32");
2121
2243
  this.mod.setValue(this.frameDescPtr + 28, 0, "i32");
2122
2244
  this.mod.ccall(sym, "number", ["number", "number", "number", "number", "number", "number"], [this.handle, this.frameDescPtr, yPtr, yStride, uvPtr, uvStride]);
2123
- this.mod.ccall("goss_free", null, ["number", "number"], [yPtr, y.length]);
2124
- this.mod.ccall("goss_free", null, ["number", "number"], [uvPtr, uv.length]);
2125
2245
  }
2126
2246
 
2127
2247
  /// Feeds one NV12 frame to the in-engine trackers only (no render), for a host
@@ -2500,7 +2620,8 @@ export class GossSession {
2500
2620
  if (this.framePixelsCapacity >= byteLength) return;
2501
2621
  if (this.framePixelsPtr !== 0) this.mod.ccall("goss_free", null, ["number", "number"], [this.framePixelsPtr, this.framePixelsCapacity]);
2502
2622
  this.framePixelsPtr = this.mod.ccall("goss_alloc", "number", ["number"], [byteLength]);
2503
- this.framePixelsCapacity = byteLength;
2623
+ this.framePixelsCapacity = this.framePixelsPtr === 0 ? 0 : byteLength;
2624
+ if (this.framePixelsPtr === 0) throw new Error("wasm allocation failed");
2504
2625
  }
2505
2626
 
2506
2627
  /// rotationDegrees omitted means the setVideoFlip state decides (a
@@ -2603,7 +2724,8 @@ export class GossSession {
2603
2724
  if (this.worldScratchLen >= byteLength) return;
2604
2725
  if (this.worldScratchPtr !== 0) this.mod.ccall("goss_free", null, ["number", "number"], [this.worldScratchPtr, this.worldScratchLen]);
2605
2726
  this.worldScratchPtr = this.mod.ccall("goss_alloc", "number", ["number"], [byteLength]);
2606
- this.worldScratchLen = byteLength;
2727
+ this.worldScratchLen = this.worldScratchPtr === 0 ? 0 : byteLength;
2728
+ if (this.worldScratchPtr === 0) throw new Error("wasm allocation failed");
2607
2729
  }
2608
2730
 
2609
2731
  /// Reports one finished frame: measured whole-pipeline time plus
@@ -2679,9 +2801,14 @@ export class GossPreviewSession {
2679
2801
  static async create(canvas: HTMLCanvasElement, wasmJsUrl: string | URL, events: GossSessionEvents = {}): Promise<GossPreviewSession> {
2680
2802
  const gosslens = await Gosslens.load(canvas, wasmJsUrl);
2681
2803
  const engine = GossEngine.create(gosslens);
2682
- await engine.initRenderer(canvas);
2683
- const session = GossSession.create(engine);
2684
- return new GossPreviewSession(gosslens, engine, session, events);
2804
+ try {
2805
+ await engine.initRenderer(canvas);
2806
+ const session = GossSession.create(engine);
2807
+ return new GossPreviewSession(gosslens, engine, session, events);
2808
+ } catch (err) {
2809
+ engine.destroy();
2810
+ throw err;
2811
+ }
2685
2812
  }
2686
2813
 
2687
2814
  abiVersion(): number {
@@ -2810,8 +2937,10 @@ export class GossPreviewSession {
2810
2937
  this.cameraFrames += 1;
2811
2938
  const width = this.video.videoWidth;
2812
2939
  const height = this.video.videoHeight;
2813
- this.scratchCanvas.width = width;
2814
- this.scratchCanvas.height = height;
2940
+ if (this.scratchCanvas.width !== width || this.scratchCanvas.height !== height) {
2941
+ this.scratchCanvas.width = width;
2942
+ this.scratchCanvas.height = height;
2943
+ }
2815
2944
  this.scratchCtx.drawImage(this.video, 0, 0, width, height);
2816
2945
  const pixels = this.scratchCtx.getImageData(0, 0, width, height);
2817
2946
  // Not mirrored here - the demo page's own CSS mirrors the canvas
@@ -2852,8 +2981,8 @@ export class GossPreviewSession {
2852
2981
  }
2853
2982
  }
2854
2983
 
2855
- export { GossWebXRWorldSource } from "./world";
2856
- export type { GossXRFrameLike } from "./world";
2857
- export { GossAudioOutput } from "./audio-output";
2858
- export { GossMicInput } from "./mic-input";
2859
- export { GossVideoTexture } from "./video-texture";
2984
+ export { GossWebXRWorldSource } from "./world.js";
2985
+ export type { GossXRFrameLike } from "./world.js";
2986
+ export { GossAudioOutput } from "./audio-output.js";
2987
+ export { GossMicInput } from "./mic-input.js";
2988
+ export { GossVideoTexture } from "./video-texture.js";
package/src/mic-input.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GossSession } from "./index";
1
+ import type { GossSession } from "./index.js";
2
2
 
3
3
  /// Captures the microphone and feeds it to the engine's audio analysis,
4
4
  /// so the level and beat triggers a lens reads fire on web the same way
@@ -1,4 +1,4 @@
1
- import type { GossSession } from "./index";
1
+ import type { GossSession } from "./index.js";
2
2
 
3
3
  /// Plays an MP4 through a hidden video element, the browser's own
4
4
  /// decoder, and feeds each frame to a named engine source a lens
package/src/world.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  /// planes, and tracked anchors off each XR frame and feeds them into
3
3
  /// the session. The page owns the XR session; this source only reads.
4
4
 
5
- import type { GossSession, GossWorldPlane, GossWorldAnchorInput } from "./index";
5
+ import type { GossSession, GossWorldPlane, GossWorldAnchorInput } from "./index.js";
6
6
 
7
7
  // WebXR's types live outside the DOM lib; the narrow slice this source
8
8
  // reads is declared here so no type package enters the build.