fix(ui): D.2b empty-slot art — container cells use inner slot bg, not the selected-container triangle
Visual gate (2026-06-22) showed the side-bag/main-pack empty cells drawing yellow triangles. Root cause: the frame-first heuristic grabbed the 36x36 container prototype's DirectState child 0x06005D9C — which is the open/SELECTED-container triangle indicator, NOT a background frame — and stamped it onto every empty cell. Fix: drop frame-first; FindIconEmpty resolves the inner m_elem_Icon ItemSlot_Empty THROUGH BaseElement inheritance (0x1000033F -> 0x10000340 -> base 0x1000033E -> 0x1000033B -> 0x06000F6E), so containers get the dark slot background matching the inventory. Pin test now asserts exact ids (0x06004D20 contents / 0x06000F6E containers) — the old structural-only assertion let the wrong-but-valid 0x06005D9C pass (Opus review Minor 2, now vindicated). The triangle + green/yellow selection square is deferred to container-switching (AP-56 reworded). Full suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
499485a672
commit
22d92315e5
4 changed files with 53 additions and 16 deletions
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
|
|
@ -44,11 +45,15 @@ public static class ItemListCellTemplate
|
|||
var proto = FindDesc(catalog, protoId);
|
||||
if (proto is null) return 0;
|
||||
|
||||
// Priority: a child's DirectState background frame (the 36x36 backpack cell's recessed
|
||||
// border, e.g. 0x06005D9C); else the m_elem_Icon child's ItemSlot_Empty media (the 32x32
|
||||
// contents cell, e.g. 0x06004D20). See the spec's explicit single-sprite contract.
|
||||
uint frame = FirstChildDirectStateImage(proto);
|
||||
return frame != 0 ? frame : IconChildEmptyImage(proto);
|
||||
// The empty-slot BACKGROUND is the m_elem_Icon's ItemSlot_Empty media, resolved THROUGH
|
||||
// BaseElement inheritance: the 36x36 container prototype (0x1000033F) nests its icon behind
|
||||
// an inner wrapper (0x10000340 -> base 0x1000033E -> 0x1000033B -> 0x06000F6E), so a raw
|
||||
// child search alone returns nothing for containers. We deliberately do NOT use the
|
||||
// prototype's DirectState child overlay: on the container prototype that child is the
|
||||
// open/selected-container TRIANGLE indicator (0x06005D9C), which retail draws ONLY on the
|
||||
// selected container (a deferred container-selection feature) — never as empty-cell art.
|
||||
// (Live visual gate 2026-06-22: frame-first stamped the triangle onto every empty cell.)
|
||||
return FindIconEmpty(catalog, proto, new HashSet<uint>());
|
||||
}
|
||||
|
||||
// ── attribute 0x1000000e: stored as EnumBaseProperty in the dat (Value is the prototype id) ──
|
||||
|
|
@ -80,28 +85,44 @@ public static class ItemListCellTemplate
|
|||
return 0;
|
||||
}
|
||||
|
||||
// ── prototype media ──
|
||||
private static uint FirstChildDirectStateImage(ElementDesc proto)
|
||||
// ── prototype media: the m_elem_Icon (0x1000033B) ItemSlot_Empty, resolved through inheritance ──
|
||||
// Find the icon child's empty media within `element`'s subtree, following BaseElement edges
|
||||
// within the same catalog (cycle-guarded by `baseSeen`). The 32x32 contents prototype carries
|
||||
// 0x1000033B as a direct child; the 36x36 container prototype reaches it only via an inherited
|
||||
// base (0x10000340 -> 0x1000033E), so a raw child search alone returns nothing for containers.
|
||||
private static uint FindIconEmpty(LayoutDesc catalog, ElementDesc element, HashSet<uint> baseSeen)
|
||||
{
|
||||
foreach (var kv in proto.Children)
|
||||
if (element.ElementId == IconChildId)
|
||||
{
|
||||
uint f = DirectStateImage(kv.Value);
|
||||
if (f != 0) return f;
|
||||
uint e = IconEmptyState(element);
|
||||
if (e != 0) return e;
|
||||
}
|
||||
foreach (var kv in element.Children)
|
||||
{
|
||||
uint e = FindIconEmpty(catalog, kv.Value, baseSeen);
|
||||
if (e != 0) return e;
|
||||
}
|
||||
if (element.BaseElement != 0 && element.BaseLayoutId == CatalogLayoutId && baseSeen.Add(element.BaseElement))
|
||||
{
|
||||
var baseDesc = FindDesc(catalog, element.BaseElement);
|
||||
if (baseDesc is not null)
|
||||
{
|
||||
uint e = FindIconEmpty(catalog, baseDesc, baseSeen);
|
||||
if (e != 0) return e;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static uint IconChildEmptyImage(ElementDesc proto)
|
||||
private static uint IconEmptyState(ElementDesc icon)
|
||||
{
|
||||
var icon = FindDescIn(proto, IconChildId); // recursive: handles inner wrappers (e.g. 0x10000340)
|
||||
if (icon is null) return 0;
|
||||
foreach (var s in icon.States)
|
||||
if (s.Key.ToString() == ItemSlotEmpty)
|
||||
{
|
||||
uint f = StateImage(s.Value);
|
||||
if (f != 0) return f;
|
||||
}
|
||||
return DirectStateImage(icon); // some prototypes carry empty media on the DirectState
|
||||
return DirectStateImage(icon); // some icons carry empty media on the DirectState
|
||||
}
|
||||
|
||||
private static uint DirectStateImage(ElementDesc d)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue