diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
index 4510fda11d..1042341337 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.SongSelect
             AddStep("select unchanged Difficulty Adjust mod", () =>
             {
                 var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull();
-                var difficultyAdjustMod = ruleset.CreateMod<ModDifficultyAdjust>();
+                var difficultyAdjustMod = ruleset.CreateMod<ModDifficultyAdjust>().AsNonNull();
                 difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.Difficulty);
                 SelectedMods.Value = new[] { difficultyAdjustMod };
             });
@@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.SongSelect
             AddStep("select changed Difficulty Adjust mod", () =>
             {
                 var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull();
-                var difficultyAdjustMod = ruleset.CreateMod<OsuModDifficultyAdjust>();
+                var difficultyAdjustMod = ruleset.CreateMod<OsuModDifficultyAdjust>().AsNonNull();
                 var originalDifficulty = advancedStats.BeatmapInfo.Difficulty;
 
                 difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index 64d5639133..7456ce06bd 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -272,7 +272,24 @@ namespace osu.Game.Rulesets.Scoring
         }
 
         /// <summary>
-        /// Computes the total score of a given finalised <see cref="ScoreInfo"/>. This should be used when a score is known to be complete.
+        /// Computes the accuracy of a given <see cref="ScoreInfo"/>.
+        /// </summary>
+        /// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
+        /// <returns>The score's accuracy.</returns>
+        [Pure]
+        public double ComputeAccuracy(ScoreInfo scoreInfo)
+        {
+            if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
+                throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
+
+            // We only extract scoring values from the score's statistics. This is because accuracy is always relative to the point of pass or fail rather than relative to the whole beatmap.
+            extractScoringValues(scoreInfo.Statistics, out var current, out var maximum);
+
+            return maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1;
+        }
+
+        /// <summary>
+        /// Computes the total score of a given <see cref="ScoreInfo"/>.
         /// </summary>
         /// <remarks>
         /// Does not require <see cref="JudgementProcessor.ApplyBeatmap"/> to have been called before use.
diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
index cffbf2c9a1..ad52b4affc 100644
--- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
+++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs
@@ -1,8 +1,6 @@
 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
 // See the LICENCE file in the repository root for full licence text.
 
-#nullable disable
-
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -12,6 +10,7 @@ using osu.Framework.Allocation;
 using osu.Framework.Audio;
 using osu.Framework.Audio.Sample;
 using osu.Framework.Bindables;
+using osu.Framework.Extensions.ObjectExtensions;
 using osu.Framework.Graphics.Rendering;
 using osu.Framework.Graphics.Shaders;
 using osu.Framework.Graphics.Textures;
@@ -44,29 +43,26 @@ namespace osu.Game.Rulesets.UI
         public ShaderManager ShaderManager { get; }
 
         /// <summary>
-        /// The ruleset config manager.
+        /// The ruleset config manager. May be null if ruleset does not expose a configuration manager.
         /// </summary>
-        public IRulesetConfigManager RulesetConfigManager { get; private set; }
+        public IRulesetConfigManager? RulesetConfigManager { get; }
 
         public DrawableRulesetDependencies(Ruleset ruleset, IReadOnlyDependencyContainer parent)
             : base(parent)
         {
             var resources = ruleset.CreateResourceStore();
 
-            if (resources != null)
-            {
-                var host = parent.Get<GameHost>();
+            var host = parent.Get<GameHost>();
 
-                TextureStore = new TextureStore(host.Renderer, parent.Get<GameHost>().CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(resources, @"Textures")));
-                CacheAs(TextureStore = new FallbackTextureStore(host.Renderer, TextureStore, parent.Get<TextureStore>()));
+            TextureStore = new TextureStore(host.Renderer, parent.Get<GameHost>().CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(resources, @"Textures")));
+            CacheAs(TextureStore = new FallbackTextureStore(host.Renderer, TextureStore, parent.Get<TextureStore>()));
 
-                SampleStore = parent.Get<AudioManager>().GetSampleStore(new NamespacedResourceStore<byte[]>(resources, @"Samples"));
-                SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
-                CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get<ISampleStore>()));
+            SampleStore = parent.Get<AudioManager>().GetSampleStore(new NamespacedResourceStore<byte[]>(resources, @"Samples"));
+            SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY;
+            CacheAs(SampleStore = new FallbackSampleStore(SampleStore, parent.Get<ISampleStore>()));
 
-                ShaderManager = new ShaderManager(host.Renderer, new NamespacedResourceStore<byte[]>(resources, @"Shaders"));
-                CacheAs(ShaderManager = new FallbackShaderManager(host.Renderer, ShaderManager, parent.Get<ShaderManager>()));
-            }
+            ShaderManager = new ShaderManager(host.Renderer, new NamespacedResourceStore<byte[]>(resources, @"Shaders"));
+            CacheAs(ShaderManager = new FallbackShaderManager(host.Renderer, ShaderManager, parent.Get<ShaderManager>()));
 
             RulesetConfigManager = parent.Get<IRulesetConfigCache>().GetConfigFor(ruleset);
             if (RulesetConfigManager != null)
