fix: complete retail parity stability pass
All checks were successful
CI / linux-portable (push) Successful in 3m41s
CI / windows-gate (push) Successful in 6m49s
CI / release (push) Successful in 3m22s

This commit is contained in:
Erik 2026-08-28 20:01:39 +02:00
parent d3df4cb20a
commit f7aa8e0eb7
131 changed files with 7765 additions and 1190 deletions

View file

@ -71,6 +71,10 @@ public sealed class RetailClientCommandCatalogTests
[InlineData("/unfilter -combat", ClientCommandId.Unfilter)]
[InlineData("/messagetypes", ClientCommandId.ListMessageTypes)]
[InlineData("/fillcomps scarabs 500", ClientCommandId.FillComponents)]
[InlineData("/day", ClientCommandId.TogglePersistentDaylight)]
[InlineData("@day ignored", ClientCommandId.TogglePersistentDaylight)]
[InlineData("/render radius 12", ClientCommandId.RenderOption)]
[InlineData("@render usage", ClientCommandId.RenderOption)]
public void CommandFamilies_ResolveToTypedClientCommands(
string input, ClientCommandId expected)
{
@ -91,18 +95,46 @@ public sealed class RetailClientCommandCatalogTests
}
[Theory]
[InlineData("/house open")]
[InlineData("/house close")]
[InlineData("/house guest add Bob")]
[InlineData("/house storage add Bob")]
[InlineData("/house nope")]
[InlineData("/house available")]
public void UnsupportedHouseSubcommand_FallsThroughToServerPassthrough(string input)
[InlineData("/house open", ClientCommandId.HouseOpenStatus, "open")]
[InlineData("/house close ignored", ClientCommandId.HouseOpenStatus, "close")]
[InlineData("/house guest add Bob", ClientCommandId.HouseGuests, "add Bob")]
[InlineData("/house storage add Lord Bob", ClientCommandId.HouseStorage, "add Lord Bob")]
[InlineData("/house remove Bob", ClientCommandId.HouseBoot, "Bob")]
[InlineData("/house boot -all", ClientCommandId.HouseBoot, "-all")]
[InlineData("/house boot_all ignored", ClientCommandId.HouseBootAll, "ignored")]
[InlineData("/house remove_all", ClientCommandId.HouseBootAll, "")]
[InlineData("/house available Cottage", ClientCommandId.HouseAvailableList, "Cottage")]
[InlineData("/house hooks off", ClientCommandId.HouseHooks, "off")]
public void HouseManagementSubcommands_ResolveLocally(
string input,
ClientCommandId expected,
string expectedArguments)
{
// Campaign CH slice CH4 (2026-08-09), Tier 1 fix #4: unrecognized
// house subcommands must reach ACE (TS-68), not be swallowed
// locally with a wrong usage message.
Assert.False(RetailClientCommandCatalog.TryMatch(input, out _));
Assert.True(RetailClientCommandCatalog.TryMatch(input, out var match));
Assert.True(match.HasValidArguments);
Assert.Equal(expected, match.Command);
Assert.Equal(expectedArguments, match.Arguments);
}
[Theory]
[InlineData("/house")]
[InlineData("/house nope")]
public void HouseUnknownOrMissingSubcommand_ShowsRetailRefusalLocally(string input)
{
Assert.True(RetailClientCommandCatalog.TryMatch(input, out var match));
Assert.Equal(ClientCommandId.HouseUnrecognizedSubcommand, match.Command);
Assert.False(match.HasValidArguments);
Assert.Equal(
"Please see @help House for more information on how to use this command.",
match.InvalidArgumentsText);
}
[Fact]
public void HouseAvailableWithoutType_UsesHslistArgumentValidation()
{
Assert.True(RetailClientCommandCatalog.TryMatch("/house available", out var match));
Assert.Equal(ClientCommandId.HouseAvailableList, match.Command);
Assert.False(match.HasValidArguments);
}
[Theory]
@ -118,24 +150,38 @@ public sealed class RetailClientCommandCatalogTests
}
[Theory]
[InlineData("/allegiance boot Bob")]
[InlineData("/allegiance ban add Bob")]
[InlineData("/allegiance motd")]
[InlineData("/allegiance")]
[InlineData("/all officer add 2 Bob")]
public void UnsupportedAllegianceSubcommand_ShowsRetailRefusal_ClientSide(string input)
[InlineData("/allegiance boot Bob", ClientCommandId.AllegianceBoot, "Bob")]
[InlineData("/allegiance ban add Bob", ClientCommandId.AllegianceBan, "add Bob")]
[InlineData("/all chat gag Bob", ClientCommandId.AllegianceChat, "gag Bob")]
[InlineData("/all ch off", ClientCommandId.AllegianceChat, "off")]
[InlineData("/allegiance broadcast hello", ClientCommandId.AllegianceBroadcast, "hello")]
[InlineData("/allegiance br hello", ClientCommandId.AllegianceBroadcast, "hello")]
[InlineData("/all officer add 2 Bob", ClientCommandId.AllegianceOfficer, "add 2 Bob")]
[InlineData("/all title set 2 Regent", ClientCommandId.AllegianceOfficerTitle, "set 2 Regent")]
[InlineData("/allegiance motd", ClientCommandId.AllegianceMotd, "")]
[InlineData("/allegiance name set A Name", ClientCommandId.AllegianceName, "set A Name")]
[InlineData("/allegiance lock bypass Bob", ClientCommandId.AllegianceLock, "bypass Bob")]
[InlineData("/allegiance house guest open", ClientCommandId.AllegianceHouse, "guest open")]
[InlineData("/motd set Welcome", ClientCommandId.AllegianceMotd, "set Welcome")]
public void AllegianceManagementSubcommands_ResolveLocally(
string input,
ClientCommandId expected,
string expectedArguments)
{
// CH4 REJECT-review Blocker 1 (2026-08-09): unlike @house (whose
// unrecognized subcommands correctly reach ACE, see the test
// above), retail's own DoAllegiance NEVER falls through to
// DoChannelCommand/the server for an unrecognized subcommand — it
// claims the whole verb unconditionally and prints its own
// client-local refusal (label_57da4b, 0x0057DA4B). The earlier
// "falls through to server passthrough" behavior here was itself
// the bug: an unmatched subcommand used to escape all the way to
// the unregistered-tag channel fallback and broadcast to the
// Allegiance chat channel.
Assert.True(RetailClientCommandCatalog.TryMatch(input, out var match));
Assert.True(match.HasValidArguments);
Assert.Equal(expected, match.Command);
Assert.Equal(expectedArguments, match.Arguments);
}
[Theory]
[InlineData("/allegiance")]
[InlineData("/allegiance nope")]
[InlineData("/all nonsense")]
public void UnknownAllegianceSubcommand_ShowsRetailRefusal_ClientSide(string input)
{
Assert.True(RetailClientCommandCatalog.TryMatch(input, out var match));
Assert.Equal(ClientCommandId.AllegianceUnrecognizedSubcommand, match.Command);
Assert.False(match.HasValidArguments);
Assert.Equal(
"Please see @help Allegiance for more information on how to use this command.",
@ -150,7 +196,7 @@ public sealed class RetailClientCommandCatalogTests
[InlineData("/allegiance info", ClientCommandId.AllegianceInfo, "")]
[InlineData("/allegiance info Bob", ClientCommandId.AllegianceInfo, "Bob")]
[InlineData("/all info Bob", ClientCommandId.AllegianceInfo, "Bob")]
public void AllegianceImplementedSubcommands_Resolve(
public void AllegianceSimpleSubcommands_Resolve(
string input, ClientCommandId expected, string expectedArguments)
{
Assert.True(RetailClientCommandCatalog.TryMatch(input, out var match));