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

MilestoneTarget DateDeliverable
M0 — Platform Stable2026-03-13All broken deps fixed, migrations applied, Tier 2 lore generating, smoke tests passing
M1 — First Playable Session2026-04-03Turn structure live, player identity in DM prompt, hot phrase + /dm command working
M2 — Full Action Flow2026-04-24Action confirmation, non-active player queue, player votes operational
M3 — IC/OOC + Personality2026-05-08Meta-mode detection, in-character assumption, DM personality tuning deployed
M4 — Content & Reporting2026-05-22Book/media content generation live, /report + /flag system in admin dashboard
M5 — Combat Tracker2026-06-12Live HP tracker UI, [COMBAT:] directives wired to death protection
M6 — Feature Complete v1.02026-06-19Spell 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:

IssueBlocksStatus
Alembic migrations missing for core + world tablesDB 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 stateWebSocket state corruption✅ Fixed — changed to --workers 1
generate_tts=True hardcoded in WS handlerPolly cost creep✅ Fixed — now reads from message payload
asyncio.get_event_loop() deprecated in asyncRuntime warnings/crashes✅ Fixed — all 6 sites use get_running_loop()
init_db() uses create_all() instead of AlembicSchema drift✅ Fixed — removed create_all, runs Alembic head
Frontend types out of sync with backendRuntime type mismatches✅ Fixed — types.ts corrected
Unused npm deps (socket.io-client, howler, zustand)Bundle bloat✅ Fixed — removed from package.json
Bot→backend auth missingUnauthenticated bot API calls✅ Fixed — bot_service_token added
Session page 400+ LOC monolithMaintainability✅ Fixed — 6 components extracted
Zero test coverageEvery new feature ships untestedStill 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.

PhaseGate IDRequired Validation
Phase 0G0.1backend + discord-bot dependencies install successfully in clean env and Docker build
Phase 0G0.2Alembic upgrade succeeds from empty DB and from current prod-like snapshot
Phase 0G0.3World engine seed check: every campaign has WorldState row post-migration
Phase 0G0.4Session summary generation writes Tier 2 rows for ended sessions
Phase 0G0.5Backend smoke tests pass: auth, campaigns, sessions, WebSocket connect/disconnect
Phase 1G1.1Active turn enforcement blocks non-active action messages server-side
Phase 1G1.2[TURN:] directive updates active_player_id and emits turn_changed
Phase 1G1.3STT speaker identity reaches DM prompt with player/character attribution
Phase 2G2.1Action confirmation lifecycle works (action_pending → confirm/cancel)
Phase 2G2.2Queue + vote flows are deterministic under reconnect and timeout
Phase 3G3.1IC/OOC meta detection routes correctly without advancing turn
Phase 4G4.1Item content generation is cached and not regenerated on repeat open
Phase 5G5.1/report persists reports and admin can resolve status
Phase 6G6.1[COMBAT:] directives mutate combat state and UI renders full tracker
Phase 7G7.1/spell returns deterministic fallback behavior when lore lookup misses

Critical Path & Dependencies

IDDependencyWhy It Matters
D1Phase 0 migrations before all feature phasesPrevents schema drift and runtime crashes
D2Session summaries (0.5) before DM behavior tuning (Phase 3+)Tier 2 context quality affects DM reliability
D3Turn system (Phase 1) before action confirmation/queue/votes (Phase 2)All action semantics depend on active-player state
D4Contract sync (backend models ↔ frontend types) before UI rolloutAvoids runtime type mismatches in WS/API payloads
D5Observability additions per phase before production rolloutEnables fast triage and safe rollback decisions

Migration & Rollback Strategy

RuleRequirement
Migration granularityKeep migrations phase-scoped; avoid combining unrelated phases in one revision
Revision namingUse phase-prefixed revisions (example: 003_phase1_turns, 004_phase2_action_flow)
Expand/contractFor WS/API schema changes: add fields/messages first, then enforce behavior in next step
Rollback safetyEvery migration PR includes downgrade verification notes
Data migrationsBackfill scripts must be idempotent and re-runnable
Feature flagsNew 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.py or 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

