⚠️ 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.

Implementación de Ventana y Fix de Instrumentación

Fecha: 2025-12-25 Step ID: 0284 Estado: VERIFIED

Resumen

Este paso implementa dos mejoras críticas: (1) Movimiento de los monitores de diagnóstico (VBLANK-ENTRY, RESET-WATCH, POLLING-WATCH) al inicio de CPU::step() para evitar que el early return de interrupciones los oculte, y (2) Implementación completa de la lógica de renderizado de la Ventana (Window) en PPU::render_scanline() considerando los registros WY (0xFF4A) y WX (0xFF4B). La ventana se renderiza correctamente encima del Background pero debajo de los Sprites, respetando el bit 5 de LCDC (Window Enable) y usando el mismo sistema de direccionamiento de tiles que el Background según el bit 4 de LCDC.

Concepto de Hardware

Window (Ventana): La Game Boy tiene una capa de renderizado llamada "Window" que es una superficie opaca que se dibuja encima del Background pero debajo de los Sprites. A diferencia del Background, la Window no tiene scroll: siempre comienza desde el tile (0,0) del tilemap seleccionado. La posición de la Window se controla mediante dos registros:

  • WY (0xFF4A): Coordenada Y de inicio de la Window. La Window solo se dibuja cuando LY >= WY.
  • WX (0xFF4B): Coordenada X de inicio de la Window con un offset de 7 píxeles. WX=7 significa que la Window comienza en X=0 de la pantalla. La Window solo se dibuja si WX <= 166.

LCDC Bit 5 (Window Enable): Controla si la Window está habilitada. Si este bit está desactivado, la Window no se renderiza, independientemente de los valores de WY y WX.

LCDC Bit 6 (Window Tilemap): Selecciona qué tilemap usar para la Window (0x9800 o 0x9C00), independiente del tilemap del Background.

LCDC Bit 4 (Tile Data Addressing): Controla el direccionamiento de tiles tanto para Background como para Window. Bit 4=1 usa direccionamiento unsigned (0x8000, tile IDs 0-255), Bit 4=0 usa direccionamiento signed (0x8800, tile IDs -128 a 127).

Orden de Renderizado: Background → Window → Sprites. La Window sobrescribe el Background en las áreas donde se dibuja.

Fuente: Pan Docs - "Window", "LCDC Register", "Tile Data Addressing"

Implementación

Se realizaron tres cambios principales:

1. Movimiento de Monitores de Diagnóstico

Los monitores VBLANK-ENTRY, RESET-WATCH y POLLING-WATCH estaban ubicados después del switch de instrucciones, lo que causaba que no se ejecutaran cuando había interrupciones que provocaban early return. Se movieron al inicio de CPU::step(), justo después de capturar el PC original pero antes de handle_interrupts().

Cambio crítico: El PC original ahora se captura al inicio de step() y se reutiliza en todo el método, asegurando que los monitores se ejecuten incluso cuando hay interrupciones.

2. Implementación Completa de render_window()

Se completó la función render_window() en PPU.cpp con la lógica completa de renderizado:

  • Verificación de LCDC bit 5 (Window Enable) y bit 7 (LCD Enable)
  • Validación de condiciones WY <= LY y WX <= 166
  • Selección de tilemap según LCDC bit 6
  • Uso del mismo sistema de direccionamiento de tiles que Background (LCDC bit 4)
  • Renderizado píxel por píxel desde window_x_start hasta SCREEN_WIDTH
  • Aplicación de paleta BGP a los píxeles de la Window

3. Integración en render_scanline()

Se agregó la llamada a render_window() en render_scanline() después del renderizado del Background pero antes de los Sprites, respetando el orden correcto de capas.

Decisiones de diseño

  • Reutilización de original_pc: Se captura una vez al inicio y se reutiliza en todo step() para evitar inconsistencias.
  • Consistencia de direccionamiento: Tanto Background como Window usan la misma lógica de direccionamiento de tiles según LCDC bit 4, garantizando coherencia.
  • Validación temprana: Se verifican todas las condiciones (LCD Enable, Window Enable, WY, WX) antes de iniciar el renderizado para optimizar rendimiento.

Archivos Afectados

  • src/core/cpp/CPU.cpp - Movimiento de monitores de diagnóstico al inicio de step()
  • src/core/cpp/PPU.cpp - Implementación completa de render_window() e integración en render_scanline()

Tests y Verificación

La implementación se validó mediante:

  • Compilación exitosa: El módulo C++ se compiló sin errores usando python setup.py build_ext --inplace
  • Validación de módulo compilado C++: El módulo viboy_core se generó correctamente como .pyd
  • Verificación de lógica: Revisión manual del código para asegurar que:
    • Los monitores se ejecutan antes del early return de interrupciones
    • La Window se renderiza solo cuando LCDC bit 5 está activo
    • El direccionamiento de tiles es consistente entre Background y Window
    • La Window respeta las condiciones WY <= LY y WX <= 166
// Ejemplo de verificación de condiciones en render_window()
if ((lcdc & 0x80) == 0 || (lcdc & 0x20) == 0) {
    return;  // LCD desactivado o Window deshabilitada
}
if (ly_ < wy || wx > 166) {
    return;  // Condiciones de posición no cumplidas
}

Fuentes Consultadas

  • Pan Docs: "Window" - Descripción del sistema de ventana
  • Pan Docs: "LCDC Register" - Bits de control del LCD
  • Pan Docs: "Tile Data Addressing" - Sistema de direccionamiento de tiles
  • Pan Docs: "Background and Window" - Orden de renderizado de capas

Integridad Educativa

Lo que Entiendo Ahora

  • Window vs Background: La Window es una capa independiente sin scroll que siempre comienza desde (0,0) del tilemap. El Background tiene scroll mediante SCX/SCY.
  • Offset de WX: WX tiene un offset de 7 píxeles porque el hardware de la Game Boy tiene esta peculiaridad. WX=7 significa posición X=0 en pantalla.
  • Direccionamiento compartido: Tanto Background como Window comparten el mismo sistema de direccionamiento de tiles (LCDC bit 4), pero pueden usar tilemaps diferentes (LCDC bits 3 y 6).
  • Monitores de diagnóstico: Para que los monitores capturen eventos críticos (como entrada a V-Blank), deben ejecutarse antes de cualquier early return, especialmente antes de handle_interrupts().

Lo que Falta Confirmar

  • Comportamiento con WX < 7: Necesita validación en hardware real si la Window se renderiza parcialmente cuando WX < 7.
  • Rendimiento: Verificar que el renderizado de la Window no afecte significativamente el rendimiento a 60 FPS.

Hipótesis y Suposiciones

Se asume que cuando WX < 7, la Window simplemente no se renderiza (window_x_start se ajusta a 0). Esto necesita validación con hardware real o ROMs de test.

Próximos Pasos

  • [ ] Validar renderizado de Window con ROMs de test que usen ventanas
  • [ ] Verificar que los monitores de diagnóstico capturan correctamente eventos de interrupción
  • [ ] Optimizar render_window() si es necesario para mantener 60 FPS
  • [ ] Implementar tests unitarios específicos para renderizado de Window