acdream/tests/AcDream.Content.Tests/InstalledMagicCatalogFociTests.cs
Erik 255b0aaeda
All checks were successful
CI / linux-portable (push) Successful in 3m18s
CI / windows-gate (push) Successful in 5m23s
CI / release (push) Successful in 2m3s
fix(magic): the foci map never loaded — casts demanded full components a focus should waive
User report: with the correct focus, scarab and tapers in the pack, a level 1
spell would not cast, and the spell examine window listed the full legacy
component recipe where retail shows only scarab and taper. The question asked
was whether the 2013 client data is too old for foci. It is not -- the EoR
dats carry the map, and the whole client-side mechanism (the requirement
service, the scarab-only formula port, the examine-window routing) was already
built and wired. It was fed an empty table.

MagicCatalog resolved the school-to-foci map with
Resolve(enumValue: 0x10000001, enumCategory: 0x28). Retail's
SpellComponentTable::SchoolOfMagic2WCID @ 0x005BC1F0 calls
DBObj::GetByEnum(0x10000001, 4): master map -> category 0x10000001 -> key 4
-> the school->WCID EnumIDMap. The 0x28 on that call is the EnumIDMap DBTYPE
tag, and it had been read as a lookup category. The master map has no
category 0x28, the resolver returned 0, and the foci map loaded EMPTY --
silently, so a carried focus was never detected: HasRequiredComponents
demanded the full account-customized formula (refusing the cast) and
GetExamineComponents displayed it.

Found by measurement rather than re-reading the code: a SpellDump --foci probe
proved category 0x28 absent, then brute-forced the portal enum tree for ACE's
FociWCIDs and found them at 0x27000003 under category 0x10000001 key 4:

    school 1 -> 15271 Foci of Strife       (War)
    school 2 -> 15270 Foci of Verdancy     (Life)
    school 3 -> 15269 Foci of Artifice     (Item)
    school 4 -> 15268 Foci of Enchantment  (Creature)
    school 5 -> 43173 Foci of Shadow       (Void)

The new Lane=InstalledDat test pins exactly that: the loaded catalog must map
every school to ACE's FociWCIDs -- external constants from the server-side
authority, deliberately not derived from the code under test, so an empty or
wrongly-resolved map cannot pass vacuously.

The infusion-augmentation half of the retail gate (properties 0x126-0x129,
0x148) was already correct against the decomp, as were the scarab-only ID set
{1..6, 0x6E, 0x6F, 0x70, 0xC0, 0xC1} and the taper-count table.

Complete Release suite: 14,469 tests pass on the standard hermetic lane
filter, 0 failures; the new installed-DAT test passes against the real dats.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:28:22 +02:00

50 lines
2.2 KiB
C#

using AcDream.Content;
using DatReaderWriter;
using DatReaderWriter.Options;
namespace AcDream.Content.Tests;
/// <summary>
/// Pins the school-to-foci-WCID map against the installed retail DATs.
/// </summary>
/// <remarks>
/// <para>
/// Retail <c>SpellComponentTable::SchoolOfMagic2WCID @ 0x005BC1F0</c> resolves
/// master map → category <c>0x10000001</c> → key <c>4</c> → the school→WCID
/// <see cref="DatReaderWriter.DBObjs.EnumIDMap"/> (<c>0x27000003</c> in the
/// EoR dats). The catalog used to pass the decomp's <c>0x28</c> — the
/// EnumIDMap DBTYPE tag on that call — as the lookup category, which resolved
/// nothing: the map loaded EMPTY, a carried focus was never detected, and
/// every cast demanded the full component list while retail (and the spell
/// examine window there) asked only for scarabs and tapers.
/// </para>
/// <para>
/// The expected WCIDs are EXTERNAL constants — ACE's
/// <c>Player_Spells.FociWCIDs</c>, the server-side authority acdream must
/// agree with — deliberately not derived from the code under test, so an
/// empty or wrongly-resolved map cannot satisfy this vacuously.
/// </para>
/// </remarks>
[Trait("Lane", "InstalledDat")]
public sealed class InstalledMagicCatalogFociTests
{
[Fact]
public void FociMap_ResolvesEveryRetailSchoolToAcesFociWcid()
{
string? datDir = ContentConformanceDats.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, DatAccessType.Read);
using var adapter = new DatCollectionAdapter(dats);
MagicCatalog catalog = MagicCatalog.Load(adapter);
// ACE Player_Spells.FociWCIDs: War=Strife, Life=Verdancy,
// Item=Artifice, Creature=Enchantment, Void=Shadow.
Assert.Equal(15271u, catalog.MagicPackWcidForSchool(1u));
Assert.Equal(15270u, catalog.MagicPackWcidForSchool(2u));
Assert.Equal(15269u, catalog.MagicPackWcidForSchool(3u));
Assert.Equal(15268u, catalog.MagicPackWcidForSchool(4u));
Assert.Equal(43173u, catalog.MagicPackWcidForSchool(5u));
}
}