AreaRequired Signal
Turn enforcementCounter: blocked non-active actions; event log with session/player IDs
Action confirmationCounters: pending/confirmed/cancelled actions
Queue/votesCounters: queued actions, vote starts/casts/timeouts
Meta modeCounter: meta-routed messages
Item contentCounter: generated vs cache-hit item contents
ReportsCounter: dm_reports_total by reason + admin resolution count
Combat trackerCounter: combat start/end, directive parse errors

Token Budget & Cost Governance

ChangeToken/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 usageUpdate AWS cost table in README.md before phase close

Risk Register (Top 6)

RiskTriggerMitigation
WS state corruption on disconnectRapid reconnect/disconnect during turn changesPer-connection exception isolation and deterministic active-player reassignment
Tier overflow/regressionAdded context blocks exceed model budgetBudget tests + trim-order assertions in lore assembly
Migration driftManual DB hotfixes diverge from Alembic historyEnforce migration-only schema changes + pre-deploy upgrade check
Bot voice regressionsMissing voice deps/FFmpeg in imageCI Docker build check + runtime startup health assertion
Report abuse/noiseLow-signal mass flagsAdd reason taxonomy + admin resolution workflow
Cost creepUnbounded Polly/Bedrock callsPer-feature cost note and default non-TTS operation

Delivery Cadence

Cadence ItemRequirement
Weekly phase reviewPost a short status update: gates passed/failed, blockers, next 7-day goals
PR size controlKeep feature PRs under ~600 LOC when possible; split protocol + UI changes if larger
Deployment policyDo not deploy a phase to production until all prior phase gates are passing
Evidence retentionKeep gate evidence links in merged PRs for rollback/debug history

Ownership & Accountability

AreaPrimary OwnerRequired Sign-off
Backend API + DB migrationsBackend leadMigration + API review
WebSocket protocol changesBackend leadWS contract review
Frontend contract + UI rolloutFrontend leadType/contract review
Discord bot commands/voiceBot leadBot command + metrics review
Prompt/lore/token budget changesAI leadToken budget + cost review
Production rolloutPlatform leadGate pass confirmation

Non-Functional Targets (v1.0)

Target IDMetricThresholdValidation
N1DM turn p95 latency (text-only, no image/TTS)≤ 4.0sSampled from backend request logs
N2WebSocket reconnect recovery≤ 3s medianReconnect simulation test
N3Session summary generation delay after session end≤ 60s p95Async job timing logs
N4dm_response delivery success during active session≥ 99.5%WS success/error counters
N5Error budget for 5xx on gameplay APIs< 1% weeklyAPI metrics dashboard

Phase Readiness Scorecard

Use this lightweight scorecard before marking a phase complete and before production rollout.

DimensionWeightScoring Rule
Exit gates passed40%100 if all required gates pass, else 0
Test health25%% passing for phase-specific automated checks
Observability coverage15%100 if required counters/logs exist and are visible in dashboard, else 0
Contract sync completeness10%100 if all checklist items are complete, else 0
Rollback readiness10%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 IDQuestionDeadline
O1Should action queue remain in-process for v1.0 or move to Redis now?Before Phase 2 implementation starts
O2Vote 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
O4Combat 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)

