cge_nes 0.1.1

Cycle-accurate NES (Nintendo Entertainment System) emulator library: CPU, PPU, cartridge, input, and iNES ROM loading.
Documentation
//! Representation of a single OAM entry and related helper types.
//!
//! An OAM entry contains four bytes in the order: Y, Tile Index, Attributes,
//! X. This module provides the `Entry` struct with accessor and field
//! read/write helpers, a `TileIndexNumber` wrapper for tile index semantics,
//! and the `Attributes` bitflags describing palette, priority and flip state.

use bitflags::bitflags;

bitflags! {
    /// Flags describing sprite attributes stored in an OAM entry's attribute byte.
    ///
    /// Bits encode palette selection, priority (in-front / behind background) and
    /// horizontal/vertical flip flags.
    #[derive(Default, PartialEq, Eq, Copy, Clone, Debug)]
    pub struct Attributes: u8 {
        const PALETTE_LOW =     0b00000001;
        const PALETTE_HIGH =    0b00000010;
        const PRIORITY =        0b00100000;
        const FLIP_H =          0b01000000;
        const FLIP_V =          0b10000000;
        const ALL =     Self::PALETTE_LOW.bits() | Self::PALETTE_HIGH.bits() | Self::PRIORITY.bits() | Self::FLIP_H.bits() | Self::FLIP_V.bits();
    }
}

/// Wrapper type for the OAM tile index byte with helper accessors.
///
/// The hardware encodes both a tile index and a small bank bit; this type
/// provides convenience methods to extract the pattern table index and bank.
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub struct TileIndexNumber(u8);

/// Index of a selected field within a 4-byte OAM entry.
///
/// Used when reading/writing OAM using a byte address (0..255) where the low
/// two bits select the field inside a 4-byte entry.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum SelectedField {
    /// Y coordinate (first byte in entry)
    YCoord = 0,
    /// Tile index (second byte in entry)
    TileNum = 1,
    /// Attribute flags (third byte in entry)
    Attributes = 2,
    /// X coordinate (fourth byte in entry)
    XCoord = 3,
}

/// Representation of a single OAM entry (4 bytes) with typed accessors.
///
/// The struct is aligned to 4 bytes to reflect the hardware layout and is
/// copyable so it can be cheaply passed around.
#[repr(align(4))]
#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)]
pub struct Entry {
    y_coord: u8,
    tile_num: TileIndexNumber,
    attributes: Attributes,
    x_coord: u8,
}

impl From<u8> for SelectedField {
    fn from(value: u8) -> Self {
        match value & 0x03 {
            0 => SelectedField::YCoord,
            1 => SelectedField::TileNum,
            2 => SelectedField::Attributes,
            3 => SelectedField::XCoord,
            _ => unreachable!(),
        }
    }
}

impl From<TileIndexNumber> for u8 {
    fn from(value: TileIndexNumber) -> Self {
        value.0
    }
}

impl From<u8> for TileIndexNumber {
    fn from(value: u8) -> Self {
        TileIndexNumber(value)
    }
}

impl TileIndexNumber {
    /// Pattern index (tile number) ignoring the low bank bit.
    pub fn pattern_index(&self) -> u8 {
        self.0 >> 1
    }

    /// Low-order bank bit associated with the tile index.
    pub fn pattern_bank(&self) -> u8 {
        self.0 & 0x01
    }
}

impl std::fmt::UpperHex for TileIndexNumber {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Delegate to the inner u8's UpperHex implementation so formatting flags are respected
        std::fmt::UpperHex::fmt(&self.0, f)
    }
}

impl std::fmt::LowerHex for TileIndexNumber {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        std::fmt::LowerHex::fmt(&self.0, f)
    }
}

impl std::fmt::Display for TileIndexNumber {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[allow(dead_code)]
impl Entry {
    fn new() -> Self {
        Self::default()
    }

    /// Y coordinate of the sprite (0..255). Typically the visible Y is 0..239.
    pub fn y(&self) -> u8 {
        self.y_coord
    }

    /// Tile index (wrapped in TileIndexNumber) for the sprite's graphic.
    pub fn tile_num(&self) -> TileIndexNumber {
        self.tile_num
    }

    /// X coordinate of the sprite (0..255). Visible X is 0..255.
    pub fn x(&self) -> u8 {
        self.x_coord
    }

    /// Raw attribute flags for the sprite.
    pub fn attributes(&self) -> Attributes {
        self.attributes
    }

    /// Set Y coordinate.
    pub fn set_y(&mut self, y: u8) {
        self.y_coord = y;
    }

    /// Set tile index.
    pub fn set_tile_num(&mut self, tile: TileIndexNumber) {
        self.tile_num = tile;
    }

    /// Set X coordinate.
    pub fn set_x(&mut self, x: u8) {
        self.x_coord = x;
    }

    /// Set attribute flags.
    pub fn set_attributes(&mut self, attr: Attributes) {
        self.attributes = attr;
    }

    /// Create an Entry from raw field values.
    pub fn with_data(y: u8, tile: u8, attr: Attributes, x: u8) -> Self {
        Self {
            y_coord: y,
            tile_num: tile.into(),
            attributes: attr,
            x_coord: x,
        }
    }

    /// Read a single byte-sized field by selected field index.
    pub fn read_field(&self, field: SelectedField) -> u8 {
        match field {
            SelectedField::YCoord => self.y_coord,
            SelectedField::TileNum => self.tile_num.into(),
            SelectedField::Attributes => self.attributes.bits(),
            SelectedField::XCoord => self.x_coord,
        }
    }

    /// Write a single byte-sized field by selected field index.
    pub fn write_field(&mut self, field: SelectedField, data: u8) {
        match field {
            SelectedField::YCoord => self.y_coord = data,
            SelectedField::TileNum => self.tile_num = data.into(),
            SelectedField::Attributes => self.attributes = Attributes::from_bits_truncate(data),
            SelectedField::XCoord => self.x_coord = data,
        }
    }

    /// Convenience: extract the two-bit palette index from attributes.
    pub fn palette_index(&self) -> u8 {
        self.attributes.bits() & 0b_0011
    }

    /// Whether vertical flip is enabled for this sprite.
    pub fn vertical_flip_flag(&self) -> bool {
        self.attributes.contains(Attributes::FLIP_V)
    }

    /// Whether horizontal flip is enabled for this sprite.
    pub fn horizontal_flip_flag(&self) -> bool {
        self.attributes.contains(Attributes::FLIP_H)
    }

    /// Whether the sprite should be drawn in front of background.
    pub fn draw_in_front_flag(&self) -> bool {
        !self.attributes.contains(Attributes::PRIORITY)
    }

    #[cfg(test)]
    pub fn set_draw_front(&mut self) {
        self.attributes.remove(Attributes::PRIORITY);
    }

    #[cfg(test)]
    pub fn set_palette(&mut self, palette: u8) {
        if (palette & 0b_1) != 0 {
            self.attributes.insert(Attributes::PALETTE_LOW);
        }

        if (palette & 0b_10) != 0 {
            self.attributes.insert(Attributes::PALETTE_HIGH);
        }
    }
}