feat(D.5.2): IconComposer.ResolveEffectDid (effect submap 0x10000005)
Add effect-overlay submap resolve: EnsureEffectSubMap walks the portal MasterMap (0x25000000) → EnumIDMap 0x10000005 → submap 0x25000009; ResolveEffectDid(effects) maps LowestSetBit(effects)+1 → RenderSurface DID with fallback to index 0x21. Golden test validates all 6 cases (Magical/Poisoned/BoostHealth/BoostStamina/Nether/zero) against the live dat. Retail ref: IconData::RenderIcons 0x0058d180. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e7b6e83cf8
commit
75ac51ac23
2 changed files with 66 additions and 0 deletions
|
|
@ -43,6 +43,15 @@ public sealed class IconComposer
|
|||
private bool _underlayResolveTried;
|
||||
private readonly Dictionary<uint, uint> _underlayDidByIndex = new();
|
||||
|
||||
// ── effect overlay resolve (EnumIDMap 0x10000005) ────────────────────────
|
||||
// Portal MasterMap (0x25000000) maps enum 0x10000005 → submap DID (0x25000009).
|
||||
// Submap maps index → 0x06 RenderSurface DID. index = LSB(effects)+1, fallback 0x21.
|
||||
// Refs: IconData::RenderIcons 0x0058d180 (effect path); the effect tile is a
|
||||
// ReplaceColor tint SOURCE, not a blit layer (see RESOLVED doc, divergence DR-1).
|
||||
private EnumIDMap? _effectSubMap;
|
||||
private bool _effectResolveTried;
|
||||
private readonly Dictionary<uint, uint> _effectDidByIndex = new();
|
||||
|
||||
public IconComposer(DatCollection dats, TextureCache cache)
|
||||
{
|
||||
_dats = dats;
|
||||
|
|
@ -84,6 +93,38 @@ public sealed class IconComposer
|
|||
if (_dats.Portal.TryGet<EnumIDMap>(subDid, out var sub)) _underlaySubMap = sub;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve the effect-overlay DID for <paramref name="effects"/> via the EnumIDMap
|
||||
/// 0x10000005 chain. index = LowestSetBit(effects)+1; if the entry is missing/zero,
|
||||
/// retail falls back to index 0x21 (the solid-black tile). NOTE: the effect path has
|
||||
/// NO lsb==-1 pre-check (unlike the type underlay), so effects==0 → index 0 → miss →
|
||||
/// fallback. (Retail IconData::RenderIcons 0x0058d180.)
|
||||
/// </summary>
|
||||
internal uint ResolveEffectDid(uint effects)
|
||||
{
|
||||
int lsb = effects == 0 ? -1 : BitOperations.TrailingZeroCount(effects);
|
||||
uint index = (uint)(lsb + 1);
|
||||
if (_effectDidByIndex.TryGetValue(index, out var cached)) return cached;
|
||||
EnsureEffectSubMap();
|
||||
uint did = 0;
|
||||
if (_effectSubMap is { } sub && sub.ClientEnumToID.TryGetValue(index, out var d)) did = d;
|
||||
if (did == 0 && _effectSubMap is { } sub2 && sub2.ClientEnumToID.TryGetValue(0x21u, out var fb))
|
||||
did = fb;
|
||||
_effectDidByIndex[index] = did;
|
||||
return did;
|
||||
}
|
||||
|
||||
private void EnsureEffectSubMap()
|
||||
{
|
||||
if (_effectResolveTried) return;
|
||||
_effectResolveTried = true;
|
||||
uint masterDid = (uint)_dats.Portal.Header.MasterMapId; // = 0x25000000
|
||||
if (masterDid == 0) return;
|
||||
if (!_dats.Portal.TryGet<EnumIDMap>(masterDid, out var master)) return;
|
||||
if (!master.ClientEnumToID.TryGetValue(0x10000005u, out var subDid)) return; // → 0x25000009
|
||||
if (_dats.Portal.TryGet<EnumIDMap>(subDid, out var sub)) _effectSubMap = sub;
|
||||
}
|
||||
|
||||
/// <summary>Pure alpha-over composite, bottom->top. Layers may differ in size;
|
||||
/// the result is sized to the FIRST (bottom) layer and upper layers are sampled
|
||||
/// top-left aligned (all icon layers are 32x32 in practice).</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue