Lab3 Studio← lab3.studio

An Unreal Engine plugin by Lab³ Studio

Smart Object Claims

A reservation, negotiation, and interruption layer for Unreal Engine's SmartObjects system.

Smart Object Claims adds the layer the engine leaves out: a registry of who holds what and why, ownership you can actually verify, and a way for two agents to agree on an interaction before either commits to it. All of it usable from Blueprint and C++.

v1.0.0Unreal Engine 5.868 automation testsC++ & Blueprint

Why use it

The engine's Smart Object subsystem owns slot state, and it does that job well. What it doesn't tell you is who holds what, why, or whether your handle is still yours. This plugin adds exactly that layer and never duplicates the one below. Here's how the two compare:

Slot stateFree, Claimed, or Occupied - and no memory of who claimed it, or whyA registry row per claim: holder, reason tag, age, occupancy - queryable by actor, slot, or handle
Handle validityIs Valid just means a handle was assigned at some point; a handle someone preempted still looks validIsClaimCurrent checks the exact object, slot, and user; a stale handle gets refused, never acted on
ClaimingMarkSlotAsClaimed - the claimability checks skip selection conditions, and scan-then-claim is a race waiting to happenTryClaim checks eligibility and claims in one call; multi-slot batches are all-or-nothing, preflighted before anything mutates
Multi-agent interactionsNot modeled - every agent claims on its ownOffer and accept negotiation: reservations for both participants, checked again at the moment of acceptance
EventsNative delegates that can fire from any threadOrdered, fully-attributed Blueprint events on the game thread - and Invalidated (it was taken from you) is not the same event as Released
Client requestsNot modeledAn optional replicated gateway that checks the connection actually owns the agent it speaks for; the server stays authoritative
CleanupEveryone promises to release their own slotsClaim tokens release when their owner dies; stale claims get reported; ValidateRegistry audits the whole registry in one call

Features

Claim registry with reverse lookupQuery claims by actor, slot, or handle, or pull the full table for debugging and HUD display. The registry mirrors engine state, and when the two disagree, the engine wins and the registry re-syncs - no question about slot state is ever answered from the mirror alone.
Claim ownership verificationIsClaimCurrent checks the registry, the engine's slot state, and the slot's current user data. ReleaseClaim refuses a stale handle rather than freeing a claim that stopped being yours.
Atomic and transactional claimsTryClaim takes the first eligible free slot in one call. ClaimSlots takes several at once, all-or-nothing: preflighted before anything mutates, deadlock-safe ordering, results keyed to the order you asked in.
Offer and accept negotiationAn initiator reserves slots for both participants and puts the interaction on offer; the invitee accepts, declines, or lets it time out. A claim is a reservation only - it starts no behavior and implies no consent.
Game-thread event deliveryThe engine's delegates can fire from any thread; this plugin catches them at a capture boundary and hands Blueprints ordered, coherent events with the claim's own context attached.
Client-initiated claims (optional module)A gateway component for networked games: clients request, the server validates and answers asynchronously with correlation IDs. See Networking.
Claim tokens, interruption, and diagnosticsTokens release their claim when the owner ends play or is destroyed. InterruptSmartObjectUser asks a running interaction to stop at its own safe point, through an optional adapter module.

Note

A slot in the Claimed state is not necessarily claimed by you.

Check ownership with IsClaimCurrent, and bind OnClaimInvalidated to be notified when a claim is taken from its holder.

Getting Started

  1. Copy the SmartObjectClaims folder into your project's Plugins folder.
  2. In the Unreal Editor, go to Edit > Plugins and enable Smart Object Claims. The engine's SmartObjects plugin is the only requirement.
  3. Restart the editor. For C++ projects, regenerate project files and rebuild.

Blueprint

Use the Get Smart Claim Subsystem node from any world context, then call the following functions:

Try ClaimClaims the first eligible free slot on an Actor. Takes a Smart Claim Request.
Try Claim SlotClaims one specific slot.
Release Claim With ResultReleases a claim and returns the reason if the release did not happen.
Is Claim CurrentReturns whether the claim still belongs to its holder. See the note below.
Create Claim TokenWraps a claim in a token that releases it when the owner ends play or is destroyed.
Get Claim TableReturns the full registry as an array of claim records.
On EventBroadcasts slot and object events with full context.

To receive events for a single Actor's Smart Objects, add a Smart Claim Listener component to that Actor.

C++

USmartClaimSubsystem* Claims = GetWorld()->GetSubsystem<USmartClaimSubsystem>();

FSmartClaimRequest Request;
Request.Claimant = MyPawn;
Request.ReasonTag = MyReasonTag;
Request.Priority  = ESmartObjectClaimPriority::Normal;
// Optional: Request.Filter carries user tags, activity requirements, behavior classes.
// Optional: Request.UserData carries your user-data struct for selection conditions.

FSmartObjectClaimHandle Handle;
if (Claims->TryClaim(SmartObjectActor, Request, Handle) == ESmartClaimResult::Claimed)
{
    // Optional: the token releases the claim automatically if MyPawn
    // ends play or is destroyed.
    USmartClaimToken* Token = Claims->CreateClaimToken(MyPawn, Handle);
}

API Reference

The complete public surface. Every Blueprint function is a native UFUNCTION with the same name, so the Blueprint reference doubles as the C++ function list; the C++ reference covers what Blueprint cannot reach – the host integration seams, module setup, and the type vocabulary. Expand a class to see its functions.

Blueprint Reference

Claims

Try ClaimClaims the first eligible free slot on an Actor, evaluating the slot's selection conditions. Takes a Smart Claim Request.
Try Claim SlotClaims one specific slot, evaluating the same eligibility contract.
Claim SlotsClaims multiple slots as an all-or-nothing batch, with results keyed to the caller's input order.
Claim Slots TransactionalClaims multiple slots as an all-or-nothing batch using the claimant, reason tag, and priority directly.
Try Claim First Free SlotClaims the first free slot on an Actor, optionally filtered by required slot tags. Convenience form of Try Claim.
Try Claim Specific SlotClaims one specific slot using the claimant, reason tag, and priority directly.
Release ClaimReleases a claim. Returns whether the release happened.
Release Claim With ResultReleases a claim and returns the reason when the release did not happen.
Release All Claims For ActorReleases every claim held by an Actor. Returns the number of claims freed.

Registry queries

Is Claim CurrentReturns whether the claim still belongs to its holder, verifying exact object, slot, and user ownership.
Get Claim RecordReturns the registry row for a claim held through this subsystem, by exact handle.
Get Claims For ActorReturns every claim record held by an Actor.
Get Slot ClaimantReturns the Actor holding a slot, or null when the slot is free or the holder could not be attributed.
Get Claim TableReturns the full registry as an array of claim records: owned claims first, then observations.

Watches and events

Acquire WatchSubscribes to a Smart Object's events and returns a lease that only the caller can release.
Acquire Watch On ActorAcquires one watch lease for every registered Smart Object component on an Actor.
Release WatchReleases exactly the given lease.
Release WatchesReleases a set of leases. Returns how many were held.
Is Watch Lease ActiveReturns whether a lease is still outstanding.
Resnapshot Smart ObjectRe-reads what is currently held on a watched object and reconciles the registry against it. Use after a streaming rebind.
Flush Pending EventsApplies and broadcasts everything the engine has reported since the last drain. Refused from inside an event listener.

Offers

Offer Interaction With RequestsClaims every listed slot on its participant's behalf and puts the interaction on offer. Each side brings a full Smart Claim Request.
Offer InteractionActor-only convenience form with default filter, priority, and user data.
Accept OfferAccepts a pending offer as the invitee. Returns whether the offer was confirmed.
Accept Offer With ResultAccepts a pending offer and returns the exact outcome, revalidating every reservation at the commit instant.
Decline OfferDeclines a pending offer as the invitee.
Decline Offer With ResultDeclines a pending offer and returns the exact outcome.
Cancel OfferWithdraws an offer as its initiator.
Cancel Offer With ResultWithdraws an offer and returns the exact outcome.
Get OfferReturns a pending offer by ID, including its reservations and live state.
Get Offers For ActorReturns every pending offer an Actor is party to, as initiator or invitee.
Get Pending OffersReturns every pending offer.

Interruption, authority, and diagnostics

Interrupt Smart Object UserRequests that an Actor's running Smart Object interaction stop. Soft by default; a hard release cancels immediately.
Interrupt Smart Object User With ResultAs above, and returns what happened – including Unsupported when no interruption provider is installed.
Create Claim TokenWraps a claim in a token that releases it automatically when the owner ends play or is destroyed.
Has Claim AuthorityReturns whether this instance can mutate claims under the current network mode.
Are Same AgentReturns whether two Actors are the same agent under the configured Agent Policy.
Validate RegistryLogs a full consistency report and returns the number of problems found. Zero means the registry is self-consistent.

Events

