File size: 4,507 Bytes
43904b7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""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)