From 2b65217d29889a98fbcd6f1647d5a55b4a0ca003 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 7 Sep 2026 08:22:22 +0200 Subject: [PATCH] fix #363: route plugin system text to the chat window, not SpewBox Owner direction 2026-09-07 (same instruction as the sibling unknown- command fix, previous commit): plugin-originated text must land in the chat window instead of retail's ClientLocal (0x1A) SpewBox-only channel. AppAutomationSurface.PostSystemMessage -- the production implementation of IPluginChat.PostSystemMessage that MossTank/VTank- style plugins call -- now passes RetailLogTextType.Default instead of ClientLocal to RuntimeCommunicationState.AddText, so the text reaches the chat transcript via Chat.OnSystemMessage instead of the SpewBox. This matches Decal's own AddChatText behavior for plugin output. IPluginChat.PostSystemMessage's doc comment is updated to describe the new destination instead of the old one. Register row AD-124 (previous commit) already covers this site alongside the sibling unknown-command change. Mutation check: temporarily reverted PostSystemMessage's AddText call back to ClientLocal and confirmed the new AppAutomationSurfaceTests.PostSystemMessage_RoutesToChatLog_NeverSpewBox test fails (Assert.Single() on an empty chat log) before restoring the fix. Also adds ChatVMTests.RecentLines_ShowsPluginSystemMessage_TaggedDefault pinning that a ChatVM bound to the same ChatLog surfaces the line. Co-Authored-By: Claude Fable 5.1 --- .../Plugins/AppAutomationSurface.cs | 11 ++++--- src/AcDream.Plugin.Abstractions/Automation.cs | 16 ++++++++-- .../Plugins/AppAutomationSurfaceTests.cs | 25 +++++++++++++++ .../ChatVMTests.cs | 32 +++++++++++++++++++ 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/AcDream.App/Plugins/AppAutomationSurface.cs b/src/AcDream.App/Plugins/AppAutomationSurface.cs index a4f87d0b..9ae412f7 100644 --- a/src/AcDream.App/Plugins/AppAutomationSurface.cs +++ b/src/AcDream.App/Plugins/AppAutomationSurface.cs @@ -1170,9 +1170,12 @@ internal sealed class AppAutomationSurface } /// - /// Routed to retail's ClientLocal log type (0x1A) — the channel the client - /// uses for its own notices. Nothing reaches the server, so a plugin cannot - /// accidentally speak in the player's name. + /// Owner direction 2026-09-07 (register row AD-124): plugin-originated + /// text now lands in the chat window (retail Default/0x00), + /// matching Decal's own AddChatText behavior — not retail's + /// ClientLocal (0x1A) SpewBox-only channel this previously used. + /// Nothing reaches the server, so a plugin cannot accidentally speak in + /// the player's name. /// public void PostSystemMessage(string text) { @@ -1181,7 +1184,7 @@ internal sealed class AppAutomationSurface RuntimeCommunicationState? communication; lock (_gate) communication = _communication; - communication?.AddText(text, RetailLogTextType.ClientLocal); + communication?.AddText(text, RetailLogTextType.Default); } public bool Submit(string text) diff --git a/src/AcDream.Plugin.Abstractions/Automation.cs b/src/AcDream.Plugin.Abstractions/Automation.cs index 894d062b..2e968d1e 100644 --- a/src/AcDream.Plugin.Abstractions/Automation.cs +++ b/src/AcDream.Plugin.Abstractions/Automation.cs @@ -302,10 +302,20 @@ public interface IPluginChat Array.Empty(); /// - /// Post a client-local system line, the channel retail uses for the - /// client's own notices. It is local to this client: nothing is sent to the - /// server and no other player sees it. + /// Post a plugin-originated system line into the chat window. It is + /// local to this client: nothing is sent to the server and no other + /// player sees it. /// + /// + /// Owner direction 2026-09-07 (register row AD-124): this used to route + /// through retail's ClientLocal (0x1A) channel — the SpewBox + /// overlay every ChatInterface window's default filter excludes. + /// The owner explicitly overrode that for plugin text, matching Decal's + /// own AddChatText behavior: plugin output now lands in the chat + /// transcript (retail Default/0x00) so it is actually visible and + /// scrolls back, never the transient overlay. See + /// AppAutomationSurface.PostSystemMessage for the implementation. + /// void PostSystemMessage(string text); /// diff --git a/tests/AcDream.App.Tests/Plugins/AppAutomationSurfaceTests.cs b/tests/AcDream.App.Tests/Plugins/AppAutomationSurfaceTests.cs index 9982ace3..b3221fde 100644 --- a/tests/AcDream.App.Tests/Plugins/AppAutomationSurfaceTests.cs +++ b/tests/AcDream.App.Tests/Plugins/AppAutomationSurfaceTests.cs @@ -146,6 +146,31 @@ public sealed class AppAutomationSurfaceTests Assert.Equal(0, second.CommunicationOwner.SubscriberCount); } + /// + /// Owner-directed override 2026-09-07 (register row AD-124): plugin + /// output ("Unknown commands like /vt or stuff from plugins ... should + /// go to the chatbox") must land in the chat log, never the transient + /// SpewBox overlay retail's own ClientLocal (0x1A) typing used to send + /// it to — the same VTank-faithful destination Decal's own + /// AddChatText uses. + /// + [Fact] + public void PostSystemMessage_RoutesToChatLog_NeverSpewBox() + { + using var runtime = GameRuntimeTestFactory.Create(); + using var surface = new AppAutomationSurface(); + surface.Bind(runtime, runtime.CharacterOwner, runtime.ActionOwner.SpellCast); + + surface.PostSystemMessage("MossTank: buffs applied."); + + var entry = Assert.Single(runtime.CommunicationOwner.Chat.Snapshot()); + Assert.Equal("MossTank: buffs applied.", entry.Text); + Assert.Equal((uint)RetailLogTextType.Default, entry.LogTextType); + + runtime.CommunicationOwner.SpewBox.Tick(0d); + Assert.Equal(0, runtime.CommunicationOwner.SpewBox.Count); + } + [Fact] public void InventoryCompletionProjectsTheCanonicalRequestReceipt() { diff --git a/tests/AcDream.UI.Abstractions.Tests/ChatVMTests.cs b/tests/AcDream.UI.Abstractions.Tests/ChatVMTests.cs index 15cb0cc5..770aae9c 100644 --- a/tests/AcDream.UI.Abstractions.Tests/ChatVMTests.cs +++ b/tests/AcDream.UI.Abstractions.Tests/ChatVMTests.cs @@ -194,4 +194,36 @@ public sealed class ChatVMTests // The stored body never carries the stamp in either state. Assert.Equal("hi", log.Snapshot()[0].Text); } + + /// + /// Owner-directed override 2026-09-07 (register row AD-124): plugin + /// output (AppAutomationSurface.PostSystemMessage, the + /// production implementation of IPluginChat.PostSystemMessage) + /// now funnels into RuntimeCommunicationState.AddText(text, + /// RetailLogTextType.Default), which calls + /// Chat.OnSystemMessage(text, (uint)Default) — the exact call + /// this test performs directly on the shared , + /// matching Decal's own AddChatText behavior for plugin text. + /// Any bound to that log (the production chat + /// window) must show the line; it must never depend on the + /// SpewBox seam, which this call + /// never touches. + /// + [Fact] + public void RecentLines_ShowsPluginSystemMessage_TaggedDefault() + { + var log = new ChatLog(); + var vm = new ChatVM(log, displayLimit: 50); + + log.OnSystemMessage( + "MossTank: buffs applied.", + chatType: (uint)RetailLogTextType.Default); + + Assert.Equal( + "MossTank: buffs applied.", + Assert.Single(vm.RecentLines())); + Assert.Equal( + (uint)RetailLogTextType.Default, + Assert.Single(log.Snapshot()).LogTextType); + } }