337. Planificar el desplazamiento temporizado de vram

Comprende el plan de desplazamiento temporizado de v/t/x antes de modificar el stepping de la PPU.

Lección 337 de 356 · tests/chapter_13_scrolling/test_337_plan_timed_vram_scrolling.py

Archivo a confirmar tras la lectura

emulator/ppu/ppu.py

Referencias

https://www.nesdev.org/wiki/PPU_scrolling
https://www.nesdev.org/wiki/PPU_scrolling#During_rendering
https://www.nesdev.org/wiki/PPU_rendering#Line-by-line_timing

Por qué existe este paso de lectura

PPU.step() es un subsistema crítico en cuanto a temporización. Un pequeño error puede producir una imagen que parece casi correcta mientras usa un scanline, nametable o posición de desplazamiento incorrectos. Antes de modificarlo, necesitamos un modelo mental estable y un plan de migración claro.

El problema actual

El viewport existente a nivel de fotograma decodifica temp_vram_addr (t) más fine X después del fotograma. Eso funciona para desplazamiento sintético simple pero falla con juegos que cambian el desplazamiento durante un fotograma.

Super Mario Bros. usa aproximadamente esta forma:

visible rows 0-30:
    fixed status-bar scroll

sprite-zero split:
    CPU prepares another horizontal position

visible rows 31-239:
    moving gameplay scroll

Un único valor muestreado después del fotograma no puede describir ambas regiones.

Hay un segundo problema: t no es exclusivamente un valor de desplazamiento. $2006 (PPUADDR) también escribe t mientras los juegos cargan nametables y paletas. Por ejemplo:

CPU writes PPUADDR $3F00
    -> t becomes $3F00

Decodificar ese valor final como desplazamiento puede seleccionar brevemente el viewport equivocado. Por eso el inicio o las transiciones de nivel pueden parecer que se desplazan rápidamente mientras la pantalla está en negro.

Los cuatro valores internos de desplazamiento

v = current VRAM address used by rendering
t = temporary VRAM address prepared by CPU register writes
x = fine horizontal pixel offset
w = first/second-write toggle for $2005 and $2006

Modelo intuitivo

t is the next address configuration being prepared.
v is the address currently moving through rendering.
x is the 0-7 pixel offset inside the first tile.
w remembers which half of a two-write register comes next.

Error común

t is the current scroll position for the entire frame.

Modelo correcto

CPU writes assemble t and x.
PPU timing copies selected fields from t into v.
Rendering advances v while tiles and scanlines are processed.

Operaciones temporizadas importantes

background-fetch dots, every 8 dots:
    increment horizontal v

dot 256:
    increment vertical v

dot 257:
    copy horizontal fields from t into v

pre-render dots 280-304:
    copy vertical fields from t into v

Campos horizontales

coarse X
horizontal nametable bit

Campos verticales

coarse Y
fine Y
vertical nametable bit

Por qué las copias son selectivas

En el dot 257, solo debe refrescarse la posición horizontal del siguiente scanline. Copiar todo t también reemplazaría el estado vertical en el momento equivocado.

Por qué se registra por scanline

El renderizador de nametables existente ya produce framebuffers de origen RGB correctos. Todavía no necesitamos reemplazarlo por un pipeline completo de obtención de píxeles por dot. En su lugar, la temporización de la PPU registrará la posición efectiva v + x una vez por cada scanline visible:

scanline 0  -> viewport X 0
scanline 1  -> viewport X 0
...
scanline 30 -> viewport X 0
scanline 31 -> viewport X 40
...

Después del fotograma, el renderizador de alto nivel usa esas posiciones registradas mientras copia cada fila de salida exactamente una vez.

Detalle del prefetch

En el dot 1, v ya está dos tiles por delante porque los dots 321-336 obtuvieron los dos primeros tiles de fondo en los registros de desplazamiento de hardware. La instantánea del scanline debe compensar esos dos incrementos de coarse X al derivar el viewport visible. De lo contrario, todo el fondo aparece desplazado 16 píxeles.

Plan de compatibilidad

  • mantener el comportamiento existente de los registros v, t, x, w y las funciones auxiliares de renderizado públicas
  • añadir operaciones de dirección puras antes de aplicarlas dentro de PPU.step()
  • conservar la antigua ruta del viewport a nivel de fotograma como mecanismo de reserva durante la migración
  • añadir el renderizado por scanline temporizado solo cuando exista un fotograma completo de estados
  • mantener idéntica la selección de filas del framebuffer y de la máscara de opacidad
  • ejecutar uv run pytest después de cada paso numerado

Próximos pasos centrados

  • incremento horizontal puro de v y wrap del nametable
  • copia horizontal pura de campos de t a v
  • incrementos en los dots de obtención horizontales y copia en el dot 257
  • incremento vertical puro con fine Y y filas 29-31
  • copia vertical pura de campos de t a v
  • incremento vertical en el dot 256 y copia vertical en el pre-renderizado
  • estado efectivo de v + fine X para cada scanline visible
  • composición del framebuffer limitada por filas
  • composición idéntica de la máscara de opacidad limitada por filas
  • máscara consciente del scanline para la planificación del sprite-zero-hit
  • validación manual de la barra de estado, el movimiento, las transiciones y los FPS de Super Mario Bros.

Límite de precisión

Este enfoque modela la temporización de las direcciones de desplazamiento y la selección del viewport por scanline. Todavía no es un renderizador completo de obtención y registros de desplazamiento del fondo por dot. Las escrituras en registros de la CPU también se temporizan con la granularidad actual de avance instrucción a instrucción.

Acción requerida tras la lectura

Añade exactamente este comentario cerca de los campos vram_addr, temp_vram_addr, fine_x y second_write_toggle en emulator/ppu/ppu.py:

# pass_test_337

No implementes cambios de desplazamiento en este paso. El comentario es el único cambio en archivos de producción necesario para pasar la prueba 337.

Ejecutar esta lección

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