Open the app

Language

For players

Explanation Live

How a place runs

Enabling a draft pushed by place-kit isn't in the app yet. Drafts written in the app can be enabled today.

Open llms.txt

This page explains the model behind place code. No steps; for those, see the quickstart.

A place

A place is a room players walk into: a building on the map, or a virtual room. It has an owner, pieces (3D models on the floor, pictures and videos on the walls), doors, and the players inside.

Your code never sees names or positions of players. It sees player ids: strings to compare and store, never to parse. They will become 16-character public ids.

Two keys, two jobs

Looks likeWhat it isWho sees it
Place keyS7kQ2xR9mPaThe public name of a place. place.json holds it.Anyone. It's in share links.
Personal keyhwk_ + 43 charactersPermission to upload code to the places it names.Only you and your agent. Shown once.

A place key starts with S, V or P and has 10 to 20 characters. Your personal key is issued in the app, on a few of your places, for a few days, and revoked from the same sheet.

Two parts, two pipes

A place's code has up to two parts. Each part is one file with one class.

The server part (SpaceScript from @hatchworld/space@1) is the authority. It reacts to events, keeps state, decides, and tells the phones. It's required.

The client part (SpaceClient from @hatchworld/space-client@1) runs on each phone inside. It draws: buttons, labels, moving things. It decides nothing. It's optional: phones already show the server's ui.say plates.

They talk through exactly two pipes:

client  this.net.action(name, target, payload)  ──▶  server  onPlayerAction(e)
server  this.emit(name, payload, { to })        ──▶  client  onServerEmit(e)

Never trust the client. The server checks who acts (e.uid, e.isOwner) and its own state before changing anything.

Why your server class is new on every event

The server creates a fresh instance of your class for every event, then throws it away. A field you set in one handler is gone in the next. What must last goes in:

  • this.state: the place's memory, JSON, up to 64 KB;
  • e.player.state: per player per place, up to 4 KB.

This is what lets a place sleep between events. It also means a handler may run twice for one event after a crash: check state before acting, so a second run does nothing new.

The client part is the opposite: one instance per visit. Its fields last while the player is inside and die when they leave.

Time and randomness

There's no setTimeout, fetch or eval. A timer is a name and a JSON payload, never a closure: this.runTimeout('close', 30000, { id }) comes back as onTimer(e).

Inside a handler, Date.now() is the event's time and Math.random() is seeded. Replaying an event gives the same result.

The manifest is your class's statics

Static fields of the server class declare what the place may do. The server reads them when you push:

StaticDeclares
capabilitiesRights you use: world.write, llm, shared, notify. A call without its right is dropped.
textsEvery text a player sees, by key, in English and at least one more language.
commandsPhrases a player may type into the place's field, each arriving as an action.
assetsThe only model files the code may stand in the room, by hash.
shared, boards, thresholdsCounters and leaderboards shared by the whole place.

The full list with bounds is in the reference, generated from the types.

Draft, then enable

Nothing you push runs until the owner enables it.

your folder ──build──▶ dist/ ──push──▶ draft version N ──Enable (owner, in the app)──▶ live
  1. place-kit build bundles and type-checks on your machine, the same way the server will.
  2. place-kit push uploads both parts. The server checks them again (strict tsc, then a compile run in the sandbox) and saves version N as a draft.
  3. The owner enables version N in the app. From then on it runs for everyone who walks in. A new version stops the old one's looks, music and highlights.

A personal key can carry the right to enable versions too, but only if the owner ticks "The agent may enable versions" when issuing it. By default it can't.

The sandbox and its limits

Your code runs in QuickJS: no network, no files, no DOM, no other imports. Each built part may import only its own API module.

LimitValue
CPU per event50 ms
Memory per event16 MB
Server part64 KB
Client part64 KB, more on request (up to 512 KB)
this.state64 KB JSON
e.player.state4 KB per player per place
Players in a roomWrite for up to 24 at once

A script that keeps failing or runs slow is switched off, and the owner sees why. All limits: Keys and limits.

What the platform already does

A system script runs before yours in every place. It greets newcomers, tracks who's inside, lets the owner throw a guest out, and lets guests borrow things for 24 hours. Don't rebuild these, and don't use the reserved ids take and kick.

Next Security model why your code is treated as hostile, and why that's good for you.

Docs