diff --git a/circleguard/investigator.py b/circleguard/investigator.py index 562ee5be..0ce5ef87 100644 --- a/circleguard/investigator.py +++ b/circleguard/investigator.py @@ -3,6 +3,7 @@ import numpy as np from circleguard.enums import Key, Detect +from circleguard.mod import Mod from circleguard.result import RelaxResult, CorrectionResult, TimewarpResult class Investigator: @@ -48,7 +49,8 @@ def investigate(self): yield CorrectionResult(replay, snaps) if self.detect & Detect.TIMEWARP: frametimes = self.frametimes(replay) - frametime = self.median_frametime(frametimes) + frametimes = self.clean_frametimes(frametimes) + frametime = self.average_frametime(frametimes) yield TimewarpResult(replay, frametime, frametimes) @staticmethod @@ -210,14 +212,36 @@ def frametimes(replay): return np.diff(replay.t) @staticmethod - def median_frametime(frametimes): + def clean_frametimes(frametimes): """ - Calculates the median time between the frames in ``frametimes``. + Cleans the frametimes to remove some artificial frametimes, eg 1-2 + frametime frames added by relax. Parameters ---------- frametimes: list[int] - The frametimes to find the median of. + The frametimes to clean. + """ + unique, count = np.unique(frametimes, return_counts=True) + unique = unique[count > np.mean(count)] + # remove low frametimes only if there is a peak in short frames + if unique[0] < unique[-1]/2: + cutoff = unique[0] + 2 + return Investigator._remove_low_frametimes(frametimes, cutoff) + elif unique[-1] <= 4: + return Investigator._remove_low_frametimes(frametimes, 4) + return frametimes + + + @staticmethod + def average_frametime(frametimes): + """ + Calculates the average time between the frames in ``frametimes``. + + Parameters + ---------- + frametimes: list[int] + The frametimes to find the average of. Notes ----- @@ -264,6 +288,43 @@ def _filter_hits(hitobjs, keypress_times, OD): return array + @staticmethod + def _remove_low_frametimes(frametimes, limit): + """ + Removes any long run of consecutive frametimes which are less than + ``limit``. + + Parameters + ---------- + frametimes: list[int] + The frametimes to remove consecutive low frametimes from. + limit: int + Consider any frametime less than or equal to this to be a "low" + frametime, and subject to removal. + + Returns + ------- + list[int] + The passed ``frametimes`` with long runs of low frametimes removed. + + Notes + ----- + These low frametimes are caused by playing with relax, or switching + desktops during a break. It is unlikely that consecutive short frames + would appear in an otherwise normal replay. + """ + run_length = 0 + low_frametime_indices = [] + for i, frametime in enumerate(frametimes): + if frametime <= limit: + run_length += 1 + # don't cut off the run prematurely + continue + if run_length >= 7: + low_frametime_indices.extend(range(i - run_length, i)) + run_length = 0 + return np.delete(frametimes, low_frametime_indices) + # TODO (some) code duplication with this method and a similar one in # ``Comparer``. Refactor Investigator and Comparer to inherit from a base # class, or move this method to utils. Preferrably the former. diff --git a/tests/resources/legit/legit_100fps_30s_map_no_keypresses_nm.osr b/tests/resources/legit/legit_100fps_30s_map_no_keypresses_nm.osr new file mode 100644 index 00000000..94dc2556 Binary files /dev/null and b/tests/resources/legit/legit_100fps_30s_map_no_keypresses_nm.osr differ diff --git a/tests/resources/legit/legit_100fps_30s_map_with_keypresses_nm.osr b/tests/resources/legit/legit_100fps_30s_map_with_keypresses_nm.osr new file mode 100644 index 00000000..7b741d70 Binary files /dev/null and b/tests/resources/legit/legit_100fps_30s_map_with_keypresses_nm.osr differ diff --git a/tests/resources/legit/legit_100fps_98s_map_no_keypresses_nm-1.osr b/tests/resources/legit/legit_100fps_98s_map_no_keypresses_nm-1.osr new file mode 100644 index 00000000..18f64f9f Binary files /dev/null and b/tests/resources/legit/legit_100fps_98s_map_no_keypresses_nm-1.osr differ diff --git a/tests/resources/legit/legit_100fps_98s_map_no_keypresses_nm-2.osr b/tests/resources/legit/legit_100fps_98s_map_no_keypresses_nm-2.osr new file mode 100644 index 00000000..81c23b82 Binary files /dev/null and b/tests/resources/legit/legit_100fps_98s_map_no_keypresses_nm-2.osr differ diff --git a/tests/resources/legit/legit_120fps_dt-1.osr b/tests/resources/legit/legit_120fps_dt-1.osr new file mode 100644 index 00000000..5f5bfefc Binary files /dev/null and b/tests/resources/legit/legit_120fps_dt-1.osr differ diff --git a/tests/resources/legit/legit_120fps_dt-2.osr b/tests/resources/legit/legit_120fps_dt-2.osr new file mode 100644 index 00000000..c6a1ae8b Binary files /dev/null and b/tests/resources/legit/legit_120fps_dt-2.osr differ diff --git a/tests/resources/legit/legit_120fps_nm.osr b/tests/resources/legit/legit_120fps_nm.osr new file mode 100644 index 00000000..303d74b5 Binary files /dev/null and b/tests/resources/legit/legit_120fps_nm.osr differ diff --git a/tests/resources/legit/legit_144fps-nm.osr b/tests/resources/legit/legit_144fps-nm.osr new file mode 100644 index 00000000..979311bd Binary files /dev/null and b/tests/resources/legit/legit_144fps-nm.osr differ diff --git a/tests/resources/legit/legit_144fps_dt.osr b/tests/resources/legit/legit_144fps_dt.osr new file mode 100644 index 00000000..e0910001 Binary files /dev/null and b/tests/resources/legit/legit_144fps_dt.osr differ diff --git a/tests/resources/legit/legit_144fps_nmrx.osr b/tests/resources/legit/legit_144fps_nmrx.osr new file mode 100644 index 00000000..1d6fa9ae Binary files /dev/null and b/tests/resources/legit/legit_144fps_nmrx.osr differ diff --git a/tests/resources/legit/legit_1peak_nm.osr b/tests/resources/legit/legit_1peak_nm.osr new file mode 100644 index 00000000..0c3169f6 Binary files /dev/null and b/tests/resources/legit/legit_1peak_nm.osr differ diff --git a/tests/resources/legit/legit_1peak_short_frames_from_break_nm.osr b/tests/resources/legit/legit_1peak_short_frames_from_break_nm.osr new file mode 100644 index 00000000..b70bda88 Binary files /dev/null and b/tests/resources/legit/legit_1peak_short_frames_from_break_nm.osr differ diff --git a/tests/resources/legit/legit_288fps_dt.osr b/tests/resources/legit/legit_288fps_dt.osr new file mode 100644 index 00000000..8f103241 Binary files /dev/null and b/tests/resources/legit/legit_288fps_dt.osr differ diff --git a/tests/resources/legit/legit_2peaks_dtrx.osr b/tests/resources/legit/legit_2peaks_dtrx.osr new file mode 100644 index 00000000..18e42cb4 Binary files /dev/null and b/tests/resources/legit/legit_2peaks_dtrx.osr differ diff --git a/tests/resources/legit/legit_2peaks_nm-1.osr b/tests/resources/legit/legit_2peaks_nm-1.osr new file mode 100644 index 00000000..93e51825 Binary files /dev/null and b/tests/resources/legit/legit_2peaks_nm-1.osr differ diff --git a/tests/resources/legit/legit_2peaks_nm-2.osr b/tests/resources/legit/legit_2peaks_nm-2.osr new file mode 100644 index 00000000..785d8af4 Binary files /dev/null and b/tests/resources/legit/legit_2peaks_nm-2.osr differ diff --git a/tests/resources/legit/legit_2peaks_short_frames_from_break_nm.osr b/tests/resources/legit/legit_2peaks_short_frames_from_break_nm.osr new file mode 100644 index 00000000..b5dfff19 Binary files /dev/null and b/tests/resources/legit/legit_2peaks_short_frames_from_break_nm.osr differ diff --git a/tests/resources/legit/legit_3peaks_dt-1.osr b/tests/resources/legit/legit_3peaks_dt-1.osr new file mode 100644 index 00000000..aa16e6e7 Binary files /dev/null and b/tests/resources/legit/legit_3peaks_dt-1.osr differ diff --git a/tests/resources/legit/legit_3peaks_dt-2.osr b/tests/resources/legit/legit_3peaks_dt-2.osr new file mode 100644 index 00000000..f15e7116 Binary files /dev/null and b/tests/resources/legit/legit_3peaks_dt-2.osr differ diff --git a/tests/resources/legit/legit_3peaks_dtrx.osr b/tests/resources/legit/legit_3peaks_dtrx.osr new file mode 100644 index 00000000..d5f1b03f Binary files /dev/null and b/tests/resources/legit/legit_3peaks_dtrx.osr differ diff --git a/tests/resources/legit/legit_3peaks_nm-1.osr b/tests/resources/legit/legit_3peaks_nm-1.osr new file mode 100644 index 00000000..973219ff Binary files /dev/null and b/tests/resources/legit/legit_3peaks_nm-1.osr differ diff --git a/tests/resources/legit/legit_lots_of_lag_frames_dt.osr b/tests/resources/legit/legit_lots_of_lag_frames_dt.osr new file mode 100644 index 00000000..a0abd729 Binary files /dev/null and b/tests/resources/legit/legit_lots_of_lag_frames_dt.osr differ diff --git a/tests/resources/legit/legit_lots_of_lag_frames_nm-1.osr b/tests/resources/legit/legit_lots_of_lag_frames_nm-1.osr new file mode 100644 index 00000000..5ddd91ec Binary files /dev/null and b/tests/resources/legit/legit_lots_of_lag_frames_nm-1.osr differ diff --git a/tests/resources/legit/legit_lots_of_lag_frames_nm-2.osr b/tests/resources/legit/legit_lots_of_lag_frames_nm-2.osr new file mode 100644 index 00000000..c1558ebc Binary files /dev/null and b/tests/resources/legit/legit_lots_of_lag_frames_nm-2.osr differ diff --git a/tests/resources/legit/legit_lots_of_short_frames_really_messy_nm.osr b/tests/resources/legit/legit_lots_of_short_frames_really_messy_nm.osr new file mode 100644 index 00000000..7bf55b33 Binary files /dev/null and b/tests/resources/legit/legit_lots_of_short_frames_really_messy_nm.osr differ diff --git a/tests/resources/legit/legit_old_replay_dt.osr b/tests/resources/legit/legit_old_replay_dt.osr new file mode 100644 index 00000000..74465162 Binary files /dev/null and b/tests/resources/legit/legit_old_replay_dt.osr differ diff --git a/tests/resources/legit/legit_old_replay_ht.osr b/tests/resources/legit/legit_old_replay_ht.osr new file mode 100644 index 00000000..71bc5b63 Binary files /dev/null and b/tests/resources/legit/legit_old_replay_ht.osr differ diff --git a/tests/resources/legit/legit_triangle_shape_really_messy_dt-1.osr b/tests/resources/legit/legit_triangle_shape_really_messy_dt-1.osr new file mode 100644 index 00000000..6898e33e Binary files /dev/null and b/tests/resources/legit/legit_triangle_shape_really_messy_dt-1.osr differ diff --git a/tests/resources/legit/legit_triangle_shape_really_messy_dt-2.osr b/tests/resources/legit/legit_triangle_shape_really_messy_dt-2.osr new file mode 100644 index 00000000..6f8bd6c9 Binary files /dev/null and b/tests/resources/legit/legit_triangle_shape_really_messy_dt-2.osr differ diff --git a/tests/resources/legit/legit_unlimited_fps_dt-1.osr b/tests/resources/legit/legit_unlimited_fps_dt-1.osr new file mode 100644 index 00000000..f9d559eb Binary files /dev/null and b/tests/resources/legit/legit_unlimited_fps_dt-1.osr differ diff --git a/tests/resources/legit/legit_unlimited_fps_dt-2.osr b/tests/resources/legit/legit_unlimited_fps_dt-2.osr new file mode 100644 index 00000000..1e42a03c Binary files /dev/null and b/tests/resources/legit/legit_unlimited_fps_dt-2.osr differ diff --git a/tests/resources/legit/legit_unlimited_fps_nm-1.osr b/tests/resources/legit/legit_unlimited_fps_nm-1.osr new file mode 100644 index 00000000..7de123fb Binary files /dev/null and b/tests/resources/legit/legit_unlimited_fps_nm-1.osr differ diff --git a/tests/resources/legit/legit_unlimited_fps_nm-2.osr b/tests/resources/legit/legit_unlimited_fps_nm-2.osr new file mode 100644 index 00000000..0024a818 Binary files /dev/null and b/tests/resources/legit/legit_unlimited_fps_nm-2.osr differ diff --git a/tests/resources/legit/legit_unlimited_fps_nm-3.osr b/tests/resources/legit/legit_unlimited_fps_nm-3.osr new file mode 100644 index 00000000..01f26aed Binary files /dev/null and b/tests/resources/legit/legit_unlimited_fps_nm-3.osr differ diff --git a/tests/resources/legit/legit_wide_peak_dt.osr b/tests/resources/legit/legit_wide_peak_dt.osr new file mode 100644 index 00000000..31fd74e9 Binary files /dev/null and b/tests/resources/legit/legit_wide_peak_dt.osr differ diff --git a/tests/resources/legit/legit_wide_peak_long_map_nm.osr b/tests/resources/legit/legit_wide_peak_long_map_nm.osr new file mode 100644 index 00000000..01b4270f Binary files /dev/null and b/tests/resources/legit/legit_wide_peak_long_map_nm.osr differ diff --git a/tests/resources/relax/relaxed-1.osr b/tests/resources/relax/relaxed-1.osr new file mode 100644 index 00000000..051e2e82 Binary files /dev/null and b/tests/resources/relax/relaxed-1.osr differ diff --git a/tests/resources/relax/relaxed-2.osr b/tests/resources/relax/relaxed-2.osr new file mode 100644 index 00000000..ac7f9bef Binary files /dev/null and b/tests/resources/relax/relaxed-2.osr differ diff --git a/tests/resources/timewarped/timewarped_144fps_90%_dt.osr b/tests/resources/timewarped/timewarped_144fps_90%_dt.osr new file mode 100644 index 00000000..221d1518 Binary files /dev/null and b/tests/resources/timewarped/timewarped_144fps_90%_dt.osr differ diff --git a/tests/resources/timewarped/timewarped_144fps_90%_nm.osr b/tests/resources/timewarped/timewarped_144fps_90%_nm.osr new file mode 100644 index 00000000..aae87768 Binary files /dev/null and b/tests/resources/timewarped/timewarped_144fps_90%_nm.osr differ diff --git a/tests/resources/timewarped/timewarped_144fps_90%_nmrx.osr b/tests/resources/timewarped/timewarped_144fps_90%_nmrx.osr new file mode 100644 index 00000000..9bbd4c7f Binary files /dev/null and b/tests/resources/timewarped/timewarped_144fps_90%_nmrx.osr differ diff --git a/tests/resources/timewarped/timewarped_144fps_95%_dt.osr b/tests/resources/timewarped/timewarped_144fps_95%_dt.osr new file mode 100644 index 00000000..d69a5787 Binary files /dev/null and b/tests/resources/timewarped/timewarped_144fps_95%_dt.osr differ diff --git a/tests/resources/timewarped/timewarped_144fps_95%_nm.osr b/tests/resources/timewarped/timewarped_144fps_95%_nm.osr new file mode 100644 index 00000000..133750a6 Binary files /dev/null and b/tests/resources/timewarped/timewarped_144fps_95%_nm.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_90%_dt-1.osr b/tests/resources/timewarped/timewarped_unlimitedfps_90%_dt-1.osr new file mode 100644 index 00000000..d5a1b9a2 Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_90%_dt-1.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_90%_dt-2.osr b/tests/resources/timewarped/timewarped_unlimitedfps_90%_dt-2.osr new file mode 100644 index 00000000..7921e7d6 Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_90%_dt-2.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_90%_nm-1.osr b/tests/resources/timewarped/timewarped_unlimitedfps_90%_nm-1.osr new file mode 100644 index 00000000..55d11bb0 Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_90%_nm-1.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_90%_nm-2.osr b/tests/resources/timewarped/timewarped_unlimitedfps_90%_nm-2.osr new file mode 100644 index 00000000..899da1d7 Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_90%_nm-2.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_90%_nm-3.osr b/tests/resources/timewarped/timewarped_unlimitedfps_90%_nm-3.osr new file mode 100644 index 00000000..353795c9 Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_90%_nm-3.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_95%_dt-1.osr b/tests/resources/timewarped/timewarped_unlimitedfps_95%_dt-1.osr new file mode 100644 index 00000000..72e576e1 Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_95%_dt-1.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_95%_dt-2.osr b/tests/resources/timewarped/timewarped_unlimitedfps_95%_dt-2.osr new file mode 100644 index 00000000..60770229 Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_95%_dt-2.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_95%_nm-1.osr b/tests/resources/timewarped/timewarped_unlimitedfps_95%_nm-1.osr new file mode 100644 index 00000000..21df136a Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_95%_nm-1.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_95%_nm-2.osr b/tests/resources/timewarped/timewarped_unlimitedfps_95%_nm-2.osr new file mode 100644 index 00000000..696a07ea Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_95%_nm-2.osr differ diff --git a/tests/resources/timewarped/timewarped_unlimitedfps_95%_nm-3.osr b/tests/resources/timewarped/timewarped_unlimitedfps_95%_nm-3.osr new file mode 100644 index 00000000..610b0297 Binary files /dev/null and b/tests/resources/timewarped/timewarped_unlimitedfps_95%_nm-3.osr differ diff --git a/tests/test_investigations.py b/tests/test_investigations.py index 50072c81..1fb725d1 100644 --- a/tests/test_investigations.py +++ b/tests/test_investigations.py @@ -1,8 +1,10 @@ -import numpy as np - from circleguard import ReplayPath, Mod, Detect, StealResultSim, StealResultCorr +from circleguard.investigator import Investigator from tests.utils import CGTestCase, DELTA, RES +import numpy as np + + class TestCorrection(CGTestCase): @classmethod def setUpClass(cls): @@ -154,9 +156,28 @@ def setUpClass(cls): cls.timewarped3 = ReplayPath(RES / "timewarped" / "timewarped-3.osr") cls.timewarped4 = ReplayPath(RES / "timewarped" / "timewarped-4.osr") + cls.relax_legit = ReplayPath(RES / "legit" / "legit_144fps_nmrx.osr") + cls.legit = ReplayPath(RES / "legit_replay1.osr") def test_cheated(self): replays = [self.timewarped1, self.timewarped2, self.timewarped3, self.timewarped4] - for r in self.cg.timewarp_check(replays): + frametimes = [11.33333, 10.66666, 8, 8.66666] + for i, r in enumerate(self.cg.timewarp_check(replays)): self.assertLess(r.frametime, Detect.FRAMETIME_LIMIT, "Timewarped replays were not detected as cheated") + self.assertAlmostEqual(r.frametime, frametimes[i], delta=DELTA, msg="Frametime was not correct") + + def test_frametime_cleaning(self): + frametimes = np.array([16, 14, 1, 17, 14, 1, 0, 16, 2, 1, 2, 3, 1, 2, 3, 1, 0, 0, + 14, 2, 1, 2, 3, 1, 2, 1, 1, 17, 19, 20, 16, 1, 25]) + expected_frametimes = [16, 14, 1, 17, 14, 1, 0, 16, 14, 17, 19, 20, 16, 1, 25] + clean_frametimes = Investigator.clean_frametimes(frametimes) + + self.assertListEqual(list(clean_frametimes), expected_frametimes, "Frametimes were not cleaned as expected") + + # make sure we're not detecting (legitimate) +RX replays as timewarped when + # they just have a ton of low frametime beacuse that's how the client works + # with the relax mod + def test_relax_legit(self): + frametime = list(self.cg.timewarp_check(self.relax_legit))[0].frametime + self.assertAlmostEqual(frametime, 7, delta=DELTA)