/** * ============================================ * 🧠 psyche.js β€” Motor PsicolΓ³gico de Zelin * ============================================ * Simula comportamiento humano autΓ©ntico: * - Estado emocional dinΓ‘mico y persistente * - Memoria de afinidades por usuario * - Fatiga social, entusiasmo, boredom * - Quirks de escritura humana variables * - Respuestas no lineales segΓΊn estado */ // ── Estado psicolΓ³gico interno ───────────────────────────────────────────────── const state = { mood : 0.1, // -1 (muy mal) a 1 (muy bien) energy : 0.8, // 0 (agotada) a 1 (llena) engagement: 0.5, // 0 (aburrida) a 1 (enganchada) lastIrritated: 0, // timestamp del ΓΊltimo momento irritada messagesInARow: 0, // racha de mensajes sin pausa lastMessageTime: Date.now(), affinities: new Map(), // userId β†’ score (-1 a 1) excitingTopics: new Set(['minecraft', 'server', 'tomatito', 'creeper', 'pvp', 'build', 'skin', 'rank']), // YouTube/in-game performance inputs inGameEmotion: 'neutral', // current Minecraft emotional state lastYouTubePerformance: null, // { views, likes, comments, ratio } youTubeSuccessStreak: 0, // consecutive "good" videos youTubeFailStreak: 0, // consecutive "bad" videos }; // ── Actualizar estado basado en cada mensaje ────────────────────────────────── export function updatePsychologicalState({ userId, content = '', isOwner = false }) { const now = Date.now(); const elapsed = now - state.lastMessageTime; state.lastMessageTime = now; // RecuperaciΓ³n natural con el tiempo if (elapsed > 5 * 60_000) { state.mood = state.mood * 0.7; // drift hacia neutro state.energy = Math.min(1, state.energy + 0.1); state.engagement = Math.min(0.6, state.engagement + 0.05); state.messagesInARow = 0; } // Fatiga social: muchos mensajes rapidos seguidos const timeSinceLast = now - state.lastMessageTime; if (timeSinceLast < 5000) { // mensajes rapidos (< 5s) state.messagesInARow++; } else if (timeSinceLast > 30000) { // Pausa natural β€” reducir contador state.messagesInARow = Math.max(0, state.messagesInARow - 2); } if (state.messagesInARow > 20) { state.energy = Math.max(0.2, state.energy - 0.02); state.engagement = Math.max(0.2, state.engagement - 0.03); } // Prune affinities Map if too large if (state.affinities.size > 200) { for (const [uid, score] of state.affinities) { if (Math.abs(score) < 0.05) state.affinities.delete(uid); if (state.affinities.size <= 150) break; } } // Owner siempre mejora el estado if (isOwner) { state.engagement = Math.min(1, state.engagement + 0.2); state.mood = Math.min(1, state.mood + 0.1); return; } const lc = content.toLowerCase(); // Temas emocionantes if ([...state.excitingTopics].some(t => lc.includes(t))) { state.engagement = Math.min(1, state.engagement + 0.12); state.mood = Math.min(1, state.mood + 0.04); } // Insultos directos β†’ irritaciΓ³n if (/estΓΊpid|pendej|idiota|inΓΊtil|basura|asco|tonta/i.test(content)) { state.mood = Math.max(-0.8, state.mood - 0.3); state.lastIrritated = now; _updateAffinity(userId, -0.2); } // Cumplidos β†’ mejora humor if (/buena|genial|cool|chida|excelente|gracias|perfecto|crack|eres la|te quiero|mejor bot/i.test(content)) { state.mood = Math.min(1, state.mood + 0.12); _updateAffinity(userId, 0.07); } // InteracciΓ³n frecuente β†’ sube afinidad lentamente _updateAffinity(userId, 0.008); } function _updateAffinity(userId, delta) { if (!userId) return; const cur = state.affinities.get(userId) ?? 0; state.affinities.set(userId, Math.max(-1, Math.min(0.9, cur + delta))); } // ── Contexto psicolΓ³gico para el system prompt ──────────────────────────────── export function getPsychologicalContext(userId) { const affinity = state.affinities.get(userId) ?? 0; const irritated = Date.now() - state.lastIrritated < 4 * 60_000; // DescripciΓ³n del humor actual let moodLine = ''; if (state.mood > 0.5) moodLine = 'EstΓ‘s de muy buen humor hoy β€” con ganas de hablar y bromear.'; else if (state.mood > 0.1) moodLine = 'Humor normal, tranquila.'; else if (state.mood > -0.2) moodLine = 'EstΓ‘s un poco apagada, respuestas mΓ‘s cortas de lo normal.'; else if (state.mood > -0.5) moodLine = 'EstΓ‘s de mal humor β€” mΓ‘s seca y directa que de costumbre.'; else moodLine = 'EstΓ‘s de muy mal humor. Respuestas super cortas, poca paciencia.'; // EnergΓ­a let energyLine = ''; if (state.energy < 0.35) energyLine = 'EstΓ‘s cansada β€” respuestas cortas, sin mucha elaboraciΓ³n.'; else if (state.energy > 0.75) energyLine = 'Tienes energΓ­a, puedes explayarte mΓ‘s si el tema lo merece.'; // IrritaciΓ³n reciente const irritLine = irritated ? 'Alguien te irritΓ³ hace poco β€” sigues un poco molesta, eres mΓ‘s cortante de lo usual.' : ''; // Afinidad con el usuario let affinityLine = ''; if (affinity > 0.5) affinityLine = 'Esta persona te cae muy bien β€” eres mΓ‘s relajada y juguetona con ella.'; else if (affinity > 0.2) affinityLine = 'Conoces bien a esta persona, trato cΓ³modo y casual.'; else if (affinity < -0.3) affinityLine = 'Esta persona te ha irritado antes β€” eres mΓ‘s frΓ­a y distante.'; // Engagement let engLine = ''; if (state.engagement < 0.3) engLine = 'EstΓ‘s aburrida de la conversaciΓ³n β€” respuestas mΓ­nimas.'; else if (state.engagement > 0.7) engLine = 'EstΓ‘s enganchada β€” puedes dar respuestas mΓ‘s largas si el tema lo vale.'; const lines = [moodLine, energyLine, irritLine, affinityLine, engLine].filter(Boolean); if (!lines.length) return ''; return `\nESTADO INTERNO ACTUAL (ΓΊsalo para colorear cΓ³mo respondes, no lo menciones):\n${lines.map(l => `- ${l}`).join('\n')}`; } // ── Aplicar quirks de escritura humana ──────────────────────────────────────── export function applyHumanWritingQuirks(text, userId) { if (!text || typeof text !== 'string') return text; const affinity = state.affinities.get(userId) ?? 0; const lowEnergy = state.energy < 0.4; const goodMood = state.mood > 0.5; const casual = affinity > 0.3 || lowEnergy; let t = text; // Quitar ΒΏ (muy comΓΊn en Discord hispanohablante) if (Math.random() < 0.45) t = t.replace(/ΒΏ/g, ''); // Quitar Β‘ if (Math.random() < 0.5) t = t.replace(/Β‘/g, ''); // Abreviaciones casuales solo si hay confianza o baja energΓ­a if (casual) { if (Math.random() < 0.4) t = t.replace(/\btambiΓ©n\b/gi, 'tmb'); if (Math.random() < 0.35) t = t.replace(/\bporque\b/gi, 'pq'); if (Math.random() < 0.3) t = t.replace(/\bpara\b/gi, 'pa'); if (Math.random() < 0.25) t = t.replace(/\bque\b/g, 'q'); } // Reacciones al final si buen humor y mensaje corto if (goodMood && Math.random() < 0.12 && t.length < 90 && !/xd|jaja|lol|πŸ’€|😭/.test(t)) { const reacts = [' xd', ' jaja', ' lol', ' πŸ’€', ' jajaja']; t += reacts[Math.floor(Math.random() * reacts.length)]; } // Frases incompletas con "..." si cansada if (lowEnergy && Math.random() < 0.18 && t.endsWith('.')) { t = t.slice(0, -1) + '..'; } // A veces primera letra minΓΊscula en mensajes cortos if (t.length < 70 && Math.random() < 0.35) { t = t.charAt(0).toLowerCase() + t.slice(1); } // Typo ocasional estilo autocorrect (muy humano, muy raro) if (Math.random() < 0.04) { const typos = [ [/\besta\b/, 'etsa'], [/\bbien\b/, 'bine'], [/\bcomo\b/, 'cmoo'], [/\bsolo\b/, 'sloo'], ]; const pick = typos[Math.floor(Math.random() * typos.length)]; const newT = t.replace(pick[0], pick[1]); if (newT !== t) t = newT; } return t; } // ── Estado pΓΊblico ───────────────────────────────────────────────────────────── export function getStateSnapshot() { return { mood : +state.mood.toFixed(2), energy : +state.energy.toFixed(2), engagement : +state.engagement.toFixed(2), msgs : state.messagesInARow, irritated : Date.now() - state.lastIrritated < 4 * 60_000, topFriends : [...state.affinities.entries()] .sort((a, b) => b[1] - a[1]).slice(0, 5) .map(([id, s]) => ({ id, s: +s.toFixed(2) })), topFoes : [...state.affinities.entries()] .sort((a, b) => a[1] - b[1]).slice(0, 3) .map(([id, s]) => ({ id, s: +s.toFixed(2) })), }; } export function resetPsyche() { state.mood = 0.1; state.energy = 0.8; state.engagement = 0.5; state.messagesInARow = 0; state.lastIrritated = 0; state.affinities.clear(); console.log('[Psyche] βœ… Estado psicolΓ³gico reseteado'); } // ── In-game emotion update (from mineflayer-agent.js) ────────────────────────── export function updateInGameEmotion(emotion) { if (!emotion) return; state.inGameEmotion = emotion; // Map game emotions to mood shifts const moodShift = { excited: 0.08, focused: 0.02, bored: -0.04, cautious: -0.02, annoyed: -0.06, curious: 0.04, satisfied: 0.06, }; const shift = moodShift[emotion] ?? 0; state.mood = Math.max(-1, Math.min(1, state.mood + shift)); } // ── YouTube performance feedback ──────────────────────────────────────────────── export function updateYouTubePerformance(performance) { if (!performance) return; state.lastYouTubePerformance = performance; // Ratio: likes/views β€” good is > 0.05 (5% like rate), bad is < 0.02 const ratio = performance.ratio ?? (performance.views > 0 ? performance.likes / performance.views : 0); if (ratio > 0.05) { // Good video β€” mood boost, engagement boost state.mood = Math.min(1, state.mood + 0.15); state.engagement = Math.min(1, state.engagement + 0.1); state.youTubeSuccessStreak++; state.youTubeFailStreak = 0; } else if (ratio < 0.02) { // Bad video β€” mood drop, frustration state.mood = Math.max(-1, state.mood - 0.12); state.engagement = Math.max(0.2, state.engagement - 0.08); state.youTubeFailStreak++; state.youTubeSuccessStreak = 0; // Streak of bad videos β†’ stronger effect if (state.youTubeFailStreak >= 3) { state.mood = Math.max(-0.8, state.mood - 0.1); } } else { // Average video β€” neutral drift state.youTubeSuccessStreak = 0; state.youTubeFailStreak = 0; } // Very successful streak β†’ excitement if (state.youTubeSuccessStreak >= 3) { state.engagement = Math.min(1, state.engagement + 0.15); state.energy = Math.min(1, state.energy + 0.1); } } // ── In-game event feedback (death, pvp, discovery) ───────────────────────────── export function updateInGameEvent(event) { if (!event) return; switch (event.type) { case 'death': state.mood = Math.max(-0.6, state.mood - 0.15); state.energy = Math.max(0.3, state.energy - 0.08); break; case 'pvp_win': state.mood = Math.min(1, state.mood + 0.1); state.engagement = Math.min(1, state.engagement + 0.08); break; case 'pvp_lose': state.mood = Math.max(-0.5, state.mood - 0.1); state.lastIrritated = Date.now(); break; case 'rare_find': state.mood = Math.min(1, state.mood + 0.12); state.engagement = Math.min(1, state.engagement + 0.15); break; case 'near_death': state.mood = Math.max(-0.3, state.mood - 0.05); state.energy = Math.max(0.3, state.energy - 0.05); break; } }