What a game is to the engine
To Labrador, a game is a set of states plus content. There is no Game class to derive from
and no IGame to implement. Your program constructs the engine’s Application, says what it
needs before a window exists, loads the content its manifest names, and hands the engine a first
state. This is the whole of the minimal sample’s main.cpp once the comments are taken out:
options.window_class_name = L"MinimalSampleWindowClass";// ASCII only: this file gets copied into new projects, and a wide// literal's encoding depends on how the compiler was told to read the// source.options.window_title = L"Labrador - minimal sample";
// One pane, so one view's worth of recording state. The default is four// - four-player split-screen, the widest layout the engine has a client// for - and every view above what a frame draws is a deferred context// and a dynamic vertex buffer created at startup and never used.// A game that fans out says so here; this one does not.options.view_capacity = 1;
app.initialize(instance, show_command);
// Everything this sample draws, named in content/manifest.json. A game// with its own kinds of asset - levels, dialogue, whatever it has -// teaches them to app.resource_loader() before this line.//// Beside the executable, wherever the executable was started from:// a relative path here is relative to the game, not to the working// directory (Application::load_manifest), and the build copies the// content there.app.load_manifest("./manifest.json");
return app.run(std::make_unique<HelloState>(&app));samples/minimal/main.cpp, lines 23–51 at 862e08b
run returns when the window closes. Everything between those lines (the window, the graphics
device, the input devices, the frame loop) is the engine’s.
Three kinds of type
Section titled “Three kinds of type”One rule decides what form each idea in the engine takes (Structural types):
| Where | Form | Examples |
|---|---|---|
| The engine calls code it has never seen | An interface you implement | State, GameObject, CollisionObject |
| The engine provides machinery you drive | A concrete class you use | Application, Scene, the widgets, the resource tables |
| The idea is your game’s policy | No type at all | There is no Player, Level, Match or Team |
The third row matters as much as the first two. The parts of “a player” that every game needs
already exist as an input slot and a view, and everything else about one belongs to your game.
A game’s level is a class of yours that owns a Scene and adds the rules.
The interfaces are small
Section titled “The interfaces are small”State is a screen or mode: a title screen, a match, a pause menu.
The engine calls its init() once, then update(dt) and draw(renderer) every frame, and tells
it when something covers it. States and the stack covers the rest.
GameObject is anything a scene updates and draws. It has
three obligations:
virtual void update(float dt) = 0; // change things hereCollisionObject is a GameObject that also has a shape, a collision layer and mask, a tag the
engine never interprets, and an on_contact callback. Its header,
engine/collision/collision_object.h, has the details.
How big an object is, is your choice
Section titled “How big an object is, is your choice”What stands behind those interfaces is up to you. A game can register one object per entity, with as deep a class hierarchy as its author likes, or one object that stands for thousands of values updated in a tight loop. The engine treats both the same: interface granularity is the dial you turn for performance.
LineSweeper turns it both ways in the same scene. Its board, its particle field and its game-over
banner are one object each, and the particle field is ten thousand particles in one flat array,
which costs three virtual calls a frame however many are alive. Beside them are two ordinary
labels. All five are added to one scene in the play state’s init():
this->board_ = this->scene_->add( std::make_unique<BoardView>(&this->world_, resources));
// AFTER THE BOARD, AND THAT IS THE WHOLE DEPTH SYSTEM. Every draw in// this sample is at layer_depth 0, so the order the scene was filled// in is the order the quads are submitted in and sparks are drawn over// the stack they came out of.//// It is handed the same const World* the board holds, and a pointer// to what the latest tick() returned. The state never tells it a line// was cleared: it works that out by keeping last frame's match and// comparing, which is a thing only a 276-byte trivially copyable// value makes cheap enough to do every frame - and the second pointer// is the one fact that comparison cannot recover, the piece as it// locked (particles.h).this->particles_ = this->scene_->add(std::make_unique<ParticleField>( &this->world_, &this->last_tick_, resources->resolve_texture(block_texture_name)));
// LAST, AND OVER THE SPARKS. The top-out banner appears on the exact// frame the field throws its largest burst, so the one screen a player// has to read is the one the effect would bury.this->banner_ = this->scene_->add( std::make_unique<TopOutBanner>(&this->world_, resources));samples/linesweeper/states/play_state.cpp, lines 108–131 at 862e08b
Each of those objects holds a pointer to the match, world_, and reads it. None of them can
change it: the match is a value the play state owns, and the presentation code that draws it
does not include the code that steps it.
Ownership is explicit
Section titled “Ownership is explicit”There is no garbage collector and no object graph kept alive behind your back. Every allocation has exactly one owner (T11), and the API says which side owns what:
- The application owns the services.
app.renderer(),app.keyboard(),app.render_resources()and the rest return pointers you borrow. They are created once, ininitialize(), and are never replaced, so a state may hold one for its whole life. - The scene owns what you add to it.
Scene::addtakes astd::unique_ptrand returns a plain pointer of the object’s own type, for the things your game still needs to speak to. The minimal sample keeps the greeting this way, to move it each frame. - States are owned by the stack. You construct one with
std::make_uniqueand hand it over; the stack destroys it when it pops or is replaced.
This is not a promise that Labrador avoids the heap or virtual calls. The interfaces above are virtual, and containers own heap memory. The rule is about ownership, not about allocation.
What the engine provides, and what it leaves to you
Section titled “What the engine provides, and what it leaves to you”| The engine provides | Your game provides |
|---|---|
| The window, the device, the frame loop and a fixed time step | What happens each step |
| A scene that updates, collides, culls and draws what you add | The objects, and what a contact means |
| Views and cameras, and drawing them in parallel | Where the views go and what the cameras follow |
| Keyboard, mouse and four gamepads, with press edges and deadzones | What each key and button does |
| A widget set with focus and directional navigation | The menus |
| A manifest of named content, resolved to handles at load | The content |
| The state stack | The states |
The engine routes on data it does not interpret: collision layers and masks, tags, names. Its headers contain no game nouns. If a file would have to change for your game to work, it is game code, and it belongs in your project rather than in the engine (The boundary).
Next: States and the stack.
Development documentation (unreleased). Built from Labrador 862e08b of 2026-10-08.