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
- Analysis & Game-Design Tools — distribution, check, simulate, sample, loot-value, drop-odds, farm-rate
- Interactive Fiction
- Choose-Your-Own-Adventure
- Expedition — the playable vertical slice
- Quests, Live-Ops & Economy — objective, questline, events, loot-value, drop-odds, farm-rate
- Artifice — procedural magic items — forge
- Furnishings — interactable room props
- Grimoire — procedural spells — spellcraft
- Generator Reference
- Atlas: Fractal Locations — place, place-contents (what's here), journey (traversal risk), craft / craft-plan
- Tier 1: Core DM Tools — encounter, npc, npc-crowd, combat, loot, shop
- Tier 2: World Depth — weather, tavern, bakery, 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.
POST /api/rpg/analyze
Auth: API key required.
Game-design analysis — given a mechanic, describe how it behaves. See the full Analysis & Game-Design Tools section below.
Request:
{
"tool": "distribution",
"params": { "notation": "4d6dl1", "target": 15 },
"format": true
}
| Field | Type | Required | Description |
|---|---|---|---|
tool | string | Yes | One of distribution, check, simulate, sample |
params | object | Yes | Tool-specific parameters (see below) |
format | boolean | No | Include a human-readable formatted string (markdown + ASCII charts) |
Response: { "tool": "...", "result": { ... }, "formatted": "..." }
Analysis & Game-Design Tools
Two pillars of the engine's design mission live here. Deterministic tools
return exact answers (no sampling); Monte-Carlo tools sample thousands of
trials and report distributions. Both are exposed via POST /api/rpg/analyze
with a tool field, and via the library as rpg.analysis.*.
| Tool | Pillar | Answers |
|---|---|---|
distribution | Deterministic | "What's the exact shape of 4d6dl1?" |
check | Deterministic | "What do I need to hit AC 16 at +7 with advantage?" |
simulate | Monte-Carlo | "Is this boss fight fair for a party of four?" |
sample | Monte-Carlo | "Sample 2d6+3 reproducibly; how fast does the mean converge?" |
distribution — exact dice distribution
Exact probability mass function and statistics of a dice expression. Plain
NdS±M*K is folded by convolution; keep/drop expressions (4d6kh3, 4d6dl1)
are enumerated exactly under a state-space cap and fall back to Monte-Carlo
(flagged exact: false) only above it.
Params: { notation: string, target?: number }
Notation: standard NdS, NdS+M, NdS-M, NdS*K, plus keep/drop —
kh/kl (keep highest/lowest N), dh/dl (drop highest/lowest N). 4d6dl1
≡ 4d6kh3 (the classic stat roll).
Result: exact, min, max, mean, mode, variance, stdev,
percentiles (p5/p25/p50/p75/p95), histogram ({value, probability, cumulative}[]),
and — if target is given — query: { target, atLeast, atMost }.
Format: Yes — renders an ASCII histogram of the full curve.
check — exact d20 success odds
Closed-form success/crit/fumble odds for a d20 roll against a target, matching
the engine's own resolution rules (hit when natural + bonus >= target; natural
crit auto-succeeds; natural auto-fail auto-misses). Advantage/disadvantage use
the exact max/min-of-two-d20 distributions.
Params: { bonus, dc, advantage?, disadvantage?, critRange?=20, autoFailOn?=1, critAutoSucceeds?=true, label? }
— for a saving throw or ability check (no auto-fail on a 1), pass autoFailOn: null.
Result: success, failure, crit, fumble, attemptsPerSuccess,
hitsOnNatural, mode.
Format: Yes.
simulate — Monte-Carlo encounter balance
Runs the engine's own combat resolver over N trials (combatants are deep-cloned each trial) and reports the outcome distribution from the party's perspective.
Params:
{
party: CombatantSpec[], // required, non-empty — the PCs
enemies: CombatantSpec[], // required, non-empty — the opposition
trials?: number, // default 1000, max 100000
maxRounds?: number // default 50; draw if unresolved
}
// CombatantSpec: { name, hp, ac, attackBonus, damageDice, damageBonus?, count?, dex?, level? }
Result: winRate, tpkRate, drawRate, rounds (mean/stdev/min/max +
histogram), partySurvivors (mean + histogram), partyHpRemaining,
partyHpRemainingOnWin, and a heuristic verdict (trivial / fair / tough
/ deadly / lethal, plus a swingy flag and a one-line rationale).
Verdict heuristic (rules of thumb, not gospel):
trivialneeds ≥98% wins, ≤1% TPK, and ≥60% HP left on wins;fair≥75% wins;tough≥50%;deadly≥25%; elselethal.swingyflags near-coin-flip win rates or high round variance.
Format: Yes — verdict, outcome table, and an ASCII survivor histogram.
sample — seedable Monte-Carlo dice sampling
Samples a dice expression with an optional seedable PRNG (mulberry32), for reproducible balance runs. Reports the standard error of the mean and, when an exact mean is computable, the convergence error against it.
Params: { notation: string, samples?=10000, seed? } — same seed ⇒ identical results.
Result: mean, stdev, min, max, standardError, histogram, and
(when available) exactMean + meanError.
Format: No.
Atlas — Fractal Nested Locations
place
Generate fractal nested locations — a scale-free, seed-deterministic, lazily-expanded tree of places. A "zone" is just one rung; the same generator runs a galaxy or a broom closet. The ladder (the set of scales) is data, so the atlas maps any domain.
Source: src/lib/rpg-engine/atlas/
Two relations recur at every scale:
- containment — a place contains places (the nesting tree)
- connection — a place links to places (the traversal graph), including rare scale-skipping portals (wormholes through the hierarchy)
The whole infinite structure is a pure function of (ladder, seed, path):
- deterministic — resolve any coordinate in O(depth), reproducibly (the seed is echoed back)
- lazy — expand a neighbourhood only when you "zoom in" (level-of-detail / fog-of-war)
A scalar field (e.g. threat, strangeness, menace) varies across siblings as a
gradient; connection cost rises where an edge crosses the gradient — the generalization of
a "contested corridor" to any continuous spatial property.
Params:
{
ladder: "realm" | "cosmos" | "dungeon", // default "realm"; ladder is data — add your own
seed: string | number, // omit for random; always echoed back for reproducibility
path: number[], // coordinate to jump to (default [] = root)
depth: number, // levels to expand below the resolved node (0–8, default 1)
nodeBudget: number // cap on nodes generated (default 4000)
}
Ladders & their scale chains:
realm— realm ▸ region ▸ settlement ▸ district ▸ building ▸ room (field:threat)cosmos— universe ▸ galaxy ▸ system ▸ world ▸ region ▸ site (field:strangeness)dungeon— complex ▸ wing ▸ hall ▸ chamber (field:menace)
Response (result): { ladder, seed, path, place, nodesGenerated }, where each place has
id, name, address, scaleKey/Label, archetype/Label, path, seed, depth, field {name,value}, blurb, childCount, childrenExpandable, children?[], connections?[]. Children appear only once
expanded; childrenExpandable + childCount describe deeper layers without materializing them.
Reproducibility & navigation: the same seed always yields the same cosmos. Pass
path: [1] (same seed) to descend into the second child; pass a deep path with depth: 0
to teleport to a coordinate and read it without generating its neighbours.
Format: Yes — an indented tree with the connection graph and an ASCII field gradient.
place-contents
What's here. Given a place's biome tags + danger field, deterministically derive what populates it: harvest nodes (herb/ore/fish/hide/reagent/timber/forage, with a profession + skill tier), creature spawns (threat-scaled, role-classified), and a loot drop table with closed-form expected gp value. The bridge from where to what, and the front half of the gather → craft → economy loop.
Source: src/lib/rpg-engine/biome/
Params: { ladder, seed, path } — resolves that coordinate (via the atlas) and
populates it. Contents are a pure function of the place's seed, so a place always has the
same resources (persistent-world property). Selection is by tag-affinity with the biome;
quantity, threat, and loot rarity scale with the danger field.
Response (result): { seed, place: {id,name,address,archetype,field}, contents }
where contents is { field, harvestNodes[], spawns[], dropTable {rolls, entries[], expectedValue} }.
Format: Yes — gathering / spawns / loot with drop percentages and EV.
journey
Traversal risk (Monte-Carlo). Route between two children of a place (least-cost path over the connection graph) and simulate the crossing thousands of times. The danger field of each leg drives encounter frequency and lethality; reports the odds the party survives. "What are the odds of surviving the road from A to B?"
Source: src/lib/rpg-engine/journey/
Params:
{
ladder, seed, path, // the place whose children you travel among
from: number | string, // start child (index or name)
to: number | string, // destination child (index or name)
party: { members, hpEach, mitigation? }, // default { members:4, hpEach:25, mitigation:0 }
trials: number, // Monte-Carlo trials (default 2000)
simSeed: number // optional — reproducible runs
}
Risk model (documented, tunable): encounterChance = clamp((0.04 + 0.007·field)·(1 + 0.25·(cost−1)), 0, 0.95);
encounter damage = (1 + ⌊field/15⌋)d6, reduced by mitigation.
Response (result): { ladder, seed, path, route, risk } — route is the leg sequence
- total cost;
riskis{ survivalRate, tpkRate, expectedEncounters, analyticExpectedEncounters, hpRemainingFraction, hpRemaining{mean,histogram}, deadliestLeg, perLeg[] }.
Format: Yes — verdict, survival %, per-leg encounter odds, deadliest leg.
craft / craft-plan
Closes the gather → craft → economy loop. Recipes consume materials whose keys align with biome harvest-node resources (iron, kingsleaf, snapper, thickpelt, …) and produce items, gated by a profession skill with the classic orange/yellow/green/grey skill-up curve. Quality (crude/standard/fine/masterwork) rises with how far skill exceeds the requirement.
Source: src/lib/rpg-engine/crafting/
craft — resolve a single craft (seedable, deterministic).
Params: { recipe: string, skill?: number, seed?: number, inventory?: Record<string,number> }.
Result: { crafted, output {item,qty,quality,value}, consumed[], skillUp, skillUpChance, skillBefore, skillAfter }.
craft-plan — Monte-Carlo project a skill grind X → Y, switching recipes as they grey out.
Params: { profession?: string, recipes?, fromSkill, toSkill, trials?, seed?, materialPrices? }.
Result: { reached, crafts {mean,stdev,min,max}, materials {mat: expectedQty}, expectedOutputValue, expectedMaterialCost?, recipeUsage[] }.
This is the "grind-time" deterministic/Monte-Carlo tool — e.g. "Smithing 1→200 ≈ 270 crafts, 974 iron, 2511 gp of materials."
Professions: smithing, alchemy, cooking, leatherworking, arcana.
Format: Yes — single craft line; projection shows crafts, materials, value, recipes used.
Interactive Fiction
A second lens for the engine: not procedural generation, but a stateful interactive-fiction state machine — the primitives a Zork needs (a mutable world of rooms + objects, a verb-noun parser, inventory, containers, light vs. darkness, gated exits, and win/lose). Where the generators answer "what exists?", this answers "what happens when the player does X?".
Source: src/lib/rpg-engine/fiction/
Library: rpg.fiction.{ miniZork, step, run, parse }. step(world, command) is
pure — it clones the world, applies one parsed command, and returns
{ world, output }, so transcripts are reproducible and states snapshottable.
POST /api/rpg/fiction
Auth: API key required. Stateless — the client carries the world
between turns, so any number of agents can play in parallel.
// Start a game:
{ "game": "mini-zork" }
// → { world, output: "West of House …", won, dead, turns }
// Take a turn (send back the world you received):
{ "world": { /* … */ }, "command": "open mailbox" }
// → { world, output: "Opening the small mailbox reveals a leaflet.", won, dead, turns, score }
Mini-Zork is a five-room proof world (West of House → Behind House → Kitchen
→ Living Room → Cellar). It exercises every IF primitive: the mailbox (openable
container revealing a leaflet), the window (a gated exit), the brass lamp vs. the
grue in the dark cellar, and a treasures-in-the-trophy-case win. The winning
walkthrough is rpg.fiction.WINNING_WALKTHROUGH.
Feasibility note. The map model here (rooms + named, optionally-gated exits) is the same graph the atlas produces — an atlas place adapts directly into these rooms, so the same engine can drive procedurally- generated interactive fiction, not just hand-authored worlds.
Choose-Your-Own-Adventure
Benchmark lens #3: a branching gamebook as a graph of passages joined by flag-gated choices ("turn to page 47 if you have the silver key", generalized). Where the fiction engine is a spatial state machine, this is a narrative graph; both share the flag/world-state idea.
Source: src/lib/rpg-engine/cyoa/
Library: rpg.cyoa.{ open, step, choose, run, choices, isWinnable, SUNKEN_FANE }.
choose/step are pure; isWinnable validates a story is actually solvable.
POST /api/rpg/cyoa
Auth: API key required. Stateless — the client carries state between turns.
{ } // start (default: The Sunken Fane) → opening text + choices
{ "state": { /* … */ }, "choice": 0 } // take the Nth available choice
// → { state, output, choices, ending }
The Sunken Fane is a sample gamebook exercising every primitive: a flag-gated
choice (you need the torch to brave the dark), a counter (collect two sigils to open
the vault), and branching win / lose endings. Winning path: rpg.cyoa.SUNKEN_FANE_WIN.
Expedition — the playable vertical slice
The capstone: a single stateful adventure that wires six systems into one loop, proving they compose. Not a generator — a game.
- atlas lays out the region (places + a connection graph + a danger gradient)
- biome stocks each place (what to gather, what stalks it, its loot)
- journey governs travel risk (crossing toward danger provokes encounters)
- crafting gates the finale (the boss-bane)
- combat (the engine's own d20 module) resolves every fight
The arc: gather reagents → craft the boss-bane → cross the danger gradient → defeat the warlord.
The warlord sits at the highest-danger place; the boss-bane is a hard gate (in playtests,
~98% win with it, 0% without) — so the loop is both rewarding and enforced.
Source: src/lib/rpg-engine/expedition/
Library: rpg.expedition.{ newGame, step, run, describe }. State is serializable;
combat uses the combat module's RNG, so fights vary (it's a game), but the arc is fixed.
POST /api/rpg/expedition
Auth: API key required. Stateless — the client carries state between turns.
{ "seed": "stranglethorn" } // start → opening place + intro
{ "state": { /* … */ }, "command": "gather" } // advance one turn → { state, output, over, won }
Commands: look · map · go <n> · gather · craft · fight · status · help.
Quests, Live-Ops & Economy
objective / questline
Quest structure (vs. flavor). An objective is a typed, checkable goal
(kill-named, collect-assemble, collect, escort, deliver, discover, clear);
a questline is a dependency graph of objectives with prerequisites — the spine of a
progression. Seeded ⇒ reproducible. (The Green Hills of Stranglethorn page-assembly
quest is the collect-assemble archetype.)
Source: src/lib/rpg-engine/objectives/
Params: objective → { seed?, type?, difficulty? }; questline → { seed?, length?=4, capstone?=true }.
Format: Yes.
events
Live-ops: a board of scheduled world events — recurring spawns (the Gurubashi Arena chest every 3h), daily/weekly windows (the Fishing Extravaganza, Sundays 14:00–16:00), and competitive tiered-reward events. Pure time arithmetic; the caller supplies the clock.
Source: src/lib/rpg-engine/liveops/
Params: { at?: number } (epoch seconds; defaults to now).
Response: each event's { active, secondsUntilStart, secondsUntilEnd, nextStart }.
Format: Yes.
loot-value / drop-odds / farm-rate (economy analysis)
The meta-game half of analysis, applied to loot and farming:
loot-value— exact expected value + variance + percentiles of a drop table (compound distribution: a random number of weighted-value draws). Params:{ rolls?, entries: {item?,weight,value}[] }.drop-odds— closed-form rare-drop math:P(≥1 in N kills), expected kills to first, and kills for a 50/90/99% confidence. Params:{ rate, kills? }.farm-rate— Monte-Carlo gold/hour with variance (seedable), plus an analytic cross-check. Params:{ killsPerHour, perKill: dropTable, hours?, trials?, seed? }.
Also on the library as rpg.analysis.{ lootValue, dropOdds, farmRate }. Drop-table input
is structural — a biome dropTable, a hand-written table, or any { rolls, entries[] }.
Format: Yes.
Grimoire — procedural spells
The artifice pattern applied to spells: full castable stat blocks across casting
traditions, coherent by construction. (Complements osr.spells, which generates evocative
two-word names — grimoire generates the whole spell.)
- tradition (the spine):
wizard · sorcery · priest · druid · paladin · ranger · illusionist · warlock · shaman · monk. Each dictates favored schools, the legal effect families, the damage/save palette, the components, and the voice — a druid's spell can't read like a necromancer's, and a monk casts somatic-only ki (never a V,S,M ritual). - level (cantrip → 9th): the power budget — dice, range, duration, save DC, concentration, and "At Higher Levels" scaling.
- effect family gated by tradition: damage / heal / ward / control / summon / illusion / curse / buff / movement / utility / divination / smite / drain / aura — each rendered tradition-flavored and level-scaled.
spellcraft
Params: { tradition?, level?, school?, family?, seed? } — any axis pins, the rest roll
(levels skew low). Seed-deterministic. Output: name · level · school · casting time · range ·
components (with themed material) · duration · effect (+ At Higher Levels) · save · flavor.
Verified: over 5,000 forges, ~99% unique spells; every family + school is tradition-legal; saves appear only when the family forces one; monks never carry material components; damage types stay within each tradition's palette. Example:
## Maw of the Patron's Will
Necromancy cantrip · Pact & Grave
Components: V, S, M (a drop of the caster's blood)
Duration: Concentration, up to 1 hour
You call forth a swarm of shadows. It obeys your commands and acts on your turn…
Format: Yes — a sourcebook stat block.
Furnishings — interactable room props
The texture layer between dungeonDressing (mood) and loot (reward): the
mundane props that furnish a room — crates, barrels, sacks, baskets,
bookshelves, racks, urns, chests — as first-class objects you can place,
search, and find things inside. Where dressing describes a room, furnishings
are the stuff you poke at.
Source: src/lib/rpg-engine/furnishings/
furnish
Params: { genre, roomType, density?, seed? } — seed-deterministic.
- genre (the domain pack):
dungeon·arcane-study·homestead·ship·sci-fi-bay. The same generator runs all of them — a dungeon storeroom grows barrels and crates; a wizard's study grows bookshelves and reagent shelves; a ship's hold grows lockers and coiled rope. Combinatorial material × condition × noun yields hundreds of distinct props. - roomType is genre-specific (library, laboratory, storage, armory, crypt, hold, cabin, cargo, kitchen…); unknown types fall back to a sensible default set.
Each prop has a kind (container/shelf/surface/fixture/decor/seating), material,
condition (pristine/worn/dusty/broken/charred/overturned), and — for searchable props —
locked / trapped flags and pre-rolled contents ({ items[], value, note? }),
gated by a per-prop loot chance (broken/overturned props are likelier empty). The occasional
container tucks a readable note (the found-document hook).
Composition: rpg.furnishings.toFictionObjects(room, roomId) adapts a furnished room
into the fiction engine's GameObjects — searchable containers become
openable props, and their contents become takeable child objects inside them. So a
generated (or Zork) room can be auto-stuffed with barrels you can actually open and loot.
Format: Yes — props grouped with their lock/trap state and what a search turns up.
Artifice — procedural magic items
A generative grammar, not a list (the named loot magic-item table still exists
for classic D&D items; this forges new ones). Produces items no one has seen before that
still make sense, because coherence is engineered along four axes:
- theme (the spine) — one of
ember · rime · tempest · umbra · radiance · venom · tide · verdant · stone · star · blood · moon. The theme alone dictates the item's name, materials, colours, appearance, the flavor and damage type of its powers, its origin, and its curse. A "rime" item never deals fire; a "star" item reads fate in constellations. - form (the gate) —
blade · axe · maul · bow · armor · shield · cloak · boots · gauntlets · helm · ring · amulet · wand · staff · orb · tome · instrument. The form decides which power families are even legal — a ring carries wards and granted spells, never a melee on-hit. - rarity (
common → uncommon → rare → very_rare → legendary → artifact) — sets the power budget, charge count, attunement, and gp value. Legendary+ items earn a true name, an origin, and a curse.
forge
Params: { theme?, form?, rarity?, seed? } — any axis can be pinned; the rest roll
(rarity weighted toward the low end). Seed-deterministic. rpg.artifice.toMagicItem(item)
adapts a forged item into the loot MagicItem shape, so it drops into hoards, shops, and
treasure hauls.
Coherence & novelty (measured): over 5,000 random forges, 100% are unique items and ~95% have unique names; every power is form-legal and theme-consistent. Example output:
## Starzenith, Voice of the Void
Legendary eye · star-touched · requires attunement
a flawless eye of meteoric silver that holds a fleck of distant light.
Charges: 10
- Expend a charge to read a fate in the stars (on a roll of 6 the charge is not spent).
- Gaze into it to read constellations that rearrange to answer; once per day, ask after a
person or place and receive a true answer, star-veiled.
Origin. Forged in the void between worlds.
Curse. It belongs to the sky and wishes to return.
Value. ~198,169 gp
Format: Yes — a sourcebook entry (appearance, powers, charges, origin, curse, value).
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" | "bakery",
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.
bakery
Generate a complete bakery — baker, named sourdough starter, time-of-day oven state, wares, signature bake, enchanted pastries, day-old basket, gossip, and a plot hook. Taverns are where adventurers drink; bakeries are where towns actually talk.
Source: src/lib/rpg-engine/world/bakeries.ts
Params:
{
quality: "poor" | "modest" | "comfortable" | "wealthy" | "aristocratic",
timeOfDay: "dawn" | "morning" | "midday" | "afternoon" | "evening" | "night",
era: "fantasy" | "alexandria" | "depression" | "mars" // default "fantasy"
}
Eras — the generator travels through time and space. Era packs (src/lib/rpg-engine/world/bakery-eras.ts) swap the flavor layer — names, wares, currency, smells, oven states, starters, hooks, gossip — while the bones stay the same everywhere humans bake bread:
fantasy(default) — the medieval-fantasy bakery described belowalexandria— under the shadow of the Lighthouse, c. 200 BCE; prices in obols, gossip from the Library, ship's biscuit for the grain fleetdepression— a pop-up cart at the edge of Central Park, 1933; doughnuts at 1¢, the Lucky Penny Bun, hooks involving a Packard from Chicagomars— Olympus Colony, Dome 7; the First Scone (Sol 88), Centrifuge Éclairs whipped at 1g, a starter whose manifest still says GEOLOGY
Response:
{
"name": "The Honeyed Crumb",
"quality": "comfortable",
"description": "The Honeyed Crumb is a comfortable bakery run by Petra Underbough. Famous for The Drunken Plum Knot.",
"smell": "Butter and sugar in quantities the neighbors describe as irresponsible.",
"ovenState": {
"timeOfDay": "dawn",
"scene": "The day's first bread comes out as the sky goes pink. Regulars wait outside in coats over nightclothes."
},
"baker": {
"name": "Petra Underbough",
"role": "Baker",
"personality": "Cheerful halfling baker who hums constantly",
"secret": "Owes a dangerous person a lot of money"
},
"starter": {
"name": "Old Greta",
"age": "47 years",
"quirk": "sulks in thunderstorms"
},
"wares": [
{ "name": "Sourdough Boule", "category": "Daily Breads", "price": "6 cp", "freshness": "oven-hot", "description": "From the house starter — ask its name" }
],
"signatureBake": {
"name": "The Drunken Plum Knot",
"story": "Plums soaked in last year's mead. The recipe arrived as payment of a gambling debt and the baker refuses to say whose.",
"price": "2 sp"
},
"enchantedPastry": {
"name": "Second-Breakfast Scone",
"effect": "Counts as a full day's rations. During your next short rest, regain +1 hp per Hit Die spent.",
"price": "5 sp"
},
"dayOldBasket": {
"items": ["Honey Bun", "Mutton Hand Pie"],
"oddity": "a small iron key, still warm"
},
"gossip": [ { "text": "...", "truth": "half_true", "reality": "...", "source": "...", "type": "local_gossip" } ],
"hook": "A noble's servant buys the same plain roll every morning and checks inside it before paying."
}
The wares always include a Traveler's Rack chance — hardtack, trail biscuits, and waybread for party resupply. Bakeries are also available as a shop type (params: { type: "bakery" }) for settlement generation.
Format: Yes — renders the full bakery as markdown, gossip and hook included.
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 |
bakery | 2 | Yes — quality, timeOfDay, era | Yes |
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"]
}
}
}