Skip to content

Commit 6c86554

Browse files
committed
add tests for current iteration of hz display
1 parent a127196 commit 6c86554

File tree

3 files changed

+157
-2
lines changed

3 files changed

+157
-2
lines changed

tests/src/hz_display.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use crate::{input, labels, util};
2+
use rusticnes_core::nes::NesState;
3+
4+
pub fn test() {
5+
let mut emu = util::emulator(None);
6+
7+
for _ in 0..4 {
8+
emu.run_until_vblank();
9+
}
10+
11+
let hz_flag = labels::get("hzFlag") as usize;
12+
let game_mode = labels::get("gameMode") as usize;
13+
let main_loop = labels::get("mainLoop");
14+
let level_number = labels::get("levelNumber") as usize;
15+
16+
// turn on hz display
17+
emu.memory.iram_raw[hz_flag] = 1;
18+
// trick game into thinking it should start an a-type run
19+
emu.memory.iram_raw[level_number] = 29;
20+
emu.memory.iram_raw[game_mode] = 4;
21+
emu.registers.pc = main_loop;
22+
23+
// wait for piece to become active
24+
util::run_n_vblanks(&mut emu, 5);
25+
// test left input sequence with delay of 5
26+
run_input_string(&mut emu, ".....L.L..L.L.");
27+
assert_hz_display(&mut emu, HzSpeed(25, 75), 4, 5, Dir::Left);
28+
29+
// wait a little, then test right input sequence
30+
util::run_n_vblanks(&mut emu, 5);
31+
run_input_string(&mut emu, "R....R.R.");
32+
assert_hz_display(&mut emu, HzSpeed(17, 17), 3, 5, Dir::Right);
33+
// make piece fall immediately so emulation takes less time
34+
run_input_string(&mut emu, "D");
35+
// fall 18 rows, then 3 frames before the 10-frame entry delay finishes
36+
util::run_n_vblanks(&mut emu, 18+10-3);
37+
run_input_string(&mut emu, "R.R.R.R.R.");
38+
assert_hz_display(&mut emu, HzSpeed(30, 5), 3, 1, Dir::Right);
39+
// TODO: have tests for -2 and -1 entry delay too.
40+
// each negative entry delay should have its own test because they all
41+
// need to be handled specifically in separate branches
42+
}
43+
44+
// note:
45+
fn run_input_string(emu: &mut NesState, inputs: &str) {
46+
for button in inputs.chars() {
47+
let controller_data = match button {
48+
'L' => input::LEFT,
49+
'R' => input::RIGHT,
50+
'D' => input::DOWN,
51+
'U' => input::UP,
52+
'A' => input::A,
53+
'B' => input::B,
54+
'S' => input::START,
55+
'T' => input::SELECT,
56+
'.' => 0,
57+
_ => panic!("character '{}' cannot be used as controller input", button),
58+
};
59+
util::set_controller(emu, controller_data);
60+
emu.run_until_vblank();
61+
}
62+
util::set_controller(emu, 0);
63+
}
64+
65+
#[derive(PartialEq)]
66+
struct HzSpeed (u8, u8);
67+
68+
enum Dir {
69+
Left,
70+
Right,
71+
}
72+
73+
fn assert_hz_display(emu: &mut NesState, speed: HzSpeed, tap: u8, dly: i8, dir: Dir) {
74+
assert_speed(emu, speed);
75+
assert_tap_count(emu, tap);
76+
assert_tap_delay(emu, dly);
77+
assert_tap_dir(emu, dir);
78+
}
79+
80+
fn assert_speed(emu: &mut NesState, speed: HzSpeed) {
81+
const HZ_ADDR: u16 = 0x21A3;
82+
let int_part = 10 * emu.mapper.debug_read_ppu(HZ_ADDR).unwrap()
83+
+ emu.mapper.debug_read_ppu(HZ_ADDR+1).unwrap();
84+
let frac_part = 10 * emu.mapper.debug_read_ppu(HZ_ADDR+3).unwrap()
85+
+ emu.mapper.debug_read_ppu(HZ_ADDR+4).unwrap();
86+
assert!(speed == HzSpeed(int_part, frac_part));
87+
}
88+
89+
fn assert_tap_count(emu: &mut NesState, tap: u8) {
90+
const TAP_COUNT_ADDR: u16 = 0x2228;
91+
assert!(tap == emu.mapper.debug_read_ppu(TAP_COUNT_ADDR).unwrap());
92+
}
93+
94+
fn assert_tap_delay(emu: &mut NesState, dly: i8) {
95+
const DELAY_ADDR: u16 = 0x2267;
96+
// sign of delay value
97+
let measured_sign = emu.mapper.debug_read_ppu(DELAY_ADDR).unwrap();
98+
if dly < 0 {
99+
assert!(measured_sign == 0x24);
100+
} else {
101+
assert!(measured_sign == 0xFF);
102+
}
103+
// magnitude
104+
let measured_abs = emu.mapper.debug_read_ppu(DELAY_ADDR+1).unwrap();
105+
assert!(measured_abs == dly.unsigned_abs());
106+
}
107+
108+
fn assert_tap_dir(emu: &mut NesState, dir: Dir) {
109+
const DIR_ADDR: u16 = 0x22A8;
110+
let measured_dir = emu.mapper.debug_read_ppu(DIR_ADDR).unwrap();
111+
match dir {
112+
Dir::Left => assert!(measured_dir == 0xD7),
113+
Dir::Right => assert!(measured_dir == 0xD6),
114+
}
115+
}

