The replay stability tests needed adjustments because hit windows have
been materially changed with the previous commit. What matters in the
replay stability tests is covering the time instants near the hit window
edges and ensuring that re-encode doesn't mutate the resulting
judgements, not what the particular numbers used are.
This is a "two-birds-with-one-stone" change, which addresses both
https://github.com/ppy/osu/issues/28744 and
https://github.com/ppy/osu/issues/11311 simultaneously.
- The replay stability issue caused by time instants being rounded to
nearest integer is fixed by this, because flooring and
subtracting/adding 0.5 from the hit window threshold makes it
impossible for a judgement to switch to anything else after replay
rounding is applied - all hit windows are always a full integer plus
0.5 milliseconds, which immunizes them to rounding-to-full-ms issues.
- The direction of applying the 0.5 adjustment additionally fixes the
disparity with stable - in osu! and taiko 0.5 is subtracted as
hit window ranges in those rulesets are exclusive on stable, while in
mania 0.5 is added, as the hit window ranges there are *inclusive* on
stable.
As should be obvious, this materially changes hit windows. To what degree
this is a *significant* change is up for discussion; I would say "no"
since hitting half a millisecond changes would require 2000fps input
recording, and we're still timestamping inputs using the update thread's
clock, that gives a 1ms resolution at best.
In the worst case, in osu! and taiko, this can change a hit window range
by 1.5ms (e.g. 300.9ms -> floored to 300ms -> 299.5ms after subtraction
of the half). It's more than the best-case resolution of input
timestamps, but not by much. Considering how cleanly this resolves the
issues in question, I see it as an acceptable tradeoff.
Closes https://github.com/ppy/osu/issues/33877.
Most likely regressed when the user tags were changed such that the
loading spinner that shows on adding/removing a vote was introdiced to
every individual tag separately. This in turn means that the
`LoadingLayer` responsible for showing the spinner also briefly consumes
all input when visible, which also means that the control briefly
becomes unhovered, breaking the logic.
This probably doesn't work on mobile because mobile input sucks. On iOS
simulator it looks somewhat fine in that the tags don't move until you
touch the screen anywhere else which seems okay if that's what actually
what happens on device as well. And if it isn't I'm not sure I can do
anything sane about it anyway.
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.
For package-managed solutions that set `OSU_EXTERNAL_UPDATE_STREAM`
(which overrides the config value), we should not allow the user to
change the release stream themselves.