1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-05 17:54:14 +08:00

Another batch of small rhythm evaluation fixes (#37609)

## [Reduce rhythm effective ratio for patterns that are speeding
up](https://github.com/ppy/osu/commit/56f66abf82b687d88e6fe83928d6dfa0f657b0a0)
Assuming the same ratio difficulty, speeding up rhythms are a bit easier
to play than slowing down ones

## [Reduce rhythm complexity sum if the final island is
long](https://github.com/ppy/osu/commit/912d81cbfedee286b634c4bcb9c4e286d91a3d88)
Currently due to how the rhythm complexity sum works rhythm difficulty
gets applied to most of the object's island - historical decay isn't
enough to counter difficulty carryover so we end up having non-zero
rhythm difficulty on long consistent rhythm patterns (for example if a
stream starts with an unusual ratio it will have non-zero rhythm
difficulty regardless of its length, even if its hundreds of objects
long). This applies a global difficulty reduction depending on the
current object's island length.
https://www.desmos.com/calculator/kvnpasbpt2

## [Fix islands being initialised incorrectly if the rhythm section is
longer than the historical
cutoff](https://github.com/ppy/osu/commit/ddf0fe758b691ca6e2a157fe335a41c4048e7e9b)

Due to the change above we now have to properly initialise islands at
the start of the rhythm loop - currently if the object we're evaluating
is a part of a >32 object consistent rhythm pattern (say a stream) it
never actually gets its island properly initialised. It doesn't matter
on the current system, but it does matter if we want to reduce
complexity sum using island length as uninitialised island always has
length of 1 so patterns that are >32 objects long stop getting their
complexity reduced after the 32th object

---------

Co-authored-by: James Wilson <tsunyoku@gmail.com>
This commit is contained in:
StanR
2026-05-04 23:29:33 +03:00
committed by GitHub
Unverified
parent b30f10f232
commit bf405fb9b0
@@ -16,8 +16,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Speed
{
private const int history_time_max = 5 * 1000; // 5 seconds
private const int history_objects_max = 32;
private const double rhythm_overall_multiplier = 0.8;
private const double rhythm_ratio_multiplier = 32.0;
private const double rhythm_overall_multiplier = 0.95;
private const double rhythm_ratio_multiplier = 26.0;
/// <summary>
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current <see cref="OsuDifficultyHitObject"/>.
@@ -70,6 +70,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Speed
double prevDelta = Math.Max(prevObj.DeltaTime, 1e-7);
double lastDelta = Math.Max(lastObj.DeltaTime, 1e-7);
// Make sure to always have the current island initialised - if we don't do it here it will only initialise on the next rhythm change
if (island.Delta == int.MaxValue)
island = new Island((int)currDelta, deltaDifferenceEpsilon);
// calculate how much current delta difference deserves a rhythm bonus
// this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200)
double deltaDifference = Math.Max(prevDelta, currDelta) / Math.Min(prevDelta, currDelta);
@@ -96,14 +100,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Speed
effectiveRatio = Math.Min(sliderEffectiveRatio, effectiveRatio);
}
bool isSpeedingUp = prevDelta > currDelta + deltaDifferenceEpsilon;
if (Math.Abs(prevDelta - currDelta) < deltaDifferenceEpsilon)
{
// island is still progressing
island.AddDelta((int)currDelta);
}
if (firstDeltaSwitch)
{
if (Math.Abs(prevDelta - currDelta) < deltaDifferenceEpsilon)
{
// island is still progressing
island.AddDelta((int)currDelta);
}
else
if (Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon)
{
// bpm change is into slider, this is easy acc window
if (currObj.BaseObject is Slider)
@@ -122,6 +129,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Speed
if (previousIsland.DeltaCount == island.DeltaCount)
effectiveRatio *= 0.5;
if (isSpeedingUp)
effectiveRatio *= 0.65;
var islandCount = islandCounts.FirstOrDefault(x => x.Island.Equals(island));
if (islandCount != default)
@@ -140,7 +150,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Speed
}
else
{
islandCounts.Add((island, 1));
if (island.DeltaCount > 0)
{
islandCounts.Add((island, 1));
}
}
// scale down the difficulty if the object is doubletappable
@@ -182,6 +195,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Speed
prevObj = currObj;
}
// If the current island is long we don't want the sum to have as big of an effect
rhythmComplexitySum *= DifficultyCalculationUtils.ReverseLerp(island.DeltaCount, 22, 3);
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_overall_multiplier) / 2.0; // produces multiplier that can be applied to strain. range [1, infinity) (not really though);
}