@myzonerocks/gosslens 0.12.0 → 0.13.0

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.
package/dist/src/index.js CHANGED
@@ -6,6 +6,14 @@
6
6
  // everything else (the frame graph, all six beauty effects, mirror and
7
7
  // rotation) straight to the engine.
8
8
  export const GOSS_OK = 0;
9
+ /// The session's scope does not carry the verb an op needs. A host can grant this
10
+ /// one; GOSS_UNSUPPORTED it cannot.
11
+ export const GOSS_OUT_OF_SCOPE = 9;
12
+ /// The mask for a set of verbs, which is what setScope takes.
13
+ export function gossVerbMask(verbs) {
14
+ return verbs.reduce((mask, verb) => mask | (1 << verb), 0);
15
+ }
16
+ export const GOSS_AGAIN = 7;
9
17
  const FRAME_FLAG_MIRROR = 0x1;
10
18
  const FRAME_ROTATION_SHIFT = 8;
11
19
  const LENS_SIGNALS_BYTES = 232;
@@ -164,6 +172,25 @@ export class Gosslens {
164
172
  // use fit a number, and a number is what every caller masks against.
165
173
  return Number(this.mod.ccall("goss_capabilities", "number", [], []));
166
174
  }
175
+ /// The rails a lens declares that this build lacks, as GOSS_CAP_ bits; zero means
176
+ /// every one it asked for is here. A catalogue filters on this rather than
177
+ /// activating a lens to find out.
178
+ lensCapabilitiesMissing(manifestJson) {
179
+ const bytes = new TextEncoder().encode(manifestJson);
180
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes.length]);
181
+ const out = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
182
+ try {
183
+ this.mod.HEAPU8.set(bytes, ptr);
184
+ const status = this.mod.ccall("goss_lens_capabilities_missing", "number", ["number", "number", "number"], [ptr, bytes.length, out]);
185
+ if (status !== GOSS_OK)
186
+ return 0;
187
+ return this.mod.HEAPU32[out >> 2];
188
+ }
189
+ finally {
190
+ this.mod.ccall("goss_free", null, ["number", "number"], [out, 8]);
191
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes.length]);
192
+ }
193
+ }
167
194
  /// Loads gosslens_web.js and checks its ABI major version. A
168
195
  /// dynamic import, not static: bun's bundler would otherwise inline
169
196
  /// this file, breaking Emscripten's own import.meta.url-relative
@@ -171,9 +198,12 @@ export class Gosslens {
171
198
  static async load(canvas, wasmJsUrl) {
172
199
  const imported = (await import(/* @vite-ignore */ String(wasmJsUrl)));
173
200
  const mod = await imported.default({ canvas });
201
+ // The engine decides, rather than this file comparing against a literal major
202
+ // that would go stale the day the major moves.
174
203
  const version = mod.ccall("goss_abi_version", "number", [], []) >>> 0;
175
- if (version >> 16 !== 0)
204
+ if (mod.ccall("goss_abi_check", "number", ["number"], [GOSS_ABI_MAJOR << 16]) !== GOSS_OK) {
176
205
  throw new Error(`gosslens abi major mismatch: ${version >> 16}`);
206
+ }
177
207
  return new Gosslens(mod, version);
178
208
  }
179
209
  /// The YCbCr to RGB conversion for a standard and range as one
@@ -788,6 +818,137 @@ export class GossEngine {
788
818
  sum += value;
789
819
  return sum;
790
820
  }
821
+ /// What the engine is doing now, as against what it was asked for. Every
822
+ /// field is measured. The u64 fields are read as their low word, which holds
823
+ /// the whole count at any rate this engine reaches.
824
+ /// What this build's media backend declares it encodes, as bit sets over the
825
+ /// codec and container enums, so a page asks rather than assuming.
826
+ mediaCapabilities() {
827
+ const bytes = 8 * 4;
828
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]);
829
+ try {
830
+ if (this.mod.ccall("goss_engine_media_capabilities", "number", ["number", "number"], [this.handle, ptr]) !== GOSS_OK)
831
+ return null;
832
+ const w = (offset) => this.mod.HEAPU32[(ptr + offset) >> 2];
833
+ return {
834
+ videoCodecs: w(0),
835
+ audioCodecs: w(4),
836
+ containers: w(8),
837
+ maxWidth: w(12),
838
+ maxHeight: w(16),
839
+ maxBitDepth: w(20),
840
+ hdr: w(24) !== 0,
841
+ zeroCopy: w(28) !== 0,
842
+ };
843
+ }
844
+ finally {
845
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
846
+ }
847
+ }
848
+ /// Everything capturable. Empty where permission has not been granted, so
849
+ /// prompt rather than treating it as an error.
850
+ screens() {
851
+ const countPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
852
+ const entryPtr = this.mod.ccall("goss_alloc", "number", ["number"], [40]);
853
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
854
+ const out = [];
855
+ try {
856
+ if (this.mod.ccall("goss_engine_screen_count", "number", ["number", "number"], [this.handle, countPtr]) !== GOSS_OK)
857
+ return out;
858
+ const count = this.mod.HEAPU32[countPtr >> 2];
859
+ for (let i = 0; i < count; i += 1) {
860
+ if (this.mod.ccall("goss_engine_screen_at", "number", ["number", "number", "number"], [this.handle, i, entryPtr]) !== GOSS_OK)
861
+ continue;
862
+ const f = (off) => this.mod.HEAPF32[(entryPtr + off) >> 2];
863
+ const u = (off) => this.mod.HEAPU32[(entryPtr + off) >> 2];
864
+ this.mod.ccall("goss_engine_screen_title", "number", ["number", "number", "number", "number", "number"], [this.handle, i, 0, 0, lenPtr]);
865
+ const needed = this.mod.HEAPU32[lenPtr >> 2];
866
+ let title = "";
867
+ if (needed > 0) {
868
+ const titlePtr = this.mod.ccall("goss_alloc", "number", ["number"], [needed]);
869
+ try {
870
+ if (this.mod.ccall("goss_engine_screen_title", "number", ["number", "number", "number", "number", "number"], [this.handle, i, titlePtr, needed, lenPtr]) === GOSS_OK) {
871
+ title = new TextDecoder().decode(this.mod.HEAPU8.subarray(titlePtr, titlePtr + needed));
872
+ }
873
+ }
874
+ finally {
875
+ this.mod.ccall("goss_free", null, ["number", "number"], [titlePtr, needed]);
876
+ }
877
+ }
878
+ out.push({ id: u(0), kind: u(8), title, logicalWidth: f(12), logicalHeight: f(16), originX: f(20), originY: f(24), scale: f(28) });
879
+ }
880
+ return out;
881
+ }
882
+ finally {
883
+ this.mod.ccall("goss_free", null, ["number", "number"], [countPtr, 4]);
884
+ this.mod.ccall("goss_free", null, ["number", "number"], [entryPtr, 40]);
885
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 8]);
886
+ }
887
+ }
888
+ /// The operators a model needs that this build does not implement. An empty
889
+ /// list means the model runs; anything in it names exactly what is missing.
890
+ mlOpSupport(model) {
891
+ const modelPtr = this.mod.ccall("goss_alloc", "number", ["number"], [model.length]);
892
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
893
+ this.mod.HEAPU8.set(model, modelPtr);
894
+ let capacity = 256;
895
+ try {
896
+ while (capacity <= 1 << 16) {
897
+ const outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [capacity]);
898
+ try {
899
+ const status = this.mod.ccall("goss_ml_op_support", "number", ["number", "number", "number", "number", "number"], [modelPtr, model.length, outPtr, capacity, lenPtr]);
900
+ if (status !== GOSS_OK && status !== GOSS_AGAIN)
901
+ return [];
902
+ const needed = this.mod.HEAPU32[lenPtr >> 2];
903
+ if (needed <= capacity) {
904
+ const text = new TextDecoder().decode(this.mod.HEAPU8.subarray(outPtr, outPtr + needed));
905
+ return text.split("\n").filter((line) => line.length !== 0);
906
+ }
907
+ capacity = needed;
908
+ }
909
+ finally {
910
+ this.mod.ccall("goss_free", null, ["number", "number"], [outPtr, capacity]);
911
+ }
912
+ }
913
+ return [];
914
+ }
915
+ finally {
916
+ this.mod.ccall("goss_free", null, ["number", "number"], [modelPtr, model.length]);
917
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 8]);
918
+ }
919
+ }
920
+ engineReport() {
921
+ const bytes = ENGINE_REPORT_BYTES;
922
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]);
923
+ try {
924
+ if (this.mod.ccall("goss_engine_read_report", "number", ["number", "number"], [this.handle, ptr]) !== 0)
925
+ return null;
926
+ // The low word of each u64 is the whole count at any size this engine
927
+ // reaches, and reading it avoids a BigInt in the hot read.
928
+ const w = (offset) => this.mod.HEAPU32[(ptr + offset) >> 2];
929
+ return {
930
+ rendererBackend: w(0),
931
+ zeroCopyImport: w(4) !== 0,
932
+ texturePoolCapacity: w(8),
933
+ texturePoolLive: w(12),
934
+ texturePoolPeak: w(16),
935
+ texturePoolExhausted: w(20),
936
+ stagingPoolCapacity: w(24),
937
+ stagingPoolLive: w(28),
938
+ stagingPoolPeak: w(32),
939
+ stagingPoolExhausted: w(36),
940
+ texturePoolBins: w(40),
941
+ texturePoolBinsRefused: w(44),
942
+ stagingPoolBins: w(48),
943
+ bgfxLiveBytes: w(56),
944
+ bgfxAllocCallsLastFrame: w(64),
945
+ bgfxBytesLastFrame: w(72),
946
+ };
947
+ }
948
+ finally {
949
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
950
+ }
951
+ }
791
952
  destroy() {
792
953
  this.mod.ccall("goss_engine_destroy", null, ["number"], [this.handle]);
793
954
  // The renderer is gone now, so bgfx no longer references the selector.
@@ -797,10 +958,35 @@ export class GossEngine {
797
958
  this.selectorCapacity = 0;
798
959
  }
799
960
  }
