Files

Every file in a game definition, with the keys it holds or the library functions it may call. A game is a single directory. At its top level sit the game config and its startup script, plus the gamehoster-commands/ and gamehoster-entities/ folders; the two player schemas and their scripts live in a gamehoster-player/ folder, and the instance schema and its two update files in a gamehoster-instance/ folder. Names prefixed gamehoster- are fixed structure; the names you choose (fields, commands, entity types) are unprefixed. JSON files list their keys; JS files are raw function bodies and list the Gamehoster functions callable inside them. Every JS body may also call Gamehoster.Time() (ms), Gamehoster.Tick() (int) and Gamehoster.Random(), a predictable float in [0,1), seeded by the current tick and context so it returns the same value on server and client and is safe inside shared logic. Backend-only bodies additionally get Gamehoster.Entropy(), a true, unpredictable [0,1) for authoritative decisions; the shared bodies that also run on the client (the -update-frontend.js files and the command handlers) do not, since predicted state must stay deterministic. The usage guide explains them in context.

gamehoster-game.json

Game-wide configuration: title, how players browse and create, the two clocks, and capacity. See Usage → Games.

KeyTypeValues / notes
gamehoster-game-titlestringDisplay name.
gamehoster-game-browsestringnone (placed straight in) or instances (pick a live instance, or create one).
gamehoster-game-createstringnone / public / password / private: whether players may create their own instance, and how it is listed.
gamehoster-game-tickRatenumberThe simulation rate: logic steps per second (dt = 1/tickRate). Identical on the server and every client.
gamehoster-game-sendRatenumberThe network rate: how often the server emits a per-player packet. Much lower than the tick rate (e.g. tick 60, send 20). Rendering has no rate: the front end runs its own frame loop and reads an interpolated view.
gamehoster-game-displayDelaynumberOptional. How far behind the present, in ticks, the front end draws the world, so it eases toward known state rather than guessing past it. Your own player is always drawn at the present whatever this is. A larger value favours accuracy where motion is hard to predict; a value near 0 favours immediacy where motion is smooth. Default is a small delay that covers ordinary network jitter.
gamehoster-game-timelineTicksnumberHow many ticks of history the timeline ring keeps. It bounds how far back a late command can be placed on its exact tick, so it is chosen to cover the worst round trip the game expects to serve.
gamehoster-game-capacitynumberThe most players one instance holds.
gamehoster-game-originsstring / arrayOptional. The web origin(s) allowed to open this game's socket, usually the one site that embeds it ("https://example.com"), or a list. "*.example.com" matches any subdomain; "*", or omitting the key, allows any origin. Requests with no Origin (non-browser clients) are always allowed. The game is served at wss://<domain>/<game> regardless; this only gates which pages may connect.

gamehoster-game-startup.js

An optional script that runs once, on the server, when the game first starts up, before any player connects. Use it for one-time setup, such as creating a set of standing instances to drop into. See Usage → Games.

FunctionWhat it does
Gamehoster.Instance.List()The ids of the live instances.
Gamehoster.Instance.Info(id){ players, capacity, … } for an instance.
Gamehoster.Instance.State.Get(id, name)Read an instance's state field.
Gamehoster.Instance.Create()Create a new instance, returns its id.

gamehoster-player-join-schema.json

What a player provides on connect, fixed for the whole session. An array of field objects, each with fully-qualified keys and no sync/audience (join data is fixed, never streamed state). See Usage → Players.

Entry keyTypeValues / notes
gamehoster-player-join-schema-namestringThe field's name.
gamehoster-player-join-schema-typestringRequired: a type (number, int, bool, string, ref, vec2, vec3, list, map).
gamehoster-player-join-schema-defaultoptionalA default value; omit it to force the player to supply one.

gamehoster-player-join.js

