@vibecook/ice 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +37 -2
  2. package/dist/chunk-5SJYHQI4.js +636 -0
  3. package/dist/chunk-5SJYHQI4.js.map +1 -0
  4. package/dist/{chunk-5S7GSXJG.js → chunk-DXU3RZZS.js} +2913 -1180
  5. package/dist/chunk-DXU3RZZS.js.map +1 -0
  6. package/dist/{chunk-FIAXPNNY.js → chunk-IASTQMU5.js} +130 -551
  7. package/dist/chunk-IASTQMU5.js.map +1 -0
  8. package/dist/{chunk-BCJ47SX7.js → chunk-OR5FNVUE.js} +460 -1
  9. package/dist/chunk-OR5FNVUE.js.map +1 -0
  10. package/dist/devtools.js +508 -372
  11. package/dist/devtools.js.map +1 -1
  12. package/dist/dom.js +3 -11
  13. package/dist/ground.js +715 -0
  14. package/dist/ground.js.map +1 -0
  15. package/dist/index.js +70 -2
  16. package/dist/kernel.js +31 -1
  17. package/dist/r3f.js +599 -62
  18. package/dist/r3f.js.map +1 -1
  19. package/dist/react.js +34 -418
  20. package/dist/react.js.map +1 -1
  21. package/dist/types/packages/core/src/catalog/camera-derived.d.ts +41 -3
  22. package/dist/types/packages/core/src/catalog/gesture.d.ts +39 -3
  23. package/dist/types/packages/core/src/catalog/graph.d.ts +7 -6
  24. package/dist/types/packages/core/src/catalog/index.d.ts +1 -0
  25. package/dist/types/packages/core/src/catalog/insert.d.ts +31 -0
  26. package/dist/types/packages/core/src/catalog/pointer.d.ts +11 -0
  27. package/dist/types/packages/core/src/catalog/scene.d.ts +37 -4
  28. package/dist/types/packages/core/src/catalog/selection-presence.d.ts +7 -0
  29. package/dist/types/packages/core/src/catalog/settings-resources.d.ts +21 -0
  30. package/dist/types/packages/core/src/doc/autosave.d.ts +15 -2
  31. package/dist/types/packages/core/src/doc/bootstrap.d.ts +29 -0
  32. package/dist/types/packages/core/src/doc/channels.d.ts +9 -0
  33. package/dist/types/packages/core/src/doc/doc-commit-sink.d.ts +2 -1
  34. package/dist/types/packages/core/src/doc/doc-kit.d.ts +16 -1
  35. package/dist/types/packages/core/src/doc/envelope.d.ts +2 -1
  36. package/dist/types/packages/core/src/doc/migrate.d.ts +8 -6
  37. package/dist/types/packages/core/src/doc/schema-migrate.d.ts +50 -0
  38. package/dist/types/packages/core/src/doc/version-gate.d.ts +3 -0
  39. package/dist/types/packages/core/src/engine/commit-sink.d.ts +24 -2
  40. package/dist/types/packages/core/src/engine/frame-info.d.ts +16 -1
  41. package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +70 -6
  42. package/dist/types/packages/core/src/guards/guarded-tx.d.ts +4 -2
  43. package/dist/types/packages/core/src/helpers/churn-guard.d.ts +40 -0
  44. package/dist/types/packages/core/src/index.d.ts +13 -7
  45. package/dist/types/packages/core/src/input/queue.d.ts +11 -0
  46. package/dist/types/packages/core/src/nav/nested-canvas.d.ts +47 -11
  47. package/dist/types/packages/core/src/ops/arrange.d.ts +15 -0
  48. package/dist/types/packages/core/src/ops/insert.d.ts +62 -0
  49. package/dist/types/packages/core/src/ops/point-pick.d.ts +11 -7
  50. package/dist/types/packages/core/src/ops/sibling-order.d.ts +70 -0
  51. package/dist/types/packages/core/src/schema/meta.d.ts +1 -0
  52. package/dist/types/packages/core/src/settings/defaults.d.ts +45 -0
  53. package/dist/types/packages/core/src/settings/ground-config.d.ts +55 -0
  54. package/dist/types/packages/core/src/systems/chrome.d.ts +6 -2
  55. package/dist/types/packages/core/src/systems/insert-ghost.d.ts +25 -0
  56. package/dist/types/packages/core/src/systems/l1-pick.d.ts +25 -11
  57. package/dist/types/packages/core/src/systems/l2-recognize.d.ts +10 -4
  58. package/dist/types/packages/core/src/systems/l3-behave.d.ts +6 -4
  59. package/dist/types/packages/core/src/systems/l3-claim.d.ts +7 -3
  60. package/dist/types/packages/core/src/systems/l3-drop.d.ts +3 -1
  61. package/dist/types/packages/core/src/systems/l3-resize.d.ts +3 -1
  62. package/dist/types/packages/core/src/systems/l3-snap.d.ts +25 -7
  63. package/dist/types/packages/core/src/systems/nav-flight.d.ts +95 -0
  64. package/dist/types/packages/core/src/widget/define-widget.d.ts +39 -0
  65. package/dist/types/packages/core/src/widget/mount-store.d.ts +11 -3
  66. package/dist/types/packages/core/src/widget/set-props.d.ts +16 -0
  67. package/dist/types/packages/core/src/widget/spawn.d.ts +21 -1
  68. package/dist/types/packages/devtools/src/attach.d.ts +77 -0
  69. package/dist/types/packages/devtools/src/dock.d.ts +38 -0
  70. package/dist/types/packages/devtools/src/gl-panel.d.ts +69 -0
  71. package/dist/types/packages/devtools/src/index.d.ts +8 -6
  72. package/dist/types/packages/dom/src/index.d.ts +1 -3
  73. package/dist/types/packages/dom/src/measure-wiring.d.ts +2 -2
  74. package/dist/types/packages/dom/src/pointer-adapter.d.ts +21 -1
  75. package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +5 -0
  76. package/dist/types/packages/ground/src/index.d.ts +52 -0
  77. package/dist/types/packages/ground/src/layer.d.ts +40 -0
  78. package/dist/types/packages/ground/src/pass.d.ts +41 -0
  79. package/dist/types/packages/ground/src/passes/grid.d.ts +21 -0
  80. package/dist/types/packages/ground/src/passes/guides-collect.d.ts +14 -0
  81. package/dist/types/packages/ground/src/passes/guides.d.ts +3 -0
  82. package/dist/types/packages/ground/src/passes/soup-collect.d.ts +35 -0
  83. package/dist/types/packages/ground/src/passes/soup-mesh.d.ts +8 -0
  84. package/dist/types/packages/ground/src/passes/wires-collect.d.ts +31 -0
  85. package/dist/types/packages/ground/src/passes/wires.d.ts +10 -0
  86. package/dist/types/packages/ground/src/renderer.d.ts +15 -0
  87. package/dist/types/packages/ice/src/ground.d.ts +1 -0
  88. package/dist/types/packages/kernel/src/bezier.d.ts +15 -0
  89. package/dist/types/packages/kernel/src/coords.d.ts +15 -0
  90. package/dist/types/packages/kernel/src/easing.d.ts +11 -0
  91. package/dist/types/packages/kernel/src/index.d.ts +3 -0
  92. package/dist/types/packages/kernel/src/layout.d.ts +118 -0
  93. package/dist/types/packages/kernel/src/nav-flight.d.ts +93 -0
  94. package/dist/types/packages/r3f/src/bridge.d.ts +73 -12
  95. package/dist/types/packages/r3f/src/composite-material.d.ts +9 -0
  96. package/dist/types/packages/r3f/src/compositor-pass.d.ts +22 -0
  97. package/dist/types/packages/r3f/src/gl-root.d.ts +76 -1
  98. package/dist/types/packages/r3f/src/gl-router.d.ts +10 -0
  99. package/dist/types/packages/r3f/src/index.d.ts +3 -2
  100. package/dist/types/packages/r3f/src/island-state.d.ts +44 -0
  101. package/dist/types/packages/r3f/src/preview-capture.d.ts +19 -0
  102. package/dist/types/packages/r3f/src/self-sustain.d.ts +39 -0
  103. package/dist/types/packages/r3f/src/use-island-frame.d.ts +16 -1
  104. package/dist/types/packages/react/src/engine-context.d.ts +8 -0
  105. package/dist/types/packages/react/src/index.d.ts +4 -2
  106. package/dist/types/packages/react/src/infinite-canvas.d.ts +40 -6
  107. package/dist/types/packages/react/src/preview-snapshots.d.ts +17 -0
  108. package/dist/types/packages/react/src/widget-preview.d.ts +14 -0
  109. package/package.json +13 -8
  110. package/dist/chunk-5S7GSXJG.js.map +0 -1
  111. package/dist/chunk-BCJ47SX7.js.map +0 -1
  112. package/dist/chunk-FIAXPNNY.js.map +0 -1
  113. package/dist/types/packages/devtools/src/panel.d.ts +0 -48
  114. package/dist/types/packages/dom/src/reflectors/grid.d.ts +0 -63
  115. package/dist/types/packages/dom/src/reflectors/wires.d.ts +0 -88
