Concept: Layouts and Groups

A layout answers three questions for a set of entries: where can this go, is this specific target legal, and what happens when one entry is moved onto another. It does not answer how big the inventory is.

Capacity lives in tags

Every cap is a gameplay tag with an integer or float max stored on the manager (ConstraintMaxTags, ConstraintMaxFloatTags) and a current value beside it. UCrimsonInventoryLayout_FlatSlot asks FCrimsonPlacementContext::GetIntCapacity(MaxSlotsTag); a weight constraint asks GetFloatCapacity(TrackedTag). Because the tags replicate, the same layout asset serves a 10-slot pouch and a 60-slot backpack, and a client can run PreviewPlacement with the exact numbers the server will use.

The placement pipeline

  1. FCrimsonPlacementQuery names the item, the count, the policy, a hint target, and the group.
  2. The manager asks every UCrimsonInventoryConstraint in ActiveConstraints (plus the item's own Fragment_Constraint rules) CanAcceptItem; any failure stops here with its FailureMessage.
  3. The layout's FindPlacement (or CanAcceptAt for an explicit target) picks the cell; constraints get a second look at the concrete target through CanAcceptAt.
  4. FCrimsonPlacementResult reports Target, QuantityPlaced, QuantityRemaining, and whether it merged into an existing entry.

Moves go through TryResolveMove, which returns an ECrimsonMoveAction of Move, Swap, or Merge.

Groups

An inventory is a tree of groups. Group 0 is the root and uses LayoutAsset. An entry whose item carries UCrimsonInventoryItemFragment_Bag spawns a child group whose id is the bag's EntryId (as int32), with the fragment's Layout, its own caps (IntCapacities, FloatCapacities), and its own Constraints. Entries inside set LayoutGroupId to that id. Group -1 (EquippedLayoutGroupId) parks equipped entries when bEquippedItemsOccupyLayout is off.

FCrimsonLayoutRuntimeState is rebuilt (RebuildLayoutRuntime) on the server after each mutation and on clients after each replicated batch; it is transient and never replicated. GetLayoutGroups reads it; Crimson.Inventory.Message.LayoutGroupsChanged fires only when the rebuild changes the set of groups or their activation.

Subtrees

Because a bag's contents reference the bag's entry id, moving a bag means moving a subtree. FCrimsonInventorySubtree is a flat node list with parent indices; ExtractSubtree / AddSubtree are the primitives, and TransferEntryTo, DropItemByEntry, DropEntriesAsBag, and FCrimsonPickupInstance::Contents all use them so a bag never arrives empty.