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>
This commit is contained in:
parent
cd6eefd0ba
commit
255b0aaeda
3 changed files with 123 additions and 4 deletions
|
|
@ -32,6 +32,60 @@ if (args.Length > 0 && args[0] == "--cursors")
|
|||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--foci")
|
||||
{
|
||||
// Retail SpellComponentTable::SchoolOfMagic2WCID @ 0x005BC1F0: master
|
||||
// enum map 0x10000001, category 0x28 -> per-school foci WCID. Dump it so
|
||||
// the client's foci detection can be checked against ACE's FociWCIDs
|
||||
// (15268..15271, 43173) instead of assumed.
|
||||
uint masterDid2 = (uint)dats.Portal.Header.MasterMapId;
|
||||
if (!dats.Portal.TryGet<DatReaderWriter.DBObjs.EnumIDMap>(masterDid2, out var master2) || master2 is null)
|
||||
throw new InvalidOperationException("no master enum map");
|
||||
Console.WriteLine($"master did=0x{masterDid2:X8}, categories: "
|
||||
+ string.Join(", ", master2.ClientEnumToID.Keys.OrderBy(k => k).Select(k => $"0x{k:X}")));
|
||||
// ACE's FociWCIDs are the ground truth for what the map must contain:
|
||||
uint[] fociWcids = [15268, 15269, 15270, 15271, 43173];
|
||||
foreach (var cat in master2.ClientEnumToID.OrderBy(k => k.Key))
|
||||
{
|
||||
DatReaderWriter.DBObjs.EnumIDMap? sub = null;
|
||||
try
|
||||
{
|
||||
dats.Portal.TryGet<DatReaderWriter.DBObjs.EnumIDMap>(cat.Value, out sub);
|
||||
}
|
||||
catch { }
|
||||
if (sub is null) continue;
|
||||
// one level: does this sub-map itself map schools to foci wcids?
|
||||
if (sub.ClientEnumToID.Values.Any(v => fociWcids.Contains(v)))
|
||||
{
|
||||
Console.WriteLine($"HIT level1 cat=0x{cat.Key:X} did=0x{cat.Value:X8}");
|
||||
foreach (var kv in sub.ClientEnumToID.OrderBy(k => k.Key))
|
||||
Console.WriteLine($" {kv.Key} -> {kv.Value}");
|
||||
}
|
||||
// two levels: category -> sub-map -> did of another EnumIDMap
|
||||
foreach (var kv2 in sub.ClientEnumToID)
|
||||
{
|
||||
DatReaderWriter.DBObjs.EnumIDMap? sub2 = null;
|
||||
try
|
||||
{
|
||||
dats.Portal.TryGet<DatReaderWriter.DBObjs.EnumIDMap>(kv2.Value, out sub2);
|
||||
}
|
||||
catch { } // not every mapped DID is an EnumIDMap
|
||||
if (sub2 is null) continue;
|
||||
if (sub2.ClientEnumToID.Values.Any(v => fociWcids.Contains(v)))
|
||||
{
|
||||
Console.WriteLine($"HIT level2 cat=0x{cat.Key:X} key=0x{kv2.Key:X} did=0x{kv2.Value:X8}");
|
||||
foreach (var kv in sub2.ClientEnumToID.OrderBy(k => k.Key))
|
||||
Console.WriteLine($" {kv.Key} -> {kv.Value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
// and the school ids the spell table itself uses, for the same check
|
||||
foreach (uint id in new uint[] { 0x0006, 0x001C, 0x022D, 0x0545, 0x14C7 })
|
||||
if (table.TryGet(id, out var sm))
|
||||
Console.WriteLine($" spell 0x{id:X4} {sm.Name,-36} schoolId={sm.SchoolId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--flags")
|
||||
{
|
||||
string want = args.Length > 1 ? args[1].ToLowerInvariant() : "bane";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue