2025-08-03 10:07:35 -04:00
|
|
|
#include <SDL3/SDL.h>
|
Check memory with the suites that already exist
`cmake --build build --target memcheck` runs every registered CTest suite
under valgrind. There are no new test programs, and there should not be any:
tests/benchutil.h notices valgrind in LD_PRELOAD and drops the benchmark
scale to 0.0005, which turns the perf suites into the broadest path coverage
in the tree at a cost valgrind can survive. A benchmark walks one path a
hundred thousand times; a leak check wants every path walked once. Same
binaries, one flag apart, and the whole run is about thirty seconds.
scripts/memcheck.sh wraps `ctest -T memcheck` because that command records
defects and still exits 0, which cannot gate anything. It also forces the
headless drivers, so the vendor GPU stack is never loaded -- that removes
thousands of unfixable findings inside amdgpu_dri.so without suppressing
anything, and it is what the suites are written for anyway. Only definite
losses, invalid accesses and uninitialised reads count; "still reachable" is
what SDL and FreeType keep for the process lifetime and says nothing about
this library. The three suppressions in scripts/valgrind.supp are each a
decision that a finding belongs to somebody else.
Six defects, all filed in TODO.md under "Memory checking", the first of
which is the one that matters: not one of the four json_load_file calls in
src/ is ever matched by a json_decref, so every asset load abandons its
parsed document -- 1.5 KB per sprite, 2.1 KB per character, 9 KB per load of
the 2x2 fixture map, and on the order of a megabyte for a real one. A game
that reloads a level on death leaks a level's worth of JSON every time.
akgl_get_property also reads up to 4 KiB past the end of every property
value it copies, and the savegame name tables read past the end of every
registry key and write what they find into the file.
tests/util.c zeroes three fixtures it used to leave as stack garbage. The
library was never at fault there -- the tests handed it uninitialised floats
and then made one real call with them -- but sixteen findings of noise in a
new gate is how a gate gets ignored.
The unit suites' TIMEOUT goes from 30 to 300 because CTest applies the same
property to the checked run, where everything is twenty times slower.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 14:59:57 -04:00
|
|
|
#include <string.h>
|
2026-01-05 08:58:06 -05:00
|
|
|
#include <akerror.h>
|
2026-07-29 18:01:05 -04:00
|
|
|
#include <akgl/error.h>
|
2026-07-31 14:23:47 -04:00
|
|
|
#include <akgl/heap.h>
|
|
|
|
|
#include <akgl/staticstring.h>
|
2026-05-07 22:20:10 -04:00
|
|
|
#include <akgl/util.h>
|
2025-08-03 10:07:35 -04:00
|
|
|
|
Namespace every exported symbol, and bump to 0.5.0
Closes internal-consistency items 1 through 6, 12 and 13. Every include guard
is _AKGL_<FILE>_H_, every in-project header include is angled, and every
exported function, type and global carries the akgl_ prefix. This is an ABI
break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename
table.
The renames were driven by renaming each declaration and letting the compiler
find the uses, not by pattern substitution: renderer, physics and camera are
also parameter and struct-member names, and a sed would have rewritten
map->physics and every akgl_RenderBackend *renderer parameter without a word.
Item 4 turned out not to be cosmetic. The library exported a global called
renderer and tests/character.c defined an SDL_Renderer *renderer of its own;
the executable's definition preempted the library's, akgl_sprite_load_json
read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load
in that suite failed. The suite reported success anyway, because libakerror's
unhandled-error handler ends in exit(errctx->status), exit keeps only the low
byte, and AKGL_ERR_SDL is exactly 256. So character had been green while
running one of its four tests, and every suite in the tree was unable to fail
on the most common status in a library built on SDL.
Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which
collapses any status a byte cannot carry onto 1, and every suite installs it.
character binds a real backend with akgl_render_2d_bind. Its fourth test then
runs for the first time and fails on a defect it has asserted all along, so
akgl_heap_release_character now walks state_sprites with
AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot
-- TODO.md Defects item 21 and half of Carried over item 1.
AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so
akgl_game_state_lock waited roughly sixteen minutes rather than one second. It
is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and
tests/game.c holds the mutex from a second thread to assert the wait -- the
contended path had no coverage at all.
Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both
install() and a generated translation unit per header, so a header that ships
is a header that is checked. Writing that found registry.h, which used
SDL_PropertiesID in eight declarations and included no SDL header.
23/23 suites pass, memcheck is clean, reindent --check is clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 23:32:21 -04:00
|
|
|
#include "testutil.h"
|
|
|
|
|
|
2026-07-31 14:23:47 -04:00
|
|
|
/**
|
|
|
|
|
* @brief How many entries of AKERR_ARRAY_ERROR are currently held by somebody.
|
|
|
|
|
*
|
|
|
|
|
* The error contexts are a fixed pool exactly like the object pools: a context
|
|
|
|
|
* is in use while its reference count is non-zero, and a function that finishes
|
|
|
|
|
* without releasing one has leaked a slot out of AKERR_MAX_ARRAY_ERROR.
|
|
|
|
|
*/
|
|
|
|
|
static int live_error_contexts(void)
|
|
|
|
|
{
|
|
|
|
|
int live = 0;
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
for ( i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
|
|
|
|
|
if ( AKERR_ARRAY_ERROR[i].refcount != 0 ) {
|
|
|
|
|
live += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return live;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *test_akgl_collide_rectangles_nullpointers(void)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
Check memory with the suites that already exist
`cmake --build build --target memcheck` runs every registered CTest suite
under valgrind. There are no new test programs, and there should not be any:
tests/benchutil.h notices valgrind in LD_PRELOAD and drops the benchmark
scale to 0.0005, which turns the perf suites into the broadest path coverage
in the tree at a cost valgrind can survive. A benchmark walks one path a
hundred thousand times; a leak check wants every path walked once. Same
binaries, one flag apart, and the whole run is about thirty seconds.
scripts/memcheck.sh wraps `ctest -T memcheck` because that command records
defects and still exits 0, which cannot gate anything. It also forces the
headless drivers, so the vendor GPU stack is never loaded -- that removes
thousands of unfixable findings inside amdgpu_dri.so without suppressing
anything, and it is what the suites are written for anyway. Only definite
losses, invalid accesses and uninitialised reads count; "still reachable" is
what SDL and FreeType keep for the process lifetime and says nothing about
this library. The three suppressions in scripts/valgrind.supp are each a
decision that a finding belongs to somebody else.
Six defects, all filed in TODO.md under "Memory checking", the first of
which is the one that matters: not one of the four json_load_file calls in
src/ is ever matched by a json_decref, so every asset load abandons its
parsed document -- 1.5 KB per sprite, 2.1 KB per character, 9 KB per load of
the 2x2 fixture map, and on the order of a megabyte for a real one. A game
that reloads a level on death leaks a level's worth of JSON every time.
akgl_get_property also reads up to 4 KiB past the end of every property
value it copies, and the savegame name tables read past the end of every
registry key and write what they find into the file.
tests/util.c zeroes three fixtures it used to leave as stack garbage. The
library was never at fault there -- the tests handed it uninitialised floats
and then made one real call with them -- but sixteen findings of noise in a
new gate is how a gate gets ignored.
The unit suites' TIMEOUT goes from 30 to 300 because CTest applies the same
property to the checked run, where everything is twenty times slower.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 14:59:57 -04:00
|
|
|
SDL_FRect testrect1 = {.x = 0, .y = 0, .w = 0, .h = 0};
|
|
|
|
|
SDL_FRect testrect2 = {.x = 0, .y = 0, .w = 0, .h = 0};
|
|
|
|
|
bool testcollide = false;
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, NULL));
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_rectangles(*, *, NULL) failed");
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
2026-05-03 23:57:55 -04:00
|
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
2025-08-03 10:07:35 -04:00
|
|
|
// noop
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, NULL, &testcollide));
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_rectangles(*, NULL, *) failed");
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
2026-05-03 23:57:55 -04:00
|
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
2025-08-03 10:07:35 -04:00
|
|
|
// noop
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(NULL, &testrect2, &testcollide));
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_rectangles(NULL, *, *) failed");
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
2026-05-03 23:57:55 -04:00
|
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
2025-08-03 10:07:35 -04:00
|
|
|
// noop
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(NULL, NULL, NULL));
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_rectangles(NULL, NULL, NULL) failed");
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
2026-05-03 23:57:55 -04:00
|
|
|
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
2025-08-03 10:07:35 -04:00
|
|
|
// noop
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Report the overlap akgl_collide_rectangles could not see
It asked whether either rectangle enclosed one of the other's four corners --
eight akgl_collide_point_rectangle calls, stopping at the first hit. That is a
different question from "do these overlap", and it has the wrong answer for one
arrangement: a tall thin rectangle crossing a short wide one overlaps in a plus
sign with no corner of either inside the other, and all eight tests said no.
A long thin platform crossing a tall thin character is exactly that shape, so
this is a shape a 2D game produces, not a curiosity. util.h carried an @note
describing it and docs/18-utilities.md had a diagram of it, both under the
heading of a limitation rather than a defect, and there was no test for it at
all -- nor for full containment, nor for a shared edge.
It is four comparisons now, on both axes. `<=` rather than `<` because
akgl_collide_point_rectangle is inclusive on all four edges and these two have
always agreed that touching counts; a span test written with `<` would have
silently changed a contract both the header and the manual state.
The test was written first and failed on the cross before the fix went in.
Two answers change for a caller upgrading, and both are in the header note and
the chapter:
- The cross reports `true`, which is the point.
- The comparison is in float rather than through akgl_Point's int members, so a
sub-pixel overlap is no longer truncated away. A pickup test that was
accidentally forgiving by up to a pixel is no longer forgiving. Both tutorials
use this for coins and hazards; both still pass.
Faster as a side effect rather than a goal, and worth recording because the
numbers move a documented budget: 24.9 ns -> 6.1 overlapping, 57.9 -> 6.1
disjoint, and the all-pairs sweep over 64 actors 115 us -> 12.2. The disjoint
case gained most because it was the one that ran all eight tests before
answering.
The three moved rows are re-recorded in PERFORMANCE.md and nothing else is.
akgl_rectangle_points is untouched by this change and reads 6.1 in the same run
against the 4.0 recorded, so 6 ns is this run's floor and the new figure means
"too cheap to measure" rather than "exactly 6.1" -- said in the prose so the
next reader does not re-baseline the table around it.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 23:12:01 -04:00
|
|
|
/**
|
|
|
|
|
* @brief The arrangements corner containment cannot see, and the ones it must keep.
|
|
|
|
|
*
|
|
|
|
|
* Eight corner-in-rectangle tests answer "do these overlap" correctly only when
|
|
|
|
|
* one rectangle encloses a corner of the other. A tall thin rectangle crossing a
|
|
|
|
|
* short wide one overlaps in a plus sign with no corner inside either, and eight
|
|
|
|
|
* corner tests all report false. That was documented on akgl_collide_rectangles
|
|
|
|
|
* as a known limitation rather than fixed, and there was no test for it.
|
|
|
|
|
*
|
Remove the corner helpers akgl_collide_rectangles no longer uses
akgl_rectangle_points, akgl_collide_point_rectangle, akgl_Point and
akgl_RectanglePoints go. They were the intermediate form of an implementation
that changed: akgl_collide_rectangles was eight corner-containment tests built on
them, and it has been four span comparisons since the cross-case fix. Nothing
outside tests/ called either function, and a point-in-rectangle test is four
comparisons a caller can write without a struct conversion in front of them.
akgl_collide_rectangles stays. It has two correct callers in the sidescroller
asking a game-level overlap question -- a coin, a hazard, from an updatefunc --
where a bool is the whole answer and a proxy plus a narrowphase call would be
computing a normal nothing reads. TODO.md records the split rather than leaving
it to be rediscovered.
Public API removal, so 194 exported akgl_ symbols against 196, and the manual's
counts move with them. The perf suite loses its rectangle_points row; the
all-pairs sweep stays as the control it is now labelled, and PERFORMANCE.md says
what 0.8.0 measured against it -- 188.5 us for 32,640 pairs at 256 actors, where
a whole step with collision attached is 54.1 us doing strictly more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 08:09:42 -04:00
|
|
|
* The three cases below the cross are the ones the rewrite could have broken
|
|
|
|
|
* while fixing it: touching edges must keep counting as a collision, because the
|
|
|
|
|
* corner form was inclusive on all four edges and the header promised it; full
|
|
|
|
|
* containment must keep working; and a separation of less than one pixel must be
|
|
|
|
|
* seen, which the old implementation could not do because it routed through the
|
|
|
|
|
* since-removed akgl_Point and truncated float to int.
|
Report the overlap akgl_collide_rectangles could not see
It asked whether either rectangle enclosed one of the other's four corners --
eight akgl_collide_point_rectangle calls, stopping at the first hit. That is a
different question from "do these overlap", and it has the wrong answer for one
arrangement: a tall thin rectangle crossing a short wide one overlaps in a plus
sign with no corner of either inside the other, and all eight tests said no.
A long thin platform crossing a tall thin character is exactly that shape, so
this is a shape a 2D game produces, not a curiosity. util.h carried an @note
describing it and docs/18-utilities.md had a diagram of it, both under the
heading of a limitation rather than a defect, and there was no test for it at
all -- nor for full containment, nor for a shared edge.
It is four comparisons now, on both axes. `<=` rather than `<` because
akgl_collide_point_rectangle is inclusive on all four edges and these two have
always agreed that touching counts; a span test written with `<` would have
silently changed a contract both the header and the manual state.
The test was written first and failed on the cross before the fix went in.
Two answers change for a caller upgrading, and both are in the header note and
the chapter:
- The cross reports `true`, which is the point.
- The comparison is in float rather than through akgl_Point's int members, so a
sub-pixel overlap is no longer truncated away. A pickup test that was
accidentally forgiving by up to a pixel is no longer forgiving. Both tutorials
use this for coins and hazards; both still pass.
Faster as a side effect rather than a goal, and worth recording because the
numbers move a documented budget: 24.9 ns -> 6.1 overlapping, 57.9 -> 6.1
disjoint, and the all-pairs sweep over 64 actors 115 us -> 12.2. The disjoint
case gained most because it was the one that ran all eight tests before
answering.
The three moved rows are re-recorded in PERFORMANCE.md and nothing else is.
akgl_rectangle_points is untouched by this change and reads 6.1 in the same run
against the 4.0 recorded, so 6 ns is this run's floor and the new figure means
"too cheap to measure" rather than "exactly 6.1" -- said in the prose so the
next reader does not re-baseline the table around it.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 23:12:01 -04:00
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext *test_akgl_collide_rectangles_arrangements(void)
|
|
|
|
|
{
|
|
|
|
|
SDL_FRect tall = { .x = 10.0f, .y = 0.0f, .w = 4.0f, .h = 40.0f };
|
|
|
|
|
SDL_FRect wide = { .x = 0.0f, .y = 10.0f, .w = 40.0f, .h = 4.0f };
|
|
|
|
|
SDL_FRect outer = { .x = 0.0f, .y = 0.0f, .w = 64.0f, .h = 64.0f };
|
|
|
|
|
SDL_FRect inner = { .x = 16.0f, .y = 16.0f, .w = 8.0f, .h = 8.0f };
|
|
|
|
|
SDL_FRect left = { .x = 0.0f, .y = 0.0f, .w = 10.0f, .h = 10.0f };
|
|
|
|
|
SDL_FRect right = { .x = 10.0f, .y = 0.0f, .w = 10.0f, .h = 10.0f };
|
|
|
|
|
SDL_FRect near1 = { .x = 0.0f, .y = 0.0f, .w = 10.9f, .h = 10.0f };
|
|
|
|
|
SDL_FRect near2 = { .x = 10.5f, .y = 0.0f, .w = 10.0f, .h = 10.0f };
|
|
|
|
|
bool collide = false;
|
|
|
|
|
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
// The cross. Neither rectangle encloses a corner of the other, and they
|
|
|
|
|
// plainly overlap in the middle.
|
|
|
|
|
CATCH(errctx, akgl_collide_rectangles(&tall, &wide, &collide));
|
|
|
|
|
TEST_ASSERT(errctx, (collide == true),
|
|
|
|
|
"a tall rectangle crossing a wide one was reported as not colliding");
|
|
|
|
|
|
|
|
|
|
// The same pair the other way round. The test is symmetric and has to be.
|
|
|
|
|
CATCH(errctx, akgl_collide_rectangles(&wide, &tall, &collide));
|
|
|
|
|
TEST_ASSERT(errctx, (collide == true),
|
|
|
|
|
"the crossing pair was reported as not colliding with the arguments swapped");
|
|
|
|
|
|
|
|
|
|
// Full containment, both orders.
|
|
|
|
|
CATCH(errctx, akgl_collide_rectangles(&outer, &inner, &collide));
|
|
|
|
|
TEST_ASSERT(errctx, (collide == true), "a contained rectangle was missed");
|
|
|
|
|
CATCH(errctx, akgl_collide_rectangles(&inner, &outer, &collide));
|
|
|
|
|
TEST_ASSERT(errctx, (collide == true), "a containing rectangle was missed");
|
|
|
|
|
|
Remove the corner helpers akgl_collide_rectangles no longer uses
akgl_rectangle_points, akgl_collide_point_rectangle, akgl_Point and
akgl_RectanglePoints go. They were the intermediate form of an implementation
that changed: akgl_collide_rectangles was eight corner-containment tests built on
them, and it has been four span comparisons since the cross-case fix. Nothing
outside tests/ called either function, and a point-in-rectangle test is four
comparisons a caller can write without a struct conversion in front of them.
akgl_collide_rectangles stays. It has two correct callers in the sidescroller
asking a game-level overlap question -- a coin, a hazard, from an updatefunc --
where a bool is the whole answer and a proxy plus a narrowphase call would be
computing a normal nothing reads. TODO.md records the split rather than leaving
it to be rediscovered.
Public API removal, so 194 exported akgl_ symbols against 196, and the manual's
counts move with them. The perf suite loses its rectangle_points row; the
all-pairs sweep stays as the control it is now labelled, and PERFORMANCE.md says
what 0.8.0 measured against it -- 188.5 us for 32,640 pairs at 256 actors, where
a whole step with collision attached is 54.1 us doing strictly more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 08:09:42 -04:00
|
|
|
// A shared edge and nothing more. Touching counts; util.h and chapter 19
|
Report the overlap akgl_collide_rectangles could not see
It asked whether either rectangle enclosed one of the other's four corners --
eight akgl_collide_point_rectangle calls, stopping at the first hit. That is a
different question from "do these overlap", and it has the wrong answer for one
arrangement: a tall thin rectangle crossing a short wide one overlaps in a plus
sign with no corner of either inside the other, and all eight tests said no.
A long thin platform crossing a tall thin character is exactly that shape, so
this is a shape a 2D game produces, not a curiosity. util.h carried an @note
describing it and docs/18-utilities.md had a diagram of it, both under the
heading of a limitation rather than a defect, and there was no test for it at
all -- nor for full containment, nor for a shared edge.
It is four comparisons now, on both axes. `<=` rather than `<` because
akgl_collide_point_rectangle is inclusive on all four edges and these two have
always agreed that touching counts; a span test written with `<` would have
silently changed a contract both the header and the manual state.
The test was written first and failed on the cross before the fix went in.
Two answers change for a caller upgrading, and both are in the header note and
the chapter:
- The cross reports `true`, which is the point.
- The comparison is in float rather than through akgl_Point's int members, so a
sub-pixel overlap is no longer truncated away. A pickup test that was
accidentally forgiving by up to a pixel is no longer forgiving. Both tutorials
use this for coins and hazards; both still pass.
Faster as a side effect rather than a goal, and worth recording because the
numbers move a documented budget: 24.9 ns -> 6.1 overlapping, 57.9 -> 6.1
disjoint, and the all-pairs sweep over 64 actors 115 us -> 12.2. The disjoint
case gained most because it was the one that ran all eight tests before
answering.
The three moved rows are re-recorded in PERFORMANCE.md and nothing else is.
akgl_rectangle_points is untouched by this change and reads 6.1 in the same run
against the 4.0 recorded, so 6 ns is this run's floor and the new figure means
"too cheap to measure" rather than "exactly 6.1" -- said in the prose so the
next reader does not re-baseline the table around it.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 23:12:01 -04:00
|
|
|
// both say so, and a span test written with < rather than <= silently
|
|
|
|
|
// changes that.
|
|
|
|
|
CATCH(errctx, akgl_collide_rectangles(&left, &right, &collide));
|
|
|
|
|
TEST_ASSERT(errctx, (collide == true),
|
|
|
|
|
"two rectangles sharing exactly one edge were reported as not colliding");
|
|
|
|
|
|
|
|
|
|
// Overlapping by four tenths of a pixel. The corner tests truncated
|
|
|
|
|
// float to int on the way in, so this read as a shared edge at 10.
|
|
|
|
|
CATCH(errctx, akgl_collide_rectangles(&near1, &near2, &collide));
|
|
|
|
|
TEST_ASSERT(errctx, (collide == true), "a sub-pixel overlap was missed");
|
|
|
|
|
|
|
|
|
|
// Separated by four tenths of a pixel. Truncation read this as touching.
|
|
|
|
|
near2.x = 11.3f;
|
|
|
|
|
CATCH(errctx, akgl_collide_rectangles(&near1, &near2, &collide));
|
|
|
|
|
TEST_ASSERT(errctx, (collide == false), "a sub-pixel gap was reported as a collision");
|
|
|
|
|
|
|
|
|
|
// Disjoint on one axis only, which is the case a span test gets wrong
|
|
|
|
|
// when it forgets to check both.
|
|
|
|
|
tall.x = 100.0f;
|
|
|
|
|
CATCH(errctx, akgl_collide_rectangles(&tall, &wide, &collide));
|
|
|
|
|
TEST_ASSERT(errctx, (collide == false),
|
|
|
|
|
"rectangles separated on x were reported as colliding");
|
|
|
|
|
tall.x = 10.0f;
|
|
|
|
|
tall.y = 100.0f;
|
|
|
|
|
CATCH(errctx, akgl_collide_rectangles(&tall, &wide, &collide));
|
|
|
|
|
TEST_ASSERT(errctx, (collide == false),
|
|
|
|
|
"rectangles separated on y were reported as colliding");
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *test_akgl_collide_rectangles_logic(void)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
SDL_FRect testrect1 = { .x = 0, .y = 0, .w = 32, .h = 32};
|
|
|
|
|
SDL_FRect testrect2 = { .x = 30, .y = 30, .w = 40, .h = 40};
|
|
|
|
|
bool testcollide = false;
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
PREPARE_ERROR(errctx);
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
ATTEMPT {
|
|
|
|
|
// Collision overlapping on the top left
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( testcollide == false ) {
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collision overlapping on the top right
|
|
|
|
|
testrect1.x = 64;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( testcollide == false ) {
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collision overlapping on the bottom left
|
|
|
|
|
testrect1.x = 0;
|
|
|
|
|
testrect1.y = 32;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( testcollide == false ) {
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collision overlapping on the bottom right
|
|
|
|
|
testrect1.x = 32;
|
|
|
|
|
testrect1.y = 32;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( testcollide == false ) {
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collision overlapping the top edge
|
|
|
|
|
testrect1.x = 0;
|
|
|
|
|
testrect1.y = 0;
|
|
|
|
|
testrect1.w = 60;
|
|
|
|
|
testrect1.h = 32;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( testcollide == false ) {
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collision overlapping the left edge
|
|
|
|
|
testrect1.x = 0;
|
|
|
|
|
testrect1.y = 0;
|
|
|
|
|
testrect1.w = 35;
|
|
|
|
|
testrect1.h = 80;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( testcollide == false ) {
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collision overlapping the right edge
|
|
|
|
|
testrect1.x = 65;
|
|
|
|
|
testrect1.y = 0;
|
|
|
|
|
testrect1.w = 60;
|
|
|
|
|
testrect1.h = 80;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( testcollide == false ) {
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collision overlapping the bottom edge
|
|
|
|
|
testrect1.x = 0;
|
|
|
|
|
testrect1.y = 65;
|
|
|
|
|
testrect1.w = 80;
|
|
|
|
|
testrect1.h = 32;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( testcollide == false ) {
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Not colliding
|
|
|
|
|
testrect1.x = 0;
|
|
|
|
|
testrect1.y = 0;
|
|
|
|
|
testrect1.w = 16;
|
|
|
|
|
testrect1.h = 16;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( testcollide == true ) {
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Invalid collision reported");
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 14:23:47 -04:00
|
|
|
/**
|
|
|
|
|
* @brief Resolving a path through the root fallback must give its context back.
|
|
|
|
|
*
|
|
|
|
|
* akgl_path_relative tries the working directory first and falls back to
|
|
|
|
|
* resolving against @p root when that reports ENOENT. That fallback used to be
|
|
|
|
|
* taken by returning from inside the HANDLE block, which skips the
|
|
|
|
|
* RELEASE_ERROR that FINISH ends with -- so every call down that branch leaked
|
|
|
|
|
* one entry of AKERR_ARRAY_ERROR, and the 129th call aborted the whole process
|
|
|
|
|
* with "Unable to pull an error context from the array!". A single map load
|
|
|
|
|
* resolves several paths this way.
|
|
|
|
|
*
|
|
|
|
|
* The loop runs well past AKERR_MAX_ARRAY_ERROR on purpose: at the old
|
|
|
|
|
* behaviour this test does not fail, it terminates the suite.
|
|
|
|
|
*/
|
Report the failures that used to be crashes
Closes Defects items 30 and 31 and Known-and-still-open items 1, 2, 5, 9 and 11.
Both string accessors in json_helpers.c ended their ATTEMPT block with
FINISH(errctx, false), which swallows the failure, and then strncpy'd through
the pointer akgl_heap_next_string never set. So the one condition the pool
exists to report -- it is full, which in practice means something is not
releasing -- arrived as a segfault somewhere else entirely. It is
FINISH(errctx, true) now, and tests/json_helpers.c claims every slot and
asserts AKGL_ERR_HEAP comes back out of both. That test segfaults against the
old code, which is also how the tilemap leak test in the previous commit
confirmed this one.
akgl_tilemap_release tested layers[i].texture and destroyed
tilesets[i].texture, so every tileset texture was freed twice on one release
and no image layer's texture was freed at all. Pointers are cleared as they go,
so a second release is safe instead of a use-after-free.
akgl_game_update_fps called game.lowfpsfunc() unguarded, on a path taken on
frame one because fps is 0 for the first second. Only akgl_game_init installs
it, and renderer.h documents the other path deliberately -- a host that owns
its window binds a backend instead. It installs the default when it finds NULL.
akgl_controller_pushmap and akgl_controller_default checked only the upper
bound, so a negative id indexed before akgl_controlmaps.
The two test-harness helpers were quietly worthless. akgl_render_and_compare
drew t1 on both passes, so it always reported a match and every image assertion
built on it asserted nothing; and akgl_compare_sdl_surfaces memcmp'd
s1->pitch * s1->h bytes out of both surfaces without checking that the second
was the same size, so a smaller one was read past its end. Both fixed, both
tested. tests/util.c also now calls the collide-point test it has defined and
never run.
25/25 pass, memcheck clean, reindent --check and check_error_protocol clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 00:32:11 -04:00
|
|
|
/**
|
|
|
|
|
* @brief akgl_compare_sdl_surfaces must check geometry before it memcmps.
|
|
|
|
|
*
|
|
|
|
|
* It compared `s1->pitch * s1->h` bytes out of both surfaces without looking at
|
|
|
|
|
* the second one's dimensions, so a smaller s2 was read past its end rather
|
|
|
|
|
* than reported as a mismatch. Benign in practice and immediately fatal under a
|
|
|
|
|
* memory checker, which is the reason to fix it rather than leave it.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext *test_akgl_compare_sdl_surfaces_checks_geometry(void)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
SDL_Surface *big = NULL;
|
|
|
|
|
SDL_Surface *small = NULL;
|
|
|
|
|
SDL_Surface *twin = NULL;
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
big = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_RGBA8888);
|
|
|
|
|
twin = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_RGBA8888);
|
|
|
|
|
small = SDL_CreateSurface(8, 8, SDL_PIXELFORMAT_RGBA8888);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, big, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, twin, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, small, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, SDL_FillSurfaceRect(big, NULL, 0), AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, SDL_FillSurfaceRect(twin, NULL, 0), AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, SDL_FillSurfaceRect(small, NULL, 0), AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
|
|
|
|
|
TEST_EXPECT_OK(errctx, akgl_compare_sdl_surfaces(big, twin),
|
|
|
|
|
"comparing two identical surfaces");
|
|
|
|
|
|
|
|
|
|
// The one that used to read 4 KiB past the end of an 8x8 surface.
|
|
|
|
|
TEST_EXPECT_STATUS(errctx, AKERR_VALUE, akgl_compare_sdl_surfaces(big, small),
|
|
|
|
|
"comparing a 32x32 surface against an 8x8 one");
|
|
|
|
|
TEST_EXPECT_STATUS(errctx, AKERR_VALUE, akgl_compare_sdl_surfaces(small, big),
|
|
|
|
|
"comparing an 8x8 surface against a 32x32 one");
|
|
|
|
|
|
|
|
|
|
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_compare_sdl_surfaces(NULL, big),
|
|
|
|
|
"comparing a NULL first surface");
|
|
|
|
|
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_compare_sdl_surfaces(big, NULL),
|
|
|
|
|
"comparing a NULL second surface");
|
|
|
|
|
|
|
|
|
|
// Same dimensions, different format: the pixels are not comparable even
|
|
|
|
|
// though the byte count might be.
|
|
|
|
|
SDL_DestroySurface(small);
|
|
|
|
|
small = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_RGB24);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, small, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
TEST_EXPECT_STATUS(errctx, AKERR_VALUE, akgl_compare_sdl_surfaces(big, small),
|
|
|
|
|
"comparing two surfaces of different pixel formats");
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( big != NULL ) {
|
|
|
|
|
SDL_DestroySurface(big);
|
|
|
|
|
}
|
|
|
|
|
if ( twin != NULL ) {
|
|
|
|
|
SDL_DestroySurface(twin);
|
|
|
|
|
}
|
|
|
|
|
if ( small != NULL ) {
|
|
|
|
|
SDL_DestroySurface(small);
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 14:23:47 -04:00
|
|
|
akerr_ErrorContext *test_akgl_path_relative_releases_contexts(void)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
akgl_String *dst = NULL;
|
|
|
|
|
int before = 0;
|
|
|
|
|
int after = 0;
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
PASS(errctx, akgl_heap_init());
|
|
|
|
|
PASS(errctx, akgl_heap_next_string(&dst));
|
|
|
|
|
before = live_error_contexts();
|
|
|
|
|
|
|
|
|
|
for ( i = 0; i < (AKERR_MAX_ARRAY_ERROR * 2); i++ ) {
|
|
|
|
|
PASS(errctx, akgl_path_relative("assets", "testcharacter.json", dst));
|
|
|
|
|
}
|
|
|
|
|
after = live_error_contexts();
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
if ( after != before ) {
|
|
|
|
|
FAIL_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
AKGL_ERR_BEHAVIOR,
|
|
|
|
|
"akgl_path_relative leaked %d error context(s) over %d root-fallback resolutions",
|
|
|
|
|
(after - before),
|
|
|
|
|
(AKERR_MAX_ARRAY_ERROR * 2));
|
|
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
IGNORE(akgl_heap_release_string(dst));
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
int main(void)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
ATTEMPT {
|
Migrate to the libakerror 1.0.0 status registry
libakerror 1.0.0 replaced the consumer-sized status-name array with a
private registry and made status-code ownership explicit and enforced.
AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, and the registry
entry points raise akerr_ErrorContext * instead of returning int. See
deps/libakerror/UPGRADING.md.
The break was not only source-level. libakgl's codes sat at
AKERR_LAST_ERRNO_VALUE + 18 through + 22, and 1.0.0 claimed exactly
those five offsets for its own AKERR_STATUS_* registry codes, so every
AKGL_ERR_* was aliasing a libakerror status. HANDLE(e,
AKGL_ERR_LOGICINTERRUPT) at physics.c:222 would have swallowed a
foreign-name refusal.
- Move the band to AKERR_FIRST_CONSUMER_STATUS (256) as fixed offsets,
so a libc that grows an errno cannot move the codes, and add
AKGL_ERR_OWNER, AKGL_ERR_LIMIT and AKGL_ERR_COUNT to describe it.
- Reserve the range and register the names through the owned entry
points, PASS-ing each: these are AKERR_NOIGNORE, and the old
akerr_name_for_status calls discarded failure silently.
- Drop the AKERR_MAX_ERR_VALUE=256 compile definition.
- Guard on AKERR_FIRST_CONSUMER_STATUS in include/akgl/error.h, which
now includes <akerror.h> so the guard is reliable. The embedded build
is fine, but the find_package path can pick up a stale installed
header, and 1.0.0 has an soname, so that pairing is an ABI mismatch
rather than a compile problem. Same guard libakstdlib already carries.
Registration also moves out of akgl_heap_init into a new akgl_error_init
in src/error.c. It was in the heap pool's initializer only because that
was the first thing akgl_game_init called, and the upgrade turned five
fire-and-forget name calls into a library-wide ownership claim that can
fail. That placement was hiding a defect: game.c raises AKGL_ERR_SDL
when SDL_CreateMutex fails, five lines before akgl_heap_init ran, so the
earliest error path in the library was guaranteed to print "Unknown
Error". akgl_error_init is now the first statement in akgl_game_init.
Callers that drive subsystems directly must call akgl_error_init first;
it is idempotent, so ordering it precisely is not required. The eleven
test suites that relied on akgl_heap_init to name their statuses now
call it explicitly, or their failure messages would have degraded to
"Unknown Error".
Add tests/error.c: assert every code reads back its registered name,
that the name table and AKGL_ERR_COUNT agree, that a foreign owner is
refused with AKERR_STATUS_NAME_FOREIGN and AKERR_STATUS_RANGE_OVERLAP,
and that repeating the init is a no-op. That last one is a live
constraint, not a triviality -- libakerror treats only an identical
reservation as a repeat, so a subset or superset raises.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 22:20:28 -04:00
|
|
|
CATCH(errctx, akgl_error_init());
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, test_akgl_collide_rectangles_nullpointers());
|
|
|
|
|
CATCH(errctx, test_akgl_collide_rectangles_logic());
|
Report the overlap akgl_collide_rectangles could not see
It asked whether either rectangle enclosed one of the other's four corners --
eight akgl_collide_point_rectangle calls, stopping at the first hit. That is a
different question from "do these overlap", and it has the wrong answer for one
arrangement: a tall thin rectangle crossing a short wide one overlaps in a plus
sign with no corner of either inside the other, and all eight tests said no.
A long thin platform crossing a tall thin character is exactly that shape, so
this is a shape a 2D game produces, not a curiosity. util.h carried an @note
describing it and docs/18-utilities.md had a diagram of it, both under the
heading of a limitation rather than a defect, and there was no test for it at
all -- nor for full containment, nor for a shared edge.
It is four comparisons now, on both axes. `<=` rather than `<` because
akgl_collide_point_rectangle is inclusive on all four edges and these two have
always agreed that touching counts; a span test written with `<` would have
silently changed a contract both the header and the manual state.
The test was written first and failed on the cross before the fix went in.
Two answers change for a caller upgrading, and both are in the header note and
the chapter:
- The cross reports `true`, which is the point.
- The comparison is in float rather than through akgl_Point's int members, so a
sub-pixel overlap is no longer truncated away. A pickup test that was
accidentally forgiving by up to a pixel is no longer forgiving. Both tutorials
use this for coins and hazards; both still pass.
Faster as a side effect rather than a goal, and worth recording because the
numbers move a documented budget: 24.9 ns -> 6.1 overlapping, 57.9 -> 6.1
disjoint, and the all-pairs sweep over 64 actors 115 us -> 12.2. The disjoint
case gained most because it was the one that ran all eight tests before
answering.
The three moved rows are re-recorded in PERFORMANCE.md and nothing else is.
akgl_rectangle_points is untouched by this change and reads 6.1 in the same run
against the 4.0 recorded, so 6 ns is this run's floor and the new figure means
"too cheap to measure" rather than "exactly 6.1" -- said in the prose so the
next reader does not re-baseline the table around it.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 23:12:01 -04:00
|
|
|
CATCH(errctx, test_akgl_collide_rectangles_arrangements());
|
Report the failures that used to be crashes
Closes Defects items 30 and 31 and Known-and-still-open items 1, 2, 5, 9 and 11.
Both string accessors in json_helpers.c ended their ATTEMPT block with
FINISH(errctx, false), which swallows the failure, and then strncpy'd through
the pointer akgl_heap_next_string never set. So the one condition the pool
exists to report -- it is full, which in practice means something is not
releasing -- arrived as a segfault somewhere else entirely. It is
FINISH(errctx, true) now, and tests/json_helpers.c claims every slot and
asserts AKGL_ERR_HEAP comes back out of both. That test segfaults against the
old code, which is also how the tilemap leak test in the previous commit
confirmed this one.
akgl_tilemap_release tested layers[i].texture and destroyed
tilesets[i].texture, so every tileset texture was freed twice on one release
and no image layer's texture was freed at all. Pointers are cleared as they go,
so a second release is safe instead of a use-after-free.
akgl_game_update_fps called game.lowfpsfunc() unguarded, on a path taken on
frame one because fps is 0 for the first second. Only akgl_game_init installs
it, and renderer.h documents the other path deliberately -- a host that owns
its window binds a backend instead. It installs the default when it finds NULL.
akgl_controller_pushmap and akgl_controller_default checked only the upper
bound, so a negative id indexed before akgl_controlmaps.
The two test-harness helpers were quietly worthless. akgl_render_and_compare
drew t1 on both passes, so it always reported a match and every image assertion
built on it asserted nothing; and akgl_compare_sdl_surfaces memcmp'd
s1->pitch * s1->h bytes out of both surfaces without checking that the second
was the same size, so a smaller one was read past its end. Both fixed, both
tested. tests/util.c also now calls the collide-point test it has defined and
never run.
25/25 pass, memcheck clean, reindent --check and check_error_protocol clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 00:32:11 -04:00
|
|
|
CATCH(errctx, test_akgl_compare_sdl_surfaces_checks_geometry());
|
2026-07-31 14:23:47 -04:00
|
|
|
CATCH(errctx, test_akgl_path_relative_releases_contexts());
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH_NORETURN(errctx);
|
|
|
|
|
}
|