⚠ HexCrawl is currently in Pre-Alpha. Expect rough edges and frequent changes. Follow the Dev Blog for updates.

How Adventures Work

I touched on adventures in The Inference Ouroboros and I wanted to go into a little more detail - both where the idea came from and how it works.

The idea

The concept of “adventures” was meant to be a late optional feature to add a little interesting user-generated content to the game. The idea was simply: “what if, instead of letting the LLM invent everything, users could write skeletal outlines of plots that the LLM then ingests to flesh out?” I thought it might be fun if the data format was small and trivial enough to allow users to create and share them with each other.

Then on a whim I bumped them up from where they were at the bottom of the roadmap. I was dealing with the problem that the quest system seemed to generate all beginnings and no endings. Every quest was another samey breadcrumb: find a clue that leads to a clue that maybe eventually might actually resolve a plot line but probably not. I thought, hmm, maybe adventures would give it more of a guide on how to find a complete story arc.

The data

I started with a very simple data format that plugged into existing systems: NPCs (especially motivations and memories), items, and locations. I used the plot line of an actual D&D adventure I wrote a long time ago: three ancient tablets from a lost civilization must be collected to reveal the location of a great treasure. I included vague outlines of the major elements - for example, this item:

{
  "id": "tablet-emoria-bottom",
  "name": "Tablet of Emoria — The Bottom Shard",
  "description": "The smallest of the three fragments, bearing the vault's cipher key along its bottom edge.",
  "icon": "Letter_Open.png",
  "location": {
	"type": "hex",
	"id": "emorian-ruins"
  }
}

And then this location, which is cross-referenced by tag in the location element of the item above:

{
  "id": "emorian-ruins",
  "name": "The Emorian Ruins",
  "description": "The crumbling remnants of the Emorian capital, half-swallowed by forest. Ancient towers lean at angles and passages honeycomb the underground levels.",
  "placement": {
	"preferredHexFilters": [
	  [{ "terrain": "forest" }, { "feature": "ruin" }],
	  [{ "feature": "ruin" }]
	]
  }
}

NPCs are written even more vaguely, specifying archetype, motivations, and relationships with other tagged NPCs, but that’s it:

{
  "id": "quest-giver",
  "archetype": "sage",
  "motivations": [
	"Find help locating and retrieving each of the three Emorian Tablet shards.",
	"Keep the vault's dangerous relics out of enemy hands."
  ],
  "reputation": "friendly",
  "relationships": []
}

And finally the loosest data type, which I call “NPC Injections” - extra motivations or memories to inject into otherwise dynamically generated NPCs. For example:

{
  "memory": "I have discovered the exact location of {{location:emorian-ruins}}",
  "motivation": "Discover lost secrets of the ancient Emorians by searching the ruins of their capital."
}

How it works

The above data is ingested during the procedural level generation. In some cases it insists new things be created, such as the item and location, and in others it dovetails into existing systems. For example, the NPC above might be created wholesale, but also if an NPC with archetype sage and reputation friendly already exists in the world, it may just merge that data into the existing NPC and apply the tag. This last bit is really nice for tying plot lines together. If the player invented an evil noble in their past history during character creation and the plot of the adventure calls for a hostile noble, they’ll get tied right into the plot.

I’ve seen this very case happen in the Tablets of Emoria adventure - one of the tablets is in the possession of a greedy noble who turned out to be someone from my past in a couple of my runs. This absolutely twisted how I approached the scenario - instead of treating with him peacefully you can bet I wanted to figure out how to steal from him or attack him.

Encounter with Lord Vethran

Why it works

I realized that the way I was generating the data was following the very instructions I give the LLM: narrative specificity has to flow downstream from mechanics. When an LLM generates a quest it begins by creating an outline without naming any specifics, and that’s exactly what’s happening here. I was following the same rules as the model.

That was a really nice bit of kismet, and ultimately I think why the system works so easily with the existing code. Better yet, because it’s intentionally designed, the hooks for the LLM to invent from later are obvious. I never wrote “This NPC gives you a quest to go find the shard hidden in the Emorian ruins”. But I did write “this NPC wants to collect the shards”, “this shard is hidden in the ruins”, and “this other NPC knows where the ruins are”. That’s a nice little breadcrumb trail for the LLM to follow to stitch content together. And the end result solves the exact problem I was facing: suddenly the quests are following an arc that actually leads somewhere.

What’s left

The only thing that remains is closing the loop on the final piece of data I put into adventures, the win condition:

{
  "text": "Recover all three Tablets of Emoria.",
  "success": {
	"type": "dialog",
	"npc_id": "quest-giver",
	"required_inventory": ["tablet-emoria-top", "tablet-emoria-middle", "tablet-emoria-bottom"]
  }
}

This data exists but the code doesn’t do anything with it yet. Once it does, it should help me answer the big design question I’ve had lingering for a while: how does the game end? Right now there’s only a day counter, and that’s really not very satisfying. Having an actual win condition is going to help immensely.

I’m also looking forward to writing more of these, and I’m still very open to the idea of making them easy for users to create and share with each other. I think that will add a lot of interest and longevity to the game. Also I think it’ll be fun to enable users to share stories of how their run of a specific adventure went, and where it collided with their character’s backstory.