Skip to content

2026 Q2 SR & PP release#37850

Merged
peppy merged 125 commits into
masterfrom
pp-dev
Jul 1, 2026
Merged

2026 Q2 SR & PP release#37850
peppy merged 125 commits into
masterfrom
pp-dev

Conversation

@tsunyoku

@tsunyoku tsunyoku commented May 20, 2026

Copy link
Copy Markdown
Member

osu, taiko and catch have deployable changes this time around.

There are 2 new databased difficulty attributes for osu ruleset.

  • ReadingDifficulty
  • ReadingDifficultNoteCount

Newspost: ppy/osu-wiki#14752

Givikap120 and others added 30 commits November 29, 2025 19:01
* Add basic scorev2 support

* Revert unnecessary score statistic changes

* Fix CI

* Revert changes

* Disable score-based misscount for scoreV2
* Simplify star rating calculations

* Refactor
Co-authored-by: StanR <8269193+stanriders@users.noreply.github.com>
Co-authored-by: StanR <8269193+stanriders@users.noreply.github.com>
Replaces a constant that assumes DecayWeight == 0.9

Co-authored-by: James Wilson <tsunyoku@gmail.com>
Co-authored-by: StanR <8269193+stanriders@users.noreply.github.com>
* Extend `Skill` to include `ObjectDifficulties`

* Remove generic

* Change `ObjectDifficulties` to be modifiable by children skills

* Fix tests
* Add `OsuHarmonicSkill`

* Make `ProcessInternal` a sealed override

* Move to main game project
* Implement new portion formula

* Change the formula

* Use base value of 5 sliders

* Make the threshold harsher

---------

Co-authored-by: StanR <8269193+stanriders@users.noreply.github.com>
…ulty values (#36209)

Fun correctness bug.

ScoreV1 multiplier uses the **nomod** peppy stars (i.e the beatmap's
"base" HP, OD and CS) in all cases, which was generally the intention
with this code but it passes the wrong beatmap resulting in it using
modded values for this.

Cross referenced with stable and this now results in the expected
multipliers.

Results in some very minor (<3pp from my testing) changes for HR and EZ
scores with combo breaks.
It's a common mistake by newer pp dev contributors to increase the
wiggle multiplier not knowing its at its maximum, so I added a comment
to try to avoid more people doing it.

---------

Co-authored-by: StanR <8269193+stanriders@users.noreply.github.com>
…e 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).
…4696)

The purpose of this change is to remove the arbitrary note based length
bonus for speed and replace it with a more concrete per note difficulty
aware calculation. As a result of the summation needing to be per-note,
chunking and in consequence peak strain reduction have both been removed
from the skill. ~~The various strain counting functions also heavily
relied on chunking and the way summation worked, so they have also been
changed to match values held before the change as much as possible
(given the different summation some changes in values are bound to
happen).~~ Above was a bug.

