Content Registry
The ContentRegistry is a read-only data structure that holds all game
definitions. It is built at load time from the content/ directory and is
never modified by the engine or renderer during gameplay.
This page is for developers working with the engine API or a custom renderer. You never touch the registry directly while writing content. It is simply where your files end up at runtime.
The registry organizes content by ID so the engine can find it without scanning project files during play.
Structure
Section titled “Structure”interface ContentRegistry { player?: PlayerCharacter; locations: Record<string, Location>; characters: Record<string, Character>; items: Record<string, Item>; maps: Record<string, Map>; dialogues: Record<string, Dialogue>; quests: Record<string, Quest>; journalEntries: Record<string, JournalEntry>; interludes: Record<string, Interlude>; locales: Record<string, LocaleData>;}Collections are indexed by each entity’s id. For example, a location with
id: tavern is stored at registry.locations.tavern. The player is different:
content/player.yaml is a single optional definition stored directly at
registry.player, never under registry.characters.
How Content is Loaded
Section titled “How Content is Loaded”The dev server (npm run dev) builds the registry automatically:
- Scans
content/subdirectories - Reads
.yamlfiles as entities based on their directory - Converts
.dlgfiles into dialogue data - Loads the translation entries from each locale file
- Serves the complete registry via
/api/content
Loading by Directory
Section titled “Loading by Directory”| Directory | Registry Field | Loader |
|---|---|---|
content/locations/*.yaml |
registry.locations |
YAML parse, keyed by id |
content/characters/*.yaml |
registry.characters |
YAML parse, keyed by id |
content/items/*.yaml |
registry.items |
YAML parse, keyed by id |
content/maps/*.yaml |
registry.maps |
YAML parse, keyed by id |
content/dialogues/*.dlg |
registry.dialogues |
DSL parser, keyed by filename |
content/quests/*.yaml |
registry.quests |
YAML parse, keyed by id |
content/journal/*.yaml |
registry.journalEntries |
YAML parse, keyed by id |
content/interludes/*.yaml |
registry.interludes |
YAML parse, keyed by id |
content/locales/*.yaml |
registry.locales |
YAML parse, keyed by filename |
content/player.yaml |
registry.player |
YAML parse, optional file |
Special Cases
Section titled “Special Cases”Locale files use their filename as the locale code. For example, en.yaml becomes registry.locales.en.
Dialogue files use the filename without its extension as the dialogue ID. For example, bartender_greeting.dlg becomes registry.dialogues.bartender_greeting.
Each project can have one optional player.yaml file. It defines the player
character’s profile fields and starting stats. It has no id,
location, dialogue, relationship, or party-membership field. The toolkit records
its source as player:player in the file map used by Studio.
game.yaml is loaded separately as a GameConfig, not part of the registry.
Its playerCreatesProfile flag decides whether the profile comes from
player.yaml or is entered by the player. If the flag and player.yaml are
both absent, the engine uses a generic Player profile and does not open the
profile modal.
Definitions, State, and Snapshots
Section titled “Definitions, State, and Snapshots”The player data has three distinct forms:
registry.playercontains the immutable definition loaded fromplayer.yaml, including detailed stat definitions such as{ name: "Strength", value: 16 }.GameState.playercontains the current profile text and current stat values. Effects update this state, and saves preserve it.snapshot.playercontains renderer-ready profile text, localized stat values, and a separatestatNamesmap.
NPCs use the same separation for stats: character YAML provides the stat names
and starting values, GameState.characterState[id].stats holds current values,
and each SnapshotCharacter exposes stats plus statNames. Keeping the name
separate lets game logic address a stable stat key while renderers display a
localized label.
Localization applies to player and character profile fields, stat names, and string stat values. Player-entered profile text remains literal.
How the Engine Uses the Registry
Section titled “How the Engine Uses the Registry”The registry is passed to the Engine constructor:
const engine = new Engine(registry);The engine uses the registry to:
- Look up location data when building snapshots
- Find character dialogues when
talkTois called - Initialize the player profile and character stats for a new game
- Resolve localization keys and interpolate variables, player profile fields, and character stats at snapshot time
- Check triggered dialogue and interlude conditions on location change
- Determine travel distances from map data
Client-Side Loading
Section titled “Client-Side Loading”In the browser, the registry is fetched from the dev server:
const response = await fetch('/api/content');const { registry, config } = await response.json();
const engine = new Engine(registry);const snapshot = engine.newGame(config);Content References
Section titled “Content References”Entities reference each other by ID:
- Character
dialoguefield references a dialogue ID - Character
locationfield references a location ID GameConfig.playerCreatesProfileselects fixed or player-entered profile text- Item
locationfield references a location ID,"inventory", or a character ID - Map
locations[].idreferences a location ID - Dialogue
triggerLocationreferences a location ID - Interlude
triggerLocationreferences a location ID INTERLUDE <id>references an interlude IDGameConfig.startLocationreferences a location IDGameConfig.startInventorycontains item IDs
npm run validate and npm run build check these references before release. At runtime, the engine uses fallback behavior where possible: some actions leave state unchanged, and a missing location produces a fallback snapshot.