Phase 9: FeedbackPipeServer (\\.\pipe\riojoy-feedback) + loopback UDP share a forgiving text line protocol into FeedbackRouter; CoalescingLampScheduler rate- governs the 9600-baud link; plasma finally wired into activation (greeting, teardown blank, PlasmaDisplay write lock); ViGEm FeedbackReceived drives RumbleLampAdapter. Per-profile Feedback config, docs/FEEDBACK.md, 425 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
109 lines
3.8 KiB
C#
109 lines
3.8 KiB
C#
using RioJoy.Core.Feedback;
|
|
using RioJoy.Core.Protocol;
|
|
using RioJoy.Core.Tests.Mapping;
|
|
using Xunit;
|
|
|
|
namespace RioJoy.Core.Tests.Feedback;
|
|
|
|
public class RumbleLampAdapterTests
|
|
{
|
|
// Flash + Bright/Bright state bytes the bands resolve to.
|
|
private const byte SlowBright = 0x3D;
|
|
private const byte MedBright = 0x3E;
|
|
private const byte FastBright = 0x3F;
|
|
|
|
[Theory]
|
|
[InlineData(0, 24, 0x00)] // below threshold → off
|
|
[InlineData(23, 24, 0x00)]
|
|
[InlineData(24, 24, SlowBright)] // first third of the remaining range
|
|
[InlineData(100, 24, SlowBright)]
|
|
[InlineData(101, 24, MedBright)] // second third
|
|
[InlineData(177, 24, MedBright)]
|
|
[InlineData(178, 24, FastBright)] // top third
|
|
[InlineData(255, 24, FastBright)]
|
|
[InlineData(0, 0, SlowBright)] // threshold 0 = never off
|
|
public void MapMotor_BandsResolveToDocumentedStates(int value, int threshold, byte expected)
|
|
{
|
|
Assert.Equal(expected, RumbleLampAdapter.MapMotor((byte)value, (byte)threshold));
|
|
}
|
|
|
|
[Fact]
|
|
public void MapMotor_MatchesRioLampStateCompose()
|
|
{
|
|
Assert.Equal(
|
|
RioLampState.Compose(LampFlash.FlashFast, LampField1.Bright, LampField2.Bright),
|
|
RumbleLampAdapter.MapMotor(255, 24));
|
|
Assert.Equal(RioLampState.SolidOff, RumbleLampAdapter.MapMotor(0, 24));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task OnRumble_DrivesEachMotorsConfiguredAddresses()
|
|
{
|
|
var sink = new RecordingSink();
|
|
var scheduler = new CoalescingLampScheduler(sink, TimeSpan.FromMilliseconds(1));
|
|
using var cts = new CancellationTokenSource();
|
|
Task pump = scheduler.RunAsync(cts.Token);
|
|
|
|
var adapter = new RumbleLampAdapter(new RumbleLampConfig
|
|
{
|
|
LargeMotorLamps = { 0x20, 0x21 },
|
|
SmallMotorLamps = { 0x30 },
|
|
}, scheduler);
|
|
|
|
adapter.OnRumble(255, 0); // big hit, no small motor
|
|
|
|
await FeedbackWait.For(() => sink.Snapshot().Length >= 3);
|
|
cts.Cancel();
|
|
await pump;
|
|
|
|
string[] sent = sink.Snapshot();
|
|
Assert.Contains("Lamp(0x20,0x3F)", sent); // large motor lamps flash fast
|
|
Assert.Contains("Lamp(0x21,0x3F)", sent);
|
|
Assert.Contains("Lamp(0x30,0x00)", sent); // small motor lamps confirmed off
|
|
}
|
|
|
|
[Fact]
|
|
public async Task OnRumble_RepeatedIdenticalValues_PostNothingNew()
|
|
{
|
|
var sink = new RecordingSink();
|
|
var scheduler = new CoalescingLampScheduler(sink, TimeSpan.FromMilliseconds(1));
|
|
using var cts = new CancellationTokenSource();
|
|
Task pump = scheduler.RunAsync(cts.Token);
|
|
|
|
var adapter = new RumbleLampAdapter(
|
|
new RumbleLampConfig { LargeMotorLamps = { 0x20 }, SmallMotorLamps = { 0x30 } },
|
|
scheduler);
|
|
|
|
// XInput-style spam: same vibration reported over and over.
|
|
for (int i = 0; i < 200; i++)
|
|
adapter.OnRumble(200, 0);
|
|
|
|
await FeedbackWait.For(() => sink.Snapshot().Length >= 2);
|
|
await Task.Delay(50);
|
|
cts.Cancel();
|
|
await pump;
|
|
|
|
Assert.Equal(2, sink.Snapshot().Length); // one state per motor, ever
|
|
}
|
|
|
|
[Fact]
|
|
public async Task OnRumble_ZeroAfterRumble_TurnsTheLampsOff()
|
|
{
|
|
var sink = new RecordingSink();
|
|
var scheduler = new CoalescingLampScheduler(sink, TimeSpan.FromMilliseconds(1));
|
|
using var cts = new CancellationTokenSource();
|
|
Task pump = scheduler.RunAsync(cts.Token);
|
|
|
|
var adapter = new RumbleLampAdapter(
|
|
new RumbleLampConfig { LargeMotorLamps = { 0x20 } }, scheduler);
|
|
|
|
adapter.OnRumble(255, 0);
|
|
await FeedbackWait.For(() => sink.Snapshot().Contains("Lamp(0x20,0x3F)"));
|
|
adapter.OnRumble(0, 0);
|
|
await FeedbackWait.For(() => sink.Snapshot().Contains("Lamp(0x20,0x00)"));
|
|
|
|
cts.Cancel();
|
|
await pump;
|
|
}
|
|
}
|