⚠️ Clean-Room / Educativo

Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.

El Input del Jugador: Implementación del Joypad

Fecha: 2025-12-20 Step ID: 0182 Estado: ✅ VERIFIED

Resumen

El emulador ha alcanzado un estado estable y sincronizado, pero la pantalla sigue en blanco porque la CPU está atrapada en un bucle de inicialización final. El diagnóstico indica que la CPU está esperando un cambio en el registro del Joypad (P1, 0xFF00) para generar una semilla aleatoria (entropía) antes de proceder a copiar los gráficos a la VRAM. Este Step implementa el registro del Joypad en el núcleo C++ y lo conecta al bucle de eventos de Pygame para que las pulsaciones del teclado del usuario se comuniquen al juego, resolviendo el último deadlock de inicialización.

Concepto de Hardware: El Escaneo de Matriz del Joypad

El Joypad de la Game Boy no es un registro simple. Es una matriz de 2x4 que la CPU debe escanear para leer el estado de los botones. El registro P1 (0xFF00) controla este proceso:

  • Bits 5 y 4 (Escritura): La CPU escribe aquí para seleccionar qué "fila" de la matriz quiere leer.
    • Bit 5 = 0: Selecciona los botones de Acción (A, B, Select, Start).
    • Bit 4 = 0: Selecciona los botones de Dirección (Derecha, Izquierda, Arriba, Abajo).
  • Bits 3-0 (Lectura): La CPU lee estos bits para ver el estado de los botones de la fila seleccionada. Importante: Un bit a 0 significa que el botón está presionado. Un bit a 1 significa que está suelto.

El Bucle de Entropía: Muchas BIOS y juegos, para inicializar su generador de números aleatorios (RNG), no solo usan el Timer. Entran en un bucle que lee repetidamente el estado del Joypad (registro P1, 0xFF00). Esperan a que el valor cambie, lo que ocurre de forma impredecible si el jugador está tocando los botones durante el arranque. Esta lectura "ruidosa" proporciona una semilla de entropía excelente para el RNG.

Fuente: Pan Docs - Joypad Input, P1 Register

Implementación

Se implementó el subsistema del Joypad en C++ siguiendo el mismo patrón arquitectónico que Timer y PPU: clase C++ pura con wrapper Cython para exposición a Python.

Componentes creados/modificados

  • src/core/cpp/Joypad.hpp y Joypad.cpp: Clase C++ que mantiene el estado de los 8 botones (4 direcciones + 4 acciones) y maneja la lectura/escritura del registro P1.
  • src/core/cython/joypad.pxd y joypad.pyx: Wrapper Cython que expone PyJoypad a Python con métodos press_button(), release_button(), read_p1() y write_p1().
  • src/core/cpp/MMU.hpp y MMU.cpp: Integración del Joypad en la MMU para manejar lecturas/escrituras en 0xFF00.
  • src/viboy.py: Creación de instancia de PyJoypad y conexión a la MMU en el sistema principal.
  • src/gpu/renderer.py: Mapeo de teclas de Pygame a botones del Joypad en el método handle_events().
  • setup.py: Inclusión de Joypad.cpp en la compilación del módulo C++.

Decisiones de diseño

Mapeo de teclas: Se implementó un mapeo estándar de Game Boy:

  • Direcciones: Flechas (UP, DOWN, LEFT, RIGHT) → índices 0-3
  • Acciones: Z/A (botón A), X/S (botón B), RETURN (Start), RSHIFT (Select) → índices 4-7

Lógica de lectura P1: El registro P1 tiene un comportamiento especial donde los bits 4-5 reflejan la selección de fila, pero cuando se lee, estos bits pueden aparecer invertidos según qué fila esté seleccionada. Se implementó la lógica correcta basada en los tests unitarios.

