Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3fddc0585 | ||
|
|
84f75e1e51 | ||
|
|
4d9fd555a0 |
36
DESIGN.md
Normal file
36
DESIGN.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Project Mnemosyne: Spatial Memory Schema
|
||||
|
||||
## Overview
|
||||
Project Mnemosyne transforms the Nexus from a static environment into a real-time visualization of Timmy's holographic memory (fact_store).
|
||||
|
||||
## 1. Room Mapping (Categories)
|
||||
Memory categories are mapped to specific spatial zones in the Nexus to provide cognitive structure.
|
||||
|
||||
| Category | Nexus Room | Visual Theme |
|
||||
| :--- | :--- | :--- |
|
||||
| user_pref | The Library | Archive shelves, soft lighting, velvet |
|
||||
| project | The Workshop | Drafting tables, holographic blueprints, metal |
|
||||
| tool | The Armory | Server racks, neon circuitry, chrome |
|
||||
| general | The Commons | Open garden, floating islands, eclectic |
|
||||
|
||||
## 2. Object Mapping (Facts)
|
||||
Every single fact in the holographic memory is represented as a 3D object.
|
||||
|
||||
- **Object Type:** Depending on the fact's content, it is rendered as a primitive (sphere, cube, pyramid) or a specific asset.
|
||||
- **Luminosity (Trust):** The glow intensity of the object is tied to its trust score (0.0 - 1.0).
|
||||
- **Scale (Importance):** The size of the object is tied to the number of relations it has to other facts.
|
||||
|
||||
## 3. Spatial Distribution (Coordinates)
|
||||
Coordinates are calculated based on semantic adjacency.
|
||||
|
||||
- **Clustering:** Facts that are 'related' (via the fact_store adjacency graph) are placed closer together.
|
||||
- **Layout Algorithm:** A 3D force-directed graph layout is used within each room.
|
||||
- **Centroid:** The most 'central' fact (highest connectivity) is placed at the room's center.
|
||||
|
||||
## 4. Lifecycle Events
|
||||
The Nexus responds to real-time memory updates:
|
||||
- **FACT_CREATED:** A new object fades into existence at the calculated coordinate.
|
||||
- **FACT_UPDATED:** The object pulses and adjusts its luminosity/scale.
|
||||
- **FACT_REMOVED:** The object dissolves into particles.
|
||||
- **FACT_RECALLED:** The object emits a beam of light toward the User/Agent, signaling active use.
|
||||
|
||||
183
app.js
183
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);
|
||||
|
||||
34
mnemosyne_schema.json
Normal file
34
mnemosyne_schema.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"rooms": {
|
||||
"user_pref": {
|
||||
"name": "The Library",
|
||||
"theme": "Archive shelves, soft lighting, velvet",
|
||||
"visual_accent": "#C2B280"
|
||||
},
|
||||
"project": {
|
||||
"name": "The Workshop",
|
||||
"theme": "Drafting tables, holographic blueprints, metal",
|
||||
"visual_accent": "#4682B4"
|
||||
},
|
||||
"tool": {
|
||||
"name": "The Armory",
|
||||
"theme": "Server racks, neon circuitry, chrome",
|
||||
"visual_accent": "#00FF00"
|
||||
},
|
||||
"general": {
|
||||
"name": "The Commons",
|
||||
"theme": "Open garden, floating islands, eclectic",
|
||||
"visual_accent": "#FFD700"
|
||||
}
|
||||
},
|
||||
"object_mapping": {
|
||||
"trust_score": {
|
||||
"property": "luminosity",
|
||||
"range": [0.0, 1.0]
|
||||
},
|
||||
"connectivity": {
|
||||
"property": "scale",
|
||||
"range": [0.5, 2.0]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user