# 07. The game and the frame

`game.h` is the top of the library. `akgl_game_init` brings up SDL, the pools, the
registries and the audio and font engines; `akgl_game_update` is one frame — update every
actor, step the physics, draw the world.

**There is exactly one of everything.** `akgl_renderer`, `akgl_physics`, `akgl_camera` and
`akgl_gamemap` are globals pointing at `akgl_default_*` storage, so a program can swap in
its own instance by reassigning the pointer without the rest of the library knowing. That
is the whole extent of the indirection: there is no notion of two worlds at once.

## The startup order

`game.h` documents a five-step sequence, and it holds up against `src/game.c`:

```c
#include <SDL3/SDL.h>
#include <akerror.h>
#include <akstdlib.h>
#include <akgl/error.h>
#include <akgl/game.h>
#include <akgl/heap.h>
#include <akgl/physics.h>
#include <akgl/registry.h>
#include <akgl/renderer.h>
#include <akgl/staticstring.h>

/* The startup order, in the order it has to happen. */
akerr_ErrorContext AKERR_NOIGNORE *startup(void)
{
    akgl_String *engine = NULL;

    PREPARE_ERROR(errctx);
    /* 1. The three required fields. akgl_game_init refuses to run without them. */
    PASS(errctx, aksl_strncpy(akgl_game.name, sizeof(akgl_game.name),
			      "manual-demo", sizeof(akgl_game.name) - 1));
    PASS(errctx, aksl_strncpy(akgl_game.version, sizeof(akgl_game.version),
			      "1.0.0", sizeof(akgl_game.version) - 1));
    PASS(errctx, aksl_strncpy(akgl_game.uri, sizeof(akgl_game.uri),
			      "tech.starfort.manual-demo", sizeof(akgl_game.uri) - 1));

    /* 2. Bring the library up: errors, pools, registries, SDL, audio, fonts. */
    PASS(errctx, akgl_game_init());

    /* 3. Configure, before anything reads the configuration. */
    PASS(errctx, akgl_set_property("game.screenwidth", "800"));
    PASS(errctx, akgl_set_property("game.screenheight", "600"));
    PASS(errctx, akgl_set_property("physics.gravity.y", "400.0"));

    /* 4. The two subsystems that read it. This is where the window appears. */
    ATTEMPT {
	CATCH(errctx, akgl_heap_next_string(&engine));
	CATCH(errctx, akgl_string_initialize(engine, "arcade"));
	CATCH(errctx, akgl_render_2d_init(akgl_renderer));
	CATCH(errctx, akgl_physics_factory(akgl_physics, engine));
    } CLEANUP {
	IGNORE(akgl_heap_release_string(engine));
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    /* 5. Load assets, then loop on akgl_game_update. */
    SUCCEED_RETURN(errctx);
}
```

**The three required fields have no defaults.** `akgl_game.name`, `.version` and `.uri` are
each checked for a non-zero length and each raises `AKERR_NULLPOINTER` if empty. All three
are load-bearing: `.uri` becomes the window title, all three become SDL's app metadata, and
`.name`, `.uri` and `.version` are compared on savegame load. `.version` must be a valid
semver string, or `akgl_game_load` fails later rather than here.

**Steps 3 and 4 cannot be swapped.** `akgl_render_2d_init` and `akgl_physics_init_arcade`
each read their configuration once, at init, and never look again. Setting
`physics.gravity.y` after the factory call changes nothing. See
[Chapter 6](06-the-registry.md).

### What `akgl_game_init` does, in order

Verified against `src/game.c`. The order matters in three places, marked:

1. **`akgl_error_init()` — first, before anything that can raise.** Everything below reports
   through `AKGL_ERR_*` codes, and a code raised before its name is registered prints as
   "Unknown Error". See [Chapter 4](04-errors.md).
