Game Loop & Lifecycle
Implementation status: Game is a real platform loop (SDL3, or the windowless headless and terminal platforms) supporting both fixed and variable timestep, with a dedicated Emscripten frame body for the browser target. GameTime, GraphicsDeviceManager, GameWindow, GameComponent and DrawableGameComponent are all present and functional. The core Framework namespace this page documents is one of CNA's strongest areas. Since this snapshot the game clock is XNA's own: with a fixed time step (the default) the first Update runs with an elapsed time of zero.
Overview
Game lives in the Microsoft::Xna::Framework namespace. It is the central class of every CNA application — it owns the window, the graphics device, the content manager, and the main loop. You write a game by subclassing Game and overriding the lifecycle virtual methods. The entry point creates your subclass and calls Run(), which hands control to the framework until the window closes.
The framework guarantees the order of lifecycle calls, pumps the selected platform's events, manages fixed- or variable-timestep scheduling, and forwards each frame to your overrides. You do not write your own event loop. Property accessors in CNA are getXProperty() / setXProperty(value), so Game's window, content manager and graphics device are reached with getWindowProperty(), getContentProperty() and getGraphicsDeviceProperty(); the lifecycle methods (Initialize, LoadContent, Update, Draw, UnloadContent) are protected virtual.
// entry point
int main() {
MyGame game;
game.Run();
return 0;
}
Lifecycle diagram
The following sequence shows the order in which the framework calls your overrides. Methods marked once are called a single time; methods marked every frame are called repeatedly until the game exits.
- Constructor — create
GraphicsDeviceManager, configure window title and back-buffer size (once) - ↓
- Initialize() — non-content game setup; register components and anything
LoadContent()depends on before callingGame::Initialize(), which initialises the components and then runsLoadContent()(once) - ↓
- LoadContent() — load textures, sounds, fonts; create GPU resources (once)
- ↓
- Update(GameTime) — game logic, input, physics (every frame)
- ↓
- Draw(GameTime) — rendering (every frame)
- ↓
- loop back to Update until Exit() is called
- ↓
- OnExiting — the
Exitingevent is raised as the loop ends (once) - ↓
- Dispose(bool) — teardown, but only when your code calls
Dispose()(Run()does not, and the destructor'sDispose(false)does not reachUnloadContent()): components and theContentManagerare disposed first, then the registered graphics device service, whose device-disposing event invokes UnloadContent() (once, and only when a graphics device service such asGraphicsDeviceManageris registered)
Two consequences are easy to miss. Exiting fires before UnloadContent(), and by the time UnloadContent() runs (when it runs at all) the ContentManager has already been disposed, so it is a place to release resources you own, not to touch content. And with a fixed time step (the default), on the very first Update(), ElapsedGameTime is zero (see GameTime).
On the web, Game may live on the stack. Older notes required a heap-allocated Game under Emscripten. That requirement is obsolete: Game::Run() now blocks on the caller's stack (it awaits requestAnimationFrame through Asyncify), so the MyGame game; game.Run(); entry point above is valid on every target. Application executables built by CNA enable Asyncify automatically; an external project that links CNA itself must link the CNA::EmscriptenAsyncify target.
Constructor
The constructor is where you create the GraphicsDeviceManager and perform any pre-initialization configuration. In CNA the base Game constructor has already built the GraphicsDevice (the renderer and, for a windowed renderer, the window) before your constructor body runs, but the manager's preferences (size, profile, presentation mode) are only applied later, when the game initialises just before Initialize(), so do not rely on them being in effect in the constructor. Set window dimensions and title through the manager and the Window property respectively.
class MyGame : public Game {
GraphicsDeviceManager graphics_;
public:
MyGame()
: graphics_(this) // explicit GraphicsDeviceManager(Game*)
{
graphics_.setPreferredBackBufferWidthProperty(1280);
graphics_.setPreferredBackBufferHeightProperty(720);
graphics_.setSynchronizeWithVerticalRetraceProperty(true);
getWindowProperty().setTitleProperty("My CNA Game");
}
};
The default project GraphicsProfile is Reach, and the Reach limits (one render target, no occlusion queries, 16-bit indices, no float render targets) are enforced on every renderer. If your game needs HiDef features, ask for them here: graphics_.setGraphicsProfileProperty(GraphicsProfile::HiDef);. The default presentation mode is Letterbox: the Viewport is the back-buffer size whatever shape the window has.
Initialize()
Initialize() is called once after the graphics device has been created. Content loading starts when your override calls Game::Initialize(), which initialises the registered components and then calls LoadContent(); anything LoadContent() needs, and every component you add, must therefore be in place before that call (a component added after it is updated and drawn but never has its Initialize() called). Use it for non-content setup: registering GameComponent objects, configuring services, and initializing game state that does not depend on loaded assets. Always call Game::Initialize() — it initialises all registered components.
void Initialize() override {
// register components before calling base
getComponentsProperty().Add(std::make_shared<MyComponent>(*this));
Game::Initialize(); // calls Initialize() on all components
// post-init game state
playerPosition = Vector2(100.0f, 200.0f);
}
Components.Add(std::shared_ptr<IGameComponent>) is a CNA extension that keeps the component alive for you; the XNA form takes a raw IGameComponent* and you must keep the object alive yourself. Component lists are mutex-guarded, so a loading-screen thread may add components.
LoadContent()
LoadContent() is called once, from inside Game::Initialize() (after it has initialised the components), so it runs at the point where your Initialize() override calls the base method. The ContentManager is fully operational here and the graphics device is ready to accept resource uploads. Load all textures, sounds, sprite fonts, and effects in this override. Create GPU resources such as vertex buffers, render targets, and effect instances here as well.
void LoadContent() override {
spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
// Load<T> returns by value (a failed load throws ContentLoadException);
// Texture2D/SpriteFont are held in std::optional members because SpriteFont has no default constructor
playerTex_.emplace(getContentProperty().Load<Texture2D>("textures/player"));
font_.emplace(getContentProperty().Load<SpriteFont>("fonts/default"));
}
A stock Game already registered the built-in XNB readers in its constructor, so .xnb, .cnb and loose assets resolve with no setup.
Update(GameTime)
Update() is called once per logical tick. All game logic belongs here: reading input, advancing physics, running AI, updating animations, and triggering audio. The GameTime argument carries timing information that lets you write frame-rate-independent code. Call Exit() from inside Update() to request a clean shutdown.
void Update(GameTime& gameTime) override {
auto kb = Keyboard::GetState();
if (kb.IsKeyDown(Keys::Escape))
Exit();
float dt = static_cast<float>(gameTime.getElapsedGameTimeProperty().getTotalSecondsProperty());
position_.X += speed * dt;
Game::Update(gameTime); // updates registered components, then pumps FrameworkDispatcher
}
Draw(GameTime)
Draw() is called once per visual frame, immediately after Update(). With fixed timestep enabled, a frame that is running behind runs extra Update calls to catch up (the accumulated time is clamped to 500 ms) and still draws once; Draw is skipped only when SuppressDraw() or Exit() was called or BeginDraw() returns false, and it is never called more than once between two Update calls. Begin every Draw() with getGraphicsDeviceProperty().Clear(...) to erase the previous frame. Finish with the swap that the framework handles automatically — you do not call a present or swap-buffers function yourself.
void Draw(const GameTime& gameTime) override {
getGraphicsDeviceProperty().Clear(Color::CornflowerBlue);
spriteBatch_->Begin();
spriteBatch_->Draw(*playerTex_, position_, Color::White);
spriteBatch_->DrawString(*font_, "Hello CNA", Vector2(8, 8), Color::White);
spriteBatch_->End();
Game::Draw(gameTime); // forwards to DrawableGameComponents
}
UnloadContent()
UnloadContent() is called once, from the graphics device service's device-disposing event, when Game::Dispose() is called explicitly (or the registered graphics device service is disposed) — that is, after the main loop has exited and after OnExiting. Run() does not call Dispose(), the destructor's Dispose(false) never reaches UnloadContent(), and nothing is called when no graphics device service such as GraphicsDeviceManager is registered, so a game that only calls Run() and lets the object go out of scope never sees it; call game.Dispose() after Run() if you rely on it. The default implementation is empty: it does not unload content. The ContentManager is disposed by Game::Dispose(bool) just before the device, so every asset loaded through it is released for you and is already gone when UnloadContent() runs. Override it only to release GPU resources you created and own yourself.
void UnloadContent() override {
// release manually created resources (the ContentManager has already been disposed)
customRenderTarget_.reset();
Game::UnloadContent(); // empty in the default implementation
}
Dispose(bool)
Dispose(bool disposing) provides a deterministic RAII-style cleanup hook. When disposing is true the call originates from an explicit teardown; when false it originates from a finaliser. In CNA the distinction still decides which cleanup runs (see below): only the true path disposes components, the content manager and the device service. Release any resources not covered by UnloadContent() here, then call the base.
void Dispose(bool disposing) override {
if (disposing) {
// deterministic cleanup
audioEngine.reset();
}
Game::Dispose(disposing);
}
What the two paths do in CNA
In CNA the disposing flag decides almost everything. game.Dispose() calls Dispose(true): every IDisposable component is disposed (a DrawableGameComponent runs its UnloadContent() there), then the ContentManager, then the registered graphics device service, whose DeviceDisposing event calls the game's UnloadContent(); the Disposed event follows. The destructor calls Dispose(false), which only marks the game disposed — and since every scope exit and delete takes that path, a game that never calls Dispose() skips all of the above. An override should therefore do its explicit cleanup under if (disposing) and always call Game::Dispose(disposing); in CNA the Disposed event is raised by the public Dispose() itself, even when an override skips the base. Repeated, re-entrant and throwing disposal are covered on Exit, Exiting, Dispose and destruction.
GameTime
GameTime is passed to every Update() and Draw() call. It carries three pieces of timing information.
| Member | Type | Description |
|---|---|---|
getElapsedGameTimeProperty() |
TimeSpan |
Time since the last Update() call. Call .getTotalSecondsProperty() to get a double suitable for delta-time movement calculations. With a fixed time step, on the first Update() it is zero (the XNA clock; measured on the real runtime), so a simulation seeded from dt does not jump on frame one; with a variable time step the first Update() receives the time measured since the loop started. |
getTotalGameTimeProperty() |
TimeSpan |
Accumulated time since the game started running. It is the time before the current step, so it advances after Update() returns, in both fixed and variable modes. Useful for time-based animations and shaders. |
getIsRunningSlowlyProperty() |
bool |
Set to true by the framework once the fixed-timestep loop's accumulated catch-up lag reaches five extra Update() steps: a single 50 ms hitch (three updates) never sets it, while a stall of about 100 ms or more does at once (see How IsRunningSlowly is decided). Useful for disabling expensive non-essential work during a slowdown. |
TimeSpan helper methods:
.getTotalSecondsProperty()— elapsed time as adoublein seconds (most common for delta-time).getTotalMillisecondsProperty()— elapsed time in milliseconds.getTotalMinutesProperty()— elapsed time in minutes
There is no GameTime::ElapsedSeconds() helper; go through getElapsedGameTimeProperty().getTotalSecondsProperty().
How IsRunningSlowly is decided
getIsRunningSlowlyProperty() is a smoothed signal, not a per-frame one. After each fixed-step tick, CNA adds the tick's extra update steps (steps minus one) to a lag counter; the flag turns on when the counter reaches five and turns off only once it is back at zero, and every tick with a single step lowers the counter by one. So one 50 ms stutter (three updates) never sets it, five consecutive two-update ticks do, and a single stall of about 100 ms or more (six or more updates in one tick) sets it at once and keeps it set for about as many normal ticks afterwards. The flag is written after the tick's updates, so the updates that caused the lag do not see it; that tick's Draw does. Variable timestep never changes it, and the browser build always reports false. The worked traces and the comparison with XNA 4.0's different rule are on GameTime and the timestep.
GraphicsDeviceManager
GraphicsDeviceManager is created in the Game constructor and configures the graphics device and swap chain. After construction you can adjust its properties before Run() is called. Call ApplyChanges() at runtime if you need to change resolution or toggle full-screen while the game is running.
| Property | Type | Description |
|---|---|---|
get/setPreferredBackBufferWidthProperty |
int |
Desired back-buffer width in pixels |
get/setPreferredBackBufferHeightProperty |
int |
Desired back-buffer height in pixels |
get/setIsFullScreenProperty |
bool |
Toggles full-screen mode; call ApplyChanges() after setting (or call ToggleFullScreen()) |
get/setPreferMultiSamplingProperty |
bool |
Enables multisample anti-aliasing (MSAA) when true, on renderers that support it |
get/setSynchronizeWithVerticalRetraceProperty |
bool |
Enables VSync; set to false for uncapped frame rate |
get/setGraphicsProfileProperty |
GraphicsProfile |
Reach (default) or HiDef; the profile's limits are enforced on every renderer |
get/setPreferredBackBufferFormatProperty, get/setPreferredDepthStencilFormatProperty |
SurfaceFormat, DepthFormat |
Back-buffer and depth/stencil format preferences |
get/setPreferredPresentationModeProperty CNAEXT |
PresentationMode |
Letterbox (default), Overscan, Stretch or native size |
getGraphicsDeviceProperty() |
GraphicsDevice* |
The underlying graphics device (from IGraphicsDeviceService); valid after Initialize(). In a Game subclass prefer getGraphicsDeviceProperty() on the game, which returns a reference. |
// Toggle full-screen at runtime
graphics_.setIsFullScreenProperty(!graphics_.getIsFullScreenProperty());
graphics_.ApplyChanges();
GameWindow
GameWindow is accessed through game.getWindowProperty(). It represents the OS window and exposes properties for the title, client area, and resize behaviour. Setting the title and resize flag in the constructor is the normal XNA pattern and works; for any renderer that needs a window, the native window is already created during Game construction, so getClientBoundsProperty() can be queried from a derived constructor, although the manager's preferred size is only applied when the game initialises, just before Initialize() runs.
| Member | Type | Description |
|---|---|---|
get/setTitleProperty |
std::string |
The title bar string shown by the OS |
getClientBoundsProperty() |
Rectangle |
The current drawable area of the window. X and Y are the client area’s desktop position where the platform reports one (0 on Wayland); Width and Height are its actual size, and in fullscreen the back buffer’s size, as after XNA’s mode switch |
get/setAllowUserResizingProperty |
bool |
When true, the user can drag the window border to resize it |
ClientSizeChanged |
event | Fired after the client area changes size; use it to rebuild render targets or recompute the projection matrix |
getCurrentOrientationProperty() |
DisplayOrientation |
Follows the surface CNA actually draws to rather than the OS window; OrientationChanged is the matching event |
FileDropEXT, TextDropEXT CNAEXT |
events | Drag-and-drop of files and text onto the window |
// In the constructor
getWindowProperty().setAllowUserResizingProperty(true);
getWindowProperty().ClientSizeChanged += [this](System::Object*, const System::EventArgs&) {
const Rectangle bounds = getWindowProperty().getClientBoundsProperty();
RebuildProjection(bounds.Width, bounds.Height);
};
GameComponent and DrawableGameComponent
CNA supports the XNA component model. Reusable subsystems can be packaged as GameComponent or DrawableGameComponent instances and added to the Game::getComponentsProperty() collection. The framework calls their lifecycle methods automatically in the correct order, in the order they were added (respecting UpdateOrder and DrawOrder sort keys).
GameComponent
The base component class. Override Initialize() and Update(GameTime). Useful for subsystems that do no rendering of their own — input managers, audio controllers, network handlers.
| Virtual method / property | Description |
|---|---|
Initialize() | Called by Game::Initialize() once the graphics device is ready (a component's constructor takes the Game&; getGameProperty() returns it) |
Update(GameTime) | Called each tick by Game::Update() |
get/setEnabledProperty | Set to false to pause Update() calls for this component |
get/setUpdateOrderProperty | Integer sort key; lower values are updated first |
DrawableGameComponent
Extends GameComponent with a Draw(GameTime) override and visibility control. Add it to Components the same way; the framework calls Draw() automatically during Game::Draw().
| Additional virtual method / property | Description |
|---|---|
LoadContent() | Called once, after Initialize(), to load component-specific assets |
Draw(GameTime) | Called each visual frame by Game::Draw() |
get/setVisibleProperty | Set to false to suppress Draw() calls without removing the component |
get/setDrawOrderProperty | Integer sort key; lower values are drawn first (back-to-front) |
Fixed vs variable timestep
CNA offers XNA's two timestep modes and, on the native loop, XNA's own clock rules (with the fixed step a zero-length first update; in both modes TotalGameTime advancing only after Update returns). It does not replicate XNA exactly: IsRunningSlowly follows a different rule, ResetElapsedTime() does nothing under the fixed step, InactiveSleepTime is stored but never applied, and the browser loop keeps its own clock (see the Emscripten caveat below and GameTime and the timestep). The default is fixed timestep.
| Property | Default | Effect |
|---|---|---|
get/setIsFixedTimeStepProperty |
true |
When true, Update() is called at the rate defined by TargetElapsedTime. The framework accumulates wall-clock time and calls Update() multiple times per render frame if the frame ran long. getIsRunningSlowlyProperty() turns true only once the accumulated catch-up lag reaches five extra steps (how it is decided). The very first Update() still has an elapsed time of zero. |
get/setTargetElapsedTimeProperty |
1/60 s | TimeSpan that sets the desired tick interval under fixed timestep. Override to target 30 Hz, 120 Hz, or any other rate. |
setIsFixedTimeStepProperty(false) |
— | Variable timestep: Update() is called exactly once per render frame; getElapsedGameTimeProperty() reflects the true wall-clock delta and varies frame to frame. Divide all movement and physics by getElapsedGameTimeProperty().getTotalSecondsProperty(). |
Emscripten caveat. The browser frame body (Game::EmscriptenMainLoopCallback) keeps its own fixed-step accumulator: it clamps a step at 250 ms, does not honour IsFixedTimeStep = false, advances TotalGameTime before Update(), and gives the first update a full step. The XNA clock rule above applies to the native Tick() path only.
Under variable timestep, never hard-code per-frame distances or velocities. Always multiply by getElapsedGameTimeProperty().getTotalSecondsProperty() to keep motion consistent regardless of frame rate.
FrameworkDispatcher::Update()
FrameworkDispatcher::Update() pumps the audio and media event queues (for example the SDL3_mixer or ALSA mixer callbacks) on the main thread, so that sound effects, streaming songs and the media player advance. The default Game::Update(GameTime&) already calls it after updating the registered components, so a normal game does nothing: just end your Update() override with Game::Update(gameTime). Call it yourself only if you override Update() without calling the base class, or in a program that has no Game at all (a tool or a test that plays audio).
void Update(GameTime& gameTime) override {
// ... your logic ...
Game::Update(gameTime); // components + FrameworkDispatcher::Update()
}
Code examples
1. Minimal Game subclass
#include <Microsoft/Xna/Framework/Game.hpp>
#include <Microsoft/Xna/Framework/GraphicsDeviceManager.hpp>
#include <Microsoft/Xna/Framework/Graphics/SpriteBatch.hpp>
#include <Microsoft/Xna/Framework/Graphics/Texture2D.hpp>
#include <Microsoft/Xna/Framework/Input/Keyboard.hpp>
#include <memory>
#include <optional>
using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;
using namespace Microsoft::Xna::Framework::Input;
class MyGame : public Game {
GraphicsDeviceManager graphics_;
std::unique_ptr<SpriteBatch> spriteBatch_;
std::optional<Texture2D> playerTex_;
public:
MyGame() : graphics_(this) {
graphics_.setPreferredBackBufferWidthProperty(1280);
graphics_.setPreferredBackBufferHeightProperty(720);
graphics_.setSynchronizeWithVerticalRetraceProperty(true);
getWindowProperty().setTitleProperty("My CNA Game");
}
protected:
void Initialize() override {
Game::Initialize();
}
void LoadContent() override {
spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
playerTex_.emplace(getContentProperty().Load<Texture2D>("textures/player"));
}
void Update(GameTime& gameTime) override {
if (Keyboard::GetState().IsKeyDown(Keys::Escape))
Exit();
Game::Update(gameTime); // components + FrameworkDispatcher::Update()
}
void Draw(const GameTime& gameTime) override {
getGraphicsDeviceProperty().Clear(Color::CornflowerBlue);
spriteBatch_->Begin();
spriteBatch_->Draw(*playerTex_, Vector2(100, 200), Color::White);
spriteBatch_->End();
Game::Draw(gameTime);
}
};
int main() {
MyGame game; // a stack-allocated Game is valid on every target, including the web
game.Run();
return 0;
}
2. Frame-rate-independent movement with ElapsedGameTime
// Member variables
Vector2 position { 0.0f, 300.0f };
const float speed = 200.0f; // pixels per second
void Update(GameTime& gameTime) override {
// getTotalSecondsProperty() converts the TimeSpan to a double
float dt = static_cast<float>(gameTime.getElapsedGameTimeProperty().getTotalSecondsProperty());
auto kb = Keyboard::GetState();
if (kb.IsKeyDown(Keys::Right)) position.X += speed * dt;
if (kb.IsKeyDown(Keys::Left)) position.X -= speed * dt;
if (kb.IsKeyDown(Keys::Down)) position.Y += speed * dt;
if (kb.IsKeyDown(Keys::Up)) position.Y -= speed * dt;
Game::Update(gameTime);
}
3. Adding a DrawableGameComponent
// HudComponent.hpp
class HudComponent : public DrawableGameComponent {
std::optional<SpriteFont> font;
std::unique_ptr<SpriteBatch> batch;
const int& score;
public:
HudComponent(Game& game, const int& score)
: DrawableGameComponent(game), score(score) {}
protected:
void LoadContent() override {
font.emplace(getGameProperty().getContentProperty().Load<SpriteFont>("fonts/hud"));
batch = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
}
public:
void Draw(const GameTime&) override {
batch->Begin();
batch->DrawString(*font,
"Score: " + std::to_string(score),
Vector2(8, 8), Color::White);
batch->End();
}
};
// In Game constructor or Initialize():
getComponentsProperty().Add(std::make_shared<HudComponent>(*this, score_));
4. Variable timestep setup
MyGame() : graphics_(this) {
graphics_.setPreferredBackBufferWidthProperty(1920);
graphics_.setPreferredBackBufferHeightProperty(1080);
// Disable fixed timestep — Update() fires once per render frame
setIsFixedTimeStepProperty(false);
// Uncap the frame rate by disabling VSync too (optional)
graphics_.setSynchronizeWithVerticalRetraceProperty(false);
}
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Device reset, disposal, adapters and format queries — Order and failure boundaries of GraphicsDevice::Reset, the manager's hooks, device-loss status, resource registration and disposal, plus CNA's adapter, format and profile queries.
- Exit, Exiting, Dispose and destruction — What each way of ending a CNA game runs: Exit versus Exiting, explicit Dispose versus destruction, the XNA 4.0 disposal order, repeated, re-entrant and throwing disposal, and component lifetimes at shutdown.
- Game components and the service container — Exact contracts of GameComponent, DrawableGameComponent, GameComponentCollection and GameServiceContainer in CNA: ordering, content loading, disposal without unregistration, events, iterators and type-keyed services.
- GameTime and the timestep: exact clock semantics — What CNA's game clock reports: GameTime protection, fixed-step catch-up with worked numbers, IsRunningSlowly hysteresis versus XNA 4.0, ResetElapsedTime, vsync versus timestep, and the browser clock.
- GameWindow, GraphicsDeviceManager and the supporting types — CNA's single GameWindow facade and its failure policy, GraphicsDeviceManager construction, CreateDevice, ApplyChanges and presentation preferences, and LaunchParameters, TitleContainer, TitleLocation and FrameworkDispatcher.
- Presentation modes, swap interval, native handles and back-buffer readback across renderers — What each renderer family does with the presentation mode, swap interval, formats and full-screen request, how window handles are borrowed, and exactly what GetBackBufferData returns.
- The Game class: contract, run modes and extension points — Exact semantics of CNA's Game base class: event types, property guards, override points, Run versus RunOneFrame versus Tick, the browser loop, reserved debug keys and the exception boundary.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-080: The Emscripten Game loop (EmscriptenMainLoopCallback) does not implement Game's loop contract — In the browser, Game ignores IsFixedTimeStep = false, SuppressDraw and ResetElapsedTime, uses FNA's clock order instead of the XNA rule the desktop loop implements, and turns a frame exception into a normal return from R
- CNA-BUG-081: Game::InactiveSleepTime is stored and validated but the loop never sleeps while the game is inactive — InactiveSleepTime is documented in Game.hpp and the C API as how long the loop sleeps while the game is inactive, but nothing in Tick, RunLoop or the browser frame reads it, so an unfocused game keeps its full frame rate
- CNA-BUG-082: GraphicsDeviceManager leaves dangling references when its lifetime is not nested inside its Game — Game never clears its cached manager and service pointers, the manager never removes its ClientSizeChanged handler, and ~GraphicsDeviceManager dereferences its Game, so a manager that dies before or after its game is a u
- CNA-BUG-244: README.md's Usage Example does not compile (nonexistent Graphics/GraphicsDeviceManager.hpp include, unqualified CornflowerBlue) and presents every frame twice — The README's game skeleton includes a nonexistent header path, writes CornflowerBlue without Color::, and calls device.Present() in Draw although EndDraw() already presents, so the copied program fails to compile and, on
- CNA-BUG-260: ExitingEventArgs is documented as the data of Game.Exiting but nothing delivers it, and this non-XNA type carries no CNAEXT marker — The header says ExitingEventArgs provides data for Game.Exiting, yet Game::Exiting is typed with plain EventArgs, nothing constructs the type, and it is not tagged CNAEXT although XNA 4.0 has no such class.
- CNA-GAP-063: A fixed-step TargetElapsedTime above 500 ms never produces an Update from Game::Tick, essentially the same ceiling XNA 4.0 has — With IsFixedTimeStep true, Game::Tick clamps the accumulator to MaxElapsedTime (500 ms) before its update loop, so a TargetElapsedTime above 500 ms is never reached: Update does not run while Draw keeps running, at about