diff --git a/src/RioJoy.Core/Compat/Net48Polyfills.cs b/src/RioJoy.Core/Compat/Net48Polyfills.cs
index 1f11890..3411887 100644
--- a/src/RioJoy.Core/Compat/Net48Polyfills.cs
+++ b/src/RioJoy.Core/Compat/Net48Polyfills.cs
@@ -10,10 +10,12 @@ namespace System.Collections.Generic
///
/// Polyfill for CollectionExtensions.GetValueOrDefault (netstandard2.1+),
/// absent on net48. Returns the value for or
- /// default(TValue) when the key is missing.
+ /// default(TValue) when the key is missing. Takes IDictionary rather than
+ /// IReadOnlyDictionary: net40 (Windows XP flavor) predates the IReadOnly*
+ /// interfaces, so shared code uses the classic ones throughout.
///
public static TValue? GetValueOrDefault(
- this IReadOnlyDictionary dictionary, TKey key)
+ this IDictionary dictionary, TKey key)
{
if (dictionary is null) throw new ArgumentNullException(nameof(dictionary));
return dictionary.TryGetValue(key, out var value) ? value : default;
diff --git a/src/RioJoy.Core/Compat/TaskCompat.cs b/src/RioJoy.Core/Compat/TaskCompat.cs
new file mode 100644
index 0000000..c4ce827
--- /dev/null
+++ b/src/RioJoy.Core/Compat/TaskCompat.cs
@@ -0,0 +1,40 @@
+namespace RioJoy.Core.Compat;
+
+///
+/// net48/net40 bridge for the handful of Task-era statics the code uses. On
+/// net40 (the Windows XP flavor) these come from Microsoft.Bcl.Async's
+/// TaskEx, and SemaphoreSlim.WaitAsync doesn't exist at all —
+/// there the wait blocks briefly instead, which at 9600 baud (tiny writes,
+/// rare contention) costs nothing measurable.
+///
+internal static class TaskCompat
+{
+#if NET40
+ public static Task Run(Action action) => TaskEx.Run(action);
+
+ public static Task Delay(TimeSpan delay, CancellationToken cancellationToken) =>
+ TaskEx.Delay(delay, cancellationToken);
+
+ public static Task WhenAny(IEnumerable tasks) => TaskEx.WhenAny(tasks);
+
+ public static Task WhenAll(IEnumerable tasks) => TaskEx.WhenAll(tasks);
+
+ public static Task WaitAsync(SemaphoreSlim semaphore, CancellationToken cancellationToken)
+ {
+ semaphore.Wait(cancellationToken); // net40: no WaitAsync; block (see class doc)
+ return TaskEx.FromResult(true);
+ }
+#else
+ public static Task Run(Action action) => Task.Run(action);
+
+ public static Task Delay(TimeSpan delay, CancellationToken cancellationToken) =>
+ Task.Delay(delay, cancellationToken);
+
+ public static Task WhenAny(IEnumerable tasks) => Task.WhenAny(tasks);
+
+ public static Task WhenAll(IEnumerable tasks) => Task.WhenAll(tasks);
+
+ public static Task WaitAsync(SemaphoreSlim semaphore, CancellationToken cancellationToken) =>
+ semaphore.WaitAsync(cancellationToken);
+#endif
+}
diff --git a/src/RioJoy.Core/Editing/CockpitPanel.cs b/src/RioJoy.Core/Editing/CockpitPanel.cs
index e3a89f7..d49e837 100644
--- a/src/RioJoy.Core/Editing/CockpitPanel.cs
+++ b/src/RioJoy.Core/Editing/CockpitPanel.cs
@@ -27,7 +27,7 @@ public sealed record PanelGroup(
bool LampCapable,
int OriginCol,
int OriginRow,
- IReadOnlyList Addresses);
+ IList Addresses);
/// One address button placed on the panel (absolute cell coordinates).
public sealed record PanelButton(int Address, PanelGroup Group, int Col, int Row, bool LampCapable);
@@ -66,7 +66,7 @@ public static class CockpitPanel
}
/// All panel groups, positioned for rendering.
- public static IReadOnlyList Groups { get; } = new[]
+ public static IList Groups { get; } = new[]
{
// Upper MFD row.
new PanelGroup("Upper Left MFD", PanelGroupKind.Mfd, 4, 2, true, 0, 1, Mfd(0x2F)),
@@ -90,7 +90,7 @@ public static class CockpitPanel
};
/// Flatten the groups to positioned s (absolute cells).
- public static IReadOnlyList Buttons()
+ public static IList Buttons()
{
var buttons = new List();
foreach (PanelGroup g in Groups)
diff --git a/src/RioJoy.Core/Editing/SheetLayout.cs b/src/RioJoy.Core/Editing/SheetLayout.cs
index 2e77367..aba2d1c 100644
--- a/src/RioJoy.Core/Editing/SheetLayout.cs
+++ b/src/RioJoy.Core/Editing/SheetLayout.cs
@@ -34,7 +34,7 @@ public sealed record SheetCell(int Row, int Col, string Text, int? Address, Shee
///
public static class SheetLayout
{
- public static IReadOnlyList Load(string path) => Parse(File.ReadAllText(path));
+ public static IList Load(string path) => Parse(File.ReadAllText(path));
///
/// The RIO address a cell's text encodes (two hex digits in 0x00–0x6F), or null.
@@ -66,7 +66,7 @@ public static class SheetLayout
/// longer than is dropped as a merged-cell
/// artifact; the clean export has none.
///
- public static IReadOnlyList Parse(string csv, int maxTextLength = 40)
+ public static IList Parse(string csv, int maxTextLength = 40)
{
if (csv is null) throw new ArgumentNullException(nameof(csv));
diff --git a/src/RioJoy.Core/Mapping/KeyCatalog.cs b/src/RioJoy.Core/Mapping/KeyCatalog.cs
index c94c2ed..2aaacac 100644
--- a/src/RioJoy.Core/Mapping/KeyCatalog.cs
+++ b/src/RioJoy.Core/Mapping/KeyCatalog.cs
@@ -13,7 +13,7 @@ public readonly record struct KeyName(string Name, byte Value);
public static class KeyCatalog
{
/// All catalogued keys, in menu order (letters, digits, F-keys, …).
- public static IReadOnlyList Entries { get; } = Build();
+ public static IList Entries { get; } = Build();
private static readonly Dictionary ByValue =
Entries.GroupBy(e => e.Value).ToDictionary(g => g.Key, g => g.First().Name);
diff --git a/src/RioJoy.Core/Mapping/RioInputMap.cs b/src/RioJoy.Core/Mapping/RioInputMap.cs
index 7a3d4cd..3dc9c7f 100644
--- a/src/RioJoy.Core/Mapping/RioInputMap.cs
+++ b/src/RioJoy.Core/Mapping/RioInputMap.cs
@@ -14,7 +14,7 @@ public sealed class RioInputMap
public RioInputMap() { }
/// Create a map from raw 16-bit words indexed by address.
- public RioInputMap(IReadOnlyDictionary entries)
+ public RioInputMap(IDictionary entries)
{
if (entries is null) throw new ArgumentNullException(nameof(entries));
foreach ((int address, ushort raw) in entries)
diff --git a/src/RioJoy.Core/Output/HidFeederJoystickSink.cs b/src/RioJoy.Core/Output/HidFeederJoystickSink.cs
index a22e91e..56066d9 100644
--- a/src/RioJoy.Core/Output/HidFeederJoystickSink.cs
+++ b/src/RioJoy.Core/Output/HidFeederJoystickSink.cs
@@ -114,7 +114,7 @@ public sealed class HidFeederJoystickSink : IJoystickSink, IDisposable
try
{
var ifData = new SP_DEVICE_INTERFACE_DATA();
- ifData.cbSize = Marshal.SizeOf();
+ ifData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));
if (!SetupDiEnumDeviceInterfaces(devInfo, IntPtr.Zero, ref guid, 0, ref ifData))
return null;
diff --git a/src/RioJoy.Core/Output/SendInputSink.cs b/src/RioJoy.Core/Output/SendInputSink.cs
index 0b745a9..51a6981 100644
--- a/src/RioJoy.Core/Output/SendInputSink.cs
+++ b/src/RioJoy.Core/Output/SendInputSink.cs
@@ -69,7 +69,7 @@ public sealed class SendInputSink : IInputSink
}
private static void Send(INPUT input) =>
- SendInput(1, new[] { input }, Marshal.SizeOf());
+ SendInput(1, new[] { input }, Marshal.SizeOf(typeof(INPUT)));
// --- Win32 interop --------------------------------------------------------
diff --git a/src/RioJoy.Core/Output/ViGEmJoystickSink.cs b/src/RioJoy.Core/Output/ViGEmJoystickSink.cs
index 5d64aaf..357c192 100644
--- a/src/RioJoy.Core/Output/ViGEmJoystickSink.cs
+++ b/src/RioJoy.Core/Output/ViGEmJoystickSink.cs
@@ -1,3 +1,4 @@
+#if !NET40 // ViGEmBus is Win10+ only; the XP flavor uses RioGamepadXP (PLAN.md §8B)
using Nefarius.ViGEm.Client;
using Nefarius.ViGEm.Client.Targets;
using Nefarius.ViGEm.Client.Targets.Xbox360;
@@ -26,7 +27,7 @@ public sealed class ViGEmJoystickSink : IJoystickSink, IDisposable
};
/// Display names for the mappable buttons, aligned with .
- public static IReadOnlyList ButtonNames { get; } = new[]
+ public static IList ButtonNames { get; } = new[]
{
"A", "B", "X", "Y", "LB", "RB", "Back", "Start", "L3", "R3", "Guide",
};
@@ -125,3 +126,4 @@ public sealed class ViGEmJoystickSink : IJoystickSink, IDisposable
_client.Dispose();
}
}
+#endif
diff --git a/src/RioJoy.Core/Overlay/GoobieDataImporter.cs b/src/RioJoy.Core/Overlay/GoobieDataImporter.cs
index 342879d..65975e4 100644
--- a/src/RioJoy.Core/Overlay/GoobieDataImporter.cs
+++ b/src/RioJoy.Core/Overlay/GoobieDataImporter.cs
@@ -18,8 +18,8 @@ public static class GoobieDataImporter
{
/// A parsed data file: the field names and one label map per row.
public sealed record Sheet(
- IReadOnlyList Fields,
- IReadOnlyList> Rows);
+ IList Fields,
+ IList> Rows);
public static Sheet Load(string path) => Parse(File.ReadAllText(path));
@@ -33,7 +33,7 @@ public static class GoobieDataImporter
throw new FormatException("No lists found; expected a header list of field names.");
List fields = lists[0];
- var rows = new List>();
+ var rows = new List>();
for (int i = 1; i < lists.Count; i++)
{
@@ -51,7 +51,7 @@ public static class GoobieDataImporter
/// Convenience: the label map for a single row (default the first), with empty
/// values dropped — ready to hand to .
///
- public static IReadOnlyDictionary LabelsForRow(Sheet sheet, int rowIndex = 0)
+ public static IDictionary LabelsForRow(Sheet sheet, int rowIndex = 0)
{
if (sheet is null) throw new ArgumentNullException(nameof(sheet));
if (rowIndex < 0 || rowIndex >= sheet.Rows.Count)
diff --git a/src/RioJoy.Core/Overlay/OverlayHitTester.cs b/src/RioJoy.Core/Overlay/OverlayHitTester.cs
index f40209d..1a2c7ab 100644
--- a/src/RioJoy.Core/Overlay/OverlayHitTester.cs
+++ b/src/RioJoy.Core/Overlay/OverlayHitTester.cs
@@ -15,7 +15,7 @@ public static class OverlayHitTester
/// A region's rectangle is its stored cell geometry; label rotation does not
/// affect hit-testing.
///
- public static IReadOnlyList RegionsAt(OverlayTemplate template, double x, double y)
+ public static IList RegionsAt(OverlayTemplate template, double x, double y)
{
if (template is null) throw new ArgumentNullException(nameof(template));
@@ -33,7 +33,7 @@ public static class OverlayHitTester
///
public static OverlayRegion? NextAt(OverlayTemplate template, double x, double y, string? currentName)
{
- IReadOnlyList hits = RegionsAt(template, x, y);
+ IList hits = RegionsAt(template, x, y);
if (hits.Count == 0)
return null;
diff --git a/src/RioJoy.Core/Overlay/OverlayLayoutEngine.cs b/src/RioJoy.Core/Overlay/OverlayLayoutEngine.cs
index 0d774f8..b2d0386 100644
--- a/src/RioJoy.Core/Overlay/OverlayLayoutEngine.cs
+++ b/src/RioJoy.Core/Overlay/OverlayLayoutEngine.cs
@@ -20,9 +20,9 @@ public sealed class OverlayLayoutEngine
/// in (or with empty text) are skipped — matching the
/// legacy behavior where a blank field produced no visible text.
///
- public IReadOnlyList Layout(
+ public IList Layout(
OverlayTemplate template,
- IReadOnlyDictionary labels,
+ IDictionary labels,
OverlayLayoutOptions? options = null)
{
if (template is null) throw new ArgumentNullException(nameof(template));
diff --git a/src/RioJoy.Core/Profiles/AutoSwitch.cs b/src/RioJoy.Core/Profiles/AutoSwitch.cs
index 67bd2fd..5969d62 100644
--- a/src/RioJoy.Core/Profiles/AutoSwitch.cs
+++ b/src/RioJoy.Core/Profiles/AutoSwitch.cs
@@ -33,7 +33,10 @@ public readonly struct SwitchDecision : IEquatable
public bool Equals(SwitchDecision other) => Mode == other.Mode && ReferenceEquals(Profile, other.Profile);
public override bool Equals(object? obj) => obj is SwitchDecision d && Equals(d);
- public override int GetHashCode() => HashCode.Combine(Mode, Profile);
+ // Manual combine: HashCode.Combine needs Microsoft.Bcl.HashCode, which has
+ // no net40 build (Windows XP flavor).
+ public override int GetHashCode() =>
+ ((int)Mode * 397) ^ (Profile?.GetHashCode() ?? 0);
public override string ToString() => Mode == SwitchMode.Activate ? $"Activate({Profile?.Name})" : Mode.ToString();
}
diff --git a/src/RioJoy.Core/Profiles/AutoSwitchWatcher.cs b/src/RioJoy.Core/Profiles/AutoSwitchWatcher.cs
index 7c481f4..210c25c 100644
--- a/src/RioJoy.Core/Profiles/AutoSwitchWatcher.cs
+++ b/src/RioJoy.Core/Profiles/AutoSwitchWatcher.cs
@@ -62,7 +62,7 @@ public sealed class AutoSwitchWatcher
{
try
{
- await Task.Delay(interval, cancellationToken).ConfigureAwait(false);
+ await Compat.TaskCompat.Delay(interval, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
diff --git a/src/RioJoy.Core/Profiles/IniFile.cs b/src/RioJoy.Core/Profiles/IniFile.cs
index 2160715..bf7bb35 100644
--- a/src/RioJoy.Core/Profiles/IniFile.cs
+++ b/src/RioJoy.Core/Profiles/IniFile.cs
@@ -44,7 +44,7 @@ public sealed class IniFile
}
/// All key/value pairs in a section (empty if the section is absent).
- public IReadOnlyDictionary Section(string name) =>
+ public IDictionary Section(string name) =>
_sections.TryGetValue(name, out Dictionary? s)
? s
: new Dictionary();
diff --git a/src/RioJoy.Core/RioJoy.Core.csproj b/src/RioJoy.Core/RioJoy.Core.csproj
index b050b9b..2e3eec4 100644
--- a/src/RioJoy.Core/RioJoy.Core.csproj
+++ b/src/RioJoy.Core/RioJoy.Core.csproj
@@ -1,26 +1,47 @@
-
- net48
+
+ net48;net40
x64
- x64
enable
enable
latest
+
+
+ x64
+
+
+
+
+
+
+
-
-
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
diff --git a/src/RioJoy.Core/Serial/RioSerialLink.cs b/src/RioJoy.Core/Serial/RioSerialLink.cs
index 4ad4b73..2d283eb 100644
--- a/src/RioJoy.Core/Serial/RioSerialLink.cs
+++ b/src/RioJoy.Core/Serial/RioSerialLink.cs
@@ -69,19 +69,19 @@ public sealed class RioSerialLink
{
// If any running loop ends (transport closed / error / cancellation),
// tear the others down too.
- await Task.WhenAny(loops).ConfigureAwait(false);
+ await Compat.TaskCompat.WhenAny(loops).ConfigureAwait(false);
}
finally
{
linked.Cancel();
- await Task.WhenAll(loops.Select(Swallow)).ConfigureAwait(false);
+ await Compat.TaskCompat.WhenAll(loops.Select(Swallow)).ConfigureAwait(false);
}
}
/// Send a pre-built packet (see ) to the RIO.
public async Task SendAsync(byte[] packet, CancellationToken cancellationToken = default)
{
- await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+ await Compat.TaskCompat.WaitAsync(_writeLock, cancellationToken).ConfigureAwait(false);
try
{
await _transport.WriteAsync(packet, cancellationToken).ConfigureAwait(false);
@@ -191,7 +191,7 @@ public sealed class RioSerialLink
{
while (!ct.IsCancellationRequested)
{
- await Task.Delay(_options.AnalogPollInterval, ct).ConfigureAwait(false);
+ await Compat.TaskCompat.Delay(_options.AnalogPollInterval, ct).ConfigureAwait(false);
await RequestAnalogAsync(ct).ConfigureAwait(false);
diff --git a/src/RioJoy.Core/Serial/SerialPortTransport.cs b/src/RioJoy.Core/Serial/SerialPortTransport.cs
index cce6bd9..49c1617 100644
--- a/src/RioJoy.Core/Serial/SerialPortTransport.cs
+++ b/src/RioJoy.Core/Serial/SerialPortTransport.cs
@@ -74,7 +74,7 @@ public sealed class SerialPortTransport : IRioTransport
// still releases the handle.
}
- Task close = Task.Run(() =>
+ Task close = Compat.TaskCompat.Run(() =>
{
try { _port.Dispose(); }
catch { /* best-effort release */ }
diff --git a/src/RioJoy.Overlay/ProfileWallpaperGenerator.cs b/src/RioJoy.Overlay/ProfileWallpaperGenerator.cs
index 9079442..73a740f 100644
--- a/src/RioJoy.Overlay/ProfileWallpaperGenerator.cs
+++ b/src/RioJoy.Overlay/ProfileWallpaperGenerator.cs
@@ -28,7 +28,7 @@ public sealed class ProfileWallpaperGenerator
public string Generate(
OverlayTemplate template,
string templateDirectory,
- IReadOnlyDictionary labels,
+ IDictionary labels,
string outputPath,
OverlayLayoutOptions? options = null)
{
diff --git a/src/RioJoy.Overlay/SkiaOverlayRenderer.cs b/src/RioJoy.Overlay/SkiaOverlayRenderer.cs
index f20be11..de50444 100644
--- a/src/RioJoy.Overlay/SkiaOverlayRenderer.cs
+++ b/src/RioJoy.Overlay/SkiaOverlayRenderer.cs
@@ -31,7 +31,7 @@ public sealed class SkiaOverlayRenderer
///
public SKBitmap Render(
OverlayTemplate template,
- IReadOnlyDictionary labels,
+ IDictionary labels,
SKBitmap baseImage,
OverlayLayoutOptions? options = null)
{
@@ -95,7 +95,7 @@ public sealed class SkiaOverlayRenderer
/// Render and encode to PNG bytes.
public byte[] RenderToPng(
OverlayTemplate template,
- IReadOnlyDictionary labels,
+ IDictionary labels,
SKBitmap baseImage,
OverlayLayoutOptions? options = null)
{
@@ -110,7 +110,7 @@ public sealed class SkiaOverlayRenderer
///
public void RenderToFile(
OverlayTemplate template,
- IReadOnlyDictionary labels,
+ IDictionary labels,
string outputPath,
OverlayLayoutOptions? options = null)
{
diff --git a/src/RioJoy.Tray/Editor/PanelCanvas.cs b/src/RioJoy.Tray/Editor/PanelCanvas.cs
index 9b6429a..2e4fb00 100644
--- a/src/RioJoy.Tray/Editor/PanelCanvas.cs
+++ b/src/RioJoy.Tray/Editor/PanelCanvas.cs
@@ -10,7 +10,7 @@ namespace RioJoy.Tray.Editor;
///
internal sealed class PanelCanvas : Control
{
- private static readonly IReadOnlyList AllButtons = CockpitPanel.Buttons();
+ private static readonly IList AllButtons = CockpitPanel.Buttons();
public PanelCanvas()
{
diff --git a/src/RioJoy.Tray/Editor/PanelView.cs b/src/RioJoy.Tray/Editor/PanelView.cs
index f842c6d..c314ecd 100644
--- a/src/RioJoy.Tray/Editor/PanelView.cs
+++ b/src/RioJoy.Tray/Editor/PanelView.cs
@@ -40,7 +40,7 @@ public static class PanelView
public const int CalOriginCol = 2;
public const int CalOriginRow = 9;
- public static IReadOnlyList CalibrationCells { get; } = new[]
+ public static IList CalibrationCells { get; } = new[]
{
new CalibrationCell(JoyStickSetting.InvertX, "Inv X", 2, 10),
new CalibrationCell(JoyStickSetting.InvertY, "Inv Y", 2, 11),
@@ -51,7 +51,7 @@ public static class PanelView
new CalibrationCell(JoyStickSetting.EnableZR, "ZR mix", 2, 16),
};
- public static Size GridSize(IReadOnlyList buttons)
+ public static Size GridSize(IList buttons)
{
int maxCol = 0, maxRow = 0;
foreach (PanelButton b in buttons)
@@ -69,8 +69,8 @@ public static class PanelView
public static void Paint(
Graphics g,
- IReadOnlyList buttons,
- IReadOnlyList groups,
+ IList buttons,
+ IList groups,
Func label,
Func function,
Func lit,
@@ -225,7 +225,7 @@ public static class PanelView
}
}
- public static PanelButton? HitTest(IReadOnlyList buttons, Point p)
+ public static PanelButton? HitTest(IList buttons, Point p)
{
foreach (PanelButton b in buttons)
if (Cell(b.Col, b.Row).Contains(p))
diff --git a/tests/RioJoy.Core.Tests/Editing/CockpitPanelTests.cs b/tests/RioJoy.Core.Tests/Editing/CockpitPanelTests.cs
index e4cdfa1..3767945 100644
--- a/tests/RioJoy.Core.Tests/Editing/CockpitPanelTests.cs
+++ b/tests/RioJoy.Core.Tests/Editing/CockpitPanelTests.cs
@@ -50,7 +50,7 @@ public class CockpitPanelTests
public void Keypad_PhysicalLayout_MapsDigitsToAddresses()
{
// Internal keypad: top-left "1" -> 0x51, bottom-middle "0" -> 0x50, "C" -> 0x5C.
- IReadOnlyList all = CockpitPanel.Buttons();
+ IList all = CockpitPanel.Buttons();
PanelGroup pad = CockpitPanel.Groups.Single(g => g.Title == "Internal Keypad");
Assert.Equal(0x51, pad.Addresses[0]); // row0 col0 = "1"
diff --git a/tests/RioJoy.Core.Tests/Editing/SheetLayoutTests.cs b/tests/RioJoy.Core.Tests/Editing/SheetLayoutTests.cs
index a025a27..0db2ac1 100644
--- a/tests/RioJoy.Core.Tests/Editing/SheetLayoutTests.cs
+++ b/tests/RioJoy.Core.Tests/Editing/SheetLayoutTests.cs
@@ -47,7 +47,7 @@ public class SheetLayoutTests
"\"\",\"IsLit\",\"\",\"\",\"0F\"\n" +
"\"\",\"\",\"\",\"\",\"HOME\"";
- IReadOnlyList cells = SheetLayout.Parse(csv);
+ IList cells = SheetLayout.Parse(csv);
SheetCell addr = Assert.Single(cells, c => c.Text == "0F");
Assert.Equal(0x0F, addr.Address);
@@ -64,7 +64,7 @@ public class SheetLayoutTests
[Fact]
public void Parse_RealSheet_HasFullAddressMap()
{
- IReadOnlyList cells = SheetLayout.Load(
+ IList cells = SheetLayout.Load(
TestRepo.CustomBackground("config-sheet.csv"));
// Every RIO address 0x00..0x6F that exists should appear exactly once as a cell.
diff --git a/tests/RioJoy.Core.Tests/Overlay/GoobieDataImporterTests.cs b/tests/RioJoy.Core.Tests/Overlay/GoobieDataImporterTests.cs
index 547f154..870cfd4 100644
--- a/tests/RioJoy.Core.Tests/Overlay/GoobieDataImporterTests.cs
+++ b/tests/RioJoy.Core.Tests/Overlay/GoobieDataImporterTests.cs
@@ -26,7 +26,7 @@ public class GoobieDataImporterTests
[Fact]
public void LabelsForRow_DropsEmptyValues()
{
- IReadOnlyDictionary labels =
+ IDictionary labels =
GoobieDataImporter.LabelsForRow(GoobieDataImporter.Parse(Sample));
Assert.True(labels.ContainsKey("b-00"));
diff --git a/tests/RioJoy.Core.Tests/Overlay/OverlayHitTesterTests.cs b/tests/RioJoy.Core.Tests/Overlay/OverlayHitTesterTests.cs
index 4f2d287..29ea2f2 100644
--- a/tests/RioJoy.Core.Tests/Overlay/OverlayHitTesterTests.cs
+++ b/tests/RioJoy.Core.Tests/Overlay/OverlayHitTesterTests.cs
@@ -21,7 +21,7 @@ public class OverlayHitTesterTests
[Fact]
public void RegionsAt_ReturnsSmallestFirst()
{
- IReadOnlyList hits = OverlayHitTester.RegionsAt(Template(), 100, 50);
+ IList hits = OverlayHitTester.RegionsAt(Template(), 100, 50);
Assert.Equal(new[] { "small", "big" }, hits.Select(r => r.Name));
}
diff --git a/tests/RioJoy.Core.Tests/Overlay/OverlayLayoutEngineTests.cs b/tests/RioJoy.Core.Tests/Overlay/OverlayLayoutEngineTests.cs
index 0c66422..d585c7c 100644
--- a/tests/RioJoy.Core.Tests/Overlay/OverlayLayoutEngineTests.cs
+++ b/tests/RioJoy.Core.Tests/Overlay/OverlayLayoutEngineTests.cs
@@ -133,7 +133,7 @@ public class OverlayLayoutEngineTests
["b-99"] = "ORPHAN", // no such region -> ignored
};
- IReadOnlyList placed = Engine.Layout(template, labels);
+ IList placed = Engine.Layout(template, labels);
Assert.Equal(2, placed.Count);
Assert.Equal("b-00", placed[0].RegionName);
diff --git a/tests/RioJoy.Core.Tests/Overlay/OverlayRenderIntegrationTests.cs b/tests/RioJoy.Core.Tests/Overlay/OverlayRenderIntegrationTests.cs
index c43e622..f8f277b 100644
--- a/tests/RioJoy.Core.Tests/Overlay/OverlayRenderIntegrationTests.cs
+++ b/tests/RioJoy.Core.Tests/Overlay/OverlayRenderIntegrationTests.cs
@@ -35,7 +35,7 @@ public class OverlayRenderIntegrationTests
Assert.Equal(template.Width, baseImg.Width);
Assert.Equal(template.Height, baseImg.Height);
- IReadOnlyDictionary labels =
+ IDictionary labels =
GoobieDataImporter.LabelsForRow(GoobieDataImporter.Load(TestRepo.CustomBackground("TEST.data")));
using SKBitmap rendered = new SkiaOverlayRenderer().Render(template, labels, baseImg);