RetailUiRuntime.TryAutoRespondToFellowshipInvite auto-declined/auto-accepted fellowship invites based on IgnoreFellowshipRequests/FellowshipAutoAcceptRequests before the dialog ever reached GameplayConfirmationController. Byte-verified across Handle_Character__ConfirmationRequest @0x005640A0, RecvNotice_FellowshipRequest @0x00490880, and MakeFellowRequestDialog @0x00490620 (whose only guard is m_fellowRequestContext) plus a whole-file sweep of both option accessors: retail's client reads neither bit on any confirmation path. ACE filters both bits server-side, so the interceptor was dead code against a correct ACE and actively harmful against a drifting one (IgnoreFellowshipRequests defaults true, so it would silently swallow real invites with no dialog and no chat line). HandleConfirmationRequest now routes every confirmation type, including 4, straight to the generic controller -- exactly like retail. No tests existed for the deleted interceptor (nothing to remove); added a test proving the type-4 dialog renders the server message verbatim (not "Continue?"-suffixed) and sends accept/decline through the generic path. Per the corrected plan D6 (docs/plans/2026-08-11-fellowship-allegiance-campaign.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
118 lines
5.1 KiB
C#
118 lines
5.1 KiB
C#
using AcDream.App.UI;
|
|
using AcDream.App.UI.Layout;
|
|
using AcDream.App.Tests.UI.Layout;
|
|
using AcDream.Core.Net.Messages;
|
|
|
|
namespace AcDream.App.Tests.UI;
|
|
|
|
public sealed class GameplayConfirmationControllerTests
|
|
{
|
|
[Fact]
|
|
public void SkillRequestAppendsContinueAndCloseNoticeSendsServerTuple()
|
|
{
|
|
var root = new UiRoot { Width = 800f, Height = 600f };
|
|
ImportedLayout? shown = null;
|
|
var factory = new RetailDialogFactory(root, _ =>
|
|
shown = FixtureLoader.LoadConfirmationDialog());
|
|
var responses = new List<(uint Type, uint Context, bool Accepted)>();
|
|
using var controller = new GameplayConfirmationController(
|
|
factory,
|
|
(type, context, accepted) => responses.Add((type, context, accepted)));
|
|
|
|
Assert.True(controller.HandleRequest(
|
|
new GameEvents.CharacterConfirmationRequest(2u, 42u, "Raise this skill?")));
|
|
Assert.Equal(
|
|
"Raise this skill? Continue?",
|
|
string.Join(" ", Assert.IsType<UiText>(shown!.FindElement(
|
|
RetailConfirmationDialogView.MessageElementId)).LinesProvider().Select(static line => line.Text)));
|
|
|
|
Assert.IsType<UiButton>(shown.FindElement(
|
|
RetailConfirmationDialogView.AcceptButtonId)).OnClick!();
|
|
|
|
Assert.Equal([(2u, 42u, true)], responses);
|
|
Assert.Equal(0u, controller.ActiveDialogContext);
|
|
}
|
|
|
|
/// <summary>
|
|
/// MUST-FIX 2 (FA4 fix round, 2026-08-12): a fellowship invite
|
|
/// (<c>ConfirmationType.Fellowship</c>, type 4) reaches this generic
|
|
/// controller and opens a dialog exactly like any other type — no
|
|
/// client-side interceptor exists anymore (D6's correction: retail's
|
|
/// client reads neither option bit on the invite path;
|
|
/// <c>RetailUiRuntime.HandleConfirmationRequest</c> now routes every
|
|
/// type, including 4, straight here). Type 4 is NOT in the
|
|
/// 2/3/5/6 " Continue?"-suffix set, so the message renders verbatim —
|
|
/// matching <c>Handle_Character__ConfirmationRequest @0x005640A0</c>'s
|
|
/// case-4 arm, which is a single call with no text transformation.
|
|
/// </summary>
|
|
[Fact]
|
|
public void FellowshipInviteRequest_Type4_OpensDialog_MessageVerbatim_AndSendsAcceptOnClose()
|
|
{
|
|
var root = new UiRoot { Width = 800f, Height = 600f };
|
|
ImportedLayout? shown = null;
|
|
var factory = new RetailDialogFactory(root, _ =>
|
|
shown = FixtureLoader.LoadConfirmationDialog());
|
|
var responses = new List<(uint Type, uint Context, bool Accepted)>();
|
|
using var controller = new GameplayConfirmationController(
|
|
factory,
|
|
(type, context, accepted) => responses.Add((type, context, accepted)));
|
|
|
|
Assert.True(controller.HandleRequest(
|
|
new GameEvents.CharacterConfirmationRequest(4u, 7u, "Alice invites you to join their fellowship.")));
|
|
|
|
Assert.Equal(
|
|
"Alice invites you to join their fellowship.",
|
|
string.Join(" ", Assert.IsType<UiText>(shown!.FindElement(
|
|
RetailConfirmationDialogView.MessageElementId)).LinesProvider().Select(static line => line.Text)));
|
|
|
|
Assert.IsType<UiButton>(shown.FindElement(
|
|
RetailConfirmationDialogView.AcceptButtonId)).OnClick!();
|
|
|
|
Assert.Equal([(4u, 7u, true)], responses);
|
|
Assert.Equal(0u, controller.ActiveDialogContext);
|
|
}
|
|
|
|
[Fact]
|
|
public void MatchingConfirmationDoneClosesDialogAndUnmatchedTupleDoesNothing()
|
|
{
|
|
var root = new UiRoot { Width = 800f, Height = 600f };
|
|
var factory = new RetailDialogFactory(root, _ =>
|
|
FixtureLoader.LoadConfirmationDialog());
|
|
var responses = new List<(uint Type, uint Context, bool Accepted)>();
|
|
using var controller = new GameplayConfirmationController(
|
|
factory,
|
|
(type, context, accepted) => responses.Add((type, context, accepted)));
|
|
controller.HandleRequest(new GameEvents.CharacterConfirmationRequest(7u, 99u, "Proceed?"));
|
|
|
|
Assert.False(controller.HandleDone(
|
|
new GameEvents.CharacterConfirmationDone(7u, 100u)));
|
|
Assert.True(factory.IsOpen);
|
|
Assert.True(controller.HandleDone(
|
|
new GameEvents.CharacterConfirmationDone(7u, 99u)));
|
|
|
|
Assert.False(factory.IsOpen);
|
|
Assert.Equal([(7u, 99u, false)], responses);
|
|
}
|
|
|
|
[Fact]
|
|
public void FactoryReset_CompletesResponseBeforeSessionTupleIsForgotten()
|
|
{
|
|
var root = new UiRoot { Width = 800f, Height = 600f };
|
|
var factory = new RetailDialogFactory(root, _ =>
|
|
FixtureLoader.LoadConfirmationDialog());
|
|
var responses = new List<(uint Type, uint Context, bool Accepted)>();
|
|
using var controller = new GameplayConfirmationController(
|
|
factory,
|
|
(type, context, accepted) => responses.Add((type, context, accepted)));
|
|
controller.HandleRequest(
|
|
new GameEvents.CharacterConfirmationRequest(7u, 99u, "Proceed?"));
|
|
|
|
factory.Reset();
|
|
controller.ResetSession();
|
|
|
|
Assert.Equal(0u, controller.ActiveDialogContext);
|
|
Assert.Equal([(7u, 99u, false)], responses);
|
|
Assert.False(factory.IsOpen);
|
|
}
|
|
|
|
}
|