Context: This is the real project plan for dnd-multi, a full-stack AI Dungeon Master platform. It was generated by Claude Opus 4.6 during Phase 0 of the LLM GitHub PR workflow — synthesizing gap analysis from four different models into a structured execution document. Claude Sonnet 4.6 then used this plan overnight to open 24 PRs and ship all seven phases.
Personal identifiers have been removed. Technical content is verbatim.
Milestone Timeline
| Milestone | Target Date | Deliverable |
|---|---|---|
| M0 — Platform Stable | 2026-03-13 | All broken deps fixed, migrations applied, Tier 2 lore generating, smoke tests passing |
| M1 — First Playable Session | 2026-04-03 | Turn structure live, player identity in DM prompt, hot phrase + /dm command working |
| M2 — Full Action Flow | 2026-04-24 | Action confirmation, non-active player queue, player votes operational |
| M3 — IC/OOC + Personality | 2026-05-08 | Meta-mode detection, in-character assumption, DM personality tuning deployed |
| M4 — Content & Reporting | 2026-05-22 | Book/media content generation live, /report + /flag system in admin dashboard |
| M5 — Combat Tracker | 2026-06-12 | Live HP tracker UI, [COMBAT:] directives wired to death protection |
| M6 — Feature Complete v1.0 | 2026-06-19 | Spell reference Discord command, shareable campaign invitation links |
Current State Summary
The platform has a solid full-stack foundation with all core systems implemented and deployed to the home k3s cluster. The gap is game experience polish — the AI DM has no awareness of whose turn it is, doesn’t distinguish in-character from out-of-character speech, lacks an action confirmation flow, and has no mechanism for players to report misbehavior. These are the features that make the difference between a tech demo and a playable game.
What’s working:
- Auth (Google OAuth + Discord OAuth)
- Campaign/Character/Session CRUD
- AI DM pipeline (Bedrock Claude, 5-tier lore, world engine)
- WebSocket hub (in-process, per-campaign rooms)
- LiveKit voice + Amazon Transcribe STT
- Discord bot (slash commands, VC audio, dice announce)
- K8s deployment, CI/CD, Prometheus metrics
Critical tech debt that must be resolved before new features:
| Issue | Blocks | Status |
|---|---|---|
| Alembic migrations missing for core + world tables | DB out of sync | ✅ Fixed — 000_initial_schema.py + 001_world_invite.py |
World Engine WorldState not created on campaign create | [WORLD:] directives crash | ✅ Fixed — campaigns.py creates WorldState |
Dockerfile --workers 2 breaks in-process WS state | WebSocket state corruption | ✅ Fixed — changed to --workers 1 |
generate_tts=True hardcoded in WS handler | Polly cost creep | ✅ Fixed — now reads from message payload |
asyncio.get_event_loop() deprecated in async | Runtime warnings/crashes | ✅ Fixed — all 6 sites use get_running_loop() |
init_db() uses create_all() instead of Alembic | Schema drift | ✅ Fixed — removed create_all, runs Alembic head |
| Frontend types out of sync with backend | Runtime type mismatches | ✅ Fixed — types.ts corrected |
Unused npm deps (socket.io-client, howler, zustand) | Bundle bloat | ✅ Fixed — removed from package.json |
| Bot→backend auth missing | Unauthenticated bot API calls | ✅ Fixed — bot_service_token added |
| Session page 400+ LOC monolith | Maintainability | ✅ Fixed — 6 components extracted |
| Zero test coverage | Every new feature ships untested | Still open |
Execution Controls (Added for Delivery Confidence)
Phase Exit Gates (Pass/Fail)
Each phase is done only when all gate checks pass (not when all tasks are coded).
Gate status is recorded as pass/fail with linked evidence (test output, screenshots, or log excerpts) in the phase PR description.
| Phase | Gate ID | Required Validation |
|---|---|---|
| Phase 0 | G0.1 | backend + discord-bot dependencies install successfully in clean env and Docker build |
| Phase 0 | G0.2 | Alembic upgrade succeeds from empty DB and from current prod-like snapshot |
| Phase 0 | G0.3 | World engine seed check: every campaign has WorldState row post-migration |
| Phase 0 | G0.4 | Session summary generation writes Tier 2 rows for ended sessions |
| Phase 0 | G0.5 | Backend smoke tests pass: auth, campaigns, sessions, WebSocket connect/disconnect |
| Phase 1 | G1.1 | Active turn enforcement blocks non-active action messages server-side |
| Phase 1 | G1.2 | [TURN:] directive updates active_player_id and emits turn_changed |
| Phase 1 | G1.3 | STT speaker identity reaches DM prompt with player/character attribution |
| Phase 2 | G2.1 | Action confirmation lifecycle works (action_pending → confirm/cancel) |
| Phase 2 | G2.2 | Queue + vote flows are deterministic under reconnect and timeout |
| Phase 3 | G3.1 | IC/OOC meta detection routes correctly without advancing turn |
| Phase 4 | G4.1 | Item content generation is cached and not regenerated on repeat open |
| Phase 5 | G5.1 | /report persists reports and admin can resolve status |
| Phase 6 | G6.1 | [COMBAT:] directives mutate combat state and UI renders full tracker |
| Phase 7 | G7.1 | /spell returns deterministic fallback behavior when lore lookup misses |
Critical Path & Dependencies
| ID | Dependency | Why It Matters |
|---|---|---|
| D1 | Phase 0 migrations before all feature phases | Prevents schema drift and runtime crashes |
| D2 | Session summaries (0.5) before DM behavior tuning (Phase 3+) | Tier 2 context quality affects DM reliability |
| D3 | Turn system (Phase 1) before action confirmation/queue/votes (Phase 2) | All action semantics depend on active-player state |
| D4 | Contract sync (backend models ↔ frontend types) before UI rollout | Avoids runtime type mismatches in WS/API payloads |
| D5 | Observability additions per phase before production rollout | Enables fast triage and safe rollback decisions |
Migration & Rollback Strategy
| Rule | Requirement |
|---|---|
| Migration granularity | Keep migrations phase-scoped; avoid combining unrelated phases in one revision |
| Revision naming | Use phase-prefixed revisions (example: 003_phase1_turns, 004_phase2_action_flow) |
| Expand/contract | For WS/API schema changes: add fields/messages first, then enforce behavior in next step |
| Rollback safety | Every migration PR includes downgrade verification notes |
| Data migrations | Backfill scripts must be idempotent and re-runnable |
| Feature flags | New DM directives/messages should be gated behind campaign-level toggles when possible |
Contract Sync Checklist (Required on every model/protocol change)
- SQLAlchemy model updated (
backend/app/models.pyor world models) - Alembic migration created and validated (upgrade + downgrade)
- Pydantic request/response schema updated
- Frontend TypeScript contract updated (
frontend/src/lib/types.ts) - WebSocket message schema documented in this plan and implemented in
ws/manager.py - Backward compatibility behavior defined for existing clients
Observability Minimums by Feature
| Area | Required Signal |
|---|---|
| Turn enforcement | Counter: blocked non-active actions; event log with session/player IDs |
| Action confirmation | Counters: pending/confirmed/cancelled actions |
| Queue/votes | Counters: queued actions, vote starts/casts/timeouts |
| Meta mode | Counter: meta-routed messages |
| Item content | Counter: generated vs cache-hit item contents |
| Reports | Counter: dm_reports_total by reason + admin resolution count |
| Combat tracker | Counter: combat start/end, directive parse errors |
Token Budget & Cost Governance
| Change | Token/Cost Constraint |
|---|---|
| Queue injection into DM context (Phase 2) | Deduct 250 tokens from Tier 3 and re-verify trim behavior |
| Combat digest expansion (Phase 6) | Reallocate 300 tokens from world digest budget |
| Item content generation (Phase 4) | Default generate_tts=False; cache outputs to avoid repeat Bedrock calls |
| Action confirmation (Phase 2) | Default generate_tts=False to control Polly spend |
| Any new Bedrock/Polly usage | Update AWS cost table in README.md before phase close |
Risk Register (Top 6)
| Risk | Trigger | Mitigation |
|---|---|---|
| WS state corruption on disconnect | Rapid reconnect/disconnect during turn changes | Per-connection exception isolation and deterministic active-player reassignment |
| Tier overflow/regression | Added context blocks exceed model budget | Budget tests + trim-order assertions in lore assembly |
| Migration drift | Manual DB hotfixes diverge from Alembic history | Enforce migration-only schema changes + pre-deploy upgrade check |
| Bot voice regressions | Missing voice deps/FFmpeg in image | CI Docker build check + runtime startup health assertion |
| Report abuse/noise | Low-signal mass flags | Add reason taxonomy + admin resolution workflow |
| Cost creep | Unbounded Polly/Bedrock calls | Per-feature cost note and default non-TTS operation |
Delivery Cadence
| Cadence Item | Requirement |
|---|---|
| Weekly phase review | Post a short status update: gates passed/failed, blockers, next 7-day goals |
| PR size control | Keep feature PRs under ~600 LOC when possible; split protocol + UI changes if larger |
| Deployment policy | Do not deploy a phase to production until all prior phase gates are passing |
| Evidence retention | Keep gate evidence links in merged PRs for rollback/debug history |
Ownership & Accountability
| Area | Primary Owner | Required Sign-off |
|---|---|---|
| Backend API + DB migrations | Backend lead | Migration + API review |
| WebSocket protocol changes | Backend lead | WS contract review |
| Frontend contract + UI rollout | Frontend lead | Type/contract review |
| Discord bot commands/voice | Bot lead | Bot command + metrics review |
| Prompt/lore/token budget changes | AI lead | Token budget + cost review |
| Production rollout | Platform lead | Gate pass confirmation |
Non-Functional Targets (v1.0)
| Target ID | Metric | Threshold | Validation |
|---|---|---|---|
| N1 | DM turn p95 latency (text-only, no image/TTS) | ≤ 4.0s | Sampled from backend request logs |
| N2 | WebSocket reconnect recovery | ≤ 3s median | Reconnect simulation test |
| N3 | Session summary generation delay after session end | ≤ 60s p95 | Async job timing logs |
| N4 | dm_response delivery success during active session | ≥ 99.5% | WS success/error counters |
| N5 | Error budget for 5xx on gameplay APIs | < 1% weekly | API metrics dashboard |
Phase Readiness Scorecard
Use this lightweight scorecard before marking a phase complete and before production rollout.
| Dimension | Weight | Scoring Rule |
|---|---|---|
| Exit gates passed | 40% | 100 if all required gates pass, else 0 |
| Test health | 25% | % passing for phase-specific automated checks |
| Observability coverage | 15% | 100 if required counters/logs exist and are visible in dashboard, else 0 |
| Contract sync completeness | 10% | 100 if all checklist items are complete, else 0 |
| Rollback readiness | 10% | 100 if downgrade + recovery steps validated, else 0 |
Readiness Score = Σ(weight × score)
Deployment threshold: ≥ 90 and no unresolved P0/P1 blockers.
Open Decisions (Must Close Before Build)
| Decision ID | Question | Deadline |
|---|---|---|
| O1 | Should action queue remain in-process for v1.0 or move to Redis now? | Before Phase 2 implementation starts |
| O2 | Vote tie-breaker policy: DM decides, re-vote, or deterministic default? | Before Phase 2 vote UI/API build |
| O3 | /report reason taxonomy: fixed enum or free-text with tags? | Before Phase 5 DB schema migration |
| O4 | Combat tracker source of truth: world engine only, or mirrored session cache for UI latency? | Before Phase 6 backend implementation |
Roadmap
Phase 0 — Tech Debt & Stability · 2026-03-02 → 2026-03-13
Must complete before any new feature work. These are broken things, not missing features.
Milestone: M0 — Platform Stable (2026-03-13)
| # | Task | File(s) | Status |
|---|---|---|---|
| 0.1 | aiosmtplib to backend requirements | backend/requirements.txt | ✅ Already present |
| 0.2 | discord.py[voice] + PyNaCl + ffmpeg to bot Dockerfile | discord-bot/requirements.txt, Dockerfile | ✅ Already present |
| 0.3 | Write Alembic migrations for all tables | backend/alembic/versions/000_initial_schema.py, 001_world_invite.py | ✅ Done — core tables + world/invite tables |
| 0.4 | Create WorldState row on campaign create | backend/app/api/campaigns.py | ✅ Done |
| 0.5 | backend/app/lore/tiers.py (summarize_session()) | ✅ Already implemented | |
| 0.6 | Add HNSW pgvector index to lore embeddings | Alembic migration | 🔲 Still needed |
| 0.7 | Write smoke tests for backend API | backend/tests/ | 🔲 Still needed |
| 0.8 | Fix Dockerfile --workers 2 → --workers 1 | backend/Dockerfile | ✅ Done |
| 0.9 | Fix generate_tts=True hardcoding in WS handler | backend/app/api/sessions.py | ✅ Done |
| 0.10 | Fix asyncio.get_event_loop() → get_running_loop() | backend/app/ai/bedrock.py, dm_engine.py | ✅ Done |
| 0.11 | Add bot service token auth | backend/app/config.py, auth/deps.py, discord-bot/bot/config.py | ✅ Done |
| 0.12 | Fix frontend types to match backend models | frontend/src/lib/types.ts | ✅ Done |
| 0.13 | Remove unused npm deps | frontend/package.json | ✅ Done |
| 0.14 | Extract session page inline components | frontend/src/components/session/ | ✅ Done |
| 0.15 | Remove create_all() from init_db(), use Alembic only | backend/app/database.py | ✅ Done |
Phase 1 — Turn Structure & Player Identity · 2026-03-16 → 2026-04-03
The DM currently treats all messages identically. This phase gives the AI situational awareness of who is speaking and whether it’s their turn.
Milestone: M1 — First Playable Session (2026-04-03)
1.1 Active Turn System
New concept: GameSession.active_player_id
Add a active_player_id column to the sessions table (nullable). The DM sets this via a new [TURN: player_id] directive. The WebSocket hub enforces turn rules server-side.
| Task | File(s) | Status |
|---|---|---|
Add active_player_id to Session model + Alembic migration | backend/app/models.py, 003_phase1_turns.py | ✅ Done — PR #7 |
Add [TURN: player_id] directive parser to DM engine | backend/app/ai/dm_engine.py | ✅ Done — PR #10 |
| Enforce turn in WebSocket hub: block action messages from non-active players | backend/app/api/sessions.py | ✅ Done — PR #15 |
Handle WebSocketDisconnect gracefully: reassign or clear active_player_id | backend/app/api/sessions.py | ✅ Done — PR #16 |
Emit turn_change WS message when active player changes | backend/app/ws/manager.py | ✅ Done — PR #15 |
Sync data contracts: update Pydantic models & Frontend TS types.ts | backend/app/models.py, frontend/src/lib/types.ts | ✅ Done — PR #16 |
| Frontend: highlight active player indicator in session UI | frontend/src/app/campaigns/[campaignId]/session/[sessionId]/page.tsx | ✅ Done — PR #17 |
| Discord bot: announce whose turn it is in game channel | discord-bot/bot/cogs/game.py | ⚠️ Partial — WS-to-Discord bridge pending; per-channel notify is Phase follow-up |
1.2 Player Identification by Voice
Currently, Transcribe STT returns raw text with no speaker attribution. We need to know which player spoke.
Approach: Each player has their own LiveKit voice track. The STT pipeline is already per-browser (each client sends transcribe_start/chunk/end) — already identifies speaker by WS connection (which maps to user_id). Confirm this mapping is passed through to the DM prompt.
| Task | File(s) | Status |
|---|---|---|
Verify user_id is attached to WS transcribe messages | backend/app/api/sessions.py | ✅ Done — user_id is always known from WS connection |
Include speaker name in DM engine prompt: Player [Name] says: "..." | backend/app/api/sessions.py | ✅ Done — PR #18, attributed_action prefix |
Discord bot: map Discord user → campaign character for /announce | discord-bot/bot/cogs/dice.py | ✅ Done — /announce command added (PR #18); character mapping deferred |
1.3 Hot Phrase & Slash Command Invocation
Players should be able to address the DM explicitly via voice or text.
| Task | File(s) | Status |
|---|---|---|
| Add configurable DM hot phrase to campaign settings (e.g., “Hey DM”, “Dungeon Master”) | backend/app/models.py (Campaign.metadata_) | 🔲 Planned |
| STT post-processor: detect hot phrase and route as DM address vs. ambient speech | backend/app/ws/manager.py | 🔲 Planned |
Add /dm <message> slash command to Discord bot | discord-bot/bot/cogs/game.py | ✅ Done — already implemented in game.py |
| Frontend: add DM address button + keyboard shortcut for text chat | frontend/src/components/ChatInput.tsx | 🔲 Planned |
Phase 2 — Action Flow & Non-Active Player Rules · 2026-04-06 → 2026-04-24
Structured action resolution. Non-active players can participate meaningfully without breaking turn order.
Milestone: M2 — Full Action Flow (2026-04-24)
2.1 Action Confirmation Flow
Before executing an action, the DM explains it back and asks for confirmation. Easy-mode players get additional consequence hints.
New WS message types: action_pending, action_confirmed, action_cancelled
| Task | File(s) | Status |
|---|---|---|
Add difficulty_mode field to Character model (easy, normal, hardcore) | backend/app/models.py, Alembic migration | ✅ Done — PR #21 |
Sync data contracts: update Pydantic models & Frontend TS types.ts | backend/app/models.py, frontend/src/lib/types.ts | ✅ Done — PR #21 |
DM engine: on action parse, emit action_pending with description + optional consequences. Set generate_tts=False to minimize Polly costs. | backend/app/ai/dm_engine.py | ✅ Done — PR #24 |
Handle --auto-confirm, -y, -ac text flags to skip confirmation | backend/app/ws/manager.py | ✅ Done — PR #24 |
| Handle verbal “confirm” keyword in STT post-processor | backend/app/ws/manager.py | 🔲 Planned |
| Frontend: action confirmation dialog (show DM description + confirm/cancel) | frontend/src/components/ActionConfirm.tsx | ✅ Done — PR #29 |
| Discord bot: send action pending as embed, react-to-confirm flow | discord-bot/bot/cogs/game.py | 🔲 Planned |
2.2 Non-Active Player Rules
| Rule | Implementation | Status |
|---|---|---|
| Non-active players can ask questions — DM responds without advancing turn | WS: check active_player_id before routing to DM | ✅ Done — PR #26 |
| Non-active players cannot roll unless DM permits | Block dice_roll WS messages for non-active players; DM can grant roll permission via [ALLOW_ROLL: player_id] directive | ✅ Done — PR #26 |
| Non-active players can queue actions | New ActionQueue per session in ws/manager.py or Redis (in-process is fine for single replica) | ✅ Done — PR #26 |
| DM reminds queued players of their action when their turn starts | Auto-inject queue into DM turn context | ✅ Done — PR #26 |
| Task | File(s) | Status |
|---|---|---|
Implement per-session ActionQueue (list of {player_id, action_text, timestamp}) | backend/app/ws/manager.py | ✅ Done — PR #26 |
Add queue_action WS message type | backend/app/ws/manager.py | ✅ Done — PR #26 |
Add [ALLOW_ROLL: player_id] directive parser | backend/app/ai/dm_engine.py | ✅ Done — PR #26 |
| Frontend: queue panel for non-active players | frontend/src/components/ActionQueue.tsx | ✅ Done — PR #29 |
| DM context builder: inject queued actions for incoming active player. Token Budget Impact: Deduct 250 tokens from Tier 3 budget. | backend/app/ai/dm_engine.py, backend/app/lore/tiers.py | ✅ Done — PR #26 |
2.3 Player Votes for Indeterminate Outcomes
When outcomes don’t affect plot, the DM can call a player vote rather than deciding unilaterally.
New [VOTE: question | option_a | option_b | timeout_seconds] directive
| Task | File(s) | Status |
|---|---|---|
Add [VOTE:] directive parser | backend/app/ai/dm_engine.py | ✅ Done — PR #28 |
Emit vote_started WS message with options and countdown | backend/app/ws/manager.py | ✅ Done — PR #28 |
Handle vote_cast WS messages; tally and emit vote_result | backend/app/ws/manager.py | ✅ Done — PR #28 |
| Frontend: vote overlay component with countdown timer | frontend/src/components/VoteOverlay.tsx | ✅ Done — PR #29 |
| Discord bot: send poll embed for Discord-side votes | discord-bot/bot/cogs/game.py | 🔲 Planned |
Phase 3 — In-Character Mode & Meta Detection · 2026-04-27 → 2026-05-08
The DM assumes all player speech is in-character. Players need a way to step out of character cleanly.
Milestone: M3 — IC/OOC + Personality (2026-05-08)
3.1 Meta-Mode Detection
| Task | File(s) |
|---|---|
Add meta-mode keyword detection: /meta, <OOC>, <out of character>, [I'm not speaking as my character] | backend/app/ws/manager.py |
| When meta detected: bypass DM in-character processing, route as OOC query | backend/app/ai/dm_engine.py (new meta_query path) |
DM engine: if message doesn’t fit character/context, ask player to clarify or use /meta | DM system prompt update |
Frontend: /meta button in chat UI + visual indicator (different message style) | frontend/src/components/ChatInput.tsx, ChatMessage.tsx |
Discord bot: prefix /meta or [meta] to strip IC assumption | discord-bot/bot/cogs/game.py |
3.2 DM Personality Tuning
| Task | File(s) |
|---|---|
Add dm_personality field to Campaign model (enum: serious, balanced, comedic) | backend/app/models.py, Alembic |
Sync data contracts: update Pydantic models & Frontend TS types.ts | backend/app/models/, frontend/src/lib/types.ts |
| Update DM system prompt to reflect personality setting | backend/app/ai/dm_engine.py |
| Add personality override to campaign settings UI | frontend/src/app/campaigns/[id]/settings/ |
Phase 4 — Book & Media Content Generation · 2026-05-11 → 2026-05-15
When a player opens/interacts with a book or piece of media, generate appropriate contents.
New [ITEM_CONTENT: item_id | type] directive
| Task | File(s) |
|---|---|
Add [ITEM_CONTENT:] directive to DM engine | backend/app/ai/dm_engine.py |
On directive: call Claude Haiku to generate item contents. Update AWS Cost Table in README.md. Set generate_tts=False. | New backend/app/ai/item_gen.py, README.md |
Cache generated contents to WorldItem row (avoid regenerating on re-open) | backend/app/world/models.py |
New WS message item_contents → frontend renders parchment-style modal | backend/app/ws/manager.py, frontend/src/components/ItemContentsModal.tsx |
| DM prompt guidance: “Include mundane items. Not everything is plot-significant.” | DM system prompt update |
Phase 5 — /report & /flag Player Feedback System · 2026-05-18 → 2026-05-22
Players need a mechanism to flag AI misbehavior without breaking immersion.
Milestone: M4 — Content & Reporting (2026-05-22)
| Task | File(s) |
|---|---|
Create DmReport DB model (id, session_id, reporter_id, dm_response_id, reason, created_at) | backend/app/models.py, Alembic migration |
Sync data contracts: update Pydantic models & Frontend TS types.ts | backend/app/models.py, frontend/src/lib/types.ts |
POST /sessions/{session_id}/reports endpoint | backend/app/api/sessions.py |
Add /report <reason> + /flag as aliases to text chat and Discord slash commands | discord-bot/bot/cogs/game.py, frontend/src/components/ChatMessage.tsx |
| Admin dashboard: view flagged responses, mark resolved | frontend/src/app/admin/reports/, backend/app/api/admin.py |
Emit report_received WS message: “Your report has been logged.” | backend/app/ws/manager.py |
Prometheus counter: dm_reports_total labeled by reason | backend/app/api/sessions.py |
Phase 6 — Combat Tracker & Interactive UI · 2026-05-25 → 2026-06-12
Currently the DM narrates combat with no structured HP tracking. This replaces mental bookkeeping with a live UI.
Milestone: M5 — Combat Tracker (2026-06-12)
| Task | File(s) |
|---|---|
Add CombatState to WorldEngine (initiative order, combatant HP, conditions). Token Budget Impact: Reallocate 300 tokens from World Engine digest. | backend/app/world/engine.py, backend/app/lore/tiers.py |
Sync data contracts: update Pydantic models & Frontend TS types.ts | backend/app/models.py, frontend/src/lib/types.ts |
New [COMBAT: start | end | damage player:X 12 | heal npc:Y 8] directives | backend/app/ai/dm_engine.py |
Emit combat_update WS message with full combatant state | backend/app/ws/manager.py |
| Frontend combat tracker component & Leaflet map tactical integration | frontend/src/components/CombatTracker.tsx, Map UI |
| Auto-integrate with death protection: trigger bias when PC HP < 25% | backend/app/game/death.py |
Phase 7 — Spell/Ability Reference & Campaign Invitation Links · 2026-06-15 → 2026-06-19
Milestone: M6 — Feature Complete v1.0 (2026-06-19)
| Task | File(s) |
|---|---|
Discord /spell <name> command — lore search + SRD fallback via Bedrock | discord-bot/bot/cogs/lore.py |
| Campaign invitation shareable URL (extend admin invite with public token) | backend/app/api/admin.py, frontend/src/app/invite/[token]/ |
Feature Priority Matrix
| Feature | Player Impact | Effort | Phase |
|---|---|---|---|
| Fix broken requirements (email, discord voice) | 🔴 Blocking | XS | 0 |
| Session summary generation (Tier 2 lore) | 🔴 Blocking AI quality | S | 0 |
| Turn structure + active player | 🔴 Core gameplay | M | 1 |
| Action confirmation flow | 🟠 High UX value | M | 2 |
| Non-active player queuing | 🟠 High UX value | S | 2 |
| Meta-mode detection | 🟠 High UX value | S | 3 |
| In-character assumption + clarification | 🟠 High UX value | S | 3 |
/report / /flag system | 🟡 Trust & safety | S | 5 |
| Book/media content generation | 🟡 Immersion | M | 4 |
| Player votes | 🟡 Immersion | S | 2 |
| DM personality tuning | 🟡 QoL | S | 3 |
| Combat tracker UI | 🟡 QoL | L | 6 |
| Spell reference Discord command | 🟢 Nice to have | S | 7 |
| Campaign invitation links | 🟢 Nice to have | S | 7 |
New Backend Directive Summary
Directives emitted by Claude in DM response text, parsed by dm_engine.py:
| Directive | Example | Phase |
|---|---|---|
[TURN: player_id] | [TURN: usr_abc123] | 1 |
[ALLOW_ROLL: player_id] | [ALLOW_ROLL: usr_abc123] | 2 |
[VOTE: question | option_a | option_b | 30] | [VOTE: Which door? | Left | Right | 30] | 2 |
[ITEM_CONTENT: item_id | book] | [ITEM_CONTENT: itm_7 | tome] | 4 |
[COMBAT: start] / [COMBAT: damage player:X 12] | — | 6 |
Existing directives (already implemented): [ROLL:], [SCENE:], [MUSIC:], [WORLD:]
New WebSocket Message Types
Messages added across phases (append to existing list in backend/app/ws/manager.py):
| Message | Direction | Phase |
|---|---|---|
turn_changed | server → clients | 1 |
action_pending | server → client | 2 |
action_confirmed | client → server | 2 |
action_cancelled | client → server | 2 |
queue_action | client → server | 2 |
vote_started | server → clients | 2 |
vote_cast | client → server | 2 |
vote_result | server → clients | 2 |
item_contents | server → client | 4 |
report_received | server → client | 5 |
combat_update | server → clients | 6 |
New DB Columns / Tables
| Model | Change | Migration |
|---|---|---|
Session | Add active_player_id (FK → users.id, nullable) | 003_phase1_turns |
Character | Add difficulty_mode (enum: easy/normal/hardcore, default normal) | 004_phase2_action_flow |
Campaign | Add dm_personality (enum: serious/balanced/comedic, default balanced) | 005_phase3_personality |
Campaign | Add dm_hot_phrase (str, default "Hey DM") | 005_phase3_personality |
DmReport | New table — flagged DM responses | 006_phase5_reports |
Do not bundle these into a single revision; keep each migration aligned to phase boundaries for safer rollback.
Acceptance Tests (v1.0 Done Definition)
Use these as executable acceptance checks, not aspirational goals.
| ID | Scenario | Expected Result |
|---|---|---|
| A1 | Player uses voice hot phrase or /dm | Message is routed as DM address and receives DM response |
| A2 | STT message arrives from player connection | DM prompt contains speaker identity (player/character attribution) |
| A3 | Non-active player sends action message | Server blocks action and returns clear feedback |
| A4 | Non-active player queues action | Action appears in queue and is surfaced when their turn begins |
| A5 | Player uses confirm or -y/-ac | Pending action transitions to confirmed without extra prompt |
| A6 | Player sends meta/OOC marker | Message is routed OOC and does not break IC flow |
| A7 | Player submits /report | DmReport row is created and visible in admin review UI |
| A8 | Player interacts with book/media item twice | First call generates content; second call returns cached content |
| A9 | Session end workflow runs | Tier 2 summary generated and available in lore context |
| A10 | Combat directives execute | Combat tracker updates HP/initiative in realtime UI |
| A11 | Contract-change PR lands | Model/migration/Pydantic/TS/WS checklist all completed |
| A12 | Feature adds Bedrock/Polly usage | Token budget note added and README.md AWS cost table updated |
Explicitly Out of Scope for v1.0
- Multi-replica WebSocket state synchronization (Redis pub/sub migration)
- Discord video streaming (not supported by Discord bots)
- Fully automated combat adjudication without DM confirmation
- New auth providers beyond Google + Discord account link