Compare commits
2 Commits
mimo/code/
...
feat/mnemo
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cfbef970e | |||
| b1f5b1b859 |
227
app.js
227
app.js
@@ -2576,6 +2576,8 @@ function gameLoop() {
|
|||||||
// Project Mnemosyne - Memory Orb Animation
|
// Project Mnemosyne - Memory Orb Animation
|
||||||
if (typeof animateMemoryOrbs === 'function') {
|
if (typeof animateMemoryOrbs === 'function') {
|
||||||
animateMemoryOrbs(delta);
|
animateMemoryOrbs(delta);
|
||||||
|
animateHolographicThreads(delta);
|
||||||
|
animateRoomTransitions(delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -3090,7 +3092,232 @@ function spawnRetrievalOrbs(results, center) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
// MNEMOSYNE — SPATIAL MEMORY INTEGRATION
|
||||||
|
// ═══════════════════════════════════════════
|
||||||
|
|
||||||
|
// Spatial memory schema state (loaded async)
|
||||||
|
let spatialMemorySchema = null;
|
||||||
|
const holographicThreads = []; // Active thread meshes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the spatial memory schema and store it for room mapping.
|
||||||
|
* Called during init. Falls back gracefully if schema unavailable.
|
||||||
|
*/
|
||||||
|
async function loadSpatialMemorySchema() {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('/spatial-memory-schema.json');
|
||||||
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||||
|
spatialMemorySchema = await resp.json();
|
||||||
|
console.info('[Mnemosyne] Spatial memory schema loaded:',
|
||||||
|
Object.keys(spatialMemorySchema.rooms).length, 'rooms');
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[Mnemosyne] Could not load spatial schema, using defaults:', err.message);
|
||||||
|
spatialMemorySchema = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the room definition for a memory category.
|
||||||
|
* @param {string} category - Memory category (user_pref, project, tool, general)
|
||||||
|
* @returns {object|null} Room definition with spatial_bounds and visual_theme
|
||||||
|
*/
|
||||||
|
function getRoomForCategory(category) {
|
||||||
|
if (!spatialMemorySchema) return null;
|
||||||
|
for (const [roomId, room] of Object.entries(spatialMemorySchema.rooms)) {
|
||||||
|
if (room.category === category) return { id: roomId, ...room };
|
||||||
|
}
|
||||||
|
// Fallback to commons for unknown categories
|
||||||
|
if (spatialMemorySchema.rooms.commons) {
|
||||||
|
return { id: 'commons', ...spatialMemorySchema.rooms.commons };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate a random position within a room's spatial bounds.
|
||||||
|
* @param {object} room - Room definition with spatial_bounds
|
||||||
|
* @returns {THREE.Vector3} Position within room bounds
|
||||||
|
*/
|
||||||
|
function getPositionInRoom(room) {
|
||||||
|
const bounds = room.spatial_bounds;
|
||||||
|
const dims = bounds.dimensions;
|
||||||
|
const center = bounds.center;
|
||||||
|
|
||||||
|
return new THREE.Vector3(
|
||||||
|
center[0] + (Math.random() - 0.5) * dims[0] * 0.8,
|
||||||
|
center[1] + (Math.random() - 0.5) * dims[1] * 0.6 + 1.5, // Float above floor
|
||||||
|
center[2] + (Math.random() - 0.5) * dims[2] * 0.8
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a categorized memory orb placed in its corresponding room.
|
||||||
|
* Extends spawnMemoryOrb with spatial placement based on category.
|
||||||
|
*
|
||||||
|
* @param {string} category - Memory category (user_pref, project, tool, general)
|
||||||
|
* @param {object} metadata - Memory metadata (source, content, score, etc.)
|
||||||
|
* @param {number} importance - 0-1 importance score (affects size/glow)
|
||||||
|
* @returns {THREE.Mesh} The spawned orb
|
||||||
|
*/
|
||||||
|
function spawnCategorizedOrb(category, metadata = {}, importance = 0.5) {
|
||||||
|
const room = getRoomForCategory(category);
|
||||||
|
const position = room ? getPositionInRoom(room) : new THREE.Vector3(0, 2, 0);
|
||||||
|
|
||||||
|
// Color from schema trust mapping or room theme
|
||||||
|
let color = 0x4af0c0; // Default cyan
|
||||||
|
if (room && room.visual_theme) {
|
||||||
|
const accent = room.visual_theme.colors?.accent;
|
||||||
|
if (accent) color = parseInt(accent.replace('#', ''), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size scales with importance
|
||||||
|
const size = 0.2 + importance * 0.5;
|
||||||
|
|
||||||
|
const orb = spawnMemoryOrb(position, color, size, {
|
||||||
|
...metadata,
|
||||||
|
category: category,
|
||||||
|
room: room ? room.id : 'unknown'
|
||||||
|
});
|
||||||
|
|
||||||
|
return orb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a holographic thread connecting two memory orbs.
|
||||||
|
* @param {THREE.Mesh} orbA - First orb
|
||||||
|
* @param {THREE.Mesh} orbB - Second orb
|
||||||
|
* @param {number} color - Thread color (default: 0x4af0c0)
|
||||||
|
* @returns {THREE.Line} The thread mesh
|
||||||
|
*/
|
||||||
|
function drawHolographicThread(orbA, orbB, color = 0x4af0c0) {
|
||||||
|
if (typeof THREE === 'undefined' || !orbA || !orbB) return null;
|
||||||
|
|
||||||
|
const points = [orbA.position.clone(), orbB.position.clone()];
|
||||||
|
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||||
|
const material = new THREE.LineBasicMaterial({
|
||||||
|
color: color,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.4,
|
||||||
|
linewidth: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const thread = new THREE.Line(geometry, material);
|
||||||
|
thread.userData = {
|
||||||
|
type: 'holographic_thread',
|
||||||
|
orbA: orbA,
|
||||||
|
orbB: orbB,
|
||||||
|
createdAt: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
scene.add(thread);
|
||||||
|
holographicThreads.push(thread);
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update holographic threads to follow their connected orbs.
|
||||||
|
* Called from the animation loop.
|
||||||
|
* @param {number} delta - Time since last frame
|
||||||
|
*/
|
||||||
|
function animateHolographicThreads(delta) {
|
||||||
|
for (let i = holographicThreads.length - 1; i >= 0; i--) {
|
||||||
|
const thread = holographicThreads[i];
|
||||||
|
if (!thread || !thread.userData) continue;
|
||||||
|
|
||||||
|
const { orbA, orbB } = thread.userData;
|
||||||
|
|
||||||
|
// Remove thread if either orb is gone
|
||||||
|
if (!orbA || !orbA.parent || !orbB || !orbB.parent) {
|
||||||
|
scene.remove(thread);
|
||||||
|
thread.geometry.dispose();
|
||||||
|
thread.material.dispose();
|
||||||
|
holographicThreads.splice(i, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update line positions to follow orbs
|
||||||
|
const positions = thread.geometry.attributes.position;
|
||||||
|
positions.setXYZ(0, orbA.position.x, orbA.position.y, orbA.position.z);
|
||||||
|
positions.setXYZ(1, orbB.position.x, orbB.position.y, orbB.position.z);
|
||||||
|
positions.needsUpdate = true;
|
||||||
|
|
||||||
|
// Pulse opacity
|
||||||
|
const age = (Date.now() - thread.userData.createdAt) / 1000;
|
||||||
|
thread.material.opacity = 0.3 + Math.sin(age * 2) * 0.1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a memory orb and animate it transitioning to its room.
|
||||||
|
* @param {string} category - Memory category
|
||||||
|
* @param {object} metadata - Memory metadata
|
||||||
|
* @param {number} importance - 0-1 importance score
|
||||||
|
* @param {THREE.Vector3} startPos - Starting position (default: above avatar)
|
||||||
|
* @returns {THREE.Mesh} The orb (already in transit)
|
||||||
|
*/
|
||||||
|
function spawnWithRoomTransition(category, metadata = {}, importance = 0.5, startPos = null) {
|
||||||
|
if (!startPos) startPos = new THREE.Vector3(0, 2, 0);
|
||||||
|
|
||||||
|
const room = getRoomForCategory(category);
|
||||||
|
const endPos = room ? getPositionInRoom(room) : new THREE.Vector3(0, 2, 0);
|
||||||
|
|
||||||
|
let color = 0x4af0c0;
|
||||||
|
if (room && room.visual_theme) {
|
||||||
|
const accent = room.visual_theme.colors?.accent;
|
||||||
|
if (accent) color = parseInt(accent.replace('#', ''), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = 0.2 + importance * 0.5;
|
||||||
|
|
||||||
|
// Spawn at start position
|
||||||
|
const orb = spawnMemoryOrb(startPos, color, size, {
|
||||||
|
...metadata,
|
||||||
|
category: category,
|
||||||
|
room: room ? room.id : 'unknown',
|
||||||
|
transitioning: true,
|
||||||
|
targetPos: endPos,
|
||||||
|
transitionStart: Date.now(),
|
||||||
|
transitionDuration: 2000 + Math.random() * 1000
|
||||||
|
});
|
||||||
|
|
||||||
|
return orb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate room transitions for orbs that are in transit.
|
||||||
|
* @param {number} delta - Time since last frame
|
||||||
|
*/
|
||||||
|
function animateRoomTransitions(delta) {
|
||||||
|
for (const orb of memoryOrbs) {
|
||||||
|
if (!orb.userData?.transitioning || !orb.userData?.targetPos) continue;
|
||||||
|
|
||||||
|
const elapsed = Date.now() - orb.userData.transitionStart;
|
||||||
|
const duration = orb.userData.transitionDuration;
|
||||||
|
const progress = Math.min(1, elapsed / duration);
|
||||||
|
|
||||||
|
// Ease-out cubic
|
||||||
|
const eased = 1 - Math.pow(1 - progress, 3);
|
||||||
|
|
||||||
|
orb.position.lerpVectors(
|
||||||
|
orb.position, // Current (already partially moved)
|
||||||
|
orb.userData.targetPos,
|
||||||
|
eased * 0.05 // Smooth interpolation factor per frame
|
||||||
|
);
|
||||||
|
|
||||||
|
if (progress >= 1) {
|
||||||
|
orb.position.copy(orb.userData.targetPos);
|
||||||
|
orb.userData.transitioning = false;
|
||||||
|
delete orb.userData.targetPos;
|
||||||
|
delete orb.userData.transitionStart;
|
||||||
|
delete orb.userData.transitionDuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
init().then(() => {
|
init().then(() => {
|
||||||
|
loadSpatialMemorySchema();
|
||||||
createAshStorm();
|
createAshStorm();
|
||||||
createPortalTunnel();
|
createPortalTunnel();
|
||||||
fetchGiteaData();
|
fetchGiteaData();
|
||||||
|
|||||||
312
spatial-memory-schema.json
Normal file
312
spatial-memory-schema.json
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"project": "Mnemosyne",
|
||||||
|
"description": "Spatial memory schema for holographic memory visualization",
|
||||||
|
"rooms": {
|
||||||
|
"library": {
|
||||||
|
"name": "The Library",
|
||||||
|
"category": "user_pref",
|
||||||
|
"description": "User preferences and personal settings",
|
||||||
|
"visual_theme": {
|
||||||
|
"lighting": "soft_ambient",
|
||||||
|
"colors": {
|
||||||
|
"primary": "#8B4513",
|
||||||
|
"secondary": "#DAA520",
|
||||||
|
"accent": "#FFD700",
|
||||||
|
"particle": "#FFE4B5"
|
||||||
|
},
|
||||||
|
"materials": {
|
||||||
|
"floor": "dark_wood",
|
||||||
|
"walls": "bookshelf",
|
||||||
|
"ceiling": "vaulted_stone"
|
||||||
|
},
|
||||||
|
"particle_effects": [
|
||||||
|
"dust_motes",
|
||||||
|
"book_sparkles"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"spatial_bounds": {
|
||||||
|
"center": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"dimensions": [
|
||||||
|
20,
|
||||||
|
10,
|
||||||
|
20
|
||||||
|
],
|
||||||
|
"orb_density": 0.7
|
||||||
|
},
|
||||||
|
"object_types": {
|
||||||
|
"preference": {
|
||||||
|
"shape": "sphere",
|
||||||
|
"base_size": 0.3,
|
||||||
|
"glow_intensity": 0.8
|
||||||
|
},
|
||||||
|
"setting": {
|
||||||
|
"shape": "cube",
|
||||||
|
"base_size": 0.4,
|
||||||
|
"glow_intensity": 0.6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"workshop": {
|
||||||
|
"name": "The Workshop",
|
||||||
|
"category": "project",
|
||||||
|
"description": "Active projects and development work",
|
||||||
|
"visual_theme": {
|
||||||
|
"lighting": "bright_work",
|
||||||
|
"colors": {
|
||||||
|
"primary": "#4682B4",
|
||||||
|
"secondary": "#B0C4DE",
|
||||||
|
"accent": "#00BFFF",
|
||||||
|
"particle": "#87CEEB"
|
||||||
|
},
|
||||||
|
"materials": {
|
||||||
|
"floor": "polished_concrete",
|
||||||
|
"walls": "blueprint_paper",
|
||||||
|
"ceiling": "industrial_metal"
|
||||||
|
},
|
||||||
|
"particle_effects": [
|
||||||
|
"blueprint_lines",
|
||||||
|
"tool_sparks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"spatial_bounds": {
|
||||||
|
"center": [
|
||||||
|
30,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"dimensions": [
|
||||||
|
25,
|
||||||
|
12,
|
||||||
|
25
|
||||||
|
],
|
||||||
|
"orb_density": 0.8
|
||||||
|
},
|
||||||
|
"object_types": {
|
||||||
|
"project": {
|
||||||
|
"shape": "pyramid",
|
||||||
|
"base_size": 0.5,
|
||||||
|
"glow_intensity": 0.9
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"shape": "cube",
|
||||||
|
"base_size": 0.3,
|
||||||
|
"glow_intensity": 0.7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"armory": {
|
||||||
|
"name": "The Armory",
|
||||||
|
"category": "tool",
|
||||||
|
"description": "Tools, skills, and capabilities",
|
||||||
|
"visual_theme": {
|
||||||
|
"lighting": "neon_glow",
|
||||||
|
"colors": {
|
||||||
|
"primary": "#2E8B57",
|
||||||
|
"secondary": "#3CB371",
|
||||||
|
"accent": "#00FF7F",
|
||||||
|
"particle": "#98FB98"
|
||||||
|
},
|
||||||
|
"materials": {
|
||||||
|
"floor": "chrome_grid",
|
||||||
|
"walls": "server_rack",
|
||||||
|
"ceiling": "neon_tube"
|
||||||
|
},
|
||||||
|
"particle_effects": [
|
||||||
|
"data_streams",
|
||||||
|
"circuit_traces"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"spatial_bounds": {
|
||||||
|
"center": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
30
|
||||||
|
],
|
||||||
|
"dimensions": [
|
||||||
|
15,
|
||||||
|
8,
|
||||||
|
15
|
||||||
|
],
|
||||||
|
"orb_density": 0.6
|
||||||
|
},
|
||||||
|
"object_types": {
|
||||||
|
"tool": {
|
||||||
|
"shape": "octahedron",
|
||||||
|
"base_size": 0.4,
|
||||||
|
"glow_intensity": 1.0
|
||||||
|
},
|
||||||
|
"skill": {
|
||||||
|
"shape": "sphere",
|
||||||
|
"base_size": 0.35,
|
||||||
|
"glow_intensity": 0.85
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commons": {
|
||||||
|
"name": "The Commons",
|
||||||
|
"category": "general",
|
||||||
|
"description": "General knowledge and miscellaneous facts",
|
||||||
|
"visual_theme": {
|
||||||
|
"lighting": "natural_daylight",
|
||||||
|
"colors": {
|
||||||
|
"primary": "#9370DB",
|
||||||
|
"secondary": "#BA55D3",
|
||||||
|
"accent": "#DA70D6",
|
||||||
|
"particle": "#E6E6FA"
|
||||||
|
},
|
||||||
|
"materials": {
|
||||||
|
"floor": "grass",
|
||||||
|
"walls": "floating_islands",
|
||||||
|
"ceiling": "open_sky"
|
||||||
|
},
|
||||||
|
"particle_effects": [
|
||||||
|
"floating_pollen",
|
||||||
|
"lightning_bugs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"spatial_bounds": {
|
||||||
|
"center": [
|
||||||
|
30,
|
||||||
|
0,
|
||||||
|
30
|
||||||
|
],
|
||||||
|
"dimensions": [
|
||||||
|
30,
|
||||||
|
15,
|
||||||
|
30
|
||||||
|
],
|
||||||
|
"orb_density": 0.5
|
||||||
|
},
|
||||||
|
"object_types": {
|
||||||
|
"fact": {
|
||||||
|
"shape": "sphere",
|
||||||
|
"base_size": 0.25,
|
||||||
|
"glow_intensity": 0.7
|
||||||
|
},
|
||||||
|
"memory": {
|
||||||
|
"shape": "dodecahedron",
|
||||||
|
"base_size": 0.3,
|
||||||
|
"glow_intensity": 0.65
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"object_properties": {
|
||||||
|
"trust_mapping": {
|
||||||
|
"description": "Maps trust score (0.0-1.0) to visual properties",
|
||||||
|
"glow_intensity": {
|
||||||
|
"min": 0.2,
|
||||||
|
"max": 1.0,
|
||||||
|
"curve": "linear"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"min": 0.3,
|
||||||
|
"max": 1.0,
|
||||||
|
"curve": "ease_in_out"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"importance_mapping": {
|
||||||
|
"description": "Maps importance (relation count) to visual properties",
|
||||||
|
"scale": {
|
||||||
|
"min": 0.2,
|
||||||
|
"max": 2.0,
|
||||||
|
"curve": "logarithmic"
|
||||||
|
},
|
||||||
|
"particle_density": {
|
||||||
|
"min": 10,
|
||||||
|
"max": 100,
|
||||||
|
"curve": "linear"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lifecycle_events": {
|
||||||
|
"FACT_CREATED": {
|
||||||
|
"animation": "fade_in",
|
||||||
|
"duration": 1.5,
|
||||||
|
"particle_effect": "spawn_burst"
|
||||||
|
},
|
||||||
|
"FACT_UPDATED": {
|
||||||
|
"animation": "pulse_glow",
|
||||||
|
"duration": 0.8,
|
||||||
|
"particle_effect": "update_ripple"
|
||||||
|
},
|
||||||
|
"FACT_REMOVED": {
|
||||||
|
"animation": "dissolve",
|
||||||
|
"duration": 2.0,
|
||||||
|
"particle_effect": "scatter"
|
||||||
|
},
|
||||||
|
"FACT_RECALLED": {
|
||||||
|
"animation": "beam_light",
|
||||||
|
"duration": 1.0,
|
||||||
|
"particle_effect": "recall_beam"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"holographic_threads": {
|
||||||
|
"description": "Visual connections between related memory orbs",
|
||||||
|
"material": "transparent_glow",
|
||||||
|
"colors": {
|
||||||
|
"strong_relation": "#00FFFF",
|
||||||
|
"medium_relation": "#00CED1",
|
||||||
|
"weak_relation": "#5F9EA0"
|
||||||
|
},
|
||||||
|
"thickness": {
|
||||||
|
"min": 0.02,
|
||||||
|
"max": 0.1,
|
||||||
|
"curve": "linear"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cross_room_portals": {
|
||||||
|
"description": "Portals connecting different memory rooms",
|
||||||
|
"effect": "swirling_vortex",
|
||||||
|
"colors": {
|
||||||
|
"library_workshop": "#FFD700",
|
||||||
|
"workshop_armory": "#00BFFF",
|
||||||
|
"armory_commons": "#00FF7F",
|
||||||
|
"commons_library": "#DA70D6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rag_integration": {
|
||||||
|
"retrieval_visualization": {
|
||||||
|
"description": "How RAG retrieval results are visualized",
|
||||||
|
"highlight_effect": "golden_glow",
|
||||||
|
"spiral_arrangement": {
|
||||||
|
"radius": 3.0,
|
||||||
|
"height_step": 0.5,
|
||||||
|
"rotation_step": 0.618033988749895
|
||||||
|
},
|
||||||
|
"relevance_scoring": {
|
||||||
|
"high": {
|
||||||
|
"color": "#FFD700",
|
||||||
|
"size_multiplier": 1.5
|
||||||
|
},
|
||||||
|
"medium": {
|
||||||
|
"color": "#FFA500",
|
||||||
|
"size_multiplier": 1.2
|
||||||
|
},
|
||||||
|
"low": {
|
||||||
|
"color": "#FF8C00",
|
||||||
|
"size_multiplier": 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query_beam": {
|
||||||
|
"description": "Beam from user to relevant memory orbs",
|
||||||
|
"color": "#FFFFFF",
|
||||||
|
"opacity": 0.8,
|
||||||
|
"pulse_frequency": 2.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"animation_timing": {
|
||||||
|
"orb_spawn_delay": 0.1,
|
||||||
|
"room_transition_duration": 2.0,
|
||||||
|
"connection_draw_speed": 0.5,
|
||||||
|
"particle_fade_time": 1.5
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user