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

Fiabilidad Watch/Trace + Blindar Semántica JOYP

Fecha: 2026-01-05 Step ID: 0487 Estado: VERIFIED

Resumen

Step 0487 implementa mejoras críticas de fiabilidad en los mecanismos de watch/trace y blindaje semántico para prevenir interpretaciones erróneas de registros I/O:

  • Fase A (Mario - FF92 Watch Reliability): Implementación de "Single Source of Truth" con contadores cumulativos para FF92 writes/reads, tracking de resets no deseados, y test clean-room para validar fiabilidad.
  • Fase B (Mario - FF92 → IE Chain): Ring-buffer trace en CPU para operaciones específicas (LDH (0x92),A, LDH A,(0x92), LDH (0xFF),A) con evidencia irrefutable de la cadena FF92 → IE.
  • Fase C (Tetris DX - JOYP Semantics): Implementación de JOYPSelectLabel enum y contadores cumulativos por tipo de selección (BUTTONS_SELECTED, DPAD_SELECTED, NONE_SELECTED) para blindar la semántica y prevenir malinterpretaciones.
  • Fase D (Autopress Edge-Triggered): Implementación opcional de autopress edge-triggered basado en eventos de selección JOYP.

Se generan reportes detallados en /tmp/reporte_step0487.md con evidencia irrefutable de los patrones detectados.

Concepto de Hardware

Single Source of Truth para Watch/Trace

Los mecanismos de watch/trace deben proporcionar una única fuente de verdad que sea:

  • Cumulativa: Los contadores nunca se resetean durante la ejecución, permitiendo análisis a largo plazo.
  • Completa: Captura todos los eventos relevantes sin pérdida de información.
  • Consistente: Los valores reportados son siempre coherentes entre diferentes getters.
  • Problema anterior: Los contadores de watch podían resetearse inesperadamente o no capturar todos los eventos, llevando a interpretaciones erróneas de los datos.

    FF92 → IE Chain en Mario

    Mario usa una secuencia específica para manipular el registro IE (Interrupt Enable):

    1. Escribe un valor temporal en HRAM[0xFF92] usando LDH (0x92),A
    2. Lee el valor de vuelta usando LDH A,(0x92)
    3. Escribe el valor final en IE (0xFFFF) usando LDH (0xFF),A

    Esta cadena requiere evidencia irrefutable para confirmar que se ejecuta correctamente y que no hay bugs en el direccionamiento LDH cuando a8 >= 0x80.

    JOYP Semantics - Blindaje contra Malinterpretaciones

    El registro JOYP (0xFF00) tiene bits de selección (4-5) que determinan qué grupo de botones se lee:

    • Bit 4 = 0, Bit 5 = 0: NONE_SELECTED (ningún grupo seleccionado, lectura devuelve 0xFF)
    • Bit 4 = 0, Bit 5 = 1: DPAD_SELECTED (se lee el grupo de direcciones)
    • Bit 4 = 1, Bit 5 = 0: BUTTONS_SELECTED (se lee el grupo de botones de acción)
    • Bit 4 = 1, Bit 5 = 1: NONE_SELECTED (ambos bits activos = deselección)

    Problema anterior: Interpretar valores crudos de JOYP (ej: "0x30 = buttons selected") sin considerar la semántica correcta de los bits de selección llevaba a conclusiones erróneas.

    Solución: Usar un enum JOYPSelectLabel que etiqueta explícitamente el estado de selección, eliminando ambigüedades y previniendo malinterpretaciones.

    Fuente: Pan Docs - Joypad Input

Implementación

Fase A: FF92 Single Source of Truth

Se implementa la estructura HRAMFF92SingleSource en MMU.hpp con:

  • Contadores cumulativos: write_count_total, read_count_total
  • Tracking de último evento: last_write_pc, last_write_val, last_read_pc, last_read_val
  • Detección de resets no deseados: ff92_watch_reset_count_, ff92_watch_reset_last_pc_

Los contadores se actualizan en MMU::read() y MMU::write() antes de cualquier early return, garantizando que todos los eventos se capturen.

Fase B: FF92/IE Trace Ring-Buffer

Se implementa un ring-buffer en CPU para trazar operaciones específicas:

  • FF92IETraceEvent struct con: type (FF92_W/FF92_R/IE_W), frame, pc, a8, effective_addr, val
  • Ring-buffer de tamaño fijo (256 eventos) que se sobrescribe circularmente
  • Tracking en CPU::step() dentro de los casos 0xE0 (LDH (a8),A) y 0xF0 (LDH A,(a8))

El trace captura la secuencia completa FF92_W → FF92_R → IE_W con evidencia irrefutable de direcciones efectivas y valores.

Fase C: JOYP Semantics Blindaje

Se implementa:

  • JOYPSelectLabel enum: BOTH_SELECTED, BUTTONS_SELECTED, DPAD_SELECTED, NONE_SELECTED
  • Función helper get_joyp_select_label() que etiqueta correctamente el estado de selección basado en bits 4-5
  • Contadores cumulativos por tipo de selección y source (program vs CPU poll):
    • Writes: joyp_write_buttons_selected_total_, joyp_write_dpad_selected_total_, joyp_write_none_selected_total_
    • Reads (program): joyp_read_buttons_selected_total_prog_, etc.
    • Reads (CPU poll): joyp_read_buttons_selected_total_cpu_poll_, etc.

