348. Scanline viewport x
Decode horizontal viewport X from one recorded scanline state.
Lesson 348 of 356 · tests/chapter_13_scrolling/test_348_scanline_viewport_x.py
File to update
emulator/rendering/ppu_background_renderer.pyReference
https://www.nesdev.org/wiki/PPU_scrollingWhy this step exists
Step 347 selected the two logical nametables that form a horizontal source pair. A later row compositor also needs the exact pixel where this scanline begins inside that 512-pixel-wide pair.
The recorded address uses the same packed scrolling layout as t
yyy NN YYYYY XXXXXHorizontal viewport X uses
XXXXX coarse X tile column
low N bit horizontal nametable selection, vram_addr bit 10
state.fine_x pixel offset inside the first tilePixel conversion
viewport_x = nametable_x * 256 + coarse_x * 8 + fine_xExample
nametable X = 1
coarse X = 5
fine X = 3
viewport X = 1 * 256 + 5 * 8 + 3 = 299Why reuse decode_background_viewport_position? The existing pure decoder already owns the packed scrolling-field conversion. Both t and recorded v have the same bit layout, so repeating masks and dimensions in this renderer would create two implementations that could drift apart.
Important invariants
- horizontal nametable bit 10 contributes 256 pixels
- coarse X contributes eight pixels per tile
- fine X contributes the final 0-7 pixel offset
- vertical nametable, coarse Y, and fine Y do not affect viewport X
- the result remains inside the logical horizontal range 0-511
- the recorded address is not rewound again
- the helper performs no rendering or memory access
Common misconception
The recorded address does not need another two-tile correction. Step 345 already rewound a copy of v twice before constructing BackgroundScanlineState. Repeating the rewind here would incorrectly shift every rendered row sixteen pixels left.
Out of scope
- selecting logical nametable addresses
- composing framebuffer rows
- applying horizontal wrap to destination pixels
- opacity-mask composition
- choosing timed data versus the old frame-level fallback
Complete example implementation
# emulator/rendering/ppu_background_renderer.py
# --- NEW BLOCK: DECODE ONE SCANLINE'S HORIZONTAL VIEWPORT ---
def _scanline_viewport_x(state: BackgroundScanlineState) -> int:
viewport_x, _ = decode_background_viewport_position(
temp_vram_addr=state.vram_addr,
fine_x=state.fine_x,
)
return viewport_xRun this lesson
uv run pytest tests/chapter_13_scrolling/test_348_scanline_viewport_x.py -v