Skip to main content

For AI Agents

This page is the routing layer for a coding agent working inside a studio's Unreal project. It does not re-explain the SDK; every row links the page that owns the fact, and the full text of every page is available as one file for your context window. Read this page, then pull only the pages a task needs.

Three machine-readable files sit beside the docs. This page is where they are described:

FileWhat it is
/helpers/unreal-sdk/llms-full.txtEvery page in this section as plain text, in sidebar order, with every C++ snippet inlined and a pointer line for every Blueprint graph.
/helpers/unreal-sdk/sdk-surface.jsonThe generated inventory of every reflected symbol the plugin ships. Check it before you go looking through headers; see SDK surface manifest.
/helpers/unreal-sdk/AGENTS.mdThe drop-in for a project root, CLAUDE.md, or .cursorrules: the locked rules and one example per marker, under 8 KB.

Where does this state go​

Ask one question: can a malicious client benefit from lying about this value? If yes, it belongs on the truth plane, in your app's server code on ck-exec, which the SDK reaches from C++ or Blueprint through Server Logic. If no, it is view state. The Two Planes owns the rule and every row but the door's; that one applies the same test.

StatePlaneMechanismOwning page
MovementViewDynamic entity mode, continuous state channelContinuous state
Animation blend stateViewmeta=(CrowdyState) property, diffed on changeCrowdy State
A one-shot trigger (muzzle flash, impact)Viewmeta=(CrowdyEvent) RPC, not a state fieldRPC events
Hit points, inventory, currencyTruthYour server codeThe Two Planes
Match scoreTruthA hub for the match in your server codeThe Two Planes
A chat lineViewCrowdyEvent with the Multicast recipient, session channelChannels
A door's open flagViewCrowdyState boolean: a lie is a visual glitch. A placed door is host-owned, so the flag is also CrowdyManualDirty and the host calls MarkStateDirty after each write. If the door gates progress (a lock), the lock state belongs in your server codeThe Two Planes, the host push
A player's display nameViewCrowdyState property; route it through the avatars service if it must persistThe Two Planes, Avatars
Authoritative or cheat-sensitive state never belongs in Crowdy State.

Crowdy State is written by the owning client and believed by everyone else. Low latency is not a reason to move a value to the view plane; predict locally and let the server's confirmed value overwrite the prediction. The Two Planes.

The marker vocabulary​

Two of the three are meta=(...) keys; the entity is a component. One canonical example each, all from the same lantern the Quickstart builds; the snippet id names the file the docs render.

MarkerGoes onCanonical exampleOwning page
CrowdyEntityA UCrowdyEntityComponent on an AActor (the component is the marker; set Ownership)qs-entityEntities, identity, ownership
CrowdyState (+ CrowdyOnRep)A UPROPERTY on an entity actor. On a client-owned entity (Ownership = Local Client, the lantern) the owner assigns and calls the notify itself; on a host-owned entity (Ownership = Host, the default a placed actor keeps) a plain assignment never ships: add CrowdyManualDirty and call MarkStateDirty after each write, or add CrowdyHeartbeatqs-stateCrowdy State, the host push
CrowdyEvent + CrowdyRecipientA UFUNCTION named X_Implementation, plus CROWDY_EVENT(X); recipient is one of SpatialMulticast, Multicast, OwningClient, Hostqs-eventRPC events

Four rules the examples assume: the first thing a client needs is its own pawn as a Dynamic, PlayerDerived, LocalClient entity spawned after On UDP Connection Success (qs-player), because the server knows a client by its actor updates and a SpatialMulticast reaches only clients with a fresh actor; that pawn registers inside its first possession, since the engine possesses a runtime spawn after its BeginPlay, so read its id and IsLocallyOwned() from OnCrowdyOwnershipAssigned, not at BeginPlay; a SpatialMulticast body runs on the caller too, so guard the call with IsLocallyOwned() (RPC events); a CrowdyOnRep function takes no parameters, read the property for the new value (Crowdy State); and the lantern's assign-and-stop is the client-owned pattern, because the Quickstart sets its Ownership to Local Client: a host-owned entity (Ownership = Host, the default a placed actor keeps: Static, Stable, level-placed) never ships a plain CrowdyState write, so mark the property CrowdyManualDirty and call MarkStateDirty after each write, or give it CrowdyHeartbeat (the host push).

