Skip to content

Commit 9fa6a67

Browse files
committed
refactor(brain): replace anatomical layout with shell-based constellation geometry
P1 of Constellation Lattice redesign (specs/2026-05-09-cortex-brain-map-redesign-plan.md). - Add brain/ShellGeometry.js — outer/inner icosphere wireframes (R=140 / R=80, subdivision 2), three orbital ellipse rings, segmented reticle ring with tick marks, center crosshair. Exports createConstellationShells / disposeConstellationShells. - Add brain/ShellLayout.js — applyShellLayout projects nodes to shell surfaces via Fibonacci sphere + seeded jitter, deterministic on node id. createShellProjectionForce replaces createBrainShapeForce. - Wire BrainVisualizer.jsx — useShellSplit state (default true) + ref, mounts constellation shells, registers shellProjection d3 force, re-applies layout on toggle. Drops THREE direct import. - Remove BRAIN_REGIONS, brainRegionForNode, brainLayoutPoint, applyBrainLayout, createBrainShapeForce, createJarvisBrainShell, BRAIN_JARVIS_SHELL_NAME, hemisphereOutline, cortexPath, ellipseRing, createLine, hashString, seededUnit, BRAIN_SHAPE_FORCE. - Update brain-visualizer.test.js to assert lattice scaffolding and that anatomy code is gone. Tests: 127 passed. Build: vite green.
1 parent f693cc8 commit 9fa6a67

4 files changed

Lines changed: 284 additions & 191 deletions

File tree

desktop/cortex-control-center/src/BrainVisualizer.jsx

