Skip to main content

Markdown Syntax

The converter reads a Markdown file with a custom structure. See the example story for a complete working file.

Frontmatterโ€‹

The file starts with a YAML-like frontmatter block:

---
title: "L'Antica Cripta"
lang: it
credits:
developer: "Your Name"
engine: "Librogame Engine"
story: "L'Antica Cripta"
year: "2025"
thanks: "Thank you for playing!"
---
FieldDescription
titleStory title.
langOutput language key. Defaults to it.
creditsNested credits block.

Sectionsโ€‹

The body is split by H1 headings. The converter recognizes:

  • # Perks
  • # Items
  • # Nodes

Perksโ€‹

# Perks

## strong_back | Strong Back
You can carry 3 extra items.

## night_vision | Night Vision
You see better in the dark.

The header format is:

## perk_id | Perk Name

The paragraph after the header becomes the perk description.

Itemsโ€‹

# Items

## potion | Healing Potion | ๐Ÿงช
> A ruby-red potion that restores health.
- image: src/images/inventories/potion.png
- usable: true
- effects: health +2
- useMessage: "You feel better!"

The item header format is:

## item_id | Item Name | Icon

Supported item properties:

PropertyExampleDescription
image- image: path.pngItem image path.
usable- usable: trueWhether the player can use the item.
effects- effects: health +2Stat changes. Separate multiple effects with commas.
useMessage- useMessage: "..."Message shown after use.

A > blockquote becomes the item description.

Nodesโ€‹

# Nodes

## start
You stand before the entrance of an ancient crypt.

- Enter carefully -> entrance {flags: entered_crypt}
- Force the doors open -> force_door {test: strength 7}
- Examine the entrance -> examine {test: intelligence 7}

A node starts with:

## node_id

Node metadataโ€‹

Use HTML comments directly after the node heading:

## entrance
<!-- music: src/music/crypt_ambient.mp3 loop -->
<!-- image: src/images/entrance.jpg -->
The entrance is dark and cold.

- Go left -> corridor
CommentDescription
<!-- music: path -->Background music.
<!-- music: path loop -->Background music with looping enabled.
<!-- image: path -->Story image.

Node textโ€‹

Non-empty lines become the node text. Multiple lines are joined with spaces.

Choicesโ€‹

A choice line uses this format:

- Choice text -> target_node {annotations}

Choice annotationsโ€‹

Annotations can be combined on the same choice.

AnnotationExampleDescription
Flags{flags: flag1, flag2}Sets story flags.
Health{health: -1}Changes health.
Sanity{sanity: -1}Changes mental health.
Items{items: id/Name/type, ...}Gives items. Type defaults to permanent when omitted.
Consume{consume: id/Name}Removes an item when the choice is selected.
Test{test: strength 9}Stat test. The converter expects a <target>_fail node.
Require item{requires: item_id}The player must carry the item.
Require flag absence{requires: !flag_name}The flag must not be set.
One-shot{oneshot}The choice disappears after being selected.

Items annotationโ€‹

- Take everything -> victory {items: potion/Healing Potion/consumable, scroll/Ancient Scroll/permanent}

The format is id/Name/type. If the type is omitted, permanent is used.

Test annotationโ€‹

- Cross the bridge -> bridge {test: dexterity 9}

The choice targets bridge on success and bridge_fail on failure. Create the failure node explicitly:

## bridge_fail
The bridge collapses. You fall into the river.

Validation warningsโ€‹

The converter reports:

  • Choices whose target node does not exist
  • Tests whose failure node is missing
  • Required or rewarded items missing from the item catalog

Warnings are written to stderr and do not prevent the JSON from being generated.

Outputโ€‹

The generated JSON contains one language key:

{
"it": {
"title": "...",
"perks": [],
"nodes": {},
"items": {},
"ui": {},
"credits": {}
}
}

The converter fills in the default Italian UI strings and uses the frontmatter title for the story and credits.