29 de abril de 2026 · Valentín Stancu
Cómo construir un rival con memoria persistente con Gemini
Devlog técnico sobre cómo Mentium guarda y usa el historial del jugador para que tu rival recuerde. Arquitectura, prompts, costes y trade-offs.
Una de las funcionalidades de Mentium que más me costó diseñar — y la que más sorprende a los jugadores en la beta — es el rival con memoria persistente. Tu rival no es un NPC genérico que dice frases random. Recuerda cuántas veces le has ganado, en qué temas eres fuerte y dónde fallas. Sus comentarios pre-partida y post-partida son únicos para ti.
Aquí cuento cómo está hecho. Sin los detalles de prompts (esos son secret sauce), pero con la arquitectura, los costes y los trade-offs reales.
El problema
Si llamas a Gemini con un prompt tipo “haz un comentario post-partida para un rival de trivia”, obtienes algo genérico:
“¡Buena partida! Vamos por la siguiente.”
No sirve. Eso lo hace cualquier juego con un random.choice(["¡Bien!", "¡Sigue así!"]). Lo interesante es:
“Llevas 4 victorias contra mí esta semana, pero todas en mitología. Hoy te he pillado en geografía africana — 4 fallos de 8. Mañana vienes preparado, ¿no?”
Esa frase requiere contexto del jugador. Y ahí está el reto.
La arquitectura, paso a paso
1. Estado del rival, persistido en Firestore
Cada jugador tiene un documento users/{uid}/rival_state que guarda:
personality: cuál de los 5 rivales le tocó (Taunter, Sage, Underdog, Friendly, Calculator). Asignado en la 3.ª partida según patrón de juego inicial.head_to_head: cuántas partidas ha jugado contra el rival, cuántas ganadas/perdidas/empatadas, racha actual.topic_stats: por categoría, % aciertos del jugador en partidas vs el rival.last_5_summaries: resúmenes condensados de las últimas 5 partidas (≤200 caracteres cada uno) generados al cerrar cada partida.last_seen_ms: timestamp de la última interacción para evitar referencias raras tipo “ayer” cuando lleva 3 semanas sin abrir el juego.
Total: ~2 KB por jugador. Despreciable a nivel de almacenamiento.
2. Resumen post-partida (no historial completo)
El error obvio sería mandar el historial completo a Gemini en cada partida. No lo hago. Tres motivos:
- Tokens: a escala, los prompts largos multiplicados por usuarios y partidas saturan el modelo.
- Latencia: prompts largos = respuestas lentas. El usuario espera el comentario al final de cada partida; no puede tardar 4 segundos.
- Privacidad: cuanto menos detalle viaje al modelo, mejor.
En su lugar, al final de cada partida (en background, sin que el usuario espere) corro un prompt cortito que pide:
“Genera un resumen de UNA frase, máximo 200 caracteres, sobre qué pasó en esta partida desde el punto de vista del rival.”
Output ejemplo: "Le gané 6-2 en cine clásico; falló dos preguntas de Hitchcock seguidas."
Eso es lo que persisto en last_5_summaries. El historial real (preguntas, respuestas, tiempos) sigue local en el dispositivo para Smart Review.
3. Comentario pre/post-partida
Cuando hace falta generar un comentario del rival, el prompt va con:
- La personalidad (
Taunter,Sage, etc.) + 2-3 frases de tono de ejemplo. - Los
last_5_summaries. - El
head_to_headresumido. - Los 2-3 topics más relevantes para esta partida.
- (Si es post-partida) qué pasó en esta partida concreta.
Tamaño total: ~500-800 tokens. Modelo: Gemini 2.5 Flash Lite (suficiente para este uso, latencia ≈ 800ms).
Por la temperatura ajustada a 0.7-0.8 y un poco de sistema-instruction estricta, las salidas son consistentes con la personalidad del rival y casi nunca se inventan datos que no estén en el contexto.
4. Caché defensivo
Si el usuario abre el menú principal 3 veces en 5 minutos, no llamo a Gemini 3 veces. La primera llamada cachea localmente y se reutiliza durante 10 minutos. Si el usuario juega una partida en ese intervalo, invalido el caché y vuelvo a llamar (porque ahora hay una partida nueva en el historial).
Lo que NO hace
Cosas que decidí explícitamente no implementar:
- Diálogo libre con el rival. Tu rival no es un chatbot. Solo aparece pre/post-partida con frases generadas. Si lo abriera al chat libre, se volvería un asistente, no un rival.
- Memoria infinita. Solo guardo las últimas 5 partidas. Si ganaste hace 3 meses con un score perfecto, el rival ya no se acuerda. Ni el rival ni tú: es como la vida.
- Cross-rival memory. Si te asignan un rival nuevo, arranca de cero. Cada rival tiene su propia memoria con su propio jugador.
Lo que aprendí
- No le pidas al modelo lo que el código puede calcular. “Cuántas veces le he ganado” es un contador, no una llamada a IA. Solo invoca al modelo para lo que requiere lenguaje natural.
- Resúmenes condensados > historial completo. Una vez que aceptas que no vas a guardar todo, todo el sistema se simplifica.
- La personalidad es prompt + tono de ejemplo, no fine-tuning. Para 5 personalidades, no merece la pena fine-tunear. Un buen system prompt con 3-4 ejemplos suficientes.
- Caché defensivo es obligatorio. Si no, las llamadas al modelo se disparan por nada.
Si te gusta este tipo de detalle técnico y quieres ver Mentium en acción, ya puedes descargarlo gratis en Google Play: Mentium 1.0 «Curie» (acceso anticipado).
— Valentín
devlog ia gemini técnico