fix(client): restore retail interaction parity
Harden keyboard and camera routing, inventory and vendor interactions, chat/emotes, relog portal flow, and paperdoll rendering. Add retail research, connected gate coverage, and release-gate validation.
This commit is contained in:
parent
0c699240e0
commit
f6fe0f2a4f
151 changed files with 10162 additions and 1211 deletions
|
|
@ -23,6 +23,9 @@ public sealed class GameplayInputCommandControllerTests
|
|||
[InlineData(InputAction.ToggleFloatingChatWindow2, "chat-window-2")]
|
||||
[InlineData(InputAction.ToggleFloatingChatWindow3, "chat-window-3")]
|
||||
[InlineData(InputAction.ToggleFloatingChatWindow4, "chat-window-4")]
|
||||
[InlineData(InputAction.ToggleChatEntry, "focus-chat")]
|
||||
[InlineData(InputAction.EnterChatMode, "focus-chat")]
|
||||
[InlineData(InputAction.LOGOUT, "logout")]
|
||||
public void RecognizedCommand_RoutesToTypedOwner(
|
||||
InputAction action,
|
||||
string expected)
|
||||
|
|
@ -35,21 +38,12 @@ public sealed class GameplayInputCommandControllerTests
|
|||
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)
|
||||
[Fact]
|
||||
public void RetiredDebugPanelCommand_IsConsumedWithoutClaimingATypedOwner()
|
||||
{
|
||||
var harness = new Harness();
|
||||
|
||||
bool handled = harness.Controller.Handle(action);
|
||||
bool handled = harness.Controller.Handle(InputAction.AcdreamToggleDebugPanel);
|
||||
|
||||
Assert.True(handled);
|
||||
Assert.Empty(harness.Calls);
|
||||
|
|
@ -80,8 +74,9 @@ public sealed class GameplayInputCommandControllerTests
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escape's priority chain: cancel a target mode, else leave player mode,
|
||||
/// else close a window.
|
||||
/// Escape's command-tier priority: cancel a target mode, otherwise toggle
|
||||
/// retail's Gameplay Options page. It must never expose the developer/fly
|
||||
/// camera or close the game window.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The free-fly rung was REMOVED (2026-08-21, user direction: free-fly
|
||||
|
|
@ -90,21 +85,15 @@ public sealed class GameplayInputCommandControllerTests
|
|||
/// rather than silently exiting a camera the player has no way to enter.
|
||||
/// </remarks>
|
||||
[Theory]
|
||||
[InlineData(true, true, true, "cancel-target")]
|
||||
[InlineData(false, true, true, "exit-player")]
|
||||
[InlineData(false, false, true, "exit-player")]
|
||||
[InlineData(false, false, false, "close")]
|
||||
public void Escape_PreservesTargetPlayerWindowPriority(
|
||||
[InlineData(true, "cancel-target")]
|
||||
[InlineData(false, "gameplay-options")]
|
||||
public void Escape_PreservesRetailTargetThenGameplayOptionsPriority(
|
||||
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);
|
||||
|
|
@ -121,19 +110,15 @@ public sealed class GameplayInputCommandControllerTests
|
|||
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);
|
||||
Combat);
|
||||
}
|
||||
|
||||
public List<string> Calls { get; } = [];
|
||||
|
|
@ -141,10 +126,8 @@ public sealed class GameplayInputCommandControllerTests
|
|||
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; }
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +140,12 @@ public sealed class GameplayInputCommandControllerTests
|
|||
calls.Add($"chat-window-{windowId}");
|
||||
|
||||
public void ToggleOptionsPanel() => calls.Add("options");
|
||||
|
||||
public void ToggleGameplayOptionsPage() => calls.Add("gameplay-options");
|
||||
|
||||
public void FocusChatEntry() => calls.Add("focus-chat");
|
||||
|
||||
public void LogOutCharacter() => calls.Add("logout");
|
||||
}
|
||||
|
||||
private sealed class FakeDiagnostics(List<string> calls)
|
||||
|
|
@ -180,11 +169,8 @@ public sealed class GameplayInputCommandControllerTests
|
|||
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)
|
||||
|
|
@ -195,14 +181,6 @@ public sealed class GameplayInputCommandControllerTests
|
|||
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(
|
||||
|
|
@ -248,8 +226,4 @@ public sealed class GameplayInputCommandControllerTests
|
|||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private sealed class FakeWindow(List<string> calls) : IGameplayWindowCommands
|
||||
{
|
||||
public void Close() => calls.Add("close");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue