@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
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The devtools dock — ONE draggable window hosting all three tools
3
+ * (2026-07-13, James: "make the 3 tools into one draggable panel").
4
+ *
5
+ * strata's observer + profiler and our GL panel each mount `position: fixed`
6
+ * into their own corner. The dock does NOT re-implement any of them (standing
7
+ * rule: wrap first-party tools): it is a fixed, draggable shell with three
8
+ * stacked slots; each tool mounts into its slot via its own `container`
9
+ * option, and dock-scoped CSS de-windows them (`position: static`, full
10
+ * width, no border/shadow) so they read as sections of one panel. Their own
11
+ * behavior is untouched — the observer keeps its tabs/collapse/vertical
12
+ * resize (its header drag goes inert: left/top are ignored at static), the
13
+ * profiler + GL strips keep their click-to-expand.
14
+ *
15
+ * Slot order is fixed by the dock, not by mount order: profiler and GL are
16
+ * one-line summary strips, the observer is the deep-dive — strips on top.
17
+ * Layout (x/y/width/open) persists to localStorage, same as the observer:
18
+ * schema edits full-reload the page by design; the dock must stay put.
19
+ */
20
+ export type DockCorner = "tl" | "tr" | "bl" | "br";
21
+ export type DockSlotId = "profiler" | "gl" | "observer";
22
+ export interface DockOptions {
23
+ /** Where the dock mounts (default: document.body). */
24
+ readonly container?: HTMLElement;
25
+ /** Initial corner when nothing is persisted yet (default "tr"). */
26
+ readonly corner?: DockCorner;
27
+ /** Header title (default "ice devtools"). */
28
+ readonly title?: string;
29
+ /** Initial width in px when nothing is persisted yet (default 420). */
30
+ readonly width?: number;
31
+ }
32
+ export interface Dock {
33
+ readonly el: HTMLElement;
34
+ /** The mount element for one tool. Empty slots are display:none via CSS. */
35
+ slot(id: DockSlotId): HTMLElement;
36
+ dispose(): void;
37
+ }
38
+ export declare function createDock(opts?: DockOptions): Dock;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The GL metrics panel — the engine-specific renderer readout strata has no
3
+ * counterpart for (2026-07-13, James: "comprehensive r3f profiler ... and our
4
+ * engine specific optimization metrics ... with nice visuals").
5
+ *
6
+ * Pure DOM, strata's visual language (same palette/typography as the observer
7
+ * + profiler HUD so the three read as one toolset). Fed by pushes of
8
+ * `GlPanelStats` — the STRUCTURAL MIRROR of @ice/r3f's `GlFrameStats`
9
+ * (devtools cannot import r3f under the walls; keep the shapes aligned):
10
+ *
11
+ * head fps · gpu ms + a gpu sparkline against the frame budget
12
+ * frame cpu / gpu bars vs budget, with session maxima
13
+ * render draw calls · triangles · points/lines · programs · geoms · textures
14
+ * virtual live render targets + total resolution, FBO bytes vs budget,
15
+ * textures the island phase census as a segmented bar, repaint/evict counters
16
+ * lod zoom → hysteresis band → effective DPR, live targets per band
17
+ * widgets Visible vs Culled population
18
+ *
19
+ * All values land via `textContent` (never innerHTML with data). Rendering is
20
+ * throttled — pushes arrive per composited frame, the DOM refreshes at ~8 Hz.
21
+ */
22
+ /** Mirror of @ice/r3f `GlFrameStats` (structural — see the header). */
23
+ export interface GlPanelStats {
24
+ readonly cpuMs: number;
25
+ readonly gpuMs: number;
26
+ readonly fps: number;
27
+ readonly drawCalls: number;
28
+ readonly triangles: number;
29
+ readonly points: number;
30
+ readonly lines: number;
31
+ readonly programs: number;
32
+ readonly geometries: number;
33
+ readonly textures: number;
34
+ readonly renderTargets: number;
35
+ readonly renderMegaPixels: number;
36
+ readonly fboBytes: number;
37
+ readonly fboBudgetBytes: number;
38
+ readonly islands: {
39
+ readonly total: number;
40
+ readonly hot: number;
41
+ readonly warm: number;
42
+ readonly waking: number;
43
+ readonly cold: number;
44
+ readonly dormant: number;
45
+ };
46
+ readonly bandHistogram: Readonly<Record<string, number>>;
47
+ readonly repainted: number;
48
+ readonly pendingPaints: number;
49
+ readonly evicted: number;
50
+ readonly zoom: number;
51
+ readonly band: number;
52
+ readonly effectiveDpr: number;
53
+ readonly visibleWidgets: number;
54
+ readonly culledWidgets: number;
55
+ }
56
+ export type GlPanelCorner = "tl" | "tr" | "bl" | "br";
57
+ export interface GlPanelOptions {
58
+ readonly container?: HTMLElement;
59
+ readonly corner?: GlPanelCorner;
60
+ /** Frame budget in ms — the sparkline's reference line (default 16.7). */
61
+ readonly budgetMs?: number;
62
+ /** Start with the detail body open (default false). */
63
+ readonly expanded?: boolean;
64
+ }
65
+ export interface GlPanel {
66
+ push(stats: GlPanelStats): void;
67
+ dispose(): void;
68
+ }
69
+ export declare function createGlPanel(opts?: GlPanelOptions): GlPanel;
@@ -1,8 +1,10 @@
1
1
  /**
2
- * @ice/devtools — the engine devtools panel (design-005 §4): pointers/recognizers,
3
- * planes, sovereignty badges, loop telemetry. A standalone DOM panel that reads
4
- * the world outside the tick — it is NOT a reflector and writes no ECS.
5
- * Import wall: @ice/core (+kernel) only; nobody imports us (depcruise-enforced).
2
+ * @ice/devtools — strata's observer panel + FPS profiler, engine-flavored
3
+ * (describe/durable/ephemeral/reflect-lane glue). Reads the world outside the
4
+ * tick; never a reflector, never writes ECS. Nobody imports us
5
+ * (depcruise-enforced leaf).
6
6
  */
