Date: April 17, 2026
Project: RPG Dungeon Master Discord Bot
Status: Discord runtime hardening is substantially closed out for logging, DM interactions, combat turn flow, inventory sync, and command guidance; larger campaign architecture is still partial
🤖 AI-Generated Project: This entire project was created by giving Claude Opus 4.5 a single prompt asking it to transform ussybot into an RPG Dungeon Master bot.
What is usable today:
- Discord-based RPG play with sessions, characters, inventory, combat, quests, NPCs, AI DM chat, and basic persistence.
- Browser chat with persisted session-scoped history and live side panels for combat, spells, status effects, and location connections.
- Web dashboard CRUD for many gameplay entities and game-data browsing/editing.
What is not yet coherent end to end:
- There is not yet one canonical campaign lifecycle across Discord and browser.
/game,/session, and/resumestill split lifecycle responsibility.- Character creation, combat, and quest progression still have duplicate or drifting implementations.
- Theme/content-pack foundation is in place (
world_theme,content_pack_id,fantasy_core, loader, pack-aware web/tool reads), but Discord runtime parity is still incomplete. - Faction runtime and storyline graph foundations are now landed, but quest lifecycle wiring, map/discovery systems, and full snapshot continuity are still not implemented end to end.
Reference spec for the next implementation wave:
WORLDBUILDING_AND_CAMPAIGN_GAP_SPEC.md
The highest-priority architectural gaps are:
- remaining larger canonicalization and roadmap work beyond the landed Discord runtime parity slices
- duplicate lifecycle flows across
/game,/session, and/resume - remaining character creation/combat canonicalization beyond the landed slices
- incomplete browser/dashboard contracts for campaign editing/admin parity
- larger roadmap features: map/discovery systems and deeper campaign canonicalization
The implementation-ready source of truth for these gaps is WORLDBUILDING_AND_CAMPAIGN_GAP_SPEC.md.
This follow-up pass continued the worldbuilding/campaign gap-spec implementation beyond browser continuity and into Discord runtime coherence.
src/cogs/game_persistence.py:
- Added context-aware session resolution so story log, recap, summary, save, resume, quest views, and auto-logging prefer the channel-bound session over guild-global fallbacks
src/cogs/combat.py:
- Updated
/combat startto bind encounters to the channel/session context instead of the first active guild session
src/cogs/dm_chat.py:
- Persisted channel binding on final active-session fallback so later runtime flows converge on the same session
src/cogs/game_master.py:
- Made the Discord GM character interview session-bound and content-pack aware
- Loaded pack-aware races/classes/starter kits/spells for interview creation
- Created interview characters with
session_id, auto-joined them to the session, and provisioned real gold/spells through canonical character tables
src/chat_handler.py:
- Added actor-scoped tool routing for batched multiplayer DM turns so player-specific tool calls no longer default to the first actor in the batch
src/utils.py:
- Added shared runtime session/content resolution helpers for live Discord command surfaces
src/cogs/spells.py:
- Made cast/learn/info/quickcast/autocomplete resolve
spells.jsonthrough the active session content pack instead of legacy flat files
src/cogs/skills.py:
- Made skill tree rendering, learn/use/info flows, and skill autocomplete resolve
skills.jsonthrough the active session content pack instead of legacy flat files
src/cogs/inventory.py:
- Made inventory views, item details, shop flows, slash-command use, and quick-use resolve
items.jsonthrough the active session content pack instead of legacy flat files
src/cogs/combat.py:
- Updated combat consumable item selection to use the same session-scoped pack item metadata as inventory
src/cogs/characters.py:
- Replaced the standalone
/character createrace/class/stat allocation flow with a thin compatibility wrapper that delegates to the canonical session-bound GM interview flow
src/tools.py + src/cogs/combat.py:
- Unified slash-command combat setup/join/spawn with the canonical combat encounter and participant creation helpers used by tool-driven combat
web/frontend/src/main.ts + web/api.py:
- Replaced campaign creator review/edit placeholder actions with real pre-finalize editing flows and finalize-time persistence hardening for edited/client-added preview data
Tests:
- Added regression coverage for channel-bound session selection, GM interview session/pack binding, batched actor-context tool routing, runtime session helper preference, and pack-aware spell runtime lookups
Local verification completed with:
.venv/bin/python -m compileall src/utils.py src/chat_handler.py src/cogs/game_persistence.py src/cogs/combat.py src/cogs/dm_chat.py src/cogs/game_master.py src/cogs/spells.py
.venv/bin/pytest tests/test_lifecycle_resume.py tests/test_dm_chat.py tests/test_combat_cog.py -qResults:
- compile checks passed
- focused lifecycle/DM chat/combat regressions passed
- The current v1 Discord runtime pack-awareness slices are landed for GM interview creation, spells, skills, inventory/items, DM action views, combat turn enforcement, reward syncing, and command guidance.
/character createalso now routes into the same canonical character creation path. Remaining work is now the larger roadmap/admin systems and deeper canonicalization gaps.
This pass closed the remaining Discord runtime gaps from the production log triage and multi-domain repair brief.
src/cogs/dm_chat.py:
- Owner-locked slash-command DM action views so other players cannot click through someone else's private action surface
- Deferred info-button interactions immediately and bound slash-command followup messages so timed-out views are removed cleanly
- DM action views now support up to 4 extracted choice buttons and preserve full option text even when button labels are shortened for Discord UI limits
src/chat_handler.py:
- Normalized tool-loop result handling so dicts and JSON-string tool errors surface consistently to the DM loop
src/tools.py + src/tool_schemas.py + src/prompts.py:
- Normalized missing-character inventory/economy errors to plain runtime strings
- Added explicit currency guidance so gold uses
give_gold/take_gold, not inventory item tools - Synced generic combat damage/heal tools back to character HP for player combatants
- Made tool attack AC respect the
defendingstatus and advance turn after resolved non-fumble attacks
src/cogs/combat.py:
- Revalidated combat item use against the active turn, consumed the turn on successful item use, and continued enemy auto-turns
- Failed flee now consumes the turn instead of allowing unlimited retries
- Rejected stale/invalid attack targets and ended combat automatically when one side is eliminated
- Updated combat-start guidance copy to match the current auto-join behavior for session party members
Tests:
- Added focused regression coverage for DM action ownership, info-button deferral, tool-result normalization, combat HP sync, miss-turn advancement, stale combat item use, and failed flee turn consumption
- Added DM chat coverage for 4-option extraction/rendering and full option-text preservation behind shortened button labels
Local verification completed with:
.venv/bin/python -m compileall src/cogs/dm_chat.py src/chat_handler.py src/tools.py src/cogs/combat.py tests/test_dm_chat.py tests/test_tools.py tests/test_combat_cog.py
.venv/bin/pytest tests/ -qResults:
- compile checks passed
- full test suite passed (
259 passed)
This pass finished the next two in-progress web/admin slices after combat and preview-edit unification.
web/frontend/src/main.ts + web/frontend/index.html:
- Completed the location connection modal flow so location detail panels can create canonical connections through the existing API
- Expanded story item cards/editor submission to include canonical
item_type,discovery_conditions, anddm_notes - Expanded story event cards/editor submission to include canonical
event_typeanddm_notes - Updated story event UI to treat
triggeredas the canonical active/resolvable status and display resolution outcomes
src/database.py:
- Hardened story event normalization so legacy editor statuses like
activeandcompletedmap into canonicaltriggeredandresolved - Updated active-event reads to return canonical triggered events instead of depending on legacy
active
Tests:
- Added focused regression coverage for location connection API metadata round-tripping
- Added story item alias/canonical update tests
- Added story event canonical status/update/resolve tests
- Added DB-level normalization tests for story item/event alias handling and active-event reads
Local verification completed with:
.venv/bin/pytest tests/test_database.py tests/test_web_phase7.py -q
npm run buildResults:
- 73 focused tests passed
- frontend TypeScript build passed
This pass completed the next requested slice pair: the canonical location-connections API resource, then the best bounded NPC/location foundation slice from the faction/NPC/monster area.
src/database.py:
- Added canonical location-connection CRUD helpers for create/get/list/update/delete
- Hardened location connection validation for self-links, cross-session links, and duplicate edges
- Added migration guards for older
location_connectionscolumns used by canonical CRUD - Updated NPC create/update flows so
location_idis canonical andlocationtext is synced from the referenced location
web/api.py:
- Added canonical
GET/POST/PATCH/DELETE /api/location-connections - Kept legacy location-scoped connect routes as compatibility wrappers over the canonical DB helper
- Expanded NPC create/update API contracts to accept
location_id
web/frontend/src/main.ts + web/frontend/index.html:
- Switched location connection creation to the canonical resource endpoint
- Added location connection deletion from the location detail view
- Switched NPC create/edit forms from free-text location input to location-backed selects
- Added occupant visibility in location details by showing NPCs assigned to that location
Tests:
- Added DB tests for canonical location-connection CRUD
- Added DB tests for NPC
location_idtolocationtext synchronization - Added API tests for canonical location-connections CRUD and NPC location-based create/update flows
Local verification completed with:
.venv/bin/pytest tests/test_database.py tests/test_web_phase7.py -q
npm run buildResults:
- 78 focused tests passed
- frontend TypeScript build passed
This pass completed the next requested pair after canonical location-connections: editable connection management in the web UI, then a bounded monster foundation slice via template-backed combat spawning.
web/frontend/src/main.ts + web/frontend/index.html:
- Reused the location connection modal for both create and edit flows against the canonical connection resource
- Added explicit edit controls for existing location connections in the location detail view
- Added a compact combat monster-spawn modal to the session combat viewer
- Added monster template preview and spawn actions for active combat
src/tools.py:
- Added shared enemy-template loading/listing helpers backed by the active content pack
- Added canonical template-to-combatant spawn normalization so web/admin monster spawns reuse the same enemy combatant path
web/api.py:
- Added
GET /api/templates/enemies - Added
POST /api/combat/{combat_id}/spawn-template - Kept combat template spawning bound to session/content-pack context
Tests:
- Added API regression coverage for location connection retarget/edit behavior
- Added tool/API regression coverage for template-backed monster listing and combat spawning
Local verification completed with:
.venv/bin/pytest tests/test_web_phase7.py tests/test_tools.py -q
npm run buildResults:
- 67 focused tests passed
- frontend TypeScript build passed
This session implemented the next bounded worldbuilding/campaign slices from WORLDBUILDING_AND_CAMPAIGN_GAP_SPEC.md: Phase 5 (NPC/monster/faction foundation) and Phase 6 (storyline/quest graph foundation).
src/database.py:
- Added faction tables (
factions,faction_memberships,character_faction_reputation) and monster/storyline tables (monster_templates,boss_phases,storylines,storyline_nodes,storyline_edges,storyline_progress,plot_points,plot_clues) - Added migration-safe columns for NPC faction metadata, combat participant template/resource state, quest storyline linkage, and quest progress branch/failure tracking
- Added CRUD/helpers for factions, faction reputation, monster templates, boss phases, storylines, plot points, and clue discovery/reveal flows
- Seeded relational monster templates from
data/game_data/packs/fantasy/core/enemies.json
src/tools.py + src/tool_schemas.py:
- Added faction tools (
get_factions,create_faction,update_faction_reputation,get_character_faction_reputation) - Added monster tools (
spawn_monster,get_stat_block) with template-backed combat spawning while preserving ad hoc fallback - Added storyline/clue tools (
get_storyline_state,advance_storyline_node,create_plot_point,record_clue_discovery,reveal_plot_point)
web/api.py:
- Added faction endpoints, monster template endpoints, storyline endpoints, and plot/clue discovery endpoints
- Fixed
GET /api/templates/npcsto use pack-aware loading - Kept
GET /api/templates/enemiesworking with DB-backed templates plus content-pack fallback
Tests:
- Added focused database and tool coverage for faction CRUD/reputation, template-backed monster spawning, storyline advancement, and clue auto-reveal thresholds
Local verification completed with:
.venv/bin/pytest tests/test_database.py tests/test_tools.py tests/test_web_phase7.py -qResults:
- 142 tests passed in the final focused run
src/cogs/quests.pywas already dirty in the worktree, so the quest lifecycle wiring that would consume the new storyline foundation was intentionally deferred.
This session hardened the new browser chat flow and completed the missing web dashboard play panels.
src/chat_handler.py:
- Added session-based combat fallback in
get_game_context()for web users without a real Discord channel - Added batch character summaries so multiplayer batched prompts include every acting character, not just the first
src/database.py:
- Added
get_active_combat_by_session(session_id) - Added
web_identitiestable helpers for server-issued browser identities - Extended
conversation_historywithsession_idsupport and added session-scoped history lookup - Added migration for existing databases missing
conversation_history.session_id
src/chat_web_identity.py:
- Added server-side UUID generation helper
- Added client IP hashing helper for persisted web identity metadata
src/cogs/dm_chat.py:
- Verified the proactive background DM task survived the Phase 8 refactor
- Hardened the task lifecycle so the loop only starts once and skips work when the bot/LLM context is unavailable
- Updated batched Discord queue entries to preserve each player's
character_id
web/api.py:
- Added
POST /api/chat/identityto mint browser chat UUIDs server-side and persist them inweb_identities - Updated
/api/chatto requireX-Web-Identityand reject unknown browser identities - Added per-IP rate limiting of
10/minuteto/api/chatwithslowapi - Updated
/api/chatto load the last 20 persisted messages for(session_id, web_user_id)before processing and save both sides of each exchange after response generation
web/frontend/index.html:
- Added live browser-chat sidebar panels for combat, spells, location connections, and status effects
web/frontend/src/main.ts:
- Switched browser chat identity bootstrapping to request a server-issued UUID and store it in
localStorage - Updated chat requests to send the UUID via
X-Web-Identity - Changed chat rendering to append messages incrementally and auto-scroll after each
renderMessage()call - Added live dashboard panel refresh for combat, spell management, location connections, and status effects
- Added spell cast shortcuts and short/long rest actions to the browser chat spell panel
web/frontend/styles.css:
- Added the missing styles for detail panels, combat cards, HP bars, spell rows, connection cards, and status chips
requirements.txt:
- Added
fastapi,uvicorn, andslowapi
Tests:
- Added coverage for session-based combat lookup, web identity persistence, session-scoped conversation history, and multi-player batch prompt context
- Refreshed stale DM chat test fixtures to match the current
DMChatconstructor
Local verification completed with:
python3 -m compileall src web tests
.venv/bin/pytest tests/test_database.py -q
.venv/bin/pytest tests/test_dm_chat.py -qResults:
- compile checks passed
- focused database tests passed
- focused DM chat tests passed
- The checked-in frontend toolchain is incomplete in this workspace (
npm run buildfails becausetscis not executable and the installed TypeScript package is missinglib/tsc.js), so end-to-end frontend bundling still needs dependency repair.
This session completed the Phase 7 runtime hardening pass focused on slash commands, guild/session isolation, and owner-safe interactive UI.
src/utils.py:
- Added
get_character_class()helper so cogs can read eitherchar_classor legacyclass - Added
ensure_interaction_owner()helper for owner-locked Discord views
src/cogs/skills.py:
- Fixed
/skillsto use normalized class lookup instead of directly readingcharacter['class'] - Added
get_skill_tree_branches()to normalizeskills.jsonbranch data when stored as a dict - Prevented runtime failures when loading skill trees from current game data
src/cogs/dm_chat.py:
- Added guild validation in
resolve_session()so cross-guild session IDs are ignored - Marked
/checkas@app_commands.guild_only()
src/cogs/game_master.py:
- Added
_get_guild_session()helper and used it for guild-scoped session commands - Updated
DMChat.start_new_session()call sites to passguild_id - Added owner checks to character creation equipment/shop views
- Fixed starter shopping checkout so remaining gold is not reduced twice
src/cogs/sessions.py:
- Added
_get_guild_session()helper and applied it across session view/join/leave/start/pause/end/delete/set-quest flows
src/cogs/game_persistence.py:
- Added
_get_guild_session()helper and applied it to/resume
src/cogs/inventory.py:
- Added owner checks to shop, inventory, item action, and consumable views so only the originating player can interact
Tests:
- Added
tests/test_phase7.pycovering:- guild filtering in
DMChat.resolve_session() - normalized
/skillsclass handling - starter shopping remaining-gold behavior
- guild filtering in
Local verification completed with:
python3 -m compileall src tests
.venv/bin/pytest tests/test_phase7.py tests/test_dm_chat.py -qResults:
- compile checks passed
- 6 focused tests passed
- True live Discord slash-command verification is still not possible in this environment without a valid bot token and server.
Problem: When starting a new game, the LLM model retained context from previous games. For example, "bingo" references from an old game would appear in a completely different church escape adventure.
Root Cause: The get_user_active_session() function was ordering by last_played DESC instead of s.id DESC, causing older but recently-played sessions to take precedence over newly created sessions in the same channel.
src/database.py:
- Changed
get_user_active_session()to order bys.id DESCinstead ofs.last_played DESC NULLS LAST - This ensures the most recently CREATED session takes priority, not most recently played
src/cogs/dm_chat.py:
- Added
get_channel_session_id(channel_id)helper function to retrieve stored session ID for a channel - Modified
get_active_session_id()to prioritize the channel's storedsession_idbefore falling back to database lookup - Updated
get_game_context()to accept optionalsession_idparameter for explicit session targeting - Added debug logging for session resolution
Problem: "Provider blocked by policy" error when using the LLM.
Solution: Changed LLM_MODEL from openai/gpt-5-nano (blocked) to openai/gpt-4o-mini in .env and web/api.py defaults.
Problem: Web-created campaigns failed to save with database errors due to schema mismatches between code and actual database structure.
| Issue | Fix |
|---|---|
Missing created_by in locations INSERT |
Added created_by column with value 'ai' |
Missing updated_at in locations INSERT |
Added updated_at column |
Wrong column location_id in npcs INSERT |
Changed to location (TEXT type) |
Missing is_party_member in npcs INSERT |
Re-added column with value 0 |
Missing created_at in quests INSERT |
Added created_at column |
| Sessions created as 'setup' status | Changed to 'active' status so game starts immediately |
The actual database schema differs from src/database.py in several ways:
locationstable has NOT NULLcreated_bycolumnnpcstable useslocation(TEXT) instead oflocation_id(INTEGER FK)npcstable hasis_party_membercolumn
Game mechanics (dice rolls, skill checks, etc.) are now displayed with styled formatting in Discord responses.
A thread-local tracker system that captures game mechanics during tool execution:
# MechanicType enum includes:
# DICE_ROLL, SKILL_CHECK, SAVING_THROW, ATTACK_ROLL, DAMAGE_ROLL,
# ITEM_GAINED, ITEM_LOST, GOLD_CHANGE, XP_GAINED, LEVEL_UP,
# HP_CHANGE, STATUS_EFFECT, QUEST_UPDATE, LOCATION_CHANGE, NPC_INTERACTION
# Usage pattern:
tracker = new_tracker() # Start fresh tracker
# ... execute tools ...
result = get_tracker()
mechanics_text = result.format_all() # Styled output| Method | What It Tracks |
|---|---|
track_dice_roll() |
Dice rolls with results, modifiers, crits |
track_skill_check() |
Skill checks with DC, success/fail |
track_saving_throw() |
Save rolls with DC, success/fail |
track_attack() |
Attack rolls with hit/miss, damage |
track_damage() |
Damage dealt with type |
track_item_gained() |
Items acquired (name, quantity, rarity) |
track_item_lost() |
Items lost or used |
track_gold_change() |
Gold gained/spent |
track_xp_gained() |
Experience points earned |
track_level_up() |
Level advancement |
track_hp_change() |
HP damage/healing |
track_status_effect() |
Buffs/debuffs applied/removed |
track_quest_update() |
Quest progress |
track_location_change() |
Location transitions |
track_npc_interaction() |
NPC conversations |
_roll_dice()- Now tracks dice rolls_roll_skill_check()- Now tracks skill checks_roll_save()- Now tracks saving throws
Bot responses now include clickable buttons for quick actions and information display.
| Component | Purpose |
|---|---|
PlayerActionButton |
Execute quick player actions (option choices, look around, continue) |
InfoButton |
View game info (character sheet, quest, location, inventory, party) |
GameActionsView |
Combined view with action + info buttons |
QuickActionsView |
Simplified view for exploration |
- 📜 Character - Shows character sheet embed (stats, HP, gold, XP)
- 📋 Quest - Shows active quest details and progress
- 🗺️ Location - Shows current location description and danger level
- 🎒 Inventory - Shows inventory items and equipped gear
- 👥 Party - Shows party composition including NPC companions
process_dm_message()now returns tuple:(response_text, mechanics_text)_delayed_process_queue()creates styled embed with mechanics and attaches button views- Buttons work with ephemeral responses for info (only requestor sees it)
Full web-based campaign creation workflow with AI worldbuilding integration.
| Endpoint | Method | Purpose |
|---|---|---|
/api/campaign/generate-preview |
POST | Generate campaign preview without committing |
/api/campaign/finalize |
POST | Commit generated campaign to database |
/api/campaign/templates |
GET | Get predefined campaign templates |
class CampaignSettings(BaseModel):
guild_id: int
dm_user_id: int
name: str
world_theme: str = "fantasy" # fantasy, sci-fi, horror, modern, steampunk
world_scale: str = "regional" # local, regional, continental, world
magic_level: str = "high" # none, low, medium, high
technology_level: str = "medieval" # primitive to futuristic
tone: str = "heroic" # gritty, heroic, comedic, horror, mystery
num_locations: int = 5
num_npcs: int = 8
num_factions: int = 3
num_quest_hooks: int = 3
world_description: Optional[str] = None
key_events: Optional[str] = None4-step wizard workflow:
- Settings - Campaign name, Discord IDs, template selection, world settings
- Generate - Loading animation with progress while generating
- Review - Preview all generated content, edit/remove items
- Success - Confirmation with stats and navigation
- Classic Fantasy (sword & sorcery)
- Dark Fantasy (gritty survival)
- Steampunk Adventure (Victorian intrigue)
- Cosmic Horror (forbidden knowledge)
- Space Opera (galactic adventures)
- Custom (build your own)
- Campaign wizard steps with active/completed states
- Template card selection grid
- Generation loading animation (orb + ring)
- Progress bar with gradient
- Preview cards with edit/remove actions
- Success screen with stats display
- Form row layouts
- Range slider styling
- Added campaign API functions:
generateCampaignPreview,finalizeCampaign,getCampaignTemplates - New page loader:
loadCampaignCreator() - Template selection with auto-fill
- Step navigation:
goToStep() - Preview population:
populatePreview() - Item editing/removal functions
- Campaign finalization with API call
The bot was failing with "The application did not respond" error when players interacted after game start. Two main issues:
- Missing database column -
prioritycolumn instory_eventstable didn't exist in databases created before schema update - Session isolation failure - Tools were using
get_active_session(guild_id)which returns ANY active session in the guild, not the specific session the user is in
- Added
_run_migrations()method todatabase.py- Automatically adds missing columns to existing databases - Migration adds
prioritycolumn tostory_eventstable if missing - Migration adds
points_of_interestcolumn tolocationstable if missing - Uses
PRAGMA table_info()to check for existing columns before altering
- Fixed query error handling in
get_active_events()- Now usesCOALESCE(priority, 0)and has fallback query - Fixed query error handling in
get_pending_events()- Same fix as above - Added
_get_session_for_context()helper intools.py- Proper session lookup:- First checks
context['session_id']if passed - Then checks
get_user_active_session(guild_id, user_id)for user's session - Finally falls back to
get_active_session(guild_id)as last resort
- First checks
- Updated all tool functions to use
_get_session_for_context()instead ofget_active_session() - Added
session_idto context indm_chat.pywhen executing tools
| Tool | Change |
|---|---|
_start_combat |
Now uses session from context, adds only party members from THAT session |
_create_quest |
Uses session from context |
_create_npc |
Uses session from context |
_get_party_info |
Uses session from context |
_add_story_entry |
Uses session from context |
_get_story_log |
Uses session from context |
_create_location |
Uses session from context |
_move_party_to_location |
Uses session from context |
_create_story_item |
Uses session from context |
_get_story_items |
Uses session from context |
_create_story_event |
Uses session from context |
_get_active_events |
Uses session from context |
_generate_npc |
Uses session from context |
_long_rest |
Uses session from context |
_end_combat_with_rewards |
Uses session from context |
- Added Spell & Ability Tools section - Documents
get_character_spells,cast_spell,get_character_abilities,use_ability - Added Skill Check Tools section - Documents
roll_skill_check - Added Leveling & Progression section - Explains automatic level ups and XP thresholds
- Added 3 new critical rules - Guidance on spell/ability usage and XP rewards
- Fixed
/api/gamedata/itemsresponse structure - Now returns{items: {weapons: [...], armor: [...], ...}}instead of raw JSON, fixing item database display - Added
PUT /api/gamedata/classes- Bulk update endpoint for saving class edits from frontend - Added
PUT /api/gamedata/races- Bulk update endpoint for saving race edits from frontend - Added
PUT /api/gamedata/skills/trees/{class_id}- Update skill tree for a specific class - Added
PUT /api/gamedata/skills- Bulk update all skills data
- Fixed classes display - Now handles
primary_statfield (was expectingprimary_ability) - Fixed abilities rendering - Classes in data use abilities as object keyed by level, now properly flattens for display
- Fixed class editing - Edit form now properly populates and saves class data
- Added skill tree editing - New
editSkillBranch()andsaveSkillBranch()functions for modifying skill branches - Item database now loads correctly - Frontend properly reads
data.itemsfrom API response
✅ 127 tests pass - All existing tests continue to pass
- Removed duplicate
update_gold()method - Second definition was returningTrueinstead of the new gold amount, breaking gold transactions - Added
update_combatant_initiative()method - Was being called by_roll_initiativetool but didn't exist - Fixed NPC relationship capping - Initial relationships weren't being capped at ±100, now uses
max(-100, min(100, value))
- Fixed all
get_active_combat()calls - 7 calls intools.pywere passingchannel_idas positional argument but method expectschannel_id=keyword argument (first param isguild_id) - Fixed
_roll_initiative()- Had broken codeasync with self.db.db_path as conn:which tried to use a string as async context manager
- Fixed
test_save_memoryassertion - Was checking for "saved" but output says "Remembered" - Fixed test
get_active_combatcalls - Same positional/keyword arg bug
✅ 127 tests pass - Full coverage across database, dice, tools, and integration scenarios
c:\Users\kyle\projects\rpg-dm-bot\
├── run.py # Entry point
├── requirements.txt # Python dependencies
├── pytest.ini # Test configuration
├── HANDOFF.md # This file
├── README.md # User documentation
├── docs/
│ └── DATABASE_ARCHITECTURE.md # Database diagrams & relationships
├── data/
│ ├── rpg.db # SQLite database (runtime)
│ └── game_data/
│ ├── classes.json # 7 character classes
│ ├── races.json # 13+ playable races
│ ├── items.json # ~100 items (weapons, armor, potions)
│ ├── enemies.json # Enemy templates
│ ├── spells.json # Spell definitions
│ ├── skills.json # Skill trees per class
│ ├── npc_templates.json
│ └── starter_kits.json
├── src/
│ ├── __init__.py
│ ├── bot.py # Main Discord bot class
│ ├── database.py # All database operations (~3800 lines)
│ ├── llm.py # LLM client with retry logic
│ ├── prompts.py # AI DM system prompts
│ ├── tool_schemas.py # OpenAI function definitions (~60 tools)
│ ├── tools.py # Tool executor (~67 implementations)
│ └── cogs/
│ ├── characters.py # Character commands
│ ├── combat.py # Combat system
│ ├── inventory.py # Inventory management
│ ├── quests.py # Quest system
│ ├── npcs.py # NPC interactions
│ ├── sessions.py # Session/campaign management
│ ├── dice.py # Dice rolling
│ ├── dm_chat.py # AI DM conversation
│ ├── game_master.py # Game flow control
│ ├── game_persistence.py # Save/load/story
│ ├── spells.py # Spell system
│ └── skills.py # Skill tree system
├── web/
│ ├── __init__.py
│ ├── api.py # FastAPI REST API (~1400 lines, ~76 endpoints)
│ └── frontend/
│ ├── index.html # Main HTML (~1200 lines)
│ ├── styles.css # CSS (~1450 lines)
│ ├── package.json # TypeScript config
│ ├── tsconfig.json
│ └── src/
│ └── main.ts # TypeScript (~2200 lines)
├── tests/
│ ├── conftest.py # Test fixtures
│ ├── test_database.py # 48 database tests
│ ├── test_dice.py # 27 dice tests
│ ├── test_integration.py # 10 integration tests
│ └── test_tools.py # 42 tool tests
└── logs/ # Runtime logs
| Component | Technology |
|---|---|
| Discord | discord.py 2.3+ with slash commands |
| Database | SQLite via aiosqlite (async) |
| LLM | Requesty.ai (OpenAI-compatible) |
| Web API | FastAPI on port 8000 |
| Frontend | TypeScript compiled to JS |
| Testing | pytest with pytest-asyncio |
27 Tables organized into subsystems:
sessions- Game campaignssession_participants- Players in sessionscharacters- Player charactersgame_state- Current game state per session
inventory- Items ownedcharacter_spells- Known spellscharacter_abilities- Class featurescharacter_skills- Skill tree unlocksspell_slots- Spell slot trackingcharacter_skill_points- Available skill pointscharacter_status_effects- Buffs/debuffs
quests- Quest definitionsquest_progress- Per-character progressnpcs- NPC definitionsnpc_relationships- Character-NPC reputation
combat_encounters- Combat sessionscombat_participants- Characters/enemies in combat
locations- World locationslocation_connections- Bidirectional location linksstory_items- Key items/artifactsstory_events- Plot events
user_memories- AI contextconversation_history- Chat historystory_log- Narrative logdice_rolls- Roll historysession_snapshots- Save statescharacter_interviews- Character creation wizard
See docs/DATABASE_ARCHITECTURE.md for full diagrams and relationships.
| Method | Endpoint | Purpose |
|---|---|---|
| GET/POST | /api/sessions |
Session CRUD |
| GET/POST | /api/characters |
Character CRUD |
| GET/POST | /api/quests |
Quest CRUD |
| GET/POST | /api/npcs |
NPC CRUD |
| GET/POST | /api/locations |
Location CRUD |
| GET/POST | /api/items |
Story items CRUD |
| GET/POST | /api/events |
Story events CRUD |
| GET/POST | /api/combat |
Combat management |
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/characters/{id}/spells |
Character's spells |
| GET | /api/characters/{id}/abilities |
Character's abilities |
| GET | /api/characters/{id}/skills |
Character's skills |
| GET | /api/characters/{id}/status-effects |
Active status effects |
| POST | /api/characters/{id}/rest/{type} |
Short/long rest |
| Method | Endpoint | Purpose |
|---|---|---|
| GET/POST | /api/locations/{id}/connections |
Location travel paths |
| GET | /api/npcs/{id}/relationships |
NPC-character relationships |
| Method | Endpoint | Purpose |
|---|---|---|
| GET/PUT | /api/gamedata/classes |
Character classes (edit all) |
| GET/PATCH/POST/DELETE | /api/gamedata/classes/{id} |
Single class CRUD |
| GET/PUT | /api/gamedata/races |
Playable races (edit all) |
| GET/PATCH/POST/DELETE | /api/gamedata/races/{id} |
Single race CRUD |
| GET/PUT | /api/gamedata/skills |
All skill data |
| GET/PUT | /api/gamedata/skills/trees/{class} |
Skill tree per class |
| GET/PATCH | /api/gamedata/skills/{id} |
Single skill CRUD |
| GET | /api/gamedata/items |
Item database (with filtering) |
| GET | /api/gamedata/spells |
Spell definitions |
| GET | /api/gamedata/enemies |
Enemy templates |
- Character creation with interview wizard
- Session/campaign management with player isolation
- Combat system with initiative and turn tracking
- Inventory with equipment slots and starter kits
- Quest system with objectives and auto-reward distribution
- NPC system with relationships and location tracking
- Dice rolling with advantage/disadvantage and session history
- AI DM chat with tool calling and comprehensive context
- Spell system with slots and casting
- Skill trees with points and unlocks
- Game persistence with save/load
- Web API for all CRUD operations (~80 endpoints)
- Frontend dashboard and management pages
- Browser chat with persisted history, server-issued web identities, and rate limiting
- Class/race editors with full edit/save functionality
- Skill tree editor with branch editing
- Item database browser with search and filtering
- Spell browser with filtering by school/level/class
- Browser chat dashboard panels for combat, spell management, location connections, and status effects
- Cross-system wiring - All game systems properly integrated
- All 67 tools matched with schemas and working
- API client methods exist for all endpoints
- Browser chat dashboard panels are now present, but frontend dependency repair is still needed before a clean local production bundle can be generated in this workspace
- No user authentication beyond server-issued browser chat identities (local use only)
- Single SQLite database - no horizontal scaling
- No WebSocket - frontend requires manual refresh
- Limited validation - inputs not fully sanitized
- Frontend build toolchain in repo is incomplete - TypeScript CLI package contents/permissions need repair for
npm run build - Campaign lifecycle is duplicated -
/game,/session, and/resumestill split responsibility - Some schema/runtime drift remains - especially
story_items,story_events,quest current_stage, andcurrent_location_id - Theme/content-pack architecture is not runtime-complete - current non-fantasy support is mostly generation flavor
The old browser-chat implementation plan is stale; browser chat already exists. Future work should follow WORLDBUILDING_AND_CAMPAIGN_GAP_SPEC.md.
- Stabilize broken schema/runtime/API drift
- Unify campaign lifecycle under one canonical
/sessionflow - Fix pause/resume continuity and persistent channel binding
- Persist first-class
world_themeandcontent_pack_idwithfantasy_coreas the v1 runtime target - Bring browser/dashboard contracts into alignment with the canonical playable session flow
- Correct
story_itemshelper/schema drift - Correct
story_eventshelper/schema drift - Stop reading nonexistent
quest['current_stage'] - Add and use
game_state.current_location_id - Fix broken API endpoints calling missing DB methods or wrong signatures
- Remove or deprecate duplicate lifecycle ownership across
/game,/session, and/resume
- full content-pack runtime switching beyond
fantasy_core - deeper faction/storyline runtime wiring into quest lifecycle and Discord/browser flows
- maps, lore, and discovery runtime/editor systems
- complete snapshot/save-point system behind the current UI
# Clone and setup
git clone https://github.com/mojomast/rpg-dm-bot.git
cd rpg-dm-bot
python -m venv .venv
source .venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp .env.example .env
# Edit .env with:
# - DISCORD_TOKEN=your_bot_token
# - OPENROUTER_API_KEY=your_api_key
# or REQUESTY_API_KEY=your_api_key
# - LLM_MODEL=your_model
# - LLM_BASE_URL=https://openrouter.ai/api/v1
# or https://router.requesty.ai/v1
# Run tests
pytest tests/ -v
# Run bot
python run.py
# Run API (separate terminal)
cd web && uvicorn api:app --reload --port 8000| Purpose | File | Key Functions |
|---|---|---|
| Database schema | src/database.py |
Lines 20-550 (CREATE TABLE statements) |
| Database methods | src/database.py |
Lines 550-3800 (~160 async methods) |
| API endpoints | web/api.py |
Entire file (~80 endpoints) |
| Frontend TypeScript | web/frontend/src/main.ts |
API object, page handlers |
| Tool definitions | src/tool_schemas.py |
TOOLS_SCHEMA list (~60 tools) |
| Tool implementations | src/tools.py |
execute_tool() and _* methods |
| DM chat loop | src/cogs/dm_chat.py |
handle_mention(), get_game_context() |
| AI prompts | src/prompts.py |
build_dm_system_prompt(), DM_CAPABILITIES |
| LLM client | src/llm.py |
chat_completion_with_tools() |
| File | Changes |
|---|---|
src/database.py |
Added _run_migrations() method for schema updates, fixed get_active_events() and get_pending_events() with error handling |
src/tools.py |
Added _get_session_for_context() helper, updated 15+ tool functions to use proper session isolation |
src/cogs/dm_chat.py |
Added session_id to tool execution context in both process_batched_messages() and process_dm_message() |
src/prompts.py |
Added Spell & Ability Tools, Skill Check Tools, and Leveling sections to DM_CAPABILITIES |
HANDOFF.md |
Updated with Session 8 changes |
docs/DATABASE_ARCHITECTURE.md |
Updated with session isolation notes |
| File | Changes |
|---|---|
web/api.py |
Fixed /api/gamedata/items response structure, added PUT endpoints for classes/races/skills |
web/frontend/src/main.ts |
Fixed class display (primary_stat), fixed abilities rendering, added skill tree editing |
README.md |
Updated web dashboard section with new endpoints |
HANDOFF.md |
Updated with Session 7 changes |
| File | Changes |
|---|---|
src/database.py |
Removed duplicate update_gold(), added update_combatant_initiative(), fixed NPC relationship capping |
src/tools.py |
Fixed 7 get_active_combat() calls to use channel_id= keyword, fixed _roll_initiative() broken async code |
tests/test_tools.py |
Fixed test_save_memory assertion, fixed get_active_combat test calls |
- ✅ Bot not responding after game start - Fixed missing
prioritycolumn with database migration - ✅ Characters from other sessions appearing - Fixed session isolation with
_get_session_for_context() - ✅ Tools not documented for spells/skills - Added to DM_CAPABILITIES prompt
- Should the browser chat use HTTP POST or WebSocket?
- How should web users be identified (UUID, username, Discord OAuth)?
- Should chat history be persisted to database or just frontend?
- Do we need streaming responses for better UX?
End of Handoff Document