refactor(headless): S5 poll SpewBox on the console pump instead of decorating one call
HeadlessConsoleChatFeedback wrapped RuntimeChatCommandFeedback per SubmitConsoleLine call, so it only ever saw interface text produced by the console's OWN typed line -- a server-driven refusal or a plugin's own Log/interface-text write (RuntimeCommunicationState.AddText's ClientLocal branch, called from anywhere else) never reached the console at all, because that branch enqueues into SpewBoxState and never touches ChatLog/RuntimeChatDelta. Delete the decorator. HeadlessConsoleSpewBoxPump instead polls the SAME SpewBoxState the graphical overlay's SpewBoxController.Tick already reads, diffing against the previous visible snapshot so it prints only newly-appeared entries. HeadlessProcessHost's ConsolePump now runs the input drain and the SpewBox pump together each tick. HeadlessSessionHost.SubmitConsoleLine drops its onInterfaceText parameter -- it is just ChatCommandRouter.Submit against a plain RuntimeChatCommandFeedback now, same as LoginCommandSequence. N1: also corrected this method's own doc comment, which described dispatch as "plugin verb registry first, then retail client/server slash commands" -- the real ChatCommandRouter.Submit order is retail's catalog, local /help, plugin verbs, the channel-tag fallback, an explicit server command, then chat. PumpPrintsInterfaceTextNotOriginatingFromTheConsole was shown to fail against a no-op Pump() (mutation) -- the enqueued plugin-shaped line never printed. UnknownVerbProducesTheSameInterfaceTextTheChatBoxShows was reworked to assert against the real SpewBoxState directly instead of the deleted decorator's callback. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
f0b7a136b2
commit
3328b2f2b3
5 changed files with 149 additions and 69 deletions
|
|
@ -383,7 +383,7 @@ public sealed class HeadlessConsoleTests
|
|||
RuntimeSessionStartStatus.Connected,
|
||||
host.Start().Status);
|
||||
|
||||
SubmitOutcome outcome = host.SubmitConsoleLine("/say hello", _ => { });
|
||||
SubmitOutcome outcome = host.SubmitConsoleLine("/say hello");
|
||||
|
||||
Assert.Equal(SubmitOutcome.Sent, outcome);
|
||||
byte[] body = Assert.Single(captured);
|
||||
|
|
@ -409,7 +409,7 @@ public sealed class HeadlessConsoleTests
|
|||
RuntimeSessionStartStatus.Connected,
|
||||
host.Start().Status);
|
||||
|
||||
SubmitOutcome outcome = host.SubmitConsoleLine("hello", _ => { });
|
||||
SubmitOutcome outcome = host.SubmitConsoleLine("hello");
|
||||
|
||||
Assert.Equal(SubmitOutcome.Sent, outcome);
|
||||
byte[] body = Assert.Single(captured);
|
||||
|
|
@ -439,7 +439,7 @@ public sealed class HeadlessConsoleTests
|
|||
"vt",
|
||||
command => received.Add(command));
|
||||
|
||||
SubmitOutcome outcome = host.SubmitConsoleLine("/vt start", _ => { });
|
||||
SubmitOutcome outcome = host.SubmitConsoleLine("/vt start");
|
||||
|
||||
Assert.Equal(SubmitOutcome.ClientHandled, outcome);
|
||||
PluginCommand command = Assert.Single(received);
|
||||
|
|
@ -448,6 +448,16 @@ public sealed class HeadlessConsoleTests
|
|||
Assert.Empty(captured);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// S5 rework: <c>SubmitConsoleLine</c> no longer takes a per-call
|
||||
/// interface-text callback — retail's transient interface text
|
||||
/// (<c>ClientLocal</c>) lands in the shared
|
||||
/// <see cref="AcDream.Core.Chat.SpewBoxState"/> exactly like every other
|
||||
/// producer of that text (see <c>RuntimeCommunicationState.AddText</c>),
|
||||
/// and the console's own per-tick pump polls it — proven directly here
|
||||
/// against the real <see cref="AcDream.Core.Chat.SpewBoxState"/> rather
|
||||
/// than a decorator only this call site could see.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void UnknownVerbProducesTheSameInterfaceTextTheChatBoxShows()
|
||||
{
|
||||
|
|
@ -461,20 +471,53 @@ public sealed class HeadlessConsoleTests
|
|||
Assert.Equal(
|
||||
RuntimeSessionStartStatus.Connected,
|
||||
host.Start().Status);
|
||||
var interfaceText = new List<string>();
|
||||
|
||||
// A bare "/" is retail's degenerate-prefix case (no letter verb) —
|
||||
// ChatCommandRouter refuses it locally instead of sending it to the
|
||||
// server or speech (ChatCommandRouterTests.
|
||||
// DegeneratePrefix_UnknownCommand_ShowsRefusal_ViaInterfaceTextSeam
|
||||
// pins the exact same text for the graphical route).
|
||||
SubmitOutcome outcome = host.SubmitConsoleLine(
|
||||
"/",
|
||||
interfaceText.Add);
|
||||
SubmitOutcome outcome = host.SubmitConsoleLine("/");
|
||||
|
||||
Assert.Equal(SubmitOutcome.UnknownCommand, outcome);
|
||||
string text = Assert.Single(interfaceText);
|
||||
Assert.Contains("Unknown command:", text);
|
||||
SpewBoxState spewBox = host.Runtime.CommunicationOwner.SpewBox;
|
||||
spewBox.Tick(host.Runtime.Clock.SimulationTimeSeconds);
|
||||
SpewBoxEntry entry = Assert.Single(spewBox.Snapshot());
|
||||
Assert.Contains("Unknown command:", entry.Text);
|
||||
}
|
||||
|
||||
// ── HeadlessConsoleSpewBoxPump: server/plugin-driven interface text ──
|
||||
|
||||
/// <summary>
|
||||
/// S5: the pump must surface interface text that never went through the
|
||||
/// console at all — a stand-in for a server- or plugin-driven
|
||||
/// <c>ClientLocal</c> write reaching <c>RuntimeCommunicationState.AddText</c>
|
||||
/// directly, exactly the case the deleted per-call
|
||||
/// <c>HeadlessConsoleChatFeedback</c> decorator could never see (it only
|
||||
/// ever wrapped THIS console's own <c>SubmitConsoleLine</c> feedback).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PumpPrintsInterfaceTextNotOriginatingFromTheConsole()
|
||||
{
|
||||
var spewBox = new SpewBoxState();
|
||||
var printed = new List<string>();
|
||||
double now = 0d;
|
||||
var pump = new HeadlessConsoleSpewBoxPump(spewBox, () => now, printed.Add);
|
||||
|
||||
// Simulates a plugin's own Log/interface-text write, or a server-
|
||||
// driven refusal — never called HeadlessConsoleController.Handle or
|
||||
// HeadlessSessionHost.SubmitConsoleLine.
|
||||
spewBox.Enqueue("[vt] navigation route loaded");
|
||||
|
||||
pump.Pump();
|
||||
|
||||
Assert.Equal(["[vt] navigation route loaded"], printed);
|
||||
|
||||
// A second pump with nothing new enqueued must not reprint the
|
||||
// still-visible entry.
|
||||
now += 0.1d;
|
||||
pump.Pump();
|
||||
Assert.Equal(["[vt] navigation route loaded"], printed);
|
||||
}
|
||||
|
||||
private static bool WaitForEndOfInput(HeadlessConsoleController controller) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue