# 04. Errors and status codes

Every one of libakgl's exported functions returns `akerr_ErrorContext AKERR_NOIGNORE *`.
You cannot read a single example in this manual until you can read that return value, which
is why this chapter comes before any subsystem.

## The protocol is libakerror's

`ATTEMPT` / `CLEANUP` / `PROCESS` / `HANDLE` / `HANDLE_GROUP` / `FINISH`, and the `PASS`,
`CATCH`, `IGNORE`, `FAIL_*` and `SUCCEED_RETURN` macros, all belong to **libakerror**.
They are documented by the project that owns them, in `deps/libakerror/README.md` —
"Library Architecture", "Lifecycle of an error in the AKError library", and "Exit status".
This manual does not restate them, because a copy here would be wrong the day libakerror
changes and nothing in this repository would notice.

What is genuinely libakgl's, and is written down nowhere else, is **which statuses these
193 functions raise and what each one means here**. That is the three tables below.

The house rules for *writing* code against the protocol — never a `*_RETURN` inside an
`ATTEMPT`, never a bare `return` out of a `HANDLE`, `CLEANUP` before `PROCESS`, an
`ATTEMPT` inside the loop rather than a `CATCH` inside one — are in `AGENTS.md` under
"Error-Handling Protocol", and two of them are enforced by the `error_protocol` test.

## Table 1 — libakgl's own status codes

libakerror reserves statuses 0–255 for the host's `errno` values and its own `AKERR_*`
codes; consumers allocate upward from `AKERR_FIRST_CONSUMER_STATUS`, which is **256**.
libakgl claims a band of seven starting there, under the owner string `"libakgl"`:

```c excerpt=include/akgl/error.h
#define AKGL_ERR_OWNER             "libakgl"
#define AKGL_ERR_BASE              AKERR_FIRST_CONSUMER_STATUS

#define AKGL_ERR_SDL               (AKGL_ERR_BASE + 0)   /**< An SDL call failed; the message carries SDL_GetError() */
#define AKGL_ERR_REGISTRY          (AKGL_ERR_BASE + 1)   /**< A registry property or lookup operation failed */
#define AKGL_ERR_HEAP              (AKGL_ERR_BASE + 2)   /**< A heap pool has no free object left to hand out */
#define AKGL_ERR_BEHAVIOR          (AKGL_ERR_BASE + 3)   /**< A component did not behave the way its contract requires */
#define AKGL_ERR_LOGICINTERRUPT    (AKGL_ERR_BASE + 4)   /**< Actor logic is telling the physics simulator to skip it */
#define AKGL_ERR_COLLISION         (AKGL_ERR_BASE + 5)   /**< A collision query could not be answered; the message says why */
#define AKGL_ERR_UI                (AKGL_ERR_BASE + 6)   /**< The UI subsystem refused, or clay reported a layout error; the message says which */
```

**`AKGL_ERR_LIMIT` is not a status code.** It is `AKGL_ERR_BASE + 7`, one past the last
one, and exists only so `AKGL_ERR_COUNT` can be computed from it. Nothing in `src/` raises
it and `akgl_error_init` does not name it. Do not write a `HANDLE(e, AKGL_ERR_LIMIT)`
arm — it would catch nothing, and if an eighth real code is ever added it would silently
start catching that instead.

| Code | Value | Means | Raised by | What the caller does |
|---|---|---|---|---|
| `AKGL_ERR_SDL` | 256 | An SDL call failed; the message carries `SDL_GetError()` | Around forty sites — anything touching a window, texture, renderer, mixer or the state mutex | Usually fatal at startup. Check the driver and the asset path |
| `AKGL_ERR_REGISTRY` | 257 | A name lookup against a registry failed | **One site only**: `akgl_controller_default`, when `actorname` is not in `AKGL_REGISTRY_ACTOR` | Bind the control map *after* the actor is initialized |
| `AKGL_ERR_HEAP` | 258 | A pool has no free slot | every `akgl_heap_next_*` | **Normally a missing release, not a small pool.** See [Chapter 5](05-the-heap.md) before raising `AKGL_MAX_HEAP_*` |
| `AKGL_ERR_BEHAVIOR` | 259 | A component did not behave the way its contract requires | **Nothing in the library.** Only `tests/` raises it, through `testutil.h` | Available to you for the same purpose: asserting a contract in your own code |
| `AKGL_ERR_LOGICINTERRUPT` | 260 | **Not a failure — a control signal.** "Skip the rest of this step for this actor" | your own `movementlogicfunc` | Raise it deliberately. See the note below |
| `AKGL_ERR_COLLISION` | 261 | A collision query could not be answered | **One cause only**: `akgl_collision_test`, when the ccd arena is exhausted. The message carries the high-water mark | Raise `AKGL_CCD_ARENA_BYTES` to the reported figure. See [Chapter 15](15-collision.md) |
| `AKGL_ERR_UI` | 262 | The UI subsystem refused, or clay reported a layout error | `akgl_ui_*` — lifecycle misuse (init twice, a widget outside the frame bracket), an arena or table at its ceiling, and layout errors surfacing from `akgl_ui_frame_end` | The message says which; the ceilings are in [Chapter 23](23-appendix-limits.md). See [Chapter 22](22-ui.md) |

