This change pulls back a significant degree of overspecialisation and
rigidity in the class structure of `HitWindows` to make subsequent
changes to hit windows, whose purpose is to improve replay playback
accuracy, possible to do cleanly.
Notably:
- `HitWindows` is full abstract now. In a few use cases, and as a
reference for ruleset implementors, `DefaultHitWindows` is provided as
a separate class instead.
This fixes the weirdness wherein `HitWindows` always declared 6 fields
for result types but some of them would never be set to a non-zero
value or read.
- `HitWindow.GetRanges()` is deleted because it is overspecialised and
prevents being able to adjust hitwindows by ±0.5ms cleanly which will
be required later.
The fallout of this is that the assertion that used `GetRanges()` in
the `HitWindows` ctor must use something else now, and the closest
thing to it was `GetAllAvailableWindows()`, which didn't return
the miss window - so I made it return the miss window and fixed the
one consumer that didn't want it (bar hit error meter) to skip it.
- Diff also contains some clean-up around `DifficultyRange` to unify
handling of it.
Mostly closes https://github.com/ppy/osu/issues/33505.
Compare
https://github.com/ppy/osu/blob/97e6187f0d7c3dbee896596a623e34627135bf0e/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs#L56-L59
I say "mostly" here because I'm rather skeptical that this is 100% rock
solid still, for one reason - namely that the game stores path control
point coordinates relative to the head, then turns them into absolute
coordinates when encoding, and then on decoding turns them back into
coordinates relative to the head, which in floating-point world is a Bad
Idea because of round-off error. But I'm not fixing that without
introducing a completely new beatmap format or rewriting half the
editor to address that, so I'll just pretend that I don't know any
of this until someone notices.
Closes https://github.com/ppy/osu/issues/33465 probably.
This reverts the replay frame de-duplication logic to what it was before
https://github.com/ppy/osu/pull/33148#discussion_r2091549388.
I don't have good reproduction steps. I tried to write a test case for
this that isn't just "press and release a key in the same frame",
thinking that maybe there was some loophole in the osu! touch input
mapper that may produce this situation artificially, but I could not in
many configurations. So I have to assume that this just *can happen*
organically.
Closes https://github.com/ppy/osu/issues/33444.
The issue here is that for a set of required mods to make sense in the
context of a freestyle playlist item, it must be consistently either
valid or invalid *as a combination* across all four default rulesets,
because freestyle also permits changing ruleset. There are two
pertinent cases here:
- Flashlight and Hidden are compatible in osu!, taiko, and catch, but
not compatible in mania. In this case I've disallowed both mods
because of symmetry, basically - I don't see one "better mod" to
disallow here.
- Accuracy Challenge and Easy are incompatible in osu!, catch, and mania
(because the mod gives extra lives) there, but *compatible* in taiko,
where it does not. In this case I've disallowed Accuracy Challenge
only, because I find its value in being forced on a freestyle room
to be much smaller than Easy's.
In the large scale of things I don't see this being very important
because my view is that 99% of the use case of required mods in
freestyle is going to be changing the track speed. So I don't think
anyone is going to care about this going away - but we can reassess if
I'm proven wrong.
Closes https://github.com/ppy/osu/issues/33231.
I'm not sure how to reproduce the instances of this reported to sentry
with `Drawable{Slider,Spinner}`, but this bug is about to be made worse
by `DrawableHoldNote` in mania getting its own `JudgementResult` subtype
in https://github.com/ppy/osu/pull/33194 - for that one to reproduce
just start gameplay test while editor is seeked to a time instant where
a hold note is mid-hold.
There's possibly an argument here that `CreateResult()` should live on
`HitObject` and not `DrawableHitObject`, and I'd even be partial to such
an argument, but doing that would be a rather hard ruleset API break
(albeit trivial one to resolve), and also may dredge up past
conversations about `Judgement` and `JudgementResult` (cf.
https://github.com/ppy/osu/pull/26563) that I would rather not get into
again.
Notably this is not source-breaking for rulesets. It may be
binary-breaking, I haven't tested.
Closes https://github.com/ppy/osu/issues/21732.
While the problem of multiple judgements in one frame and ordering of
`RevertResult()` calls as described in the issue is a real one, this
commit is a bit of a sidestep of the entire issue.
The idea here that while *snapshots* of instantaneous combo values are
susceptible to such ordering foibles on revert, *deltas* are not - and
such, when deltas are using to update the combo counts on revert,
ordering stops mattering so much.
With judgements occurring forcing emission of important frames, there's
the question of emitting redundant frames. In a majority of cases
still, a judgement *will* be tied to a specific user input, which means
that there's a high likelihood of duplicate frames, as one will be
emitted by the input change, and another by the judgement.
To a degree this is a pre-existing issue. The replay recording code
responds to both mouse movement and key presses by recording frames, so
depending on the intrinsic (basically undefined) ordering between
handling mouse move and key presses, it was possible for multiple frames
to be emitted with the same timestamp to the replay, each containing
partial input state changes (e.g. first frame with mouse position
changed, and the second with a key pressed).
The extent to which this increases the frame count will obviously depend
on the beatmap, but in my ballpark testing across all rulesets on a
single "relatively standard" beatmap (think no aspire chicanery), around
4-10% of frames can end up being duplicates / not meaningful. This has
implications both on replay size and on playback performance.
This commit therefore performs de-duplication of the frames based on
timestamp - only the last frame with a given timestamp ends up in the
final replay.
The CPU cost of this is negligible to the point of not being useful to
profile - it's a single condition gated behind a single float comparison
- Closes https://github.com/ppy/osu/issues/4287
- Probably closes https://github.com/ppy/osu/issues/25405 (but not
retroactively)
Up until now, whether or not a replay frame is emitted depended solely
on the user's input, i.e. mouse movement or key presses/releases. This,
intersected with the replay playback system which is given allowance to
perform interpolation between replay frames, leads to potential
situations wherein a replay can play inaccurately when a judgement takes
place without user input meaningfully changing. One such case is slider
ends with their 36ms of judgement leniency; see
https://github.com/ppy/osu/issues/25405#issuecomment-2879031106 for
details on that.
To that end, this commit aims to counteract that issue by *forcing* an
important replay frame to be emitted on every new judgement recorded
during gameplay. This will only benefit rulesets wherein judgements can
occur that are not inherently tied to user input changing, which are
going to be osu! as mentioned above, and maybe possibly catch. I don't
foresee this doing anything relevant for taiko or mania.
This fell out of my work on hit window-related replay issues. In my WIP
branch (that is probably going to get PR'd as draft soon) I refactored
`HitWindows` and found a few straggling test failures due to some places
reading hit windows for results that were not actually supported by the
underlying `HitWindows` implementations, leading to returning garbage
(mostly zeroes).
Importantly, there is one actual usage in game code with impact here -
`TaikoModSingleTap` was attempting to read the hit window for MEH, when
MEH was never actually a valid hit result in taiko (OK is). This was a
result of a copy-paste oversight from osu!, specifically from
https://github.com/ppy/osu/blob/51cf835fb6300aca53b5b98143d606f64d7a4d49/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs#L58