@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
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Snap guide computation (Figma-style), ported verbatim from v1
3
+ * `ecs/spatial/snap.ts` (audited CLEAN-PORT):
4
+ * Phase 1 — edge/center alignment (7 X + 7 Y candidate pairs per reference,
5
+ * per-axis nearest-wins, ties within 0.01 accumulate guides)
6
+ * Phase 2 — equal spacing (between-two-neighbors midpoint; extend-a-pattern),
7
+ * same-perpendicular-band filter
8
+ * Merge — alignment takes priority per axis; equal spacing fills the other;
9
+ * indicators recomputed post-snap at threshold × 0.5.
10
+ * Pure function over plain bounds. Candidate GATHERING (spatial index, tags,
11
+ * Active scoping) is the caller's job — design-003 §5.2/§6.
12
+ */
13
+ export interface SnapGuide {
14
+ axis: "x" | "y";
15
+ /** World-space coordinate of the alignment line */
16
+ position: number;
17
+ type: "edge" | "center";
18
+ }
19
+ export interface EqualSpacingIndicator {
20
+ axis: "x" | "y";
21
+ /** The equal gap value (world units) */
22
+ gap: number;
23
+ /** Pairs of (from, to) marking each equal gap segment */
24
+ segments: {
25
+ from: number;
26
+ to: number;
27
+ }[];
28
+ /** Position on the perpendicular axis (for rendering) */
29
+ perpPosition: number;
30
+ }
31
+ export interface SnapResult {
32
+ /** Snap-corrected delta (world units). Apply to entity position. */
33
+ snapDx: number;
34
+ snapDy: number;
35
+ guides: SnapGuide[];
36
+ spacings: EqualSpacingIndicator[];
37
+ }
38
+ export interface EntityBounds {
39
+ x: number;
40
+ y: number;
41
+ width: number;
42
+ height: number;
43
+ }
44
+ export declare function computeSnapGuides(dragged: EntityBounds, references: EntityBounds[], threshold: number): SnapResult;
@@ -0,0 +1,16 @@
1
+ import type { AABB } from "./shapes";
2
+ export interface SpatialEntry<Id> extends AABB {
3
+ id: Id;
4
+ }
5
+ export declare class SpatialIndex<Id> {
6
+ private tree;
7
+ private entries;
8
+ upsert(id: Id, bounds: AABB): void;
9
+ remove(id: Id): void;
10
+ /** All entries intersecting the given AABB. */
11
+ search(bounds: AABB): SpatialEntry<Id>[];
12
+ /** Entries within `tolerance` of a point (z-priority is the caller's job). */
13
+ searchPoint(x: number, y: number, tolerance?: number): SpatialEntry<Id>[];
14
+ clear(): void;
15
+ get size(): number;
16
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Hysteresis-banded zoom-resolution policy — ported from v1 `ZoomBands.ts`
3
+ * (RFC-002). An island FBO is allocated at `widget bounds × dpr × band(zoom)`;
4
+ * while zoom stays within `[band × 0.5, band × 2]` no repaint is needed.
5
+ * Bands are powers of 2 (each covers a 4× display range).
6
+ */
7
+ export declare const ZOOM_BANDS: readonly [0.0625, 0.125, 0.25, 0.5, 1, 2, 4, 8, 16];
8
+ /** Smallest band ≥ zoom (clamped to the ladder). */
9
+ export declare function selectBand(zoom: number): number;
10
+ /**
11
+ * True when zoom left the `[band × 0.5, band × 2]` window of the band the
12
+ * island was painted at. `paintedBand ≤ 0` = never painted (handled elsewhere).
13
+ */
14
+ export declare function isOutOfBand(currentZoom: number, paintedBand: number): boolean;
15
+ /** FBO pixel size for a widget at a band (v1: `bounds × dpr × band`, min 1px). */
16
+ export declare function fboPixelSize(worldWidth: number, worldHeight: number, dpr: number, band: number): {
17
+ width: number;
18
+ height: number;
19
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The GL bridge — the engine↔R3F seam (design-004 §3, two-level invalidation).
3
+ *
4
+ * Owns everything ENGINE-facing: the module-side island state, the island
5
+ * registry (scene+camera handles the frame pass iterates), per-island dirt
6
+ * observers, the `r3fInvalidator` reflector, `useIslandFrame` callbacks, the
7
+ * render write trap, and entity-death cleanup. Everything GL-facing (FBO
8
+ * pool, quads, the actual passes) lives in `GLViews`, whose lifetime is the
9
+ * Canvas/context's — the bridge outlives Canvas remounts (HMR) and carries
10
+ * the invalidation wiring across them.
11
+ *
12
+ * Two-level invalidation, mechanized:
13
+ * - COMPOSITE dirt (cheap — re-run the frame pass, sample cached textures):
14
+ * camera/viewport via the reflector; per-island Position/StackZ Tier-3;
15
+ * island enter/exit via register/unregister. All call `requestFrame()`.
16
+ * - ISLAND dirt (expensive — repaint the FBO): per-island props-group and
17
+ * Size Tier-3 → `bumpPaint()` + `requestFrame()`; the animation signal
18
+ * (declared `animated` / live `useIslandFrame` refs) and band crossings
19
+ * are evaluated IN the frame pass (band repaints suppressed while
20
+ * `Camera.gesturing`; the gesture-end camera write re-fires the reflector,
21
+ * so the once-at-gesture-end repaint needs no extra machinery).
22
+ *
23
+ * Runtime-three-free (type-only imports): unit tests drive the bridge with a
24
+ * real world and fake scene/camera handles, no GL context.
25
+ */
26
+ import type { OrthographicCamera, Scene } from "three";
27
+ import { type Engine, type Entity } from "@ice/core";
28
+ import { type IslandStateStore } from "./island-state";
29
+ import { type RenderWriteTrap } from "./dev-write-trap";
30
+ export interface IslandHandle {
31
+ readonly scene: Scene;
32
+ readonly camera: OrthographicCamera;
33
+ }
34
+ /** `useIslandFrame` callback: dt in ms, fired only on frames its island paints. */
35
+ export type IslandFrameCallback = (dtMs: number) => void;
36
+ export interface GLBridgeOpts {
37
+ /** Arm the zero-render→ECS-writes trap (DEV builds / traces). Default false. */
38
+ readonly devAssertRenderWrites?: boolean;
39
+ }
40
+ export interface GLBridge {
41
+ readonly engine: Engine;
42
+ readonly state: IslandStateStore;
43
+ readonly renderAssert: RenderWriteTrap;
44
+ /**
45
+ * Island mount (the `<Island>` component): registers the scene/camera pair,
46
+ * stamps `animatedDecl` from the widget type, and arms the per-island dirt
47
+ * observers. The returned cleanup removes handle + observers ONLY — state
48
+ * and any pooled FBO survive (retention is decoupled from cull; they die on
49
+ * entity death, reset, or eviction).
50
+ */
51
+ registerIsland(entity: Entity, handle: IslandHandle): () => void;
52
+ islandFor(entity: Entity): IslandHandle | undefined;
53
+ islands(): IterableIterator<[Entity, IslandHandle]>;
54
+ /** Subscribe a paint-time callback; holds an animation ref while live. */
55
+ addFrameCallback(entity: Entity, cb: IslandFrameCallback): () => void;
56
+ frameCallbacksFor(entity: Entity): readonly IslandFrameCallback[];
57
+ /** Composite invalidation — schedule a frame pass (coalesced). */
58
+ requestFrame(): void;
59
+ /** Island invalidation — this island must repaint (props/size/manual dirt). */
60
+ bumpPaint(entity: Entity): void;
61
+ /** Composite-quad lift scale (1 = rest). Scales the CARD on the canvas, corners intact. */
62
+ setCompositeScale(entity: Entity, scale: number): void;
63
+ /** GLViews wires R3F's demand-loop `invalidate` here (null on unmount). */
64
+ setInvalidate(fn: (() => void) | null): void;
65
+ /** Tear down reflector + observers + state (doc close / app teardown). */
66
+ uninstall(): void;
67
+ }
68
+ export declare function createGLBridge(engine: Engine, opts?: GLBridgeOpts): GLBridge;
@@ -0,0 +1,17 @@
1
+ import { ShaderMaterial } from "three";
2
+ import type { Texture } from "three";
3
+ /**
4
+ * Per-island composite material. Each island quad gets its own instance so the
5
+ * per-quad `map` uniform is independent; Three compiles the shader once and
6
+ * reuses the program across instances since they share source (verify in dev via
7
+ * `renderer.info.programs.length === 1` for the composite shader).
8
+ */
9
+ export declare class CompositeMaterial extends ShaderMaterial {
10
+ private readonly mapUniform;
11
+ private readonly opacityUniform;
12
+ constructor();
13
+ /** Bind the island's FBO texture (null clears). */
14
+ setMap(map: Texture | null): void;
15
+ /** Composite opacity. Pass-through — no clamping; callers pass valid 0..1. */
16
+ setOpacity(opacity: number): void;
17
+ }
@@ -0,0 +1,83 @@
1
+ import { type Entity, type World } from "@ice/core";
2
+ import type { GLBridge } from "./bridge";
3
+ export interface TargetLike {
4
+ /** Opaque texture handle threaded to the quad (three: `.texture`). */
5
+ readonly texture: unknown;
6
+ }
7
+ export interface PoolLike {
8
+ acquire(key: number, worldW: number, worldH: number, effectiveDpr: number): TargetLike;
9
+ get(key: number): TargetLike | null;
10
+ touch(key: number): void;
11
+ release(key: number): void;
12
+ bytesUsed(): number;
13
+ entryInfos(): readonly {
14
+ key: number;
15
+ bytes: number;
16
+ lastUsedMs: number;
17
+ }[];
18
+ }
19
+ export interface QuadLike {
20
+ setTransform(x: number, y: number, sx: number, sy: number): void;
21
+ setTexture(t: unknown): void;
22
+ setVisible(v: boolean): void;
23
+ setRenderOrder(n: number): void;
24
+ }
25
+ export interface QuadsLike {
26
+ ensure(key: number): QuadLike;
27
+ remove(key: number): void;
28
+ keys(): readonly number[];
29
+ }
30
+ export interface GlLike {
31
+ setRenderTarget(t: TargetLike | null): void;
32
+ /** Clear the CURRENT target to transparent black. */
33
+ clear(): void;
34
+ render(scene: object, camera: object): void;
35
+ setPixelRatio(n: number): void;
36
+ getPixelRatio(): number;
37
+ }
38
+ export interface CompCameraLike {
39
+ setFrustum(f: {
40
+ left: number;
41
+ right: number;
42
+ top: number;
43
+ bottom: number;
44
+ x: number;
45
+ y: number;
46
+ }): void;
47
+ /**
48
+ * The ACTUAL camera object handed to `gl.render` for the composite pass —
49
+ * three's renderer instanceof-checks it, so this must be the real
50
+ * `OrthographicCamera`, NOT this adapter. (Field bug 2026-07-10: the pass
51
+ * rendered with the adapter itself; three threw "camera is not an instance
52
+ * of THREE.Camera" every frame and P2 never composited. The headless fakes
53
+ * couldn't catch it — both types erase to `object` and the fake render is
54
+ * a no-op.)
55
+ */
56
+ readonly raw: object;
57
+ }
58
+ export interface IslandCameraLike {
59
+ setFrustum(halfW: number, halfH: number): void;
60
+ }
61
+ export interface PassContext {
62
+ readonly world: World;
63
+ readonly bridge: GLBridge;
64
+ readonly pool: PoolLike;
65
+ readonly quads: QuadsLike;
66
+ readonly gl: GlLike;
67
+ readonly compCamera: CompCameraLike;
68
+ /** Adapt an island's ortho camera (three or fake) for frustum writes. */
69
+ readonly islandCamera: (entity: Entity) => IslandCameraLike | undefined;
70
+ readonly compositeScene: object;
71
+ readonly maxFboBytes: number;
72
+ readonly maxRepaintsPerFrame: number;
73
+ readonly dtMs: number;
74
+ }
75
+ export interface PassStats {
76
+ repainted: number;
77
+ /** Paint-needing islands deferred by the stagger cap — caller re-invalidates. */
78
+ pendingPaints: number;
79
+ evicted: number;
80
+ fboBytes: number;
81
+ anyHot: boolean;
82
+ }
83
+ export declare function runCompositorPass(ctx: PassContext): PassStats;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The zero-render→ECS-writes DEV assertion (M7 exit criterion; design-004 §3
3
+ * fix #3). While a compositor frame pass runs — including island paints and
4
+ * `useIslandFrame` callbacks — every world mutation throws with attribution.
5
+ *
6
+ * strata 0.4.0 (petition 4 LANDED): one persistent `world.devOnWrite`
7
+ * registration + an armed flag. The hook is DEV-only, synchronous, and
8
+ * PRE-MUTATION with throws propagating to the mutator's caller — so a throw
9
+ * while armed is a clean veto whose stack names the offender. Fired from
10
+ * strata's internal chokepoints, it covers every route the old own-property
11
+ * mutator shadows could miss: bound/destructured method references, document
12
+ * transactions, sync/attach drains, undo echoes, and any mutator added
13
+ * upstream later. (Raw `batch.col()` column pokes remain the documented
14
+ * carve-out — no chokepoint exists by design.)
15
+ *
16
+ * `enabled` is the app-side production gate the 0.4.0 notes ask for: in an
17
+ * un-shimmed browser production bundle strata's own DEV flag can evaluate
18
+ * true, so registration is gated HERE (default off; demos/traces opt in).
19
+ */
20
+ import type { World } from "@ice/core";
21
+ export interface RenderWriteTrap {
22
+ /** Arm the trap (begin twice without end is a bug — throws). */
23
+ begin(): void;
24
+ /** Disarm; always call in `finally`. */
25
+ end(): void;
26
+ /** Unregister the hook entirely (bridge teardown). */
27
+ dispose(): void;
28
+ readonly enabled: boolean;
29
+ }
30
+ export declare function createRenderWriteTrap(world: World, enabled: boolean): RenderWriteTrap;
@@ -0,0 +1,21 @@
1
+ import { type ReactElement } from "react";
2
+ import { type Texture } from "three";
3
+ import { type Engine, type WidgetMountStore } from "@ice/core";
4
+ import type { GLBridge } from "./bridge";
5
+ export interface GLViewsProps {
6
+ readonly engine: Engine;
7
+ readonly bridge: GLBridge;
8
+ readonly store: WidgetMountStore;
9
+ /** FBO byte budget (default `RUNTIME_BUDGETS.fboBytes`, 256 MB). */
10
+ readonly maxFboBytes?: number;
11
+ /** Repaint stagger per composited frame (v1 default 4). */
12
+ readonly maxRepaintsPerFrame?: number;
13
+ /**
14
+ * Shared IBL: stamped as `scene.environment` on EVERY island's private
15
+ * scene (arrival/change repaints all islands). Load it app-side (e.g.
16
+ * drei's useEnvironment in a Suspense boundary) — undefined until then is
17
+ * fine; islands paint unlit-by-IBL and repaint when it lands.
18
+ */
19
+ readonly environment?: Texture | null;
20
+ }
21
+ export declare function GLViews({ engine, bridge, store, maxFboBytes, maxRepaintsPerFrame, environment, }: GLViewsProps): ReactElement;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The pointer router's GL path (design-004 §4, rev 2 corrected).
3
+ *
4
+ * At EVENT time (outside the tick — world reads are verified-unrestricted)
5
+ * the router runs its own synchronous point-pick against THE spatial index
6
+ * via core's `pickTopAt` — the identical narrow-phase the in-tick `picking`
7
+ * system runs, so the two can never disagree. It does NOT consult last-frame
8
+ * `Targets`: that relation is absent for a touch's first event (the pointer
9
+ * entity doesn't exist yet at down) and stale for fast mouse crossings —
10
+ * rev 1 of the design ate the first tap on every interactive GL widget.
11
+ * `Targets` remains the in-tick authority; this pick is a routing decision,
12
+ * not a second truth.
13
+ *
14
+ * Hit a GL-view widget → island-local convert (kernel, THE Y-flip) → raycast
15
+ * the island's scene through its ortho camera → dispatch synthetic events to
16
+ * the intersected objects' R3F handlers. A handler calling
17
+ * `stopPropagation()` claims the event: the adapter flags the fact
18
+ * `surfaceHandled` and recognizers skip it (`HandledByWidget`, the M4 spine).
19
+ *
20
+ * Capture semantics (per island): a claimed down captures the pointer — its
21
+ * moves/ups go to that island until up/cancel, mirroring how DOM widget
22
+ * content holds `setPointerCapture` for sustained interactions. Hover
23
+ * enter/leave are synthesized from pick transitions on buttonless moves.
24
+ *
25
+ * R3F-INTERNAL-ADJACENT (the one acknowledged spot, implementation-plan
26
+ * risks): handlers are read from `object.__r3f.handlers` — the instance
27
+ * state R3F v9 attaches to every declaratively-created three object. No
28
+ * other R3F internal is touched; the dispatcher owns its own event shape.
29
+ * Mid-event ordering note (design-004 §4): handlers may run engine ops —
30
+ * legal; such mutations land immediately while the same event's position
31
+ * facts land next tick via the queue.
32
+ */
33
+ import { type Intersection, type Object3D } from "three";
34
+ import type { SpatialIndex } from "@ice/kernel";
35
+ import { type Entity, type World } from "@ice/core";
36
+ import type { GLBridge } from "./bridge";
37
+ /** The synthetic event island handlers receive (R3F-handler-compatible core). */
38
+ export interface IslandPointerEvent {
39
+ readonly type: string;
40
+ readonly nativeEvent: PointerEvent;
41
+ /** Island-local intersection point (three world = island space). */
42
+ readonly point: Intersection["point"] | undefined;
43
+ readonly distance: number;
44
+ readonly object: Object3D;
45
+ readonly eventObject: Object3D;
46
+ readonly intersections: readonly Intersection[];
47
+ readonly entity: Entity;
48
+ stopPropagation(): void;
49
+ readonly stopped: boolean;
50
+ }
51
+ export interface GLPointerRouterDeps {
52
+ readonly world: World;
53
+ readonly bridge: GLBridge;
54
+ readonly index: SpatialIndex<Entity>;
55
+ }
56
+ export interface GLPointerRouter {
57
+ /** The adapter's `glRoute` seam. True = island content claimed the event. */
58
+ route(kind: "down" | "move" | "up" | "cancel", screenX: number, screenY: number, native: PointerEvent): boolean;
59
+ }
60
+ export declare function createGLPointerRouter({ world, bridge, index }: GLPointerRouterDeps): GLPointerRouter;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @ice/r3f — R3F islands + the virtual-texture compositor (design-004 §3–4).
3
+ * The ONLY package that touches three/@react-three (as peers). Top of the
4
+ * chain: r3f → react → dom → core → kernel.
5
+ */
6
+ export { createGLBridge, type GLBridge, type GLBridgeOpts, type IslandHandle, type IslandFrameCallback } from "./bridge";
7
+ export { createIslandStateStore, type IslandRenderState, type IslandStateStore, type PaintedAt } from "./island-state";
8
+ export { runCompositorPass, type CompCameraLike, type GlLike, type IslandCameraLike, type PassContext, type PassStats, type PoolLike, type QuadLike, type QuadsLike, type TargetLike, } from "./compositor-pass";
9
+ export { GLViews, type GLViewsProps } from "./gl-root";
10
+ export { Island, type IslandProps } from "./island";
11
+ export { IslandContext, useIslandContext, useIslandFrame, useIslandInvalidate, useIslandLift, type IslandContextValue, } from "./use-island-frame";
12
+ export { RenderTargetPool, type PoolEntryInfo } from "./pool";
13
+ export { ResourceRegistry } from "./resource-registry";
14
+ export { CompositeMaterial } from "./composite-material";
15
+ export { createRenderWriteTrap, type RenderWriteTrap } from "./dev-write-trap";
16
+ export { createGLPointerRouter, type GLPointerRouter, type GLPointerRouterDeps, type IslandPointerEvent, } from "./gl-router";
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Module-side island render state (design-004 §3 fix #3: ZERO render→ECS
3
+ * writes — v1 kept this as an ECS component `R3FRenderState` and wrote it
4
+ * from inside `useFrame`, which the engine laws ban; band/generation/paint
5
+ * bookkeeping now lives here, outside the world entirely).
6
+ *
7
+ * One entry per GL widget the compositor has ever touched this session.
8
+ * Entries OUTLIVE island unmount (cull) — that is the retention decoupling:
9
+ * a culled widget's entry keeps its `fboGeneration`/`paintedAt` so re-entry
10
+ * composites from the retained texture (phase Warm) instead of re-waking.
11
+ * Entries die only on entity death, world reset, or `drop()`.
12
+ *
13
+ * Pure state + counters; no three.js, no ECS, no timing — the GLViews frame
14
+ * pass reads/writes it synchronously.
15
+ */
16
+ import type { IslandPhase } from "@ice/kernel";
17
+ /** Resolution + band an island's FBO was last painted at (v1 `paintedAt`). */
18
+ export interface PaintedAt {
19
+ readonly w: number;
20
+ readonly h: number;
21
+ readonly dpr: number;
22
+ /** The zoom BAND painted at (not live zoom) — hysteresis compares against it. */
23
+ readonly band: number;
24
+ }
25
+ export interface IslandRenderState {
26
+ phase: IslandPhase;
27
+ /** Bumped by dirt (props Tier-3, size dirt, manual invalidate). */
28
+ paintGeneration: number;
29
+ /** Generation last painted into the FBO; -1 = no valid texture (fresh or evicted). */
30
+ fboGeneration: number;
31
+ paintedAt: PaintedAt;
32
+ /** Live `useIslandFrame` subscriptions (animation signal while > 0). */
33
+ animRefs: number;
34
+ /** The widget type declared `animated` (repaint every visible frame). */
35
+ animatedDecl: boolean;
36
+ /**
37
+ * Monotonic stamp of the last repaint — the stagger's FAIRNESS key. Without
38
+ * it, more Hot islands than maxRepaintsPerFrame starves the same island
39
+ * every pass (5 Hot vs cap 4 froze the shapes card, 2026-07-12).
40
+ */
41
+ lastPaintSeq: number;
42
+ /**
43
+ * Composite-quad scale (lift-on-hold): scales the CARD on the canvas —
44
+ * texture, rounded alpha corners and all — instead of the scene content
45
+ * (which the card-sized frustum would crop into hard corners, 2026-07-12
46
+ * field report). ≠1 also pops render order over neighbors.
47
+ */
48
+ compositeScale: number;
49
+ }
50
+ export interface IslandStateStore {
51
+ /** Get-or-create (Cold, unpainted). */
52
+ ensure(key: number): IslandRenderState;
53
+ get(key: number): IslandRenderState | undefined;
54
+ all(): IterableIterator<[number, IslandRenderState]>;
55
+ /** Island dirt: the next frame pass must repaint this island. */
56
+ bumpPaint(key: number): void;
57
+ markPainted(key: number, at: PaintedAt): void;
58
+ /** FBO released (eviction/death): the quad hides until first repaint. */
59
+ markEvicted(key: number): void;
60
+ addAnimRef(key: number): void;
61
+ removeAnimRef(key: number): void;
62
+ /** Animation signal: declared `animated` OR any live `useIslandFrame`. */
63
+ isAnimating(key: number): boolean;
64
+ setAnimatedDecl(key: number, animated: boolean): void;
65
+ drop(key: number): void;
66
+ clear(): void;
67
+ }
68
+ export declare function createIslandStateStore(): IslandStateStore;
@@ -0,0 +1,13 @@
1
+ import { type ReactElement } from "react";
2
+ import { type Texture } from "three";
3
+ import type { Entity, WidgetType, World } from "@ice/core";
4
+ import type { GLBridge } from "./bridge";
5
+ export interface IslandProps {
6
+ readonly bridge: GLBridge;
7
+ readonly world: World;
8
+ readonly entity: Entity;
9
+ readonly widget: WidgetType;
10
+ /** Shared IBL for this island's private scene (see GLViews.environment). */
11
+ readonly environment?: Texture | null;
12
+ }
13
+ export declare function Island({ bridge, world, entity, widget, environment }: IslandProps): ReactElement | null;
@@ -0,0 +1,56 @@
1
+ import { WebGLRenderTarget } from "three";
2
+ /** Snapshot row returned by {@link RenderTargetPool.entryInfos}. */
3
+ export interface PoolEntryInfo {
4
+ key: number;
5
+ bytes: number;
6
+ lastUsedMs: number;
7
+ }
8
+ export declare class RenderTargetPool {
9
+ private entries;
10
+ private totalBytes;
11
+ private disposed;
12
+ private readonly now;
13
+ /**
14
+ * @param now Injected monotonic clock for LRU timestamps. Defaults to
15
+ * `performance.now()`; tests pass a deterministic stub so `lastUsedMs`
16
+ * assertions do not depend on wall-clock timing.
17
+ */
18
+ constructor(now?: () => number);
19
+ /**
20
+ * Get or create an FBO for `key` at the requested world size and effective
21
+ * device-pixel-ratio. Returns the existing target unchanged when its pixel
22
+ * dimensions already match; otherwise disposes the stale target and allocates
23
+ * a new one sized to the new resolution.
24
+ */
25
+ acquire(key: number, worldW: number, worldH: number, effectiveDpr: number): WebGLRenderTarget;
26
+ /** Look up an FBO without creating one. */
27
+ get(key: number): WebGLRenderTarget | null;
28
+ /**
29
+ * Refresh `lastUsedMs` without re-acquiring. The compositor calls this for
30
+ * every island it samples in the composition pass — without it, warm islands
31
+ * that never repaint would freeze their timestamp at their last paint and the
32
+ * LRU would treat still-visible islands as stale (design-004 §3).
33
+ */
34
+ touch(key: number): void;
35
+ /**
36
+ * Release `key`'s FBO. Returns true if something was released. Safe after
37
+ * dispose — returns false rather than corrupting the byte counter or
38
+ * double-disposing the target.
39
+ */
40
+ release(key: number): boolean;
41
+ /** Total GPU bytes consumed by the pool. */
42
+ bytesUsed(): number;
43
+ /** Number of FBOs currently held. */
44
+ size(): number;
45
+ /** True after `dispose()` — callers should re-create the pool instead of using it. */
46
+ isDisposed(): boolean;
47
+ /** Iterate live entries (key + target). */
48
+ forEach(cb: (key: number, rt: WebGLRenderTarget) => void): void;
49
+ /**
50
+ * Snapshot of every live entry's `bytes` + `lastUsedMs` — the input an
51
+ * eviction pass ranks by LRU.
52
+ */
53
+ entryInfos(): PoolEntryInfo[];
54
+ /** Dispose every FBO. After this, acquire throws and release returns false. */
55
+ dispose(): void;
56
+ }
@@ -0,0 +1,30 @@
1
+ import type { BufferGeometry, Material, Texture } from "three";
2
+ export declare class ResourceRegistry {
3
+ private geometries;
4
+ private materials;
5
+ private textures;
6
+ private disposed;
7
+ acquireGeometry<T extends BufferGeometry>(key: string, factory: () => T): T;
8
+ releaseGeometry(key: string): void;
9
+ acquireMaterial<T extends Material>(key: string, factory: () => T): T;
10
+ releaseMaterial(key: string): void;
11
+ acquireTexture<T extends Texture>(key: string, factory: () => T): T;
12
+ releaseTexture(key: string): void;
13
+ /** Number of distinct shared geometries currently held. */
14
+ geometryCount(): number;
15
+ /** Number of distinct shared materials currently held. */
16
+ materialCount(): number;
17
+ /** Number of distinct shared textures currently held. */
18
+ textureCount(): number;
19
+ /**
20
+ * Estimated GPU bytes for shared geometry attribute buffers. Best-effort —
21
+ * actual footprint depends on driver alignment, but a useful relative metric
22
+ * for the profiler.
23
+ */
24
+ geometryBytes(): number;
25
+ /** Dispose every resource and clear the registry. */
26
+ dispose(): void;
27
+ /** True after `dispose()` — callers should re-create the registry instead of using it. */
28
+ isDisposed(): boolean;
29
+ private releaseFrom;
30
+ }
@@ -0,0 +1,24 @@
1
+ import type { Entity } from "@ice/core";
2
+ import type { GLBridge, IslandFrameCallback } from "./bridge";
3
+ export interface IslandContextValue {
4
+ readonly bridge: GLBridge;
5
+ readonly entity: Entity;
6
+ }
7
+ /** Provided per-island by `<Island>`; null outside island content. */
8
+ export declare const IslandContext: import("react").Context<IslandContextValue | null>;
9
+ export declare function useIslandContext(): IslandContextValue;
10
+ /** Per-frame tick while mounted (island goes Hot). `cb(dtMs)` on paint. */
11
+ export declare function useIslandFrame(cb: IslandFrameCallback): void;
12
+ /**
13
+ * One-shot repaint scheduler for content that changes outside React's render
14
+ * cycle (imperative subscriptions, sockets). Bumps the island's paint
15
+ * generation + wakes the demand loop.
16
+ */
17
+ export declare function useIslandInvalidate(): () => void;
18
+ /**
19
+ * Composite-quad lift: scales the CARD on the canvas (texture + rounded alpha
20
+ * corners together) and pops it over neighbors while ≠1. Scaling scene content
21
+ * instead crops the corners at the card-sized frustum — the 2026-07-12
22
+ * hard-corner field report. Resets to 1 on unmount.
23
+ */
24
+ export declare function useIslandLift(scale: number): void;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `<EngineProvider>` + engine access hooks (design-005 §5; the M6 field
3
+ * amendment 2026-07-10 gap — grep "useCommit" in design-005).
4
+ *
5
+ * The whole React surface reaches the running canvas through ONE context
6
+ * carrying the {@link CanvasEngine} facade (world + engine + stack + runtime +
7
+ * ops + docs). Widgets never hand-roll store plumbing again: {@link useOps}
8
+ * gives the engine-owned write paths, {@link useCommit} (its own module) the
9
+ * sanctioned durable commit seam, and {@link useWorld} the read/observe escape
10
+ * hatch. `<InfiniteCanvas>` provides this automatically; apps that mount
11
+ * WidgetRoot themselves wrap it in `<EngineProvider>`.
12
+ */
13
+ import { type CanvasEngine, type CanvasOps, type World } from "@ice/core";
14
+ import { type ReactElement, type ReactNode } from "react";
15
+ export interface EngineProviderProps {
16
+ readonly engine: CanvasEngine;
17
+ readonly children?: ReactNode;
18
+ }
19
+ /** Publish the canvas engine to the React subtree (WidgetRoot + userland hooks). */
20
+ export declare function EngineProvider({ engine, children }: EngineProviderProps): ReactElement;
21
+ /** The canvas engine for this subtree; throws with guidance outside a provider. */
22
+ export declare function useCanvasEngine(): CanvasEngine;
23
+ /** The engine-owned write paths (design-005 §4): setTool, deleteSelection, etc. */
24
+ export declare function useOps(): CanvasOps;
25
+ /**
26
+ * Escape hatch — the raw {@link World} for READS and Tier-3 OBSERVATION only.
27
+ * Mutation goes through {@link useOps}/{@link useCommit} (a gesture-claimed,
28
+ * one-tx-per-gesture durable write; direct world writes bypass sovereignty and
29
+ * the guards). DEV-warns once on first use.
30
+ */
31
+ export declare function useWorld(): World;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * React hooks over the engine's Tier-3 reactivity (design-005 §2 hooks;
3
+ * design-004 §2 frozen-hidden rule).
4
+ *
5
+ * Implemented directly on `world.reactive.observeValue` + `useSyncExternalStore`
6
+ * rather than strata's own react module: core is react-free by law, so
7
+ * strata/react cannot be re-exported through it — and the adapter is ~20
8
+ * lines. Value semantics are strata Tier-3: equality-suppressed, fired at
9
+ * notify(), never during the tick.
10
+ *
11
+ * FROZEN-HIDDEN: a widget inside a hidden (culled-but-kept-mounted) host must
12
+ * not re-render on doc edits — `useWidgetProps` reads {@link WidgetHiddenContext}
13
+ * (provided per-portal by WidgetRoot) and suspends its subscription while
14
+ * hidden, serving the last snapshot; on show it resubscribes and refreshes.
15
+ */
16
+ import { type Component, type Entity, type World } from "@ice/core";
17
+ /** Per-portal hidden flag (WidgetRoot provides it; defaults to live). */
18
+ export declare const WidgetHiddenContext: import("react").Context<boolean>;
19
+ /**
20
+ * Tier-3 component value subscription (equality-suppressed). Snapshot
21
+ * STABILITY is load-bearing: strata's `get()` materializes a fresh object per
22
+ * read (SoA columns), and `useSyncExternalStore` treats a new reference as a
23
+ * new snapshot — so reads go through a shallow-equality cache and only a real
24
+ * field change yields a new reference.
25
+ */
26
+ export declare function useWorldComponent<S>(world: World, entity: Entity, component: Component<S>): S | undefined;
27
+ /**
28
+ * Typed props for one conflict group of a widget (design-005 §2). Group
29
+ * component values come back whole; `p.json` fields are parsed. Identity is
30
+ * stable per underlying group value (memo on the raw reference).
31
+ */
32
+ export declare function useWidgetProps<T extends Record<string, unknown>>(world: World, entity: Entity, type: string, group?: string): T | undefined;
33
+ /**
34
+ * Selection membership (Tier-1 wake on Selected churn; O(1) snapshot).
35
+ * Frozen-hidden like every widget hook: a selection change must not re-render
36
+ * 256 hidden trees (review finding — design-004 §2 applies to ALL hooks).
37
+ */
38
+ export declare function useSelected(world: World, entity: Entity): boolean;
39
+ /** LOD tier (design-004 §8); "normal" until the breakpoint system stamps one. */
40
+ export declare function useBreakpoint(world: World, entity: Entity): string;