@vibecook/ice 0.1.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 (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -0
  3. package/dist/chunk-5S7GSXJG.js +6003 -0
  4. package/dist/chunk-5S7GSXJG.js.map +1 -0
  5. package/dist/chunk-BCJ47SX7.js +550 -0
  6. package/dist/chunk-BCJ47SX7.js.map +1 -0
  7. package/dist/chunk-FIAXPNNY.js +1446 -0
  8. package/dist/chunk-FIAXPNNY.js.map +1 -0
  9. package/dist/devtools.js +417 -0
  10. package/dist/devtools.js.map +1 -0
  11. package/dist/dom.js +41 -0
  12. package/dist/dom.js.map +1 -0
  13. package/dist/index.js +450 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/kernel.js +63 -0
  16. package/dist/kernel.js.map +1 -0
  17. package/dist/r3f.js +1101 -0
  18. package/dist/r3f.js.map +1 -0
  19. package/dist/react.js +440 -0
  20. package/dist/react.js.map +1 -0
  21. package/dist/types/packages/core/src/boot/hmr.d.ts +12 -0
  22. package/dist/types/packages/core/src/catalog/camera-derived.d.ts +144 -0
  23. package/dist/types/packages/core/src/catalog/gesture.d.ts +254 -0
  24. package/dist/types/packages/core/src/catalog/graph.d.ts +55 -0
  25. package/dist/types/packages/core/src/catalog/index.d.ts +32 -0
  26. package/dist/types/packages/core/src/catalog/pointer.d.ts +111 -0
  27. package/dist/types/packages/core/src/catalog/scene.d.ts +34 -0
  28. package/dist/types/packages/core/src/catalog/selection-presence.d.ts +53 -0
  29. package/dist/types/packages/core/src/catalog/settings-resources.d.ts +46 -0
  30. package/dist/types/packages/core/src/doc/autosave.d.ts +69 -0
  31. package/dist/types/packages/core/src/doc/bootstrap.d.ts +74 -0
  32. package/dist/types/packages/core/src/doc/broadcast-relay.d.ts +52 -0
  33. package/dist/types/packages/core/src/doc/channels.d.ts +49 -0
  34. package/dist/types/packages/core/src/doc/doc-commit-sink.d.ts +22 -0
  35. package/dist/types/packages/core/src/doc/doc-kit.d.ts +50 -0
  36. package/dist/types/packages/core/src/doc/envelope.d.ts +36 -0
  37. package/dist/types/packages/core/src/doc/migrate.d.ts +69 -0
  38. package/dist/types/packages/core/src/doc/version-gate.d.ts +49 -0
  39. package/dist/types/packages/core/src/engine/commit-sink.d.ts +61 -0
  40. package/dist/types/packages/core/src/engine/engine.d.ts +62 -0
  41. package/dist/types/packages/core/src/engine/frame-info.d.ts +16 -0
  42. package/dist/types/packages/core/src/engine/instantiate.d.ts +45 -0
  43. package/dist/types/packages/core/src/engine/pipeline.d.ts +17 -0
  44. package/dist/types/packages/core/src/engine/reflectors.d.ts +56 -0
  45. package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +120 -0
  46. package/dist/types/packages/core/src/guards/dev.d.ts +2 -0
  47. package/dist/types/packages/core/src/guards/guarded-tx.d.ts +51 -0
  48. package/dist/types/packages/core/src/guards/live-writer.d.ts +33 -0
  49. package/dist/types/packages/core/src/guards/resource-writer.d.ts +17 -0
  50. package/dist/types/packages/core/src/helpers/phase-set.d.ts +70 -0
  51. package/dist/types/packages/core/src/helpers/version-stamps.d.ts +55 -0
  52. package/dist/types/packages/core/src/index.d.ts +63 -0
  53. package/dist/types/packages/core/src/input/measure-queue.d.ts +30 -0
  54. package/dist/types/packages/core/src/input/queue.d.ts +60 -0
  55. package/dist/types/packages/core/src/interaction/install.d.ts +58 -0
  56. package/dist/types/packages/core/src/nav/nested-canvas.d.ts +62 -0
  57. package/dist/types/packages/core/src/ops/cascade.d.ts +18 -0
  58. package/dist/types/packages/core/src/ops/claims.d.ts +15 -0
  59. package/dist/types/packages/core/src/ops/gestures.d.ts +10 -0
  60. package/dist/types/packages/core/src/ops/point-pick.d.ts +42 -0
  61. package/dist/types/packages/core/src/ops/port-geometry.d.ts +17 -0
  62. package/dist/types/packages/core/src/ops/selection.d.ts +14 -0
  63. package/dist/types/packages/core/src/presence/index.d.ts +7 -0
  64. package/dist/types/packages/core/src/presence/presence-kit.d.ts +39 -0
  65. package/dist/types/packages/core/src/presence/publish.d.ts +14 -0
  66. package/dist/types/packages/core/src/presence/remote-cursors.d.ts +18 -0
  67. package/dist/types/packages/core/src/schema/meta.d.ts +43 -0
  68. package/dist/types/packages/core/src/schema/prefab.d.ts +69 -0
  69. package/dist/types/packages/core/src/settings/defaults.d.ts +56 -0
  70. package/dist/types/packages/core/src/systems/camera-sim.d.ts +19 -0
  71. package/dist/types/packages/core/src/systems/chrome.d.ts +48 -0
  72. package/dist/types/packages/core/src/systems/cleanup.d.ts +19 -0
  73. package/dist/types/packages/core/src/systems/l0-input.d.ts +31 -0
  74. package/dist/types/packages/core/src/systems/l1-pick.d.ts +44 -0
  75. package/dist/types/packages/core/src/systems/l1-wires.d.ts +30 -0
  76. package/dist/types/packages/core/src/systems/l2-arbitrate.d.ts +40 -0
  77. package/dist/types/packages/core/src/systems/l2-recognize.d.ts +45 -0
  78. package/dist/types/packages/core/src/systems/l3-behave.d.ts +24 -0
  79. package/dist/types/packages/core/src/systems/l3-claim.d.ts +26 -0
  80. package/dist/types/packages/core/src/systems/l3-connect.d.ts +57 -0
  81. package/dist/types/packages/core/src/systems/l3-draw.d.ts +21 -0
  82. package/dist/types/packages/core/src/systems/l3-drop.d.ts +21 -0
  83. package/dist/types/packages/core/src/systems/l3-marquee.d.ts +35 -0
  84. package/dist/types/packages/core/src/systems/l3-ports.d.ts +30 -0
  85. package/dist/types/packages/core/src/systems/l3-resize.d.ts +17 -0
  86. package/dist/types/packages/core/src/systems/l3-snap.d.ts +19 -0
  87. package/dist/types/packages/core/src/systems/l4-cursor.d.ts +24 -0
  88. package/dist/types/packages/core/src/systems/measure-ingest.d.ts +26 -0
  89. package/dist/types/packages/core/src/tools/define-tool.d.ts +79 -0
  90. package/dist/types/packages/core/src/widget/define-widget.d.ts +124 -0
  91. package/dist/types/packages/core/src/widget/equip.d.ts +14 -0
  92. package/dist/types/packages/core/src/widget/mount-store.d.ts +47 -0
  93. package/dist/types/packages/core/src/widget/props.d.ts +98 -0
  94. package/dist/types/packages/core/src/widget/spawn.d.ts +30 -0
  95. package/dist/types/packages/devtools/src/index.d.ts +8 -0
  96. package/dist/types/packages/devtools/src/panel.d.ts +48 -0
  97. package/dist/types/packages/dom/src/host.d.ts +24 -0
  98. package/dist/types/packages/dom/src/index.d.ts +19 -0
  99. package/dist/types/packages/dom/src/loop.d.ts +11 -0
  100. package/dist/types/packages/dom/src/measure-adapter.d.ts +31 -0
  101. package/dist/types/packages/dom/src/measure-wiring.d.ts +49 -0
  102. package/dist/types/packages/dom/src/planes.d.ts +33 -0
  103. package/dist/types/packages/dom/src/pointer-adapter.d.ts +48 -0
  104. package/dist/types/packages/dom/src/reflectors/chrome.d.ts +33 -0
  105. package/dist/types/packages/dom/src/reflectors/cursor.d.ts +21 -0
  106. package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +54 -0
  107. package/dist/types/packages/dom/src/reflectors/graybox.d.ts +31 -0
  108. package/dist/types/packages/dom/src/reflectors/grid.d.ts +63 -0
  109. package/dist/types/packages/dom/src/reflectors/plane-transform.d.ts +38 -0
  110. package/dist/types/packages/dom/src/reflectors/remote-cursors.d.ts +20 -0
  111. package/dist/types/packages/dom/src/reflectors/wires.d.ts +88 -0
  112. package/dist/types/packages/ice/src/devtools.d.ts +1 -0
  113. package/dist/types/packages/ice/src/dom.d.ts +1 -0
  114. package/dist/types/packages/ice/src/index.d.ts +1 -0
  115. package/dist/types/packages/ice/src/kernel.d.ts +1 -0
  116. package/dist/types/packages/ice/src/r3f.d.ts +1 -0
  117. package/dist/types/packages/ice/src/react.d.ts +1 -0
  118. package/dist/types/packages/kernel/src/anchors.d.ts +18 -0
  119. package/dist/types/packages/kernel/src/bezier.d.ts +38 -0
  120. package/dist/types/packages/kernel/src/coords.d.ts +67 -0
  121. package/dist/types/packages/kernel/src/eviction.d.ts +23 -0
  122. package/dist/types/packages/kernel/src/index.d.ts +13 -0
  123. package/dist/types/packages/kernel/src/shapes.d.ts +22 -0
  124. package/dist/types/packages/kernel/src/snap.d.ts +44 -0
  125. package/dist/types/packages/kernel/src/spatial-index.d.ts +16 -0
  126. package/dist/types/packages/kernel/src/zoom-bands.d.ts +19 -0
  127. package/dist/types/packages/r3f/src/bridge.d.ts +68 -0
  128. package/dist/types/packages/r3f/src/composite-material.d.ts +17 -0
  129. package/dist/types/packages/r3f/src/compositor-pass.d.ts +83 -0
  130. package/dist/types/packages/r3f/src/dev-write-trap.d.ts +30 -0
  131. package/dist/types/packages/r3f/src/gl-root.d.ts +21 -0
  132. package/dist/types/packages/r3f/src/gl-router.d.ts +60 -0
  133. package/dist/types/packages/r3f/src/index.d.ts +16 -0
  134. package/dist/types/packages/r3f/src/island-state.d.ts +68 -0
  135. package/dist/types/packages/r3f/src/island.d.ts +13 -0
  136. package/dist/types/packages/r3f/src/pool.d.ts +56 -0
  137. package/dist/types/packages/r3f/src/resource-registry.d.ts +30 -0
  138. package/dist/types/packages/r3f/src/use-island-frame.d.ts +24 -0
  139. package/dist/types/packages/react/src/engine-context.d.ts +31 -0
  140. package/dist/types/packages/react/src/hooks.d.ts +40 -0
  141. package/dist/types/packages/react/src/index.d.ts +14 -0
  142. package/dist/types/packages/react/src/infinite-canvas.d.ts +71 -0
  143. package/dist/types/packages/react/src/keymap.d.ts +42 -0
  144. package/dist/types/packages/react/src/use-commit.d.ts +35 -0
  145. package/dist/types/packages/react/src/use-presence.d.ts +23 -0
  146. package/dist/types/packages/react/src/use-tool.d.ts +4 -0
  147. package/dist/types/packages/react/src/widget-root.d.ts +33 -0
  148. package/package.json +112 -0
package/dist/r3f.js ADDED
@@ -0,0 +1,1101 @@
1
+ import {
2
+ Camera,
3
+ Grab,
4
+ Position,
5
+ PrefabId,
6
+ RUNTIME_BUDGETS,
7
+ Size,
8
+ StackZ,
9
+ Viewport,
10
+ Visible,
11
+ pickTopAt,
12
+ widgets
13
+ } from "./chunk-5S7GSXJG.js";
14
+ import {
15
+ compositeCameraFrustum,
16
+ computeIslandPhase,
17
+ fboPixelSize,
18
+ isOutOfBand,
19
+ screenToWorld,
20
+ selectBand,
21
+ selectEvictions,
22
+ worldRectToComposite,
23
+ worldToIsland
24
+ } from "./chunk-BCJ47SX7.js";
25
+
26
+ // ../r3f/src/island-state.ts
27
+ var FRESH_PAINTED_AT = { w: 0, h: 0, dpr: 1, band: 0 };
28
+ function createIslandStateStore() {
29
+ const entries = /* @__PURE__ */ new Map();
30
+ let paintSeq = 0;
31
+ const ensure = (key) => {
32
+ let s = entries.get(key);
33
+ if (s === void 0) {
34
+ s = {
35
+ phase: "Cold",
36
+ paintGeneration: 0,
37
+ fboGeneration: -1,
38
+ paintedAt: FRESH_PAINTED_AT,
39
+ animRefs: 0,
40
+ animatedDecl: false,
41
+ lastPaintSeq: 0,
42
+ compositeScale: 1
43
+ };
44
+ entries.set(key, s);
45
+ }
46
+ return s;
47
+ };
48
+ return {
49
+ ensure,
50
+ get: (key) => entries.get(key),
51
+ all: () => entries.entries(),
52
+ bumpPaint(key) {
53
+ ensure(key).paintGeneration += 1;
54
+ },
55
+ markPainted(key, at) {
56
+ const s = ensure(key);
57
+ s.fboGeneration = s.paintGeneration;
58
+ s.paintedAt = at;
59
+ s.lastPaintSeq = ++paintSeq;
60
+ },
61
+ markEvicted(key) {
62
+ const s = entries.get(key);
63
+ if (s !== void 0) s.fboGeneration = -1;
64
+ },
65
+ addAnimRef(key) {
66
+ ensure(key).animRefs += 1;
67
+ },
68
+ removeAnimRef(key) {
69
+ const s = entries.get(key);
70
+ if (s !== void 0 && s.animRefs > 0) s.animRefs -= 1;
71
+ },
72
+ isAnimating(key) {
73
+ const s = entries.get(key);
74
+ return s !== void 0 && (s.animatedDecl || s.animRefs > 0);
75
+ },
76
+ setAnimatedDecl(key, animated) {
77
+ ensure(key).animatedDecl = animated;
78
+ },
79
+ drop(key) {
80
+ entries.delete(key);
81
+ },
82
+ clear() {
83
+ entries.clear();
84
+ }
85
+ };
86
+ }
87
+
88
+ // ../r3f/src/dev-write-trap.ts
89
+ function createRenderWriteTrap(world, enabled) {
90
+ let armed = false;
91
+ let unregister = null;
92
+ if (enabled) {
93
+ unregister = world.devOnWrite((kind) => {
94
+ if (!armed) return;
95
+ throw new Error(
96
+ `ice: world mutation (${kind}) during the GL render pass \u2014 the render path never writes ECS (design-004 \xA73). Move the write to a system, an input fact, or an app op OUTSIDE useIslandFrame/render.`
97
+ );
98
+ });
99
+ }
100
+ return {
101
+ enabled,
102
+ begin() {
103
+ if (!enabled) return;
104
+ if (armed) throw new Error("ice: render write trap begin() while already armed.");
105
+ armed = true;
106
+ },
107
+ end() {
108
+ armed = false;
109
+ },
110
+ dispose() {
111
+ armed = false;
112
+ unregister?.();
113
+ unregister = null;
114
+ }
115
+ };
116
+ }
117
+
118
+ // ../r3f/src/bridge.ts
119
+ function createGLBridge(engine, opts = {}) {
120
+ const world = engine.world;
121
+ const state = createIslandStateStore();
122
+ const islands = /* @__PURE__ */ new Map();
123
+ const frameCbs = /* @__PURE__ */ new Map();
124
+ const renderAssert = createRenderWriteTrap(world, opts.devAssertRenderWrites === true);
125
+ let invalidate = null;
126
+ let pending = false;
127
+ const requestFrame = () => {
128
+ if (invalidate !== null) invalidate();
129
+ else pending = true;
130
+ };
131
+ const removeReflector = engine.registerReflector({
132
+ name: "r3fInvalidator",
133
+ observe: { resources: [Camera, Viewport] },
134
+ flush: () => requestFrame()
135
+ });
136
+ const removeWorldObserver = world.observe({
137
+ onDestroy: (e) => {
138
+ if (state.get(e) !== void 0) {
139
+ state.drop(e);
140
+ frameCbs.delete(e);
141
+ requestFrame();
142
+ }
143
+ },
144
+ onReset: () => {
145
+ state.clear();
146
+ frameCbs.clear();
147
+ requestFrame();
148
+ }
149
+ });
150
+ return {
151
+ engine,
152
+ state,
153
+ renderAssert,
154
+ registerIsland(entity, handle) {
155
+ islands.set(entity, handle);
156
+ const s = state.ensure(entity);
157
+ const type = world.get(entity, PrefabId)?.id;
158
+ const widget = typeof type === "string" ? widgets.get(type) : void 0;
159
+ state.setAnimatedDecl(entity, widget?.animated === true);
160
+ const unsubs = [
161
+ // island dirt — content resolution/props changed, FBO stale:
162
+ world.reactive.observeValue(entity, Size, () => {
163
+ state.bumpPaint(entity);
164
+ requestFrame();
165
+ }),
166
+ // composite dirt — quad transform/order changed, texture still good:
167
+ world.reactive.observeValue(entity, Position, () => requestFrame()),
168
+ world.reactive.observeValue(entity, StackZ, () => requestFrame())
169
+ ];
170
+ for (const group of widget?.groups ?? []) {
171
+ unsubs.push(
172
+ world.reactive.observeValue(entity, group.component, () => {
173
+ state.bumpPaint(entity);
174
+ requestFrame();
175
+ })
176
+ );
177
+ }
178
+ if (s.fboGeneration < 0) state.bumpPaint(entity);
179
+ requestFrame();
180
+ return () => {
181
+ for (const u of unsubs) u();
182
+ islands.delete(entity);
183
+ requestFrame();
184
+ };
185
+ },
186
+ islandFor: (entity) => islands.get(entity),
187
+ islands: () => islands.entries(),
188
+ addFrameCallback(entity, cb) {
189
+ let set = frameCbs.get(entity);
190
+ if (set === void 0) {
191
+ set = /* @__PURE__ */ new Set();
192
+ frameCbs.set(entity, set);
193
+ }
194
+ set.add(cb);
195
+ state.addAnimRef(entity);
196
+ requestFrame();
197
+ return () => {
198
+ set.delete(cb);
199
+ state.removeAnimRef(entity);
200
+ };
201
+ },
202
+ frameCallbacksFor(entity) {
203
+ const set = frameCbs.get(entity);
204
+ return set === void 0 ? [] : [...set];
205
+ },
206
+ requestFrame,
207
+ bumpPaint(entity) {
208
+ state.bumpPaint(entity);
209
+ requestFrame();
210
+ },
211
+ setCompositeScale(entity, scale) {
212
+ const s = state.ensure(entity);
213
+ if (s.compositeScale === scale) return;
214
+ s.compositeScale = scale;
215
+ requestFrame();
216
+ },
217
+ setInvalidate(fn) {
218
+ invalidate = fn;
219
+ if (fn !== null && pending) {
220
+ pending = false;
221
+ fn();
222
+ }
223
+ },
224
+ uninstall() {
225
+ removeReflector();
226
+ removeWorldObserver();
227
+ renderAssert.dispose();
228
+ islands.clear();
229
+ frameCbs.clear();
230
+ state.clear();
231
+ invalidate = null;
232
+ }
233
+ };
234
+ }
235
+
236
+ // ../r3f/src/compositor-pass.ts
237
+ var GRABBED_RENDER_ORDER = 1e9;
238
+ function runCompositorPass(ctx) {
239
+ const { world, bridge, pool, quads, gl } = ctx;
240
+ const stats = { repainted: 0, pendingPaints: 0, evicted: 0, fboBytes: 0, anyHot: false };
241
+ const cam = world.getResource(Camera);
242
+ const vp = world.getResource(Viewport);
243
+ if (cam === void 0 || vp === void 0 || vp.w === 0) return stats;
244
+ ctx.compCamera.setFrustum(compositeCameraFrustum(cam, vp.w, vp.h));
245
+ const idleDpr = vp.dpr > 0 ? vp.dpr : 1;
246
+ const targetDpr = cam.gesturing ? Math.min(idleDpr, 1) : idleDpr;
247
+ if (gl.getPixelRatio() !== targetDpr) gl.setPixelRatio(targetDpr);
248
+ const phaseOf = (e) => {
249
+ const alive = world.isAlive(e);
250
+ const visible = alive && world.hasTag(e, Visible) && bridge.islandFor(e) !== void 0;
251
+ const s = bridge.state.get(e);
252
+ const hasFbo = s !== void 0 && s.fboGeneration >= 0 && pool.get(e) !== null;
253
+ return computeIslandPhase(true, visible, bridge.state.isAnimating(e), hasFbo);
254
+ };
255
+ for (const [e, s] of bridge.state.all()) {
256
+ s.phase = phaseOf(e);
257
+ if (s.phase === "Hot") stats.anyHot = true;
258
+ }
259
+ const band = selectBand(cam.zoom);
260
+ const effectiveDpr = gl.getPixelRatio() * band;
261
+ const toPaint = [];
262
+ for (const [e, s] of bridge.state.all()) {
263
+ if (bridge.islandFor(e) === void 0) continue;
264
+ const wantsPhase = s.phase === "Hot" || s.phase === "Waking";
265
+ 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);
268
+ }
269
+ toPaint.sort((a, b) => {
270
+ const sa = bridge.state.get(a);
271
+ const sb = bridge.state.get(b);
272
+ const coldA = sa !== void 0 && sa.fboGeneration < 0 ? 0 : 1;
273
+ const coldB = sb !== void 0 && sb.fboGeneration < 0 ? 0 : 1;
274
+ if (coldA !== coldB) return coldA - coldB;
275
+ return (sa?.lastPaintSeq ?? 0) - (sb?.lastPaintSeq ?? 0);
276
+ });
277
+ const cap = Math.max(1, ctx.maxRepaintsPerFrame);
278
+ for (const e of toPaint) {
279
+ if (stats.repainted >= cap) {
280
+ stats.pendingPaints = toPaint.length - stats.repainted;
281
+ break;
282
+ }
283
+ const handle = bridge.islandFor(e);
284
+ const s = bridge.state.get(e);
285
+ const size = world.isAlive(e) ? world.get(e, Size) : void 0;
286
+ if (handle === void 0 || s === void 0 || size === void 0 || size.w <= 0 || size.h <= 0) continue;
287
+ if (s.phase === "Hot") {
288
+ for (const cb of bridge.frameCallbacksFor(e)) cb(ctx.dtMs);
289
+ }
290
+ ctx.islandCamera(e)?.setFrustum(size.w / 2, size.h / 2);
291
+ const fbo = pool.acquire(e, size.w, size.h, effectiveDpr);
292
+ gl.setRenderTarget(fbo);
293
+ try {
294
+ gl.clear();
295
+ gl.render(handle.scene, handle.camera);
296
+ } finally {
297
+ gl.setRenderTarget(null);
298
+ }
299
+ const px = fboPixelSize(size.w, size.h, effectiveDpr, 1);
300
+ bridge.state.markPainted(e, { w: px.width, h: px.height, dpr: effectiveDpr, band });
301
+ stats.repainted += 1;
302
+ }
303
+ if (pool.bytesUsed() > ctx.maxFboBytes) {
304
+ const candidates = [];
305
+ for (const info of pool.entryInfos()) {
306
+ const s = bridge.state.get(info.key);
307
+ const phase = s === void 0 ? "Dormant" : phaseOf(info.key);
308
+ if (s !== void 0) s.phase = phase;
309
+ candidates.push({
310
+ id: info.key,
311
+ phase,
312
+ bytes: info.bytes,
313
+ lastUsedMs: info.lastUsedMs
314
+ });
315
+ }
316
+ for (const key of selectEvictions(candidates, pool.bytesUsed(), ctx.maxFboBytes)) {
317
+ if (bridge.state.get(key)?.phase === "Dormant") {
318
+ console.debug(`[ice/r3f] evicting Dormant island ${key} \u2014 consider raising the FBO budget`);
319
+ }
320
+ pool.release(key);
321
+ bridge.state.markEvicted(key);
322
+ stats.evicted += 1;
323
+ }
324
+ }
325
+ const liveKeys = /* @__PURE__ */ new Set();
326
+ for (const [e] of bridge.state.all()) liveKeys.add(e);
327
+ for (const info of pool.entryInfos()) {
328
+ if (!liveKeys.has(info.key)) pool.release(info.key);
329
+ }
330
+ for (const key of quads.keys()) {
331
+ if (!liveKeys.has(key)) quads.remove(key);
332
+ }
333
+ for (const [e, s] of bridge.state.all()) {
334
+ const quad = quads.ensure(e);
335
+ const alive = world.isAlive(e);
336
+ const fbo = pool.get(e);
337
+ const pos = alive ? world.get(e, Position) : void 0;
338
+ const size = alive ? world.get(e, Size) : void 0;
339
+ if (fbo === null || s.fboGeneration < 0 || pos === void 0 || size === void 0) {
340
+ quad.setVisible(false);
341
+ continue;
342
+ }
343
+ const q = worldRectToComposite({ x: pos.x, y: pos.y, width: size.w, height: size.h });
344
+ const lift = s.compositeScale;
345
+ quad.setTransform(q.x, q.y, q.sx * lift, q.sy * lift);
346
+ quad.setTexture(fbo.texture);
347
+ const grabbed = world.get(e, Grab) !== void 0;
348
+ quad.setRenderOrder(grabbed || lift !== 1 ? GRABBED_RENDER_ORDER : world.get(e, StackZ)?.z ?? 0);
349
+ quad.setVisible(true);
350
+ pool.touch(e);
351
+ }
352
+ gl.setRenderTarget(null);
353
+ gl.clear();
354
+ gl.render(ctx.compositeScene, ctx.compCamera.raw);
355
+ stats.fboBytes = pool.bytesUsed();
356
+ return stats;
357
+ }
358
+
359
+ // ../r3f/src/gl-root.tsx
360
+ import { useFrame, useThree } from "@react-three/fiber";
361
+ import {
362
+ createElement as createElement2,
363
+ useEffect as useEffect3,
364
+ useMemo as useMemo2,
365
+ useRef as useRef2,
366
+ useSyncExternalStore
367
+ } from "react";
368
+ import { Mesh, OrthographicCamera as OrthographicCamera2, PlaneGeometry } from "three";
369
+
370
+ // ../r3f/src/composite-material.ts
371
+ import { ShaderMaterial } from "three";
372
+ var VERTEX_SHADER = (
373
+ /* glsl */
374
+ `
375
+ varying vec2 vUv;
376
+ void main() {
377
+ vUv = uv;
378
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
379
+ }
380
+ `
381
+ );
382
+ var FRAGMENT_SHADER = (
383
+ /* glsl */
384
+ `
385
+ uniform sampler2D map;
386
+ uniform float uOpacity;
387
+ varying vec2 vUv;
388
+
389
+ void main() {
390
+ vec4 c = texture2D(map, vUv);
391
+ if (c.a < 0.001) discard;
392
+ c.a *= uOpacity;
393
+ gl_FragColor = c;
394
+ #include <colorspace_fragment>
395
+ }
396
+ `
397
+ );
398
+ var CompositeMaterial = class extends ShaderMaterial {
399
+ // Direct handles onto our two uniform cells. ShaderMaterial types `uniforms`
400
+ // as an index signature, so `this.uniforms.map` reads as possibly-undefined
401
+ // under strict indexing; holding the cells we created sidesteps that and keeps
402
+ // the setters allocation-free.
403
+ mapUniform;
404
+ opacityUniform;
405
+ constructor() {
406
+ const uniforms = {
407
+ map: { value: null },
408
+ uOpacity: { value: 1 }
409
+ };
410
+ super({
411
+ vertexShader: VERTEX_SHADER,
412
+ fragmentShader: FRAGMENT_SHADER,
413
+ uniforms,
414
+ transparent: true,
415
+ depthWrite: false,
416
+ // Explicit: this pass must never tone-map (the FBO is already display-
417
+ // ready). v1 left toneMapped at the ShaderMaterial default and relied on
418
+ // the absent tonemapping chunk alone (CompositionMaterial.ts:140-142); we
419
+ // set it false so the no-tone-map contract holds even if the shader ever
420
+ // gains chunks. Behaviour is identical for the current shader.
421
+ toneMapped: false
422
+ });
423
+ this.mapUniform = uniforms.map;
424
+ this.opacityUniform = uniforms.uOpacity;
425
+ }
426
+ /** Bind the island's FBO texture (null clears). */
427
+ setMap(map) {
428
+ this.mapUniform.value = map;
429
+ }
430
+ /** Composite opacity. Pass-through — no clamping; callers pass valid 0..1. */
431
+ setOpacity(opacity) {
432
+ this.opacityUniform.value = opacity;
433
+ }
434
+ // dispose() is inherited from ShaderMaterial — it fires the dispose event that
435
+ // frees the compiled program; no override needed.
436
+ };
437
+
438
+ // ../r3f/src/island.tsx
439
+ import { createPortal } from "@react-three/fiber";
440
+ import { createElement, useEffect as useEffect2, useMemo } from "react";
441
+ import { OrthographicCamera, Scene } from "three";
442
+
443
+ // ../r3f/src/use-island-frame.ts
444
+ import { createContext, useCallback, useContext, useEffect, useRef } from "react";
445
+ var IslandContext = createContext(null);
446
+ function useIslandContext() {
447
+ const ctx = useContext(IslandContext);
448
+ if (ctx === null) {
449
+ throw new Error("ice: useIslandFrame/useIslandInvalidate must run inside a GL widget's island.");
450
+ }
451
+ return ctx;
452
+ }
453
+ function useIslandFrame(cb) {
454
+ const { bridge, entity } = useIslandContext();
455
+ const ref = useRef(cb);
456
+ ref.current = cb;
457
+ useEffect(() => bridge.addFrameCallback(entity, (dt) => ref.current(dt)), [bridge, entity]);
458
+ }
459
+ function useIslandInvalidate() {
460
+ const { bridge, entity } = useIslandContext();
461
+ return useCallback(() => bridge.bumpPaint(entity), [bridge, entity]);
462
+ }
463
+ function useIslandLift(scale) {
464
+ const { bridge, entity } = useIslandContext();
465
+ useEffect(() => {
466
+ bridge.setCompositeScale(entity, scale);
467
+ }, [bridge, entity, scale]);
468
+ useEffect(() => () => bridge.setCompositeScale(entity, 1), [bridge, entity]);
469
+ }
470
+
471
+ // ../r3f/src/island.tsx
472
+ function Island({ bridge, world, entity, widget, environment }) {
473
+ const scene = useMemo(() => new Scene(), []);
474
+ const camera = useMemo(() => {
475
+ const cam = new OrthographicCamera(-1, 1, 1, -1, 0.1, 2e3);
476
+ cam.position.set(0, 0, 500);
477
+ cam.lookAt(0, 0, 0);
478
+ return cam;
479
+ }, []);
480
+ useEffect2(
481
+ () => bridge.registerIsland(entity, { scene, camera }),
482
+ [bridge, entity, scene, camera]
483
+ );
484
+ useEffect2(() => {
485
+ scene.environment = environment ?? null;
486
+ bridge.bumpPaint(entity);
487
+ }, [environment, scene, bridge, entity]);
488
+ const View = widget.component;
489
+ if (View == null) return null;
490
+ return createPortal(
491
+ createElement(
492
+ IslandContext.Provider,
493
+ { value: { bridge, entity } },
494
+ createElement(View, { entity, world })
495
+ ),
496
+ scene
497
+ );
498
+ }
499
+
500
+ // ../r3f/src/pool.ts
501
+ import { SRGBColorSpace, WebGLRenderTarget } from "three";
502
+ var COLOR_BYTES_PER_PIXEL = 4;
503
+ var SAMPLES = 4;
504
+ function targetBytes(pixelWidth, pixelHeight) {
505
+ return pixelWidth * pixelHeight * COLOR_BYTES_PER_PIXEL * (SAMPLES > 1 ? 2 : 1);
506
+ }
507
+ var RenderTargetPool = class {
508
+ entries = /* @__PURE__ */ new Map();
509
+ totalBytes = 0;
510
+ disposed = false;
511
+ now;
512
+ /**
513
+ * @param now Injected monotonic clock for LRU timestamps. Defaults to
514
+ * `performance.now()`; tests pass a deterministic stub so `lastUsedMs`
515
+ * assertions do not depend on wall-clock timing.
516
+ */
517
+ constructor(now = () => performance.now()) {
518
+ this.now = now;
519
+ }
520
+ /**
521
+ * Get or create an FBO for `key` at the requested world size and effective
522
+ * device-pixel-ratio. Returns the existing target unchanged when its pixel
523
+ * dimensions already match; otherwise disposes the stale target and allocates
524
+ * a new one sized to the new resolution.
525
+ */
526
+ acquire(key, worldW, worldH, effectiveDpr) {
527
+ if (this.disposed) {
528
+ throw new Error("RenderTargetPool: cannot acquire after dispose");
529
+ }
530
+ const pixelWidth = Math.max(1, Math.round(worldW * effectiveDpr));
531
+ const pixelHeight = Math.max(1, Math.round(worldH * effectiveDpr));
532
+ const nowMs = this.now();
533
+ const existing = this.entries.get(key);
534
+ if (existing && existing.pixelWidth === pixelWidth && existing.pixelHeight === pixelHeight) {
535
+ existing.lastUsedMs = nowMs;
536
+ return existing.rt;
537
+ }
538
+ if (existing) {
539
+ existing.rt.dispose();
540
+ this.totalBytes -= existing.bytes;
541
+ }
542
+ const rt = new WebGLRenderTarget(pixelWidth, pixelHeight, { samples: SAMPLES });
543
+ rt.texture.colorSpace = SRGBColorSpace;
544
+ const bytes = targetBytes(pixelWidth, pixelHeight);
545
+ this.entries.set(key, { rt, pixelWidth, pixelHeight, effectiveDpr, bytes, lastUsedMs: nowMs });
546
+ this.totalBytes += bytes;
547
+ return rt;
548
+ }
549
+ /** Look up an FBO without creating one. */
550
+ get(key) {
551
+ return this.entries.get(key)?.rt ?? null;
552
+ }
553
+ /**
554
+ * Refresh `lastUsedMs` without re-acquiring. The compositor calls this for
555
+ * every island it samples in the composition pass — without it, warm islands
556
+ * that never repaint would freeze their timestamp at their last paint and the
557
+ * LRU would treat still-visible islands as stale (design-004 §3).
558
+ */
559
+ touch(key) {
560
+ const entry = this.entries.get(key);
561
+ if (!entry) return;
562
+ entry.lastUsedMs = this.now();
563
+ }
564
+ /**
565
+ * Release `key`'s FBO. Returns true if something was released. Safe after
566
+ * dispose — returns false rather than corrupting the byte counter or
567
+ * double-disposing the target.
568
+ */
569
+ release(key) {
570
+ if (this.disposed) return false;
571
+ const entry = this.entries.get(key);
572
+ if (!entry) return false;
573
+ entry.rt.dispose();
574
+ this.totalBytes = Math.max(0, this.totalBytes - entry.bytes);
575
+ this.entries.delete(key);
576
+ return true;
577
+ }
578
+ /** Total GPU bytes consumed by the pool. */
579
+ bytesUsed() {
580
+ return this.totalBytes;
581
+ }
582
+ /** Number of FBOs currently held. */
583
+ size() {
584
+ return this.entries.size;
585
+ }
586
+ /** True after `dispose()` — callers should re-create the pool instead of using it. */
587
+ isDisposed() {
588
+ return this.disposed;
589
+ }
590
+ /** Iterate live entries (key + target). */
591
+ forEach(cb) {
592
+ for (const [key, entry] of this.entries) cb(key, entry.rt);
593
+ }
594
+ /**
595
+ * Snapshot of every live entry's `bytes` + `lastUsedMs` — the input an
596
+ * eviction pass ranks by LRU.
597
+ */
598
+ entryInfos() {
599
+ const out = [];
600
+ for (const [key, entry] of this.entries) {
601
+ out.push({ key, bytes: entry.bytes, lastUsedMs: entry.lastUsedMs });
602
+ }
603
+ return out;
604
+ }
605
+ /** Dispose every FBO. After this, acquire throws and release returns false. */
606
+ dispose() {
607
+ if (this.disposed) return;
608
+ for (const entry of this.entries.values()) entry.rt.dispose();
609
+ this.entries.clear();
610
+ this.totalBytes = 0;
611
+ this.disposed = true;
612
+ }
613
+ };
614
+
615
+ // ../r3f/src/gl-root.tsx
616
+ function GLViews({
617
+ engine,
618
+ bridge,
619
+ store,
620
+ maxFboBytes = RUNTIME_BUDGETS.fboBytes,
621
+ maxRepaintsPerFrame = 4,
622
+ environment
623
+ }) {
624
+ const world = engine.world;
625
+ const gl = useThree((s) => s.gl);
626
+ const scene = useThree((s) => s.scene);
627
+ const set = useThree((s) => s.set);
628
+ const invalidate = useThree((s) => s.invalidate);
629
+ const poolRef = useRef2(null);
630
+ if (poolRef.current === null || poolRef.current.isDisposed()) {
631
+ poolRef.current = new RenderTargetPool();
632
+ }
633
+ const pool = poolRef.current;
634
+ const quadGeometry = useMemo2(() => new PlaneGeometry(1, 1), []);
635
+ const compCamera = useMemo2(() => new OrthographicCamera2(0, 1, 0, -1, 0.1, 1e4), []);
636
+ useEffect3(() => {
637
+ set({ camera: compCamera });
638
+ }, [set, compCamera]);
639
+ const quadsRef = useRef2(/* @__PURE__ */ new Map());
640
+ const quads = useMemo2(
641
+ () => ({
642
+ ensure(key) {
643
+ let mesh = quadsRef.current.get(key);
644
+ if (mesh === void 0) {
645
+ mesh = new Mesh(quadGeometry, new CompositeMaterial());
646
+ mesh.frustumCulled = false;
647
+ mesh.visible = false;
648
+ scene.add(mesh);
649
+ quadsRef.current.set(key, mesh);
650
+ }
651
+ const m = mesh;
652
+ return {
653
+ setTransform: (x, y, sx, sy) => {
654
+ m.position.set(x, y, 0);
655
+ m.scale.set(sx, sy, 1);
656
+ },
657
+ setTexture: (t) => m.material.setMap(t),
658
+ setVisible: (v) => {
659
+ m.visible = v;
660
+ },
661
+ setRenderOrder: (n) => {
662
+ m.renderOrder = n;
663
+ }
664
+ };
665
+ },
666
+ remove(key) {
667
+ const mesh = quadsRef.current.get(key);
668
+ if (mesh !== void 0) {
669
+ scene.remove(mesh);
670
+ mesh.material.dispose();
671
+ quadsRef.current.delete(key);
672
+ }
673
+ },
674
+ keys: () => [...quadsRef.current.keys()]
675
+ }),
676
+ [scene, quadGeometry]
677
+ );
678
+ const glLike = useMemo2(
679
+ () => ({
680
+ setRenderTarget: (t) => gl.setRenderTarget(t),
681
+ clear: () => {
682
+ gl.setClearColor(0, 0);
683
+ gl.clear(true, true, false);
684
+ },
685
+ render: (s, c) => gl.render(s, c),
686
+ setPixelRatio: (n) => gl.setPixelRatio(n),
687
+ getPixelRatio: () => gl.getPixelRatio()
688
+ }),
689
+ [gl]
690
+ );
691
+ useEffect3(() => {
692
+ bridge.setInvalidate(() => invalidate());
693
+ return () => bridge.setInvalidate(null);
694
+ }, [bridge, invalidate]);
695
+ useEffect3(() => {
696
+ const quadMap = quadsRef.current;
697
+ return () => {
698
+ for (const mesh of quadMap.values()) {
699
+ scene.remove(mesh);
700
+ mesh.material.dispose();
701
+ }
702
+ quadMap.clear();
703
+ pool.dispose();
704
+ for (const [key] of bridge.state.all()) bridge.state.markEvicted(key);
705
+ };
706
+ }, [scene, pool, bridge]);
707
+ useFrame((_, delta) => {
708
+ bridge.renderAssert.begin();
709
+ let stats;
710
+ try {
711
+ stats = runCompositorPass({
712
+ world,
713
+ bridge,
714
+ pool,
715
+ quads,
716
+ gl: glLike,
717
+ compCamera: {
718
+ raw: compCamera,
719
+ // the REAL camera — gl.render instanceof-checks it
720
+ setFrustum: (f) => {
721
+ compCamera.left = f.left;
722
+ compCamera.right = f.right;
723
+ compCamera.top = f.top;
724
+ compCamera.bottom = f.bottom;
725
+ compCamera.position.set(f.x, f.y, 1e3);
726
+ compCamera.updateProjectionMatrix();
727
+ }
728
+ },
729
+ islandCamera: (entity) => {
730
+ const handle = bridge.islandFor(entity);
731
+ if (handle === void 0) return void 0;
732
+ return {
733
+ setFrustum: (halfW, halfH) => {
734
+ handle.camera.left = -halfW;
735
+ handle.camera.right = halfW;
736
+ handle.camera.top = halfH;
737
+ handle.camera.bottom = -halfH;
738
+ handle.camera.updateProjectionMatrix();
739
+ }
740
+ };
741
+ },
742
+ compositeScene: scene,
743
+ maxFboBytes,
744
+ maxRepaintsPerFrame,
745
+ dtMs: delta * 1e3
746
+ });
747
+ } finally {
748
+ bridge.renderAssert.end();
749
+ }
750
+ if (stats.anyHot || stats.pendingPaints > 0) invalidate();
751
+ }, 1);
752
+ const entries = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
753
+ const islands = [];
754
+ for (const entry of entries) {
755
+ if (entry.hidden) continue;
756
+ if (!world.isAlive(entry.entity)) continue;
757
+ const type = world.get(entry.entity, PrefabId)?.id;
758
+ const widget = typeof type === "string" ? widgets.get(type) : void 0;
759
+ if (widget === void 0 || widget.surface !== "gl") continue;
760
+ islands.push(
761
+ createElement2(Island, {
762
+ key: String(entry.entity),
763
+ bridge,
764
+ world,
765
+ entity: entry.entity,
766
+ widget,
767
+ ...environment !== void 0 ? { environment } : {}
768
+ })
769
+ );
770
+ }
771
+ return createElement2("group", null, islands);
772
+ }
773
+
774
+ // ../r3f/src/resource-registry.ts
775
+ var ResourceRegistry = class {
776
+ geometries = /* @__PURE__ */ new Map();
777
+ materials = /* @__PURE__ */ new Map();
778
+ textures = /* @__PURE__ */ new Map();
779
+ disposed = false;
780
+ acquireGeometry(key, factory) {
781
+ const existing = this.geometries.get(key);
782
+ if (existing) {
783
+ existing.refCount++;
784
+ return existing.resource;
785
+ }
786
+ const resource = factory();
787
+ this.geometries.set(key, { resource, refCount: 1 });
788
+ return resource;
789
+ }
790
+ releaseGeometry(key) {
791
+ this.releaseFrom(this.geometries, key);
792
+ }
793
+ acquireMaterial(key, factory) {
794
+ const existing = this.materials.get(key);
795
+ if (existing) {
796
+ existing.refCount++;
797
+ return existing.resource;
798
+ }
799
+ const resource = factory();
800
+ this.materials.set(key, { resource, refCount: 1 });
801
+ return resource;
802
+ }
803
+ releaseMaterial(key) {
804
+ this.releaseFrom(this.materials, key);
805
+ }
806
+ acquireTexture(key, factory) {
807
+ const existing = this.textures.get(key);
808
+ if (existing) {
809
+ existing.refCount++;
810
+ return existing.resource;
811
+ }
812
+ const resource = factory();
813
+ this.textures.set(key, { resource, refCount: 1 });
814
+ return resource;
815
+ }
816
+ releaseTexture(key) {
817
+ this.releaseFrom(this.textures, key);
818
+ }
819
+ /** Number of distinct shared geometries currently held. */
820
+ geometryCount() {
821
+ return this.geometries.size;
822
+ }
823
+ /** Number of distinct shared materials currently held. */
824
+ materialCount() {
825
+ return this.materials.size;
826
+ }
827
+ /** Number of distinct shared textures currently held. */
828
+ textureCount() {
829
+ return this.textures.size;
830
+ }
831
+ /**
832
+ * Estimated GPU bytes for shared geometry attribute buffers. Best-effort —
833
+ * actual footprint depends on driver alignment, but a useful relative metric
834
+ * for the profiler.
835
+ */
836
+ geometryBytes() {
837
+ let total = 0;
838
+ for (const { resource } of this.geometries.values()) {
839
+ for (const attr of Object.values(resource.attributes)) {
840
+ if ("array" in attr && attr.array.byteLength) {
841
+ total += attr.array.byteLength;
842
+ }
843
+ }
844
+ if (resource.index) {
845
+ total += resource.index.array.byteLength;
846
+ }
847
+ }
848
+ return total;
849
+ }
850
+ /** Dispose every resource and clear the registry. */
851
+ dispose() {
852
+ if (this.disposed) return;
853
+ for (const { resource } of this.geometries.values()) resource.dispose();
854
+ for (const { resource } of this.materials.values()) resource.dispose();
855
+ for (const { resource } of this.textures.values()) resource.dispose();
856
+ this.geometries.clear();
857
+ this.materials.clear();
858
+ this.textures.clear();
859
+ this.disposed = true;
860
+ }
861
+ /** True after `dispose()` — callers should re-create the registry instead of using it. */
862
+ isDisposed() {
863
+ return this.disposed;
864
+ }
865
+ releaseFrom(map, key) {
866
+ const entry = map.get(key);
867
+ if (!entry) return;
868
+ entry.refCount--;
869
+ if (entry.refCount <= 0) {
870
+ queueMicrotask(() => {
871
+ const current = map.get(key);
872
+ if (current && current.refCount <= 0) {
873
+ current.resource.dispose();
874
+ map.delete(key);
875
+ }
876
+ });
877
+ }
878
+ }
879
+ };
880
+
881
+ // ../r3f/src/gl-router.ts
882
+ import { Raycaster, Vector2 } from "three";
883
+ function handlersOf(obj) {
884
+ const inst = obj.__r3f;
885
+ if (inst === void 0 || (inst.eventCount ?? 0) === 0) return void 0;
886
+ return inst.handlers;
887
+ }
888
+ function createGLPointerRouter({ world, bridge, index }) {
889
+ const raycaster = new Raycaster();
890
+ const ndc = new Vector2();
891
+ const captures = /* @__PURE__ */ new Map();
892
+ const hovered = /* @__PURE__ */ new Map();
893
+ const unclaimedDowns = /* @__PURE__ */ new Map();
894
+ const castAt = (screenX, screenY) => {
895
+ const cam = world.getResource(Camera);
896
+ if (cam === void 0) return void 0;
897
+ const w = screenToWorld(screenX, screenY, cam);
898
+ const hit = pickTopAt(world, index, w.x, w.y, 0);
899
+ if (hit === void 0) return void 0;
900
+ const type = world.get(hit, PrefabId)?.id;
901
+ const widget = typeof type === "string" ? widgets.get(type) : void 0;
902
+ if (widget === void 0 || widget.surface !== "gl") return void 0;
903
+ const handle = bridge.islandFor(hit);
904
+ const pos = world.get(hit, Position);
905
+ const size = world.get(hit, Size);
906
+ if (handle === void 0 || pos === void 0 || size === void 0 || size.w <= 0 || size.h <= 0) {
907
+ return void 0;
908
+ }
909
+ const local = worldToIsland(w.x, w.y, { x: pos.x, y: pos.y, width: size.w, height: size.h });
910
+ ndc.set(local.x / (size.w / 2), local.y / (size.h / 2));
911
+ const cam3 = handle.camera;
912
+ cam3.left = -size.w / 2;
913
+ cam3.right = size.w / 2;
914
+ cam3.top = size.h / 2;
915
+ cam3.bottom = -size.h / 2;
916
+ cam3.updateProjectionMatrix();
917
+ cam3.updateMatrixWorld(true);
918
+ handle.scene.updateMatrixWorld(true);
919
+ raycaster.setFromCamera(ndc, cam3);
920
+ return { entity: hit, hits: raycaster.intersectObjects(handle.scene.children, true) };
921
+ };
922
+ const dispatch = (type, eventType, entity, hits, native, only) => {
923
+ const visited = /* @__PURE__ */ new Set();
924
+ let stopped = false;
925
+ let claimedBy;
926
+ const targets = [];
927
+ if (hits.length === 0 && only !== void 0) {
928
+ targets.push({ obj: only, hit: void 0 });
929
+ }
930
+ for (const hit of hits) {
931
+ for (let obj = hit.object; obj !== null; obj = obj.parent) {
932
+ if (visited.has(obj)) continue;
933
+ visited.add(obj);
934
+ targets.push({ obj, hit });
935
+ }
936
+ }
937
+ for (const { obj, hit } of targets) {
938
+ const fn = handlersOf(obj)?.[type];
939
+ if (fn === void 0) continue;
940
+ let localStop = false;
941
+ const ev = {
942
+ type: eventType,
943
+ nativeEvent: native,
944
+ point: hit?.point,
945
+ distance: hit?.distance ?? 0,
946
+ object: hit?.object ?? obj,
947
+ eventObject: obj,
948
+ intersections: hits,
949
+ entity,
950
+ stopPropagation() {
951
+ localStop = true;
952
+ },
953
+ get stopped() {
954
+ return localStop;
955
+ }
956
+ };
957
+ fn(ev);
958
+ if (localStop) {
959
+ stopped = true;
960
+ claimedBy = obj;
961
+ break;
962
+ }
963
+ }
964
+ return { stopped, claimedBy };
965
+ };
966
+ const syncHover = (now, native) => {
967
+ const nowEntity = now?.entity;
968
+ const nowObjs = /* @__PURE__ */ new Set();
969
+ if (now !== void 0) {
970
+ for (const hit of now.hits) {
971
+ for (let obj = hit.object; obj !== null; obj = obj.parent) nowObjs.add(obj);
972
+ }
973
+ }
974
+ for (const [entity, prev] of hovered) {
975
+ const gone = [];
976
+ for (const obj of prev) {
977
+ if (entity !== nowEntity || !nowObjs.has(obj)) gone.push(obj);
978
+ }
979
+ for (const obj of gone) {
980
+ prev.delete(obj);
981
+ handlersOf(obj)?.onPointerOut?.({
982
+ type: "pointerout",
983
+ nativeEvent: native,
984
+ point: void 0,
985
+ distance: 0,
986
+ object: obj,
987
+ eventObject: obj,
988
+ intersections: [],
989
+ entity,
990
+ stopPropagation() {
991
+ },
992
+ stopped: false
993
+ });
994
+ }
995
+ if (prev.size === 0) hovered.delete(entity);
996
+ }
997
+ if (now !== void 0 && nowEntity !== void 0) {
998
+ let set = hovered.get(nowEntity);
999
+ if (set === void 0) {
1000
+ set = /* @__PURE__ */ new Set();
1001
+ hovered.set(nowEntity, set);
1002
+ }
1003
+ for (const obj of nowObjs) {
1004
+ if (set.has(obj) || handlersOf(obj) === void 0) continue;
1005
+ set.add(obj);
1006
+ handlersOf(obj)?.onPointerOver?.({
1007
+ type: "pointerover",
1008
+ nativeEvent: native,
1009
+ point: void 0,
1010
+ distance: 0,
1011
+ object: obj,
1012
+ eventObject: obj,
1013
+ intersections: now.hits,
1014
+ entity: nowEntity,
1015
+ stopPropagation() {
1016
+ },
1017
+ stopped: false
1018
+ });
1019
+ }
1020
+ }
1021
+ };
1022
+ return {
1023
+ route(kind, screenX, screenY, native) {
1024
+ if (kind === "down") {
1025
+ const cast2 = castAt(screenX, screenY);
1026
+ if (cast2 === void 0) {
1027
+ unclaimedDowns.delete(native.pointerId);
1028
+ return false;
1029
+ }
1030
+ const { stopped, claimedBy } = dispatch("onPointerDown", "pointerdown", cast2.entity, cast2.hits, native);
1031
+ if (stopped) {
1032
+ unclaimedDowns.delete(native.pointerId);
1033
+ captures.set(native.pointerId, { entity: cast2.entity, downObject: claimedBy });
1034
+ return true;
1035
+ }
1036
+ const top = cast2.hits[0]?.object;
1037
+ if (top !== void 0) unclaimedDowns.set(native.pointerId, { entity: cast2.entity, object: top });
1038
+ return false;
1039
+ }
1040
+ const capture = captures.get(native.pointerId);
1041
+ if (kind === "move") {
1042
+ if (capture !== void 0) {
1043
+ const cast2 = castAt(screenX, screenY);
1044
+ const hits2 = cast2 !== void 0 && cast2.entity === capture.entity ? cast2.hits : [];
1045
+ dispatch("onPointerMove", "pointermove", capture.entity, hits2, native, capture.downObject);
1046
+ return true;
1047
+ }
1048
+ 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);
1053
+ }
1054
+ }
1055
+ return false;
1056
+ }
1057
+ if (capture === void 0) {
1058
+ const pending = unclaimedDowns.get(native.pointerId);
1059
+ 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);
1064
+ }
1065
+ }
1066
+ return false;
1067
+ }
1068
+ captures.delete(native.pointerId);
1069
+ const cast = castAt(screenX, screenY);
1070
+ const hits = cast !== void 0 && cast.entity === capture.entity ? cast.hits : [];
1071
+ if (kind === "up") {
1072
+ 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);
1076
+ }
1077
+ } else {
1078
+ dispatch("onPointerCancel", "pointercancel", capture.entity, hits, native, capture.downObject);
1079
+ }
1080
+ return true;
1081
+ }
1082
+ };
1083
+ }
1084
+ export {
1085
+ CompositeMaterial,
1086
+ GLViews,
1087
+ Island,
1088
+ IslandContext,
1089
+ RenderTargetPool,
1090
+ ResourceRegistry,
1091
+ createGLBridge,
1092
+ createGLPointerRouter,
1093
+ createIslandStateStore,
1094
+ createRenderWriteTrap,
1095
+ runCompositorPass,
1096
+ useIslandContext,
1097
+ useIslandFrame,
1098
+ useIslandInvalidate,
1099
+ useIslandLift
1100
+ };
1101
+ //# sourceMappingURL=r3f.js.map