Skip to content

Entity schemas

Entity schemas describe what data each entity type carries and how it participates in network sync. They live as one .yaml file per entity in the directory set by entity_schema in golem.yaml (default schemas/entities/).

entity: Player # PascalCase type name in your game
vars:
health: { tag: 1, type: double, sync: tick }
display_name: { tag: 2, type: string, sync: once }
  • entity: The type name; generated code uses it in type names (e.g. SyncedPlayer for Player in go-server).
  • vars: Snake_case keys; each must carry an explicit tag (see below) so the wire format stays stable when you rename or reorder variables.

Every entity automatically has position fields. You do not declare them in YAML; they are injected by the framework and synchronized like tick fields.

In the default 2D mode, generated entities have pos_x and pos_y. The generated Go server struct exposes Position() (float32, float32) and SetPosition(x, y float32), while JS, Go, and C# clients expose posX / posY, PosX() / PosY(), and PosX / PosY.

In 3D mode (simulation.dimensions: 3 in golem.yaml), generated entities also have pos_z. Go server code uses Position3D() (float32, float32, float32) and SetPosition3D(x, y, z float32) for full 3D movement. Position() still returns the XY projection so existing interest management can keep using circular fields of interest. JS clients expose posZ, Go clients expose PosZ(), and C# / Unity clients expose PosZ.

The names pos_x, pos_y, and in 3D projects pos_z, are reserved—using them as var names in your schema results in an error.

Every variable must have a tag — a positive integer that maps to a stable Protobuf field number in the serialized wire format. Tags must be unique within an entity:

vars:
health: { tag: 1, type: double, sync: tick } # required; must be unique within this entity
display_name: { tag: 2, type: string, sync: once }

You can number tags freely starting from 1. You can leave gaps, and you can renumber them only if you discard all persisted snapshots at the same time (the schema fingerprint will change). Missing or duplicate tags are a bake error.

If an entity should be visible to every client regardless of interest management distance, set global: true:

entity: Scoreboard
global: true
vars:
score_a: { tag: 1, type: int32 }
score_b: { tag: 2, type: int32 }

Global entities still have a position, but it is not used for FOI visibility checks. When interest management is not enabled, global has no effect—every entity reaches every client anyway.

By default every entity type is included in snapshots. Set persistent: false on types that should be left out—short-lived projectiles, session-specific markers, enemies that respawn from static data:

entity: Projectile
persistent: false
vars:
speed: { tag: 1, type: float, sync: tick }

After golem-bake, the generated type returns false from IsPersistent(). The snapshot system checks this automatically. Omitting persistent defaults to true.

After golem-bake, go-server emits a Go struct named SyncedPlayer (for an entity named Player) with typed Get and Set methods for every field you declared—Health(), SetHealth(v float64), DisplayName(), SetDisplayName(s string), plus the implicit position methods. In 2D code that is usually Position() / SetPosition(x, y). In 3D code, use Position3D() / SetPosition3D(x, y, z) when height matters.

ValueBehavior
tick (default)Field is tracked every tick for changes and included in delta serialization when dirty.
onceSkips the per-tick dirty bitmask; templates treat these fields as full snapshot / spawn-time data.

Omitting sync means tick.

Values use the Protocol Buffers scalar vocabulary (bake maps them straight into the generated serializers). Supported examples include:

double, float, int32, int64, uint32, uint64, sint32, sint64, bool, string, bytes

Unknown types fail at bake time. The same names appear in command schemas.

Entity vars can also hold lists or dictionaries using the following syntax:

Type expressionGo typeTypeScript type
list<int32>[]int32number[]
list<Item>[]ItemItem[]
dict<string, int32>map[string]int32Record<string, number>
dict<string, Item>map[string]ItemRecord<string, Item>

T in list<T> and V in dict<K,V> can be any proto scalar or a custom type you define under types_schema in golem.yaml (default schemas/types/). K must be a proto scalar. See Custom types & collections for how to define custom types and the delta semantics for collections.

  • At least one entity schema must exist; an empty entity_schema directory is an error.
  • Every variable must have a tag integer ≥ 1, unique within its entity. Missing or duplicate tags are a bake error.
  • The names pos_x and pos_y are reserved for implicit position fields. In 3D projects, pos_z is reserved too. Using reserved names in vars is a bake error.
  • list<T> and dict<K,V> fields can reference any proto scalar or a custom type defined in your types directory (default schemas/types/). Referencing an unknown custom type is a bake error.

After edits, run golem-bake to regenerate outputs.