Renumber chapters 15 to 21 up by one, to open a slot for collision

A pure rename plus link rewrite and nothing else. Collision is a pluggable
subsystem with two implementations, shapes, a response hook and a tile binding;
that is a chapter, and burying it inside the physics chapter is the shape this
manual otherwise avoids -- rendering and drawing are 8 and 9, spritesheets and
characters and actors are 10, 11 and 12.

Kept separate from writing the chapter because **nothing validates a
cross-reference target.** The example harness reads fenced blocks; it does not
follow links. A rename mixed into five hundred lines of new prose is not
reviewable, and a broken link would land silently. As its own commit it is
reviewable as a rename, and a grep for dangling `](NN-` targets is clean.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-02 07:15:25 -04:00
parent fcad2822a9
commit bace818998
19 changed files with 49 additions and 49 deletions

View File

@@ -17,8 +17,8 @@ The two tutorials build real, running programs that live under [`examples/`](exa
| | |
|---|---|
| [A 2D sidescroller](docs/19-tutorial-sidescroller.md) | Gravity, a jump, collision you write yourself, coins and hazards |
| [A top-down JRPG](docs/20-tutorial-jrpg.md) | A town map, NPCs spawned from map objects, four-way animation, a text box, a follower |
| [A 2D sidescroller](docs/20-tutorial-sidescroller.md) | Gravity, a jump, collision you write yourself, coins and hazards |
| [A top-down JRPG](docs/21-tutorial-jrpg.md) | A town map, NPCs spawned from map objects, four-way animation, a text box, a follower |
For per-function reference, build the Doxygen output with `doxygen Doxyfile`; it is a CI
gate, so an undocumented symbol fails the build.

View File

@@ -70,7 +70,7 @@ hit it, and an entry in `TODO.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) |
| 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](16-text-and-fonts.md) |
| A text cache | Every `akgl_text_rendertextat` rasterizes, uploads, blits and destroys | [16](17-text-and-fonts.md) |
## Who owns which documentation
@@ -86,12 +86,12 @@ caller actually write — and links out for the rest.
| `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 17](17-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 16](16-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 18](18-utilities.md) |
| 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 17](17-audio.md) is the one audio subsystem that is
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
@@ -122,5 +122,5 @@ can read that return value. After that, [Chapter 3](03-getting-started.md) gets
the screen, and the subsystem chapters can be read in any order.
If you would rather start by building something, the two tutorials —
[Chapter 19](19-tutorial-sidescroller.md) and [Chapter 20](20-tutorial-jrpg.md) — are
[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.

View File

@@ -243,7 +243,7 @@ starts at 256, so `exit(AKGL_ERR_SDL)` is a wait status of 0 and the shell sees
**`akgl_text_unloadallfonts()` before `SDL_Quit()`.** `SDL_Quit` destroys the property
registry the fonts live in and takes the last reference to every one of them with it. This
program loads no fonts, and the call is in the shutdown anyway because that is where it
belongs the moment one is loaded — [Chapter 16](16-text-and-fonts.md).
belongs the moment one is loaded — [Chapter 17](17-text-and-fonts.md).
**Sixty `akgl_game_update` calls log `Low FPS! 0` sixty times.** `akgl_game.fps` is a
completed-second average, so it reads 0 for the first second of the process, which is under
@@ -285,4 +285,4 @@ assertions in `tests/` work.
[Chapter 5](05-the-heap.md) for the pools you just claimed a string from,
[Chapter 7](07-the-game-and-the-frame.md) for what `akgl_game_update` actually does, or
[Chapter 19](19-tutorial-sidescroller.md) to start building a game.
[Chapter 20](20-tutorial-sidescroller.md) to start building a game.

View File

@@ -97,7 +97,7 @@ Statuses raised by the libraries underneath also reach you unchanged. `aksl_fope
`akgl_error_init` can raise libakerror's `AKERR_STATUS_RANGE_OVERLAP`,
`AKERR_STATUS_RANGE_FULL` or `AKERR_STATUS_NAME_FULL`.
[Chapter 21](21-appendix-limits.md) has the per-function cross-reference.
[Chapter 22](22-appendix-limits.md) has the per-function cross-reference.
## Table 3 — the exit status trap
@@ -264,6 +264,6 @@ Four things in there are libakgl-specific and worth naming:
release.
- [Chapter 6](06-the-registry.md) — the name lookups behind `AKERR_KEY` and
`AKERR_NULLPOINTER`.
- [Chapter 21](21-appendix-limits.md) — which function raises what.
- [Chapter 22](22-appendix-limits.md) — which function raises what.
- `deps/libakerror/README.md` — the protocol itself, and the exit-status discussion.
- The generated Doxygen reference — every function's own `@throws` list.

View File

@@ -344,4 +344,4 @@ all visible in `src/heap.c`.
- [Chapter 4](04-errors.md) — `AKGL_ERR_HEAP` in the status tables.
- [Chapter 6](06-the-registry.md) — the registries the releases clear entries from.
- [Chapter 21](21-appendix-limits.md) — every `AKGL_MAX_*` in one place.
- [Chapter 22](22-appendix-limits.md) — every `AKGL_MAX_*` in one place.

View File

@@ -224,5 +224,5 @@ registry's.
- [Chapter 7](07-the-game-and-the-frame.md) — where in startup the registries are created,
and where configuration has to be in place by.
- [Chapter 14](14-physics.md) — choosing a backend, and what the `physics.*` properties do.
- [Chapter 21](21-appendix-limits.md) — the configuration table again, alongside every
- [Chapter 22](22-appendix-limits.md) — the configuration table again, alongside every
`AKGL_MAX_*`.

View File

@@ -367,4 +367,4 @@ error-reporting path. See [Chapter 4](04-errors.md).
what `draw_world` does with the layers.
- [Chapter 14](14-physics.md) — what `simulate` does with the `dt` this chapter does not
pace.
- [Chapter 21](21-appendix-limits.md) — `AKGL_GAME_*` and the rest of the constants.
- [Chapter 22](22-appendix-limits.md) — `AKGL_GAME_*` and the rest of the constants.

View File

@@ -326,7 +326,7 @@ colour it found, which is also what makes it testable without a world.
- [Chapter 08](08-rendering.md) — the backend these all take, and the frame the
calls sit inside.
- [Chapter 16](16-text-and-fonts.md) — text, which is textures rather than
- [Chapter 17](17-text-and-fonts.md) — text, which is textures rather than
primitives.
- [Chapter 05](05-the-heap.md) — the pools, and why `copy_region` returning owned
memory is the exception rather than the rule.

View File

@@ -295,7 +295,7 @@ The `akgl_actor_cmhf_*` functions ("control map handler function") are what
`akgl_controller_default` binds the arrow keys and the D-pad to, and what a game's
own `akgl_Control` bindings can point at. They take the control map's **target
actor**, not a global "player", so they work for any number of locally controlled
actors. See [Chapter 15](15-input.md).
actors. See [Chapter 16](16-input.md).
| Handler | Sets | Clears first | Writes |
|---|---|---|---|
@@ -441,7 +441,7 @@ for `akgl_Actor`.
- [Chapter 11](11-characters.md) — the template, and the state-to-sprite map.
- [Chapter 14](14-physics.md) — what happens to `t`, `e`, `v` and `a` each step.
- [Chapter 15](15-input.md) — control maps, and binding the handlers above.
- [Chapter 16](16-input.md) — control maps, and binding the handlers above.
- [Chapter 08](08-rendering.md) — where `renderfunc` gets called from.
- [Chapter 04](04-errors.md) — `AKGL_ERR_LOGICINTERRUPT` and the rest of the
status tables.

View File

@@ -37,7 +37,7 @@ why the library's own map, `akgl_default_gamemap`, is a file-scope object in
The bounds are fixed at compile time, so the size is fixed too. Every field is present
whether the map uses it or not: a 20x15 map with one tileset and two layers costs the same
25.2 MiB as a 511x511 one. Raising any of the constants below raises this number and
changes the ABI — see [Chapter 21](21-appendix-limits.md).
changes the ABI — see [Chapter 22](22-appendix-limits.md).
## Limits

View File

@@ -333,8 +333,8 @@ contract is "nothing collides", which is an answer. The arcade one means "nobody
written this yet", and a caller who reaches it should find out.
Until it exists, collision belongs in your `movementlogicfunc`, as in the example above.
The sidescroller tutorial in [Chapter 19](19-tutorial-sidescroller.md) does exactly that and
says why. `akgl_collide_*` in [Chapter 18](18-utilities.md) gives you the rectangle tests.
The sidescroller tutorial in [Chapter 20](20-tutorial-sidescroller.md) does exactly that and
says why. `akgl_collide_*` in [Chapter 19](19-utilities.md) gives you the rectangle tests.
### Four gaps in the feel

View File

@@ -1,4 +1,4 @@
# 15. Input
# 16. Input
libakgl does not have an input system so much as a *binding* system. SDL3 owns events,
keycodes, keymods, gamepad enumeration and text composition, and it documents all of them
@@ -138,7 +138,7 @@ With XInput2 the events carry the physical slave device's `sourceid` and
`SDL_GetKeyboards()[0]` may be the master. Binding `SDL_GetKeyboards()[0]` therefore gives
you a map that matches nothing at all, and controls that silently do nothing.
That is not hypothetical — the sidescroller in [Chapter 19](19-tutorial-sidescroller.md)
That is not hypothetical — the sidescroller in [Chapter 20](20-tutorial-sidescroller.md)
shipped exactly that bug, and its smoke test passed anyway because the test called the
handlers instead of dispatching events.

View File

@@ -1,4 +1,4 @@
# 16. Text and fonts
# 17. Text and fonts
**SDL3_ttf owns fonts.** It opens the file, rasterizes the glyphs, and computes the metrics;
its API and its behaviour are documented in the

View File

@@ -1,4 +1,4 @@
# 17. Audio
# 18. Audio
**There are two audio subsystems in libakgl and they have nothing to do with each other.**
Not two layers, not two APIs over one engine — two unrelated things that happen to make

View File

@@ -1,4 +1,4 @@
# 18. Utilities
# 19. Utilities
`util.h`, `json_helpers.h` and `staticstring.h` hold the pieces that belong to no subsystem:
rectangle overlap, asset path resolution, typed JSON accessors, and the pooled string type

View File

@@ -1,4 +1,4 @@
# 19. Tutorial: a 2D sidescroller
# 20. Tutorial: a 2D sidescroller
A complete game: a Tiled level, a player who runs and jumps, platforms that hold him up,
four coins to collect, a blob that patrols and a moth that flies. It is about nine hundred
@@ -94,7 +94,7 @@ libakgl has exactly one startup order that works and it is written down at the t
`akgl_game.name`, `.version` and `.uri` are required and have no defaults —
`akgl_game_init` refuses to run without all three, because the window title, SDL's
application metadata and the savegame compatibility check are built from them. `aksl_strncpy`
rather than `strncpy`, per [Chapter 18](18-utilities.md) and `AGENTS.md`: these are
rather than `strncpy`, per [Chapter 19](19-utilities.md) and `AGENTS.md`: these are
fixed-width fields that end up as registry keys.
```c excerpt=examples/sidescroller/main.c
@@ -469,7 +469,7 @@ actually arrives.
The `_on` handlers are the library's unchanged. `akgl_actor_cmhf_right_on` clears
`FACE_ALL | MOVING_ALL`, sets `MOVING_RIGHT | FACE_RIGHT` and signs `ax` from the character
— exactly right. Only the release half needed replacing. [Chapter 15](15-input.md) covers
— exactly right. Only the release half needed replacing. [Chapter 16](16-input.md) covers
building a control map; the whole of this game's is six bindings, three keyboard and three
gamepad.
@@ -603,7 +603,7 @@ memory at runtime is one fewer failure mode. See [Chapter 5](05-the-heap.md).
## Picking things up
Collecting a coin is a rectangle test from [Chapter 18](18-utilities.md) and a pool release:
Collecting a coin is a rectangle test from [Chapter 19](19-utilities.md) and a pool release:
```c excerpt=examples/sidescroller/player.c
PASS(errctx, hitbox(ss_game.coins[i], 8.0f, &other));
@@ -693,7 +693,7 @@ program's `CLEANUP` block, which is reached whether startup succeeded or not —
**There is no `akgl_game_shutdown`.** Teardown belongs to the application, and the order
matters in one place: `akgl_text_unloadallfonts` has to run before `TTF_Quit` or `SDL_Quit`,
because those destroy the fonts underneath the registry that still points at them
([Chapter 16](16-text-and-fonts.md)). This game loads no fonts and calls it anyway, because
([Chapter 17](17-text-and-fonts.md)). This game loads no fonts and calls it anyway, because
the ordering is the thing worth copying.
What this does **not** do is unwind the sprite, spritesheet and character pools. They are
@@ -806,7 +806,7 @@ rather than a score — an assertion on the numbers would be a test of the sched
- [Chapter 12](12-actors.md) — the state mask, the six hooks, and the control handlers.
- [Chapter 13](13-tilemaps.md) — what the map loader accepts, and the three extensions to
Tiled this level uses.
- [Chapter 15](15-input.md) — control maps, and why a binding that never fires is usually
- [Chapter 16](16-input.md) — control maps, and why a binding that never fires is usually
the wrong device id.
- [Chapter 20](20-tutorial-jrpg.md) — the same library from the other end: a top-down game
- [Chapter 21](21-tutorial-jrpg.md) — the same library from the other end: a top-down game
with no gravity, where the content pipeline is the interesting part.

View File

@@ -1,4 +1,4 @@
# 20. Tutorial: a top-down JRPG
# 21. Tutorial: a top-down JRPG
A town, three people standing in it, a party member who follows you around, and
a box that tells you what the elder thinks of the road north. About six hundred
@@ -7,7 +7,7 @@ lines of C, in `examples/jrpg/`, built by `all` and run headless by `ctest`.
This is the **content-pipeline** tutorial. Its subject is the road from a
directory of JSON and PNG to a world with people in it: twenty-four sprites, three
characters, one Tiled map that spawns its own actors, four-way per-facing
animation, and text on top. [Chapter 19](19-tutorial-sidescroller.md) is the
animation, and text on top. [Chapter 20](20-tutorial-sidescroller.md) is the
physics one — gravity, a jump, a coin — and the two are deliberately
complementary. If you want to know how `ey` accumulates, read that one.
@@ -130,7 +130,7 @@ There is no diagonal row and there does not need to be one. The default arrow
bindings clear *every* facing and movement bit before setting their own
(`akgl_actor_cmhf_left_on` and its five siblings all do), so holding two
directions moves in whichever was pressed last rather than diagonally. That is
documented in [Chapter 15](15-input.md), and it is why an eight-way character
documented in [Chapter 16](16-input.md), and it is why an eight-way character
sheet would be wasted on the default bindings.
## The map spawns the actors
@@ -424,7 +424,7 @@ and a decision about which reading is canonical.
Text in libakgl is immediate mode. Every `akgl_text_rendertextat` call
rasterizes the string through SDL_ttf, uploads it to a texture, blits it, and
destroys the texture and the surface again — there is no glyph atlas and no text
object to keep. [Chapter 16](16-text-and-fonts.md) says plainly that this is
object to keep. [Chapter 17](17-text-and-fonts.md) says plainly that this is
wrong for a page of static prose redrawn sixty times a second, and it is exactly
right for a line of dialogue that is on screen only while somebody is talking.
@@ -565,7 +565,7 @@ the third thing here is a deliberate omission:
- **`akgl_text_unloadallfonts` must run before `SDL_Quit`.** Fonts live in an SDL
property registry, and `SDL_Quit` destroys the registry — taking the last
reference to every font still in it, with no way left to close them.
[Chapter 16](16-text-and-fonts.md) covers the ordering.
[Chapter 17](17-text-and-fonts.md) covers the ordering.
- **`akgl_tilemap_release` is not called**, and that is the fourth workaround.
Its layer loop destroys `tilesets[i].texture` rather than `layers[i].texture`,
so it double-frees every tileset texture and never frees an image layer's, and
@@ -663,13 +663,13 @@ failing a frame. [Chapter 4](04-errors.md) covers installing your own
## Where to look next
- [Chapter 19](19-tutorial-sidescroller.md) — the same shape of program with
- [Chapter 20](20-tutorial-sidescroller.md) — the same shape of program with
gravity, a jump and platforms, which is where the physics model earns its keep.
- [Chapter 13](13-tilemaps.md) — the map format, the three libakgl extensions to
Tiled, and the four constraints the loader really enforces.
- [Chapter 11](11-characters.md) and [Chapter 12](12-actors.md) — the template
and the instance, and why the state word is the whole key.
- [Chapter 15](15-input.md) — control maps, the default bindings, and the
- [Chapter 16](16-input.md) — control maps, the default bindings, and the
keystroke ring this game does not use.
- [Chapter 21](21-appendix-limits.md) — every compile-time ceiling in one place,
- [Chapter 22](22-appendix-limits.md) — every compile-time ceiling in one place,
including the 64-actor pool this town uses four of.

View File

@@ -1,4 +1,4 @@
# 21. Appendix: limits, statuses and properties
# 22. Appendix: limits, statuses and properties
Three reference tables that no single chapter owns: which function raises which status,
every compile-time bound, and every configuration property the library reads.

View File

@@ -27,13 +27,13 @@ per-function reference, build the Doxygen output with `doxygen Doxyfile`.
| **[12. Actors](12-actors.md)** | The state bitmask, the six behaviour hooks, parents and children, layers |
| **[13. Tilemaps](13-tilemaps.md)** | Tiled TMJ, libakgl's three extensions, and the limits that bind a map |
| **[14. Physics](14-physics.md)** | Thrust, environment and velocity; the `null` and `arcade` backends; what is not implemented |
| **[15. Input](15-input.md)** | Control maps, push-not-poll dispatch, the keystroke ring, gamepads |
| **[16. Text and fonts](16-text-and-fonts.md)** | Loading, drawing, measuring, and the teardown order that matters |
| **[17. Audio](17-audio.md)** | The three-voice synthesizer, and the separate background-music path |
| **[18. Utilities](18-utilities.md)** | Collision helpers, path resolution, the JSON accessors, static strings |
| **[19. Tutorial: a 2D sidescroller](19-tutorial-sidescroller.md)** | Gravity, a jump, collision you write yourself, coins and hazards |
| **[20. Tutorial: a top-down JRPG](20-tutorial-jrpg.md)** | A town map, NPCs spawned from map objects, four-way animation, a text box, a follower |
| **[21. Appendix](21-appendix-limits.md)** | Every limit, every status, every configuration property |
| **[15. Input](16-input.md)** | Control maps, push-not-poll dispatch, the keystroke ring, gamepads |
| **[16. Text and fonts](17-text-and-fonts.md)** | Loading, drawing, measuring, and the teardown order that matters |
| **[17. Audio](18-audio.md)** | The three-voice synthesizer, and the separate background-music path |
| **[18. Utilities](19-utilities.md)** | Collision helpers, path resolution, the JSON accessors, static strings |
| **[19. Tutorial: a 2D sidescroller](20-tutorial-sidescroller.md)** | Gravity, a jump, collision you write yourself, coins and hazards |
| **[20. Tutorial: a top-down JRPG](21-tutorial-jrpg.md)** | A town map, NPCs spawned from map objects, four-way animation, a text box, a follower |
| **[21. Appendix](22-appendix-limits.md)** | Every limit, every status, every configuration property |
Both tutorials are complete programs under [`examples/`](../examples). They build with the
library and run headless in CI, and the chapters quote them rather than restating them — so