From 11d7b6dd25dcd35383c5395e428ae87b83b4e2ee Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Tue, 24 Mar 2026 00:31:47 -0400 Subject: [PATCH] feat: implement Timmy's mood lighting for Nexus atmosphere --- app.js | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 485320f..39d08ef 100644 --- a/app.js +++ b/app.js @@ -14,7 +14,14 @@ const NEXUS = { constellationLine: 0x334488, constellationFade: 0x112244, accent: 0x4488ff, - } + }, + moods: { + content: { bg: 0x002828, ambient: 1.4, particleIntensity: 0.9, particleSpeed: 0.01 }, + busy: { bg: 0x003838, ambient: 1.8, particleIntensity: 1.2, particleSpeed: 0.02 }, + contemplative: { bg: 0x000818, ambient: 0.8, particleIntensity: 0.6, particleSpeed: 0.005 }, + error: { bg: 0x280808, ambient: 1.0, particleIntensity: 0.8, particleSpeed: 0.01 } + }, + currentMood: 'content' }; // === ASSET LOADER === @@ -343,6 +350,37 @@ window.addEventListener('resize', () => { // === ANIMATION LOOP === const clock = new THREE.Clock(); +// Mood derivation variables +let chatActivity = 0; +let prMergeRate = 0; +let errorCount = 0; +let lastMoodUpdate = 0; + +/** + * Derives Timmy's mood based on current metrics. + */ +function deriveMood() { + const now = clock.getElapsedTime(); + if (now - lastMoodUpdate < 5) return; // Update mood every 5 seconds + lastMoodUpdate = now; + + if (errorCount > 3) { + NEXUS.currentMood = 'error'; + } else if (chatActivity > 5 || prMergeRate > 1) { + NEXUS.currentMood = 'busy'; + } else if (chatActivity < 2 && prMergeRate === 0) { + NEXUS.currentMood = 'contemplative'; + } else { + NEXUS.currentMood = 'content'; + } + + // Update scene atmosphere based on mood + const mood = NEXUS.moods[NEXUS.currentMood]; + scene.background = new THREE.Color(mood.bg); + ambientLight.intensity = mood.ambient; + starMaterial.opacity = mood.particleIntensity; +} + /** * Main animation loop — called each frame via requestAnimationFrame. * @returns {void} @@ -351,6 +389,9 @@ function animate() { // Only start animation after assets are loaded requestAnimationFrame(animate); const elapsed = clock.getElapsedTime(); + + // Update mood based on metrics + deriveMood(); // Smooth camera transition for overview mode const targetT = overviewMode ? 1 : 0; @@ -440,6 +481,10 @@ import { wsClient } from './ws-client.js'; wsClient.connect(); +// Simulate error count for mood derivation (replace with real error tracking) +setInterval(() => { + if (Math.random() < 0.1) errorCount++; +}, 10000); window.addEventListener('player-joined', (/** @type {CustomEvent} */ event) => { console.log('Player joined:', event.detail); }); @@ -450,6 +495,7 @@ window.addEventListener('player-left', (/** @type {CustomEvent} */ event) => { window.addEventListener('chat-message', (/** @type {CustomEvent} */ event) => { console.log('Chat message:', event.detail); + chatActivity++; if (typeof event.detail?.text === 'string' && event.detail.text.toLowerCase().includes('sovereignty')) { triggerSovereigntyEasterEgg(); } @@ -615,7 +661,10 @@ async function initCommitBanners() { ]; // Load commit banners after assets are ready - initCommitBanners(); +initCommitBanners(); + +// Update PR merge rate for mood derivation +prMergeRate++; } const spreadX = [-7, -3.5, 0, 3.5, 7];