This is a very special case where online beatmap/ruleset models are
being ferried via `ScoreInfo` in what appear to `BeatmapDifficultyCache`
as local `BeatmapInfo`/`RulesetInfo` models. Here, BDC will incorrectly
attempt to proceed with calculating true difficulty where it cannot, and
return 0.
This is fixed locally because `ScoreInfo` is a very weird model, and I'm
not sure whether BDC should contain logic to work around this.
This was reported internally in
https://discord.com/channels/90072389919997952/1259818301517725707/1343470899357024286.
The issue described was that sample specifications on control points in
stable disappeared after the beatmap was updated from lazer.
The reason why the sample specifications were getting dropped is that
they got lost in the logic that attempts to translate per-hitobject
samples that lazer has back into stable "green line" type control
points. That process only attempted to preserve volume and custom sample
bank, but did not keep the standard bank - likely because it's kind of
superfluous information *for correct sample playback of the objects*, as
the samples get encoded again for each object individually. However
dropping this information makes for a subpar editing experience.
The choice of which sample to pick the bank from is sort of arbitrary
and I'm not sure if there's a correct one to pick. Intuitively picking
the normal sample's bank (if there is one) seems most correct.
Why is this a thing at all? How has it survived this long? I don't know.
As far as I can tell this only manifests on selected beatmaps with "slow
swells" that spend the entire beatmap moving in the background. On other
beatmaps the tick is faded out, probably due to the initial transform
application that normally "works" but fails hard on these slow swells.
Can be seen on https://osu.ppy.sh/beatmapsets/1432454#taiko/2948222.
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*.
The lack of this bricks chat completely due to newtonsoft
deserialisation errors:
2025-02-24 08:32:58 [verbose]: Processing response from https://dev.ppy.sh/api/v2/chat/updates failed with Newtonsoft.Json.JsonSerializationException: Error converting value "TEAM" to type 'osu.Game.Online.Chat.ChannelType'. Path 'presence[39].type', line 1, position 13765.
2025-02-24 08:32:58 [verbose]: ---> System.ArgumentException: Requested value 'TEAM' was not found.
2025-02-24 08:32:58 [verbose]: at Newtonsoft.Json.Utilities.EnumUtils.ParseEnum(Type enumType, NamingStrategy namingStrategy, String value, Boolean disallowNumber)
2025-02-24 08:32:58 [verbose]: at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)