fix(ui): D.2b-B — inventory panels render over the backdrop (#145 continuation) + captions
Visual verification surfaced two render bugs (controller LOGIC was already correct): 1. BACKDROP WASH-OUT (the big one — #145 continuation). The mounted backpack/ 3D-items panels inherited their sub-window root's ZLevel 1000 via the merge's zero-wins-base rule. The #145 ZOrder fold (ReadOrder − ZLevel·10000) turned 1000 into ZOrder ≈ −10,000,000 — sinking the panels BEHIND the frame's Alphablend backdrop (ZLevel 100 → ≈ −1,000,000). The backdrop then overpainted the panels' captions/burden-meter/cells (the paperdoll root is ZLevel 0 so it escaped, which is why the previous session thought #145 was done). Fix: the sub-window mount now keeps each slot's OWN frame ZLevel, so panels sit in front of the backdrop. Root-caused via a one-shot sprite-segment-order dump (backdrop was painting after the panel content) + a live ZLevel probe. 2. CAPTIONS. The caption elements resolve to UiText; driving a nested child UiText didn't paint. AttachCaption now drives the host UiText directly. Locked by InventoryFrameImportProbe (real-dat smoke: asserts each mounted panel's ZOrder > the backdrop's). Visually confirmed by the user: dark backdrop behind, Burden 17% + vertical bar + Contents-of-Backpack + full item grid all visible. Build + App(532)/Core(1526) tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1ccf07b705
commit
417b1375fd
3 changed files with 108 additions and 3 deletions
|
|
@ -82,9 +82,9 @@ public sealed class InventoryController
|
||||||
_burdenMeter.Fill = () => _burdenFill;
|
_burdenMeter.Fill = () => _burdenFill;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Captions: attach a centered UiText child carrying the known string. "Contents of
|
// Captions: drive each host UiText directly with the known string (the caption
|
||||||
// Backpack" + "%d%%" are procedural in retail (gm3DItemsUI/gmBackpackUI PostInit/
|
// elements resolve to UiText). "Contents of Backpack" + "%d%%" are procedural in retail
|
||||||
// SetLoadLevel); "Burden" is the dat label. (Caption pass, spec §5.)
|
// (gm3DItemsUI/gmBackpackUI PostInit/SetLoadLevel); "Burden" is the dat label. (Spec §5.)
|
||||||
AttachCaption(layout.FindElement(BurdenCaptionId), () => "Burden", datFont);
|
AttachCaption(layout.FindElement(BurdenCaptionId), () => "Burden", datFont);
|
||||||
AttachCaption(layout.FindElement(ContentsCaptionId), () => "Contents of Backpack", datFont);
|
AttachCaption(layout.FindElement(ContentsCaptionId), () => "Contents of Backpack", datFont);
|
||||||
AttachCaption(layout.FindElement(BurdenTextId), () => _burdenPercent + "%", datFont);
|
AttachCaption(layout.FindElement(BurdenTextId), () => _burdenPercent + "%", datFont);
|
||||||
|
|
@ -168,6 +168,30 @@ public sealed class InventoryController
|
||||||
private void AttachCaption(UiElement? host, Func<string> text, UiDatFont? datFont)
|
private void AttachCaption(UiElement? host, Func<string> text, UiDatFont? datFont)
|
||||||
{
|
{
|
||||||
if (host is null) return;
|
if (host is null) return;
|
||||||
|
|
||||||
|
// The caption elements (0x100001D7 "Burden", 0x100001C5 "Contents of Backpack",
|
||||||
|
// 0x100001D8 "%") resolve to UiText (Type-0 inheriting a text base — confirmed live).
|
||||||
|
// Drive the host UiText DIRECTLY: it is already in the paint tree and renders, whereas
|
||||||
|
// a nested child UiText did not paint. Set it to a static centered single-line label.
|
||||||
|
if (host is UiText t)
|
||||||
|
{
|
||||||
|
t.Centered = true;
|
||||||
|
t.DatFont = datFont;
|
||||||
|
t.ClickThrough = true;
|
||||||
|
t.AcceptsFocus = false;
|
||||||
|
t.IsEditControl = false;
|
||||||
|
t.CapturesPointerDrag = false;
|
||||||
|
t.LinesProvider = () =>
|
||||||
|
{
|
||||||
|
var s = text();
|
||||||
|
return string.IsNullOrEmpty(s)
|
||||||
|
? Array.Empty<UiText.Line>()
|
||||||
|
: new[] { new UiText.Line(s, CaptionColor) };
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback (non-UiText host): attach a centered label child.
|
||||||
var label = new UiText
|
var label = new UiText
|
||||||
{
|
{
|
||||||
Left = 0f, Top = 0f, Width = host.Width, Height = host.Height,
|
Left = 0f, Top = 0f, Width = host.Width, Height = host.Height,
|
||||||
|
|
|
||||||
|
|
@ -242,8 +242,21 @@ public static class LayoutImporter
|
||||||
// (vitals/chat/toolbar). See ShouldMountBaseChildren + the B-Grid spec.
|
// (vitals/chat/toolbar). See ShouldMountBaseChildren + the B-Grid spec.
|
||||||
if (baseChildren is not null
|
if (baseChildren is not null
|
||||||
&& ShouldMountBaseChildren(d.Children.Count, self.StateMedia.Count, baseChildren.Count))
|
&& ShouldMountBaseChildren(d.Children.Count, self.StateMedia.Count, baseChildren.Count))
|
||||||
|
{
|
||||||
result.Children.AddRange(baseChildren);
|
result.Children.AddRange(baseChildren);
|
||||||
|
|
||||||
|
// The mounted slot's layer WITHIN THE FRAME is its OWN ZLevel, not the mounted
|
||||||
|
// sub-window root's. The gm*UI sub-window roots carry ZLevel 1000 (their standalone
|
||||||
|
// top-window layer); ElementReader.Merge's zero-wins-base rule made the slot (own
|
||||||
|
// ZLevel 0) inherit that 1000, and the #145 ZOrder fold (ReadOrder − ZLevel·10000)
|
||||||
|
// turns 1000 into ZOrder ≈ −10,000,000 — sinking the whole panel BEHIND the frame's
|
||||||
|
// Alphablend backdrop (ZLevel 100 → ≈ −1,000,000). The backdrop then overpaints the
|
||||||
|
// panel's captions/meter/cells (the wash-out bug; the paperdoll root happens to be
|
||||||
|
// ZLevel 0 so it escaped). Restore the slot's own frame-layer so the panel sits in
|
||||||
|
// FRONT of the backdrop. (B-Controller debug 2026-06-21; continuation of #145.)
|
||||||
|
result.ZLevel = self.ZLevel;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using AcDream.App.UI.Layout;
|
||||||
|
using DatReaderWriter;
|
||||||
|
using DatReaderWriter.Options;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AcDream.App.Tests.UI.Layout;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Real-dat smoke test for the gmInventoryUI frame (0x21000023) import + the sub-window
|
||||||
|
/// mount. Locks the #145-continuation fix: the mount keeps each mounted panel slot's OWN
|
||||||
|
/// frame ZLevel instead of inheriting the sub-window root's ZLevel 1000. Before the fix the
|
||||||
|
/// merge's zero-wins-base rule gave the slots ZLevel 1000 → the #145 ZOrder fold
|
||||||
|
/// (ReadOrder − ZLevel·10000) sank them to ≈ −10,000,000, BEHIND the frame's Alphablend
|
||||||
|
/// backdrop (ZLevel 100 → ≈ −1,000,000), and the backdrop washed out the panels' captions,
|
||||||
|
/// burden meter, and item cells. Skips when the live dat directory is absent (CI).
|
||||||
|
/// </summary>
|
||||||
|
public class InventoryFrameImportProbe
|
||||||
|
{
|
||||||
|
private const uint Frame = 0x21000023u;
|
||||||
|
private const uint Backdrop = 0x100001D0u; // full-window Alphablend backdrop (ZLevel 100)
|
||||||
|
private const uint BackpackPanel = 0x100001CEu; // mounted gmBackpackUI slot
|
||||||
|
private const uint ItemsPanel = 0x100001CFu; // mounted gm3DItemsUI slot
|
||||||
|
private const uint PaperdollPanel = 0x100001CDu; // mounted gmPaperDollUI slot
|
||||||
|
private const uint BurdenMeter = 0x100001D9u; // backpack content (proves the mount attached it)
|
||||||
|
private const uint ContentsGrid = 0x100001C6u; // 3D-items content
|
||||||
|
|
||||||
|
private static string? DatDir()
|
||||||
|
{
|
||||||
|
var d = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR")
|
||||||
|
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||||
|
"Documents", "Asheron's Call");
|
||||||
|
return Directory.Exists(d) ? d : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Mounted_panels_sit_in_front_of_the_backdrop()
|
||||||
|
{
|
||||||
|
var datDir = DatDir();
|
||||||
|
if (datDir is null) return; // CI: no live dat — skip (this is a smoke test)
|
||||||
|
|
||||||
|
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||||
|
var layout = LayoutImporter.Import(dats, Frame, _ => (0u, 0, 0), null);
|
||||||
|
Assert.NotNull(layout);
|
||||||
|
|
||||||
|
var backdrop = layout!.FindElement(Backdrop);
|
||||||
|
Assert.NotNull(backdrop); // the full-window backdrop is a direct child of the frame
|
||||||
|
|
||||||
|
// The sub-window mount brings each panel's content into the by-id index.
|
||||||
|
Assert.NotNull(layout.FindElement(BurdenMeter)); // backpack burden meter
|
||||||
|
Assert.NotNull(layout.FindElement(ContentsGrid)); // 3D-items contents grid
|
||||||
|
|
||||||
|
// The fix: every mounted panel slot must draw IN FRONT of the backdrop
|
||||||
|
// (higher ZOrder = painted later = on top), so the backdrop sits behind the content.
|
||||||
|
foreach (var (id, name) in new[]
|
||||||
|
{
|
||||||
|
(BackpackPanel, "backpack"), (ItemsPanel, "3D-items"), (PaperdollPanel, "paperdoll"),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
var panel = layout.FindElement(id);
|
||||||
|
Assert.True(panel is not null, $"{name} panel 0x{id:X8} missing from the imported tree");
|
||||||
|
Assert.True(panel!.ZOrder > backdrop!.ZOrder,
|
||||||
|
$"{name} panel ZOrder {panel.ZOrder} must be > backdrop ZOrder {backdrop.ZOrder} " +
|
||||||
|
"(else the Alphablend backdrop overpaints/washes out the panel content)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue