The new chapter 15 covers the opt-in setup, where resolution runs in the step and why it runs after the move, shapes and the z-extrusion trap, the asymmetric masks and the defaults that matter more than the mechanism, tiles as a source rather than proxies, the response hook and the three ways it is easy to write wrongly, sensors, the four queries, both partitioners, and what is still not implemented. Collision falsified claims in eight other chapters. The physics chapter said "There is no collision detection, at all" and that collide always raises AKERR_API; actors had six behaviour hooks; the heap had five pools and four non-claiming acquires; the status band held five codes; and the design philosophy had exactly two pluggable subsystems. The appendix gains a collision.h status section, the three new pool ceilings and a table of the collision constants that are deliberately not in a public header. Also fixes chapter labels the renumbering commit left pointing at their old numbers -- "18. Utilities" linked to 19-utilities.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
128 lines
7.8 KiB
Markdown
128 lines
7.8 KiB
Markdown
# 01. Introduction
|
|
|
|
libakgl is a C library for building 2D games on SDL3. This is version **0.7.0**. It ships
|
|
156 functions declared across the twenty public headers in `include/akgl/`, plus
|
|
`akgl_version()` from the generated `version.h` — 157 exported symbols in
|
|
`libakgl.so.0.7`.
|
|
|
|
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` |
|
|
| Runtime `malloc` | Five statically sized pools with reference counts; `AKGL_ERR_HEAP` when one 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` |
|
|
|
|
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 all 156 functions
|
|
return `akerr_ErrorContext AKERR_NOIGNORE *` and you cannot read a single example until you
|
|
can read that return value. 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.
|