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