On EventBroadcasts slot and object state changes on watched objects, with full context, on the game thread.
On Claim InvalidatedBroadcasts when a claim made through this subsystem stops being its holder's without a release – preempted, or the slot went away.
On Stale Claim DetectedBroadcasts when a claimed but never-occupied slot exceeds Stale Claim Seconds. Report-only.
On Interaction OfferedBroadcasts when an interaction is put on offer.
On Interaction ConfirmedBroadcasts when an offer is accepted and every reservation is confirmed current.
On Interaction Rolled BackBroadcasts when an offer is declined, cancelled, timed out, or lost a participant or reservation.
On User InterruptedBroadcasts when a user's interaction is interrupted through this subsystem.

Functions and events

Watch TargetWatches a specific Smart Object Actor.
Watch Target By HandleWatches a specific Smart Object by handle.
Unwatch TargetStops watching a Smart Object Actor.
Unwatch Target By HandleStops watching a Smart Object by handle.
Unwatch All TargetsReleases every watch this component holds.
Refresh Owner TargetsReconciles the owner's Smart Object components against the watched set: acquires what is new, releases what is gone, and resnapshots what remains. Use after a World Partition rebind.
Get Watched TargetsReturns the handles this component is watching.
On Target EventBroadcasts slot and object events for the watched targets.
On Offered To OwnerBroadcasts offers where the owner, or its possession counterpart, is the invitee.

Functions

Release NowReleases the claim immediately instead of waiting for the owner to die. Safe to call twice.
Is HeldReturns whether the token still holds its claim.
Get Claim HandleReturns the claim handle this token wraps.
Get Owning ActorReturns the Actor this token releases on behalf of.

Functions

Get Smart Claim SubsystemReturns the claim subsystem for the context's world.
Break Smart Object Claim HandleSplits a claim handle into its object handle, slot handle, and user flags.
Equal (Smart Object Claim Handle)Compares two claim handles for exact equality.

Functions and events

Request ClaimRequests a claim on the first eligible free slot of an Actor, across the network. Returns a request ID.
Request Claim SlotRequests a claim on one specific slot. Returns a request ID.
Request ReleaseRequests the release of one claim. Refused unless the claim's holder is this connection's agent.
Request Release AllRequests the release of every claim held by a claimant owned by this connection.
Request Accept OfferAccepts a pending offer as an invitee owned by this connection.
Request Decline OfferDeclines a pending offer as an invitee owned by this connection.
On Claim ResultBroadcasts the result of a claim request, correlated by request ID.
On Release ResultBroadcasts the result of a release request, including the number of claims freed.
On Offer ResultBroadcasts the result of an offer response request.

C++ Reference

