From c8277b1e17d1be7fa4354b32237b6f4fe638fd32 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 30 Jun 2025 23:50:49 +0300 Subject: [PATCH 1/9] implement full lazer support for acc generation in gui --- PerformanceCalculatorGUI/RulesetHelper.cs | 70 ++++++++++++++++++----- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index 6220b7a6d5..7575a53f0e 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; @@ -66,7 +67,7 @@ public static Dictionary GenerateHitResultsForRuleset(RulesetInf { return ruleset.OnlineID switch { - 0 => generateOsuHitResults(accuracy, beatmap, countMiss, countMeh, countGood, countLargeTickMisses, countSliderTailMisses), + 0 => generateOsuHitResults(accuracy, beatmap, mods, countMiss, countMeh, countGood, countLargeTickMisses, countSliderTailMisses), 1 => generateTaikoHitResults(accuracy, beatmap, countMiss, countGood), 2 => generateCatchHitResults(accuracy, beatmap, countMiss, countMeh, countGood), 3 => generateManiaHitResults(accuracy, beatmap, mods, countMiss), @@ -74,20 +75,49 @@ public static Dictionary GenerateHitResultsForRuleset(RulesetInf }; } - private static Dictionary generateOsuHitResults(double accuracy, IBeatmap beatmap, int countMiss, int? countMeh, int? countGood, int? countLargeTickMisses, int? countSliderTailMisses) + private static Dictionary generateOsuHitResults(double accuracy, IBeatmap beatmap, Mod[] mods, int countMiss, int? countMeh, int? countGood, int? countLargeTickMisses, int? countSliderTailMisses) { + bool usingClassicSliderAccuracy = mods.OfType().Any(m => m.NoSliderHeadAccuracy.Value); + int countGreat; int totalResultCount = beatmap.HitObjects.Count; + int countLargeTicks = beatmap.HitObjects.Sum(obj => obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat)); + int countSmallTicks = beatmap.HitObjects.Count(x => x is Slider); + + // Sliderheads are large ticks too if slideracc is disabled + if (usingClassicSliderAccuracy) + countLargeTicks += countSmallTicks; + + countLargeTickMisses = Math.Min(countLargeTickMisses ?? 0, countLargeTicks); + countSliderTailMisses = Math.Min(countSliderTailMisses ?? 0, countSmallTicks); + if (countMeh != null || countGood != null) { countGreat = totalResultCount - (countGood ?? 0) - (countMeh ?? 0) - countMiss; } else { - // Total result count excluding countMiss - int relevantResultCount = totalResultCount - countMiss; + // If there's no classic slider accuracy - we need to weight circle judgements accordingly + double normalJudgementWeight = 1.0; + + // Relevant result count without misses (normal misses and slider-related misses + // We need to exclude them from judgement count so total value will be equal to desired after misses are accounted for + double relevantResultCount; + + if (usingClassicSliderAccuracy) + { + relevantResultCount = totalResultCount - countMiss; + } + else + { + double maxSliderPortion = countSmallTicks * 0.5 + countLargeTicks * 0.1; + normalJudgementWeight = (totalResultCount + maxSliderPortion) / totalResultCount; + + double missedSliderPortion = (double)countSliderTailMisses * 0.5 + (double)countLargeTickMisses * 0.1; + relevantResultCount = totalResultCount - (countMiss + missedSliderPortion) / normalJudgementWeight; + } // Accuracy excluding countMiss. We need that because we're trying to achieve target accuracy without touching countMiss // So it's better to pretened that there were 0 misses in the 1st place @@ -103,7 +133,7 @@ private static Dictionary generateOsuHitResults(double accuracy, double ratio50To100 = Math.Pow(1 - (relevantAccuracy - 0.25) / 0.75, 2); // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c50 = c100 * ratio50to100 - double count100Estimate = 6 * relevantResultCount * (1 - relevantAccuracy) / (5 * ratio50To100 + 4); + double count100Estimate = 6 * relevantResultCount * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * normalJudgementWeight; // Get count50 according to c50 = c100 * ratio50to100 double count50Estimate = count100Estimate * ratio50To100; @@ -124,17 +154,17 @@ private static Dictionary generateOsuHitResults(double accuracy, double count50Estimate = relevantResultCount - count100Estimate; // Round it to get int number of 100s - countGood = (int?)Math.Round(count100Estimate); + countGood = (int?)Math.Round(count100Estimate * normalJudgementWeight); // Get number of 50s as difference between total mistimed hits and count100 - countMeh = (int?)(Math.Round(count100Estimate + count50Estimate) - countGood); + countMeh = (int?)(Math.Round((count100Estimate + count50Estimate) * normalJudgementWeight) - countGood); } // If accuracy is less than 16.67% - it means that we have only 50s or misses // Assuming that we removed misses in the 1st place - that means that we need to add additional misses to achieve target accuracy else { // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = c100 = 0 - double count50Estimate = 6 * relevantResultCount * relevantAccuracy; + double count50Estimate = 6 * (totalResultCount - countMiss) * relevantAccuracy; // We have 0 100s, because we can't start adding 100s again after reaching "only 50s" point countGood = 0; @@ -146,6 +176,10 @@ private static Dictionary generateOsuHitResults(double accuracy, countMiss = (int)(totalResultCount - countMeh); } + // Clamp goods if total amount is bigger than possible + countGood -= Math.Clamp((int)(countGood + countMeh + countMiss - totalResultCount), 0, (int)countGood); + countMeh -= Math.Clamp((int)(countGood + countMeh + countMiss - totalResultCount), 0, (int)countMeh); + // Rest of the hits are 300s countGreat = (int)(totalResultCount - countGood - countMeh - countMiss); } @@ -159,10 +193,16 @@ private static Dictionary generateOsuHitResults(double accuracy, }; if (countLargeTickMisses != null) - result[HitResult.LargeTickMiss] = countLargeTickMisses.Value; + { + result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; + result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; + } if (countSliderTailMisses != null) - result[HitResult.SliderTailHit] = beatmap.HitObjects.Count(x => x is Slider) - countSliderTailMisses.Value; + { + result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; + if (usingClassicSliderAccuracy) result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; + } return result; } @@ -279,7 +319,7 @@ public static double GetAccuracyForRuleset(RulesetInfo ruleset, IBeatmap beatmap { return ruleset.OnlineID switch { - 0 => getOsuAccuracy(beatmap, statistics), + 0 => getOsuAccuracy(beatmap, statistics, mods), 1 => getTaikoAccuracy(statistics), 2 => getCatchAccuracy(statistics), 3 => getManiaAccuracy(statistics, mods), @@ -287,8 +327,10 @@ public static double GetAccuracyForRuleset(RulesetInfo ruleset, IBeatmap beatmap }; } - private static double getOsuAccuracy(IBeatmap beatmap, Dictionary statistics) + private static double getOsuAccuracy(IBeatmap beatmap, Dictionary statistics, Mod[] mods) { + bool usingClassicSliderAccuracy = mods.OfType().Any(m => m.NoSliderHeadAccuracy.Value); + int countGreat = statistics[HitResult.Great]; int countGood = statistics[HitResult.Ok]; int countMeh = statistics[HitResult.Meh]; @@ -297,7 +339,7 @@ private static double getOsuAccuracy(IBeatmap beatmap, Dictionary x is Slider); @@ -305,7 +347,7 @@ private static double getOsuAccuracy(IBeatmap beatmap, Dictionary obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat)); int countLargeTickHit = countLargeTicks - countLargeTicksMiss; From a20964a9fad4027680a16797a3f03500561d44fa Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 30 Jun 2025 23:53:02 +0300 Subject: [PATCH 2/9] do the same for CLI --- .../Simulate/OsuSimulateCommand.cs | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs index 81079e8d33..8505dcb85a 100644 --- a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs @@ -50,28 +50,57 @@ protected override Dictionary GenerateHitResults(IBeatmap beatma // Use lazer info only if score has sliderhead accuracy if (mods.OfType().Any(m => m.NoSliderHeadAccuracy.Value)) { - return generateHitResults(beatmap, Accuracy / 100, Misses, Mehs, Goods, null, null); + return generateHitResults(beatmap, Accuracy / 100, mods, Misses, Mehs, Goods, null, null); } else { - return generateHitResults(beatmap, Accuracy / 100, Misses, Mehs, Goods, largeTickMisses, sliderTailMisses); + return generateHitResults(beatmap, Accuracy / 100, mods, Misses, Mehs, Goods, largeTickMisses, sliderTailMisses); } } - private static Dictionary generateHitResults(IBeatmap beatmap, double accuracy, int countMiss, int? countMeh, int? countGood, int? countLargeTickMisses, int? countSliderTailMisses) + private static Dictionary generateHitResults(IBeatmap beatmap, double accuracy, Mod[] mods, int countMiss, int? countMeh, int? countGood, int? countLargeTickMisses, int? countSliderTailMisses) { + bool usingClassicSliderAccuracy = mods.OfType().Any(m => m.NoSliderHeadAccuracy.Value); + int countGreat; int totalResultCount = beatmap.HitObjects.Count; + int countLargeTicks = beatmap.HitObjects.Sum(obj => obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat)); + int countSmallTicks = beatmap.HitObjects.Count(x => x is Slider); + + // Sliderheads are large ticks too if slideracc is disabled + if (usingClassicSliderAccuracy) + countLargeTicks += countSmallTicks; + + countLargeTickMisses = Math.Min(countLargeTickMisses ?? 0, countLargeTicks); + countSliderTailMisses = Math.Min(countSliderTailMisses ?? 0, countSmallTicks); + if (countMeh != null || countGood != null) { countGreat = totalResultCount - (countGood ?? 0) - (countMeh ?? 0) - countMiss; } else { - // Total result count excluding countMiss - int relevantResultCount = totalResultCount - countMiss; + // If there's no classic slider accuracy - we need to weight circle judgements accordingly + double normalJudgementWeight = 1.0; + + // Relevant result count without misses (normal misses and slider-related misses + // We need to exclude them from judgement count so total value will be equal to desired after misses are accounted for + double relevantResultCount; + + if (usingClassicSliderAccuracy) + { + relevantResultCount = totalResultCount - countMiss; + } + else + { + double maxSliderPortion = countSmallTicks * 0.5 + countLargeTicks * 0.1; + normalJudgementWeight = (totalResultCount + maxSliderPortion) / totalResultCount; + + double missedSliderPortion = (double)countSliderTailMisses * 0.5 + (double)countLargeTickMisses * 0.1; + relevantResultCount = totalResultCount - (countMiss + missedSliderPortion) / normalJudgementWeight; + } // Accuracy excluding countMiss. We need that because we're trying to achieve target accuracy without touching countMiss // So it's better to pretened that there were 0 misses in the 1st place @@ -87,7 +116,7 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d double ratio50To100 = Math.Pow(1 - (relevantAccuracy - 0.25) / 0.75, 2); // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c50 = c100 * ratio50to100 - double count100Estimate = 6 * relevantResultCount * (1 - relevantAccuracy) / (5 * ratio50To100 + 4); + double count100Estimate = 6 * relevantResultCount * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * normalJudgementWeight; // Get count50 according to c50 = c100 * ratio50to100 double count50Estimate = count100Estimate * ratio50To100; @@ -108,17 +137,17 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d double count50Estimate = relevantResultCount - count100Estimate; // Round it to get int number of 100s - countGood = (int?)Math.Round(count100Estimate); + countGood = (int?)Math.Round(count100Estimate * normalJudgementWeight); // Get number of 50s as difference between total mistimed hits and count100 - countMeh = (int?)(Math.Round(count100Estimate + count50Estimate) - countGood); + countMeh = (int?)(Math.Round((count100Estimate + count50Estimate) * normalJudgementWeight) - countGood); } // If accuracy is less than 16.67% - it means that we have only 50s or misses // Assuming that we removed misses in the 1st place - that means that we need to add additional misses to achieve target accuracy else { // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = c100 = 0 - double count50Estimate = 6 * relevantResultCount * relevantAccuracy; + double count50Estimate = 6 * (totalResultCount - countMiss) * relevantAccuracy; // We have 0 100s, because we can't start adding 100s again after reaching "only 50s" point countGood = 0; @@ -130,6 +159,10 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d countMiss = (int)(totalResultCount - countMeh); } + // Clamp goods if total amount is bigger than possible + countGood -= Math.Clamp((int)(countGood + countMeh + countMiss - totalResultCount), 0, (int)countGood); + countMeh -= Math.Clamp((int)(countGood + countMeh + countMiss - totalResultCount), 0, (int)countMeh); + // Rest of the hits are 300s countGreat = (int)(totalResultCount - countGood - countMeh - countMiss); } @@ -143,16 +176,24 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d }; if (countLargeTickMisses != null) - result[HitResult.LargeTickMiss] = countLargeTickMisses.Value; + { + result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; + result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; + } if (countSliderTailMisses != null) - result[HitResult.SliderTailHit] = beatmap.HitObjects.Count(x => x is Slider) - countSliderTailMisses.Value; + { + result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; + if (usingClassicSliderAccuracy) result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; + } return result; } protected override double GetAccuracy(IBeatmap beatmap, Dictionary statistics, Mod[] mods) { + bool usingClassicSliderAccuracy = mods.OfType().Any(m => m.NoSliderHeadAccuracy.Value); + int countGreat = statistics[HitResult.Great]; int countGood = statistics[HitResult.Ok]; int countMeh = statistics[HitResult.Meh]; @@ -161,7 +202,7 @@ protected override double GetAccuracy(IBeatmap beatmap, Dictionary x is Slider); @@ -169,10 +210,10 @@ protected override double GetAccuracy(IBeatmap beatmap, Dictionary obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat)); - int countLargeTickHit = countLargeTicks - countLargeTickMiss; + int countLargeTickHit = countLargeTicks - countLargeTicksMiss; total += 0.6 * countLargeTickHit; max += 0.6 * countLargeTicks; From 405849039e71e0ff19191bbe84f20e5a7350ec08 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Tue, 1 Jul 2025 00:31:49 +0300 Subject: [PATCH 3/9] fix CI --- .../Simulate/OsuSimulateCommand.cs | 15 ++++----------- PerformanceCalculatorGUI/RulesetHelper.cs | 15 ++++----------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs index 8505dcb85a..443ec6864a 100644 --- a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs @@ -175,17 +175,10 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d { HitResult.Miss, countMiss } }; - if (countLargeTickMisses != null) - { - result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; - result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; - } - - if (countSliderTailMisses != null) - { - result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; - if (usingClassicSliderAccuracy) result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; - } + result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; + result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; + result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; + if (usingClassicSliderAccuracy) result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; return result; } diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index 7575a53f0e..47774ab3d8 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -192,17 +192,10 @@ private static Dictionary generateOsuHitResults(double accuracy, { HitResult.Miss, countMiss } }; - if (countLargeTickMisses != null) - { - result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; - result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; - } - - if (countSliderTailMisses != null) - { - result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; - if (usingClassicSliderAccuracy) result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; - } + result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; + result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; + result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; + if (usingClassicSliderAccuracy) result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; return result; } From 0c099e27461d2e3af2d85f85a359b941b283e3b8 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 2 Jul 2025 12:27:14 +0300 Subject: [PATCH 4/9] Reorder --- PerformanceCalculator/Simulate/OsuSimulateCommand.cs | 8 ++++---- PerformanceCalculatorGUI/RulesetHelper.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs index 443ec6864a..da5af0d893 100644 --- a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs @@ -82,13 +82,13 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d } else { - // If there's no classic slider accuracy - we need to weight circle judgements accordingly - double normalJudgementWeight = 1.0; - - // Relevant result count without misses (normal misses and slider-related misses + // Relevant result count without misses (normal misses and slider-related misses) // We need to exclude them from judgement count so total value will be equal to desired after misses are accounted for double relevantResultCount; + // If there's no classic slider accuracy - we need to weight circle judgements accordingly + double normalJudgementWeight = 1.0; + if (usingClassicSliderAccuracy) { relevantResultCount = totalResultCount - countMiss; diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index 47774ab3d8..c24f42a32a 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -99,13 +99,13 @@ private static Dictionary generateOsuHitResults(double accuracy, } else { - // If there's no classic slider accuracy - we need to weight circle judgements accordingly - double normalJudgementWeight = 1.0; - - // Relevant result count without misses (normal misses and slider-related misses + // Relevant result count without misses (normal misses and slider-related misses) // We need to exclude them from judgement count so total value will be equal to desired after misses are accounted for double relevantResultCount; + // If there's no classic slider accuracy - we need to weight circle judgements accordingly + double normalJudgementWeight = 1.0; + if (usingClassicSliderAccuracy) { relevantResultCount = totalResultCount - countMiss; From b850f53b1d778c6313b717fdbde3c63edbff5291 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Thu, 16 Oct 2025 23:46:24 +0300 Subject: [PATCH 5/9] Rename relevant result and change comment --- .../Simulate/OsuSimulateCommand.cs | 18 ++++++++++-------- PerformanceCalculatorGUI/RulesetHelper.cs | 18 ++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs index da5af0d893..504a2b1ba8 100644 --- a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs @@ -84,14 +84,16 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d { // Relevant result count without misses (normal misses and slider-related misses) // We need to exclude them from judgement count so total value will be equal to desired after misses are accounted for - double relevantResultCount; + double countSuccessfulHits; - // If there's no classic slider accuracy - we need to weight circle judgements accordingly + // If there's no classic slider accuracy - we need to weight normal judgements accordingly. + // Normal judgements in this context are 300s, 100s, 50s and misses. + // Slider-related judgements are large tick hits/misses and slider tail hits/misses. double normalJudgementWeight = 1.0; if (usingClassicSliderAccuracy) { - relevantResultCount = totalResultCount - countMiss; + countSuccessfulHits = totalResultCount - countMiss; } else { @@ -99,12 +101,12 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d normalJudgementWeight = (totalResultCount + maxSliderPortion) / totalResultCount; double missedSliderPortion = (double)countSliderTailMisses * 0.5 + (double)countLargeTickMisses * 0.1; - relevantResultCount = totalResultCount - (countMiss + missedSliderPortion) / normalJudgementWeight; + countSuccessfulHits = totalResultCount - (countMiss + missedSliderPortion) / normalJudgementWeight; } // Accuracy excluding countMiss. We need that because we're trying to achieve target accuracy without touching countMiss // So it's better to pretened that there were 0 misses in the 1st place - double relevantAccuracy = accuracy * totalResultCount / relevantResultCount; + double relevantAccuracy = accuracy * totalResultCount / countSuccessfulHits; // Clamp accuracy to account for user trying to break the algorithm by inputting impossible values relevantAccuracy = Math.Clamp(relevantAccuracy, 0, 1); @@ -116,7 +118,7 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d double ratio50To100 = Math.Pow(1 - (relevantAccuracy - 0.25) / 0.75, 2); // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c50 = c100 * ratio50to100 - double count100Estimate = 6 * relevantResultCount * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * normalJudgementWeight; + double count100Estimate = 6 * countSuccessfulHits * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * normalJudgementWeight; // Get count50 according to c50 = c100 * ratio50to100 double count50Estimate = count100Estimate * ratio50To100; @@ -131,10 +133,10 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d else if (relevantAccuracy >= 1.0 / 6) { // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = 0 - double count100Estimate = 6 * relevantResultCount * relevantAccuracy - relevantResultCount; + double count100Estimate = 6 * countSuccessfulHits * relevantAccuracy - countSuccessfulHits; // We only had 100s and 50s in that scenario so rest of the hits are 50s - double count50Estimate = relevantResultCount - count100Estimate; + double count50Estimate = countSuccessfulHits - count100Estimate; // Round it to get int number of 100s countGood = (int?)Math.Round(count100Estimate * normalJudgementWeight); diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index c24f42a32a..9e8f40044a 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -101,14 +101,16 @@ private static Dictionary generateOsuHitResults(double accuracy, { // Relevant result count without misses (normal misses and slider-related misses) // We need to exclude them from judgement count so total value will be equal to desired after misses are accounted for - double relevantResultCount; + double countSuccessfulHits; - // If there's no classic slider accuracy - we need to weight circle judgements accordingly + // If there's no classic slider accuracy - we need to weight normal judgements accordingly. + // Normal judgements in this context are 300s, 100s, 50s and misses. + // Slider-related judgements are large tick hits/misses and slider tail hits/misses. double normalJudgementWeight = 1.0; if (usingClassicSliderAccuracy) { - relevantResultCount = totalResultCount - countMiss; + countSuccessfulHits = totalResultCount - countMiss; } else { @@ -116,12 +118,12 @@ private static Dictionary generateOsuHitResults(double accuracy, normalJudgementWeight = (totalResultCount + maxSliderPortion) / totalResultCount; double missedSliderPortion = (double)countSliderTailMisses * 0.5 + (double)countLargeTickMisses * 0.1; - relevantResultCount = totalResultCount - (countMiss + missedSliderPortion) / normalJudgementWeight; + countSuccessfulHits = totalResultCount - (countMiss + missedSliderPortion) / normalJudgementWeight; } // Accuracy excluding countMiss. We need that because we're trying to achieve target accuracy without touching countMiss // So it's better to pretened that there were 0 misses in the 1st place - double relevantAccuracy = accuracy * totalResultCount / relevantResultCount; + double relevantAccuracy = accuracy * totalResultCount / countSuccessfulHits; // Clamp accuracy to account for user trying to break the algorithm by inputting impossible values relevantAccuracy = Math.Clamp(relevantAccuracy, 0, 1); @@ -133,7 +135,7 @@ private static Dictionary generateOsuHitResults(double accuracy, double ratio50To100 = Math.Pow(1 - (relevantAccuracy - 0.25) / 0.75, 2); // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c50 = c100 * ratio50to100 - double count100Estimate = 6 * relevantResultCount * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * normalJudgementWeight; + double count100Estimate = 6 * countSuccessfulHits * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * normalJudgementWeight; // Get count50 according to c50 = c100 * ratio50to100 double count50Estimate = count100Estimate * ratio50To100; @@ -148,10 +150,10 @@ private static Dictionary generateOsuHitResults(double accuracy, else if (relevantAccuracy >= 1.0 / 6) { // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c300 = 0 - double count100Estimate = 6 * relevantResultCount * relevantAccuracy - relevantResultCount; + double count100Estimate = 6 * countSuccessfulHits * relevantAccuracy - countSuccessfulHits; // We only had 100s and 50s in that scenario so rest of the hits are 50s - double count50Estimate = relevantResultCount - count100Estimate; + double count50Estimate = countSuccessfulHits - count100Estimate; // Round it to get int number of 100s countGood = (int?)Math.Round(count100Estimate * normalJudgementWeight); From a2a61ce0be3cebcbcf3ec9af724e09ad926da1a8 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 3 Nov 2025 17:40:38 +0200 Subject: [PATCH 6/9] Revert rename --- PerformanceCalculator/Simulate/OsuSimulateCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs index 504a2b1ba8..f47bfedba2 100644 --- a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs @@ -205,10 +205,10 @@ protected override double GetAccuracy(IBeatmap beatmap, Dictionary obj.NestedHitObjects.Count(x => x is SliderTick or SliderRepeat)); - int countLargeTickHit = countLargeTicks - countLargeTicksMiss; + int countLargeTickHit = countLargeTicks - countLargeTickMiss; total += 0.6 * countLargeTickHit; max += 0.6 * countLargeTicks; From 4d40eccb5e7ca062f4c35f146b27a406dfec0b99 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 10 Nov 2025 22:37:54 +0100 Subject: [PATCH 7/9] Separate the small tick miss block --- PerformanceCalculator/Simulate/OsuSimulateCommand.cs | 5 ++++- PerformanceCalculatorGUI/RulesetHelper.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs index f47bfedba2..2fd0255b04 100644 --- a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs @@ -180,7 +180,10 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; - if (usingClassicSliderAccuracy) result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; + + // Only classic slider accuracy scores has small tick misses + if (usingClassicSliderAccuracy) + result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; return result; } diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index 9e8f40044a..6de3318171 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -197,7 +197,10 @@ private static Dictionary generateOsuHitResults(double accuracy, result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; - if (usingClassicSliderAccuracy) result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; + + // Only classic slider accuracy scores has small tick misses + if (usingClassicSliderAccuracy) + result[HitResult.SmallTickMiss] = (int)countSliderTailMisses; return result; } From c4fc855cc4e6f6471e99fac20a548d0f6cb6ca00 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 10 Nov 2025 22:41:06 +0100 Subject: [PATCH 8/9] add new lines after large tick judgements --- PerformanceCalculator/Simulate/OsuSimulateCommand.cs | 1 + PerformanceCalculatorGUI/RulesetHelper.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs index 2fd0255b04..9576c9ba9b 100644 --- a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs @@ -179,6 +179,7 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; + result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; // Only classic slider accuracy scores has small tick misses diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index 6de3318171..b2bca922e4 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -196,6 +196,7 @@ private static Dictionary generateOsuHitResults(double accuracy, result[HitResult.LargeTickHit] = countLargeTicks - (int)countLargeTickMisses; result[HitResult.LargeTickMiss] = (int)countLargeTickMisses; + result[usingClassicSliderAccuracy ? HitResult.SmallTickHit : HitResult.SliderTailHit] = countSmallTicks - (int)countSliderTailMisses; // Only classic slider accuracy scores has small tick misses From 56bf793c16cfb7896d5d7218d583eb20f12afd3a Mon Sep 17 00:00:00 2001 From: Givy120 Date: Sat, 4 Apr 2026 15:10:54 +0200 Subject: [PATCH 9/9] Rename to nonSliderJudgements for now --- PerformanceCalculator/Simulate/OsuSimulateCommand.cs | 12 ++++++------ PerformanceCalculatorGUI/RulesetHelper.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs index 72cc9d8948..d630b10445 100644 --- a/PerformanceCalculator/Simulate/OsuSimulateCommand.cs +++ b/PerformanceCalculator/Simulate/OsuSimulateCommand.cs @@ -89,7 +89,7 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d // If there's no classic slider accuracy - we need to weight normal judgements accordingly. // Normal judgements in this context are 300s, 100s, 50s and misses. // Slider-related judgements are large tick hits/misses and slider tail hits/misses. - double normalJudgementWeight = 1.0; + double nonSliderJudgementWeight = 1.0; if (usingClassicSliderAccuracy) { @@ -98,10 +98,10 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d else { double maxSliderPortion = countSmallTicks * 0.5 + countLargeTicks * 0.1; - normalJudgementWeight = (totalResultCount + maxSliderPortion) / totalResultCount; + nonSliderJudgementWeight = (totalResultCount + maxSliderPortion) / totalResultCount; double missedSliderPortion = (double)countSliderTailMisses * 0.5 + (double)countLargeTickMisses * 0.1; - countSuccessfulHits = totalResultCount - (countMiss + missedSliderPortion) / normalJudgementWeight; + countSuccessfulHits = totalResultCount - (countMiss + missedSliderPortion) / nonSliderJudgementWeight; } // Accuracy excluding countMiss. We need that because we're trying to achieve target accuracy without touching countMiss @@ -118,7 +118,7 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d double ratio50To100 = Math.Pow(1 - (relevantAccuracy - 0.25) / 0.75, 2); // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c50 = c100 * ratio50to100 - double count100Estimate = 6 * countSuccessfulHits * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * normalJudgementWeight; + double count100Estimate = 6 * countSuccessfulHits * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * nonSliderJudgementWeight; // Get count50 according to c50 = c100 * ratio50to100 double count50Estimate = count100Estimate * ratio50To100; @@ -139,10 +139,10 @@ private static Dictionary generateHitResults(IBeatmap beatmap, d double count50Estimate = countSuccessfulHits - count100Estimate; // Round it to get int number of 100s - countGood = (int?)Math.Round(count100Estimate * normalJudgementWeight); + countGood = (int?)Math.Round(count100Estimate * nonSliderJudgementWeight); // Get number of 50s as difference between total mistimed hits and count100 - countMeh = (int?)(Math.Round((count100Estimate + count50Estimate) * normalJudgementWeight) - countGood); + countMeh = (int?)(Math.Round((count100Estimate + count50Estimate) * nonSliderJudgementWeight) - countGood); } // If accuracy is less than 16.67% - it means that we have only 50s or misses // Assuming that we removed misses in the 1st place - that means that we need to add additional misses to achieve target accuracy diff --git a/PerformanceCalculatorGUI/RulesetHelper.cs b/PerformanceCalculatorGUI/RulesetHelper.cs index b2bca922e4..51b0d3e4e6 100644 --- a/PerformanceCalculatorGUI/RulesetHelper.cs +++ b/PerformanceCalculatorGUI/RulesetHelper.cs @@ -106,7 +106,7 @@ private static Dictionary generateOsuHitResults(double accuracy, // If there's no classic slider accuracy - we need to weight normal judgements accordingly. // Normal judgements in this context are 300s, 100s, 50s and misses. // Slider-related judgements are large tick hits/misses and slider tail hits/misses. - double normalJudgementWeight = 1.0; + double nonSliderJudgementWeight = 1.0; if (usingClassicSliderAccuracy) { @@ -115,10 +115,10 @@ private static Dictionary generateOsuHitResults(double accuracy, else { double maxSliderPortion = countSmallTicks * 0.5 + countLargeTicks * 0.1; - normalJudgementWeight = (totalResultCount + maxSliderPortion) / totalResultCount; + nonSliderJudgementWeight = (totalResultCount + maxSliderPortion) / totalResultCount; double missedSliderPortion = (double)countSliderTailMisses * 0.5 + (double)countLargeTickMisses * 0.1; - countSuccessfulHits = totalResultCount - (countMiss + missedSliderPortion) / normalJudgementWeight; + countSuccessfulHits = totalResultCount - (countMiss + missedSliderPortion) / nonSliderJudgementWeight; } // Accuracy excluding countMiss. We need that because we're trying to achieve target accuracy without touching countMiss @@ -135,7 +135,7 @@ private static Dictionary generateOsuHitResults(double accuracy, double ratio50To100 = Math.Pow(1 - (relevantAccuracy - 0.25) / 0.75, 2); // Derived from the formula: Accuracy = (6 * c300 + 2 * c100 + c50) / (6 * totalHits), assuming that c50 = c100 * ratio50to100 - double count100Estimate = 6 * countSuccessfulHits * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * normalJudgementWeight; + double count100Estimate = 6 * countSuccessfulHits * (1 - relevantAccuracy) / (5 * ratio50To100 + 4) * nonSliderJudgementWeight; // Get count50 according to c50 = c100 * ratio50to100 double count50Estimate = count100Estimate * ratio50To100; @@ -156,10 +156,10 @@ private static Dictionary generateOsuHitResults(double accuracy, double count50Estimate = countSuccessfulHits - count100Estimate; // Round it to get int number of 100s - countGood = (int?)Math.Round(count100Estimate * normalJudgementWeight); + countGood = (int?)Math.Round(count100Estimate * nonSliderJudgementWeight); // Get number of 50s as difference between total mistimed hits and count100 - countMeh = (int?)(Math.Round((count100Estimate + count50Estimate) * normalJudgementWeight) - countGood); + countMeh = (int?)(Math.Round((count100Estimate + count50Estimate) * nonSliderJudgementWeight) - countGood); } // If accuracy is less than 16.67% - it means that we have only 50s or misses // Assuming that we removed misses in the 1st place - that means that we need to add additional misses to achieve target accuracy