package/dist/r3f.js CHANGED
@@ -1,19 +1,36 @@
1
1
  import {
2
+ EngineProvider,
3
+ hasPreviewSnapshot,
4
+ setPreviewSnapshot
5
+ } from "./chunk-5SJYHQI4.js";
6
+ import "./chunk-IASTQMU5.js";
7
+ import {
8
+ Active,
2
9
  Camera,
10
+ Culled,
11
+ GESTURE_DEFAULTS,
12
+ GestureSettings,
3
13
  Grab,
14
+ Opacity,
4
15
  Position,
5
16
  PrefabId,
6
17
  RUNTIME_BUDGETS,
7
18
  Size,
8
19
  StackZ,
20
+ StageMode,
9
21
  Viewport,
10
22
  Visible,
23
+ createCanvasEngine,
24
+ createSiblingOrderIndex,
25
+ defineQuery,
26
+ navFlightActive,
11
27
  pickTopAt,
12
28
  widgets
13
- } from "./chunk-5S7GSXJG.js";
29
+ } from "./chunk-DXU3RZZS.js";
14
30
  import {
15
31
  compositeCameraFrustum,
16
32
  computeIslandPhase,
33
+ cubicBezierEase,
17
34
  fboPixelSize,
18
35
  isOutOfBand,
19
36
  screenToWorld,
@@ -21,9 +38,10 @@ import {
21
38
  selectEvictions,
22
39
  worldRectToComposite,
23
40
  worldToIsland
24
- } from "./chunk-BCJ47SX7.js";
41
+ } from "./chunk-OR5FNVUE.js";
25
42
 
26
43
  // ../r3f/src/island-state.ts
44
+ var LIFT_DEFAULT_MS = 180;
27
45
  var FRESH_PAINTED_AT = { w: 0, h: 0, dpr: 1, band: 0 };
28
46
  function createIslandStateStore() {
29
47
  const entries = /* @__PURE__ */ new Map();
@@ -39,7 +57,17 @@ function createIslandStateStore() {
39
57
  animRefs: 0,
40
58
  animatedDecl: false,
41
59
  lastPaintSeq: 0,
42
- compositeScale: 1
60
+ pendingDtMs: 0,
61
+ compositeScale: 1,
62
+ liftTarget: 1,
63
+ liftFrom: 1,
64
+ liftElapsedMs: 0,
65
+ liftMs: 0,
66
+ compositeOpacity: 1,
67
+ opacityTarget: 1,
68
+ opacityFrom: 1,
69
+ opacityElapsedMs: 0,
70
+ opacityMs: 0
43
71
  };
44
72
  entries.set(key, s);
45
73
  }
