Skip to content

World schemas

Some data belongs to the world, not to any individual entity: zone metadata, gravity constants, map dimensions, spawn tables. This data is static (or rarely changes), server-authoritative, and should reach every client on connect. World schemas give you a typed way to declare, serialize, and deliver that data without hand-writing any marshaling code.

When a client connects, the server automatically sends one WorldUpdate frame per world type before any entity snapshots. When data changes mid-session, call PushWorldData and the server re-broadcasts that type to all connected clients.

Create one .yaml file per world type in the directory set by world_schema in golem.yaml (default schemas/world/):

schemas/world/zone.yaml
world: Zone
fields:
name:
type: string
gravity:
type: float
max_enemies:
type: int32
  • world: PascalCase type name. Generated Go type is ZoneData; proto message is also ZoneData.
  • fields: Snake_case keys, same proto scalar types as entity schemas. Keys are sorted for stable wire tags.

After golem-bake, the go-server integration emits world_pb.go in your output directory with a ZoneData struct that has typed fields for everything you declared—Name string, Gravity float32, MaxEnemies int32. Populate and store in your server startup:

srv.World.Set(&synced.ZoneData{
Name: "Overworld",
Gravity: 9.8,
MaxEnemies: 50,
})

srv.World is always initialized. Clients receive the stored data on connect; call srv.PushWorldData("Zone") to re-broadcast after an in-game change.

Some game data is best thought of as a named registry of items: every item type in the game, every spell, every unit definition. In Unity you’d use a ScriptableObject list. In golem-engine, catalog world schemas give you the same pattern: author the entries in a plain YAML list, declare the schema once, and golem-bake generates a typed loader and delivers the full catalog to every client on connect.

Add a world schema file with source.format: catalog:

schemas/world/item_catalog.yaml
world: ItemCatalog
source:
format: catalog
file: catalgolem/items.yaml # path relative to your project root
type: Item # must match a custom type under types_schema (default schemas/types/)
key: id # optional: the field to use as map key; absent = list
  • type — references a custom type defined under types_schema (e.g. schemas/types/item.yaml by default). See Custom types & collections.
  • key — when set, the generated field is a dict<KeyType, Item> keyed by that field. When omitted, the generated field is a list<Item>. Either way, golem-bake checks at bake time that all key values are present and unique.

The file at catalgolem/items.yaml (the file: path) is a flat YAML sequence—one entry per item:

- id: 1
name: "Iron Sword"
damage: 10
- id: 2
name: "Fire Staff"
damage: 25

Field names must match the snake_case field names declared in your custom type. Missing fields default to zero values; extra fields are silently ignored.

After golem-bake the go-server integration emits:

  • ItemCatalogData struct — with a single Items field. Dict mode: map[int32]Item. List mode: []Item.
  • LoadItemCatalogData(path string) (*ItemCatalogData, error) — reads the catalog YAML at startup and populates the struct.

Wire it up at server startup:

d, err := synced.LoadItemCatalogData("catalgolem/items.yaml")
if err != nil {
log.Fatal(err)
}
srv.World.Set(d)

Clients receive ItemCatalogData as part of the initial world snapshot, before any entity state.

For dict catalogs (key: set), the generated WorldManager on the TypeScript client exposes a typed lookup method alongside the raw getter:

// Raw access — the full map sent by the server
worldManager.itemCatalog?.items // Record<number, Item>
// Generated lookup helper
worldManager.getItemCatalogById(1) // Item | undefined

For list catalogs (no key:), only the array getter is generated:

worldManager.itemCatalog?.items // Item[]

Use a catalog when your data is authored as a list of uniform records and you want golem-bake to manage the loader and wire format automatically. Use plain fields: when your world data is a single struct with named scalar fields—zone gravity, map dimensions, configuration constants.

You can mix both approaches: a catalog world type for items, a plain world type for zone config, and a Tiled source for map data.

If your team uses Tiled (.tmj files) or LDtk (.ldtk files), you can link a world schema directly to a map file. The source: block in the world YAML tells golem-bake to generate a LoadXxxData() helper that reads and parses the file at server startup.

