What lusine gives you¶
A game made with lusine is mostly Trait: its data and the laws that change it. Almost everything else comes with lusine. This page lists what you get, and then what your game writes itself.
What comes with lusine¶
The engine. A small Vulkan engine that runs your game on the graphics card:
- A window and a frame loop, with two separate rate limits: how many simulation steps a second, and how many frames a second are drawn. The window shows the last finished step, and where frames outnumber steps it draws smoothly between the last two. Space pauses, the right arrow steps once while paused, F1 opens the engine's settings panel, Escape quits.
- Viewports: rectangles of the window, each showing a grid of colours from your program (any number of them, the same grid in several), each with its own pan and zoom, fixed, aimed by your C++ every step, or read from values your simulation writes. A viewport showing a rendered picture can draw it at a fraction of its pixels (its render scale). Your game can replace the list of viewports while it runs.
- Keys and the mouse: each key and each pointer event over a viewport is a row of
lusine.input.keysorlusine.input.pointers, appended before the step it belongs to, on the desktop and in the browser alike: a key by its place on the keyboard, a pointer by its viewport and where in it, from 0 to 1.lusine.keyboard.heldkeeps which keys are down. A game that reads the keys takes Space and the right arrow from the engine. - Panels, drawn with ImGui over the view. You describe a panel as a few rows (a line of text, a value with its unit, a plot over the last steps, a series of a chart), and the engine reads the values and draws them. You can also draw your own ImGui panels.
- Sound: if your program writes
lusine.sound's block of samples each step, the engine plays it. The library synthesises it on the graphics card from your own tables, every sound as partials in one pool: a ringing partial for each body a contact strikes (its pitch from its material's stiffness and density), a rumble for each rolling contact, notes of instruments, and an ambience under them (a park of wind, birds and distant voices). Your game keeps only its choices: its materials, how loud each sound is (rows oflusine.sound.gain), and the notes it plays. - Instruments, in packs: an instrument is rows, a name and its partials (a ratio to the note's pitch, a level, an envelope, a detune, a waveform), and a note of it is a row your game appends: its pitch, velocity and name. A pack is a folder of such rows with a
pack.toml(its name, version and what it depends on); lusine ships the base pack (library/packs/lusine.base/: the interface's clicks and chirps,lusine.base.ui.clickand the rest, and an organ,lusine.base.organ, held until your game releases the note) and the chip pack (library/packs/lusine.chip/: an 8-bit console's pulse leads, triangle bass and noise kit on the sine, pulse, triangle, saw and NES-noise waveforms), and a pack of your own, or one you are given, joins your game through yourtrait.toml. A pack holds rows only, and names each row by a key its own name begins (chiptune.lead), never by a number it chose; the conventions are in the header oflibrary/lusine/sound/sound.trait. - Tuning: a
tuning.jsonbeside the executable (and the player's ownuser/tuning.json) picks the fastest of the ways the compiler can run a step on a given graphics card. Every way gives the same results. - A command line every game takes: the board's size, a random starting fill, its seed, a number of steps, a run with no window (
--headless),--help, and your game's own options beside them. - The control channel (
--control): a running game that tools can talk to, to compile Trait into it, call its procedures and read its values.
The core, the part of the engine that runs your compiled program. It makes the buffers, uploads the starting state, launches each step, keeps several steps in flight, and reads back what you ask for one step later. It takes your C++'s writes (a value stored, a row added or removed) and applies them in order at the start of the next step, and loads a starting state. It collects the errors a step raises, and a run that raised one ends with a failing exit code. Your C++ never handles a GPU buffer.
The library, more than a hundred kernels and the tables they read, written in Trait and checked: physical laws (drag, buoyancy, diffusion, elasticity, the atmosphere), motion and collisions, cameras, rendering by tracing rays (shapes, shading, shadows, the sky), sound, the sun, moon and stars, finance (returns, bonds, options, order matching), statistics, random draws that repeat exactly, and units, constants and materials. Your source calls them by name. The library lists them all.
The web host, which runs the same compiled program in a web browser on WebGPU, with a canvas, a pause button, sound and your panels.
The build tooling. One CMake call, lusine_game_executable, runs the Trait compiler when you build, compiles the shaders and makes your executable, its test and its shipping zip. pack.mjs turns the same source into a web page. init.py writes a new game that builds and runs.
Help for Claude Code. lusine's skills (building and running programs, driving a running game, writing a game's lines) and trait's (reading and writing Trait, writing a kernel, tuning) come with lusine, and Claude Code reads them from your game's checkout. lusine's MCP server gives Claude Code a running game as tools: start or connect to it, compile Trait into it, call its procedures and read the answers.
What your game writes¶
Its Trait source: what its world is made of, its rules, and what is drawn. This is where the game is.
A short C++ main that hands the run to lusine. This is the one lusine's template writes for a new game:
#include <utility>
#include "core/runGame.h"
#include "scenes/frameLoop.h"
int main(int argc, char** argv)
{
lusine::core::CoreRun run;
run.spirv = LUSINE_GAME_SPV; // the unit's SPIR-V in the game directory beside the executable, built and shipped
run.defaults = { { { 160, 90 } }, { { "mygame.board.on", 0.5 } } };
FrameLoopGame game{};
game.name = "mygame";
game.latticeView = "mygame.board.colour"; // one viewport over the whole window (scenes/viewports.h)
game.simulateRate = 60.0;
return lusine::core::RunGame(argc, argv, game, std::move(run));
}
It says where the compiled shaders are, the board's starting size and fill, which colours to show and how fast to step. A desktop game shows what else it can set.
What only your game knows, where it needs it, in that same main:
- values to read back each step, and what to do with them (show them, log them, check them);
- its own checks, which decide whether a run passed;
- its own command-line options.
For the web, no C++ at all: the options you give pack.mjs (the size, the colours shown, the starting pattern, the speed, the panels) are the whole setup. A web game shows how.