Runs on the server the moment a player connects, and does two jobs: it routes the player into an instance (picking a live one that still has room or creating a fresh one) and then sets them up, filling their per-instance state from their fixed join data. Gamehoster.Context.playerId is the joining player; once they have joined, Gamehoster.Context.instanceId names their instance. See Usage → Players.

FunctionWhat it does
Gamehoster.Context.playerIdThe joining player's id.
Gamehoster.Join.Get(name)A join-schema value the player supplied.
Gamehoster.Instance.List()Ids of the live instances, to route between.
Gamehoster.Instance.Info(id){ players, capacity, … } for an instance, to choose between them.
Gamehoster.Instance.State.Get(id, name)Read an instance's state field, e.g. to filter by phase.
Gamehoster.Instance.Join(id)Place this player into that instance.
Gamehoster.Instance.Create()Make a fresh instance, returns its id.
Gamehoster.Player.List() · Gamehoster.Player.State.Get(id, name)Read the other players in the instance.
Gamehoster.Player.State.Set(Gamehoster.Context.playerId, name, value)Fill the joining player's per-instance state.

gamehoster-player-schema.json

The player's per-instance state (the fields they carry inside an instance) in the compact, typed form: a map keyed by field name, each entry an object { type, sync, [audience], [smooth], [default] }. See Usage → Players. The instance and entity schemas share this exact shape. Each field declares sync (how a client obtains the value) and, unless it is server-only, an audience (who may receive it), plus an optional smooth.

