Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix frametimes for relax plays #153

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
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
69 changes: 65 additions & 4 deletions circleguard/investigator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
-----
Expand Down Expand Up @@ -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.
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added tests/resources/legit/legit_120fps_dt-1.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_120fps_dt-2.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_120fps_nm.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_144fps-nm.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_144fps_dt.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_144fps_nmrx.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_1peak_nm.osr
Binary file not shown.
Binary file not shown.
Binary file added tests/resources/legit/legit_288fps_dt.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_2peaks_dtrx.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_2peaks_nm-1.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_2peaks_nm-2.osr
Binary file not shown.
Binary file not shown.
Binary file added tests/resources/legit/legit_3peaks_dt-1.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_3peaks_dt-2.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_3peaks_dtrx.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_3peaks_nm-1.osr
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added tests/resources/legit/legit_old_replay_dt.osr
Binary file not shown.
Binary file added tests/resources/legit/legit_old_replay_ht.osr
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added tests/resources/legit/legit_wide_peak_dt.osr
Binary file not shown.
Binary file not shown.
Binary file added tests/resources/relax/relaxed-1.osr
Binary file not shown.
Binary file added tests/resources/relax/relaxed-2.osr
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
27 changes: 24 additions & 3 deletions tests/test_investigations.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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)