Jev Juega Super Mario Bros.
Control en tiempo real con IA Decisional System One · TypeSafe AI
En el curso original de MarI/O con NEAT vimos cómo una población de cientos de redes neuronales compite generación tras generación, alterando pesos y conexiones mediante mutaciones al azar hasta descubrir por pura selección natural cómo pasar el nivel 1-1.
En esta carpeta (mario_jev) reemplazamos por completo el entrenamiento genético por un enfoque
completamente distinto: Jev (de TypeSafe AI),
la primera arquitectura System One diseñada para tomar decisiones inmediatas, tipadas y estructuradas
directamente consumibles por software sin generar texto token a token.
Noul y el estado del control de NES en cada frame.
De NEAT a Jev: Dos Mundos
Evolución ciega vs Inteligencia Semántica Inmediata
Para entender la diferencia radical entre ambos enfoques, comparemos sus mecanismos fundamentales:
| Característica | NEAT (Neuroevolución) | Jev (TypeSafe System One) |
|---|---|---|
| Entrenamiento previo | Requiere horas/días (cientos de generaciones). Comienza sin saber qué es el suelo. | Cero entrenamiento local. El modelo ya comprende nociones de obstáculos, gravedad y objetivos. |
| Entrada (Input) | 170 números planos (1, -1, 0) sin etiquetas semánticas. | Estado estructurado (JSON) con distancias, alturas de tuberías y mapa ASCII. |
| Salida (Output) | 6 flotantes de activación matemática continua. | Respuestas tipadas (Choice con probabilidades y Noul booleanos calibrados). |
| Explicabilidad | Caja negra: grafos densos de conexiones sinuosas. | Totalmente transparente: conocemos la probabilidad de cada opción y la urgencia de salto. |
¿Qué es un Modelo System One?
La metáfora de Kahneman en Inteligencia Artificial
El psicólogo Daniel Kahneman describió en Pensar rápido, pensar despacio dos modos de pensamiento humano:
- Sistema 2 (Deliberativo): Lento, secuencial, analítico. Es lo que hace un LLM tradicional (ChatGPT, Claude) cuando genera una larga respuesta palabra por palabra.
- Sistema 1 (Intuitivo / Reflejo): Instantáneo, paralelo, reactivo. Es lo que usa un conductor al esquivar un obstáculo en la carretera o lo que un jugador de Mario usa para saltar sobre un Goomba en una fracción de segundo.
Los LLMs tradicionales son inadecuados para controlar un videojuego en tiempo real porque:
- Tardan cientos de milisegundos por token generado en texto libre.
- Forzarlos a devolver JSON requiere parsing frágil y reintentos.
- No ofrecen probabilidades calibradas directamente evaluadas en paralelo.
Juicios de Jev y Decisiones en Código
El principio rector: "Judgments from Jev, decisions in code"
El error más común al integrar modelos de lenguaje o de decisión en videojuegos es pedirle a la IA que elija directamente la micro-mecánica física (por ejemplo: "mantén presionado A durante 14 frames y luego retrocede 3 píxeles"). Los modelos se descalibran ante la inercia continua y oscilan.
En nuestro diseño, Jev formula juicios perceptuales atómicos en paralelo utilizando la primitiva Noul, cada uno con una probabilidad calibrada entre 0.0 y 1.0:
questions = {
"obstaculo": Noul(
instructions="¿Hay una tubería, escalón o bloque sólido bloqueando el avance inmediato?"
),
"foso": Noul(
instructions="¿Hay un precipicio o foso sin suelo al frente que requiera un salto largo?"
),
"enemigo": Noul(
instructions="¿Hay un enemigo muy cercano al frente que requiera saltar para evitar el choque?"
),
"enemigo_aterrizaje": Noul(
instructions="¿Mario está en el aire cayendo y descenderá directamente sobre un enemigo peligroso?"
),
"camino_despejado": Noul(
instructions="¿El camino hacia la derecha está completamente despejado y seguro para correr?"
)
}
Todas estas preguntas se envían agrupadas (bundled) al cliente de TypeSafe en un único forward pass:
client.system_one(state=estado, questions=questions, model="jev-latest")
Luego, el código en Python evalúa la tabla de verdad y ejecuta la física del control de NES de forma determinista y sin latencia.
Selecciona un escenario del juego para observar los 5 juicios tipados de Jev y la acción que decide el código:
De la RAM al JSON Semántico
mario.py · extraer_estado_semantico
La NES real (y stable-retro) guarda el estado del mundo en bytes hexadecimales de RAM:
0x6D * 256 + 0x86: Coordenada horizontal (X) de Mario en el mundo.0xCE + 16: Coordenada vertical (Y) en pantalla.0x1D: Estado físico de Mario (0= en el suelo,1= saltando,2= cayendo).0x500: Matriz de casillas y bloques sólidos del nivel.0x0F + e: Bandera de existencia y tipo del enemigo e (hasta 5 simultáneos).
En lugar de entregar una matriz numérica plana a una red de neuronas, mario.py empaqueta
la información en un diccionario semántico estructurado:
{
"mario": {
"x": 384,
"y": 176,
"en_suelo": true,
"fase_salto": 0
},
"entorno_inmediato": {
"obstaculo_adelante": true,
"distancia_obstaculo": 2,
"altura_obstaculo_bloques": 3,
"foso_adelante": false
},
"enemigos": [
{"dx": 3, "dy": 0, "peligroso": true}
],
"peligro_inminente": true,
"mapa_ascii": ".............\n......M......\n...##########\n"
}
Este formato permite que Jev evalúe el peligro espacial y tome decisiones con precisión sin necesidad de preentrenamiento específico sobre la ROM.
Física de la NES y Decision Pacing
Hardware real, flanco de botón A y 60 FPS
Un emulador de NES corre a 60 cuadros por segundo (16.6 ms por cuadro). Llamar a una red remota cada 16 ms saturaría el canal. Por ello, combinamos dos conceptos técnicos clave:
1. Decision Pacing (Inferencia cada 6 cuadros)
Jev realiza inferencia cada CUADROS_POR_DECISION = 6 cuadros (~100 ms). En los cuadros intermedios, el ControladorMario en Python mantiene la inercia, sostiene botones y monitoriza eventos críticos a 60 FPS sin latencia.
2. Especificidades del Hardware de NES
- Transición de salto (flanco ascendente de A): En la NES original, dejar el botón
Apresionado de forma continua impide que Mario vuelva a saltar cuando aterriza. El controlador liberaAtan pronto como Mario toca el suelo (ram[0x1D] == 0) para habilitar un nuevo impulso. - Salto de altura variable: Mario salta más alto mientras más tiempo se mantenga presionado
A. Para tuberías de 3 y 4 bloques, el controlador sostieneAhasta por 20 cuadros seguidos. - Corrección aerodinámica en el aire: Si Mario está cayendo en una trayectoria que terminaría aterrizando sobre un enemigo o trampa, el controlador aplica una fuerza contraria (
LEFTen el aire) para corregir el punto de caída.
Generalización y Soporte Multi-Nivel
1-1, 1-2, 1-4, 2-1, 8-1 sin re-entrenar
Una de las mayores debilidades de la neuroevolución con NEAT es el sobreajuste geográfico: la red aprende que al llegar a la coordenada X=380 debe saltar porque ahí estaba la primera tubería del 1-1. Si se le coloca en el nivel 1-2 (subterráneo), salta a ciegas contra el techo o cae en el primer foso.
stable-retro inicia los niveles a partir de archivos de estado de memoria (LevelX-Y.state). El proyecto incluye detección automática de estados locales e incorporados para saltar a cualquier mundo al instante.
Arquitectura del Código
Organización modular en mario_jev/
mario.py: Emulador stable-retro, lectura de memoria RAM de NES (0x1D, 0x6D, 0xCE, 0x500), extractor de estado semántico yControladorMario.agente_jev.py: Cliente de TypeSafe AI. Agrupa las 5 preguntasNoul, invoca el modelojev-latesty aplica la tabla de verdad para resolver la táctica.ver.py: Visualizador en pantalla dividida en tiempo real con PyGame (juego a 60 FPS a la izquierda, telemetría y grilla 13×13 de Jev a la derecha).jugar.py: Ejecutor veloz en consola sin interfaz gráfica para pruebas masivas de vidas y cálculo de fitness.