tryassay 0.21.1 → 0.22.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 (37) hide show
  1. package/README.md +4 -4
  2. package/demo/.claude/.truth_last_prompt +1 -0
  3. package/demo/.claude/truth_status +1 -0
  4. package/demo/css/style.css +1181 -0
  5. package/demo/data/demo-events.json +103 -0
  6. package/demo/index.html +222 -0
  7. package/demo/js/chat.js +292 -0
  8. package/demo/js/code-panel.js +206 -0
  9. package/demo/js/demo-mode.js +107 -0
  10. package/demo/js/orb.js +634 -0
  11. package/demo/js/question-cards.js +207 -0
  12. package/demo/js/sse-client.js +473 -0
  13. package/demo/js/state.js +162 -0
  14. package/demo/js/timeline.js +394 -0
  15. package/demo/js/voice.js +154 -0
  16. package/dist/api/server.d.ts +1 -0
  17. package/dist/api/server.js +65 -2
  18. package/dist/api/server.js.map +1 -1
  19. package/dist/cli.js +13 -0
  20. package/dist/cli.js.map +1 -1
  21. package/dist/commands/demo.d.ts +5 -0
  22. package/dist/commands/demo.js +107 -0
  23. package/dist/commands/demo.js.map +1 -0
  24. package/dist/commands/runtime.d.ts +4 -0
  25. package/dist/commands/runtime.js +50 -3
  26. package/dist/commands/runtime.js.map +1 -1
  27. package/dist/runtime/agents/planner-agent.d.ts +5 -2
  28. package/dist/runtime/agents/planner-agent.js +232 -1
  29. package/dist/runtime/agents/planner-agent.js.map +1 -1
  30. package/dist/runtime/app-create-orchestrator.d.ts +4 -0
  31. package/dist/runtime/app-create-orchestrator.js +151 -48
  32. package/dist/runtime/app-create-orchestrator.js.map +1 -1
  33. package/dist/runtime/dashboard-sync.d.ts +25 -0
  34. package/dist/runtime/dashboard-sync.js +169 -0
  35. package/dist/runtime/dashboard-sync.js.map +1 -0
  36. package/dist/runtime/types.d.ts +28 -0
  37. package/package.json +3 -2
package/demo/js/orb.js ADDED
@@ -0,0 +1,634 @@
1
+ // orb.js — Three.js orb with phase morphing, particles, effects + bloom
2
+ import * as THREE from 'three';
3
+ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
4
+ import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
5
+ import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
6
+
7
+ // ──────────────────────────────────────────────
8
+ // Phase color mapping
9
+ // ──────────────────────────────────────────────
10
+ const PHASE_COLORS = {
11
+ idle: new THREE.Color(0x1e3a5f),
12
+ initializing: new THREE.Color(0x1e3a5f),
13
+ planning: new THREE.Color(0x06b6d4),
14
+ verifying_plan: new THREE.Color(0xf59e0b),
15
+ requirements_refining: new THREE.Color(0xf59e0b),
16
+ scaffolding: new THREE.Color(0x22c55e),
17
+ building_feature: new THREE.Color(0x22c55e),
18
+ build_verifying: new THREE.Color(0xf59e0b),
19
+ build_repairing: new THREE.Color(0xef4444),
20
+ integration_verifying: new THREE.Color(0xf59e0b),
21
+ functional_testing: new THREE.Color(0xf59e0b),
22
+ functional_repairing: new THREE.Color(0xef4444),
23
+ cross_verifying: new THREE.Color(0xf59e0b),
24
+ finalizing: new THREE.Color(0x22c55e),
25
+ completed: new THREE.Color(0xffffff),
26
+ failed: new THREE.Color(0xef4444)
27
+ };
28
+
29
+ // ──────────────────────────────────────────────
30
+ // Layer 1: Phase Behavior Config
31
+ // ──────────────────────────────────────────────
32
+ const PHASE_BEHAVIORS = {
33
+ idle: {
34
+ targetRadius: 1.2, noiseAmplitude: 0.02, noiseFrequency: 0.8, noiseSpeed: 0.3,
35
+ solidOpacity: 0.85, transmission: 0.6, metalness: 0.1,
36
+ wireframeOpacity: 0.15, emissiveIntensity: 0.3,
37
+ shellMin: 1.5, shellMax: 3.5, particleSpeedMult: 1.0, convergence: 0.0,
38
+ rotationSpeed: 0.003, wobbleAmplitude: 0.1, bloomStrength: 1.2,
39
+ scanRing: false, connectionArcs: false,
40
+ },
41
+ planning: {
42
+ targetRadius: 1.45, noiseAmplitude: 0.15, noiseFrequency: 1.2, noiseSpeed: 0.8,
43
+ solidOpacity: 0.6, transmission: 0.7, metalness: 0.05,
44
+ wireframeOpacity: 0.25, emissiveIntensity: 0.5,
45
+ shellMin: 2.0, shellMax: 4.5, particleSpeedMult: 1.8, convergence: 0.0,
46
+ rotationSpeed: 0.006, wobbleAmplitude: 0.2, bloomStrength: 1.4,
47
+ scanRing: false, connectionArcs: false,
48
+ },
49
+ verifying_plan: {
50
+ targetRadius: 1.05, noiseAmplitude: 0.03, noiseFrequency: 0.6, noiseSpeed: 0.2,
51
+ solidOpacity: 0.9, transmission: 0.3, metalness: 0.4,
52
+ wireframeOpacity: 0.1, emissiveIntensity: 0.4,
53
+ shellMin: 1.3, shellMax: 2.5, particleSpeedMult: 0.6, convergence: 0.0,
54
+ rotationSpeed: 0.002, wobbleAmplitude: 0.05, bloomStrength: 1.3,
55
+ scanRing: true, connectionArcs: false,
56
+ },
57
+ scaffolding: {
58
+ targetRadius: 1.2, noiseAmplitude: 0.01, noiseFrequency: 0.5, noiseSpeed: 0.2,
59
+ solidOpacity: 0.3, transmission: 0.2, metalness: 0.0,
60
+ wireframeOpacity: 1.0, emissiveIntensity: 0.4,
61
+ shellMin: 1.5, shellMax: 3.0, particleSpeedMult: 1.0, convergence: 0.0,
62
+ rotationSpeed: 0.004, wobbleAmplitude: 0.08, bloomStrength: 1.1,
63
+ scanRing: false, connectionArcs: false,
64
+ },
65
+ building_feature: {
66
+ targetRadius: 1.3, noiseAmplitude: 0.05, noiseFrequency: 0.7, noiseSpeed: 0.4,
67
+ solidOpacity: 0.8, transmission: 0.4, metalness: 0.15,
68
+ wireframeOpacity: 0.2, emissiveIntensity: 0.45,
69
+ shellMin: 1.4, shellMax: 3.0, particleSpeedMult: 1.2, convergence: 0.3,
70
+ rotationSpeed: 0.004, wobbleAmplitude: 0.12, bloomStrength: 1.3,
71
+ scanRing: false, connectionArcs: false,
72
+ },
73
+ build_verifying: {
74
+ targetRadius: 1.1, noiseAmplitude: 0.02, noiseFrequency: 0.5, noiseSpeed: 0.15,
75
+ solidOpacity: 0.9, transmission: 0.3, metalness: 0.4,
76
+ wireframeOpacity: 0.1, emissiveIntensity: 0.4,
77
+ shellMin: 1.3, shellMax: 2.5, particleSpeedMult: 0.5, convergence: 0.0,
78
+ rotationSpeed: 0.002, wobbleAmplitude: 0.04, bloomStrength: 1.3,
79
+ scanRing: true, connectionArcs: false,
80
+ },
81
+ build_repairing: {
82
+ targetRadius: 1.2, noiseAmplitude: 0.2, noiseFrequency: 1.5, noiseSpeed: 1.0,
83
+ solidOpacity: 0.7, transmission: 0.4, metalness: 0.1,
84
+ wireframeOpacity: 0.4, emissiveIntensity: 0.6,
85
+ shellMin: 1.5, shellMax: 3.5, particleSpeedMult: 2.0, convergence: 0.0,
86
+ rotationSpeed: 0.008, wobbleAmplitude: 0.25, bloomStrength: 1.5,
87
+ scanRing: false, connectionArcs: false,
88
+ },
89
+ cross_verifying: {
90
+ targetRadius: 1.1, noiseAmplitude: 0.03, noiseFrequency: 0.6, noiseSpeed: 0.2,
91
+ solidOpacity: 0.75, transmission: 0.5, metalness: 0.3,
92
+ wireframeOpacity: 0.15, emissiveIntensity: 0.4,
93
+ shellMin: 1.3, shellMax: 2.8, particleSpeedMult: 0.8, convergence: 0.0,
94
+ rotationSpeed: 0.003, wobbleAmplitude: 0.06, bloomStrength: 1.3,
95
+ scanRing: false, connectionArcs: true,
96
+ },
97
+ finalizing: {
98
+ targetRadius: 1.2, noiseAmplitude: 0.0, noiseFrequency: 0.5, noiseSpeed: 0.1,
99
+ solidOpacity: 0.95, transmission: 0.2, metalness: 0.7,
100
+ wireframeOpacity: 0.05, emissiveIntensity: 0.5,
101
+ shellMin: 1.3, shellMax: 2.0, particleSpeedMult: 0.3, convergence: 0.0,
102
+ rotationSpeed: 0.001, wobbleAmplitude: 0.02, bloomStrength: 1.5,
103
+ scanRing: false, connectionArcs: false,
104
+ },
105
+ completed: {
106
+ targetRadius: 1.2, noiseAmplitude: 0.0, noiseFrequency: 0.5, noiseSpeed: 0.1,
107
+ solidOpacity: 0.95, transmission: 0.1, metalness: 0.3,
108
+ wireframeOpacity: 0.0, emissiveIntensity: 1.0,
109
+ shellMin: 1.3, shellMax: 2.0, particleSpeedMult: 0.4, convergence: 0.0,
110
+ rotationSpeed: 0.001, wobbleAmplitude: 0.03, bloomStrength: 2.0,
111
+ scanRing: false, connectionArcs: false,
112
+ },
113
+ failed: {
114
+ targetRadius: 1.3, noiseAmplitude: 0.35, noiseFrequency: 2.0, noiseSpeed: 1.5,
115
+ solidOpacity: 0.5, transmission: 0.3, metalness: 0.0,
116
+ wireframeOpacity: 0.6, emissiveIntensity: 0.8,
117
+ shellMin: 2.5, shellMax: 5.0, particleSpeedMult: 3.0, convergence: -0.5,
118
+ rotationSpeed: 0.01, wobbleAmplitude: 0.4, bloomStrength: 1.8,
119
+ scanRing: false, connectionArcs: false,
120
+ },
121
+ };
122
+
123
+ // Phase aliases — map runtime phases to visual configs
124
+ PHASE_BEHAVIORS.initializing = PHASE_BEHAVIORS.idle;
125
+ PHASE_BEHAVIORS.plan_questioning = PHASE_BEHAVIORS.planning;
126
+ PHASE_BEHAVIORS.plan_refining = PHASE_BEHAVIORS.verifying_plan;
127
+ PHASE_BEHAVIORS.requirements_refining = PHASE_BEHAVIORS.verifying_plan;
128
+ PHASE_BEHAVIORS.integration_verifying = PHASE_BEHAVIORS.build_verifying;
129
+ PHASE_BEHAVIORS.functional_testing = PHASE_BEHAVIORS.build_verifying;
130
+ PHASE_BEHAVIORS.functional_repairing = PHASE_BEHAVIORS.build_repairing;
131
+
132
+ // ──────────────────────────────────────────────
133
+ // Simplex 3D Noise (inline, no deps)
134
+ // ──────────────────────────────────────────────
135
+ const _PERM = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];
136
+ const _p = new Uint8Array(512);
137
+ for (let i = 0; i < 512; i++) _p[i] = _PERM[i & 255];
138
+
139
+ const _g3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0],[1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1],[0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]];
140
+
141
+ function simplex3(xin, yin, zin) {
142
+ const F3 = 1 / 3, G3 = 1 / 6;
143
+ const s = (xin + yin + zin) * F3;
144
+ const i = Math.floor(xin + s), j = Math.floor(yin + s), k = Math.floor(zin + s);
145
+ const t = (i + j + k) * G3;
146
+ const x0 = xin - (i - t), y0 = yin - (j - t), z0 = zin - (k - t);
147
+
148
+ let i1, j1, k1, i2, j2, k2;
149
+ if (x0 >= y0) {
150
+ if (y0 >= z0) { i1=1;j1=0;k1=0;i2=1;j2=1;k2=0; }
151
+ else if (x0 >= z0) { i1=1;j1=0;k1=0;i2=1;j2=0;k2=1; }
152
+ else { i1=0;j1=0;k1=1;i2=1;j2=0;k2=1; }
153
+ } else {
154
+ if (y0 < z0) { i1=0;j1=0;k1=1;i2=0;j2=1;k2=1; }
155
+ else if (x0 < z0) { i1=0;j1=1;k1=0;i2=0;j2=1;k2=1; }
156
+ else { i1=0;j1=1;k1=0;i2=1;j2=1;k2=0; }
157
+ }
158
+
159
+ const x1 = x0-i1+G3, y1 = y0-j1+G3, z1 = z0-k1+G3;
160
+ const x2 = x0-i2+2*G3, y2 = y0-j2+2*G3, z2 = z0-k2+2*G3;
161
+ const x3 = x0-1+3*G3, y3 = y0-1+3*G3, z3 = z0-1+3*G3;
162
+
163
+ const ii = i & 255, jj = j & 255, kk = k & 255;
164
+ let n = 0;
165
+
166
+ let t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
167
+ if (t0 > 0) { t0 *= t0; const g = _g3[_p[ii+_p[jj+_p[kk]]] % 12]; n += t0*t0*(g[0]*x0+g[1]*y0+g[2]*z0); }
168
+ let t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
169
+ if (t1 > 0) { t1 *= t1; const g = _g3[_p[ii+i1+_p[jj+j1+_p[kk+k1]]] % 12]; n += t1*t1*(g[0]*x1+g[1]*y1+g[2]*z1); }
170
+ let t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
171
+ if (t2 > 0) { t2 *= t2; const g = _g3[_p[ii+i2+_p[jj+j2+_p[kk+k2]]] % 12]; n += t2*t2*(g[0]*x2+g[1]*y2+g[2]*z2); }
172
+ let t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
173
+ if (t3 > 0) { t3 *= t3; const g = _g3[_p[ii+1+_p[jj+1+_p[kk+1]]] % 12]; n += t3*t3*(g[0]*x3+g[1]*y3+g[2]*z3); }
174
+
175
+ return 32 * n;
176
+ }
177
+
178
+ // ──────────────────────────────────────────────
179
+ // Module state
180
+ // ──────────────────────────────────────────────
181
+ let scene, camera, renderer, composer, bloomPass;
182
+ let orb, wireMat, particles, rings = [];
183
+ let scanRingMesh, arcGroup;
184
+ let targetColor = new THREE.Color(0x1e3a5f);
185
+ let currentColor = new THREE.Color(0x1e3a5f);
186
+ let time = 0;
187
+
188
+ // Morph state
189
+ let vertexDirections;
190
+ let currentBehavior = { ...PHASE_BEHAVIORS.idle };
191
+ let targetBehavior = PHASE_BEHAVIORS.idle;
192
+
193
+ // Effects
194
+ const microEffects = [];
195
+ const arcs = [];
196
+ let arcTimer = 0;
197
+ const ARC_SPAWN_INTERVAL = 0.5;
198
+ const ARC_LIFETIME = 2.0;
199
+
200
+ // ──────────────────────────────────────────────
201
+ // Helpers
202
+ // ──────────────────────────────────────────────
203
+ function randomDirection() {
204
+ const theta = Math.random() * Math.PI * 2;
205
+ const phi = Math.acos(2 * Math.random() - 1);
206
+ return new THREE.Vector3(
207
+ Math.sin(phi) * Math.cos(theta),
208
+ Math.sin(phi) * Math.sin(theta),
209
+ Math.cos(phi)
210
+ );
211
+ }
212
+
213
+ function lerpBehavior(current, target, t) {
214
+ for (const key in target) {
215
+ if (typeof target[key] === 'number') {
216
+ current[key] += (target[key] - current[key]) * t;
217
+ } else if (typeof target[key] === 'boolean') {
218
+ current[key] = target[key];
219
+ }
220
+ }
221
+ }
222
+
223
+ function spawnArc() {
224
+ const pos = orb.geometry.attributes.position;
225
+ const i1 = Math.floor(Math.random() * pos.count);
226
+ let i2 = Math.floor(Math.random() * pos.count);
227
+ if (i2 === i1) i2 = (i2 + 1) % pos.count;
228
+
229
+ const points = [
230
+ new THREE.Vector3(pos.getX(i1), pos.getY(i1), pos.getZ(i1)),
231
+ new THREE.Vector3(pos.getX(i2), pos.getY(i2), pos.getZ(i2)),
232
+ ];
233
+ const geo = new THREE.BufferGeometry().setFromPoints(points);
234
+ const mat = new THREE.LineBasicMaterial({
235
+ color: currentColor.clone(),
236
+ transparent: true,
237
+ opacity: 0,
238
+ blending: THREE.AdditiveBlending,
239
+ depthWrite: false,
240
+ });
241
+ const line = new THREE.Line(geo, mat);
242
+ arcGroup.add(line);
243
+ arcs.push({ line, v1: i1, v2: i2, age: 0 });
244
+ }
245
+
246
+ // ──────────────────────────────────────────────
247
+ // Init
248
+ // ──────────────────────────────────────────────
249
+ export function initOrb() {
250
+ const container = document.getElementById('orb-container');
251
+ if (!container) return;
252
+
253
+ const w = container.clientWidth;
254
+ const h = container.clientHeight;
255
+
256
+ scene = new THREE.Scene();
257
+
258
+ camera = new THREE.PerspectiveCamera(60, w / h, 0.1, 1000);
259
+ camera.position.z = 5;
260
+
261
+ renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
262
+ renderer.setSize(w, h);
263
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
264
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
265
+ renderer.toneMappingExposure = 1.2;
266
+ container.appendChild(renderer.domElement);
267
+
268
+ // Post-processing
269
+ composer = new EffectComposer(renderer);
270
+ composer.addPass(new RenderPass(scene, camera));
271
+
272
+ bloomPass = new UnrealBloomPass(
273
+ new THREE.Vector2(w, h),
274
+ 1.2, 0.5, 0.3
275
+ );
276
+ composer.addPass(bloomPass);
277
+
278
+ // Orb — translucent icosahedron
279
+ const orbGeo = new THREE.IcosahedronGeometry(1.2, 4);
280
+ const orbMat = new THREE.MeshPhysicalMaterial({
281
+ color: 0x1e3a5f,
282
+ metalness: 0.1,
283
+ roughness: 0.2,
284
+ transmission: 0.6,
285
+ thickness: 1.5,
286
+ clearcoat: 1.0,
287
+ clearcoatRoughness: 0.1,
288
+ emissive: 0x1e3a5f,
289
+ emissiveIntensity: 0.3,
290
+ transparent: true,
291
+ opacity: 0.85,
292
+ wireframe: false
293
+ });
294
+ orb = new THREE.Mesh(orbGeo, orbMat);
295
+ scene.add(orb);
296
+
297
+ // Snapshot vertex directions for morph engine
298
+ const posAttr = orbGeo.attributes.position;
299
+ const vCount = posAttr.count;
300
+ vertexDirections = new Float32Array(vCount * 3);
301
+ for (let vi = 0; vi < vCount; vi++) {
302
+ const vx = posAttr.getX(vi), vy = posAttr.getY(vi), vz = posAttr.getZ(vi);
303
+ const len = Math.sqrt(vx * vx + vy * vy + vz * vz);
304
+ vertexDirections[vi * 3] = vx / len;
305
+ vertexDirections[vi * 3 + 1] = vy / len;
306
+ vertexDirections[vi * 3 + 2] = vz / len;
307
+ }
308
+
309
+ // Wireframe overlay
310
+ const wireGeo = new THREE.IcosahedronGeometry(1.22, 2);
311
+ wireMat = new THREE.MeshBasicMaterial({
312
+ color: 0x1e3a5f,
313
+ wireframe: true,
314
+ transparent: true,
315
+ opacity: 0.15
316
+ });
317
+ orb.add(new THREE.Mesh(wireGeo, wireMat));
318
+
319
+ // Particle system — 200 orbiting particles
320
+ const particleCount = 200;
321
+ const particleGeo = new THREE.BufferGeometry();
322
+ const positions = new Float32Array(particleCount * 3);
323
+ const velocities = new Float32Array(particleCount * 3);
324
+
325
+ for (let i = 0; i < particleCount; i++) {
326
+ const theta = Math.random() * Math.PI * 2;
327
+ const phi = Math.acos(2 * Math.random() - 1);
328
+ const r = 1.8 + Math.random() * 1.2;
329
+ positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
330
+ positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
331
+ positions[i * 3 + 2] = r * Math.cos(phi);
332
+ velocities[i * 3] = (Math.random() - 0.5) * 0.01;
333
+ velocities[i * 3 + 1] = (Math.random() - 0.5) * 0.01;
334
+ velocities[i * 3 + 2] = (Math.random() - 0.5) * 0.01;
335
+ }
336
+
337
+ particleGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
338
+ const particleMat = new THREE.PointsMaterial({
339
+ color: 0x06b6d4,
340
+ size: 0.03,
341
+ transparent: true,
342
+ opacity: 0.6,
343
+ blending: THREE.AdditiveBlending,
344
+ depthWrite: false
345
+ });
346
+ particles = new THREE.Points(particleGeo, particleMat);
347
+ scene.add(particles);
348
+ particles.__velocities = velocities;
349
+
350
+ // Scan ring (Layer 3) — horizontal ring that sweeps through orb during verification
351
+ const scanGeo = new THREE.RingGeometry(0.8, 1.5, 64);
352
+ const scanMat = new THREE.MeshBasicMaterial({
353
+ color: 0xf59e0b,
354
+ transparent: true,
355
+ opacity: 0.0,
356
+ side: THREE.DoubleSide,
357
+ blending: THREE.AdditiveBlending,
358
+ depthWrite: false,
359
+ });
360
+ scanRingMesh = new THREE.Mesh(scanGeo, scanMat);
361
+ scanRingMesh.rotation.x = Math.PI / 2;
362
+ scanRingMesh.visible = false;
363
+ scene.add(scanRingMesh);
364
+
365
+ // Arc group (Layer 3) — child of orb so arcs rotate with it
366
+ arcGroup = new THREE.Group();
367
+ orb.add(arcGroup);
368
+
369
+ // Lights
370
+ const ambient = new THREE.AmbientLight(0x111122, 0.5);
371
+ scene.add(ambient);
372
+
373
+ const point = new THREE.PointLight(0x06b6d4, 2, 20);
374
+ point.position.set(0, 0, 3);
375
+ scene.add(point);
376
+
377
+ // Subscribe to state
378
+ AppState.on('phase_change', ({ to }) => {
379
+ targetColor = PHASE_COLORS[to] || PHASE_COLORS.idle;
380
+ targetBehavior = PHASE_BEHAVIORS[to] || PHASE_BEHAVIORS.idle;
381
+ updateAgentRings();
382
+ });
383
+
384
+ AppState.on('verification', (v) => {
385
+ if (v && v.result === 'fail') {
386
+ microEffects.push({ type: 'ripple', origin: randomDirection(), strength: 0.2, decay: 0.92 });
387
+ microEffects.push({ type: 'emissive', strength: 1.0, decay: 0.9 });
388
+ } else if (v) {
389
+ microEffects.push({ type: 'ripple', origin: randomDirection(), strength: 0.08, decay: 0.94 });
390
+ }
391
+ });
392
+
393
+ AppState.on('code_generated', () => {
394
+ microEffects.push({ type: 'convergence', strength: 0.5, decay: 0.92 });
395
+ microEffects.push({ type: 'emissive', strength: 0.3, decay: 0.93 });
396
+ });
397
+
398
+ AppState.on('task_update', (task) => {
399
+ if (task && task.status === 'completed') {
400
+ microEffects.push({ type: 'emissive', strength: 0.5, decay: 0.91 });
401
+ }
402
+ });
403
+
404
+ AppState.on('agent_status', () => updateAgentRings());
405
+
406
+ // Resize
407
+ window.addEventListener('resize', () => {
408
+ const w2 = container.clientWidth;
409
+ const h2 = container.clientHeight;
410
+ camera.aspect = w2 / h2;
411
+ camera.updateProjectionMatrix();
412
+ renderer.setSize(w2, h2);
413
+ composer.setSize(w2, h2);
414
+ });
415
+
416
+ animate();
417
+ }
418
+
419
+ // ──────────────────────────────────────────────
420
+ // Agent rings (unchanged)
421
+ // ──────────────────────────────────────────────
422
+ function updateAgentRings() {
423
+ const agents = AppState.get().agents.filter(a => a.status === 'active');
424
+
425
+ rings.forEach(r => scene.remove(r));
426
+ rings = [];
427
+
428
+ const ringColors = [0x06b6d4, 0x22c55e, 0xf59e0b, 0xa855f7];
429
+ agents.forEach((agent, i) => {
430
+ const ringGeo = new THREE.TorusGeometry(1.6 + i * 0.25, 0.008, 8, 64);
431
+ const ringMat = new THREE.MeshBasicMaterial({
432
+ color: ringColors[i % ringColors.length],
433
+ transparent: true,
434
+ opacity: 0.5
435
+ });
436
+ const ring = new THREE.Mesh(ringGeo, ringMat);
437
+
438
+ ring.rotation.x = Math.PI / 3 + i * 0.4;
439
+ ring.rotation.y = i * 0.7;
440
+ ring.__speed = 0.3 + i * 0.15;
441
+ ring.__axis = i % 2 === 0 ? 'y' : 'x';
442
+
443
+ scene.add(ring);
444
+ rings.push(ring);
445
+ });
446
+ }
447
+
448
+ // ──────────────────────────────────────────────
449
+ // Animate — all 4 layers
450
+ // ──────────────────────────────────────────────
451
+ function animate() {
452
+ requestAnimationFrame(animate);
453
+ time += 0.016;
454
+
455
+ const b = currentBehavior;
456
+
457
+ // ── Layer 2: Behavior lerp ──
458
+ lerpBehavior(currentBehavior, targetBehavior, 0.02);
459
+
460
+ // ── Color lerp ──
461
+ currentColor.lerp(targetColor, 0.03);
462
+ orb.material.color.copy(currentColor);
463
+ orb.material.emissive.copy(currentColor);
464
+
465
+ // ── Layer 2: Material morphing ──
466
+ let emissiveBoost = 0;
467
+ for (const fx of microEffects) {
468
+ if (fx.type === 'emissive') emissiveBoost += fx.strength;
469
+ }
470
+
471
+ orb.material.opacity = b.solidOpacity;
472
+ orb.material.transmission = b.transmission;
473
+ orb.material.metalness = b.metalness;
474
+ orb.material.emissiveIntensity = b.emissiveIntensity + Math.sin(time * 2) * 0.1 + emissiveBoost;
475
+
476
+ // Wireframe
477
+ wireMat.color.copy(currentColor);
478
+ wireMat.opacity = b.wireframeOpacity;
479
+
480
+ // Bloom
481
+ bloomPass.strength = b.bloomStrength;
482
+
483
+ // ── Layer 2: Vertex morphing ──
484
+ const pos = orb.geometry.attributes.position;
485
+ const noiseT = time * b.noiseSpeed;
486
+
487
+ for (let i = 0; i < pos.count; i++) {
488
+ const dx = vertexDirections[i * 3];
489
+ const dy = vertexDirections[i * 3 + 1];
490
+ const dz = vertexDirections[i * 3 + 2];
491
+
492
+ const n = simplex3(
493
+ dx * b.noiseFrequency + noiseT,
494
+ dy * b.noiseFrequency + noiseT * 0.7,
495
+ dz * b.noiseFrequency + noiseT * 0.5
496
+ );
497
+
498
+ // Micro-effect displacement (ripples)
499
+ let microDisp = 0;
500
+ for (const fx of microEffects) {
501
+ if (fx.type === 'ripple') {
502
+ const dot = dx * fx.origin.x + dy * fx.origin.y + dz * fx.origin.z;
503
+ const prox = Math.max(0, dot);
504
+ microDisp += prox * prox * fx.strength;
505
+ }
506
+ }
507
+
508
+ const r = b.targetRadius + n * b.noiseAmplitude + microDisp;
509
+ const tx = dx * r;
510
+ const ty = dy * r;
511
+ const tz = dz * r;
512
+
513
+ pos.setX(i, pos.getX(i) + (tx - pos.getX(i)) * 0.04);
514
+ pos.setY(i, pos.getY(i) + (ty - pos.getY(i)) * 0.04);
515
+ pos.setZ(i, pos.getZ(i) + (tz - pos.getZ(i)) * 0.04);
516
+ }
517
+ pos.needsUpdate = true;
518
+ orb.geometry.computeVertexNormals();
519
+
520
+ // ── Layer 2: Rotation ──
521
+ orb.rotation.y += b.rotationSpeed;
522
+ orb.rotation.x = Math.sin(time * 0.5) * b.wobbleAmplitude;
523
+
524
+ // ── Layer 2: Particle behavior ──
525
+ const ppos = particles.geometry.attributes.position;
526
+ const vel = particles.__velocities;
527
+
528
+ let convergenceBoost = 0;
529
+ for (const fx of microEffects) {
530
+ if (fx.type === 'convergence') convergenceBoost += fx.strength;
531
+ }
532
+ const totalConvergence = b.convergence + convergenceBoost;
533
+
534
+ for (let i = 0; i < ppos.count; i++) {
535
+ const x = ppos.getX(i);
536
+ const y = ppos.getY(i);
537
+ const z = ppos.getZ(i);
538
+ const dist = Math.sqrt(x * x + y * y + z * z);
539
+
540
+ const speed = 0.005 * b.particleSpeedMult / Math.max(dist, 0.5);
541
+ let nx = x * Math.cos(speed) - z * Math.sin(speed) + vel[i * 3];
542
+ let ny = y + vel[i * 3 + 1];
543
+ let nz = x * Math.sin(speed) + z * Math.cos(speed) + vel[i * 3 + 2];
544
+
545
+ // Convergence/divergence
546
+ if (totalConvergence !== 0 && dist > 0.1) {
547
+ const invDist = 1 / dist;
548
+ nx -= x * invDist * totalConvergence * 0.01;
549
+ ny -= y * invDist * totalConvergence * 0.01;
550
+ nz -= z * invDist * totalConvergence * 0.01;
551
+ }
552
+
553
+ ppos.setX(i, nx);
554
+ ppos.setY(i, ny);
555
+ ppos.setZ(i, nz);
556
+
557
+ // Shell bounds
558
+ const newDist = Math.sqrt(nx * nx + ny * ny + nz * nz);
559
+ if (newDist > b.shellMax || newDist < b.shellMin) {
560
+ const targetDist = (b.shellMin + b.shellMax) / 2;
561
+ const scale = targetDist / newDist;
562
+ ppos.setX(i, nx * scale);
563
+ ppos.setY(i, ny * scale);
564
+ ppos.setZ(i, nz * scale);
565
+ }
566
+ }
567
+ ppos.needsUpdate = true;
568
+ particles.material.color.copy(currentColor);
569
+
570
+ // ── Layer 3: Scan ring ──
571
+ if (b.scanRing) {
572
+ scanRingMesh.visible = true;
573
+ scanRingMesh.position.y = Math.sin(time * 2) * 1.3;
574
+ scanRingMesh.material.color.copy(currentColor);
575
+ scanRingMesh.material.opacity = 0.3 + Math.sin(time * 4) * 0.15;
576
+ } else {
577
+ if (scanRingMesh.material.opacity > 0.01) {
578
+ scanRingMesh.material.opacity *= 0.9;
579
+ } else {
580
+ scanRingMesh.visible = false;
581
+ }
582
+ }
583
+
584
+ // ── Layer 3: Connection arcs ──
585
+ if (b.connectionArcs) {
586
+ arcTimer += 0.016;
587
+ if (arcTimer >= ARC_SPAWN_INTERVAL) {
588
+ arcTimer -= ARC_SPAWN_INTERVAL;
589
+ spawnArc();
590
+ }
591
+ }
592
+
593
+ for (let i = arcs.length - 1; i >= 0; i--) {
594
+ arcs[i].age += 0.016;
595
+ const at = arcs[i].age / ARC_LIFETIME;
596
+ if (at >= 1.0) {
597
+ arcGroup.remove(arcs[i].line);
598
+ arcs[i].line.geometry.dispose();
599
+ arcs[i].line.material.dispose();
600
+ arcs.splice(i, 1);
601
+ } else {
602
+ // Update endpoints from current vertex positions
603
+ const lp = arcs[i].line.geometry.attributes.position;
604
+ lp.setXYZ(0, pos.getX(arcs[i].v1), pos.getY(arcs[i].v1), pos.getZ(arcs[i].v1));
605
+ lp.setXYZ(1, pos.getX(arcs[i].v2), pos.getY(arcs[i].v2), pos.getZ(arcs[i].v2));
606
+ lp.needsUpdate = true;
607
+ arcs[i].line.material.opacity = Math.sin(at * Math.PI) * 0.6;
608
+ arcs[i].line.material.color.copy(currentColor);
609
+ }
610
+ }
611
+
612
+ if (!b.connectionArcs && arcs.length === 0) {
613
+ arcTimer = 0;
614
+ }
615
+
616
+ // ── Layer 4: Micro-effect decay ──
617
+ for (let i = microEffects.length - 1; i >= 0; i--) {
618
+ microEffects[i].strength *= microEffects[i].decay;
619
+ if (microEffects[i].strength < 0.005) {
620
+ microEffects.splice(i, 1);
621
+ }
622
+ }
623
+
624
+ // ── Agent ring rotation ──
625
+ rings.forEach(ring => {
626
+ if (ring.__axis === 'y') {
627
+ ring.rotation.y += ring.__speed * 0.016;
628
+ } else {
629
+ ring.rotation.x += ring.__speed * 0.016;
630
+ }
631
+ });
632
+
633
+ composer.render();
634
+ }