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"); } }