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);