helixmind 0.2.15 → 0.2.17

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.

Potentially problematic release.


This version of helixmind might be problematic. Click here for more details.

@@ -595,21 +595,23 @@ scene.fog = new THREE.FogExp2(0x050510, 0.00005); // Reduced fog for clearer vis
595
595
  const camera = new THREE.PerspectiveCamera(55, innerWidth / innerHeight, 0.1, 10000);
596
596
  camera.position.set(0, 200, 1200); // Centered on force-directed graph
597
597
 
598
- const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
598
+ const renderer = new THREE.WebGLRenderer({ antialias: false, alpha: false, powerPreference: 'high-performance' });
599
599
  renderer.setSize(innerWidth, innerHeight);
600
- renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
600
+ renderer.setPixelRatio(1);
601
601
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
602
602
  renderer.toneMappingExposure = 1.2;
603
603
  document.body.prepend(renderer.domElement);
604
604
 
605
- // =========== POST-PROCESSING (Bloom) ===========
605
+ // =========== POST-PROCESSING (Bloom at half resolution) ===========
606
+ const bloomW = Math.round(innerWidth / 2);
607
+ const bloomH = Math.round(innerHeight / 2);
606
608
  const composer = new EffectComposer(renderer);
607
609
  composer.addPass(new RenderPass(scene, camera));
608
610
  const bloomPass = new UnrealBloomPass(
609
- new THREE.Vector2(innerWidth, innerHeight),
610
- 0.8, // strength
611
- 0.4, // radius
612
- 0.6 // threshold
611
+ new THREE.Vector2(bloomW, bloomH),
612
+ 0.6, // strength (reduced)
613
+ 0.3, // radius
614
+ 0.7 // threshold (higher = fewer objects bloom)
613
615
  );
614
616
  composer.addPass(bloomPass);
615
617
 
@@ -636,9 +638,9 @@ const fillLight = new THREE.PointLight(0x00FF88, 0.4, 3000);
636
638
  fillLight.position.set(400, 100, -300);
637
639
  scene.add(fillLight);
638
640
 
639
- // =========== BACKGROUND PARTICLES ===========
641
+ // =========== BACKGROUND PARTICLES (reduced count) ===========
640
642
  const starGeo = new THREE.BufferGeometry();
641
- const starCount = 2000;
643
+ const starCount = 800;
642
644
  const starPos = new Float32Array(starCount * 3);
643
645
  for (let i = 0; i < starCount * 3; i++) starPos[i] = (Math.random() - 0.5) * 8000;
644
646
  starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
@@ -692,13 +694,21 @@ class BrainManager {
692
694
  // Connected nodes will pull together, unconnected repel — organic clusters form naturally
693
695
  const SPREAD = 600; // Initial scatter radius
694
696
 
697
+ // Shared geometries — reuse instead of creating per node
698
+ const sharedIcoGeo = new THREE.IcosahedronGeometry(1, 1); // unit size, detail 1 (low poly)
699
+ const sharedOctGeo = new THREE.OctahedronGeometry(1, 1);
700
+ const sharedHitGeo = new THREE.SphereGeometry(1, 6, 4); // minimal segments
701
+ const sharedRingGeo = new THREE.RingGeometry(1, 1.6, 16); // reduced segments
702
+ const sharedWaveGeo = new THREE.RingGeometry(1, 1.1, 16);
703
+ const sharedWireGeo = new THREE.OctahedronGeometry(1, 0);
704
+
695
705
  data.nodes.forEach((node, i) => {
696
706
  const isWeb = node.level === 6;
697
707
 
698
708
  // Random spherical distribution as starting positions
699
- const phi = Math.acos(2 * Math.random() - 1); // polar angle
700
- const theta = Math.random() * Math.PI * 2; // azimuthal angle
701
- const r = SPREAD * (0.3 + Math.random() * 0.7); // random radius
709
+ const phi = Math.acos(2 * Math.random() - 1);
710
+ const theta = Math.random() * Math.PI * 2;
711
+ const r = SPREAD * (0.3 + Math.random() * 0.7);
702
712
  const x = r * Math.sin(phi) * Math.cos(theta);
703
713
  const y = r * Math.sin(phi) * Math.sin(theta);
704
714
  const z = r * Math.cos(phi);
@@ -710,66 +720,65 @@ class BrainManager {
710
720
  const size = LEVEL_SIZES[node.level] || 3;
711
721
  const color = new THREE.Color(LEVEL_COLORS[node.level] || 0x00FFFF);
712
722
 
713
- // Core geometry: Octahedron for L6 Web, Icosahedron for others
714
- const coreGeo = isWeb
715
- ? new THREE.OctahedronGeometry(size, 2)
716
- : new THREE.IcosahedronGeometry(size, 3);
723
+ // Core mesh shared geometry, scaled per node
717
724
  const coreMat = new THREE.MeshStandardMaterial({
718
725
  color, emissive: color, emissiveIntensity: LEVEL_GLOW[node.level] || 0.6,
719
726
  roughness: isWeb ? 0.1 : 0.15,
720
727
  metalness: isWeb ? 0.9 : 0.7,
721
728
  transparent: true, opacity: 0.95,
722
729
  });
723
- const coreMesh = new THREE.Mesh(coreGeo, coreMat);
730
+ const coreMesh = new THREE.Mesh(isWeb ? sharedOctGeo : sharedIcoGeo, coreMat);
731
+ coreMesh.scale.set(size, size, size);
724
732
  coreMesh.position.copy(node._pos);
725
733
  coreMesh.userData = node;
726
734
  coreMesh.userData._color = color.clone();
735
+ coreMesh.userData._baseSize = size;
727
736
  this.nodeGroup.add(coreMesh);
728
737
  this.nodeMeshes.push(coreMesh);
729
738
 
730
- // Invisible larger sphere for easier click hit detection
731
- const hitGeo = new THREE.SphereGeometry(size * 3, 8, 6);
739
+ // Hit detection shared geometry, scaled
732
740
  const hitMat = new THREE.MeshBasicMaterial({ visible: false });
733
- const hitMesh = new THREE.Mesh(hitGeo, hitMat);
741
+ const hitMesh = new THREE.Mesh(sharedHitGeo, hitMat);
742
+ hitMesh.scale.set(size * 3, size * 3, size * 3);
734
743
  hitMesh.position.copy(node._pos);
735
744
  hitMesh.userData = node;
736
745
  hitMesh.userData._coreMesh = coreMesh;
737
746
  this.nodeGroup.add(hitMesh);
738
747
  this.nodeHitMeshes.push(hitMesh);
739
748
 
740
- // Outer glow ring (bigger for L6)
749
+ // Glow ring shared geometry, scaled
741
750
  const ringScale = isWeb ? 2.8 : 1.4;
742
- const ringOuter = isWeb ? 3.5 : 2.2;
743
- const ringGeo = new THREE.RingGeometry(size * ringScale, size * ringOuter, 32);
744
751
  const ringMat = new THREE.MeshBasicMaterial({
745
752
  color, transparent: true, opacity: isWeb ? 0.12 : 0.08, side: THREE.DoubleSide,
746
753
  });
747
- const ring = new THREE.Mesh(ringGeo, ringMat);
754
+ const ring = new THREE.Mesh(sharedRingGeo, ringMat);
755
+ ring.scale.set(size * ringScale, size * ringScale, size * ringScale);
748
756
  ring.position.copy(node._pos);
749
757
  ring.userData = { nodeIndex: i };
750
758
  this.nodeGroup.add(ring);
751
759
  this.glowRings.push(ring);
752
760
 
753
- // L6 Web nodes get extra "signal wave" rings (wifi-like emanation)
761
+ // L6 Web nodes: signal wave rings (reduced to 2) + wireframe shell
754
762
  if (isWeb) {
755
- for (let w = 0; w < 3; w++) {
756
- const waveGeo = new THREE.RingGeometry(size * (3 + w * 2), size * (3.3 + w * 2), 32);
763
+ for (let w = 0; w < 2; w++) {
764
+ const waveScale = size * (3 + w * 2);
757
765
  const waveMat = new THREE.MeshBasicMaterial({
758
766
  color: 0xFFAA00, transparent: true, opacity: 0, side: THREE.DoubleSide,
759
767
  });
760
- const waveMesh = new THREE.Mesh(waveGeo, waveMat);
768
+ const waveMesh = new THREE.Mesh(sharedWaveGeo, waveMat);
769
+ waveMesh.scale.set(waveScale, waveScale, waveScale);
761
770
  waveMesh.position.copy(node._pos);
762
771
  waveMesh.userData = { nodeIndex: i, waveIndex: w, _startTime: 0 };
763
772
  this.nodeGroup.add(waveMesh);
764
773
  this.webSignalRings.push(waveMesh);
765
774
  }
766
775
 
767
- // Wireframe outer shell for L6
768
- const wireGeo = new THREE.OctahedronGeometry(size * 1.8, 1);
769
776
  const wireMat = new THREE.MeshBasicMaterial({
770
777
  color: 0xFFAA00, wireframe: true, transparent: true, opacity: 0.15,
771
778
  });
772
- const wireMesh = new THREE.Mesh(wireGeo, wireMat);
779
+ const wireMesh = new THREE.Mesh(sharedWireGeo, wireMat);
780
+ const ws = size * 1.8;
781
+ wireMesh.scale.set(ws, ws, ws);
773
782
  wireMesh.position.copy(node._pos);
774
783
  wireMesh.userData = { nodeIndex: i, isWireShell: true };
775
784
  this.nodeGroup.add(wireMesh);
@@ -835,124 +844,129 @@ class BrainManager {
835
844
  simulateForces() {
836
845
  if (!this.simActive) return;
837
846
  if (this.simSteps <= 0) { this.simActive = false; return; }
838
- this.simSteps--;
839
847
 
848
+ // Run multiple sim steps per frame to converge faster (less total frames with sim overhead)
849
+ const stepsPerFrame = Math.min(4, this.simSteps);
840
850
  const nodes = BRAIN_DATA.nodes;
841
- const repulsion = 18000; // Nodes push each other apart
842
- const edgeAttraction = 0.003; // Connected nodes pull together (strong)
843
- const edgeIdealLen = 80; // Ideal edge length — spring rest length
851
+ const n = nodes.length;
852
+ const repulsion = 18000;
853
+ const edgeAttraction = 0.003;
854
+ const edgeIdealLen = 80;
844
855
  const damping = 0.82;
845
- const centerPull = 0.0003; // Gentle pull to keep graph centered
856
+ const centerPull = 0.0003;
846
857
  const cutoff = 800;
847
-
848
- // Spatial grid for O(n) repulsion instead of O(n²)
858
+ const cutoff2 = cutoff * cutoff;
849
859
  const cellSize = cutoff;
850
- const grid = new Map();
851
- for (let i = 0; i < nodes.length; i++) {
852
- const cx = Math.floor(nodes[i]._pos.x / cellSize);
853
- const cy = Math.floor(nodes[i]._pos.y / cellSize);
854
- const cz = Math.floor(nodes[i]._pos.z / cellSize);
855
- const key = cx + ',' + cy + ',' + cz;
856
- if (!grid.has(key)) grid.set(key, []);
857
- grid.get(key).push(i);
858
- }
859
860
 
860
- // Repulsion all nodes push each other away (equal in all axes)
861
- for (const [key, cell] of grid) {
862
- const [cx, cy, cz] = key.split(',').map(Number);
863
- for (let dx = -1; dx <= 1; dx++) {
864
- for (let dy = -1; dy <= 1; dy++) {
865
- for (let dz = -1; dz <= 1; dz++) {
866
- const nk = (cx+dx) + ',' + (cy+dy) + ',' + (cz+dz);
867
- const neighbor = grid.get(nk);
868
- if (!neighbor) continue;
869
- for (const i of cell) {
870
- for (const j of neighbor) {
871
- if (j <= i) continue;
872
- const ddx = nodes[i]._pos.x - nodes[j]._pos.x;
873
- const ddy = nodes[i]._pos.y - nodes[j]._pos.y;
874
- const ddz = nodes[i]._pos.z - nodes[j]._pos.z;
875
- const dist2 = ddx*ddx + ddy*ddy + ddz*ddz;
876
- if (dist2 > cutoff * cutoff) continue;
877
- const dist = Math.sqrt(dist2) + 1;
878
- const force = repulsion / (dist * dist);
879
- const fx = (ddx / dist) * force;
880
- const fy = (ddy / dist) * force;
881
- const fz = (ddz / dist) * force;
882
- nodes[i]._vel.x += fx; nodes[i]._vel.y += fy; nodes[i]._vel.z += fz;
883
- nodes[j]._vel.x -= fx; nodes[j]._vel.y -= fy; nodes[j]._vel.z -= fz;
884
- }
885
- }
861
+ for (let step = 0; step < stepsPerFrame; step++) {
862
+ this.simSteps--;
863
+
864
+ // Spatial grid reuse object for less GC
865
+ const grid = new Map();
866
+ for (let i = 0; i < n; i++) {
867
+ const p = nodes[i]._pos;
868
+ const key = (Math.floor(p.x / cellSize) * 73856093 ^ Math.floor(p.y / cellSize) * 19349663 ^ Math.floor(p.z / cellSize) * 83492791) | 0;
869
+ const arr = grid.get(key);
870
+ if (arr) arr.push(i); else grid.set(key, [i]);
871
+ }
872
+
873
+ // Repulsion
874
+ for (const [, cell] of grid) {
875
+ const len = cell.length;
876
+ for (let a = 0; a < len; a++) {
877
+ const i = cell[a];
878
+ const pi = nodes[i]._pos;
879
+ const vi = nodes[i]._vel;
880
+ for (let b = a + 1; b < len; b++) {
881
+ const j = cell[b];
882
+ const pj = nodes[j]._pos;
883
+ const ddx = pi.x - pj.x;
884
+ const ddy = pi.y - pj.y;
885
+ const ddz = pi.z - pj.z;
886
+ const dist2 = ddx*ddx + ddy*ddy + ddz*ddz;
887
+ if (dist2 > cutoff2) continue;
888
+ const dist = Math.sqrt(dist2) + 1;
889
+ const force = repulsion / (dist * dist);
890
+ const fx = (ddx / dist) * force;
891
+ const fy = (ddy / dist) * force;
892
+ const fz = (ddz / dist) * force;
893
+ vi.x += fx; vi.y += fy; vi.z += fz;
894
+ nodes[j]._vel.x -= fx; nodes[j]._vel.y -= fy; nodes[j]._vel.z -= fz;
886
895
  }
887
896
  }
888
897
  }
889
- }
890
-
891
- // Edge spring forces — connected nodes attract with ideal distance
892
- for (const edge of BRAIN_DATA.edges) {
893
- const s = this.nodeMap.get(edge.source);
894
- const t = this.nodeMap.get(edge.target);
895
- if (!s || !t) continue;
896
- const dx = t._pos.x - s._pos.x;
897
- const dy = t._pos.y - s._pos.y;
898
- const dz = t._pos.z - s._pos.z;
899
- const dist = Math.sqrt(dx*dx + dy*dy + dz*dz) + 0.01;
900
- // Spring: pull if > ideal, push if < ideal
901
- const displacement = dist - edgeIdealLen;
902
- const strength = edgeAttraction * displacement * (0.5 + edge.weight * 0.5);
903
- const fx = (dx / dist) * strength;
904
- const fy = (dy / dist) * strength;
905
- const fz = (dz / dist) * strength;
906
- s._vel.x += fx; s._vel.y += fy; s._vel.z += fz;
907
- t._vel.x -= fx; t._vel.y -= fy; t._vel.z -= fz;
908
- }
909
898
 
910
- // Apply velocity + gentle centering
911
- for (const node of nodes) {
912
- // Gentle pull toward origin so graph doesn't drift away
913
- node._vel.x -= node._pos.x * centerPull;
914
- node._vel.y -= node._pos.y * centerPull;
915
- node._vel.z -= node._pos.z * centerPull;
899
+ // Edge springs
900
+ const edges = BRAIN_DATA.edges;
901
+ for (let e = 0; e < edges.length; e++) {
902
+ const edge = edges[e];
903
+ const s = this.nodeMap.get(edge.source);
904
+ const t = this.nodeMap.get(edge.target);
905
+ if (!s || !t) continue;
906
+ const dx = t._pos.x - s._pos.x;
907
+ const dy = t._pos.y - s._pos.y;
908
+ const dz = t._pos.z - s._pos.z;
909
+ const dist = Math.sqrt(dx*dx + dy*dy + dz*dz) + 0.01;
910
+ const strength = edgeAttraction * (dist - edgeIdealLen) * (0.5 + edge.weight * 0.5);
911
+ const fx = (dx / dist) * strength;
912
+ const fy = (dy / dist) * strength;
913
+ const fz = (dz / dist) * strength;
914
+ s._vel.x += fx; s._vel.y += fy; s._vel.z += fz;
915
+ t._vel.x -= fx; t._vel.y -= fy; t._vel.z -= fz;
916
+ }
916
917
 
917
- node._vel.multiplyScalar(damping);
918
- node._pos.add(node._vel);
918
+ // Apply velocity + centering
919
+ for (let i = 0; i < n; i++) {
920
+ const node = nodes[i];
921
+ node._vel.x -= node._pos.x * centerPull;
922
+ node._vel.y -= node._pos.y * centerPull;
923
+ node._vel.z -= node._pos.z * centerPull;
924
+ node._vel.x *= damping; node._vel.y *= damping; node._vel.z *= damping;
925
+ node._pos.x += node._vel.x; node._pos.y += node._vel.y; node._pos.z += node._vel.z;
926
+ }
919
927
  }
920
928
 
921
- // Sync meshes
922
- this.nodeMeshes.forEach((mesh, i) => { mesh.position.copy(nodes[i]._pos); });
923
- this.nodeHitMeshes.forEach((mesh, i) => { mesh.position.copy(nodes[i]._pos); });
924
- this.glowRings.forEach((ring, i) => {
925
- const ni = ring.userData.nodeIndex;
926
- if (ni < nodes.length) ring.position.copy(nodes[ni]._pos);
927
- ring.lookAt(camera.position);
928
- });
929
+ // Sync meshes (once per frame, not per sim step)
930
+ for (let i = 0; i < n; i++) {
931
+ const p = nodes[i]._pos;
932
+ this.nodeMeshes[i].position.set(p.x, p.y, p.z);
933
+ this.nodeHitMeshes[i].position.set(p.x, p.y, p.z);
934
+ }
935
+ const rings = this.glowRings;
936
+ for (let i = 0; i < rings.length; i++) {
937
+ const ni = rings[i].userData.nodeIndex;
938
+ if (ni < n) rings[i].position.copy(nodes[ni]._pos);
939
+ }
929
940
 
930
- // Update edge positions
931
- this.edgeLines.forEach(line => {
932
- const e = line.userData;
941
+ // Update edges
942
+ const lines = this.edgeLines;
943
+ for (let i = 0; i < lines.length; i++) {
944
+ const e = lines[i].userData;
933
945
  const s = this.nodeMap.get(e.source);
934
946
  const t = this.nodeMap.get(e.target);
935
947
  if (s && t) {
936
- const pos = line.geometry.attributes.position;
948
+ const pos = lines[i].geometry.attributes.position;
937
949
  pos.setXYZ(0, s._pos.x, s._pos.y, s._pos.z);
938
950
  pos.setXYZ(1, t._pos.x, t._pos.y, t._pos.z);
939
951
  pos.needsUpdate = true;
940
952
  }
941
- });
953
+ }
942
954
  }
943
955
  }
944
956
 
945
957
  const brain = new BrainManager();
946
958
  brain.loadData(BRAIN_DATA);
947
959
 
948
- // =========== RAYCASTER & INTERACTION ===========
960
+ // =========== RAYCASTER & INTERACTION (throttled) ===========
949
961
  const raycaster = new THREE.Raycaster();
950
962
  const mouse = new THREE.Vector2();
951
963
  let hoveredMesh = null;
952
964
  let selectedMesh = null;
953
965
  let hoverScale = 1;
966
+ let lastRayTime = 0;
967
+ let pendingMouseEvent = null;
954
968
 
955
- renderer.domElement.addEventListener('mousemove', (e) => {
969
+ function doRaycast(e) {
956
970
  mouse.x = (e.clientX / innerWidth) * 2 - 1;
957
971
  mouse.y = -(e.clientY / innerHeight) * 2 + 1;
958
972
 
@@ -966,18 +980,14 @@ renderer.domElement.addEventListener('mousemove', (e) => {
966
980
  const node = hitMesh.userData;
967
981
  const coreMesh = hitMesh.userData._coreMesh;
968
982
 
969
- // Unhover previous
970
983
  if (hoveredMesh && hoveredMesh !== coreMesh) {
971
984
  hoveredMesh.material.emissiveIntensity = LEVEL_GLOW[hoveredMesh.userData.level] || 0.6;
972
985
  }
973
986
 
974
987
  hoveredMesh = coreMesh;
975
988
  renderer.domElement.style.cursor = 'pointer';
976
-
977
- // Hover glow
978
989
  coreMesh.material.emissiveIntensity = 1.8;
979
990
 
980
- // Tooltip
981
991
  tooltip.style.display = 'block';
982
992
  const tx = Math.min(e.clientX + 18, innerWidth - 330);
983
993
  const ty = Math.min(e.clientY + 18, innerHeight - 100);
@@ -997,6 +1007,16 @@ renderer.domElement.addEventListener('mousemove', (e) => {
997
1007
  renderer.domElement.style.cursor = 'default';
998
1008
  tooltip.style.display = 'none';
999
1009
  }
1010
+ }
1011
+
1012
+ renderer.domElement.addEventListener('mousemove', (e) => {
1013
+ // Throttle raycasting to max every 60ms (~16 checks/sec instead of 100+)
1014
+ pendingMouseEvent = e;
1015
+ const now = performance.now();
1016
+ if (now - lastRayTime < 60) return;
1017
+ lastRayTime = now;
1018
+ doRaycast(e);
1019
+ pendingMouseEvent = null;
1000
1020
  });
1001
1021
 
1002
1022
  renderer.domElement.addEventListener('click', () => {
@@ -1214,98 +1234,97 @@ document.querySelectorAll('[data-edge]').forEach(btn => {
1214
1234
  });
1215
1235
  });
1216
1236
 
1217
- // =========== ANIMATION LOOP ===========
1237
+ // =========== ANIMATION LOOP (optimized) ===========
1218
1238
  let frameCount = 0;
1239
+ let fpsFrames = 0;
1219
1240
  let lastFpsTime = performance.now();
1220
1241
 
1221
1242
  function animate() {
1222
1243
  requestAnimationFrame(animate);
1244
+
1245
+ // Force simulation — only when active, skip when converged
1223
1246
  brain.simulateForces();
1224
1247
  controls.update();
1225
1248
 
1226
1249
  const now = Date.now();
1227
1250
 
1228
- // Node breathing pulse + scale breathing + L6 rotation
1229
- brain.nodeMeshes.forEach((m, i) => {
1230
- if (selectedMesh && selectedMesh !== m) return;
1231
- if (m.material.emissiveIntensity > 1.5) return; // hovered or search-highlighted
1232
-
1233
- const lvl = m.userData.level;
1234
- const base = LEVEL_GLOW[lvl] || 0.6;
1235
- const speed = 0.002 + (lvl * 0.0004); // Higher levels pulse faster
1236
-
1237
- if (lvl === 6) {
1238
- // L6 Web nodes: slow rotation + stronger pulse + color shift
1239
- m.rotation.y += 0.005;
1240
- m.rotation.x += 0.002;
1241
- const t = Math.sin(now * 0.003 + i * 0.5);
1242
- m.material.emissiveIntensity = base + t * 0.35;
1243
- // Color shift yellow → cyan
1244
- const r = 1.0 - t * 0.5;
1245
- const g = 0.67 + t * 0.16;
1246
- const b = t * 0.5;
1247
- m.material.emissive.setRGB(r, g, b);
1248
- } else {
1249
- const pulse = base + Math.sin(now * speed + i * 0.7) * 0.15;
1250
- m.material.emissiveIntensity = pulse;
1251
- // Subtle scale breathing — nodes "breathe"
1252
- const s = 1 + Math.sin(now * 0.0015 + i * 1.1) * 0.04;
1253
- m.scale.set(s, s, s);
1254
- }
1255
- });
1256
-
1257
- // Glow rings face camera + gentle pulse (lookAt every 3rd frame for performance)
1251
+ // Node animations every 3rd frame (imperceptible difference, 3x less work)
1258
1252
  if (frameCount % 3 === 0) {
1259
- brain.glowRings.forEach((r, i) => {
1260
- r.lookAt(camera.position);
1261
- if (!selectedMesh) {
1262
- const scale = 1 + Math.sin(now * 0.001 + i * 0.5) * 0.08;
1263
- r.scale.set(scale, scale, scale);
1253
+ const meshes = brain.nodeMeshes;
1254
+ for (let i = 0; i < meshes.length; i++) {
1255
+ const m = meshes[i];
1256
+ if (selectedMesh && selectedMesh !== m) continue;
1257
+ if (m.material.emissiveIntensity > 1.5) continue;
1258
+
1259
+ const lvl = m.userData.level;
1260
+ const base = LEVEL_GLOW[lvl] || 0.6;
1261
+
1262
+ if (lvl === 6) {
1263
+ m.rotation.y += 0.015; // 3x per update = same visual speed
1264
+ m.rotation.x += 0.006;
1265
+ const t = Math.sin(now * 0.003 + i * 0.5);
1266
+ m.material.emissiveIntensity = base + t * 0.35;
1267
+ m.material.emissive.setRGB(1.0 - t * 0.5, 0.67 + t * 0.16, t * 0.5);
1268
+ } else {
1269
+ const speed = 0.002 + (lvl * 0.0004);
1270
+ m.material.emissiveIntensity = base + Math.sin(now * speed + i * 0.7) * 0.15;
1271
+ const bs = m.userData._baseSize || 1;
1272
+ const s = bs * (1 + Math.sin(now * 0.0015 + i * 1.1) * 0.04);
1273
+ m.scale.set(s, s, s);
1264
1274
  }
1265
- });
1275
+ }
1276
+ }
1277
+
1278
+ // Glow rings — every 8th frame
1279
+ if (frameCount % 8 === 0) {
1280
+ const rings = brain.glowRings;
1281
+ for (let i = 0; i < rings.length; i++) {
1282
+ rings[i].lookAt(camera.position);
1283
+ }
1266
1284
  }
1267
1285
 
1268
- // L6 Signal wave animation (WiFi-like rings expanding outward) — every 2nd frame
1269
- if (brain.webSignalRings && frameCount % 2 === 0) {
1270
- brain.webSignalRings.forEach(ring => {
1286
+ // L6 signal waves — every 6th frame
1287
+ if (brain.webSignalRings && brain.webSignalRings.length > 0 && frameCount % 6 === 0) {
1288
+ const rings = brain.webSignalRings;
1289
+ for (let i = 0; i < rings.length; i++) {
1290
+ const ring = rings[i];
1271
1291
  const ni = ring.userData.nodeIndex;
1272
1292
  const wi = ring.userData.waveIndex;
1273
1293
  if (ni < BRAIN_DATA.nodes.length) {
1274
1294
  ring.position.copy(BRAIN_DATA.nodes[ni]._pos);
1275
1295
  }
1276
1296
  ring.lookAt(camera.position);
1277
-
1278
- // Staggered wave animation: each ring pulses with offset
1279
- const cycle = 3000; // ms per cycle
1297
+ const cycle = 3000;
1280
1298
  const offset = wi * (cycle / 3);
1281
- const t = ((now + offset) % cycle) / cycle; // 0..1
1299
+ const t = ((now + offset) % cycle) / cycle;
1300
+ ring.material.opacity = (t < 0.5 ? t : 1 - t) * 0.16;
1301
+ const s = 1 + t * 0.6;
1302
+ ring.scale.set(s, s, s);
1303
+ }
1304
+ }
1282
1305
 
1283
- // Fade in during first half, fade out during second half
1284
- if (t < 0.5) {
1285
- ring.material.opacity = t * 0.16;
1286
- const s = 1 + t * 0.6;
1287
- ring.scale.set(s, s, s);
1288
- } else {
1289
- ring.material.opacity = (1 - t) * 0.16;
1290
- const s = 1 + t * 0.6;
1291
- ring.scale.set(s, s, s);
1292
- }
1293
- });
1306
+ // Light animation every 4th frame
1307
+ if (frameCount % 4 === 0) {
1308
+ mainLight.position.x = Math.sin(now * 0.0003) * 40;
1309
+ mainLight.position.z = Math.cos(now * 0.0003) * 40;
1310
+ rimLight.intensity = 0.6 + Math.sin(now * 0.001) * 0.25;
1294
1311
  }
1295
1312
 
1296
- // Move lights + dynamic rim pulse for organic feel
1297
- mainLight.position.x = Math.sin(now * 0.0003) * 40;
1298
- mainLight.position.z = Math.cos(now * 0.0003) * 40;
1299
- rimLight.intensity = 0.6 + Math.sin(now * 0.001) * 0.25;
1313
+ // Process pending mousemove if throttled
1314
+ if (pendingMouseEvent) {
1315
+ doRaycast(pendingMouseEvent);
1316
+ pendingMouseEvent = null;
1317
+ }
1300
1318
 
1301
1319
  // Render with bloom
1302
1320
  composer.render();
1303
1321
 
1304
1322
  // FPS counter
1305
1323
  frameCount++;
1324
+ fpsFrames++;
1306
1325
  if (now - lastFpsTime > 1000) {
1307
- document.getElementById('fps-counter').textContent = frameCount;
1308
- frameCount = 0;
1326
+ document.getElementById('fps-counter').textContent = fpsFrames;
1327
+ fpsFrames = 0;
1309
1328
  lastFpsTime = now;
1310
1329
  }
1311
1330
  }
@@ -1317,6 +1336,7 @@ addEventListener('resize', () => {
1317
1336
  camera.updateProjectionMatrix();
1318
1337
  renderer.setSize(innerWidth, innerHeight);
1319
1338
  composer.setSize(innerWidth, innerHeight);
1339
+ bloomPass.resolution.set(Math.round(innerWidth / 2), Math.round(innerHeight / 2));
1320
1340
  });
1321
1341
 
1322
1342
  // =========== WEBSOCKET LIVE UPDATES ===========
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/cli/brain/template.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,iBAAiB,CAAC,IAAiB;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAEtC,OAAO;;;;;;0CAMiC,IAAI,CAAC,IAAI,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCAsZxB,IAAI,CAAC,IAAI,CAAC,UAAU,iBAAiB,IAAI,CAAC,IAAI,CAAC,UAAU,eAAe,IAAI,CAAC,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,iBAAiB,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,IAAI,CAAC,IAAI,CAAC,WAAW;;wCAE3L,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;uCACpD,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA+JvE,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA04BL,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA2kBvE,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../../src/cli/brain/template.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,iBAAiB,CAAC,IAAiB;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAEtC,OAAO;;;;;;0CAMiC,IAAI,CAAC,IAAI,CAAC,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCAsZxB,IAAI,CAAC,IAAI,CAAC,UAAU,iBAAiB,IAAI,CAAC,IAAI,CAAC,UAAU,eAAe,IAAI,CAAC,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,iBAAiB,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,IAAI,CAAC,IAAI,CAAC,WAAW;;wCAE3L,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;uCACpD,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA+JvE,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBA85BL,IAAI,CAAC,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA2kBvE,CAAC;AACT,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/chat.ts"],"names":[],"mappings":"AAwDA,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAkLD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAi3DrE"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/chat.ts"],"names":[],"mappings":"AAwDA,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAkLD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAq7DrE"}