AccessGetWorld()->GetSubsystem<USmartClaimSubsystem>(). A UTickableWorldSubsystem; it ticks only while it has claims, offers, or queued work.
Blueprint parityEvery function in the Blueprint reference is a native UFUNCTION on this class with the same name and C++ signature.
SetInterruptionProvider / ClearInterruptionProvider / HasInterruptionProviderStatic. Installs the FSmartClaimInterruptionProvider delegate that InterruptSmartObjectUser calls. The optional GameplayInteractions adapter module installs one at startup; with no provider installed, interruption returns Unsupported.
SetUserDataAgentResolver / ClearUserDataAgentResolver / HasUserDataAgentResolverStatic. Installs the FSmartClaimUserDataAgentResolver delegate. If your project's user-data schema names an agent, install a resolver so the subsystem can validate that the schema's agent matches Request.Claimant, the same way it validates FSmartObjectActorUserData.
Per-world overridesStaleClaimSeconds, bAllowAnonymousClaims, AgentPolicy, NetworkMode, SweepIntervalSeconds, and DefaultPreemption are public UPROPERTYs on the subsystem, seeded from project settings on Initialize and overridable per world.
AccessCreate on a PlayerController (or resolve with FindComponentByClass). The component enables replication in its constructor.
Blueprint parityThe six Request functions and three result delegates in the Blueprint reference are the complete public surface.
IsAgentOfOwnerStatic. Returns whether a claimant is an agent the given controller may act for: the controller itself, its possession pair under the subsystem's AgentPolicy, or an Actor the controller owns. This is the ownership rule the server applies to every request; it is exposed for tests and for hosts extending the gateway.
USmartClaimListenerSmartClaimListener.h. An ActorComponent; bAutoWatchOwner controls whether it watches the owner's Smart Object components automatically. All functions match the Blueprint reference.
USmartClaimTokenSmartClaimToken.h. Created only through USmartClaimSubsystem::CreateClaimToken. The subsystem retains live tokens and invalidates them when their claim is released or preempted.
USmartClaimLibrarySmartClaimLibrary.h. Static BlueprintFunctionLibrary; the three helpers match the Blueprint reference.
USmartClaimSettingsSmartClaimSettings.h. UDeveloperSettings (Config=Game). Read with GetDefault<USmartClaimSettings>(). Backs the Project Settings page; the subsystem seeds its per-world values from it on Initialize.
ModulesSmartObjectClaims (Runtime, core), SmartObjectClaimsNet (Runtime, optional gateway), SmartObjectClaimsGameplayInteractions (Runtime, optional interruption adapter), SmartObjectClaimsTests (UncookedOnly).
DependenciesAdd "SmartObjectClaims" to PublicDependencyModuleNames. Add "SmartObjectClaimsNet" only if you use the gateway from C++. The core module depends on Core, CoreUObject, Engine, GameplayTags, and SmartObjectsModule; it does not depend on AIModule, GameplayTasks, or GameplayInteractions.
LoggingLogSmartClaims is a public log category. Raise it with the console command: log LogSmartClaims Verbose.
Schema versionSmartClaims::CurrentSchemaVersion and SmartClaims::MinSupportedSchemaVersion define the accepted range for FSmartClaimRequest::Version and FSmartClaimOfferRequest::Version. Requests outside the range return UnsupportedVersion.
FSmartClaimRequestEverything a claim needs: Claimant, ReasonTag, Priority, Preemption, the engine's FSmartObjectRequestFilter, RequiredSlotTags, an FInstancedStruct UserData for selection-condition binding, and bEligibilityAlreadyChecked.
FSmartClaimOfferRequestAn offer expressed with the same contract as a direct claim: initiator and invitee each bring a full FSmartClaimRequest, plus slots, a reason tag, and a timeout.
FSmartClaimRecordOne registry row: claim handle (owned rows only), object and slot handles, claimant, reason tag, age, occupancy, offer ID, source, and attribution.
FSmartClaimEventOne slot or object event: kind, object and slot handles, both Actors, the engine's native reason and tag, the claim's reason tag, source, attribution, claim handle, offer ID, and a delivery-order sequence number.
FSmartClaimOffer / FSmartClaimReservationA queryable offer with its live state, deadline, revision, terminal reason, and the reservations it holds.
FSmartClaimSlotResultOne entry of a batch claim, keyed to the caller's input index, including any displaced holder under preemption.
FSmartClaimWatchLeaseA watch you hold: a lease ID plus the watched object handle. Check IsValid before storing.
FSmartClaimInterruptRequestWhat the interruption provider receives: user, possession counterpart, reason tag, hard/soft intent, and the exact claim when known.
EnumsESmartClaimResult, ESmartClaimReleaseResult, ESmartClaimOfferState, ESmartClaimOfferResponse, ESmartClaimInterruptResult, ESmartClaimPreemption, ESmartClaimAgentPolicy, ESmartClaimNetworkMode, ESmartClaimEventKind, ESmartClaimSource, ESmartClaimAttribution, ESmartClaimParticipantRole, ESmartClaimWatchReason. Each value is documented in SmartClaimTypes.h.

Networking

Mutating claims is the server's job; reading them stays open to clients. When a client needs to claim something, add a SmartClaimNetGatewayComponent (from the optional SmartObjectClaimsNet module) to your PlayerController and call its request functions. Every request hands back a correlation ID, and the answer arrives on the matching delegate.

Ownership validationThe server validates that the requesting connection owns the agent named in the request. A request for any other agent returns NotOwnedByRequester.
Registry-based releaseThe holder of a claim is resolved from the registry, not from the request; a handle sent by a client does not prove ownership.
Result codes, not disconnectsFailed validation returns a result code. Invalid inputs occur naturally when a client loses a race, so they are answered, not punished.

Actor references inside a request must be replicated Actors to resolve on the server, and a custom UserData struct travels only if it supports network serialization.

Configuration

You can configure the plugin in Project Settings > Plugins > Smart Object Claims. Each setting has a tested runtime effect and can be overridden per world on the subsystem.

StaleClaimSecondsdefault: 30The age in seconds at which a claimed but never-occupied slot is reported as stale. Stale claims are reported only; they are never released automatically.
SweepIntervalSecondsdefault: 2The interval at which offer timeouts are checked and claims are reconciled against the engine.
bAllowAnonymousClaimsdefault: falseDetermines whether a claim with no claimant is permitted. An anonymous claim cannot be released by actor or attributed to a holder.
AgentPolicydefault: PawnAndControllerAreOneAgentDetermines whether a Pawn and its Controller are treated as one agent. The policy is applied consistently across claims, queries, releases, offers, and interruption.
DefaultPreemptiondefault: NeverThe preemption behavior used by requests that leave Preemption unspecified. A value set explicitly on a request overrides this setting.
NetworkModedefault: AutoDetermines which peers can mutate claims. Auto is local in standalone and server-authoritative in networked games.

