Try it on Discord • Features • How It Works • Technical Highlights • Setup • Architecture • License
MythWobble is a Discord bot that generates dynamic, AI-driven text adventure games. Players interact through Discord threads using buttons and text input to make choices that shape AI-generated narratives. Supports solo and multiplayer (up to 4 players).
Join the Discord server to try MythWobble without setting up your own instance.
- AI-generated narratives — every playthrough is unique, powered by any OpenAI-compatible LLM (default: Gemini Flash)
- Multiplayer — up to 4 players per game, with turn-based response collection and per-player targeting
- Rich game configuration — choose theme, tone, narrative length, number of turns, and language
- Multi-language support — play in any language the LLM supports; canonical state kept in English, output in player's language
- 8-block memory system — structured context management that maintains narrative coherence across 50+ turn games within token budgets
- Deterministic summarization — rule-based turn compression (no LLM call), triggers every 5 turns with fallback extraction
- Plot generation — story skeletons with act structure, turning points, and multiple ending types (victory, defeat, bittersweet, cliffhanger)
- Saga mode — chain multiple games into connected story arcs with character/world state carried forward
- Gameplay resilience — death spiral prevention via action categorization, repetition detection, and stall recovery with director guidance injection
- Per-game command queues — PostgreSQL-backed FIFO queues with advisory locks for race-free concurrent game processing
- Prompt injection mitigations — defense-in-depth input sanitization with Unicode normalization, delimiter isolation, and suspicious pattern auditing
- Scene recovery — survives bot crashes mid-turn; recovers unsent scenes on restart
- Cost controls — per-session and daily cost limits, per-turn token tracking with 6-decimal precision
- Timeout handling — automatic warnings at 4 min, timeouts at 5 min, 24-hour abandonment detection
- Rate limiting — per-user and per-guild rate limits to prevent abuse
- 1500+ tests — comprehensive unit and integration test coverage
- A player runs
/startin any Discord channel - The bot creates a private thread and guides setup (players, theme, tone, narrative length, turns, language)
- A plot synopsis with story skeleton is generated to guide narrative consistency
- Each turn, the LLM generates a narrative scene with 3-5 strategically distinct action choices
- Players pick an action via buttons or type a custom response
- The story continues until the target turn count, ending with a narrative conclusion
- Optionally, continue the story as a new chapter via saga mode
Discord requires interaction acknowledgment within 3 seconds, but LLM calls take 3-30 seconds. MythWobble handles this by immediately deferring the reply (deferReply() in <100ms), queuing the action in PostgreSQL, and editing the reply once the LLM responds.
The bot uses an 8-block memory system to keep the LLM context focused within a 6,100-token input budget:
| Block | Purpose | Token Budget |
|---|---|---|
| System Preamble | Narrator persona, output format, language policy | 1,000 |
| Metadata | Theme, tone, player list, turn count, progress % | 400 |
| Players State | Character stats, inventory, known facts | 500 |
| World State | Locations, NPCs, environment, global facts | 500 |
| Plot Summary | Compressed history of older turns | 1,500 |
| Recent Turns | Last 5 turns verbatim (sliding window) | 2,000 |
| Control State | Expected players, pacing, director guidance | 200 |
| Gameplay Tracking | Death spiral detection (internal only, not sent to LLM) | 0 |
See docs/MEMORY_SYSTEM.md for the full deep-dive.
Each game session has a dedicated PostgreSQL-backed FIFO command queue. Actions are processed sequentially per game (no race conditions) while different games process concurrently. Worker serialization uses PostgreSQL advisory locks (pg_advisory_xact_lock) to eliminate TOCTOU race conditions in action claiming. Idempotency is enforced at the database level via UNIQUE constraints on request IDs — not application-level checks.
See docs/CONCURRENCY.md for the full deep-dive.
Provider-agnostic via the OpenAI-compatible client — works with Gemini, GPT, Claude, Groq, and local models. The response parsing pipeline uses strict Zod validation with multi-stage graceful fallbacks: if JSON parsing fails, it tries markdown extraction, then regex extraction, then uses raw text as narrative. The design principle: never reject an LLM response entirely — a partial narrative is better than an error.
See docs/LLM_INTEGRATION.md for the full deep-dive.
Actions are categorized via regex heuristics into strategic categories (direct, stealth, social, investigate, creative, retreat). A 5-turn sliding window detects repetition (same approach 3+ times) and stall (5 turns without state progress). When detected, director guidance is injected into the system prompt, forcing the LLM to offer fundamentally different approaches.
When multiple players must respond, the pending_responses table with PRIMARY KEY (session_id, user_id) prevents duplicates at the database level. INSERT ... ON CONFLICT DO NOTHING handles concurrent button clicks atomically. After all expected players respond (or timeout), all responses are aggregated and processed in a single LLM call.
Discord's custom_id has a strict 100-character limit. MythWobble stores full button payloads server-side with 8-character nanoid references. Format: prefix|sessionId|payloadId (~40 chars). Expired buttons gracefully fall back using the embedded session ID.
If the bot crashes after committing a turn to the database but before sending the Discord message, the scene recovery system detects unsent scenes on startup and delivers them. No turns are ever lost.
Plot generation uses randomly selected narrative structure templates (e.g., "The Hidden Cost", "The Unreliable Ally") that provide act-by-act story progression guidance. The LLM follows the skeleton while adapting to player choices, producing more satisfying story arcs than unconstrained generation.
Before applying state updates from the LLM, the engine validates location IDs exist, NPC references are consistent, and inventory items exist before removal. Invalid updates are skipped with warnings — the narrative always continues.
| Component | Technology |
|---|---|
| Runtime | TypeScript / Node.js |
| Discord | discord.js v14 |
| Database | PostgreSQL with JSONB + Drizzle ORM |
| LLM | OpenAI-compatible client (Gemini, GPT, Claude, local models) |
| Validation | Zod (env vars, LLM responses, player input) |
| Testing | Vitest (1500+ unit tests + integration tests) |
| Logging | Pino (structured JSON logging) |
| Token Counting | tiktoken |
| ID Generation | nanoid |
This project was built entirely with Claude Code and uses several of its advanced features:
Five specialized agents run during development:
| Agent | Purpose |
|---|---|
documentation-auditor |
Audits docs for inconsistencies before/after changes |
code-reviewer |
Reviews implementation against patterns and standards |
test-coverage-analyzer |
Identifies test gaps after completing features |
architecture-validator |
Validates design decisions against architecture docs |
security-auditor |
Checks for vulnerabilities before deployment |
| Skill | What it does |
|---|---|
/deploy |
Full production deployment with DB migration checklist, log viewing, service management |
/test-discord |
E2E testing via Chrome automation — Claude Code calls mcp-rubber-duck which drives Chrome DevTools to navigate Discord, create games, click buttons, and verify gameplay flow. Gemini Flash processes DOM snapshots instead of Opus, reducing cost. |
Pre-commit hook runs the full test suite and linter before every git commit, ensuring no broken code is committed.
- Node.js >= 20
- PostgreSQL (or Docker)
- A Discord bot token (guide)
- An LLM API key (Gemini, OpenAI, etc.)
git clone https://github.com/nesquikm/mythwobble.git
cd mythwobble
npm installCopy the example environment file and fill in your values:
cp .env.example .envKey variables:
| Variable | Description |
|---|---|
DISCORD_TOKEN |
Bot token from Discord Developer Portal |
DISCORD_CLIENT_ID |
Application ID from Discord Developer Portal |
DATABASE_URL |
PostgreSQL connection string |
LLM_API_KEY |
API key for your LLM provider |
LLM_BASE_URL |
OpenAI-compatible API endpoint |
LLM_MODEL |
Model name (e.g., gemini-2.5-flash) |
See .env.example for the full list with game settings, timeouts, rate limits, cost controls, and scheduler intervals.
Start PostgreSQL via Docker Compose:
docker compose up -dRun migrations:
npm run db:migrate# Development (hot reload)
npm run dev
# Production
npm run build
npm start- Create a new application at discord.com/developers
- Go to Bot tab, copy the token →
DISCORD_TOKEN - Copy the Application ID →
DISCORD_CLIENT_ID - Enable Privileged Gateway Intents: Message Content
- Go to OAuth2 → URL Generator, select
bot+applications.commands - Bot permissions: Send Messages, Create Public Threads, Send Messages in Threads, Embed Links, Read Message History
- Use the generated URL to invite the bot to your server
MythWobble runs well on a small VPS (1 vCPU, 1 GB RAM, ~$6/month). For production:
- Clone the repo on your server
- Set up
.envwith production values - Set up PostgreSQL (Docker or native)
- Build and run:
npm run build && npm start - Use systemd or PM2 to keep the bot running
The deploy skill (.claude/skills/deploy/SKILL.md) contains a full deployment reference with migration checklist.
npm test # Unit tests (1500+)
npm run test:integration # Integration tests (game flow, handlers)
npm run test:all # Both
npm run lint # ESLint
npm run typecheck # TypeScript strict modeDetailed technical documentation in docs/:
| Document | Content |
|---|---|
| ARCHITECTURE.md | Comprehensive reference — schema, state machine, all subsystems (~1700 lines) |
| MEMORY_SYSTEM.md | Deep-dive into the 8-block memory architecture, token budgeting, and summarization |
| CONCURRENCY.md | Per-game command queues, advisory locks, idempotency, and multiplayer response aggregation |
| LLM_INTEGRATION.md | Prompt engineering, structured output parsing, language enforcement, prompt injection mitigations |
| RETENTION_DESIGN.md | Design exploration for player retention via worlds and profiles |
| RETENTION_ANALYSIS.md | Analysis of retention approaches with web research and alternative mechanisms |

