347. Par horizontal de scanline

Seleccionar el par lógico de nametables horizontal para un scanline registrado.

Lección 347 de 356 · tests/chapter_13_scrolling/test_347_scanline_horizontal_pair.py

Archivo a actualizar

emulator/rendering/ppu_background_renderer.py

Referencias

https://www.nesdev.org/wiki/PPU_scrolling
https://www.nesdev.org/wiki/PPU_nametables

Por qué existe este paso

El frame temporizado completado almacena un vram_addr efectivo y un valor de X fina para cada scanline visible. Antes de que un renderizador posterior pueda copiar los píxeles de ese scanline, debe saber qué dos nametables lógicas horizontalmente adyacentes forman el par de origen.

Los campos de selección de nametable dentro de vram_addr son:

bit 10: horizontal nametable position inside a 512-pixel-wide pair
bit 11: vertical nametable row that selects the pair

La selección de par, por tanto, usa únicamente el bit 11

bit 11 clear: ($2000, $2400)
bit 11 set:   ($2800, $2C00)

Modelo intuitivo

upper logical row:  $2000 | $2400
lower logical row:  $2800 | $2C00

El auxiliar elige una fila completa. Un paso posterior usará el bit 10, la X gruesa y la X fina para elegir la posición de píxel horizontal dentro de esa fila.

Invariantes importantes

  • solo el bit 11 de vram_addr afecta al par seleccionado
  • el bit 10 no cambia el par
  • la X gruesa, la Y gruesa, la Y fina y la X fina no cambian el par
  • los valores devueltos son direcciones lógicas de nametable de la PPU
  • el mirroring del cartucho sigue siendo comportamiento de PpuBus
  • el auxiliar es puro y no realiza renderizado ni acceso a memoria

Concepto erróneo común

El bit 10 no selecciona entre los pares horizontales superior e inferior. Identifica la nametable horizontal izquierda o derecha dentro del par y pasará a formar parte del cálculo de la X del viewport en la siguiente lección.

Fuera de alcance

  • decodificar la X del viewport horizontal
  • leer bytes de nametable o de tabla de patrones
  • aplicar el mirroring del cartucho
  • componer filas del framebuffer
  • elegir entre los datos temporizados completados y el mecanismo de respaldo antiguo

Implementación de ejemplo completa

# emulator/rendering/ppu_background_renderer.py

# --- UPDATED LINES: IMPORT THE RECORDED SCANLINE VALUE ---
from emulator.ppu.ppu import (
    BackgroundScanlineState,
    CTRL_BACKGROUND_PATTERN_TABLE,
    PPU,
)

...

# --- NEW BLOCK: SELECT ONE SCANLINE'S LOGICAL HORIZONTAL PAIR ---
def _scanline_horizontal_pair(
    state: BackgroundScanlineState,
) -> tuple[int, int]:
    nametable_y = (state.vram_addr >> 11) & 1
    left_base = BASE_NAMETABLE_ADDR + nametable_y * 0x0800

    return left_base, left_base + 0x0400

Ejecutar esta lección

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