neser 0.3.1

NESER - NES Emulator in Rust. Desktop (SDL) and WebAssembly frontends.
Documentation
//! VS System PPU palette tables.
//!
//! The VS System arcade cabinets used RGB PPUs (2C03, 2C04, 2C05) with fixed
//! internal palette ROMs. The 2C04 variants have scrambled palettes as a copy
//! protection measure. Each 3-digit DAC value (R, G, B with digits 0-7) maps
//! to an 8-bit channel via `floor(255 * digit / 7)`.
//!
//! Source: NESdev Wiki — PPU palettes
//! <https://www.nesdev.org/wiki/PPU_palettes>

use crate::nes::cartridge::VsPpuType;

/// Returns the VS System palette for the given PPU type, or `None` if the
/// type uses the standard composite (NTSC/PAL) palette.
pub fn vs_palette(ppu_type: &VsPpuType) -> Option<&'static [(u8, u8, u8); 64]> {
    match ppu_type {
        VsPpuType::Rp2c03b
        | VsPpuType::Rp2c03g
        | VsPpuType::Rc2c03b
        | VsPpuType::Rc2c03c
        | VsPpuType::Rc2c05_01
        | VsPpuType::Rc2c05_02
        | VsPpuType::Rc2c05_03
        | VsPpuType::Rc2c05_04
        | VsPpuType::Rc2c05_05 => Some(&PALETTE_2C03),
        VsPpuType::Rp2c04_0001 => Some(&PALETTE_2C04_0001),
        VsPpuType::Rp2c04_0002 => Some(&PALETTE_2C04_0002),
        VsPpuType::Rp2c04_0003 => Some(&PALETTE_2C04_0003),
        VsPpuType::Rp2c04_0004 => Some(&PALETTE_2C04_0004),
        VsPpuType::Unknown(_) => None,
    }
}

/// 2C03 / 2C05 RGB palette (shared by all non-2C04 VS PPU variants).
///
/// DAC formula: `channel = floor(255 * digit / 7)` where digit is 0–7.
#[rustfmt::skip]
pub const PALETTE_2C03: [(u8, u8, u8); 64] = [
    // $0x
    (0x6D, 0x6D, 0x6D), (0x00, 0x24, 0x91), (0x00, 0x00, 0xDA), (0x6D, 0x48, 0xDA),
    (0x91, 0x00, 0x6D), (0xB6, 0x00, 0x6D), (0xB6, 0x24, 0x00), (0x91, 0x48, 0x00),
    (0x6D, 0x48, 0x00), (0x24, 0x48, 0x00), (0x00, 0x6D, 0x24), (0x00, 0x91, 0x00),
    (0x00, 0x48, 0x48), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00),
    // $1x
    (0xB6, 0xB6, 0xB6), (0x00, 0x6D, 0xDA), (0x00, 0x48, 0xFF), (0x91, 0x00, 0xFF),
    (0xB6, 0x00, 0xFF), (0xFF, 0x00, 0x91), (0xFF, 0x00, 0x00), (0xDA, 0x6D, 0x00),
    (0x91, 0x6D, 0x00), (0x24, 0x91, 0x00), (0x00, 0x91, 0x00), (0x00, 0xB6, 0x6D),
    (0x00, 0x91, 0x91), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00),
    // $2x
    (0xFF, 0xFF, 0xFF), (0x6D, 0xB6, 0xFF), (0x91, 0x91, 0xFF), (0xDA, 0x6D, 0xFF),
    (0xFF, 0x00, 0xFF), (0xFF, 0x6D, 0xFF), (0xFF, 0x91, 0x00), (0xFF, 0xB6, 0x00),
    (0xDA, 0xDA, 0x00), (0x6D, 0xDA, 0x00), (0x00, 0xFF, 0x00), (0x48, 0xFF, 0xDA),
    (0x00, 0xFF, 0xFF), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00),
    // $3x
    (0xFF, 0xFF, 0xFF), (0xB6, 0xDA, 0xFF), (0xDA, 0xB6, 0xFF), (0xFF, 0xB6, 0xFF),
    (0xFF, 0x91, 0xFF), (0xFF, 0xB6, 0xB6), (0xFF, 0xDA, 0x91), (0xFF, 0xFF, 0x48),
    (0xFF, 0xFF, 0x6D), (0xB6, 0xFF, 0x48), (0x91, 0xFF, 0x6D), (0x48, 0xFF, 0xDA),
    (0x91, 0xDA, 0xFF), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00), (0x00, 0x00, 0x00),
];

