# 21. Appendix: limits, statuses and properties

Three reference tables that no single chapter owns: which function raises which status,
every compile-time bound, and every configuration property the library reads.

The constant tables come in as `excerpt=` blocks against their headers, so they cannot
drift from the values the library was built with — and so the hand-aligned columns those
headers maintain survive.

## A. Status cross-reference

**What this table is.** The statuses a caller can expect to see out of each function. It is
built from the `FAIL_*` and `HANDLE*` sites in `src/` — the function's own body and the
`static` helpers only it calls — plus, where a nested libakgl call is part of the documented
contract, that status too.

**It is not exhaustive, and cannot be.** Every libakgl function a given one calls can
propagate anything from its own row, and so can libakstdlib: nearly everything that touches
a file also carries whatever `aksl_fopen`, `aksl_fread`, `aksl_fwrite` and `aksl_fclose`
report, including bare `errno` values such as `ENOENT`, `EACCES` and `ENOSPC`. Use the table
to write the `HANDLE` arms you care about, not to prove a status cannot arrive.

`(H)` marks a status the function **handles** rather than raises.

What the statuses mean is [Chapter 4](04-errors.md).

### `error.h`

| Function | Raises |
|---|---|
| `akgl_error_init` | `AKERR_STATUS_RANGE_OVERLAP`, `AKERR_STATUS_RANGE_FULL`, `AKERR_STATUS_NAME_FULL` (all libakerror's) |

### `heap.h`

| Function | Raises |
|---|---|
| `akgl_heap_init`, `akgl_heap_init_actor` | *nothing — no failure path today* |
| `akgl_heap_next_actor`, `_sprite`, `_spritesheet`, `_character`, `_string` | `AKGL_ERR_HEAP` |
| `akgl_heap_release_actor`, `_sprite`, `_spritesheet`, `_character`, `_string` | `AKERR_NULLPOINTER` |

### `registry.h`

| Function | Raises |
|---|---|
| `akgl_registry_init` and the eight `akgl_registry_init_*` | `AKERR_NULLPOINTER` |
| `akgl_registry_load_properties` | `AKERR_NULLPOINTER`, `AKERR_KEY`, `AKERR_TYPE`, `AKGL_ERR_HEAP` |
| `akgl_set_property` | `AKERR_NULLPOINTER` |
| `akgl_get_property` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS`, `AKERR_VALUE`, `AKGL_ERR_HEAP` |

### `game.h`

| Function | Raises |
|---|---|
| `akgl_game_init` | `AKERR_NULLPOINTER`, `AKGL_ERR_SDL` |
| `akgl_game_update_fps`, `akgl_game_lowfps` | *`void` — cannot report* |
| `akgl_game_state_lock` | `AKGL_ERR_SDL` |
| `akgl_game_state_unlock` | *nothing* |
| `akgl_game_update` | `AKGL_ERR_SDL`, plus whatever `updatefunc`, `akgl_tilemap_scale_actor`, the physics backend or the renderer raises |
| `akgl_game_save` | `AKERR_NULLPOINTER`, `AKERR_IO`, `AKERR_OUTOFBOUNDS` |
| `akgl_game_save_actors` | `AKERR_NULLPOINTER`, `AKERR_IO` |
| `akgl_game_load` | `AKERR_NULLPOINTER`, `AKERR_API`, `AKERR_EOF`, `AKERR_IO`, `AKERR_VALUE` |
| `akgl_game_load_versioncmp` | `AKERR_NULLPOINTER`, `AKERR_VALUE`, `AKERR_API` |

### `actor.h`

| Function | Raises |
|---|---|
| `akgl_actor_initialize` | `AKERR_NULLPOINTER`, `AKERR_KEY` |
| `akgl_actor_set_character` | `AKERR_NULLPOINTER` — **including "no such character"** |
| `akgl_actor_add_child` | `AKERR_NULLPOINTER`, `AKERR_RELATIONSHIP`, `AKERR_OUTOFBOUNDS` |
| `akgl_actor_update` | `AKERR_NULLPOINTER`, `AKERR_KEY` (H) |
| `akgl_actor_render` | `AKERR_NULLPOINTER`, `AKERR_KEY` (H), `AKERR_OUTOFBOUNDS` (H) |
| `akgl_actor_automatic_face`, `_logic_movement`, `_logic_changeframe` | `AKERR_NULLPOINTER` |
| `akgl_actor_cmhf_*_on`, `akgl_actor_cmhf_*_off` | `AKERR_NULLPOINTER` |
| `akgl_registry_iterate_actor` | `AKERR_NULLPOINTER`, `AKERR_KEY` — **`void`; unhandled means the process exits** |

### `character.h`

| Function | Raises |
|---|---|
| `akgl_character_initialize` | `AKERR_NULLPOINTER`, `AKERR_KEY` |
| `akgl_character_sprite_add` | `AKERR_NULLPOINTER`, `AKERR_KEY` |
| `akgl_character_sprite_get` | `AKERR_NULLPOINTER`, `AKERR_KEY` |
| `akgl_character_load_json` | `AKERR_NULLPOINTER`, `AKERR_KEY` for a state name that is not in `AKGL_REGISTRY_ACTOR_STATE_STRINGS`, and `AKERR_TYPE` from the JSON accessors |
| `akgl_character_state_sprites_iterate` | `AKERR_NULLPOINTER` — **`void`; unhandled means the process exits** |

### `sprite.h`

| Function | Raises |
|---|---|
| `akgl_sprite_initialize` | `AKERR_NULLPOINTER`, `AKERR_KEY` |
| `akgl_sprite_load_json` | `AKERR_NULLPOINTER`, `AKERR_VALUE`, `AKERR_OUTOFBOUNDS` |
| `akgl_spritesheet_initialize` | `AKERR_NULLPOINTER`, `AKERR_KEY`, `AKGL_ERR_SDL` |
| `akgl_spritesheet_coords_for_frame` | `AKERR_NULLPOINTER` |

### `json_helpers.h`

| Function | Raises |
|---|---|
| `akgl_get_json_object_value`, `_boolean_`, `_integer_`, `_number_`, `_double_`, `_string_`, `_array_value` | `AKERR_NULLPOINTER`, **`AKERR_KEY` for absent**, **`AKERR_TYPE` for wrong type** |
| `akgl_get_json_array_index_object`, `_integer`, `_string` | `AKERR_NULLPOINTER`, **`AKERR_OUTOFBOUNDS` for a short array**, `AKERR_TYPE` |
| `akgl_get_json_with_default` | `AKERR_NULLPOINTER`; handles `AKERR_KEY` (H), `AKERR_OUTOFBOUNDS` (H), `AKERR_INDEX` (H) |

`akgl_get_json_string_value` and `akgl_get_json_array_index_string` also raise
`AKGL_ERR_HEAP` when they have to claim a pooled string and the pool is empty.

### `physics.h`

| Function | Raises |
|---|---|
| `akgl_physics_init_null`, `akgl_physics_init_arcade` | `AKERR_NULLPOINTER` |
| `akgl_physics_factory` | `AKERR_NULLPOINTER`, **`AKERR_KEY` for an unknown backend name** |
| `akgl_physics_simulate` | `AKERR_NULLPOINTER`; handles `AKGL_ERR_LOGICINTERRUPT` (H) |
| `akgl_physics_null_gravity`, `_collide`, `_move` | `AKERR_NULLPOINTER` |
| `akgl_physics_arcade_gravity`, `_move` | `AKERR_NULLPOINTER` |
| `akgl_physics_arcade_collide` | `AKERR_NULLPOINTER`, **`AKERR_API` — not implemented** |

### `renderer.h`

| Function | Raises |
|---|---|
| `akgl_render_2d_init` | `AKERR_NULLPOINTER`, `AKGL_ERR_SDL`, plus `akgl_get_property`'s |
| `akgl_render_2d_bind`, `_shutdown`, `_frame_start`, `_frame_end`, `_draw_texture`, `_draw_world` | `AKERR_NULLPOINTER` |
| `akgl_render_2d_draw_mesh` | **`AKERR_API` — not implemented** |

### `draw.h`

| Function | Raises |
|---|---|
| `akgl_draw_point`, `_line`, `_rect`, `_filled_rect` | `AKERR_NULLPOINTER` |
| `akgl_draw_background` | `AKERR_NULLPOINTER`, `AKGL_ERR_SDL` |
| `akgl_draw_circle` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS`, `AKGL_ERR_SDL` |
| `akgl_draw_flood_fill` | `AKERR_NULLPOINTER`, `AKGL_ERR_SDL`, `AKERR_OUTOFBOUNDS` past `AKGL_DRAW_MAX_FLOOD_SPANS` |
| `akgl_draw_copy_region`, `_paste_region` | `AKERR_NULLPOINTER`, `AKGL_ERR_SDL` |

### `tilemap.h`

| Function | Raises |
|---|---|
| `akgl_tilemap_load` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS` |
| `akgl_tilemap_load_layers` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS` |
| `akgl_tilemap_load_layer_tile` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS` |
| `akgl_tilemap_load_layer_image` | `AKERR_NULLPOINTER`, `AKGL_ERR_SDL` |
| `akgl_tilemap_load_layer_objects` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS` |
| `akgl_tilemap_load_layer_object_actor` | `AKERR_KEY` |
| `akgl_tilemap_load_tilesets` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS` past `AKGL_TILEMAP_MAX_TILESETS` |
| `akgl_tilemap_load_tilesets_each` | `AKERR_NULLPOINTER` |
| `akgl_tilemap_load_physics` | handles `AKERR_KEY` (H) — every physics property is optional |
| `akgl_get_json_tilemap_property` | `AKERR_NULLPOINTER`, `AKERR_KEY`, `AKERR_TYPE` |
| `akgl_tilemap_draw`, `_draw_tileset` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS` |
| `akgl_tilemap_scale_actor`, `akgl_tilemap_release` | `AKERR_NULLPOINTER` |

### `controller.h`

| Function | Raises |
|---|---|
| `akgl_controller_open_gamepads`, `_list_keyboards` | `AKERR_NULLPOINTER` |
| `akgl_controller_handle_event`, `_button_down`, `_button_up`, `_added`, `_removed` | `AKERR_NULLPOINTER` |
| `akgl_controller_pushmap` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS` |
| `akgl_controller_default` | `AKERR_OUTOFBOUNDS`, **`AKGL_ERR_REGISTRY`** — the library's only site |
| `akgl_controller_poll_key`, `_poll_keystroke` | `AKERR_NULLPOINTER` |

### `text.h`, `audio.h`, `assets.h`, `util.h`, `staticstring.h`

| Function | Raises |
|---|---|
| `akgl_text_loadfont` | `AKERR_NULLPOINTER`, `AKGL_ERR_SDL` |
| `akgl_text_unloadfont` | `AKERR_NULLPOINTER`, `AKERR_KEY` |
| `akgl_text_rendertextat` | `AKERR_NULLPOINTER` — including a `NULL` `akgl_renderer`, a `NULL` `sdl_renderer`, and a backend never run through `akgl_render_2d_bind` and so missing `draw_texture`. An **empty** string is not an error: it returns success having drawn nothing |
| `akgl_text_measure`, `_measure_wrapped` | `AKERR_NULLPOINTER`. An empty string is legal and measures 0 wide by one line high |
| `akgl_audio_tone`, `akgl_audio_sweep` | `AKERR_OUTOFBOUNDS` |
| `akgl_audio_voice_active`, `akgl_audio_mix` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS` |
| `akgl_load_start_bgm` | `AKERR_NULLPOINTER`, `AKGL_ERR_SDL` |
| `akgl_path_relative` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS` |
| `akgl_rectangle_points`, `akgl_collide_point_rectangle`, `akgl_collide_rectangles` | `AKERR_NULLPOINTER` |
| `akgl_compare_sdl_surfaces` | `AKERR_NULLPOINTER`, `AKERR_VALUE` |
| `akgl_render_and_compare` | `AKERR_NULLPOINTER`, `AKERR_IO`, `AKGL_ERR_SDL` |
| `akgl_string_initialize` | `AKERR_NULLPOINTER` |
| `akgl_string_copy` | `AKERR_NULLPOINTER`, `AKERR_OUTOFBOUNDS`, `AKERR_VALUE` |

## B. Compile-time limits

Everything below is fixed when the library is compiled. **Only the `AKGL_MAX_HEAP_*` five
are overridable**, and even those have to be overridden for the whole build — see
[Chapter 5](05-the-heap.md), "The ceilings are a compile-time ABI constraint". The rest are
plain `#define`s with no `#ifndef` guard: changing one means editing the header and
rebuilding libakgl and everything linking it.

### The object pools

```c excerpt=include/akgl/heap.h
#ifndef AKGL_MAX_HEAP_ACTOR
#define AKGL_MAX_HEAP_ACTOR                    64
#endif
#ifndef AKGL_MAX_HEAP_SPRITE
#define AKGL_MAX_HEAP_SPRITE                   (AKGL_MAX_HEAP_ACTOR * 16)
#endif
#ifndef AKGL_MAX_HEAP_SPRITESHEET
#define AKGL_MAX_HEAP_SPRITESHEET              AKGL_MAX_HEAP_SPRITE
#endif
#ifndef AKGL_MAX_HEAP_CHARACTER
#define AKGL_MAX_HEAP_CHARACTER                256
#endif
#ifndef AKGL_MAX_HEAP_STRING
#define AKGL_MAX_HEAP_STRING                   256
#endif
```

Exceeding any of them is `AKGL_ERR_HEAP`.

### Strings

`AKGL_MAX_STRING_LENGTH` is `PATH_MAX`, which is 4096 on Linux. That is the capacity of
every `akgl_String`, and it is why the string pool is over a megabyte on its own.

### Actors

```c excerpt=include/akgl/actor.h
/** @brief Longest actor name, including the terminator. Names are truncated, not rejected. */
#define AKGL_ACTOR_MAX_NAME_LENGTH      128
/** @brief Children one actor can carry. A child moves with its parent rather than simulating. */
#define AKGL_ACTOR_MAX_CHILDREN         8
```

`AKGL_ACTOR_MAX_STATES` is 32, fixed by the width of `int32_t state` rather than chosen.
A ninth child is `AKERR_OUTOFBOUNDS`; an over-long name is silently truncated.

### Sprites and spritesheets

```c excerpt=include/akgl/sprite.h
#define AKGL_SPRITE_MAX_FRAMES                 16
#define AKGL_SPRITE_MAX_NAME_LENGTH            128
#define AKGL_SPRITE_MAX_REGISTRY_SIZE          1024
#define AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH  512
```

`akgl_sprite_load_json` bounds the `frames` array against `AKGL_SPRITE_MAX_FRAMES` before
writing anything, and refuses a frame number that will not fit a `uint8_t` rather than
truncating it into an index naming a different tile (`TODO.md` item 16).

**`AKGL_SPRITE_MAX_REGISTRY_SIZE` is dead.** It is defined in `sprite.h` and referenced
nowhere in `src/`, `include/`, `tests/` or `util/`. It bounds nothing. Do not size anything
against it.

### Tilemaps

```c excerpt=include/akgl/tilemap.h
/** @brief Widest map, in tiles. Width times height is what is actually bounded. */
#define AKGL_TILEMAP_MAX_WIDTH                        512
/** @brief Tallest map, in tiles. */
#define AKGL_TILEMAP_MAX_HEIGHT                       512
/** @brief Layers per map. Also the number of draw passes akgl_render_2d_draw_world makes. */
#define AKGL_TILEMAP_MAX_LAYERS                       16
/** @brief Tilesets per map. */
#define AKGL_TILEMAP_MAX_TILESETS                     16
/** @brief Entries in a tileset's offset table. Indexed by *local* tile id, so a tileset with a high `firstgid` still starts at 0. */
#define AKGL_TILEMAP_MAX_TILES_PER_IMAGE              65536
/** @brief Longest tileset name. */
#define AKGL_TILEMAP_MAX_TILESET_NAME_SIZE            512
/** @brief Longest resolved tileset image path. */
#define AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE        PATH_MAX
/** @brief Longest object name. Note that an object naming an actor is truncated at #AKGL_ACTOR_MAX_NAME_LENGTH (128) instead. */
#define AKGL_TILEMAP_MAX_OBJECT_NAME_SIZE             512
/** @brief Objects in one object layer. Not enforced by the loader -- a longer group writes past the array. */
#define AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER            128
```

Three corrections to what those comments say, all verified against `src/tilemap.c`:

- **`AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER` *is* enforced.** The comment above says it is not.
  `akgl_tilemap_load_layer_objects` bounds `j` at the top of the loop body and raises
  `AKERR_OUTOFBOUNDS`; `akgl_tilemap_load_tilesets` does the same for
  `AKGL_TILEMAP_MAX_TILESETS`. Both landed in 0.5.0 (`TODO.md`, "Known and still open"
  item 17), and the header comment was not updated with them.
- **Width and height are not bounded individually.** `akgl_tilemap_load` checks
  `width * height >= AKGL_TILEMAP_MAX_WIDTH * AKGL_TILEMAP_MAX_HEIGHT`, and the comparison
  is `>=`, so the true ceiling is **262143 tiles** in any shape. A 1024×256 map loads; a
  512×512 one does not.
- **`AKGL_TILEMAP_MAX_TILES_PER_IMAGE` is not checked anywhere.** It sizes
  `tile_offsets[65536][2]` in the struct and nothing validates a tileset against it. It is
  also why `akgl_Tileset` is large: 512 KiB of offset table per tileset, whatever the image
  actually holds.

### Controllers

```c excerpt=include/akgl/controller.h
/** @brief How many control maps exist -- effectively the local player limit. */
#define AKGL_MAX_CONTROL_MAPS 8
/** @brief Bindings per control map. The default map installed by akgl_controller_default uses 8 of them. */
#define AKGL_MAX_CONTROLS     32
```

`AKGL_CONTROLLER_KEY_BUFFER` is 32 — the keystroke ring, sized so a program polling once a
frame never loses a key to a fast typist. `AKGL_CONTROLLER_KEYSTROKE_TEXT` is 8, enough for
one UTF-8 code point and its terminator; a longer commit is truncated **on a code point
boundary** rather than through the middle of one.

### Drawing

`AKGL_DRAW_MAX_FLOOD_SPANS` is 4096 — the fixed stack of horizontal runs `akgl_draw_flood_fill`
keeps instead of recursing per pixel. A region needing more pending runs at once reports
`AKERR_OUTOFBOUNDS` rather than overflowing; an ordinary convex or moderately concave shape
needs a few dozen.

### Audio

```c excerpt=include/akgl/audio.h
/** @brief Sample rate of the generated stream, in frames per second. */
#define AKGL_AUDIO_SAMPLE_RATE 44100

/** @brief Frames the device callback generates per pass through the mixer. */
#define AKGL_AUDIO_MIX_FRAMES  512
```

`AKGL_AUDIO_MAX_VOICES` is 3. `AKGL_AUDIO_SWEEP_TICK_HZ` is 60, and it divides the sample
rate exactly, so a sweep step boundary always lands on a whole frame —
`AKGL_AUDIO_SWEEP_TICK_FRAMES` is 735.

The SDL_mixer side is separate: `AKGL_GAME_AUDIO_MAX_TRACKS` is 64 playback tracks, and
**slot 0 is unused** — `AKGL_GAME_AUDIO_TRACK_BGM` is slot 1, and the rest are yours.

### Time and the frame

| Constant | Value | Meaning |
|---|---|---|
| `AKGL_TIME_ONESEC_NS` | 1000000000 | Nanoseconds in a second — the unit `SDL_GetTicksNS` reports in |
| `AKGL_TIME_ONEMS_NS` | 1000000 | Nanoseconds in a millisecond — the scale factor from JSON frame durations to internal ones |
| `AKGL_GAME_STATE_LOCK_BUDGET_MS` | 1000 | How long `akgl_game_state_lock` keeps trying before raising |
| `AKGL_GAME_STATE_LOCK_RETRY_MS` | 100 | How long it sleeps between attempts |
| `AKGL_PHYSICS_DEFAULT_MAX_TIMESTEP` | 0.05 | Default `physics.max_timestep`, in seconds — three frames at 60 Hz |

`AKGL_TIME_ONEMS_NS` was called `AKGL_TIME_ONESEC_MS` until 0.5.0 and held 1000000 — a name
and a value describing two different quantities. The state lock read it as a one-second
budget and was wrong by a factor of a thousand, blocking for roughly sixteen minutes.

### The status band

| Constant | Value |
|---|---|
| `AKGL_ERR_BASE` | 256 (`AKERR_FIRST_CONSUMER_STATUS`) |
| `AKGL_ERR_COUNT` | 5 |
| `AKGL_ERR_LIMIT` | 261 — **the one-past-the-end sentinel, not a status** |

## C. Configuration properties

Everything the library reads out of `AKGL_REGISTRY_PROPERTIES`. **Every value is a string**,
including the numbers, and each is parsed by whoever reads it.

| Property | Default | Read by | Read when | Meaning |
|---|---|---|---|---|
| `game.screenwidth` | `"0"` | `akgl_render_2d_init` | once, at init | Window and camera width in pixels. Passed straight to `SDL_CreateWindowAndRenderer` |
| `game.screenheight` | `"0"` | `akgl_render_2d_init` | once, at init | Window and camera height in pixels |
| `physics.gravity.x` | `"0.0"` | `akgl_physics_init_arcade` | once, at init | Constant acceleration on x, px/s² |
| `physics.gravity.y` | `"0.0"` | `akgl_physics_init_arcade` | once, at init | Constant acceleration on y, px/s². Positive is down |
| `physics.gravity.z` | `"0.0"` | `akgl_physics_init_arcade` | once, at init | Constant acceleration on z, px/s² |
| `physics.drag.x` | `"0.0"` | `akgl_physics_init_arcade` | once, at init | Fraction of environmental velocity shed per second on x |
| `physics.drag.y` | `"0.0"` | `akgl_physics_init_arcade` | once, at init | Same for y. **The only brake on falling** — there is no terminal velocity, and `ey` approaches `gravity_y / drag_y` |
| `physics.drag.z` | `"0.0"` | `akgl_physics_init_arcade` | once, at init | Same for z |
| `physics.max_timestep` | `"0.05"` | `akgl_physics_init_arcade` | once, at init | Ceiling on a single simulation `dt`, in seconds |

That is the complete list — nine properties, read by two functions, each exactly once.

**`physics.engine` is not read by anything.** `physics.h` says in two places that
`akgl_game_init` passes it to `akgl_physics_factory`; `akgl_game_init` never calls the
factory, and the string does not appear in `src/`. Pass `"null"` or `"arcade"` to
`akgl_physics_factory` yourself. Correcting the header is a source change and is deliberately
out of scope for this manual.

**Everything is read at init.** Setting any of these after `akgl_render_2d_init` or
`akgl_physics_init_arcade` has no effect at all, and reports success. See
[Chapter 6](06-the-registry.md), and the startup order in
[Chapter 7](07-the-game-and-the-frame.md).

**A property set before `AKGL_REGISTRY_PROPERTIES` exists is silently discarded.**
`akgl_set_property` does not check SDL's return value. `akgl_game_init` and
`akgl_registry_init` both create the registry; a hand-rolled startup that creates neither
gets no configuration and no error.

## Where to go next

- [Chapter 4](04-errors.md) — what each status means.
- [Chapter 5](05-the-heap.md) — the pools, and the ABI constraint on their ceilings.
- [Chapter 6](06-the-registry.md) — the property store these live in.
- The generated Doxygen reference — every function's own `@throws` list and full signature.
