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

@ -15,11 +15,10 @@ public sealed class GameWindowSlice8BoundaryTests
AssertAppearsInOrder(
body,
"startupStore.LoadDisplay()",
"QualitySettings.WithEnvOverrides(startupBase)",
"_displayFramePacing.InitializeStartup(startupDisplay.VSync)",
"RuntimeSettingsSnapshot startup = _runtimeSettings.Startup",
"_displayFramePacing.InitializeStartup(startup.Display.VSync)",
"VSync = startupPacing.UseVSync",
"Samples = startupQuality.MsaaSamples",
"Samples = startup.Quality.MsaaSamples",
"Window.Create(options)",
"_displayFramePacing.BindSurface(",
"_windowCallbacks = SilkWindowCallbackBinding.Create(",
@ -66,7 +65,8 @@ public sealed class GameWindowSlice8BoundaryTests
"_cameraPointerInput = AcDream.App.Input.CameraPointerInputController.Create(",
"_cameraPointerInput.AttachRaw();",
"_dats = RuntimeDatCollectionFactory.OpenReadOnly(_datDir);",
"LoadAndApplyPersistedSettings();",
"_runtimeSettings.ApplyStartup(",
"new RuntimeSettingsStartupTargets(",
"_uiHost = new AcDream.App.UI.UiHost(",
"_uiHost.WireMouse(m)",
"_uiHost.WireKeyboard(kb)",
@ -192,7 +192,7 @@ public sealed class GameWindowSlice8BoundaryTests
"private AcDream.App.Net.LiveSessionHost");
string body = MethodBody(
"private void OnFramebufferResize(",
"private void ApplyDisplayWindowState(");
"private uint? PickWorldGuidAtCursor(");
Assert.Contains("=> _framebufferResize.Resize(newSize);", body, StringComparison.Ordinal);
Assert.DoesNotContain("Viewport(", body, StringComparison.Ordinal);
@ -209,6 +209,68 @@ public sealed class GameWindowSlice8BoundaryTests
StringComparison.Ordinal);
}
[Fact]
public void RuntimeSettings_IsOneTwoPhaseOwnerWithoutWindowStateMirrors()
{
string source = GameWindowSource();
string run = MethodBody("public void Run()", "private void OnLoad()");
string load = MethodBody(
"private void OnLoad()",
"private AcDream.App.Net.LiveSessionHost");
string shutdown = MethodBody(
"private ResourceShutdownTransaction CreateShutdownTransaction()",
"private void OnFocusChanged(bool focused)");
Assert.Contains(
"private readonly RuntimeSettingsController _runtimeSettings;",
source,
StringComparison.Ordinal);
Assert.Contains(
"_runtimeSettings = new RuntimeSettingsController(",
source,
StringComparison.Ordinal);
Assert.DoesNotContain("new SettingsStore(", source, StringComparison.Ordinal);
Assert.DoesNotContain("new AcDream.UI.Abstractions.Panels.Settings.SettingsStore(", source, StringComparison.Ordinal);
Assert.DoesNotContain(".LoadDisplay()", source, StringComparison.Ordinal);
Assert.DoesNotContain(".LoadAudio()", source, StringComparison.Ordinal);
Assert.DoesNotContain(".LoadGameplay()", source, StringComparison.Ordinal);
Assert.DoesNotContain(".LoadChat()", source, StringComparison.Ordinal);
Assert.DoesNotContain(".LoadCharacter(", source, StringComparison.Ordinal);
Assert.DoesNotContain("_persistedDisplay", source, StringComparison.Ordinal);
Assert.DoesNotContain("_persistedAudio", source, StringComparison.Ordinal);
Assert.DoesNotContain("_persistedGameplay", source, StringComparison.Ordinal);
Assert.DoesNotContain("_persistedChat", source, StringComparison.Ordinal);
Assert.DoesNotContain("_persistedCharacter", source, StringComparison.Ordinal);
Assert.DoesNotContain("_activeToonKey", source, StringComparison.Ordinal);
Assert.DoesNotContain("_settingsStore", source, StringComparison.Ordinal);
Assert.DoesNotContain("_settingsVm", source, StringComparison.Ordinal);
Assert.DoesNotContain("LoadAndApplyPersistedSettings", source, StringComparison.Ordinal);
Assert.DoesNotContain("private void ApplyDisplayWindowState", source, StringComparison.Ordinal);
Assert.DoesNotContain("public void ReapplyQualityPreset", source, StringComparison.Ordinal);
AssertAppearsInOrder(
run,
"RuntimeSettingsSnapshot startup = _runtimeSettings.Startup",
"_displayFramePacing.InitializeStartup(startup.Display.VSync)",
"Samples = startup.Quality.MsaaSamples",
"Window.Create(options)");
AssertAppearsInOrder(
load,
"_runtimeSettings.ApplyStartup(",
"if (DevToolsEnabled)",
"TerrainAtlas.Build(",
"_runtimeSettings.BindRuntimeTargets(",
"_liveSessionHost.Start(_options)");
AssertAppearsInOrder(
shutdown,
"_runtimeSettings.UnbindViewModel()",
"_runtimeSettings.UnbindRuntimeTargets",
"_retailUiRuntime?.Dispose()",
"_streamer?.Dispose()",
"_wbDrawDispatcher?.Dispose()",
"_terrain?.Dispose()");
}
[Fact]
public void UpdateRenderFocusAndCloseRemainNarrowHostEdges()
{

View file

@ -152,7 +152,7 @@ public sealed class PrivatePresentationRendererTests
typeof(AcDream.UI.Abstractions.Panels.Debug.DebugVM),
FieldTypes(typeof(DevToolsPanelSet)));
Assert.Contains(
typeof(AcDream.UI.Abstractions.Panels.Settings.SettingsVM),
typeof(AcDream.App.Settings.IRuntimeSettingsPreviewSource),
FieldTypes(typeof(RuntimeWorldFrameSettingsPreview)));
Assert.Contains(
typeof(AcDream.App.Streaming.LocalPlayerTeleportController),

File diff suppressed because it is too large Load diff

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;
}
}

