Projects, state and actions
A project is a set of definitions. A definition describes a type of Gizmo. A placed instance combines one definition with its own identity and validated state.
Definitions and instances
| Concept | Example | Responsibility |
|---|---|---|
| Project | A collection of game pieces | Atomic installation of up to 16 definitions |
| Definition | my-game.counter, version 1 | Initial state, validator, actions and presentation |
| Instance | One counter at a world cell | Independent saved state and host-owned identity |
Types are namespaced identifiers, not display labels. A state version is a positive integer. Types and emojis must be unique within a project. Changing a label is different from changing a type or state version.
Initial state and validation
initial() creates state for a new placement. valid(value) must verify every field and bound. State must be JSON data: no functions, class instances, non-finite numbers, or circular references.
ts
type State = { count: number };
const valid = (value: unknown): value is State =>
record(value) &&
exactKeys(value, ['count']) &&
Number.isSafeInteger(value.count) &&
Number(value.count) >= 0 && Number(value.count) <= 9999;1
2
3
4
5
6
7
2
3
4
5
6
7
Import record and exactKeys from @scape/sdk. They help check shape; still check required fields and ranges. The Counter source shows a complete definition.
Actions
An action declares permission and returns new state. Reducers are synchronous and side-effect-free. Use the supplied context for time, actor identity and randomness; do not read the environment or call external services.
ts
increment: {
permission: 'participant',
run: (state, payload) => {
requirePayload(payload, []);
if (state.count >= 9999) {
throw new ObjectActionError(409, 'Reset before adding more.');
}
return { count: state.count + 1 };
},
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
requirePayload rejects undeclared keys. It does not validate the types or values of required fields; do that in the reducer. Invalid resulting state is rejected by the runtime.
Permissions
participant: a permitted participant can invoke the action.editor: the host must grant editing permission for that instance.remover: the host must grant removal permission for the source.
A disabled button helps the interface but does not enforce authorization. The server checks the action independently. In local tests, you supply context; that does not prove real-world access.
State versus configuration
State belongs to a live instance. Portable configuration copies only declared editor settings for a new placement. It never copies identity, ownership, counters, or topology metadata automatically.
ObjectInstance.linkId is host-managed grouping metadata outside authored state. Treat it as opaque. See movement and travel.
Updates
The entire project updates atomically. Existing placed state must validate under the new definitions. There is no automatic version migration: remove affected instances before changing identity, version, fixed collision behavior, or removing a definition.