2. Stamp `akgl_game.libversion` from `AKGL_VERSION`.
3. Seed the frame clock: `gameStartTime`, `lastIterTime` and `lastFPSTime` all get
   `SDL_GetTicksNS()`.
4. Install `akgl_game_lowfps` as `lowfpsfunc`.
5. Create the state mutex. Failure is `AKGL_ERR_SDL`.
6. **Take the state lock.** Everything from here to the end runs holding it.
7. Check `name`, `version` and `uri` are non-empty.
8. `akgl_heap_init()` — zero all eight pools.
9. The eight registry initializers: actor, sprite, spritesheet, character, font, music,
   properties, actor-state-strings. **Note that `akgl_registry_init` is not called** —
   `akgl_game_init` calls the eight individually. See [Chapter 6](06-the-registry.md).
10. `SDL_SetAppMetadata` from the three fields.
11. Zero all `AKGL_MAX_CONTROL_MAPS` control maps.
12. `SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD | SDL_INIT_AUDIO)`.
13. Feed the bundled controller database to `SDL_AddGamepadMapping`, one entry at a time.
    A rejected entry is `AKGL_ERR_SDL` and aborts startup.
14. `akgl_controller_open_gamepads()`.
15. `MIX_Init()` and `MIX_CreateMixerDevice()` on the default playback device.
16. `TTF_Init()`.
17. Point `akgl_camera`, `akgl_renderer`, `akgl_physics` and `akgl_gamemap` at their default
    storage.
18. Release the state lock.

**What it does not do: create the window, choose a physics backend, or load any
configuration.** Those three read properties, so they belong to the caller, after step 3
above.

**A failure anywhere from step 6 onward returns with the state mutex held.** `game.h` says
so. On a single-threaded program that is survivable — SDL mutexes are recursive, so the
same thread can take it again — but the mutex is never made available to another thread, so
anything else that calls `akgl_game_state_lock` will spend a second retrying and then fail.
A failed `akgl_game_init` is not something to retry; report it and exit.

## One frame

`akgl_game_update(opflags)` is the whole frame, and it is short enough to state exactly:

```text
  akgl_game_state_lock()                     <- retries, does not block
  akgl_game_update_fps()                     <- counts the frame, maybe fires lowfpsfunc
  for each slot in akgl_heap_actors:
      skip if refcount == 0
      skip if OP_LAYERMASK and actor->layer != opflags->layerid
      OP_TILEMAPSCALE ? akgl_tilemap_scale_actor(...) : actor->scale = 1.0
      actor->updatefunc(actor)               <- exactly once per actor
  akgl_physics->simulate(akgl_physics, NULL)
  akgl_renderer->draw_world(akgl_renderer, NULL)
  akgl_game_state_unlock()
```

Five things follow from that, and four of them are not obvious.

**The sweep walks the pool, not the registry.** It reads `akgl_heap_actors` directly and
skips slots whose `refcount` is 0. That is why an actor released mid-frame simply stops
being visited, and why the actor registry's contents do not affect the update pass at all.

**Each actor's `updatefunc` runs exactly once.** Until 0.5.0 this loop sat inside a walk
over `AKGL_TILEMAP_MAX_LAYERS` that never compared an actor's `layer` to the layer being
walked, so every actor updated sixteen times a frame — 70 µs of work to do 4.4 µs of it.
Updating an actor is not a per-layer operation. **Drawing is**, and
`akgl_render_2d_draw_world` walks the layers itself.

**`opflags` is not forwarded.** `simulate` and `draw_world` are both passed `NULL`, so they
use their own defaults regardless of what you asked for. Setting
`AKGL_ITERATOR_OP_LAYERMASK` restricts the *update* sweep and nothing else; the whole world
still draws.

**A `NULL` `opflags` means `AKGL_ITERATOR_OP_UPDATE` alone** — every live actor once, no
layer mask, and every actor's `scale` forced to 1.0 because `AKGL_ITERATOR_OP_TILEMAPSCALE`
is not set. If your sprites are rendering at the wrong size, that flag is the first thing to
check.

