What's Changed
The customer-visible changes to the Unreal SDK since 2.0 / 2.1, newest first, each with what changed and what your project does about it. The list comes from a diff of the 2.1.0 public headers against 2.15.0 plus what each release since carried; anything not listed here kept its name, its pins, and its behavior as far as that diff shows.
When you come here
You built against the SDK in July, you are updating the plugin to 2.15 or later (2.24.0 is current), and you want the list of things that compile differently, behave differently, or no longer exist, before you open the editor. Read top to bottom once. The entries that can break a C++ build are the teams rename (between 2.1.0 and 2.12.0), the removed DevLogin (2026-08-30), in 2.15.0 the array delegates, in 2.19.0 the removal of the Game Model API (see its entry), and in 2.20.0 the renamed pin delegates of the Channels, Teams, Avatars and sign-in nodes, which a C++ AddDynamic handler binds to (see On Error). In 2.21.0, a C++ AddDynamic handler bound to the Call Server Function action's On Success or On Failed needs one more parameter, FName Code (see its entry). In 2.22.0, voice chat changes its packet format, so every player needs the new SDK version for voice, and a C++ handler bound to OnAudioNotify or the other old voice delegates no longer compiles (see Voice chat).
The SDK's Game Model API (containers and attributes, Effects, sessions, pre-seeding) was built on the game API's legacy game-model engine, which ck-exec replaced; the game API no longer serves it. The entries that describe it are kept as history. Do not adopt it: v2.18.0, the release that adds Server Objects, deprecates it, and v2.19.0 removes it, as described under 2026-10-12 (SDK v2.19.0). Its replacement is Server Objects, described under 2026-10-07 (SDK v2.18.0); Move from Game Models says what to change.
2026-11-02 (SDK v2.24.0)
No breaking change and nothing deprecated. From this release the plugin's MIGRATION.md lists the same changes with the exact steps to take.
Sign-out, packaged installs and MIGRATION.md
- No phantom host after sign-out. On sign-out the SDK told the host subsystem there was no host by setting the host user id to 0, and the subsystem turned that 0 into a valid-looking id. Is Host Set stayed true, Get Host ID returned an id that belongs to no player, and On Host Elected fired with a host that does not exist. After a level travel, or on a map without networking, sign-out could also leave the host set. Now sign-out clears the host before On Logout fires, and user id 0 gives an empty (invalid) id: Is Host Set is false, Get Host ID is invalid, and Is Host is false (as before). When a host had been elected on the current map, On Host Elected also fires once, on the next frame, with an invalid
HostIDand the previous host asPreviousHostID. The first election on each map (the host subsystem belongs to the world, so it starts again after a level travel) fires On Host Elected with an invalidPreviousHostID, where it used to carry the same made-up id. A host poll answer that arrives while nobody is signed in, such as one still in flight at sign-out, is ignored, so it can no longer elect a host again after sign-out. What you do: if you bind On Host Elected, treat an invalidHostIDorPreviousHostIDas "no host". See Host Election. - Packaged plugin zips now carry the plugin's
Config/folder. The plugin'sConfig/FilterPlugin.iniheld only comments, so a plugin built with UAT BuildPlugin (which is what the package script runs) leftConfig/out of the zip.Config/DefaultCrowdySDK.ini, which holds the SDK's own CoreRedirects, was missing from those installs. The filter now includes/Config/...,/MIGRATION.md,/README.mdand the license file. What you do: if you install from a packaged zip, take the 2.24.0 package. CoreRedirects you copied into your ownDefaultEngine.inias a workaround may stay; duplicates are harmless. See Installation. - The plugin builds with UAT BuildPlugin again. Packaging the plugin with
RunUAT BuildPlugin(as a Fab submission or a prebuilt-binaries release does) failed: UHT requires an explicitCategoryon every editor- or Blueprint-exposed property when it builds a plugin that way, and its game target compiles each file without a precompiled header, which exposed files that relied on one for engine types. Every exposed property now has aCategory, and every file includes what it uses. The Teams and Channels permission structs' fields moved from theCrowdy|TeamsandCrowdy|Channelscategories toCrowdy SDK|TeamsandCrowdy SDK|Channels; that only changes how the editor groups them. What you do: nothing; if you package the plugin yourself,BuildPluginnow succeeds. - New:
MIGRATION.mdat the plugin root. It ships in the plugin and in the public CrowdySDK-Unreal repository. For each release it lists what changed and the exact steps to migrate, laid out so a person or an AI agent can follow it. From now on every user-visible SDK change adds an entry there along with its docs. What you do: when you update the plugin, read the entries for the releases between your version and the new one. See Installation.
Spawned entities appear once
- Spawn data reaches the actor players see. The actor pool replaced the actor a spawn event made with a pooled one, so what On Crowdy Spawned set on it was lost, and when an update arrived before the spawn event, On Crowdy Spawned never fired at all. Now the pool draws the spawn event's own actor, and On Crowdy Spawned fires once per entity on each client that receives the spawn event, in either arrival order. What you do: nothing; per-instance appearance passed in
InitialStatenow shows on other clients that were there for the spawn. See Entities and spawning. - Static entities reach late joiners, spawn data and owner included. A Static entity spawned with Spawn Crowdy Entity sent nothing after its spawn event, so a player who joined later never saw it. Now its owner repeats the spawn event every 4 seconds, from where the entity is now. A late joiner builds it as if it had seen the spawn: the actor, its owner, and On Crowdy Spawned with the spawn data. Clients that already have it ignore the repeats, and a repeat that arrives after the entity's destroy cannot bring it back. Each repeat is about 280 bytes plus your spawn data, about 70 bytes per second per entity. What you do: budget the traffic if you spawn many Static entities. See Entities and spawning.
- No second copy of your own entities. The client that spawned an entity also drew a pooled copy of it, driven by the server's echo of its own updates. That copy is gone by default. What you do: if a single-client demo relies on it, turn on Render Local Owner on the map profile's Actor Pool Backend Config. See Rendering backends.
- A destroyed entity stays destroyed. An update that arrived just after an entity's destroy event drew it again for about 12 seconds, and Destroy Crowdy Entity kept sending updates for the length of Destroy Delay. Now sending stops when the destroy is called, and updates in the half second after a destroy event are not drawn. What you do: nothing. See Entities and spawning.
- Ownership moves redraw the entity. When ownership of an entity moves to or from your client, the actor pool sets its drawing up again on the next update: you stop seeing an entity you handed away frozen, and an entity handed to you is no longer pulled back to the echo of your own updates. What you do: nothing.
2026-10-31 (SDK v2.23.0)
Nearby text chat and voice distance
- Nearby text chat. Send Proximity Text on the new Crowdy Proximity Chat subsystem sends a line to every player within a distance you give, counted in chunks from the chunk your pawn stands in, and On Proximity Text Received hands you every line players near you send, with the sender's actor id and entity id. On Proximity Text Refused reports a line the server refused. A line is plain UTF-8, so it reaches CrowdyJS and CrowdyCPP players in the same app. The proximity text message an earlier SDK sent from C++ (
FTextMessageRequest) now carries the text alone: itsUserIDandUsernamestill compile but are not sent. What you do: nothing, unless you want local chat. See Nearby Text Chat. - How far your voice reaches. Set Voice Chat Distance and Get Voice Chat Distance on the Crowdy SDK subsystem set it from 0 to 8 chunks; the default stays 8, the farthest the server relays, so nothing changes until you set it. What you do: set it if voice should stay local. See How far your voice reaches.
- Voice is now sent from where your pawn stands. Earlier versions addressed every voice frame to the chunk last given to Set Player Current Chunk Coordinates, which the SDK never set, so voice reach was measured from the origin chunk. What you do: nothing; with a pawn, voice now follows the player.
- Audio that is not the microphone, for C++.
UVoiceChatSubsystem::FeedVoicePcmsends 48 kHz mono samples as your voice through the microphone's own gate and encoder, andEndVoiceFeedends the burst. What you do: nothing, unless you voice a bot or play a recorded line. See Sending audio that is not the microphone.
2026-10-30 (SDK v2.22.0)
Voice chat has a new packet format, shared with the CrowdyJS and CrowdyCPP SDKs. Players on an earlier SDK version cannot hear players on this one, and the reverse. Update every client that should take part in voice.
Voice chat
- Voice is rewritten on the shared voice format. It uses Opus in 20 ms frames, with reordering and loss concealment, and it needs no player pawn, so it works in any map. The microphone and the Play, Mute, owner echo and timeout settings carry across map changes. Up to 16 speakers are heard at once, and received voice plays about 120 ms behind. What you do: update every client that should take part in voice; your existing voice graphs keep working. See Voice Chat.
- New nodes to see the microphone and who is talking. Get Voice Mic State returns Off, Starting, Capturing, No Device or Error, and On Voice Mic State Changed fires when it changes. On Voice Speaker Talking gives a Speaker Uuid and Talking: talking means voice above a silence gate, and it ends about 0.3 seconds after the speaker stops. A silent microphone sends nothing. In C++ they are
GetVoiceMicState(),OnVoiceMicStateChangedandOnVoiceSpeakerTalkingonUCrowdySDKSubsystem. What you do: nothing, unless you want a talk indicator. See Showing who is talking. - Toggle Owner Echo is now an exec node. Earlier versions never ran it from Blueprint. What you do: wire it into a graph if you called it there, and turn it off again after the check. See Confirming capture with owner echo.
- Other players are muted until Play Voice Chat. The six existing voice nodes keep their names. What you do: call Play Voice Chat once if your game should hear others. Set Voice Chat Stream Timeout Threshold now clamps its value to 0.5 seconds up to 1 hour.
- The old voice delegates are removed.
OnAudioNotify,OnAudioDataGenerated,OnAudioDataReceivedandOnStreamTimeoutonUVoiceChatSubsystemare gone, and so are the two delegates of the audio device monitor. This breaks a C++ build that binds one of them. What you do: bindOnVoiceSpeakerTalkingonUCrowdySDKSubsysteminstead ofOnAudioNotify. See Blueprint Delegates. - Microphone handling. A missing microphone shows No Device and voice chat opens it as soon as one is available. On Windows a changed default microphone restarts capture by itself. A microphone that delivers nothing goes to Error with a hint about the OS microphone permission. What you do: nothing.
- Platform notes. Linux has no engine audio capture, so voice capture reports it is not supported there. A packaged Mac game needs
NSMicrophoneUsageDescriptionin its Info.plist, which the engine's Mac template does not include. What you do: on Mac, add the key; see Platforms. - Voice counters for C++.
UVoiceChatSubsystem::GetStats()returns packets sent and received, frames played, concealed and dropped, and the speakers heard right now. What you do: nothing; read it from a debug overlay or a soak test if useful. See Watching activity.
Warnings where things used to fail silently
- A bad
CrowdyRecipient,CrowdyDecayorCrowdyDistancevalue now warns. A meta value that is not a valid name logs a warning that lists the valid names, and the default is used. A Blueprint replicated event gets a compiler warning on the node. What you do: fix the name the warning shows. See Recipients and routing and RPC meta keys. - A Multicast or SpatialMulticast event on an actor with no registered entity now warns. The warning differs when the Crowdy Entity Component exists but has not registered yet, for example on a pawn before it is possessed: call the event after registration. If the actor has no component, add one. What you do: read the warning; the advice depends on which case it is. See RPC events in Blueprint.
- Mark Crowdy State Dirty warns once per world. It warns when the actor has no Crowdy Entity Component, or when the property name is not one the class has. What you do: add the component, or fix the name. See CrowdyState.
- Get Server Value and Set Server Value warn once when the name is not a value of the struct. What you do: fix the name. See Call its functions, from Blueprint.
Crash fixes
- No crash without a game instance. Get Chunk Coordinate At Location and Crowdy Is Connected To Server no longer crash when called where there is no game instance, such as an editor world or a construction script. What you do: nothing. See Recipients and routing.
Remote movement
- No more one-frame flicks after a sender hitches. When a player's game stalled and its next two updates arrived almost together, the actor pool's shipped policy extrapolated their tiny gap up to 200 times, so other players saw that player flick far away for one frame. Extrapolation now measures the step over the recent average gap, so it continues at the sender's normal pace. A custom policy built on
TInterpolatedFieldgets the same fix. What you do: nothing. See The sample ring.
Quieter logs
- Fewer lines for things that are fine. Event payload registration no longer logs a Warning per struct. A send refused while connecting is reported once per second, with a count of the ones suppressed. A map with networking off no longer logs Errors at load. "Host not set yet" and the missing host subsystem message are trace-gated. What you do: nothing; a log filter you wrote for the old lines will not match. See Log categories.
- The
CrowdyMass.*diagnostic commands are for development builds. The four commands are no longer in Shipping builds, and they print normal output, not warnings. What you do: nothing. See Mass troubleshooting.
Clearer messages
- Server Object refusals name the Blueprint nodes. Get Server Object says it needs a Definition or an Owner; an empty Instance Id is named; an unknown function lists the type's functions; wrong parameters point at Make Inputs. What you do: nothing. See Get a Server Object and Troubleshooting.
- Definition validation says what to fix. It names the Timer, Message or Function that has no Name, and says how to fix a reserved name. See Create a type.
- "Payload not registered" errors name the struct and say how it registers. See RPC events in C++.
- Studio connection errors say what to check. See Projects and apps.
Blueprint
- Call Server Function shows Outcome and Reason on the node. They no longer sit under the small arrow; Retryable and Code still do. Wires you made keep working. See Call its functions, from Blueprint.
- The ready pin on Get Server Variable and Get Server State reads "Is Ready". It is the same pin, so existing wires keep working. An optional variable's own Has Value pin is unchanged. See Get a variable.
- Struct field tooltips show on Server Object value pins. See What the pins are.
- Crowdy SDK nodes sit under one "Crowdy SDK" palette category. Search works as before and no graph changes. What you do: nothing; the paths in the docs now read
Crowdy SDK|.... - More tooltips. Dispatch Game Event's target, decay and distance values have tooltips. The network statistics tooltips state their units, and Dropped Client Notifies is now Client Notifies In Flight. What you do: nothing, unless a graph or a note refers to the old name.
Deprecated
- Reload Endpoints From Settings and Set Expected Actor Update State Size. The first was already a no-op, and the second had no effect. Both still work. What you do: remove them from your graphs. See Project settings.
Saved state
- A level-placed actor's fallback key no longer includes the PIE prefix. The path-based key now matches between Play In Editor and packaged builds, except on World Partition maps. State saved from PIE under the old key is not read back. What you do: nothing for packaged data; expect PIE to start without the state it saved before. See Persistence.
Studio
- Enter submits the sign-in form and the first step of Create App.
- More confirmations. Revoking all of a user's or group's grid grants, revoking a tier feature, and switching a Live app to Draft now ask first.
- Tooltips and feedback. Registry badges and grid buttons have tooltips, and Tools > Rebuild Crowdy Registry shows a result notification.
See Sign in, Grids, Projects and apps and Inspector and Registry.
Mass
- An area spawner stops at the first failed spawn and logs one line. What you do: read the log line and fix what it names, then spawn again. See Turn Mass on for a map.
- Spatial-grid settings are greyed out when the grid is off.
For AI agents
- New
registryop.-run=CrowdyStudio -op=registry [-class=substring] [-norebuild] [-out=file]rebuilds the baked registry and prints every entity class's CrowdyEvents (recipient, decay, distance) and CrowdyState properties (owner-only, manual dirty, heartbeat, OnRep) as JSON. What you do: use it instead of reading the sources when you need an event's routing. See Sync from the command line. - The docs' AI helper files come as an index plus per-section files and a slim symbol index. See For AI Agents.
2026-10-29 (SDK v2.21.2)
- Read kit actors without a copy. The Crowdy Kit Actor subsystem gains Get Kit Actor Count (Kind) and Get Kit Actor (Kind, Index, Kit Actor out, returns a bool), and C++
ViewKitActors(FName Kind), which returns a const reference. Get Kit Actors still works but copies everything. Kind None means every kind for the nodes; forViewKitActorsit is the actors of no named kind. What you do: a per-tick read, such as a Mass map's Blueprint, switches to Count and Get Kit Actor; follow an actor by its id, since its index changes as others come and go. See Show a starter's actors. - Kit actors are placed by the session's chunk size. They used a fixed 1600; they now use
UCrowdyGameSession::SetChunkSize.FCrowdyKitActorStategainsChunkPosition, the raw chunks the hub sent, andLocationstays in world units. What you do: nothing. See Show a starter's actors. - A starter's On Changed runs once at Ready. On X Changed and On Variables Changed no longer also run before Ready, so a snapshot pushed while Connecting does not announce. What you do: nothing, unless a graph relied on the early run. See Follow a variable.
- Owner Values the object does not read report no value. Inventory, Wallet and Progress of another player's
CSO_Kit_Player, or of any while signed out, now give Has Value false, not an empty value with true. What you do: branch on Has Value where you showed an empty one. See Values only the player reads. - Owner Values are read again after a pushed hub restart. What you do: nothing. See Values only the player reads.
- A snapshot's value wins over an Owner Value of the same field. In a customized starter whose snapshot carries a field that is also an Owner Value, the value used to freeze. What you do: nothing. See Values only the player reads.
- No Get Server State where nothing is visible. It is not offered for a Server Object whose State has no player-visible variables, such as
CSO_Kit_Npcs; an existing node on one fails to compile with a message. What you do: remove the node. See Get a variable. - Config ids that differ only by case are refused. Add starter and Deploy refuse a starter Config with two ids of one kind (item, currency, faction, counter, pity group) that differ only by case, since the SDK reads them as one. What you do: rename one. See Add a starter.
- Bad kit actor updates are dropped. An update with a Name over 128 bytes or a Mode over 32 bytes is dropped, and so is one whose id, hp, max hp or owner is not a whole 64-bit integer. What you do: nothing. See Show a starter's actors.
- Call as Developer from the command line reaches a starter's read-only player functions.
-op=callcalls them as the developer, today the npcs starter'sstate. What you do: nothing. See Call a function as the developer. - The director publishes less. While an encounter runs it publishes only events on a hit or a build; the next tick, at most 500 ms later, carries the snapshot. The hit reply still returns health at once. What you do: nothing. See Defend against waves.
- The zone's realtime event carries
centerZ. The ZoneChange event gainscenterZbesidecenterY, the same ground value. What you do: nothing. See Shrink a circle. - A sign-in can be cancelled without signing out.
CancelSignIn()on the Crowdy Authentication subsystem (and on the Crowdy SDK Subsystem) stops a restore, a password sign-in, an email link or social sign-in waiting for the browser, or one held for the terms. Only the call'sOnErroranswers, withSign-in cancelled., and a cancelled restore keeps the saved session, whichLogoutwould delete.IsSignInPending()says when a Cancel button makes sense. What you do: if your sign-in screen's Cancel or Back button callsLogout, callCancelSignIninstead so a returning player keeps their saved session. See Cancel a sign-in.
2026-10-28 (SDK v2.21.1)
- Blueprint sign-in starts from the Game Mode, not the Game Instance's Event Init. A Blueprint Game Instance's Event Init runs before the SDK's subsystems exist, so the earlier Blueprint examples, which signed in and bound events there, failed: Login and Restore Session reported "subsystem not found" and Bind Event bound nothing. The examples now run from the Game Mode's Event BeginPlay and check Is Signed In first, since BeginPlay runs again in every map. C++ examples, which use
InitafterSuper::Init(), are unchanged. What you do: if your Blueprint Game Instance signs in or binds SDK events in Event Init, move that graph to your Game Mode's Event BeginPlay. See Signing in. - Two packaged clients can sign in as different accounts. Clients sharing a
Savedfolder (Development copies in one folder, or any Shipping copies of one Windows user) shared one saved session. Start each extra client with-crowdysession=<name>, and it keeps a session of its own; with no switch the default session is used, so existing sign-ins still restore. What you do: nothing, unless you run several clients from one folder. See Two packaged or standalone clients. - A deploy leaves a dropped type's Server Objects to the platform. The platform now stops the Server Objects of a type a deploy or a rollback drops, keeping their saved state, and drops that type's switch, so the SDK no longer restarts those types before it deploys.
-op=deploy's result losesstoppedRemovedandstopRemovedFailed, and-op=instancesgainsorphaned, true for a Server Object whose type the active version does not deploy. What you do: a script that readsstoppedRemovedorstopRemovedFailedstops reading them. See From the command line. - CrowdyCPP 0.74.0. The realtime library moves from 0.73.0 to 0.74.0, which reads
orphanedon each Server Object; the SDK sends and receives as before. What you do: nothing.
2026-10-26 (SDK v2.21.0)
- Run Generate Server Code once on every Server Object type (the Generate button on Server Compute does all of them). Generated crates now pin ckx-sdk 0.12.1, and Server Compute refuses to deploy a crate generated before; your
logic.rsis kept. - In C++, add an
FName Codeparameter to a handler bound withAddDynamicto the Call Server Function action's On Success or On Failed. The delegate has a fifth parameter, so the build breaks until you do.
Game Kit starters
- The Game Kit starters are Server Object types. Add starter on Server Compute, or
-op=addstarter, adds any of the platform's seventeen Game Kit starters (world,matchmaker,session,leaderboard,player,market,npcs, six game modes,deck,arena,raceandball) to the project as an ordinary type, with the starters it needs: its Rust underServer/<Type Name>/, its definitionCSO_Kit_*under/Game/ServerObjects/GameKit/, its values as C++ structs inCrowdyGameKitTypes.h. A game uses it with the nodes it already knows, and the definition's new Game Kit category holds what only starters have. Withworldadded,worldis the app's root and the Game Kit World setting points Get Players Online at it.-op=removestarterremoves a starter and those that need it. What you do: nothing, unless you want a starter. See Use the Game Kit starters. - Refused, and a Code on every call's answer.
ECrowdyServerCallOutcomehas a new last value, Refused, for a starter's refusal of the game's own, such as a full match, andFCrowdyServerCallResultaCode(FName), such asfull; your own types never answer Refused. The Call nodes have an advanced Code pin, and the Call Server Function action's delegateFCrowdyServerCallDonehas a fifth parameter,FName Code. This breaks a C++ build that binds On Success or On Failed withAddDynamic. What you do: add the parameter; give a Blueprint Switch on the outcome that lists every pin a Refused pin if it calls a starter. See Refusals and their codes. - Find Match, Is My Turn and Get Turn Player. An async Find Match node joins a matchmaker queue, follows it and accepts the match; Is My Turn and Get Turn Player read a session's turn. In C++,
CrowdyKit::FindMatchand its siblings inCrowdyGameKitMatch.h. What you do: nothing. See Find a match and take turns. - A starter's actors are drawn as classes you choose. A starter whose server code moves actors, such as the mobs of
npcsor the ball, has them drawn as the classes its Kit Actors names, each with a Crowdy Kit Actor component holding its Kit Actor (with the Instance Id of the hub to call for it); the new Game Kit Actor Starters setting cooks them, and Kit Actor Timeout in the map profile ends one that stops updating. What you do: nothing. See Show a starter's actors. - The Crowdy Server Object component reports where the player is. For a starter with a Position Function (the arena, race and ball), the component sends the owner's place by itself, on real time so a paused game keeps reporting; new settings Position Report and Snap To Kept Position, and the event On Position Kept. What you do: nothing. See Report where the player is.
- Grant and deposit soft currency from Studio and the command line. The
playerstarter's row on Server Compute has Grant soft currency... and Deposit soft currency..., and the command line-op=grantand-op=deposit; Call as Developer and-op=callfill a starter function's emptyRefwith a fresh one. Soft currency is game state, never real money. What you do: nothing. See Grant and deposit soft currency from Studio.
Parties, chat, emotes and spectating
- Parties, guilds, chat rooms, emotes and spectating, as the Game Kit's other clients do them. The Crowdy Kit Social subsystem and its nodes make parties, guilds and chat rooms on teams and channels, with Send Chat and On Chat Message; Send Emote and On Emote on Crowdy Kit Emotes reach the players nearby, within two budgets (
crowdy.kit.emotes.*console variables); the Crowdy Spectator component keeps a player with no avatar receiving what happens near it. A client on 2.20.0 or older logs a Warning for each emote and spectator beat it hears. A CrowdyEvent whose generated type id would be 101, which the Game Kit's emote uses, takes another and logs a Warning naming both. What you do: nothing; if that Warning names one of your events, players on this SDK and on an older one stop exchanging it until they all update. See Play together.
Deploying and Server Compute
- A deploy builds in parts, reuses unchanged code, and stops the types it removes. A project over 16 crates or 2 MB of source is built in parts instead of refused (staging versions with stand-ins named
ck_staged_, a reserved prefix); a crate whose files match a revision the app holds is not built again (Rebuild All and Deploy and-rebuildbuild everything); and the running Server Objects of a type the deploy leaves out are stopped just before it.-op=deploy's result gainsbuilds,reused,stagedVersions,restoredVersion,artifacts,stoppedRemovedandstopRemovedFailed, and its top-levelbuildId,buildStatus,buildLogandsdkVersiondescribe the last build only and are absent when nothing was built. What you do: rename a type whose Type Name starts withck_staged_; a script that readsbuildStatusreadsokandbuildsinstead. See A deploy in parts and The deploy's result. - Server Object crates pin ckx-sdk 0.12.1. Generate writes
ckx-sdk = "0.12.1"in a crate'sCargo.toml(it was0.7.0), and Deploy refuses a crate generated before. A Generate button on Server Compute, and-op=generate, generate every type that is out of date. What you do: press Generate once after updating; it keeps yourlogic.rs. See Generate it. - A starter's variables have their Get and On Changed nodes. On a starter's definition the menu offered no Get or On Changed for its variables, and a pasted one failed to compile; they work now, and a flag such as
bHasWavehas a Get and no On Changed, since On Wave Changed reports it. What you do: nothing. See Read a value that may be none. - A type the active version lacks says it is not deployed, as a type never deployed does, after you make an older version active; and a call while the connection redials into a paused or switched-off app reports Paused where it reported Timeout, as do the platform's new pause statuses. What you do: nothing. See Outcomes.
The command line and agents
- Drive the editor, Server Compute and Crowdy Studio from a shell. The Server Compute commandlet gains
whoami,generate,exporttype,newtype,edittype,removetype,renametype,addstarter,removestarter,grant,deposit,restartwithout a type,enablefor the whole app, more oflogsandstats, andbatch(several operations in one editor start); the new-run=CrowdyStudiocommandlet syncs, creates apps and manages access features, teams, channels and grids. Operations that write the project refuse while any Unreal Editor is running unless you pass-editoropen, and those that change the Game Kit settings refuse a read-onlyDefaultGame.ini. What you do: nothing; point a coding agent at For AI agents. See From the command line and Sync from the command line.
Sign-in
- On Login fires for every failed restore. On Login now fires with
bSuccessfalse for every failed session restore,No saved session foundon a first launch included. What you do: if your false branch shows an error, skip that message, or call Restore Session only when Has Saved Session is true. See Restore a saved session. - A sign-in under way is cancelled by a logout or Clear Saved Session. A sign-in, token mint, refresh or link still in flight then reports
Signed out before sign-in finished.on the caller's On Error and signs nobody in. What you do: call Clear Saved Session only when no sign-in is under way. See Session persistence and sign-out. - Other sign-in changes. A sign-in held for consent fails at once in a process with no window instead of waiting forever; password sign-in and the app token are retried when the platform is busy (
crowdy.net.retry.busy); a token answer with no runtime gate clears a pause; Server Objects reconnect when another account signs in; Development builds havecrowdy.auth.signin.file,crowdy.auth.statusandcrowdy.auth.signout. What you do: nothing. See Authentication and Sign in from the console.
Crowdy Studio
- Studio fixes. A team role offers the team permissions (Manage Team, Manage Members, Manage Roles, Invite Members) and no longer Send Messages; a maximum set back to unlimited shows unlimited; an organization-token sign-in survives a network error; the module loads in the Default phase. What you do: nothing. See Teams and channels.
Runtime and Mass
- The actor tracker's Configure applies only before the tracker starts, which is before any Blueprint can call it, so it now logs a Warning and changes nothing; the node is deprecated. What you do: set the limits in the map profile or the developer settings and remove the call, Blueprint or C++, or the actors past the profile's limit stop being tracked. See The actor tracker.
- An actor id that is not hexadecimal keys apart. To Guid now hashes such an id instead of reading it as
FFFFFFFFparts. What you do: derive again a key you saved from such an id. - A promoted shell is hittable only on its character mesh and on components tagged
CrowdyTraceCollision, and a Mass trace the grid cannot handle tests every entity. What you do: tag a hand-made hit boxCrowdyTraceCollision. See The near tier component's settings. - Smaller changes. A Watch Record added from a records handler runs once; the typed Call node labels its limit pin Call Timeout when the function has a Timeout input; an Allowed Exec Gateway Domains entry that is not a domain is named in a Warning;
crowdy.registry.dumplogs the baked registry; Sync Actions Into Bank can be called from a script. What you do: correct a gateway domain entry the Warning names; nothing else.
CrowdyCPP 0.73.0
- CrowdyCPP 0.73.0, and On Send Refused. The realtime library moves from 0.67.0 to 0.73.0; the SDK sends and receives as before. The platform has two access tier permissions for realtime messages,
use_text_chatanduse_direct_messages, and the new On Send Refused event (UCrowdyCppReplicationSubsystem::OnSendRefused) reports each send the server refused and the permission it needs. What you do: nothing; bind On Send Refused if the player should hear that a chat or an event went nowhere. See When the server refuses a send.
2026-10-16 (SDK v2.20.0)
The generated code changed for types with a timer, On Player Joined, On Player Left or Remove Members Who Leave. Until you run Generate Server Code on each of them, Server Compute marks it Out of date and refuses to deploy the project, and so does -op=deploy. Your logic.rs is kept. If CI deploys, commit the regenerated Server/ folder.
- A Server Object type can ask for more platform data. A new Platform Data category in the definition holds Reads World Data, moved there from Access, and eight new settings: Reads Players, Changes Players' App State, Changes the World, Reads Grids, Manages Grids, Grants Grid Permissions, Reads Players' Access and Manages Players' Access (
bReadsPlayers,bWritesPlayerState,bWritesWorld,bReadsGrids,bManagesGrids,bGrantsGridPermissions,bReadsAccess,bManagesAccess). Deploy asks the platform for a permission for each one ticked, so the server code can read players, change a player's app state, write voxels, read or manage grids, grant grid permissions, or read and manage players' access to the app. Before, Deploy could ask only forplayers.readandworld.read, and the platform refused the others. What you do: tick what your server code uses, and deploy. See Platform data. - Deploy warns about platform data the code uses without asking for it. Before it sends anything, the Deploy dialog lists each call into platform data whose setting is off, and
-op=changesand-op=deployadd them aswarnings. They do not stop a deploy. See Deploy. - A changed Platform Data setting is a change to deploy. Before, ticking Reads World Data alone left the type at No changes with Deploy unavailable. The Changed badge now also lists platform data and player access.
- A type players cannot reach is closed to them. A type whose functions are all Server Only, with no variable Visible to Players, no records, no members and neither player event, is now deployed with players kept out of it, and Server Compute says "closed to players". Acquiring one in a game is refused at once with a reason saying so; before, it failed after its first read. What you do: nothing; a game had nothing to read or call on such a type. Its next deploy lists it as changed (player access). See A type players cannot reach.
- A refusal by the platform says why. A call the platform refused used to read "This player may not call Feed on this Server Object". It now reads "The server refused the call to Feed: " followed by the platform's own words. A call refused because its type or app is switched off or paused is a different outcome, Paused; see the entry below. A call the platform could not place, for example with the app at its instance limit, adds the platform's words after "The server could not be reached". A Server Object waiting to be read gives the same reasons, and one that could not connect at all now says why, such as a paused app, instead of a word like "Rejected". Refusals by your server code's own rules (Callable By, Owner Only, Cooldown) read as before, and every
OutcomeandbRetryableis unchanged except for Paused. What you do: if your game comparesReasonwith the old text, compareOutcomeinstead. See Outcomes. - A Server Object type can set its own server limits. A collapsed Server Limits category in the definition holds Memory per Instance, Time per Call, Fuel per Call (Millions) and Calls Waiting (
MemoryMegabytes,CallTimeLimitMs,FuelPerCallMillions,MaxWaitingCalls). Before, every type ran with the platform's defaults: 64 MB, 2 seconds and 50 million fuel a call, and 1,024 waiting calls. Deploy sends only the ones you change, so a type that keeps them deploys as before, and a changed one is a change to deploy (the Changed badge lists server limits). A player's connection now waits up to 20 seconds for a call's answer instead of 10: the longest Time per Call plus time spent waiting behind other calls. What you do: nothing, unless a type needs longer calls, more memory, or less memory so more of them fit. See Every setting. - Server Compute can restart a type, and shows what runs. A running type has a Restart button beside its switch: its running Server Objects are saved, stopped and started again on the live version at their next call, without refusing any calls, and players watching one read it again. After a deploy that changed a type with Server Objects still running the previous version, the Deploy card offers Move now or Leave them. Each type's line adds how many of its Server Objects are running, a red line names any the platform will not place and why, the header shows the app's Server Objects and reserved memory against its limits ("3 of 16 Server Objects · 192 of 1,024 MB"), and the Budget paused badge gives the platform's reason. The command line adds
-op=restart -type=T -yesand-op=instances;-op=statusgives the counts and limits, and a deploy's result itssdkVersion, theckx-sdkthe platform compiled against. Before, the only way to move running Server Objects to a new deploy was to switch the type off and on, which refused calls meanwhile. This needs a platform with ck-exec 0.15 or later. What you do: nothing. See Restarting a type. - Call a Server Only function by hand, and Developers Only. A Server Only function's Details now has Call as Developer..., which calls it on a live Server Object as you, the app's developer, with its inputs and the answer shown; the command line adds
-op=call -type=T -fn=F -key=K -inputs=<json> -yes. Callable By gains Developers Only, for a function only a person should run: players and other server code are refused with "only developer tools may call this function". Before, a Server Only function could only be reached from other server code or a tool of your own. What you do: nothing; regenerate a type's server code after choosing Developers Only. See Call a function as the developer. - Get Players Online. A new node, Get Players Online (
UCrowdyServerObjectLibrary::GetPlayersOnline), gives how many players are in the app's world: the number the platform's realtime servers report, in which a player who leaves is still counted for a few minutes. The count is kept by the app's root Server Object, which Deploy now generates fromCSO_PlayersOnlinein the SDK's content and opens to players; before, the root kept nothing and players could not reach it.CSO_PlayersOnlineworks with every node that takes a definition, so On Changed follows the count. What you do: deploy once after updating the SDK: Server Compute offers a deploy until the app runs the new root, and until then Get Players Online returns false and logs why once. See How many players are online. - A project with no Server Object types can deploy, and the SDK's root is tracked. Deploy used to need at least one type, and a removed last type kept running. It now sends the Crowdy SDK's own root alone, which keeps Players Online and removes every type; the confirm says "This project has no Server Object types, so only the Crowdy SDK's root (Players Online) is deployed." When a deploy removes types, the confirm adds "Their saved state is kept and is used again if you deploy them again." The root is also compared like a type: once a deploy from this SDK has recorded it, a later SDK whose root code differs makes Server Compute offer a deploy (the root is not a row), and an app whose root an earlier SDK deployed is compared only after your next deploy. What you do: deploy when Server Compute offers it. See Deploy.
- Generate is stricter about
root,lib.rsand macros. Generate refuses the SDK's own root definition ("This is the Crowdy SDK's own root; its server code is built into every deploy, so there is nothing to generate.") and a project type namedroot, and tells you to choose another Type Name. It asks before rewriting alib.rsthat loads a module file of your own (a top-levelmod name;other thanlogicandtypes), with No as the default. When theimplblock inlogic.rsuses a macro such asmy_macro!(...), Generate and the Server Code tab no longer insert stubs, since the macro may define some functions; they copy them (Copy Stubs) instead. The question that offers stubs now says it writes into the file on disk and to save or close the file in another program first, and the tab says when another program saved the file without the functions Generate added. Out of date now names the first line that differs. What you do: declare your own modules at the top oflogic.rs(mod helpers;there loadssrc/logic/helpers.rs), never inlib.rs. See The Server Code tab. - Platform data hints are quieter and clearer. The warning for a call into platform data the type did not ask for now ignores code in Rust raw strings and in
#[cfg(test)]items. Reads Players shows ticked and greyed, with a tooltip naming why, when Members From is Crowdy Team, the records belong to Crowdy Teams, or a function players may call has a Required Feature; a Required Feature on a Developers Only or Server Only function no longer asks forplayers.read. What you do: nothing. See Platform data. - A Cooldown or Required Feature on a function players cannot call is flagged. A Developers Only or Server Only function with either shows a warning under it, and Deploy lists it ("... Cooldown has no effect, since only players' calls are limited." or "... Required Feature has no effect, since only players are checked."), because only players are limited and checked. What you do: clear the setting, or let players call the function. See Required Feature.
- Call as Developer reaches built-in functions, and big ids need quotes. A built-in member function such as Add Member (when Members From is This Object) has a play button on its row in the Members list, and
-op=call -fn=finds functions by name, built-ins included. In the command line's JSON inputs, a 64-bit integer above 9007199254740991 must be a string, for example"Player": "9007199254740993"; a larger unquoted number is refused. What you do: quote such ids in-inputs. See Call a function as the developer. - Server Compute compares limits the way the platform applies them. A limit that is out of range counts as the nearest value in range, and a limit written as a string is read as a number, so another tool's manifest no longer shows "server limits" as changed forever. After a deploy, the offer to move running Server Objects decides which ran the previous version from the Server Objects read as the deploy was sent (by their epoch), not from your computer's clock. What you do: nothing. See Overview.
- A call past its Time per Call restarts the Server Object. The platform answers the call
DeadlineExceeded(the SDK reports Timeout, see the entry below for the reasons) and restarts that Server Object from its last save, so changes since the last save are lost. The limit covers the function itself, not the time a call waits behind others. What you do: keep functions short, and set Save Every low on a type whose recent changes matter. See Server Limits. - Get Players Online waits once a minute when the app has no root. If the app has no Crowdy SDK root (deployed with an older SDK, or never deployed from Server Compute), the root now asks again once a minute, with the reason in the log, instead of every few seconds; once the app is deployed from Server Compute the count arrives without restarting the game. What you do: nothing. See How many players are online.
- Retries are spread out. Every Server Object retry and redial wait is now stretched by a random amount, up to half again: a failed read's first retry waits 1 to 1.5 seconds instead of exactly 1, a redial 1 to 1.5 seconds, then 2 to 3, up to 30 to 45, and a busy call's retries 0.25 to 0.375 seconds, then 0.5 to 0.75. Many players no longer retry in step after a server restart. What you do: nothing. See Waiting is not failing.
- A timeout says which side gave up. The outcome is still Timeout, and
Reasonnow tells the two apart. "AttackBoss ran past its Time per Call on the server: handler exceeded its deadline" means the server's own limit stopped the call (for a read, "The server took longer than this type's Time per Call to read this Server Object", or "watch"). "The server sent no answer in time; the connection may be slow or lost" means no answer reached the game within 20 seconds. What you do: nothing; showReasonas before. See Outcomes. - A call to a switched-off or paused server comes back Paused.
ECrowdyServerCallOutcomehas a new last value, Paused: the game's server, or the type, is switched off or paused (by its owner, or because the app ran out of funds). Calls refused this way used to come back Denied; they now come back Paused withbRetryabletrue and a reason such as "The server is switched off or paused, so it refused the call to Feed:village_beaconis switched off in app 123". A read or watch of such a server keeps trying, and its waiting reason reads "The server is switched off or paused, so this Server Object waits: " followed by the platform's words; a connection refused this way reads "The server is switched off or paused for<Type Name>: " followed by the error. What you do: a Blueprint Switch on the outcome gains a Paused pin to wire, and code that only checked for Denied should handle Paused too. See Outcomes. - The Game Session tells you the app is paused.
UCrowdyGameSession(category Crowdy SDK, Game Session) has Is App Paused and Get App Paused Reason, both pure, and the event On App Paused Changed (Paused,Reason). It turns on when signing in or refreshing the app token reports the app paused, or when the realtime server refuses the player's updates because the app is paused, and off when a later token answer says the app is active, when the realtime server relays to the player again (or their updates go through for 15 seconds), and on sign-out. The realtime log line for that refusal now names the codeAppPaused. What you do: show a "the world is paused" message instead of retrying. See When the app is paused. - The Game Session tells you the player's access ended, and Server Objects stop.
UCrowdyGameSessionhas the event On Access Ended (OnAccessEnded:Reason,BlockedUntil) and the pure Has Access Ended, Get Access Ended Reason and Get Access Blocked Until. It fires when the player's access to the app ends: suspended, revoked, expired, signed out by the platform, or the account blocked. Every Server Object then fails withYour access ended:followed by the reason, a call in flight or made later finishes with the new outcome Access Ended (ECrowdyServerCallOutcome::AccessEnded, not retryable), and nothing reconnects. A successful sign-in clears it; a Crowdy Server Object component and a typed node set to Find By Asset get their object again on their own, and code that callsAcquireitself calls it again. When the end time of a timed block is learned after access already ended, the event fires once more with it. What you do: show the reason and Blocked Until, return the player to the sign-in screen, and give a Blueprint Switch on the call outcome an Access Ended pin. See When the player's access ends and Outcomes. - A revoked, suspended or missing access is told so too. When a sign-in or the background token renewal is refused with
ACCESS_REVOKED,ACCESS_SUSPENDEDorACCESS_NOT_GRANTED, On Access Ended fires with the server's message and Blocked Until carries a suspension's end, then the SDK stops renewing the token until the next sign-in. Before, only the Server Object connection's close and a blocked account ended access, so a game with no Server Object saw nothing. The realtime connection now also asks for a new app token when its reconnect is refused for an invalid token, so a sanctioned player hears it within seconds and a player whose access was restored reconnects, where before the reconnect failed until the game restarted. What you do: nothing beyond handling On Access Ended. See When the app refuses the player's access. - A blocked account is told so at sign-in. Sign-in, register, the magic link, a social sign-in and a restored session fail with the server's message when the account is blocked, On Access Ended fires with Blocked Until (an ISO-8601 time, empty when the block lasts until it is lifted), and the app token is no longer rotated in the background until the player signs in again. What you do: show the message your sign-in screen already receives. See When the account is blocked.
- Ban and unban a channel or team member. New nodes Ban Channel Member, Unban Channel Member, Ban Team Member and Unban Team Member, and the C++ calls
BanChannelMember,UnbanChannelMemberonUCrowdyChannelsandBanTeamMember,UnbanTeamMemberonUCrowdyTeams. A ban takes an optionalReasonof at most 500 characters (a longer one fails at once with aPolicyViolationerror) and answers the member record with statusbanned; an unban answersWas Banned. A banned user's join, request to join or add fails with the new error codeBannedinECrowdyChannelErrorCodeandECrowdyTeamErrorCode, taken from the server's own code. What you do: nothing for existing code; aswitchon the error code that lists every value needs aBannedcase. See Ban a member for channels and Ban a member for teams. - On Error shows the error on Channels, Teams, Avatars and sign-in nodes. The latent nodes' On Success and On Error now share one set of data pins: the result, then
Error(with itsCode) andMessage; the sign-in nodes (Login, Register, Restore Session, Begin Magic Link Sign In, Begin Social Sign In) carryResultandMessage, and Record Player Consents carriesMessage. Before, Blueprint could not read the error on On Error. On On Success,ErrorandMessageare empty; on On Error the result is empty. A graph linked to the old pins keeps its links. In C++ the multicast pin types are renamed (FChannelAsyncResult,FTeamAsyncResult,FAvatarAsyncResultand the rest of that family, replacing the...AsyncOnSuccessones, andFAuthAsyncResult,FRecordPlayerConsentsAsyncResultfor sign-in); a C++ handler bound withAddDynamicto one of these pins takes the added parameters. The per-callFOnChannel...,FOnTeam...andFOnAvatar...delegates are unchanged. What you do: wireErrororMessagewhere a graph needs the failure. See the nodes on Channels, Teams and Avatars. - A Server Object gateway must be on the Game API's own domains. The SDK now checks that the gateway the Game API names is on the Game API's own domains, as the C++ library's own connect does. A new Project Settings entry, Crowdy SDK, Server Objects, Allowed Exec Gateway Domains (
AllowedExecGatewayDomains), lists extra domains for a server you host yourself. A gateway outside both is refused for good: every Server Object fails with "Could not open the connection to the server: the Game API named a gateway outside its own domains (...); if you host the server yourself, add its domain to Allowed Exec Gateway Domains in Project Settings", and it is not retried until you acquire an object again. What you do: nothing on the platform's servers; add your domain if you host the server yourself. See Server Object gateway domains. - Players who left while a Server Object was stopped are cleaned up. When a Server Object restarts, the platform does not tell it about players whose connection closed while it was stopped. The generated code now keeps the connection counts it restored as unconfirmed: right after the restart it tells watching players to read again, and any call from a player confirms them. A player not heard from within two minutes is treated as having left (Remove Members Who Leave removes them,
on_player_leftruns); a player who reconnects in that time just continues, andon_player_joineddoes not run again. The timer namecrowdy_sessionsis now reserved, and a definition with a timer of that name is refused. What you do: run Generate Server Code on each type with On Player Joined, On Player Left or Remove Members Who Leave, then deploy; until you do, Server Compute marks it Out of date and refuses to deploy the project. Rename a timer calledCrowdySessions. See Players who left while it was stopped. - A changed timer keeps its old schedule if arming fails. When a deploy changes a timer's Runs, running Server Objects arm the new schedule without stopping the timer first, so if arming fails the timer keeps its old schedule and the server log says "timer EndMatch keeps any old schedule: the changed one could not be armed: ...". A timer you did not change keeps its clock. What you do: run Generate Server Code on each type with a timer, then deploy; until you do, Server Compute marks it Out of date and refuses to deploy the project. See Timers and events.
- The limit on running Server Objects is documented. An app with no funds can run 16 Server Objects at once, within 1 GB; a funded app 16,384, within 64 GB. Past it nothing new starts and calls come back Unavailable. For many players, prefer one shared object that holds a record per player (or per area, match or team) over one object per player. What you do: nothing. See Large groups.
- Trace lines and warnings are tidier. With
crowdy.exec.traceon, the Players Online root prints its missing Instance Id as-(exec: root/- read ...). Get Players Online's "has no count yet" warning prints once for each reason in each game instance, so once per Play In Editor client. What you do: nothing. - A voxel update with more than 1,024 bytes of state is refused when you send it. The send returns false with "the voxel state is N octets, over the 1024 a voxel update carries", instead of reporting the update queued and then dropping it with a warning. What you do: keep a voxel's state at 1,024 bytes or less.
- Records in Blueprint. A Server Object that keeps a record per entity gains Holds Records, Num Records, Watch Record and Stop Watching Record, and the event On Records Updated. Watch Record takes a Record Id and an event (
FCrowdyServerRecordEvent: Object, Record Id, Changed) that runs after each change of that record and when it is removed (Find Record is then false), and once at once when the record is already current; records dropped because the object Failed are not reported, On Status Changed is. On Records Updated (RecordsUpdated, anFCrowdyServerRecordsUpdated) is On Records Changed with each changed record's Changed Variables, which is empty when only its owner or team changed. On Records Changed is unchanged, and C++ already hadWatchRecordandOnRecordsChangedwith the changed fields. What you do: nothing. See From Blueprint. - Variables added in the asset are read and followed by the typed nodes. For a definition whose variables are added in the asset (the List form), Get, Get Server State and On Changed read and follow them with the same pins as for a struct; this is now stated on the pages. What you do: nothing. See Read and follow its variables.
- Get Definition, and a widget without a component. Get Definition is a new pure node on a Server Object, giving the definition asset it was made from, to pass to nodes that take one. A widget needs no component for a Server Object: Get Server Object with the widget as Owner, or a typed node with Find By Asset, already works, and the page now says so. What you do: nothing. See Without the component.
- Ping Server Objects. A new latent node (
UCrowdyServerPingAction; in C++UCrowdyServerObjectSubsystem::Ping) asks the platform for an answer over the open Server Object connection: On Success gives Round Trip Ms, On Failed gives Reason. It never opens a connection, so with none open it fails at once with "No Server Object connection is open". The platform answers at most 60 pings every 10 seconds on a connection and drops the rest without a word, so the SDK refuses more itself with "Too many pings: the platform answers at most 60 every 10 seconds". What you do: nothing; ping on a timer of a second or more. See Ping the platform. - A Call node can give up sooner. The Call Server Function nodes, typed and generic, have an advanced Timeout input in seconds, and C++
UCrowdyServerObject::Calla trailingfloat TimeoutSeconds = 0.f. It can only shorten the wait: 0 leaves the project's Server Object Call Timeout. When it runs out the call fails with Timeout, not retryable, andReasonreads "No answer within 5 seconds; FeedBeacon may still have run on the server"; an answer that arrives later is not reported. What you do: nothing. See Give up sooner. - Server Object connection timeouts are settings. The Connection tab of Crowdy Studio's Project page has a Server Objects heading with Call Timeout (
ServerObjectCallTimeoutSeconds, 11 to 120 seconds, default 20, above the longest Time per Call) and Open Timeout (ServerObjectOpenTimeoutSeconds, 1 to 60 seconds, default 10). They are written toDefaultGame.ini, shown read-only under Project Settings, Crowdy SDK, Developer, Server Objects, and apply from the next Play and to the editor's developer connections. A dropped connection is always reopened. Before, the call wait was fixed at 20 seconds, as the entries above say. What you do: nothing unless a game needs a different wait. See Server Object timeouts. - Server Objects can send messages. A definition's new Server Messages section lists messages (
Messages, an array ofFCrowdyServerMessage: Name, Payload as variables added in the asset or a struct, an advanced Server Name and Players Can Listen). The type's server code sends one withmessages::send_<name>(&payload), and everyone listening hears it once: each player who listens to that Server Object, and each Server Object type that listens to it. Before, a Server Object could tell players only through its variables, which keep the last value but not that something happened. A message is an event: it is not kept and not replayed. It is published only when the function that sent it succeeds, after that function's changes to the variables and in the order it sent them, and a function that refuses sends nothing. Players Can Listen is on by default; off, only a type that Listens To this one hears the message, it does not open the type to players, and a player trying to watch it gets a warning and nothing. Two messages with one Name, or one topic, are refused when you save. When a type has a message players can listen to and Readable By is not Every Player, or any variable is Owner Only or Team Only, the editor warns "Every player who can connect to this type hears its messages, whoever may read its variables. Keep private data out of messages.", since a message reaches every player who can connect, whoever may read the variables. What you do: nothing for an existing type. To announce an event, add a message, send it fromlogic.rs, then generate and deploy. See Server messages and Send a message. - Hear a message from Blueprint or C++. Blueprint gets a typed node for each message players can listen to, On BossDefeated Received (CSO_Boss): Bind, Then and Received execution pins and an output pin for each value in the payload, with Target or Find By Asset as on On Changed. Watch Message and Stop Watching Message (
K2_WatchMessage,K2_UnwatchMessage; the event is anFCrowdyServerMessageEventwith Object, Message and Payload, read with Get Server Value) work by name. C++ hasUCrowdyServerObject::WatchMessageandUnwatchMessagewith anFOnCrowdyServerMessagelistener. Nothing runs for a message sent before the game listened. A message that arrives while the object is reading its values is held, up to 32, and announced once the values are current; a message sent while the connection is closed is lost, not replayed; and a listen the platform refuses is asked again after a wait that grows from 1 second to 30, with one warning giving the platform's reason. In a record type, a message can arrive before the record change it follows, since records go out in batches. A connection allows 256 subscriptions, and each subscribe counts against the 120 calls every 10 seconds. What you do: nothing. See Hear its messages, from Blueprint, from C++ and A message never arrives. - A Server Object type can listen to another type's messages. A definition's new Listen To list, beside Can Call, names types whose messages the type's server code hears; each is also callable, so Listen To joins the type's
calls. Inlogic.rs,listen::<type>::<message>(ctx, key)starts listening to one Server Object by its Instance Id andlisten::<type>::stop_<message>(ctx, key)stops, and the generator adds a handler toFunctionsfor each message of a listened type,on_<type>_<message>(&mut self, ctx, key, payload), with nopayloadfor a message with no values. A listen lasts, is renewed when the Server Object starts again, and takes effect only when the function that made it succeeds; if the platform refuses it, the function fails withcould not listen to boss.boss_defeated:and the reason. A handler works like any other: its changes reach the watchers and an error puts the state back. A handler whose name is a function's or a timer's is refused when you generate. What you do: nothing for an existing type; regenerate and deploy a type after adding Listen To, and write the handlers Generate offers. See Listen to a message. - A Server Object type can be a Server Function Library. A definition's new Kind (
Kind, anECrowdyServerObjectKind: Server Object or Server Function Library) makes a type that has functions and nothing else, like a Blueprint Function Library. It saves nothing between calls, has no Instance Id, and is answered by several instances at once; the platform calls it a spoke. Choosing it hides, and ignores everywhere, the variables, Visible to Players, Readable By, Only One Instance, Holds, members, areas, timers, player events, messages, Listen To, Save Every and Stop When Idle For; they stay on the asset and come back when the Kind does. A new Scaling category sets Min Instances, Max Instances and Parallel Calls per Instance (MinInstances,MaxInstances,ParallelCallsPerInstance; the platform'sreplicas,max_replicasandconcurrency, 1 to 64, 1 to 64 and 1 to 256, defaults 1, 1 and 16): the server adds an instance, up to Max Instances, once calls have waited 2 seconds, and removes one after about 30 seconds nearly idle. Each instance counts toward the app's limit on running Server Objects (16 without funds), and the first call after a library has been idle can take over a second. Saving refuses a function that is Members or Leader or has a Cooldown, a Max Instances below Min Instances, and a Server Object that lists a library under Listen To; a library needs no State struct. Its generated crate implementsSpoke, withimpl Functions for Libraryinlogic.rs. Server Compute's row says "Server Function Library" and "1 to 4 instances" in place of the variable count, the Changed badge and-op=changeslistkindandscaling, and a deploy that changes a type's kind says so and that its saved data is kept. Before, every type was a Server Object. What you do: nothing for an existing type. To share logic that keeps nothing, make a library, generate, writelogic.rsand deploy. See Make a Server Function Library, A Server Function Library and Loot for the Hollow Giant. - Call a library from Blueprint or C++. A library offers a Call node for each function, under Server Function Libraries in the palette, that needs no Target and no Instance Id, and no Get, On Changed or On Received. C++ has
UCrowdyServerObjectLibrary::CallLibraryFunction(WorldContext, Definition, Function, Inputs, OnDone, TimeoutSeconds), with anFInstancedStructor anFInstancedPropertyBagas Inputs. The game instance'sUCrowdyServerObjectSubsystemholds the library until it shuts down, so there is nothing to release, andOnDoneruns exactly once: a null definition, a definition that is not a library or an unknown function completes at once asBadRequest, and no world asUnavailable, each with a plainReason. What you do: nothing. See Call a Server Function Library, from Blueprint and from C++. - A type can call any type. A definition's new Can Call Any Type check-box (
bCanCallAnyType), in Can Call, lets its server code call and listen to every type in the app, including types added later; Deploy writes"calls": ["*"]in place of the names. Can Call and Listen To still decide which types get typed helpers, and with the box tickedlogic.rsalso getscalls::any(ctx, node_type, key, method, payload), which calls a type by name with an encoded payload and returns the undecoded reply. A Server Object or library whose Can Call lists a library gets a typed helper with no key. A call to a type the app does not run fails with "app N has no node type". What you do: nothing; tick it only for a type that calls types it cannot name when you write the code. See Call any type. - CrowdyCPP 0.67.0. The SDK's
devbranch now builds on CrowdyCPP 0.67.0 (0.57.0 before; 0.61.0 added the platform calls above, 0.62.0 and 0.63.0 the input log's missing records, request cancelling, channel and team bans and the ck-exec close for a player whose access ended, 0.64.0 to 0.66.0 the Game Kit's client code and a randomized Server Object reconnect wait, 0.67.0 the rename that sends both halves of a player's name). The Game Kit has no Unreal nodes yet. Nothing in the SDK's API changed. What you do: nothing. - A failed connection made for the root or a library names no type. Its
GetFailureReason()reads "Could not open the connection to the server: ..." (or "The server is switched off or paused: ..."), since that try did not tell the platform a type; before, it named the root or the library as if the problem were theirs. What you do: nothing. See Waiting is not failing. - Account settings in the game. New nodes under Crowdy SDK, Account, and the same calls on
UCrowdyAuthentication: Request Password Reset and Resend Confirmation Email (both work signed out and answer the same whether or not the address has an account), Change Password, Add Password (for an account made by magic link or social sign-in), Sign Out Everywhere and Delete Account (both then sign out here). A failure carries anFCrowdyAccountErrorwhoseCodenames Wrong Password, No Password, Password Already Set, Not Signed In and Rate Limited. What you do: add what your settings and sign-in screens need; a game in a store that requires it offers Delete Account behind its own confirm dialog. See Account settings. - Sign-out ends the session on the server.
Logouton the Crowdy SDK Subsystem now tells the server to end the session, and every gameplay token made from it, before it forgets the token; before, the server session stayed valid until it expired. It does not wait for the answer, so signing out offline still works. Crowdy Studio's Sign Out does the same for an account sign-in. What you do: nothing. See Session persistence and sign-out. - The player's own access, and free access. Get My Access reads the player's access to this app (
FCrowdyAppAccess: None, Active, Suspended, Expired or Revoked, the tier, when it expires or a suspension ends, and whether it came from the free tier), also while the app refuses them. Claim Free Access claims the app's free access for a player whose sign-in was refused withACCESS_NOT_GRANTEDand finishes that sign-in; it never lifts a revoke. What you do: show a Get Free Access button where your sign-in screen shows that refusal. See When the player has no access yet. - Player names. A new Crowdy Players subsystem (
UCrowdyPlayers) reads players' public names,FCrowdyPlayerProfile(UserID,Gamertag,Disambiguation), one with Get Player Name or up to 100 with Get Player Names, and renames the signed-in player with Set Player Name, refused with Name Taken when another player has the pair. What you do: use them for nametags, rosters and a Choose Your Name step. See Player Names. - Crowdy Studio has an Account page. Under Home, for the account Studio is signed in with: your name, changing or adding your password, and signing out everywhere. What you do: nothing. See Your account.
- Server Compute's Overview is a list and a detail. The types are listed on the left, with Search types and the filters All, To deploy and Problems; the selected type's details and controls are on the right. Deploy moved to the page's title row, with the number of types it changes, so it no longer scrolls away under a long list; the status line and Deploy starter pack sit in a strip under the tabs. What you do: nothing. See Overview.
2026-10-12 (SDK v2.19.0)
-
Signing in now asks the player to agree to the terms and confirm their age. Since the server's v2.35.0 release, no gameplay token is issued until the player has agreed to the current documents and attested they are at least 18, or the age of majority where they live if that is higher. The SDK handles it for you: a sign-in, sign-up, session restore or token refresh that is refused for this reason is held, not failed, and a prompt in the Crowded Kingdoms colors opens over the game viewport with the five documents as links, two check boxes, Accept and Decline. Accept records the agreement and the sign-in the player started completes, with its usual events (
OnLogin,OnRegister,OnSessionRestored,OnAppTokenRefreshed). Decline fails it withSign-in needs the terms and the age confirmation.The prompt also appears in the middle of a session when a document gets a new version. What you do: nothing if the built-in prompt suits your game; make sure a sign-in screen does not time out a sign-in that is waiting for the player. A dedicated server or any process with no window cannot show the prompt, so sign such an account in once with a window. See Let the player agree to the terms and, for the server side, Terms and age of majority. -
You can turn the prompt off and draw your own. The developer setting Show Consent Prompt (
bShowConsentPrompt, on by default) switches the built-in prompt off. New onUCrowdyAuthentication: the eventsOnLegalAcceptanceRequiredandOnLegalAcceptanceEnded(FOnLegalAcceptanceRequired,FOnLegalAcceptanceEnded, no parameters), the functionsRecordPlayerConsents,DeclineLegalAcceptance,IsLegalAcceptancePendingand the staticGetLegalDocuments(an array of the newFCrowdyLegalDocument, withTitleandUrl), and the Blueprint node Record Player Consents (UCrowdyAuth_RecordPlayerConsents). Bind the required event, show the documents with two check boxes, and callRecordPlayerConsents(true, true, ...)only after the player ticked both; close your UI onOnLegalAcceptanceEnded. Never record consent without the player's own accept. See Draw your own prompt. -
Crowdy Studio shows the same prompt. When the account you signed in to Studio with has not yet agreed to the current terms, Studio opens it in a window titled Crowdy Studio: terms and age before it carries on; declining stops there with a message. See Agreeing to the terms.
-
The SDK now vendors CrowdyCPP 0.57.0, the native client its transport is built on (the
CrowdyCppBridgemodule), which carries the new consent call. What you do: nothing; you never include it directly. See Installation. -
CrowdyMass is part of the SDK. There is no CrowdyMass plugin to enable: the Mass modules ship inside CrowdySDK, along with the engine's MassGameplay and MassCrowd plugins they need. A map opts in by setting its map profile's Backend Class to Crowdy Mass Rendering Backend; the actor pool stays the default. The module names are unchanged, so a
Build.csthat lists"CrowdyMass"keeps working, and the shipped assets now live under/CrowdySDK/Mass/. The starting-point profile/CrowdySDK/Mass/Data/DA_CrowdyMassDefaultProfilehas Enable Networking on, and its folder is always cooked. What you do: remove the CrowdyMass entry from your.uproject's plugin list, and from thePluginslist of any.upluginof your own that names it, and delete the separate plugin folder if you have one. A C++ project that still names it stops at build time with an error naming the file that references it. If the editor offers to disable one of your own plugins because CrowdyMass is missing, answer No and edit that file instead. See Draw a crowd with Mass. -
The Mass subsystems exist only on a Mass map.
UCrowdyMassEntitySubsystemandUCrowdyMassServerRecordsSubsystemare now created only in Play In Editor and game worlds whose map profile has Backend Class set to Crowdy Mass Rendering Backend. On an actor pool map the Crowdy Mass nodes and C++ calls that need them do nothing, quietly: a Crowdy Mass Area Spawner spawns nothing and warns once, Set Crowdy Mass Hit Zone Table warns once and hits use the default zone, and Receive Crowdy Event on Entity and Receive Server Values Changed on Entity log once and receive nothing. Play Crowdy Mass Action and the action lookup still work there, because they read the actor's class. What you do: in C++ that needs the subsystem, gate your own world subsystem inShouldCreateSubsystemso it exists only on maps that draw crowd rows (see Add your own crowd data), and check the result for null where you get the subsystem. See Fix a crowd that does not look right. -
The anim set's Bank is used at run time. Authoring Bank on the Crowdy Mass Anim Set is now Bank. A config the SDK builds from the class renders through it, so a class whose anim set names a Bank animates with nothing else authored. What you do: open and resave your anim sets so the rename persists. See Set up the animation set.
-
A derived crowd row stands where the actor's mesh stands. A config the SDK builds from the class copies the mesh component's location and rotation into the mesh entry's Local Transform. Before, a derived row of a character floated by its mesh's drop and faced a quarter turn away. What you do: nothing for a derived class; an authored config sets its own Local Transform. If you gave a near tier shell's
Meshor its Entity Transform Offset a drop or a turn to make up for the old placement, remove it, or the promoted actor now sinks and faces backwards. A mesh nested under other components is placed by the whole chain of components up to the actor's root, and a mesh that is the actor's root gets no placement of its own.crowdy.mass.derivenow prints where the derived config places the mesh ("placed at ... turned ..."), so you can check it. See Make a character draw as a crowd row. -
The SDK turns off the engine's animation pass for you. The CrowdyMass module switches off
MassConsumeInstancedSkinnedMeshAnimationProcessorwhen the game starts, because the SDK hands each row's animation to the renderer itself. Before, a project had to addConfig/DefaultMass.inito do it. What you do: nothing; you can delete that entry. IfLogCrowdyMasswarns that more than one processor is filling a component's animation batch, something turned the engine's pass back on, such as ticking it in Project Settings, Mass, or aDefaultMass.inithat sets it to True; remove that. This holds in every project with CrowdySDK, including one that never draws crowd rows: a Mass crowd of your own that uses skinned meshes then animates through the SDK's pass, which covers moving instances in visible chunks; a crowd of your own that stands still gets no animation updates. The MetaHuman crowd plugin's own pass, if you use that plugin, is left alone; turn it off under[/Script/MetaHumanCrowd.MetaHumanMassSimpleAnimationProcessor]inDefaultMass.iniif you want. See The engine's animation pass. -
A Blueprint receive node keeps the action. Before, binding Receive Crowdy Event on Entity to an event marked an action replaced both the event's body and the automatic action write on a crowd row, so the row stopped animating, and Blueprint had no node to start it. Now a bound node still replaces the body on a row, but the declared action is still written, so the row plays it. A C++ handler registered with
RegisterEventHandlerstill replaces both, and writes the action itself withCrowdyMassWriteAction. What you do: nothing; if you worked around the old limit, you can drop the workaround. See Receive its events, from Blueprint. -
Typed C++ calls on a crowd row.
CROWDY_MASS_SEND_EVENT(Target, Class, Event, Args...), inEvents/CrowdyMassEventLibrary.h, calls aCROWDY_EVENTdeclared onClassfor the rowTargetnames. The arguments are checked against the event's parameters when your code compiles.UCrowdyMassServerValuesLibrary::GetServerValueAs<T>(Target, Name, T& Out)reads a row's server variable into a C++ value of exactly that type, and returns false and resetsOutwhen the value has not arrived, the variable is missing or the type differs.FindServerValuePropertyandPropertyHolds<T>come with it. What you do: nothing;SendEventToMassTarget(Target, UFunction*, const void*)andGetServerValueIntostill work. See Call an event on it and Read its server values. -
An actor pool map pays less for Crowdy Mass. On a map whose profile does not select the Mass backend, the two Crowdy Mass processors that ran every frame now sit out that world, and a Crowdy Mass Near Tier component does not tick there until something binds it. Destroyed entities of your own Mass skinned crowd on such a map now have their animation tracks released; before, they stayed allocated for the session. What you do: nothing. For a world subsystem of your own that needs the Mass entity subsystem, return
Super::ShouldCreateSubsystem(Outer) && GetDefault<UCrowdyMassEntitySubsystem>()->ShouldCreateSubsystem(Outer)fromShouldCreateSubsystem. It then exists only on maps that draw crowd rows, and an actor pool map's log stays free of the engine's "Failed to initialize subsystem dependency" line. See Add your own crowd data. -
The area spawner's list of ids is named in the editor.
ACrowdyMassAreaSpawner::GetSpawnedUUIDsnow shows as Get Spawned UUIDs. See Turn Mass on for a map. -
The Game Model API is removed. Gone: containers and attributes (the
CrowdyModelandCrowdyContainermetadata), model functions, Crowdy Effects (the assets, the effect graph editor and the Apply Effect nodes), sessions on the Game Model subsystem, automations, signals, pre-seeding, the Game Kit, container manifests, andUCrowdyGameModelSubsystem; thecrowdy.gamemodel.*console variables and theLogCrowdyGameModellog category; and theDefaultModelNotificationCarriersetting, which an old saved config now has ignored. A Blueprint variable's Crowdy Replication dropdown offers only None and Replicated; Server Owned is gone. Server Objects replace the API; see Move from Game Models. -
Crowdy Studio has an Access page and no Game Model page. Access, in the Configure group under Project, holds the features and tier-feature grants that were on the Game Model page: a searchable feature list, and for the selected feature every access tier with a Grant or Revoke button. It loads its lists the first time you open it for the selected app, and has a Refresh button. Reading and changing features now works: the old page sent them with the wrong credential, and the server refused them. See Crowdy Studio Overview.
-
A Server Object function can require a feature. Required Feature, beside Callable By, names a feature key the caller's access tier must grant; other players are refused with
Deniedand a reason such as "needs the vip feature". Define features and grant them on the Access page. A type that uses it asks for theplayers.readscope when deployed. See Required Feature. -
Server Object timers can run on a schedule. A timer's Runs has a third choice, On Schedule, with a Cron expression (five fields, six with seconds first, or
@hourly,@daily,@weekly,@monthly,@yearly) and a UTC Offset in minutes. The generated server code arms the next run before your function is called, so an error in your function does not end the schedule. Generate again after you add one. See Access, members and timers. -
A Server Object component in Player's Team mode asks for the player's teams again every 5 seconds while it waits, instead of waiting for the next sign-in when one request failed.
-
A Type Name change offers the right folder when two crates name one definition. It offers the crate Generate wrote last, not the first by name.
-
A changed timer reaches running Server Objects. The generated server code saves each timer's Runs; after a deploy that changes one, a pending timer is armed again under the new Runs, a new Start Automatically timer starts, and a removed timer stops. A schedule changed to Every no longer stops in instances that were running. Generate again; the first deploy of the new code re-arms nothing. See Access, members and timers.
-
A Server Object waiting to connect says why. When the connection to the server cannot be opened,
GetFailureReason()on every Server Object still in Connecting reads "Could not open the connection to the server for<Type Name>: ..." until it opens. The next try uses another type's Server Object, so one type that is not deployed or is switched off no longer holds up the others. See Troubleshooting. -
Seamless travel pauses the grace period. A Server Object whose owners were destroyed by a seamless travel is kept until the travel ends, as it already was across a map load.
-
Two types cannot share a Logic File. Deploy refuses a project where two Server Object types use the same Logic File, naming both: each type builds the file against its own State.
-
Generate and Remove from project leave server code the SDK did not generate. A
Server/<Type Name>/folder holding a crate you wrote yourself (itsCargo.tomlrecords no definition and itslib.rsandtypes.rslack the generator's header) is no longer overwritten by Generate Server Code or deleted by Remove from project...; Generate refuses with "... holds server code the Crowdy SDK did not generate, so Generate would overwrite it. Choose another Type Name, or move that folder.", and the type shows Problem on the Server Compute page. See Generate it. -
Deploy and Deploy starter pack say what they take offline. Deploy's confirmation names the live types the project does not have right after its first sentence, worded "It removes tip_jar, which this project does not have; their Server Objects stop.", which includes server code deployed some other way. Deploy starter pack's confirmation now names every type the live version runs. See Deploy.
-
CrowdyMass no longer carries Game Model values.
GetModelValue,GetModelTargetState,GetEffectTargetand the model fields are removed. Crowd rows read Server Object values from a record per entity instead; see the next entries. -
A Server Object can keep a record per entity. Holds, a new setting on the definition asset, can be A Record per Entity: the variables are kept once for each entity of a crowd, keyed by the entity's id, in one Server Object for an area, a match or a team. Each variable is Server Only, Everyone, Owner Only or Team Only; Everyone values reach players in batches every Send Changes Every (250 ms unless you change it), and Owner Only and Team Only values are fetched by the owner or the team at most once a second. Max Records (2048, at most 4096) bounds an object. The server code gets
get,get_mut,insert,remove,set_owner,set_team,ids,len,owner,team,owned_byandarea_ofonRecords. Team From says where a team comes from: This Object's Records, the default, or Crowdy Team, whose ids the server checks through the platform (deploying asks forplayers.read). In Blueprint, Find Record, Get Record Ids and On Records Changed; in C++,GetRecord,GetRecordIds,OnRecordsChangedandWatchRecordonUCrowdyServerObject. A record type needs Readable By set to Every Player and Members From set to None, and reserves the function namechangesand the timer namecrowdy_flush. What you do: nothing unless you adopt it. Generate and deploy a type again after you change its Holds. See Keep a record per entity and Raiders on the north road. -
The Crowdy Server Object component has a This Actor's Area mode. The component joins the Server Object of the area the actor stands in, by the definition's Area Size and
ECrowdyServerObjectInstanceMode::ThisActorsArea, and moves to the next as the actor crosses into it. Area Size and Areas Followed Around a Player are new settings of the definition. See Pick the object and Get a Server Object. -
A definition can read world data. Reads World Data, in Access, lets the type's server code ask the world which actors are near or in a chunk. Deploy asks the platform for the
world.readpermission for such a type. It is off by default. See Reads World Data. -
Crowd rows read Server Object values. A row of a crowd drawn by CrowdyMass reads the record of its entity with Get Server Value on an entity target, hears changes with Receive Server Values Changed on Entity, and a promoted actor does the same through its Crowdy Mass Near Tier component. The entity's actor class carries a Crowdy Server Object component with a type that holds records. See Crowd rows.
-
Channels no longer drop payloads that begin
cmc:,csg:orgms|. The SDK used to filter those out before your handler saw them, because Game Model notifications used them. They now arrive like any other payload. See Channels. -
The CrowdyState layout's error for a container property names a Server Object. The text now reads "use a CrowdyEvent RPC or a Server Object" where it ended "or a Game Model container". See Crowdy State.
-
Breaking for C++:
ICrowdyEntitySubscriber::ApplyModelChangesis removed, so a class that implements the interface and marks itoverridestops compiling.UCrowdyEntitySubsystem::RegisterSubParticipant,RegisterSubParticipantAsandDeriveSubParticipantIDare removed.FCrowdyModelIdentityis removed;FCrowdyEntityIdentityhasStableNetIDFromActorPathandNetIDFromBindingKey.FCrowdyEntityRecord::AnchorNetIDis removed.ICrowdyBindingKeyProviderstays, with its header now underReplication/Components/.
-
Some leftovers give no warning. A C++ property or Blueprint variable that still carries
CrowdyModelorCrowdyContainermetadata compiles and does nothing. A Blueprint that still has Apply Effect nodes loses them when it loads, with no error. Crowdy Effect, container manifest and Game Kit assets no longer load. -
What you do:
- Before you upgrade, open and compile your project on the release that adds Server Objects. Its warnings list every use of Game Models, including each Apply Effect node, and you can move each system to a Server Object while the nodes are still there to read.
- Search your C++ for
CrowdyModel(andCrowdyContainer) and clean up what is left. - Fix the C++ that no longer compiles, from the list above.
- Delete the effect, container manifest and Game Kit assets, once you have noted what they described.
- Check any code that relied on channel payloads starting
cmc:,csg:orgms|being dropped. - Do a clean cook, not an iterative one: the baked registry's layout changed.
- See Move from Game Models.
2026-10-07 (SDK v2.18.0)
-
The Game Model API is deprecated and does nothing; a later release removes it. It covers Game Model containers and attributes (the
CrowdyModelandCrowdyContainermarkers and container tags), model functions and Invoke Model Function, Crowdy Effects and the Apply Effect nodes, invoke policies, sessions and turns, collections, container manifests and pre-seeding, the Game Kits, automations and signals, Listen for Model Changes,UCrowdyGameModelSubsystemand the librariesUCrowdyModel,UCrowdyGameModel,UCrowdyModelValueandUCrowdyEffects. Every call now fails at once, once: an async node fires its Failed pin immediately with "Game Models are deprecated and no longer available; use Server Compute." (codeGAME_MODEL_DEPRECATED), Listen for Model Changes never fires, nothing is sent to a server, waited for or retried, reads return their defaults (Get Local User Id still answers), and the SDK logs the deprecation once per run. In C++ the classes and functions are marked deprecated, so the compiler warns (C4996 on MSVC) wherever your code names one; they are warnings only, and nothing fails unless your project treats warnings as errors. In Blueprint the Game Model library functions and the subsystem's events show the engine's deprecation warning, and each Blueprint that uses Game Models also gets one compiler warning listing its uses (the first 12, then how many more),[CrowdySDK] This Blueprint uses Game Models, which are deprecated and no longer available; move it to a Server Object (Server Compute). Uses: ..., when it compiles, loads or cooks; assets still load, save and cook. The deprecated library functions no longer appear in the Blueprint context menu, and the editor silently drops a deprecated call node when you paste or duplicate it unless Expose Deprecated Functions is on in Editor Preferences; the async Game Model nodes and the Apply Effect nodes are still listed, and the per-Blueprint warning flags them. A Failed pin wired straight back into the same node with no Delay used to retry once per frame; it now calls itself straight away, because the failure is immediate, and can hang or crash the game. Get Local User Id is replaced by Get User ID on the Crowdy Game Session. In Crowdy Studio the Game Model page shows a deprecation banner and no longer loads its lists, schema sync, pre-seeding, purge and Game Kit deploy refuse with the same message, and the tier-features panel on that page keeps working; in a Crowdy Effect asset's editor the sync status in the toolbar reads "Deprecated". In CrowdyMass, Game Model values no longer land on crowd entities: Get Crowdy Mass Model Value answers "no fragment" and Bind Entity Model Containers binds nothing and returns 0; landing Server Object values on crowd entities is planned for a later release. A subsystem tagged as a Game Model container is no longer registered as an entity participant by the Game Model system. What you do: take this release and read the warnings, which name the uses; author each system as a Server Object type and switch the call sites; add a Delay to, or remove, any Failed loop; and seed any data that must persist with a one-off script, since nothing moves automatically. Server Objects need ck-exec on your app's environment, so a project can finish moving once its environment has it. See Move from Game Models. -
Server Objects: server-owned state on ck-exec, from C++. A new module,
CrowdyExec, lets a game use server code on ck-exec without meeting the platform's vocabulary. AUCrowdyServerObjectDefinitiondata asset describes one type: the struct the server keeps, the fields players may watch, and the Server Functions they may call.UCrowdyServerObjectSubsystem::Acquirehands out a sharedUCrowdyServerObjectper definition and Instance Id, held for its owners and given back 10 seconds after the last one is gone;WatchValuesreports each server change once, naming every field it touched;Callsends a params struct and completes once with anFCrowdyServerCallResultwhoseECrowdyServerCallOutcomesays what happened, after the SDK has retried a busy server; a call the server may already have run is never repeated for you. Log categoryLogCrowdyExec. What you do: nothing unless you adopt it. To adopt it, addCrowdyExecto your module'sBuild.cs, and deploy the type's server code to your app; C++ only for now, with Blueprint support to come. See Server Logic. -
Generate Server Code: a Server Object type's server code, written from its definition asset. Right-click a
UCrowdyServerObjectDefinitionasset in the Content Browser and choose Generate Server Code. The editor checks the definition, then writes a Rust crate for ck-exec underServer/<Type Name>/in your project:Cargo.toml,src/types.rs(your structs and enums, with their Unreal defaults) andsrc/lib.rs(the glue that answers and publishes the watched values, applies Owner Only and Who Can Call, refuses withunknown_method,deniedorbad_params, and saves and loads the state) are rewritten every time;src/logic.rs, where you implement theFunctionstrait with one method per Server Function, is written once and never overwritten. A Server Function that returns an error leaves the state as it was and publishes nothing. Before a regeneration drops a struct, field or enum value, or changes a field's type, the editor lists each one and asks whether to go on. The definition asset's editor also has a Server Code tab: the path of the Server Functions file, an external-editor button, and an editor for the file's code with Save and Revert (Revert asks before it discards edits; unsaved edits survive a refresh), while its toolbar has a Generate button (the same as the right-click command, with the same warning). A Code Source choice on the tab, Generated or My own file, says where the type's Server Functions come from: with My own file you choose your own.rsfile, for example one shared in your repo, Generate Server Code writes onlyCargo.toml,lib.rsandtypes.rsand never creates or overwrites your file, and Server Compute's Deploy sends your file as the type'slogic.rs. Generated usesServer/<Type Name>/src/logic.rs. What you do: nothing unless you use Server Objects. If you do, generate each type's crate, write itslogic.rs, commitServer/<Type Name>/(withouttarget/), and build and deploy it from Crowdy Studio's Server Compute page (see the next entry). See Write its server logic. -
Server Compute: build and deploy a project's server code from Crowdy Studio, and watch it run. Open Crowdy Studio and choose Server Compute in its navigation; the page uses Studio's sign-in and the app set in Studio. A header shows the app and one status badge (Live: version N, Nothing deployed, Switched off with a Switch on button, or Budget paused), over four tabs (a fifth, Settings, comes in the next entry). Overview lists every Server Object type in the project with the state of its generated code (Ready, Needs generating, Out of date, Problem), and Deploy sends all of them, plus a small root the SDK writes, to ck-exec's build and deploys the result as the app's active server code, replacing what was active; a type whose code is not Ready has to be regenerated first, and the confirmation warns about server code files with unsaved edits. The build output is shown when the build finishes, succeeded or failed. Versions lists the deployed versions with a Make active button per row (a rollback). Logs shows the app's logs by type and level, with Show this call on a line to see only that call and Load older to page back. Activity shows how the calls to each Server Function went over the last hour, day or week. Each type on Overview can be switched off and on (its calls are refused while it is off; its Server Objects are saved and stopped). Deploy starter pack deploys the platform's example types instead. The asset editor's Server Code tab also warns before Save overwrites a file that changed on disk, disables Save and Generate Server Code until the Type Name is valid, and closing the editor asks about unsaved server code edits. The command
UnrealEditor-Cmd <project> -run=CrowdyServerCompute -op=deploy -yesdoes the same for CI, with-op=status,versions,starters,activate,disable,enable,logsandstatsalongside it and-out=<file>for a JSON result. The operations that replace or switch the app's live server code (deploy,starters,activate,disable,enable) need-yes; without it they refuse and send nothing. What you do: nothing unless you use Server Objects. If you do, deploy from the page instead of the platform's own build and deploy, and mind that a deploy replaces the app's active server code. Add-yesto any CI job that runs one of those operations. See Server Compute. -
Server Objects: a definition asset editor, change tracking, and Remove. Double-clicking a
UCrowdyServerObjectDefinitionnow opens its own asset editor, like the Material or Blueprint editors, instead of a property list. Its toolbar, after Save and Browse, has Generate, Deploy (with a small dropdown: Open Server Compute, which opens Crowdy Studio on the Server Compute page itself, and Show build output) and a status readout, a coloured dot and one line such as "No changes" with the live version, or "Changed since version 7". It has two tabs you can rearrange, and the editor remembers the layout. Server Code, on the left, holds the file's path, the Code Source combo, the deployed revisions and the code, with line numbers and Rust colouring and Save, Revert and open-in-external-editor buttons. Details, on the right, has native property rows: a Server Object group (Type Name, Description, Who Can Read, State, Players See as tick-boxes drawn from State's fields, Save Every, Stop When Idle For), the Server Functions array (each entry shows Name and Who Can Call on its header, Sends and Replies underneath, and Server Name in a collapsed Advanced group), and a collapsed Advanced group with Server Names and Baked. Nothing about the definition's data changed, only its labels: Visibility reads Who Can Read, Watched Fields Players See, Save Interval Seconds Save Every, Idle Timeout Seconds Stop When Idle For, Params Sends and Reply Replies. Type Name is suggested from the asset name (DA_VillageBeaconbecomesvillage_beacon) and checked as you type, and a Type Name another definition already uses is a Problem that disables Generate and Deploy. The field and enum-value overrides under Server Names now pick from lists instead of asking you to type a name. Player Callable is replaced by Who Can Call, with Players and Server only: a Server only function refuses a player's call withdenied, and only other server code and developer tools can call it. Nothing is converted, so a function that had Player Callable off is Players again until you set it to Server only, and the type has to be generated and deployed again. New Description (shown under the header and on the Server Compute page) and Code Source (Generated or My own file): an empty Logic File no longer means the generated one, and Logic File is used only when Code Source is My own file, so set it once if you had your own file. With My own file and no file chosen the type cannot be generated or deployed, and the messages say "Code Source is My own file but no file is chosen".Each deploy now records what it sent in
Server/<Type Name>/revisions.json: per deployed revision, the files as sent, a fingerprint that ignores line endings and trailing whitespace, the versions and build digests. From it the Server Compute Overview gives each type one badge against the live version, No changes, Changed (with what differs: server code, save interval, idle timeout), New, or Live code unknown (the live version runs code this project has no record of), and shows a Removed row for a type that is live but no longer in the project. Deploy is offered only when something changed, the status line says what ("Since version 7: 1 changed, 1 new."), and the Deploy confirmation lists what it adds, changes, replaces unrecorded and removes. The Versions tab gains a line per version saying what changed from the one before. On the editor's Server Code tab, the deployed revisions combo lets you view an earlier deploy's code, Compare with yours (a line diff), or Restore it into the editor as unsaved changes, with a warning when the revision was built from a different definition. A type's ... menu on the Overview tab has Open definition and Remove from project..., which deletes the definition and itsServer/<Type Name>/folder after a confirmation that says what stays and why; the type keeps running until the next deploy removes it. A new Settings tab holds Revisions to keep (3 to 50, default 5), saved inConfig/DefaultEditor.ini. From the command line,-op=changesreports the same comparison as JSON (liveVersion,types,problems,ready,hasChanges), and-op=deployrecords revisions and lists any it could not save underrecordErrors. What you do: nothing unless you use Server Objects. If you do, commitrevisions.jsonwith each type's crate (andConfig/DefaultEditor.iniif you change the setting) so your teammates see the same states, and in a CI job that deploys, commit the changedrevisions.jsonfiles afterwards. See Create a Server Object type, Write its server logic and Server Compute. -
Server Objects: Lists instead of structs. A definition's State, and each Server Function's Sends and Replies, can now each be a Struct, as before, or a List: named values added right in the asset with Unreal's property bag editor (the + button adds a value; pick its name and type and set its starting value), with no struct to write. New rows choose which: State As before State, and Sends As and Replies As before Sends and Replies in each Server Functions entry, all defaulting to Struct (
ECrowdyServerValuesForm; C++ propertiesStateForm/StateList,ParamsForm/ParamsListandReplyForm/ReplyList, the Lists beingFInstancedPropertyBags). A List travels exactly like a struct with the same fields, so the server code and saved data do not care which you chose; its starting values are the defaults a new Server Object starts from and the generated server code's defaults. The generated server code names a State List after the Type Name plusState(village_beacongivesVillageBeaconState) and a function's Lists<Function>Paramsand<Function>Reply, the names the structs usually have, sologic.rsdoes not change. Players See ticks a State List's values the same way. Saving checks the Lists like structs, and adds "Add at least one value to the State list" and "Two lists are named FeedBeaconParams; give their functions different names". A List value's name is its name on the server and Field Names cannot keep an old one, so renaming it loses its saved value, as renaming a struct field does without Field Names. From C++,UCrowdyServerObject::MakeParamshands out a function's params List with its starting values to set by name and pass to a newCalloverload taking anFInstancedPropertyBag,CrowdyExec::ToList(Result.Reply)(inCrowdyExecCodec.h) reads a List reply by name, andGetStateList()returns a List State's watched values; passing a struct to a function that takes a List completes asBadRequest. Blueprint nodes are not here yet. What you do: nothing; existing definitions stay Struct. Use a List for values only one function or type needs, and keep a struct for values you share, may rename, or use elsewhere in C++. See Create a Server Object type and Call its functions. -
Server Objects: the Server Functions header row is labelled. Each entry's header row now names its two cells: Function before the name box ("The function's name, used to call it from Blueprint and C++ (the server's method name is made from it)") and Who can call before the Players / Server only choice ("Players: any player can call this function. Server only: only other server code and developer tools can; a player's call is refused."). Nothing else about the entry changed. What you do: nothing. See Create a Server Object type.
-
Server Objects: the Server Object asset editor now works like the Blueprint editor. It has three tabs that dock and rearrange, with the layout remembered: Server Code on the left, unchanged; Server Object at the top right, where My Blueprint is, listing the type's Variables (each with its type pill and an eye: open is Visible to Players, closed is Server Only) and Functions (each with its pins as the node reads, such as
(Oil) -> Oil, and a Server Only tag), with + buttons, right-click menus, and double-click to jump to a function'sfninlogic.rs; and Details at the bottom right, which follows the selection: the type's settings with nothing selected, a variable's name, type, visibility and default value, or a function's Name, Callable By, Inputs and Outputs. Labels now use Blueprint's words: Who Can Read is Readable By (Every Player or Owner Only), Who Can Call is Callable By (Players or Server Only), Players See is Visible to Players, State As is the Variables' Use Struct check-box and State is State Struct, Sends is Inputs (Use Struct, Input Struct), Replies is Outputs (Use Struct, Output Struct), and a List is now variables, inputs or outputs added in the asset. The save check "Add at least one value to the State list" now reads "Add at least one variable, or use a State struct". The wire, saved data, generated server code names and C++ calls are unchanged. What you do: nothing. See Create a Server Object type. -
Server Objects: Blueprint nodes and the Crowdy Server Object component. The
CrowdyExecmodule now exposes Server Objects to Blueprint. The Crowdy Server Object component (UCrowdyServerObjectComponent, in the Crowdy class group) holds one Server Object for its actor: it joins at Begin Play and lets go at End Play, and the Server Object is given back a few seconds after its last holder lets go. Its properties are Definition, Instance Mode and Instance Id. Instance Mode (ECrowdyServerObjectInstanceMode) is Instance Id (the typed id; every actor with the same one shares a Server Object), Signed-In Player (the player's user id, for Owner Only types; it waits for sign-in and joins again after a new sign-in) or This Actor (the actor's placement in the level, the same on every machine; only for actors placed in the level). It raises On Variables Changed and On Status Changed (Object is None when it could not join; Get Failure Reason says why) and has Get Server Object, Get Failure Reason and Rejoin. The nodes, under Crowdy SDK > Server Objects: Get Server Object (Definition, Instance Id, Owner, which defaults to self; the object, or None with Error), Release Server Object, Get Player Instance Id (the signed-in player's user id), Call Server Function (latent,UCrowdyServerCallAction: Object, Function, Inputs; On Success or On Failed, exactly one, each with Outputs, Outcome, Reason and Retryable), Make Inputs (a function's Inputs with their defaults), Set Server Value and Get Server Value (by name, with a wildcard Value pin; false when there is no value by that name or the type differs), and on the object Get Status, Get Failure Reason, Get Instance Id, Get Variables, Watch Variables (runs an event after every change, and once at once when the Variables are already current, naming what changed), Stop Watching Variables and On Status Changed. Typed nodes, with typed pins, follow in a later entry below. What you do: nothing; the C++ API is unchanged. Blueprint needs noBuild.csentry. See Get a Server Object, from Blueprint. -
Server Objects: members, access rules, timers and Can Call. The definition asset gains four Details categories. Access:
ECrowdyServerObjectVisibility(Readable By) gains Members;ECrowdyServerFunctionCaller(Callable By) gains Members and Leader; each function gets Cooldown (CooldownSeconds, per player, only successful calls count); and a number input gets a Value Range, a Min and Max in the input's Details (a struct input'sClampMinandClampMaxread the same), refused asbad_params: Amount must be 1 to 100. Members: Members From (ECrowdyServerMembersSource: None, This Object, Crowdy Team), and for This Object Max Members (default 100; 0 is no limit and needs Show Members to Players off, which with it on must be 1 to 4,096), Show Members to Players and Remove Members Who Leave. This Object adds six built-in functions, greyed out in a Built-in section of the Server Object panel: Join, Leave, Add Member (server only), Remove Member, Make Leader and Set Open for Joining (the last three for the leader), and Open For Joining, which starts true and opens again when the last member leaves. The first member leads; when the leader goes the longest-standing member does. Crowdy Team takes the team id as the Instance Id, Members is the team's members and Leader a member who can manage the team. Timers & Events: Timers (FCrowdyServerTimer: Name, Every or After, Seconds, Start Automatically), each a function inlogic.rs, and On Player Joined and On Player Left, which count connections. Can Call: other definitions this one's server code calls with typed functions.logic.rsgetsmembers::...,timers::startandtimers::stop, andcalls::<type>::<function>. A refusaldenied:reaches the caller as the Denied outcome (it was aServerError) andcooldown:as Denied withbRetryable. The component (UCrowdyServerObjectComponent) gains two Instance Mode choices, Player's Team (Team Id; several teams fail and ask for one) and From Server Value (Source Definition, Source Instance, Source Variable, followed as it changes), andUCrowdyServerObjectgainsGetMembers,GetMemberCount,GetLeader,IsOpenForJoining,IsMemberandIsLeader, with changes namedMembers,MemberCount,LeaderandOpenForJoining. What you do: nothing for existing types; to use a setting, set it, then generate and deploy the type again. A Server Only function's refusal now reads as Denied, so code that checked for aServerErrorstarting withdeniedshould check the outcome. See Access, members and timers and Guild halls and arena lobbies. -
Server Objects: typed Blueprint nodes, find by asset, and Only One Instance. In any Blueprint graph, typing a variable's name now offers
Get <Variable> (<Asset>)andOn <Variable> Changed (<Asset>), a function's name offersCall <Function> (<Asset>), and each asset offersGet Server State (<Asset>), all under Server Objects and the asset's name, with a real pin for every value instead of a name and a wildcard. A generic Call Server Function with a Function dropdown is offered as well. Only variables ticked Visible to Players are offered. The pins: Boolean, Byte, Integer, Integer64, Float, String, Name, enums, Vector, Rotator, Vector2D, Quat, IntPoint, IntVector, LinearColor, Color, DateTime, Timespan, Guid, GameplayTag, soft object and soft class references, your own structs (with Split Struct Pin), and Array, Set and Map. Types only C++ has widen:int8,int16anduint16to Integer,uint32anduint64to Integer64, and aTOptionalto a value pin with a Has Value pin beside it. A value that does not fit is refused: a Call fails through On Failed as a Bad Request naming the input, and a Get logs a warning and reads the default. An Array, Set, Map or optional inside another, and a C++ struct or enum that is notBlueprintType, have no pin, and the compile says which and why. Get reads the shared Server Object's current value, not a copy, and reads the variable's default until the server has sent the values; its advanced<Variable> Has Valuepin (collapsed, expand the node's arrow) says whether they have arrived, an optional's visible Has Value is false when it is empty or they have not arrived, and Get Server State has one advanced Has Values pin. A Call whose input does not fit its type, such as -1 for auint32, sends nothing and runs On Failed with a Reason naming the input. A variable, input or output named like one of a node's own pins (Team Id,Instance Id,Value Changed,then,execute,self,Server Function,Call Outcome,Call Reason,Call Retryable,On Success,On Failed, or another value's name plusHas Value) gets no node, and the compile says to rename it. AnOn <Variable> Changedwhose Bind is not connected gives a compile warning.On <Variable> Changedtakes its Bind from Begin Play once, runs at once with the current value and after every change of that variable, stops when the Blueprint is destroyed, moves when it is bound again, and through a component follows whatever object the component joins.Call <Function>has typed input and output pins and On Success and On Failed with Outcome, Reason and Retryable. The object comes from Target (the Server Object, a component, or an actor with one; Self by default), or from Find By Asset (right-click the node): Instance Id, Signed-In Player or Player's Team, for a widget with nothing to pass in. Only One Instance, a new setting in the asset's Access, gives a type one shared instance for every player, like a registry or a world event, and its nodes need neither a Target nor an Instance; saving refuses it with Readable By set to Owner Only or Members From set to Crowdy Team, and the generated server code refuses any other instance. Renaming or retyping a variable in the asset updates the nodes, and a wire that no longer fits is a compile error. A new page, Tip jar: your first Server Object, builds a tip jar with only the editor. Behavior change: the names On Variables Changed and Watch Variables give in their Changed list are now the names Get Server Value finds, the variable's own name in the asset, also for a variable renamed with Server Names (they used to give the server's name). What you do: nothing for existing Blueprints unless one compared a Changed name with a renamed variable's server name; use the variable's own name. The untyped nodes are unchanged. Generate and deploy a type again to use Only One Instance. See Get a Server Object, from Blueprint and Only One Instance. -
Server Objects: a shared boss example and
crowdy.exec.trace. A new page, Shared boss fight, builds a boss that every player hits from start to finish: the structs or the asset's own variables, Readable By set to Every Player, aHitfunction that players may call with a 0.25 second Cooldown and a Value Range of 1 to 25 on itsDamage, aRespawntimer that is After 10 seconds and not started automatically, thelogic.rsthat refuses a hit while the boss is defeated, clamps health at 0, starts the timer at 0 and restores full health on respawn, a Deploy from Server Compute, the Crowdy Server Object component on the actor (This Actor, Instance Id or From Server Value), the typed Blueprint nodes, two players in Play In Editor, and switching the type off and on from Server Compute to see both players recover.CrowdyExecgains the trace console variablecrowdy.exec.trace, off by default, with the lines logged underLogCrowdyExecand startingexec:. When it is on it logs each Server Object's status changes, every read and push applied with its epoch, sequence number and the names of the variables that changed, each re-read and why (gap,epoch,reconnectorownerOnly), subscribes, each call sent and answered with its outcome and time in milliseconds, and connections closing and redialing. It never prints a value or a token. What you do: nothing unless you want the lines; to see them, entercrowdy.exec.trace 1in the console or start with-ini:Engine:[ConsoleVariables]:crowdy.exec.trace=1. See Shared boss fight and Seeing what a Server Object is doing. -
Server Objects: a timer that cannot start refuses the call. If the platform refuses a timer that a Server Function starts with
timers::start, the whole call is refused: nothing it changed is kept or published, timers it already started in that call are cancelled, and the player seesServerErrorwith a reason such asthe change was not kept: timer Respawn could not start: <reason>. What you do: nothing; generate and deploy the type again to get it. See Timers and events. -
Server Objects: renames ask, every value is checked, and each function keeps its own defaults.
- Renaming a variable, input or output added in the asset asks first. Generate Server Code notices the rename and asks whether to keep the old name on the server, so saved values carry over; Yes adds an entry to the new List Value Names under Server Names (
FCrowdyServerListValueName: List, value id, Server Name). Before, the rename dropped every saved value under the old name without a word.types.rsnow records each such value's id in its comment. - Changing a Type Name finds the old folder. Each generated
Cargo.tomlnow records its definition asset, and Generate Server Code offers to move the folder generated under the old name, keeping yourlogic.rs. The record stays on your machine: a deploy sendsCargo.tomlwithout it. A Type Name another definition already uses is refused from the Content Browser too. - The generated code checks every value an Unreal client refuses: also GUID text, names and gameplay tags over 1,023 characters, soft object and class paths, dates and time spans, more than 65,536 list, set and map entries in one message, and keys Unreal counts as one. A new Server Object's starting values are checked (
bad_seed), and a read of a state holding such a value is refused withunreadableand the field, which the Server Object shows as its reason while it waits in Connecting, instead of failing for every player. - Each function keeps its own default values in Make Inputs, the call nodes and replies, where two functions whose inputs or outputs had the same shape got the first one's.
- Fixed: a Server Function's name could reach the server in another letter case in a packaged game, refused as
unknown_method; it is now stored when the asset is saved. The palette's Get and On Changed entries now refresh after an edit to a Blueprint struct nested in a type's variables, or one that fixes a failed save. - What you do: generate and deploy your types again. Commit the regenerated
Cargo.tomlandtypes.rs; every type shows as changed once on Server Compute. See After you change the definition and Troubleshooting.
- Renaming a variable, input or output added in the asset asks first. Generate Server Code notices the rename and asks whether to keep the old name on the server, so saved values carry over; Yes adds an entry to the new List Value Names under Server Names (
-
Server Objects: Type Settings, CSO_ asset names, tidier Details and Call nodes.
- Type Settings. The Server Object panel starts with a pinned Type Settings row (gear icon), selected when the asset opens; it shows the type's own settings in Details. A Type Settings toggle before Generate in the toolbar does the same from anywhere. Clicking an empty part of the panel still shows them, now with the row highlighted, and the hint "Select a variable or function in the Server Object panel to edit it." is gone.
- CSO_ asset names. Server Object assets take the prefix
CSO_(Crowdy Server Object). Add > Crowdy > Server Object in the Content Browser makes one namedCSO_NewServerObject; Add > Miscellaneous > Data Asset still works. The Type Name suggestion dropsCSO_, orDA_for older assets, soCSO_VillageBeaconsuggestsvillage_beacon, and the palette category reads CSO Village Beacon. - Details. Save Every and Stop When Idle For always show seconds (300 s, not 5 min); Value Range is two boxes, Min and Max; a timer's Repeat is now Runs (Once After or Every) and Seconds is Time, and its row reads "Respawn: Once After 10 s"; the built-in functions show their inputs, such as Add Member (Player); Code Source My own file reads My Own File; and the Variables, Inputs and Outputs source is the Use Struct check-box everywhere. Type Name uses the normal font.
- Call nodes.
Call <Function>and Call Server Function show Then, On Success and On Failed on the right, then the function's outputs. Outcome, Reason and Retryable (Call Outcome,Call Reason,Call Retryable) move to the node's advanced area, under the small arrow at the bottom of the node. - On Changed more than once. Each
On <Variable> Changednode you place is its own binding, so one graph can bind the same variable from Begin Play and from a custom event; each runs once per change, and running the same node again replaces its own binding. - A new page, Relate Server Objects to each other: one to one, one to many, many to one and many to many with the existing settings, and good practice for ids, ownership, calls between types and limits.
-
Server Objects: Generate adds what logic.rs is missing, timers have constants, and the code tab follows the file.
- Missing functions.
logic.rsis written once, so a Server Function, timer or event added later reached only theFunctionstrait and the crate stopped compiling. Generate Server Code now names whatlogic.rslacks and asks "Add empty versions of the missing functions?"; Yes adds them before the closing brace of yourimpl Functionsblock (a function refuses with "not written yet", a timer or event does nothing). Without such a block they go to the clipboard. A method the type no longer has is named, never deleted. The Server Code tab shows the same as a line above the code, with Add Stubs (as unsaved changes) or Copy Stubs. - The Server Code tab follows the file. A save from VS Code or another editor now shows in the tab within a second; with unsaved changes in the tab, it keeps them and offers Reload or Keep Mine. Before, the tab kept the old text until Revert.
- Timer constants. The generated
timersmodule has one constant per timer, its function's name in capitals:timers::END_MATCHholds"EndMatch". Writetimers::start(ctx, timers::END_MATCH)so a misspelt name does not compile; a typed name is matched exactly, capitals included, and only fails when it runs. A timer whose constant would beQUEUEDis refused at Generate. - What you do: generate again;
lib.rschanges for types with timers. Switching yourtimers::startandtimers::stopcalls to the constants is optional. See After you change the definition and Timers and events. - What you do: nothing required. Existing
DA_assets keep working; rename them toCSO_if you like. A status pin you already wired stays visible on the Call node. See Create a Server Object type and Call its functions.
- Missing functions.
-
Server Objects and Crowdy Teams: a final pass.
- The Crowdy Teams "my teams" cache keeps itself current. Signing in (log in, register, magic link, social, or a restored session) clears it and fills it again with a Get My Teams; signing out clears it, so
HasCachedTeams()is false andOnMyTeamsCacheChangedfires with an empty list if it held anything. A successful Create Team, Join Team, Leave Team, Delete Team, Update Team, Remove Team Member, Set Team Member Roles, Update Team Role or Delete Team Role refreshes it; Request To Join does not, since the membership is pending. An answer to a Get My Teams sent before a sign-out never fills the cache, and an older answer never replaces a newer one. C++ gainsClearMyTeamsCache()(not Blueprint). Before, only Get My Teams filled it and nothing cleared it. What you do: nothing; you can drop a Get My Teams call you made after a join or leave. See Crowdy Teams. - Too many new names no longer fail a whole message. A client accepts at most 1 Mi (1,048,576) characters of new names (Name values and soft object path parts it has not seen before) per run. Before, going past it failed the whole message. Now a new Name reads as None and an object path made of new names reads as empty, the rest of the message still applies, a map or set entry whose key was skipped is dropped, and the client logs one warning. The text "None" never counts. See Create a Server Object type.
- Lists that start with different values get their own struct in the generated server code. Lists that share a shape (one copied from the other) shared one struct, and later ones became
pub type X = Y;aliases that took the first List's starting values. Now a List that starts with different values gets its own struct and defaults, and one that starts with the same values stays an alias. Generate shows a notice when a List switches, such as "TipReply is now its own struct, since its starting values differ from those of TipJarState; code that uses one for the other no longer builds." What you do: generate again; fix anylogic.rsthat used one of those types for the other. See Create a Server Object type. - Moving or renaming a definition asset keeps its server code findable. Saving a moved or renamed definition updates the record in its crate's
Cargo.toml, so a later Type Name change still finds the folder. See After you change the definition. - Fixed: a Ready Server Object no longer keeps an earlier read refusal's text as its failure reason once a read succeeds.
- The Crowdy Teams "my teams" cache keeps itself current. Signing in (log in, register, magic link, social, or a restored session) clears it and fills it again with a Get My Teams; signing out clears it, so
-
Actor updates carry their payload type id.
FCrowdyActorUpdategainsPayloadTypeID, the payload type id the sender's state blob led with, kept even when no struct is registered for it on this client. The stranded and evicted pending-activation warnings, for an entity waiting on a spawn event that never resolves its class, now print that id when the state could not be decoded instead of saying the payload cannot be named. What you do: nothing; it is diagnostic. See Entities and spawning. -
The CrowdySDK module no longer lists HTTP as a public dependency. It never used it. A game module that only picked up Unreal's HTTP module because it came in through
CrowdySDK's public dependencies now needs"HTTP"in its ownBuild.cs. Separately, theCrowdyCppBridgemodule's build no longer prints a precompiled-header warning or the twoCS0618obsolete-property warnings. What you do: add"HTTP"to your module's dependency list if your code calls it and relied on this. See Installation. -
Test-only accessors on
FCrowdyCppClientare compiled only with automation tests.MakeForTest,GetLastTestRequest,GetLastTestRequestBody,SetTestStampsHandOff,SetTestResponseScript,SetTestOnRequest, and theTest*WebSocket*helpers, andFCrowdyCppLoopback::SetTimeForTests/ClearTimeForTests, now exist only whenWITH_DEV_AUTOMATION_TESTSis set, never in Shipping. What you do: guard any code that calls them behind the sameWITH_DEV_AUTOMATION_TESTScheck. -
Updates for actors not yet on screen wait in a bounded queue. They used to queue without limit while the worker threads that place them were stalled. Past
crowdy.replication.tracker.maxqueuedupdates(default 8192), a further update is dropped, counted, and reported in a warning at most once a second; the actor appears on a later update. A working client never reaches it. What you do: nothing; if you see the warning, the worker pool is not keeping up. See Console variables.
The SDK's Game Model API (containers and attributes, Effects, sessions, pre-seeding) was built on the game API's legacy game-model engine, which ck-exec replaced; the game API no longer serves it. The entries that describe it are kept as history. Do not adopt it: since v2.18.0 the SDK marks it deprecated and makes every call fail at once, and a later release removes it. Its replacement is Server Objects, described under SDK v2.18.0; Move from Game Models says what to change.
2026-09-26 (SDK v2.17.0)
- The actor pool grows instead of dropping entities. Up to and including 2.16.0 a class's pool held
DefaultPoolSizePerClassactors (8) and never grew, so a client with more than eight live remote entities of one class drew only eight: the rest logged "Pool exhausted" and were never retried, and one whose spawn event had already made an actor lost that actor too, destroyed before the pool was asked. A Game Model container bound to such an entity never bound. Now a full pool spawns one more actor, up to the newMaxPoolSizePerClasson the Actor Pool Backend Config (default 256); the up-front sizes stay as they were. The spawn event's actor is only replaced once a pool actor is secured, and at the cap it is kept and drawn instead. An entity the pool cannot draw yet is retried when another of its class leaves or when its spawn event registers it, and the cap warns once per class instead of once per entity (each waiting entity is now acrowdy.pool.traceline). In the sandbox's two-client test, the observing client went from 0 of 20 respawned entities alive and bound to all 20. A custom rendering backend can take part in the retry by overriding the new optionalUCrowdyRenderingBackend::IsInstanceActive. What you do: nothing; raiseMaxPoolSizePerClassif a pool warns it is at its cap. See Rendering backends. - A remote destroy no longer hands a still-live pool actor to another entity. When a destroy event with a destroy delay arrived and the entity then left the tracker inside that delay, the pool actor went back to the pool, was given to the next entity, and was destroyed under it when the delay ran out, leaving that entity invisible until it timed out. The actor now stays with the destroy until it is gone, and an entity that keeps sending after its actor was destroyed is activated again. An actor's
EndPlayalso no longer removes the entity record of an actor that replaced it under the same id. What you do: nothing. - More trace lines for entities that do not appear.
crowdy.entity.tracenow logs the success path too: "Spawn dispatched", "Spawn received" and "Entity registered", each with the id, class and location.crowdy.pool.tracelogs each activation, each pool growth step, and each entity waiting at the cap.crowdy.net.tracelogs every spatial message received (opcode, actor, chunk) and the transport's totals, so "the server never sent it" can be told apart from "the client dropped it". What you do: nothing; see Troubleshooting. - Known server issue: a player who just joined can stay invisible until they move. The replication server refuses an actor's first update while it loads permissions, then accepts every later one but does not relay the actor to other players, or theirs to it, until it moves. The SDK sends what it should; this has been reported. See Troubleshooting.
- A string value that changes only in letter case is now a change. Unreal compares strings without regard to case by default, and the Game Model used that comparison to decide whether a value changed, so
alicebecomingAlicewas never applied: the member kept the old spelling and noCrowdyOnRepor change delegate fired. It now applies like any other change, on pulls, confirmed invokes, and free containers. The same fix reaches the editor: Crowdy Studio's sync preview now shows a value, policy, or effect expression that changed only in case (it used to report nothing and never send it), the Apply Effect node keeps a string input you set to a different case of its default instead of dropping it, including after the Blueprint is reloaded, and the effect script editor keeps an edit that only changes case. What you do: nothing; the first sync after updating may upload a function whose stored text differs from your project only in case, once. - Your own invokes no longer pull back what they just wrote. The notification the server sends for your own invoke used to be dropped once per container, so several quick invokes on one container pulled it again for all but the first. The SDK now expects one notification per invoke, counted when the request is sent, and drops that many, each expiring three seconds after its invoke; an invoke that fails or writes nothing gives its count back, and pulls instead if its notification was already dropped. Since a dropped notification could have been another client's write, a container that had one dropped is pulled once more within about three seconds. In the sandbox, a client invoking its own row ten times a second for a minute went from 118 pulls of that row to 29 (20 of them the three-second catch-up), with the watching client still pulling every change.
crowdy.gamemodel.statsgainsSelfEchoMarksExpiredandSelfEchoBackstopPulls, andSelfInvokesMarkednow counts invoke attempts sent (a retried invoke counts each attempt). What you do: nothing; another client's write to a container you are writing yourself can take up to about three seconds to show, where before it usually came with the next pull. See Change pings and pull. - Your copies of other players' entities bind from one paged list per type, and first pulls after a bind are merged. A remote proxy used to read its owner's row by key, one call each and again on every retry while the owner had not created it yet; it now joins the same paged list shared entities use, under the session active when it is listed, and one the list does not find yet waits for the next list instead of calling on its own. A copy never creates its owner's row. A copy-only list reads at most one page per copy it looks for; a type with more rows than that (an app-wide per-player type) is marked, and its copies read one key at a time from then on. The first pull after any container binds now waits the usual tenth of a second, so entities binding together read their state in one bulk call, and every entity bound to that container receives it; an invoke you send while that pull waits keeps its value when the pull lands. In the sandbox's two-client test, the observing client bound the same 40 copies with 10 list calls instead of 140 keyed reads, and the spawning client read the state of its 200 new containers in 10 calls instead of 200.
crowdy.gamemodel.bulkresolve's help now names remote copies, andcrowdy.gamemodel.statsgainsBulkResolveListPages. What you do: nothing; a newly bound entity may show its defaults up to a tenth of a second longer than before. See Pre-seeding and Change pings and pull. - At most one state-applying pull per container is ever in flight, and a pull that would overlap it is held and merged instead of sent again. A change notification, Refresh Game Model (
PullNow), a first bind, or a bulk bind requested while a pull of that container is already out waits for it to land, then follows up: an explicit request (Refresh Game Model, a bulk bind) is sent right away, a notification or a first bind goes back through the usual tenth-of-a-second merge window. A bulk bind leaves out a container already being pulled. A read that never applies state (the combat kit's Get Combatant State, for example) is never held.PullNow's tooltip now says it pulls now, or right after a read of that container already in flight lands, andPullDataContainer'sOnDonefires exactly once for the read that lands after the call, its own or the follow-up. Separately, a pull landing after your own invoke already wrote a newer value no longer rolls that value back: a key your invoke wrote after the pull was sent keeps the invoke's value, and if the pulled value actually differed, one follow-up pull is queued to re-converge the row; a key written before the pull is not protected, since the pull is newer. This does not cover another client's write landing in that same window, which still reaches you through the next notification as before.crowdy.gamemodel.statsgainsPullsHeldBehindInFlight,FollowUpPulls, andStaleKeysSkipped, printed aspulls held behind in flight N follow-ups N stale keys skipped N;OverlappingPullsnow counts only pulls that still applied state while another was out despite this, and should read near zero. What you do: nothing to keep working; a caller pollingPullNowin a tight loop now gets fewer actual round trips for the same calls, not more state. See Change pings and pull. - The SDK now retries a platform-busy refusal on its own before the caller ever sees a failure. A query (a container state read, a bulk state read, a
ListContainerspage, or any other SDK query) resends on any refusal the platform blames on itself and marks retryable, exceptWRONG_DATACENTER(the SDK already follows that redirect) andAPP_UNAVAILABLE(nowhere else to serve it). A container ensure and agameModelInvokeresend only onPLATFORM_BUSY, the one code that means the work never started, so nothing is ever applied twice. At most 3 retries, waiting the server's suggested time when it names one (at least 0.1 s, plus up to 20% jitter, and not retried past 5 s) or a random 0.1-0.2 s doubled each time otherwise; at most 256 requests may be waiting out a backoff at once, past which a refusal goes straight to the caller. An invoke's busy retries run beside its existing rate-limit retry (RATE_LIMITED, 2 retries): the two caps are separate, and every attempt still counts toward the SDK's local invoke allowance window.crowdy.net.retry.busy(default 1, every build) turns it off, handing every refusal straight to the caller as before.crowdy.gamemodel.statsgainsRetries/RecoveredByRetryper operation and aninvoke busy retries N recovered M gave up Kline (InvokeBusyRetries/InvokeBusyRecovered/InvokeBusyGaveUponUCrowdyGameModelSubsystem::GetNetStats); a failure reason now counts only the final outcome, not each retried attempt.UCrowdyGameModelSubsystem::IsBusyRefusalandFCrowdyCppJsonResult::Blame/bRetryable/RetryAfterMsare new. What you do: nothing to keep working; a caller that built its own retry loop around a busy refusal is now retrying on top of the SDK's own retries. See Console variables, Coalescing, and Change pings and pull. - The SDK now asks for HTTP/2 on its own requests by default.
crowdy.net.http2(default 1, every build including Shipping, read per request) setsIHttpRequest's HTTP version to2TLS, falling back to HTTP/1.1 when the server does not offer it; plainhttp://stays HTTP/1.1 regardless. Only the SDK's own requests are affected: the game's other HTTP traffic and the engine'shttp.CurlAllowHTTP2are untouched. On the test tier, multiplexing took a burst of 300 reads in 10 seconds from about 7.2 s p50 to 0.41 s, and stopped an unrelated call issued mid-burst from being delayed behind it. What you do: nothing to keep working, but read the caveat before you rely on the default: with HTTP/2 on, a request that fails at the network level (not a cancel or a timeout) can have its curl diagnostics logged atWarningwith the request headers, bearer token included, in any build with logging enabled (a default Shipping build compiles logging out); setcrowdy.net.http2 0if you ship or collect logs from a build with logging enabled. Faster delivery also means a burst reaches the server together, so expect morePLATFORM_BUSYrefusals under heavy bursts; the SDK retries those on its own, see the busy-retry entry below. See Console variables and Change pings and pull. ListContainersnow reads every matching row instead of only the server's first page. It pages the server 1000 rows at a time until a short page, up to 50 pages (50,000 rows), logging a warning if it stops there with more left unread; a failed page fails the whole call (bOkfalse, no partial list), and a row the server returns on two pages comes back once. For an app-scoped type,SessionIdis now ignored the same way create and ensure already ignore it.FCrowdyCppClient::ListContainersat the bridge layer is unchanged and still returns one page. What you do: nothing to keep working, but a caller that assumed at most 200 rows back may now see more, and a caller that actually wanted only the first page has no way to ask for that anymore. See Collections and Free Containers.- The invoke allowance governor now counts only invokes. A pull, a list, an ensure, or any other Game Model read used to spend the same 120-per-10s allowance as a
gameModelInvoke, which could stretch the coalesce merge window, the refresh-pull merge window, and container manifest pre-seeding under pure read traffic. Now an invoke is counted only when its request actually starts (a budget retry counts again); the coalesce window still widens as invokes approach the allowance, but the refresh-pull merge window no longer widens and manifest pre-seeding is no longer paced by the invoke count at all. What you do: nothing to keep working; a project whose pulls or a large manifest apply were being slowed down by the old shared counting should see that stop. See Coalescing and Pre-seeding containers. crowdy.gamemodel.statssplits each operation's latency into SDK time and HTTP time, breaks its failures down by reason, and adds in-flight and dispatched counts. Each op row gainsSdkP50Ms/SdkP95Ms(request created to handed off to the engine's HTTP module) andHttpP50Ms/HttpP95Ms(handed off to completion delivered, including any wait inside the HTTP module, the network, the server, and up to one frame for delivery); a cancelled request records no SDK/HTTP split.FCrowdyGameModelOpStatsRow::FailureReasons(and the log line'sfailures: CODE xN, CODE xM) counts each failure under the server's error code, orgraphql,http <status>,rejected,canceled, ortransportwhen there is none, up to eight reasons per op with the rest underother. Transport totals gainInFlightandPeakInFlight; net stats gainInvokesDispatched. No behavior changed. What you do: nothing; it is read-only, and a high HTTP time next to a low SDK time on the same op points at queueing inside the engine's HTTP module, not the SDK or the network. See Change pings and pull.- New network diagnostics for the Game Model path.
crowdy.gamemodel.statslogs op calls, failures, and latency per server operation; pull counts and how many were redundant or overlapping; self-echo counts; resolves and re-resolves; invokes in the rate window beside the SDK's own governor count; transport bytes; the top pulled containers; and invoke faults by code, since the last reset.crowdy.gamemodel.stats.resetzeroes it. Both are new console commands, stripped from Shipping builds like the existing watch commands. The same numbers are onUCrowdyGameModelSubsystemin C++:GetNetStats,ResetNetStats,GetInvokesInWindow,GetOpStats,DescribeNetStats. No behavior changed. What you do: nothing; it is read-only. See Change pings and pull and Console variables.
2026-09-23 (SDK v2.16.0)
- Backend gains a Test tier, and its default now follows the SDK build's own release tier.
ECrowdyEnvironmentadds Test between Dev and Production, so the Backend selector on the Sign In page now offers Dev (shared), Test, Production, and Custom. The built-in hosts behind Dev and Production also moved, tohttps://ck.dev.crowdedkingdoms.comandhttps://ck.prod.crowdedkingdoms.com; the previous hosts,api.dev.crowdedkingdoms.comandapi.crowdedkingdoms.com, no longer resolve, so a project left on Dev or Production with no Custom Discovery URL could not reach the backend. This release fixes that and addshttps://ck.test.crowdedkingdoms.comfor the new Test tier. Separately, a project's Backend used to default to Production until you ran Config Sync; it now defaults to the tier this SDK build was released for (Dev on a dev build, Test on a test build, Production on the public release), and the Discovery URL default follows the same tier instead of always pointing at Dev. Each tier also vendors its own CrowdyCPP build (0.42.1), so a connection opened with no origin configured at all dials that tier's host directly; a released build dialsck.prod.crowdedkingdoms.com. What you do: nothing if yourDefaultGame.inialready names anEnvironment, which it does for any project that has used the Backend selector; that value is unaffected. A brand-new project now connects out of the box on whichever tier's build you are running, instead of defaulting to a Production host you may not have an app on. A new static C++ read,UCrowdySDKDeveloperSettings::GetReleaseEnvironment(), returns the tier a build defaults to; it is not exposed to Blueprint. See Project settings and Installation.
2026-09-22 (SDK v2.15.0)
Six changes. One is a source break for C++ projects; two remove work you may have done; one moves the moment a player pawn gets its identity; one changes how inbound traffic is signed and needs nothing from you; one is a Crowdy Studio panel. The examples on this site are written for 2.15.0.
-
Crowdy Studio shows every value a live model holds. The Live tab's one-line summary, which named the first few attributes and then said "and N more", is now a panel under the instance list: one row per attribute with its value, what it holds, and its description, plus the attributes the model declares that the live model has no value for, a search box over names and values, Copy value and Copy values, and an Internal switch for the runtime's own keys. The page's Pre-seed containers strip hides itself while the Live tab is open, so the list and the panel get its height. What you do: nothing; it is an editor panel.
-
Inbound bundles arrive with one signature per datagram. The transport now tells the replication server what it can read (a
CLIENT_CAPABILITIESmessage, sent once the connection is up and every 15 seconds after), and a server at v0.30.0 or later answers by packing its notifications intoMESSAGE_BUNDLE_SIGNEDdatagrams: one HMAC over the whole bundle instead of one per member. The messages your handlers receive are the same; only the verification cost per datagram drops. Up to and including 2.14.0 every member carried and was checked against its own signature, and an older server still does that. What you do: nothing.crowdy.net.recv.signedbundles 0turns the advertisement off for a diagnostic comparison, read when a connection opens likecrowdy.net.send.bundle. See Connection and reconnect. -
Eighteen array delegates pass
const TArray<T>&instead ofTArray<T>by value. Ten single-cast:FOnTeamsSuccess,FOnTeamMembersSuccess,FOnTeamRolesSuccess,FOnMyTeamsSuccess(teams),FOnAvatarsSuccess,FOnAppStatesSuccess(avatars),FOnChannelsSuccess,FOnChannelMembersSuccess,FOnChannelRolesSuccess,FOnMyChannelsSuccess(channels). Three multicast cache events a 2.1 project is likely to have bound withAddDynamic:FOnMyTeamsCacheChanged,FOnMyAvatarsCacheChanged,FOnMyChannelsCacheChanged. Five Game Model outcome events, for a project that adopted collections or sessions on 2.13 or 2.14:FCrowdyCollectionItemsOutcome,FCrowdyTraverseOutcome(both parameters),FCrowdyContainerRefsOutcome,FCrowdySessionsOutcome,FCrowdySessionEventsOutcome. What you do: on 2.15.0, any C++UFUNCTIONbound to one of them changes its parameter fromTArray<FCrowdyTeam> Teamstoconst TArray<FCrowdyTeam>& Teams, and likewise for the rest. A Blueprint custom event bound to one of these pins is unaffected; Blueprint has no by-value versus by-reference distinction on the pin.
A C++ project that takes 2.15.0 and rebuilds gets a compile error naming the handler's parameter. Nothing changes silently: if the project builds, it is right. On 2.14.0 and earlier the delegates pass by value, so a handler changed before you update fails to bind for the same reason.
- The actor pool draws with no Backend Config at all. Up to and including 2.14.0, a map profile whose Backend Config was empty, or whose config named no Replication Policy Class, left remote Dynamic entities tracked but never rendered, so choosing a Backend Config was mandatory in practice. In 2.15.0 a missing config falls back to a built-in transform-interpolation policy,
UCrowdyTransformRepPolicy, and that step is optional. What you do: nothing. A project that authored its own Backend Config is unaffected; a project that left it empty and worked around the blank screen can delete the workaround once it is on 2.15.0. See Map profile and Rendering backends. - The shipped transform policy interpolates across a ring of samples. Up to and including 2.14.0,
UCrowdyTransformRepPolicykept two samples per proxy: it snapped to a lone sample, blended between the two it held, and stopped at the newer one when the render time passed it, so a late update showed as a pause and the next as a jump. In 2.15.0 it keeps a ring of recent samples per proxy inTInterpolatedField, samples the pair that brackets the render time, and extrapolates from the last two for at most 0.2 seconds when the render time runs past the newest. What you do: nothing. Proxies drawn by the shipped policy move more smoothly through late and lost updates. A project that wrote its own policy to get that behavior can drop it, or rebuild it onTInterpolatedFieldand keep its extra fields; see Rendering backends. - A Player Derived pawn possessed after it begins play gets its account identity on possession, respawned pawns included. Up to and including 2.14.0,
UCrowdyEntityComponentread the Player Derived identity once, inBeginPlay, and the engine possesses a pawn spawned during play (RestartPlayer, a respawn, a spawn from a connected handler) only after that, so every runtime-spawned player pawn registered under a random id with one warning, and whatever is keyed on the local player id (the host lookup, ownership requests, the drop of your own echoed events) never worked for it; only a pawn possessed beforeBeginPlaygot the account identity. In 2.15.0 a Player Derived pawn with no controller atBeginPlaywaits, and registers under the account id inside its first possession, after the pawn's own Possessed and Controller Changed events; the first controller of any kind completes it (an AI controller gets the old fallback and warning), a later unpossess or repossess changes nothing, and a respawn that leaves the earlier pawn alive hands the id to the new pawn and clears the old one, which stops replicating. What you do: read the pawn's id andIsLocallyOwned()fromOnCrowdyOwnershipAssigned(On Crowdy Ownership Assigned) or the entity subsystem'sOnEntityRegistered, not fromBeginPlayor Possessed: on 2.15.0 a pawn graph that gates on Is Locally Owned atBeginPlayreads false and runs nothing, where on 2.14.0 it ran with the random id. A pawn that begins play before the sign-in has answered is unchanged: it derives from user id 0 without a warning, so keep the spawn behind On UDP Connection Success. See Quickstart and Entities, Identity, and Ownership.
2026-09-17 (SDK v2.14.0)
Everything in 2.14.0 is additive. No game-facing function, pin, or type was renamed or removed, so a project on 2.0 / 2.1 has no required action for this release. Four things are new.
- Sessions can seed from app-scoped rows.
FCrowdyGameModelCreateSessionOptionsgainedSeedFromAppTypeNames(container types whose keyed app-scoped rows are copied into the new session as it is created) andSeedInitialState(ECrowdySessionSeedState:DefaultsorApp, sent only when the type list is non-empty). The create response carriesSeededContainerCount, valid whenbHasSeededContainerCountis set. Create Game Session exposes all of them. Each seeded type must be admin-instantiable or carry a bind policy; the whole create is refused otherwise. - Container states pull in bulk. The subsystem reads many containers in one call, up to 500 per request, chunked automatically past that, for a caller that already holds the id set. It is what Get Collection With Items' State uses instead of one pull per item.
- A container type can be app-scoped.
meta=(CrowdyContainer="X", CrowdyScope="App")declares one row per key for the whole app, bound with no session. The default is session-scoped and unchanged. An app-scoped type never takes a session id; naming one on it is refused with the codeCONTAINER_TYPE_APP_SCOPED, which Get Last Crowdy Model Error Code returns. Worth doing on a project that has a shared, admin- or player-owned container every client used to race to create: declare itCrowdyInstantiableBy="Admin"and pre-seed it from Crowdy Studio. - The active session survives opening a new map. Set Active Crowdy Session (
SetActiveSession) now persists on the Game Instance, and a new map reads it back when it loads. A Leave or End of the active session forgets it, as does Clear Active Crowdy Session (ClearActiveSession). Before 2.14.0 a fresh map never inherited a session. What you do: when a match ends, leave or end its session, or call Clear Active Crowdy Session, before you open the next map.
A game that never clears the active session between matches loads its next map with placed shared containers still bound into the ended match's session instead of the app-global scope. Reads return the stale session's rows, and writes go there. Leave, end, or clear on the way out of every match.
2026-09-15 (SDK v2.13.0)
Game Model sessions are new. The July SDK had no game-models section and no session concept; 2.13.0 adds both. UCrowdyGameModelSubsystem creates, joins, leaves, ends, locks, and transfers host on a session (CreateSession, JoinSession, LeaveSession, EndSession, SetSessionAdmission, TransferSessionHost, WatchSession), with presence per participant and a typed change stream on On Game Session Changed (OnSessionChanged). The Blueprint category is Crowdy SDK, Game Model, Sessions & Turns.
- What changed: authoritative, cheat-sensitive state (HP, inventory, scores, match rules) has a server-owned home, a Game Model container, and a match has a server-owned lifetime, a session. Neither existed in the SDK you integrated. High-frequency view state (movement, animation) stays where it was, in CrowdyState; the two do not merge.
- Session words are enums.
FCrowdyGameModelSession::Statusis anECrowdySessionStatus; admission isECrowdySessionAdmission; presence isECrowdySessionPresence; the reason a session ended isECrowdySessionEndReason, and the reason a participant left isECrowdySessionLeftReason. Each has a matching filter enum where a query takes one (ECrowdySessionStatusFilter,ECrowdySessionAdmissionFilter). In Blueprint, a Switch on ECrowdySessionStatus branches on a status; there is no string to compare. - What you do otherwise: nothing is required. A project that keeps its gameplay state in its own code keeps working.
2026-09-15 (SDK v2.12.0)
Outbound messages are bundled. Several messages the SDK sends in the same pass now travel in one datagram instead of one datagram each. The switch is the CVar crowdy.net.send.bundle, default 1, read when a connection opens. No API changed and nothing you send looks different to your handlers.
- What you do on a current replication server: nothing.
- What you do on a replication server older than v0.27.0: upgrade the server, or set
crowdy.net.send.bundle 0inDefaultEngine.ini([ConsoleVariables]) until you can. An older server does not unpack a bundle, so a bundled pair is dropped without an error. If reliable events start arriving in ones and not in pairs after the update, this is the first thing to check. See Console CVars.
2026-08-30 (SDK, sign-in)
The dev-only sign-in bypass is gone. UCrowdyAuthentication::DevLogin, email-only sign-in against a server flagged for it and never usable in production, is removed from the SDK; the server stopped accepting it on 2026-08-20. It does not exist as a deprecated stub; a call to it does not compile.
- What changed:
Login(Email, Password, ...)andRegister(Email, Password, ...)work against every tier, including a local or development server, and are the replacement for any scripted sign-in. Magic link (BeginMagicLinkSignIn) and social sign-in (BeginSocialSignIn) are unchanged. - What you do: replace each
DevLogincall withLoginorRegisterand supply a real password. For a headless test client, register a dedicated account once and store its password the way you store any other secret, never in the project. - Email + password is first-class and permanent. If the July pages left you expecting passwordless sign-in to replace it, they should not have: password sign-in sits beside magic link and social as an equal method and has no removal planned. Nothing about it changed between 2.1 and 2.14. See Authentication.
Switch it to Login or Register with a real password before you update the plugin, not after. The failure is a compile error in your code, so a job that builds the project fails before it ever signs in.
Between 2.1.0 and 2.12.0 (SDK)
Changes that shipped between the July plugin and v2.12.0. A project moving from 2.1 to any later release meets all of them at once.
- Teams types are named for teams, not groups.
FCrowdyGroup,FCrowdyGroupMember,FCrowdyGroupMembership,FCrowdyGroupRole,FCrowdyRolePermissionsandFCrowdyAppGroupPolicyare nowFCrowdyTeam,FCrowdyTeamMember,FCrowdyTeamMembership,FCrowdyTeamRole,FCrowdyTeamPermissionsandFCrowdyTeamPolicy, and their members followed:GroupIdtoTeamId,GroupMemberIdtoTeamMemberId,GroupRoleIdtoTeamRoleId,Membership.GrouptoTeam,bManageGrouptobManageTeam,MaxGroupsPerUsertoMaxTeamsPerUser,FCrowdyTeamsResult.GroupstoTeams,FCrowdyChannelsResult.GroupstoChannels, and the enumeratorECrowdyTeamPermission::ManageGrouptoManageTeam. What you do: in C++, rename; a handler such asOnCacheChanged(TArray<FCrowdyGroupMembership>)or a read ofTeam.GroupIdno longer compiles. Blueprint assets follow the plugin's own CoreRedirects and need only a resave, except a node whose function parameter was renamed (GroupIdtoTeamIdon theUCrowdyTeamscalls): open that graph once and reconnect the renamed pin. See Teams and Packaging. SetManagementApiUrlis gone;SetDiscoveryUrlreplaces it.UCrowdySDKSubsystem::SetManagementApiUrlhas no counterpart and no redirect. What you do: callSetDiscoveryUrlfor the shared origin, orSetGameApiUrlfor the gameplay endpoint; in Blueprint the old node is missing on load and is replaced by hand. See Project settings.- The query and subscription subsystems are gone.
UCrowdyQuerySubsystemandUCrowdySubscriptionSubsystem(theirConnect,ExecuteQuery,GetSubscriptionStatecalls andOnConnected,OnDisconnected,OnGetVersionInfo,OnTeleportevents) do not exist. GraphQL now goes through the service subsystems. What you do: remove the calls; a Blueprint that used them has missing nodes on load. The services pages (Teams, Avatars, Authentication) are where the same data comes from now. - The C++ reception-layer interface is gone.
ICrowdyReceptionLayerandUCrowdySDKSubsystem::RegisterReceptionLayer/IsLayerRegisteredare removed. What you do: a C++ system that implemented the interface subscribes instead:UCrowdySDKSubsystem::SubscribeToEventPayload,SubscribeToActorUpdatePayload,SubscribeToAllPayloadsandSubscribeToOpcodereturn anFCrowdySubscriptionhandle that delivers for as long as it lives. The Blueprint Crowdy Blueprint Reception Layer is unchanged; see Recipients and routing. - The connection monitor is no longer part of the documented path.
UCrowdySDKSubsystemretries a dropped UDP connection on its own and broadcasts On UDP Timed Out when a re-assignment failed and On UDP Connection Success when the connection is back. A project that also callsInitConnectionMonitorruns a second, redundant retry loop beside the SDK's. Five functions onUCrowdySDKSubsystemare deprecated. Three do nothing:StartUDPTimeoutMonitoring,TriggerUdpHeartbeat,DeregisterAllReceptionLayers.StopUDPTimeoutMonitoringonly stops the latency ping, andSetQueryEndpointforwards toSetGameApiUrl. What you do: bind the two events in your Game Instance'sInit, remove every call toInitConnectionMonitorand to the first four, and replaceSetQueryEndpointwithSetGameApiUrlorSetDiscoveryUrl. The compiler warns on each deprecated call, so a build log lists them for you. See Connection and reconnect. - A project that configures no map profile now runs networked. The plugin registers a shipped default profile, so a map with no
MapProfilesentry and noDefaultProfileresolves to it: networking on, the actor pool as backend. In 2.1 such a map went inactive. What you do: nothing if you have profiles; if you relied on an unlisted map being inactive, give it a profile with Enable Networking off. See Map profile.
Persistence
UCrowdyPersistenceSubsystem is deprecated in these docs. The push / pull of tagged gameplay structs over UDP and GraphQL that the July services/persistence page described still exists in the SDK, with its three callables PushState, PullState, and ClearAllState (and the Blueprint Push State / Pull State wildcards), and it keeps working in 2.14. It receives no further development, and no removal date is set. The plugin does not mark it, so no compiler or Blueprint warning appears. The two struct tags it reads, CrowdyPersistent (push and pull this struct) and CrowdySingleton (one slot, not one per instance), are still honored and go with it.
- What you do: nothing today. When you touch that code next, move the state into your app's server code on ck-exec, which is what persistence was standing in for. Authoritative and cheat-sensitive state belongs on the server, never in CrowdyState; see The Two Planes.
- What not to do: do not start a new feature on
PushState. Server code gives you the same durable, per-subject state plus server-side validation, and it is where the effort goes.
Gotchas
- For a C++ project on 2.14.0 the source breaks are the teams rename, the removals listed under "Between 2.1.0 and 2.12.0", and
DevLogin. If your project builds clean against 2.14.0 after those, nothing else here is blocking; the array delegates come with the next release. - A Blueprint project's exposure is the renamed
GroupIdpins on the teams calls, the missingDevLogin,SetManagementApiUrl, query and subscription nodes on load, the persistence nodes (still present, no warning) and the connection-monitor nodes (still present, marked deprecated). crowdy.net.send.bundle 0is a compatibility switch for an old server, not a debugging aid. Leave it at1on a current server.- The sample project targets SDK 2.1.0 and predates every entry on this page; its tour is a 2.1 tour.
Related
- Authentication: every sign-in method that exists now.
- Connection and reconnect: the two events that replace the connection monitor.
- Map profile and Rendering backends: the optional Backend Config.
- Teams, Avatars, Channels: the services whose types were renamed and whose array delegates change next release.
- Recipients and routing: the Blueprint reception layer that survives the C++ interface's removal.
- Console CVars:
crowdy.net.send.bundleand the rest.