@vibecook/ice 0.4.0 → 0.7.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 (39) hide show
  1. package/README.md +4 -4
  2. package/dist/{chunk-3PHPWRB2.js → chunk-55QCOE74.js} +7 -4
  3. package/dist/chunk-55QCOE74.js.map +1 -0
  4. package/dist/{chunk-ENMRSITH.js → chunk-5OU3OWDH.js} +16 -3
  5. package/dist/chunk-5OU3OWDH.js.map +1 -0
  6. package/dist/{chunk-7LZAXQAO.js → chunk-LATXYFMO.js} +6823 -3926
  7. package/dist/chunk-LATXYFMO.js.map +1 -0
  8. package/dist/devtools.js +5 -2
  9. package/dist/devtools.js.map +1 -1
  10. package/dist/dom.js +2 -2
  11. package/dist/ground.js +1 -1
  12. package/dist/index.js +35 -1
  13. package/dist/r3f.js +3 -3
  14. package/dist/react.js +5 -3
  15. package/dist/types/packages/core/src/behavior/compile.d.ts +74 -0
  16. package/dist/types/packages/core/src/behavior/define-behavior.d.ts +56 -0
  17. package/dist/types/packages/core/src/behavior/differ.d.ts +91 -0
  18. package/dist/types/packages/core/src/behavior/harness.d.ts +65 -0
  19. package/dist/types/packages/core/src/behavior/migrate.d.ts +87 -0
  20. package/dist/types/packages/core/src/behavior/public-reads.d.ts +31 -0
  21. package/dist/types/packages/core/src/behavior/runtime.d.ts +102 -0
  22. package/dist/types/packages/core/src/behavior/types.d.ts +358 -0
  23. package/dist/types/packages/core/src/behavior/world-view.d.ts +36 -0
  24. package/dist/types/packages/core/src/engine/engine.d.ts +38 -1
  25. package/dist/types/packages/core/src/engine/guests.d.ts +199 -0
  26. package/dist/types/packages/core/src/engine/reflectors.d.ts +8 -0
  27. package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +26 -0
  28. package/dist/types/packages/core/src/guards/guarded-tx.d.ts +57 -0
  29. package/dist/types/packages/core/src/index.d.ts +11 -3
  30. package/dist/types/packages/core/src/widget/define-widget.d.ts +28 -0
  31. package/dist/types/packages/core/src/widget/props.d.ts +35 -1
  32. package/dist/types/packages/core/src/widget/spawn.d.ts +14 -0
  33. package/dist/types/packages/dom/src/loop.d.ts +9 -0
  34. package/dist/types/packages/react/src/hooks.d.ts +12 -1
  35. package/dist/types/packages/react/src/index.d.ts +1 -1
  36. package/package.json +31 -65
  37. package/dist/chunk-3PHPWRB2.js.map +0 -1
  38. package/dist/chunk-7LZAXQAO.js.map +0 -1
  39. package/dist/chunk-ENMRSITH.js.map +0 -1
package/dist/devtools.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  WheelPan,
15
15
  WheelZoom,
16
16
  Wire
17
- } from "./chunk-7LZAXQAO.js";
17
+ } from "./chunk-LATXYFMO.js";
18
18
  import "./chunk-OR5FNVUE.js";
19
19
 
20
20
  // ../devtools/src/attach.ts