**A live actor's `updatefunc` is called without a `NULL` check.** A hand-built `akgl_Actor`
that never went through `akgl_actor_initialize` crashes here rather than raising.

### There is no frame pacing

`akgl_game_update` does not sleep, does not wait for vertical blank, and does not ask SDL
to. `akgl_render_2d_init` calls `SDL_CreateWindowAndRenderer` with no flags and never calls
`SDL_SetRenderVSync`, so **the loop runs as fast as the machine will let it** and burns a
core doing it.

That is a deliberate omission rather than an oversight — pacing policy belongs to the game —
but it means the pacing is yours to add. Either call `SDL_SetRenderVSync(renderer, 1)` on
`akgl_renderer->sdl_renderer` after `akgl_render_2d_init`, or measure the frame and
`SDL_DelayNS` the remainder yourself. Physics is unaffected either way:
`akgl_physics_simulate` computes its own `dt` from `SDL_GetTicksNS` and bounds it at
`physics.max_timestep`.

### `akgl_game.fps` reads 0 for the first second

`akgl_game_update_fps` recomputes `fps` only when a **full second** has elapsed since the
last recomputation, so it is a completed-second average and not an instantaneous figure. For
the first second of the process it is 0.

0 is below the low-FPS threshold of 30. **So `lowfpsfunc` fires on every single frame until
the first second is up** — several hundred `Low FPS! 0` lines from the default handler
before the game has drawn anything worth measuring. That is expected, not a symptom.

If you replace `lowfpsfunc` with something that sheds work, guard it on `fps > 0` or it will
shed work during startup, which is the worst moment for it. The hook is also what an
embedder has to know about: `akgl_game_update_fps` installs the default if it finds the
pointer `NULL`, because a host binding its own renderer with `akgl_render_2d_bind` never ran
`akgl_game_init` and used to crash here on frame one.

A caller running its own loop rather than `akgl_game_update` has to call
`akgl_game_update_fps` itself; nothing else does.

## The state lock

`akgl_game.state` is a single `int32_t` of application-defined flags. **The library never
reads it.** The mutex guarding it, `akgl_game.statelock`, is the only thread-safety
libakgl offers, and it protects the state flags — not the pools, not the registries, not
the renderer.

`akgl_game_state_lock` **polls; it does not block.** It calls `SDL_TryLockMutex` every
`AKGL_GAME_STATE_LOCK_RETRY_MS` (100 ms) until `AKGL_GAME_STATE_LOCK_BUDGET_MS` (1000 ms) is
spent, then raises `AKGL_ERR_SDL`. The point is that a deadlock reports an error you can act
on instead of hanging the process. `SDL_GetError()` is appended for whatever it is worth,
but after a failed `SDL_TryLockMutex` it is usually stale or empty — contention is reported
by the return value, not by an error string. **The status is the signal, not the text.**

Two behaviours to plan around:

**Every failure path in `akgl_game_update` returns holding the lock.** Every step from
`akgl_game_state_lock` onward propagates with `PASS`, which returns immediately, so the
`akgl_game_state_unlock` at the bottom is never reached. Because SDL mutexes are recursive
the next frame on the same thread still gets the lock, so a single-threaded game does not
deadlock — it accumulates one unmatched lock per failed frame, and the mutex is never
released to any other thread again. **Treat a failed `akgl_game_update` as terminal.** If
you genuinely must continue, unlock once yourself before the next frame — but only when you
know the failure came from *inside* the frame rather than from the lock acquisition itself,
because unlocking a mutex this thread does not hold is undefined behaviour in SDL.

**`akgl_game_state_lock` succeeds trivially before `akgl_game_init`.** `SDL_TryLockMutex`
returns true when passed `NULL`, and `akgl_game.statelock` is `NULL` until step 5 of
startup. It locks nothing and reports success.

