From 383e8b7b556d68930a8c932a433af88f97247fc5 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 21 Jun 2026 09:09:15 +0200 Subject: [PATCH] =?UTF-8?q?refactor(ui):=20D.2b-B=20=E2=80=94=20Populate?= =?UTF-8?q?=20uses=20GetContents=20only=20(drop=20test-driven=20fallback)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The implementer added a full-scan fallback in Populate() to accommodate a test that seeded items via AddOrUpdate (which deliberately does NOT touch the container index — only Ingest/MoveItem do). That was a production workaround for a faulty test, and inconsistent (it only triggered when the index was wholly empty). Root-fix: Populate reads GetContents(player) only — the index IS retail's per-container item list. The test now seeds via the faithful indexed path (AddOrUpdate + MoveItem → Reindex) through a SeedContained helper. 530 App tests green. --- .../UI/Layout/InventoryController.cs | 31 ++++--------------- .../UI/Layout/InventoryControllerTests.cs | 22 +++++++++---- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/AcDream.App/UI/Layout/InventoryController.cs b/src/AcDream.App/UI/Layout/InventoryController.cs index 310e2239..1f0e29ab 100644 --- a/src/AcDream.App/UI/Layout/InventoryController.cs +++ b/src/AcDream.App/UI/Layout/InventoryController.cs @@ -121,38 +121,19 @@ public sealed class InventoryController } /// Retail gm*UI display refresh: partition the player's direct contents into loose - /// items (the "Contents of Backpack" grid) and side bags (the pack-selector list). - /// Uses the container index when available (items ingested via Ingest/MoveItem) or - /// falls back to a full scan — both paths are correct for the two production flows. + /// items (the "Contents of Backpack" grid) and side bags (the pack-selector list). The + /// container index () IS retail's per-container + /// item list — items enter it via the CreateObject (Ingest) / server-move (MoveItem) flows + /// that call Reindex, slot-sorted. public void Populate() { uint p = _playerGuid(); - - // GetContents returns indexed + slot-sorted items (the production path via Ingest). - // For AddOrUpdate-seeded items (tests + initial populate before indexing), we fall - // back to scanning all objects with ContainerId == p, sorted by slot. - var indexed = _objects.GetContents(p); - IEnumerable guids; - if (indexed.Count > 0) - { - guids = indexed; - } - else - { - var direct = new List<(int slot, uint id)>(); - foreach (var o in _objects.Objects) - if (o.ContainerId == p) - direct.Add((o.ContainerSlot, o.ObjectId)); - direct.Sort((a, b) => a.slot.CompareTo(b.slot)); - var ids = new List(direct.Count); - foreach (var (_, id) in direct) ids.Add(id); - guids = ids; - } + var contents = _objects.GetContents(p); _contentsGrid?.Flush(); _containerList?.Flush(); - foreach (var guid in guids) + foreach (var guid in contents) { var item = _objects.Get(guid); if (item is null) continue; diff --git a/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs b/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs index 99d5b34b..14d6053e 100644 --- a/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs +++ b/tests/AcDream.App.Tests/UI/Layout/InventoryControllerTests.cs @@ -54,17 +54,27 @@ public class InventoryControllerTests iconIds: (_, _, _, _, _) => 0u, // no real GL upload in tests strength: () => strength, datFont: null); + // Place an object in a container at a slot via the faithful production path: + // AddOrUpdate registers it, MoveItem sets ContainerId+ContainerSlot and calls Reindex + // (the same machinery server-driven moves use), so GetContents returns it slot-ordered. + private static void SeedContained(ClientObjectTable t, uint guid, uint container, int slot, + int burden = 0, ItemType type = ItemType.None) + { + t.AddOrUpdate(new ClientObject { ObjectId = guid, Burden = burden, Type = type }); + t.MoveItem(guid, container, slot); + } + [Fact] public void Populate_fills_contents_grid_with_loose_items_only() { var (layout, grid, containers, _, _, _, _, _) = BuildLayout(); var objects = new ClientObjectTable(); - objects.AddOrUpdate(new ClientObject { ObjectId = 0xA, ContainerId = Player, - ContainerSlot = 0, Burden = 10 }); // loose - objects.AddOrUpdate(new ClientObject { ObjectId = 0xB, ContainerId = Player, - ContainerSlot = 1, Burden = 20 }); // loose - objects.AddOrUpdate(new ClientObject { ObjectId = 0xC, ContainerId = Player, - ContainerSlot = 2, Type = ItemType.Container }); // side bag + // Seed via the indexed path: AddOrUpdate registers the object, MoveItem places it in + // the container at a slot (the faithful "server moved item into container" flow that + // calls Reindex). GetContents — retail's per-container list — then returns them ordered. + SeedContained(objects, 0xA, Player, slot: 0, burden: 10); // loose + SeedContained(objects, 0xB, Player, slot: 1, burden: 20); // loose + SeedContained(objects, 0xC, Player, slot: 2, type: ItemType.Container); // side bag Bind(layout, objects);