@crowdedkingdoms/crowdyjs 13.0.0 → 13.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -599
- package/dist/crowdy-client.d.ts +5 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/domains/udp.d.ts.map +1 -1
- package/dist/domains/udp.js +25 -0
- package/dist/generated/graphql.d.ts +10 -4
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/realtime.d.ts +22 -0
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +77 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,122 +1,34 @@
|
|
|
1
1
|
# CrowdyJS
|
|
2
2
|
|
|
3
|
-
The official browser-first TypeScript SDK for **Crowded Kingdoms**. CrowdyJS
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
> model is unchanged). The schema is resynced from the unified SDL, and the
|
|
9
|
-
> retired surfaces are removed: `client.environments` (dedicated customer
|
|
10
|
-
> environments no longer exist), the per-environment usage rollups and
|
|
11
|
-
> capacity tier catalogs, and everything on `client.operator` except the
|
|
12
|
-
> platform compute ceilings (infra operations moved to the separate
|
|
13
|
-
> infra-control-plane service). See [MIGRATION.md](MIGRATION.md).
|
|
3
|
+
The official browser-first TypeScript SDK for **Crowded Kingdoms**. CrowdyJS
|
|
4
|
+
gives you typed clients for the whole platform: passwordless identity and
|
|
5
|
+
studio administration (the Management surface), world data and the abstract
|
|
6
|
+
game model (the Game surface), and the UDP proxy realtime stream — all over
|
|
7
|
+
one unified GraphQL API.
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
npm install @crowdedkingdoms/crowdyjs
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
> **Renamed package, same version line.** This SDK moved to the `@crowdedkingdoms` npm org but **kept its v6 version line**. `@crowdedkingdoms/crowdyjs@6.1.1` continues directly from the former `@crowdedkingdomstudios/crowdyjs@6.1.0` — same code, new package name. (Two interim `1.0.x` publishes during the org move reset the version by mistake; they remain on npm but are superseded by `6.1.1`, which is `latest`.) See [MIGRATION.md](MIGRATION.md).
|
|
22
|
-
|
|
23
|
-
CrowdyJS v4 targets browsers by default and uses native `fetch`, `WebSocket`, `crypto`, `btoa`, and `atob`. Node tools can still use the SDK, but must provide browser-compatible globals when opening realtime connections.
|
|
24
|
-
|
|
25
|
-
> **CrowdyJS v12 (breaking): Agentic Crowdy Studio.** v12 adds the accepted
|
|
26
|
-
> `crowdy.studio-agent/1`, `crowdy.agent-tools/1`, and
|
|
27
|
-
> `crowdy.player-host/1` browser contracts. Import the provider-neutral durable
|
|
28
|
-
> client and immutable tool registry from
|
|
29
|
-
> `@crowdedkingdoms/crowdyjs/agent`, and generic game observations/control from
|
|
30
|
-
> `@crowdedkingdoms/crowdyjs/player-host`. Crowdy Studio can mount the
|
|
31
|
-
> integrated Ask/Build/Play dock when given an injectable agent transport.
|
|
32
|
-
> There is deliberately no OpenRouter key/client, raw GraphQL executor, DOM
|
|
33
|
-
> driver, `fetch`, shell, unrestricted SDK bridge, or client-mod `host_call` in
|
|
34
|
-
> these surfaces. The reconciled Game API SDL, generated operations, and
|
|
35
|
-
> production `CrowdyAgentGraphQLTransport` are included and exposed as
|
|
36
|
-
> `client.crowdyStudioAgent`; tests may still inject
|
|
37
|
-
> `CrowdyStudioAgentTransportV1`. Existing manual Studio mounting remains
|
|
38
|
-
> unchanged when `agent` is omitted.
|
|
39
|
-
>
|
|
40
|
-
> **Server compatibility (v8.13):** v8.13 adds the **flow-correlation surface**: `gameModel.flow({ appId, flowId })` stitches one flow correlation id into a single cross-engine timeline (model events + automation runs + compute module runs, each time-ascending), and the default event/run fragments now select the nullable `flowId` field on `GmEvent` / `GmAutomationRun` / `WasmModuleRun`. Both require the 2026-07-19 `cks-game-api` dev line (the `2026-07-19-compute-fleet-hardening` migration for the columns and the `gameModelFlow` query for the timeline); older servers reject the operations with a GraphQL validation error, and everything else keeps working. `gameModel.flow` requires the app-admin `manage_apps` permission (it is a diagnostics surface). v8.13 also extends the kit invoke helpers' gameplay-verdict mapping: the typed invoke-contract violation `computeInvoke` raises (`BAD_REQUEST` with an "Invoke params violate ..." message, game-api 2026-07-19+) now maps onto `{ success: false, errorMessage }` (`kitInvoke`) / `{ success: false, reason }` (engine invokes) instead of throwing — see the exported `isKitVerdictError` predicate.
|
|
41
|
-
>
|
|
42
|
-
> **Server compatibility:** v8.12's **container query predicates** (`where`/`limit`/`offset` on `gameModel.containers`), **automation compute actions** (`actionKind: 'compute_invoke'` with `computeModuleName`/`computeExport` on `gameModel.upsertAutomation`; `property_changed` events additionally carry `oldValue`/`newValue` to compute modules), **container-change push** (`gameModel.containerChanged` — metadata-only pull-on-push over graphql-transport-ws; on Node ≤ 21 pass `webSocketImpl`), and **invoke-trigger contracts** (`contractJson` on `compute.upsertTrigger`/`moduleTriggers`, validated server-side pre-sandbox) require the 2026-07 `cks-game-api` dev line with the `2026-07-19-automation-compute-action` migration; older servers reject the new arguments/fields (omit them and everything else keeps working). (npm 8.11.0 is a partial cut of this surface — prefer 8.12.0.) v8.12 also adds the `runOptimisticAction` kit helper (client-only: the packaged optimistic apply → referee invoke → confirm/rollback loop with actionId receipts). v5.2+ targets environments on release **v0.1.19 or later** (`cks-game-api >= v0.10.3`, `cks-management-api >= v0.1.70`). The destructive mutations send an `idempotencyKey` argument that older servers don't define. v6.1's `client.gameApps.deleteGrid` additionally requires release **v0.1.33+** (`cks-game-api >= v0.12.3`). The game-model **permission effects** fields (`permissionEffects` on `gameModel.upsertFunction`/`seed`, `permissionEffectsAppliedJson` on events) require a `cks-game-api` build with the `2026-07-17-model-permission-effects` migration (v0.13.11+); older servers reject queries/mutations that include them (omit the fields and everything else keeps working). The **permission-read** surface (the `has_grid_permission`/`grid_at`/`has_chunk_permission` expression builtins the kit's `chunkPermission` locks compile to, and selector `*PermissionWhere` predicates) additionally requires `cks-game-api` **v0.13.12+**. Older `cks-game-api` builds report game-model **invoke policy denials** as `FORBIDDEN` GraphQL errors instead of resolving with `success: false`; as of this version the kit's invoke helpers map that error onto the documented `{ success: false, errorMessage }` result, so kit callers behave identically against both server generations. v8.6's **`client.compute`** (Compute Modules — server-side Rust/WASM logic) requires a `cks-game-api` build with the compute surface (the `compute*` root fields, v0.13.13+ dev line); older servers reject these operations with a GraphQL validation error, and everything else keeps working. v8.9's **realtime + live-ops surfaces** (`kit.abilities`/`movement`/`territory`/`racing`/`liveops`/`moderation`/`telemetry`, the loot engine path, `client.compute.deployTemplate` + `kit.deploy({engines})`, and the type-94..98 event parsers) complete the 30-abstraction catalog; v8.8's **session-genre engine surfaces** (`kit.instances`/`director`/`matchmaking`/`minigames`, the engine paths on matches/decks/leaderboards, `economy.orderBook`, and the type-91/92/93 event parsers) talk to the Wave 2 engine templates; capability detection keeps model-only deployments on today's behavior. v8.7's **engine kit surfaces** (`kit.mobs`, `kit.pets`, `kit.combat.attackRouted`, `kit.worldsim.forecast`, and the `kit/wire` pose/lane registry) talk to compute-module game engines built on the Wave 0/1 `cks-game-api` dev line (`crowdy-game-kit` crates); capability detection makes them degrade gracefully — model-only deployments keep today's behavior.
|
|
43
|
-
>
|
|
44
|
-
> **P1 player runtime compatibility:** `client.playerCompute`,
|
|
45
|
-
> `client.playerModel` (owner/grid-confined flexible data + automations), first-class grid
|
|
46
|
-
> ownership methods on `client.gameApps`, and app code-admission methods on
|
|
47
|
-
> `client.apps` require the 2026-07-20 game-api/management-api player-runtime
|
|
48
|
-
> schemas. Player-authorized one-chunk claims and owner release through
|
|
49
|
-
> `marketplace.claimGridChunk` / `marketplace.releaseClaimedGrid` require the
|
|
50
|
-
> corresponding 2026-07-22 Game API claim schema. Older servers reject only
|
|
51
|
-
> those operations during GraphQL validation.
|
|
52
|
-
>
|
|
53
|
-
> **App-scoped player-count compatibility:** `gameModel.activePlayerCount(appId)`
|
|
54
|
-
> and `gameModel.activePlayerCountChanged({ appId }, handlers)` require the
|
|
55
|
-
> matching 2026-07-24 Game API schema and an app token scoped to that exact app;
|
|
56
|
-
> the subscription also requires `wsUrl`. They count active gameplay sessions,
|
|
57
|
-
> not distinct users or actors, and an abandoned session can remain counted for
|
|
58
|
-
> approximately 120 seconds. Only `FRESH` snapshots are complete:
|
|
59
|
-
> `PARTIAL`/`UNAVAILABLE` are not authoritative zeroes. The subscription is
|
|
60
|
-
> best-effort and has no initial event; establish it, query the snapshot,
|
|
61
|
-
> deduplicate by `revision`, and requery after reconnects or revision gaps.
|
|
62
|
-
|
|
63
|
-
> **v8.21.1 Node realtime routing hotfix:** app-routing responses provide a
|
|
64
|
-
> WebSocket base URL. CrowdyJS now normalizes that base to `/graphql` before
|
|
65
|
-
> constructing the realtime client; previously Node/bot clients connected to
|
|
66
|
-
> the origin root and remained stuck in `connecting` with no notifications.
|
|
67
|
-
>
|
|
68
|
-
> **Player-code authoring DX:** `playerCompute.setRequires`,
|
|
69
|
-
> `marketplace.trustGridAuthor`, self-authored `gridClientMods` fields, and
|
|
70
|
-
> version-keyed client artifact fetches require the 2026-07-22 authoring-DX
|
|
71
|
-
> migration. The v11 `mountCrowdyStudio` surface is project-first: cloud project
|
|
72
|
-
> revisions, target-scoped files, atomic autosave, and explicit draft/live/stop
|
|
73
|
-
> orchestration. Its Rust language worker receives source plus the committed
|
|
74
|
-
> platform index only—never a credential and never a server connection.
|
|
75
|
-
> CrowdyJS 11 requires the matching Crowdy Studio Game API schema; the previous
|
|
76
|
-
> package subpath, exports, client property, GraphQL roots, and schema types were
|
|
77
|
-
> removed without aliases.
|
|
78
|
-
|
|
79
|
-
> **v8.10 inventory authority:** generated craft/barter transactions work on
|
|
80
|
-
> existing Model servers. Compute-refereed durable commits require Compute SDK
|
|
81
|
-
> `0.1.3` / the `model_invoke` host call.
|
|
82
|
-
|
|
83
|
-
## Standalone builds and schema refresh
|
|
84
|
-
|
|
85
|
-
CrowdyJS is a standalone public package: a clean clone builds with
|
|
86
|
-
`npm install && npm run build` using the schema artifacts committed to this
|
|
87
|
-
repo — no other repositories and no network access required:
|
|
88
|
-
|
|
89
|
-
- `schema.gql` — merged Management API + Game API SDL.
|
|
90
|
-
- `src/generated/graphql.ts` — generated TypeScript operation types.
|
|
91
|
-
|
|
92
|
-
Schema refresh (maintainers) is explicit, from the published SDLs
|
|
93
|
-
([management-api.graphql](https://docs.crowdedkingdoms.com/schema/management-api.graphql),
|
|
94
|
-
[game-api.graphql](https://docs.crowdedkingdoms.com/schema/game-api.graphql)):
|
|
9
|
+
Authentication follows a two-token model: an identity **session token** for
|
|
10
|
+
account and admin operations, and short-lived **app-scoped tokens** for
|
|
11
|
+
gameplay, minted per app through `client.portal`.
|
|
95
12
|
|
|
96
|
-
|
|
97
|
-
npm run schema:sync:prod
|
|
98
|
-
npm run codegen
|
|
99
|
-
```
|
|
13
|
+
Start here:
|
|
100
14
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
15
|
+
- [SDK guide](https://docs.crowdedkingdoms.com/crowdyjs/readme) — the canonical
|
|
16
|
+
walkthrough of everything in this README.
|
|
17
|
+
- [Build a game](https://docs.crowdedkingdoms.com/build-a-game/intro) — a
|
|
18
|
+
hands-on tutorial from sign-in to a playable voxel game.
|
|
19
|
+
- [Platform overview](https://docs.crowdedkingdoms.com/overview/client-workflow)
|
|
20
|
+
— how a client session flows through the platform.
|
|
105
21
|
|
|
106
|
-
|
|
107
|
-
only the committed internal `src/live-coding/assets/browser-authoring-index.json` and its
|
|
108
|
-
generated TypeScript copy; it never discovers or reads a sibling game-api
|
|
109
|
-
checkout. Coordinated maintainers compare an explicitly supplied exporter path:
|
|
22
|
+
## Install
|
|
110
23
|
|
|
111
24
|
```bash
|
|
112
|
-
npm
|
|
113
|
-
# To accept that exact source, then regenerate the committed TypeScript:
|
|
114
|
-
npm run authoring-index:drift -- --source /path/to/browser-authoring-index.json --write
|
|
115
|
-
npm run authoring-index:generate
|
|
25
|
+
npm install @crowdedkingdoms/crowdyjs
|
|
116
26
|
```
|
|
117
27
|
|
|
118
|
-
|
|
119
|
-
|
|
28
|
+
CrowdyJS targets browsers by default and uses native `fetch`, `WebSocket`,
|
|
29
|
+
`crypto`, `btoa`, and `atob`. Node tools can still use the SDK, but must
|
|
30
|
+
provide browser-compatible globals when opening realtime connections (e.g.
|
|
31
|
+
pass `webSocketImpl` on Node ≤ 21 for subscriptions).
|
|
120
32
|
|
|
121
33
|
## Quick start
|
|
122
34
|
|
|
@@ -130,7 +42,7 @@ const client = createCrowdyClient({
|
|
|
130
42
|
// Game API (world data + UDP proxy)
|
|
131
43
|
httpUrl: 'https://game.example.com',
|
|
132
44
|
wsUrl: 'wss://game.example.com',
|
|
133
|
-
// Management API (passwordless sign-in, profile)
|
|
45
|
+
// Management API (passwordless sign-in, profile, admin)
|
|
134
46
|
managementUrl: 'https://management.example.com',
|
|
135
47
|
tokenStore: new BrowserLocalStorageTokenStore(),
|
|
136
48
|
realtime: {
|
|
@@ -155,390 +67,149 @@ const me = await client.users.me();
|
|
|
155
67
|
console.log(me.email);
|
|
156
68
|
```
|
|
157
69
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
per-game client — see [Overworld portals & app-scoped tokens (v7)](#overworld-portals--app-scoped-tokens-v7).
|
|
161
|
-
|
|
162
|
-
If `managementUrl` is omitted, the SDK falls back to `httpUrl` for backwards-compat with the single-endpoint deployment.
|
|
163
|
-
|
|
164
|
-
## Sub-clients at a glance
|
|
165
|
-
|
|
166
|
-
**Game-client surface** (end-user, browser-safe):
|
|
167
|
-
|
|
168
|
-
| Sub-client | What it does |
|
|
169
|
-
|---|---|
|
|
170
|
-
| `client.auth` | Passwordless sign-in (magic link, social/OIDC, dev bypass), log out, and linked identities (`myIdentities`, `linkIdentity`/`unlinkIdentity`). |
|
|
171
|
-
| `client.users` | `me`, `updateGamertag`, profile reads. |
|
|
172
|
-
| `client.session` | Token store, `restore()`, `getToken()`, manual `setToken()`. |
|
|
173
|
-
| `client.serverStatus` | `gameClientBootstrap(appId)` — per-app version info, UDP status, spatial limits. |
|
|
174
|
-
| `client.chunks`, `client.voxels`, `client.actors`, `client.avatars`, `client.state` | World data reads + writes. |
|
|
175
|
-
| `client.host` | Game-host election (`get`, `amIHost`) + actor liveness `heartbeat`. `amIHost` is UI convenience only — authoritative host gating uses `gameModelInvoke`'s `is_host` policy. |
|
|
176
|
-
| `client.teleport` | Teleport requests. |
|
|
177
|
-
| `client.channels`, `client.teams` | Messaging channels and app-scoped player teams (membership + roles). |
|
|
178
|
-
| `client.gameModel` | Abstract game model: containers, properties, functions (incl. model-driven `notify_*` effects), sessions, **app-scoped active-session counts** (`activePlayerCount`, `activePlayerCountChanged`), and **automations / NPCs** (`upsertAutomation`, `runAutomation`, `automationRuns`, `automationStats`, …). |
|
|
179
|
-
| `client.compute` | **Compute Modules** — server-side Rust/WASM logic: author + deploy source (`upsertModule`, `deployVersion`, `waitForCompile`), triggers + policy, synchronous `invoke`, and monitoring (`moduleRuns`, `moduleStats`, `moduleLogs`, `appDiagnostics`). Modules run server-only; see [Compute Modules docs](https://docs.crowdedkingdoms.com/game-api/compute-modules). |
|
|
180
|
-
| `client.playerCompute` | Player-authored SERVER/CLIENT Rust/WASM bound to player-owned grids: deploy source, activate/deactivate, list modules/versions, and delete self-authored modules. |
|
|
181
|
-
| `client.crowdyStudio` | Cloud project, personal-library, and common-file APIs for Crowdy Studio: target-scoped files, metadata/module names, pairing preference, optimistic revisions, copy-by-value imports, and atomic metadata/file saves. Generated operations are pinned to the committed merged SDL. |
|
|
182
|
-
| `client.crowdyStudioAgent` | Generated, app-token Game API transport for durable agent sessions, Relay history/session pages, descriptors/budgets, exact approvals, browser tool results, heartbeat, control mutations, and ordered event subscriptions. |
|
|
183
|
-
| `client.playerModel` | Player-owned flexible model containers and grid-confined automations (`containers`, `createContainer`, `setProperty`, `automations`, `createAutomation`, …). |
|
|
184
|
-
| `client.marketplace` | Player-code store/install/consent flows plus player-authorized grid claims: `claimGridOwnership` preserves the existing-grid policy flow, while `claimGridChunk` atomically creates and owns one chunk under `SELF_CLAIM` and `releaseClaimedGrid` releases an eligible owner-created claim. |
|
|
185
|
-
| `client.udp` | UDP proxy subscriptions + spatial mutations (`sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`, `sendTextPacket`, `sendClientEvent`). |
|
|
186
|
-
| `client.realtime` | Connection status, manual `connect()` / `disconnect()`, `onStatus()` listener. |
|
|
187
|
-
| `client.refreshGameplayToken()` | Safely rotates an active game client's app token: disconnects the old-token UDP proxy, refreshes/stores the token, and opens the new-token proxy while existing realtime handlers resubscribe in place. |
|
|
188
|
-
| `client.world(appId)` | Higher-level helpers for browser games (`actor.join`, `actor.sendState`, `actor.sendText`). |
|
|
70
|
+
The Management and Game APIs may share one origin or be split; if
|
|
71
|
+
`managementUrl` is omitted, the SDK falls back to `httpUrl`.
|
|
189
72
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
grid before the normal SDK path reaches server authorization. Crowdy Studio
|
|
194
|
-
hot-swaps only the exact version-keyed artifact returned after a successful
|
|
195
|
-
CLIENT compile.
|
|
73
|
+
**Gameplay needs an app-scoped token, not the session token.** Mint one per
|
|
74
|
+
app and drive the Game API world/UDP surface from a per-game client — see
|
|
75
|
+
[Authentication](#authentication-session-token-vs-app-scoped-tokens).
|
|
196
76
|
|
|
197
|
-
##
|
|
77
|
+
## Authentication: session token vs app-scoped tokens
|
|
198
78
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
**Conflict**, or **Offline**, with retry and conflict-resolution actions.
|
|
79
|
+
Passwordless sign-in returns an **identity session token** that works only
|
|
80
|
+
against the Management API — account, studio admin, and token minting. Each
|
|
81
|
+
game is entered with a short-lived **app-scoped token** confined to that one
|
|
82
|
+
app, so a game stack never receives the player's full session.
|
|
204
83
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
events. Give the host an explicit width and height; Crowdy Studio relayouts
|
|
208
|
-
Monaco and collapses secondary panes from its own container width.
|
|
84
|
+
Use two clients: an Overworld/identity client (session token) and a per-game
|
|
85
|
+
client (app token), sharing only the Management URL.
|
|
209
86
|
|
|
210
87
|
```ts
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
playerCompute: game.playerCompute,
|
|
216
|
-
playerWallet: identity.playerWallet,
|
|
217
|
-
appId,
|
|
218
|
-
gridId,
|
|
219
|
-
grid,
|
|
220
|
-
workerUrl: playerCodeGlueWorkerUrl,
|
|
221
|
-
onHostCall,
|
|
88
|
+
// Overworld/identity client
|
|
89
|
+
const overworld = createCrowdyClient({
|
|
90
|
+
managementUrl,
|
|
91
|
+
tokenStore: new BrowserLocalStorageTokenStore('crowdyjs:session'),
|
|
222
92
|
});
|
|
93
|
+
// Passwordless sign-in (magic link, social/OIDC, or dev bypass) yields the session token.
|
|
94
|
+
await overworld.auth.requestLoginLink({ email, redirectUri });
|
|
95
|
+
await overworld.auth.completeLoginLink(tokenFromLink);
|
|
223
96
|
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
97
|
+
// Native / same-origin: mint directly, then build a game client.
|
|
98
|
+
const t = await overworld.portal.mintAppToken(appId);
|
|
99
|
+
const game = createCrowdyClient({
|
|
100
|
+
httpUrl: t.gameApiUrl!,
|
|
101
|
+
wsUrl: t.gameApiWsUrl!,
|
|
102
|
+
managementUrl,
|
|
103
|
+
tokenStore: new BrowserLocalStorageTokenStore('crowdyjs:app:' + appId),
|
|
104
|
+
});
|
|
105
|
+
game.setToken(t.token);
|
|
106
|
+
game.world(appId).subscribe({ actorUpdate: (n) => { /* ... */ } });
|
|
227
107
|
```
|
|
228
108
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
`updateFile`) and project settings—there is no session-only source blob, fixed
|
|
232
|
-
module name, or template JSON API. The deploy conversion to the Game API's
|
|
233
|
-
legacy `sourceFilesJson` input happens only inside the controller's
|
|
234
|
-
`playerCompute.deploy` call.
|
|
235
|
-
|
|
236
|
-
The built-in UI provides a project switcher/new-project wizard
|
|
237
|
-
(server/client/full-stack), target explorer, personal library/common files,
|
|
238
|
-
tabs, settings, Problems/Build/Logs/Runs/Invoke panels, and explicit **Test
|
|
239
|
-
draft**, **Deploy live**, and **Stop project** actions. Full-stack deployment is
|
|
240
|
-
ordered: save once → compile CLIENT → compile SERVER → set/clear the pairing
|
|
241
|
-
requirement only after both compiles → enable SERVER → hot-swap the exact
|
|
242
|
-
version-keyed CLIENT artifact. Partial compile failures never write a new
|
|
243
|
-
requirement. Stop always attempts both server disable and client/poll cleanup
|
|
244
|
-
and reports each failure.
|
|
245
|
-
|
|
246
|
-
Monaco uses target-prefixed model URIs (`.../server/Cargo.toml` and
|
|
247
|
-
`.../client/Cargo.toml`) and opens every loaded project/library/common Rust file
|
|
248
|
-
in one bounded local worker workspace. Completion, hover, symbols, and
|
|
249
|
-
definition therefore work across loaded files; lifecycle snippets and hover
|
|
250
|
-
notes distinguish SERVER platform execution from CLIENT broker host calls.
|
|
251
|
-
Local tree-sitter markers are labeled advisory. Platform rustc output is parsed
|
|
252
|
-
separately into authoritative path/line/column markers and remains visible in
|
|
253
|
-
Build.
|
|
254
|
-
|
|
255
|
-
The Rust worker receives source files and the strict embedded platform index
|
|
256
|
-
only. It receives no credential, opens no socket/fetch path, and never falls
|
|
257
|
-
back to a server language service. If Monaco, Worker, a WASM asset, or a custom
|
|
258
|
-
platform index fails, the same Crowdy Studio mount keeps project/target tabs and
|
|
259
|
-
uses one file-aware textarea—never a raw JSON blob.
|
|
260
|
-
|
|
261
|
-
Modern bundlers must preserve module workers and package `.wasm` assets. Custom
|
|
262
|
-
pipelines can pass `languageWorkerFactory` and `editorWorkerFactory`. The
|
|
263
|
-
embedded index remains the byte-identical game export (`schemaVersion: 2`, Rust
|
|
264
|
-
1.97.1, SDK 0.1.5, ABI 0, 725 symbols, content hash
|
|
265
|
-
`3f5f39d4…18ffb`).
|
|
266
|
-
|
|
267
|
-
### Embed Crowdy Studio in your game (v12.1)
|
|
268
|
-
|
|
269
|
-
Most games should not hand-roll the window chrome around `mountCrowdyStudio`.
|
|
270
|
-
The embed kit ships the proven Blocks with Friends shell as reusable, game
|
|
271
|
-
-agnostic components (exported from `@crowdedkingdoms/crowdyjs/crowdy-studio`):
|
|
272
|
-
|
|
273
|
-
- **`createCrowdyStudioEmbed(options)` / `CrowdyStudioEmbed`** — a responsive
|
|
274
|
-
panel that renders a resizable right dock on desktop and a focus-trapped
|
|
275
|
-
fullscreen modal below 1000 px. It owns Escape/close-key semantics, the
|
|
276
|
-
compact header (title, grid pill, on-demand Context drawer, Close), loading
|
|
277
|
-
and retry chrome, and assembles the `mountCrowdyStudio` call — including the
|
|
278
|
-
agent block when the client exposes `crowdyStudioAgent` and the game passes a
|
|
279
|
-
`playerHost`.
|
|
280
|
-
- **`CrowdyStudioEmbedDock`** — the accessible game/studio splitter with
|
|
281
|
-
persisted width (`ck:crowdy-studio:embed:dock-width:v1`), arrow-key/Home/End
|
|
282
|
-
resize, and ARIA value text.
|
|
283
|
-
- **`CrowdyStudioTextHud`** — the text-only presentation sink for CLIENT-mod
|
|
284
|
-
`hud_set` payloads plus the drawer HUD preview. Untrusted payloads render as
|
|
285
|
-
text, never HTML.
|
|
286
|
-
- **`ensureCrowdyStudioEmbedStyles()`** — injected `ck-crowdy-studio-embed-*`
|
|
287
|
-
styles; games restyle by overriding classes. While docked, the panel sets
|
|
288
|
-
`--ck-game-right-inset` on `document.body` so game HUDs can keep clear of
|
|
289
|
-
the dock.
|
|
109
|
+
Browser cross-origin handoff is OAuth2 Authorization Code + PKCE — the
|
|
110
|
+
verifier never leaves the game origin:
|
|
290
111
|
|
|
291
112
|
```ts
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
113
|
+
// Game origin, on "enter": redirect to the Overworld authorize page.
|
|
114
|
+
location.assign(await game.portal.beginEntry({
|
|
115
|
+
appId, authorizeUrl: 'https://overworld.example.com/authorize',
|
|
116
|
+
redirectUri: location.origin + location.pathname,
|
|
117
|
+
}));
|
|
296
118
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
appId,
|
|
300
|
-
gameName: 'My Game',
|
|
301
|
-
suppressGameplayInput: () => pauseInput(),
|
|
302
|
-
onLayoutChange: () => resizeCanvas(),
|
|
303
|
-
});
|
|
119
|
+
// Overworld /authorize page (holds the session token):
|
|
120
|
+
location.assign(await overworld.portal.handleAuthorizeRequest());
|
|
304
121
|
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
gridId,
|
|
308
|
-
targetPermissions: {
|
|
309
|
-
SERVER: { canWrite: true, canRun: true },
|
|
310
|
-
CLIENT: { canWrite: false, canRun: false }, // SERVER-only embeds omit workerUrl
|
|
311
|
-
},
|
|
312
|
-
});
|
|
122
|
+
// Game origin, on callback boot: exchange code+verifier -> app token (stored).
|
|
123
|
+
const entered = await game.portal.completeEntry();
|
|
313
124
|
```
|
|
314
125
|
|
|
315
|
-
|
|
316
|
-
optional `hud` sink, and a `playerHost` adapter. Note that CLIENT mods outside
|
|
317
|
-
Blocks with Friends remain gated by program decision D13; new games should
|
|
318
|
-
start SERVER-only exactly as above.
|
|
126
|
+
Game-to-game routes through the Overworld for a fresh per-game token.
|
|
319
127
|
|
|
320
|
-
|
|
321
|
-
`@crowdedkingdoms/crowdyjs/player-host`: **`PlayerControlGate`** (capture-phase
|
|
322
|
-
human preemption seam with offline Stop) and **`AgentControlBanner`** (the
|
|
323
|
-
always-visible-on-control Pause/Stop region, self-injected
|
|
324
|
-
`ck-agent-control-*` styles).
|
|
128
|
+
Notes:
|
|
325
129
|
|
|
326
|
-
|
|
130
|
+
- `portal.mintAppToken(appId)` returns the token plus `gameApiUrl` /
|
|
131
|
+
`gameApiWsUrl` / `expiresAt`; it does **not** store the token on the identity
|
|
132
|
+
client — set it on the per-game client.
|
|
133
|
+
- Each client's `AuthState` is observed by both its HTTP client and its
|
|
134
|
+
realtime socket, so HTTP and WebSocket auth never drift within a client.
|
|
135
|
+
- `client.session.restore()` reads from the configured `tokenStore`.
|
|
136
|
+
`BrowserLocalStorageTokenStore` is provided; bring your own `TokenStore` for
|
|
137
|
+
SSR or Node. PKCE state uses `BrowserSessionPkceStore` by default.
|
|
138
|
+
- Use `client.auth.setToken(token)` to seed a token externally (e.g. when
|
|
139
|
+
restoring auth from a non-default storage).
|
|
327
140
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
The committed merged schema and generated operations cover:
|
|
141
|
+
Deeper reading: [Portals & app-scoped tokens](https://docs.crowdedkingdoms.com/management-api/portals-and-app-tokens)
|
|
142
|
+
and [Sign in (passwordless)](https://docs.crowdedkingdoms.com/management-api/authentication).
|
|
331
143
|
|
|
332
|
-
|
|
333
|
-
- `crowdyStudioProjectCreate(input)`
|
|
334
|
-
- `crowdyStudioProjectSave(input)` for one atomic metadata/file delta
|
|
335
|
-
- `crowdyStudioLibraryFiles(appId)` / `crowdyStudioLibrarySave(input)`
|
|
336
|
-
- `crowdyStudioCommonFiles(appId)`
|
|
337
|
-
- `crowdyStudioProjectImportFile(input)` for copy-by-value imports
|
|
144
|
+
### Token refresh during gameplay
|
|
338
145
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
146
|
+
Before an app token expires, call `game.refreshGameplayToken()` while gameplay
|
|
147
|
+
is active. It closes the old-token UDP proxy, refreshes and stores the token,
|
|
148
|
+
and opens the new-token proxy while existing realtime handlers resubscribe in
|
|
149
|
+
place. It deliberately stops on the first failed stage: if the old proxy cannot
|
|
150
|
+
confirm disconnect, no refresh is attempted; if refresh fails, the old token is
|
|
151
|
+
retained; if the new proxy connect fails, the fresh token remains stored —
|
|
152
|
+
surface the error and retry `game.udp.connect()` instead of rotating again.
|
|
345
153
|
|
|
346
|
-
|
|
154
|
+
`game.portal.refresh()` remains available for clients with no active UDP
|
|
155
|
+
lifecycle to preserve.
|
|
347
156
|
|
|
348
|
-
|
|
349
|
-
operations plus the production `CrowdyAgentGraphQLTransport` exposed as
|
|
350
|
-
`client.crowdyStudioAgent`:
|
|
351
|
-
|
|
352
|
-
```ts
|
|
353
|
-
import {
|
|
354
|
-
CROWDY_AGENT_TOOL_REGISTRY_V1,
|
|
355
|
-
CrowdyStudioAgentController,
|
|
356
|
-
} from '@crowdedkingdoms/crowdyjs/agent';
|
|
357
|
-
|
|
358
|
-
const agent = new CrowdyStudioAgentController({
|
|
359
|
-
transport: game.crowdyStudioAgent,
|
|
360
|
-
createSession: {
|
|
361
|
-
appId,
|
|
362
|
-
projectId,
|
|
363
|
-
gridId,
|
|
364
|
-
mode: 'BUILD',
|
|
365
|
-
providerDataConsent: true,
|
|
366
|
-
idempotencyKey: crypto.randomUUID(),
|
|
367
|
-
},
|
|
368
|
-
beforeAgentWork: () => studio.controller.prepareForAgentWork(),
|
|
369
|
-
});
|
|
370
|
-
await agent.initialize(); // attach epoch → durable replay/gap fill → live tail
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
`CROWDY_AGENT_TOOL_REGISTRY_V1` is an immutable, digest-pinned registry of the
|
|
374
|
-
minimum Studio/project/workspace/library/template/diagnostics/runtime and
|
|
375
|
-
generic game tools. Every descriptor has exact logical and provider wire names,
|
|
376
|
-
semantic version, bounded input/output JSON schemas,
|
|
377
|
-
`additionalProperties:false`, executor, mode, risk/effect/reversibility,
|
|
378
|
-
scope, approval, idempotency, timeout, and redaction metadata. Input schemas
|
|
379
|
-
reject caller-supplied identity, epoch, lease, approval, endpoint, token, and
|
|
380
|
-
other authority fields. `CrowdyAgentToolRegistry.fromWireName` is exact and
|
|
381
|
-
case-sensitive; browser dispatch is execute-once by `toolCallId`, and ambiguous
|
|
382
|
-
effects become `OUTCOME_UNKNOWN` instead of being repeated.
|
|
383
|
-
|
|
384
|
-
`CrowdyStudioAgentTransportV1` owns no credentials and exposes only typed
|
|
385
|
-
durable orchestration operations: session/history/descriptor/budget queries;
|
|
386
|
-
create/attach/mode/event-ack/message/approval/tool-result/lease/pause/resume/
|
|
387
|
-
cancel/close mutations; and the ordered event subscription. A generated adapter
|
|
388
|
-
maps these methods to the operation names in
|
|
389
|
-
`CROWDY_AGENT_GRAPHQL_OPERATIONS_V1`:
|
|
390
|
-
|
|
391
|
-
- `crowdyStudioAgentSession`, `crowdyStudioAgentSessions`,
|
|
392
|
-
`crowdyStudioAgentHistory`, `crowdyStudioAgentToolDescriptors`, and
|
|
393
|
-
`crowdyStudioAgentBudget`;
|
|
394
|
-
- `crowdyStudioAgentCreateSession`, `AttachClient`, `SetMode`,
|
|
395
|
-
`AcknowledgeEvents`, `Heartbeat`, `SendMessage`, `ApproveTool`, `RejectTool`,
|
|
396
|
-
`ToolResult`, `GrantLease`, `RevokeLease`, `Pause`, `Resume`, `CancelRun`,
|
|
397
|
-
and `CloseSession` (all with the `crowdyStudioAgent` prefix);
|
|
398
|
-
- `crowdyStudioAgentEvents(sessionId, afterSeq, clientEpoch)`.
|
|
399
|
-
|
|
400
|
-
The controller applies only contiguous decimal-string sequences, deduplicates
|
|
401
|
-
event IDs, fills gaps from durable history, acknowledges the highest
|
|
402
|
-
contiguous cursor, fences old attach epochs, and explicitly reconnects without
|
|
403
|
-
resuming a run or Play lease. Approval methods use the exact displayed argument
|
|
404
|
-
hash. Human edit/game input calls local preemption before best-effort transport
|
|
405
|
-
cleanup. Attach uses a stable `clientInstanceId` and the server
|
|
406
|
-
`replayAfterSeq`; public message input maps to Game API `content`, cancellation
|
|
407
|
-
always sends an explicit run id, and PLAY sends a two-second heartbeat only
|
|
408
|
-
while attached, active, and visible. Heartbeat, kill, and stale-epoch failures
|
|
409
|
-
immediately clear local authority.
|
|
410
|
-
|
|
411
|
-
Mode changes adopt the complete server-repinned session (registry digest,
|
|
412
|
-
provider/app policy revisions, and context version) before loading its new
|
|
413
|
-
effective descriptors. The Studio mount resolves the selected saved project
|
|
414
|
-
after `CrowdyStudioController.initialize()`, overrides caller guesses when
|
|
415
|
-
creating a BUILD session, and rejects an existing session bound to another
|
|
416
|
-
project. Because v1 has no set-project mutation, switching projects fences the
|
|
417
|
-
agent and requires a new session/remount.
|
|
418
|
-
|
|
419
|
-
An active BUILD workspace lease is renewed every ten seconds through the same
|
|
420
|
-
authenticated heartbeat; the server returns its renewed 30-second expiry.
|
|
421
|
-
Human edits, project/context changes, lease revocation, disconnect, and destroy
|
|
422
|
-
stop renewal and abort browser handlers. Run events retain their stable
|
|
423
|
-
`code`, `reason`, and typed safe `error`.
|
|
424
|
-
|
|
425
|
-
The committed descriptor digest fixture is checked on every build against the
|
|
426
|
-
canonical registry. Coordinated refreshes first run
|
|
427
|
-
`npm run agent-descriptors:drift -- --source <game-api-fixture>`; builds then
|
|
428
|
-
recompute the full and canonical 28-tool Game API subset digest (14 mandatory
|
|
429
|
-
game plus 14 Studio/diagnostic/runtime tools) so the fixture cannot silently
|
|
430
|
-
diverge.
|
|
431
|
-
|
|
432
|
-
Mount the responsive agent dock by adding `agent`; omit it to retain the v11
|
|
433
|
-
manual UI unchanged:
|
|
434
|
-
|
|
435
|
-
```ts
|
|
436
|
-
const studio = await mountCrowdyStudio(host, {
|
|
437
|
-
projectProvider: game.crowdyStudio,
|
|
438
|
-
playerCompute: game.playerCompute,
|
|
439
|
-
appId,
|
|
440
|
-
gridId,
|
|
441
|
-
agent: {
|
|
442
|
-
transport: game.crowdyStudioAgent,
|
|
443
|
-
sessionId,
|
|
444
|
-
playerHost: bwfPlayerHostAdapter,
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
studio.agent?.sendMessage('Explain the current diagnostics');
|
|
449
|
-
studio.controlLeaseManager?.preempt('HUMAN_INPUT');
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
The dock provides human-owned Ask/Build/Play selection, chat streaming,
|
|
453
|
-
plan/tool status, exact approval hashes, diff/checkpoint cards, budget, visible
|
|
454
|
-
lease scope/expiry, Pause/Resume/Stop, and checkpoint-restore requests. It uses
|
|
455
|
-
text nodes for all untrusted content, ARIA status/log/group labels, visible
|
|
456
|
-
focus, keyboard submission, and a container-query layout that moves the agent
|
|
457
|
-
dock below narrow embeds.
|
|
458
|
-
|
|
459
|
-
The headless `CrowdyStudioController` now also exposes:
|
|
157
|
+
## Game-loop lifecycle
|
|
460
158
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
159
|
+
1. Sign in (passwordless) on the identity client with `client.auth`, or
|
|
160
|
+
restore a stored session with `client.session.restore()`. This yields the
|
|
161
|
+
**session token** (Management API only).
|
|
162
|
+
2. Mint an **app-scoped token** for the app (`identity.portal.mintAppToken(appId)`,
|
|
163
|
+
or the PKCE portal flow across origins) and build a per-game client holding
|
|
164
|
+
it (`game.setToken(token)`). The gameplay steps below run on that **game**
|
|
165
|
+
client.
|
|
166
|
+
3. Subscribe to UDP proxy notifications with `game.udp.subscribe(handlers, appId)`
|
|
167
|
+
— `appId` is **required** (the SDK opens the realtime socket on demand and
|
|
168
|
+
scopes it to that app).
|
|
169
|
+
4. Join a chunk by sending an initial actor update.
|
|
170
|
+
5. Send actor, voxel, text, audio, and client-event updates through `game.udp`
|
|
171
|
+
or the higher-level `game.world(appId)` helpers.
|
|
172
|
+
6. Before the app token expires, call `game.refreshGameplayToken()`.
|
|
173
|
+
7. Call `client.close()` (and `game.close()`) when disposing the SDK instances.
|
|
470
174
|
|
|
471
|
-
|
|
175
|
+
## Sub-clients at a glance
|
|
472
176
|
|
|
473
|
-
|
|
474
|
-
`PlayerHostAdapterV1`, capability/observation/command/result types and schemas,
|
|
475
|
-
`AgentControlLeaseManager`, and `createPlayerHostAgentTools`. A game adapter
|
|
476
|
-
implements only:
|
|
177
|
+
**Game-client surface** (end-user, browser-safe):
|
|
477
178
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
179
|
+
| Sub-client | What it does |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `client.auth` | Passwordless sign-in (magic link, social/OIDC, dev bypass), log out, and linked identities (`myIdentities`, `linkIdentity`/`unlinkIdentity`). |
|
|
182
|
+
| `client.users` | `me`, `updateGamertag`, profile reads. |
|
|
183
|
+
| `client.session` | Token store, `restore()`, `getToken()`, manual `setToken()`. |
|
|
184
|
+
| `client.portal` | App-scoped token minting (`mintAppToken`) and the cross-origin PKCE entry flow (`beginEntry` / `handleAuthorizeRequest` / `completeEntry` / `refresh`). |
|
|
185
|
+
| `client.platform` | Public platform configuration (`config()`). |
|
|
186
|
+
| `client.serverStatus` | `gameClientBootstrap(appId)` — per-app version info, UDP status, spatial limits. |
|
|
187
|
+
| `client.chunks`, `client.voxels`, `client.actors`, `client.avatars`, `client.state` | World data reads + writes: terrain/LODs, voxel edit + history/rollback, durable actors, avatars, per-user app state blobs. |
|
|
188
|
+
| `client.host` | Game-host election (`get`, `amIHost`) + actor liveness `heartbeat`. `amIHost` is UI convenience only — authoritative host gating uses `gameModelInvoke`'s `is_host` policy. |
|
|
189
|
+
| `client.teleport` | Teleport requests. |
|
|
190
|
+
| `client.channels`, `client.teams` | Messaging channels and app-scoped player teams (membership + roles). |
|
|
191
|
+
| `client.gameModel` | Abstract game model: containers, properties, functions (incl. model-driven `notify_*` effects), sessions, app-scoped active-session counts (`activePlayerCount`, `activePlayerCountChanged`), container-change push (`containerChanged`), flow-correlation timelines (`flow`), and automations / NPCs (`upsertAutomation`, `runAutomation`, `automationRuns`, `automationStats`, …). |
|
|
192
|
+
| `client.compute` | Compute Modules — server-side Rust/WASM logic: author + deploy source (`upsertModule`, `deployVersion`, `deployTemplate`, `waitForCompile`), triggers + policy, synchronous `invoke`, and monitoring (`moduleRuns`, `moduleStats`, `moduleLogs`, `appDiagnostics`). See [Compute Modules](https://docs.crowdedkingdoms.com/game-api/compute-modules). |
|
|
193
|
+
| `client.playerCompute` | Player-authored SERVER/CLIENT Rust/WASM bound to player-owned grids: deploy source, activate/deactivate, list modules/versions, delete self-authored modules. |
|
|
194
|
+
| `client.playerModel` | Player-owned flexible model containers and grid-confined automations (`containers`, `createContainer`, `setProperty`, `automations`, `createAutomation`, …). |
|
|
195
|
+
| `client.playerWallet` | Player spend: balance, spend caps, card setup, policy, charges. |
|
|
196
|
+
| `client.marketplace` | Player-code store/install/consent flows plus player-authorized grid claims (`claimGridOwnership`, `claimGridChunk`, `releaseClaimedGrid`) and client-mod artifact fetches. |
|
|
197
|
+
| `client.crowdyStudio` | Cloud project, personal-library, and common-file APIs for Crowdy Studio: target-scoped files, metadata/module names, optimistic revisions, copy-by-value imports, atomic saves. |
|
|
198
|
+
| `client.crowdyStudioAgent` | Generated, app-token Game API transport for durable agent sessions: history/session pages, descriptors/budgets, approvals, tool results, heartbeat, control mutations, ordered event subscriptions. |
|
|
199
|
+
| `client.udp` | UDP proxy subscriptions + spatial mutations (`sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`, `sendTextPacket`, `sendClientEvent`, `sendSingleActorMessage`, `sendChannelMessage`). |
|
|
200
|
+
| `client.realtime` | Connection status, manual `connect()` / `disconnect()`, `onStatus()` listener. |
|
|
201
|
+
| `client.refreshGameplayToken()` | Safely rotates an active game client's app token (see [Token refresh](#token-refresh-during-gameplay)). |
|
|
202
|
+
| `client.world(appId)` | Higher-level helpers for browser games (`actor.join`, `actor.sendState`, `actor.sendText`, `actor.sendToActor`). |
|
|
203
|
+
| `client.kit(appId)` | Game Kit: ready-made mappings of game concepts onto the game model — see [Game Kit](#game-kit). |
|
|
204
|
+
| `createWorldSession(client, appId, config)` | World Stores: opt-in, SDK-managed game state from the `@crowdedkingdoms/crowdyjs/stores` subpath — see [World Stores](#world-stores). |
|
|
488
205
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
`FishingService`, `RideService`, and existing referee/model calls. Construct
|
|
492
|
-
`createPlayerHostAgentTools(adapter)` and pass `adapter` as the mount's
|
|
493
|
-
`playerHost`. The returned lease manager enforces TTL, scopes, client epoch,
|
|
494
|
-
host/controlled-entity revision, observation freshness, modal/death state,
|
|
495
|
-
per-command rate limits, approvals, and tool-call dedup. Call
|
|
496
|
-
`leaseManager.preempt('HUMAN_INPUT')` synchronously on key/mouse/touch input,
|
|
497
|
-
`ESCAPE` on Escape, and the corresponding reason on death, target/context
|
|
498
|
-
change, disconnect, or Stop. `clearAgentIntent` must immediately zero movement,
|
|
499
|
-
look, and pending action intent. BWF does not integrate through raw CrowdyJS,
|
|
500
|
-
DOM input, UDP packets, `PlayerCodeBroker`, or client-mod host calls.
|
|
501
|
-
|
|
502
|
-
BWF should pass only `game.crowdyStudioAgent` and its
|
|
503
|
-
`BwfPlayerHostAdapter` to the Studio mount; the mount derives the selected
|
|
504
|
-
project id. Its adapter must implement the exact 14 mandatory game surfaces,
|
|
505
|
-
classify combat as conditional approval, return typed failed/denied/unknown
|
|
506
|
-
outcomes, and make `clearAgentIntent` synchronously cancel shared movement and
|
|
507
|
-
action intents when the dispatcher aborts.
|
|
508
|
-
|
|
509
|
-
The reconciled Game API development pilot advertises the exact 28-tool
|
|
510
|
-
follow-up subset, including all 14 mandatory game descriptors. CrowdyJS routes
|
|
511
|
-
all game tools through
|
|
512
|
-
`PlayerHostAdapterV1`, the scoped lease gate, conditional combat approval,
|
|
513
|
-
safety stop, heartbeat fencing, and typed result continuation. BWF Play now
|
|
514
|
-
remains blocked only on the concrete BWF adapter/shared-intent wiring and its
|
|
515
|
-
host/app policy configuration.
|
|
516
|
-
|
|
517
|
-
The same browser dispatcher routes backend-advertised `runtime.test_draft`,
|
|
518
|
-
`runtime.deploy_live`, `runtime.invoke`, and `runtime.stop` through the
|
|
519
|
-
headless Studio controller. Draft testing is routine BUILD work; live deploy
|
|
520
|
-
and LIVE invoke require the exact approval grant; stop remains an idempotent
|
|
521
|
-
safety action. Inner game `FAILED`, `DENIED`, or `OUTCOME_UNKNOWN` results are
|
|
522
|
-
promoted to matching outer terminal tool results and are never reported as
|
|
523
|
-
successful.
|
|
524
|
-
|
|
525
|
-
Runtime execution is plan-bound: requested targets must exactly equal the
|
|
526
|
-
selected project’s authoritative target set, so a full-stack project cannot run
|
|
527
|
-
CLIENT after SERVER-only authorization. Live deploy additionally rechecks the
|
|
528
|
-
saved revision, canonical project content hash (including module names/files),
|
|
529
|
-
and pairing preference after autosave. Invoke checks the exact running
|
|
530
|
-
DRAFT/LIVE environment and export; stop always stops the complete selected
|
|
531
|
-
project as a safety action rather than interpreting a partial target.
|
|
532
|
-
|
|
533
|
-
| `createWorldSession(client, appId, config)` (from `@crowdedkingdoms/crowdyjs/stores`) | World Stores: opt-in, SDK-managed game state — typed codecs (`structCodec` binary DSL), your own actor with a 5 Hz send loop (`session.self`), a remote-actor registry with lanes/history/staleness (`session.actors`), attributed send errors (`session.errors`), a chunk/voxel cache with realtime merge + worldgen write-back (`session.chunks`), channel/direct-message inboxes + a typed event router, host tracking, typed save/avatar state, and a game-model container mirror. Only configured stores exist (compile-time + runtime); unimported stores tree-shake away. |
|
|
534
|
-
| `client.kit(appId)` | Game Kit: ready-made mappings of game concepts onto the game model — `kit.inventory`, `kit.objects` (lockable doors/chests with custom permissions), `kit.npcs`, `kit.plots` (buy/rent land with transactional, replication-enforced grid grants), and the genre layers `kit.economy` (wallets/shops/trades/market), `kit.progression` (xp/skills/achievements/rating), `kit.loot`, `kit.quests`, `kit.combat`, `kit.matches` (session lobbies/turns/scores with notify-to-pull channels), `kit.decks` (hidden hands), `kit.worldsim` (clock/nodes/crops/waves), `kit.social` (parties/guilds/chat over teams+channels), `kit.leaderboards`, `kit.features` (tier gates), and the engine-aware helpers `kit.mobs` (refereed attacks, defs/slots, contact-damage parsing), `kit.pets` (adopt/summon/dismiss/rename), `kit.instances` (private world slices, seeded runs), `kit.director` (encounter runs), `kit.matchmaking` (queues/proposals/rating), `kit.minigames` (invoke-loop wrapper), `kit.economy.orderBook` (escrowed bid/ask market), engine paths on `kit.matches`/`kit.decks`/`kit.leaderboards`, `kit.quests` tutorial sequencing, `kit.engines` (compute capability detection) with `kit/wire` (the engine pose codec, `engineLanes()`, and the 77/90/91/92/93 event parsers) — plus blueprint builders + `kit.deploy(...)` for the admin "load the rules" step. |
|
|
535
|
-
|
|
536
|
-
**Studio-admin surface** (privileged; drive with a server-side / studio token, grouped under `client.admin`):
|
|
206
|
+
**Studio-admin surface** (privileged; drive with a server-side / studio token,
|
|
207
|
+
grouped under `client.admin` and mirrored at the top level):
|
|
537
208
|
|
|
538
209
|
| Sub-client | What it does |
|
|
539
210
|
|---|---|
|
|
540
211
|
| `client.organizations` | Orgs, members, RBAC roles, org API tokens. |
|
|
541
|
-
| `client.apps` | App discovery + routing (`
|
|
212
|
+
| `client.apps` | App registry, discovery + routing (`create`, `routeFor`, `marketplace`), visibility, and player-code admission mode / allow-list administration. |
|
|
542
213
|
| `client.appAccess` | Access tiers + per-user grants. |
|
|
543
214
|
| `client.billing` | Org wallet + per-app spend budgets. |
|
|
544
215
|
| `client.payments` | Payment checkouts (wallet top-ups, plan purchases). |
|
|
@@ -546,36 +217,31 @@ project as a safety action rather than interpreting a partial target.
|
|
|
546
217
|
| `client.usage` | Replication + GraphQL usage reporting. |
|
|
547
218
|
| `client.sharedEnvironment` | Publish to shared, runtime gating, spend caps, auto-billing. |
|
|
548
219
|
| `client.gameApps` | App grids (`createGrid` / `deleteGrid`), first-class grid ownership (`ownership` / `assignOwnership` / `transferOwnership`), and grid runtime-permission administration. |
|
|
549
|
-
| `client.apps` | App registry/routing plus player-code admission mode and code/author/org allow-list administration. |
|
|
550
220
|
|
|
551
221
|
**Operator surface** (platform operations; requires `is_operator`):
|
|
552
222
|
|
|
553
223
|
| Sub-client | What it does |
|
|
554
224
|
|---|---|
|
|
555
|
-
| `client.operator` | Platform compute ceilings (
|
|
225
|
+
| `client.operator` | Platform compute ceilings (`computePlatformCeilings`, `setComputePlatformCeilings`). Infrastructure operations live in the separate infra-control-plane service, not this SDK. |
|
|
556
226
|
|
|
557
|
-
Auth, user reads, and the studio-admin / operator surfaces target
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
1. Sign in (passwordless) on the identity client with `client.auth` — `requestLoginLink`/`completeLoginLink` (magic link), `socialLoginStart`/`socialLoginComplete` (social/OIDC), or `devLogin` (dev/test only) — or `client.session.restore()`. This yields the **session token** (Management API only).
|
|
562
|
-
2. Mint an **app-scoped token** for the app (`identity.portal.mintAppToken(appId)`, or the PKCE portal flow across origins) and build a per-game client holding it (`game.setToken(token)`). The gameplay steps below run on that **game** client.
|
|
563
|
-
3. Subscribe to UDP proxy notifications with `game.udp.subscribe(handlers, appId)` — `appId` is **required** (the SDK opens the realtime socket on demand and scopes it to that app).
|
|
564
|
-
4. Join a chunk by sending an initial actor update.
|
|
565
|
-
5. Send actor, voxel, text, audio, and client-event updates through `game.udp` or the higher-level `game.world(appId)` helpers.
|
|
566
|
-
6. Before the app token expires, call `game.refreshGameplayToken()` while gameplay is active. It closes the old-token UDP proxy before rotating the token, opens the new-token proxy, and lets the existing realtime subscription restart without adding handlers. Use `game.portal.refresh()` directly only when no UDP proxy lifecycle needs to be preserved.
|
|
567
|
-
7. Call `client.close()` (and `game.close()`) when disposing the SDK instances.
|
|
227
|
+
Auth, user reads, and the studio-admin / operator surfaces target
|
|
228
|
+
`managementUrl` and use the **identity session token**; the game-client
|
|
229
|
+
world/UDP surfaces target `httpUrl` / `wsUrl` and require an **app-scoped
|
|
230
|
+
token** for that app.
|
|
568
231
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
232
|
+
CrowdyJS wraps the full public Management + Game API surface — every
|
|
233
|
+
non-deprecated public root field has a typed method, with Relay `*Connection`
|
|
234
|
+
cursor-pagination variants alongside the legacy offset lists. The SDK never
|
|
235
|
+
relaxes server-side authorization: exposing an operation here just gives you a
|
|
236
|
+
typed wrapper; the caller still needs the right token and permission. Drive
|
|
237
|
+
privileged surfaces from a studio backend with an org-scoped or admin token,
|
|
238
|
+
not from an untrusted browser.
|
|
575
239
|
|
|
576
240
|
## Per-app routing
|
|
577
241
|
|
|
578
|
-
|
|
242
|
+
`mintAppToken` returns `gameApiUrl` / `gameApiWsUrl`, so you rarely need a
|
|
243
|
+
separate routing query. When you do (e.g. pre-flight discovery), query the
|
|
244
|
+
app's routing fields on the Management API:
|
|
579
245
|
|
|
580
246
|
```graphql
|
|
581
247
|
query AppForRouting($appId: BigInt!) {
|
|
@@ -588,23 +254,21 @@ query AppForRouting($appId: BigInt!) {
|
|
|
588
254
|
}
|
|
589
255
|
```
|
|
590
256
|
|
|
591
|
-
`gameApiUrl` is populated for
|
|
592
|
-
(`deploymentTarget: "shared"`) apps. When it's set, build a
|
|
593
|
-
`CrowdyClient` with `httpUrl: gameApiUrl` (and the matching `wsUrl`) holding
|
|
594
|
-
app's
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
so you rarely need this separate routing query. Apps with no `gameApiUrl` keep
|
|
598
|
-
working against the default `httpUrl` you configured.
|
|
257
|
+
`gameApiUrl` is populated for both dedicated (`splitMode`) and shared
|
|
258
|
+
(`deploymentTarget: "shared"`) apps. When it's set, build a second
|
|
259
|
+
`CrowdyClient` with `httpUrl: gameApiUrl` (and the matching `wsUrl`) holding
|
|
260
|
+
that app's app-scoped token, then drive gameplay through that client. Apps
|
|
261
|
+
with no `gameApiUrl` keep working against the default `httpUrl` you
|
|
262
|
+
configured. See [Loading an app's Game API](https://docs.crowdedkingdoms.com/crowdyjs/shared-environment-routing).
|
|
599
263
|
|
|
600
264
|
## Realtime notifications
|
|
601
265
|
|
|
602
|
-
`subscribe` takes the handlers **and a required `appId`** (second argument).
|
|
603
|
-
Game API scopes the realtime session to that app and rejects an
|
|
604
|
-
subscription with a `RealtimeConnectionEvent`
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
app
|
|
266
|
+
`subscribe` takes the handlers **and a required `appId`** (second argument).
|
|
267
|
+
The Game API scopes the realtime session to that app and rejects an
|
|
268
|
+
app-agnostic subscription with a `RealtimeConnectionEvent`
|
|
269
|
+
(`code: 'APP_ID_REQUIRED'`); a missing or invalid gameplay token is rejected
|
|
270
|
+
with `AUTH_REQUIRED`. Run one client per app (each holding that app's
|
|
271
|
+
app-scoped token) when a player is in multiple apps at once.
|
|
608
272
|
|
|
609
273
|
```ts
|
|
610
274
|
const appId = '1';
|
|
@@ -623,6 +287,10 @@ const unsubscribe = client.udp.subscribe(
|
|
|
623
287
|
// A direct actor-to-actor message addressed to you.
|
|
624
288
|
console.log(event.uuid, event.payload); // payload is base64
|
|
625
289
|
},
|
|
290
|
+
channelMessage: (event) => {
|
|
291
|
+
// A message broadcast on a channel you're subscribed to.
|
|
292
|
+
console.log(event.channelId, event.payload); // payload is base64
|
|
293
|
+
},
|
|
626
294
|
genericError: (event) => {
|
|
627
295
|
console.warn(event.sequenceNumber, event.errorCode);
|
|
628
296
|
},
|
|
@@ -647,7 +315,13 @@ client.realtime.onStatus((status) => {
|
|
|
647
315
|
unsubscribe();
|
|
648
316
|
```
|
|
649
317
|
|
|
650
|
-
The SDK uses the `graphql-transport-ws` protocol through `graphql-ws`,
|
|
318
|
+
The SDK uses the `graphql-transport-ws` protocol through `graphql-ws`,
|
|
319
|
+
reconnects with backoff, re-reads the current token before reconnecting, and
|
|
320
|
+
resubscribes automatically. `RealtimeConnectionEvent` carries a `retryable`
|
|
321
|
+
flag: `UDP_PROXY_CONNECTION_FAILED` is transient (back off and resubscribe),
|
|
322
|
+
while `AUTH_REQUIRED` / `APP_ID_REQUIRED` must be fixed by the caller first.
|
|
323
|
+
Unsubscribing stops delivery only; call `client.udp.disconnect()` to close the
|
|
324
|
+
UDP proxy session.
|
|
651
325
|
|
|
652
326
|
## Spatial sends
|
|
653
327
|
|
|
@@ -664,15 +338,19 @@ const response = await client.udp.sendActorUpdateAndWait({
|
|
|
664
338
|
console.log(response.__typename, response.sequenceNumber);
|
|
665
339
|
```
|
|
666
340
|
|
|
667
|
-
The plain `sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`,
|
|
341
|
+
The plain `sendActorUpdate`, `sendVoxelUpdate`, `sendAudioPacket`,
|
|
342
|
+
`sendTextPacket`, and `sendClientEvent` methods return the GraphQL mutation
|
|
343
|
+
result immediately. The `AndWait` variants allocate a `sequenceNumber` when
|
|
344
|
+
one is missing and wait for either a matching notification or
|
|
345
|
+
`GenericErrorResponse`. `sendChannelMessage` broadcasts an opaque payload on a
|
|
346
|
+
channel.
|
|
668
347
|
|
|
669
348
|
### Actor-to-actor messages
|
|
670
349
|
|
|
671
350
|
```ts
|
|
672
351
|
// Delivered only to the actor whose UUID matches `targetUuid`; you must know
|
|
673
|
-
// that actor's current chunk. Fire-and-forget — the sender gets no echo
|
|
674
|
-
//
|
|
675
|
-
// `SingleActorMessageNotification` on its subscription.
|
|
352
|
+
// that actor's current chunk. Fire-and-forget — the sender gets no echo. The
|
|
353
|
+
// target receives a `SingleActorMessageNotification` on its subscription.
|
|
676
354
|
await client.udp.sendSingleActorMessage({
|
|
677
355
|
appId: '1',
|
|
678
356
|
chunk: { x: '7', y: '1', z: '2' }, // the TARGET actor's chunk
|
|
@@ -699,23 +377,24 @@ await actor.sendToActor(
|
|
|
699
377
|
);
|
|
700
378
|
```
|
|
701
379
|
|
|
702
|
-
The world helpers are thin wrappers over `client.udp.*` with the appId
|
|
380
|
+
The world helpers are thin wrappers over `client.udp.*` with the appId
|
|
381
|
+
pre-bound — convenient for browser games. Advanced callers can always use
|
|
382
|
+
`client.udp.*` with the generated GraphQL input types directly.
|
|
703
383
|
|
|
704
384
|
## World Stores
|
|
705
385
|
|
|
706
386
|
The core client is a thin transport; the **World Stores** layer
|
|
707
|
-
(`@crowdedkingdoms/crowdyjs/stores
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
subscription and ONE scheduler.
|
|
387
|
+
(`@crowdedkingdoms/crowdyjs/stores`) adds the source-of-truth data structures
|
|
388
|
+
every game otherwise hand-writes: actor registries, chunk/voxel caches, error
|
|
389
|
+
attribution, message inboxes, host tracking, and typed durable-state wrappers
|
|
390
|
+
— all driven by ONE shared `udpNotifications` subscription and ONE scheduler.
|
|
712
391
|
|
|
713
392
|
```ts
|
|
714
393
|
import {
|
|
715
394
|
createWorldSession, structCodec, f32, u8, jsonCodec, workerTicker,
|
|
716
395
|
} from '@crowdedkingdoms/crowdyjs/stores';
|
|
717
396
|
|
|
718
|
-
// Describe your replication state ONCE (
|
|
397
|
+
// Describe your replication state ONCE (binary layouts, declaratively):
|
|
719
398
|
const poseCodec = structCodec({
|
|
720
399
|
x: f32(), y: f32(), z: f32(), yaw: f32(),
|
|
721
400
|
flags: u8(), held: u8(),
|
|
@@ -750,6 +429,13 @@ session.errors.onError((e) => console.warn(e.errorCode, e.send?.kind));
|
|
|
750
429
|
session.dispose();
|
|
751
430
|
```
|
|
752
431
|
|
|
432
|
+
The available stores are `self` (your actor + send loop), `actors` (remote
|
|
433
|
+
actor registry with lanes/history/staleness), `errors` (attributed send
|
|
434
|
+
errors), `chunks` (chunk/voxel cache with realtime merge + worldgen
|
|
435
|
+
write-back), `channelInbox` / `actorInbox` (message inboxes), `events` (typed
|
|
436
|
+
event router), `host` (host tracking), `save` / `avatar` (typed durable
|
|
437
|
+
state), and `model` (game-model container mirror).
|
|
438
|
+
|
|
753
439
|
Every store is **opt-in twice over**: only configured stores are constructed
|
|
754
440
|
(and only they exist on the session's TYPE — `session.host` without
|
|
755
441
|
`host: ...` in the config is a compile error), and the layer lives behind the
|
|
@@ -795,8 +481,7 @@ const result = await kit.objects.open(doorId, { keyId });
|
|
|
795
481
|
if (!result.success) console.warn('locked:', result.errorMessage);
|
|
796
482
|
```
|
|
797
483
|
|
|
798
|
-
Land sale closes the permission loop end to end
|
|
799
|
-
effects, v0.13.12+ for the chunk-permission reads):
|
|
484
|
+
Land sale closes the permission loop end to end:
|
|
800
485
|
|
|
801
486
|
```ts
|
|
802
487
|
// Studio: sell a plot over a grid; doors on it honor the purchase automatically.
|
|
@@ -816,28 +501,138 @@ selector has `candidatePermissionWhere: [{ userFrom: { property: 'owner_user_id'
|
|
|
816
501
|
op: 'lacks', key: 'access', grid: { property: 'grid_id' } }]` reacts only to
|
|
817
502
|
intruders.
|
|
818
503
|
|
|
819
|
-
|
|
820
|
-
|
|
504
|
+
The kit covers the common genre staples end to end — each layer is a blueprint
|
|
505
|
+
builder plus a typed runtime helper:
|
|
821
506
|
|
|
822
507
|
| Layer | Builder → helper | Highlights |
|
|
823
508
|
| --- | --- | --- |
|
|
824
|
-
|
|
|
509
|
+
| Inventory | `inventoryBlueprint` → `kit.inventory` | bags/stacks, grant/consume/transfer, craft, barter |
|
|
510
|
+
| Objects | `lockBlueprint` → `kit.objects` | lockable doors/chests with key or permission authority |
|
|
511
|
+
| NPCs | `npcBlueprint` → `kit.npcs` | automation-driven NPC instances (spawn, runNow, enable) |
|
|
512
|
+
| Plots | `plotBlueprint` → `kit.plots` | buy/rent land with transactional, replication-enforced grid grants |
|
|
513
|
+
| Economy | `economyBlueprint` → `kit.economy` | multi-currency wallets, atomic shop buys, escrow trades, player market, escrowed order book (`kit.economy.orderBook`) |
|
|
825
514
|
| Progression | `progressionBlueprint` → `kit.progression` | xp/levels via the `fn:` curve helper, skill prerequisite chains, achievements, host-gated rating |
|
|
826
515
|
| Loot | `lootBlueprint` → `kit.loot` | weighted tables unrolled into seed-driven expressions, atomic single-claim, event-triggered drops |
|
|
827
|
-
| Quests | `questsBlueprint` → `kit.quests` | event-automation progress, atomic claim into stack+wallet, cron daily resets |
|
|
828
|
-
| Combat | `combatBlueprint` → `kit.combat` | server-side damage/death, status-effect tick automation
|
|
516
|
+
| Quests | `questsBlueprint` → `kit.quests` | event-automation progress, atomic claim into stack+wallet, cron daily resets, tutorial sequencing |
|
|
517
|
+
| Combat | `combatBlueprint` → `kit.combat` | server-side damage/death, status-effect tick automation, `turnBased`/`hostSynced`, routed attacks |
|
|
829
518
|
| Matches | `matchesBlueprint` → `kit.matches` | session lobbies/rounds/turns/scores, per-match channel + `onMatchChanged` (notify-to-pull) |
|
|
830
519
|
| Decks | `decksBlueprint` → `kit.decks` | hidden hands via owner-visibility `card_id`, shuffle-by-position automation |
|
|
831
|
-
| World sim | `worldsimBlueprint` → `kit.worldsim` | day/night clock with spatial notify, node regen + atomic gather, crops, wave counters |
|
|
520
|
+
| World sim | `worldsimBlueprint` → `kit.worldsim` | day/night clock with spatial notify, node regen + atomic gather, crops, wave counters, forecasts |
|
|
832
521
|
| Social | `guildBlueprint` → `kit.social` | parties/guilds/chat over teams+channels, grid territory grants, guild hall + bank composite |
|
|
833
522
|
| Leaderboards | `leaderboardsBlueprint` → `kit.leaderboards` | trusted keep-best submits, client-side ranking, cron seasons |
|
|
523
|
+
| Live ops | `liveopsBlueprint` → `kit.liveops` | timed event windows and seasons |
|
|
524
|
+
| Moderation | `moderationBlueprint` → `kit.moderation` | reports, queues, mutes |
|
|
525
|
+
| Telemetry | `telemetryBlueprint` → `kit.telemetry` | counters and lightweight event tracking |
|
|
834
526
|
| Monetization | `featureGate` → `kit.features` | feature keys, tier grants, `*policyExtra` gating on builders |
|
|
527
|
+
| Abilities | — → `kit.abilities` | ability definitions, casts, loadouts |
|
|
528
|
+
| Movement | — → `kit.movement` | movement warden configs + violation parsing |
|
|
529
|
+
| Territory | — → `kit.territory` | control points, factions, enrollment |
|
|
530
|
+
| Racing | — → `kit.racing` | courses, entries, possession (claim/pass/shoot) |
|
|
531
|
+
|
|
532
|
+
Engine-aware layers talk to compute-module game engines when they are
|
|
533
|
+
deployed, and degrade gracefully on model-only apps via capability detection
|
|
534
|
+
(`kit.engines`): `kit.mobs` (refereed attacks, defs/slots, contact-damage
|
|
535
|
+
parsing), `kit.pets` (adopt/summon/dismiss/rename), `kit.instances` (private
|
|
536
|
+
world slices, seeded runs), `kit.director` (encounter runs),
|
|
537
|
+
`kit.matchmaking` (queues/proposals/rating), `kit.minigames` (invoke-loop
|
|
538
|
+
wrapper), plus engine paths on `kit.matches` / `kit.decks` /
|
|
539
|
+
`kit.leaderboards` and the `kit/wire` pose codec + event parsers. Deploy
|
|
540
|
+
engines alongside blueprints with `kit.deploy(blueprints, { engines })`.
|
|
835
541
|
|
|
836
542
|
See the docs guides [Modeling game concepts](https://docs.crowdedkingdoms.com/game-api/modeling-game-concepts)
|
|
837
543
|
(the underlying model + genre map) and [Game Kit](https://docs.crowdedkingdoms.com/crowdyjs/game-kit)
|
|
838
544
|
(the SDK surface + the simulation-tier / notify-to-pull / timer / hidden-info
|
|
839
545
|
/ anti-cheat patterns).
|
|
840
546
|
|
|
547
|
+
## Crowdy Studio
|
|
548
|
+
|
|
549
|
+
Crowdy Studio is the in-game SERVER/CLIENT Rust authoring surface for player
|
|
550
|
+
code: cloud projects with target-scoped files, optimistic-concurrency
|
|
551
|
+
autosave, draft/live/stop orchestration, and a Monaco editor backed by a local
|
|
552
|
+
Rust language worker. The worker receives source files and the embedded
|
|
553
|
+
platform index only — never a credential and never a server connection.
|
|
554
|
+
Compiled CLIENT artifacts run through `PlayerCodeBroker`, which keeps tokens
|
|
555
|
+
on the page, allow-lists host calls, and locally clamps chunk-targeted effects
|
|
556
|
+
to the owned grid before the normal SDK path reaches server authorization.
|
|
557
|
+
|
|
558
|
+
Most games embed the ready-made shell rather than hand-rolling window chrome
|
|
559
|
+
around `mountCrowdyStudio`:
|
|
560
|
+
|
|
561
|
+
```ts
|
|
562
|
+
import { createCrowdyStudioEmbed } from '@crowdedkingdoms/crowdyjs/crowdy-studio';
|
|
563
|
+
// Self-starting glue worker for CLIENT mods (Vite shown; any bundler that
|
|
564
|
+
// packages module workers works):
|
|
565
|
+
import workerUrl from '@crowdedkingdoms/crowdyjs/player-glue-worker?worker&url';
|
|
566
|
+
|
|
567
|
+
const studio = createCrowdyStudioEmbed({
|
|
568
|
+
client: game, // CrowdyClient: crowdyStudio, playerCompute, playerWallet, crowdyStudioAgent
|
|
569
|
+
appId,
|
|
570
|
+
gameName: 'My Game',
|
|
571
|
+
suppressGameplayInput: () => pauseInput(),
|
|
572
|
+
onLayoutChange: () => resizeCanvas(),
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// Per open (for example after the player claims a grid):
|
|
576
|
+
studio.toggle({
|
|
577
|
+
gridId,
|
|
578
|
+
targetPermissions: {
|
|
579
|
+
SERVER: { canWrite: true, canRun: true },
|
|
580
|
+
CLIENT: { canWrite: false, canRun: false }, // SERVER-only embeds omit workerUrl
|
|
581
|
+
},
|
|
582
|
+
});
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
The embed renders a resizable right dock on desktop and a focus-trapped
|
|
586
|
+
fullscreen modal on narrow screens, and mounts the agent dock automatically
|
|
587
|
+
when the client exposes `crowdyStudioAgent` and the game passes a
|
|
588
|
+
`playerHost`. For custom chrome, call `mountCrowdyStudio(host, options)`
|
|
589
|
+
directly; for a headless integration, use `new CrowdyStudioController(options)`.
|
|
590
|
+
New games should start SERVER-only. Untrusted HUD payloads always render as
|
|
591
|
+
text, never HTML.
|
|
592
|
+
|
|
593
|
+
See [Crowdy Studio & player client mods](https://docs.crowdedkingdoms.com/crowdyjs/player-client-mods)
|
|
594
|
+
and [Embed Crowdy Studio in your game](https://docs.crowdedkingdoms.com/crowdyjs/crowdy-studio-embed).
|
|
595
|
+
|
|
596
|
+
### Agentic Crowdy Studio
|
|
597
|
+
|
|
598
|
+
The agent surface adds an Ask/Build/Play AI dock on top of Crowdy Studio,
|
|
599
|
+
built from three browser contracts (`crowdy.studio-agent/1`,
|
|
600
|
+
`crowdy.agent-tools/1`, `crowdy.player-host/1`):
|
|
601
|
+
|
|
602
|
+
- `@crowdedkingdoms/crowdyjs/agent` exports `CrowdyStudioAgentController` (the
|
|
603
|
+
durable session client: contiguous event ordering, replay/gap fill,
|
|
604
|
+
attach-epoch fencing, exact approval hashes, budgets, pause/resume/stop) and
|
|
605
|
+
`CROWDY_AGENT_TOOL_REGISTRY_V1`, an immutable digest-pinned registry of
|
|
606
|
+
bounded, schema-validated tools. There is deliberately no raw GraphQL
|
|
607
|
+
executor, DOM driver, `fetch`, shell, or unrestricted SDK bridge in these
|
|
608
|
+
surfaces.
|
|
609
|
+
- `client.crowdyStudioAgent` is the production GraphQL transport for durable
|
|
610
|
+
agent sessions; pass it (plus a `playerHost` adapter) to the Studio mount's
|
|
611
|
+
`agent` option to get the integrated dock.
|
|
612
|
+
- `@crowdedkingdoms/crowdyjs/player-host` exports the generic game
|
|
613
|
+
observation/control contract: implement `PlayerHostAdapterV1` over your
|
|
614
|
+
game's typed intent methods, and the exported `AgentControlLeaseManager`,
|
|
615
|
+
`PlayerControlGate`, and `AgentControlBanner` enforce scoped leases, TTLs,
|
|
616
|
+
synchronous human preemption, and always-visible Pause/Stop chrome.
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
import { CrowdyStudioAgentController } from '@crowdedkingdoms/crowdyjs/agent';
|
|
620
|
+
|
|
621
|
+
const agent = new CrowdyStudioAgentController({
|
|
622
|
+
transport: game.crowdyStudioAgent,
|
|
623
|
+
createSession: {
|
|
624
|
+
appId, projectId, gridId,
|
|
625
|
+
mode: 'BUILD',
|
|
626
|
+
providerDataConsent: true,
|
|
627
|
+
idempotencyKey: crypto.randomUUID(),
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
await agent.initialize(); // attach epoch → durable replay/gap fill → live tail
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
See [Agentic Crowdy Studio](https://docs.crowdedkingdoms.com/crowdyjs/agentic-crowdy-studio)
|
|
634
|
+
for the full session, lease, and approval model.
|
|
635
|
+
|
|
841
636
|
## Errors
|
|
842
637
|
|
|
843
638
|
Transport and protocol failures throw structured error classes:
|
|
@@ -849,11 +644,18 @@ Transport and protocol failures throw structured error classes:
|
|
|
849
644
|
- `CrowdyRealtimeError` — realtime subscription couldn't be established or was dropped.
|
|
850
645
|
- `CrowdyProtocolError` — server response failed schema validation.
|
|
851
646
|
|
|
852
|
-
GraphQL errors carry a stable `extensions.code` (e.g. `UNAUTHENTICATED`,
|
|
647
|
+
GraphQL errors carry a stable `extensions.code` (e.g. `UNAUTHENTICATED`,
|
|
648
|
+
`SCOPE_MISSING`, `FORBIDDEN`, `IDEMPOTENCY_CONFLICT`, `RATE_LIMITED`) plus,
|
|
649
|
+
where applicable, `extensions.remediation` and `extensions.requiredPermission`.
|
|
650
|
+
Branch on `error.extensions?.code` rather than parsing messages.
|
|
853
651
|
|
|
854
652
|
## Idempotent retries
|
|
855
653
|
|
|
856
|
-
Destructive game-client mutations accept an optional **idempotency key**. Pass
|
|
654
|
+
Destructive game-client mutations accept an optional **idempotency key**. Pass
|
|
655
|
+
a stable key (e.g. `crypto.randomUUID()`) and a network retry replays the
|
|
656
|
+
first result instead of applying the side effect twice. Reusing a key with
|
|
657
|
+
different arguments throws a `CrowdyGraphQLError` with
|
|
658
|
+
`extensions.code === 'IDEMPOTENCY_CONFLICT'`. Keys expire server-side after 24h.
|
|
857
659
|
|
|
858
660
|
```ts
|
|
859
661
|
const key = crypto.randomUUID();
|
|
@@ -864,102 +666,56 @@ await client.teams.leave(groupId, key); // leaveTeam
|
|
|
864
666
|
await client.voxels.rollback({ ...input, idempotencyKey: key }); // input field
|
|
865
667
|
```
|
|
866
668
|
|
|
867
|
-
The key parameter is optional and trailing, so it's safe to omit.
|
|
868
|
-
|
|
869
|
-
## Auth notes
|
|
669
|
+
The key parameter is optional and trailing, so it's safe to omit.
|
|
870
670
|
|
|
871
|
-
-
|
|
872
|
-
- `client.session.restore()` reads from the configured `tokenStore`. `BrowserLocalStorageTokenStore` is provided; bring your own for SSR or Node usage.
|
|
873
|
-
- Each client's `AuthState` is observed by both its HTTP client and its realtime socket, so HTTP and WebSocket auth never drift within a client. Hold the **identity session token** on the management/identity client and an **app-scoped token** on each per-game client (`client.portal` — see the [v7 section](#overworld-portals--app-scoped-tokens-v7)).
|
|
874
|
-
|
|
875
|
-
## Overworld portals & app-scoped tokens (v7)
|
|
876
|
-
|
|
877
|
-
As of v7 gameplay requires an **app-scoped token**, not the session token.
|
|
878
|
-
Passwordless sign-in returns an **identity session token** (Management API only —
|
|
879
|
-
account, studio admin, and minting); each game is entered with a short-lived token
|
|
880
|
-
confined to that one app, so a game stack never receives the player's full session.
|
|
671
|
+
## Low-level GraphQL access
|
|
881
672
|
|
|
882
|
-
|
|
883
|
-
|
|
673
|
+
Typed sub-client methods are first-class, but generated operation documents
|
|
674
|
+
are also available through a transport escape hatch — `client.graphql` for the
|
|
675
|
+
Game API and `client.management` for the Management API:
|
|
884
676
|
|
|
885
677
|
```ts
|
|
886
|
-
|
|
887
|
-
const overworld = createCrowdyClient({ managementUrl, tokenStore: new BrowserLocalStorageTokenStore('crowdyjs:session') });
|
|
888
|
-
// Passwordless sign-in (magic link, social/OIDC, or dev bypass) yields the session token.
|
|
889
|
-
await overworld.auth.requestLoginLink({ email, redirectUri });
|
|
890
|
-
await overworld.auth.completeLoginLink(tokenFromLink);
|
|
678
|
+
import { VersionInfoDocument } from '@crowdedkingdoms/crowdyjs/generated';
|
|
891
679
|
|
|
892
|
-
|
|
893
|
-
const t = await overworld.portal.mintAppToken(appId);
|
|
894
|
-
const game = createCrowdyClient({ httpUrl: t.gameApiUrl!, wsUrl: t.gameApiWsUrl!, managementUrl,
|
|
895
|
-
tokenStore: new BrowserLocalStorageTokenStore('crowdyjs:app:' + appId) });
|
|
896
|
-
game.setToken(t.token);
|
|
897
|
-
game.world(appId).subscribe({ actorUpdate: (n) => { /* ... */ } });
|
|
680
|
+
const data = await client.graphql.request(VersionInfoDocument);
|
|
898
681
|
```
|
|
899
682
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
```ts
|
|
904
|
-
// Game origin, on "enter": redirect to the Overworld authorize page.
|
|
905
|
-
location.assign(await game.portal.beginEntry({
|
|
906
|
-
appId, authorizeUrl: 'https://overworld.example.com/authorize',
|
|
907
|
-
redirectUri: location.origin + location.pathname,
|
|
908
|
-
}));
|
|
683
|
+
For any brand-new server field not yet wrapped, `client.graphql.request(...)` /
|
|
684
|
+
`client.management.request(...)` always works.
|
|
909
685
|
|
|
910
|
-
|
|
911
|
-
location.assign(await overworld.portal.handleAuthorizeRequest());
|
|
686
|
+
## Maintainers: schema artifacts and fixtures
|
|
912
687
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
await game.refreshGameplayToken();
|
|
917
|
-
```
|
|
688
|
+
CrowdyJS is a standalone public package: a clean clone builds with
|
|
689
|
+
`npm install && npm run build` using the artifacts committed to this repo —
|
|
690
|
+
no other repositories and no network access required:
|
|
918
691
|
|
|
919
|
-
`
|
|
920
|
-
|
|
921
|
-
`refreshGameplayToken()` so the old Bearer closes its proxy before the token is
|
|
922
|
-
revoked.
|
|
923
|
-
|
|
924
|
-
Game-to-game routes through the Overworld for a fresh per-game token. New
|
|
925
|
-
realtime codes: `APP_TOKEN_REQUIRED`, `APP_SCOPE_MISMATCH`; new `UdpErrorCode`:
|
|
926
|
-
`TOKEN_EXPIRED`. See [MIGRATION.md](MIGRATION.md) for the full v7 breaking guide.
|
|
927
|
-
|
|
928
|
-
## Surface scope & security
|
|
929
|
-
|
|
930
|
-
As of v6 (completed in v6.1), CrowdyJS wraps the **full** management-api + game-api
|
|
931
|
-
public surface, not just the game-client subset — every non-deprecated public root
|
|
932
|
-
field has a typed method, with Relay `*Connection` cursor-pagination variants
|
|
933
|
-
alongside the legacy offset lists. The surfaces are namespaced by audience:
|
|
934
|
-
|
|
935
|
-
- **Game-client** (`client.auth`, `client.users`, `client.udp`, `client.world(...)`,
|
|
936
|
-
`client.chunks`/`voxels`/`actors`/`avatars`/`state`/`teleport`/`channels`/`teams`/
|
|
937
|
-
`gameModel`/`host`) — safe for untrusted browser clients with an end-user token.
|
|
938
|
-
- **Studio-admin** (`client.admin.*` — also reachable at the top level, e.g.
|
|
939
|
-
`client.billing`) — privileged organization/app administration. Drive these from a
|
|
940
|
-
**studio backend** with an org-scoped or admin token, **not** from an untrusted
|
|
941
|
-
browser; the server still enforces the relevant org/app permission on every call.
|
|
942
|
-
- **Operator** (`client.operator`) — platform-policy operations (compute
|
|
943
|
-
ceilings) that require `users.is_operator`. For internal operator tooling
|
|
944
|
-
only. Infrastructure operations live in the separate infra-control-plane
|
|
945
|
-
service as of v13, not this SDK.
|
|
946
|
-
|
|
947
|
-
The SDK never relaxes server-side authorization — exposing an operation here just
|
|
948
|
-
gives you a typed wrapper; the caller still needs the right token and permission. For
|
|
949
|
-
any brand-new server field not yet wrapped, the low-level escape hatch
|
|
950
|
-
(`client.graphql.request(...)` / `client.management.request(...)`) always works.
|
|
692
|
+
- `schema.gql` — merged Management API + Game API SDL.
|
|
693
|
+
- `src/generated/graphql.ts` — generated TypeScript operation types.
|
|
951
694
|
|
|
952
|
-
|
|
695
|
+
Schema refresh is explicit, from the published SDLs
|
|
696
|
+
([management-api.graphql](https://docs.crowdedkingdoms.com/schema/management-api.graphql),
|
|
697
|
+
[game-api.graphql](https://docs.crowdedkingdoms.com/schema/game-api.graphql)):
|
|
953
698
|
|
|
954
|
-
|
|
699
|
+
```bash
|
|
700
|
+
npm run schema:sync:prod
|
|
701
|
+
npm run codegen
|
|
702
|
+
```
|
|
955
703
|
|
|
956
|
-
|
|
957
|
-
|
|
704
|
+
(`npm run schema:sync:paths -- --management <file-or-url> --game <file-or-url>`
|
|
705
|
+
accepts explicit sources.) Commit `schema.gql` and `src/generated/graphql.ts`
|
|
706
|
+
together whenever the public GraphQL surface changes; `npm run check:schema`
|
|
707
|
+
detects drift in CI/release work.
|
|
958
708
|
|
|
959
|
-
|
|
960
|
-
|
|
709
|
+
Two more committed fixtures follow the same boundary — the build validates
|
|
710
|
+
them locally and never reads a sibling checkout; coordinated maintainers
|
|
711
|
+
refresh them from an explicitly supplied source:
|
|
961
712
|
|
|
962
|
-
|
|
713
|
+
- The browser Rust authoring index:
|
|
714
|
+
`npm run authoring-index:drift -- --source <exporter-json> [--write]`, then
|
|
715
|
+
`npm run authoring-index:generate`.
|
|
716
|
+
- The agent tool-descriptor fixture:
|
|
717
|
+
`npm run agent-descriptors:drift -- --source <game-api-fixture>`; every
|
|
718
|
+
build re-checks the digests.
|
|
963
719
|
|
|
964
720
|
## Migration
|
|
965
721
|
|