Project Mammut Wiki
Project Mammut is currently in open beta. The UI is being updated a lot during this time, and you may run into bugs. If you'd like to help develop the engine or suggest features, come find us on Bluesky or Discord.

Revealer

Written 5 Aug 2026 · Updated 8 Aug 2026 · Requires Mammut 0.6.2+

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:

  1. The raw text is tokenised into tags, actions, logic blocks, words, and whitespace.
  2. Logic blocks ({if}, {for}, {set}, {print}, {$var}) are evaluated and spliced into plain output tokens.
  3. 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:

  1. If the click landed on an inline link (a <link> style tag), it jumps straight to that node, the same as [AutoLink(...)].
  2. Otherwise, if the reveal is waiting on [AwaitClick()] or similar, the click resolves it.
  3. 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}