XForge - Inventory X – Agnostic UI

A fully C++ backend-first inventory plugin for Unreal Engine 5.7. Grid layouts, equip slots, nested containers, complete multiplayer support with client prediction and server reconciliation — and a built-in cross-inventory system for player-to-chest interactions. No UI forced on you. Bring your own.

Discord | Documentation & Tutorial | Youtube

The Explainer - Interactive Documentation and Tutorials, chat directly with the codebase (link with Order verification)

What's included

Backend-first, UI-agnostic

Pure C++ runtime. No widget dependencies in the core. Build any UI on top using the fully exposed Blueprint API.

Grid, slot & nested containers

N×M items, 90° rotation, equip slots, and nested containers — bags inside bags inside bags.

Server authoritative + prediction

Full client prediction with a 7-phase reconciliation protocol, handle remap table and cascading invalidation.

Tag-driven rules

Acceptance, denial, equip compatibility, stacking and access policies — all driven by GameplayTags and data assets. No hardcoded game logic.

Cross-inventory sessions

Open a trusted server-authoritative session between two inventories. Multi-viewer, single-writer lock. Atomic commits across two components.

Sample UI included

Two complete sample modules — single inventory and dual-panel cross-inventory — built exclusively on the public API.

Two layers, one plugin

All current and future layers are separate modules and can be removed if the feature they provide isn’t needed for your project.

Layer 1 — Core

  • UInventoryComponent with flat registries

  • Grid layout engine (N×M, rotation, first-fit)

  • Rules engine with GameplayTag queries

  • 9 operations: add, remove, move, swap, split, merge, rotate, equip, unequip

  • Atomic batch operations

  • Delta replication via FFastArraySerializer

  • Client prediction & reconciliation

  • Full Blueprint public API

  • 39+ automation tests

Layer 2 — Cross-Inventory

  • Trusted interaction sessions (Open / Suspended / Closed)

  • Multi-view / single-writer lock model

  • Policy-driven security evaluator

  • TransferItem, SplitStack, MergeStack, SwapItems across components, Equip and Unequip

  • Rollback journal for atomic cross-component commits

  • Session prediction with dual RevisionBarrier

  • Scoped handle remap for item & container handles

  • SessionEpoch invalidation on lock change

  • Automation suite

Layer 3 — Crafting System

  • Not yet, it’s being planned.

Architecture at a glance

Definitions layer

Reusable data assets for item definitions, container layouts, stack rules, equip rules and cosmetics. Hard references for gameplay rules, soft references for cosmetics so dedicated servers stay lean.

Runtime state

Flat registries of container, item and fragment records. Typed handles with generation counters. No per-item UObject allocations. Instance fragments for durability, ammo, condition and any custom state you add.

Operation engine

Every mutation follows Validate → Simulate → Commit → Notify. Batches are all-or-nothing. Validation runs against pre-batch state, not cumulative intermediate state.

Networking

Owner-only OwnerTags, delta replication with explicit MarkArrayDirty / MarkItemDirty contracts, initial authoritative state flag, listen server host through the same logical pipeline as remote clients.

Cross-inventory

Session registry in a WorldSubsystem. UInventoryInteractionComponent on the PlayerController as the sole RPC entry point. Server always builds the authority context from the connection, never trusts client-declared identity.

Designed for real projects

Dedicated server safe

Cosmetic assets are soft-referenced and never loaded by the runtime core.

Extend without forking

Fragment system, open tag rules and data-asset pipeline let you add game-specific logic without touching the core.

Save/load ready

Stable handles, flat registries and FInventoryDefinitionId are designed to make persistence a natural addition.

No framework lock-in

No GAS, no CommonUI, no external dependencies beyond GameplayTags and NetCore.

1 Like

:package: Inventory_X Update! — v1.1

Major Architecture Update

A ground-up refresh of the fragment data model, replication boundary, and client prediction system. Focused on performance and stronger anti-cheat guarantees.

:bullseye: Highlights

:building_construction: New fragment data model

single source of truth (FragmentEntries asset-or-inline)

:high_voltage: Runtime vs Static fragment lifecycle

new introduced static fragments now live on the definition only, eliminating per-item replication for read-only data and reducing network bandwidth on heavy inventories.

:shield: Hardened anti-cheat boundary

Closed an item-duplication attack vector.

:video_game: Handle-preserving rollback

