Commit Graph

38 Commits

Author SHA1 Message Date
dba0f8db89 Draw shapes immediately against a renderer
BASIC 7.0's graphics verbs -- DRAW, BOX, CIRCLE, PAINT, SSHAPE and GSHAPE -- all
plot against the current screen right now rather than adding to a scene, and
draw.h had exactly one function in it. This adds akgl_draw_point, _line, _rect,
_filled_rect, _circle, _flood_fill, _copy_region and _paste_region, each taking
the akgl_RenderBackend the host already initialized.

Color is an argument rather than a current-color global, so there is no second
copy of state to disagree with the caller's own, and each call restores the
renderer's draw color when it is done. SDL3 has no circle and no flood fill: the
circle is a midpoint circle, and the fill reads the target back, walks the
region with a fixed 4096-entry span stack, and blits back only the bounding box
of what changed. Exhausting that stack reports AKERR_OUTOFBOUNDS and says in the
header that the region is left partially filled.

tests/draw.c draws into a 64x64 software renderer under the dummy video driver
and reads the pixels back, so it needs no display and no offscreen harness.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:05:36 -04:00
1ddc64010a Let a caller take keystrokes without owning the event loop
akbasic's GET and GETKEY ask "is there a keystroke waiting, yes or no" and must
not require the interpreter to pump SDL events itself. akgl_controller_poll_key
drains one key per call from a fixed 32-entry ring that
akgl_controller_handle_event fills, so the host keeps pumping and the embedded
interpreter reads at its own pace. akgl_controller_flush_keys discards a
backlog.

The recording happens before the control-map scan, not after: that scan returns
as soon as a binding claims the event, so recording afterwards would have
dropped exactly the keys a game also acts on. A full buffer refuses the newest
keystroke rather than overwriting the oldest, so a caller reading a line gets
what was typed first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 06:57:50 -04:00
17e6e04c79 Measure rendered text without drawing it
akbasic needs the advance width of one character cell to build a terminal-style
text surface, and the wrapped size to know where a string crosses the right
margin. akgl_text_measure and akgl_text_measure_wrapped report both over
TTF_GetStringSize and TTF_GetStringSizeWrapped; neither needs a renderer, so
this half of the text subsystem is testable without the offscreen harness.

A negative wrap length is refused with AKERR_OUTOFBOUNDS: SDL_ttf reads it as a
very large unsigned width and quietly stops wrapping, which would return a
measurement that is wrong rather than an error the caller can see.

Also fixes akgl_text_loadfont, which checked name twice and passed an unchecked
filepath to TTF_OpenFont (TODO item 39).

The fixture font is a 10 KB monospaced ASCII subset of Liberation Mono, renamed
because "Liberation" is a Reserved Font Name; see
tests/assets/akgl_test_mono.LICENSE.txt. Monospaced so the suite can assert
width("AAAA") == 4 * width("A") rather than hardcode glyph metrics.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 06:55:25 -04:00
c5a7b6053d Derive the library version from one place and give the .so a soname
The version was written down twice and agreed with itself by luck.
AKGL_VERSION was hand-maintained in include/akgl/game.h, and nothing
connected it to the build, which had no version at all:

- project() declared no VERSION, so the @PROJECT_VERSION@ substitution
  in akgl.pc.in expanded to nothing and every installed akgl.pc shipped
  an empty Version field. pkg-config --modversion returned "" and
  --atleast-version could not work at all.
- libakgl.so had no VERSION or SOVERSION, so there was no soname and no
  symlink chain. A stale libakgl could be paired with new headers
  silently, which is the failure libakerror added its own soname to
  prevent.
- MY_LIBRARY_VERSION at CMakeLists.txt:288 was never set anywhere. It
  built main_lib_dest as "lib/akgl-", and main_lib_dest was then never
  read. Dead line, removed.

project(akgl VERSION 0.1.0) is now the only place the number appears. It
drives the generated include/akgl/version.h, the shared library VERSION
and SOVERSION, and the Version field in akgl.pc. Held at 0.1.0 to match
the constant it replaces, so akgl_game_load_versioncmp still compares
savegames against the same value.

include/akgl/version.h is generated by configure_file from version.h.in
and publishes AKGL_VERSION, AKGL_VERSION_MAJOR/MINOR/PATCH and
AKGL_VERSION_AT_LEAST(major, minor, patch). That macro is the point:
libakstdlib could not write that test against libakerror, which
publishes no version macro, and had to feature-test on
AKERR_FIRST_CONSUMER_STATUS instead. game.h now includes the generated
header rather than defining the version itself, so consumers see
AKGL_VERSION exactly where they saw it before.

While the major version is 0 the soname carries major.minor, giving
libakgl.so.0.1. A plain SOVERSION 0 would assert that 0.1.0 and 0.2.0
are ABI-interchangeable, and they are not -- the preceding commit alone
added akgl_error_init. At 1.0.0 the soname becomes the major alone,
matching libakerror.

akgl_version() in src/version.c reports the version of the libakgl that
is actually linked, where AKGL_VERSION reports what the caller compiled
against. Comparing the two is the only way to catch a stale shared
library on the loader path; the macro alone cannot see it. It returns a
string rather than an akerr_ErrorContext * because it cannot fail, for
the same reason akerr_name_for_status does.

Add tests/version.c: the string and the numeric components must describe
one version, the linked library must agree with the headers, and
AKGL_VERSION_AT_LEAST must order correctly at the major, minor and patch
boundaries. Verified out of band that bumping project() to 1.2.3 moves
version.h, akgl.pc and the soname together, and that an install produces
the libakgl.so -> .so.0.1 -> .so.0.1.0 chain with SONAME libakgl.so.0.1.

