Files
libakgl/docs/23-appendix-limits.md
Andrew Kesterson 8f848d80be Document the UI subsystem: chapter 22, with the appendix moving to 23
docs/22-ui.md covers the clay-backed UI: what clay owns against what libakgl
owns, bring-up and the arena refusal contract, the frame bracket and where it
sits in a real frame, the consumed-event rules (with the one-frame-stale hit
test stated plainly), the three widgets, menus across keyboard, gamepad and
mouse, writing raw CLAY() with an application-owned press edge, images and
styles, and how layout errors surface from frame_end with a captured trace.
Every listing is a checked block: one linked-and-run example with pinned
output, eight excerpts quoting examples/uidemo and examples/jrpg/textbox.c,
and the chapter figure is a frame out of uidemo via docs_game_figures.

The chapter owes and pays the comparison the tutorials earn: the dialog
widget's default style reproduces the JRPG textbox palette, the two listings
sit side by side, and the trade is stated -- 125 lines you own completely
against one call that costs you the subsystem. Neither is deprecated;
chapter 21 keeps teaching the hand-rolled panel on purpose.

The appendix moves to 23 (links in chapters 4, 5, 6, 7, 13 and the TOC
updated) and gains the ui.h status cross-reference, the AKGL_UI_* limits
table, and the new draw primitives' rows. Its status-band figures were stale
at COUNT 5 / LIMIT 261 while the band already held six codes; now correct at
7 / 263. Chapter 4 gains AKGL_ERR_UI (262) in the excerpt, the table and the
name list.

Co-Authored-By: Claude Code (Claude Fable 5, claude-fable-5) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 11:32:20 -04:00

25 KiB
Raw Blame History

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

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, _collision_proxy, _collision_cell, _bspnode AKGL_ERR_HEAP
akgl_heap_release_actor, _sprite, _spritesheet, _character, _string, _collision_proxy, _collision_cell, _bspnode 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_NULLPOINTERincluding "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_KEYvoid; 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_NULLPOINTERvoid; 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, plus akgl_collision_resolve's. A no-op when self->collision is NULL

collision.h

Function Raises
akgl_collision_shape_box, _shape_circle, _shape_capsule, _shape_bounds, _shape_interacts AKERR_NULLPOINTER
akgl_collision_world_init AKERR_NULLPOINTER, AKERR_VALUE for a non-positive cell size, plus akgl_partitioner_factory's
akgl_collision_bind_tilemap AKERR_NULLPOINTER, plus akgl_get_json_properties_boolean's; handles AKERR_KEY (H) for a layer with no collidable property
akgl_collision_test AKERR_NULLPOINTER, AKGL_ERR_COLLISION when the ccd arena is exhausted — the message carries the high-water mark
akgl_collision_resolve AKERR_NULLPOINTER, plus akgl_collision_test's and the actor's own collidefunc's
akgl_collision_settle AKERR_NULLPOINTER, AKERR_VALUE when the shape is not clear within maxsteps
akgl_collision_solid_at, _box_blocked, _query_box, _substeps, _sync_actors AKERR_NULLPOINTER
akgl_collision_proxy_initialize, _proxy_sync, _cell_initialize AKERR_NULLPOINTER
akgl_partitioner_factory AKERR_NULLPOINTER, AKERR_KEY for an unknown partitioner name
akgl_partitioner_init_grid, _init_bsp AKERR_NULLPOINTER. The BSP's insert raises AKGL_ERR_HEAP past AKGL_MAX_HEAP_COLLISION_PROXY proxies
akgl_actor_collide_block AKERR_NULLPOINTER

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
akgl_draw_filled_rounded_rect AKERR_NULLPOINTER, AKGL_ERR_SDL
akgl_draw_arc AKERR_NULLPOINTER, AKERR_OUTOFBOUNDS, AKGL_ERR_SDL
akgl_draw_set_clip AKERR_NULLPOINTER, AKGL_ERR_SDL — a NULL rectangle is legal here and clears the clip

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

ui.h

