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

@ -312,8 +312,8 @@ public class ChatCommandRouterTests
// own client-local refusal.
[Theory]
[InlineData("/allegiance boot Bob")]
[InlineData("/all boot Bob")]
[InlineData("/allegiance nope Bob")]
[InlineData("/all nope Bob")]
public void AllegianceUnrecognizedSubcommand_ShowsRetailRefusal_NeverBroadcastsOrSends(string input)
{
// #363 / register row AP-183: retail types this refusal 0x1A
@ -331,6 +331,29 @@ public class ChatCommandRouterTests
Assert.Empty(log.Snapshot());
}
[Theory]
[InlineData("/allegiance boot Lord Bob", ClientCommandId.AllegianceBoot, "Lord Bob")]
[InlineData("/house storage add Lord Bob", ClientCommandId.HouseStorage, "add Lord Bob")]
[InlineData("/motd set Welcome home", ClientCommandId.AllegianceMotd, "set Welcome home")]
public void AdministrationCommand_PublishesTypedClientCommand(
string input,
ClientCommandId expected,
string expectedArguments)
{
var (vm, _, bus) = Fixture();
SubmitOutcome outcome = ChatCommandRouter.Submit(
input,
vm,
bus,
ChatChannelKind.Say);
Assert.Equal(SubmitOutcome.ClientHandled, outcome);
var command = Assert.IsType<ExecuteClientCommandCmd>(Assert.Single(bus.Published));
Assert.Equal(expected, command.Command);
Assert.Equal(expectedArguments, command.Arguments);
}
[Fact]
public void RegisteredChannelVerb_NeverReachesTheRawFallback()
{

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));

View file

@ -489,8 +489,11 @@ public sealed class RetailCommandHelpTableTests
// ported -- see RetailCommandHelpTable.MessageTypesDetail) --
// 43/4/0, zero remaining unverified leaf verbs.
// CT-B4 (2026-08-21): "log" joined the catalog, bringing its already-
// extracted retail Detail text with it -- 44/4/0.
Assert.Equal(44, extractedCount);
// extracted retail Detail text with it -- 44/4/0. #361 adds the
// already-extracted @day and @render handlers -- 46/4/0. #360 adds
// standalone @motd, whose retail Detail text was already extracted
// in RetailCommandHelpTable.Motd -- 47/4/0.
Assert.Equal(47, extractedCount);
Assert.Equal(4, confirmedNullCount);
Assert.Equal(0, unverifiedCount);
}

View file

@ -20,9 +20,9 @@ namespace AcDream.UI.Abstractions.Tests.Panels.Chat;
/// function pointer (verified byte-level in the registry doc); it can
/// never execute in retail OR acdream. Typing it bare reaches the server
/// as literal text.</item>
/// <item><see cref="Status.ServerPassthrough"/> — a real retail verb
/// acdream has not yet ported (TS-68/TS-69/TS-70, or the deliberate
/// @loadfile product decision); falls through to ACE as literal text.</item>
/// <item><see cref="Status.ServerPassthrough"/> — the deliberate
/// <c>@loadfile</c> product decision; it falls through to ACE as literal
/// text.</item>
/// </list>
/// The FIRST test enforces that every verb resolves to the RIGHT status.
/// The SECOND enforces the ownership rule in reverse: nothing in
@ -118,7 +118,7 @@ public sealed class RetailCommandRegistryConformanceTests
// §2.5 Allegiance management (6; "ab" already counted in §2.3)
new(Status.Implemented, "allegiance", "all"),
new(Status.Implemented, "alh", "ah"),
new(Status.ServerPassthrough, "motd"), // TS-68
new(Status.Implemented, "motd"),
new(Status.Implemented, "speaker"),
// §2.5b Housing (7)
@ -141,12 +141,12 @@ public sealed class RetailCommandRegistryConformanceTests
// §2.7 Status / display (8)
new(Status.Implemented, "age"),
new(Status.Implemented, "birth"),
new(Status.ServerPassthrough, "day"), // TS-69 — no sky/time-of-day override hook
new(Status.Implemented, "day"),
new(Status.Implemented, "endurance"),
new(Status.Implemented, "framerate"),
new(Status.Implemented, "loc"),
new(Status.Implemented, "version"),
new(Status.ServerPassthrough, "render"), // TS-69 — no SmartBox equivalent
new(Status.Implemented, "render"),
// §2.8 Interface layout, emotes, social, components (18)
new(Status.Implemented, "saveui"),
@ -220,8 +220,8 @@ public sealed class RetailCommandRegistryConformanceTests
// two totals shift by one against the CH4 audit. The 152 verb total
// is unchanged, which is what NoDuplicateVerbsAcrossEntries and the
// section counts protect.
Assert.Equal(4, Registry.Where(e => e.Status == Status.ServerPassthrough).Sum(e => e.Verbs.Length));
Assert.Equal(139, Registry.Where(e => e.Status == Status.Implemented).Sum(e => e.Verbs.Length));
Assert.Equal(1, Registry.Where(e => e.Status == Status.ServerPassthrough).Sum(e => e.Verbs.Length));
Assert.Equal(142, Registry.Where(e => e.Status == Status.Implemented).Sum(e => e.Verbs.Length));
}
[Fact]