Syntax
Source Files and Modules
A game is a tree of modules, as in Rust. Each module is a source file, an inline mod { … }
block, or a block with items in a function body. This chapter covers files, the module tree, paths,
visibility, use, the standard library and its prelude, and assets.
.kis and .kix
| Extension | Contains |
|---|---|
.kis | The core language |
.kix | The core language plus forms: s!(…), choice!{…}, t![…] and the UI forms such as button!{…} |
Forms are fixed syntax that stands for ordinary expressions, so everything written in a .kix
file can also be written, more verbosely, in a .kis file. They are not macros: Kagari has no
macros, and the set of forms is fixed by the language. A form in a .kis file is a syntax error
(E0001).
The extension decides the syntax only. It never decides what code may do: that is the job of
function kinds. Any kind of function may appear in either file. By
convention the story and UI are written in .kix files, and code without forms (types, constants,
helpers, renders) in .kis files.
The root file
The compiler starts from one root file: project.entry in kagari.toml, by default
src/main.kis (see Projects). It must be a .kis or .kix file
(E1001). The root file is the module crate, and it must
declare fn main(), which describes the game system
(E7001). Every other file is reached through mod declarations;
a file that no mod declares is not part of the game.
All files of a game are compiled together into one .kagari file.
Modules
// src/main.kix: the root modulemod characters; // a file modulemod story;mod ui;mod helpers { // an inline module pub const fn twice(x: u32) -> u32 { x * 2 }}
use characters::{HANAKO, AZUSA};mod name;declares a file module. Its file is looked up as described below.mod name { … }declares an inline module with the items between the braces.- A function body or block that contains items is a nameless block scope; see
Items in blocks. A file module cannot be declared in a block
(
E1005). - Items may be used before their declaration.
Where the file of a module is
For mod name; the compiler looks in the child directory of the declaring module:
- in the root file, or in a file named
mod.kisormod.kix, the child directory is the directory of that file; - in any other file
foo.kisorfoo.kix, it is the directoryfoo/next to the file; - inside an inline module
mod a { … }, the name of each enclosing inline module is appended:a/.
In the child directory, these four candidates are tried: name.kis, name.kix, name/mod.kis
and name/mod.kix. Exactly one must exist. None is
E1002, more than one is
E1003, and a file that is already loaded as another module is
E1004.
Here story.kix declares mod day1;, so its child directory is src/story/. ui/mod.kis is the
file of ui itself, so its child directory is src/ui/.
Paths
A path names an item: segments separated by ::, such as std::screen::size or
Route::Hanako. A path may start with one of these keywords:
| Start | Refers to |
|---|---|
crate | the root module of the game |
self | the current module |
super | the parent module; super::super goes up two levels. Going above the root is E1016 |
Self | the type of the current impl (see Methods) |
A path cannot start with ::. Generic arguments are written in types as Vec<u8> and in
expressions with the turbofish Vec::<u8>::new().
The first segment of a plain path is looked up in this order:
- the enclosing block scopes, innermost first;
- the items and imports of the current module;
std, the standard library, unless the module defines or imports its ownstd;- the prelude;
- the primitive types
bool,Intand the Rust integer types.
The following segments are looked up in the module, enum or type that the path names so far:
items of a module, variants of an enum, and associated functions and constants of a type. A name
that is not found is E1016.
Namespaces
Each module has two namespaces:
| Namespace | Holds |
|---|---|
| types | modules, structs, enums, type aliases, traits |
| values | functions, constants, statics, and the constructors of tuple and unit structs |
A name may be used once in each namespace. A second definition in the same namespace is
E1014. Because the namespaces are separate, a constant and a
module may share a name: AZUSA is an entity id and AZUSA::CLOSE_EYE_SMILE one of her images.
pub const AZUSA: EntityId = EntityId(217);
pub mod AZUSA { pub const CLOSE_EYE_SMILE: ImageAsset = asset("azusa/close_eye_smile.png");}Visibility
An item is private unless it is marked pub. A private item is visible in the module that
defines it and in all modules nested in that module. Naming a private item from anywhere else is
E1017. There are no restricted forms such as pub(crate).
- Associated functions and constants of an
implfollow the same rule: they are private unless markedpub. - Enum variants are as visible as their enum.
- Struct fields may be marked
pub, as in the standard library. The compiler does not check field visibility yet: every field can be read and written wherever the struct is visible.
use declarations
use brings names into the current module or block:
| Form | Effect |
|---|---|
use std::screen::size; | imports size |
use characters::{HANAKO, AZUSA}; | imports several names; groups may nest |
use crate::widen as w; | imports under another name |
use crate::widen as _; | imports without binding a name |
use Route::*; | glob import: every visible name of a module, or every variant of an enum |
pub use self::text::Text; | re-exports: the name becomes part of this module for others |
- An import takes the item from every namespace that has the name:
use characters::AZUSA;imports both the constant and the module. - Use paths have no generic arguments.
- A name defined in the module, or imported by name, takes precedence over a glob import.
- When two glob imports supply different items for one name, using that name is
E1018. - An import that leads nowhere is
E1015. Importing a private item isE1017.
The standard library
The standard library is the module std. Its items are described in the
STD Reference:
| Module | Contents | STD Reference |
|---|---|---|
std | String, Vec, Range, Rat, Decimal, Option, Result; the traits Eq, Integer, RandChoose and Asset; the asset types, asset and system_font | std |
std::prelude | the names imported into every module | std::prelude |
std::time | Duration (whole milliseconds), ms, secs | std::time |
std::narrative | steps (Step, push_step, Say, Choice), rng_choose, meta fields, the Narrative handle | std::narrative |
std::narrative::text | Text, text effects, ToText, text styles | std::narrative::text |
std::screen | the screen: entities, properties, layers, Hud, camera, filters | std::screen |
std::screen::anim | tweens, transitions, motions, easings | std::screen::anim |
std::screen::audio | music and ambience channels, sounds, audio fades | std::screen::audio |
std::stage | characters, slots and side faces on top of std::screen | std::stage |
std::system | System, settings, seen sets, bindings | std::system |
std::system::nav | navigation between modes, starting and loading the story | std::system::nav |
std::system::saves | save slots and save information | std::system::saves |
std::ui | UI elements, signals, the functions behind the UI forms | std::ui |
std::ui::style | the looks of UI controls | std::ui::style |
The prelude
The prelude is imported into every module. A module’s own items and imports take precedence over it. It contains:
| From | Names |
|---|---|
std | String, Vec, Rat, Decimal, Range, Option, Result, Eq, Integer, RandChoose, ImageAsset, AudioAsset, VoiceAsset, VideoAsset, FontAsset, asset, system_font; the variants None, Some, Ok, Err |
std::time | Duration, ms, secs |
std::screen | Screen, Render, EntityId, Part, Point, Offset, Size, Anchor, Color, Plane, Visibility, point, offset, rgb, hex, BLACK, WHITE, TRANSPARENT |
std::screen::anim | Easing, Tween, Transition, Motion, tween, instant, cut, fade, rule, wipe, through, motion, jump, nod, shake, quake |
std::screen::audio | looped, sound, crossfade |
std::stage | Character, character |
std::narrative | Text, TextEffect, Say, Choice, ChoiceArm, push_step, rng_choose, Meta, meta, Narrative |
std::narrative::text | emphasis, dots, color_text, bold, pause, click_wait, mark |
std::system | nav, Setting, setting, System, Settings, Volume, volume, RootPage, Overlay, GameOverlay, Dialog |
std::ui | signal, Element |
Everything else is imported with use, for example use std::screen::size;.
Assets
An asset is a file of the game, bound to a constant:
const CG_001: ImageAsset = asset("cg/cg_001.png");const AZUSA_0072: VoiceAsset = asset("voice/azusa_0072.ogg");#[builtin] pub const fn asset<A: Asset>(path: narrative::Text) -> A;These types implement the trait Asset:
| Type | File |
|---|---|
ImageAsset | an image: PNG, JPEG or WebP |
AudioAsset | music or a sound |
VoiceAsset | a voice file, bound to a line |
VideoAsset | a movie |
FontAsset | a font |
ColorShader<A>, MixShader<A>, TextureShader<A>, MaskShader<A>, LocalMotionShader<A>, TransitionalMotionShader<A> | a WGSL shader (.wgsl) of one role, with arguments of type A; see std::screen::shader |
The type of the constant chooses A; asset::<ImageAsset>("…") names it explicitly. The path
follows these rules:
- It is a string literal (
E2028) and not empty (E1028). - It is relative to the asset directory:
[assets] dirinkagari.toml, by defaultassetsin the project directory. Both/and\separate directories. - It stays inside the asset directory: no absolute path, no drive (
C:), and no..that leaves it (E1029). - It does not enter the top-level directories
system-fontsandstd, which are reserved for system fonts and the standard library’s own assets (E1032). - The file exists when the game is compiled
(
E1027), so a missing file is a compile error, not a failure during play.
A file asset type is a finite type: its values are exactly the asset constants of the program. The story may therefore choose between assets at runtime, for example a voice line by route. Two asset values are equal when they name the same file. The shader types are not finite; renders use them.
System fonts
A font installed on the building machine can be bound without copying it into the asset directory:
const GOTHIC: FontAsset = system_font("msgothic.ttc");#[builtin] pub const fn system_font(path: narrative::Text) -> FontAsset;The path is a non-empty string literal relative to a system font directory and must stay inside it. The directories are searched in this order, and the first one that has the file wins:
| Platform | Directories |
|---|---|
| Windows | %LOCALAPPDATA%\Microsoft\Windows\Fonts, %WINDIR%\Fonts |
| macOS | ~/Library/Fonts, /Library/Fonts, /System/Library/Fonts |
| Linux and others | $XDG_DATA_HOME/fonts (default ~/.local/share/fonts), ~/.fonts, then fonts in each directory of $XDG_DATA_DIRS (default /usr/local/share:/usr/share) |
A font that is not found is E1030.
[assets] system-fonts = false in kagari.toml forbids system fonts
(E1031). The standard library ships no fonts, and
System::font, the default font, is required.
Assets in the build
kagari build packs every asset file the program uses into archives (.kiarchive) next to the
.kagari file. Each file is stored under its path relative to the asset directory, with /
separators, and goes into the archive named after the first directory of that path:
| Asset path | Archive |
|---|---|
bg/classroom.png | bg.kiarchive |
cg/cg_001.png | cg.kiarchive |
logo.png (directly in the asset directory) | data.kiarchive |
a system font msgothic.ttc | system-fonts.kiarchive, as system-fonts/msgothic.ttc |
a std shader such as std::shaders::color::BLUR | std.kiarchive, as std/shaders/color/blur.wgsl |
Grouping files by top-level directory therefore decides how the game’s data is split into
archives. See The kagari CLI for the build output.
Last updated Oct 29, 2026