@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
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Lab DOM scaffolding helper.
3
+ *
4
+ * Eliminates per-project boilerplate: instead of every lab HTML page
5
+ * declaring its own <canvas>, <div id="boot">, <div id="hud">, knob
6
+ * editor pane + the supporting CSS, consumers call
7
+ *
8
+ * const dom = mountLabDom();
9
+ * runLab({ element, ...dom });
10
+ *
11
+ * The helper:
12
+ * - Injects `LAB_CSS` once into `<head>` (no-op on second call).
13
+ * - Creates the standard ids `runLab()` / `mountEditor()` expect:
14
+ * `boot`, `app`, `canvas`, `hud`, `lab-controls`.
15
+ * - Returns the ref bundle in the shape `runLab()` accepts.
16
+ *
17
+ * Existing DOM nodes with matching ids are reused (no duplication).
18
+ * This means a project that wants custom CSS for one element can still
19
+ * hand-write the HTML and skip this helper — the contract is unchanged.
20
+ */
21
+ import { LAB_CSS } from './css.js';
22
+ const STYLE_ID = 'triscope-lab-css';
23
+ function ensureStyleInjected() {
24
+ if (typeof document === 'undefined')
25
+ return;
26
+ if (document.getElementById(STYLE_ID))
27
+ return;
28
+ const style = document.createElement('style');
29
+ style.id = STYLE_ID;
30
+ style.textContent = LAB_CSS;
31
+ document.head.appendChild(style);
32
+ }
33
+ function getOrCreate(id, tag, parent) {
34
+ const existing = document.getElementById(id);
35
+ if (existing)
36
+ return existing;
37
+ const el = document.createElement(tag);
38
+ el.id = id;
39
+ parent.appendChild(el);
40
+ return el;
41
+ }
42
+ /**
43
+ * Build (or reuse) the standard lab DOM and return the refs `runLab()`
44
+ * needs. Idempotent: calling twice does not duplicate nodes.
45
+ */
46
+ export function mountLabDom() {
47
+ if (typeof document === 'undefined') {
48
+ throw new Error('mountLabDom() requires a browser DOM (document).');
49
+ }
50
+ ensureStyleInjected();
51
+ const body = document.body;
52
+ const boot = getOrCreate('boot', 'div', body);
53
+ if (!boot.textContent)
54
+ boot.textContent = 'Initialising Triscope · WebGPU...';
55
+ const app = getOrCreate('app', 'div', body);
56
+ // Canvas lives inside `#app` so the label-overlay positioning math in
57
+ // `runLab` (which is relative to `#app`) lines up with the canvas pixels.
58
+ let canvas = document.getElementById('canvas');
59
+ if (!canvas) {
60
+ canvas = document.createElement('canvas');
61
+ canvas.id = 'canvas';
62
+ app.appendChild(canvas);
63
+ }
64
+ const hud = getOrCreate('hud', 'div', body);
65
+ if (!hud.textContent)
66
+ hud.textContent = '- fps · WebGPU';
67
+ const editorContainer = getOrCreate('lab-controls', 'div', body);
68
+ return {
69
+ canvas,
70
+ hud,
71
+ boot,
72
+ labelContainer: app,
73
+ editorContainer,
74
+ };
75
+ }
76
+ //# sourceMappingURL=dom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom.js","sourceRoot":"","sources":["../../src/lab/dom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAUnC,MAAM,QAAQ,GAAG,kBAAkB,CAAC;AAEpC,SAAS,mBAAmB;IAC1B,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO;IAC5C,IAAI,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC;QAAE,OAAO;IAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,GAAG,QAAQ,CAAC;IACpB,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,WAAW,CAAwB,EAAU,EAAE,GAAW,EAAE,MAAmB;IACtF,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAa,CAAC;IACzD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAM,CAAC;IAC5C,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;IACX,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACvB,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,mBAAmB,EAAE,CAAC;IAEtB,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC3B,MAAM,IAAI,GAAG,WAAW,CAAiB,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9D,IAAI,CAAC,IAAI,CAAC,WAAW;QAAE,IAAI,CAAC,WAAW,GAAG,mCAAmC,CAAC;IAE9E,MAAM,GAAG,GAAG,WAAW,CAAiB,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAE5D,sEAAsE;IACtE,0EAA0E;IAC1E,IAAI,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAA6B,CAAC;IAC3E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,CAAC,EAAE,GAAG,QAAQ,CAAC;QACrB,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,GAAG,GAAG,WAAW,CAAiB,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC5D,IAAI,CAAC,GAAG,CAAC,WAAW;QAAE,GAAG,CAAC,WAAW,GAAG,gBAAgB,CAAC;IAEzD,MAAM,eAAe,GAAG,WAAW,CAAiB,cAAc,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAEjF,OAAO;QACL,MAAM;QACN,GAAG;QACH,IAAI;QACJ,cAAc,EAAE,GAAG;QACnB,eAAe;KAChB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Ring-buffered numeric probe used by the lab harness to summarize animated
3
+ * Element state without ever leaving the CPU (no GPU readback). The buffer
4
+ * holds the last `capacity` (sample, time) pairs; `stats()` returns aggregates
5
+ * including a zero-crossing-based estimate of dominant frequency.
6
+ *
7
+ * Extracted from `runLab` so the math is unit-testable in isolation.
8
+ */
9
+ export interface ProbeStats {
10
+ latest: number;
11
+ mean: number;
12
+ min: number;
13
+ max: number;
14
+ peakToPeak: number;
15
+ zeroCrossingsPerSec: number;
16
+ dominantFreqHz: number;
17
+ /** Last up-to-32 samples, in temporal order. */
18
+ samples: number[];
19
+ }
20
+ export declare class MotionProbeBuffer {
21
+ private readonly cap;
22
+ private readonly buf;
23
+ private readonly times;
24
+ private writeIdx;
25
+ private count;
26
+ constructor(capacity?: number);
27
+ push(value: number, time: number): void;
28
+ get size(): number;
29
+ get capacity(): number;
30
+ /** Returns (samples, times) in temporal order: oldest first, newest last. */
31
+ ordered(): {
32
+ samples: number[];
33
+ times: number[];
34
+ };
35
+ stats(): ProbeStats | null;
36
+ }
37
+ /**
38
+ * Pure stats kernel. `samples` and `times` must be the same length and in
39
+ * temporal order (oldest first). `times` is in seconds.
40
+ *
41
+ * Frequency estimate: count sign changes of (sample - mean); each full cycle
42
+ * has two zero-crossings, so divide by 2 to get cycles per duration, then by
43
+ * duration to get Hz. Robust for clean sinusoids; not a substitute for FFT on
44
+ * noisy / multi-frequency signals.
45
+ */
46
+ export declare function computeProbeStats(samples: number[], times: number[]): ProbeStats | null;
47
+ //# sourceMappingURL=motion-probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motion-probe.d.ts","sourceRoot":"","sources":["../src/motion-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,KAAK,CAAK;gBAEN,QAAQ,SAAM;IAO1B,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAOvC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,6EAA6E;IAC7E,OAAO,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE;IAmBjD,KAAK,IAAI,UAAU,GAAG,IAAI;CAK3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CAgDvF"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Ring-buffered numeric probe used by the lab harness to summarize animated
3
+ * Element state without ever leaving the CPU (no GPU readback). The buffer
4
+ * holds the last `capacity` (sample, time) pairs; `stats()` returns aggregates
5
+ * including a zero-crossing-based estimate of dominant frequency.
6
+ *
7
+ * Extracted from `runLab` so the math is unit-testable in isolation.
8
+ */
9
+ export class MotionProbeBuffer {
10
+ cap;
11
+ buf;
12
+ times;
13
+ writeIdx = 0;
14
+ count = 0;
15
+ constructor(capacity = 120) {
16
+ if (capacity <= 0)
17
+ throw new Error('MotionProbeBuffer capacity must be > 0');
18
+ this.cap = capacity;
19
+ this.buf = new Float32Array(capacity);
20
+ this.times = new Float32Array(capacity);
21
+ }
22
+ push(value, time) {
23
+ this.buf[this.writeIdx] = value;
24
+ this.times[this.writeIdx] = time;
25
+ this.writeIdx = (this.writeIdx + 1) % this.cap;
26
+ if (this.count < this.cap)
27
+ this.count += 1;
28
+ }
29
+ get size() {
30
+ return this.count;
31
+ }
32
+ get capacity() {
33
+ return this.cap;
34
+ }
35
+ /** Returns (samples, times) in temporal order: oldest first, newest last. */
36
+ ordered() {
37
+ const n = this.count;
38
+ const samples = new Array(n);
39
+ const times = new Array(n);
40
+ if (n < this.cap) {
41
+ for (let i = 0; i < n; i++) {
42
+ samples[i] = this.buf[i];
43
+ times[i] = this.times[i];
44
+ }
45
+ }
46
+ else {
47
+ for (let i = 0; i < this.cap; i++) {
48
+ const j = (this.writeIdx + i) % this.cap;
49
+ samples[i] = this.buf[j];
50
+ times[i] = this.times[j];
51
+ }
52
+ }
53
+ return { samples, times };
54
+ }
55
+ stats() {
56
+ if (this.count === 0)
57
+ return null;
58
+ const { samples, times } = this.ordered();
59
+ return computeProbeStats(samples, times);
60
+ }
61
+ }
62
+ /**
63
+ * Pure stats kernel. `samples` and `times` must be the same length and in
64
+ * temporal order (oldest first). `times` is in seconds.
65
+ *
66
+ * Frequency estimate: count sign changes of (sample - mean); each full cycle
67
+ * has two zero-crossings, so divide by 2 to get cycles per duration, then by
68
+ * duration to get Hz. Robust for clean sinusoids; not a substitute for FFT on
69
+ * noisy / multi-frequency signals.
70
+ */
71
+ export function computeProbeStats(samples, times) {
72
+ const n = samples.length;
73
+ if (n === 0)
74
+ return null;
75
+ if (times.length !== n) {
76
+ throw new Error(`computeProbeStats: samples (${n}) and times (${times.length}) length mismatch`);
77
+ }
78
+ let min = samples[0];
79
+ let max = samples[0];
80
+ let sum = 0;
81
+ for (let i = 0; i < n; i++) {
82
+ const v = samples[i];
83
+ if (v < min)
84
+ min = v;
85
+ if (v > max)
86
+ max = v;
87
+ sum += v;
88
+ }
89
+ const mean = sum / n;
90
+ // Sign-based crossing counter. Samples sitting exactly on the mean (within
91
+ // ZERO_EPS) carry the previous sign forward instead of triggering a false
92
+ // crossing — important because sinusoids sampled at frame-aligned times
93
+ // routinely produce values like sin(π) ≈ 1.2e-16 that confuse a strict
94
+ // <=0 / >=0 comparator.
95
+ const ZERO_EPS = 1e-9;
96
+ let crossings = 0;
97
+ let prevSign = 0;
98
+ for (let i = 0; i < n; i++) {
99
+ const v = samples[i] - mean;
100
+ const s = Math.abs(v) < ZERO_EPS ? 0 : v > 0 ? 1 : -1;
101
+ if (s !== 0) {
102
+ if (prevSign !== 0 && s !== prevSign)
103
+ crossings += 1;
104
+ prevSign = s;
105
+ }
106
+ }
107
+ const duration = Math.max(times[n - 1] - times[0], 1e-6);
108
+ const dominantFreqHz = crossings / 2 / duration;
109
+ const zeroCrossingsPerSec = crossings / duration;
110
+ const tail = samples.slice(Math.max(0, n - 32));
111
+ return {
112
+ latest: samples[n - 1],
113
+ mean: +mean.toFixed(4),
114
+ min: +min.toFixed(4),
115
+ max: +max.toFixed(4),
116
+ peakToPeak: +(max - min).toFixed(4),
117
+ zeroCrossingsPerSec: +zeroCrossingsPerSec.toFixed(2),
118
+ dominantFreqHz: +dominantFreqHz.toFixed(2),
119
+ samples: tail,
120
+ };
121
+ }
122
+ //# sourceMappingURL=motion-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motion-probe.js","sourceRoot":"","sources":["../src/motion-probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Mean Rec.709 luminance below which a rendered pane is considered "black" —
3
+ * i.e. the GPU drew nothing. Matches the literal the ocean-galleon smoke has
4
+ * used (smoke.mjs ~line 367); kept here as the single source of truth so the
5
+ * harness, the MCP server fallback, and the smoke all agree.
6
+ */
7
+ export declare const BLACK_FRAME_LUMINANCE = 0.004;
8
+ /**
9
+ * True when a camera's mean luminance indicates it rendered black. A non-finite
10
+ * luminance (probe unavailable) returns false — we only assert "black" on real
11
+ * evidence, never on a missing measurement, so it can't manufacture CI failures.
12
+ */
13
+ export declare function isBlackFrame(luminance: number, threshold?: number): boolean;
14
+ //# sourceMappingURL=probe-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe-utils.d.ts","sourceRoot":"","sources":["../src/probe-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,SAAwB,GAAG,OAAO,CAG1F"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Mean Rec.709 luminance below which a rendered pane is considered "black" —
3
+ * i.e. the GPU drew nothing. Matches the literal the ocean-galleon smoke has
4
+ * used (smoke.mjs ~line 367); kept here as the single source of truth so the
5
+ * harness, the MCP server fallback, and the smoke all agree.
6
+ */
7
+ export const BLACK_FRAME_LUMINANCE = 0.004;
8
+ /**
9
+ * True when a camera's mean luminance indicates it rendered black. A non-finite
10
+ * luminance (probe unavailable) returns false — we only assert "black" on real
11
+ * evidence, never on a missing measurement, so it can't manufacture CI failures.
12
+ */
13
+ export function isBlackFrame(luminance, threshold = BLACK_FRAME_LUMINANCE) {
14
+ if (!Number.isFinite(luminance))
15
+ return false;
16
+ return luminance < threshold;
17
+ }
18
+ //# sourceMappingURL=probe-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe-utils.js","sourceRoot":"","sources":["../src/probe-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC;AAE3C;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,SAAS,GAAG,qBAAqB;IAC/E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,SAAS,GAAG,SAAS,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { CameraSpec, Element } from './types.js';
2
+ export type Bounds = NonNullable<Element['bounds']>;
3
+ export declare const UNIT_BOUNDS: Bounds;
4
+ /** 4 fitted CameraSpecs (front/side/top/three-quarter) around `bounds`. */
5
+ export declare function autoCameras(bounds?: Bounds): Record<string, CameraSpec>;
6
+ //# sourceMappingURL=scene-cameras.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-cameras.d.ts","sourceRoot":"","sources":["../src/scene-cameras.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEtD,MAAM,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;AAEpD,eAAO,MAAM,WAAW,EAAE,MAA8C,CAAC;AAEzE,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAoBvE"}
@@ -0,0 +1,20 @@
1
+ export const UNIT_BOUNDS = { min: [-1, -1, -1], max: [1, 1, 1] };
2
+ /** 4 fitted CameraSpecs (front/side/top/three-quarter) around `bounds`. */
3
+ export function autoCameras(bounds) {
4
+ const b = bounds ?? UNIT_BOUNDS;
5
+ const c = [0, 1, 2].map((k) => (b.min[k] + b.max[k]) / 2);
6
+ const size = Math.max(b.max[0] - b.min[0], b.max[1] - b.min[1], b.max[2] - b.min[2], 1);
7
+ const d = size * 1.8;
8
+ const target = [c[0], c[1], c[2]];
9
+ return {
10
+ front: { position: [c[0], c[1], c[2] + d], target, fit: true },
11
+ side: { position: [c[0] + d, c[1], c[2]], target, fit: true },
12
+ top: { position: [c[0], c[1] + d, c[2]], target, fit: true },
13
+ 'three-quarter': {
14
+ position: [c[0] + d * 0.7, c[1] + d * 0.5, c[2] + d * 0.7],
15
+ target,
16
+ fit: true,
17
+ },
18
+ };
19
+ }
20
+ //# sourceMappingURL=scene-cameras.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-cameras.js","sourceRoot":"","sources":["../src/scene-cameras.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,WAAW,GAAW,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AAEzE,2EAA2E;AAC3E,MAAM,UAAU,WAAW,CAAC,MAAe;IACzC,MAAM,CAAC,GAAG,MAAM,IAAI,WAAW,CAAC;IAChC,MAAM,CAAC,GAA6B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAIjF,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACxF,MAAM,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC;IACrB,MAAM,MAAM,GAA6B,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,OAAO;QACL,KAAK,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE;QAC9D,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE;QAC7D,GAAG,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE;QAC5D,eAAe,EAAE;YACf,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;YAC1D,MAAM;YACN,GAAG,EAAE,IAAI;SACV;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ export interface CameraDelta {
2
+ position?: [number, number, number];
3
+ target?: [number, number, number];
4
+ fov?: number;
5
+ }
6
+ /** Apply a camera delta in place. Returns false only if `cam` is missing. */
7
+ export declare function applyCameraDelta(cam: any, delta: CameraDelta): boolean;
8
+ /**
9
+ * Show/hide an element in the live scene by toggling its root's `visible` flag
10
+ * — the design-doc "solo a track" model of runtime add/remove, with zero
11
+ * dispose/remount risk. For a composed scene the children are looked up by name
12
+ * via `handle.userData.childrenByName` (populated by composeElements); for a
13
+ * single-element lab the mounted element itself matches. Returns false if the
14
+ * name doesn't resolve. NOTE: only the element's `root` subtree toggles — an
15
+ * element that adds lights directly to the scene (not under root) should parent
16
+ * them under root to hide cleanly.
17
+ */
18
+ export declare function applyElementToggle(handle: any, mountedElement: {
19
+ name?: string;
20
+ } | undefined, name: string, enabled: boolean): boolean;
21
+ //# sourceMappingURL=scene-delta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-delta.d.ts","sourceRoot":"","sources":["../src/scene-delta.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAUD,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAqBtE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,GAAG,EACX,cAAc,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EAC7C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,GACf,OAAO,CAMT"}
@@ -0,0 +1,57 @@
1
+ // Pure Scene-Description-Layer (SDL) camera mutation — the data behind
2
+ // set_scene_param. Lets an agent repoint a camera (position/target/fov) live,
3
+ // without a .ts edit + reload. Duck-typed (no three import) so it's
4
+ // unit-testable; the harness passes the live PerspectiveCamera.
5
+ //
6
+ // CRITICAL: a target change must also update `userData.target`, which the
7
+ // harness's targetOf() reads for telemetry — otherwise the camera moves but
8
+ // `read_telemetry .cameras.<name>.target` keeps reporting the stale value.
9
+ function isVec3(v) {
10
+ return (Array.isArray(v) &&
11
+ v.length === 3 &&
12
+ v.every((n) => typeof n === 'number' && Number.isFinite(n)));
13
+ }
14
+ /** Apply a camera delta in place. Returns false only if `cam` is missing. */
15
+ export function applyCameraDelta(cam, delta) {
16
+ if (!cam)
17
+ return false;
18
+ let changed = false;
19
+ // Position first, so the subsequent lookAt() computes orientation from the
20
+ // new position.
21
+ if (isVec3(delta.position)) {
22
+ cam.position.set(delta.position[0], delta.position[1], delta.position[2]);
23
+ changed = true;
24
+ }
25
+ if (isVec3(delta.target)) {
26
+ cam.lookAt(delta.target[0], delta.target[1], delta.target[2]);
27
+ cam.userData = cam.userData ?? {};
28
+ cam.userData.target = [delta.target[0], delta.target[1], delta.target[2]];
29
+ changed = true;
30
+ }
31
+ if (typeof delta.fov === 'number' && Number.isFinite(delta.fov)) {
32
+ cam.fov = delta.fov;
33
+ changed = true;
34
+ }
35
+ if (changed && typeof cam.updateProjectionMatrix === 'function')
36
+ cam.updateProjectionMatrix();
37
+ return true;
38
+ }
39
+ /**
40
+ * Show/hide an element in the live scene by toggling its root's `visible` flag
41
+ * — the design-doc "solo a track" model of runtime add/remove, with zero
42
+ * dispose/remount risk. For a composed scene the children are looked up by name
43
+ * via `handle.userData.childrenByName` (populated by composeElements); for a
44
+ * single-element lab the mounted element itself matches. Returns false if the
45
+ * name doesn't resolve. NOTE: only the element's `root` subtree toggles — an
46
+ * element that adds lights directly to the scene (not under root) should parent
47
+ * them under root to hide cleanly.
48
+ */
49
+ export function applyElementToggle(handle, mountedElement, name, enabled) {
50
+ const child = handle?.userData?.childrenByName?.[name];
51
+ const target = child ?? (mountedElement?.name === name ? handle : null);
52
+ if (!target?.root)
53
+ return false;
54
+ target.root.visible = enabled;
55
+ return true;
56
+ }
57
+ //# sourceMappingURL=scene-delta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-delta.js","sourceRoot":"","sources":["../src/scene-delta.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,8EAA8E;AAC9E,oEAAoE;AACpE,gEAAgE;AAChE,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,2EAA2E;AAQ3E,SAAS,MAAM,CAAC,CAAU;IACxB,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAChB,CAAC,CAAC,MAAM,KAAK,CAAC;QACd,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAC5D,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,GAAQ,EAAE,KAAkB;IAC3D,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,2EAA2E;IAC3E,gBAAgB;IAChB,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1E,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC;QAClC,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1E,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;QACpB,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,IAAI,OAAO,GAAG,CAAC,sBAAsB,KAAK,UAAU;QAAE,GAAG,CAAC,sBAAsB,EAAE,CAAC;IAC9F,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAW,EACX,cAA6C,EAC7C,IAAY,EACZ,OAAgB;IAEhB,MAAM,KAAK,GAAG,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,KAAK,IAAI,CAAC,cAAc,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACxE,IAAI,CAAC,MAAM,EAAE,IAAI;QAAE,OAAO,KAAK,CAAC;IAChC,MAAM,CAAC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC9B,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Pure scene-graph serializer — the data behind `inspect_scene`.
3
+ *
4
+ * Gives an AI agent a single machine-readable view of "what is in this scene"
5
+ * (meshes/lights/groups, triangle counts, world positions, materials, and the
6
+ * file:line source tag) so it can diagnose a render bug in one call instead of
7
+ * issuing 10-30 screenshot probes. Operates on duck-typed Object3D-like nodes
8
+ * so it has no `three` dependency and is unit-testable in node; the harness
9
+ * passes a `three`-based `getWorldPosition`.
10
+ */
11
+ import type { SourceTag } from './source-tag.js';
12
+ export interface SceneNode {
13
+ uuid: string;
14
+ name: string;
15
+ type: string;
16
+ visible: boolean;
17
+ /** World-space position, rounded to 3 decimals. */
18
+ worldPosition: [number, number, number];
19
+ triangleCount: number;
20
+ materialKind?: string;
21
+ materialColor?: string;
22
+ uniformNames?: string[];
23
+ /** file:line where this object was added to the scene (from the source-tag patch). */
24
+ source?: SourceTag['source'];
25
+ /** Ancestor names, root-first (root itself excluded). */
26
+ parentChain: string[];
27
+ }
28
+ export interface LightNode {
29
+ uuid: string;
30
+ name: string;
31
+ /**
32
+ * Concrete light class, e.g. "DirectionalLight" / "HemisphereLight". Exposed
33
+ * as both `type` (consistent with SceneNode.type) and `lightType` (explicit).
34
+ */
35
+ type: string;
36
+ lightType: string;
37
+ visible: boolean;
38
+ worldPosition: [number, number, number];
39
+ intensity?: number;
40
+ /** Light color as #rrggbb. */
41
+ color?: string;
42
+ /** Hemisphere ground color, when applicable. */
43
+ groundColor?: string;
44
+ /** Point/spot falloff distance + decay; spot cone angle (radians). */
45
+ distance?: number;
46
+ decay?: number;
47
+ angle?: number;
48
+ source?: SourceTag['source'];
49
+ parentChain: string[];
50
+ }
51
+ export interface SerializeSceneResult {
52
+ nodes: SceneNode[];
53
+ /**
54
+ * Every light in the scene, ALWAYS included regardless of the `nodes` cap.
55
+ * Lights have 0 triangles, so the triangle-count sort + maxNodes truncation
56
+ * would otherwise drop them — yet they're a prime tuning target (a named
57
+ * light is read/writable via read_uniform/set_uniform "name.intensity").
58
+ */
59
+ lights: LightNode[];
60
+ /** Total nodes found (before maxNodes truncation). */
61
+ total: number;
62
+ truncated: boolean;
63
+ }
64
+ export interface SerializeSceneOptions {
65
+ /**
66
+ * Cap on returned nodes (default 120) to bound the payload — nodes are sorted
67
+ * by triangle count, so the default surfaces the heaviest/most-significant
68
+ * geometry first; `truncated`+`total` flag the rest (raise maxNodes to see it).
69
+ * A full 500-node dump of a busy scene was ~160 KB and blew the agent's token
70
+ * budget; 120 keeps the common "what's in this scene" answer compact.
71
+ */
72
+ maxNodes?: number;
73
+ /** Harness supplies a three-based world-position getter; defaults to local position. */
74
+ getWorldPosition?: (o: any) => [number, number, number];
75
+ }
76
+ export declare function serializeScene(root: any, opts?: SerializeSceneOptions): SerializeSceneResult;
77
+ export declare function triangleCount(o: any): number;
78
+ //# sourceMappingURL=scene-introspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-introspect.d.ts","sourceRoot":"","sources":["../src/scene-introspect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,mDAAmD;IACnD,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,sFAAsF;IACtF,MAAM,CAAC,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC7B,yDAAyD;IACzD,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC7B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB;;;;;OAKG;IACH,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACzD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,GAAE,qBAA0B,GAAG,oBAAoB,CAsBhG;AAkED,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,CAW5C"}