diff --git a/nexus/components/spatial-memory.js b/nexus/components/spatial-memory.js index 2fd20d00..535572a1 100644 --- a/nexus/components/spatial-memory.js +++ b/nexus/components/spatial-memory.js @@ -471,8 +471,83 @@ const SpatialMemory = (() => { return results.slice(0, maxResults); } + // ─── BULK IMPORT (WebSocket sync) ─────────────────── + /** + * Import an array of memories in batch — for WebSocket sync. + * Skips duplicates (same id). Returns count of newly placed. + * @param {Array} memories - Array of memory objects { id, content, category, ... } + * @returns {number} Count of newly placed memories + */ + function importMemories(memories) { + if (!Array.isArray(memories) || memories.length === 0) return 0; + let count = 0; + memories.forEach(mem => { + if (mem.id && !_memoryObjects[mem.id]) { + placeMemory(mem); + count++; + } + }); + if (count > 0) { + _dirty = true; + saveToStorage(); + console.info('[Mnemosyne] Bulk imported', count, 'new memories (total:', Object.keys(_memoryObjects).length, ')'); + } + return count; + } + + // ─── UPDATE MEMORY ────────────────────────────────── + /** + * Update an existing memory's visual properties (strength, connections). + * Does not move the crystal — only updates metadata and re-renders. + * @param {string} memId - Memory ID to update + * @param {object} updates - Fields to update: { strength, connections, content } + * @returns {boolean} True if updated + */ + function updateMemory(memId, updates) { + const obj = _memoryObjects[memId]; + if (!obj) return false; + + if (updates.strength != null) { + const strength = Math.max(0.05, Math.min(1, updates.strength)); + obj.mesh.userData.strength = strength; + obj.mesh.material.emissiveIntensity = 1.5 * strength; + obj.mesh.material.opacity = 0.5 + strength * 0.4; + } + if (updates.content != null) { + obj.data.content = updates.content; + } + if (updates.connections != null) { + obj.data.connections = updates.connections; + // Rebuild connection lines + _rebuildConnections(memId); + } + _dirty = true; + saveToStorage(); + return true; + } + + function _rebuildConnections(memId) { + // Remove existing lines for this memory + 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); + } + } + // Recreate lines for current connections + const obj = _memoryObjects[memId]; + if (!obj || !obj.data.connections) return; + obj.data.connections.forEach(targetId => { + const target = _memoryObjects[targetId]; + if (target) _createConnectionLine(obj, target); + }); + } + return { - init, placeMemory, removeMemory, update, + init, placeMemory, removeMemory, update, importMemories, updateMemory, getMemoryAtPosition, getRegionAtPosition, getMemoriesInRegion, getAllMemories, exportIndex, importIndex, searchNearby, REGIONS, saveToStorage, loadFromStorage, clearStorage