Skip to content

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:

samples/minimal/main.cpp
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";
options.resolution = ScreenResolution::s_1280_720;
// 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;
Application app(std::move(options));
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));

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.

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.

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 here
virtual void draw(DrawList& draw_list) const = 0; // and only read them here
virtual mattmath::RectangleF bounds() const = 0; // where it is, for culling

CollisionObject 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.

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():

samples/linesweeper/states/play_state.cpp
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));

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.

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, in initialize(), and are never replaced, so a state may hold one for its whole life.
  • The scene owns what you add to it. Scene::add takes a std::unique_ptr and 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_unique and 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.