Files
libakgl/docs/01-introduction.md
Andrew Kesterson 986c80d0ec Bump to 0.8.0, close Target 12, and record what is still missing
Collision changed the collide slot's signature and grew four public structs, so
this is an ABI break and the soname moves to libakgl.so.0.8. The manual's counts
move with it: 195 functions across 23 headers, 183 of which return an error
context.

Target 12 -- "collision for 256 actors under 2 ms without the caller writing a
broad phase" -- is met. A new benchmark times the whole step with a world
attached, which is the number the target is actually about rather than a
narrowphase call in isolation: 15.4 us at 64 actors, and 54.1 us at 256 in a
purpose-built -DAKGL_MAX_HEAP_ACTOR=256 configuration. Over that 4x range the
step grew 3.5x while the all-pairs control grew 15.6x, which is the n-squared
the index exists to avoid. Plan item 7 is rewritten to record what shipped and
why both partitioners exist; the Construct and Phaser citations stay.

Three new TODO entries. Actor rotation, scoped to the five places it touches and
the one place it does not -- collision_support is written so rotating `dir` in
and the answer back is the whole narrowphase change, and rotational *response*
is explicitly a different piece of work. The swept narrowphase FLAG_BULLET
reserves, with the tunnelling arithmetic written out so a game can check its own
numbers against 1280 px/s. And the ccd arena being single-threaded and sized by
measurement at 7,264 bytes per GJK/EPA box pair.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 07:53:30 -04:00

8.3 KiB

01. Introduction

libakgl is a C library for building 2D games on SDL3. This is version 0.8.0. It ships 195 functions declared across the twenty-three public headers in include/akgl/, plus akgl_version() from the generated version.h — 196 exported akgl_ symbols in libakgl.so.0.8.

0.8.0 is an ABI break. The collide slot on akgl_PhysicsBackend changed signature, four public structs grew, and akgl_physics_arcade_collide stopped raising AKERR_API. Collision is the reason; it is Chapter 15.

It gives you object pools instead of malloc, name-based registries instead of pointer plumbing, a per-frame tick that updates and draws every live actor, JSON asset formats for sprites and characters, a Tiled map loader, arcade physics, a controller/keyboard input layer, text, and a three-voice tone synthesizer.

What it refuses to be

It is not an engine. There is no editor, no scene graph, no scripting layer, no asset pipeline that runs before the compiler, and no runtime that owns your main. You write a C program; libakgl is a library it calls.

The refusals are specific, and each one is a design decision documented in Chapter 2:

Not here Instead
Inheritance, RTTI, dynamic dispatch on a type tag A record of function pointers you populate — akgl_RenderBackend, akgl_PhysicsBackend, akgl_Partitioner
Runtime malloc Eight statically sized pools with reference counts, plus a static arena for the narrowphase; AKGL_ERR_HEAP when a pool is full
An editor or a project format Tiled TMJ maps and hand-writable JSON for sprites and characters
A scripting layer C. Behaviour attaches as function pointers on akgl_Actor
Two worlds at once Four swappable globals: akgl_renderer, akgl_physics, akgl_camera, akgl_gamemap
Collision you cannot switch off A collision pointer on the backend. NULL is byte-identical to a build before collision existed

The library also does not own your window. akgl_render_2d_init creates one for you, and akgl_render_2d_bind is the same job with that half removed, for a host that already has an SDL_Renderer — see Chapter 8.

What a frame costs

PERFORMANCE.md measures a 640x480 game with a full screen of 16-pixel tiles and 64 actors, software-rasterized. At 60 fps a frame is 16.67 ms:

Part of the frame Cost Share of 16.67 ms
Logic: 64 actor updates + one physics sweep 0.006 ms 0.03%
Collision over 64 actors, all-pairs, as a control 0.012 ms 0.07%
Clear + present 0.023 ms 0.1%
64 actor renders (48x48 blits) 0.191 ms 1.1%
1200 tile blits 16.26 ms 97.6%
Six lines of HUD text 0.076 ms 0.5%

libakgl's own per-tile overhead is about 0.03 ms per frame, under 0.2%. That number is the gap between akgl_tilemap_draw at 16.26 ms and a raw SDL_RenderTexture loop issuing the same 1200 blits from the same scattered source tiles at 16.23 ms. It covers the bounds arithmetic, the tileset scan, the offset-table lookup, the backend indirection and the error macros.

