lusine and Trait¶
lusine is an engine for games and simulations that run on the graphics card. You describe your world in Trait, a small language for data and the laws that change it, and lusine runs it: as a program on your computer, or as a page in a web browser.
Games being made with it include Life (Conway's Game of Life), Boules (a game of pétanque, its balls drawn by tracing rays of light), Bourse (a stock market) and fallingsand (a world of falling sand, water and heat).
What Trait looks like¶
This is Life, the whole program:
namespace life;
lusine.version version = { { number = 1 } };
struct cell { bool alive; };
lattice<cell, 2> board(spacing = 1 m, boundary = periodic);
struct { integer population; } totals;
bool generation(bool alive)
{
let n = count(previous(neighbours(8).alive)) where alive;
if (previous(alive)) return n >= 2 && n <= 3;
return n == 3;
}
integer census(table<bool> alive) { return count(alive) where alive; }
void main()
{
board.alive = generation(board.alive);
totals.population = census(board.alive);
}
The version line is Life's own version number, which every namespace declares exactly once (Trait refuses a namespace with none or two; lusine.version is its type, from lusine's library). It only grows. The language means it to order a save's renames and upgrades; lusine does not read it yet, and a save whose contents a newer build changed is refused, not upgraded. A cell is alive or not. The board is a grid of cells whose edges wrap around. generation is the rule for one cell: count the eight neighbours that were alive when the step began, then live on with two or three, or be born with exactly three. main says what each step does: every cell takes its next generation, and the population is counted.
You state the rule once, for one cell. You never write a loop over the board, a GPU buffer or a shader: the compiler works out how to run it on the graphics card, including that every cell must read its neighbours as they were before any of them changed.
How it fits together¶
flowchart LR
game["Your game's Trait source"] --> compiler["The Trait compiler"]
library["lusine's library: units, materials, physics"] --> compiler
compiler --> unit["A unit: the program's tables and its GPU code"]
unit --> native["Your game's executable, on lusine's engine (Vulkan)"]
unit --> web["A web page, on lusine's browser host (WebGPU)"]
A game is its Trait source and its data, plus, for the desktop, a short C++ main that hands the run to lusine's engine (the template gives you a working one; the web needs none). What lusine gives you lists what the engine does for you and what is left to your game. When you build the game, the Trait compiler reads your source together with lusine's library and writes a unit: everything the program needs, in a form the engine can run. lusine's engine runs the unit on your graphics card, and the same source can also be packed as a web page that runs in any browser with WebGPU.
The library is where the physics lives: units like metres and seconds, materials, and more than a hundred small laws called kernels (drag, buoyancy, the sun's position, light and shadow, option prices), each written once and checked. Your game calls them by name.
Where to go next¶
- What lusine gives you: what comes with lusine, and what your game writes itself.
- Your first game: start a game from lusine's template, build it, run it, then change its rule.
- A desktop game and a web game: the two kinds of game, and everything each can set.
- Building a game: what a game's build gets from lusine, and how to ship it.
- The Trait language: how to write Trait, from its declarations, types and units to what the compiler refuses.
- The library: the kernels you can call, layer by layer, each with a page that checks it.
- Translating a game: a game's words in more than one language.
- Glossary: the words lusine uses, each with one meaning.