Game System (E6xxx)
The System built by main() defines the game’s modes: root pages, overlays, dialogs, the game mode and its overlays. Kagari builds a graph of how the nav::* handlers move between these modes. It checks that every mode can be reached, that every mode can be left, and that the mode stack stays bounded.
These checks need a whole game, so each snippet below is a complete program: the same minimal main() with its settings, an entry scene and a game view, followed by the modes that trigger the diagnostic.
W6001 unreachable-mode
Warning. No reachable mode ever opens this mode, so the player can never see it. The warning also fires when no reachable mode calls nav::start or nav::load, so the game itself never starts.
use std::narrative::text::text_style;use std::screen::size;
static TEXT_SPEED: Setting<Duration> = setting(ms(30));static AUTO_DELAY: Setting<Duration> = setting(ms(1300));static AUTO_DELAY_PER_CHAR: Setting<Duration> = setting(ms(60));static SKIP_UNREAD: Setting<bool> = setting(false);static MASTER: Setting<Volume> = setting(volume(100));static MUSIC: Setting<Volume> = setting(volume(60));static SOUND: Setting<Volume> = setting(volume(100));static VOICE: Setting<Volume> = setting(volume(100));static VOICE_SUSTAIN: Setting<bool> = setting(false);static WAIT_FOR_VOICE: Setting<bool> = setting(true);static WINDOW_OPACITY: Setting<Int<0, 100>> = setting(80);static FULLSCREEN: Setting<bool> = setting(false);static ANIMATIONS: Setting<bool> = setting(true);const FONT: FontAsset = asset("fonts/default.otf");
fn main() -> System { System { title: "Example", resolution: size(1280, 720), start: Title, game: GameView, save_slots: 20, auto_slots: 3, backlog: 200, font: FONT, text: text_style(26, WHITE), settings: Settings { text_speed: TEXT_SPEED, auto_delay: AUTO_DELAY, auto_delay_per_char: AUTO_DELAY_PER_CHAR, skip_unread: SKIP_UNREAD, master: MASTER, music: MUSIC, sound: SOUND, voice: VOICE, voice_sustain: VOICE_SUSTAIN, wait_for_voice: WAIT_FOR_VOICE, window_opacity: WINDOW_OPACITY, fullscreen: FULLSCREEN, animations: ANIMATIONS, }, bindings: Vec::new(), }}
#[scene]#[entry]pushdown fn Start() {}
fn GameView(game: Narrative) -> Element { game_screen!(game = game)}
fn Title() -> RootPage { RootPage(button!{on_click = || nav::start(Start, (), |_end| ()), t!["Start"]})}
fn Unused() -> Overlay { Overlay(button!{on_click = || nav::back(), t!["Back"]})}Fix: open the mode from a reachable handler (e.g. button!{on_click = || nav::call(Unused), t!["More"]}), or delete it.
E6002 back-from-base
Error. nav::back() runs in a mode at the base of the mode stack, such as a root page or the game mode. Nothing is under it, so going back does nothing.
use std::narrative::text::text_style;use std::screen::size;
static TEXT_SPEED: Setting<Duration> = setting(ms(30));static AUTO_DELAY: Setting<Duration> = setting(ms(1300));static AUTO_DELAY_PER_CHAR: Setting<Duration> = setting(ms(60));static SKIP_UNREAD: Setting<bool> = setting(false);static MASTER: Setting<Volume> = setting(volume(100));static MUSIC: Setting<Volume> = setting(volume(60));static SOUND: Setting<Volume> = setting(volume(100));static VOICE: Setting<Volume> = setting(volume(100));static VOICE_SUSTAIN: Setting<bool> = setting(false);static WAIT_FOR_VOICE: Setting<bool> = setting(true);static WINDOW_OPACITY: Setting<Int<0, 100>> = setting(80);static FULLSCREEN: Setting<bool> = setting(false);static ANIMATIONS: Setting<bool> = setting(true);const FONT: FontAsset = asset("fonts/default.otf");
fn main() -> System { System { title: "Example", resolution: size(1280, 720), start: Title, game: GameView, save_slots: 20, auto_slots: 3, backlog: 200, font: FONT, text: text_style(26, WHITE), settings: Settings { text_speed: TEXT_SPEED, auto_delay: AUTO_DELAY, auto_delay_per_char: AUTO_DELAY_PER_CHAR, skip_unread: SKIP_UNREAD, master: MASTER, music: MUSIC, sound: SOUND, voice: VOICE, voice_sustain: VOICE_SUSTAIN, wait_for_voice: WAIT_FOR_VOICE, window_opacity: WINDOW_OPACITY, fullscreen: FULLSCREEN, animations: ANIMATIONS, }, bindings: Vec::new(), }}
#[scene]#[entry]pushdown fn Start() {}
fn GameView(game: Narrative) -> Element { game_screen!(game = game)}
fn Title() -> RootPage { RootPage(column![ button!{on_click = || nav::start(Start, (), |_end| ()), t!["Start"]}, button!{on_click = || nav::back(), t!["Back"]}, ])}Fix: from a base mode, use nav::goto(...) to switch pages, or nav::quit(), instead of nav::back().
E6003 trapped-mode
Error. None of this mode’s handlers calls nav::back, nav::goto, nav::start, nav::load or nav::quit, and no key binding performs Action::Back. Once the mode opens, the player can’t leave it.
use std::narrative::text::text_style;use std::screen::size;
static TEXT_SPEED: Setting<Duration> = setting(ms(30));static AUTO_DELAY: Setting<Duration> = setting(ms(1300));static AUTO_DELAY_PER_CHAR: Setting<Duration> = setting(ms(60));static SKIP_UNREAD: Setting<bool> = setting(false);static MASTER: Setting<Volume> = setting(volume(100));static MUSIC: Setting<Volume> = setting(volume(60));static SOUND: Setting<Volume> = setting(volume(100));static VOICE: Setting<Volume> = setting(volume(100));static VOICE_SUSTAIN: Setting<bool> = setting(false);static WAIT_FOR_VOICE: Setting<bool> = setting(true);static WINDOW_OPACITY: Setting<Int<0, 100>> = setting(80);static FULLSCREEN: Setting<bool> = setting(false);static ANIMATIONS: Setting<bool> = setting(true);const FONT: FontAsset = asset("fonts/default.otf");
fn main() -> System { System { title: "Example", resolution: size(1280, 720), start: Title, game: GameView, save_slots: 20, auto_slots: 3, backlog: 200, font: FONT, text: text_style(26, WHITE), settings: Settings { text_speed: TEXT_SPEED, auto_delay: AUTO_DELAY, auto_delay_per_char: AUTO_DELAY_PER_CHAR, skip_unread: SKIP_UNREAD, master: MASTER, music: MUSIC, sound: SOUND, voice: VOICE, voice_sustain: VOICE_SUSTAIN, wait_for_voice: WAIT_FOR_VOICE, window_opacity: WINDOW_OPACITY, fullscreen: FULLSCREEN, animations: ANIMATIONS, }, bindings: Vec::new(), }}
#[scene]#[entry]pushdown fn Start() {}
fn GameView(game: Narrative) -> Element { game_screen!(game = game)}
fn Title() -> RootPage { RootPage(column![ button!{on_click = || nav::start(Start, (), |_end| ()), t!["Start"]}, button!{on_click = || nav::dialog(ConfirmReset), t!["Reset"]}, ])}
// No way out.fn ConfirmReset() -> Dialog { Dialog(button!{on_click = || MUSIC.set(volume(60)), t!["Reset"]})}Fix: add a way out, e.g. button!{on_click = || nav::back(), t!["Cancel"]}, or bind a key to Action::Back (default_bindings() does).
W6004 unbounded-stack
Warning. Overlays open each other, or one opens itself, with nav::call. Each call pushes another mode, so the mode stack can grow without bound.
use std::narrative::text::text_style;use std::screen::size;
static TEXT_SPEED: Setting<Duration> = setting(ms(30));static AUTO_DELAY: Setting<Duration> = setting(ms(1300));static AUTO_DELAY_PER_CHAR: Setting<Duration> = setting(ms(60));static SKIP_UNREAD: Setting<bool> = setting(false);static MASTER: Setting<Volume> = setting(volume(100));static MUSIC: Setting<Volume> = setting(volume(60));static SOUND: Setting<Volume> = setting(volume(100));static VOICE: Setting<Volume> = setting(volume(100));static VOICE_SUSTAIN: Setting<bool> = setting(false);static WAIT_FOR_VOICE: Setting<bool> = setting(true);static WINDOW_OPACITY: Setting<Int<0, 100>> = setting(80);static FULLSCREEN: Setting<bool> = setting(false);static ANIMATIONS: Setting<bool> = setting(true);const FONT: FontAsset = asset("fonts/default.otf");
fn main() -> System { System { title: "Example", resolution: size(1280, 720), start: Title, game: GameView, save_slots: 20, auto_slots: 3, backlog: 200, font: FONT, text: text_style(26, WHITE), settings: Settings { text_speed: TEXT_SPEED, auto_delay: AUTO_DELAY, auto_delay_per_char: AUTO_DELAY_PER_CHAR, skip_unread: SKIP_UNREAD, master: MASTER, music: MUSIC, sound: SOUND, voice: VOICE, voice_sustain: VOICE_SUSTAIN, wait_for_voice: WAIT_FOR_VOICE, window_opacity: WINDOW_OPACITY, fullscreen: FULLSCREEN, animations: ANIMATIONS, }, bindings: Vec::new(), }}
#[scene]#[entry]pushdown fn Start() {}
fn GameView(game: Narrative) -> Element { game_screen!(game = game)}
fn Title() -> RootPage { RootPage(column![ button!{on_click = || nav::start(Start, (), |_end| ()), t!["Start"]}, button!{on_click = || nav::call(Gallery), t!["Gallery"]}, ])}
fn Gallery() -> Overlay { Overlay(column![ button!{on_click = || nav::call(Gallery), t!["Again"]}, button!{on_click = || nav::back(), t!["Back"]}, ])}Fix: go back before opening the other overlay, or switch overlays without pushing, so the cycle doesn’t stack.
Last updated Oct 29, 2026