@@ -96,10 +92,9 @@ namespace osu.Game.Rulesets.UI
 
             isDisposed = true;
 
-            SampleStore?.Dispose();
-            TextureStore?.Dispose();
-            ShaderManager?.Dispose();
-            RulesetConfigManager = null;
+            if (ShaderManager.IsNotNull()) SampleStore.Dispose();
+            if (TextureStore.IsNotNull()) TextureStore.Dispose();
+            if (ShaderManager.IsNotNull()) ShaderManager.Dispose();
         }
 
         #endregion
@@ -160,7 +155,7 @@ namespace osu.Game.Rulesets.UI
 
             public void Dispose()
             {
-                primary?.Dispose();
+                if (primary.IsNotNull()) primary.Dispose();
             }
         }
 
@@ -185,7 +180,7 @@ namespace osu.Game.Rulesets.UI
             protected override void Dispose(bool disposing)
             {
                 base.Dispose(disposing);
-                primary?.Dispose();
+                if (primary.IsNotNull()) primary.Dispose();
             }
         }
 
@@ -201,12 +196,12 @@ namespace osu.Game.Rulesets.UI
                 this.fallback = fallback;
             }
 
-            public override byte[] LoadRaw(string name) => primary.LoadRaw(name) ?? fallback.LoadRaw(name);
+            public override byte[]? LoadRaw(string name) => primary.LoadRaw(name) ?? fallback.LoadRaw(name);
 
             protected override void Dispose(bool disposing)
             {
                 base.Dispose(disposing);
-                primary?.Dispose();
+                if (primary.IsNotNull()) primary.Dispose();
             }
         }
     }
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs
index 8e79c89685..6e939c3916 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs
@@ -1,6 +1,7 @@
 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
 // See the LICENCE file in the repository root for full licence text.
 
+using System.Threading;
 using osu.Framework.Allocation;
 using osu.Framework.Audio;
 using osu.Game.Beatmaps;
@@ -34,8 +35,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
         }
 
         [BackgroundDependencyLoader]
-        private void load()
+        private void load(CancellationToken cancellationToken)
         {
+            // HUD overlay may not be loaded if load has been cancelled early.
+            if (cancellationToken.IsCancellationRequested)
+                return;
+
             HUDOverlay.PlayerSettingsOverlay.Expire();
             HUDOverlay.HoldToQuit.Expire();
         }
diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs
index d0eb8f8ca1..b3d5066a9e 100644
--- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs
+++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs
@@ -47,7 +47,10 @@ namespace osu.Game.Screens.Play.HUD
             if (clock != null)
                 gameplayClock = clock;
 
-            AutoSizeAxes = Axes.Y;
+            // Lock height so changes in text autosize (if character height changes)
+            // don't cause parent invalidation.
+            Height = 14;
+
             Children = new Drawable[]
             {
                 new Container
diff --git a/osu.Game/Skinning/LegacyJudgementPieceNew.cs b/osu.Game/Skinning/LegacyJudgementPieceNew.cs
index b87fc9acb5..2cb055d8ba 100644
--- a/osu.Game/Skinning/LegacyJudgementPieceNew.cs
+++ b/osu.Game/Skinning/LegacyJudgementPieceNew.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Skinning
             if (result != HitResult.Miss)
             {
                 //new judgement shows old as a temporary effect
-                AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f)
+                AddInternal(temporaryOldStyle = new LegacyJudgementPieceOld(result, createMainDrawable, 1.05f, true)
                 {
                     Blending = BlendingParameters.Additive,
                     Anchor = Anchor.Centre,
diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs
index c47f46ed2b..3f4d13c082 100644
--- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs
+++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs
@@ -18,11 +18,13 @@ namespace osu.Game.Skinning
         private readonly HitResult result;
 
         private readonly float finalScale;
+        private readonly bool forceTransforms;
 
-        public LegacyJudgementPieceOld(HitResult result, Func<Drawable> createMainDrawable, float finalScale = 1f)
+        public LegacyJudgementPieceOld(HitResult result, Func<Drawable> createMainDrawable, float finalScale = 1f, bool forceTransforms = false)
         {
             this.result = result;
             this.finalScale = finalScale;
+            this.forceTransforms = forceTransforms;
 
             AutoSizeAxes = Axes.Both;
             Origin = Anchor.Centre;
@@ -43,8 +45,8 @@ namespace osu.Game.Skinning
             this.FadeInFromZero(fade_in_length);
             this.Delay(fade_out_delay).FadeOut(fade_out_length);
 
-            // legacy judgements don't play any transforms if they are an animation.
-            if (animation?.FrameCount > 1)
+            // legacy judgements don't play any transforms if they are an animation.... UNLESS they are the temporary displayed judgement from new piece.
+            if (animation?.FrameCount > 1 && !forceTransforms)
                 return;
 
             switch (result)