345. Registrar el estado de scroll de scanlines visibles
Registrar el estado de viewport efectivo en el dot 1 de cada scanline visible.
Lección 345 de 356 · tests/chapter_13_scrolling/test_345_record_visible_scanline_scroll_state.py
Archivo a actualizar
emulator/ppu/ppu.pyReferencias
https://www.nesdev.org/wiki/PPU_rendering#Cycles_1-256
https://www.nesdev.org/wiki/PPU_scrolling#DetailsPor qué existe este paso
La PPU ahora actualiza v con la temporización de renderizado horizontal y vertical, pero el renderizador de alto nivel existente se ejecuta después del frame. Necesita un pequeño registro de la dirección efectiva que representó cada fila de pantalla visible.
Un estado inmutable almacena
vram_addr:
the copied address represented by the prefetched pixels
fine_x:
the separate 0-7 pixel offset inside the first tileEn el dot 1, la v real está dos columnas de tiles por delante porque los dots 321-336 precargaron los dos primeros tiles del scanline actual. Por ello, el registro realiza:
copied visible v = real v rewound once, then rewound againEjemplo
current scanline = 20
real v coarse X = 7
copied visible coarse X:
7 -> 6 -> 5
stored destination:
current_scanline_scroll_states[20]Los rebobinados cambian la posición horizontal del tile, no el índice de scanline. El estado pertenece al scanline actual 20, no al scanline 18.
Condiciones de registro
- el renderizado de fondo o de sprites está habilitado
- el scanline es visible: 0-239
- el dot actual es 1
Invariantes importantes
- el búfer siempre tiene exactamente 240 entradas
- cada PPU posee un búfer independiente
- el registro no modifica el PPU.vram_addr real
- la X fina se almacena por separado
- el post-render, el VBlank y el pre-render no se registran
Fuera de alcance
- archivar el frame completado
- sustituir entradas ausentes
- reiniciar el búfer del frame actual
- la composición del framebuffer o de la máscara de opacidad
Implementación de ejemplo completa
# emulator/ppu/ppu.py
# --- NEW BLOCK: EFFECTIVE STATE FOR ONE VISIBLE SCANLINE ---
@dataclass(frozen=True)
class BackgroundScanlineState:
vram_addr: int
fine_x: int
@dataclass
class PPU:
...
# --- NEW LINE: CURRENT FRAME'S VISIBLE SCANLINE STATES ---
current_scanline_scroll_states: list[
BackgroundScanlineState | None
] = field(default_factory=lambda: [None] * 240)
...
# --- NEW BLOCK: RECORD THE CURRENT VISIBLE SCANLINE ---
def _record_visible_scanline_scroll_state(self) -> None:
rendering_enabled = self.mask & (
MASK_SHOW_BACKGROUND | MASK_SHOW_SPRITES
)
if not rendering_enabled:
return
if not 0 <= self.scanline < 240:
return
if self.cycle != 1:
return
visible_vram_addr = decrement_horizontal_vram_addr(
decrement_horizontal_vram_addr(self.vram_addr)
)
self.current_scanline_scroll_states[self.scanline] = (
BackgroundScanlineState(
vram_addr=visible_vram_addr,
fine_x=self.fine_x,
)
)
def step(self, cycles: int = 1) -> None:
...
for _ in range(cycles):
self.cycle += 1
self._step_horizontal_rendering_address()
self._step_vertical_rendering_address()
# --- NEW LINE: RECORD THE CURRENT SCANLINE AT DOT 1 ---
self._record_visible_scanline_scroll_state()
...Ejecutar esta lección
uv run pytest tests/chapter_13_scrolling/test_345_record_visible_scanline_scroll_state.py -v