1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-13 20:33:35 +08:00

Fix osu!catch SR buzz slider detection (#32412)

* Use `normalized_hitobject_radius` during osu!catch buzz slider detection

Currently the algorithm considers some buzz sliders as standstills when
in reality they require movement. This happens because `HalfCatcherWidth`
isn't normalized while `exactDistanceMoved` is, leading to an inaccurate
comparison.

`normalized_hitobject_radius` is the normalized value of `HalfCatcherWidth`
and replacing one with the other fixes the problem.

* Rename `normalized_hitobject_radius` to `normalized_half_catcher_width`

The current name is confusing because hit objects have no radius in the
context of osu!catch difficulty calculation. The new name conveys the
actual purpose of the value.

* Only set `normalized_half_catcher_width` in `CatchDifficultyHitObject`

Prevents potential bugs if the value were to be changed in one of the
classes but not in both.

* Use `CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH` directly

Requested during code review.

---------

Co-authored-by: James Wilson <tsunyoku@gmail.com>
This commit is contained in:
wulpine
2025-03-24 20:04:40 +03:00
committed by GitHub
Unverified
parent 8b11be5ac0
commit 0c3ee1938e
2 changed files with 10 additions and 9 deletions
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
{
public class CatchDifficultyHitObject : DifficultyHitObject
{
private const float normalized_hitobject_radius = 41.0f;
public const float NORMALIZED_HALF_CATCHER_WIDTH = 41.0f;
public new PalpableCatchHitObject BaseObject => (PalpableCatchHitObject)base.BaseObject;
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
: base(hitObject, lastObject, clockRate, objects, index)
{
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
float scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
float scalingFactor = NORMALIZED_HALF_CATCHER_WIDTH / halfCatcherWidth;
NormalizedPosition = BaseObject.EffectiveX * scalingFactor;
LastNormalizedPosition = LastObject.EffectiveX * scalingFactor;
@@ -12,7 +12,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
public class Movement : StrainDecaySkill
{
private const float absolute_player_positioning_error = 16f;
private const float normalized_hitobject_radius = 41.0f;
private const double direction_change_bonus = 21.0;
protected override double SkillMultiplier => 1;
@@ -55,8 +54,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
float playerPosition = Math.Clamp(
lastPlayerPosition.Value,
catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error),
catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error)
catchCurrent.NormalizedPosition - (CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH - absolute_player_positioning_error),
catchCurrent.NormalizedPosition + (CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH - absolute_player_positioning_error)
);
float distanceMoved = playerPosition - lastPlayerPosition.Value;
@@ -83,7 +82,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
}
// Base bonus for every movement, giving some weight to streams.
distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 2) / (CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 6)
/ sqrtStrain;
}
// Bonus for edge dashes.
@@ -102,10 +102,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
}
// There is an edge case where horizontal back and forth sliders create "buzz" patterns which are repeated "movements" with a distance lower than
// the platter's width but high enough to be considered a movement due to the absolute_player_positioning_error and normalized_hitobject_radius offsets
// the platter's width but high enough to be considered a movement due to the absolute_player_positioning_error and NORMALIZED_HALF_CATCHER_WIDTH offsets
// We are detecting this exact scenario. The first back and forth is counted but all subsequent ones are nullified.
// To achieve that, we need to store the exact distances (distance ignoring absolute_player_positioning_error and normalized_hitobject_radius)
if (Math.Abs(exactDistanceMoved) <= HalfCatcherWidth * 2 && exactDistanceMoved == -lastExactDistanceMoved && catchCurrent.StrainTime == lastStrainTime)
// To achieve that, we need to store the exact distances (distance ignoring absolute_player_positioning_error and NORMALIZED_HALF_CATCHER_WIDTH)
if (Math.Abs(exactDistanceMoved) <= CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 2 && exactDistanceMoved == -lastExactDistanceMoved
&& catchCurrent.StrainTime == lastStrainTime)
{
if (isInBuzzSection)
distanceAddition = 0;