Mammut's Revealer is the dialogue engine: it turns a chapter node's into the letter-by-letter reveal players see on screen. It's the main way your own scene scripts find out what is happening during a conversation. This page covers how it actually works. For the full markup syntax and API method list, see the Revealer reference and Coding; for the individual [Action(...)] tags, see Actions and Logic.
The Reveal pipeline
Initialisation
Revealer.Init(element, { container }) Initialises the Revealer and attaches a single click listener to the passed text element's container element, not the text element itself. See Clicks below
Start Chapter
Calling Revealer.StartRevealChapter(...) or Revealer.RevealNode(id) runs a node's content through a fixed pipeline:
- The raw text is tokenised into tags, actions, logic blocks, words, and whitespace.
- Logic blocks (
{if},{for},{set},{print},{$var}) are evaluated and spliced into plain output tokens. - What remains is revealed token by token: words are typed out character by character at the configured speed, style tags open and close nested spans, and actions run as they're reached.
Only one reveal is ever active at a time. Starting a new node cancels whatever is currently running, so you never need to stop one manually before starting another.
When a node get picked up by the revealer, the first thing that happens is a logic pass is applied to the node's contents. This process will resolve all the logic steps (changing variables, looping resolving if statements etc.), The output is then passed to a second module for revealing. This means if you change variables in a node, this all happens when the node gets loaded regardless of where the variable change instruction is listed in the code.
Built-in actions versus custom actions
Some [Action(...)] tags are handled directly by Revealer: AwaitClick, ClickClear, AwaitClickClear, Pause, TextSpeed, AutoLink, SwitchScene, LinkOption, and the audio actions (Music, StopMusic, Ambient, StopAmbient, SFX, Voice). Everything else dispatches as an event with the same name, for a scene script to catch with Revealer.Register. See Actions for what each one does.
SetCharacter, UnsetCharacter, and UnsetCharacterAll sit in between: Revealer has no built-in idea of what a character portrait is, so it doesn't act on them directly, but it does pause the reveal automatically the moment one of these fires. That gives your scene script a beat to swap a portrait or play a transition before calling Revealer.Unpause() to let the next line continue.
Handlers and the event object
Every Revealer.Register handler receives one event object: event.name, event.args (positional), and event.kwargs (named). A few built-in events add extra fields, such as options on OptionsReady and char on OnTextCharPlace.
If a handler returns a Promise, the reveal waits for it to resolve before continuing. This is a general mechanism, not something special to character events: any handler for any event can pause the reveal this way simply by being asynchronous.
Control objects versus global control
StartRevealChapter* and RevealNode return a control object scoped to that specific reveal: skip(), pause(), resume(), stop(), isPaused(), and a promise that resolves once the node has fully finished, including any NodeEnd and OptionsReady events it fires.
Revealer.Pause(), Revealer.Unpause(), and Revealer.IsPaused() do the same job globally, without needing a reference to a particular control object. This is what SetCharacter and friends use internally, and it's the more convenient option any time you don't already have the control object for the reveal you want to affect.
Clicking and skipping
The revealer will manage waiting for different types of clicks when needed:
- If the click landed on an inline link (a
<link>style tag), it jumps straight to that node, the same as[AutoLink(...)]. - Otherwise, if the reveal is waiting on
[AwaitClick()]or similar, the click resolves it. - Otherwise, the click skips the rest of the current word's typewriter animation, snapping any pending text into place immediately.
An inline <link> tag is a separate mechanism from [LinkOption(...)]: it jumps immediately on click with no OptionsReady step, while LinkOption collects choices and fires them all together once the node ends.
Expressions
Logic blocks evaluate expressions after substituting $variable references, restricted to a safe set of characters. This is enough for the arithmetic, comparisons, and string handling {if}, {for}, {set}, and {print}