Skip to content

Decompose snap aim evaluator into several methods - #38201

Open
tsunyoku wants to merge 127 commits into
ppy:pp-devfrom
tsunyoku:decompose-snap-eval
Open

Decompose snap aim evaluator into several methods#38201
tsunyoku wants to merge 127 commits into
ppy:pp-devfrom
tsunyoku:decompose-snap-eval

Conversation

@tsunyoku

Copy link
Copy Markdown
Member

I think this is a lot easier to read. The levels of nesting we were reaching, and coupling totally independent bonuses together makes this super overwhelming.

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 (ppy#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 (ppy#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).
…y#34696)

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 (ppy#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 (ppy#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 - ppy#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 (ppy#36487)

There was a recent PR (ppy#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` (ppy#36573)

Slightly reducing aim/speedaim doubledipping, but mostly just done for
consistency and ease of understanding of existing relation between both
return DiffUtils.Pow(baseNerf + (1 - baseNerf) * vectorRepetition * maximum_vector_influence * stackFactor, 2);
}

private static double highBpmBonus(double ms) => 1 / (1 - DiffUtils.Pow(0.03, DiffUtils.Pow(ms / 1000, 0.65)));

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.

We now have five of these. Move to a DiffUtils helper method?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Each one of these has different constants, I think it may cause more confusion to try and consolidate

Comment thread osu.Game.Rulesets.Osu/Difficulty/Evaluators/Aim/SnapAimEvaluator.cs Outdated
@peppy

peppy commented Jul 17, 2026

Copy link
Copy Markdown
Member

Probably need to run a diffcalc spreadsheets run here to guarantee no actual changes occur.


private static double calcAngleWideness(double angle) => DiffUtils.Smoothstep(angle, double.DegreesToRadians(40), double.DegreesToRadians(140));

public static double CalcAngleAcuteness(double angle) => DiffUtils.Smoothstep(angle, double.DegreesToRadians(140), double.DegreesToRadians(40));

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.

I feel like we can move this out to ODHO at this point as well

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Move what?

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.

CalcAngleAcuteness, CalcAngleWideness

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Pending Review

Development

Successfully merging this pull request may close these issues.