Skip to content

Content Registry

The ContentRegistry is a read-only data structure that holds all game content. It is built at load time from the content/ directory and is never modified by the engine or renderer during gameplay.

The registry provides a single indexed source of truth for all static game data, allowing the engine to resolve IDs quickly and build snapshots without scanning files.

interface ContentRegistry {
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>;
}

Every entity is indexed by its id field. For example, a location with id: tavern is stored at registry.locations.tavern.

The dev server (npm run dev) builds the registry automatically:

  1. Scans content/ subdirectories
  2. Parses .yaml files as entities based on their directory
  3. Parses .dlg files with the dialogue parser
  4. Loads locale files as flat key-value dictionaries
  5. Serves the complete registry via /api/content
Directory→ Registry FieldLoader
content/locations/*.yamlregistry.locationsYAML parse, keyed by id
content/characters/*.yamlregistry.charactersYAML parse, keyed by id
content/items/*.yamlregistry.itemsYAML parse, keyed by id
content/maps/*.yamlregistry.mapsYAML parse, keyed by id
content/dialogues/*.dlgregistry.dialoguesDSL parser, keyed by filename
content/quests/*.yamlregistry.questsYAML parse, keyed by id
content/journal/*.yamlregistry.journalEntriesYAML parse, keyed by id
content/interludes/*.yamlregistry.interludesYAML parse, keyed by id
content/locales/*.yamlregistry.localesYAML parse, keyed by filename

Locale files don’t have an id field. They’re keyed by filename: en.yamlregistry.locales.en.

Dialogue files use the filename (without extension) as the dialogue ID: bartender_greeting.dlgregistry.dialogues.bartender_greeting.

game.yaml is loaded separately as a GameConfig, not part of the registry.

The registry is passed to the Engine constructor:

const engine = new Engine(registry, initialState);

The engine uses the registry to:

  • Look up location data when building snapshots
  • Find character dialogues when talkTo is called
  • Resolve localization keys and interpolate {varName} placeholders at snapshot time
  • Check triggered dialogue and interlude conditions on location change
  • Determine travel distances from map data

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, createInitialState(config));

Entities reference each other by ID:

  • Character dialogue field → Dialogue ID
  • Character location field → Location ID
  • Item location field → Location ID, "inventory", or Character ID
  • Map locations[].id → Location ID
  • Dialogue triggerLocation → Location ID
  • Interlude triggerLocation → Location ID
  • INTERLUDE <id> effect → Interlude ID
  • GameConfig startLocation → Location ID
  • GameConfig startInventory → Item IDs

These references are resolved at runtime when the engine looks them up. Missing references are handled gracefully (the action becomes a no-op).