feedback: plasma box, explicit fonts, and per-position text coalescing
Three endpoint gaps, found building the Descent 3 score overlay - the boxed place|score field the original games drew over the callsign: plasma box <x> <y> <w> <h> draws an outlined box with a blanked interior, the overlay chrome, as one ESC P graphics write. The wire addresses whole bytes horizontally, so the write covers the byte-aligned span containing the box and clears span pixels outside it; documented, with 8-px alignment the advice. Boxes queue FIFO with rows. plasma text gains an explicit font: a third numeric token after the position, with text still following, so `plasma text 2 2 7` still displays "7". Auto-fit picks the font by LENGTH - short text always rendered large, and a "1" that must fit a 12-px box simply could not be sent before. ResolvePosText generalizes the legacy Score-font special case: 0 = auto, nonzero honored. Text coalescing is now per position. The global rule - any queued text superseded every other queued text - meant the documented two-field layout (callsign top, score bottom) could not survive its own send burst: the second field silently ate the first whenever both were queued. A newer text now replaces only a queued text at the same (x,y). 15 new tests; 472 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -219,6 +219,65 @@ public class FeedbackLineParserTests
|
||||
{
|
||||
Assert.NotEmpty(ParseError(line));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlasmaText_ThirdNumberWithTextFollowing_IsFont()
|
||||
{
|
||||
FeedbackCommand cmd = Parse("plasma text 35 21 2 \"1\"");
|
||||
Assert.Equal(35, cmd.X);
|
||||
Assert.Equal(21, cmd.Y);
|
||||
Assert.Equal(2, cmd.Font);
|
||||
Assert.Equal("1", cmd.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlasmaText_ThirdNumberAsLastToken_IsTextNotFont()
|
||||
{
|
||||
// `plasma text 2 2 7` keeps displaying "7", as it always has.
|
||||
FeedbackCommand cmd = Parse("plasma text 2 2 7");
|
||||
Assert.Equal(2, cmd.X);
|
||||
Assert.Equal(2, cmd.Y);
|
||||
Assert.Equal(0, cmd.Font);
|
||||
Assert.Equal("7", cmd.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlasmaText_NoFontGiven_IsAuto()
|
||||
{
|
||||
Assert.Equal(0, Parse("plasma text 12 3 \"FUEL LOW\"").Font);
|
||||
Assert.Equal(0, Parse("plasma text \"VIPER 1-1\"").Font);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlasmaText_FontOutOfRange_Rejected()
|
||||
{
|
||||
Assert.Contains("font", ParseError("plasma text 2 2 99 \"X\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlasmaBox_ParsesGeometry()
|
||||
{
|
||||
FeedbackCommand cmd = Parse("plasma box 32 19 64 12");
|
||||
Assert.Equal(FeedbackCommandKind.PlasmaBox, cmd.Kind);
|
||||
Assert.Equal(32, cmd.X);
|
||||
Assert.Equal(19, cmd.Y);
|
||||
Assert.Equal(64, cmd.Width);
|
||||
Assert.Equal(12, cmd.Height);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("plasma box")] // nothing
|
||||
[InlineData("plasma box 32 19 64")] // missing h
|
||||
[InlineData("plasma box 32 19 64 12 x")] // trailing token
|
||||
[InlineData("plasma box 128 0 1 1")] // x out of range
|
||||
[InlineData("plasma box 0 32 1 1")] // y out of range
|
||||
[InlineData("plasma box 100 0 40 1")] // spills off the right edge
|
||||
[InlineData("plasma box 0 28 1 8")] // spills off the bottom
|
||||
[InlineData("plasma box 0 0 0 5")] // zero width
|
||||
public void PlasmaBox_Malformed_ReturnsErrorText(string line)
|
||||
{
|
||||
Assert.NotEmpty(ParseError(line));
|
||||
}
|
||||
}
|
||||
|
||||
public class FeedbackLineBufferTests
|
||||
|
||||
@@ -146,6 +146,66 @@ public class FeedbackRouterTests : IDisposable
|
||||
Assert.Equal(2, router.DroppedCommands); // the two superseded middles
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PlasmaText_CoalescesPerPosition_OtherFieldsSurvive()
|
||||
{
|
||||
// The documented multi-field layout sends several positioned texts in a
|
||||
// burst (callsign top, score bottom). Coalescing is per (x,y): a newer
|
||||
// score supersedes the queued score, never the queued callsign.
|
||||
var transport = new GatedTransport();
|
||||
var router = new FeedbackRouter();
|
||||
router.Attach(_scheduler, new RioInputMap(), new PlasmaDisplay(transport),
|
||||
new ProfileFeedbackConfig());
|
||||
|
||||
router.Dispatch(Text("PARK")); // goes busy, parked on the gate
|
||||
router.Dispatch(new FeedbackCommand
|
||||
{ Kind = FeedbackCommandKind.PlasmaText, Text = "VIPER", X = 2, Y = 2 });
|
||||
router.Dispatch(new FeedbackCommand
|
||||
{ Kind = FeedbackCommandKind.PlasmaText, Text = "SCORE 1", X = 2, Y = 18 });
|
||||
router.Dispatch(new FeedbackCommand
|
||||
{ Kind = FeedbackCommandKind.PlasmaText, Text = "SCORE 2", X = 2, Y = 18 }); // supersedes SCORE 1 only
|
||||
transport.Open();
|
||||
|
||||
var texts = new List<string>();
|
||||
for (int i = 0; i < 15; i++)
|
||||
{
|
||||
byte[] w = await transport.NextWriteAsync();
|
||||
string s = System.Text.Encoding.GetEncoding(28591).GetString(w);
|
||||
if (w.Length > 0 && w[0] != 0x1B)
|
||||
texts.Add(s); // the text chunks, minus ESC command prefixes
|
||||
if (texts.Count == 3)
|
||||
break;
|
||||
}
|
||||
Assert.Equal(new[] { "PARK", "VIPER", "SCORE 2" }, texts);
|
||||
Assert.Equal(1, router.DroppedCommands); // only SCORE 1 superseded
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PlasmaBox_WritesOutlineWithBlankedInterior()
|
||||
{
|
||||
var transport = new GatedTransport();
|
||||
var router = new FeedbackRouter();
|
||||
router.Attach(_scheduler, new RioInputMap(), new PlasmaDisplay(transport),
|
||||
new ProfileFeedbackConfig());
|
||||
|
||||
// 16px-wide box at a byte boundary, 4 rows: bytes are exact.
|
||||
router.Dispatch(new FeedbackCommand
|
||||
{ Kind = FeedbackCommandKind.PlasmaBox, X = 32, Y = 10, Width = 16, Height = 4 });
|
||||
transport.Open();
|
||||
|
||||
byte[] w = await transport.NextWriteAsync();
|
||||
// ESC P s=0 y=10 x=4 w=2 h=4, then 8 data bytes.
|
||||
Assert.Equal(new byte[] { 0x1B, (byte)'P', 0, 10, 4, 2, 4 }, w.Take(7).ToArray());
|
||||
byte[] data = w.Skip(7).ToArray();
|
||||
Assert.Equal(new byte[]
|
||||
{
|
||||
0xFF, 0xFF, // top edge: all lit
|
||||
0x80, 0x01, // interior row: only the side walls
|
||||
0x80, 0x01,
|
||||
0xFF, 0xFF, // bottom edge
|
||||
}, data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PlasmaRows_StreamInFifoOrder_NotCoalesced()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user