View file

@ -434,6 +434,44 @@ public sealed class SettingsVMTests
Assert.True(vm.HasUnsavedChanges);
}
[Fact]
public void ApplyExternalGameplayChange_updates_both_snapshots_and_preserves_other_drafts()
{
var (vm, _, _, _, _, _, _, _, _, _) = Build();
bool persistedAutoTarget = !GameplaySettings.Default.AutoTarget;
vm.SetGameplay(vm.GameplayDraft with
{
ShowTooltips = !GameplaySettings.Default.ShowTooltips,
CoordinatesOnRadar = !GameplaySettings.Default.CoordinatesOnRadar,
});
vm.ApplyExternalGameplayChange(gameplay => gameplay with
{
AutoTarget = persistedAutoTarget,
});
Assert.Equal(persistedAutoTarget, vm.GameplayDraft.AutoTarget);
Assert.Equal(
!GameplaySettings.Default.ShowTooltips,
vm.GameplayDraft.ShowTooltips);
Assert.Equal(
!GameplaySettings.Default.CoordinatesOnRadar,
vm.GameplayDraft.CoordinatesOnRadar);
Assert.True(vm.HasUnsavedChanges);
vm.Cancel();
Assert.Equal(persistedAutoTarget, vm.GameplayDraft.AutoTarget);
Assert.Equal(
GameplaySettings.Default.ShowTooltips,
vm.GameplayDraft.ShowTooltips);
Assert.Equal(
GameplaySettings.Default.CoordinatesOnRadar,
vm.GameplayDraft.CoordinatesOnRadar);
Assert.False(vm.HasUnsavedChanges);
}
[Fact]
public void Save_invokes_gameplay_callback_with_draft()
{