/// RP2C04-0001 scrambled palette.
#[rustfmt::skip]
pub const PALETTE_2C04_0001: [(u8, u8, u8); 64] = [
    // $0x
    (0xFF, 0xB6, 0xB6), (0xDA, 0x6D, 0xFF), (0xFF, 0x00, 0x00), (0x91, 0x91, 0xFF),
    (0x00, 0x91, 0x91), (0x24, 0x48, 0x00), (0x48, 0x48, 0x48), (0xFF, 0x00, 0x91),
    (0xFF, 0xFF, 0xFF), (0x6D, 0x6D, 0x6D), (0xFF, 0xB6, 0x00), (0xB6, 0x00, 0x6D),
    (0x91, 0x00, 0x6D), (0xDA, 0xDA, 0x00), (0x6D, 0x48, 0x00), (0xFF, 0xFF, 0xFF),
    // $1x
    (0x6D, 0xB6, 0xFF), (0xDA, 0xB6, 0x6D), (0x6D, 0x24, 0x00), (0x6D, 0xDA, 0x00),
    (0x91, 0xDA, 0xFF), (0xDA, 0xB6, 0xFF), (0xFF, 0xDA, 0x91), (0x00, 0x48, 0xFF),
    (0xFF, 0xDA, 0x00), (0x48, 0xFF, 0xDA), (0x00, 0x00, 0x00), (0x48, 0x00, 0x00),
    (0xDA, 0xDA, 0xDA), (0x91, 0x91, 0x91), (0xFF, 0x00, 0xFF), (0x00, 0x24, 0x91),
    // $2x
    (0x00, 0x00, 0x6D), (0xB6, 0xDA, 0xFF), (0xFF, 0xB6, 0xFF), (0x00, 0xFF, 0x00),
    (0x00, 0xFF, 0xFF), (0x00, 0x48, 0x48), (0x00, 0xB6, 0x6D), (0xB6, 0x00, 0xFF),
    (0x00, 0x00, 0x00), (0x91, 0x48, 0x00), (0xFF, 0x91, 0xFF), (0xB6, 0x24, 0x00),
    (0x91, 0x00, 0xFF), (0x00, 0x00, 0xDA), (0xFF, 0x91, 0x00), (0x00, 0x00, 0x00),
    // $3x
    (0x00, 0x00, 0x00), (0x24, 0x91, 0x00), (0xB6, 0xB6, 0xB6), (0x00, 0x6D, 0x24),
    (0xB6, 0xFF, 0x48), (0x6D, 0x48, 0xDA), (0xFF, 0xFF, 0x00), (0xDA, 0x6D, 0x00),
    (0x00, 0x48, 0x00), (0x00, 0x6D, 0xDA), (0x00, 0x91, 0x00), (0x24, 0x24, 0x24),
    (0xFF, 0xFF, 0x6D), (0xFF, 0x6D, 0xFF), (0x91, 0x6D, 0x00), (0x91, 0xFF, 0x6D),
];

