Skip to content

Data model

Everything ChatMapper produces — JSON, XML, .cmp, Yarn, the HTML player — is a view of one model. Learn it once and every export is obvious. The JSON format page is the normative key-by-key specification; this page is the model in prose.

A project is one document. It has a Title, Author, Description, a primary Language (en-US), a Version string recording which schema version wrote it (informational), a project-level UserScript in Lua that runs once at the start of a playthrough, and Assets.

Project
├── Language, Title, Version, Author, Description, UserScript
├── Assets
│ ├── Actors[] — characters; one has IsPlayer = "True"
│ ├── Items[] — objects, and quests in some engines
│ ├── Locations[] — places
│ ├── UserVariables[] — named state with an initial value
│ └── Conversations[] — each a graph of DialogNodes
└── _editor — editor-only metadata; may be ignored or stripped

Actors, Items, Locations and UserVariables share one shape: an integer ID unique within its own collection, and a Fields map.

{ "ID": 2, "Fields": { "Name": "Ferryman", "IsPlayer": "False", "Description": "Works the last crossing." } }

Every field value is a string. "True", "30", "[]" — regardless of the field’s declared type. Coerce at the boundary of your code, once, and never write a non-string value back.

Conventional fields:

CollectionFields
ActorsName, IsPlayer, Description, Pictures, Age, Gender
ItemsName, Description, Pictures, Purpose, Scene
LocationsName, Description, Pictures
UserVariablesName, Initial Value, Description

Projects add their own fields freely (see Extending).

Lua only ever says Variable["has_token"]. The variable’s ID is editor bookkeeping — and ChatMapper XML has no ID attribute on <UserVariable> at all, so IDs do not survive a JSON → XML → JSON trip. Key variables by Fields.Name; treat the ID as advisory.

Initial Value is a string that a runtime should coerce: "True"/"False" (case-insensitive) to boolean, a numeric string to a number, anything else stays text. The simulator, the HTML player and the reference parser all use that rule.

A conversation is a directed graph of dialogue nodes — a tree in the common case, but cycles (hubs, loops) and merges are legal and common.

{
"ID": 1,
"NodeColor": "White",
"Fields": { "Title": "Asking for passage", "Actor": "1", "Conversant": "2", "Primary Location": "1" },
"DialogNodes": [ … ]
}
  • ID is unique across conversations.
  • Fields.Actor and Fields.Conversant are stringified actor IDs ("2", never "Ferryman"). "" and "-1" both mean unset. They are defaults that new nodes inherit in the editor; the node’s own values are what a runtime uses.
  • Exactly one node has IsRoot: true. Conventionally its ID is 0 and its title is START.

A node is one beat: a line, a choice, or a structural group.

KeyMeaning
IDUnique within the conversation.
ConversationIDMust equal the owning conversation’s ID.
IsRootThe start marker. Never spoken, even if it has text.
IsGroupStructural: not played; its children are the choice set.
ConditionsStringLua expression; empty means always true.
UserScriptLua statements run when the node is entered.
FalseConditionAction0/"Block" — drop the node when the condition fails. 1/"Passthrough" — offer its children instead.
ConditionPriority1 High … 5 Low, 3 Normal. Lower wins.
DelaySimStatusHold the node’s status update until the next choice set is built.
NodeColorCanvas tint, a name ("Green") or the legacy integer.
FieldsThe text and metadata below.
OutgoingLinksLinks out of this node.
FieldMeaning
TitleAuthor-facing label. Never shown to players.
ActorStringified actor ID — who speaks.
ConversantStringified actor ID — who is spoken to.
Dialogue TextThe spoken line.
Menu TextPresent ⇒ this node is a player choice; the value is the button label. Absent ⇒ the node plays automatically.
ParentheticalDelivery note (wearily).
Audio Files, Video File, Animation Files, PicturesMedia references.

The distinction that matters most: Dialogue Text is what is spoken; Menu Text is what appears on a choice button. A player-choice node usually has both — the button label and what the player then says — and they are often the same string.

Audio Files, Animation Files and Pictures are lists, serialised as a bracketed, semicolon-separated string: "[intro_01.mp3;intro_02.mp3]". Empty is "[]". Video File is a single reference. References are filenames (or URLs); the files themselves are delivered separately, or inside a CMPKG.

Audio pairs with sentence splits by position: a line "One.|Two." with "[a.mp3;b.mp3]" plays a.mp3 over One. and b.mp3 over Two.

Additional languages are stored as extra fields on the node, titled with the locale: a de-DE translation of Dialogue Text lives in a field titled de-DE (the Desktop convention), and a translated Menu Text in Menu Text de-DE. The project’s _editor.languages lists the locales the editor knows about; a runtime can also discover them by scanning field titles that look like locales.

Links live on the origin node and name their destination by conversation and node ID:

{ "Filename": null, "OriginConvoID": 1, "DestinationConvoID": 1, "OriginDialogID": 3, "DestinationDialogID": 5 }
  • OriginConvoID / OriginDialogID must match the node the link sits on.
  • DestinationConvoID may name a different conversation — a cross-conversation jump, fully supported.
  • Filename is non-null only for a link into a different project file. Most hosts, including Unity’s Dialogue System, cannot follow those; leave it null.

Link order on a node is menu order.

Canonical form is the name; the integer is the legacy Desktop enum and is accepted on read.

NameLegacy int
Red0
Orange1
Yellow2
Green3
LightBlue4
DarkBlue5
Pink6
Purple7
White(none — the serialised default)

One top-level object for editor-only metadata: canvas positions, layout direction, collapsed subtrees, custom field templates and per-field type hints, extra languages, reviewer notes and review status. It is stripped from interchange exports and any consumer may ignore it. Never put anything there that the project’s meaning depends on. Details in Extending the format.

  • Conversation IDs are global; node IDs are per conversation. A node is addressed by the pair (ConversationID, ID).
  • Asset IDs are per collection: actor 1 and item 1 are unrelated.
  • DialogNodes order is document order and carries no meaning; link order does.