Schema reference#
Every closed set, every constant, every validation rule and every bound this world enforces, in one place. The other documents describe the shapes — a design has parts, a part has a primitive and a material. This one says which primitives, which materials, how long a name may be and what happens at the edge, so that a person, a script or a language model writing into this world can know rather than guess.
Every value here is the one the server actually enforces. Where more than one door checks the same thing, they are held to the same figure and the same words.
How to read this page#
| Column | Means |
|---|---|
| Wire | Exactly what to write in JSON |
| Number | The stored numeric value of an enum member. Never sent, but it is what the database and any binary format would carry |
| Default | What you get by saying nothing |
| At the edge | What happens when the value is out of range: refused, clamped, or dropped |
Three refusal styles appear throughout, and they are not interchangeable:
- Refused as a value. A validator returns a list of problems, or a call returns
nil, reason. Nothing throws. This is how designs, filesystem calls and script starts fail. - Refused as an error. The action comes back as
action_resultwithSuccess: falseand anErrorstring, or a JSON-RPC error. - Dropped. The value is ignored and the rest proceeds. This is deliberate for AI output: a brain that returns one nonsense action still gets the other seven carried out.
The wire format itself#
One serialiser serves the WebSocket and the world's own storage, so a component stored and the same component sent cannot disagree about their shape.
| Rule | Value |
|---|---|
| Property names | PascalCase, and case-sensitive — ProtocolVersion, not protocolVersion |
| Enums | Names, not numbers, spelled exactly as this page lists them: "Local", "Persistent", "BeveledCube" |
| Enum reading | Case-insensitive, so "local" is accepted. A plain JSON number is also accepted. An unknown name fails the whole message, which is answered with error |
Flag enums (Permission) | Comma-and-space separated names: "Inspect, Use". A named combination wins, so the full set writes as "All" and the empty set as "None". Reading accepts any case; a | separator is not accepted |
| Nulls | Written, not omitted, with two exceptions: the Token in a bootstrap's Player block, and a metrics snapshot's PlanetName |
NaN and Infinity | Accepted as the JSON strings "NaN", "Infinity", "-Infinity" by the serialiser — and then refused by every validator that takes geometry, because a non-finite number is what survives arithmetic and breaks a renderer far away |
That rule is the socket's and the database's, and nothing else's. Three other JSON surfaces exist and all of them are camelCase:
| Where | Notes |
|---|---|
The HTTP JSON endpoints — /health, /ready, /metrics, /world, /events, /api/regions | camelCase, with the same spellings for ids, times and positions. So the same metrics snapshot is "EntitiesTotal" over the socket and "entitiesTotal" at /metrics |
POST /mcp tool payloads | A foreign wire format with its own conventions; enums are still names. Embedded component bodies are the socket's own form, so they stay PascalCase inside |
GET /designs/{id}.json | A description for something reasoning about a design, not the message that builds one. Its enum-like fields are already plain strings |
GET /designs/{id}/visual.json is the exception to that exception: it is PascalCase, because it is not a description of a design but the design's own geometry exactly as the socket sends it. The browser module that draws the catalogue's patterned preview is the one that draws the world, and giving it a second spelling of the same document to read would have been a second shape to keep in step.
/events shows the double form simulated time takes outside the socket: atTicks is the number, and at is the same moment written for a person, as T+1d 14:24:33. Only the ticks are worth parsing.
Identifiers on the wire#
| Type | Wire | Example |
|---|---|---|
EntityId, PlayerId, AgentId, BlueprintId, PlanetId, EventId | A GUID as a string | "6b1f...-...-...-...-...." |
OwnerId | "world", or "<kind>:<guid>" with the kind lowercased | "player:6b1f…" |
EntityType | Its lowercase name, revalidated on read | "robot" |
PartId | Its name. The empty string means "no parent", which is how a root part is written | "mast", "" |
SimulationTime | An integer tick count, 10,000,000 to the simulated second. Never a date | 36000000000 |
TimeSpan | An integer tick count, same scale | 600000000 |
ChunkId | An object: {"X": 12, "Z": -3} — global chunk indices, not metres | |
WorldPosition | An object: {"X": …, "Y": …, "Z": …} — absolute metres, Y up | |
WorldRotation | An object with X, Y, Z, W — a quaternion |
OwnerId accepts four kinds when parsing — world, player, agent, group — but nothing in a running world issues a group owner today; it exists so the identity can carry one without a storage migration. world: with a GUID is refused: the world's owner id is the bare word world and nothing else.
Closed sets#
Every set in this section is exhaustive. A value outside it is refused, not guessed. The number column is the stored value; it is not what travels.
AccessSubject#
What an access change is about: a thing, or the ground.
| Wire | Number | Meaning |
|---|---|---|
Entity | 0 | One entity, named by its id. |
Land | 1 | The claim covering a position. |
AccessSubjectKind#
Whether an access change is about a thing or a piece of ground.
| Wire | Number | Meaning |
|---|---|---|
Entity | 0 | One thing, named by its id. |
Land | 1 | The claim covering a position. |
AffordanceAudience#
Which kind of client a verb belongs in front of.
A set of these is one value, written as the names joined with a comma and a space: Authoring, Everything. The numbers add up, and a named combination wins when one matches.
| Wire | Number | Meaning |
|---|---|---|
Visiting | 1 | Verbs that are part of being in the world rather than making it. |
Authoring | 2 | Verbs that change what the world is made of, or who may touch it. In this version the clients that author are the MCP tools an AI agent drives. |
Everything | 3 | Both — every verb the kind of thing affords. |
AgentState#
What an agent is doing.
| Wire | Number | Meaning |
|---|---|---|
Idle | 0 | Alive, with nothing scheduled. Costs nothing. |
Waiting | 1 | Waiting for its next scheduled thought. |
Thinking | 2 | Its brain is running right now. |
Finished | 3 | Every goal is done. It will not be woken again unless something happens. |
Failed | 4 | Its brain failed. Kept, so the failure can be looked at. |
AgentTrigger#
Why an agent is thinking now.
| Wire | Number | Meaning |
|---|---|---|
Scheduled | 0 | Its own scheduled thought. |
Created | 1 | Just created, thinking for the first time. |
GoalCompleted | 2 | A goal was completed. |
ScriptCrashed | 3 | A script it started crashed. |
ResourceProblem | 4 | Something ran out — a battery, most likely. |
MessageReceived | 5 | Another computer sent it a message. |
AssetMaterial#
The materials a definition may reference.
| Wire | Number | Meaning |
|---|---|---|
Grass | 0 | Ground green. |
ForestGrass | 1 | The darker green under trees. |
Wood | 2 | Timber. |
Leaves | 3 | Foliage green. |
Stone | 4 | Grey rock. |
Water | 5 | Water blue. |
Metal | 6 | Grey metal. |
White | 7 | Plain white. |
Black | 8 | Plain black. |
Red | 9 | Plain red. |
Blue | 10 | Plain blue. |
Yellow | 11 | Plain yellow. |
Orange | 12 | Plain orange. |
BiomeType#
The natural character of a piece of terrain.
| Wire | Number | Meaning |
|---|---|---|
Water | 0 | Below sea level. |
Grassland | 1 | Open land. Sparse trees. |
Forest | 2 | Dense tree cover. |
Mountain | 3 | High, exposed ground. Rocky and largely treeless. |
ChatScope#
Who hears a line of chat.
| Wire | Number | Meaning |
|---|---|---|
Public | 0 | Everybody in the world. The default, and what "chat" means. |
Local | 1 | Everybody at the same place as the speaker, so a town can have a conversation of its own without it reaching the whole planet. |
Direct | 2 | One named participant, and nobody else. |
ClaimRefusal#
The reason a claim was refused.
| Wire | Number | Meaning |
|---|---|---|
None | 0 | It was granted. |
AlreadyClaimed | 1 | Someone else already holds one of the chunks. |
Invalid | 2 | The world cannot hold a claim, and nothing can claim nothing. |
NotClaimed | 3 | Nobody holds that ground, so there is nothing there to give back. |
NotYours | 4 | Somebody else holds it. Only the holder may give ground back. |
DesignSaveOutcome#
What became of a described design.
| Wire | Number | Meaning |
|---|---|---|
Created | 0 | A new design, under a new identity. |
Unchanged | 1 | Nothing was written: an identical design of this author's already existed, or the revision described the design exactly as it stands. |
Updated | 2 | A draft nothing was built from, re-described under its own identity. |
Superseded | 3 | A new version under a new identity, because things stand that were built from the old one and they keep it. |
DesignSeverity#
How much a finding matters.
| Wire | Number | Meaning |
|---|---|---|
Note | 0 | Worth knowing, and possibly deliberate. |
Fault | 1 | Almost certainly not what the author meant. |
FileSystemError#
Why a filesystem operation was refused.
| Wire | Number | Meaning |
|---|---|---|
None | 0 | It worked. |
NotFound | 1 | No such file or directory. |
AlreadyExists | 2 | Something is already there. |
ParentMissing | 3 | The containing directory does not exist. |
NotADirectory | 4 | A file was named where a directory was needed. |
IsADirectory | 5 | A directory was named where a file was needed. |
TooLarge | 6 | Over the largest size one file may be. |
TooManyFiles | 7 | Over the number of files one machine may hold. |
OutOfSpace | 8 | The machine's storage is full. |
InvalidPath | 9 | It is not a valid virtual path. |
NotEmpty | 10 | A directory with things still in it. |
InputFieldKind#
The kinds of value the world can ask a person for.
| Wire | Number | Meaning |
|---|---|---|
Text | 0 | A line of text. |
Number | 1 | A whole number. |
Permissions | 2 | A set of Permission names, comma separated. |
LuaProcessState#
What a Lua process is doing.
| Wire | Number | Meaning |
|---|---|---|
Ready | 0 | Loaded, not yet run. |
Running | 1 | Executing right now. |
Sleeping | 2 | Waiting for a scheduled wake-up. Costs nothing meanwhile. |
Finished | 3 | Ran to completion. |
Stopped | 4 | Stopped by request. |
Crashed | 5 | Ended in an error, including running out of instruction budget. |
NaturalObjectKind#
What kind of thing the generator placed.
| Wire | Number | Meaning |
|---|---|---|
Tree | 0 | A tree. |
Rock | 1 | A rock. |
OwnerKind#
The category of actor that can own something.
| Wire | Number | Meaning |
|---|---|---|
World | 0 | Owned by the world itself: naturally generated, unclaimed. |
Player | 1 | Owned by a human player. |
Agent | 2 | Owned by an AI agent. |
Group | 3 | Owned collectively by a group. |
Permission#
What an actor may do with something.
A set of these is one value, written as the names joined with a comma and a space: Inspect, Use. The numbers add up, and a named combination wins when one matches.
| Wire | Number | Meaning |
|---|---|---|
None | 0 | Nothing. |
Inspect | 1 | Look at it and read its components. |
Use | 2 | Operate it without changing it. |
Build | 4 | Place new things on the claimed area. |
Modify | 8 | Change its state or configuration. |
Destroy | 16 | Remove it from the world. |
Program | 32 | Write or start scripts on it. |
Transfer | 64 | Give it to another owner. |
Control | 128 | Drive it directly, such as moving a robot. |
Handle | 256 | Reach into what it holds: take items out of it, or put items in. |
All | 511 | Everything an owner may do by default. |
PersistenceClass#
How much effort the world owes an entity's continued existence.
| Wire | Number | Meaning |
|---|---|---|
Ephemeral | 0 | May disappear once it stops being relevant. Nothing is owed. |
Aggregatable | 1 | May be folded into an aggregate and regenerated from it later. Natural trees are the archetype: the forest is remembered, not each trunk. |
Persistent | 2 | Must survive individually, exactly as it was. |
Historic | 3 | Must survive individually and is historically significant, so it is also kept in the world event log. |
PrimitiveShape#
The shapes a procedural model is built from.
| Wire | Number | Meaning |
|---|---|---|
Cube | 0 | A box. |
BeveledCube | 1 | A cube with its edges cut, which reads better at low poly counts. |
LowPolySphere | 2 | A faceted sphere, deliberately coarse. |
Cylinder | 3 | A cylinder, normalised so its scale is its size in metres. |
Cone | 4 | A cone, standing on its base. |
Wedge | 5 | A triangular prism: ramps, roofs, blades. |
Plane | 6 | A flat quad, for panels and signs. |
Ribbon | 7 | A strip of a given width and thickness laid along a path: a road, a footpath, a fence line, a canal, a jetty. |
ReadoutKind#
How a readout is drawn above the thing carrying it.
| Wire | Number | Meaning |
|---|---|---|
Text | 0 | A line of text: a word, a count, a name for what the thing is doing. |
Bar | 1 | A bar that fills from empty to full, for a number out of a maximum. |
ReadoutSource#
Where a readout's number comes from.
| Wire | Number | Meaning |
|---|---|---|
Given | 0 | The number whoever put the readout up gave, which goes on changing by itself at the rate they set and needs no further writing. |
Health | 1 | The thing's own health, out of the most it can have. |
Energy | 2 | The charge in the thing's battery, out of its capacity. |
Growth | 3 | How much the thing has grown, out of how big it gets. |
RefusalKind#
A refusal the caller is expected to finish in its own vocabulary.
| Wire | Number | Meaning |
|---|---|---|
None | 0 | Nothing to add; the reason stands on its own. |
NotBuilt | 1 | It is generated nature, so the removal for nature applies. |
NotNatural | 2 | It was built, so demolition applies. |
RegionState#
What a region is currently costing.
| Wire | Number | Meaning |
|---|---|---|
Dormant | 0 | Nothing is happening and nothing is scheduled. Costs nothing. |
Aggregated | 1 | Nature here is held as a summary rather than as individuals. |
Operational | 2 | Machines are working, but nobody is watching. |
Active | 3 | A player is near. Everything is live and streamed. |
RegionTransition#
Why a region changed state. Kept for the log, and for asking why.
| Wire | Number | Meaning |
|---|---|---|
PlayerArrived | 0 | A player came within range. |
PlayerLeft | 1 | The last player left. |
MachineStarted | 2 | A machine here started working. |
WentQuiet | 3 | Nothing was scheduled any more. |
Summarised | 4 | Its nature was folded into a summary. |
Restored | 5 | Its nature was regenerated from the summary. |
RouteSeverity#
How much a thing found along a route matters.
| Wire | Number | Meaning |
|---|---|---|
Note | 0 | Worth knowing, and possibly deliberate. |
Fault | 1 | The way is broken here: nobody walks this. |
ScriptError#
Why a script could not be started or run.
| Wire | Number | Meaning |
|---|---|---|
None | 0 | It worked. |
NotFound | 1 | No such file on that computer. |
TooLarge | 2 | Over the largest size a script may be. |
TooManyProcesses | 3 | Over the number of processes one machine may run. |
CompileFailed | 4 | It is not valid Lua. |
RuntimeFailed | 5 | It threw. |
OutOfInstructions | 6 | It used its whole budget in one execution. |
NoComputer | 7 | The entity has no computer component. |
SimulationEventType#
The kinds of scheduled work the simulation knows about.
| Wire | Number | Meaning |
|---|---|---|
EntityWake | 0 | A sleeping entity should be brought back into active simulation. |
GrowthUpdate | 1 | A growing thing should settle its accumulated growth. |
BatteryEmpty | 2 | An energy store is predicted to reach empty at this moment. |
AgentThink | 3 | An AI agent should think. |
ScriptWake | 4 | A sleeping Lua process should resume. |
ScheduledAction | 5 | A deferred action requested by a script, agent or player. |
Landing | 6 | Something unsupported reaches the ground at this moment. |
SimulationSpeed#
How fast simulated time runs relative to real time.
| Wire | Number | Meaning |
|---|---|---|
Paused | 0 | The clock stands still. |
Normal | 1 | Real time. |
Double | 2 | Twice real time. |
Fast | 5 | Five times real time. |
Faster | 10 | Ten times real time. |
Fastest | 50 | Fifty times real time. |
StartingCatalogue#
How much of the shipped catalogue a brand new world is given.
| Wire | Number | Meaning |
|---|---|---|
None | 0 | Nothing at all. The catalogue is empty until somebody authors something. |
Character | 1 | The shipped character, and nothing else. The default. |
All | 2 | Every design this build ships: the character, the ScoutBot, the crate, the pavilion and the lift. |
WorldEventKind#
The kinds of thing worth remembering happened.
| Wire | Number | Meaning |
|---|---|---|
WorldCreated | 0 | A world was generated for the first time. |
ClaimCreated | 1 | Somebody claimed ground. |
EntityCreated | 2 | Something was built. |
EntityDestroyed | 3 | Something was removed. |
BlueprintCreated | 4 | A design was saved. |
ScriptStarted | 5 | A script was started on a machine. |
ScriptCrashed | 6 | A script failed, or ran out of instructions. |
AgentCreated | 7 | An agent was created. |
AgentGoalCompleted | 8 | An agent finished one of its goals. |
AccessChanged | 9 | Somebody let somebody else in, or showed them out. |
BlueprintUpdated | 10 | A draft was re-described under its own identity. |
BlueprintDeleted | 11 | A design nothing was built from was removed from the catalogue. |
AgentDismissed | 12 | An agent was dismissed: it stopped existing and its estate passed to whoever employed it. |
ClaimReleased | 13 | Somebody gave ground back, in whole or in part. |
Notes on particular sets#
The tables above are read off the world itself. What follows is what a table cannot say: where a set is used, and why it is shaped the way it is.
Permission: Handle is not Use#
Operating a thing and emptying it only ever looked like one permission by accident, and the difference is a security boundary. A claim can widen Inspect and Use over anything standing on it — that is what makes a street walkable — but it widens Handle only over things belonging to the claim's own owner. If it widened everything, then since a player's body is property standing on ground like any crate, the flag that made a shop walk-into-able would be the flag that emptied its visitors, in both directions. transfer_items and exchange_items need Handle on both ends.
Seven of the nine can be handed out. The grantable set is Inspect, Use, Build, Modify, Destroy, Program and Handle — Transfer and Control are not grantable, and nothing in that list lets the grantee grant on.
Three spellings exist for the same set, and they are not interchangeable:
| Where | How it is written |
|---|---|
| WebSocket, and storage | The flags string: "Inspect, Use" |
MCP tools (world_share, world_ask_access) | An array of lowercase words: ["inspect", "use"]. Trimmed and case-folded, so " Use " works. A word that is not one of the seven grantable ones contributes nothing and is silently ignored; a list that yields None is refused |
| Lua | See the Lua vocabulary |
PersistenceClass decides how a thing is removed#
It is the only thing that does: Aggregatable is felled with remove_natural, which writes a row saying it is gone; everything else was built and is taken down with demolish_entity, which deletes the rows it had. The affordance the server advertises on an entity is already the right one of the two. Over MCP the same pair is world_clear_natural and world_demolish, reaching the same two methods.
RefusalKind is never on the wire#
It rides beside the reason inside the server. The world decides which of the two removals applies — by PersistenceClass, in one place — but the action it should have been is called remove_natural over the socket and world_clear_natural over MCP, and the world knows neither name. So it states the fact and tags it, and each door appends the pointer in the words its own caller can act on.
DesignSaveOutcome is a word on the wire#
Never a number: MCP answers with the lowercase word in outcome, and the socket's action_result.Design.Outcome carries the same word, plus deleted for the action that removes one. Which applies depends on facts only the server holds — whether the design exists, whose it is, whether anything stored was built from it — so a caller reads the word rather than predicting it.
unchanged is the one that can answer about a design under a different name from the one asked for, because what a design is decides and the name does not. The MCP answer then carries nameAsked alongside name, so a caller can see that the name it asked for was given to nothing.
ReadoutKind and ReadoutSource describe a caption, not a fact#
A readout is what a thing shows above itself, and nothing in the world ever reads one back: the simulation does not consult a caption to decide anything, and taking one down changes nothing but what is drawn.
ReadoutSource is the half worth understanding. Everything but Given names a number the world already keeps and already sends — health, a battery, how grown something is — so a bar bound to one is true for ever with nobody maintaining it, and costs nothing on the wire because those components travel anyway. A source the thing does not carry is refused rather than stored, because a bar that could never move is worse than no bar. Given is a number of the author's own, stored as a value, a rate and a moment, so a bar that empties over the next half minute is one row that nothing ever touches again.
A design may bind a reading only to a number it would itself be allowed to declare, which is why energy may appear in one and health and growth may not — the same two sentences that refuse those components refuse a reading of them.
OwnerKind is part of the identity#
A player and an agent that happen to share a GUID are different owners, and a permission check never conflates them. Nothing in a running world issues a group owner today; the kind exists so the identity can carry one later.
NaturalObjectKind and its variant#
Variant beside it is a kind-specific sub-type — a tree species, or a rock shape — and is an open integer, not a set.
WorldEventKind at the endpoints#
GET /events returns these as the kind field of each entry; it has no filter of its own. The MCP world_history tool's kind argument matches them, and does so case-insensitively. The list is short deliberately: a log that records everything is one nobody reads.
ClaimRefusal is all or nothing#
A request naming any chunk somebody already holds is refused entirely; claims are never partially granted.
AgentState and AgentTrigger as a brain sees them#
agent_list carries the state as a plain string field, not as an enum, but the spellings are the same. The trigger is the value a Lua brain reads as world.trigger: an agent is never woken because a tick passed, and naming the reasons is what makes that rule visible.
SimulationSpeed is the multiplier#
The number is the multiplier, which is why Paused is naturally zero. simulation_control carries the integer, not the name. There is one clock for everybody: a client cannot run its own world faster, only a connection from the machine the world runs on may change it, and when somebody does every connected client is told.
AccessSubject and AccessSubjectKind#
Two names for the same distinction, one on the server and one in the agent action set. Letting somebody build in your town is not letting them into your house: over someone else's property standing on your land, a claim can widen access to Inspect and Use and no further.
InputFieldKind serves two askers#
An affordance the server advertises on an entity may need values before it can be sent; a question something in the world has put to somebody may collect values beside the choice. Each field says what kind of thing it is so a client can draw the right control, and one client control serves both. Permissions is the server's own, written on an affordance it composed itself; a question raised from a script may ask only for Text or a Number.
DesignSeverity and the finding codes#
The finding codes themselves — vanishing, enormous, crack, buried, detached, protrudes, headroom, hovers, limb_missing — are listed with their thresholds under design review thresholds.
FileSystemError and ScriptError in Lua#
Both come back as the second value of a Lua call — nil, reason — and are never thrown. TooLarge is the file or script size limit, TooManyFiles and TooManyProcesses the counts, each listed under numeric bounds.
SimulationEventType is the scheduler's own#
Nothing outside the server can name one and none of them appears on the wire, but it is a closed set worth knowing exists: content that scripts and blueprints invent arrives as ScheduledAction carrying its own payload, so new content never needs a new event type.
Open sets, and the rules that replace enumeration#
Four vocabularies in this world are deliberately not closed. Listing them would be a lie, because the whole point is that a design, a script or an agent can introduce a value nobody wrote down. Each one has a rule instead, and the rule is enforced.
| Vocabulary | Rule | Where the world's own values are |
|---|---|---|
EntityType | A validated string, at most 48 characters | tree, rock, robot, computer, character, building, item are the seven the world itself uses. house, road and market exist in a running world because an agent wrote those words in a design |
| Semantic tags | 1–32 characters, lowercase letters, digits and underscore | The eleven character tags below. Anything else is a name the design invented, and something looking for it will simply not find it |
| Item names | 1–48 characters, non-blank | Whatever a design or script trades in |
| Visual keys, verbs, group tags, metadata keys | Length-bounded free text | tree, rock, blueprint are what the world produces; Open, Trade, Read are the verbs the shipped designs use |
The type is a label for querying and presentation. Behaviour comes from components, so adding a type never means adding a code path — which is exactly why it cannot be an enum.
The semantic tags a character is expected to carry#
The tag is a free string; these are the names the generator writes and the animator looks for, gathered in one place so the two cannot drift apart over a typo.
| Tag | Part |
|---|---|
root | The figure's own origin |
torso | The body |
head | The head |
upper_arm_left / lower_arm_left | Left arm, two segments |
upper_arm_right / lower_arm_right | Right arm, two segments |
upper_leg_left / lower_leg_left | Left leg, two segments |
upper_leg_right / lower_leg_right | Right leg, two segments |
Required for a character — the seven a figure needs at minimum: root, torso, head, upper_arm_left, upper_arm_right, upper_leg_left, upper_leg_right. A design carrying torso and head and at least one of the eight limb names is treated as a character, and the review reports each of the seven it is missing as a limb_missing note. A four-legged survey robot with a torso and a head but none of the limb names is left alone, because it is exactly as intended.
The limbs an animator swings: the eight upper_/lower_ names above.
Registered component names#
The stable names components are stored and sent under, and what each one carries. The names are part of the storage contract and may not change once a world exists, and a name not in this list is refused on the way in rather than ignored: silently dropping a component would corrupt an entity on the next save. Every field is written exactly as it is named here, inside the component's own JSON, wherever an entity is described; a field marked derived is worked out by the world and sent, and is never read back.
computer#
A virtual computer's capacities.
| Field | Type | Meaning |
|---|---|---|
CpuInstructionsPerSecond | number | Budget for script execution, in Lua instructions per simulated second. |
MemoryBytes | integer | Memory the machine claims, in bytes. |
StorageBytes | integer | Total size the virtual filesystem may occupy. |
IdlePowerW | number | Power drawn while doing nothing, in watts. |
energy_consumer#
Something that draws power.
| Field | Type | Meaning |
|---|---|---|
DemandW | number | Power it draws when enabled, in watts. |
IsEnabled | boolean | Whether it is switched on. |
EffectiveDemandW | number, derived | What it actually draws right now: the demand, or nothing while switched off. |
energy_producer#
Something that generates power.
| Field | Type | Meaning |
|---|---|---|
OutputW | number | Power it makes when enabled, in watts. |
IsEnabled | boolean | Whether it is running. |
EffectiveOutputW | number, derived | What this actually contributes right now. |
energy_storage#
Stored energy, computed analytically rather than ticked.
| Field | Type | Meaning |
|---|---|---|
CapacityWh | number | How much it can hold, in watt-hours. |
EnergyAtReferenceWh | number | Energy held at ReferenceTime. |
ReferenceTime | SimulationTime | The moment the stored energy was last settled; everything since is worked out from the net power. |
NetPowerW | number | Production minus consumption. Negative means draining. |
IsDraining | boolean, derived | Whether it is losing energy. |
IsCharging | boolean, derived | Whether it is gaining energy. |
group#
Marks an entity as part of something bigger.
| Field | Type | Meaning |
|---|---|---|
Root | EntityId | The entity this one is part of. Its own id if it is the root. |
Tag | string | What this piece is to the whole, such as front_door. |
MaxTagLength= 32 — Longest a piece's tag may be.
growth#
Growth over simulated time, computed analytically.
| Field | Type | Meaning |
|---|---|---|
PlantedAt | SimulationTime | When it started growing. |
ReferenceTime | SimulationTime | When growth was last settled. Nothing happens between this and now until asked. |
BiomassAtReferenceKg | number | How much of it there was at the reference moment, in kilograms. |
MaxBiomassKg | number | How big it can get, in kilograms. |
GrowthRateKgPerDay | number | How fast it grows with ample water, in kilograms a simulated day. |
WaterFactor | number | Availability of water, from 0 (none, no growth) to 1 (ample). |
EffectiveRateKgPerDay | number, derived | How fast it is growing now, water taken into account. |
IsMature | boolean, derived | Whether it is fully grown. |
health#
Damage state.
| Field | Type | Meaning |
|---|---|---|
Current | number | Health left. |
Max | number | Health when undamaged. |
IsAlive | boolean, derived | Whether any health is left. |
Fraction | number, derived | Health left as a share of the maximum, from 0 to 1. |
interactable#
Says that something can be used, and what to call it.
| Field | Type | Meaning |
|---|---|---|
Verb | string | What using it is called: Open, Trade, Read. |
Hint | string | A line for whoever is looking at it, if the design has something to say. |
RangeMeters | number | How close you have to be, in metres. Further away, using it is refused and the refusal names the distance (WhyTooFarToUse). |
MaxVerbLength= 24 — Longest the verb may be.MaxHintLength= 96 — Longest the hint may be.
inventory#
What an entity is carrying.
| Field | Type | Meaning |
|---|---|---|
MaxSlots | integer | How many kinds of item it can hold at once. |
Items | object of integer by string | Quantity per item name, ordered so serialisation is deterministic. |
UsedSlots | integer, derived | How many kinds it holds now. |
TotalQuantity | integer, derived | How many items altogether. |
IsEmpty | boolean, derived | Whether it holds nothing. |
MaxItemNameLength= 48 — Longest an item's name may be.
movement#
The ability to move, and the current intent to.
| Field | Type | Meaning |
|---|---|---|
MaxSpeedMetersPerSecond | number | How fast it can go, in metres a second. |
Destination | WorldPosition | Where the entity is trying to get to, if anywhere. |
DepartedAt | SimulationTime | When the journey began. Lets anything reading this — the client above all — work out how far along it is without being told again. |
Origin | WorldPosition | Where the journey began. Null when nothing is under way. |
IsMoving | boolean, derived | Whether a journey is under way. |
ArrivesAt | SimulationTime, derived | When this journey ends, worked out from where and when it began rather than from where the entity is filed. |
DefaultWalkSpeedMetersPerSecond= 1.4 — A comfortable walking pace, and what a body gets when its design forgot to move.
ownership#
Permissions the owner has granted to others.
| Field | Type | Meaning |
|---|---|---|
Grants | object of Permission by OwnerId | What each named player or agent has been allowed, beyond what anybody may do. |
PublicPermissions | Permission | What anybody at all may do with this thing. |
HasGrants | boolean, derived | Whether anybody has been let in, by name or by policy. |
part_state#
The parts of a built thing that are not as they were designed.
| Field | Type | Meaning |
|---|---|---|
Parts | object of PartState by string | What has been done, by part id. |
IsEmpty | boolean, derived | Whether every part is as it was designed. |
IsMoving | boolean, derived | Whether anything on this thing is still moving of its own accord. |
MaxParts= 128 — How many parts of one thing may carry state.
physics#
Weight: the thing that makes an unsupported object fall.
| Field | Type | Meaning |
|---|---|---|
GravityMetersPerSecondSquared | number | How hard it falls, in metres a second squared. |
FallingFrom | WorldPosition | Where the fall began, or null when the thing is at rest. |
FellAt | SimulationTime | When the fall began, so anything reading this — the client above all — can draw the arc without being told again. |
InitialVelocityMetersPerSecond | number | Upward speed at the moment the fall began. Zero for something simply let go; positive for something thrown up, which rises before it falls. |
RestingHeightMeters | number | The height the fall ends at: the ground under it. |
IsFalling | boolean, derived | Whether it is in the air. |
EarthGravity= 9.81 — Earth's, near enough, and the default when a design says nothing.
readouts#
What a thing is showing above itself.
| Field | Type | Meaning |
|---|---|---|
Readouts | object of Readout by string | What is being shown, by the name whoever put it there gave it. |
IsEmpty | boolean, derived | Whether this thing is showing nothing at all. |
IsChanging | boolean, derived | Whether any of these numbers is still moving of its own accord. |
MaxReadouts= 4 — How many readings one thing may show at once.
script#
The script an entity runs.
| Field | Type | Meaning |
|---|---|---|
ScriptPath | string | The path of the script it runs, on its own filesystem. |
AutoStart | boolean | Whether the script should start when the entity wakes or the server restarts. |
MaxPathLength= 256 — Longest a script's path may be.
sensor#
The ability to detect nearby entities.
| Field | Type | Meaning |
|---|---|---|
RangeMeters | number | How far it can see, in metres. |
MaxResults | integer | The most things one scan reports. |
transform#
Where an entity is. Every entity has one.
| Field | Type | Meaning |
|---|---|---|
Position | WorldPosition | Where it stands, in metres. |
Rotation | WorldRotation | Which way it faces. |
Scale | number | How large it is drawn relative to its design; 1 is as designed. |
visual#
How an entity should be drawn.
| Field | Type | Meaning |
|---|---|---|
VisualKey | string | Names the family of visual, such as tree or rock. |
Variant | integer | Which variant within that family, such as a tree species. |
Blueprint | BlueprintId | Set when the visual is generated from a stored blueprint. |
IsGenerated | boolean, derived | Whether it comes from a design rather than from the generator. |
MaxVisualKeyLength= 64 — Longest a visual key may be.
water#
Held water.
| Field | Type | Meaning |
|---|---|---|
StoredLitres | number | How much water it holds. |
CapacityLitres | number | How much it can hold. |
Saturation | number, derived | Fill level from 0 to 1, usable directly as a growth water factor. |
IsDry | boolean, derived | Whether it holds none. |
Shapes inside components#
The objects the component fields above are made of.
PartColour#
A colour a design chose for itself.
| Field | Type | Meaning |
|---|---|---|
Red | number | Red, from 0 to 1. |
Green | number | Green, from 0 to 1. |
Blue | number | Blue, from 0 to 1. |
Alpha | number | Opacity. Below one makes glass, water and anything else you can see through. |
IsValid | boolean, derived | Whether every channel is finite and within range. |
PartMotion#
A number that goes on changing by itself.
| Field | Type | Meaning |
|---|---|---|
Start | number | The value at Since. |
RatePerSecond | number | How fast it changes, and in which direction. |
Since | SimulationTime | When it was last set. |
Limit | number | Where it stops, or null to go on forever. A door swings to ninety degrees and halts; a wheel turns until the car does not. |
IsMoving | boolean, derived | Whether this is still going anywhere. |
PartState#
What has been done to one part of a design.
| Field | Type | Meaning |
|---|---|---|
Solid | boolean | Whether this part blocks movement, overriding the design. |
Visible | boolean | Whether this part is drawn at all. |
Colour | PartColour | A colour, overriding both the design's colour and its material. |
Axis | WorldPosition | The axis a turn spins about and a slide travels along, in the design's own space. Defaults to up, which is what a door hinges on. |
Turn | PartMotion | How far the part has turned about Axis, in radians. |
Slide | PartMotion | How far the part has moved along Axis, in metres. |
IsEmpty | boolean, derived | Whether this says anything at all. |
IsMoving | boolean, derived | Whether anything here is still changing on its own. |
EffectiveAxis | WorldPosition, derived | The axis to use, which is up unless the part said otherwise. |
Readout#
One reading shown above a thing in the world.
| Field | Type | Meaning |
|---|---|---|
Kind | ReadoutKind | Whether this is drawn as a bar or as a line of text. |
Source | ReadoutSource | Where the number comes from. |
Label | string | A word or two naming what is being shown, or null for none. |
Text | string | Words to show instead of a number, or null when this shows a number. |
Colour | PartColour | What colour to draw it, or null to let the client choose. |
Value | PartMotion | The number, when Source is Given: where it was, how fast it is changing and where it stops. |
Maximum | number | The number that counts as full, when this shows a given number. |
Order | integer | Where this sits in the stack, smallest first. |
IsEmpty | boolean, derived | Whether this says anything worth drawing. |
IsChanging | boolean, derived | Whether this number is still moving of its own accord. |
A blueprint may not carry transform: it is set when the entity is spawned, and a blueprint that names one is refused.
Ten of them may be declared by a design somebody wrote — computer, energy_consumer, energy_producer, energy_storage, interactable, inventory, movement, physics, script, sensor — each within bounds. The other eight are refused by name, with a sentence saying why that one is not an author's to write. The rule is applied at the one place a design enters the catalogue, so it holds for every design that arrives from outside the server, whichever door it came through. The table in Blueprints has the figures and the reasoning.
Validated strings#
The values people get wrong. Every one of these is checked by allowing rather than by forbidding — a charset, not a blocklist, because blocklists get bypassed.
| Value | Charset | Length | First character | Case | Rejection |
|---|---|---|---|---|---|
PartId | a–z, 0–9, _ | 1–32 | Must be a lowercase letter | Ordinal, case-sensitive. Mast is not a valid id at all | Fails the whole message on the wire; a part needs an id from the validator |
EntityType | a–z, 0–9, _ | 1–48 | Must be a lowercase letter | Ordinal, case-sensitive | Fails the whole message on the wire |
| Semantic tag | a–z, 0–9, _ | 1–32, or absent entirely | No rule — unlike the two above, a tag may start with a digit | Ordinal | a semantic tag must be 1 to 32 characters, or a semantic tag may contain only lowercase letters, digits and underscores |
| Login name | letters, digits, _, -, . | 2–32 | Must be a letter | Trimmed and lowercased before use, so Youri and youri are one account | Refused, naming the rule that failed |
| Display name | Any | ≤ 64 | — | Trimmed. Defaults to the login name if blank | A display name may be at most 64 characters. |
| Agent name | Any non-blank | ≤ 64 | — | As given | Refused when the agent is created |
| Agent goal name | Any non-blank | ≤ 120 | — | As given | Refused when the agent is created |
| Design name | Any non-blank | ≤ 64 | — | As given | a model needs a name, or the name may be at most 64 characters |
| Blueprint name | Any non-blank | ≤ 64 | — | As given | a blueprint needs a name, or the same length message |
| Item name | Any non-blank | ≤ 48 | — | Ordinal | An item name may be at most 48 characters. |
| Interactable verb | Any non-blank | ≤ 24 | — | As given | A verb may be at most 24 characters. |
| Interactable hint | Any | ≤ 96 | — | As given | A hint may be at most 96 characters. |
| Declared component name | One of the ten on the allowlist | — | — | Ordinal | <name>: is not a component a design may declare, or the sentence saying why that one is refused |
| Visual key | Any non-blank | ≤ 64 | — | As given | A visual key may be at most 64 characters. |
| Group tag | Any | 1–32 when present | — | As given | A group tag may be 1 to 32 characters. |
| Planet name | Any non-blank | ≤ 64 | — | Trimmed | A planet name may be at most 64 characters. |
Account kind | human or agent | — | — | Matched case-insensitively; absent or empty means human | 400 from /api/register: 'kind' must be 'human' or 'agent'. Not stored — it chooses whether the answer carries MCP configuration |
| Account token | base64 of 32 random bytes | 44 characters | — | Ordinal, compared in constant time against a stored SHA-256 hash | 401 from /api/session; '<name>' belongs to somebody… from hello. An account may hold several, and any of them proves it |
| Passphrase | Any non-blank | 12–256 | — | The characters as typed, hashed with PBKDF2-HMAC-SHA256 (210,000 iterations, 16-byte salt) and compared in constant time | 400: A passphrase must be at least 12 characters and at most 256, and must not simply be your login name. Optional per account; never asked of a model |
| Device code | digits, with spaces and hyphens ignored | exactly 8 digits | — | Ordinal, compared in constant time | 401 from /api/devices/claim, one sentence for never-minted, expired, used and replaced alike. Lives five minutes, works once |
| Device name | letters, digits, space, ', -, _, ., (, ) | ≤ 48 | — | Anything else is dropped rather than escaped | Never refused: an empty result falls back to the browser the server recognises, and then to A device |
| Resume key | letters, digits, -, _ | 8–64 when present | — | Ordinal, and only ever compared with another connection's | A resume key is 8 to 64 characters of letters, digits, hyphens or underscores. from hello. Nothing is stored: it names a browsing context for as long as it is connected |
BlueprintId is not a validated string: it is a GUID, and a value that is not a GUID fails as JSON before anything sees it. The same goes for EntityId, AgentId, PlayerId, PlanetId and EventId.
Virtual paths#
VirtualPath is a security boundary, not a convenience. Every path reaching it comes from Lua or an AI agent.
| Rule | Value |
|---|---|
| Must be absolute | Starts with /. There is no working directory and no resolution step |
| Whole-path length | ≤ 256 characters |
| Segment length | ≤ 64 characters |
| Depth | ≤ 16 segments |
| Segment charset | Letters, digits, _, -, . |
. and .. | Refused outright as segments, rather than resolved. Nothing can refer outside the filesystem even in principle |
| Trailing slash | Accepted and normalised away — /home/agent/ is /home/agent |
| Comparison | Ordinal, case-sensitive |
| Rejection | '<path>' is not a valid virtual path. — from Lua, as nil, reason |
/etc/passwd is a perfectly valid virtual path naming a file that does not exist. It has nothing to do with the host.
ScriptComponent.ScriptPath is checked separately and slightly differently at its own edge: ≤ 256 characters, must start with /, and may not contain .. anywhere in the string — belt and braces on untrusted input.
Numeric bounds and defaults#
Simulation limits#
These are the shipped figures. A world's operator may change them, and every answer that hits one names the limit it hit, so a world set up differently still tells you what it is holding you to. A world set up with impossible figures refuses to start rather than misbehaving later: every one must be greater than zero, and LuaInstructionSlice may not exceed MaxLuaInstructionsPerExecution, or the budget would never be checked.
| Key | Default | What it bounds |
|---|---|---|
MaxScheduledEventsPerEntity | 256 | How many events one entity may have queued at once. Stops a runaway script from filling the scheduler with its own wake-ups. |
MaxEventsPerAdvance | 1,000,000 | Backstop for a handler that keeps scheduling work at the current moment. Draining stops rather than looping forever; the remaining events stay queued for the next advance, so the server degrades instead of hanging. |
MaxVirtualFileSize | 65,536 | Largest single file a virtual computer may hold, in bytes. Stops a script from filling storage with one write; a larger write is refused. |
MaxVirtualFilesPerComputer | 256 | How many files one virtual computer may hold. Another is refused. |
MaxComputerLogEntries | 500 | How many log lines a virtual computer keeps before discarding the oldest. |
MaxLuaInstructionsPerExecution | 200,000 | Lua instructions one execution may use before the process is killed. This is what stops while true do end from blocking the server. |
LuaInstructionSlice | 5,000 | How many instructions run between yields. The interpreter hands control back this often, so the figure is the granularity of the budget rather than a limit in itself: smaller checks more often and costs more. |
MaxLuaProcessesPerComputer | 8 | How many processes one machine may run at once. Starting another is refused. |
MaxScriptExecutionsPerTick | 64 | How many script executions one tick may run before the rest wait. Deferred, not lost: the rest run on the next tick. |
MaxScriptSizeBytes | 65,536 | Largest script that may be loaded, in bytes. A larger one is refused, not cut short. |
MaxScheduledEventsPerScript | 32 | How many callbacks one script may have waiting. Scheduling another is refused. |
MaxQueryResults | 64 | Rows a single world query may return. Clamped silently: a query asking for more gets this many. |
MaxAgentActionsPerThought | 8 | How many actions one thought may ask for. A brain that returns a hundred requests gets the first few carried out and can ask again next time — the world is not obliged to do everything an AI imagines at once. |
MinAgentThinkInterval | 1 s | The shortest interval an agent may be rescheduled at. A brain that asks to think again sooner is held to this, so that no brain can become the per-tick loop this world never runs. |
MaxDesignSpeedMetersPerSecond | 8 | Fastest a design may claim to move, in metres a second. |
MinDesignSpeedMetersPerSecond | 0.05 | Slowest a design may claim to move. A floor, not a courtesy: travel time is distance over speed, and a speed near zero turns the arrival the scheduler has to predict into a TimeSpan that does not exist. |
MaxDesignSensorRangeMeters | 128 | How far a design may claim to see, in metres. |
MaxDesignInventorySlots | 64 | How many kinds of item a design may claim to hold. |
MaxDesignItemQuantity | 100 | How much of any one item a design may arrive holding. |
MaxDesignStockQuantity | 500 | How much of everything together a design may arrive holding. |
MaxWorldItemQuantity | 10,000 | How many of any one item name may exist in this world at once. |
MaxDesignEnergyCapacityWh | 100,000 | Largest battery a design may claim, in watt-hours. |
MaxDesignPowerW | 5,000 | Most power a design may claim to draw or to make, in watts. |
MinDesignPowerW | 0.01 | Least power a design may claim to draw or make, other than none at all. The same floor as the speed, for the same reason: the moment a battery runs flat is capacity over draw, and a draw near zero is a moment no clock can hold. |
MaxDesignCpuInstructionsPerSecond | 1,000,000 | Fastest processor a design may claim, in instructions a second. |
MaxDesignMemoryBytes | 67,108,864 | Most memory a design may claim, in bytes. |
MaxDesignStorageBytes | 16,777,216 (derived, not configurable) | Most storage a design may claim, in bytes — and the only figure on a computer that is really spent, so it is derived rather than chosen. A filesystem cannot hold more than its largest file times its file count, and a design claiming more would be claiming space that does not exist. |
MaxDesignGravityMetersPerSecondSquared | 30 | Strongest gravity a design may claim, in metres a second squared. |
MinDesignGravityMetersPerSecondSquared | 0.1 | Weakest gravity a design may claim. A falling thing's landing is predicted from it, so gravity near zero is a fall that never lands. |
MaxDesignInteractionRangeMeters | 8 | How far away a design may claim to be usable from, and how far anything is usable from at all, in metres. |
MaxTransferReachMeters | 8 | How far apart two things may be and still pass goods, in metres. |
MaxPromptQuestionLength | 160 | Longest a question, or an answer typed into one, may be. Refused, not cut short. |
MaxPromptOptions | 8 | How many answers one question may offer. More is refused. |
MaxPromptOptionLength | 48 | Longest the wording on one answer, or on one field, may be. |
MaxPromptFields | 4 | How many values one question may collect beside the choice. |
MaxPendingPrompts | 8 | How many questions one person may have waiting at once. The next is refused until one is answered or runs out. |
PromptLifetime | 120 s | How long a question stands, and how long having dealt with something leaves it able to ask. |
MaxPromptReachMeters | 8 | How near you have to be standing for something to put a question to you, in metres. |
AllowTeleport | True | Whether anything in this world may be put somewhere without walking there. |
An operator sets these under Limits in the server's configuration, by the same names, except that the two spans — the shortest think interval and the prompt lifetime — are given there in seconds. Every answer that hits one names the limit it hit, by the name above.
The prompt figures bound text that came out of a Lua script, a language model or somebody else's design and is on its way to a person's screen. A question is also cleaned exactly as a chat line is — formatting characters dropped, control characters and whitespace collapsed — and is refused outright if it contains < or >. There is deliberately no token bucket on questions: what makes a dialogue unwelcome is not having asked for it, and the rule that you can only be asked by something you have just used or just answered bounds that where a rate could not.
Chat limits#
A token bucket per account, measured against the wall clock.
| Key | Default | Ceiling | Note |
|---|---|---|---|
MaxChatMessageLength | 240 characters, after cleaning | Hard-refused above 4096 at startup | Longer than a socket frame could carry is not a limit. The same for people and agents: a machine has no more right to a long speech than a person |
ChatMessagesPerMinute | 30 | — | The sustained rate one account may speak at |
ChatBurst | 5 | — | Messages back to back before that rate applies |
AgentChatMessagesPerMinute | 6 | May not exceed ChatMessagesPerMinute | A fifth of a person's. An agent never gets bored, and the public channel is shared |
AgentChatBurst | 2 | May not exceed ChatBurst |
Configuring agents to out-talk people is refused at startup rather than discovered when the public channel fills.
There is deliberately no cap on how many people hear a message. Nobody can address a list — public is "everybody the server chose", direct names exactly one participant — so a recipient cap would be a cap on how many people may stand in a town square together.
Account API limits#
Two more token buckets, keyed on the calling address rather than on an account — because the routes they guard are where an account comes from, and a caller there has nothing else to be known by.
| Key | Default | Counted | At the edge |
|---|---|---|---|
RegistrationsPerMinute | 2 | Every POST /api/register, including a malformed one | 429 with error: too many accounts from your address, wait a minute |
RegistrationBurst | 5 | — | Registrations back to back before that rate applies |
FailedSignInsPerMinute | 5 | Only a refused POST /api/session, /api/signin, /api/passphrase, /api/devices, /api/devices/revoke, /api/devices/code or /api/devices/claim. One that succeeds spends nothing | 429 with ok: false and the same sentence. While the bucket is empty a correct token is refused too |
FailedSignInBurst | 10 | — | Wrong credentials back to back before that rate applies |
Every field must be greater than zero, and the per-minute rates are whole numbers, which is what makes the refusal's "wait a minute" true whatever they are set to.
One more bucket has no setting, because its size is part of why an eight-digit code is safe and an operator lowering it would not see that argument: the whole world together may get 30 device codes wrong a minute, with 60 back to back, after which /api/devices/claim refuses everybody for up to a minute. A per-address limit cannot see a thousand machines guessing slowly; this can. Nothing else is affected — playing, signing in with a token and signing in with a passphrase all carry on — and no code is destroyed by it.
A malformed registration is counted, because otherwise the cheapest way past the limit would be to send rubbish. A refused sign-in is counted whether the token was wrong or absent, because a guesser learns the same thing either way.
An address is not a person: everybody behind one NAT shares a bucket, and anybody with a VPN or a cloud account has as many as they care to pay for. This is a speed bump against casual abuse and not protection against a distributed attacker. X-Forwarded-For is not read — it is a string the caller writes — and IPv6 is keyed on the /64. The WebSocket hello spends the same two buckets: a hello that registers an account is a registration and one that is refused is a failed sign-in, so the socket is not a second, unmetered door.
World settings#
Chosen by the world's operator when the world is made or the server started. These are the shipped defaults, and the ones a player or a client can notice; world_bootstrap and GET /world report the live values of the first five.
| Setting | Default | What it does |
|---|---|---|
PlanetName | Artificial World | Name given to a world when it is created. Non-blank; ≤ 64 characters |
Seed | 20260909 | Seed for a new world. Ignored once one exists — the stored seed wins, because changing it would regenerate a different planet under the existing changes |
PlanetRadiusMeters | 1000000 | How far from the origin the planet extends |
RegionSizeMeters | 1024 | The unit of region activity |
ChunkSizeMeters | 128 | The unit of streaming, claiming and spatial indexing |
SnapshotIntervalSeconds | 15 | How often live state is written to storage. Durable actions — a claim, a saved design, a file write, a felled tree — do not wait for it |
StartSpeed | 1 | One of 0, 1, 2, 5, 10, 50 |
MaxSubscribedChunksPerClient | 512 | Chunks one client may watch at once. Silently dropped past the cap, not refused |
RequirePlayerToken | false | Whether a hello naming an account must present its token. True also means the account must already exist, so registering happens over /api/register and not on the socket. Anonymous sessions are unaffected |
SeedDemoAgent | true | Whether a new world gets a demo agent |
DemoAgentName | Surveyor | What that agent is called |
The grid sizes are properties of a world in the making: changing them for a world that already exists changes which chunk everything falls in.
MCP options#
| Setting | Default | What it does |
|---|---|---|
Enabled | true | Whether the endpoint exists at all |
ReadOnly | false | When true, the acting tools are not offered and are refused if called anyway |
AllowedOrigins | empty | Origins a browser may make MCP requests from. Empty refuses every request carrying an Origin header, which is what stops a page you happen to open from reaching a world only you can reach |
MaxSessions | 32 | Sessions at once, evicted least-recently-used |
SessionIdleMinutes | 60 | How long a session, and your presence in the world, survive without a request |
Transport and request caps#
| Cap | Value | At the edge |
|---|---|---|
| WebSocket message | 256 KiB | Message too large., and the read loop stops |
| MCP request body | 256 KiB | JSON-RPC parse error (−32700), HTTP 400 |
| MCP tool argument: any string | ≤ 4096 characters | Refused, before any tighter per-tool limit |
| MCP tool argument: any array | ≤ 64 items | Refused |
| MCP unknown argument names | — | Refused: every tool schema sets additionalProperties: false |
GET /events?limit= | Default 50, clamped to 1–500 | Clamped silently, on the page and in the figures alike |
GET /designs?page= | Default 1, clamped to 1–pages | Clamped silently |
| Concurrent connections | No application cap | Bounded only by the host |
What a design may contain#
| Key | Default | What it bounds |
|---|---|---|
MaxParts | 512 | How many shapes one design may be made of. |
MaxPartDimensionMeters | 128 | Largest any single part may be along any one axis, in metres. |
MaxReachMeters | 64 | How far from the design's own origin a placed part may reach sideways, in metres. |
MaxHeightMeters | 1,024 | How high above its own origin a placed part may reach, in metres. |
MaxDepthBelowMeters | 64 | How far below its own origin a placed part may reach, in metres. |
MaxDepth | 8 | How deep the parent chain may go. |
MinPartDimensionMeters | 0.01 | Smallest any single part may be along any one axis, in metres. A plane is the one shape allowed to be thinner. |
MaxDiameterMeters | 128 (derived, not configurable) | The widest a design can be, corner to corner across its footprint: the bound every horizontal search has to add to its own radius. |
And the counts and lengths fixed in the design's own shape:
| Constant | Value | What it bounds |
|---|---|---|
BlueprintDefinition.MaxNameLength | 64 | Longest a design's name may be. |
BlueprintDefinition.MaxScripts | 8 | How many scripts one design may carry. |
BlueprintDefinition.MaxIncludes | 16 | How many other designs one design may include. |
BlueprintDefinition.MaxIncludeOffsetMeters | 256 | How far an included design may be placed from the one including it. |
ProceduralAssetDefinition.MaxNameLength | 64 | Longest a model's name may be. |
AssetPart.MaxSemanticTagLength | 32 | Longest a part's semantic tag may be. |
AssetPart.MaxSurfaceTiling | 64 | The most times a pattern may repeat across a metre. |
AssetPart.MinSurfaceTiling | 0.01 | The fewest times a pattern may repeat across a metre. |
AssetPart.MaxPathPoints | 64 | The most points a path may have. |
BlueprintPart.MaxTagLength | 32 | Longest an included design's tag may be. |
SvgMaterial.MaxNameLength | 48 | Longest a material's name may be. |
SvgMaterial.MaxPerDesign | 8 | The most materials one design may carry. |
PartStateComponent.MaxParts | 128 | How many parts of one thing may carry state. |
A validator reports every problem, not the first, because the author may be a language model that needs the whole list to correct itself. Nothing throws; refusals are values.
The three extent bounds are deliberately not one number, and they are checked against where a part ends up — parents, turns and ribbon paths applied — rather than against the offset it was written with. Sideways is half a chunk, because every index in this world is a grid in X and Z and widening a design widens every search that runs on every step a walker takes; upwards is a kibibyte of metres, because no index has a Y axis and the only real bound on height is what the client can draw. Something wider than one chunk is built as a composite of designs, each piece an entity filed in its own chunk.
Other rules a design must satisfy: a name; at least one part; unique part ids; a parent that exists and is not the part itself; no cycles; finite position, rotation and scale; a known primitive and material; a Surface naming a material the design itself carries; and Path and Draped only on a Ribbon, whose consecutive path points must be at least 0.01 m apart — two coincident points have no direction, and a client normalising one would divide by zero. An included design needs a blueprint id, a finite offset and rotation, and a tag of at most 32 characters.
Colour#
A part may name a colour of its own instead of taking its material's. Three numbers cannot reference a file, a shader or an asset, so there is nothing to ration here — the closed list bought no safety, and a world where anything can be made should not ration its colours.
| Field | Range | At the edge |
|---|---|---|
Red, Green, Blue, Alpha | 0.0–1.0, finite | colour <c> has a channel outside 0..1 or is not finite |
Alpha defaults to 1.0. Values are linear, not sRGB, and unpremultiplied, so a design copying a named material's numbers gets that colour exactly.
Written by hand, a colour may be given as hex: #rgb, #rrggbb or #rrggbbaa, with or without the #. Any other length, or any non-hex character, parses as nothing rather than as a guess — red is three characters and would otherwise have been read as shorthand hex.
Surface materials#
A part may be painted with a small SVG drawing carried by its own design. The drawing is multiplied over the part's colour, so a white part shows it exactly and a coloured part shows it tinted.
| Field | Where | Default | At the edge |
|---|---|---|---|
Materials | ProceduralAssetDefinition | [] | N materials exceeds the limit of 8 |
Materials[].Id | — | Derived, not chosen. the material's id is not the digest of its svg; it is derived, not chosen | |
Materials[].Name | — | a material needs a name; over 48 characters is refused | |
Materials[].Svg | — | See the allowlist below | |
Surface | AssetPart | null | names the surface '<id>', which this design does not carry |
SurfaceTiling | AssetPart | 1.0 | Outside 0.01 – 64, or not finite: tiles its surface <n> times a metre. Ignored entirely on a part with no Surface |
Two materials with the same Id in one design is is carried twice.
The identity is a digest of the drawing's exact bytes, with no normalisation: the same drawing written by two people is one material, and whitespace that differs makes a different one. Callers do not compute it. A placeholder Id, used consistently by the material and by the parts that name it, is rewritten to the real digest before validation, by the one binding step both the socket and MCP go through.
What a drawing may contain#
| Key | Default | What it bounds |
|---|---|---|
MaxBytes | 16,384 | The most UTF-8 bytes one drawing may be. |
MaxElements | 512 | The most elements one drawing may contain. |
MaxDepth | 12 | How deeply elements may nest. |
MaxAttributesPerElement | 24 | The most attributes one element may carry. |
MaxAttributeLength | 4,096 | The most characters one attribute value may be. |
SvgMaterial.MaxPerDesign, above, bounds how many of them one design may carry. Each refusal names the element, the attribute and the figure; the byte limit is checked before parsing.
The allowlist#
An allowlist, not a blocklist. A blocklist has to be right about every dangerous construct forever; an allowlist has to be right about the forty that draw a pattern, and everything invented after this was written is refused by default.
Elements. svg, g, defs, title, desc, rect, circle, ellipse, line, polyline, polygon, path, linearGradient, radialGradient, stop, pattern. Anything else: '<name>' is not an allowed element.
Deliberately absent, and why: script and handler execute; foreignObject embeds arbitrary HTML; image and use exist to reference something else, which is a fetch; style carries CSS and with it @import; text needs a font, and naming a font is naming something to load; filter, mask, clipPath and the animation elements are expensive, stateful, or both.
Attributes. id, viewBox, preserveAspectRatio, version, width, height, x, y, x1, y1, x2, y2, cx, cy, r, rx, ry, fx, fy, fr, d, points, transform, fill, fill-opacity, fill-rule, opacity, stroke, stroke-width, stroke-opacity, stroke-linecap, stroke-linejoin, stroke-miterlimit, stroke-dasharray, stroke-dashoffset, shape-rendering, vector-effect, offset, stop-color, stop-opacity, gradientUnits, gradientTransform, spreadMethod, patternUnits, patternContentUnits, patternTransform. Anything else — every on* handler, style, class, href — is '<element>' carries '<name>', which is not an allowed attribute.
Attribute values. None may contain javascript:, data:, http://, https://, //, &#, < or expression(. url(...) may point only at a fragment of the same drawing: url(#name), where the name is letters, digits, _ and -. Anything else is referencing something outside the drawing.
Namespaces. SVG's, or none at all — a drawing written by hand without an xmlns is accepted, because that is the obvious thing to write and it is handed to the browser as SVG regardless. A prefixed namespace declaration (xmlns:xlink) is refused, which is how the old spelling of an external reference goes.
The document. The root must be svg and must carry a viewBox, so the drawing's own coordinates mean the same thing in every renderer. No DOCTYPE, no entity declaration or reference, no processing instruction (<?xml-stylesheet?> is a stylesheet reference with a different syntax), and no text content. Document type definitions are prohibited outright and nothing outside the drawing is ever resolved — which is the whole of the billion-laughs and external-entity defence, rather than a count of expansions.
A drawing that is not XML at all is the drawing is not readable as xml: ..., with the parser's own sentence, not an exception.
Where a pattern cannot be drawn#
/designs previews, the parts table and the PNG renders are flat shading and cannot show a pattern. They show the drawing's swatch instead: the average of its fills and stops, weighted by area where the shape says its area (rect, circle, ellipse) and counted once where it does not, with strokes at 15% of their weight. Read out of the text, never rasterised — the server does not rasterise anything a caller sent it — and the untrusted SVG is never embedded in a page the server serves.
Character proportions#
The shipped Settler, and every figure the world generates for itself, is built from six proportions rather than from measurements. Nothing you send names them — a character you design is parts like any other design — but they are why a generated person always stands up. Every field is clamped, never refused. A figure that asks for a head four times the body gets the widest head this allows and a character that still stands up; refusing outright would make one bad number lose the whole design. A value that is not a number is replaced with the default rather than clamped, because NaN has no nearest bound.
| Field | Default | Range |
|---|---|---|
HeightMeters | 1.8 | 0.6 – 3.0 |
LegFraction | 0.48 | 0.30 – 0.60 |
HeadFraction | 0.13 | 0.08 – 0.25 |
ShoulderFraction | 0.26 | 0.14 – 0.45 |
ArmFraction | 0.36 | 0.20 – 0.50 |
Build | 1.0 | 0.5 – 1.5 |
Clamping is idempotent: clamping a clamped set changes nothing.
Design review thresholds#
The review has no authority and refuses nothing — it is advice, and every check exists because that fault has actually happened here. These are the numbers behind the findings listed in BLUEPRINTS.md.
| Code | Severity | Threshold |
|---|---|---|
vanishing | Fault | Thinner than 0.001 m in some direction, and not a Plane |
enormous | Note | More than 500 m across |
crack | Fault | A gap over 0.002 m and up to 0.06 m, where the two parts present at least 0.05 m² of face to each other and no third part bridges the space |
buried | Note | Entirely inside another part, within 0.002 m |
detached | Fault | A cluster more than 0.01 m from the body of the design. Touching is transitive, so a chimney on a roof on a wall is one thing |
protrudes | Fault | Coming up more than 0.002 m through a part that overhangs it by at least 0.02 m |
headroom | Fault | A part tagged door or gate shorter than 1.9 m, which is how tall a person is |
hovers | Note | The lowest part more than 0.25 m above the design's own origin |
limb_missing | Note | One of the seven required character tags absent from something that is otherwise a character |
The 0.05 m² face requirement is why a settler's arm hanging beside its hip is not reported as a crack: two road slabs that fail to meet share half a square metre, an arm beside a hip a fortieth of one, and a page that cries wolf about eleven parts of a character is a page nobody reads.
Agent memory and history#
| Limit | Value | At the edge |
|---|---|---|
| Memory keys | 64 | Silently ignored. The write does not happen and nothing is said, because the writer may be a language model |
| Memory key length | 64 | Silently ignored |
| Memory value length | 4096 | Silently ignored |
| Goals per agent | 16 | The action is refused with a reason |
| Memory entries shown to another agent | 12, each ≤ 160 characters | Truncated for display |
| Last actions kept | 6 | The oldest are dropped |
| Inbox depth | 32 | Further messages refused |
| Agent search radius | ≤ 8192 m | Clamped |
Writing a key with the value false removes it. Two key names are conventions the shipped brains agree on rather than rules: centre for a work site, and step for where a builder has got to.
Message and action vocabularies#
Client to server#
Discriminator property: "type". An unrecognised value is not ignored — it fails deserialisation, and the server answers error with Malformed message.
type | Required fields | Optional fields |
|---|---|---|
hello | ProtocolVersion | Token, ClientName (default "unknown"), LoginName, DisplayName, ResumeKey |
chunk_subscribe | Chunks | |
chunk_unsubscribe | Chunks | |
action_request | RequestId, Action | |
simulation_control | Speed | |
metrics_request | — | |
blueprint_request | Blueprint | |
blueprint_list_request | — | |
player_list_request | — | |
agent_list_request | — | |
chat_send | Text | Scope (default Public), To |
ProtocolVersion is 1. It is not a setting: a client either speaks it or is told so.
Omitting LoginName gives an anonymous session, which can watch the world, read designs and inspect things, and owns nothing. Anonymous sessions act as the world itself, which is why they cannot claim, build or design.
Actions inside action_request#
Discriminator property: "action", nested inside the Action object. Also fails deserialisation on an unknown value.
action | Required fields | Optional fields |
|---|---|---|
move_entity | Entity, Destination | |
remove_natural | Entity | |
demolish_entity | Entity | WithContents (default false) — destroys what it holds with it, and only its owner may |
inspect_entity | Entity | |
claim_land | Chunks | |
release_land | Chunks | |
program_entity | Entity, ScriptPath | |
spawn_blueprint | Blueprint, Position | |
save_blueprint | Name, EntityType, Visual | Components (allowlisted and bounded — see above), Scripts, Includes |
set_access | To, Permissions | Granting (default true), Entity, At |
set_public_access | Permissions | Entity, At |
request_access | Permissions | Note, Entity, At |
create_agent | Name, Brain, Goals | At, Script |
dismiss_agent | Agent | |
use_entity | Entity | |
transfer_items | From, To, Item, Quantity | |
exchange_items | A, B, Give, GiveQuantity, Take, TakeQuantity | |
start_script | Entity, ScriptPath | |
stop_script | Entity, Pid |
For the three access actions, naming Entity makes it about a thing and omitting it makes it about the claim covering At.
Server to client#
Discriminator property: "type".
type | Carries |
|---|---|
world_bootstrap | The planet, its grid sizes, sea level, the simulation state, the spawn position, and Player (null when anonymous) |
chunk_snapshot | Chunk, DominantBiome, Heightmap, Entities |
entity_delta | Chunk, Updated, Removed |
action_result | RequestId, Success, Error, Entity |
simulation_state | Simulation |
server_metrics | Metrics |
blueprint | One BlueprintDefinition |
blueprint_list | Summaries: Id, Name, Version, Creator, PartCount |
player_list | ClientId, LoginName, ClientName, Position, SubscribedChunks, IsYou |
agent_list | Per agent: Id, Name, Brain, State, ThoughtCount, NextThinkTimeTicks, Body, Home, Position, Destination, Goals, Memory, Reasoning, LastActions |
chat | Scope, From, FromName, Text, At, To, ToName |
error | Message, Detail |
PlayerSnapshot.Token is the one field omitted from JSON when null, and it is sent exactly once — at registration.
HTTP endpoints#
| Method | Route | Query | Answers |
|---|---|---|---|
| GET | /health | — | status, service, version, commit, build, utc |
| GET | /ready | — | {"status":"ready"}, or 503 {"status":"starting"} until the world is loaded |
| GET | /metrics | format=json|html | The metrics snapshot |
| GET | /world | format=json|html | Planet, seed, generator version, grid sizes, clock and speed |
| GET | /events | limit (default 50, clamped 1–500), format=json|html | count, and events newest first |
| GET | /regions | format=json|html | A page of every region anybody has used. Asked for figures, it answers 302 to /api/regions |
| GET | /api/regions | limit (default and maximum 500, clamped 1–500) | regionSizeMeters, count, total, truncated, the gate the answer cost, and a row per region that has something in it. Empty wilderness is not listed |
| GET | /designs | page (default 1, clamped) | The design gallery |
| GET | /designs/{id} | — | One design |
| GET | /designs/{id}.json | — | The machine-readable design document, camelCase |
| GET | /designs/{id}/{view}.png | size=thumb for the small one | A drawing |
| GET | /designs/{id}/visual.json | — | The design as geometry, with its surface drawings. PascalCase, being the socket's own shape |
| POST | /designs/preview | view=iso|front|left|right|top, views=all or a list, size=thumb | A design drawn without being saved. Needs X-World-Player and X-World-Token |
| GET | /docs/{slug} | — | One of these documents |
| GET | /register | — | Registering a person or a model |
| GET | /account | — | The devices that can act as you, your passphrase, and a code for adding one |
| GET | /account.js | — | That page's behaviour |
| GET | /play | — | The browser client |
| GET | /play/{asset} | — | One of the client's assets. A fixed list; anything else is 404 |
| POST | /api/register | — | loginName, optional displayName, kind and passphrase. 201 with the account and its token, 400 on a bad name, kind or passphrase, 409 on a name already registered |
| POST | /api/session | — | loginName, token. 200 {ok, playerId, displayName, passphraseSet}, or 401 {ok, error} for an unknown name, an invalid name or a wrong token alike |
| POST | /api/signin | — | loginName, passphrase, optional deviceName. 200 with a token minted for this device, or 401 |
| POST | /api/passphrase | — | loginName, and either token or currentPassphrase, plus passphrase. 200, 400 with the rule, or 401 |
| POST | /api/devices | — | loginName, token. 200 {ok, loginName, passphraseSet, devices}, or 401 |
| POST | /api/devices/revoke | — | loginName, token, deviceId. 200, 404 for a device this account does not have, 409 for the last one when there is no passphrase, or 401 |
| POST | /api/devices/code | — | loginName, token. 200 {ok, code, expiresAt, expiresInSeconds}, 503 when the world is holding too many, or 401 |
| POST | /api/devices/claim | — | code, optional deviceName. 200 with a token minted for this device, or 401 |
| GET | /api/online | — | players (display name, login name, anonymous, position, region, connected since) and agents (name, owner, state, position) |
| GET | /api/spawn | — | x, y, z and why: where a newcomer should start, and the reason |
| — | /ws | — | The WebSocket. Anything but an upgrade gets 400 |
| POST | /mcp | — | JSON-RPC 2.0. GET and DELETE are 405 |
POST /designs/preview takes a JSON body of exactly the shape world_create_design takes — name, entityType, parts and materials — read by the same reader and refused in the same words, with the same 256 KiB body limit /mcp has. It saves nothing and never touches the world. With Accept: image/png, or with a view in the query string, the answer is a single PNG at 460 pixels (size=thumb gives 190); otherwise it is JSON carrying the design review and the drawn views, each as a data:image/png;base64,… URI. An unusable design is 400 with {"refused": "…"}; a body over the limit is 413.
It asks who is asking. Send X-World-Player and X-World-Token — the login name and token you were given when you registered, the same two headers the MCP tools take — or the answer is 401. Drawing a design is the most expensive thing this server will do for one request, so it is not done for a stranger, and it is rationed per address: too many and the answer is 429.
One view unless you ask for more. The JSON answer draws the isometric alone; ?views=all gives the set, and ?views=front,top gives exactly those. The answer names what it drew in drawnViews and what else there is in availableViews, so a caller that wanted the rest can ask for it.
MCP tools#
Every tool additionally accepts a session string (≤ 256 characters), and every tool's schema sets additionalProperties: false.
| Tool | Required | Optional |
|---|---|---|
world_whoami | — | — |
world_overview | — | — |
world_look | x, z | y (omit to stand on the ground), radiusMeters (1–256, default 64, clamped), type (any valid EntityType, not a fixed list), limit (1–100, default 25, clamped) |
world_route | fromX, fromZ, toX, toZ | fromY (omit to start on the highest surface over the first point), stepMeters (0.25–16, default 1, clamped), climbMeters (0.05–4, default 0.5, clamped), headroomMeters (0.5–8, default 1.9, clamped). At most 2000 steps and 96 chunks; longer is refused |
world_inspect | entityId | — |
world_agents | — | limit (1–200) |
world_agent | agentId | — |
world_history | — | limit (1–200, default 25), kind — a WorldEventKind name, matched case-insensitively |
world_designs | — | search, limit (1–100) |
world_design | designId | — |
world_preview_design | one of designId or parts | name (≤ 64) and entityType (≤ 32), required with parts. See the part shape below |
world_claims | — | mine, limit (1–100) |
world_machine | entityId | logLines (1–100, default 20, clamped). Needs Program on that machine |
world_preview_design is the only tool that answers with more than text. Its result carries the front and top views as image content blocks — {"type": "image", "data": "<base64>", "mimeType": "image/png"} — after the text block, in both protocol revisions, which spell a content block the same way. They are drawn 256 pixels square rather than the gallery's 460, because an MCP result travels through a model's context: two views of a seven-part house encode to about 4.5 KB of base64. Giving both designId and parts is refused, and so is giving neither. Nothing is saved by a preview.
The acting tools below are offered only when Mcp:ReadOnly is false, and are refused if called anyway.
| Tool | Required | Optional |
|---|---|---|
world_join | loginName | displayName, token |
world_take_body | — | x, z |
world_walk | entityId, x, z | — |
world_use | entityId | — |
world_take_items | fromEntityId, toEntityId, item, quantity (1–1,000,000) | — |
world_clear_natural | entityId | — |
world_demolish | entityId | withContents — destroys what it holds with it, and only its owner may say so |
world_claim_land | chunks (each "x,z", ≤ 64 of them) | publicUse |
world_release_land | chunks (each "x,z", ≤ 64 of them) | — |
world_shape_ground | x, z, halfSize (0.5–64 m), and exactly one of height / delta | skirt (0–32 m, default 5; 0 leaves a face rather than a ramp). One cut moves the ground 32 m at most, and a chunk carries 64 pads |
world_release_ground | x, z | halfSize (0.5–64 m, default 8) — every pad whose middle is inside that square is forgotten |
world_create_design | name (≤ 64), entityType (≤ 32), parts (1–512) | See the part shape below |
world_build_design | designId, x, z | y, onGround (default true) |
world_share | permissions (≤ 8 words), and one of entityId / x+z | to (a login name or an owner id), anyone, revoke |
world_ask_access | permissions (≤ 8 words), and one of entityId / x+z | note (≤ 160) |
world_create_agent | name, goals (≤ 16), and one of brain / script | whichever of brain (≤ 64) / script (≤ MaxScriptSizeBytes) it did not use, x, z |
world_dismiss_agent | agentId | — |
world_build_town | x, z | name (default Planner) |
world_say | text (≤ MaxChatMessageLength, 240) | to (≤ 64) — a login name, an agent name, or an identity. Omit for public; local has no meaning here |
world_listen | — | limit (1–100, default 100, clamped) |
world_write_script | entityId, source (≤ MaxScriptSizeBytes) | path (≤ 128, default /main.lua) |
world_run_script | entityId | path (≤ 128, default /main.lua), logLines (1–100, default 20) |
world_stop_script | entityId, pid | — |
world_say and world_listen are refused for an anonymous connection: a line has to come from somebody the world can hold to it. Chat is rate limited per identity at the player rate, not the tighter agent one — an MCP caller is a player. An inbox holds at most 100 unread lines and drops the oldest beyond that, reporting the count as missed; reading consumes what it returns.
The three programming tools and world_machine each need Permission.Program on the named entity, judged by PermissionService exactly as program_entity, start_script and stop_script are over the socket. A written file meets VirtualPath parsing and VirtualFileSystem's MaxVirtualFileSize, MaxVirtualFilesPerComputer and capacity limits; a started script meets MaxLuaInstructionsPerExecution and MaxLuaProcessesPerComputer. Every answer reports those five figures back.
MCP callers identify themselves with the X-World-Player and X-World-Token headers, or by calling world_join. The protocol versions offered are 2026-07-28 (the latest), 2025-11-25 and 2025-06-18.
world_create_agent takes a brain in one of two forms and they are not interchangeable. brain is the name of one this server already carries — city, lua, dummy, as world_overview lists them. script is the Lua source of one the caller wrote, so a new kind of agent is a file rather than a feature. Sending a script makes the brain lua whatever brain said; sending neither is refused with a sentence naming both.
A script is measured, compiled and refused before the agent exists: over MaxScriptSizeBytes in UTF-8 bytes, or Lua that will not load, and there is no agent afterwards. What survives goes to /home/agent/brain.lua on the agent's own machine — an ordinary file, saved to the database with the machine, readable and replaceable afterwards with world_machine and world_write_script — and the answer reports that path back as brainScript. It then runs where every other script in this world runs: LuaSandbox, MaxLuaInstructionsPerExecution, and no second door into the scripting host.
A part in world_create_design#
The same shape is taken by world_preview_design and by POST /designs/preview, read by one reader for all three, so a part that previews is a part that saves.
This is the third spelling of the shape and material vocabularies — after the wire's exact enum names and the agent brain's forgiving one — and the one written for a model rather than for a compiler. Underscores are stripped and the rest is matched case-insensitively, so beveled_cube, BeveledCube and beveledcube are the same shape, and forest_grass is ForestGrass.
| Field | Required | Rule |
|---|---|---|
id | Yes | The PartId rule: lowercase letters, digits and underscore, ≤ 32, starting with a letter |
shape | Yes | A PrimitiveShape, underscores optional. A name that is not one refuses the whole design, naming the part and listing the eight |
parent | No | Another part's id |
x, y, z | No | Offset from the parent's pivot, in metres, in the parent's own frame — so a turned parent swings the child round with it, while its size leaves the child alone. 0 each |
width, height, depth | No | Size in metres, 0.01–128 — the same limits every design is held to. 1 each. Note the names: this tool spells the scale out rather than calling it a scale, because it is a size and not a multiplier |
yaw, pitch, roll | No | How the part is turned, in degrees, −360 to 360. 0 each. yaw is about the up axis, pitch about east, roll about north; they are applied roll, then pitch, then yaw. A part is stored as a quaternion and always has been, and this is the door in: nobody writes one by hand, so this tool takes the three angles and converts them. An angle outside the bounds refuses the design naming the part, the figure and the bound |
material | No | An AssetMaterial, underscores optional. Defaults to White; an unrecognised one refuses the design |
colour | No | #rgb, #rrggbb or #rrggbbaa, ≤ 12 characters. Refuses the design if it is not one |
surface | No | The name of one of this design's materials. A name it does not carry refuses the design and lists the ones it does |
tiling | No | Repeats of the surface across a metre, 0.01–64. 1.0 |
solid | No | false. Only literal true counts |
path | No | For a ribbon only: the line it runs along, as [{ "x": …, "z": … }] in metres in the part's own frame. Two to 64 points, consecutive ones at least 0.01 m apart, each within 64 m of the design's origin. Left out, a ribbon is a straight run of depth metres along north. A path on anything else, or one that is not a list of points, refuses the design |
draped | No | false. For a ribbon only: whether it lies on the ground it crosses, taking its heights from the terrain under its own points rather than holding the shape the design gave it. A design with anything draped in it is neither levelled for nor tilted to the ground |
tag | No | The semantic tag, ≤ 32 |
A field that is not one of these refuses the design, naming the part, the field and the ones it could have been. The same holds for capability and for the entries of materials, scripts, capability.stock and capability.shows: the schema these are published with says no other fields are accepted, and accepting one in silence would mean a design that quietly does less than it was written to do.
world_create_design, world_preview_design and POST /designs/preview also take materials: at most eight { name, svg } objects. The name is what a part's surface refers to; the identity a material really has is a digest of its own bytes, minted by the server, because asking a caller to hash its own SVG would be asking for a way to get it wrong. Two materials under one name refuses the design. The drawings themselves go to the same allowlist described under Surface materials, through the same validator a save runs — so a design that previews is a design that will save.
Refusing outright is the opposite of what the agent-brain reader does with the same two vocabularies, and deliberately so: a tool call can say what was wrong and be called again in the same breath, where an agent's thought cannot.
entityType must satisfy the EntityType rule and is otherwise open — house, lamp, bench and statue are as valid as the seven the world ships with.
Every MCP string argument is trimmed, and a string that is empty after trimming counts as absent rather than as an error — so a required argument given as " " is reported as missing. A control character other than newline or tab is refused outright. Where a tool takes an enum name, it is matched case-insensitively on purpose, so a model does not have to guess the casing.
What an agent brain may write#
A brain returns one table. Everything in it is treated as hostile: an action the reader does not recognise is dropped, a field of the wrong type is ignored, and nothing throws — a brain that returns nonsense gets a decision that does nothing, not a broken server.
| Field | Type | Default |
|---|---|---|
actions | A list of action tables, applied in order | None |
thinkAgainIn | Seconds until the next thought, a positive number | Omit it to go dormant |
remember | A map of key to string. A value of false forgets the key | Nothing written |
completed | A list of goal names now finished | Nothing completed |
reasoning | A line for the log, never parsed | Nothing |
Two caps apply in sequence, and they are different numbers. The reader keeps at most 64 actions from the returned table; the runtime then carries out the first MaxAgentActionsPerThought of those, eight by default. Everything past either line is dropped silently — not refused, and not remembered for next time. world.maxActions tells a brain the live value of the second one, so a well-behaved script can size its own list to match.
The fifteen action kinds#
The whole vocabulary an agent has. It is closed on purpose: a brain that may one day be a language model must not be able to name an operation nobody wrote.
The kind string is matched case-sensitively. "Move_Entity" is not move_entity; it matches nothing and the action is dropped.
kind | Required | Optional, with defaults |
|---|---|---|
observe_world | — | radius (64), type |
inspect_entity | entity | — |
claim_land | a position | publicUse (false) |
release_land | a position | — |
create_blueprint | name, parts | See the design sub-schema below |
spawn_blueprint | blueprint, a position | — |
write_file | computer | path (""), contents ("") |
start_script | computer | path ("") |
stop_script | computer | pid (0) |
move_entity | entity, a position | — |
take_body | — | blueprint, or design as an alias. Omit both for the shipped character |
set_access | to | permissions, revoke (false), entity, a position |
set_public_access | — | permissions, entity, a position |
request_access | — | permissions, note, entity, a position |
send_message | target | message ("") |
wait | — | reason |
An action whose required field is missing or unparsable is dropped whole.
Positions may be written either as at = { x, y, z } or as x, y, z on the action table itself. Both x and z are needed for a position to resolve at all; y defaults to 0. For spawn_blueprint, leaving the height out is meaningful: no height named means "stand it on the ground", and naming one means exactly that height.
For the three access actions, the subject is inferred rather than named: entity present makes it about a thing, and its absence makes it about the claim covering the position.
The design a create_blueprint describes#
| Field | Required | Default and behaviour at the edge |
|---|---|---|
name | Yes | Non-blank |
parts | Yes | Non-empty after unusable parts are dropped |
entityType | No | "item". Must satisfy the EntityType rule |
key | No | The name. Used to derive a deterministic id, so re-running a brain does not accumulate designs |
materials | No | Empty. A list of { name, svg } tables, at most eight. The name is what a part's surface refers to; the identity is minted from the drawing's own bytes, the same way the other two doors mint it |
components, scripts, includes | No | Empty. components is held to the allowlist, so a design that asks for ownership or a walk speed of 10,000 is refused rather than saved |
Per part:
| Field | Required | Default and behaviour at the edge |
|---|---|---|
id | Yes | Must satisfy the PartId rule, or the part is dropped |
primitive | No | Cube. Matched case-insensitively against PrimitiveShape; an unrecognised name falls back to Cube rather than failing the part |
material | No | White. Same rule, falling back to White |
parent | No | None, making it a root part |
position, scale | No | {0,0,0} and {1,1,1} |
colour or color | No | None. A hex string, or {r,g,b,a} / {red,green,blue,alpha} |
tag | No | None. Becomes the semantic tag |
solid | No | false |
surface | No | None. The name of one of this design's materials. A name the design does not carry — or one two drawings share, which resolves to neither — leaves the part naming a surface that is not there, and the validator refuses the design saying which part asked |
tiling | No | 1.0. Repeats of the surface across a metre, 0.01–64. Ignored on a part with no surface |
path, draped | No | None and false. Ribbons only, at most 64 points |
The silent fallbacks are deliberate: a design with one misspelled material is worth building with the wrong colour, and refusing the whole thing would make one bad word lose the design. The validator still has the final say on everything else.
A surface is the one field a part refuses the design over rather than falling back, and deliberately: a fallback would be a wall silently painted with nothing, which looks like a design that worked. The drawing itself is never checked here — it reaches the catalogue exactly as the script wrote it and is held to the allowlist, the size bounds and the element bounds by the same validator a browser's and a model's design meet, in the same words.
What a brain reads#
The world table passed to think(world) is built fresh each thought, from what the world knew at that instant. It is not the world global a computer script sees — a brain has no globals at all.
| Field | What it is |
|---|---|
now | Simulated seconds |
trigger | An AgentTrigger name, PascalCase: Scheduled, Created, … |
position, chunk | {x, y, z}, and the chunk's name |
chunkIsClaimed, chunkIsMine, ownedClaims | The ground under it, and how many chunks it holds |
name, home, thoughts | Its name, its machine's entity id, its thought count |
body | {id, design, position, speed, walking, destination}, or nil while it has none. destination is present only while walking |
memory | Its durable store, as a table of strings |
goals | A list of {name, done} |
nearby, owned | Lists of {id, type, owner, mine, distance, position} |
agents | Peers: {id, name, owner, brain, state, body, position, distance, goals, me}, plus step and doing when it has published them |
blueprints | {id, name, version, mine} |
files, runningScripts, inbox | Its own machine's filesystem, processes and waiting messages |
lastResults | {kind, ok, error, detail} per action of the previous thought. error and detail are the empty string, not nil, when absent |
maxActions | The live MaxAgentActionsPerThought |
lastResults is the feedback loop. An agent that keeps being refused the same thing is a stuck agent, and this is where it finds out.
The Lua vocabulary#
What a program running on a machine in the world can call. Failures follow the Lua convention and return nil, "reason" rather than raising, because a missing file should not end a program. Full prose is in LUA_API.md; this is the value reference.
| Global | Functions |
|---|---|
world | self, position, inspect, query, agents, move, moveTo, create, use, transfer, exchange, inventory, destroy, claim, hasPermission, send, receive, say, sayNearby, tell, part, setPart, ask, carry |
computer | readFile, writeFile, deleteFile, listFiles, fileExists, makeDirectory, log, time, startProcess, stopProcess |
schedule | after, every |
sensor | scan |
events | on |
Query-shaped calls take an options table:
| Call | Options | Defaults and clamps |
|---|---|---|
world.query(options) | type, radius, limit | radius 32 and must be finite and positive; limit 16, clamped to 1–MaxQueryResults |
world.agents(options) | radius, limit | radius 0, meaning everywhere; limit 16, clamped the same way |
sensor.scan(options) | radius, type, or a bare number meaning the radius | Range and result count come from the machine's own sensor component. A script cannot see further by asking for more |
schedule.after(seconds, fn) and schedule.every(seconds, fn) refuse a non-function with schedule expects a function, a negative or non-finite delay with a delay must be a non-negative number of seconds, and anything past MaxScheduledEventsPerScript with this script already has 32 scheduled callbacks, which is the limit. every re-arms at no less than 0.001 s, and stops silently when its process dies.
events.on(name, fn) takes any non-blank name; there is no closed set, and 'chat' is a convention the shipped scripts follow rather than a rule. A blank name is an event needs a name. Three names the world itself raises: chat (scope, who, text, id), use (who, what) and answer (who, option, values, promptId).
world.ask(who, question, answers[, values]) is the world asking rather than being asked.
| Argument | Accepted |
|---|---|
who | An owner id — player:… or agent:… — as the use and answer events hand it to you. A name is not accepted here; a question goes to an identity |
question | Text, cleaned as a chat line is, at most MaxPromptQuestionLength. < or > anywhere in it refuses the whole question |
answers | A list, at most MaxPromptOptions. Each entry is either a plain string — the button and the name are then the same word — or {name = …, label = …}. name is at most 32 characters of letters, digits, _, - and .; label is at most MaxPromptOptionLength. At least one, or nobody can answer |
values | Optional. A list of at most MaxPromptFields tables of {name, label, kind, hint}. kind is number or anything else, meaning text — Permissions is the server's own and is refused to a script |
It answers with the question's id as a string, or nil, reason. The commonest reasons are they are not dealing with you… (nobody may be asked out of the blue), … is not standing within 8 m of this, and the bounds above.
world.carry(who) takes somebody who has just answered you wherever this machine is going, and answers with the travel time in seconds or nil, reason. There is no destination argument and there cannot be one: a thing may bring you to itself and nowhere else. It is spent by being used, so a second journey needs a second answer, and … has not agreed to go anywhere with this is what an unspent one says.
String constants a Lua caller must spell exactly#
| Where | Accepted spellings |
|---|---|
world.hasPermission(id, permission) | The full Permission enum by name, case-insensitively: inspect, use, build, modify, destroy, program, transfer, control, handle, all, none. .NET's flag syntax works too, so "Build,Modify" is one query — and so does a bare number, because nothing excludes it |
An agent action's permissions | Only the seven grantable words, case-insensitively and trimmed: inspect, use, build, modify, destroy, program, handle. A single string or a list of them. transfer, control and all are not accepted, nor is a number; anything unrecognised contributes nothing, and a list yielding None is refused by the server as a grant that says nothing |
A part's primitive / material | Any PrimitiveShape / AssetMaterial name, case-insensitively, falling back to Cube / White |
| A colour | #rgb, #rrggbb, #rrggbbaa, with or without the #. Anything else parses as nothing |
world.setPart's change table | solid, visible, colour, axis, turn, turnRate, turnTo, slide, slideRate, slideTo, reset. Any field omitted means "leave it" |
world.setReadout's change table | kind (bar, text), source (given, health, energy, growth), label, text, colour, value, rate, limit, max, order, clear. An omitted field means "leave it"; label = '' and text = '' mean "take it off" |
The two permission vocabularies are genuinely different, and the narrower one is the deliberate half: the grantable set is seven permissions, so the agent action set has seven words. world.hasPermission only asks a question and can therefore afford to name anything.
Chat scope is not a string in Lua at all. It is chosen by which function is called: world.say is Public, world.sayNearby is Local, and world.tell(who, text) is Direct. All three go through the same rate limiter, and a script speaks at the agent rate — six a minute, burst of two — not a person's.
A chat line is cleaned before it is measured: Unicode format characters are removed, every control character and every run of whitespace becomes a single space, and leading and trailing space is dropped. The 240-character limit applies to what is left, so That is N characters; 240 is the most one message may be. counts the cleaned text and not what was typed.
Where the prose is#
This page is the values. What they mean, and why the world is arranged this way, is elsewhere:
- The world — time, people, ownership, land and permissions.
- Protocol — each message in context, and a worked session.
- Blueprints — how a design describes a thing, with examples.
- Lua API — what a program running inside the world can do.
- AI agents — how a brain is written, and what one is for.
- MCP — connecting a model to a running world.