Skip to content

Yarn Spinner

Export → Yarn Spinner writes one .yarn file for the whole project in Yarn Spinner 2/3 syntax. Drop it into any Yarn Spinner host — Unity, Godot, Unreal, a web runner — and start at the Setup node.

example-scenario.yarn is the example project after export; it is reproduced below.

ChatMapperYarn
ProjectOne .yarn file. A header comment maps every Yarn title back to its conversation and node.
Project script + variablesA generated Setup node: <<declare>> for every variable (from Initial Value), the translated project script, then a jump to the first conversation’s root. This is the entry point.
Playable node (root included)One Yarn node, titled <Conversation>_<NodeTitle> (sanitised to letters, digits and _; suffixed _2, _3 on collision).
Group nodeNo Yarn node. A group is expanded at every site that links to it, and its condition and script are folded into each child it contributes.
Dialogue TextSpeaker: line — one Yarn line per | segment. [var=Name] becomes {$Name}.
One successor<<jump Title>>
Several successors-> shortcut options, one per successor, each with its <<jump>>. Labelled by Menu Text ([f]/[a] stripped).
A lone [f] successorA one-entry option menu, as the Desktop app would show.
Cross-conversation linkA plain <<jump>> — the file is one namespace.
ConditionsString<<if …>> around the jump/option, when translatable.
UserScript<<set …>> statements at the top of the node’s body, when translatable.
Actor NameThe Speaker: prefix.

Every multi-successor set becomes options, because ChatMapper offers any multi-successor set as choices — the exporter does not guess from the presence of Menu Text.

Only exact translations are emitted:

LuaYarn
Variable["x"]$x
== ~= < <= > >=== != < <= > >=
and or notand or not
numbers, strings, true, falsethe same
Variable["x"] = <expr><<set $x = <expr>>>

Anything else — Dialog[n].SimStatus, Actor[…] reads, function calls, nil, string concatenation — is not translated. In that case:

  • the statement is emitted as a // UNTRANSLATED: … comment exactly where it was, so the human finishing the port sees it in context, and
  • the export’s warnings list it with the node it came from, so the writer gets a punch list.

A condition that cannot be translated has its gate removed (the jump is unconditional) and is reported the same way — the content stays reachable rather than silently vanishing.

Variables referenced but never declared in the project are declared as 0 with a warning, so the file compiles.

Yarn titles and variable names must start with a letter and contain only word characters. Everything else collapses to _; a name that would start with a digit gets an N_ prefix. The header comment is the map back.

// Yarn Spinner export from ChatMapper.
// Project: The Ferry at Dusk
// Entry point: Setup
//
// Node title map — Yarn title <= conversation / node it came from:
// Asking_for_passage_Start <= conversation 1 "Asking for passage" / node 0 "START"
// Asking_for_passage_Opening_line <= conversation 1 "Asking for passage" / node 1 "Opening line"
// Asking_for_passage_Accept_the_token <= conversation 1 "Asking for passage" / node 3 "Accept the token"
// Asking_for_passage_Stay_ashore <= conversation 1 "Asking for passage" / node 4 "Stay ashore"
// Asking_for_passage_Ending_crossing <= conversation 1 "Asking for passage" / node 5 "Ending - crossing"
// Asking_for_passage_Ending_ashore <= conversation 1 "Asking for passage" / node 6 "Ending - ashore"
title: Setup
---
<<declare $has_token = false>>
<<set $has_token = false>>
<<jump Asking_for_passage_Start>>
===
title: Asking_for_passage_Start
---
<<jump Asking_for_passage_Opening_line>>
===
title: Asking_for_passage_Opening_line
---
Ferryman: Last crossing of the day.
Ferryman: Coming aboard, or staying with the rain?
-> I'll take the crossing.
<<jump Asking_for_passage_Accept_the_token>>
-> I'll wait for morning.
<<jump Asking_for_passage_Stay_ashore>>
===
title: Asking_for_passage_Accept_the_token
---
<<set $has_token = true>>
Traveller: I'll take the crossing.
<<if $has_token == true>>
<<jump Asking_for_passage_Ending_crossing>>
<<endif>>
===
title: Asking_for_passage_Stay_ashore
---
Traveller: I'll wait for morning.
<<jump Asking_for_passage_Ending_ashore>>
===
title: Asking_for_passage_Ending_crossing
---
Ferryman: Mind the step. She rolls once we clear the piling.
===
title: Asking_for_passage_Ending_ashore
---
Ferryman: Suit yourself. The rain is not in a hurry either.
===

Note how the group node Traveller’s options has no Yarn node: its two children appear directly as options under Opening line.

  • Media fields (Audio Files, Pictures…) and custom fields are not part of Yarn’s model. Ship them separately — the JSON export alongside the Yarn, or a CMPKG — keyed by the node IDs in the header map.
  • SimStatus has no Yarn equivalent; Yarn’s visited() function is the nearest idea, and a script that needs it is reported for manual translation.
  • Priority and Passthrough have no Yarn equivalent. Successors are emitted in link order with their conditions; a design that relies on priority suppression or passthrough should be checked in the Yarn host.
  • Emphasis tags are plain text to Yarn. They pass through inside the line; strip or style them in your line view.

There is no Yarn importer. Treat the .yarn as a build artifact: edit in ChatMapper, re-export.