use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BgAffine {
pub pa: i16,
pub pb: i16,
pub pc: i16,
pub pd: i16,
pub x: i32,
pub y: i32,
pub internal_x: i32,
pub internal_y: i32,
}
impl Default for BgAffine {
fn default() -> Self {
Self {
pa: 0x0100,
pb: 0,
pc: 0,
pd: 0x0100,
x: 0,
y: 0,
internal_x: 0,
internal_y: 0,
}
}
}
impl BgAffine {
pub fn latch_reference_points(&mut self) {
self.internal_x = self.x;
self.internal_y = self.y;
}
pub fn increment_reference_points(&mut self) {
self.internal_x = self.internal_x.wrapping_add(self.pb as i32);
self.internal_y = self.internal_y.wrapping_add(self.pd as i32);
}
pub fn write_x_low(&mut self, lo: u16) {
self.x = sign_extend_28(((self.x as u32) & 0xFFFF_0000) | (lo as u32));
self.internal_x = self.x;
}
pub fn write_x_high(&mut self, hi: u16) {
self.x = sign_extend_28(((hi as u32) << 16) | ((self.x as u32) & 0x0000_FFFF));
self.internal_x = self.x;
}
pub fn write_y_low(&mut self, lo: u16) {
self.y = sign_extend_28(((self.y as u32) & 0xFFFF_0000) | (lo as u32));
self.internal_y = self.y;
}
pub fn write_y_high(&mut self, hi: u16) {
self.y = sign_extend_28(((hi as u32) << 16) | ((self.y as u32) & 0x0000_FFFF));
self.internal_y = self.y;
}
}
fn sign_extend_28(value: u32) -> i32 {
let masked = (value & 0x0FFF_FFFF) as i32;
(masked << 4) >> 4
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_match_hardware_power_on_values() {
let a = BgAffine::default();
assert_eq!(a.pa, 0x0100, "PA should default to 0x0100 (identity)");
assert_eq!(a.pb, 0);
assert_eq!(a.pc, 0);
assert_eq!(a.pd, 0x0100, "PD should default to 0x0100 (identity)");
assert_eq!(a.x, 0);
assert_eq!(a.y, 0);
}
#[test]
fn pa_pb_pc_pd_are_signed_8_8_fixed_point() {
let a = BgAffine {
pa: 0x0100_u16 as i16,
pd: 0x0100_u16 as i16,
pb: 0xFF00_u16 as i16,
..Default::default()
};
assert_eq!(a.pa, 0x0100);
assert_eq!(a.pd, 0x0100);
assert_eq!(a.pb, -256);
}
#[test]
fn x_low_then_high_preserves_low_halfword() {
let mut a = BgAffine::default();
a.write_x_low(0x1234);
a.write_x_high(0x0005);
assert_eq!(a.x, 0x0005_1234);
}
#[test]
fn x_high_then_low_preserves_high_halfword() {
let mut a = BgAffine::default();
a.write_x_high(0x000A);
a.write_x_low(0xBEEF);
assert_eq!(a.x, 0x000A_BEEF);
}
#[test]
fn x_high_discards_bits_above_27() {
let mut a = BgAffine::default();
a.write_x_low(0x0001);
a.write_x_high(0xF000); assert_eq!(a.x, 0x0000_0001);
}
#[test]
fn x_sign_extends_bit_27() {
let mut a = BgAffine::default();
a.write_x_low(0x0000);
a.write_x_high(0x0800); assert_eq!(a.x, -0x0800_0000);
}
#[test]
fn y_independent_of_x() {
let mut a = BgAffine::default();
a.write_x_low(0x1111);
a.write_x_high(0x0002);
a.write_y_low(0xABCD);
a.write_y_high(0x0003);
assert_eq!(a.x, 0x0002_1111);
assert_eq!(a.y, 0x0003_ABCD);
}
#[test]
fn y_sign_extends_bit_27() {
let mut a = BgAffine::default();
a.write_y_high(0x0FFF);
a.write_y_low(0xFFFF);
assert_eq!(a.y, -1);
}
}