Chapter I.
PotionQuest RPG Engine API Reference
Base URL:
https://potionquest.comVersion: 1.0 — 35+ procedural tabletop RPG generators, pure TypeScript, zero AI dependencies.
Source:
apps/potionquest/src/lib/rpg-engine/
Table of Contents
- Authentication
- Rate Limiting
- Error Responses
- Endpoints
- Generator Reference
- Tier 1: Core DM Tools — encounter, npc, npc-crowd, combat, loot, shop
- Tier 2: World Depth — weather, tavern, rumor, trap, settlement, faction, religion, travel
- Tier 3: OSR Flavor — spell, spellbook, career, pockets, dying-words, dungeon-dressing, door, monster-motivation, found-document, spell-mishap
- Advanced — room-atmosphere, boss-encounter, hazard, secret, riddle, quest-hook, curse, hireling, prophecy, nightmare, scar, reputation, death-legacy, legendary-affix
Authentication
All endpoints except GET /api/rpg/info require an API key via one of:
X-API-Key: <key>
Authorization: Bearer <key>
The API key is stored as SST secret RpgApiKey in stage potionquest-prod. Logged-in admin users can also authenticate via their potionquest_auth JWT session cookie (used by the admin dashboard at /admin).
Unauthorized response:
{ "error": "Unauthorized. Provide a valid API key via X-API-Key header." }
HTTP 401.
Rate Limiting
- 60 requests per 60 seconds per IP address
- Applied before authentication check (prevents brute force)
- IP extracted from
x-forwarded-fororx-real-ipheaders, hashed with SHA-256 - In-memory store, resets on Lambda cold start
Rate limit exceeded response:
{
"error": "Rate limit exceeded",
"retryAfterSeconds": 42
}
HTTP 429. Headers: Retry-After: 42, X-RateLimit-Remaining: 0.
Error Responses
All errors follow this shape:
{ "error": "Human-readable error message" }
| Status | Meaning |
|---|---|
| 400 | Bad request — missing or invalid parameters |
| 401 | Unauthorized — missing or invalid API key |
| 429 | Rate limit exceeded |
| 500 | Server error — generator failure |
Endpoints
GET /api/rpg/info
Auth: None required.
Returns full API documentation including all generator types, parameter examples, and endpoint descriptions. Useful for programmatic discovery.
curl -s https://potionquest.com/api/rpg/info | python3 -m json.tool
POST /api/rpg/dice
Auth: API key required.
Roll dice using standard tabletop notation.
Request:
{
"notation": "2d6+3",
"count": 5
}
| Field | Type | Required | Description |
|---|---|---|---|
notation | string | Yes | Dice notation: NdS, NdS+M, NdS-M, NdS*M |
count | number | No | Batch roll count (max 100, default 1) |
Response (single roll):
{
"notation": "2d6+3",
"result": {
"notation": "2d6+3",
"rolls": [4, 2],
"modifier": 3,
"multiplier": 1,
"total": 9
}
}
Response (batch roll, count > 1):
{
"notation": "2d6+3",
"count": 5,
"results": [
{ "notation": "2d6+3", "rolls": [4, 2], "modifier": 3, "multiplier": 1, "total": 9 },
{ "notation": "2d6+3", "rolls": [6, 1], "modifier": 3, "multiplier": 1, "total": 10 }
]
}
Notation examples: 1d20, 2d6, 1d100, 4d6+0, 1d20+5, 8d6, 2d10-1, 1d6*10
POST /api/rpg/generate
Auth: API key required.
Generate procedural RPG content from 35+ generator types.
Request:
{
"type": "encounter",
"params": { "terrain": "forest", "partyLevel": 5, "partySize": 4 },
"count": 1,
"format": true
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Generator type (see full list below) |
params | object | No | Generator-specific parameters (defaults used if omitted) |
count | number | No | Number of results (max 20, default 1) |
format | boolean | No | Include human-readable formatted string in response |
Response (single, count = 1):
{
"type": "encounter",
"result": { ... },
"formatted": "Human-readable text (only if format: true)"
}
Response (batch, count > 1):
{
"type": "encounter",
"count": 3,
"results": [ { ... }, { ... }, { ... } ],
"formatted": [ "text1", "text2", "text3" ]
}
Invalid type response:
{ "error": "Unknown generator type: 'bad-type'. GET /api/rpg/info for available types." }
HTTP 400.
Generator Reference
Every generator is documented with:
- Params — all accepted fields with valid values
- Response — exact JSON shape returned in
result - Format — whether
format: trueproduces aformattedstring
All params are optional unless marked required. When omitted, generators pick random values.
Tier 1: Core DM Tools
encounter
Generate a complete combat encounter with monsters scaled to party level.
Source: src/lib/rpg-engine/encounters/generator.ts
Params:
{
terrain: "forest" | "mountain" | "swamp" | "desert" | "arctic" | "plains"
| "jungle" | "coast" | "underwater" | "urban" | "dungeon" | "cave"
| "ruins" | "sewers",
partyLevel: number, // 1-20
partySize: number, // typically 3-6
difficulty: "trivial" | "easy" | "medium" | "hard" | "deadly", // default: "medium"
allowBoss: boolean, // default: false
minMonsters: number, // default: 1
maxMonsters: number // default: 8
}
Response:
{
"id": "enc_abc123",
"name": "Goblin Ambush",
"description": "A band of goblins lies in wait...",
"monsters": [
{
"monster": {
"id": "mon_xyz",
"name": "Goblin Scout",
"emoji": "👺",
"level": 1,
"type": "humanoid",
"size": "small",
"stats": { "str": 8, "dex": 14, "int": 10, "con": 10 },
"ac": 13,
"hp": 7,
"attackBonus": 4,
"damageDice": "1d6",
"damageBonus": 2,
"damageType": "piercing",
"weaponType": "shortbow",
"xp": 50,
"gold": "2d6",
"specialAbilities": ["Nimble Escape"],
"description": "A wiry goblin with a crude shortbow",
"behavior": "ambusher"
},
"count": 3,
"position": "flanking"
}
],
"difficulty": "medium",
"xpTotal": 150,
"goldTotal": "6d6",
"terrain": "forest",
"ambush": true,
"lair": false,
"notes": "The goblins attack from the treeline"
}
Format: No dedicated format function.
npc
Generate a fully-realized non-player character with personality, appearance, background, and roleplaying hooks.
Source: src/lib/rpg-engine/npcs/generator.ts
Params:
{
race: "human" | "elf" | "dwarf" | "halfling" | "gnome" | "half-elf"
| "half-orc" | "tiefling" | "dragonborn" | "orc" | "goblin",
gender: "male" | "female" | "nonbinary",
socialClass: "peasant" | "common" | "merchant" | "noble" | "outcast",
occupation: string, // free-text, or omit for random
disposition: "friendly" | "neutral" | "unfriendly" | "hostile",
ageRange: "young" | "adult" | "middle-aged" | "elderly"
}
Response:
{
"name": "Shaena Gamgee",
"race": "halfling",
"gender": "female",
"personality": {
"trait": "Cheerful and optimistic, always sees the bright side",
"quirk": "Cracks knuckles frequently",
"motivation": "Building a legacy that outlasts them",
"fear": "Being poor again",
"secret": "Served a dark power in their youth",
"voiceHint": "Uses flowery, elaborate vocabulary"
},
"appearance": {
"age": "Adult, in the prime of life",
"build": "Stocky and solid",
"distinguishingFeature": "Wears distinctive jewelry",
"clothing": "Traveling gear, ready for the road",
"demeanor": "Busy and preoccupied"
},
"background": {
"occupation": "Entertainer (bard, actor, etc.)",
"origin": "From a distant land",
"formativeEvent": "Found faith (or lost it)",
"connection": "Saved an adventurer's life once"
},
"oneLiner": "Cheerful halfling entertainer who cracks knuckles frequently",
"catchphrase": "\"Between you and me...\"",
"knownFor": "Their incredible memory for names",
"rumorAboutThem": "They once stole from a noble and got away with it"
}
Format: No dedicated format function.
npc-crowd
Generate a batch of NPCs suitable for populating a tavern or market.
Source: src/lib/rpg-engine/npcs/generator.ts
Params:
{
size: number, // crowd size (how many NPCs)
quality: "squalid" | "poor" | "modest" | "comfortable" | "wealthy" | "aristocratic"
}
Response: Array of NPC objects (same shape as npc above).
Format: No.
combat
Generate a combat setup with initiative order and tactical notes.
Source: src/lib/rpg-engine/encounters/ (uses encounter infrastructure)
Params: Same as encounter.
Response: Same as encounter with additional combat-specific metadata.
Format: No.
loot
Generate a treasure hoard with coins, gems, art objects, and magic items.
Source: src/lib/rpg-engine/loot/treasure.ts
Params:
{
level: "minor" | "standard" | "major" | "legendary"
}
Response:
{
"level": "major",
"coins": {
"cp": 0,
"sp": 200,
"ep": 0,
"gp": 500,
"pp": 25
},
"gems": [
{
"name": "Star Ruby",
"quality": "exceptional",
"value": 1000,
"description": "A deep crimson ruby with a perfect six-rayed star"
}
],
"art": [
{
"name": "Gold-Inlaid Ebony Chest",
"quality": "masterwork",
"value": 750,
"description": "An ornate chest with gold filigree",
"origin": "Dwarven craftsman"
}
],
"magicItems": [
{
"name": "Flame Tongue Longsword",
"rarity": "rare",
"type": "weapon",
"attunement": true,
"description": "A longsword wreathed in magical fire",
"properties": ["Deals extra 2d6 fire damage"],
"charges": null,
"value": 5000
}
],
"totalValue": 8475,
"description": "A king's ransom in gold, gems, and enchanted steel"
}
rarity values: "common", "uncommon", "rare", "very_rare", "legendary", "artifact"
type values: "weapon", "armor", "potion", "scroll", "ring", "wand", "staff", "rod", "wondrous", "consumable"
Format: Yes — formatted returns a human-readable treasure summary.
shop
Generate a complete shop with owner, inventory, prices, and secrets.
Source: src/lib/rpg-engine/loot/shops.ts
Params:
{
type: "general_store" | "blacksmith" | "armorer" | "alchemist" | "magic_shop"
| "temple" | "tavern" | "jeweler" | "fletcher" | "stables"
| "herbalist" | "bookstore" | "tailor" | "exotic_goods",
quality: "poor" | "modest" | "comfortable" | "wealthy" | "aristocratic",
size: "small" | "medium" | "large"
}
Response:
{
"name": "The Iron Anvil",
"type": "blacksmith",
"quality": "comfortable",
"owner": {
"name": "Durgan Steelhand",
"personality": "Gruff but fair dwarf who judges people by their handshake",
"secret": "Forges weapons for the thieves' guild on the side"
},
"description": "A well-kept smithy with a roaring forge...",
"inventory": [
{
"name": "Longsword",
"category": "weapons",
"basePrice": 15,
"currentPrice": 18,
"quantity": 3,
"rarity": "common"
}
],
"roomRate": "1 gp/night",
"currentEvent": "A sale on surplus shields",
"weakness": "Low stock of exotic materials"
}
Format: Yes.
Tier 2: World Depth
weather
Generate weather conditions with mechanical effects on gameplay.
Source: src/lib/rpg-engine/world/weather.ts
Params:
{
climate: "arctic" | "cold" | "temperate" | "warm" | "tropical" | "desert",
season: "spring" | "summer" | "autumn" | "winter"
}
Response:
{
"name": "Freezing Rain",
"description": "A bitter rain that freezes on contact...",
"icon": "🌧️",
"temperature": "Below freezing",
"wind": "Strong gusts from the north",
"visibility": "Reduced to 100 feet",
"effects": {
"travel": "Half speed on roads, quarter speed off-road",
"combat": "Difficult terrain everywhere",
"rangedAttacks": "Disadvantage beyond 30 feet",
"perception": "-5 to Perception checks",
"stealth": "+5 to Stealth (rain covers sound)",
"fire": "Open flames extinguished, fire damage halved",
"special": "CON save DC 12 each hour or gain 1 exhaustion"
},
"duration": "2d4 hours",
"forecast": "Clearing by nightfall"
}
Format: Yes — formats the effects block.
tavern
Generate a complete tavern with staff, menu, rumors, and atmosphere.
Source: src/lib/rpg-engine/world/taverns.ts
Params:
{
quality: "squalid" | "poor" | "modest" | "comfortable" | "wealthy" | "aristocratic"
}
Response:
{
"name": "The Dragon and Anchor",
"type": "fancy",
"quality": "wealthy",
"description": "An elegant establishment...",
"atmosphere": "Elegant decor with private booths",
"owner": {
"name": "Rose Goodbarrel",
"role": "Owner",
"personality": "Fiercely protective halfling who uses overly formal language",
"secret": "Served a dark power in their youth"
},
"staff": [
{
"name": "Fang Bloodfist",
"role": "Bartender",
"personality": "Humble half-orc who peppers speech with foreign words"
}
],
"menu": {
"drinks": [
{ "name": "Aged Wine", "price": "2 gp" },
{ "name": "Dragon's Breath Whiskey", "price": "3 gp" }
],
"food": [
{ "name": "Venison Steak", "price": "2 gp" }
],
"specialty": {
"name": "Seven-Course Feast",
"price": "10 gp",
"description": "A culinary journey"
}
},
"roomRate": "2 gp/night - Luxury suite with breakfast",
"rumors": [
{
"text": "I heard the Stranger and the Mayor are having an affair.",
"truth": "true",
"reality": "It's true and will cause scandal when revealed.",
"source": "An old timer who's seen it all",
"type": "local_gossip"
}
],
"currentEvent": "A storytelling contest",
"notableFeature": "A stage where bards perform nightly"
}
type values: "dive", "roadhouse", "inn", "tavern", "alehouse", "fancy"
Format: No.
rumor
Generate tavern rumors with truth levels and adventure hooks.
Source: src/lib/rpg-engine/world/rumors.ts
Params:
{
count: number // default: 3
}
Response:
{
"text": "They say there's treasure hidden in the old graveyard.",
"truth": "half_true",
"reality": "There was treasure, but someone already took it.",
"source": "The innkeeper",
"type": "adventure_hook",
"hook": "The party could investigate the location."
}
truth values: "true", "mostly_true", "half_true", "mostly_false", "false"
type values: "local_gossip", "adventure_hook", "warning", "legend", "trade_info"
Format: No.
trap
Generate dungeon traps with detection, disarming, and damage.
Source: src/lib/rpg-engine/world/traps.ts
Params:
{
severity: "harmless" | "dangerous" | "deadly",
type: "mechanical" | "magical" | "hybrid" | "environmental",
dungeonLevel: number
}
Response:
{
"name": "Poison Dart Wall",
"type": "mechanical",
"severity": "dangerous",
"description": "Tiny holes line the corridor walls at chest height...",
"trigger": "Pressure plate in the center of the hallway",
"effect": "Darts fire from both walls — 2d4 piercing + DC 13 CON save or poisoned",
"avoidance": "DC 14 Perception to spot holes, DC 12 Thieves' Tools to jam mechanism",
"damage": "2d4+poison",
"saveDC": 13,
"saveType": "CON",
"disarmDC": 12
}
Format: Yes.
settlement
Generate a town or city with government, economy, problems, and NPCs.
Source: src/lib/rpg-engine/world/settlements.ts
Params:
{
size: "thorp" | "hamlet" | "village" | "small_town" | "large_town" | "city" | "metropolis",
government: "autocracy" | "council" | "democracy" | "magocracy" | "theocracy" | "oligarchy" | "anarchy"
}
Response:
{
"name": "Thornhaven",
"size": "village",
"population": 340,
"government": "council",
"leader": { "...NPC object..." },
"description": "A quiet farming village on the edge of the Thornwood...",
"notableLocations": ["The Old Mill", "Temple of the Harvest", "Thornhaven Inn"],
"economy": "Agriculture — wheat and barley",
"culture": "Insular and suspicious of outsiders",
"problems": ["Disappearing livestock", "Tax collectors from the Baron"],
"rumors": [ "...Rumor objects..." ],
"defenses": "Wooden palisade, volunteer militia of 20",
"relationships": [
{ "faction": "Baron's Tax Collectors", "attitude": "hostile" }
]
}
Format: Yes.
faction
Generate a political or social organization with goals, secrets, and hierarchy.
Source: src/lib/rpg-engine/world/factions.ts
Params:
{
type: "guild" | "noble_house" | "religious_order" | "criminal_syndicate"
| "military_order" | "merchant_company" | "secret_society" | "arcane_circle"
| "political_party" | "tribal_clan" | "revolutionary_movement" | "monster_cult",
size: "tiny" | "small" | "medium" | "large" | "massive",
alignment: "lawful" | "neutral" | "chaotic"
}
Response:
{
"name": "The Obsidian Hand",
"type": "criminal_syndicate",
"size": "medium",
"influence": "regional",
"alignment": "chaotic",
"morality": "evil",
"description": "A ruthless network of thieves and assassins...",
"symbol": "A black hand clutching a gemstone",
"headquarters": "The undercity beneath Portmaw",
"leader": {
"name": "The Whisper",
"title": "Shadow Master",
"personality": "Calculating and patient, never raises their voice",
"secret": "Is actually the mayor's spouse"
},
"goals": ["Control the docks trade", "Eliminate the Merchant's Guild"],
"methods": ["Blackmail", "Smuggling", "Strategic assassination"],
"resources": ["Network of informants", "Hidden tunnels", "Corrupt guards"],
"weakness": "Internal power struggles between lieutenants",
"secrets": ["The leader's true identity", "A vault beneath the old temple"],
"relationships": [
{ "faction": "City Watch", "attitude": "corrupt alliance" }
],
"joinRequirements": "Must perform a theft and be vouched by two members",
"memberBenefits": ["Fence for stolen goods", "Safe houses", "Legal protection"],
"ranks": ["Finger", "Palm", "Fist", "Shadow", "Shadow Master"]
}
influence values: "local", "regional", "national", "continental", "global"
morality values: "good", "neutral", "evil"
Format: Yes.
religion
Generate a deity with domains, commandments, followers, and enemies.
Source: src/lib/rpg-engine/world/religions.ts
Params:
{
domain: string, // e.g. "war", "nature", "death"
alignment: string // e.g. "lawful good", "chaotic neutral"
}
Response:
{
"name": "Veltharion",
"domains": ["Knowledge", "Magic"],
"alignment": "Lawful Neutral",
"symbol": "An open book with a glowing eye",
"description": "The All-Seeing Sage, keeper of forbidden knowledge...",
"commandments": [
"Seek knowledge above all",
"Never destroy a book",
"Share wisdom only with the worthy"
],
"priests": "Wear grey robes with silver trim, shave their heads",
"sacred_animals": ["Owl", "Raven"],
"enemies": ["Followers of ignorance", "Book burners"],
"followers": "Scholars, wizards, librarians, and seekers of truth"
}
Format: Yes.
travel
Generate a travel event for overland journeys.
Source: src/lib/rpg-engine/world/travel.ts
Params:
{
terrain: "forest" | "mountain" | "swamp" | "desert" | "arctic" | "plains"
| "jungle" | "coast" | "underwater" | "urban" | "dungeon" | "cave"
| "ruins" | "sewers",
distance: number,
difficulty: string
}
Response:
{
"day": 3,
"weather": "Overcast with light drizzle",
"event": "The road forks at a crumbling wayshrine",
"encounter": "A merchant caravan heading the opposite direction",
"complication": "Bridge washed out — must ford the river",
"distance": 24
}
Format: Yes.
Tier 3: OSR Flavor
spell
Generate an OSR-style two-word spell name with magical properties.
Source: src/lib/rpg-engine/osr/spells.ts
Params:
{
element: "fire" | "ice" | "lightning" | "earth" | "water" | "air" | "shadow"
| "light" | "death" | "life" | "mind" | "time" | "chaos" | "order"
| "nature" | "arcane",
form: "physical" | "ethereal" | "mutation" | "summoning" | "enchantment"
| "divination" | "illusion" | "transmutation",
includeSuggestion: boolean // include a brief spell effect description
}
Response:
{
"name": "Sleet Vapor",
"words": ["Sleet", "Vapor"],
"element": "ice",
"form": "physical",
"suggestion": "Creates a freezing mist that obscures vision and chills to the bone"
}
Format: Yes — e.g. "**Sleet Vapor** [physical]"
spellbook
Generate a collection of spells (batch spell generation).
Source: src/lib/rpg-engine/osr/spells.ts
Params:
{
count: number, // default: 6
element: "...", // same as spell
form: "...", // same as spell
includeSuggestion: boolean
}
Response: Array of spell objects.
Format: Yes — formats as a titled list of spells.
career
Generate a failed career background for character creation (OSR funnel style).
Source: src/lib/rpg-engine/osr/careers.ts
Params:
{
category: "trade" | "service" | "arcane" | "criminal" | "academic"
| "military" | "religious" | "artistic" | "bureaucratic" | "bizarre",
failureReason: "disgraced" | "bankrupt" | "exiled" | "cursed" | "bored"
| "fired" | "exposed" | "traumatized" | "outlawed" | "surpassed",
includeBackstory: boolean,
includeBurden: boolean
}
Response:
{
"title": "Failed Alchemist (Bankrupt)",
"profession": "Alchemist",
"failureReason": "bankrupt",
"category": "arcane",
"equipment": ["Cracked alembic", "3 unmarked vials", "Stained notebook"],
"skill": "Can identify potions by smell",
"backstory": "Spent their fortune chasing the philosopher's stone...",
"burden": "Owes 200gp to a dangerous creditor"
}
Format: Yes.
pockets
Search a person's pockets — generate found items with narrative hooks.
Source: src/lib/rpg-engine/osr/pockets.ts
Params:
{
personType: "commoner" | "soldier" | "noble" | "criminal" | "merchant"
| "scholar" | "priest" | "mage" | "traveler" | "creature",
itemCount: number,
ensureHook: boolean // guarantee at least one item has a narrative hook
}
Response:
{
"items": [
{
"description": "A locket containing a portrait of someone who looks like the party's patron",
"category": "mysterious",
"hook": "Why does this dead bandit carry the mayor's portrait?",
"value": 5
},
{
"description": "3 copper coins and a button",
"category": "mundane",
"value": 0.03
}
],
"totalValue": 5.03,
"summary": "A few meager possessions and one unsettling locket"
}
category values: "personal", "mysterious", "valuable", "mundane", "disturbing", "useful", "romantic", "religious"
Format: Yes.
dying-words
Generate last words for a dying enemy or NPC.
Source: src/lib/rpg-engine/osr/dying-words.ts
Params:
{
enemyType: "beast" | "humanoid" | "undead" | "cultist" | "boss" | "minion" | "innocent",
category: "blessing" | "curse" | "confession" | "warning" | "defiance" | "mystery"
}
Response:
{
"text": "You think you've won... but the Crimson Gate is already open...",
"category": "warning",
"type": "cultist",
"tone": "defiant with a knowing smile"
}
Format: Yes.
dungeon-dressing
Generate atmospheric details for dungeon rooms — sensory descriptions that bring rooms to life.
Source: src/lib/rpg-engine/osr/dungeon-dressing.ts
Params:
{
roomType: "corridor" | "chamber" | "vault" | "crypt" | "throne" | "library"
| "armory" | "barracks" | "kitchen",
category: "visual" | "auditory" | "olfactory" | "tactile" | "unsettling"
}
Response:
{
"roomType": "crypt",
"details": [
{
"category": "visual",
"description": "Faded frescoes on the walls depict a funeral procession",
"type": "decoration"
},
{
"category": "olfactory",
"description": "The air smells of old incense and dry stone",
"type": "atmosphere"
}
],
"description": "A somber crypt with faded frescoes and lingering incense..."
}
Format: Yes.
door
Generate a dungeon door with condition, material, clues, and hooks.
Source: src/lib/rpg-engine/osr/doors.ts
Params:
{
condition: "locked" | "stuck" | "trapped" | "open" | "hidden" | "magical" | "barred" | "broken",
includeSound: boolean,
includeHook: boolean
}
Response:
{
"condition": "locked",
"material": "Iron-bound oak",
"feature": "Dwarven runes etched around the frame",
"clue": "Fresh scratch marks near the lock",
"sound": "Faint chanting from the other side",
"hook": "The key was last seen worn by the missing priest"
}
Format: Yes.
monster-motivation
Generate what a monster is doing, wants, and fears — turns combat into roleplay.
Source: src/lib/rpg-engine/osr/monster-motivations.ts
Params:
{
monsterType: "beast" | "humanoid" | "undead" | "construct" | "aberration"
| "dragon" | "elemental" | "fey" | "fiend" | "giant" | "plant",
disposition: "hostile" | "unfriendly" | "neutral" | "friendly" | "allied"
}
Response:
{
"monsterType": "dragon",
"disposition": "unfriendly",
"goal": "Recovering a stolen egg from a nearby village",
"fear": "That its species is dying out",
"doingNow": "Circling overhead, watching the party's movements",
"complication": "The egg is actually in the party's cart, mistaken for a gem"
}
Format: Yes.
found-document
Generate a discoverable document — letters, journals, maps, contracts.
Source: src/lib/rpg-engine/osr/found-documents.ts
Params:
{
type: "letter" | "journal" | "map" | "contract" | "note" | "inscription" | "record" | "prophecy",
condition: "fresh" | "aged" | "damaged" | "ancient" | "burned"
}
Response:
{
"type": "journal",
"condition": "damaged",
"title": "Journal of Expedition XVI",
"content": "Day 14 — The passage narrows. Brenn insists we turn back...",
"author": "Scholar Aldric Voss",
"age": "Perhaps 50 years old",
"value": "Invaluable to the right buyer",
"hook": "The final entry mentions a sealed door matching one the party just found"
}
Format: Yes.
spell-mishap
Generate a spell gone wrong — wild magic, backfires, and catastrophic failures.
Source: src/lib/rpg-engine/osr/spell-mishaps.ts
Params:
{
school: "abjuration" | "conjuration" | "divination" | "enchantment"
| "evocation" | "illusion" | "necromancy" | "transmutation" | "wild_magic",
severity: "minor" | "major" | "catastrophic"
}
Response:
{
"description": "The spell rebounds, transforming the caster's hair into living snakes",
"severity": "major",
"effect": "Caster gains a snake-hair crown — disadvantage on CHA checks, but can see in darkness",
"school": "transmutation",
"saveDC": 15,
"duration": "Until dispelled or 24 hours"
}
Format: Yes.
Advanced Generators
room-atmosphere
Generate an atmosphere of wrongness for a dungeon room — the feeling that something is off.
Source: src/lib/rpg-engine/generators/room-atmosphere.ts
Params:
{
severity: "subtle" | "obvious" | "overwhelming",
category: "spatial" | "temporal" | "biological" | "psychological"
| "elemental" | "supernatural" | "decay" | "corruption",
ensureSecret: boolean
}
Response:
{
"wrongness": "Shadows in this room point toward the center, not away from light sources",
"category": "spatial",
"severity": "subtle",
"sensory": [
"A faint hum just below the threshold of hearing",
"The air tastes metallic"
],
"modifier": {
"name": "Gravity Flux",
"description": "Gravity shifts slightly toward the room's center",
"mechanicalEffect": "Movement costs +5 ft when moving away from center",
"duration": "while_in_room"
},
"secret": "The room is built over a buried lodestone enchanted by a mad wizard",
"investigation": "DC 15 Arcana reveals faint transmutation magic saturating the floor"
}
duration values: "instant", "while_in_room", "lingering", "permanent"
Format: Yes.
boss-encounter
Generate a multi-phase boss fight with lair actions, legendary actions, weaknesses, and monologue.
Source: src/lib/rpg-engine/generators/boss-encounters.ts
Params:
{
tier: "miniboss" | "major" | "legendary" | "mythic",
type: "dragon" | "demon" | "undead_lord" | "giant" | "aberration" | "archfey"
| "elemental_lord" | "construct" | "beast_alpha" | "dark_wizard"
| "fallen_hero" | "eldritch_horror",
partyLevel: number,
includeMonologue: boolean
}
Response:
{
"name": "Xalrathi the Hollow King",
"type": "undead_lord",
"tier": "legendary",
"description": "A skeletal monarch wreathed in necrotic flame...",
"phases": [
{
"name": "The Hollow Court",
"trigger": "Combat begins",
"hpThreshold": 100,
"description": "Xalrathi fights with cold precision, flanked by spectral courtiers",
"abilities": ["Necrotic Blast", "Summon Courtier"],
"behavior": "Stays on throne, directs minions",
"environmental": "Spectral courtiers block line of sight"
},
{
"name": "The King Rises",
"trigger": "Below 50% HP",
"hpThreshold": 50,
"description": "Xalrathi stands, his crown blazing with dark fire",
"abilities": ["Crown of Dread", "Life Drain"],
"behavior": "Aggressive melee, targets healers",
"environmental": "Throne room cracks, necrotic energy seeps from floor"
}
],
"lairActions": [
{
"name": "Spectral Chains",
"effect": "Ghostly chains erupt from the floor — DC 15 DEX or restrained",
"initiative": 20,
"save": "DEX 15",
"damage": "2d8 necrotic"
}
],
"legendaryActions": [
{ "name": "Necrotic Bolt", "cost": 1, "effect": "Ranged spell attack, 3d6 necrotic" },
{ "name": "Raise Fallen", "cost": 2, "effect": "A slain courtier rises with half HP" },
{ "name": "Death's Embrace", "cost": 3, "effect": "All creatures within 15 ft make DC 16 CON save or take 4d8 necrotic and Xalrathi heals for half" }
],
"legendaryResistances": 3,
"weaknesses": [
{
"description": "The crown is the source of his power",
"discovery": "DC 18 Arcana reveals the crown pulses with each ability used",
"benefit": "Destroying the crown (AC 20, 30 HP) removes legendary resistances",
"obviousness": "hinted"
}
],
"signature": "Crown of the Hollow King — necrotic explosion on death",
"monologue": "You dare enter my court? I have ruled this kingdom for a thousand years...",
"deathThroes": "The crown shatters, releasing a wave of freed souls. The throne room begins to collapse.",
"recommendedLevel": 12
}
Format: Yes.
hazard
Generate environmental hazards — natural disasters, magical anomalies, terrain dangers.
Source: src/lib/rpg-engine/generators/hazards.ts
Params:
{
type: "natural" | "magical" | "trap" | "environmental",
severity: "nuisance" | "dangerous" | "deadly",
terrain: "dungeon" | "forest" | "swamp" | "mountain" | "desert"
| "arctic" | "underground" | "urban" | "coastal"
}
Response:
{
"type": "magical",
"name": "Wild Magic Sinkhole",
"description": "A patch of ground where reality is thin...",
"trigger": "Casting any spell within 30 feet",
"effect": "Roll on Wild Magic Surge table, spell also targets caster",
"avoidance": "DC 16 Arcana to detect, avoid casting near it",
"severity": "dangerous",
"damage": "Varies by surge",
"saveType": "WIS",
"saveDC": 14,
"duration": "Permanent until sealed",
"terrain": ["dungeon", "underground"]
}
Format: Yes.
secret
Generate hidden passages, secret doors, and concealed caches.
Source: src/lib/rpg-engine/generators/secrets.ts
Params:
{
type: "door" | "compartment" | "passage" | "cache",
complexity: "simple" | "moderate" | "complex",
terrain: "forest" | "mountain" | "swamp" | "desert" | "arctic" | "plains"
| "jungle" | "coast" | "underwater" | "urban" | "dungeon" | "cave"
| "ruins" | "sewers"
}
Response:
{
"type": "passage",
"name": "The Whispering Crack",
"description": "A narrow fissure behind a bookshelf, just wide enough to squeeze through",
"mechanism": "Pull the red book on the third shelf",
"discoveryDC": 16,
"contents": "A dusty corridor leading to the old treasury",
"clues": [
"Draft of cold air from behind the shelves",
"Scratch marks on the floor in an arc pattern"
]
}
Format: Yes.
riddle
Generate riddles for dungeon doors, sphinx encounters, and puzzle rooms.
Source: src/lib/rpg-engine/generators/riddles.ts
Params:
{
difficulty: "easy" | "medium" | "hard" | "legendary",
category: "wordplay" | "object" | "concept" | "nature" | "logic" | "moral",
includeSource: boolean
}
Response:
{
"text": "I have cities but no houses, forests but no trees, water but no fish. What am I?",
"answer": "A map",
"answerCategory": "object",
"difficulty": "medium",
"category": "object",
"hints": [
"I can be found on a wall or in a pocket",
"Explorers love me",
"I show you where to go but I've never been there"
],
"alternativeAnswers": ["Atlas", "Globe"],
"source": "Inscribed above an ancient door in Dwarvish"
}
Format: Yes (optionally shows/hides the answer).
quest-hook
Generate adventure hooks with patrons, complications, twists, and rewards.
Source: src/lib/rpg-engine/generators/quest-hooks.ts
Params:
{
type: "rescue" | "retrieval" | "elimination" | "escort" | "investigation"
| "defense" | "delivery" | "exploration" | "heist" | "sabotage",
patronType: "noble" | "merchant" | "priest" | "criminal" | "commoner"
| "mysterious" | "official" | "scholar",
stakeLevel: "personal" | "local" | "regional" | "world",
includeDeadline: boolean
}
Response:
{
"type": "investigation",
"hook": "Children in the village have been sleepwalking to the old mill every night",
"patron": "Elara Thornwood, the village healer",
"patronType": "commoner",
"complication": "The mill owner is the mayor's brother",
"twist": "The children aren't being controlled — they're trying to free something trapped in the mill",
"stakes": "If the entity is freed without preparation, it will consume the village",
"stakeLevel": "local",
"reward": "The healer's entire savings (50gp) and a rare herb that cures any disease",
"deadline": "The next full moon, in 3 days"
}
Format: Yes.
curse
Generate curses with mechanical effects, triggers, cures, and hidden benefits.
Source: src/lib/rpg-engine/generators/curses.ts
Params:
{
type: "magical" | "divine" | "demonic" | "fae" | "natural" | "alchemical",
severity: "minor" | "major" | "legendary",
origin: "inherited" | "inflicted" | "bargained" | "accidental" | "deserved" | "prophesied",
includeHiddenBenefit: boolean
}
Response:
{
"name": "The Midas Blight",
"type": "fae",
"severity": "major",
"origin": "bargained",
"effect": "Everything the cursed touches turns to gold — including food and living things",
"mechanicalEffect": "Objects touched become gold (worthless as currency, too heavy). Food turns to gold on contact. Unarmed attacks deal 2d6 bludgeoning as flesh hardens.",
"trigger": "Skin contact with any organic material",
"cure": "Return the original bargain — place the golden apple back on the Fae Queen's tree",
"hiddenBenefit": "Gold-touched items can be used as improvised weapons. The cursed is immune to petrification.",
"duration": "Until cured"
}
Format: Yes.
hireling
Generate hirelings for hire — porters, guides, guards, with loyalty, skills, and deal-breakers.
Source: src/lib/rpg-engine/generators/hirelings.ts
Params:
{
type: "porter" | "torchbearer" | "guard" | "guide" | "healer" | "sage"
| "scout" | "cook" | "animal-handler" | "translator" | "locksmith" | "entertainer",
minLoyalty: number, // 2-20
includeBackground: boolean,
includeSecret: boolean
}
Response:
{
"name": "Pip Farthing",
"type": "torchbearer",
"description": "A wiry teenager with burn-scarred hands and an eager grin",
"loyalty": 14,
"loyaltyLevel": "loyal",
"morale": "confident",
"skill": "Can keep a torch lit in any weather",
"quirk": "Names every torch and mourns when they burn out",
"dealBreaker": "Will not enter water deeper than their knees",
"payment": {
"dailyRate": 1,
"bonusTrigger": "Surviving a combat encounter",
"nonMonetary": "A good meal and a warm place to sleep"
},
"specialAbility": "Can juggle lit torches as a distraction",
"background": "Orphan who grew up in the lamplighter's guild",
"secret": "Is actually the missing heir of a minor noble house"
}
loyaltyLevel values: "disloyal", "reluctant", "steady", "loyal", "devoted"
morale values: "broken", "shaken", "nervous", "steady", "confident", "inspired"
Format: Yes.
prophecy
Generate cryptic prophecies with hidden meanings and plot twists.
Source: src/lib/rpg-engine/generators/prophecies.ts
Params:
{
scope: "personal" | "local" | "world" | "cosmic",
tone: "hopeful" | "ominous" | "neutral" | "cryptic",
includeSource: boolean
}
Response:
{
"scope": "world",
"text": "When the three moons align and the silver tower sings, the Sleeper beneath the sea shall wake, and the world shall know a second dawn — or an endless night.",
"subject": "The return of an ancient god",
"condition": "The three moons must align (happens every 1,000 years — next occurrence in 3 months)",
"interpretation": "Most scholars believe it refers to a literal awakening of something beneath the ocean",
"twist": "The 'Sleeper' is not a god but a weapon, and the 'second dawn' is its detonation",
"tone": "ominous",
"source": "The Black Codex, written by the last Oracle of Vel'Thuum"
}
Format: Yes.
nightmare
Generate character nightmares — prophetic, traumatic, or just deeply strange.
Source: src/lib/rpg-engine/generators/nightmares.ts
Params:
{
type: "prophetic" | "traumatic" | "strange" | "warning" | "memory",
character: string // character name/description for personalization
}
Response:
{
"type": "prophetic",
"description": "You stand in a field of black wheat under a red sky. Each stalk whispers a name you've forgotten. At the center, a door stands alone — your hand reaches for the handle but your reflection in the brass knob is not your own.",
"mood": "Dread mixed with compulsion",
"imagery": [
"Black wheat whispering names",
"A red sky without a sun",
"A door standing alone in a field",
"A wrong reflection"
],
"emotionalImpact": "The dreamer wakes with the certainty that the door is real and waiting",
"wakeEffects": "DC 12 WIS save or gain no benefit from this rest",
"meaning": "The door represents a choice the character will face — the wrong reflection is who they'll become if they choose wrong"
}
Format: Yes.
scar
Generate battle scars with narrative weight — physical, emotional, and supernatural.
Source: src/lib/rpg-engine/generators/scars.ts
Params:
{
type: "physical" | "emotional" | "supernatural",
visibility: "hidden" | "subtle" | "noticeable" | "prominent" | "disfiguring",
damageType: "slashing" | "piercing" | "bludgeoning" | "fire" | "cold" | "acid"
| "lightning" | "necrotic" | "radiant" | "psychic" | "poison"
}
Response:
{
"type": "supernatural",
"name": "The Witch's Kiss",
"description": "A lip-shaped burn mark on the back of the hand that glows faintly blue in moonlight",
"origin": "Touched by a hag's dying curse",
"location": "Back of the right hand",
"visibility": "subtle",
"effect": "Advantage on saves against fey magic, but fey creatures can always sense you within 60 ft",
"roleplayNote": "The scar tingles when fey creatures are nearby",
"damageType": "necrotic",
"healable": false,
"healingMethod": "Only by the hag who placed it (she's dead)"
}
Format: Yes.
reputation
Generate reputation effects — fame, infamy, and the consequences of being known.
Source: src/lib/rpg-engine/generators/reputation.ts
Params:
{
type: "fame" | "infamy" | "mixed",
region: string // e.g. "the Northern Reaches"
}
Response:
{
"type": "fame",
"title": "The Dragon Slayer of Thornhaven",
"description": "Word of the dragon's defeat has spread across the region",
"level": 4,
"consequences": [
"Free drinks in most taverns",
"Nobles seek you out for quests",
"Aspiring adventurers follow you around"
],
"howToIncreaseIt": "Defeat another significant threat publicly",
"howToDecreaseit": "Fail publicly, or be absent when needed",
"inRegion": "the Northern Reaches"
}
level: 1 (barely known) to 5 (legendary)
Format: Yes.
death-legacy
Generate what happens when a character dies — last words, physical legacy, supernatural effects, and how they're remembered.
Source: src/lib/rpg-engine/generators/death-legacy.ts
Params:
{
characterClass: "warrior" | "paladin" | "ranger" | "rogue" | "wizard" | "cleric"
| "druid" | "bard" | "barbarian" | "monk" | "warlock" | "sorcerer",
causeOfDeath: "combat" | "sacrifice" | "curse" | "old-age" | "poison"
| "magic" | "betrayal" | "heroic"
}
Response:
{
"lastWords": {
"text": "Tell them... I held the line.",
"type": "hope",
"finalBreath": true
},
"physical": {
"items": ["Dented shield (now legendary)", "Blood-stained journal", "A ring meant for someone back home"],
"restingPlace": "Buried on the hilltop where they fell, facing the enemy's land",
"remainsCondition": "Peaceful, as if sleeping"
},
"memory": {
"description": "The soldiers who survived tell the story every year on the anniversary",
"rememberedBy": "The entire regiment and the village they saved",
"duration": "Generations — a holiday is named after them"
},
"supernatural": {
"description": "On foggy mornings, a spectral figure stands on the hilltop in a guardian's pose",
"magnitude": "noticeable",
"permanent": true
},
"legacy": {
"type": "spiritual",
"description": "Their courage inspires a new order of shield-bearers",
"manifestation": "The shield glows when wielded by someone defending the innocent",
"implications": "The shield becomes a relic — future quests may revolve around it"
}
}
supernatural.magnitude values: "subtle", "noticeable", "dramatic", "world-changing"
legacy.type values: "physical", "spiritual", "social", "magical", "prophetic"
Format: Yes.
legendary-affix
Generate Diablo-style item affixes — prefixes and suffixes for magical items.
Source: src/lib/rpg-engine/generators/legendary-affixes.ts
Params:
{
itemType: "weapon" | "armor" | "accessory" | "any",
powerLevel: "common" | "uncommon" | "rare" | "epic" | "legendary",
element: "fire" | "ice" | "lightning" | "poison" | "holy" | "shadow"
| "arcane" | "nature" | "psychic" | "force",
stat: "strength" | "dexterity" | "constitution" | "intelligence"
| "wisdom" | "charisma" | "speed" | "armor" | "health",
prefixOnly: boolean,
suffixOnly: boolean,
forceDouble: boolean // guarantee both prefix and suffix
}
Response:
{
"prefix": {
"name": "Blazing",
"effect": "Wreathed in magical fire that never extinguishes",
"mechanicalBonus": "+1d6 fire damage",
"element": "fire",
"powerLevel": "rare",
"itemTypes": ["weapon"]
},
"suffix": {
"name": "of the Titan",
"effect": "Imbued with the strength of giants",
"mechanicalBonus": "+2 STR, advantage on Athletics",
"stat": "strength",
"powerLevel": "rare",
"itemTypes": ["weapon", "armor", "accessory"]
},
"combinedPowerLevel": "rare",
"hasSynergy": true,
"synergyEffect": "Fire damage scales with STR modifier",
"affixCount": 2
}
Usage example: Apply to a longsword to get Blazing Longsword of the Titan — +1d6 fire damage, +2 STR, fire damage scales with STR modifier.
Format: Yes.
Quick Reference: All Generator Types
| Type | Tier | Params? | Format? |
|---|---|---|---|
encounter | 1 | Yes — terrain, partyLevel, partySize, difficulty, allowBoss | No |
npc | 1 | Yes — race, gender, socialClass, occupation, disposition, ageRange | No |
npc-crowd | 1 | Yes — size, quality | No |
combat | 1 | Yes — same as encounter | No |
loot | 1 | Yes — level | Yes |
shop | 1 | Yes — type, quality, size | Yes |
weather | 2 | Yes — climate, season | Yes |
tavern | 2 | Yes — quality | No |
rumor | 2 | Yes — count | No |
trap | 2 | Yes — severity, type, dungeonLevel | Yes |
settlement | 2 | Yes — size, government | Yes |
faction | 2 | Yes — type, size, alignment | Yes |
religion | 2 | Yes — domain, alignment | Yes |
travel | 2 | Yes — terrain, distance, difficulty | Yes |
spell | 3 | Yes — element, form, includeSuggestion | Yes |
spellbook | 3 | Yes — count, element, form | Yes |
career | 3 | Yes — category, failureReason, includeBackstory, includeBurden | Yes |
pockets | 3 | Yes — personType, itemCount, ensureHook | Yes |
dying-words | 3 | Yes — enemyType, category | Yes |
dungeon-dressing | 3 | Yes — roomType, category | Yes |
door | 3 | Yes — condition, includeSound, includeHook | Yes |
monster-motivation | 3 | Yes — monsterType, disposition | Yes |
found-document | 3 | Yes — type, condition | Yes |
spell-mishap | 3 | Yes — school, severity | Yes |
room-atmosphere | Adv | Yes — severity, category, ensureSecret | Yes |
boss-encounter | Adv | Yes — tier, type, partyLevel, includeMonologue | Yes |
hazard | Adv | Yes — type, severity, terrain | Yes |
secret | Adv | Yes — type, complexity, terrain | Yes |
riddle | Adv | Yes — difficulty, category, includeSource | Yes |
quest-hook | Adv | Yes — type, patronType, stakeLevel, includeDeadline | Yes |
curse | Adv | Yes — type, severity, origin, includeHiddenBenefit | Yes |
hireling | Adv | Yes — type, minLoyalty, includeBackground, includeSecret | Yes |
prophecy | Adv | Yes — scope, tone, includeSource | Yes |
nightmare | Adv | Yes — type, character | Yes |
scar | Adv | Yes — type, visibility, damageType | Yes |
reputation | Adv | Yes — type, region | Yes |
death-legacy | Adv | Yes — characterClass, causeOfDeath | Yes |
legendary-affix | Adv | Yes — itemType, powerLevel, element, stat, prefixOnly, suffixOnly, forceDouble | Yes |
MUD & AI Player
A set of experimental CLI harnesses that chain the RPG Engine generators into full playable experiences. All three live under src/lib/rpg-engine/ and are not part of the HTTP API — they are run as local Node scripts (npx tsx …) against the same generator library that powers the endpoints above.
MUD Overview
File: src/lib/rpg-engine/mud/index.ts
A text adventure that stitches together the OSR and advanced generators — doors, room atmospheres, monsters with motivations, secrets, riddles, hazards, loot with legendary affixes, found documents, curses — into a dungeon crawl. A local Ollama LLM (default model deepseek-r1:latest at http://localhost:11434) narrates the scene; the generators supply the structured content. Works entirely offline once the model is pulled.
Procedural elements wired into the MUD loop:
door,room-atmosphere,hazard,secret,riddle,monster-motivationcareer(for the player character),pockets,dying-wordsquest-hook,curse,hireling,legendary-affix,nightmare,prophecy,found-document,spell-mishap
npx tsx apps/potionquest/src/lib/rpg-engine/mud/index.ts
# or
./rpg-mud.sh
Playing the MUD
The MUD is a REPL — type a command at the prompt, the narrator responds, the room state updates.
Supported commands (free-form; the parser matches on verbs):
| Verb | Effect |
|---|---|
look | Re-describe the current room, including atmosphere and visible features |
search | Attempt to uncover the room's secret (if any) |
open | Interact with the room's door (condition, locks, traps) |
fight | Engage the current monster; resolves HP/motivation/dying-words on defeat |
talk | Attempt a social resolution with the monster (motivation-driven) |
read | Read any found-document in the room |
rest | Recover HP; risks a nightmare roll |
inventory | List carried loot (may include legendary affixes) |
quit | End the session |
Environment:
OLLAMA_URL— defaults tohttp://localhost:11434OLLAMA_MODEL— defaults todeepseek-r1:latest
AI Player
Files: src/lib/rpg-engine/mud/ai-player.ts, src/lib/rpg-engine/mud/claude-plays.ts
Autonomous agents that drive the MUD loop without a human at the keyboard. The decision-making agent inspects the current GameState (HP, gold, inventory, room contents, active monster) and picks an action; the engine resolves it with the same generators the interactive MUD uses; a transcript is emitted to stdout.
ai-player.ts— generic autonomous runner: rolls up a character, walks the dungeon, makes combat/search/talk decisions until death or victoryclaude-plays.ts— a scripted strategic playthrough of "The Hollow Throne" adventure, suitable for recording canonical game logs
npx tsx apps/potionquest/src/lib/rpg-engine/mud/ai-player.ts
npx tsx apps/potionquest/src/lib/rpg-engine/mud/claude-plays.ts
MCP Server
File: src/lib/rpg-engine/mcp-server/index.ts
Config: apps/potionquest/.mcp.json (registers the server as potionquest-rpg)
A Model Context Protocol server that exposes the MUD to Claude (or any MCP client) as callable tools. Claude can then reason about the state turn-by-turn instead of the game engine picking a best-guess action — the engine provides the world, Claude provides the player.
Exposed tools:
| Tool | Purpose |
|---|---|
rpg_start_game | Begin a new run, optionally with a player name |
rpg_get_state | Return the current room, HP, inventory, active monster, and visible features |
rpg_action | Take an action — look, search, open, fight, talk, read, rest |
rpg_get_options | Enumerate valid actions for the current state |
Transport is stdio, launched by the MCP client:
{
"mcpServers": {
"potionquest-rpg": {
"type": "stdio",
"command": "npx",
"args": ["tsx", "/absolute/path/to/apps/potionquest/src/lib/rpg-engine/mcp-server/index.ts"]
}
}
}