use crate::cartridge::base_mapper::BaseMapper;
use crate::cartridge::mapper::{Mapper, MapperCapabilities};
pub struct Mapper330 {
base: BaseMapper,
}
impl Mapper330 {
pub fn new(ctx: super::mapper::MapperContext) -> Self {
let capabilities = MapperCapabilities {
prg_bank_size_kb: 32,
chr_bank_size_kb: 8,
max_prg_ram_kb: 8,
..Default::default()
};
let mut base = BaseMapper::new(&ctx, capabilities);
base.configure_prg_banking(32 * 1024);
base.configure_chr_banking(8 * 1024);
let mut mapper = Self { base };
mapper.update_banks();
mapper
}
fn update_banks(&mut self) {
let last_bank = self.base.prg_bank_count().saturating_sub(1) as i16;
self.base.select_prg_page(0, last_bank);
self.base.select_chr_page(0, 0);
}
}
impl Mapper for Mapper330 {
fn base(&self) -> &BaseMapper {
&self.base
}
fn base_mut(&mut self) -> &mut BaseMapper {
&mut self.base
}
fn read_prg(&self, addr: u16) -> u8 {
match addr {
0x6000..=0x7FFF => self.base.try_read_prg_ram(addr).unwrap_or(0),
0x8000..=0xFFFF => self.base.read_prg_rom(addr),
_ => 0,
}
}
fn write_prg(&mut self, addr: u16, value: u8) {
if (0x6000..=0x7FFF).contains(&addr) {
self.base.try_write_prg_ram(addr, value);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cartridge::NametableLayout;
use crate::cartridge::mapper::{MapperContext, create_mapper};
use crate::cartridge::test_helpers::banked_data;
const PRG_BANKS: usize = 5; const CHR_BANKS: usize = 7;
fn make_mapper() -> Mapper330 {
let prg = banked_data(32 * 1024, PRG_BANKS);
let chr = banked_data(8 * 1024, CHR_BANKS);
Mapper330::new(
MapperContext::new_for_test(330, prg, chr, NametableLayout::Horizontal)
.with_prg_ram_banks(1),
)
}
#[test]
fn mapper_330_is_registered() {
let prg = banked_data(32 * 1024, PRG_BANKS);
let chr = banked_data(8 * 1024, CHR_BANKS);
let result = create_mapper(
MapperContext::new_for_test(330, prg, chr, NametableLayout::Horizontal)
.with_prg_ram_banks(1),
);
assert!(
result.is_ok(),
"Mapper 330 must be registered in the factory"
);
}
#[test]
fn power_on_prg_8000_reads_last_bank_first_byte() {
let prg = banked_data(32 * 1024, PRG_BANKS);
let chr = banked_data(8 * 1024, CHR_BANKS);
let expected = prg[(PRG_BANKS - 1) * 32 * 1024];
let mapper = Mapper330::new(
MapperContext::new_for_test(330, prg, chr, NametableLayout::Horizontal)
.with_prg_ram_banks(1),
);
assert_eq!(
mapper.read_prg(0x8000),
expected,
"$8000 must map to last PRG bank at power-on"
);
}
#[test]
fn power_on_prg_ffff_reads_last_bank_last_byte() {
let prg = banked_data(32 * 1024, PRG_BANKS);
let chr = banked_data(8 * 1024, CHR_BANKS);
let expected = prg[PRG_BANKS * 32 * 1024 - 1];
let mapper = Mapper330::new(
MapperContext::new_for_test(330, prg, chr, NametableLayout::Horizontal)
.with_prg_ram_banks(1),
);
assert_eq!(
mapper.read_prg(0xFFFF),
expected,
"$FFFF must read last byte of last PRG bank"
);
}
#[test]
fn power_on_chr_0000_reads_bank_0() {
let mut mapper = make_mapper();
assert_eq!(
mapper.read_chr(0x0000),
0,
"$0000 must map to CHR bank 0 at power-on"
);
}
#[test]
fn power_on_chr_1fff_reads_bank_0_last_byte() {
let prg = banked_data(32 * 1024, PRG_BANKS);
let chr = banked_data(8 * 1024, CHR_BANKS);
let expected = chr[0x1FFF];
let mut mapper = Mapper330::new(
MapperContext::new_for_test(330, prg, chr, NametableLayout::Horizontal)
.with_prg_ram_banks(1),
);
assert_eq!(
mapper.read_chr(0x1FFF),
expected,
"$1FFF must read last byte of CHR bank 0"
);
}
#[test]
fn prg_nvram_read_write_at_6000() {
let mut mapper = make_mapper();
mapper.write_prg(0x6000, 0xAB);
assert_eq!(
mapper.read_prg(0x6000),
0xAB,
"$6000 PRG-NVRAM must be readable after write"
);
}
#[test]
fn prg_nvram_read_write_at_7fff() {
let mut mapper = make_mapper();
mapper.write_prg(0x7FFF, 0xCD);
assert_eq!(
mapper.read_prg(0x7FFF),
0xCD,
"$7FFF PRG-NVRAM must be readable after write"
);
}
#[test]
fn writes_to_prg_rom_space_do_not_change_prg() {
let mut mapper = make_mapper();
let initial = mapper.read_prg(0x8000);
mapper.write_prg(0x8000, 0xFF);
mapper.write_prg(0xFFFF, 0xFF);
assert_eq!(
mapper.read_prg(0x8000),
initial,
"PRG must remain at last bank after writes to ROM space"
);
}
#[test]
fn mirroring_is_horizontal() {
let mapper = make_mapper();
assert_eq!(
mapper.get_mirroring(),
NametableLayout::Horizontal,
"Mirroring must be fixed horizontal"
);
}
#[test]
fn mirroring_not_changed_by_writes() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0xFF);
assert_eq!(
mapper.get_mirroring(),
NametableLayout::Horizontal,
"Mirroring must not change after writes"
);
}
#[test]
fn irq_never_pending() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0xFF);
assert!(!mapper.irq_pending(), "Mapper 330 must never assert IRQ");
}
#[test]
fn registers_snapshot_is_empty() {
let mapper = make_mapper();
assert!(
mapper.registers_snapshot().is_empty(),
"Mapper 330 has no registers; snapshot must be empty"
);
}
#[test]
fn restore_registers_with_empty_data_is_noop() {
let mut mapper = make_mapper();
let expected = mapper.read_prg(0x8000);
mapper.restore_registers(&[]);
assert_eq!(mapper.read_prg(0x8000), expected);
}
#[test]
fn reset_leaves_prg_at_last_bank() {
let prg = banked_data(32 * 1024, PRG_BANKS);
let chr = banked_data(8 * 1024, CHR_BANKS);
let expected = prg[(PRG_BANKS - 1) * 32 * 1024];
let mut mapper = Mapper330::new(
MapperContext::new_for_test(330, prg, chr, NametableLayout::Horizontal)
.with_prg_ram_banks(1),
);
mapper.reset();
assert_eq!(
mapper.read_prg(0x8000),
expected,
"PRG must remain at last bank after reset"
);
}
}