acdream/tests/AcDream.Core.Tests/Input/RetailActionIdentityRoundTripTests.cs
Erik f6fe0f2a4f
All checks were successful
CI / linux-portable (push) Successful in 3m27s
CI / windows-gate (push) Successful in 6m42s
CI / release (push) Successful in 2m12s
fix(client): restore retail interaction parity
Harden keyboard and camera routing, inventory and vendor interactions, chat/emotes, relog portal flow, and paperdoll rendering. Add retail research, connected gate coverage, and release-gate validation.
2026-08-26 20:45:11 +02:00

108 lines
4.6 KiB
C#

using System.Collections.Generic;
using System.Linq;
using AcDream.Content;
using AcDream.Core.Input;
using AcDream.Runtime.Gameplay;
using AcDream.UI.Abstractions.Input;
using DatReaderWriter;
using Xunit;
namespace AcDream.Core.Tests.Input;
/// <summary>
/// Campaign KB's installed-DAT contract: all 306 user-bindable ActionMap rows
/// have distinct live identities and their default chord sets match the two
/// retail MasterInputMaps exactly. Missing, aliased, or guessed rows fail here.
/// </summary>
[Trait("Lane", "InstalledDat")]
public sealed class RetailActionIdentityRoundTripTests
{
[Fact]
public void MappedActions_DatUnionDefaultBindings_MatchRetailDefaults()
{
string? datDir = Conformance.ConformanceDats.ResolveDatDir();
if (datDir is null) Assert.Fail("Lane=InstalledDat requires an installed retail DAT directory; see docs/release-gate.md.");
using var dats = new DatCollection(datDir);
var source = new DatCollectionAdapter(dats);
RetailActionMapSnapshot? snapshot = RetailActionMapReader.Read(source);
Assert.NotNull(snapshot);
Assert.Equal(306, snapshot!.Rows.Count);
var unresolvedRows = snapshot.Rows
.Where(row => !RetailActionIdentityTable.TryResolve(
row.InputMapId,
row.ActionId,
out _))
.Select(row => $"0x{row.InputMapId:X8}/0x{row.ActionId:X8}")
.ToArray();
Assert.Empty(unresolvedRows);
Assert.Equal(306, RetailActionIdentityTable.Map.Count);
Assert.Equal(306, RetailActionIdentityTable.Map.Values.Distinct().Count());
Assert.Equal(306, RetailActionIdentityTable.ReverseMap.Count);
var optionIds = new HashSet<uint>();
foreach (RetailActionMapRow row in snapshot.Rows.Where(
static row => row.InputMapId == 0x10000008u))
{
Assert.True(RetailActionIdentityTable.TryResolve(
row.InputMapId,
row.ActionId,
out InputAction action));
Assert.True(
RetailActionIdentityTable.TryGetCharacterOptionId(
action,
out uint optionId),
$"CharacterSettings row 0x{row.ActionId:X8} has no PlayerOption id");
Assert.True(CharacterOptionTable.TryGet(optionId, out _));
Assert.True(optionIds.Add(optionId), $"duplicate PlayerOption id 0x{optionId:X2}");
}
Assert.Equal(48, optionIds.Count);
KeyBindings retailDefaults = KeyBindings.RetailDefaults();
// Aggregate DAT default chords by resolved InputAction — a single action can
// be reached by more than one DAT row (e.g. the Camera/CameraAlternate pair).
var datChordsByAction = new Dictionary<InputAction, HashSet<KeyChord>>();
var unresolvedScanCodes = new List<string>();
foreach (RetailActionMapRow row in snapshot.Rows)
{
if (!RetailActionIdentityTable.TryResolve(row.InputMapId, row.ActionId, out InputAction action))
continue;
if (!datChordsByAction.TryGetValue(action, out HashSet<KeyChord>? set))
datChordsByAction[action] = set = new HashSet<KeyChord>();
foreach (RetailKeyChord raw in row.DefaultBindings)
{
Silk.NET.Input.Key? key = RetailScanCodeMap.ToSilkKey(raw.Scan, raw.Device);
if (key is null)
{
unresolvedScanCodes.Add(
$"{action}: DAT default scan=0x{raw.Scan:X2} dev={raw.Device} has no "
+ "RetailScanCodeMap entry");
continue;
}
set.Add(new KeyChord(key.Value, RetailScanCodeMap.ToModifierMask(raw.Modifier), (byte)raw.Device));
}
}
Assert.Equal(306, datChordsByAction.Count);
Assert.Empty(unresolvedScanCodes);
var mismatches = new List<string>();
foreach ((InputAction action, HashSet<KeyChord> datChords) in datChordsByAction)
{
var acdreamChords = retailDefaults.ForAction(action).Select(b => b.Chord).ToHashSet();
if (!datChords.SetEquals(acdreamChords))
{
mismatches.Add(
$"{action}: DAT union=[{string.Join(",", datChords)}] vs "
+ $"RetailDefaults()=[{string.Join(",", acdreamChords)}]");
}
}
Assert.True(mismatches.Count == 0,
$"{mismatches.Count} DAT-vs-RetailDefaults() disagreements:\n"
+ string.Join("\n", mismatches));
}
}