Entry keyTypeValues / notes
typestringRequired: number, int, bool, string, ref, vec2, vec3, list, map.
syncstringRequired: how the client obtains the value. server (computed and kept on the backend, never sent) / sent (computed on the backend and pushed to clients that may see it; the client treats it as truth and snaps) / shared (computed on both sides by running the same commands, reproduced from the relayed commands, reconciled by the occasional keyframe).
audiencestringWho may receive it: owner (only the object's owner) or all (everyone the object is currently relevant to). Ignored (and omitted) when sync is server.
smoothboolOptional, render interpolation only, default false. Set true on continuous quantities (positions); leave it off for discrete ones (scores, phases, ids).
defaultValue on create.
tostringRequired for ref: the target type.

gamehoster-player-leave.js

Backend: cleanup when a player leaves their instance. Gamehoster.Context.playerId is the leaving player and Gamehoster.Context.instanceId their instance. See Usage → Players.

FunctionWhat it does
Gamehoster.Context.playerIdThe leaving player's id.
Gamehoster.Instance.State.Get(Gamehoster.Context.instanceId, name) / .Set(Gamehoster.Context.instanceId, name, value)Read / write the instance's own state.
Gamehoster.Player.List() · Gamehoster.Player.State.Get(id, name) / .Set(id, name, value)Read or write the players still in the instance.
Gamehoster.Entity.List(type) · Gamehoster.Entity.State.Get(id, name)Read entities.
Gamehoster.Entity.Destroy(id)Remove an entity they owned.

gamehoster-player-view.js

Optional: per-viewer relevance (fog of war). It runs on the backend at the send rate, viewer-centric (Gamehoster.Context.playerId is the viewer), and marks which players and entities are relevant to that viewer with Gamehoster.View.Include(id). Relevance is the outer gate: a thing you are not shown sends you none of its fields, and one becoming relevant arrives as an enter (leaving, as a leave), so include a thing slightly before it is needed to pre-fetch it. Omit this file and everything is relevant to everyone: full visibility. Large data (maps, level geometry) is modelled as entities so it streams once on enter, not per tick. See Usage → Players.

FunctionWhat it does
Gamehoster.Context.playerIdThe viewer whose relevance set is being built.
Gamehoster.View.Include(id)Mark a player or entity relevant to this viewer (also the pre-fetch hook for soon-relevant things).
Gamehoster.Player.List() · Gamehoster.Player.State.Get(id, name)Read players, to decide who is relevant.
Gamehoster.Entity.List(type) · Gamehoster.Entity.State.Get(id, name)Read entities, to decide what is relevant.
Gamehoster.Instance.State.Get(Gamehoster.Context.instanceId, name)Read the instance's own state.

gamehoster-player-update-backend.js

The player's own per-tick update: the optional, backend-only private extra, run after the shared gamehoster-player-update-frontend.js (which the backend runs first). It writes only its own state, but unlike the shared file it may reach that player's server and sent fields and make discrete authoritative writes, and it may call Gamehoster.Entropy(). Gamehoster.Context.playerId is the player and Gamehoster.Context.instanceId the instance. Omit it entirely for an all-shared player. See Usage → Updates.

FunctionWhat it does
Gamehoster.Context.playerIdThis player's id.
Gamehoster.Player.State.Get(Gamehoster.Context.playerId, name) / .Set(Gamehoster.Context.playerId, name, value)Read / write own state only.
Gamehoster.Instance.State.Get(Gamehoster.Context.instanceId, name)Read the instance's own state.
Gamehoster.Player.List() · Gamehoster.Player.State.Get(id, name)Read other players.
Gamehoster.Entity.List(type) · Gamehoster.Entity.State.Get(id, name)Read entities.

gamehoster-player-update-frontend.js

The player's own per-tick update: the shared body, run on both sides (the client and the backend, which runs it first). It predicts where it can: it may read the shared and sent fields of relevant objects plus its own owner fields, and may .Set only its own shared / owner fields; it never reads server fields. It may call Gamehoster.Random() (predictable) but not Gamehoster.Entropy(), since predicted state must stay deterministic. Gamehoster.Context.playerId is the player and Gamehoster.Context.instanceId the instance. Optional: omit it for an all-private player that lives only in gamehoster-player-update-backend.js. See Usage → Updates.

FunctionWhat it does
Gamehoster.Context.playerIdThis player's id.
Gamehoster.Player.State.Get(Gamehoster.Context.playerId, name) / .Set(Gamehoster.Context.playerId, name, value)Read / write own shared and owner fields.
Gamehoster.Instance.State.Get(Gamehoster.Context.instanceId, name)Read a shared / sent instance state field.
Gamehoster.Player.List() · Gamehoster.Player.State.Get(id, name)Read other relevant players' shared / sent state.
Gamehoster.Entity.List(type) · Gamehoster.Entity.State.Get(id, name)Read relevant entities' shared / sent state.

gamehoster-instance-schema.json

The instance's own state, the fields that describe the whole room rather than any one player or entity: the phase (waiting / playing / over), the winner, and the fixed tuning constants that shape a match. Same compact form as the player schema: a map keyed by field name, each entry { type, sync, [audience], [smooth], [default] }. Instance state stays small and always relevant (phase, score, constants); it is never gated by view. See Usage → Instances.

Entry keyTypeValues / notes
typestringRequired: number, int, bool, string, ref, vec2, vec3, list, map.
syncstringRequired: server / sent / shared; see the player schema.
audiencestringowner / all; omitted when sync is server.
smoothboolOptional render-interpolation flag; see the player schema.
defaultValue on create.
tostringRequired for ref: the target type.

gamehoster-instance-update-backend.js

The instance update's optional, backend-only private extra: step 3 of the tick, run after the shared gamehoster-instance-update-frontend.js (which the backend runs first). The one place for global, authoritative change: the phase machine (waiting → playing → over), scoring, the serve, cross-cutting logic, and spawning or destroying entities. It may write server / sent fields and call Gamehoster.Entropy(). Gamehoster.Context.instanceId is the instance; there is no current player here. See Usage → Updates.

FunctionWhat it does
Gamehoster.Instance.State.Get(Gamehoster.Context.instanceId, name) / .Set(Gamehoster.Context.instanceId, name, value)Read / write the instance's own state.
Gamehoster.Player.List()Ids of players in this instance.
Gamehoster.Player.State.Get(id, name) / .Set(id, name, value)Read / write any player's state.
Gamehoster.Entity.List(type)Ids of entities of a type.
Gamehoster.Entity.State.Get(id, name) / .Set(id, name, value)Read / write any entity's state.
Gamehoster.Entity.Spawn(type, state)Create an entity, returns its id.
Gamehoster.Entity.Destroy(id)Remove an entity.

gamehoster-instance-update-frontend.js

The instance update's shared body, run on both sides (the client and the backend, which runs it first): the predictable part, reading shared / sent fields and writing only shared ones. No spawn or destroy; scoring, the serve and the phase machine are backend-only and arrive as sent state. May call Gamehoster.Random() but not Gamehoster.Entropy(). Gamehoster.Context.instanceId is the instance. Optional, like its backend counterpart: a slot may have either file, both, or neither. See Usage → Updates.

FunctionWhat it does
Gamehoster.Instance.State.Get(Gamehoster.Context.instanceId, name) / .Set(Gamehoster.Context.instanceId, name, value)Read the instance's shared / sent state; .Set applies to shared fields only.
Gamehoster.Player.List() · Gamehoster.Player.State.Get(id, name)Read players' shared / sent state.
Gamehoster.Entity.List(type)Ids of entities of a type.
Gamehoster.Entity.State.Get(id, name) / .Set(id, name, value)Read / write shared entity fields.

gamehoster-commands/<name>/gamehoster-command-schema.json

One directory per command under gamehoster-commands/: holding this parameter schema, the gamehoster-command.json writes manifest, and the handler. The schema is the command's input parameters, keyed by name: each entry is { type } (for example an axis number, or a { from, to } pair). Command parameters carry no sync/audience; they are transient input, not stored state. An empty object is a bare trigger with no parameters. See Usage → Commands.

Entry keyTypeValues / notes
typestringRequired: a type.
tostringRequired for ref: the target type.

gamehoster-commands/<name>/gamehoster-command.json

The command's declarative dependency manifest, one key, writes: the list of state fields the handler may change, as dotted paths player.<field> (the sender's own state) or instance.<field>. A command whose only effect is a server-side spawn or authoritative change writes [].