@@ -505,8 +505,11 @@ function attachDevtools(engine, opts = {}) {
505
505
  if (profiler !== null) {
506
506
  core.enableTelemetry();
507
507
  removePublish = core.onPublish(() => {
508
- const us = core.lastFrame()?.phaseFlushMicros.get("reflect");
508
+ const us = core.lastFrame()?.reflectMicros;
509
509
  if (us !== void 0 && us > 0) profiler.lane("reflect", us / 1e3);
510
+ for (const g of core.guests.list()) {
511
+ if (g.status === "running" && g.lastMs > 0) profiler.lane(`guest:${g.id}`, g.lastMs);
512
+ }
510
513
  });
511
514
  }
512
515
  let glPanel = null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../devtools/src/attach.ts","../../devtools/src/dock.ts","../../devtools/src/gl-panel.ts","../../devtools/src/index.ts"],"sourcesContent":["/**\n * @ice/devtools — strata's first-party tools, engine-flavored (rebuilt\n * 2026-07-13, superseding the M10 hand-rolled 4-tab panel: the standing rule\n * is observers/inspectors WRAP `@vibecook/strata-ecs/tools`, never\n * re-implement them).\n *\n * `attachDevtools(engine)` = strata's observer panel + FPS profiler, plus the\n * engine glue strata cannot know:\n *\n * DESCRIBE PrefabId → widget type; recognizers → kind + live phase;\n * pointers/ports/wires/peers/chrome labeled as such.\n * DURABLE TAB fed from the facade's CURRENT DocSession (docId, converged\n * snapshot, attachment baseline) — the getter re-reads per\n * poll, so it tracks docs.create/open/join/close live.\n * EPHEMERAL TAB fed from an app-provided PresenceSession getter (the\n * facade keeps presence private; collab demos pass theirs).\n * \"reflect\" LANE the engine's post-notify reflector flush cost — the one\n * frame slice strata's world hooks cannot see — reported\n * into the profiler via an onPublish hook reading\n * `lastFrame()` telemetry (one frame late by construction:\n * publish runs before this frame's reflect).\n *\n * By default all three tools mount into ONE draggable dock (dock.ts —\n * 2026-07-13, James: \"make the 3 tools into one draggable panel\"); pass\n * `dock: false` for the classic scattered corners.\n *\n * Reads only, outside the tick (verified-unrestricted); never a reflector,\n * never writes ECS. Nobody imports this package (depcruise-enforced leaf).\n */\nimport type { Entity, World } from \"@vibecook/strata-ecs\";\nimport {\n attachObserver,\n attachProfiler,\n defaultDescribe,\n type DescribeFn,\n type EntityDescription,\n type ObserverDurableSource,\n type ObserverEphemeralSource,\n type ObserverHandle,\n type ObserverOptions,\n type ProfilerHandle,\n type ProfilerOptions,\n} from \"@vibecook/strata-ecs/tools\";\nimport { createDock, type Dock, type DockOptions, type DockSlotId } from \"./dock\";\nimport { createGlPanel, type GlPanel, type GlPanelOptions, type GlPanelStats } from \"./gl-panel\";\nimport {\n CursorVisual,\n Drag,\n GesturePhases,\n GestureSuspended,\n HandleSpec,\n LongPress,\n Pinch,\n Pointer,\n Port,\n PrefabId,\n PresencePeer,\n Tap,\n WheelPan,\n WheelZoom,\n Wire,\n type CanvasEngine,\n type Engine,\n type PresenceSession,\n} from \"@ice/core\";\n\nexport interface DevtoolsOpts {\n /** Where the dock (or, with `dock: false`, each panel) mounts (default: document.body). */\n readonly container?: HTMLElement;\n /**\n * ONE draggable dock hosting all three tools (the default). `false` scatters\n * them to their classic fixed corners; an object sets initial placement.\n * The dock mounts lazily with the first tool that needs it.\n */\n readonly dock?: boolean | Pick<DockOptions, \"corner\" | \"title\" | \"width\">;\n /** Observer panel: `false` to omit, or strata passthrough options. */\n readonly observer?: boolean | Pick<ObserverOptions, \"defaultTab\" | \"tab\" | \"recorderCap\">;\n /** Profiler HUD: `false` to omit, or strata passthrough options. */\n readonly profiler?: boolean | Pick<ProfilerOptions, \"budgetMs\" | \"windowSize\" | \"corner\" | \"expanded\" | \"lanes\">;\n /** GL metrics panel: `false` to omit, or placement/budget options. Mounts lazily on the first `glStats` push. */\n readonly glPanel?: boolean | Pick<GlPanelOptions, \"corner\" | \"budgetMs\" | \"expanded\">;\n /** Collab demos: the presence session whose store feeds the ephemeral tab. */\n readonly presence?: () => PresenceSession | null | undefined;\n /** Override entity labeling (default: the engine describe below). */\n readonly describe?: DescribeFn;\n}\n\nexport interface DevtoolsHandle {\n readonly observer: ObserverHandle | null;\n readonly profiler: ProfilerHandle | null;\n /** Host-cost lane passthrough (paint, decode…); no-op when the profiler is off. */\n lane(name: string, ms: number): void;\n /**\n * Feed one GL frame's metrics (wire `<GLViews onFrameStats>` here). The GL\n * panel mounts on the first push — renderer counts, virtual-texture census,\n * LOD banding, cull population. No-op when opts.glPanel === false.\n */\n glStats(stats: GlPanelStats): void;\n /** Tear all panels + the telemetry hook down. Idempotent. */\n detach(): void;\n}\n\nconst COLOR = {\n widget: \"#3fb950\",\n gesture: \"#a371f7\",\n pointer: \"#58a6ff\",\n graph: \"#ffa657\",\n presence: \"#f778ba\",\n chrome: \"#8b949e\",\n} as const;\n\nconst PHASE_ORDER = [\"Active\", \"Possible\", \"Recognized\", \"Pending\", \"Ended\", \"Cancelled\", \"Failed\"] as const;\n\nfunction phaseOf(world: World, e: Entity): string | null {\n // Suspended is a rider OUTSIDE the phase set (a pinch freezes single-pointer\n // recognizers without ending them) — surface it first, it explains \"stuck\".\n if (world.hasTag(e, GestureSuspended)) return \"suspended\";\n for (const name of PHASE_ORDER) {\n if (world.hasTag(e, GesturePhases.tags[name])) return name.toLowerCase();\n }\n return null;\n}\n\n/**\n * Engine-aware entity labeling for the observer's lists + timeline: identity\n * off ROLE (widget type, recognizer kind + phase, pointer device), not off\n * raw component-name composition. Falls back to strata's defaultDescribe.\n */\nexport function engineDescribe(world: World, e: Entity): EntityDescription {\n const prefab = world.get(e, PrefabId)?.id;\n if (typeof prefab === \"string\") return { label: prefab, color: COLOR.widget };\n\n const kind = world.has(e, Tap)\n ? \"tap\"\n : world.has(e, LongPress)\n ? \"longpress\"\n : world.has(e, Pinch)\n ? \"pinch\"\n : world.has(e, WheelZoom)\n ? \"wheelzoom\"\n : world.has(e, WheelPan)\n ? \"wheelpan\"\n : world.has(e, Drag)\n ? \"drag\"\n : undefined;\n if (kind !== undefined) return { label: kind, color: COLOR.gesture, phase: phaseOf(world, e) };\n\n if (world.has(e, Pointer)) return { label: `pointer:${world.read(e, Pointer).device}`, color: COLOR.pointer };\n if (world.has(e, Port)) return { label: \"port\", color: COLOR.graph };\n if (world.hasTag(e, Wire)) return { label: \"wire\", color: COLOR.graph };\n if (world.hasTag(e, PresencePeer)) return { label: \"peer\", color: COLOR.presence };\n if (world.has(e, CursorVisual)) return { label: \"cursor\", color: COLOR.presence };\n if (world.has(e, HandleSpec)) return { label: \"handle\", color: COLOR.chrome };\n return defaultDescribe(world, e);\n}\n\n/** Accepts the facade (durable tab included) or a bare core engine. */\nexport type DevtoolsEngine = CanvasEngine | Engine;\n\nexport function attachDevtools(engine: DevtoolsEngine, opts: DevtoolsOpts = {}): DevtoolsHandle {\n const isFacade = \"docs\" in engine;\n const core: Engine = isFacade ? engine.engine : engine;\n const world = core.world;\n\n // Durable tab: only the facade knows the current session; the getter is\n // re-read each poll so it swaps live across doc open/close/join. The store\n // and attachment are the STRUCTURAL mirrors the observer contract documents\n // (docId + snapshot / baseline) — never a sync path.\n const docs = isFacade ? engine.docs : undefined;\n const durable =\n docs === undefined\n ? undefined\n : (): ObserverDurableSource | null => {\n const s = docs.current();\n if (s === undefined) return null;\n return {\n store: s.store as unknown as ObserverDurableSource[\"store\"],\n attachment: s.attachment as unknown as ObserverDurableSource[\"attachment\"],\n };\n };\n\n const presence = opts.presence;\n const ephemeral =\n presence === undefined\n ? undefined\n : (): ObserverEphemeralSource | null => presence()?.eph ?? null;\n\n // The dock is created with the first tool that mounts (so all-flags-off\n // attaches — and glPanel-only attaches before the first push — stay DOM-free).\n let dock: Dock | null = null;\n const mountIn = (id: DockSlotId): { container?: HTMLElement } => {\n if (opts.dock === false) return opts.container !== undefined ? { container: opts.container } : {};\n if (dock === null) {\n dock = createDock({\n ...(typeof opts.dock === \"object\" ? opts.dock : {}),\n ...(opts.container !== undefined ? { container: opts.container } : {}),\n });\n }\n return { container: dock.slot(id) };\n };\n\n const observer =\n opts.observer === false\n ? null\n : attachObserver(world, {\n ...(typeof opts.observer === \"object\" ? opts.observer : {}),\n ...mountIn(\"observer\"),\n describe: opts.describe ?? engineDescribe,\n ...(durable !== undefined ? { durable } : {}),\n ...(ephemeral !== undefined ? { ephemeral } : {}),\n });\n\n const profiler =\n opts.profiler === false\n ? null\n : attachProfiler(world, {\n ...(typeof opts.profiler === \"object\" ? opts.profiler : {}),\n ...mountIn(\"profiler\"),\n });\n\n // The \"reflect\" lane: reflector flushes run POST-notify, invisible to\n // strata's in-world hooks. Publish runs before this frame's reflect, so the\n // hook reports the PREVIOUS frame's cost — a one-frame lag a HUD can carry.\n let removePublish: (() => void) | undefined;\n if (profiler !== null) {\n core.enableTelemetry();\n removePublish = core.onPublish(() => {\n const us = core.lastFrame()?.phaseFlushMicros.get(\"reflect\");\n if (us !== undefined && us > 0) profiler.lane(\"reflect\", us / 1000);\n });\n }\n\n let glPanel: GlPanel | null = null;\n let detached = false;\n return {\n observer,\n profiler,\n lane(name, ms) {\n profiler?.lane(name, ms);\n },\n glStats(stats) {\n if (detached || opts.glPanel === false) return;\n if (glPanel === null) {\n glPanel = createGlPanel({\n ...(typeof opts.glPanel === \"object\" ? opts.glPanel : {}),\n ...mountIn(\"gl\"),\n });\n }\n glPanel.push(stats);\n },\n detach() {\n if (detached) return;\n detached = true;\n removePublish?.();\n observer?.dispose();\n profiler?.dispose();\n glPanel?.dispose();\n glPanel = null;\n dock?.dispose();\n dock = null;\n },\n };\n}\n","/**\n * The devtools dock — ONE draggable window hosting all three tools\n * (2026-07-13, James: \"make the 3 tools into one draggable panel\").\n *\n * strata's observer + profiler and our GL panel each mount `position: fixed`\n * into their own corner. The dock does NOT re-implement any of them (standing\n * rule: wrap first-party tools): it is a fixed, draggable shell with three\n * stacked slots; each tool mounts into its slot via its own `container`\n * option, and dock-scoped CSS de-windows them (`position: static`, full\n * width, no border/shadow) so they read as sections of one panel. Their own\n * behavior is untouched — the observer keeps its tabs/collapse/vertical\n * resize (its header drag goes inert: left/top are ignored at static), the\n * profiler + GL strips keep their click-to-expand.\n *\n * Slot order is fixed by the dock, not by mount order: profiler and GL are\n * one-line summary strips, the observer is the deep-dive — strips on top.\n * Layout (x/y/width/open) persists to localStorage, same as the observer:\n * schema edits full-reload the page by design; the dock must stay put.\n */\n\nexport type DockCorner = \"tl\" | \"tr\" | \"bl\" | \"br\";\n\nexport type DockSlotId = \"profiler\" | \"gl\" | \"observer\";\n\nexport interface DockOptions {\n /** Where the dock mounts (default: document.body). */\n readonly container?: HTMLElement;\n /** Initial corner when nothing is persisted yet (default \"tr\"). */\n readonly corner?: DockCorner;\n /** Header title (default \"ice devtools\"). */\n readonly title?: string;\n /** Initial width in px when nothing is persisted yet (default 420). */\n readonly width?: number;\n}\n\nexport interface Dock {\n readonly el: HTMLElement;\n /** The mount element for one tool. Empty slots are display:none via CSS. */\n slot(id: DockSlotId): HTMLElement;\n dispose(): void;\n}\n\nconst LS_KEY = \"ice-dock:layout\";\nconst STYLE_ID = \"ice-dock-style\";\nconst MARGIN = 8;\nconst KEEP = 90; // px that must stay on screen so the header is always grabbable\nconst SLOT_ORDER: readonly DockSlotId[] = [\"profiler\", \"gl\", \"observer\"];\n\ninterface Layout {\n x?: number;\n y?: number;\n w?: number;\n open?: boolean;\n}\n\nfunction loadLayout(): Layout {\n try {\n return JSON.parse(localStorage.getItem(LS_KEY) ?? \"{}\") as Layout;\n } catch {\n return {};\n }\n}\n\nfunction saveLayout(patch: Layout): void {\n try {\n localStorage.setItem(LS_KEY, JSON.stringify({ ...loadLayout(), ...patch }));\n } catch {\n /* private mode / quota — non-fatal for a dev tool */\n }\n}\n\n// strata-obs's window chrome, verbatim, plus the de-windowing overrides for\n// the hosted tools. `!important` beats both the tools' own stylesheets and\n// the observer's inline width restored from ITS localStorage layout.\nconst CSS = `\n.ice-dock { position: fixed; z-index: 99999; width: 420px; min-width: 340px; max-width: 92vw;\n background: rgba(13,17,23,.96); border: 1px solid #30363d; border-radius: 10px;\n color: #e6edf3; font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;\n box-shadow: 0 8px 28px rgba(0,0,0,.45); overflow: hidden; resize: horizontal; }\n.ice-dock * { box-sizing: border-box; }\n.ice-dock.collapsed { resize: none; }\n.ice-dock.dragging { opacity: .92; cursor: grabbing; }\n.ice-dock-head { display: flex; align-items: center; gap: 7px; padding: 7px 10px; cursor: grab;\n background: rgba(22,27,34,.96); user-select: none; }\n.ice-dock-grip { color: #484f58; }\n.ice-dock-name { font-weight: 700; color: #56d4dd; }\n.ice-dock-btn { background: none; border: none; color: #8b949e; cursor: pointer; font: inherit;\n padding: 2px 6px; margin-left: auto; }\n.ice-dock-btn:hover { color: #e6edf3; }\n.ice-dock-body { max-height: calc(100vh - 96px); overflow-y: auto; }\n.ice-dock.collapsed .ice-dock-body { display: none; }\n.ice-dock-slot { border-top: 1px solid #30363d; }\n.ice-dock-slot:empty { display: none; }\n\n/* De-window the hosted tools — the dock is the window now. */\n.ice-dock .strata-obs, .ice-dock .strata-prof, .ice-dock .ice-gl {\n position: static !important; width: auto !important; min-width: 0 !important;\n border: none !important; border-radius: 0 !important; box-shadow: none !important; }\n.ice-dock .strata-obs { resize: vertical !important; }\n.ice-dock .strata-obs-head { cursor: default; }\n.ice-dock .strata-prof-body, .ice-dock .ice-gl-body { min-width: 0; }\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID) !== null) return;\n const el = doc.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n doc.head.appendChild(el);\n}\n\nexport function createDock(opts: DockOptions = {}): Dock {\n const container = opts.container ?? document.body;\n const doc = container.ownerDocument;\n const win = doc.defaultView ?? window;\n injectStyle(doc);\n\n const clampPos = (x: number, y: number, w: number): { x: number; y: number } => {\n const maxX = win.innerWidth - KEEP;\n const minX = Math.min(MARGIN, MARGIN - (w - KEEP));\n const maxY = win.innerHeight - 34;\n return { x: Math.min(Math.max(x, minX), maxX), y: Math.min(Math.max(y, MARGIN), maxY) };\n };\n\n const root = doc.createElement(\"div\");\n root.className = \"ice-dock\";\n\n const head = doc.createElement(\"div\");\n head.className = \"ice-dock-head\";\n const grip = doc.createElement(\"span\");\n grip.className = \"ice-dock-grip\";\n grip.setAttribute(\"aria-hidden\", \"true\");\n grip.textContent = \"⠿\";\n const name = doc.createElement(\"span\");\n name.className = \"ice-dock-name\";\n name.textContent = opts.title ?? \"ice devtools\";\n const collapse = doc.createElement(\"button\");\n collapse.type = \"button\";\n collapse.className = \"ice-dock-btn\";\n collapse.title = \"Collapse\";\n head.append(grip, name, collapse);\n\n const body = doc.createElement(\"div\");\n body.className = \"ice-dock-body\";\n const slots = new Map<DockSlotId, HTMLElement>();\n for (const id of SLOT_ORDER) {\n const s = doc.createElement(\"div\");\n s.className = \"ice-dock-slot\";\n s.dataset.slot = id;\n body.appendChild(s);\n slots.set(id, s);\n }\n\n root.append(head, body);\n container.appendChild(root);\n\n // restore layout: width, then position (clamped), then open state\n const l = loadLayout();\n const w = typeof l.w === \"number\" ? l.w : (opts.width ?? 420);\n root.style.width = `${w}px`;\n const corner = opts.corner ?? \"tr\";\n const fx = corner === \"tl\" || corner === \"bl\" ? 16 : win.innerWidth - w - 16;\n const fy = corner === \"tl\" || corner === \"tr\" ? 16 : win.innerHeight - 220;\n const p = clampPos(l.x ?? fx, l.y ?? fy, w);\n root.style.left = `${p.x}px`;\n root.style.top = `${p.y}px`;\n let open = l.open ?? true;\n const applyOpen = (): void => {\n root.classList.toggle(\"collapsed\", !open);\n collapse.textContent = open ? \"▾\" : \"▸\";\n };\n applyOpen();\n collapse.addEventListener(\"click\", () => {\n open = !open;\n saveLayout({ open });\n applyOpen();\n });\n\n let drag: { id: number; sx: number; sy: number; ox: number; oy: number } | null = null;\n head.addEventListener(\"pointerdown\", (e) => {\n if ((e.target as HTMLElement).closest(\"button\") !== null) return; // let buttons be buttons\n const r = root.getBoundingClientRect();\n drag = { id: e.pointerId, sx: e.clientX, sy: e.clientY, ox: r.left, oy: r.top };\n if (typeof head.setPointerCapture === \"function\") {\n try {\n head.setPointerCapture(e.pointerId);\n } catch {\n /* synthetic events (tests) have no active pointer */\n }\n }\n root.classList.add(\"dragging\");\n e.preventDefault();\n });\n head.addEventListener(\"pointermove\", (e) => {\n if (drag?.id !== e.pointerId) return;\n const q = clampPos(drag.ox + (e.clientX - drag.sx), drag.oy + (e.clientY - drag.sy), root.offsetWidth);\n root.style.left = `${q.x}px`;\n root.style.top = `${q.y}px`;\n });\n const endDrag = (e: PointerEvent): void => {\n if (drag?.id !== e.pointerId) return;\n drag = null;\n root.classList.remove(\"dragging\");\n const r = root.getBoundingClientRect();\n saveLayout({ x: r.left, y: r.top });\n };\n head.addEventListener(\"pointerup\", endDrag);\n head.addEventListener(\"pointercancel\", endDrag);\n\n const onWinResize = (): void => {\n const r = root.getBoundingClientRect();\n const q = clampPos(r.left, r.top, root.offsetWidth);\n root.style.left = `${q.x}px`;\n root.style.top = `${q.y}px`;\n };\n win.addEventListener(\"resize\", onWinResize);\n const ro = new ResizeObserver(() => {\n saveLayout({ w: root.offsetWidth });\n });\n ro.observe(root);\n\n let disposed = false;\n return {\n el: root,\n slot(id) {\n // SLOT_ORDER covers the full DockSlotId union — the map is total.\n return slots.get(id) as HTMLElement;\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n win.removeEventListener(\"resize\", onWinResize);\n ro.disconnect();\n root.remove();\n },\n };\n}\n","/**\n * The GL metrics panel — the engine-specific renderer readout strata has no\n * counterpart for (2026-07-13, James: \"comprehensive r3f profiler ... and our\n * engine specific optimization metrics ... with nice visuals\").\n *\n * Pure DOM, strata's visual language (same palette/typography as the observer\n * + profiler HUD so the three read as one toolset). Fed by pushes of\n * `GlPanelStats` — the STRUCTURAL MIRROR of @ice/r3f's `GlFrameStats`\n * (devtools cannot import r3f under the walls; keep the shapes aligned):\n *\n * head fps · gpu ms + a gpu sparkline against the frame budget\n * frame cpu / gpu bars vs budget, with session maxima\n * render draw calls · triangles · points/lines · programs · geoms · textures\n * virtual live render targets + total resolution, FBO bytes vs budget,\n * textures the island phase census as a segmented bar, repaint/evict counters\n * lod zoom → hysteresis band → effective DPR, live targets per band\n * widgets Visible vs Culled population\n *\n * All values land via `textContent` (never innerHTML with data). Rendering is\n * throttled — pushes arrive per composited frame, the DOM refreshes at ~8 Hz.\n */\n\n/** Mirror of @ice/r3f `GlFrameStats` (structural — see the header). */\nexport interface GlPanelStats {\n readonly cpuMs: number;\n readonly gpuMs: number;\n readonly fps: number;\n readonly drawCalls: number;\n readonly triangles: number;\n readonly points: number;\n readonly lines: number;\n readonly programs: number;\n readonly geometries: number;\n readonly textures: number;\n readonly renderTargets: number;\n readonly renderMegaPixels: number;\n readonly fboBytes: number;\n readonly fboBudgetBytes: number;\n readonly islands: {\n readonly total: number;\n readonly hot: number;\n readonly warm: number;\n readonly waking: number;\n readonly cold: number;\n readonly dormant: number;\n };\n readonly bandHistogram: Readonly<Record<string, number>>;\n readonly repainted: number;\n readonly pendingPaints: number;\n readonly evicted: number;\n readonly zoom: number;\n readonly band: number;\n readonly effectiveDpr: number;\n readonly visibleWidgets: number;\n readonly culledWidgets: number;\n}\n\nexport type GlPanelCorner = \"tl\" | \"tr\" | \"bl\" | \"br\";\n\nexport interface GlPanelOptions {\n readonly container?: HTMLElement;\n readonly corner?: GlPanelCorner;\n /** Frame budget in ms — the sparkline's reference line (default 16.7). */\n readonly budgetMs?: number;\n /** Start with the detail body open (default false). */\n readonly expanded?: boolean;\n}\n\nexport interface GlPanel {\n push(stats: GlPanelStats): void;\n dispose(): void;\n}\n\nconst STYLE_ID = \"ice-gl-style\";\n\n// strata-prof's palette, verbatim — the panels must read as one toolset.\nconst CSS = `\n.ice-gl { position: fixed; z-index: 99998; background: rgba(13,17,23,.96);\n border: 1px solid #30363d; border-radius: 10px; color: #e6edf3;\n font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;\n box-shadow: 0 6px 22px rgba(0,0,0,.45); user-select: none; }\n.ice-gl * { box-sizing: border-box; }\n.ice-gl.tl { top: 10px; left: 10px; } .ice-gl.tr { top: 10px; right: 10px; }\n.ice-gl.bl { bottom: 10px; left: 10px; } .ice-gl.br { bottom: 10px; right: 10px; }\n.ice-gl-head { display: flex; align-items: center; gap: 8px; padding: 6px 9px; cursor: pointer; }\n.ice-gl-name { font-weight: 700; color: #56d4dd; }\n.ice-gl-fps { font-weight: 700; color: #a371f7; min-width: 46px; }\n.ice-gl-gpu { color: #8b949e; min-width: 74px; }\n.ice-gl-caret { color: #8b949e; margin-left: auto; }\n.ice-gl-body { display: none; border-top: 1px solid #21262d; padding: 7px 9px 8px; min-width: 300px; }\n.ice-gl.open .ice-gl-body { display: block; }\n.ice-gl-sect { color: #8b949e; font-weight: 700; padding: 5px 0 2px; }\n.ice-gl-row { display: flex; align-items: center; gap: 7px; padding: 1px 0; white-space: nowrap; }\n.ice-gl-name2 { min-width: 64px; color: #8b949e; }\n.ice-gl-bar { flex: 1; height: 5px; border-radius: 3px; background: #21262d; overflow: hidden; }\n.ice-gl-bar > i { display: block; height: 100%; background: #58a6ff; border-radius: 3px; }\n.ice-gl-stat { color: #e6edf3; margin-left: auto; }\n.ice-gl-dim { color: #8b949e; }\n.ice-gl-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px 10px; padding: 2px 0; }\n.ice-gl-cell b { display: block; color: #e6edf3; font-weight: 700; }\n.ice-gl-cell span { color: #8b949e; }\n.ice-gl-seg { display: flex; height: 7px; border-radius: 3px; overflow: hidden; background: #21262d; margin: 3px 0 2px; }\n.ice-gl-seg > i { display: block; height: 100%; }\n.ice-gl-legend { color: #8b949e; padding-bottom: 2px; }\n`;\n\nconst PHASE_COLORS: Readonly<Record<string, string>> = {\n hot: \"#ff7b72\",\n waking: \"#58a6ff\",\n warm: \"#3fb950\",\n cold: \"#8b949e\",\n dormant: \"#484f58\",\n};\n\nconst SPARK_W = 132;\nconst SPARK_H = 26;\nconst SPARK_SAMPLES = SPARK_W / 2;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID) !== null) return;\n const el = doc.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n doc.head.appendChild(el);\n}\n\nconst fmtInt = (n: number): string => n.toLocaleString();\nconst fmtK = (n: number): string => (n >= 1e6 ? `${(n / 1e6).toFixed(2)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n));\nconst fmtMs = (ms: number): string => `${ms.toFixed(2)}ms`;\nconst fmtMB = (b: number): string => `${(b / 1048576).toFixed(0)}MB`;\n\nexport function createGlPanel(opts: GlPanelOptions = {}): GlPanel {\n const container = opts.container ?? document.body;\n const doc = container.ownerDocument;\n const budgetMs = opts.budgetMs ?? 16.7;\n injectStyle(doc);\n\n const root = doc.createElement(\"div\");\n root.className = `ice-gl ${opts.corner ?? \"bl\"}${opts.expanded === true ? \" open\" : \"\"}`;\n\n const head = doc.createElement(\"div\");\n head.className = \"ice-gl-head\";\n const name = doc.createElement(\"span\");\n name.className = \"ice-gl-name\";\n name.textContent = \"ice gl\";\n const fpsEl = doc.createElement(\"span\");\n fpsEl.className = \"ice-gl-fps\";\n const gpuEl = doc.createElement(\"span\");\n gpuEl.className = \"ice-gl-gpu\";\n const spark = doc.createElement(\"canvas\");\n spark.width = SPARK_W;\n spark.height = SPARK_H;\n spark.style.display = \"block\";\n const caret = doc.createElement(\"span\");\n caret.className = \"ice-gl-caret\";\n caret.textContent = \"▾\";\n head.append(name, fpsEl, gpuEl, spark, caret);\n head.addEventListener(\"click\", () => root.classList.toggle(\"open\"));\n\n const body = doc.createElement(\"div\");\n body.className = \"ice-gl-body\";\n root.append(head, body);\n container.appendChild(root);\n\n // --- static skeleton (rebuilt values only — no per-push DOM churn) --------\n const sect = (label: string): void => {\n const s = doc.createElement(\"div\");\n s.className = \"ice-gl-sect\";\n s.textContent = label;\n body.appendChild(s);\n };\n const bar = (label: string): { fill: HTMLElement; stat: HTMLElement } => {\n const row = doc.createElement(\"div\");\n row.className = \"ice-gl-row\";\n const n = doc.createElement(\"span\");\n n.className = \"ice-gl-name2\";\n n.textContent = label;\n const b = doc.createElement(\"div\");\n b.className = \"ice-gl-bar\";\n const fill = doc.createElement(\"i\");\n b.appendChild(fill);\n const stat = doc.createElement(\"span\");\n stat.className = \"ice-gl-stat\";\n row.append(n, b, stat);\n body.appendChild(row);\n return { fill, stat };\n };\n const textRow = (): HTMLElement => {\n const row = doc.createElement(\"div\");\n row.className = \"ice-gl-row\";\n body.appendChild(row);\n return row;\n };\n\n sect(\"frame\");\n const cpuBar = bar(\"cpu\");\n const gpuBar = bar(\"gpu\");\n\n sect(\"render\");\n const grid = doc.createElement(\"div\");\n grid.className = \"ice-gl-grid\";\n body.appendChild(grid);\n const cell = (label: string): HTMLElement => {\n const c = doc.createElement(\"div\");\n c.className = \"ice-gl-cell\";\n const v = doc.createElement(\"b\");\n const l = doc.createElement(\"span\");\n l.textContent = label;\n c.append(v, l);\n grid.appendChild(c);\n return v;\n };\n const callsEl = cell(\"calls\");\n const trisEl = cell(\"triangles\");\n const ptsEl = cell(\"pts+lines\");\n const progsEl = cell(\"programs\");\n const geosEl = cell(\"geometries\");\n const texsEl = cell(\"textures\");\n\n sect(\"virtual textures\");\n const rtRow = textRow();\n const fboBar = bar(\"fbo mem\");\n const seg = doc.createElement(\"div\");\n seg.className = \"ice-gl-seg\";\n body.appendChild(seg);\n const legend = doc.createElement(\"div\");\n legend.className = \"ice-gl-legend\";\n body.appendChild(legend);\n const paintRow = textRow();\n\n sect(\"lod\");\n const lodRow = textRow();\n const bandsRow = textRow();\n\n sect(\"widgets\");\n const cullBar = bar(\"visible\");\n\n // --- push/render (throttled) ----------------------------------------------\n const samples: number[] = [];\n let latest: GlPanelStats | undefined;\n let maxCpu = 0;\n let maxGpu = 0;\n let evictedTotal = 0;\n let lastRender = 0;\n let disposed = false;\n\n const drawSpark = (): void => {\n const ctx = spark.getContext(\"2d\");\n if (ctx === null) return;\n ctx.clearRect(0, 0, SPARK_W, SPARK_H);\n const top = budgetMs * 2; // budget sits mid-height\n for (let i = 0; i < samples.length; i++) {\n const v = samples[i] ?? 0;\n const h = Math.min(SPARK_H, (v / top) * SPARK_H);\n ctx.fillStyle = v > budgetMs ? \"#ff7b72\" : \"#3fb950\";\n ctx.fillRect(i * 2, SPARK_H - h, 1.6, h);\n }\n ctx.fillStyle = \"rgba(139,148,158,.55)\";\n ctx.fillRect(0, SPARK_H / 2, SPARK_W, 1);\n };\n\n const render = (): void => {\n const s = latest;\n if (s === undefined) return;\n fpsEl.textContent = s.fps > 0 ? `${Math.round(s.fps)} fps` : \"– fps\";\n gpuEl.textContent = s.gpuMs > 0 ? `gpu ${fmtMs(s.gpuMs)}` : \"gpu –\";\n drawSpark();\n if (!root.classList.contains(\"open\")) return;\n\n cpuBar.fill.style.width = `${Math.min(100, (s.cpuMs / budgetMs) * 100)}%`;\n cpuBar.stat.textContent = `${fmtMs(s.cpuMs)} · max ${fmtMs(maxCpu)}`;\n gpuBar.fill.style.width = `${Math.min(100, (s.gpuMs / budgetMs) * 100)}%`;\n gpuBar.fill.style.background = s.gpuMs > budgetMs ? \"#ff7b72\" : \"#58a6ff\";\n gpuBar.stat.textContent = `${fmtMs(s.gpuMs)} · max ${fmtMs(maxGpu)}`;\n\n callsEl.textContent = fmtInt(s.drawCalls);\n trisEl.textContent = fmtK(s.triangles);\n ptsEl.textContent = fmtK(s.points + s.lines);\n progsEl.textContent = fmtInt(s.programs);\n geosEl.textContent = fmtInt(s.geometries);\n texsEl.textContent = fmtInt(s.textures);\n\n rtRow.textContent = `${s.renderTargets} targets · ${s.renderMegaPixels.toFixed(1)} MP total`;\n fboBar.fill.style.width = `${Math.min(100, (s.fboBytes / s.fboBudgetBytes) * 100)}%`;\n fboBar.fill.style.background = s.fboBytes > s.fboBudgetBytes * 0.85 ? \"#ffa657\" : \"#58a6ff\";\n fboBar.stat.textContent = `${fmtMB(s.fboBytes)} / ${fmtMB(s.fboBudgetBytes)}`;\n\n seg.textContent = \"\";\n const phases: Array<[string, number]> = [\n [\"hot\", s.islands.hot],\n [\"waking\", s.islands.waking],\n [\"warm\", s.islands.warm],\n [\"cold\", s.islands.cold],\n [\"dormant\", s.islands.dormant],\n ];\n const denom = Math.max(1, s.islands.total);\n const legendParts: string[] = [];\n for (const [phase, count] of phases) {\n if (count === 0) continue;\n const i = doc.createElement(\"i\");\n i.style.width = `${(count / denom) * 100}%`;\n i.style.background = PHASE_COLORS[phase] ?? \"#8b949e\";\n seg.appendChild(i);\n legendParts.push(`${count} ${phase}`);\n }\n legend.textContent = legendParts.length > 0 ? legendParts.join(\" · \") : \"no islands\";\n paintRow.textContent = `repainted ${s.repainted}/frame · pending ${s.pendingPaints} · evicted Σ${evictedTotal}`;\n\n lodRow.textContent = `zoom ${(s.zoom * 100).toFixed(0)}% → band ×${s.band} → paint dpr ${s.effectiveDpr.toFixed(2)}`;\n const bands = Object.entries(s.bandHistogram).sort((a, b) => a[0].localeCompare(b[0]));\n bandsRow.textContent =\n bands.length > 0 ? `targets by band: ${bands.map(([k, v]) => `${k}: ${v}`).join(\" · \")}` : \"no live targets\";\n\n const pop = Math.max(1, s.visibleWidgets + s.culledWidgets);\n cullBar.fill.style.width = `${(s.visibleWidgets / pop) * 100}%`;\n cullBar.fill.style.background = \"#3fb950\";\n cullBar.stat.textContent = `${s.visibleWidgets} visible · ${s.culledWidgets} culled`;\n };\n\n return {\n push(s) {\n if (disposed) return;\n latest = s;\n maxCpu = Math.max(maxCpu, s.cpuMs);\n maxGpu = Math.max(maxGpu, s.gpuMs);\n evictedTotal += s.evicted;\n samples.push(s.gpuMs > 0 ? s.gpuMs : s.cpuMs);\n if (samples.length > SPARK_SAMPLES) samples.splice(0, samples.length - SPARK_SAMPLES);\n const now = performance.now();\n if (now - lastRender >= 120) {\n lastRender = now;\n render();\n }\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n root.remove();\n },\n };\n}\n","/**\n * @ice/devtools — strata's observer panel + FPS profiler, engine-flavored\n * (describe/durable/ephemeral/reflect-lane glue). Reads the world outside the\n * tick; never a reflector, never writes ECS. Nobody imports us\n * (depcruise-enforced leaf).\n */\nexport const DEVTOOLS_VERSION = \"0.1.0\";\n\nexport {\n attachDevtools,\n engineDescribe,\n type DevtoolsEngine,\n type DevtoolsHandle,\n type DevtoolsOpts,\n} from \"./attach\";\nexport { createDock, type Dock, type DockCorner, type DockOptions, type DockSlotId } from \"./dock\";\nexport { createGlPanel, type GlPanel, type GlPanelCorner, type GlPanelOptions, type GlPanelStats } from \"./gl-panel\";\n"],"mappings":"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","names":["w","STYLE_ID","CSS","injectStyle"]}
