From f3fddc05857681982940857a91b4831bb19e99ea Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Wed, 8 Apr 2026 23:58:35 -0400 Subject: [PATCH] feat: implement Project Mnemosyne spatial memory visuals --- app.js | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/app.js b/app.js index 046b35c..50624a2 100644 --- a/app.js +++ b/app.js @@ -22,6 +22,146 @@ const NEXUS = { } }; +class MnemosyneManager { + constructor(scene) { + this.scene = scene; + this.schema = null; + this.memoryObjects = new Map(); // factId -> Mesh + this.rooms = {}; // category -> { center, color } + } + + async init() { + try { + const response = await fetch('./mnemosyne_schema.json'); + this.schema = await response.json(); + this._setupRooms(); + console.log('Mnemosyne initialized'); + } catch (e) { + console.error('Mnemosyne init failed:', e); + } + } + + _setupRooms() { + const roomConfigs = this.schema.rooms; + const roomNames = Object.keys(roomConfigs); + + // Arrange rooms in a circle around the center + const radius = 15; + roomNames.forEach((cat, i) => { + const angle = (i / roomNames.length) * Math.PI * 2; + this.rooms[cat] = { + name: roomConfigs[cat].name, + center: new THREE.Vector3(Math.cos(angle) * radius, 0, Math.sin(angle) * radius), + color: new THREE.Color(roomConfigs[cat].visual_accent) + }; + + // Add a visual marker for the room (a floating holographic sign) + this._createRoomMarker(cat); + }); + } + + _createRoomMarker(cat) { + const room = this.rooms[cat]; + const group = new THREE.Group(); + + const geo = new THREE.TorusGeometry(2, 0.05, 16, 100); + const mat = new THREE.MeshBasicMaterial({ color: room.color, transparent: true, opacity: 0.4 }); + const ring = new THREE.Mesh(geo, mat); + ring.rotation.x = Math.PI / 2; + group.add(ring); + + group.position.copy(room.center); + group.position.y = 0.1; + this.scene.add(group); + } + + spawnFact(fact) { + const { id, category, trust, importance, content } = fact; + const room = this.rooms[category]; + if (!room) return; + + // Visuals based on schema + const geo = this._getGeometryForFact(content); + const mat = new THREE.MeshPhysicalMaterial({ + color: room.color, + emissive: room.color, + emissiveIntensity: trust * 2, + transparent: true, + opacity: 0.8, + transmission: 0.5, + thickness: 1, + }); + + const mesh = new THREE.Mesh(geo, mat); + + // Position: random jitter around room center + mesh.position.copy(room.center); + mesh.position.x += (Math.random() - 0.5) * 4; + mesh.position.z += (Math.random() - 0.5) * 4; + mesh.position.y = 1 + Math.random() * 2; + + // Scale based on importance + const s = 0.1 + importance * 0.4; + mesh.scale.setScalar(s); + + this.memoryObjects.set(id, mesh); + this.scene.add(mesh); + + // Animation: Fade in + mesh.scale.setScalar(0); + new Promise(resolve => { + const start = performance.now(); + const duration = 1000; + const animate = (now) => { + const progress = Math.min((now - start) / duration, 1); + mesh.scale.setScalar(s * progress); + if (progress < 1) requestAnimationFrame(animate); + }; + requestAnimationFrame(animate); + }); + } + + _getGeometryForFact(content) { + const rand = Math.random(); + if (rand < 0.33) return new THREE.SphereGeometry(0.5, 16, 16); + if (rand < 0.66) return new THREE.BoxGeometry(0.5, 0.5, 0.5); + return new THREE.ConeGeometry(0.4, 0.7, 16); + } + + updateFact(fact) { + const mesh = this.memoryObjects.get(fact.id); + if (!mesh) return; + + // Update luminosity based on trust + mesh.material.emissiveIntensity = fact.trust * 2; + + // Update scale based on importance + const s = 0.1 + fact.importance * 0.4; + mesh.scale.setScalar(s); + } + + removeFact(id) { + const mesh = this.memoryObjects.get(id); + if (!mesh) return; + + // Animation: Fade out/shrink + const start = performance.now(); + const duration = 500; + const originalScale = mesh.scale.x; + const animate = (now) => { + const progress = Math.min((now - start) / duration, 1); + mesh.scale.setScalar(originalScale * (1 - progress)); + if (progress < 1) { + requestAnimationFrame(animate); + } else { + this.scene.remove(mesh); + this.memoryObjects.delete(id); + } + }; + requestAnimationFrame(animate); + } +} + // ═══ STATE ═══ let camera, scene, renderer, composer; let clock, playerPos, playerRot; @@ -99,6 +239,9 @@ async function init() { updateLoad(10); scene = new THREE.Scene(); + window.mnemosyne = new MnemosyneManager(scene); + await mnemosyne.init(); + simulateMemoryStream(); scene.fog = new THREE.FogExp2(0x050510, 0.012); camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000); @@ -1760,6 +1903,46 @@ function closeVisionOverlay() { let lastThoughtTime = 0; let pulseTimer = 0; + +// ═══ MNEMOSYNE SIMULATION ═══ +function simulateMemoryStream() { + const categories = ['user_pref', 'project', 'tool', 'general']; + const facts = []; + + setInterval(() => { + const id = 'fact_' + Math.random().toString(36).substr(2, 9); + const category = categories[Math.floor(Math.random() * categories.length)]; + const fact = { + id, + category, + trust: Math.random(), + importance: Math.random(), + content: 'Simulated holographic memory fragment ' + id + }; + + // We need access to the mnemosyne instance. + // I'll make it a global in app.js for simplicity in this prototype. + if (window.mnemosyne) { + window.mnemosyne.spawnFact(fact); + facts.push(fact); + } + + // Cleanup old facts + if (facts.length > 30) { + const oldFact = facts.shift(); + if (window.mnemosyne) window.mnemosyne.removeFact(oldFact.id); + } + + // Occasionally update a random fact + if (Math.random() > 0.7 && facts.length > 0) { + const target = facts[Math.floor(Math.random() * facts.length)]; + target.trust = Math.random(); + target.importance = Math.random(); + if (window.mnemosyne) window.mnemosyne.updateFact(target); + } + }, 7000); +} + function gameLoop() { requestAnimationFrame(gameLoop); const delta = Math.min(clock.getDelta(), 0.1);