Skip to main content

Model-driven notifications

A game model function can push a realtime notification to clients as part of its invocation — so a server-driven change (an NPC acting, a trap firing, a score updating) reaches players the same way a player-driven change does, with no extra client send. The notification arrives on the client.udp.subscribe stream players are already listening to.

For the full contract (the three delivery kinds, required arguments, and delivery semantics) see Game API → Model-driven notifications.

Declaring notifications (studio-admin)

Add a notifications array when you author the function with client.gameModel.upsertFunction. Each entry has a kind (spatial | channel | actor), an optional emitAs (spatial only), and an args list of { name, expression } where expression is a model expression evaluated in the invocation's context:

await client.gameModel.upsertFunction({
appId: '1',
name: 'ringBell',
invokeScope: 'player',
invokePolicyJson: JSON.stringify({ type: 'owner_of_self' }),
mutations: [{ target: 'self', property: 'lastRung', expression: 'now()' }],
notifications: [
{
kind: 'spatial',
emitAs: 'server_event',
args: [
{ name: 'chunk_x', expression: 'self.chunk_x' },
{ name: 'chunk_y', expression: 'self.chunk_y' },
{ name: 'chunk_z', expression: 'self.chunk_z' },
{ name: 'event_type', expression: '42' },
{ name: 'distance', expression: '16' },
],
},
],
});

client.gameModel.getFunction({ appId, name }) returns the stored notifications so you can read them back.

Receiving them (game client)

No new subscription is required — the notifications surface on the existing udpNotifications handlers. Like any realtime subscription, this runs on the app's app-scoped token (an identity session token is rejected with APP_TOKEN_REQUIRED), so open it from a per-game client that holds the token — see Portals & app-scoped tokens:

client.udp.subscribe(
{
// kind: 'spatial' (emitAs: 'server_event') →
serverEvent: (e) => console.log('model event', e.eventType, e.state),
// kind: 'channel' →
channelMessage: (e) => console.log('channel', e.channelId, e.payload),
// kind: 'actor' →
singleActorMessage: (e) => console.log('to me', e.uuid, e.payload),
},
'1', // appId (required)
);

state / payload are base64, exactly like the client-initiated spatial sends. These notifications are server-originated and best-effort — there is no sequenceNumber correlation or delivery ACK (treat them like any ServerEventNotification: a hint to react/refresh).

Player- and NPC-driven changes notify identically

Because notifications live on the function, they fire whether the function is invoked by a player (client.gameModel.invoke) or headlessly by an automation / NPC. That is the recommended way to make NPC and other server-driven model changes visible to players in real time.