chained predicted operations no longer drop dependent ops during reconcile. Fixes a long-standing prediction edge case in cross-inventory transfers.

:test_tube: +22 new automation tests across registry, prediction, rollback journal, and Layer2 transfer paths.

:package: Cleaner public API

explicit GetRuntime*, GetStatic*, GetEffective* family. Old generalist API removed.

Changelog (UE 5.1 - 5.7)

:package: Data Model

New

- FInventoryItemFragmentEntry — a single entry that is either an asset reference (reusable fragment definition) or inline data (one-off, completely defined in the item).

- EInventoryFragmentLifecycle enum on every fragment, with two values:
    - RuntimePerItem — creates a per-item record, replicates, participates in snapshots/merge     (legacy default behavior).
    - StaticDefinition — definition-side only, never replicated, never per-item — perfect for     read-only data shared across all instances of an item type.

- UInventoryItemFragmentDefinition extended with Lifecycle (defaults to RuntimePerItem for backward-compatible behavior).

Removed

- TArray<TObjectPtr<UInventoryItemFragmentDefinition>> FragmentDefinitions on UInventoryItemDefinition.

Why it matters
Static fragments (e.g., ammo type, weapon category, item rarity) no longer pay replication cost or per-item allocation cost. Runtime fragments (e.g., durability, current ammo, condition) keep their existing per-item replicated semantics.

:electric_plug: API

Old names have been removed in favor of explicit Runtime/Static/Effective families:

- GetItemFragments -> GetRuntimeItemFragments, GetStaticItemFragments, GetEffectiveItemFragments

- GetFragmentByTag -> GetRuntimeFragmentByTag, GetStaticFragmentByTag, GetEffectiveFragmentByTag

- HasFragment -Z HasRuntimeFragment, HasStaticFragment, HasEffectiveFragment

- TryGetDurabilityFragment -> TryGetRuntimeDurabilityFragment, TryGetStaticDurabilityFragment, TryGetEffectiveDurabilityFragment

- TryGetAmmoFragment -> Same Runtime/Static/Effective split

- TryGetConditionFragment -> Same Runtime/Static/Effective split

- GetFragmentsForItem -> GetRuntimeFragmentHandlesForItem

- Internal_CreateFragment -> Internal_CreateRuntimeFragment


New view types

- FInventoryStaticFragmentView — static fragment data, no handle (handles are runtime-only).

- FInventoryEffectiveFragmentView — unified view with Source = RuntimeRecord | StaticDefinition discriminator and a bIsMutableRuntime flag.

FInventoryFragmentHandle remains valid only for runtime fragments. No fake handles are ever generated for static fragments.

:counterclockwise_arrows_button: Replication, Snapshot, Drop/Pickup

- Trusted snapshot (ExportItemSubtreeSnapshot/ImportItemSubtreeTrusted) carries only runtime fragments. Static fragment data is derived server-side and client-side from DefinitionId.

- Drop actor PickupableItemComponent replicates only PresentationFragments (runtime-only). UI consumers should query GetEffective* to render full per-item state including static contributions.

- Layer2 cross-inventory bridge snapshots are runtime-only; static data is resolved per-endpoint from the resolved definition.

- Snapshot import now rejects any fragment payload whose FragmentTypeTag does not correspond to a RuntimePerItem spec on the resolved definition (defense against client-crafted snapshots).

:balance_scale: Stack Rules & Validation

- InventoryStackValidator::ValidateMerge (Layer1) and InventoryInteractionStackValidator::ValidateMerge (Layer2) compare only runtime fragments when bRequireMatchingFragmentsForMerge is true.

- Two stacks of the same definition with only static fragments always merge cleanly — static fragments are equal by construction.

- Layer2 merge no longer requires both endpoints to resolve the definition: as long as one endpoint resolves and DefinitionId matches, the merge proceeds. Fixes spurious rejections under asset streaming / hot-reload scenarios.

:shield: Security & Anti-Cheat

- AddItem and RemoveItem are strictly server-authoritative. The validation gate enforces bServerInitiated == true, and Server_SubmitOperation_Implementation deliberately does not propagate that flag from client RPCs. Client autonomous proxies cannot directly create or destroy items via RPC.