Lines changed: 27 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { Component, memo, startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
22
import ForceGraph3D from "react-force-graph-3d";
3-
import * as THREE from "three";
43
import { getAgentColor, truncate } from "./constants.js";
54
import { AppIcon } from "./ui-icons.jsx";
5+
import {
6+
CONSTELLATION_SHELL_NAME,
7+
createConstellationShells,
8+
disposeConstellationShells,
9+
} from "./brain/ShellGeometry.js";
10+
import { applyShellLayout, createShellProjectionForce } from "./brain/ShellLayout.js";
611

712
const BRAIN_NODE_COLORS = Object.freeze({
813
memory: "#22d3ee",
@@ -23,16 +28,6 @@ const BRAIN_LEGEND = Object.freeze([
2328
const BRAIN_FOCUS_DISTANCE = 136;
2429
const BRAIN_FOCUS_TRANSITION_MS = 1550;
2530
const BRAIN_OVERVIEW_LINK_CAP = 96;
26-
const BRAIN_SHAPE_FORCE = 0.42;
27-
const BRAIN_JARVIS_SHELL_NAME = "cortex-jarvis-brain-shell";
28-
29-
const BRAIN_REGIONS = Object.freeze([
30-
{ key: "frontal", x: 0.88, y: 0.34, z: 0.12 },
31-
{ key: "parietal", x: 0.55, y: 0.74, z: -0.18 },
32-
{ key: "temporal", x: 0.90, y: -0.30, z: 0.22 },
33-
{ key: "occipital", x: 0.36, y: -0.10, z: -0.34 },
34-
{ key: "limbic", x: 0.30, y: 0.06, z: 0.48 },
35-
]);
3631

3732
// Error boundary to catch Three.js/WebGL crashes
3833
class GraphErrorBoundary extends Component {
@@ -88,21 +83,6 @@ function graphNodePosition(node) {
8883
};
8984
}
9085

91-
function hashString(value) {
92-
let hash = 2166136261;
93-
const text = String(value || "");
94-
for (let index = 0; index < text.length; index += 1) {
95-
hash ^= text.charCodeAt(index);
96-
hash = Math.imul(hash, 16777619);
97-
}
98-
return hash >>> 0;
99-
}
100-
101-
function seededUnit(seed, salt = 0) {
102-
const value = Math.sin((seed + (salt * 1013)) * 12.9898) * 43758.5453;
103-
return value - Math.floor(value);
104-
}
105-
10686
function brainOverviewThreshold(nodeCount) {
10787
return Math.max(480, Math.min(1080, Math.sqrt(Math.max(nodeCount, 1)) * 72));
10888
}
@@ -133,156 +113,6 @@ function isDecisionNode(node) {
133113
return node?.group === "decision" || node?.type === "decision";
134114
}
135115

136-
function brainRegionForNode(node, seed) {
137-
if (isDecisionNode(node)) return seed % 2 === 0 ? BRAIN_REGIONS[0] : BRAIN_REGIONS[4];
138-
const type = String(node?.type || "").toLowerCase();
139-
if (type.includes("episodic") || type.includes("conversation")) return BRAIN_REGIONS[2];
140-
if (type.includes("summary") || type.includes("semantic")) return BRAIN_REGIONS[1];
141-
if (type.includes("goal") || type.includes("plan")) return BRAIN_REGIONS[0];
142-
return BRAIN_REGIONS[seed % BRAIN_REGIONS.length];
143-
}
144-
145-
function brainLayoutPoint(node, index, total) {
146-
const seed = hashString(`${node.id}:${node.agent}:${node.type}:${index}`);
147-
const region = brainRegionForNode(node, seed);
148-
const hemisphere = seed % 2 === 0 ? -1 : 1;
149-
const angle = (index * 2.399963229728653) + (seededUnit(seed, 2) * Math.PI * 2);
150-
const density = 0.28 + (seededUnit(seed, 3) * 0.72);
151-
const layer = total > 1 ? index / (total - 1) : 0.5;
152-
const outerX = 120;
153-
const outerY = 92;
154-
const outerZ = 74;
155-
const centralGap = 22;
156-
const x = hemisphere * (centralGap + (region.x * outerX * 0.66))
157-
+ (hemisphere * Math.cos(angle) * outerX * 0.20 * density);
158-
const y = ((region.y - 0.18) * outerY)
159-
+ (Math.sin(angle) * outerY * 0.24 * density)
160-
+ (Math.sin(layer * Math.PI) * 16);
161-
const z = (region.z * outerZ)
162-
+ (Math.sin(angle * 1.7) * outerZ * 0.32 * density);
163-
164-
return {
165-
brainRegion: region.key,
166-
brainHemisphere: hemisphere < 0 ? "left" : "right",
167-
brainX: x,
168-
brainY: y,
169-
brainZ: z,
170-
x,
171-
y,
172-
z,
173-
};
174-
}
175-
176-
function applyBrainLayout(nodes) {
177-
const total = Math.max(nodes.length, 1);
178-
return nodes.map((node, index) => ({
179-
...node,
180-
...brainLayoutPoint(node, index, total),
181-
}));
182-
}
183-
184-
function createBrainShapeForce(strength = BRAIN_SHAPE_FORCE) {
185-
let nodes = [];
186-
function force(alpha) {
187-
const pull = Math.min(0.24, strength * alpha);
188-
for (const node of nodes) {
189-
if (!Number.isFinite(node.brainX) || !Number.isFinite(node.brainY) || !Number.isFinite(node.brainZ)) continue;
190-
node.vx += (node.brainX - (node.x || 0)) * pull;
191-
node.vy += (node.brainY - (node.y || 0)) * pull;
192-
node.vz += (node.brainZ - (node.z || 0)) * pull;
193-
}
194-
}
195-
force.initialize = assignedNodes => { nodes = assignedNodes || []; };
196-
return force;
197-
}
198-
199-
function createLine(points, color = "#40e0ff", opacity = 0.36) {
200-
const material = new THREE.LineBasicMaterial({
201-
color,
202-
transparent: true,
203-
opacity,
204-
depthWrite: false,
205-
blending: THREE.AdditiveBlending,
206-
});
207-
const geometry = new THREE.BufferGeometry().setFromPoints(points);
208-
return new THREE.Line(geometry, material);
209-
}
210-
211-
function hemisphereOutline(centerX, radiusX, radiusY, zBias, side) {
212-
const points = [];
213-
const segments = 112;
214-
for (let index = 0; index <= segments; index += 1) {
215-
const theta = (index / segments) * Math.PI * 2;
216-
const lobe = 1 + (Math.sin(theta * 3 + side) * 0.035);
217-
const x = centerX + (Math.cos(theta) * radiusX * lobe);
218-
const y = Math.sin(theta) * radiusY * (1 - (Math.cos(theta) * 0.05));
219-
const z = zBias + (Math.sin(theta * 2) * 12) + (Math.cos(theta) * side * 8);
220-
points.push(new THREE.Vector3(x, y, z));
221-
}
222-
return points;
223-
}
224-
225-
function cortexPath(centerX, y, width, z, side, phase) {
226-
const points = [];
227-
const segments = 72;
228-
for (let index = 0; index <= segments; index += 1) {
229-
const t = index / segments;
230-
const x = centerX - (width / 2) + (width * t);
231-
const arch = Math.sin(t * Math.PI) * 18;
232-
const wave = Math.sin((t * Math.PI * 5) + phase) * 5;
233-
points.push(new THREE.Vector3(x, y + arch + wave, z + (Math.cos(t * Math.PI) * side * 7)));
234-
}
235-
return points;
236-
}
237-
238-
function ellipseRing(radiusX, radiusY, z, rotationX = 0, rotationY = 0) {
239-
const points = [];
240-
const segments = 128;
241-
const euler = new THREE.Euler(rotationX, rotationY, 0);
242-
for (let index = 0; index <= segments; index += 1) {
243-
const theta = (index / segments) * Math.PI * 2;
244-
const point = new THREE.Vector3(Math.cos(theta) * radiusX, Math.sin(theta) * radiusY, z);
245-
point.applyEuler(euler);
246-
points.push(point);
247-
}
248-
return points;
249-
}
250-
251-
function createJarvisBrainShell() {
252-
const group = new THREE.Group();
253-
group.name = BRAIN_JARVIS_SHELL_NAME;
254-
255-
const lines = [
256-
createLine(hemisphereOutline(-76, 72, 82, -4, -1), "#40e0ff", 0.42),
257-
createLine(hemisphereOutline(76, 72, 82, -4, 1), "#40e0ff", 0.42),
258-
createLine(hemisphereOutline(-76, 54, 62, 18, -1), "#40e0ff", 0.18),
259-
createLine(hemisphereOutline(76, 54, 62, 18, 1), "#40e0ff", 0.18),
260-
createLine(cortexPath(-76, 36, 92, 20, -1, 0.2), "#40e0ff", 0.28),
261-
createLine(cortexPath(-76, 4, 106, 10, -1, 1.7), "#40e0ff", 0.24),
262-
createLine(cortexPath(-76, -32, 82, -2, -1, 2.9), "#ffd166", 0.16),
263-
createLine(cortexPath(76, 36, 92, 20, 1, 1.1), "#40e0ff", 0.28),
264-
createLine(cortexPath(76, 4, 106, 10, 1, 2.4), "#40e0ff", 0.24),
265-
createLine(cortexPath(76, -32, 82, -2, 1, 3.2), "#ffd166", 0.16),
266-
createLine([
267-
new THREE.Vector3(0, -72, 12),
268-
new THREE.Vector3(-4, -40, 20),
269-
new THREE.Vector3(5, -8, 24),
270-
new THREE.Vector3(-3, 30, 18),
271-
new THREE.Vector3(0, 74, 10),
272-
], "#f8fbff", 0.22),
273-
createLine(ellipseRing(172, 110, -10, Math.PI * 0.20, 0), "#40e0ff", 0.14),
274-
createLine(ellipseRing(138, 84, 24, Math.PI * 0.34, Math.PI * 0.12), "#ffd166", 0.10),
275-
createLine(ellipseRing(118, 68, -32, Math.PI * 0.48, -Math.PI * 0.10), "#40e0ff", 0.11),
276-
];
277-
278-
for (const line of lines) {
279-
line.renderOrder = 1;
280-
group.add(line);
281-
}
282-
283-
return group;
284-
}
285-
286116
function brainNodeBaseColor(node) {
287117
return isDecisionNode(node) ? BRAIN_NODE_COLORS.decision : BRAIN_NODE_COLORS.memory;
288118
}
@@ -392,7 +222,9 @@ function BrainVisualizerComponent({ api = null, cortexBase = "http://127.0.0.1:7
392222
const selectionFrameRef = useRef(null);
393223
const zoomFrameRef = useRef(null);
394224
const viewDepthRef = useRef("detail");
225+
const useShellSplitRef = useRef(true);
395226
const [graphData, setGraphData] = useState({ nodes: [], links: [] });
227+
const [useShellSplit, setUseShellSplit] = useState(true);
396228
const [loading, setLoading] = useState(true);
397229
const [error, setError] = useState(null);
398230
const [hoverNode, setHoverNode] = useState(null);
@@ -409,6 +241,14 @@ function BrainVisualizerComponent({ api = null, cortexBase = "http://127.0.0.1:7
409241
selectedNodeRef.current = selectedNode;
410242
}, [selectedNode]);
411243

244+
useEffect(() => {
245+
useShellSplitRef.current = useShellSplit;
246+
setGraphData(prev => {
247+
if (!prev?.nodes?.length) return prev;
248+
return { ...prev, nodes: applyShellLayout(prev.nodes, { useShellSplit }) };
249+
});
250+
}, [useShellSplit]);
251+
412252
useEffect(() => () => {
413253
if (selectionFrameRef.current) cancelAnimationFrame(selectionFrameRef.current);
414254
if (zoomFrameRef.current) cancelAnimationFrame(zoomFrameRef.current);
@@ -445,11 +285,11 @@ function BrainVisualizerComponent({ api = null, cortexBase = "http://127.0.0.1:7
445285
if (!scene) return undefined;
446286

447287
if (!jarvisShellRef.current) {
448-
jarvisShellRef.current = createJarvisBrainShell();
288+
jarvisShellRef.current = createConstellationShells();
449289
}
450290

451291
const shell = jarvisShellRef.current;
452-
if (!scene.getObjectByName(BRAIN_JARVIS_SHELL_NAME)) {
292+
if (!scene.getObjectByName(CONSTELLATION_SHELL_NAME)) {
453293
scene.add(shell);
454294
}
455295

@@ -458,6 +298,13 @@ function BrainVisualizerComponent({ api = null, cortexBase = "http://127.0.0.1:7
458298
};
459299
}, [active, graphData.nodes.length, webglAvailable]);
460300

301+
useEffect(() => () => {
302+
if (jarvisShellRef.current) {
303+
disposeConstellationShells(jarvisShellRef.current);
304+
jarvisShellRef.current = null;
305+
}
306+
}, []);
307+
461308
const syncViewDepth = useCallback(() => {
462309
const graph = graphRef.current;
463310
const camera = graph && typeof graph.camera === "function" ? graph.camera() : null;
@@ -524,7 +371,7 @@ function BrainVisualizerComponent({ api = null, cortexBase = "http://127.0.0.1:7
524371
if (linkForce && typeof linkForce.strength === "function") {
525372
linkForce.strength(link => Math.min(0.32, Math.max(0.04, Number(link.weight || 1) * 0.08)));
526373
}
527-
if (typeof graph.d3Force === "function") graph.d3Force("brainShape", createBrainShapeForce());
374+
if (typeof graph.d3Force === "function") graph.d3Force("shellProjection", createShellProjectionForce());
528375
if (typeof graph.d3ReheatSimulation === "function") graph.d3ReheatSimulation();
529376
} catch {
530377
// Force tuning is best-effort; the graph should still render with library defaults.
@@ -678,7 +525,7 @@ function BrainVisualizerComponent({ api = null, cortexBase = "http://127.0.0.1:7
678525
// Final validation — ensure all link targets exist
679526
const validLinks = links.filter(l => nodeIds.has(l.source) && nodeIds.has(l.target));
680527

681-
setGraphData({ nodes: applyBrainLayout(nodes), links: validLinks });
528+
setGraphData({ nodes: applyShellLayout(nodes, { useShellSplit: useShellSplitRef.current }), links: validLinks });
682529
setLoading(false);
683530
} catch (err) {
684531
setError(err.message);

desktop/cortex-control-center/src/brain-visualizer.test.js

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { describe, expect, it } from "vitest";
33

44
const source = readFileSync(new URL("./BrainVisualizer.jsx", import.meta.url), "utf8");
55
const css = readFileSync(new URL("./styles.css", import.meta.url), "utf8");
6+
const shellGeometry = readFileSync(new URL("./brain/ShellGeometry.js", import.meta.url), "utf8");
7+
const shellLayout = readFileSync(new URL("./brain/ShellLayout.js", import.meta.url), "utf8");
68

79
function readBlock(text, needle) {
810
const start = text.indexOf(needle);
@@ -67,22 +69,39 @@ describe("Brain visualizer", () => {
6769
expect(source).toContain("viewDepth === \"overview\"");
6870
});
6971

70-
it("uses a deterministic brain-shaped layout instead of a generic force cluster", () => {
71-
expect(source).toContain("const BRAIN_REGIONS = Object.freeze");
72-
expect(source).toContain("function brainLayoutPoint");
73-
expect(source).toContain("function applyBrainLayout");
74-
expect(source).toContain("function createBrainShapeForce");
75-
expect(source).toContain("nodes: applyBrainLayout(nodes)");
76-
expect(source).toContain("graph.d3Force(\"brainShape\", createBrainShapeForce())");
72+
it("uses the constellation lattice layout instead of anatomical hemispheres", () => {
73+
expect(source).not.toContain("BRAIN_REGIONS");
74+
expect(source).not.toContain("brainRegionForNode");
75+
expect(source).not.toContain("applyBrainLayout");
76+
expect(source).not.toContain("createBrainShapeForce");
77+
expect(source).toContain("import { applyShellLayout, createShellProjectionForce }");
78+
expect(source).toContain("applyShellLayout(nodes, { useShellSplit: useShellSplitRef.current })");
79+
expect(source).toContain("graph.d3Force(\"shellProjection\", createShellProjectionForce())");
7780
});
7881

79-
it("renders the Jarvis brain shell inside the rotatable 3D scene", () => {
80-
expect(source).toContain("import * as THREE from \"three\"");
81-
expect(source).toContain("const BRAIN_JARVIS_SHELL_NAME");
82-
expect(source).toContain("function createJarvisBrainShell");
82+
it("renders the constellation shell scaffolding inside the rotatable 3D scene", () => {
83+
expect(source).not.toContain("createJarvisBrainShell");
84+
expect(source).not.toContain("BRAIN_JARVIS_SHELL_NAME");
85+
expect(source).toContain("createConstellationShells()");
86+
expect(source).toContain("CONSTELLATION_SHELL_NAME");
8387
expect(source).toContain("scene.add(shell)");
8488
expect(source).toContain("controlType=\"orbit\"");
8589
expect(source).toContain("enableNavigationControls={true}");
8690
expect(source).toContain("showNavInfo={false}");
8791
});
92+
93+
it("ShellGeometry exports the constellation builders", () => {
94+
expect(shellGeometry).toContain("export const CONSTELLATION_SHELL_NAME");
95+
expect(shellGeometry).toContain("export function createConstellationShells");
96+
expect(shellGeometry).toContain("export function disposeConstellationShells");
97+
expect(shellGeometry).toContain("IcosahedronGeometry");
98+
expect(shellGeometry).toContain("WireframeGeometry");
99+
});
100+
101+
it("ShellLayout exposes deterministic shell projection", () => {
102+
expect(shellLayout).toContain("export function applyShellLayout");
103+
expect(shellLayout).toContain("export function createShellProjectionForce");
104+
expect(shellLayout).toContain("useShellSplit");
105+
expect(shellLayout).toContain("fibonacciSphere");
106+
});
88107
});

0 commit comments

Comments
 (0)