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

@@ -24,16 +24,6 @@
#include "testutil.h"
/*
* akgl/controller.h declares these as akgl_controller_handle_*, but src has
* always defined them as gamepad_handle_*. Declared here under the names that
* actually link, so the tests can reach them without renaming shipped symbols.
*/
akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event);
akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event);
akerr_ErrorContext *gamepad_handle_added(void *appstate, SDL_Event *event);
akerr_ErrorContext *gamepad_handle_removed(void *appstate, SDL_Event *event);
/** @brief Keyboard id the tests bind their control maps to. */
#define TEST_KBID 11
/** @brief Gamepad id the tests bind their control maps to. */
@@ -424,7 +414,7 @@ akerr_ErrorContext *test_controller_gamepad_button_handlers(void)
player->state = 0;
player->movement_controls_face = false;
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event),
TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event),
"dpad down press handler");
TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_DOWN),
"the dpad down handler did not set MOVING_DOWN (state %d)", player->state);
@@ -432,7 +422,7 @@ akerr_ErrorContext *test_controller_gamepad_button_handlers(void)
TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_FACE_DOWN),
"the dpad down handler did not set FACE_DOWN (state %d)", player->state);
TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event),
TEST_EXPECT_OK(e, akgl_controller_handle_button_up(&appstate_placeholder, &event),
"dpad down release handler");
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_MOVING_DOWN),
"the dpad down release handler did not clear MOVING_DOWN (state %d)", player->state);
@@ -441,55 +431,55 @@ akerr_ErrorContext *test_controller_gamepad_button_handlers(void)
player->state = 0;
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_UP);
TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad up press handler");
TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event), "dpad up press handler");
TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_UP),
"the dpad up handler did not set MOVING_UP (state %d)", player->state);
TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad up release handler");
TEST_EXPECT_OK(e, akgl_controller_handle_button_up(&appstate_placeholder, &event), "dpad up release handler");
player->state = 0;
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad left press handler");
TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event), "dpad left press handler");
TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_LEFT),
"the dpad left handler did not set MOVING_LEFT (state %d)", player->state);
TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad left release handler");
TEST_EXPECT_OK(e, akgl_controller_handle_button_up(&appstate_placeholder, &event), "dpad left release handler");
player->state = 0;
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad right press handler");
TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event), "dpad right press handler");
TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_RIGHT),
"the dpad right handler did not set MOVING_RIGHT (state %d)", player->state);
TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad right release handler");
TEST_EXPECT_OK(e, akgl_controller_handle_button_up(&appstate_placeholder, &event), "dpad right release handler");
// With automatic facing on, the handler leaves facing to the actor's own
// face function.
player->state = 0;
player->movement_controls_face = true;
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event),
TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event),
"dpad down press with automatic facing on");
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_FACE_DOWN),
"the handler set facing even though the actor faces automatically (state %d)",
player->state);
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_down(NULL, &event),
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_button_down(NULL, &event),
"dpad press handler with a NULL appstate");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
gamepad_handle_button_down(&appstate_placeholder, NULL),
akgl_controller_handle_button_down(&appstate_placeholder, NULL),
"dpad press handler with a NULL event");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_up(NULL, &event),
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_button_up(NULL, &event),
"dpad release handler with a NULL appstate");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
gamepad_handle_button_up(&appstate_placeholder, NULL),
akgl_controller_handle_button_up(&appstate_placeholder, NULL),
"dpad release handler with a NULL event");
// With no actor named "player" registered there is nothing to drive.
CATCH(e, akgl_registry_init_actor());
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
gamepad_handle_button_down(&appstate_placeholder, &event),
akgl_controller_handle_button_down(&appstate_placeholder, &event),
"dpad press handler with no player actor registered");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
gamepad_handle_button_up(&appstate_placeholder, &event),
akgl_controller_handle_button_up(&appstate_placeholder, &event),
"dpad release handler with no player actor registered");
} CLEANUP {
} PROCESS(e) {
@@ -506,22 +496,22 @@ akerr_ErrorContext *test_controller_device_events(void)
// The dummy drivers present no gamepads, so the add and remove handlers
// take their "unknown device" paths. Neither may crash.
make_button_event(&event, SDL_EVENT_GAMEPAD_ADDED, 999, 0);
TEST_EXPECT_OK(e, gamepad_handle_added(&appstate_placeholder, &event),
TEST_EXPECT_OK(e, akgl_controller_handle_added(&appstate_placeholder, &event),
"handling an add for a device that cannot be opened");
make_button_event(&event, SDL_EVENT_GAMEPAD_REMOVED, 999, 0);
TEST_EXPECT_OK(e, gamepad_handle_removed(&appstate_placeholder, &event),
TEST_EXPECT_OK(e, akgl_controller_handle_removed(&appstate_placeholder, &event),
"handling a remove for a device that was never open");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_added(NULL, &event),
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_added(NULL, &event),
"add handler with a NULL appstate");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
gamepad_handle_added(&appstate_placeholder, NULL),
akgl_controller_handle_added(&appstate_placeholder, NULL),
"add handler with a NULL event");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_removed(NULL, &event),
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_removed(NULL, &event),
"remove handler with a NULL appstate");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
gamepad_handle_removed(&appstate_placeholder, NULL),
akgl_controller_handle_removed(&appstate_placeholder, NULL),
"remove handler with a NULL event");
} CLEANUP {
} PROCESS(e) {

View File

@@ -24,14 +24,6 @@
#include "testutil.h"
/*
* Exported by src/game.c but not declared in akgl/game.h, because they are
* savegame internals rather than part of the supported surface. Declared here
* so the tests can reach them without widening the public API.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp);
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion);
/** @brief Scratch savegame path, created and removed by the tests that use it. */
static char savepath[] = "akgl_test_savegame.bin";
/** @brief Scratch path for deliberately malformed savegames. */

View File

@@ -0,0 +1,42 @@
/**
* @file header_pool_override.c
* @brief Checks that the compile-time pool-size overrides in heap.h actually fire.
*
* `heap.h` wraps every `AKGL_MAX_HEAP_*` ceiling in an `#ifndef` so a game that
* needs more actors can define its own before including it. That mechanism was
* dead until 0.5.0: `actor.h`, `sprite.h` and `character.h` defined the same
* four unconditionally, and `heap.h` includes all three *above* its own
* `#ifndef` block, so whichever landed first won and the guard never fired. The
* override was documented, and did nothing.
*
* The assertion is the compile. Nothing here links against the pools -- it
* cannot, because the library was built with the default ceilings and this
* translation unit deliberately disagrees with it. That disagreement is the
* documented consequence of overriding a ceiling, not something this file is
* working around: the arrays are sized at compile time, so the library and
* everything linking it have to agree.
*/
#define AKGL_MAX_HEAP_ACTOR 128
#define AKGL_MAX_HEAP_STRING 512
#include <akgl/heap.h>
#if AKGL_MAX_HEAP_ACTOR != 128
#error "heap.h overrode a caller's AKGL_MAX_HEAP_ACTOR; the #ifndef guard is dead again"
#endif
#if AKGL_MAX_HEAP_STRING != 512
#error "heap.h overrode a caller's AKGL_MAX_HEAP_STRING; the #ifndef guard is dead again"
#endif
/* The derived ceilings have to follow the override rather than a stale literal. */
#if AKGL_MAX_HEAP_SPRITE != (128 * 16)
#error "AKGL_MAX_HEAP_SPRITE no longer derives from AKGL_MAX_HEAP_ACTOR"
#endif
int akgl_header_selftest_pool_override(void);
int akgl_header_selftest_pool_override(void)
{
return 0;
}