refactor(settings): OP9 — retire the dead F11 settings surface + fully-superseded GameplaySettings

Campaign OP slice OP9 code retirement (docs/plans/2026-08-10-options-panel-campaign.md
§OP9). The retail four-tab Options panel (OP1-OP8) is now acdream's one
in-client settings surface (D1) — this commit deletes the pre-retail-UI
surfaces it fully superseded. Pure retirement: no behavior change to
anything live, verified by dispositioning every one of the 15 src files
and 8 test files that referenced the deleted types before touching any
of them.

1. SettingsVM + SettingsPanel (the old F11 IPanel surface, unrendered
   since Campaign V slice V11's ImGui deletion) deleted outright, plus
   their two dedicated test files. IPanel/IPanelRenderer stay — ChatPanel/
   DebugPanel/VitalsPanel still implement IPanel, so the contract does NOT
   become unreferenced.

2. RuntimeSettingsController's SettingsVM binding seam deleted:
   CreateViewModel, CreateViewModelBinding, RuntimeSettingsViewModelBinding,
   the _viewModel field, UnbindViewModel, and every _viewModel? notification
   call (ToggleFrameRate, LoadCharacterContext, RestoreDefaultCharacterContext,
   SyncChatFromServerOptions). CreateViewModelBinding had zero production
   callers (test-only). HasDraftPreview/DisplayPreview/AudioPreview stay on
   IRuntimeSettingsPreviewSource (WorldRenderFrameBuilder and
   SettingsParticleRangeSource still consume it) but now trivially mirror
   the committed Display/Audio snapshot — HasDraftPreview was already
   always false in production. GameWindowLifetime's matching
   "settings view model" shutdown stage is deleted.

3. IDevToolsGameplayCommands + DevToolsGameplayCommands deleted from
   GameplayInputCommandController.cs. All three members were dead:
   ToggleSettingsPanel() had ZERO dispatch sites (ToggleOptionsPanel always
   routed to _retained, never to _devTools); ToggleDebugPanel()/
   FocusChatInput() had dispatch sites (F1/Ctrl+F1, Tab) but empty no-op
   bodies (their ImGui DebugPanel/ChatPanel targets were already gone).
   The two live dispatch sites are kept as inert `return true;` cases
   (still consuming the key, matching the prior no-op's "handled"
   contract) instead of falling through to a lower-priority scope.
   SessionPlayerComposition.cs's `new DevToolsGameplayCommands()`
   construction is removed. No `ToggleSettingsPanel` InputAction exists
   (only `ToggleOptionsPanel`, rebound at OP3) — nothing to remove there
   or from KeyBindings.RetailDefaults()/keymap fixtures.

4. GameplaySettings deleted entirely (the type, SettingsStore's
   LoadGameplay/SaveGameplay/BuildGameplayObject, RuntimeSettingsController's
   Gameplay property/SetAcceptLootPermits). Verified all 13 remaining
   members (ToggleRun, AdvancedCombatUI, ShowTooltips,
   VividTargetingIndicator, SideBySideVitals, CoordinatesOnRadar,
   SpellDuration, AllowGive, ShowHelm, ShowCloak, LockUI, UseMouseTurning,
   AcceptLootPermits — the three combat ones already died at OP4/AP-196)
   were ALREADY bound through CharacterOptionTable/
   CharacterOptionsPageController's server-bit seam at OP4 before deleting
   the client-local mirror — no (c)-case genuinely-client-local member
   was found; disposition (b) covers 100% of the surface. SetUiLocked
   rewritten to compare only against the last value actually pushed to
   _runtimeTargets (MUST-FIX 4's guard), with no second store left to
   read or write. LiveSessionRuntimeFactory's SetAcceptLootPermits binding
   now sends the wire option only (the GameplaySettings write-behind call
   removed as dead output). CharacterSettings/DisplaySettings/
   AudioSettings/ChatSettings and their SettingsStore Load/Save surfaces
   are UNTOUCHED per the campaign contract.

   Per-file disposition (15 src + 8 test files that referenced
   GameplaySettings before this commit):
   - GameplaySettings.cs, SettingsVM.cs, SettingsPanel.cs: the types
     themselves — deleted.
   - SettingsStore.cs, RuntimeSettingsController.cs,
     LiveSessionRuntimeFactory.cs: real usage — API deleted/rewritten.
   - RetailUiRuntime.cs, InteractionRetainedUiComposition.cs,
     SessionPlayerComposition.cs, CombatUiController.cs,
     LiveCombatAttackOperations.cs, LivePresentationComposition.cs,
     FrameRootComposition.cs, RuntimeCharacterState.cs,
     CombatCameraTargetSource.cs: doc-comment-only or interface-name
     substring matches (ICombatGameplaySettingsSource) — left as accurate
     historical record, no forward reference to the deleted type.
   - Tests: RuntimeSettingsControllerTests.cs and SettingsStoreTests.cs
     rewritten (Gameplay-specific tests deleted; SaveDisplay/SaveAudio/
     SaveChat tests re-targeted off the now-public methods instead of the
     retired SettingsVM draft/Save() indirection); GameplaySettingsTests.cs/
     SettingsVMTests.cs/SettingsPanelTests.cs deleted; the remaining three
     (CharacterOptionCombatSettingsSourceTests.cs,
     CombatCameraTargetSourceTests.cs, LiveCombatAttackOperationsTests.cs)
     were comment/interface-name-only, untouched.

5. Register: AP-196 (OP4's partial GameplaySettings retirement, which left
   five fields as write-behind mirrors) is fully retired now that the
   record is gone outright — marked ~~AP-196~~ RETIRED with its retirement
   note, active-row count 143 -> 142. No other row cited the deleted types
   directly (AP-194/AP-193 cite CharacterOptionTable.cs, not
   GameplaySettings.cs).

6. Settings.json migration honesty: SettingsStore no longer reads or
   writes the "gameplay" top-level key, so an existing file carrying one
   from a pre-OP9 build is neither parsed nor dropped — the existing
   SaveSection raw-JSON-text preservation mechanism (unknown top-level
   keys survive every subsequent save) carries it forward untouched.
   Two new targeted tests
   (LeftoverGameplaySection_FromAnOlderSettingsJson_DoesNotBreakOtherLoads,
   LeftoverGameplaySection_SurvivesAnUnrelatedSave) pin this.

InputAction.ToggleOptionsPanel's stale doc comment (still describing the
retired ImGui SettingsPanel) and a handful of other dangling doc
references (DisplaySettings.cs, ChatOpacityLink.cs,
SettingsDevToolsComposition.cs, InputDispatcherCaptureTests.cs) are
reworded to point at the current retail Options panel / OP8
KeyboardConfigController.

Build: dotnet build -c Release green, 0 errors. Tests: dotnet test -c
Release --no-build — 13,075 passed / 4 skipped / 0 failed (13,079 total),
down from the stated baseline of 13,155 passed / 4 skipped / 0 failed
(13,159 total) — the -80 delta is exactly the deleted SettingsVM/
SettingsPanel/GameplaySettings test surface (three whole files plus the
Gameplay-specific cases trimmed from RuntimeSettingsControllerTests.cs/
SettingsStoreTests.cs), with zero regressions elsewhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-11 13:18:53 +02:00
parent 1a57f96efe
commit 371197a345
22 changed files with 228 additions and 3260 deletions

View file

@ -206,64 +206,58 @@ public sealed class SettingsStoreTests : System.IDisposable
Assert.True(store.LoadDisplay().ShowFps);
}
// -- Gameplay section round-trip --------------------------------------
// -- Gameplay section: OP9 migration honesty ---------------------------
//
// Campaign OP slice OP9 (docs/plans/2026-08-10-options-panel-campaign.md
// §OP9) retired the Gameplay tab's client-local GameplaySettings record
// and Load/SaveGameplay outright — all 13 fields were already re-pointed
// to the server-bit seam (RuntimeCharacterOptionsState) at OP4. An
// EXISTING settings.json written by a pre-OP9 build can still carry a
// "gameplay" top-level block; these two tests pin that SettingsStore
// tolerates it (no Load* method reads it, so it neither throws nor is
// silently dropped) rather than requiring a migration step.
[Fact]
public void LoadGameplay_returns_defaults_when_file_is_missing()
{
var store = new SettingsStore(_tempPath);
Assert.Equal(GameplaySettings.Default, store.LoadGameplay());
}
[Fact]
public void SaveGameplay_then_LoadGameplay_round_trips_all_fields()
{
var store = new SettingsStore(_tempPath);
var original = GameplaySettings.Default with
{
AdvancedCombatUI = true,
ShowHelm = false,
LockUI = true,
UseMouseTurning = true,
AcceptLootPermits = false,
};
store.SaveGameplay(original);
var loaded = store.LoadGameplay();
Assert.Equal(original, loaded);
}
[Fact]
public void LoadGameplay_falls_back_per_field_when_keys_missing()
public void LeftoverGameplaySection_FromAnOlderSettingsJson_DoesNotBreakOtherLoads()
{
File.WriteAllText(_tempPath, """
{
"version": 1,
"gameplay": { "lockUI": true }
"version": 2,
"display": { "resolution": "1366x768" },
"gameplay": { "lockUI": true, "toggleRun": false }
}
""");
var store = new SettingsStore(_tempPath);
var loaded = store.LoadGameplay();
Assert.True(loaded.LockUI);
Assert.Equal(GameplaySettings.Default.ToggleRun, loaded.ToggleRun);
Assert.Equal(GameplaySettings.Default.ShowHelm, loaded.ShowHelm);
Assert.Equal("1366x768", store.LoadDisplay().Resolution);
Assert.Equal(AudioSettings.Default, store.LoadAudio());
Assert.Equal(ChatSettings.Default, store.LoadChat());
}
[Fact]
public void All_three_sections_coexist_in_one_settings_json()
public void LeftoverGameplaySection_SurvivesAnUnrelatedSave()
{
// The generic SaveSection round-trip preservation (raw-JSON-text
// carry-forward of unknown top-level keys, exercised elsewhere by
// SaveDisplay_preserves_unknown_top_level_keys) is exactly what
// keeps an orphaned "gameplay" block alive forever once nothing
// reads or writes it — this pins that mechanism for the specific
// section OP9 just retired.
File.WriteAllText(_tempPath, """
{
"version": 2,
"gameplay": { "lockUI": true, "showHelm": false }
}
""");
var store = new SettingsStore(_tempPath);
store.SaveDisplay(DisplaySettings.Default with { Resolution = "2560x1440" });
store.SaveAudio(AudioSettings.Default with { Master = 0.5f });
store.SaveGameplay(GameplaySettings.Default with { LockUI = true });
// All three load correctly from the same file.
Assert.Equal("2560x1440", store.LoadDisplay().Resolution);
Assert.Equal(0.5f, store.LoadAudio().Master);
Assert.True(store.LoadGameplay().LockUI);
store.SaveDisplay(DisplaySettings.Default with { Resolution = "1920x1080" });
var raw = File.ReadAllText(_tempPath);
Assert.Contains("\"gameplay\"", raw);
Assert.Contains("\"lockUI\": true", raw);
Assert.Contains("\"showHelm\": false", raw);
Assert.Contains("1920x1080", raw);
}
// -- Chat section round-trip ------------------------------------------
@ -387,17 +381,15 @@ public sealed class SettingsStoreTests : System.IDisposable
}
[Fact]
public void All_four_sections_coexist_in_one_settings_json()
public void All_three_sections_coexist_in_one_settings_json()
{
var store = new SettingsStore(_tempPath);
store.SaveDisplay(DisplaySettings.Default with { Resolution = "2560x1440" });
store.SaveAudio(AudioSettings.Default with { Master = 0.5f });
store.SaveGameplay(GameplaySettings.Default with { LockUI = true });
store.SaveChat(ChatSettings.Default with { HearTradeChat = false, FontSize = 14f });
Assert.Equal("2560x1440", store.LoadDisplay().Resolution);
Assert.Equal(0.5f, store.LoadAudio().Master);
Assert.True(store.LoadGameplay().LockUI);
Assert.False(store.LoadChat().HearTradeChat);
Assert.Equal(14f, store.LoadChat().FontSize);
}
@ -468,19 +460,17 @@ public sealed class SettingsStoreTests : System.IDisposable
}
[Fact]
public void All_five_sections_coexist_in_one_settings_json()
public void All_four_sections_coexist_in_one_settings_json()
{
var store = new SettingsStore(_tempPath);
store.SaveDisplay(DisplaySettings.Default with { Resolution = "2560x1440" });
store.SaveAudio(AudioSettings.Default with { Master = 0.5f });
store.SaveGameplay(GameplaySettings.Default with { LockUI = true });
store.SaveChat(ChatSettings.Default with { HearTradeChat = false });
store.SaveCharacter("+Acdream",
CharacterSettings.Default with { DefaultChatChannel = "Fellowship" });
Assert.Equal("2560x1440", store.LoadDisplay().Resolution);
Assert.Equal(0.5f, store.LoadAudio().Master);
Assert.True(store.LoadGameplay().LockUI);
Assert.False(store.LoadChat().HearTradeChat);
Assert.Equal("Fellowship", store.LoadCharacter("+Acdream").DefaultChatChannel);
}
@ -489,7 +479,7 @@ public sealed class SettingsStoreTests : System.IDisposable
public void WindowPositions_RoundTripPerCharacterAndPreserveSettings()
{
var store = new SettingsStore(_tempPath);
store.SaveGameplay(GameplaySettings.Default with { LockUI = true });
store.SaveDisplay(DisplaySettings.Default with { Resolution = "2560x1440" });
store.SaveWindowPosition("Alice", "radar", new UiWindowPosition(321.5f, 18f));
store.SaveWindowPosition("Bob", "radar", new UiWindowPosition(44f, 55f));
@ -498,14 +488,14 @@ public sealed class SettingsStoreTests : System.IDisposable
Assert.Equal(new UiWindowPosition(44f, 55f),
store.LoadWindowPosition("Bob", "radar"));
Assert.Null(store.LoadWindowPosition("Alice", "inventory"));
Assert.True(store.LoadGameplay().LockUI);
Assert.Equal("2560x1440", store.LoadDisplay().Resolution);
}
[Fact]
public void WindowLayouts_RoundTripPerCharacterResolutionAndPreserveSettings()
{
var store = new SettingsStore(_tempPath);
store.SaveGameplay(GameplaySettings.Default with { LockUI = true });
store.SaveDisplay(DisplaySettings.Default with { Resolution = "2560x1440" });
var alice1080 = new UiWindowLayout(10f, 20f, 500f, 300f, true, false, true);
var alice1440 = new UiWindowLayout(30f, 40f, 650f, 420f, false, true, false);
var bob1080 = new UiWindowLayout(50f, 60f, 310f, 132f, true, false, false);
@ -518,7 +508,7 @@ public sealed class SettingsStoreTests : System.IDisposable
Assert.Equal(alice1440, store.LoadWindowLayout("Alice", "2560x1440", "chat", default));
Assert.Equal(bob1080, store.LoadWindowLayout("Bob", "1920x1080", "toolbar", default));
Assert.Null(store.LoadWindowLayout("Alice", "1920x1080", "inventory", default));
Assert.True(store.LoadGameplay().LockUI);
Assert.Equal("2560x1440", store.LoadDisplay().Resolution);
}
[Fact]