{ "writes": ["player.y"] }
pong's moveY manifest: it moves only the sender's paddle

writes feeds the deploy-time predictability check and nothing else. A field's sync is fixed in its schema, the same for every viewer; it never splits shared-vs-sent per player at runtime. From writes the engine proves, once, that each shared field is reproducible on every client that can see it (its inputs owner-driven and in view); a field that could depend on out-of-view state must be sent, and the check rejects the definition until it is. See Usage → Commands.

KeyTypeValues / notes
writesarrayDotted field paths the handler may write: player.<field> or instance.<field>. Empty for spawn-only / server-authoritative commands.

gamehoster-commands/<name>/gamehoster-command-handler-frontend.js

The shared handler. Applies the command on both sides. Gamehoster.Context.playerId is the sending player and Gamehoster.Context.instanceId their instance. It writes only the sender's shared and owner fields, and only the ones the command writes, so every client reproduces the same result. A command that isn't allowed right now is simply ignored: the handler reads the sender's state and does nothing when it doesn't permit the action. Being deterministic it has Gamehoster.Random() but not Gamehoster.Entropy(). See Usage → Commands.

FunctionWhat it does
Gamehoster.Command.Get(param)A value from the command schema.
Gamehoster.Context.playerIdThe sending player's id.
Gamehoster.Player.State.Get(Gamehoster.Context.playerId, name) / .Set(Gamehoster.Context.playerId, name, value)Read / write the sender's own state.
Gamehoster.Instance.State.Get(Gamehoster.Context.instanceId, name) / .Set(Gamehoster.Context.instanceId, name, value)Read / write the shared instance state.
Gamehoster.Player.List() · Gamehoster.Player.State.Get(id, name)Read other players.
Gamehoster.Entity.List(type) · Gamehoster.Entity.State.Get(id, name)Read entities.

