328. Bits de nametable temporal de PPUCTRL

Copiar la selección de nametable base de PPUCTRL en temp_vram_addr.

Lección 328 de 356 · tests/chapter_13_scrolling/test_328_ppuctrl_temp_nametable_bits.py

Archivo a actualizar

emulator/ppu/ppu.py

Documentación de referencia

https://www.nesdev.org/wiki/PPU_scrolling#$2000_(PPUCTRL)_write
https://www.nesdev.org/wiki/PPU_registers

Por qué existe este paso

La dirección de scrolling del PPU se ensambla a partir de varias escrituras de registros visibles para la CPU. El proyecto ya gestiona la mayoría de las partes de $2005 PPUSCROLL:

coarse X -> temp_vram_addr bits 0-4
coarse Y -> temp_vram_addr bits 5-9
fine Y   -> temp_vram_addr bits 12-14
fine X   -> separate fine_x field

La pieza que falta es la selección lógica del nametable base

PPUCTRL bits 0-1 -> temp_vram_addr bits 10-11

Sin esta conexión, temp_vram_addr se comporta como si el scrolling siempre comenzara desde el nametable lógico 0 ($2000), incluso cuando el software del juego seleccionó $2400, $2800 o $2C00. Por tanto, un futuro renderizador de viewport cruzaría el límite lógico equivocado.

Distribución interna del scrolling

temp_vram_addr (t): yyy NN YYYYY XXXXX

XXXXX -> coarse X tile position
YYYYY -> coarse Y tile position
NN    -> logical nametable selection
yyy   -> fine Y pixel position

fine_x (x) is stored separately

Mapeo de PPUCTRL

bits 0-1 = 00 -> logical nametable $2000
bits 0-1 = 01 -> logical nametable $2400
bits 0-1 = 10 -> logical nametable $2800
bits 0-1 = 11 -> logical nametable $2C00

Implementación sugerida

# emulator/ppu/ppu.py

case 0x2000:
    self.ctrl = value

    # --- NEW LINE: COPY BASE NAMETABLE INTO temp_vram_addr: ...GH.. ........ <- value: ......GH
    self.temp_vram_addr = (self.temp_vram_addr & 0b1111_0011_1111_1111) | ((value & CTRL_BASE_NAMETABLE_MASK) << 10)
    # --- END NEW LINE ---

¿Por qué limpiar primero los bits 10-11? Un juego puede cambiar de cualquier nametable lógico a cualquier otro nametable lógico. Usar solo OR establecería los bits nuevos pero no podría limpiar los antiguos. La operación de limpiar y luego insertar reemplaza la selección anterior conservando X grueso, Y grueso y Y fino.

¿Por qué no usar el antiguo campo de scroll? $2005 recibe dos escrituras, pero el campo de scroll de compatibilidad almacena solo el byte más reciente. temp_vram_addr junto con fine_x conservan el estado completo al estilo del hardware.

Distinción importante

Este paso registra la selección de nametable lógico. El mirroring del cartucho luego mapea esa selección lógica sobre la RAM física de nametables. Ninguna de las dos operaciones mueve por sí misma el viewport visible; la extracción/renderizado del viewport llega en pasos posteriores.

Fuera del alcance

  • derivar la X/Y en píxeles del viewport
  • renderizar nametables adyacentes
  • transferir t a v en los dots exactos del hardware
  • cambiar el mirroring de PpuBus
  • fixtures de ROM comerciales

Ejecutar esta lección

uv run pytest tests/chapter_13_scrolling/test_328_ppuctrl_temp_nametable_bits.py -v