-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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 spinner cheese by accounting for spin directionality #25157
Changes from 4 commits
af7180a
9011170
80ac0c4
4f0d55e
92524d4
4e057b4
08b7c3d
15c1fcb
d08ba76
baf4130
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using NUnit.Framework; | ||
using osu.Game.Rulesets.Osu.Objects.Drawables; | ||
|
||
namespace osu.Game.Rulesets.Osu.Tests | ||
{ | ||
[TestFixture] | ||
public class SpinnerSpinHistoryTest | ||
{ | ||
private SpinnerSpinHistory history = null!; | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
history = new SpinnerSpinHistory(); | ||
} | ||
|
||
[TestCase(0, 0)] | ||
[TestCase(10, 10)] | ||
[TestCase(180, 180)] | ||
[TestCase(350, 350)] | ||
[TestCase(360, 360)] | ||
[TestCase(370, 370)] | ||
[TestCase(540, 540)] | ||
[TestCase(720, 720)] | ||
// --- | ||
[TestCase(-0, 0)] | ||
[TestCase(-10, 10)] | ||
[TestCase(-180, 180)] | ||
[TestCase(-350, 350)] | ||
[TestCase(-360, 360)] | ||
[TestCase(-370, 370)] | ||
[TestCase(-540, 540)] | ||
[TestCase(-720, 720)] | ||
public void TestSpinOneDirection(float spin, float expectedRotation) | ||
{ | ||
history.ReportDelta(500, spin); | ||
Assert.That(history.TotalRotation, Is.EqualTo(expectedRotation)); | ||
} | ||
|
||
[TestCase(0, 0, 0, 0)] | ||
// --- | ||
[TestCase(10, -10, 0, 10)] | ||
[TestCase(-10, 10, 0, 10)] | ||
// --- | ||
[TestCase(10, -20, 0, 10)] | ||
[TestCase(-10, 20, 0, 10)] | ||
// --- | ||
[TestCase(20, -10, 0, 20)] | ||
[TestCase(-20, 10, 0, 20)] | ||
// --- | ||
[TestCase(10, -360, 0, 350)] | ||
[TestCase(-10, 360, 0, 350)] | ||
// --- | ||
[TestCase(360, -10, 0, 370)] | ||
[TestCase(360, 10, 0, 370)] | ||
[TestCase(-360, 10, 0, 370)] | ||
[TestCase(-360, -10, 0, 370)] | ||
// --- | ||
[TestCase(10, 10, 10, 30)] | ||
[TestCase(10, 10, -10, 20)] | ||
[TestCase(10, -10, 10, 10)] | ||
[TestCase(-10, -10, -10, 30)] | ||
[TestCase(-10, -10, 10, 20)] | ||
[TestCase(-10, 10, 10, 10)] | ||
// --- | ||
[TestCase(10, -20, -350, 360)] | ||
[TestCase(10, -20, 350, 340)] | ||
[TestCase(-10, 20, 350, 360)] | ||
[TestCase(-10, 20, -350, 340)] | ||
public void TestSpinMultipleDirections(float spin1, float spin2, float spin3, float expectedRotation) | ||
{ | ||
history.ReportDelta(500, spin1); | ||
history.ReportDelta(1000, spin2); | ||
history.ReportDelta(1500, spin3); | ||
Assert.That(history.TotalRotation, Is.EqualTo(expectedRotation)); | ||
} | ||
|
||
// One spin | ||
[TestCase(370, -50, 320)] | ||
[TestCase(-370, 50, 320)] | ||
// Two spins | ||
[TestCase(740, -420, 320)] | ||
[TestCase(-740, 420, 320)] | ||
public void TestRemoveAndCrossFullSpin(float deltaToAdd, float deltaToRemove, float expectedRotation) | ||
{ | ||
history.ReportDelta(1000, deltaToAdd); | ||
history.ReportDelta(500, deltaToRemove); | ||
Assert.That(history.TotalRotation, Is.EqualTo(expectedRotation)); | ||
} | ||
|
||
// One spin + partial | ||
[TestCase(400, -30, -50, 320)] | ||
[TestCase(-400, 30, 50, 320)] | ||
// Two spins + partial | ||
[TestCase(800, -430, -50, 320)] | ||
[TestCase(-800, 430, 50, 320)] | ||
public void TestRemoveAndCrossFullAndPartialSpins(float deltaToAdd1, float deltaToAdd2, float deltaToRemove, float expectedRotation) | ||
{ | ||
history.ReportDelta(1000, deltaToAdd1); | ||
history.ReportDelta(1500, deltaToAdd2); | ||
history.ReportDelta(500, deltaToRemove); | ||
Assert.That(history.TotalRotation, Is.EqualTo(expectedRotation)); | ||
} | ||
|
||
[Test] | ||
public void TestRewindMultipleFullSpins() | ||
{ | ||
history.ReportDelta(500, 360); | ||
history.ReportDelta(1000, 720); | ||
|
||
Assert.That(history.TotalRotation, Is.EqualTo(1080)); | ||
|
||
history.ReportDelta(250, -900); | ||
|
||
Assert.That(history.TotalRotation, Is.EqualTo(180)); | ||
} | ||
|
||
[Test] | ||
public void TestRewindIntoSegmentThatHasNotCrossedZero() | ||
{ | ||
history.ReportDelta(1000, -180); | ||
Assert.That(history.TotalRotation, Is.EqualTo(180)); | ||
history.ReportDelta(1500, 90); | ||
Assert.That(history.TotalRotation, Is.EqualTo(180)); | ||
history.ReportDelta(2000, 450); | ||
Assert.That(history.TotalRotation, Is.EqualTo(360)); | ||
history.ReportDelta(1750, -45); | ||
Assert.That(history.TotalRotation, Is.EqualTo(315)); | ||
} | ||
|
||
[Test] | ||
public void TestRewindOverDirectionChange() | ||
{ | ||
history.ReportDelta(1000, 40); // max is now CW 40 degrees | ||
Assert.That(history.TotalRotation, Is.EqualTo(40)); | ||
history.ReportDelta(1100, -90); // max is now CCW 50 degrees | ||
Assert.That(history.TotalRotation, Is.EqualTo(50)); | ||
history.ReportDelta(1200, 110); // max is now CW 60 degrees | ||
Assert.That(history.TotalRotation, Is.EqualTo(60)); | ||
|
||
history.ReportDelta(1000, -20); | ||
Assert.That(history.TotalRotation, Is.EqualTo(40)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,7 +53,6 @@ public void Setup() => Schedule(() => | |
/// While off-centre, vibrates backwards and forwards on the x-axis, from centre-50 to centre+50, every 50ms. | ||
/// </summary> | ||
[Test] | ||
[Ignore("An upcoming implementation will fix this case")] | ||
public void TestVibrateWithoutSpinningOffCentre() | ||
{ | ||
List<ReplayFrame> frames = new List<ReplayFrame>(); | ||
|
@@ -81,7 +80,6 @@ public void TestVibrateWithoutSpinningOffCentre() | |
/// While centred on the slider, vibrates backwards and forwards on the x-axis, from centre-50 to centre+50, every 50ms. | ||
/// </summary> | ||
[Test] | ||
[Ignore("An upcoming implementation will fix this case")] | ||
public void TestVibrateWithoutSpinningOnCentre() | ||
{ | ||
List<ReplayFrame> frames = new List<ReplayFrame>(); | ||
|
@@ -130,7 +128,6 @@ public void TestSpinSingleDirection(float amount, int expectedTicks) | |
/// No ticks should be hit since the total rotation is -0.5 (0.5 CW + 1 CCW = 0.5 CCW). | ||
/// </summary> | ||
[Test] | ||
[Ignore("An upcoming implementation will fix this case")] | ||
public void TestSpinHalfBothDirections() | ||
{ | ||
performTest(new SpinFramesGenerator(time_spinner_start) | ||
|
@@ -149,7 +146,6 @@ public void TestSpinHalfBothDirections() | |
[TestCase(-180, 540, 1)] | ||
[TestCase(180, -900, 2)] | ||
[TestCase(-180, 900, 2)] | ||
[Ignore("An upcoming implementation will fix this case")] | ||
public void TestSpinOneDirectionThenChangeDirection(float direction1, float direction2, int expectedTicks) | ||
{ | ||
performTest(new SpinFramesGenerator(time_spinner_start) | ||
|
@@ -162,18 +158,24 @@ public void TestSpinOneDirectionThenChangeDirection(float direction1, float dire | |
} | ||
|
||
[Test] | ||
[Ignore("An upcoming implementation will fix this case")] | ||
public void TestRewind() | ||
{ | ||
AddStep("set manual clock", () => manualClock = new ManualClock { Rate = 1 }); | ||
|
||
List<ReplayFrame> frames = new SpinFramesGenerator(time_spinner_start) | ||
.Spin(360, 500) // 2000ms -> 1 full CW spin | ||
.Spin(-180, 500) // 2500ms -> 0.5 CCW spins | ||
.Spin(90, 500) // 3000ms -> 0.25 CW spins | ||
.Spin(450, 500) // 3500ms -> 1 full CW spin | ||
.Spin(180, 500) // 4000ms -> 0.5 CW spins | ||
.Build(); | ||
List<ReplayFrame> frames = | ||
new SpinFramesGenerator(time_spinner_start) | ||
// 1500ms start | ||
.Spin(360, 500) | ||
// 2000ms -> 1 full CW spin | ||
.Spin(-180, 500) | ||
// 2500ms -> 1 full CW spin + 0.5 CCW spins | ||
.Spin(90, 500) | ||
// 3000ms -> 1 full CW spin + 0.25 CCW spins | ||
.Spin(450, 500) | ||
// 3500ms -> 2 full CW spins | ||
.Spin(180, 500) | ||
// 4000ms -> 2 full CW spins + 0.5 CW spins | ||
.Build(); | ||
|
||
loadPlayer(frames); | ||
|
||
|
@@ -194,11 +196,11 @@ public void TestRewind() | |
assertTotalRotation(3750, 810); | ||
assertTotalRotation(3500, 720); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed, it may be good to have these assert There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have added this, fixed the clock interpolation and removed the test lenience as it is now precise \o/. |
||
assertTotalRotation(3250, 530); | ||
smoogipoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assertTotalRotation(3000, 540); | ||
assertTotalRotation(3000, 450); | ||
smoogipoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assertTotalRotation(2750, 540); | ||
smoogipoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assertTotalRotation(2500, 540); | ||
assertTotalRotation(2250, 360); | ||
assertTotalRotation(2000, 180); | ||
assertTotalRotation(2250, 450); | ||
assertTotalRotation(2000, 360); | ||
assertTotalRotation(1500, 0); | ||
|
||
void assertTotalRotation(double time, float expected) | ||
|
@@ -211,7 +213,8 @@ void assertTotalRotation(double time, float expected) | |
void addSeekStep(double time) | ||
{ | ||
AddStep($"seek to {time}", () => clock.Seek(time)); | ||
AddUntilStep("wait for seek to finish", () => drawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time)); | ||
// Lenience is required due to interpolation running slightly ahead on a stalled clock. | ||
AddUntilStep("wait for seek to finish", () => drawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(time).Within(40)); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original test was correct - this should be 180 because we've rewound somewhere in the segment
[1000, 2000)
. We haven't done (and aren't in the process of doing) the 450 rotation yet - that happens in one instant att = 2000
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change is intentional and correct to my eyes, but explaining this may require a voice call if it doesn't immediately make sense as to why. The important frame consideration in the PR description comes into play here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in voice, the primary issue here is that it should be impossible for the (1750, -45) report to exist as it's passing through multiple 180-degree turns, even without important frames. I still feel like this test is illogical in its current existence, and either needs to be updated (as I did - passing through 0 from a point very close to 0) or removed.