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.

Coding

Written 5 Aug 2026 · Updated 7 Aug 2026

Project Mammut is a HTML5 game engine, and uses Javascript as its core programming language.

Every scene in a project has its own script, made of lifecycle hooks and any custom methods you add. This page is a reference for what's available to write against.

Scene lifecycle

onEnter() {
  // Scene is about to be shown.
},

onStart() {
  // Called after onEnter, once attached scenes are ready. this.dom is populated here.
},

OnExit() {
  // Scene is being removed. Clean up timers/listeners here.
},

// Any other method can be triggered from Revealer markup via [MethodName(...)]

Inside any of these, and any handler, this is the scene instance. this.dom, this.root, this.vars, and any custom methods or properties you've added are all available.

this, the scene instance

  • this.root / this.dom.root: the scene's own root element, which gets the same helpers as any other node (see below).
  • this.dom.<nodeId>: every node marked "referenceable" in the Scene Editor, by id.
  • this.vars: scene-local variables. this.getLocalVar(id, defaultValue) and this.setLocalVar(id, value) read and write it.
  • this.attachedScenes: child scenes attached to this one, populated automatically for "scene" nodes.

DOM node helpers

Attached automatically to every node in this.dom (see _setupDomHelpers in sceneBase.js):

  • hide(), show(), isHidden(), toggleVisibility(): toggle a shared hidden class, independent of the node's own Visible flag and of each other's internal state.
  • disableEvents(), enableEvents(): block or restore mouse and pointer interaction.
  • setState(name, repeat = true): swap in a named CSS state class. Returns a Promise that resolves when that state's CSS animation finishes, or immediately if it has none:
    await this.dom.card.setState("flip");
    
  • Animator nodes: play(clip), pause(), resume(), stop(), restart(clip).
  • Particle nodes: start(), stop(), pause(), resume(), burst(n).

Revealer: text reveal and markup events

Revealer.Init(this.dom.text_box, { container: this.root });
const ctrl = await Revealer.StartRevealChapter("my_chapter");

Revealer.Register("SetCharacter", (event) => {
  const key = event.args[0];
  const variant = event.kwargs?.variant;
});
  • Register(eventName, handler) / Register(eventName, filter, handler): the filtered form only invokes handler for one specific key or gamepad button. The check happens inside the manager, not your handler.
  • Unregister / UnregisterAll take the same arguments.
  • Every handler receives one event object: event.name, event.args (positional), event.kwargs (named key=value), plus event-specific fields like event.options or event.char.
  • Built-in lifecycle events: NodeStart, NodeEnd, SectionStart, SectionEnd, WaitingForClick, OptionsReady, OnTextCharPlace, SetCharacter, plus any custom [ActionName(...)] tag you use in dialogue.
  • Pause() / Unpause() / IsPaused(): freeze or resume the reveal. Returning a Promise from a handler does this automatically until it resolves.
  • RevealNode(nodeId): jump straight to a node.
  • Full reference: /revealer-reference.html, shipped with the app.

Assets: reading project data

Assets.GetCharacter("alice");   // { key, name, image, variants, ... }
Assets.GetChapter("chapter_01");
Assets.GetImage("bg_forest");
Assets.GetAudio("theme_music");
Assets.GetVariant("alice", "smiling"); // a specific variant object
Assets.Get(key, kind);            // generic lookup
Assets.GetAll(kind);              // every asset of a kind
Assets.Register(name, kind, data); // register something at runtime

Store: persistent variables

Store.Get("playerName", "Alex"); // reads, seeding the default if unset
Store.Set("playerName", "Sam");
Store.Register("playerName", (newVal, oldVal) => { /* react to changes */ });
Store.Unregister("playerName", handler);

{$varName} and {set($var, expr)} in dialogue text read and write through this same store.

SceneManager

SceneManager.SwitchScene("main_menu");
SceneManager.GetScene("settings_panel")?.togglePanel(); // reach another loaded scene directly
SceneManager.GetCurrentScene();
await SceneManager.LoadScene("settings_panel"); // ensure it's loaded before calling into it
SceneManager.AttachScene(name) / DetachScene(name);

Every scene registers itself globally by filename once loaded, so GetScene can reach any currently-loaded scene, sibling or not.

AudioManager

AudioManager.PlayMusic("theme");      // crossfades
AudioManager.StopMusic(fadeSeconds);
AudioManager.PlayAmbient("rain");
AudioManager.StopAmbient(fadeSeconds);
AudioManager.PlaySFX("click");
AudioManager.PlayVoice("line_042");
AudioManager.SetMasterVolume(0.8);    // also Music/Sfx/Ambient/Voice
AudioManager.GetMasterVolume();
AudioManager.ApplySettings(settings);
AudioManager.SaveSettings() / LoadSettings();
AudioManager.Register("SFXPlayed", handler); // and other audio events

InputManager

InputManager.Register("KeyDown", (e) => { if (e.code === "Space") jump(); });
InputManager.Register("KeyDown", "Space", (e) => jump()); // pre-filtered by the manager
InputManager.Register("GamepadButtonDown", InputManager.GamepadButton.A, (e) => jump());
InputManager.IsKeyDown("ArrowLeft");
InputManager.IsGamepadButtonDown(gamepadIndex, button);

code is the physical key, layout-independent (e.g. "Escape", "Space"); key is the layout-aware character. GamepadButton has named constants (A, B, X, Y, LeftBumper, DPadUp, and so on) for the standard controller mapping.

Application

Application.GetPlatform();       // "tauri" or "web"
Application.Quit();
Application.Restart();
Application.EnterFullscreen() / ExitFullscreen() / ToggleFullscreen() / IsFullscreen();
Application.OpenURL(url);

Fullscreen uses the browser's Fullscreen API on web and the native OS window under Tauri. That means Escape always exiting fullscreen is a web-only browser restriction, not something the desktop build has to work around.

SaveManager

SaveManager.Save(slot);
SaveManager.Load(slot);
SaveManager.Exists(slot);
SaveManager.GetSlotInfo(slot);   // { date, scene }
SaveManager.GetGameSettings() / SetGameSettings(settings) / SaveGameSettings();

Logging

log("hello");
logWarning("careful");
logError("something broke");
Logger.Register((level, args) => { /* pipe to your own UI, etc. */ });
Logger.Unregister(handler);

Debugging

You can learn how to debug your projects here.