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
134 lines
8.3 KiB
Markdown
134 lines
8.3 KiB
Markdown
# 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](15-collision.md).
|
|
|
|
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](02-design-philosophy.md):
|
|
|
|
| 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](08-rendering.md).
|
|
|
|
## 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](14-physics.md) |
|
|
| Friction / deceleration | Releasing a direction zeroes thrust and stops the actor dead. Right for Zelda, wrong for Mario | [14](14-physics.md) |
|
|
| Savegames | `akgl_game_save` writes the name tables but not the objects, so a file is not yet enough to restore a session | [07](07-the-game-and-the-frame.md) |
|
|
| Rotation | No actor carries an angle, and every collision shape is axis-aligned | [15](15-collision.md) |
|
|
| 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](15-collision.md) |
|
|
| Mesh drawing | `akgl_render_2d_draw_mesh` raises `AKERR_API`; the hook is reserved for a 3D backend | [08](08-rendering.md) |
|
|
| A text cache | Every `akgl_text_rendertextat` rasterizes, uploads, blits and destroys | [16](17-text-and-fonts.md) |
|
|
|
|
## 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](04-errors.md) |
|
|
| `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](https://wiki.libsdl.org/SDL3/) | The backend vtable, the frame contract, what libakgl does to the renderer's state |
|
|
| Image decoding | [SDL3_image](https://wiki.libsdl.org/SDL3_image/) | Which formats reach a spritesheet, and when they are decoded |
|
|
| Audio decoding, `MIX_Audio`, mixers and tracks | [SDL3_mixer](https://wiki.libsdl.org/SDL3_mixer/) | `akgl_load_start_bgm` and the track table — [Chapter 18](18-audio.md) |
|
|
| TTF rasterizing and metrics | [SDL3_ttf](https://wiki.libsdl.org/SDL3_ttf/) | The font registry, the teardown ordering trap, the per-call cost — [Chapter 17](17-text-and-fonts.md) |
|
|
| `json_t`, `json_decref`, the parser | [jansson](https://jansson.readthedocs.io/) | `akgl_get_json_*` status semantics and the borrowed-reference rule — [Chapter 19](19-utilities.md) |
|
|
| The TMJ map format, layers, tilesets, custom properties | [Tiled](https://doc.mapeditor.org/en/stable/reference/json-map-format/) | libakgl's extensions and limits — [Chapter 13](13-tilemaps.md) |
|
|
|
|
The three-voice synthesizer in [Chapter 18](18-audio.md) 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:
|
|
|
|
```sh norun
|
|
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](04-errors.md) 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](03-getting-started.md) 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](20-tutorial-sidescroller.md) and [Chapter 21](21-tutorial-jrpg.md) — are
|
|
complete programs under `examples/`, built by default and smoke-run in CI.
|