A 5e storytelling engine with an LLM DM

Heal list-shaped skills/tools and keep wikilinks out of narration master

Two things I ran into in one onboarding session, bundled together since neither's big enough to stand alone: - The DM's opening salvo crashed `update_character` with "list indices must be integers or slices, not str" — the LLM had written `proficiencies.skills` as a flat list of names instead of a name→"proficient"/"expertise" dict, then came back with `proficiencies.skills.survival = "proficient"`-style dot-path updates that can't walk into a list. Tightened the schema so the wrong shape gets rejected with a concrete dict example, added coercion in `coerce_character` so existing bad yamls heal on load (mirrors the `resources` / `equipment` pattern already there), and swapped `_set_nested`'s bare `TypeError` for a returned DM-readable message so any future "can't dot-path into a list" scenario surfaces as a rejection instead of a stack trace. - DM was narrating `[[Name]]` wikilinks in the prose it sends the player. Added a line to `prompts/dm-system.md` spelling out that wikilinks belong in tool calls only, never in narration. New tests live in their own `tests/test_character_proficiencies.py` rather than growing `tests/test_character.py` past its loq baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>


+226 -11
4 changed files