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>
243 lines
8.7 KiB
C#
243 lines
8.7 KiB
C#
using AcDream.App.Combat;
|
|
using AcDream.App.Diagnostics;
|
|
using AcDream.App.Input;
|
|
using AcDream.Runtime;
|
|
using AcDream.Runtime.Gameplay;
|
|
using AcDream.UI.Abstractions.Input;
|
|
|
|
namespace AcDream.App.Tests.Input;
|
|
|
|
public sealed class GameplayInputCommandControllerTests
|
|
{
|
|
[Theory]
|
|
[InlineData(InputAction.ToggleInventoryPanel, "inventory")]
|
|
[InlineData(InputAction.AcdreamToggleFlyMode, "fly-or-chase")]
|
|
[InlineData(InputAction.AcdreamTogglePlayerMode, "player-mode")]
|
|
// Campaign OP slice OP3 (D1): F11 now opens the retail Options panel
|
|
// through IRetainedGameplayWindowCommands, not the OP9-retired
|
|
// IDevToolsGameplayCommands.ToggleSettingsPanel() no-op (which was
|
|
// never dispatched from this switch in the first place).
|
|
[InlineData(InputAction.ToggleOptionsPanel, "options")]
|
|
[InlineData(InputAction.CombatToggleCombat, "combat")]
|
|
[InlineData(InputAction.ToggleFloatingChatWindow1, "chat-window-1")]
|
|
[InlineData(InputAction.ToggleFloatingChatWindow2, "chat-window-2")]
|
|
[InlineData(InputAction.ToggleFloatingChatWindow3, "chat-window-3")]
|
|
[InlineData(InputAction.ToggleFloatingChatWindow4, "chat-window-4")]
|
|
public void RecognizedCommand_RoutesToTypedOwner(
|
|
InputAction action,
|
|
string expected)
|
|
{
|
|
var harness = new Harness();
|
|
|
|
bool handled = harness.Controller.Handle(action);
|
|
|
|
Assert.True(handled);
|
|
Assert.Equal([expected], harness.Calls);
|
|
}
|
|
|
|
// OP9: AcdreamToggleDebugPanel/ToggleChatEntry retired the
|
|
// IDevToolsGameplayCommands seam they used to forward to — both
|
|
// targets (the ImGui-era DebugPanel/ChatPanel) were already gone
|
|
// (Campaign V slice V11), so the seam's own body was an unconditional
|
|
// no-op. The action is still consumed (handled == true, matching the
|
|
// prior no-op's contract) but claims no typed-owner call.
|
|
[Theory]
|
|
[InlineData(InputAction.AcdreamToggleDebugPanel)]
|
|
[InlineData(InputAction.ToggleChatEntry)]
|
|
public void RetiredDevToolsCommand_IsConsumedWithoutClaimingATypedOwner(
|
|
InputAction action)
|
|
{
|
|
var harness = new Harness();
|
|
|
|
bool handled = harness.Controller.Handle(action);
|
|
|
|
Assert.True(handled);
|
|
Assert.Empty(harness.Calls);
|
|
}
|
|
|
|
[Fact]
|
|
public void DiagnosticCommand_PrecedesRemainingCommandSwitch()
|
|
{
|
|
var harness = new Harness();
|
|
harness.Diagnostics.HandledAction = InputAction.AcdreamToggleDebugPanel;
|
|
|
|
bool handled = harness.Controller.Handle(
|
|
InputAction.AcdreamToggleDebugPanel);
|
|
|
|
Assert.True(handled);
|
|
Assert.Equal(["diagnostic"], harness.Calls);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnknownCommand_IsNotClaimed()
|
|
{
|
|
var harness = new Harness();
|
|
|
|
bool handled = harness.Controller.Handle(InputAction.MovementForward);
|
|
|
|
Assert.False(handled);
|
|
Assert.Empty(harness.Calls);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(true, true, true, "cancel-target")]
|
|
[InlineData(false, true, true, "exit-fly")]
|
|
[InlineData(false, false, true, "exit-player")]
|
|
[InlineData(false, false, false, "close")]
|
|
public void Escape_PreservesTargetFlyPlayerWindowPriority(
|
|
bool targetMode,
|
|
bool flyMode,
|
|
bool playerMode,
|
|
string expected)
|
|
{
|
|
var harness = new Harness
|
|
{
|
|
TargetMode = { IsActive = targetMode },
|
|
Camera = { IsFly = flyMode },
|
|
Player = { IsPlayer = playerMode },
|
|
};
|
|
|
|
bool handled = harness.Controller.Handle(InputAction.EscapeKey);
|
|
|
|
Assert.True(handled);
|
|
Assert.Equal([expected], harness.Calls);
|
|
}
|
|
|
|
private sealed class Harness
|
|
{
|
|
public Harness()
|
|
{
|
|
Retained = new FakeRetained(Calls);
|
|
Diagnostics = new FakeDiagnostics(Calls);
|
|
Player = new FakePlayerMode(Calls);
|
|
TargetMode = new FakeTargetMode(Calls);
|
|
Camera = new FakeCamera(Calls);
|
|
Combat = new FakeCombat(Calls);
|
|
Runtime = new FakeRuntimeView();
|
|
Window = new FakeWindow(Calls);
|
|
Controller = new GameplayInputCommandController(
|
|
Retained,
|
|
Diagnostics,
|
|
Player,
|
|
TargetMode,
|
|
Camera,
|
|
Runtime,
|
|
Combat,
|
|
Window);
|
|
}
|
|
|
|
public List<string> Calls { get; } = [];
|
|
public FakeRetained Retained { get; }
|
|
public FakeDiagnostics Diagnostics { get; }
|
|
public FakePlayerMode Player { get; }
|
|
public FakeTargetMode TargetMode { get; }
|
|
public FakeCamera Camera { get; }
|
|
public FakeCombat Combat { get; }
|
|
public FakeRuntimeView Runtime { get; }
|
|
public FakeWindow Window { get; }
|
|
public GameplayInputCommandController Controller { get; }
|
|
}
|
|
|
|
private sealed class FakeRetained(List<string> calls)
|
|
: IRetainedGameplayWindowCommands
|
|
{
|
|
public void ToggleInventory() => calls.Add("inventory");
|
|
|
|
public void ToggleFloatingChatWindow(int windowId) =>
|
|
calls.Add($"chat-window-{windowId}");
|
|
|
|
public void ToggleOptionsPanel() => calls.Add("options");
|
|
}
|
|
|
|
private sealed class FakeDiagnostics(List<string> calls)
|
|
: IRuntimeDiagnosticCommands
|
|
{
|
|
public InputAction? HandledAction { get; set; }
|
|
|
|
public bool Handle(InputAction action)
|
|
{
|
|
if (action != HandledAction)
|
|
return false;
|
|
calls.Add("diagnostic");
|
|
return true;
|
|
}
|
|
|
|
public void CycleTimeOfDay() => calls.Add("time");
|
|
public void CycleWeather() => calls.Add("weather");
|
|
public void ToggleCollisionWireframes() => calls.Add("collision");
|
|
}
|
|
|
|
private sealed class FakePlayerMode(List<string> calls)
|
|
: IPlayerModeGameplayCommands
|
|
{
|
|
public bool IsPlayer { get; set; }
|
|
public bool IsPlayerMode => IsPlayer;
|
|
public void ToggleFlyOrChase() => calls.Add("fly-or-chase");
|
|
public void TogglePlayerMode() => calls.Add("player-mode");
|
|
public void ExitPlayerMode() => calls.Add("exit-player");
|
|
}
|
|
|
|
private sealed class FakeTargetMode(List<string> calls)
|
|
: IItemTargetModeCommands
|
|
{
|
|
public bool IsActive { get; set; }
|
|
public bool IsAnyTargetModeActive => IsActive;
|
|
public void CancelTargetMode() => calls.Add("cancel-target");
|
|
}
|
|
|
|
private sealed class FakeCamera(List<string> calls)
|
|
: IGameplayCameraModeCommands
|
|
{
|
|
public bool IsFly { get; set; }
|
|
public bool IsFlyMode => IsFly;
|
|
public void ExitFlyMode() => calls.Add("exit-fly");
|
|
}
|
|
|
|
private sealed class FakeCombat(List<string> calls) : IRuntimeCombatCommands
|
|
{
|
|
public RuntimeCommandResult Execute(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
RuntimeCombatCommand command)
|
|
{
|
|
Assert.Equal(RuntimeCombatCommand.ToggleMode, command);
|
|
calls.Add("combat");
|
|
return new RuntimeCommandResult(
|
|
RuntimeCommandStatus.Accepted,
|
|
expectedGeneration);
|
|
}
|
|
|
|
public RuntimeCommandResult ExecuteAttack(
|
|
RuntimeGenerationToken expectedGeneration,
|
|
in RuntimeCombatAttackInput command) =>
|
|
new(
|
|
RuntimeCommandStatus.Unsupported,
|
|
expectedGeneration);
|
|
}
|
|
|
|
private sealed class FakeRuntimeView : IGameRuntimeView
|
|
{
|
|
public RuntimeGenerationToken Generation => new(7);
|
|
public RuntimeLifecycleSnapshot Lifecycle => throw new NotSupportedException();
|
|
public IGameRuntimeClock Clock => throw new NotSupportedException();
|
|
public IRuntimeEntityView Entities => throw new NotSupportedException();
|
|
public IRuntimeInventoryView Inventory => throw new NotSupportedException();
|
|
public IRuntimeInventoryStateView InventoryState =>
|
|
throw new NotSupportedException();
|
|
public IRuntimeCharacterView Character =>
|
|
throw new NotSupportedException();
|
|
public IRuntimeSocialView Social => throw new NotSupportedException();
|
|
public IRuntimeChatView Chat => throw new NotSupportedException();
|
|
public IRuntimeActionView Actions => throw new NotSupportedException();
|
|
public IRuntimeMovementView Movement => throw new NotSupportedException();
|
|
public AcDream.Runtime.World.IRuntimeWorldEnvironmentView Environment =>
|
|
throw new NotSupportedException();
|
|
public IRuntimePortalView Portal => throw new NotSupportedException();
|
|
public RuntimeStateCheckpoint CaptureCheckpoint() =>
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
private sealed class FakeWindow(List<string> calls) : IGameplayWindowCommands
|
|
{
|
|
public void Close() => calls.Add("close");
|
|
}
|
|
}
|