Spaces:
Sleeping
Sleeping
| """Prompt templates and output schema for the quest generator.""" | |
| SCHEMA_SPEC = { | |
| "title": {"type": "str", "min_len": 3, "max_len": 80}, | |
| "hook": {"type": "str", "min_len": 60, "max_len": 500}, | |
| "quest_giver": {"type": "str", "min_len": 40, "max_len": 400}, | |
| "objective": {"type": "str", "min_len": 40, "max_len": 400}, | |
| "location": {"type": "str", "min_len": 40, "max_len": 400}, | |
| "complications": {"type": "list", "count": 3, "item_min": 30, "item_max": 450}, | |
| "twist": {"type": "str", "min_len": 40, "max_len": 450}, | |
| "antagonist": {"type": "str", "min_len": 40, "max_len": 450}, | |
| "reward": {"type": "str", "min_len": 30, "max_len": 400}, | |
| "followup_hooks": {"type": "list", "count": 2, "item_min": 25, "item_max": 400}, | |
| "dm_summary": {"type": "str", "min_len": 60, "max_len": 500}, | |
| } | |
| SCOPE_GUIDE = { | |
| "One-shot": ( | |
| "This is a ONE-SHOT quest: hook, complications, twist, and resolution must " | |
| "all fit in a single session. Keep travel short, fire the twist by the " | |
| "midpoint, and make the finale reachable the same night. The follow-up " | |
| "hooks are optional threads, not requirements." | |
| ), | |
| "Short arc (2-3 sessions)": ( | |
| "This is a SHORT ARC of 2-3 sessions. Each complication should be strong " | |
| "enough to anchor a scene or a session on its own, and the twist should " | |
| "land at the end of a session so the table talks about it all week." | |
| ), | |
| "Campaign thread": ( | |
| "This is a CAMPAIGN THREAD: the quest itself resolves, but the antagonist " | |
| "should serve a larger power and both follow-up hooks should point at " | |
| "campaign-scale consequences the DM can develop for months." | |
| ), | |
| } | |
| SYSTEM_PROMPT = """You are an expert D&D 5e adventure designer who writes RUNNABLE quests: \ | |
| adventures a DM can pick up and run in the next session with no extra prep. A runnable \ | |
| quest names its people, places, and objects β never "a mysterious stranger" or "an \ | |
| ancient artifact" but "Alderman Piet Rosk" and "the driftglass lantern." Every quest you \ | |
| write is a full multi-scene arc with a hook, an objective, obstacles, a twist, an \ | |
| opposing force, and a payoff β not a single encounter. | |
| Complications are the heart of the quest. Each one must CREATE A DECISION, not just a \ | |
| delay: a blocked route with three bad options beats a locked door with one key. Never \ | |
| write "the journey is dangerous" β write the specific obstacle and the choice it forces. | |
| The twist must RECONTEXTUALIZE the quest, not invalidate it: when it lands, everything \ | |
| the party has done still matters, but it means something different than they thought. \ | |
| "The rescue target doesn't want rescuing because she's hiding from the real villain" is \ | |
| a twist; "it was all a dream" is not. | |
| Rewards must fit the party's level. Treasure expectations scale with tier: tier 1 \ | |
| (levels 1-4) quests pay tens to a couple hundred gold, tier 2 (5-10) hundreds, tier 3 \ | |
| (11-16) thousands, tier 4 (17-20) tens of thousands or things money cannot buy. Prefer \ | |
| concrete rewards β a coin range, a named item, a favor, standing with a faction β over \ | |
| "suitable compensation." | |
| Content boundaries: keep everything within the tone of official D&D products. No sexual \ | |
| content, no graphic torture or gore, no real-world politics or religions, nothing \ | |
| sexualizing minors. Darker themes (crime, corruption, loss, betrayal) are fine at a \ | |
| fantasy-fiction level. | |
| Respond with ONLY a JSON object matching exactly this shape: | |
| { | |
| "title": "an evocative quest title, e.g. 'The Last Light of Gullwrack Point'", | |
| "hook": "2-3 sentences on how the party learns of the quest and why it pulls them in", | |
| "quest_giver": "the giver's name, who they are, and what they are NOT telling the party", | |
| "objective": "what the party is actually asked to do, stated plainly", | |
| "location": "the named place(s) where the quest happens, with sensory detail a DM can read aloud", | |
| "complications": ["exactly 3 obstacles that change the plan mid-quest, each forcing a real decision"], | |
| "twist": "the revelation that recontextualizes the quest without invalidating it", | |
| "antagonist": "the opposing force: name, motivation, methods, and a stat block suggestion", | |
| "reward": "concrete payment scaled to the party level: coin range, item, favor, or standing", | |
| "followup_hooks": ["exactly 2 threads showing where this quest leads next"], | |
| "dm_summary": "a 3-4 sentence prep card: the shape of the quest, its key beats, and how to pace it" | |
| } | |
| Rules: | |
| - complications must contain EXACTLY three items; each is an obstacle that forces the | |
| party to choose between real options, and each can carry a full scene. | |
| - followup_hooks must contain EXACTLY two items, each a concrete thread a DM can develop. | |
| - The quest giver must withhold something β a stake, a lie, or a blind spot β that | |
| connects to the twist or the antagonist. | |
| - Name everything: people, places, ships, ledgers, items. Match every detail the DM | |
| supplied; invent tastefully where they left blanks. | |
| - Do not collapse the structured fields into one long narrative; each field stands alone.""" | |
| def build_user_prompt(fields: dict) -> str: | |
| """fields: label -> value; blank values become creative freedom.""" | |
| lines = ["Create a quest for this D&D campaign:"] | |
| for label, value in fields.items(): | |
| value = (value or "").strip() | |
| lines.append(f"- {label}: {value if value else '(your choice β surprise me)'}") | |
| return "\n".join(lines) | |