use crate::bus::bus::BusDevice;
use crate::input::ArkanoidController;
use crate::input::Controller;
use crate::input::PowerPad;
use crate::input::Zapper;
use std::cell::RefCell;
use std::ops::RangeInclusive;
use std::rc::Rc;
pub(crate) struct ControllerDevice {
controllers: [Rc<RefCell<Box<dyn Controller>>>; 2],
four_score_extra_button_states: Rc<RefCell<[u8; 2]>>,
four_score_enabled: bool,
four_score_strobe: bool,
four_score_index: [u8; 2],
famicom_four_players_enabled: bool,
famicom_four_players_strobe: bool,
famicom_four_players_index: [u8; 2],
famicom_mode: bool,
arkanoid_expansion: Option<Rc<RefCell<ArkanoidController>>>,
arkanoid_famicom_enabled: bool,
zapper_expansion: Option<Rc<RefCell<Zapper>>>,
zapper_famicom_enabled: bool,
power_pad_expansion: Option<Rc<RefCell<PowerPad>>>,
power_pad_famicom_enabled: bool,
}
impl ControllerDevice {
#[cfg(test)]
pub(crate) fn new(
port1_controller: Rc<RefCell<Box<dyn Controller>>>,
port2_controller: Rc<RefCell<Box<dyn Controller>>>,
) -> Self {
Self::new_with_four_score_state(
port1_controller,
port2_controller,
false,
false,
Rc::new(RefCell::new([0, 0])),
)
}
pub(crate) fn new_with_four_score_state(
port1_controller: Rc<RefCell<Box<dyn Controller>>>,
port2_controller: Rc<RefCell<Box<dyn Controller>>>,
four_score_enabled: bool,
famicom_four_players_enabled: bool,
four_score_extra_button_states: Rc<RefCell<[u8; 2]>>,
) -> Self {
Self {
controllers: [port1_controller, port2_controller],
four_score_extra_button_states,
four_score_enabled,
four_score_strobe: false,
four_score_index: [0, 0],
famicom_four_players_enabled,
famicom_four_players_strobe: false,
famicom_four_players_index: [0, 0],
famicom_mode: false,
arkanoid_expansion: None,
arkanoid_famicom_enabled: false,
zapper_expansion: None,
zapper_famicom_enabled: false,
power_pad_expansion: None,
power_pad_famicom_enabled: false,
}
}
pub(crate) fn set_four_score_enabled(&mut self, enabled: bool) {
self.four_score_enabled = enabled;
self.four_score_index = [0, 0];
self.four_score_strobe = false;
}
pub(crate) fn set_famicom_four_players_enabled(&mut self, enabled: bool) {
self.famicom_four_players_enabled = enabled;
self.famicom_four_players_index = [0, 0];
self.famicom_four_players_strobe = false;
}
pub(crate) fn set_arkanoid_famicom_expansion(
&mut self,
expansion: Option<Rc<RefCell<ArkanoidController>>>,
) {
self.arkanoid_expansion = expansion;
}
pub(crate) fn set_arkanoid_famicom_enabled(&mut self, enabled: bool) {
self.arkanoid_famicom_enabled = enabled;
}
pub(crate) fn set_zapper_famicom_expansion(&mut self, expansion: Option<Rc<RefCell<Zapper>>>) {
self.zapper_expansion = expansion;
}
pub(crate) fn set_zapper_famicom_enabled(&mut self, enabled: bool) {
self.zapper_famicom_enabled = enabled;
}
pub(crate) fn set_power_pad_famicom_expansion(
&mut self,
expansion: Option<Rc<RefCell<PowerPad>>>,
) {
self.power_pad_expansion = expansion;
}
pub(crate) fn set_power_pad_famicom_enabled(&mut self, enabled: bool) {
self.power_pad_famicom_enabled = enabled;
}
fn read_arkanoid_famicom_bit(&mut self, port_index: usize, is_dummy_read: bool) -> u8 {
let mut controller_state = self.controllers[port_index]
.borrow_mut()
.read(is_dummy_read);
if let Some(ref arkanoid) = self.arkanoid_expansion {
let expansion_bit = if port_index == 0 {
arkanoid.borrow().read_expansion_trigger()
} else {
arkanoid.borrow_mut().read_expansion_knob(is_dummy_read)
};
controller_state = (controller_state & !0x02) | expansion_bit;
}
controller_state
}
fn read_four_score_bit(&mut self, port_index: usize, is_dummy_read: bool) -> u8 {
let idx = self.four_score_index[port_index];
let mut controller_state = self.controllers[port_index]
.borrow_mut()
.read(is_dummy_read);
let serial_bit = if idx < 8 {
controller_state & 0x01
} else if idx < 16 {
let extra_state = self.four_score_extra_button_states.borrow();
let player_state = extra_state[port_index];
(player_state >> (idx - 8)) & 0x01
} else if idx < 24 {
let signature = if port_index == 0 { 0x10 } else { 0x20 };
(signature >> (idx - 16)) & 0x01
} else {
1
};
controller_state = (controller_state & !0x01) | serial_bit;
if !is_dummy_read && !self.four_score_strobe {
self.four_score_index[port_index] = self.four_score_index[port_index].saturating_add(1);
}
controller_state
}
fn read_famicom_four_players_bit(&mut self, port_index: usize, is_dummy_read: bool) -> u8 {
let mut controller_state = self.controllers[port_index]
.borrow_mut()
.read(is_dummy_read);
let idx = self.famicom_four_players_index[port_index];
let extra_state = self.four_score_extra_button_states.borrow()[port_index];
let serial_bit = if idx < 8 {
(extra_state >> idx) & 0x01
} else {
1
};
controller_state = (controller_state & !0x02) | (serial_bit << 1);
if !is_dummy_read && !self.famicom_four_players_strobe {
self.famicom_four_players_index[port_index] =
self.famicom_four_players_index[port_index].saturating_add(1);
}
controller_state
}
fn read_zapper_famicom_bit(&mut self, port_index: usize, is_dummy_read: bool) -> u8 {
let mut controller_state = self.controllers[port_index]
.borrow_mut()
.read(is_dummy_read);
if let (1, Some(zapper)) = (port_index, &self.zapper_expansion) {
let zapper_bits = zapper.borrow_mut().read(is_dummy_read);
controller_state = (controller_state & !0x18) | (zapper_bits & 0x18);
}
controller_state
}
fn read_power_pad_famicom_bit(&mut self, port_index: usize, is_dummy_read: bool) -> u8 {
if port_index != 1 {
return self.controllers[port_index]
.borrow_mut()
.read(is_dummy_read);
}
let mut controller_state = self.controllers[port_index]
.borrow_mut()
.read(is_dummy_read);
if let Some(power_pad) = &self.power_pad_expansion {
let power_pad_bits = power_pad.borrow_mut().read(is_dummy_read);
controller_state = (controller_state & !0x18) | (power_pad_bits & 0x18);
}
controller_state
}
}
impl BusDevice for ControllerDevice {
fn read(&mut self, addr: u16, open_bus: u8, is_dummy_read: bool) -> Option<u8> {
let index = (addr - 0x4016) as usize;
let mut controller_state = if self.four_score_enabled {
self.read_four_score_bit(index, is_dummy_read)
} else if self.famicom_four_players_enabled {
self.read_famicom_four_players_bit(index, is_dummy_read)
} else if self.arkanoid_famicom_enabled {
self.read_arkanoid_famicom_bit(index, is_dummy_read)
} else if self.zapper_famicom_enabled {
self.read_zapper_famicom_bit(index, is_dummy_read)
} else if self.power_pad_famicom_enabled {
self.read_power_pad_famicom_bit(index, is_dummy_read)
} else {
self.controllers[index].borrow_mut().read(is_dummy_read)
};
if self.famicom_mode && addr == 0x4016 {
controller_state &= !0x04;
}
let open_bus_mask = if self.famicom_mode
&& !(self.zapper_famicom_enabled && addr == 0x4017)
&& !(self.power_pad_famicom_enabled && addr == 0x4017)
{
0xF8
} else {
0xE0
};
Some((open_bus & open_bus_mask) | (controller_state & !open_bus_mask))
}
fn write(&mut self, addr: u16, value: u8, _is_dummy_write: bool) -> bool {
match addr {
0x4016 => {
let new_strobe = value & 0x01 != 0;
if self.four_score_strobe && !new_strobe {
self.four_score_index = [0, 0];
}
self.four_score_strobe = new_strobe;
if self.famicom_four_players_strobe && !new_strobe {
self.famicom_four_players_index = [0, 0];
}
self.famicom_four_players_strobe = new_strobe;
self.controllers[0].borrow_mut().write_strobe(value);
self.controllers[1].borrow_mut().write_strobe(value);
if let Some(ref arkanoid) = self.arkanoid_expansion {
arkanoid.borrow_mut().write_strobe(value);
}
if let Some(ref power_pad) = self.power_pad_expansion {
power_pad.borrow_mut().write_strobe(value);
}
true
}
0x4017 => false,
_ => false,
}
}
fn address_range(&self) -> RangeInclusive<u16> {
0x4016..=0x4017
}
fn sync_controller_modes(
&mut self,
four_score_enabled: bool,
famicom_four_players_enabled: bool,
famicom_mode: bool,
arkanoid_famicom_enabled: bool,
zapper_famicom_enabled: bool,
power_pad_famicom_enabled: bool,
) {
self.set_four_score_enabled(four_score_enabled);
self.set_famicom_four_players_enabled(famicom_four_players_enabled);
self.famicom_mode = famicom_mode;
self.set_arkanoid_famicom_enabled(arkanoid_famicom_enabled);
self.set_zapper_famicom_enabled(zapper_famicom_enabled);
self.set_power_pad_famicom_enabled(power_pad_famicom_enabled);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::input::{Button, ControllerInput, PowerPad};
fn read_24_bits(device: &mut ControllerDevice, addr: u16) -> u32 {
let mut value = 0u32;
for bit in 0..24 {
let sample = device.read(addr, 0x00, false).unwrap() & 0x01;
value |= (sample as u32) << bit;
}
value
}
struct TestController {
reads: Rc<RefCell<u32>>,
dummy_reads: Rc<RefCell<u32>>,
}
impl TestController {
fn new(reads: Rc<RefCell<u32>>, dummy_reads: Rc<RefCell<u32>>) -> Self {
Self { reads, dummy_reads }
}
}
impl Controller for TestController {
fn write_strobe(&mut self, _value: u8) {}
fn read(&mut self, is_dummy_read: bool) -> u8 {
if is_dummy_read {
*self.dummy_reads.borrow_mut() += 1;
} else {
*self.reads.borrow_mut() += 1;
}
0
}
fn capture_state(&self) -> crate::input::ControllerState {
crate::input::ControllerState::Joypad(crate::input::JoypadState {
strobe: false,
button_index: 0,
button_states: 0,
})
}
fn restore_state(&mut self, _state: &crate::input::ControllerState) {}
fn set_button(&mut self, _button: Button, _pressed: bool) -> bool {
true
}
fn set_mouse_x_position(&mut self, _position: u8) -> bool {
false
}
fn set_mouse_y_position(&mut self, _position: u8) -> bool {
false
}
fn set_mouse_left_button(&mut self, _pressed: bool) -> bool {
false
}
fn input_type(&self) -> ControllerInput {
ControllerInput::Gamepad
}
}
fn create_test_controller_device() -> ControllerDevice {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let controller1: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads.clone(), dummy_reads.clone()),
)));
let controller2: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads, dummy_reads),
)));
ControllerDevice::new(controller1, controller2)
}
#[test]
fn test_dummy_read_uses_no_clock() {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let reads_check = reads.clone();
let dummy_reads_check = dummy_reads.clone();
let controller1 = Rc::new(RefCell::new(Box::new(TestController::new(
reads.clone(),
dummy_reads.clone(),
)) as Box<dyn Controller>));
let controller2 = Rc::new(RefCell::new(
Box::new(TestController::new(reads, dummy_reads)) as Box<dyn Controller>,
));
let mut device = ControllerDevice::new(controller1, controller2);
device.read(0x4016, 0xFF, true);
assert_eq!(*reads_check.borrow(), 0);
assert_eq!(*dummy_reads_check.borrow(), 1);
}
#[test]
fn test_gamepad_open_bus_only_on_bits_5_to_7() {
let mut device = create_test_controller_device();
let result = device.read(0x4016, 0xBF, false).unwrap();
assert_eq!(
result, 0xA0,
"Expected $A0 (only bits 5-7 from open bus), got ${:02X}",
result
);
}
#[test]
fn test_gamepad_open_bus_with_40() {
let mut device = create_test_controller_device();
let result = device.read(0x4016, 0x40, false).unwrap();
assert_eq!(
result, 0x40,
"Expected $40 (bits 5-7 from open bus $40), got ${:02X}",
result
);
}
#[test]
fn test_famicom_open_bus_bits_3_to_7() {
let mut device = create_test_controller_device();
device.famicom_mode = true;
let result = device.read(0x4016, 0x40, false).unwrap();
assert_eq!(
result, 0x40,
"Expected $40 (bits 3-7 from open bus $40), got ${:02X}",
result
);
}
#[test]
fn test_famicom_open_bus_bits_3_4_differ_from_nes() {
let mut device = create_test_controller_device();
let nes_result = device.read(0x4016, 0x18, false).unwrap();
assert_eq!(
nes_result, 0x00,
"NES-001 should ground bits 3-4, got ${:02X}",
nes_result
);
device.famicom_mode = true;
let famicom_result = device.read(0x4016, 0x18, false).unwrap();
assert_eq!(
famicom_result, 0x18,
"Famicom should pass bits 3-4 from open bus, got ${:02X}",
famicom_result
);
}
#[test]
fn test_four_score_port1_sequence() {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let controller1: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads.clone(), dummy_reads.clone()),
)));
let controller2: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads, dummy_reads),
)));
let mut device = ControllerDevice::new(controller1, controller2);
device.set_four_score_enabled(true);
assert!(device.write(0x4016, 1, false));
assert!(device.write(0x4016, 0, false));
let bits = read_24_bits(&mut device, 0x4016);
assert_eq!(bits, 0x0010_0000);
}
#[test]
fn test_four_score_port2_sequence() {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let controller1: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads.clone(), dummy_reads.clone()),
)));
let controller2: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads, dummy_reads),
)));
let mut device = ControllerDevice::new(controller1, controller2);
device.set_four_score_enabled(true);
assert!(device.write(0x4016, 1, false));
assert!(device.write(0x4016, 0, false));
let bits = read_24_bits(&mut device, 0x4017);
assert_eq!(bits, 0x0020_0000);
}
#[test]
fn test_famicom_four_players_sets_player3_serial_on_4016_bit1() {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let controller1: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads.clone(), dummy_reads.clone()),
)));
let controller2: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads, dummy_reads),
)));
let extra_states = Rc::new(RefCell::new([0x01, 0x00]));
let mut device = ControllerDevice::new_with_four_score_state(
controller1,
controller2,
false,
true,
extra_states,
);
assert!(device.write(0x4016, 1, false));
assert!(device.write(0x4016, 0, false));
let first = device.read(0x4016, 0x00, false).unwrap();
assert_eq!(first & 0x02, 0x02);
}
#[test]
fn test_famicom_four_players_sets_player4_serial_on_4017_bit1() {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let controller1: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads.clone(), dummy_reads.clone()),
)));
let controller2: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads, dummy_reads),
)));
let extra_states = Rc::new(RefCell::new([0x00, 0x01]));
let mut device = ControllerDevice::new_with_four_score_state(
controller1,
controller2,
false,
true,
extra_states,
);
assert!(device.write(0x4016, 1, false));
assert!(device.write(0x4016, 0, false));
let first = device.read(0x4017, 0x00, false).unwrap();
assert_eq!(first & 0x02, 0x02);
}
#[test]
fn test_famicom_microphone_bit2_of_4016_is_always_zero() {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let controller1: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads.clone(), dummy_reads.clone()),
)));
let controller2: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads, dummy_reads),
)));
let mut device = ControllerDevice::new_with_four_score_state(
controller1,
controller2,
false,
true, Rc::new(RefCell::new([0xFF, 0xFF])), );
assert!(device.write(0x4016, 1, false));
assert!(device.write(0x4016, 0, false));
for _ in 0..16 {
let value = device.read(0x4016, 0x00, false).unwrap();
assert_eq!(
value & 0x04,
0,
"Microphone bit (bit 2) should always be 0, got ${:02X}",
value
);
}
}
fn create_arkanoid_expansion_device() -> (ControllerDevice, Rc<RefCell<ArkanoidController>>) {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let controller1: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads.clone(), dummy_reads.clone()),
)));
let controller2: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads, dummy_reads),
)));
let arkanoid = Rc::new(RefCell::new(ArkanoidController::new()));
let mut device = ControllerDevice::new(controller1, controller2);
device.set_arkanoid_famicom_expansion(Some(arkanoid.clone()));
device.set_arkanoid_famicom_enabled(true);
device.famicom_mode = true;
(device, arkanoid)
}
#[test]
fn test_arkanoid_famicom_expansion_fire_on_4016_bit1() {
let (mut device, arkanoid) = create_arkanoid_expansion_device();
let value = device.read(0x4016, 0x00, false).unwrap();
assert_eq!(
value & 0x02,
0x00,
"Fire should be 0 when not pressed, got ${:02X}",
value
);
arkanoid.borrow_mut().set_trigger(true);
let value = device.read(0x4016, 0x00, false).unwrap();
assert_eq!(
value & 0x02,
0x02,
"Fire should be 1 on bit 1 when pressed, got ${:02X}",
value
);
}
#[test]
fn test_arkanoid_famicom_expansion_knob_on_4017_bit1() {
let (mut device, arkanoid) = create_arkanoid_expansion_device();
arkanoid.borrow_mut().set_position(0x92);
assert!(device.write(0x4016, 1, false));
assert!(device.write(0x4016, 0, false));
let expected_bits = [0, 1, 1, 0, 1, 1, 0, 1];
for (i, expected) in expected_bits.iter().enumerate() {
let value = device.read(0x4017, 0x00, false).unwrap();
assert_eq!(
(value >> 1) & 0x01,
*expected,
"Bit {} expected {}, got value 0x{:02X}",
i,
expected,
value
);
}
}
#[test]
fn test_arkanoid_famicom_expansion_strobe_forwarded() {
let (mut device, arkanoid) = create_arkanoid_expansion_device();
arkanoid.borrow_mut().set_position(0x92);
assert!(device.write(0x4016, 1, false));
assert!(device.write(0x4016, 0, false));
let first = device.read(0x4017, 0x00, false).unwrap();
let _ = device.read(0x4017, 0x00, false).unwrap();
assert!(device.write(0x4016, 1, false));
assert!(device.write(0x4016, 0, false));
let after_strobe = device.read(0x4017, 0x00, false).unwrap();
assert_eq!(
first & 0x02,
after_strobe & 0x02,
"Strobe should reset shift register to first bit"
);
}
#[test]
fn test_arkanoid_famicom_expansion_does_not_affect_port_controller_bit0() {
let (mut device, _arkanoid) = create_arkanoid_expansion_device();
let value = device.read(0x4016, 0x00, false).unwrap();
assert_eq!(
value & 0x01,
0x00,
"Bit 0 should come from port controller, got ${:02X}",
value
);
}
fn create_zapper_expansion_device() -> (ControllerDevice, Rc<RefCell<crate::input::Zapper>>) {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let controller1: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads.clone(), dummy_reads.clone()),
)));
let controller2: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads, dummy_reads),
)));
let ppu = Rc::new(RefCell::new(crate::ppu::Ppu::new_for_testing(
crate::console::TimingMode::Ntsc,
)));
let app_context = Rc::new(RefCell::new(
crate::app_context::AppContext::new_with_config(crate::console::Config::default()),
));
let zapper = Rc::new(RefCell::new(crate::input::Zapper::new(ppu, app_context)));
let mut device = ControllerDevice::new(controller1, controller2);
device.set_zapper_famicom_expansion(Some(zapper.clone()));
device.set_zapper_famicom_enabled(true);
device.famicom_mode = true;
(device, zapper)
}
fn create_power_pad_expansion_device() -> (ControllerDevice, Rc<RefCell<PowerPad>>) {
let reads = Rc::new(RefCell::new(0));
let dummy_reads = Rc::new(RefCell::new(0));
let controller1: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads.clone(), dummy_reads.clone()),
)));
let controller2: Rc<RefCell<Box<dyn Controller>>> = Rc::new(RefCell::new(Box::new(
TestController::new(reads, dummy_reads),
)));
let power_pad = Rc::new(RefCell::new(PowerPad::new()));
let mut device = ControllerDevice::new(controller1, controller2);
device.set_power_pad_famicom_expansion(Some(power_pad.clone()));
device.set_power_pad_famicom_enabled(true);
device.famicom_mode = true;
(device, power_pad)
}
#[test]
fn test_power_pad_famicom_expansion_serial_on_4017_bits_3_4() {
let (mut device, power_pad) = create_power_pad_expansion_device();
power_pad
.borrow_mut()
.set_button(crate::input::PowerPadButton::One, true);
power_pad
.borrow_mut()
.set_button(crate::input::PowerPadButton::Four, true);
assert!(device.write(0x4016, 1, false));
assert!(device.write(0x4016, 0, false));
let first = device.read(0x4017, 0x00, false).unwrap();
let second = device.read(0x4017, 0x00, false).unwrap();
assert_eq!(first & 0x18, 0x10);
assert_eq!(second & 0x18, 0x08);
}
#[test]
fn test_zapper_famicom_expansion_trigger_on_4017_bit4() {
let (mut device, zapper) = create_zapper_expansion_device();
let value = device.read(0x4017, 0x00, false).unwrap();
assert_eq!(
value & 0x10,
0x00,
"Trigger bit 4 should be 0 when not pressed, got ${:02X}",
value
);
zapper.borrow_mut().set_mouse_left_button(true);
let value = device.read(0x4017, 0x00, false).unwrap();
assert_eq!(
value & 0x10,
0x10,
"Trigger bit 4 should be 1 when pressed, got ${:02X}",
value
);
}
#[test]
fn test_zapper_famicom_expansion_trigger_not_on_4016() {
let (mut device, zapper) = create_zapper_expansion_device();
zapper.borrow_mut().set_mouse_left_button(true);
let value = device.read(0x4016, 0x00, false).unwrap();
assert_eq!(
value & 0x10,
0x00,
"Trigger bit 4 should NOT appear on $4016 (expansion bits only on $4017), got ${:02X}",
value
);
}
#[test]
fn test_zapper_famicom_expansion_light_sense_no_light_on_bit3() {
let (mut device, _zapper) = create_zapper_expansion_device();
let value = device.read(0x4017, 0x00, false).unwrap();
assert_eq!(
value & 0x08,
0x08,
"Light sense bit 3 should be 1 when no light detected, got ${:02X}",
value
);
}
#[test]
fn test_zapper_famicom_expansion_does_not_affect_bit0() {
let (mut device, _zapper) = create_zapper_expansion_device();
let value = device.read(0x4017, 0x00, false).unwrap();
assert_eq!(
value & 0x01,
0x00,
"Bit 0 should come from port controller, got ${:02X}",
value
);
}
#[test]
fn test_zapper_famicom_expansion_open_bus_preserves_bits_3_4_on_4017() {
let (mut device, zapper) = create_zapper_expansion_device();
zapper.borrow_mut().set_mouse_left_button(true);
let value = device.read(0x4017, 0x00, false).unwrap();
assert_eq!(
value & 0x18,
0x18,
"Bits 3-4 should be driven by Zapper expansion (not open bus 0x00). Got ${:02X}",
value
);
}
#[test]
fn test_zapper_famicom_expansion_open_bus_4016_keeps_famicom_mask() {
let (mut device, zapper) = create_zapper_expansion_device();
zapper.borrow_mut().set_mouse_left_button(true);
let value = device.read(0x4016, 0xFF, false).unwrap();
assert_eq!(
value & 0xF8,
0xF8,
"$4016 bits 3-7 should be open bus (mask 0xF8) even with Zapper expansion. Got ${:02X}",
value
);
}
}