DNDNote Docs
Reference

Adventure Format — Markdown & JSON

The deterministic, no-AI authoring spec for DND.chat adventures — Markdown and JSON rules, scene limits, and copy-paste examples.

When you want the Autopilot DM to run your own adventure — not a published module, not an AI-generated one-shot — you hand DND.chat a structured file. A .md/.markdown or .json adventure is parsed deterministically: no language model, no PDF reader, no page range, and zero cost. Because you supply the content yourself, this is the legally-clean alternative to running a print module through the PDF Shredder.

This page is the canonical authoring spec. It's the same guide DND.chat shows you in-app via the Structured format guide help modal when you stage a file in the Autopilot DM panel.

Why use it

The structured importer turns your text into the same scene graph every other ingest source produces, then saves it. Specifically:

  • No AI, no cost. Parsing is purely mechanical — there is no LLM pass, no pdf.js, and no per-page processing. Import is instant.
  • Your content stays yours. You're not extracting someone else's PDF; you wrote it.
  • It runs like any other adventure. Scenes save to your campaign's chat_spaces.metadata.autopilot with source: "structured" and are embedded for retrieval, so /recall and the Autopilot DM's per-turn memory work exactly as they do for an imported module.

How import works

Two parsers sit behind one importer. The right one is chosen by your file:

  • By extension. .md / .markdown → Markdown parser; .json → JSON parser.
  • Ambiguous extensions are sniffed: if the trimmed text starts with { or [, JSON is tried first; otherwise it's parsed as Markdown.

To import, open the Autopilot DM panel in your campaign tavern, click Choose PDF / Markdown / JSON, pick your file, and click Import. The panel detects the structured format and hides the PDF-only page-range and suggested-start controls — those don't apply. Parsing happens immediately with no AI cost, and the scenes save straight to your campaign.

Importing is DM-only. You can also paste JSON directly in the Campaign Builder.

Markdown rules

Markdown is the friendliest way to author. The parser walks your document top to bottom and builds scenes from a small, predictable set of rules:

  • A level-1 or level-2 ATX heading (# or ## ) opens a new scene, titled by the heading text. Deeper headings (### and below) are treated as ordinary body text, not new scenes.
  • A blockquote run (> …) appearing directly under the heading, before any prose becomes that scene's boxed read-aloud — the "read this to the players" text.
  • A blockquote that appears after prose folds into the body instead of the boxed read-aloud.
  • Ordinary paragraphs append to the scene body.
  • The first image ![alt](url) under a scene records that scene's mapUrl. Image-only lines contribute nothing else to the body.
  • Content before the first heading seeds an implicit scene titled Opening, so nothing you write is dropped.

A copy-paste Markdown example

# The Sunken Crypt

> Cold water laps at your ankles as you descend the last step. The
> air smells of brine and old rot. Somewhere ahead, something drags
> itself across stone.

The stair opens into a flooded antechamber. Three sarcophagi line the
far wall, their lids slid half-open. A faint green glow leaks from the
middle one.

![Map of the flooded antechamber](https://example.com/crypt-map.png)

## The Drowned Guardian

> The water erupts. A bloated, barnacle-crusted figure rises, seaweed
> trailing from its rusted armor.

The guardian attacks anyone who disturbs the sarcophagi. It fights
until destroyed, then crumbles to silt.

Here the lead blockquote under each heading becomes the boxed read-aloud, the following paragraphs become the scene body, and the image is captured as the first scene's mapUrl.

JSON rules

JSON gives you exact control over every field. The shape is validated with zod against the adventure schema, so a malformed file is rejected rather than half-imported.

{
  "title": "The Sunken Crypt",
  "scenes": [
    {
      "title": "The Sunken Crypt",
      "readAloud": "Cold water laps at your ankles as you descend the last step. The air smells of brine and old rot.",
      "npcs": ["Drowned acolyte"],
      "exits": ["Collapsed tunnel north", "Flooded stair back up"],
      "mapUrl": "https://example.com/crypt-map.png"
    },
    {
      "title": "The Drowned Guardian",
      "readAloud": "The water erupts. A bloated, barnacle-crusted figure rises, seaweed trailing from its rusted armor."
    }
  ]
}
  • The top-level title is optional; scenes is required and must contain at least one scene.
  • Each scene's fields — title, readAloud, npcs[], exits[], mapUrl — are all optional.
  • A ```json code fence is stripped before parsing, so you can paste a fenced block straight from a chat or doc and it still imports.

Limits

Both parsers share the same caps. Stay inside them and your file imports cleanly:

LimitCap
Scenes per adventureup to 200
Read-aloud per scene≤ 10,000 characters (MAX_READ_ALOUD)
NPCs per scene≤ 12
Exits per scene≤ 8
Minimum scenes≥ 1

One more thing worth knowing: scenes whose read-aloud is shorter than roughly 80 characters (MIN_SCENE_BODY) can be dropped during import. If a scene matters, give it a sentence or two of substance.

A note on maps

mapUrl is carry-through only. The importer captures it — from a Markdown ![](url) image or a JSON mapUrl field — and persists it with the scene, but scene maps are not rendered yet. Maps are also never extracted from PDFs; mapUrl only ever comes from a structured file you author. Treat it as forward-looking: setting it today does no harm and reserves the value for when map display lands.