cge_nes 0.1.2

Cycle-accurate NES (Nintendo Entertainment System) emulator library: CPU, PPU, cartridge, input, and iNES ROM loading.
Documentation
//! Sprite data handling for PPU rendering.
//!
//! This module contains the data structures used to track and process sprite
//! information during PPU rendering. It handles:
//! * Storing sprite attributes from OAM
//! * Managing sprite pattern data for the current scanline
//! * Supporting sprite rendering operations

use crate::ppu::oam;

/// Represents a sprite's data needed for rendering.
///
/// Contains both the original Object Attribute Memory (OAM) entry data
/// and the calculated pattern table data for the current scanline.
/// The 8-byte alignment helps optimize memory access patterns.
#[repr(align(8))]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct SpriteData {
    /// Original OAM entry containing position, attributes, and tile number
    obj_attributes: oam::Entry,
    /// Pattern table data for current scanline as (high byte, low byte),
    /// or None if not yet loaded
    row_color_index_bytes: Option<(u8, u8)>,
    /// The index of this sprite in OAM (0..=63). Used to identify
    /// sprite 0 for the sprite-0 hit flag and to preserve OAM priority
    /// ordering after latching.
    oam_index: u8,
}

impl SpriteData {
    /// Creates a new sprite data entry from an OAM entry and its OAM index.
    ///
    /// # Parameters
    /// * `obj_attributes` - The OAM entry containing the sprite's base data
    /// * `oam_index` - The sprite's index in OAM (0..=63)
    pub fn with_oam_index(obj_attributes: oam::Entry, oam_index: u8) -> Self {
        Self {
            obj_attributes,
            row_color_index_bytes: None,
            oam_index,
        }
    }

    /// Returns the original OAM entry for this sprite.
    pub fn obj_attributes(&self) -> oam::Entry {
        self.obj_attributes
    }

    /// Returns the OAM index of this sprite (0..=63).
    pub fn oam_index(&self) -> u8 {
        self.oam_index
    }

    /// Returns the pattern table data for the current scanline, if loaded.
    pub fn row_color_index_bytes(&self) -> Option<(u8, u8)> {
        self.row_color_index_bytes
    }

    /// Sets the pattern table data for the current scanline.
    ///
    /// # Parameters
    /// * `row_color_index_bytes` - Tuple of (high byte, low byte) from pattern table
    pub fn set_row_color_index_bytes(&mut self, row_color_index_bytes: (u8, u8)) {
        self.row_color_index_bytes = Some(row_color_index_bytes);
    }

    /// Stores one of the two pattern-table bytes for the current row of this
    /// sprite, used by the cycle-accurate sprite fetch path.
    ///
    /// The two pattern-table reads for a given sprite row happen on different
    /// cycles (low bitplane first, then high bitplane). `high_byte = false`
    /// stores the low bitplane byte and `high_byte = true` stores the high
    /// bitplane byte. The pair is kept in `(high, low)` order to match
    /// [`Self::set_row_color_index_bytes`].
    pub fn set_row_color_index_byte(&mut self, value: u8, high_byte: bool) {
        let mut row_color_index_bytes = self.row_color_index_bytes.unwrap_or((0, 0));
        if high_byte {
            row_color_index_bytes.0 = value;
        } else {
            row_color_index_bytes.1 = value;
        }
        self.row_color_index_bytes = Some(row_color_index_bytes);
    }

    /// Sets the sprite to be drawn in front of background tiles (test helper).
    #[cfg(test)]
    pub fn set_draw_front(&mut self) {
        self.obj_attributes.set_draw_front()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn set_row_color_index_byte_initializes_then_updates() {
        let mut sprite = SpriteData::with_oam_index(oam::Entry::default(), 0);

        // No row bytes loaded yet; set_row_color_index_byte initializes the pair.
        sprite.set_row_color_index_byte(0xAA, false); // low bitplane
        assert_eq!(sprite.row_color_index_bytes(), Some((0x00, 0xAA)));

        sprite.set_row_color_index_byte(0xBB, true); // high bitplane
        assert_eq!(sprite.row_color_index_bytes(), Some((0xBB, 0xAA)));
    }

    #[test]
    fn set_row_color_index_byte_overwrites_previous() {
        let mut sprite = SpriteData::with_oam_index(oam::Entry::default(), 0);
        sprite.set_row_color_index_bytes((0x11, 0x22));

        sprite.set_row_color_index_byte(0x33, false); // low bitplane
        assert_eq!(sprite.row_color_index_bytes(), Some((0x11, 0x33)));

        sprite.set_row_color_index_byte(0x44, true); // high bitplane
        assert_eq!(sprite.row_color_index_bytes(), Some((0x44, 0x33)));
    }
}