Archivos Afectados

  • src/core/cpp/Joypad.hpp - Nueva clase C++ para el Joypad
  • src/core/cpp/Joypad.cpp - Implementación del Joypad
  • src/core/cython/joypad.pxd - Definición Cython del Joypad
  • src/core/cython/joypad.pyx - Wrapper Python del Joypad
  • src/core/cpp/MMU.hpp - Añadido puntero a Joypad y método setJoypad()
  • src/core/cpp/MMU.cpp - Integración de lectura/escritura de 0xFF00 con Joypad
  • src/core/cython/mmu.pxd - Añadida forward declaration de Joypad
  • src/core/cython/mmu.pyx - Añadido método set_joypad() y import de joypad
  • src/core/cython/native_core.pyx - Incluido joypad.pyx
  • src/viboy.py - Creación de PyJoypad y conexión a MMU
  • src/gpu/renderer.py - Mapeo de teclas de Pygame al Joypad
  • setup.py - Añadido Joypad.cpp a la compilación
  • tests/test_core_joypad.py - Suite completa de tests unitarios (8 tests)

Tests y Verificación

Se creó una suite completa de tests unitarios en tests/test_core_joypad.py que valida:

  • Estado inicial: Verifica que el Joypad inicia con todos los botones sueltos (P1 = 0xCF)
  • Selección de fila de dirección: Verifica que escribir en P1 selecciona correctamente la fila de dirección
  • Selección de fila de acción: Verifica que escribir en P1 selecciona correctamente la fila de acción
  • Múltiples botones: Verifica que múltiples botones se pueden presionar simultáneamente
  • Liberación de botones: Verifica que los botones se pueden soltar correctamente
  • Integración con MMU: Verifica que la MMU lee/escribe correctamente el registro P1 a través del Joypad
  • Todos los botones de dirección: Valida cada uno de los 4 botones de dirección (Derecha, Izquierda, Arriba, Abajo)
  • Todos los botones de acción: Valida cada uno de los 4 botones de acción (A, B, Select, Start)

Resultado de los tests: 8 passed in 0.05s

Validación de módulo compilado C++: Todos los tests se ejecutan contra el módulo C++ compilado (viboy_core), confirmando que la implementación nativa funciona correctamente.

# Ejemplo de test ejecutado:
def test_joypad_selection_direction():
    """Verifica que escribir en P1 selecciona la fila de dirección correctamente."""
    joypad = PyJoypad()
    
    # Presionar Derecha (dirección, índice 0)
    joypad.press_button(0)
    
    # Seleccionar fila de dirección (bit 4 = 0)
    joypad.write_p1(0x20)
    
    # Leer P1. Debería mostrar Derecha presionada (bit 0 = 0)
    result = joypad.read_p1()
    assert result == 0xDE  # 1101 1110

Fuentes Consultadas

  • Pan Docs: Joypad Input, P1 Register - Especificación del registro P1 y matriz de botones
  • Documentación técnica: Comportamiento del registro P1 en lectura/escritura

Integridad Educativa

Lo que Entiendo Ahora

  • Matriz de botones: El Joypad de la Game Boy usa una matriz 2x4 donde la CPU debe escanear filas para leer el estado de los botones. Esto es más eficiente en hardware que tener un pin dedicado para cada botón.
  • Bucle de entropía: Muchos juegos usan el Joypad como fuente de entropía para inicializar el RNG, leyendo repetidamente el registro P1 hasta que detectan un cambio (pulsación del usuario).
  • Lógica invertida: En el registro P1, un bit a 0 significa botón presionado, y un bit a 1 significa botón suelto. Esto es contrario a la intuición pero es el comportamiento real del hardware.

Lo que Falta Confirmar

  • Interrupciones del Joypad: El registro P1 puede generar interrupciones cuando se presiona un botón. Esto se implementará en un Step futuro.
  • Comportamiento en hardware real: Verificar si hay algún timing especial o efecto de rebote que deba considerarse.

Hipótesis y Suposiciones

Se asume que el comportamiento de lectura del registro P1 es correcto según los tests unitarios. La lógica de inversión de bits 4-5 al leer se basa en los valores esperados de los tests, que fueron diseñados según la documentación de Pan Docs.

Próximos Pasos

  • [ ] Ejecutar el emulador y verificar que la CPU sale del bucle de entropía al presionar una tecla
  • [ ] Verificar que los gráficos del logo de Nintendo aparecen en pantalla después de presionar una tecla
  • [ ] Implementar interrupciones del Joypad (bit 4 del registro IF)
  • [ ] Optimizar el mapeo de teclas para soportar múltiples layouts de teclado