private IEnumerable <Issue> GetRecoveryIssues(Beatmap beatmap, Spinner spinner) { HitObject nextObject = spinner.Next(); // Do not check time between two spinners since all you'd need to do is keep spinning. if (nextObject == null || nextObject is Spinner) { yield break; } double recoveryTime = nextObject.time - spinner.endTime; var line = beatmap.GetTimingLine <UninheritedLine>(nextObject.time); double bpmScaling = GetScaledTiming(line.bpm); double recoveryTimeScaled = recoveryTime / bpmScaling; double[] recoveryTimeExpected = { 1000, 500, 250 }; // 4, 2 and 1 beats respectively, 240 bpm // Tries both scaled and regular recoveries, and only if both are exceeded does it create an issue. for (int diffIndex = 0; diffIndex < recoveryTimeExpected.Length; ++diffIndex) { // Picks whichever is greatest of the scaled and regular versions. double expectedScaledMultiplier = bpmScaling < 1 ? bpmScaling : 1; double expectedRecovery = Math.Ceiling(recoveryTimeExpected[diffIndex] * expectedScaledMultiplier * expectedMultiplier); double problemThreshold = recoveryTimeExpected[diffIndex]; double warningThreshold = recoveryTimeExpected[diffIndex] * 1.2; if (recoveryTimeScaled < problemThreshold && recoveryTime < problemThreshold) { yield return(new Issue(GetTemplate("Problem Recovery"), beatmap, Timestamp.Get(spinner, nextObject), recoveryTime, expectedRecovery) .ForDifficulties((Beatmap.Difficulty)diffIndex)); } else if (recoveryTimeScaled < warningThreshold && recoveryTime < warningThreshold) { yield return(new Issue(GetTemplate("Warning Recovery"), beatmap, Timestamp.Get(spinner, nextObject), recoveryTime, expectedRecovery) .ForDifficulties((Beatmap.Difficulty)diffIndex)); } } }