7
- export declare const DEVTOOLS_VERSION = "0.0.0";
8
- export { attachDevtools, type DevtoolsHandle, type DevtoolsOpts } from "./panel";
7
+ export declare const DEVTOOLS_VERSION = "0.1.0";
8
+ export { attachDevtools, engineDescribe, type DevtoolsEngine, type DevtoolsHandle, type DevtoolsOpts, } from "./attach";
9
+ export { createDock, type Dock, type DockCorner, type DockOptions, type DockSlotId } from "./dock";
10
+ export { createGlPanel, type GlPanel, type GlPanelCorner, type GlPanelOptions, type GlPanelStats } from "./gl-panel";
@@ -9,11 +9,9 @@ export { createPlaneTransformReflector, type CameraPlanes } from "./reflectors/p
9
9
  export { createGrayboxReflector } from "./reflectors/graybox";
10
10
  export { createDomWidgetsReflector, type DomWidgetsHost, type DomWidgetsReflector, } from "./reflectors/dom-widgets";
11
11
  export { startRafLoop } from "./loop";
12
- export { attachPointerAdapter, type GLRoute, type PointerAdapterOpts } from "./pointer-adapter";
12
+ export { attachPointerAdapter, type GLRoute, type GLRouteVerdict, type PointerAdapterOpts } from "./pointer-adapter";
13
13
  export { createCursorReflector } from "./reflectors/cursor";
14
14
  export { createRemoteCursorsReflector, type RemoteCursorsReflector } from "./reflectors/remote-cursors";
15
- export { createGridReflector, DEFAULT_GRID_CONFIG, type GridConfig } from "./reflectors/grid";
16
- export { createWiresReflector, DEFAULT_WIRES_CONFIG, type WirePreview, type WiresConfig, type WiresReflector, } from "./reflectors/wires";
17
15
  export { createChromeReflector } from "./reflectors/chrome";
18
16
  export { attachMeasureAdapter, type MeasureAdapter } from "./measure-adapter";
19
17
  export { wireMeasurement, type MeasureWiringHosts, type MeasureWiringOpts } from "./measure-wiring";
