@vibecook/ice 0.14.0 → 0.15.0-desk.2

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 (62) hide show
  1. package/dist/{chunk-ZPUPBULB.js → chunk-52GMQ2BN.js} +1320 -450
  2. package/dist/chunk-52GMQ2BN.js.map +1 -0
  3. package/dist/{chunk-BA7XJC27.js → chunk-K4HUZAYJ.js} +49 -34
  4. package/dist/chunk-K4HUZAYJ.js.map +1 -0
  5. package/dist/{chunk-DYK6KNLG.js → chunk-PFJN3XN2.js} +39 -14
  6. package/dist/chunk-PFJN3XN2.js.map +1 -0
  7. package/dist/{chunk-IBWWLA6D.js → chunk-U4YMA4TJ.js} +6 -5
  8. package/dist/chunk-U4YMA4TJ.js.map +1 -0
  9. package/dist/desk-kit.js +2 -2
  10. package/dist/desk-objects.js +15 -9
  11. package/dist/desk-objects.js.map +1 -1
  12. package/dist/desk.js +31 -3
  13. package/dist/devtools.js +1 -1
  14. package/dist/dom.js +2 -2
  15. package/dist/index.js +1 -1
  16. package/dist/react.js +9 -7
  17. package/dist/react.js.map +1 -1
  18. package/dist/types/packages/core/src/index.d.ts +1 -1
  19. package/dist/types/packages/core/src/interaction/install.d.ts +7 -1
  20. package/dist/types/packages/core/src/systems/held.d.ts +3 -1
  21. package/dist/types/packages/core/src/systems/l1-pick.d.ts +16 -0
  22. package/dist/types/packages/core/src/systems/l2-recognize.d.ts +3 -1
  23. package/dist/types/packages/core/src/systems/press-button.d.ts +25 -0
  24. package/dist/types/packages/core/src/systems/tray.d.ts +3 -0
  25. package/dist/types/packages/desk/src/compose/builder.d.ts +22 -1
  26. package/dist/types/packages/desk/src/compose/marks.d.ts +7 -0
  27. package/dist/types/packages/desk/src/compose/pick.d.ts +1 -1
  28. package/dist/types/packages/desk/src/compose/reflector.d.ts +9 -0
  29. package/dist/types/packages/desk/src/engine/budget.d.ts +5 -0
  30. package/dist/types/packages/desk/src/faults.d.ts +38 -0
  31. package/dist/types/packages/desk/src/ground.d.ts +158 -9
  32. package/dist/types/packages/desk/src/hold/focus.d.ts +1 -1
  33. package/dist/types/packages/desk/src/hold/pose.d.ts +27 -7
  34. package/dist/types/packages/desk/src/host/index.d.ts +1 -1
  35. package/dist/types/packages/desk/src/host/layer.d.ts +110 -4
  36. package/dist/types/packages/desk/src/index.d.ts +5 -2
  37. package/dist/types/packages/desk/src/kind.d.ts +7 -5
  38. package/dist/types/packages/desk/src/kinds/world.d.ts +20 -2
  39. package/dist/types/packages/desk/src/kit/editor.d.ts +3 -1
  40. package/dist/types/packages/desk/src/kit/hold.d.ts +1 -1
  41. package/dist/types/packages/desk/src/missing/layout.d.ts +27 -0
  42. package/dist/types/packages/desk/src/missing/object.d.ts +6 -0
  43. package/dist/types/packages/desk/src/missing/pass.d.ts +39 -0
  44. package/dist/types/packages/desk/src/missing/shaders.d.ts +7 -0
  45. package/dist/types/packages/desk/src/missing/slots.d.ts +8 -0
  46. package/dist/types/packages/desk/src/shaders.gen.d.ts +5 -4
  47. package/dist/types/packages/desk/src/theme.d.ts +22 -0
  48. package/dist/types/packages/desk/src/tray/drawer.d.ts +16 -5
  49. package/dist/types/packages/desk/src/tray/flux.d.ts +4 -1
  50. package/dist/types/packages/desk/src/tray/index.d.ts +1 -1
  51. package/dist/types/packages/desk/src/tray/pass.d.ts +7 -2
  52. package/dist/types/packages/desk/src/tray/specimens.d.ts +21 -6
  53. package/dist/types/packages/dom/src/desk-host.d.ts +15 -2
  54. package/dist/types/packages/dom/src/pointer-adapter.d.ts +6 -0
  55. package/dist/types/packages/objects/src/board/layout.d.ts +1 -1
  56. package/dist/types/packages/objects/src/shaders.gen.d.ts +1 -1
  57. package/dist/types/packages/react/src/selection-menu.d.ts +5 -2
  58. package/package.json +1 -1
  59. package/dist/chunk-BA7XJC27.js.map +0 -1
  60. package/dist/chunk-DYK6KNLG.js.map +0 -1
  61. package/dist/chunk-IBWWLA6D.js.map +0 -1
  62. package/dist/chunk-ZPUPBULB.js.map +0 -1
