Importers
Every importer turns a foreign file into canonical ChatMapper JSON plus a report of everything that did not map one-to-one. The mappings are deliberately boring, reversible where possible, and documented here so a migration can be planned before it is run.
Detection
Section titled “Detection”Formats are detected by content, not extension. Each importer scores the
file and the highest confidence wins, which is how two .json formats
(Arcweave, articy) and two Twine dialects (Twee 3, Twine 2 HTML) coexist.
The report
Section titled “The report”interface ImportReport { source: string; // importer id: "csv" | "twine" | "arcweave" | "articy" | "chatmapper-xml" | … stats: { conversations, nodes, links, actors, variables, skipped, … }; warnings: { message: string; where?: string }[]; // non-fatal; the import still succeeds lossy: { kind: string; // e.g. "harlowe-macro", "arcscript-expression" where: string; // passage title, element id, row number original: string; // the source text, verbatim preservedIn?: string; // custom field on the node that now holds it count?: number; }[];}The editor shows the report before writing anything. The principle behind
lossy: nothing is dropped silently. If the importer could not translate
it, the original text is in the report and in a custom field on the node
where a human can finish the job.
Spreadsheets (CSV, TSV, XLSX)
Section titled “Spreadsheets (CSV, TSV, XLSX)”Forgiving by design: writers hand over whatever their sheet looks like and a valid conversation comes out.
Columns — case-insensitive, order-free, aliases accepted:
| Column | Aliases | Meaning |
|---|---|---|
id | node id | Node ID. Optional. |
actor | speaker, character | Speaker name; actors are created as met. |
text | dialogue, line | Dialogue Text. |
menu | choice, menu_text | Menu Text. A row with menu text is a choice. |
parent | parent_id, from | The row’s parent node. |
goto | link_to, next | An extra link out of this row. |
condition | Lua condition. | |
script | Lua script. | |
conversation | Conversation title (falls back to the sheet name). | |
title | Node title. |
Any other column is kept verbatim as a custom field named by its header, and listed as lossy so you know it happened.
Structure inference when parent is absent: a plain row hangs off the
row before it; a run of consecutive choice rows all hang off the last plain
row, so the common line, line, choice, choice shape becomes a real branch.
Anything more intricate needs parent / goto — that is why they exist.
Speakers: a blank actor continues the previous speaker for plain rows;
a choice row with no actor gets an auto-created player actor. Variables
referenced in conditions and scripts are declared automatically.
The CSV and Excel guide is the writer-facing version.
Twine (Twee 3, Twine 2 HTML)
Section titled “Twine (Twee 3, Twine 2 HTML)”| Twine | ChatMapper |
|---|---|
| Story | Conversation. An HTML archive with several stories → several conversations. |
| Passage | Dialogue node, spoken by an auto-created Narrator actor. |
[[Target]] | Plain link to the target’s node. |
[[Label->Target]], [[Target<-Label]], [[Label|Target]] | An interstitial player node carrying Label as Menu Text, whose single child is the target. |
| Start passage | Linked from a synthetic START (ID 0). The start passage is not itself the root, because roots are never spoken. |
| Passage position | Canvas position, rescaled from Twine’s 100×100 grid. |
| Tags, story metadata | Reported. |
(set: $x to 1) / <<set $x = 1>> with one variable and one literal | Translated to Variable["x"] = 1. |
Every other macro (if, link, goto, display…) | Stripped from the display text, preserved verbatim in twine_markup_raw, reported once per macro name per passage. |
Why link labels become nodes: in Twine the label belongs to the edge, and two passages can link to the same target under different labels. Writing the label into the target’s Menu Text would collapse those; one extra hop keeps the graph unambiguous and lossless.
Arcweave
Section titled “Arcweave”Reads the web app’s Export → JSON (single-language and multi-language shapes; only the base locale is imported, the rest reported).
| Arcweave | ChatMapper |
|---|---|
| Leaf board | Conversation. Folder boards are structure only. |
| Element | Dialogue node in its board’s conversation. Position kept 1:1. |
| Connection | Link. Cross-board connections become cross-conversation links. |
| Connection label | Menu Text on the target node. When a second inbound connection would need to overwrite a label, condition or script the target already has, a relay node is inserted instead (a player choice node for a label, a group node for a condition). Nothing is overwritten. |
Entry elements (nothing links to them, or startingElement) | Linked from a synthetic START. Not promoted to root. |
| Character component attached to an element | The node’s Actor. Other attached components → arcweave_components. Attributes → arcweave_attr_<name>. |
| Branch → condition → output | Flattened to direct links; each condition’s arcscript becomes the target’s ConditionsString, with if / else-if / else order preserved as ConditionPriority (lower tried first). |
| Jumper | Resolved to its target element. |
| arcscript, mechanical subset (comparisons and boolean combinations of declared variables; literal or simple-arithmetic assignments) | Translated to Lua. |
| Any other arcscript | Kept verbatim in arcweave_script_raw / arcweave_condition_raw, reported. Never half-translated. |
| Rich text beyond paragraphs and entities | Plain text kept; raw HTML preserved when formatting would be lost. |
| Arcweave string IDs | arcweave_id on every node, actor, variable and conversation. |
articy:draft X
Section titled “articy:draft X”Reads the single-document JSON export (Settings / Project /
GlobalVariables / ObjectDefinitions / Packages / Hierarchy).
| articy | ChatMapper |
|---|---|
| Entity | Actor (DisplayName, Text; template features as custom fields). |
| Dialogue | Conversation. |
| FlowFragment containing dialogue | Conversation. |
| FlowFragment containing none | Transparent folder; links pass through it. |
| DialogueFragment | Dialogue node: Text, MenuText, Speaker, StageDirections → Parenthetical. |
| Hub | Group node. |
| Condition / Instruction | Group node carrying ConditionsString / UserScript. |
| Jump | Not a node: incoming links are rewritten to the jump’s target. |
| Location / Zone / Spot | Location stub. |
| Asset | Item stub (binaries skipped). |
| GlobalVariables | UserVariables, namespace flattened (Set.Var → Set_Var). |
| Pin connections | Into an input pin = enter (→ that conversation’s root); into an output pin = leave, following the container’s own outgoing connections — which is what turns an articy exit into a cross-conversation link. |
| Positions | Kept. |
| Scripts beyond the mechanical subset | articy_script_raw, reported. |
| Object IDs | articy_id. |
Two deliberate approximations, both reported: articy Conditions branch true/false on separate output pins, but ChatMapper gates the whole node, so both branches become plain links and the false branch is flagged; and articy has no player flag, so an entity that speaks at least one fragment with MenuText is inferred to be the player.
ChatMapper XML, .cmp, .cmpkg, JSON
Section titled “ChatMapper XML, .cmp, .cmpkg, JSON”- XML — the Desktop serialisation, parsed in pure TypeScript. Lossless for everything the model holds; see ChatMapper XML.
.cmp/.cmpkg— decoded server-side (the Desktop app’s binary format is AES-encrypted with a key compiled into the freely distributed app; it is not a security boundary). A.cmpkg’s media is attached to the project. The Python decoder has been verified across Desktop versions 1.2.0.0 and 1.5.1.0.- JSON — the canonical format; imported as-is after normalisation.
After a migration
Section titled “After a migration”Search the project for _raw to find every node with untranslated logic,
validate, and simulate the main path. The writer-facing checklist is on
Importing existing work.