312. Sprite priority uses background opaque mask

Use the background opacity mask to apply sprite priority bit 5.

Lesson 312 of 356 · tests/chapter_09_sprite_rendering/test_312_sprite_priority_uses_background_opaque_mask.py

File to update

emulator/rendering/sprite_renderer.py

Why this step exists

Step 310 built a per-pixel background opacity mask. Step 311 threaded that mask through the sprite rendering pipeline. This step finally uses the mask in the one-sprite renderer.

Rule

If a sprite pixel is non-transparent,
and the sprite has priority bit 5 set,
and the background mask says that screen pixel is opaque,
then keep the background pixel and skip drawing the sprite pixel.

Important ordering

1. sprite CHR color index 0 -> skip
2. offscreen pixel -> skip
3. behind-background sprite over opaque background -> skip
4. otherwise draw sprite pixel

Example implementation fragment

def render_sprite_8x8_to_framebuffer (...) -> ...

...
... 

screen_x = sprite.x + tile_x
screen_y = sprite.y + tile_y

if not (0 <= screen_x < framebuffer.width):
    continue
if not (0 <= screen_y < framebuffer.height):
    continue

# --- ADD THIS NEW BLOCK ---
if (
    background_opaque_mask is not None
    and attributes.is_behind_background
    and background_opaque_mask[screen_y * framebuffer.width + screen_x]
):
    continue
# --- END NEW BLOCK ---

framebuffer.set_pixel(...)

Why after clipping? The mask index uses screen coordinates. If you index the mask before checking that the pixel is on screen, offscreen sprites can read the wrong mask entry or raise an IndexError.

Out of scope

  • building the mask inside Console.render_framebuffer()
  • selecting the background pattern table from PPUCTRL bit 4
  • sprite 0 hit
  • sprite overflow
  • pygame

Run this lesson

uv run pytest tests/chapter_09_sprite_rendering/test_312_sprite_priority_uses_background_opaque_mask.py -v