Kagari
Type to search documentation.

Overview

Architecture

This chapter is the big picture: how a Kagari game goes from source files to a running game, what each part of a game is written in, and how the parts may talk to each other. Later chapters define each piece precisely.

From source to running game

kagari.toml project src/ .kis .kix assets/ images, audio, fonts kagari build check, analyze, emit, pack assets diagnostics kagari check, editor target/v0/ game.kagari bytecode *.kiarchive asset archives desktop runtime profile native_v0 data directory settings, meta, saves running game

The project. A game is a project: a directory with a kagari.toml manifest, source files under src/, and assets under assets/. Compilation starts at one root file, src/main.kis by default, and follows its mod declarations to every other source file. A source file is either .kis (the plain language) or .kix (the language plus forms such as s!(…) and choice!{…}); the extension selects the syntax only, never the meaning. Assets are named in the source by constants such as const BG_ROOF: ImageAsset = asset("bg/roof.png");, and the compiler checks that each file exists.

The build. kagari build compiles the whole program at once. It parses and resolves every file, checks types and function kinds, evaluates constants and renders, analyzes the story and the game system, and emits the program. Any error stops the build; warnings do not. The result goes to <out-dir>/<target>/, target/v0/ by default:

OutputContent
game.kagariThe compiled program: story, renders and game system
*.kiarchiveThe assets the program references, one archive per top-level directory of the asset directory, data.kiarchive for files directly in it, system-fonts.kiarchive for installed fonts the game names, and std.kiarchive for the std shaders it uses

Before it writes anything, the build checks that every runtime profile listed in the manifest can decode every referenced asset. kagari check reports the same diagnostics without writing output, and the language server shows them in the editor as you type. The build also validates every shader the program uses against its role, and fails on a shader that does not validate.

The runtime. The desktop runtime, kagari-desktop-runtime, plays a built game on Windows, macOS and Linux. It loads game.kagari from the game directory (by default, the directory of the executable, so a shipped game puts the executable next to game.kagari and the archives), verifies the program before running any of it, runs fn main once to get the description of the game system, and shows the start page. Settings, meta and save slots go to a data directory outside the game directory. The runtime implements the runtime profile native_v0: the whole standard library except video assets. A headless mode runs the same game without a window, driven by a script, for automated tests.

What each part of a game is written in

A game has three parts, and each is written in one kind of function:

PartKindContains
The storypushdown fnScenes (#[scene]), instant procedures, story variables (static mut); the steps where the player reads and chooses
The presentationconst fnRender functions that change the screen at each step, staging helpers such as character recipes, text effects, constants
The game systemfnfn main, the title page, menus, save and load pages, settings, the backlog, the game view: everything around the story

Types, constants, static handles for settings and meta, and asset constants are ordinary items shared by all three parts. A module may mix kinds freely; the kind belongs to the function, not to the file.

Game system · fn main, title, menus, save and load, settings, game view Story · pushdown fn scenes, steps, choices, story variables Presentation · const fn renders, staging helpers, text effects, constants starts an entry scene nav::start finite arguments Narrative handle input, lines, saves ending value write-only meta picks a render for each step × renders see only the screen; the story never reads it may call const fn allowed never

The call rules fix which arrows exist:

  • fn may call fn and const fn. It cannot call a pushdown fn and cannot read a story variable.
  • pushdown fn may call pushdown fn and const fn, never fn. Every value it holds at run time has a finite type.
  • const fn may call only const fn. It cannot read story variables, settings, meta or anything else outside its arguments.

The rules hold through function pointers and closures too, because their types carry the kind.

Two nested machines

At run time a game is two machines, one inside the other.

  • The outer system machine keeps a stack of modes: a root page such as the title or a gallery, or the game mode, at the base; overlays such as Config, Save or Backlog, and dialogs, pushed on top. Input goes to the top mode only.
  • The game mode holds one running story: the story machine. It is a pushdown machine: its state, σ, is the position in a scene, the call stack of the scenes and instant procedures that are running, and the story variables. With it the game mode keeps the current screen and the trace of the player’s choices.

The story’s call stack lives inside the game mode, never on the mode stack. So a save holds the running story and nothing of the menus around it; loading a save replaces the game mode instead of pushing onto the stack; and “the game is running” means exactly that the stack has a game mode. While an overlay or dialog is on top of the game mode, the story is paused.

StateLifetimeIn a save?
SettingsPersistent, globalNo
Meta: unlocks, read text, seen imagesPersistent, globalNo
Save slotsPersistentThey are the saves
The mode stack and the state of each modeUntil the game quitsNo
The running story: σ and the screenUntil the story ends or another replaces itYes, as the trace that reaches it

How the parts talk

Information flows one way through the three parts. The story decides what happens, the presentation decides how it looks, and the game system decides everything around it; no part reads back what a later part made of its output.

From the story to the presentation

Each step names its render: s!(render = roof, …). When the story reaches the step, the engine runs the render on the current screen and shows the result. The render receives only the screen, so it cannot depend on story variables, and the story cannot read the screen, because Screen is not a finite type. Presentation that depends on the story is chosen by the story, by passing a different render in each branch.

Between the game system and the story

The game system and the story meet at four points only:

PointDirectionWhat passes
Entry scenessystem to storynav::start(entry, args, on_exit) starts a story at an #[entry] scene with arguments of finite types. This is the only way into the story
The Narrative handleboth waysThe game view and game overlays get a handle to the running story. Through it they show the current line, choice and backlog, and they advance, select an arm, save or rewind. They cannot read story variables through it
Endingsstory to systemWhen the entry scene returns, its value goes to the exit handler on_exit, which must handle every possible ending
Metastory to systemThe story writes meta with Meta::write and never reads it back during the run. The system reads meta and may pass it to the next story as an entry argument

Settings never reach the story. A render that depends on a setting does not read its value; it gates an entity on the setting, and the engine applies the setting when it draws.

A run of a game

  1. The runtime loads and verifies game.kagari, runs fn main, and shows the start page given in the System record it returns.
  2. Pages, overlays and dialogs are components: plain fn that build UI. A button handler calls a navigation function such as nav::call(Config) or nav::start(…).
  3. nav::start replaces the mode stack with a new game mode running the entry scene.
  4. The story runs instantly up to its first step. The step’s render changes the screen, the game view shows the line, and the story waits.
  5. Player input resumes the story: advancing past an s! step, or selecting an arm of a choice!. The story runs to the next step. The engine autosaves at each choice.
  6. When the entry scene returns, the exit handler receives the ending and navigates, for example to the credits.

The compiler’s analyses cover steps 3 to 6 for every possible run: every entry scene, every value of its arguments and every sequence of choices.

Last updated Oct 29, 2026