800
- /// Per-preview runtime: frame submission, beauty, tracking, lens. Owns
801
- /// its own scratch allocations (frame descriptor, pixel buffer,
802
- /// landmarks) rather than one shared per-engine pool - matches every
803
- /// other SDK's own per-session confinement.
961
+ /// The ABI major these bindings were generated against: handing the engine its own
962
+ /// version back would always match and test nothing. The major, not the whole version,
963
+ /// because only the major decides compatibility. Held to the header by the abi gate.
964
+ const GOSS_ABI_MAJOR = 0;
965
+ /// What a report struct measures, checked against the engine's own layout.
966
+ const ENGINE_REPORT_BYTES = 80;
967
+ const SESSION_REPORT_BYTES = 88;
968
+ /// What a lens node is doing, as against what its manifest asked for.
969
+ export var GossNodeState;
970
+ (function (GossNodeState) {
971
+ GossNodeState[GossNodeState["Ready"] = 0] = "Ready";
972
+ GossNodeState[GossNodeState["Degraded"] = 1] = "Degraded";
973
+ GossNodeState[GossNodeState["Failed"] = 2] = "Failed";
974
+ })(GossNodeState || (GossNodeState = {}));
975
+ /// Why a node is not ready.
976
+ export var GossNodeReason;
977
+ (function (GossNodeReason) {
978
+ GossNodeReason[GossNodeReason["None"] = 0] = "None";
979
+ GossNodeReason[GossNodeReason["OutOfMemory"] = 1] = "OutOfMemory";
980
+ GossNodeReason[GossNodeReason["AssetMissing"] = 2] = "AssetMissing";
981
+ GossNodeReason[GossNodeReason["AssetMalformed"] = 3] = "AssetMalformed";
982
+ GossNodeReason[GossNodeReason["AssetTooLarge"] = 4] = "AssetTooLarge";
983
+ GossNodeReason[GossNodeReason["ShaderMissing"] = 5] = "ShaderMissing";
984
+ GossNodeReason[GossNodeReason["ShaderLinkFailed"] = 6] = "ShaderLinkFailed";
985
+ GossNodeReason[GossNodeReason["ModelRejected"] = 7] = "ModelRejected";
986
+ GossNodeReason[GossNodeReason["ModelUnsupported"] = 8] = "ModelUnsupported";
987
+ GossNodeReason[GossNodeReason["CapabilityUnavailable"] = 9] = "CapabilityUnavailable";
988
+ GossNodeReason[GossNodeReason["ConstraintFailed"] = 10] = "ConstraintFailed";
989
+ })(GossNodeReason || (GossNodeReason = {}));
804
990
  export class GossSession {
805
991
  mod;
806
992
  handle;
@@ -834,6 +1020,7 @@ export class GossSession {
834
1020
  /// freed in destroy() - no per-call wasm heap churn.
835
1021
  scratchPtr = 0;
836
1022
  scratchCapacity = 0;
1023
+ lensChangeInFlight = false;
837
1024
  constructor(mod, handle) {
838
1025
  this.mod = mod;
839
1026
  this.handle = handle;
@@ -905,15 +1092,40 @@ export class GossSession {
905
1092
  /// A lens built entirely from beauty.* nodes (beauty-baseline, say)
906
1093
  /// activates and runs for real regardless, since those go through
907
1094
  /// applyWebBeautyChain's own embedded shaders, not a per-lens one.
908
- activateLens(manifestJson) {
1095
+ /// Activates a lens, resolving false when it is live but a node the manifest did
1096
+ /// not mark optional could not do what it asked; nodeReports says which and why.
1097
+ /// On the WebGPU build this suspends and creates work that reaches Dawn, so not
1098
+ /// awaiting left the module suspended and the next render reentered it.
1099
+ async activateLens(manifestJson) {
909
1100
  const bytes = new TextEncoder().encode(manifestJson);
910
1101
  const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes.length]);
911
1102
  this.mod.HEAPU8.set(bytes, ptr);
912
- this.mod.ccall("goss_session_activate_lens", "number", ["number", "number", "number"], [this.handle, ptr, bytes.length]);
913
- this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes.length]);
1103
+ this.lensChangeInFlight = true;
1104
+ try {
1105
+ const status = (await this.mod.ccall("goss_session_activate_lens", "number", ["number", "number", "number"], [this.handle, ptr, bytes.length], { async: true }));
1106
+ return status === GOSS_OK;
1107
+ }
1108
+ finally {
1109
+ this.lensChangeInFlight = false;
1110
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes.length]);
1111
+ }
914
1112
  }
