From 2876cebdd7d9979ace86eadff56bdc659c9806cd Mon Sep 17 00:00:00 2001 From: Natelytle <92956514+Natelytle@users.noreply.github.com> Date: Thu, 22 Jan 2026 14:05:21 -0500 Subject: [PATCH] Move strain's influence in the difficulty of a note based on deltaTime to the evaluator level (#36417) This PR moves the influence of d/t^2 from the skill (through strain) directly into the evaluator level as a bonus applied at the end. This makes it more clear what d/t^2 is, and the fact that it applies to *all* bonuses in the evaluator. As well, the peak strain value of a note is now equal to the evaluator value. This comes with a couple benefits: 1. The BPM weight is now subject to balance, and can be flattened easily for a more "d/t" like system (previously this required hacky solutions) 2. StrainDecayBase becomes a much more useful variable now that it does not affect the difficulty of the note itself. When adjusting this in live, your star rating would double upon changing from 0.15 in aim to 0.3, and now it is intuitive what it does (makes strain take longer to accumulate). This means that future balancing efforts can use evaluators to dynamically adjust strainDecayBase (potentially letting large spikes provide more strain for the same difficulty if they're wide angle, for example). 3. In the object inspector, you get the actual maximum difficulty value of a note as seen in the ObjectDifficulties list. This makes it easier to tell what notes are deemed as harder by the system. For context, previously, a note of difficulty 1 at 200bpm would cap out at 6 as a result of strain, and a note of the same difficulty but at 300bpm would cap out at 8. The actual implementation is really really simple. I'm willing to move this to flashlight if wanted (I don't think it's necessary), or even abstract this away so that (1 - decay) doesn't look like a balancing constant. Side note: this is equivalent to live, except for notes with a deltaTime of less than 25ms (since I am using AdjustedDeltaTime in the aim evaluator for the bonus to avoid divisions by zero). --- .../Difficulty/Evaluators/AimEvaluator.cs | 4 ++++ .../Difficulty/Evaluators/SpeedEvaluator.cs | 4 ++++ osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 7 +++++-- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 6 ++++-- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs index d311bcf93d..257087d69e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs @@ -162,9 +162,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators if (withSliderTravelDistance) aimStrain += sliderBonus * slider_multiplier; + aimStrain *= highBpmBonus(osuCurrObj.AdjustedDeltaTime); + return aimStrain; } + private static double highBpmBonus(double ms) => 1 / (1 - Math.Pow(0.15, ms / 1000)); + private static double calcWideAngleBonus(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(40), double.DegreesToRadians(140)); private static double calcAcuteAngleBonus(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(140), double.DegreesToRadians(40)); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs index e6197b177b..a35d24d7a8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs @@ -69,8 +69,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Base difficulty with all bonuses double difficulty = (1 + speedBonus + distanceBonus) * 1000 / strainTime; + difficulty *= highBpmBonus(osuCurrObj.AdjustedDeltaTime); + // Apply penalty if there's doubletappable doubles return difficulty * doubletapness; } + + private static double highBpmBonus(double ms) => 1 / (1 - Math.Pow(0.3, ms / 1000)); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index dbdfef4199..a97bad877d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Evaluators; +using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Utils; using osu.Game.Rulesets.Osu.Objects; @@ -38,8 +39,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double StrainValueAt(DifficultyHitObject current) { - currentStrain *= strainDecay(current.DeltaTime); - currentStrain += AimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * skillMultiplier; + double decay = strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime); + + currentStrain *= decay; + currentStrain += AimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * (1 - decay) * skillMultiplier; if (current.BaseObject is Slider) sliderStrains.Add(currentStrain); diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 33dc23b7d8..cbba1de152 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -39,8 +39,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double StrainValueAt(DifficultyHitObject current) { - currentStrain *= strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime); - currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, Mods) * skillMultiplier; + double decay = strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime); + + currentStrain *= decay; + currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, Mods) * (1 - decay) * skillMultiplier; currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);