fix(headless): N5 dim only lifecycle/command/portal lines
HeadlessConsoleRenderer dimmed every line uniformly, so chat and interface text (player-visible content) read the same washed-out weight as scheduling/session-status noise like "entered world" or "command rejected: ...". Only lifecycle, command, and portal lines are scheduling noise; chat and interface text now print at the terminal's default weight. ChatAndInterfaceTextPrintAtDefaultWeightNeverDimmed was shown to fail against the prior dim-everything WriteLine (mutation: dim parameter not yet threaded through, every call still unconditionally wrapped in the ANSI dim/reset pair) -- the chat line and interface text both carried the dim escape sequence. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
70e86c180e
commit
e3639a4c18
2 changed files with 96 additions and 13 deletions
|
|
@ -28,32 +28,35 @@ internal sealed class HeadlessConsoleRenderer : IRuntimeEventObserver
|
|||
{
|
||||
string? line = HeadlessConsoleChatFormatter.Format(delta.Entry);
|
||||
if (!string.IsNullOrEmpty(line))
|
||||
WriteLine(line);
|
||||
WriteLine(line, dim: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail's transient "interface text" (SpewBox, <c>ClientLocal</c>
|
||||
/// type) never touches <see cref="RuntimeCommunicationState.Chat"/> —
|
||||
/// see <c>RuntimeCommunicationState.AddText</c> — so it never reaches
|
||||
/// <see cref="OnChat"/>. <see cref="HeadlessConsoleChatFeedback"/> calls
|
||||
/// this directly for the SAME text
|
||||
/// <c>ChatCommandRouter.Submit</c>'s <c>ShowInterfaceText</c> path would
|
||||
/// otherwise only enqueue into the polled <c>SpewBoxState</c>.
|
||||
/// <see cref="OnChat"/>. <c>HeadlessConsoleSpewBoxPump</c> calls this
|
||||
/// directly, once per console tick, for whatever text is newly visible
|
||||
/// in the polled <see cref="AcDream.Core.Chat.SpewBoxState"/> — the
|
||||
/// SAME seam the graphical overlay's own SpewBox controller reads, so
|
||||
/// server- and plugin-driven interface text prints here too, not only
|
||||
/// the console's own submissions. Default weight (N5) — this is
|
||||
/// player-visible interface text, not scheduling noise.
|
||||
/// </summary>
|
||||
internal void WriteInterfaceText(string text) => WriteLine(text);
|
||||
internal void WriteInterfaceText(string text) => WriteLine(text, dim: false);
|
||||
|
||||
public void OnLifecycle(in RuntimeLifecycleDelta delta)
|
||||
{
|
||||
switch (delta.Current)
|
||||
{
|
||||
case RuntimeLifecycleState.InWorld:
|
||||
WriteLine("entered world");
|
||||
WriteLine("entered world", dim: true);
|
||||
break;
|
||||
case RuntimeLifecycleState.Stopping:
|
||||
WriteLine("disconnecting");
|
||||
WriteLine("disconnecting", dim: true);
|
||||
break;
|
||||
case RuntimeLifecycleState.Faulted:
|
||||
WriteLine("session faulted");
|
||||
WriteLine("session faulted", dim: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -61,13 +64,21 @@ internal sealed class HeadlessConsoleRenderer : IRuntimeEventObserver
|
|||
public void OnCommand(in RuntimeCommandDelta delta)
|
||||
{
|
||||
if (delta.Status == RuntimeCommandStatus.Rejected)
|
||||
WriteLine($"command rejected: {delta.Domain} {delta.Text}".TrimEnd());
|
||||
{
|
||||
WriteLine(
|
||||
$"command rejected: {delta.Domain} {delta.Text}".TrimEnd(),
|
||||
dim: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPortal(in RuntimePortalDelta delta)
|
||||
{
|
||||
if (delta.Portal.IsMaterialized)
|
||||
WriteLine($"portal -> cell 0x{delta.Portal.DestinationCell:X8}");
|
||||
{
|
||||
WriteLine(
|
||||
$"portal -> cell 0x{delta.Portal.DestinationCell:X8}",
|
||||
dim: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEntity(in RuntimeEntityDelta delta)
|
||||
|
|
@ -86,9 +97,15 @@ internal sealed class HeadlessConsoleRenderer : IRuntimeEventObserver
|
|||
{
|
||||
}
|
||||
|
||||
private void WriteLine(string text)
|
||||
/// <summary>
|
||||
/// N5 (2026-09-07 review round): only lifecycle/command/portal lines are
|
||||
/// dimmed — scheduling and session-status noise, not player-visible
|
||||
/// content. Chat and interface text print at the terminal's default
|
||||
/// weight.
|
||||
/// </summary>
|
||||
private void WriteLine(string text, bool dim)
|
||||
{
|
||||
_output.WriteLine(_useColor ? Dim + text + Reset : text);
|
||||
_output.WriteLine(_useColor && dim ? Dim + text + Reset : text);
|
||||
_output.Flush();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -365,6 +365,72 @@ public sealed class HeadlessConsoleTests
|
|||
Assert.Equal(expected, HeadlessConsoleChatFormatter.Format(entry));
|
||||
}
|
||||
|
||||
// ── HeadlessConsoleRenderer: N5 dim-weight rules ─────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// N5: chat and interface text are player-visible content, not
|
||||
/// scheduling noise — they must print at the terminal's default weight,
|
||||
/// never dimmed, even when color is enabled.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChatAndInterfaceTextPrintAtDefaultWeightNeverDimmed()
|
||||
{
|
||||
var output = new StringWriter();
|
||||
var renderer = new HeadlessConsoleRenderer(output, useColor: true);
|
||||
var entry = new RuntimeChatEntry(
|
||||
Revision: 1,
|
||||
SenderGuid: 0x50000010u,
|
||||
Kind: (int)ChatKind.LocalSpeech,
|
||||
Sender: "Bob",
|
||||
Text: "hi",
|
||||
ChannelName: string.Empty);
|
||||
|
||||
renderer.OnChat(new RuntimeChatDelta(default, entry));
|
||||
renderer.WriteInterfaceText("Unknown command: /x");
|
||||
|
||||
string text = output.ToString();
|
||||
Assert.DoesNotContain("[2m", text);
|
||||
Assert.Contains("[Local] Bob: hi", text);
|
||||
Assert.Contains("Unknown command: /x", text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// N5: lifecycle, command, and portal lines are scheduling/session-
|
||||
/// status noise, not player-visible content — dimmed when color is
|
||||
/// enabled.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void LifecycleCommandAndPortalLinesAreDimmedWhenColorIsEnabled()
|
||||
{
|
||||
var output = new StringWriter();
|
||||
var renderer = new HeadlessConsoleRenderer(output, useColor: true);
|
||||
|
||||
renderer.OnLifecycle(new RuntimeLifecycleDelta(
|
||||
default, RuntimeLifecycleState.Starting, RuntimeLifecycleState.InWorld));
|
||||
renderer.OnCommand(new RuntimeCommandDelta(
|
||||
default, RuntimeCommandDomain.Chat, 0, RuntimeCommandStatus.Rejected, Text: "boom"));
|
||||
renderer.OnPortal(new RuntimePortalDelta(
|
||||
default,
|
||||
new RuntimePortalSnapshot(
|
||||
Generation: 1,
|
||||
RuntimePortalKind.Portal,
|
||||
Readiness: new RuntimeDestinationReadiness(
|
||||
1, 0x12345678u, false, false, 0, true, true, true),
|
||||
Materialized: true,
|
||||
Completed: false,
|
||||
Cancelled: false,
|
||||
WorldViewportObserved: true,
|
||||
WorldSimulationAvailable: true,
|
||||
InvariantFailureCount: 0,
|
||||
WaitCueShown: false,
|
||||
PortalMaterializationCount: 1)));
|
||||
|
||||
string[] lines = output.ToString()
|
||||
.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
|
||||
Assert.Equal(3, lines.Length);
|
||||
Assert.All(lines, line => Assert.Contains("[2m", line));
|
||||
}
|
||||
|
||||
// ── HeadlessSessionHost.SubmitConsoleLine: the real dispatch pipeline ─
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue