Compare commits

..

2 Commits

Author SHA1 Message Date
6f949698fe Merge pull request #1148
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
Staging Verification Gate / verify-staging (push) Failing after 5s
Merged PR #1148
2026-04-10 03:43:56 +00:00
6cf1f4d078 feat(mnemosyne): implement memory orb system with game loop integration\n\n- Added spawnMemoryOrb() with PBR materials and point lighting\n- Added removeMemoryOrb() with proper resource disposal\n- Added animateMemoryOrbs() for pulse/fade animation\n- Added spawnRetrievalOrbs() for RAG result visualization\n- Integrated animateMemoryOrbs(delta) into gameLoop()\n- Orbs auto-fade after 30s with smooth 10s fade-out\n\nFixes #1147\nSupersedes PR #1147 (blocked by branch protection)
Some checks failed
CI / test (pull_request) Failing after 9s
CI / validate (pull_request) Failing after 19s
Review Approval Gate / verify-review (pull_request) Successful in 4s
2026-04-10 02:13:31 +00:00

111
app.js
View File

@@ -2573,6 +2573,12 @@ function gameLoop() {
updateAshStorm(delta, elapsed);
// Project Mnemosyne - Memory Orb Animation
if (typeof animateMemoryOrbs === 'function') {
animateMemoryOrbs(delta);
}
const mode = NAV_MODES[navModeIdx];
const chatActive = document.activeElement === document.getElementById('chat-input');
@@ -2771,6 +2777,12 @@ function gameLoop() {
composer.render();
updateAshStorm(delta, elapsed);
// Project Mnemosyne - Memory Orb Animation
if (typeof animateMemoryOrbs === 'function') {
animateMemoryOrbs(delta);
}
updatePortalTunnel(delta, elapsed);
if (workshopScanMat) workshopScanMat.uniforms.uTime.value = clock.getElapsedTime();
@@ -2934,11 +2946,13 @@ function updateAshStorm(delta, elapsed) {
}
// ═══════════════════════════════════════════
// PROJECT MNEMOSYNE — HOLOGRAPHIC MEMORY ORBS
// ═══════════════════════════════════════════
// Memory orbs registry for animation loop
const memoryOrbs = [];
/**
* Spawn a glowing memory orb at the given position.
* Used to visualize RAG retrievals and memory recalls in the Nexus.
@@ -2950,14 +2964,16 @@ function updateAshStorm(delta, elapsed) {
* @returns {THREE.Mesh} The created orb mesh
*/
function spawnMemoryOrb(position, color = 0x4af0c0, size = 0.5, metadata = {}) {
// Create geometry with higher detail for better glow effect
if (typeof THREE === 'undefined' || typeof scene === 'undefined') {
console.warn('[Mnemosyne] THREE/scene not available for orb spawn');
return null;
}
const geometry = new THREE.SphereGeometry(size, 32, 32);
// Use MeshStandardMaterial for PBR rendering with transmission
const material = new THREE.MeshStandardMaterial({
color: color,
emissive: color,
emissiveIntensity: 2.5, // High emissive for glow
emissiveIntensity: 2.5,
metalness: 0.3,
roughness: 0.2,
transparent: true,
@@ -2970,67 +2986,50 @@ function spawnMemoryOrb(position, color = 0x4af0c0, size = 0.5, metadata = {}) {
orb.castShadow = true;
orb.receiveShadow = true;
// Store metadata for interaction
orb.userData = {
type: 'memory_orb',
pulse: 0,
pulseSpeed: 0.002 + Math.random() * 0.001, // Slight variation
pulse: Math.random() * Math.PI * 2, // Random phase offset
pulseSpeed: 0.002 + Math.random() * 0.001,
originalScale: size,
metadata: metadata,
createdAt: Date.now()
};
// Add point light for local illumination
// Point light for local illumination
const light = new THREE.PointLight(color, 1.5, 8);
light.position.set(0, 0, 0);
orb.add(light);
// Add to scene if available
if (typeof scene !== 'undefined' && scene) {
scene.add(orb);
}
// Add to memory orbs registry for animation
if (typeof memoryOrbs !== 'undefined') {
memoryOrbs.push(orb);
}
scene.add(orb);
memoryOrbs.push(orb);
console.info('[Mnemosyne] Memory orb spawned:', metadata.source || 'unknown');
return orb;
}
/**
* Remove a memory orb from the scene.
* Remove a memory orb from the scene and dispose resources.
* @param {THREE.Mesh} orb - The orb to remove
*/
function removeMemoryOrb(orb) {
if (!orb) return;
// Remove from scene
if (orb.parent) {
orb.parent.remove(orb);
}
// Dispose geometry and material
if (orb.parent) orb.parent.remove(orb);
if (orb.geometry) orb.geometry.dispose();
if (orb.material) orb.material.dispose();
// Remove from registry
if (typeof memoryOrbs !== 'undefined') {
const idx = memoryOrbs.indexOf(orb);
if (idx > -1) memoryOrbs.splice(idx, 1);
}
const idx = memoryOrbs.indexOf(orb);
if (idx > -1) memoryOrbs.splice(idx, 1);
}
/**
* Animate all memory orbs (call from render loop).
* Animate all memory orbs — pulse, rotate, and fade.
* Called from gameLoop() every frame.
* @param {number} delta - Time since last frame
*/
function animateMemoryOrbs(delta) {
if (typeof memoryOrbs === 'undefined') return;
for (const orb of memoryOrbs) {
if (!orb.userData) continue;
for (let i = memoryOrbs.length - 1; i >= 0; i--) {
const orb = memoryOrbs[i];
if (!orb || !orb.userData) continue;
// Pulse animation
orb.userData.pulse += orb.userData.pulseSpeed * delta * 1000;
@@ -3040,34 +3039,37 @@ function animateMemoryOrbs(delta) {
// Gentle rotation
orb.rotation.y += delta * 0.5;
// Fade based on age (optional: orbs fade after 30 seconds)
// Fade after 30 seconds
const age = (Date.now() - orb.userData.createdAt) / 1000;
if (age > 30) {
const fadeStart = 30;
const fadeDuration = 10;
const fadeProgress = Math.min(1, (age - fadeStart) / fadeDuration);
const fadeProgress = Math.min(1, (age - 30) / fadeDuration);
orb.material.opacity = 0.85 * (1 - fadeProgress);
if (fadeProgress >= 1) {
removeMemoryOrb(orb);
i--; // Adjust index after removal
}
}
}
}
/**
* Spawn memory orbs for RAG retrieval results.
* @param {Array} results - Array of retrieval results with {content, score, source}
* @param {THREE.Vector3} center - Center position to spawn orbs around
* Spawn memory orbs arranged in a spiral for RAG retrieval results.
* @param {Array} results - Array of {content, score, source}
* @param {THREE.Vector3} center - Center position (default: above avatar)
*/
function spawnRetrievalOrbs(results, center = new THREE.Vector3(0, 2, 0)) {
if (!results || !Array.isArray(results)) return;
function spawnRetrievalOrbs(results, center) {
if (!results || !Array.isArray(results) || results.length === 0) return;
if (!center) {
center = new THREE.Vector3(0, 2, 0);
}
const colors = [0x4af0c0, 0x7b5cff, 0xffd700, 0xff4466, 0x00ff88];
const radius = 3;
results.forEach((result, i) => {
// Arrange in a spiral pattern
const angle = (i / results.length) * Math.PI * 2;
const height = (i / results.length) * 2 - 1;
@@ -3077,24 +3079,17 @@ function spawnRetrievalOrbs(results, center = new THREE.Vector3(0, 2, 0)) {
center.z + Math.sin(angle) * radius
);
// Color based on relevance score
const colorIdx = Math.min(colors.length - 1, Math.floor(result.score * colors.length));
const color = colors[colorIdx];
const colorIdx = Math.min(colors.length - 1, Math.floor((result.score || 0.5) * colors.length));
const size = 0.3 + (result.score || 0.5) * 0.4;
// Size based on score
const size = 0.3 + result.score * 0.4;
spawnMemoryOrb(position, color, size, {
source: result.source,
score: result.score,
contentPreview: result.content?.substring(0, 100) || ''
spawnMemoryOrb(position, colors[colorIdx], size, {
source: result.source || 'unknown',
score: result.score || 0,
contentPreview: (result.content || '').substring(0, 100)
});
});
}
// Initialize memory orbs registry
const memoryOrbs = [];
init().then(() => {
createAshStorm();
createPortalTunnel();