using System.Linq;
using AcDream.Core.Physics;
using AcDream.Core.Tests.Conformance;
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Options;
using Xunit;
namespace AcDream.Core.Tests.Physics;
///
/// Real-DAT availability tests for the L.1b dual command catalog. A command
/// only actually ANIMATES if the entity's MotionTable has a
/// Links or Modifiers entry for the full 32-bit value — an
/// enum/table value alone proves nothing about whether the local DATs will
/// ever play it.
///
///
/// Reproduces the scan from
/// docs/research/2026-06-26-ace-vs-2013-motion-command-gap.md as
/// pinned assertions: a command "exists" for a MotionTable when its full
/// 32-bit value appears as either (a) an outer Links key's low 16
/// bits (the from-state -> command transition key), (b) an inner
/// Links[...].MotionData key (the actual animation-bearing target),
/// or (c) a Modifiers key. This three-way check is what reproduces
/// the gap doc's hit counts exactly (verified against a live scan of the
/// local client_portal.dat: LifestoneRecall/ACE=19,
/// MarketplaceRecall/ACE=19, AllegianceHometownRecall/ACE=19,
/// OffhandSlashHigh/ACE=31, HouseRecall/ACE=24 tables; all four
/// 2013-numbered equivalents=0 tables; HouseRecall/2013=42 tables, i.e.
/// "both exist" for HouseRecall specifically).
///
///
///
/// Gated the same way as the existing DAT-backed conformance suite — see
/// (also mirrored by
/// DoorBugTrajectoryReplayTests.ResolveDatDir): resolve
/// ACDREAM_DAT_DIR or the default Documents\Asheron's Call
/// path, and skip cleanly (return, no failure) when the dats aren't
/// present (CI has no local AC install).
///
///
public class MotionCommandCatalogDatTests
{
///
/// True if any local MotionTable can actually animate
/// — the from-state transition key
/// (Links outer, low 16 bits), the animation-bearing inner key
/// (Links[...].MotionData), or a Modifiers entry.
///
private static bool ExistsInAnyMotionTable(DatCollection dats, uint fullCommand)
{
ushort low16 = (ushort)(fullCommand & 0xFFFFu);
int fullAsInt = unchecked((int)fullCommand);
foreach (var id in dats.GetAllIdsOfType())
{
var mt = dats.Get(id);
if (mt is null) continue;
bool outerLow = mt.Links.Keys.Any(k => (k & 0xFFFF) == low16);
if (outerLow) return true;
bool innerFull = mt.Links.Values.Any(md => md.MotionData.ContainsKey(fullAsInt));
if (innerFull) return true;
if (mt.Modifiers.ContainsKey(fullAsInt)) return true;
}
return false;
}
[Fact]
public void AceShiftedRecallCommands_ExistInLocalMotionTables()
{
var datDir = ConformanceDats.ResolveDatDir();
if (datDir is null) return;
using var dats = new DatCollection(datDir, DatAccessType.Read);
Assert.True(ExistsInAnyMotionTable(dats, 0x10000153u), "LifestoneRecall (ACE 0x10000153) must exist in local DAT MotionTables");
Assert.True(ExistsInAnyMotionTable(dats, 0x1000013Au), "HouseRecall (ACE 0x1000013A) must exist in local DAT MotionTables");
}
[Fact]
public void TwoThousandThirteenLifestoneAndHouseRecall_AlsoExist()
{
var datDir = ConformanceDats.ResolveDatDir();
if (datDir is null) return;
using var dats = new DatCollection(datDir, DatAccessType.Read);
// LifestoneRecall's 2013 numbering (0x10000150) does NOT exist locally —
// only the ACE-shifted value animates. HouseRecall's 2013 numbering
// (0x10000137) DOES exist (the gap doc: "both exist; the old value is
// very common").
Assert.False(ExistsInAnyMotionTable(dats, 0x10000150u), "LifestoneRecall (2013 0x10000150) unexpectedly found");
Assert.True(ExistsInAnyMotionTable(dats, 0x10000137u), "HouseRecall (2013 0x10000137) must also exist in local DAT MotionTables");
}
[Theory]
[InlineData(0x10000166u)] // MarketplaceRecall (ACE)
[InlineData(0x10000171u)] // AllegianceHometownRecall (ACE)
[InlineData(0x10000173u)] // OffhandSlashHigh (ACE)
public void AceOnlyCommands_ExistOnlyUnderShiftedIds(uint aceFullCommand)
{
var datDir = ConformanceDats.ResolveDatDir();
if (datDir is null) return;
using var dats = new DatCollection(datDir, DatAccessType.Read);
Assert.True(ExistsInAnyMotionTable(dats, aceFullCommand),
$"ACE value 0x{aceFullCommand:X8} must exist in local DAT MotionTables");
}
[Theory]
[InlineData(0x10000163u)] // MarketplaceRecall (2013)
[InlineData(0x1000016Eu)] // AllegianceHometownRecall (2013)
[InlineData(0x10000170u)] // OffhandSlashHigh (2013)
public void TwoThousandThirteenOnlyValues_HaveZeroLinkHits(uint retail2013FullCommand)
{
var datDir = ConformanceDats.ResolveDatDir();
if (datDir is null) return;
using var dats = new DatCollection(datDir, DatAccessType.Read);
Assert.False(ExistsInAnyMotionTable(dats, retail2013FullCommand),
$"2013 value 0x{retail2013FullCommand:X8} unexpectedly found in local DAT MotionTables");
}
}