1
+ {"version":3,"sources":["../../devtools/src/attach.ts","../../devtools/src/dock.ts","../../devtools/src/gl-panel.ts","../../devtools/src/index.ts"],"sourcesContent":["/**\n * @ice/devtools — strata's first-party tools, engine-flavored (rebuilt\n * 2026-07-13, superseding the M10 hand-rolled 4-tab panel: the standing rule\n * is observers/inspectors WRAP `@vibecook/strata-ecs/tools`, never\n * re-implement them).\n *\n * `attachDevtools(engine)` = strata's observer panel + FPS profiler, plus the\n * engine glue strata cannot know:\n *\n * DESCRIBE PrefabId → widget type; recognizers → kind + live phase;\n * pointers/ports/wires/peers/chrome labeled as such.\n * DURABLE TAB fed from the facade's CURRENT DocSession (docId, converged\n * snapshot, attachment baseline) — the getter re-reads per\n * poll, so it tracks docs.create/open/join/close live.\n * EPHEMERAL TAB fed from an app-provided PresenceSession getter (the\n * facade keeps presence private; collab demos pass theirs).\n * \"reflect\" LANE the engine's post-notify reflector flush cost — the one\n * frame slice strata's world hooks cannot see — reported\n * into the profiler via an onPublish hook reading\n * `lastFrame()` telemetry (one frame late by construction:\n * publish runs before this frame's reflect).\n *\n * By default all three tools mount into ONE draggable dock (dock.ts —\n * 2026-07-13, James: \"make the 3 tools into one draggable panel\"); pass\n * `dock: false` for the classic scattered corners.\n *\n * Reads only, outside the tick (verified-unrestricted); never a reflector,\n * never writes ECS. Nobody imports this package (depcruise-enforced leaf).\n */\nimport type { Entity, World } from \"@vibecook/strata-ecs\";\nimport {\n attachObserver,\n attachProfiler,\n defaultDescribe,\n type DescribeFn,\n type EntityDescription,\n type ObserverDurableSource,\n type ObserverEphemeralSource,\n type ObserverHandle,\n type ObserverOptions,\n type ProfilerHandle,\n type ProfilerOptions,\n} from \"@vibecook/strata-ecs/tools\";\nimport { createDock, type Dock, type DockOptions, type DockSlotId } from \"./dock\";\nimport { createGlPanel, type GlPanel, type GlPanelOptions, type GlPanelStats } from \"./gl-panel\";\nimport {\n CursorVisual,\n Drag,\n GesturePhases,\n GestureSuspended,\n HandleSpec,\n LongPress,\n Pinch,\n Pointer,\n Port,\n PrefabId,\n PresencePeer,\n Tap,\n WheelPan,\n WheelZoom,\n Wire,\n type CanvasEngine,\n type Engine,\n type PresenceSession,\n} from \"@ice/core\";\n\nexport interface DevtoolsOpts {\n /** Where the dock (or, with `dock: false`, each panel) mounts (default: document.body). */\n readonly container?: HTMLElement;\n /**\n * ONE draggable dock hosting all three tools (the default). `false` scatters\n * them to their classic fixed corners; an object sets initial placement.\n * The dock mounts lazily with the first tool that needs it.\n */\n readonly dock?: boolean | Pick<DockOptions, \"corner\" | \"title\" | \"width\">;\n /** Observer panel: `false` to omit, or strata passthrough options. */\n readonly observer?: boolean | Pick<ObserverOptions, \"defaultTab\" | \"tab\" | \"recorderCap\">;\n /** Profiler HUD: `false` to omit, or strata passthrough options. */\n readonly profiler?: boolean | Pick<ProfilerOptions, \"budgetMs\" | \"windowSize\" | \"corner\" | \"expanded\" | \"lanes\">;\n /** GL metrics panel: `false` to omit, or placement/budget options. Mounts lazily on the first `glStats` push. */\n readonly glPanel?: boolean | Pick<GlPanelOptions, \"corner\" | \"budgetMs\" | \"expanded\">;\n /** Collab demos: the presence session whose store feeds the ephemeral tab. */\n readonly presence?: () => PresenceSession | null | undefined;\n /** Override entity labeling (default: the engine describe below). */\n readonly describe?: DescribeFn;\n}\n\nexport interface DevtoolsHandle {\n readonly observer: ObserverHandle | null;\n readonly profiler: ProfilerHandle | null;\n /** Host-cost lane passthrough (paint, decode…); no-op when the profiler is off. */\n lane(name: string, ms: number): void;\n /**\n * Feed one GL frame's metrics (wire `<GLViews onFrameStats>` here). The GL\n * panel mounts on the first push — renderer counts, virtual-texture census,\n * LOD banding, cull population. No-op when opts.glPanel === false.\n */\n glStats(stats: GlPanelStats): void;\n /** Tear all panels + the telemetry hook down. Idempotent. */\n detach(): void;\n}\n\nconst COLOR = {\n widget: \"#3fb950\",\n gesture: \"#a371f7\",\n pointer: \"#58a6ff\",\n graph: \"#ffa657\",\n presence: \"#f778ba\",\n chrome: \"#8b949e\",\n} as const;\n\nconst PHASE_ORDER = [\"Active\", \"Possible\", \"Recognized\", \"Pending\", \"Ended\", \"Cancelled\", \"Failed\"] as const;\n\nfunction phaseOf(world: World, e: Entity): string | null {\n // Suspended is a rider OUTSIDE the phase set (a pinch freezes single-pointer\n // recognizers without ending them) — surface it first, it explains \"stuck\".\n if (world.hasTag(e, GestureSuspended)) return \"suspended\";\n for (const name of PHASE_ORDER) {\n if (world.hasTag(e, GesturePhases.tags[name])) return name.toLowerCase();\n }\n return null;\n}\n\n/**\n * Engine-aware entity labeling for the observer's lists + timeline: identity\n * off ROLE (widget type, recognizer kind + phase, pointer device), not off\n * raw component-name composition. Falls back to strata's defaultDescribe.\n */\nexport function engineDescribe(world: World, e: Entity): EntityDescription {\n const prefab = world.get(e, PrefabId)?.id;\n if (typeof prefab === \"string\") return { label: prefab, color: COLOR.widget };\n\n const kind = world.has(e, Tap)\n ? \"tap\"\n : world.has(e, LongPress)\n ? \"longpress\"\n : world.has(e, Pinch)\n ? \"pinch\"\n : world.has(e, WheelZoom)\n ? \"wheelzoom\"\n : world.has(e, WheelPan)\n ? \"wheelpan\"\n : world.has(e, Drag)\n ? \"drag\"\n : undefined;\n if (kind !== undefined) return { label: kind, color: COLOR.gesture, phase: phaseOf(world, e) };\n\n if (world.has(e, Pointer)) return { label: `pointer:${world.read(e, Pointer).device}`, color: COLOR.pointer };\n if (world.has(e, Port)) return { label: \"port\", color: COLOR.graph };\n if (world.hasTag(e, Wire)) return { label: \"wire\", color: COLOR.graph };\n if (world.hasTag(e, PresencePeer)) return { label: \"peer\", color: COLOR.presence };\n if (world.has(e, CursorVisual)) return { label: \"cursor\", color: COLOR.presence };\n if (world.has(e, HandleSpec)) return { label: \"handle\", color: COLOR.chrome };\n return defaultDescribe(world, e);\n}\n\n/** Accepts the facade (durable tab included) or a bare core engine. */\nexport type DevtoolsEngine = CanvasEngine | Engine;\n\nexport function attachDevtools(engine: DevtoolsEngine, opts: DevtoolsOpts = {}): DevtoolsHandle {\n const isFacade = \"docs\" in engine;\n const core: Engine = isFacade ? engine.engine : engine;\n const world = core.world;\n\n // Durable tab: only the facade knows the current session; the getter is\n // re-read each poll so it swaps live across doc open/close/join. The store\n // and attachment are the STRUCTURAL mirrors the observer contract documents\n // (docId + snapshot / baseline) — never a sync path.\n const docs = isFacade ? engine.docs : undefined;\n const durable =\n docs === undefined\n ? undefined\n : (): ObserverDurableSource | null => {\n const s = docs.current();\n if (s === undefined) return null;\n return {\n store: s.store as unknown as ObserverDurableSource[\"store\"],\n attachment: s.attachment as unknown as ObserverDurableSource[\"attachment\"],\n };\n };\n\n const presence = opts.presence;\n const ephemeral =\n presence === undefined\n ? undefined\n : (): ObserverEphemeralSource | null => presence()?.eph ?? null;\n\n // The dock is created with the first tool that mounts (so all-flags-off\n // attaches — and glPanel-only attaches before the first push — stay DOM-free).\n let dock: Dock | null = null;\n const mountIn = (id: DockSlotId): { container?: HTMLElement } => {\n if (opts.dock === false) return opts.container !== undefined ? { container: opts.container } : {};\n if (dock === null) {\n dock = createDock({\n ...(typeof opts.dock === \"object\" ? opts.dock : {}),\n ...(opts.container !== undefined ? { container: opts.container } : {}),\n });\n }\n return { container: dock.slot(id) };\n };\n\n const observer =\n opts.observer === false\n ? null\n : attachObserver(world, {\n ...(typeof opts.observer === \"object\" ? opts.observer : {}),\n ...mountIn(\"observer\"),\n describe: opts.describe ?? engineDescribe,\n ...(durable !== undefined ? { durable } : {}),\n ...(ephemeral !== undefined ? { ephemeral } : {}),\n });\n\n const profiler =\n opts.profiler === false\n ? null\n : attachProfiler(world, {\n ...(typeof opts.profiler === \"object\" ? opts.profiler : {}),\n ...mountIn(\"profiler\"),\n });\n\n // The \"reflect\" lane: reflector flushes run POST-notify, invisible to\n // strata's in-world hooks. Publish runs before this frame's reflect, so the\n // hook reports the PREVIOUS frame's cost — a one-frame lag a HUD can carry.\n //\n // FIXED 2026-08-15 (petition I14): this read `phaseFlushMicros.get(\"reflect\")`,\n // a map keyed by strata PHASE names — and reflect is not a phase, so the\n // lookup was always undefined and the lane NEVER reported since it shipped.\n // `FrameTelemetry.reflectMicros` is the real source (engine.ts arms the\n // reflector registry's own timing from `enableTelemetry`).\n //\n // The GUEST lanes (petition I14, 2026-08-15): guest work runs between the\n // tick and the publish hooks, so — like reflect — no strata in-world hook can\n // see it, and it was previously invisible to the profiler entirely. One lane\n // per guest, reporting its own last invocation; suspended guests stop\n // reporting rather than flatlining at zero, so a silent lane means \"gone\",\n // which is exactly what the doctor's row says too.\n let removePublish: (() => void) | undefined;\n if (profiler !== null) {\n core.enableTelemetry();\n removePublish = core.onPublish(() => {\n const us = core.lastFrame()?.reflectMicros;\n if (us !== undefined && us > 0) profiler.lane(\"reflect\", us / 1000);\n for (const g of core.guests.list()) {\n if (g.status === \"running\" && g.lastMs > 0) profiler.lane(`guest:${g.id}`, g.lastMs);\n }\n });\n }\n\n let glPanel: GlPanel | null = null;\n let detached = false;\n return {\n observer,\n profiler,\n lane(name, ms) {\n profiler?.lane(name, ms);\n },\n glStats(stats) {\n if (detached || opts.glPanel === false) return;\n if (glPanel === null) {\n glPanel = createGlPanel({\n ...(typeof opts.glPanel === \"object\" ? opts.glPanel : {}),\n ...mountIn(\"gl\"),\n });\n }\n glPanel.push(stats);\n },\n detach() {\n if (detached) return;\n detached = true;\n removePublish?.();\n observer?.dispose();\n profiler?.dispose();\n glPanel?.dispose();\n glPanel = null;\n dock?.dispose();\n dock = null;\n },\n };\n}\n","/**\n * The devtools dock — ONE draggable window hosting all three tools\n * (2026-07-13, James: \"make the 3 tools into one draggable panel\").\n *\n * strata's observer + profiler and our GL panel each mount `position: fixed`\n * into their own corner. The dock does NOT re-implement any of them (standing\n * rule: wrap first-party tools): it is a fixed, draggable shell with three\n * stacked slots; each tool mounts into its slot via its own `container`\n * option, and dock-scoped CSS de-windows them (`position: static`, full\n * width, no border/shadow) so they read as sections of one panel. Their own\n * behavior is untouched — the observer keeps its tabs/collapse/vertical\n * resize (its header drag goes inert: left/top are ignored at static), the\n * profiler + GL strips keep their click-to-expand.\n *\n * Slot order is fixed by the dock, not by mount order: profiler and GL are\n * one-line summary strips, the observer is the deep-dive — strips on top.\n * Layout (x/y/width/open) persists to localStorage, same as the observer:\n * schema edits full-reload the page by design; the dock must stay put.\n */\n\nexport type DockCorner = \"tl\" | \"tr\" | \"bl\" | \"br\";\n\nexport type DockSlotId = \"profiler\" | \"gl\" | \"observer\";\n\nexport interface DockOptions {\n /** Where the dock mounts (default: document.body). */\n readonly container?: HTMLElement;\n /** Initial corner when nothing is persisted yet (default \"tr\"). */\n readonly corner?: DockCorner;\n /** Header title (default \"ice devtools\"). */\n readonly title?: string;\n /** Initial width in px when nothing is persisted yet (default 420). */\n readonly width?: number;\n}\n\nexport interface Dock {\n readonly el: HTMLElement;\n /** The mount element for one tool. Empty slots are display:none via CSS. */\n slot(id: DockSlotId): HTMLElement;\n dispose(): void;\n}\n\nconst LS_KEY = \"ice-dock:layout\";\nconst STYLE_ID = \"ice-dock-style\";\nconst MARGIN = 8;\nconst KEEP = 90; // px that must stay on screen so the header is always grabbable\nconst SLOT_ORDER: readonly DockSlotId[] = [\"profiler\", \"gl\", \"observer\"];\n\ninterface Layout {\n x?: number;\n y?: number;\n w?: number;\n open?: boolean;\n}\n\nfunction loadLayout(): Layout {\n try {\n return JSON.parse(localStorage.getItem(LS_KEY) ?? \"{}\") as Layout;\n } catch {\n return {};\n }\n}\n\nfunction saveLayout(patch: Layout): void {\n try {\n localStorage.setItem(LS_KEY, JSON.stringify({ ...loadLayout(), ...patch }));\n } catch {\n /* private mode / quota — non-fatal for a dev tool */\n }\n}\n\n// strata-obs's window chrome, verbatim, plus the de-windowing overrides for\n// the hosted tools. `!important` beats both the tools' own stylesheets and\n// the observer's inline width restored from ITS localStorage layout.\nconst CSS = `\n.ice-dock { position: fixed; z-index: 99999; width: 420px; min-width: 340px; max-width: 92vw;\n background: rgba(13,17,23,.96); border: 1px solid #30363d; border-radius: 10px;\n color: #e6edf3; font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;\n box-shadow: 0 8px 28px rgba(0,0,0,.45); overflow: hidden; resize: horizontal; }\n.ice-dock * { box-sizing: border-box; }\n.ice-dock.collapsed { resize: none; }\n.ice-dock.dragging { opacity: .92; cursor: grabbing; }\n.ice-dock-head { display: flex; align-items: center; gap: 7px; padding: 7px 10px; cursor: grab;\n background: rgba(22,27,34,.96); user-select: none; }\n.ice-dock-grip { color: #484f58; }\n.ice-dock-name { font-weight: 700; color: #56d4dd; }\n.ice-dock-btn { background: none; border: none; color: #8b949e; cursor: pointer; font: inherit;\n padding: 2px 6px; margin-left: auto; }\n.ice-dock-btn:hover { color: #e6edf3; }\n.ice-dock-body { max-height: calc(100vh - 96px); overflow-y: auto; }\n.ice-dock.collapsed .ice-dock-body { display: none; }\n.ice-dock-slot { border-top: 1px solid #30363d; }\n.ice-dock-slot:empty { display: none; }\n\n/* De-window the hosted tools — the dock is the window now. */\n.ice-dock .strata-obs, .ice-dock .strata-prof, .ice-dock .ice-gl {\n position: static !important; width: auto !important; min-width: 0 !important;\n border: none !important; border-radius: 0 !important; box-shadow: none !important; }\n.ice-dock .strata-obs { resize: vertical !important; }\n.ice-dock .strata-obs-head { cursor: default; }\n.ice-dock .strata-prof-body, .ice-dock .ice-gl-body { min-width: 0; }\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID) !== null) return;\n const el = doc.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n doc.head.appendChild(el);\n}\n\nexport function createDock(opts: DockOptions = {}): Dock {\n const container = opts.container ?? document.body;\n const doc = container.ownerDocument;\n const win = doc.defaultView ?? window;\n injectStyle(doc);\n\n const clampPos = (x: number, y: number, w: number): { x: number; y: number } => {\n const maxX = win.innerWidth - KEEP;\n const minX = Math.min(MARGIN, MARGIN - (w - KEEP));\n const maxY = win.innerHeight - 34;\n return { x: Math.min(Math.max(x, minX), maxX), y: Math.min(Math.max(y, MARGIN), maxY) };\n };\n\n const root = doc.createElement(\"div\");\n root.className = \"ice-dock\";\n\n const head = doc.createElement(\"div\");\n head.className = \"ice-dock-head\";\n const grip = doc.createElement(\"span\");\n grip.className = \"ice-dock-grip\";\n grip.setAttribute(\"aria-hidden\", \"true\");\n grip.textContent = \"⠿\";\n const name = doc.createElement(\"span\");\n name.className = \"ice-dock-name\";\n name.textContent = opts.title ?? \"ice devtools\";\n const collapse = doc.createElement(\"button\");\n collapse.type = \"button\";\n collapse.className = \"ice-dock-btn\";\n collapse.title = \"Collapse\";\n head.append(grip, name, collapse);\n\n const body = doc.createElement(\"div\");\n body.className = \"ice-dock-body\";\n const slots = new Map<DockSlotId, HTMLElement>();\n for (const id of SLOT_ORDER) {\n const s = doc.createElement(\"div\");\n s.className = \"ice-dock-slot\";\n s.dataset.slot = id;\n body.appendChild(s);\n slots.set(id, s);\n }\n\n root.append(head, body);\n container.appendChild(root);\n\n // restore layout: width, then position (clamped), then open state\n const l = loadLayout();\n const w = typeof l.w === \"number\" ? l.w : (opts.width ?? 420);\n root.style.width = `${w}px`;\n const corner = opts.corner ?? \"tr\";\n const fx = corner === \"tl\" || corner === \"bl\" ? 16 : win.innerWidth - w - 16;\n const fy = corner === \"tl\" || corner === \"tr\" ? 16 : win.innerHeight - 220;\n const p = clampPos(l.x ?? fx, l.y ?? fy, w);\n root.style.left = `${p.x}px`;\n root.style.top = `${p.y}px`;\n let open = l.open ?? true;\n const applyOpen = (): void => {\n root.classList.toggle(\"collapsed\", !open);\n collapse.textContent = open ? \"▾\" : \"▸\";\n };\n applyOpen();\n collapse.addEventListener(\"click\", () => {\n open = !open;\n saveLayout({ open });\n applyOpen();\n });\n\n let drag: { id: number; sx: number; sy: number; ox: number; oy: number } | null = null;\n head.addEventListener(\"pointerdown\", (e) => {\n if ((e.target as HTMLElement).closest(\"button\") !== null) return; // let buttons be buttons\n const r = root.getBoundingClientRect();\n drag = { id: e.pointerId, sx: e.clientX, sy: e.clientY, ox: r.left, oy: r.top };\n if (typeof head.setPointerCapture === \"function\") {\n try {\n head.setPointerCapture(e.pointerId);\n } catch {\n /* synthetic events (tests) have no active pointer */\n }\n }\n root.classList.add(\"dragging\");\n e.preventDefault();\n });\n head.addEventListener(\"pointermove\", (e) => {\n if (drag?.id !== e.pointerId) return;\n const q = clampPos(drag.ox + (e.clientX - drag.sx), drag.oy + (e.clientY - drag.sy), root.offsetWidth);\n root.style.left = `${q.x}px`;\n root.style.top = `${q.y}px`;\n });\n const endDrag = (e: PointerEvent): void => {\n if (drag?.id !== e.pointerId) return;\n drag = null;\n root.classList.remove(\"dragging\");\n const r = root.getBoundingClientRect();\n saveLayout({ x: r.left, y: r.top });\n };\n head.addEventListener(\"pointerup\", endDrag);\n head.addEventListener(\"pointercancel\", endDrag);\n\n const onWinResize = (): void => {\n const r = root.getBoundingClientRect();\n const q = clampPos(r.left, r.top, root.offsetWidth);\n root.style.left = `${q.x}px`;\n root.style.top = `${q.y}px`;\n };\n win.addEventListener(\"resize\", onWinResize);\n const ro = new ResizeObserver(() => {\n saveLayout({ w: root.offsetWidth });\n });\n ro.observe(root);\n\n let disposed = false;\n return {\n el: root,\n slot(id) {\n // SLOT_ORDER covers the full DockSlotId union — the map is total.\n return slots.get(id) as HTMLElement;\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n win.removeEventListener(\"resize\", onWinResize);\n ro.disconnect();\n root.remove();\n },\n };\n}\n","/**\n * The GL metrics panel — the engine-specific renderer readout strata has no\n * counterpart for (2026-07-13, James: \"comprehensive r3f profiler ... and our\n * engine specific optimization metrics ... with nice visuals\").\n *\n * Pure DOM, strata's visual language (same palette/typography as the observer\n * + profiler HUD so the three read as one toolset). Fed by pushes of\n * `GlPanelStats` — the STRUCTURAL MIRROR of @ice/r3f's `GlFrameStats`\n * (devtools cannot import r3f under the walls; keep the shapes aligned):\n *\n * head fps · gpu ms + a gpu sparkline against the frame budget\n * frame cpu / gpu bars vs budget, with session maxima\n * render draw calls · triangles · points/lines · programs · geoms · textures\n * virtual live render targets + total resolution, FBO bytes vs budget,\n * textures the island phase census as a segmented bar, repaint/evict counters\n * lod zoom → hysteresis band → effective DPR, live targets per band\n * widgets Visible vs Culled population\n *\n * All values land via `textContent` (never innerHTML with data). Rendering is\n * throttled — pushes arrive per composited frame, the DOM refreshes at ~8 Hz.\n */\n\n/** Mirror of @ice/r3f `GlFrameStats` (structural — see the header). */\nexport interface GlPanelStats {\n readonly cpuMs: number;\n readonly gpuMs: number;\n readonly fps: number;\n readonly drawCalls: number;\n readonly triangles: number;\n readonly points: number;\n readonly lines: number;\n readonly programs: number;\n readonly geometries: number;\n readonly textures: number;\n readonly renderTargets: number;\n readonly renderMegaPixels: number;\n readonly fboBytes: number;\n readonly fboBudgetBytes: number;\n readonly islands: {\n readonly total: number;\n readonly hot: number;\n readonly warm: number;\n readonly waking: number;\n readonly cold: number;\n readonly dormant: number;\n };\n readonly bandHistogram: Readonly<Record<string, number>>;\n readonly repainted: number;\n readonly pendingPaints: number;\n readonly evicted: number;\n readonly zoom: number;\n readonly band: number;\n readonly effectiveDpr: number;\n readonly visibleWidgets: number;\n readonly culledWidgets: number;\n}\n\nexport type GlPanelCorner = \"tl\" | \"tr\" | \"bl\" | \"br\";\n\nexport interface GlPanelOptions {\n readonly container?: HTMLElement;\n readonly corner?: GlPanelCorner;\n /** Frame budget in ms — the sparkline's reference line (default 16.7). */\n readonly budgetMs?: number;\n /** Start with the detail body open (default false). */\n readonly expanded?: boolean;\n}\n\nexport interface GlPanel {\n push(stats: GlPanelStats): void;\n dispose(): void;\n}\n\nconst STYLE_ID = \"ice-gl-style\";\n\n// strata-prof's palette, verbatim — the panels must read as one toolset.\nconst CSS = `\n.ice-gl { position: fixed; z-index: 99998; background: rgba(13,17,23,.96);\n border: 1px solid #30363d; border-radius: 10px; color: #e6edf3;\n font: 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;\n box-shadow: 0 6px 22px rgba(0,0,0,.45); user-select: none; }\n.ice-gl * { box-sizing: border-box; }\n.ice-gl.tl { top: 10px; left: 10px; } .ice-gl.tr { top: 10px; right: 10px; }\n.ice-gl.bl { bottom: 10px; left: 10px; } .ice-gl.br { bottom: 10px; right: 10px; }\n.ice-gl-head { display: flex; align-items: center; gap: 8px; padding: 6px 9px; cursor: pointer; }\n.ice-gl-name { font-weight: 700; color: #56d4dd; }\n.ice-gl-fps { font-weight: 700; color: #a371f7; min-width: 46px; }\n.ice-gl-gpu { color: #8b949e; min-width: 74px; }\n.ice-gl-caret { color: #8b949e; margin-left: auto; }\n.ice-gl-body { display: none; border-top: 1px solid #21262d; padding: 7px 9px 8px; min-width: 300px; }\n.ice-gl.open .ice-gl-body { display: block; }\n.ice-gl-sect { color: #8b949e; font-weight: 700; padding: 5px 0 2px; }\n.ice-gl-row { display: flex; align-items: center; gap: 7px; padding: 1px 0; white-space: nowrap; }\n.ice-gl-name2 { min-width: 64px; color: #8b949e; }\n.ice-gl-bar { flex: 1; height: 5px; border-radius: 3px; background: #21262d; overflow: hidden; }\n.ice-gl-bar > i { display: block; height: 100%; background: #58a6ff; border-radius: 3px; }\n.ice-gl-stat { color: #e6edf3; margin-left: auto; }\n.ice-gl-dim { color: #8b949e; }\n.ice-gl-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px 10px; padding: 2px 0; }\n.ice-gl-cell b { display: block; color: #e6edf3; font-weight: 700; }\n.ice-gl-cell span { color: #8b949e; }\n.ice-gl-seg { display: flex; height: 7px; border-radius: 3px; overflow: hidden; background: #21262d; margin: 3px 0 2px; }\n.ice-gl-seg > i { display: block; height: 100%; }\n.ice-gl-legend { color: #8b949e; padding-bottom: 2px; }\n`;\n\nconst PHASE_COLORS: Readonly<Record<string, string>> = {\n hot: \"#ff7b72\",\n waking: \"#58a6ff\",\n warm: \"#3fb950\",\n cold: \"#8b949e\",\n dormant: \"#484f58\",\n};\n\nconst SPARK_W = 132;\nconst SPARK_H = 26;\nconst SPARK_SAMPLES = SPARK_W / 2;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID) !== null) return;\n const el = doc.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n doc.head.appendChild(el);\n}\n\nconst fmtInt = (n: number): string => n.toLocaleString();\nconst fmtK = (n: number): string => (n >= 1e6 ? `${(n / 1e6).toFixed(2)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n));\nconst fmtMs = (ms: number): string => `${ms.toFixed(2)}ms`;\nconst fmtMB = (b: number): string => `${(b / 1048576).toFixed(0)}MB`;\n\nexport function createGlPanel(opts: GlPanelOptions = {}): GlPanel {\n const container = opts.container ?? document.body;\n const doc = container.ownerDocument;\n const budgetMs = opts.budgetMs ?? 16.7;\n injectStyle(doc);\n\n const root = doc.createElement(\"div\");\n root.className = `ice-gl ${opts.corner ?? \"bl\"}${opts.expanded === true ? \" open\" : \"\"}`;\n\n const head = doc.createElement(\"div\");\n head.className = \"ice-gl-head\";\n const name = doc.createElement(\"span\");\n name.className = \"ice-gl-name\";\n name.textContent = \"ice gl\";\n const fpsEl = doc.createElement(\"span\");\n fpsEl.className = \"ice-gl-fps\";\n const gpuEl = doc.createElement(\"span\");\n gpuEl.className = \"ice-gl-gpu\";\n const spark = doc.createElement(\"canvas\");\n spark.width = SPARK_W;\n spark.height = SPARK_H;\n spark.style.display = \"block\";\n const caret = doc.createElement(\"span\");\n caret.className = \"ice-gl-caret\";\n caret.textContent = \"▾\";\n head.append(name, fpsEl, gpuEl, spark, caret);\n head.addEventListener(\"click\", () => root.classList.toggle(\"open\"));\n\n const body = doc.createElement(\"div\");\n body.className = \"ice-gl-body\";\n root.append(head, body);\n container.appendChild(root);\n\n // --- static skeleton (rebuilt values only — no per-push DOM churn) --------\n const sect = (label: string): void => {\n const s = doc.createElement(\"div\");\n s.className = \"ice-gl-sect\";\n s.textContent = label;\n body.appendChild(s);\n };\n const bar = (label: string): { fill: HTMLElement; stat: HTMLElement } => {\n const row = doc.createElement(\"div\");\n row.className = \"ice-gl-row\";\n const n = doc.createElement(\"span\");\n n.className = \"ice-gl-name2\";\n n.textContent = label;\n const b = doc.createElement(\"div\");\n b.className = \"ice-gl-bar\";\n const fill = doc.createElement(\"i\");\n b.appendChild(fill);\n const stat = doc.createElement(\"span\");\n stat.className = \"ice-gl-stat\";\n row.append(n, b, stat);\n body.appendChild(row);\n return { fill, stat };\n };\n const textRow = (): HTMLElement => {\n const row = doc.createElement(\"div\");\n row.className = \"ice-gl-row\";\n body.appendChild(row);\n return row;\n };\n\n sect(\"frame\");\n const cpuBar = bar(\"cpu\");\n const gpuBar = bar(\"gpu\");\n\n sect(\"render\");\n const grid = doc.createElement(\"div\");\n grid.className = \"ice-gl-grid\";\n body.appendChild(grid);\n const cell = (label: string): HTMLElement => {\n const c = doc.createElement(\"div\");\n c.className = \"ice-gl-cell\";\n const v = doc.createElement(\"b\");\n const l = doc.createElement(\"span\");\n l.textContent = label;\n c.append(v, l);\n grid.appendChild(c);\n return v;\n };\n const callsEl = cell(\"calls\");\n const trisEl = cell(\"triangles\");\n const ptsEl = cell(\"pts+lines\");\n const progsEl = cell(\"programs\");\n const geosEl = cell(\"geometries\");\n const texsEl = cell(\"textures\");\n\n sect(\"virtual textures\");\n const rtRow = textRow();\n const fboBar = bar(\"fbo mem\");\n const seg = doc.createElement(\"div\");\n seg.className = \"ice-gl-seg\";\n body.appendChild(seg);\n const legend = doc.createElement(\"div\");\n legend.className = \"ice-gl-legend\";\n body.appendChild(legend);\n const paintRow = textRow();\n\n sect(\"lod\");\n const lodRow = textRow();\n const bandsRow = textRow();\n\n sect(\"widgets\");\n const cullBar = bar(\"visible\");\n\n // --- push/render (throttled) ----------------------------------------------\n const samples: number[] = [];\n let latest: GlPanelStats | undefined;\n let maxCpu = 0;\n let maxGpu = 0;\n let evictedTotal = 0;\n let lastRender = 0;\n let disposed = false;\n\n const drawSpark = (): void => {\n const ctx = spark.getContext(\"2d\");\n if (ctx === null) return;\n ctx.clearRect(0, 0, SPARK_W, SPARK_H);\n const top = budgetMs * 2; // budget sits mid-height\n for (let i = 0; i < samples.length; i++) {\n const v = samples[i] ?? 0;\n const h = Math.min(SPARK_H, (v / top) * SPARK_H);\n ctx.fillStyle = v > budgetMs ? \"#ff7b72\" : \"#3fb950\";\n ctx.fillRect(i * 2, SPARK_H - h, 1.6, h);\n }\n ctx.fillStyle = \"rgba(139,148,158,.55)\";\n ctx.fillRect(0, SPARK_H / 2, SPARK_W, 1);\n };\n\n const render = (): void => {\n const s = latest;\n if (s === undefined) return;\n fpsEl.textContent = s.fps > 0 ? `${Math.round(s.fps)} fps` : \"– fps\";\n gpuEl.textContent = s.gpuMs > 0 ? `gpu ${fmtMs(s.gpuMs)}` : \"gpu –\";\n drawSpark();\n if (!root.classList.contains(\"open\")) return;\n\n cpuBar.fill.style.width = `${Math.min(100, (s.cpuMs / budgetMs) * 100)}%`;\n cpuBar.stat.textContent = `${fmtMs(s.cpuMs)} · max ${fmtMs(maxCpu)}`;\n gpuBar.fill.style.width = `${Math.min(100, (s.gpuMs / budgetMs) * 100)}%`;\n gpuBar.fill.style.background = s.gpuMs > budgetMs ? \"#ff7b72\" : \"#58a6ff\";\n gpuBar.stat.textContent = `${fmtMs(s.gpuMs)} · max ${fmtMs(maxGpu)}`;\n\n callsEl.textContent = fmtInt(s.drawCalls);\n trisEl.textContent = fmtK(s.triangles);\n ptsEl.textContent = fmtK(s.points + s.lines);\n progsEl.textContent = fmtInt(s.programs);\n geosEl.textContent = fmtInt(s.geometries);\n texsEl.textContent = fmtInt(s.textures);\n\n rtRow.textContent = `${s.renderTargets} targets · ${s.renderMegaPixels.toFixed(1)} MP total`;\n fboBar.fill.style.width = `${Math.min(100, (s.fboBytes / s.fboBudgetBytes) * 100)}%`;\n fboBar.fill.style.background = s.fboBytes > s.fboBudgetBytes * 0.85 ? \"#ffa657\" : \"#58a6ff\";\n fboBar.stat.textContent = `${fmtMB(s.fboBytes)} / ${fmtMB(s.fboBudgetBytes)}`;\n\n seg.textContent = \"\";\n const phases: Array<[string, number]> = [\n [\"hot\", s.islands.hot],\n [\"waking\", s.islands.waking],\n [\"warm\", s.islands.warm],\n [\"cold\", s.islands.cold],\n [\"dormant\", s.islands.dormant],\n ];\n const denom = Math.max(1, s.islands.total);\n const legendParts: string[] = [];\n for (const [phase, count] of phases) {\n if (count === 0) continue;\n const i = doc.createElement(\"i\");\n i.style.width = `${(count / denom) * 100}%`;\n i.style.background = PHASE_COLORS[phase] ?? \"#8b949e\";\n seg.appendChild(i);\n legendParts.push(`${count} ${phase}`);\n }\n legend.textContent = legendParts.length > 0 ? legendParts.join(\" · \") : \"no islands\";\n paintRow.textContent = `repainted ${s.repainted}/frame · pending ${s.pendingPaints} · evicted Σ${evictedTotal}`;\n\n lodRow.textContent = `zoom ${(s.zoom * 100).toFixed(0)}% → band ×${s.band} → paint dpr ${s.effectiveDpr.toFixed(2)}`;\n const bands = Object.entries(s.bandHistogram).sort((a, b) => a[0].localeCompare(b[0]));\n bandsRow.textContent =\n bands.length > 0 ? `targets by band: ${bands.map(([k, v]) => `${k}: ${v}`).join(\" · \")}` : \"no live targets\";\n\n const pop = Math.max(1, s.visibleWidgets + s.culledWidgets);\n cullBar.fill.style.width = `${(s.visibleWidgets / pop) * 100}%`;\n cullBar.fill.style.background = \"#3fb950\";\n cullBar.stat.textContent = `${s.visibleWidgets} visible · ${s.culledWidgets} culled`;\n };\n\n return {\n push(s) {\n if (disposed) return;\n latest = s;\n maxCpu = Math.max(maxCpu, s.cpuMs);\n maxGpu = Math.max(maxGpu, s.gpuMs);\n evictedTotal += s.evicted;\n samples.push(s.gpuMs > 0 ? s.gpuMs : s.cpuMs);\n if (samples.length > SPARK_SAMPLES) samples.splice(0, samples.length - SPARK_SAMPLES);\n const now = performance.now();\n if (now - lastRender >= 120) {\n lastRender = now;\n render();\n }\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n root.remove();\n },\n };\n}\n","/**\n * @ice/devtools — strata's observer panel + FPS profiler, engine-flavored\n * (describe/durable/ephemeral/reflect-lane glue). Reads the world outside the\n * tick; never a reflector, never writes ECS. Nobody imports us\n * (depcruise-enforced leaf).\n */\nexport const DEVTOOLS_VERSION = \"0.1.0\";\n\nexport {\n attachDevtools,\n engineDescribe,\n type DevtoolsEngine,\n type DevtoolsHandle,\n type DevtoolsOpts,\n} from \"./attach\";\nexport { createDock, type Dock, type DockCorner, type DockOptions, type DockSlotId } from \"./dock\";\nexport { createGlPanel, type GlPanel, type GlPanelCorner, type GlPanelOptions, type GlPanelStats } from \"./gl-panel\";\n"],"mappings":"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","names":["w","STYLE_ID","CSS","injectStyle"]}
package/dist/dom.js CHANGED
@@ -19,8 +19,8 @@ import {
19
19
  startRafLoop,
20
20
  wheelCede,
21
21
  wireMeasurement
22
- } from "./chunk-3PHPWRB2.js";
23
- import "./chunk-7LZAXQAO.js";
22
+ } from "./chunk-55QCOE74.js";
23
+ import "./chunk-LATXYFMO.js";
24
24
  import "./chunk-OR5FNVUE.js";