`akgl_game_state_unlock` has no failure path and returns `NULL`. That includes the case
that matters — unlocking a mutex this thread does not hold — which is undefined behaviour in
SDL rather than an error here.

## Iterators are a work order

There is no iterator object, and **there is no `next()`**. Traversal is SDL's —
`SDL_EnumerateProperties` over a registry — and `akgl_Iterator` is the `userdata` carried
into each callback, saying *which* entries to touch and *what* to do to each one.

```c excerpt=include/akgl/iterator.h
/** @brief Selects operations and an optional layer for actor traversal. */
typedef struct {
    uint32_t flags;     /**< Bitwise OR of the `AKGL_ITERATOR_OP_*` values below. */
    uint8_t layerid;    /**< Layer to restrict the sweep to. Read only when #AKGL_ITERATOR_OP_LAYERMASK is set. */
} akgl_Iterator;

#define AKGL_ITERATOR_OP_UPDATE                (1 << 0)             // 1    Call the actor's updatefunc
#define AKGL_ITERATOR_OP_RENDER                (1 << 1)             // 2    Call the actor's renderfunc
#define AKGL_ITERATOR_OP_RELEASE               (1 << 2)             // 4    Release the object back to its heap layer
#define AKGL_ITERATOR_OP_LAYERMASK             (1 << 3)             // 8    Skip anything whose layer != layerid
#define AKGL_ITERATOR_OP_TILEMAPSCALE          (1 << 4)             // 16   Scale actors to the tilemap; otherwise force scale 1.0
```

The operations are **independent bits, not an enum**: one sweep can update, scale and
render, and they run in that fixed order regardless of the order the bits were set in. Bits
5 through 31 are declared `AKGL_ITERATOR_OP_UNDEFINED_*` and are unused.

Passing `NULL` where an `akgl_Iterator *` is expected is fine at the top-level entry points
— `akgl_game_update`, `akgl_physics_simulate` and `akgl_render_2d_draw_world` each
substitute their own defaults — but it is `AKERR_NULLPOINTER` once inside a callback.

**`akgl_game_update` reads only two of the five bits**: `AKGL_ITERATOR_OP_LAYERMASK` and
`AKGL_ITERATOR_OP_TILEMAPSCALE`. It calls `updatefunc` unconditionally rather than checking
`AKGL_ITERATOR_OP_UPDATE`, because updating every live actor once is the whole job. The
callback `akgl_registry_iterate_actor` does check all of them, and that is the one the
render sweep goes through.

## The main loop

```c
#include <SDL3/SDL.h>
#include <akerror.h>
#include <akgl/error.h>
#include <akgl/game.h>
#include <akgl/iterator.h>

/* A frame loop. A failed akgl_game_update is terminal: it returns holding the
 * state lock, and FINISH_NORETURN is what turns that into a stack trace and a
 * truthful exit status instead of a second frame on a poisoned mutex. */
int main(void)
{
    akgl_Iterator work = {
	.flags = (AKGL_ITERATOR_OP_UPDATE | AKGL_ITERATOR_OP_TILEMAPSCALE),
	.layerid = 0
    };
    SDL_Event event;
    bool running = true;

    PREPARE_ERROR(errctx);
    ATTEMPT {
	while ( running == true ) {
	    while ( SDL_PollEvent(&event) == true ) {
		if ( event.type == SDL_EVENT_QUIT ) {
		    running = false;
		}
	    }
	    CATCH(errctx, akgl_game_update(&work));
	}
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH_NORETURN(errctx);
}
```

The `CATCH` inside the `while` is safe **only because the loop is the entire body of the
`ATTEMPT`**. `CATCH` reports failure by `break`ing, and a `break` binds to the innermost
enclosing loop, so it leaves the `while` and falls into `CLEANUP`/`PROCESS`/`FINISH` — which
is what you want. Put anything after the loop and you have to account for reaching it after
a failure. `AGENTS.md` spells the rule out, and `scripts/check_error_protocol.py` cannot see
this one.