- Closed an item-duplication attack vector. A bypass flag introduced during an early version was identified as a regression and removed. The flag had allowed client-side validation to accept arbitrary AddItem predictions; it has been fully eliminated, and explicit anti-pattern documentation has been added to the architecture guide.

- New security tests confirm that RequestOperation(AddItem) and RequestOperation(RemoveItem) from ROLE_AutonomousProxy:
    - return Rejected immediately at local validation;
    - never enqueue a pending prediction;
    - never emit a Server_SubmitOperation RPC.

:video_game: Client Prediction

Predicted operations now:

- create only runtime fragment records (static specs do not allocate);
- rollback removes only the runtime records they created (handle-preserving);
- replay correctly preserves source item identity through chained operation reconciliation.


Handle-Preserving Rollback (new in this version)

A subtle bug in Layer2 chained predictions has been fixed. Previously, when a client predicted two transfers in a fast sequence (e.g., move backpack to other inventory, then move weapon into backpack's child container), the authoritative reconciliation of the first operation would re-import the destroyed source items with new handles, breaking the second operation's source resolution and silently invalidating it.


The fix:

- New CreateRecordWithExistingHandle registry primitive that re-uses original handle + generation across destroy/restore cycles.

- Trusted seam variants (ImportItemSubtreePredictedPreservingHandles, ImportItemSubtreeTrustedPreservingHandles) that preserve identity through rollback.

- Symmetric application to both predicted rollback and trusted (server-side transaction failure) rollback for architectural consistency.

- Atomic prevalidation prevents partial registry mutations.


Idempotency Hardening

The rollback idempotency check has been upgraded from a presence-only check to a full tri-state structural verification:

- NotPresent -> proceed with handle-preserving import.
- PresentEquivalent -> idempotent skip (handles + structure match snapshot exactly).
- PresentMismatch -> fail loudly with audit log instead of silent success.

Conservative fallback for runtime fragments: if any item in the snapshot carries fragment data, the idempotency shortcut is bypassed

:high_voltage: Performance

- Definition-side fragment cache is array-first: contiguous TArray<FInventoryResolvedFragmentSpec> with linear-scan helpers. No TMap for tag lookup — small fragment counts make linear scan cache-friendlier than hash maps with allocations and pointer chasing.

- Cache prepared in PostLoad and PostEditChangeProperty, not re-resolved on every query.

- Repeated GetEffectiveFragmentSpecs() calls return stable storage (verified by automation test) — zero re-resolution cost on hot paths like tooltip rendering.

- Static-heavy inventories (e.g., shops, world containers with read-only items) see significantly reduced FragmentFastArray size on both server and client.

- Network bandwidth reduction: items with only StaticDefinition fragments replicate zero fragment records.

:test_tube: Test Coverage

Significant suite expansion:

- +11 prediction tests covering Move/Split/Merge/Equip rollback, runtime fragment preservation, and security boundaries.

- +9 rollback journal tests including all 4 structural drift detection cases (Quantity, Placement, ParentContainer, ChildContainerLink), runtime fragment fallback, and idempotency happy path.

- +6 trusted seam tests for handle-preserving import/export.

- +3 registry tests for CreateRecordWithExistingHandle (revive after release, duplicate id rejection, generation preservation).

- Reconciliation suite cleaned: removed orphan tests and updated old tests

Final test status: 117/117 broad Layer2 tests + 9/9 rollback journal + 4/4 handle preserve regression — all green

:bug: Bug Fixes

- Chained Layer2 predictions silently invalidated — fixed via handle-preserving rollback re-import.

- Idempotent rollback could mask state corruption — replaced presence-only check with structural verification + audit logging.

- Partial subtree presence treated as success — now correctly classified as PresentMismatch and fails with explicit log.

- Layer2 merge spurious rejection under asset streaming — fallback resolution path uses whichever endpoint resolves the definition (both items must already share DefinitionId).

:wrench: Internal Improvements

- Internal_CreateFragment renamed to Internal_CreateRuntimeFragment for consistency with the runtime-only contract.

- LookupCache.ItemToFragments is documented and enforced as runtime-only.

- New FInventoryResolvedFragmentSpec cache type unifies runtime, static, and effective spec views on the definition.

- Editor UX: when BaseFragmentAsset is set, inline fields are hidden via EditCondition/EditConditionHides to make the asset-or-inline contract visually unambiguous.

- PostEditChangeProperty clears inline fields when an asset is assigned to prevent zombie data.