World schemas
World schemas
Section titled “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.
Basic world data (fields only)
Section titled “Basic world data (fields only)”Create one .yaml file per world type in the directory set by world_schema in golem.yaml (default schemas/world/):
world: Zone
fields: name: type: string gravity: type: float max_enemies: type: int32world: PascalCase type name. Generated Go type isZoneData; proto message is alsoZoneData.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.
Catalog data
Section titled “Catalog data”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.
Declaring a catalog schema
Section titled “Declaring a catalog schema”Add a world schema file with source.format: catalog:
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 = listtype— references a custom type defined undertypes_schema(e.g.schemas/types/item.yamlby default). See Custom types & collections.key— when set, the generated field is adict<KeyType, Item>keyed by that field. When omitted, the generated field is alist<Item>. Either way,golem-bakechecks at bake time that all key values are present and unique.
The catalog data file
Section titled “The catalog data file”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: 25Field names must match the snake_case field names declared in your custom type. Missing fields default to zero values; extra fields are silently ignored.
What bake generates
Section titled “What bake generates”After golem-bake the go-server integration emits:
ItemCatalogDatastruct — with a singleItemsfield. 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.
Client access
Section titled “Client access”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 serverworldManager.itemCatalog?.items // Record<number, Item>
// Generated lookup helperworldManager.getItemCatalogById(1) // Item | undefinedFor list catalogs (no key:), only the array getter is generated:
worldManager.itemCatalog?.items // Item[]Catalog vs plain world fields
Section titled “Catalog vs plain world fields”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.
Tiled and LDtk integration
Section titled “Tiled and LDtk integration”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.
Declaring the source
Section titled “Declaring the source”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_datafield 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:.
Generated loader
Section titled “Generated loader”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.
LDtk example
Section titled “LDtk example”world: Dungeon
source: format: ldtk file: maps/dungeon.ldtk extract: - name: floor_count type: int32 - name: theme type: stringThe 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.
Serving map files over HTTP
Section titled “Serving map files over HTTP”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.
Server-side map use
Section titled “Server-side map use”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 itselfif 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 projectsfor _, 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_schemadirectory (defaultschemas/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_dataandmap_urlare reserved field names whensource:is set for Tiled/LDtk; declaring them explicitly is a bake error.source.formatmust be"tiled","ldtk", or"catalog"; any other value is a bake error.source.filemust not be empty whensource:is present.- Catalog sources require
source.typeto be set; it must match a type defined undertypes_schema(defaultschemas/types/). - If
source.keyis set on a catalog,golem-bakevalidates 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.