For AIs, authors & curious humans
The Training Sim
blueprint.
The complete schema for the blueprint JSON: every step type, every field, every default and every rule the importer enforces. Hand this document to an AI or point it at /blueprint.md, describe your training, then paste the JSON it produces into the Training Sim Studio.
# The Training Sim Blueprint (v1)
A blueprint is a single JSON object that describes a whole interactive
training simulation: the chat conversation AND the decisions the viewer
makes. You can write one yourself, but the usual flow is to hand this
document to an AI, describe your training, and paste or upload the JSON
it produces into ChatAnimate's Training Sim Studio (Interactions →
Import blueprint).
Everything below is the exact contract the importer enforces. Unknown
fields are ignored, never fatal. When something small is off, the import
fixes it and tells you what it changed.
---
## The overall shape
```json
{
"version": 1,
"title": "The Art of Saying No",
"participants": [],
"messages": [],
"steps": [],
"settings": {}
}
```
| Field | What it means | Required |
|---|---|---|
| `version` | Always `1`. | yes |
| `title` | A working title. The author can change it later. | no |
| `participants` | Optional people in the chat (see below). | no |
| `messages` | The conversation script: what plays on the phone. | one of messages / steps |
| `steps` | The decisions: questions, videos, documents, branches. | one of messages / steps |
| `settings` | Pass score, retake behavior, certificate, message speed. | no |
A blueprint with only `messages` imports a conversation with no
interactions. A blueprint with only `steps` adds interactions to the
conversation that is already open in the editor.
---
## messages
Each message is one chat bubble.
```json
{ "from": "left", "text": "Got a sec? The client moved the deadline.", "delay": 1.5 }
```
| Field | What it means | Default |
|---|---|---|
| `from` | Who speaks: `"left"` or `"right"`, or a participant name from `participants`. | alternates left, right, left... |
| `text` | The message text. Required. | |
| `delay` | Pause before this message appears, in seconds (0 to 60). `0` means it appears instantly, with no typing pause. | auto |
| `image` | A public image URL shown as the message attachment. Omit for plain text. | none |
| `thought` | `true` renders it as an internal thought bubble instead of a sent message. | `false` |
| `typingSpeed` | `"slow"`, `"medium"`, `"fast"` or `"instant"` (appears with no typing animation). | `"medium"` |
Write real, human dialogue. Training sims work when the conversation
feels like a real chat: short messages, natural timing, mistakes and
hesitation included.
## participants (optional)
```json
[{ "name": "Maya", "color": "#ff7a59" }]
```
`name` is what `from` refers to. `color` is a bubble color hint. If you
skip participants, the chat uses the two default people already in the
editor.
---
## steps
Steps run in order, top to bottom. Each step is one interaction or one
invisible logic move. Every gate step (question, feedback, info, video,
document) needs `afterMessage`: the message number it appears after,
counting from 1. A question with `afterMessage: 3` pops open once
message 3 has played.
**The step-to-step flow.** By default each step continues to the one
below it and the last step ends the simulation. Any step can carry a
top-level `goTo` to change its default: `{"type": "info", ..., "goTo":
"finish"}` skips straight to that step. Questions route per answer (see
below), and when an answer has no `goTo` it takes the step's own
default, then the sequential next. The same is true when a question is
skipped or times out: the default path runs.
**Two ways to branch.** Every `goTo` (an answer's, a card's, a jump's)
can land on one of two things, and they compose:
1. **A coaching card**: an `info` or `feedback` step anchored after
the same message as the question. The viewer answers, gets the card
that matches THAT answer, then the flow carries on. In the studio
this is the "New coaching card for this answer" row under the
question's *Where each answer leads*.
2. **A new branch of messages**: a `playback` step with its own
message range. The viewer answers, and the chat itself changes:
a different slice of your `messages` plays next. In the studio this
is the "New branch of messages" row. See "Branches of messages"
below for the full pattern.
### question (the one viewers answer)
```json
{
"id": "first-move",
"type": "question",
"afterMessage": 3,
"question": "What should Jordan do first?",
"input": "multiple-choice",
"required": true,
"timeLimit": 30,
"answers": [
{ "text": "Ask what changed", "points": 10, "goTo": "next" },
{ "text": "Promise it anyway", "goTo": "pressure" }
]
}
```
| Field | What it means | Default |
|---|---|---|
| `id` | A name other steps can point at with `goTo`. Generated when omitted. | `question-N` |
| `afterMessage` | Appears after this message number (1-based). | required |
| `question` | The text the viewer answers. Required. | |
| `input` | `multiple-choice`, `yes-no`, `rating`, `checkbox`, `dropdown`, `short`, `paragraph`, `image-tap`, `text-match`. | `multiple-choice` |
| `required` | `false` lets the viewer skip it. | `true` |
| `timeLimit` | Seconds to answer. No limit when omitted. | none |
| `answers` | The choices (2 to 6). Only for choice inputs. | |
Each answer:
| Field | What it means | Default |
|---|---|---|
| `text` | The answer the viewer picks (or the accepted spelling on `text-match`). Required. | |
| `points` | Points a correct answer awards. The experience keeps a running score. | 0 |
| `goTo` | Where this answer leads: a step `id`, `"next"` (the step below) or `"end"`. | `"next"` |
| `region` | `image-tap` only: the tap area on the picture, `{ "x": 10, "y": 20, "w": 30, "h": 15 }` in percentages. | |
You do NOT manage variables or conditions for answer routing. The
importer writes that bookkeeping itself. Give each answer a `goTo` and
it just works. When several answers share one target card, point them
at the same `id`.
**A question and the cards its answers route to normally sit at the
same `afterMessage`** (the question and its coaching cards all anchor
after the same message). The player opens the card that matches the
answer the viewer picked, never a sibling card that happens to sit at
the same position. Keep the question above its cards in the steps list
so the default (below) path stays meaningful.
Two extra inputs for the question step (S367):
- **`image-tap` ("tap the red flags")**: add `imageUrl` (a public image
URL) on the step, and give each answer a `region` (percentages of the
picture). The viewer taps the matching spot. Ideal for phishing
screenshots, hazard spotting and diagram questions.
- **`text-match` (type the answer)**: each answer's `text` is one
accepted spelling. The viewer types; matching ignores case and extra
spaces. Ideal for fill-in-the-blank knowledge checks.
### feedback / info (cards, no answer)
```json
{ "type": "feedback", "afterMessage": 6, "title": "How did that feel?", "body": "Notice Jordan did not apologize for the deadline." }
```
`feedback` asks how it is going. `info` is a read-and-continue card.
`title` is optional, `body` (the card text) is required.
### video (watch before continuing)
```json
{ "type": "video", "afterMessage": 8, "videoUrl": "https://.../safety.mp4", "label": "The safety walkthrough", "requireWatch": 0.9 }
```
`videoUrl` can be a public video URL. It can also be omitted: the step
imports with no video and the author picks one from the Media section
later. `requireWatch` is the fraction the viewer must actually watch
(0.5 to 1). Fast-forwarding does not count. Default `0.9`, the training
standard.
**Placeholders.** When you (or the AI) do not have a real URL, omit
`videoUrl` and write the `label` as the instruction the author sees:
`"label": "Upload: the warehouse safety walkthrough"`. The label
travels with the step, so the author knows exactly which file belongs
where. See "Media placeholders" below.
### document (a PDF to read and confirm)
```json
{ "type": "document", "afterMessage": 9, "documentUrl": "https://.../policy.pdf", "label": "The expense policy" }
```
The viewer opens it in the built-in reader and confirms reading it.
`acknowledge: false` turns the confirmation off. `documentUrl` may be
omitted the same way a video URL may (a placeholder label tells the
author which PDF to upload).
### jump (skip ahead)
```json
{ "type": "jump", "goTo": "wrap-up" }
```
### score (award points)
```json
{ "type": "score", "points": 10 }
```
### delay (a beat of silence)
```json
{ "type": "delay", "seconds": 2 }
```
### variable (remember something)
```json
{ "type": "variable", "ops": [{ "op": "add", "name": "stress", "value": 1 }] }
```
`op` is `set`, `add` or `increment`. The variable `score` is special:
quiz points add to it automatically.
### condition (a fork in the road)
```json
{
"type": "condition",
"paths": [
{ "when": "score", "is": "atLeast", "value": 20, "goTo": "passed" },
{ "goTo": "recap" }
]
}
```
Paths run top to bottom, first match wins. `is` is one of `equals`,
`isNot`, `atLeast`, `atMost`, `contains`. A path with no `when` is the
default and should be last. When reads a variable (`score` unless you
name another). Prefer conditions on `score` and let the answer `goTo`
routing handle everything else.
### playback (replay a slice of the conversation)
```json
{ "type": "playback", "fromMessage": 1, "toMessage": 4 }
```
Message numbers are 1-based, inclusive. A playback step plays its
slice INSTEAD of continuing linearly, which makes it the building
block of branches (below).
### Branches of messages (changing what the chat says)
`messages` is a pool, not one fixed script. Write the messages of
several different continuations in ONE array, then route each answer
to a `playback` step that plays its own slice:
```json
{
"messages": [
{ "from": "left", "text": "hi, quick supplier query" },
{ "from": "right", "text": "sure" },
{ "from": "left", "text": "can you confirm Sarah's surname?" },
{ "from": "right", "text": "..." },
{ "from": "left", "text": "ok that's all I need" }
],
"steps": [
{
"id": "ask",
"type": "question",
"afterMessage": 3,
"question": "Should you confirm the surname?",
"answers": [
{ "text": "No, verify the requester first", "points": 10, "goTo": "good-path" },
{ "text": "Sure, she seems friendly", "goTo": "scam-unfolds" }
]
},
{
"id": "good-path",
"type": "playback",
"fromMessage": 4,
"toMessage": 5
},
{
"id": "scam-unfolds",
"type": "playback",
"fromMessage": 6,
"toMessage": 11
},
{ "type": "completion" }
]
}
```
In this shape the first five messages set the scene (only the first few
are shown; the array keeps going). The `good-path` branch plays
messages 4 to 5, the safe wrap-up. The `scam-unfolds` branch plays
messages 6 to 11 (also in the array, omitted from the snippet): the
conversation that unfolds when the viewer answers wrong and the social
engineer keeps pulling information. Keep a branch's messages
contiguous: ranges that are easier to read are ranges that are easier
to route.
The rules that make a branch work:
- Cards that belong to a branch anchor INSIDE that branch's range (an
`afterMessage` between its `fromMessage` and `toMessage`), because a
card only appears when the conversation plays across its position.
- A branch rejoins the main flow through its own `goTo` (or the step
below it), usually at the next question or the completion.
- `settings.typing: "instant"` is a good idea when a training has many
branches: viewers re-watch slices without the typing delay.
### Media placeholders (images, videos, PDFs)
Media steps accept real public URLs when you have them. When you do
not, do not guess: leave a placeholder the author completes in the
studio after import:
- **video**: omit `videoUrl`, write the instruction in `label`
(`"label": "Upload: the safety walkthrough"`).
- **document**: omit `documentUrl`, same `label` pattern
(`"label": "Upload: the 2026 expense policy PDF"`).
- **image-tap question**: omit `imageUrl`; the question still imports
and the editor's "Upload a picture" lane lights up for it.
- **a card's picture**: omit the card's `image` and describe the
intended picture in the `body` ("Attach the invoice screenshot as
this card's image").
The `label` field is what the author sees next to the upload lane, so
it is the natural place to say which file goes where. A fabricated
URL, by contrast, breaks the experience for every viewer, which is
why the importer treats invented URLs as the one unforgivable sin.
### Combinations that work together
- **Coaching per answer**: wrong answers each `goTo` their own card at
the question's `afterMessage`; the correct answer continues. All
cards meet again at the next step.
- **A branch per answer**: answers route to `playback` steps with
different ranges, so the chat itself changes (the pattern above).
- **Media then question**: a `video` or `document` step first (the
watch or read requirement holds the viewer), then the question that
checks understanding.
- **Score fork near the end**: a `condition` on `score` sends passers
straight to the completion and sends strugglers to a recap
`playback` step first.
- **Mixed depth**: a branch may itself contain questions, videos and
documents, so one wrong turn can open a whole corrective sequence
before rejoining.
### completion (the finish line)
```json
{ "type": "completion" }
```
One per blueprint. The final screen: score, recap, certificate link and
the retake decision (from `settings`).
---
## settings
```json
{ "passScore": 20, "quizRetake": "enforce", "certificate": true, "typing": "instant" }
```
| Field | What it means | Default |
|---|---|---|
| `passScore` | Points needed to pass. `null` = no pass mark. | `null` |
| `quizRetake` | `"show"` tells failing viewers their score, `"enforce"` makes them retake until they pass. | `"show"` |
| `certificate` | `true` offers passing viewers a downloadable certificate, generated on demand. Nothing is emailed. | `false` |
| `typing` | `"instant"` makes every message appear immediately with no typing animation: one switch instead of setting it per message. `"typing"` keeps the realistic typed-out chat. The global overrides any per-message `typingSpeed`. | `"typing"` |
---
## Rules the importer enforces
1. `version` must be `1`.
2. A gate step without `afterMessage` is an error.
3. A choice question needs at least two answers (yes-no takes two at
most). Six answers is the maximum.
4. Every `goTo` must name a step id that exists, or be `"next"` / `"end"`.
5. A playback range must be a real ascending range inside the
conversation.
6. `afterMessage` beyond the conversation's length is clamped to the
last message.
7. At most one completion step (extras are dropped).
8. A card whose `afterMessage` is not inside any playback range (when
playback steps are used) triggers a warning: viewers following that
branch will never cross it, so the card will not show. Move the
card's anchor inside a replayed range or widen the range.
Small problems (a bad `delay`, seven answers, an unknown field) become
warnings: the import continues with sensible fixes and lists every one.
---
## The prompt to give the AI
Copy everything between the lines into your AI chat, then describe your
training.
The prompt is generated from the same module the importer uses
(`BLUEPRINT_AI_PROMPT`), so it always matches the real schema: required
vs optional fields, legal values, how `goTo` routing, scores and media
gating work, and the rule that a `goTo` must name a step id that exists
in the same blueprint. The Ask-an-AI tab in the studio copies the
current version for you.
```
You build ChatAnimate Training Sim blueprints. Return ONLY one valid
JSON object following the schema below. No prose before or after. No
markdown code fences.
THE OVERALL SHAPE
{ "version": 1, "title": "...", "messages": [ ... ], "steps": [ ... ],
"settings": { "passScore": null, "quizRetake": "show", "certificate": false, "typing": "typing" } }
MESSAGES: { "from": "left|right", "text": "required", "delay": 0-60
(0 = instant, no typing pause), "image": "public url (optional)",
"thought": true (optional), "typingSpeed": "slow|medium|fast|instant"
(optional) }. 12 to 30 short, human messages.
STEPS: cards (question, feedback, info, video, document) each need
"afterMessage" (1-based). Logic steps (jump, score, delay, variable,
condition, playback, completion) run between cards. Give a step an "id"
(lowercase words with dashes) only when something routes to it.
ROUTING: a "goTo" must be the id of a step in THIS blueprint, or
"next", or "end". Never invent a goTo that is not one of your own step
ids.
SCORING: correct answers carry "points"; score steps add "points";
settings.passScore is the pass mark the finish screen compares against.
SPEED: settings.typing = "instant" makes every message appear
immediately, no typing animation. Use it for fast-paced training or
flows with many branches. Default "typing".
One completion step, last. Unknown fields are ignored, never fatal.
When in doubt, produce a simpler valid blueprint rather than guessing
an option.
My training is about: ...
```
Then paste the AI's JSON into Training Sim Studio → Interactions →
Import blueprint. ChatAnimate validates it, tells you anything it had to
fix and applies it. Export the current draft any time: the same format
comes back out, ready for the next round with the AI. If the AI's answer
fails validation, paste the error lines back to the AI together with
your exported blueprint and ask it to fix them.