/// RP2C04-0002 scrambled palette.
#[rustfmt::skip]
pub const PALETTE_2C04_0002: [(u8, u8, u8); 64] = [
    // $0x
    (0x00, 0x00, 0x00), (0xFF, 0xB6, 0x00), (0x91, 0x6D, 0x00), (0xB6, 0xFF, 0x48),
    (0x91, 0xFF, 0x6D), (0xFF, 0x6D, 0xFF), (0x00, 0x91, 0x91), (0xB6, 0xDA, 0xFF),
    (0xFF, 0x00, 0x00), (0x91, 0x00, 0xFF), (0xFF, 0xFF, 0x6D), (0xFF, 0x91, 0xFF),
    (0xFF, 0xFF, 0xFF), (0xDA, 0x6D, 0xFF), (0x91, 0xDA, 0xFF), (0x00, 0x91, 0x00),
    // $1x
    (0x00, 0x48, 0x00), (0x6D, 0xB6, 0xFF), (0xB6, 0x24, 0x00), (0xDA, 0xDA, 0xDA),
    (0x00, 0xB6, 0x6D), (0x6D, 0xDA, 0x00), (0x48, 0x00, 0x00), (0x91, 0x91, 0xFF),
    (0x48, 0x48, 0x48), (0xFF, 0x00, 0xFF), (0x00, 0x00, 0x6D), (0x48, 0xFF, 0xDA),
    (0xDA, 0xB6, 0xFF), (0x6D, 0x48, 0x00), (0x00, 0x00, 0x00), (0x6D, 0x48, 0xDA),
    // $2x
    (0x91, 0x00, 0x6D), (0xFF, 0xDA, 0x91), (0xFF, 0x91, 0x00), (0xFF, 0xB6, 0xFF),
    (0x00, 0x6D, 0xDA), (0x6D, 0x24, 0x00), (0xB6, 0xB6, 0xB6), (0x00, 0x00, 0xDA),
    (0xB6, 0x00, 0xFF), (0xFF, 0xDA, 0x00), (0x6D, 0x6D, 0x6D), (0x24, 0x48, 0x00),
    (0x00, 0x48, 0xFF), (0x00, 0x00, 0x00), (0xDA, 0xDA, 0x00), (0xFF, 0xFF, 0xFF),
    // $3x
    (0xDA, 0xB6, 0x6D), (0x24, 0x24, 0x24), (0x00, 0xFF, 0x00), (0xDA, 0x6D, 0x00),
    (0x00, 0x48, 0x48), (0x00, 0x24, 0x91), (0xFF, 0x00, 0x91), (0x24, 0x91, 0x00),
    (0x00, 0x00, 0x00), (0x00, 0xFF, 0xFF), (0x91, 0x48, 0x00), (0xFF, 0xFF, 0x00),
    (0xFF, 0xB6, 0xB6), (0xB6, 0x00, 0x6D), (0x00, 0x6D, 0x24), (0x91, 0x91, 0x91),
];

/// RP2C04-0003 scrambled palette.
#[rustfmt::skip]
pub const PALETTE_2C04_0003: [(u8, u8, u8); 64] = [
    // $0x
    (0xB6, 0x00, 0xFF), (0xFF, 0x6D, 0xFF), (0x91, 0xFF, 0x6D), (0xB6, 0xB6, 0xB6),
    (0x00, 0x91, 0x00), (0xFF, 0xFF, 0xFF), (0xB6, 0xDA, 0xFF), (0x24, 0x48, 0x00),
    (0x00, 0x24, 0x91), (0x00, 0x00, 0x00), (0xFF, 0xDA, 0x91), (0x6D, 0x48, 0x00),
    (0xFF, 0x00, 0x91), (0xDA, 0xDA, 0xDA), (0xDA, 0xB6, 0x6D), (0x91, 0xDA, 0xFF),
    // $1x
    (0x91, 0x91, 0xFF), (0x00, 0x91, 0x91), (0xB6, 0x00, 0x6D), (0x00, 0x48, 0xFF),
    (0x24, 0x91, 0x00), (0x91, 0x6D, 0x00), (0xDA, 0x6D, 0x00), (0x00, 0xB6, 0x6D),
    (0x6D, 0x6D, 0x6D), (0x6D, 0x48, 0xDA), (0x00, 0x00, 0x00), (0x00, 0x00, 0xDA),
    (0xFF, 0x00, 0x00), (0xB6, 0x24, 0x00), (0xFF, 0x91, 0xFF), (0xFF, 0xB6, 0xB6),
    // $2x
    (0xDA, 0x6D, 0xFF), (0x00, 0x48, 0x00), (0x00, 0x00, 0x6D), (0xFF, 0xFF, 0x00),
    (0x24, 0x24, 0x24), (0xFF, 0xB6, 0x00), (0xFF, 0x91, 0x00), (0xFF, 0xFF, 0xFF),
    (0x6D, 0xDA, 0x00), (0x91, 0x00, 0x6D), (0x6D, 0xB6, 0xFF), (0xFF, 0x00, 0xFF),
    (0x00, 0x6D, 0xDA), (0x91, 0x91, 0x91), (0x00, 0x00, 0x00), (0x6D, 0x24, 0x00),
    // $3x
    (0x00, 0xFF, 0xFF), (0x48, 0x00, 0x00), (0xB6, 0xFF, 0x48), (0xFF, 0xB6, 0xFF),
    (0x91, 0x48, 0x00), (0x00, 0xFF, 0x00), (0xDA, 0xDA, 0x00), (0x48, 0x48, 0x48),
    (0x00, 0x6D, 0x24), (0x00, 0x00, 0x00), (0xDA, 0xB6, 0xFF), (0xFF, 0xFF, 0x6D),
    (0x91, 0x00, 0xFF), (0x48, 0xFF, 0xDA), (0xFF, 0xDA, 0x00), (0x00, 0x48, 0x48),
];

