Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions libenforcer-wasm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ pub fn is_equal_coord(one: &Coord, other: &Coord) -> bool {
pub fn is_box_controller(coordinates: &[Coord]) -> bool {
const RIM_COORD_MAX: usize = 432;
const THREE_MINUTES: usize = 10800; // frames
const MIN_ANALOG_SMALL_OFF_AXIS_RATIO: f64 = 0.02;
const MIN_ANALOG_SMALL_OFF_AXIS_UNIQUE: usize = 32;

let (small_off_axis_count, small_off_axis_unique_count) =
count_small_off_axis_coords(coordinates);
let small_off_axis_ratio = if coordinates.is_empty() {
0.0
} else {
small_off_axis_count as f64 / coordinates.len() as f64
};

if small_off_axis_ratio >= MIN_ANALOG_SMALL_OFF_AXIS_RATIO
&& small_off_axis_unique_count >= MIN_ANALOG_SMALL_OFF_AXIS_UNIQUE
{
return false;
}

let rim_count = count_rim_coords(coordinates);
let mut rim_proportion = rim_count as f64 / RIM_COORD_MAX as f64;
Expand All @@ -32,6 +48,26 @@ pub fn is_box_controller(coordinates: &[Coord]) -> bool {
rim_proportion < 0.50
}

/// Count coordinates with natural analog off-axis evidence.
/// These are coordinates where both axes are active, but one axis is a small
/// nonzero value. Box controllers should almost never produce many of these.
fn count_small_off_axis_coords(coords: &[Coord]) -> (usize, usize) {
const SMALL_OFF_AXIS_THRESHOLD: f64 = 0.08;

let mut count = 0;
let mut unique_coords = HashSet::new();

for coord in coords {
let min_abs_axis = coord.x.abs().min(coord.y.abs());
if min_abs_axis > 0.0 && min_abs_axis < SMALL_OFF_AXIS_THRESHOLD {
count += 1;
unique_coords.insert((coord.x.to_bits(), coord.y.to_bits()));
}
}

(count, unique_coords.len())
}

/// Count unique coordinates on the rim of the joystick
/// A coordinate is on the rim if its distance from center is >= 1.0
fn count_rim_coords(coords: &[Coord]) -> usize {
Expand Down
74 changes: 73 additions & 1 deletion libenforcer-wasm/tests/utils_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
mod common;

use common::*;
use libenforcer_wasm::{parser, types, types::Coord, utils};
use libenforcer_wasm::{checks, parser, types::ControllerType, types::Coord, utils};
use peppi::game::Game;
use peppi::io::slippi::de::read as read_slippi;
use std::io::Cursor;
Expand Down Expand Up @@ -211,6 +211,78 @@ fn test_is_box_inputs_orca_dataset_e() {
);
}

#[cfg(not(target_arch = "wasm32"))]
#[test]
fn test_is_box_inputs_issue15_orca_replays() {
let cases = [
(
"legal/analog/orca/issue15_battlefield.slp",
"issue #15 Battlefield",
),
(
"legal/analog/orca/issue15_dream_land.slp",
"issue #15 Dream Land",
),
];

for (path, label) in cases {
let data = read_slp_file(path);
let game = read_slippi(&mut Cursor::new(&data), None).unwrap();
let player_data = parser::extract_player_data(&game, 0).unwrap();

assert_eq!(
utils::is_box_controller(&player_data.main_coords),
false,
"{} Orca P1 should not be detected as box",
label
);
}
}

#[cfg(not(target_arch = "wasm32"))]
#[test]
fn test_analyze_player_issue15_orca_replays_as_analog() {
let cases = [
(
"legal/analog/orca/issue15_battlefield.slp",
"issue #15 Battlefield",
),
(
"legal/analog/orca/issue15_dream_land.slp",
"issue #15 Dream Land",
),
];

for (path, label) in cases {
let data = read_slp_file(path);
let game = read_slippi(&mut Cursor::new(&data), None).unwrap();
let player_data = parser::extract_player_data(&game, 0).unwrap();
let analysis = checks::analyze_player(&player_data);

assert_eq!(
analysis.controller_type,
ControllerType::Analog,
"{} Orca P1 should be analyzed as analog",
label
);
assert!(
analysis.is_legal,
"{} Orca P1 should pass aggregate legality",
label
);
assert!(
analysis.sdi.is_none(),
"{} Orca P1 should skip box-only SDI checks",
label
);
assert!(
analysis.input_fuzzing.is_none(),
"{} Orca P1 should skip box-only input fuzzing checks",
label
);
}
}

#[cfg(not(target_arch = "wasm32"))]
#[test]
fn test_is_box_inputs_xbox_controller_a() {
Expand Down
Binary file not shown.
Binary file not shown.
Loading