World/Brain: an actual brain — procedural node+edge scaffold
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled

Replaces the random per-id ellipsoid scatter with a fixed, deterministic brain
scaffold (frontend/src/components/world/brain.ts): two folded cerebral hemispheres
split by a longitudinal fissure (multi-octave noise = gyri/sulci), a cerebellum,
and a brain-stem — ~430 nodes wired into a k-nearest-neighbour edge network.

WorldCanvas Hierarchy renderer: the scaffold is one Points cloud + one LineSegments
(the dormant brain), with an activation field over the nodes. Agents seat into a
deterministic fixed region (agentRegion hash); their seat brightens on work and
fires activity-coloured signals along the real edges, which chain through the mesh
and light its pathways. Idle → a dim, gently-breathing brain. Same brain every
reload; orbit/pinch to see the 3D anatomy.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-24 05:12:04 -07:00
co-authored by Claude Opus 4.8
parent 86524cb309
commit db7569e5d3
2 changed files with 233 additions and 127 deletions
+97 -127
View File
@@ -20,6 +20,7 @@ import { useClawmatesLive, useLiveState } from "@/lib/live/useClawmatesLive";
import { WorldFlow, type WorldItem } from "../dashboard/flow/WorldFlow"; import { WorldFlow, type WorldItem } from "../dashboard/flow/WorldFlow";
import { BRAIN, agentRegion } from "./brain";
import { WorldEngine, type Formation, type WorldSeed } from "./engine"; import { WorldEngine, type Formation, type WorldSeed } from "./engine";
type ReplayEvent = { t: number; type: string; data: Record<string, unknown> }; type ReplayEvent = { t: number; type: string; data: Record<string, unknown> };
@@ -69,27 +70,6 @@ function makeGlowTexture(): THREE.Texture {
return tex; return tex;
} }
/** A deterministic 3D position for an agent-neuron in the brain layout — a
* two-hemisphere ellipsoid keyed off the agent id. */
function neuronPos(id: string): THREE.Vector3 {
let h = 2166136261;
for (let i = 0; i < id.length; i++) {
h ^= id.charCodeAt(i);
h = Math.imul(h, 16777619);
}
const r1 = ((h >>> 0) % 1000) / 1000;
const r2 = ((h >>> 7) % 1000) / 1000;
const r3 = ((h >>> 13) % 1000) / 1000;
const theta = r1 * Math.PI * 2;
const phi = Math.acos(2 * r2 - 1);
const rad = 95 + r3 * 80;
let x = rad * Math.sin(phi) * Math.cos(theta) * 1.35;
const y = rad * Math.sin(phi) * Math.sin(theta) * 0.92;
const z = rad * Math.cos(phi) * 0.78;
x += (x >= 0 ? 1 : -1) * 48; // split into two hemispheres
return new THREE.Vector3(x, y, z);
}
interface WorldCanvasProps { interface WorldCanvasProps {
roots: WorldSeed[]; roots: WorldSeed[];
selectedId: string | null; selectedId: string | null;
@@ -285,19 +265,43 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
); );
gourceGroup.add(beams); gourceGroup.add(beams);
// brain (hierarchy) — pathway edges between agent-neurons // brain (hierarchy) — a FIXED procedural scaffold (nodes + edges shaped like a
// brain). Agents seat into fixed regions; activation diffuses through the edges.
const N = BRAIN.count;
const scaffoldGeom = new THREE.BufferGeometry();
scaffoldGeom.setAttribute("position", new THREE.Float32BufferAttribute(BRAIN.positions.slice(), 3));
const scaffoldColors = new Float32Array(N * 3);
scaffoldGeom.setAttribute("color", new THREE.BufferAttribute(scaffoldColors, 3));
const scaffoldPoints = new THREE.Points(
scaffoldGeom,
new THREE.PointsMaterial({ size: 5, map: glow, vertexColors: true, blending: THREE.AdditiveBlending, transparent: true, depthWrite: false, sizeAttenuation: true }),
);
brainGroup.add(scaffoldPoints);
const E = BRAIN.edges.length / 2; // edge count
const edgePos = new Float32Array(E * 2 * 3);
for (let e = 0; e < E; e++) {
const a = BRAIN.edges[e * 2];
const b = BRAIN.edges[e * 2 + 1];
edgePos[e * 6] = BRAIN.positions[a * 3];
edgePos[e * 6 + 1] = BRAIN.positions[a * 3 + 1];
edgePos[e * 6 + 2] = BRAIN.positions[a * 3 + 2];
edgePos[e * 6 + 3] = BRAIN.positions[b * 3];
edgePos[e * 6 + 4] = BRAIN.positions[b * 3 + 1];
edgePos[e * 6 + 5] = BRAIN.positions[b * 3 + 2];
}
const brainEdgeGeom = new THREE.BufferGeometry(); const brainEdgeGeom = new THREE.BufferGeometry();
brainEdgeGeom.setAttribute("position", new THREE.BufferAttribute(edgePos, 3));
const edgeColors = new Float32Array(E * 2 * 3);
brainEdgeGeom.setAttribute("color", new THREE.BufferAttribute(edgeColors, 3));
const brainEdges = new THREE.LineSegments( const brainEdges = new THREE.LineSegments(
brainEdgeGeom, brainEdgeGeom,
new THREE.LineBasicMaterial({ vertexColors: true, transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending, depthWrite: false }), new THREE.LineBasicMaterial({ vertexColors: true, transparent: true, opacity: 0.9, blending: THREE.AdditiveBlending, depthWrite: false }),
); );
brainGroup.add(brainEdges); brainGroup.add(brainEdges);
const brainPositions = new Map<string, THREE.Vector3>();
const neuronAct = new Map<string, number>(); const activation = new Float32Array(N).fill(0.04); // dormant baseline
const neuronExp = new Map<string, number>(); // cumulative experience → neuron size const neuronExp = new Map<string, number>(); // per-agent cumulative experience
const brainAdj = new Map<string, string[]>();
let brainEdgeIds: string[] = []; // flat [a0,b0,a1,b1,…] for per-edge lighting
let brainCount = -1;
// particle system (sparks + trails): additive points, ring buffer, RGB→0 fade // particle system (sparks + trails): additive points, ring buffer, RGB→0 fade
const MAXP = 1400; const MAXP = 1400;
@@ -403,108 +407,54 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
particleGeom.attributes.color.needsUpdate = true; particleGeom.attributes.color.needsUpdate = true;
}; };
// Brain pathway graph: connect each neuron to its 3 nearest (rebuilt on count change). // Brain (hierarchy): the fixed scaffold is the dormant mesh; agents seat into
const buildBrainGraph = (ids: string[]) => { // fixed regions whose activity diffuses through the real edges + fires signals.
brainAdj.clear(); const seatPos = new THREE.Vector3();
const pos = ids.map((id) => brainPositions.get(id)!);
const segs: number[] = [];
const pairs: string[] = [];
for (let i = 0; i < ids.length; i++) {
const ds = ids.map((_, j) => ({ j, d: i === j ? Infinity : pos[i].distanceToSquared(pos[j]) }));
ds.sort((a, b) => a.d - b.d);
const k = Math.min(3, ids.length - 1);
const nbrs: string[] = [];
for (let n = 0; n < k; n++) {
const j = ds[n].j;
nbrs.push(ids[j]);
if (i < j) {
segs.push(pos[i].x, pos[i].y, pos[i].z, pos[j].x, pos[j].y, pos[j].z);
pairs.push(ids[i], ids[j]);
}
}
brainAdj.set(ids[i], nbrs);
}
brainEdgeIds = pairs;
brainEdgeGeom.setAttribute("position", new THREE.Float32BufferAttribute(segs, 3));
brainEdgeGeom.setAttribute("color", new THREE.Float32BufferAttribute(new Float32Array(segs.length), 3));
brainEdgeGeom.attributes.position.needsUpdate = true;
};
// Neural-brain view: agents are neurons in a dormant mesh; active agents fire
// signals along pathways to neighbors (chaining), so the brain comes alive.
const renderBrain = (dt: number) => { const renderBrain = (dt: number) => {
// Neurons = agents (bright, drive firing) + structure nodes (org/company/ // decay activation toward the dormant baseline + a resting shimmer so the
// team) as a dim support mesh, so even a near-idle brain has body. // brain keeps breathing when idle
type Neuron = { id: string; color: string; fire: string; agent: boolean; boost: number }; for (let i = 0; i < N; i++) activation[i] = Math.max(0.04, activation[i] - dt * 0.6);
const neurons: Neuron[] = []; for (let k = 0; k < 3; k++) {
const i = (Math.random() * N) | 0;
activation[i] = Math.min(0.6, activation[i] + 0.12);
}
const seen = new Set<string>();
for (const p of engine.pawns.values()) { for (const p of engine.pawns.values()) {
const recent = p.idle < 2.5 ? 1 : 0; // a recent touch lights the neuron seen.add(p.id);
neurons.push({ id: p.id, color: p.color, fire: p.fireColor ?? p.color, agent: true, boost: (p.status === "working" ? 0.9 : 0) + recent }); const seat = agentRegion(p.id, N);
} const recent = p.idle < 2.5 ? 1 : 0;
for (const n of engine.nodes.values()) { const boost = (p.status === "working" ? 0.9 : 0) + recent;
if (n.tier === "org" || n.tier === "company" || n.tier === "team") activation[seat] = Math.min(1, activation[seat] + boost * dt * 1.6);
neurons.push({ id: n.id, color: n.color, fire: n.color, agent: false, boost: n.heat }); const act = activation[seat];
} let exp = neuronExp.get(p.id) ?? 0;
const ids = neurons.map((n) => n.id); exp = Math.min(1, exp + act * dt * 0.15);
const seen = new Set(ids); neuronExp.set(p.id, exp);
for (const ne of neurons) { let g = neuronGlows.get(p.id);
if (!brainPositions.has(ne.id)) brainPositions.set(ne.id, neuronPos(ne.id));
const pos = brainPositions.get(ne.id)!;
let g = neuronGlows.get(ne.id);
if (!g) { if (!g) {
g = newGlow(brainGroup); g = newGlow(brainGroup);
neuronGlows.set(ne.id, g); neuronGlows.set(p.id, g);
} }
let act = neuronAct.get(ne.id) ?? (ne.agent ? 0.06 : 0.03); seatPos.set(BRAIN.positions[seat * 3], BRAIN.positions[seat * 3 + 1], BRAIN.positions[seat * 3 + 2]);
act = Math.min(1, act + ne.boost * dt * 1.3); g.position.copy(seatPos);
if (Math.random() < dt * 0.5) act = Math.min(1, act + 0.08); // resting shimmer → the brain breathes const gs = 18 + exp * 14 + act * 30;
act = Math.max(ne.agent ? 0.06 : 0.025, act - dt * 0.22);
neuronAct.set(ne.id, act);
let exp = neuronExp.get(ne.id) ?? 0;
exp = Math.min(1, exp + act * dt * 0.15); // experience grows with activity
neuronExp.set(ne.id, exp);
g.position.copy(pos);
const base = (ne.agent ? 16 : 8) + exp * (ne.agent ? 16 : 7);
const gs = base + act * (ne.agent ? 36 : 16);
g.scale.set(gs, gs, 1); g.scale.set(gs, gs, 1);
const gm = g.material as THREE.SpriteMaterial; const gm = g.material as THREE.SpriteMaterial;
gm.color.set(ne.color); gm.color.set(p.color);
gm.opacity = (ne.agent ? 0.16 : 0.06) + act * (ne.agent ? 0.84 : 0.5); gm.opacity = 0.3 + act * 0.7;
// fire a signal along a pathway (chaining through the mesh) // fire a signal along a real edge to a neighbour (chaining through the mesh)
const nbrs = brainAdj.get(ne.id); const nbrs = BRAIN.adjacency[seat];
if (nbrs && nbrs.length && Math.random() < act * dt * (ne.agent ? 7 : 3.5)) { if (nbrs.length && Math.random() < act * dt * 8) {
const tid = nbrs[(Math.random() * nbrs.length) | 0]; const tj = nbrs[(Math.random() * nbrs.length) | 0];
const tp = brainPositions.get(tid); const dx = BRAIN.positions[tj * 3] - seatPos.x;
if (tp) { const dy = BRAIN.positions[tj * 3 + 1] - seatPos.y;
const dx = tp.x - pos.x; const dz = BRAIN.positions[tj * 3 + 2] - seatPos.z;
const dy = tp.y - pos.y; const len = Math.hypot(dx, dy, dz) || 1;
const dz = tp.z - pos.z; const speed = 130;
const len = Math.hypot(dx, dy, dz) || 1; spawn(seatPos.x, seatPos.y, seatPos.z, p.fireColor ?? p.color, (dx / len) * speed, (dy / len) * speed, (dz / len) * speed, speed / len + 0.3);
const speed = 150; activation[tj] = Math.min(1, activation[tj] + 0.2);
spawn(pos.x, pos.y, pos.z, ne.fire, (dx / len) * speed, (dy / len) * speed, (dz / len) * speed, speed / len + 0.25);
neuronAct.set(tid, Math.min(1, (neuronAct.get(tid) ?? 0.05) + 0.14));
}
} }
} }
// light up pathways by endpoint activity (lit pathways stand out; gradient
// from each end so the brighter neuron dominates its edges)
const ecol = brainEdgeGeom.getAttribute("color") as THREE.BufferAttribute | undefined;
if (ecol) {
const arr = ecol.array as Float32Array;
for (let e = 0; e + 1 < brainEdgeIds.length; e += 2) {
const va = 0.05 + (neuronAct.get(brainEdgeIds[e]) ?? 0) * 0.95;
const vb = 0.05 + (neuronAct.get(brainEdgeIds[e + 1]) ?? 0) * 0.95;
const ia = e * 3;
const ib = (e + 1) * 3;
arr[ia] = 0.43 * va;
arr[ia + 1] = 0.71 * va;
arr[ia + 2] = va;
arr[ib] = 0.43 * vb;
arr[ib + 1] = 0.71 * vb;
arr[ib + 2] = vb;
}
ecol.needsUpdate = true;
}
for (const [id, g] of neuronGlows) { for (const [id, g] of neuronGlows) {
if (!seen.has(id)) { if (!seen.has(id)) {
brainGroup.remove(g); brainGroup.remove(g);
@@ -512,10 +462,27 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
neuronGlows.delete(id); neuronGlows.delete(id);
} }
} }
if (ids.length !== brainCount) {
buildBrainGraph(ids); // scaffold point + edge colours from the activation field (lit pathways)
brainCount = ids.length; for (let i = 0; i < N; i++) {
const v = 0.05 + activation[i] * 0.95;
scaffoldColors[i * 3] = 0.42 * v;
scaffoldColors[i * 3 + 1] = 0.66 * v;
scaffoldColors[i * 3 + 2] = v;
} }
scaffoldGeom.attributes.color.needsUpdate = true;
for (let e = 0; e < E; e++) {
const va = 0.04 + activation[BRAIN.edges[e * 2]] * 0.96;
const vb = 0.04 + activation[BRAIN.edges[e * 2 + 1]] * 0.96;
const i0 = e * 6;
edgeColors[i0] = 0.42 * va;
edgeColors[i0 + 1] = 0.66 * va;
edgeColors[i0 + 2] = va;
edgeColors[i0 + 3] = 0.42 * vb;
edgeColors[i0 + 4] = 0.66 * vb;
edgeColors[i0 + 5] = vb;
}
brainEdgeGeom.attributes.color.needsUpdate = true;
}; };
const col = new THREE.Color(); const col = new THREE.Color();
@@ -567,8 +534,8 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
renderBrain(dt); renderBrain(dt);
updateParticles(dt); updateParticles(dt);
if (!userInteracted) { if (!userInteracted) {
controls.target.lerp(new THREE.Vector3(0, 0, 0), 0.05); controls.target.lerp(new THREE.Vector3(0, -18, 0), 0.05);
camera.position.lerp(new THREE.Vector3(0, 0, 520), 0.04); camera.position.lerp(new THREE.Vector3(0, -18, 460), 0.04);
} }
controls.update(); controls.update();
bloom.strength = 0.75; bloom.strength = 0.75;
@@ -803,6 +770,9 @@ export function WorldCanvas({ roots, selectedId, onSelect, expanded, onToggleExp
edgeGeom.dispose(); edgeGeom.dispose();
beamGeom.dispose(); beamGeom.dispose();
brainEdgeGeom.dispose(); brainEdgeGeom.dispose();
(brainEdges.material as THREE.Material).dispose();
scaffoldGeom.dispose();
(scaffoldPoints.material as THREE.Material).dispose();
particleGeom.dispose(); particleGeom.dispose();
glow.dispose(); glow.dispose();
composer.dispose(); composer.dispose();
+136
View File
@@ -0,0 +1,136 @@
// A procedural, deterministic brain scaffold for the Hierarchy view: a fixed
// node + edge network shaped like an actual brain — two folded cerebral
// hemispheres (split by a longitudinal fissure), a cerebellum, and a brain-stem.
// Agents seat into fixed regions of it (agentRegion); their activity diffuses
// through the real edges. Seeded so the brain is identical on every reload.
const R = 120; // overall brain radius scale
function mulberry32(seed: number): () => number {
let s = seed >>> 0;
return () => {
s = (s + 0x6d2b79f5) | 0;
let t = Math.imul(s ^ (s >>> 15), 1 | s);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
// Multi-octave sinusoidal "folds" over (theta, phi) → gyri/sulci wrinkles.
function folds(theta: number, phi: number): number {
return (
0.1 * Math.sin(6 * theta + 1.3) * Math.cos(5 * phi) +
0.07 * Math.sin(9 * theta + 2.1) * Math.sin(7 * phi + 0.5) +
0.05 * Math.cos(13 * theta) * Math.sin(11 * phi + 1.1)
);
}
export interface BrainScaffold {
positions: Float32Array; // count*3
adjacency: number[][]; // per-node neighbour indices (for signal propagation)
edges: Uint16Array; // deduped index pairs (for line rendering)
count: number;
}
export function buildBrainScaffold(): BrainScaffold {
const rng = mulberry32(0x9e3779b9);
const pts: number[] = [];
const push = (x: number, y: number, z: number) => pts.push(x, y, z);
// Two cerebral hemispheres — an ellipsoid (longer front↔back, a bit wider than
// tall), folded to one side of a longitudinal fissure and wrinkled by folds().
const cere = { x: 0.86, y: 0.78, z: 1.12 };
const gap = 9; // fissure half-gap
const HEMI = 175;
for (const side of [-1, 1]) {
for (let i = 0; i < HEMI; i++) {
const u = rng() * 2 - 1; // cos(phi)
const t = rng() * Math.PI * 2; // theta
const s = Math.sqrt(1 - u * u);
const nx = Math.abs(s * Math.cos(t)) * side; // fold to one hemisphere
const ny = u;
const nz = s * Math.sin(t);
const f = 1 + folds(t, Math.acos(u));
let x = nx * cere.x * R * f;
let y = (ny * cere.y - 0.06) * R * f;
const z = nz * cere.z * R * f;
if (y < 0) y *= 0.82; // flatter underside
x += side * gap;
push(x, y, z);
}
}
// Cerebellum — a denser, more wrinkled ellipsoid at the back-bottom.
const CB = 64;
for (let i = 0; i < CB; i++) {
const u = rng() * 2 - 1;
const t = rng() * Math.PI * 2;
const s = Math.sqrt(1 - u * u);
const f = 1 + 1.8 * folds(t * 1.7, Math.acos(u));
push((s * Math.cos(t)) * 0.5 * R * f, (u * 0.34 - 0.92) * R, (s * Math.sin(t) * 0.42 - 0.64) * R);
}
// Brain-stem — a short tapering column descending from the bottom-centre.
const ST = 16;
for (let i = 0; i < ST; i++) {
const tt = i / ST;
const r = (1 - tt) * 10 + 3;
const a = rng() * Math.PI * 2;
push(Math.cos(a) * r, (-0.74 - tt * 0.52) * R, (-0.5 + Math.sin(a) * 0.04) * R);
}
const positions = new Float32Array(pts);
const count = positions.length / 3;
// Edges: each node to its K nearest neighbours within MAXD (so edges hug the
// surface — no cross-brain chords). Build a node-edge network + adjacency.
const K = 4;
const MAXD = 50;
const adjacency: number[][] = Array.from({ length: count }, () => []);
for (let i = 0; i < count; i++) {
const ix = positions[i * 3];
const iy = positions[i * 3 + 1];
const iz = positions[i * 3 + 2];
const near: { j: number; d: number }[] = [];
for (let j = 0; j < count; j++) {
if (j === i) continue;
const dx = ix - positions[j * 3];
const dy = iy - positions[j * 3 + 1];
const dz = iz - positions[j * 3 + 2];
near.push({ j, d: dx * dx + dy * dy + dz * dz });
}
near.sort((a, b) => a.d - b.d);
for (let n = 0; n < near.length && adjacency[i].length < K; n++) {
if (Math.sqrt(near[n].d) > MAXD) break;
adjacency[i].push(near[n].j);
}
}
const seen = new Set<string>();
const edges: number[] = [];
for (let i = 0; i < count; i++) {
for (const j of adjacency[i]) {
const a = Math.min(i, j);
const b = Math.max(i, j);
const key = `${a}_${b}`;
if (!seen.has(key)) {
seen.add(key);
edges.push(a, b);
}
}
}
return { positions, adjacency, edges: new Uint16Array(edges), count };
}
/** Deterministic fixed "seat" node for an agent (FNV-1a hash). */
export function agentRegion(id: string, count: number): number {
let h = 2166136261;
for (let i = 0; i < id.length; i++) {
h ^= id.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return (h >>> 0) % count;
}
/** The single shared brain (deterministic — same every reload). */
export const BRAIN = buildBrainScaffold();