@@ -19,6 +19,7 @@ import {
19
19
  LAYER_IDLE_MS,
20
20
  MARKS,
21
21
  MAT_GRID,
22
+ MISSING,
22
23
  MatUniforms,
23
24
  NOISE_SIZE,
24
25
  NO_GLYPHS,
@@ -75,7 +76,7 @@ import {
75
76
  solveFlightStart,
76
77
  spring,
77
78
  tilted
78
- } from "./chunk-BA7XJC27.js";
79
+ } from "./chunk-K4HUZAYJ.js";
79
80
  import {
80
81
  BLEND_OVER,
81
82
  bindGroup,
@@ -136,6 +137,7 @@ import {
136
137
  defineQuery,
137
138
  defineWidget,
138
139
  departedCameraOf,
140
+ frameParent,
139
141
  gateVerdict,
140
142
  heldEntity,
141
143
  menuActionsFor,
@@ -153,13 +155,435 @@ import {
153
155
  trayTakeProps,
154
156
  widgetSpawnInits,
155
157
  widgetTypeFor
156
- } from "./chunk-DYK6KNLG.js";
158
+ } from "./chunk-PFJN3XN2.js";
157
159
  import {
158
160
  cubicBezierEase,
159
161
  specimenFit,
160
162
  trayScrollMax
161
163
  } from "./chunk-JS6PG6YD.js";
162
164
 
165
+ // ../desk/src/faults.ts
166
+ var KIND_STRIKES = 3;
167
+ var KIND_MISSING = -1;
168
+ function faultText(err) {
169
+ const lines = (err instanceof Error ? err.message : typeof err === "object" && err !== null && "message" in err ? String(err.message) : String(err)).split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
170
+ return (lines.length > 1 && lines[0]?.startsWith("WGSL ") ? `${lines[0]} ${lines[1]}` : lines[0]) ?? "refused";
171
+ }
172
+ function createKindFaults() {
173
+ const missing = /* @__PURE__ */ new Set();
174
+ const strikes = /* @__PURE__ */ new Map();
175
+ const listeners = /* @__PURE__ */ new Set();
176
+ let list = [];
177
+ const go = (kind, reason) => {
178
+ missing.add(kind);
179
+ faults.size = missing.size;
180
+ const fault = { kind, reason };
181
+ list = [...list, fault];
182
+ console.error(`[ice] desk: the kind "${kind}" is MISSING \u2014 ${reason}. Its objects are drawn as missing and nothing of it is called again (petition I24).`);
183
+ for (const l of [...listeners]) l(fault);
184
+ };
185
+ const faults = {
186
+ size: 0,
187
+ missing: (kind) => missing.size > 0 && missing.has(kind),
188
+ refuse(kind, reason) {
189
+ if (missing.has(kind)) return;
190
+ go(kind, reason);
191
+ },
192
+ strike(kind, call, err, entity) {
193
+ if (missing.has(kind)) return false;
194
+ const n = (strikes.get(kind) ?? 0) + 1;
195
+ strikes.set(kind, n);
196
+ const on = entity === void 0 ? "" : ` on entity ${entity}`;
197
+ if (n < KIND_STRIKES) {
198
+ console.warn(`[ice] desk: the kind "${kind}" threw in its \`${call}\`${on} \u2014 strike ${n} of ${KIND_STRIKES}; the frame goes on without it`, err);
199
+ return false;
200
+ }
201
+ go(kind, `its \`${call}\` threw${on} (strike ${n} of ${KIND_STRIKES}): ${faultText(err)}`);
202
+ return true;
203
+ },
204
+ strikes: (kind) => strikes.get(kind) ?? 0,
205
+ list: () => list,
206
+ subscribe(listener) {
207
+ listeners.add(listener);
208
+ return () => {
209
+ listeners.delete(listener);
210
+ };
211
+ }
212
+ };
213
+ return faults;
214
+ }
215
+
216
+ // ../desk/src/missing/layout.ts
217
+ var MissingFace = defineStruct("MissingFace", [
218
+ ["rect", "vec4f"],
219
+ // centre x, y; half extents x, y — world units
220
+ ["ink", "vec4f"],
221
+ // the ink (rgb), the object's fade (w)
222
+ ["mix", "vec4f"],
223
+ // the wash's, the hatching's and the edge's alpha
224
+ ["shape", "vec4f"]
225
+ // the corner (world units); the hatching's pitch and weight, the edge's weight (CSS px)
226
+ ]);
227
+ var isMissingRecord = (r) => typeof r === "object" && r !== null && r.missing === true;
228
+ var INKS = { light: rgb(MISSING.ink.light), dark: rgb(MISSING.ink.dark) };
229
+ function missingFace(rect, theme, fade = 1) {
230
+ return { missing: true, cx: rect.cx, cy: rect.cy, hx: Math.abs(rect.w) / 2, hy: Math.abs(rect.h) / 2, ink: INKS[theme.name] ?? INKS.light, fade };
231
+ }
232
+ var NONE = { rect: [0, 0, 0, 0], ink: [0, 0, 0, 0], mix: [0, 0, 0, 0], shape: [0, 0, 0, 0] };
233
+ function missingValues(r) {
234
+ if (!isMissingRecord(r)) return NONE;
235
+ return {
236
+ rect: [r.cx, r.cy, r.hx, r.hy],
237
+ ink: [r.ink[0], r.ink[1], r.ink[2], r.fade],
238
+ mix: [MISSING.wash, MISSING.hatch, MISSING.edge, 0],
239
+ shape: [MISSING.corner, MISSING.pitch, MISSING.weight, MISSING.edgeWeight]
240
+ };
241
+ }
242
+ function missingHit(g, wx, wy) {
243
+ return Math.abs(wx - g.cx) <= Math.abs(g.w) / 2 && Math.abs(wy - g.cy) <= Math.abs(g.h) / 2 ? "content" : null;
244
+ }
245
+
246
+ // ../desk/src/engine/records.ts
247
+ var DEFAULT_MAX = 65536;
248
+ var GAP = 4;
249
+ var NONE2 = { aux: 0, blockN: 0 };
250
+ function createRecordStore(opts) {
251
+ const { device, def, label, pack } = opts;
252
+ const max = opts.max ?? DEFAULT_MAX;
253
+ const blocksDef = opts.blocks;
254
+ const per = blocksDef?.per ?? 0;
255
+ let capacity = Math.max(1, Math.min(opts.capacity, max));
256
+ let cpu = def.alloc(capacity);
257
+ let gpu = storageBuffer(device, def.size * capacity, label);
258
+ let blockCpu = blocksDef ? blocksDef.def.alloc(capacity * per) : null;
259
+ let blockGpu = blocksDef ? storageBuffer(device, blocksDef.def.size * capacity * per, blocksDef.label) : null;
260
+ let orderCpu = new Uint32Array(capacity);
261
+ let orderGpu = storageBuffer(device, 4 * capacity, `${label} order`);
262
+ let version = 0;
263
+ let grown = 0;
264
+ const byKey = /* @__PURE__ */ new Map();
265
+ let bySlot = new Array(capacity).fill(null);
266
+ const free = [];
267
+ let next = 0;
268
+ let gen = 0;
269
+ let lastKeys = null;
270
+ let prevOrder = [];
271
+ let count = 0;
272
+ const dirty = [];
273
+ let written = 0;
274
+ let bytes = 0;
275
+ let orderWrites = 0;
276
+ const grow = () => {
277
+ if (capacity >= max) throw new Error(`${label}: ${capacity} record slots is the most this store grows to`);
278
+ const was = capacity;
279
+ capacity = Math.min(capacity * 2, max);
280
+ const nextCpu = def.alloc(capacity);
281
+ new Uint8Array(nextCpu.bytes).set(new Uint8Array(cpu.bytes));
282
+ cpu = nextCpu;
283
+ gpu.destroy();
284
+ gpu = storageBuffer(device, def.size * capacity, label);
285
+ if (blocksDef && blockCpu && blockGpu) {
286
+ const nextBlocks = blocksDef.def.alloc(capacity * per);
287
+ new Uint8Array(nextBlocks.bytes).set(new Uint8Array(blockCpu.bytes));
288
+ blockCpu = nextBlocks;
289
+ blockGpu.destroy();
290
+ blockGpu = storageBuffer(device, blocksDef.def.size * capacity * per, blocksDef.label);
291
+ }
292
+ const nextOrder = new Uint32Array(capacity);
293
+ nextOrder.set(orderCpu);
294
+ orderCpu = nextOrder;
295
+ orderGpu.destroy();
296
+ orderGpu = storageBuffer(device, 4 * capacity, `${label} order`);
297
+ const nextBySlot = new Array(capacity).fill(null);
298
+ for (let i = 0; i < was; i++) nextBySlot[i] = bySlot[i] ?? null;
299
+ bySlot = nextBySlot;
300
+ version += 1;
301
+ grown += 1;
302
+ };
303
+ let grew = false;
304
+ const alloc = () => {
305
+ const reused = free.pop();
306
+ if (reused !== void 0) return reused;
307
+ if (next === capacity) {
308
+ grow();
309
+ grew = true;
310
+ }
311
+ return next++;
312
+ };
313
+ const release = (ent) => {
314
+ byKey.delete(ent.key);
315
+ bySlot[ent.slot] = null;
316
+ free.push(ent.slot);
317
+ };
318
+ const resetAll = () => {
319
+ bySlot.fill(null);
320
+ byKey.clear();
321
+ free.length = 0;
322
+ next = 0;
323
+ };
324
+ const packInto = (ent, record, aux) => {
325
+ ent.record = record;
326
+ ent.aux = aux;
327
+ ent.blockN = pack(record, aux, cpu, ent.slot, blockCpu, ent.slot * per);
328
+ dirty.push(ent.slot);
329
+ };
330
+ const runs = () => {
331
+ dirty.sort((a, b) => a - b);
332
+ const out = [];
333
+ for (const s of dirty) {
334
+ const last = out[out.length - 1];
335
+ if (last !== void 0 && s <= last[1] + 1 + GAP) {
336
+ if (s > last[1]) last[1] = s;
337
+ } else out.push([s, s]);
338
+ }
339
+ return out;
340
+ };
341
+ const upload = (n, orderMoved) => {
342
+ let rewriteOrder = orderMoved;
343
+ if (grew) {
344
+ grew = false;
345
+ if (next > 0) {
346
+ device.queue.writeBuffer(gpu, 0, cpu.bytes, 0, next * def.size);
347
+ bytes += next * def.size;
348
+ }
349
+ if (blocksDef && blockCpu && blockGpu && next > 0) {
350
+ device.queue.writeBuffer(blockGpu, 0, blockCpu.bytes, 0, next * per * blocksDef.def.size);
351
+ bytes += next * per * blocksDef.def.size;
352
+ }
353
+ written += dirty.length;
354
+ dirty.length = 0;
355
+ rewriteOrder = true;
356
+ } else if (dirty.length > 0) {
357
+ for (const [a, b] of runs()) {
358
+ const n0 = b - a + 1;
359
+ device.queue.writeBuffer(gpu, a * def.size, cpu.bytes, a * def.size, n0 * def.size);
360
+ bytes += n0 * def.size;
361
+ }
362
+ if (blocksDef && blockCpu && blockGpu) {
363
+ const bsize = blocksDef.def.size;
364
+ for (const s of dirty) {
365
+ const ent = bySlot[s];
366
+ if (!ent || ent.blockN === 0) continue;
367
+ device.queue.writeBuffer(blockGpu, s * per * bsize, blockCpu.bytes, s * per * bsize, ent.blockN * bsize);
368
+ bytes += ent.blockN * bsize;
369
+ }
370
+ }
371
+ written += dirty.length;
372
+ dirty.length = 0;
373
+ }
374
+ if (rewriteOrder) {
375
+ if (n > 0) device.queue.writeBuffer(orderGpu, 0, orderCpu, 0, n);
376
+ orderWrites += 1;
377
+ prevOrder = Array.from(orderCpu.subarray(0, n));
378
+ }
379
+ };
380
+ return {
381
+ get records() {
382
+ return gpu;
383
+ },
384
+ get order() {
385
+ return orderGpu;
386
+ },
387
+ get blocks() {
388
+ return blockGpu;
389
+ },
390
+ get version() {
391
+ return version;
392
+ },
393
+ prepare(records, keys, aux) {
394
+ gen += 1;
395
+ const n = records.length;
396
+ let orderChanged = n !== prevOrder.length;
397
+ if (keys === void 0) {
398
+ resetAll();
399
+ lastKeys = null;
400
+ for (let i = 0; i < n; i++) {
401
+ const slot = alloc();
402
+ const ent = { key: -0.5 - i, slot, record: void 0, aux: 0, gen, blockN: 0 };
403
+ bySlot[slot] = ent;
404
+ packInto(ent, records[i], aux ? aux(i) : 0);
405
+ if (!orderChanged && prevOrder[i] !== slot) orderChanged = true;
406
+ orderCpu[i] = slot;
407
+ }
408
+ } else {
409
+ if (keys.length !== n) throw new Error(`${label}: ${keys.length} keys for ${n} records`);
410
+ if (lastKeys === null) resetAll();
411
+ const same = lastKeys !== null && lastKeys.length === n && lastKeys.every((k, i) => k === keys[i]);
412
+ for (let i = 0; i < n; i++) {
413
+ const key = keys[i];
414
+ let ent = byKey.get(key);
415
+ if (ent === void 0) {
416
+ const slot = alloc();
417
+ ent = { key, slot, record: void 0, aux: 0, gen, blockN: 0 };
418
+ byKey.set(key, ent);
419
+ bySlot[slot] = ent;
420
+ }
421
+ ent.gen = gen;
422
+ const a = aux ? aux(i) : 0;
423
+ if (ent.record !== records[i] || ent.aux !== a) packInto(ent, records[i], a);
424
+ if (!orderChanged && prevOrder[i] !== ent.slot) orderChanged = true;
425
+ orderCpu[i] = ent.slot;
426
+ }
427
+ if (!same) {
428
+ if (lastKeys !== null) for (const k of lastKeys) {
429
+ const ent = byKey.get(k);
430
+ if (ent !== void 0 && ent.gen !== gen) release(ent);
431
+ }
432
+ lastKeys = keys.slice();
433
+ }
434
+ }
435
+ upload(n, orderChanged);
436
+ count = n;
437
+ return n;
438
+ },
439
+ slotAt: (i) => i < count ? orderCpu[i] : -1,
440
+ entryAt(i) {
441
+ if (i >= count) return NONE2;
442
+ const ent = bySlot[orderCpu[i]];
443
+ return ent === null || ent === void 0 ? NONE2 : { aux: ent.aux, blockN: ent.blockN };
444
+ },
445
+ invalidate() {
446
+ for (const ent of byKey.values()) ent.record = void 0;
447
+ },
448
+ stats: () => ({ written, bytes, orderWrites, slots: next - free.length, capacity, grown }),
449
+ dispose() {
450
+ gpu.destroy();
451
+ orderGpu.destroy();
452
+ blockGpu?.destroy();
453
+ byKey.clear();
454
+ }
455
+ };
456
+ }
457
+
458
+ // ../desk/src/missing/shaders.ts
459
+ var MISSING_SHADER_FILES = { entry: "missing/missing-pass.wgsl" };
460
+ function missingShaders(text2 = shaderText) {
461
+ return kitWgsl(["view", "portal", "sdf"], {
462
+ structs: [MissingFace],
463
+ entry: { label: MISSING_SHADER_FILES.entry, text: text2(MISSING_SHADER_FILES).entry }
464
+ }, text2);
465
+ }
466
+
467
+ // ../desk/src/missing/pass.ts
468
+ var BLEND_PREMUL = {
469
+ color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
470
+ alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
471
+ };
472
+ var MAX_FACES = 65536;
473
+ var NO_RECORDS = { written: 0, bytes: 0, orderWrites: 0, slots: 0, capacity: 0, grown: 0 };
474
+ var MissingPass = class _MissingPass {
475
+ name = "desk/missing";
476
+ store = null;
477
+ group = null;
478
+ boundStore = -1;
479
+ boundAssets = -1;
480
+ count = 0;
481
+ device;
482
+ shared;
483
+ mat;
484
+ constructor(device, shared, mat) {
485
+ this.device = device;
486
+ this.shared = shared;
487
+ this.mat = mat;
488
+ }
489
+ /**
490
+ * The root's pass on `mat`, made NOW (no await — a quarantine asks for it mid-frame): its layout and its one pipeline (it is lit by
491
+ * nothing — the same in every slot). The desk's own WGSL, composed from the host's text; WebGPU compiles it before its first draw.
492
+ */
493
+ static make(device, format, mat, text2) {
494
+ const layout = bindLayout(device, [
495
+ { binding: 0, stages: ["vertex", "fragment"], buffer: "uniform" },
496
+ { binding: 1, stages: ["vertex", "fragment"], buffer: "read-only-storage" },
497
+ { binding: 2, stages: ["vertex"], buffer: "read-only-storage" }
498
+ ], "desk/missing");
499
+ const shader = compose(missingShaders(text2));
500
+ const module = device.createShaderModule({ code: shader.code, label: shader.label });
501
+ const pipeline = device.createRenderPipeline({
502
+ label: "desk/missing",
503
+ layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
504
+ vertex: { module, entryPoint: "vs" },
505
+ fragment: { module, entryPoint: "fs", targets: [{ format, blend: BLEND_PREMUL }] },
506
+ primitive: { topology: "triangle-list" }
507
+ });
508
+ return new _MissingPass(device, { layout, pipeline }, mat);
509
+ }
510
+ /** A slot's own faces on the shared pipeline, through `mat`'s view block. */
511
+ spawn(mat) {
512
+ return new _MissingPass(this.device, this.shared, mat);
513
+ }
514
+ prepare(_encoder, _slot, records, extra) {
515
+ if (records.length === 0 && this.store === null) {
516
+ this.count = 0;
517
+ return 0;
518
+ }
519
+ this.store ??= createRecordStore({
520
+ device: this.device,
521
+ def: MissingFace,
522
+ capacity: 16,
523
+ max: MAX_FACES,
524
+ label: "desk/missing",
525
+ pack: (r, _aux, into, slot) => {
526
+ into.set(missingValues(r), slot);
527
+ return 0;
528
+ }
529
+ });
530
+ this.count = this.store.prepare(records.length > MAX_FACES ? records.slice(0, MAX_FACES) : records, extra?.keys !== void 0 && extra.keys.length > MAX_FACES ? extra.keys.slice(0, MAX_FACES) : extra?.keys);
531
+ this.rebind();
532
+ return this.count;
533
+ }
534
+ rebind() {
535
+ const store = this.store;
536
+ if (store === null || this.group !== null && this.boundStore === store.version && this.boundAssets === this.mat.assetVersion) return;
537
+ this.group = bindGroup(this.device, this.shared.layout, [this.mat.view, store.records, store.order], "desk/missing");
538
+ this.boundStore = store.version;
539
+ this.boundAssets = this.mat.assetVersion;
540
+ }
541
+ drawRange(pass, first, end) {
542
+ const hi = Math.min(end, this.count);
543
+ if (first >= hi || this.group === null) return;
544
+ pass.setPipeline(this.shared.pipeline);
545
+ pass.setBindGroup(0, this.group);
546
+ pass.draw(6, hi - first, 0, first);
547
+ }
548
+ /** The store's counters (a rig's witness) — nothing until the slot's first face. */
549
+ records() {
550
+ return this.store?.stats() ?? NO_RECORDS;
551
+ }
552
+ dispose() {
553
+ this.store?.dispose();
554
+ this.store = null;
555
+ this.group = null;
556
+ }
557
+ };
558
+ function missingFaces(device, format, mat, text2) {
559
+ let root = null;
560
+ return {
561
+ spawn: (m) => {
562
+ root ??= MissingPass.make(device, format, mat, text2);
563
+ return root.spawn(m);
564
+ },
565
+ get made() {
566
+ return root !== null;
567
+ },
568
+ dispose: () => {
569
+ root?.dispose();
570
+ root = null;
571
+ }
572
+ };
573
+ }
574
+
575
+ // ../desk/src/missing/object.ts
576
+ var MISSING_KIND = "desk.missing";
577
+ var MISSING_OBJECT = {
578
+ name: MISSING_KIND,
579
+ stratum: "things",
580
+ reach: 0,
581
+ create: async (device, format, mat) => MissingPass.make(device, format, mat),
582
+ resolve: (ctx) => ctx.rect,
583
+ record: (g, ctx) => missingFace(g, ctx.theme, ctx.flux.fade),
584
+ hit: (g, wx, wy) => missingHit(g, wx, wy)
585
+ };
586
+
163
587
  // ../desk/src/kind.ts
164
588
  var STRATA = ["pads", "sheets", "things"];
165
589
 
@@ -231,9 +655,9 @@ function band(stretch) {
231
655
  const a = Math.abs(stretch);
232
656
  return Math.sign(stretch) * B * (1 - 1 / (1 + a * DRAWER.band.c / B));
233
657
  }
234
- function scrollRange(vw, vh, bottom) {
658
+ function scrollRange(vw, vh, bottom, foot = 0) {
235
659
  const { h } = drawerSize(vw, vh);
236
- return trayScrollMax(bottom, h, DRAWER.pitch);
660
+ return trayScrollMax(bottom, h - foot, DRAWER.pitch);
237
661
  }
238
662
 
239
663
  // ../desk/src/tray/lattice.ts
@@ -346,7 +770,7 @@ var TrayUniforms = defineStruct("TrayUniforms", [
346
770
  ["accessory", "vec4f"],
347
771
  // K5a: the accessories' powder coat, linear, and their shadows' strength on the board
348
772
  ["fade", "vec4f"]
349
- // the content's fade under the header (design-018 §4, R4): its band F, the clear header H above it, CSS px; —, —
773
+ // the content's fade under the header (design-018 §4, R4): its band F, the clear header H above it, CSS px; a host's foot inset (I21; 0 none); —
350
774
  ]);
351
775
  var TrayAccessoryStruct = defineStruct("TrayAccessory", [
352
776
  ["box", "vec4f"],
@@ -373,7 +797,7 @@ function trayShaders(text2) {
373
797
  }
374
798
 
375
799
  // ../desk/src/card/card.ts
376
- var BLEND_PREMUL = {
800
+ var BLEND_PREMUL2 = {
377
801
  color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
378
802
  alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
379
803
  };
@@ -541,8 +965,8 @@ async function buildCard(device, format, kinds) {
541
965
  const module = await compile(device, compose(cardShaders(kinds)));
542
966
  const pl = device.createPipelineLayout({ bindGroupLayouts: [layout] });
543
967
  const [pipeline, litPipeline] = await Promise.all([
544
- renderPipeline(device, { label: "card/flat", layout: pl, module, format, blend: BLEND_PREMUL }),
545
- renderPipeline(device, { label: "card/flat, lit from elsewhere", layout: pl, module, format, blend: BLEND_PREMUL, constants: { LIT_ELSEWHERE: 1 } })
968
+ renderPipeline(device, { label: "card/flat", layout: pl, module, format, blend: BLEND_PREMUL2 }),
969
+ renderPipeline(device, { label: "card/flat, lit from elsewhere", layout: pl, module, format, blend: BLEND_PREMUL2, constants: { LIT_ELSEWHERE: 1 } })
546
970
  ]);
547
971
  return { ok: true, layout, pipeline, litPipeline };
548
972
  } catch (e) {
@@ -709,13 +1133,21 @@ var CardPass = class _CardPass {
709
1133
  }
710
1134
  };
711
1135
 
1136
+ // ../desk/src/missing/slots.ts
1137
+ function swapToMissing(s, name, faces) {
1138
+ const k = s.kinds.get(name);
1139
+ if (k === void 0 || faces === void 0) return void 0;
1140
+ s.kinds.set(name, { name, stratum: k.stratum, pass: faces.spawn(s.mat) });
1141
+ return k.pass;
1142
+ }
1143
+
712
1144
  // ../desk/src/hold/focus.ts
713
1145
  var HoldUniformsStruct = defineStruct("HoldUniforms", [
714
1146
  ["texel", "vec4f"],
715
1147
  ["mix", "vec4f"]
716
1148
  ]);
717
1149
  var KAWASE = { levels: 2, offsetPerTexel: 0.25 };
718
- var BLEND_PREMUL2 = {
1150
+ var BLEND_PREMUL3 = {
719
1151
  color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
720
1152
  alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
721
1153
  };
@@ -763,7 +1195,7 @@ var HoldPass = class _HoldPass {
763
1195
  renderPipeline(device, { label: "hold/down", layout: pl, module, fragment: "fs_down", format }),
764
1196
  renderPipeline(device, { label: "hold/up", layout: pl, module, fragment: "fs_up", format }),
765
1197
  renderPipeline(device, { label: "hold/desk", layout: pl, module, fragment: "fs_desk", format }),
766
- renderPipeline(device, { label: "hold/hand", layout: pl, module, fragment: "fs_hand", format, blend: BLEND_PREMUL2 })
1198
+ renderPipeline(device, { label: "hold/hand", layout: pl, module, fragment: "fs_hand", format, blend: BLEND_PREMUL3 })
767
1199
  ]);
768
1200
  return new _HoldPass(device, format, layout, pipes);
769
1201
  }
@@ -1472,7 +1904,7 @@ function selectionBox(frames) {
1472
1904
  }
1473
1905
 
1474
1906
  // ../desk/src/marks/pass.ts
1475
- var BLEND_PREMUL3 = {
1907
+ var BLEND_PREMUL4 = {
1476
1908
  color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
1477
1909
  alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
1478
1910
  };
@@ -1512,7 +1944,7 @@ var MarksPass = class _MarksPass {
1512
1944
  { binding: 3, stages: ["fragment"], sampler: "filtering" }
1513
1945
  ], "marks");
1514
1946
  const pl = device.createPipelineLayout({ label: "marks", bindGroupLayouts: [layout] });
1515
- const pipeline = await renderPipeline(device, { label: "marks", layout: pl, module, format, blend: BLEND_PREMUL3 });
1947
+ const pipeline = await renderPipeline(device, { label: "marks", layout: pl, module, format, blend: BLEND_PREMUL4 });
1516
1948
  return new _MarksPass(device, mat, pipeline, layout);
1517
1949
  }
1518
1950
  /**
@@ -1791,12 +2223,22 @@ function carryView(pose, rect) {
1791
2223
  }
1792
2224
 
1793
2225
  // ../desk/src/tray/specimens.ts
1794
- function faceClip(rect, vh) {
2226
+ function recordOf(kind, ctx, faults) {
2227
+ if (faults === void 0) return kind.record(kind.resolve(ctx), ctx);
2228
+ if (faults.missing(kind.name)) return missingFace(ctx.rect, ctx.theme, ctx.flux.fade);
2229
+ try {
2230
+ return kind.record(kind.resolve(ctx), ctx);
2231
+ } catch (err) {
2232
+ faults.strike(kind.name, "record", err, ctx.entity);
2233
+ return void 0;
2234
+ }
2235
+ }
2236
+ function faceClip(rect, vh, foot = 0) {
1795
2237
  const a = DRAWER.arris;
1796
2238
  const x0 = rect.x + a;
1797
2239
  const x1 = rect.x + rect.w - a;
1798
2240
  const y0 = rect.y + a + DRAWER.header;
1799
- const y1 = Math.max(vh, rect.y + rect.h) + DRAWER.radius;
2241
+ const y1 = foot > 0 ? rect.y + rect.h - foot : Math.max(vh, rect.y + rect.h) + DRAWER.radius;
1800
2242
  return { cx: (x0 + x1) / 2, cy: (y0 + y1) / 2, hx: (x1 - x0) / 2, hy: (y1 - y0) / 2, r: DRAWER.radius - a };
1801
2243
  }
1802
2244
  function trayGrid(grid) {
@@ -1813,7 +2255,7 @@ function specimenFrames(specimens, drawn, env) {
1813
2255
  const P = DRAWER.pitch;
1814
2256
  const grid = trayGrid(env.grid);
1815
2257
  const { width, height, dpr } = env.view;
1816
- const face2 = faceClip(drawn.rect, height);
2258
+ const face2 = faceClip(drawn.rect, height, drawn.foot);
1817
2259
  for (const s of specimens) {
1818
2260
  const x0 = drawn.rect.x + s.rect.x;
1819
2261
  const y0 = drawn.rect.y + s.rect.y - drawn.scroll;
@@ -1839,7 +2281,8 @@ function specimenFrames(specimens, drawn, env) {
1839
2281
  ...s.local !== void 0 ? { local: s.local } : {},
1840
2282
  ...s.asset !== void 0 ? { asset: s.asset } : {}
1841
2283
  };
1842
- const record = s.kind.record(s.kind.resolve(ctx), ctx);
2284
+ const record = recordOf(s.kind, ctx, env.faults);
2285
+ if (record === void 0) continue;
1843
2286
  const hx = x0 + s.rect.w / 2;
1844
2287
  const hy = s.accessory === "shelf" ? y0 + s.rect.h : y0;
1845
2288
  out.push({
@@ -1917,7 +2360,8 @@ function carriedFrame(pose, src, env) {
1917
2360
  dt: 0,
1918
2361
  ...src.local !== void 0 ? { local: src.local } : {}
1919
2362
  };
1920
- const record = src.kind.record(src.kind.resolve(ctx), ctx);
2363
+ const record = recordOf(src.kind, ctx, env.faults);
2364
+ if (record === void 0) return void 0;
1921
2365
  const x0 = (r.x - v.camX) * v.zoom;
1922
2366
  const y0 = (r.y - v.camY) * v.zoom;
1923
2367
  return {
@@ -1934,7 +2378,7 @@ function carriedFrame(pose, src, env) {
1934
2378
  };
1935
2379
  }
1936
2380
  function facePresent(drawn, vh) {
1937
- return { opacity: 1, portal: faceClip(drawn.rect, vh) };
2381
+ return { opacity: 1, portal: faceClip(drawn.rect, vh, drawn.foot) };
1938
2382
  }
1939
2383
  var TraySlots = class {
1940
2384
  device;
@@ -1972,7 +2416,7 @@ var TraySlots = class {
1972
2416
  const mat = this.root.mat.spawn();
1973
2417
  void program.create(this.device, this.format, mat).then((pass) => {
1974
2418
  this.making.delete(type);
1975
- if (this.disposed) {
2419
+ if (this.disposed || this.root.kinds.get(kind)?.pass !== own.pass) {
1976
2420
  pass.dispose();
1977
2421
  mat.dispose();
1978
2422
  return;
@@ -2012,6 +2456,19 @@ var TraySlots = class {
2012
2456
  for (const s of this.slots.values()) for (const k of s.kinds.values()) t = Math.min(t, k.pass.idleAt?.(ms) ?? Number.POSITIVE_INFINITY);
2013
2457
  return t;
2014
2458
  }
2459
+ /**
2460
+ * Kind `name` went MISSING (petition I24 — the ground's quarantine): its entry in every slot made so far becomes the desk's missing
2461
+ * faces on the slot's mat (a slot made from now on spawns the root's entry, which is already the face's). Returns the passes the
2462
+ * entries held; the ground lets them go.
2463
+ */
2464
+ quarantine(name, faces) {
2465
+ const gone = [];
2466
+ for (const s of this.slots.values()) {
2467
+ const p = swapToMissing(s, name, faces);
2468
+ if (p !== void 0) gone.push(p);
2469
+ }
2470
+ return gone;
2471
+ }
2015
2472
  /** The slots made so far (a rig's witness). */
2016
2473
  get size() {
2017
2474
  return this.slots.size;
@@ -2027,7 +2484,7 @@ var TraySlots = class {
2027
2484
  };
2028
2485
 
2029
2486
  // ../desk/src/tray/pass.ts
2030
- var BLEND_PREMUL4 = {
2487
+ var BLEND_PREMUL5 = {
2031
2488
  color: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" },
2032
2489
  alpha: { srcFactor: "one", dstFactor: "one-minus-src-alpha", operation: "add" }
2033
2490
  };
@@ -2082,6 +2539,8 @@ var TrayPass = class _TrayPass {
2082
2539
  sent = false;
2083
2540
  /** The quads the last prepare laid: 0 while the drawer is shut (p 0) — then it draws nothing. */
2084
2541
  quads = 0;
2542
+ /** A host's foot inset this frame (I21): the veil lays a second quad, over the foot, in its one draw. */
2543
+ foot = 0;
2085
2544
  group = null;
2086
2545
  boundAssets = -1;
2087
2546
  last = null;
@@ -2115,9 +2574,9 @@ var TrayPass = class _TrayPass {
2115
2574
  ], "tray/pegboard");
2116
2575
  const pl = device.createPipelineLayout({ label: "tray/pegboard", bindGroupLayouts: [layout] });
2117
2576
  const pipelines = await Promise.all([
2118
- renderPipeline(device, { label: "tray/pegboard", layout: pl, module, format, blend: BLEND_PREMUL4 }),
2119
- renderPipeline(device, { label: "tray/pegboard/accessories", layout: pl, module, format, blend: BLEND_PREMUL4, fragment: "fs_accessory" }),
2120
- renderPipeline(device, { label: "tray/pegboard/veil", layout: pl, module, format, blend: BLEND_PREMUL4, vertex: "vs_veil", fragment: "fs_veil" })
2577
+ renderPipeline(device, { label: "tray/pegboard", layout: pl, module, format, blend: BLEND_PREMUL5 }),
2578
+ renderPipeline(device, { label: "tray/pegboard/accessories", layout: pl, module, format, blend: BLEND_PREMUL5, fragment: "fs_accessory" }),
2579
+ renderPipeline(device, { label: "tray/pegboard/veil", layout: pl, module, format, blend: BLEND_PREMUL5, vertex: "vs_veil", fragment: "fs_veil" })
2121
2580
  ]);
2122
2581
  return new _TrayPass(device, mat, pipelines, layout);
2123
2582
  }
@@ -2131,8 +2590,10 @@ var TrayPass = class _TrayPass {
2131
2590
  const p = Math.min(Math.max(inputs.p, 0), 1);
2132
2591
  const rect = drawerRect(view.width, view.height, p);
2133
2592
  const { rowBase, frac } = carry(inputs.scroll, P);
2593
+ const foot = inputs.foot !== void 0 && inputs.foot > 0 ? inputs.foot : 0;
2594
+ this.foot = foot;
2134
2595
  if (p <= 0) {
2135
- this.last = { rect, rowBase, frac, dim: 0, accessories: [] };
2596
+ this.last = { rect, rowBase, frac, dim: 0, accessories: [], ...foot > 0 ? { foot } : {} };
2136
2597
  this.quads = 0;
2137
2598
  return 0;
2138
2599
  }
@@ -2165,7 +2626,7 @@ var TrayPass = class _TrayPass {
2165
2626
  keepFine: [keep(fp, 210), keep(fp, 42), keep(fp, 105), 0],
2166
2627
  keepEdge: [keep(fp, 9), keep(fp, 18), keep(fp, 36), 0],
2167
2628
  accessory: [...T.accessory, T.accessoryShadow],
2168
- fade: [DRAWER.fade, DRAWER.header, 0, 0]
2629
+ fade: [DRAWER.fade, DRAWER.header, foot, 0]
2169
2630
  };
2170
2631
  this.tray.set(values);
2171
2632
  const gobo = grid.mat.gobo;
@@ -2182,7 +2643,7 @@ var TrayPass = class _TrayPass {
2182
2643
  this.sent = true;
2183
2644
  const accessories = (inputs.specimens ?? []).map(accessoryOf);
2184
2645
  this.layAccessories(accessories);
2185
- this.last = { rect, rowBase, frac, dim, accessories };
2646
+ this.last = { rect, rowBase, frac, dim, accessories, ...foot > 0 ? { foot } : {} };
2186
2647
  this.quads = 1;
2187
2648
  return 1;
2188
2649
  }
@@ -2242,13 +2703,13 @@ var TrayPass = class _TrayPass {
2242
2703
  /**
2243
2704
  * THE VEIL (design-018 rev 5) — LAST, over everything the drawer holds (the caller left the scissor on the whole view): the plain board
2244
2705
  * whole in the header and fading out over its ramp, so what hangs there and the holes behind it fade into the board together — a
2245
- * specimen never turns see-through over a hole. One quad.
2706
+ * specimen never turns see-through over a hole. One quad — and with a host's foot (I21) a second, over the foot, in the same draw.
2246
2707
  */
2247
2708
  drawVeil(pass) {
2248
2709
  if (this.quads === 0) return;
2249
2710
  pass.pushDebugGroup("tray/pegboard/veil");
2250
2711
  this.bind(pass, this.veilPipeline);
2251
- pass.draw(6, 1, 0, 0);
2712
+ pass.draw(this.foot > 0 ? 12 : 6, 1, 0, 0);
2252
2713
  pass.popDebugGroup();
2253
2714
  }
2254
2715
  /** What the last prepare laid (a rig's witness). */
@@ -2265,26 +2726,127 @@ var TrayPass = class _TrayPass {
2265
2726
 
2266
2727
  // ../desk/src/ground.ts
2267
2728
  var visible = (p) => (p?.opacity ?? 1) > 0;
2268
- async function createSlotSet(device, format, mat, programs) {
2729
+ function checkPrograms(programs) {
2269
2730
  const names = /* @__PURE__ */ new Set();
2270
2731
  for (const p of programs) {
2271
2732
  if (!p.name) throw new Error("ground: a kind needs a name \u2014 it is the kind's key in every slot");
2733
+ if (p.name === MISSING_KIND) throw new Error(`ground: the kind name "${MISSING_KIND}" is the desk's own \u2014 the missing face's (petition I24)`);
2272
2734
  if (names.has(p.name)) throw new Error(`ground: two kinds are named "${p.name}" \u2014 a kind's name is its key in every slot`);
2273
2735
  if (!STRATA.includes(p.stratum)) throw new Error(`ground: kind "${p.name}" lies in no stratum the ground draws ("${p.stratum}"; ${STRATA.join(", ")})`);
2274
2736
  names.add(p.name);
2275
2737
  }
2276
- const [passes, cards] = await Promise.all([Promise.all(programs.map((p) => p.create(device, format, mat))), createCardShared(device, format, programs)]);
2738
+ }
2739
+ var SCOPES = ["validation", "out-of-memory", "internal"];
2740
+ var watched = (device) => typeof device.pushErrorScope === "function";
2741
+ var pushScopes = (device) => {
2742
+ if (watched(device)) for (const f of SCOPES) device.pushErrorScope(f);
2743
+ };
2744
+ function popScopes(device) {
2745
+ if (!watched(device)) return Promise.resolve(null);
2746
+ const popped = SCOPES.map(() => device.popErrorScope());
2747
+ return Promise.all(popped).then((errors) => errors.find((e) => e !== null) ?? null);
2748
+ }
2749
+ var settle = (p) => {
2750
+ try {
2751
+ return Promise.resolve(p()).then((value) => ({ value }), (error) => ({ error }));
2752
+ } catch (error) {
2753
+ return Promise.resolve({ error });
2754
+ }
2755
+ };
2756
+ function letGo(name, pass) {
2757
+ try {
2758
+ pass.dispose();
2759
+ } catch (err) {
2760
+ console.error(`desk: the kind "${name}"'s pass threw in its dispose`, err);
2761
+ }
2762
+ }
2763
+ async function createKindPasses(device, format, mat, programs, onError) {
2764
+ pushScopes(device);
2765
+ const made = programs.map((p) => {
2766
+ pushScopes(device);
2767
+ const created = settle(() => p.create(device, format, mat));
2768
+ return { created, own: popScopes(device) };
2769
+ });
2770
+ const first = await Promise.all(made.map(async (m) => ({ created: await m.created, own: await m.own })));
2771
+ const window2 = await popScopes(device);
2772
+ const passes = [];
2773
+ const faults = [];
2774
+ const refuse = (p, why) => {
2775
+ faults.push({ kind: p.name, reason: why });
2776
+ };
2777
+ const threw = (err) => `refused at create \u2014 ${faultText(err)}`;
2778
+ const raised = (err) => `refused at create \u2014 a GPU error while its pass was made: ${faultText(err)}`;
2779
+ first.forEach(({ created, own }, i) => {
2780
+ const p = programs[i];
2781
+ if ("error" in created) {
2782
+ passes.push(void 0);
2783
+ refuse(p, threw(created.error));
2784
+ return;
2785
+ }
2786
+ if (own !== null) {
2787
+ passes.push(void 0);
2788
+ letGo(p.name, created.value);
2789
+ refuse(p, raised(own));
2790
+ return;
2791
+ }
2792
+ passes.push(created.value);
2793
+ });
2794
+ if (window2 !== null) {
2795
+ let found = false;
2796
+ for (let i = 0; i < programs.length; i++) {
2797
+ const p = programs[i];
2798
+ const had = passes[i];
2799
+ if (had === void 0) continue;
2800
+ letGo(p.name, had);
2801
+ pushScopes(device);
2802
+ const again = await settle(() => p.create(device, format, mat));
2803
+ const own = await popScopes(device);
2804
+ if ("error" in again) {
2805
+ passes[i] = void 0;
2806
+ found = true;
2807
+ refuse(p, threw(again.error));
2808
+ continue;
2809
+ }
2810
+ if (own !== null) {
2811
+ passes[i] = void 0;
2812
+ found = true;
2813
+ letGo(p.name, again.value);
2814
+ refuse(p, raised(own));
2815
+ continue;
2816
+ }
2817
+ passes[i] = again.value;
2818
+ }
2819
+ if (!found) onError?.(window2);
2820
+ }
2821
+ faults.sort((a, b) => programs.findIndex((p) => p.name === a.kind) - programs.findIndex((p) => p.name === b.kind));
2822
+ return { passes, faults };
2823
+ }
2824
+ function slotSetOf(device, mat, programs, made, missing, cards) {
2277
2825
  const kinds = /* @__PURE__ */ new Map();
2278
2826
  for (let i = 0; i < programs.length; i++) {
2279
2827
  const p = programs[i];
2280
- kinds.set(p.name, { name: p.name, stratum: p.stratum, pass: passes[i], ...p.composite ? { composite: true } : {} });
2828
+ const pass = made.passes[i];
2829
+ kinds.set(p.name, pass === void 0 ? { name: p.name, stratum: p.stratum, pass: missing.spawn(mat) } : { name: p.name, stratum: p.stratum, pass, ...p.composite ? { composite: true } : {} });
2281
2830
  }
2282
- return cards === null ? { mat, kinds } : { mat, kinds, card: new CardPass(device, cards, mat, kinds) };
2831
+ const card = cards === null ? void 0 : new CardPass(device, cards, mat, kinds);
2832
+ return { mat, kinds, missing, ...card !== void 0 ? { card } : {}, ...made.faults.length > 0 ? { faults: made.faults } : {} };
2833
+ }
2834
+ var standing = (programs, made) => programs.filter((_, i) => made.passes[i] !== void 0);
2835
+ var unattributed = (e) => {
2836
+ console.error("desk: a GPU error while the kinds were made, no kind's alone \u2014", e.message);
2837
+ };
2838
+ async function createSlotSet(device, format, mat, programs, onError = unattributed) {
2839
+ checkPrograms(programs);
2840
+ const made = await createKindPasses(device, format, mat, programs, onError);
2841
+ const cards = await createCardShared(device, format, standing(programs, made));
2842
+ return slotSetOf(device, mat, programs, made, missingFaces(device, format, mat), cards);
2283
2843
  }
2284
2844
  var SlotPool = class {
2285
2845
  slots = [];
2286
2846
  used = 0;
2287
2847
  root;
2848
+ /** False once a kind the card composes went missing (petition I24 — `Ground.quarantine`): every slot's card, spawned or yet to be, is off. */
2849
+ cardOn = true;
2288
2850
  constructor(root) {
2289
2851
  this.root = root;
2290
2852
  }
@@ -2298,10 +2860,15 @@ var SlotPool = class {
2298
2860
  const kinds = /* @__PURE__ */ new Map();
2299
2861
  for (const k of this.root.kinds.values()) kinds.set(k.name, { name: k.name, stratum: k.stratum, pass: k.pass.spawn(mat), ...k.composite ? { composite: true } : {} });
2300
2862
  const card = this.root.card?.spawn(mat, kinds);
2863
+ if (card !== void 0 && !this.cardOn) card.on = false;
2301
2864
  this.slots.push(card === void 0 ? { mat, kinds } : { mat, kinds, card });
2302
2865
  }
2303
2866
  return this.slots[this.used++];
2304
2867
  }
2868
+ /** Every slot spawned so far (a quarantine walks them). */
2869
+ each(fn) {
2870
+ for (const s of this.slots) fn(s);
2871
+ }
2305
2872
  /** Slots spawned so far — the churn instrument. */
2306
2873
  get size() {
2307
2874
  return this.slots.length;
@@ -2421,7 +2988,13 @@ function prepareFrame(encoder, root, pool, inputs, grid = DEFAULT_GRID, target,
2421
2988
  }
2422
2989
  const indexOf = [];
2423
2990
  for (const o of objects) {
2424
- const list = records.get(o.kind);
2991
+ let list = records.get(o.kind);
2992
+ if (!list && o.kind === MISSING_KIND && root.missing !== void 0) {
2993
+ s.kinds.set(MISSING_KIND, { name: MISSING_KIND, stratum: "things", pass: root.missing.spawn(s.mat) });
2994
+ list = [];
2995
+ records.set(MISSING_KIND, list);
2996
+ keys.set(MISSING_KIND, []);
2997
+ }
2425
2998
  if (!list) throw new Error(`ground: no kind "${o.kind}" is registered (${[...s.kinds.keys()].join(", ") || "none"})`);
2426
2999
  indexOf.push(list.length);
2427
3000
  list.push(o.record);
@@ -2459,7 +3032,7 @@ function prepareFrame(encoder, root, pool, inputs, grid = DEFAULT_GRID, target,
2459
3032
  for (const k of s.kinds.values()) {
2460
3033
  const list = records.get(k.name) ?? [];
2461
3034
  if (list.length === 0) {
2462
- drawn.push([k.name, 0]);
3035
+ if (k.name !== MISSING_KIND) drawn.push([k.name, 0]);
2463
3036
  continue;
2464
3037
  }
2465
3038
  const own = root.kinds.get(k.name);
@@ -2511,7 +3084,7 @@ function prepareFrame(encoder, root, pool, inputs, grid = DEFAULT_GRID, target,
2511
3084
  const tray = [];
2512
3085
  const specimens = inputs.tray?.specimens;
2513
3086
  if (specimens !== void 0 && specimens.length > 0 && traySlots !== void 0 && inputs.tray !== void 0) {
2514
- const present = facePresent({ rect: trayRectOf(inputs), scroll: inputs.tray.scroll }, inputs.view.height);
3087
+ const present = facePresent({ rect: trayRectOf(inputs), scroll: inputs.tray.scroll, ...inputs.tray.foot !== void 0 ? { foot: inputs.tray.foot } : {} }, inputs.view.height);
2515
3088
  for (const f of specimens) {
2516
3089
  const slot = traySlots.get(f.type, f.kind);
2517
3090
  if (slot === void 0) continue;
@@ -2550,7 +3123,7 @@ function drawTray(pass, size, dpr, tray, slots, marks, carried) {
2550
3123
  if (rect !== void 0) {
2551
3124
  for (const s of slots ?? []) drawSlot(pass, size, dpr, { ...s, bare: true });
2552
3125
  if (marks !== null && marks.tagCount > 0) {
2553
- const [x, y, w, h] = scissorOf({ opacity: 1, portal: faceBox(rect, size.h / dpr) }, dpr, size);
3126
+ const [x, y, w, h] = scissorOf({ opacity: 1, portal: faceBox(rect, size.h / dpr, tray.laid?.foot ?? 0) }, dpr, size);
2554
3127
  if (w > 0 && h > 0) {
2555
3128
  pass.setScissorRect(x, y, w, h);
2556
3129
  marks.drawTags(pass);
@@ -2564,13 +3137,27 @@ function drawTray(pass, size, dpr, tray, slots, marks, carried) {
2564
3137
  pass.setScissorRect(0, 0, size.w, size.h);
2565
3138
  }
2566
3139
  }
2567
- function faceBox(rect, vh) {
3140
+ function faceBox(rect, vh, foot) {
2568
3141
  const x0 = rect.x + DRAWER.arris;
2569
3142
  const x1 = rect.x + rect.w - DRAWER.arris;
2570
3143
  const y0 = rect.y + DRAWER.arris + DRAWER.header;
2571
- const y1 = Math.max(vh, rect.y + rect.h);
3144
+ const y1 = foot > 0 ? rect.y + rect.h - foot : Math.max(vh, rect.y + rect.h);
2572
3145
  return { cx: (x0 + x1) / 2, cy: (y0 + y1) / 2, hx: (x1 - x0) / 2, hy: (y1 - y0) / 2, r: 0 };
2573
3146
  }
3147
+ function encodeFrame(encoder, passes, inputs, into, target) {
3148
+ const { root, pool, grid, marks, tray, traySlots } = passes;
3149
+ const trayed = inputs.tray !== void 0 && tray !== null ? tray.prepare(inputs.view, inputs.theme, inputs.grid ?? grid, inputs.mat, inputs.tray) : 0;
3150
+ const prepared = prepareFrame(encoder, root, pool, inputs, grid, target, inputs.tray !== void 0 ? traySlots ?? void 0 : void 0);
3151
+ const marked = marks !== null ? marks.prepare(inputs.marks, trayed > 0 && tray !== null ? { view: inputs.view, tags: tagsOf(inputs.tray), night: inputs.theme.matLight.night } : void 0) : 0;
3152
+ const bg = inputs.theme.canvasBg;
3153
+ const size = into.size();
3154
+ const pass = beginPass(encoder, into.view(), [bg[0], bg[1], bg[2], 1], "ground");
3155
+ const drawn = drawFrame(pass, size, inputs.view.dpr, prepared.incoming, prepared.outgoing);
3156
+ if (marked > 0) marks?.draw(pass);
3157
+ if ((trayed > 0 || prepared.carried !== void 0) && tray !== null) drawTray(pass, size, inputs.view.dpr, tray, prepared.tray, marks, prepared.carried);
3158
+ pass.end();
3159
+ return { stats: { ...drawn.incoming, kinds: prepared.kinds, outgoing: drawn.outgoing, portals: prepared.portals, ...prepared.dropped ? { dropped: prepared.dropped } : {} }, prepared, marked };
3160
+ }
2574
3161
  var Ground = class _Ground {
2575
3162
  device;
2576
3163
  surface;
@@ -2598,6 +3185,8 @@ var Ground = class _Ground {
2598
3185
  }
2599
3186
  /** The drops last said, by kind (D7). */
2600
3187
  dropSaid = /* @__PURE__ */ new Map();
3188
+ /** The kinds MISSING on this ground (petition I24): refused at create, or quarantined since — their entries draw the missing face. */
3189
+ missing = /* @__PURE__ */ new Set();
2601
3190
  constructor(device, surface2, root, marks, hold, tray, programs) {
2602
3191
  this.device = device;
2603
3192
  this.surface = surface2;
@@ -2608,21 +3197,68 @@ var Ground = class _Ground {
2608
3197
  this.tray = tray;
2609
3198
  this.pool = new SlotPool(root);
2610
3199
  this.traySlots = tray === null ? null : new TraySlots(device, surface2.format, root, programs);
3200
+ for (const f of root.faults ?? []) this.missing.add(f.kind);
2611
3201
  }
3202
+ /**
3203
+ * The mat first (every kind's pass is made on it), then every kind's pass in its own error scope (`createKindPasses` — a kind refused
3204
+ * there is MISSING from the start: `faults()`, petition I24), then — once the kinds' window has closed, so nothing of theirs is taken
3205
+ * for a kind's — the card over the kinds that stood, the marks, the hand and the tray, together. The missing faces are made only when
3206
+ * one is asked for (a kind refused here, a quarantine, an object with no kind).
3207
+ */
2612
3208
  static async create(opts) {
2613
3209
  const surf = opts.surface;
2614
3210
  const mat = await CuttingMat.create(opts.device, surf.format, opts.mat);
2615
- const root = await createSlotSet(opts.device, surf.format, mat, opts.kinds);
2616
- const [marks, hold, tray] = await Promise.all([
3211
+ checkPrograms(opts.kinds);
3212
+ const made = await createKindPasses(opts.device, surf.format, mat, opts.kinds, opts.onError ?? unattributed);
3213
+ const [cards, marks, hold, tray] = await Promise.all([
3214
+ createCardShared(opts.device, surf.format, standing(opts.kinds, made)),
2617
3215
  opts.marks === void 0 ? null : MarksPass.create(opts.device, surf.format, opts.marks, mat),
2618
3216
  opts.hold === void 0 ? null : HoldPass.create(opts.device, surf.format, opts.hold),
2619
3217
  opts.tray === void 0 ? null : TrayPass.create(opts.device, surf.format, opts.tray, mat)
2620
3218
  ]);
2621
- return new _Ground(opts.device, surf, root, marks, hold, tray, opts.kinds);
3219
+ return new _Ground(opts.device, surf, slotSetOf(opts.device, mat, opts.kinds, made, missingFaces(opts.device, surf.format, mat), cards), marks, hold, tray, opts.kinds);
2622
3220
  }
2623
- /** The root's pass of the kind registered as `name` (undefined if none) — a host reaches its kind's own API through it: the note's ink pages, the whiteboard's rasters. */
3221
+ /**
3222
+ * The root's pass of the kind registered as `name` (undefined if none, or MISSING — petition I24: nothing of a missing kind is reached
3223
+ * again) — a host reaches its kind's own API through it: the note's ink pages, the whiteboard's rasters.
3224
+ */
2624
3225
  pass(name) {
2625
- return this.root.kinds.get(name)?.pass;
3226
+ return this.missing.has(name) ? void 0 : this.root.kinds.get(name)?.pass;
3227
+ }
3228
+ /** The kinds REFUSED when this ground was made (petition I24): each its name and why. Quarantines since are the layer's word. */
3229
+ faults() {
3230
+ return this.root.faults ?? [];
3231
+ }
3232
+ /**
3233
+ * THE QUARANTINE (petition I24): kind `name` is MISSING from now on — its entry in every slot this ground holds (the root, the pool's,
3234
+ * the tray's) becomes a spawn of that slot's missing face, under its name and in its stratum, and the passes it had are disposed (the
3235
+ * pool's and the tray's first; the root's — whose shared resources go with it — last; a dispose that throws is said and passed). A
3236
+ * kind the flat card composes turns the card off in every slot, for good (K7b's own A/B door: each material kind then draws its own
3237
+ * runs — the card's pixels, more draws): its resources leave the card's bind group, and no draw may reach them. The next frame draws
3238
+ * the kind's objects in the missing face (the builder makes their records so); one already made for its own pass is drawn as nothing.
3239
+ * A kind already missing, or none of that name: nothing.
3240
+ */
3241
+ quarantine(name) {
3242
+ if (this.missing.has(name) || !this.root.kinds.has(name)) return;
3243
+ this.missing.add(name);
3244
+ if (this.root.card?.materialOf(name) !== void 0) {
3245
+ this.root.card.on = false;
3246
+ this.pool.cardOn = false;
3247
+ this.pool.each((s) => {
3248
+ if (s.card !== void 0) s.card.on = false;
3249
+ });
3250
+ }
3251
+ const faces = this.root.missing;
3252
+ const gone = [];
3253
+ this.pool.each((s) => {
3254
+ const p = swapToMissing(s, name, faces);
3255
+ if (p !== void 0) gone.push(p);
3256
+ });
3257
+ gone.push(...this.traySlots?.quarantine(name, faces) ?? []);
3258
+ const root = swapToMissing(this.root, name, faces);
3259
+ if (root !== void 0) gone.push(root);
3260
+ for (const p of gone) letGo(name, p);
3261
+ if (this.heldCache.stamp !== null) this.heldCache.stamp = "";
2626
3262
  }
2627
3263
  /** Size the canvas to its CSS box; returns the CSS size and dpr the frame should use. (A host may size the canvas itself instead.) */
2628
3264
  fit(maxDpr = 2) {
@@ -2637,17 +3273,18 @@ var Ground = class _Ground {
2637
3273
  for (const k of this.root.kinds.values()) k.pass.endHold?.();
2638
3274
  }
2639
3275
  const encoder = this.device.createCommandEncoder({ label: "ground" });
2640
- const trayed = inputs.tray !== void 0 && this.tray !== null ? this.tray.prepare(inputs.view, inputs.theme, inputs.grid ?? this.grid, inputs.mat, inputs.tray) : 0;
2641
- const prepared = prepareFrame(encoder, this.root, this.pool, inputs, this.grid, void 0, inputs.tray !== void 0 ? this.traySlots ?? void 0 : void 0);
2642
- const marked = this.marks !== null ? this.marks.prepare(inputs.marks, trayed > 0 && this.tray !== null ? { view: inputs.view, tags: tagsOf(inputs.tray), night: inputs.theme.matLight.night } : void 0) : 0;
2643
- const bg = inputs.theme.canvasBg;
2644
- const pass = beginPass(encoder, this.surface.view(), [bg[0], bg[1], bg[2], 1], "ground");
2645
- const drawn = drawFrame(pass, this.surface.size(), inputs.view.dpr, prepared.incoming, prepared.outgoing);
2646
- if (marked > 0) this.marks?.draw(pass);
2647
- if ((trayed > 0 || prepared.carried !== void 0) && this.tray !== null) drawTray(pass, this.surface.size(), inputs.view.dpr, this.tray, prepared.tray, this.marks, prepared.carried);
2648
- pass.end();
3276
+ const { stats } = encodeFrame(encoder, this, inputs, this.surface);
2649
3277
  this.device.queue.submit([encoder.finish()]);
2650
- return this.said({ ...drawn.incoming, kinds: prepared.kinds, outgoing: drawn.outgoing, portals: prepared.portals, ...prepared.dropped ? { dropped: prepared.dropped } : {} });
3278
+ return this.said(stats);
3279
+ }
3280
+ /**
3281
+ * THE CAPTURE DOOR's GPU half (petition I23; `captureFrame`): `inputs` — the last frame's, as `render` took them — drawn once
3282
+ * more into a readable texture at the view's dpr × `scale`, `rect` (CSS px of the view) read back. The swap chain is not touched,
3283
+ * no frame is drawn, nothing is kept: the still and its readback buffer are made for the call and destroyed after it. Undefined
3284
+ * when the device is lost mid-copy, or the rect lies off the view.
3285
+ */
3286
+ capture(inputs, opts = {}) {
3287
+ return captureFrame(this.device, this, this.surface.format, inputs, this.heldCache, opts);
2651
3288
  }
2652
3289
  /** A kind whose cap turns objects away is an ERROR on the host's console (D7): said when the drop begins and again if it grows. */
2653
3290
  said(stats) {
@@ -2659,12 +3296,13 @@ var Ground = class _Ground {
2659
3296
  for (const kind of [...this.dropSaid.keys()]) if (!(kind in now)) this.dropSaid.delete(kind);
2660
3297
  return stats;
2661
3298
  }
2662
- /** The pool's slots, then the root's kinds in reverse registration order, then the mat. */
3299
+ /** The pool's slots, then the root's kinds in reverse registration order (a missing kind's face among them), its missing face, then the mat. */
2663
3300
  dispose() {
2664
3301
  this.pool.dispose();
2665
3302
  this.traySlots?.dispose();
2666
3303
  this.root.card?.dispose();
2667
3304
  for (const k of [...this.root.kinds.values()].reverse()) k.pass.dispose();
3305
+ this.root.missing?.dispose();
2668
3306
  this.marks?.dispose();
2669
3307
  this.hold?.dispose();
2670
3308
  this.tray?.dispose();
@@ -2687,7 +3325,7 @@ function tagsOf(tray) {
2687
3325
  const P = DRAWER.pitch;
2688
3326
  return (tray?.specimens ?? []).map((f) => ({ label: f.label, x: (f.screen.x0 + f.screen.x1) / 2, y: f.screen.y1 + (f.accessory === "shelf" ? 0.24 * P : 0) + 0.45 * P }));
2689
3327
  }
2690
- function renderHeldFrame(device, hold, root, pool, grid, into, inputs, held, cache) {
3328
+ function renderHeldFrame(device, hold, root, pool, grid, into, inputs, held, cache, label = "hold") {
2691
3329
  const size = into.size();
2692
3330
  const dpr = inputs.view.dpr;
2693
3331
  const bg = inputs.theme.canvasBg;
@@ -2695,29 +3333,104 @@ function renderHeldFrame(device, hold, root, pool, grid, into, inputs, held, cac
2695
3333
  if (remade || cache.stamp !== held.stamp) {
2696
3334
  cache.stamp = held.stamp;
2697
3335
  cache.copies += 1;
2698
- const encoder2 = device.createCommandEncoder({ label: "hold/copy" });
3336
+ const encoder2 = device.createCommandEncoder({ label: `${label}/copy` });
2699
3337
  const { held: _held, marks: _marks, ...rest } = inputs;
2700
3338
  const copy = { ...rest, view: { ...inputs.view, dpr: dpr / 2 } };
2701
3339
  const prepared = prepareFrame(encoder2, root, pool, copy, grid, "copy");
2702
- const pass2 = beginPass(encoder2, hold.desk.view, [bg[0], bg[1], bg[2], 1], "hold/copy");
3340
+ const pass2 = beginPass(encoder2, hold.desk.view, [bg[0], bg[1], bg[2], 1], `${label}/copy`);
2703
3341
  const drawn = drawFrame(pass2, { w: hold.desk.width, h: hold.desk.height }, dpr / 2, prepared.incoming, prepared.outgoing);
2704
3342
  pass2.end();
2705
3343
  hold.blur(encoder2, held.blur * dpr / 2);
2706
3344
  device.queue.submit([encoder2.finish()]);
2707
3345
  cache.stats = { ...drawn.incoming, kinds: prepared.kinds, outgoing: drawn.outgoing, portals: prepared.portals, ...prepared.dropped ? { dropped: prepared.dropped } : {} };
2708
3346
  }
2709
- const encoder = device.createCommandEncoder({ label: "hold" });
3347
+ const encoder = device.createCommandEncoder({ label });
2710
3348
  const handInputs = { view: held.view, grid: held.grid, objects: [held.object, ...held.riders ?? []], theme: { ...inputs.theme, matLight: held.light } };
2711
3349
  const hand = prepareFrame(encoder, root, pool, handInputs, held.grid, "hand");
2712
- const handPass = beginPass(encoder, hold.hand.view, [0, 0, 0, 0], "hold/hand");
3350
+ const handPass = beginPass(encoder, hold.hand.view, [0, 0, 0, 0], `${label}/hand`);
2713
3351
  drawSlot(handPass, size, dpr, { ...hand.incoming, bare: true });
2714
3352
  handPass.end();
2715
- const pass = beginPass(encoder, into.view(), [bg[0], bg[1], bg[2], 1], "hold");
3353
+ const pass = beginPass(encoder, into.view(), [bg[0], bg[1], bg[2], 1], label);
2716
3354
  hold.composite(pass, { e: held.e, dim: held.dim, saturate: held.filter.saturate, brightness: held.filter.brightness });
2717
3355
  pass.end();
2718
3356
  device.queue.submit([encoder.finish()]);
2719
3357
  return cache.stats ?? { k0: 0, fade: 0, wind: false, kinds: hand.kinds, outgoing: null, portals: 0 };
2720
3358
  }
3359
+ var captureNumber = (what, v, positive) => {
3360
+ if (typeof v !== "number" || !Number.isFinite(v) || positive && v <= 0) throw new Error(`capture: ${what} must be a ${positive ? "positive " : ""}finite number (got ${String(v)})`);
3361
+ return v;
3362
+ };
3363
+ function checkCapture(opts) {
3364
+ captureNumber("scale", opts.scale ?? 1, true);
3365
+ const r = opts.rect;
3366
+ if (r === void 0) return;
3367
+ captureNumber("rect.x", r.x, false);
3368
+ captureNumber("rect.y", r.y, false);
3369
+ captureNumber("rect.width", r.width, false);
3370
+ captureNumber("rect.height", r.height, false);
3371
+ }
3372
+ function scaledInputs(inputs, scale) {
3373
+ if (scale === 1) return inputs;
3374
+ const dprOf = (v) => ({ ...v, dpr: v.dpr * scale });
3375
+ const slotOf = (s) => ({ ...s, view: dprOf(s.view), ...s.portals !== void 0 ? { portals: s.portals.map(slotOf) } : {} });
3376
+ const { outgoing, held, marks, tray } = inputs;
3377
+ return {
3378
+ ...slotOf(inputs),
3379
+ ...outgoing !== void 0 ? { outgoing: slotOf(outgoing) } : {},
3380
+ ...held !== void 0 ? { held: { ...held, view: dprOf(held.view) } } : {},
3381
+ ...marks !== void 0 ? { marks: { ...marks, view: dprOf(marks.view) } } : {},
3382
+ ...tray !== void 0 ? { tray: { ...tray, ...tray.specimens !== void 0 ? { specimens: tray.specimens.map((f) => ({ ...f, view: dprOf(f.view) })) } : {}, ...tray.carried !== void 0 ? { carried: tray.carried.map((f) => ({ ...f, view: dprOf(f.view) })) } : {} } } : {}
3383
+ };
3384
+ }
3385
+ async function captureFrame(device, passes, format, inputs, cache, opts = {}) {
3386
+ checkCapture(opts);
3387
+ const frame = scaledInputs(inputs, opts.scale ?? 1);
3388
+ const dpr = frame.view.dpr;
3389
+ const size = { w: Math.max(1, Math.round(frame.view.width * dpr)), h: Math.max(1, Math.round(frame.view.height * dpr)) };
3390
+ let box2 = { x: 0, y: 0, w: size.w, h: size.h };
3391
+ const r = opts.rect;
3392
+ if (r !== void 0) {
3393
+ const x0 = Math.max(0, Math.round(r.x * dpr));
3394
+ const y0 = Math.max(0, Math.round(r.y * dpr));
3395
+ const x1 = Math.min(size.w, Math.round((r.x + r.width) * dpr));
3396
+ const y1 = Math.min(size.h, Math.round((r.y + r.height) * dpr));
3397
+ box2 = { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };
3398
+ if (box2.w <= 0 || box2.h <= 0) return void 0;
3399
+ }
3400
+ const still = new Target(device, { format, label: "capture/still", readable: true }, size.w, size.h);
3401
+ const into = { view: () => still.view, size: () => size };
3402
+ const rowBytes = Math.ceil(box2.w * 4 / 256) * 256;
3403
+ const readback = device.createBuffer({ label: "capture/readback", size: rowBytes * box2.h, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ });
3404
+ try {
3405
+ const copy = (encoder) => {
3406
+ encoder.copyTextureToBuffer({ texture: still.texture, origin: [box2.x, box2.y] }, { buffer: readback, bytesPerRow: rowBytes }, [box2.w, box2.h]);
3407
+ };
3408
+ if (frame.held !== void 0 && frame.held.e > 0 && passes.hold !== null) {
3409
+ renderHeldFrame(device, passes.hold, passes.root, passes.pool, passes.grid, into, frame, frame.held, cache, "capture");
3410
+ const encoder = device.createCommandEncoder({ label: "capture/read" });
3411
+ copy(encoder);
3412
+ device.queue.submit([encoder.finish()]);
3413
+ } else {
3414
+ const encoder = device.createCommandEncoder({ label: "capture" });
3415
+ encodeFrame(encoder, passes, frame, into, "capture");
3416
+ copy(encoder);
3417
+ device.queue.submit([encoder.finish()]);
3418
+ }
3419
+ try {
3420
+ await readback.mapAsync(GPUMapMode.READ);
3421
+ } catch {
3422
+ return void 0;
3423
+ }
3424
+ const padded = new Uint8Array(readback.getMappedRange());
3425
+ const bytes = new Uint8Array(new ArrayBuffer(box2.w * box2.h * 4));
3426
+ for (let y = 0; y < box2.h; y++) bytes.set(padded.subarray(y * rowBytes, y * rowBytes + box2.w * 4), y * box2.w * 4);
3427
+ readback.unmap();
3428
+ return { width: box2.w, height: box2.h, format, bytes };
3429
+ } finally {
3430
+ readback.destroy();
3431
+ still.dispose();
3432
+ }
3433
+ }
2721
3434
 
2722
3435
  // ../desk/src/mat/tilt.ts
2723
3436
  function secondOrder(f = 1, z = 0.3, r = 2, value = [0, 0]) {
@@ -3303,12 +4016,38 @@ function createDeskBuilder(world, opts) {
3303
4016
  const S = opts.springs ?? SPRINGS;
3304
4017
  const rungCtx = { entity: NO_ENTITY, rect: { cx: 0, cy: 0, w: 0, h: 0 }, props: {}, zoom: 1, dpr: 1, local: void 0, px: 0 };
3305
4018
  const locals = opts.locals;
4019
+ const faults = opts.faults;
3306
4020
  const forget = (kind, e) => {
3307
4021
  locals?.get(kind.name)?.forget?.(e);
3308
4022
  };
4023
+ const drawerOf = (kind) => kind === MISSING_OBJECT || faults?.missing(kind.name) === true ? MISSING_OBJECT : kind;
4024
+ const fault = (kind, call, err, e) => {
4025
+ if (faults === void 0) throw err;
4026
+ faults.strike(kind.name, call, err, e);
4027
+ };
4028
+ const kindless = /* @__PURE__ */ new Set();
4029
+ const mounted = /* @__PURE__ */ new Set();
4030
+ for (const w of opts.objects) {
4031
+ const k = objectKindOf(w);
4032
+ if (k !== void 0) mounted.add(k.name);
4033
+ }
4034
+ const unmounted = /* @__PURE__ */ new Set();
4035
+ const veilers = [];
4036
+ const veilerNames = [];
4037
+ const liftedBy = (st, e) => {
4038
+ const local = locals?.get(st.kind.name);
4039
+ if (local?.lifted === void 0) return false;
4040
+ try {
4041
+ return local.lifted(e) === true;
4042
+ } catch (err) {
4043
+ fault(st.kind, "lifted", err, e);
4044
+ return false;
4045
+ }
4046
+ };
3309
4047
  const ghostS = (opts.ghostMs ?? GHOST_MS) / 1e3;
3310
4048
  const marginPx = opts.marginPx ?? MARGIN_PX;
3311
4049
  const redressMs = opts.redressMs ?? REDRESS_MS;
4050
+ const reserves = opts.hold ?? HOLD;
3312
4051
  const order = createSiblingOrderIndex(world);
3313
4052
  const states = /* @__PURE__ */ new Map();
3314
4053
  const ghosts = /* @__PURE__ */ new Map();
@@ -3418,7 +4157,7 @@ function createDeskBuilder(world, opts) {
3418
4157
  const h = world.readField(e, Size, "h") ?? 0;
3419
4158
  st.rect = rectOf({ x, y }, { w, h });
3420
4159
  const props = {};
3421
- for (const g of st.widget.groups) {
4160
+ for (const g of st.widget?.groups ?? []) {
3422
4161
  const v = world.get(e, g.component);
3423
4162
  if (v !== void 0) for (const name of Object.keys(g.fields)) props[name] = v[name];
3424
4163
  }
@@ -3443,13 +4182,28 @@ function createDeskBuilder(world, opts) {
3443
4182
  const id3 = world.get(e, PrefabId)?.id;
3444
4183
  if (typeof id3 !== "string") return void 0;
3445
4184
  const widget = widgetTypeFor(world, id3);
3446
- const kind = objectKindOf(widget);
3447
- if (widget === void 0 || kind === void 0) return void 0;
4185
+ let kind = objectKindOf(widget);
4186
+ if (kind !== void 0 && faults !== void 0 && !mounted.has(kind.name)) {
4187
+ if (!unmounted.has(id3)) {
4188
+ unmounted.add(id3);
4189
+ console.warn(`[ice] desk: the type "${id3}" has a kind ("${kind.name}") this desk was not mounted with \u2014 a desk's kinds are compiled at its mount, and a type registered after it adds none: its objects are drawn as missing; remount to draw it (petition I25)`);
4190
+ }
4191
+ kind = MISSING_OBJECT;
4192
+ }
4193
+ if (kind === void 0) {
4194
+ if (faults === void 0) return void 0;
4195
+ kind = MISSING_OBJECT;
4196
+ if (!kindless.has(id3)) {
4197
+ kindless.add(id3);
4198
+ console.warn(`[ice] desk: the type "${id3}" has no kind on this desk \u2014 its objects are drawn as missing (petition I24)`);
4199
+ }
4200
+ }
3448
4201
  meet(kind);
3449
4202
  const insert = world.has(e, InsertGhost);
3450
4203
  const st = {
3451
4204
  kind,
3452
4205
  widget,
4206
+ face: false,
3453
4207
  rect: { cx: 0, cy: 0, w: 0, h: 0 },
3454
4208
  props: {},
3455
4209
  selected: false,
@@ -3490,7 +4244,7 @@ function createDeskBuilder(world, opts) {
3490
4244
  if (st === void 0) return void 0;
3491
4245
  if (st.dirty) refresh(e, st);
3492
4246
  if (had === void 0 && landing.length > 0 && !st.insert) {
3493
- const i = landing.findIndex((l2) => l2.type === st.widget.type && Math.abs(l2.rect.cx - st.rect.cx) < 0.5 && Math.abs(l2.rect.cy - st.rect.cy) < 0.5);
4247
+ const i = landing.findIndex((l2) => l2.type === st.widget?.type && Math.abs(l2.rect.cx - st.rect.cx) < 0.5 && Math.abs(l2.rect.cy - st.rect.cy) < 0.5);
3494
4248
  const l = landing[i];
3495
4249
  if (l !== void 0) {
3496
4250
  st.lift = l.lift;
@@ -3532,25 +4286,38 @@ function createDeskBuilder(world, opts) {
3532
4286
  const frameGridOf = (frame, root, looks) => {
3533
4287
  if (frame === void 0 || !world.isAlive(frame) || !world.hasTag(frame, Container)) return root;
3534
4288
  const st = stateOf(frame);
3535
- if (st === void 0 || st.kind.insideGrid === void 0) return root;
3536
- return st.kind.insideGrid({ props: st.props, look: looks.get(st.kind.name) }, root);
4289
+ const kind = st === void 0 ? void 0 : drawerOf(st.kind);
4290
+ if (st === void 0 || kind?.insideGrid === void 0) return root;
4291
+ try {
4292
+ return kind.insideGrid({ props: st.props, look: looks.get(kind.name) }, root);
4293
+ } catch (err) {
4294
+ fault(kind, "insideGrid", err, frame);
4295
+ return root;
4296
+ }
3537
4297
  };
3538
4298
  const navFace = (container, cam) => {
3539
4299
  const st = stateOf(container);
3540
- if (st === void 0 || st.kind.face === void 0) return void 0;
3541
- let G;
3542
- if (st.seen === seq && st.geometry !== null && st.slot === "root") G = st.geometry;
3543
- else {
3544
- const grid = lastGrid;
3545
- const theme = lastTheme;
3546
- if (grid === void 0 || theme === void 0) return void 0;
3547
- const vp = world.getResource(Viewport);
3548
- const view2 = { camX: cam.x, camY: cam.y, zoom: cam.zoom, width: vp?.w ?? 0, height: vp?.h ?? 0, dpr: vp?.dpr ?? 1 };
3549
- const local = locals?.get(st.kind.name);
3550
- const ctx = { entity: container, rect: st.rect, props: st.props, flux: FLUX_REST, look: lastLooks?.get(st.kind.name), theme, lamp: lampOf(grid.mat.plane), view: view2, grid, dt: 0, ...local !== void 0 ? { local } : {} };
3551
- G = st.kind.resolve(ctx);
4300
+ const kind = st === void 0 ? void 0 : drawerOf(st.kind);
4301
+ if (st === void 0 || kind?.face === void 0) return void 0;
4302
+ let face2;
4303
+ try {
4304
+ let G;
4305
+ if (st.seen === seq && st.geometry !== null && st.slot === "root" && !st.face) G = st.geometry;
4306
+ else {
4307
+ const grid = lastGrid;
4308
+ const theme = lastTheme;
4309
+ if (grid === void 0 || theme === void 0) return void 0;
4310
+ const vp = world.getResource(Viewport);
4311
+ const view2 = { camX: cam.x, camY: cam.y, zoom: cam.zoom, width: vp?.w ?? 0, height: vp?.h ?? 0, dpr: vp?.dpr ?? 1 };
4312
+ const local = locals?.get(kind.name);
4313
+ const ctx = { entity: container, rect: st.rect, props: st.props, flux: FLUX_REST, look: lastLooks?.get(kind.name), theme, lamp: lampOf(grid.mat.plane), view: view2, grid, dt: 0, ...local !== void 0 ? { local } : {} };
4314
+ G = kind.resolve(ctx);
4315
+ }
4316
+ face2 = kind.face(G);
4317
+ } catch (err) {
4318
+ fault(kind, "face", err, container);
4319
+ return void 0;
3552
4320
  }
3553
- const face2 = st.kind.face(G);
3554
4321
  if (face2 === void 0) return void 0;
3555
4322
  const vpSize = viewportOf();
3556
4323
  const view = insideViewOfFace(face2, st.kind.faceLaw?.radius ?? 0, contentOf(container), cam, vpSize, FIT, PORTAL_GATE);
@@ -3657,10 +4424,17 @@ function createDeskBuilder(world, opts) {
3657
4424
  for (const c of childrenOf(e)) {
3658
4425
  if (chips.length >= cap) break;
3659
4426
  const cst = stateOf(c);
3660
- if (cst === void 0 || cst.kind.chip === void 0) continue;
4427
+ const ckind = cst === void 0 ? void 0 : drawerOf(cst.kind);
4428
+ if (cst === void 0 || ckind?.chip === void 0) continue;
3661
4429
  const cctx = contextOf(c, cst, insideView, insideGrid, insideLamp, false);
3662
4430
  work.resolved += 1;
3663
- const chip = cst.kind.chip(cst.kind.resolve(cctx), cctx);
4431
+ let chip;
4432
+ try {
4433
+ chip = ckind.chip(ckind.resolve(cctx), cctx);
4434
+ } catch (err) {
4435
+ fault(ckind, "chip", err, c);
4436
+ continue;
4437
+ }
3664
4438
  if (chip === null) continue;
3665
4439
  const finish = finishOf(chip.finish, finishes);
3666
4440
  if (finish === void 0) {
@@ -3671,28 +4445,32 @@ function createDeskBuilder(world, opts) {
3671
4445
  }
3672
4446
  return chips;
3673
4447
  };
3674
- const insideOf = (e, st, G, ctx, slotCam, slotGrid) => {
3675
- const face2 = st.kind.face?.(G);
4448
+ const insideOf = (e, st, kind, G, ctx, slotCam, slotGrid) => {
4449
+ const face2 = kind.face?.(G);
3676
4450
  if (face2 === void 0) {
3677
4451
  st.inside = null;
3678
4452
  st.content = null;
3679
4453
  return void 0;
3680
4454
  }
3681
4455
  const content = contentOf(e);
3682
- const view = insideViewOfFace(face2, st.kind.faceLaw?.radius ?? 0, content, slotCam, vpSize, FIT, PORTAL_GATE);
4456
+ const view = insideViewOfFace(face2, kind.faceLaw?.radius ?? 0, content, slotCam, vpSize, FIT, PORTAL_GATE);
3683
4457
  st.inside = view;
3684
4458
  st.content = content;
3685
4459
  return { content, view, chips: chipsOf(e, st, ctx, view, slotGrid) };
3686
4460
  };
3687
- const checkReuse = (e, st, view, slotGrid, slotLamp, springs, give, slotCam) => {
4461
+ const checkReuse = (e, st, kind, view, slotGrid, slotLamp, springs, give, slotCam) => {
3688
4462
  const r = st.rect;
3689
4463
  const drawn = give === 0 ? r : { ...r, cx: r.cx + give / slotCam.zoom };
3690
4464
  const ctx = contextOf(e, st, view, slotGrid, slotLamp, springs, drawn);
3691
- const G = st.kind.resolve(ctx);
3692
- const face2 = st.kind.face?.(G);
3693
- const inside = face2 === void 0 ? void 0 : { content: st.content, view: st.inside, chips: chipsOf(e, st, ctx, st.inside, slotGrid) };
3694
- const R = st.kind.record(G, inside === void 0 ? ctx : { ...ctx, inside });
3695
- if (!sameRecord(R, st.record)) mismatches += 1;
4465
+ try {
4466
+ const G = kind.resolve(ctx);
4467
+ const face2 = kind.face?.(G);
4468
+ const inside = face2 === void 0 ? void 0 : { content: st.content, view: st.inside, chips: chipsOf(e, st, ctx, st.inside, slotGrid) };
4469
+ const R = kind.record(G, inside === void 0 ? ctx : { ...ctx, inside });
4470
+ if (!sameRecord(R, st.record)) mismatches += 1;
4471
+ } catch (err) {
4472
+ fault(kind, "record", err, e);
4473
+ }
3696
4474
  };
3697
4475
  const buildSlot = (members, slotCam, slotGrid, slot, depth, skip, redressOut2) => {
3698
4476
  const slotLamp = lampOf(slotGrid.mat.plane);
@@ -3729,9 +4507,11 @@ function createDeskBuilder(world, opts) {
3729
4507
  st.inside = null;
3730
4508
  continue;
3731
4509
  }
4510
+ const kind = (faults?.size ?? 0) === 0 ? st.kind : drawerOf(st.kind);
4511
+ const face2 = kind === MISSING_OBJECT;
3732
4512
  const r = st.rect;
3733
- const hx = r.w / 2 + st.kind.reach;
3734
- const hy = r.h / 2 + st.kind.reach;
4513
+ const hx = r.w / 2 + kind.reach;
4514
+ const hy = r.h / 2 + kind.reach;
3735
4515
  if (r.cx + hx < x0 || r.cx - hx > x1 || r.cy + hy < y0 || r.cy - hy > y1) {
3736
4516
  st.geometry = null;
3737
4517
  st.record = null;
@@ -3741,60 +4521,87 @@ function createDeskBuilder(world, opts) {
3741
4521
  }
3742
4522
  const give = slot === "root" ? marks.giveOf(e) : 0;
3743
4523
  const fluxMoved = springs ? stepSprings(e, st) : false;
3744
- const kind = st.kind;
3745
4524
  const fresh = st.record === null || st.geometry === null;
3746
- let remake = fresh || st.stale || remakeAll || fluxMoved || prevSlot !== slot || give !== st.give || kind.composite === true || restless?.has(kind.name) === true;
4525
+ let remake = fresh || st.stale || remakeAll || fluxMoved || prevSlot !== slot || give !== st.give || st.face !== face2 || kind.composite === true || restless?.has(kind.name) === true;
3747
4526
  if (fresh) work.fresh += 1;
3748
4527
  if (restless?.has(kind.name) === true) work.restless += 1;
3749
- if (!remake && (redpr || slotCam.zoom !== st.zoom)) {
3750
- if (kind.rung !== void 0) {
3751
- if (rungOf(kind, e, st, slotCam) !== st.rung) {
3752
- remake = true;
3753
- work.rerung += 1;
3754
- } else st.zoom = slotCam.zoom;
3755
- } else if (kind.rezoom === true) {
3756
- remake = true;
3757
- work.rezoomed += 1;
3758
- }
3759
- }
3760
4528
  let G;
3761
4529
  let R;
3762
- if (!remake) {
3763
- G = st.geometry;
3764
- R = st.record;
3765
- work.reused += 1;
3766
- if (kind.face !== void 0) {
3767
- const face2 = kind.face(G);
3768
- st.inside = face2 === void 0 ? null : insideViewOfFace(face2, st.kind.faceLaw?.radius ?? 0, st.content, slotCam, vpSize, FIT, PORTAL_GATE);
4530
+ let call = "rung";
4531
+ try {
4532
+ if (!remake && (redpr || slotCam.zoom !== st.zoom)) {
4533
+ if (kind.rung !== void 0) {
4534
+ if (rungOf(kind, e, st, slotCam) !== st.rung) {
4535
+ remake = true;
4536
+ work.rerung += 1;
4537
+ } else st.zoom = slotCam.zoom;
4538
+ } else if (kind.rezoom === true) {
4539
+ remake = true;
4540
+ work.rezoomed += 1;
4541
+ }
3769
4542
  }
3770
- if (verifying) checkReuse(e, st, view, slotGrid, slotLamp, springs, give, slotCam);
3771
- } else {
3772
- const drawn = give === 0 ? r : { ...r, cx: r.cx + give / slotCam.zoom };
3773
- const ctx = contextOf(e, st, view, slotGrid, slotLamp, springs, drawn);
3774
- work.resolved += 1;
3775
- G = kind.resolve(ctx);
3776
- const inside = insideOf(e, st, G, ctx, slotCam, slotGrid);
3777
- work.recorded += 1;
3778
- R = kind.record(G, inside === void 0 ? ctx : { ...ctx, inside });
3779
- const landed = locals?.get(kind.name)?.landed?.(e) ?? 0;
3780
- if (landed !== st.landed) {
3781
- st.landed = landed;
3782
- deskLanded = true;
4543
+ if (!remake) {
4544
+ G = st.geometry;
4545
+ R = st.record;
4546
+ work.reused += 1;
4547
+ if (kind.face !== void 0) {
4548
+ call = "face";
4549
+ const shown = kind.face(G);
4550
+ st.inside = shown === void 0 ? null : insideViewOfFace(shown, kind.faceLaw?.radius ?? 0, st.content, slotCam, vpSize, FIT, PORTAL_GATE);
4551
+ }
4552
+ if (verifying) checkReuse(e, st, kind, view, slotGrid, slotLamp, springs, give, slotCam);
4553
+ } else {
4554
+ const drawn = give === 0 ? r : { ...r, cx: r.cx + give / slotCam.zoom };
4555
+ const ctx = contextOf(e, st, view, slotGrid, slotLamp, springs, drawn);
4556
+ work.resolved += 1;
4557
+ call = "resolve";
4558
+ G = kind.resolve(ctx);
4559
+ call = "face";
4560
+ const inside = insideOf(e, st, kind, G, ctx, slotCam, slotGrid);
4561
+ work.recorded += 1;
4562
+ call = "record";
4563
+ R = kind.record(G, inside === void 0 ? ctx : { ...ctx, inside });
4564
+ call = "landed";
4565
+ const landed = face2 ? 0 : locals?.get(kind.name)?.landed?.(e) ?? 0;
4566
+ if (landed !== st.landed) {
4567
+ st.landed = landed;
4568
+ deskLanded = true;
4569
+ }
4570
+ call = "rung";
4571
+ const rung = kind.rung === void 0 ? 0 : rungOf(kind, e, st, slotCam);
4572
+ call = "frame";
4573
+ const markRow = slot === "root" ? { entity: e, frame: kind.frame?.(G) ?? rectFrame(drawn), rect: { x0: r.cx - r.w / 2, y0: r.cy - r.h / 2, x1: r.cx + r.w / 2, y1: r.cy + r.h / 2 }, selected: st.selected, locked: st.locked, grabbed: st.grabbed, resizable: st.resizable } : null;
4574
+ st.geometry = G;
4575
+ st.record = R;
4576
+ st.stale = false;
4577
+ st.give = give;
4578
+ st.zoom = slotCam.zoom;
4579
+ st.rung = rung;
4580
+ st.face = face2;
4581
+ st.markRow = markRow;
3783
4582
  }
3784
- st.geometry = G;
3785
- st.record = R;
3786
- st.stale = false;
3787
- st.give = give;
3788
- st.zoom = slotCam.zoom;
3789
- st.rung = kind.rung === void 0 ? 0 : rungOf(kind, e, st, slotCam);
3790
- st.markRow = slot === "root" ? { entity: e, frame: kind.frame?.(G) ?? rectFrame(drawn), rect: { x0: r.cx - r.w / 2, y0: r.cy - r.h / 2, x1: r.cx + r.w / 2, y1: r.cy + r.h / 2 }, selected: st.selected, locked: st.locked, grabbed: st.grabbed, resizable: st.resizable } : null;
4583
+ } catch (err) {
4584
+ st.geometry = null;
4585
+ st.record = null;
4586
+ st.inside = null;
4587
+ st.markRow = null;
4588
+ st.stale = true;
4589
+ fault(kind, call, err, e);
4590
+ continue;
3791
4591
  }
3792
4592
  const at2 = rows2.length;
3793
- rows2.push({ entity: e, kind: kind.name, record: R, band: st.band });
4593
+ rows2.push({ entity: e, kind: st.kind.name, record: R, band: st.band });
3794
4594
  if (slot === "root" && st.markRow !== null) markRows.push(st.markRow);
3795
4595
  const iv = st.inside;
3796
4596
  if (iv !== null && portalsOn && depth < PORTAL_DEPTH && e !== skip && iv.presence > 0) {
3797
- cands.push({ at: at2, e, view: iv, grid: kind.insideGrid?.({ props: st.props, look: looks.get(kind.name) }, slotGrid) ?? slotGrid });
4597
+ let inner = slotGrid;
4598
+ try {
4599
+ inner = kind.insideGrid?.({ props: st.props, look: looks.get(kind.name) }, slotGrid) ?? slotGrid;
4600
+ } catch (err) {
4601
+ fault(kind, "insideGrid", err, e);
4602
+ continue;
4603
+ }
4604
+ cands.push({ at: at2, e, view: iv, grid: inner });
3798
4605
  }
3799
4606
  }
3800
4607
  cands.sort((a, b) => b.view.clip.hx * b.view.clip.hy - a.view.clip.hx * a.view.clip.hy);
@@ -3833,14 +4640,30 @@ function createDeskBuilder(world, opts) {
3833
4640
  else redressOut = { frame: redress.frame, from: redress.from, u };
3834
4641
  }
3835
4642
  }
3836
- const veilers = [];
3837
- if (locals !== void 0) {
3838
- for (const local of locals.values()) if (local.veils !== void 0) veilers.push(local);
4643
+ veilers.length = 0;
4644
+ veilerNames.length = 0;
4645
+ if (locals !== void 0) for (const name of locals.keys()) {
4646
+ const local = locals.get(name);
4647
+ if (local.veils !== void 0) {
4648
+ veilers.push(local);
4649
+ veilerNames.push(name);
4650
+ }
3839
4651
  }
3840
- const veiledNow = { has: (e) => {
3841
- for (let i = 0; i < veilers.length; i++) if (veilers[i].veils?.().has(e) === true) return true;
3842
- return false;
3843
- } };
4652
+ const veiledNow = {
4653
+ has: (e) => {
4654
+ for (let i = 0; i < veilers.length; i++) {
4655
+ const name = veilerNames[i];
4656
+ if (faults?.missing(name) === true) continue;
4657
+ try {
4658
+ if (veilers[i].veils?.().has(e) === true) return true;
4659
+ } catch (err) {
4660
+ if (faults === void 0) throw err;
4661
+ faults.strike(name, "veils", err, e);
4662
+ }
4663
+ }
4664
+ return false;
4665
+ }
4666
+ };
3844
4667
  const handRiders = /* @__PURE__ */ new Set();
3845
4668
  const heldNow = heldEntity(world);
3846
4669
  if (heldNow !== void 0) {
@@ -3859,7 +4682,7 @@ function createDeskBuilder(world, opts) {
3859
4682
  let heldBuild;
3860
4683
  if (hand !== null) {
3861
4684
  const hst = world.isAlive(hand.entity) ? stateOf(hand.entity) : void 0;
3862
- const binding = hst?.kind.open;
4685
+ const binding = hst === void 0 || drawerOf(hst.kind) === MISSING_OBJECT ? void 0 : hst.kind.open;
3863
4686
  if (hst === void 0 || binding === void 0) hand = null;
3864
4687
  else {
3865
4688
  const pin = bopts.hold;
@@ -3884,59 +4707,79 @@ function createDeskBuilder(world, opts) {
3884
4707
  }
3885
4708
  if (hand.dir < 0 && hand.p >= 1) hand = null;
3886
4709
  else if (hand.e > 0) {
3887
- const extentLocal = binding.extent({ rect: hst.rect, props: hst.props });
3888
- const angle = numberProp(hst.props, "angle", 0);
3889
- const ox = extentLocal.cx - hst.rect.cx;
3890
- const oy = extentLocal.cy - hst.rect.cy;
3891
- const ca = Math.cos(angle);
3892
- const sa = Math.sin(angle);
3893
- const extentWorld = { cx: hst.rect.cx + ca * ox - sa * oy, cy: hst.rect.cy + sa * ox + ca * oy, w: extentLocal.w, h: extentLocal.h };
3894
- const face2 = binding.spread === true ? binding.page?.({ entity: hand.entity, local: locals?.get(hst.kind.name) }) ?? 0 : 0;
3895
- const target = readingTarget(extentLocal, vpSize, binding.spread === true, face2);
3896
- const user = world.get(hand.entity, HeldView) ?? HELD_USER_REST;
3897
- const pose = heldPose(homePose(extentWorld, angle, cam), target, user, hand.e);
3898
- const { cam: heldCam, grow } = heldCamera(pose, hst.rect, extentLocal, cam.zoom, binding.pose === "eye", vpSize);
3899
- const heldView = { camX: heldCam.x, camY: heldCam.y, zoom: heldCam.zoom, width: vp.width, height: vp.height, dpr: vp.dpr };
3900
- const heldGrid = { ...frameGrid, mat: { ...frameGrid.mat, gobo: { ...frameGrid.mat.gobo, opacity: 0 } } };
3901
- const base = contextOf(hand.entity, hst, heldView, heldGrid, lampOf(heldGrid.mat.plane), false);
3902
- const props = typeof hst.props.angle === "number" ? { ...hst.props, angle: pose.angle } : hst.props;
3903
- const snap = pin !== void 0 || bopts.freeze === true;
3904
- const hctx = { ...base, props, held: { e: hand.e, open: openTarget, grow, snap } };
3905
- work.resolved += 1;
3906
- work.recorded += 1;
3907
- const G = hst.kind.resolve(hctx);
3908
- const R = hst.kind.record(G, hctx);
3909
- hst.geometry = G;
3910
- hst.record = R;
3911
- hst.stale = true;
3912
- hst.inside = null;
3913
- hst.slot = "root";
3914
- hst.seen = seq;
3915
- const openness = binding.openness?.(hctx);
3916
- hand.openness = openness ?? 0;
3917
- const settledNow = pin !== void 0 ? hand.e >= 1 : hand.dir > 0 && hand.p >= 1;
3918
- const coverMoving = openness !== void 0 && (openTarget ? openness < 1 - 1e-3 : openness > 1e-3);
3919
- if (pin === void 0 && (hand.dir < 0 || hand.p < 1 || coverMoving)) live = true;
3920
- const riders = [];
3921
- const typeId = world.get(hand.entity, PrefabId)?.id;
3922
- for (const rider of typeof typeId === "string" ? widgetTypeFor(world, typeId)?.riders?.(world, hand.entity) ?? [] : []) {
3923
- const rst = veiledNow.has(rider) ? void 0 : stateOf(rider);
3924
- if (rst === void 0) continue;
3925
- const rctx = contextOf(rider, rst, heldView, heldGrid, lampOf(heldGrid.mat.plane), false);
4710
+ try {
4711
+ const extentLocal = binding.extent({ rect: hst.rect, props: hst.props });
4712
+ const angle = numberProp(hst.props, "angle", 0);
4713
+ const ox = extentLocal.cx - hst.rect.cx;
4714
+ const oy = extentLocal.cy - hst.rect.cy;
4715
+ const ca = Math.cos(angle);
4716
+ const sa = Math.sin(angle);
4717
+ const extentWorld = { cx: hst.rect.cx + ca * ox - sa * oy, cy: hst.rect.cy + sa * ox + ca * oy, w: extentLocal.w, h: extentLocal.h };
4718
+ const face2 = binding.spread === true ? binding.page?.({ entity: hand.entity, local: locals?.get(hst.kind.name) }) ?? 0 : 0;
4719
+ const target = readingTarget(extentLocal, vpSize, binding.spread === true, face2, reserves);
4720
+ const user = world.get(hand.entity, HeldView) ?? HELD_USER_REST;
4721
+ const pose = heldPose(homePose(extentWorld, angle, cam), target, user, hand.e);
4722
+ const { cam: heldCam, grow } = heldCamera(pose, hst.rect, extentLocal, cam.zoom, binding.pose === "eye", vpSize);
4723
+ const heldView = { camX: heldCam.x, camY: heldCam.y, zoom: heldCam.zoom, width: vp.width, height: vp.height, dpr: vp.dpr };
4724
+ const heldGrid = { ...frameGrid, mat: { ...frameGrid.mat, gobo: { ...frameGrid.mat.gobo, opacity: 0 } } };
4725
+ const base = contextOf(hand.entity, hst, heldView, heldGrid, lampOf(heldGrid.mat.plane), false);
4726
+ const props = typeof hst.props.angle === "number" ? { ...hst.props, angle: pose.angle } : hst.props;
4727
+ const snap = pin !== void 0 || bopts.freeze === true;
4728
+ const hctx = { ...base, props, held: { e: hand.e, open: openTarget, grow, snap } };
3926
4729
  work.resolved += 1;
3927
4730
  work.recorded += 1;
3928
- riders.push({ kind: rst.kind.name, record: rst.kind.record(rst.kind.resolve(rctx), rctx), key: rider });
3929
- handRiders.add(rider);
4731
+ const G = hst.kind.resolve(hctx);
4732
+ const R = hst.kind.record(G, hctx);
4733
+ hst.geometry = G;
4734
+ hst.record = R;
4735
+ hst.stale = true;
4736
+ hst.inside = null;
4737
+ hst.slot = "root";
4738
+ hst.seen = seq;
4739
+ const openness = binding.openness?.(hctx);
4740
+ hand.openness = openness ?? 0;
4741
+ const settledNow = pin !== void 0 ? hand.e >= 1 : hand.dir > 0 && hand.p >= 1;
4742
+ const coverMoving = openness !== void 0 && (openTarget ? openness < 1 - 1e-3 : openness > 1e-3);
4743
+ if (pin === void 0 && (hand.dir < 0 || hand.p < 1 || coverMoving)) live = true;
4744
+ const riders = [];
4745
+ const typeId = world.get(hand.entity, PrefabId)?.id;
4746
+ for (const rider of typeof typeId === "string" ? widgetTypeFor(world, typeId)?.riders?.(world, hand.entity) ?? [] : []) {
4747
+ const rst = veiledNow.has(rider) ? void 0 : stateOf(rider);
4748
+ if (rst === void 0) continue;
4749
+ const rkind = drawerOf(rst.kind);
4750
+ const rctx = contextOf(rider, rst, heldView, heldGrid, lampOf(heldGrid.mat.plane), false);
4751
+ work.resolved += 1;
4752
+ work.recorded += 1;
4753
+ let record;
4754
+ try {
4755
+ record = rkind.record(rkind.resolve(rctx), rctx);
4756
+ } catch (err) {
4757
+ fault(rkind, "record", err, rider);
4758
+ continue;
4759
+ }
4760
+ riders.push({ kind: rst.kind.name, record, key: rider });
4761
+ handRiders.add(rider);
4762
+ }
4763
+ heldBuild = {
4764
+ entity: hand.entity,
4765
+ e: hand.e,
4766
+ settled: settledNow,
4767
+ landing: hand.dir < 0,
4768
+ frame: { ...heldFrame(pose, extentLocal, target.single, face2), settled: settledNow },
4769
+ inputs: { object: { kind: hst.kind.name, record: R, key: hand.entity }, ...riders.length > 0 ? { riders } : {}, view: heldView, grid: heldGrid, e: hand.e, ...heldFocus(hand.e, vpSize, theme) },
4770
+ deskSeq
4771
+ };
4772
+ } catch (err) {
4773
+ const held = hand?.entity;
4774
+ hst.geometry = null;
4775
+ hst.record = null;
4776
+ hst.inside = null;
4777
+ hst.stale = true;
4778
+ hand = null;
4779
+ heldBuild = void 0;
4780
+ handRiders.clear();
4781
+ fault(hst.kind, "record", err, held);
3930
4782
  }
3931
- heldBuild = {
3932
- entity: hand.entity,
3933
- e: hand.e,
3934
- settled: settledNow,
3935
- landing: hand.dir < 0,
3936
- frame: { ...heldFrame(pose, extentLocal, target.single, face2), settled: settledNow },
3937
- inputs: { object: { kind: hst.kind.name, record: R, key: hand.entity }, ...riders.length > 0 ? { riders } : {}, view: heldView, grid: heldGrid, e: hand.e, ...heldFocus(hand.e, vpSize, theme) },
3938
- deskSeq
3939
- };
3940
4783
  }
3941
4784
  }
3942
4785
  }
@@ -3947,7 +4790,7 @@ function createDeskBuilder(world, opts) {
3947
4790
  dirtyStates.clear();
3948
4791
  if (locals !== void 0) {
3949
4792
  const check = (e, st) => {
3950
- const v = !st.grabbed && locals.get(st.kind.name)?.lifted?.(e) === true;
4793
+ const v = !st.grabbed && liftedBy(st, e);
3951
4794
  if (v !== st.lifted) {
3952
4795
  st.lifted = v;
3953
4796
  orderDirty = true;
@@ -3975,7 +4818,7 @@ function createDeskBuilder(world, opts) {
3975
4818
  const st = stateOf(e);
3976
4819
  if (st === void 0) continue;
3977
4820
  st.active = true;
3978
- st.lifted = !st.grabbed && locals?.get(st.kind.name)?.lifted?.(e) === true;
4821
+ st.lifted = !st.grabbed && liftedBy(st, e);
3979
4822
  (st.grabbed || st.lifted ? tiers[1] : tiers[0]).push(e);
3980
4823
  }
3981
4824
  });
@@ -4047,8 +4890,17 @@ function createDeskBuilder(world, opts) {
4047
4890
  const ctx = { entity: e, rect: g.rect, props: g.props, flux: { ...g.flux, ring: 0, fade: 1 - g.del }, look: looks.get(g.kind.name), theme, lamp: rootLamp, view: rootView, grid: frameGrid, dt, ...g.asset !== void 0 ? { asset: g.asset } : {}, ...local !== void 0 ? { local } : {} };
4048
4891
  work.resolved += 1;
4049
4892
  work.recorded += 1;
4050
- const G = g.kind.resolve(ctx);
4051
- const row = { entity: void 0, kind: g.kind.name, record: g.kind.record(G, ctx), band: g.band, ghostOf: e };
4893
+ const gkind = drawerOf(g.kind);
4894
+ let record;
4895
+ try {
4896
+ record = gkind.record(gkind.resolve(ctx), ctx);
4897
+ } catch (err) {
4898
+ ghosts.delete(e);
4899
+ forget(g.kind, e);
4900
+ fault(gkind, "record", err, e);
4901
+ continue;
4902
+ }
4903
+ const row = { entity: void 0, kind: g.kind.name, record, band: g.band, ghostOf: e };
4052
4904
  let at2 = g.next === void 0 ? -1 : rows.findIndex((q) => q.entity === g.next);
4053
4905
  if (at2 < 0) {
4054
4906
  at2 = rows.length;
@@ -4135,7 +4987,17 @@ function createDeskBuilder(world, opts) {
4135
4987
  }
4136
4988
  const objects = rows.map((r) => ({ kind: r.kind, record: r.record, key: r.entity !== void 0 ? r.entity : -r.ghostOf }));
4137
4989
  const veiledSet = /* @__PURE__ */ new Set();
4138
- for (const local of locals?.values() ?? []) for (const e of local.veils?.() ?? []) veiledSet.add(e);
4990
+ for (let i = 0; i < veilers.length; i++) {
4991
+ const name = veilerNames[i];
4992
+ const local = veilers[i];
4993
+ if (locals?.get(name) !== local) continue;
4994
+ try {
4995
+ for (const e of local.veils?.() ?? []) veiledSet.add(e);
4996
+ } catch (err) {
4997
+ if (faults === void 0) throw err;
4998
+ faults.strike(name, "veils", err);
4999
+ }
5000
+ }
4139
5001
  veiledList = veiledSet;
4140
5002
  const ruler2 = frameGrid.mat.ruler;
4141
5003
  const marked = marks.frame({ rows: markRows, cam, view: vp, dt, night: theme.name === "dark", rulers: ruler2.on ? { margin: ruler2.margin, band: ruler2.band } : null });
@@ -4225,7 +5087,7 @@ function createDeskBuilder(world, opts) {
4225
5087
  const st = states.get(e);
4226
5088
  if (st === void 0) continue;
4227
5089
  if (st.insert) {
4228
- landing.push({ type: st.widget.type, rect: st.rect, lift: st.lift, liftV: st.liftV });
5090
+ landing.push({ type: st.widget?.type ?? "", rect: st.rect, lift: st.lift, liftV: st.liftV });
4229
5091
  forget(st.kind, e);
4230
5092
  } else {
4231
5093
  ghostOf(e, st);
@@ -4332,8 +5194,25 @@ function createDeskBuilder(world, opts) {
4332
5194
  heldPart(e, x, y) {
4333
5195
  const w = heldToWorld(e, x, y);
4334
5196
  const st = states.get(e);
4335
- if (w === void 0 || st === void 0 || st.geometry === null) return null;
4336
- return st.kind.hit(st.geometry, w[0], w[1]);
5197
+ if (w === void 0 || st === void 0 || st.geometry === null || st.face) return null;
5198
+ try {
5199
+ return st.kind.hit(st.geometry, w[0], w[1]);
5200
+ } catch (err) {
5201
+ fault(st.kind, "hit", err, e);
5202
+ return null;
5203
+ }
5204
+ },
5205
+ hitAt(e, wx, wy) {
5206
+ const st = states.get(e);
5207
+ if (st === void 0 || st.geometry === null) return void 0;
5208
+ if (st.face) return MISSING_OBJECT.hit(st.geometry, wx, wy);
5209
+ if (drawerOf(st.kind) === MISSING_OBJECT) return void 0;
5210
+ try {
5211
+ return st.kind.hit(st.geometry, wx, wy);
5212
+ } catch (err) {
5213
+ fault(st.kind, "hit", err, e);
5214
+ return null;
5215
+ }
4337
5216
  },
4338
5217
  stats: () => stats,
4339
5218
  dispose() {
@@ -4396,6 +5275,14 @@ function createRasterBudget(cap, floor = Math.floor(cap * BUDGET_FLOOR)) {
4396
5275
  const ent = entries.get(k);
4397
5276
  if (ent !== void 0) drop(k, ent);
4398
5277
  },
5278
+ forget(owner) {
5279
+ let bytes = 0;
5280
+ for (const [k, ent] of [...entries]) if (ent.owner === owner) {
5281
+ drop(k, ent);
5282
+ bytes += ent.bytes;
5283
+ }
5284
+ return bytes;
5285
+ },
4399
5286
  trim(keep2) {
4400
5287
  const limit = room();
4401
5288
  if (cached <= limit) return 0;
@@ -4551,6 +5438,10 @@ function createPickSource(builder, opts = {}) {
4551
5438
  if (G === void 0 || kind === void 0) return void 0;
4552
5439
  const hp = builder.heldPoint?.(e, wx, wy);
4553
5440
  const [x, y] = hp ?? [wx, wy];
5441
+ if (builder.hitAt !== void 0) {
5442
+ const part = builder.hitAt(e, x, y);
5443
+ return part === void 0 ? void 0 : part ?? "outside";
5444
+ }
4554
5445
  return kind.hit(G, x, y) ?? "outside";
4555
5446
  },
4556
5447
  live: () => builder.live() || opts.moving?.() === true
@@ -4559,7 +5450,8 @@ function createPickSource(builder, opts = {}) {
4559
5450
 
4560
5451
  // ../desk/src/tray/flux.ts
4561
5452
  var EPS2 = 1e-4;
4562
- function createTrayFlux() {
5453
+ function createTrayFlux(opts = {}) {
5454
+ const foot = opts.foot ?? 0;
4563
5455
  let facts = null;
4564
5456
  let p = 0;
4565
5457
  let from = 0;
@@ -4633,8 +5525,8 @@ function createTrayFlux() {
4633
5525
  const pp = P?.p ?? p;
4634
5526
  const pb = P?.band ?? shown;
4635
5527
  const rect = drawerRect(vw, vh, pp);
4636
- drawn = { x: rect.x, y: rect.y, w: rect.w, h: rect.h, p: pp, max: scrollRange(vw, vh, f.bottom ?? 0), pitch: DRAWER.pitch, scroll: f.scroll + pb, face: rect.h, head: DRAWER.arris + DRAWER.header };
4637
- return { p: pp, scroll: f.scroll + pb };
5528
+ drawn = { x: rect.x, y: rect.y, w: rect.w, h: rect.h, p: pp, max: scrollRange(vw, vh, f.bottom ?? 0, foot), pitch: DRAWER.pitch, scroll: f.scroll + pb, face: rect.h, head: DRAWER.arris + DRAWER.header, ...foot > 0 ? { foot } : {} };
5529
+ return { p: pp, scroll: f.scroll + pb, ...foot > 0 ? { foot } : {} };
4638
5530
  },
4639
5531
  live: () => moving && pinned === null,
4640
5532
  frame: () => drawn,
@@ -4652,12 +5544,15 @@ var localPointersQ2 = defineQuery([Pointer, LocalPointer, PointerScreen]);
4652
5544
  var trayQ = defineQuery([Tray]);
4653
5545
  var insertQ = defineQuery([InsertGhost, Position, Size]);
4654
5546
  function createDeskReflector(opts) {
4655
- const { world, builder, ambient } = opts;
5547
+ const { world, builder, ambient, faults } = opts;
4656
5548
  const maxDpr = opts.maxDpr ?? 2;
4657
5549
  let theme = opts.theme;
4658
5550
  let palette = opts.palette;
4659
5551
  let grid = opts.grid ?? DEFAULT_GRID;
4660
- let looks = looksOf(opts.kinds, palette, theme);
5552
+ const lookKinds = () => faults === void 0 || faults.size === 0 ? opts.kinds : opts.kinds.filter((k) => !faults.missing(k.name));
5553
+ const mountedKinds = new Set(opts.kinds.map((k) => k.name));
5554
+ const kindOn = (kind) => kind === void 0 || faults === void 0 || mountedKinds.has(kind.name) ? kind : void 0;
5555
+ let looks = looksOf(lookKinds(), palette, theme);
4661
5556
  let dirty = true;
4662
5557
  let disposed = false;
4663
5558
  let redraws = 0;
@@ -4682,7 +5577,7 @@ function createDeskReflector(opts) {
4682
5577
  let gridGen = 0;
4683
5578
  let pinGen = 0;
4684
5579
  const wakes = { world: 0, removed: 0, reset: 0, order: 0, hover: 0, marks: 0, camera: 0, viewport: 0, nav: 0, theme: 0, grid: 0, pin: 0, ambient: 0, live: 0, ink: 0, tray: 0 };
4685
- const tray = createTrayFlux();
5580
+ const tray = createTrayFlux(opts.tray?.foot !== void 0 ? { foot: opts.tray.foot } : {});
4686
5581
  let drawnSpecimens = [];
4687
5582
  const carry2 = createTrayCarry();
4688
5583
  let drawnCarried = [];
@@ -4810,11 +5705,11 @@ function createDeskReflector(opts) {
4810
5705
  let trayed = tray.step(now, vp.w, vp.h);
4811
5706
  drawnSpecimens = [];
4812
5707
  if (trayed !== void 0 && trayed.p > 0 && tray.pinned()?.bare !== true) {
4813
- const specimens = readSpecimens(w, te, opts.locals);
5708
+ const specimens = readSpecimens(w, te, opts.locals, kindOn);
4814
5709
  if (specimens.length > 0) {
4815
5710
  ground.warmTray(specimens.map((q) => [q.type, q.kind.name]), trayLanded);
4816
5711
  for (const q of specimens) if (q.local !== void 0) faced.set(q.key, q.local);
4817
- drawnSpecimens = specimenFrames(specimens, { rect: drawerRect(vp.w, vp.h, trayed.p), scroll: trayed.scroll }, { view: { width: vp.w, height: vp.h, dpr }, theme, grid, looks, lift: (t) => tray.lift(t) });
5712
+ drawnSpecimens = specimenFrames(specimens, { rect: drawerRect(vp.w, vp.h, trayed.p), scroll: trayed.scroll, ...trayed.foot !== void 0 ? { foot: trayed.foot } : {} }, { view: { width: vp.w, height: vp.h, dpr }, theme, grid, looks, lift: (t) => tray.lift(t), ...faults !== void 0 ? { faults } : {} });
4818
5713
  trayed = { ...trayed, specimens: drawnSpecimens };
4819
5714
  }
4820
5715
  }
@@ -4846,11 +5741,11 @@ function createDeskReflector(opts) {
4846
5741
  drawnCarried = [];
4847
5742
  if (trayed !== void 0 && poses.length > 0) {
4848
5743
  const L = lampOf(built.grid.mat.plane);
4849
- const env = { view: { width: vp.w, height: vp.h, dpr }, theme, grid, looks };
5744
+ const env = { view: { width: vp.w, height: vp.h, dpr }, theme, grid, looks, ...faults !== void 0 ? { faults } : {} };
4850
5745
  const made = [];
4851
5746
  for (const pose of poses) {
4852
5747
  const widget = widgetTypeFor(w, pose.type);
4853
- const kind = objectKindOf(widget);
5748
+ const kind = kindOn(objectKindOf(widget));
4854
5749
  if (widget === void 0 || kind === void 0) continue;
4855
5750
  const local = opts.locals?.get(kind.name);
4856
5751
  if (pose.ghost !== void 0) {
@@ -4859,7 +5754,8 @@ function createDeskReflector(opts) {
4859
5754
  const size = w.get(g, Size);
4860
5755
  if (at2 === void 0 || size === void 0) continue;
4861
5756
  const r = rectOf(at2, size);
4862
- made.push(carriedFrame(pose, { kind, rect: { x: r.cx - r.w / 2, y: r.cy - r.h / 2, w: r.w, h: r.h }, props: propsOf(w, g, widget), key: g, lamp: L, ...local !== void 0 ? { local } : {} }, env));
5757
+ const frame2 = carriedFrame(pose, { kind, rect: { x: r.cx - r.w / 2, y: r.cy - r.h / 2, w: r.w, h: r.h }, props: propsOf(w, g, widget), key: g, lamp: L, ...local !== void 0 ? { local } : {} }, env);
5758
+ if (frame2 !== void 0) made.push(frame2);
4863
5759
  continue;
4864
5760
  }
4865
5761
  const n = widget.defaultSize;
@@ -4868,10 +5764,11 @@ function createDeskReflector(opts) {
4868
5764
  const cy = cam.y + pose.py / cam.zoom - (pose.v - 0.5) * n.h;
4869
5765
  const key = -(1073741824 + pose.id);
4870
5766
  if (copyLocal !== void 0) faced.set(key, copyLocal);
4871
- made.push(carriedFrame(pose, { kind, rect: { x: -n.w / 2, y: -n.h / 2, w: n.w, h: n.h }, props: takenProps(widget), key, lamp: { x: L.x - cx, y: L.y - cy, h: L.h }, ...copyLocal !== void 0 ? { local: copyLocal } : {} }, env));
5767
+ const frame = carriedFrame(pose, { kind, rect: { x: -n.w / 2, y: -n.h / 2, w: n.w, h: n.h }, props: takenProps(widget), key, lamp: { x: L.x - cx, y: L.y - cy, h: L.h }, ...copyLocal !== void 0 ? { local: copyLocal } : {} }, env);
5768
+ if (frame !== void 0) made.push(frame);
4872
5769
  }
4873
5770
  ground.warmTray(carry2.types().flatMap((t) => {
4874
- const k = objectKindOf(widgetTypeFor(w, t));
5771
+ const k = kindOn(objectKindOf(widgetTypeFor(w, t)));
4875
5772
  return k === void 0 ? [] : [[carrySlot(t), k.name]];
4876
5773
  }), trayLanded);
4877
5774
  drawnCarried = made;
@@ -4935,7 +5832,7 @@ function createDeskReflector(opts) {
4935
5832
  setTheme(t, p) {
4936
5833
  theme = t;
4937
5834
  if (p !== void 0) palette = p;
4938
- looks = looksOf(opts.kinds, palette, theme);
5835
+ looks = looksOf(lookKinds(), palette, theme);
4939
5836
  dirty = true;
4940
5837
  wakes.theme += 1;
4941
5838
  themeGen += 1;
@@ -4987,14 +5884,14 @@ function createDeskReflector(opts) {
4987
5884
  }
4988
5885
  };
4989
5886
  }
4990
- function readSpecimens(w, tray, locals) {
5887
+ function readSpecimens(w, tray, locals, on) {
4991
5888
  if (tray === void 0) return [];
4992
5889
  const out = [];
4993
5890
  for (const e of specimensOf(w, tray)) {
4994
5891
  const type = w.get(e, PrefabId)?.id;
4995
5892
  if (typeof type !== "string") continue;
4996
5893
  const widget = widgetTypeFor(w, type);
4997
- const kind = objectKindOf(widget);
5894
+ const kind = on(objectKindOf(widget));
4998
5895
  const entry = widget?.tray;
4999
5896
  const at2 = w.get(e, Position);
5000
5897
  const size = w.get(e, Size);
@@ -5323,6 +6220,7 @@ function createDeskEditor(opts) {
5323
6220
  },
5324
6221
  placement: () => placed,
5325
6222
  dispose() {
6223
+ end();
5326
6224
  if (idle !== void 0) clearTimeout(idle);
5327
6225
  lent = void 0;
5328
6226
  el.removeEventListener("input", onInput);
@@ -6282,20 +7180,56 @@ function kindsOf(types, extra) {
6282
7180
  for (const k of extra) if (!byName.has(k.name)) byName.set(k.name, k);
6283
7181
  return { kinds: [...byName.values()], objectKinds };
6284
7182
  }
7183
+ function bitmapOf(c) {
7184
+ const n = c.width * c.height * 4;
7185
+ const rgba = new Uint8ClampedArray(n);
7186
+ const b = c.bytes;
7187
+ const bgra = c.format.startsWith("bgra");
7188
+ for (let i = 0; i < n; i += 4) {
7189
+ rgba[i] = b[bgra ? i + 2 : i];
7190
+ rgba[i + 1] = b[i + 1];
7191
+ rgba[i + 2] = b[bgra ? i : i + 2];
7192
+ rgba[i + 3] = 255;
7193
+ }
7194
+ return createImageBitmap(new ImageData(rgba, c.width, c.height), { premultiplyAlpha: "none", colorSpaceConversion: "none" });
7195
+ }
6285
7196
  var toldAwake = /* @__PURE__ */ new Set();
6286
7197
  function tellAwake(kind) {
6287
7198
  if (toldAwake.has(kind)) return;
6288
7199
  toldAwake.add(kind);
6289
7200
  console.warn(`[ice] desk: the kind "${kind}" declares a local \`tick\` and no \`due\` \u2014 it is due every frame, so the desk never sleeps. Declare \`KindLocal.due(now)\`: now while it moves, a later time, or Infinity until a fact, an input or \`KindHost.wake\` moves it (design-016 K7a).`);
6290
7201
  }
7202
+ var toldAnchor = /* @__PURE__ */ new Set();
7203
+ function tellAnchor(kind, call, err) {
7204
+ const key = `${call} ${kind}`;
7205
+ if (toldAnchor.has(key)) return;
7206
+ toldAnchor.add(key);
7207
+ const kept = call === "readout" ? "no word (petition I22)" : "no swatches (petition I24)";
7208
+ console.error(`[ice] desk: the kind "${kind}"'s \`open.${call}\` threw \u2014 the anchor carries its held tools and ${kept}; said once a page`, err);
7209
+ }
7210
+ function hostNumber(name, v) {
7211
+ if (v === void 0) return void 0;
7212
+ if (typeof v !== "number" || !Number.isFinite(v) || v < 0) throw new Error(`[ice] deskLayer: \`${name}\` is ${String(v)} \u2014 a finite number \u2265 0 (CSS px, or ms for a time)`);
7213
+ return v;
7214
+ }
7215
+ function holdOf(hold) {
7216
+ const top = hostNumber("hold.top", hold?.top);
7217
+ const band2 = hostNumber("hold.band", hold?.band);
7218
+ return { reserves: top === void 0 && band2 === void 0 ? void 0 : { top: top ?? HOLD.top, band: band2 ?? HOLD.band }, travelMs: hostNumber("hold.travelMs", hold?.travelMs) };
7219
+ }
6291
7220
  function deskLayer(opts) {
6292
7221
  return (ctx) => {
7222
+ const mountedAt = performance.now();
7223
+ const boot = {};
6293
7224
  const { host, world } = ctx;
6294
7225
  const doc = host.container.ownerDocument;
6295
7226
  const view = doc.defaultView;
6296
7227
  const types = /* @__PURE__ */ new Set([...(ctx.catalog?.widgetTypes() ?? []).filter((t) => t.object !== void 0), ...opts.objects ?? []]);
6297
7228
  const { kinds, objectKinds } = kindsOf([...types], opts.kinds ?? []);
6298
7229
  looksOf(objectKinds, opts.palette, opts.theme);
7230
+ const hold = holdOf(opts.hold);
7231
+ const foot = hostNumber("tray.foot", opts.tray?.foot);
7232
+ const cursors = opts.cursors !== false;
6299
7233
  const canvas = doc.createElement("canvas");
6300
7234
  canvas.style.position = "absolute";
6301
7235
  canvas.style.left = "0";
@@ -6315,9 +7249,11 @@ function deskLayer(opts) {
6315
7249
  let profiler;
6316
7250
  let status = { state: "pending" };
6317
7251
  const statusHeard = /* @__PURE__ */ new Set();
7252
+ const faults = createKindFaults();
6318
7253
  const setStatus = (next) => {
6319
- status = next;
6320
- for (const l of [...statusHeard]) l(next);
7254
+ const { faults: _was, ...rest } = next;
7255
+ status = faults.size > 0 ? { ...rest, faults: faults.list() } : rest;
7256
+ for (const l of [...statusHeard]) l(status);
6321
7257
  };
6322
7258
  let disposed = false;
6323
7259
  let ended = false;
@@ -6365,21 +7301,51 @@ function deskLayer(opts) {
6365
7301
  }
6366
7302
  const keeps = (owner, key) => locals.get(owner)?.keeps?.(key) ?? false;
6367
7303
  const readMarquee = ctx.readMarquee;
6368
- const builder = createDeskBuilder(world, { objects: [...types], locals, ...opts.springs !== void 0 ? { springs: opts.springs } : {}, ...readMarquee !== void 0 ? { marquee: readMarquee } : {}, ...ctx.spatial !== void 0 ? { spatial: ctx.spatial } : {} });
7304
+ const builder = createDeskBuilder(world, { objects: [...types], locals, faults, ...opts.springs !== void 0 ? { springs: opts.springs } : {}, ...readMarquee !== void 0 ? { marquee: readMarquee } : {}, ...ctx.spatial !== void 0 ? { spatial: ctx.spatial } : {}, ...hold.reserves !== void 0 ? { hold: hold.reserves } : {} });
6369
7305
  const typeNamed = new Map([...types].map((t) => [t.type, t]));
6370
7306
  const menuOf = (e) => {
6371
7307
  const id3 = world.isAlive(e) ? world.get(e, PrefabId)?.id : void 0;
6372
7308
  return typeof id3 === "string" ? typeNamed.get(id3)?.menu ?? [] : [];
6373
7309
  };
7310
+ const propsOf2 = (e) => {
7311
+ const id3 = world.isAlive(e) ? world.get(e, PrefabId)?.id : void 0;
7312
+ const props = {};
7313
+ for (const g of (typeof id3 === "string" ? typeNamed.get(id3)?.groups : void 0) ?? []) Object.assign(props, world.get(e, g.component) ?? {});
7314
+ return props;
7315
+ };
7316
+ const readoutOf = (kind, e) => {
7317
+ const r = kind?.open?.readout;
7318
+ if (r === void 0 || kind === void 0) return void 0;
7319
+ let word = r;
7320
+ if (typeof r === "function") {
7321
+ try {
7322
+ word = r({ world, entity: e, props: () => propsOf2(e), local: locals.get(kind.name) });
7323
+ } catch (err) {
7324
+ tellAnchor(kind.name, "readout", err);
7325
+ return void 0;
7326
+ }
7327
+ }
7328
+ return typeof word === "string" && word.length > 0 ? word : void 0;
7329
+ };
7330
+ const swatchesOf = (kind) => {
7331
+ if (kind?.open?.swatches === void 0) return {};
7332
+ try {
7333
+ return kind.open.swatches(compose2.look(kind.name));
7334
+ } catch (err) {
7335
+ tellAnchor(kind.name, "swatches", err);
7336
+ return {};
7337
+ }
7338
+ };
6374
7339
  const anchorOf = () => {
6375
7340
  const a = builder.anchor();
6376
7341
  if (trayOpen(world)) return { ...a, box: null, count: 0 };
6377
7342
  const h = builder.hand();
6378
7343
  if (h === void 0) return withKindActs(a, selectedEntities(world), menuOf);
6379
7344
  const kind = builder.kindOf(h.entity);
6380
- const swatches = kind?.open?.swatches?.(compose2.look(kind.name)) ?? {};
7345
+ const swatches = swatchesOf(kind);
6381
7346
  const active = world.isAlive(h.entity) ? world.get(h.entity, HeldTool)?.id ?? "" : "";
6382
- return { ...a, held: { tools: heldSlots(kind?.open?.tools ?? [], swatches), active, landing: h.landing, settled: h.settled } };
7347
+ const readout = readoutOf(kind, h.entity);
7348
+ return { ...a, held: { tools: heldSlots(kind?.open?.tools ?? [], swatches), active, landing: h.landing, settled: h.settled, ...hold.travelMs !== void 0 ? { travelMs: hold.travelMs } : {}, ...readout !== void 0 ? { readout } : {} } };
6383
7349
  };
6384
7350
  const listeners = /* @__PURE__ */ new Set();
6385
7351
  let published = "";
@@ -6413,15 +7379,28 @@ function deskLayer(opts) {
6413
7379
  trayPublished = key;
6414
7380
  for (const l of [...trayListeners]) l();
6415
7381
  };
7382
+ let presenting = false;
7383
+ const firstFrame = () => {
7384
+ if (presenting) return;
7385
+ presenting = true;
7386
+ const queue = drawDevice?.queue;
7387
+ if (typeof queue?.onSubmittedWorkDone !== "function") return;
7388
+ queue.onSubmittedWorkDone().then(() => {
7389
+ boot.presented ??= performance.now() - mountedAt;
7390
+ }, () => {
7391
+ });
7392
+ };
6416
7393
  const compose2 = createDeskReflector({
6417
7394
  onFrame: () => {
6418
7395
  publish();
6419
7396
  publishTray();
7397
+ firstFrame();
6420
7398
  },
6421
7399
  world,
6422
7400
  builder,
6423
7401
  kinds: objectKinds,
6424
7402
  ambient,
7403
+ faults,
6425
7404
  ground: () => ground,
6426
7405
  attach: { resize: (w, h) => {
6427
7406
  if (canvas.width !== w || canvas.height !== h) {
@@ -6434,6 +7413,7 @@ function deskLayer(opts) {
6434
7413
  grid,
6435
7414
  locals,
6436
7415
  ...opts.maxDpr !== void 0 ? { maxDpr: opts.maxDpr } : {},
7416
+ ...foot !== void 0 ? { tray: { foot } } : {},
6437
7417
  ...opts.name !== void 0 ? { name: opts.name } : {},
6438
7418
  // a frame asked for outside the flush — a pin, an ink landing, the ground arriving, a theme: a sleeping loop wakes (K7a)
6439
7419
  onWake: (reason) => frame?.wake(`desk:${reason}`)
@@ -6441,6 +7421,8 @@ function deskLayer(opts) {
6441
7421
  motionQuery?.addEventListener("change", syncMotion);
6442
7422
  const docs = opts.docs ?? NO_DOCS;
6443
7423
  const drivers = /* @__PURE__ */ new Map();
7424
+ const driverKinds = /* @__PURE__ */ new Map();
7425
+ const parked = [];
6444
7426
  const ofKind = (e, name) => builder.kindOf(e)?.name === name && !world.has(e, InsertGhost);
6445
7427
  const kindNamed = (name) => objectKinds.some((k) => k.name === name) ? (e) => ofKind(e, name) : void 0;
6446
7428
  const providing = (key) => [...types].some((t) => t.provides.includes(key)) ? (e) => {
@@ -6465,7 +7447,10 @@ function deskLayer(opts) {
6465
7447
  refused: (e) => builder.meetTape(e),
6466
7448
  wake: () => compose2.wake("ink")
6467
7449
  });
6468
- if (d !== void 0) drivers.set(t.type, d);
7450
+ if (d !== void 0) {
7451
+ drivers.set(t.type, d);
7452
+ driverKinds.set(t.type, k.name);
7453
+ }
6469
7454
  }
6470
7455
  const driver = (type) => drivers.get(type);
6471
7456
  const textParts = [];
@@ -6516,11 +7501,17 @@ function deskLayer(opts) {
6516
7501
  restless.clear();
6517
7502
  for (const [name, local] of locals) {
6518
7503
  if (local.tick === void 0) continue;
6519
- if (timeAlone && !woken.has(name) && (local.due?.(now) ?? now) > now) continue;
6520
- kindTicks[name] = (kindTicks[name] ?? 0) + 1;
6521
- if (local.tick(now)) {
6522
- want = true;
6523
- restless.add(name);
7504
+ let call = "due";
7505
+ try {
7506
+ if (timeAlone && !woken.has(name) && (local.due?.(now) ?? now) > now) continue;
7507
+ kindTicks[name] = (kindTicks[name] ?? 0) + 1;
7508
+ call = "tick";
7509
+ if (local.tick(now)) {
7510
+ want = true;
7511
+ restless.add(name);
7512
+ }
7513
+ } catch (err) {
7514
+ faults.strike(name, call, err);
6524
7515
  }
6525
7516
  }
6526
7517
  woken.clear();
@@ -6545,11 +7536,22 @@ function deskLayer(opts) {
6545
7536
  profiler?.flushed(spent);
6546
7537
  }
6547
7538
  };
7539
+ const dueOf = (local, now) => {
7540
+ try {
7541
+ return local.due?.(now) ?? now;
7542
+ } catch (err) {
7543
+ for (const [name, l] of locals) if (l === local) {
7544
+ faults.strike(name, "due", err);
7545
+ break;
7546
+ }
7547
+ return Number.POSITIVE_INFINITY;
7548
+ }
7549
+ };
6548
7550
  const deskDue = (now) => {
6549
7551
  let t = following || woken.size > 0 ? now : compose2.due(now);
6550
7552
  for (const local of locals.values()) {
6551
7553
  if (local.tick === void 0) continue;
6552
- const d = local.due?.(now) ?? now;
7554
+ const d = dueOf(local, now);
6553
7555
  if (d < t) t = d;
6554
7556
  }
6555
7557
  const tray = ground?.trayIdleAt() ?? Number.POSITIVE_INFINITY;
@@ -6559,6 +7561,31 @@ function deskLayer(opts) {
6559
7561
  };
6560
7562
  const stopWake = frame?.wakeWhen("desk", deskDue);
6561
7563
  const stopText = opts.text?.onVersion?.(() => frame?.wake("desk:text"));
7564
+ const retire = (name) => {
7565
+ const local = locals.get(name);
7566
+ locals.delete(name);
7567
+ woken.delete(name);
7568
+ for (const [type, kind] of driverKinds) {
7569
+ const d = drivers.get(type);
7570
+ if (kind === name && d !== void 0) {
7571
+ drivers.delete(type);
7572
+ parked.push(d);
7573
+ }
7574
+ }
7575
+ rasters.clear(name);
7576
+ budget.forget(name);
7577
+ try {
7578
+ local?.dispose?.();
7579
+ } catch (err) {
7580
+ console.error(`[ice] desk: the missing kind "${name}"'s desk state threw in its dispose`, err);
7581
+ }
7582
+ ground?.quarantine(name);
7583
+ compose2.wake("ink");
7584
+ };
7585
+ faults.subscribe((f) => {
7586
+ retire(f.kind);
7587
+ if (status.state !== "pending") setStatus(status);
7588
+ });
6562
7589
  const pick = createPickSource(builder, { moving: () => moving });
6563
7590
  const framePick = ctx.framePick;
6564
7591
  if (framePick !== void 0) framePick.current = pick;
@@ -6605,27 +7632,31 @@ function deskLayer(opts) {
6605
7632
  };
6606
7633
  const shared = ctx.gpu;
6607
7634
  const device = shared !== void 0 ? Promise.resolve(adopt(shared.adapter, shared.device, events)) : gpu === void 0 ? Promise.reject(new Error("WebGPU is unavailable here (no navigator.gpu)")) : acquire({ gpu, label: "desk", ...events });
6608
- const boot = device.then(async (g) => {
7635
+ const booted = device.then(async (g) => {
6609
7636
  if (disposed) {
6610
7637
  if (shared === void 0) g.device.destroy();
6611
7638
  throw new Error("disposed before the device arrived");
6612
7639
  }
7640
+ boot.device = performance.now() - mountedAt;
6613
7641
  drawDevice = g.device;
6614
7642
  if (shared === void 0) ownDevice = g.device;
6615
7643
  if (opts.gpuLedger === true) ledger = instrumentMemory(g.device);
6616
7644
  opts.onDevice?.(g.device);
6617
- const made = await Ground.create({ device: g.device, surface: surface(g.device, canvas), mat: matShaders(shaderText(MAT_SHADER_FILES)), kinds, marks: marksShaders(shaderText(MARKS_SHADER_FILES)), hold: holdShaders(shaderText(HOLD_SHADER_FILES)), tray: trayShaders(shaderText) });
7645
+ const made = await Ground.create({ device: g.device, surface: surface(g.device, canvas), mat: matShaders(shaderText(MAT_SHADER_FILES)), kinds, marks: marksShaders(shaderText(MARKS_SHADER_FILES)), hold: holdShaders(shaderText(HOLD_SHADER_FILES)), tray: trayShaders(shaderText), ...events.onError !== void 0 ? { onError: events.onError } : {} });
6618
7646
  if (disposed || ended) {
6619
7647
  made.dispose();
6620
7648
  return;
6621
7649
  }
7650
+ boot.compiled = performance.now() - mountedAt;
6622
7651
  made.mat.setNoise(blueNoise());
6623
7652
  made.grid = grid;
7653
+ for (const f of made.faults()) faults.refuse(f.kind, f.reason);
7654
+ for (const f of faults.list()) made.quarantine(f.kind);
6624
7655
  ground = made;
6625
7656
  if (status.state === "pending") setStatus({ state: "ready" });
6626
7657
  compose2.ready();
6627
7658
  });
6628
- boot.catch((e) => {
7659
+ booted.catch((e) => {
6629
7660
  if (!disposed) fail("no desk \u2014 the adapter, the device or the pipelines were refused", e);
6630
7661
  });
6631
7662
  const setGrid = (next) => {
@@ -6633,8 +7664,42 @@ function deskLayer(opts) {
6633
7664
  if (ground !== null) ground.grid = next;
6634
7665
  compose2.configureGrid(next);
6635
7666
  };
7667
+ let capturing = null;
7668
+ let captures = Promise.resolve();
7669
+ const captureKey = (o) => JSON.stringify([o.scale ?? 1, o.rect === void 0 ? null : [o.rect.x, o.rect.y, o.rect.width, o.rect.height]]);
7670
+ const capture = (opts2 = {}) => {
7671
+ checkCapture(opts2);
7672
+ const key = captureKey(opts2);
7673
+ const asked2 = compose2.lastInputs();
7674
+ const same = capturing;
7675
+ if (same !== null && same.key === key && same.inputs === asked2) return same.promise;
7676
+ const run = captures.then(async () => {
7677
+ const g = ground;
7678
+ const inputs = compose2.lastInputs();
7679
+ if (disposed || ended || g === null || status.state !== "ready" || inputs === null) return void 0;
7680
+ const bytes = await g.capture(inputs, opts2);
7681
+ return bytes === void 0 ? void 0 : await bitmapOf(bytes);
7682
+ }).finally(() => {
7683
+ if (capturing?.promise === run) capturing = null;
7684
+ });
7685
+ capturing = { key, inputs: asked2, promise: run };
7686
+ captures = run.catch(() => void 0);
7687
+ return run;
7688
+ };
7689
+ const pickAt = ctx.pickAt;
7690
+ const pickPoint = (point) => {
7691
+ if (typeof point?.x !== "number" || typeof point.y !== "number" || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
7692
+ throw new Error(`[ice] desk: pick \u2014 the point is ${JSON.stringify(point) ?? String(point)}: { x, y } in CSS px of the view, two finite numbers`);
7693
+ }
7694
+ if (pickAt === void 0 || ground === null || compose2.lastInputs() === null || heldEntity(world) !== void 0) return null;
7695
+ const hit = pickAt(point.x, point.y);
7696
+ const type = hit === void 0 ? void 0 : world.get(hit.entity, PrefabId)?.id;
7697
+ if (hit === void 0 || typeof type !== "string") return null;
7698
+ return { entity: hit.entity, type, canvas: frameParent(world) ?? NO_ENTITY, part: hit.part };
7699
+ };
6636
7700
  return {
6637
7701
  reflector,
7702
+ cursors,
6638
7703
  canvas,
6639
7704
  available: () => ground !== null && (status.state === "ready" || status.state === "degraded"),
6640
7705
  status: () => status,
@@ -6647,6 +7712,8 @@ function deskLayer(opts) {
6647
7712
  device: () => drawDevice ?? void 0,
6648
7713
  ground: () => ground,
6649
7714
  setTheme: (t, p) => compose2.setTheme(t, p),
7715
+ capture,
7716
+ pick: pickPoint,
6650
7717
  configureMat: (mat) => setGrid({ ...grid, mat: { ...grid.mat, ...mat, ...mat.gobo !== void 0 ? { gobo: { ...grid.mat.gobo, ...mat.gobo } } : {}, ...mat.ruler !== void 0 ? { ruler: { ...grid.mat.ruler, ...mat.ruler } } : {} } }),
6651
7718
  configureFadeIn: (fadeIn) => setGrid({ ...grid, fadeIn }),
6652
7719
  setPlate(name, bytes) {
@@ -6690,6 +7757,7 @@ function deskLayer(opts) {
6690
7757
  hand: () => builder.hand(),
6691
7758
  setPortals: (on) => compose2.pinBuild({ portals: on }),
6692
7759
  tuneLaw(kind, law) {
7760
+ if (faults.missing(kind)) return;
6693
7761
  for (const t of types) {
6694
7762
  const k = objectKindOf(t);
6695
7763
  if (k?.name === kind) k.tune?.(law);
@@ -6751,7 +7819,7 @@ function deskLayer(opts) {
6751
7819
  }
6752
7820
  return profiler;
6753
7821
  },
6754
- perf: () => ({ ...perf, kindTicks: { ...kindTicks } }),
7822
+ perf: () => ({ ...perf, kindTicks: { ...kindTicks }, boot: { ...boot } }),
6755
7823
  memory: () => budget.stats(),
6756
7824
  rasters: () => rasters.stats(),
6757
7825
  gpuMemory: () => ledger,
@@ -6768,7 +7836,8 @@ function deskLayer(opts) {
6768
7836
  wakes: () => compose2.wakes(),
6769
7837
  due: (now) => {
6770
7838
  const kinds2 = {};
6771
- for (const [name, local] of locals) if (local.tick !== void 0) kinds2[name] = local.due?.(now) ?? now;
7839
+ for (const [name, local] of locals) if (local.tick !== void 0) kinds2[name] = dueOf(local, now);
7840
+ for (const f of faults.list()) kinds2[f.kind] = KIND_MISSING;
6772
7841
  const busy = [];
6773
7842
  for (const [type, d] of drivers) if (d.idle?.() !== true) busy.push(type);
6774
7843
  return { at: deskDue(now), reflector: compose2.due(now), drivers: following ? now : Number.POSITIVE_INFINITY, following: busy, woken: [...woken], kinds: kinds2, tray: ground?.trayIdleAt() ?? Number.POSITIVE_INFINITY, rasters: rasters.size > 0 ? now : Number.POSITIVE_INFINITY };
@@ -6780,7 +7849,7 @@ function deskLayer(opts) {
6780
7849
  dirty: () => {
6781
7850
  if (compose2.dirty() || woken.size > 0) return true;
6782
7851
  const now = performance.now();
6783
- for (const local of locals.values()) if (local.tick !== void 0 && (local.due?.(now) ?? now) <= now) return true;
7852
+ for (const local of locals.values()) if (local.tick !== void 0 && dueOf(local, now) <= now) return true;
6784
7853
  return false;
6785
7854
  },
6786
7855
  builder,
@@ -6843,7 +7912,7 @@ function deskLayer(opts) {
6843
7912
  listeners.clear();
6844
7913
  trayListeners.clear();
6845
7914
  statusHeard.clear();
6846
- for (const d of drivers.values()) d.dispose?.();
7915
+ for (const d of [...drivers.values(), ...parked]) d.dispose?.();
6847
7916
  editor.dispose();
6848
7917
  for (const local of locals.values()) local.dispose?.();
6849
7918
  motionQuery?.removeEventListener("change", syncMotion);
@@ -7097,219 +8166,16 @@ async function ablateKinds(a, opts = {}) {
7097
8166
  return { frames, rounds, base: baseS, control: sampled(c), aa: median(c) - baseS.median, noise, kinds: out, samples: Object.fromEntries(ms) };
7098
8167
  }
7099
8168
 
7100
- // ../desk/src/engine/records.ts
7101
- var DEFAULT_MAX = 65536;
7102
- var GAP = 4;
7103
- var NONE = { aux: 0, blockN: 0 };
7104
- function createRecordStore(opts) {
7105
- const { device, def, label, pack } = opts;
7106
- const max = opts.max ?? DEFAULT_MAX;
7107
- const blocksDef = opts.blocks;
7108
- const per = blocksDef?.per ?? 0;
7109
- let capacity = Math.max(1, Math.min(opts.capacity, max));
7110
- let cpu = def.alloc(capacity);
7111
- let gpu = storageBuffer(device, def.size * capacity, label);
7112
- let blockCpu = blocksDef ? blocksDef.def.alloc(capacity * per) : null;
7113
- let blockGpu = blocksDef ? storageBuffer(device, blocksDef.def.size * capacity * per, blocksDef.label) : null;
7114
- let orderCpu = new Uint32Array(capacity);
7115
- let orderGpu = storageBuffer(device, 4 * capacity, `${label} order`);
7116
- let version = 0;
7117
- let grown = 0;
7118
- const byKey = /* @__PURE__ */ new Map();
7119
- let bySlot = new Array(capacity).fill(null);
7120
- const free = [];
7121
- let next = 0;
7122
- let gen = 0;
7123
- let lastKeys = null;
7124
- let prevOrder = [];
7125
- let count = 0;
7126
- const dirty = [];
7127
- let written = 0;
7128
- let bytes = 0;
7129
- let orderWrites = 0;
7130
- const grow = () => {
7131
- if (capacity >= max) throw new Error(`${label}: ${capacity} record slots is the most this store grows to`);
7132
- const was = capacity;
7133
- capacity = Math.min(capacity * 2, max);
7134
- const nextCpu = def.alloc(capacity);
7135
- new Uint8Array(nextCpu.bytes).set(new Uint8Array(cpu.bytes));
7136
- cpu = nextCpu;
7137
- gpu.destroy();
7138
- gpu = storageBuffer(device, def.size * capacity, label);
7139
- if (blocksDef && blockCpu && blockGpu) {
7140
- const nextBlocks = blocksDef.def.alloc(capacity * per);
7141
- new Uint8Array(nextBlocks.bytes).set(new Uint8Array(blockCpu.bytes));
7142
- blockCpu = nextBlocks;
7143
- blockGpu.destroy();
7144
- blockGpu = storageBuffer(device, blocksDef.def.size * capacity * per, blocksDef.label);
7145
- }
7146
- const nextOrder = new Uint32Array(capacity);
7147
- nextOrder.set(orderCpu);
7148
- orderCpu = nextOrder;
7149
- orderGpu.destroy();
7150
- orderGpu = storageBuffer(device, 4 * capacity, `${label} order`);
7151
- const nextBySlot = new Array(capacity).fill(null);
7152
- for (let i = 0; i < was; i++) nextBySlot[i] = bySlot[i] ?? null;
7153
- bySlot = nextBySlot;
7154
- version += 1;
7155
- grown += 1;
7156
- };
7157
- let grew = false;
7158
- const alloc = () => {
7159
- const reused = free.pop();
7160
- if (reused !== void 0) return reused;
7161
- if (next === capacity) {
7162
- grow();
7163
- grew = true;
7164
- }
7165
- return next++;
7166
- };
7167
- const release = (ent) => {
7168
- byKey.delete(ent.key);
7169
- bySlot[ent.slot] = null;
7170
- free.push(ent.slot);
7171
- };
7172
- const resetAll = () => {
7173
- bySlot.fill(null);
7174
- byKey.clear();
7175
- free.length = 0;
7176
- next = 0;
7177
- };
7178
- const packInto = (ent, record, aux) => {
7179
- ent.record = record;
7180
- ent.aux = aux;
7181
- ent.blockN = pack(record, aux, cpu, ent.slot, blockCpu, ent.slot * per);
7182
- dirty.push(ent.slot);
7183
- };
7184
- const runs = () => {
7185
- dirty.sort((a, b) => a - b);
7186
- const out = [];
7187
- for (const s of dirty) {
7188
- const last = out[out.length - 1];
7189
- if (last !== void 0 && s <= last[1] + 1 + GAP) {
7190
- if (s > last[1]) last[1] = s;
7191
- } else out.push([s, s]);
7192
- }
7193
- return out;
7194
- };
7195
- const upload = (n, orderMoved) => {
7196
- let rewriteOrder = orderMoved;
7197
- if (grew) {
7198
- grew = false;
7199
- if (next > 0) {
7200
- device.queue.writeBuffer(gpu, 0, cpu.bytes, 0, next * def.size);
7201
- bytes += next * def.size;
7202
- }
7203
- if (blocksDef && blockCpu && blockGpu && next > 0) {
7204
- device.queue.writeBuffer(blockGpu, 0, blockCpu.bytes, 0, next * per * blocksDef.def.size);
7205
- bytes += next * per * blocksDef.def.size;
7206
- }
7207
- written += dirty.length;
7208
- dirty.length = 0;
7209
- rewriteOrder = true;
7210
- } else if (dirty.length > 0) {
7211
- for (const [a, b] of runs()) {
7212
- const n0 = b - a + 1;
7213
- device.queue.writeBuffer(gpu, a * def.size, cpu.bytes, a * def.size, n0 * def.size);
7214
- bytes += n0 * def.size;
7215
- }
7216
- if (blocksDef && blockCpu && blockGpu) {
7217
- const bsize = blocksDef.def.size;
7218
- for (const s of dirty) {
7219
- const ent = bySlot[s];
7220
- if (!ent || ent.blockN === 0) continue;
7221
- device.queue.writeBuffer(blockGpu, s * per * bsize, blockCpu.bytes, s * per * bsize, ent.blockN * bsize);
7222
- bytes += ent.blockN * bsize;
7223
- }
7224
- }
7225
- written += dirty.length;
7226
- dirty.length = 0;
7227
- }
7228
- if (rewriteOrder) {
7229
- if (n > 0) device.queue.writeBuffer(orderGpu, 0, orderCpu, 0, n);
7230
- orderWrites += 1;
7231
- prevOrder = Array.from(orderCpu.subarray(0, n));
7232
- }
7233
- };
7234
- return {
7235
- get records() {
7236
- return gpu;
7237
- },
7238
- get order() {
7239
- return orderGpu;
7240
- },
7241
- get blocks() {
7242
- return blockGpu;
7243
- },
7244
- get version() {
7245
- return version;
7246
- },
7247
- prepare(records, keys, aux) {
7248
- gen += 1;
7249
- const n = records.length;
7250
- let orderChanged = n !== prevOrder.length;
7251
- if (keys === void 0) {
7252
- resetAll();
7253
- lastKeys = null;
7254
- for (let i = 0; i < n; i++) {
7255
- const slot = alloc();
7256
- const ent = { key: -0.5 - i, slot, record: void 0, aux: 0, gen, blockN: 0 };
7257
- bySlot[slot] = ent;
7258
- packInto(ent, records[i], aux ? aux(i) : 0);
7259
- if (!orderChanged && prevOrder[i] !== slot) orderChanged = true;
7260
- orderCpu[i] = slot;
7261
- }
7262
- } else {
7263
- if (keys.length !== n) throw new Error(`${label}: ${keys.length} keys for ${n} records`);
7264
- if (lastKeys === null) resetAll();
7265
- const same = lastKeys !== null && lastKeys.length === n && lastKeys.every((k, i) => k === keys[i]);
7266
- for (let i = 0; i < n; i++) {
7267
- const key = keys[i];
7268
- let ent = byKey.get(key);
7269
- if (ent === void 0) {
7270
- const slot = alloc();
7271
- ent = { key, slot, record: void 0, aux: 0, gen, blockN: 0 };
7272
- byKey.set(key, ent);
7273
- bySlot[slot] = ent;
7274
- }
7275
- ent.gen = gen;
7276
- const a = aux ? aux(i) : 0;
7277
- if (ent.record !== records[i] || ent.aux !== a) packInto(ent, records[i], a);
7278
- if (!orderChanged && prevOrder[i] !== ent.slot) orderChanged = true;
7279
- orderCpu[i] = ent.slot;
7280
- }
7281
- if (!same) {
7282
- if (lastKeys !== null) for (const k of lastKeys) {
7283
- const ent = byKey.get(k);
7284
- if (ent !== void 0 && ent.gen !== gen) release(ent);
7285
- }
7286
- lastKeys = keys.slice();
7287
- }
7288
- }
7289
- upload(n, orderChanged);
7290
- count = n;
7291
- return n;
7292
- },
7293
- slotAt: (i) => i < count ? orderCpu[i] : -1,
7294
- entryAt(i) {
7295
- if (i >= count) return NONE;
7296
- const ent = bySlot[orderCpu[i]];
7297
- return ent === null || ent === void 0 ? NONE : { aux: ent.aux, blockN: ent.blockN };
7298
- },
7299
- invalidate() {
7300
- for (const ent of byKey.values()) ent.record = void 0;
7301
- },
7302
- stats: () => ({ written, bytes, orderWrites, slots: next - free.length, capacity, grown }),
7303
- dispose() {
7304
- gpu.destroy();
7305
- orderGpu.destroy();
7306
- blockGpu?.destroy();
7307
- byKey.clear();
7308
- }
7309
- };
7310
- }
7311
-
7312
8169
  export {
8170
+ KIND_STRIKES,
8171
+ KIND_MISSING,
8172
+ faultText,
8173
+ createKindFaults,
8174
+ isMissingRecord,
8175
+ missingFace,
8176
+ createRecordStore,
8177
+ MISSING_KIND,
8178
+ MISSING_OBJECT,
7313
8179
  STRATA,
7314
8180
  DRAWER,
7315
8181
  drawerSize,
@@ -7329,15 +8195,20 @@ export {
7329
8195
  TRAY_SHADER_FILES,
7330
8196
  trayShaders,
7331
8197
  visible,
8198
+ createKindPasses,
7332
8199
  createSlotSet,
7333
8200
  SlotPool,
7334
8201
  drawSlot,
7335
8202
  drawFrame,
7336
8203
  prepareFrame,
7337
8204
  drawTray,
8205
+ encodeFrame,
7338
8206
  Ground,
7339
8207
  tagsOf,
7340
8208
  renderHeldFrame,
8209
+ checkCapture,
8210
+ scaledInputs,
8211
+ captureFrame,
7341
8212
  AMBIENT_DEFAULTS,
7342
8213
  createAmbient,
7343
8214
  rectOf,
@@ -7393,7 +8264,6 @@ export {
7393
8264
  inkRaster,
7394
8265
  kindsIn,
7395
8266
  withoutKind,
7396
- ablateKinds,
7397
- createRecordStore
8267
+ ablateKinds
7398
8268
  };
7399
- //# sourceMappingURL=chunk-ZPUPBULB.js.map
8269
+ //# sourceMappingURL=chunk-52GMQ2BN.js.map