Huis page:
[https://pp.huismetbenen.nl/rankings/players/length-bonus](https://pp.huismetbenen.nl/rankings/players/length-bonus)

---------

Co-authored-by: James Wilson <tsunyoku@gmail.com>
…rs (#35555)

## This pr resolves the issue with the angle calculation.
The old system calculated the angle between three objects. If the last
object was a long slider that the player needed to follow, the system
calculated the angle using only the slider’s end point and the previous
object, which caused long sliders to have incorrect angle values.

---------

Co-authored-by: StanR <hi@stanr.info>
Co-authored-by: James Wilson <tsunyoku@gmail.com>
Currently the slider bonus works on the assumption that the travel
velocity of the previous slider is a part of the current object's
difficulty because it is part of the movement from prev to curr.
However, this is contradicted by the fact that `currVelocity` is a
combination of prev->curr + curr slider velocity and is actually
breaking the assumption that slider difficulty is contained in the
slider itself that we take when calculating difficult slider strains.

This makes it so that the slider bonus difficulty is contained in the
slider itself instead of buffing the next object, which makes both the
calculation overall more consistent and the slider factor calculation
actually work as expected.

Aim multiplier got slightly lowered because this change makes most of
the sliders gain a little bit

---------

Co-authored-by: James Wilson <tsunyoku@gmail.com>
…! ruleset (#33196)

This PR aims to replace the current bonuses used to award high approach
rates and scores made using the Hidden mod with a Reading skill that
takes into account each note's reading difficulty separately, Important
to note is the fact that as reading difficulty is now a skill the
bonuses are now additive instead of multiplicative, meaning there are
vast changes in deltas on the high and low end of scores. Due to the
nature of adding a new skill new difficulty and performance attributes
need to be added.

Huis page:
[https://pp.huismetbenen.nl/rankings/admin/kwotaq-reading](url)

---------

Co-authored-by: apollo-dw <83023433+apollo-dw@users.noreply.github.com>
Co-authored-by: js1086 <js1086@student.le.ac.uk>
Co-authored-by: tsunyoku <tsunyoku@gmail.com>
Co-authored-by: StanR <hi@stanr.info>
This doesn't solve _flow_ aim in any way, but makes it so that `Speed`
doesn't have distance scaling (read as "aim") anymore which fixes some
issues related to that like the length bonus behaving incorrectly, and
in general `Speed` being an aim+tap skill instead of just a tapping
skill

---------

Co-authored-by: James Wilson <tsunyoku@gmail.com>
Right now they're summed normally what opens a problem that FL rewards
too much pp when combined with reading map, since you're memorizing FL
anyway.

Co-authored-by: James Wilson <tsunyoku@gmail.com>
Part of this PR - #27303

Current aim calculation have a flaw of sliderless aim still accounting
for sliders. This happens because of usage of `LazyJumpDistance` as a
main distance metric.

This PR is fixing this by adding `JumpDistance` as true sliderless
metric, using it instead of `LazyJumpDistance`.
This can introduce very rare cases where sliderless aim is worth more
than normal aim (because of velocity change bonus).

The effect of this is minimal on most of the maps. It can be seen the
best on this map - https://osu.ppy.sh/beatmapsets/594751#osu/1257904
Before:

![image](https://github.com/user-attachments/assets/e96773e6-3274-4ed6-8293-ed9bdfa213d0)

After:

![image](https://github.com/user-attachments/assets/126e861b-c28d-4ed4-a0db-f5305225a749)

---------

Co-authored-by: James Wilson <tsunyoku@gmail.com>
Co-authored-by: StanR <8269193+stanriders@users.noreply.github.com>
It existed for one map and now that that map is fine there's no need to
do it anymore.
… higher (#36487)

There was a recent PR (#36464) that was
aimed on accounting for the fact that getting your map partially
memorized with FL makes reading easier, so those bonuses shouldn't
reward full pp to each other.
But in cases where FL pp is significantly lower than reading it have
lead to cases where FL adds practically 0 additional pp.
This PR is adjusting a formula to account for cases like this.

FL reward on this map -
https://osu.ppy.sh/beatmapsets/1487999#osu/3259719 with EZHDHT(FL):
Full bonus: 408pp -> 486pp (+78pp)
Current: 408pp -> 424pp (+16pp)
This PR: 408pp -> 478pp (+70pp)

Co-authored-by: StanR <8269193+stanriders@users.noreply.github.com>
This should buff raw speed plays like Ivaxa Violation, at the same time
undoing part of the buff on the lower end scores like Save Me NM
Moved part of the multiplier out of the Pow to be more intuitive (it
multiplies the 20 by Pow(difficulty/4), so it's more clear that it would
be equal to 1 on difficulty = 4)
The scaling itself was adjusted to be more similar to live (so
buffs/nerfs on 98% acc remains +- the same through the difficulty curve)
This should be a pretty simple fix for doubles being systemically broken
in aim. Doubles get essentially zero bonus from the aim eval itself -
wide & acute bonuses are zero due to the lack of distance, and velocity
is close to zero _again_ because of the lack of distance. However, the
delta times of these notes mean that `highBpmBonus` is very kind and
will buff the strain quite significantly.

This change means that the 2nd note of a double should get next to no
aim strain, which feels like correct behaviour. From testing, streams,
stacks etc. are essentially unchanged by this due to the fact its using
radius rather than diameter.

https://pp.huismetbenen.nl/rankings/players/doubles-strain
It removes unnecessary function `DurationSpentInvisible` function that
just rescaled preempt.
Now it's just using preempt directly.
I've made multiplier to be very close to the current one, so pp deltas
should be minimal.
…SINGLE_SPACING_THRESHOLD` (#36573)

Slightly reducing aim/speedaim doubledipping, but mostly just done for
consistency and ease of understanding of existing relation between both
Comment thread osu.Game/Rulesets/Difficulty/Utils/DiffUtils.cs Outdated
@peppy

peppy commented Jun 28, 2026

Copy link
Copy Markdown
Member

I've applied all my pending optimisations.

For 10,000 computations of osu! beatmap (no beatmap parsing)

master: 32,336 ms / 13,463 mb allocs
This PR (before optimisations): 52,882 ms / 17,521 mb allocs
This PR (now): 41,195 ms / 10,241 mb allocs

@peppy

peppy commented Jun 28, 2026

Copy link
Copy Markdown
Member

!diffcalc
RULESET=osu
OSU_A=https://github.com/ppy/osu/tree/pp-dev-no-optimisations
OSU_B=#37850

@github-actions

Copy link
Copy Markdown

Difficulty calculation failed: https://github.com/ppy/osu/actions/runs/28331275763

I'd push these straight into the branch but I don't have perms /shrug
@peppy

peppy commented Jun 29, 2026

Copy link
Copy Markdown
Member

diffcalc failure was out of disk space, re-running.

peppy
peppy previously approved these changes Jun 29, 2026
@peppy

peppy commented Jun 29, 2026

Copy link
Copy Markdown
Member

I'm setting up a background run of diffcalc, just running on my own pc for now because i foresee getting the pp-dev branch merged and nugetted taking CI / process time. will be done in a way where results can be transferred to production if all looks good.

I've already gone through them and shared my general discontent at where things are, largely due to lacking documentation and added complexity (see https://discord.com/channels/546120878908506119/1283498831224373278 history for those with access. discussion once again happening in many places which aren't public for unknown reason)

Main themes:

  • there's no inline documentation (stanr says "check PRs instead inline would be messy" but i'm sure we can all agree to disagree with that)
  • things were hugely inefficient mostly fixed, but some skills are still doing large iterations over all hitobjects per hiobject
  • the work that diffcalc is doing is being split more and more over different methods, to the point it's hard to track what is doing anything, see exhibit a:
master this PR
Jump Desktop 2026-06-29 at 05 53 25 Jump Desktop 2026-06-29 at 05 53 43

master was already pretty weird, but the new code is now doing work in:

  • getDifficultyHitObjects (this now has ruleset specific somewhat-intensive calculations going on, setting up stored values for future steps, see computeSliderCursorPosition and setDistances)
  • ProcessInternal of multiple Skills (with several sub levels beneath that)
  • ObjectDifficultyAt of multiple Skills (how is this different from the above? 🤷)
  • CreateDifficultyAttribs (final summary calculations, regularly drawing from the previous calculations even though there's no guaranteed ordering documented anywhere)

The overall flow is incomprehensible without days of learning the code, which is what I did this week. It feels to me nothing short of what I've seen in a past life when developers make a system overly convoluted to ensure job security. I don't think that's the case here, but it's at that level.

Before any further complexity is added, there needs to be a large refactor, driven with input from someone on the core team, bringing things back in line with sanity.

Merge pending spreadsheet results.

@ppy/team-client If anyone feels the need to review the changes before merge then now's your chance (let me know if that's happening).

@bdach

bdach commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

If anyone feels the need to review the changes before merge then now's your chance (let me know if that's happening).

I have no intention of doing this.

Comment on lines +214 to +215

public static double Pow(double x, int exponent) => exponent switch

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not do this? This hasn't even been properly benchmarked - the benchmark references the method above which just redirects through... Math.Pow()??

Here's the results of this int exponent version:

| Method       | Exponent | Mean      | Error     | StdDev    |
|------------- |--------- |----------:|----------:|----------:|
| MathPow      | 0        | 0.3504 ns | 0.0012 ns | 0.0010 ns |
| DiffUtilsPow | 0        | 0.5246 ns | 0.0035 ns | 0.0029 ns |
| MathPow      | 1        | 0.3508 ns | 0.0015 ns | 0.0013 ns |
| DiffUtilsPow | 1        | 0.5416 ns | 0.0080 ns | 0.0067 ns |
| MathPow      | 2        | 0.3608 ns | 0.0098 ns | 0.0091 ns |
| DiffUtilsPow | 2        | 0.5400 ns | 0.0073 ns | 0.0057 ns |
| MathPow      | 3        | 0.3525 ns | 0.0152 ns | 0.0127 ns |
| DiffUtilsPow | 3        | 0.5527 ns | 0.0140 ns | 0.0124 ns |
| MathPow      | 4        | 0.3538 ns | 0.0073 ns | 0.0064 ns |
| DiffUtilsPow | 4        | 0.5483 ns | 0.0151 ns | 0.0134 ns |
| MathPow      | 5        | 0.3369 ns | 0.0042 ns | 0.0035 ns |
| DiffUtilsPow | 5        | 0.5472 ns | 0.0047 ns | 0.0044 ns |

i.e. Unless I'm missing something, it's a regression in all cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, benchmark in PR is a very outdated version which doesn't actually run the work, will push a fix.

@github-actions

Copy link
Copy Markdown

Difficulty calculation failed: https://github.com/ppy/osu/actions/runs/28331275763

@tsunyoku

Copy link
Copy Markdown
Member Author

master was already pretty weird, but the new code is now doing work in:

  • getDifficultyHitObjects (this now has ruleset specific somewhat-intensive calculations going on, setting up stored values for future steps, see computeSliderCursorPosition and setDistances)
  • ProcessInternal of multiple Skills (with several sub levels beneath that)
  • ObjectDifficultyAt of multiple Skills (how is this different from the above? 🤷)
  • CreateDifficultyAttribs (final summary calculations, regularly drawing from the previous calculations even though there's no guaranteed ordering documented anywhere)

For my own sanity, you are aware that most of this was the case in master and was not changed by this release cycle?

ProcessInternal and ObjectDifficultyAt are new and things have been jigged around there, but everything else mentioned has been the case for a lot longer.

@peppy

peppy commented Jun 30, 2026

Copy link
Copy Markdown
Member

For my own sanity, you are aware that most of this was the case in master and was not changed by this release cycle?

Yes, see the first line of my text that you quoted.

"Used to be bad, now is worse, next time things have got to get better because we can't go further in this complexity direction" is what I'm getting at.

@peppy

peppy commented Jun 30, 2026

Copy link
Copy Markdown
Member

!diffcalc
RULESET=osu
OSU_A=https://github.com/ppy/osu/tree/pp-dev-no-optimisations
OSU_B=#37850
RANKED_ONLY=1

@github-actions

Copy link
Copy Markdown

@peppy peppy merged commit 97e94ba into master Jul 1, 2026
15 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.