#TaskFile(s)Status
0.1Add aiosmtplib to backend requirementsbackend/requirements.txt✅ Already present
0.2Add discord.py[voice] + PyNaCl + ffmpeg to bot Dockerfilediscord-bot/requirements.txt, Dockerfile✅ Already present
0.3Write Alembic migrations for all tablesbackend/alembic/versions/000_initial_schema.py, 001_world_invite.py✅ Done — core tables + world/invite tables
0.4Create WorldState row on campaign createbackend/app/api/campaigns.py✅ Done
0.5Implement session summary auto-generationbackend/app/lore/tiers.py (summarize_session())✅ Already implemented
0.6Add HNSW pgvector index to lore embeddingsAlembic migration🔲 Still needed
0.7Write smoke tests for backend APIbackend/tests/🔲 Still needed
0.8Fix Dockerfile --workers 2--workers 1backend/Dockerfile✅ Done
0.9Fix generate_tts=True hardcoding in WS handlerbackend/app/api/sessions.py✅ Done
0.10Fix asyncio.get_event_loop()get_running_loop()backend/app/ai/bedrock.py, dm_engine.py✅ Done
0.11Add bot service token authbackend/app/config.py, auth/deps.py, discord-bot/bot/config.py✅ Done
0.12Fix frontend types to match backend modelsfrontend/src/lib/types.ts✅ Done
0.13Remove unused npm depsfrontend/package.json✅ Done
0.14Extract session page inline componentsfrontend/src/components/session/✅ Done
0.15Remove create_all() from init_db(), use Alembic onlybackend/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.

TaskFile(s)Status
Add active_player_id to Session model + Alembic migrationbackend/app/models.py, 003_phase1_turns.py✅ Done — PR #7
Add [TURN: player_id] directive parser to DM enginebackend/app/ai/dm_engine.py✅ Done — PR #10
Enforce turn in WebSocket hub: block action messages from non-active playersbackend/app/api/sessions.py✅ Done — PR #15
Handle WebSocketDisconnect gracefully: reassign or clear active_player_idbackend/app/api/sessions.py✅ Done — PR #16
Emit turn_change WS message when active player changesbackend/app/ws/manager.py✅ Done — PR #15
Sync data contracts: update Pydantic models & Frontend TS types.tsbackend/app/models.py, frontend/src/lib/types.ts✅ Done — PR #16
Frontend: highlight active player indicator in session UIfrontend/src/app/campaigns/[campaignId]/session/[sessionId]/page.tsx✅ Done — PR #17
Discord bot: announce whose turn it is in game channeldiscord-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.

TaskFile(s)Status
Verify user_id is attached to WS transcribe messagesbackend/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 /announcediscord-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.

