Files
hermes-agent/config/nexus-templates/portal_template.js
Allegro d9cf77e382 feat: Issue #42 - Nexus Architect for autonomous Three.js world building
Implement Phase 31: Autonomous 'Nexus' Expansion & Architecture

DELIVERABLES:
- agent/nexus_architect.py: AI agent for natural language to Three.js conversion
  * Prompt engineering for LLM-driven immersive environment generation
  * Mental state integration for dynamic aesthetic tuning
  * Mood preset system (contemplative, energetic, mysterious, etc.)
  * Room and portal design generation

- tools/nexus_build_tool.py: Build tool interface with functions:
  * create_room(name, description, style) - Generate room modules
  * create_portal(from_room, to_room, style) - Generate portal connections
  * add_lighting(room, type, color, intensity) - Add Three.js lighting
  * add_geometry(room, shape, position, material) - Add 3D objects
  * generate_scene_from_mood(mood_description) - Mood-based generation
  * deploy_nexus_module(module_code, test=True) - Deploy and test

- agent/nexus_deployment.py: Real-time deployment system
  * Hot-reload Three.js modules without page refresh
  * Validation (syntax check, Three.js API compliance)
  * Rollback on error with version history
  * Module versioning and status tracking

- config/nexus-templates/: Template library
  * base_room.js - Base room template (Three.js r128+)
  * portal_template.js - Portal template (circular, rectangular, stargate)
  * lighting_presets.json - Warm, cool, dramatic, serene, crystalline presets
  * material_presets.json - 15 material presets including Timmy's gold, Allegro blue

- tests/test_nexus_architect.py: Comprehensive test coverage
  * Unit tests for all components
  * Integration tests for full workflow
  * Template file validation

DESIGN PRINCIPLES:
- Modular architecture (each room = separate JS module)
- Valid Three.js code (r128+ compatible)
- Hot-reloadable (no page refresh needed)
- Mental state integration (SOUL.md values influence aesthetic)

NEXUS AESTHETIC GUIDELINES:
- Timmy's color: warm gold (#D4AF37)
- Allegro's color: motion blue (#4A90E2)
- Sovereignty theme: crystalline structures, clean lines
- Service theme: open spaces, welcoming lighting
- Default mood: contemplative, expansive, hopeful
2026-04-01 02:45:36 +00:00

340 lines
9.9 KiB
JavaScript

/**
* Nexus Portal Template
*
* Template for creating portals between rooms.
* Supports multiple visual styles and transition effects.
*
* Compatible with Three.js r128+
*/
(function() {
'use strict';
/**
* Portal configuration
*/
const PORTAL_CONFIG = {
colors: {
frame: '#D4AF37', // Timmy's gold
energy: '#4A90E2', // Allegro blue
core: '#FFFFFF',
},
animation: {
rotationSpeed: 0.5,
pulseSpeed: 2.0,
pulseAmplitude: 0.1,
},
collision: {
radius: 2.0,
height: 4.0,
}
};
/**
* Create a portal
* @param {string} fromRoom - Source room name
* @param {string} toRoom - Target room name
* @param {string} style - Portal style (circular, rectangular, stargate)
* @returns {THREE.Group} The portal group
*/
function createPortal(fromRoom, toRoom, style = 'circular') {
const portal = new THREE.Group();
portal.name = `portal_${fromRoom}_to_${toRoom}`;
portal.userData = {
type: 'portal',
fromRoom: fromRoom,
toRoom: toRoom,
isActive: true,
style: style,
};
// Create based on style
switch(style) {
case 'rectangular':
createRectangularPortal(portal);
break;
case 'stargate':
createStargatePortal(portal);
break;
case 'circular':
default:
createCircularPortal(portal);
break;
}
// Add collision trigger
createTriggerZone(portal);
// Setup animation
setupAnimation(portal);
return portal;
}
/**
* Create circular portal (default)
*/
function createCircularPortal(portal) {
const { frame, energy } = PORTAL_CONFIG.colors;
// Outer frame
const frameGeo = new THREE.TorusGeometry(2, 0.2, 16, 100);
const frameMat = new THREE.MeshStandardMaterial({
color: frame,
emissive: frame,
emissiveIntensity: 0.5,
roughness: 0.3,
metalness: 0.9,
});
const frameMesh = new THREE.Mesh(frameGeo, frameMat);
frameMesh.castShadow = true;
frameMesh.name = 'frame';
portal.add(frameMesh);
// Inner energy field
const fieldGeo = new THREE.CircleGeometry(1.8, 64);
const fieldMat = new THREE.MeshBasicMaterial({
color: energy,
transparent: true,
opacity: 0.4,
side: THREE.DoubleSide,
});
const field = new THREE.Mesh(fieldGeo, fieldMat);
field.name = 'energy_field';
portal.add(field);
// Particle ring
createParticleRing(portal);
}
/**
* Create rectangular portal
*/
function createRectangularPortal(portal) {
const { frame, energy } = PORTAL_CONFIG.colors;
const width = 3;
const height = 4;
// Frame segments
const frameMat = new THREE.MeshStandardMaterial({
color: frame,
emissive: frame,
emissiveIntensity: 0.5,
roughness: 0.3,
metalness: 0.9,
});
// Create frame border
const borderGeo = new THREE.BoxGeometry(width + 0.4, height + 0.4, 0.2);
const border = new THREE.Mesh(borderGeo, frameMat);
border.name = 'frame';
portal.add(border);
// Inner field
const fieldGeo = new THREE.PlaneGeometry(width, height);
const fieldMat = new THREE.MeshBasicMaterial({
color: energy,
transparent: true,
opacity: 0.4,
side: THREE.DoubleSide,
});
const field = new THREE.Mesh(fieldGeo, fieldMat);
field.name = 'energy_field';
portal.add(field);
}
/**
* Create stargate-style portal
*/
function createStargatePortal(portal) {
const { frame } = PORTAL_CONFIG.colors;
// Main ring
const ringGeo = new THREE.TorusGeometry(2, 0.3, 16, 100);
const ringMat = new THREE.MeshStandardMaterial({
color: frame,
emissive: frame,
emissiveIntensity: 0.4,
roughness: 0.4,
metalness: 0.8,
});
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.name = 'main_ring';
portal.add(ring);
// Chevron decorations
for (let i = 0; i < 9; i++) {
const angle = (i / 9) * Math.PI * 2;
const chevron = createChevron();
chevron.position.set(
Math.cos(angle) * 2,
Math.sin(angle) * 2,
0
);
chevron.rotation.z = angle + Math.PI / 2;
chevron.name = `chevron_${i}`;
portal.add(chevron);
}
// Inner vortex
const vortexGeo = new THREE.CircleGeometry(1.7, 32);
const vortexMat = new THREE.MeshBasicMaterial({
color: PORTAL_CONFIG.colors.energy,
transparent: true,
opacity: 0.5,
});
const vortex = new THREE.Mesh(vortexGeo, vortexMat);
vortex.name = 'vortex';
portal.add(vortex);
}
/**
* Create a chevron for stargate style
*/
function createChevron() {
const shape = new THREE.Shape();
shape.moveTo(-0.2, 0);
shape.lineTo(0, 0.4);
shape.lineTo(0.2, 0);
shape.lineTo(-0.2, 0);
const geo = new THREE.ExtrudeGeometry(shape, {
depth: 0.1,
bevelEnabled: false
});
const mat = new THREE.MeshStandardMaterial({
color: PORTAL_CONFIG.colors.frame,
emissive: PORTAL_CONFIG.colors.frame,
emissiveIntensity: 0.3,
});
return new THREE.Mesh(geo, mat);
}
/**
* Create particle ring effect
*/
function createParticleRing(portal) {
const particleCount = 50;
const particles = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const angle = (i / particleCount) * Math.PI * 2;
const radius = 2 + (Math.random() - 0.5) * 0.4;
positions[i * 3] = Math.cos(angle) * radius;
positions[i * 3 + 1] = Math.sin(angle) * radius;
positions[i * 3 + 2] = (Math.random() - 0.5) * 0.5;
}
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const particleMat = new THREE.PointsMaterial({
color: PORTAL_CONFIG.colors.energy,
size: 0.05,
transparent: true,
opacity: 0.8,
});
const particleSystem = new THREE.Points(particles, particleMat);
particleSystem.name = 'particles';
portal.add(particleSystem);
}
/**
* Create trigger zone for teleportation
*/
function createTriggerZone(portal) {
const triggerGeo = new THREE.CylinderGeometry(
PORTAL_CONFIG.collision.radius,
PORTAL_CONFIG.collision.radius,
PORTAL_CONFIG.collision.height,
32
);
const triggerMat = new THREE.MeshBasicMaterial({
color: 0x00ff00,
transparent: true,
opacity: 0.0, // Invisible
wireframe: true,
});
const trigger = new THREE.Mesh(triggerGeo, triggerMat);
trigger.position.y = PORTAL_CONFIG.collision.height / 2;
trigger.name = 'trigger_zone';
trigger.userData.isTrigger = true;
portal.add(trigger);
}
/**
* Setup portal animation
*/
function setupAnimation(portal) {
const { rotationSpeed, pulseSpeed, pulseAmplitude } = PORTAL_CONFIG.animation;
portal.userData.animate = function(time) {
// Rotate energy field
const energyField = this.getObjectByName('energy_field') ||
this.getObjectByName('vortex');
if (energyField) {
energyField.rotation.z = time * rotationSpeed;
}
// Pulse effect
const pulse = 1 + Math.sin(time * pulseSpeed) * pulseAmplitude;
const frame = this.getObjectByName('frame') ||
this.getObjectByName('main_ring');
if (frame) {
frame.scale.set(pulse, pulse, 1);
}
// Animate particles
const particles = this.getObjectByName('particles');
if (particles) {
particles.rotation.z = -time * rotationSpeed * 0.5;
}
};
}
/**
* Check if a point is inside the portal trigger zone
*/
function checkTrigger(portal, point) {
const trigger = portal.getObjectByName('trigger_zone');
if (!trigger) return false;
// Simple distance check
const dx = point.x - portal.position.x;
const dz = point.z - portal.position.z;
const distance = Math.sqrt(dx * dx + dz * dz);
return distance < PORTAL_CONFIG.collision.radius;
}
/**
* Activate/deactivate portal
*/
function setActive(portal, active) {
portal.userData.isActive = active;
const energyField = portal.getObjectByName('energy_field') ||
portal.getObjectByName('vortex');
if (energyField) {
energyField.visible = active;
}
}
// Export
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
createPortal,
checkTrigger,
setActive,
PORTAL_CONFIG
};
} else if (typeof window !== 'undefined') {
window.NexusPortals = window.NexusPortals || {};
window.NexusPortals.create = createPortal;
}
return { createPortal, checkTrigger, setActive, PORTAL_CONFIG };
})();