refactor(settings): own two-phase runtime settings

Move pre-window loading, startup application, live settings mutation, toon context, quality reapply, and SettingsVM loans behind one RuntimeSettingsController. Preserve retail command behavior, ordered target publication, draft semantics, and retryable failure convergence while removing duplicate GameWindow state and feature bodies.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 13:30:22 +02:00
parent 4eae9b4f5a
commit fec0d94148
24 changed files with 2379 additions and 599 deletions

View file

@ -1,8 +1,7 @@
using AcDream.App.Settings;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Update;
using AcDream.UI.Abstractions.Input;
using AcDream.UI.Abstractions.Panels.Settings;
using Silk.NET.Input;
namespace AcDream.App.Tests.World;
@ -13,73 +12,45 @@ public sealed class LiveObjectFrameControllerTests
[InlineData(
ParticleRange.Extended,
ParticleVisibilityController.ExtendedRangeMultiplier)]
public void ParticleRange_NullSettingsUsesConfiguredFallback(
ParticleRange fallback,
public void ParticleRange_UsesCurrentSettingsPreview(
ParticleRange range,
float expected)
{
var source = new SettingsParticleRangeSource(null, fallback);
var preview = new FakeSettingsPreview(
DisplaySettings.Default with { ParticleRange = range });
var source = new SettingsParticleRangeSource(preview);
Assert.Equal(expected, source.RangeMultiplier);
}
[Fact]
public void ParticleRange_LiveSettingsDraftOverridesFallbackAndUpdatesImmediately()
public void ParticleRange_UpdatesImmediatelyWhenPreviewChanges()
{
SettingsVM settings = CreateSettings();
var source = new SettingsParticleRangeSource(settings, ParticleRange.Retail);
var preview = new FakeSettingsPreview(DisplaySettings.Default);
var source = new SettingsParticleRangeSource(preview);
settings.SetDisplay(settings.DisplayDraft with { ParticleRange = ParticleRange.Retail });
preview.DisplayPreview = preview.DisplayPreview with
{
ParticleRange = ParticleRange.Retail,
};
Assert.Equal(1f, source.RangeMultiplier);
settings.SetDisplay(settings.DisplayDraft with { ParticleRange = ParticleRange.Extended });
preview.DisplayPreview = preview.DisplayPreview with
{
ParticleRange = ParticleRange.Extended,
};
Assert.Equal(
ParticleVisibilityController.ExtendedRangeMultiplier,
source.RangeMultiplier);
}
private static SettingsVM CreateSettings()
private sealed class FakeSettingsPreview(DisplaySettings display)
: IRuntimeSettingsPreviewSource
{
var dispatcher = InputDispatcher.CreateDetached(
new NullKeyboardSource(),
new NullMouseSource(),
new KeyBindings());
dispatcher.Attach();
return new SettingsVM(
new KeyBindings(),
dispatcher,
static _ => { },
DisplaySettings.Default,
static _ => { },
AudioSettings.Default,
static _ => { },
GameplaySettings.Default,
static _ => { },
ChatSettings.Default,
static _ => { },
CharacterSettings.Default,
static _ => { });
}
public bool HasDraftPreview => true;
private sealed class NullKeyboardSource : IKeyboardSource
{
#pragma warning disable CS0067
public event Action<Key, ModifierMask>? KeyDown;
public event Action<Key, ModifierMask>? KeyUp;
#pragma warning restore CS0067
public bool IsHeld(Key key) => false;
public ModifierMask CurrentModifiers => ModifierMask.None;
}
public DisplaySettings DisplayPreview { get; set; } = display;
private sealed class NullMouseSource : IMouseSource
{
#pragma warning disable CS0067
public event Action<MouseButton, ModifierMask>? MouseDown;
public event Action<MouseButton, ModifierMask>? MouseUp;
public event Action<float, float>? MouseMove;
public event Action<float>? Scroll;
#pragma warning restore CS0067
public bool IsHeld(MouseButton button) => false;
public bool WantCaptureMouse => false;
public bool WantCaptureKeyboard => false;
public AudioSettings AudioPreview => AudioSettings.Default;
}
}