This is still a workaround but arguably it's something we could leave in
place without much loss. I think this at least feels better than the
previous code.
Notably, you could argue the test coverage of the fail case is lower
since made it implicit that all tests will avoid the "backwards seek"
detections. But we never really had tests correctly- fail on the original
so I don't see any loss of value.
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.
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.
Closes https://github.com/ppy/osu/issues/32052.
Sooooo... this is going to be a rant...
To understand why this is going to require a rant, dear reader, please
do the following:
1. Read the issue thread and follow the reproduction scenario (download
map linked, fire up autoplay, seek near end, wait for results, hear
the sample spam).
2. Now exit out to song select, *hide the toolbar*, and attempt
reproducing the issue again.
3. Depending on ambient mood, laugh or cry.
Now, *why on earth* would the *TOOLBAR* have any bearing on anything?
Well, the chain of failure is something like this:
- The toolbar hides for the duration of gameplay, naturally.
- When progressing to results, the toolbar gets automatically unhidden.
- This triggers invalidations on `ScrollingHitObjectContainer`. I'm not
precisely sure which property it is that triggers the invalidations,
but one clearly does. It may be position or size or whichever.
- When the invalidation is triggered on `layoutCache`, the next
`Update()` call is going to recompute lifetimes for ALL hitobject
entries.
- In case of swells, it happens that the calculated lifetime end of the
swell is larger than what it actually ended up being determined as at
the instant of judging the swell, and thus, the swell is *resurrected*,
reassigned a DHO, and the DHO calls `UpdateState()` and plays the
sample again despite the `samplePlayed` flag in `LegacySwell`, because
all of that is ephemeral state that does not survive a hitobject
getting resurrected.
Now I *could* just fix this locally to the swell, maybe, by having some
time lenience check, but the fact that hitobjects can be resurrected by
the *toolbar* appearing, of all possible causes in the world, feels
just completely wrong. So I'm adding a local check in SHOC to not
overwrite lifetime ends of judged object entries.
The reason why I'm making that check specific to end time is that I can
see valid reasons why you would want to recompute lifetime *start* even
on a judged object (such as playfield geometry changing in a significant
way). I can't think of a valid reason to do that to lifetime *end*.
Also makes the mod display initialization sequence (start expanded, then
unexpand) controlled by HUDOverlay rather than mod display itself. This
enabled different treatment depending on whether the mod display is
viewed in the skin editor or in the player.
Co-authored-by: Bartłomiej Dach <dach.bartlomiej@gmail.com>
Several users have reported stutters when this happens. It's potentially
from the error report overhead. We now know that this is a BASS level
issue anyway, so having this logging is not helpful.