/// RP2C04-0004 scrambled palette.
#[rustfmt::skip]
pub const PALETTE_2C04_0004: [(u8, u8, u8); 64] = [
    // $0x
    (0x91, 0x6D, 0x00), (0x6D, 0x48, 0xDA), (0x00, 0x91, 0x91), (0xDA, 0xDA, 0x00),
    (0x00, 0x00, 0x00), (0xFF, 0xB6, 0xB6), (0x00, 0x24, 0x91), (0xDA, 0x6D, 0x00),
    (0xB6, 0xB6, 0xB6), (0x6D, 0x24, 0x00), (0x00, 0xFF, 0x00), (0x00, 0x00, 0x6D),
    (0xFF, 0xDA, 0x91), (0xFF, 0xFF, 0x00), (0x00, 0x91, 0x00), (0xB6, 0xFF, 0x48),
    // $1x
    (0xFF, 0x6D, 0xFF), (0x48, 0x00, 0x00), (0x00, 0x48, 0xFF), (0xFF, 0x91, 0xFF),
    (0x00, 0x00, 0x00), (0x48, 0x48, 0x48), (0xB6, 0x24, 0x00), (0xFF, 0x91, 0x00),
    (0xDA, 0xB6, 0x6D), (0x00, 0xB6, 0x6D), (0x91, 0x91, 0xFF), (0x24, 0x91, 0x00),
    (0x91, 0x00, 0x6D), (0x00, 0x00, 0x00), (0x91, 0xFF, 0x6D), (0x6D, 0xB6, 0xFF),
    // $2x
    (0xB6, 0x00, 0x6D), (0x00, 0x6D, 0x24), (0x91, 0x48, 0x00), (0x00, 0x00, 0xDA),
    (0x91, 0x00, 0xFF), (0xB6, 0x00, 0xFF), (0x6D, 0x6D, 0x6D), (0xFF, 0x00, 0x91),
    (0x00, 0x48, 0x48), (0xDA, 0xDA, 0xDA), (0x00, 0x6D, 0xDA), (0x00, 0x48, 0x00),
    (0x24, 0x24, 0x24), (0xFF, 0xFF, 0x6D), (0x91, 0x91, 0x91), (0xFF, 0x00, 0xFF),
    // $3x
    (0xFF, 0xB6, 0xFF), (0xFF, 0xFF, 0xFF), (0x6D, 0x48, 0x00), (0xFF, 0x00, 0x00),
    (0xFF, 0xDA, 0x00), (0x48, 0xFF, 0xDA), (0xFF, 0xFF, 0xFF), (0x91, 0xDA, 0xFF),
    (0x00, 0x00, 0x00), (0xFF, 0xB6, 0x00), (0xDA, 0x6D, 0xFF), (0xB6, 0xDA, 0xFF),
    (0x6D, 0xDA, 0x00), (0xDA, 0xB6, 0xFF), (0x00, 0xFF, 0xFF), (0x24, 0x48, 0x00),
];

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

    #[test]
    fn vs_palette_rp2c04_0001_returns_some() {
        let palette = vs_palette(&VsPpuType::Rp2c04_0001);
        assert!(palette.is_some(), "RP2C04-0001 should have a VS palette");
    }

    #[test]
    fn vs_palette_rp2c04_0002_returns_some() {
        let palette = vs_palette(&VsPpuType::Rp2c04_0002);
        assert!(palette.is_some(), "RP2C04-0002 should have a VS palette");
    }

    #[test]
    fn vs_palette_rp2c04_0003_returns_some() {
        let palette = vs_palette(&VsPpuType::Rp2c04_0003);
        assert!(palette.is_some(), "RP2C04-0003 should have a VS palette");
    }

    #[test]
    fn vs_palette_rp2c04_0004_returns_some() {
        let palette = vs_palette(&VsPpuType::Rp2c04_0004);
        assert!(palette.is_some(), "RP2C04-0004 should have a VS palette");
    }

    #[test]
    fn vs_palette_2c03_variants_return_2c03_palette() {
        // All 2C03/2C05 variants share the same RGB palette
        for ppu_type in &[
            VsPpuType::Rp2c03b,
            VsPpuType::Rp2c03g,
            VsPpuType::Rc2c03b,
            VsPpuType::Rc2c03c,
            VsPpuType::Rc2c05_01,
            VsPpuType::Rc2c05_02,
            VsPpuType::Rc2c05_03,
            VsPpuType::Rc2c05_04,
            VsPpuType::Rc2c05_05,
        ] {
            let palette = vs_palette(ppu_type);
            assert!(palette.is_some(), "{:?} should have a VS palette", ppu_type);
            assert_eq!(
                palette.unwrap(),
                &PALETTE_2C03,
                "{:?} should use the 2C03 palette",
                ppu_type
            );
        }
    }

    #[test]
    fn vs_palette_unknown_returns_none() {
        assert!(vs_palette(&VsPpuType::Unknown(42)).is_none());
    }

    // Spot-check specific entries from NESdev wiki data.
    // RP2C04-0001 $00 = DAC 755 → (0xFF, 0xB6, 0xB6)
    #[test]
    fn rp2c04_0001_entry_00_is_755() {
        assert_eq!(PALETTE_2C04_0001[0x00], (0xFF, 0xB6, 0xB6));
    }

    // RP2C04-0001 $08 = DAC 777 → (0xFF, 0xFF, 0xFF) (white)
    #[test]
    fn rp2c04_0001_entry_08_is_white() {
        assert_eq!(PALETTE_2C04_0001[0x08], (0xFF, 0xFF, 0xFF));
    }

    // RP2C04-0002 $00 = DAC 000 → (0x00, 0x00, 0x00) (black)
    #[test]
    fn rp2c04_0002_entry_00_is_black() {
        assert_eq!(PALETTE_2C04_0002[0x00], (0x00, 0x00, 0x00));
    }

    // RP2C04-0003 $05 = DAC 777 → (0xFF, 0xFF, 0xFF)
    #[test]
    fn rp2c04_0003_entry_05_is_white() {
        assert_eq!(PALETTE_2C04_0003[0x05], (0xFF, 0xFF, 0xFF));
    }

    // RP2C04-0004 $04 = DAC 000 → (0x00, 0x00, 0x00)
    #[test]
    fn rp2c04_0004_entry_04_is_black() {
        assert_eq!(PALETTE_2C04_0004[0x04], (0x00, 0x00, 0x00));
    }

    // 2C03 $00 = DAC 333 → (0x6D, 0x6D, 0x6D)
    #[test]
    fn palette_2c03_entry_00_is_333() {
        assert_eq!(PALETTE_2C03[0x00], (0x6D, 0x6D, 0x6D));
    }

    // 2C03 $20 = DAC 777 → (0xFF, 0xFF, 0xFF)
    #[test]
    fn palette_2c03_entry_20_is_white() {
        assert_eq!(PALETTE_2C03[0x20], (0xFF, 0xFF, 0xFF));
    }

    // 2C03 $0D = DAC 000 → (0x00, 0x00, 0x00)
    #[test]
    fn palette_2c03_entry_0d_is_black() {
        assert_eq!(PALETTE_2C03[0x0D], (0x00, 0x00, 0x00));
    }

    #[test]
    fn all_vs_palettes_have_64_entries() {
        assert_eq!(PALETTE_2C03.len(), 64);
        assert_eq!(PALETTE_2C04_0001.len(), 64);
        assert_eq!(PALETTE_2C04_0002.len(), 64);
        assert_eq!(PALETTE_2C04_0003.len(), 64);
        assert_eq!(PALETTE_2C04_0004.len(), 64);
    }
}