gameboy-rust/tests/mosaic/demo.rs
0xrsydn 45b45e5c52 feat(video): track background and sprite mosaic state
Add vertical mosaic counters and background/sprite sampling regressions.
2026-10-05 12:58:38 +07:00

100 lines
4.1 KiB
Rust

use super::*;
use gba_rust::{
input::{Button, Buttons},
mosaic_demo::{MosaicDemo, MOSAIC_STATE},
tile_demo::TileDemo,
};
#[test]
fn cpu_demo_cycles_all_sizes_and_wraps_without_host_video_writes() {
let mut demo = MosaicDemo::new().unwrap();
let mut frame = Framebuffer::default();
assert_eq!(demo.machine().memory().read16(DISPCNT).unwrap(), 0);
assert_eq!(demo.machine().memory().read16(VRAM).unwrap(), 0);
for update in 1..=129 {
demo.frame(Buttons::default(), &mut frame).unwrap();
let m = demo.machine().memory();
assert_eq!(m.read32(MOSAIC_STATE).unwrap(), update);
assert_eq!(m.read16(DISPCNT).unwrap(), 0x1340);
assert_eq!(m.read16(BG0CNT).unwrap(), 0x1844);
assert_eq!(m.read16(BG1CNT).unwrap(), 0xd0c1);
assert_eq!(m.read16(OAM).unwrap(), 0x1048);
assert_eq!(m.read32(MOSAIC).unwrap(), 0);
assert_eq!(m.display_position().scanline, 160);
let size = (1 + (update / 8) % 16) as usize;
// Check terrain and foreground away from the sprite for every size.
for y in 0..48 {
for x in 0..WIDTH {
let sx = x / size * size;
let sy = y / size * size;
let cross = sx / 8 % 4 == 1 && sy / 8 % 4 == 1 && (sx % 8 == 3 || sy % 8 == 3);
let color = if cross {
0x3ff
} else {
[0x260, 0x3a0, 0x7d20, 0x7e80]
[((sx / 32 + sy / 32) % 2) * 2 + (sx / 2 + sy / 2) % 2]
};
assert_eq!(at(&frame, x, y), rgb(color), "update={update} at={x},{y}");
}
}
}
}
#[test]
fn both_bypasses_restore_original_scene_with_scroll_rotation_zoom_and_reset() {
let mut demo = MosaicDemo::new().unwrap();
let mut base = TileDemo::new().unwrap();
let mut f = Framebuffer::default();
let mut reference = Framebuffer::default();
for i in 0..32 {
let buttons = Buttons::default()
.with(Button::Left, i < 16)
.with(Button::Down, true)
.with(Button::Start, i == 31)
.with(Button::L, (8..24).contains(&i))
.with(Button::R, (16..28).contains(&i));
base.frame(buttons, &mut reference).unwrap();
demo.frame(buttons.with(Button::A, true).with(Button::B, true), &mut f)
.unwrap();
assert_eq!(f.pixels(), reference.pixels());
let m = demo.machine().memory();
assert_ne!(m.read16(OAM).unwrap() & 0x1000, 0);
assert_eq!(m.read16(OAM + 2).unwrap() & 0x1000, 0); // Z no longer flips.
assert_eq!(m.read16(OAM + 4).unwrap() & 0xc00, 0); // X no longer lowers priority.
assert_eq!(
m.read32(MOSAIC_STATE + 4).unwrap(),
base.machine().memory().read32(MOSAIC_STATE + 4).unwrap()
);
}
assert_eq!(demo.machine().memory().read32(MOSAIC_STATE + 4).unwrap(), 0);
assert_eq!(demo.machine().memory().read32(MOSAIC_STATE + 8).unwrap(), 0);
}
#[test]
fn dma_word_write_sets_bg_and_obj_fields_while_unused_halfword_ignores_writes() {
use gba_rust::{
cpu::Cpu,
dma::DMA_BASE,
machine::{Machine, StepKind},
};
let mut m = memory();
m.write16(DISPCNT, 0x1140).unwrap();
m.write16(BG0CNT, 0x1040).unwrap();
fill(&mut m, VRAM, 32, 0x1212);
m.write16(PAL + 4, 31).unwrap();
m.write16(PAL + 2, 0x3e0).unwrap();
object(&mut m, 0, 0x1000, 0, 0);
fill(&mut m, VRAM + 0x10000, 32, 0x0101);
m.write16(PAL + 0x202, 0x7c00).unwrap();
m.write32(0x0200_0000, 0xffff_0302).unwrap(); // BG3x1, OBJ4x1.
m.write32(DMA_BASE + 36, 0x0200_0000).unwrap();
m.write32(DMA_BASE + 40, MOSAIC).unwrap();
m.write32(DMA_BASE + 44, 0x8400_0001).unwrap();
let mut machine = Machine::new(Cpu::new(0x0300_0000), m);
assert_eq!(machine.step().unwrap(), StepKind::Dma { channel: 3 });
let f = render(machine.memory());
assert_eq!(at(&f, 3, 0), rgb(0x7c00));
assert_eq!(at(&f, 8, 0), rgb(31)); // BG samples x6, not x8's independent phase.
assert_eq!(at(&f, 9, 0), rgb(0x3e0));
assert_eq!(machine.memory().read32(MOSAIC).unwrap(), 0);
}