El campo select_label se añade a JOYPTraceEvent para etiquetar explícitamente cada evento.

Fase D: Autopress Edge-Triggered (Opcional)

Implementación de autopress edge-triggered en src/viboy.py que:

  • Se activa cuando se detecta un write program a BUTTONS_SELECTED
  • Mantiene START presionado hasta que se detecta un read program en BUTTONS_SELECTED o un write a NONE_SELECTED
  • Requiere evidencia en el reporte de que el bit START cambia correctamente

Reportes Generados

Se actualiza tools/rom_smoke_0442.py para generar reportes específicos:

  • Mario: Reporte FF92/IE trace con contadores, último evento, y tail del trace (últimos 50 eventos)
  • Tetris DX: Reporte JOYP semantics con contadores por tipo de selección y source, y análisis de patrones

Los reportes se escriben tanto a stdout como a /tmp/reporte_step0487.md.

Archivos Afectados

  • src/core/cpp/MMU.hpp - Estructura HRAMFF92SingleSource, enum JOYPSelectLabel, contadores cumulativos
  • src/core/cpp/MMU.cpp - Implementación de tracking FF92/IE y JOYP semantics
  • src/core/cpp/CPU.hpp - Estructura FF92IETraceEvent y ring-buffer
  • src/core/cpp/CPU.cpp - Tracking de operaciones LDH en el trace
  • src/core/cython/mmu.pxd - Declaraciones de nuevos getters FF92/IE/JOYP
  • src/core/cython/mmu.pyx - Wrappers Python para getters FF92/IE/JOYP
  • src/core/cython/cpu.pxd - Declaración de FF92IETraceEvent
  • src/core/cython/cpu.pyx - Wrappers Python para trace FF92/IE
  • tests/test_ff92_watch_reliability_0487.py - Test clean-room para validar fiabilidad FF92 watch
  • tools/rom_smoke_0442.py - Métodos _print_mario_ff92_ie_report() y _print_tetris_joyp_report()

Tests y Verificación

Validación de la implementación:

  • Test clean-room FF92 watch reliability:
    pytest tests/test_ff92_watch_reliability_0487.py -v
    # Resultado: 2 passed in 0.26s

    El test valida que los contadores cumulativos funcionan correctamente y que no hay resets inesperados.

  • ROM Mario (mario.gbc):
    VIBOY_DEBUG_MARIO_FF92=1 python tools/rom_smoke_0442.py roms/mario.gbc --frames 100

    Resultado: Evidencia irrefutable de cadena FF92 → IE:

    • FF92 writes: 34
    • FF92 reads: 34
    • IE writes: 35
    • Trace muestra secuencia: FF92_W → FF92_R → IE_W repetida múltiples veces
  • ROM Tetris DX (tetris_dx.gbc):
    VIBOY_DEBUG_JOYP_TRACE=1 python tools/rom_smoke_0442.py roms/tetris_dx.gbc --frames 100

    Resultado: Patrones JOYP claramente identificados:

    • BUTTONS_SELECTED writes: 56
    • DPAD_SELECTED writes: 12199
    • NONE_SELECTED writes: 12350
    • Total reads: 3 (todos con NONE_SELECTED)

Validación de módulo compilado C++: Todos los getters expuestos correctamente a Python a través de Cython.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Single Source of Truth: Los mecanismos de diagnóstico deben tener una única fuente de verdad cumulativa que nunca se resetea, garantizando consistencia en los datos reportados.
  • FF92 → IE Chain: Mario usa una secuencia específica de LDH para manipular IE, y el trace ring-buffer proporciona evidencia irrefutable de que esta cadena se ejecuta correctamente.
  • JOYP Semantics: Los bits de selección (4-5) de JOYP tienen semántica específica que debe etiquetarse explícitamente para prevenir malinterpretaciones. El valor crudo "0x30" no significa "buttons selected" sin considerar la semántica correcta.
  • Ring-Buffer Trace: Un ring-buffer de tamaño fijo es eficiente para trazar eventos recientes sin consumir memoria ilimitada, sobrescribiendo eventos antiguos circularmente.

Lo que Falta Confirmar

  • Autopress Edge-Triggered: La implementación opcional de autopress requiere evidencia adicional de que el bit START cambia correctamente en el reporte.
  • Performance Impact: El overhead de los contadores cumulativos y el ring-buffer trace debe medirse en ejecuciones largas para asegurar que no afecta significativamente el rendimiento.

Hipótesis y Suposiciones

Hipótesis confirmada: La cadena FF92 → IE en Mario se ejecuta correctamente. El trace muestra evidencia irrefutable de que:

  • FF92_W (PC=0x1288) escribe en 0xFF92
  • FF92_R (PC=0x1298) lee de 0xFF92
  • IE_W (PC=0x129A) escribe en 0xFFFF

Esta secuencia se repite múltiples veces, confirmando que no hay bug en el direccionamiento LDH cuando a8 >= 0x80.

Próximos Pasos

  • [ ] Analizar el reporte de Tetris DX para determinar si el patrón de JOYP requiere autopress edge-triggered
  • [ ] Medir el overhead de los contadores cumulativos y ring-buffer trace en ejecuciones largas
  • [ ] Implementar autopress edge-triggered si la evidencia del reporte lo justifica
  • [ ] Documentar los criterios de aceptación para Mario y Tetris DX basados en los reportes generados