Players can now paste the full text of a freely available adventure
when creating a game, and the DM will follow it instead of inventing
its own plot/NPCs/locations. Reuses the existing rulebook RAG
pipeline (chunking, local embedding) rather than injecting the raw
text into every turn, since real adventure modules range from a few
pages to hundreds — far beyond what fits in a prompt.
- New adventure_chunks table (game-scoped, unlike the global
rulebook_chunks) + Game.adventure_text storing the raw source.
- ingest_adventure_text() chunks/embeds synchronously during
POST /api/games when adventure_text is non-empty.
- build_adventure_rag_block() retrieves by similarity to the latest
player message, scoped to game_id — plus always includes chunk 0
(the adventure's opening) regardless of the query, since early
Session-0 messages rarely resemble the adventure's actual hook and
pure similarity search could miss the beginning entirely.
- System prompt instructs the DM to follow provided adventure
excerpts strictly, deviating only when players clearly go off-script.
- Frontend: optional adventure-text field in the game creation form,
a has_adventure flag surfaced as a small badge on the game card and
session header.
Verified with an isolated two-game test that chunks/retrieval never
leak across games (the critical failure mode here), and live against
x.ai: a custom-written one-shot's specific NPCs, location, and hook
appeared verbatim in the DM's opening scene instead of invented ones.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Context was pure full-text replay of every message, capped at 40k
chars with oldest-dropped-first — no summarization/world-state layer,
as called out as still-missing in an earlier conversation.
Add a new update_world_state DM tool that persists a compact,
DM-authored recap (key NPCs, current location, open plot threads,
party/inventory state) to a new world_state table, one row per game.
It's injected into the system prompt every turn, ahead of the raw
message window. The DM is instructed to call it regularly — at every
scene change or major event, not just at the end — sending the full
current picture each time (matches the replace-not-merge pattern
already used for combat_stats/abilities/equipment).
Since the summary now backs up everything older, shrink the raw
message window from 40k to 16k chars — it's recent continuity now,
not the sole memory of the session. Full history remains available
via "Volltext laden" regardless, since that reads the messages table
directly rather than through this context builder.
Simplified from the original plan sketch (state JSONB) to a single
free-text summary field — natural-language recaps are something an
LLM authors well; a structured world model would need a schema the
DM would have to conform to for no real benefit here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Only the player character's HP was tracked before, so a monster's
death was never detected — only the DM's own (unenforced) narration
of it. Add a new update_monster_hp tool the DM calls whenever an
NPC/monster is introduced or takes damage, stored as DM-only
bookkeeping on Game (never exposed via GameRead, since HP/AC of NPCs
is meant to stay secret from players). The tool reports back
"defeated": true once HP drops to 0 so the DM can react to it — but
unlike a character dying, this does NOT auto-end the session; whether
a monster's death should end the game is a separate decision left to
the DM's own end_game call, or to a future rule.
Building an isolated test for this caught a real bug along the way:
the update mutated the matched monster's dict in place before
reassigning the list, which made SQLAlchemy's old-vs-new JSONB
comparison see identical content and silently skip writing the
change. Fixed by building fresh dicts instead of mutating shared ones.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Games now have a status/ended_reason pair. The DM can call the new
end_game tool when the story reaches a real conclusion (victory,
defeat, or a resolved one-shot), and the backend independently ends
the game whenever a character's HP drops to 0 or below, regardless of
whether the DM narrates it. The frontend shows a banner and a
"Beendet" badge once a game ends.
HP moves from the freeform combat_stats bag into dedicated
current_hp/max_hp columns on Character, since reliably detecting 0 HP
requires a real integer rather than parsing strings like "3/10" out of
an LLM-authored key/value dict. Also fixed combat_stats to fully
replace on each upsert instead of merging, matching its documented
contract — the merge was leaving stale keys (old HP/TP text) behind
after the model stopped sending them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Broadcast a "rolling" WS event when the DM invokes roll_dice, and show
a cycling dice-emoji animation with the roll notation in the chat while
waiting for the result, instead of just the generic typing indicator.
Also split the dice-roll prompt rule into skill/save checks (DC shown
to the player) vs. attack rolls (AC stays secret but must still be
labeled as a roll), and made the "always roll yourself, never ask the
player to roll" instruction unconditional.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Character sheets gain structured combat_stats, abilities (name +
plain-language explanation), and equipment fields, populated by the
upsert_character_sheet tool; the detail page renders them as their
own sections instead of one prose block, with a player-card-style
attribute grid (full ability names + modifiers).
- Fix double-escaped unicode occasionally left in tool-call JSON
arguments (a model quirk) and stop the app's own json.dumps calls
from re-introducing it (ensure_ascii=False).
- Render chat messages as markdown (react-markdown + Tailwind
typography) instead of raw text, and widen the games/characters/
chat layout to use more of the screen on desktop.
- Sharpen the DM system prompt: state the ability + DC for each
offered action option before the player commits, and narrate rolls
as DC → result → pass/fail before the story consequence; also stop
re-asking Session-0 questions already answered in the game's
name/description.
- Make the DM's LLM provider configurable (xai default, lmstudio for
a local OpenAI-compatible server via host.docker.internal) instead
of hardcoded to x.ai.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
run_dm_turn now loads the game's name/description into the system
prompt on every turn, and the Session-0 instructions tell the DM to
check those framing details first and skip re-asking genre/world,
scope, or experience level when they're already covered there —
only the still-open points (player count, characters, rule-depth
preference, content boundaries) get asked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RAG: switch Postgres to pgvector, chunk and embed the three D&D
rulebooks locally via sentence-transformers, and retrieve relevant
excerpts per DM turn (query = latest player message) to ground the
system prompt. Retrieval runs off the event loop and is capped by a
relevance threshold and a max character budget so it can't blow up
context size or cost.
Game setup wizard: creating a game now opens a short chat where the
DM asks about genre, length, and the player's experience level, then
proposes a name and description via a tool call. The player can edit
both before creating the game. Stateless endpoint — the frontend
carries the conversation, no DB needed since the game doesn't exist
yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FastAPI + Postgres backend with fastapi-users auth, games/characters
CRUD, and a WebSocket chat endpoint where an x.ai Grok DM narrates
play, rolls dice, and maintains character sheets via tool calls.
React + Tailwind frontend covers registration/login, game list and
creation, participation-code join flow, character viewer, and a
real-time chat session view. Docker Compose skeleton (Postgres +
backend) included; Caddy/frontend container wiring is deferred to
the deploy milestone.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>