915
- deactivateLens() {
916
- this.mod.ccall("goss_session_deactivate_lens", null, ["number"], [this.handle]);
1113
+ /// Deactivation destroys the same resources, so it suspends for the same
1114
+ /// reason and is awaited for the same reason.
1115
+ async deactivateLens() {
1116
+ this.lensChangeInFlight = true;
1117
+ try {
1118
+ await this.mod.ccall("goss_session_deactivate_lens", null, ["number"], [this.handle], { async: true });
1119
+ }
1120
+ finally {
1121
+ this.lensChangeInFlight = false;
1122
+ }
1123
+ }
1124
+ /// True while an activation or deactivation is unwound. A render during that
1125
+ /// window reenters a suspended module, which Asyncify cannot do, so a caller
1126
+ /// driving its own frame loop skips the frame rather than calling in.
1127
+ get isLensChangeInFlight() {
1128
+ return this.lensChangeInFlight;
917
1129
  }
918
1130
  /// Advances the active lens's triggers/param ramps by dtUs, evaluating
919
1131
  /// them against signals - omitted fields read as false/zero, so a bare
@@ -975,6 +1187,234 @@ export class GossSession {
975
1187
  /// Casts a world-space ray against the submitted world mesh, returning the
976
1188
  /// nearest surface hit `{ point, distance }`, or null when no mesh is
977
1189
  /// submitted or the ray misses. A tap-to-place lens anchors content there.
1190
+ /// Decodes a PNG to packed RGBA8 through the decoder the engine already carries,
1191
+ /// for a caller holding an encoded image and none of its own.
1192
+ decodePng(bytes) {
1193
+ const inPtr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes.length]);
1194
+ const wPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
1195
+ const hPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
1196
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
1197
+ let outPtr = 0;
1198
+ let needed = 0;
1199
+ try {
1200
+ this.mod.HEAPU8.set(bytes, inPtr);
1201
+ const args = ["number", "number", "number", "number", "number", "number", "number"];
1202
+ this.mod.ccall("goss_engine_decode_png", "number", args, [inPtr, bytes.length, 0, 0, wPtr, hPtr, lenPtr]);
1203
+ needed = this.mod.HEAPU32[lenPtr >> 2];
1204
+ if (needed === 0)
1205
+ return null;
1206
+ outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [needed]);
1207
+ if (this.mod.ccall("goss_engine_decode_png", "number", args, [inPtr, bytes.length, outPtr, needed, wPtr, hPtr, lenPtr]) !== GOSS_OK)
1208
+ return null;
1209
+ return {
1210
+ rgba: this.mod.HEAPU8.slice(outPtr, outPtr + needed),
1211
+ width: this.mod.HEAPU32[wPtr >> 2],
1212
+ height: this.mod.HEAPU32[hPtr >> 2],
1213
+ };
1214
+ }
1215
+ finally {
1216
+ this.mod.ccall("goss_free", null, ["number", "number"], [inPtr, bytes.length]);
1217
+ if (outPtr !== 0)
1218
+ this.mod.ccall("goss_free", null, ["number", "number"], [outPtr, needed]);
1219
+ for (const [ptr, size] of [[wPtr, 4], [hPtr, 4], [lenPtr, 8]]) {
1220
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, size]);
1221
+ }
1222
+ }
1223
+ }
1224
+ /// A walkable route over the submitted world mesh, so an agent walks content
1225
+ /// across real scanned ground. Null when no mesh is submitted or no route exists.
1226
+ pathAcrossWorld(start, goal) {
1227
+ const capacity = 256;
1228
+ const aPtr = this.mod.ccall("goss_alloc", "number", ["number"], [12]);
1229
+ const bPtr = this.mod.ccall("goss_alloc", "number", ["number"], [12]);
1230
+ const outBytes = capacity * 12;
1231
+ const outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [outBytes]);
1232
+ const countPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
1233
+ try {
1234
+ this.mod.HEAPF32.set(start, aPtr / 4);
1235
+ this.mod.HEAPF32.set(goal, bPtr / 4);
1236
+ const args = ["number", "number", "number", "number", "number", "number"];
1237
+ if (this.mod.ccall("goss_session_path_across_world", "number", args, [this.handle, aPtr, bPtr, outPtr, capacity, countPtr]) !== GOSS_OK)
1238
+ return null;
1239
+ const found = Math.min(this.mod.HEAPU32[countPtr >> 2], capacity);
1240
+ const out = [];
1241
+ for (let i = 0; i < found; i += 1) {
1242
+ const w = (outPtr >> 2) + i * 3;
1243
+ out.push([this.mod.HEAPF32[w], this.mod.HEAPF32[w + 1], this.mod.HEAPF32[w + 2]]);
1244
+ }
1245
+ return out;
1246
+ }
1247
+ finally {
1248
+ for (const [ptr, bytes] of [[aPtr, 12], [bPtr, 12], [outPtr, outBytes], [countPtr, 8]]) {
1249
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
1250
+ }
1251
+ }
1252
+ }
1253
+ /// What a submitted plane is, as a named kind rather than the platform's own
1254
+ /// number, and whether a thing can rest on it.
1255
+ planeKind(planeId) {
1256
+ const kindPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
1257
+ const bearingPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
1258
+ try {
1259
+ const args = ["number", "number", "number", "number"];
1260
+ if (this.mod.ccall("goss_session_plane_kind", "number", args, [this.handle, planeId, kindPtr, bearingPtr]) !== GOSS_OK)
1261
+ return null;
1262
+ return { kind: this.mod.HEAPU32[kindPtr >> 2], bearing: this.mod.HEAPU32[bearingPtr >> 2] === 1 };
1263
+ }
1264
+ finally {
1265
+ this.mod.ccall("goss_free", null, ["number", "number"], [kindPtr, 4]);
1266
+ this.mod.ccall("goss_free", null, ["number", "number"], [bearingPtr, 4]);
1267
+ }
1268
+ }
1269
+ /// The plane this session would call the floor: the lowest bearing surface it
1270
+ /// has been shown, or null when it has been shown none.
1271
+ floorPlaneId() {
1272
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
1273
+ try {
1274
+ if (this.mod.ccall("goss_session_floor_plane", "number", ["number", "number"], [this.handle, ptr]) !== GOSS_OK)
1275
+ return null;
1276
+ return this.mod.HEAPU32[ptr >> 2];
1277
+ }
1278
+ finally {
1279
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, 8]);
1280
+ }
1281
+ }
1282
+ /// Where a footprint fits, best surface first: the bearing plane with the most
1283
+ /// room left afterwards. An empty array is an answer.
1284
+ placeOn(item, occupants = []) {
1285
+ const itemPtr = this.mod.ccall("goss_alloc", "number", ["number"], [12]);
1286
+ const occupantBytes = occupants.length * 24;
1287
+ const occupantPtr = occupants.length === 0 ? 0 : this.mod.ccall("goss_alloc", "number", ["number"], [occupantBytes]);
1288
+ const capacity = 32;
1289
+ const outBytes = capacity * 24;
1290
+ const outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [outBytes]);
1291
+ const countPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
1292
+ try {
1293
+ this.mod.HEAPF32.set([item.width, item.depth, item.height ?? 0], itemPtr / 4);
1294
+ // Each occupant is a u64 id then four floats: the id is written as two words
1295
+ // because the heap view is 32-bit and an id past 2^32 is not a plane id any
1296
+ // platform hands out.
1297
+ occupants.forEach((o, i) => {
1298
+ const base = occupantPtr + i * 24;
1299
+ this.mod.HEAPU32[base >> 2] = o.planeId;
1300
+ this.mod.HEAPU32[(base >> 2) + 1] = 0;
1301
+ this.mod.HEAPF32.set([o.x, o.z, o.width, o.depth], (base + 8) / 4);
1302
+ });
1303
+ const args = ["number", "number", "number", "number", "number", "number", "number"];
1304
+ const status = this.mod.ccall("goss_session_place_on", "number", args, [this.handle, itemPtr, occupantPtr, occupants.length, outPtr, capacity, countPtr]);
1305
+ if (status !== GOSS_OK && status !== GOSS_AGAIN)
1306
+ return [];
1307
+ const found = Math.min(this.mod.HEAPU32[countPtr >> 2], capacity);
1308
+ const out = [];
1309
+ for (let i = 0; i < found; i += 1) {
1310
+ const base = outPtr + i * 24;
1311
+ const floats = (base + 8) / 4;
1312
+ out.push({
1313
+ planeId: this.mod.HEAPU32[base >> 2],
1314
+ position: [this.mod.HEAPF32[floats], this.mod.HEAPF32[floats + 1], this.mod.HEAPF32[floats + 2]],
1315
+ freeFraction: this.mod.HEAPF32[floats + 3],
1316
+ });
1317
+ }
1318
+ return out;
1319
+ }
1320
+ finally {
1321
+ this.mod.ccall("goss_free", null, ["number", "number"], [itemPtr, 12]);
1322
+ if (occupantPtr !== 0)
1323
+ this.mod.ccall("goss_free", null, ["number", "number"], [occupantPtr, occupantBytes]);
1324
+ this.mod.ccall("goss_free", null, ["number", "number"], [outPtr, outBytes]);
1325
+ this.mod.ccall("goss_free", null, ["number", "number"], [countPtr, 8]);
1326
+ }
1327
+ }
1328
+ /// Point to point in metres with its uncertainty. `known` is false when either
1329
+ /// end vouched for no accuracy, so a sigma of zero is never read as certainty.
1330
+ measureBetween(from, to, fromAccuracyM = 0, toAccuracyM = 0) {
1331
+ const aPtr = this.mod.ccall("goss_alloc", "number", ["number"], [12]);
1332
+ const bPtr = this.mod.ccall("goss_alloc", "number", ["number"], [12]);
1333
+ const valuePtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
1334
+ const sigmaPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
1335
+ const knownPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
1336
+ try {
1337
+ this.mod.HEAPF32.set(from, aPtr / 4);
1338
+ this.mod.HEAPF32.set(to, bPtr / 4);
1339
+ const args = ["number", "number", "number", "number", "number", "number", "number", "number"];
1340
+ const status = this.mod.ccall("goss_session_measure_between", "number", args, [this.handle, aPtr, fromAccuracyM, bPtr, toAccuracyM, valuePtr, sigmaPtr, knownPtr]);
1341
+ if (status !== GOSS_OK)
1342
+ return null;
1343
+ return {
1344
+ metres: this.mod.HEAPF32[valuePtr >> 2],
1345
+ sigma: this.mod.HEAPF32[sigmaPtr >> 2],
1346
+ known: this.mod.HEAPU32[knownPtr >> 2] === 1,
1347
+ };
1348
+ }
1349
+ finally {
1350
+ for (const [ptr, bytes] of [[aPtr, 12], [bPtr, 12], [valuePtr, 4], [sigmaPtr, 4], [knownPtr, 4]]) {
1351
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
1352
+ }
1353
+ }
1354
+ }
1355
+ /// What this device can offer another: one landmark per world anchor it holds,
1356
+ /// in its own frame. A pose never crosses.
1357
+ sharedLandmarks() {
1358
+ const capacity = 32;
1359
+ const bytes = capacity * 24;
1360
+ const outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]);
1361
+ const countPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
1362
+ try {
1363
+ const args = ["number", "number", "number", "number"];
1364
+ const status = this.mod.ccall("goss_session_shared_landmarks", "number", args, [this.handle, outPtr, capacity, countPtr]);
1365
+ if (status !== GOSS_OK && status !== GOSS_AGAIN)
1366
+ return [];
1367
+ const found = Math.min(this.mod.HEAPU32[countPtr >> 2], capacity);
1368
+ const out = [];
1369
+ for (let i = 0; i < found; i += 1) {
1370
+ const base = outPtr + i * 24;
1371
+ const floats = (base + 8) / 4;
1372
+ out.push({
1373
+ id: this.mod.HEAPU32[base >> 2],
1374
+ position: [this.mod.HEAPF32[floats], this.mod.HEAPF32[floats + 1], this.mod.HEAPF32[floats + 2]],
1375
+ confidence: this.mod.HEAPF32[floats + 3],
1376
+ });
1377
+ }
1378
+ return out;
1379
+ }
1380
+ finally {
1381
+ this.mod.ccall("goss_free", null, ["number", "number"], [outPtr, bytes]);
1382
+ this.mod.ccall("goss_free", null, ["number", "number"], [countPtr, 8]);
1383
+ }
1384
+ }
1385
+ /// The transform from the sender's origin into this one, column-major, with the
1386
+ /// fit it achieved. Null when fewer than three landmarks matched, which cannot
1387
+ /// fix a rigid transform.
1388
+ alignShared(theirs) {
1389
+ const bytes = Math.max(theirs.length, 1) * 24;
1390
+ const listPtr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]);
1391
+ const transformPtr = this.mod.ccall("goss_alloc", "number", ["number"], [64]);
1392
+ const rmsPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
1393
+ const matchedPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
1394
+ try {
1395
+ theirs.forEach((l, i) => {
1396
+ const base = listPtr + i * 24;
1397
+ this.mod.HEAPU32[base >> 2] = l.id;
1398
+ this.mod.HEAPU32[(base >> 2) + 1] = 0;
1399
+ this.mod.HEAPF32.set([l.position[0], l.position[1], l.position[2], l.confidence ?? 1], (base + 8) / 4);
1400
+ });
1401
+ const args = ["number", "number", "number", "number", "number", "number"];
1402
+ const status = this.mod.ccall("goss_session_align_shared", "number", args, [this.handle, listPtr, theirs.length, transformPtr, rmsPtr, matchedPtr]);
1403
+ if (status !== GOSS_OK)
1404
+ return null;
1405
+ return {
1406
+ transform: this.mod.HEAPF32.slice(transformPtr >> 2, (transformPtr >> 2) + 16),
1407
+ rmsError: this.mod.HEAPF32[rmsPtr >> 2],
1408
+ matched: this.mod.HEAPU32[matchedPtr >> 2],
1409
+ };
1410
+ }
1411
+ finally {
1412
+ this.mod.ccall("goss_free", null, ["number", "number"], [listPtr, bytes]);
1413
+ this.mod.ccall("goss_free", null, ["number", "number"], [transformPtr, 64]);
1414
+ this.mod.ccall("goss_free", null, ["number", "number"], [rmsPtr, 4]);
1415
+ this.mod.ccall("goss_free", null, ["number", "number"], [matchedPtr, 4]);
1416
+ }
1417
+ }
978
1418
  raycastWorldMesh(origin, direction) {
979
1419
  const oPtr = this.mod.ccall("goss_alloc", "number", ["number"], [12]);
980
1420
  this.mod.HEAPF32.set(origin, oPtr / 4);
@@ -2366,6 +2806,621 @@ export class GossSession {
2366
2806
  this.mod.ccall("goss_session_write_reconstruction", "number", ["number", "number", "number"], [this.handle, ptr, gaussians.length / 14]);
2367
2807
  this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
2368
2808
  }
2809
+ /// Every section this build writes, asked of the engine rather than assumed: a
2810
+ /// hand-written mask excluded the embedding section the day it was added.
2811
+ selectAll() {
2812
+ return this.mod.ccall("goss_perception_select_all", "number", [], []);
2813
+ }
2814
+ /// How many verbs this engine build knows, asked of the engine rather than taken
2815
+ /// from the enum, so a newer engine behind this wrapper is not misread.
2816
+ verbCount() {
2817
+ return this.mod.ccall("goss_scope_verb_count", "number", [], []);
2818
+ }
2819
+ /// The engine's own name for a verb, so a refusal reads as a sentence and a
2820
+ /// permission prompt reads as words rather than a bitmask.
2821
+ verbName(verb) {
2822
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
2823
+ let outPtr = 0;
2824
+ let needed = 0;
2825
+ try {
2826
+ const args = ["number", "number", "number", "number"];
2827
+ this.mod.ccall("goss_scope_verb_name", "number", args, [verb, 0, 0, lenPtr]);
2828
+ needed = this.mod.HEAPU32[lenPtr >> 2];
2829
+ if (needed === 0)
2830
+ return null;
2831
+ outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [needed]);
2832
+ if (this.mod.ccall("goss_scope_verb_name", "number", args, [verb, outPtr, needed, lenPtr]) !== GOSS_OK)
2833
+ return null;
2834
+ return new TextDecoder().decode(this.mod.HEAPU8.subarray(outPtr, outPtr + needed));
2835
+ }
2836
+ finally {
2837
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 8]);
2838
+ if (outPtr !== 0)
2839
+ this.mod.ccall("goss_free", null, ["number", "number"], [outPtr, needed]);
2840
+ }
2841
+ }
2842
+ /// One versioned record of what the engine currently sees. Every section
2843
+ /// carries its own tag, version and byte length, so a consumer built against an
2844
+ /// older schema steps over what it does not know. Sized in one retry.
2845
+ perceptionSnapshot(select = this.selectAll()) {
2846
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
2847
+ try {
2848
+ this.mod.ccall("goss_session_perception_snapshot", "number", ["number", "number", "number", "number", "number"], [this.handle, select, 0, 0, lenPtr]);
2849
+ const needed = this.mod.HEAPU32[lenPtr >> 2];
2850
+ if (needed === 0)
2851
+ return null;
2852
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [needed]);
2853
+ try {
2854
+ const status = this.mod.ccall("goss_session_perception_snapshot", "number", ["number", "number", "number", "number", "number"], [this.handle, select, ptr, needed, lenPtr]);
2855
+ if (status !== GOSS_OK)
2856
+ return null;
2857
+ const written = this.mod.HEAPU32[lenPtr >> 2];
2858
+ return this.mod.HEAPU8.slice(ptr, ptr + written);
2859
+ }
2860
+ finally {
2861
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, needed]);
2862
+ }
2863
+ }
2864
+ finally {
2865
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 4]);
2866
+ }
2867
+ }
2868
+ /// The same record as compact JSON, for a gateway that speaks it.
2869
+ perceptionJson(select = this.selectAll()) {
2870
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
2871
+ try {
2872
+ this.mod.ccall("goss_session_perception_json", "number", ["number", "number", "number", "number", "number"], [this.handle, select, 0, 0, lenPtr]);
2873
+ const needed = this.mod.HEAPU32[lenPtr >> 2];
2874
+ if (needed === 0)
2875
+ return null;
2876
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [needed]);
2877
+ try {
2878
+ const status = this.mod.ccall("goss_session_perception_json", "number", ["number", "number", "number", "number", "number"], [this.handle, select, ptr, needed, lenPtr]);
2879
+ if (status !== GOSS_OK)
2880
+ return null;
2881
+ const written = this.mod.HEAPU32[lenPtr >> 2];
2882
+ return new TextDecoder().decode(this.mod.HEAPU8.slice(ptr, ptr + written));
2883
+ }
2884
+ finally {
2885
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, needed]);
2886
+ }
2887
+ }
2888
+ finally {
2889
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 4]);
2890
+ }
2891
+ }
2892
+ /// Opens a screen as a source. A scale of zero takes the surface's own.
2893
+ openScreen(surfaceId, scale = 0) {
2894
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
2895
+ try {
2896
+ if (this.mod.ccall("goss_session_open_screen", "number", ["number", "number", "number", "number"], [this.handle, surfaceId, scale, ptr]) !== GOSS_OK)
2897
+ return null;
2898
+ return this.mod.HEAPU32[ptr >> 2];
2899
+ }
2900
+ finally {
2901
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, 4]);
2902
+ }
2903
+ }
2904
+ closeScreen(screen) {
2905
+ return this.mod.ccall("goss_session_close_screen", "number", ["number", "number"], [this.handle, screen]) === GOSS_OK;
2906
+ }
2907
+ /// Submits the newest frame. False when the screen has not changed, so a still
2908
+ /// desktop costs nothing.
2909
+ stepScreen(screen, source = "") {
2910
+ const bytes = new TextEncoder().encode(source);
2911
+ const ptr = bytes.length === 0 ? 0 : this.mod.ccall("goss_alloc", "number", ["number"], [bytes.length]);
2912
+ try {
2913
+ if (ptr !== 0)
2914
+ this.mod.HEAPU8.set(bytes, ptr);
2915
+ return this.mod.ccall("goss_session_step_screen", "number", ["number", "number", "number", "number"], [this.handle, screen, ptr, bytes.length]) === GOSS_OK;
2916
+ }
2917
+ finally {
2918
+ if (ptr !== 0)
2919
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes.length]);
2920
+ }
2921
+ }
2922
+ /// Where a normalized point lands: logical points, backing pixels, desktop.
2923
+ screenPoint(screen, x, y) {
2924
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [24]);
2925
+ try {
2926
+ if (this.mod.ccall("goss_session_screen_point", "number", ["number", "number", "number", "number", "number", "number", "number"], [this.handle, screen, x, y, ptr, ptr + 8, ptr + 16]) !== GOSS_OK)
2927
+ return null;
2928
+ const f = (off) => this.mod.HEAPF32[(ptr + off) >> 2];
2929
+ return { logical: [f(0), f(4)], pixel: [f(8), f(12)], desktop: [f(16), f(20)] };
2930
+ }
2931
+ finally {
2932
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, 24]);
2933
+ }
2934
+ }
2935
+ /// Narrows what this session answers. A session opens fully permissive; a read
2936
+ /// out of scope is dropped from the record rather than failing, and a verb out
2937
+ /// of scope is refused.
2938
+ setScope(sections, verbs) {
2939
+ return this.mod.ccall("goss_session_set_scope", "number", ["number", "number", "number"], [this.handle, sections, verbs]) === GOSS_OK;
2940
+ }
2941
+ scope() {
2942
+ const sectionsPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
2943
+ const verbsPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
2944
+ try {
2945
+ if (this.mod.ccall("goss_session_scope", "number", ["number", "number", "number"], [this.handle, sectionsPtr, verbsPtr]) !== GOSS_OK)
2946
+ return { sections: 0, verbs: 0 };
2947
+ return { sections: this.mod.HEAPU32[sectionsPtr >> 2], verbs: this.mod.HEAPU32[verbsPtr >> 2] };
2948
+ }
2949
+ finally {
2950
+ this.mod.ccall("goss_free", null, ["number", "number"], [sectionsPtr, 4]);
2951
+ this.mod.ccall("goss_free", null, ["number", "number"], [verbsPtr, 4]);
2952
+ }
2953
+ }
2954
+ /// Opens the memory plane. Nothing is remembered until this is called, and
2955
+ /// the bound is yours, so the memory you were promised is the memory you get.
2956
+ memoryOpen(dim, maxEntries = 4096) {
2957
+ return this.mod.ccall("goss_session_memory_open", "number", ["number", "number", "number"], [this.handle, dim, maxEntries]) === GOSS_OK;
2958
+ }
2959
+ memoryClose() {
2960
+ return this.mod.ccall("goss_session_memory_close", "number", ["number"], [this.handle]) === GOSS_OK;
2961
+ }
2962
+ /// Remembers one embedding; the same id replaces rather than duplicating.
2963
+ remember(id, embedding) {
2964
+ const bytes = embedding.length * 4;
2965
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]);
2966
+ try {
2967
+ this.mod.HEAPF32.set(embedding, ptr >> 2);
2968
+ return this.mod.ccall("goss_session_memory_remember", "number", ["number", "number", "number", "number"], [this.handle, id, ptr, embedding.length]) === GOSS_OK;
2969
+ }
2970
+ finally {
2971
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
2972
+ }
2973
+ }
2974
+ forget(id) {
2975
+ return this.mod.ccall("goss_session_memory_forget", "number", ["number", "number"], [this.handle, id]) === GOSS_OK;
2976
+ }
2977
+ /// The nearest remembered embeddings, fewer than k on a smaller memory rather
2978
+ /// than padded with nothing.
2979
+ memorySearch(query, k = 8) {
2980
+ const queryBytes = query.length * 4;
2981
+ const queryPtr = this.mod.ccall("goss_alloc", "number", ["number"], [queryBytes]);
2982
+ const idsPtr = this.mod.ccall("goss_alloc", "number", ["number"], [k * 8]);
2983
+ const scoresPtr = this.mod.ccall("goss_alloc", "number", ["number"], [k * 4]);
2984
+ const countPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
2985
+ try {
2986
+ this.mod.HEAPF32.set(query, queryPtr >> 2);
2987
+ if (this.mod.ccall("goss_session_memory_search", "number", ["number", "number", "number", "number", "number", "number", "number"], [this.handle, queryPtr, query.length, k, idsPtr, scoresPtr, countPtr]) !== GOSS_OK)
2988
+ return [];
2989
+ const count = this.mod.HEAPU32[countPtr >> 2];
2990
+ const out = [];
2991
+ for (let i = 0; i < count; i += 1) {
2992
+ // The low word of the id is the whole value at any count this engine
2993
+ // reaches, and reading it avoids a BigInt in the hot read.
2994
+ out.push({ id: this.mod.HEAPU32[(idsPtr + i * 8) >> 2], score: this.mod.HEAPF32[(scoresPtr + i * 4) >> 2] });
2995
+ }
2996
+ return out;
2997
+ }
2998
+ finally {
2999
+ this.mod.ccall("goss_free", null, ["number", "number"], [queryPtr, queryBytes]);
3000
+ this.mod.ccall("goss_free", null, ["number", "number"], [idsPtr, k * 8]);
3001
+ this.mod.ccall("goss_free", null, ["number", "number"], [scoresPtr, k * 4]);
3002
+ this.mod.ccall("goss_free", null, ["number", "number"], [countPtr, 4]);
3003
+ }
3004
+ }
3005
+ memoryStats() {
3006
+ const countPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
3007
+ const bytesPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
3008
+ try {
3009
+ if (this.mod.ccall("goss_session_memory_stats", "number", ["number", "number", "number"], [this.handle, countPtr, bytesPtr]) !== GOSS_OK)
3010
+ return { count: 0, bytes: 0 };
3011
+ return { count: this.mod.HEAPU32[countPtr >> 2], bytes: this.mod.HEAPU32[bytesPtr >> 2] };
3012
+ }
3013
+ finally {
3014
+ this.mod.ccall("goss_free", null, ["number", "number"], [countPtr, 4]);
3015
+ this.mod.ccall("goss_free", null, ["number", "number"], [bytesPtr, 8]);
3016
+ }
3017
+ }
3018
+ /// The whole memory as bytes, so a cold start is instant.
3019
+ memorySave() {
3020
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
3021
+ try {
3022
+ this.mod.ccall("goss_session_memory_save", "number", ["number", "number", "number", "number"], [this.handle, 0, 0, lenPtr]);
3023
+ const needed = this.mod.HEAPU32[lenPtr >> 2];
3024
+ if (needed === 0)
3025
+ return new Uint8Array(0);
3026
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [needed]);
3027
+ try {
3028
+ if (this.mod.ccall("goss_session_memory_save", "number", ["number", "number", "number", "number"], [this.handle, ptr, needed, lenPtr]) !== GOSS_OK)
3029
+ return new Uint8Array(0);
3030
+ return new Uint8Array(this.mod.HEAPU8.subarray(ptr, ptr + needed));
3031
+ }
3032
+ finally {
3033
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, needed]);
3034
+ }
3035
+ }
3036
+ finally {
3037
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 8]);
3038
+ }
3039
+ }
3040
+ /// The memory sealed under a host key. The nonce is yours: reusing one under
3041
+ /// the same key breaks the cipher.
3042
+ memorySaveSealed(key, nonce) {
3043
+ const keyPtr = this.mod.ccall("goss_alloc", "number", ["number"], [key.length]);
3044
+ const noncePtr = this.mod.ccall("goss_alloc", "number", ["number"], [nonce.length]);
3045
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
3046
+ try {
3047
+ this.mod.HEAPU8.set(key, keyPtr);
3048
+ this.mod.HEAPU8.set(nonce, noncePtr);
3049
+ this.mod.ccall("goss_session_memory_save_sealed", "number", ["number", "number", "number", "number", "number", "number"], [this.handle, keyPtr, noncePtr, 0, 0, lenPtr]);
3050
+ const needed = this.mod.HEAPU32[lenPtr >> 2];
3051
+ if (needed === 0)
3052
+ return new Uint8Array(0);
3053
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [needed]);
3054
+ try {
3055
+ if (this.mod.ccall("goss_session_memory_save_sealed", "number", ["number", "number", "number", "number", "number", "number"], [this.handle, keyPtr, noncePtr, ptr, needed, lenPtr]) !== GOSS_OK)
3056
+ return new Uint8Array(0);
3057
+ return new Uint8Array(this.mod.HEAPU8.subarray(ptr, ptr + needed));
3058
+ }
3059
+ finally {
3060
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, needed]);
3061
+ }
3062
+ }
3063
+ finally {
3064
+ this.mod.ccall("goss_free", null, ["number", "number"], [keyPtr, key.length]);
3065
+ this.mod.ccall("goss_free", null, ["number", "number"], [noncePtr, nonce.length]);
3066
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 8]);
3067
+ }
3068
+ }
3069
+ memoryLoadSealed(key, bytes) {
3070
+ const keyPtr = this.mod.ccall("goss_alloc", "number", ["number"], [key.length]);
3071
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes.length]);
3072
+ try {
3073
+ this.mod.HEAPU8.set(key, keyPtr);
3074
+ this.mod.HEAPU8.set(bytes, ptr);
3075
+ return this.mod.ccall("goss_session_memory_load_sealed", "number", ["number", "number", "number", "number"], [this.handle, keyPtr, ptr, bytes.length]) === GOSS_OK;
3076
+ }
3077
+ finally {
3078
+ this.mod.ccall("goss_free", null, ["number", "number"], [keyPtr, key.length]);
3079
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes.length]);
3080
+ }
3081
+ }
3082
+ memoryLoad(bytes) {
3083
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes.length]);
3084
+ try {
3085
+ this.mod.HEAPU8.set(bytes, ptr);
3086
+ return this.mod.ccall("goss_session_memory_load", "number", ["number", "number", "number"], [this.handle, ptr, bytes.length]) === GOSS_OK;
3087
+ }
3088
+ finally {
3089
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes.length]);
3090
+ }
3091
+ }
3092
+ /// Turns on the text rail. A detector alone finds where the text is, which
3093
+ /// is what a redaction or a rectified crop needs; pass a recogniser and its
3094
+ /// dictionary to get strings back.
3095
+ enableText(detector, recognizer, dictionary, detectSide = 320) {
3096
+ const rec = recognizer ?? new Uint8Array(0);
3097
+ const dict = dictionary ?? new Uint8Array(0);
3098
+ const detPtr = this.mod.ccall("goss_alloc", "number", ["number"], [detector.length]);
3099
+ const recPtr = rec.length === 0 ? 0 : this.mod.ccall("goss_alloc", "number", ["number"], [rec.length]);
3100
+ const dictPtr = dict.length === 0 ? 0 : this.mod.ccall("goss_alloc", "number", ["number"], [dict.length]);
3101
+ try {
3102
+ this.mod.HEAPU8.set(detector, detPtr);
3103
+ if (recPtr !== 0)
3104
+ this.mod.HEAPU8.set(rec, recPtr);
3105
+ if (dictPtr !== 0)
3106
+ this.mod.HEAPU8.set(dict, dictPtr);
3107
+ return this.mod.ccall("goss_session_enable_text", "number", ["number", "number", "number", "number", "number", "number", "number", "number"], [this.handle, detPtr, detector.length, recPtr, rec.length, dictPtr, dict.length, detectSide]) === GOSS_OK;
3108
+ }
3109
+ finally {
3110
+ this.mod.ccall("goss_free", null, ["number", "number"], [detPtr, detector.length]);
3111
+ if (recPtr !== 0)
3112
+ this.mod.ccall("goss_free", null, ["number", "number"], [recPtr, rec.length]);
3113
+ if (dictPtr !== 0)
3114
+ this.mod.ccall("goss_free", null, ["number", "number"], [dictPtr, dict.length]);
3115
+ }
3116
+ }
3117
+ disableText() {
3118
+ return this.mod.ccall("goss_session_disable_text", "number", ["number"], [this.handle]) === GOSS_OK;
3119
+ }
3120
+ /// How many readings the frame holds and how many the bound turned away.
3121
+ textCount() {
3122
+ const countPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
3123
+ const refusedPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
3124
+ try {
3125
+ if (this.mod.ccall("goss_session_text_count", "number", ["number", "number", "number"], [this.handle, countPtr, refusedPtr]) !== GOSS_OK) {
3126
+ return { live: 0, refused: 0 };
3127
+ }
3128
+ return { live: this.mod.HEAPU32[countPtr >> 2], refused: this.mod.HEAPU32[refusedPtr >> 2] };
3129
+ }
3130
+ finally {
3131
+ this.mod.ccall("goss_free", null, ["number", "number"], [countPtr, 4]);
3132
+ this.mod.ccall("goss_free", null, ["number", "number"], [refusedPtr, 8]);
3133
+ }
3134
+ }
3135
+ /// Everything the frame says, each reading with its quadrilateral and string.
3136
+ readings() {
3137
+ const { live } = this.textCount();
3138
+ const entryBytes = 64;
3139
+ const entryPtr = this.mod.ccall("goss_alloc", "number", ["number"], [entryBytes]);
3140
+ const lenPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
3141
+ const out = [];
3142
+ try {
3143
+ for (let i = 0; i < live; i += 1) {
3144
+ if (this.mod.ccall("goss_session_text_at", "number", ["number", "number", "number"], [this.handle, i, entryPtr]) !== GOSS_OK)
3145
+ continue;
3146
+ const f32 = (off) => this.mod.HEAPF32[(entryPtr + off) >> 2];
3147
+ const u32 = (off) => this.mod.HEAPU32[(entryPtr + off) >> 2];
3148
+ this.mod.ccall("goss_session_text_string", "number", ["number", "number", "number", "number", "number"], [this.handle, i, 0, 0, lenPtr]);
3149
+ const needed = this.mod.HEAPU32[lenPtr >> 2];
3150
+ let text = "";
3151
+ if (needed > 0) {
3152
+ const textPtr = this.mod.ccall("goss_alloc", "number", ["number"], [needed]);
3153
+ try {
3154
+ if (this.mod.ccall("goss_session_text_string", "number", ["number", "number", "number", "number", "number"], [this.handle, i, textPtr, needed, lenPtr]) === GOSS_OK) {
3155
+ text = new TextDecoder().decode(this.mod.HEAPU8.subarray(textPtr, textPtr + needed));
3156
+ }
3157
+ }
3158
+ finally {
3159
+ this.mod.ccall("goss_free", null, ["number", "number"], [textPtr, needed]);
3160
+ }
3161
+ }
3162
+ out.push({
3163
+ text,
3164
+ quad: [f32(0), f32(4), f32(8), f32(12), f32(16), f32(20), f32(24), f32(28)],
3165
+ confidence: f32(32),
3166
+ origin: u32(36),
3167
+ script: u32(40),
3168
+ direction: u32(44),
3169
+ trackId: u32(48),
3170
+ line: u32(52),
3171
+ paragraph: u32(56),
3172
+ });
3173
+ }
3174
+ return out;
3175
+ }
3176
+ finally {
3177
+ this.mod.ccall("goss_free", null, ["number", "number"], [entryPtr, entryBytes]);
3178
+ this.mod.ccall("goss_free", null, ["number", "number"], [lenPtr, 8]);
3179
+ }
3180
+ }
3181
+ /// Adds or updates one annotation. The same id replaces rather than
3182
+ /// duplicating, so moving one box every frame leaks no entry per frame.
3183
+ annotate(a, text = "") {
3184
+ const bytes = 64;
3185
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]);
3186
+ const encoded = new TextEncoder().encode(text);
3187
+ const textPtr = encoded.length === 0 ? 0 : this.mod.ccall("goss_alloc", "number", ["number"], [encoded.length]);
3188
+ try {
3189
+ const u32 = (off, v) => { this.mod.HEAPU32[(ptr + off) >> 2] = v; };
3190
+ const f32 = (off, v) => { this.mod.HEAPF32[(ptr + off) >> 2] = v; };
3191
+ u32(0, a.id);
3192
+ u32(4, a.kind);
3193
+ u32(8, a.space ?? 0);
3194
+ for (let i = 0; i < 4; i += 1)
3195
+ f32(12 + i * 4, a.rect?.[i] ?? 0);
3196
+ u32(28, a.trackId ?? 0);
3197
+ this.mod.HEAPU8[ptr + 32] = a.colour?.[0] ?? 255;
3198
+ this.mod.HEAPU8[ptr + 33] = a.colour?.[1] ?? 255;
3199
+ this.mod.HEAPU8[ptr + 34] = a.colour?.[2] ?? 255;
3200
+ this.mod.HEAPU8[ptr + 35] = a.colour?.[3] ?? 255;
3201
+ this.mod.HEAP32[(ptr + 36) >> 2] = a.z ?? 0;
3202
+ f32(40, a.opacity ?? 1);
3203
+ u32(44, a.lifetimeKind ?? 0);
3204
+ u32(48, a.lifetimeValue ?? 0);
3205
+ u32(52, 0);
3206
+ u32(56, a.onLost ?? 0);
3207
+ f32(60, a.value ?? 0);
3208
+ if (textPtr !== 0)
3209
+ this.mod.HEAPU8.set(encoded, textPtr);
3210
+ return this.mod.ccall("goss_session_annotate", "number", ["number", "number", "number", "number"], [this.handle, ptr, textPtr, encoded.length]) === GOSS_OK;
3211
+ }
3212
+ finally {
3213
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
3214
+ if (textPtr !== 0)
3215
+ this.mod.ccall("goss_free", null, ["number", "number"], [textPtr, encoded.length]);
3216
+ }
3217
+ }
3218
+ annotationRemove(id) {
3219
+ return this.mod.ccall("goss_session_annotation_remove", "number", ["number", "number"], [this.handle, id]) === GOSS_OK;
3220
+ }
3221
+ annotationClear() {
3222
+ return this.mod.ccall("goss_session_annotation_clear", "number", ["number"], [this.handle]) === GOSS_OK;
3223
+ }
3224
+ /// Live count and how many adds the bound turned away.
3225
+ annotationCount() {
3226
+ const countPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
3227
+ const refusedPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
3228
+ try {
3229
+ if (this.mod.ccall("goss_session_annotation_count", "number", ["number", "number", "number"], [this.handle, countPtr, refusedPtr]) !== GOSS_OK) {
3230
+ return { live: 0, refused: 0 };
3231
+ }
3232
+ return { live: this.mod.HEAPU32[countPtr >> 2], refused: this.mod.HEAPU32[refusedPtr >> 2] };
3233
+ }
3234
+ finally {
3235
+ this.mod.ccall("goss_free", null, ["number", "number"], [countPtr, 4]);
3236
+ this.mod.ccall("goss_free", null, ["number", "number"], [refusedPtr, 8]);
3237
+ }
3238
+ }
3239
+ /// Installs the egress policy: what the brain sees and what it costs.
3240
+ egressConfigure(config) {
3241
+ const bytes = 48;
3242
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]);
3243
+ try {
3244
+ const u32 = (off, v) => { this.mod.HEAPU32[(ptr + off) >> 2] = v; };
3245
+ u32(0, config.targetLongEdge ?? 0);
3246
+ u32(4, config.format ?? 0);
3247
+ u32(8, config.quality ?? 80);
3248
+ u32(12, config.maxFps ?? 0);
3249
+ u32(16, config.maxBytesPerSecond ?? 0);
3250
+ u32(20, 0);
3251
+ u32(24, config.source ?? 0);
3252
+ u32(28, config.trigger ?? 0b1010);
3253
+ this.mod.HEAPF32[(ptr + 32) >> 2] = config.changeThreshold ?? 0.02;
3254
+ u32(40, config.keyframeIntervalUs ?? 5_000_000);
3255
+ u32(44, 0);
3256
+ return this.mod.ccall("goss_session_egress_configure", "number", ["number", "number"], [this.handle, ptr]) === GOSS_OK;
3257
+ }
3258
+ finally {
3259
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
3260
+ }
3261
+ }
3262
+ /// One frame, whatever the change score says.
3263
+ egressRequest() {
3264
+ return this.mod.ccall("goss_session_egress_request", "number", ["number"], [this.handle]) === GOSS_OK;
3265
+ }
3266
+ /// Whether this frame is worth sending and why. Null when there are no pixels
3267
+ /// to score, which is not a failure.
3268
+ egressDecide() {
3269
+ const bytes = 40;
3270
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]);
3271
+ try {
3272
+ if (this.mod.ccall("goss_session_egress_decide", "number", ["number", "number"], [this.handle, ptr]) !== GOSS_OK)
3273
+ return null;
3274
+ const w = (off) => this.mod.HEAPU32[(ptr + off) >> 2];
3275
+ return {
3276
+ send: w(0) !== 0,
3277
+ reason: w(4),
3278
+ changeScore: this.mod.HEAPF32[(ptr + 8) >> 2],
3279
+ sinceLastUs: w(16),
3280
+ sentTotal: w(24),
3281
+ heldTotal: w(32),
3282
+ };
3283
+ }
3284
+ finally {
3285
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
3286
+ }
3287
+ }
3288
+ /// The session's events as an async iterable, which is how a page wants them:
3289
+ /// `for await (const e of session.eventStream())`. Polls on an interval because
3290
+ /// the ring is drained by the caller and there is nothing to await on the
3291
+ /// engine side. A drop is yielded as an event of its own rather than swallowed.
3292
+ async *eventStream(pollMs = 16, batch = 64) {
3293
+ for (;;) {
3294
+ const drained = this.pollEvents(batch);
3295
+ for (const event of drained.events)
3296
+ yield event;
3297
+ if (drained.dropped > 0) {
3298
+ yield { kind: 0, sequence: 0, timestampUs: 0, a: drained.dropped, b: 0, value: 0 };
3299
+ }
3300
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
3301
+ }
3302
+ }
3303
+ /// Drains the session's event ring in order. `dropped` says whether anything
3304
+ /// was missed since the last drain, and is cleared by the read.
3305
+ pollEvents(capacity = 64) {
3306
+ // kind u32 at 0, sequence u64 at 8, timestamp i64 at 16, a u32 at 24,
3307
+ // b u32 at 28, value f32 at 32; forty bytes with the tail padding.
3308
+ const stride = 40;
3309
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [capacity * stride]);
3310
+ const countPtr = this.mod.ccall("goss_alloc", "number", ["number"], [4]);
3311
+ const droppedPtr = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
3312
+ try {
3313
+ if (this.mod.ccall("goss_session_poll_events", "number", ["number", "number", "number", "number", "number"], [this.handle, ptr, capacity, countPtr, droppedPtr]) !== GOSS_OK) {
3314
+ return { events: [], dropped: 0 };
3315
+ }
3316
+ const n = this.mod.HEAPU32[countPtr >> 2];
3317
+ const events = [];
3318
+ for (let i = 0; i < n; i += 1) {
3319
+ const base = ptr + i * stride;
3320
+ events.push({
3321
+ kind: this.mod.HEAPU32[base >> 2],
3322
+ // The low word of the sequence is the whole count at any rate a session
3323
+ // reaches, and reading it avoids a BigInt in the hot drain.
3324
+ sequence: this.mod.HEAPU32[(base + 8) >> 2],
3325
+ timestampUs: this.mod.HEAPU32[(base + 16) >> 2],
3326
+ a: this.mod.HEAPU32[(base + 24) >> 2],
3327
+ b: this.mod.HEAPU32[(base + 28) >> 2],
3328
+ value: this.mod.HEAPF32[(base + 32) >> 2],
3329
+ });
3330
+ }
3331
+ return { events, dropped: this.mod.HEAPU32[droppedPtr >> 2] };
3332
+ }
3333
+ finally {
3334
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, capacity * stride]);
3335
+ this.mod.ccall("goss_free", null, ["number", "number"], [countPtr, 4]);
3336
+ this.mod.ccall("goss_free", null, ["number", "number"], [droppedPtr, 8]);
3337
+ }
3338
+ }
3339
+ /// A break the page saw and the engine cannot: the camera track ended, the
3340
+ /// tab was hidden. Declared so the gap it leaves is the break rather than
3341
+ /// drift counted against the engine.
3342
+ reportInterruption(kind) {
3343
+ return this.mod.ccall("goss_session_report_interruption", "number", ["number", "number"], [this.handle, kind]) === GOSS_OK;
3344
+ }
3345
+ /// This session's counters: frames in and out, the rung and how often it
3346
+ /// moved, the analysis each modality ran, the nodes that are not ready.
3347
+ sessionReport() {
3348
+ const bytes = SESSION_REPORT_BYTES;
3349
+ const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]);
3350
+ try {
3351
+ if (this.mod.ccall("goss_session_read_report", "number", ["number", "number"], [this.handle, ptr]) !== 0)
3352
+ return null;
3353
+ // The low word of each u64 is the whole count at any rate this engine
3354
+ // reaches, and reading it avoids a BigInt in the hot read.
3355
+ const low = (offset) => this.mod.HEAPU32[(ptr + offset) >> 2];
3356
+ return {
3357
+ framesSubmitted: low(0),
3358
+ framesRendered: low(8),
3359
+ degradeLevel: low(16),
3360
+ degradeTransitions: low(20),
3361
+ faceAnalysis: low(24),
3362
+ handAnalysis: low(32),
3363
+ poseAnalysis: low(40),
3364
+ segmentationAnalysis: low(48),
3365
+ mlAnalysis: low(56),
3366
+ nodesDegraded: low(64),
3367
+ nodeReportsLost: low(68),
3368
+ scriptFaults: low(72),
3369
+ mlPlanGrowths: low(76),
3370
+ mlPlanBytes: low(80),
3371
+ };
3372
+ }
3373
+ finally {
3374
+ this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
3375
+ }
3376
+ }
3377
+ /// Every node of the active lens that is not doing what its manifest asked,
3378
+ /// beside how many diagnostics could not be recorded at all. An empty list
3379
+ /// with a non-zero lost count means the lens degraded in ways the session
3380
+ /// could not write down, which is not the same as a lens that is fine.
3381
+ nodeReports() {
3382
+ const scratch = this.mod.ccall("goss_alloc", "number", ["number"], [8]);
3383
+ let count = 0;
3384
+ let lost = 0;
3385
+ try {
3386
+ const status = this.mod.ccall("goss_session_node_report_count", "number", ["number", "number", "number"], [this.handle, scratch, scratch + 4]);
3387
+ if (status !== 0)
3388
+ return { reports: [], lost: 0 };
3389
+ count = this.mod.HEAPU32[scratch >> 2];
3390
+ lost = this.mod.HEAPU32[(scratch + 4) >> 2];
3391
+ }
3392
+ finally {
3393
+ this.mod.ccall("goss_free", null, ["number", "number"], [scratch, 8]);
3394
+ }
3395
+ const reports = [];
3396
+ // One report is three u32; the id buffer is sized for a manifest node id.
3397
+ const record = this.mod.ccall("goss_alloc", "number", ["number"], [12]);
3398
+ const idBytes = 256;
3399
+ const idPtr = this.mod.ccall("goss_alloc", "number", ["number"], [idBytes + 4]);
3400
+ try {
3401
+ for (let at = 0; at < count; at += 1) {
3402
+ if (this.mod.ccall("goss_session_node_report_at", "number", ["number", "number", "number"], [this.handle, at, record]) !== 0)
3403
+ break;
3404
+ const nodeIndex = this.mod.HEAPU32[record >> 2];
3405
+ const state = this.mod.HEAPU32[(record + 4) >> 2];
3406
+ const reason = this.mod.HEAPU32[(record + 8) >> 2];
3407
+ let id = "";
3408
+ const lenPtr = idPtr + idBytes;
3409
+ if (this.mod.ccall("goss_session_node_report_id", "number", ["number", "number", "number", "number", "number"], [this.handle, at, idPtr, idBytes, lenPtr]) === 0) {
3410
+ const len = this.mod.HEAPU32[lenPtr >> 2];
3411
+ if (len > 0 && len <= idBytes) {
3412
+ id = new TextDecoder().decode(this.mod.HEAPU8.subarray(idPtr, idPtr + len));
3413
+ }
3414
+ }
3415
+ reports.push({ id, nodeIndex, state: state, reason: reason });
3416
+ }
3417
+ }
3418
+ finally {
3419
+ this.mod.ccall("goss_free", null, ["number", "number"], [record, 12]);
3420
+ this.mod.ccall("goss_free", null, ["number", "number"], [idPtr, idBytes + 4]);
3421
+ }
3422
+ return { reports, lost };
3423
+ }
2369
3424
  /// Submits a bare camera pose and projection, for a host driving a scan with
