From 1a91b384b503213b90cbe9931ec05a004f709923 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Mon, 16 Feb 2026 16:22:16 -0500 Subject: [PATCH] Add content loading pipeline documentation --- docs/how/content-loading.rst | 151 +++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 docs/how/content-loading.rst diff --git a/docs/how/content-loading.rst b/docs/how/content-loading.rst new file mode 100644 index 0000000..0398471 --- /dev/null +++ b/docs/how/content-loading.rst @@ -0,0 +1,151 @@ +============================= +content loading pipeline +============================= + +the content pipeline converts static TOML definitions into runtime Python objects +at startup. all loading happens once before accepting player connections — no hot-reload. + +startup sequence +================ + +from ``server.py run_server()``, in order:: + + 1. init_db() → database + 2. init_game_time() → game clock + 3. World() + terrain → procedural terrain + 4. Zone (overworld) → overworld from terrain + 5. load_zones() → content zones (hub, tavern, etc) from content/zones/ + 6. load_commands() → TOML commands from content/commands/ + 7. load_help_topics() → help topics from content/help/ + 8. register_combat_commands() → combat moves from content/combat/ + 9. load_mob_templates() → mob definitions from content/mobs/ + 10. load_thing_templates() → thing/container definitions from content/things/ + 11. load_recipes() → crafting recipes from content/recipes/ + 12. load_all_dialogues() → NPC dialogue trees from content/dialogue/ + +common loader pattern +===================== + +every content type follows the same pattern:: + + 1. load_X(path) → parse single TOML file into dataclass + 2. load_Xs(directory) → iterate .toml files, call load_X each, return dict + 3. At startup: global_registry.update(load_Xs(dir)) + +the loader functions are synchronous. they read files, parse TOML, validate data, +and populate module-level registries. errors during load are logged but don't +crash the server (graceful degradation). + +content directory structure +=========================== + +all content lives under ``content/``:: + + content/commands/ → command definitions (name, aliases, handler or message) + content/combat/ → combat moves (attacks, defenses, variants) + content/things/ → thing/container templates + content/mobs/ → mob templates with loot and schedules + content/zones/ → zone definitions (terrain grids, portals, spawns, ambient) + content/recipes/ → crafting recipes + content/dialogue/ → NPC dialogue trees + content/help/ → help topic files + content/stories/ → z-machine game files (not TOML, loaded separately) + +each subdirectory contains multiple ``.toml`` files. the loader iterates them +all and merges results into a single registry. + +global registries +================= + +module-level dicts, populated at startup, read-only during play:: + + commands/__init__.py: _registry dict + combat/commands.py: combat_moves dict + things.py: thing_templates dict + mobs.py: mob_templates dict, mobs list + zones.py: zone_registry dict + crafting.py: recipes dict + commands/help.py: _help_topics dict + commands/talk.py: dialogue_trees dict + +these dicts are never mutated after startup. new content requires a server restart. + +handler resolution +================== + +commands and thing verbs can reference Python functions via ``"module:function"`` +strings. at load time, the loader uses ``importlib`` to resolve the string into +a callable. + +commands can also have inline message text instead of a handler. in that case, +the loader wraps the message in a simple async handler that sends the text. + +if handler resolution fails, the loader logs a warning and continues. the command +is registered but won't work (graceful degradation). + +template vs instance +==================== + +templates are immutable definitions stored as dataclasses. spawning creates mutable +runtime objects (``Thing``, ``Mob``, etc). + +templates stay in the registry forever. instances are created and destroyed during +play. when a mob dies, the template remains — you can spawn another. + +this separation keeps content definitions clean and allows multiple instances of +the same template (ten wolves from one ``wolf`` template). + +validation +========== + +different content types validate at different levels: + +dialogue trees +-------------- + +validate all node references at load time. if a choice points to a nonexistent node, +``load_dialogue()`` raises ``ValueError`` and the server won't start. + +combat moves +------------ + +validate ``countered_by`` references. if a move references a nonexistent counter, +the loader logs a warning but continues. the move is still usable, just without +that counter. + +zone portals +------------ + +parse ``"zone_name:x,y"`` target format at load time. if the format is invalid, +the portal won't work. if the zone doesn't exist yet (load order), it's fine — +the portal stores the string and runtime lookup happens when someone uses it. + +code +==== + +startup sequence:: + + src/mudlib/server.py (run_server function) + +loaders:: + + src/mudlib/commands/__init__.py (load_command, load_commands) + src/mudlib/combat/commands.py (load_combat_move, register_combat_commands) + src/mudlib/things.py (load_thing_templates) + src/mudlib/mobs.py (load_mob_template, load_mob_templates) + src/mudlib/zones.py (load_zone, load_zones) + src/mudlib/crafting.py (load_recipes) + src/mudlib/commands/help.py (load_help_topic, load_help_topics) + src/mudlib/commands/talk.py (load_dialogue, load_all_dialogues) + +content directories:: + + content/commands/ + content/combat/ + content/things/ + content/mobs/ + content/zones/ + content/recipes/ + content/dialogue/ + content/help/ + content/stories/