`akgl_error_init` reserves the band all-or-nothing and registers a name for each of the
seven: `"SDL Error"`, `"Registry Error"`, `"Heap Error"`, `"Behavior Error"`,
`"Logic Interrupt"`, `"Collision Error"`, `"UI Error"`. Those names are what a stack trace
prints.

Two rows deserve more than a cell.

**`AKGL_ERR_REGISTRY` is rarer than its name suggests.** The obvious candidates do not
raise it. `akgl_actor_initialize` raises `AKERR_KEY` when the registry write fails, and
`akgl_actor_set_character` raises `AKERR_NULLPOINTER` when the named character is not in
`AKGL_REGISTRY_CHARACTER`. If you are writing a `HANDLE` arm for "that name was not
registered", `AKERR_NULLPOINTER` and `AKERR_KEY` are the statuses you will actually see.

**`AKGL_ERR_LOGICINTERRUPT` only works from one place.** `akgl_physics_simulate` wraps each
actor's step in an `ATTEMPT` and calls `actor->movementlogicfunc` through `CATCH`, with a
`HANDLE(errctx, AKGL_ERR_LOGICINTERRUPT)` arm that does nothing — so a raise from your
movement logic skips that actor's gravity, drag and move for this step and the loop carries
on to the next actor. The backend's own `gravity` and `move` are called through `PASS` in
the same block, and `PASS` returns out of the function. **A backend hook that raises
`AKGL_ERR_LOGICINTERRUPT` aborts the whole physics step**, leaving every remaining actor
unsimulated and `gravity_time` unadvanced. Raise it from `movementlogicfunc` and nowhere
else.

## Table 2 — libakerror statuses libakgl raises, and what they mean here

The statuses are libakerror's; their libakgl meaning is not. This table was built by
grepping every `FAIL_*` and `HANDLE*` site in `src/`, not by reading header prose.

| Status | What it means when a libakgl function raises it |
|---|---|
| `AKERR_NULLPOINTER` | By a wide margin the most common — roughly six of every seven raise sites in `src/`. A required pointer argument was `NULL` — *or* a required field was empty (`akgl_game.name`/`.version`/`.uri`), *or* a name was looked up and not found. `akgl_actor_set_character` and `akgl_get_property` both use it for "absent", and `registry_create` uses it for "SDL could not allocate the property set" |
| `AKERR_KEY` | **A key is absent, or a registry write was refused.** A missing JSON object member (every `akgl_get_json_*_value`), a character with no sprite for a state, a physics backend name that is neither `null` nor `arcade`, a failed `SDL_SetPointerProperty` in `akgl_actor_initialize`. Frequently a `HANDLE` arm rather than a failure |
| `AKERR_TYPE` | **A JSON value was present but the wrong type.** All of `src/json_helpers.c` and `akgl_get_json_tilemap_property`. This is the status that separates "you did not write that key" (`AKERR_KEY`) from "you wrote it as a string and it wants a number" |
| `AKERR_OUTOFBOUNDS` | A value did not fit its destination, or an index ran off the end. `aksl_strncpy` truncation, an array index past the end of a JSON array (`akgl_get_json_array_index_*`), a ninth child on an actor, a tile past `AKGL_TILEMAP_MAX_*`, a control map id out of range |
| `AKERR_VALUE` | A value parsed but was not usable: a version string that is not semver (`akgl_game_load_versioncmp`), two surfaces that differ (`akgl_compare_sdl_surfaces`), a zero-sized destination or an overlapping copy from the `aksl_` wrappers |
| `AKERR_RELATIONSHIP` | **One site**: `akgl_actor_add_child`, when the child already has a parent. Detach it first |
| `AKERR_API` | Two distinct meanings, so read the message. (1) **The function is not implemented** — `akgl_render_2d_draw_mesh` does `FAIL_RETURN(..., AKERR_API, "Not implemented")`, and it is reached by an ordinary-looking call. (2) **A savegame does not match this build** — `akgl_game_load` and `akgl_game_load_versioncmp` |
| `AKERR_IO` | A read or write failed, or a savegame had trailing data after its name tables. Distinct from `AKERR_EOF` — that separation is the whole reason `aksl_fgetc` exists |
| `AKERR_EOF` | End of input. Sometimes the *desired* outcome: `require_at_eof` in `src/game.c` handles it and treats anything else as a failure |
| `AKERR_INDEX` | **libakgl never raises this.** It appears once, as a `HANDLE_GROUP` arm in `akgl_get_json_with_default`, so that a caller whose *own* code raises it still gets a default. An out-of-range JSON array index arrives as `AKERR_OUTOFBOUNDS` |

