API changelog
Notable, consumer-facing changes to the public Crowded Kingdoms APIs (Management API, Game API, Replication API) and SDKs. Newest first.
The intent is that a breaking change ships with a deprecation window — the field keeps
working and is marked @deprecated in the schema (visible in the
reference and the downloadable SDL) until
a stated removal date. Four removals did not get one, and each is called out in
its own entry rather than left to be discovered: the customer-provisioned
environment surface on 2026-07-27, the dev sign-in bypass on
2026-08-20, the game API's legacy engines on
2026-09-28 on the dev environment, which test
followed on 2026-10-04 and production on 2026-10-06, and Crowdy Games hosting on
2026-10-29 on every
environment. Treat the published SDL as the authority on what exists today; this page
is the record of how it got there.
Older entries describe the game API's legacy engines (game models, automations, compute modules and player code), which ck-exec replaced. Their links lead to the matching section of from the legacy engines, which maps what you used to what you use now.
crowdy-compute was never publicly distributedSeveral entries below announce a crowdy-compute CLI. It was an internal
convenience and was never published. The entries are left as written because
this page is a historical record. The compute modules it deployed were replaced by
ck-exec, which builds and deploys through the game API (execBuild, execDeploy); its
own CLI is not published either. See
from the legacy engines. To build and test
on your own machine, use the open dev kit.
2026-11-02 (Unreal SDK v2.24.0: spawned entities appear once; MIGRATION.md)
Per-item detail: What's Changed. No breaking change and nothing deprecated.
- Spawned entities appear once on every client, keep their spawn data in either arrival order, and reach players who join later: a Static entity's owner repeats its spawn every 4 seconds. The owner no longer draws a pooled copy of its own entity unless Render Local Owner is set, and Destroy Crowdy Entity stops sending at once. See Entities and spawning.
- No phantom host after sign-out: Is Host Set is false and Get Host ID is invalid once the player signs out. See Host Election.
- Packaged installs: the plugin zip carries the SDK's
Config/folder and its CoreRedirects, and the plugin builds with UAT BuildPlugin again. MIGRATION.mdships at the plugin root: for each release from 2.24.0, what changed and the exact steps to migrate.
2026-11-04 (Crowded Kingdoms admits players from 13)
Production opened to players aged 13 to 17 on 2026-11-04: Game API v2.61.1, replication server v0.43.0 and ck-exec 0.20.0, with CrowdyJS 18.25.0, CrowdyCPP 0.76.0 and CrowdyPy 0.24.0. See Players under 18 and player safety.
- Documents: the platform's player documents are the
Player Terms and the
Player Privacy Policy
(
player-terms-2026-11-04,player-privacy-2026-11-04), which every player accepts at their next mint; the age-of-majority attestation is ignored. Developers accept the developer documents before creating or administering an organization, and only adults may. Organizations accept the new Org-Managed Players Addendum. See Legal documents. - Age: the youngest player is 13. A browser sign-up gives a birth date and a country
(
AGE_REQUIREDwithout), and an organization gives each player'sageYearsandcountry. See Age, country and who may play. - Players under 18 get the minors' defaults (voice and video off until a parent consents, then contacts only; no direct messages), in apps that keep the safety log and have appointed a moderator.
- Not yet in production: parental consent. Kids Web Services is not available there yet:
requestParentalConsentanswersPARENTAL_CONSENT_UNAVAILABLEwithextensions.reasonnot_configured, so a player under 18 cannot turn voice or video on there yet. - Game API v2.61.1:
PARENTAL_CONSENT_UNAVAILABLEcarriesextensions.reason(not_configured,provider_refused,provider_unavailable); after an age refusal, a client that sendsx-ck-device-idis locked by that device for 24 hours, and one that sends none by its address and browser for an hour; a player under 18 is refusedSAFETY_LOG_REQUIREDorSAFETY_MODERATOR_REQUIREDwhen Studio's hosted sign-in issues the code, as at the mint. - SDKs: CrowdyJS 18.25.0 adds
safetyModeratorRequiredOf, thereasononparentalConsentUnavailableOf(err),hasSafetyModeratoron app reads,regionCodeon the territory list andregionin an organization's assertion claims; CrowdyCPP 0.76.0 and CrowdyPy 0.24.0 the same, their consent readers returning the refusal with its reason instead of a boolean. - The Minecraft server page is unpublished until the server mod's release is public.
2026-11-02 (dev: the safety baseline completed; the realtime rules; the SDKs)
On the dev environment: Game API v2.61.0, replication server v0.43.0, CrowdyJS 18.24.0, CrowdyCPP 0.75.0 and CrowdyPy 0.23.0. See Players under 18 and player safety.
- The realtime servers enforce players' settings, contacts and blocks (v0.43.0), on the
sending and the receiving side. Behaviour change: a player now needs
use_voice_chat,use_video_chat,use_text_chatoruse_direct_messagesto receive that medium too, not only to send it. A block stops communication between two players (voice, video, text, direct and channel messages, a player's events and app-defined messages); movement and world edits still pass. NewUNAUTHORIZEDrefusals: voice or video with its send setting off, a minor's direct message, a send under another player's actor. Each voice and video switch of the safety log now records only its medium. See Voice, video, contacts and blocks. - Age refusals name no age (
AGE_REQUIREMENT_NOT_METcarries no extensions), and after one the same client, Minecraft account or managed player is refused for 24 hours. An account that declares an age under 13 is deleted and erased. See Age, country and who may play. - Countries checked by region:
territoryPolicieslists regions (regionCode), a managed player there needsregion, andTERRITORY_NOT_SUPPORTEDcarriesextensions.region. Crowded Kingdoms staff pass the country check at the mint. SAFETY_MODERATOR_REQUIRED: a player under 18 is admitted only to an app whose organization has appointed a moderator, a member holding a role withmoderate_safety(the admin role alone does not count);App.hasSafetyModeratorsays whether it has. See Moderators.- Who reads the safety log on an app that admits players under 18: moderators, only the
sessions a report covers; each player their own; every read audited. A minor's sessions stay
out of
manage_appsreads on any app. See the safety log. - Crowded Kingdoms receives every
CHILD_SAFETYreport, across all games. - The SDKs wrap the program: CrowdyJS
client.safety,client.moderation,apps.recording, the age and document calls and the refusal readers; CrowdyCPP the same, andcrowdy::startGameSession(token, options)to start a session from any app token; CrowdyPyclient.safetyandclient.moderation. - Coming in the next Game API release:
PARENTAL_CONSENT_UNAVAILABLEsays why inextensions.reason(not_configured,provider_refused,provider_unavailable).
2026-10-31 (Unreal SDK v2.23.0: nearby text chat; how far voice reaches)
Per-item detail: What's Changed.
- Nearby text chat: Send Proximity Text reaches the players within a distance of your pawn's chunk, and On Proximity Text Received hands you their lines. Lines are plain UTF-8, shared with CrowdyJS and CrowdyCPP players. See Nearby Text Chat.
- Set Voice Chat Distance keeps voice local, from 0 to 8 chunks; the default stays 8. Voice and text are now addressed to the chunk your pawn stands in. See Voice Chat.
- Audio that is not the microphone, for C++:
FeedVoicePcmsends it through the microphone's own gate and encoder.
2026-10-30 (dev: players under 18 and player safety)
On the dev environment: Game API v2.60.0, and ck-exec 0.20.0 with ckx-sdk 0.13.0 and the dev
kit 0.6.0. Crowded Kingdoms opens to players aged 13 to 17 at one moment, once this baseline is
live in production; until then every player is 18 or older. See
Players under 18 and player safety.
- Breaking, for every player: countries Crowded Kingdoms does not serve are refused. Every
sign-up, mint and refresh checks the country the player gave, the one their connection comes
from, or the one their organization sends, and answers
TERRITORY_NOT_SUPPORTED.territoryPolicieslists them. See Countries. - Age:
registertakesbirthDate,countryandparentalConfirmation;recordPlayerAgegives an account its age once;me { ageBracket country }; managed players carryageYearsandcountry, onmintManagedPlayerTokenand as assertion claims;App.minPlayerAgeandeffectiveMinPlayerAge. New codesAGE_REQUIREDandAGE_REQUIREMENT_NOT_MET. See Age, country and who may play. - Documents: an app's own required documents (
setAppRequiredDocuments,appRequiredDocuments);playerLegalDocuments;recordPlayerConsentstakesappIdandmethod, andattestAgeOfMajorityis optional in the schema (still required until the opening);playerLegalAcceptance(appId);LEGAL_ACCEPTANCE_REQUIREDlistsextensions.missingDocuments; the developer documents (developerLegalDocuments,recordDeveloperConsents,developerLegalAcceptance), empty until the opening. See Legal documents. - Communication: voice and video settings per player and app (
myCommSettings,setMyCommSettings), contacts, blocks, andrequestParentalConsentthrough Kids Web Services;SOCIAL_LIMIT_REACHEDandPARENTAL_CONSENT_UNAVAILABLE. The replication server release after v0.42.0, not released yet, enforces them, and checks every receiving player's permission keys too: a tier withoutuse_voice_chatwill no longer hear voice. See Voice, video, contacts and blocks. - The safety log:
replayLoggingRecordsVoiceandreplayLoggingRecordsVideo(the media switch sets both),App.recordingfor the notice and the indicator, andSAFETY_LOG_REQUIREDon an app that admits players under 18. See the safety log. - Reports:
fileSafetyReport; the organization permissionmoderate_safety;safetyReports,resolveSafetyReportandescalateSafetyReport; each report preserves both players' recorded sessions, accounts and hub data; deleting a reported account sets a legal hold. See Safety reports and moderation. - Account deletion also erases the player's communication settings, contacts and blocks, and what the app's ck-exec hubs hold of them.
- ck-exec 0.20.0,
ckx-sdk0.13.0:Hub::on_player_erasedandHub::on_player_export,ctx.players().blocked,ctx.safety().reportwith the scopesafety.write,age_bracketonplayers.get, andTestHub::eraseandexport, in the dev kit from 0.6.0. See Players' data, blocks and reports. - Minecraft sign-in for Crowded Kingdoms' own apps:
minecraftChallengeandsignInWithMinecraft, withMINECRAFT_VERIFICATION_FAILEDandMINECRAFT_VERIFICATION_UNAVAILABLE. - The SDKs did not wrap the new fields yet: CrowdyJS 18.24.0, CrowdyCPP 0.75.0 and CrowdyPy 0.23.0 do (2026-11-02).
- Every new code is on Error codes.
2026-10-30 (Unreal SDK v2.22.0: voice chat on the shared voice format; no flicks after a hitch; UX fixes)
Per-item detail: What's Changed.
- Voice chat rebuilt on the voice format the CrowdyJS and CrowdyCPP SDKs share: Opus with reordering and loss concealment, no pawn needed, settings kept across maps. Every player needs 2.22.0 or later for voice: earlier versions cannot hear this one. New: Get Voice Mic State, On Voice Mic State Changed, On Voice Speaker Talking. See Voice Chat.
- Remote copies no longer flick away for a frame after their sender hitches.
- Warnings where mistakes used to fail silently, fewer noisy logs, clearer messages, one
Crowdy SDK palette category, Studio confirmations, two deprecations, and
-run=CrowdyStudio -op=registryfor checking markers from a shell.
2026-10-29 (Crowdy Games retired: host your game yourself)
Crowded Kingdoms no longer hosts games. Breaking, with no deprecation window, on every environment. Build your game and serve it from a static host of your own; the platform behind it is unchanged. See Host your game.
- Retired: the games website,
crowdy.games, on every environment; the Overworld lobby; the first-party shell that framed third-party games; the first-party games served there (Blocks with Friends, Matchstick and others); and the content host,crowdedcontent.com, that served developers' bundles. - The hosting API is switched off.
claimGameHosting,beginGamePublish,completeGamePublish,abandonGamePublish,setHostedGameEnabledandhostedGamePublishesanswerCONTENT_HOSTING_DISABLED, andhostedGame,hostedGamesandmyHostedGamesreturnnullor an empty list. The fields stay in the schema. TheHOSTED_*error codes are retired with them (error codes). CrowdyJS keepsclient.hosting,publishDirectoryandEmbeddedHost, deprecated. See Hosting on Crowdy Games (retired). - Host the build yourself, on any static host that can set response headers: the
cross-origin isolation headers, the game's Content-Security-Policy, and a separate policy
for the Studio agent's
/dsh/path. Register your origin under the app's redirect URIs in Studio (also the API's CORS allow-list) and set its launch URL. The Construct'snpm run setup -- --origin https://your.hostregisters the origin, and sets it as the launch URL when the app has none; itsnpm run publishis retired. See Host your game. - Studio's Live code opens your launch URL, the one set on the app in Studio under
Settings > Sign-in & redirect URIs: your own deployment, or, while you develop, the
template's dev server (
LIVE_DRAFT=1 npm run dev,http://localhost:5175). See the game editor. - Unchanged: the API; Crowdy Studio; hosted sign-in through Studio's
/authorizefor a game on its own origin; ck-exec world hubs and mods; and the Studio dock and agent embedded in your own game. Mods start from a game you run: The Construct locally, or a game whose developer embeds Studio. See Build mods with Crowdy Studio and what a game needs for mods.
2026-10-29 (Unreal SDK v2.21.2: cancel a sign-in without signing out; Game Kit fixes)
Per-item detail: What's Changed.
CancelSignInandIsSignInPendingstop a sign-in that has not finished (a restore, a password sign-in, an email link or social sign-in waiting for the browser, or one held for the terms) without signing out, so a cancelled restore keeps the saved session. See Cancel a sign-in.- Kit actors: Get Kit Actor Count and Get Kit Actor read them without a copy, they are placed by the session's chunk size, and malformed updates are dropped. See Show a starter's actors.
- Server Object reads: a starter's On Changed runs once at Ready, another player's Owner Values report no value, and Get Server State is not offered where nothing is visible. See Values only the player reads.
- Starters: Config ids that differ only by case are refused,
-op=callreaches a starter's read-only player functions, the director publishes less, and the zone's event carriescenterZ. See Use the Game Kit starters.
2026-10-28 (Unreal SDK v2.21.1: Blueprint sign-in from the Game Mode; a saved session per client; the platform stops removed types; CrowdyCPP 0.74.0)
Per-item detail: What's Changed.
- Blueprint sign-in starts from the Game Mode. A Blueprint Game Instance's Event Init runs before the SDK's subsystems exist, so the earlier Blueprint examples could not sign in. They now run from the Game Mode's Event BeginPlay. See Signing in.
-crowdysession=<name>gives a packaged or standalone client its own saved session, so clients sharing aSavedfolder can sign in as different accounts. See Two packaged or standalone clients.- A deploy leaves removed types to the platform. Server Compute no longer restarts a type before
a deploy removes it, since ck-exec 0.19.0 stops it;
-op=deploy's result dropsstoppedRemovedandstopRemovedFailed, and-op=instancesshowsorphaned. See From the command line. - CrowdyCPP 0.74.0.
2026-10-26 (dev: a deploy stops the node types it drops, and takes their switches with them; ck-exec 0.19.0, Game API v2.58.0, CrowdyJS 18.22.0, CrowdyCPP 0.74.0, CrowdyPy 0.22.0)
- A deploy or a rollback stops the node types it drops. When
execDeployorexecActivateVersionmakes active a version without a type, that type's running instances are persisted and stopped at once, timers or not. Before, a hub with a pending timer kept running for as long as players were in the app, held its memory against the app's limits, and could not be stopped:execRestartTyperefuses a type the active version doesn't have. Each keeps its saved state, so a later version that deploys the type again starts its hubs from it. Instances left running this way before the release stop on their own once it is out (ck-exec 0.19.0). See instances and versions. - A dropped type takes its switch with it. A type switched off with
execSetEnabledwhen a deploy or a rollback drops it leavesdisabledTypestoo. A version that deploys a type of that name again starts it switched on. Before, it came back switched off, and the switch could not be cleared while the type was gone (ck-exec 0.19.0). See the kill switch. ExecInstance.orphanedis true for an instance whose type the active version no longer deploys, and null from an older manager (Game API v2.58.0). CrowdyJS 18.22.0'sexec.instances, CrowdyCPP 0.74.0'sexec().instancesand CrowdyPy 0.22.0'sexec.instancesreturn it. Each needs Game API v2.58.0, which an older API refuses to select. If your tooling stopped a type's instances itself before deploying it away, it no longer needs to.
2026-10-26 (Unreal SDK v2.21.0: the Game Kit starters as Server Object types; parties, chat, emotes and spectating; deploys in parts; driving the editor from a shell; CrowdyCPP 0.73.0)
Per-item detail: What's Changed.
After updating, run Generate Server Code once on every Server Object type before you deploy
(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. In C++, a handler bound
to the Call Server Function action's On Success or On Failed needs a new last parameter,
FName Code.
- The Game Kit starters are Server Object types. Add starter on Server Compute adds any of the platform's seventeen Game Kit starters to a project as an ordinary type, used with the nodes a game already knows, with Find Match, kit actors drawn as your own classes, and the position reporter on the Crowdy Server Object component. A starter's refusal reads Refused with a Code. See Use the Game Kit starters.
- Parties, guilds, chat rooms, emotes and spectating, as the Game Kit's other clients do them. See Play together.
- Deploys go in parts and reuse unchanged code. A large project is built in parts instead of refused, an unchanged crate is not built again, and a deploy stops the types it removes. See A deploy in parts.
- Drive the editor, Server Compute and Crowdy Studio from a shell, for scripts and coding agents. See For AI agents.
- Sign-in: On Login now fires for every failed restore,
No saved session foundincluded, and a logout or Clear Saved Session cancels a sign-in under way. See What's Changed. - CrowdyCPP 0.73.0 on every tier, and On Send Refused for a realtime send the server refused. See When the server refuses a send.
2026-10-25 (dev and test: an org token of a switched-off organization; Game API v2.57.1)
- A switched-off organization is told so on dev and test. An org token whose
organization's managed players are switched off was refused
TIER_ACCESS_REQUIREDthere, which read as a missing grant. It now gets what production always answered: sign-ins andprovisionManagedPlayersare refusedFORBIDDEN("not enabled", which an operator changes), and reading, renaming, signing out and deleting your players work. On those environmentsTIER_ACCESS_REQUIREDnow means only that none of your organization's members may use the environment. Production is unchanged. See Org-managed players.
2026-10-25 (dev: names other players see, and a send in flight during a move; Game API v2.57.0, CrowdyJS 18.21.0, CrowdyCPP 0.73.0, CrowdyPy 0.21.0)
- Gamertags and disambiguations refuse layout-changing characters.
updateGamertag,register, the org-managed player inputs and a managed assertion'sgamertagclaim refuse a name with a control character (a newline or a tab, for example), the line or paragraph separator, or a bidirectional formatting character such as U+202E, the right-to-left override. Other players see the name, and those characters change how the text around it is laid out. Letters in any script and emoji are fine.registeralso refuses a gamertag longer than 64 characters, asupdateGamertagalways did. The refusal is aBAD_REQUESTnaming the field. - An awaited send in flight during a move is resent at once. An actor or voxel update still waiting for its echo when the client reconnects or moves to another server is resent, with a fresh sequence number, as soon as the new session is up, instead of after its timeout. That is still the send's one retry; audio, text and event sends are never resent.
2026-10-24 (dev: a session's first message is no longer lost; replication server v0.42.0, CrowdyJS 18.20.0, CrowdyCPP 0.72.0, CrowdyPy 0.20.0)
- The first message of a session, and the first into a new region, wait instead of
failing. A message that reaches Buddy before your session is ready, or before the
permissions of a region you just entered have loaded, is held and processed once they
are, for up to about two seconds. Until now the first was dropped and the second refused
UNAUTHORIZED, so the first send after connecting, reconnecting or moving to another server was lost and clients sent a warm-up message first. They no longer need to.UNAUTHORIZEDnow means the permission is missing, or could not be confirmed in time. See Error codes. - The SDKs retry a session's first awaited send once. Against an older replication
server, the first
*AndWait/*_and_waitsend of each session (the first connect, a reconnect, or a move) is sent again, with a fresh sequence number, when it is refusedUNAUTHORIZEDor, for actor and voxel updates, when its echo times out. Audio, text and event sends succeed by timing out, so their timeout is never retried; in CrowdyJS a send whosesequenceNumberyou chose is not retried either. - CrowdyJS times an awaited send's echo from when the send was accepted. Opening the
proxy session or the binary relay on the first send no longer shortens the wait.
client.realtime.expectSequence()andsessionEpoch()are new; CrowdyCPP addsConnection::sessionEpoch().
2026-10-21 (every environment: org-managed players, behind the Org-Managed Players Addendum; Game API v2.53.0, CrowdyJS 18.17.0, CrowdyCPP 0.69.0, CrowdyPy 0.17.0)
-
Org-managed players. An organization that runs its own player accounts signs its players in itself, so they never type a Crowded Kingdoms password in its game and Crowded Kingdoms holds no email or password for them. There are two routes:
- its server mints a player's app token with an org token (
mintManagedPlayerToken, scopeplayers:mint); - its sign-in service signs a short assertion, with a key registered through
registerOrgSigningKey, which the player's device trades withexchangeManagedPlayerAssertion.
The device plays with an ordinary app token. A managed player has no wallet and plays only its organization's apps. The organization administers them with
managedPlayers,managedPlayer,provisionManagedPlayers(up to 500 a call),updateManagedPlayer,endManagedPlayerSessions,deleteManagedPlayerandorgManagedPlayerSettings. See Org-managed players. - its server mints a player's app token with an org token (
-
The Org-Managed Players Addendum. Before an organization's players are created or signed in, an admin of the organization accepts the addendum once, in Studio (the organization's Players tab). Until then sign-ins, token refreshes and provisioning answer
ORG_LEGAL_ACCEPTANCE_REQUIRED.orgManagedPlayerSettingsreportstermsVersion,termsUrl,termsAcceptedandviewerMayAcceptTerms, andacceptManagedPlayerTerms(orgId, version)records the acceptance. -
Off until switched on. Crowded Kingdoms switches org-managed players on per organization; ask at hello@crowdedkingdoms.com.
-
Org tokens can sign players in.
createOrgTokentakesscopes(players:mint,players:manage) andappIds. A secret starts withcko_and is shown once, and a request that sends one with anOriginheader is refused (ORG_TOKEN_NOT_ALLOWED).revokeOrgToken(orgTokenId, endSessions: true)also ends the sessions the token signed in. -
Mods your organization pays for.
setAppManagedPlayerMods(appId, enabled)(manage_billing) is read back asApp.managedPlayerModsEnabled. Where it is on, managed players' mods, project storage and Studio agent use are billed to the organization. -
New error codes:
ORG_LEGAL_ACCEPTANCE_REQUIRED,ORG_TOKEN_REQUIRED,ORG_TOKEN_NOT_ALLOWED,ASSERTION_REJECTED(withextensions.reason),MANAGED_PLAYER_LIMIT,MANAGED_PLAYER_NOT_ALLOWEDandFEATURE_DISABLED. See Error codes. -
SDKs. CrowdyJS 18.16.0 and 18.17.0, CrowdyCPP 0.68.0 and 0.69.0, and CrowdyPy 0.16.0 and 0.17.0 carry all of this. In CrowdyJS that is
portal.adoptAppToken,portal.exchangeManagedPlayerAssertion,client.managedPlayers,assertionRejectedOf,managedPlayerNotAllowedOfandorgLegalAcceptanceRequiredOf. None wraps the org-token mint or the addendum's acceptance: the first belongs on the organization's server, the second to a person in Studio. -
Fix: CrowdyJS
isLegalAcceptanceRequiredErrorand CrowdyPyis_legal_acceptance_required_errorno longer read the organization's refusal as the player's own clickwrap. -
Dev had a first version earlier the same day (Game API v2.52.x, CrowdyJS 18.16.0) without the addendum; test and production got both together.
2026-10-20 (dev: a browser sends opcode 140, and a voxel edit that waits for the server; CrowdyJS 18.15.0, CrowdyCPP 0.67.4, CrowdyPy 0.15.0)
- A browser client sends app-defined spatial messages.
client.udp.sendGenericSpatial(CrowdyJS 18.15.0) sendsGENERIC_SPATIAL_1(opcode 140) on the binary relay, so a browser, a CrowdyCPP or Unreal client and a CrowdyPy client exchange them both ways. There is no GraphQL mutation for it; see the SDK guide. CrowdyPy 0.15.0 sends it fromclient.udptoo (send_generic_spatial). - A voxel edit that waits for the server.
setVoxel({ optimistic: false })in CrowdyJS,ChunkStore::setVoxel(..., optimistic = false)in CrowdyCPP 0.67.4 andchunks.set_voxel(..., optimistic=False)in CrowdyPy 0.15.0 change nothing locally until the server's echo of the edit arrives, so the cache holds only what the server accepted. - CrowdyCPP's CI now builds and tests the optional Opus wrapper (
-DCROWDY_WITH_OPUS=ON).
2026-10-16 (Unreal SDK v2.20.0: Server Objects ask for platform data and set server limits, and send and hear messages; Server Function Libraries and Can Call Any Type; refusals say why; Restart; account settings and player names)
Per-item detail: What's Changed.
After updating, run Generate Server Code on every Server Object type with a timer, On Player Joined, On Player Left or Remove Members Who Leave before you deploy. Their generated code changed, and until it is regenerated Server Compute marks them Out of date and refuses to deploy the project.
- A Server Object type can ask for more platform data. Its definition's new Platform Data
settings declare the ck-exec scopes
players.read,players.write,world.read,world.write,grids.read,grids.write,permissions.write,access.readandaccess.write; Deploy warns about calls into a scope the type does not ask for, and a type whose functions are all Server Only with nothing visible to players deploys withclient: false. See Platform data. - A refusal by the platform says why. A call or read refused because a type is switched off or
an app is paused by billing, or one the platform could not place, carries the platform's message
in
Reason, and a failed connection givesexecConnect's reason. - A Server Object type can set its own server limits. Its definition's new Server Limits
settings send
memory_mb,deadline_ms,fuel_per_callandmailboxwhen changed from the platform's defaults, and a player's connection waits up to 20 seconds for a call's answer. See Every setting. - Server Compute restarts a type. A Restart button and
-op=restartmove a type's running Server Objects to the live version (execRestartType, ck-exec 0.15); after a deploy the page offers to move what still runs the previous version, shows each type's running count and any instance held back, and the app's use against its instance and memory limits. The SDK vendors CrowdyCPP 0.67.0. See Restarting a type. - Call as Developer and Developers Only. A Server Only function can be called on a live Server
Object from the editor or
-op=call, overexecConnectAsDeveloper; Callable By gains Developers Only (call.developer()in the generated code). See Call a function as the developer. - A failed connection made for the root or a Server Function Library names no type in its failure reason, since that try tells the platform none. See Waiting is not failing.
- Players online. The root hub Deploy sends is now generated from the SDK's
CSO_PlayersOnline: it keepspresence.countfromon_presenceas a variable players read, and is deployed withclient: true. Get Players Online reads it. See How many players are online. - A project with no Server Object types can deploy. Deploy sends the Crowdy SDK's root alone, and the confirm says that removed types keep their saved state. The root is compared like a type, so a later SDK whose root differs makes Server Compute offer a deploy. See Deploy.
- Generate refuses
rootand asks before rewriting yourlib.rs. Put your own modules inlogic.rs. A macro in theimplblock makes Generate and the Server Code tab copy stubs instead of inserting them, and Out of date names the first differing line. See The Server Code tab. - Clearer platform data and caller hints. The scope hint ignores raw strings and tests, Reads Players shows ticked and greyed with the reason, and Deploy warns about a Cooldown or Required Feature on a Developers Only or Server Only function. See Platform data.
- Call as Developer reaches built-in functions such as Add Member; in
-op=callinputs, a 64-bit integer above 9007199254740991 is written as a string. See Call a function as the developer. - Limits and restarts compare correctly. Server limits are compared as the platform applies them, and the offer to move running Server Objects uses the instances read at deploy, by epoch.
- A call past its Time per Call restarts the Server Object from its last save, so changes since the last save are lost. See Server Limits.
- Get Players Online waits a minute between asks when the app has no Crowdy SDK root, and the count arrives once the app is deployed, without restarting the game.
- Retries are spread out. Every Server Object retry and redial wait is stretched by a random amount, up to half again (a failed read's first retry waits 1 to 1.5 seconds, a redial 1 to 1.5 seconds up to 30 to 45, a busy call's retries 0.25 to 0.375 seconds, then 0.5 to 0.75), so many players do not retry in step after a restart. See Waiting is not failing.
- A timeout says which side gave up. Timeout now carries a reason that tells the server's own Time per Call limit ("... ran past its Time per Call on the server: ...") from no answer reaching the game within 20 seconds ("The server sent no answer in time; the connection may be slow or lost"). See Outcomes.
- A call to a switched-off or paused server comes back Paused. The call outcome enum gains Paused as its last value: the 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 be Denied; they are now Paused and retryable, so a Blueprint Switch on the outcome gains a Paused pin and code that only checked Denied should handle Paused too. A read or watch of such a server keeps trying. See Outcomes.
- The Game Session tells you the app is paused.
UCrowdyGameSessiongains Is App Paused, Get App Paused Reason and the event On App Paused Changed, so a game can show a "the world is paused" message instead of retrying. The realtime log line for the refusal names the codeAppPaused. See When the app is paused. - The Game Session tells you the player's access ended, and Server Objects stop.
UCrowdyGameSessiongains the event On Access Ended (Reason,BlockedUntil) and Has Access Ended, Get Access Ended Reason and Get Access Blocked Until. Every Server Object then fails with "Your access ended: " and the reason, calls finish with the new outcome Access Ended (not retryable), and nothing reconnects until the player signs in again. See When the player's access ends. - 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, Blocked Until carries a suspension's end, and the renewal stops until the next sign-in, also in a game that holds no Server Object. A realtime reconnect refused for an invalid token now asks for a new one at once, so a restored player reconnects instead of failing until the game restarts. 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, On Access Ended fires with Blocked Until, and the app token is not rotated in the background until the next sign-in. 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, with matching C++ calls; a ban takes an
optional reason of up to 500 characters. A banned user's join, request or add fails with the new
error code
Banned. See Ban a member and Ban a member. - On Error shows the error on Channels, Teams, Avatars and sign-in nodes. On Success and
On Error share one set of pins: the result,
Error(with itsCode) andMessage; the sign-in nodes carryResultandMessage. Existing graphs keep their links. Breaking in C++: the pin delegate types are renamed (FChannelAsyncResult,FTeamAsyncResult,FAuthAsyncResultand the rest), so a handler bound withAddDynamictakes the added parameters. See Channels. - A Server Object gateway must be on the Game API's own domains. A new Project Settings entry,
Allowed Exec Gateway Domains (
AllowedExecGatewayDomains), lists extra domains for a server you host yourself; a gateway outside both is refused for good. See Server Object gateway domains. - Players who left while a Server Object was stopped are cleaned up. Generated code keeps the
connection counts it restored as unconfirmed; a player not heard from within two minutes of a
restart is treated as having left. The timer name
crowdy_sessionsis now reserved. See Players who left while it was stopped. - A changed timer keeps its old schedule if arming fails, and an unchanged timer keeps its clock. See Timers and events.
- The limit on running Server Objects is documented: 16 at once (and 1 GB) for an app with no funds, 16,384 (and 64 GB) for a funded one. For many players, keep a record per player in one shared object. See Large groups.
- A voxel update with more than 1,024 bytes of state is refused when it is sent, with the reason, instead of being reported queued and then dropped.
- 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, which names each changed record's changed variables. Watch Record runs after each change of one record and when it is removed. See From Blueprint.
- Variables added in the asset (the List form) are read and followed by the typed Get, Get Server State and On Changed nodes, and a new Get Definition node gives the asset a Server Object was made from. A widget needs no component: Get Server Object with the widget as Owner, or a typed node with Find By Asset. See Without the component.
- Ping Server Objects. A new latent node measures the round trip to the platform over the open Server Object connection (On Success gives Round Trip Ms, On Failed a Reason); it never opens a connection, and the SDK refuses more than 60 pings every 10 seconds, the most the platform answers. See Ping the platform.
- A Call node can give up sooner. An advanced Timeout input (
TimeoutSecondsin C++) shortens one call's wait; when it runs out the call fails with Timeout, not retryable, and the call may still have run on the server. See Give up sooner. - Server Object connection timeouts are settings. Crowdy Studio's Connection tab has Call
Timeout (11 to 120 seconds, default 20) and Open Timeout (1 to 60 seconds, default 10) under
Server Objects, stored in
DefaultGame.iniand applied from the next Play. See Server Object timeouts. - Server Objects send messages. A type's definition lists messages (Server Messages), and its
server code sends one with
messages::send_<name>(&payload)to everyone listening, once the function that sent it succeeds. A message is an event: not kept, not replayed. Players Can Listen (on by default) chooses whether players hear it or only other Server Object types. The editor warns that a message reaches every player who can connect to the type, whoever may read its variables. See Server messages. - Games hear messages. Blueprint has a typed On Message Received node for each message,
and Watch Message and Stop Watching Message by name; C++ has
WatchMessageandUnwatchMessage. A message that arrives while the object reads its values is held and announced once they are current, and one sent while the connection is closed is lost. See Hear its messages. - A type can listen to another type's messages. The new Listen To list adds the types to the
type's
calls; server code starts and stops listening withlisten::<type>::<message>and gets anon_<type>_<message>handler. A listen takes effect only when its function succeeds. See Listen to a message. - Server Function Libraries. A definition's new Kind makes a type that is only functions, like
a Blueprint Function Library: it saves nothing between calls, has no Instance Id and is answered by
several instances at once (a ck-exec spoke). Its Scaling settings, Min Instances, Max
Instances and Parallel Calls per Instance, send
replicas,max_replicasandconcurrency. The editor hides what a library cannot have, Server Compute shows its instances and the deploy names a change of kind. See Make a Server Function Library. - Games call a library. Blueprint has a Call node under Server Function Libraries that
needs no Target and no Instance Id; C++ has
UCrowdyServerObjectLibrary::CallLibraryFunction, which runs itsOnDoneexactly once. See Call a Server Function Library. - Can Call Any Type. A check-box on a definition that deploys
calls: ["*"]and gives the server codecalls::any, a call to any type by name; the typed helpers still come from the lists. See Call any type. - Account settings in the game. Request Password Reset, Resend Confirmation Email, Change
Password, Add Password, Sign Out Everywhere and Delete Account, as nodes and on
UCrowdyAuthentication, with an errorCodefor the refusals worth handling. See Account settings. - Sign-out ends the session on the server, in the game and in Crowdy Studio, without waiting for the answer. See Session persistence and sign-out.
- The player's own access, and free access. Get My Access reads it, also while the app refuses
the player; Claim Free Access claims the app's free access after an
ACCESS_NOT_GRANTEDsign-in and finishes that sign-in. See When the player has no access yet. - Player names.
UCrowdyPlayersreads one or up to 100 players' public names and renames the signed-in player. See Player Names. - Crowdy Studio's Account page sets your own name and password and signs out everywhere. See Your account.
- Server Compute's Overview lists the types beside the selected one's details, with search and filters, and Deploy is always in the title row. See Overview.
2026-10-15 (dev: the economy; ckx-sdk 0.12.1, CrowdyJS 18.13.0, CrowdyCPP 0.66.0, CrowdyPy 0.14.0, ckx-kit 0.5.0)
- The economy, in hub state and not a ledger. Two starters:
player, one hub per player (inventory with mail for overflow, soft currencies, crafting, loot with pity, idle production, levels, achievements, quests with first-session tutorials), andmarket(vendors that restock, order books, trades between two players, with what each puts up held in their own hub). Every SDK's kit gainsplayerand its views (inventory,wallet,progression,achievements,quests,crafting,loot,production) andshops,marketandtrades. Read the economy and its limits before you put anything of value in it. - The refusal code
unavailable: a hub could not finish a change because another hub did not answer, and kept no receipt for it. The kits send a call with an action id again (see the protocol). ckx_sdk::kit::econ(ckx-sdk 0.12.0) has the pieces: items, wallets, holds, references answered once, recipes, loot, production, vendors, an order book, levels, achievements, quests. 0.12.1, the version the kit publishes, gives references their own bound (128 bytes); the market makes every reference from its own key and a counter, never from a client's action id (see how it stays consistent).- The open dev kit carries ckx-sdk 0.12.1 and the
playerandmarkettemplates (kit 0.5.0).
2026-10-15 (dev: the genre starters; ckx-sdk 0.11.0, CrowdyJS 18.12.0, CrowdyCPP 0.65.0, CrowdyPy 0.13.0, ckx-kit 0.4.0)
- Nine genre starters.
leaderboard(boards hubs submit to, seasons on a schedule),deck(card tables with private hands),tactics(grid battles in phases),director(waves, towers, a boss),minigame(duels of hidden throws),instance(a party's private run: seeded floors, permadeath, expiry, depth to a leaderboard),arena(classes and abilities in real time),racing(raceandball) andterritory(territoryandzone). Every SDK's kit gains a module for each:leaderboards,tables,tactics,encounters,minigames,instances,arenas,racingandterritory. See the starters and genres and the kit. - ckx-sdk 0.11.0 adds the pieces they are made of (
kit::play,kit::sim,kit::ai, with a movement warden for reported positions) andkit::args_or_default; a debug build refuses a kit message body that names a header field (v,epoch,seq,kind). - A build may take 10 minutes (it was 5): a build compiles its crates one after another, and the whole starter pack is thirteen. See builds.
- The open dev kit carries ckx-sdk 0.11.0 (kit 0.4.0).
2026-10-15 (dev: the Game Kit on ck-exec; ckx-sdk 0.10.0, CrowdyJS 18.11.0, CrowdyCPP 0.64.0, CrowdyPy 0.12.0, ckx-kit 0.3.0)
- The Game Kit.
ckx_sdk::kit(ckx-sdk 0.10.0) is the server half of a game kit whose client half is in every SDK: CrowdyJS'sclient.kit(appId), CrowdyCPP'smakeKitand CrowdyPy'sclient.kit(app_id)gain typed clients for the starter hubs (world,matchmaking,sessions,npcs),topologyto compose, build and deploy them, and the conventionsspectate,emotesandshards. See the Game Kit. - The starter hubs speak the kit protocol. Their topics carry numbered
snapshotanddeltamessages a client mirrors and resyncs, their changes take anaction_idthey answer once, and their refusals carry akit:<code>:code. Their earlier endpoints and topics are unchanged. The matchmaker matches parties whole, and the areas keep players' pets. See the protocol and the starters. - Exec connections reconnect with jitter in every SDK, and CrowdyCPP's
calltakes a per-call timeout. A CrowdyJS fix: anumberof 2^32 or more in a call's arguments went out as a float, which a hub's integer field refuses (timestamps, player ids); it goes out as a 64-bit integer now (CrowdyJS 18.11.0). - The open dev kit carries ckx-sdk 0.10.0 (kit 0.3.0).
2026-10-14 (dev: a sanction reaches a player in a paused app, one player's share of an app's instances, Crowd Altar answers ACCOUNT_BLOCKED; replication server v0.40.1, Game API v2.45.0, ck-exec 0.17.0, CrowdyPy 0.11.0, Crowd Altar v1.10.2)
- A sanction reaches a player in a paused app. A paused app keeps its rule for players
whose access stands: their sends are answered
APP_PAUSEDand they still receive. A player whose access is revoked, expired or suspended now stops receiving channel traffic there too, as in any other app; their sends still answerAPP_PAUSEDfirst (replication server v0.40.1). - A sanction lifted within seconds stays lifted. After a suspension, revoke or block, the server looks again ten seconds later and ends any token minted since. If the sanction was lifted inside those ten seconds (unsuspended, granted again, unblocked), that second look now leaves the player alone; before, it ended the token they minted after being let back in (Game API v2.45.0).
- One player's share of an app's ck-exec instances. Hubs placed for one player's own calls
(a call or subscription to a new key, or
execConnectplacing one beside them) count against that player, at most 64 at once. Past it their next new key is answeredUnavailablenaming the bound, while every other player still places. The root hub and spoke replicas count against nobody, and a stopped instance that holds no snapshot is forgotten after 10 minutes (ck-exec 0.17.0). See instances. - Crowd Altar's sign-in answers
ACCOUNT_BLOCKEDfor a blocked account, withextensions.blockedUntilfor a timed block: the refusal ck-api gives (Crowd Altar v1.10.2). It answeredFORBIDDENbefore. - CrowdyPy 0.11.0 catches up with CrowdyCPP 0.63.0:
client.channels.ban/unbanandclient.teams.ban/unban;account_blocked_ofandgroup_member_banned_ofread the refusals; a ck-exec connection closed4403is final, andon_access_endedhears why.
2026-10-13 (dev: suspending, revoking or blocking a player cuts every access path, and channel and team bans; replication server v0.39.0, Game API v2.44.1, ck-exec 0.16.0, CrowdyJS 18.10.0, CrowdyCPP 0.63.0)
- A suspension or a revoke now cuts everything the player does in the app. Before, it
ended their gameplay tokens and refused signing in, but an open ck-exec connection stayed
open, a channel kept delivering to and from them, their mods kept running, and some calls
into the app still answered. Now their realtime connection is dropped and channel messages
stop in both directions (a channel send that still reaches a realtime server is refused with
UNAUTHORIZED; a ranged one used to answerINVALID_APP_ID), their ck-exec connections close with code4403, their mods in the app stop, and their other calls into the app (purchases, model calls, joining a channel or team, your server code'splayers.featuresandplayers.check_permission) are refused withACCESS_REVOKEDorACCESS_SUSPENDED. Their wallet, receipts and own input logs stay readable, and a member of your organization keeps their developer access. A grant that expires is cut the same way within a minute, and can be bought or granted again. See revoking, suspending and banning. - Channel and team bans.
banChannelMember/unbanChannelMemberandbanTeamMember/unbanTeamMember, with the group'smanage_memberspermission. A banned user is removed, stops receiving the channel, and joining, requesting to join and being added are refused withGROUP_MEMBER_BANNEDuntil the ban is lifted. Leaving or being removed does not lift it. - Account blocks. Crowded Kingdoms can block an account on the whole platform. Its
requests, and signing in (after the password check), are refused with
ACCOUNT_BLOCKEDon every app;extensions.blockedUntilsays when a timed block lapses. Crowd Altar refuses its sign-in too, withFORBIDDENand a message that names the block. Show it; retrying will not help. - ck-exec 0.16.0. A host closes a connection with code
4403and the reason when what it rests on ends: the player's access, their session (signing out, everywhere too, or withdrawing the app's authorization), the account, or a developer's place in the organization. For 90 seconds it refuses the connect tokens that cut covers if they were minted before it (HTTP 401, "access ended"). See when the gateway refuses. - CrowdyJS 18.10.0 and CrowdyCPP 0.63.0.
banandunbanon channels and teams;accountBlockedOfandgroupMemberBannedOf; anExecConnectionclosed with4403(EXEC_CLOSE_ACCESS_ENDED,kExecCloseAccessEnded) does not reconnect, fails its pending callsDenied, andonAccessEndedhears the reason. Older SDKs treat the close as a lost connection, and their reconnect is refused. CrowdyPy picks it up when it vendors CrowdyCPP 0.63.0. - Hub voxel writes no longer wait for the list of realtime servers. A hub's
world.set_voxelspaid about 430 ms whenever it wrote more than two seconds after its last write, while the Game API read the list of realtime servers it announces writes to. The list is now refreshed in the background: on dev a hub's 16-voxel write takes about 15 ms (p50 14 ms inorand 19 ms inva, against 435 and 437 before). rollbackVoxelUpdatesrespects a closed wilderness. When the app's wilderness is closed, an applied rollback that would write any chunk of closed, unclaimed wilderness is refused (403) before it writes anything. A dry run is unchanged.- Build limits. Each step of an
execBuildmay use 120 CPU-seconds per process, and cargo runs two jobs at a time. The largest crate we know of builds in about 20. A step killed at the limit fails the build with a message that says so.
2026-10-13 (rate card: the legacy automation and compile rows are gone)
- Four rows that only the legacy engines fed are off the rate card. On the developer
card,
automation_compute_units, the automations' share of the pooled CPU allowance; on the player card,player_automation_units,player_wasm_compile_countandplayer_wasm_compile_cpu_ms. Automations and server-side player compiles went with the legacy engines, and no usage has been recorded against these rows since, so no bill changes. - The pools are what they already were in practice. The 20 CPU-hours a month each app
gets are pooled across GraphQL resolvers and your app's ck-exec code, and a player's
monthly trial is spent by mod compute (
player_wasm_compute_units). The four names disappear frombillingRateCardand from new statements, andsetBillingRaterefuses them, as it refuses the dimensions retired before.appPlayerUsagekeepsautomationUnitsandcompileCountso the API shape does not change; both stay 0. See the shared environment and player billing.
2026-10-13 (CrowdyCPP 0.62.0, CrowdyPy 0.10.0: the input log says what it lost)
Both SDKs catch up with CrowdyJS 18.9.0, and both need the Game API release after v2.40.2,
because every sessions read selects missingRecords.
- CrowdyCPP 0.62.0. Sessions carry
missingRecords.graphql::inputLogReadRetryable(errors)says when a read is worth repeating with the same cursor, andgraphql::inputLogFundsNeededOfreadsrequiredMicrousd/spendableMicrousdfromINPUT_LOG_FUNDS_NEEDED. A newgraphql::CancelTokencancels a request from any thread: each input-log read has an overload taking one, andGraphQLClienttakes it throughRequestOptions. A cancelled request ends asCrowdyCancelledError/GraphQLErrorKind::Cancelled; the client's timeout still applies. AswitchoverGraphQLErrorKindorErrcneeds the newCancelledcase. See compatibility. - CrowdyPy 0.10.0. Sessions carry
missingRecords, and the docstrings carry the new end reasons and the retry rules. Cancelling a read's task closes its request. It vendors CrowdyCPP 0.62.0. See compatibility.
2026-10-13 (rate card: rows written by compute modules are no longer priced)
- Writes by compute modules are charged by the byte only. The rate card's
wasm_db_rows_writtenrow (40¢ per million rows, no allowance) is gone from every environment; writes go on being charged aswasm_write_bytes, 25¢ per decimal GB above the 1 GB each app gets a month. Nothing changes in the API or the SDKs: the dimension disappears frombillingRateCardand from new hourly statements, andsetBillingRaterefuses the name, as it refuses the counts retired on 2026-09-06. The Free Tier and Billing Basis and the Pricing page say so. See the shared environment.
2026-10-12 (Unreal SDK v2.19.0: Game Models removed; crowds with Mass, records per entity, the terms and age prompt)
The release after Server Objects removes the SDK's Game Model API, which is breaking for every project that still uses it. The release vendors CrowdyCPP 0.57.0 on every tier. Per-item detail: What's Changed.
-
Signing in asks the player to agree to the terms and confirm their age. For the Game API's terms and age gate, a refused sign-in, restore or token refresh is held, and a prompt in the Crowded Kingdoms colors opens over the game; Accept records the agreement and the sign-in completes. A game can turn the prompt off and draw its own. Crowdy Studio shows the same prompt. The SDK vendors CrowdyCPP 0.57.0. See Let the player agree to the terms.
-
CrowdyMass is part of the Unreal SDK. There is no separate plugin to enable: a map opts in by setting its profile's Backend Class to Crowdy Mass Rendering Backend, and the actor pool stays the default. Module names are unchanged, the shipped assets live under
/CrowdySDK/Mass/, and the CrowdyMass entry comes out of your.uprojectand any.upluginof your own that names it, or a C++ build stops naming that file. A class whose anim set names a Bank animates with nothing else authored, and its rows stand where its mesh stands, even when the mesh is nested under other components. The SDK turns off the engine's animation pass for you, so noConfig/DefaultMass.iniis needed; that holds in every project with the SDK, so a skinned Mass crowd of your own that stands still gets no animation updates. A Blueprint Receive Crowdy Event on Entity node no longer stops a row playing its declared action. C++ gains the typedCROWDY_MASS_SEND_EVENTandGetServerValueAs. A map that does not use the Mass backend now pays almost nothing for it, and your own world subsystems should gate themselves withShouldCreateSubsystem. See Draw a crowd with Mass. -
The Unreal SDK's Game Model API is removed. Containers and attributes, model functions, Crowdy Effects and the Apply Effect nodes, sessions, automations, pre-seeding, the Game Kit and
UCrowdyGameModelSubsystemare gone, along with the Server Owned replication choice, CrowdyMass's Game Model values and Crowdy Studio's Game Model page. Studio gains an Access page for features and tier-feature grants, which now read and change correctly (the old page sent them with the wrong credential). Some C++ API changes break a build (ICrowdyEntitySubscriber::ApplyModelChanges, the sub-participant functions,FCrowdyModelIdentity). Channels no longer drop payloads startingcmc:,csg:orgms|. Open and compile your project on the Server Objects release first to list every use, search your C++ forCrowdyModel, and do a clean cook after upgrading. See Move from Game Models. -
A Server Object function can require a feature. Required Feature names a feature key the caller's access tier must grant; other players are refused. See Required Feature.
-
Server Object timers can run on a schedule. On Schedule takes a cron expression and a UTC offset; the generated code re-arms each run before your function is called. See Access, members and timers.
-
A changed timer reaches running Server Objects. After a deploy that changes a timer's Runs, a pending timer is armed again under the new Runs and a new automatic timer starts; generate again, and the first deploy of the new code re-arms nothing. See Access, members and timers.
-
Fixes: a Player's Team Server Object component asks for the player's teams again every 5 seconds while it waits; a Type Name change offers the crate Generate wrote last when two name one definition; a Server Object waiting to connect gives the connection's error as its reason, and one type that cannot be placed no longer holds up the others; seamless travel pauses the grace period; Deploy refuses two types that share a Logic File.
-
Server code the SDK did not generate is left alone. Generate Server Code refuses, and Remove from project... keeps, a
Server/<Type Name>/folder holding a crate you wrote yourself; Deploy names the live types it takes offline right after its first sentence, and Deploy starter pack names every type the live version runs. See Deploy. -
A Server Object can keep a record per entity. For a crowd: the same variables kept once for each entity in one Server Object per area, match or team, each variable visible to everyone, to the entity's owner, to its team, or to nobody. The component has a This Actor's Area mode, and crowd rows read the values. See Keep a record per entity.
-
A Server Object type can read world data. Reads World Data lets its server code ask which actors are near a chunk; deploying asks for the
world.readpermission. See Reads World Data.
2026-10-12 (dev, latest: input logging reports what it lost, ends every session and says when to retry; replication server v0.38.1, Game API v2.41.0, CrowdyJS 18.9.0)
Input logging fixes and one new requirement. See Input logging.
- Turning replay logging on needs funds for what it will keep.
updateApprefusesreplayLoggingEnabled: truewithINPUT_LOG_FUNDS_NEEDEDunless the organization's wallet can spend at least the projected cost of keeping one retention period of the app's recent traffic (extensions.requiredMicrousdandspendableMicrousdsay how much), or the organization is exempt from billing. Recordings already kept go on being billed after logging is turned off, until they age out. - Every recorded session gets an end. Turning replay logging off while a session is open ends
it as
logging_off; a replication server that stops (a deploy) ends its open sessions asshutdown, and the client reconnects to another server, where the same session continues and its end is replaced. Recording that pauses because the app's logging setting could not be confirmed ends its sessions aspaused, which the player does not see. A session whose end was not recorded is closed asunrecordedat its last input, once an hour has passed without one. A session that records again after it ended reopens. - A session says how many of its inputs were lost.
InputLogSession.missingRecordscounts the inputs the replication server accepted that never reached the log. Recording is best-effort: an overloaded server or a broker outage leaves a gap, and this is how it shows. A lost end of a recording cannot be counted; its session closes asunrecorded. - Reads say when to retry.
inputLogMessagesanswersINPUT_LOG_TEMPORARILY_UNAVAILABLEwhen the log cannot be read right now, where it used to answerINPUT_LOG_UNAVAILABLEor an empty page. Each server runs one read per user, and two per app and per organization, at a time: a read over that share waits for an earlier one within its few-second deadline, and answersINPUT_LOG_RATE_LIMITEDif the wait runs out. Both carryextensions.retryable: retry with the same cursor, a bounded number of times. A read whose HTTP request is closed stops and frees its share at once (CrowdyJS 18.9.0: thesignaloption ofinputLog.messages), and ends asINPUT_LOG_READ_ABORTED, a client error nobody receives. An operation may selectinputLogMessagesonly once. - A cursor belongs to its session.
inputLogMessagesrefuses a cursor from another session withBAD_USER_INPUT, as it refuses a malformed one. - Offset cursors are bounded. A connection cursor that points past 10,000,000 rows is
BAD_USER_INPUT;first: 0returns one edge.walletTransactionsclampslimitto 1 to 200 andoffsetto 0 to 9,999,999. appUsageSummaryandorgUsageSummaryreport each total once. An app with both replication and GraphQL usage in the window had its replication bytes multiplied by its number of GraphQL usage rows, and the other way round, and an organization's summary counted only its own apps from the derived tables. Crowdy Studio's usage figures were affected too. Charges were never affected.- CrowdyJS 18.9.0 selects
missingRecordsand documents the above, so it needs this Game API. CrowdyCPP and CrowdyPy follow it.
2026-10-10 (dev, later: faster hub voxel writes, mods hear every Game API voxel write, builds report their SDK; Game API v2.40.2, CrowdyJS 18.8.0, CrowdyCPP 0.61.0, CrowdyPy 0.9.0)
- Hub voxel writes are faster.
world.set_voxelswrites its batch in one statement and sends it to nearby players once, instead of one write and one send per voxel. On dev a 16-voxel batch used to outlast ckx-sdk's 2-second node call deadline: the write landed and the hub gotDeadlineExceeded. The batch is still all or none, and a voxel listed twice keeps the later write and reaches players once. See World and platform data. - Mods hear every Game API voxel write. A mod's
on_worldvoxelsevents now come fromworld.set_voxels(the app's hubs and mods, the mod's own included),updateChunkand an applied rollback, as well asupdateVoxel; before, onlyupdateVoxelproduced them. A player's realtime voxel edit still produces none. See Mods. - Builds report their SDK.
ExecBuild.sdkVersion(fromexecBuildStatus,execModBuildStatusand the build mutations) is theckx-sdkversion a build compiled against, orcrowdy-client-sdk's for a CLIENT half, and the build log's first line names the toolchain. The platform compiles every crate against its own copy of the SDK, whatever version the crate names. CrowdyJS 18.8.0, CrowdyCPP 0.61.0 and CrowdyPy 0.9.0 return it, and need Game API v2.40.2: an older API refuses their build calls. See Builds.
2026-10-10 (dev: the open mod dev kit; ckx-sdk 0.9.0 on crates.io, with native tests)
Additive. Modules are unchanged (guest ABI 5).
- Develop hubs, spokes and mods on your machine. The open dev kit,
ckx-kit(@crowdedkingdoms/ckx-kiton npm; MIT or Apache-2.0, github.com/CrowdedKingdoms/ckx-kit), starts a crate from the templates and starter packs, runs the platform's source checks, builds with the Rust release, release profile and dependency versions the platform pins, checks the module and the mod limits, and meters a mod's CLIENT half, printing the capability summary and hash the platform's build reports. A crate that passes there builds on the platform. See develop on your machine. ckx-sdkandcrowdy-client-sdkare on crates.io, under MIT or Apache-2.0.ckx-sdk0.9.0 tests nodes natively.ckx_sdk::testingruns a hub, a spoke or a mod undercargo testagainst a fake of the platform: calls, timers on a clock the test moves, topics, presence, sessions, a mod's world events, snapshots and restarts, each call held to the platform's bounds. A mod'sFakeGridanswers it as the node API does, refusals included.ckx_sdk::limitsstates the platform's bounds. See test.
2026-10-10 (dev: free hub tier, pause signals, app-defined voxels, channel voice, hub grid and access APIs; replication server v0.37.0, Game API v2.40.1, ck-exec 0.15.0, CrowdyJS 18.7.0, CrowdyCPP 0.60.0, CrowdyPy 0.8.0)
Additive, apart from three behaviour changes called out below. From the asks of a studio building a Minecraft integration.
- Hubs run free for a small game. The monthly quota now includes 1 GB of
compute-module writes per app (what hubs write when they save), and a save identical to the
last one is neither stored nor billed. An app whose organization cannot be charged may run 16
hub instances reserving 1 GB per datacenter; a funded one 16,384 and 64 GB.
execAppStatusreports the limits and current use. See free tier. - A paused app says so.
mintAppToken,refreshAppTokenandexchangePortalCodestill mint and reportruntimeGate { status reason };gameClientBootstrapreports it instead of failing, withwildernessWritesOpen; the realtime proxy refuses withAPP_PAUSED; replication answers a paused app's sends with error code 33 (APP_PAUSED) instead of silence, and serves the session again once the app is active. A paused hub's refusal names the cause. See what "access denied" means. - Voxel positions and types are yours to define within signed 16 bits on every write path;
nothing checks 0-15 or 0-255 any more. One 1 KiB cap on a voxel's state, replication included.
A chunk read applies up to 65,536 recorded edits (
voxelStatesTruncatedpast that); a rollback is stamped with its own time, so readers of edits since their last sync see it; a chunk's state is capped at 64 KiB. Behaviour change: an accepted voxel edit is always delivered back to its sender, once. See voxel positions and types. - Typed refusals.
ACTOR_EXISTS(withownedByCaller) for a uuid the app already has;ACCESS_REVOKED,ACCESS_SUSPENDED(withsuspendedUntil) andACCESS_NOT_GRANTEDfrom the mint paths, none of which tell the player to buy anything. See error codes. - Other players' accounts are private. Behaviour change:
user(id)returns private fields only to the player themselves; game clients useplayerProfile/playerProfilesfor names. - Voice on a channel.
sendChannelAudioandChannelAudioNotification(opcodes 35 and 36), a channelsend_voiceright andmembersCanSpeak. See voice on a channel. - Hub code manages grids and access (ck-exec 0.15.0, ckx-sdk 0.8.0):
grids.write(create and delete its own grids, give groups keys, open grids),access.read/access.write(grant, revoke, suspend players),world.set_chunk_state, voxel authors, the grid owner and the wilderness setting; an app's own hub writes skip the wilderness rule.execRestartTypemoves a type's running instances to the active version. Players can be suspended until a time (suspendAppAccess), tier keys re-applied (resyncTierGridPermissions), and claims can grant keys (claimOwnerKeys). Permission changes reach every Game API server at once. See world and platform data. Behaviour change for module authors: ckx-sdk 0.8.0 makesPlayer.disambiguationoptional and voxel typesi16. - Idle players on different servers meet. A heartbeat that reached a server ahead of the first update kept two idle players on different servers apart; they now see each other at the first full update (3 s with the SDK defaults). No client cadence is required.
- CrowdyJS 18.7.0 / CrowdyCPP 0.60.0 / CrowdyPy 0.8.0: optional voice payload helpers (a
10-byte header, a packetizer, a jitter buffer; CrowdyCPP optionally wraps libopus), channel
audio, opcode 140 delivered to the app, chunk stores that keep wide edits, CrowdyCPP's
onVoxel, and wrappers for everything above. They need Game API v2.40 or later.
2026-10-09 (dev: input logging; replication server v0.36.1, Game API v2.39.0, CrowdyJS 18.6.0, CrowdyCPP 0.59.0, CrowdyPy 0.7.0)
Additive. Nothing records until an app turns it on.
- An app can record its players' inputs. With replay logging on
(
App.replayLoggingEnabled, set withupdateAppbymanage_apps, or in Crowdy Studio's app settings), every client message the replication servers accept for the app is recorded with its arrival time, player and session.inputLogSessionslists the recorded sessions andinputLogMessagesreads one session's inputs, each as the client sent it without its authentication tail. A player reads their own;manage_appsreads every session. Inputs are kept for the published retention (7 days). See Input logging. - Stored input logs are a new rate-card line,
input_log_storage_byte_hours, priced per GB-month with no free allowance. Turning logging on is refused withINPUT_LOG_FUNDS_NEEDEDunless the organization's wallet has a spendable balance. See Shared environment. - CrowdyJS 18.6.0:
client.inputLog.sessions/messages; every app read selectsreplayLoggingEnabled. - CrowdyCPP 0.59.0:
client.inputLog().sessions/messages, each with anAsynctwin. Parity is pinned to CrowdyJS 18.6.0. - CrowdyPy 0.7.0:
client.input_log.sessions/messages.
2026-10-09 (dev: channel messages limited by distance; replication server v0.35.0, Game API v2.38.0, CrowdyJS 18.5.0, CrowdyCPP 0.58.0, CrowdyPy 0.6.0)
Additive; receivers change nothing.
- A channel message can reach only the members near an origin chunk. The new
sendRangedChannelMessagemutation and theCHANNEL_MESSAGE_RANGED_REQUESTUDP message (type 32) take an origin chunk and amaxDistancein chunks. A member receives the message when one of its live actors is within that straight-line (Euclidean) distance of the origin, boundary included, so 5 reaches a member 3 chunks east and 4 north but not one 4 east and 4 north. Any distance up to 2147483647 is accepted. Members receive the ordinaryChannelMessageNotification, the send right issendChannelMessage's, the sender gets no echo, and a member with no live actor receives nothing. A replication server older than v0.35.0 drops the request. See Limit delivery by distance and the wire format. - Channel publishes are rate-limited like every other client message (replication server
v0.35.0). A
CHANNEL_MESSAGE_REQUESTnow counts against the session's message rate, and one over it is dropped; once the session's token has expired it is answeredTOKEN_EXPIRED. Spatial sends already behaved this way. - CrowdyJS 18.5.0:
client.udp.sendRangedChannelMessage,serializeRangedChannelMessageandCHANNEL_RANGED_MAX_DISTANCE. - CrowdyCPP 0.58.0:
Connection::sendRangedChannelMessageandwire::encodeRangedChannelMessage. Parity is pinned to CrowdyJS 18.5.0. - CrowdyPy 0.6.0:
client.udp.send_ranged_channel_messageandcrowdypy.wire.encode_ranged_channel_message, plus CrowdyJS 18.4.0's terms and age gate:auth.record_player_consents,auth.player_legal_acceptance,register'saccept_legalandattest_age_of_majority, andis_legal_acceptance_required_error.
2026-10-07 (Unreal SDK v2.18.0: Server Objects; Game Models deprecated)
Server Objects are additive; the Game Model deprecation below is not, and affects every project that uses Game Models. The release vendors CrowdyCPP 0.56.0 on every tier. Per-item detail: What's Changed.
- The Unreal SDK's Game Model API is deprecated and does nothing; a later release removes it.
Every call fails at once with
GAME_MODEL_DEPRECATED, C++ warns at each use, each Blueprint that uses Game Models gets one compiler warning listing them, and Crowdy Studio's Game Model page and CrowdyMass's Game Model values stop working. Server Objects replace it. See Move from Game Models. - Server Objects bring ck-exec to the Unreal SDK, from C++. A data asset describes a type of server-owned state (the struct the server keeps, the fields players may watch, the functions they may call); the game acquires a shared Server Object by type and Instance Id, is told each time its watched values change, and calls its Server Functions, with a busy server retried for it. Blueprint use is in a later entry below. See Server Logic.
- Generate Server Code writes a Server Object type's server code from its definition asset.
Right-click the asset in the Content Browser: the editor writes a Rust crate under
Server/<Type Name>/in the project, regenerates the glue that keeps watched values, Owner Only and Who Can Call in step with the definition, and leaves you one file,logic.rs, for what each Server Function does. It warns before a regeneration drops a field or enum value. The asset's editor also has a Server Code tab: an external-editor button and an editor for the code with Save and Revert, and a Generate button in its toolbar. Code Source lets you choose My own file, a.rsfile of your own, instead of the generatedlogic.rs; Generate Server Code then never creates or overwrites it, and Server Compute sends it as the type'slogic.rs. See Write its server logic. - Server Compute builds and deploys a project's server code, and shows how it runs. Open
Crowdy Studio and choose Server Compute in its navigation; it uses Studio's sign-in and the
app set there. Deploy sends every Server Object type in the project to ck-exec's build and
deploys the result as the app's active server code, with the build output shown on the page.
Its tabs list the versions (with Make active to go back to an earlier one), switch one type
or the whole app off and on, read the logs, including one call's lines, and show how the calls
to each Server Function went. The same operations run from the command line for CI with
UnrealEditor-Cmd <project> -run=CrowdyServerCompute -op=deploy -yes;deploy,starters,activate,disableandenablerefuse without-yesand send nothing. See Server Compute. - The Server Object definition asset has its own editor, and Server Compute tracks what changed.
Double-clicking the asset opens an editor like the Material or Blueprint editors: a toolbar with
Generate, Deploy and a status readout, a Server Code tab with line numbers and Rust
colouring, and a Details tab with the settings, the watched values ticked from State's fields
and the Type Name suggested and checked as you type. Some settings read differently, for example Who
Can Read and Players See. Open Server Compute in the Deploy dropdown opens Crowdy Studio on
that page. Who Can Call (Players or Server only) replaces Player Callable: a Server only
function refuses a player's call. Each deploy now records what it sent beside the type's crate,
in
revisions.json, so the Server Compute page can tell you, per type, that it has No changes, has Changed, is New or is a Removed type still live, and offers Deploy only when something changed. The asset lists the type's earlier deployed code to view, compare or restore, a type can be removed from the project from its menu, and a Settings tab sets how many revisions are kept.-op=changesreports the same comparison for CI. Commitrevisions.jsonwith the crate. See Create a Server Object type and Server Compute. - A Server Object's State, params and reply can be Lists instead of structs. Set State As,
Sends As or Replies As to List and add named values right in the asset, each with a
type and a starting value, with no struct to write. A List travels exactly like a struct with the
same fields, and the generated server code gives it the name a struct would have
(
VillageBeaconState,FeedBeaconParams,FeedBeaconReply), sologic.rsdoes not change. From C++,MakeParamsand aCalloverload send a List,CrowdyExec::ToListreads a List reply andGetStateLista List State, all by name. Existing definitions stay Struct. See Create a Server Object type. - Each Server Functions entry labels its header row with Function before the name and Who can call before the Players / Server only choice, each with a tooltip saying what it does.
- The Server Object asset editor now works like the Blueprint editor. A Server Object panel,
where My Blueprint is, lists the type's Variables (each with its type pill and an eye for
Visible to Players) and Functions (each with its pins, such as
(Oil) -> Oil), and Details follows the selection. Labels use Blueprint's words: Readable By (was Who Can Read), Callable By (was Who Can Call; Players or Server Only), Visible to Players (was Players See), Inputs and Outputs (were Sends and Replies), each with a Use Struct check-box in place of State As, Sends As and Replies As, and Lists are now variables, inputs or outputs added in the asset. The wire, saved data and C++ calls are unchanged. See Create a Server Object type. - Server Objects work from Blueprint. A Crowdy Server Object component holds a Server Object for its actor from Begin Play to End Play: pick the definition and an Instance Mode, Instance Id (actors with the same id share one), Signed-In Player (the player's own, for Owner Only types) or This Actor (a placed actor's place in the level), and it raises On Variables Changed and On Status Changed. The Server Objects nodes cover the rest: Get Server Object, Release Server Object, Get Player Instance Id, Call Server Function (with On Success and On Failed, exactly one of which runs), Make Inputs, Set Server Value and Get Server Value (by name), and on the object Get Status, Get Failure Reason, Get Instance Id, Get Variables, Watch Variables and Stop Watching Variables. Typed nodes with typed pins follow in a later entry below. See Get a Server Object, from Blueprint.
- Server Objects get members, access rules, timers and Can Call. In the asset, Readable By
gains Members and Callable By gains Members and Leader; each function can have a
Cooldown (seconds, per player) and each number input a Value Range. Members From is
None, This Object (Join, Leave, Add Member, Remove Member, Make Leader and Set Open For
Joining, a leader, Max Members, and Open For Joining) or Crowdy Team. Timers
(Every or After, each a function in
logic.rs) and the On Player Joined and On Player Left events run on the server, and Can Call lets one type call another with typed calls. A refusal by one of these rules reaches the caller as Denied, and a cooldown as Denied and retryable. The component gains the Player's Team and From Server Value modes, and the object the nodes Get Members, Get Member Count, Get Leader, Is Open For Joining, Is Member and Is Leader. Generate and deploy a type again to use them. See Access, members and timers and Guild halls and arena lobbies. - Typed Blueprint nodes, find by asset and Only One Instance. In any Blueprint graph, a variable's name now offers
Get <Variable> (<Asset>)andOn <Variable> Changed (<Asset>), a function's nameCall <Function> (<Asset>), and each assetGet Server State (<Asset>), all under Server Objects, with a real pin for every value: Boolean to Map, enums, vectors, colors, dates, tags, soft references and your own structs (Split Struct Pin).On <Variable> Changedis bound once from Begin Play, runs at once with the current value and after every change of that variable, and needs no unbinding.Call <Function>has typed inputs and outputs, with On Success and On Failed. The object comes from Target (Self by default) or from Find By Asset: Instance Id, Signed-In Player or Player's Team. A new Only One Instance setting gives a type one shared instance for every player, which needs no Instance at all. Only variables ticked Visible to Players are offered, and a value that does not fit its pin is refused, not cut: a Call sends nothing and runs On Failed naming the input. Get has an advanced Has Value pin, false until the server has sent the values. A name that clashes with a node's own pin (such asTeam IdorOn Success) gets no node, and the compile says to rename it. The untyped nodes stay for dynamic cases. Behavior change: the names in On Variables Changed and Watch Variables are now the ones Get Server Value finds, also for variables renamed with Server Names. A new walkthrough, Tip jar: your first Server Object, builds a tip jar with only the editor. See Get a Server Object, from Blueprint. - A shared boss, end to end, and a trace for Server Objects. A new example page,
Shared boss fight, builds one boss that every player hits, with
its health on the server, a per-player cooldown, a 1 to 25 damage range, hits refused while it is
defeated and a Respawn timer that brings it back, then plays it with two players and switches the
type off and on from Server Compute. The new console variable
crowdy.exec.trace(off by default, log categoryLogCrowdyExec) prints each Server Object's status changes, the reads and pushes it applied with their epoch, sequence number and changed variable names, why it read again, its subscribes, each call sent and answered with its time, and connection closes and redials. It prints no values and no tokens. See Seeing what a Server Object is doing. - A timer that cannot start refuses the call. If the platform refuses a timer a Server Function starts, the call fails as
ServerError("the change was not kept: timer Respawn could not start: ..."), nothing it changed is kept or published, and timers it already started in that call are cancelled. See Timers and events. - Renames ask, every value is checked. Renaming a variable, input or output added in the asset now asks at Generate whether to keep the old name on the server, so saved values carry over; a new Type Name offers to move the old server code folder; the generated code checks every value an Unreal client would refuse, including a state loaded from an older save (
unreadable); each function keeps its own default values; and a function name can no longer reach the server in the wrong letter case in a packaged game. Generate and deploy your types again. See After you change the definition. - Type Settings, CSO_ names, tidier nodes. The Server Object panel opens on a pinned Type Settings row, with a matching toolbar button, for the type's own settings. New assets come from Add > Crowdy > Server Object and are named
CSO_; olderDA_assets keep working. Details reads more plainly (seconds, Min and Max, timers that run Every or Once After), Call nodes tuck Outcome, Reason and Retryable under the arrow at the bottom (wired ones stay), and anOn <Variable> Changedcan be placed more than once. A new page, Relate Server Objects to each other, covers registries, shared objects, lobbies and teams. Nothing to do. - Generate fills in what logic.rs is missing. A Server Function, timer or event added after
logic.rswas written is now offered as an empty version at Generate, or with Add Stubs in the Server Code tab, instead of only breaking the build; a method the type no longer has is named. Each timer's name is a constant,timers::END_MATCH, so a misspelt timer no longer compiles. The Server Code tab now picks up a save from another editor by itself. Generate again. See After you change the definition. - The Crowdy Teams cache keeps itself current. Signing in clears and refills the "my teams" cache, signing out empties it (
OnMyTeamsCacheChangedfires with an empty list), and a successful create, join, leave, delete, update, member removal or role change refreshes it; Request To Join does not, since the membership is pending. A stale Get My Teams answer never fills it. C++ gainsClearMyTeamsCache(). See Crowdy Teams. - Too many new names no longer fail a whole Server Object message. A client accepts at most 1 Mi (1,048,576) characters of new names per run; past it a new Name reads as None, an object path of new names reads as empty, the rest of the message still applies and the client logs one warning. See Create a Server Object type.
- Lists that start with different values get their own struct in the generated code. A List that shares another's shape but starts with different values is its own struct, not a
pub typealias, and Generate shows a notice when a List switches; one that starts with the same values stays an alias. Generate again and fix anylogic.rsthat used one for the other. See Create a Server Object type. - Moving or renaming a definition asset keeps its server code findable. Saving it updates its crate's
Cargo.tomlrecord, so a later Type Name change still finds the folder. A Ready Server Object also stops keeping an earlier read refusal's text as its failure reason. - Smaller changes. Actor updates carry the payload type id their sender used; the
CrowdySDKmodule no longer lists HTTP as a public dependency (add"HTTP"to your ownBuild.csif you relied on it);FCrowdyCppClient's test-only accessors exist only with automation tests; and updates for actors not yet on screen wait in a bounded queue (crowdy.replication.tracker.maxqueuedupdates). See What's Changed.
2026-10-07 (the terms and age gate: Game API v2.35.0 on dev, test and prod; CrowdyJS 18.4.0 and CrowdyCPP 0.57.0 on dev)
-
Players agree to the terms and attest the age of majority before playing. Since Game API v2.35.0,
mintAppToken,createPortalAuthorizationCodeandrefreshAppTokenanswerLEGAL_ACCEPTANCE_REQUIRED(403) until the player has agreed to the current required documents (Game Terms, API Terms, SDK Developer Terms, Free Tier and Billing Basis, Overworld Privacy Policy) and attested that they are at least 18, or the age of majority where they live if that is higher.- Studio's hosted
/authorizeand its register page ask for both, so a browser game changes nothing. When a refresh is refused this way, send the player back through hosted sign-in. - A native client shows its own two checkboxes and calls the new
recordPlayerConsents.playerLegalAcceptancesays whether that is still needed. - A browser
registermust sendacceptLegalandattestAgeOfMajority.
- Studio's hosted
-
CrowdyJS 18.4.0 (dev):
client.auth.recordPlayerConsents,client.auth.playerLegalAcceptance, the two fields onauth.register, andisLegalAcceptanceRequiredError. -
CrowdyCPP 0.57.0 (dev):
auth().recordPlayerConsentsandauth().playerLegalAcceptance(each with anAsynctwin), and aregisterUseroverload carrying both fields. Parity is pinned to CrowdyJS 18.4.0.
2026-10-07 (dev: a grid mod's page-held host calls; CrowdyJS 18.3.0)
-
CrowdyJS 18.3.0:
createGridHostCallstakes alocal.page(fn, args)hook for the host calls only the page can answer:- the player's input (
input_axes,input_look,input_key); - the player's own body (
pose_get,pose_set,pose_release,teleport_request); - the mod's own actors (
actor_spawn,actor_pose,actor_despawn); - the scene (
scene_catalog,scene_instances); - presentation (
avatar_appearance,avatar_state_set,voice_set,video_set); - the player's own sends (
send_client_event,events_poll,send_text,send_actor_message,send_channel_message).
Without the hook each is refused as not offered, as before, and
clockis answered locally. The server refuses all of them. See Answering host calls. - the player's input (
2026-10-03 (dev: chunk loads show recorded voxel edits; CrowdyJS 18.2.0, CrowdyCPP 0.56.0)
Every voxel write except a chunk write-back lands only in the chunk's edit log: a hub's or
mod's world.set_voxels, updateVoxel, and realtime voxel updates. Since Game API v2.33.0 on
the dev environment, getChunk and getChunksByDistance return each recorded edit as a
voxelStates entry with its voxel type (getChunksByDistance only when voxelStates is
selected). The stored voxels hold none of them, so a client that reads only voxels loses a
hub's block on reload.
- CrowdyCPP 0.56.0:
ChunkStore::ensureAround's one bulk load selectsvoxelStatesand puts each entry over the stored grid. AnIChunkSourceof your own reports them inStoredChunk::voxelStates. See WorldSession. - CrowdyJS 18.2.0:
ChunkStorekeeps a chunk it has already loaded when a later bulk load returns it again; before, moving put the stored grid back over the hydrated edits. Hydration also puts the edits on a chunk stored withvoxels: null. A store hydrates when it has avoxelStateCodecorhydrateVoxelStates: true. See World Stores. - A chunk that has never been stored comes back from neither read, even when edits were recorded for it.
2026-10-01 (CrowdyPy 0.5.0)
CrowdyPy catches up with CrowdyJS 18.1.0 and CrowdyCPP 0.55.0.
client.exec.connectsends the connect token only to a gateway on the platform's own domain, as CrowdyJS and CrowdyCPP do. See Where the connect token goes. A gateway that refuses the token is reported asDenied, with its reason.- Open grids:
game_apps.open_permissionsandset_open_permissions. - CrowdyPy is on PyPI:
pip install crowdypy.
2026-10-01 (open grids in the SDKs, CrowdyJS 18.1.0, CrowdyCPP 0.55.0; ck-exec limits)
The SDK side of the ck-exec preview's security review. On the dev environment.
- Open grids in the SDKs.
client.gameApps.setOpenPermissions/openPermissions(CrowdyJS) andgameApps().setOpenPermissions/openPermissions(CrowdyCPP) wrapsetGridOpenPermissions/gridOpenPermissions: the keys a grid grants every player with access. A zone everyone may build in has to grantupdate_voxel_dataitself now, because the most specific grid over a chunk decides who builds there. See open grids. - Where the connect token goes.
exec.connectdials a ck-exec gateway only when it is on the platform's own domain, overwss:whenever the game API ishttps:; any other gateway is refused without being dialed. See connect from a game. - A refused connect token is
Deniedagain in Node (CrowdyJS with thewspackage) and in CrowdyCPP, with the gateway's reason where the transport can read it. A browser cannot read the refusal and still reportsUnavailable. See when the gateway refuses. - ck-exec's limits are written down: 16 connections per player and app through a host
(
HTTP 429past them), what closes a connection (a text message, too many WebSocket pings), a ceiling of 1,024 running instances per app, and what one handler call may send, return and save. See call limits and what one call may send and return.
2026-10-01 (CrowdyPy 0.4.0, the Python SDK)
CrowdyPy is the new official Python SDK. It covers CrowdyJS's whole surface with Python names, and its realtime path is CrowdyCPP's native UDP replication client, compiled into the wheel. See the CrowdyPy docs.
- One
cp312-abi3wheel per platform for CPython 3.12 and later, plus free-threaded 3.14. Both an asyncio client (crowdypy.AsyncCrowdyClient) and a blocking one (crowdypy.sync.CrowdyClient) are included. - Native UDP replication with batched sends and zero-copy notification batches, plus the World Stores, the Game Kit, the ck-exec gateway, GraphQL subscriptions, the headless Crowdy Studio and the player-host observation contract.
- The HMAC guide gains a Python example.
2026-09-29 to 2026-09-30 (dev: who may build where, and the security review's fixes)
The ck-exec preview's security review, on the dev environment. CrowdyJS 18.0.2 to 18.0.4 and CrowdyCPP 0.52.0 and 0.53.0 carry the SDK side.
- The most specific grid decides a voxel write. Every app has a world grid that grants
every player, so a write used to pass wherever any grid covering the chunk granted it. Now
the smallest grid covering the chunk decides: the player needs
update_voxel_dataon that grid, the world grid decides only the wilderness, and a chunk no grid covers is refused.updateVoxel,updateChunk,sendVoxelUpdate, a mod'sworld.set_voxelsand, from replication server v0.34.0, a client's direct UDP voxel update all apply it. A zone everyone should build in grants everyone itself (setGridOpenPermissions, at most 32 open grids per app), andclaimGridChunkclaims only the wilderness (GRID_NOT_CLAIMABLEinside another grid). See which grid decides a voxel write. - The wilderness setting.
App.wildernessWritesOpen(default true;updateAppsets it) closes the wilderness to every voxel write when false.updateChunknow needsmanage_appsor the voxel permission for that chunk. See wilderness. - Voxel writes are range-checked. A voxel outside 0–15 on any axis of its chunk, or a type
outside 0–255, is refused on every write path:
updateVoxel,sendVoxelUpdate,updateChunk'svoxelStatesandworld.set_voxels. - A player can take back an agreement to a CLIENT half:
execRevokeClientModConsentandexecRevokeAuthorTrust(CrowdyJS 18.0.3revokeClientModConsent,revokeAuthorTrust,ExecClientHalves.revokeandforgetAuthor; CrowdyCPP 0.52.0). See serving it to visitors. - CLIENT halves hold to the page's rules (CrowdyJS 18.0.2): a spatial or channel send goes out as an actor uuid the page derives for the grid, never one the CLIENT half names, and the page asks the player before the in-browser agent tests a draft.
- A refused chunk write-back is dropped by
ChunkStoreinstead of being retried (CrowdyJS 18.0.4, CrowdyCPP 0.53.0). - ck-exec 0.10's gateway checks the connect token before the WebSocket opens (a refusal is
HTTP 401, no longer a4401close) and limits pings, subscriptions, message size and new connections from one address. See call limits.
2026-09-28 (dev: the legacy engines removed)
Breaking, with no deprecation window, and on the dev environment only so far. The game API removed its four legacy developer-code engines: game models, automations, compute modules, and player code with its browser client modules and player models. That took 129 root fields, 140 types and 11 error codes out of the schema. ck-exec replaces them, and from the legacy engines maps every removed call to what replaces it; the engines' deleted pages redirect to their sections there. Test and production keep the engines until they move to ck-exec. (They did: test on 2026-10-04 and production on 2026-10-06.)
- CrowdyJS 18.0 removes their SDK surface (
client.gameModel,client.compute,client.playerCompute,client.playerModel, the Game Kit's engines andclient.operator), and Crowdy Studio runs on ck-exec only. 18.0.1 also drops what only platform staff can call. See CrowdyJS's migration notes. - CrowdyCPP 0.50.0 removes the same, and 0.51.0 wraps only what players, developers and org admins can call.
2026-09-25 to 2026-09-27 (dev: the ck-exec preview)
ck-exec, the runtime for your game's server code, is in preview on the dev environment. You write hubs (state, one instance per key) and spokes (stateless, scaled out) in Rust, build them to WebAssembly, and deploy them with the app; players call them over a WebSocket to an execution host. See the ck-exec overview. Each line below arrived in the CrowdyJS and CrowdyCPP versions it names, on their dev releases.
- Connecting players (
execConnect; CrowdyJS 17.9.0, CrowdyCPP 0.44.0): connect from a game. - Operating your code (
execLogs,execInstances,execVersions,execActivateVersion,execSetEnabled,execConnectAsDeveloper; CrowdyJS 17.10.0, CrowdyCPP 0.45.0): operations. - Builds on the platform and starter packs (
execBuild,execBuildStatus,execStarters; CrowdyJS 17.11.0, CrowdyCPP 0.46.0): builds and starter packs. - Mods, players' code on grids they own (
execModBuild,execModDeployand the rest; CrowdyJS 17.12.0, CrowdyCPP 0.47.0): mods. Since 2026-09-27 a mod bills its owner's player wallet once the owner's monthly trial (250,000 compute units in each app) is used, and an empty wallet or a spend cap switches off only that owner's mods (PLAYER_WALLET_EMPTY,PLAYER_SPEND_CAP): who pays for a mod. - Following one call and counters per endpoint (
execLogs(flow:),execEndpointStats; CrowdyJS 17.13.0, CrowdyCPP 0.48.0). From CrowdyJS 17.13.0 Crowdy Studio deploys a SERVER target as a mod. - CLIENT halves, a mod's browser code (
execModClientBuild,execModClientDeploy,execGridClientMods,execConsentClientMod,execTrustAuthor,execModClientArtifact; CrowdyJS 17.14.0 withExecClientHalves, CrowdyCPP 0.49.0): CLIENT halves.
2026-09-23 (CPU price)
The CPU rate is $0.20 per CPU-hour, one core. It was $3.60. A CPU-hour is one core busy for one hour, not one machine. The same price covers GraphQL resolvers, automations, compute modules, and player-authored compute. The monthly allowance is unchanged: 20 CPU-hours pooled per app. The price in effect is the rate card in your account and on the pricing page. A price applies from the next settlement period for usage that has already been billed this month.
2026-09-26 (Unreal SDK v2.17.0)
Entities that went missing now appear, and the Game Model path makes fewer calls. Most projects change nothing; read the HTTP/2 logging caveat if you ship or collect logs from a build with logging on. Per-item detail: What's Changed.
- Remote entities no longer go missing past eight per class. The actor pool grows on demand up to
MaxPoolSizePerClass(256), keeps a spawn event's actor until a pool actor is secured, and retries an entity it cannot draw yet instead of dropping it. See Rendering backends. - The Game Model path makes fewer, cheaper calls. Only invokes count toward the invoke allowance; one
state-applying pull per container is in flight at a time; remote copies bind from one paged list per type;
your own invokes no longer pull back what they wrote; busy refusals are retried by the SDK; HTTP/2 by
default (
crowdy.net.http2 0turns it off: a failed request's curl diagnostics can log its headers, bearer token included, in a build with logging on);ListContainersreads every page;crowdy.gamemodel.statsfor diagnostics. See Move from Game Models. - A value that changes only in letter case now applies, in the runtime and in Crowdy Studio.
- Known server issue: a player who just joined can stay invisible to others until they move. See Troubleshooting.
2026-09-23 (Unreal SDK v2.16.0)
One change, additive: the Backend selector gains a Test tier. Per-item detail: What's Changed.
- Backend gains a Test tier, and its default now follows the SDK build's own release tier. The
built-in hosts behind Dev and Production moved to
ck.dev.crowdedkingdoms.comandck.prod.crowdedkingdoms.com; the oldapi.dev.crowdedkingdoms.comandapi.crowdedkingdoms.comhosts no longer resolve. The new Test tier resolves tock.test.crowdedkingdoms.com. A project's default Backend now follows the build's own release tier instead of always defaulting to Production, so the public release still defaults to Production. Each tier vendors its own CrowdyCPP (0.42.1). See Project settings.
2026-09-22 (grid-scoped parity: Game API, CrowdyJS 17.7.0, CrowdyCPP 0.43.0, crowdy-dsh 0.4.0)
Player code inside a grid can now do everything app-scoped code can, confined to the grid. Additive, except that the crowdy-dsh bridge protocol moves to v4 (CrowdyJS 17.7 pairs with crowdy-dsh 0.4).
- Player host parity.
emit_channelandemit_eventno longer returndenied_v1. A module posts into its grid's grid channels and publishes on the grid event bus (other modules on the grid; studio modules that add agrid_eventtrigger).container_get_batch,edge_add/edge_delete,sessions_listandavatar_state_getanswer on a grid. Every spatial kind originates in the grid and reachesmin(distance, 8, spatialMaxDistance). Details: Player code. - Studio
emit_spatial:server_eventis opcode 139 andclient_event138 for studio modules too (they were 140 / 139, which CrowdyJS never decoded), and both require the[u16 eventType]prefix. - New GraphQL:
mintGridToken(grid-scoped tokens),createGridChannel,gridChannels,gameModelSessions(gridId),CreateSessionInput.gridId,GmSession.gridId,Group.gridId,PlayerComputeDeployInput.gridEvents, and thechannelEgress/spatialMaxDistance/gridEventEgressplayer policy knobs. - Compute SDK 0.1.6:
api::emit_event_to(target, name, payload); game kitkit-core::grid(publish, send_to, post, decode). - CrowdyJS 17.7.0:
client.grid(appId, gridId),client.grids,@crowdedkingdoms/crowdyjs/grid-program(the full SDK in a sandboxed JS program),startGridMod/createGridHostCalls, the broker allowlist from the platform host catalog, the page-local grid event bus for CLIENT mods. Guide. - CrowdyCPP 0.43.0:
client.grids()andsessions(..., gridId). - crowdy-dsh 0.4.0:
grid_context,grid_program_run,grid_program_statusagent tools.
2026-09-22 (Unreal SDK v2.15.0)
Six changes. One is a source break for C++ projects and is the only one that asks anything of a game; the rest are additive or remove work a project may have done. Per-item detail and the before-and-after of each: What's Changed.
- Eighteen array delegates pass
const TArray<T>&instead ofTArray<T>by value: the ten single-cast teams, avatars and channels callbacks, three multicast cache events, and five Game Model outcome events. A C++ project that updates and rebuilds gets a compile error naming the handler's parameter, and changesTArray<FCrowdyTeam> Teamstoconst TArray<FCrowdyTeam>& Teams. Blueprint pins are unaffected. Nothing changes silently: if it builds, it is right. - The actor pool draws with no Backend Config at all. Up to 2.14.0 a map profile with an empty
Backend Config, or one naming no Replication Policy Class, tracked remote entities and drew
none of them. It now falls back to the built-in
UCrowdyTransformRepPolicy, so authoring a Backend Config is optional. A project that worked around the blank screen can drop the workaround. See Rendering backends. - The shipped transform policy interpolates across a ring of samples instead of the two it used to keep, and extrapolates for at most 0.2 s past the newest, so a late or lost update no longer shows as a pause and a jump.
- A Player Derived pawn possessed after it begins play gets its account identity on
possession, respawned pawns included. Up to 2.14.0 the identity was read once at
BeginPlay, which every runtime-spawned player pawn misses, so it registered under a random id and anything keyed on the local player id did not work for it. Read the pawn's id andIsLocallyOwned()from On Crowdy Ownership Assigned, not atBeginPlay. See Entities, identity and ownership. - The Unreal client reads signed downlink bundles (CrowdyCPP 0.42.1 vendored). It
advertises
BUNDLE_SIGNEDonce the realtime connection is up and every 15 s after, and a Replication API v0.30.0 server then sends its notifications asMESSAGE_BUNDLE_SIGNED: one HMAC per datagram verified in the transport instead of one per member, with the messages a game receives unchanged. An older server ignores the advertisement and keeps the per-member form.crowdy.net.recv.signedbundles 0, read when a connection opens, turns the advertisement off for a before-and-after comparison. See Connection and reconnect. - Crowdy Studio shows every value a live model holds. The Game Model page's Live tab used to summarize a selected live model on one line that named a few attributes and then said "and N more". It now fills a panel under the instance list: one row per attribute with its value, what it holds and its description, the attributes the model declares that this live model has no value for (reading not set), a search box over names and values, Copy value and Copy values, and an Internal switch for the keys the runtime keeps for itself. The page's pre-seed strip hides itself while the Live tab is open, so the list and the panel get its height. See Game Models authoring.
2026-09-21 (Replication API v0.30.0 / v0.31.0, Game API v2.8, CrowdyJS 17.6, CrowdyCPP 0.42)
Everything here is additive; a client that does nothing new sees nothing new.
- Signed downlink bundles, opt-in (Replication API v0.30.0). A client that
sends
CLIENT_CAPABILITIES(opcode 29) with theBUNDLE_SIGNEDbit receives its bundled notifications asMESSAGE_BUNDLE_SIGNED(opcode 30): the same framing, members without a per-member HMAC, and one 32-byte HMAC over the whole datagram. Seven actor members fit per bundle instead of six and the server does one HMAC per datagram instead of one per member; measured on the test tier, egress per delivered notification fell 18 %. A client that never advertises is served exactly as before, and a server older than v0.30.0 ignores opcode 29. Details and the verification recipe: Wire formats — signed bundles. - CrowdyJS 17.6.0 and CrowdyCPP 0.42.1 advertise
BUNDLE_SIGNEDon everyreadyand every 15 s (a token refresh or a server-side migration resets the server's record). CrowdyCPP 0.42.0 built opcode 29 and never put it on the wire. CrowdyCPP verifies the trailing HMAC; CrowdyJS strips it and walks (it never verified downlink HMACs). Nothing to change in a game that is on CrowdyJS 17.6.0 or CrowdyCPP 0.42.1. - Replication API v0.31.0 — interest-scoped peer presence. Server-internal: the peer heartbeat no longer announces every actor to every server, so a sparse world no longer hits a fleet-wide chunk ceiling (~8 000 populated chunks) however many servers run. No wire change; nothing to change in a client.
- CLIENT compute mods: tick rate and mouse input (Game API v2.8.0, CrowdyJS
17.6.0). A CLIENT crate may set
[package.metadata.crowdy] tick_interval_ms(16–1000; default 1000) in itsCargo.toml— the only key admitted in that table — and callapi::pointer_clicks()(CLIENT only,inputcapability group, 400/s) to drain the host game's mouse clicks each tick. See Build mods — tick rate and mouse input. emit_spatial("server_event", …)is opcode 139 (SERVER_EVENT_NOTIFICATION) with the payload framed[u16 eventType LE][state…], andclient_eventis 138 (Game API v2.8.0). Before this aserver_eventwent out as an untyped generic spatial blob that typed decoders (CrowdyJS's event router, a CLIENT mod's scene catalog) never saw. Compute host API.
2026-09-19 (Game API v2.7.0, Replication API v0.29.x)
refreshAppTokenmay now answerauthorizedServer: nullwhile your server is healthy. A native client that passescurrentServeron refresh is told to keep its socket when the reply names a server and to callserverWithLeastClientsagain when it isnull; since v2.7.0nullis also returned when the node is running near its capacity and a cooler sibling has room — the refresh is the cheapest moment for the fleet to spread players, so under load expect to be moved occasionally. CrowdyJSPortalAPI.refresh(currentServer)and CrowdyCPPrefreshTokenalready do this; browser clients on the UDP proxy re-place on every refresh by design. Portals & app-scoped tokens.- Placement avoids hot servers (Game API v2.7.0) and the replication servers
rebalance resident players below their Full line (Replication API v0.29.0–v0.29.5,
which also bundles server-to-server traffic). Server-side only; a client sees at
most a
COMMAND_RECONNECTit already had to handle.
2026-09-16 (Game API v2.6.0, CrowdyJS 17.5, CrowdyCPP 0.41)
- Bulk container reads and keyed seeding.
gameModelContainerspages for real (omittedlimit= 200, max 1 000,BAD_REQUESTabove) and forwardsbindingKey;containerStates(appId, containerIds)(max 500) is the bulk twin ofcontainerState;seedaccepts a per-containerbindingKeyand a per-typescope: session | app;createSession({ seedFromApp })stamps the app's keyed template rows into the new session (max 2 000 rows). Seeded copies of an ended session are dropped after the tier's retention window — 7 days on every tier. CrowdyJS 17.5.0 wraps all of it (containers(),containerStates(),seed,createSession({ seedFromApp }),kit.matches.create({ seedFromApp })); CrowdyCPP 0.41.0 mirrors it. Game models, CrowdyJS game model.
2026-09-15 (Replication API v0.28.0, Game API / Management API)
Security hardening from an internal review of authentication and authorization. Nothing here changes a conforming client; each item says what would.
- Client→server spatial messages must be signed (Replication API v0.28.0). A
long-spatial datagram from a client with
containsAuth = 0is now dropped without a reply. The unsigned tail bound a packet to its session only by thegameTokenIdin it, which is not a secret. CrowdyJS, CrowdyCPP and the Unreal SDK have always signed; only a hand-rolled client that omitted the HMAC is affected. See HMAC and the worked example. - Per-session send-rate limit (Replication API v0.28.0): 500 messages/second sustained, bursts to 1,000, silent drop beyond. The busiest legitimate mix the docs describe (voice + video + actor updates, every bundle member counted) is roughly a quarter of that. See Rate limits.
- Revocation reaches the realtime plane.
logout,logoutAllDevices,changePassword,resetPasswordandrevokeAppAuthorizationnow tell the replication servers, so a native client's session ends within seconds instead of at its app token's expiry. - Tokens are stored hashed. Session, e-mail confirmation, password-reset and organization tokens are stored as a hash and cannot be shown again after the response or e-mail that carries them. App-scoped tokens are the exception (they are the per-message HMAC key). When each tier took this change every credential was reset once: signed-in sessions, app-scoped tokens held by game clients, organization tokens, and confirmation / reset links minted before it. Sign in again, re-mint app tokens, and request a new link where needed.
- A group fetched by id is served only to an app token for its own app.
channel(groupId),channelMembers,team,teamMembers, the role reads and every by-id channel/team mutation answerNOT_FOUNDwhen the app-scoped token belongs to a different app — and when the caller presents an identity session token, which is never a gameplay credential. Mint an app token (mintAppToken) to work on groups, as every first-party client already does. Same-app app tokens are unchanged. OrgTokenWithSecret.token's description now says what is true: returned once, stored hashed.
2026-09-14 (Game API v2.1–v2.4, CrowdyJS 17.2–17.4, CrowdyCPP 0.38–0.40)
- Game-model sessions (Game API v2.4.0, CrowdyJS 17.4.0):
leaveSession,setSessionAdmission,transferSessionHost,endSession,sessionSnapshot,sessionEvents,sessionInspectand thesessionChangedsubscription;GmSessioncarriesadmission,maxParticipants,participantCount,hostUserId,hostTerm,revision,endedAt,endReason. Two rules:leaveSessionrequires theincarnationthe join returned, and presence is the player's replicated actor — a participant with no fresh actor after the join grace window is expired by the server (create withpresence: 'none'to opt out). Error codesSESSION_FULL,SESSION_LOCKED,SESSION_CLOSED,SESSION_ENDED,SESSION_NOT_PARTICIPANT,SESSION_TARGET_NOT_PARTICIPANT,SESSION_INCARNATION_STALE,SESSION_HOST_TERM_STALE. Roster, admission, host and presence. - Third-party hosting on Crowdy Games (Game API v2.1.0, CrowdyJS 17.2.0):
client.hosting(claim a slug, publish a bundle, list), the Node subpath@crowdedkingdoms/crowdyjs/hosting(publishDirectory), andEmbeddedHostfor sign-in under the Crowdy Games shell. Hosting mutations take an identity session, never an app token. Hosting. - Guided "Create repository on GitHub" (Game API v2.3.0, CrowdyJS 17.3.0):
githubNewRepositoryUrl/githubRepositorySlug,controller.createGitHubRepository(),status.repositorySelection. The GitHub App still cannot create a repository itself. Crowdy Studio and GitHub.
2026-09-13 (Replication API v0.27.0, CrowdyCPP 0.37, CrowdyJS 17.1)
Clients may bundle their requests. MESSAGE_BUNDLE (opcode 2) — the framing the
replication server has always used to pack several notifications into one datagram —
is now accepted on the uplink too. Nothing removed; a client that keeps sending one
message per datagram is unaffected.
- Replication server v0.27.0: a client datagram whose first byte is
2is unpacked and every member is gated, authenticated and rate-accounted exactly as if it had arrived alone. Rules: ≤ 1232 bytes, ≤ 32 members, no nesting (a nested member is dropped, its siblings proceed), a framing fault drops the remainder of the datagram but keeps the members already processed, and the datagram's wire bytes are metered once. See Message Bundle per Datagram. - CrowdyCPP 0.37.0:
replication::Connectionbundles outbound sends by default (Config::bundleSends,Config::bundleWindowMs= 1 ms), flushing on the window, on capacity, onConnection::flushSends(), at the end ofWorldSession::tick(), before*AndWait, and on disconnect. A lone message is sent unwrapped.Stats::bundlesSentis new;datagramsSentmay now be less thanmessagesSent.bundleSends = falseis the 0.36 behaviour. See Replication client. - CrowdyJS 17.1.0: the same on the binary relay (
realtime.binaryTransport):realtime.bundleSends,realtime.bundleWindowMs,client.udp.flushSends(),client.realtime.binaryRelayStats(). The GraphQL transport is unchanged — the proxy signs one message per mutation. - Rollout order: deploy the replication server before shipping an SDK build
that bundles. Against an older server, two messages sent within a window are
dropped together;
bundleSends: falseis the escape hatch.
2026-09-13 (Game API v2.0.0, CrowdyJS 17, crowdy-dsh 0.3)
A bound GitHub repository is the project's working tree; GitHub stays optional. Breaking, without a deprecation window: the GitHub surface was nine days old and the deploy-input change is what makes "what compiles is what was saved" true.
- Optional, reversible.
CrowdyStudioProject.sourceisSTUDIOuntil the owner binds a repository andGITHUBwhile one is bound;crowdyStudioProjectCreateis unchanged.crowdyStudioGitHubBindgains a requiredinitial—PUSH_PROJECTcommits the project into the branch (GITHUB_REPO_HAS_FILESif it already has rust under the layout roots),TAKE_REPOSITORYadopts the branch (GITHUB_REPO_EMPTYif it has none). Unbind keeps the files. - While bound,
filesis the server's mirror of the rust under the layout roots atgithubSha, read as before.crowdyStudioProjectSaveFiles,crowdyStudioProjectSavewith file bodies andcrowdyStudioProjectImportFilerefuse withGITHUB_BOUND_USE_CONTENTS; writes arecrowdyStudioGitHubPutFile/ newcrowdyStudioGitHubDeleteFilecommits carryingexpectedCommitSha(stale →GITHUB_STALE_SHA; the blobshais now optional). NewcrowdyStudioGitHubRefreshbrings the mirror to the branch head; newcrowdyStudioGitHubLayoutresolvescrowdy.jsonserver-side (clients must not parse it).crowdyStudioGitHubTreereturns{ commitSha, entries };Tree/File/Layouttake an optional full 40-hexcommitSha. - Removed:
crowdyStudioGitHubSetAutosaveandCrowdyStudioGitHubStatus.autosave;DeployPlayerComputeInput.sourceFilesJson,sdkVersion,abiVersion.playerComputeDeploynow takesprojectId(+commitShafor aGITHUBproject) and the server resolves the source;PlayerWasmModuleVersiongainsprojectId,sourceRevision,githubCommitSha. The org compute API (computeDeployVersion) is unchanged. - Tokens: the app token may call Status / Layout / Tree / File / PutFile / DeleteFile / Refresh (owner-scoped); ConnectUrl / Repos / Bind / Unbind need the identity session. A game never needs an identity session for GitHub. Every GitHub field is datacenter-only.
- CrowdyJS 17.0.0:
saveProjectcommits a bound project automatically (one commit per changed file);bind(…, initial),refresh(),layout(),deleteFile(), thegithubembed option;pushToGitHub,pullFromGitHub,setAutosave, the Push / Pull buttons and the SDK-sidecrowdy.jsonhelpers are gone. Bridge protocol v3;@crowdedkingdoms/crowdy-dsh0.3.x writes a bound project the same way. - See Connect your GitHub repo to a Studio project and Player code.
2026-09-11 (Game API, CrowdyJS 16, CrowdyCPP 0.34)
The Crowdy Agent orchestrator is replaced by the in-browser DeepSeek Harness and a metered model endpoint. Breaking, without a deprecation window: the orchestrator was allowlisted development that no released game depended on except through CrowdyJS 15's agent dock, which is retired with it.
- Removed from the Game API (21 root fields): queries
crowdyStudioAgentSession,crowdyStudioAgentSessions,crowdyStudioAgentHistory,crowdyStudioAgentToolDescriptors,crowdyStudioAgentBudget; mutationscrowdyStudioAgentCreateSession,...AttachClient,...SetMode,...AcknowledgeEvents,...Heartbeat,...SendMessage,...ApproveTool,...RejectTool,...ToolResult,...GrantLease,...RevokeLease,...Pause,...Resume,...CancelRun,...CloseSession; subscriptioncrowdyStudioAgentEvents. The six tables behind them are dropped. A CrowdyJS 15.x client loses its agent dock when this deploys. - Added: REST
GET /v1/model/modelsandPOST /v1/model/chat/completions(OpenAI-compatible, app-token bearer, ZDR routing, per-request reservation and settlement at the app's rate card); GraphQLcrowdyStudioProviderConsent,crowdyStudioSetProviderConsent,crowdyStudioModelUsage;SetCrowdyStudioAgentAppPolicyInput.fundingwithpayerKind: PLAYER | ORG(defaultPLAYER;ORGneedsmanage_billing); theplayer_model_microusdmetric in hourly player billing andAGENT_FUNDS_NEEDED(HTTP 402). See Agentic Crowdy Studio and the model endpoint. - CrowdyJS 16.0.0:
dshmount option and@crowdedkingdoms/crowdyjs/crowdy-dshreplace theagentoption,client.crowdyStudioAgent,/crowdy-agent,PlayerControlGate,AgentControlBannerand the lease manager. Games ship the harness from the published@crowdedkingdoms/crowdy-dshpackage under/dsh/of their origin. See Agentic Crowdy Studio. - CrowdyCPP 0.34.0:
crowdy/agent/*, the Studio host adapter, agent projection, lease manager and control gate are removed;CrowdyStudioAgentAPIkeeps policy/usage and gains consent and model usage. - Management UI: the app's Agent policy panel gains "Who pays for model usage" (player wallet by default; org wallet for billing admins) and a usage table with the payer.
- Rollout order: ck-api first (this removes the 15.x dock on that tier),
then CrowdyJS
16.0.0-<tier>.N, then@crowdedkingdoms/crowdy-dsh, then the games' pins. Schema contraction (the six tables) is a separate--allow-contractorder after the ck-api deploy.
2026-09-10 (Game API)
Cookie CSRF matches any presented ck_csrf. (ck-api v1.98.0, Studio
v1.23.0)
- Prod's
.crowdedkingdoms.comDomain leftover plus the tier cookie no longer fails hosted/authorizewithCSRF_MISMATCH. The guard accepts a header that matches anyck_csrfon the Cookie header. Studio last-wins and prefers this tier's API-host Domain. - Action: none for SDK / game clients (Bearer skips CSRF). Do not ask us
to narrow the prod cookie Domain — public
studio.crowdedkingdoms.comneeds it.
2026-09-09 (Game API)
Studio session moves to an HttpOnly cookie + CSRF. (ck-api v1.92.1, Studio v1.21.0)
- Sign-in still returns
AuthResponse.tokenfor native clients, CrowdyJS, Construct, and scripts. Those keepAuthorization: Bearerand skip CSRF. - Studio no longer writes
localStorage.auth_token. ck-api setsck_session(HttpOnly; Secure; SameSite=Lax) and readableck_csrf. Subsequent Studio GraphQL calls send credentials plusX-CSRF-Token. - Cookie auth from a non-first-party
OriginisCOOKIE_AUTH_ORIGIN_REFUSED. Missing or mismatched CSRF isCSRF_REQUIRED/CSRF_MISMATCH. Customer origins still hold only app tokens from hosted/authorize. - Prod cookie Domain is
.crowdedkingdoms.comso publicstudio.crowdedkingdoms.comcan send CSRF.v1.92.0derived.prod.crowdedkingdoms.comfrom the labelledFRONTEND_URLand public Studio never receivedck_csrf. - Action: none for SDK / game clients. Studio users sign in again if an
old
localStoragebearer has expired; leftover bearers keep working until then.
Idempotent replay keeps ISO timestamps; voxelState is optional. (ck-api
v1.91.0, CrowdyJS 15.8.0)
- Nest's default DateTime scalar serialized a replayed ISO string as
null, which 500'dgrantAppAccess(and any other@Idempotent()Date field) on retry. The registeredDateTimescalar accepts the string. VoxelUpdateRequestInput.voxelStateis optional. CrowdyJSsetVoxelomits the field when there is no state.- CrowdyCPP 0.31.0 adds
refreshAsync(ip4, port)so an async gameplay token rotation can name the current Buddy (the sync overloads already could).
2026-09-08 (Game API)
Invoke policies now apply to app admins. bypassPolicy is the explicit, audited
override. (ck-api v1.89.0)
- Until now a caller holding
manage_appsskipped every Game Model invoke policy implicitly, with nothing on the result to say so. A developer testing their own game with their own account therefore saw policies that appeared unenforced. From v1.89.0 an admin'sgameModelInvokeis judged exactly like a player's. InvokeFunctionInput.bypassPolicy: Boolean(defaultfalse) skips the policy for one call. Honoured only withmanage_apps(NOT_ALLOWEDotherwise); the result carriesGmInvokeResult.policyBypassed: trueand the call is audit-logged.- A stored
conditionleaf with no compiledastnow refuses rather than throws. - Clarified in Game models:
gameModelFunctionsstrips the compiledastfrominvokePolicyJsonon read-back, andself.owner_user_idin a condition is a declared property, never the row owner ($self_owner_id/owner_of_self). - Action: GM tooling that ran with an admin account and depended on the skip must
add
bypassPolicy: true. Nothing else changes.
2026-09-08
Hosted sign-in: a browser game on its own domain signs players in through Studio. Direct sign-in is first-party only. No deprecation window.
- Breaking, and deliberately without a window (the platform has no third-party
customers in production yet). Every direct sign-in mutation on the Management API --
login,register,requestLoginLink,completeLoginLink,socialLoginStart,socialLoginComplete,checkAuthMethod,requestPasswordReset,resetPassword,confirmEmail,resendConfirmationEmail, and the/auth/*REST twins -- is served only to first-party browser origins (Crowded Kingdoms Studio, crowdy.games) and to non-browser callers (noOriginheader: Node, CLIs, Unreal/Unity, CrowdyCPP). From any other browser origin the answer isextensions.codeHOSTED_SIGN_IN_REQUIRED(403). ck-apiv1.88.0. - What to do instead: send the player to Studio's hosted sign-in page
(
https://studio.<tier>.crowdedkingdoms.com/authorize) with a PKCE challenge and exchange the code they return with for an app-scoped token (exchangePortalCode, unchanged and public). In CrowdyJS 15.6.0 that isclient.portal.signIn({ appId, redirectUri })andclient.portal.handleSignInCallback();isHostedSignInRequiredErrorrecognises the refusal. Your origin must be one of the app's redirect URIs (Studio > Apps > Settings > Sign-in & redirect URIs). Why: a page on a customer's domain that collects a Crowded Kingdoms password is indistinguishable from a phishing page. Sign in · Portals & app-scoped tokens. - CORS follows the same registry. The API accepts browser requests from first-party origins and from every origin in some app's redirect URIs -- read live, honoured within ten seconds of a save, no restart. Anything else gets no CORS headers. Until now the API reflected any origin.
- Sessions are revoked on recovery.
resetPassworddeletes every session of the account (and every app token minted from one);changePassworddeletes every session but the calling one. Both used to leave them alone. - Rate limits on every public sign-in mutation, per address and per client;
logincounts failures per address (ten in fifteen minutes answersRATE_LIMITED). Masked mutations stay masked when limited. - GraphQL introspection is off on every tier. The SDL is published here and ships in CrowdyJS. Queries deeper than 12 levels or wider than 2,000 fields are refused at validation.
- The Construct 0.3.0 follows: hosted sign-in, and Setup runs from
npm run setup(it needs a session the browser no longer holds). Crowdy-Games: every game's authorize URL is Studio's; the Overworld'sauthorize.htmlforwards there.
2026-09-07
The Construct — the public starter repository; simple-web-demo retired
CrowdedKingdoms/the-constructis the public starter for browser games on Crowded Kingdoms and the build-a-game tutorial's companion: an engine-agnostic platform layer over CrowdyJS 15.4 (two tokens, datacenter routing, token rotation, World Stores), a three.js hub and a pixi.js paint program driven by one session, a kit-seeded game model, Crowdy Studio embedded with SERVER and CLIENT mods, and a Setup that creates your organization and app with no card (from inside the game until 2026-09-08; fromnpm run setupsince). MIT; a GitHub template repository; cloneprod.- The
simple-web-demorepository (June 2026; afile:SDK dependency and the retired per-developer environment handles) is removed. Nothing it demonstrated is lost: every chapter of the tutorial still stands alone, and The Construct'sPaintSceneis the same shared canvas built with World Stores. - Embed Crowdy Studio no longer tells new games to start SERVER-only; it describes both postures and points at The Construct for the full one.
- Create your first app now matches the live wizard: App (with datacenter) → Review, no billing step; the free allowance is monthly.
2026-09-06
ck-api v1.85.0 — the card is bytes, CPU-hours and storage; usage is charged as it arrives; the development quota is monthly
One pricing change, one cadence change, and the counts are gone.
- Six count dimensions are retired:
replication_recv_msgs,replication_send_msgs,graphql_recv_ops,udp_notifications,wasm_egress_msgs(shared card) andplayer_egress_msgs(player card). A datagram, an API operation or a notification is paid for by the bytes it moves and the CPU it takes — both of which were already priced — so a count was the same action billed twice. Their rate and allowance rows are deleted on every tier;setBillingRaterefuses the names; the counts are still recorded and shown inappUsageSummary. Every operation a client can call is now audited against the card indocs/billing-coverage.md(ck-api repo). - Every dimension is charged as the usage arrives. Until now bytes were billed
within a minute (monthly aggregate, rounded up once, debited as each cent was
crossed) while every other dimension waited 60–75 minutes for the hour to close
and settle. Now every dimension takes the byte path. The hourly rows in
appSharedUsageChargescontinue, as a statement:amountCentsis what was debited during that hour andwalletTransactionIdis null (the debits have their ownshared_usagetransactions);usageSnapshot._pricing.modeis"statement"on rows written under the new rule. - The free tier is a monthly development quota. Per app per UTC calendar
month: 5 GB egress, 5 GB ingress, 20 CPU-hours pooled across GraphQL
resolvers, automations and compute modules (
compute_cpu_mson the card), and 1 GB-month of stored data. Compute-module writes carry no allowance. The hourly allowances are gone. - Rate card fields.
RateCardEntry/PublicRateCardEntrygainfreeUnitsandfreePeriod(HOUR | DAY | MONTH);freePerHouris now null for any allowance that is not hourly, andfreePerMonthis the monthly-allowances table (the byte aggregates and the player trial) as before.SetBillingRateInputgainsfreeUnitsandfreePeriod;freePerHourstill works and meansfreeUnitswithfreePeriod: HOUR. - Player billing is unchanged in cadence (per closed hour against the monthly
trial pool); the retired
player_egress_msgsis the only player-card change.
2026-09-01
ck-api v1.73.0 — nothing runs for an app with no player in it; reservations split
Three behaviour changes, one reprice, and the API Terms are published at last.
-
alwaysOnis retired andcomputeUpsertModuleREFUSESalwaysOn: true.WasmModule.alwaysOnis@deprecatedand alwaysfalse. A compute module now ticks only while its app has at least one player connected somewhere in the fleet, and stops when the last one leaves. This is a removal without a deprecation window on the INPUT side, because the alternative was to keep accepting a flag that no longer did anything: an app passingtruewould have believed its world was advancing while it was not. Thealways_on_module_minutesbilling dimension is gone with it — it could only ever meter zero. -
Scheduled work is presence-gated. A
scheduleautomation that comes due while its app is empty is skipped and rescheduled from the moment a player returns; the missed runs are never made up. Timers wait and fire late rather than firing into an empty world.eventandmanualtriggers, and synchronouscomputeInvoke, are unaffected.Write for it. Advance the world by
now - lastRunrather than one step per run, and store expiries as timestamps rather than remaining-tick counters. An automation that assumes a cadence will silently fall behind whenever nobody is playing. See Presence. -
Reservations split into two dimensions and mean something different.
App.reservedEgressBytesPerSecis@deprecatedin favour ofApp.reservedUdpBytesPerSec, joined by a newApp.reservedGraphqlOpsPerSec(GraphQL operations/sec). Reserving one does not reserve the other.A reservation is now a capacity floor: it obliges CK to provision and hold that much for you. It is not a ceiling (use above it is metered, not refused), not a data allowance (the fee buys capacity, not volume, and is charged in addition to metered usage — reserving 5 MB/s does not make the first 5 MB/s free), and not how the free-tier cap is lifted. Funding the org wallet or enabling auto-billing lifts the ~1 MB/s shaping; before today a reservation doubled as that bypass. See Reserved capacity.
-
Egress repriced to 19c. Byte dimensions moved from 15c to 19c per GiB, and
aggregate_data_volumeis 19c per decimal GB. The free tier is unchanged at 5 decimal GB of client egress per app per calendar month. -
The measurement basis is stated. Volume is egress only, measured as wire bytes at the network interface — including the transport and network headers each frame carries — and counted after any compression the service applies. Ingress is metered but does not count toward Aggregate Data Volume. A client-side byte counter will not match your bill.
-
The API Terms of Service and Rate Card are now published. Previously they existed but were not reachable: API Terms of Service, Free Tier and Billing Basis, SDK Developer Terms.
-
SDKs. CrowdyJS 15.4.0 drops
alwaysOnfrom the module fragment, addssharedEnvironment.setReservedThroughput(), and reads both reservation dimensions. CrowdyCPP 0.29.0 does the same and documents thatConnection::Statsbyte counters are a local diagnostic that will not reconcile against a bill. Both previously told developers to setalwaysOnfor modules that must run without connected players.
2026-08-28
ck-api v1.67.0 — a notification aimed at another app's channel is now caught
channel_name, and why you should switch to it. A channel notification now takespayloadplus exactly one ofchannel_idorchannel_name. A name is resolved on every invocation against the app the function is running in; an id is resolved once, when you wrote it. That matters because channel membership is scoped to the app, so a function naming a channel that belongs to a different app produces a notification that is built, sent to every server, and dropped for want of a recipient — while your invoke succeeds and the run records success, because emission is best-effort and never fails your function. The only symptom is silence. This is what an app recreated or moved between organizations leaves behind when its model is copied with an id in it. Your client was never affected: it joins__crowdy_session_<appId>by name and follows the app it is connected to. This gives the server the same property. Existingchannel_idnotifications keep working unchanged.- Two new system params,
$app_idand$session_channel_name, so a model never has to write down which app it belongs to. - Two new lint codes on
gameModelLint:notification_channel_foreign(error — the channel belongs to another app) andnotification_channel_unknown(warning — no such channel here). Only literal ids and names can be checked; a computed one cannot, which is a further reason to prefer a name. - You can now see it happening.
gameModelAppDiagnosticsgainsnotificationsEmitted24handnotificationsUndeliverable24h; a non-zero undeliverable count beside a healthy run history is this bug. The offending function is named by a newNOTIFICATION_UNDELIVERABLEentry inuserCodeFaults. Note this is not "delivery is broken" — it counts datagrams that were delivered to every server successfully and had no recipient.
2026-08-22
ck-api v1.61.0 → v1.63.0 — a completed purchase is now fulfilled
All three releases are on dev, test and production (prod/v1.63.0).
- Purchases were being charged and not fulfilled. A payment provider tells us a
session completed by calling a webhook, and that callback was the half that was
broken: the URL each provider held named a host that had been decommissioned, on
every tier. The hosted checkout worked, the card was charged and the money moved —
and nothing on our side ever heard about it, so no entitlement was granted and no
checkout reached
completed. If you have a customer whose payment succeeded and whose entitlement never arrived, that is this, and it is fixed. - PayPal webhook signatures are verified against PayPal's published certificate rather than only through the provider's verification endpoint (v1.61.0). Both verdicts must agree, so a forged callback is rejected even if one path is unavailable.
- A webhook naming a checkout we no longer hold is recorded instead of dropped (v1.62.0). It previously failed to insert and was retried forever.
- Stripe's
Invoice.subscriptionmoved in their Basil API version and we were reading the old location, so subscription-lifecycle events were delivered, verified, recorded — and inert (v1.63.0). Renewals and cancellations act again.
Nothing about the request you make changed: no field was added, removed or renamed by any of the three. This is behaviour a client cannot see and can only be told about.
2026-08-21
ck-api v1.60.0 + CrowdyJS 15.1.0 — error codes are correct, and a passwordless account can add a password
Two changes an integrator has to act on, both live on dev, test and production.
extensions.codeis now correct for roughly 155 refusals. Until v1.60.0 only four HTTP statuses reached you as a distinct code (400, 401, 403, 422); everything else — everyNOT_FOUND, everyCONFLICT— arrived asINTERNAL_SERVER_ERRORwith the real status inextensions.httpStatus. If you branched onhttpStatusyou are unaffected. If you branched oncode, re-read Error codes before your next release.Throws CONFLICTno longer appears in any mutation description. Where a refusal stopped carrying that code, the description stopped claiming it.- New:
setInitialPassword(newPassword). A signed-in user whose account has no password — one created by magic link or a social provider — can add one from the session alone.changePasswordstill requires the current password and now refuses such an account withPASSWORD_NOT_SETinstead ofUNAUTHENTICATED. - Four new codes, all of which mean the session is fine and the user should not
be signed out:
PASSWORD_ALREADY_SET,PASSWORD_NOT_SET,INVALID_CURRENT_PASSWORD,EMAIL_ALREADY_REGISTERED. The first three previously arrived asUNAUTHENTICATED, which is also what an expired session looks like — so a client that signs the user out onUNAUTHENTICATEDwas signing them out for mistyping a password. - CrowdyJS 15.1.0 wraps all four password mutations (
auth.setInitialPassword,auth.changePassword,auth.requestPasswordReset,auth.resetPassword) and adds three error-code predicates. Games pin the SDK exactly; there is no caret to carry you onto it.
See Sign in and Error codes.
2026-08-20 (email)
ck-api v1.53.0 — transactional email is actually sent
- Magic links, email confirmations and password resets now arrive. Sending was
switched off on every tier before this release, so every flow that depends on the
player reading an email could be started and never completed. They are sent from
noreply@crowdedkingdomstudios.comon dev, test and production. - This is what makes the bypass removal below survivable, and the order matters: an account the bypass created has no password, and a magic link is the way back into it.
- A hard bounce or a spam complaint suppresses an address, and a later successful delivery clears only a transient bounce. If a player reports never receiving anything, a bad address earlier in that address's history is the first thing to check rather than the last.
- No schema change: no field was added, removed or renamed.
2026-08-20
BREAKING — the dev sign-in bypass is gone from every tier
devLoginis deleted, and so is thedevTokenfield onrequestLoginLink. Neither exists on dev, test or production; there is no environment in which authentication is weaker than production.devLoginreturned a session for any address with no proof of ownership, anddevTokenput the emailed one-time token in the response body.- Use
login/registerinstead. Email + password has existed throughout and is a first-class permanent method, not a fallback: any page that described this platform as passwordless was wrong. - SDK wrappers were removed in CrowdyJS 15.0.0 and CrowdyCPP 0.26.0. The Unreal
SDK's
DevLoginentry point and its Dev Login Blueprint node have nothing to call in any build that still ships them. - An account the bypass created has no password, so the bypass going away removes
the only way it was ever signed into. Sign in with a magic link to that address, then
setInitialPassword(above) to attach one.
2026-08-18
BEHAVIOUR CHANGE — a player's free usage is a monthly trial, not an hourly allowance
- There is no hourly free allowance for a player any more. It is zero. In its place each (player, app) pair gets a pooled monthly trial — 250,000 compute units by default — after which usage is charged to the player's wallet. An hourly figure made a small mod permanently free, which is not what a trial is for; a monthly pool is spendable in one afternoon or across a month, and it is the same total either way.
- Read the allowance rather than assuming it.
billingRateCard(scope:)carries the per-metric free monthly and free hourly figures alongside the price. A tier's card is data an operator can correct, so a figure quoted in a client is a figure that will be wrong. - Read the unit as well as the price. A rate is a price and a unit
(
unitLabel/unitQuantity), and they are one number:20c per GiB-monthand20c per 100 MB-hourare the same price and differ by four orders of magnitude in what they cost. ck-api v1.50.0 made a rate correction move both together. - Sub-cent usage no longer rounds up per metric. It carries in whole micro-cents and rounds once across all metrics at the end of the hour. Without that, zero hourly free would have put a floor of roughly 60c–$1.20 a month on a player who does almost nothing.
- A developer's markup is paid into the org wallet in the same transaction as the
player debit (ledger entry type
markup_payout), instead of accruing to a balance nothing ever paid out from. - The org's own shared free allowance is a separate figure and is still hourly. Nothing above changes it.
2026-07-27
BREAKING — one API origin, and customer-provisioned environments are retired
Draft — the wording of this entry is under review. It records a change that reached customers on 2026-07-27 and was never written down here, which is why an integrator could still find a five-step provisioning recipe in these docs a month later.
- The Management API and the Game API are two surfaces of one origin, and one
server answers both. The separate management service was absorbed on 2026-07-27 and
its repository archived.
management-api.graphqlis now that unified SDL — it is the same schemagame-api.graphqldescribes, published under both names because the guides are split that way. Base URLs are per tier, never per organization. - The customer-provisioned environment surface is gone from the published SDL:
environmentDatacenters,environmentFlavors,environmentQuote,createEnvironment,orgEnvironment,linkAppToEnvironment,redeployEnvironmentandenvironmentRedeployPlan. Both classes went with it — the multi-VM dedicated stack and the single-VM developer sandbox (environmentClass: "dev_single"). They were retired without replacement in that form; there is no per-tenant stack to provision and no API for one. - What to use instead:
publishAppToShared. Every app runs on the tier's shared platform, scoped by itsappId. It has been in the published SDL since this changelog began, so this is a surface being removed rather than a capability arriving: publishing is free under your org's app-slot quota (platformConfig.freeAppsPerOrg, default 3) and metered against the org wallet beyond it. Read the app'sgameApiUrl/deploymentTargetbefore a player joins, and discover the tier's origin fromplatformConfig.sharedGameApiUrlrather than hard-coding it. See Shared environment & billing. - This is not a deprecation window. The fields are absent, so a call naming one
fails to validate against the schema rather than returning a deprecation warning. If
you built against them,
publishAppToSharedplus the app's routing fields is the whole migration — there is no per-component flavor, scaling bound or datacenter choice to carry across, because there is no stack to size. - Customer-provisioned environments stay retired; every app uses the shared
platform (
publishAppToShared). Contact Crowded Kingdoms if you need enterprise isolation.
2026-07-24
Game API v0.21.0 + CrowdyJS 12.2.0 + CrowdyCPP 0.15.0 — app-scoped active player-count events (additive)
- New
gameModelActivePlayerCount(appId)snapshot returns the best-known app-wide gameplay-session count plusstatus(FRESH/PARTIAL/UNAVAILABLE), nullableobservedAt, andrevision. OnlyFRESHis authoritative; missing telemetry is never silently reported as an authoritative zero. Requests require a bearer app-scoped token matchingappId. - New best-effort, post-observation
gameModelActivePlayerCountChanged(appId)subscription publishespreviousCount,currentCount,delta,revision, andobservedAt. Consumers deduplicate by revision and re-query the snapshot on startup, reconnect, or a revision gap. - The gauge counts active app-scoped gameplay sessions, not distinct users, actors, game-model sessions, or per-server load. Explicit disconnect or deauthorization, token expiry, and inactivity expiry remove a session; abandoned sessions can linger for roughly 120 seconds plus observation latency, and brief reconnect overlap can transiently count twice.
- Automations can use
gameModelUpsertAutomationTrigger(onEvent: "player_count_changed"). Runs receiveprevious_player_count,current_player_count,player_count_delta, andplayer_count_revision; event values override same-named static params.debounceMsuses trailing-edge coalescing, while model-event filters are invalid. Existing autonomous-function guards, budgets, circuit breakers, and metering still apply. - CrowdyJS exposes
client.gameModel.activePlayerCount(appId)andclient.gameModel.activePlayerCountChanged({ appId }, handlers). - CrowdyCPP exposes typed
gameModel().activePlayerCount(appId)sync/async reads andgameModel().activePlayerCountChanged(appId, callbacks)over its GraphQL subscription client.
See Game Models → Active player count and Autonomous processes → Player-count changes.
2026-07-24
Game API v0.20.0 — ensured containers and $self_container_id
Additive Game API release for game-model developers:
- New
gameModelEnsureContainermutation: an atomic get-or-create for game model containers keyed by an opaque, caller-chosenbindingKey(scoped to app + type + session). N concurrent callers converge on one container without client-side leader election. Returns the container plus acreatedflag; creation-only inputs (displayName,properties, …) are ignored when the container already exists. See Ensured containers. GmContainerexposes the new nullablebindingKeyfield, andgameModelContainersaccepts abindingKeyfilter.- New
$self_container_idsystem parameter available in model function bodies, policyconditionexpressions, and notificationargsexpressions — it names the container the expression runs against and cannot be spoofed by caller-supplied params. See Model-driven notifications.
Schema change is additive only (nullable column + partial unique index); no realtime wire or Replication API impact.
2026-07-24
Agentic Crowdy Studio — historical development rollout (superseded)
Historical. The version numbers in this entry describe the July 2026 development train. They are not the live stack. Current CrowdyJS:
npm view @crowdedkingdoms/crowdyjs dist-tags.
The coordinated development stack at that time was: environment release
v0.1.94 with Game API v0.19.16, Management API
v0.1.193-dev, CrowdyJS 12.0.0, the matching Blocks with Friends
bundle, and these public docs. It was a development rollout—not
production or general availability—and did not authorize unattended
real-money activity.
- OpenRouter now uses the stable streaming
/api/v1/chat/completionstransport, not the Responses beta. Requests enforcerequire_parameters, ZDR,data_collection: "deny", no plugins, and no unsafe fallback. Multi-tool provider rounds are rejected or serialized locally; every tool name, input, and output remains strictly validated against the local pinned descriptor. - The allowlisted development model is
openai/gpt-oss-120bbecause its tool endpoint supports ZDR. Model, request/token/cost caps, and usage remain platform allowlisted and funded. The provider key remains encrypted and server-only. - Sanitized live evidence passed Ask's expected exact response; Build
workspace.file.read; checkpointedworkspace.file.patchwith a source revision advance; and an agent-edited draft compile after ordinary platform ABI boilerplate was added. - Play completed a bounded
game.observedispatch/result and dispatchedgame.control.move. Human input then revoked the lease and preempted the run; a late success from the old context was rejected withAGENT_CONTEXT_STALE. - The deployed BWF bundle, visible takeover banner, and offline/local Stop browser gates passed.
- Stabilization train (historical):
v0.1.94was the final tracked environment manifest for that July 2026 train. Intermediary direct-ingest manifests used during stabilization were backfilled into release history and are not separate supported targets. Do not redeploy from this entry — usedeployed-versions.shfor what is live.
The public record deliberately excludes account identifiers, session/tool IDs, credentials, content hashes, secret values, and provider bodies.
2026-07-23
Development preview — Agentic Crowdy Studio (CrowdyJS 12, Game API, Management API, Blocks with Friends)
This coordinated contract is implemented on development branches and reflected in the downloadable development schemas. It is not a production deployment or general-availability announcement. The feature remains disabled/killed by default, and this preview does not authorize production, unattended real-money activity, wallet actions, or broad autonomous gameplay.
- CrowdyJS 12 (breaking greenfield agent contract): adds
@crowdedkingdoms/crowdyjs/agentand/player-host, while/crowdy-studiogains the integrated Ask/Build/Play dock.client.crowdyStudioAgentis the typed app-token Game API transport for durable replay/ack, epochs, exact approvals, leases, heartbeat, tool results, and reconnect. The immutablecrowdy.agent-tools/1registry, execute-once browser dispatcher, checkpoint-aware project controller, andcrowdy.player-host/1contracts contain no provider key, raw GraphQL executor, DOM driver, shell, fetch, UDP escape hatch, or client-mod bridge. Existing manual Studio mounts remain unchanged whenagentis omitted. - Game API Agent GraphQL: adds five owner/app queries, 15 idempotent control
mutations, and the ordered
crowdyStudioAgentEventssubscription under thecrowdyStudioAgent*prefix. Durable sessions/runs/events/tool calls, approvals, workspace/Play leases, checkpoints, budgets/usage, client epochs, provider orchestration, policy freshness, and stableAGENT_*errors live in the Game API. The pilot registry advertises only implemented, policy/mode/host-filtered tools and separately requiresuse_studio_agent; live deploy, destructive, trust, economic, and irreversible work remains exact-human-approval gated. - Management policy GraphQL and S2S: adds app reads
crowdyStudioAgentPolicy,crowdyStudioAgentEffectivePolicy,crowdyStudioAgentUsage, app mutationsetCrowdyStudioAgentPolicy, and operatorcpCrowdyStudioAgentPlatformPolicy,cpSetCrowdyStudioAgentPlatformPolicy, andcpSetCrowdyStudioAgentAppKill. Platform/app model, tool, mode, risk, budget, privacy, retention, and kill layers publish the nestedcrowdy.studio-agent-policy/1replica contract; sanitized terminal usage returns throughcrowdy.studio-agent-usage/1. Missing/stale policy fails closed.use_studio_agentis app-only, separately grantable, and not in the default tier. The pilot is platform-funded and never debits a player wallet. - Blocks with Friends host: adds a bounded
BwfPlayerHostAdapter, observation builder, exact generic command router over the same typed intent services used by humans, synchronous input/death/offline takeover, and an accessible external lease/Pause/Stop banner. Observations expire and are bounded; commands bind observation/host/entity revisions. High-risk grid/trust/commerce and PvP actions remain unadvertised. - Public guides now cover the player/SDK flow, game-host boundary, and Management app policy.
CrowdyJS 11.1 + Blocks with Friends: resizable Crowdy Studio
- Crowdy Studio now fills and observes its host element, relayouts Monaco when a split pane changes size, and responds to container width instead of the whole browser viewport.
- Blocks with Friends embeds it as a resizable right-hand desktop dock with a live game viewport, focus-scoped game/editor input, and a full-screen narrow-screen fallback.
- Source still autosaves independently from runtime. Creators explicitly choose Test draft or Deploy live before SERVER or CLIENT changes compile and apply; this release adds no auto-deploy and changes no GraphQL schema.
CrowdyJS 11 + Game API: Crowdy Studio rename
- New private Game API project storage separates autosaved SERVER/CLIENT source from immutable compile versions. Projects use optimistic revisions; personal-library files stay owner-only; studio common files are immutable and copy into projects by value.
- Final Game API roots are
crowdyStudioProjects,crowdyStudioProject,crowdyStudioProjectCreate, atomiccrowdyStudioProjectSave,crowdyStudioProjectSaveMetadata,crowdyStudioProjectSaveFiles,crowdyStudioProjectSetArchived,crowdyStudioLibraryFiles,crowdyStudioLibrarySave,crowdyStudioLibrarySetArchived,crowdyStudioCommonFiles,crowdyStudioCommonPublish,crowdyStudioProjectImportFile, andcrowdyStudioProjectCreateFromModules. Project, library, common-file, input, and enum schema types now useCrowdyStudio*; theplayerCompute*deployment/runtime surface is intentionally unchanged. - CrowdyJS
11.0.0exportsmountCrowdyStudioandCrowdyStudioControllerfrom@crowdedkingdoms/crowdyjs/crowdy-studio; the project provider isclient.crowdyStudio. Crowdy Studio adds cloud autosave/conflicts, project file CRUD, My Library/Common Files, target-aware Monaco/fallback editors, authoritative rustc markers, full-stack deploy/pairing, Runs/Logs/Invoke, wallet/quota status, and truthful server+client stop behavior. - The v10
mountModStudio,ModStudioController,client.playerCodeProjects,mod-studiopackage subpath, andplayerCodeProject*/PlayerCodeProject*GraphQL names are removed, not deprecated. That authoring surface was still greenfield, so there are no legacy aliases to preserve. - Blocks with Friends embeds the accessible Crowdy Studio, passes authoritative grid bounds and target-specific permissions, and seeds its first-party entrypoints into the common-file catalog.
CrowdyJS 9.0.0: browser-local Rust authoring
- CrowdyJS
9.0.0is published. Blocks with Friends dev and test use it and load the Rust analysis worker, parser/grammar WASM, and generated platform symbol index as same-origin browser assets. - This breaking major removes
languageServiceUrlandappTokenfromMountLiveCodingIDEOptions. Monaco instead talks to a lazily loaded browser module worker over local LSP/JSON-RPC messages. Parser/grammar WASM and the generated platform symbol index are packaged assets; there is no public authoring endpoint, authoring token, or server language-service fallback. - Syntax diagnostics, completion, hover, symbols, and workspace navigation are advisory. The worker is not rustc or rust-analyzer and does not provide borrow checking, complete trait/procedural-macro/build-script semantics, or a full Cargo build. Deploy still uses the authoritative server-side compiler.
- This migration changes no GraphQL field, SDL, or replication wire format.
Dev and test now return HTTP 410 for a WebSocket Upgrade to the retired
/authoring-lsproute. No production deployment is claimed by this entry.
2026-07-22
Durable environment deployment progress (Management API)
cpChangeOrder,orgEnvironment.deployProgress, and destroy progress can now return read-only projected task/step progress for durable change orders instead of empty arrays. Recorded execution progress remains authoritative when available.- Planned work remains
pendinguntil an outcome is observed or inferred. Fields unavailable for projected progress remain null, and identifiers should be treated as opaque. - For projected step progress,
attemptis0when no attempt or outcome has been observed or inferred, and1when an attempt or outcome has been observed or inferred.
Machine-readable Game API permission metadata
- The refreshed downloadable Game API SDL now publishes
@requiresPermissionmetadata on permission-gated root fields. This makes existing authorization requirements discoverable to tools and agents; it does not change runtime authorization behavior.
2026-07-20
Terrain grounding for engine agents + chunk_get repair (game-api v0.14.5)
chunk_getnow works (its SQL had referenced a nonexistent column since Phase 3, so every call errored) and returns the dense voxel grid asstateBase64plus the game's opaque metadata blob aschunkStateBase64.- New
crowdy-game-kit-simterrain::TerrainCache: cached, per-tick budgeted ground sampling over dense chunks (ground_y(x, z)— the server-side equivalent of a client ground scan), fail-soft on unloaded chunks and non-voxel games. - The
mob-engineandnpc-enginetemplates (and Blocks with Friends'bwf-mobs) now walk agents ON the terrain instead of approximating height from nearby players — the approximation floated/flew mobs whenever players jumped or flew, and NPC heights froze at their seeded values.
Redeploy dry run: preview what a release will do before running it
Retired 2026-07-27 along with the rest of the customer-provisioned environment surface.
environmentRedeployPlanis not in the published SDL. See the 2026-07-27 entry.
- New Management API query
environmentRedeployPlan(input)— the DRY RUN ofredeployEnvironment. Same input, read-only: it resolves the same target version and returns per-component version diffs (game-api, Buddy, base images), whether game-DB schema DDL applies or is skipped (schemaWillApply+schemaGitRef), Buddy artifact resolution, the exact pipeline tasks/steps the change order would run (enumerated through the real planner), andblockers— everything that would make the real mutation fail (active change order, missing flavors, non-deployable version) reported instead of thrown. Requiresview_environments(the mutation still requiresmanage_environments). - SDK coverage: CrowdyJS 8.15 (
client.environments.redeployPlan(input)) and CrowdyCPP v0.11.0 (admin_().redeployPlan(input)).
Complete flow timelines: demand-driven compute runs always record
wasm_module_runsnow records every demand-driven run —invokeandevententries, success or failure, each carrying itsflowId— in addition to the existing init rows, failures, and circuit probes. Flow timelines (gameModelFlow) therefore show the compute leg of a cross-engine chain instead of only its Model/Automation legs. Healthy high-frequency ticks still aggregate into per-minute usage rather than one row per tick.computeAppDiagnosticsgainstoolchainRustVersion/toolchainWasmOptVersion: the compile-toolchain fingerprint of the replica that served the query (null when the toolchain is not provisioned). Skewed replicas compile correct but non-shared artifacts, so surfacing the fingerprint makes fleet drift visible from the studio. CrowdyJS 8.14.1 / CrowdyCPP v0.10.1 select the new fields.
Operator-editable platform compute ceilings (Management API)
- New operator-only Management API surface: query
cpComputePlatformCeilingsand mutationcpSetComputePlatformCeilingsread and patch the nine platform ceilings the Game API'scomputeSetPolicyclamps per-app compute policies against (maxModules,maxTickHz,fuelPerTick,fuelPerInvoke,maxMemoryMb,maxRunMs,maxDbOpsPerTick,maxEgressMsgsPerMin,maxEgressBytesPerMin). Patch semantics: omitted = unchanged, explicitnull= clear the override (env/default bootstrap values apply), value > 0 = set. Requiresis_operator; changes are audited. Both fields went with the legacy engines: see policy and ceilings. - Ceiling edits replica-sync to every game-api and take effect in the
computeSetPolicyclamp within ~30 seconds — no game-api restart. TheCOMPUTE_PLATFORM_MAX_*environment variables remain bootstrap defaults. - Game API behavior change (non-breaking): the
computeSetPolicyceiling clamp now reflects operator-set values, so the ceiling named in the... exceeds the platform ceiling (N)error can change over time. - SDK coverage: CrowdyJS 8.14 (
client.operator.computePlatformCeilings()/setComputePlatformCeilings(input)) and CrowdyCPP v0.10.0 (operator_().computePlatformCeilings()/setComputePlatformCeilings) wrap the two fields with the same patch semantics.
2026-07-19
Flow-correlation query + SDK sweep (CrowdyJS 8.13 / CrowdyCPP 0.9)
- New Game API query
gameModelFlow(appId, flowId): stitch one flow correlation id into a single cross-engine timeline — thegameModelEventsrows,gameModelAutomationRunsandcomputeModuleRunssharing theflowIdminted at the entry edge, each array ordered by time ascending. A diagnostics surface gated by app-adminmanage_apps; see Tracing a flow. Partial indexes back theflow_idlookups on all three tables. - CrowdyJS 8.13 / CrowdyCPP 0.9: the default event/run selections now
include
flowId, andgameModel.flow({ appId, flowId })/gameModel().flow(appId, flowId)fetch the stitched timeline (parity 0 missing). Older servers reject the new field/operation with a validation error — everything else keeps working. - Kit invoke helpers treat
computeInvoke's typed contract violation (BAD_REQUEST"Invoke params violate …") as a gameplay verdict:kitInvokeresolves{ success: false, errorMessage }and engine invokes resolve{ success: false, reason }instead of throwing (newisKitVerdictErrorpredicate in CrowdyJS).
Compute fleet hardening: revision-guarded state, flow correlation, shared artifacts, lane codegen
- State-blob writes are revision-guarded: the tick-lease holder always wins
and a non-lease instance's stale
state_setis dropped with an observable module-log warning — keep referee-critical records in Model, not the blob (see the new state-contract warning in Compute Modules). - New
flowIdongameModelEvents,gameModelAutomationRunsandcomputeModuleRuns: one correlation id per entry call, carried acrossmodel_invoke, event triggers andemit_eventcascades — cross-engine flows ("what happened to this kill's reward") are now stitchable. wasm_module_artifacts: modules compile once per fleet; replicas fetch bytes by cache key instead of recompiling, and instances log a toolchain fingerprint at boot (COMPUTE_EXPECTED_RUST_VERSIONturns skew into a loud error).crowdy-compute lanes: declare a fixed-size actor-lane layout once (JSON) and generate matched little-endian codecs for Rust, TypeScript and C++ — no more hand-packing the same bytes on every side.
Container-change push, typed invoke contracts, optimistic-action kit
- New subscription
gameModelContainerChanged: post-commit, metadata-only container-change events (which container, which keys — pull the visibility-filtered state on receipt) with typeName/session filters, fanned out across API replicas. Replaces interval polling with pull-on-push; Blocks with Friends' NPC reconcile loop now rides it with a polling fallback for older servers. - Compute invoke triggers may declare a typed contract
(
contractJson): declared params are validated pre-sandbox (structuredBAD_REQUESTinstead of a guest runtime error), contracts surface oncomputeModuleTriggers, andcrowdy-compute typesgenerates TypeScript wrappers from them. - CrowdyJS 8.11 adds
gameModel.containerChanged(...)and therunOptimisticActionkit helper (the packaged optimistic apply → referee invoke → confirm/rollback loop with actionId receipts); CrowdyCPP 0.8 mirrors withcrowdy::kit::run_optimistic_action(parity 0 missing; the push stream is waived for native clients).
Container query predicates, automation compute actions, event deltas
gameModelContainersgainswhere(up to 8 AND-combined property predicates, the automation-selector shape, type defaults honored) pluslimit/offsetpaging; the compute host mirrors it ascontainers_list_where(SDK0.1.5).- Automations gain
actionKind: compute_invoke: bind a schedule/event/ manual automation directly to a compute module's invoke export (trusted server path,targetMode: global) — the first-class home for cron-shaped compute work, replacing the marker-function pattern. property_changedevent deliveries to compute modules now carry theoldValue/newValuedelta.- SDKs: CrowdyJS 8.11 / CrowdyCPP 0.8 expose the new arguments and
fields (
containersWhereconvenience in CPP); older servers reject the new arguments — omit them and everything else keeps working.
Compute SDK 0.1.4 — atomic world+model referee commits
- New host call
model_invoke_with_world: up to 16 voxel writes commit on the same SQL transaction as anautonomousInvocableModel function. A denied function touches no voxel; a failed voxel write rolls the Model commit back. Each write charges one data op. - Blocks with Friends'
mine/placereferee moved onto the atomic call — the earlier compensation/refund ordering and its bounded loss windows are retired (action receipts remain for client retry idempotency). - No GraphQL schema changes; see the Compute host API reference.
Docs: the "self-reported vitals" client-trust pattern
- Choosing Game APIs now names the
self-reported vitals pattern (client-committed survival stats under
owner_of_self) with its four guardrails: clamp every write, gate restoration on consumed resources, never gate grants or competitive results on self-reported state, and move abuse-sensitive writes behind a compute referee (the expression language deliberately has no clock builtin, so Model invoke policies cannot express cooldowns).
Choosing Game APIs + BWF/TMS authority dogfood
- The canonical five-tier API guide now maps mechanics and genre starters to platform primitives, Model functions, automations, compute engines and client conventions.
- Compute SDK
0.1.3adds app-scopedmodel_invoke, preserving Model transactions/events/notifications when a live referee commits durable results. - CrowdyJS 8.10 / CrowdyCPP 0.7 add atomic inventory crafting and barter, including a hardened server-grant posture.
- BWF schema v6 moves craft/smelt/trade/chests/quests to atomic Model transactions and PvP/fishing/mine/place/rewards to compute referees.
- TMS now routes all battle actions, turns, enemy AI and outcomes through
tms-battle; automations remain test fixtures only.
Compute hardening complete -- measured limits, calibrated billing, failure containment, and the Model-vs-Compute guide
Compute hardening for this release:
- Compute billing's deterministic equivalent is now 22M fuel per unit instead of the earlier 28M placeholder.
- Failure handling covers compile rollback, fuel/watchdog/OOM/panic containment, circuit reset, 256 KB state rejection, deploy mid-tick, event cascade depth, and spend-cap pausing.
- Runtime fixes: trigger upserts no longer stack duplicate rows; tick-rate edits reload a live module; failed compiles restore the prior succeeded version.
- New Model API vs Compute decision guide, measured engine policy-footprint table, and calibrated billing/limits prose.
2026-07-19
Realtime + live-ops engines and the template registry -- the game-kit catalog complete (CrowdyJS 8.9.0, CrowdyCPP v0.6.0)
Wave 3 closes out the 30-abstraction game-kit catalog with the realtime/ competitive set (all additive):
- New GraphQL surface:
computeTemplates+computeDeployTemplate— the platform's engine-template registry. Deploy any canonical engine by NAME (source-hash-deduped, triggers bound, enabled in one call);moduleNameruns parameterizations side by side. SDK sugar:client.compute.deployTemplate(...)andkit.deploy(blueprints, { engines: [...] }). - Kit crates:
kit-playgainsabilities(cast books, sub-stepped projectiles, AoE falloff) andtiming(checkpoints/laps/ghost tracks);kit-sim::zonesgains shrinking circles (BR schedules with warning/shrink/settle events). - Engine templates:
abilities-engine(AbilityDef-driven casts, type-94 events),movement-warden(observe/flag envelopes, type-95),territory(capture/decay/siege/income, type-96),racing(server-timed laps, auto-boarded results, ghost replays, type-97) +possession(the authoritative ball),liveops-scheduler(window modifiers on the compute bus) — plus thearena-blitz(G6) andzone-rush(G14 BR-lite) acceptance examples. - CrowdyJS 8.9.0 / CrowdyCPP v0.6.0: new
kit.abilities/movement/territory/racing/liveops/moderation/telemetrysurfaces, thekit.lootengine path (pity rolls), liveops/moderation/telemetry blueprints, and event types 94–98 with parsers in both SDKs. Everything capability-detected; the movement warden observes and flags only — it never corrects (client prediction is untouched).
2026-07-19
Session-genre engines -- kit-play completion, kit-econ, six new engine templates, and SDK surfaces (CrowdyJS 8.8.0, CrowdyCPP v0.5.0)
Wave 2 of the game-kit program brings the session genres to the paved road (all additive):
- Kit crates:
crowdy-game-kit-playcompletes withturns(initiative, timeouts, simultaneous reveal),score(authoritative win conditions), andcards(server-held hidden hands, seeded shuffles);crowdy-game-kit-aiadds flow fields, a path cache, and budgeted turn-game movers; newcrowdy-game-kit-econships order-book markets, server-computed standings, production chains with offline catch-up, and pity-timer loot. All allowlisted + vendored. - Engine templates:
match-engine(server-driven lifecycle over MatchMeta),deck-engine(true hidden information),instance-engine(private world slices, seeded runs),director(wave schedules, boss phases, party scaling),matchmaking(rating buckets, party blocks, compute-event handoff to matches),market-engine+board-engine(escrowed order books; tie-aware server rankings with season snapshots), and theminigameinvoke-loop scaffold — plus playable examples per genre:card-duel,dungeon-run,tower-defense,gacha-shrine,idle-factory. The Tactical Model Simulator's enemy phase + outcome authority moved into atms-battlecompute referee (kit-ai). - CrowdyJS 8.8.0 / CrowdyCPP v0.5.0: engine paths on
kit.matches/kit.decks/kit.leaderboards, newkit.instances/kit.director/kit.matchmaking/kit.minigames,kit.economy.orderBook, quests FTUE tutorial sequencing, and reserved event types 91 (turn) / 92 (score) / 93 (proposal) with parsers in both SDKs. Everything capability-detected — model-only deployments keep their behavior.
2026-07-19
Compute Engines -- the game-kit crate family, engine templates, and SDK engine surfaces (CrowdyJS 8.7.0, CrowdyCPP v0.4.0)
Server-side game engines become a paved road (all additive):
- Three new platform-vendored kit crates join
crowdy-game-kit-coreon the module dependency allowlist:crowdy-game-kit-ai(budget-capped A* over aCostProvider, steering behaviors, FSM + JSON behavior-tree interpreter),crowdy-game-kit-sim(deterministic day cycle, weather fronts, resource nodes, timestamp growth/farming, wave schedules, rule zones), andcrowdy-game-kit-play(the combat referee: presence-based hit validation, damage pipeline, kill credit, contact damage). - Engine templates — deployable, data-driven reference engines in
compute-examples/engines/:npc-engine(behavior-tree agents + pets),mob-engine(pooled spawns, aggro/leash/packs, refereedattack_mob),world-engine(weather + nodes + farming). The CLI scaffolds a copy withcrowdy-compute new <name> --engine <npc|mob|world>; apetsexample ships alongside the original five. New docs page: Compute engines. - CrowdyJS 8.7.0 — engine kit surfaces: the
kit/wirepose/lane registry (engineLanes(),enginePoseCodec, type-77/90 event parsers),kit.mobs,kit.pets,kit.combat.attackRouted,kit.worldsim.forecast,kit.npcs.overlayLivePoses, and per-session engine capability detection (kit.engines) so the same client code runs on model-only deployments. - CrowdyCPP v0.4.0 — the same surfaces in C++ (
crowdy/kit/wire.hpp,kit.mobs(),kit.pets(),attackRouted,forecast), parity-tracked against CrowdyJS.
2026-07-18
Compute Modules -- developer tooling: crowdy-compute CLI, game-kit utility crate, examples + tutorial
The compute developer experience grows a paved road (all additive):
crowdy-computeCLI (in thecompute-examplesrepository folder):newscaffolds a module crate,checkmirrors the deploy validation locally (plus a realwasm32-wasip1build when a toolchain is present),deployruns the full upsert → compile-wait → triggers → enable flow idempotently,watch/invoke/statuscover the observe loop.crowdy-game-kit-core— a platform-vendored Rust utility crate for module authors: durable-state harness, actor-pose wire codecs, chunk math, player presence, cadence helpers, event framing, invoke routing, seeded RNG. Addcrowdy-game-kit-core = "0.1.0"to your module's dependencies.- SDK
0.1.2adds two host functions:container_get_batch(up to 32 containers + properties in one data-op) andactors_list_radius(a chunk box of actors in one call), plus a native test-host shim so module crates cancargo testoff-platform. - Five runnable examples (tick-counter, scoreboard, npc-pathfinder, world-weather, mini-game) and a new Compute tutorial — zero to a live module in under 30 minutes.
Game API -- Compute Modules: server-side Rust/WebAssembly logic (additive)
The Game API gains Compute Modules — studios write Rust, deploy the source through GraphQL, and the platform compiles it to WebAssembly and runs it server-side, sandboxed and fuel-metered:
- New GraphQL surface (additive): mutations
computeUpsertModule,computeDeployVersion,computeSetModuleEnabled,computeDeleteModule,computeUpsertTrigger,computeDeleteTrigger,computeSetPolicy,computeInvoke; queriescomputeModules,computeModule,computeModuleVersions,computeModuleTriggers,computeModulePolicy,computeModuleRuns,computeModuleStats,computeModuleLogs,computeAppDiagnostics. Full signatures in the GraphQL reference. - Triggers: fixed-rate ticks, model/compute event subscriptions, and
client-callable invoke exports (synchronous RPC via
computeInvoke, gated by the same authority-policy trees as model functions). - Host API: typed, app-scoped access to game-model data, app state blobs,
chunks/voxels/actors, and replication emits that arrive on the existing
udpNotificationsstream — see the Compute host API reference. Clients need no changes. - Permissions: two new org permission keys —
manage_compute(authoring) andview_compute_diagnostics(monitoring). Org owners hold both by default; existingmanage_appsgrants are unaffected. - Billing: three new shared-environment usage metrics with free hourly
allowances —
wasm_compute_units,wasm_egress_msgs,wasm_egress_bytes(see Shared environment). Rates are placeholders pending load-test calibration. - The management UI app dashboard gains a Compute tab (author, deploy, watch compiles, monitor runs) driven by the same public API.
No existing schema fields, wire messages, or behaviors changed.
Game API v0.13.13 -- invoke policy denials return results; userAppState round-trip fix
Two consumer-facing behavior fixes in gameModelInvoke and the per-user app
state store (no schema shape or wire change):
- Invoke policy denials are results, not errors. A
gameModelInvokerejected by the function's invoke policy (owner_of_self,condition,is_host, ...) now resolves withsuccess: falseand anerrorMessage, and writes a failure event visible ingameModelEvents— matching the documented kit contract ("authority denials are not exceptions — checksuccess"). Scope violations (aninvokeScope: "server"function called without app-admin rights) still throwFORBIDDEN. If your client caughtFORBIDDENto detect gameplay denials, checksuccessinstead;@crowdedkingdoms/crowdyjs@8.4.6and CrowdyCPP handle both server generations transparently in their kit helpers. updateUserAppStatestores what you send. The mutation now decodes its base64stateinput before storage, souserAppState/userAppStatesreturn exactly the base64 that was written. Previously reads returned a double-encoded value; rows written through older servers return the correct encoding after their next write.
CrowdyJS 8.4.5 / 8.4.6 (npm) — kitInvoke maps FORBIDDEN policy
denials from older Game API builds onto the documented
{ success: false, errorMessage } result (8.4.6 republishes 8.4.5 with the
runtime VERSION constant synced).
CrowdyCPP v0.1.0 -- initial public release of the native C++ SDK
CrowdyCPP is the official portable C++ SDK (C++20, CMake, Linux/Windows/macOS), now documented in the new CrowdyCPP docs tab:
- Native UDP replication. Unlike browser-first CrowdyJS, replication goes directly to the replication servers over the Replication API wire protocol — zero-copy framing, HMAC-signed sends, verified receives, automatic token refresh and reconnect. See Replication client.
- Full GraphQL surface parity with CrowdyJS (same domains, two-token model, and error codes), a WorldSession layer mirroring World Stores, and the full 15-layer Game Kit with blueprint equivalence — worlds deployed from either SDK are playable from both.
- Engine-wrappable by design: pluggable HTTP/crypto/clock/log interfaces and a thread-free manual-pump mode for engine plugins. See Engine integration.
SDK-only: no schema or wire change; servers need no changes.
2026-07-18
Game API v0.13.12.2 + CrowdyJS 8.4.1 -- schema hygiene + plot owner-mirror kinds (additive)
A description-only hygiene pass on the Game API schema (no wire, DDL, or behavior change) plus a small CrowdyJS Game Kit patch:
ActorUpdateResponse/VoxelUpdateResponseare formally marked legacy. TheseUdpNotificationunion members are never emitted — the game server retired their dedicated opcodes; an applied update arrives as your own*Notificationself-echo and failures arrive asGenericErrorResponse. Their type descriptions, the union description, and the UDP proxy guide now say so explicitly, and the guide's example subscription no longer selects them. They remain in the union for backward compatibility and will be removed in a future major version.- Deprecation reasons now carry removal dates (per the agent-readiness
checklist): the offset-pagination
limit/offsetargs onvoxelUpdateHistory/gameModelEvents(and their*Connectionvariants, where they are ignored) state removal no earlier than 2027-01-01. - CrowdyJS 8.4.1:
plotBlueprintgainsownerIdKind: 'int' | 'string'— string owner mirrors (the Blocks-with-Friends convention) now work with kit plots: guards compare viato_string($caller_user_id), buying writes the owner as a string, and""is the for-sale sentinel. The bundled schema/reference pick up the hygiene descriptions.
Published SDL + GraphQL reference regenerated. Requires nothing — additive documentation; clients need no changes.
2026-07-18
CrowdyJS 8.4.0 -- World Stores: SDK-managed game state (additive)
A new opt-in layer, @crowdedkingdoms/crowdyjs/stores, moves the client-side
bookkeeping every game hand-writes (actor registries, pose codecs, chunk
caches, chat rings, host polling — ~1,600+ LOC in the reference MMO) into the
SDK as typed, queryable, source-of-truth stores fed by ONE shared
udpNotifications subscription:
- Codecs:
StateCodec<T>withjsonCodec/textCodec/rawCodecandstructCodec— a declarative fixed-layout binary DSL for replication state (the 48-byte pose in ~10 lines). Developers register their custom types + encoders/decoders once; stores speak typed values everywhere. session.self(LocalActorStore): minted + persisted actor uuid, typed state, a 5 Hz send loop with send-on-change dedup + periodic keyframes, and queryablelastSent/lastAck(the applied self-echo) /lastError/status.session.actors(RemoteActorStore): decode-once registry with self-echo filtering, timestamped sample history for interpolation, read-time staleness + reaping, join/update/leave events, and lanes (players vs mobs from one stream).session.errors:GenericErrorResponses attributed to the tracked sends that caused them (per-actor lookup, ring buffer).session.chunks(ChunkStore): dedupedgetChunksByDistanceloading with automatic sparse-voxel-state hydration, realtimevoxelUpdatemerge, typed voxel/chunk-state codecs, optimisticsetVoxel, and the deterministic-worldgen write-back pattern (onMissing+ throttled queue).- Messaging:
channelInbox(per-channel typed history — inbound channel fan-out at last),actorInbox(typed direct messages),events(per-eventType codecs for client/server events +lastEvent). - Durable:
host(heartbeat tracker),save(typed app save blob with debounced autosave),avatar(typed public/private/app state),model(ContainerMirror — typed game-model snapshots with notify-to-pull channel binding).
Ergonomics: compile-time toggles — the core client never imports the
layer ("sideEffects": false; unimported stores tree-shake away) and the
session's TypeScript type is conditional on the config, so unconfigured
stores don't exist. Background-tab safe — writes ride unthrottled
WebSocket events; timer-driven work runs on an injectable Ticker with a
workerTicker() (dedicated Web Worker, exempt from background-tab timer
throttling). SDK-only: no schema or wire change. See
CrowdyJS → World Stores.
2026-07-18
CrowdyJS 8.3.0 -- Game Kit genre layers (additive)
The Game Kit grows from four building blocks to a genre-covering catalog — eleven new layers, each a blueprint builder + typed runtime helper, all pure composition over the existing GraphQL surface (no schema change):
- Economy (
economyBlueprint/kit.economy): multi-currency wallets, atomic shop buys,$self_owner_id-pinned escrow trades and player market, optional restock automation. Trusted mints default to server scope. - Progression (
kit.progression): xp/levels via thefn:curve-helper pattern, skill prerequisite chains, threshold achievements, host-gated rating for match results. - Loot (
kit.loot): weighted tables unrolled into seed-driven expression chains at build time, atomic single-claim grants, event-triggered pooled drops. - Quests (
kit.quests): event-automation progress, atomic claim-into-stack+wallet, cron daily resets. - Combat (
kit.combat): server-side damage/death, status-effect ticks via the selector-join pattern,turnBased/hostSynced/reviveGroupoptions. - Matches (
kit.matches): session-backed lobbies/rounds/turns/scores with a per-match notification channel (notify-to-pull;onMatchChanged) and counter-based turn ticks. - Decks (
kit.decks): hidden hands via owner-visibility properties (the two-property reveal trick) and shuffle-by-position automations. - World simulation (
kit.worldsim): day/night clock with a spatial notification, regenerating nodes, crops, host-read wave counters. - Social (
kit.social+guildBlueprint): parties/guilds/chat over teams + channels, grid territory grants, and a composite guild-hall + bank blueprint. - Leaderboards (
kit.leaderboards): trusted keep-best submits, client-side ranking, cron season rolls. - Monetization (
kit.features+featureGate): feature keys, tier grants, and*policyExtragating options on the plot/lock builders.
Cross-cutting: blueprints.ts split into per-concept modules (import paths
unchanged), shared KitTrustedAuthority / ownerIdKind conventions, and a
new pattern guide (simulation tiers, notify-to-pull, timers without a clock,
hidden information, anti-cheat checklist). See
CrowdyJS → Game Kit and the expanded
genre map. Requires
cks-game-api v0.13.12.1+.
2026-07-18
Game API -- permission-read builtins + selector permission predicates (additive)
Game-model logic can now read the runtime grid ACL and grid layout, completing the read+write loop that permission effects opened:
- Six new expression builtins, usable in mutations, return expressions,
notification args, permission-effect expressions, and policy
conditionrules:has_grid_permission(user, key[, grid]),grid_at(cx, cy, cz[, mode])(overlap modesfirst|smallest|largest),has_chunk_permission(user, key, cx, cy, cz[, mode]),grid_contains,grid_min,grid_max. Reads are app-scoped, cached per invocation, charged 25 gas per uncached lookup, and observe grants applied by the same invocation's permission effects (read-your-writes). - Automation selector permission predicates:
selfPermissionWhere/candidatePermissionWherefilter automation targets by whether the user behind a container has/lacks a grid permission (owner- or property-derived user id; literal, property-derived, or any-grid scope) — one batched ACL query per predicate. Validated atgameModelUpsertAutomationtime. - Upload static analysis warns on wrong builtin arity, invalid
mode/axis literals, and unknown permission keys.
Read-only feature: no schema migration and no wire change. See
Game Models → Reading permissions from expressions
and Autonomous processes → Permission predicates.
CrowdyJS 8.2.0 ships the matching Game Kit surface (plotBlueprint,
chunk-permission lock authority, typed selector predicates). Requires
cks-game-api v0.13.12+.
2026-07-17
Game API -- model permission effects (additive)
Game-model functions can now write runtime grid permissions as declared,
transactional effects. A new permissionEffects array on
gameModelUpsertFunction / gameModelSeed functions declares grants/revokes
({ action, permissionKeys, userExpression, gridIdExpression, ttlSecondsExpression? }) that apply in the same transaction as the
function's property mutations — "pay gold AND get plot access" is one atomic
invoke, immediately enforced by the replication layer on movement/voxel writes.
Details:
- Expressions are compiled server-side and evaluated in the invocation context;
the system params
$caller_user_id,$current_turn_user_id,$self_owner_id, and$session_idare now injected into function-body evaluation as well (previously policyconditionexpressions only) and cannot be spoofed by same-named caller params. - Effects are capped at 4 per function, gas-charged, validated against the
runtime_permissionscatalog, and require the grantee to hold app access; a failing effect rolls back the whole invocation (success: false). - New audit field
GmEvent.permissionEffectsAppliedJsonrecords every applied effect (player- and automation-driven alike). - New types:
FunctionPermissionEffectInput,GmFunctionPermissionEffect(returned onGmFunction.permissionEffects).
See Game Models → Permission effects
and the worked land-purchase example in
Modeling game concepts.
Requires cks-game-api with the 2026-07-17-model-permission-effects migration.
2026-07-10
Unreal SDK 2.1.0 -- Crowdy State, replicated subsystems, and host authority
A large, mostly additive release on the 2.0 architecture: a client-authoritative view plane for per-property replication, non-actor subsystem participants, and an explicit host-authority and ownership surface. New capabilities:
- Crowdy State property replication. Mark a
UPROPERTYor a Blueprint variable withmeta=(CrowdyState)and the owning client diffs and replicates just that value to peers on the client-authoritative view plane, with no snapshot struct or executor. Bare per-property markers tune it:CrowdyOnRep(a parameterless RepNotify),CrowdyOwnerOnly(deliver only to the entity's owner),CrowdyManualDirty(send on an explicitMarkStateDirtyrather than every tick), andCrowdyHeartbeat(opt into the periodic keyframe re-send for late joiners). See Crowdy State and the metadata keys reference. - Replicated subsystems. A host-owned UE Subsystem can take part in both view planes (Crowdy State properties and CrowdyEvents) without becoming an actor, via a function library or two abstract base classes. See Replicated subsystems.
- Host authority and explicit ownership transfer. New
Ownership,HostOverride, andStateHeartbeatfields onUCrowdyEntityComponentfor level-placed world entities and host super-user writes; a request/grant ownership-transfer flow (UCrowdyOwnershipTransfer); a server-validated host check (the Is Crowdy Entity Host (Server) node); and client-side ownership helpers (DoesCrowdyEntityOwn,IsCrowdyEntityHost,GetCrowdyEntityComponent). See Host authority and Ownership transfer.
Breaking changes
These are the only changes that touch an existing 2.0 project; everything above is additive.
CrowdyHasAuthorityrenamed. The Blueprint-pure "am I the host" check onUCrowdyUtilitiesis nowGetCrowdyHasAuthority(Blueprint DisplayName still "Crowdy Has Authority"). The oldCrowdyHasAuthorityname is now the exec/branch variant (Blueprint "Switch Crowdy Has Authority"). Update C++ callers fromCrowdyHasAuthority(this)toGetCrowdyHasAuthority(this); Blueprint nodes re-resolve on recompile. See Host authority.ECrowdyDecayRatecorrected. Removed the non-functionalLinear_100value (theCrowdyDecayoption on aSpatialMulticastCrowdyEvent). It shared an underlying value withLinear_50, so the two were indistinguishable on the wire. The ladder is nowNo_Decay(0),Exponential_Decay(1),Linear_50(2),Linear_25(3),Linear_10(4),Linear_5(5). A Blueprint that had selected "Linear 100 Decay" should re-select a rate; it behaved exactly like "Linear 50 Decay" before. See Recipients and routing.- RPC container parameters hardened. A CrowdyEvent parameter that buries a container
(
TArray/TSet/TMap) inside a struct is now rejected at registration, because a malformed packet could drive an unbounded allocation from its element count; pass the container as a top-level parameter instead. DirectTArray/TSet/TMapof supported element types still work. The set/map parameter wire format also changed (internal blob version 2), so every peer must run a build from this line -- a mixed 2.0/2.1 session drops set/map RPCs at the version gate. See RPC parameter types.
2026-07-01
Social and magic-link sign-in (Unreal SDK, additive)
- Three new Blueprint Async Action latent nodes round out the sign-in surface: Dev
Login, Magic Link Sign In, and Social Sign In, joining the existing
Login / Register / Restore Session nodes on
UCrowdyAuthentication. Each has Success/Error exec pins, so a Blueprint-only project no longer needs to wire the underlying delegates by hand. See Blueprint nodes. - Crowdy Studio's sign-in page now offers a federated social provider or an emailed magic link, alongside the existing email + password, dev sign-in, and organization token options. All four session-scoped methods (password, social, magic link, dev) grant full authoring access — teams, channels, grids, game models, Config Sync, and the Web Console; only the organization token remains management-only. See Crowdy Studio: Signing in.
No breaking changes — email + password sign-in (Login/Register) remains fully
supported side by side with the new methods.
2026-06-28
Dedicated environments available to all studios (Management API)
Retired 2026-07-27. Everything announced below was removed from the published SDL and is no longer served. Nothing here is a surface to build against; see the 2026-07-27 entry and Shared environment & billing.
- You can now create multi-VM dedicated environments (
environmentClass: "dedicated", the default) directly — an isolated Game API fleet, database, and Buddy replication stack for your studio. Previously only the single-VM developer sandbox (environmentClass: "dev_single") was available and dedicated returned a "coming soon" error.createEnvironment/environmentQuotetake the four per-component flavors (databaseFlavor,gameApiFlavor,udpBuddyFlavor,caddyFlavor) plus scaling bounds; creation still gates on the org wallet (environmentQuote.canCreate) and requiresmanage_environments. The dedicated-environments page was removed; see Shared environment & billing. CksEnvironmentgains an additiveisSharedboolean (true only for the platform's shared environment; always false for your environments). No breaking changes. Clients still discover an app's runtime viaapp.gameApiUrl/platformConfig.sharedGameApiUrl— the shared Game API endpoint is resolved dynamically, so never hard-code it.
2026-06-26
CrowdyJS package — npm org move, v6 version line restored
- The SDK is published as
@crowdedkingdoms/crowdyjs(moved from the former@crowdedkingdomstudiosorg). The version line continues the v6 series: the current release is6.1.1(npmlatest), the direct successor to the old org's6.1.0— same code, new package name. Two interim1.0.xpublishes during the org move reset the version by mistake; they remain installable but are superseded by6.1.1. Install is unpinned, sonpm install @crowdedkingdoms/crowdyjsresolves to6.1.1. See the CrowdyJS SDK guide.
2026-06-26
Game model automations / NPCs (Game API, additive)
- Server-driven automations invoke your game model functions
on their own — on a schedule or in reaction to model activity — so you can build NPCs,
spawners, and ticking world systems that run with no client connected. (Superseded
2026-09-01: scheduled automations are now skipped while an app has no players —
see that entry and Presence.) New GraphQL:
gameModelUpsertAutomation,gameModelUpsertAutomationTrigger,gameModelRunAutomation,gameModelSetAutomationEnabled/Policy, and the monitoring queriesgameModelAutomations,gameModelAutomationRuns,gameModelAutomationStats, andgameModelAppDiagnostics. The entry-point function opts in withautonomousInvocable. All requiremanage_apps. See Autonomous processes (NPCs).
Model-driven realtime notifications (Game API, additive)
- A model function can now push a realtime notification to clients as part of its
invocation, via a
notificationsarray ongameModelUpsertFunction(andgameModelSeed). Each effect has akind(spatial|channel|actor) and arguments built from model expressions; the notification arrives on the existingudpNotificationsstream as aServerEventNotification,ChannelMessageNotification, orSingleActorMessageNotification. Player-invoked and automation-driven changes notify players identically. See Model-driven notifications.
deleteGrid (Game API, additive)
- New
deleteGrid(input: { appId, gridId })mutation removes a studio-created peer grid (e.g. to unblockGRID_OVERLAPS_EXISTING). Hybrid result likecreateGrid; refuses the default world grid and grids with nested children; requiresmanage_apps. See Grids and permissions.
Management API additions (additive)
environmentQuoteandorgEnvironment(s)now returnenvironmentClassandsingleBoxFlavor;appUsageSummarynow returns automation activity totals (automationRuns,automationInvocations,automationComputeUnits); newbuddyBillingTiers/graphqlBillingTiers/postgresBillingTierscatalogs withupdateEnvironmentBillingTiers; and aplayerPulselive-concurrency query.
CrowdyJS v6.1 (SDK, additive)
- The SDK now wraps the full public surface — every non-deprecated root field has a
typed method, including the above. New highlights:
client.gameApps.deleteGrid, the game-model automation +notificationswrappers, the game-model studio reads, and Relay*Connectioncursor-pagination variants alongside the offset lists.deleteGridrequires a server on releasev0.1.33+. See the CrowdyJS guides: Automations, Model-driven notifications, and Grids.
2026-06-13
Signed server→client notifications (Replication API)
- Buddy now signs server→client long-spatial notifications (actor / voxel / audio /
text / generic-spatial / single-actor) with a per-recipient HMAC-SHA256 keyed on
your 64-octet game token — the same scheme you already use to sign client→server
messages. Signed notifications arrive with
containsAuth = 1and a 32-byte HMAC in the spatial tail (the 8-byte slot after it carries server epoch-millis, not part of the HMAC). - Native (direct-UDP) clients: verify the HMAC and drop any
containsAuth = 1long-spatial notification whose tag doesn't match. See HMAC for the algorithm, key handling, and per-language libraries (OpenSSL/C++, .NET, Node, Python, Go, Rust), plus Send and receive. - Browser clients on the GraphQL UDP proxy are unaffected — the proxy handles the signed format transparently.
Additive to the wire layout (the HMAC slot already existed); no fields removed.
2026-06-13 (load shedding)
Resource-aware load shedding (additive)
- New
ServerStatevaluesNearCapacityandFull. Game servers now report a resource-overload state.serverWithLeastClientsalready returns onlyReadyForClientsservers, so an overloaded server is automatically skipped for new connections — no client change needed for host selection. - New Replication API opcode
COMMAND_RECONNECT(22). A server under hard overload asks a client to move:[22][32B HMAC], where the HMAC (HMAC-SHA256 keyed on your 64-octet game token over the type byte) authenticates the command as server-originated. Native (direct-UDP) clients should verify it, then re-queryserverWithLeastClientsand reconnect within the grace period; see Operations → Load shedding and Wire formats. Browser clients on the GraphQL UDP proxy are migrated automatically and never see this message.
No fields were removed; both changes are additive.
2026-06-13 (later)
Agent-readiness design changes (additive)
- Idempotency keys. Economy-sensitive and destructive mutations now accept an optional
idempotencyKey(on theinputobject or as a top-level argument). Replaying with the same key + identical parameters returns the first result instead of re-applying; a different payload under the same key returns the newIDEMPOTENCY_CONFLICTerror code. Covers Management API checkout/billing/grant/quota/app/environment/org mutations and Game APIrollbackVoxelUpdates,revokeGridPermissions, team and actor/avatar deletes. - Relay cursor pagination. New
*Connectionqueries (first/after,edges/pageInfo) for the largest lists (e.g.usersConnection,appsConnection,checkoutsConnection,paymentEventsConnection,walletTransactionsConnection,appUserAccessConnection,voxelUpdateHistoryConnection,actorsConnection,gameModelEventsConnection). The offset queries remain; theirlimit/offsetargs are now@deprecated. See Pagination. - Machine-readable permissions. Guarded fields now carry a
@requiresPermission(scope:, permission:, scopeArg:)directive in the SDL/introspection. - Structured errors. New error codes (
SCOPE_MISSING,CONFLICT,IDEMPOTENCY_CONFLICT,RATE_LIMITED, plus correctedUNAUTHENTICATED/NOT_FOUND) andextensions.remediation/extensions.requiredPermission. See Error codes. - Security fix.
effectiveQuotanow requiresview_usageon the most-specific scope (tier/app/org) instead of being readable by any authenticated user; metric-only lookups (free-tier defaults) remain open. This is a deliberate behavior change for cross-tenant callers.
No fields were removed. The ID/UUID scheme and the UDP wire-protocol versioning are unchanged by design.
2026-06-13
Documentation & schema self-description
- Every public GraphQL query, mutation, subscription, argument, field, and enum value now carries a description across the Management API and Game API — including the required permission and side effects on each operation. These flow into the GraphQL reference and the downloadable SDL.
- Published downloadable SDL at stable URLs:
/schema/management-api.graphql,/schema/game-api.graphql,/schema/crowdyjs.graphql. - Added an
/llms.txtindex and a For AI agents quickstart, plus consolidated Error codes, Pagination, and Rate limits references. - Replication API: published a consolidated opcode/byte-layout reference, documented the NAK-vs-silent-drop failure model and the best-effort reliability contract, and added a worked example packet.
Deprecations (Management API)
CheckoutPurpose.DONATIONandCheckoutPurpose.PROPERTY_TOKENSare deprecated — these products are no longer purchasable. UseORG_WALLET_TOPUPorAPP_ACCESS_PURCHASE. Existing historical checkouts are unaffected.- The
myDonationDataandmyPropertyTokensqueries are deprecated (legacy read paths retained for historical records only).
No fields were removed in this release. Deprecated fields continue to function.
Earlier
Prior releases predate this public changelog. For SDK breaking-change notes, see the CrowdyJS migration guide in the CrowdyJS docs.