Function Raises
akgl_ui_init AKERR_OUTOFBOUNDS, AKGL_ERR_UI — already initialized, or an arena clay's structures do not fit; the message carries both byte counts
akgl_ui_shutdown nothing — idempotent by design
akgl_ui_resize AKGL_ERR_UI, AKERR_OUTOFBOUNDS
akgl_ui_font_register AKERR_NULLPOINTER, AKGL_ERR_UI, AKERR_OUTOFBOUNDS past AKGL_UI_FONT_NAME_LENGTH
akgl_ui_handle_event AKERR_NULLPOINTER, AKERR_OUTOFBOUNDS from a resize event
akgl_ui_frame_begin AKGL_ERR_UI — uninitialized, or a frame already open
akgl_ui_frame_end AKERR_NULLPOINTER, AKGL_ERR_UI — no frame open, clay layout errors, an over-long text run, an unresolvable fontId — plus whatever the draw primitives raise
akgl_ui_dialog, _label, _menu AKERR_NULLPOINTER, AKGL_ERR_UI outside the frame bracket; _label and _menu add AKERR_OUTOFBOUNDS
akgl_ui_menu_handle_event AKERR_NULLPOINTER, AKERR_OUTOFBOUNDS
akgl_ui_execute_commands AKERR_NULLPOINTER, AKGL_ERR_UI, plus whatever the draw primitives raise
akgl_ui_measure_text cannot report — clay's callback signature; failures stash and surface from akgl_ui_frame_end

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_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_* eight, AKGL_CCD_ARENA_BYTES and the AKGL_UI_* family are overridable, and even those have to be overridden for the whole build — see Chapter 5, "The ceilings are a compile-time ABI constraint". The rest are plain #defines with no #ifndef guard: changing one means editing the header and rebuilding libakgl and everything linking it.

The object pools

#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
#ifndef AKGL_MAX_HEAP_COLLISION_PROXY
#define AKGL_MAX_HEAP_COLLISION_PROXY          (AKGL_MAX_HEAP_ACTOR * 2)
#endif
#ifndef AKGL_MAX_HEAP_BSPNODE
#define AKGL_MAX_HEAP_BSPNODE                  (AKGL_MAX_HEAP_COLLISION_PROXY)
#endif
#ifndef AKGL_MAX_HEAP_COLLISION_CELL
#define AKGL_MAX_HEAP_COLLISION_CELL           (AKGL_MAX_HEAP_COLLISION_PROXY * 16)
#endif

Exceeding any of them is AKGL_ERR_HEAP.

Collision

Three of these are not in a public header, because nothing outside the broad phase may size anything against them:

Constant Value Where Bounds
AKGL_CCD_ARENA_BYTES 65536 collision_arena.h The narrowphase's whole allocation budget. Overridable. One GJK/EPA box pair measures 7,264 bytes
AKGL_COLLISION_GRID_COLS / _ROWS 128 src/collision_grid.c The dense cell-head array, 32 KB. A map wider than 128 cells wraps rather than growing
AKGL_COLLISION_GRID_MAX_SPAN 16 src/collision_grid.c Cells one proxy may occupy. Anything larger spills to one oversized chain, which is what makes AKGL_MAX_HEAP_COLLISION_CELL provable rather than hopeful
AKGL_COLLISION_MAX_SUBSTEPS 8 src/collision.c Sub-steps per actor per step, and therefore the speed above which tunnelling returns
AKGL_COLLISION_MAX_ITERATIONS 100 src/collision.c ccd_t.max_iterations. libccd's own default is (unsigned long)-1 — an unbounded loop inside a frame
AKGL_COLLISION_DEPTH_RATIO 2.0 collision.h The z extrusion, as a multiple of the largest planar half-extent. See Chapter 15 before changing it

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

/** @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

#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

/** @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

/** @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.

AKGL_DRAW_ROUNDED_RECT_SEGMENTS is 16 — triangles per corner of akgl_draw_filled_rounded_rect. AKGL_DRAW_ARC_MAX_POINTS is 64 — the vertex ceiling one akgl_draw_arc spends, two per step along the curve; the step count scales with the swept angle under it. Both size fixed arrays, so neither is a per-call cost and neither is overridable.

The UI

Every AKGL_UI_* limit is guarded by #ifndef, so a consumer may override any of them the way the pool ceilings are overridden — for the whole build, before <akgl/ui.h> is seen. akgl_ui_init checks that the arena still fits the element and word ceilings and refuses with both byte counts in the message when it does not, so a raised ceiling without a raised arena is a loud startup failure rather than a corruption.

Constant Value Bounds
AKGL_UI_MAX_ELEMENTS 1024 Elements one layout may declare
AKGL_UI_MAX_MEASURE_WORDS 4096 Words clay's text-measurement cache holds
AKGL_UI_ARENA_BYTES 1048576 Static storage clay lays its structures out in (812544 needed at the defaults)
AKGL_UI_MAX_FONTS 8 fontId table slots
AKGL_UI_FONT_NAME_LENGTH 64 Bytes per fontId slot's registry name, terminator included
AKGL_UI_MAX_TEXT_BYTES 1024 Bytes one text render command's line may occupy
AKGL_UI_MENU_MAX_ITEMS 16 Entries one akgl_UiMenu may carry
AKGL_UI_DIALOG_HEIGHT 56 Height of the akgl_ui_dialog panel, in pixels

Audio

/** @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 unusedAKGL_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 7
AKGL_ERR_LIMIT 263 — 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, and the startup order in Chapter 7.

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 — what each status means.
  • Chapter 5 — the pools, and the ABI constraint on their ceilings.
  • Chapter 6 — the property store these live in.
  • The generated Doxygen reference — every function's own @throws list and full signature.