@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.
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/compose.d.ts +11 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +152 -0
- package/dist/compose.js.map +1 -0
- package/dist/editor.d.ts +14 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +131 -0
- package/dist/editor.js.map +1 -0
- package/dist/harness.d.ts +199 -0
- package/dist/harness.d.ts.map +1 -0
- package/dist/harness.js +1027 -0
- package/dist/harness.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect.d.ts +94 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +434 -0
- package/dist/inspect.js.map +1 -0
- package/dist/knob-utils.d.ts +22 -0
- package/dist/knob-utils.d.ts.map +1 -0
- package/dist/knob-utils.js +51 -0
- package/dist/knob-utils.js.map +1 -0
- package/dist/lab/css.d.ts +11 -0
- package/dist/lab/css.d.ts.map +1 -0
- package/dist/lab/css.js +57 -0
- package/dist/lab/css.js.map +1 -0
- package/dist/lab/dom.d.ts +13 -0
- package/dist/lab/dom.d.ts.map +1 -0
- package/dist/lab/dom.js +76 -0
- package/dist/lab/dom.js.map +1 -0
- package/dist/motion-probe.d.ts +47 -0
- package/dist/motion-probe.d.ts.map +1 -0
- package/dist/motion-probe.js +122 -0
- package/dist/motion-probe.js.map +1 -0
- package/dist/probe-utils.d.ts +14 -0
- package/dist/probe-utils.d.ts.map +1 -0
- package/dist/probe-utils.js +18 -0
- package/dist/probe-utils.js.map +1 -0
- package/dist/scene-cameras.d.ts +6 -0
- package/dist/scene-cameras.d.ts.map +1 -0
- package/dist/scene-cameras.js +20 -0
- package/dist/scene-cameras.js.map +1 -0
- package/dist/scene-delta.d.ts +21 -0
- package/dist/scene-delta.d.ts.map +1 -0
- package/dist/scene-delta.js +57 -0
- package/dist/scene-delta.js.map +1 -0
- package/dist/scene-introspect.d.ts +78 -0
- package/dist/scene-introspect.d.ts.map +1 -0
- package/dist/scene-introspect.js +164 -0
- package/dist/scene-introspect.js.map +1 -0
- package/dist/scene-registry.d.ts +36 -0
- package/dist/scene-registry.d.ts.map +1 -0
- package/dist/scene-registry.js +64 -0
- package/dist/scene-registry.js.map +1 -0
- package/dist/scene-view.d.ts +52 -0
- package/dist/scene-view.d.ts.map +1 -0
- package/dist/scene-view.js +171 -0
- package/dist/scene-view.js.map +1 -0
- package/dist/source-tag.d.ts +34 -0
- package/dist/source-tag.d.ts.map +1 -0
- package/dist/source-tag.js +120 -0
- package/dist/source-tag.js.map +1 -0
- package/dist/telemetry.d.ts +53 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +302 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +142 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/uniform-access.d.ts +32 -0
- package/dist/uniform-access.d.ts.map +1 -0
- package/dist/uniform-access.js +144 -0
- package/dist/uniform-access.js.map +1 -0
- package/dist/validate.d.ts +2 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +81 -0
- package/dist/validate.js.map +1 -0
- package/dist/vite.d.ts +3 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +4 -0
- package/dist/vite.js.map +1 -0
- package/dist/warnings.d.ts +24 -0
- package/dist/warnings.d.ts.map +1 -0
- package/dist/warnings.js +26 -0
- package/dist/warnings.js.map +1 -0
- package/package.json +60 -0
- package/src/compose.ts +164 -0
- package/src/editor.ts +138 -0
- package/src/harness.ts +1263 -0
- package/src/index.ts +58 -0
- package/src/inspect.ts +498 -0
- package/src/knob-utils.ts +60 -0
- package/src/lab/css.ts +56 -0
- package/src/lab/dom.ts +88 -0
- package/src/motion-probe.ts +135 -0
- package/src/probe-utils.ts +17 -0
- package/src/scene-cameras.ts +33 -0
- package/src/scene-delta.ts +69 -0
- package/src/scene-introspect.ts +230 -0
- package/src/scene-registry.ts +103 -0
- package/src/scene-view.ts +204 -0
- package/src/source-tag.ts +139 -0
- package/src/telemetry.ts +337 -0
- package/src/three-webgpu-shim.d.ts +130 -0
- package/src/types.ts +121 -0
- package/src/uniform-access.ts +152 -0
- package/src/validate.ts +82 -0
- package/src/vite.ts +5 -0
- package/src/warnings.ts +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAkHA;;uEAEuE;AACvE,MAAM,UAAU,WAAW,CAAC,CAAO;IACjC,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACvC,OAAO,CAAC,CAAC,OAAO,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure live-uniform read/write by path — the data layer behind read_uniform /
|
|
3
|
+
* set_uniform. Lets an agent probe or nudge ANY material uniform / material
|
|
4
|
+
* property / object property (e.g. a shader uniform that was never declared as
|
|
5
|
+
* a knob, or `sun.intensity`) without a source edit + reload. Duck-typed (no
|
|
6
|
+
* `three` import) so it's unit-testable; the harness passes the live scene.
|
|
7
|
+
*
|
|
8
|
+
* Path format: "<objectName|uuid>.<key>" — split on the LAST dot, so dotted
|
|
9
|
+
* object names still work and the trailing segment is the uniform/property key.
|
|
10
|
+
* Writes are TRANSIENT (live only, not persisted via /__knob).
|
|
11
|
+
*/
|
|
12
|
+
export interface UniformReadResult {
|
|
13
|
+
kind: 'uniform' | 'material-property' | 'object-property' | 'not-found';
|
|
14
|
+
path: string;
|
|
15
|
+
value?: unknown;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface UniformWriteResult {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
kind: 'uniform' | 'material-property' | 'object-property' | 'not-found';
|
|
21
|
+
path: string;
|
|
22
|
+
previous?: unknown;
|
|
23
|
+
current?: unknown;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function parseUniformPath(path: string): {
|
|
27
|
+
objectId: string;
|
|
28
|
+
key: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function readUniformValue(root: any, path: string): UniformReadResult;
|
|
31
|
+
export declare function writeUniformValue(root: any, path: string, value: unknown): UniformWriteResult;
|
|
32
|
+
//# sourceMappingURL=uniform-access.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uniform-access.d.ts","sourceRoot":"","sources":["../src/uniform-access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,GAAG,mBAAmB,GAAG,iBAAiB,GAAG,WAAW,CAAC;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,SAAS,GAAG,mBAAmB,GAAG,iBAAiB,GAAG,WAAW,CAAC;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAIhF;AA0DD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAQ3E;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAmD7F"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure live-uniform read/write by path — the data layer behind read_uniform /
|
|
3
|
+
* set_uniform. Lets an agent probe or nudge ANY material uniform / material
|
|
4
|
+
* property / object property (e.g. a shader uniform that was never declared as
|
|
5
|
+
* a knob, or `sun.intensity`) without a source edit + reload. Duck-typed (no
|
|
6
|
+
* `three` import) so it's unit-testable; the harness passes the live scene.
|
|
7
|
+
*
|
|
8
|
+
* Path format: "<objectName|uuid>.<key>" — split on the LAST dot, so dotted
|
|
9
|
+
* object names still work and the trailing segment is the uniform/property key.
|
|
10
|
+
* Writes are TRANSIENT (live only, not persisted via /__knob).
|
|
11
|
+
*/
|
|
12
|
+
export function parseUniformPath(path) {
|
|
13
|
+
const i = path.lastIndexOf('.');
|
|
14
|
+
if (i < 0)
|
|
15
|
+
return { objectId: path, key: '' };
|
|
16
|
+
return { objectId: path.slice(0, i), key: path.slice(i + 1) };
|
|
17
|
+
}
|
|
18
|
+
function findObject(root, id) {
|
|
19
|
+
if (!root)
|
|
20
|
+
return null;
|
|
21
|
+
const stack = [root];
|
|
22
|
+
let guard = 0;
|
|
23
|
+
while (stack.length && guard++ < 100000) {
|
|
24
|
+
const o = stack.pop();
|
|
25
|
+
if (o?.name === id || o?.uuid === id)
|
|
26
|
+
return o;
|
|
27
|
+
const kids = o?.children;
|
|
28
|
+
if (Array.isArray(kids))
|
|
29
|
+
for (let i = kids.length - 1; i >= 0; i--)
|
|
30
|
+
stack.push(kids[i]);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/** Locate where a key's value lives: a legacy uniform slot, a material prop, or an object prop. */
|
|
35
|
+
function locateSlot(obj, key) {
|
|
36
|
+
if (!key)
|
|
37
|
+
return null;
|
|
38
|
+
const mat = Array.isArray(obj?.material) ? obj.material[0] : obj?.material;
|
|
39
|
+
try {
|
|
40
|
+
if (mat?.uniforms && typeof mat.uniforms === 'object' && mat.uniforms[key] !== undefined) {
|
|
41
|
+
return { kind: 'uniform', container: mat.uniforms[key], prop: 'value' };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
/* fall through */
|
|
46
|
+
}
|
|
47
|
+
if (mat && key in mat)
|
|
48
|
+
return { kind: 'material-property', container: mat, prop: key };
|
|
49
|
+
if (obj && key in obj)
|
|
50
|
+
return { kind: 'object-property', container: obj, prop: key };
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/** Bound, JSON-safe serialization of a slot value (Color→hex, Vector→array, texture→tag). */
|
|
54
|
+
function serializeVal(v) {
|
|
55
|
+
if (v === null || v === undefined)
|
|
56
|
+
return null;
|
|
57
|
+
const t = typeof v;
|
|
58
|
+
if (t === 'number' || t === 'string' || t === 'boolean')
|
|
59
|
+
return v;
|
|
60
|
+
try {
|
|
61
|
+
if (v.isTexture || v.image || v.source)
|
|
62
|
+
return '[texture]';
|
|
63
|
+
if (typeof v.getHexString === 'function')
|
|
64
|
+
return `#${v.getHexString()}`;
|
|
65
|
+
if (typeof v.x === 'number' && typeof v.y === 'number') {
|
|
66
|
+
const out = [v.x, v.y];
|
|
67
|
+
if (typeof v.z === 'number')
|
|
68
|
+
out.push(v.z);
|
|
69
|
+
if (typeof v.w === 'number')
|
|
70
|
+
out.push(v.w);
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* exotic value — fall through */
|
|
76
|
+
}
|
|
77
|
+
return '[object]';
|
|
78
|
+
}
|
|
79
|
+
export function readUniformValue(root, path) {
|
|
80
|
+
const { objectId, key } = parseUniformPath(path);
|
|
81
|
+
const obj = findObject(root, objectId);
|
|
82
|
+
if (!obj)
|
|
83
|
+
return { kind: 'not-found', path, error: `no object named/uuid "${objectId}"` };
|
|
84
|
+
const slot = locateSlot(obj, key);
|
|
85
|
+
if (!slot)
|
|
86
|
+
return { kind: 'not-found', path, error: `no uniform/property "${key}" on "${objectId}"` };
|
|
87
|
+
return { kind: slot.kind, path, value: serializeVal(slot.container[slot.prop]) };
|
|
88
|
+
}
|
|
89
|
+
export function writeUniformValue(root, path, value) {
|
|
90
|
+
const { objectId, key } = parseUniformPath(path);
|
|
91
|
+
const obj = findObject(root, objectId);
|
|
92
|
+
if (!obj)
|
|
93
|
+
return { ok: false, kind: 'not-found', path, error: `no object named/uuid "${objectId}"` };
|
|
94
|
+
const slot = locateSlot(obj, key);
|
|
95
|
+
if (!slot)
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
kind: 'not-found',
|
|
99
|
+
path,
|
|
100
|
+
error: `no uniform/property "${key}" on "${objectId}"`,
|
|
101
|
+
};
|
|
102
|
+
const previous = serializeVal(slot.container[slot.prop]);
|
|
103
|
+
try {
|
|
104
|
+
const cur = slot.container[slot.prop];
|
|
105
|
+
if (cur && typeof cur.getHexString === 'function' && typeof cur.set === 'function') {
|
|
106
|
+
cur.set(value); // THREE.Color — accepts '#hex' or number
|
|
107
|
+
}
|
|
108
|
+
else if (cur &&
|
|
109
|
+
typeof cur.set === 'function' &&
|
|
110
|
+
typeof cur.x === 'number' &&
|
|
111
|
+
Array.isArray(value)) {
|
|
112
|
+
cur.set(...value); // THREE.Vector*
|
|
113
|
+
}
|
|
114
|
+
else if (typeof cur === 'number') {
|
|
115
|
+
// Coerce to the slot's existing type so a "6.0" from a loosely-typed tool
|
|
116
|
+
// call doesn't silently store a STRING (it would work by accident in
|
|
117
|
+
// arithmetic but read back as the wrong type and break strict consumers).
|
|
118
|
+
slot.container[slot.prop] = Number(value);
|
|
119
|
+
}
|
|
120
|
+
else if (typeof cur === 'boolean') {
|
|
121
|
+
slot.container[slot.prop] = value === true || value === 'true';
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
slot.container[slot.prop] = value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
kind: slot.kind,
|
|
131
|
+
path,
|
|
132
|
+
previous,
|
|
133
|
+
error: String(e?.message ?? e),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
ok: true,
|
|
138
|
+
kind: slot.kind,
|
|
139
|
+
path,
|
|
140
|
+
previous,
|
|
141
|
+
current: serializeVal(slot.container[slot.prop]),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=uniform-access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uniform-access.js","sourceRoot":"","sources":["../src/uniform-access.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAMA,wBAAgB,eAAe,CAAC,EAAE,EAAE,GAAG,GAAG,MAAM,EAAE,CAuEjD"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Pure Element-contract validation. The harness runs this before mount so a
|
|
2
|
+
// malformed element surfaces a precise, actionable error in telemetry warnings
|
|
3
|
+
// + the boot overlay, instead of a cryptic "harness not mounted within 10s".
|
|
4
|
+
const KNOB_TYPES = new Set(['number', 'int', 'color', 'boolean', 'trigger']);
|
|
5
|
+
export function validateElement(el) {
|
|
6
|
+
const problems = [];
|
|
7
|
+
if (!el || typeof el !== 'object') {
|
|
8
|
+
return ['element is not an object'];
|
|
9
|
+
}
|
|
10
|
+
if (typeof el.name !== 'string' || el.name.length === 0) {
|
|
11
|
+
problems.push('element.name must be a non-empty string');
|
|
12
|
+
}
|
|
13
|
+
if (typeof el.mount !== 'function') {
|
|
14
|
+
problems.push('element.mount must be a function');
|
|
15
|
+
}
|
|
16
|
+
// `cameras: 'auto'` is valid — the harness synthesizes fitted presets from
|
|
17
|
+
// bounds (runSceneView's default), so it must NOT trip the "declare a camera"
|
|
18
|
+
// warning (which would pollute read_telemetry .warnings on every boot).
|
|
19
|
+
if (el.cameras === 'auto') {
|
|
20
|
+
/* ok */
|
|
21
|
+
}
|
|
22
|
+
else if (!el.cameras ||
|
|
23
|
+
typeof el.cameras !== 'object' ||
|
|
24
|
+
Object.keys(el.cameras).length === 0) {
|
|
25
|
+
problems.push("element.cameras must declare at least one named camera (or 'auto')");
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
for (const [name, spec] of Object.entries(el.cameras)) {
|
|
29
|
+
if (!isVec3(spec?.position) || !isVec3(spec?.target)) {
|
|
30
|
+
problems.push(`camera "${name}" needs position[3] and target[3]`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (el.knobs !== undefined) {
|
|
35
|
+
if (typeof el.knobs !== 'object' || el.knobs === null) {
|
|
36
|
+
problems.push('element.knobs must be an object');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
for (const [key, k] of Object.entries(el.knobs)) {
|
|
40
|
+
if (!k || !KNOB_TYPES.has(k.type)) {
|
|
41
|
+
problems.push(`knob "${key}" has an unknown type "${k?.type}"`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if ((k.type === 'number' || k.type === 'int') &&
|
|
45
|
+
(typeof k.min !== 'number' || typeof k.max !== 'number')) {
|
|
46
|
+
problems.push(`knob "${key}" (${k.type}) needs numeric min and max`);
|
|
47
|
+
}
|
|
48
|
+
if ((k.type === 'number' || k.type === 'int') && typeof k.default !== 'number') {
|
|
49
|
+
problems.push(`knob "${key}" (${k.type}) needs a numeric default`);
|
|
50
|
+
}
|
|
51
|
+
if (k.type === 'color' && typeof k.default !== 'string') {
|
|
52
|
+
problems.push(`knob "${key}" (color) needs a string default`);
|
|
53
|
+
}
|
|
54
|
+
if (k.type === 'boolean' && typeof k.default !== 'boolean') {
|
|
55
|
+
problems.push(`knob "${key}" (boolean) needs a boolean default`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const hook of ['onKnob', 'telemetry', 'events']) {
|
|
61
|
+
if (el[hook] !== undefined && typeof el[hook] !== 'function') {
|
|
62
|
+
problems.push(`element.${hook} must be a function if present`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (el.motionProbes !== undefined) {
|
|
66
|
+
if (typeof el.motionProbes !== 'object' || el.motionProbes === null) {
|
|
67
|
+
problems.push('element.motionProbes must be an object of functions');
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
for (const [key, fn] of Object.entries(el.motionProbes)) {
|
|
71
|
+
if (typeof fn !== 'function')
|
|
72
|
+
problems.push(`motionProbe "${key}" must be a function`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return problems;
|
|
77
|
+
}
|
|
78
|
+
function isVec3(v) {
|
|
79
|
+
return Array.isArray(v) && v.length === 3 && v.every((n) => typeof n === 'number');
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/vite.js
ADDED
package/dist/vite.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../src/vite.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,wEAAwE;AAGxE,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** One non-fatal failure the harness swallowed but wants the agent to see. */
|
|
2
|
+
export interface WarningEntry {
|
|
3
|
+
/** Wall-clock ms when recorded. */
|
|
4
|
+
ts: number;
|
|
5
|
+
/** Where it came from: 'postState' | 'postManifest' | 'pollKnobs' | 'telemetry-fn' | 'motion-probe' | 'event-drain' | 'black-frame' | 'knob-clamp' | … */
|
|
6
|
+
source: string;
|
|
7
|
+
/** Short human-readable summary. */
|
|
8
|
+
message: string;
|
|
9
|
+
/** Optional extra context (stack tail, value, …). */
|
|
10
|
+
detail?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface WarningRing {
|
|
13
|
+
push: (source: string, message: string, detail?: string) => void;
|
|
14
|
+
list: () => WarningEntry[];
|
|
15
|
+
readonly size: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Bounded ring of recent swallowed failures, surfaced via telemetry `.warnings`
|
|
19
|
+
* so an agent can answer "why is telemetry stale / the knob not applying / this
|
|
20
|
+
* pane black?" with a `read_telemetry .warnings` call instead of guessing. The
|
|
21
|
+
* harness wires this into the `.catch(()=>{})` sites that used to fail silently.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createWarningRing(cap?: number, now?: () => number): WarningRing;
|
|
24
|
+
//# sourceMappingURL=warnings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warnings.d.ts","sourceRoot":"","sources":["../src/warnings.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,mCAAmC;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,0JAA0J;IAC1J,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,IAAI,EAAE,MAAM,YAAY,EAAE,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,SAAK,EAAE,GAAG,GAAE,MAAM,MAAyB,GAAG,WAAW,CAgB7F"}
|
package/dist/warnings.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded ring of recent swallowed failures, surfaced via telemetry `.warnings`
|
|
3
|
+
* so an agent can answer "why is telemetry stale / the knob not applying / this
|
|
4
|
+
* pane black?" with a `read_telemetry .warnings` call instead of guessing. The
|
|
5
|
+
* harness wires this into the `.catch(()=>{})` sites that used to fail silently.
|
|
6
|
+
*/
|
|
7
|
+
export function createWarningRing(cap = 32, now = () => Date.now()) {
|
|
8
|
+
const buf = [];
|
|
9
|
+
return {
|
|
10
|
+
push(source, message, detail) {
|
|
11
|
+
const entry = { ts: now(), source, message };
|
|
12
|
+
if (detail !== undefined)
|
|
13
|
+
entry.detail = detail;
|
|
14
|
+
buf.push(entry);
|
|
15
|
+
if (buf.length > cap)
|
|
16
|
+
buf.shift();
|
|
17
|
+
},
|
|
18
|
+
list() {
|
|
19
|
+
return buf.slice();
|
|
20
|
+
},
|
|
21
|
+
get size() {
|
|
22
|
+
return buf.length;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=warnings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warnings.js","sourceRoot":"","sources":["../src/warnings.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAG,GAAG,EAAE,EAAE,MAAoB,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;IAC9E,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,OAAO;QACL,IAAI,CAAC,MAAc,EAAE,OAAe,EAAE,MAAe;YACnD,MAAM,KAAK,GAAiB,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC3D,IAAI,MAAM,KAAK,SAAS;gBAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;YAChD,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAChB,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG;gBAAE,GAAG,CAAC,KAAK,EAAE,CAAC;QACpC,CAAC;QACD,IAAI;YACF,OAAO,GAAG,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;QACD,IAAI,IAAI;YACN,OAAO,GAAG,CAAC,MAAM,CAAC;QACpB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@triscope/core",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Triscope runtime: Element contract, multi-camera lab harness, Vite telemetry plugin.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./vite": {
|
|
15
|
+
"types": "./dist/vite.d.ts",
|
|
16
|
+
"import": "./dist/vite.js",
|
|
17
|
+
"default": "./dist/vite.js"
|
|
18
|
+
},
|
|
19
|
+
"./src/*": "./src/*"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"three": ">=0.170.0",
|
|
31
|
+
"vite": ">=5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^20.0.0",
|
|
35
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
36
|
+
"jsdom": "^25.0.0",
|
|
37
|
+
"three": "^0.176.0",
|
|
38
|
+
"typescript": "^5.6.0",
|
|
39
|
+
"vite": "^5.4.0",
|
|
40
|
+
"vitest": "^2.1.9"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -p tsconfig.json",
|
|
44
|
+
"prepublishOnly": "npm run build",
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"test:coverage": "vitest run --coverage --coverage.reporter=text --coverage.reporter=html"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"three.js",
|
|
52
|
+
"webgpu",
|
|
53
|
+
"tsl",
|
|
54
|
+
"ai-tools",
|
|
55
|
+
"claude-code",
|
|
56
|
+
"shader-tuning",
|
|
57
|
+
"3d",
|
|
58
|
+
"lab"
|
|
59
|
+
]
|
|
60
|
+
}
|
package/src/compose.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
import type { CameraSpec, Element, MountContext, MountHandle, TriscopeEvent } from './types.js';
|
|
21
|
+
|
|
22
|
+
export interface ComposeOptions {
|
|
23
|
+
/** Name of the composite element. Used for the state file + manifest. Default: 'composite'. */
|
|
24
|
+
name?: string;
|
|
25
|
+
/** Override the auto-computed bounds (union of children's). */
|
|
26
|
+
bounds?: Element['bounds'];
|
|
27
|
+
/** Override the lab URL (otherwise inherits the first child's). */
|
|
28
|
+
labUrl?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ChildHandles {
|
|
32
|
+
handles: MountHandle[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function composeElements(elements: Element[], opts: ComposeOptions = {}): Element {
|
|
36
|
+
if (!Array.isArray(elements) || elements.length === 0) {
|
|
37
|
+
throw new Error('composeElements: pass a non-empty array of Element');
|
|
38
|
+
}
|
|
39
|
+
const name = opts.name ?? 'composite';
|
|
40
|
+
const labUrl = opts.labUrl ?? elements[0]?.labUrl;
|
|
41
|
+
const bounds = opts.bounds ?? unionBounds(elements);
|
|
42
|
+
|
|
43
|
+
// Pre-build the merged camera/knob/motionProbe maps so the Element shape
|
|
44
|
+
// is correct before mount runs. onKnob/telemetry/motionProbes/events
|
|
45
|
+
// close over `elements` (static) and reach into handle.userData.handles
|
|
46
|
+
// (the live MountHandle list) for per-element lookups.
|
|
47
|
+
const cameras: Element['cameras'] = {};
|
|
48
|
+
const knobs: Element['knobs'] = {};
|
|
49
|
+
const motionProbes: Element['motionProbes'] = {};
|
|
50
|
+
for (let i = 0; i < elements.length; i++) {
|
|
51
|
+
const el = elements[i];
|
|
52
|
+
const camSpecs = el.cameras === 'auto' ? autoCameras(el.bounds) : (el.cameras ?? {});
|
|
53
|
+
for (const [cn, c] of Object.entries(camSpecs)) {
|
|
54
|
+
(cameras as Record<string, CameraSpec>)[nsKey(el.name, cn)] = c;
|
|
55
|
+
}
|
|
56
|
+
for (const [kn, k] of Object.entries(el.knobs ?? {})) {
|
|
57
|
+
(knobs as Record<string, any>)[nsKey(el.name, kn)] = k;
|
|
58
|
+
}
|
|
59
|
+
for (const [pn, p] of Object.entries(el.motionProbes ?? {})) {
|
|
60
|
+
const idx = i; // capture
|
|
61
|
+
(motionProbes as Record<string, any>)[nsKey(el.name, pn)] = (
|
|
62
|
+
handle: MountHandle,
|
|
63
|
+
ctx: MountContext,
|
|
64
|
+
) => {
|
|
65
|
+
const childHandle = (handle.userData as unknown as ChildHandles)?.handles?.[idx];
|
|
66
|
+
return childHandle ? p(childHandle, ctx) : 0;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
name,
|
|
73
|
+
labUrl,
|
|
74
|
+
bounds,
|
|
75
|
+
cameras,
|
|
76
|
+
knobs,
|
|
77
|
+
mount: ({ parent, ctx }) => {
|
|
78
|
+
const handles: MountHandle[] = elements.map((el) => el.mount({ parent, ctx }));
|
|
79
|
+
// name → child handle, so the SDL can show/hide a child at runtime
|
|
80
|
+
// (applyElementToggle) without dispose/remount.
|
|
81
|
+
const childrenByName: Record<string, MountHandle> = {};
|
|
82
|
+
elements.forEach((el, i) => {
|
|
83
|
+
childrenByName[el.name] = handles[i];
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
root: parent,
|
|
87
|
+
userData: { handles, childrenByName } as unknown as Record<string, unknown>,
|
|
88
|
+
dispose: () => {
|
|
89
|
+
for (const h of handles) {
|
|
90
|
+
try {
|
|
91
|
+
h.dispose();
|
|
92
|
+
} catch {
|
|
93
|
+
/* keep tearing down the rest */
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
onKnob: (handle, key, value) => {
|
|
100
|
+
const [elName, knobKey] = splitNs(key);
|
|
101
|
+
if (!elName || !knobKey) return;
|
|
102
|
+
const idx = elements.findIndex((e) => e.name === elName);
|
|
103
|
+
if (idx < 0) return;
|
|
104
|
+
const childHandle = (handle.userData as unknown as ChildHandles)?.handles?.[idx];
|
|
105
|
+
if (!childHandle) return;
|
|
106
|
+
elements[idx].onKnob?.(childHandle, knobKey, value);
|
|
107
|
+
},
|
|
108
|
+
telemetry: (handle, ctx) => {
|
|
109
|
+
const out: Record<string, unknown> = {};
|
|
110
|
+
const childHandles = (handle.userData as unknown as ChildHandles)?.handles ?? [];
|
|
111
|
+
for (let i = 0; i < elements.length; i++) {
|
|
112
|
+
const el = elements[i];
|
|
113
|
+
const h = childHandles[i];
|
|
114
|
+
out[el.name] = h && el.telemetry ? el.telemetry(h, ctx) : {};
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
},
|
|
118
|
+
motionProbes,
|
|
119
|
+
events: (handle, ctx) => {
|
|
120
|
+
const out: TriscopeEvent[] = [];
|
|
121
|
+
const childHandles = (handle.userData as unknown as ChildHandles)?.handles ?? [];
|
|
122
|
+
for (let i = 0; i < elements.length; i++) {
|
|
123
|
+
const el = elements[i];
|
|
124
|
+
const h = childHandles[i];
|
|
125
|
+
if (!el.events || !h) continue;
|
|
126
|
+
try {
|
|
127
|
+
for (const ev of el.events(h, ctx) ?? []) {
|
|
128
|
+
// Namespace event types so a `collision` from ship and water
|
|
129
|
+
// are distinguishable downstream.
|
|
130
|
+
out.push({ ...ev, type: nsKey(el.name, ev.type) });
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
/* ignore element-level failures */
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function unionBounds(elements: Element[]): Element['bounds'] {
|
|
142
|
+
const min: [number, number, number] = [Infinity, Infinity, Infinity];
|
|
143
|
+
const max: [number, number, number] = [-Infinity, -Infinity, -Infinity];
|
|
144
|
+
let any = false;
|
|
145
|
+
for (const el of elements) {
|
|
146
|
+
if (!el.bounds) continue;
|
|
147
|
+
any = true;
|
|
148
|
+
for (let i = 0; i < 3; i++) {
|
|
149
|
+
if (el.bounds.min[i] < min[i]) min[i] = el.bounds.min[i];
|
|
150
|
+
if (el.bounds.max[i] > max[i]) max[i] = el.bounds.max[i];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return any ? { min, max } : undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function nsKey(elementName: string, key: string): string {
|
|
157
|
+
return `${elementName}.${key}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function splitNs(key: string): [string, string] {
|
|
161
|
+
const dot = key.indexOf('.');
|
|
162
|
+
if (dot < 0) return ['', key];
|
|
163
|
+
return [key.slice(0, dot), key.slice(dot + 1)];
|
|
164
|
+
}
|