Spaces:
Paused
Paused
| /** | |
| * ============================================ | |
| * π§ 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; | |
| } | |
| } | |