After testing out the icon on device for a bit, we decided the osu! logo
was a little too large.
This PR replaces the app icons with a new version where the sizing of
the logo itself is aligned to Apple's recommended boundary.
<img width="198" height="169" alt="Screenshot 2026-01-30 at 2 33 24 PM"
src="https://github.com/user-attachments/assets/c66fb016-9c19-4ff5-814a-6f302ec459ca"
/>
Regressed in d6bf4fd90d.
One very visible instance of this regression is the login form.
https://github.com/user-attachments/assets/5ba10ac5-4cb1-49af-b55c-89cf58ca0b44
The `CommentEditor` usage was discovered with one of my favourite tricks
which is doing
```diff
diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
index fefe776b01..c17cca726b 100644
--- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs
@@ -42,6 +42,12 @@ public partial class OsuTextBox : BasicTextBox
Margin = new MarginPadding { Left = 2 },
};
+ public new bool Masking
+ {
+ get => base.Masking;
+ set => base.Masking = value;
+ }
+
protected bool DrawBorder { get; init; } = true;
private OsuCaret? caret;
```
and then looking for usages of the setter. That's all due diligence
you're getting here, I'm not auditing every single text box in the game.
And yes, the `CommentEditor` usage is OMEGA dodgy but the change applied
here is the only one that preserves its visual appearance. I'm not
putting in time to fix it.
Probably closes https://github.com/ppy/osu/issues/36492.
This is dumb but it's in large part my stupidity.
To begin with, the immediate direct offending call that causes the
observed symptoms is
https://github.com/ppy/osu/blob/a401c7d5e9d6d8b05b2ec293145ad308dfe9d6d0/osu.Game/Screens/Edit/Components/FormSampleSet.cs#L296
The reason why this "invalidation" affects sample volume is that in the
framework implementation, the call [removes the relevant sample factory
from the sample store which is an audio
component](https://github.com/ppy/osu-framework/blob/5b716dcbef6f99e03188a7a7706361fa8445c754/osu.Framework/Audio/Sample/SampleStore.cs#L65-L72).
In the process it also [unbinds audio
adjustments](https://github.com/ppy/osu-framework/blob/5b716dcbef6f99e03188a7a7706361fa8445c754/osu.Framework/Audio/AudioCollectionManager.cs#L37-L38),
which *would* have the effect of resetting the sample volume to 100%,
effectively (and I've pretty much confirmed that that's what happens).
Now for why this call sometimes does the right thing and sometimes
doesn't: Sometimes the call is made in response to an *actual* change to
the beatmap skin, which is correct and expected, if very indirect, but
sometimes it is made *over-eagerly* when there is no reason to recycle
anything yet.
One such circumstance is entering the setup screen, which will still
"invalidate" (read: remove) the samples, but the compose tab hasn't seen
any change to the beatmap skin, so when it is returned to, it has no
reason to retrieve the sample again, and as such it will try to play
samples which are for better or worse in a completely undefined state
because they're not supposed to be *in use* anymore.
Therefore, the right thing here would seem to be to take the
responsibility of invalidation from a random component, and move it to a
place that's *actually* correlated to every other component needing to
recycle samples, e.g. `EditorBeatmapSkin` responding to changes in the
beatmap resources via raising `BeatmapSkinChanged`.
Unfortunately, because of the structure of everything, this recycle
needs to go from targeted to individual samples, to nuking the entire
store. The reason for this is that `RealmBackedResourceStore` does not
provide information as to *what* resources changed, it just says that
*the set of them* did.
For the recycle to be smarter, `EditorBeatmapSkin` would need to know
not only which samples were added or replaced, but also which ones were
*removed*, so that users don't hear phantom samples that no longer exist
in the editor later. That would however be a lot of hassle for nothing,
so I just recycle everything here and hope it won't matter.
As to why I could only reproduce this on this one beatmap - I'm not
super sure. The failure does not seem to be specific to beatmaps, but it
may be exacerbated by certain patterns of accessing samples which means
that beatmaps with high BPM like the one I managed to reproduce this on
may just be more susceptible to this.
As a final note, I do realise that this is not fundamentally improving
the surrounding systems and it's still a pretty rickety thing to do.
It's still on the consumers to know and respond to the sample store
recycle and this is likely to fail if a consumer ever doesn't. That
said, I have no brighter ideas at this point in time that won't involve
me spending a week refactoring audio.
Closes https://github.com/ppy/osu/issues/36490.
While I'm out here already taking heat for deleting mod combinations let
me do more of that.
The main problem with the mod combination here, again, is that the
application of the mods does not commute.
- The current behaviour is that TP is applied first, then DA. This is,
again, "enforced" by the mod select overlay implicitly enforcing order
of the mod instances in the global mod bindable to match the display
order of mods inside it.
Even this doesn't "work" correctly as is, because as the bug reporter
points out, if they throw on DA with no changes expecting the map's
default AR to be applied, it still gets halved. This is because DA works
in a way wherein if you don't touch the AR slider, DA does not touch AR.
Which means that the DA slider should *really* be at *half* of the map's
base AR by default in this case because TP is active. How do you program
this?
- The *alternative* behaviour would be that DA is applied first, then
TP. This in turn would mean that the effective range of AR adjustment
offered by DA when TP is active would be halved to [0, 5] (or [-5, 5.5]
with extended ranges). How do you program this?
The above is just client-side concerns, while leaving out the other
giant concern, which is "how do you get every single place in the game
that may want to apply mods to a beatmap to apply them *in the same
order*?". Then extend that to server-side components, then extend that
to every external library that may want to re-implement SR/PP
calculations, etc. etc.
One additional remark:
What the bug reporter *did not* say however, but I am saying, is that
there's an elephant in the room, and that is the Easy mod, which *also*
changes AR, but also *happens* to apply commutatively with Target
Practice simply because both mods are implemented to halve the AR, which
means that the order of application doesn't matter. If I were *really*
bent on being a bad guy and just deleting mod combinations
indiscriminately, I'd delete that one as well. But I'm not doing that.
Effectively closing https://github.com/ppy/osu/issues/21471 as a
wontfix.
The issue is demonstrated by the following test case:
<details>
<summary>patch</summary>
```diff
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
index 31498295da..36b4fe5122 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFreezeFrame.cs
@@ -55,5 +55,49 @@ public void TestSkipToFirstSpinnerNotSuppressed()
PassCondition = () => Player.GameplayClockContainer.GameplayStartTime > 0
});
}
+
+ [Test]
+ public void TestFreezeFrameAppliedBeforeHidden()
+ {
+ CreateModTest(new ModTestData
+ {
+ Mods =
+ [
+ new OsuModFreezeFrame(),
+ new OsuModHidden(),
+ ],
+ CreateBeatmap = () => new OsuBeatmap
+ {
+ HitObjects =
+ {
+ new HitCircle { StartTime = 3000, Position = OsuPlayfield.BASE_SIZE / 2, NewCombo = true },
+ new HitCircle { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 },
+ }
+ },
+ PassCondition = () => ((HitCircle)Player.GameplayState.Beatmap.HitObjects[1]).TimeFadeIn == 480
+ });
+ }
+
+ [Test]
+ public void TestFreezeFrameAppliedAfterHidden()
+ {
+ CreateModTest(new ModTestData
+ {
+ Mods =
+ [
+ new OsuModHidden(),
+ new OsuModFreezeFrame(),
+ ],
+ CreateBeatmap = () => new OsuBeatmap
+ {
+ HitObjects =
+ {
+ new HitCircle { StartTime = 3000, Position = OsuPlayfield.BASE_SIZE / 2, NewCombo = true },
+ new HitCircle { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 },
+ }
+ },
+ PassCondition = () => ((HitCircle)Player.GameplayState.Beatmap.HitObjects[1]).TimeFadeIn == 480
+ });
+ }
}
}
```
</details>
The reason that this is happening is a data dependency. Freeze Frame
modifies `TimePreempt` of hitobjects:
https://github.com/ppy/osu/blob/54c0b2c20c3209f1f17be81b8883ef66ec8a83bb/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs#L53
while Hidden uses `TimePreempt` to set `TimeFadeIn`:
https://github.com/ppy/osu/blob/54c0b2c20c3209f1f17be81b8883ef66ec8a83bb/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs#L45
Therefore the final value of `TimeFadeIn` with these two mods active
depends on the order of application.
The reason why I'm bothering to do this is that I was pinged from the PP
development server about this again in the context of some ongoing
'rework' and I wish to get ahead of this before it becomes a bigger
problem than it already is.
The current order of application of these mods as done in `Player` is
constant, but essentially undefined. I'm not even sure what even
enforces the current order. It's currently Hidden, then Freeze Frame. If
I were to guess, the thing "enforcing" (insert 400 tonne solid lead air
quotes) it is probably the mod select overlay with its "I need to own
all of the mod instances in the global bindable" substitution logic.
I'm already getting pushback for this from the PP server crowd who are
attempting to justify the current "behaviour" by saying that the player
base wants this or by saying that it's already broken so it should
remain that way forever. I am however not willing to accept
[stable-taiko-tier stupidity
again](https://github.com/ppy/osu/pull/27136#issuecomment-1957055402)
and am not willing to accept the complexity this would invite everywhere
else.
I do not see any other easy way of fixing this problem at this point in
time. I had hoped that I could inline the `TimePreempt` read in
`OsuModHidden`, but it's not that easy because it's set in multiple
places.
Existing HDFF scores would probably need to be delisted from the
leaderboards. I'm not even sure I can get myself to pretend to care.
Removes the previous `AppIcon.appiconset` bundle and replaces it with
all of the necessary asset slices to enable all of the Liquid Glass
variants of the app icon on iOS 26, while also preserving backwards
compatibility with older iOS versions.
<img width="1164" height="388" alt="LiquidGlass"
src="https://github.com/user-attachments/assets/7d6e7a90-3fe5-4853-9c63-35144359f49e"
/>
---------
Co-authored-by: Dean Herbert <pe@ppy.sh>
This removes the disabled sound, but I think that's fine. If we want
that, it should be handled by `HoverClickSounds` (which I'm currently
intentionally not using because it can be a bit noisy).
Closes#36503.
This was attempted to be fixed by frenzibyte using some hack workaround
logic, but this is the true fix.
Things were never matching due to `UpdateSize` spamming `Resize`
transforms every frame, causing the fade out to complete before
transforms have reached a final state.