@@ -33,8 +33,8 @@ export interface MeasureWiringHosts {
33
33
  }
34
34
  export interface MeasureWiringOpts {
35
35
  /**
36
- * Border-box read for the forced re-measure on show. Defaults to
37
- * `getBoundingClientRect`; tests inject a deterministic stub.
36
+ * Border-box read for the forced re-measure on show. Defaults to layout
37
+ * `offsetWidth`/`offsetHeight`; tests inject a deterministic stub.
38
38
  */
39
39
  readonly measure?: (el: HTMLElement) => {
40
40
  w: number;
@@ -28,6 +28,15 @@
28
28
  * through L0). `stopPropagation()` inside widget content is the OTHER boundary:
29
29
  * a stopped event never reaches the container listener, so it never becomes a
30
30
  * canvas fact at all — that is the design, not a gap.
31
+ *
32
+ * Hover-time amendment (design-002 §8, 2026-07-18): down/move facts ALSO carry
33
+ * `overInteractive` — the same `crossesInteractive` check, run at hover time,
34
+ * OR'd with the GL router's hover verdict — so the world can telegraph the
35
+ * opt-out BEFORE a down (cursor affordances). It never gates gestures:
36
+ * `surfaceHandled` stays down-only and keeps its exact meaning. During a
37
+ * container-captured drag every event retargets to the container, so
38
+ * `overInteractive` reads false mid-gesture — correct: the gesture owns the
39
+ * pointer, not the widget under it.
31
40
  */
32
41
  import { type InputQueue } from "@ice/core";
33
42
  import type { CanvasHost } from "./host";
@@ -41,7 +50,18 @@ import type { CanvasHost } from "./host";
41
50
  * recognizers skip it via `HandledByWidget` — the same contract as the
42
51
  * native-interactive opt-out.
43
52
  */
44
- export type GLRoute = (kind: "down" | "move" | "up" | "cancel", screenX: number, screenY: number, native: PointerEvent) => boolean;
53
+ export type GLRoute = (kind: "down" | "move" | "up" | "cancel", screenX: number, screenY: number, native: PointerEvent) => boolean | GLRouteVerdict;
54
+ /**
55
+ * The richer GL verdict (hover-time amendment, 2026-07-18): `handled` keeps the
56
+ * boolean's exact meaning (island content claimed the event); `overInteractive`
57
+ * adds the hover fact — the pick chain holds claim-capable content (a mesh with
58
+ * a down/click handler), so a down HERE would be the island's. Plain `boolean`
59
+ * returns stay legal (≡ `{ handled }`), so existing routes never break.
60
+ */
61
+ export interface GLRouteVerdict {
62
+ readonly handled: boolean;
63
+ readonly overInteractive?: boolean;
64
+ }
45
65
  export interface PointerAdapterOpts {
46
66
  readonly glRoute?: GLRoute;
47
67
  }
@@ -24,6 +24,11 @@
24
24
  * dedicated observer; the note below is historical:
25
25
  * picked up on the next Position/Size stamp or membership change; the ResizeObserver
26
26
  * measurement path — a later slice — will arm its own dirt.)
27
+ * - **opacity**: a private observer on `[Opacity]` (attach/detach/value) sets a
28
+ * dirty flag; the change-only pass writes `style.opacity` on the host (absent
29
+ * component = 1 = property cleared). The GL twin is the composite quad's
30
+ * `uOpacity` — a gl widget's host here carries only its DOM chrome, so the
31
+ * two halves fade from the same cell.
27
32
  * - **drag-promote** (P3): a private observer on `[Grab]` membership sets a dirty
28
33
  * flag; a host whose entity holds `Grab` re-parents content→lifted plane and back