Statuses raised by the libraries underneath also reach you unchanged. `aksl_fopen` reports
`ENOENT` and `EACCES` as themselves; `aksl_fclose` reports `ENOSPC` and `EDQUOT`.
`akgl_error_init` can raise libakerror's `AKERR_STATUS_RANGE_OVERLAP`,
`AKERR_STATUS_RANGE_FULL` or `AKERR_STATUS_NAME_FULL`.

[Chapter 23](23-appendix-limits.md) has the per-function cross-reference.

## Table 3 — the exit status trap

This is arithmetic, and it is silent.

| You write | Wait status the shell sees | Why |
|---|---|---|
| `exit(AKGL_ERR_SDL)` | **0 — a clean run** | An exit status is one byte. `AKGL_ERR_SDL` is 256, and 256 & 0xFF is 0 |
| `akerr_exit(status)` | 0 → 0, 1–255 → itself, anything else → 125 | 125 is `AKERR_EXIT_STATUS_UNREPRESENTABLE` |

Every suite in `tests/` once reported success on the single most common failure a library
built on SDL can have — a renderer that would not come up. `tests/character.c` aborted at
its second of four tests and was green until 0.5.0.

**You do not normally need to do anything about this.** libakerror's default unhandled-error
handler already calls `akerr_exit()`, so a `main` that ends in `FINISH_NORETURN` exits
correctly. The trap is only live if you replace libakerror's
`akerr_handler_unhandled_error` pointer with your own handler, or call `exit(3)` on a
status yourself. If you do either, call `akerr_exit()`.

## Four traps that are libakgl's, not libakerror's

### `akgl_error_init()` must run before anything that can raise

`akgl_error_init` claims the status band *and* registers the six names. A code raised
before it runs has no name in the registry, so every stack trace carrying it prints
**"Unknown Error"** instead of "Heap Error" — and the status number, 258, means nothing to
a reader.

`akgl_game_init` calls it first, before anything else that can fail — that is why the
mutex creation and the `akgl_game.name` checks sit *after* it. A host with its own startup
path, such as one binding an existing renderer with `akgl_render_2d_bind` instead of
calling `akgl_game_init`, must call `akgl_error_init` itself. Repeat calls are a no-op, so
a program that cannot order its initialization precisely may simply call it more than once.

### The SDL callbacks end in `FINISH_NORETURN`, and that exits the process

`SDL_EnumerateProperties` takes a `void`-returning callback, so a failure inside one has
nowhere to go. All six of libakgl's therefore end in `FINISH_NORETURN`, which logs the
trace and hands the context to libakerror's unhandled-error handler — **and the default
handler exits the process.** They are `akgl_registry_iterate_actor` (`src/actor.c`),
`akgl_character_state_sprites_iterate` (`src/character.c`), and the four savegame name
iterators in `src/game.c`.

A reader meets this the first time a sprite name is wrong: an actor's `renderfunc` raises
`AKERR_KEY`, the callback cannot return it, and the game exits mid-frame with a stack trace
instead of returning an error you could have handled.

Two consequences worth planning for:

- A write error part-way through `akgl_game_save` terminates the game rather than reaching
  `akgl_game_save`'s return value. `game.h` says so on `akgl_game_save_actors`.
- If you want a different outcome, point libakerror's `akerr_handler_unhandled_error`
  function pointer at your own handler — and call `akerr_exit()` from it, per Table 3.

Note that `akgl_game_update` does **not** go through the callback. It walks
`akgl_heap_actors` directly and propagates through `PASS`, so an error from an actor's
`updatefunc` does reach you. The callback path is the render sweep and the savegame writer.

### `akgl_get_json_with_default` hands back the context you gave it

This is the one libakgl function whose ownership rule you have to know before you write
your first loader. It takes an incoming `akerr_ErrorContext *`, and when it does not handle
that status it returns **the same pointer**, because its `FINISH(e, true)` passes `e` up.

