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