Skip to main content

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.18.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), and, in 2.15.0, the array delegates.

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 CrowdyModel and CrowdyContainer markers 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, UCrowdyGameModelSubsystem and the libraries UCrowdyModel, UCrowdyGameModel, UCrowdyModelValue and UCrowdyEffects. 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." (code GAME_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. A UCrowdyServerObjectDefinition data asset describes one type: the struct the server keeps, the fields players may watch, and the Server Functions they may call. UCrowdyServerObjectSubsystem::Acquire hands out a shared UCrowdyServerObject per definition and Instance Id, held for its owners and given back 10 seconds after the last one is gone; WatchValues reports each server change once, naming every field it touched; Call sends a params struct and completes once with an FCrowdyServerCallResult whose ECrowdyServerCallOutcome says what happened, after the SDK has retried a busy server; a call the server may already have run is never repeated for you. Log category LogCrowdyExec. What you do: nothing unless you adopt it. To adopt it, add CrowdyExec to your module's Build.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 UCrowdyServerObjectDefinition asset in the Content Browser and choose Generate Server Code. The editor checks the definition, then writes a Rust crate for ck-exec under Server/<Type Name>/ in your project: Cargo.toml, src/types.rs (your structs and enums, with their Unreal defaults) and src/lib.rs (the glue that answers and publishes the watched values, applies Owner Only and Who Can Call, refuses with unknown_method, denied or bad_params, and saves and loads the state) are rewritten every time; src/logic.rs, where you implement the Functions trait 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 .rs file, for example one shared in your repo, Generate Server Code writes only Cargo.toml, lib.rs and types.rs and never creates or overwrites your file, and Server Compute's Deploy sends your file as the type's logic.rs. Generated uses Server/<Type Name>/src/logic.rs. What you do: nothing unless you use Server Objects. If you do, generate each type's crate, write its logic.rs, commit Server/<Type Name>/ (without target/), 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 -yes does the same for CI, with -op=status, versions, starters, activate, disable, enable, logs and stats alongside 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 -yes to 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 UCrowdyServerObjectDefinition now 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_VillageBeacon becomes village_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 with denied, 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 its Server/<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 in Config/DefaultEditor.ini. From the command line, -op=changes reports the same comparison as JSON (liveVersion, types, problems, ready, hasChanges), and -op=deploy records revisions and lists any it could not save under recordErrors. What you do: nothing unless you use Server Objects. If you do, commit revisions.json with each type's crate (and Config/DefaultEditor.ini if you change the setting) so your teammates see the same states, and in a CI job that deploys, commit the changed revisions.json files 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++ properties StateForm/StateList, ParamsForm/ParamsList and ReplyForm/ReplyList, the Lists being FInstancedPropertyBags). 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 plus State (village_beacon gives VillageBeaconState) and a function's Lists <Function>Params and <Function>Reply, the names the structs usually have, so logic.rs does 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::MakeParams hands out a function's params List with its starting values to set by name and pass to a new Call overload taking an FInstancedPropertyBag, CrowdyExec::ToList(Result.Reply) (in CrowdyExecCodec.h) reads a List reply by name, and GetStateList() returns a List State's watched values; passing a struct to a function that takes a List completes as BadRequest. 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's fn in logic.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 CrowdyExec module 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 no Build.cs entry. 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's ClampMin and ClampMax read the same), refused as bad_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 in logic.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.rs gets members::..., timers::start and timers::stop, and calls::<type>::<function>. A refusal denied: reaches the caller as the Denied outcome (it was a ServerError) and cooldown: as Denied with bRetryable. 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), and UCrowdyServerObject gains GetMembers, GetMemberCount, GetLeader, IsOpenForJoining, IsMember and IsLeader, with changes named Members, MemberCount, Leader and OpenForJoining. 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 a ServerError starting with denied should 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>) and On <Variable> Changed (<Asset>), a function's name offers Call <Function> (<Asset>), and each asset offers Get 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, int16 and uint16 to Integer, uint32 and uint64 to Integer64, and a TOptional to 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 not BlueprintType, 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 Value pin (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 a uint32, 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 plus Has Value) gets no node, and the compile says to rename it. An On <Variable> Changed whose Bind is not connected gives a compile warning. On <Variable> Changed takes 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, a Hit function that players may call with a 0.25 second Cooldown and a Value Range of 1 to 25 on its Damage, a Respawn timer that is After 10 seconds and not started automatically, the logic.rs that 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. CrowdyExec gains the trace console variable crowdy.exec.trace, off by default, with the lines logged under LogCrowdyExec and starting exec:. 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, reconnect or ownerOnly), 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, enter crowdy.exec.trace 1 in 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 sees ServerError with a reason such as the 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.rs now records each such value's id in its comment.
    • Changing a Type Name finds the old folder. Each generated Cargo.toml now records its definition asset, and Generate Server Code offers to move the folder generated under the old name, keeping your logic.rs. The record stays on your machine: a deploy sends Cargo.toml without 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 with unreadable and 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.toml and types.rs; every type shows as changed once on Server Compute. See After you change the definition and Troubleshooting.
  • 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 named CSO_NewServerObject; Add > Miscellaneous > Data Asset still works. The Type Name suggestion drops CSO_, or DA_ for older assets, so CSO_VillageBeacon suggests village_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> Changed node 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.rs is written once, so a Server Function, timer or event added later reached only the Functions trait and the crate stopped compiling. Generate Server Code now names what logic.rs lacks and asks "Add empty versions of the missing functions?"; Yes adds them before the closing brace of your impl Functions block (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 timers module has one constant per timer, its function's name in capitals: timers::END_MATCH holds "EndMatch". Write timers::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 be QUEUED is refused at Generate.
    • What you do: generate again; lib.rs changes for types with timers. Switching your timers::start and timers::stop calls 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 to CSO_ 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.
  • 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 and OnMyTeamsCacheChanged fires 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++ gains ClearMyTeamsCache() (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 any logic.rs that 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.
  • Actor updates carry their payload type id. FCrowdyActorUpdate gains PayloadTypeID, 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 own Build.cs. Separately, the CrowdyCppBridge module's build no longer prints a precompiled-header warning or the two CS0618 obsolete-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 FCrowdyCppClient are compiled only with automation tests. MakeForTest, GetLastTestRequest, GetLastTestRequestBody, SetTestStampsHandOff, SetTestResponseScript, SetTestOnRequest, and the Test*WebSocket* helpers, and FCrowdyCppLoopback::SetTimeForTests / ClearTimeForTests, now exist only when WITH_DEV_AUTOMATION_TESTS is set, never in Shipping. What you do: guard any code that calls them behind the same WITH_DEV_AUTOMATION_TESTS check.

  • 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 Game Model entries below no longer reach a server.

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 DefaultPoolSizePerClass actors (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 new MaxPoolSizePerClass on 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 a crowdy.pool.trace line). 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 optional UCrowdyRenderingBackend::IsInstanceActive. What you do: nothing; raise MaxPoolSizePerClass if 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 EndPlay also 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.trace now logs the success path too: "Spawn dispatched", "Spawn received" and "Entity registered", each with the id, class and location. crowdy.pool.trace logs each activation, each pool growth step, and each entity waiting at the cap. crowdy.net.trace logs 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 alice becoming Alice was never applied: the member kept the old spelling and no CrowdyOnRep or 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.stats gains SelfEchoMarksExpired and SelfEchoBackstopPulls, and SelfInvokesMarked now 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, and crowdy.gamemodel.stats gains BulkResolveListPages. 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, and PullDataContainer's OnDone fires 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.stats gains PullsHeldBehindInFlight, FollowUpPulls, and StaleKeysSkipped, printed as pulls held behind in flight N follow-ups N stale keys skipped N; OverlappingPulls now 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 polling PullNow in 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 ListContainers page, or any other SDK query) resends on any refusal the platform blames on itself and marks retryable, except WRONG_DATACENTER (the SDK already follows that redirect) and APP_UNAVAILABLE (nowhere else to serve it). A container ensure and a gameModelInvoke resend only on PLATFORM_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.stats gains Retries / RecoveredByRetry per operation and an invoke busy retries N recovered M gave up K line (InvokeBusyRetries / InvokeBusyRecovered / InvokeBusyGaveUp on UCrowdyGameModelSubsystem::GetNetStats); a failure reason now counts only the final outcome, not each retried attempt. UCrowdyGameModelSubsystem::IsBusyRefusal and FCrowdyCppJsonResult::Blame / bRetryable / RetryAfterMs are 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) sets IHttpRequest's HTTP version to 2TLS, falling back to HTTP/1.1 when the server does not offer it; plain http:// stays HTTP/1.1 regardless. Only the SDK's own requests are affected: the game's other HTTP traffic and the engine's http.CurlAllowHTTP2 are 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 at Warning with the request headers, bearer token included, in any build with logging enabled (a default Shipping build compiles logging out); set crowdy.net.http2 0 if you ship or collect logs from a build with logging enabled. Faster delivery also means a burst reaches the server together, so expect more PLATFORM_BUSY refusals under heavy bursts; the SDK retries those on its own, see the busy-retry entry below. See Console variables and Change pings and pull.
  • ListContainers now 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 (bOk false, no partial list), and a row the server returns on two pages comes back once. For an app-scoped type, SessionId is now ignored the same way create and ensure already ignore it. FCrowdyCppClient::ListContainers at 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.stats splits 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 gains SdkP50Ms / SdkP95Ms (request created to handed off to the engine's HTTP module) and HttpP50Ms / 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's failures: CODE xN, CODE xM) counts each failure under the server's error code, or graphql, http <status>, rejected, canceled, or transport when there is none, up to eight reasons per op with the rest under other. Transport totals gain InFlight and PeakInFlight; net stats gain InvokesDispatched. 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.stats logs 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.reset zeroes it. Both are new console commands, stripped from Shipping builds like the existing watch commands. The same numbers are on UCrowdyGameModelSubsystem in 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. ECrowdyEnvironment adds 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, to https://ck.dev.crowdedkingdoms.com and https://ck.prod.crowdedkingdoms.com; the previous hosts, api.dev.crowdedkingdoms.com and api.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 adds https://ck.test.crowdedkingdoms.com for 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 dials ck.prod.crowdedkingdoms.com. What you do: nothing if your DefaultGame.ini already names an Environment, 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_CAPABILITIES message, 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 into MESSAGE_BUNDLE_SIGNED datagrams: 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 0 turns the advertisement off for a diagnostic comparison, read when a connection opens like crowdy.net.send.bundle. See Connection and reconnect.

  • Eighteen array delegates pass const TArray<T>& instead of TArray<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 with AddDynamic: 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++ UFUNCTION bound to one of them changes its parameter from TArray<FCrowdyTeam> Teams to const 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.

This is a source break, not a behavior change

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, UCrowdyTransformRepPolicy kept 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 in TInterpolatedField, 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 on TInterpolatedField and 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, UCrowdyEntityComponent read the Player Derived identity once, in BeginPlay, 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 before BeginPlay got the account identity. In 2.15.0 a Player Derived pawn with no controller at BeginPlay waits, 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 and IsLocallyOwned() from OnCrowdyOwnershipAssigned (On Crowdy Ownership Assigned) or the entity subsystem's OnEntityRegistered, not from BeginPlay or Possessed: on 2.15.0 a pawn graph that gates on Is Locally Owned at BeginPlay reads 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. FCrowdyGameModelCreateSessionOptions gained SeedFromAppTypeNames (container types whose keyed app-scoped rows are copied into the new session as it is created) and SeedInitialState (ECrowdySessionSeedState: Defaults or App, sent only when the type list is non-empty). The create response carries SeededContainerCount, valid when bHasSeededContainerCount is 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 code CONTAINER_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 it CrowdyInstantiableBy="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 map loaded after a finished match binds into that match unless you clear the session

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::Status is an ECrowdySessionStatus; admission is ECrowdySessionAdmission; presence is ECrowdySessionPresence; the reason a session ended is ECrowdySessionEndReason, and the reason a participant left is ECrowdySessionLeftReason. 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 0 in DefaultEngine.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, ...) and Register(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 DevLogin call with Login or Register and 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.
A build script or CI job that signs in with DevLogin breaks at the next SDK update

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, FCrowdyRolePermissions and FCrowdyAppGroupPolicy are now FCrowdyTeam, FCrowdyTeamMember, FCrowdyTeamMembership, FCrowdyTeamRole, FCrowdyTeamPermissions and FCrowdyTeamPolicy, and their members followed: GroupId to TeamId, GroupMemberId to TeamMemberId, GroupRoleId to TeamRoleId, Membership.Group to Team, bManageGroup to bManageTeam, MaxGroupsPerUser to MaxTeamsPerUser, FCrowdyTeamsResult.Groups to Teams, FCrowdyChannelsResult.Groups to Channels, and the enumerator ECrowdyTeamPermission::ManageGroup to ManageTeam. What you do: in C++, rename; a handler such as OnCacheChanged(TArray<FCrowdyGroupMembership>) or a read of Team.GroupId no longer compiles. Blueprint assets follow the plugin's own CoreRedirects and need only a resave, except a node whose function parameter was renamed (GroupId to TeamId on the UCrowdyTeams calls): open that graph once and reconnect the renamed pin. See Teams and Packaging.
  • SetManagementApiUrl is gone; SetDiscoveryUrl replaces it. UCrowdySDKSubsystem::SetManagementApiUrl has no counterpart and no redirect. What you do: call SetDiscoveryUrl for the shared origin, or SetGameApiUrl for 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. UCrowdyQuerySubsystem and UCrowdySubscriptionSubsystem (their Connect, ExecuteQuery, GetSubscriptionState calls and OnConnected, OnDisconnected, OnGetVersionInfo, OnTeleport events) 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. ICrowdyReceptionLayer and UCrowdySDKSubsystem::RegisterReceptionLayer / IsLayerRegistered are removed. What you do: a C++ system that implemented the interface subscribes instead: UCrowdySDKSubsystem::SubscribeToEventPayload, SubscribeToActorUpdatePayload, SubscribeToAllPayloads and SubscribeToOpcode return an FCrowdySubscription handle 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. UCrowdySDKSubsystem retries 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 calls InitConnectionMonitor runs a second, redundant retry loop beside the SDK's. Five functions on UCrowdySDKSubsystem are deprecated. Three do nothing: StartUDPTimeoutMonitoring, TriggerUdpHeartbeat, DeregisterAllReceptionLayers. StopUDPTimeoutMonitoring only stops the latency ping, and SetQueryEndpoint forwards to SetGameApiUrl. What you do: bind the two events in your Game Instance's Init, remove every call to InitConnectionMonitor and to the first four, and replace SetQueryEndpoint with SetGameApiUrl or SetDiscoveryUrl. 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 MapProfiles entry and no DefaultProfile resolves 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 GroupId pins on the teams calls, the missing DevLogin, 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 0 is a compatibility switch for an old server, not a debugging aid. Leave it at 1 on 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.