diff --git a/app.js b/app.js index e94f10b..da340c8 100644 --- a/app.js +++ b/app.js @@ -30,10 +30,13 @@ let mouseDown = false; let batcaveTerminals = []; let portals = []; // Registry of active portals let visionPoints = []; // Registry of vision points +let agents = []; // Registry of agent presences let activePortal = null; // Portal currently in proximity let activeVisionPoint = null; // Vision point currently in proximity let portalOverlayActive = false; let visionOverlayActive = false; +let thoughtStreamMesh; +let harnessPulseMesh; let particles, dustParticles; let debugOverlay; let frameCount = 0, lastFPSTime = 0, fps = 0; @@ -116,6 +119,9 @@ async function init() { createDustParticles(); updateLoad(85); createAmbientStructures(); + createAgentPresences(); + createThoughtStream(); + createHarnessPulse(); updateLoad(90); composer = new EffectComposer(renderer); @@ -430,6 +436,113 @@ function createTerminalPanel(parent, x, y, rot, title, color, lines) { batcaveTerminals.push({ group, scanMat, borderMat }); } +// ═══ AGENT PRESENCE SYSTEM ═══ +function createAgentPresences() { + const agentData = [ + { id: 'timmy', name: 'TIMMY', color: NEXUS.colors.primary, pos: { x: -4, z: -4 } }, + { id: 'kimi', name: 'KIMI', color: NEXUS.colors.secondary, pos: { x: 4, z: -4 } }, + { id: 'claude', name: 'CLAUDE', color: NEXUS.colors.gold, pos: { x: 0, z: -6 } }, + ]; + + agentData.forEach(data => { + const group = new THREE.Group(); + group.position.set(data.pos.x, 0, data.pos.z); + + const color = new THREE.Color(data.color); + + // Agent Orb + const orbGeo = new THREE.SphereGeometry(0.4, 32, 32); + const orbMat = new THREE.MeshPhysicalMaterial({ + color: color, + emissive: color, + emissiveIntensity: 2, + roughness: 0, + metalness: 1, + transmission: 0.8, + thickness: 0.5, + }); + const orb = new THREE.Mesh(orbGeo, orbMat); + orb.position.y = 3; + group.add(orb); + + // Halo + const haloGeo = new THREE.TorusGeometry(0.6, 0.02, 16, 64); + const haloMat = new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 0.4 }); + const halo = new THREE.Mesh(haloGeo, haloMat); + halo.position.y = 3; + halo.rotation.x = Math.PI / 2; + group.add(halo); + + // Label + const canvas = document.createElement('canvas'); + canvas.width = 256; + canvas.height = 64; + const ctx = canvas.getContext('2d'); + ctx.font = 'bold 24px "Orbitron", sans-serif'; + ctx.fillStyle = '#' + color.getHexString(); + ctx.textAlign = 'center'; + ctx.fillText(data.name, 128, 40); + const tex = new THREE.CanvasTexture(canvas); + const mat = new THREE.MeshBasicMaterial({ map: tex, transparent: true, side: THREE.DoubleSide }); + const label = new THREE.Mesh(new THREE.PlaneGeometry(2, 0.5), mat); + label.position.y = 3.8; + group.add(label); + + scene.add(group); + agents.push({ id: data.id, group, orb, halo, color }); + }); +} + +function createThoughtStream() { + const geo = new THREE.CylinderGeometry(8, 8, 12, 32, 1, true); + const mat = new THREE.ShaderMaterial({ + transparent: true, + side: THREE.BackSide, + depthWrite: false, + uniforms: { + uTime: { value: 0 }, + uColor: { value: new THREE.Color(NEXUS.colors.primary) }, + }, + vertexShader: ` + varying vec2 vUv; + void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } + `, + fragmentShader: ` + uniform float uTime; + uniform vec3 uColor; + varying vec2 vUv; + + float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); } + + void main() { + float flow = fract(vUv.y - uTime * 0.1); + float lines = step(0.98, fract(vUv.x * 20.0 + uTime * 0.05)); + float dots = step(0.99, hash(vUv * 50.0 + floor(uTime * 10.0) * 0.01)); + + float alpha = (lines * 0.1 + dots * 0.5) * smoothstep(0.0, 0.2, vUv.y) * smoothstep(1.0, 0.8, vUv.y); + gl_FragColor = vec4(uColor, alpha * 0.3); + } + `, + }); + thoughtStreamMesh = new THREE.Mesh(geo, mat); + thoughtStreamMesh.position.y = 6; + scene.add(thoughtStreamMesh); +} + +function createHarnessPulse() { + const geo = new THREE.RingGeometry(0.1, 0.2, 64); + const mat = new THREE.MeshBasicMaterial({ + color: NEXUS.colors.primary, + transparent: true, + opacity: 0, + side: THREE.DoubleSide, + }); + harnessPulseMesh = new THREE.Mesh(geo, mat); + harnessPulseMesh.rotation.x = -Math.PI / 2; + harnessPulseMesh.position.y = 0.1; + scene.add(harnessPulseMesh); +} + // ═══ VISION SYSTEM ═══ function createVisionPoints(data) { data.forEach(config => { @@ -1042,11 +1155,31 @@ function closeVisionOverlay() { } // ═══ GAME LOOP ═══ +let lastThoughtTime = 0; +let pulseTimer = 0; + function gameLoop() { requestAnimationFrame(gameLoop); const delta = Math.min(clock.getDelta(), 0.1); const elapsed = clock.elapsedTime; + // Agent Thought Simulation + if (elapsed - lastThoughtTime > 4) { + lastThoughtTime = elapsed; + simulateAgentThought(); + } + + // Harness Pulse + pulseTimer += delta; + if (pulseTimer > 8) { + pulseTimer = 0; + triggerHarnessPulse(); + } + if (harnessPulseMesh) { + harnessPulseMesh.scale.addScalar(delta * 15); + harnessPulseMesh.material.opacity = Math.max(0, harnessPulseMesh.material.opacity - delta * 0.5); + } + const mode = NAV_MODES[navModeIdx]; const chatActive = document.activeElement === document.getElementById('chat-input'); @@ -1152,6 +1285,19 @@ function gameLoop() { vp.light.intensity = 1 + Math.sin(elapsed * 3) * 0.3; }); + // Animate Agents + agents.forEach((agent, i) => { + agent.orb.position.y = 3 + Math.sin(elapsed * 2 + i) * 0.15; + agent.halo.rotation.z = elapsed * 0.5; + agent.halo.scale.setScalar(1 + Math.sin(elapsed * 3 + i) * 0.1); + agent.orb.material.emissiveIntensity = 2 + Math.sin(elapsed * 4 + i) * 1; + }); + + if (thoughtStreamMesh) { + thoughtStreamMesh.material.uniforms.uTime.value = elapsed; + thoughtStreamMesh.rotation.y = elapsed * 0.05; + } + if (particles?.material?.uniforms) { particles.material.uniforms.uTime.value = elapsed; } @@ -1203,4 +1349,63 @@ function onResize() { composer.setSize(w, h); } +// ═══ AGENT SIMULATION ═══ +function simulateAgentThought() { + const agentIds = ['timmy', 'kimi', 'claude']; + const agentId = agentIds[Math.floor(Math.random() * agentIds.length)]; + const thoughts = { + timmy: [ + 'Analyzing portal stability...', + 'Sovereign nodes synchronized.', + 'Memory stream optimization complete.', + 'Scanning for external interference...', + 'The harness is humming beautifully.', + ], + kimi: [ + 'Processing linguistic patterns...', + 'Context window expanded.', + 'Synthesizing creative output...', + 'Awaiting user prompt sequence.', + 'Neural weights adjusted.', + ], + claude: [ + 'Reasoning through complex logic...', + 'Ethical guardrails verified.', + 'Refining thought architecture...', + 'Connecting disparate data points.', + 'Deep analysis in progress.', + ] + }; + + const thought = thoughts[agentId][Math.floor(Math.random() * thoughts[agentId].length)]; + addAgentLog(agentId, thought); +} + +function addAgentLog(agentId, text) { + const container = document.getElementById('agent-log-content'); + if (!container) return; + + const entry = document.createElement('div'); + entry.className = 'agent-log-entry'; + entry.innerHTML = `[${agentId.toUpperCase()}]${text}`; + + container.prepend(entry); + if (container.children.length > 6) { + container.lastElementChild.remove(); + } +} + +function triggerHarnessPulse() { + if (!harnessPulseMesh) return; + harnessPulseMesh.scale.setScalar(0.1); + harnessPulseMesh.material.opacity = 0.8; + + // Flash the core + const core = scene.getObjectByName('nexus-core'); + if (core) { + core.material.emissiveIntensity = 10; + setTimeout(() => { if (core) core.material.emissiveIntensity = 2; }, 200); + } +} + init(); diff --git a/index.html b/index.html index dfdf76a..dd4d42d 100644 --- a/index.html +++ b/index.html @@ -74,6 +74,12 @@ The Nexus + +