25
25
  export {
26
26
  CLAIM_OWNS_ESCAPE,
package/dist/ground.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  defineQuery,
24
24
  portSlots,
25
25
  widgets
26
- } from "./chunk-7LZAXQAO.js";
26
+ } from "./chunk-LATXYFMO.js";
27
27
  import {
28
28
  portAnchor,
29
29
  tessellateCubic,
package/dist/index.js CHANGED
@@ -4,6 +4,9 @@ import {
4
4
  ActiveTool,
5
5
  All,
6
6
  Any,
7
+ BEHAVIOR_DEFAULT_PHASE,
8
+ BEHAVIOR_MARKER_PREFIX,
9
+ BEHAVIOR_PHASES,
7
10
  BoardRoot,
8
11
  CAMERA_DEFAULTS,
9
12
  CHROME_DEFAULTS,
@@ -41,6 +44,7 @@ import {
41
44
  FrameMode,
42
45
  GESTURE_DEFAULTS,
43
46
  GRAPH_PICK_DEFAULTS,
47
+ GUEST_BUDGET,
44
48
  GestureActive,
45
49
  GestureCancelled,
46
50
  GestureEnded,
@@ -162,15 +166,21 @@ import {
162
166
  arrangeWidgets,
163
167
  attachBroadcastRelay,
164
168
  attachPresence,
169
+ attachSpawnBehaviors,
165
170
  attachSpawnParent,
171
+ behaviorMarkerKey,
172
+ behaviors,
166
173
  broadcastChannelByteChannel,
167
174
  buildOrdinals,
168
175
  bumpVersion,
169
176
  cancelActiveGestures,
170
177
  cascadeDestroy,
178
+ classifyBehaviorVersion,
171
179
  clearSelection,
172
180
  compareStackOrder,
181
+ compileBehavior,
173
182
  createActiveMembership,
183
+ createBehaviorHarness,
174
184
  createBreakpointSystem,
175
185
  createCameraSystems,
176
186
  createCanvasEngine,
@@ -182,6 +192,7 @@ import {
182
192
  createDrawTool,
183
193
  createEngine,
184
194
  createFrameControl,
195
+ createGuestRegistry,
185
196
  createInputQueue,
186
197
  createLiveWriter,
187
198
  createMeasureIngest,
@@ -204,6 +215,7 @@ import {
204
215
  currentNavEntry,
205
216
  currentNavFrame,
206
217
  decodeEnvelope,
218
+ defineBehavior,
207
219
  defineComponent,
208
220
  definePhaseSet,
209
221
  definePrefab,
@@ -216,8 +228,10 @@ import {
216
228
  defineTickSystem,
217
229
  defineTool,
218
230
  defineWidget,
231
+ describeBehavior,
219
232
  devGuardsEnabled,
220
233
  distPointToBox,
234
+ docBehaviorVersion,
221
235
  encodeEnvelope,
222
236
  ensureCanvasSurface,
223
237
  enumOf,
@@ -232,8 +246,10 @@ import {
232
246
  installPresence,
233
247
  installWidgetRuntime,
234
248
  instantiate,
249
+ isDurableEligible,
235
250
  isMidGesture,
236
251
  joinDoc,
252
+ makeChurnGuard,
237
253
  makeDefaultMayDiverge,
238
254
  makeVersionGuard,
239
255
  navFlightActive,
@@ -255,6 +271,7 @@ import {
255
271
  setSelection,
256
272
  setWidgetProps,
257
273
  spawnWidget,
274
+ stampBehaviorMarker,
258
275
  stampEngineMeta,
259
276
  startAutosave,
260
277
  startNavFlight,
@@ -263,7 +280,7 @@ import {
263
280
  widgetSpawnInits,
264
281
  widgets,
265
282
  writeRuntimeResource
266
- } from "./chunk-7LZAXQAO.js";
283
+ } from "./chunk-LATXYFMO.js";
267
284
  import "./chunk-OR5FNVUE.js";
268
285
  export {
269
286
  Accepts,
@@ -271,6 +288,9 @@ export {
271
288
  ActiveTool,
272
289
  All,
273
290
  Any,
291
+ BEHAVIOR_DEFAULT_PHASE,
292
+ BEHAVIOR_MARKER_PREFIX,
293
+ BEHAVIOR_PHASES,
274
294
  BoardRoot,
275
295
  CAMERA_DEFAULTS,
276
296
  CHROME_DEFAULTS,
@@ -308,6 +328,7 @@ export {
308
328
  FrameMode,
309
329
  GESTURE_DEFAULTS,
310
330
  GRAPH_PICK_DEFAULTS,
331
+ GUEST_BUDGET,
311
332
  GestureActive,
312
333
  GestureCancelled,
313
334
  GestureEnded,
@@ -429,15 +450,21 @@ export {
429
450
  arrangeWidgets,
430
451
  attachBroadcastRelay,
431
452
  attachPresence,
453
+ attachSpawnBehaviors,
432
454
  attachSpawnParent,
455
+ behaviorMarkerKey,
456
+ behaviors,
433
457
  broadcastChannelByteChannel,
434
458
  buildOrdinals,
435
459
  bumpVersion,
436
460
  cancelActiveGestures,
437
461
  cascadeDestroy,
462
+ classifyBehaviorVersion,
438
463
  clearSelection,
439
464
  compareStackOrder,
465
+ compileBehavior,
440
466
  createActiveMembership,
467
+ createBehaviorHarness,
441
468
  createBreakpointSystem,
442
469
  createCameraSystems,
443
470
  createCanvasEngine,
@@ -449,6 +476,7 @@ export {
449
476
  createDrawTool,
450
477
  createEngine,
451
478
  createFrameControl,
479
+ createGuestRegistry,
452
480
  createInputQueue,
453
481
  createLiveWriter,
454
482
  createMeasureIngest,
@@ -471,6 +499,7 @@ export {
471
499
  currentNavEntry,
472
500
  currentNavFrame,
473
501
  decodeEnvelope,
502
+ defineBehavior,
474
503
  defineComponent,
475
504
  definePhaseSet,
476
505
  definePrefab,
@@ -483,8 +512,10 @@ export {
483
512
  defineTickSystem,
484
513
  defineTool,
485
514
  defineWidget,
515
+ describeBehavior,
486
516
  devGuardsEnabled,
487
517
  distPointToBox,
518
+ docBehaviorVersion,
488
519
  encodeEnvelope,
489
520
  ensureCanvasSurface,
490
521
  enumOf,
@@ -499,8 +530,10 @@ export {
499
530
  installPresence,
500
531
  installWidgetRuntime,
501
532
  instantiate,
533
+ isDurableEligible,
502
534
  isMidGesture,
503
535
  joinDoc,
536
+ makeChurnGuard,
504
537
  makeDefaultMayDiverge,
505
538
  makeVersionGuard,
506
539
  navFlightActive,
@@ -522,6 +555,7 @@ export {
522
555
  setSelection,
523
556
  setWidgetProps,
524
557
  spawnWidget,
558
+ stampBehaviorMarker,
525
559
  stampEngineMeta,
526
560
  startAutosave,
527
561
  startNavFlight,
package/dist/r3f.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  EngineProvider,
3
3
  hasPreviewSnapshot,
4
4
  setPreviewSnapshot
5
- } from "./chunk-ENMRSITH.js";
6
- import "./chunk-3PHPWRB2.js";
5
+ } from "./chunk-5OU3OWDH.js";
6
+ import "./chunk-55QCOE74.js";
7
7
  import {
8
8
  Active,
9
9
  Camera,
@@ -26,7 +26,7 @@ import {
26
26
  navFlightActive,
27
27
  pickTopAt,
28
28
  widgets
29
- } from "./chunk-7LZAXQAO.js";
29
+ } from "./chunk-LATXYFMO.js";
30
30
  import {
31
31
  compositeCameraFrustum,
32
32
  computeIslandPhase,
package/dist/react.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  hasPreviewSnapshot,
11
11
  setPreviewSnapshot,
12
12
  subscribePreviewSnapshots,
13
+ useBehavior,
13
14
  useBreakpoint,
14
15
  useCanvasEngine,
15
16
  useCommit,
@@ -24,9 +25,9 @@ import {
24
25
  useWidgetProps,
25
26
  useWorld,
26
27
  useWorldComponent
27
- } from "./chunk-ENMRSITH.js";
28
- import "./chunk-3PHPWRB2.js";
29
- import "./chunk-7LZAXQAO.js";
28
+ } from "./chunk-5OU3OWDH.js";
29
+ import "./chunk-55QCOE74.js";
30
+ import "./chunk-LATXYFMO.js";
30
31
  import "./chunk-OR5FNVUE.js";
31
32
  export {
32
33
  EngineProvider,
@@ -40,6 +41,7 @@ export {
40
41
  hasPreviewSnapshot,
41
42
  setPreviewSnapshot,
42
43
  subscribePreviewSnapshots,
44
+ useBehavior,
43
45
  useBreakpoint,
44
46
  useCanvasEngine,
45
47
  useCommit,
@@ -0,0 +1,74 @@
1
+ import type { Batch, CollectOptions, Component, Relation, Resource, System, SystemAccess, SystemCtx, Tag, TickSystem } from "@vibecook/strata-ecs";
2
+ import type { AnySystem } from "../engine/pipeline";
3
+ import type { AnyBehaviorDef, BehaviorPhase } from "./types";
4
+ /**
5
+ * The bodies the runtime supplies. Compilation owns SHAPE (query, access,
6
+ * runIf, name, registration identity); the runtime owns behavior. Keeping the
7
+ * seam here means the compiler's rules — the split, the partition, the gating —
8
+ * are unit-testable without standing up a delivery loop.
9
+ */
10
+ export interface BehaviorSystemHooks {
11
+ /**
12
+ * The delivery `runIf`: drains the collector, stashes the delta for the body,
13
+ * evaluates the polls, and decides. Side-effecting BY DESIGN — there is no
14
+ * non-destructive peek on a `ChangeCollector`, so the drain has to happen
15
+ * in the gate or the gate cannot see the work (the `makeChurnGuard` idiom).
16
+ */
17
+ shouldDeliver(ctx: SystemCtx): boolean;
18
+ deliver(ctx: SystemCtx): void;
19
+ /** The tick `runIf` — cheap: instance count, suspension, freeze. */
20
+ shouldTick(ctx: SystemCtx): boolean;
21
+ tickChunk(batch: Batch, ctx: SystemCtx): void;
22
+ /**
23
+ * Run `body` inside this behavior's breaker bracket + fault domain. Returns
24
+ * false when the breaker is holding the behavior down or the body threw.
25
+ */
26
+ charge(body: () => void): boolean;
27
+ /** Breaker verdict — polled from `runIf`, so it must be cheap and never throw. */
28
+ suspended(): boolean;
29
+ /**
30
+ * A frame the behavior sat out because the breaker suspended it. The runtime
31
+ * DISCARDS its collectors here: a suspended behavior that stops draining
32
+ * still journals, so its collectors would grow for as long as the suspension
33
+ * lasts and then hand the resumed behavior a delta describing a world many
34
+ * frames old.
35
+ */
36
+ onSuspendedFrame(): void;
37
+ }
38
+ export interface CompiledBehavior {
39
+ readonly behavior: AnyBehaviorDef;
40
+ readonly phase: BehaviorPhase;
41
+ /** The framework collector's subscription (always `coarse: false`). */
42
+ readonly collect: CollectOptions;
43
+ /** `reads:` partitioned by kind — each routed to a different mechanism. */
44
+ readonly readComponents: readonly Component[];
45
+ readonly readTags: readonly Tag[];
46
+ readonly readRelations: readonly Relation[];
47
+ readonly readResources: readonly Resource[];
48
+ readonly deliveryAccess: SystemAccess;
49
+ readonly tickAccess: SystemAccess | undefined;
50
+ readonly delivery: TickSystem;
51
+ /** Present only when the declaration has an `on.tick` hook. */
52
+ readonly tick: System | undefined;
53
+ /** Registration order: delivery first, then tick. */
54
+ readonly systems: readonly AnySystem[];
55
+ }
56
+ /**
57
+ * Is `c` a cell the document can hold? Two sources: a durable prefab's
58
+ * eligible set, and (BF-D6) any durable behavior's own data component —
59
+ * registered behavior components are UNIVERSALLY durable-eligible.
60
+ *
61
+ * This is deliberately compile-time and not definition-time. At module-eval
62
+ * time the prefab registry may not yet hold the widget whose Position a
63
+ * behavior writes, so a definition-time answer would depend on import order —
64
+ * and an import-order-dependent refusal is worse than no refusal, because it
65
+ * passes on the developer's machine and fails in the bundle.
66
+ */
67
+ export declare function isDurableEligible(c: Component): boolean;
68
+ export declare function partitionReads(b: AnyBehaviorDef): {
69
+ components: Component[];
70
+ tags: Tag[];
71
+ relations: Relation[];
72
+ resources: Resource[];
73
+ };
74
+ export declare function compileBehavior(b: AnyBehaviorDef, hooks: BehaviorSystemHooks): CompiledBehavior;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * `defineBehavior` — definition, validation, and the process-global registry
3
+ * (design-009 §4.1).
4
+ *
5
+ * Compile output is one generated component per behavior (`behavior:<name>`),
6
+ * which is also the CONFLICT UNIT: one behavior = one component = one
7
+ * committer-wins cell. The guidance line for authors, worth repeating here
8
+ * because it is the rule that keeps collaboration sane: *would two peers
9
+ * plausibly edit these fields concurrently? Then they are two behaviors.*
10
+ *
11
+ * Everything in this file is definition-time. The compiler (compile.ts) owns
12
+ * the checks that need a populated world — above all the durable-eligibility
13
+ * refusal, which cannot run here: at module-eval time the prefab registry may
14
+ * not yet hold the widget whose Position the behavior writes, and a check that
15
+ * depends on import order is a check that reports differently on a good day.
16
+ */
17
+ import type { Component } from "@vibecook/strata-ecs";
18
+ import { type AnyBehaviorDef, type BehaviorDescription, type BehaviorHandle, type BehaviorRead, type BehaviorSchema, type BehaviorSpec, type BehaviorStore, type DataOf } from "./types";
19
+ /**
20
+ * The generated component's name. Prefixed so a behavior can never collide
21
+ * with `defineWidget`'s `<type>:<group>` components — and, because this string
22
+ * is the DURABLE key that projection is driven by, it is a permanent wire
23
+ * format decision, not an implementation detail. A collision anywhere is loud
24
+ * (`ensureComponent` throws on same-name/different-shape), never silent.
25
+ */
26
+ export declare function behaviorComponentName(name: string): string;
27
+ export declare const behaviors: {
28
+ get(name: string): AnyBehaviorDef | undefined;
29
+ all(): AnyBehaviorDef[];
30
+ /** The behavior that owns a generated data component, if any. */
31
+ forComponent(c: Component): AnyBehaviorDef | undefined;
32
+ /** Is `c` a behavior's own data component? (the BF-D6 eligibility branch). */
33
+ isBehaviorComponent(c: Component): boolean;
34
+ };
35
+ export type ReadKind = "component" | "tag" | "relation" | "resource" | "behavior";
36
+ /**
37
+ * Classify a `reads:` entry. Handles are opaque and structurally ambiguous
38
+ * (Component and Resource have IDENTICAL shapes), so classification goes
39
+ * through ICE's own schema registries — which doubles as the §4.1 rule that
40
+ * reads must name REGISTERED schema: anything defined outside the `@ice/core`
41
+ * wrappers is unclassifiable here and is rejected rather than guessed at.
42
+ */
43
+ export declare function classifyRead(r: BehaviorRead): {
44
+ kind: ReadKind;
45
+ name: string;
46
+ } | undefined;
47
+ /** The read component a `reads:` entry contributes, if it contributes one. */
48
+ export declare function readComponentOf(r: BehaviorRead): Component | undefined;
49
+ export declare function describeBehavior(behavior: AnyBehaviorDef): BehaviorDescription;
50
+ export declare function defineBehavior<const S extends BehaviorStore, const Sch extends BehaviorSchema = BehaviorSchema>(name: string, spec: BehaviorSpec<S, Sch>): BehaviorHandle<DataOf<Sch>>;
51
+ /**
52
+ * TEST-ONLY registry wipe (mirrors `__resetPrefabsForTests`; deliberately NOT
53
+ * on the barrel). strata's schema registry has no reset, so a test that wipes
54
+ * this must still use file-unique behavior names.
55
+ */
56
+ export declare function __resetBehaviorsForTests(): void;