## There is no `akgl_game_shutdown`

**Teardown is entirely the caller's.** No function in this library undoes `akgl_game_init`.
What there is, in the order it has to run:

| Step | Call | Why the order |
|---|---|---|
| 1 | `akgl_text_unloadfont` for each font you loaded | Must precede `TTF_Quit`. `text.h` documents the trap: `SDL_Quit` destroying the engine out from under a live `TTF_Font` is not a clean unload |
| 2 | `akgl_heap_release_*` for anything you still hold | `akgl_heap_release_spritesheet` destroys `SDL_Texture`s and must run on the renderer's thread, before the renderer goes |
| 3 | `akgl_audio_shutdown()` | Puts the synthesizer voices back |
| 4 | `akgl_renderer->shutdown(akgl_renderer)` | `akgl_render_2d_shutdown` |
| 5 | `TTF_Quit()`, `MIX_Quit()`, `SDL_Quit()` | SDL's own |

A process that is exiting anyway can skip most of it — the OS reclaims the pools, and SDL
reclaims what it owns — but a program that tears a *level* down and builds another one has
to get steps 1 and 2 right or it exhausts the pools. `akgl_heap_init_actor` plus
`akgl_registry_init_actor` is the cheap version of that for actors only; see
[Chapter 5](05-the-heap.md).

## Savegames are partial

`akgl_game_save` and `akgl_game_load` exist and work, and **they do not yet save your game.**
Read this before building anything on them.

What `akgl_game_save` writes:

1. The `akgl_Game` struct verbatim — metadata, timing, FPS accounting and the state flags.
2. Four name tables — actors, sprites, spritesheets, characters — each mapping a registered
   name to **the address the object had at save time**, each terminated by a zeroed name
   field and a zeroed pointer.

What it does not write: **the objects themselves.** Not one actor, sprite, sheet or
character. The name tables are the mechanism that would let a loader reconnect pointers
between objects that will sit at different addresses next run; the objects those pointers
would point at are not in the file. `akgl_game_load` correspondingly rebuilds the four
old-address-to-current-object maps and stops there. `game.h` says so on both functions.

Three further things to know:

- **`akgl_game_load` refuses a save that does not match this build**, comparing the library
  version, the game version, the game name and the game URI. Versions are compared by
  **exact** semver equality, not compatibility, because the file is a raw memory image and
  semver has no way to say "the layout did not move". A mismatch is `AKERR_API`.
- **A save containing any registered spritesheet cannot be read back.** The writer uses
  `AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH` (512) for that table and the reader used
  `AKGL_ACTOR_MAX_NAME_LENGTH` (128) for all four. That is fixed as of 0.5.0 (`TODO.md`,
  "Defects") — and what catches it is the `require_at_eof` check, which
  turns a field-width disagreement into `AKERR_IO` instead of a silent load with wrong maps.
  A reader whose widths disagree finds a run of zeros mid-entry, stops early, and would
  otherwise report success.
- **`akgl_game_load` leaks four `SDL_PropertiesID` per call.** The maps it builds are never
  destroyed.

A write error part-way through the name tables **terminates the process** rather than
returning. The entries go through `SDL_EnumerateProperties`, whose callbacks cannot report
failure upward and end in `FINISH_NORETURN`; only the terminators are written through the
error-reporting path. See [Chapter 4](04-errors.md).

## Where to go next

- [Chapter 4](04-errors.md) — the statuses everything here raises.
- [Chapter 6](06-the-registry.md) — the configuration step 3 above writes into.
- [Chapter 8](08-rendering.md) — `akgl_render_2d_init` versus `akgl_render_2d_bind`, and
  what `draw_world` does with the layers.
- [Chapter 14](14-physics.md) — what `simulate` does with the `dt` this chapter does not
  pace.
- [Chapter 23](23-appendix-limits.md) — `AKGL_GAME_*` and the rest of the constants.