2370
3425
  /// no platform world session behind it: a selfie scan on the front camera,
2371
3426
  /// where the depth comes from a lens's own net rather than a sensor. Both
@@ -2437,7 +3492,10 @@ export class GossSession {
2437
3492
  /// Reports one finished frame: measured whole-pipeline time plus
2438
3493
  /// thermal pressure (nominal by default - no browser API surfaces
2439
3494
  /// device thermal state). Returns the degradation level in effect
2440
- /// for the next frame.
3495
+ /// for the next frame. A page that never calls this still degrades:
3496
+ /// the engine derives the frame period from the camera timestamps it
3497
+ /// is handed. Calling it lets the page account for its own work
3498
+ /// outside the engine as well.
2441
3499
  reportFrame(frameTimeUs, thermal = 0 /* GossThermal.Nominal */) {
2442
3500
  return this.mod.ccall("goss_session_report_frame", "number", ["number", "number", "number"], [this.handle, frameTimeUs, thermal]);
2443
3501
  }
@@ -2542,11 +3600,18 @@ export class GossPreviewSession {
2542
3600
  setBlush(amount) {
2543
3601
  this.session.setBlush(amount);
2544
3602
  }
3603
+ /// Awaited, because activation suspends on the WebGPU build. A caller that
3604
+ /// fires and forgets leaves the module unwound and the next render reenters
3605
+ /// it.
2545
3606
  activateLens(manifestJson) {
2546
- this.session.activateLens(manifestJson);
3607
+ return this.session.activateLens(manifestJson);
2547
3608
  }
2548
3609
  deactivateLens() {
2549
- this.session.deactivateLens();
3610
+ return this.session.deactivateLens();
3611
+ }
3612
+ /// True while a lens change is unwound; a frame loop skips its render.
3613
+ get isLensChangeInFlight() {
3614
+ return this.session.isLensChangeInFlight;
2550
3615
  }
2551
3616
  tickLens(dtUs, signals = {}) {
2552
3617
  this.session.tickLens(dtUs, signals);
@@ -2668,4 +3733,5 @@ export { GossVideoTexture } from "./video-texture.js";
2668
3733
  // The trackers the tracking module runs, so a page reaches them through the one package entry
2669
3734
  // the way the Swift and Kotlin SDKs carry theirs.
2670
3735
  export { GossFaceTracker, GossHandTracker, GossPoseTracker, GossSegmenter, GOSS_FACE_BLENDSHAPE_COUNT, GOSS_MAX_HANDS } from "./tracking.js";
3736
+ export { shareScreen } from "./screen-capture.js";
2671
3737
  //# sourceMappingURL=index.js.map