Verification

This table is what the release gates actually verify - the right column is unverified, which is not the same as broken. You don't have to take our word for any of it: the 68-test automation suite ships inside the plugin and runs on your machine. Open Window > Test Automation, filter for SmartObjectClaims, and run - all 68 must pass. Or headless:

UnrealEditor-Cmd.exe YourProject.uproject -ExecCmds="Automation RunTests SmartObjectClaims" -TestExit="Automation Test Queue Empty" -unattended -NullRHI -log
EngineUnreal Engine 5.8. All gates on this page are run against this version.5.7 and earlier, 5.9 and later
TargetsEditor and Game, Development and Shipping, through an isolated BuildPlugin run with -StrictIncludesClient, Server, and Commandlet targets, which require a source engine build
PlatformsWin64Linux, Mac, consoles, and mobile. The plugin contains no platform-specific code, but these platforms have not been verified.
NetworkingStandalone. The gateway's ownership validation and full request/response path, exercised in-process.Traffic across a live NetDriver: dedicated and listen servers with connected clients
CookThe packaged plugin mounts and cooks inside a generated content-only host projectA full-game BuildCookRun, staging, and pak
Scale10,000 live claims, measuredPopulations beyond 10,000 and long-running churn
Tests68 automation tests, including a 500-request flood, a 500-claim release storm, and a seeded 3,000-operation randomized consistency testSanitizers, World Partition streaming, and seamless travel

Registry cost at 10,000 live claims

Measured on Win64 Development. The performance test logs the same numbers on whatever machine it runs on, so measure on your own hardware before you budget against these.

Claim~66 µs/op
IsClaimCurrent~54 µs/op
GetClaimTable (10k rows)~2 ms
Single release~247 µs/op
Mass release, 5k claims / 50 actors~0.5 s total

Design

Eleven decisions shape the plugin, and each one forbids something specific. Summaries below; the full record, prohibitions included, ships with the plugin as DESIGN.md.

USmartObjectSubsystem owns slot state. The plugin maintains a mirror of claims made through its own API, kept in sync by the engine's event delegates. When the mirror and the engine disagree, the engine is authoritative and the mirror re-synchronizes. No question about slot state is answered from the mirror alone.

Claiming a slot reserves it. A claim does not start a behavior, and claiming a slot on another agent's behalf does not commit that agent to anything. Two claimed slots do not mean an interaction has begun.

The invitee accepts, declines, or lets the offer time out. Every reservation is revalidated when the offer is accepted; if any reservation has been lost, the remainder is rolled back and the result is ReservationLost. Offers carry the same request contract as direct claims, including filters, priority, and custom user data.

InterruptSmartObjectUser asks the system running the interaction to stop at its own safe point. The core module does not depend on GameplayInteractions, GameplayTasks, or AIModule; an optional adapter module installs the interruption provider. With no provider installed, the call returns Unsupported.

A claim that is held but never becomes occupied is logged and broadcast after StaleClaimSeconds. Releasing it automatically would hide the leak that caused it and could race a holder that is legitimately slow to start.

There is no polling and no world scan. A claim watches its own object automatically; any other object is watched through an explicit lease. A lease can only be released by the caller that acquired it.

Release, ownership checks, token binding, and event matching all compare the full claim handle. No code path that decides ownership performs a lookup by slot alone.

The engine's native callback is a capture boundary. Events are applied and broadcast on the game thread, and the subject of each event is decided by its kind rather than by whichever claim holds the slot at delivery time. An event never combines fields from two claims.

A client that calls the subsystem directly is refused and the refusal is logged once per world. Client-initiated claims go through the optional SmartObjectClaimsNet gateway module, which validates that the requesting connection owns the agent it acts for. A request for another connection's agent returns NotOwnedByRequester.

Every setting configures a mechanism, and every setting has a tested runtime effect. Gameplay policy, such as when an agent should claim or interrupt, belongs to the host project.

A request carrying a schema version outside the supported range is refused with UnsupportedVersion. Additive API changes can land in 1.x releases; changes that rename, remove, or reshape the API wait for 2.0.

Support

The README, CHANGELOG, and design record ship inside the plugin package. For everything else, use the channels below.