File size: 12,125 Bytes
ee826ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/**
 * ============================================
 * 🧠 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;
  }
}