TaskFile(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 speechbackend/app/ws/manager.py🔲 Planned
Add /dm <message> slash command to Discord botdiscord-bot/bot/cogs/game.py✅ Done — already implemented in game.py
Frontend: add DM address button + keyboard shortcut for text chatfrontend/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

TaskFile(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.tsbackend/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 confirmationbackend/app/ws/manager.py✅ Done — PR #24
Handle verbal “confirm” keyword in STT post-processorbackend/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 flowdiscord-bot/bot/cogs/game.py🔲 Planned

2.2 Non-Active Player Rules

RuleImplementationStatus
Non-active players can ask questions — DM responds without advancing turnWS: check active_player_id before routing to DM✅ Done — PR #26
Non-active players cannot roll unless DM permitsBlock 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 actionsNew 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 startsAuto-inject queue into DM turn context✅ Done — PR #26
TaskFile(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 typebackend/app/ws/manager.py✅ Done — PR #26
Add [ALLOW_ROLL: player_id] directive parserbackend/app/ai/dm_engine.py✅ Done — PR #26
Frontend: queue panel for non-active playersfrontend/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

TaskFile(s)Status
Add [VOTE:] directive parserbackend/app/ai/dm_engine.py✅ Done — PR #28
Emit vote_started WS message with options and countdownbackend/app/ws/manager.py✅ Done — PR #28
Handle vote_cast WS messages; tally and emit vote_resultbackend/app/ws/manager.py✅ Done — PR #28
Frontend: vote overlay component with countdown timerfrontend/src/components/VoteOverlay.tsx✅ Done — PR #29
Discord bot: send poll embed for Discord-side votesdiscord-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

TaskFile(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 querybackend/app/ai/dm_engine.py (new meta_query path)
DM engine: if message doesn’t fit character/context, ask player to clarify or use /metaDM 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 assumptiondiscord-bot/bot/cogs/game.py

3.2 DM Personality Tuning

TaskFile(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.tsbackend/app/models/, frontend/src/lib/types.ts
Update DM system prompt to reflect personality settingbackend/app/ai/dm_engine.py
Add personality override to campaign settings UIfrontend/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

TaskFile(s)
Add [ITEM_CONTENT:] directive to DM enginebackend/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 modalbackend/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)

TaskFile(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.tsbackend/app/models.py, frontend/src/lib/types.ts
POST /sessions/{session_id}/reports endpointbackend/app/api/sessions.py
Add /report <reason> + /flag as aliases to text chat and Discord slash commandsdiscord-bot/bot/cogs/game.py, frontend/src/components/ChatMessage.tsx
Admin dashboard: view flagged responses, mark resolvedfrontend/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 reasonbackend/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)

TaskFile(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.tsbackend/app/models.py, frontend/src/lib/types.ts
New [COMBAT: start | end | damage player:X 12 | heal npc:Y 8] directivesbackend/app/ai/dm_engine.py
Emit combat_update WS message with full combatant statebackend/app/ws/manager.py
Frontend combat tracker component & Leaflet map tactical integrationfrontend/src/components/CombatTracker.tsx, Map UI
Auto-integrate with death protection: trigger bias when PC HP < 25%backend/app/game/death.py

Milestone: M6 — Feature Complete v1.0 (2026-06-19)

TaskFile(s)
Discord /spell <name> command — lore search + SRD fallback via Bedrockdiscord-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

FeaturePlayer ImpactEffortPhase
Fix broken requirements (email, discord voice)🔴 BlockingXS0
Session summary generation (Tier 2 lore)🔴 Blocking AI qualityS0
Turn structure + active player🔴 Core gameplayM1
Action confirmation flow🟠 High UX valueM2
Non-active player queuing🟠 High UX valueS2
Meta-mode detection🟠 High UX valueS3
In-character assumption + clarification🟠 High UX valueS3
/report / /flag system🟡 Trust & safetyS5
Book/media content generation🟡 ImmersionM4
Player votes🟡 ImmersionS2
DM personality tuning🟡 QoLS3
Combat tracker UI🟡 QoLL6
Spell reference Discord command🟢 Nice to haveS7
Campaign invitation links🟢 Nice to haveS7

New Backend Directive Summary

Directives emitted by Claude in DM response text, parsed by dm_engine.py:

DirectiveExamplePhase
[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):

MessageDirectionPhase
turn_changedserver → clients1
action_pendingserver → client2
action_confirmedclient → server2
action_cancelledclient → server2
queue_actionclient → server2
vote_startedserver → clients2
vote_castclient → server2
vote_resultserver → clients2
item_contentsserver → client4
report_receivedserver → client5
combat_updateserver → clients6

New DB Columns / Tables

ModelChangeMigration
SessionAdd active_player_id (FK → users.id, nullable)003_phase1_turns
CharacterAdd difficulty_mode (enum: easy/normal/hardcore, default normal)004_phase2_action_flow
CampaignAdd dm_personality (enum: serious/balanced/comedic, default balanced)005_phase3_personality
CampaignAdd dm_hot_phrase (str, default "Hey DM")005_phase3_personality
DmReportNew table — flagged DM responses006_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.

IDScenarioExpected Result
A1Player uses voice hot phrase or /dmMessage is routed as DM address and receives DM response
A2STT message arrives from player connectionDM prompt contains speaker identity (player/character attribution)
A3Non-active player sends action messageServer blocks action and returns clear feedback
A4Non-active player queues actionAction appears in queue and is surfaced when their turn begins
A5Player uses confirm or -y/-acPending action transitions to confirmed without extra prompt
A6Player sends meta/OOC markerMessage is routed OOC and does not break IC flow
A7Player submits /reportDmReport row is created and visible in admin review UI
A8Player interacts with book/media item twiceFirst call generates content; second call returns cached content
A9Session end workflow runsTier 2 summary generated and available in lore context
A10Combat directives executeCombat tracker updates HP/initiative in realtime UI
A11Contract-change PR landsModel/migration/Pydantic/TS/WS checklist all completed
A12Feature adds Bedrock/Polly usageToken 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