Content Format & Level Design
Status: Design specification Date: 2026-03-26
Overview
World content (areas, rooms, NPCs, mobs, items, quests) is authored as JSON files, one per area. These files can be:
- Hand-written by builders
- Generated by AI models (Claude, etc.) from area briefs
- Edited in any text editor or future web-based builder
- Validated before loading
- Version-controlled in git
- Loaded into the %mud-world agent via admin poke
Content is state, not code. Adding a new dungeon doesn’t require an OTA update — it’s a poke to the running agent.
Entity Model
Mobs vs NPCs
These are fundamentally different entities that share a room but serve different purposes:
| Aspect | Mob | NPC |
|---|---|---|
| Purpose | Combat encounter | Interaction, story, services |
| Primary data | Stats, loot tables, aggro | Dialogue, quests, services |
| Player interaction | Kill it | Talk to it |
| On death | Drops loot, awards XP, respawns on reset | Shouldn’t normally die (but can have combat stats) |
| Examples | Shadow wolf, goblin, dragon | Shopkeeper, quest giver, sage, guard |
Some entities are both — a guard NPC who gives directions but fights if attacked. These are NPCs with optional combat stats.
Spawn Types
| Type | Exists When | Location | Behavior |
|---|---|---|---|
| Fixed | Always (respawns on area reset if killed) | Specific room, never moves | Quest NPCs, shopkeepers, bosses, guards |
| Roaming | Always (respawns on area reset if killed) | Spawns in one room, wanders within area | Regular mobs, patrol guards |
| Encounter | Created on room entry, temporary | Generated in the room the player enters | Wilderness random fights |
Fixed entities are part of the geography. Players learn “the blacksmith is north of the fountain.” They’re always there (or will be after the next reset).
Roaming entities create unpredictability. You know there are wolves in the Darkwood, but you don’t know which room they’re in right now. They wander via mob AI on regen ticks.
Encounters are stateless. No permanent existence in the world. A percentage chance fires on room entry, a mob appears, you fight it (or flee), it’s gone. Good for wilderness areas where constant mob population would feel artificial.
Area File Format (JSON)
One file per area. Contains everything needed to bring the area to life.
{
"area": {
"id": "darkwood-forest",
"name": "The Darkwood Forest",
"min-level": 10,
"max-level": 20,
"reset-interval": "~m.15",
"description": "A dense, ancient forest shrouded in perpetual twilight. Something has corrupted the heart of the Darkwood, and shadow wolves now roam where once only deer grazed."
},
"rooms": [
{
"id": 1001,
"name": "Forest Entrance",
"description": "The road narrows to a dirt path here, swallowed by towering oaks whose canopy blocks most of the sky. A weathered signpost leans at an angle, its warnings barely legible. The air smells of damp earth and something faintly acrid.",
"sector": "forest",
"coord": {"x": 0, "y": 0, "z": 0},
"exits": {"north": 1002, "south": 500},
"flags": ["waypoint"]
},
{
"id": 1002,
"name": "Overgrown Trail",
"description": "Thick roots buckle the path underfoot. Strange markings scar the bark of the surrounding trees — deep gouges, too deliberate to be animal. The light dims noticeably here, as if the canopy has thickened to shut out the sky.",
"sector": "forest",
"coord": {"x": 0, "y": -1, "z": 0},
"exits": {"south": 1001, "north": 1003, "east": 1004}
},
{
"id": 1008,
"name": "The Hollow",
"description": "The trees open into a sunken clearing carpeted in dead leaves. No birdsong reaches here. The darkness isn't just absence of light — it moves, pooling in the low places like black water. Tendrils of shadow seep from a narrow cave entrance to the east.",
"sector": "forest",
"coord": {"x": 0, "y": -4, "z": 0},
"exits": {"south": 1007, "east": 1012}
},
{
"id": 1012,
"name": "Shadow Altar Chamber",
"description": "The cave opens into a chamber of rough-hewn stone. At its center, a crude altar of black rock pulses with dark energy, each beat sending visible ripples through the shadows. Runes carved into the altar's surface glow with a sickly violet light.",
"sector": "underground",
"coord": {"x": 1, "y": -4, "z": 0},
"exits": {"west": 1008},
"flags": ["no-recall", "dark"]
}
],
"npcs": [
{
"id": 5001,
"name": "Warden Elara",
"short-desc": "Warden Elara, keeper of the forest edge",
"long-desc": "A weathered woman in green leather leans against the signpost, watching the treeline with sharp eyes.",
"look-desc": "Elara's face is lined from years in the sun. A longbow hangs across her back, and her hands bear the calluses of someone who's drawn it ten thousand times. A silver badge on her collar marks her as a Forest Warden.",
"room": 1001,
"spawn-type": "fixed",
"flags": ["stationary", "quest-giver", "dialogue"],
"services": [],
"combat-stats": null,
"dialogue": {
"mode": "static",
"greeting": "You look like you can handle yourself. Good — we need people who aren't afraid of the dark.",
"topics": {
"forest": "Something's wrong in the Darkwood. The shadow wolves are bolder than they've ever been, and there are... things deeper in that weren't there a season ago.",
"wolves": "They hunt in packs of two or three. Stay sharp and you'll manage. But don't go past the Hollow — that's where the big ones den.",
"warden": "I keep watch here. Someone has to. The town council thinks a signpost and a warning is enough. I know better.",
"hollow": "North through the trails, past the ravine. You'll know it when the trees stop letting light through. That's where I lost my partner last month."
},
"keywords": {
"partner": "Maren. Best tracker I ever knew. Went to investigate the source of the corruption and never came back. I'd go myself, but someone has to hold this post. If you find any sign of her... I need to know.",
"maren": "Maren. Best tracker I ever knew. Went to investigate the source of the corruption and never came back."
}
},
"quests": ["darkwood-shadow-source", "darkwood-find-maren"]
},
{
"id": 5005,
"name": "Old Gareth",
"short-desc": "Old Gareth, the tavern keeper",
"long-desc": "A heavyset man with a scarred apron wipes the bar with a rag that's seen better days.",
"look-desc": "Gareth's hands are thick and steady, the hands of someone who's broken up more bar fights than he can count. His eyes are sharp despite the wrinkles, and he seems to notice everything that happens in his tavern.",
"room": 1001,
"spawn-type": "fixed",
"flags": ["stationary", "dialogue", "shopkeeper"],
"services": ["shop"],
"combat-stats": null,
"dialogue": {
"mode": "llm",
"system-context": "You are Old Gareth, the tavern keeper at the Rusty Anchor near the Darkwood Forest entrance. You've run this tavern for 30 years. You're gruff but kind. You know rumors about every area in the region. You speak in short, direct sentences. You have a bad knee and complain about the weather. You water down the ale but would never admit it. You've seen a lot of adventurers come and go — most don't come back from the deep forest.",
"world-context-keys": ["recent-events", "area-lore-darkwood", "player-reputation"],
"fallback-greeting": "What'll it be?",
"rate-limit": 10
},
"quests": []
},
{
"id": 5010,
"name": "Warden Maren",
"short-desc": "Warden Maren, trapped in shadow",
"long-desc": "A woman in torn green leather is suspended in a web of solidified shadow, barely conscious.",
"look-desc": "Maren's face is drawn and pale, her eyes flickering between consciousness and something darker. Her warden's badge is cracked. Despite her captivity, her hand still grips a broken dagger.",
"room": 1018,
"spawn-type": "fixed",
"flags": ["stationary", "quest-target", "dialogue"],
"services": [],
"combat-stats": null,
"dialogue": {
"mode": "static",
"greeting": "You... you're not one of them. The altar — is it destroyed?",
"topics": {
"altar": "I found it. A shadow altar, deep in the cave. Something built it — something intelligent. I tried to destroy it but the guardian... I wasn't strong enough.",
"shadow": "It's not natural darkness. It thinks. It hunts. It trapped me here and... fed on me, I think. I'm weaker than I should be.",
"elara": "Elara sent you? She's alive? Tell her... tell her I'm sorry I didn't come back."
}
},
"quests": [],
"post-rescue": {
"new-room": 1001,
"new-long-desc": "Warden Maren stands beside Elara, her color slowly returning. A new longbow hangs across her back.",
"new-dialogue": {
"greeting": "Good to see you again. I owe you my life — and I don't forget debts.",
"topics": {
"forest": "It's healing. Slowly. The deep woods are still dangerous, but the shadow is retreating.",
"elara": "She gave me an earful when I got back. Then she cried. Then another earful. That's Elara."
}
}
}
}
],
"mob-templates": [
{
"id": 2001,
"name": "shadow wolf",
"short-desc": "a shadow wolf",
"long-desc": "A wolf made of living shadow paces here, its eyes like embers.",
"look-desc": "Larger than any natural wolf, this creature seems to absorb the light around it. Its fur ripples like dark water, and its growl resonates somewhere below the range of normal hearing.",
"level": 12,
"stats": {"str": 14, "int": 6, "wis": 6, "dex": 16, "con": 12, "luck": 8},
"max-hp": 180,
"damage": 10,
"hit-bonus": 5,
"xp-reward": 140,
"gold-min": 5,
"gold-max": 20,
"damage-type": "slash",
"flags": ["aggressive"],
"loot": [
{"item-id": 3001, "chance": 15},
{"item-id": 3002, "chance": 5}
]
},
{
"id": 2010,
"name": "shadow guardian",
"short-desc": "a massive shadow guardian",
"long-desc": "A towering figure of pure darkness stands before the altar, its form shifting and reforming like smoke in a windstorm.",
"look-desc": "This creature has no face, no features — just a vaguely humanoid shape made of concentrated shadow. Where its eyes should be, two points of violet light burn. It radiates cold.",
"level": 18,
"stats": {"str": 22, "int": 14, "wis": 10, "dex": 12, "con": 20, "luck": 10},
"max-hp": 750,
"damage": 18,
"hit-bonus": 10,
"xp-reward": 600,
"gold-min": 50,
"gold-max": 150,
"damage-type": "shadow",
"flags": ["sentinel", "assist"],
"loot": [
{"item-id": 3005, "chance": 100},
{"item-id": 3006, "chance": 25}
],
"boss": true,
"boss-mechanics": {
"phase-2-hp-pct": 50,
"phase-2-text": "The guardian shrieks — a sound like tearing metal. The shadows in the room surge toward it, and it grows larger.",
"phase-2-damage-bonus": 50,
"phase-2-abilities": ["shadow-burst"]
}
}
],
"item-templates": [
{
"id": 3001,
"name": "shadow fang",
"short-desc": "a curved shadow fang",
"long-desc": "A dark, curved fang lies here, still faintly warm.",
"look-desc": "Pulled from the jaw of a shadow wolf, this tooth radiates a faint chill. It might be worth something to the right buyer.",
"item-type": "other",
"binding": "none",
"level": 10,
"weight": 1,
"value": 25
},
{
"id": 3002,
"name": "shadowhide cloak",
"short-desc": "a cloak of woven shadow",
"long-desc": "A dark cloak lies pooled on the ground like spilled ink.",
"look-desc": "This cloak seems to drink in the surrounding light. It's eerily lightweight and warm despite being cool to the touch.",
"item-type": "armor",
"binding": "none",
"level": 12,
"weight": 3,
"value": 150,
"slot": "about",
"armor-stats": {"defense": 8, "resistances": {"shadow": 15}}
},
{
"id": 3005,
"name": "guardian's shadow core",
"short-desc": "a pulsing core of pure shadow",
"long-desc": "A sphere of darkness sits here, throbbing with a faint heartbeat.",
"look-desc": "The heart of the shadow guardian — a sphere of concentrated darkness the size of your fist. It pulses slowly, as if still alive. Violet light flickers within its depths.",
"item-type": "other",
"binding": "protected",
"level": 15,
"weight": 2,
"value": 500
},
{
"id": 3006,
"name": "penumbral greatsword",
"short-desc": "a greatsword wreathed in shadow",
"long-desc": "A massive dark blade leans against the wall, wisps of shadow trailing from its edge.",
"look-desc": "This greatsword was forged from the altar's stone. Shadow clings to its edge like flame clings to a torch. It's heavier than it looks.",
"item-type": "weapon",
"binding": "none",
"level": 16,
"weight": 8,
"value": 800,
"slot": "wield",
"weapon-stats": {"dice-num": 4, "dice-size": 8, "damage-type": "shadow"},
"effects": [{"stat": "str", "modifier": 2}]
},
{
"id": 3030,
"name": "warden trail marker",
"short-desc": "a carved warden mark",
"long-desc": "A small mark has been carved into a tree here — the sigil of the Forest Wardens.",
"look-desc": "A deliberate carving in the bark: three overlapping oak leaves, the warden's mark. It's weathered but recent — no more than a few weeks old. Someone carved it in a hurry.",
"item-type": "other",
"binding": "soulbound",
"level": 1,
"weight": 0,
"value": 0
}
],
"resets": [
{"type": "npc-load", "npc-id": 5001, "room": 1001, "spawn-type": "fixed"},
{"type": "npc-load", "npc-id": 5005, "room": 1001, "spawn-type": "fixed"},
{"type": "npc-load", "npc-id": 5010, "room": 1018, "spawn-type": "fixed"},
{"type": "mob-load", "mob-id": 2001, "room": 1003, "max": 2, "spawn-type": "fixed"},
{"type": "mob-load", "mob-id": 2001, "area-rooms": [1002, 1004, 1005, 1006], "max": 3, "spawn-type": "roaming"},
{"type": "mob-load", "mob-id": 2010, "room": 1012, "max": 1, "spawn-type": "fixed"},
{"type": "obj-load", "item-id": 3030, "room": 1009},
{"type": "obj-load", "item-id": 3030, "room": 1014},
{"type": "obj-load", "item-id": 3030, "room": 1016}
],
"encounters": [
{
"mob-id": 2001,
"rooms": [1003, 1004, 1005, 1006, 1007],
"chance": 15,
"min-level": 8,
"max-level": 25,
"cooldown": 3,
"description": "A shadow wolf leaps from the underbrush!"
},
{
"mob-id": 2002,
"rooms": [1008, 1009, 1010, 1011],
"chance": 20,
"min-level": 12,
"max-level": 30,
"cooldown": 2,
"description": "Tendrils of shadow coalesce into a writhing form before you!"
}
],
"quest-lines": [
{
"id": "darkwood-shadow-source",
"name": "The Shadow in the Darkwood",
"description": "Warden Elara suspects something is corrupting the Darkwood Forest. Investigate the source of the shadow wolves.",
"min-level": 10,
"giver-npc": 5001,
"giver-dialogue": {
"offer": "I need someone to find out what's driving these shadow wolves out of the deep forest. Head north, past the Overgrown Trail, and look for anything unnatural. Will you do this?",
"accept": "Good. Be careful past the Hollow. And if you see anything that looks like pure shadow moving on its own... run first, ask questions later.",
"decline": "I understand. It's not a small ask. Come back if you change your mind.",
"in-progress": "Any luck in the forest? The wolves are getting bolder every day.",
"complete": "A shadow altar? Gods. That explains everything. You've done more for this forest than the town council has in years. Take this — you've earned it."
},
"steps": [
{
"id": "investigate-trails",
"type": "explore",
"description": "Explore the trails in the outer Darkwood",
"target-rooms": [1002, 1003, 1004],
"journal": "Elara asked me to investigate the source of the shadow wolves. I should head north into the Darkwood trails.",
"on-complete": {
"text": "The claw marks on the trees grow deeper as you head north. Whatever is driving the wolves, it's coming from further in.",
"unlock-step": "investigate-hollow"
}
},
{
"id": "investigate-hollow",
"type": "explore",
"description": "Find the Hollow that Elara mentioned",
"target-rooms": [1008],
"journal": "The trails show signs of something large moving through. Elara mentioned a place called the Hollow — north past the ravine.",
"on-complete": {
"text": "The Hollow is unnaturally dark. The shadow here isn't just absence of light — it moves. Tendrils of darkness seep from a narrow cave entrance to the east.",
"unlock-step": "enter-shadow-cave"
}
},
{
"id": "enter-shadow-cave",
"type": "explore",
"description": "Enter the shadow cave",
"target-rooms": [1012],
"journal": "Something is generating the shadow corruption from a cave east of the Hollow.",
"on-complete": {
"text": "Inside the cave, a crude altar of black stone pulses with dark energy. This is the source. Shadowy runes cover its surface — and something is guarding it.",
"unlock-step": "destroy-altar"
}
},
{
"id": "destroy-altar",
"type": "kill",
"description": "Defeat the Shadow Guardian and destroy the altar",
"target-mob": 2010,
"target-room": 1012,
"journal": "A shadow guardian protects the altar. I need to defeat it to destroy the source of the corruption.",
"on-complete": {
"text": "The guardian dissolves into wisps of shadow. Without its protector, the altar cracks and crumbles. The oppressive darkness in the cave begins to lift. You should report back to Warden Elara.",
"set-flags": ["destroyed-shadow-altar"],
"return-to-giver": true
}
}
],
"rewards": {
"xp": 2000,
"gold": 500,
"quest-points": 10,
"items": [
{
"template-id": 3005,
"overrides": {
"name": "Elara's Gratitude",
"short-desc": "a silver warden's badge",
"binding": "protected",
"look-desc": "A silver badge stamped with a forest oak — Warden Elara's personal badge, given to you in recognition of your service to the Darkwood."
}
}
]
},
"followup-quests": ["darkwood-find-maren"],
"world-effects": {
"set-flags": ["darkwood-shadow-cleared"],
"disable-resets": [
{"type": "mob-load", "mob-id": 2001, "area-rooms": [1002, 1004, 1005, 1006]}
],
"disable-encounters": [
{"mob-id": 2001, "rooms": [1003, 1004, 1005, 1006, 1007]}
],
"change-descriptions": [
{
"room": 1002,
"new-description": "The thick roots still buckle the path, but the strange markings on the trees have faded. Birdsong has returned to this part of the forest — tentative, but real."
}
],
"npc-dialogue-update": {
"npc": 5001,
"add-topics": {
"altar": "You destroyed it. I can feel the forest breathing again. But Maren is still out there..."
}
}
}
},
{
"id": "darkwood-find-maren",
"name": "The Lost Warden",
"description": "Warden Elara's partner Maren disappeared while investigating the Darkwood. Find out what happened to her.",
"min-level": 14,
"requires-flags": ["destroyed-shadow-altar"],
"giver-npc": 5001,
"giver-dialogue": {
"offer": "You destroyed the altar, but Maren is still out there. With the shadow weakened, maybe now... will you look for her?",
"accept": "She was heading deeper than the Hollow. Past the cave, into the old growth. Look for warden trail markers — she always left them.",
"decline": "I understand. But please... if you change your mind.",
"in-progress": "Have you found any sign of her? The trail markers, anything?",
"complete": "She's... alive? Where? I... thank you. I thought I'd lost her forever."
},
"steps": [
{
"id": "find-trail-markers",
"type": "fetch",
"description": "Search for Maren's trail markers beyond the shadow cave",
"target-items": [3030],
"count": 3,
"journal": "Elara said Maren always left trail markers. I should search the rooms beyond the shadow cave for carved warden marks.",
"on-complete": {
"text": "The third marker is scratched hastily into a tree, with an arrow pointing east and the word 'TRAPPED' gouged beneath it.",
"unlock-step": "rescue-maren"
}
},
{
"id": "rescue-maren",
"type": "interact",
"description": "Find and free Maren",
"target-room": 1018,
"target-npc": 5010,
"interaction": "talk",
"journal": "Maren's last marker says 'TRAPPED.' She must be somewhere to the east of the deep forest.",
"on-complete": {
"text": "Maren is alive but weak, trapped in a web of solidified shadow. She gasps when she sees you. 'The altar... is it destroyed?' When you nod, relief floods her face. 'Then let's go home.'",
"set-flags": ["found-maren"],
"return-to-giver": true
}
}
],
"rewards": {
"xp": 3500,
"gold": 800,
"quest-points": 15,
"items": [
{
"template-id": 3006,
"overrides": {
"name": "Maren's Longbow",
"short-desc": "a finely crafted warden's longbow",
"binding": "protected",
"look-desc": "A beautifully crafted longbow, left behind by Warden Maren during her captivity. She insisted you take it."
}
}
]
},
"world-effects": {
"set-flags": ["maren-rescued"],
"relocate-npc": {
"npc": 5010,
"to-room": 1001,
"apply-post-rescue": true
},
"npc-dialogue-update": {
"npc": 5001,
"add-topics": {
"maren": "She's right here, thanks to you. Still recovering, but she's tough. Tougher than me, honestly."
}
}
}
}
],
"boards": [
{
"room": 1001,
"name": "A weathered signpost",
"max-posts": 20,
"initial-posts": [
{
"author": "Forest Warden",
"subject": "DANGER — Shadow Wolves",
"body": "Travelers be warned: shadow wolves have been sighted in the outer forest. Do not travel alone. Report unusual shadow activity to Warden Elara at the forest entrance.",
"pinned": true
},
{
"author": "Aylor Town Council",
"subject": "Bounty: Shadow Wolf Pelts",
"body": "The council is offering 10 gold per shadow wolf pelt delivered to the town hall. Proof of kill required.",
"pinned": false
}
]
}
],
"shops": [
{
"keeper-npc": 5005,
"room": 1001,
"inventory": [3010, 3011, 3012],
"buy-markup": 150,
"sell-markdown": 50
}
]
}
Quest Step Types
| Type | Mechanic | Completion Trigger | Example |
|---|---|---|---|
explore | Visit specific room(s) | Player enters all target rooms | “Find the Hollow” |
kill | Defeat specific mob | Target mob dies while quest active | “Defeat the Shadow Guardian” |
fetch | Collect item(s) | Player has N of target item in inventory | “Find 3 trail markers” |
interact | Talk to NPC or use object | Player talks to NPC or interacts with object in target room | “Free Maren” |
deliver | Bring item to NPC | Player gives target item to target NPC | “Bring the amulet to Elara” |
escort | Keep NPC alive through rooms | NPC reaches destination room alive | “Guide Maren to safety” |
Quest Flow
1. Player talks to NPC with "quest-giver" flag
2. NPC delivers "offer" dialogue
3. Player accepts (or declines)
4. First step activates, journal updates
5. Player completes step → "on-complete" text fires
6. Next step unlocks via "unlock-step"
7. Repeat until final step
8. If final step has "return-to-giver": player must return to NPC
9. NPC delivers "complete" dialogue
10. Rewards granted (XP, gold, QP, items)
11. World effects applied (flags, mob changes, description changes, NPC relocations)
12. Followup quests become available
World Effects
Quests can permanently change the world for the player who completed them:
| Effect | What It Does |
|---|---|
set-flags | Set flags on player’s world-record (persists across sessions) |
disable-resets | Stop specific mob spawns (shadow wolves gone after altar destroyed) |
disable-encounters | Stop random encounters in specific rooms |
change-descriptions | Room descriptions change to reflect quest completion |
relocate-npc | Move an NPC to a new room (Maren returns to entrance) |
npc-dialogue-update | Add/change dialogue topics on an NPC |
unlock-area | Open a previously locked exit or area |
spawn-npc | Make a new NPC appear |
Per-player vs global: World effects are per-player by default. If you destroy the altar, shadow wolves stop spawning for you in the outer forest. Other players who haven’t done the quest still see them. The room description changes are sent to you on room entry based on your flags.
This means the same room can look different to different players based on their quest progress. The server checks the player’s flags and sends the appropriate description variant.
NPC Data Structure
Hoon Types
+$ npc
$: id=@ud
name=@t
short-desc=@t
long-desc=@t
look-desc=@t
room=room-id
spawn-type=?(%fixed %roaming)
flags=(set npc-flag)
services=(list npc-service)
combat-stats=(unit mob-combat-data) :: optional — NPC can fight if attacked
=dialogue-config
quests=(list @tas) :: quest IDs this NPC offers
post-state=(map @tas npc-state-change) :: flag → how NPC changes after quest
==
::
+$ npc-flag
$? %stationary %quest-giver %dialogue %shopkeeper
%trainer %healer %banker %quest-target
==
::
+$ npc-service
$? %shop %train %heal %bank %identify %repair ==
::
+$ mob-combat-data
$: level=@ud
=stats
max-hp=@ud
damage=@ud
hit-bonus=@ud
xp-reward=@ud
gold-min=@ud
gold-max=@ud
=damage-type
==
::
+$ dialogue-config
$% [%static =static-dialogue]
[%llm =llm-dialogue]
==
::
+$ static-dialogue
$: greeting=@t
topics=(map @t @t) :: keyword → response
keywords=(map @t @t) :: hidden keywords, revealed by gameplay
==
::
+$ llm-dialogue
$: system-context=@t :: character prompt for the LLM
world-context-keys=(list @t) :: what world state to inject
fallback-greeting=@t :: if LLM unavailable
rate-limit=@ud :: max interactions/minute/player
==
::
+$ npc-state-change
$: new-room=(unit room-id)
new-long-desc=(unit @t)
new-dialogue=(unit static-dialogue)
==
Encounter Type
+$ encounter-def
$: mob-id=@ud
rooms=(list room-id) :: rooms where this can trigger
chance=@ud :: percentage per room entry
min-level=@ud :: player must be at least this level
max-level=@ud :: player must be at most this level
cooldown=@ud :: room moves before can trigger again
description=@t :: "A shadow wolf leaps from the underbrush!"
==
Quest Types
+$ quest-line
$: id=@tas
name=@t
description=@t
min-level=@ud
requires-flags=(set @tas) :: flags player must have to start
giver-npc=@ud :: NPC who offers this quest
giver-dialogue=quest-dialogue
steps=(list quest-step)
rewards=quest-rewards
followup-quests=(list @tas)
world-effects=quest-world-effects
==
::
+$ quest-dialogue
$: offer=@t
accept=@t
decline=@t
in-progress=@t
complete=@t
==
::
+$ quest-step
$: id=@tas
=step-type
description=@t
journal=@t :: journal entry for player
on-complete=step-completion
==
::
+$ step-type
$% [%explore target-rooms=(list room-id)]
[%kill target-mob=@ud target-room=room-id]
[%fetch target-item=@ud count=@ud]
[%interact target-room=room-id target-npc=@ud interaction=@t]
[%deliver target-item=@ud target-npc=@ud]
[%escort target-npc=@ud destination=room-id]
==
::
+$ step-completion
$: text=@t
unlock-step=(unit @tas) :: next step to activate
set-flags=(set @tas)
return-to-giver=?
==
::
+$ quest-rewards
$: xp=@ud
gold=@ud
quest-points=@ud
items=(list quest-reward-item)
==
::
+$ quest-reward-item
$: template-id=item-id
overrides=(map @tas @t) :: make it special
==
::
+$ quest-world-effects
$: set-flags=(set @tas)
disable-resets=(list reset-cmd)
disable-encounters=(list encounter-def)
change-descriptions=(map room-id @t) :: room → new description
relocate-npcs=(map @ud room-id) :: npc-id → new room
npc-dialogue-updates=(map @ud (map @t @t)) :: npc-id → new topics
==
LLM NPC Integration
Architecture (Future — Hook Designed Now)
Some NPCs have dialogue.mode = "llm" instead of "static". When a player interacts with these NPCs, the dialogue handler routes to an external LLM instead of keyword matching.
Player says "tell me about the darkwood" to Old Gareth
│
├── dialogue.mode == "static"?
│ Yes → lookup "darkwood" in topics map → return response
│
├── dialogue.mode == "llm"?
│ Yes → spawn thread:
│ 1. Build prompt:
│ - system-context (NPC personality)
│ - world-context (recent events, area lore, player info)
│ - conversation history (last 5 exchanges with this NPC)
│ - player message
│ 2. Call LLM API
│ 3. Return response as [%chat "say" "Old Gareth" response]
│ 4. If API fails: return fallback-greeting
Cost Control
| Control | Mechanism |
|---|---|
| Few LLM NPCs | 3-5 per world max. Most NPCs stay static. |
| Rate limit | Per player per NPC (default 10/minute). Prevents spam. |
| Cheap model | Use Haiku for tavern banter. Reserve Opus for lore-critical NPCs. |
| Context capping | Max 5 exchanges of history. No unbounded context growth. |
| Guest restriction | Guests get 3 LLM interactions per session. Citizens unlimited (within rate limit). |
| Operator toggle | enable-llm-npcs flag in world-config. Off by default. Operator’s API bill. |
| Caching | Cache common question→response pairs. If 10 players ask “what’s happening” the same hour, reuse the first response. |
| Fallback | If API is down or quota exceeded, NPC falls back to fallback-greeting. Never breaks the game. |
World Context Injection
The world-context-keys field tells the LLM handler what game state to include in the prompt:
"world-context-keys": ["recent-events", "area-lore-darkwood", "player-reputation"]
Resolves to context injected into system prompt:
RECENT EVENTS:
- The shadow altar in Darkwood was destroyed 2 days ago by Wanderer
- 3 new players arrived today
- The Shadowfen Dragon has not been defeated yet
AREA LORE (DARKWOOD):
- Ancient forest, recently corrupted by shadow magic
- Wardens Elara and Maren guard the entrance
- The Hollow is the deepest and most dangerous area
PLAYER CONTEXT:
- Wanderer is a level 15 warrior
- They destroyed the shadow altar (flag: destroyed-shadow-altar)
- They've visited this NPC 3 times before
This makes LLM NPCs contextually aware without the model needing to know everything about the game engine. The world state is summarized and injected.
AI Content Generation Pipeline
The Workflow
1. BRIEF (human, 5-10 minutes)
"Dark enchanted forest, levels 10-20, 20 rooms. Theme: shadow magic
corruption. Two wardens at entrance — one gives quests, one is the
tavern keeper. Missing warden quest chain. Boss: shadow guardian.
3 mob types escalating. Hidden trail markers. Bulletin board."
2. GENERATE (AI, ~30 seconds)
Feed brief + schema + style guide + balance formulas to Claude.
Model generates complete area JSON: rooms, NPCs, mobs, items,
quests, encounters, boards, shops.
3. VALIDATE (automated, instant)
Run output through validator script:
✓ All room IDs unique, exits reference valid rooms
✓ No orphan rooms (all reachable)
✓ Mob stats balanced for level range
✓ Items have valid types/slots/bindings
✓ Quest steps reference existing rooms/NPCs/mobs
✓ NPC dialogue has all required fields
✓ Coordinates don't collide
✓ Descriptions meet minimum length
✓ No placeholder text detected
4. REVIEW (human, 15-30 minutes)
Read through in text editor or web preview:
- Descriptions evocative and consistent?
- Layout makes spatial sense?
- Difficulty curve appropriate?
- Quest narrative compelling?
- Lore consistent with rest of world?
- NPC personality comes through?
5. LOAD (admin poke, instant)
[%area-import json-blob] → agent parses, creates all entities
Area is live. Walk in and play.
6. ITERATE (ongoing)
Play-test, get feedback.
Edit JSON and reimport, or use OLC for small tweaks.
AI Prompt Template
You are a MUD area builder. Generate a complete area file in JSON format
following the schema below.
AREA BRIEF:
{user's brief}
BALANCE TARGETS:
- Mob HP: level * 15 + CON * 4 (±20%)
- Mob damage: level / 2 + STR / 5
- Mob XP: level * 10 + level² / 10
- Boss mobs: 3x HP, 1.5x damage, 100% guaranteed unique drop
- Armor defense: ~level / 2
- Weapon dice: scale with level (1d6 at L1, 4d8 at L20)
- Gold drops: level * 1-3 (min), level * 3-8 (max)
WRITING STYLE:
- Third person present tense, no "you"
- 3-5 sentences per room description
- Sensory details: sight, sound, smell, touch
- No ASCII art, no purple prose
- NPC dialogue: natural speech, personality through word choice
- Boss descriptions: vivid, foreboding, memorable
ROOM LAYOUT:
- Not a straight corridor — create interesting topology
- At least one branching path or loop
- Waypoint at entrance
- Boss room at the end, behind a "point of no return" feel
- 1-2 hidden or non-obvious exits for exploration rewards
QUEST DESIGN:
- Steps should flow narratively, not feel like a checklist
- Journal entries in first person ("I should head north...")
- On-complete text should advance the story, not just say "done"
- World effects should make the area feel different after quest completion
- Reward items should have thematic descriptions, not just stats
JSON SCHEMA:
{our complete area file format}
Cost Estimates
| Area Size | Rooms | Tokens Out | Cost (Haiku) | Cost (Sonnet) |
|---|---|---|---|---|
| Small | 10 | ~3K | ~$0.04 | ~$0.10 |
| Medium | 20 | ~6K | ~$0.08 | ~$0.20 |
| Large | 40 | ~12K | ~$0.15 | ~$0.40 |
A 300-room flagship world at 15 areas: $1-6 total in generation costs.
Human-AI Split Strategy
| Content | Who Generates | Why |
|---|---|---|
| Room descriptions (corridor rooms) | AI | Formulaic, high volume |
| Room descriptions (key locations) | Human or AI+edit | Narrative weight matters |
| Mob stat blocks | AI | Purely mechanical, formulaic |
| Mob descriptions | AI | Good enough for generic mobs |
| Boss descriptions | Human or AI+edit | Memorable moments need polish |
| NPC dialogue trees | AI draft, human edit | Personality needs human touch |
| Quest narratives | Human brief → AI expansion | Story structure needs human intent |
| Item descriptions | AI | Thematic but formulaic |
| Item stats | AI | Mechanical, balance-formula-driven |
| Map layouts | AI generates, human reviews topology | AI is decent at graph generation |
| Board initial posts | AI | Flavor content, low stakes |
Agent State Additions
:: Add to state-0:
npcs=(map @ud npc) :: NPC definitions
npc-instances=(map instance-id npc) :: spawned NPC instances
quest-defs=(map @tas quest-line) :: quest line definitions
player-quests=(map session-id (map @tas quest-progress)) :: active quest tracking
encounter-defs=(list encounter-def) :: random encounter definitions
encounter-cooldowns=(map session-id (map @ud @ud)) :: player → (mob-id → moves since last encounter of this type)
::
+$ quest-progress
$: quest-id=@tas
current-step=@tas
completed-steps=(set @tas)
started=@da
==
Admin Commands for Content
:: Add to %mud-admin:
[%area-import json=@t] :: load complete area from JSON
[%area-export area=area-id] :: dump area to JSON (for editing)
[%npc-create =npc]
[%npc-edit npc=@ud field=@tas value=@t]
[%quest-create =quest-line]
[%quest-edit quest=@tas field=@tas value=@t]
[%encounter-add =encounter-def]
[%encounter-remove mob-id=@ud rooms=(list room-id)]
The %area-import command is the workhorse — one poke loads an entire area from JSON. This is what the AI pipeline produces and what builders submit for review. %area-export dumps an area back to JSON for editing, version control, or sharing.
Implementation Additions (2026-03-29)
Room Fields Added
Rooms now support these additional optional fields beyond the original schema:
{
"id": 1001,
"name": "Forest Entrance",
"description": "...",
"sector": "forest",
"coord": {"x": 0, "y": 0, "z": 0},
"exits": {"north": 1002},
"flags": ["waypoint"],
"image": "forest-entrance.jpg",
"audio": "wind-trees",
"hooks": [
{
"trigger": "on-enter",
"conditions": [{"type": "flag", "flag": "visited-forest", "op": "not-set"}],
"actions": [
{"type": "message", "text": "The forest canopy closes overhead..."},
{"type": "set-flag", "flag": "visited-forest"}
]
}
]
}
- image
(string, optional): Image URL or path for the room. Empty string or omitted = none. - audio
(string, optional): Audio cue identifier. Empty string or omitted = none. - hooks
(array, optional): Scripted room triggers. See DESIGN-hooks.md for full documentation. Empty array or omitted = no hooks.
Content Created
Two complete area files exist in content/:
- tutorial-academy.json — “The Ruined Cathedral” tutorial world. 18 rooms, 4 mob types, 15 items, 5 NPCs, example hooks on rooms 6, 16, and 101. Connected south (room 9) to Shattered Covenant.
- shattered-covenant.json — Full world: 215 rooms across 9 areas, 41 mobs, 79 items, 25 NPCs, 9 quest lines, 8 shops. Level 1-30 progression. Main quest: stop the Covenant of Ash from awakening the Unborn God.
NPC Dialogue Audio Fields
Static dialogue NPCs can have optional audio URLs for their greeting and topic responses:
{
"dialogue": {
"mode": "static",
"greeting": "Welcome, traveler.",
"greeting-audio": "https://cdn.example.com/npcs/kael-greeting.mp3",
"topics": {
"work": "Head to the forest.",
"sword": "A fine blade you carry."
},
"topic-audio": {
"work": "https://cdn.example.com/npcs/kael-work.mp3",
"sword": "https://cdn.example.com/npcs/kael-sword.mp3"
},
"keywords": {}
}
}
Audio fields are optional. If present, the client plays the audio when the player talks to the NPC or asks about a topic (requires audio toggle enabled in Settings).
Item Template Transforms
Item templates can declare environmental transformations via a transforms map:
{
"id": 42,
"name": "Crackers",
"short-desc": "a packet of dry crackers",
"long-desc": "A packet of crackers lies here.",
"item-type": "other",
"binding": "none",
"level": 1,
"weight": 1,
"value": 2,
"transforms": {"wet": 43}
}
When the player has the “wet” effect and a transform-items hook fires (e.g. walking through a waterfall), crackers (template 42) become soggy crackers (template 43). The transform key matches the effect name from add-effect hook actions. Items without a matching transform entry are unaffected.
TTS Generation Pipeline
A script at scripts/generate-tts.py generates audio files for room descriptions and NPC dialogue:
- Uses the OpenAI TTS API to generate MP3 files
- Processes room descriptions → narration audio (stored at CDN, URL written to room’s
narrationfield) - Processes NPC greetings and topic responses → dialogue audio (URLs written to
greeting-audioandtopic-audiofields) - Auto-updates the JSON area files with CDN URLs after upload
- Flags:
--dry-run(preview without generating),--rooms-only(skip NPC dialogue),--npcs-only(skip room narration) - Reads area JSON files from
content/, outputs MP3s to a staging directory, uploads to CDN, patches JSON in place