Owner direction 2026-09-07 (verbatim): "Unknown commands like /vt or
stuff from plugins shall now go to the SpewBox. They should go to the
chatbox." Retail itself types ChatCommandRouter's "Unknown command"
refusals as ClientLocal (0x1A) -- the bit every ChatInterface window's
default filter excludes, so they only ever reached the transient
SpewBox overlay and left no transcript record.
Three call sites in ChatCommandRouter.Submit/EmitVerbHelp now call
IChatCommandFeedback.ShowSystemMessage (chat scroll, retail
Default/0x00) instead of ShowInterfaceText (SpewBox): the degenerate-
prefix "Unknown command: {verb}." guard, EmitVerbHelp's confirmed-
null-help branch, and EmitVerbHelp's unresolved-verb fallback. Every
OTHER 0x1A refusal in this file (AP-183 bad-argument refusals of REAL
retail commands -- lifestone, marketplace, channel list/on/off,
allegiance, house, the generic HandleFailureEvent(0x26) fallback,
DoStupidChannelHack, DoReply) is unchanged and still SpewBox-only --
the owner named only unknown commands and plugin text.
This is a deliberate deviation from retail's own 0x1A typing, recorded
as register row AD-124 (also covers the sibling plugin-text change in
a follow-up commit). docs/ISSUES.md #363/#367 get a one-line note
under each pointing at the re-route; their CLOSED status is untouched.
Mutation check: temporarily reverted all three ShowSystemMessage call
sites back to ShowInterfaceText and confirmed the 3 new/changed pinned
tests fail (Assert.Single() on an empty chat log) while the AP-183
boundary test (real command, bad args, still SpewBox) continues to
pass -- see ChatCommandRouterFeedbackRoutingTests.cs and the updated
ChatCommandRouterTests.cs/RetailCommandHelpTableTests.cs assertions.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
111 lines
5.1 KiB
C#
111 lines
5.1 KiB
C#
using AcDream.Core.Chat;
|
|
using AcDream.Runtime.Chat;
|
|
using AcDream.Runtime.Gameplay;
|
|
|
|
namespace AcDream.Runtime.Tests.Chat;
|
|
|
|
/// <summary>
|
|
/// Owner direction 2026-09-07 (verbatim): "Unknown commands like /vt or
|
|
/// stuff from plugins shall now go to the SpewBox. They should go to the
|
|
/// chatbox." Register row AD-124 records the deviation from retail's own
|
|
/// ClientLocal (0x1A) typing for exactly these two families. This file pins
|
|
/// the <see cref="ChatCommandRouter"/> half of that change at the Runtime
|
|
/// layer — <see cref="RuntimeChatCommandFeedback"/> bound to a real
|
|
/// <see cref="RuntimeCommunicationState"/> — since the existing router
|
|
/// coverage in <c>AcDream.UI.Abstractions.Tests</c> only exercises the
|
|
/// <c>ChatVM</c> feedback implementation. The plugin-text half is pinned at
|
|
/// the App layer (<c>AppAutomationSurfaceTests.PostSystemMessage_RoutesToChatLog_NeverSpewBox</c>),
|
|
/// since <c>AppAutomationSurface</c> is the App-layer production
|
|
/// implementation of <c>IPluginChat</c>.
|
|
/// </summary>
|
|
public sealed class ChatCommandRouterFeedbackRoutingTests
|
|
{
|
|
[Fact]
|
|
public void DegeneratePrefix_UnknownCommandRefusal_RoutesToChatLog_NeverSpewBox()
|
|
{
|
|
// "/" alone (no letter verb) is the degenerate-prefix guard's
|
|
// "Unknown command: {verb}." refusal — retail itself types this
|
|
// 0x1A (ClientLocal / SpewBox-only); the owner override moves it to
|
|
// the chat scroll (Default/0x00) instead.
|
|
using var communication = new RuntimeCommunicationState();
|
|
var feedback = new RuntimeChatCommandFeedback(communication);
|
|
|
|
SubmitOutcome outcome = ChatCommandRouter.Submit(
|
|
"/", feedback, NullCommandBus.Instance, ChatChannelKind.Say);
|
|
|
|
Assert.Equal(SubmitOutcome.UnknownCommand, outcome);
|
|
ChatEntry entry = Assert.Single(communication.Chat.Snapshot());
|
|
Assert.Contains("Unknown command:", entry.Text);
|
|
Assert.Equal((uint)RetailLogTextType.Default, entry.LogTextType);
|
|
|
|
communication.SpewBox.Tick(0d);
|
|
Assert.Equal(0, communication.SpewBox.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void HelpUnresolvedVerb_UnknownCommandText_RoutesToChatLog_NeverSpewBox()
|
|
{
|
|
// "/help nonsenseverb" hits EmitVerbHelp's final unresolved-verb
|
|
// fallback (RetailCommandHelpTable.UnknownCommand), the exact
|
|
// existing retail-swept text — only the destination changes.
|
|
using var communication = new RuntimeCommunicationState();
|
|
var feedback = new RuntimeChatCommandFeedback(communication);
|
|
|
|
SubmitOutcome outcome = ChatCommandRouter.Submit(
|
|
"/help nonsenseverb", feedback, NullCommandBus.Instance, ChatChannelKind.Say);
|
|
|
|
Assert.Equal(SubmitOutcome.ClientHandled, outcome);
|
|
ChatEntry entry = Assert.Single(communication.Chat.Snapshot());
|
|
Assert.Equal(RetailCommandHelpTable.UnknownCommand, entry.Text);
|
|
Assert.Equal((uint)RetailLogTextType.Default, entry.LogTextType);
|
|
|
|
communication.SpewBox.Tick(0d);
|
|
Assert.Equal(0, communication.SpewBox.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void HelpConfirmedNullVerb_UnknownCommandText_RoutesToChatLog_NeverSpewBox()
|
|
{
|
|
// "index" is one of the four catalog verbs retail registers with a
|
|
// genuinely NULL help pointer (RetailCommandHelpTable.
|
|
// CatalogVerbsWithNoRetailHelp) — EmitVerbHelp's OTHER "Unknown
|
|
// command" call site, distinct from the unresolved-verb fallback
|
|
// above.
|
|
using var communication = new RuntimeCommunicationState();
|
|
var feedback = new RuntimeChatCommandFeedback(communication);
|
|
|
|
SubmitOutcome outcome = ChatCommandRouter.Submit(
|
|
"/help index", feedback, NullCommandBus.Instance, ChatChannelKind.Say);
|
|
|
|
Assert.Equal(SubmitOutcome.ClientHandled, outcome);
|
|
ChatEntry entry = Assert.Single(communication.Chat.Snapshot());
|
|
Assert.Equal(RetailCommandHelpTable.UnknownCommand, entry.Text);
|
|
Assert.Equal((uint)RetailLogTextType.Default, entry.LogTextType);
|
|
|
|
communication.SpewBox.Tick(0d);
|
|
Assert.Equal(0, communication.SpewBox.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void RealCommandBadArguments_StillRoutesToSpewBox_NeverChatLog()
|
|
{
|
|
// Boundary pin: AP-183's bad-argument refusals of REAL retail
|
|
// commands are UNCHANGED by the owner's 2026-09-07 direction, which
|
|
// named only unknown commands and plugin text. "/ls now" (Lifestone
|
|
// with bad args) must still land in the SpewBox exclusively.
|
|
using var communication = new RuntimeCommunicationState();
|
|
var feedback = new RuntimeChatCommandFeedback(communication);
|
|
|
|
SubmitOutcome outcome = ChatCommandRouter.Submit(
|
|
"/ls now", feedback, NullCommandBus.Instance, ChatChannelKind.Say);
|
|
|
|
Assert.Equal(SubmitOutcome.ClientHandled, outcome);
|
|
Assert.Empty(communication.Chat.Snapshot());
|
|
|
|
communication.SpewBox.Tick(0d);
|
|
Assert.Equal(1, communication.SpewBox.Count);
|
|
Assert.Equal(
|
|
"Please see @help lifestone for more information on how to use this command.",
|
|
communication.SpewBox.Snapshot()[0].Text);
|
|
}
|
|
}
|