From 078e92c3cbe47babd87ce7e1864e34b0bca9b195 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Mon, 23 Mar 2026 18:40:19 -0400 Subject: [PATCH] feat: add Kimi & Perplexity as visible workshop agents (#10) - Kimi agent placed at WORKBENCH (left, -13, 0, -4): animated figure with activity rings, desk + monitor showing current issue (#10), name label and floor zone marker - Perplexity agent placed at LIBRARY (right-front, 5, 0, 9): animated figure with orbiting research orbs, bookshelf with pulsing data crystals, floating research query panel - Both agents: glowing CapsuleGeometry body + head, PointLight, color- coded per brand (Kimi #ff8844, Perplexity #20d9d2), bobbing idle animation in game loop - HUD agent-status panel (top-right) shows both agents online with colored status dots and current task - AGENTS terminal panel updated to show spatial locations [WB]/[LB] Fixes #10 Co-Authored-By: Claude Sonnet 4.6 --- app.js | 327 ++++++++++++++++++++++++++++++++++++++++++++++++++++- index.html | 15 +++ style.css | 57 ++++++++++ 3 files changed, 397 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 60689a0..9af7599 100644 --- a/app.js +++ b/app.js @@ -35,6 +35,12 @@ let frameCount = 0, lastFPSTime = 0, fps = 0; let chatOpen = true; let loadProgress = 0; +// Agent colors +const AGENTS = { + kimi: { color: 0xff8844, hex: '#ff8844', label: 'KIMI', task: 'coding #10' }, + perplexity: { color: 0x20d9d2, hex: '#20d9d2', label: 'PERPLEXITY', task: 'research' }, +}; + // ═══ INIT ═══ function init() { clock = new THREE.Clock(); @@ -77,6 +83,9 @@ function init() { createDustParticles(); updateLoad(85); createAmbientStructures(); + updateLoad(88); + createKimiWorkbench(); + createPerplexityLibrary(); updateLoad(90); // Post-processing @@ -387,10 +396,10 @@ function createBatcaveTerminal() { title: '🤖 AGENTS', lines: [ 'Claude Code ● ACTIVE', - 'Kimi ● ACTIVE', + 'Kimi [WB] ● CODING', 'Gemini ○ STANDBY', 'Hermes ● ACTIVE', - 'Perplexity ● ACTIVE', + 'Perplexity[LB] ● RESEARCH', ], color: 0xff8844, }); @@ -787,6 +796,271 @@ function createAmbientStructures() { scene.add(pedestal); } +// ═══ AGENT: KIMI (Workbench — left side) ═══ +function createKimiWorkbench() { + const group = new THREE.Group(); + group.position.set(-13, 0, -4); + group.rotation.y = 0.45; + group.name = 'kimi-workbench'; + + const col = AGENTS.kimi.color; + const hex = AGENTS.kimi.hex; + + // Desk surface + const deskMat = new THREE.MeshStandardMaterial({ + color: 0x1a1a2e, roughness: 0.3, metalness: 0.7, + emissive: 0x1a0800, emissiveIntensity: 0.12, + }); + const desk = new THREE.Mesh(new THREE.BoxGeometry(3.5, 0.12, 1.5), deskMat); + desk.position.set(0, 1.38, 0); + desk.castShadow = true; + group.add(desk); + // Desk legs + [[-1.5, -0.6], [-1.5, 0.6], [1.5, -0.6], [1.5, 0.6]].forEach(([lx, lz]) => { + const leg = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.4, 0.1), deskMat.clone()); + leg.position.set(lx, 0.7, lz); + group.add(leg); + }); + + // Monitor body + const monitor = new THREE.Mesh( + new THREE.BoxGeometry(1.85, 1.15, 0.08), + new THREE.MeshStandardMaterial({ color: 0x050510, roughness: 0.5, metalness: 0.8 }) + ); + monitor.position.set(0, 2.22, -0.51); + group.add(monitor); + // Monitor screen (canvas texture) + const sc = document.createElement('canvas'); + sc.width = 256; sc.height = 160; + const sx = sc.getContext('2d'); + sx.fillStyle = '#000a14'; + sx.fillRect(0, 0, 256, 160); + sx.font = 'bold 13px "JetBrains Mono", monospace'; + sx.fillStyle = hex; + sx.fillText('> kimi@nexus:~$', 8, 20); + sx.fillStyle = '#4af0c0'; + sx.fillText('ISSUE #10: Agent Presence', 8, 40); + sx.fillStyle = '#a0b8d0'; + sx.fillText('Building workshop agents...', 8, 60); + sx.fillText('PROGRESS: ████████░░ 80%', 8, 80); + sx.fillStyle = hex; + sx.fillText('● CODING', 8, 110); + sx.font = '10px "JetBrains Mono", monospace'; + sx.fillStyle = '#3a4a5a'; + sx.fillText('branch: claude/issue-10', 8, 140); + const screenMesh = new THREE.Mesh( + new THREE.PlaneGeometry(1.7, 1.05), + new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(sc), side: THREE.FrontSide }) + ); + screenMesh.position.set(0, 2.22, -0.465); + group.add(screenMesh); + // Monitor screen glow + const screenGlow = new THREE.Mesh( + new THREE.PlaneGeometry(1.85, 1.15), + new THREE.MeshBasicMaterial({ color: col, transparent: true, opacity: 0.06, side: THREE.DoubleSide }) + ); + screenGlow.position.set(0, 2.22, -0.52); + group.add(screenGlow); + + // Agent body + const figMat = new THREE.MeshPhysicalMaterial({ + color: col, emissive: col, emissiveIntensity: 0.45, + roughness: 0.2, metalness: 0.6, transparent: true, opacity: 0.85, + }); + const body = new THREE.Mesh(new THREE.CapsuleGeometry(0.22, 0.72, 8, 16), figMat); + body.name = 'kimi-body'; + body.position.set(-1.5, 2.0, 0.4); + group.add(body); + const head = new THREE.Mesh(new THREE.SphereGeometry(0.2, 16, 16), figMat.clone()); + head.name = 'kimi-head'; + head.position.set(-1.5, 2.92, 0.4); + group.add(head); + + // Activity rings + const rMat = new THREE.MeshBasicMaterial({ color: col, transparent: true, opacity: 0.6 }); + const ring1 = new THREE.Mesh(new THREE.TorusGeometry(0.44, 0.025, 8, 32), rMat); + ring1.name = 'kimi-ring1'; + ring1.position.set(-1.5, 2.42, 0.4); + group.add(ring1); + const ring2 = new THREE.Mesh(new THREE.TorusGeometry(0.6, 0.018, 8, 32), rMat.clone()); + ring2.name = 'kimi-ring2'; + ring2.position.set(-1.5, 2.42, 0.4); + ring2.rotation.x = Math.PI / 3; + group.add(ring2); + + // Point light + const glow = new THREE.PointLight(col, 1.8, 7, 1.5); + glow.position.set(-1.5, 2.5, 0.4); + group.add(glow); + + // Name label + const lc = document.createElement('canvas'); + lc.width = 256; lc.height = 48; + const lx = lc.getContext('2d'); + lx.font = 'bold 26px "Orbitron", sans-serif'; + lx.fillStyle = hex; + lx.textAlign = 'center'; + lx.fillText('◈ KIMI', 128, 34); + const label = new THREE.Mesh( + new THREE.PlaneGeometry(1.8, 0.34), + new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(lc), transparent: true, side: THREE.DoubleSide }) + ); + label.position.set(-1.5, 3.55, 0.4); + group.add(label); + + // Floor zone marker + const zc = document.createElement('canvas'); + zc.width = 320; zc.height = 40; + const zx = zc.getContext('2d'); + zx.font = '15px "JetBrains Mono", monospace'; + zx.fillStyle = '#5a3a1a'; + zx.textAlign = 'center'; + zx.fillText('[ WORKBENCH ]', 160, 26); + const zone = new THREE.Mesh( + new THREE.PlaneGeometry(3, 0.3), + new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(zc), transparent: true, side: THREE.DoubleSide }) + ); + zone.position.set(0, 0.06, 0); + zone.rotation.x = -Math.PI / 2; + group.add(zone); + + scene.add(group); +} + +// ═══ AGENT: PERPLEXITY (Library — right-front) ═══ +function createPerplexityLibrary() { + const group = new THREE.Group(); + group.position.set(5, 0, 9); + group.rotation.y = Math.PI + 0.2; + group.name = 'perplexity-library'; + + const col = AGENTS.perplexity.color; + const hex = AGENTS.perplexity.hex; + + const shelfMat = new THREE.MeshStandardMaterial({ + color: 0x0a0f1a, roughness: 0.4, metalness: 0.6, + emissive: 0x001a18, emissiveIntensity: 0.12, + }); + + // Shelf back panel + const back = new THREE.Mesh(new THREE.BoxGeometry(3.6, 4.6, 0.14), shelfMat); + back.position.set(0, 2.3, -0.45); + group.add(back); + // Shelf sides + for (const sx of [-1.8, 1.8]) { + const side = new THREE.Mesh(new THREE.BoxGeometry(0.12, 4.6, 0.65), shelfMat.clone()); + side.position.set(sx, 2.3, -0.2); + group.add(side); + } + // Shelf boards + data crystals + for (let i = 0; i < 4; i++) { + const board = new THREE.Mesh(new THREE.BoxGeometry(3.35, 0.08, 0.55), shelfMat.clone()); + board.position.set(0, 0.55 + i * 1.1, -0.2); + group.add(board); + for (let j = 0; j < 5; j++) { + const crystalGeo = new THREE.OctahedronGeometry(0.09 + (i + j) % 3 * 0.03, 0); + const crystalMat = new THREE.MeshPhysicalMaterial({ + color: col, emissive: col, + emissiveIntensity: 0.5 + ((i * 3 + j) % 5) * 0.12, + roughness: 0.1, metalness: 0.3, transmission: 0.4, thickness: 0.5, + }); + const crystal = new THREE.Mesh(crystalGeo, crystalMat); + crystal.name = `px-crystal-${i}-${j}`; + crystal.position.set(-1.4 + j * 0.7, 0.67 + i * 1.1, -0.05); + crystal.rotation.set((j % 3) * 0.4, (i + j) * 0.8, (i % 2) * 0.3); + group.add(crystal); + } + } + + // Agent body + const figMat = new THREE.MeshPhysicalMaterial({ + color: col, emissive: col, emissiveIntensity: 0.45, + roughness: 0.2, metalness: 0.6, transparent: true, opacity: 0.85, + }); + const body = new THREE.Mesh(new THREE.CapsuleGeometry(0.22, 0.72, 8, 16), figMat); + body.name = 'perplexity-body'; + body.position.set(1.8, 2.0, 0.55); + group.add(body); + const head = new THREE.Mesh(new THREE.SphereGeometry(0.2, 16, 16), figMat.clone()); + head.name = 'perplexity-head'; + head.position.set(1.8, 2.92, 0.55); + group.add(head); + + // Orbiting research orbs + const orbMat = new THREE.MeshBasicMaterial({ color: col }); + for (let i = 0; i < 3; i++) { + const orb = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 8), orbMat.clone()); + orb.name = `perplexity-orb-${i}`; + group.add(orb); + } + + // Point light + const glow = new THREE.PointLight(col, 1.8, 7, 1.5); + glow.position.set(1.8, 2.5, 0.55); + group.add(glow); + + // Research query panel + const qc = document.createElement('canvas'); + qc.width = 320; qc.height = 160; + const qx = qc.getContext('2d'); + qx.fillStyle = 'rgba(0,10,10,0.85)'; + qx.fillRect(0, 0, 320, 160); + qx.font = 'bold 14px "JetBrains Mono", monospace'; + qx.fillStyle = hex; + qx.fillText('PERPLEXITY RESEARCH', 10, 22); + qx.strokeStyle = hex; + qx.globalAlpha = 0.25; + qx.beginPath(); qx.moveTo(10, 30); qx.lineTo(310, 30); qx.stroke(); + qx.globalAlpha = 1; + qx.font = '12px "JetBrains Mono", monospace'; + qx.fillStyle = '#88c8c8'; + qx.fillText('QUERY: agent spatial presence', 10, 52); + qx.fillText('SOURCES: 12 indexed', 10, 72); + qx.fillText('CONFIDENCE: ████████ 94%', 10, 92); + qx.fillStyle = hex; + qx.fillText('● RESEARCHING', 10, 126); + const queryPanel = new THREE.Mesh( + new THREE.PlaneGeometry(2.2, 1.1), + new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(qc), transparent: true, side: THREE.DoubleSide }) + ); + queryPanel.name = 'perplexity-query'; + queryPanel.position.set(1.8, 3.65, 0.85); + group.add(queryPanel); + + // Name label + const lc = document.createElement('canvas'); + lc.width = 320; lc.height = 48; + const lx = lc.getContext('2d'); + lx.font = 'bold 22px "Orbitron", sans-serif'; + lx.fillStyle = hex; + lx.textAlign = 'center'; + lx.fillText('◈ PERPLEXITY', 160, 34); + const label = new THREE.Mesh( + new THREE.PlaneGeometry(2.2, 0.34), + new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(lc), transparent: true, side: THREE.DoubleSide }) + ); + label.position.set(1.8, 5.1, 0.55); + group.add(label); + + // Floor zone marker + const zc = document.createElement('canvas'); + zc.width = 320; zc.height = 40; + const zx = zc.getContext('2d'); + zx.font = '15px "JetBrains Mono", monospace'; + zx.fillStyle = '#1a3a38'; + zx.textAlign = 'center'; + zx.fillText('[ LIBRARY ]', 160, 26); + const zone = new THREE.Mesh( + new THREE.PlaneGeometry(3, 0.3), + new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(zc), transparent: true, side: THREE.DoubleSide }) + ); + zone.position.set(0, 0.06, 0); + zone.rotation.x = -Math.PI / 2; + group.add(zone); + + scene.add(group); +} + // ═══ CONTROLS ═══ function setupControls() { document.addEventListener('keydown', (e) => { @@ -941,6 +1215,55 @@ function gameLoop() { } } + // Animate Kimi + const kimiBody = scene.getObjectByName('kimi-body'); + const kimiHead = scene.getObjectByName('kimi-head'); + const kimiRing1 = scene.getObjectByName('kimi-ring1'); + const kimiRing2 = scene.getObjectByName('kimi-ring2'); + if (kimiBody) { + const bob = Math.sin(elapsed * 2.8) * 0.05; + kimiBody.position.y = 2.0 + bob; + kimiHead.position.y = 2.92 + bob; + } + if (kimiRing1) { + kimiRing1.rotation.z = elapsed * 2.8; + kimiRing1.rotation.y = elapsed * 1.0; + } + if (kimiRing2) { + kimiRing2.rotation.z = -elapsed * 2.0; + kimiRing2.rotation.x = Math.PI / 3 + elapsed * 0.9; + } + + // Animate Perplexity + const pxBody = scene.getObjectByName('perplexity-body'); + const pxHead = scene.getObjectByName('perplexity-head'); + if (pxBody) { + const bob = Math.sin(elapsed * 1.6 + 1.5) * 0.04; + pxBody.position.y = 2.0 + bob; + pxHead.position.y = 2.92 + bob; + } + for (let i = 0; i < 3; i++) { + const orb = scene.getObjectByName(`perplexity-orb-${i}`); + if (orb) { + const angle = elapsed * 1.8 + (i / 3) * Math.PI * 2; + orb.position.set( + 1.8 + Math.cos(angle) * 0.5, + 2.5 + Math.sin(angle * 0.7) * 0.18, + 0.55 + Math.sin(angle) * 0.5 + ); + } + } + const pxQuery = scene.getObjectByName('perplexity-query'); + if (pxQuery) { + pxQuery.position.y = 3.65 + Math.sin(elapsed * 0.9) * 0.1; + } + for (let i = 0; i < 4; i++) { + for (let j = 0; j < 5; j++) { + const cx = scene.getObjectByName(`px-crystal-${i}-${j}`); + if (cx) cx.material.emissiveIntensity = 0.4 + Math.sin(elapsed * 1.5 + i + j * 0.8) * 0.28; + } + } + // Animate nexus core const core = scene.getObjectByName('nexus-core'); if (core) { diff --git a/index.html b/index.html index 3a2c6ea..55fdeb8 100644 --- a/index.html +++ b/index.html @@ -95,6 +95,21 @@ + +
+
AGENTS ONLINE
+
+ + KIMI + coding #10 · workbench +
+
+ + PERPLEXITY + research · library +
+
+
WASD move   Mouse look   Enter chat diff --git a/style.css b/style.css index 519b05e..746eccf 100644 --- a/style.css +++ b/style.css @@ -330,6 +330,63 @@ canvas#nexus-canvas { background: rgba(74, 240, 192, 0.1); } +/* === AGENT STATUS PANEL === */ +.agent-status-panel { + position: absolute; + top: var(--space-3); + right: var(--space-3); + background: var(--color-surface); + backdrop-filter: blur(var(--panel-blur)); + border: 1px solid var(--color-border); + border-radius: var(--panel-radius); + padding: var(--space-2) var(--space-3) var(--space-3); + pointer-events: none; + min-width: 200px; +} +.agent-status-header { + font-family: var(--font-display); + font-size: 10px; + letter-spacing: 0.14em; + color: var(--color-text-muted); + margin-bottom: var(--space-2); + padding-bottom: var(--space-1); + border-bottom: 1px solid var(--color-border); +} +.agent-row { + display: flex; + align-items: center; + gap: var(--space-2); + padding: 3px 0; + font-size: var(--text-xs); +} +.agent-dot { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; + animation: dot-pulse 2s ease-in-out infinite; +} +.agent-dot-kimi { + background: #ff8844; + box-shadow: 0 0 6px #ff8844; +} +.agent-dot-perplexity { + background: #20d9d2; + box-shadow: 0 0 6px #20d9d2; + animation-delay: 0.7s; +} +.agent-name { + font-weight: 700; + color: var(--color-text-bright); + letter-spacing: 0.05em; + min-width: 76px; + font-size: 11px; +} +.agent-task { + color: var(--color-text-muted); + font-size: 10px; +} + /* === FOOTER === */ .nexus-footer { position: fixed; -- 2.43.0