@@ -122,17 +150,45 @@ function createGLBridge(engine, opts = {}) {
122
150
  const islands = /* @__PURE__ */ new Map();
123
151
  const frameCbs = /* @__PURE__ */ new Map();
124
152
  const renderAssert = createRenderWriteTrap(world, opts.devAssertRenderWrites === true);
125
- let invalidate = null;
126
- let pending = false;
153
+ const now = opts.now ?? (() => performance.now());
154
+ let renderNow = null;
155
+ let wantsFrame = false;
156
+ let animDueAt = null;
157
+ let rendering = false;
127
158
  const requestFrame = () => {
128
- if (invalidate !== null) invalidate();
129
- else pending = true;
159
+ wantsFrame = true;
130
160
  };
131
161
  const removeReflector = engine.registerReflector({
132
162
  name: "r3fInvalidator",
133
- observe: { resources: [Camera, Viewport] },
163
+ // StageMode: a background-hold flip must re-fire the pass — freezing on
164
+ // take, and (critically) REPAINTING Hot islands on release. The observe
165
+ // list also keeps strata enforcement armed (a rig must observe — the
166
+ // always-flush advance reflector below deliberately observes nothing).
167
+ observe: { resources: [Camera, Viewport, StageMode] },
134
168
  flush: () => requestFrame()
135
169
  });
170
+ const removeAdvanceReflector = engine.registerReflector({
171
+ name: "r3fAdvance",
172
+ always: true,
173
+ flush: () => {
174
+ if (rendering) return;
175
+ const t = now();
176
+ if (!wantsFrame && (animDueAt === null || t < animDueAt)) return;
177
+ if (renderNow === null) return;
178
+ wantsFrame = false;
179
+ animDueAt = null;
180
+ rendering = true;
181
+ try {
182
+ renderNow(t);
183
+ } finally {
184
+ rendering = false;
185
+ }
186
+ }
187
+ });
188
+ const order = createSiblingOrderIndex(world);
189
+ const removeOrderObserver = order.observe(() => {
190
+ if (order.stale()) requestFrame();
191
+ });
136
192
  const removeWorldObserver = world.observe({
137
193
  onDestroy: (e) => {
138
194
  if (state.get(e) !== void 0) {
@@ -151,6 +207,7 @@ function createGLBridge(engine, opts = {}) {
151
207
  engine,
152
208
  state,
153
209
  renderAssert,
210
+ order,
154
211
  registerIsland(entity, handle) {
155
212
  islands.set(entity, handle);
156
213
  const s = state.ensure(entity);
@@ -163,9 +220,12 @@ function createGLBridge(engine, opts = {}) {
163
220
  state.bumpPaint(entity);
164
221
  requestFrame();
165
222
  }),
166
- // composite dirt — quad transform/order changed, texture still good:
223
+ // composite dirt — quad transform changed, texture still good (order
224
+ // dirt is bridge-scoped: the ChildOf reorder wake above):
167
225
  world.reactive.observeValue(entity, Position, () => requestFrame()),
168
- world.reactive.observeValue(entity, StackZ, () => requestFrame())
226
+ // composite dirt — the durable Opacity cell rides the quad's uOpacity,
227
+ // never the island's FBO (a fade must not repaint the content):
228
+ world.reactive.observeValue(entity, Opacity, () => requestFrame())
169
229
  ];
170
230
  for (const group of widget?.groups ?? []) {
171
231
  unsubs.push(
@@ -208,63 +268,115 @@ function createGLBridge(engine, opts = {}) {
208
268
  state.bumpPaint(entity);
209
269
  requestFrame();
210
270
  },
211
- setCompositeScale(entity, scale) {
271
+ setCompositeScale(entity, scale, durationMs = LIFT_DEFAULT_MS) {
212
272
  const s = state.ensure(entity);
213
- if (s.compositeScale === scale) return;
214
- s.compositeScale = scale;
273
+ if (s.liftTarget === scale) return;
274
+ s.liftTarget = scale;
275
+ if (durationMs <= 0) {
276
+ s.compositeScale = scale;
277
+ s.liftMs = 0;
278
+ s.liftElapsedMs = 0;
279
+ } else {
280
+ s.liftFrom = s.compositeScale;
281
+ s.liftMs = durationMs;
282
+ s.liftElapsedMs = 0;
283
+ }
215
284
  requestFrame();
216
285
  },
217
- setInvalidate(fn) {
218
- invalidate = fn;
219
- if (fn !== null && pending) {
220
- pending = false;
221
- fn();
286
+ setCompositeOpacity(entity, opacity, durationMs = LIFT_DEFAULT_MS) {
287
+ const s = state.ensure(entity);
288
+ if (s.opacityTarget === opacity) return;
289
+ s.opacityTarget = opacity;
290
+ if (durationMs <= 0) {
291
+ s.compositeOpacity = opacity;
292
+ s.opacityMs = 0;
293
+ s.opacityElapsedMs = 0;
294
+ } else {
295
+ s.opacityFrom = s.compositeOpacity;
296
+ s.opacityMs = durationMs;
297
+ s.opacityElapsedMs = 0;
298
+ }
299
+ requestFrame();
300
+ },
301
+ setRenderNow(fn) {
302
+ renderNow = fn;
303
+ },
304
+ schedulePass(plan) {
305
+ if (plan === "now") {
306
+ wantsFrame = true;
307
+ animDueAt = null;
308
+ } else if (plan === "none") {
309
+ animDueAt = null;
310
+ } else {
311
+ animDueAt = now() + plan;
222
312
  }
223
313
  },
224
314
  uninstall() {
225
315
  removeReflector();
316
+ removeAdvanceReflector();
317
+ removeOrderObserver();
226
318
  removeWorldObserver();
227
319
  renderAssert.dispose();
228
320
  islands.clear();
229
321
  frameCbs.clear();
230
322
  state.clear();
231
- invalidate = null;
323
+ renderNow = null;
324
+ wantsFrame = false;
325
+ animDueAt = null;
232
326
  }
233
327
  };
234
328
  }
235
329
 
236
330
  // ../r3f/src/compositor-pass.ts
237
331
  var GRABBED_RENDER_ORDER = 1e9;
332
+ var LIFT_EASE = cubicBezierEase(0.2, 0.9, 0.3, 1.2);
333
+ var OPACITY_EASE = cubicBezierEase(0.25, 0.1, 0.25, 1);
238
334
  function runCompositorPass(ctx) {
239
335
  const { world, bridge, pool, quads, gl } = ctx;
240
- const stats = { repainted: 0, pendingPaints: 0, evicted: 0, fboBytes: 0, anyHot: false };
336
+ const stats = {
337
+ repainted: 0,
338
+ pendingPaints: 0,
339
+ evicted: 0,
340
+ fboBytes: 0,
341
+ anyHot: false,
342
+ liftAnimating: false
343
+ };
241
344
  const cam = world.getResource(Camera);
242
345
  const vp = world.getResource(Viewport);
243
346
  if (cam === void 0 || vp === void 0 || vp.w === 0) return stats;
347
+ const inFlight2 = navFlightActive(world);
348
+ const inMotion = cam.gesturing || inFlight2;
349
+ const backgrounded = (world.getResource(StageMode)?.backgroundHolds ?? 0) > 0;
350
+ const frozen = inFlight2 || backgrounded;
244
351
  ctx.compCamera.setFrustum(compositeCameraFrustum(cam, vp.w, vp.h));
245
352
  const idleDpr = vp.dpr > 0 ? vp.dpr : 1;
246
- const targetDpr = cam.gesturing ? Math.min(idleDpr, 1) : idleDpr;
353
+ const targetDpr = inMotion ? Math.min(idleDpr, 1) : idleDpr;
247
354
  if (gl.getPixelRatio() !== targetDpr) gl.setPixelRatio(targetDpr);
248
355
  const phaseOf = (e) => {
249
356
  const alive = world.isAlive(e);
357
+ const active = alive && world.hasTag(e, Active);
250
358
  const visible = alive && world.hasTag(e, Visible) && bridge.islandFor(e) !== void 0;
251
359
  const s = bridge.state.get(e);
252
360
  const hasFbo = s !== void 0 && s.fboGeneration >= 0 && pool.get(e) !== null;
253
- return computeIslandPhase(true, visible, bridge.state.isAnimating(e), hasFbo);
361
+ return computeIslandPhase(active, visible, bridge.state.isAnimating(e), hasFbo);
254
362
  };
255
363
  for (const [e, s] of bridge.state.all()) {
256
364
  s.phase = phaseOf(e);
257
365
  if (s.phase === "Hot") stats.anyHot = true;
258
366
  }
259
367
  const band = selectBand(cam.zoom);
260
- const effectiveDpr = gl.getPixelRatio() * band;
368
+ const effectiveDpr = Math.min(gl.getPixelRatio(), ctx.maxPaintDpr) * band;
261
369
  const toPaint = [];
262
370
  for (const [e, s] of bridge.state.all()) {
263
371
  if (bridge.islandFor(e) === void 0) continue;
264
372
  const wantsPhase = s.phase === "Hot" || s.phase === "Waking";
265
373
  const genDirty = s.paintGeneration > s.fboGeneration;
266
- const bandStale = !cam.gesturing && s.fboGeneration >= 0 && isOutOfBand(cam.zoom, s.paintedAt.band);
267
- if (wantsPhase || genDirty || bandStale) toPaint.push(e);
374
+ const bandStale = !inMotion && s.fboGeneration >= 0 && isOutOfBand(cam.zoom, s.paintedAt.band);
375
+ const paintable = frozen ? wantsPhase && s.fboGeneration < 0 : wantsPhase || genDirty || bandStale;
376
+ if (paintable) {
377
+ toPaint.push(e);
378
+ if (s.phase === "Hot") s.pendingDtMs += ctx.dtMs;
379
+ }
268
380
  }
269
381
  toPaint.sort((a, b) => {
270
382
  const sa = bridge.state.get(a);
@@ -285,7 +397,9 @@ function runCompositorPass(ctx) {
285
397
  const size = world.isAlive(e) ? world.get(e, Size) : void 0;
286
398
  if (handle === void 0 || s === void 0 || size === void 0 || size.w <= 0 || size.h <= 0) continue;
287
399
  if (s.phase === "Hot") {
288
- for (const cb of bridge.frameCallbacksFor(e)) cb(ctx.dtMs);
400
+ const owedMs = s.pendingDtMs;
401
+ s.pendingDtMs = 0;
402
+ for (const cb of bridge.frameCallbacksFor(e)) cb(owedMs);
289
403
  }
290
404
  ctx.islandCamera(e)?.setFrustum(size.w / 2, size.h / 2);
291
405
  const fbo = pool.acquire(e, size.w, size.h, effectiveDpr);
@@ -330,29 +444,63 @@ function runCompositorPass(ctx) {
330
444
  for (const key of quads.keys()) {
331
445
  if (!liveKeys.has(key)) quads.remove(key);
332
446
  }
447
+ const hasChrome = (e) => {
448
+ const type = world.get(e, PrefabId)?.id;
449
+ return typeof type === "string" && widgets.get(type)?.chrome != null;
450
+ };
451
+ const composited = [];
452
+ let clip = null;
453
+ const ordinals = bridge.order.ordinals();
333
454
  for (const [e, s] of bridge.state.all()) {
334
455
  const quad = quads.ensure(e);
335
456
  const alive = world.isAlive(e);
336
457
  const fbo = pool.get(e);
337
458
  const pos = alive ? world.get(e, Position) : void 0;
338
459
  const size = alive ? world.get(e, Size) : void 0;
339
- if (fbo === null || s.fboGeneration < 0 || pos === void 0 || size === void 0) {
460
+ if (s.phase === "Dormant" || fbo === null || s.fboGeneration < 0 || pos === void 0 || size === void 0) {
340
461
  quad.setVisible(false);
341
462
  continue;
342
463
  }
343
464
  const q = worldRectToComposite({ x: pos.x, y: pos.y, width: size.w, height: size.h });
465
+ if (s.liftElapsedMs < s.liftMs) {
466
+ s.liftElapsedMs += ctx.dtMs;
467
+ const t = Math.min(1, s.liftElapsedMs / s.liftMs);
468
+ s.compositeScale = t >= 1 ? s.liftTarget : s.liftFrom + (s.liftTarget - s.liftFrom) * LIFT_EASE(t);
469
+ if (t < 1) stats.liftAnimating = true;
470
+ }
471
+ if (s.opacityElapsedMs < s.opacityMs) {
472
+ s.opacityElapsedMs += ctx.dtMs;
473
+ const t = Math.min(1, s.opacityElapsedMs / s.opacityMs);
474
+ s.compositeOpacity = t >= 1 ? s.opacityTarget : s.opacityFrom + (s.opacityTarget - s.opacityFrom) * OPACITY_EASE(t);
475
+ if (t < 1) stats.liftAnimating = true;
476
+ }
344
477
  const lift = s.compositeScale;
345
478
  quad.setTransform(q.x, q.y, q.sx * lift, q.sy * lift);
346
479
  quad.setTexture(fbo.texture);
480
+ const baseOpacity = world.get(e, Opacity)?.a ?? 1;
481
+ quad.setOpacity(Math.min(1, Math.max(0, baseOpacity * s.compositeOpacity)));
347
482
  const grabbed = world.get(e, Grab) !== void 0;
348
- quad.setRenderOrder(grabbed || lift !== 1 ? GRABBED_RENDER_ORDER : world.get(e, StackZ)?.z ?? 0);
483
+ quad.setRenderOrder(
484
+ grabbed || lift !== 1 ? GRABBED_RENDER_ORDER : ordinals.get(e) ?? world.get(e, StackZ)?.z ?? 0
485
+ );
349
486
  quad.setVisible(true);
350
487
  pool.touch(e);
488
+ composited.push({ key: e, quad });
489
+ if (clip === null && grabbed && hasChrome(e)) {
490
+ const hw = q.sx * lift / 2;
491
+ const hh = q.sy * lift / 2;
492
+ clip = { key: e, minX: q.x - hw, minY: q.y - hh, maxX: q.x + hw, maxY: q.y + hh };
493
+ }
494
+ }
495
+ for (const { key, quad } of composited) {
496
+ if (clip === null) quad.setDragClip(0, 0, 0, 0, false);
497
+ else quad.setDragClip(clip.minX, clip.minY, clip.maxX, clip.maxY, key === clip.key);
351
498
  }
352
499
  gl.setRenderTarget(null);
353
500
  gl.clear();
354
501
  gl.render(ctx.compositeScene, ctx.compCamera.raw);
355
502
  stats.fboBytes = pool.bytesUsed();
503
+ if (backgrounded) stats.anyHot = false;
356
504
  return stats;
357
505
  }
358
506
 
@@ -373,8 +521,10 @@ var VERTEX_SHADER = (
373
521
  /* glsl */
374
522
  `
375
523
  varying vec2 vUv;
524
+ varying vec2 vWorld;
376
525
  void main() {
377
526
  vUv = uv;
527
+ vWorld = (modelMatrix * vec4(position, 1.0)).xy;
378
528
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
379
529
  }
380
530
  `
@@ -384,9 +534,20 @@ var FRAGMENT_SHADER = (
384
534
  `
385
535
  uniform sampler2D map;
386
536
  uniform float uOpacity;
537
+ uniform vec4 uDraggedRect;
538
+ uniform float uIsDragged;
387
539
  varying vec2 vUv;
540
+ varying vec2 vWorld;
388
541
 
389
542
  void main() {
543
+ // Drag clip (v1 RFC-003, composite-space): while a chrome-carrying GL card
544
+ // is dragged, every OTHER quad discards fragments inside its rect so the
545
+ // card's P1 DOM chrome shows through. (0,0,0,0) rect \u21D2 measure-zero, off.
546
+ if (uIsDragged < 0.5 &&
547
+ vWorld.x >= uDraggedRect.x && vWorld.x <= uDraggedRect.z &&
548
+ vWorld.y >= uDraggedRect.y && vWorld.y <= uDraggedRect.w) {
549
+ discard;
550
+ }
390
551
  vec4 c = texture2D(map, vUv);
391
552
  if (c.a < 0.001) discard;
392
553
  c.a *= uOpacity;
@@ -402,10 +563,15 @@ var CompositeMaterial = class extends ShaderMaterial {
402
563
  // the setters allocation-free.
403
564
  mapUniform;
404
565
  opacityUniform;
566
+ draggedRectUniform;
567
+ isDraggedUniform;
405
568
  constructor() {
406
569
  const uniforms = {
407
570
  map: { value: null },
408
- uOpacity: { value: 1 }
571
+ uOpacity: { value: 1 },
572
+ // three uploads number[] as vec4 — no Vector4 allocation needed.
573
+ uDraggedRect: { value: [0, 0, 0, 0] },
574
+ uIsDragged: { value: 0 }
409
575
  };
410
576
  super({
411
577
  vertexShader: VERTEX_SHADER,
@@ -422,6 +588,8 @@ var CompositeMaterial = class extends ShaderMaterial {
422
588
  });
423
589
  this.mapUniform = uniforms.map;
424
590
  this.opacityUniform = uniforms.uOpacity;
591
+ this.draggedRectUniform = uniforms.uDraggedRect;
592
+ this.isDraggedUniform = uniforms.uIsDragged;
425
593
  }
426
594
  /** Bind the island's FBO texture (null clears). */
427
595
  setMap(map) {
@@ -431,6 +599,21 @@ var CompositeMaterial = class extends ShaderMaterial {
431
599
  setOpacity(opacity) {
432
600
  this.opacityUniform.value = opacity;
433
601
  }
602
+ /**
603
+ * Drag clip, composite-space min/max (v1's uDraggedRect re-cut from screen
604
+ * space). (0,0,0,0) = inactive. Mutates the vec4 in place — no allocation.
605
+ */
606
+ setDraggedRect(minX, minY, maxX, maxY) {
607
+ const r = this.draggedRectUniform.value;
608
+ r[0] = minX;
609
+ r[1] = minY;
610
+ r[2] = maxX;
611
+ r[3] = maxY;
612
+ }
613
+ /** True on the dragged card's own quad — exempt from the clip. */
614
+ setIsDragged(isDragged) {
615
+ this.isDraggedUniform.value = isDragged ? 1 : 0;
616
+ }
434
617
  // dispose() is inherited from ShaderMaterial — it fires the dispose event that
435
618
  // frees the compiled program; no override needed.
436
619
  };
@@ -460,13 +643,20 @@ function useIslandInvalidate() {
460
643
  const { bridge, entity } = useIslandContext();
461
644
  return useCallback(() => bridge.bumpPaint(entity), [bridge, entity]);
462
645
  }
463
- function useIslandLift(scale) {
646
+ function useIslandLift(scale, durationMs) {
464
647
  const { bridge, entity } = useIslandContext();
465
648
  useEffect(() => {
466
- bridge.setCompositeScale(entity, scale);
467
- }, [bridge, entity, scale]);
649
+ bridge.setCompositeScale(entity, scale, durationMs);
650
+ }, [bridge, entity, scale, durationMs]);
468
651
  useEffect(() => () => bridge.setCompositeScale(entity, 1), [bridge, entity]);
469
652
  }
653
+ function useIslandOpacity(opacity, durationMs) {
654
+ const { bridge, entity } = useIslandContext();
655
+ useEffect(() => {
656
+ bridge.setCompositeOpacity(entity, opacity, durationMs);
657
+ }, [bridge, entity, opacity, durationMs]);
658
+ useEffect(() => () => bridge.setCompositeOpacity(entity, 1), [bridge, entity]);
659
+ }
470
660
 
471
661
  // ../r3f/src/island.tsx
472
662
  function Island({ bridge, world, entity, widget, environment }) {
@@ -500,9 +690,13 @@ function Island({ bridge, world, entity, widget, environment }) {
500
690
  // ../r3f/src/pool.ts
501
691
  import { SRGBColorSpace, WebGLRenderTarget } from "three";
502
692
  var COLOR_BYTES_PER_PIXEL = 4;
693
+ var DEPTH_BYTES_PER_PIXEL = 4;
503
694
  var SAMPLES = 4;
504
695
  function targetBytes(pixelWidth, pixelHeight) {
505
- return pixelWidth * pixelHeight * COLOR_BYTES_PER_PIXEL * (SAMPLES > 1 ? 2 : 1);
696
+ const msaa = SAMPLES > 1;
697
+ const colorBytes = COLOR_BYTES_PER_PIXEL * (msaa ? 1 + SAMPLES : 1);
698
+ const depthBytes = DEPTH_BYTES_PER_PIXEL * (msaa ? SAMPLES : 1);
699
+ return pixelWidth * pixelHeight * (colorBytes + depthBytes);
506
700
  }
507
701
  var RenderTargetPool = class {
508
702
  entries = /* @__PURE__ */ new Map();
@@ -612,20 +806,37 @@ var RenderTargetPool = class {
612
806
  }
613
807
  };
614
808
 
809
+ // ../r3f/src/self-sustain.ts
810
+ var RATE_CAP_SLACK_MS = 4;
811
+ function selfSustainPlan(stats, elapsedMs, maxFps) {
812
+ if (stats.liftAnimating) return "now";
813
+ if (!stats.anyHot && stats.pendingPaints <= 0) return "none";
814
+ const interval = 1e3 / Math.max(1, maxFps);
815
+ const delay = interval - elapsedMs - RATE_CAP_SLACK_MS;
816
+ return delay <= 0 ? "now" : delay;
817
+ }
818
+
615
819
  // ../r3f/src/gl-root.tsx
820
+ var visibleWidgetsQ = defineQuery([Visible]);
821
+ var culledWidgetsQ = defineQuery([Culled]);
616
822
  function GLViews({
617
823
  engine,
618
824
  bridge,
619
825
  store,
620
826
  maxFboBytes = RUNTIME_BUDGETS.fboBytes,
621
827
  maxRepaintsPerFrame = 4,
622
- environment
828
+ maxAnimationFps = 60,
829
+ maxPaintDpr = 1.5,
830
+ environment,
831
+ onFrameStats
623
832
  }) {
624
833
  const world = engine.world;
625
834
  const gl = useThree((s) => s.gl);
626
835
  const scene = useThree((s) => s.scene);
627
836
  const set = useThree((s) => s.set);
628
- const invalidate = useThree((s) => s.invalidate);
837
+ const advance2 = useThree((s) => s.advance);
838
+ const frameloop = useThree((s) => s.frameloop);
839
+ const clock = useThree((s) => s.clock);
629
840
  const poolRef = useRef2(null);
630
841
  if (poolRef.current === null || poolRef.current.isDisposed()) {
631
842
  poolRef.current = new RenderTargetPool();
@@ -660,6 +871,12 @@ function GLViews({
660
871
  },
661
872
  setRenderOrder: (n) => {
662
873
  m.renderOrder = n;
874
+ },
875
+ setOpacity: (o) => m.material.setOpacity(o),
876
+ setDragClip: (minX, minY, maxX, maxY, exempt) => {
877
+ const mat = m.material;
878
+ mat.setDraggedRect(minX, minY, maxX, maxY);
879
+ mat.setIsDragged(exempt);
663
880
  }
664
881
  };
665
882
  },
@@ -689,9 +906,29 @@ function GLViews({
689
906
  [gl]
690
907
  );
691
908
  useEffect3(() => {
692
- bridge.setInvalidate(() => invalidate());
693
- return () => bridge.setInvalidate(null);
694
- }, [bridge, invalidate]);
909
+ clock.elapsedTime = performance.now() / 1e3;
910
+ bridge.setRenderNow((nowMs) => advance2(nowMs / 1e3, true));
911
+ return () => bridge.setRenderNow(null);
912
+ }, [bridge, advance2, clock]);
913
+ useEffect3(() => {
914
+ if (frameloop !== "never") {
915
+ console.warn(
916
+ `[ice/r3f] GLViews: <Canvas frameloop="${frameloop}"> \u2014 use frameloop="never". The engine drives frames (reflect-phase advance); a self-scheduling loop renders out of phase with the DOM planes.`
917
+ );
918
+ }
919
+ }, [frameloop]);
920
+ const statsCbRef = useRef2(onFrameStats);
921
+ statsCbRef.current = onFrameStats;
922
+ const gpuProfilerRef = useRef2(null);
923
+ const gpuProfilerState = useRef2("idle");
924
+ useEffect3(
925
+ () => () => {
926
+ gpuProfilerRef.current?.dispose();
927
+ gpuProfilerRef.current = null;
928
+ gpuProfilerState.current = "idle";
929
+ },
930
+ []
931
+ );
695
932
  useEffect3(() => {
696
933
  const quadMap = quadsRef.current;
697
934
  return () => {
@@ -705,6 +942,25 @@ function GLViews({
705
942
  };
706
943
  }, [scene, pool, bridge]);
707
944
  useFrame((_, delta) => {
945
+ const profiling = statsCbRef.current !== void 0;
946
+ if (profiling) {
947
+ if (gpuProfilerState.current === "idle") {
948
+ gpuProfilerState.current = "loading";
949
+ void import("stats-gl").then(async ({ StatsProfiler }) => {
950
+ const p = new StatsProfiler({ trackGPU: true });
951
+ await p.init(gl);
952
+ gpuProfilerRef.current = p;
953
+ gpuProfilerState.current = "ready";
954
+ }).catch(() => {
955
+ gpuProfilerState.current = "failed";
956
+ });
957
+ }
958
+ gl.info.autoReset = false;
959
+ gl.info.reset();
960
+ } else if (!gl.info.autoReset) {
961
+ gl.info.autoReset = true;
962
+ }
963
+ const passStart = performance.now();
708
964
  bridge.renderAssert.begin();
709
965
  let stats;
710
966
  try {
@@ -742,12 +998,77 @@ function GLViews({
742
998
  compositeScene: scene,
743
999
  maxFboBytes,
744
1000
  maxRepaintsPerFrame,
1001
+ maxPaintDpr,
745
1002
  dtMs: delta * 1e3
746
1003
  });
747
1004
  } finally {
748
1005
  bridge.renderAssert.end();
749
1006
  }
750
- if (stats.anyHot || stats.pendingPaints > 0) invalidate();
1007
+ const cb = statsCbRef.current;
1008
+ if (cb !== void 0) {
1009
+ const gpu = gpuProfilerRef.current;
1010
+ gpu?.update();
1011
+ const d = gpu?.getData();
1012
+ const info = gl.info;
1013
+ let total = 0;
1014
+ let hot = 0;
1015
+ let warm = 0;
1016
+ let waking = 0;
1017
+ let cold = 0;
1018
+ let dormant = 0;
1019
+ let pixels = 0;
1020
+ const bandHistogram = {};
1021
+ for (const [e, s] of bridge.state.all()) {
1022
+ total++;
1023
+ if (s.phase === "Hot") hot++;
1024
+ else if (s.phase === "Warm") warm++;
1025
+ else if (s.phase === "Waking") waking++;
1026
+ else if (s.phase === "Dormant") dormant++;
1027
+ else cold++;
1028
+ if (s.fboGeneration >= 0 && pool.get(e) !== null) {
1029
+ pixels += s.paintedAt.w * s.paintedAt.h;
1030
+ const key = `\xD7${s.paintedAt.band}`;
1031
+ bandHistogram[key] = (bandHistogram[key] ?? 0) + 1;
1032
+ }
1033
+ }
1034
+ let visibleWidgets = 0;
1035
+ let culledWidgets = 0;
1036
+ world.query(visibleWidgetsQ).each((b) => {
1037
+ visibleWidgets += b.count;
1038
+ });
1039
+ world.query(culledWidgetsQ).each((b) => {
1040
+ culledWidgets += b.count;
1041
+ });
1042
+ const zoom = world.getResource(Camera)?.zoom ?? 1;
1043
+ const band = selectBand(zoom);
1044
+ cb({
1045
+ cpuMs: performance.now() - passStart,
1046
+ gpuMs: d?.gpu ?? 0,
1047
+ fps: d?.fps ?? 0,
1048
+ drawCalls: info.render.calls,
1049
+ triangles: info.render.triangles,
1050
+ points: info.render.points,
1051
+ lines: info.render.lines,
1052
+ programs: info.programs?.length ?? 0,
1053
+ geometries: info.memory.geometries,
1054
+ textures: info.memory.textures,
1055
+ renderTargets: pool.size(),
1056
+ renderMegaPixels: pixels / 1e6,
1057
+ fboBytes: pool.bytesUsed(),
1058
+ fboBudgetBytes: maxFboBytes,
1059
+ islands: { total, hot, warm, waking, cold, dormant },
1060
+ bandHistogram,
1061
+ repainted: stats.repainted,
1062
+ pendingPaints: stats.pendingPaints,
1063
+ evicted: stats.evicted,
1064
+ zoom,
1065
+ band,
1066
+ effectiveDpr: Math.min(gl.getPixelRatio(), maxPaintDpr) * band,
1067
+ visibleWidgets,
1068
+ culledWidgets
1069
+ });
1070
+ }
1071
+ bridge.schedulePass(selfSustainPlan(stats, performance.now() - passStart, maxAnimationFps));
751
1072
  }, 1);
752
1073
  const entries = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
753
1074
  const islands = [];
@@ -778,6 +1099,9 @@ var ResourceRegistry = class {
778
1099
  textures = /* @__PURE__ */ new Map();
779
1100
  disposed = false;
780
1101
  acquireGeometry(key, factory) {
1102
+ if (this.disposed) {
1103
+ throw new Error("ResourceRegistry: cannot acquire after dispose");
1104
+ }
781
1105
  const existing = this.geometries.get(key);
782
1106
  if (existing) {
783
1107
  existing.refCount++;
@@ -791,6 +1115,9 @@ var ResourceRegistry = class {
791
1115
  this.releaseFrom(this.geometries, key);
792
1116
  }
793
1117
  acquireMaterial(key, factory) {
1118
+ if (this.disposed) {
1119
+ throw new Error("ResourceRegistry: cannot acquire after dispose");
1120
+ }
794
1121
  const existing = this.materials.get(key);
795
1122
  if (existing) {
796
1123
  existing.refCount++;
@@ -804,6 +1131,9 @@ var ResourceRegistry = class {
804
1131
  this.releaseFrom(this.materials, key);
805
1132
  }
806
1133
  acquireTexture(key, factory) {
1134
+ if (this.disposed) {
1135
+ throw new Error("ResourceRegistry: cannot acquire after dispose");
1136
+ }
807
1137
  const existing = this.textures.get(key);
808
1138
  if (existing) {
809
1139
  existing.refCount++;
@@ -888,14 +1218,53 @@ function handlersOf(obj) {
888
1218
  function createGLPointerRouter({ world, bridge, index }) {
889
1219
  const raycaster = new Raycaster();
890
1220
  const ndc = new Vector2();
1221
+ const routerOrder = createSiblingOrderIndex(world);
891
1222
  const captures = /* @__PURE__ */ new Map();
892
1223
  const hovered = /* @__PURE__ */ new Map();
893
1224
  const unclaimedDowns = /* @__PURE__ */ new Map();
1225
+ let hoverInteractive = false;
1226
+ const chainInteractive = (hits) => {
1227
+ for (const h of hits) {
1228
+ for (let obj = h.object; obj !== null; obj = obj.parent) {
1229
+ const handlers = handlersOf(obj);
1230
+ if (handlers !== void 0 && (handlers.onPointerDown !== void 0 || handlers.onClick !== void 0)) {
1231
+ return true;
1232
+ }
1233
+ }
1234
+ }
1235
+ return false;
1236
+ };
1237
+ const withinTapSlop = (downX, downY, upX, upY) => {
1238
+ const slop = world.getResource(GestureSettings)?.tapSlopPx ?? GESTURE_DEFAULTS.tapSlopPx;
1239
+ const dx = upX - downX;
1240
+ const dy = upY - downY;
1241
+ return dx * dx + dy * dy <= slop * slop;
1242
+ };
1243
+ const chainHas = (hits, target) => {
1244
+ for (const h of hits) {
1245
+ for (let obj = h.object; obj !== null; obj = obj.parent) {
1246
+ if (obj === target) return true;
1247
+ }
1248
+ }
1249
+ return false;
1250
+ };
1251
+ const filterToChain = (hits, target) => {
1252
+ const kept = [];
1253
+ for (const h of hits) {
1254
+ for (let obj = h.object; obj !== null; obj = obj.parent) {
1255
+ if (obj === target) {
1256
+ kept.push(h);
1257
+ break;
1258
+ }
1259
+ }
1260
+ }
1261
+ return kept;
1262
+ };
894
1263
  const castAt = (screenX, screenY) => {
895
1264
  const cam = world.getResource(Camera);
896
1265
  if (cam === void 0) return void 0;
897
1266
  const w = screenToWorld(screenX, screenY, cam);
898
- const hit = pickTopAt(world, index, w.x, w.y, 0);
1267
+ const hit = pickTopAt(world, index, w.x, w.y, 0, void 0, routerOrder.ordinals());
899
1268
  if (hit === void 0) return void 0;
900
1269
  const type = world.get(hit, PrefabId)?.id;
901
1270
  const widget = typeof type === "string" ? widgets.get(type) : void 0;
@@ -919,6 +1288,11 @@ function createGLPointerRouter({ world, bridge, index }) {
919
1288
  raycaster.setFromCamera(ndc, cam3);
920
1289
  return { entity: hit, hits: raycaster.intersectObjects(handle.scene.children, true) };
921
1290
  };
1291
+ const captureHits = (x, y, capture) => {
1292
+ const cast = castAt(x, y);
1293
+ if (cast === void 0 || cast.entity !== capture.entity) return [];
1294
+ return capture.downObject === void 0 ? cast.hits : filterToChain(cast.hits, capture.downObject);
1295
+ };
922
1296
  const dispatch = (type, eventType, entity, hits, native, only) => {
923
1297
  const visited = /* @__PURE__ */ new Set();
924
1298
  let stopped = false;
@@ -1022,34 +1396,48 @@ function createGLPointerRouter({ world, bridge, index }) {
1022
1396
  return {
1023
1397
  route(kind, screenX, screenY, native) {
1024
1398
  if (kind === "down") {
1025
- const cast2 = castAt(screenX, screenY);
1026
- if (cast2 === void 0) {
1399
+ const cast = castAt(screenX, screenY);
1400
+ if (cast === void 0) {
1027
1401
  unclaimedDowns.delete(native.pointerId);
1028
1402
  return false;
1029
1403
  }
1030
- const { stopped, claimedBy } = dispatch("onPointerDown", "pointerdown", cast2.entity, cast2.hits, native);
1404
+ const { stopped, claimedBy } = dispatch("onPointerDown", "pointerdown", cast.entity, cast.hits, native);
1031
1405
  if (stopped) {
1032
1406
  unclaimedDowns.delete(native.pointerId);
1033
- captures.set(native.pointerId, { entity: cast2.entity, downObject: claimedBy });
1407
+ captures.set(native.pointerId, {
1408
+ entity: cast.entity,
1409
+ downObject: claimedBy,
1410
+ downX: screenX,
1411
+ downY: screenY
1412
+ });
1034
1413
  return true;
1035
1414
  }
1036
- const top = cast2.hits[0]?.object;
1037
- if (top !== void 0) unclaimedDowns.set(native.pointerId, { entity: cast2.entity, object: top });
1415
+ const top = cast.hits[0]?.object;
1416
+ if (top !== void 0) {
1417
+ unclaimedDowns.set(native.pointerId, {
1418
+ entity: cast.entity,
1419
+ object: top,
1420
+ downX: screenX,
1421
+ downY: screenY
1422
+ });
1423
+ }
1038
1424
  return false;
1039
1425
  }
1040
1426
  const capture = captures.get(native.pointerId);
1041
1427
  if (kind === "move") {
1042
1428
  if (capture !== void 0) {
1043
- const cast2 = castAt(screenX, screenY);
1044
- const hits2 = cast2 !== void 0 && cast2.entity === capture.entity ? cast2.hits : [];
1429
+ hoverInteractive = true;
1430
+ const hits2 = captureHits(screenX, screenY, capture);
1045
1431
  dispatch("onPointerMove", "pointermove", capture.entity, hits2, native, capture.downObject);
1046
1432
  return true;
1047
1433
  }
1434
+ hoverInteractive = false;
1048
1435
  if (native.buttons === 0) {
1049
- const cast2 = castAt(screenX, screenY);
1050
- syncHover(cast2, native);
1051
- if (cast2 !== void 0) {
1052
- dispatch("onPointerMove", "pointermove", cast2.entity, cast2.hits, native);
1436
+ const cast = castAt(screenX, screenY);
1437
+ hoverInteractive = cast !== void 0 && chainInteractive(cast.hits);
1438
+ syncHover(cast, native);
1439
+ if (cast !== void 0) {
1440
+ dispatch("onPointerMove", "pointermove", cast.entity, cast.hits, native);
1053
1441
  }
1054
1442
  }
1055
1443
  return false;
@@ -1057,29 +1445,176 @@ function createGLPointerRouter({ world, bridge, index }) {
1057
1445
  if (capture === void 0) {
1058
1446
  const pending = unclaimedDowns.get(native.pointerId);
1059
1447
  unclaimedDowns.delete(native.pointerId);
1060
- if (kind === "up" && pending !== void 0) {
1061
- const cast2 = castAt(screenX, screenY);
1062
- if (cast2 !== void 0 && cast2.entity === pending.entity && cast2.hits[0]?.object === pending.object) {
1063
- dispatch("onClick", "click", cast2.entity, cast2.hits, native, pending.object);
1448
+ if (kind === "up" && pending !== void 0 && withinTapSlop(pending.downX, pending.downY, screenX, screenY)) {
1449
+ const cast = castAt(screenX, screenY);
1450
+ if (cast !== void 0 && cast.entity === pending.entity && cast.hits[0]?.object === pending.object) {
1451
+ dispatch("onClick", "click", cast.entity, cast.hits, native, pending.object);
1064
1452
  }
1065
1453
  }
1066
1454
  return false;
1067
1455
  }
1068
1456
  captures.delete(native.pointerId);
1069
- const cast = castAt(screenX, screenY);
1070
- const hits = cast !== void 0 && cast.entity === capture.entity ? cast.hits : [];
1457
+ const hits = captureHits(screenX, screenY, capture);
1071
1458
  if (kind === "up") {
1072
1459
  const { claimedBy } = dispatch("onPointerUp", "pointerup", capture.entity, hits, native, capture.downObject);
1073
- const clickTarget = claimedBy ?? capture.downObject;
1074
- if (clickTarget !== void 0 && clickTarget === capture.downObject) {
1075
- dispatch("onClick", "click", capture.entity, hits, native, clickTarget);
1460
+ if (capture.downObject !== void 0 && (claimedBy === void 0 || claimedBy === capture.downObject) && chainHas(hits, capture.downObject) && withinTapSlop(capture.downX, capture.downY, screenX, screenY)) {
1461
+ dispatch("onClick", "click", capture.entity, hits, native, capture.downObject);
1076
1462
  }
1077
1463
  } else {
1078
1464
  dispatch("onPointerCancel", "pointercancel", capture.entity, hits, native, capture.downObject);
1079
1465
  }
1080
1466
  return true;
1467
+ },
1468
+ overInteractive: () => hoverInteractive
1469
+ };
1470
+ }
1471
+
1472
+ // ../r3f/src/preview-capture.tsx
1473
+ import { advance, createRoot } from "@react-three/fiber";
1474
+ import { createElement as createElement3 } from "react";
1475
+ var sandbox = null;
1476
+ function ensureEntity(def) {
1477
+ if (sandbox === null) {
1478
+ const ce = createCanvasEngine();
1479
+ ce.docs.create();
1480
+ ce.world.sync();
1481
+ ce.step(16);
1482
+ sandbox = { ce, entities: /* @__PURE__ */ new Map() };
1483
+ }
1484
+ const hit = sandbox.entities.get(def.type);
1485
+ if (hit !== void 0) return hit;
1486
+ try {
1487
+ const e = sandbox.ce.ops.spawnWidget(def.type, {
1488
+ x: 0,
1489
+ y: 0,
1490
+ undoable: false,
1491
+ ...def.previewProps !== void 0 ? { props: def.previewProps } : {}
1492
+ });
1493
+ sandbox.ce.world.sync();
1494
+ sandbox.ce.step(32);
1495
+ sandbox.entities.set(def.type, e);
1496
+ return e;
1497
+ } catch (err) {
1498
+ console.warn(`ice: preview capture \u2014 sandbox spawn failed for "${def.type}".`, err);
1499
+ return void 0;
1500
+ }
1501
+ }
1502
+ function stubBridge(callbacks) {
1503
+ const stub = {
1504
+ addFrameCallback: (_e, cb) => {
1505
+ callbacks.add(cb);
1506
+ return () => callbacks.delete(cb);
1507
+ },
1508
+ bumpPaint: () => {
1509
+ },
1510
+ setCompositeScale: () => {
1511
+ },
1512
+ setCompositeOpacity: () => {
1081
1513
  }
1082
1514
  };
1515
+ return stub;
1516
+ }
1517
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1518
+ var inFlight = null;
1519
+ function captureWidgetPreviews(opts = {}) {
1520
+ if (inFlight !== null) return inFlight;
1521
+ const run = doCapture(opts).finally(() => {
1522
+ inFlight = null;
1523
+ });
1524
+ inFlight = run;
1525
+ return run;
1526
+ }
1527
+ async function doCapture(opts) {
1528
+ const wanted = opts.types ?? widgets.all().filter((d) => d.surface === "gl").map((d) => d.type);
1529
+ const defs = wanted.map((t) => widgets.get(t)).filter((d) => d !== void 0 && d.surface === "gl" && d.component !== null && d.component !== void 0).filter((d) => !hasPreviewSnapshot(d.type));
1530
+ if (defs.length === 0) return [];
1531
+ const canvas = document.createElement("canvas");
1532
+ const root = createRoot(canvas);
1533
+ const captured = [];
1534
+ let resolvedEnv;
1535
+ try {
1536
+ for (const def of defs) {
1537
+ try {
1538
+ const entity = ensureEntity(def);
1539
+ if (entity === void 0 || sandbox === null) continue;
1540
+ const { w, h } = def.defaultSize;
1541
+ const scale = opts.scale ?? Math.min(2, 512 / Math.max(w, h));
1542
+ const callbacks = /* @__PURE__ */ new Set();
1543
+ root.configure({
1544
+ frameloop: "never",
1545
+ gl: { alpha: true, antialias: true, preserveDrawingBuffer: true },
1546
+ orthographic: true,
1547
+ // The island frustum verbatim (island.tsx / the paint pass): center
1548
+ // origin, Y-up, camera on +Z looking at the content plane; `manual`
1549
+ // pins it against R3F's resize auto-frustum.
1550
+ camera: {
1551
+ left: -w / 2,
1552
+ right: w / 2,
1553
+ top: h / 2,
1554
+ bottom: -h / 2,
1555
+ near: 0.1,
1556
+ far: 2e3,
1557
+ position: [0, 0, 500],
1558
+ manual: true
1559
+ },
1560
+ size: { width: w, height: h, top: 0, left: 0 },
1561
+ dpr: scale
1562
+ });
1563
+ const View = def.component;
1564
+ const store = root.render(
1565
+ createElement3(
1566
+ EngineProvider,
1567
+ { engine: sandbox.ce },
1568
+ createElement3(
1569
+ IslandContext.Provider,
1570
+ { value: { bridge: stubBridge(callbacks), entity } },
1571
+ createElement3(View, { entity, world: sandbox.ce.world })
1572
+ )
1573
+ )
1574
+ );
1575
+ if (resolvedEnv === void 0) {
1576
+ resolvedEnv = typeof opts.environment === "function" ? opts.environment(store.getState().gl) : opts.environment ?? null;
1577
+ }
1578
+ store.getState().scene.environment = resolvedEnv;
1579
+ const cam = store.getState().camera;
1580
+ cam.left = -w / 2;
1581
+ cam.right = w / 2;
1582
+ cam.top = h / 2;
1583
+ cam.bottom = -h / 2;
1584
+ cam.near = 0.1;
1585
+ cam.far = 2e3;
1586
+ cam.position.set(0, 0, 500);
1587
+ cam.lookAt(0, 0, 0);
1588
+ cam.updateProjectionMatrix();
1589
+ cam.updateMatrixWorld(true);
1590
+ await sleep(30);
1591
+ const frames = opts.settleFrames ?? 12;
1592
+ for (let i = 0; i < frames; i++) {
1593
+ for (const cb of callbacks) cb(16.7);
1594
+ advance(i * 16.7 / 1e3, false, store.getState());
1595
+ await sleep(16);
1596
+ }
1597
+ for (const cb of callbacks) cb(16.7);
1598
+ advance(frames * 16.7 / 1e3, false, store.getState());
1599
+ const snap = document.createElement("canvas");
1600
+ snap.width = canvas.width;
1601
+ snap.height = canvas.height;
1602
+ snap.getContext("2d")?.drawImage(canvas, 0, 0);
1603
+ setPreviewSnapshot(def.type, snap);
1604
+ captured.push(def.type);
1605
+ } catch (err) {
1606
+ console.warn(`ice: preview capture failed for "${def.type}" \u2014 its tile keeps the fallback.`, err);
1607
+ }
1608
+ await new Promise((r) => {
1609
+ const w = window;
1610
+ if (w.requestIdleCallback !== void 0) w.requestIdleCallback(() => r(), { timeout: 500 });
1611
+ else setTimeout(r, 50);
1612
+ });
1613
+ }
1614
+ } finally {
1615
+ root.unmount();
1616
+ }
1617
+ return captured;
1083
1618
  }
1084
1619
  export {
1085
1620
  CompositeMaterial,
@@ -1088,6 +1623,7 @@ export {
1088
1623
  IslandContext,
1089
1624
  RenderTargetPool,
1090
1625
  ResourceRegistry,
1626
+ captureWidgetPreviews,
1091
1627
  createGLBridge,
1092
1628
  createGLPointerRouter,
1093
1629
  createIslandStateStore,
@@ -1096,6 +1632,7 @@ export {
1096
1632
  useIslandContext,
1097
1633
  useIslandFrame,
1098
1634
  useIslandInvalidate,
1099
- useIslandLift
1635
+ useIslandLift,
1636
+ useIslandOpacity
1100
1637
  };
1101
1638
  //# sourceMappingURL=r3f.js.map