Give every exported function a declaration, and check that it stays that way

Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.

The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.

The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.

scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.

The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.

Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.

24/24 pass, reindent --check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 23:44:38 -04:00
parent 9924d74dcc
commit 3a262bee54
29 changed files with 676 additions and 620 deletions

View File

@@ -109,8 +109,6 @@ extern char *AKGL_ACTOR_STATE_STRING_NAMES[AKGL_ACTOR_MAX_STATES+1];
/** @brief Children one actor can carry. A child moves with its parent rather than simulating. */
#define AKGL_ACTOR_MAX_CHILDREN 8
/** @brief Actors in the pool. Override before including heap.h to change it; see heap.h. */
#define AKGL_MAX_HEAP_ACTOR 64
/** @brief Represents a live actor, including state, motion, hierarchy, and behavior callbacks. */
typedef struct akgl_Actor {

View File

@@ -20,7 +20,6 @@
#include <akgl/sprite.h>
#define AKGL_CHARACTER_MAX_NAME_LENGTH 128
#define AKGL_MAX_HEAP_CHARACTER 256
/** @brief Defines reusable movement parameters and actor-state sprite bindings. */
typedef struct akgl_Character {
@@ -62,7 +61,7 @@ typedef struct akgl_Character {
* #AKGL_REGISTRY_CHARACTER -- in practice, because akgl_registry_init
* has not run and the registry id is still 0.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_initialize(akgl_Character *basechar, char *name);
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_initialize(akgl_Character *obj, char *name);
/**
* @brief Bind a sprite to one exact combination of actor-state bits.
*

View File

@@ -138,49 +138,77 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void);
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_event(void *appstate, SDL_Event *event);
/**
* @brief Declared but not defined under this name. Do not call.
* @brief Set the movement and facing bits on the "player" actor for a D-pad or arrow press.
*
* The implementation exists as `gamepad_handle_button_down` in `src/controller.c`
* and is `static`-in-spirit -- it is not declared anywhere -- so a caller that
* uses this declaration compiles and then fails to link. TODO.md, "Known and
* still open" item 10.
* A whole-application shortcut rather than a control-map binding: it looks the
* actor up by the literal name `"player"` in #AKGL_REGISTRY_ACTOR instead of
* being told which actor to act on, so it only ever drives one. The
* akgl_actor_cmhf_* handlers are the general form.
*
* @param appstate Application state supplied by SDL.
* @param event SDL input event to process.
* @return Nothing; it cannot be called.
* Facing follows movement unless the actor sets `movement_controls_face`, which
* is how an actor that aims independently of the direction it walks opts out.
*
* @param appstate Passed through from SDL. Required as a non-`NULL` token only;
* never read.
* @param event The button or key event. Required. Both the gamepad and
* keyboard unions are read on every call, so the arm that does
* not correspond to the event type is read as garbage -- which
* works only because no real button and keycode pair collides.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`, or if there is
* no actor registered under the name "player".
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_down(void *appstate, SDL_Event *event);
/**
* @brief Declared but not defined under this name. Do not call.
* @brief Clear the movement bit on the "player" actor for a D-pad or arrow release, and reset its animation.
*
* See akgl_controller_handle_button_down. The implementation is
* `gamepad_handle_button_up`.
* The counterpart to akgl_controller_handle_button_down. It clears only the movement
* bit, leaving the facing bits alone so the actor keeps looking the way it was
* walking, and rewinds `curSpriteFrameId` to 0 so the next step starts from the
* first frame of the walk cycle rather than wherever it stopped.
*
* @param appstate Application state supplied by SDL.
* @param event SDL input event to process.
* @return Nothing; it cannot be called.
* @param appstate Passed through from SDL. Required as a non-`NULL` token only.
* @param event The button or key release event. Required.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`, or if there is
* no actor registered under the name "player".
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_up(void *appstate, SDL_Event *event);
/**
* @brief Declared but not defined under this name. Do not call.
* @brief Open a gamepad that has just been plugged in, and log its mapping.
*
* See akgl_controller_handle_button_down. The implementation is
* `gamepad_handle_added`, which opens a newly plugged-in gamepad.
* SDL delivers no button events from an unopened gamepad, so a device that
* appears mid-session has to be opened here the way akgl_controller_open_gamepads
* opens the ones present at startup. A gamepad SDL has already opened is logged
* and left alone.
*
* @param appstate Application state supplied by SDL.
* @param event SDL input event to process.
* @return Nothing; it cannot be called.
* The mapping is logged because a controller with no entry in the database
* produces no button events at all, and that is otherwise indistinguishable
* from a broken binding.
*
* @param appstate Passed through from SDL. Required as a non-`NULL` token only.
* @param event The SDL_EVENT_GAMEPAD_ADDED event. Required. The joystick id
* is read out of the `gbutton` arm rather than `gdevice`, which
* works because the two share their `which` field.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`.
*
* @note A failed `SDL_OpenGamepad` is logged rather than reported: this returns
* success and the device stays silent.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_added(void *appstate, SDL_Event *event);
/**
* @brief Declared but not defined under this name. Do not call.
* @brief Close a gamepad that has been unplugged.
*
* See akgl_controller_handle_button_down. The implementation is
* `gamepad_handle_removed`, which closes an unplugged gamepad.
* An unplugged device SDL still has open is a leaked handle, so this closes it.
* A removal for a device that was never opened is logged and otherwise ignored.
* Any control map still holding that `jsid` simply stops matching -- the map is
* not torn down.
*
* @param appstate Application state supplied by SDL.
* @param event SDL input event to process.
* @return Nothing; it cannot be called.
* @param appstate Passed through from SDL. Required as a non-`NULL` token only.
* @param event The SDL_EVENT_GAMEPAD_REMOVED event. Required.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate, SDL_Event *event);

View File

@@ -177,17 +177,7 @@ extern SDL_FRect *akgl_camera;
* @note It takes the state lock on the way in and releases it on the way out, so
* a failure part-way through leaves the mutex held.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init();
/**
* @brief Declared but never defined. Do not call.
*
* There is no definition anywhere in the library, so a translation unit that
* calls this compiles and then fails to link. Screen setup is akgl_render_2d_init.
* Tracked in TODO.md under header/implementation surface drift.
*
* @return Nothing; it cannot be called.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init_screen();
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init(void);
/**
* @brief Count this frame, and recompute the frame rate once a second has passed.
*
@@ -198,7 +188,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init_screen();
* the low-FPS threshold and so fires `lowfpsfunc` on every frame until the first
* second is up.
*/
void akgl_game_update_fps();
void akgl_game_update_fps(void);
/**
* @brief Write the game state and the name-to-pointer tables to a save file.
*
@@ -311,4 +301,51 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void);
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags);
/*
* These functions are part of the internal API and should not be called by the
* user. They are only exposed here for unit testing.
*/
/**
* @brief Write all four name-to-address tables, each with its terminator.
*
* Actors, sprites, spritesheets, characters, in that order -- which is the order
* akgl_game_load reads them back in. Each table is the registry enumerated one
* entry at a time, followed by a zeroed name field and a zeroed pointer that the
* reader stops on.
*
* @param fp The open save-game stream. Required.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p fp is `NULL`.
* @throws AKERR_IO On a stream error or a short write while emitting a
* terminator.
*
* @note Only the terminators are written through the error-reporting path. The
* entries themselves go through `SDL_EnumerateProperties`, whose callbacks
* cannot report failure upward and instead terminate the process -- so a
* write error mid-table never reaches this function's return value.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp);
/**
* @brief Refuse a save file unless its version matches the running one exactly.
*
* Both strings are parsed as semver and compared with `"="` -- exact equality,
* not compatibility. That is deliberate and strict: a save file is a raw memory
* image of `akgl_Game` and the object tables, so any change to a struct layout
* invalidates it, and semver has no way to say "the layout did not move".
*
* @param versiontype What is being compared -- `"library"` or `"game"`. Used
* only to build the message, but required, since a bare
* "incompatible version" error would not say which.
* @param newversion The version read out of the save file. Required.
* @param curversion The version of the running program or library. Required.
* @return `NULL` when the two match, otherwise an error context owned by the
* caller.
* @throws AKERR_NULLPOINTER If any of the three arguments is `NULL`.
* @throws AKERR_VALUE If either string is not valid semver. The message says
* which side it came from.
* @throws AKERR_API If both parse but are not equal. The message quotes both.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion);
#endif //_AKGL_GAME_H_

View File

@@ -74,7 +74,7 @@ extern akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING];
* -- but check it anyway; the signature exists so a layer that needs
* real setup has somewhere to report from.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init();
akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init(void);
/**
* @brief Zero the actor pool only.
*
@@ -85,7 +85,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init();
*
* @return `NULL`. No failure path today; see akgl_heap_init.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init_actor();
akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init_actor(void);
/**
* @brief Claim a free actor slot.
* @param dest Receives a pointer to the free slot. Required, and **not**

View File

@@ -64,7 +64,7 @@ extern SDL_PropertiesID AKGL_REGISTRY_PROPERTIES;
* @warning This does not create #AKGL_REGISTRY_PROPERTIES -- see the warning on
* this file.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init();
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init(void);
/**
* @brief Create the music registry.
* @return `NULL` on success, otherwise an error context owned by the caller.
@@ -72,7 +72,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init();
* @note Overwrites the existing id without destroying the old set, so calling it
* twice leaks the first one along with everything registered in it.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_music();
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_music(void);
/**
* @brief Create the font registry.
* @return `NULL` on success, otherwise an error context owned by the caller.
@@ -80,7 +80,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_music();
* @note Overwrites the existing id without destroying the old set. See
* akgl_registry_init_music.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font();
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font(void);
/**
* @brief Create the actor registry, destroying any previous one first.
*
@@ -94,7 +94,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font();
* @throws AKERR_NULLPOINTER If SDL cannot allocate the property set. The old one
* has already been destroyed at that point, so the registry is left at 0.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor();
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor(void);
/**
* @brief Create the sprite registry.
* @return `NULL` on success, otherwise an error context owned by the caller.
@@ -102,7 +102,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor();
* @note Overwrites the existing id without destroying the old set. See
* akgl_registry_init_music.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_sprite();
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_sprite(void);
/**
* @brief Create the spritesheet registry.
* @return `NULL` on success, otherwise an error context owned by the caller.
@@ -110,7 +110,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_sprite();
* @note Overwrites the existing id without destroying the old set. See
* akgl_registry_init_music.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_spritesheet();
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_spritesheet(void);
/**
* @brief Create the character registry.
* @return `NULL` on success, otherwise an error context owned by the caller.
@@ -118,7 +118,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_spritesheet();
* @note Overwrites the existing id without destroying the old set. See
* akgl_registry_init_music.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character();
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character(void);
/**
* @brief Create the configuration-property registry.
*
@@ -133,7 +133,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character();
* @note Overwrites the existing id without destroying the old set, so every
* property set before a second call is lost.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_properties();
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_properties(void);
/**
* @brief Load a JSON configuration file into the property registry.
*
@@ -172,7 +172,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_load_properties(char *fname);
* and `MOVING_OUT`, so those two states cannot be named from JSON at all.
* TODO.md items 24-26.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor_state_strings();
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor_state_strings(void);
/**
* @brief Set one configuration property.
*

View File

@@ -28,8 +28,6 @@
#define AKGL_SPRITE_MAX_REGISTRY_SIZE 1024
#define AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH 512
#define AKGL_MAX_HEAP_SPRITE (AKGL_MAX_HEAP_ACTOR * 16)
#define AKGL_MAX_HEAP_SPRITESHEET AKGL_MAX_HEAP_SPRITE
/** @brief Stores a loaded spritesheet texture and frame geometry. */
typedef struct {

View File

@@ -206,19 +206,18 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load(char *fname, akgl_Tilemap *
*
* Drawing goes through the global `akgl_renderer`, not through a backend passed in.
*
* @param dest The map to draw from. Required. Not an output parameter
* despite the name.
* @param map The map to draw from. Required.
* @param viewport The rectangle of the map, in map pixels, that is on screen.
* Required. Usually the global `akgl_camera`.
* @param layeridx Which layer to draw, 0 to `numlayers - 1`. **Not**
* bounds-checked: an index past the end reads a neighbouring
* layer, or past the array.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p dest or @p viewport is `NULL`.
* @throws AKERR_NULLPOINTER If @p map or @p viewport is `NULL`.
* @throws AKERR_* Whatever the renderer's `draw_texture` raises. The first
* failure abandons the rest of the layer.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw(akgl_Tilemap *dest, SDL_FRect *viewport, int layeridx);
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw(akgl_Tilemap *map, SDL_FRect *viewport, int layeridx);
/**
* @brief Draw a whole tileset to the screen, tile by tile, from its offset table.
*
@@ -227,11 +226,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw(akgl_Tilemap *dest, SDL_FRe
* matches the source image the offset table is right -- and if tiles are shifted
* or duplicated, it is not. The `charviewer` utility uses it.
*
* @param dest The map holding the tileset. Required. Not an output
* parameter despite the name.
* @param map The map holding the tileset. Required.
* @param tilesetidx Which tileset to draw, 0 to `numtilesets - 1`.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p dest is `NULL`.
* @throws AKERR_NULLPOINTER If @p map is `NULL`.
* @throws AKERR_OUTOFBOUNDS If @p tilesetidx is at or above `numtilesets`. A
* negative index is not rejected.
* @throws AKERR_* Whatever the renderer's `draw_texture` raises.
@@ -240,7 +238,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw(akgl_Tilemap *dest, SDL_FRe
* tileset's, so a tileset whose tiles are a different size from the map's
* is reconstructed at the wrong pitch.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw_tileset(akgl_Tilemap *dest, int tilesetidx);
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw_tileset(akgl_Tilemap *map, int tilesetidx);
/*
* These functions are part of the internal API and should not be called by the user.
@@ -490,4 +488,152 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_release(akgl_Tilemap *dest);
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, akgl_Actor *actor);
/**
* @brief Read a Tiled custom property declared as `number`.
*
* Tiled writes `float` for a floating-point property, so this matches nothing
* Tiled produces today -- akgl_get_json_properties_float is the one the loader
* uses. Kept because a hand-written or older map may still say `number`.
*
* @param obj The Tiled object whose `properties` array to search. Required.
* @param key The property name. Required.
* @param dest Receives the value as a `float`. Not written on any failure path.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`.
* @throws AKERR_KEY If the property is absent, or @p obj has no properties.
* @throws AKERR_TYPE If the property is not declared `number`, or its `value` is
* not numeric.
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_number(json_t *obj, char *key, float *dest);
/**
* @brief Read a Tiled custom property declared as `float`.
*
* The spelling Tiled actually writes for a floating-point property. This is how
* the `scale` on a perspective marker is read.
*
* @param obj The Tiled object whose `properties` array to search. Required.
* @param key The property name. Required.
* @param dest Receives the value. Not written on any failure path.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`.
* @throws AKERR_KEY If the property is absent, or @p obj has no properties.
* @throws AKERR_TYPE If the property is not declared `float`, or its `value` is
* not numeric.
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_float(json_t *obj, char *key, float *dest);
/**
* @brief Read a Tiled custom property declared as `float`, at full precision.
*
* Same declared type as akgl_get_json_properties_float, but writing a `double`.
* The map's physics constants are `double`, which is the reason both exist.
*
* @param obj The Tiled object whose `properties` array to search. Required.
* @param key The property name. Required.
* @param dest Receives the value. Not written on any failure path.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`.
* @throws AKERR_KEY If the property is absent, or @p obj has no properties.
* akgl_tilemap_load_physics passes this status to
* akgl_get_json_with_default, which turns it into a default of 0.0.
* @throws AKERR_TYPE If the property is not declared `float`, or its `value` is
* not numeric.
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_double(json_t *obj, char *key, double *dest);
/**
* @brief Turn one Tiled object into a live actor, creating it if it is not already registered.
*
* The object's name is the actor's registry key, which is what lets the same
* actor be placed by more than one object: the first placement creates it and
* binds the character named in the object's `character` property, and every
* later one just takes another reference. Either way the object's position,
* visibility, and layer are copied onto the actor and the object keeps a
* pointer to it.
*
* Not declared in tilemap.h -- reachable only through
* akgl_tilemap_load_layer_objects.
*
* @param curobj The tilemap object, with its `name`, `x`, `y`, and
* `visible` already read from JSON. Required, unchecked,
* dereferenced at once.
* @param layerdatavalue The object's JSON, for the custom properties --
* `character` and `state` -- that are not part of Tiled's
* own object fields. Required.
* @param layerid The layer this object sits on, copied onto the actor.
* @param dirname Directory to resolve paths against. Accepted for
* signature consistency; nothing here uses it.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_KEY If the object's name is empty -- an actor has to be
* addressable -- if the `character` or `state` property is absent, or if
* the named character is not in #AKGL_REGISTRY_CHARACTER.
* @throws AKERR_TYPE If `character` is not declared `string` or `state` not
* declared `int`.
* @throws AKERR_NULLPOINTER If akgl_actor_initialize refuses the name.
* @throws AKGL_ERR_HEAP If the actor or string pool is exhausted.
*
* @note An existing actor's `character` and position are overwritten by each
* later placement, so two objects naming one actor leave it wherever the
* last one put it rather than producing two.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *curobj, json_t *layerdatavalue, int layerid, akgl_String *dirname);
/**
* @brief Load one image layer: upload its image as a single texture.
*
* An image layer is one picture rather than a grid, which is what a parallax
* backdrop wants. The layer's `width` and `height` are taken from the loaded
* texture rather than from the JSON, so they are always the true pixel size.
*
* Not declared in tilemap.h -- reachable only through akgl_tilemap_load_layers.
*
* @param dest The map to load into. Required.
* @param root The layer's JSON object. Required.
* @param layerid Which layer slot to fill. Not bounds-checked.
* @param dirname Directory to resolve the layer's `image` path against.
* Required. Joined with a `/` rather than run through
* akgl_path_relative, so unlike a tileset image this path is not
* canonicalized and an absolute one will not work.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p dest, @p root, or @p dirname is `NULL`.
* @throws AKERR_KEY If the layer has no `image`.
* @throws AKERR_TYPE If `image` is not a string.
* @throws AKGL_ERR_SDL If the image cannot be loaded. The message carries
* `SDL_GetError()`.
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
*
* @note The joined path is truncated at
* #AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE without complaint, so an
* over-long path fails as a missing file rather than as a length error.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname);
/**
* @brief Give the map its own physics backend, if it asked for one.
*
* A map with a `physics.model` custom property gets its own backend, configured
* from `physics.gravity.x`/`.y`/`.z` and `physics.drag.x`/`.y`/`.z`, and
* `use_own_physics` is set so a caller knows to simulate through it. Each
* constant defaults to 0.0 when absent, so a map can name a model and override
* only what it cares about.
*
* Absence is the normal case at every level: a map with no properties at all, or
* with properties but no `physics.model`, uses the game's global backend and
* this returns success. That is why the AKERR_KEY handlers here are not error
* paths -- they are the "map did not ask" path.
*
* Not declared in tilemap.h -- reachable only through akgl_tilemap_load.
*
* @param dest The map to configure. Required in practice; not checked here,
* since akgl_tilemap_load has already done so.
* @param root The map's root JSON object. Required, likewise.
* @return `NULL` on success -- including when the map declared no physics at
* all -- otherwise an error context owned by the caller.
* @throws AKERR_KEY If `physics.model` names a backend that does not exist.
* @throws AKERR_TYPE If `physics.model` is not declared `string`, or one of the
* six constants is not declared `float`.
* @throws AKERR_NULLPOINTER If akgl_physics_factory refuses its arguments.
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_physics(akgl_Tilemap *dest, json_t *root);
#endif //_AKGL_TILEMAP_H_