From a4c68520ea6383757c5f1eaf5711dddacf1fecf3 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 23 Jun 2026 00:02:11 +0200 Subject: [PATCH] fix(D.2b): paperdoll empty slots show a visible frame (not transparent) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User correction at the visual gate: the green "figure" in the paperdoll is the LIVE 3D character (the doll — can be naked), NOT per-slot silhouettes. The default view (Slots button OFF) = the doll + non-armor slots; pressing Slots hides the doll and shows the armor slots. So the doll + the Slots toggle are Slice 2 (the UiViewport); there are no per-slot silhouette sprites to chase. For Slice 1 (no doll yet) the right empty-slot look is simply a VISIBLE FRAME so every slot position can be seen + used — which fixes the "I see only slots with equipment, no empty slots" report. The earlier transparent (EmptySprite=0) came from the stale silhouette assumption. PaperdollController now takes an emptySlotSprite; GameWindow passes the inventory grid's empty square (0x06004D20) for a consistent visible frame. App suite 580 green. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 5 ++++- src/AcDream.App/UI/Layout/PaperdollController.cs | 12 ++++++++---- .../UI/Layout/PaperdollControllerTests.cs | 11 ++++++----- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 386a68cc..a3ab1db0 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -2252,7 +2252,10 @@ public sealed class GameWindow : IDisposable invLayout, Objects, playerGuid: () => _playerServerGuid, iconIds: (type, icon, under, over, effects) => iconComposer.GetIcon(type, icon, under, over, effects), - sendWield: (item, mask) => _liveSession?.SendGetAndWieldItem(item, mask)); + sendWield: (item, mask) => _liveSession?.SendGetAndWieldItem(item, mask), + // Empty equip slots show a visible frame (same square as the inventory grid) so every + // slot position is seen + usable; the live 3D character (the doll) is Slice 2. + emptySlotSprite: contentsEmpty); Console.WriteLine("[D.2b-B] retail inventory window from LayoutDesc importer (0x21000023)."); } diff --git a/src/AcDream.App/UI/Layout/PaperdollController.cs b/src/AcDream.App/UI/Layout/PaperdollController.cs index c5ba54c7..41763f01 100644 --- a/src/AcDream.App/UI/Layout/PaperdollController.cs +++ b/src/AcDream.App/UI/Layout/PaperdollController.cs @@ -53,7 +53,8 @@ public sealed class PaperdollController : IItemListDragHandler private PaperdollController( ImportedLayout layout, ClientObjectTable objects, Func playerGuid, - Func iconIds, Action? sendWield) + Func iconIds, Action? sendWield, + uint emptySlotSprite) { _objects = objects; _playerGuid = playerGuid; _iconIds = iconIds; _sendWield = sendWield; @@ -64,7 +65,9 @@ public sealed class PaperdollController : IItemListDragHandler list.RegisterDragHandler(this); list.Cell.SourceKind = ItemDragSource.Equipment; list.Cell.SlotIndex = i; // SlotMap position = this equipped item's drag-payload SourceSlot when dragged out to unwield - list.Cell.EmptySprite = 0u; // TRANSPARENT empty slot (brainstorm decision; no doll behind it in Slice 1) + list.Cell.EmptySprite = emptySlotSprite; // visible empty-slot frame so every position is seen + usable. The live + // 3D character (the "figure" — Slice 1/2 correction: NOT a per-slot + // silhouette) is the doll viewport, which arrives in Slice 2 with the Slots toggle. // Cell.SpriteResolve + the default accept/reject sprites (ItemSlot_DragOver_Accept ring // 0x060011F9 / reject circle 0x060011F8 — the discrete-slot frames, NOT the inventory grid's // insert-arrow 0x060011F7) are already wired by DatWidgetFactory when it built the UiItemList; @@ -82,8 +85,9 @@ public sealed class PaperdollController : IItemListDragHandler public static PaperdollController Bind( ImportedLayout layout, ClientObjectTable objects, Func playerGuid, - Func iconIds, Action? sendWield = null) - => new PaperdollController(layout, objects, playerGuid, iconIds, sendWield); + Func iconIds, Action? sendWield = null, + uint emptySlotSprite = 0u) + => new PaperdollController(layout, objects, playerGuid, iconIds, sendWield, emptySlotSprite); private void OnObjectChanged(ClientObject o) { if (Concerns(o)) Populate(); } private void OnObjectMoved(ClientObject o, uint from, uint to) diff --git a/tests/AcDream.App.Tests/UI/Layout/PaperdollControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/PaperdollControllerTests.cs index 584eb7b2..add55b4f 100644 --- a/tests/AcDream.App.Tests/UI/Layout/PaperdollControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/PaperdollControllerTests.cs @@ -32,10 +32,11 @@ public class PaperdollControllerTests } private static PaperdollController Bind(ImportedLayout layout, ClientObjectTable objects, - List<(uint item, uint mask)>? wields = null) + List<(uint item, uint mask)>? wields = null, uint emptySlot = 0u) => PaperdollController.Bind(layout, objects, () => Player, iconIds: (_, _, _, _, _) => 0x1234u, - sendWield: wields is null ? null : (i, m) => wields.Add((i, m))); + sendWield: wields is null ? null : (i, m) => wields.Add((i, m)), + emptySlotSprite: emptySlot); private static void SeedEquipped(ClientObjectTable t, uint guid, EquipMask loc) { @@ -112,11 +113,11 @@ public class PaperdollControllerTests } [Fact] - public void Empty_equip_slot_is_transparent() // EmptySprite=0 ⇒ nothing drawn (no doll behind it in Slice 1) + public void Empty_equip_slot_shows_the_configured_frame() // visible frame so all positions are usable (Slice 1; the doll is Slice 2) { var (layout, lists) = BuildLayout(); - Bind(layout, new ClientObjectTable()); - Assert.Equal(0u, lists[HeadSlot].Cell.EmptySprite); + Bind(layout, new ClientObjectTable(), emptySlot: 0x06004D20u); + Assert.Equal(0x06004D20u, lists[HeadSlot].Cell.EmptySprite); } [Fact]