Skip to content

Commit c740248

Browse files
harsizclaude
andcommitted
Rebalance star rating curve and pp formula
Stars — fitted power-law 0.344 * bps^1.45: Analytically derived from the three calibration points (5,3) (8,6) (13,12). Fits all three to within 2%. Low-BPS maps fall below 1 and floor to 1★. DT/NC: clockRate^0.5 so 1.5x speed → +22% stars, not +50% HR/HHR: modFactor^0.6 similarly dampened Target check: 0.5 BPS → 1★ (floor) 5 BPS → 3.0★ 8 BPS → 5.9★ 13 BPS → 12.1★ 13 BPS + DT → ~14.8★ PP — capped ~3000 at 50★ via stars^1.5 * 8: Old formula used stars^2 * 5 which grew too fast. stars^1.5 * 8 peaks at 2830 for a perfect 50★ play. Added length factor: sqrt(total/1000) + 0.10, capped at 1.0 50 notes → x0.32 200 → x0.58 500 → x0.82 1000+ → x1.0 Accuracy^6 and hitRate^2 penalties unchanged. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
1 parent 429b775 commit c740248

2 files changed

Lines changed: 40 additions & 25 deletions

File tree

‎osu.Game.Rulesets.SoundVis/Beatmaps/SoundVisDifficultyCalculator.cs‎

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,31 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
2828
double totalDuration = (objects[^1].StartTime - objects[0].StartTime) / 1000.0;
2929
double bps = totalDuration > 0 ? objects.Count / totalDuration : 1.0;
3030

31-
// clockRate already encodes DT/HT/NC speed (passed in by the framework).
32-
// Approach-speed mods add extra difficulty on top.
33-
double modMultiplier = 1.0;
31+
// Approach-speed mod factors — dampened exponent so HR/HHR don't
32+
// inflate stars out of proportion (same reasoning as clockRate below).
33+
double modFactor = 1.0;
3434
foreach (var mod in mods)
3535
{
36-
if (mod is SoundVisModHarderHardRock) { modMultiplier = 1.9; break; }
37-
if (mod is SoundVisModHardRock) { modMultiplier = 1.4; break; }
36+
if (mod is SoundVisModHarderHardRock) { modFactor = 1.9; break; }
37+
if (mod is SoundVisModHardRock) { modFactor = 1.4; break; }
3838
}
3939

40-
// Power-law formula so density differences matter at all levels.
41-
// density = notes/sec adjusted for playback rate
42-
// stars = 1.8 * density^1.1 (sub-linear growth at the low end,
43-
// steeper above ~4 BPS so hard maps get meaningfully higher stars)
44-
// Cap raised to 50 — theoretical max with min-gap=60ms is ~16 BPS base,
45-
// which gives ~38★; DT(1.5x) could push that to ~57★ → capped at 50.
46-
double density = bps * clockRate * modMultiplier;
47-
double stars = Math.Round(Math.Clamp(1.8 * Math.Pow(density, 1.1), 1.0, 50.0), 2);
40+
// Star formula fitted to targets:
41+
// 0.5 BPS → ~1★ 2 BPS → ~1★ 5 BPS → ~3★
42+
// 8 BPS → ~6★ 13 BPS → ~12★
43+
//
44+
// Curve: 0.344 * bps^1.45 fits (5,3), (8,6), (13,12) to within ~2%.
45+
// Low-BPS maps naturally fall below 1 and are clamped to the floor.
46+
//
47+
// clockRate^0.5 — DT(1.5x) adds ~22% stars instead of 50%,
48+
// so 13 BPS + DT ≈ 15★ rather than 18★.
49+
// modFactor^0.6 — same dampening for approach-speed mods.
50+
double base_stars = 0.344 * Math.Pow(bps, 1.45);
51+
double stars = base_stars
52+
* Math.Pow(clockRate, 0.5)
53+
* Math.Pow(modFactor, 0.6);
54+
55+
stars = Math.Round(Math.Clamp(stars, 1.0, 50.0), 2);
4856

4957
return new DifficultyAttributes(mods, stars);
5058
}

‎osu.Game.Rulesets.SoundVis/Beatmaps/SoundVisPerformanceCalculator.cs‎

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,29 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
1919
score.Statistics.TryGetValue(HitResult.Miss, out int misses);
2020
int total = greats + misses;
2121

22-
// Miss penalty — each miss reduces pp, scales with how many you got
22+
// ── Difficulty component ────────────────────────────────────────────────
23+
// stars^1.5 × 8 peaks at ~2 830 pp for a 50★ map (≈ 3 000 max).
24+
// stars^2 was the old value; it scaled too fast.
25+
double diffPp = Math.Pow(stars, 1.5) * 8.0;
26+
27+
// ── Accuracy factor ─────────────────────────────────────────────────────
28+
// ^6 makes this very punishing near 100%:
29+
// 100% → ×1.00 95% → ×0.74 90% → ×0.53 85% → ×0.38
30+
double accFactor = Math.Pow(accuracy, 6.0);
31+
32+
// ── Miss penalty ────────────────────────────────────────────────────────
33+
// hitRate^2: losing 10% of notes → ~81% pp; losing 50% → ~25% pp.
2334
double hitRate = total > 0 ? (double)greats / total : 1.0;
2435
double missPenalty = Math.Pow(hitRate, 2.0);
2536

26-
// PP formula — mirrors osu! standard's steep difficulty scaling:
27-
//
28-
// diffPp = stars² × 5 (quadratic: doubling stars → 4× pp, just like osu! std)
29-
// accFactor = accuracy^6 (very sensitive near 100%; 95% acc ≈ 74% of max pp)
30-
// missPenalty (not miss-forgiving at all for many misses)
31-
//
32-
// The logarithmic "volume bonus" from the old formula has been removed so pp
33-
// doesn't start big and slow down — it grows purely with star rating.
34-
double diffPp = Math.Pow(stars, 2.0) * 5.0;
35-
double accFactor = Math.Pow(accuracy, 6.0);
37+
// ── Length / volume factor ──────────────────────────────────────────────
38+
// Shorter maps give meaningfully less pp than long ones at equal difficulty.
39+
// Scales from ~0.3 at 50 notes to 1.0 at 1 000+ notes (smooth square-root).
40+
// 50 notes → ×0.32 200 notes → ×0.58 500 notes → ×0.82
41+
// 800 notes → ×0.95 1000+ notes → ×1.00
42+
double lengthFactor = Math.Min(1.0, Math.Sqrt(total / 1000.0) + 0.10);
3643

37-
double pp = Math.Max(0, diffPp * accFactor * missPenalty);
44+
double pp = Math.Max(0, diffPp * accFactor * missPenalty * lengthFactor);
3845

3946
return new PerformanceAttributes { Total = pp };
4047
}

0 commit comments

Comments
 (0)