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.
The mapping
Section titled “The mapping”| ChatMapper | Yarn |
|---|---|
| Project | One .yarn file. A header comment maps every Yarn title back to its conversation and node. |
| Project script + variables | A 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 node | No 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 Text | Speaker: 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] successor | A one-entry option menu, as the Desktop app would show. |
| Cross-conversation link | A 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 Name | The 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.
What the Lua translation covers
Section titled “What the Lua translation covers”Only exact translations are emitted:
| Lua | Yarn |
|---|---|
Variable["x"] | $x |
== ~= < <= > >= | == != < <= > >= |
and or not | and or not |
numbers, strings, true, false | the 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.
The example, exported
Section titled “The example, exported”// 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.
What does not carry
Section titled “What does not carry”- 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. SimStatushas no Yarn equivalent; Yarn’svisited()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.
Round trip
Section titled “Round trip”There is no Yarn importer. Treat the .yarn as a build artifact: edit in
ChatMapper, re-export.