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.
| Key | Type | Values / notes |
|---|---|---|
gamehoster-game-title | string | Display name. |
gamehoster-game-browse | string | none (placed straight in) or instances (pick a live instance, or create one). |
gamehoster-game-create | string | none / public / password / private: whether players may create their own instance, and how it is listed. |
gamehoster-game-tickRate | number | The simulation rate: logic steps per second (dt = 1/tickRate). Identical on the server and every client. |
gamehoster-game-sendRate | number | The 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-displayDelay | number | Optional. 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-timelineTicks | number | How 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-capacity | number | The most players one instance holds. |
gamehoster-game-origins | string / array | Optional. 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.
| Function | What 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 key | Type | Values / notes |
|---|---|---|
gamehoster-player-join-schema-name | string | The field's name. |
gamehoster-player-join-schema-type | string | Required: a type (number, int, bool, string, ref, vec2, vec3, list, map). |
gamehoster-player-join-schema-default | optional | A 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.
| Function | What it does |
|---|---|
Gamehoster.Context.playerId | The 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 key | Type | Values / notes |
|---|---|---|
type | string | Required: number, int, bool, string, ref, vec2, vec3, list, map. |
sync | string | Required: 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). |
audience | string | Who 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. |
smooth | bool | Optional, render interpolation only, default false. Set true on continuous quantities (positions); leave it off for discrete ones (scores, phases, ids). |
default | — | Value on create. |
to | string | Required 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.
| Function | What it does |
|---|---|
Gamehoster.Context.playerId | The 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.
| Function | What it does |
|---|---|
Gamehoster.Context.playerId | The 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.
| Function | What it does |
|---|---|
Gamehoster.Context.playerId | This 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.
| Function | What it does |
|---|---|
Gamehoster.Context.playerId | This 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 key | Type | Values / notes |
|---|---|---|
type | string | Required: number, int, bool, string, ref, vec2, vec3, list, map. |
sync | string | Required: server / sent / shared; see the player schema. |
audience | string | owner / all; omitted when sync is server. |
smooth | bool | Optional render-interpolation flag; see the player schema. |
default | — | Value on create. |
to | string | Required 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.
| Function | What 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.
| Function | What 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 key | Type | Values / notes |
|---|---|---|
type | string | Required: a type. |
to | string | Required 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"] }
moveY manifest: it moves only the sender's paddlewrites 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.
| Key | Type | Values / notes |
|---|---|---|
writes | array | Dotted 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.
| Function | What it does |
|---|---|
Gamehoster.Command.Get(param) | A value from the command schema. |
Gamehoster.Context.playerId | The 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 key | Type | Values / notes |
|---|---|---|
type | string | Required: number, int, bool, string, ref, vec2, vec3, list, map. |
sync | string | Required: server / sent / shared; see the player schema. |
audience | string | owner / all; omitted when sync is server. |
smooth | bool | Optional render-interpolation flag; see the player schema. |
default | — | Value on create. |
to | string | Required 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.
| Function | What it does |
|---|---|
Gamehoster.Context.entityId | This 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.
| Function | What it does |
|---|---|
Gamehoster.Context.entityId | This 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
-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.