Skip to main content

Grids and grid programs

Player code inside a grid can do what app-scoped code can, confined to that grid. Code that runs on the server for a grid is a ck-exec mod (client.exec.mod*). In the browser, CrowdyJS 17.7 gives it three shapes.

client.grid() — one grid, bound​

const plot = client.grid(appId, gridId);
await plot.mintToken(); // learns the box
await plot.channels.create('plot-chat'); // a grid channel (owner only)
await plot.send.text({ chunk: { x: 4, y: 0, z: 0 }, uuid, text: 'go!', distance: 2 });

channels fills in the app and grid; send checks that each message originates in the grid and throws GridScopeError before any request if not. The server enforces the same rules.

CrowdyJS 18 removed the grid scope's sessions, model and compute, which called the game model's sessions, the player model and player compute; the scope has channels and send. A grid's server-side state and logic belong in its mods.

JS grid programs — the full SDK in a sandbox​

A grid program is player-authored JavaScript that runs in a network-less sandbox (an iframe with sandbox="allow-scripts" and connect-src 'none', or a worker) and uses real CrowdyJS:

// inside the sandbox
import { createGridProgramClient } from '@crowdedkingdoms/crowdyjs/grid-program';

const { client, grid, appId } = await createGridProgramClient(port);
client.udp.subscribe({ text: (n) => console.log(n.text) }, appId);
await grid.send.text({ chunk: grid.bounds!.low, uuid, text: 'hello from the grid', distance: 2 });
// on the page, which holds the player's app token
import { hostGridProgram } from '@crowdedkingdoms/crowdyjs/grid-program';

const { port1, port2 } = new MessageChannel();
iframe.contentWindow!.postMessage({ type: 'grid-program-port' }, '*', [port2]);
await hostGridProgram({
port: port1,
scope: client.grid(appId, gridId),
graphqlUrl: 'https://…/graphql',
graphqlWsUrl: 'wss://…/graphql',
});

The program's fetch and WebSocket are relays over the port. The host attaches a grid-scoped token the program never sees (it holds a placeholder) and refuses obviously wrong traffic: one query or mutation per request, no subscriptions over HTTP, size and rate caps. So whatever the program's code does, it reaches exactly as far as a grid token.

startGridMod — one runtime for both​

import { startGridMod } from '@crowdedkingdoms/crowdyjs';

const a = await client.exec.modClientArtifactBytes(appId, modId); // a CLIENT half
await startGridMod({
spec: {
kind: 'wasm', engine: 'ck-exec', moduleName: a.name, artifact: a.bytes,
artifactHash: a.digest, fuelPerDispatch: a.fuelPerDispatch, tickIntervalMs: a.tickIntervalMs,
consentedHostCalls: a.capabilitySummary.hostFunctions, workerUrl,
},
scope,
client,
});
await startGridMod({ spec: { kind: 'program', moduleName, port }, scope, client, graphqlUrl, graphqlWsUrl });

The wasm spec runs a ck-exec mod's CLIENT half, with engine: 'ck-exec' and the digest, fuel budget, tick interval and host calls it was served with (artifactHash, fuelPerDispatch, tickIntervalMs, consentedHostCalls; CrowdyJS 17.14.0). createGridHostCalls answers the host calls crowdy-client-sdk makes (world reads, voxel_set, emit_spatial, emit_channel, user state), plus the page-local grid event bus: emit_event reaches the other CLIENT halves on the same grid in this browser through on_event. ExecClientHalves runs every CLIENT half a grid serves.

'ck-exec' is the spec's only engine and its default, and artifactHash, fuelPerDispatch and consentedHostCalls are required: a spec without them does not start. The legacy CLIENT modules' model and session host calls (container_*, containers_list, property_set, model_invoke, sessions_list) went with the game model, and the broker refuses them.