Commit Graph

10 Commits

Author SHA1 Message Date
Thorsten f5c62d27fd Let players roll their own physical dice instead of the DM
Adds the "Ich würfle selbst" checkbox the user asked for, plus the
structural piece that makes it actually reliable: a tool call can't
block mid-turn waiting for a human to go find a d20, so this can't be
a pure client-side toggle — the DM has to think in two turns (ask,
then later recognize the answer), and it's easy for an LLM to lose
track of that across a real gap in the conversation.

game_participants gets self_rolls (the toggle) and pending_roll (what
roll is currently awaited, if any). When self_rolls is on, roll_dice
doesn't touch the RNG — it validates the notation, stores it as
pending_roll, and returns an "awaiting_player_roll" result that tells
the DM to ask for that exact roll and wait, never inventing a number.
At the top of the player's next turn, orchestrator.run_dm_turn reads
back any pending_roll, injects it as an explicit reminder into the
system prompt ("the player's message is probably answering this"),
and clears it — so the DM doesn't have to rely on remembering what it
asked for several messages ago.

Frontend: the checkbox lives in the game header (PATCH
/api/games/{id}/self-rolls, per-participant). A persistent "🎲 Du bist
am Zug" banner shows the pending notation/reason — persistent, not
transient like the typing/rolling indicators, since answering it means
physically finding a die and rolling, which takes real time. It's
seeded from GameRead.my_pending_roll on load so it survives a page
reload, and only clears when the player actually sends their next
message (not when the DM's message arrives, which would make it
disappear before there was time to read it).

Verified live end-to-end against x.ai in a real session: toggled the
setting, asked for something requiring a check, got "würfle bitte
1d20+1" instead of an auto-rolled result, confirmed the banner
survived a full page reload, replied "14", and the DM used that
number directly ("Du hast 14 gewürfelt — das reicht") without
re-rolling — with pending_roll correctly cleared in the DB afterward.
Also confirmed per-participant scoping (a second participant's flag
stayed off) and that toggling back off works cleanly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 13:59:48 +02:00
Thorsten a58730fce7 Let owners delete their games and characters, with confirmation
Add DELETE /api/games/{id} (creator-only) and DELETE
/api/characters/{id} (owner-only), each 403ing for anyone else.

Deleting a game relies on the existing cascade FKs (game_participants,
messages, world_state, adventure_chunks all cascade on games.id) — a
plain session.delete() is enough.

Deleting a character is trickier: game_participants.character_id and
messages.character_id reference it with no ON DELETE clause (a
character can outlive a game and vice versa), so deleting one that's
been played would hit a FK violation. Null out both references first
— game and message history stay intact, just detached from the
now-gone character, same as how a participant with no character
selected already renders.

Frontend: a reusable ConfirmDialog component, a "Löschen" button on
GameCard/CharacterCard visible only to the owner (checked against
useAuth()'s user id), wired through GamesList/CharactersList so the
list updates locally after a successful delete.

Verified: isolated backend test proved the character delete's FK
nulling works without violation and preserves the game/message rows;
live in the browser, deleting a game removed it from the list with no
orphaned rows left in any of the four related tables, and the
character delete dialog's cancel path correctly leaves everything
untouched. Ownership scoping confirmed live too — no delete button
appears on other users' games.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:14:48 +02:00
Thorsten b77f066259 Add per-game adventure text with RAG-based retrieval
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>
2026-09-02 13:13:59 +02:00
Thorsten ff4c6f07e3 Make the participation code copyable as a shareable invite link
The code was shown as plain text on the game card with no way to copy
it. It's now a button that copies a full invite link
(origin/games/{id}?code={code}) to the clipboard instead of just the
bare code, since the recipient still needs the game id to get there.

GameSession's join form reads that ?code= param on mount and
pre-fills the input, so following the link lands straight on "click
Beitreten" instead of needing the code typed in by hand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 18:15:47 +02:00
Thorsten 8da834c5c1 Add game end status with an end_game DM tool and automatic 0-HP detection
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>
2026-09-01 17:44:46 +02:00
Thorsten 2b99451fdf Add dice-roll animation and tighten attack-roll prompt rules
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>
2026-09-01 17:28:37 +02:00
Thorsten 624644619c Add character detail, markdown rendering, wider layout, and LLM provider switch
- 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>
2026-09-01 15:18:15 +02:00
Thorsten ec53ed3aba Add DM typing indicator to the game chat
The server broadcasts a "typing" event to the whole room right when
it starts the DM turn, and broadcasts turn failures to everyone too
(previously only the sender saw them, which would've left other
players' indicators stuck forever). The frontend shows a pulsing
"Dungeon Master schreibt …" line that clears once the real reply or
an error arrives.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 07:59:10 +02:00
Thorsten 419f5e3a89 Add rulebook RAG pipeline and LLM-driven game setup wizard
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>
2026-08-31 20:03:05 +02:00
Thorsten f37dc9fa76 Add Phase 1 MVP: D&D text-adventure server
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>
2026-08-31 18:43:08 +02:00