tests/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod score;
1616
mod sps;
1717
mod toprow;
1818
mod tspins;
19+
mod hz_display;
1920

2021
use gumdrop::Options;
2122

@@ -46,7 +47,7 @@ struct TestOptions {
4647
fn main() {
4748
let options = TestOptions::parse_args_default_or_exit();
4849

49-
let tests: [(&str, fn()); 10] = [
50+
let tests: [(&str, fn()); 11] = [
5051
("garbage4", garbage::test_garbage4_crash),
5152
("floor", floor::test),
5253
("tspins", tspins::test),
@@ -57,6 +58,7 @@ fn main() {
5758
("rng seeds", rng::test),
5859
("sps", sps::test),
5960
("palettes", palettes::test),
61+
("hz_display", hz_display::test),
6062
];
6163

6264
// run tests

tests/src/util.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use rusticnes_core::nes::NesState;
22
use rusticnes_core::{ cartridge, opcodes, opcode_info };
3-
use crate::labels;
3+
use minifb::{Key, KeyRepeat};
4+
use crate::{labels, video};
45

56
pub fn emulator(rom: Option<&[u8]>) -> NesState {
67
let rom = rom.unwrap_or(include_bytes!("../../tetris.nes"));
@@ -11,6 +12,43 @@ pub fn emulator(rom: Option<&[u8]>) -> NesState {
1112
emu
1213
}
1314

15+
pub fn run_n_vblanks(emu: &mut NesState, n: i32) {
16+
for _ in 0..n {
17+
emu.run_until_vblank();
18+
}
19+
}
20+
21+
/// debug helper for showing the current visual state.
22+
/// hotkeys: 'q' closes window, 's' steps by a frame
23+
#[allow(dead_code)]
24+
pub fn preview(emu: &mut NesState) {
25+
let mut view = video::Video::new();
26+
view.window.set_key_repeat_rate(0.1);
27+
loop {
28+
if !view.window.is_open() {
29+
break;
30+
}
31+
if view.window.is_key_pressed(Key::Q, KeyRepeat::No) {
32+
break;
33+
}
34+
if view.window.is_key_pressed(Key::S, KeyRepeat::Yes) {
35+
emu.run_until_vblank();
36+
}
37+
view.render(emu);
38+
}
39+
}
40+
41+
// emu.p1_input inverts the traditional bit assignments for the controller
42+
// (e.g. 0x01 corresponds to A) to more accurately emulate how bits are read
43+
// in.
44+
pub fn set_controller(emu: &mut NesState, buttons: u8) {
45+
let mut flipped_buttons = 0u8;
46+
for i in 0..8 {
47+
flipped_buttons |= ((buttons >> i) & 1) << (7-i);
48+
}
49+
emu.p1_input = flipped_buttons;
50+
}
51+
1452
pub fn run_to_return(emu: &mut NesState, print: bool) {
1553
opcodes::push(emu, 0);
1654
opcodes::push(emu, 0);

0 commit comments

Comments
 (0)