Compare commits
3 Commits
mimo/code/
...
pr-1139
| 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 ═══
|
// ═══ STATE ═══
|
||||||
let camera, scene, renderer, composer;
|
let camera, scene, renderer, composer;
|
||||||
let clock, playerPos, playerRot;
|
let clock, playerPos, playerRot;
|
||||||
@@ -99,6 +239,9 @@ async function init() {
|
|||||||
updateLoad(10);
|
updateLoad(10);
|
||||||
|
|
||||||
scene = new THREE.Scene();
|
scene = new THREE.Scene();
|
||||||
|
window.mnemosyne = new MnemosyneManager(scene);
|
||||||
|
await mnemosyne.init();
|
||||||
|
simulateMemoryStream();
|
||||||
scene.fog = new THREE.FogExp2(0x050510, 0.012);
|
scene.fog = new THREE.FogExp2(0x050510, 0.012);
|
||||||
|
|
||||||
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
|
camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||||||
@@ -1760,6 +1903,46 @@ function closeVisionOverlay() {
|
|||||||
let lastThoughtTime = 0;
|
let lastThoughtTime = 0;
|
||||||
let pulseTimer = 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() {
|
function gameLoop() {
|
||||||
requestAnimationFrame(gameLoop);
|
requestAnimationFrame(gameLoop);
|
||||||
const delta = Math.min(clock.getDelta(), 0.1);
|
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