Curso & Arquitectura de Software

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.

Demostración en vivo · Nivel 1-1 superado: A la izquierda, Super Mario Bros. en la NES ejecutándose a 60 FPS. A la derecha, el panel de telemetría de Jev (TypeSafe AI) procesando la grilla 13×13, las probabilidades en tiempo real de los 5 juicios Noul y el estado del control de NES en cada frame.
1

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.
2

¿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:

  1. Tardan cientos de milisegundos por token generado en texto libre.
  2. Forzarlos a devolver JSON requiere parsing frágil y reintentos.
  3. No ofrecen probabilidades calibradas directamente evaluadas en paralelo.
Jev resuelve esto: Es un modelo no autorregresivo. Recibe el estado del juego y un conjunto de preguntas simultáneas, evaluándolas todas en un único paso hacia adelante (forward pass).
3

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.

La regla de oro de TypeSafe AI: "Judgments from Jev, decisions in code" (Los juicios son de Jev, las decisiones se toman en código).

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.

Simulador Interactivo de Percepción Jev

Selecciona un escenario del juego para observar los 5 juicios tipados de Jev y la acción que decide el código:

Acción de Control Decidida: RUN_RIGHT Correr hacia la derecha (B + RIGHT)
Noul('camino_despejado')99%
Noul('obstaculo')3%
Noul('foso')1%
Noul('enemigo')2%
Noul('enemigo_aterrizaje')0%
4

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.

5

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 A presionado de forma continua impide que Mario vuelva a saltar cuando aterriza. El controlador libera A tan 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 sostiene A hasta 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 (LEFT en el aire) para corregir el punto de caída.
6

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.

Jev es generalizable: Al evaluar nociones espaciales relativas ("¿tengo un foso a 2 casillas?"), Jev puede jugar de inmediato en niveles subterráneos (1-2), castillos de Bowser (1-4) o mundos avanzados (2-1, 8-1) utilizando la misma lógica de juicios.

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.

7

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 y ControladorMario.
  • agente_jev.py: Cliente de TypeSafe AI. Agrupa las 5 preguntas Noul, invoca el modelo jev-latest y 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.