Test the collision scan, and measure what it costs

`spr_collisions()` had no test. `tests/sprite_verbs.c` drives the collision path
end to end but through a mock backend, so the real overlap arithmetic in
`src/sprite_akgl.c` could have changed what `BUMP(1)` reports for every program
in existence and the suite would still have printed 110/110. That gap is closed
here, before anything touches the arithmetic, so that there is a *before* to
compare an *after* against.

Six cases against the real akgl backend: nothing defined, two sprites
overlapping, edge-to-edge, a hidden sprite, and the x-expand bit doubling the box
that collides rather than only the one that draws. Edge-to-edge earns its place
-- the test is a strict `<`, a tile-aligned program puts sprites there
constantly, and a replacement answering "touching" instead of "overlapping"
would change every one of them silently.

**The seventh is the cross-shaped overlap**, and it is the one to watch. A tall
thin sprite crossing a short wide one overlaps without either rectangle holding a
corner of the other; `akgl_collide_rectangles()` is documented as answering "no"
there, which is why `src/sprite_akgl.c` does the four comparisons itself rather
than calling it. Two further assertions stop that test passing by accident: each
sprite is moved clear along the axis it is supposed to be short on, so a sprite
that came out the wrong size fails rather than quietly reporting an ordinary
overlap.

`tests/collision_perf.c` answers the question nobody had measured. The service
runs at the top of every interpreter *step* and the frontend takes 256 steps per
rendered frame, so a busy program scans up to 256 times a frame over sprites that
have not moved. At RelWithDebInfo, scale 10, best of 5: the scan is 96.3 ns at
eight overlapping sprites and 19.4 ns at none, against a rendered frame of
1.17 ms. **256 scans is 24.7 us, or 2.1% of a frame, in the pathological case,
and 0.42% for a program with no sprites.**

So the per-step cadence stays. It is what makes a collision report describe where
the sprites have just been moved to rather than where they were, and 2% of a
frame in a case no real program reaches is not worth changing when a handler
fires for every program that already works. The numbers and that conclusion are
in `MAINTENANCE.md` so it does not get re-argued.

The benchmark borrows libakgl's `benchutil.h` by include path rather than copying
it, the way the fixture font is already borrowed, and is labelled `perf` so
`ctest -LE perf` can leave it out. It runs at scale 1 in the ordinary suite --
1.2 seconds -- because a benchmark nothing ever builds is a benchmark that rots.

Both suites green: 111 with akgl, 110 without.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
This commit is contained in:
2026-08-02 09:25:22 -04:00
parent 1fb808f480
commit d61e352f36
4 changed files with 513 additions and 0 deletions

View File

@@ -354,6 +354,27 @@ if(AKBASIC_WITH_AKGL)
TIMEOUT 60
ENVIRONMENT "SDL_VIDEODRIVER=dummy;SDL_AUDIODRIVER=dummy")
# What the collision scan costs against what a frame costs. Labelled `perf` so
# `ctest -LE perf` leaves it out of the ordinary run: it is the only test here
# that deliberately spends wall-clock time, and the numbers are meaningless in
# the Debug trees anyway -- see the file's own header.
add_executable(akbasic_test_collision_perf tests/collision_perf.c)
target_compile_options(akbasic_test_collision_perf PRIVATE -Wall -Wextra)
target_link_libraries(akbasic_test_collision_perf PRIVATE akbasic_akgl)
# libakgl's benchmark harness, borrowed by include path rather than copied, for
# the same reason the fixture font is: a second copy is a fork. SYSTEM because
# benchutil.h is a header of statics and -Wunused-function would fire on every
# helper this suite happens not to call.
target_include_directories(akbasic_test_collision_perf SYSTEM PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/deps/libakgl/tests")
target_compile_definitions(akbasic_test_collision_perf PRIVATE
AKBASIC_TEST_FONT="${CMAKE_CURRENT_SOURCE_DIR}/deps/libakgl/tests/assets/akgl_test_mono.ttf")
_add_test(NAME collision_perf COMMAND akbasic_test_collision_perf)
_set_tests_properties(collision_perf PROPERTIES
LABELS perf
TIMEOUT 300
ENVIRONMENT "SDL_VIDEODRIVER=dummy;SDL_RENDER_DRIVER=software")
# The frontend suite. Separate from the one above because it tests the *host*
# rather than the adaptors -- it is the only test that links akbasic_frontend,
# and the only one that opens a window and pumps events. It uses the