250. Ppudata 调色板读取例外

实现 PPUDATA ($2007) 的调色板读取例外。

250 / 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_250_ppudata_palette_read_exception.py

参考资料

https://www.nesdev.org/wiki/PPU_registers#PPUDATA

要更新的文件

emulator/ppu/ppu.py

要添加的常量

PALETTE_START_ADDR = 0x3F00
PALETTE_END_ADDR = 0x3FFF

为什么需要这一步

正常的 PPUDATA 读取是带缓冲的

read $2007:
    return old ppu_data_buffer
    reload ppu_data_buffer from ppu_bus.read(vram_addr)
    increment vram_addr

调色板读取是一个例外。当 vram_addr 指向调色板内存时:

$3F00-$3FFF

PPU 会立即返回调色板字节,而不是返回旧的缓冲区值。

重要细节

旧的缓冲区仍会被丢弃。在正常的 NES PPU 上,该缓冲区会从调色板地址背后镜像的命名表内存重新加载:

$3F00 -> reload buffer from $2F00
$3F10 -> reload buffer from $2F10

这意味着调色板读取会同时影响

  • 返回给 CPU 的值
  • 后续正常读取所使用的内部 ppu_data_buffer

建议的实现伪代码,与显式的注释风格保持一致:

PALETTE_START_ADDR = 0x3F00
PALETTE_END_ADDR = 0x3FFF

case 0x2007:  # PPU DATA read
    # Palette data is returned immediately.
    if PALETTE_START_ADDR <= self.vram_addr <= PALETTE_END_ADDR:
        value = self.ppu_bus.read(self.vram_addr)

        # Read buffer is discarded and reloaded from shadowed memory:
        # vram_addr - 0x1000
        # Example: 0x3F00 -> 0x2F00
        self.ppu_data_buffer = self.ppu_bus.read(self.vram_addr - 0x1000)
    else:
        # Normal PPUDATA reads return the old buffer first.
        value = self.ppu_data_buffer

        # Then the buffer is reloaded from current PPU memory.
        self.ppu_data_buffer = self.ppu_bus.read(self.vram_addr)

    increment = 32 if self.ctrl & CTRL_VRAM_INCREMENT_BY_32 else 1

    # Keep vram_addr in the 14-bit PPU address range.
    self.vram_addr = (self.vram_addr + increment) & 0x3FFF

    # Preserve self.data for old test compatibility/debugging.
    self.data = value
    return self.data

超出范围

  • 调色板 RAM 镜像,例如 $3F10 -> $3F00
  • 精确的调色板颜色值
  • 渲染

运行本课

uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_250_ppudata_palette_read_exception.py -v