gamehoster-commands/<name>/gamehoster-command-handler-backend.js

Optional. Runs on the server only, right after the shared handler, for a command with a private or authoritative effect: a hidden roll, an entity spawn, a write to a sent or server field. It reaches the world through the same Gamehoster.* as the shared handler, with the sender in Gamehoster.Context, and being server-only it may write everything and use Gamehoster.Entropy(). Most commands have none. See Usage → Commands.

gamehoster-entities/<type>/gamehoster-entity-schema.json

One directory per entity type under gamehoster-entities/. The schema is the entity's own state, in the same compact form as the player and instance schemas: a map keyed by field name, each entry { type, sync, [audience], [smooth], [default] }. Entities are the unit of relevance, so large streamed data (maps, geometry, assets) is modelled as an entity and sent once on enter. See Usage → Entities.

Entry keyTypeValues / notes
typestringRequired: number, int, bool, string, ref, vec2, vec3, list, map.
syncstringRequired: server / sent / shared; see the player schema.
audiencestringowner / all; omitted when sync is server.
smoothboolOptional render-interpolation flag; see the player schema.
defaultValue on create.
tostringRequired for ref: the target type.

gamehoster-entities/<type>/gamehoster-entity-update-backend.js

The entity update's optional, backend-only private extra: step 2 of the tick, run after the shared gamehoster-entity-update-frontend.js (which the backend runs first). It writes only its own state, but may reach that entity's server / sent fields and call Gamehoster.Entropy(). Gamehoster.Context.entityId is the entity and Gamehoster.Context.instanceId its instance. Omit it for an all-shared entity, as pong's ball does, keeping only the shared file. See Usage → Updates.

FunctionWhat it does
Gamehoster.Context.entityIdThis entity's id.
Gamehoster.Entity.State.Get(Gamehoster.Context.entityId, name) / .Set(Gamehoster.Context.entityId, name, value)Read / write own state only.
Gamehoster.Instance.State.Get(Gamehoster.Context.instanceId, name)Read the instance's own state.
Gamehoster.Player.List() · Gamehoster.Player.State.Get(id, name)Read players.
Gamehoster.Entity.List(type) · Gamehoster.Entity.State.Get(id, name)Read other entities.

gamehoster-entities/<type>/gamehoster-entity-update-frontend.js

The entity update's shared body, run on both sides (the client and the backend, which runs it first), the predictable part: reads shared / sent fields, writes its own shared fields only. May call Gamehoster.Random() but not Gamehoster.Entropy(). Gamehoster.Context.entityId is the entity and Gamehoster.Context.instanceId its instance. Optional: pong's ball is one integration step and keeps only this file; an all-private entity keeps only gamehoster-entity-update-backend.js. See Usage → Updates.

FunctionWhat it does
Gamehoster.Context.entityIdThis entity's id.
Gamehoster.Entity.State.Get(Gamehoster.Context.entityId, name) / .Set(Gamehoster.Context.entityId, name, value)Read / write own shared fields only.
Gamehoster.Instance.State.Get(Gamehoster.Context.instanceId, name)Read a shared / sent instance state field.
Gamehoster.Player.List() · Gamehoster.Player.State.Get(id, name)Read players' shared / sent state.
Gamehoster.Entity.List(type) · Gamehoster.Entity.State.Get(id, name)Read other entities' shared / sent state.

What ships to the client

The generated client contains only the front-end JS and the fields a player may see: the -update-frontend.js bodies, the front-end effect of each command handler, and the sent / shared fields of the objects relevant to that player. Server-only logic (the -update-backend.js bodies, the server half of command handling, and every sync: "server" field) is never sent, so a secret is provably absent from the client, not merely hidden. The results of server-only logic reach a client only by being written into a sent field or a spawn.