use crate::grouscan::{grouscan_cross_sign, CfType};
#[derive(Debug, Clone, Copy)]
pub struct CfNarrowInputs<'a> {
pub gpz_at_entry: [i32; 2],
pub gdz_old: [i32; 2],
pub gi0: i32,
pub gi1: i32,
pub gylookup: &'a [i32],
pub old_mip: u32,
}
const MAX_VIRTUAL_STEPS: u32 = 64;
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
pub fn cf_narrow_simulate(cf: &mut [CfType], in_: &CfNarrowInputs<'_>) {
if in_.old_mip == 0 || cf.is_empty() || in_.gylookup.is_empty() {
return;
}
let n_steps = (1u32 << in_.old_mip)
.saturating_sub(1)
.min(MAX_VIRTUAL_STEPS);
let gdz_finer: [i32; 2] = [in_.gdz_old[0] >> 1, in_.gdz_old[1] >> 1];
let mut virtual_gpz = in_.gpz_at_entry;
if std::env::var_os("ROXLAP_CF_NARROW_NOP").is_some() {
return;
}
for _step in 0..n_steps {
let lane = usize::from(virtual_gpz[1] < virtual_gpz[0]);
let virtual_ogx = virtual_gpz[lane] & -0x1_0000_i32;
for entry in cf.iter_mut() {
let z1_idx = entry.z1 as usize;
if z1_idx < in_.gylookup.len() {
let gy_raw_fwall = in_.gylookup[z1_idx];
let test = grouscan_cross_sign(entry.cx1, entry.cy1, virtual_ogx, gy_raw_fwall);
if test > 0 && entry.i1 > entry.i0 {
entry.cx1 = entry.cx1.wrapping_sub(in_.gi0);
entry.cy1 = entry.cy1.wrapping_sub(in_.gi1);
entry.i1 -= 1;
}
}
let z0_idx = entry.z0 as usize;
if z0_idx < in_.gylookup.len() {
let gy_raw_cwall = in_.gylookup[z0_idx];
let test = grouscan_cross_sign(entry.cx0, entry.cy0, virtual_ogx, gy_raw_cwall);
if test <= 0 && entry.i0 < entry.i1 {
entry.cx0 = entry.cx0.wrapping_add(in_.gi0);
entry.cy0 = entry.cy0.wrapping_add(in_.gi1);
entry.i0 += 1;
}
}
}
virtual_gpz[lane] = virtual_gpz[lane].wrapping_add(gdz_finer[lane]);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn cf(i0: isize, i1: isize, cx1: i32, cy1: i32, z1: i32) -> CfType {
CfType {
i0,
i1,
z0: 0,
z1,
cx0: 0,
cy0: 0,
cx1,
cy1,
chz_layer: 0,
}
}
fn gylookup_with_one_at(z1: usize, gy: i32) -> Vec<i32> {
let mut g = vec![0i32; (z1 + 1).max(8)];
g[z1] = gy;
g
}
#[test]
fn old_mip_zero_is_noop() {
let mut cf_arr = [cf(0, 100, 0x0001_0000, 0, 10)];
let gylookup = gylookup_with_one_at(10, 1);
let inputs = CfNarrowInputs {
gpz_at_entry: [0x0001_0000, i32::MAX],
gdz_old: [0x0002_0000, 0],
gi0: 0x0002_0000,
gi1: 0,
gylookup: &gylookup,
old_mip: 0,
};
cf_narrow_simulate(&mut cf_arr, &inputs);
assert_eq!(cf_arr[0].i1, 100);
assert_eq!(cf_arr[0].cx1, 0x0001_0000);
assert_eq!(cf_arr[0].cy1, 0);
}
#[test]
fn axis_aligned_one_step_narrows_once() {
let mut cf_arr = [cf(0, 100, 0x0001_0000, 0, 10)];
let gylookup = gylookup_with_one_at(10, 1);
let inputs = CfNarrowInputs {
gpz_at_entry: [0x0001_0000, i32::MAX],
gdz_old: [0x0002_0000, 0],
gi0: 0x0002_0000,
gi1: 0,
gylookup: &gylookup,
old_mip: 1,
};
cf_narrow_simulate(&mut cf_arr, &inputs);
assert_eq!(cf_arr[0].i1, 99);
assert_eq!(cf_arr[0].cx1, -0x0001_0000);
assert_eq!(cf_arr[0].cy1, 0);
}
#[test]
fn axis_aligned_negative_cross_sign_no_narrow() {
let mut cf_arr = [cf(0, 100, 0x0001_0000, 0, 10)];
let gylookup = gylookup_with_one_at(10, -1);
let inputs = CfNarrowInputs {
gpz_at_entry: [0x0001_0000, i32::MAX],
gdz_old: [0x0002_0000, 0],
gi0: 0x0002_0000,
gi1: 0,
gylookup: &gylookup,
old_mip: 3,
};
cf_narrow_simulate(&mut cf_arr, &inputs);
assert_eq!(cf_arr[0].i1, 100);
assert_eq!(cf_arr[0].cx1, 0x0001_0000);
}
#[test]
fn cy_dominated_one_narrow_then_stops() {
let mut cf_arr = [cf(0, 100, 0x0001_0000, 0x0001_0000, 10)];
let gylookup = gylookup_with_one_at(10, 0);
let inputs = CfNarrowInputs {
gpz_at_entry: [0x0001_0000, i32::MAX],
gdz_old: [0x0002_0000, 0],
gi0: 0x0001_0000,
gi1: 0x0001_0000,
gylookup: &gylookup,
old_mip: 2, };
cf_narrow_simulate(&mut cf_arr, &inputs);
assert_eq!(cf_arr[0].i1, 99);
assert_eq!(cf_arr[0].cx1, 0);
assert_eq!(cf_arr[0].cy1, 0);
}
#[test]
fn empty_inputs_are_noop() {
let mut cf_arr: [CfType; 0] = [];
let gylookup = vec![1i32; 16];
let inputs = CfNarrowInputs {
gpz_at_entry: [0, 0],
gdz_old: [0, 0],
gi0: 0,
gi1: 0,
gylookup: &gylookup,
old_mip: 4,
};
cf_narrow_simulate(&mut cf_arr, &inputs);
assert!(cf_arr.is_empty());
let mut cf_arr2 = [cf(0, 100, 0x10000, 0, 0)];
let inputs2 = CfNarrowInputs {
gpz_at_entry: [0, 0],
gdz_old: [0, 0],
gi0: 0,
gi1: 0,
gylookup: &[],
old_mip: 4,
};
cf_narrow_simulate(&mut cf_arr2, &inputs2);
assert_eq!(cf_arr2[0].i1, 100);
}
#[test]
fn radar_exhaustion_stops_narrowing() {
let mut cf_arr = [cf(50, 50, 0x0010_0000, 0, 10)];
let gylookup = gylookup_with_one_at(10, 100); let inputs = CfNarrowInputs {
gpz_at_entry: [0x0001_0000, i32::MAX],
gdz_old: [0x0002_0000, 0],
gi0: 0x0001_0000,
gi1: 0,
gylookup: &gylookup,
old_mip: 5, };
cf_narrow_simulate(&mut cf_arr, &inputs);
assert!(
cf_arr[0].i1 >= 50,
"i1 should not go below i0; got {}",
cf_arr[0].i1
);
}
#[test]
fn cumulative_narrowing_stops_when_cx_s16_zero() {
let mut cf_arr = [cf(0, 100, 0x0001_0000, 0, 10)];
let gylookup = gylookup_with_one_at(10, 1);
let inputs = CfNarrowInputs {
gpz_at_entry: [0x0001_0000, i32::MAX],
gdz_old: [0x0002_0000, 0],
gi0: 0x0000_4000,
gi1: 0,
gylookup: &gylookup,
old_mip: 3, };
cf_narrow_simulate(&mut cf_arr, &inputs);
assert_eq!(cf_arr[0].i1, 99);
assert_eq!(cf_arr[0].cx1, 0x0000_C000);
}
#[test]
fn drawcwall_narrows_when_test_negative() {
let mut cf_arr = [CfType {
i0: 0,
i1: 100,
z0: 5,
z1: 10,
cx0: 0x0001_0000,
cy0: 0,
cx1: -0x1000_0000, cy1: 0,
chz_layer: 0,
}];
let mut gylookup = vec![0i32; 16];
gylookup[5] = -1;
gylookup[10] = 0;
let inputs = CfNarrowInputs {
gpz_at_entry: [0x0001_0000, i32::MAX],
gdz_old: [0x0002_0000, 0],
gi0: 0x0001_0000,
gi1: 0,
gylookup: &gylookup,
old_mip: 1,
};
cf_narrow_simulate(&mut cf_arr, &inputs);
assert_eq!(cf_arr[0].i0, 1, "drawcwall-side i0 should have advanced");
assert_eq!(
cf_arr[0].cx0, 0x0002_0000,
"drawcwall-side cx0 should have advanced by +gi0"
);
}
#[test]
fn ogx_evolution_can_re_trigger_narrowing() {
let mut cf_arr = [cf(0, 100, 0x0001_0000, 0x0001_0000, 10)];
let gylookup = gylookup_with_one_at(10, -1);
let inputs = CfNarrowInputs {
gpz_at_entry: [0x0001_0000, i32::MAX],
gdz_old: [0x0002_0000, 0],
gi0: 0x0001_0000,
gi1: 0,
gylookup: &gylookup,
old_mip: 2, };
let before_i1 = cf_arr[0].i1;
let before_cx1 = cf_arr[0].cx1;
cf_narrow_simulate(&mut cf_arr, &inputs);
assert!(
cf_arr[0].i1 < before_i1,
"should have narrowed (i1: {} -> {})",
before_i1,
cf_arr[0].i1
);
assert!(cf_arr[0].cx1 < before_cx1, "cx1 should have decreased");
}
}