From 4366013e498236297e303583a60bb3a07d6cc3e4 Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Fri, 27 Mar 2026 19:09:41 -0700
Subject: [PATCH 01/13] lobotomize rhythm
---
.../Difficulty/Evaluators/Speed/RhythmEvaluator.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
index e2ee41162b41..ee2254463789 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
@@ -86,7 +86,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current)
// for example a slider-circle-circle pattern should be evaluated as a regular triple and not as a single->double
if (prevObj.BaseObject is Slider)
{
- double sliderLazyEndDelta = currObj.MinimumJumpTime;
+ double sliderLazyEndDelta = currObj.AdjustedDeltaTime - prevObj.LazyTravelTime;
double sliderLazyDeltaDifference = Math.Max(sliderLazyEndDelta, currDelta) / Math.Min(sliderLazyEndDelta, currDelta);
double sliderRealEndDelta = currObj.LastObjectEndDeltaTime;
From 0d08e219667504e061aad9b6183a7a499cbf2050 Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 15:49:31 -0700
Subject: [PATCH 02/13] Use correct time
---
.../Evaluators/Speed/RhythmEvaluator.cs | 2 +-
.../Preprocessing/OsuDifficultyHitObject.cs | 55 ++-----------------
2 files changed, 7 insertions(+), 50 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
index ee2254463789..e10ce15b3720 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
@@ -86,7 +86,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current)
// for example a slider-circle-circle pattern should be evaluated as a regular triple and not as a single->double
if (prevObj.BaseObject is Slider)
{
- double sliderLazyEndDelta = currObj.AdjustedDeltaTime - prevObj.LazyTravelTime;
+ double sliderLazyEndDelta = currObj.TailTime;
double sliderLazyDeltaDifference = Math.Max(sliderLazyEndDelta, currDelta) / Math.Min(sliderLazyEndDelta, currDelta);
double sliderRealEndDelta = currObj.LastObjectEndDeltaTime;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index 221b2c637852..ab5dc7f80e0f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -60,27 +60,10 @@ public class OsuDifficultyHitObject : DifficultyHitObject
public double LazyJumpDistance { get; private set; }
///
- /// Normalised shortest distance to consider for a jump between the previous and this .
+ /// Amount of time elapsed between the end of and the start of ,
+ /// adjusted by clockrate and capped to a minimum of ms.
///
- ///
- /// This is bounded from above by , and is smaller than the former if a more natural path is able to be taken through the previous .
- ///
- ///
- /// Suppose a linear slider - circle pattern.
- ///
- /// Following the slider lazily (see: ) will result in underestimating the true end position of the slider as being closer towards the start position.
- /// As a result, overestimates the jump distance because the player is able to take a more natural path by following through the slider to its end,
- /// such that the jump is felt as only starting from the slider's true end position.
- ///
- /// Now consider a slider - circle pattern where the circle is stacked along the path inside the slider.
- /// In this case, the lazy end position correctly estimates the true end position of the slider and provides the more natural movement path.
- ///
- public double MinimumJumpDistance { get; private set; }
-
- ///
- /// The time taken to travel through , with a minimum value of 25ms.
- ///
- public double MinimumJumpTime { get; private set; }
+ public double TailTime;
///
/// Normalised distance between the start and end position of this .
@@ -209,7 +192,7 @@ private void setDistances(double clockRate)
TravelTime = Math.Max(LazyTravelTime / clockRate, MIN_DELTA_TIME);
}
- MinimumJumpTime = AdjustedDeltaTime;
+ TailTime = AdjustedDeltaTime;
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
if (BaseObject is Spinner || LastObject is Spinner)
@@ -222,37 +205,11 @@ private void setDistances(double clockRate)
JumpDistance = (LastObject.StackedPosition - BaseObject.StackedPosition).Length * scalingFactor;
LazyJumpDistance = (BaseObject.StackedPosition - lastCursorPosition).Length * scalingFactor;
- MinimumJumpDistance = LazyJumpDistance;
- if (LastObject is Slider lastSlider && lastDifficultyObject != null)
+ if (LastObject is Slider && lastDifficultyObject != null)
{
double lastTravelTime = Math.Max(lastDifficultyObject.LazyTravelTime / clockRate, MIN_DELTA_TIME);
- MinimumJumpTime = Math.Max(AdjustedDeltaTime - lastTravelTime, MIN_DELTA_TIME);
-
- //
- // There are two types of slider-to-object patterns to consider in order to better approximate the real movement a player will take to jump between the hitobjects.
- //
- // 1. The anti-flow pattern, where players cut the slider short in order to move to the next hitobject.
- //
- // <======o==> ← slider
- // | ← most natural jump path
- // o ← a follow-up hitcircle
- //
- // In this case the most natural jump path is approximated by LazyJumpDistance.
- //
- // 2. The flow pattern, where players follow through the slider to its visual extent into the next hitobject.
- //
- // <======o==>---o
- // ↑
- // most natural jump path
- //
- // In this case the most natural jump path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject.
- //
- // Thus, the player is assumed to jump the minimum of these two distances in all cases.
- //
-
- float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor;
- MinimumJumpDistance = Math.Max(0, Math.Min(LazyJumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius));
+ TailTime = Math.Max(AdjustedDeltaTime - lastTravelTime, MIN_DELTA_TIME);
}
if (lastLastDifficultyObject != null && lastLastDifficultyObject.BaseObject is not Spinner)
From 397e41bcc6bdffe3a0d8c7bf340cc17d2754ee21 Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 15:59:49 -0700
Subject: [PATCH 03/13] Rename LazyJumpDistance and JumpDistance to better
represent roles
---
.../Difficulty/Evaluators/Aim/AgilityEvaluator.cs | 2 +-
.../Difficulty/Evaluators/Aim/FlowAimEvaluator.cs | 6 +++---
.../Difficulty/Evaluators/Aim/SnapAimEvaluator.cs | 10 +++++-----
.../Difficulty/Evaluators/FlashlightEvaluator.cs | 2 +-
.../Difficulty/Evaluators/ReadingEvaluator.cs | 10 +++++-----
.../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 8 ++++----
6 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/AgilityEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/AgilityEvaluator.cs
index 98a5c7680e15..bbe3a7e6e425 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/AgilityEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/AgilityEvaluator.cs
@@ -25,7 +25,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current)
var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
double travelDistance = osuPrevObj?.LazyTravelDistance ?? 0;
- double distance = travelDistance + osuCurrObj.LazyJumpDistance;
+ double distance = travelDistance + osuCurrObj.TailDistance;
double distanceScaled = Math.Min(distance, distance_cap) / distance_cap;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
index 9b1903e6137c..762000c5696e 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
@@ -26,15 +26,15 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
- double currDistance = withSliderTravelDistance ? osuCurrObj.LazyJumpDistance : osuCurrObj.JumpDistance;
- double prevDistance = withSliderTravelDistance ? osuLastObj.LazyJumpDistance : osuLastObj.JumpDistance;
+ double currDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.HeadDistance;
+ double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.HeadDistance;
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
if (osuLastObj.BaseObject is Slider && withSliderTravelDistance)
{
// If the last object is a slider, then we extend the travel velocity through the slider into the current object.
- double sliderDistance = osuLastObj.LazyTravelDistance + osuCurrObj.LazyJumpDistance;
+ double sliderDistance = osuLastObj.LazyTravelDistance + osuCurrObj.TailDistance;
currVelocity = Math.Max(currVelocity, sliderDistance / osuCurrObj.AdjustedDeltaTime);
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
index 75ecfe89ecc1..c6397f1f4a97 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
@@ -42,17 +42,17 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
const int diameter = OsuDifficultyHitObject.NORMALISED_DIAMETER;
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
- double currDistance = withSliderTravelDistance ? osuCurrObj.LazyJumpDistance : osuCurrObj.JumpDistance;
+ double currDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.HeadDistance;
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
if (osuLastObj.BaseObject is Slider && withSliderTravelDistance)
{
- double sliderDistance = osuLastObj.LazyTravelDistance + osuCurrObj.LazyJumpDistance;
+ double sliderDistance = osuLastObj.LazyTravelDistance + osuCurrObj.TailDistance;
currVelocity = Math.Max(currVelocity, sliderDistance / osuCurrObj.AdjustedDeltaTime);
}
- double prevDistance = withSliderTravelDistance ? osuLastObj.LazyJumpDistance : osuLastObj.JumpDistance;
+ double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.HeadDistance;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
double wideAngleBonus = 0;
@@ -159,7 +159,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
// Apply high circle size bonus
aimStrain *= osuCurrObj.SmallCircleBonus;
- aimStrain *= highBpmBonus(osuCurrObj.AdjustedDeltaTime, osuCurrObj.LazyJumpDistance);
+ aimStrain *= highBpmBonus(osuCurrObj.AdjustedDeltaTime, osuCurrObj.TailDistance);
return aimStrain;
}
@@ -201,7 +201,7 @@ private static double vectorAngleRepetition(OsuDifficultyHitObject current, OsuD
double vectorRepetition = Math.Pow(Math.Min(0.5 / constantAngleCount, 1), 2);
- double stackFactor = DifficultyCalculationUtils.Smootherstep(current.LazyJumpDistance, 0, OsuDifficultyHitObject.NORMALISED_DIAMETER);
+ double stackFactor = DifficultyCalculationUtils.Smootherstep(current.TailDistance, 0, OsuDifficultyHitObject.NORMALISED_DIAMETER);
double currAngle = current.Angle.Value;
double lastAngle = previous.Angle.Value;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
index e828eba1cc61..5e5bec1e49d3 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
@@ -67,7 +67,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, IReadOnly
smallDistNerf = Math.Min(1.0, jumpDistance / 75.0);
// We also want to nerf stacks so that only the first object of the stack is accounted for.
- double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0);
+ double stackNerf = Math.Min(1.0, (currentObj.TailDistance / scalingFactor) / 25.0);
// Bonus based on how visible the object is.
double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, mods.OfType().Any(m => !m.OnlyFadeApproachCircles.Value)));
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
index fe6d49661b9c..4cea7f494ca7 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
@@ -31,7 +31,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd
var currObj = (OsuDifficultyHitObject)current;
var nextObj = (OsuDifficultyHitObject)current.Next(0);
- double velocity = Math.Max(1, currObj.LazyJumpDistance / currObj.AdjustedDeltaTime); // Only allow velocity to buff
+ double velocity = Math.Max(1, currObj.TailDistance / currObj.AdjustedDeltaTime); // Only allow velocity to buff
double currentVisibleObjectDensity = retrieveCurrentVisibleObjectDensity(currObj);
double pastObjectDifficultyInfluence = getPastObjectDifficultyInfluence(currObj);
@@ -72,7 +72,7 @@ private static double calculateDensityDifficulty(OsuDifficultyHitObject? nextObj
if (nextObj != null)
{
// Reduce difficulty if movement to next object is small
- futureObjectDifficultyInfluence *= DifficultyCalculationUtils.Smootherstep(nextObj.LazyJumpDistance, 15, distance_influence_threshold);
+ futureObjectDifficultyInfluence *= DifficultyCalculationUtils.Smootherstep(nextObj.TailDistance, 15, distance_influence_threshold);
}
// Value higher note densities exponentially
@@ -134,7 +134,7 @@ private static double calculateHiddenDifficulty(OsuDifficultyHitObject currObj,
var previousObj = (OsuDifficultyHitObject)currObj.Previous(0);
// Buff perfect stacks only if current note is completely invisible at the time you click the previous note.
- if (currObj.LazyJumpDistance == 0 && currObj.OpacityAt(previousObj.BaseObject.StartTime, true) == 0 && previousObj.StartTime > currObj.StartTime - currObj.Preempt)
+ if (currObj.TailDistance == 0 && currObj.OpacityAt(previousObj.BaseObject.StartTime, true) == 0 && previousObj.StartTime > currObj.StartTime - currObj.Preempt)
hiddenDifficulty += hidden_multiplier * 2500 / Math.Pow(currObj.AdjustedDeltaTime, 1.5); // Perfect stacks are harder the less time between notes
return hiddenDifficulty;
@@ -149,7 +149,7 @@ private static double getPastObjectDifficultyInfluence(OsuDifficultyHitObject cu
double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false);
// When aiming an object small distances mean previous objects may be cheesed, so it doesn't matter whether they were arranged confusingly.
- loopDifficulty *= DifficultyCalculationUtils.Smootherstep(loopObj.LazyJumpDistance, 15, distance_influence_threshold);
+ loopDifficulty *= DifficultyCalculationUtils.Smootherstep(loopObj.TailDistance, 15, distance_influence_threshold);
// Account less for objects close to the max reading window
double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime;
@@ -245,7 +245,7 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current)
angleDifferenceAlternating = double.Lerp(Math.PI, 0.1 * angleDifferenceAlternating, weight);
}
- double stackFactor = DifficultyCalculationUtils.Smootherstep(loopObj.LazyJumpDistance, 0, OsuDifficultyHitObject.NORMALISED_RADIUS);
+ double stackFactor = DifficultyCalculationUtils.Smootherstep(loopObj.TailDistance, 0, OsuDifficultyHitObject.NORMALISED_RADIUS);
constantAngleCount += Math.Cos(3 * Math.Min(double.DegreesToRadians(30), Math.Min(angleDifference, angleDifferenceAlternating) * stackFactor)) * longIntervalFactor;
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index ab5dc7f80e0f..9f1ee6194f7e 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -49,7 +49,7 @@ public class OsuDifficultyHitObject : DifficultyHitObject
///
/// Normalised distance from the start position of the previous to the start position of this .
///
- public double JumpDistance { get; private set; }
+ public double HeadDistance { get; private set; }
///
/// Normalised distance from the "lazy" end position of the previous to the start position of this .
@@ -57,7 +57,7 @@ public class OsuDifficultyHitObject : DifficultyHitObject
/// The "lazy" end position is the position at which the cursor ends up if the previous hitobject is followed with as minimal movement as possible (i.e. on the edge of slider follow circles).
///
///
- public double LazyJumpDistance { get; private set; }
+ public double TailDistance { get; private set; }
///
/// Amount of time elapsed between the end of and the start of ,
@@ -203,8 +203,8 @@ private void setDistances(double clockRate)
Vector2 lastCursorPosition = lastDifficultyObject != null ? getEndCursorPosition(lastDifficultyObject) : LastObject.StackedPosition;
- JumpDistance = (LastObject.StackedPosition - BaseObject.StackedPosition).Length * scalingFactor;
- LazyJumpDistance = (BaseObject.StackedPosition - lastCursorPosition).Length * scalingFactor;
+ HeadDistance = (LastObject.StackedPosition - BaseObject.StackedPosition).Length * scalingFactor;
+ TailDistance = (BaseObject.StackedPosition - lastCursorPosition).Length * scalingFactor;
if (LastObject is Slider && lastDifficultyObject != null)
{
From 2cec45f1460d9f47b320e776df598b03eb589784 Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 16:27:38 -0700
Subject: [PATCH 04/13] refactor greatly
---
.../Evaluators/Aim/AgilityEvaluator.cs | 4 +-
.../Evaluators/Aim/FlowAimEvaluator.cs | 14 +---
.../Evaluators/Aim/SnapAimEvaluator.cs | 19 ++----
.../Evaluators/FlashlightEvaluator.cs | 2 +-
.../Preprocessing/OsuDifficultyHitObject.cs | 66 ++++++++++---------
5 files changed, 48 insertions(+), 57 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/AgilityEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/AgilityEvaluator.cs
index bbe3a7e6e425..da641cc4d80d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/AgilityEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/AgilityEvaluator.cs
@@ -22,10 +22,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current)
return 0;
var osuCurrObj = (OsuDifficultyHitObject)current;
- var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
- double travelDistance = osuPrevObj?.LazyTravelDistance ?? 0;
- double distance = travelDistance + osuCurrObj.TailDistance;
+ double distance = osuCurrObj.GetDistance(true);
double distanceScaled = Math.Min(distance, distance_cap) / distance_cap;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
index 762000c5696e..146b3d392db8 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
@@ -26,18 +26,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
- double currDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.HeadDistance;
- double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.HeadDistance;
+ double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
+ double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.GetDistance(false);
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
-
- if (osuLastObj.BaseObject is Slider && withSliderTravelDistance)
- {
- // If the last object is a slider, then we extend the travel velocity through the slider into the current object.
- double sliderDistance = osuLastObj.LazyTravelDistance + osuCurrObj.TailDistance;
- currVelocity = Math.Max(currVelocity, sliderDistance / osuCurrObj.AdjustedDeltaTime);
- }
-
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
double flowDifficulty = currVelocity;
@@ -104,7 +96,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
if (osuCurrObj.BaseObject is Slider && withSliderTravelDistance)
{
// Include slider velocity to make velocity more consistent with snap
- flowDifficulty += osuCurrObj.TravelDistance / osuCurrObj.TravelTime;
+ flowDifficulty += osuCurrObj.SliderBonusDistance / osuCurrObj.TravelTime;
}
// Final velocity is being raised to a power because flow difficulty scales harder with both high distance and time, and we want to account for that
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
index c6397f1f4a97..6b98748fc193 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
@@ -42,17 +42,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
const int diameter = OsuDifficultyHitObject.NORMALISED_DIAMETER;
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
- double currDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.HeadDistance;
- double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
+ double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
+ double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.GetDistance(false);
- // But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
- if (osuLastObj.BaseObject is Slider && withSliderTravelDistance)
- {
- double sliderDistance = osuLastObj.LazyTravelDistance + osuCurrObj.TailDistance;
- currVelocity = Math.Max(currVelocity, sliderDistance / osuCurrObj.AdjustedDeltaTime);
- }
-
- double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.HeadDistance;
+ double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
double wideAngleBonus = 0;
@@ -122,7 +115,9 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
if (withSliderTravelDistance)
{
// We want to use just the object jump without slider velocity when awarding differences
- currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
+ // This is objectively incorrect in some cases, but kept for now to preserve values
+ double velocityChangeDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.GetDistance(false);
+ currVelocity = velocityChangeDistance / osuCurrObj.AdjustedDeltaTime;
}
// Scale with ratio of difference compared to 0.5 * max dist.
@@ -140,7 +135,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
if (osuCurrObj.BaseObject is Slider)
{
// Reward sliders based on velocity.
- sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime;
+ sliderBonus = osuCurrObj.SliderBonusDistance / osuCurrObj.TravelTime;
}
// Penalize angle repetition.
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
index 5e5bec1e49d3..5e88335d382d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
@@ -99,7 +99,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, IReadOnly
if (osuCurrent.BaseObject is Slider osuSlider)
{
// Invert the scaling factor to determine the true travel distance independent of circle size.
- double pixelTravelDistance = osuCurrent.LazyTravelDistance / scalingFactor;
+ double pixelTravelDistance = osuCurrent.TravelDistance / scalingFactor;
// Reward sliders based on velocity.
sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.TravelTime - min_velocity), 0.5);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index 9f1ee6194f7e..e71355ad5d33 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -46,11 +46,6 @@ public class OsuDifficultyHitObject : DifficultyHitObject
///
public readonly double Preempt;
- ///
- /// Normalised distance from the start position of the previous to the start position of this .
- ///
- public double HeadDistance { get; private set; }
-
///
/// Normalised distance from the "lazy" end position of the previous to the start position of this .
///
@@ -66,32 +61,26 @@ public class OsuDifficultyHitObject : DifficultyHitObject
public double TailTime;
///
- /// Normalised distance between the start and end position of this .
+ /// The distance travelled by the cursor upon completion of this if it is a
+ /// and was hit with as few movements as possible.
///
public double TravelDistance { get; private set; }
///
- /// The time taken to travel through , with a minimum value of 25ms for objects.
+ /// The time taken to travel through , with a minimum value of 25ms for objects.
///
public double TravelTime { get; private set; }
///
- /// The position of the cursor at the point of completion of this if it is a
- /// and was hit with as few movements as possible.
- ///
- public Vector2? LazyEndPosition { get; private set; }
-
- ///
- /// The distance travelled by the cursor upon completion of this if it is a
- /// and was hit with as few movements as possible.
+ /// Normalised distance between the start and end position of this .
///
- public double LazyTravelDistance { get; private set; }
+ public double SliderBonusDistance { get; private set; }
///
- /// The time taken by the cursor upon completion of this if it is a
+ /// The position of the cursor at the point of completion of this if it is a
/// and was hit with as few movements as possible.
///
- public double LazyTravelTime { get; private set; }
+ public Vector2? LazyEndPosition { get; private set; }
///
/// Angle the player has to take to hit this .
@@ -110,6 +99,17 @@ public class OsuDifficultyHitObject : DifficultyHitObject
///
public double SmallCircleBonus { get; private set; }
+ ///
+ /// Normalised distance the cursor travels from the previous to the start position of this ,
+ /// including the slider body.
+ ///
+ private double distanceWithSlider { get; set; }
+
+ ///
+ /// Normalised distance from the start position of the previous to the start position of this .
+ ///
+ private double distanceWithoutSlider { get; set; }
+
private readonly OsuDifficultyHitObject? lastLastDifficultyObject;
private readonly OsuDifficultyHitObject? lastDifficultyObject;
@@ -127,7 +127,7 @@ public OsuDifficultyHitObject(HitObject hitObject, HitObject lastObject, double
Preempt = BaseObject.TimePreempt / clockRate;
- computeSliderCursorPosition();
+ computeSliderCursorPosition(clockRate);
setDistances(clockRate);
}
@@ -188,8 +188,7 @@ private void setDistances(double clockRate)
if (BaseObject is Slider currentSlider)
{
// Bonus for repeat sliders until a better per nested object strain system can be achieved.
- TravelDistance = LazyTravelDistance * Math.Max(1, Math.Pow(currentSlider.RepeatCount, 0.3));
- TravelTime = Math.Max(LazyTravelTime / clockRate, MIN_DELTA_TIME);
+ SliderBonusDistance = TravelDistance * Math.Max(1, Math.Pow(currentSlider.RepeatCount, 0.3));
}
TailTime = AdjustedDeltaTime;
@@ -203,18 +202,22 @@ private void setDistances(double clockRate)
Vector2 lastCursorPosition = lastDifficultyObject != null ? getEndCursorPosition(lastDifficultyObject) : LastObject.StackedPosition;
- HeadDistance = (LastObject.StackedPosition - BaseObject.StackedPosition).Length * scalingFactor;
+ distanceWithoutSlider = (LastObject.StackedPosition - BaseObject.StackedPosition).Length * scalingFactor;
TailDistance = (BaseObject.StackedPosition - lastCursorPosition).Length * scalingFactor;
if (LastObject is Slider && lastDifficultyObject != null)
{
- double lastTravelTime = Math.Max(lastDifficultyObject.LazyTravelTime / clockRate, MIN_DELTA_TIME);
- TailTime = Math.Max(AdjustedDeltaTime - lastTravelTime, MIN_DELTA_TIME);
+ TailTime = Math.Max(AdjustedDeltaTime - lastDifficultyObject.TravelTime, MIN_DELTA_TIME);
+ distanceWithSlider = distanceWithoutSlider + lastDifficultyObject.TravelDistance;
+ }
+ else
+ {
+ distanceWithSlider = distanceWithoutSlider;
}
if (lastLastDifficultyObject != null && lastLastDifficultyObject.BaseObject is not Spinner)
{
- if (lastDifficultyObject!.BaseObject is Slider prevSlider && lastDifficultyObject.TravelDistance > 0)
+ if (lastDifficultyObject!.BaseObject is Slider prevSlider && lastDifficultyObject.SliderBonusDistance > 0)
lastCursorPosition = prevSlider.HeadCircle.StackedPosition;
Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastDifficultyObject);
@@ -229,7 +232,7 @@ private void setDistances(double clockRate)
}
}
- private void computeSliderCursorPosition()
+ private void computeSliderCursorPosition(double clockRate)
{
if (BaseObject is not Slider slider)
return;
@@ -281,9 +284,10 @@ private void computeSliderCursorPosition()
nestedObjects = reordered;
}
- LazyTravelTime = trackingEndTime - slider.StartTime;
+ double lazyTravelTime = trackingEndTime - slider.StartTime;
+ TravelTime = Math.Max(lazyTravelTime / clockRate, MIN_DELTA_TIME);
- double endTimeMin = LazyTravelTime / slider.SpanDuration;
+ double endTimeMin = lazyTravelTime / slider.SpanDuration;
if (endTimeMin % 2 >= 1)
endTimeMin = 1 - endTimeMin % 1;
else
@@ -329,7 +333,7 @@ private void computeSliderCursorPosition()
// this finds the positional delta from the required radius and the current position, and updates the currCursorPosition accordingly, as well as rewarding distance.
currCursorPosition = Vector2.Add(currCursorPosition, Vector2.Multiply(currMovement, (float)((currMovementLength - requiredMovement) / currMovementLength)));
currMovementLength *= (currMovementLength - requiredMovement) / currMovementLength;
- LazyTravelDistance += currMovementLength;
+ TravelDistance += currMovementLength;
}
if (i == nestedObjects.Count - 1)
@@ -341,7 +345,7 @@ private double calculateSliderAngle(OsuDifficultyHitObject lastDifficultyObject,
{
Vector2 lastCursorPosition = getEndCursorPosition(lastDifficultyObject);
- if (lastDifficultyObject.BaseObject is Slider prevSlider && lastDifficultyObject.TravelDistance > 0)
+ if (lastDifficultyObject.BaseObject is Slider prevSlider && lastDifficultyObject.SliderBonusDistance > 0)
{
OsuHitObject secondLastNestedObject = (OsuHitObject)prevSlider.NestedHitObjects[^2];
lastLastCursorPosition = secondLastNestedObject.StackedPosition;
@@ -365,5 +369,7 @@ private Vector2 getEndCursorPosition(OsuDifficultyHitObject difficultyHitObject)
{
return difficultyHitObject.LazyEndPosition ?? difficultyHitObject.BaseObject.StackedPosition;
}
+
+ public double GetDistance(bool withSlider) => withSlider ? distanceWithSlider : distanceWithoutSlider;
}
}
From 19800ba787c8c512872c840f7b9997200f0170b6 Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 16:33:15 -0700
Subject: [PATCH 05/13] Fix distanceWithSlider
---
.../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index e71355ad5d33..8eb58a63657a 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -208,7 +208,7 @@ private void setDistances(double clockRate)
if (LastObject is Slider && lastDifficultyObject != null)
{
TailTime = Math.Max(AdjustedDeltaTime - lastDifficultyObject.TravelTime, MIN_DELTA_TIME);
- distanceWithSlider = distanceWithoutSlider + lastDifficultyObject.TravelDistance;
+ distanceWithSlider = TailDistance + lastDifficultyObject.TravelDistance;
}
else
{
From 9d75ddd538b15d90e9e9fad176ce1ffe767a7a84 Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 16:41:38 -0700
Subject: [PATCH 06/13] Refactor further for better readability
---
.../Evaluators/Aim/FlowAimEvaluator.cs | 2 +-
.../Evaluators/Aim/SnapAimEvaluator.cs | 2 +-
.../Difficulty/Evaluators/FlashlightEvaluator.cs | 4 ++--
.../Preprocessing/OsuDifficultyHitObject.cs | 16 ++++++++--------
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
index 146b3d392db8..9a404fdf1e64 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
@@ -96,7 +96,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
if (osuCurrObj.BaseObject is Slider && withSliderTravelDistance)
{
// Include slider velocity to make velocity more consistent with snap
- flowDifficulty += osuCurrObj.SliderBonusDistance / osuCurrObj.TravelTime;
+ flowDifficulty += osuCurrObj.SliderBonusDistance / osuCurrObj.SliderTime;
}
// Final velocity is being raised to a power because flow difficulty scales harder with both high distance and time, and we want to account for that
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
index 6b98748fc193..0c0fbed07d81 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
@@ -135,7 +135,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
if (osuCurrObj.BaseObject is Slider)
{
// Reward sliders based on velocity.
- sliderBonus = osuCurrObj.SliderBonusDistance / osuCurrObj.TravelTime;
+ sliderBonus = osuCurrObj.SliderBonusDistance / osuCurrObj.SliderTime;
}
// Penalize angle repetition.
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
index 5e88335d382d..480c1077df2a 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
@@ -99,10 +99,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, IReadOnly
if (osuCurrent.BaseObject is Slider osuSlider)
{
// Invert the scaling factor to determine the true travel distance independent of circle size.
- double pixelTravelDistance = osuCurrent.TravelDistance / scalingFactor;
+ double pixelTravelDistance = osuCurrent.SliderDistance / scalingFactor;
// Reward sliders based on velocity.
- sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.TravelTime - min_velocity), 0.5);
+ sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.SliderTime - min_velocity), 0.5);
// Longer sliders require more memorisation.
sliderBonus *= pixelTravelDistance;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index 8eb58a63657a..eed424a1f0a8 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -64,12 +64,12 @@ public class OsuDifficultyHitObject : DifficultyHitObject
/// The distance travelled by the cursor upon completion of this if it is a
/// and was hit with as few movements as possible.
///
- public double TravelDistance { get; private set; }
+ public double SliderDistance { get; private set; }
///
/// The time taken to travel through , with a minimum value of 25ms for objects.
///
- public double TravelTime { get; private set; }
+ public double SliderTime { get; private set; }
///
/// Normalised distance between the start and end position of this .
@@ -188,7 +188,7 @@ private void setDistances(double clockRate)
if (BaseObject is Slider currentSlider)
{
// Bonus for repeat sliders until a better per nested object strain system can be achieved.
- SliderBonusDistance = TravelDistance * Math.Max(1, Math.Pow(currentSlider.RepeatCount, 0.3));
+ SliderBonusDistance = SliderDistance * Math.Max(1, Math.Pow(currentSlider.RepeatCount, 0.3));
}
TailTime = AdjustedDeltaTime;
@@ -202,13 +202,13 @@ private void setDistances(double clockRate)
Vector2 lastCursorPosition = lastDifficultyObject != null ? getEndCursorPosition(lastDifficultyObject) : LastObject.StackedPosition;
- distanceWithoutSlider = (LastObject.StackedPosition - BaseObject.StackedPosition).Length * scalingFactor;
+ distanceWithoutSlider = (BaseObject.StackedPosition - LastObject.StackedPosition).Length * scalingFactor;
TailDistance = (BaseObject.StackedPosition - lastCursorPosition).Length * scalingFactor;
if (LastObject is Slider && lastDifficultyObject != null)
{
- TailTime = Math.Max(AdjustedDeltaTime - lastDifficultyObject.TravelTime, MIN_DELTA_TIME);
- distanceWithSlider = TailDistance + lastDifficultyObject.TravelDistance;
+ TailTime = Math.Max(TailTime - lastDifficultyObject.SliderTime, MIN_DELTA_TIME);
+ distanceWithSlider = TailDistance + lastDifficultyObject.SliderDistance;
}
else
{
@@ -285,7 +285,7 @@ private void computeSliderCursorPosition(double clockRate)
}
double lazyTravelTime = trackingEndTime - slider.StartTime;
- TravelTime = Math.Max(lazyTravelTime / clockRate, MIN_DELTA_TIME);
+ SliderTime = Math.Max(lazyTravelTime / clockRate, MIN_DELTA_TIME);
double endTimeMin = lazyTravelTime / slider.SpanDuration;
if (endTimeMin % 2 >= 1)
@@ -333,7 +333,7 @@ private void computeSliderCursorPosition(double clockRate)
// this finds the positional delta from the required radius and the current position, and updates the currCursorPosition accordingly, as well as rewarding distance.
currCursorPosition = Vector2.Add(currCursorPosition, Vector2.Multiply(currMovement, (float)((currMovementLength - requiredMovement) / currMovementLength)));
currMovementLength *= (currMovementLength - requiredMovement) / currMovementLength;
- TravelDistance += currMovementLength;
+ SliderDistance += currMovementLength;
}
if (i == nestedObjects.Count - 1)
From 04e551ef0e3d894a27bfec58f05173ad4b07d70a Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 16:51:34 -0700
Subject: [PATCH 07/13] Fix small deltas
---
.../Difficulty/Evaluators/Aim/FlowAimEvaluator.cs | 4 +++-
.../Difficulty/Evaluators/Aim/SnapAimEvaluator.cs | 9 +++++----
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
index 9a404fdf1e64..180e8a0fbd20 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
@@ -29,6 +29,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.GetDistance(false);
+ double currFakeDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.GetDistance(false); // Keeping to preserve values
+
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
@@ -77,7 +79,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
{
if (withSliderTravelDistance)
{
- currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
+ currVelocity = currFakeDistance / osuCurrObj.AdjustedDeltaTime;
}
// Scale with ratio of difference compared to 0.5 * max dist.
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
index 0c0fbed07d81..13631a694fd4 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
@@ -45,6 +45,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.GetDistance(false);
+ double currFakeDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.GetDistance(false); // Keeping to preserve values
+
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
@@ -87,8 +89,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
// Apply wiggle bonus for jumps that are [radius, 3*diameter] in distance, with < 110 angle
// https://www.desmos.com/calculator/dp0v0nvowc
wiggleBonus = angleBonus
- * DifficultyCalculationUtils.Smootherstep(currDistance, radius, diameter)
- * Math.Pow(DifficultyCalculationUtils.ReverseLerp(currDistance, diameter * 3, diameter), 1.8)
+ * DifficultyCalculationUtils.Smootherstep(currFakeDistance, radius, diameter)
+ * Math.Pow(DifficultyCalculationUtils.ReverseLerp(currFakeDistance, diameter * 3, diameter), 1.8)
* DifficultyCalculationUtils.Smootherstep(currAngle, double.DegreesToRadians(110), double.DegreesToRadians(60))
* DifficultyCalculationUtils.Smootherstep(prevDistance, radius, diameter)
* Math.Pow(DifficultyCalculationUtils.ReverseLerp(prevDistance, diameter * 3, diameter), 1.8)
@@ -116,8 +118,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
{
// We want to use just the object jump without slider velocity when awarding differences
// This is objectively incorrect in some cases, but kept for now to preserve values
- double velocityChangeDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.GetDistance(false);
- currVelocity = velocityChangeDistance / osuCurrObj.AdjustedDeltaTime;
+ currVelocity = currFakeDistance / osuCurrObj.AdjustedDeltaTime;
}
// Scale with ratio of difference compared to 0.5 * max dist.
From ab4328f830b14f0b5fc2f918e9822131c06e873f Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 16:56:24 -0700
Subject: [PATCH 08/13] Rename variables to reduce potential misinterpretations
---
.../Evaluators/Aim/FlowAimEvaluator.cs | 6 ++---
.../Evaluators/Aim/SnapAimEvaluator.cs | 10 ++++-----
.../Evaluators/FlashlightEvaluator.cs | 6 ++---
.../Difficulty/Evaluators/ReadingEvaluator.cs | 10 ++++-----
.../Evaluators/Speed/RhythmEvaluator.cs | 2 +-
.../Preprocessing/OsuDifficultyHitObject.cs | 22 +++++++++----------
6 files changed, 28 insertions(+), 28 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
index 180e8a0fbd20..a3efa671e67b 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
@@ -27,9 +27,9 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
- double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.GetDistance(false);
+ double prevDistance = withSliderTravelDistance ? osuLastObj.TailJumpDistance : osuLastObj.GetDistance(false);
- double currFakeDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.GetDistance(false); // Keeping to preserve values
+ double currFakeDistance = withSliderTravelDistance ? osuCurrObj.TailJumpDistance : osuCurrObj.GetDistance(false); // Keeping to preserve values
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
@@ -98,7 +98,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
if (osuCurrObj.BaseObject is Slider && withSliderTravelDistance)
{
// Include slider velocity to make velocity more consistent with snap
- flowDifficulty += osuCurrObj.SliderBonusDistance / osuCurrObj.SliderTime;
+ flowDifficulty += osuCurrObj.SliderBonusDistance / osuCurrObj.SliderTravelTime;
}
// Final velocity is being raised to a power because flow difficulty scales harder with both high distance and time, and we want to account for that
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
index 13631a694fd4..89feb90e0ba8 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
@@ -43,9 +43,9 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
- double prevDistance = withSliderTravelDistance ? osuLastObj.TailDistance : osuLastObj.GetDistance(false);
+ double prevDistance = withSliderTravelDistance ? osuLastObj.TailJumpDistance : osuLastObj.GetDistance(false);
- double currFakeDistance = withSliderTravelDistance ? osuCurrObj.TailDistance : osuCurrObj.GetDistance(false); // Keeping to preserve values
+ double currFakeDistance = withSliderTravelDistance ? osuCurrObj.TailJumpDistance : osuCurrObj.GetDistance(false); // Keeping to preserve values
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
@@ -136,7 +136,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
if (osuCurrObj.BaseObject is Slider)
{
// Reward sliders based on velocity.
- sliderBonus = osuCurrObj.SliderBonusDistance / osuCurrObj.SliderTime;
+ sliderBonus = osuCurrObj.SliderBonusDistance / osuCurrObj.SliderTravelTime;
}
// Penalize angle repetition.
@@ -155,7 +155,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
// Apply high circle size bonus
aimStrain *= osuCurrObj.SmallCircleBonus;
- aimStrain *= highBpmBonus(osuCurrObj.AdjustedDeltaTime, osuCurrObj.TailDistance);
+ aimStrain *= highBpmBonus(osuCurrObj.AdjustedDeltaTime, osuCurrObj.TailJumpDistance);
return aimStrain;
}
@@ -197,7 +197,7 @@ private static double vectorAngleRepetition(OsuDifficultyHitObject current, OsuD
double vectorRepetition = Math.Pow(Math.Min(0.5 / constantAngleCount, 1), 2);
- double stackFactor = DifficultyCalculationUtils.Smootherstep(current.TailDistance, 0, OsuDifficultyHitObject.NORMALISED_DIAMETER);
+ double stackFactor = DifficultyCalculationUtils.Smootherstep(current.TailJumpDistance, 0, OsuDifficultyHitObject.NORMALISED_DIAMETER);
double currAngle = current.Angle.Value;
double lastAngle = previous.Angle.Value;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
index 480c1077df2a..9b1dabd782db 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/FlashlightEvaluator.cs
@@ -67,7 +67,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, IReadOnly
smallDistNerf = Math.Min(1.0, jumpDistance / 75.0);
// We also want to nerf stacks so that only the first object of the stack is accounted for.
- double stackNerf = Math.Min(1.0, (currentObj.TailDistance / scalingFactor) / 25.0);
+ double stackNerf = Math.Min(1.0, (currentObj.TailJumpDistance / scalingFactor) / 25.0);
// Bonus based on how visible the object is.
double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, mods.OfType().Any(m => !m.OnlyFadeApproachCircles.Value)));
@@ -99,10 +99,10 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, IReadOnly
if (osuCurrent.BaseObject is Slider osuSlider)
{
// Invert the scaling factor to determine the true travel distance independent of circle size.
- double pixelTravelDistance = osuCurrent.SliderDistance / scalingFactor;
+ double pixelTravelDistance = osuCurrent.SliderBodyDistance / scalingFactor;
// Reward sliders based on velocity.
- sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.SliderTime - min_velocity), 0.5);
+ sliderBonus = Math.Pow(Math.Max(0.0, pixelTravelDistance / osuCurrent.SliderTravelTime - min_velocity), 0.5);
// Longer sliders require more memorisation.
sliderBonus *= pixelTravelDistance;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
index 4cea7f494ca7..7f202e0add7f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
@@ -31,7 +31,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd
var currObj = (OsuDifficultyHitObject)current;
var nextObj = (OsuDifficultyHitObject)current.Next(0);
- double velocity = Math.Max(1, currObj.TailDistance / currObj.AdjustedDeltaTime); // Only allow velocity to buff
+ double velocity = Math.Max(1, currObj.TailJumpDistance / currObj.AdjustedDeltaTime); // Only allow velocity to buff
double currentVisibleObjectDensity = retrieveCurrentVisibleObjectDensity(currObj);
double pastObjectDifficultyInfluence = getPastObjectDifficultyInfluence(currObj);
@@ -72,7 +72,7 @@ private static double calculateDensityDifficulty(OsuDifficultyHitObject? nextObj
if (nextObj != null)
{
// Reduce difficulty if movement to next object is small
- futureObjectDifficultyInfluence *= DifficultyCalculationUtils.Smootherstep(nextObj.TailDistance, 15, distance_influence_threshold);
+ futureObjectDifficultyInfluence *= DifficultyCalculationUtils.Smootherstep(nextObj.TailJumpDistance, 15, distance_influence_threshold);
}
// Value higher note densities exponentially
@@ -134,7 +134,7 @@ private static double calculateHiddenDifficulty(OsuDifficultyHitObject currObj,
var previousObj = (OsuDifficultyHitObject)currObj.Previous(0);
// Buff perfect stacks only if current note is completely invisible at the time you click the previous note.
- if (currObj.TailDistance == 0 && currObj.OpacityAt(previousObj.BaseObject.StartTime, true) == 0 && previousObj.StartTime > currObj.StartTime - currObj.Preempt)
+ if (currObj.TailJumpDistance == 0 && currObj.OpacityAt(previousObj.BaseObject.StartTime, true) == 0 && previousObj.StartTime > currObj.StartTime - currObj.Preempt)
hiddenDifficulty += hidden_multiplier * 2500 / Math.Pow(currObj.AdjustedDeltaTime, 1.5); // Perfect stacks are harder the less time between notes
return hiddenDifficulty;
@@ -149,7 +149,7 @@ private static double getPastObjectDifficultyInfluence(OsuDifficultyHitObject cu
double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false);
// When aiming an object small distances mean previous objects may be cheesed, so it doesn't matter whether they were arranged confusingly.
- loopDifficulty *= DifficultyCalculationUtils.Smootherstep(loopObj.TailDistance, 15, distance_influence_threshold);
+ loopDifficulty *= DifficultyCalculationUtils.Smootherstep(loopObj.TailJumpDistance, 15, distance_influence_threshold);
// Account less for objects close to the max reading window
double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime;
@@ -245,7 +245,7 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current)
angleDifferenceAlternating = double.Lerp(Math.PI, 0.1 * angleDifferenceAlternating, weight);
}
- double stackFactor = DifficultyCalculationUtils.Smootherstep(loopObj.TailDistance, 0, OsuDifficultyHitObject.NORMALISED_RADIUS);
+ double stackFactor = DifficultyCalculationUtils.Smootherstep(loopObj.TailJumpDistance, 0, OsuDifficultyHitObject.NORMALISED_RADIUS);
constantAngleCount += Math.Cos(3 * Math.Min(double.DegreesToRadians(30), Math.Min(angleDifference, angleDifferenceAlternating) * stackFactor)) * longIntervalFactor;
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
index e10ce15b3720..729beb666c9d 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
@@ -86,7 +86,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current)
// for example a slider-circle-circle pattern should be evaluated as a regular triple and not as a single->double
if (prevObj.BaseObject is Slider)
{
- double sliderLazyEndDelta = currObj.TailTime;
+ double sliderLazyEndDelta = currObj.AdjustedTailDeltaTime;
double sliderLazyDeltaDifference = Math.Max(sliderLazyEndDelta, currDelta) / Math.Min(sliderLazyEndDelta, currDelta);
double sliderRealEndDelta = currObj.LastObjectEndDeltaTime;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index eed424a1f0a8..f61051ac5e50 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -52,24 +52,24 @@ public class OsuDifficultyHitObject : DifficultyHitObject
/// The "lazy" end position is the position at which the cursor ends up if the previous hitobject is followed with as minimal movement as possible (i.e. on the edge of slider follow circles).
///
///
- public double TailDistance { get; private set; }
+ public double TailJumpDistance { get; private set; }
///
/// Amount of time elapsed between the end of and the start of ,
/// adjusted by clockrate and capped to a minimum of ms.
///
- public double TailTime;
+ public double AdjustedTailDeltaTime;
///
/// The distance travelled by the cursor upon completion of this if it is a
/// and was hit with as few movements as possible.
///
- public double SliderDistance { get; private set; }
+ public double SliderBodyDistance { get; private set; }
///
/// The time taken to travel through , with a minimum value of 25ms for objects.
///
- public double SliderTime { get; private set; }
+ public double SliderTravelTime { get; private set; }
///
/// Normalised distance between the start and end position of this .
@@ -188,10 +188,10 @@ private void setDistances(double clockRate)
if (BaseObject is Slider currentSlider)
{
// Bonus for repeat sliders until a better per nested object strain system can be achieved.
- SliderBonusDistance = SliderDistance * Math.Max(1, Math.Pow(currentSlider.RepeatCount, 0.3));
+ SliderBonusDistance = SliderBodyDistance * Math.Max(1, Math.Pow(currentSlider.RepeatCount, 0.3));
}
- TailTime = AdjustedDeltaTime;
+ AdjustedTailDeltaTime = AdjustedDeltaTime;
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
if (BaseObject is Spinner || LastObject is Spinner)
@@ -203,12 +203,12 @@ private void setDistances(double clockRate)
Vector2 lastCursorPosition = lastDifficultyObject != null ? getEndCursorPosition(lastDifficultyObject) : LastObject.StackedPosition;
distanceWithoutSlider = (BaseObject.StackedPosition - LastObject.StackedPosition).Length * scalingFactor;
- TailDistance = (BaseObject.StackedPosition - lastCursorPosition).Length * scalingFactor;
+ TailJumpDistance = (BaseObject.StackedPosition - lastCursorPosition).Length * scalingFactor;
if (LastObject is Slider && lastDifficultyObject != null)
{
- TailTime = Math.Max(TailTime - lastDifficultyObject.SliderTime, MIN_DELTA_TIME);
- distanceWithSlider = TailDistance + lastDifficultyObject.SliderDistance;
+ AdjustedTailDeltaTime = Math.Max(AdjustedTailDeltaTime - lastDifficultyObject.SliderTravelTime, MIN_DELTA_TIME);
+ distanceWithSlider = TailJumpDistance + lastDifficultyObject.SliderBodyDistance;
}
else
{
@@ -285,7 +285,7 @@ private void computeSliderCursorPosition(double clockRate)
}
double lazyTravelTime = trackingEndTime - slider.StartTime;
- SliderTime = Math.Max(lazyTravelTime / clockRate, MIN_DELTA_TIME);
+ SliderTravelTime = Math.Max(lazyTravelTime / clockRate, MIN_DELTA_TIME);
double endTimeMin = lazyTravelTime / slider.SpanDuration;
if (endTimeMin % 2 >= 1)
@@ -333,7 +333,7 @@ private void computeSliderCursorPosition(double clockRate)
// this finds the positional delta from the required radius and the current position, and updates the currCursorPosition accordingly, as well as rewarding distance.
currCursorPosition = Vector2.Add(currCursorPosition, Vector2.Multiply(currMovement, (float)((currMovementLength - requiredMovement) / currMovementLength)));
currMovementLength *= (currMovementLength - requiredMovement) / currMovementLength;
- SliderDistance += currMovementLength;
+ SliderBodyDistance += currMovementLength;
}
if (i == nestedObjects.Count - 1)
From f40e3084ab8bc63a6cc5cd33836da19dc3ba482b Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 16:57:14 -0700
Subject: [PATCH 09/13] remove Adjusted from TailDeltaTime
---
.../Difficulty/Evaluators/Speed/RhythmEvaluator.cs | 2 +-
.../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
index 729beb666c9d..8a1c3ee79589 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Speed/RhythmEvaluator.cs
@@ -86,7 +86,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current)
// for example a slider-circle-circle pattern should be evaluated as a regular triple and not as a single->double
if (prevObj.BaseObject is Slider)
{
- double sliderLazyEndDelta = currObj.AdjustedTailDeltaTime;
+ double sliderLazyEndDelta = currObj.TailDeltaTime;
double sliderLazyDeltaDifference = Math.Max(sliderLazyEndDelta, currDelta) / Math.Min(sliderLazyEndDelta, currDelta);
double sliderRealEndDelta = currObj.LastObjectEndDeltaTime;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index f61051ac5e50..d2b668d203ca 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -58,7 +58,7 @@ public class OsuDifficultyHitObject : DifficultyHitObject
/// Amount of time elapsed between the end of and the start of ,
/// adjusted by clockrate and capped to a minimum of ms.
///
- public double AdjustedTailDeltaTime;
+ public double TailDeltaTime;
///
/// The distance travelled by the cursor upon completion of this if it is a
@@ -191,7 +191,7 @@ private void setDistances(double clockRate)
SliderBonusDistance = SliderBodyDistance * Math.Max(1, Math.Pow(currentSlider.RepeatCount, 0.3));
}
- AdjustedTailDeltaTime = AdjustedDeltaTime;
+ TailDeltaTime = AdjustedDeltaTime;
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
if (BaseObject is Spinner || LastObject is Spinner)
@@ -207,7 +207,7 @@ private void setDistances(double clockRate)
if (LastObject is Slider && lastDifficultyObject != null)
{
- AdjustedTailDeltaTime = Math.Max(AdjustedTailDeltaTime - lastDifficultyObject.SliderTravelTime, MIN_DELTA_TIME);
+ TailDeltaTime = Math.Max(TailDeltaTime - lastDifficultyObject.SliderTravelTime, MIN_DELTA_TIME);
distanceWithSlider = TailJumpDistance + lastDifficultyObject.SliderBodyDistance;
}
else
From 96b7489b3da43542edbbb90ec8d9febd9f89c9d2 Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 17:13:37 -0700
Subject: [PATCH 10/13] Remove fake distance
---
.../Difficulty/Evaluators/Aim/FlowAimEvaluator.cs | 7 -------
.../Difficulty/Evaluators/Aim/SnapAimEvaluator.cs | 15 +++------------
2 files changed, 3 insertions(+), 19 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
index a3efa671e67b..b923296bf37f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
@@ -29,8 +29,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
double prevDistance = withSliderTravelDistance ? osuLastObj.TailJumpDistance : osuLastObj.GetDistance(false);
- double currFakeDistance = withSliderTravelDistance ? osuCurrObj.TailJumpDistance : osuCurrObj.GetDistance(false); // Keeping to preserve values
-
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
@@ -77,11 +75,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
if (Math.Max(prevVelocity, currVelocity) != 0)
{
- if (withSliderTravelDistance)
- {
- currVelocity = currFakeDistance / osuCurrObj.AdjustedDeltaTime;
- }
-
// Scale with ratio of difference compared to 0.5 * max dist.
double distRatio = DifficultyCalculationUtils.Smoothstep(Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity), 0, 1);
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
index 89feb90e0ba8..bc4911629a46 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
@@ -45,8 +45,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
double prevDistance = withSliderTravelDistance ? osuLastObj.TailJumpDistance : osuLastObj.GetDistance(false);
- double currFakeDistance = withSliderTravelDistance ? osuCurrObj.TailJumpDistance : osuCurrObj.GetDistance(false); // Keeping to preserve values
-
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
@@ -89,8 +87,8 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
// Apply wiggle bonus for jumps that are [radius, 3*diameter] in distance, with < 110 angle
// https://www.desmos.com/calculator/dp0v0nvowc
wiggleBonus = angleBonus
- * DifficultyCalculationUtils.Smootherstep(currFakeDistance, radius, diameter)
- * Math.Pow(DifficultyCalculationUtils.ReverseLerp(currFakeDistance, diameter * 3, diameter), 1.8)
+ * DifficultyCalculationUtils.Smootherstep(currDistance, radius, diameter)
+ * Math.Pow(DifficultyCalculationUtils.ReverseLerp(currDistance, diameter * 3, diameter), 1.8)
* DifficultyCalculationUtils.Smootherstep(currAngle, double.DegreesToRadians(110), double.DegreesToRadians(60))
* DifficultyCalculationUtils.Smootherstep(prevDistance, radius, diameter)
* Math.Pow(DifficultyCalculationUtils.ReverseLerp(prevDistance, diameter * 3, diameter), 1.8)
@@ -114,13 +112,6 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
if (Math.Max(prevVelocity, currVelocity) != 0)
{
- if (withSliderTravelDistance)
- {
- // We want to use just the object jump without slider velocity when awarding differences
- // This is objectively incorrect in some cases, but kept for now to preserve values
- currVelocity = currFakeDistance / osuCurrObj.AdjustedDeltaTime;
- }
-
// Scale with ratio of difference compared to 0.5 * max dist.
double distRatio = DifficultyCalculationUtils.Smoothstep(Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity), 0, 1);
@@ -155,7 +146,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
// Apply high circle size bonus
aimStrain *= osuCurrObj.SmallCircleBonus;
- aimStrain *= highBpmBonus(osuCurrObj.AdjustedDeltaTime, osuCurrObj.TailJumpDistance);
+ aimStrain *= highBpmBonus(osuCurrObj.AdjustedDeltaTime, currDistance);
return aimStrain;
}
From 644e67897c1f43e8c07517a850d8b4064b0f3cc8 Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 17:16:51 -0700
Subject: [PATCH 11/13] Use real distance in reading
---
.../Difficulty/Evaluators/ReadingEvaluator.cs | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
index 7f202e0add7f..77d8d99aeee4 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/ReadingEvaluator.cs
@@ -31,7 +31,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidd
var currObj = (OsuDifficultyHitObject)current;
var nextObj = (OsuDifficultyHitObject)current.Next(0);
- double velocity = Math.Max(1, currObj.TailJumpDistance / currObj.AdjustedDeltaTime); // Only allow velocity to buff
+ double velocity = Math.Max(1, currObj.GetDistance(true) / currObj.AdjustedDeltaTime); // Only allow velocity to buff
double currentVisibleObjectDensity = retrieveCurrentVisibleObjectDensity(currObj);
double pastObjectDifficultyInfluence = getPastObjectDifficultyInfluence(currObj);
@@ -72,7 +72,7 @@ private static double calculateDensityDifficulty(OsuDifficultyHitObject? nextObj
if (nextObj != null)
{
// Reduce difficulty if movement to next object is small
- futureObjectDifficultyInfluence *= DifficultyCalculationUtils.Smootherstep(nextObj.TailJumpDistance, 15, distance_influence_threshold);
+ futureObjectDifficultyInfluence *= DifficultyCalculationUtils.Smootherstep(nextObj.GetDistance(true), 15, distance_influence_threshold);
}
// Value higher note densities exponentially
@@ -134,7 +134,8 @@ private static double calculateHiddenDifficulty(OsuDifficultyHitObject currObj,
var previousObj = (OsuDifficultyHitObject)currObj.Previous(0);
// Buff perfect stacks only if current note is completely invisible at the time you click the previous note.
- if (currObj.TailJumpDistance == 0 && currObj.OpacityAt(previousObj.BaseObject.StartTime, true) == 0 && previousObj.StartTime > currObj.StartTime - currObj.Preempt)
+ // Distance without slider is deliberately used since perfectly stacked slider heads should also be buffed
+ if (currObj.GetDistance(false) == 0 && currObj.OpacityAt(previousObj.BaseObject.StartTime, true) == 0 && previousObj.StartTime > currObj.StartTime - currObj.Preempt)
hiddenDifficulty += hidden_multiplier * 2500 / Math.Pow(currObj.AdjustedDeltaTime, 1.5); // Perfect stacks are harder the less time between notes
return hiddenDifficulty;
@@ -149,7 +150,7 @@ private static double getPastObjectDifficultyInfluence(OsuDifficultyHitObject cu
double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false);
// When aiming an object small distances mean previous objects may be cheesed, so it doesn't matter whether they were arranged confusingly.
- loopDifficulty *= DifficultyCalculationUtils.Smootherstep(loopObj.TailJumpDistance, 15, distance_influence_threshold);
+ loopDifficulty *= DifficultyCalculationUtils.Smootherstep(loopObj.GetDistance(true), 15, distance_influence_threshold);
// Account less for objects close to the max reading window
double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime;
@@ -245,7 +246,7 @@ private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current)
angleDifferenceAlternating = double.Lerp(Math.PI, 0.1 * angleDifferenceAlternating, weight);
}
- double stackFactor = DifficultyCalculationUtils.Smootherstep(loopObj.TailJumpDistance, 0, OsuDifficultyHitObject.NORMALISED_RADIUS);
+ double stackFactor = DifficultyCalculationUtils.Smootherstep(loopObj.GetDistance(true), 0, OsuDifficultyHitObject.NORMALISED_RADIUS);
constantAngleCount += Math.Cos(3 * Math.Min(double.DegreesToRadians(30), Math.Min(angleDifference, angleDifferenceAlternating) * stackFactor)) * longIntervalFactor;
}
From 9565ca23eccf68dd2dae041f8bd6f087ab3d5137 Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Sun, 29 Mar 2026 17:31:16 -0700
Subject: [PATCH 12/13] use correct distance for prev object
---
.../Difficulty/Evaluators/Aim/FlowAimEvaluator.cs | 2 +-
.../Difficulty/Evaluators/Aim/SnapAimEvaluator.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
index b923296bf37f..e38a967c09ea 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/FlowAimEvaluator.cs
@@ -27,7 +27,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
- double prevDistance = withSliderTravelDistance ? osuLastObj.TailJumpDistance : osuLastObj.GetDistance(false);
+ double prevDistance = osuLastObj.GetDistance(withSliderTravelDistance);
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
index bc4911629a46..bb85be655c77 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
@@ -43,7 +43,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
double currDistance = osuCurrObj.GetDistance(withSliderTravelDistance);
- double prevDistance = withSliderTravelDistance ? osuLastObj.TailJumpDistance : osuLastObj.GetDistance(false);
+ double prevDistance = osuLastObj.GetDistance(withSliderTravelDistance);
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
From bd3c1c6dd7009d80ed6d8461e1dcbae6fc33e99f Mon Sep 17 00:00:00 2001
From: Nathan Corbett <75299710+Finadoggie@users.noreply.github.com>
Date: Wed, 1 Apr 2026 20:35:30 -0700
Subject: [PATCH 13/13] Use correct distance for kwotangular
---
.../Difficulty/Evaluators/Aim/SnapAimEvaluator.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
index bb85be655c77..bf97e3d2f822 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs
@@ -131,7 +131,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
}
// Penalize angle repetition.
- aimStrain *= vectorAngleRepetition(osuCurrObj, osuLastObj);
+ aimStrain *= vectorAngleRepetition(osuCurrObj, osuLastObj, withSliderTravelDistance);
aimStrain += wiggleBonus * wiggle_multiplier;
aimStrain += velocityChangeBonus * velocity_change_multiplier;
@@ -157,7 +157,7 @@ public static double EvaluateDifficultyOf(DifficultyHitObject current, bool with
private static double highBpmBonus(double ms, double distance) => 1 / (1 - Math.Pow(0.03, Math.Pow(ms / 1000, 0.65)))
* DifficultyCalculationUtils.Smootherstep(distance, 0, OsuDifficultyHitObject.NORMALISED_RADIUS);
- private static double vectorAngleRepetition(OsuDifficultyHitObject current, OsuDifficultyHitObject previous)
+ private static double vectorAngleRepetition(OsuDifficultyHitObject current, OsuDifficultyHitObject previous, bool withSliderTravelDistance)
{
if (current.Angle == null || previous.Angle == null)
return 1;
@@ -188,7 +188,7 @@ private static double vectorAngleRepetition(OsuDifficultyHitObject current, OsuD
double vectorRepetition = Math.Pow(Math.Min(0.5 / constantAngleCount, 1), 2);
- double stackFactor = DifficultyCalculationUtils.Smootherstep(current.TailJumpDistance, 0, OsuDifficultyHitObject.NORMALISED_DIAMETER);
+ double stackFactor = DifficultyCalculationUtils.Smootherstep(current.GetDistance(withSliderTravelDistance), 0, OsuDifficultyHitObject.NORMALISED_DIAMETER);
double currAngle = current.Angle.Value;
double lastAngle = previous.Angle.Value;