[claude] Unified memory graph & sovereignty loop visualization (#18) #24

Closed
claude wants to merge 1 commits from claude/the-nexus:claude/issue-18 into main
3 changed files with 313 additions and 0 deletions

255
app.js
View File

@@ -35,6 +35,28 @@ let frameCount = 0, lastFPSTime = 0, fps = 0;
let chatOpen = true;
let loadProgress = 0;
// Memory & Sovereignty loop state
let memoryNodeMeshes = [];
let sovereigntyGroup;
let loopPulseSphere;
let loopPulseT = 0;
// Memory node type colors
const MEMORY_COLORS = {
user: 0x4af0c0,
feedback: 0xffd700,
project: 0x7b5cff,
reference: 0x4488ff,
};
// Sovereignty loop stages
const LOOP_STAGES = [
{ label: 'EXPORT', color: 0x4af0c0, angle: Math.PI / 2 },
{ label: 'COMPRESS', color: 0xffd700, angle: 0 },
{ label: 'TRAIN', color: 0x7b5cff, angle: -Math.PI / 2 },
{ label: 'EVAL', color: 0xff4466, angle: Math.PI },
];
// ═══ INIT ═══
function init() {
clock = new THREE.Clock();
@@ -77,6 +99,9 @@ function init() {
createDustParticles();
updateLoad(85);
createAmbientStructures();
updateLoad(87);
createMemoryGraph();
createSovereigntyLoop();
updateLoad(90);
// Post-processing
@@ -787,6 +812,202 @@ function createAmbientStructures() {
scene.add(pedestal);
}
// ═══ MEMORY GRAPH ═══
function createMemoryGraph() {
const group = new THREE.Group();
group.position.set(-14, 2.5, -8);
group.name = 'memory-graph';
const NODES = [
{ type: 'user', label: 'Role: Sovereign Dev', pos: [ 0, 2.5, 0 ] },
{ type: 'user', label: 'Prefs: Terse replies', pos: [-1.8, 3.8, -0.8 ] },
{ type: 'feedback', label: 'No trailing summaries', pos: [ 1.8, 3.5, 0.5 ] },
{ type: 'feedback', label: 'Real DB in tests', pos: [ 2.2, 1.2, -1.2 ] },
{ type: 'project', label: 'Nexus v1 build', pos: [-2.2, 1.5, 1.2 ] },
{ type: 'project', label: 'Portal system', pos: [ 0.5, -0.2, 2.0 ] },
{ type: 'reference', label: 'Gitea API', pos: [-1.2, -0.2, -1.8 ] },
{ type: 'reference', label: 'Issue tracker', pos: [ 1.0, -1.2, -0.5 ] },
{ type: 'project', label: 'Sovereignty loop', pos: [-0.5, -1.5, 1.0 ] },
];
const EDGES = [
[0, 1], [0, 2], [0, 4], [0, 6],
[1, 2], [2, 3], [3, 7], [4, 5],
[5, 8], [6, 7], [7, 8], [4, 8],
];
// Build node meshes
NODES.forEach((n, i) => {
const color = MEMORY_COLORS[n.type];
const geo = new THREE.SphereGeometry(0.22, 12, 12);
const mat = new THREE.MeshStandardMaterial({
color,
emissive: color,
emissiveIntensity: 1.2,
roughness: 0.2,
metalness: 0.6,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(...n.pos);
mesh.name = `mem-node-${i}`;
group.add(mesh);
memoryNodeMeshes.push(mesh);
// Outer glow ring
const ringGeo = new THREE.TorusGeometry(0.35, 0.025, 6, 24);
const ringMat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.45 });
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.position.set(...n.pos);
ring.rotation.x = Math.PI / 2;
group.add(ring);
// Label sprite
const lc = document.createElement('canvas');
lc.width = 320; lc.height = 48;
const lctx = lc.getContext('2d');
lctx.font = '18px "JetBrains Mono", monospace';
lctx.fillStyle = '#' + new THREE.Color(color).getHexString();
lctx.fillText(n.label, 6, 32);
const ltex = new THREE.CanvasTexture(lc);
ltex.minFilter = THREE.LinearFilter;
const lmat = new THREE.MeshBasicMaterial({ map: ltex, transparent: true, depthWrite: false, side: THREE.DoubleSide });
const lmesh = new THREE.Mesh(new THREE.PlaneGeometry(1.4, 0.22), lmat);
lmesh.position.set(n.pos[0] + 0.85, n.pos[1], n.pos[2]);
group.add(lmesh);
});
// Build edge lines
EDGES.forEach(([a, b]) => {
const pa = new THREE.Vector3(...NODES[a].pos);
const pb = new THREE.Vector3(...NODES[b].pos);
const points = [pa, pb];
const lineGeo = new THREE.BufferGeometry().setFromPoints(points);
const lineMat = new THREE.LineBasicMaterial({
color: 0x2a3a5a,
transparent: true,
opacity: 0.5,
});
const line = new THREE.Line(lineGeo, lineMat);
group.add(line);
});
// Section title label
const tc = document.createElement('canvas');
tc.width = 512; tc.height = 56;
const tctx = tc.getContext('2d');
tctx.font = 'bold 26px "Orbitron", sans-serif';
tctx.fillStyle = '#4af0c0';
tctx.textAlign = 'center';
tctx.fillText('◈ MEMORY GRAPH', 256, 38);
const ttex = new THREE.CanvasTexture(tc);
ttex.minFilter = THREE.LinearFilter;
const tmat = new THREE.MeshBasicMaterial({ map: ttex, transparent: true, depthWrite: false, side: THREE.DoubleSide });
const tmesh = new THREE.Mesh(new THREE.PlaneGeometry(4, 0.44), tmat);
tmesh.position.set(0, 5.5, 0);
group.add(tmesh);
scene.add(group);
}
// ═══ SOVEREIGNTY LOOP ═══
function createSovereigntyLoop() {
const group = new THREE.Group();
group.position.set(-14, 2.5, 5);
group.name = 'sovereignty-loop';
sovereigntyGroup = group;
const RADIUS = 2.5;
// Orbit ring
const orbitGeo = new THREE.TorusGeometry(RADIUS, 0.04, 8, 80);
const orbitMat = new THREE.MeshBasicMaterial({ color: 0x1a2a4a, transparent: true, opacity: 0.6 });
group.add(new THREE.Mesh(orbitGeo, orbitMat));
// Stage nodes
LOOP_STAGES.forEach((stage) => {
const x = Math.cos(stage.angle) * RADIUS;
const z = Math.sin(stage.angle) * RADIUS;
const geo = new THREE.SphereGeometry(0.35, 14, 14);
const mat = new THREE.MeshStandardMaterial({
color: stage.color,
emissive: stage.color,
emissiveIntensity: 1.0,
roughness: 0.15,
metalness: 0.7,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(x, 0, z);
group.add(mesh);
// Halo
const hgeo = new THREE.TorusGeometry(0.5, 0.03, 6, 20);
const hmat = new THREE.MeshBasicMaterial({ color: stage.color, transparent: true, opacity: 0.4 });
const halo = new THREE.Mesh(hgeo, hmat);
halo.position.set(x, 0, z);
group.add(halo);
// Stage label
const lc = document.createElement('canvas');
lc.width = 320; lc.height = 56;
const lctx = lc.getContext('2d');
lctx.font = 'bold 28px "Orbitron", sans-serif';
lctx.fillStyle = '#' + new THREE.Color(stage.color).getHexString();
lctx.textAlign = 'center';
lctx.fillText(stage.label, 160, 38);
const ltex = new THREE.CanvasTexture(lc);
ltex.minFilter = THREE.LinearFilter;
const lmat = new THREE.MeshBasicMaterial({ map: ltex, transparent: true, depthWrite: false, side: THREE.DoubleSide });
const lmesh = new THREE.Mesh(new THREE.PlaneGeometry(1.6, 0.28), lmat);
lmesh.position.set(x * 1.5, 0.7, z * 1.5);
group.add(lmesh);
});
// Central soul sphere
const soulGeo = new THREE.IcosahedronGeometry(0.45, 2);
const soulMat = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
emissive: 0x8877ff,
emissiveIntensity: 2.5,
roughness: 0,
metalness: 1,
transmission: 0.3,
});
const soul = new THREE.Mesh(soulGeo, soulMat);
soul.name = 'soul-sphere';
group.add(soul);
// Traveling pulse sphere
const pGeo = new THREE.SphereGeometry(0.15, 8, 8);
const pMat = new THREE.MeshStandardMaterial({
color: 0xffffff,
emissive: 0xffffff,
emissiveIntensity: 3,
roughness: 0,
metalness: 1,
});
loopPulseSphere = new THREE.Mesh(pGeo, pMat);
loopPulseSphere.name = 'loop-pulse';
group.add(loopPulseSphere);
// Section title
const tc = document.createElement('canvas');
tc.width = 512; tc.height = 56;
const tctx = tc.getContext('2d');
tctx.font = 'bold 24px "Orbitron", sans-serif';
tctx.fillStyle = '#7b5cff';
tctx.textAlign = 'center';
tctx.fillText('◈ SOVEREIGNTY LOOP', 256, 38);
const ttex = new THREE.CanvasTexture(tc);
ttex.minFilter = THREE.LinearFilter;
const tmat = new THREE.MeshBasicMaterial({ map: ttex, transparent: true, depthWrite: false, side: THREE.DoubleSide });
const tmesh = new THREE.Mesh(new THREE.PlaneGeometry(4, 0.44), tmat);
tmesh.position.set(0, 4, 0);
group.add(tmesh);
scene.add(group);
}
// ═══ CONTROLS ═══
function setupControls() {
document.addEventListener('keydown', (e) => {
@@ -950,6 +1171,40 @@ function gameLoop() {
core.material.emissiveIntensity = 1.5 + Math.sin(elapsed * 2) * 0.5;
}
// Animate memory graph — pulse nodes and slow float
memoryNodeMeshes.forEach((mesh, i) => {
mesh.material.emissiveIntensity = 0.8 + 0.6 * Math.sin(elapsed * 1.5 + i * 0.8);
mesh.position.y += Math.sin(elapsed * 0.9 + i * 1.1) * 0.003;
mesh.rotation.y = elapsed * 0.4 + i;
});
const memGraph = scene.getObjectByName('memory-graph');
if (memGraph) {
memGraph.rotation.y = Math.sin(elapsed * 0.15) * 0.2;
}
// Animate sovereignty loop
const soul = scene.getObjectByName('soul-sphere');
if (soul) {
soul.rotation.y = elapsed * 0.8;
soul.rotation.x = elapsed * 0.4;
soul.material.emissiveIntensity = 2 + Math.sin(elapsed * 2.5) * 0.8;
}
if (sovereigntyGroup) {
sovereigntyGroup.rotation.y = elapsed * 0.12;
}
// Traveling pulse
if (loopPulseSphere) {
loopPulseT = (elapsed * 0.4) % 1;
const angle = loopPulseT * Math.PI * 2;
const RADIUS = 2.5;
loopPulseSphere.position.x = Math.cos(angle) * RADIUS;
loopPulseSphere.position.z = Math.sin(angle) * RADIUS;
// Color matches current stage
const stageIdx = Math.floor(loopPulseT * 4) % 4;
loopPulseSphere.material.emissive.setHex(LOOP_STAGES[stageIdx].color);
loopPulseSphere.material.color.setHex(LOOP_STAGES[stageIdx].color);
}
// Render
composer.render();

View File

@@ -95,6 +95,20 @@
</div>
</div>
<!-- Memory type legend (top-right) -->
<div class="hud-memory-legend">
<div class="legend-title">MEMORY NODES</div>
<div class="legend-item"><span class="legend-dot" style="background:#4af0c0;"></span> User</div>
<div class="legend-item"><span class="legend-dot" style="background:#ffd700;"></span> Feedback</div>
<div class="legend-item"><span class="legend-dot" style="background:#7b5cff;"></span> Project</div>
<div class="legend-item"><span class="legend-dot" style="background:#4488ff;"></span> Reference</div>
<div class="legend-title" style="margin-top:8px;">SOVEREIGNTY LOOP</div>
<div class="legend-item"><span class="legend-dot" style="background:#4af0c0;"></span> EXPORT</div>
<div class="legend-item"><span class="legend-dot" style="background:#ffd700;"></span> COMPRESS</div>
<div class="legend-item"><span class="legend-dot" style="background:#7b5cff;"></span> TRAIN</div>
<div class="legend-item"><span class="legend-dot" style="background:#ff4466;"></span> EVAL</div>
</div>
<!-- Minimap / Controls hint -->
<div class="hud-controls">
<span>WASD</span> move &nbsp; <span>Mouse</span> look &nbsp; <span>Enter</span> chat

View File

@@ -359,3 +359,47 @@ canvas#nexus-canvas {
display: none;
}
}
/* === MEMORY LEGEND === */
.hud-memory-legend {
position: fixed;
top: var(--space-4);
right: var(--space-4);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--panel-radius);
padding: var(--space-3) var(--space-4);
font-family: var(--font-body);
font-size: var(--text-xs);
color: var(--color-text-muted);
backdrop-filter: blur(var(--panel-blur));
pointer-events: none;
z-index: 100;
min-width: 140px;
}
.hud-memory-legend .legend-title {
font-family: var(--font-display);
font-size: 9px;
letter-spacing: 0.1em;
color: var(--color-primary);
margin-bottom: var(--space-1);
text-transform: uppercase;
}
.hud-memory-legend .legend-item {
display: flex;
align-items: center;
gap: var(--space-2);
line-height: 1.8;
color: var(--color-text);
}
.hud-memory-legend .legend-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
box-shadow: 0 0 4px currentColor;
}