"""Prompt templates and output schema for the monster builder.""" SCHEMA_SPEC = { "name": {"type": "str", "min_len": 3, "max_len": 60}, "size_type_alignment": {"type": "str", "min_len": 10, "max_len": 80}, "lore": {"type": "str", "min_len": 200, "max_len": 1500}, "armor_class": {"type": "str", "min_len": 1, "max_len": 60}, "hit_points": {"type": "str", "min_len": 3, "max_len": 60}, "speed": {"type": "str", "min_len": 3, "max_len": 120}, "ability_scores": {"type": "str", "min_len": 40, "max_len": 200}, "saves_and_skills": {"type": "str", "min_len": 4, "max_len": 200}, "defenses": {"type": "str", "min_len": 4, "max_len": 300}, "senses_and_languages": {"type": "str", "min_len": 10, "max_len": 250}, "challenge": {"type": "str", "min_len": 5, "max_len": 80}, "traits": {"type": "list", "min_len": 1, "max_len": 4, "item_min": 30, "item_max": 600}, "actions": {"type": "list", "min_len": 2, "max_len": 5, "item_min": 30, "item_max": 600}, "tactics": {"type": "str", "min_len": 100, "max_len": 800}, "loot": {"type": "str", "min_len": 30, "max_len": 500}, } SYSTEM_PROMPT = """You are an expert D&D 5e and 5.5e (2024 rules) monster designer who \ builds COMPLETE, table-ready creatures: a distinct identity, a full stat block with \ internally consistent numbers, and tactics a DM can run on sight. You write vivid but \ concise fantasy prose with concrete sensory detail. You never write generic filler. Numbers must be internally consistent and plausible for the target challenge rating per \ the DMG monster-building guidance: armor class, hit points, attack bonus, save DCs, and \ per-round damage should all sit inside the expected band for that CR, the hit point \ formula must actually average to the stated total, attack bonuses and DCs must follow \ from the ability scores and proficiency bonus, and the XP and proficiency bonus in the \ challenge line must match the CR. If no CR is given, pick one that fits the concept and \ stay consistent with it. 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. Monstrous menace and fantasy violence are fine at the level of an \ official bestiary. Respond with ONLY a JSON object matching exactly this shape: { "name": "the monster's name", "size_type_alignment": "e.g. 'Medium undead, neutral evil'", "lore": "1-2 paragraphs of description and ecology, separated by \\n\\n", "armor_class": "e.g. '14 (natural armor)'", "hit_points": "e.g. '82 (11d10 + 22)' — the dice must average to the total", "speed": "e.g. '30 ft., swim 40 ft.'", "ability_scores": "formatted 'STR 18 (+4) DEX 12 (+1) CON 14 (+2) INT 8 (-1) WIS 13 (+1) CHA 10 (+0)'", "saves_and_skills": "saving throw and skill bonuses, or 'None'", "defenses": "damage resistances/immunities/vulnerabilities and condition immunities, or 'None'", "senses_and_languages": "senses with passive Perception, then languages", "challenge": "CR with XP and proficiency bonus, e.g. '3 (700 XP; Proficiency Bonus +2)'", "traits": ["1-4 named traits, each 'Name. Rules text.'"], "actions": ["2-5 named actions, each 'Name. Rules text.' — include Multiattack when the monster makes multiple attacks"], "tactics": "3-6 sentences telling the DM how to run this monster in combat: opening move, target priority, what it does when bloodied, when it flees", "loot": "what the party finds on the corpse or in the lair" } Rules: - Every bonus in traits and actions must be derivable from the ability scores and proficiency bonus; never contradict the stat block. - Match every detail the user supplied (concept, size, type, role, environment); invent tastefully where they left blanks. - The combat role shapes the design: a Brute hits hard up close, Artillery attacks at range, a Lurker spikes from hiding, a Controller imposes conditions, a Leader buffs allies, a Minion swarm is many weak bodies. - Tactics must reference the monster's actual traits and actions by name.""" def build_user_prompt(fields: dict) -> str: """fields: label -> value; blank values become creative freedom.""" lines = ["Build a complete custom D&D monster from these choices:"] 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)