Layouts

UCrimsonInventoryLayout is the topology strategy. It is a UDataAsset so a single layout asset (e.g. BP_PlayerGrid_20x8) can be referenced by many components - every player, every chest of a given type. Layouts hold no per-component state and no caps. All caps come from the constraint stack at runtime, all per-component derived state lives in FCrimsonLayoutRuntimeState on the component.

Order of veto

Every placement goes through three stages:

1. Layout `FindPlacement` - geometric feasibility ("is there a 2x2 rectangle anywhere?")
2. Layout `CanAcceptAt` - geometric validity of the chosen target ("is cell (3,1) actually in bounds?")
3. Constraint stack - gameplay vetoes (slot cap, weight, type filter, custom rules)

If any stage fails, the failure reason is broadcast via BroadcastOnConstraintFailed on the owning client.

Virtual surface

cpp
UCLASS(Abstract, Blueprintable, BlueprintType)
class UCrimsonInventoryLayout : public UDataAsset
{
UFUNCTION(BlueprintNativeEvent)
FCrimsonPlacementResult FindPlacement(
const FCrimsonPlacementContext& Context,
const FCrimsonPlacementQuery& Query) const;
UFUNCTION(BlueprintNativeEvent)
bool CanAcceptAt(
const FCrimsonPlacementContext& Context,
const FCrimsonPlacementQuery& Query,
const FCrimsonPlacementTarget& Target,
FText& OutFailureReason) const;
UFUNCTION(BlueprintNativeEvent)
FCrimsonMoveResult TryResolveMove(
const FCrimsonPlacementContext& Context,
const FCrimsonMoveQuery& Query) const;
UFUNCTION(BlueprintNativeEvent)
int32 GetCapacityHint(const FCrimsonPlacementContext& Context) const;
UFUNCTION(BlueprintNativeEvent)
FCrimsonItemFootprint GetItemFootprint(const UCrimsonInventoryItemDefinition* Definition) const;
UFUNCTION(BlueprintNativeEvent)
bool SupportsRotation() const;
UFUNCTION(BlueprintNativeEvent)
bool SupportsStacking() const;
virtual void RebuildOccupancy(
const UCrimsonInventoryManagerComponent* Component,
const TArray<FCrimsonInventoryEntry>& Entries,
FCrimsonLayoutRuntimeState& OutState) const;
};

Built-in layouts

ClassTopologyReads from constraints
UCrimsonInventoryLayout_FlatSlotIndexed slot listSlot count -> tag Crimson.Inventory.Constraint.Slots
UCrimsonInventoryLayout_Grid2D cell gridWidth / Height -> tags .GridWidth / .GridHeight. Reads footprint Dimensions + Rotation.
UCrimsonInventoryLayout_WeightOrdered list, no spatialCap enforced via _FloatTag weight constraint (not by the layout)

Placement types (FCrimsonInventoryLayoutTypes.h)

cpp
UENUM(BlueprintType)
enum class ECrimsonItemRotation : uint8
{
None, CW90, Rot180, CCW90
};
UENUM(BlueprintType)
enum class ECrimsonPlacementPolicy : uint8
{
MergeFirst, // stack into existing, then take first free slot for overflow
FirstFree, // skip stacking; lowest-index free slot
LastFree, // skip stacking; highest-index free slot
MergeOnly, // stack into existing only; reject overflow
ExplicitTarget // use Query.Hint exactly
};
UENUM(BlueprintType)
enum class ECrimsonMoveAction : uint8
{
None, Move, Swap, Merge
};
USTRUCT(BlueprintType)
struct FCrimsonItemFootprint
{
FIntPoint Dimensions = FIntPoint(1, 1);
float Weight = 0.f;
int32 SlotCost = 1;
bool bAllowRotation = false;
};
USTRUCT(BlueprintType)
struct FCrimsonPlacementTarget
{
int32 SlotIndex = -1;
FIntPoint GridPosition = FIntPoint(-1, -1);
ECrimsonItemRotation Rotation = ECrimsonItemRotation::None;
int32 LayoutGroupId = 0;
};
USTRUCT(BlueprintType)
struct FCrimsonPlacementQuery
{
TSubclassOf<UCrimsonInventoryItemDefinition> ItemDef;
int32 RequestedStackCount = 1;
ECrimsonPlacementPolicy Policy = ECrimsonPlacementPolicy::MergeFirst;
FCrimsonPlacementTarget Hint;
int32 LayoutGroupId = 0;
};
USTRUCT(BlueprintType)
struct FCrimsonPlacementResult
{
bool bSuccess = false;
FText FailureReason;
FCrimsonPlacementTarget Target;
int32 QuantityPlaced = 0;
int32 QuantityRemaining = 0;
bool bMergedIntoExisting = false;
int64 MergedTargetEntryId = 0;
};

Runtime occupancy cache

FCrimsonLayoutRuntimeState is a transient struct owned by the component. The layout populates it in RebuildOccupancy from the current entry list. Clients rebuild from FastArray callbacks (PostReplicatedAdd/Change/Remove) and from OnRep_InventoryList.

cpp
USTRUCT(BlueprintType)
struct FCrimsonLayoutRuntimeState
{
/** Shared: stable EntryId -> its current target. */
TMap<int64, FCrimsonPlacementTarget> EntryIdToTarget;
/** FlatSlot / Weight: slot index -> EntryId. */
TMap<int32, int64> SlotIndexToEntryId;
/** Grid layouts: cell (X + Y*Width) -> EntryId, 0 if empty. */
TArray<int64> GridOccupancy;
/** Cached grid bounds; (0,0) for non-grid layouts. */
FIntPoint GridSize = FIntPoint::ZeroValue;
};
Authoring a custom layout
Subclass UCrimsonInventoryLayout (BP or C++). Override FindPlacement_Implementation to decide where items go; optionally override CanAcceptAt, TryResolveMove, GetCapacityHint, RebuildOccupancy. Read parameters (caps, dimensions) from the constraint stack via Context.Component, never bake them into the layout asset.