So the context has exactly one owner at a time, and it is never both yours and its. A
`CLEANUP` block that releases your original pointer *as well as* releasing what the call
returned releases the same slot twice, and a double-released context corrupts the failure
instead of reporting it. That is not hypothetical: it is how the first draft of the
`AKERR_OUTOFBOUNDS` test in `tests/json_helpers.c` passed against the unfixed library.
`AGENTS.md` documents the shape under "Testing Guidelines"; `TODO.md` item 18 records the
history.

The correct shape: take the result into a local, `NULL` your own pointer immediately, and
decide from the local.

It defaults on three statuses — `AKERR_KEY`, `AKERR_OUTOFBOUNDS` and `AKERR_INDEX` — so
both a missing object member and a short array give you the default. It does **not** default
on `AKERR_TYPE`: a key that is present with the wrong type is a mistake in the document, not
an absent value, and it propagates.

### libakerror 2.0.1 is a hard floor, checked at compile time

`include/akgl/error.h` carries two `#error` feature tests, because libakerror publishes no
version macro and each probes the narrowest thing that a required release introduced:

- `AKERR_FIRST_CONSUMER_STATUS` — added in 1.0.0 with the private status registry. Without
  it, `AKGL_ERR_BASE` does not exist and the failure lands much later, inside `src/heap.c`.
- `AKERR_EXIT_STATUS_UNREPRESENTABLE` — added in 2.0.1 with `akerr_exit()`.

Both failures are an *installed header* being stale, not a build-tree problem. 2.0.0 is an
ABI break — `akerr_next_error()` returns a context that already holds its reference, and
`__akerr_last_ignored` is thread-local — and both expand at libakgl's own call sites through
`IGNORE` and the `FAIL_*` macros. The soname is `libakerror.so.2`, so the *libraries* cannot
be mixed by accident; the header can. Rebuild and reinstall libakerror.

## One worked example

A loader reading an optional integer out of a JSON document. It is the smallest piece of
real libakgl code that exercises both halves of the chapter: a status that means "absent
and that is fine", and the ownership rule on the function that turns it into a default.

```c
#include <jansson.h>
#include <akerror.h>
#include <akgl/error.h>
#include <akgl/json_helpers.h>

/* Read an optional integer out of a loaded document, defaulting to 4. */
akerr_ErrorContext AKERR_NOIGNORE *read_speed(json_t *doc, int *dest)
{
    akerr_ErrorContext *keyerr = NULL;
    akerr_ErrorContext *unhandled = NULL;
    int def = 4;

    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, doc, AKERR_NULLPOINTER, "NULL document");
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination");

    keyerr = akgl_get_json_integer_value(doc, "speed", dest);
    unhandled = akgl_get_json_with_default(keyerr, &def, dest, sizeof(int));
    keyerr = NULL;
    if ( unhandled != NULL ) {
	return unhandled;
    }
    SUCCEED_RETURN(errctx);
}

int main(void)
{
    json_t *doc = NULL;
    int speed = 0;

    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, akgl_error_init());
	doc = json_pack("{s:i}", "width", 32);
	CATCH(errctx, read_speed(doc, &speed));
    } CLEANUP {
	if ( doc != NULL ) {
	    json_decref(doc);
	}
    } PROCESS(errctx) {
    } FINISH_NORETURN(errctx);
}
```

Four things in there are libakgl-specific and worth naming:

1. **`akgl_error_init()` is the first call**, before anything that can raise.
2. **`keyerr = NULL;` on the line after the call.** Ownership passed either way — handled
   or not — and the local is the only surviving reference. Nothing else in the function may
   touch `keyerr` again.
3. **A `speed` key that is absent is not an error.** `akgl_get_json_integer_value` raises
   `AKERR_KEY`, `akgl_get_json_with_default` handles it and writes 4. A `speed` key that is
   present as a string *is* an error: `AKERR_TYPE` is not defaulted and comes back as
   `unhandled`.
4. **`main` ends in `FINISH_NORETURN`.** That is what gets you `akerr_exit()` and a
   truthful exit status without writing the trap from Table 3.

## Where to go next

- [Chapter 5](05-the-heap.md) — `AKGL_ERR_HEAP`, and why it is almost always a missing
  release.
- [Chapter 6](06-the-registry.md) — the name lookups behind `AKERR_KEY` and
  `AKERR_NULLPOINTER`.
- [Chapter 23](23-appendix-limits.md) — which function raises what.
- `deps/libakerror/README.md` — the protocol itself, and the exit-status discussion.
- The generated Doxygen reference — every function's own `@throws` list.
