use crate::cartridge::base_mapper::BaseMapper;
use crate::cartridge::mapper::{Mapper, MapperCapabilities};
const WRAM_SIZE: usize = 32 * 1024;
const WRAM_PAGE_SIZE: usize = 8 * 1024;
pub struct Mapper329 {
base: BaseMapper,
reg: u8,
wram: [u8; WRAM_SIZE],
}
impl Mapper329 {
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: 32,
..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,
reg: 0,
wram: [0; WRAM_SIZE],
};
mapper.update_banks();
mapper
}
fn update_banks(&mut self) {
let prg_bank = (self.reg & 0x1F) as i16;
self.base.select_prg_page(0, prg_bank);
self.base.select_chr_page(0, 0);
}
fn wram_page(&self) -> usize {
((self.reg >> 6) & 0x03) as usize
}
fn wram_offset(&self, addr: u16) -> usize {
let page_base = self.wram_page() * WRAM_PAGE_SIZE;
page_base + (addr - 0x6000) as usize
}
}
impl Mapper for Mapper329 {
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.wram[self.wram_offset(addr)],
0x8000..=0xFFFF => self.base.read_prg_rom(addr),
_ => 0,
}
}
fn write_prg(&mut self, addr: u16, value: u8) {
match addr {
0x6000..=0x7FFF => {
let offset = self.wram_offset(addr);
self.wram[offset] = value;
}
0x8000..=0xFFFF => {
self.reg = value;
self.update_banks();
}
_ => {}
}
}
fn wram_size(&self) -> usize {
WRAM_SIZE
}
fn wram_snapshot(&self) -> Vec<u8> {
self.wram.to_vec()
}
fn load_wram_snapshot(&mut self, data: &[u8]) {
let len = data.len().min(WRAM_SIZE);
self.wram[..len].copy_from_slice(&data[..len]);
}
fn registers_snapshot(&self) -> Vec<u8> {
vec![self.reg]
}
fn restore_registers(&mut self, data: &[u8]) {
if let Some(&r) = data.first() {
self.reg = r;
self.update_banks();
}
}
}
#[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 = 11; const CHR_BANKS: usize = 7;
fn make_mapper() -> Mapper329 {
let prg = banked_data(32 * 1024, PRG_BANKS);
let chr = banked_data(8 * 1024, CHR_BANKS);
Mapper329::new(MapperContext::new_for_test(
329,
prg,
chr,
NametableLayout::Horizontal,
))
}
#[test]
fn mapper_329_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(
329,
prg,
chr,
NametableLayout::Horizontal,
));
assert!(
result.is_ok(),
"Mapper 329 must be registered in the factory"
);
}
#[test]
fn initial_prg_bank_is_zero() {
let mapper = make_mapper();
assert_eq!(
mapper.read_prg(0x8000),
0x00,
"initial PRG bank should be 0"
);
}
#[test]
fn prg_banking_selects_32kb_bank_via_low_5_bits() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0x03);
assert_eq!(
mapper.read_prg(0x8000),
0x03,
"PRG bank 3 should be mapped at $8000"
);
}
#[test]
fn prg_bank_register_ignores_upper_bits() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0xC3);
assert_eq!(
mapper.read_prg(0x8000),
0x03,
"PRG bank should use only bits 4:0"
);
}
#[test]
fn prg_register_write_at_any_address_in_8000_ffff() {
let mut mapper = make_mapper();
mapper.write_prg(0xFFFF, 0x05);
assert_eq!(
mapper.read_prg(0x8000),
0x05,
"write to $FFFF should set PRG bank 5"
);
}
#[test]
fn chr_is_fixed_at_bank_0_initially() {
let mapper = make_mapper();
assert_eq!(
mapper.base.read_chr(0x0000),
0x00,
"CHR should be at bank 0"
);
}
#[test]
fn chr_remains_at_bank_0_after_register_write() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0x06);
assert_eq!(
mapper.base.read_chr(0x0000),
0x00,
"CHR must remain at bank 0"
);
}
#[test]
fn initial_wram_page_is_zero() {
let mut mapper = make_mapper();
mapper.write_prg(0x6000, 0xAB);
assert_eq!(
mapper.read_prg(0x6000),
0xAB,
"initial WRAM page 0 should be accessible at $6000"
);
}
#[test]
fn wram_page_selected_by_bits_7_6() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0x00); mapper.write_prg(0x6000, 0xAA);
mapper.write_prg(0x8000, 0x40); mapper.write_prg(0x6000, 0xBB);
mapper.write_prg(0x8000, 0x80); mapper.write_prg(0x6000, 0xCC);
mapper.write_prg(0x8000, 0xC0); mapper.write_prg(0x6000, 0xDD);
mapper.write_prg(0x8000, 0x00); assert_eq!(mapper.read_prg(0x6000), 0xAA, "page 0 should hold 0xAA");
mapper.write_prg(0x8000, 0x40);
assert_eq!(mapper.read_prg(0x6000), 0xBB, "page 1 should hold 0xBB");
mapper.write_prg(0x8000, 0x80);
assert_eq!(mapper.read_prg(0x6000), 0xCC, "page 2 should hold 0xCC");
mapper.write_prg(0x8000, 0xC0);
assert_eq!(mapper.read_prg(0x6000), 0xDD, "page 3 should hold 0xDD");
}
#[test]
fn wram_pages_are_independent_8kb_windows() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0x00);
mapper.write_prg(0x6000, 0x11);
mapper.write_prg(0x7FFF, 0x22);
mapper.write_prg(0x8000, 0x40);
assert_ne!(
mapper.read_prg(0x6000),
0x11,
"page 1 $6000 should differ from page 0"
);
assert_ne!(
mapper.read_prg(0x7FFF),
0x22,
"page 1 $7FFF should differ from page 0"
);
mapper.write_prg(0x8000, 0x00);
assert_eq!(
mapper.read_prg(0x6000),
0x11,
"page 0 $6000 should be preserved"
);
assert_eq!(
mapper.read_prg(0x7FFF),
0x22,
"page 0 $7FFF should be preserved"
);
}
#[test]
fn wram_size_is_32kb() {
let mapper = make_mapper();
assert_eq!(mapper.wram_size(), 32 * 1024, "WRAM must be 32 KiB");
}
#[test]
fn wram_snapshot_and_restore_roundtrip() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0x00);
mapper.write_prg(0x6000, 0x42);
mapper.write_prg(0x8000, 0x40);
mapper.write_prg(0x6000, 0x99);
let snapshot = mapper.wram_snapshot();
let mut mapper2 = make_mapper();
mapper2.load_wram_snapshot(&snapshot);
mapper2.write_prg(0x8000, 0x00);
assert_eq!(
mapper2.read_prg(0x6000),
0x42,
"page 0 value should survive snapshot"
);
mapper2.write_prg(0x8000, 0x40);
assert_eq!(
mapper2.read_prg(0x6000),
0x99,
"page 1 value should survive snapshot"
);
}
#[test]
fn registers_snapshot_and_restore_roundtrip() {
let mut mapper = make_mapper();
mapper.write_prg(0x8000, 0xC5); let snap = mapper.registers_snapshot();
let mut mapper2 = make_mapper();
mapper2.restore_registers(&snap);
assert_eq!(
mapper2.read_prg(0x8000),
0x05,
"PRG bank should be restored to 5"
);
}
}