Also ignore include/akgl/version.h. include/ precedes the build tree on
the include path, so a stray copy there would shadow the generated one
and pin every consumer to whatever version it was generated at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 22:36:50 -04:00
9b124f2e27 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
28bde4176d 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
549b27d3eb Document the libakgl API with Doxygen
Add file, structure, global, and function documentation across all libakgl-owned headers and sources, including parameter contracts and likely AKERR/AKGL_ERR exceptions. Add a strict Doxyfile and build the generated API documentation in Gitea CI with warnings treated as failures.

Co-authored-by: Codex (GPT-5) <noreply@openai.com>
2026-07-30 01:10:31 -04:00
cf9ebb206f Repair embedded dependency build and test setup
Isolate vendored test registration, namespace project error codes, and update dependency revisions. Fix mutable sprite path handling, out-of-tree fixtures, and charviewer initialization while documenting the outstanding character-to-sprite refcount bug.

Co-authored-by: Codex (GPT-5) <noreply@openai.com>
2026-07-29 18:17:08 -04:00
652ee4cdf3 Tilemaps can now load their own physics engines from map properties. The gravity for the arcade simulation is acting a little funny when populated from the map properties. 2026-06-02 17:11:16 -04:00
9fed59c4c8 Abstract the global renderer, physics, camera and gamemap objects behind a pointer, so you can swap in and out different ones as needed 2026-06-02 13:15:26 -04:00
941eeb2493 Got the physics system applying gravity and drag correctly. (Mostly? Seems like it? Seems good.) 2026-05-26 16:45:04 -04:00
314ce5e10d Physics simulation engine implemented, basic cases (null and 2d SideScroller) works 2026-05-26 10:36:31 -04:00
d87c5d2c20 More rendering subsystem breakout, added a physics subsystem, everything now fires from akgl_game_update() for the user 2026-05-25 21:29:18 -04:00
6314ad7f26 Start abstracting the rendering backend away to make it easier to switch to GPU rendering later 2026-05-24 21:59:29 -04:00
8f613397d6 Loading sprites and tiles both work correctly with relative paths, now using akgl_path_relative* helpers 2026-05-24 19:57:57 -04:00
980bbc56fb Fixed loading of tilesets and images from tilemaps to correctly use relative paths. 2026-05-24 10:28:35 -04:00
e3edd5b855 Add a method that automatically opens gamepads so they will send events 2026-05-21 21:43:51 -04:00
f695a035c8 Add a low FPS callback to aid in debugging, and fix a huge memory leak in akgl_text_rendertextat 2026-05-15 11:07:31 -04:00
73b1a4cab0 Make perspective scale work more explicitly from properties 2026-05-13 23:36:49 -04:00
aad196d84e akgl_heap_release* should not cascade into dependent objects (releasing actors should not release characters, sprites, and spritesheets) 2026-05-13 22:45:52 -04:00
9443fa8532 Fix bug in akgl_heap_release_actor preventing it from being cleared out of the registry 2026-05-13 19:28:38 -04:00
dc2e88b72f Fix a scale bug (default should be 1.0), add tilemap release function 2026-05-13 16:56:24 -04:00
53e4f5c14f Tilemaps can have two objects placed on it, 'p_foreground' and 'p_vanishing' whose placement form a virtual perspective plane, which causes sprites to scale as they move up and down the screen between them, creating the illusion of depth on the screen 2026-05-13 09:57:24 -04:00
b8dee456ca Add keyboard enumeration function. Make image layers work. 2026-05-13 08:02:59 -04:00
36dfd47a06 Fixed a bug in akgl_heap_next_string, made screen width and height load from properties, not on the game object 2026-05-13 04:55:19 -04:00
23dbc7d985 Make mkcontrollers script work when building as a submodule 2026-05-12 21:40:31 -04:00
c79d93dd58 Wrap subdirectory compilation in conditional 2026-05-12 21:01:47 -04:00
ccd26494d9 Import all dependencies as submodules, make cmake do the right thing 2026-05-12 16:45:42 -04:00
5439b8004b Move some of the sprite math out of the actor into the sprite 2026-05-12 15:21:36 -04:00
0f01126bad More akstdlib conversion 2026-05-11 20:58:36 -04:00
4a02f0364f Start moving away from wrapping libc stuff in akerror, and move towards akstdlib that does this for me 2026-05-10 00:03:45 -04:00
cc0916cd1f Added a (registry object name -> registry object pointer) map to the save method so that registry reference by name can have their actor, sprite, spritesheet, and character references reset 2026-05-09 14:45:37 -04:00
4c771227f5 Encode library version and game version into savegame files and check them both 2026-05-08 23:48:35 -04:00
ff6b282112 Added basic game save function, doesn't save much yet, but implements version comparison of binary saves for compatibility 2026-05-08 23:15:11 -04:00
0bd1ae1df8 Move music tracks out of akgl_Game to make it more suitable for serialization 2026-05-08 22:16:43 -04:00
e0a59e2447 Add types.h, standardize integer types on stdint 2026-05-08 22:01:56 -04:00
5dda86d887 Add properties registry. Start bounding defines with conditionals so users can change library allocation size. 2026-05-08 10:16:33 -04:00
a0b2dda4cf Rename library to AKGL (AKLabs Game Library) 2026-05-07 22:20:10 -04:00