schemas/world/level1.yaml
world: Level1
source:
format: tiled # "tiled" or "ldtk"
file: maps/level1.tmj # path relative to your project root
extract: # custom map properties → typed proto fields (optional)
- name: gravity
type: float
- name: spawn_count
type: int32
  • When source: is present, fields: is optional—extracted fields are merged with any explicitly declared ones.
  • Fields are sorted alphabetically for stable wire tags. A synthetic bytes tile_data field is appended last; it carries the raw file contents so clients can pass them to their own Tiled/LDtk renderer.
  • The same proto scalar types used in entity schemas apply in extract:.

golem-bake emits a LoadLevel1Data(path string) (*Level1Data, error) helper alongside the struct. Call it at startup:

d, err := synced.LoadLevel1Data("maps/level1.tmj")
if err != nil {
log.Fatal(err)
}
srv.World.Set(d)

The loader reads the file once, populates extracted fields from the map’s custom properties (gravity, spawn_count in the example above), and stores the raw bytes in TileData. Everything is ready for serialization through the normal WorldUpdate path.

Tiled properties are extracted from the map’s top-level custom properties. LDtk properties are extracted from the first level’s field instances.

schemas/world/dungeon.yaml
world: Dungeon
source:
format: ldtk
file: maps/dungeon.ldtk
extract:
- name: floor_count
type: int32
- name: theme
type: string

The LoadDungeonData(path string) loader calls ldtk.Parse internally, reads floor_count and theme from the first level’s field instances, and stores the raw .ldtk JSON in TileData.

Embedding raw map bytes in the WorldUpdate proto works well for small maps. For larger files, set url_prefix in the source block instead of omitting it: golem-bake then generates a string map_url field (the URL prefix plus the filename) rather than bytes tile_data. Clients use that URL to fetch the file out-of-band over HTTP.

Set MapDir in ServerConfig (or mount MapFileHandler on your own mux) to have the server serve those files. See Map file serving for the full wiring.

The golem/tiled and golem/ldtk packages are also available directly for server-side game logic—collision grids, pathfinding, spawn point detection—independent of any codegen or client delivery:

import "golem-engine/golem/tiled"
m, err := tiled.Load("maps/level1.tmj")
if err != nil {
log.Fatal(err)
}
// Collision layer: tiles layer named "Collision"
collision := m.LayerByName("Collision")
// Custom properties on the map itself
if p, ok := m.PropertyByName("gravity"); ok {
gravity = p.FloatValue()
}
import "golem-engine/golem/ldtk"
proj, err := ldtk.Load("maps/dungeon.ldtk")
if err != nil {
log.Fatal(err)
}
// All levels across single- and multi-world projects
for _, level := range proj.AllLevels() {
entities := level.LayerByIdentifier("Entities")
// entities.EntityInstances — spawn points, triggers, etc.
}

Both packages let you navigate the map structure with typed helpers. For Tiled maps, m.LayerByName(name) returns the first top-level layer matching that name (or nil), and m.PropertyByName(name) looks up a named custom property and gives you FloatValue(), IntValue(), StringValue(), or BoolValue() accessors. For LDtk projects, proj.AllLevels() returns every level regardless of single- or multi-world mode, proj.LevelByIdentifier(id) finds one level by its LDtk identifier, level.LayerByIdentifier(id) finds a layer instance, and level.FieldByIdentifier(id) returns a field instance with the same typed value accessors.

  • Each world type name must be unique across files in your world_schema directory (default schemas/world/); duplicates are a bake error.
  • Field keys are sorted alphabetically for stable wire tags. Adding a field shifts tags for fields that sort after it—treat changes with the same care as any proto field addition.
  • tile_data and map_url are reserved field names when source: is set for Tiled/LDtk; declaring them explicitly is a bake error.
  • source.format must be "tiled", "ldtk", or "catalog"; any other value is a bake error.
  • source.file must not be empty when source: is present.
  • Catalog sources require source.type to be set; it must match a type defined under types_schema (default schemas/types/).
  • If source.key is set on a catalog, golem-bake validates that every entry in the catalog file has that field and that all key values are unique.
  • The world schema directory is optional—if it does not exist, bake skips world generation without error.

After any schema change, run golem-bake to regenerate outputs.