Three things that break silently in a cooked build​

Editor and Play in Editor hide all three. Only a packaged build shows them.

BreakWhy it is silentFixOwning page
Reading HasMetaData at runtimeCooked and Shipping builds strip UObject metadata; the check answers false and marker-driven code goes dark with no errorRead UCrowdyBakedRegistry; the SDK already does this for RPC and Crowdy State metadataPackaging
A marker added after the last cookThe baked registry is a snapshot taken at cook time; a new marker is invisible until it is rebakedCook again: the cook rebakes the registry at its start, so the break is a package cooked before the marker existed. Tools > Rebuild Crowdy Registry or Studio's Rebuild (Deep Scan) only refresh the in-editor view of the bakePackaging, Inspector and Registry
A placement guid read at BeginPlayThe engine releases the per-placement guid before BeginPlay in a cooked build; the editor keeps it aliveRead placement data at OnRegister, never at BeginPlayStable identity in a packaged build

A fourth hazard is loud, not silent: Shipping sets WITH_DEV_AUTOMATION_TESTS to 0, and a target that forces dev tests back on can then fail to compile a test file through a !UE_BUILD_SHIPPING region it reaches via a header. What Shipping strips.

Settings never to hand-edit​

Config Sync in Crowdy Studio writes the Network category of UCrowdySDKDeveloperSettings into Config/DefaultGame.ini under [/Script/CrowdyReplication.CrowdySDKDeveloperSettings]. The fields are read-only in Project Settings on purpose; a hand-pasted value is overwritten by the next sync, and a drifted project fails to connect with no obvious error.

Never hand-typeOwned by you (fine to edit)Owning page
Environment, DiscoveryUrl, AppID, OrgId, GameApiHttpUrl, GameApiWsUrl, UDPProtocol, UDPTimeoutSeconds, HostPollIntervalSecondsMapProfiles, DefaultProfile, BakedRegistry, PreloadedEntityClasses, IDOverrides, ClassIDOverridesConfig Sync, Project settings

Do not copy the plugin's [CoreRedirects] into the project's DefaultEngine.ini either; Plugins/CrowdySDK/Config/DefaultCrowdySDK.ini already carries them. Packaging.

Testing without a second client​

Both switches are off by default. Turn them back off before testing real two-client routing. Testing locally.

Console variableWhat it doesOwning section
crowdy.rpc.loopback 1A sent CrowdyEvent is also delivered to your own receive path, once; a call made from inside the replayed body does not loop againSingle-client RPC loopback
crowdy.state.loopback 1A state delta for an owned entity is decoded onto a lazily spawned local mirror (RemoteProxy role, its own NetID), so OnRep fires with one client. The mirror's trace line always says (targeted); that label is cosmeticSingle-client Crowdy State loopback

Trace console variables​

Every area exposes crowdy.<area>.trace, off by default; warnings and errors print regardless. The four to reach for first, and the two to leave alone:

Console variableShowsOwning page
crowdy.rpc.traceEvery CrowdyEvent send and receive: function, entity, addressing, owned or delegatedTesting locally
crowdy.state.traceOwned-entity diffing, delta emission, datagram sizesTesting locally
crowdy.entity.traceRegistry add and remove, event routing and dispatch, actor trackingTesting locally
crowdy.net.traceUDP transport: socket open and close, sends and receives, pool churnTesting locally
crowdy.serialize.scopes, crowdy.state.scopesCPU trace scopes around every message decode, a real per-message cost; leave off unless reading that timingConsole variables

The full table, including behavior switches and diagnostic commands, is on Console variables; which log category pairs with which trace is on Log categories.

Locked rules in one place​

RuleOwning page
Every id is int64; the app id travels as a JSON string and the SDK encodes it, never youThe Two Planes
The host is a convention, not enforcement; never gate cheat-sensitive state on itHost is a convention, Host authority
Presence is the player's actor: a client with no fresh actor is at no position and receives no spatial eventSessions and presence
A world subsystem null-checks GetGameInstance() in Initialize(); it is null in the transient world at engine startHost authority, Replicated subsystems
An array parameter on a delegate handler is const TArray<T>&; a by-value parameter does not match and AddDynamic will not compileTeams
A Multicast call has a 1024-byte payload budget and is dropped loudly, never truncated; large payloads go on SpatialMulticastRPC events