29
34
  * on release. Re-parenting MOVES the host node (with its content child), which
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @ice/ground — the P0 ground stratum as ONE WebGPU canvas (design-004 §1
3
+ * as-built amendment, 2026-07-16). three's WebGPURenderer + TSL, WebGL2
4
+ * fallback automatic. Off the react chain: react receives the layer as an
5
+ * OPAQUE factory (`<InfiniteCanvas ground={ground(...)}>`); imperative apps
6
+ * register `layer.reflector` themselves. Passes: dot grid, wires, snap
7
+ * guides — future ground shaders/effects join the same pass registry.
8
+ * (Selection chrome moved OFF the ground 2026-07-17: rings live with the
9
+ * cards, the multi-select union box is P4 dom chrome — always on top.)
10
+ */
11
+ import type { GridConfig, SnapGuidesConfig, WirePreviewBuffer, WiresConfig, World } from "@ice/core";
12
+ import { type GroundLayerHost, type GroundReflector } from "./layer";
13
+ import type { GroundPass } from "./pass";
14
+ import { type GroundRendererLike } from "./renderer";
15
+ export type { GroundFrame, GroundPass } from "./pass";
16
+ export type { GroundReflector } from "./layer";
17
+ export type { GroundRendererLike } from "./renderer";
18
+ export { SoupBuilder, parseCssColor, type Rgba, type TriSoup } from "./passes/soup-collect";
19
+ export { collectGuides } from "./passes/guides-collect";
20
+ export { collectWires } from "./passes/wires-collect";
21
+ export { DEFAULT_GRID_CONFIG, DEFAULT_SNAP_GUIDES_CONFIG, DEFAULT_WIRES_CONFIG, type GridConfig, type SnapGuidesConfig, type WiresConfig, } from "@ice/core";
22
+ export interface GroundContext {
23
+ readonly host: GroundLayerHost;
24
+ readonly world: World;
25
+ /** Connect-drag preview reader (graph boards); omit on boards without wires. */
26
+ readonly readWirePreview?: () => WirePreviewBuffer;
27
+ }
28
+ export interface GroundLayer {
29
+ readonly reflector: GroundReflector;
30
+ /** Live grid re-tune (the react `grid` prop forwards here). */
31
+ configureGrid(cfg: Partial<GridConfig>): void;
32
+ dispose(): void;
33
+ }
34
+ export interface GroundOptions {
35
+ readonly grid?: Partial<GridConfig>;
36
+ readonly wires?: Partial<WiresConfig>;
37
+ readonly guides?: Partial<SnapGuidesConfig>;
38
+ /** Extra app passes, rendered after the built-ins (renderOrder ≥ 3 is yours). */
39
+ readonly passes?: readonly GroundPass[];
40
+ /** Force the WebGL2 backend (debug / e2e A-B runs). */
41
+ readonly forceWebGL?: boolean;
42
+ /** TEST seam: inject a fake renderer (headless orchestration tests). */
43
+ readonly rendererOverride?: GroundRendererLike;
44
+ }
45
+ export type GroundFactory = (ctx: GroundContext) => GroundLayer;
46
+ /**
47
+ * Build the ground layer factory. Usage:
48
+ * react apps: <InfiniteCanvas ground={ground({ grid: {...} })} …>
49
+ * imperative apps: const layer = ground()( { host, world, readWirePreview } );
50
+ * engine.registerReflector(layer.reflector);
51
+ */
52
+ export declare function ground(opts?: GroundOptions): GroundFactory;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The ground LAYER — one reflector, one canvas, one render (design-004 §1
3
+ * as-built 2026-07-16: P0 consolidated from three stacked canvases into a
4
+ * single WebGPU canvas; grid, wires and snap-guides are now PASSES of this
5
+ * layer, and future ground effects join the same registry).
6
+ *
7
+ * Coordinate model: the layer renders in SCREEN CSS-px space — an ortho
8
+ * camera maps [0..w]×[0..h] (y-down) to clip, and passes build their geometry
9
+ * through the kernel `worldToScreen` seam per collect (the proven 2D-canvas
10
+ * semantics: geometry rebuilds on the same dirt that re-stroked the old
11
+ * canvases; the grid stays a per-pixel screen-space shader). The Y-flip
12
+ * question never arises here — `worldToScreen` IS the one conversion (L13).
13
+ *
14
+ * Reflector contract (design-002 §5): post-notify, output-only, never reads
15
+ * layout in flush (the ResizeObserver owns sizing), never writes ECS.
16
+ * `always: true` with a private dirty union — an idle scene renders zero
17
+ * frames; ANY dirty pass re-renders the whole canvas (one draw, same cost
18
+ * class as a pan frame under the old grid). WebGPU init is async: flushes
19
+ * before ready are deferred (flags stay armed), the first ready flush paints.
20
+ */
21
+ import { type ReflectorDef, type World } from "@ice/core";
22
+ import type { GroundPass } from "./pass";
23
+ import type { GroundRendererLike } from "./renderer";
24
+ export interface GroundLayerHost {
25
+ readonly container: HTMLElement;
26
+ readonly contentPlane: HTMLElement;
27
+ }
28
+ export interface GroundReflector extends ReflectorDef {
29
+ /** False while init is pending or after a device/context failure. */
30
+ available(): boolean;
31
+ /** Whole-canvas renders so far (the churn instrument — 0 on an idle scene). */
32
+ redraws(): number;
33
+ }
34
+ export interface GroundLayerInternals {
35
+ reflector: GroundReflector;
36
+ /** Wake every pass + render (config changes, external invalidation). */
37
+ invalidateAll(): void;
38
+ dispose(): void;
39
+ }
40
+ export declare function createLayer(host: GroundLayerHost, world: World, renderer: GroundRendererLike, passes: readonly GroundPass[]): GroundLayerInternals;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The ground-pass contract (design-004 §1, as-built 2026-07-16: P0 is ONE
3
+ * WebGPU canvas; passes are its draw layers).
4
+ *
5
+ * A pass owns a scene `object` (mesh/group with a TSL node material), arms its
6
+ * own dirty observers, and rebuilds its geometry/uniforms in `collect` — PURE
7
+ * world reads in the layer's SCREEN-px coordinate space (the ortho camera maps
8
+ * CSS px → clip; world→screen goes through the kernel `worldToScreen` seam,
9
+ * Law 13). Heavy per-pass math (tessellation, triangle-soup emission) lives in
10
+ * sibling `*-collect.ts` modules that import ONLY core/kernel, so collectors
11
+ * unit-test under happy-dom with no GPU and no three import.
12
+ */
13
+ import type { World } from "@ice/core";
14
+ import type { Object3D } from "three/webgpu";
15
+ /** Per-flush frame facts every pass collects against. */
16
+ export interface GroundFrame {
17
+ /** CSS-px viewport size. */
18
+ readonly width: number;
19
+ readonly height: number;
20
+ readonly dpr: number;
21
+ /** Engine camera: world top-left + zoom (grid.ts P0 contract). */
22
+ readonly camera: {
23
+ readonly x: number;
24
+ readonly y: number;
25
+ readonly zoom: number;
26
+ };
27
+ }
28
+ export interface GroundPass {
29
+ readonly name: string;
30
+ /** The pass's scene object; the layer adds it once and disposes with the pass. */
31
+ readonly object: Object3D;
32
+ /**
33
+ * Arm dirty observers on the world; `wake()` marks this pass for a
34
+ * re-collect on the next flush. Camera/resize dirt is LAYER-owned (it wakes
35
+ * every pass) — arm only pass-specific sources here. Returns unsubscribers.
36
+ */
37
+ arm(world: World, wake: () => void): Array<() => void>;
38
+ /** Rebuild geometry/uniforms from the world (read-only; screen-px space). */
39
+ collect(world: World, frame: GroundFrame): void;
40
+ dispose(): void;
41
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The dot-grid pass — TSL port of the raw-WebGL grid (itself verbatim from v1
3
+ * GridRenderer.ts; the fragment math below is that shader re-expressed as a
4
+ * node graph, level loop unrolled in JS). Screen-space: a fullscreen triangle
5
+ * whose vertex stage passes clip coords through; the fragment reconstructs
6
+ * world position from `screenCoordinate` (top-left device px on BOTH backends
7
+ * — three normalizes, so the old manual y-flip is gone) + camera uniforms.
8
+ *
9
+ * One deliberate re-expression: the GLSL if/else fade ladder became the
10
+ * closed form `clamp(min(rise, fall), 0, 1)` — identical piecewise output
11
+ * (rise 0→1 across fadeIn, plateau 1, fall 1→0 across fadeOut) with no
12
+ * branching. Everything else (fract-distance dots, 0.5-device-px AA,
13
+ * max-composited levels) matches term for term.
14
+ */
15
+ import { type GridConfig } from "@ice/core";
16
+ import type { GroundPass } from "../pass";
17
+ export interface GridPass extends GroundPass {
18
+ /** Live re-tune (the react `grid` prop seam) — partial merge over current values. */
19
+ configure(cfg: Partial<GridConfig>): void;
20
+ }
21
+ export declare function createGridPass(initial?: Partial<GridConfig>): GridPass;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Snap-guides collector — the pure ECS→triangles half of the guides pass
3
+ * (v1 SnapGuideRenderer look, carried over from the 2D-canvas reflector it
4
+ * replaces): full-viewport magenta alignment lines (`GuideLine.from == to` ⇒
5
+ * full span — design-003 §6), bounded spans, and equal-spacing bars with 4px
6
+ * end ticks. Screen-px output via the kernel `worldToScreen` seam. NO guide
7
+ * count caps (the pool grows; so does the soup).
8
+ */
9
+ import { type SnapGuidesConfig, type World } from "@ice/core";
10
+ import type { GroundFrame } from "../pass";
11
+ import { type TriSoup } from "./soup-collect";
12
+ export declare const guideQ: import("@vibecook/strata-ecs").Query;
13
+ export declare const barQ: import("@vibecook/strata-ecs").Query;
14
+ export declare function collectGuides(world: World, frame: GroundFrame, config?: SnapGuidesConfig): TriSoup;
@@ -0,0 +1,3 @@
1
+ import { type SnapGuidesConfig } from "@ice/core";
2
+ import type { GroundPass } from "../pass";
3
+ export declare function createGuidesPass(initial?: Partial<SnapGuidesConfig>): GroundPass;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Triangle-soup builders — the PURE half of the geometry passes (no three
3
+ * import; unit-tested under happy-dom). Collectors emit screen-px triangles
4
+ * with per-vertex rgba into a growable soup; the three-side `soup-mesh`
5
+ * uploads them. MSAA (renderer antialias) smooths edges, matching the old 2D
6
+ * canvases' stroke AA closely enough at 1–2 px widths.
7
+ */
8
+ export interface TriSoup {
9
+ /** xyz per vertex (z = 0 — the layer renders flat in screen space). */
10
+ positions: Float32Array;
11
+ /** rgba per vertex, 0-1. */
12
+ colors: Float32Array;
13
+ vertexCount: number;
14
+ }
15
+ export type Rgba = readonly [number, number, number, number];
16
+ export declare class SoupBuilder {
17
+ private pos;
18
+ private col;
19
+ private vert;
20
+ /** Axis-aligned rect (two triangles). */
21
+ rect(x: number, y: number, w: number, h: number, c: Rgba): void;
22
+ /** Stroke segment (a→b) of `width`, flat caps — a quad across the segment normal. */
23
+ segment(ax: number, ay: number, bx: number, by: number, width: number, c: Rgba): void;
24
+ /** Stroke a polyline (interleaved xy) as per-segment quads (smooth-curve joins). */
25
+ polyline(points: ArrayLike<number>, width: number, c: Rgba): void;
26
+ /** Filled disc as a triangle fan (MSAA smooths the rim; 16 segs ≈ round at ≤8 px). */
27
+ disc(cx: number, cy: number, r: number, c: Rgba, segments?: number): void;
28
+ build(): TriSoup;
29
+ }
30
+ /**
31
+ * `rgba(r, g, b, a)` / `rgb(…)` / `#rrggbb` → [r,g,b,a] 0-1 (the WiresConfig
32
+ * colors are CSS strings — v1/2D-canvas heritage). Unknown formats fall back
33
+ * to opaque mid-gray rather than throwing in a reflector path.
34
+ */
35
+ export declare function parseCssColor(css: string): Rgba;
@@ -0,0 +1,8 @@
1
+ import { Mesh } from "three/webgpu";
2
+ import type { TriSoup } from "./soup-collect";
3
+ export interface SoupMesh {
4
+ readonly mesh: Mesh;
5
+ update(soup: TriSoup): void;
6
+ dispose(): void;
7
+ }
8
+ export declare function createSoupMesh(renderOrder: number): SoupMesh;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Wires collector — the pure ECS→triangles half of the wires pass. Port of
3
+ * the 2D-canvas wires reflector's drawing, kernel-tessellated:
4
+ *
5
+ * - Wire geometry NEVER reads a port ENTITY (design-001 §5.3): endpoints
6
+ * resolve from the wire's `WireFrom`/`WireTo` widgets + `WirePorts` ids
7
+ * against the type registry's static port schema (core `portSlots` — the
8
+ * old local resolver's TODO, retired), so a CULLED endpoint still draws.
9
+ * - Cubics build in WORLD space then map control points through the kernel
10
+ * `worldToScreen` seam — bezier curves are affine-invariant, so the mapped
11
+ * cubic IS the screen curve — and `tessellateCubic` (0.25 px tolerance)
12
+ * flattens with density that tracks zoom for free.
13
+ * - Connect preview: solid when snapped-compatible, dashed [6,4] otherwise
14
+ * (dash placement from the tessellation's arc-length prefix).
15
+ * - Materialized port dots: triangle-fan discs, active color while any
16
+ * connect drag is routed.
17
+ */
18
+ import { type WirePreviewBuffer, type WiresConfig, type World } from "@ice/core";
19
+ import type { GroundFrame } from "../pass";
20
+ import { type TriSoup } from "./soup-collect";
21
+ export declare const wireQ: import("@vibecook/strata-ecs").Query;
22
+ export declare const geometryQ: import("@vibecook/strata-ecs").Query;
23
+ export declare const measuredQ: import("@vibecook/strata-ecs").Query;
24
+ export declare const selectedQ: import("@vibecook/strata-ecs").Query;
25
+ export declare const portQ: import("@vibecook/strata-ecs").Query;
26
+ export declare const routedConnectQ: import("@vibecook/strata-ecs").Query;
27
+ /** A live connect drag moves the preview every frame — Drag value dirt covers it. */
28
+ export declare const connectDragQ: import("@vibecook/strata-ecs").Query;
29
+ /** Nav churn signal: Active membership flips re-collect (scope filter below). */
30
+ export declare const activeQ: import("@vibecook/strata-ecs").Query;
31
+ export declare function collectWires(world: World, frame: GroundFrame, config?: WiresConfig, preview?: WirePreviewBuffer): TriSoup;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Wires pass — the three-side wrapper over the pure `collectWires` (renders
3
+ * above the grid, below the guides, matching the old P0 canvas order).
4
+ * Dirt mirrors the old 2D reflector's observers; the connect preview rides
5
+ * `[RoutedConnect, Drag]` value dirt (the buffer updates exactly when the
6
+ * connect recognizer's Drag advances) instead of a per-frame value diff.
7
+ */
8
+ import { type WirePreviewBuffer, type WiresConfig } from "@ice/core";
9
+ import type { GroundPass } from "../pass";
10
+ export declare function createWiresPass(initial?: Partial<WiresConfig>, readPreview?: () => WirePreviewBuffer): GroundPass;
@@ -0,0 +1,15 @@
1
+ import type { Camera, Scene } from "three/webgpu";
2
+ /** The layer's renderer seam — tests inject a fake (compositor `GlLike` precedent). */
3
+ export interface GroundRendererLike {
4
+ readonly canvas: HTMLCanvasElement;
5
+ ready(): boolean;
6
+ failed(): boolean;
7
+ /** Register a one-shot "became ready" callback (fires immediately if already ready). */
8
+ onReady(cb: () => void): void;
9
+ setSize(cssW: number, cssH: number, dpr: number): void;
10
+ render(scene: Scene, camera: Camera): void;
11
+ dispose(): void;
12
+ }
13
+ export declare function createGroundRenderer(doc: Document, opts?: {
14
+ forceWebGL?: boolean;
15
+ }): GroundRendererLike;
@@ -0,0 +1 @@
1
+ export * from "../../ground/src/index";
@@ -29,6 +29,21 @@ export declare function cubicPoint(c: Cubic, t: number): Vec2;
29
29
  * culling/picking prefilters.
30
30
  */
31
31
  export declare function cubicAABB(c: Cubic): AABB;
32
+ export interface Tessellation {
33
+ /** Flattened polyline, interleaved [x0,y0, x1,y1, …] in the cubic's own space. */
34
+ points: Float32Array;
35
+ /** Cumulative arc length at each polyline point (same unit; [0] = 0). */
36
+ arcLength: Float32Array;
37
+ }
38
+ /**
39
+ * Adaptive cubic flattening + prefix arc length (2026-07-16, the @ice/ground
40
+ * wires pass: stroke tessellation and dash placement both ride this). Emit
41
+ * density follows curvature — flat spans emit one segment regardless of
42
+ * length; tolerance is in the cubic's OWN space (the ground renderer passes
43
+ * screen-px cubics with ~0.25 px tolerance, so density tracks zoom for free).
44
+ * `maxDepth` bounds pathological inputs (2^depth segments worst case).
45
+ */
46
+ export declare function tessellateCubic(c: Cubic, tolerance?: number, maxDepth?: number): Tessellation;
32
47
  /** Distance from a point to a line segment. */
33
48
  export declare function distanceToSegment(px: number, py: number, ax: number, ay: number, bx: number, by: number): number;
34
49
  /**
@@ -26,6 +26,21 @@ export declare function worldToScreen(worldX: number, worldY: number, camera: Ca
26
26
  * Returns the new camera (does not mutate). Clamping is the caller's policy.
27
27
  */
28
28
  export declare function zoomAtPoint(camera: CameraState, screenX: number, screenY: number, newZoom: number): CameraState;
29
+ /**
30
+ * The DEFAULT framing camera (2026-07-18, James: "do zoom to fit, but with a
31
+ * upper and bottom cap … should feel natural"): zoom-to-fit `content` (a
32
+ * world-space bbox) with `pad` px of breathing room on every side, zoom
33
+ * clamped into [minZoom, maxZoom] — the natural band: never so far in that
34
+ * one small card fills the screen, never so far out that the board turns
35
+ * into an ant farm. The content CENTER stays centered whether or not a cap
36
+ * engages. Callers own the band policy (engine default: FIT_DEFAULTS ∩ the
37
+ * session's hard CameraLimits) and must pass a real viewport.
38
+ */
39
+ export declare function fitCamera(content: Rect, vpW: number, vpH: number, opts: {
40
+ pad: number;
41
+ minZoom: number;
42
+ maxZoom: number;
43
+ }): CameraState;
29
44
  /**
30
45
  * Camera → the ONE per-plane CSS transform (design-002 §5 `planeTransform`).
31
46
  * Children are laid out in WORLD units inside the plane (left/top = world x/y),
@@ -0,0 +1,11 @@
1
+ /**
2
+ * CSS-style cubic-bezier timing functions (pure math — kernel).
3
+ *
4
+ * `cubicBezierEase(x1, y1, x2, y2)` builds f(t): the exact curve CSS
5
+ * `transition-timing-function: cubic-bezier(…)` runs, so GL surfaces can
6
+ * match DOM transitions beat-for-beat (the composite-quad lift mirrors the
7
+ * DOM card lift's spring, cubic-bezier(0.2, 0.9, 0.3, 1.2)). y outside [0,1]
8
+ * (overshoot springs) is legal and expected; x1/x2 are clamped to [0,1] per
9
+ * spec so x(u) stays monotone and invertible.
10
+ */
11
+ export declare function cubicBezierEase(x1: number, y1: number, x2: number, y2: number): (t: number) => number;
@@ -11,3 +11,6 @@ export * from "./zoom-bands";
11
11
  export * from "./eviction";
12
12
  export * from "./anchors";
13
13
  export * from "./bezier";
14
+ export * from "./easing";
15
+ export * from "./nav-flight";
16
+ export * from "./layout";