Fix the type, macro and state-table defects, and the leftover debris

Closes internal-consistency items 19 through 36, 38 and 41. Item 37, the ~180
redundant casts, is deliberately left open with its reasoning in TODO.md: the
benefit only arrives once the build turns on the warnings those casts suppress,
and doing it before that is churn across the two files with the most
outstanding functional defects.

The two that were real bugs are in the actor state table.
AKGL_ACTOR_STATE_STRING_NAMES was declared [AKGL_ACTOR_MAX_STATES+1] and
defined [32], so a consumer trusting the declared bound read past the object;
and indices 11 and 12 were named UNDEFINED_11 and UNDEFINED_12 where actor.h
has MOVING_IN and MOVING_OUT, so no character JSON could bind a sprite to
either state. tests/registry.c now walks the whole table -- every entry
non-NULL, every entry resolving to its own bit, no two entries sharing a name.

The bitmask macros are parenthesized and AKGL_BITMASK_CLEAR has lost the
semicolon inside its body. Writing tests/bitmasks.c for that turned up
something worth knowing: the obvious test does not catch it. For a bit that is
set, the misparse `!(mask & bit) == bit` gives the same answer as the correct
one. It only diverges for an unset bit whose value is not 1, and that is the
shape the suite uses now.

akgl_draw_background was the last public function outside the error protocol.
It takes a backend like everything else in draw.h, restores the draw colour it
found, and is tested -- TODO.md had it filed under "needs the offscreen
renderer harness", which was never true; what it needed was to stop reading the
global.

All eight registry initializers go through one helper, so the seven that leaked
an SDL_PropertiesID on every call after the first no longer do. Fixed in the
same place because it is the same function: akgl_registry_init never called
akgl_registry_init_properties, which made akgl_set_property a silent no-op for
anyone not going through akgl_game_init -- Defects, Known and still open item 3.

Also: AKGL_COLLIDE_RECTANGLES (three open parens, two closes) and akgl_Frame
deleted, float32_t/float64_t used consistently, the developer-specific debug
logging removed from the controller inner loop, the abandoned SDL_GetBasePath
comments removed, nine unused locals removed, and dst renamed to dest.

akgl_game_update's default flags no longer OR the same bit twice. That changes
nothing today, and the reason is Performance item 32: the loop never reads
either bit, which is why every actor is updated sixteen times a frame. Still
open.

25/25 pass, memcheck clean, reindent --check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
2026-08-01 00:15:36 -04:00
parent 7ce9333ee2
commit 5664995086
28 changed files with 735 additions and 398 deletions

View File

@@ -74,13 +74,6 @@
/* ==================== GAME STATE VARIABLES =================== */
/** @brief Describes a renderable frame. Declared but not used by anything in the library. */
typedef struct {
float32_t w; /**< Width in pixels. */
float32_t h; /**< Height in pixels. */
SDL_Texture *texture; /**< The frame's texture. */
} akgl_Frame;
/** @brief Stores application-defined game-state flags. */
typedef struct {
int32_t flags; /**< Meaning is entirely the application's; the library never reads it. Guard changes with akgl_game_state_lock. */
@@ -132,19 +125,29 @@ extern SDL_FRect *akgl_camera;
/**
* @brief True when every bit of `y` is set in `x`. Not "any of them" -- all of them.
* @warning Unparenthesized: it expands to `(x & y) == y`, so `!AKGL_BITMASK_HAS(a, b)`
* parses as `!(a & b) == b`. Use #AKGL_BITMASK_HASNOT rather than
* negating this. TODO.md item 21.
*
* Fully parenthesized, so it composes: `!AKGL_BITMASK_HAS(a, b)` and
* `AKGL_BITMASK_HAS(a, b) && cond` both mean what they read as. Until 0.5.0 it
* expanded to a bare `(x & y) == y`, so a negation bound to the `&` and parsed
* as `!(a & b) == b`. Nothing in the tree negated it -- #AKGL_BITMASK_HASNOT
* exists for that -- which is the only reason it was latent rather than live.
*/
#define AKGL_BITMASK_HAS(x, y) (x & y) == y
/** @brief True when at least one bit of `y` is missing from `x`. Same parenthesization caveat as #AKGL_BITMASK_HAS. */
#define AKGL_BITMASK_HASNOT(x, y) (x & y) != y
#define AKGL_BITMASK_HAS(x, y) ((((x) & (y)) == (y)))
/** @brief True when at least one bit of `y` is missing from `x`. */
#define AKGL_BITMASK_HASNOT(x, y) ((((x) & (y)) != (y)))
/** @brief Set every bit of `y` in `x`. Modifies `x`. */
#define AKGL_BITMASK_ADD(x, y) x |= y
#define AKGL_BITMASK_ADD(x, y) ((x) |= (y))
/** @brief Clear every bit of `y` in `x`. Modifies `x`. */
#define AKGL_BITMASK_DEL(x, y) x &= ~(y)
/** @brief Clear every bit of `x`. Carries its own trailing semicolon, so do not add another. */
#define AKGL_BITMASK_CLEAR(x) x = 0;
#define AKGL_BITMASK_DEL(x, y) ((x) &= ~(y))
/**
* @brief Clear every bit of `x`. Modifies `x`.
*
* Carries no trailing semicolon: write `AKGL_BITMASK_CLEAR(flags);` like any
* other statement. It used to include one, so every ordinary use produced an
* empty statement after it and a use as the whole body of an unbraced `if`
* would have taken the following statement with it.
*/
#define AKGL_BITMASK_CLEAR(x) ((x) = 0)
/**
* @brief Bring the whole library up: error codes, pools, registries, SDL, audio, fonts, gamepads.