It does not cover the pixels, and the pixels are the frame. It does not tell you what libakgl costs on a GPU backend, where the blits get cheap and libakgl's share of a much shorter frame rises — PERFORMANCE.md says so explicitly and is the reason the per-operation numbers there matter more than these totals. And it is one laptop, one build type, one afternoon: treat the absolute numbers as that machine's and the ratios as the library's.

What is not implemented

Named here rather than discovered later. Each gets a callout in the chapter where you would hit it, and an entry in TODO.md.

Gap Behaviour today Chapter
Terminal velocity Gravity accumulates into ey unbounded. physics.drag.y is the only brake 14
Friction / deceleration Releasing a direction zeroes thrust and stops the actor dead. Right for Zelda, wrong for Mario 14
Savegames akgl_game_save writes the name tables but not the objects, so a file is not yet enough to restore a session 07
Rotation No actor carries an angle, and every collision shape is axis-aligned 15
Continuous collision Sub-stepping is capped, so something moving faster than about 1280 px/s on 16-pixel tiles can still pass through a wall 15
Mesh drawing akgl_render_2d_draw_mesh raises AKERR_API; the hook is reserved for a 3D backend 08
A text cache Every akgl_text_rendertextat rasterizes, uploads, blits and destroys 16

Who owns which documentation

This manual documents libakgl. It does not re-document its dependencies. Every project below is documented by the people who own its code, and a paraphrase here would be wrong the day one of them changes without anything in this repository noticing. So each chapter answers two questions — what does libakgl add or constrain here, and what does a libakgl caller actually write — and links out for the rest.

Topic Owned by What this manual owes you
ATTEMPT/CLEANUP/PROCESS/HANDLE/FINISH, PASS, CATCH, IGNORE libakerror (deps/libakerror) Which statuses libakgl raises and what they mean here — Chapter 4
aksl_strncpy, aksl_fclose, aksl_fgetc, aksl_snprintf libakstdlib (deps/libakstdlib) Which ones libakgl requires you to use, and why
SDL_Renderer, SDL_Texture, events, SDL_PropertiesID SDL3 The backend vtable, the frame contract, what libakgl does to the renderer's state
Image decoding SDL3_image Which formats reach a spritesheet, and when they are decoded
Audio decoding, MIX_Audio, mixers and tracks SDL3_mixer akgl_load_start_bgm and the track table — Chapter 18
TTF rasterizing and metrics SDL3_ttf The font registry, the teardown ordering trap, the per-call cost — Chapter 17
json_t, json_decref, the parser jansson akgl_get_json_* status semantics and the borrowed-reference rule — Chapter 19
The TMJ map format, layers, tilesets, custom properties Tiled libakgl's extensions and limits — Chapter 13

The three-voice synthesizer in Chapter 18 is the one audio subsystem that is libakgl's own, and it is documented here in full. It has nothing to do with SDL3_mixer.

Where the per-function reference lives

This manual is narrative. It teaches a task and links to the generated Doxygen for signatures, parameters and per-function @throws lists:

doxygen Doxyfile

Every header already carries a substantial @file block explaining its subsystem's design rationale, and Doxyfile sets WARN_IF_UNDOCUMENTED = YES with WARN_AS_ERROR = FAIL_ON_WARNINGS, so an undocumented symbol fails CI. The gap these chapters fill is navigation and worked examples, not reference text. They deliberately do not restate the 156 signatures — a hand-copied signature table is exactly the artifact that drifts, and it would compete with a reference that CI already keeps honest.

Where a chapter genuinely needs a declaration or a constant table in front of you, it uses an excerpt= block whose contents are checked against the header on every test run. The text you read is the header.

Reading order

Chapter 4 comes before every subsystem chapter, because 183 of those 195 functions return akerr_ErrorContext AKERR_NOIGNORE * and you cannot read a single example until you can read that return value. The dozen that do not are the void SDL enumeration callbacks and the collision arena's accessors. After that, Chapter 3 gets a window on the screen, and the subsystem chapters can be read in any order.

If you would rather start by building something, the two tutorials — Chapter 20 and Chapter 21 — are complete programs under examples/, built by default and smoke-run in CI.