// ═══════════════════════════════════════════ // PROJECT MNEMOSYNE — SPATIAL MEMORY SCHEMA // ═══════════════════════════════════════════ // // Maps memories to persistent locations in the 3D Nexus world. // Each region corresponds to a semantic category. Memories placed // in a region stay there across sessions, forming a navigable // holographic archive. // // World layout (hex cylinder, radius 25): // North (z-) → Documents & Knowledge // South (z+) → Projects & Tasks // East (x+) → Code & Engineering // West (x-) → Conversations & Social // Center → Active Working Memory // Below (y-) → Archive (cold storage) // // Usage from app.js: // SpatialMemory.init(scene); // SpatialMemory.placeMemory({ id, content, category, ... }); // SpatialMemory.importIndex(savedIndex); // SpatialMemory.update(delta); // ═══════════════════════════════════════════ const SpatialMemory = (() => { // ─── REGION DEFINITIONS ─────────────────────────────── const REGIONS = { engineering: { label: 'Code & Engineering', center: [15, 0, 0], radius: 10, color: 0x4af0c0, glyph: '\u2699', description: 'Source code, debugging sessions, architecture decisions' }, social: { label: 'Conversations & Social', center: [-15, 0, 0], radius: 10, color: 0x7b5cff, glyph: '\uD83D\uDCAC', description: 'Chats, discussions, human interactions' }, knowledge: { label: 'Documents & Knowledge', center: [0, 0, -15], radius: 10, color: 0xffd700, glyph: '\uD83D\uDCD6', description: 'Papers, docs, research, learned concepts' }, projects: { label: 'Projects & Tasks', center: [0, 0, 15], radius: 10, color: 0xff4466, glyph: '\uD83C\uDFAF', description: 'Active tasks, issues, milestones, goals' }, working: { label: 'Active Working Memory', center: [0, 0, 0], radius: 5, color: 0x00ff88, glyph: '\uD83D\uDCA1', description: 'Current focus — transient, high-priority memories' }, archive: { label: 'Archive', center: [0, -3, 0], radius: 20, color: 0x334455, glyph: '\uD83D\uDDC4', description: 'Cold storage — rarely accessed, aged-out memories' } }; // ─── STATE ──────────────────────────────────────────── let _scene = null; let _regionMarkers = {}; let _memoryObjects = {}; let _connectionLines = []; let _initialized = false; // ─── CRYSTAL GEOMETRY (persistent memories) ─────────── function createCrystalGeometry(size) { return new THREE.OctahedronGeometry(size, 0); } // ─── REGION MARKER ─────────────────────────────────── function createRegionMarker(regionKey, region) { const cx = region.center[0]; const cy = region.center[1] + 0.06; const cz = region.center[2]; const ringGeo = new THREE.RingGeometry(region.radius - 0.5, region.radius, 6); const ringMat = new THREE.MeshBasicMaterial({ color: region.color, transparent: true, opacity: 0.15, side: THREE.DoubleSide }); const ring = new THREE.Mesh(ringGeo, ringMat); ring.rotation.x = -Math.PI / 2; ring.position.set(cx, cy, cz); ring.userData = { type: 'region_marker', region: regionKey }; const discGeo = new THREE.CircleGeometry(region.radius - 0.5, 6); const discMat = new THREE.MeshBasicMaterial({ color: region.color, transparent: true, opacity: 0.03, side: THREE.DoubleSide }); const disc = new THREE.Mesh(discGeo, discMat); disc.rotation.x = -Math.PI / 2; disc.position.set(cx, cy - 0.01, cz); _scene.add(ring); _scene.add(disc); // Floating label const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 64; const ctx = canvas.getContext('2d'); ctx.font = '24px monospace'; ctx.fillStyle = '#' + region.color.toString(16).padStart(6, '0'); ctx.textAlign = 'center'; ctx.fillText(region.glyph + ' ' + region.label, 128, 40); const texture = new THREE.CanvasTexture(canvas); const spriteMat = new THREE.SpriteMaterial({ map: texture, transparent: true, opacity: 0.6 }); const sprite = new THREE.Sprite(spriteMat); sprite.position.set(cx, 3, cz); sprite.scale.set(4, 1, 1); _scene.add(sprite); return { ring, disc, sprite }; } // ─── PLACE A MEMORY ────────────────────────────────── function placeMemory(mem) { if (!_scene) return null; const region = REGIONS[mem.category] || REGIONS.working; const pos = mem.position || _assignPosition(mem.category, mem.id); const strength = Math.max(0.05, Math.min(1, mem.strength != null ? mem.strength : 0.7)); const size = 0.2 + strength * 0.3; const geo = createCrystalGeometry(size); const mat = new THREE.MeshStandardMaterial({ color: region.color, emissive: region.color, emissiveIntensity: 1.5 * strength, metalness: 0.6, roughness: 0.15, transparent: true, opacity: 0.5 + strength * 0.4 }); const crystal = new THREE.Mesh(geo, mat); crystal.position.set(pos[0], pos[1] + 1.5, pos[2]); crystal.castShadow = true; crystal.userData = { type: 'spatial_memory', memId: mem.id, region: mem.category, pulse: Math.random() * Math.PI * 2, strength: strength, createdAt: mem.timestamp || new Date().toISOString() }; const light = new THREE.PointLight(region.color, 0.8 * strength, 5); crystal.add(light); _scene.add(crystal); _memoryObjects[mem.id] = { mesh: crystal, data: mem, region: mem.category }; if (mem.connections && mem.connections.length > 0) { _drawConnections(mem.id, mem.connections); } console.info('[Mnemosyne] Spatial memory placed:', mem.id, 'in', region.label); return crystal; } // ─── DETERMINISTIC POSITION ────────────────────────── function _assignPosition(category, memId) { const region = REGIONS[category] || REGIONS.working; const cx = region.center[0]; const cy = region.center[1]; const cz = region.center[2]; const r = region.radius * 0.7; let hash = 0; for (let i = 0; i < memId.length; i++) { hash = ((hash << 5) - hash) + memId.charCodeAt(i); hash |= 0; } const angle = (Math.abs(hash % 360) / 360) * Math.PI * 2; const dist = (Math.abs((hash >> 8) % 100) / 100) * r; const height = (Math.abs((hash >> 16) % 100) / 100) * 3; return [cx + Math.cos(angle) * dist, cy + height, cz + Math.sin(angle) * dist]; } // ─── CONNECTIONS ───────────────────────────────────── function _drawConnections(memId, connections) { const src = _memoryObjects[memId]; if (!src) return; connections.forEach(targetId => { const tgt = _memoryObjects[targetId]; if (!tgt) return; const points = [src.mesh.position.clone(), tgt.mesh.position.clone()]; const geo = new THREE.BufferGeometry().setFromPoints(points); const mat = new THREE.LineBasicMaterial({ color: 0x334455, transparent: true, opacity: 0.2 }); const line = new THREE.Line(geo, mat); line.userData = { type: 'connection', from: memId, to: targetId }; _scene.add(line); _connectionLines.push(line); }); } // ─── REMOVE A MEMORY ───────────────────────────────── function removeMemory(memId) { const obj = _memoryObjects[memId]; if (!obj) return; if (obj.mesh.parent) obj.mesh.parent.remove(obj.mesh); if (obj.mesh.geometry) obj.mesh.geometry.dispose(); if (obj.mesh.material) obj.mesh.material.dispose(); for (let i = _connectionLines.length - 1; i >= 0; i--) { const line = _connectionLines[i]; if (line.userData.from === memId || line.userData.to === memId) { if (line.parent) line.parent.remove(line); line.geometry.dispose(); line.material.dispose(); _connectionLines.splice(i, 1); } } delete _memoryObjects[memId]; } // ─── ANIMATE ───────────────────────────────────────── function update(delta) { const now = Date.now(); Object.values(_memoryObjects).forEach(obj => { const mesh = obj.mesh; if (!mesh || !mesh.userData) return; mesh.rotation.y += delta * 0.3; mesh.userData.pulse += delta * 1.5; const pulse = 1 + Math.sin(mesh.userData.pulse) * 0.08; mesh.scale.setScalar(pulse); if (mesh.material) { const base = mesh.userData.strength || 0.7; mesh.material.emissiveIntensity = 1.0 + Math.sin(mesh.userData.pulse * 0.7) * 0.5 * base; } }); Object.values(_regionMarkers).forEach(marker => { if (marker.ring && marker.ring.material) { marker.ring.material.opacity = 0.1 + Math.sin(now * 0.001) * 0.05; } }); } // ─── INIT ──────────────────────────────────────────── function init(scene) { _scene = scene; _initialized = true; Object.entries(REGIONS).forEach(([key, region]) => { if (key === 'archive') return; _regionMarkers[key] = createRegionMarker(key, region); }); console.info('[Mnemosyne] Spatial Memory Schema initialized —', Object.keys(REGIONS).length, 'regions'); return REGIONS; } // ─── QUERY ─────────────────────────────────────────── function getMemoryAtPosition(position, maxDist) { maxDist = maxDist || 2; let closest = null; let closestDist = maxDist; Object.values(_memoryObjects).forEach(obj => { const d = obj.mesh.position.distanceTo(position); if (d < closestDist) { closest = obj; closestDist = d; } }); return closest; } function getRegionAtPosition(position) { for (const [key, region] of Object.entries(REGIONS)) { const dx = position.x - region.center[0]; const dz = position.z - region.center[2]; if (Math.sqrt(dx * dx + dz * dz) <= region.radius) return key; } return null; } function getMemoriesInRegion(regionKey) { return Object.values(_memoryObjects).filter(o => o.region === regionKey); } function getAllMemories() { return Object.values(_memoryObjects).map(o => o.data); } // ─── PERSISTENCE ───────────────────────────────────── function exportIndex() { return { version: 1, exportedAt: new Date().toISOString(), regions: Object.fromEntries( Object.entries(REGIONS).map(([k, v]) => [k, { label: v.label, center: v.center, radius: v.radius, color: v.color }]) ), memories: Object.values(_memoryObjects).map(o => ({ id: o.data.id, content: o.data.content, category: o.region, position: [o.mesh.position.x, o.mesh.position.y - 1.5, o.mesh.position.z], source: o.data.source || 'unknown', timestamp: o.data.timestamp || o.mesh.userData.createdAt, strength: o.mesh.userData.strength || 0.7, connections: o.data.connections || [] })) }; } function importIndex(index) { if (!index || !index.memories) return 0; let count = 0; index.memories.forEach(mem => { if (!_memoryObjects[mem.id]) { placeMemory(mem); count++; } }); console.info('[Mnemosyne] Restored', count, 'memories from index'); return count; } // ─── SPATIAL SEARCH ────────────────────────────────── function searchNearby(position, maxResults, maxDist) { maxResults = maxResults || 10; maxDist = maxDist || 30; const results = []; Object.values(_memoryObjects).forEach(obj => { const d = obj.mesh.position.distanceTo(position); if (d <= maxDist) results.push({ memory: obj.data, distance: d, position: obj.mesh.position.clone() }); }); results.sort((a, b) => a.distance - b.distance); return results.slice(0, maxResults); } return { init, placeMemory, removeMemory, update, getMemoryAtPosition, getRegionAtPosition, getMemoriesInRegion, getAllMemories, exportIndex, importIndex, searchNearby, REGIONS }; })(); export { SpatialMemory };