@triscope/core 0.4.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 (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/dist/compose.d.ts +11 -0
  4. package/dist/compose.d.ts.map +1 -0
  5. package/dist/compose.js +152 -0
  6. package/dist/compose.js.map +1 -0
  7. package/dist/editor.d.ts +14 -0
  8. package/dist/editor.d.ts.map +1 -0
  9. package/dist/editor.js +131 -0
  10. package/dist/editor.js.map +1 -0
  11. package/dist/harness.d.ts +199 -0
  12. package/dist/harness.d.ts.map +1 -0
  13. package/dist/harness.js +1027 -0
  14. package/dist/harness.js.map +1 -0
  15. package/dist/index.d.ts +32 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +20 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/inspect.d.ts +94 -0
  20. package/dist/inspect.d.ts.map +1 -0
  21. package/dist/inspect.js +434 -0
  22. package/dist/inspect.js.map +1 -0
  23. package/dist/knob-utils.d.ts +22 -0
  24. package/dist/knob-utils.d.ts.map +1 -0
  25. package/dist/knob-utils.js +51 -0
  26. package/dist/knob-utils.js.map +1 -0
  27. package/dist/lab/css.d.ts +11 -0
  28. package/dist/lab/css.d.ts.map +1 -0
  29. package/dist/lab/css.js +57 -0
  30. package/dist/lab/css.js.map +1 -0
  31. package/dist/lab/dom.d.ts +13 -0
  32. package/dist/lab/dom.d.ts.map +1 -0
  33. package/dist/lab/dom.js +76 -0
  34. package/dist/lab/dom.js.map +1 -0
  35. package/dist/motion-probe.d.ts +47 -0
  36. package/dist/motion-probe.d.ts.map +1 -0
  37. package/dist/motion-probe.js +122 -0
  38. package/dist/motion-probe.js.map +1 -0
  39. package/dist/probe-utils.d.ts +14 -0
  40. package/dist/probe-utils.d.ts.map +1 -0
  41. package/dist/probe-utils.js +18 -0
  42. package/dist/probe-utils.js.map +1 -0
  43. package/dist/scene-cameras.d.ts +6 -0
  44. package/dist/scene-cameras.d.ts.map +1 -0
  45. package/dist/scene-cameras.js +20 -0
  46. package/dist/scene-cameras.js.map +1 -0
  47. package/dist/scene-delta.d.ts +21 -0
  48. package/dist/scene-delta.d.ts.map +1 -0
  49. package/dist/scene-delta.js +57 -0
  50. package/dist/scene-delta.js.map +1 -0
  51. package/dist/scene-introspect.d.ts +78 -0
  52. package/dist/scene-introspect.d.ts.map +1 -0
  53. package/dist/scene-introspect.js +164 -0
  54. package/dist/scene-introspect.js.map +1 -0
  55. package/dist/scene-registry.d.ts +36 -0
  56. package/dist/scene-registry.d.ts.map +1 -0
  57. package/dist/scene-registry.js +64 -0
  58. package/dist/scene-registry.js.map +1 -0
  59. package/dist/scene-view.d.ts +52 -0
  60. package/dist/scene-view.d.ts.map +1 -0
  61. package/dist/scene-view.js +171 -0
  62. package/dist/scene-view.js.map +1 -0
  63. package/dist/source-tag.d.ts +34 -0
  64. package/dist/source-tag.d.ts.map +1 -0
  65. package/dist/source-tag.js +120 -0
  66. package/dist/source-tag.js.map +1 -0
  67. package/dist/telemetry.d.ts +53 -0
  68. package/dist/telemetry.d.ts.map +1 -0
  69. package/dist/telemetry.js +302 -0
  70. package/dist/telemetry.js.map +1 -0
  71. package/dist/types.d.ts +142 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +9 -0
  74. package/dist/types.js.map +1 -0
  75. package/dist/uniform-access.d.ts +32 -0
  76. package/dist/uniform-access.d.ts.map +1 -0
  77. package/dist/uniform-access.js +144 -0
  78. package/dist/uniform-access.js.map +1 -0
  79. package/dist/validate.d.ts +2 -0
  80. package/dist/validate.d.ts.map +1 -0
  81. package/dist/validate.js +81 -0
  82. package/dist/validate.js.map +1 -0
  83. package/dist/vite.d.ts +3 -0
  84. package/dist/vite.d.ts.map +1 -0
  85. package/dist/vite.js +4 -0
  86. package/dist/vite.js.map +1 -0
  87. package/dist/warnings.d.ts +24 -0
  88. package/dist/warnings.d.ts.map +1 -0
  89. package/dist/warnings.js +26 -0
  90. package/dist/warnings.js.map +1 -0
  91. package/package.json +60 -0
  92. package/src/compose.ts +164 -0
  93. package/src/editor.ts +138 -0
  94. package/src/harness.ts +1263 -0
  95. package/src/index.ts +58 -0
  96. package/src/inspect.ts +498 -0
  97. package/src/knob-utils.ts +60 -0
  98. package/src/lab/css.ts +56 -0
  99. package/src/lab/dom.ts +88 -0
  100. package/src/motion-probe.ts +135 -0
  101. package/src/probe-utils.ts +17 -0
  102. package/src/scene-cameras.ts +33 -0
  103. package/src/scene-delta.ts +69 -0
  104. package/src/scene-introspect.ts +230 -0
  105. package/src/scene-registry.ts +103 -0
  106. package/src/scene-view.ts +204 -0
  107. package/src/source-tag.ts +139 -0
  108. package/src/telemetry.ts +337 -0
  109. package/src/three-webgpu-shim.d.ts +130 -0
  110. package/src/types.ts +121 -0
  111. package/src/uniform-access.ts +152 -0
  112. package/src/validate.ts +82 -0
  113. package/src/vite.ts +5 -0
  114. package/src/warnings.ts +41 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 triscope contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @triscope/core
2
+
3
+ The triscope lab runtime. Wraps a Three.js + WebGPU scene in a small **Element**
4
+ contract and boots a deterministic, machine-inspectable "lab" page that the
5
+ [`@triscope/mcp`](https://www.npmjs.com/package/@triscope/mcp) tools (and a human)
6
+ can observe and tune live.
7
+
8
+ ```ts
9
+ import { runLab, runSceneLab, runSceneView } from '@triscope/core';
10
+
11
+ // Single element:
12
+ runLab(myElement);
13
+
14
+ // A scene of N namespaced elements (runtime add/remove):
15
+ runSceneLab({ elements: [ship, sea, sky] });
16
+
17
+ // Zero-boilerplate: wrap any existing THREE.Object3D (auto cameras + autoKnobs):
18
+ runSceneView(myGroup, { autoKnobs: true });
19
+ ```
20
+
21
+ ## What's in here
22
+
23
+ - **`runLab` / `runSceneLab` / `runSceneView`** — the lab entry points.
24
+ - **`Element`** — `{ name, cameras | 'auto', mount() → { root, dispose }, knobs?, telemetry?, motionProbes?, … }`.
25
+ - **`autoCameras(bounds)`** — four fitted camera presets from an AABB.
26
+ - The Vite telemetry plugin: `import { triscopeTelemetryPlugin } from '@triscope/core/vite'`
27
+ (serves `/__state`, `/__knob`, `/__manifest`, `/__scene`).
28
+
29
+ A single-element `runLab` page uses **bare** camera/knob names; a `runSceneLab`
30
+ scene namespaces them `<element>.<key>`.
31
+
32
+ ## Scaffold a project
33
+
34
+ ```sh
35
+ npm init triscope my-app # greenfield
36
+ npx triscope adopt # retrofit an existing Vite + Three project
37
+ ```
38
+
39
+ Part of the [triscope](https://github.com/tedin7/triscope) monorepo. MIT.
@@ -0,0 +1,11 @@
1
+ import type { Element } from './types.js';
2
+ export interface ComposeOptions {
3
+ /** Name of the composite element. Used for the state file + manifest. Default: 'composite'. */
4
+ name?: string;
5
+ /** Override the auto-computed bounds (union of children's). */
6
+ bounds?: Element['bounds'];
7
+ /** Override the lab URL (otherwise inherits the first child's). */
8
+ labUrl?: string;
9
+ }
10
+ export declare function composeElements(elements: Element[], opts?: ComposeOptions): Element;
11
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAc,OAAO,EAA4C,MAAM,YAAY,CAAC;AAEhG,MAAM,WAAW,cAAc;IAC7B,+FAA+F;IAC/F,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3B,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAwGvF"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * composeElements — fold multiple Elements into one composite Element.
3
+ *
4
+ * The Element contract is already enough for composition (the README has
5
+ * said "composition is just an element that mounts other elements" from
6
+ * day one) but writing the boilerplate by hand is tedious. This helper
7
+ * does the bookkeeping: it merges cameras, knobs, motionProbes, events,
8
+ * and telemetry under a `<elementName>.<key>` namespace so two elements
9
+ * declaring a camera both named "top" don't collide.
10
+ *
11
+ * Pass the result to runLab(). Inspect mode keeps working — each
12
+ * underlying element's meshes still get their own auto source-tags
13
+ * (the patch is global, not per-element).
14
+ *
15
+ * Knob routing: when the harness calls onKnob('ship.windPressure', x),
16
+ * we split on the first dot, find the matching element, and forward to
17
+ * its onKnob with the un-prefixed key ('windPressure').
18
+ */
19
+ import { autoCameras } from './scene-cameras.js';
20
+ export function composeElements(elements, opts = {}) {
21
+ if (!Array.isArray(elements) || elements.length === 0) {
22
+ throw new Error('composeElements: pass a non-empty array of Element');
23
+ }
24
+ const name = opts.name ?? 'composite';
25
+ const labUrl = opts.labUrl ?? elements[0]?.labUrl;
26
+ const bounds = opts.bounds ?? unionBounds(elements);
27
+ // Pre-build the merged camera/knob/motionProbe maps so the Element shape
28
+ // is correct before mount runs. onKnob/telemetry/motionProbes/events
29
+ // close over `elements` (static) and reach into handle.userData.handles
30
+ // (the live MountHandle list) for per-element lookups.
31
+ const cameras = {};
32
+ const knobs = {};
33
+ const motionProbes = {};
34
+ for (let i = 0; i < elements.length; i++) {
35
+ const el = elements[i];
36
+ const camSpecs = el.cameras === 'auto' ? autoCameras(el.bounds) : (el.cameras ?? {});
37
+ for (const [cn, c] of Object.entries(camSpecs)) {
38
+ cameras[nsKey(el.name, cn)] = c;
39
+ }
40
+ for (const [kn, k] of Object.entries(el.knobs ?? {})) {
41
+ knobs[nsKey(el.name, kn)] = k;
42
+ }
43
+ for (const [pn, p] of Object.entries(el.motionProbes ?? {})) {
44
+ const idx = i; // capture
45
+ motionProbes[nsKey(el.name, pn)] = (handle, ctx) => {
46
+ const childHandle = handle.userData?.handles?.[idx];
47
+ return childHandle ? p(childHandle, ctx) : 0;
48
+ };
49
+ }
50
+ }
51
+ return {
52
+ name,
53
+ labUrl,
54
+ bounds,
55
+ cameras,
56
+ knobs,
57
+ mount: ({ parent, ctx }) => {
58
+ const handles = elements.map((el) => el.mount({ parent, ctx }));
59
+ // name → child handle, so the SDL can show/hide a child at runtime
60
+ // (applyElementToggle) without dispose/remount.
61
+ const childrenByName = {};
62
+ elements.forEach((el, i) => {
63
+ childrenByName[el.name] = handles[i];
64
+ });
65
+ return {
66
+ root: parent,
67
+ userData: { handles, childrenByName },
68
+ dispose: () => {
69
+ for (const h of handles) {
70
+ try {
71
+ h.dispose();
72
+ }
73
+ catch {
74
+ /* keep tearing down the rest */
75
+ }
76
+ }
77
+ },
78
+ };
79
+ },
80
+ onKnob: (handle, key, value) => {
81
+ const [elName, knobKey] = splitNs(key);
82
+ if (!elName || !knobKey)
83
+ return;
84
+ const idx = elements.findIndex((e) => e.name === elName);
85
+ if (idx < 0)
86
+ return;
87
+ const childHandle = handle.userData?.handles?.[idx];
88
+ if (!childHandle)
89
+ return;
90
+ elements[idx].onKnob?.(childHandle, knobKey, value);
91
+ },
92
+ telemetry: (handle, ctx) => {
93
+ const out = {};
94
+ const childHandles = handle.userData?.handles ?? [];
95
+ for (let i = 0; i < elements.length; i++) {
96
+ const el = elements[i];
97
+ const h = childHandles[i];
98
+ out[el.name] = h && el.telemetry ? el.telemetry(h, ctx) : {};
99
+ }
100
+ return out;
101
+ },
102
+ motionProbes,
103
+ events: (handle, ctx) => {
104
+ const out = [];
105
+ const childHandles = handle.userData?.handles ?? [];
106
+ for (let i = 0; i < elements.length; i++) {
107
+ const el = elements[i];
108
+ const h = childHandles[i];
109
+ if (!el.events || !h)
110
+ continue;
111
+ try {
112
+ for (const ev of el.events(h, ctx) ?? []) {
113
+ // Namespace event types so a `collision` from ship and water
114
+ // are distinguishable downstream.
115
+ out.push({ ...ev, type: nsKey(el.name, ev.type) });
116
+ }
117
+ }
118
+ catch {
119
+ /* ignore element-level failures */
120
+ }
121
+ }
122
+ return out;
123
+ },
124
+ };
125
+ }
126
+ function unionBounds(elements) {
127
+ const min = [Infinity, Infinity, Infinity];
128
+ const max = [-Infinity, -Infinity, -Infinity];
129
+ let any = false;
130
+ for (const el of elements) {
131
+ if (!el.bounds)
132
+ continue;
133
+ any = true;
134
+ for (let i = 0; i < 3; i++) {
135
+ if (el.bounds.min[i] < min[i])
136
+ min[i] = el.bounds.min[i];
137
+ if (el.bounds.max[i] > max[i])
138
+ max[i] = el.bounds.max[i];
139
+ }
140
+ }
141
+ return any ? { min, max } : undefined;
142
+ }
143
+ function nsKey(elementName, key) {
144
+ return `${elementName}.${key}`;
145
+ }
146
+ function splitNs(key) {
147
+ const dot = key.indexOf('.');
148
+ if (dot < 0)
149
+ return ['', key];
150
+ return [key.slice(0, dot), key.slice(dot + 1)];
151
+ }
152
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Knob } from './types.js';
2
+ type KnobChange = (key: string, value: number | string | boolean) => void;
3
+ /**
4
+ * Render a minimal slider/color/checkbox editor for the element's knobs into the given DOM container.
5
+ * Returns a `setValue(key, value)` function so external sources (MCP) can keep the UI in sync.
6
+ *
7
+ * Stays deliberately bare-bones — the styling is the host page's job.
8
+ */
9
+ export declare function mountEditor(container: HTMLElement, knobs: Record<string, Knob>, initial: Record<string, number | string | boolean>, onChange: KnobChange): {
10
+ setValue: (key: string, value: number | string | boolean) => void;
11
+ destroy: () => void;
12
+ };
13
+ export {};
14
+ //# sourceMappingURL=editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,KAAK,UAAU,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;AAE1E;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EAClD,QAAQ,EAAE,UAAU,GACnB;IAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,KAAK,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAmH5F"}
package/dist/editor.js ADDED
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Render a minimal slider/color/checkbox editor for the element's knobs into the given DOM container.
3
+ * Returns a `setValue(key, value)` function so external sources (MCP) can keep the UI in sync.
4
+ *
5
+ * Stays deliberately bare-bones — the styling is the host page's job.
6
+ */
7
+ export function mountEditor(container, knobs, initial, onChange) {
8
+ container.replaceChildren();
9
+ container.classList.add('triscope-editor');
10
+ const inputs = new Map();
11
+ const values = new Map();
12
+ for (const [key, spec] of Object.entries(knobs)) {
13
+ const row = document.createElement('div');
14
+ row.className = 'triscope-editor__row';
15
+ row.dataset.knobKey = key;
16
+ const label = document.createElement('label');
17
+ label.textContent = spec.label ?? key;
18
+ label.title = key;
19
+ row.appendChild(label);
20
+ const input = document.createElement('input');
21
+ input.dataset.knobKey = key;
22
+ const out = document.createElement('output');
23
+ if (spec.type === 'number' || spec.type === 'int') {
24
+ input.type = 'range';
25
+ input.min = String(spec.min);
26
+ input.max = String(spec.max);
27
+ input.step = String(spec.type === 'int' ? 1 : (spec.step ?? (spec.max - spec.min) / 200));
28
+ const v = typeof initial[key] === 'number' ? Number(initial[key]) : spec.default;
29
+ input.value = String(v);
30
+ out.textContent = formatNumber(v, spec.type);
31
+ input.oninput = () => {
32
+ const num = spec.type === 'int' ? parseInt(input.value, 10) : parseFloat(input.value);
33
+ out.textContent = formatNumber(num, spec.type);
34
+ onChange(key, num);
35
+ };
36
+ }
37
+ else if (spec.type === 'color') {
38
+ input.type = 'color';
39
+ const v = typeof initial[key] === 'string' ? String(initial[key]) : spec.default;
40
+ input.value = v;
41
+ out.textContent = v;
42
+ input.oninput = () => {
43
+ out.textContent = input.value;
44
+ onChange(key, input.value);
45
+ };
46
+ }
47
+ else if (spec.type === 'boolean') {
48
+ input.type = 'checkbox';
49
+ const v = typeof initial[key] === 'boolean' ? Boolean(initial[key]) : spec.default;
50
+ input.checked = v;
51
+ out.textContent = v ? 'on' : 'off';
52
+ input.onchange = () => {
53
+ out.textContent = input.checked ? 'on' : 'off';
54
+ onChange(key, input.checked);
55
+ };
56
+ }
57
+ else if (spec.type === 'trigger') {
58
+ // Trigger renders as a button. Each click fires onChange(true) — the
59
+ // value carries no state, the act itself is the signal.
60
+ const btn = document.createElement('button');
61
+ btn.type = 'button';
62
+ btn.textContent = spec.label ?? key;
63
+ btn.dataset.knobKey = key;
64
+ btn.onclick = () => {
65
+ out.textContent = 'fired';
66
+ onChange(key, true);
67
+ setTimeout(() => {
68
+ out.textContent = '–';
69
+ }, 400);
70
+ };
71
+ out.textContent = '–';
72
+ row.appendChild(btn);
73
+ row.appendChild(out);
74
+ container.appendChild(row);
75
+ values.set(key, out);
76
+ // No persistent input element — leave inputs.set unset; setValue from
77
+ // external pulse will still flash the output.
78
+ continue;
79
+ }
80
+ row.appendChild(input);
81
+ row.appendChild(out);
82
+ container.appendChild(row);
83
+ inputs.set(key, input);
84
+ values.set(key, out);
85
+ }
86
+ return {
87
+ setValue(key, value) {
88
+ const out = values.get(key);
89
+ const spec = knobs[key];
90
+ if (!out || !spec)
91
+ return;
92
+ if (spec.type === 'trigger') {
93
+ // External pulse — flash the output to confirm receipt; no input
94
+ // element to sync because trigger has no persistent state.
95
+ out.textContent = 'fired';
96
+ setTimeout(() => {
97
+ out.textContent = '–';
98
+ }, 400);
99
+ return;
100
+ }
101
+ const inp = inputs.get(key);
102
+ if (!inp)
103
+ return;
104
+ if (spec.type === 'number' || spec.type === 'int') {
105
+ inp.value = String(value);
106
+ out.textContent = formatNumber(Number(value), spec.type);
107
+ }
108
+ else if (spec.type === 'color') {
109
+ inp.value = String(value);
110
+ out.textContent = String(value);
111
+ }
112
+ else if (spec.type === 'boolean') {
113
+ inp.checked = Boolean(value);
114
+ out.textContent = inp.checked ? 'on' : 'off';
115
+ }
116
+ },
117
+ destroy() {
118
+ container.replaceChildren();
119
+ },
120
+ };
121
+ }
122
+ function formatNumber(v, type) {
123
+ if (type === 'int')
124
+ return String(Math.round(v));
125
+ if (Math.abs(v) >= 100)
126
+ return v.toFixed(1);
127
+ if (Math.abs(v) >= 1)
128
+ return v.toFixed(2);
129
+ return v.toFixed(3);
130
+ }
131
+ //# sourceMappingURL=editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.js","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,199 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import { type InspectSelection } from './inspect.js';
3
+ import { type CameraDelta } from './scene-delta.js';
4
+ import { type LightNode, type SceneNode } from './scene-introspect.js';
5
+ import type { CameraSpec, Element, MountHandle } from './types.js';
6
+ import { type UniformReadResult, type UniformWriteResult } from './uniform-access.js';
7
+ import { type WarningEntry } from './warnings.js';
8
+ declare global {
9
+ interface Window {
10
+ __TRISCOPE__?: TriscopeGlobal;
11
+ /** Set when element.mount() throws or the contract is invalid; read by the
12
+ * MCP browser pool to report the real cause instead of a mount timeout. */
13
+ __TRISCOPE_MOUNT_ERROR__?: {
14
+ element?: string;
15
+ message: string;
16
+ problems: string[];
17
+ };
18
+ }
19
+ }
20
+ interface TriscopeGlobal {
21
+ element: Element;
22
+ handle: MountHandle;
23
+ renderer: THREE.WebGPURenderer;
24
+ scene: THREE.Scene;
25
+ cameras: Record<string, THREE.PerspectiveCamera>;
26
+ knobValues: Record<string, number | string | boolean>;
27
+ setKnob: (key: string, value: number | string | boolean) => void;
28
+ sampleTelemetry: () => Record<string, unknown>;
29
+ /** Inspect-mode selection — set by clicking a mesh in `?inspect=` mode. */
30
+ lastSelection?: InspectSelection | null;
31
+ /** Capture one frame per named camera; returns base64 PNGs keyed by camera. */
32
+ captureViews: () => Promise<Record<string, string>>;
33
+ /**
34
+ * Capture N frames of a single camera spaced by dt seconds. In `mode: 'time'`
35
+ * (default), the RAF loop is paused and `time.value` is stepped forward
36
+ * deterministically — works for shader-driven motion. In `mode: 'real'`, the
37
+ * RAF keeps running and frames are sampled at wall-clock intervals — needed
38
+ * for CPU-integrated state (springs, particles).
39
+ */
40
+ captureMotionFrames: (camera: string, opts?: {
41
+ frames?: number;
42
+ dt?: number;
43
+ mode?: 'time' | 'real';
44
+ }) => Promise<string[]>;
45
+ /** Per-camera GPU probe stats from the most recent captureViews() call. */
46
+ lastGpuProbes?: Record<string, GpuProbeStats>;
47
+ /** Recent non-fatal failures the harness swallowed (telemetry/knob/probe). */
48
+ warnings?: WarningEntry[];
49
+ /**
50
+ * Snapshot the live scene graph: meshes/lights/groups with triangle counts,
51
+ * world positions, material kind/color, uniform names, and the file:line
52
+ * source tag. One call replaces a flurry of capture probes when an agent
53
+ * needs to know what's actually in the scene.
54
+ */
55
+ queryScene: (maxNodes?: number) => {
56
+ nodes: SceneNode[];
57
+ lights: LightNode[];
58
+ total: number;
59
+ truncated: boolean;
60
+ };
61
+ /** Read any material uniform / material property / object property by
62
+ * "objectName|uuid.key" path — even undeclared ones. */
63
+ readUniform: (path: string) => UniformReadResult;
64
+ /** Write the same, live and transient (not persisted). Returns previous/current. */
65
+ setUniform: (path: string, value: unknown) => UniformWriteResult;
66
+ /**
67
+ * SDL: apply a scene delta live (repoint cameras, override knobs) WITHOUT a
68
+ * reload, and persist it via /__scene so it survives one. Returns the live
69
+ * scene state after applying.
70
+ */
71
+ setSceneParam: (delta: {
72
+ cameras?: Record<string, CameraDelta>;
73
+ knobs?: Record<string, unknown>;
74
+ elements?: Record<string, {
75
+ enabled?: boolean;
76
+ }>;
77
+ }) => {
78
+ cameras: Record<string, {
79
+ position: number[];
80
+ target: number[];
81
+ fov: number;
82
+ }>;
83
+ knobs: Record<string, number | string | boolean>;
84
+ elements: Record<string, {
85
+ enabled: boolean;
86
+ }>;
87
+ };
88
+ /** Current live camera positions/targets/fov + knob values + element on/off. */
89
+ getScene: () => {
90
+ cameras: Record<string, {
91
+ position: number[];
92
+ target: number[];
93
+ fov: number;
94
+ }>;
95
+ knobs: Record<string, number | string | boolean>;
96
+ elements: Record<string, {
97
+ enabled: boolean;
98
+ }>;
99
+ };
100
+ /**
101
+ * Mount a registered-but-unmounted element live (runSceneLab scenes). Adds
102
+ * its namespaced cameras/knobs and rebuilds the grid. Returns false if the
103
+ * name is unknown or already mounted. A single-element runLab has nothing
104
+ * else to add, so this is a no-op there.
105
+ */
106
+ addElement: (name: string) => boolean;
107
+ /** Dispose a mounted element live + rebuild the grid. False if not mounted. */
108
+ removeElement: (name: string) => boolean;
109
+ /** All element names in the scene registry (mounted or not). */
110
+ availableElements: () => string[];
111
+ /** Currently mounted element names, in registry order. */
112
+ mountedElements: () => string[];
113
+ /** Monotonic count of rendered frames since boot (gates capture readiness). */
114
+ framesRendered: () => number;
115
+ }
116
+ export interface CommonLabOptions {
117
+ canvas: HTMLCanvasElement;
118
+ editorContainer?: HTMLElement | null;
119
+ labelContainer?: HTMLElement | null;
120
+ hud?: HTMLElement | null;
121
+ bootOverlay?: HTMLElement | null;
122
+ telemetryIntervalMs?: number;
123
+ knobPollMs?: number;
124
+ /** Optional clear color for the scene before each frame. Default `#0a1a20`. */
125
+ clearColor?: number;
126
+ /**
127
+ * Fixed [width, height] in CSS pixels to which the canvas is resized for
128
+ * every captureViews / captureMotionFrames call, then restored. Use this
129
+ * when you need reproducible framing across page reloads (otherwise the
130
+ * canvas tracks clientWidth/clientHeight which can drift). Off by default.
131
+ */
132
+ captureSize?: [number, number];
133
+ }
134
+ export interface LabOptions extends CommonLabOptions {
135
+ element: Element;
136
+ }
137
+ export interface SceneLabOptions extends CommonLabOptions {
138
+ /** Registry of element types the scene can mount/unmount at runtime. */
139
+ elements: Element[];
140
+ /** Names initially mounted (defaults to every registered element). */
141
+ mounted?: string[];
142
+ }
143
+ export interface LabHandle {
144
+ /** The primary mounted element (first slot); undefined-safe as slots change. */
145
+ element: Element;
146
+ setKnob: (key: string, value: number | string | boolean) => void;
147
+ captureViews: () => Promise<Record<string, string>>;
148
+ /** Mount a registered element live (runSceneLab). No-op for single-element labs. */
149
+ addElement: (name: string) => boolean;
150
+ /** Dispose a mounted element live (runSceneLab). */
151
+ removeElement: (name: string) => boolean;
152
+ /** All element names in the scene registry. */
153
+ availableElements: () => string[];
154
+ /** Currently mounted element names. */
155
+ mountedElements: () => string[];
156
+ stop: () => void;
157
+ }
158
+ /**
159
+ * Boot a multi-camera lab page for a single Element.
160
+ * One WebGPURenderer, one scene, one Element, N scissored viewports.
161
+ *
162
+ * Thin wrapper over {@link runSceneCore}: a one-entry, non-namespaced registry.
163
+ * The single-element path is behaviorally identical to before this was
164
+ * generalized — bare camera/knob names, `elements: { [name]: … }`,
165
+ * `project: name`.
166
+ */
167
+ export declare function runLab(opts: LabOptions): Promise<LabHandle>;
168
+ /**
169
+ * Boot a multi-camera lab page for a SCENE of N Elements, with runtime
170
+ * mount/unmount via the returned handle's addElement/removeElement (and the
171
+ * MCP add_element/remove_element tools). Camera + knob keys are namespaced
172
+ * `<element>.<key>` so two elements that both declare `top`/`speed` never
173
+ * collide. Shares one renderer/scene with runLab through {@link runSceneCore}.
174
+ */
175
+ export declare function runSceneLab(opts: SceneLabOptions): Promise<LabHandle>;
176
+ /**
177
+ * Aggregated brightness/contrast scalars computed from a rendered canvas.
178
+ * Probes are run by the harness during `captureViews()` — they validate
179
+ * that the GPU actually drew something (luminance > 0 means the frame is
180
+ * not black; p95/p5 ratio > 1 means there's contrast).
181
+ */
182
+ export interface GpuProbeStats {
183
+ /** Mean perceptual luminance (Rec.709) in [0, 1]. */
184
+ luminance: number;
185
+ /** 5th percentile luminance. */
186
+ p5: number;
187
+ /** 95th percentile luminance. */
188
+ p95: number;
189
+ /** p95 / max(p5, 1/255) — dynamic range proxy. */
190
+ dynamicRange: number;
191
+ /** Number of pixels sampled (typically 64×36 = 2304). */
192
+ samples: number;
193
+ /** Set true when this camera's mean luminance indicates a black render. */
194
+ blackFrame?: boolean;
195
+ }
196
+ export declare function makeCamera(spec: CameraSpec, bounds?: Element['bounds']): THREE.PerspectiveCamera;
197
+ export declare function fitCameraToBounds(cam: THREE.PerspectiveCamera, spec: CameraSpec, bounds: NonNullable<Element['bounds']>): void;
198
+ export {};
199
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"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"}